Skip to content

Commit c4f0968

Browse files
committed
Add tests and revise some implementations (NSTimeZone)
1 parent 8dc63e9 commit c4f0968

File tree

3 files changed

+118
-18
lines changed

3 files changed

+118
-18
lines changed

CoreFoundation/Base.subproj/ForSwiftFoundationOnly.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -287,8 +287,6 @@ typedef pthread_t _CFThreadRef;
287287

288288
CF_EXPORT _CFThreadRef _CFThreadCreate(const _CFThreadAttributes attrs, void *_Nullable (* _Nonnull startfn)(void *_Nullable), void *restrict _Nullable context);
289289

290-
CF_EXPORT Boolean _CFTimeZoneInitWithTimeIntervalFromGMT(CFTimeZoneRef result, CFTimeInterval ti);
291-
292290
CF_EXPORT Boolean _CFCharacterSetIsLongCharacterMember(CFCharacterSetRef theSet, UTF32Char theChar);
293291
CF_EXPORT CFCharacterSetRef _CFCharacterSetCreateCopy(CFAllocatorRef alloc, CFCharacterSetRef theSet);
294292
CF_EXPORT CFMutableCharacterSetRef _CFCharacterSetCreateMutableCopy(CFAllocatorRef alloc, CFCharacterSetRef theSet);

Foundation/NSTimeZone.swift

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -79,12 +79,26 @@ open class NSTimeZone : NSObject, NSCopying, NSSecureCoding, NSCoding {
7979
_CFDeinit(self)
8080
}
8181

