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