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-2011, International Business Machines Corporation and * 6 * others. All Rights Reserved. * 7 ****************************************************************************** 8 */ 9 10 package com.ibm.icu.impl.duration; 11 12 import java.util.TimeZone; 13 14 import com.ibm.icu.impl.duration.impl.DataRecord; 15 import com.ibm.icu.impl.duration.impl.PeriodFormatterData; 16 import com.ibm.icu.impl.duration.impl.PeriodFormatterDataService; 17 18 /** 19 * Default implementation of PeriodBuilderFactory. This creates builders that 20 * use approximate durations. 21 */ 22 class BasicPeriodBuilderFactory implements PeriodBuilderFactory { 23 private PeriodFormatterDataService ds; 24 private Settings settings; 25 26 private static final short allBits = 0xff; 27 BasicPeriodBuilderFactory(PeriodFormatterDataService ds)28 BasicPeriodBuilderFactory(PeriodFormatterDataService ds) { 29 this.ds = ds; 30 this.settings = new Settings(); 31 } 32 approximateDurationOf(TimeUnit unit)33 static long approximateDurationOf(TimeUnit unit) { 34 return TimeUnit.approxDurations[unit.ordinal]; 35 } 36 37 class Settings { 38 boolean inUse; 39 short uset = allBits; 40 TimeUnit maxUnit = TimeUnit.YEAR; 41 TimeUnit minUnit = TimeUnit.MILLISECOND; 42 int maxLimit; 43 int minLimit; 44 boolean allowZero = true; 45 boolean weeksAloneOnly; 46 boolean allowMillis = true; 47 setUnits(int uset)48 Settings setUnits(int uset) { 49 if (this.uset == uset) { 50 return this; 51 } 52 Settings result = inUse ? copy() : this; 53 54 result.uset = (short)uset; 55 56 if ((uset & allBits) == allBits) { 57 result.uset = allBits; 58 result.maxUnit = TimeUnit.YEAR; 59 result.minUnit = TimeUnit.MILLISECOND; 60 } else { 61 int lastUnit = -1; 62 for (int i = 0; i < TimeUnit.units.length; ++i) { 63 if (0 != (uset & (1 << i))) { 64 if (lastUnit == -1) { 65 result.maxUnit = TimeUnit.units[i]; 66 } 67 lastUnit = i; 68 } 69 } 70 if (lastUnit == -1) { 71 // currently empty, but this might be transient so no fail 72 result.minUnit = result.maxUnit = null; 73 } else { 74 result.minUnit = TimeUnit.units[lastUnit]; 75 } 76 } 77 78 return result; 79 } 80 effectiveSet()81 short effectiveSet() { 82 if (allowMillis) { 83 return uset; 84 } 85 return (short)(uset & ~(1 << TimeUnit.MILLISECOND.ordinal)); 86 } 87 effectiveMinUnit()88 TimeUnit effectiveMinUnit() { 89 if (allowMillis || minUnit != TimeUnit.MILLISECOND) { 90 return minUnit; 91 } 92 // -1 to skip millisecond 93 for (int i = TimeUnit.units.length - 1; --i >= 0;) { 94 if (0 != (uset & (1 << i))) { 95 return TimeUnit.units[i]; 96 } 97 } 98 return TimeUnit.SECOND; // default for pathological case 99 } 100 setMaxLimit(float maxLimit)101 Settings setMaxLimit(float maxLimit) { 102 int val = maxLimit <= 0 ? 0 : (int)(maxLimit*1000); 103 if (maxLimit == val) { 104 return this; 105 } 106 Settings result = inUse ? copy() : this; 107 result.maxLimit = val; 108 return result; 109 } 110 setMinLimit(float minLimit)111 Settings setMinLimit(float minLimit) { 112 int val = minLimit <= 0 ? 0 : (int)(minLimit*1000); 113 if (minLimit == val) { 114 return this; 115 } 116 Settings result = inUse ? copy() : this; 117 result.minLimit = val; 118 return result; 119 } 120 setAllowZero(boolean allow)121 Settings setAllowZero(boolean allow) { 122 if (this.allowZero == allow) { 123 return this; 124 } 125 Settings result = inUse ? copy() : this; 126 result.allowZero = allow; 127 return result; 128 } 129 setWeeksAloneOnly(boolean weeksAlone)130 Settings setWeeksAloneOnly(boolean weeksAlone) { 131 if (this.weeksAloneOnly == weeksAlone) { 132 return this; 133 } 134 Settings result = inUse ? copy() : this; 135 result.weeksAloneOnly = weeksAlone; 136 return result; 137 } 138 setAllowMilliseconds(boolean allowMillis)139 Settings setAllowMilliseconds(boolean allowMillis) { 140 if (this.allowMillis == allowMillis) { 141 return this; 142 } 143 Settings result = inUse ? copy() : this; 144 result.allowMillis = allowMillis; 145 return result; 146 } 147 setLocale(String localeName)148 Settings setLocale(String localeName) { 149 PeriodFormatterData data = ds.get(localeName); 150 return this 151 .setAllowZero(data.allowZero()) 152 .setWeeksAloneOnly(data.weeksAloneOnly()) 153 .setAllowMilliseconds(data.useMilliseconds() != DataRecord.EMilliSupport.NO); 154 } 155 setInUse()156 Settings setInUse() { 157 inUse = true; 158 return this; 159 } 160 createLimited(long duration, boolean inPast)161 Period createLimited(long duration, boolean inPast) { 162 if (maxLimit > 0) { 163 long maxUnitDuration = approximateDurationOf(maxUnit); 164 if (duration * 1000 > maxLimit * maxUnitDuration) { 165 return Period.moreThan(maxLimit/1000f, maxUnit).inPast(inPast); 166 } 167 } 168 169 if (minLimit > 0) { 170 TimeUnit emu = effectiveMinUnit(); 171 long emud = approximateDurationOf(emu); 172 long eml = (emu == minUnit) ? minLimit : 173 Math.max(1000, (approximateDurationOf(minUnit) * minLimit) / emud); 174 if (duration * 1000 < eml * emud) { 175 return Period.lessThan(eml/1000f, emu).inPast(inPast); 176 } 177 } 178 return null; 179 } 180 copy()181 public Settings copy() { 182 Settings result = new Settings(); 183 result.inUse = inUse; 184 result.uset = uset; 185 result.maxUnit = maxUnit; 186 result.minUnit = minUnit; 187 result.maxLimit = maxLimit; 188 result.minLimit = minLimit; 189 result.allowZero = allowZero; 190 result.weeksAloneOnly = weeksAloneOnly; 191 result.allowMillis = allowMillis; 192 return result; 193 } 194 } 195 196 @Override setAvailableUnitRange(TimeUnit minUnit, TimeUnit maxUnit)197 public PeriodBuilderFactory setAvailableUnitRange(TimeUnit minUnit, 198 TimeUnit maxUnit) { 199 int uset = 0; 200 for (int i = maxUnit.ordinal; i <= minUnit.ordinal; ++i) { 201 uset |= 1 << i; 202 } 203 if (uset == 0) { 204 throw new IllegalArgumentException("range " + minUnit + " to " + maxUnit + " is empty"); 205 } 206 settings = settings.setUnits(uset); 207 return this; 208 } 209 210 @Override setUnitIsAvailable(TimeUnit unit, boolean available)211 public PeriodBuilderFactory setUnitIsAvailable(TimeUnit unit, 212 boolean available) { 213 int uset = settings.uset; 214 if (available) { 215 uset |= 1 << unit.ordinal; 216 } else { 217 uset &= ~(1 << unit.ordinal); 218 } 219 settings = settings.setUnits(uset); 220 return this; 221 } 222 223 @Override setMaxLimit(float maxLimit)224 public PeriodBuilderFactory setMaxLimit(float maxLimit) { 225 settings = settings.setMaxLimit(maxLimit); 226 return this; 227 } 228 229 @Override setMinLimit(float minLimit)230 public PeriodBuilderFactory setMinLimit(float minLimit) { 231 settings = settings.setMinLimit(minLimit); 232 return this; 233 } 234 235 @Override setAllowZero(boolean allow)236 public PeriodBuilderFactory setAllowZero(boolean allow) { 237 settings = settings.setAllowZero(allow); 238 return this; 239 } 240 241 @Override setWeeksAloneOnly(boolean aloneOnly)242 public PeriodBuilderFactory setWeeksAloneOnly(boolean aloneOnly) { 243 settings = settings.setWeeksAloneOnly(aloneOnly); 244 return this; 245 } 246 247 @Override setAllowMilliseconds(boolean allow)248 public PeriodBuilderFactory setAllowMilliseconds(boolean allow) { 249 settings = settings.setAllowMilliseconds(allow); 250 return this; 251 } 252 253 @Override setLocale(String localeName)254 public PeriodBuilderFactory setLocale(String localeName) { 255 settings = settings.setLocale(localeName); 256 return this; 257 } 258 259 @Override setTimeZone(TimeZone timeZone)260 public PeriodBuilderFactory setTimeZone(TimeZone timeZone) { 261 // ignore this 262 return this; 263 } 264 getSettings()265 private Settings getSettings() { 266 if (settings.effectiveSet() == 0) { 267 return null; 268 } 269 return settings.setInUse(); 270 } 271 272 /** 273 * Return a builder that represents relative time in terms of the single 274 * given TimeUnit 275 * 276 * @param unit the single TimeUnit with which to represent times 277 * @return a builder 278 */ 279 @Override getFixedUnitBuilder(TimeUnit unit)280 public PeriodBuilder getFixedUnitBuilder(TimeUnit unit) { 281 return FixedUnitBuilder.get(unit, getSettings()); 282 } 283 284 /** 285 * Return a builder that represents relative time in terms of the 286 * largest period less than or equal to the duration. 287 * 288 * @return a builder 289 */ 290 @Override getSingleUnitBuilder()291 public PeriodBuilder getSingleUnitBuilder() { 292 return SingleUnitBuilder.get(getSettings()); 293 } 294 295 /** 296 * Return a builder that formats the largest one or two periods, 297 * Starting with the largest period less than or equal to the duration. 298 * It formats two periods if the first period has a count < 2 299 * and the next period has a count >= 1. 300 * 301 * @return a builder 302 */ 303 @Override getOneOrTwoUnitBuilder()304 public PeriodBuilder getOneOrTwoUnitBuilder() { 305 return OneOrTwoUnitBuilder.get(getSettings()); 306 } 307 308 /** 309 * Return a builder that formats the given number of periods, 310 * starting with the largest period less than or equal to the 311 * duration. 312 * 313 * @return a builder 314 */ 315 @Override getMultiUnitBuilder(int periodCount)316 public PeriodBuilder getMultiUnitBuilder(int periodCount) { 317 return MultiUnitBuilder.get(periodCount, getSettings()); 318 } 319 } 320 321 abstract class PeriodBuilderImpl implements PeriodBuilder { 322 323 protected BasicPeriodBuilderFactory.Settings settings; 324 325 @Override create(long duration)326 public Period create(long duration) { 327 return createWithReferenceDate(duration, System.currentTimeMillis()); 328 } 329 approximateDurationOf(TimeUnit unit)330 public long approximateDurationOf(TimeUnit unit) { 331 return BasicPeriodBuilderFactory.approximateDurationOf(unit); 332 } 333 334 @Override createWithReferenceDate(long duration, long referenceDate)335 public Period createWithReferenceDate(long duration, long referenceDate) { 336 boolean inPast = duration < 0; 337 if (inPast) { 338 duration = -duration; 339 } 340 Period ts = settings.createLimited(duration, inPast); 341 if (ts == null) { 342 ts = handleCreate(duration, referenceDate, inPast); 343 if (ts == null) { 344 ts = Period.lessThan(1, settings.effectiveMinUnit()).inPast(inPast); 345 } 346 } 347 return ts; 348 } 349 350 @Override 351 public PeriodBuilder withTimeZone(TimeZone timeZone) { 352 // ignore the time zone 353 return this; 354 } 355 356 @Override 357 public PeriodBuilder withLocale(String localeName) { 358 BasicPeriodBuilderFactory.Settings newSettings = settings.setLocale(localeName); 359 if (newSettings != settings) { 360 return withSettings(newSettings); 361 } 362 return this; 363 } 364 365 protected abstract PeriodBuilder withSettings(BasicPeriodBuilderFactory.Settings settingsToUse); 366 367 protected abstract Period handleCreate(long duration, long referenceDate, 368 boolean inPast); 369 370 protected PeriodBuilderImpl(BasicPeriodBuilderFactory.Settings settings) { 371 this.settings = settings; 372 } 373 } 374 375 class FixedUnitBuilder extends PeriodBuilderImpl { 376 private TimeUnit unit; 377 378 public static FixedUnitBuilder get(TimeUnit unit, BasicPeriodBuilderFactory.Settings settingsToUse) { 379 if (settingsToUse != null && (settingsToUse.effectiveSet() & (1 << unit.ordinal)) != 0) { 380 return new FixedUnitBuilder(unit, settingsToUse); 381 } 382 return null; 383 } 384 385 FixedUnitBuilder(TimeUnit unit, BasicPeriodBuilderFactory.Settings settings) { 386 super(settings); 387 this.unit = unit; 388 } 389 390 @Override 391 protected PeriodBuilder withSettings(BasicPeriodBuilderFactory.Settings settingsToUse) { 392 return get(unit, settingsToUse); 393 } 394 395 @Override 396 protected Period handleCreate(long duration, long referenceDate, 397 boolean inPast) { 398 if (unit == null) { 399 return null; 400 } 401 long unitDuration = approximateDurationOf(unit); 402 return Period.at((float)((double)duration/unitDuration), unit) 403 .inPast(inPast); 404 } 405 } 406 407 class SingleUnitBuilder extends PeriodBuilderImpl { 408 SingleUnitBuilder(BasicPeriodBuilderFactory.Settings settings) { 409 super(settings); 410 } 411 412 public static SingleUnitBuilder get(BasicPeriodBuilderFactory.Settings settings) { 413 if (settings == null) { 414 return null; 415 } 416 return new SingleUnitBuilder(settings); 417 } 418 419 @Override 420 protected PeriodBuilder withSettings(BasicPeriodBuilderFactory.Settings settingsToUse) { 421 return SingleUnitBuilder.get(settingsToUse); 422 } 423 424 @Override 425 protected Period handleCreate(long duration, long referenceDate, 426 boolean inPast) { 427 short uset = settings.effectiveSet(); 428 for (int i = 0; i < TimeUnit.units.length; ++i) { 429 if (0 != (uset & (1 << i))) { 430 TimeUnit unit = TimeUnit.units[i]; 431 long unitDuration = approximateDurationOf(unit); 432 if (duration >= unitDuration) { 433 return Period.at((float)((double)duration/unitDuration), unit) 434 .inPast(inPast); 435 } 436 } 437 } 438 return null; 439 } 440 } 441 442 class OneOrTwoUnitBuilder extends PeriodBuilderImpl { 443 OneOrTwoUnitBuilder(BasicPeriodBuilderFactory.Settings settings) { 444 super(settings); 445 } 446 447 public static OneOrTwoUnitBuilder get(BasicPeriodBuilderFactory.Settings settings) { 448 if (settings == null) { 449 return null; 450 } 451 return new OneOrTwoUnitBuilder(settings); 452 } 453 454 @Override 455 protected PeriodBuilder withSettings(BasicPeriodBuilderFactory.Settings settingsToUse) { 456 return OneOrTwoUnitBuilder.get(settingsToUse); 457 } 458 459 @Override 460 protected Period handleCreate(long duration, long referenceDate, 461 boolean inPast) { 462 Period period = null; 463 short uset = settings.effectiveSet(); 464 for (int i = 0; i < TimeUnit.units.length; ++i) { 465 if (0 != (uset & (1 << i))) { 466 TimeUnit unit = TimeUnit.units[i]; 467 long unitDuration = approximateDurationOf(unit); 468 if (duration >= unitDuration || period != null) { 469 double count = (double)duration/unitDuration; 470 if (period == null) { 471 if (count >= 2) { 472 period = Period.at((float)count, unit); 473 break; 474 } 475 period = Period.at(1, unit).inPast(inPast); 476 duration -= unitDuration; 477 } else { 478 if (count >= 1) { 479 period = period.and((float)count, unit); 480 } 481 break; 482 } 483 } 484 } 485 } 486 return period; 487 } 488 } 489 490 class MultiUnitBuilder extends PeriodBuilderImpl { 491 private int nPeriods; 492 493 MultiUnitBuilder(int nPeriods, BasicPeriodBuilderFactory.Settings settings) { 494 super(settings); 495 this.nPeriods = nPeriods; 496 } 497 498 public static MultiUnitBuilder get(int nPeriods, BasicPeriodBuilderFactory.Settings settings) { 499 if (nPeriods > 0 && settings != null) { 500 return new MultiUnitBuilder(nPeriods, settings); 501 } 502 return null; 503 } 504 505 @Override 506 protected PeriodBuilder withSettings(BasicPeriodBuilderFactory.Settings settingsToUse) { 507 return MultiUnitBuilder.get(nPeriods, settingsToUse); 508 } 509 510 @Override 511 protected Period handleCreate(long duration, long referenceDate, 512 boolean inPast) { 513 Period period = null; 514 int n = 0; 515 short uset = settings.effectiveSet(); 516 for (int i = 0; i < TimeUnit.units.length; ++i) { 517 if (0 != (uset & (1 << i))) { 518 TimeUnit unit = TimeUnit.units[i]; 519 if (n == nPeriods) { 520 break; 521 } 522 long unitDuration = approximateDurationOf(unit); 523 if (duration >= unitDuration || n > 0) { 524 ++n; 525 double count = (double)duration / unitDuration; 526 if (n < nPeriods) { 527 count = Math.floor(count); 528 duration -= (long)(count * unitDuration); 529 } 530 if (period == null) { 531 period = Period.at((float)count, unit).inPast(inPast); 532 } else { 533 period = period.and((float)count, unit); 534 } 535 } 536 } 537 } 538 return period; 539 } 540 } 541 542