82+
// `init(forSecondsFromGMT:)` is not a failable initializer, so we need a designated initializer that isn't failable.
83+
internal init(_name tzName: String) {
84+
super.init()
85+
_CFTimeZoneInit(_cfObject, tzName._cfObject, nil)
86+
}
87+
8288
// Time zones created with this never have daylight savings and the
8389
// offset is constant no matter the date; the name and abbreviation
8490
// do NOT follow the POSIX convention (of minutes-west).
85-
public init(forSecondsFromGMT seconds: Int) {
86-
super.init()
87-
_CFTimeZoneInitWithTimeIntervalFromGMT(_cfObject, CFTimeInterval(seconds))
91+
public convenience init(forSecondsFromGMT seconds: Int) {
92+
let sign = seconds < 0 ? "-" : "+"
93+
let absoluteValue = abs(seconds)
94+
var minutes = absoluteValue / 60
95+
if (absoluteValue % 60) >= 30 { minutes += 1 }
96+
var hours = minutes / 60
97+
minutes %= 60
98+
hours = min(hours, 99) // Two digits only; leave CF to enforce actual max offset.
99+
let mm = minutes < 10 ? "0\(minutes)" : "\(minutes)"
100+
let hh = hours < 10 ? "0\(hours)" : "\(hours)"
101+
self.init(_name: "GMT" + sign + hh + mm)
88102
}
89103

90104
public convenience init?(abbreviation: String) {
@@ -98,7 +112,7 @@ open class NSTimeZone : NSObject, NSCopying, NSSecureCoding, NSCoding {
98112
open func encode(with aCoder: NSCoder) {
99113
if aCoder.allowsKeyedCoding {
100114
aCoder.encode(self.name._bridgeToObjectiveC(), forKey:"NS.name")
101-
// darwin versions of this method can and will encode mutable data, however it is not required for compatability
115+
// Darwin versions of this method can and will encode mutable data, however it is not required for compatibility
102116
aCoder.encode(self.data._bridgeToObjectiveC(), forKey:"NS.data")
103117
} else {
104118
}
@@ -174,7 +188,6 @@ extension NSTimeZone {
174188

175189
open class func resetSystemTimeZone() {
176190
CFTimeZoneResetSystem()
177-
// NotificationCenter.default.post(name: NSNotification.Name.NSSystemTimeZoneDidChange, object: nil)
178191
}
179192

180193
open class var `default`: TimeZone {
@@ -183,28 +196,24 @@ extension NSTimeZone {
183196
}
184197
set {
185198
CFTimeZoneSetDefault(newValue._cfObject)
186-
// NotificationCenter.default.post(name: NSNotification.Name.NSSystemTimeZoneDidChange, object: nil)
187199
}
188200
}
189201

190202
open class var local: TimeZone { NSUnimplemented() }
191203

192204
open class var knownTimeZoneNames: [String] {
193205
guard let knownNames = CFTimeZoneCopyKnownNames() else { return [] }
194-
return knownNames._swiftObject.map { ($0 as! NSString)._swiftObject }
206+
return knownNames._nsObject._bridgeToSwift() as! [String]
195207
}
196208

197209
open class var abbreviationDictionary: [String : String] {
198210
get {
199211
guard let dictionary = CFTimeZoneCopyAbbreviationDictionary() else { return [:] }
200-
var result = [String : String]()
201-
dictionary._swiftObject.forEach {
202-
result[($0 as! NSString)._swiftObject] = ($1 as! NSString)._swiftObject
203-
}
204-
return result
212+
return dictionary._nsObject._bridgeToSwift() as! [String : String]
205213
}
206214
set {
207-
CFTimeZoneSetAbbreviationDictionary(newValue._cfObject)
215+
// CFTimeZoneSetAbbreviationDictionary(newValue._cfObject)
216+
NSUnimplemented()
208217
}
209218
}
210219

@@ -248,7 +257,7 @@ extension NSTimeZone {
248257
#else
249258
let cfStyle = CFTimeZoneNameStyle(style.rawValue)
250259
#endif
251-
return CFTimeZoneCopyLocalizedName(self._cfObject, cfStyle, locale?._cfObject)._swiftObject
260+
return CFTimeZoneCopyLocalizedName(self._cfObject, cfStyle, locale?._cfObject ?? CFLocaleCopyCurrent())._swiftObject
252261
}
253262

254263
}
@@ -285,5 +294,5 @@ extension NSTimeZone {
285294
}
286295

287296
extension NSNotification.Name {
288-
public static let NSSystemTimeZoneDidChange = NSNotification.Name(rawValue: "NSSystemTimeZoneDidChangeNotification")
297+
public static let NSSystemTimeZoneDidChange = NSNotification.Name(rawValue: "NSSystemTimeZoneDidChangeNotification") // NSUnimplemented
289298
}

TestFoundation/TestNSTimeZone.swift

Lines changed: 94 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,17 @@ class TestNSTimeZone: XCTestCase {
2525
return [
2626
// Disabled see https://bugs.swift.org/browse/SR-300
2727
// ("test_abbreviation", test_abbreviation),
28+
29+
// Disabled because `CFTimeZoneSetAbbreviationDictionary()` attempts
30+
// to release non-CF objects while removing values from
31+
// `__CFTimeZoneCache`
32+
// ("test_abbreviationDictionary", test_abbreviationDictionary),
33+
34+
("test_changingDefaultTimeZone", test_changingDefaultTimeZone),
35+
("test_computedPropertiesMatchMethodReturnValues", test_computedPropertiesMatchMethodReturnValues),
2836
("test_initializingTimeZoneWithOffset", test_initializingTimeZoneWithOffset),
2937
("test_initializingTimeZoneWithAbbreviation", test_initializingTimeZoneWithAbbreviation),
38+
3039
// Also disabled due to https://bugs.swift.org/browse/SR-300
3140
// ("test_systemTimeZoneUsesSystemTime", test_systemTimeZoneUsesSystemTime),
3241
]
@@ -38,12 +47,96 @@ class TestNSTimeZone: XCTestCase {
3847
let abbreviation2 = tz.abbreviation(for: Date())
3948
XCTAssertEqual(abbreviation1, abbreviation2, "\(abbreviation1) should be equal to \(abbreviation2)")
4049
}
41-
50+
51+
func test_abbreviationDictionary() {
52+
let oldDictionary = TimeZone.abbreviationDictionary
53+
let newDictionary = [
54+
"UTC": "UTC",
55+
"JST": "Asia/Tokyo",
56+
"GMT": "GMT",
57+
"ICT": "Asia/Bangkok",
58+
"TEST": "Foundation/TestNSTimeZone"
59+
]
60+
TimeZone.abbreviationDictionary = newDictionary
61+
XCTAssertEqual(TimeZone.abbreviationDictionary, newDictionary)
62+
TimeZone.abbreviationDictionary = oldDictionary
63+
XCTAssertEqual(TimeZone.abbreviationDictionary, oldDictionary)
64+
}
65+
66+
func test_changingDefaultTimeZone() {
67+
let oldDefault = NSTimeZone.default
68+
let oldSystem = NSTimeZone.system
69+
70+
let expectedDefault = TimeZone(identifier: "GMT-0400")!
71+
NSTimeZone.default = expectedDefault
72+
let newDefault = NSTimeZone.default
73+
let newSystem = NSTimeZone.system
74+
XCTAssertEqual(oldSystem, newSystem)
75+
XCTAssertEqual(expectedDefault, newDefault)
76+
77+
let expectedDefault2 = TimeZone(identifier: "GMT+0400")!
78+
NSTimeZone.default = expectedDefault2
79+
let newDefault2 = NSTimeZone.default
80+
XCTAssertEqual(expectedDefault2, newDefault2)
81+
XCTAssertNotEqual(newDefault, newDefault2)
82+
83+
NSTimeZone.default = oldDefault
84+
let revertedDefault = NSTimeZone.default
85+
XCTAssertEqual(oldDefault, revertedDefault)
86+
}
87+
88+
func test_computedPropertiesMatchMethodReturnValues() {
89+
let tz = NSTimeZone.default
90+
let obj = tz._bridgeToObjectiveC()
91+
92+
let secondsFromGMT1 = tz.secondsFromGMT()
93+
let secondsFromGMT2 = obj.secondsFromGMT
94+
let secondsFromGMT3 = tz.secondsFromGMT()
95+
XCTAssert(secondsFromGMT1 == secondsFromGMT2 || secondsFromGMT2 == secondsFromGMT3, "\(secondsFromGMT1) should be equal to \(secondsFromGMT2), or in the rare circumstance where a daylight saving time transition has just occurred, \(secondsFromGMT2) should be equal to \(secondsFromGMT3)")
96+
97+
let abbreviation1 = tz.abbreviation()
98+
let abbreviation2 = obj.abbreviation
99+
XCTAssertEqual(abbreviation1, abbreviation2, "\(abbreviation1) should be equal to \(abbreviation2)")
100+
101+
let isDaylightSavingTime1 = tz.isDaylightSavingTime()
102+
let isDaylightSavingTime2 = obj.isDaylightSavingTime
103+
let isDaylightSavingTime3 = tz.isDaylightSavingTime()
104+
XCTAssert(isDaylightSavingTime1 == isDaylightSavingTime2 || isDaylightSavingTime2 == isDaylightSavingTime3, "\(isDaylightSavingTime1) should be equal to \(isDaylightSavingTime2), or in the rare circumstance where a daylight saving time transition has just occurred, \(isDaylightSavingTime2) should be equal to \(isDaylightSavingTime3)")
105+
106+
let daylightSavingTimeOffset1 = tz.daylightSavingTimeOffset()
107+
let daylightSavingTimeOffset2 = obj.daylightSavingTimeOffset
108+
XCTAssertEqual(daylightSavingTimeOffset1, daylightSavingTimeOffset2, "\(daylightSavingTimeOffset1) should be equal to \(daylightSavingTimeOffset2)")
109+
110+
let nextDaylightSavingTimeTransition1 = tz.nextDaylightSavingTimeTransition
111+
let nextDaylightSavingTimeTransition2 = obj.nextDaylightSavingTimeTransition
112+
let nextDaylightSavingTimeTransition3 = tz.nextDaylightSavingTimeTransition(after: Date())
113+
XCTAssert(nextDaylightSavingTimeTransition1 == nextDaylightSavingTimeTransition2 || nextDaylightSavingTimeTransition2 == nextDaylightSavingTimeTransition3, "\(nextDaylightSavingTimeTransition1) should be equal to \(nextDaylightSavingTimeTransition2), or in the rare circumstance where a daylight saving time transition has just occurred, \(nextDaylightSavingTimeTransition2) should be equal to \(nextDaylightSavingTimeTransition3)")
114+
}
115+
116+
func test_knownTimeZoneNames() {
117+
let known = NSTimeZone.knownTimeZoneNames
118+
XCTAssertNotEqual([], known, "known time zone names not expected to be empty")
119+
}
120+
42121
func test_initializingTimeZoneWithOffset() {
43122
let tz = TimeZone(identifier: "GMT-0400")
44123
XCTAssertNotNil(tz)
45124
let seconds = tz?.secondsFromGMT(for: Date())
46125
XCTAssertEqual(seconds, -14400, "GMT-0400 should be -14400 seconds but got \(seconds) instead")
126+
127+
let tz2 = TimeZone(secondsFromGMT: -14400)
128+
XCTAssertNotNil(tz2)
129+
let expectedName = "GMT-0400"
130+
let actualName = tz2?.identifier
131+
XCTAssertEqual(actualName, expectedName, "expected name \"\(expectedName)\" is not equal to \"\(actualName)\"")
132+
let expectedLocalizedName = "GMT-04:00"
133+
let actualLocalizedName = tz2?.localizedName(for: .generic, locale: Locale(identifier: "en_US"))
134+
XCTAssertEqual(actualLocalizedName, expectedLocalizedName, "expected name \"\(expectedLocalizedName)\" is not equal to \"\(actualLocalizedName)\"")
135+
let seconds2 = tz2?.secondsFromGMT()
136+
XCTAssertEqual(seconds2, -14400, "GMT-0400 should be -14400 seconds but got \(seconds2) instead")
137+
138+
let tz3 = TimeZone(identifier: "GMT-9999")
139+
XCTAssertNil(tz3)
47140
}
48141

49142
func test_initializingTimeZoneWithAbbreviation() {

0 commit comments

Comments
 (0)