/*
* -----------------------------------------------------------------------
* Copyright © 2013-2015 Meno Hochschild, <http://www.menodata.de/>
* -----------------------------------------------------------------------
* This file (PrettyTime.java) is part of project Time4J.
*
* Time4J is free software: You can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* Time4J is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Time4J. If not, see <http://www.gnu.org/licenses/>.
* -----------------------------------------------------------------------
*/
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.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.temporal.TemporalAmount;
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.*;
import static net.time4j.ClockUnit.*;
/**
* <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
* @doctags.concurrency {immutable}
*/
/*[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
* @doctags.concurrency {immutable}
*/
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<>();
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<>();
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;
//~ Konstruktoren -----------------------------------------------------
private PrettyTime(
Locale loc,
TimeSource<?> refClock,
char zeroDigit,
String minusSign,
IsoUnit emptyUnit,
boolean weekToDays,
boolean shortStyle
) {
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;
}
//~ 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);
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);
}
/**
* <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);
}
/**
* <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);
}
/**
* <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);
}
/**
* <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);
}
/**
* <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);
}
/**
* <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);
}
/**
* <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>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>Short-cut for {@code print(Duration.from(threeten))}. </p>
*
* @param threeten object representing a duration which might contain several units and quantities
* @return formatted list output in normalized form
* @throws IllegalArgumentException in case of conversion failures
* @since 4.4
* @see #print(Duration)
*/
/*[deutsch]
* <p>Abkürzung für {@code print(Duration.from(threeten))}. </p>
*
* @param threeten object representing a duration which might contain several units and quantities
* @return formatted list output in normalized form
* @throws IllegalArgumentException in case of conversion failures
* @since 4.4
* @see #print(Duration)
*/
public String print(TemporalAmount threeten) {
return this.print(Duration.from(threeten));
}
/**
* <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>Short-cut for {@code print(Duration.from(threeten), width)}. </p>
*
* @param threeten object representing a duration which might contain several units and quantities
* @param width text width (ABBREVIATED as synonym for SHORT)
* @return formatted list output in normalized form
* @throws IllegalArgumentException in case of conversion failures
* @since 4.0
* @see #print(Duration, TextWidth)
*/
/*[deutsch]
* <p>Abkürzung für {@code print(Duration.from(threeten), width)}. </p>
*
* @param threeten object representing a duration which might contain several units and quantities
* @param width text width (ABBREVIATED as synonym for SHORT)
* @return formatted list output in normalized form
* @throws IllegalArgumentException in case of conversion failures
* @since 4.0
* @see #print(Duration, TextWidth)
*/
public String print(
TemporalAmount threeten,
TextWidth width
) {
return this.print(Duration.from(threeten), width);
}
/**
* <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<>();
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
return MessageFormat.format(
UnitPatterns.of(this.locale).getListPattern(width, count),
parts.toArray(new Object[count]));
}
/**
* <p>Short-cut for {@code print(Duration.from(threeten), width, printZero, maxLength)}. </p>
*
* @param threeten 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 in normalized form
* @throws IllegalArgumentException if maxLength is smaller than {@code 1} or in case of conversion failures
* @since 4.0
* @see #print(Duration, TextWidth, boolean, int)
*/
/*[deutsch]
* <p>Abkürzung für {@code print(Duration.from(threeten), width, printZero, maxLength)}. </p>
*
* @param threeten 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 in normalized form
* @throws IllegalArgumentException if maxLength is smaller than {@code 1} or in case of conversion failures
* @since 4.0
* @see #print(Duration, TextWidth, boolean, int)
*/
public String print(
TemporalAmount threeten,
TextWidth width,
boolean printZero,
int maxLength
) {
return this.print(Duration.from(threeten), width, printZero, maxLength);
}
/**
* <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 = () -> 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 = () -> 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 threeten object relative to the current time of
* {@link #getReferenceClock()} as duration in at most second
* precision or less. </p>
*
* @param zdt relative time
* @return formatted output of relative time, either in past or in future
* @see #printRelative(Instant, ZoneId)
* @since 4.8
*/
/*[deutsch]
* <p>Formatiert den angegebenen Zeitpunkt in JSR-310-Schreibweise relativ zur aktuellen Zeit
* der Referenzuhr {@link #getReferenceClock()} als Dauer in maximal
* Sekundengenauigkeit. </p>
*
* @param zdt relative time
* @return formatted output of relative time, either in past or in future
* @see #printRelative(Instant, ZoneId)
* @since 4.8
*/
public String printRelative(ZonedDateTime zdt) {
return this.printRelative(zdt.toInstant(), zdt.getZone());
}
/**
* <p>Formats given time point relative to the current time of
* {@link #getReferenceClock()} as duration in at most second
* precision or less. </p>
*
* @param instant relative time point
* @param zoneId 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 4.8
*/
/*[deutsch]
* <p>Formatiert den angegebenen Zeitpunkt relativ zur aktuellen Zeit
* der Referenzuhr {@link #getReferenceClock()} als Dauer in maximal
* Sekundengenauigkeit. </p>
*
* @param instant relative time point
* @param zoneId 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 4.8
*/
public String printRelative(
Instant instant,
ZoneId zoneId
) {
return this.printRelative(Moment.from(instant), Timezone.of(zoneId.getId()), 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" or
* "tomorrow" is possible. Example: </p>
*
* <pre>
* TimeSource<?> clock = () -> 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"
* oder "morgen" möglich. Beispiel: </p>
*
* <pre>
* TimeSource<?> clock = () -> 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>
*
* @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>
*
* @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 (
(amount == 1L)
&& unit.equals(CalendarUnit.DAYS)
) {
UnitPatterns patterns = UnitPatterns.of(this.locale);
String replacement = (duration.isNegative() ? patterns.getYesterdayWord() : patterns.getTomorrowWord());
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 (
(amount == 1L)
&& unit.equals(CalendarUnit.DAYS)
) {
UnitPatterns patterns = UnitPatterns.of(this.locale);
String replacement = (duration.isNegative() ? patterns.getYesterdayWord() : patterns.getTomorrowWord());
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 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();
}
}