Skip to content

Commit 1965003

Browse files
drodriguezparkera
authored andcommitted
Fix DateFormatter behaviour for nil locale and timeZone. (#1681)
The locale and timeZone properties of DateFormatter seems to be "null resetteable" in macOS, but the Swift Foundation behaviour differs. Before this patch, setting locale to nil would segfault the program the next time the formatter is used, while timeZone will incorrectly return nil. After this patch a nil locale will simply reset to the current locale and a nil timeZone will reset to the system one. The tests check that the initial defaults are also correctly set.
1 parent 0b4b227 commit 1965003

File tree

2 files changed

+49
-3
lines changed

2 files changed

+49
-3
lines changed

Foundation/DateFormatter.swift

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ open class DateFormatter : Formatter {
9191

9292
internal func _setFormatterAttributes(_ formatter: CFDateFormatter) {
9393
_setFormatterAttribute(formatter, attributeName: kCFDateFormatterIsLenient, value: isLenient._cfObject)
94-
_setFormatterAttribute(formatter, attributeName: kCFDateFormatterTimeZone, value: timeZone?._cfObject)
94+
_setFormatterAttribute(formatter, attributeName: kCFDateFormatterTimeZone, value: _timeZone?._cfObject)
9595
if let ident = _calendar?.identifier {
9696
_setFormatterAttribute(formatter, attributeName: kCFDateFormatterCalendarName, value: Calendar._toNSCalendarIdentifier(ident).rawValue._cfObject)
9797
} else {
@@ -160,11 +160,31 @@ open class DateFormatter : Formatter {
160160
}
161161
}
162162

163-
/*@NSCopying*/ open var locale: Locale! = .current { willSet { _reset() } }
163+
/*@NSCopying*/ internal var _locale: Locale? { willSet { _reset() } }
164+
open var locale: Locale! {
165+
get {
166+
guard let locale = _locale else { return .current }
167+
return locale
168+
}
169+
set {
170+
_locale = newValue
171+
}
172+
}
164173

165174
open var generatesCalendarDates = false { willSet { _reset() } }
166175

167-
/*@NSCopying*/ open var timeZone: TimeZone! = NSTimeZone.system { willSet { _reset() } }
176+
/*@NSCopying*/ internal var _timeZone: TimeZone? { willSet { _reset() } }
177+
open var timeZone: TimeZone! {
178+
get {
179+
guard let timeZone = _timeZone else {
180+
return (CFDateFormatterCopyProperty(_cfObject, kCFDateFormatterTimeZone) as! NSTimeZone)._swiftObject
181+
}
182+
return timeZone
183+
}
184+
set {
185+
_timeZone = timeZone
186+
}
187+
}
168188

169189
/*@NSCopying*/ internal var _calendar: Calendar! { willSet { _reset() } }
170190
open var calendar: Calendar! {

TestFoundation/TestDateFormatter.swift

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ class TestDateFormatter: XCTestCase {
2222
("test_customDateFormat", test_customDateFormat),
2323
("test_setLocalizedDateFormatFromTemplate", test_setLocalizedDateFormatFromTemplate),
2424
("test_dateFormatString", test_dateFormatString),
25+
("test_setLocaleToNil", test_setLocaleToNil),
26+
("test_setTimeZoneToNil", test_setTimeZoneToNil),
2527
]
2628
}
2729

@@ -330,4 +332,28 @@ class TestDateFormatter: XCTestCase {
330332
XCTAssertEqual(f.dateFormat, dateFormat)
331333
}
332334
}
335+
336+
func test_setLocaleToNil() {
337+
let f = DateFormatter()
338+
// Locale should be the current one by default
339+
XCTAssertEqual(f.locale, .current)
340+
341+
f.locale = nil
342+
343+
// Locale should go back to current.
344+
XCTAssertEqual(f.locale, .current)
345+
346+
// A nil locale should not crash a subsequent operation
347+
let result: String? = f.string(from: Date())
348+
XCTAssertNotNil(result)
349+
}
350+
351+
func test_setTimeZoneToNil() {
352+
let f = DateFormatter()
353+
// Time zone should be the system one by default.
354+
XCTAssertEqual(f.timeZone, NSTimeZone.system)
355+
f.timeZone = nil
356+
// Time zone should go back to the system one.
357+
XCTAssertEqual(f.timeZone, NSTimeZone.system)
358+
}
333359
}

0 commit comments

Comments
 (0)