52 lines
2.2 KiB
Swift
52 lines
2.2 KiB
Swift
import Foundation
|
||
|
||
/// Parses and renders MCMS timestamps. MCMS emits UTC timestamps with a literal
|
||
/// space (e.g. `2026-05-04 21:51:57.394127`), so `ISO8601DateFormatter` will not
|
||
/// parse them. Try microsecond precision first, then seconds (field guide
|
||
/// §3.7 / §8.5). The `en_US_POSIX` locale forces stable parse rules regardless
|
||
/// of the device's 12/24-hour setting.
|
||
enum MongoTimestamp {
|
||
private static let withMicroseconds = parser("yyyy-MM-dd HH:mm:ss.SSSSSS")
|
||
private static let seconds = parser("yyyy-MM-dd HH:mm:ss")
|
||
|
||
private static func parser(_ format: String) -> DateFormatter {
|
||
let f = DateFormatter()
|
||
f.locale = Locale(identifier: "en_US_POSIX")
|
||
f.timeZone = TimeZone(identifier: "UTC")
|
||
f.dateFormat = format
|
||
return f
|
||
}
|
||
|
||
static func date(from string: String) -> Date? {
|
||
withMicroseconds.date(from: string) ?? seconds.date(from: string)
|
||
}
|
||
|
||
/// Human duration between two MCMS timestamps — e.g. uptime computed as
|
||
/// (device state `Time` − `Online Time`), so it uses the device's own clock
|
||
/// rather than the phone's. Returns nil if either is unparseable or end < start.
|
||
static func duration(from startString: String?, to endString: String?) -> String? {
|
||
guard let startString, let start = date(from: startString),
|
||
let endString, let end = date(from: endString) else { return nil }
|
||
let total = Int(end.timeIntervalSince(start))
|
||
guard total >= 0 else { return nil }
|
||
let days = total / 86400, hours = (total % 86400) / 3600, mins = (total % 3600) / 60
|
||
if days > 0 { return "\(days)d \(hours)h" }
|
||
if hours > 0 { return "\(hours)h \(mins)m" }
|
||
return "\(mins)m"
|
||
}
|
||
|
||
private static let display: DateFormatter = {
|
||
let f = DateFormatter()
|
||
f.dateStyle = .medium
|
||
f.timeStyle = .short
|
||
return f
|
||
}()
|
||
|
||
/// Render a raw MCMS timestamp in the device's locale/zone, falling back to
|
||
/// the original string if it doesn't parse.
|
||
static func displayString(_ string: String?) -> String? {
|
||
guard let string else { return nil }
|
||
guard let date = date(from: string) else { return string }
|
||
return display.string(from: date)
|
||
}
|
||
}
|