Skip to content

Commit 6545d1b

Browse files
committed
Fixed TimeZone.init(secondsFromGMT:) failability
1 parent 67b1038 commit 6545d1b

File tree

2 files changed

+18
-5
lines changed

2 files changed

+18
-5
lines changed

Foundation/TimeZone.swift

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,15 @@ public struct TimeZone : Hashable, Equatable, ReferenceConvertible {
7878
/// Time zones created with this never have daylight savings and the offset is constant no matter the date. The identifier and abbreviation do NOT follow the POSIX convention (of minutes-west).
7979
///
8080
/// - parameter seconds: The number of seconds from GMT.
81-
/// - returns: A time zone.
82-
public init(secondsFromGMT seconds: Int) {
81+
/// - returns: A time zone, or `nil` if a valid time zone could not be created from `seconds`.
82+
public init?(secondsFromGMT seconds: Int) {
83+
// Seconds boundaries check should actually be placed in NSTimeZone.init(forSecondsFromGMT:) which should return
84+
// nil if the check fails. However, NSTimeZone.init(forSecondsFromGMT:) is not a failable initializer, so it
85+
// cannot return nil.
86+
// It is not a failable initializer because we want to have parity with Darwin's NSTimeZone, which is
87+
// Objective-C and has a wrong _Nonnull annotation.
88+
if (seconds < -18 * 3600 || 18 * 3600 < seconds) { return nil }
89+
8390
_wrapped = NSTimeZone(forSecondsFromGMT: seconds)
8491
_autoupdating = false
8592
}

TestFoundation/TestNSTimeZone.swift

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -143,16 +143,22 @@ class TestNSTimeZone: XCTestCase {
143143
let tz2 = TimeZone(secondsFromGMT: -14400)
144144
XCTAssertNotNil(tz2)
145145
let expectedName = "GMT-0400"
146-
let actualName = tz2.identifier
146+
let actualName = tz2?.identifier
147147
XCTAssertEqual(actualName, expectedName, "expected name \"\(expectedName)\" is not equal to \"\(actualName as Optional)\"")
148148
let expectedLocalizedName = "GMT-04:00"
149-
let actualLocalizedName = tz2.localizedName(for: .generic, locale: Locale(identifier: "en_US"))
149+
let actualLocalizedName = tz2?.localizedName(for: .generic, locale: Locale(identifier: "en_US"))
150150
XCTAssertEqual(actualLocalizedName, expectedLocalizedName, "expected name \"\(expectedLocalizedName)\" is not equal to \"\(actualLocalizedName as Optional)\"")
151-
let seconds2 = tz2.secondsFromGMT()
151+
let seconds2 = tz2?.secondsFromGMT() ?? 0
152152
XCTAssertEqual(seconds2, -14400, "GMT-0400 should be -14400 seconds but got \(seconds2) instead")
153153

154154
let tz3 = TimeZone(identifier: "GMT-9999")
155155
XCTAssertNil(tz3)
156+
157+
XCTAssertNotNil(TimeZone(secondsFromGMT: -18 * 3600))
158+
XCTAssertNotNil(TimeZone(secondsFromGMT: 18 * 3600))
159+
160+
XCTAssertNil(TimeZone(secondsFromGMT: -18 * 3600 - 1))
161+
XCTAssertNil(TimeZone(secondsFromGMT: 18 * 3600 + 1))
156162
}
157163

158164
func test_initializingTimeZoneWithAbbreviation() {

0 commit comments

Comments
 (0)