Skip to content

Commit 653601b

Browse files
james-rantDaiki Matsudate
authored andcommitted
Fix #1424 [SWIFT4] Date Encoding Issues (#1442)
Ensure the same date format string is used throughout the generated code (use the one set in Configuration.swift). Ensure the same date formatter options are used when encoding dates as well as decoding dates. If a consumer has set their own date formatter on CodableHelper, use that when encoding dates too. Adds DateFormatTests to the SWIFT4 unit tests. Updates the SWIFT4 petstore samples
1 parent 7564d62 commit 653601b

File tree

47 files changed

+538
-102
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+538
-102
lines changed

modules/openapi-generator/src/main/resources/swift4/CodableHelper.mustache

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ open class CodableHelper {
2626
formatter.calendar = Calendar(identifier: .iso8601)
2727
formatter.locale = Locale(identifier: "en_US_POSIX")
2828
formatter.timeZone = TimeZone(secondsFromGMT: 0)
29-
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSXXXXX"
29+
formatter.dateFormat = Configuration.dateFormat
3030
decoder.dateDecodingStrategy = .formatted(formatter)
3131
}
3232

@@ -55,7 +55,7 @@ open class CodableHelper {
5555
formatter.calendar = Calendar(identifier: .iso8601)
5656
formatter.locale = Locale(identifier: "en_US_POSIX")
5757
formatter.timeZone = TimeZone(secondsFromGMT: 0)
58-
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSXXXXX"
58+
formatter.dateFormat = Configuration.dateFormat
5959
encoder.dateEncodingStrategy = .formatted(formatter)
6060
}
6161

modules/openapi-generator/src/main/resources/swift4/Extensions.mustache

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,10 +67,16 @@ extension Data: JSONEncodable {
6767
}
6868

6969
private let dateFormatter: DateFormatter = {
70-
let fmt = DateFormatter()
71-
fmt.dateFormat = Configuration.dateFormat
72-
fmt.locale = Locale(identifier: "en_US_POSIX")
73-
return fmt
70+
if let formatter = CodableHelper.dateformatter {
71+
return formatter
72+
} else {
73+
let formatter = DateFormatter()
74+
formatter.calendar = Calendar(identifier: .iso8601)
75+
formatter.locale = Locale(identifier: "en_US_POSIX")
76+
formatter.timeZone = TimeZone(secondsFromGMT: 0)
77+
formatter.dateFormat = Configuration.dateFormat
78+
return formatter
79+
}
7480
}()
7581

