LoginSignup
13
14

More than 5 years have passed since last update.

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

Last updated at Posted at 2016-06-01

ナノ秒単位のタイムスタンプなんて簡単。

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

要約

作ってみた。
そしてGitHubで公開した。
MITライセンスだよ。

ソースコード

ファイルは一つ。ファイル名はTimeSpecification.swiftにしてみた。
そして、Swiftでの構造体の名前はTimeSpecificationにしてみた。プロパティ(stored property)はsecondsnanosecondsのみ。
以下、抜粋して説明。説明の順番とソース内のコードの順番は必ずしも一致しないので悪しからず…。
難しいことは何一つしていない。というかできない。

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_secstruct timespecではtime_tだけど、mach_timespec_tではunsigned int1だし、.tv_nsecはそれぞれlongclock_res_t(typedef int clock_res_t;)だし、中身は全然違う。
 ということで、一旦typealiasでどちらもCTimeSpecという名前にしちゃってみる。

#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でも、メンバの名前が一緒だからできること:

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

いざTimeSpecificationの定義

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

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でカレンダークロックを指定するとマイクロ秒単位でしか時間を取得できないみたい。

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のほうに時間がかかった。実際、SwiftのプロジェクトなのにRubyのコードが4割を占めている。てへっ。


  1. 2106年問題が発生しそうだ 

13
14
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
14