field_ios

In-the-field app for Radroots on iOS
git clone https://radroots.dev/git/field_ios.git
Log | Files | Refs | README | LICENSE

TeraCheckedTime.swift (2384B)


      1 import Foundation
      2 
      3 enum TeraClockError: Error, Sendable, Equatable {
      4   case nonfinite
      5   case beforeUnixEpoch
      6   case overflow
      7   case zeroNotAllowed
      8 }
      9 
     10 struct TeraClock: Sendable {
     11   private let now: @Sendable () -> Date
     12 
     13   static let system = Self(now: { Date() })
     14 
     15   init(now: @escaping @Sendable () -> Date) {
     16     self.now = now
     17   }
     18 
     19   static func fixed(unixSeconds: UInt64) -> Self {
     20     Self(now: { Date(timeIntervalSince1970: TimeInterval(unixSeconds)) })
     21   }
     22 
     23   func unixSeconds(requirePositive: Bool = false) throws -> UInt64 {
     24     try Self.unixSeconds(from: now(), requirePositive: requirePositive)
     25   }
     26 
     27   func unixMilliseconds(requirePositive: Bool = false) throws -> UInt64 {
     28     try Self.unixMilliseconds(from: now(), requirePositive: requirePositive)
     29   }
     30 
     31   static func unixSeconds(
     32     from date: Date,
     33     requirePositive: Bool = false
     34   ) throws -> UInt64 {
     35     try unsigned(date.timeIntervalSince1970, multiplier: 1, requirePositive: requirePositive)
     36   }
     37 
     38   static func unixMilliseconds(
     39     from date: Date,
     40     requirePositive: Bool = false
     41   ) throws -> UInt64 {
     42     try unsigned(date.timeIntervalSince1970, multiplier: 1000, requirePositive: requirePositive)
     43   }
     44 
     45   static func signedUnixMilliseconds(from date: Date) throws -> Int64 {
     46     let value = try unixMilliseconds(from: date)
     47     guard let signed = Int64(exactly: value) else {
     48       throw TeraClockError.overflow
     49     }
     50     return signed
     51   }
     52 
     53   private static func unsigned(
     54     _ seconds: TimeInterval,
     55     multiplier: TimeInterval,
     56     requirePositive: Bool
     57   ) throws -> UInt64 {
     58     guard seconds.isFinite else {
     59       throw TeraClockError.nonfinite
     60     }
     61     guard seconds >= 0 else {
     62       throw TeraClockError.beforeUnixEpoch
     63     }
     64     let scaled = seconds * multiplier
     65     guard scaled.isFinite,
     66       let value = UInt64(exactly: scaled.rounded(.down))
     67     else {
     68       throw TeraClockError.overflow
     69     }
     70     guard !requirePositive || value > 0 else {
     71       throw TeraClockError.zeroNotAllowed
     72     }
     73     return value
     74   }
     75 }
     76 
     77 enum TeraStateTransitionError: Error, Sendable, Equatable {
     78   case generationOverflow
     79 }
     80 
     81 enum TeraCheckedStateTransition {
     82   static func nextGeneration(after generation: UInt64) throws -> UInt64 {
     83     let (next, overflow) = generation.addingReportingOverflow(1)
     84     guard !overflow else {
     85       throw TeraStateTransitionError.generationOverflow
     86     }
     87     return next
     88   }
     89 }