Skip to content

Commit

Permalink
Add default TZ to list of valid TZs
Browse files Browse the repository at this point in the history
Summary: For some reason, Apple will return a timezone via the default timezone and then report that it is not in its list of `knownTimeZoneNames`. However, it can still be passed to the constructor of `NSTimeZone`, so for all intents and purposes it should be considered a valid time zone. Therefore, we manually add the TZ. However, because there is a chance that the default TZ can change while the program is running, we must keep track of a list of all valid timezones. This begins as just the knownTimeZoneNames. It is then potentially appended to every time the default time zone is requested, with the value of the default time zone.

Reviewed By: neildhar

Differential Revision: D41042493

fbshipit-source-id: ec72e7692acd7a0f823f70e7f9070d8d5a4c2768
  • Loading branch information
fbmal7 authored and facebook-github-bot committed Nov 12, 2022
1 parent 2fbd6ac commit 1fe1ccb
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 17 deletions.
55 changes: 38 additions & 17 deletions lib/Platform/Intl/PlatformIntlApple.mm
Original file line number Diff line number Diff line change
Expand Up @@ -571,24 +571,50 @@ ResolvedLocale resolveLocale(
return result;
}

static std::unordered_map<std::u16string, std::u16string> validTimeZoneNames() {
std::unordered_map<std::u16string, std::u16string> result;
static auto nsTimeZoneNames = [NSTimeZone.knownTimeZoneNames
arrayByAddingObjectsFromArray:NSTimeZone.abbreviationDictionary.allKeys];
for (NSString *timeZoneName in nsTimeZoneNames) {
result.emplace(
nsStringToU16String(timeZoneName.uppercaseString),
nsStringToU16String(timeZoneName));
}
return result;
static std::unordered_map<std::u16string, std::u16string>
&validTimeZoneNames() {
// This stores all the timezones the Intl API considers valid. It is
// intentionally leaked to avoid destruction order problems.
static auto *validNames = [] {
auto nsTimeZoneNames = [NSTimeZone.knownTimeZoneNames
arrayByAddingObjectsFromArray:NSTimeZone.abbreviationDictionary
.allKeys];
auto *names = new std::unordered_map<std::u16string, std::u16string>();
for (NSString *timeZoneName in nsTimeZoneNames) {
auto canonical = nsStringToU16String(timeZoneName);
auto upper = toASCIIUppercase(canonical);
names->emplace(std::move(upper), std::move(canonical));
}
return names;
}();
return *validNames;
}

// https://402.ecma-international.org/8.0/#sec-defaulttimezone
// There is a weird quirk on iOS regarding default time zones and
// NSTimeZone.knownTimeZoneNames. Unforunately, knownTimeZoneNames is not
// accurate. There is a chance the default timezone reported via
// NSTimeZone.defaultTimeZone is not present in knownTimeZoneNames. However, the
// NSTimeZone constructor can actually still understand this default TZ.
// Therefore, anytime the defaultTimeZone is requested we record it by manually
// inserting it into the timezones we consider valid. Note we have to do this
// every time the method is called, because there is a chance the default
// timezone can change while the program is running. In that case, we would want
// to accept both the old and new timezones.
std::u16string getDefaultTimeZone(NSLocale *locale) {
std::u16string tz = nsStringToU16String(NSTimeZone.defaultTimeZone.name);
// emplace won't insert duplicates if the TZ hasn't changed since the last
// call to getDefaultTimeZone.
validTimeZoneNames().emplace(toASCIIUppercase(tz), tz);
return tz;
}

// https://402.ecma-international.org/8.0/#sec-canonicalizetimezonename
std::u16string canonicalizeTimeZoneName(std::u16string_view tz) {
// 1. Let ianaTimeZone be the Zone or Link name of the IANA Time Zone Database
// such that timeZone, converted to upper case as described in 6.1, is equal
// to ianaTimeZone, converted to upper case as described in 6.1.
static const auto timeZones = validTimeZoneNames();
const auto &timeZones = validTimeZoneNames();
auto ianaTimeZoneIt = timeZones.find(toASCIIUppercase(tz));
auto ianaTimeZone = (ianaTimeZoneIt != timeZones.end())
? ianaTimeZoneIt->second
Expand All @@ -604,14 +630,9 @@ ResolvedLocale resolveLocale(
return ianaTimeZone;
}

// https://402.ecma-international.org/8.0/#sec-defaulttimezone
std::u16string getDefaultTimeZone(NSLocale *locale) {
return nsStringToU16String(NSTimeZone.defaultTimeZone.name);
}

// https://402.ecma-international.org/8.0/#sec-isvalidtimezonename
static bool isValidTimeZoneName(std::u16string_view tz) {
static const auto timeZones = validTimeZoneNames();
const auto &timeZones = validTimeZoneNames();
return timeZones.find(toASCIIUppercase(tz)) != timeZones.end();
}

Expand Down
24 changes: 24 additions & 0 deletions test/hermes/intl/default-tz.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/


// RUN: TZ=US/Pacific %hermes -O -target=HBC %s | %FileCheck --match-full-lines %s
// REQUIRES: intl

print("test default timezone");
// CHECK-LABEL: test default timezone

const timeZone = new Intl.DateTimeFormat().resolvedOptions().timeZone;
const options = {
year: 'numeric', month: 'numeric', day: 'numeric',
hour: 'numeric', minute: 'numeric', second: 'numeric',
hour12: false,
timeZone: timeZone
};
const date = new Date(Date.UTC(2020, 0, 2, 3, 45, 00, 30));
print(new Intl.DateTimeFormat('en-US', options).format(date));
// CHECK-NEXT: 1/1/2020, 19:45:00

0 comments on commit 1fe1ccb

Please sign in to comment.