Skip to content

Commit 9c3e246

Browse files
jungshikMylesBorins
authored andcommitted
deps: backport 4e18190 from V8 upstream
Original commit message: Timezone name check fix 1. Location names with more than one underscores (e.g. Ho_Chi_Minh) didn't work because of the way capturing works with repeated patterns in RE. It's now supported by changing the RE to capture the whole string and splitting on '_' in the next step. 2. Adds support for location names with a hyphen 3. Adds support for timezone ids with three parts (e.g. American/Argentina/Buenos_Aires) 4. Adds special handling of 'au', 'es' and 'of' in zone ids. They need to be kept in lowercase. (see the full list at https://en.wikipedia.org/wiki/List_of_tz_database_time_zones ) 5. Adds regression tests for all the above and make the existing tests more robust against future ICU changes. ICU canonicalizes zone names to deprecated names, but it may change. ( http://bugs.icu-project.org/trac/ticket/12044 ) BUG=364374 LOG=Y Review URL: https://codereview.chromium.org/1529363005 Cr-Commit-Position: refs/heads/master@{#33097} PR-URL: #15562 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl> Reviewed-By: Steven R Loomis <srloomis@us.ibm.com>
1 parent 43d1ac3 commit 9c3e246

File tree

6 files changed

+146
-27
lines changed

6 files changed

+146
-27
lines changed

deps/v8/include/v8-version.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
#define V8_MAJOR_VERSION 4
1212
#define V8_MINOR_VERSION 5
1313
#define V8_BUILD_NUMBER 103
14-
#define V8_PATCH_LEVEL 52
14+
#define V8_PATCH_LEVEL 53
1515

1616
// Use 1 for candidates and 0 otherwise.
1717
// (Boolean macro values are not supported by all preprocessors.)

deps/v8/src/i18n.js

+59-12
Original file line numberDiff line numberDiff line change
@@ -170,12 +170,25 @@ var TIMEZONE_NAME_CHECK_RE = UNDEFINED;
170170

171171
function GetTimezoneNameCheckRE() {
172172
if (IS_UNDEFINED(TIMEZONE_NAME_CHECK_RE)) {
173-
TIMEZONE_NAME_CHECK_RE =
174-
new GlobalRegExp('^([A-Za-z]+)/([A-Za-z]+)(?:_([A-Za-z]+))*$');
173+
TIMEZONE_NAME_CHECK_RE = new GlobalRegExp(
174+
'^([A-Za-z]+)/([A-Za-z_-]+)((?:\/[A-Za-z_-]+)+)*$')
175175
}
176176
return TIMEZONE_NAME_CHECK_RE;
177177
}
178178

179+
/**
180+
* Matches valid location parts of IANA time zone names.
181+
*/
182+
var TIMEZONE_NAME_LOCATION_PART_RE = UNDEFINED;
183+
184+
function GetTimezoneNameLocationPartRE() {
185+
if (IS_UNDEFINED(TIMEZONE_NAME_LOCATION_PART_RE)) {
186+
TIMEZONE_NAME_LOCATION_PART_RE =
187+
new GlobalRegExp('^([A-Za-z]+)((?:[_-][A-Za-z]+)+)*$');
188+
}
189+
return TIMEZONE_NAME_LOCATION_PART_RE;
190+
}
191+
179192
/**
180193
* Adds bound method to the prototype of the given object.
181194
*/
@@ -672,6 +685,34 @@ function toTitleCaseWord(word) {
672685
%StringToLowerCase(%_CallFunction(word, 1, StringSubstr));
673686
}
674687

688+
/**
689+
* Returns titlecased location, bueNos_airES -> Buenos_Aires
690+
* or ho_cHi_minH -> Ho_Chi_Minh. It is locale-agnostic and only
691+
* deals with ASCII only characters.
692+
* 'of', 'au' and 'es' are special-cased and lowercased.
693+
*/
694+
function toTitleCaseTimezoneLocation(location) {
695+
var match = %_CallFunction(location, GetTimezoneNameLocationPartRE(), StringMatch);
696+
if (IS_NULL(match)) throw MakeRangeError(kExpectedLocation, location);
697+
698+
var result = toTitleCaseWord(match[1]);
699+
if (!IS_UNDEFINED(match[2]) && 2 < match.length) {
700+
// The first character is a separator, '_' or '-'.
701+
// None of IANA zone names has both '_' and '-'.
702+
var separator = %_CallFunction(match[2], 0, 1, StringSubstring);
703+
var parts = %_CallFunction(match[2], separator, StringSplit);
704+
for (var i = 1; i < parts.length; i++) {
705+
var part = parts[i]
706+
var lowercasedPart = %StringToLowerCase(part);
707+
result = result + separator +
708+
((lowercasedPart !== 'es' &&
709+
lowercasedPart !== 'of' && lowercasedPart !== 'au') ?
710+
toTitleCaseWord(part) : lowercasedPart);
711+
}
712+
}
713+
return result;
714+
}
715+
675716
/**
676717
* Canonicalizes the language tag, or throws in case the tag is invalid.
677718
*/
@@ -1723,8 +1764,8 @@ addBoundMethod(Intl.DateTimeFormat, 'v8Parse', parseDate, 1);
17231764

17241765

17251766
/**
1726-
* Returns canonical Area/Location name, or throws an exception if the zone
1727-
* name is invalid IANA name.
1767+
* Returns canonical Area/Location(/Location) name, or throws an exception
1768+
* if the zone name is invalid IANA name.
17281769
*/
17291770
function canonicalizeTimeZoneID(tzID) {
17301771
// Skip undefined zones.
@@ -1739,16 +1780,22 @@ function canonicalizeTimeZoneID(tzID) {
17391780
return 'UTC';
17401781
}
17411782

1742-
// We expect only _ and / beside ASCII letters.
1743-
// All inputs should conform to Area/Location from now on.
1783+
// TODO(jshin): Add support for Etc/GMT[+-]([1-9]|1[0-2])
1784+
1785+
// We expect only _, '-' and / beside ASCII letters.
1786+
// All inputs should conform to Area/Location(/Location)* from now on.
17441787
var match = %_CallFunction(tzID, GetTimezoneNameCheckRE(), StringMatch);
1745-
if (IS_NULL(match)) throw MakeRangeError(kExpectedLocation, tzID);
1788+
if (IS_NULL(match)) throw MakeRangeError(kExpectedTimezoneID, tzID);
1789+
1790+
var result = toTitleCaseTimezoneLocation(match[1]) + '/' +
1791+
toTitleCaseTimezoneLocation(match[2]);
17461792

1747-
var result = toTitleCaseWord(match[1]) + '/' + toTitleCaseWord(match[2]);
1748-
var i = 3;
1749-
while (!IS_UNDEFINED(match[i]) && i < match.length) {
1750-
result = result + '_' + toTitleCaseWord(match[i]);
1751-
i++;
1793+
if (!IS_UNDEFINED(match[3]) && 3 < match.length) {
1794+
var locations = %_CallFunction(match[3], '/', StringSplit);
1795+
// The 1st element is empty. Starts with i=1.
1796+
for (var i = 1; i < locations.length; i++) {
1797+
result = result + '/' + toTitleCaseTimezoneLocation(locations[i]);
1798+
}
17521799
}
17531800

17541801
return result;

deps/v8/src/messages.h

+5-1
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,11 @@ class CallSite {
261261
T(UnsupportedSuper, "Unsupported reference to 'super'") \
262262
/* RangeError */ \
263263
T(DateRange, "Provided date is not in valid range.") \
264-
T(ExpectedLocation, "Expected Area/Location for time zone, got %") \
264+
T(ExpectedTimezoneID, \
265+
"Expected Area/Location(/Location)* for time zone, got %") \
266+
T(ExpectedLocation, \
267+
"Expected letters optionally connected with underscores or hyphens for " \
268+
"a location, got %") \
265269
T(InvalidArrayBufferLength, "Invalid array buffer length") \
266270
T(InvalidArrayLength, "Invalid array length") \
267271
T(InvalidCodePoint, "Invalid code point %") \

deps/v8/test/mjsunit/regress/regress-487322.js

-13
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// Copyright 2015 the V8 project authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
if (this.Intl) {
6+
// chromium:364374
7+
8+
// Locations with 2 underscores are accepted and normalized.
9+
// 'of' and 'es' are always lowercased.
10+
df = new Intl.DateTimeFormat('en-US', {'timeZone': 'eUrope/isLe_OF_man'})
11+
assertEquals('Europe/Isle_of_Man', df.resolvedOptions().timeZone);
12+
13+
df = new Intl.DateTimeFormat('en-US', {'timeZone': 'africa/Dar_eS_salaam'})
14+
assertEquals('Africa/Dar_es_Salaam', df.resolvedOptions().timeZone);
15+
16+
df = new Intl.DateTimeFormat('en-US', {'timeZone': 'America/port_of_spain'})
17+
assertEquals('America/Port_of_Spain', df.resolvedOptions().timeZone);
18+
19+
// Zone ids with more than 2 parts are accepted and normalized.
20+
df = new Intl.DateTimeFormat('en-US', {'timeZone': 'America/north_Dakota/new_salem'})
21+
assertEquals('America/North_Dakota/New_Salem', df.resolvedOptions().timeZone);
22+
23+
// 3-part zone IDs are accepted and normalized.
24+
// Two Buenose Aires aliases are identical.
25+
df1 = new Intl.DateTimeFormat('en-US', {'timeZone': 'America/aRgentina/buenos_aIres'})
26+
df2 = new Intl.DateTimeFormat('en-US', {'timeZone': 'America/Argentina/Buenos_Aires'})
27+
assertEquals(df1.resolvedOptions().timeZone, df2.resolvedOptions().timeZone);
28+
29+
df2 = new Intl.DateTimeFormat('en-US', {'timeZone': 'America/Buenos_Aires'})
30+
assertEquals(df1.resolvedOptions().timeZone, df2.resolvedOptions().timeZone);
31+
32+
df1 = new Intl.DateTimeFormat('en-US', {'timeZone': 'America/Indiana/Indianapolis'})
33+
df2 = new Intl.DateTimeFormat('en-US', {'timeZone': 'America/Indianapolis'})
34+
assertEquals(df1.resolvedOptions().timeZone, df2.resolvedOptions().timeZone);
35+
36+
// ICU does not recognize East-Indiana. Add later when it does.
37+
// df2 = new Intl.DateTimeFormat('en-US', {'timeZone': 'America/East-Indiana'})
38+
// assertEquals(df1.resolvedOptions().timeZone, df2.resolvedOptions().timeZone);
39+
40+
41+
// Zone IDs with hyphens. 'au' has to be in lowercase.
42+
df = new Intl.DateTimeFormat('en-US', {'timeZone': 'America/port-aU-pRince'})
43+
assertEquals('America/Port-au-Prince', df.resolvedOptions().timeZone);
44+
45+
// Accepts Ho_Chi_Minh and treats it as identical to Saigon
46+
df1 = new Intl.DateTimeFormat('en-US', {'timeZone': 'Asia/Ho_Chi_Minh'})
47+
df2 = new Intl.DateTimeFormat('en-US', {'timeZone': 'Asia/Saigon'})
48+
assertEquals(df1.resolvedOptions().timeZone, df2.resolvedOptions().timeZone);
49+
50+
// Throws for invalid timezone ids.
51+
assertThrows(() => Intl.DateTimeFormat(undefined, {timeZone: 'Europe/_Paris'}));
52+
assertThrows(() => Intl.DateTimeFormat(undefined, {timeZone: 'America/New__York'}));
53+
assertThrows(() => Intl.DateTimeFormat(undefined, {timeZone: 'America//New_York'}));
54+
assertThrows(() => Intl.DateTimeFormat(undefined, {timeZone: 'America/New_York_'}));
55+
assertThrows(() => Intl.DateTimeFormat(undefined, {timeZone: 'America/New_Y0rk'}));
56+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Copyright 2015 the V8 project authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
if (this.Intl) {
6+
// Normalizes Kat{h,}mandu (chromium:487322)
7+
// According to the IANA timezone db, Kathmandu is the current canonical
8+
// name, but ICU got it backward. To make this test robust against a future
9+
// ICU change ( http://bugs.icu-project.org/trac/ticket/12044 ),
10+
// just check that Kat(h)mandu is resolved identically.
11+
df1 = new Intl.DateTimeFormat('en-US', {'timeZone': 'Asia/Katmandu'})
12+
df2 = new Intl.DateTimeFormat('en-US', {'timeZone': 'Asia/Kathmandu'})
13+
assertEquals(df1.resolvedOptions().timeZone, df2.resolvedOptions().timeZone);
14+
15+
// Normalizes Ulan_Bator to Ulaanbaatar. Unlike Kat(h)mandu, ICU got this
16+
// right so that we make sure that Ulan_Bator is resolved to Ulaanbaatar.
17+
df = new Intl.DateTimeFormat('en-US', {'timeZone': 'Asia/Ulaanbaatar'})
18+
assertEquals('Asia/Ulaanbaatar', df.resolvedOptions().timeZone);
19+
20+
df = new Intl.DateTimeFormat('en-US', {'timeZone': 'Asia/Ulan_Bator'})
21+
assertEquals('Asia/Ulaanbaatar', df.resolvedOptions().timeZone);
22+
23+
// Throws for unsupported time zones.
24+
assertThrows(() => Intl.DateTimeFormat(undefined, {timeZone: 'Aurope/Paris'}));
25+
}

0 commit comments

Comments
 (0)