Index: src/js/i18n.js |
diff --git a/src/js/i18n.js b/src/js/i18n.js |
index a859a9fbeb9f1028e0c6245364a800651d8e7f29..c32e8f18e6938a4f396350c3bef3181cfe14ee8a 100644 |
--- a/src/js/i18n.js |
+++ b/src/js/i18n.js |
@@ -176,13 +176,26 @@ var TIMEZONE_NAME_CHECK_RE = UNDEFINED; |
function GetTimezoneNameCheckRE() { |
if (IS_UNDEFINED(TIMEZONE_NAME_CHECK_RE)) { |
- TIMEZONE_NAME_CHECK_RE = |
- new GlobalRegExp('^([A-Za-z]+)/([A-Za-z]+)(?:_([A-Za-z]+))*$'); |
+ TIMEZONE_NAME_CHECK_RE = new GlobalRegExp( |
+ '^([A-Za-z]+)/([A-Za-z_-]+)((?:\/[A-Za-z_-]+)+)*$'); |
} |
return TIMEZONE_NAME_CHECK_RE; |
} |
/** |
+ * Matches valid location parts of IANA time zone names. |
+ */ |
+var TIMEZONE_NAME_LOCATION_PART_RE = UNDEFINED; |
+ |
+function GetTimezoneNameLocationPartRE() { |
+ if (IS_UNDEFINED(TIMEZONE_NAME_LOCATION_PART_RE)) { |
+ TIMEZONE_NAME_LOCATION_PART_RE = |
+ new GlobalRegExp('^([A-Za-z]+)((?:[_-][A-Za-z]+)+)*$'); |
+ } |
+ return TIMEZONE_NAME_LOCATION_PART_RE; |
+} |
+ |
+/** |
* Adds bound method to the prototype of the given object. |
*/ |
function addBoundMethod(obj, methodName, implementation, length) { |
@@ -679,6 +692,34 @@ function toTitleCaseWord(word) { |
} |
/** |
+ * Returns titlecased location, bueNos_airES -> Buenos_Aires |
+ * or ho_cHi_minH -> Ho_Chi_Minh. It is locale-agnostic and only |
+ * deals with ASCII only characters. |
+ * 'of', 'au' and 'es' are special-cased and lowercased. |
+ */ |
+function toTitleCaseTimezoneLocation(location) { |
+ var match = %_Call(StringMatch, location, GetTimezoneNameLocationPartRE()); |
+ if (IS_NULL(match)) throw MakeRangeError(kExpectedLocation, location); |
+ |
+ var result = toTitleCaseWord(match[1]); |
+ if (!IS_UNDEFINED(match[2]) && 2 < match.length) { |
+ // The first character is a separator, '_' or '-'. |
+ // None of IANA zone names has both '_' and '-'. |
+ var separator = %_Call(StringSubstring, match[2], 0, 1); |
+ var parts = %_Call(StringSplit, match[2], separator); |
+ for (var i = 1; i < parts.length; i++) { |
+ var part = parts[i] |
+ var lowercasedPart = %StringToLowerCase(part); |
+ result = result + separator + |
+ ((lowercasedPart !== 'es' && |
+ lowercasedPart !== 'of' && lowercasedPart !== 'au') ? |
+ toTitleCaseWord(part) : lowercasedPart); |
+ } |
+ } |
+ return result; |
+} |
+ |
+/** |
* Canonicalizes the language tag, or throws in case the tag is invalid. |
*/ |
function canonicalizeLanguageTag(localeID) { |
@@ -1735,8 +1776,8 @@ addBoundMethod(Intl.DateTimeFormat, 'v8Parse', parseDate, 1); |
/** |
- * Returns canonical Area/Location name, or throws an exception if the zone |
- * name is invalid IANA name. |
+ * Returns canonical Area/Location(/Location) name, or throws an exception |
+ * if the zone name is invalid IANA name. |
*/ |
function canonicalizeTimeZoneID(tzID) { |
// Skip undefined zones. |
@@ -1751,16 +1792,22 @@ function canonicalizeTimeZoneID(tzID) { |
return 'UTC'; |
} |
- // We expect only _ and / beside ASCII letters. |
- // All inputs should conform to Area/Location from now on. |
+ // TODO(jshin): Add support for Etc/GMT[+-]([1-9]|1[0-2]) |
+ |
+ // We expect only _, '-' and / beside ASCII letters. |
+ // All inputs should conform to Area/Location(/Location)* from now on. |
var match = %_Call(StringMatch, tzID, GetTimezoneNameCheckRE()); |
- if (IS_NULL(match)) throw MakeRangeError(kExpectedLocation, tzID); |
+ if (IS_NULL(match)) throw MakeRangeError(kExpectedTimezoneID, tzID); |
+ |
+ var result = toTitleCaseTimezoneLocation(match[1]) + '/' + |
+ toTitleCaseTimezoneLocation(match[2]); |
- var result = toTitleCaseWord(match[1]) + '/' + toTitleCaseWord(match[2]); |
- var i = 3; |
- while (!IS_UNDEFINED(match[i]) && i < match.length) { |
- result = result + '_' + toTitleCaseWord(match[i]); |
- i++; |
+ if (!IS_UNDEFINED(match[3]) && 3 < match.length) { |
+ var locations = %_Call(StringSplit, match[3], '/'); |
+ // The 1st element is empty. Starts with i=1. |
+ for (var i = 1; i < locations.length; i++) { |
+ result = result + '/' + toTitleCaseTimezoneLocation(locations[i]); |
+ } |
} |
return result; |