com.ibm.icu.impl.DayPeriodRules 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#License
/*
*******************************************************************************
* Copyright (C) 2016, International Business Machines Corporation and
* others. All Rights Reserved.
*******************************************************************************
*/
package com.ibm.icu.impl;
import java.util.HashMap;
import java.util.Map;
import com.ibm.icu.util.ICUException;
import com.ibm.icu.util.ULocale;
import com.ibm.icu.util.UResourceBundle;
public final class DayPeriodRules {
public enum DayPeriod {
MIDNIGHT,
NOON,
MORNING1,
AFTERNOON1,
EVENING1,
NIGHT1,
MORNING2,
AFTERNOON2,
EVENING2,
NIGHT2,
AM,
PM;
public static DayPeriod[] VALUES = DayPeriod.values();
private static DayPeriod fromStringOrNull(CharSequence str) {
if ("midnight".contentEquals(str)) { return MIDNIGHT; }
if ("noon".contentEquals(str)) { return NOON; }
if ("morning1".contentEquals(str)) { return MORNING1; }
if ("afternoon1".contentEquals(str)) { return AFTERNOON1; }
if ("evening1".contentEquals(str)) { return EVENING1; }
if ("night1".contentEquals(str)) { return NIGHT1; }
if ("morning2".contentEquals(str)) { return MORNING2; }
if ("afternoon2".contentEquals(str)) { return AFTERNOON2; }
if ("evening2".contentEquals(str)) { return EVENING2; }
if ("night2".contentEquals(str)) { return NIGHT2; }
if ("am".contentEquals(str)) { return AM; }
if ("pm".contentEquals(str)) { return PM; }
return null;
}
}
private enum CutoffType {
BEFORE,
AFTER, // TODO: AFTER is deprecated in CLDR 29. Remove.
FROM,
AT;
private static CutoffType fromStringOrNull(CharSequence str) {
if ("from".contentEquals(str)) { return CutoffType.FROM; }
if ("before".contentEquals(str)) { return CutoffType.BEFORE; }
if ("after".contentEquals(str)) { return CutoffType.AFTER; }
if ("at".contentEquals(str)) { return CutoffType.AT; }
return null;
}
}
private static final class DayPeriodRulesData {
Map localesToRuleSetNumMap = new HashMap();
DayPeriodRules[] rules;
int maxRuleSetNum = -1;
}
private static final class DayPeriodRulesDataSink extends UResource.Sink {
private DayPeriodRulesData data;
private DayPeriodRulesDataSink(DayPeriodRulesData data) {
this.data = data;
}
@Override
public void put(UResource.Key key, UResource.Value value, boolean noFallback) {
UResource.Table dayPeriodData = value.getTable();
for (int i = 0; dayPeriodData.getKeyAndValue(i, key, value); ++i) {
if (key.contentEquals("locales")) {
UResource.Table locales = value.getTable();
for (int j = 0; locales.getKeyAndValue(j, key, value); ++j) {
int setNum = parseSetNum(value.getString());
data.localesToRuleSetNumMap.put(key.toString(), setNum);
}
} else if (key.contentEquals("rules")) {
UResource.Table rules = value.getTable();
processRules(rules, key, value);
}
}
}
private void processRules(UResource.Table rules, UResource.Key key, UResource.Value value) {
for (int i = 0; rules.getKeyAndValue(i, key, value); ++i) {
ruleSetNum = parseSetNum(key.toString());
data.rules[ruleSetNum] = new DayPeriodRules();
UResource.Table ruleSet = value.getTable();
for (int j = 0; ruleSet.getKeyAndValue(j, key, value); ++j) {
period = DayPeriod.fromStringOrNull(key);
if (period == null) { throw new ICUException("Unknown day period in data."); }
UResource.Table periodDefinition = value.getTable();
for (int k = 0; periodDefinition.getKeyAndValue(k, key, value); ++k) {
if (value.getType() == UResourceBundle.STRING) {
// Key-value pairs (e.g. before{6:00})
CutoffType type = CutoffType.fromStringOrNull(key);
addCutoff(type, value.getString());
} else {
// Arrays (e.g. before{6:00, 24:00}
cutoffType = CutoffType.fromStringOrNull(key);
UResource.Array cutoffArray = value.getArray();
int length = cutoffArray.getSize();
for (int l = 0; l < length; ++l) {
cutoffArray.getValue(l, value);
addCutoff(cutoffType, value.getString());
}
}
}
setDayPeriodForHoursFromCutoffs();
for (int k = 0; k < cutoffs.length; ++k) {
cutoffs[k] = 0;
}
}
for (DayPeriod period : data.rules[ruleSetNum].dayPeriodForHour) {
if (period == null) {
throw new ICUException("Rules in data don't cover all 24 hours (they should).");
}
}
}
}
// Members.
private int cutoffs[] = new int[25]; // [0] thru [24]; 24 is allowed is "before 24".
// "Path" to data.
private int ruleSetNum;
private DayPeriod period;
private CutoffType cutoffType;
// Helpers.
private void addCutoff(CutoffType type, String hourStr) {
if (type == null) { throw new ICUException("Cutoff type not recognized."); }
int hour = parseHour(hourStr);
cutoffs[hour] |= 1 << type.ordinal();
}
private void setDayPeriodForHoursFromCutoffs() {
DayPeriodRules rule = data.rules[ruleSetNum];
for (int startHour = 0; startHour <= 24; ++startHour) {
// AT cutoffs must be either midnight or noon.
if ((cutoffs[startHour] & (1 << CutoffType.AT.ordinal())) > 0) {
if (startHour == 0 && period == DayPeriod.MIDNIGHT) {
rule.hasMidnight = true;
} else if (startHour == 12 && period == DayPeriod.NOON) {
rule.hasNoon = true;
} else {
throw new ICUException("AT cutoff must only be set for 0:00 or 12:00.");
}
}
// FROM/AFTER and BEFORE must come in a pair.
if ((cutoffs[startHour] & (1 << CutoffType.FROM.ordinal())) > 0 ||
(cutoffs[startHour] & (1 << CutoffType.AFTER.ordinal())) > 0) {
for (int hour = startHour + 1;; ++hour) {
if (hour == startHour) {
// We've gone around the array once and can't find a BEFORE.
throw new ICUException(
"FROM/AFTER cutoffs must have a matching BEFORE cutoff.");
}
if (hour == 25) { hour = 0; }
if ((cutoffs[hour] & (1 << CutoffType.BEFORE.ordinal())) > 0) {
rule.add(startHour, hour, period);
break;
}
}
}
}
}
private static int parseHour(String str) {
int firstColonPos = str.indexOf(':');
if (firstColonPos < 0 || !str.substring(firstColonPos).equals(":00")) {
throw new ICUException("Cutoff time must end in \":00\".");
}
String hourStr = str.substring(0, firstColonPos);
if (firstColonPos != 1 && firstColonPos != 2) {
throw new ICUException("Cutoff time must begin with h: or hh:");
}
int hour = Integer.parseInt(hourStr);
// parseInt() throws NumberFormatException if hourStr isn't proper.
if (hour < 0 || hour > 24) {
throw new ICUException("Cutoff hour must be between 0 and 24, inclusive.");
}
return hour;
}
} // DayPeriodRulesDataSink
private static class DayPeriodRulesCountSink extends UResource.Sink {
private DayPeriodRulesData data;
private DayPeriodRulesCountSink(DayPeriodRulesData data) {
this.data = data;
}
@Override
public void put(UResource.Key key, UResource.Value value, boolean noFallback) {
UResource.Table rules = value.getTable();
for (int i = 0; rules.getKeyAndValue(i, key, value); ++i) {
int setNum = parseSetNum(key.toString());
if (setNum > data.maxRuleSetNum) {
data.maxRuleSetNum = setNum;
}
}
}
}
private static final DayPeriodRulesData DATA = loadData();
private boolean hasMidnight;
private boolean hasNoon;
private DayPeriod[] dayPeriodForHour;
private DayPeriodRules() {
hasMidnight = false;
hasNoon = false;
dayPeriodForHour = new DayPeriod[24];
}
/**
* Get a DayPeriodRules object given a locale.
* If data hasn't been loaded, it will be loaded for all locales at once.
* @param locale locale for which the DayPeriodRules object is requested.
* @return a DayPeriodRules object for `locale`.
*/
public static DayPeriodRules getInstance(ULocale locale) {
String localeCode = locale.getBaseName();
if (localeCode.isEmpty()) { localeCode = "root"; }
Integer ruleSetNum = null;
while (ruleSetNum == null) {
ruleSetNum = DATA.localesToRuleSetNumMap.get(localeCode);
if (ruleSetNum == null) {
localeCode = ULocale.getFallback(localeCode);
if (localeCode.isEmpty()) {
// Saves a lookup in the map.
break;
}
} else {
break;
}
}
if (ruleSetNum == null || DATA.rules[ruleSetNum] == null) {
// Data doesn't exist for the locale requested.
return null;
}
return DATA.rules[ruleSetNum];
}
public double getMidPointForDayPeriod(DayPeriod dayPeriod) {
int startHour = getStartHourForDayPeriod(dayPeriod);
int endHour = getEndHourForDayPeriod(dayPeriod);
double midPoint = (startHour + endHour) / 2.0;
if (startHour > endHour) {
// dayPeriod wraps around midnight. Shift midPoint by 12 hours, in the direction that
// lands it in [0, 24).
midPoint += 12;
if (midPoint >= 24) {
midPoint -= 24;
}
}
return midPoint;
}
private static DayPeriodRulesData loadData() {
DayPeriodRulesData data = new DayPeriodRulesData();
ICUResourceBundle rb = ICUResourceBundle.getBundleInstance(
ICUData.ICU_BASE_NAME,
"dayPeriods",
ICUResourceBundle.ICU_DATA_CLASS_LOADER,
true);
DayPeriodRulesCountSink countSink = new DayPeriodRulesCountSink(data);
rb.getAllItemsWithFallback("rules", countSink);
data.rules = new DayPeriodRules[data.maxRuleSetNum + 1];
DayPeriodRulesDataSink sink = new DayPeriodRulesDataSink(data);
rb.getAllItemsWithFallback("", sink);
return data;
}
private int getStartHourForDayPeriod(DayPeriod dayPeriod) throws IllegalArgumentException {
if (dayPeriod == DayPeriod.MIDNIGHT) { return 0; }
if (dayPeriod == DayPeriod.NOON) { return 12; }
if (dayPeriodForHour[0] == dayPeriod && dayPeriodForHour[23] == dayPeriod) {
// dayPeriod wraps around midnight. Start hour is later than end hour.
for (int i = 22; i >= 1; --i) {
if (dayPeriodForHour[i] != dayPeriod) {
return (i + 1);
}
}
} else {
for (int i = 0; i <= 23; ++i) {
if (dayPeriodForHour[i] == dayPeriod) {
return i;
}
}
}
// dayPeriod doesn't exist in rule set; throw exception.
throw new IllegalArgumentException();
}
private int getEndHourForDayPeriod(DayPeriod dayPeriod) {
if (dayPeriod == DayPeriod.MIDNIGHT) { return 0; }
if (dayPeriod == DayPeriod.NOON) { return 12; }
if (dayPeriodForHour[0] == dayPeriod && dayPeriodForHour[23] == dayPeriod) {
// dayPeriod wraps around midnight. End hour is before start hour.
for (int i = 1; i <= 22; ++i) {
if (dayPeriodForHour[i] != dayPeriod) {
// i o'clock is when a new period starts, therefore when the old period ends.
return i;
}
}
} else {
for (int i = 23; i >= 0; --i) {
if (dayPeriodForHour[i] == dayPeriod) {
return (i + 1);
}
}
}
// dayPeriod doesn't exist in rule set; throw exception.
throw new IllegalArgumentException();
}
// Getters.
public boolean hasMidnight() { return hasMidnight; }
public boolean hasNoon() { return hasNoon; }
public DayPeriod getDayPeriodForHour(int hour) { return dayPeriodForHour[hour]; }
// Helpers.
private void add(int startHour, int limitHour, DayPeriod period) {
for (int i = startHour; i != limitHour; ++i) {
if (i == 24) { i = 0; }
dayPeriodForHour[i] = period;
}
}
private static int parseSetNum(String setNumStr) {
if (!setNumStr.startsWith("set")) {
throw new ICUException("Set number should start with \"set\".");
}
String numStr = setNumStr.substring(3); // e.g. "set17" -> "17"
return Integer.parseInt(numStr); // This throws NumberFormatException if numStr isn't a proper number.
}
}