/src/main/java/org/ocpsoft/prettytime/PrettyTime.java
Java | 422 lines | 240 code | 39 blank | 143 comment | 29 complexity | 49855e65606f1514963e3bb0650066d0 MD5 | raw file
Possible License(s): Apache-2.0
- /*
- * Copyright 2012 <a href="mailto:lincolnbaxter@gmail.com">Lincoln Baxter, III</a>
- *
- * 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 org.ocpsoft.prettytime;
- import java.util.ArrayList;
- import java.util.Collections;
- import java.util.Date;
- import java.util.LinkedHashMap;
- import java.util.List;
- import java.util.Locale;
- import java.util.Map;
- import org.ocpsoft.prettytime.impl.DurationImpl;
- import org.ocpsoft.prettytime.impl.ResourcesTimeFormat;
- import org.ocpsoft.prettytime.impl.ResourcesTimeUnit;
- import org.ocpsoft.prettytime.units.Century;
- import org.ocpsoft.prettytime.units.Day;
- import org.ocpsoft.prettytime.units.Decade;
- import org.ocpsoft.prettytime.units.Hour;
- import org.ocpsoft.prettytime.units.JustNow;
- import org.ocpsoft.prettytime.units.Millennium;
- import org.ocpsoft.prettytime.units.Millisecond;
- import org.ocpsoft.prettytime.units.Minute;
- import org.ocpsoft.prettytime.units.Month;
- import org.ocpsoft.prettytime.units.Second;
- import org.ocpsoft.prettytime.units.TimeUnitComparator;
- import org.ocpsoft.prettytime.units.Week;
- import org.ocpsoft.prettytime.units.Year;
- /**
- * A utility for creating social-networking style timestamps. (e.g. "just now", "moments ago", "3 days ago",
- * "within 2 months")
- * <p>
- * <b>Usage:</b>
- * <p>
- * <code>
- * PrettyTime t = new PrettyTime();<br/>
- * String timestamp = t.format(new Date());<br/>
- * //result: moments from now
- * <p>
- * </code>
- *
- * @author <a href="mailto:lincolnbaxter@gmail.com>Lincoln Baxter, III</a>
- */
- public class PrettyTime implements LocaleAware
- {
- private volatile Date reference;
- private volatile Locale locale = Locale.getDefault();
- private volatile Map<TimeUnit, TimeFormat> formatMap = new LinkedHashMap<TimeUnit, TimeFormat>();
- /**
- * Default constructor
- */
- public PrettyTime()
- {
- initTimeUnits();
- }
- /**
- * Accept a {@link Date} timestamp to represent the point of reference for comparison. This may be changed by the
- * user, after construction.
- * <p>
- * See {@code PrettyTime.setReference(Date timestamp)}.
- *
- * @param reference
- */
- public PrettyTime(final Date reference)
- {
- this();
- setReference(reference);
- }
- /**
- * Construct a new instance using the given {@link Locale} instead of the system default.
- */
- public PrettyTime(final Locale locale)
- {
- this.locale = locale;
- initTimeUnits();
- }
- /**
- * Accept a {@link Date} timestamp to represent the point of reference for comparison. This may be changed by the
- * user, after construction. Use the given {@link Locale} instead of the system default.
- * <p>
- * See {@code PrettyTime.setReference(Date timestamp)}.
- */
- public PrettyTime(final Date reference, final Locale locale)
- {
- this(locale);
- setReference(reference);
- }
- /**
- * Calculate the approximate duration between the referenceDate and date
- */
- public Duration approximateDuration(final Date then)
- {
- Date ref = reference;
- if (null == ref)
- {
- ref = new Date();
- }
- long difference = then.getTime() - ref.getTime();
- return calculateDuration(difference);
- }
- private void initTimeUnits()
- {
- addUnit(new JustNow());
- addUnit(new Millisecond());
- addUnit(new Second());
- addUnit(new Minute());
- addUnit(new Hour());
- addUnit(new Day());
- addUnit(new Week());
- addUnit(new Month());
- addUnit(new Year());
- addUnit(new Decade());
- addUnit(new Century());
- addUnit(new Millennium());
- }
- private void addUnit(ResourcesTimeUnit unit)
- {
- formatMap.put(unit, new ResourcesTimeFormat(unit, locale));
- }
- private Duration calculateDuration(final long difference)
- {
- long absoluteDifference = Math.abs(difference);
- // Required for thread-safety
- List<TimeUnit> units = new ArrayList<TimeUnit>(getUnits().size());
- units.addAll(getUnits());
- DurationImpl result = new DurationImpl();
- for (int i = 0; i < units.size(); i++)
- {
- TimeUnit unit = units.get(i);
- long millisPerUnit = Math.abs(unit.getMillisPerUnit());
- long quantity = Math.abs(unit.getMaxQuantity());
- boolean isLastUnit = (i == units.size() - 1);
- if ((0 == quantity) && !isLastUnit)
- {
- quantity = units.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, absoluteDifference));
- }
- else
- {
- result.setQuantity(difference / millisPerUnit);
- }
- result.setDelta(difference - result.getQuantity() * millisPerUnit);
- break;
- }
- }
- return result;
- }
- private long getSign(final long difference, final long absoluteDifference)
- {
- 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}
- * <p>
- * <b>Note</b>: 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 <i>now</i> if no reference timestamp was
- * provided
- * @return A sorted {@link 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<Duration> calculatePreciseDuration(final Date then)
- {
- if (null == reference)
- {
- reference = new Date();
- }
- List<Duration> result = new ArrayList<Duration>();
- 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 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 Date} to be formatted
- * @return A formatted string representing {@code then}
- */
- public String format(Date then)
- {
- if (then == null)
- {
- then = new Date();
- }
- Duration d = approximateDuration(then);
- return format(d);
- }
- /**
- * Format the given {@link 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 Date} to be formatted
- * @return A formatted string representing {@code then}
- */
- public String formatUnrounded(Date then)
- {
- if (then == null)
- {
- then = new Date();
- }
- 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)
- {
- 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)
- {
- 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<Duration> durations)
- {
- String result = null;
- if (durations != 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));
- builder.append(" ");
- }
- else {
- builder.append(format.format(duration));
- }
- }
- result = format.decorateUnrounded(duration, builder.toString());
- }
- return result;
- }
- /**
- * Get the registered {@link TimeFormat} for the given {@link TimeUnit} or null if none exists.
- */
- public TimeFormat getFormat(TimeUnit unit)
- {
- if (formatMap.get(unit) != null)
- {
- return formatMap.get(unit);
- }
- return null;
- }
- /**
- * Get the current reference timestamp.
- * <p>
- * See {@code PrettyTime.setReference(Date timestamp)}
- *
- * @return
- */
- public Date getReference()
- {
- return reference;
- }
- /**
- * Set the reference timestamp.
- * <p>
- * 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 void setReference(final Date timestamp)
- {
- reference = timestamp;
- }
- /**
- * Get a {@link List} of the current configured {@link TimeUnit} instances in calculations.
- *
- * @return
- */
- public List<TimeUnit> getUnits()
- {
- List<TimeUnit> units = new ArrayList<TimeUnit>(formatMap.keySet());
- Collections.sort(units, new TimeUnitComparator());
- return Collections.unmodifiableList(units);
- }
- /**
- * 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 void registerUnit(final TimeUnit unit, TimeFormat format)
- {
- formatMap.put(unit, format);
- }
- /**
- * Get the currently configured {@link Locale} for this {@link PrettyTime} object.
- */
- 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()}.
- */
- @Override
- public void setLocale(final Locale locale)
- {
- this.locale = locale;
- for (TimeFormat format : formatMap.values()) {
- if (format instanceof LocaleAware)
- ((LocaleAware) format).setLocale(locale);
- }
- }
- @Override
- public String toString()
- {
- return "PrettyTime [reference=" + reference + ", locale=" + locale + "]";
- }
- /**
- * Remove all registered {@link TimeUnit} instances.
- */
- public void clearUnits()
- {
- formatMap.clear();
- }
- }