Skip to content

Commit caa8d0a

Browse files
committed
Fix Off-By-One Error With System TimeZone
This one is nasty, since it creates problems for DateFormatter, while appearing to behave correctly. (Mostly) By copying 1 too many bytes, we wind up with the null terminator in the CFString. This breaks DateFormatter in an annoying way that the system TimeZone is always considered to be GMT by the formatter. You can ask for the zone’s abbreviation, and it will be correct. The name will appear to be correct, except the length is 1 longer than it should be. All sorts of fun. But the moment you ask DateFormatter to give you a string (or parse one), it assumes the TimeZone is GMT and ruins your day. Included is a test case which can detect this. The downside is that it doesn’t detect the problem if GMT is already the current TimeZone. It is possible a second, more targeted test should also be added here? This also breaks getting the description on the NSTimeZone/CFTimeZone, but not the TimeZone because of the wrapping at play, because of the terminator that gets inserted into the string description.
1 parent e86c0dc commit caa8d0a

File tree

2 files changed

+34
-1
lines changed

2 files changed

+34
-1
lines changed

CoreFoundation/NumberDate.subproj/CFTimeZone.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -793,7 +793,7 @@ static CFTimeZoneRef __CFTimeZoneCreateSystem(void) {
793793
size_t zoneInfoDirLen = CFStringGetLength(__tzZoneInfo);
794794
if (strncmp(linkbuf, tzZoneInfo, zoneInfoDirLen) == 0) {
795795
name = CFStringCreateWithBytes(kCFAllocatorSystemDefault, (uint8_t *)linkbuf + zoneInfoDirLen,
796-
strlen(linkbuf) - zoneInfoDirLen + 1, kCFStringEncodingUTF8, false);
796+
strlen(linkbuf) - zoneInfoDirLen, kCFStringEncodingUTF8, false);
797797
} else {
798798
name = CFStringCreateWithBytes(kCFAllocatorSystemDefault, (uint8_t *)linkbuf, strlen(linkbuf), kCFStringEncodingUTF8, false);
799799
}

TestFoundation/TestDateFormatter.swift

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ class TestDateFormatter: XCTestCase {
2525
("test_setLocaleToNil", test_setLocaleToNil),
2626
("test_setTimeZoneToNil", test_setTimeZoneToNil),
2727
("test_setTimeZone", test_setTimeZone),
28+
("test_ExpectedTimeZone", test_ExpectedTimeZone),
2829
]
2930
}
3031

@@ -375,4 +376,36 @@ class TestDateFormatter: XCTestCase {
375376
f.timeZone = losAngeles
376377
XCTAssertEqual(f.timeZone, losAngeles)
377378
}
379+
380+
func test_ExpectedTimeZone() {
381+
let gmt = TimeZone(abbreviation: DEFAULT_TIMEZONE)
382+
let newYork = TimeZone(identifier: "America/New_York")!
383+
let losAngeles = TimeZone(identifier: "America/Los_Angeles")!
384+
385+
XCTAssertNotEqual(newYork, losAngeles)
386+
387+
let now = Date()
388+
389+
let f = DateFormatter()
390+
f.dateFormat = "z"
391+
392+
// Case 1: TimeZone.current
393+
f.timeZone = TimeZone.current
394+
XCTAssertEqual(f.string(from: now), f.timeZone.abbreviation())
395+
396+
// Case 2: New York
397+
f.timeZone = newYork
398+
XCTAssertEqual(f.string(from: now), f.timeZone.abbreviation())
399+
400+
// Case 3: Los Angeles
401+
f.timeZone = losAngeles
402+
XCTAssertEqual(f.string(from: now), f.timeZone.abbreviation())
403+
404+
guard gmt != TimeZone.current else {
405+
print("Inconclusive: This test checks to see if the formatter produces the same TZ as TimeZone.current")
406+
print("When it fails, TimeZone.current formats as GMT instead of normal.")
407+
print("Unfortunately, we can't use GMT as TimeZone.current for this test to be conclusive.")
408+
return
409+
}
410+
}
378411
}

0 commit comments

Comments
 (0)