/* * Licensed by the author of Time4J-project. * * See the NOTICE file distributed with this work for additional * information regarding copyright ownership. The copyright owner * licenses this file to you 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 net.time4j; import net.time4j.base.MathUtils; import net.time4j.base.ResourceLoader; import net.time4j.base.TimeSource; import net.time4j.base.UnixTime; import net.time4j.engine.TimeSpan; import net.time4j.format.NumberSymbolProvider; import net.time4j.format.NumberSystem; import net.time4j.format.NumberType; import net.time4j.format.PluralCategory; import net.time4j.format.PluralRules; import net.time4j.format.TemporalFormatter; import net.time4j.format.TextWidth; import net.time4j.tz.TZID; import net.time4j.tz.Timezone; import net.time4j.tz.ZonalOffset; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.TimeUnit; import static net.time4j.CalendarUnit.DAYS; import static net.time4j.CalendarUnit.MONTHS; import static net.time4j.CalendarUnit.WEEKS; import static net.time4j.CalendarUnit.YEARS; import static net.time4j.ClockUnit.HOURS; import static net.time4j.ClockUnit.MICROS; import static net.time4j.ClockUnit.MILLIS; import static net.time4j.ClockUnit.MINUTES; import static net.time4j.ClockUnit.NANOS; import static net.time4j.ClockUnit.SECONDS; /** * <p>Enables formatted output as usually used in social media in different * languages. </p> * * <p>Parsing is not included because there is no general solution for all * locales. Instead users must keep the backing duration object and use it * for printing. </p> * * @author Meno Hochschild * @since 1.2 */ /*[deutsch] * <p>Ermöglicht formatierte Ausgaben einer Dauer für soziale Medien * ("social media style") in verschiedenen Sprachen. </p> * * <p>Der Rückweg der Interpretation (<i>parsing</i>) ist nicht enthalten, * weil so nicht alle Sprachen unterstützt werden können. Stattdessen * werden Anwender angehalten, das korrespondierende Dauer-Objekt im Hintergrund * zu halten und es für die formatierte Ausgabe zu nutzen. </p> * * @author Meno Hochschild * @since 1.2 */ public final class PrettyTime { //~ Statische Felder/Initialisierungen -------------------------------- private static final NumberSymbolProvider NUMBER_SYMBOLS; static { NumberSymbolProvider p = null; int count = 0; for (NumberSymbolProvider tmp : ResourceLoader.getInstance().services(NumberSymbolProvider.class)) { int size = tmp.getAvailableLocales().length; if (size >= count) { // includes SymbolProviderSPI if available p = tmp; count = size; } } if (p == null) { p = NumberSymbolProvider.DEFAULT; } NUMBER_SYMBOLS = p; } private static final int MIO = 1000000; private static final ConcurrentMap<Locale, PrettyTime> LANGUAGE_MAP = new ConcurrentHashMap<Locale, PrettyTime>(); private static final IsoUnit[] STD_UNITS; private static final IsoUnit[] TSP_UNITS; private static final Set<IsoUnit> SUPPORTED_UNITS; private static final long START_1972; static { IsoUnit[] stdUnits = {YEARS, MONTHS, WEEKS, DAYS, HOURS, MINUTES, SECONDS}; STD_UNITS = stdUnits; TSP_UNITS = new IsoUnit[] {YEARS, MONTHS, DAYS, HOURS, MINUTES, SECONDS}; Set<IsoUnit> tmp = new HashSet<IsoUnit>(); Collections.addAll(tmp, stdUnits); tmp.add(NANOS); SUPPORTED_UNITS = Collections.unmodifiableSet(tmp); START_1972 = 2 * 365 * 86400L; } //~ Instanzvariablen -------------------------------------------------- private final PluralRules rules; private final Locale locale; private final TimeSource<?> refClock; private final char zeroDigit; private final String minusSign; private final IsoUnit emptyUnit; private final boolean weekToDays; private final boolean shortStyle; private final String stdListSeparator; private final String endListSeparator; //~ Konstruktoren ----------------------------------------------------- private PrettyTime( Locale loc, TimeSource<?> refClock, char zeroDigit, String minusSign, IsoUnit emptyUnit, boolean weekToDays, boolean shortStyle, String stdListSeparator, String endListSeparator ) { super(); if (emptyUnit == null) { throw new NullPointerException("Missing zero time unit."); } else if (refClock == null) { throw new NullPointerException("Missing reference clock."); } // throws NPE if language == null this.rules = PluralRules.of(loc, NumberType.CARDINALS); this.locale = loc; this.refClock = refClock; this.zeroDigit = zeroDigit; this.emptyUnit = emptyUnit; this.minusSign = minusSign; this.weekToDays = weekToDays; this.shortStyle = shortStyle; this.stdListSeparator = stdListSeparator; this.endListSeparator = endListSeparator; } //~ Methoden ---------------------------------------------------------- /** * <p>Gets an instance of {@code PrettyTime} for given language, * possibly cached. </p> * * @param locale the language an instance is searched for * @return pretty time object for formatting durations or relative time * @since 1.2 */ /*[deutsch] * <p>Liefert eine Instanz von {@code PrettyTime} für die angegebene * Sprache, eventuell aus dem Cache. </p> * * @param locale the language an instance is searched for * @return pretty time object for formatting durations or relative time * @since 1.2 */ public static PrettyTime of(Locale locale) { PrettyTime ptime = LANGUAGE_MAP.get(locale); if (ptime == null) { ptime = new PrettyTime( locale, SystemClock.INSTANCE, NUMBER_SYMBOLS.getZeroDigit(locale), NUMBER_SYMBOLS.getMinusSign(locale), SECONDS, false, false, null, null); PrettyTime old = LANGUAGE_MAP.putIfAbsent(locale, ptime); if (old != null) { ptime = old; } } return ptime; } /** * <p>Gets the language of this instance. </p> * * @return language * @since 1.2 */ /*[deutsch] * <p>Liefert die Bezugssprache. </p> * * @return Spracheinstellung * @since 1.2 */ public Locale getLocale() { return this.locale; } /** * <p>Yields the reference clock for formatting of relative times. </p> * * @return reference clock or system clock if not yet specified * @since 1.2 * @see #withReferenceClock(TimeSource) * @see #printRelative(UnixTime, TZID) * @see #printRelative(UnixTime, String) */ /*[deutsch] * <p>Liefert die Bezugsuhr für formatierte Ausgaben der relativen * Zeit. </p> * * @return Zeitquelle oder die Systemuhr, wenn noch nicht angegeben * @since 1.2 * @see #withReferenceClock(TimeSource) * @see #printRelative(UnixTime, TZID) * @see #printRelative(UnixTime, String) */ public TimeSource<?> getReferenceClock() { return this.refClock; } /** * <p>Yields a changed copy of this instance with given reference * clock. </p> * * @param clock new reference clock * @return new instance of {@code PrettyTime} with changed reference clock * @since 1.2 * @see #getReferenceClock() * @see #printRelative(UnixTime, TZID) * @see #printRelative(UnixTime, String) */ /*[deutsch] * <p>Legt die Bezugszeit für relative Zeitangaben neu fest. </p> * * @param clock new reference clock * @return new instance of {@code PrettyTime} with changed reference clock * @since 1.2 * @see #getReferenceClock() * @see #printRelative(UnixTime, TZID) * @see #printRelative(UnixTime, String) */ public PrettyTime withReferenceClock(TimeSource<?> clock) { return new PrettyTime( this.locale, clock, this.zeroDigit, this.minusSign, this.emptyUnit, this.weekToDays, this.shortStyle, this.stdListSeparator, this.endListSeparator); } /** * <p>Defines the localized zero digit based on given decimal number system. </p> * * @param numberSystem decimal number system * @return changed copy of this instance * @throws IllegalArgumentException if the number system is not decimal * @see #withZeroDigit(char) * @since 3.24/4.20 */ /*[deutsch] * <p>Definiert die lokalisierte Nullziffer, indem das angegebene Dezimalzahlsystem ausgewertet wird. </p> * * @param numberSystem decimal number system * @return changed copy of this instance * @throws IllegalArgumentException if the number system is not decimal * @see #withZeroDigit(char) * @since 3.24/4.20 */ public PrettyTime withZeroDigit(NumberSystem numberSystem) { if (numberSystem.isDecimal()) { return this.withZeroDigit(numberSystem.getDigits().charAt(0)); } else { throw new IllegalArgumentException("Number system is not decimal: " + numberSystem); } } /** * <p>Defines the localized zero digit. </p> * * <p>In most languages the zero digit is just ASCII-"0", * but for example in arabic locales the digit can also be the char * {@code U+0660}. By default Time4J will try to use the configuration * of the module i18n or else the JDK-setting. This method can override * it however. </p> * * @param zeroDigit localized zero digit * @return changed copy of this instance * @since 1.2 * @see java.text.DecimalFormatSymbols#getZeroDigit() * @see net.time4j.format.NumberSymbolProvider#getZeroDigit(Locale) */ /*[deutsch] * <p>Definiert die lokalisierte Nullziffer. </p> * * <p>In den meisten Sprachen ist die Nullziffer ASCII-"0", * aber etwa im arabischen Sprachraum kann das Zeichen auch {@code U+0660} * sein. Per Vorgabe wird Time4J versuchen, die Konfiguration des * i18n-Moduls oder sonst die JDK-Einstellung zu verwenden. Diese * Methode überschreibt jedoch den Standard. </p> * * @param zeroDigit localized zero digit * @return changed copy of this instance * @since 1.2 * @see java.text.DecimalFormatSymbols#getZeroDigit() * @see net.time4j.format.NumberSymbolProvider#getZeroDigit(Locale) */ public PrettyTime withZeroDigit(char zeroDigit) { if (this.zeroDigit == zeroDigit) { return this; } return new PrettyTime( this.locale, this.refClock, zeroDigit, this.minusSign, this.emptyUnit, this.weekToDays, this.shortStyle, this.stdListSeparator, this.endListSeparator); } /** * <p>Defines the localized minus sign. </p> * * <p>In most languages the minus sign is just {@code U+002D}. By default * Time4J will try to use the configuration of the module i18n or else the * JDK-setting. This method can override it however. Especially for arabic, * it might make sense to first add a unicode marker (either LRM * {@code U+200E} or RLM {@code U+200F}) in front of the minus sign * in order to control the orientation in right-to-left-style. </p> * * @param minusSign localized minus sign (possibly with unicode markers) * @return changed copy of this instance * @since 2.1 * @see java.text.DecimalFormatSymbols#getMinusSign() * @see net.time4j.format.NumberSymbolProvider#getMinusSign(Locale) */ /*[deutsch] * <p>Definiert das lokalisierte Minuszeichen. </p> * * <p>In den meisten Sprachen ist es einfach das Zeichen {@code U+002D}. * Per Vorgabe wird Time4J versuchen, die Konfiguration des * i18n-Moduls oder sonst die JDK-Einstellung zu verwenden. Diese * Methode überschreibt jedoch den Standard. Besonders für * Arabisch kann es sinnvoll sein, vor dem eigentlichen Minuszeichen * einen Unicode-Marker (entweder LRM {@code U+200E} oder RLM * {@code U+200F}) einzufügen, um die Orientierung des Minuszeichens * in der traditionellen Rechts-nach-links-Schreibweise zu * kontrollieren. </p> * * @param minusSign localized minus sign (possibly with unicode markers) * @return changed copy of this instance * @since 2.1 * @see java.text.DecimalFormatSymbols#getMinusSign() * @see net.time4j.format.NumberSymbolProvider#getMinusSign(Locale) */ public PrettyTime withMinusSign(String minusSign) { if (minusSign.equals(this.minusSign)) { // NPE-check return this; } return new PrettyTime( this.locale, this.refClock, this.zeroDigit, minusSign, this.emptyUnit, this.weekToDays, this.shortStyle, this.stdListSeparator, this.endListSeparator); } /** * <p>Defines the time unit used for formatting an empty duration. </p> * * <p>Time4J uses seconds as default. This method can override the * default however. </p> * * @param emptyUnit time unit for usage in an empty duration * @return changed copy of this instance * @since 1.2 * @see #print(Duration, TextWidth) */ /*[deutsch] * <p>Definiert die Zeiteinheit für die Verwendung in der * Formatierung einer leeren Dauer. </p> * * <p>Vorgabe ist die Sekundeneinheit. Diese Methode kann die Vorgabe * jedoch überschreiben. </p> * * @param emptyUnit time unit for usage in an empty duration * @return changed copy of this instance * @since 1.2 * @see #print(Duration, TextWidth) */ public PrettyTime withEmptyUnit(CalendarUnit emptyUnit) { if (this.emptyUnit.equals(emptyUnit)) { return this; } return new PrettyTime( this.locale, this.refClock, this.zeroDigit, this.minusSign, emptyUnit, this.weekToDays, this.shortStyle, this.stdListSeparator, this.endListSeparator); } /** * <p>Defines the time unit used for formatting an empty duration. </p> * * <p>Time4J uses seconds as default. This method can override the * default however. </p> * * @param emptyUnit time unit for usage in an empty duration * @return changed copy of this instance * @since 1.2 * @see #print(Duration, TextWidth) */ /*[deutsch] * <p>Definiert die Zeiteinheit für die Verwendung in der * Formatierung einer leeren Dauer. </p> * * <p>Vorgabe ist die Sekundeneinheit. Diese Methode kann die Vorgabe * jedoch überschreiben. </p> * * @param emptyUnit time unit for usage in an empty duration * @return changed copy of this instance * @since 1.2 * @see #print(Duration, TextWidth) */ public PrettyTime withEmptyUnit(ClockUnit emptyUnit) { if (this.emptyUnit.equals(emptyUnit)) { return this; } return new PrettyTime( this.locale, this.refClock, this.zeroDigit, this.minusSign, emptyUnit, this.weekToDays, this.shortStyle, this.stdListSeparator, this.endListSeparator); } /** * <p>Determines that weeks will always be normalized to days. </p> * * @return changed copy of this instance * @since 2.0 */ /*[deutsch] * <p>Legt fest, daß Wochen immer zu Tagen normalisiert werden. </p> * * @return changed copy of this instance * @since 2.0 */ public PrettyTime withWeeksToDays() { if (this.weekToDays) { return this; } return new PrettyTime( this.locale, this.refClock, this.zeroDigit, this.minusSign, this.emptyUnit, true, this.shortStyle, this.stdListSeparator, this.endListSeparator); } /** * <p>Mandates the use of abbreviations as default style. </p> * * <p>All {@code print()}-methods with explicit {@code TextWidth}-parameters will ignore this * setting however. This method is mainly relevant for printing relative times. </p> * * @return changed copy of this instance * @since 3.6/4.4 */ /*[deutsch] * <p>Legt die Verwendung von Abkürzungen als Vorgabestil fest. </p> * * <p>Alle {@code print()}-Methoden mit einem expliziten {@code TextWidth}-Parameter ignorieren diese * Einstellung. Diese Methode ist hauptsächlich für die Formatierung von relativen Zeitangaben * von Bedeutung. </p> * * @return changed copy of this instance * @since 3.6/4.4 */ public PrettyTime withShortStyle() { if (this.shortStyle) { return this; } return new PrettyTime( this.locale, this.refClock, this.zeroDigit, this.minusSign, this.emptyUnit, this.weekToDays, true, this.stdListSeparator, this.endListSeparator); } /** * <p>Mandates the use of given default list separator. </p> * * <p>Usually the locale specifies any list patterns for printing durations. However, this * method allows customized list separators. Example: </p> * * <pre> * assertThat( * PrettyTime.of(Locale.US) * .withDefaultListSeparator(" | ") * .withLastListSeparator(" + ") * .print(Duration.ofCalendarUnits(1, 2, 3), TextWidth.WIDE), * is("1 year | 2 months + 3 days")); * </pre> * * @param separator the separator characters between any duration items * @return changed copy of this instance * @see #withLastListSeparator(String) * @since 4.2 */ /*[deutsch] * <p>Legt einen neuen Standard für die Listentrennzeichen fest. </p> * * <p>Normalerweise legt die angewandte {@code Locale} die verwendeten Listentrennzeichen fest. * Diese Methode erlaubt benutzerdefinierte Listentrennzeichen. Beispiel: </p> * * <pre> * assertThat( * PrettyTime.of(Locale.US) * .withDefaultListSeparator(" | ") * .withLastListSeparator(" + ") * .print(Duration.ofCalendarUnits(1, 2, 3), TextWidth.WIDE), * is("1 year | 2 months + 3 days")); * </pre> * * @param separator the separator characters between any duration items * @return changed copy of this instance * @see #withLastListSeparator(String) * @since 4.2 */ public PrettyTime withDefaultListSeparator(String separator) { if (separator.equals(this.stdListSeparator)) { // includes NPE-check return this; } return new PrettyTime( this.locale, this.refClock, this.zeroDigit, this.minusSign, this.emptyUnit, this.weekToDays, this.shortStyle, separator, this.endListSeparator); } /** * <p>Mandates the use of given list separator at the last position. </p> * * <p><strong>Important:</strong> A default list separator must be specified * otherwise this method will have no effect. </p> * * @param separator the separator characters between the last two duration items * @return changed copy of this instance * @see #withDefaultListSeparator(String) * @since 4.2 */ /*[deutsch] * <p>Legt die Listentrennzeichen an der letzten Position fest. </p> * * <p><strong>Wichtig:</strong> Die Standardlistentrennzeichen müssen angegeben * werden, damit diese Methode Wirkung zeigt. </p> * * @param separator the separator characters between the last two duration items * @return changed copy of this instance * @see #withDefaultListSeparator(String) * @since 4.2 */ public PrettyTime withLastListSeparator(String separator) { if (separator.equals(this.endListSeparator)) { // includes NPE-check return this; } return new PrettyTime( this.locale, this.refClock, this.zeroDigit, this.minusSign, this.emptyUnit, this.weekToDays, this.shortStyle, this.stdListSeparator, separator); } /** * <p>Determines the localized word for "yesterday". </p> * * @return String * @since 3.43/4.38 */ /*[deutsch] * <p>Ermittelt das lokalisierte Wort für "gestern". </p> * * @return String * @since 3.43/4.38 */ public String printYesterday() { return UnitPatterns.of(this.getLocale()).getYesterdayWord(); } /** * <p>Determines the localized word for "today". </p> * * @return String * @since 3.24/4.20 */ /*[deutsch] * <p>Ermittelt das lokalisierte Wort für "heute". </p> * * @return String * @since 3.24/4.20 */ public String printToday() { return UnitPatterns.of(this.getLocale()).getTodayWord(); } /** * <p>Determines the localized word for "tomorrow". </p> * * @return String * @since 3.43/4.38 */ /*[deutsch] * <p>Ermittelt das lokalisierte Wort für "morgen". </p> * * @return String * @since 3.43/4.38 */ public String printTomorrow() { return UnitPatterns.of(this.getLocale()).getTomorrowWord(); } /** * <p>Obtains the localized word for given last day-of-week if available. </p> * * @param weekday the last day of week * @return localized word, maybe empty * @since 4.1 */ /*[deutsch] * <p>Liefert das lokalisierte Wort für den angegebenen letzten Wochentag, falls verfügbar. </p> * * @param weekday the last day of week * @return localized word, maybe empty * @since 4.1 */ public String printLast(Weekday weekday) { return UnitPatterns.of(this.getLocale()).labelForLast(weekday); } /** * <p>Obtains the localized word for given next day-of-week if available. </p> * * @param weekday the next day of week * @return localized word, maybe empty * @since 4.1 */ /*[deutsch] * <p>Liefert das lokalisierte Wort für den angegebenen nächsten Wochentag, falls verfügbar. </p> * * @param weekday the next day of week * @return localized word, maybe empty * @since 4.1 */ public String printNext(Weekday weekday) { return UnitPatterns.of(this.getLocale()).labelForNext(weekday); } /** * <p>Formats given duration in calendar units. </p> * * <p>Note: Millennia, centuries and decades are automatically normalized * to years while quarter-years are normalized to months. </p> * * @param amount count of units (quantity) * @param unit calendar unit * @param width text width (ABBREVIATED as synonym for SHORT) * @return formatted output * @since 1.2 * @see #print(Duration, TextWidth) */ /*[deutsch] * <p>Formatiert die angegebene Dauer in kalendarischen Zeiteinheiten. </p> * * <p>Hinweis: Jahrtausende, Jahrhunderte und Dekaden werden automatisch * zu Jahren normalisiert, während Quartale zu Monaten normalisiert * werden. </p> * * @param amount Anzahl der Einheiten * @param unit kalendarische Zeiteinheit * @param width text width (ABBREVIATED as synonym for SHORT) * @return formatierte Ausgabe * @since 1.2 * @see #print(Duration, TextWidth) */ public String print( long amount, CalendarUnit unit, TextWidth width ) { UnitPatterns p = UnitPatterns.of(this.locale); CalendarUnit u; switch (unit) { case MILLENNIA: amount = MathUtils.safeMultiply(amount, 1000); u = CalendarUnit.YEARS; break; case CENTURIES: amount = MathUtils.safeMultiply(amount, 100); u = CalendarUnit.YEARS; break; case DECADES: amount = MathUtils.safeMultiply(amount, 10); u = CalendarUnit.YEARS; break; case YEARS: u = CalendarUnit.YEARS; break; case QUARTERS: amount = MathUtils.safeMultiply(amount, 3); u = CalendarUnit.MONTHS; break; case MONTHS: u = CalendarUnit.MONTHS; break; case WEEKS: if (this.weekToDays) { amount = MathUtils.safeMultiply(amount, 7); u = CalendarUnit.DAYS; } else { u = CalendarUnit.WEEKS; } break; case DAYS: u = CalendarUnit.DAYS; break; default: throw new UnsupportedOperationException(unit.name()); } String pattern = p.getPattern(width, this.getCategory(amount), u); return this.format(pattern, amount); } /** * <p>Formats given duration in clock units. </p> * * @param amount count of units (quantity) * @param unit clock unit * @param width text width (ABBREVIATED as synonym for SHORT) * @return formatted output * @since 1.2 * @see #print(Duration, TextWidth) */ /*[deutsch] * <p>Formatiert die angegebene Dauer in Uhrzeiteinheiten. </p> * * @param amount Anzahl der Einheiten * @param unit Uhrzeiteinheit * @param width text width (ABBREVIATED as synonym for SHORT) * @return formatierte Ausgabe * @since 1.2 * @see #print(Duration, TextWidth) */ public String print( long amount, ClockUnit unit, TextWidth width ) { String pattern = UnitPatterns.of(this.locale).getPattern(width, this.getCategory(amount), unit); return this.format(pattern, amount); } /** * <p>Formats the total given duration. </p> * * <p>A localized output is only supported for the units * {@link CalendarUnit#YEARS}, {@link CalendarUnit#MONTHS}, * {@link CalendarUnit#WEEKS}, {@link CalendarUnit#DAYS} and * all {@link ClockUnit}-units. This method performs an internal * normalization if any other unit is involved. </p> * * <p>Note: This method uses full words by default. If * {@link #withShortStyle()} is called then abbreviations will be used. </p> * * @param duration object representing a duration which might contain several units and quantities * @return formatted list output * @since 3.6/4.4 */ /*[deutsch] * <p>Formatiert die gesamte angegebene Dauer. </p> * * <p>Eine lokalisierte Ausgabe ist nur für die Zeiteinheiten * {@link CalendarUnit#YEARS}, {@link CalendarUnit#MONTHS}, * {@link CalendarUnit#WEEKS}, {@link CalendarUnit#DAYS} und * alle {@link ClockUnit}-Instanzen vorhanden. Bei Bedarf werden * andere Einheiten zu diesen normalisiert. </p> * * <p>Hinweis: Diese Methode verwendet standardmäß volle * Wörter. Falls {@link #withShortStyle()} aufgerufen wurde, werden * Abkürzungen verwendet. </p> * * @param duration object representing a duration which might contain several units and quantities * @return formatted list output * @since 3.6/4.4 */ public String print(Duration<?> duration) { TextWidth width = (this.shortStyle ? TextWidth.ABBREVIATED : TextWidth.WIDE); return this.print(duration, width, false, Integer.MAX_VALUE); } /** * <p>Formats the total given duration. </p> * * <p>A localized output is only supported for the units * {@link CalendarUnit#YEARS}, {@link CalendarUnit#MONTHS}, * {@link CalendarUnit#WEEKS}, {@link CalendarUnit#DAYS} and * all {@link ClockUnit}-units. This method performs an internal * normalization if any other unit is involved. </p> * * @param duration object representing a duration which might contain * several units and quantities * @param width text width (ABBREVIATED as synonym for SHORT) * @return formatted list output * @since 1.2 */ /*[deutsch] * <p>Formatiert die gesamte angegebene Dauer. </p> * * <p>Eine lokalisierte Ausgabe ist nur für die Zeiteinheiten * {@link CalendarUnit#YEARS}, {@link CalendarUnit#MONTHS}, * {@link CalendarUnit#WEEKS}, {@link CalendarUnit#DAYS} und * alle {@link ClockUnit}-Instanzen vorhanden. Bei Bedarf werden * andere Einheiten zu diesen normalisiert. </p> * * @param duration object representing a duration which might contain * several units and quantities * @param width text width (ABBREVIATED as synonym for SHORT) * @return formatted list output * @since 1.2 */ public String print( Duration<?> duration, TextWidth width ) { return this.print(duration, width, false, Integer.MAX_VALUE); } /** * <p>Formats given duration. </p> * * <p>Like {@link #print(Duration, TextWidth)}, but offers the * option to limit the count of displayed duration items and also * to print items with zero amount. The first printed duration item * has always a non-zero amount however. Example: </p> * * <pre> * Duration<?> dur = * Duration.ofZero().plus(1, DAYS).plus(4, ClockUnit.MINUTES); * System.out.println( * PrettyTime.of(Locale.FRANCE).print(dur, TextWidth.WIDE, true, 3)); * // output: 1 jour, 0 heure et 4 minutes * </pre> * * @param duration object representing a duration which might contain * several units and quantities * @param width text width (ABBREVIATED as synonym for SHORT) * @param printZero determines if zero amounts shall be printed, too * @param maxLength maximum count of displayed items * @return formatted list output * @throws IllegalArgumentException if maxLength is smaller than {@code 1} * @since 2.0 */ /*[deutsch] * <p>Formatiert die angegebene Dauer. </p> * * <p>Wie {@link #print(Duration, TextWidth)}, aber mit der Option, die * Anzahl der Dauerelemente zu begrenzen und auch Elemente mit dem * Betrag {@code 0} auszugeben. Das erste ausgegebene Element hat aber * immer einen Betrag ungleich {@code 0}. Beispiel: </p> * * <pre> * Duration<?> dur = * Duration.ofZero().plus(1, DAYS).plus(4, ClockUnit.MINUTES); * System.out.println( * PrettyTime.of(Locale.FRANCE).print(dur, TextWidth.WIDE, true, 3)); * // output: 1 jour, 0 heure et 4 minutes * </pre> * * @param duration object representing a duration which might contain * several units and quantities * @param width text width (ABBREVIATED as synonym for SHORT) * @param printZero determines if zero amounts shall be printed, too * @param maxLength maximum count of displayed items * @return formatted list output * @throws IllegalArgumentException if maxLength is smaller than {@code 1} * @since 2.0 */ public String print( Duration<?> duration, TextWidth width, boolean printZero, int maxLength ) { if (maxLength < 1) { throw new IllegalArgumentException( "Max length is invalid: " + maxLength); } // special case of empty duration if (duration.isEmpty()) { if (this.emptyUnit.isCalendrical()) { CalendarUnit unit = CalendarUnit.class.cast(this.emptyUnit); return this.print(0, unit, width); } else { ClockUnit unit = ClockUnit.class.cast(this.emptyUnit); return this.print(0, unit, width); } } // fill values-array from duration boolean negative = duration.isNegative(); long[] values = new long[8]; pushDuration(values, duration, this.refClock, this.weekToDays); // format duration items List<Object> parts = new ArrayList<Object>(); int count = 0; for (int i = 0; i < values.length; i++) { if ( (count < maxLength) && (!this.weekToDays || (i != 2)) && ((printZero && (count > 0)) || (values[i] > 0)) ) { IsoUnit unit = ((i == 7) ? NANOS : STD_UNITS[i]); parts.add(this.format(values[i], unit, negative, width)); count++; } } // duration is not empty here assert (count > 0); // special case of only one item if (count == 1) { return parts.get(0).toString(); } // multiple items >= 2 String listPattern; if (this.stdListSeparator != null) { String endSep = this.endListSeparator; if (endSep == null) { endSep = this.stdListSeparator; } StringBuilder sb = new StringBuilder(); sb.append("{0}"); int max = count - 1; for (int i = 1; i < max; i++) { sb.append(this.stdListSeparator); sb.append('{'); sb.append(i); sb.append('}'); } sb.append(endSep); sb.append('{'); sb.append(max); sb.append('}'); listPattern = sb.toString(); } else { listPattern = UnitPatterns.of(this.locale).getListPattern(width, count); } return MessageFormat.format( listPattern, parts.toArray(new Object[count])); } /** * <p>Formats given time point relative to the current time of * {@link #getReferenceClock()} as duration in at most second * precision or less - using the system timezone. </p> * * @param moment relative time point * @return formatted output of relative time, either in past or in future * @see #printRelative(UnixTime, Timezone, TimeUnit) * @since 3.4/4.3 */ /*[deutsch] * <p>Formatiert den angegebenen Zeitpunkt relativ zur aktuellen Zeit * der Referenzuhr {@link #getReferenceClock()} als Dauer in maximal * Sekundengenauigkeit - bezüglich der Systemzeitzone. </p> * * @param moment relative time point * @return formatted output of relative time, either in past or in future * @see #printRelative(UnixTime, Timezone, TimeUnit) * @since 3.4/4.3 */ public String printRelativeInStdTimezone(UnixTime moment) { return this.printRelative(moment, Timezone.ofSystem(), TimeUnit.SECONDS); } /** * <p>Formats given time point relative to the current time of * {@link #getReferenceClock()} as duration in at most second * precision or less. </p> * * <p>Example how to query for a coming leap second: </p> * * <pre> * TimeSource<?> clock = new TimeSource<Moment>() { * public Moment currentTime() { * return PlainTimestamp.of(2015, 6, 30, 23, 59, 54).atUTC(); * }}; * String remainingDurationInRealSeconds = * PrettyTime.of(Locale.ENGLISH).withReferenceClock(clock).printRelative( * PlainTimestamp.of(2015, 7, 1, 0, 0, 0).atUTC(), * ZonalOffset.UTC); * System.out.println(remainingDurationInRealSeconds); // in 7 seconds * </pre> * * @param moment relative time point * @param tzid time zone id for translating to a local duration * @return formatted output of relative time, either in past or in future * @see #printRelative(UnixTime, Timezone, TimeUnit) * @since 1.2 */ /*[deutsch] * <p>Formatiert den angegebenen Zeitpunkt relativ zur aktuellen Zeit * der Referenzuhr {@link #getReferenceClock()} als Dauer in maximal * Sekundengenauigkeit. </p> * * <p>Beispiel, das zeigt, wie eine bevorstehende Schaltsekunde abgefragt werden kann: </p> * * <pre> * TimeSource<?> clock = new TimeSource<Moment>() { * public Moment currentTime() { * return PlainTimestamp.of(2015, 6, 30, 23, 59, 54).atUTC(); * }}; * String remainingDurationInRealSeconds = * PrettyTime.of(Locale.ENGLISH).withReferenceClock(clock).printRelative( * PlainTimestamp.of(2015, 7, 1, 0, 0, 0).atUTC(), * ZonalOffset.UTC); * System.out.println(remainingDurationInRealSeconds); // in 7 seconds * </pre> * * @param moment relative time point * @param tzid time zone id for translating to a local duration * @return formatted output of relative time, either in past or in future * @see #printRelative(UnixTime, Timezone, TimeUnit) * @since 1.2 */ public String printRelative( UnixTime moment, TZID tzid ) { return this.printRelative(moment, Timezone.of(tzid), TimeUnit.SECONDS); } /** * <p>Formats given time point relative to the current time of * {@link #getReferenceClock()} as duration in at most second * precision or less. </p> * * @param moment relative time point * @param tzid time zone id for translating to a local duration * @return formatted output of relative time, either in past or in future * @see #printRelative(UnixTime, Timezone, TimeUnit) * @since 1.2 */ /*[deutsch] * <p>Formatiert den angegebenen Zeitpunkt relativ zur aktuellen Zeit * der Referenzuhr {@link #getReferenceClock()} als Dauer in maximal * Sekundengenauigkeit. </p> * * @param moment relative time point * @param tzid time zone id for translating to a local duration * @return formatted output of relative time, either in past or in future * @see #printRelative(UnixTime, Timezone, TimeUnit) * @since 1.2 */ public String printRelative( UnixTime moment, String tzid ) { return this.printRelative(moment, Timezone.of(tzid), TimeUnit.SECONDS); } /** * <p>Formats given time point relative to the current time of {@link #getReferenceClock()} * as duration in given precision or less. </p> * * <p>If day precision is given then output like "today", "yesterday", * "tomorrow" or "last Wednesday" etc. is possible. Example: </p> * * <pre> * TimeSource<?> clock = new TimeSource<Moment>() { * public Moment currentTime() { * return PlainTimestamp.of(2015, 8, 1, 10, 24, 5).atUTC(); * }}; * String durationInDays = * PrettyTime.of(Locale.GERMAN).withReferenceClock(clock).printRelative( * PlainTimestamp.of(2015, 8, 1, 17, 0).atUTC(), * Timezone.of(EUROPE.BERLIN), * TimeUnit.DAYS); * System.out.println(durationInDays); // heute (german word for today) * </pre> * * @param moment relative time point * @param tz time zone for translating to a local duration * @param precision maximum precision of relative time (not more than seconds) * @return formatted output of relative time, either in past or in future * @since 3.6/4.4 */ /*[deutsch] * <p>Formatiert den angegebenen Zeitpunkt relativ zur aktuellen Zeit * der Referenzuhr {@link #getReferenceClock()} als Dauer in der angegebenen * maximalen Genauigkeit. </p> * * <p>Wenn Tagesgenauigkeit angegeben ist, sind auch Ausgaben wie "heute", "gestern", * "morgen" oder "letzten Mittwoch" etc. möglich. Beispiel: </p> * * <pre> * TimeSource<?> clock = new TimeSource<Moment>() { * public Moment currentTime() { * return PlainTimestamp.of(2015, 8, 1, 10, 24, 5).atUTC(); * }}; * String durationInDays = * PrettyTime.of(Locale.GERMAN).withReferenceClock(clock).printRelative( * PlainTimestamp.of(2015, 8, 1, 17, 0).atUTC(), * Timezone.of(EUROPE.BERLIN), * TimeUnit.DAYS); * System.out.println(durationInDays); // heute * </pre> * * @param moment relative time point * @param tz time zone for translating to a local duration * @param precision maximum precision of relative time (not more than seconds) * @return formatted output of relative time, either in past or in future * @since 3.6/4.4 */ public String printRelative( UnixTime moment, Timezone tz, TimeUnit precision ) { UnixTime ref = this.getReferenceClock().currentTime(); Moment t1 = Moment.from(ref); Moment t2 = Moment.from(moment); if (precision.compareTo(TimeUnit.SECONDS) <= 0) { long delta = t1.until(t2, TimeUnit.SECONDS); if (Math.abs(delta) < 60L) { return this.printRelativeSeconds(t1, t2, delta); } } return this.printRelativeTime(t1, t2, tz, precision, null, null); } /** * <p>Formats given time point relative to the current time of {@link #getReferenceClock()} * as duration in given precision or as absolute date-time. </p> * * <p>If the calculated duration in seconds is bigger than {@code maxdelta} then the absolute date-time * will be printed else a relative expression will be used. </p> * * @param moment relative time point * @param tz time zone for translating to a local duration * @param precision maximum precision of relative time (not more than seconds) * @param maxdelta maximum deviation of given moment from clock in posix seconds for relative printing * @param formatter used for printing absolute time if the deviation is bigger than maxdelta * @return formatted output of relative time, either in past or in future * @since 3.6/4.4 */ /*[deutsch] * <p>Formatiert den angegebenen Zeitpunkt relativ zur aktuellen Zeit * der Referenzuhr {@link #getReferenceClock()} als Dauer in der angegebenen * maximalen Genauigkeit oder als absolute Datumszeit. </p> * * <p>Wenn die berechnete Dauer in Sekunden größer als {@code maxdelta} ist, wird * die absolute Datumszeit formatiert, sonst wird ein relativer Ausdruck verwendet. </p> * * @param moment relative time point * @param tz time zone for translating to a local duration * @param precision maximum precision of relative time (not more than seconds) * @param maxdelta maximum deviation of given moment from clock in posix seconds for relative printing * @param formatter used for printing absolute time if the deviation is bigger than maxdelta * @return formatted output of relative time, either in past or in future * @since 3.6/4.4 */ public String printRelativeOrDateTime( UnixTime moment, Timezone tz, TimeUnit precision, long maxdelta, TemporalFormatter<Moment> formatter ) { UnixTime ref = this.getReferenceClock().currentTime(); Moment t1 = Moment.from(ref); Moment t2 = Moment.from(moment); long delta = t1.until(t2, TimeUnit.SECONDS); if (Math.abs(delta) > maxdelta) { return formatter.format(t2); } else if ( (precision.compareTo(TimeUnit.SECONDS) <= 0) && (Math.abs(delta) < 60L) ) { return this.printRelativeSeconds(t1, t2, delta); } return this.printRelativeTime(t1, t2, tz, precision, null, null); } /** * <p>Formats given time point relative to the current time of {@link #getReferenceClock()} * as duration in given precision or as absolute date-time. </p> * * @param moment time point whose deviation from clock is to be printed * @param tz time zone for translating to a local duration * @param precision maximum precision of relative time (not more than seconds) * @param maxRelativeUnit maximum time unit which will still be printed in a relative way * @param formatter used for printing absolute time if the leading unit is bigger than maxRelativeUnit * @return formatted output of relative time, either in past or in future * @since 3.6/4.4 */ /*[deutsch] * <p>Formatiert den angegebenen Zeitpunkt relativ zur aktuellen Zeit * der Referenzuhr {@link #getReferenceClock()} als Dauer in der angegebenen * maximalen Genauigkeit oder als absolute Datumszeit. </p> * * @param moment time point whose deviation from clock is to be printed * @param tz time zone for translating to a local duration * @param precision maximum precision of relative time (not more than seconds) * @param maxRelativeUnit maximum time unit which will still be printed in a relative way * @param formatter used for printing absolute time if the leading unit is bigger than maxRelativeUnit * @return formatted output of relative time, either in past or in future * @since 3.6/4.4 */ public String printRelativeOrDateTime( UnixTime moment, Timezone tz, TimeUnit precision, CalendarUnit maxRelativeUnit, TemporalFormatter<Moment> formatter ) { if (maxRelativeUnit == null) { throw new NullPointerException("Missing max relative unit."); } UnixTime ref = this.getReferenceClock().currentTime(); Moment t1 = Moment.from(ref); Moment t2 = Moment.from(moment); long delta = t1.until(t2, TimeUnit.SECONDS); if ( (precision.compareTo(TimeUnit.SECONDS) <= 0) && (Math.abs(delta) < 60L) ) { return this.printRelativeSeconds(t1, t2, delta); } return this.printRelativeTime(t1, t2, tz, precision, maxRelativeUnit, formatter); } /** * <p>Formats given date relative to the current date of {@link #getReferenceClock()} * as duration or as absolute date. </p> * * @param date calendar date whose deviation from clock is to be printed * @param tzid time zone identifier for getting current reference date * @param maxRelativeUnit maximum calendar unit which will still be printed in a relative way * @param formatter used for printing absolute date if the leading unit is bigger than maxRelativeUnit * @return formatted output of relative date, either in past or in future * @since 3.7/4.5 */ /*[deutsch] * <p>Formatiert das angegebene Datum relativ zum aktuellen Datum der Referenzuhr {@link #getReferenceClock()} * als Dauer oder als absolute Datumszeit. </p> * * @param date calendar date whose deviation from clock is to be printed * @param tzid time zone identifier for getting current reference date * @param maxRelativeUnit maximum calendar unit which will still be printed in a relative way * @param formatter used for printing absolute date if the leading unit is bigger than maxRelativeUnit * @return formatted output of relative date, either in past or in future * @since 3.7/4.5 */ public String printRelativeOrDate( PlainDate date, TZID tzid, CalendarUnit maxRelativeUnit, TemporalFormatter<PlainDate> formatter ) { if (maxRelativeUnit == null) { throw new NullPointerException("Missing max relative unit."); } Moment refTime = Moment.from(this.getReferenceClock().currentTime()); PlainDate refDate = refTime.toZonalTimestamp(tzid).toDate(); Duration<CalendarUnit> duration; if (this.weekToDays) { duration = Duration.inYearsMonthsDays().between(refDate, date); } else { CalendarUnit[] stdUnits = {YEARS, MONTHS, WEEKS, DAYS}; duration = Duration.in(stdUnits).between(refDate, date); } if (duration.isEmpty()) { return this.getEmptyRelativeString(TimeUnit.DAYS); } TimeSpan.Item<CalendarUnit> item = duration.getTotalLength().get(0); long amount = item.getAmount(); CalendarUnit unit = item.getUnit(); if (Double.compare(unit.getLength(), maxRelativeUnit.getLength()) > 0) { return formatter.format(date); } else if (unit.equals(CalendarUnit.DAYS)) { String replacement = this.getRelativeReplacement(date, duration.isNegative(), amount); if (!replacement.isEmpty()) { return replacement; } } String pattern = ( duration.isNegative() ? this.getPastPattern(amount, unit) : this.getFuturePattern(amount, unit)); return this.format(pattern, amount); } private String printRelativeSeconds( Moment t1, Moment t2, long delta ) { if (t1.getPosixTime() >= START_1972 && t2.getPosixTime() >= START_1972) { delta = SI.SECONDS.between(t1, t2); // leap second correction } if (delta == 0) { return UnitPatterns.of(this.locale).getNowWord(); } long amount = Math.abs(delta); String pattern = ( (delta < 0) ? this.getPastPattern(amount, ClockUnit.SECONDS) : this.getFuturePattern(amount, ClockUnit.SECONDS)); return this.format(pattern, amount); } private String printRelativeTime( Moment ref, Moment moment, Timezone tz, TimeUnit precision, CalendarUnit maxRelativeUnit, TemporalFormatter<Moment> formatter ) { PlainTimestamp start = PlainTimestamp.from( ref, tz.getOffset(ref)); PlainTimestamp end = PlainTimestamp.from( moment, tz.getOffset(moment)); IsoUnit[] units = (this.weekToDays ? TSP_UNITS : STD_UNITS); Duration<IsoUnit> duration = Duration.in(tz, units).between(start, end); if (duration.isEmpty()) { return this.getEmptyRelativeString(precision); } TimeSpan.Item<IsoUnit> item = duration.getTotalLength().get(0); long amount = item.getAmount(); IsoUnit unit = item.getUnit(); if (unit instanceof ClockUnit) { if (5 - ((ClockUnit) unit).ordinal() < precision.ordinal()) { return this.getEmptyRelativeString(precision); } } else if ( (maxRelativeUnit != null) && (Double.compare(unit.getLength(), maxRelativeUnit.getLength()) > 0) ) { return formatter.format(moment); } else if (unit.equals(CalendarUnit.DAYS)) { String replacement = this.getRelativeReplacement(end.toDate(), duration.isNegative(), amount); if (!replacement.isEmpty()) { return replacement; } } String pattern; if (duration.isNegative()) { if (unit.isCalendrical()) { pattern = this.getPastPattern(amount, (CalendarUnit) unit); } else { pattern = this.getPastPattern(amount, (ClockUnit) unit); } } else { if (unit.isCalendrical()) { pattern = this.getFuturePattern(amount, (CalendarUnit) unit); } else { pattern = this.getFuturePattern(amount, (ClockUnit) unit); } } return this.format(pattern, amount); } private String getRelativeReplacement( PlainDate date, boolean negative, long amount ) { if ((amount >= 1L) && (amount <= 7L)) { UnitPatterns patterns = UnitPatterns.of(this.locale); if (amount == 1L) { return (negative ? patterns.getYesterdayWord() : patterns.getTomorrowWord()); } else { Weekday dayOfWeek = date.getDayOfWeek(); return (negative ? patterns.labelForLast(dayOfWeek): patterns.labelForNext(dayOfWeek)); } } return ""; } private String getEmptyRelativeString(TimeUnit precision) { UnitPatterns patterns = UnitPatterns.of(this.locale); if (precision.equals(TimeUnit.DAYS)) { String replacement = patterns.getTodayWord(); if (!replacement.isEmpty()) { return replacement; } } return patterns.getNowWord(); } private String getPastPattern( long amount, CalendarUnit unit ) { UnitPatterns patterns = UnitPatterns.of(this.locale); PluralCategory category = this.getCategory(amount); return patterns.getPatternInPast(category, this.shortStyle, unit); } private String getFuturePattern( long amount, CalendarUnit unit ) { UnitPatterns patterns = UnitPatterns.of(this.locale); PluralCategory category = this.getCategory(amount); return patterns.getPatternInFuture(category, this.shortStyle, unit); } private String getPastPattern( long amount, ClockUnit unit ) { UnitPatterns patterns = UnitPatterns.of(this.locale); PluralCategory category = this.getCategory(amount); return patterns.getPatternInPast(category, this.shortStyle, unit); } private String getFuturePattern( long amount, ClockUnit unit ) { UnitPatterns patterns = UnitPatterns.of(this.locale); PluralCategory category = this.getCategory(amount); return patterns.getPatternInFuture(category, this.shortStyle, unit); } private PluralCategory getCategory(long amount) { return this.rules.getCategory(Math.abs(amount)); } private static void pushDuration( long[] values, Duration<?> duration, TimeSource<?> refClock, boolean weekToDays ) { int len = duration.getTotalLength().size(); for (int i = 0; i < len; i++) { TimeSpan.Item<? extends IsoUnit> item = duration.getTotalLength().get(i); IsoUnit unit = item.getUnit(); long amount = item.getAmount(); if (unit instanceof CalendarUnit) { push(values, CalendarUnit.class.cast(unit), amount, weekToDays); } else if (unit instanceof ClockUnit) { push(values, ClockUnit.class.cast(unit), amount); } else if (unit instanceof OverflowUnit) { push( values, OverflowUnit.class.cast(unit).getCalendarUnit(), amount, weekToDays); } else if (unit.equals(CalendarUnit.weekBasedYears())) { values[0] = MathUtils.safeAdd(amount, values[0]); // YEARS } else { // approximated duration by normalization without nanos Moment unix = Moment.from(refClock.currentTime()); PlainTimestamp start = unix.toZonalTimestamp(ZonalOffset.UTC); PlainTimestamp end = start.plus(amount, unit); IsoUnit[] units = (weekToDays ? TSP_UNITS : STD_UNITS); Duration<?> part = Duration.in(units).between(start, end); pushDuration(values, part, refClock, weekToDays); } } } private static void push( long[] values, CalendarUnit unit, long amount, boolean weekToDays ) { int index; switch (unit) { case MILLENNIA: amount = MathUtils.safeMultiply(amount, 1000); index = 0; // YEARS break; case CENTURIES: amount = MathUtils.safeMultiply(amount, 100); index = 0; // YEARS break; case DECADES: amount = MathUtils.safeMultiply(amount, 10); index = 0; // YEARS break; case YEARS: index = 0; // YEARS break; case QUARTERS: amount = MathUtils.safeMultiply(amount, 3); index = 1; // MONTHS break; case MONTHS: index = 1; // MONTHS break; case WEEKS: if (weekToDays) { amount = MathUtils.safeMultiply(amount, 7); index = 3; // DAYS } else { index = 2; // WEEKS } break; case DAYS: index = 3; // DAYS break; default: throw new UnsupportedOperationException(unit.name()); } values[index] = MathUtils.safeAdd(amount, values[index]); } private static void push( long[] values, ClockUnit unit, long amount ) { int index; switch (unit) { case HOURS: index = 4; break; case MINUTES: index = 5; break; case SECONDS: index = 6; break; case MILLIS: amount = MathUtils.safeMultiply(amount, MIO); index = 7; // NANOS break; case MICROS: amount = MathUtils.safeMultiply(amount, 1000); index = 7; // NANOS break; case NANOS: index = 7; // NANOS break; default: throw new UnsupportedOperationException(unit.name()); } values[index] = MathUtils.safeAdd(amount, values[index]); } private String format( long amount, IsoUnit unit, boolean negative, TextWidth width ) { long value = amount; if (negative) { value = MathUtils.safeNegate(amount); } if (SUPPORTED_UNITS.contains(unit)) { if (unit.isCalendrical()) { CalendarUnit u = CalendarUnit.class.cast(unit); return this.print(value, u, width); } else { ClockUnit u = ClockUnit.class.cast(unit); if (u == NANOS) { if ((amount % MIO) == 0) { u = MILLIS; value = value / MIO; } else if ((amount % 1000) == 0) { u = MICROS; value = value / 1000; } } return this.print(value, u, width); } } throw new UnsupportedOperationException("Unknown unit: " + unit); } private String format( String pattern, long amount ) { for (int i = 0, n = pattern.length(); i < n; i++) { if ( (i < n - 2) && (pattern.charAt(i) == '{') && (pattern.charAt(i + 1) == '0') && (pattern.charAt(i + 2) == '}') ) { StringBuilder sb = new StringBuilder(pattern); sb.replace(i, i + 3, this.format(amount)); return sb.toString(); } } if (amount < 0) { return this.minusSign + pattern; } return pattern; } private String format(long amount) { String num = String.valueOf(Math.abs(amount)); char zero = this.zeroDigit; StringBuilder sb = new StringBuilder(); if (amount < 0) { sb.append(this.minusSign); } for (int i = 0, n = num.length(); i < n; i++) { char c = num.charAt(i); if (zero != '0') { c = (char) (c + zero - '0'); } sb.append(c); } return sb.toString(); } }