Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SPM Prep - Convert "WordPress JSON date" logic from Objective-C to Swift #758

Merged
merged 13 commits into from
Mar 21, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions WordPressKit.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@
3F758FD324F6C68200BBA2FC /* AnnouncementServiceRemote.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F758FD224F6C68200BBA2FC /* AnnouncementServiceRemote.swift */; };
3F8308A729EE683500354497 /* ActivityTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F8308A629EE683500354497 /* ActivityTests.swift */; };
3FB8642C2888089F003A86BE /* BuildkiteTestCollector in Frameworks */ = {isa = PBXBuildFile; productRef = 3FB8642B2888089F003A86BE /* BuildkiteTestCollector */; };
3FFCC0412BA995290051D229 /* NSDate+RFC3339Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FFCC0402BA995290051D229 /* NSDate+RFC3339Tests.swift */; };
3FFCC0472BAA6EF40051D229 /* NSDate+RFC3339.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FFCC0462BAA6EF40051D229 /* NSDate+RFC3339.swift */; };
40247DFA2120D8E100AE1C3C /* AutomatedTransferService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40247DF92120D8E100AE1C3C /* AutomatedTransferService.swift */; };
40247DFC2120E69600AE1C3C /* AutomatedTransferStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40247DFB2120E69600AE1C3C /* AutomatedTransferStatus.swift */; };
404057C5221B30400060250C /* StatsSearchTermTimeIntervalData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 404057C4221B30400060250C /* StatsSearchTermTimeIntervalData.swift */; };
Expand Down Expand Up @@ -485,8 +487,6 @@
93BD27711EE737A8002BB00B /* ServiceRemoteWordPressXMLRPC.h in Headers */ = {isa = PBXBuildFile; fileRef = 93BD276D1EE737A8002BB00B /* ServiceRemoteWordPressXMLRPC.h */; settings = {ATTRIBUTES = (Public, ); }; };
93BD27721EE737A9002BB00B /* ServiceRemoteWordPressXMLRPC.m in Sources */ = {isa = PBXBuildFile; fileRef = 93BD276E1EE737A8002BB00B /* ServiceRemoteWordPressXMLRPC.m */; };
93BD277C1EE73944002BB00B /* HTTPAuthenticationAlertController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93BD27741EE73944002BB00B /* HTTPAuthenticationAlertController.swift */; };
93BD277D1EE73944002BB00B /* NSDate+WordPressJSON.h in Headers */ = {isa = PBXBuildFile; fileRef = 93BD27751EE73944002BB00B /* NSDate+WordPressJSON.h */; settings = {ATTRIBUTES = (Public, ); }; };
93BD277E1EE73944002BB00B /* NSDate+WordPressJSON.m in Sources */ = {isa = PBXBuildFile; fileRef = 93BD27761EE73944002BB00B /* NSDate+WordPressJSON.m */; };
93BD277F1EE73944002BB00B /* WordPressComOAuthClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93BD27771EE73944002BB00B /* WordPressComOAuthClient.swift */; };
93BD27801EE73944002BB00B /* WordPressComRestApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93BD27781EE73944002BB00B /* WordPressComRestApi.swift */; };
93BD27811EE73944002BB00B /* WordPressOrgXMLRPCApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93BD27791EE73944002BB00B /* WordPressOrgXMLRPCApi.swift */; };
Expand Down Expand Up @@ -798,6 +798,8 @@
3F758FD224F6C68200BBA2FC /* AnnouncementServiceRemote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnnouncementServiceRemote.swift; sourceTree = "<group>"; };
3F8308A629EE683500354497 /* ActivityTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityTests.swift; sourceTree = "<group>"; };
3FB8642D288813E9003A86BE /* UnitTests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = UnitTests.xctestplan; sourceTree = "<group>"; };
3FFCC0402BA995290051D229 /* NSDate+RFC3339Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSDate+RFC3339Tests.swift"; sourceTree = "<group>"; };
3FFCC0462BAA6EF40051D229 /* NSDate+RFC3339.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSDate+RFC3339.swift"; sourceTree = "<group>"; };
40247DF92120D8E100AE1C3C /* AutomatedTransferService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutomatedTransferService.swift; sourceTree = "<group>"; };
40247DFB2120E69600AE1C3C /* AutomatedTransferStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutomatedTransferStatus.swift; sourceTree = "<group>"; };
404057C4221B30400060250C /* StatsSearchTermTimeIntervalData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatsSearchTermTimeIntervalData.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1218,8 +1220,6 @@
93BD276D1EE737A8002BB00B /* ServiceRemoteWordPressXMLRPC.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ServiceRemoteWordPressXMLRPC.h; sourceTree = "<group>"; };
93BD276E1EE737A8002BB00B /* ServiceRemoteWordPressXMLRPC.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ServiceRemoteWordPressXMLRPC.m; sourceTree = "<group>"; };
93BD27741EE73944002BB00B /* HTTPAuthenticationAlertController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTTPAuthenticationAlertController.swift; sourceTree = "<group>"; };
93BD27751EE73944002BB00B /* NSDate+WordPressJSON.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSDate+WordPressJSON.h"; sourceTree = "<group>"; };
93BD27761EE73944002BB00B /* NSDate+WordPressJSON.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSDate+WordPressJSON.m"; sourceTree = "<group>"; };
93BD27771EE73944002BB00B /* WordPressComOAuthClient.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WordPressComOAuthClient.swift; sourceTree = "<group>"; };
93BD27781EE73944002BB00B /* WordPressComRestApi.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WordPressComRestApi.swift; sourceTree = "<group>"; };
93BD27791EE73944002BB00B /* WordPressOrgXMLRPCApi.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WordPressOrgXMLRPCApi.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1904,6 +1904,7 @@
FFA4D4A82423B10A00BF5180 /* WordPressOrgRestApiTests.swift */,
74B335DB1F06F4180053A184 /* WordPressOrgXMLRPCApiTests.swift */,
740B23D51F17F7C100067A2A /* XMLRPCTestable.swift */,
3FFCC0402BA995290051D229 /* NSDate+RFC3339Tests.swift */,
);
name = Tests;
sourceTree = "<group>";
Expand Down Expand Up @@ -2518,8 +2519,7 @@
4A05E7952B2FCB6400C25E3B /* NonceRetrieval.swift */,
4A05E7992B2FDC3200C25E3B /* WordPressOrgRestApi.swift */,
93BD27741EE73944002BB00B /* HTTPAuthenticationAlertController.swift */,
93BD27751EE73944002BB00B /* NSDate+WordPressJSON.h */,
93BD27761EE73944002BB00B /* NSDate+WordPressJSON.m */,
3FFCC0462BAA6EF40051D229 /* NSDate+RFC3339.swift */,
93BD27771EE73944002BB00B /* WordPressComOAuthClient.swift */,
93BD27781EE73944002BB00B /* WordPressComRestApi.swift */,
93BD27791EE73944002BB00B /* WordPressOrgXMLRPCApi.swift */,
Expand Down Expand Up @@ -2797,7 +2797,6 @@
93BD273C1EE73282002BB00B /* AccountServiceRemoteREST.h in Headers */,
93BD27711EE737A8002BB00B /* ServiceRemoteWordPressXMLRPC.h in Headers */,
93BD276F1EE737A8002BB00B /* ServiceRemoteWordPressComREST.h in Headers */,
93BD277D1EE73944002BB00B /* NSDate+WordPressJSON.h in Headers */,
93BD273B1EE73282002BB00B /* AccountServiceRemote.h in Headers */,
93BD27691EE736A8002BB00B /* RemoteUser.h in Headers */,
74B5F0DC1EF829B800B411E7 /* BlogServiceRemote.h in Headers */,
Expand Down Expand Up @@ -3427,7 +3426,6 @@
9311A68B1F22625A00704AC9 /* TaxonomyServiceRemoteXMLRPC.m in Sources */,
C797196E2679007B0072F984 /* SelfHostedPluginManagementClient.swift in Sources */,
40414060220F9F1F00CF7C5B /* StatsAllTimesInsight.swift in Sources */,
93BD277E1EE73944002BB00B /* NSDate+WordPressJSON.m in Sources */,
8B2F4BED24ABCAEF0056C08A /* Decodable+Dictionary.swift in Sources */,
E194CB731FBDEF6500B0A8B8 /* PluginState.swift in Sources */,
4A57A6882B54C68C008D0660 /* Constants.m in Sources */,
Expand Down Expand Up @@ -3459,6 +3457,7 @@
E632D7781F6E047400297F6D /* SocialLogin2FANonceInfo.swift in Sources */,
32FC1D29255C91ED00CD0A7B /* JetpackScanServiceRemote.swift in Sources */,
9F3E0B9B208732B3009CB5BA /* RemoteReaderSiteInfoSubscription.swift in Sources */,
3FFCC0472BAA6EF40051D229 /* NSDate+RFC3339.swift in Sources */,
7403A2E41EF06ED500DED7DC /* AccountSettingsRemote.swift in Sources */,
3236F77824AE34B40088E8F3 /* ReaderTopicServiceRemote+Interests.swift in Sources */,
FE20A6A4282A96C00025E975 /* RemoteBloggingPromptsSettings.swift in Sources */,
Expand Down Expand Up @@ -3611,6 +3610,7 @@
F4B0F4802ACB4EA9003ABC61 /* AllDomainsResultDomainTests.swift in Sources */,
74585B901F0D51F900E7E667 /* DomainsServiceRemoteRESTTests.swift in Sources */,
BAFA775624ADAB3C000F0D3A /* MockPluginDirectoryEntryProvider.swift in Sources */,
3FFCC0412BA995290051D229 /* NSDate+RFC3339Tests.swift in Sources */,
3F8308A729EE683500354497 /* ActivityTests.swift in Sources */,
9AB6D64A218727D60008F274 /* PostServiceRemoteRESTRevisionsTest.swift in Sources */,
01438D382B6A35FB0097D60A /* stats-summary.json in Sources */,
Expand Down
38 changes: 38 additions & 0 deletions WordPressKit/NSDate+RFC3339.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import Foundation

