com.ibm.icu.impl.duration.BasicPeriodBuilderFactory Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of icu4j Show documentation
Show all versions of icu4j Show documentation
International Component for Unicode for Java (ICU4J) is a mature, widely used Java library
providing Unicode and Globalization support
// © 2016 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
/*
******************************************************************************
* Copyright (C) 2007-2011, International Business Machines Corporation and *
* others. All Rights Reserved. *
******************************************************************************
*/
package com.ibm.icu.impl.duration;
import java.util.TimeZone;
import com.ibm.icu.impl.duration.impl.DataRecord;
import com.ibm.icu.impl.duration.impl.PeriodFormatterData;
import com.ibm.icu.impl.duration.impl.PeriodFormatterDataService;
/**
* Default implementation of PeriodBuilderFactory. This creates builders that
* use approximate durations.
*/
class BasicPeriodBuilderFactory implements PeriodBuilderFactory {
private PeriodFormatterDataService ds;
private Settings settings;
private static final short allBits = 0xff;
BasicPeriodBuilderFactory(PeriodFormatterDataService ds) {
this.ds = ds;
this.settings = new Settings();
}
static long approximateDurationOf(TimeUnit unit) {
return TimeUnit.approxDurations[unit.ordinal];
}
class Settings {
boolean inUse;
short uset = allBits;
TimeUnit maxUnit = TimeUnit.YEAR;
TimeUnit minUnit = TimeUnit.MILLISECOND;
int maxLimit;
int minLimit;
boolean allowZero = true;
boolean weeksAloneOnly;
boolean allowMillis = true;
Settings setUnits(int uset) {
if (this.uset == uset) {
return this;
}
Settings result = inUse ? copy() : this;
result.uset = (short)uset;
if ((uset & allBits) == allBits) {
result.uset = allBits;
result.maxUnit = TimeUnit.YEAR;
result.minUnit = TimeUnit.MILLISECOND;
} else {
int lastUnit = -1;
for (int i = 0; i < TimeUnit.units.length; ++i) {
if (0 != (uset & (1 << i))) {
if (lastUnit == -1) {
result.maxUnit = TimeUnit.units[i];
}
lastUnit = i;
}
}
if (lastUnit == -1) {
// currently empty, but this might be transient so no fail
result.minUnit = result.maxUnit = null;
} else {
result.minUnit = TimeUnit.units[lastUnit];
}
}
return result;
}
short effectiveSet() {
if (allowMillis) {
return uset;
}
return (short)(uset & ~(1 << TimeUnit.MILLISECOND.ordinal));
}
TimeUnit effectiveMinUnit() {
if (allowMillis || minUnit != TimeUnit.MILLISECOND) {
return minUnit;
}
// -1 to skip millisecond
for (int i = TimeUnit.units.length - 1; --i >= 0;) {
if (0 != (uset & (1 << i))) {
return TimeUnit.units[i];
}
}
return TimeUnit.SECOND; // default for pathological case
}
Settings setMaxLimit(float maxLimit) {
int val = maxLimit <= 0 ? 0 : (int)(maxLimit*1000);
if (maxLimit == val) {
return this;
}
Settings result = inUse ? copy() : this;
result.maxLimit = val;
return result;
}
Settings setMinLimit(float minLimit) {
int val = minLimit <= 0 ? 0 : (int)(minLimit*1000);
if (minLimit == val) {
return this;
}
Settings result = inUse ? copy() : this;
result.minLimit = val;
return result;
}
Settings setAllowZero(boolean allow) {
if (this.allowZero == allow) {
return this;
}
Settings result = inUse ? copy() : this;
result.allowZero = allow;
return result;
}
Settings setWeeksAloneOnly(boolean weeksAlone) {
if (this.weeksAloneOnly == weeksAlone) {
return this;
}
Settings result = inUse ? copy() : this;
result.weeksAloneOnly = weeksAlone;
return result;
}
Settings setAllowMilliseconds(boolean allowMillis) {
if (this.allowMillis == allowMillis) {
return this;
}
Settings result = inUse ? copy() : this;
result.allowMillis = allowMillis;
return result;
}
Settings setLocale(String localeName) {
PeriodFormatterData data = ds.get(localeName);
return this
.setAllowZero(data.allowZero())
.setWeeksAloneOnly(data.weeksAloneOnly())
.setAllowMilliseconds(data.useMilliseconds() != DataRecord.EMilliSupport.NO);
}
Settings setInUse() {
inUse = true;
return this;
}
Period createLimited(long duration, boolean inPast) {
if (maxLimit > 0) {
long maxUnitDuration = approximateDurationOf(maxUnit);
if (duration * 1000 > maxLimit * maxUnitDuration) {
return Period.moreThan(maxLimit/1000f, maxUnit).inPast(inPast);
}
}
if (minLimit > 0) {
TimeUnit emu = effectiveMinUnit();
long emud = approximateDurationOf(emu);
long eml = (emu == minUnit) ? minLimit :
Math.max(1000, (approximateDurationOf(minUnit) * minLimit) / emud);
if (duration * 1000 < eml * emud) {
return Period.lessThan(eml/1000f, emu).inPast(inPast);
}
}
return null;
}
public Settings copy() {
Settings result = new Settings();
result.inUse = inUse;
result.uset = uset;
result.maxUnit = maxUnit;
result.minUnit = minUnit;
result.maxLimit = maxLimit;
result.minLimit = minLimit;
result.allowZero = allowZero;
result.weeksAloneOnly = weeksAloneOnly;
result.allowMillis = allowMillis;
return result;
}
}
@Override
public PeriodBuilderFactory setAvailableUnitRange(TimeUnit minUnit,
TimeUnit maxUnit) {
int uset = 0;
for (int i = maxUnit.ordinal; i <= minUnit.ordinal; ++i) {
uset |= 1 << i;
}
if (uset == 0) {
throw new IllegalArgumentException("range " + minUnit + " to " + maxUnit + " is empty");
}
settings = settings.setUnits(uset);
return this;
}
@Override
public PeriodBuilderFactory setUnitIsAvailable(TimeUnit unit,
boolean available) {
int uset = settings.uset;
if (available) {
uset |= 1 << unit.ordinal;
} else {
uset &= ~(1 << unit.ordinal);
}
settings = settings.setUnits(uset);
return this;
}
@Override
public PeriodBuilderFactory setMaxLimit(float maxLimit) {
settings = settings.setMaxLimit(maxLimit);
return this;
}
@Override
public PeriodBuilderFactory setMinLimit(float minLimit) {
settings = settings.setMinLimit(minLimit);
return this;
}
@Override
public PeriodBuilderFactory setAllowZero(boolean allow) {
settings = settings.setAllowZero(allow);
return this;
}
@Override
public PeriodBuilderFactory setWeeksAloneOnly(boolean aloneOnly) {
settings = settings.setWeeksAloneOnly(aloneOnly);
return this;
}
@Override
public PeriodBuilderFactory setAllowMilliseconds(boolean allow) {
settings = settings.setAllowMilliseconds(allow);
return this;
}
@Override
public PeriodBuilderFactory setLocale(String localeName) {
settings = settings.setLocale(localeName);
return this;
}
@Override
public PeriodBuilderFactory setTimeZone(TimeZone timeZone) {
// ignore this
return this;
}
private Settings getSettings() {
if (settings.effectiveSet() == 0) {
return null;
}
return settings.setInUse();
}
/**
* Return a builder that represents relative time in terms of the single
* given TimeUnit
*
* @param unit the single TimeUnit with which to represent times
* @return a builder
*/
@Override
public PeriodBuilder getFixedUnitBuilder(TimeUnit unit) {
return FixedUnitBuilder.get(unit, getSettings());
}
/**
* Return a builder that represents relative time in terms of the
* largest period less than or equal to the duration.
*
* @return a builder
*/
@Override
public PeriodBuilder getSingleUnitBuilder() {
return SingleUnitBuilder.get(getSettings());
}
/**
* Return a builder that formats the largest one or two periods,
* Starting with the largest period less than or equal to the duration.
* It formats two periods if the first period has a count < 2
* and the next period has a count >= 1.
*
* @return a builder
*/
@Override
public PeriodBuilder getOneOrTwoUnitBuilder() {
return OneOrTwoUnitBuilder.get(getSettings());
}
/**
* Return a builder that formats the given number of periods,
* starting with the largest period less than or equal to the
* duration.
*
* @return a builder
*/
@Override
public PeriodBuilder getMultiUnitBuilder(int periodCount) {
return MultiUnitBuilder.get(periodCount, getSettings());
}
}
abstract class PeriodBuilderImpl implements PeriodBuilder {
protected BasicPeriodBuilderFactory.Settings settings;
@Override
public Period create(long duration) {
return createWithReferenceDate(duration, System.currentTimeMillis());
}
public long approximateDurationOf(TimeUnit unit) {
return BasicPeriodBuilderFactory.approximateDurationOf(unit);
}
@Override
public Period createWithReferenceDate(long duration, long referenceDate) {
boolean inPast = duration < 0;
if (inPast) {
duration = -duration;
}
Period ts = settings.createLimited(duration, inPast);
if (ts == null) {
ts = handleCreate(duration, referenceDate, inPast);
if (ts == null) {
ts = Period.lessThan(1, settings.effectiveMinUnit()).inPast(inPast);
}
}
return ts;
}
@Override
public PeriodBuilder withTimeZone(TimeZone timeZone) {
// ignore the time zone
return this;
}
@Override
public PeriodBuilder withLocale(String localeName) {
BasicPeriodBuilderFactory.Settings newSettings = settings.setLocale(localeName);
if (newSettings != settings) {
return withSettings(newSettings);
}
return this;
}
protected abstract PeriodBuilder withSettings(BasicPeriodBuilderFactory.Settings settingsToUse);
protected abstract Period handleCreate(long duration, long referenceDate,
boolean inPast);
protected PeriodBuilderImpl(BasicPeriodBuilderFactory.Settings settings) {
this.settings = settings;
}
}
class FixedUnitBuilder extends PeriodBuilderImpl {
private TimeUnit unit;
public static FixedUnitBuilder get(TimeUnit unit, BasicPeriodBuilderFactory.Settings settingsToUse) {
if (settingsToUse != null && (settingsToUse.effectiveSet() & (1 << unit.ordinal)) != 0) {
return new FixedUnitBuilder(unit, settingsToUse);
}
return null;
}
FixedUnitBuilder(TimeUnit unit, BasicPeriodBuilderFactory.Settings settings) {
super(settings);
this.unit = unit;
}
@Override
protected PeriodBuilder withSettings(BasicPeriodBuilderFactory.Settings settingsToUse) {
return get(unit, settingsToUse);
}
@Override
protected Period handleCreate(long duration, long referenceDate,
boolean inPast) {
if (unit == null) {
return null;
}
long unitDuration = approximateDurationOf(unit);
return Period.at((float)((double)duration/unitDuration), unit)
.inPast(inPast);
}
}
class SingleUnitBuilder extends PeriodBuilderImpl {
SingleUnitBuilder(BasicPeriodBuilderFactory.Settings settings) {
super(settings);
}
public static SingleUnitBuilder get(BasicPeriodBuilderFactory.Settings settings) {
if (settings == null) {
return null;
}
return new SingleUnitBuilder(settings);
}
@Override
protected PeriodBuilder withSettings(BasicPeriodBuilderFactory.Settings settingsToUse) {
return SingleUnitBuilder.get(settingsToUse);
}
@Override
protected Period handleCreate(long duration, long referenceDate,
boolean inPast) {
short uset = settings.effectiveSet();
for (int i = 0; i < TimeUnit.units.length; ++i) {
if (0 != (uset & (1 << i))) {
TimeUnit unit = TimeUnit.units[i];
long unitDuration = approximateDurationOf(unit);
if (duration >= unitDuration) {
return Period.at((float)((double)duration/unitDuration), unit)
.inPast(inPast);
}
}
}
return null;
}
}
class OneOrTwoUnitBuilder extends PeriodBuilderImpl {
OneOrTwoUnitBuilder(BasicPeriodBuilderFactory.Settings settings) {
super(settings);
}
public static OneOrTwoUnitBuilder get(BasicPeriodBuilderFactory.Settings settings) {
if (settings == null) {
return null;
}
return new OneOrTwoUnitBuilder(settings);
}
@Override
protected PeriodBuilder withSettings(BasicPeriodBuilderFactory.Settings settingsToUse) {
return OneOrTwoUnitBuilder.get(settingsToUse);
}
@Override
protected Period handleCreate(long duration, long referenceDate,
boolean inPast) {
Period period = null;
short uset = settings.effectiveSet();
for (int i = 0; i < TimeUnit.units.length; ++i) {
if (0 != (uset & (1 << i))) {
TimeUnit unit = TimeUnit.units[i];
long unitDuration = approximateDurationOf(unit);
if (duration >= unitDuration || period != null) {
double count = (double)duration/unitDuration;
if (period == null) {
if (count >= 2) {
period = Period.at((float)count, unit);
break;
}
period = Period.at(1, unit).inPast(inPast);
duration -= unitDuration;
} else {
if (count >= 1) {
period = period.and((float)count, unit);
}
break;
}
}
}
}
return period;
}
}
class MultiUnitBuilder extends PeriodBuilderImpl {
private int nPeriods;
MultiUnitBuilder(int nPeriods, BasicPeriodBuilderFactory.Settings settings) {
super(settings);
this.nPeriods = nPeriods;
}
public static MultiUnitBuilder get(int nPeriods, BasicPeriodBuilderFactory.Settings settings) {
if (nPeriods > 0 && settings != null) {
return new MultiUnitBuilder(nPeriods, settings);
}
return null;
}
@Override
protected PeriodBuilder withSettings(BasicPeriodBuilderFactory.Settings settingsToUse) {
return MultiUnitBuilder.get(nPeriods, settingsToUse);
}
@Override
protected Period handleCreate(long duration, long referenceDate,
boolean inPast) {
Period period = null;
int n = 0;
short uset = settings.effectiveSet();
for (int i = 0; i < TimeUnit.units.length; ++i) {
if (0 != (uset & (1 << i))) {
TimeUnit unit = TimeUnit.units[i];
if (n == nPeriods) {
break;
}
long unitDuration = approximateDurationOf(unit);
if (duration >= unitDuration || n > 0) {
++n;
double count = (double)duration / unitDuration;
if (n < nPeriods) {
count = Math.floor(count);
duration -= (long)(count * unitDuration);
}
if (period == null) {
period = Period.at((float)count, unit).inPast(inPast);
} else {
period = period.and((float)count, unit);
}
}
}
}
return period;
}
}