7682
extension Date: JSONEncodable {

samples/client/petstore/swift4/default/PetstoreClient/Classes/OpenAPIs/APIs/FakeAPI.swift

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -455,13 +455,16 @@ open class FakeAPI {
455455
/**
456456
Fake endpoint to test group parameters (optional)
457457

458+
- parameter requiredStringGroup: (query) Required String in group parameters
459+
- parameter requiredBooleanGroup: (header) Required Boolean in group parameters
460+
- parameter requiredInt64Group: (query) Required Integer in group parameters
458461
- parameter stringGroup: (query) String in group parameters (optional)
459462
- parameter booleanGroup: (header) Boolean in group parameters (optional)
460463
- parameter int64Group: (query) Integer in group parameters (optional)
461464
- parameter completion: completion handler to receive the data and the error objects
462465
*/
463-
open class func testGroupParameters(stringGroup: Int? = nil, booleanGroup: Bool? = nil, int64Group: Int64? = nil, completion: @escaping ((_ data: Void?,_ error: Error?) -> Void)) {
464-
testGroupParametersWithRequestBuilder(stringGroup: stringGroup, booleanGroup: booleanGroup, int64Group: int64Group).execute { (response, error) -> Void in
466+
open class func testGroupParameters(requiredStringGroup: Int, requiredBooleanGroup: Bool, requiredInt64Group: Int64, stringGroup: Int? = nil, booleanGroup: Bool? = nil, int64Group: Int64? = nil, completion: @escaping ((_ data: Void?,_ error: Error?) -> Void)) {
467+
testGroupParametersWithRequestBuilder(requiredStringGroup: requiredStringGroup, requiredBooleanGroup: requiredBooleanGroup, requiredInt64Group: requiredInt64Group, stringGroup: stringGroup, booleanGroup: booleanGroup, int64Group: int64Group).execute { (response, error) -> Void in
465468
if error == nil {
466469
completion((), error)
467470
} else {
@@ -475,22 +478,28 @@ open class FakeAPI {
475478
Fake endpoint to test group parameters (optional)
476479
- DELETE /fake
477480
- Fake endpoint to test group parameters (optional)
481+
- parameter requiredStringGroup: (query) Required String in group parameters
482+
- parameter requiredBooleanGroup: (header) Required Boolean in group parameters
483+
- parameter requiredInt64Group: (query) Required Integer in group parameters
478484
- parameter stringGroup: (query) String in group parameters (optional)
479485
- parameter booleanGroup: (header) Boolean in group parameters (optional)
480486
- parameter int64Group: (query) Integer in group parameters (optional)
481487
- returns: RequestBuilder<Void>
482488
*/
483-
open class func testGroupParametersWithRequestBuilder(stringGroup: Int? = nil, booleanGroup: Bool? = nil, int64Group: Int64? = nil) -> RequestBuilder<Void> {
489+
open class func testGroupParametersWithRequestBuilder(requiredStringGroup: Int, requiredBooleanGroup: Bool, requiredInt64Group: Int64, stringGroup: Int? = nil, booleanGroup: Bool? = nil, int64Group: Int64? = nil) -> RequestBuilder<Void> {
484490
let path = "/fake"
485491
let URLString = PetstoreClientAPI.basePath + path
486492
let parameters: [String:Any]? = nil
487493

488494
var url = URLComponents(string: URLString)
489495
url?.queryItems = APIHelper.mapValuesToQueryItems([
496+
"required_string_group": requiredStringGroup.encodeToJSON(),
497+
"required_int64_group": requiredInt64Group.encodeToJSON(),
490498
"string_group": stringGroup?.encodeToJSON(),
491499
"int64_group": int64Group?.encodeToJSON()
492500
])
493501
let nillableHeaders: [String: Any?] = [
502+
"required_boolean_group": requiredBooleanGroup,
494503
"boolean_group": booleanGroup
495504
]
496505
let headerParameters = APIHelper.rejectNilHeaders(nillableHeaders)

samples/client/petstore/swift4/default/PetstoreClient/Classes/OpenAPIs/CodableHelper.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ open class CodableHelper {
2626
formatter.calendar = Calendar(identifier: .iso8601)
2727
formatter.locale = Locale(identifier: "en_US_POSIX")
2828
formatter.timeZone = TimeZone(secondsFromGMT: 0)
29-
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSXXXXX"
29+
formatter.dateFormat = Configuration.dateFormat
3030
decoder.dateDecodingStrategy = .formatted(formatter)
3131
}
3232

@@ -55,7 +55,7 @@ open class CodableHelper {
5555
formatter.calendar = Calendar(identifier: .iso8601)
5656
formatter.locale = Locale(identifier: "en_US_POSIX")
5757
formatter.timeZone = TimeZone(secondsFromGMT: 0)
58-
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSXXXXX"
58+
formatter.dateFormat = Configuration.dateFormat
5959
encoder.dateEncodingStrategy = .formatted(formatter)
6060
}
6161

samples/client/petstore/swift4/default/PetstoreClient/Classes/OpenAPIs/Extensions.swift

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,10 +66,16 @@ extension Data: JSONEncodable {
6666
}
6767

6868
private let dateFormatter: DateFormatter = {
69-
let fmt = DateFormatter()
70-
fmt.dateFormat = Configuration.dateFormat
71-
fmt.locale = Locale(identifier: "en_US_POSIX")
72-
return fmt
69+
if let formatter = CodableHelper.dateformatter {
70+
return formatter
71+
} else {
72+
let formatter = DateFormatter()
73+
formatter.calendar = Calendar(identifier: .iso8601)
74+
formatter.locale = Locale(identifier: "en_US_POSIX")
75+
formatter.timeZone = TimeZone(secondsFromGMT: 0)
76+
formatter.dateFormat = Configuration.dateFormat
77+
return formatter
78+
}
7379
}()
7480

7581
extension Date: JSONEncodable {

samples/client/petstore/swift4/default/SwaggerClientTests/SwaggerClient.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
objects = {
88

99
/* Begin PBXBuildFile section */
10+
1A501F48219C3DC600F372F6 /* DateFormatTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A501F47219C3DC600F372F6 /* DateFormatTests.swift */; };
1011
54DA06C1D70D78EC0EC72B61 /* Pods_SwaggerClientTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F65B6638217EDDC99D103B16 /* Pods_SwaggerClientTests.framework */; };
1112
6D4EFB951C692C6300B96B06 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4EFB941C692C6300B96B06 /* AppDelegate.swift */; };
1213
6D4EFB971C692C6300B96B06 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4EFB961C692C6300B96B06 /* ViewController.swift */; };
@@ -30,6 +31,7 @@
3031
/* End PBXContainerItemProxy section */
3132

3233
/* Begin PBXFileReference section */
34+
1A501F47219C3DC600F372F6 /* DateFormatTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DateFormatTests.swift; sourceTree = "<group>"; };
3335
289E8A9E9C0BB66AD190C7C6 /* Pods-SwaggerClientTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SwaggerClientTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SwaggerClientTests/Pods-SwaggerClientTests.debug.xcconfig"; sourceTree = "<group>"; };
3436
6D4EFB911C692C6300B96B06 /* SwaggerClient.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SwaggerClient.app; sourceTree = BUILT_PRODUCTS_DIR; };
3537
6D4EFB941C692C6300B96B06 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
@@ -132,6 +134,7 @@
132134
6D4EFBB41C693BE200B96B06 /* PetAPITests.swift */,
133135
6D4EFBB61C693BED00B96B06 /* StoreAPITests.swift */,
134136
6D4EFBB81C693BFC00B96B06 /* UserAPITests.swift */,
137+
1A501F47219C3DC600F372F6 /* DateFormatTests.swift */,
135138
);
136139
path = SwaggerClientTests;
137140
sourceTree = "<group>";
@@ -360,6 +363,7 @@
360363
files = (
361364
6D4EFBB71C693BED00B96B06 /* StoreAPITests.swift in Sources */,
362365
6D4EFBB91C693BFC00B96B06 /* UserAPITests.swift in Sources */,
366+
1A501F48219C3DC600F372F6 /* DateFormatTests.swift in Sources */,
363367
6D4EFBB51C693BE200B96B06 /* PetAPITests.swift in Sources */,
364368
);
365369
runOnlyForDeploymentPostprocessing = 0;
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
//
2+
// DateFormatTests.swift
3+
// SwaggerClientTests
4+
//
5+
// Created by James on 14/11/2018.
6+
// Copyright © 2018 Swagger. All rights reserved.
7+
//
8+
9+
import Foundation
10+
import XCTest
11+
@testable import PetstoreClient
12+
@testable import SwaggerClient
13+
14+
class DateFormatTests: XCTestCase {
15+
16+
struct DateTest: Codable {
17+
let date: Date
18+
}
19+
20+
override func setUp() {
21+
super.setUp()
22+
// Put setup code here. This method is called before the invocation of each test method in the class.
23+
}
24+
25+
override func tearDown() {
26+
// Put teardown code here. This method is called after the invocation of each test method in the class.
27+
super.tearDown()
28+
}
29+
30+
func testEncodeToJSONAlwaysResultsInUTCEncodedDate() {
31+
var dateComponents = DateComponents()
32+
dateComponents.calendar = Calendar(identifier: .gregorian)
33+
dateComponents.year = 2018
34+
dateComponents.month = 11
35+
dateComponents.day = 14
36+
dateComponents.hour = 11
37+
dateComponents.minute = 35
38+
dateComponents.second = 43
39+
dateComponents.nanosecond = 500
40+
41+
// Testing a date with a timezone of +00:00 (UTC)
42+
dateComponents.timeZone = TimeZone(secondsFromGMT: 0)
43+
XCTAssert(dateComponents.isValidDate)
44+
45+
guard let utcDate = dateComponents.date else {
46+
XCTFail("Couldn't get a valid date")
47+
return
48+
}
49+
50+
var encodedDate = utcDate.encodeToJSON() as! String
51+
XCTAssert(encodedDate.hasSuffix("Z"))
52+
53+
// test with a positive timzone offset from UTC
54+
dateComponents.timeZone = TimeZone(secondsFromGMT: 60 * 60) // +01:00
55+
XCTAssert(dateComponents.isValidDate)
56+
57+
guard let nonUTCDate1 = dateComponents.date else {
58+
XCTFail("Couldn't get a valid date")
59+
return
60+
}
61+
62+
encodedDate = nonUTCDate1.encodeToJSON() as! String
63+
XCTAssert(encodedDate.hasSuffix("Z"))
64+
65+
// test with a negative timzone offset from UTC
66+
dateComponents.timeZone = TimeZone(secondsFromGMT: -(60 * 60)) // -01:00
67+
XCTAssert(dateComponents.isValidDate)
68+
69+
guard let nonUTCDate2 = dateComponents.date else {
70+
XCTFail("Couldn't get a valid date")
71+
return
72+
}
73+
74+
encodedDate = nonUTCDate2.encodeToJSON() as! String
75+
XCTAssert(encodedDate.hasSuffix("Z"))
76+
}
77+
78+
func testCodableAlwaysResultsInUTCEncodedDate() {
79+
let jsonData = "{\"date\":\"1970-01-01T00:00:00.000Z\"}".data(using: .utf8)!
80+
let decodeResult = CodableHelper.decode(DateTest.self, from: jsonData)
81+
XCTAssert(decodeResult.decodableObj != nil && decodeResult.error == nil)
82+
83+
var dateComponents = DateComponents()
84+
dateComponents.calendar = Calendar(identifier: .gregorian)
85+
dateComponents.year = 1970
86+
dateComponents.month = 01
87+
dateComponents.day = 01
88+
dateComponents.hour = 00
89+
dateComponents.minute = 00
90+
dateComponents.second = 00
91+
92+
// Testing a date with a timezone of +00:00 (UTC)
93+
dateComponents.timeZone = TimeZone(secondsFromGMT: 0)
94+
XCTAssert(dateComponents.isValidDate)
95+
96+
guard let date = dateComponents.date else {
97+
XCTFail("Couldn't get a valid date")
98+
return
99+
}
100+
101+
let dateTest = DateTest(date: date)
102+
let encodeResult = CodableHelper.encode(dateTest)
103+
XCTAssert(encodeResult.data != nil && encodeResult.error == nil)
104+
guard let jsonString = String(data: encodeResult.data!, encoding: .utf8) else {
105+
XCTFail("Unable to convert encoded data to string.")
106+
return
107+
}
108+
109+
let exampleJSONString = "{\"date\":\"1970-01-01T00:00:00.000Z\"}"
110+
XCTAssert(jsonString == exampleJSONString, "Encoded JSON String: \(jsonString) should match: \(exampleJSONString)")
111+
}
112+
113+
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
3.3.0-SNAPSHOT
1+
3.3.3-SNAPSHOT

samples/client/petstore/swift4/objcCompatible/PetstoreClient/Classes/OpenAPIs/APIs.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@
77
import Foundation
88

99
open class PetstoreClientAPI {
10-
open static var basePath = "http://petstore.swagger.io:80/v2"
11-
open static var credential: URLCredential?
12-
open static var customHeaders: [String:String] = [:]
13-
open static var requestBuilderFactory: RequestBuilderFactory = AlamofireRequestBuilderFactory()
10+
public static var basePath = "http://petstore.swagger.io:80/v2"
11+
public static var credential: URLCredential?
12+
public static var customHeaders: [String:String] = [:]
13+
public static var requestBuilderFactory: RequestBuilderFactory = AlamofireRequestBuilderFactory()
1414
}
1515

1616
open class RequestBuilder<T> {

samples/client/petstore/swift4/objcCompatible/PetstoreClient/Classes/OpenAPIs/APIs/FakeAPI.swift

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -452,6 +452,63 @@ open class FakeAPI {
452452
return requestBuilder.init(method: "GET", URLString: (url?.string ?? URLString), parameters: parameters, isBody: false, headers: headerParameters)
453453
}
454454

455+
/**
456+
Fake endpoint to test group parameters (optional)
457+
458+
- parameter requiredStringGroup: (query) Required String in group parameters
459+
- parameter requiredBooleanGroup: (header) Required Boolean in group parameters
460+
- parameter requiredInt64Group: (query) Required Integer in group parameters
461+
- parameter stringGroup: (query) String in group parameters (optional)
462+
- parameter booleanGroup: (header) Boolean in group parameters (optional)
463+
- parameter int64Group: (query) Integer in group parameters (optional)
464+
- parameter completion: completion handler to receive the data and the error objects
465+
*/
466+
open class func testGroupParameters(requiredStringGroup: Int, requiredBooleanGroup: Bool, requiredInt64Group: Int64, stringGroup: Int? = nil, booleanGroup: Bool? = nil, int64Group: Int64? = nil, completion: @escaping ((_ data: Void?,_ error: Error?) -> Void)) {
467+
testGroupParametersWithRequestBuilder(requiredStringGroup: requiredStringGroup, requiredBooleanGroup: requiredBooleanGroup, requiredInt64Group: requiredInt64Group, stringGroup: stringGroup, booleanGroup: booleanGroup, int64Group: int64Group).execute { (response, error) -> Void in
468+
if error == nil {
469+
completion((), error)
470+
} else {
471+
completion(nil, error)
472+
}
473+
}
474+
}
475+
476+
477+
/**
478+
Fake endpoint to test group parameters (optional)
479+
- DELETE /fake
480+
- Fake endpoint to test group parameters (optional)
481+
- parameter requiredStringGroup: (query) Required String in group parameters
482+
- parameter requiredBooleanGroup: (header) Required Boolean in group parameters
483+
- parameter requiredInt64Group: (query) Required Integer in group parameters
484+
- parameter stringGroup: (query) String in group parameters (optional)
485+
- parameter booleanGroup: (header) Boolean in group parameters (optional)
486+
- parameter int64Group: (query) Integer in group parameters (optional)
487+
- returns: RequestBuilder<Void>
488+
*/
489+
open class func testGroupParametersWithRequestBuilder(requiredStringGroup: Int, requiredBooleanGroup: Bool, requiredInt64Group: Int64, stringGroup: Int? = nil, booleanGroup: Bool? = nil, int64Group: Int64? = nil) -> RequestBuilder<Void> {
490+
let path = "/fake"
491+
let URLString = PetstoreClientAPI.basePath + path
492+
let parameters: [String:Any]? = nil
493+
494+
var url = URLComponents(string: URLString)
495+
url?.queryItems = APIHelper.mapValuesToQueryItems([
496+
"required_string_group": requiredStringGroup.encodeToJSON(),
497+
"required_int64_group": requiredInt64Group.encodeToJSON(),
498+
"string_group": stringGroup?.encodeToJSON(),
499+
"int64_group": int64Group?.encodeToJSON()
500+
])
501+
let nillableHeaders: [String: Any?] = [
502+
"required_boolean_group": requiredBooleanGroup,
503+
"boolean_group": booleanGroup
504+
]
505+
let headerParameters = APIHelper.rejectNilHeaders(nillableHeaders)
506+
507+
let requestBuilder: RequestBuilder<Void>.Type = PetstoreClientAPI.requestBuilderFactory.getNonDecodableBuilder()
508+
509+
return requestBuilder.init(method: "DELETE", URLString: (url?.string ?? URLString), parameters: parameters, isBody: false, headers: headerParameters)
510+
}
511+
455512
/**
456513
test inline additionalProperties
457514

0 commit comments

Comments
 (0)