extension NSDate {

@objc
static let rfc3339DateFormatter = DateFormatter.rfc3339Formatter

/// Parses a date string
///
/// Dates in the format specified in http://www.w3.org/TR/NOTE-datetime should be OK.
/// The kind of dates returned by the REST API should match that format, even if the doc promises ISO 8601.
///
/// Parsing the full ISO 8601, or even RFC 3339 is more complex than this, and makes no sense right now.
///
/// - Warning: This method doesn't support fractional seconds or dates with leap seconds (23:59:60 turns into 23:59:00)
//
// Needs to be `public` because of the usages in the Objective-C code.
@objc(dateWithWordPressComJSONString:)
public static func with(wordPressComJSONString jsonString: String) -> Date? {
self.rfc3339DateFormatter.date(from: jsonString)
}

@objc(WordPressComJSONString)
public func wordPressComJSONString() -> String {
NSDate.rfc3339DateFormatter.string(from: self as Date)
}
}

extension DateFormatter {

static let rfc3339Formatter: DateFormatter = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you are going to make breaking changes, I guess we have an option of getting rid of the NSDate extension above and renaming this rfc3339Formatter to be something like wordPressComDateFormatter.

let formatter = DateFormatter()
formatter.dateFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ssZ"
formatter.timeZone = NSTimeZone(forSecondsFromGMT: 0) as TimeZone
formatter.locale = NSLocale(localeIdentifier: "en_US_POSIX") as Locale
return formatter
}()
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seemed neater to define this pre-configured DateFormatter in a DateFormatter extension, rather than NSDate.

