Windowsのファイル時刻を扱うためのライブラリを作成した話
RustでWindowsのファイル時刻を扱うためのライブラリのnt-time
を開発したので紹介します。
ファイル時刻とは
nt-time
クレートについて説明する前に、ファイル時刻について説明します。
ファイル時刻は、1601年1月1日0時0分0秒 (UTC) から経過した100ナノ秒間隔の数を表す64ビット整数値で、Unix系のシステムにおけるUNIX時間に相当するものです。
ファイル時刻は、NTFSや7zのタイムスタンプとして利用されています[1]。
ファイル時刻は、Win32 APIではGetSystemTime()
を使用することで取得することができ、.NETではDateTime.ToFileTime()
を使用することで取得することができます。
最大値は60056年5月28日5時36分10秒955161500 (UTC) ですが、WindowsのAPIでは最大値は64ビット符号付き整数の最大値に制限されます。
nt-timeとは
nt-time
クレートはこのファイル時刻を扱うためのライブラリです。
ファイル時刻を表す型としてnt_time::FileTime
を定義しています。
この型はNew Type Patternを利用しており、内部的にはu64
です。
FileTime
は以下のような機能を実装しています。
-
core::time::Duration
などとの加算や減算 -
std
やtime
クレートやchrono
クレートの時間を表す型との相互変換 - UNIX時間やMS-DOSの日付と時刻との相互変換
- Serdeを利用したシリアライズとデシリアライズ
コード例
use core::time::Duration;
use nt_time::{
time::{macros::datetime, OffsetDateTime},
FileTime,
};
let ft = FileTime::NT_TIME_EPOCH;
assert_eq!(
OffsetDateTime::try_from(ft).unwrap(),
datetime!(1601-01-01 00:00 UTC)
);
let ft = ft + Duration::from_secs(11_644_473_600);
assert_eq!(
OffsetDateTime::try_from(ft).unwrap(),
OffsetDateTime::UNIX_EPOCH
);
assert_eq!(ft.to_raw(), 116_444_736_000_000_000);
assert_eq!(FileTime::new(u64::MAX), FileTime::MAX);
定数
以下の3つの定数を定義しています。
-
FileTime::NT_TIME_EPOCH
- NT time epoch (1601-01-01 00:00:00 UTC) を表します -
FileTime::UNIX_EPOCH
- Unix epoch (1970-01-01 00:00:00 UTC) を表します -
FileTime::MAX
-FileTime
が表すことのできる最大値 (+60056-05-28 05:36:10.955161500 UTC) を表します
演算
FileTime
はAdd
を実装しているので、core::time::Duration
やtime::Duration
の値を加算することができます。
Sub
ではこれらに加えてstd::time::SystemTime
などの時間を表す型との演算を実装しているので、FileTime
からこれらの型の値を減算することができます。
core::time::Duration
との演算ではオーバーフローした場合に飽和するメソッド (FileTime::saturating_add()
、FileTime::saturating_sub()
) やNone
を返すメソッド (FileTime::checked_add()
、FileTime::checked_sub()
) を定義しています。
また、PartialEq
とPartialOrd
も実装しているので、これらの期間や時間を表す型との同値関係と順序関係を比較することができます。
相互変換
FileTime
はFrom
やTryFrom
によってstd::time::SystemTime
、time::OffsetDateTime
、chrono::DateTime<chrono::offset::Utc>
と相互変換することができます。
これらは変換元の値が変換先の型の範囲外の場合はTryFrom
を使うようになっています。
また、u64
の値からFileTime
を作成すること (FileTime::new()
) とその逆 (FileTime::to_raw()
) にも対応しています。
UNIX時間との相互変換については、秒単位とナノ秒単位に対応しています。
MS-DOSの日付と時刻との相互変換については、exFATなどの10ミリ秒単位の分解能とUTCオフセットに対応しています。
use core::time::Duration;
use nt_time::{
time::{OffsetDateTime, UtcOffset},
FileTime,
};
// `1970-01-01 00:00:00 UTC`.
let ut = i64::default();
assert_eq!(
OffsetDateTime::from_unix_timestamp(ut).unwrap(),
OffsetDateTime::UNIX_EPOCH
);
let ft = FileTime::from_unix_time(ut).unwrap();
assert_eq!(ft, FileTime::UNIX_EPOCH);
// `1980-01-01 00:00:00 UTC`.
let ft = ft + Duration::from_secs(315_532_800);
let dos_dt = ft.to_dos_date_time(Some(UtcOffset::UTC)).unwrap();
assert_eq!(dos_dt, (0x0021, u16::MIN, u8::MIN, Some(UtcOffset::UTC)));
シリアライズとデシリアライズ
Serdeを利用してISO 8601形式、RFC 2822形式、RFC 3339形式、UNIX時間との間でシリアライズとデシリアライズを行うことができます。
use nt_time::{
serde::{Deserialize, Serialize},
serde_with::iso_8601,
FileTime,
};
#[derive(Debug, Deserialize, PartialEq, Serialize)]
struct DateTime(#[serde(with = "iso_8601")] FileTime);
let json = serde_json::to_string(&DateTime(FileTime::UNIX_EPOCH)).unwrap();
assert_eq!(json, r#""+001970-01-01T00:00:00.000000000Z""#);
assert_eq!(
serde_json::from_str::<DateTime>(&json).unwrap(),
DateTime(FileTime::UNIX_EPOCH)
);
終わりに
RustでWindowsのファイル時刻を扱うためのライブラリのnt-time
クレートを紹介しました。
詳細なドキュメントはDocs.rsで確認することができます。
Discussion