1/* 2****************************************************************************** 3* Copyright (C) 2007-2010, International Business Machines Corporation and * 4* others. All Rights Reserved. * 5****************************************************************************** 6*/ 7 8package com.ibm.icu.impl.duration; 9 10import com.ibm.icu.impl.duration.BasicPeriodFormatterFactory.Customizations; 11import com.ibm.icu.impl.duration.impl.DataRecord.ECountVariant; 12import com.ibm.icu.impl.duration.impl.DataRecord.EMilliSupport; 13import com.ibm.icu.impl.duration.impl.DataRecord.ESeparatorVariant; 14import com.ibm.icu.impl.duration.impl.DataRecord.ETimeDirection; 15import com.ibm.icu.impl.duration.impl.DataRecord.ETimeLimit; 16import com.ibm.icu.impl.duration.impl.PeriodFormatterData; 17 18/** 19 * Core implementation class for PeriodFormatter. 20 */ 21class BasicPeriodFormatter implements PeriodFormatter { 22 private BasicPeriodFormatterFactory factory; 23 private String localeName; 24 private PeriodFormatterData data; 25 private Customizations customs; 26 27 BasicPeriodFormatter(BasicPeriodFormatterFactory factory, 28 String localeName, 29 PeriodFormatterData data, 30 Customizations customs) { 31 this.factory = factory; 32 this.localeName = localeName; 33 this.data = data; 34 this.customs = customs; 35 } 36 37 public String format(Period period) { 38 if (!period.isSet()) { 39 throw new IllegalArgumentException("period is not set"); 40 } 41 return format(period.timeLimit, period.inFuture, period.counts); 42 } 43 44 public PeriodFormatter withLocale(String locName) { 45 if (!this.localeName.equals(locName)) { 46 PeriodFormatterData newData = factory.getData(locName); 47 return new BasicPeriodFormatter(factory, locName, newData, 48 customs); 49 } 50 return this; 51 } 52 53 private String format(int tl, boolean inFuture, int[] counts) { 54 int mask = 0; 55 for (int i = 0; i < counts.length; ++i) { 56 if (counts[i] > 0) { 57 mask |= 1 << i; 58 } 59 } 60 61 // if the data does not allow formatting of zero periods, 62 // remove these from consideration. If the result has no 63 // periods set, return null to indicate we could not format 64 // the duration. 65 if (!data.allowZero()) { 66 for (int i = 0, m = 1; i < counts.length; ++i, m <<= 1) { 67 if ((mask & m) != 0 && counts[i] == 1) { 68 mask &= ~m; 69 } 70 } 71 if (mask == 0) { 72 return null; 73 } 74 } 75 76 // if the data does not allow milliseconds but milliseconds are 77 // set, merge them with seconds and force display of seconds to 78 // decimal with 3 places. 79 boolean forceD3Seconds = false; 80 if (data.useMilliseconds() != EMilliSupport.YES && 81 (mask & (1 << TimeUnit.MILLISECOND.ordinal)) != 0) { 82 int sx = TimeUnit.SECOND.ordinal; 83 int mx = TimeUnit.MILLISECOND.ordinal; 84 int sf = 1 << sx; 85 int mf = 1 << mx; 86 switch (data.useMilliseconds()) { 87 case EMilliSupport.WITH_SECONDS: { 88 // if there are seconds, merge with seconds, otherwise leave alone 89 if ((mask & sf) != 0) { 90 counts[sx] += (counts[mx]-1)/1000; 91 mask &= ~mf; 92 forceD3Seconds = true; 93 } 94 } break; 95 case EMilliSupport.NO: { 96 // merge with seconds, reset seconds before use just in case 97 if ((mask & sf) == 0) { 98 mask |= sf; 99 counts[sx] = 1; 100 } 101 counts[sx] += (counts[mx]-1)/1000; 102 mask &= ~mf; 103 forceD3Seconds = true; 104 } break; 105 } 106 } 107 108 // get the first and last units that are set. 109 int first = 0; 110 int last = counts.length - 1; 111 while (first < counts.length && (mask & (1 << first)) == 0) ++first; 112 while (last > first && (mask & (1 << last)) == 0) --last; 113 114 // determine if there is any non-zero unit 115 boolean isZero = true; 116 for (int i = first; i <= last; ++i) { 117 if (((mask & (1 << i)) != 0) && counts[i] > 1) { 118 isZero = false; 119 break; 120 } 121 } 122 123 StringBuffer sb = new StringBuffer(); 124 125 // if we've been requested to not display a limit, or there are 126 // no non-zero units, do not display the limit. 127 if (!customs.displayLimit || isZero) { 128 tl = ETimeLimit.NOLIMIT; 129 } 130 131 // if we've been requested to not display the direction, or there 132 // are no non-zero units, do not display the direction. 133 int td; 134 if (!customs.displayDirection || isZero) { 135 td = ETimeDirection.NODIRECTION; 136 } else { 137 td = inFuture ? ETimeDirection.FUTURE : ETimeDirection.PAST; 138 } 139 140 // format the initial portion of the string before the units. 141 // record whether we need to use a digit prefix (because the 142 // initial portion forces it) 143 boolean useDigitPrefix = data.appendPrefix(tl, td, sb); 144 145 // determine some formatting params and initial values 146 boolean multiple = first != last; 147 boolean wasSkipped = true; // no initial skip marker 148 boolean skipped = false; 149 boolean countSep = customs.separatorVariant != ESeparatorVariant.NONE; 150 151 // loop for formatting the units 152 for (int i = first, j = i; i <= last; i = j) { 153 if (skipped) { 154 // we didn't format the previous unit 155 data.appendSkippedUnit(sb); 156 skipped = false; 157 wasSkipped = true; 158 } 159 160 while (++j < last && (mask & (1 << j)) == 0) { 161 skipped = true; // skip 162 } 163 164 TimeUnit unit = TimeUnit.units[i]; 165 int count = counts[i] - 1; 166 167 int cv = customs.countVariant; 168 if (i == last) { 169 if (forceD3Seconds) { 170 cv = ECountVariant.DECIMAL3; 171 } 172 // else leave unchanged 173 } else { 174 cv = ECountVariant.INTEGER; 175 } 176 boolean isLast = i == last; 177 boolean mustSkip = data.appendUnit(unit, count, cv, customs.unitVariant, 178 countSep, useDigitPrefix, multiple, isLast, wasSkipped, sb); 179 skipped |= mustSkip; 180 wasSkipped = false; 181 182 if (customs.separatorVariant != ESeparatorVariant.NONE && j <= last) { 183 boolean afterFirst = i == first; 184 boolean beforeLast = j == last; 185 boolean fullSep = customs.separatorVariant == ESeparatorVariant.FULL; 186 useDigitPrefix = data.appendUnitSeparator(unit, fullSep, afterFirst, beforeLast, sb); 187 } else { 188 useDigitPrefix = false; 189 } 190 } 191 data.appendSuffix(tl, td, sb); 192 193 return sb.toString(); 194 } 195} 196