Files
hyungi_document_server/clients/ds-app/Sources/DSKit/DSDate.swift
T

34 lines
1.7 KiB
Swift

import Foundation
/// Date parsing for the contract's mixed shapes. Two ISO-8601 datetime shapes (with / without
/// fractional seconds) coexist with date-only strings in the SAME response (digest), so a single
/// JSONDecoder.dateDecodingStrategy is impossible. Dates are decoded as `String?` raw and parsed here.
///
/// Concurrency (B-1 review): uses value-type `Date.ISO8601FormatStyle` (Sendable) as static lets
/// no shared reference formatter, so no `nonisolated(unsafe)`.
///
/// Date-only (B-1 r2 review): `digest_date` should be DISPLAYED from its raw string (no Date conversion)
/// to avoid KST off-by-one; `parse` here fixes date-only to UTC and is for sort/window math only.
public enum DSDate {
private static let isoFractional = Date.ISO8601FormatStyle(includingFractionalSeconds: true)
private static let isoPlain = Date.ISO8601FormatStyle(includingFractionalSeconds: false)
public static func parse(_ s: String?) -> Date? {
guard let s, !s.isEmpty else { return nil }
if let d = try? isoFractional.parse(s) { return d }
if let d = try? isoPlain.parse(s) { return d }
return parseDateOnly(s)
}
/// Date-only "YYYY-MM-DD" fixed to UTC (avoids timezone off-by-one). Value-type Calendar, Sendable-safe.
private static func parseDateOnly(_ s: String) -> Date? {
let p = s.split(separator: "-")
guard p.count == 3, let y = Int(p[0]), let m = Int(p[1]), let d = Int(p[2]) else { return nil }
var comps = DateComponents()
comps.year = y; comps.month = m; comps.day = d
var cal = Calendar(identifier: .gregorian)
cal.timeZone = TimeZone(identifier: "UTC")!
return cal.date(from: comps)
}
}