Swiftでもナノ秒単位のタイムスタンプ！

# ナノ秒単位のタイムスタンプなんて簡単。
　そう、C言語ならね。　
　ただ、Linuxでは`clock_gettime()`、OS Xでは`clock_get_time()`と関数名が違う。ナノ秒単位の時間を表す構造体の名前も違う。その構造体を使ってタイムスタンプをゲットする手順も違う。
　SwiftはC言語の関数を使うことができるし、せっかくSwiftがLinuxとOS X双方で使えるようになったということもあるし、この違いをSwiftで吸収してみよう。

# 要約
作ってみた。
そして[GitHubで公開](https://github.com/YOCKOW/SwiftTimeSpecification)した。
MITライセンスだよ。

# ソースコード

ファイルは一つ。ファイル名は[TimeSpecification.swift](https://github.com/YOCKOW/SwiftTimeSpecification/blob/master/Sources/Library/TimeSpecification.swift)にしてみた。
そして、Swiftでの構造体の名前は`TimeSpecification`にしてみた。プロパティ(stored property)は`seconds`と`nanoseconds`のみ。
以下、抜粋して説明。説明の順番とソース内のコードの順番は必ずしも一致しないので悪しからず…。
難しいことは何一つしていない。というかできない。

## OSの違いを吸収する呪文

　OS固有の標準ライブラリを使えるように`import`する。Linuxなら`Glibc`、OS Xなら`Darwin`。
　ナノ秒単位の時間を表すのはLinuxでは`struct timespec`で、OS Xでは`mach_timespec_t`(`typedef struct mach_timespec mach_timespec_t;`)。どちらも秒は`.tv_sec`でナノ秒は`.tv_nsec`というメンバで表現される。しかし、`.tv_sec`は`struct timespec`では`time_t`だけど、`mach_timespec_t`では`unsigned int`[^tv_sec_unsigned_int]だし、`.tv_nsec`はそれぞれ`long`、`clock_res_t`(`typedef int clock_res_t;`)だし、中身は全然違う。
　ということで、一旦`typealias`でどちらも`CTimeSpec`という名前にしちゃってみる。

[^tv_sec_unsigned_int]: [2106年問題](http://www.wdic.org/w/TECH/2106%E5%B9%B4%E5%95%8F%E9%A1%8C)が発生しそうだ

```swift
#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
```

　`CTimeSpec`による`TimeSpecification`の初期化は簡単。`struct timespec`でも`mach_timespec_t`でも、メンバの名前が一緒だからできること：

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

## いざTimeSpecificationの定義

　使い道を考えると`Comparable`プロトコルを適用しておこう。あとはついでに`ExpressibleByIntegerLiteral `と`ExpressibleByFloatLiteral`も適用しておけば便利かなと思って…。
　あと、足し算や引き算をしたとき、一つの時間表現は一意に定まるように`nanoseconds`は常に非負整数であるよう正規化するようにしよう。

```swift
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
    }
  }
}
```

## どうやってタイムスタンプをゲットするか？

　どういう仕様にするか迷ったけど、ここでは`Clock`という`enum`を定義して、そのメソッドを使って取得するようにしてみた。たとえば、`let now = Clock.System.timeSpecification()`でシステムクロックを取得。
　メソッドの中ではC言語のAPIをそのまま使う。どうやら取得には失敗することもあるようなので、一応、Optionalで返すようにした。
　なお、OS Xでカレンダークロックを指定するとマイクロ秒単位でしか時間を取得できないみたい。

```swift
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
  }
}
```

# おまけ

　どっちかというと、ライブラリを簡単にビルドできるように書いたRubyのスクリプト[build-install.rb](https://github.com/YOCKOW/SwiftTimeSpecification/blob/master/build-install.rb)のほうに時間がかかった。実際、SwiftのプロジェクトなのにRubyのコードが4割を占めている。てへっ。