21 changes: 0 additions & 21 deletions WordPressKit/NSDate+WordPressJSON.h

This file was deleted.

26 changes: 0 additions & 26 deletions WordPressKit/NSDate+WordPressJSON.m

This file was deleted.

2 changes: 1 addition & 1 deletion WordPressKit/PostServiceRemoteREST+Extended.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ private func decodePost(from object: AnyObject) async throws -> RemotePost {

private func makeParameters<T: Encodable>(from value: T) throws -> [String: AnyObject] {
let encoder = JSONEncoder()
encoder.dateEncodingStrategy = .formatted(NSDate.rfc3339DateFormatter())
encoder.dateEncodingStrategy = .formatted(NSDate.rfc3339DateFormatter)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I forgot about the API breakage concerns when making this a static let. 🤦‍♂️

Turns out there is one use of this outside the library, in the WordPress iOS tests.

I'll update the code reintroducing the static func but marking it as deprecated. This way, we can remove it at the next "serious" breaking change occasion.

let data = try encoder.encode(value)
let object = try JSONSerialization.jsonObject(with: data)
guard let dictionary = object as? [String: AnyObject] else {
Expand Down
2 changes: 1 addition & 1 deletion WordPressKit/RemoteCommentV2.swift
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ extension RemoteCommentV2: Decodable {

// since `date_gmt` is already in GMT timezone, manually add the timezone string to make the rfc3339 formatter happy (or it will throw otherwise).
guard let dateString = try? container.decode(String.self, forKey: .date),
let date = NSDate(wordPressComJSONString: dateString + "+00:00") as Date? else {
let date = NSDate.with(wordPressComJSONString: dateString + "+00:00") else {
throw DecodingError.dataCorruptedError(forKey: .date, in: container, debugDescription: "Date parsing failed")
}
self.date = date
Expand Down
1 change: 0 additions & 1 deletion WordPressKit/WordPressKit.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ FOUNDATION_EXPORT const unsigned char WordPressKitVersionString[];
#import <WordPressKit/RemoteTheme.h>
#import <WordPressKit/RemoteUser.h>

#import <WordPressKit/NSDate+WordPressJSON.h>
#import <WordPressKit/NSString+MD5.h>

#import <WordPressKit/WPKitLogging.h>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ final class CommentServiceRemoteREST_APIv2Tests: RemoteTestCase, RESTTestable {
XCTAssertEqual(firstComment.authorID, 135)
XCTAssertEqual(firstComment.authorName, "John Doe")
XCTAssertEqual(firstComment.authorURL, "https://example.com/john-doe")
XCTAssertEqual(firstComment.date, NSDate(wordPressComJSONString: "2021-07-01T10:50:11+00:00") as Date)
XCTAssertEqual(firstComment.date, NSDate.with(wordPressComJSONString: "2021-07-01T10:50:11+00:00"))
XCTAssertEqual(firstComment.content, "<p>Some example comment.</p>\n")
XCTAssertEqual(firstComment.link, "https://example.com/2021/05/25/example-post/comment-page-1/#comment-2")
XCTAssertEqual(firstComment.status, "approve") // verify that it's converted correctly.
Expand Down
4 changes: 2 additions & 2 deletions WordPressKitTests/CommentServiceRemoteRESTTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ final class CommentServiceRemoteRESTTests: RemoteTestCase, RESTTestable {
XCTAssertEqual(comment.authorEmail, "[email protected]")
XCTAssertEqual(comment.authorUrl, "author URL")
XCTAssertEqual(comment.authorIP, "000.0.00.000")
XCTAssertEqual(comment.date, NSDate(wordPressComJSONString: "2021-08-04T07:58:49+00:00") as Date)
XCTAssertEqual(comment.date, NSDate.with(wordPressComJSONString: "2021-08-04T07:58:49+00:00"))
XCTAssertEqual(comment.link, "comment URL")
XCTAssertEqual(comment.parentID, nil)
XCTAssertEqual(comment.postID, NSNumber(value: 1))
Expand Down Expand Up @@ -91,7 +91,7 @@ final class CommentServiceRemoteRESTTests: RemoteTestCase, RESTTestable {
XCTAssertEqual(comment.authorEmail, "[email protected]")
XCTAssertEqual(comment.authorUrl, "author URL")
XCTAssertEqual(comment.authorIP, "000.0.00.000")
XCTAssertEqual(comment.date, NSDate(wordPressComJSONString: "2021-08-04T07:58:49+00:00") as Date)
XCTAssertEqual(comment.date, NSDate.with(wordPressComJSONString: "2021-08-04T07:58:49+00:00"))
XCTAssertEqual(comment.link, "comment URL")
XCTAssertEqual(comment.parentID, nil)
XCTAssertEqual(comment.postID, NSNumber(value: 1))
Expand Down
43 changes: 43 additions & 0 deletions WordPressKitTests/NSDate+RFC3339Tests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
@testable import WordPressKit
import XCTest

// This is an incomplete test for implementing RFC 3339.
// It's purpose is to ensure our code "works".
//
// See also:
//
// - https://datatracker.ietf.org/doc/html/rfc3339
class NSDateRFC3339Tests: XCTestCase {

func testDateFormatterConfiguration() throws {
let rfc3339Formatter = try XCTUnwrap(NSDate.rfc3339DateFormatter)

XCTAssertEqual(rfc3339Formatter.timeZone, TimeZone(secondsFromGMT: 0))
XCTAssertEqual(rfc3339Formatter.locale, Locale(identifier: "en_US_POSIX"))
XCTAssertEqual(rfc3339Formatter.dateFormat, "yyyy'-'MM'-'dd'T'HH':'mm':'ssZ")
}

func testValidRFC3339DateFromString() {
XCTAssertEqual(
NSDate.with(wordPressComJSONString: "2023-03-19T15:00:00Z"),
Date(timeIntervalSince1970: 1_679_238_000)
)
}

func testInvalidRFC3339DateFromString() {
XCTAssertNil(NSDate.with(wordPressComJSONString: "2024-01-01"))
}

func testInvalidDateFromString() {
XCTAssertNil(NSDate.with(wordPressComJSONString: "not a date"))
}

func testValidRFC3339StringFromDate() {
XCTAssertEqual(
NSDate(timeIntervalSince1970: 1_679_238_000).wordPressComJSONString(),
// Apparently, NSDateFormatter doesn't offer a way to specify Z vs +0000.
// This might go all the way back to the ISO 8601 and RFC 3339 specs overlap.
"2023-03-19T15:00:00+0000"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have you tried to put a single quote ' around the Z in the format: "yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'"?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BTW, this is one of the reasons I think we should avoid using standard names in function names. It's pretty difficult to ensure conformance.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we are being really strict here, this test case should fail, because the output is not in RFC3339 time format.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @crazytonyli !

I didn't noticed that 🤦‍♂️

I tried locally and it does the job at returning 2023-03-19T15:00:00Z as I originally expected.

But ☝️ other tests that deal with API and dates fail. I think maybe we should leave it like this for the moment, and update / investigate later on. After all, the format the code uses right now is the "original" one.

What do you think?

)
}
}
2 changes: 1 addition & 1 deletion WordPressKitTests/PostServiceRemoteRESTAutosaveTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ class PostServiceRemoteRESTAutosaveTests: RemoteTestCase, RESTTestable {
XCTAssertEqual(remotePost.autosave.content, "<!-- wp:paragraph -->\n<p>Uno.</p>\n<!-- /wp:paragraph -->")
XCTAssertEqual(remotePost.autosave.excerpt, "abc")
XCTAssertEqual(remotePost.autosave.previewURL, "https://hello.wordpress.com/2019/10/28/hello-world/?preview=true&preview_nonce=07346f4e5d")
XCTAssertEqual(remotePost.autosave.modifiedDate, NSDate(wordPressComJSONString: "2019-10-28T02:06:39+00:00") as Date?)
XCTAssertEqual(remotePost.autosave.modifiedDate, NSDate.with(wordPressComJSONString: "2019-10-28T02:06:39+00:00"))
expect.fulfill()
}, failure: { _ in
XCTFail("This callback shouldn't get called")
Expand Down