PageRenderTime 93ms CodeModel.GetById 28ms RepoModel.GetById 7ms app.codeStats 0ms

/src/main/java/org/ocpsoft/prettytime/PrettyTime.java

http://github.com/ocpsoft/prettytime
Java | 422 lines | 240 code | 39 blank | 143 comment | 29 complexity | 49855e65606f1514963e3bb0650066d0 MD5 | raw file
Possible License(s): Apache-2.0
  1. /*
  2. * Copyright 2012 <a href="mailto:lincolnbaxter@gmail.com">Lincoln Baxter, III</a>
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package org.ocpsoft.prettytime;
  17. import java.util.ArrayList;
  18. import java.util.Collections;
  19. import java.util.Date;
  20. import java.util.LinkedHashMap;
  21. import java.util.List;
  22. import java.util.Locale;
  23. import java.util.Map;
  24. import org.ocpsoft.prettytime.impl.DurationImpl;
  25. import org.ocpsoft.prettytime.impl.ResourcesTimeFormat;
  26. import org.ocpsoft.prettytime.impl.ResourcesTimeUnit;
  27. import org.ocpsoft.prettytime.units.Century;
  28. import org.ocpsoft.prettytime.units.Day;
  29. import org.ocpsoft.prettytime.units.Decade;
  30. import org.ocpsoft.prettytime.units.Hour;
  31. import org.ocpsoft.prettytime.units.JustNow;
  32. import org.ocpsoft.prettytime.units.Millennium;
  33. import org.ocpsoft.prettytime.units.Millisecond;
  34. import org.ocpsoft.prettytime.units.Minute;
  35. import org.ocpsoft.prettytime.units.Month;
  36. import org.ocpsoft.prettytime.units.Second;
  37. import org.ocpsoft.prettytime.units.TimeUnitComparator;
  38. import org.ocpsoft.prettytime.units.Week;
  39. import org.ocpsoft.prettytime.units.Year;
  40. /**
  41. * A utility for creating social-networking style timestamps. (e.g. "just now", "moments ago", "3 days ago",
  42. * "within 2 months")
  43. * <p>
  44. * <b>Usage:</b>
  45. * <p>
  46. * <code>
  47. * PrettyTime t = new PrettyTime();<br/>
  48. * String timestamp = t.format(new Date());<br/>
  49. * //result: moments from now
  50. * <p>
  51. * </code>
  52. *
  53. * @author <a href="mailto:lincolnbaxter@gmail.com>Lincoln Baxter, III</a>
  54. */
  55. public class PrettyTime implements LocaleAware
  56. {
  57. private volatile Date reference;
  58. private volatile Locale locale = Locale.getDefault();
  59. private volatile Map<TimeUnit, TimeFormat> formatMap = new LinkedHashMap<TimeUnit, TimeFormat>();
  60. /**
  61. * Default constructor
  62. */
  63. public PrettyTime()
  64. {
  65. initTimeUnits();
  66. }
  67. /**
  68. * Accept a {@link Date} timestamp to represent the point of reference for comparison. This may be changed by the
  69. * user, after construction.
  70. * <p>
  71. * See {@code PrettyTime.setReference(Date timestamp)}.
  72. *
  73. * @param reference
  74. */
  75. public PrettyTime(final Date reference)
  76. {
  77. this();
  78. setReference(reference);
  79. }
  80. /**
  81. * Construct a new instance using the given {@link Locale} instead of the system default.
  82. */
  83. public PrettyTime(final Locale locale)
  84. {
  85. this.locale = locale;
  86. initTimeUnits();
  87. }
  88. /**
  89. * Accept a {@link Date} timestamp to represent the point of reference for comparison. This may be changed by the
  90. * user, after construction. Use the given {@link Locale} instead of the system default.
  91. * <p>
  92. * See {@code PrettyTime.setReference(Date timestamp)}.
  93. */
  94. public PrettyTime(final Date reference, final Locale locale)
  95. {
  96. this(locale);
  97. setReference(reference);
  98. }
  99. /**
  100. * Calculate the approximate duration between the referenceDate and date
  101. */
  102. public Duration approximateDuration(final Date then)
  103. {
  104. Date ref = reference;
  105. if (null == ref)
  106. {
  107. ref = new Date();
  108. }
  109. long difference = then.getTime() - ref.getTime();
  110. return calculateDuration(difference);
  111. }
  112. private void initTimeUnits()
  113. {
  114. addUnit(new JustNow());
  115. addUnit(new Millisecond());
  116. addUnit(new Second());
  117. addUnit(new Minute());
  118. addUnit(new Hour());
  119. addUnit(new Day());
  120. addUnit(new Week());
  121. addUnit(new Month());
  122. addUnit(new Year());
  123. addUnit(new Decade());
  124. addUnit(new Century());
  125. addUnit(new Millennium());
  126. }
  127. private void addUnit(ResourcesTimeUnit unit)
  128. {
  129. formatMap.put(unit, new ResourcesTimeFormat(unit, locale));
  130. }
  131. private Duration calculateDuration(final long difference)
  132. {
  133. long absoluteDifference = Math.abs(difference);
  134. // Required for thread-safety
  135. List<TimeUnit> units = new ArrayList<TimeUnit>(getUnits().size());
  136. units.addAll(getUnits());
  137. DurationImpl result = new DurationImpl();
  138. for (int i = 0; i < units.size(); i++)
  139. {
  140. TimeUnit unit = units.get(i);
  141. long millisPerUnit = Math.abs(unit.getMillisPerUnit());
  142. long quantity = Math.abs(unit.getMaxQuantity());
  143. boolean isLastUnit = (i == units.size() - 1);
  144. if ((0 == quantity) && !isLastUnit)
  145. {
  146. quantity = units.get(i + 1).getMillisPerUnit() / unit.getMillisPerUnit();
  147. }
  148. // does our unit encompass the time duration?
  149. if ((millisPerUnit * quantity > absoluteDifference) || isLastUnit)
  150. {
  151. result.setUnit(unit);
  152. if (millisPerUnit > absoluteDifference)
  153. {
  154. // we are rounding up: get 1 or -1 for past or future
  155. result.setQuantity(getSign(difference, absoluteDifference));
  156. }
  157. else
  158. {
  159. result.setQuantity(difference / millisPerUnit);
  160. }
  161. result.setDelta(difference - result.getQuantity() * millisPerUnit);
  162. break;
  163. }
  164. }
  165. return result;
  166. }
  167. private long getSign(final long difference, final long absoluteDifference)
  168. {
  169. if (0 > difference)
  170. {
  171. return -1;
  172. }
  173. else
  174. {
  175. return 1;
  176. }
  177. }
  178. /**
  179. * Calculate to the precision of the smallest provided {@link TimeUnit}, the exact duration represented by the
  180. * difference between the reference timestamp, and {@code then}
  181. * <p>
  182. * <b>Note</b>: Precision may be lost if no supplied {@link TimeUnit} is granular enough to represent one millisecond
  183. *
  184. * @param then The date to be compared against the reference timestamp, or <i>now</i> if no reference timestamp was
  185. * provided
  186. * @return A sorted {@link List} of {@link Duration} objects, from largest to smallest. Each element in the list
  187. * represents the approximate duration (number of times) that {@link TimeUnit} to fit into the previous
  188. * element's delta. The first element is the largest {@link TimeUnit} to fit within the total difference
  189. * between compared dates.
  190. */
  191. public List<Duration> calculatePreciseDuration(final Date then)
  192. {
  193. if (null == reference)
  194. {
  195. reference = new Date();
  196. }
  197. List<Duration> result = new ArrayList<Duration>();
  198. long difference = then.getTime() - reference.getTime();
  199. Duration duration = calculateDuration(difference);
  200. result.add(duration);
  201. while (0 != duration.getDelta())
  202. {
  203. duration = calculateDuration(duration.getDelta());
  204. result.add(duration);
  205. }
  206. return result;
  207. }
  208. /**
  209. * Format the given {@link Date} object. This method applies the {@code PrettyTime.approximateDuration(date)} method
  210. * to perform its calculation. If {@code then} is null, it will default to {@code new Date()}; also decorate for
  211. * past/future tense.
  212. *
  213. * @param duration the {@link Date} to be formatted
  214. * @return A formatted string representing {@code then}
  215. */
  216. public String format(Date then)
  217. {
  218. if (then == null)
  219. {
  220. then = new Date();
  221. }
  222. Duration d = approximateDuration(then);
  223. return format(d);
  224. }
  225. /**
  226. * Format the given {@link Date} object. This method applies the {@code PrettyTime.approximateDuration(date)} method
  227. * to perform its calculation. If {@code then} is null, it will default to {@code new Date()}; also decorate for
  228. * past/future tense. Rounding rules are ignored.
  229. *
  230. * @param duration the {@link Date} to be formatted
  231. * @return A formatted string representing {@code then}
  232. */
  233. public String formatUnrounded(Date then)
  234. {
  235. if (then == null)
  236. {
  237. then = new Date();
  238. }
  239. Duration d = approximateDuration(then);
  240. return formatUnrounded(d);
  241. }
  242. /**
  243. * Format the given {@link Duration} object, using the {@link TimeFormat} specified by the {@link TimeUnit} contained
  244. * within; also decorate for past/future tense.
  245. *
  246. * @param duration the {@link Duration} to be formatted
  247. * @return A formatted string representing {@code duration}
  248. */
  249. public String format(final Duration duration)
  250. {
  251. TimeFormat format = getFormat(duration.getUnit());
  252. String time = format.format(duration);
  253. return format.decorate(duration, time);
  254. }
  255. /**
  256. * Format the given {@link Duration} object, using the {@link TimeFormat} specified by the {@link TimeUnit} contained
  257. * within; also decorate for past/future tense. Rounding rules are ignored.
  258. *
  259. * @param duration the {@link Duration} to be formatted
  260. * @return A formatted string representing {@code duration}
  261. */
  262. public String formatUnrounded(Duration duration)
  263. {
  264. TimeFormat format = getFormat(duration.getUnit());
  265. String time = format.formatUnrounded(duration);
  266. return format.decorateUnrounded(duration, time);
  267. }
  268. /**
  269. * Format the given {@link Duration} objects, using the {@link TimeFormat} specified by the {@link TimeUnit}
  270. * contained within. Rounds only the last {@link Duration} object.
  271. *
  272. * @param durations the {@link Duration}s to be formatted
  273. * @return A list of formatted strings representing {@code durations}
  274. */
  275. public String format(final List<Duration> durations)
  276. {
  277. String result = null;
  278. if (durations != null) {
  279. StringBuilder builder = new StringBuilder();
  280. Duration duration = null;
  281. TimeFormat format = null;
  282. for (int i = 0; i < durations.size(); i++) {
  283. duration = durations.get(i);
  284. format = getFormat(duration.getUnit());
  285. boolean isLast = (i == durations.size() - 1);
  286. if (!isLast) {
  287. builder.append(format.formatUnrounded(duration));
  288. builder.append(" ");
  289. }
  290. else {
  291. builder.append(format.format(duration));
  292. }
  293. }
  294. result = format.decorateUnrounded(duration, builder.toString());
  295. }
  296. return result;
  297. }
  298. /**
  299. * Get the registered {@link TimeFormat} for the given {@link TimeUnit} or null if none exists.
  300. */
  301. public TimeFormat getFormat(TimeUnit unit)
  302. {
  303. if (formatMap.get(unit) != null)
  304. {
  305. return formatMap.get(unit);
  306. }
  307. return null;
  308. }
  309. /**
  310. * Get the current reference timestamp.
  311. * <p>
  312. * See {@code PrettyTime.setReference(Date timestamp)}
  313. *
  314. * @return
  315. */
  316. public Date getReference()
  317. {
  318. return reference;
  319. }
  320. /**
  321. * Set the reference timestamp.
  322. * <p>
  323. * If the Date formatted is before the reference timestamp, the format command will produce a String that is in the
  324. * past tense. If the Date formatted is after the reference timestamp, the format command will produce a string that
  325. * is in the future tense.
  326. */
  327. public void setReference(final Date timestamp)
  328. {
  329. reference = timestamp;
  330. }
  331. /**
  332. * Get a {@link List} of the current configured {@link TimeUnit} instances in calculations.
  333. *
  334. * @return
  335. */
  336. public List<TimeUnit> getUnits()
  337. {
  338. List<TimeUnit> units = new ArrayList<TimeUnit>(formatMap.keySet());
  339. Collections.sort(units, new TimeUnitComparator());
  340. return Collections.unmodifiableList(units);
  341. }
  342. /**
  343. * Register the given {@link TimeUnit} and corresponding {@link TimeFormat} instance to be used in calculations. If
  344. * an entry already exists for the given {@link TimeUnit}, its format will be overwritten with the given
  345. * {@link TimeFormat}.
  346. */
  347. public void registerUnit(final TimeUnit unit, TimeFormat format)
  348. {
  349. formatMap.put(unit, format);
  350. }
  351. /**
  352. * Get the currently configured {@link Locale} for this {@link PrettyTime} object.
  353. */
  354. public Locale getLocale()
  355. {
  356. return locale;
  357. }
  358. /**
  359. * Set the the {@link Locale} for this {@link PrettyTime} object. This may be an expensive operation, since this
  360. * operation calls {@link TimeUnit#setLocale(Locale)} for each {@link TimeUnit} in {@link #getUnits()}.
  361. */
  362. @Override
  363. public void setLocale(final Locale locale)
  364. {
  365. this.locale = locale;
  366. for (TimeFormat format : formatMap.values()) {
  367. if (format instanceof LocaleAware)
  368. ((LocaleAware) format).setLocale(locale);
  369. }
  370. }
  371. @Override
  372. public String toString()
  373. {
  374. return "PrettyTime [reference=" + reference + ", locale=" + locale + "]";
  375. }
  376. /**
  377. * Remove all registered {@link TimeUnit} instances.
  378. */
  379. public void clearUnits()
  380. {
  381. formatMap.clear();
  382. }
  383. }