Skip to content
This repository was archived by the owner on Sep 15, 2025. It is now read-only.

Commit 2a90451

Browse files
authored
Breaking change – Modernize NSDate and Date WP.com extensions (#759)
2 parents 5686c93 + e4f8fba commit 2a90451

File tree

9 files changed

+116
-46
lines changed

9 files changed

+116
-46
lines changed

WordPressKit.podspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
Pod::Spec.new do |s|
44
s.name = 'WordPressKit'
5-
s.version = '14.1.0'
5+
s.version = '15.0.0-beta.1'
66

77
s.summary = 'WordPressKit offers a clean and simple WordPress.com and WordPress.org API.'
88
s.description = <<-DESC

WordPressKit.xcodeproj/project.pbxproj

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,12 @@
7171
3F758FD324F6C68200BBA2FC /* AnnouncementServiceRemote.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F758FD224F6C68200BBA2FC /* AnnouncementServiceRemote.swift */; };
7272
3F8308A729EE683500354497 /* ActivityTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F8308A629EE683500354497 /* ActivityTests.swift */; };
7373
3FB8642C2888089F003A86BE /* BuildkiteTestCollector in Frameworks */ = {isa = PBXBuildFile; productRef = 3FB8642B2888089F003A86BE /* BuildkiteTestCollector */; };
74-
3FFCC0412BA995290051D229 /* NSDate+RFC3339Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FFCC0402BA995290051D229 /* NSDate+RFC3339Tests.swift */; };
75-
3FFCC0472BAA6EF40051D229 /* NSDate+RFC3339.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FFCC0462BAA6EF40051D229 /* NSDate+RFC3339.swift */; };
74+
3FFCC0412BA995290051D229 /* Date+WordPressComTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FFCC0402BA995290051D229 /* Date+WordPressComTests.swift */; };
75+
3FFCC0472BAA6EF40051D229 /* NSDate+WordPressCom.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FFCC0462BAA6EF40051D229 /* NSDate+WordPressCom.swift */; };
76+
3FFCC0492BAB98130051D229 /* DateFormatter+WordPressCom.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FFCC0482BAB98130051D229 /* DateFormatter+WordPressCom.swift */; };
77+
3FFCC04B2BABA5220051D229 /* DateFormatter+WordPressComTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FFCC04A2BABA5220051D229 /* DateFormatter+WordPressComTests.swift */; };
78+
3FFCC04D2BABA6980051D229 /* NSDate+WordPressComTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FFCC04C2BABA6980051D229 /* NSDate+WordPressComTests.swift */; };
79+
3FFCC04F2BABA6E60051D229 /* Date+WordPressCom.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FFCC04E2BABA6E60051D229 /* Date+WordPressCom.swift */; };
7680
40247DFA2120D8E100AE1C3C /* AutomatedTransferService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40247DF92120D8E100AE1C3C /* AutomatedTransferService.swift */; };
7781
40247DFC2120E69600AE1C3C /* AutomatedTransferStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40247DFB2120E69600AE1C3C /* AutomatedTransferStatus.swift */; };
7882
404057C5221B30400060250C /* StatsSearchTermTimeIntervalData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 404057C4221B30400060250C /* StatsSearchTermTimeIntervalData.swift */; };
@@ -798,8 +802,12 @@
798802
3F758FD224F6C68200BBA2FC /* AnnouncementServiceRemote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnnouncementServiceRemote.swift; sourceTree = "<group>"; };
799803
3F8308A629EE683500354497 /* ActivityTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityTests.swift; sourceTree = "<group>"; };
800804
3FB8642D288813E9003A86BE /* UnitTests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = UnitTests.xctestplan; sourceTree = "<group>"; };
801-
3FFCC0402BA995290051D229 /* NSDate+RFC3339Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSDate+RFC3339Tests.swift"; sourceTree = "<group>"; };
802-
3FFCC0462BAA6EF40051D229 /* NSDate+RFC3339.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSDate+RFC3339.swift"; sourceTree = "<group>"; };
805+
3FFCC0402BA995290051D229 /* Date+WordPressComTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Date+WordPressComTests.swift"; sourceTree = "<group>"; };
806+
3FFCC0462BAA6EF40051D229 /* NSDate+WordPressCom.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSDate+WordPressCom.swift"; sourceTree = "<group>"; };
807+
3FFCC0482BAB98130051D229 /* DateFormatter+WordPressCom.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DateFormatter+WordPressCom.swift"; sourceTree = "<group>"; };
808+
3FFCC04A2BABA5220051D229 /* DateFormatter+WordPressComTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DateFormatter+WordPressComTests.swift"; sourceTree = "<group>"; };
809+
3FFCC04C2BABA6980051D229 /* NSDate+WordPressComTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSDate+WordPressComTests.swift"; sourceTree = "<group>"; };
810+
3FFCC04E2BABA6E60051D229 /* Date+WordPressCom.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Date+WordPressCom.swift"; sourceTree = "<group>"; };
803811
40247DF92120D8E100AE1C3C /* AutomatedTransferService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutomatedTransferService.swift; sourceTree = "<group>"; };
804812
40247DFB2120E69600AE1C3C /* AutomatedTransferStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutomatedTransferStatus.swift; sourceTree = "<group>"; };
805813
404057C4221B30400060250C /* StatsSearchTermTimeIntervalData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatsSearchTermTimeIntervalData.swift; sourceTree = "<group>"; };
@@ -1904,7 +1912,9 @@
19041912
FFA4D4A82423B10A00BF5180 /* WordPressOrgRestApiTests.swift */,
19051913
74B335DB1F06F4180053A184 /* WordPressOrgXMLRPCApiTests.swift */,
19061914
740B23D51F17F7C100067A2A /* XMLRPCTestable.swift */,
1907-
3FFCC0402BA995290051D229 /* NSDate+RFC3339Tests.swift */,
1915+
3FFCC0402BA995290051D229 /* Date+WordPressComTests.swift */,
1916+
3FFCC04C2BABA6980051D229 /* NSDate+WordPressComTests.swift */,
1917+
3FFCC04A2BABA5220051D229 /* DateFormatter+WordPressComTests.swift */,
19081918
);
19091919
name = Tests;
19101920
sourceTree = "<group>";
@@ -2519,7 +2529,9 @@
25192529
4A05E7952B2FCB6400C25E3B /* NonceRetrieval.swift */,
25202530
4A05E7992B2FDC3200C25E3B /* WordPressOrgRestApi.swift */,
25212531
93BD27741EE73944002BB00B /* HTTPAuthenticationAlertController.swift */,
2522-
3FFCC0462BAA6EF40051D229 /* NSDate+RFC3339.swift */,
2532+
3FFCC0462BAA6EF40051D229 /* NSDate+WordPressCom.swift */,
2533+
3FFCC04E2BABA6E60051D229 /* Date+WordPressCom.swift */,
2534+
3FFCC0482BAB98130051D229 /* DateFormatter+WordPressCom.swift */,
25232535
93BD27771EE73944002BB00B /* WordPressComOAuthClient.swift */,
25242536
93BD27781EE73944002BB00B /* WordPressComRestApi.swift */,
25252537
93BD27791EE73944002BB00B /* WordPressOrgXMLRPCApi.swift */,
@@ -3392,6 +3404,7 @@
33923404
93BD277F1EE73944002BB00B /* WordPressComOAuthClient.swift in Sources */,
33933405
740B23B91F17EC7300067A2A /* PostServiceRemoteREST.m in Sources */,
33943406
93BD27801EE73944002BB00B /* WordPressComRestApi.swift in Sources */,
3407+
3FFCC04F2BABA6E60051D229 /* Date+WordPressCom.swift in Sources */,
33953408
404057D2221C56AB0060250C /* StatsTopCountryTimeIntervalData.swift in Sources */,
33963409
E11C2AD21FA77FB90023BDE2 /* SitePlugin.swift in Sources */,
33973410
4A68E3DF29407100004AC3DC /* RemoteReaderTopic.swift in Sources */,
@@ -3457,7 +3470,7 @@
34573470
E632D7781F6E047400297F6D /* SocialLogin2FANonceInfo.swift in Sources */,
34583471
32FC1D29255C91ED00CD0A7B /* JetpackScanServiceRemote.swift in Sources */,
34593472
9F3E0B9B208732B3009CB5BA /* RemoteReaderSiteInfoSubscription.swift in Sources */,
3460-
3FFCC0472BAA6EF40051D229 /* NSDate+RFC3339.swift in Sources */,
3473+
3FFCC0472BAA6EF40051D229 /* NSDate+WordPressCom.swift in Sources */,
34613474
7403A2E41EF06ED500DED7DC /* AccountSettingsRemote.swift in Sources */,
34623475
3236F77824AE34B40088E8F3 /* ReaderTopicServiceRemote+Interests.swift in Sources */,
34633476
FE20A6A4282A96C00025E975 /* RemoteBloggingPromptsSettings.swift in Sources */,
@@ -3516,6 +3529,7 @@
35163529
74C473AC1EF2F75E009918F2 /* SiteManagementServiceRemote.swift in Sources */,
35173530
74585B971F0D54B400E7E667 /* RemoteDomain.swift in Sources */,
35183531
0C1C08432B9CD8D200E52F8C /* PostServiceRemoteREST+Extended.swift in Sources */,
3532+
3FFCC0492BAB98130051D229 /* DateFormatter+WordPressCom.swift in Sources */,
35193533
74A44DD01F13C64B006CD8F4 /* RemoteNotification.swift in Sources */,
35203534
8B52B901257AC5A200221663 /* Date+endOfDay.swift in Sources */,
35213535
E1D6B558200E473A00325669 /* TimeZoneServiceRemote.swift in Sources */,
@@ -3610,12 +3624,13 @@
36103624
F4B0F4802ACB4EA9003ABC61 /* AllDomainsResultDomainTests.swift in Sources */,
36113625
74585B901F0D51F900E7E667 /* DomainsServiceRemoteRESTTests.swift in Sources */,
36123626
BAFA775624ADAB3C000F0D3A /* MockPluginDirectoryEntryProvider.swift in Sources */,
3613-
3FFCC0412BA995290051D229 /* NSDate+RFC3339Tests.swift in Sources */,
3627+
3FFCC0412BA995290051D229 /* Date+WordPressComTests.swift in Sources */,
36143628
3F8308A729EE683500354497 /* ActivityTests.swift in Sources */,
36153629
9AB6D64A218727D60008F274 /* PostServiceRemoteRESTRevisionsTest.swift in Sources */,
36163630
01438D382B6A35FB0097D60A /* stats-summary.json in Sources */,
36173631
7430C9BD1F192C0F0051B8E6 /* ReaderPostServiceRemoteTests.m in Sources */,
36183632
1DC837C229B9F04F009DCD4B /* RemoteVideoPressVideoTests.swift in Sources */,
3633+
3FFCC04D2BABA6980051D229 /* NSDate+WordPressComTests.swift in Sources */,
36193634
FAD1345125909DEA00A8FEB1 /* JetpackBackupServiceRemoteTests.swift in Sources */,
36203635
8B2F4BE924ABC9DC0056C08A /* ReaderPostServiceRemote+CardsTests.swift in Sources */,
36213636
40F9880C221ACEEE00B7B369 /* StatsRemoteV2Tests.swift in Sources */,
@@ -3643,6 +3658,7 @@
36433658
4A05E79C2B2FDC6100C25E3B /* WordPressOrgAPITests.swift in Sources */,
36443659
3297E1DE2564653A00287D21 /* JetpackScanServiceRemoteTests.swift in Sources */,
36453660
01438D352B6A2B2C0097D60A /* stats-visits-month-unit-week.json in Sources */,
3661+
3FFCC04B2BABA5220051D229 /* DateFormatter+WordPressComTests.swift in Sources */,
36463662
9F3E0BAC20873785009CB5BA /* ServiceRequestTest.swift in Sources */,
36473663
4624223E2548C26D002B8A12 /* SiteDesignServiceRemoteTests.swift in Sources */,
36483664
4A1DEF44293051BC00322608 /* LoggingTests.swift in Sources */,
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
extension Date {
2+
3+
/// Parses a date string
4+
///
5+
/// Dates in the format specified in http://www.w3.org/TR/NOTE-datetime should be OK.
6+
/// The kind of dates returned by the REST API should match that format, even if the doc promises ISO 8601.
7+
///
8+
/// Parsing the full ISO 8601, or even RFC 3339 is more complex than this, and makes no sense right now.
9+
///
10+
/// - SeeAlso: [WordPress.com REST API docs](https://developer.wordpress.com/docs/api/)
11+
/// - Warning: This method doesn't support fractional seconds or dates with leap seconds (23:59:60 turns into 23:59:00)
12+
static func with(wordPressComJSONString jsonString: String) -> Date? {
13+
DateFormatter.wordPressCom.date(from: jsonString)
14+
}
15+
16+
var wordPressComJSONString: String {
17+
DateFormatter.wordPressCom.string(from: self)
18+
}
19+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
extension DateFormatter {
2+
3+
/// A `DateFormatter` configured to manage dates compatible with the WordPress.com API.
4+
///
5+
/// - SeeAlso: [https://developer.wordpress.com/docs/api/](https://developer.wordpress.com/docs/api/)
6+
static let wordPressCom: DateFormatter = {
7+
let formatter = DateFormatter()
8+
formatter.dateFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ssZ"
9+
formatter.timeZone = NSTimeZone(forSecondsFromGMT: 0) as TimeZone
10+
formatter.locale = NSLocale(localeIdentifier: "en_US_POSIX") as Locale
11+
return formatter
12+
}()
13+
}
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,30 @@
11
import Foundation
22

3+
// This `NSDate` extension wraps the `Date` implementation.
4+
//
5+
// It's done in two types because we cannot expose the `Date` methods to Objective-C, since `Date` is not a class:
6+
//
7+
// `@objc can only be used with members of classes, @objc protocols, and concrete extensions of classes`
38
extension NSDate {
49

5-
static let rfc3339Formatter = DateFormatter.rfc3339Formatter
6-
7-
@objc
8-
@available(*, deprecated, message: "Please use the static property instead. This is here for backwards compatibility and will soon be removed.")
9-
public static func rfc3339DateFormatter() -> DateFormatter {
10-
rfc3339Formatter
11-
}
12-
1310
/// Parses a date string
1411
///
1512
/// Dates in the format specified in http://www.w3.org/TR/NOTE-datetime should be OK.
1613
/// The kind of dates returned by the REST API should match that format, even if the doc promises ISO 8601.
1714
///
1815
/// Parsing the full ISO 8601, or even RFC 3339 is more complex than this, and makes no sense right now.
1916
///
17+
/// - SeeAlso: [WordPress.com REST API docs](https://developer.wordpress.com/docs/api/)
2018
/// - Warning: This method doesn't support fractional seconds or dates with leap seconds (23:59:60 turns into 23:59:00)
2119
//
2220
// Needs to be `public` because of the usages in the Objective-C code.
2321
@objc(dateWithWordPressComJSONString:)
2422
public static func with(wordPressComJSONString jsonString: String) -> Date? {
25-
self.rfc3339Formatter.date(from: jsonString)
23+
Date.with(wordPressComJSONString: jsonString)
2624
}
2725

2826
@objc(WordPressComJSONString)
2927
public func wordPressComJSONString() -> String {
30-
NSDate.rfc3339Formatter.string(from: self as Date)
28+
(self as Date).wordPressComJSONString
3129
}
3230
}
33-
34-
extension DateFormatter {
35-
36-
static let rfc3339Formatter: DateFormatter = {
37-
let formatter = DateFormatter()
38-
formatter.dateFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ssZ"
39-
formatter.timeZone = NSTimeZone(forSecondsFromGMT: 0) as TimeZone
40-
formatter.locale = NSLocale(localeIdentifier: "en_US_POSIX") as Locale
41-
return formatter
42-
}()
43-
}

WordPressKit/PostServiceRemoteREST+Extended.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ private func decodePost(from object: AnyObject) async throws -> RemotePost {
4040

4141
private func makeParameters<T: Encodable>(from value: T) throws -> [String: AnyObject] {
4242
let encoder = JSONEncoder()
43-
encoder.dateEncodingStrategy = .formatted(NSDate.rfc3339Formatter)
43+
encoder.dateEncodingStrategy = .formatted(.wordPressCom)
4444
let data = try encoder.encode(value)
4545
let object = try JSONSerialization.jsonObject(with: data)
4646
guard let dictionary = object as? [String: AnyObject] else {
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
@testable import WordPressKit
2+
import XCTest
3+
4+
// This is an incomplete test for implementing RFC 3339.
5+
// It's purpose is to ensure our code "works".
6+
//
7+
// See also:
8+
//
9+
// - https://developer.wordpress.com/docs/api/
10+
// - https://datatracker.ietf.org/doc/html/rfc3339
11+
class DateWordPressComTests: XCTestCase {
12+
13+
func testValidRFC3339DateFromString() {
14+
XCTAssertEqual(
15+
Date.with(wordPressComJSONString: "2023-03-19T15:00:00Z"),
16+
Date(timeIntervalSince1970: 1_679_238_000)
17+
)
18+
}
19+
20+
func testInvalidRFC3339DateFromString() {
21+
XCTAssertNil(Date.with(wordPressComJSONString: "2024-01-01"))
22+
}
23+
24+
func testInvalidDateFromString() {
25+
XCTAssertNil(Date.with(wordPressComJSONString: "not a date"))
26+
}
27+
28+
func testValidRFC3339StringFromDate() {
29+
XCTAssertEqual(
30+
Date(timeIntervalSince1970: 1_679_238_000).wordPressComJSONString,
31+
// Apparently, NSDateFormatter doesn't offer a way to specify Z vs +0000.
32+
// This might go all the way back to the ISO 8601 and RFC 3339 specs overlap.
33+
"2023-03-19T15:00:00+0000"
34+
)
35+
}
36+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
@testable import WordPressKit
2+
import XCTest
3+
4+
class DateFormatterWordPressComTests: XCTestCase {
5+
6+
func testDateFormatterConfiguration() throws {
7+
let rfc3339Formatter = try XCTUnwrap(DateFormatter.wordPressCom)
8+
9+
XCTAssertEqual(rfc3339Formatter.timeZone, TimeZone(secondsFromGMT: 0))
10+
XCTAssertEqual(rfc3339Formatter.locale, Locale(identifier: "en_US_POSIX"))
11+
XCTAssertEqual(rfc3339Formatter.dateFormat, "yyyy'-'MM'-'dd'T'HH':'mm':'ssZ")
12+
}
13+
}

WordPressKitTests/NSDate+RFC3339Tests.swift renamed to WordPressKitTests/NSDate+WordPressComTests.swift

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,7 @@
11
@testable import WordPressKit
22
import XCTest
33

4-
// This is an incomplete test for implementing RFC 3339.
5-
// It's purpose is to ensure our code "works".
6-
//
7-
// See also:
8-
//
9-
// - https://datatracker.ietf.org/doc/html/rfc3339
10-
class NSDateRFC3339Tests: XCTestCase {
11-
12-
func testDateFormatterConfiguration() throws {
13-
let rfc3339Formatter = try XCTUnwrap(NSDate.rfc3339Formatter)
14-
15-
XCTAssertEqual(rfc3339Formatter.timeZone, TimeZone(secondsFromGMT: 0))
16-
XCTAssertEqual(rfc3339Formatter.locale, Locale(identifier: "en_US_POSIX"))
17-
XCTAssertEqual(rfc3339Formatter.dateFormat, "yyyy'-'MM'-'dd'T'HH':'mm':'ssZ")
18-
}
4+
class NSDateWordPressComTests: XCTestCase {
195

206
func testValidRFC3339DateFromString() {
217
XCTAssertEqual(

0 commit comments

Comments
 (0)