com.yy.androidlib.util.prettytime.PrettyTime Maven / Gradle / Ivy
/*
* Copyright 2012 Lincoln Baxter, III
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.yy.androidlib.util.prettytime;
import com.yy.androidlib.util.prettytime.impl.DurationImpl;
import com.yy.androidlib.util.prettytime.impl.ResourcesTimeFormat;
import com.yy.androidlib.util.prettytime.impl.ResourcesTimeUnit;
import com.yy.androidlib.util.prettytime.units.*;
import java.util.*;
/**
* A utility for creating social-networking style timestamps. (e.g. "just now", "moments ago", "3 days ago",
* "within 2 months")
*
* Usage:
*
*
* PrettyTime t = new PrettyTime();
* String timestamp = t.format(new Date());
* //result: moments from now
*
*
*
* @author localUnits = getUnits();
DurationImpl result = new DurationImpl();
for (int i = 0; i < localUnits.size(); i++) {
TimeUnit unit = localUnits.get(i);
long millisPerUnit = Math.abs(unit.getMillisPerUnit());
long quantity = Math.abs(unit.getMaxQuantity());
boolean isLastUnit = (i == localUnits.size() - 1);
if ((0 == quantity) && !isLastUnit) {
quantity = localUnits.get(i + 1).getMillisPerUnit() / unit.getMillisPerUnit();
}
// does our unit encompass the time duration?
if ((millisPerUnit * quantity > absoluteDifference) || isLastUnit) {
result.setUnit(unit);
if (millisPerUnit > absoluteDifference) {
// we are rounding up: get 1 or -1 for past or future
result.setQuantity(getSign(difference));
result.setDelta(0);
} else {
result.setQuantity(difference / millisPerUnit);
result.setDelta(difference - result.getQuantity() * millisPerUnit);
}
break;
}
}
return result;
}
private long getSign(final long difference) {
if (0 > difference) {
return -1;
} else {
return 1;
}
}
/**
* Calculate to the precision of the smallest provided {@link TimeUnit}, the exact duration represented by the
* difference between the reference timestamp, and {@code then}
*
* Note: Precision may be lost if no supplied {@link TimeUnit} is granular enough to represent one millisecond
*
* @param then The date to be compared against the reference timestamp, or now if no reference timestamp was
* provided
* @return A sorted {@link java.util.List} of {@link Duration} objects, from largest to smallest. Each element in the list
* represents the approximate duration (number of times) that {@link TimeUnit} to fit into the previous
* element's delta. The first element is the largest {@link TimeUnit} to fit within the total difference
* between compared dates.
*/
public List calculatePreciseDuration(final Date then) {
if (then == null) {
throw new IllegalArgumentException("Date to calculate must not be null.");
}
if (null == reference) {
reference = new Date();
}
List result = new ArrayList();
long difference = then.getTime() - reference.getTime();
Duration duration = calculateDuration(difference);
result.add(duration);
while (0 != duration.getDelta()) {
duration = calculateDuration(duration.getDelta());
result.add(duration);
}
return result;
}
/**
* Format the given {@link java.util.Date} object. This method applies the {@code PrettyTime.approximateDuration(date)} method
* to perform its calculation. If {@code then} is null, it will default to {@code new Date()}; also decorate for
* past/future tense.
*
* @param duration the {@link java.util.Date} to be formatted
* @return A formatted string representing {@code then}
*/
public String format(Date then) {
if (then == null) {
throw new IllegalArgumentException("Date to format must not be null.");
}
Duration d = approximateDuration(then);
return format(d);
}
/**
* Format the given {@link java.util.Calendar} object. This method applies the {@code PrettyTime.approximateDuration(date)} method
* to perform its calculation. If {@code then} is null, it will default to {@code new Date()}; also decorate for
* past/future tense.
*
* @param duration the {@link java.util.Calendar} whose date is to be formatted
* @return A formatted string representing {@code then}
*/
public String format(Calendar then) {
if (then == null) {
throw new IllegalArgumentException("Provided Calendar must not be null.");
}
return format(then.getTime());
}
/**
* Format the given {@link java.util.Date} object. This method applies the {@code PrettyTime.approximateDuration(date)} method
* to perform its calculation. If {@code then} is null, it will default to {@code new Date()}; also decorate for
* past/future tense. Rounding rules are ignored.
*
* @param duration the {@link java.util.Date} to be formatted
* @return A formatted string representing {@code then}
*/
public String formatUnrounded(Date then) {
if (then == null) {
throw new IllegalArgumentException("Date to format must not be null.");
}
Duration d = approximateDuration(then);
return formatUnrounded(d);
}
/**
* Format the given {@link Duration} object, using the {@link TimeFormat} specified by the {@link TimeUnit} contained
* within; also decorate for past/future tense.
*
* @param duration the {@link Duration} to be formatted
* @return A formatted string representing {@code duration}
*/
public String format(final Duration duration) {
if (duration == null) {
throw new IllegalArgumentException("Duration to format must not be null.");
}
TimeFormat format = getFormat(duration.getUnit());
String time = format.format(duration);
return format.decorate(duration, time);
}
/**
* Format the given {@link Duration} object, using the {@link TimeFormat} specified by the {@link TimeUnit} contained
* within; also decorate for past/future tense. Rounding rules are ignored.
*
* @param duration the {@link Duration} to be formatted
* @return A formatted string representing {@code duration}
*/
public String formatUnrounded(Duration duration) {
if (duration == null) {
throw new IllegalArgumentException("Duration to format must not be null.");
}
TimeFormat format = getFormat(duration.getUnit());
String time = format.formatUnrounded(duration);
return format.decorateUnrounded(duration, time);
}
/**
* Format the given {@link Duration} objects, using the {@link TimeFormat} specified by the {@link TimeUnit}
* contained within. Rounds only the last {@link Duration} object.
*
* @param durations the {@link Duration}s to be formatted
* @return A list of formatted strings representing {@code durations}
*/
public String format(final List durations) {
if (durations == null) {
throw new IllegalArgumentException("Duration list must not be null.");
}
StringBuilder builder = new StringBuilder();
Duration duration = null;
TimeFormat format = null;
for (int i = 0; i < durations.size(); i++) {
duration = durations.get(i);
format = getFormat(duration.getUnit());
boolean isLast = (i == durations.size() - 1);
if (!isLast) {
builder.append(format.formatUnrounded(duration));
} else {
builder.append(format.format(duration));
}
}
if (format != null) {
return format.decorateUnrounded(duration, builder.toString());
} else {
return "";
}
}
/**
* Given a date, returns a non-relative format string for the
* approximate duration of the difference between the date and now.
*
* @param date the date to be formatted
* @return A formatted string of the approximate duration
*/
public String formatApproximateDuration(Date date) {
Duration duration = approximateDuration(date);
return formatDuration(duration);
}
/**
* Given a duration, returns a non-relative format string.
*
* @param duration the duration to be formatted
* @return A formatted string of the duration
*/
public String formatDuration(Duration duration) {
TimeFormat timeFormat = getFormat(duration.getUnit());
return timeFormat.format(duration);
}
/**
* Get the registered {@link TimeFormat} for the given {@link TimeUnit} or null if none exists.
*/
public TimeFormat getFormat(TimeUnit unit) {
if (unit == null) {
throw new IllegalArgumentException("Time unit must not be null.");
}
if (units.get(unit) != null) {
return units.get(unit);
}
return null;
}
/**
* Get the current reference timestamp.
*
* See {@code PrettyTime.setReference(Date timestamp)}
*
* @return
*/
public Date getReference() {
return reference;
}
/**
* Set the reference timestamp.
*
* If the Date formatted is before the reference timestamp, the format command will produce a String that is in the
* past tense. If the Date formatted is after the reference timestamp, the format command will produce a string that
* is in the future tense.
*/
public PrettyTime setReference(final Date timestamp) {
reference = timestamp;
return this;
}
/**
* Get a {@link java.util.List} of the current configured {@link TimeUnit} instances in calculations.
*
* @return
*/
public List getUnits() {
if (mCachedUnits == null) {
List result = new ArrayList(units.keySet());
Collections.sort(result, new TimeUnitComparator());
mCachedUnits = Collections.unmodifiableList(result);
}
return mCachedUnits;
}
/**
* Get the registered {@link TimeUnit} for the given {@link TimeUnit} type or null if none exists.
*
* @return
*/
@SuppressWarnings("unchecked")
public UNIT getUnit(final Class unitType) {
if (unitType == null) {
throw new IllegalArgumentException("Unit type to get must not be null.");
}
for (TimeUnit unit : units.keySet()) {
if (unitType.isAssignableFrom(unit.getClass())) {
return (UNIT) unit;
}
}
return null;
}
/**
* Register the given {@link TimeUnit} and corresponding {@link TimeFormat} instance to be used in calculations. If
* an entry already exists for the given {@link TimeUnit}, its format will be overwritten with the given
* {@link TimeFormat}.
*/
public PrettyTime registerUnit(final TimeUnit unit, TimeFormat format) {
if (unit == null) {
throw new IllegalArgumentException("Unit to register must not be null.");
}
if (format == null) {
throw new IllegalArgumentException("Format to register must not be null.");
}
mCachedUnits = null;
units.put(unit, format);
if (unit instanceof LocaleAware) {
((LocaleAware>) unit).setLocale(locale);
}
if (format instanceof LocaleAware) {
((LocaleAware>) format).setLocale(locale);
}
return this;
}
/**
* Removes the mapping for the given {@link TimeUnit} type. This effectively de-registers the unit so it will not be
* used in formatting. Returns the {@link TimeFormat} that was registered for the given {@link TimeUnit} type, or
* null if no unit of the given type was registered.
*/
public TimeFormat removeUnit(final Class unitType) {
if (unitType == null) {
throw new IllegalArgumentException("Unit type to remove must not be null.");
}
for (TimeUnit unit : units.keySet()) {
if (unitType.isAssignableFrom(unit.getClass())) {
mCachedUnits = null;
return units.remove(unit);
}
}
return null;
}
/**
* Removes the mapping for the given {@link TimeUnit}. This effectively de-registers the unit so it will not be used
* in formatting. Returns the {@link TimeFormat} that was registered for the given {@link TimeUnit}, or null if no
* such unit was registered.
*/
public TimeFormat removeUnit(final TimeUnit unit) {
if (unit == null) {
throw new IllegalArgumentException("Unit to remove must not be null.");
}
mCachedUnits = null;
return units.remove(unit);
}
@Override
public String toString() {
return "PrettyTime [reference=" + reference + "]";
}
/**
* Remove all registered {@link TimeUnit} instances.
*
* @return The removed {@link TimeUnit} instances.
*/
public List clearUnits() {
List result = getUnits();
mCachedUnits = null;
units.clear();
return result;
}
public Locale getLocale() {
return locale;
}
/**
* Set the the {@link Locale} for this {@link PrettyTime} object. This may be an expensive operation, since this
* operation calls {@link TimeUnit#setLocale(Locale)} for each {@link TimeUnit} in {@link #getUnits()}.
*/
public PrettyTime setLocale(final Locale locale) {
this.locale = locale;
for (TimeUnit unit : units.keySet()) {
if (unit instanceof LocaleAware) {
((LocaleAware>) unit).setLocale(locale);
}
}
for (TimeFormat format : units.values()) {
if (format instanceof LocaleAware) {
((LocaleAware>) format).setLocale(locale);
}
}
return this;
}
}