Files
duooomi-ios-sdk/Sources/DuooomiBleSDK/Utils/BeijingTimeProvider.swift

128 lines
4.7 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import Foundation
/// ISO 8601 `2026-05-07T16:58:00+08:00` bind / setPlayMode payload `time`
///
/// fallback
/// 1. API body`sysTime2` `sysTime1`
/// 2. response.headers["Date"]HTTP RFC 1123
/// 3. `Date()` + 8h
///
/// 5 bind/setPlayMode
final class BeijingTimeProvider {
private static let suningURL = URL(string: "https://quan.suning.com/getSysTime.do")!
private static let cacheTTL: TimeInterval = 5
private static let fetchTimeout: TimeInterval = 3
private static let beijingOffsetSeconds: TimeInterval = 8 * 3600
private let session: URLSession
private let queue = DispatchQueue(label: "duooomi.ble.beijingTime")
private var cached: (value: String, at: Date)?
private var inflight: [(String) -> Void] = []
private var fetching = false
init(session: URLSession = .shared) {
self.session = session
}
///
func fetch(completion: @escaping (String) -> Void) {
queue.async { [weak self] in
guard let self = self else { return }
if let cached = self.cached, Date().timeIntervalSince(cached.at) < Self.cacheTTL {
self.dispatch(cached.value, to: completion)
return
}
self.inflight.append(completion)
if self.fetching { return }
self.fetching = true
self.performFetch { value in
self.queue.async {
self.cached = (value, Date())
let pending = self.inflight
self.inflight = []
self.fetching = false
pending.forEach { self.dispatch(value, to: $0) }
}
}
}
}
private func dispatch(_ value: String, to completion: @escaping (String) -> Void) {
DispatchQueue.main.async { completion(value) }
}
private func performFetch(_ done: @escaping (String) -> Void) {
var req = URLRequest(url: Self.suningURL)
req.timeoutInterval = Self.fetchTimeout
req.httpMethod = "GET"
session.dataTask(with: req) { [weak self] data, resp, _ in
guard let self = self else { return }
if let data = data, let body = try? JSONDecoder().decode(SuningTimeBody.self, from: data),
let value = self.formatSuningBody(body) {
done(value)
return
}
if let http = resp as? HTTPURLResponse,
let dateHeader = http.headerValue(caseInsensitive: "Date"),
let value = self.formatHTTPDateHeader(dateHeader) {
done(value)
return
}
done(self.formatLocal())
}.resume()
}
private func formatSuningBody(_ body: SuningTimeBody) -> String? {
if let s = body.sysTime2,
s.range(of: #"^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$"#, options: .regularExpression) != nil {
return s.replacingOccurrences(of: " ", with: "T") + "+08:00"
}
if let s = body.sysTime1, s.count == 14, Int(s) != nil {
let y = s.prefix(4)
let mo = s.dropFirst(4).prefix(2)
let d = s.dropFirst(6).prefix(2)
let h = s.dropFirst(8).prefix(2)
let mi = s.dropFirst(10).prefix(2)
let se = s.dropFirst(12).prefix(2)
return "\(y)-\(mo)-\(d)T\(h):\(mi):\(se)+08:00"
}
return nil
}
private func formatHTTPDateHeader(_ raw: String) -> String? {
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.timeZone = TimeZone(identifier: "GMT")
formatter.dateFormat = "EEE, dd MMM yyyy HH:mm:ss zzz"
guard let date = formatter.date(from: raw) else { return nil }
return Self.formatBeijing(date)
}
private func formatLocal() -> String {
Self.formatBeijing(Date())
}
private static func formatBeijing(_ date: Date) -> String {
var calendar = Calendar(identifier: .gregorian)
calendar.timeZone = TimeZone(secondsFromGMT: Int(beijingOffsetSeconds))!
let c = calendar.dateComponents([.year, .month, .day, .hour, .minute, .second], from: date)
return String(
format: "%04d-%02d-%02dT%02d:%02d:%02d+08:00",
c.year ?? 1970, c.month ?? 1, c.day ?? 1,
c.hour ?? 0, c.minute ?? 0, c.second ?? 0
)
}
}
private struct SuningTimeBody: Decodable {
let sysTime1: String?
let sysTime2: String?
}