packages/locale/src/format/cldr/CldrZoneTextPrinterParser.js
/*
* @copyright (c) 2017, Philipp Thuerwaechter & Pattrick Hueper
* @license BSD-3-Clause (see LICENSE.md in the root directory of this source tree)
*/
import {
_ as jodaInternal,
TextStyle,
TemporalQueries,
ZoneId,
ZoneOffset,
ZoneRulesProvider,
} from '@js-joda/core';
import { getOrCreateCldrInstance, getOrCreateMapZones, loadCldrData } from './CldrCache';
const { assert: { requireNonNull, requireInstance } } = jodaInternal;
//-----------------------------------------------------------------------
const LENGTH_COMPARATOR = (str1, str2) => {
let cmp = str2.length - str1.length;
if (cmp === 0) {
cmp = str1.localeCompare(str2);
}
return cmp;
};
/**
* Cache for `_cachedResolveZoneIdText`.
*
* Its basic structure is:
* Obj { locale: zoneId }
* Obj { zoneId: style}
* Obj { style: type}
* Obj { type: resolvedZoneIdText}
*/
const resolveZoneIdTextCache = {};
/**
* Prints or parses a zone ID.
*/
export default class CldrZoneTextPrinterParser {
/** The text style to output. */
constructor(textStyle) {
requireNonNull(textStyle, 'textStyle');
requireInstance(textStyle, TextStyle, 'textStyle');
this._textStyle = textStyle;
this._zoneIdsLocales = {};
loadCldrData('supplemental/likelySubtags.json');
loadCldrData('supplemental/metaZones.json');
}
_cachedResolveZoneIdText(cldr, zoneId, style, type) {
if (resolveZoneIdTextCache[cldr.locale] == null) {
resolveZoneIdTextCache[cldr.locale] = {};
}
const zoneIdToStyle = resolveZoneIdTextCache[cldr.locale];
if (zoneIdToStyle[zoneId] == null) {
zoneIdToStyle[zoneId] = {};
}
const styleToType = zoneIdToStyle[zoneId];
if (styleToType[style] == null) {
styleToType[style] = {};
}
const typeToResolvedZoneIdText = styleToType[style];
if (typeToResolvedZoneIdText[type] == null) {
typeToResolvedZoneIdText[type] = this._resolveZoneIdText(cldr, zoneId, style, type);
}
return typeToResolvedZoneIdText[type];
}
_resolveZoneIdText(cldr, zoneId, style, type) {
const zoneData = cldr.main(`dates/timeZoneNames/zone/${zoneId}/${style}/${type}`);
if (zoneData) {
return zoneData;
} else {
const metazoneInfo = cldr.get(`supplemental/metaZones/metazoneInfo/timezone/${zoneId}`);
if (metazoneInfo) {
// const zoneData = cldr.main(`dates/timeZoneNames/metazone/Acre`);
// TODO: determine metaZone for current temporal, for now, we use the last one :/
const metazone = metazoneInfo[metazoneInfo.length - 1]['usesMetazone']['_mzone'];
let metaZoneData = cldr.main(`dates/timeZoneNames/metazone/${metazone}/${style}/${type}`);
if (metaZoneData) {
return metaZoneData;
} else {
// type fallback, first generic, then standard
metaZoneData = cldr.main(`dates/timeZoneNames/metazone/${metazone}/${style}/generic`);
if (!metaZoneData) {
metaZoneData = cldr.main(`dates/timeZoneNames/metazone/${metazone}/${style}/standard`);
}
if (metaZoneData) {
return metaZoneData;
} else {
const mapZones = getOrCreateMapZones(cldr);
// find preferred Zone and resolve again
const preferredZone = mapZones[metazone][cldr.attributes.territory];
if (preferredZone) {
if (preferredZone !== zoneId) {
return this._cachedResolveZoneIdText(cldr, preferredZone, style, type);
}
} else {
// find golden Zone and resolve again
const goldenZone = mapZones[metazone]['001'];
if (goldenZone !== zoneId) {
return this._cachedResolveZoneIdText(cldr, goldenZone, style, type);
}
}
}
}
}
}
}
//-----------------------------------------------------------------------
print(context, buf) {
//see http://www.unicode.org/reports/tr35/tr35-dates.html#Time_Zone_Names
const zone = context.getValueQuery(TemporalQueries.zoneId());
/* istanbul ignore if */ // shouldn't happen... getValueQuery throws before returning null
if (zone == null) {
return false;
}
if (zone.normalized() instanceof ZoneOffset) {
buf.append(zone.id());
return true;
}
const daylight = false;
const hasDaylightSupport = false;
/* TODO: currently js-joda-timezone does not support ZoneRules.isDaylightSavings() ... uncomment if it does
const temporal = context.temporal();
if (temporal.isSupported(ChronoField.INSTANT_SECONDS)) {
hasDaylightSupport = true;
const instant = Instant.ofEpochSecond(temporal.getLong(ChronoField.INSTANT_SECONDS));
daylight = zone.rules().isDaylightSavings(instant);
}*/
const tzType = hasDaylightSupport ? (daylight ? 'daylight' : 'standard') : 'generic';
const tzstyle = (this._textStyle.asNormal() === TextStyle.FULL ? 'long' : 'short');
loadCldrData(`main/${context.locale().localeString()}/timeZoneNames.json`);
const cldr = getOrCreateCldrInstance(context.locale().localeString());
const text = this._cachedResolveZoneIdText(cldr, zone.id(), tzstyle, tzType);
if (text) {
buf.append(text);
} else {
// fallback, print zoneId
buf.append(zone.id());
}
return true;
}
_resolveZoneIds(localString) {
if(this._zoneIdsLocales[localString] != null) {
return this._zoneIdsLocales[localString];
}
const ids = {};
loadCldrData(`main/${localString}/timeZoneNames.json`);
const cldr = getOrCreateCldrInstance(localString);
for (const id of ZoneRulesProvider.getAvailableZoneIds()) {
ids[id] = id;
const tzstyle = (this._textStyle.asNormal() === TextStyle.FULL ? 'long' : 'short');
const genericText = this._cachedResolveZoneIdText(cldr, id, tzstyle, 'generic');
if (genericText) {
ids[genericText] = id;
}
const standardText = this._cachedResolveZoneIdText(cldr, id, tzstyle, 'standard');
if (standardText) {
ids[standardText] = id;
}
const daylightText = this._cachedResolveZoneIdText(cldr, id, tzstyle, 'daylight');
if (daylightText) {
ids[daylightText] = id;
}
}
// threeten is using a (sorted) TreeMap... so we need to sort the keys
const sortedKeys = Object.keys(ids).sort(LENGTH_COMPARATOR);
this._zoneIdsLocales[localString] = { ids, sortedKeys };
return this._zoneIdsLocales[localString];
}
// That's a very poor implementation, there are missing bug fixes and functionality from threeten and jdk
parse(context, text, position) {
for (const name of ['UTC', 'GMT']) {
if (context.subSequenceEquals(text, position, name, 0, name.length)) {
context.setParsedZone(ZoneId.of(name));
return position + name.length;
}
}
const { ids, sortedKeys } = this._resolveZoneIds(context.locale().localeString());
for (const name of sortedKeys) {
if (context.subSequenceEquals(text, position, name, 0, name.length)) {
context.setParsedZone(ZoneId.of(ids[name]));
return position + name.length;
}
}
return ~position;
}
toString() {
return `ZoneText(${this._textStyle})`;
}
}