diff --git a/CHANGELOG.md b/CHANGELOG.md index 1dd443dc..543863c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,7 +34,7 @@ _None._ ### Breaking Changes -_None._ +- Reworked the `NSDate` RFC3339 / WordPress.com JSON conversions API [#759] ### New Features diff --git a/WordPressKit.podspec b/WordPressKit.podspec index 21a17789..56a26741 100644 --- a/WordPressKit.podspec +++ b/WordPressKit.podspec @@ -2,7 +2,7 @@ Pod::Spec.new do |s| s.name = 'WordPressKit' - s.version = '14.1.0' + s.version = '15.0.0-beta.1' s.summary = 'WordPressKit offers a clean and simple WordPress.com and WordPress.org API.' s.description = <<-DESC diff --git a/WordPressKit.xcodeproj/project.pbxproj b/WordPressKit.xcodeproj/project.pbxproj index d6d1b331..c5e75da7 100644 --- a/WordPressKit.xcodeproj/project.pbxproj +++ b/WordPressKit.xcodeproj/project.pbxproj @@ -71,6 +71,12 @@ 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 /* Date+WordPressComTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FFCC0402BA995290051D229 /* Date+WordPressComTests.swift */; }; + 3FFCC0472BAA6EF40051D229 /* NSDate+WordPressCom.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FFCC0462BAA6EF40051D229 /* NSDate+WordPressCom.swift */; }; + 3FFCC0492BAB98130051D229 /* DateFormatter+WordPressCom.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FFCC0482BAB98130051D229 /* DateFormatter+WordPressCom.swift */; }; + 3FFCC04B2BABA5220051D229 /* DateFormatter+WordPressComTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FFCC04A2BABA5220051D229 /* DateFormatter+WordPressComTests.swift */; }; + 3FFCC04D2BABA6980051D229 /* NSDate+WordPressComTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FFCC04C2BABA6980051D229 /* NSDate+WordPressComTests.swift */; }; + 3FFCC04F2BABA6E60051D229 /* Date+WordPressCom.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FFCC04E2BABA6E60051D229 /* Date+WordPressCom.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 */; }; @@ -485,8 +491,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 */; }; @@ -798,6 +802,12 @@ 3F758FD224F6C68200BBA2FC /* AnnouncementServiceRemote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnnouncementServiceRemote.swift; sourceTree = ""; }; 3F8308A629EE683500354497 /* ActivityTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityTests.swift; sourceTree = ""; }; 3FB8642D288813E9003A86BE /* UnitTests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = UnitTests.xctestplan; sourceTree = ""; }; + 3FFCC0402BA995290051D229 /* Date+WordPressComTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Date+WordPressComTests.swift"; sourceTree = ""; }; + 3FFCC0462BAA6EF40051D229 /* NSDate+WordPressCom.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSDate+WordPressCom.swift"; sourceTree = ""; }; + 3FFCC0482BAB98130051D229 /* DateFormatter+WordPressCom.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DateFormatter+WordPressCom.swift"; sourceTree = ""; }; + 3FFCC04A2BABA5220051D229 /* DateFormatter+WordPressComTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DateFormatter+WordPressComTests.swift"; sourceTree = ""; }; + 3FFCC04C2BABA6980051D229 /* NSDate+WordPressComTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSDate+WordPressComTests.swift"; sourceTree = ""; }; + 3FFCC04E2BABA6E60051D229 /* Date+WordPressCom.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Date+WordPressCom.swift"; sourceTree = ""; }; 40247DF92120D8E100AE1C3C /* AutomatedTransferService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutomatedTransferService.swift; sourceTree = ""; }; 40247DFB2120E69600AE1C3C /* AutomatedTransferStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutomatedTransferStatus.swift; sourceTree = ""; }; 404057C4221B30400060250C /* StatsSearchTermTimeIntervalData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatsSearchTermTimeIntervalData.swift; sourceTree = ""; }; @@ -1218,8 +1228,6 @@ 93BD276D1EE737A8002BB00B /* ServiceRemoteWordPressXMLRPC.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ServiceRemoteWordPressXMLRPC.h; sourceTree = ""; }; 93BD276E1EE737A8002BB00B /* ServiceRemoteWordPressXMLRPC.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ServiceRemoteWordPressXMLRPC.m; sourceTree = ""; }; 93BD27741EE73944002BB00B /* HTTPAuthenticationAlertController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTTPAuthenticationAlertController.swift; sourceTree = ""; }; - 93BD27751EE73944002BB00B /* NSDate+WordPressJSON.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSDate+WordPressJSON.h"; sourceTree = ""; }; - 93BD27761EE73944002BB00B /* NSDate+WordPressJSON.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSDate+WordPressJSON.m"; sourceTree = ""; }; 93BD27771EE73944002BB00B /* WordPressComOAuthClient.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WordPressComOAuthClient.swift; sourceTree = ""; }; 93BD27781EE73944002BB00B /* WordPressComRestApi.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WordPressComRestApi.swift; sourceTree = ""; }; 93BD27791EE73944002BB00B /* WordPressOrgXMLRPCApi.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WordPressOrgXMLRPCApi.swift; sourceTree = ""; }; @@ -1904,6 +1912,9 @@ FFA4D4A82423B10A00BF5180 /* WordPressOrgRestApiTests.swift */, 74B335DB1F06F4180053A184 /* WordPressOrgXMLRPCApiTests.swift */, 740B23D51F17F7C100067A2A /* XMLRPCTestable.swift */, + 3FFCC0402BA995290051D229 /* Date+WordPressComTests.swift */, + 3FFCC04C2BABA6980051D229 /* NSDate+WordPressComTests.swift */, + 3FFCC04A2BABA5220051D229 /* DateFormatter+WordPressComTests.swift */, ); name = Tests; sourceTree = ""; @@ -2518,8 +2529,9 @@ 4A05E7952B2FCB6400C25E3B /* NonceRetrieval.swift */, 4A05E7992B2FDC3200C25E3B /* WordPressOrgRestApi.swift */, 93BD27741EE73944002BB00B /* HTTPAuthenticationAlertController.swift */, - 93BD27751EE73944002BB00B /* NSDate+WordPressJSON.h */, - 93BD27761EE73944002BB00B /* NSDate+WordPressJSON.m */, + 3FFCC0462BAA6EF40051D229 /* NSDate+WordPressCom.swift */, + 3FFCC04E2BABA6E60051D229 /* Date+WordPressCom.swift */, + 3FFCC0482BAB98130051D229 /* DateFormatter+WordPressCom.swift */, 93BD27771EE73944002BB00B /* WordPressComOAuthClient.swift */, 93BD27781EE73944002BB00B /* WordPressComRestApi.swift */, 93BD27791EE73944002BB00B /* WordPressOrgXMLRPCApi.swift */, @@ -2797,7 +2809,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 */, @@ -3393,6 +3404,7 @@ 93BD277F1EE73944002BB00B /* WordPressComOAuthClient.swift in Sources */, 740B23B91F17EC7300067A2A /* PostServiceRemoteREST.m in Sources */, 93BD27801EE73944002BB00B /* WordPressComRestApi.swift in Sources */, + 3FFCC04F2BABA6E60051D229 /* Date+WordPressCom.swift in Sources */, 404057D2221C56AB0060250C /* StatsTopCountryTimeIntervalData.swift in Sources */, E11C2AD21FA77FB90023BDE2 /* SitePlugin.swift in Sources */, 4A68E3DF29407100004AC3DC /* RemoteReaderTopic.swift in Sources */, @@ -3427,7 +3439,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 */, @@ -3459,6 +3470,7 @@ E632D7781F6E047400297F6D /* SocialLogin2FANonceInfo.swift in Sources */, 32FC1D29255C91ED00CD0A7B /* JetpackScanServiceRemote.swift in Sources */, 9F3E0B9B208732B3009CB5BA /* RemoteReaderSiteInfoSubscription.swift in Sources */, + 3FFCC0472BAA6EF40051D229 /* NSDate+WordPressCom.swift in Sources */, 7403A2E41EF06ED500DED7DC /* AccountSettingsRemote.swift in Sources */, 3236F77824AE34B40088E8F3 /* ReaderTopicServiceRemote+Interests.swift in Sources */, FE20A6A4282A96C00025E975 /* RemoteBloggingPromptsSettings.swift in Sources */, @@ -3517,6 +3529,7 @@ 74C473AC1EF2F75E009918F2 /* SiteManagementServiceRemote.swift in Sources */, 74585B971F0D54B400E7E667 /* RemoteDomain.swift in Sources */, 0C1C08432B9CD8D200E52F8C /* PostServiceRemoteREST+Extended.swift in Sources */, + 3FFCC0492BAB98130051D229 /* DateFormatter+WordPressCom.swift in Sources */, 74A44DD01F13C64B006CD8F4 /* RemoteNotification.swift in Sources */, 8B52B901257AC5A200221663 /* Date+endOfDay.swift in Sources */, E1D6B558200E473A00325669 /* TimeZoneServiceRemote.swift in Sources */, @@ -3611,11 +3624,13 @@ F4B0F4802ACB4EA9003ABC61 /* AllDomainsResultDomainTests.swift in Sources */, 74585B901F0D51F900E7E667 /* DomainsServiceRemoteRESTTests.swift in Sources */, BAFA775624ADAB3C000F0D3A /* MockPluginDirectoryEntryProvider.swift in Sources */, + 3FFCC0412BA995290051D229 /* Date+WordPressComTests.swift in Sources */, 3F8308A729EE683500354497 /* ActivityTests.swift in Sources */, 9AB6D64A218727D60008F274 /* PostServiceRemoteRESTRevisionsTest.swift in Sources */, 01438D382B6A35FB0097D60A /* stats-summary.json in Sources */, 7430C9BD1F192C0F0051B8E6 /* ReaderPostServiceRemoteTests.m in Sources */, 1DC837C229B9F04F009DCD4B /* RemoteVideoPressVideoTests.swift in Sources */, + 3FFCC04D2BABA6980051D229 /* NSDate+WordPressComTests.swift in Sources */, FAD1345125909DEA00A8FEB1 /* JetpackBackupServiceRemoteTests.swift in Sources */, 8B2F4BE924ABC9DC0056C08A /* ReaderPostServiceRemote+CardsTests.swift in Sources */, 40F9880C221ACEEE00B7B369 /* StatsRemoteV2Tests.swift in Sources */, @@ -3643,6 +3658,7 @@ 4A05E79C2B2FDC6100C25E3B /* WordPressOrgAPITests.swift in Sources */, 3297E1DE2564653A00287D21 /* JetpackScanServiceRemoteTests.swift in Sources */, 01438D352B6A2B2C0097D60A /* stats-visits-month-unit-week.json in Sources */, + 3FFCC04B2BABA5220051D229 /* DateFormatter+WordPressComTests.swift in Sources */, 9F3E0BAC20873785009CB5BA /* ServiceRequestTest.swift in Sources */, 4624223E2548C26D002B8A12 /* SiteDesignServiceRemoteTests.swift in Sources */, 4A1DEF44293051BC00322608 /* LoggingTests.swift in Sources */, diff --git a/WordPressKit/Date+WordPressCom.swift b/WordPressKit/Date+WordPressCom.swift new file mode 100644 index 00000000..5619c59b --- /dev/null +++ b/WordPressKit/Date+WordPressCom.swift @@ -0,0 +1,19 @@ +extension Date { + + /// 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. + /// + /// - SeeAlso: [WordPress.com REST API docs](https://developer.wordpress.com/docs/api/) + /// - Warning: This method doesn't support fractional seconds or dates with leap seconds (23:59:60 turns into 23:59:00) + static func with(wordPressComJSONString jsonString: String) -> Date? { + DateFormatter.wordPressCom.date(from: jsonString) + } + + var wordPressComJSONString: String { + DateFormatter.wordPressCom.string(from: self) + } +} diff --git a/WordPressKit/DateFormatter+WordPressCom.swift b/WordPressKit/DateFormatter+WordPressCom.swift new file mode 100644 index 00000000..1bb03baf --- /dev/null +++ b/WordPressKit/DateFormatter+WordPressCom.swift @@ -0,0 +1,13 @@ +extension DateFormatter { + + /// A `DateFormatter` configured to manage dates compatible with the WordPress.com API. + /// + /// - SeeAlso: [https://developer.wordpress.com/docs/api/](https://developer.wordpress.com/docs/api/) + static let wordPressCom: DateFormatter = { + 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 + }() +} diff --git a/WordPressKit/NSDate+WordPressCom.swift b/WordPressKit/NSDate+WordPressCom.swift new file mode 100644 index 00000000..2170399c --- /dev/null +++ b/WordPressKit/NSDate+WordPressCom.swift @@ -0,0 +1,30 @@ +import Foundation + +// This `NSDate` extension wraps the `Date` implementation. +// +// It's done in two types because we cannot expose the `Date` methods to Objective-C, since `Date` is not a class: +// +// `@objc can only be used with members of classes, @objc protocols, and concrete extensions of classes` +extension NSDate { + + /// 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. + /// + /// - SeeAlso: [WordPress.com REST API docs](https://developer.wordpress.com/docs/api/) + /// - 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? { + Date.with(wordPressComJSONString: jsonString) + } + + @objc(WordPressComJSONString) + public func wordPressComJSONString() -> String { + (self as Date).wordPressComJSONString + } +} diff --git a/WordPressKit/NSDate+WordPressJSON.h b/WordPressKit/NSDate+WordPressJSON.h deleted file mode 100644 index 5d9c2171..00000000 --- a/WordPressKit/NSDate+WordPressJSON.h +++ /dev/null @@ -1,21 +0,0 @@ -#import - -@interface NSDate (WordPressJSON) - -+ (NSDateFormatter *)rfc3339DateFormatter; - -/** - 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) - */ - -+ (instancetype)dateWithWordPressComJSONString:(NSString *)string; - -- (NSString *)WordPressComJSONString; - -@end diff --git a/WordPressKit/NSDate+WordPressJSON.m b/WordPressKit/NSDate+WordPressJSON.m deleted file mode 100644 index b3774998..00000000 --- a/WordPressKit/NSDate+WordPressJSON.m +++ /dev/null @@ -1,26 +0,0 @@ -#import "NSDate+WordPressJSON.h" - -@implementation NSDate (WordPressJSON) - -+ (NSDateFormatter *)rfc3339DateFormatter { - static NSDateFormatter *rfc3339DateFormatter; - if (rfc3339DateFormatter == nil) { - rfc3339DateFormatter = [[NSDateFormatter alloc] init]; - NSLocale *enUSPOSIXLocale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"]; - - [rfc3339DateFormatter setLocale:enUSPOSIXLocale]; - [rfc3339DateFormatter setDateFormat:@"yyyy'-'MM'-'dd'T'HH':'mm':'ssZ"]; - [rfc3339DateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]]; - } - return rfc3339DateFormatter; -} - -+ (instancetype)dateWithWordPressComJSONString:(NSString *)string { - return [[self rfc3339DateFormatter] dateFromString:string]; -} - -- (NSString *)WordPressComJSONString { - return [[[self class] rfc3339DateFormatter] stringFromDate:self]; -} - -@end diff --git a/WordPressKit/PostServiceRemoteREST+Extended.swift b/WordPressKit/PostServiceRemoteREST+Extended.swift index f84b49f2..0af670fb 100644 --- a/WordPressKit/PostServiceRemoteREST+Extended.swift +++ b/WordPressKit/PostServiceRemoteREST+Extended.swift @@ -40,7 +40,7 @@ private func decodePost(from object: AnyObject) async throws -> RemotePost { private func makeParameters(from value: T) throws -> [String: AnyObject] { let encoder = JSONEncoder() - encoder.dateEncodingStrategy = .formatted(NSDate.rfc3339DateFormatter()) + encoder.dateEncodingStrategy = .formatted(.wordPressCom) let data = try encoder.encode(value) let object = try JSONSerialization.jsonObject(with: data) guard let dictionary = object as? [String: AnyObject] else { diff --git a/WordPressKit/RemoteCommentV2.swift b/WordPressKit/RemoteCommentV2.swift index b407bf6d..a80eaea2 100644 --- a/WordPressKit/RemoteCommentV2.swift +++ b/WordPressKit/RemoteCommentV2.swift @@ -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 diff --git a/WordPressKit/WordPressKit.h b/WordPressKit/WordPressKit.h index bec245f2..f498488e 100644 --- a/WordPressKit/WordPressKit.h +++ b/WordPressKit/WordPressKit.h @@ -47,7 +47,6 @@ FOUNDATION_EXPORT const unsigned char WordPressKitVersionString[]; #import #import -#import #import #import diff --git a/WordPressKitTests/CommentServiceRemoteREST+APIv2Tests.swift b/WordPressKitTests/CommentServiceRemoteREST+APIv2Tests.swift index 613959d6..1cdb540e 100644 --- a/WordPressKitTests/CommentServiceRemoteREST+APIv2Tests.swift +++ b/WordPressKitTests/CommentServiceRemoteREST+APIv2Tests.swift @@ -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, "

