2026-06-04 02:29:23 -07:00
|
|
|
@testable import ForgejoKit
|
2026-02-28 11:10:51 -08:00
|
|
|
import Foundation
|
|
|
|
|
import Testing
|
|
|
|
|
|
|
|
|
|
struct DateDecodingTests {
|
|
|
|
|
private func decoder() -> JSONDecoder {
|
2026-06-04 02:29:23 -07:00
|
|
|
let jsonDecoder = JSONDecoder()
|
|
|
|
|
jsonDecoder.dateDecodingStrategy = forgejoDateDecodingStrategy
|
|
|
|
|
return jsonDecoder
|
2026-02-28 11:10:51 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private struct DateWrapper: Codable {
|
|
|
|
|
let date: Date
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private var utcCalendar: Calendar {
|
|
|
|
|
var calendar = Calendar(identifier: .gregorian)
|
|
|
|
|
calendar.timeZone = TimeZone(identifier: "UTC")!
|
|
|
|
|
return calendar
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// MARK: - ISO 8601 without fractional seconds
|
|
|
|
|
|
2026-06-15 03:06:27 -07:00
|
|
|
@Test func decodesDateWithoutFractionalSeconds() throws {
|
2026-02-28 11:10:51 -08:00
|
|
|
let json = #"{"date":"2024-01-15T10:30:00Z"}"#
|
|
|
|
|
let result = try decoder().decode(DateWrapper.self, from: Data(json.utf8))
|
|
|
|
|
|
|
|
|
|
#expect(utcCalendar.component(.year, from: result.date) == 2024)
|
|
|
|
|
#expect(utcCalendar.component(.month, from: result.date) == 1)
|
|
|
|
|
#expect(utcCalendar.component(.day, from: result.date) == 15)
|
|
|
|
|
#expect(utcCalendar.component(.hour, from: result.date) == 10)
|
|
|
|
|
#expect(utcCalendar.component(.minute, from: result.date) == 30)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// MARK: - ISO 8601 with fractional seconds
|
|
|
|
|
|
2026-06-15 03:06:27 -07:00
|
|
|
@Test func decodesDateWithFractionalSeconds() throws {
|
2026-02-28 11:10:51 -08:00
|
|
|
let json = #"{"date":"2024-06-20T14:05:30.123Z"}"#
|
|
|
|
|
let result = try decoder().decode(DateWrapper.self, from: Data(json.utf8))
|
|
|
|
|
|
|
|
|
|
#expect(utcCalendar.component(.year, from: result.date) == 2024)
|
|
|
|
|
#expect(utcCalendar.component(.month, from: result.date) == 6)
|
|
|
|
|
#expect(utcCalendar.component(.day, from: result.date) == 20)
|
|
|
|
|
#expect(utcCalendar.component(.hour, from: result.date) == 14)
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-15 03:06:27 -07:00
|
|
|
@Test func decodesDateWithTripleZeroFraction() throws {
|
2026-02-28 11:10:51 -08:00
|
|
|
let json = #"{"date":"2024-01-15T10:30:00.000Z"}"#
|
|
|
|
|
let result = try decoder().decode(DateWrapper.self, from: Data(json.utf8))
|
|
|
|
|
|
|
|
|
|
#expect(utcCalendar.component(.second, from: result.date) == 0)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// MARK: - Invalid dates
|
|
|
|
|
|
2026-06-15 03:06:27 -07:00
|
|
|
@Test func throwsOnInvalidDateString() {
|
2026-02-28 11:10:51 -08:00
|
|
|
let json = #"{"date":"not-a-date"}"#
|
|
|
|
|
#expect(throws: DecodingError.self) {
|
|
|
|
|
_ = try decoder().decode(DateWrapper.self, from: Data(json.utf8))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-15 03:06:27 -07:00
|
|
|
@Test func throwsOnEmptyDateString() {
|
2026-02-28 11:10:51 -08:00
|
|
|
let json = #"{"date":""}"#
|
|
|
|
|
#expect(throws: DecodingError.self) {
|
|
|
|
|
_ = try decoder().decode(DateWrapper.self, from: Data(json.utf8))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// MARK: - Both formats decode to same value
|
|
|
|
|
|
2026-06-15 03:06:27 -07:00
|
|
|
@Test func bothFormatsProduceSameDate() throws {
|
2026-02-28 11:10:51 -08:00
|
|
|
let jsonWithout = #"{"date":"2024-03-10T08:00:00Z"}"#
|
|
|
|
|
let jsonWith = #"{"date":"2024-03-10T08:00:00.000Z"}"#
|
|
|
|
|
|
2026-06-04 02:29:23 -07:00
|
|
|
let jsonDecoder = decoder()
|
|
|
|
|
let resultWithout = try jsonDecoder.decode(DateWrapper.self, from: Data(jsonWithout.utf8))
|
|
|
|
|
let resultWith = try jsonDecoder.decode(DateWrapper.self, from: Data(jsonWith.utf8))
|
2026-02-28 11:10:51 -08:00
|
|
|
|
|
|
|
|
#expect(resultWithout.date == resultWith.date)
|
|
|
|
|
}
|
|
|
|
|
}
|