| /* |
| ****************************************************************************** |
| * Copyright (C) 2007, 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.*; |
| 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 localeName) { |
| if (!this.localeName.equals(localeName)) { |
| PeriodFormatterData newData = factory.getData(localeName); |
| return new BasicPeriodFormatter(factory, localeName, 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(); |
| } |
| } |