382 lines
14 KiB
Java
382 lines
14 KiB
Java
/* GENERATED SOURCE. DO NOT MODIFY. */
|
|
// © 2016 and later: Unicode, Inc. and others.
|
|
// License & terms of use: http://www.unicode.org/copyright.html
|
|
/*
|
|
*******************************************************************************
|
|
* Copyright (C) 2016, International Business Machines Corporation and
|
|
* others. All Rights Reserved.
|
|
*******************************************************************************
|
|
*/
|
|
package android.icu.impl;
|
|
|
|
import java.util.HashMap;
|
|
import java.util.Map;
|
|
|
|
import android.icu.util.ICUException;
|
|
import android.icu.util.ULocale;
|
|
import android.icu.util.UResourceBundle;
|
|
|
|
/**
|
|
* @hide Only a subset of ICU is exposed in Android
|
|
*/
|
|
public final class DayPeriodRules {
|
|
/**
|
|
* @hide Only a subset of ICU is exposed in Android
|
|
*/
|
|
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<String, Integer> localesToRuleSetNumMap = new HashMap<String, Integer>();
|
|
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 {@code 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.
|
|
}
|
|
}
|