/* ****************************************************************************** * Copyright (C) 2007-2008, 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 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(); } }