Some example comment.

\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. diff --git a/WordPressKitTests/CommentServiceRemoteRESTTests.swift b/WordPressKitTests/CommentServiceRemoteRESTTests.swift index cc1df856..b1b00f35 100644 --- a/WordPressKitTests/CommentServiceRemoteRESTTests.swift +++ b/WordPressKitTests/CommentServiceRemoteRESTTests.swift @@ -50,7 +50,7 @@ final class CommentServiceRemoteRESTTests: RemoteTestCase, RESTTestable { XCTAssertEqual(comment.authorEmail, "author@email.com") 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)) @@ -91,7 +91,7 @@ final class CommentServiceRemoteRESTTests: RemoteTestCase, RESTTestable { XCTAssertEqual(comment.authorEmail, "author@email.com") 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)) diff --git a/WordPressKitTests/Date+WordPressComTests.swift b/WordPressKitTests/Date+WordPressComTests.swift new file mode 100644 index 00000000..92c8cb5e --- /dev/null +++ b/WordPressKitTests/Date+WordPressComTests.swift @@ -0,0 +1,36 @@ +@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://developer.wordpress.com/docs/api/ +// - https://datatracker.ietf.org/doc/html/rfc3339 +class DateWordPressComTests: XCTestCase { + + func testValidRFC3339DateFromString() { + XCTAssertEqual( + Date.with(wordPressComJSONString: "2023-03-19T15:00:00Z"), + Date(timeIntervalSince1970: 1_679_238_000) + ) + } + + func testInvalidRFC3339DateFromString() { + XCTAssertNil(Date.with(wordPressComJSONString: "2024-01-01")) + } + + func testInvalidDateFromString() { + XCTAssertNil(Date.with(wordPressComJSONString: "not a date")) + } + + func testValidRFC3339StringFromDate() { + XCTAssertEqual( + Date(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" + ) + } +} diff --git a/WordPressKitTests/DateFormatter+WordPressComTests.swift b/WordPressKitTests/DateFormatter+WordPressComTests.swift new file mode 100644 index 00000000..e8642fab --- /dev/null +++ b/WordPressKitTests/DateFormatter+WordPressComTests.swift @@ -0,0 +1,13 @@ +@testable import WordPressKit +import XCTest + +class DateFormatterWordPressComTests: XCTestCase { + + func testDateFormatterConfiguration() throws { + let rfc3339Formatter = try XCTUnwrap(DateFormatter.wordPressCom) + + XCTAssertEqual(rfc3339Formatter.timeZone, TimeZone(secondsFromGMT: 0)) + XCTAssertEqual(rfc3339Formatter.locale, Locale(identifier: "en_US_POSIX")) + XCTAssertEqual(rfc3339Formatter.dateFormat, "yyyy'-'MM'-'dd'T'HH':'mm':'ssZ") + } +} diff --git a/WordPressKitTests/NSDate+WordPressComTests.swift b/WordPressKitTests/NSDate+WordPressComTests.swift new file mode 100644 index 00000000..50c3e211 --- /dev/null +++ b/WordPressKitTests/NSDate+WordPressComTests.swift @@ -0,0 +1,29 @@ +@testable import WordPressKit +import XCTest + +class NSDateWordPressComTests: XCTestCase { + + 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" + ) + } +} diff --git a/WordPressKitTests/PostServiceRemoteRESTAutosaveTests.swift b/WordPressKitTests/PostServiceRemoteRESTAutosaveTests.swift index 8b85dfbd..0883b4c4 100644 --- a/WordPressKitTests/PostServiceRemoteRESTAutosaveTests.swift +++ b/WordPressKitTests/PostServiceRemoteRESTAutosaveTests.swift @@ -41,7 +41,7 @@ class PostServiceRemoteRESTAutosaveTests: RemoteTestCase, RESTTestable { XCTAssertEqual(remotePost.autosave.content, "\n

Uno.

\n") 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")