com.ibm.icu.impl.duration.BasicPeriodFormatter Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of icu4j Show documentation
Show all versions of icu4j Show documentation
International Component for Unicode for Java (ICU4J) is a mature, widely used Java library
providing Unicode and Globalization support
// © 2016 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html#License
/*
******************************************************************************
* Copyright (C) 2007-2010, International Business Machines Corporation and *
* others. All Rights Reserved. *
******************************************************************************
*/
package com.ibm.icu.impl.duration;
import com.ibm.icu.impl.duration.BasicPeriodFormatterFactory.Customizations;
import com.ibm.icu.impl.duration.impl.DataRecord.ECountVariant;
import com.ibm.icu.impl.duration.impl.DataRecord.EMilliSupport;
import com.ibm.icu.impl.duration.impl.DataRecord.ESeparatorVariant;
import com.ibm.icu.impl.duration.impl.DataRecord.ETimeDirection;
import com.ibm.icu.impl.duration.impl.DataRecord.ETimeLimit;
import com.ibm.icu.impl.duration.impl.PeriodFormatterData;
/**
* Core implementation class for PeriodFormatter.
*/
class BasicPeriodFormatter implements PeriodFormatter {
private BasicPeriodFormatterFactory factory;
private String localeName;
private PeriodFormatterData data;
private Customizations customs;
BasicPeriodFormatter(BasicPeriodFormatterFactory factory,
String localeName,
PeriodFormatterData data,
Customizations customs) {
this.factory = factory;
this.localeName = localeName;
this.data = data;
this.customs = customs;
}
@Override
public String format(Period period) {
if (!period.isSet()) {
throw new IllegalArgumentException("period is not set");
}
return format(period.timeLimit, period.inFuture, period.counts);
}
@Override
public PeriodFormatter withLocale(String locName) {
if (!this.localeName.equals(locName)) {
PeriodFormatterData newData = factory.getData(locName);
return new BasicPeriodFormatter(factory, locName, newData,
customs);
}
return this;
}
private String format(int tl, boolean inFuture, int[] counts) {
int mask = 0;
for (int i = 0; i < counts.length; ++i) {
if (counts[i] > 0) {
mask |= 1 << i;
}
}
// if the data does not allow formatting of zero periods,
// remove these from consideration. If the result has no
// periods set, return null to indicate we could not format
// the duration.
if (!data.allowZero()) {
for (int i = 0, m = 1; i < counts.length; ++i, m <<= 1) {
if ((mask & m) != 0 && counts[i] == 1) {
mask &= ~m;
}
}
if (mask == 0) {
return null;
}
}
// if the data does not allow milliseconds but milliseconds are
// set, merge them with seconds and force display of seconds to
// decimal with 3 places.
boolean forceD3Seconds = false;
if (data.useMilliseconds() != EMilliSupport.YES &&
(mask & (1 << TimeUnit.MILLISECOND.ordinal)) != 0) {
int sx = TimeUnit.SECOND.ordinal;
int mx = TimeUnit.MILLISECOND.ordinal;
int sf = 1 << sx;
int mf = 1 << mx;
switch (data.useMilliseconds()) {
case EMilliSupport.WITH_SECONDS: {
// if there are seconds, merge with seconds, otherwise leave alone
if ((mask & sf) != 0) {
counts[sx] += (counts[mx]-1)/1000;
mask &= ~mf;
forceD3Seconds = true;
}
} break;
case EMilliSupport.NO: {
// merge with seconds, reset seconds before use just in case
if ((mask & sf) == 0) {
mask |= sf;
counts[sx] = 1;
}
counts[sx] += (counts[mx]-1)/1000;
mask &= ~mf;
forceD3Seconds = true;
} break;
}
}
// get the first and last units that are set.
int first = 0;
int last = counts.length - 1;
while (first < counts.length && (mask & (1 << first)) == 0) ++first;
while (last > first && (mask & (1 << last)) == 0) --last;
// determine if there is any non-zero unit
boolean isZero = true;
for (int i = first; i <= last; ++i) {
if (((mask & (1 << i)) != 0) && counts[i] > 1) {
isZero = false;
break;
}
}
StringBuffer sb = new StringBuffer();
// if we've been requested to not display a limit, or there are
// no non-zero units, do not display the limit.
if (!customs.displayLimit || isZero) {
tl = ETimeLimit.NOLIMIT;
}
// if we've been requested to not display the direction, or there
// are no non-zero units, do not display the direction.
int td;
if (!customs.displayDirection || isZero) {
td = ETimeDirection.NODIRECTION;
} else {
td = inFuture ? ETimeDirection.FUTURE : ETimeDirection.PAST;
}
// format the initial portion of the string before the units.
// record whether we need to use a digit prefix (because the
// initial portion forces it)
boolean useDigitPrefix = data.appendPrefix(tl, td, sb);
// determine some formatting params and initial values
boolean multiple = first != last;
boolean wasSkipped = true; // no initial skip marker
boolean skipped = false;
boolean countSep = customs.separatorVariant != ESeparatorVariant.NONE;
// loop for formatting the units
for (int i = first, j = i; i <= last; i = j) {
if (skipped) {
// we didn't format the previous unit
data.appendSkippedUnit(sb);
skipped = false;
wasSkipped = true;
}
while (++j < last && (mask & (1 << j)) == 0) {
skipped = true; // skip
}
TimeUnit unit = TimeUnit.units[i];
int count = counts[i] - 1;
int cv = customs.countVariant;
if (i == last) {
if (forceD3Seconds) {
cv = ECountVariant.DECIMAL3;
}
// else leave unchanged
} else {
cv = ECountVariant.INTEGER;
}
boolean isLast = i == last;
boolean mustSkip = data.appendUnit(unit, count, cv, customs.unitVariant,
countSep, useDigitPrefix, multiple, isLast, wasSkipped, sb);
skipped |= mustSkip;
wasSkipped = false;
if (customs.separatorVariant != ESeparatorVariant.NONE && j <= last) {
boolean afterFirst = i == first;
boolean beforeLast = j == last;
boolean fullSep = customs.separatorVariant == ESeparatorVariant.FULL;
useDigitPrefix = data.appendUnitSeparator(unit, fullSep, afterFirst, beforeLast, sb);
} else {
useDigitPrefix = false;
}
}
data.appendSuffix(tl, td, sb);
return sb.toString();
}
}