Skip to content

Commit 7418081

Browse files
authored
Merge pull request #1704 from Kaiede/dateFormatterFix
Fix DateFormatter TimeZone Setter
2 parents 72d6933 + 218e0e2 commit 7418081

File tree

5 files changed

+90
-13
lines changed

5 files changed

+90
-13
lines changed

CoreFoundation/NumberDate.subproj/CFTimeZone.c

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

Foundation/DateFormatter.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -176,13 +176,13 @@ open class DateFormatter : Formatter {
176176
/*@NSCopying*/ internal var _timeZone: TimeZone? { willSet { _reset() } }
177177
open var timeZone: TimeZone! {
178178
get {
179-
guard let timeZone = _timeZone else {
179+
guard let tz = _timeZone else {
180180
return (CFDateFormatterCopyProperty(_cfObject, kCFDateFormatterTimeZone) as! NSTimeZone)._swiftObject
181181
}
182-
return timeZone
182+
return tz
183183
}
184184
set {
185-
_timeZone = timeZone
185+
_timeZone = newValue
186186
}
187187
}
188188

TestFoundation/TestCalendar.swift

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -331,7 +331,16 @@ class TestNSDateComponents: XCTestCase {
331331
// 2286-11-20 17:46:41
332332
let date4 = Date(timeIntervalSince1970: 10_000_000_001)
333333

334-
let diff1 = Calendar.current.dateComponents([.month, .year, .day], from: date1, to: date2)
334+
// The date components below assume UTC/GMT time zone.
335+
guard let timeZone = TimeZone(abbreviation: "UTC") else {
336+
XCTFail("Unable to create UTC TimeZone for Test")
337+
return
338+
}
339+
340+
var calendar = Calendar.current
341+
calendar.timeZone = timeZone
342+
343+
let diff1 = calendar.dateComponents([.month, .year, .day], from: date1, to: date2)
335344
XCTAssertEqual(diff1.year, 1)
336345
XCTAssertEqual(diff1.month, 5)
337346
XCTAssertEqual(diff1.isLeapMonth, false)
@@ -350,35 +359,35 @@ class TestNSDateComponents: XCTestCase {
350359
XCTAssertNil(diff1.calendar)
351360
XCTAssertNil(diff1.timeZone)
352361

353-
let diff2 = Calendar.current.dateComponents([.weekOfMonth], from: date2, to: date1)
362+
let diff2 = calendar.dateComponents([.weekOfMonth], from: date2, to: date1)
354363
XCTAssertEqual(diff2.weekOfMonth, -76)
355364
XCTAssertEqual(diff2.isLeapMonth, false)
356365

357-
let diff3 = Calendar.current.dateComponents([.weekday], from: date2, to: date1)
366+
let diff3 = calendar.dateComponents([.weekday], from: date2, to: date1)
358367
XCTAssertEqual(diff3.weekday, -536)
359368
XCTAssertEqual(diff3.isLeapMonth, false)
360369

361-
let diff4 = Calendar.current.dateComponents([.weekday, .weekOfMonth], from: date1, to: date2)
370+
let diff4 = calendar.dateComponents([.weekday, .weekOfMonth], from: date1, to: date2)
362371
XCTAssertEqual(diff4.weekday, 4)
363372
XCTAssertEqual(diff4.weekOfMonth, 76)
364373
XCTAssertEqual(diff4.isLeapMonth, false)
365374

366-
let diff5 = Calendar.current.dateComponents([.weekday, .weekOfYear], from: date1, to: date2)
375+
let diff5 = calendar.dateComponents([.weekday, .weekOfYear], from: date1, to: date2)
367376
XCTAssertEqual(diff5.weekday, 4)
368377
XCTAssertEqual(diff5.weekOfYear, 76)
369378
XCTAssertEqual(diff5.isLeapMonth, false)
370379

371-
let diff6 = Calendar.current.dateComponents([.month, .weekOfMonth], from: date1, to: date2)
380+
let diff6 = calendar.dateComponents([.month, .weekOfMonth], from: date1, to: date2)
372381
XCTAssertEqual(diff6.month, 17)
373382
XCTAssertEqual(diff6.weekOfMonth, 2)
374383
XCTAssertEqual(diff6.isLeapMonth, false)
375384

376-
let diff7 = Calendar.current.dateComponents([.weekOfYear, .weekOfMonth], from: date2, to: date1)
385+
let diff7 = calendar.dateComponents([.weekOfYear, .weekOfMonth], from: date2, to: date1)
377386
XCTAssertEqual(diff7.weekOfYear, -76)
378387
XCTAssertEqual(diff7.weekOfMonth, 0)
379388
XCTAssertEqual(diff7.isLeapMonth, false)
380389

381-
let diff8 = Calendar.current.dateComponents([.era, .quarter, .year, .month, .day, .hour, .minute, .second, .nanosecond, .calendar, .timeZone], from: date2, to: date3)
390+
let diff8 = calendar.dateComponents([.era, .quarter, .year, .month, .day, .hour, .minute, .second, .nanosecond, .calendar, .timeZone], from: date2, to: date3)
382391
XCTAssertEqual(diff8.era, 0)
383392
XCTAssertEqual(diff8.year, 315)
384393
XCTAssertEqual(diff8.quarter, 0)
@@ -392,7 +401,7 @@ class TestNSDateComponents: XCTestCase {
392401
XCTAssertNil(diff8.calendar)
393402
XCTAssertNil(diff8.timeZone)
394403

395-
let diff9 = Calendar.current.dateComponents([.era, .quarter, .year, .month, .day, .hour, .minute, .second, .nanosecond, .calendar, .timeZone], from: date4, to: date3)
404+
let diff9 = calendar.dateComponents([.era, .quarter, .year, .month, .day, .hour, .minute, .second, .nanosecond, .calendar, .timeZone], from: date4, to: date3)
396405
XCTAssertEqual(diff9.era, 0)
397406
XCTAssertEqual(diff9.year, 0)
398407
XCTAssertEqual(diff9.quarter, 0)

TestFoundation/TestDateFormatter.swift

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ class TestDateFormatter: XCTestCase {
2424
("test_dateFormatString", test_dateFormatString),
2525
("test_setLocaleToNil", test_setLocaleToNil),
2626
("test_setTimeZoneToNil", test_setTimeZoneToNil),
27+
("test_setTimeZone", test_setTimeZone),
28+
("test_expectedTimeZone", test_expectedTimeZone),
2729
]
2830
}
2931

@@ -356,4 +358,54 @@ class TestDateFormatter: XCTestCase {
356358
// Time zone should go back to the system one.
357359
XCTAssertEqual(f.timeZone, NSTimeZone.system)
358360
}
361+
362+
func test_setTimeZone() {
363+
// Test two different time zones. Should ensure that if one
364+
// happens to be TimeZone.current, we still get a valid test.
365+
let newYork = TimeZone(identifier: "America/New_York")!
366+
let losAngeles = TimeZone(identifier: "America/Los_Angeles")!
367+
368+
XCTAssertNotEqual(newYork, losAngeles)
369+
370+
// Case 1: New York
371+
let f = DateFormatter()
372+
f.timeZone = newYork
373+
XCTAssertEqual(f.timeZone, newYork)
374+
375+
// Case 2: Los Angeles
376+
f.timeZone = losAngeles
377+
XCTAssertEqual(f.timeZone, losAngeles)
378+
}
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+
f.locale = Locale(identifier: "en_US_POSIX")
392+
393+
// Case 1: TimeZone.current
394+
// This case can catch some issues that cause TimeZone.current to be
395+
// treated like GMT, but it doesn't work if TimeZone.current is GMT.
396+
// If you do find an issue like this caused by this first case,
397+
// it would benefit from a more specific test that fails when
398+
// TimeZone.current is GMT as well.
399+
// (ex. TestTimeZone.test_systemTimeZoneName)
400+
f.timeZone = TimeZone.current
401+
XCTAssertEqual(f.string(from: now), TimeZone.current.abbreviation())
402+
403+
// Case 2: New York
404+
f.timeZone = newYork
405+
XCTAssertEqual(f.string(from: now), newYork.abbreviation())
406+
407+
// Case 3: Los Angeles
408+
f.timeZone = losAngeles
409+
XCTAssertEqual(f.string(from: now), losAngeles.abbreviation())
410+
}
359411
}

TestFoundation/TestTimeZone.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
88
//
99

10+
import CoreFoundation
11+
1012
class TestTimeZone: XCTestCase {
1113

1214
static var allTests: [(String, (TestTimeZone) -> () throws -> Void)] {
@@ -29,6 +31,7 @@ class TestTimeZone: XCTestCase {
2931

3032
("test_customMirror", test_tz_customMirror),
3133
("test_knownTimeZones", test_knownTimeZones),
34+
("test_systemTimeZoneName", test_systemTimeZoneName),
3235
]
3336
}
3437

@@ -198,4 +201,17 @@ class TestTimeZone: XCTestCase {
198201
XCTAssertNotNil(TimeZone(identifier: tz), "Cant instantiate valid timeZone: \(tz)")
199202
}
200203
}
204+
205+
func test_systemTimeZoneName() {
206+
// Ensure that the system time zone creates names the same way as creating them with an identifier.
207+
// If it isn't the same, bugs in DateFormat can result, but in this specific case, the bad length
208+
// is only visible to CoreFoundation APIs, and the Swift versions hide it, making it hard to detect.
209+
let timeZone = CFTimeZoneCopySystem()
210+
let timeZoneName = CFTimeZoneGetName(timeZone)
211+
212+
let createdTimeZone = TimeZone(identifier: TimeZone.current.identifier)!
213+
214+
XCTAssertEqual(CFStringGetLength(timeZoneName), TimeZone.current.identifier.count)
215+
XCTAssertEqual(CFStringGetLength(timeZoneName), createdTimeZone.identifier.count)
216+
}
201217
}

0 commit comments

Comments
 (0)