Clock Epochs
01 何が問題だったのか
SE-0329 で導入された ContinuousClock と SuspendingClock は、now を使ってその時点の Instant を得られますが、Instant 単体は「時計の内部的な基準点からの経過時間」を表す不透明な値です。基準点(エポック)そのものにアクセスする手段がなく、.now が時計の内部で何を基準に測られているのかをプログラム上から知ることができませんでした。
Clock プロトコルがエポックを要求していないのは妥当な設計です。時計の種類によっては、意味のある基準点を持たないものもあるためです。しかし ContinuousClock や SuspendingClock は、実装上は OS が提供する基準点(多くのプラットフォームではブート時刻)からの経過で時間を測っています。この基準点はシステム内では一貫しているため、取り出せれば次のような情報が得られるはずでした。
ContinuousClockでは「システムの uptime」(ブートからの総経過時間)SuspendingClockでは「システムの active time」(スリープ時間を除く、実際に動いていた時間)
加えて、同一システム上で動く別のプロセスや別の言語で書かれたコードでも、同じ OS の facility を使っていれば同じ基準点を共有します。そのため、エポックからの Duration を介せば、プロセス間で時間を比較できる可能性もあります。
ところが SE-0329 の時点ではエポックを表すプロパティが存在せず、こうした用途には別途 mach_continuous_time や clock_gettime などのプラットフォーム固有 API を直接呼ぶ必要がありました。
02 どのように解決されるのか
ContinuousClock と SuspendingClock それぞれに、システムのエポックを表す systemEpoch プロパティを追加します。これは当該時計の「ゼロ点」に相当する Instant で、.now との差を取ることでエポックからの経過時間が得られます。
extension ContinuousClock {
public var systemEpoch: Instant { get }
}
extension SuspendingClock {
public var systemEpoch: Instant { get }
}
Apple プラットフォーム、Linux、Windows など主要なプラットフォームでは、これらの時計のシステムエポックはブート時に設定されます。そのため、エポックからの経過を測ることで uptime や active time を取得できます。
let clock = ContinuousClock()
let uptime = clock.now - clock.systemEpoch
let clock = SuspendingClock()
let activeTime = clock.now - clock.systemEpoch
ContinuousClock はスリープ中も時間を刻み続けるため、now - systemEpoch はブートから現在までの総経過時間、すなわち uptime を表します。一方 SuspendingClock はシステムがスリープしている間は進まないので、now - systemEpoch は実際にシステムが動いていた時間、すなわち active time を表します。
プラットフォーム依存であることに注意
systemEpoch の値はシステム固有で、同じシステム内では一貫していますが、システムをまたいで同じ意味を持つとは限りません。名前に system が付いているのはこのことを示すためで、ファイルやネットワーク越しにシリアライズして別マシンで突き合わせる用途には使えません。また、プラットフォームによっては uptime の概念がうまく当てはまらなかったり、プライバシー上の理由でエポックをプログラムから観測できる形では提供しない選択をしたりすることもあり得ます。そのため「ブート時刻をエポックとする」挙動はあくまで多くの一般的なプラットフォームで成り立つ性質であり、全プラットフォームで保証されるものではありません。
同一システム内では一貫しているという性質は、同じ OS の facility を使っている別プロセスや別言語のコードとの間で、エポックからの Duration を介して時間的基準を揃えるといった用途に活用できます。