blob: a5f4a0307765181a31e50c43a80f0973bba3ced4 [file] [log] [blame]
/*
******************************************************************************
* 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;
}
public String format(Period period) {
if (!period.isSet()) {
throw new IllegalArgumentException("period is not set");
}
return format(period.timeLimit, period.inFuture, period.counts);
}
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();
}
}