All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.day.cq.commons.date.RelativeTimeFormat Maven / Gradle / Ivy

/*
 * Copyright 1997-2010 Day Management AG
 * Barfuesserplatz 6, 4001 Basel, Switzerland
 * All Rights Reserved.
 *
 * This software is the confidential and proprietary information of
 * Day Management AG, ("Confidential Information"). You shall not
 * disclose such Confidential Information and shall use it only in
 * accordance with the terms of the license agreement you entered into
 * with Day.
 */
package com.day.cq.commons.date;

import com.day.cq.i18n.I18n;
import org.apache.commons.lang.StringUtils;
import org.joda.time.Period;

import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Locale;
import java.util.ResourceBundle;

/**
 * The RelativeTimeFormat provides formatting of relative time periods. Relative time periods in this
 * context indicate a duration from a given start date/time to the current system time or the given end date/time.
 * 

* These periods can be formatted to provide human readable strings describing the duration of that period. For example * the duration with start date 2010-06-30 3:25:00 PM and end date 2010-06-30 3:25:16 would be 16 seconds. This can for * example be formatted as "16 seconds ago". With an end date of e.g. 2010-07-02 5:23:00 AM, the resulting formatted * string could be "Jul 2 (2 days ago)". *

* The desired formatting can be defined using a formatting pattern, similar to many date formatting tools * in existence. Relative time format patterns also result in outputting standard date/time information, e.g. the date * or hour included in a relative time format string, e.g. "Jun 30 (16 seconds ago)". The format of the standard * date/time information is delegated to {@link SimpleDateFormat}. Thus, in addition to the main relative time format * pattern, sub-patterns for the standard date/time blocks can be specified in the constructor ({@link * #RelativeTimeFormat(String, String, String, String, java.util.ResourceBundle)}. Patterns for time, medium date and * long date information can be specified. *

* The relative time formatting also uses localized words for output, such as e.g. the month names (e.g. "Jul" or * "July") or the words "ago" and "minutes" to e.g. output "2 minutes ago". For localization the default system locale * is used, unless a custom resource bundle is provided in the constructor. The custom resource bundle must provide a * locale, otherwise again the default system locale is used, which mit result in mixed language outputs, such as "30. * June (vor 2 Minuten)" (english/german mixture). *

* Within date and time pattern strings, unquoted letters from 'A' to 'Z' and from * 'a' to 'z' are interpreted as pattern letters representing the components of a date or time * string. All other characters are not interpreted; they're simply copied into the output string during formatting or * matched against the input string during parsing. *

* The format function gives the ability to specify the insertion of the word ago in the resulting string. For example, * it can be used to get a string like "15 seconds ago". If "ago" is false "15 seconds will be returned. *

* The following pattern letters are defined (all other characters from 'A' to 'Z' and from * 'a' to 'z' are reserved): *

* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
LetterDate or Time ComponentPresentationExamples
dNumber of days of the period (localized)Text2 days
DDynamic date. This formats the start date of the period according to the amount of time it is in the * past with respect to period's end date: *
    *
  • start is last year: date is formated according to the longPattern, default: * MM/dd/yyyy (see {@link SimpleDateFormat}). *
  • *
  • start is 1 day (24h) or more in the past: date is formated according to the * shortPattern, default: MMM dd (see {@link SimpleDateFormat}). *
  • *
  • start is less than 24h in the past: date is formated according to the timePattern, * default: HH:mm (see {@link SimpleDateFormat}). *
  • *
Text21/12/2010, Jun 30, 21:34
hNumber of hours in the period (localized)Text4 hours
mNumber of minutes in the period (localized)Text40 minutes
MNumber of months in the period (localized)Text3 months
rAmount of time since the start of the period (localized). The amount is structured into the following units: * seconds, minutes, hours, days, months and years. Always the largest non-zero unit is output. E.g. if * the amount is 2 days, 4 minutes and 54 seconds, the output would be "2 days".Text4 months
sNumber of seconds in the period (localized)Text16 seconds
yNumber of years in the period (localized)Text1 year
YFully dynamic auto-mode. The auto-mode reflects Gmail-style adaptation of the relative time output * string, and is subject to the following rules: *
    *
  • start is last year: only the full date is output, according to longPattern, * default: MM/dd/yyyy.
  • *
  • start is 2 weeks or more in the past: the month and day in month are output, according to * shortPattern, default: MMM dd.
  • *
  • start is 24 hours or more in the past: the month and day in month are output, additionally * the number of days are put in brackets. The month and day in month are formatted according to the * shortPattern.
  • *
  • start is less than 24 hours in the past: the time is output according to * timePattern and additionally in brackets the relative amount of time passed according * to the r pattern character.
  • *
  • Do not need to put the boolean to true when format is called with the pattern 'Y'. "ago" will be included automatically * in the resulting string.
  • *
*
Text21/12/2009 *
Jun 30 *
Sep 4 (12 days ago) *
15:25 (2 minutes ago *
*
*/ public class RelativeTimeFormat { private static final String PATTERN_CHARS = "dDhmMrsyY"; public static final String SHORT = "r"; public static final String FULL = "y M d h m s"; public static final String FACEBOOK = SHORT; public static final String GMAIL = "Y"; private final ResourceBundle bundle; private final char[] compiledPattern; private final Calendar calendar; private final Calendar now; private final SimpleDateFormat timeFmt; private final SimpleDateFormat shortFmt; private final SimpleDateFormat longFmt; private Locale locale; private Period period; private I18n i18n; /** * Constructs a new RelativeTimeFormat instance. * * @param pattern The pattern for the relative time. */ public RelativeTimeFormat(final String pattern) { this(pattern, null, null, null, null); } /** * Constructs a new RelativeTimeFormat instance. * * @param pattern The pattern for the relative time. * @param bundle The {@link ResourceBundle} for i18n of the time labels (e.g. "days", "seconds", ...), may be * null. */ public RelativeTimeFormat(final String pattern, final ResourceBundle bundle) { this(pattern, null, null, null, bundle); } /** * Constructs a new RelativeTimeFormat instance. * * @param pattern The formatting pattern for the relative time. * @param timePattern The {@link SimpleDateFormat} pattern for time (default: HH:mm), may be null. * @param shortPattern The {@link SimpleDateFormat} pattern for month and day (default: MMM d), may be * null. * @param longPattern The {@link SimpleDateFormat} pattern for full date (default: MM/dd/yyyy), may be * null. * @param bundle The {@link ResourceBundle} for i18n of the time labels (e.g. "days", "seconds", ...), may be * null. */ public RelativeTimeFormat(final String pattern, final String timePattern, final String shortPattern, final String longPattern, final ResourceBundle bundle) { if (StringUtils.isBlank(pattern)) { throw new IllegalArgumentException("pattern may not be null or empty"); } this.bundle = bundle; getLocale(); i18n = new I18n(bundle); // compile the provided relative time pattern compiledPattern = compile(pattern); // initialize the reference calendar instance calendar = Calendar.getInstance(locale); now = Calendar.getInstance(locale); // initialize date formats timeFmt = new SimpleDateFormat(StringUtils.defaultIfEmpty(timePattern, i18n.get("HH:mm", "Java date format for a time (http://java.sun.com/j2se/1.5.0/docs/api/java/text/SimpleDateFormat.html)")), locale); shortFmt = new SimpleDateFormat(StringUtils.defaultIfEmpty(shortPattern, i18n.get("MMM d", "Java date format for a date (http://java.sun.com/j2se/1.5.0/docs/api/java/text/SimpleDateFormat.html)")), locale); longFmt = new SimpleDateFormat(StringUtils.defaultIfEmpty(longPattern, i18n.get("MM/dd/yyyy", "Java date format for a date (http://java.sun.com/j2se/1.5.0/docs/api/java/text/SimpleDateFormat.html)")), locale); } /** * Formats the time period from the given start date/time according to the pattern selected in the * constructor. The end date/time of the period is the current system time. To specify your own end date/time of the * period, use {@link #format(long, long)}. * * @param start The start date/time of the period to be formatted in milliseconds. * * @return A String representing the formatted period. */ public String format(final long start) { return format(start, System.currentTimeMillis(), false); } /** * Formats the time period from the given start date/time according to the pattern selected in the * constructor. The end date/time of the period is the current system time. To specify your own end date/time of the * period, use {@link #format(long, long)}. * * @param start The start date/time of the period to be formatted in milliseconds. * @param ago The resulting string should contain "ago" (or localized variant) * * @return A String representing the formatted period. */ public String format(final long start, final boolean ago) { return format(start, System.currentTimeMillis(), ago); } /** * Formats the time period as defined via the given start date/time and the given end * date/time, according to the pattern selected in the constructor. * * @param start The start date/time of the period to be formatted in milliseconds. * @param end The end date/time of the period to be formatted in milliseconds. * * @return A String representing the formatted period. */ public String format(final long start, final long end) { return format(start, end, false); } /** * Formats the time period as defined via the given start date/time and the given end * date/time, according to the pattern selected in the constructor. * * @param start The start date/time of the period to be formatted in milliseconds. * @param end The end date/time of the period to be formatted in milliseconds. * @param ago The resulting string should contain "ago" (or localized variant) * * @return A String representing the formatted period. */ public String format(final long start, final long end, final boolean ago) { calendar.setTimeInMillis(start); now.setTimeInMillis(end); period = new Period(start, end); final StringBuilder buffer = new StringBuilder(); int i = 0; for (final char c : compiledPattern) { final int index = PATTERN_CHARS.indexOf(c); if (index >= 0) { if (i == compiledPattern.length-1) { fieldFormat(index, buffer, ago); } else { fieldFormat(index, buffer, false); } } else { buffer.append(c); } i++; } return buffer.toString(); } private void fieldFormat(final int index, final StringBuilder buffer, final boolean ago) { final int years = period.getYears(); final int months = period.getMonths(); final int weeks = period.getWeeks(); final int days = (weeks > 0) ? period.getDays() + weeks * 7 : period.getDays(); final int hours = period.getHours(); final int minutes = period.getMinutes(); final int seconds = period.getSeconds(); String result = ""; // switches for index of "dDhmMrsyY" switch (index) { case 0: // 'd' - day if (days == 1) { result = ago ? i18n.get("{0} day ago", null, days) : i18n.get("{0} day", null, days); } else { result = ago ? i18n.get("{0} days ago", null, days) : i18n.get("{0} days", null, days); } break; case 1: // 'D' - dynamic date buffer.append(getDynamicDate(months, weeks, days)); break; case 2: // 'h' - hours if (hours == 1) { result = ago ? i18n.get("{0} hour ago", null, hours) : i18n.get("{0} hour", null, hours); } else { result = ago ? i18n.get("{0} hours ago", null, hours) : i18n.get("{0} hours", null, hours); } break; case 3: // 'm' - minutes if (minutes == 1) { result = ago ? i18n.get("{0} minute ago", null, minutes) : i18n.get("{0} minute", null, minutes); } else { result = ago ? i18n.get("{0} minutes ago", null, minutes) : i18n.get("{0} minutes", null, minutes); } break; case 4: // 'M' - months if (months == 1) { result = ago ? i18n.get("{0} month ago", null, months) : i18n.get("{0} month", null, months); } else { result = ago ? i18n.get("{0} months ago", null, months) : i18n.get("{0} months", null, months); } break; case 5: // 'r' - relative time (auto mode) buffer.append(getRelative(years, months, days, hours, minutes, seconds, ago)); break; case 6: // 's' - seconds if (seconds < 1) { result = i18n.get("now", null, seconds); } else { if (seconds == 1) { result = ago ? i18n.get("{0} second ago", null, seconds) : i18n.get("{0} second", null, seconds); } else { result = ago ? i18n.get("{0} seconds ago", null, seconds) : i18n.get("{0} seconds", null, seconds); } } break; case 7: // 'y' - years if (years == 1) { result = ago ? i18n.get("{0} year ago", null, years) : i18n.get("{0} year", null, years); } else { result = ago ? i18n.get("{0} years ago", null, years) : i18n.get("{0} years", null, years); } break; case 8: // 'Y' - full dynamic auto mode buffer.append(getDynamicDate(months, weeks, days)); if (calendar.get(Calendar.YEAR) == now.get(Calendar.YEAR) && months == 0 && weeks < 2) { buffer.append(" (") .append(i18n.get("{0}", null, getRelative(years, months, days, hours, minutes, seconds, true))) .append(")"); } break; default: } buffer.append(result); } private String getRelative(final int years, final int months, final int days, final int hours, final int minutes, final int seconds, final boolean ago) { String label = ""; if (0 < years) { if (years == 1) { label = ago ? i18n.get("{0} year ago", null, years) : i18n.get("{0} year", null, years); } else { label = ago ? i18n.get("{0} years ago", null, years) : i18n.get("{0} years", null, years); } } else if (0 < months) { if (months == 1) { label = ago ? i18n.get("{0} month ago", null, months) : i18n.get("{0} month", null, months); } else { label = ago ? i18n.get("{0} months ago", null, months) : i18n.get("{0} months", null, months); } } else if (0 < days) { int count = days; if (count == 1) { label = ago ? i18n.get("{0} day ago", null, count) : i18n.get("{0} day", null, count); } else { label = ago ? i18n.get("{0} days ago", null, count) : i18n.get("{0} days", null, count); } } else if (0 < hours) { if (hours == 1) { label = ago ? i18n.get("{0} hour ago", null, hours) : i18n.get("{0} hour", null, hours); } else { label = ago ? i18n.get("{0} hours ago", null, hours) : i18n.get("{0} hours", null, hours); } } else if (0 < minutes) { if (minutes == 1) { label = ago ? i18n.get("{0} minute ago", null, minutes) : i18n.get("{0} minute", null, minutes); } else { label = ago ? i18n.get("{0} minutes ago", null, minutes) : i18n.get("{0} minutes", null, minutes); } } else if (0 <= seconds) { if (seconds < 1) { label = i18n.get("now", null, seconds); } else { if (seconds == 1) { label = ago ? i18n.get("{0} second ago", null, seconds) : i18n.get("{0} second", null, seconds); } else { label = ago ? i18n.get("{0} seconds ago", null, seconds) : i18n.get("{0} seconds", null, seconds); } } } return label; } private String getDynamicDate(final int months, final int weeks, final int days) { // period start date is last year => display full numeric date if (calendar.get(Calendar.YEAR) < now.get(Calendar.YEAR)) { return longFmt.format(calendar.getTime()); } else if (months > 0 || weeks >= 2) { // period is more than or equal two weeks return shortFmt.format(calendar.getTime()); } else if (days > 0) { // period is more than or equal 24h and less than two weeks return shortFmt.format(calendar.getTime()); } else { return timeFmt.format(calendar.getTime()); } } private char[] compile(final String pattern) { final StringBuilder buffer = new StringBuilder(); final int length = pattern.length(); for (int i = 0; i < length; i++) { final char c = pattern.charAt(i); if (!(c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z')) { // punctuation/spaces/braces/... buffer.append(c); } else { // alphabetic if (PATTERN_CHARS.indexOf(c) >= 0) { buffer.append(c); } else { throw new IllegalArgumentException("illegal pattern character \"'" + c + "\'"); } } } return buffer.toString().toCharArray(); } private Locale getLocale() { if (null == locale && null != bundle) { locale = bundle.getLocale(); } else if (null != locale) { return locale; } if (null == locale) { locale = Locale.getDefault(); } return locale; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy