Merge pull request #3131 from daboe01/fixed-CPTimeZone-initWithName-does-not-return-the-timeZone-with-the-good-daylight-saving-

fixed: CPTimeZone initWithName: does not return the timeZone with the good daylight saving
This commit is contained in:
daboe01 authored and GitHub committed 2025-07-05 13:16:56 +02:00
commit fc582d8125
2 files changed
+87 -19

No files matched your search

+55 -7
View File
@@ -105,6 +105,35 @@ function abbreviationForDate(date)
// Return nil if no valid abbreviation could be determined.
return nil;
}
function _abbreviationForNameAndDate(tzName, date)
{
// This is a helper function based on the existing `abbreviationForDate`.
// It determines the abbreviation for a given IANA name based on the provided date,
// which allows it to respect daylight saving time.
try {
var options = {
timeZone: tzName,
timeZoneName: 'long'
};
// The 'en-US' locale provides a predictable format for parsing.
var dateString = date.toLocaleString('en-US', options);
// This regex is copied from the global 'abbreviationForDate' function.
// It strips the date and time, leaving the long time zone name.
var longTZName = dateString.replace(/^([0]?\d|[1][0-2])\/((?:[0]?|[1-2])\d|[3][0-1])\/([2][01]|[1][6-9])\d{2}(,?\s*([0]?\d|[1][0-2])(\:[0-5]\d){1,2})*\s*([aApP][mM]{0,2})?\s*/, "");
// Create the abbreviation from the long name (e.g., "Pacific Daylight Time" -> "PDT")
var abbreviation = longTZName.split(" ").map(function(l) { return l[0]}).join("");
return abbreviation;
} catch (e) {
// The tzName might be invalid for toLocaleString, which throws a RangeError.
// In this case, we can't determine the abbreviation.
return nil;
}
}
/*!
@class CPTimeZone
@ingroup foundation
@@ -574,19 +603,38 @@ function abbreviationForDate(date)
{
_name = tzName;
var keys = [abbreviationDictionary keyEnumerator],
key;
// Determine the abbreviation based on the current date to handle DST.
var currentAbbreviation = _abbreviationForNameAndDate(tzName, [CPDate date]);
while (key = [keys nextObject])
// If we got a valid abbreviation from the date, and it's one we know about, use it.
// Otherwise, fall back to the old logic.
if (currentAbbreviation && [abbreviationDictionary containsKey:currentAbbreviation])
{
var value = [abbreviationDictionary valueForKey:key];
_abbreviation = currentAbbreviation;
}
else
{
// FALLBACK: Find the first matching abbreviation in the dictionary.
// Note: This is not DST-aware and may not be correct, but it preserves
// the original behavior for cases where the dynamic lookup fails.
var keys = [abbreviationDictionary keyEnumerator],
key;
if ([value isEqualToString:_name])
while (key = [keys nextObject])
{
_abbreviation = key;
break;
var value = [abbreviationDictionary valueForKey:key];
if ([value isEqualToString:_name])
{
_abbreviation = key;
break;
}
}
}
// If no abbreviation could be found by any means, initialization fails.
if (!_abbreviation)
return nil;
}
return self;
+32 -12
View File
@@ -347,22 +347,42 @@
[self assert:[timeZone1 isEqualToTimeZone:timeZone2] equals:NO];
}
- (void)testSystemTimeZonesAreNotNilInModernBrowsers
- (void)testInitWithNameRespectsDaylightSaving
{
// This test verifies the fix for issue #2855, where modern browsers
// could cause system time zone methods to return nil.
var local = [CPTimeZone localTimeZone];
[self assert:(local !== nil) equals:YES];
// This test verifies that initWithName: correctly selects the abbreviation
// (e.g., PST vs. PDT) based on the current date's daylight saving status.
var system = [CPTimeZone systemTimeZone];
[self assert:(system !== nil) equals:YES];
// 1. Test a time zone that observes DST, like America/Los_Angeles.
var laTimeZoneName = @"America/Los_Angeles";
var expectedAbbreviationLA;
var defaultTZ = [CPTimeZone defaultTimeZone];
[self assert:(defaultTZ !== nil) equals:YES];
try {
// This logic mimics the _abbreviationForNameAndDate helper function in CPTimeZone.j
var options = { timeZone: laTimeZoneName, timeZoneName: 'long' };
var dateString = (new Date()).toLocaleString('en-US', options);
var longTZName = dateString.replace(/^([0]?\d|[1][0-2])\/((?:[0]?|[1-2])\d|[3][0-1])\/([2][01]|[1][6-9])\d{2}(,?\s*([0]?\d|[1][0-2])(\:[0-5]\d){1,2})*\s*([aApP][mM]{0,2})?\s*/, "");
expectedAbbreviationLA = longTZName.split(" ").map(function(l) { return l[0]}).join("");
} catch (e) {
[self fail:"Could not determine expected abbreviation for America/Los_Angeles"];
return;
}
// Also check that they are all the same object, as per initialization logic
[self assert:local equals:system];
[self assert:local equals:defaultTZ];
var timeZoneLA = [[CPTimeZone alloc] initWithName:laTimeZoneName];
if (timeZoneLA == nil)
[self fail:"Time zone for America/Los_Angeles should be created successfully."];
[self assert:[timeZoneLA abbreviation] equals:expectedAbbreviationLA];
// 2. Test a time zone that does not observe DST, like Pacific/Honolulu.
var hnlTimeZoneName = @"Pacific/Honolulu";
var timeZoneHNL = [[CPTimeZone alloc] initWithName:hnlTimeZoneName];
if (timeZoneHNL == nil)
[self fail:"Time zone for Pacific/Honolulu should be created successfully."];
// For a non-DST zone, the abbreviation is constant.
[self assert:[timeZoneHNL abbreviation] equals:@"HST"];
}
@end