Time stamps in nanoseconds even in Swift!

Time stamps in nanoseconds are easy.

Yes, in C language. However, the function name is different from clock_gettime () on Linux and clock_get_time () on OS X. The names of structures that represent time in nanoseconds are also different. The procedure for getting a time stamp using that structure is also different. Swift can use C functions, and Swift is now available on both Linux and OS X, so let's absorb this difference with Swift.

wrap up

Tried to make it. Then published on GitHub. It's an MIT license.

Source code

There is only one file. I tried to name the file TimeSpecification.swift. And I named the structure in Swift TimeSpecification. The only properties (stored property) are seconds and nanoseconds. The following is an excerpt and explanation. The order of the explanations and the order of the code in the source do not always match, so don't be afraid ... I haven't done anything difficult. I can't say that.

A spell that absorbs the difference in OS

ʻImportso that you can use the OS-specific standard library.Glibc for Linux, Darwinfor OS X. The nanosecond time is represented bystruct timespec on Linux and mach_timespec_t (typedef struct mach_timespec mach_timespec_t;) on OS X. In both cases, seconds are represented by the member .tv_sec and nanoseconds are represented by the member .tv_nsec. However, .tv_sec is time_tinstruct timespec, but ʻunsigned int [^ tv_sec_unsigned_int] in mach_timespec_t, and .tv_nsec is long and clock_res_t (typedef int clock_res_t;) But the contents are completely different. So, let's name both CTimeSpec once with typealias.

[^ tv_sec_unsigned_int]: 2106 issue is likely to occur

#if os(Linux)
  import Glibc
  private typealias CTimeSpec = timespec
#elseif os(OSX) || os(iOS) || os(watchOS) || os(tvOS)
  import Darwin
  private let mach_task_self:() -> mach_port_t = { return mach_task_self_ }
  private typealias CTimeSpec = mach_timespec_t
#else
  // UNKNOWN OS
#endif

Initializing the Time Specification with the C Time Spec is easy. What you can do because the member names are the same for both struct timespec and mach_timespec_t:

extension TimeSpecification {
  fileprivate init(_ cts:CTimeSpec) {
    self.seconds = Int64(cts.tv_sec)
    self.nanoseconds = Int32(cts.tv_nsec)
  }
}

Now the definition of Time Specification

Considering the usage, let's apply the Comparable protocol. After that, I thought it would be convenient to apply ʻExpressibleByIntegerLiteral and ʻExpressibleByFloatLiteral ... Also, when adding or subtracting, let's normalize nanoseconds so that it is always a non-negative integer so that one time representation is uniquely determined.

public struct TimeSpecification: Comparable,
                                 ExpressibleByIntegerLiteral,
                                 ExpressibleByFloatLiteral {
  public var seconds:Int64 = 0
  public var nanoseconds:Int32 = 0 {
    didSet { self.normalize() }
  }
  
  public init(seconds:Int64, nanoseconds:Int32) {
    self.seconds = seconds
    self.nanoseconds = nanoseconds
    self.normalize()
  }
  
  public mutating func normalize() {
    // `nanoseconds` must be always zero or positive value and less than 1_000_000_000
    if self.nanoseconds >= 1_000_000_000 {
      self.seconds += Int64(self.nanoseconds / 1_000_000_000)
      self.nanoseconds = self.nanoseconds % 1_000_000_000
    } else if self.nanoseconds < 0 {
      // For example,
      //   (seconds:3, nanoseconds:-2_123_456_789)
      //   -> (seconds:0, nanoseconds:876_543_211)
      self.seconds += Int64(self.nanoseconds / 1_000_000_000) - 1
      self.nanoseconds = self.nanoseconds % 1_000_000_000 + 1_000_000_000
    }
  }
}

How to get a time stamp?

I was wondering what the specification should be, but here I defined a ʻenum called Clockand tried to get it using that method. For example, get the system clock withlet now = Clock.System.timeSpecification ()`. In the method, use the C language API as it is. Apparently, it may fail to get it, so I decided to return it as Optional. If you specify the calendar clock on OS X, it seems that you can only get the time in microseconds.

public enum Clock {
  case Calendar
  case System
  
  public func timeSpecification() -> TimeSpecification? {
    var c_timespec:CTimeSpec = CTimeSpec(tv_sec:0, tv_nsec:0)
    
    let clock_id:CInt
    var retval:CInt = -1
    
    #if os(Linux)
      clock_id = (self == .Calendar) ? CLOCK_REALTIME : CLOCK_MONOTONIC
      retval = clock_gettime(clock_id, &c_timespec)
    #elseif os(OSX) || os(iOS) || os(watchOS) || os(tvOS)
      var clock_name: clock_serv_t = 0
      clock_id = (self == .Calendar) ? CALENDAR_CLOCK : SYSTEM_CLOCK
      retval = host_get_clock_service(mach_host_self(), clock_id, &clock_name)
      if retval != 0 { return nil }
      retval = clock_get_time(clock_name, &c_timespec)
      _ = mach_port_deallocate(mach_task_self(), clock_name)
    #endif
    
    return (retval == 0) ? TimeSpecification(c_timespec) : nil
  }
}

bonus

If anything, the Ruby script build-install.rb written to make it easier to build the library. It took a long time. In fact, even though it is a Swift project, Ruby code occupies 40%. Tehe.

Recommended Posts

Time stamps in nanoseconds even in Swift!
Compare objects in Swift
Division becomes 0 in Swift
Multidimensional array in Swift
Photo library in Swift UI
Implement Swift UITextField in code