本文へ移動
Hikari 仕様

std:time — 日時 (Date / Time / Instant / Duration)

標準ライブラリモジュール (index.md)。本書中の裸の §N は本書の節を指す。言語の意味論は ../language-spec.md を参照。

import t := "std:time" は日時を扱う 4 つの不透明型 (Date / Time / Instant / Duration) のコンストラクターと曜日タグを slot に持つ namespace object を t に束縛する。prelude の now() (epoch ミリ秒 Int) を補完し、暦の構築・分解・整形・期間演算・順序比較を提供する。

std:fs / std:term と違い I/O を伴わず、純粋・同期 (Future を返さない)。すべての関数・メソッドは即値を返す。

UTC 固定であるInstant はタイムゾーンを保持しない絶対 epoch ミリ秒であり、「UTC」は暦フィールドを読む / 文字列化するときの解釈規約にすぎない (§3)。ローカル時刻・IANA 名前ゾーン・DST は将来枠 (§9)。非 UTC 出力は固定オフセットのレンダリングのみ対応する (§4)。

import t := "std:time"

match t.of({ year := 2026, month := 6, day := 19, hour := 5, minute := 30 |}) {
  Ok(inst) => print(inst.to_iso!)  # "2026-06-19T05:30:00Z"
  Err(e) => print(e.message)
}

t.now().add(t.hours(3)).to_iso!             # 3 時間後の Instant を ISO 文字列に

以下では import t := "std:time" で束縛したものとして t.now 等で記す。

1. 不透明型

4 つの型は内部表現を露出しない不透明値型で、いずれも int64 を 1 つ内包する (Hikari は浮動小数を持たない、language-spec.md §7)。生成は §5 のコンストラクター経由のみ。

意味 Inspect()
Date 暦日 (年月日のみ・ゾーン無し) 2026-06-19
Time 時刻帯 (時分秒ミリ・日付/ゾーン無し、0:00:00〜23:59:59.999) 05:30:00 / 05:30:00.250
Instant 絶対時刻の一点 (UTC 解釈) 2026-06-19T05:30:00Z
Duration 期間 (負可) Duration(1h30m0s)

これらの型は値等価 (==) と順序 (< <= > >=language-spec.md §9.2) を持つ。等価は内部表現の一致で判定され、別インスタンスでも同じ時点・同じ期間なら等しい (不透明値型は Equatable で値等価を実装する。language-spec.md §9.1)。順序は各型の compare メソッド経由で導出される。+ / - 演算子はオーバーロードしない — 加減算は add / sub / diff メソッドで行う。単項 - も同じでDurationnegate メソッドを持っていても -dur は書けない (単項 - が引くのは受け手が宣言したスロットだけで、ランタイム層が実装する組込メソッドは引かない。../language-spec.md §8.6)。符号反転は dur.negate! と書く。

4 型は std:time型メンバーとして export され、型注釈に書ける (language-spec.md §13 のモジュール型 export と対称)。import t := "std:time" のもとで x: t.Instant と修飾参照するか、import { Instant, Duration } := "std:time" で名前を取り出して x: Instant と書く。不透明性は保たれ、注釈に書けても内部表現は露出しない (生成は §5 のコンストラクター経由のみ・パターン分解は不可)。

4 型はいずれもCopyable である (../language-spec.md の複製の節)。不透明型だがホストの可変状態を持たない不変な値なので、複製は Int を複製するのと同じ意味を持つ。

2. Instant とタイムゾーンの意味論

Instantゾーンを格納しない。持つのは絶対 epoch ミリ秒だけである。「UTC である」ことは値の属性ではなく、暦フィールドを読む / 文字列化するときの解釈規約である。

  • inst.year! 等の暦フィールドは「UTC で解釈した値」を返す。
  • inst.to_iso! の末尾 Z は UTC 規約でレンダリングした結果であって、値が UTC を覚えているからではない。
  • date.at(time) → Instant が UTC でタイムラインに落とすのも at の規約 (格納ではない)。
  • 等価は epoch ミリ秒の一致のみ (ゾーン差を考えなくてよい)。
  • 閏秒は値に現れない。 持つのが epoch ミリ秒だけなので目盛りとして存在せず、diff の返す Duration は暦上の 2 点間に閏秒が挟まっても増えない。挿入された秒に固有の Instant は無く、of にも second := 60 は渡せない (§5 の範囲検査)。

将来のタイムゾーン対応は Instant を不変のまま、別型 (Instant + zone) を新設して載せる方針 (§9)。

3. 取得方法

import t := "std:time"

返るのは閉じた (closed) namespace object。slot は §5 のコンストラクター Builtin 群と、§6 の曜日タグ 7 個。型メソッド (§7) は受け手の型に対して登録され、inst.year! のように呼ぶ。

4. 固定オフセットのレンダリング

Instant をゾーン無しに保ったまま +09:00 のような表示を行うため、純粋なフォーマット関数として固定オフセット出力を提供する (§7.1 Instant の to_iso_at / format_at)。

  • オフセットは UTC からの分 (Int)。例: JST = 540、IST = 330、UTC−5 = -300
  • 範囲は ±1080 分 (±18:00)。これを超える値を渡すと呼び出し位置で Error (panic 型、language-spec.md §16)。
  • DST・IANA 名前ゾーンは非対応 — オフセットは呼び手が与える。
  • 出力は Instant の絶対値を変えず、表示だけをオフセット適用後の壁掛け時刻で行う。
  • 暦フィールドアクセサー (year! 等) は UTC 固定のまま (オフセット下のフィールド取得は将来枠 §9)。
  • 入力側は対称で、parse_isoZ / ±HH:MM を受理して絶対時刻にまとめる (オフセットは正規化で捨てる)。

5. namespace コンストラクター

生成口のみ自由関数として namespace の slot に置く。型不一致 (object 以外を渡す等) は呼び出し位置で Error (panic 型)、範囲外・解析失敗は ResultErr({kind, message}) (§8)。

名前 効果 戻り 意味
now t.now() Time Instant 現在時刻 (UTC)
today t.today() Time Date 現在の UTC 日付
of t.of(rec) なし Result(Instant) UTC 暦から構築。rec = { year, month, day, hour?, minute?, second?, milli? |} (時刻部省略は 0)
date t.date(rec) なし Result(Date) { year, month, day |}
time_of t.time_of(rec) なし Result(Time) { hour, minute?, second?, milli? |} (分以降省略は 0)
from_epoch_ms t.from_epoch_ms(n) なし Instant epoch ミリ秒 Int から。prelude now() / fs.mtime の橋渡し
millis / seconds / minutes / hours / days t.hours(n) なし Duration 単位 × n の期間。days は 24h 固定
parse_iso t.parse_iso(text) なし Result(Instant) RFC3339。Z / ±HH:MM を受理し絶対時刻へまとめる
parse t.parse(layout, text) なし Result(Instant) strftime 風レイアウトで解析 (§7.5)
parse_date t.parse_date(text) なし Result(Date) ISO YYYY-MM-DD
parse_time t.parse_time(text) なし Result(Time) ISO HH:MM:SS[.SSS]

of / date / time_ofyear / month / day / hour を必須スロット (time_ofhour のみ必須) とし、省略可スロットは 0 を既定とする。必須スロットの欠落・非 Int や暦として範囲外の値は Err (§8)。Result 値は match で分岐するか、値が要るだけなら t.of(...).unwrap! でよい。

6. 曜日 (Variant enum)

曜日は Monday..Sunday の 7 個の Variant 値として namespace に export される (t.Monday 等)。Instant.weekday! / Date.weekday! はこれらのいずれかを返す。

inst := t.of({ year := 2026, month := 6, day := 19 |}).unwrap!   # 金曜

inst.weekday! == t.Friday          #> true

label := match inst.weekday! {
  t.Saturday => "weekend"
  t.Sunday => "weekend"
  _ => "weekday"
}                                    # "weekday"

match の値パターンには曜日タグ (t.Saturday 等) を置き、== で照合する。

7. メソッド

無引数メソッドは postfix ! で呼ぶ (v!v()、language-spec.md)。引数の型不一致は呼び出し位置で Error (panic 型)。

7.1 Instant

メソッド 効果 戻り 意味
year / month / day / hour / minute / second / millisecond / day_of_year inst.year! なし Int UTC 解釈の暦フィールド (month 1-12、day 1-31、day_of_year 1-366)
weekday inst.weekday! なし Variant Monday..Sunday (§6)
epoch_ms inst.epoch_ms! なし Int epoch ミリ秒
date inst.date! なし Date 日付部 (UTC)
time inst.time! なし Time 時刻部 (UTC)
add inst.add(dur) なし Instant dur (Duration) を加算
sub inst.sub(dur) なし Instant dur (Duration) を減算
diff inst.diff(other) なし Duration self − other
add_days inst.add_days(n) なし Instant n 日加算 (Int)
add_months inst.add_months(n) なし Instant n ヶ月加算。日は対象月末にクランプ (例 1/31 +1 → 2/28)
add_years inst.add_years(n) なし Instant n 年加算 (月末クランプ)
to_iso inst.to_iso! なし String RFC3339・UTC・末尾 Z。ミリ秒は非 0 のときのみ .SSS
format inst.format(layout) なし String strftime 風整形 (UTC、§7.5)
to_iso_at inst.to_iso_at(offset_min) なし String RFC3339・末尾 ±HH:MM (§4)
format_at inst.format_at(layout, offset_min) なし String オフセット適用後の壁掛け時刻で整形 (§4)
compare inst.compare(other) なし Ordering epoch ミリ秒の全順序

7.2 Date

メソッド 効果 戻り 意味
year / month / day / day_of_year date.year! なし Int 暦フィールド
weekday date.weekday! なし Variant Monday..Sunday
add_days / add_months / add_years date.add_days(n) なし Date 暦演算 (月末クランプ)
at date.at(time) なし Instant time (Time) と結合し UTC でタイムラインに落とす
to_iso date.to_iso! なし String YYYY-MM-DD
format date.format(layout) なし String strftime 風整形
compare date.compare(other) なし Ordering 日付の全順序

7.3 Time

メソッド 効果 戻り 意味
hour / minute / second / millisecond tm.hour! なし Int 時刻フィールド
to_iso tm.to_iso! なし String HH:MM:SS[.SSS]
format tm.format(layout) なし String strftime 風整形 (時刻ディレクティブ前提)
compare tm.compare(other) なし Ordering 時刻の全順序

7.4 Duration

メソッド 効果 戻り 意味
as_millis / as_seconds / as_minutes / as_hours / as_days dur.as_seconds! なし Int 総量 (0 方向 truncate)
add / sub dur.add(o) / dur.sub(o) なし Duration 期間同士の加減算
negate dur.negate! なし Duration 符号反転
compare dur.compare(other) なし Ordering 期間の全順序

7.5 strftime ディレクティブ

format / format_at / parse が解釈するディレクティブ:

ディレクティブ 意味
%Y 年 (4 桁)
%m 月 (01-12)
%d 日 (01-31)
%H 時 (00-23)
%M 分 (00-59)
%S 秒 (00-59)
%L ミリ秒 (000-999)
%j 年内通日 (001-366)
%a / %A 曜日 (略 / 完全・英語)
%b / %B 月名 (略 / 完全・英語)
%p AM / PM
%z オフセット (±HHMM)。format は UTC で +0000format_at は与えたオフセット
%% リテラル %
  • format / format_at未知ディレクティブ %x はリテラル通過 (% と当該文字をそのまま出力)。整形は total で必ず String を返す。
  • parse%Y %m %d %H %M %S %L %z%%・リテラルを解釈する。%z を含む parse はオフセットを読んで絶対時刻にまとめる。不一致は Err({kind := "parse_error" …}) (§8)。

8. エラーモデル

回復可能な失敗は ResultErr(e)e{kind, message} (language-spec.md §16)。std:time が返す正規 kind (閉じた一覧):

kind 関数 意味
invalid_date of / date 年月日が暦として範囲外、または必須スロットの欠落・非 Int
invalid_time time_of 時分秒ミリが範囲外、または必須スロットの欠落・非 Int
parse_error parse_iso / parse / parse_date / parse_time テキストがレイアウトに一致しない
match t.of(rec) {
  Ok(inst) => use(inst)
  Err(e) => match e.kind {
    "invalid_date" => print("out of range date")
    _ => print(e.message)
  }
}

messageof: ... のように関数名を含む人間可読文字列。分岐は kind で行い、message の文字列マッチに依存しないこと。一方、型不一致 (object 以外を of に渡す、Duration でない値を Instant.add に渡す、to_iso_at のオフセットが範囲外、等) は回復可能エラーではなく、呼び出し位置で Error (panic 型、language-spec.md §16) になる。暦フィールド・epoch ミリ秒・Duration の量など処理系側の数値として保持する引数の受理範囲は実装定義 (64 bit 符号付き整数) であり、超過はいずれの場合もエラーになる (of 系は Errfrom_epoch_ms/add_days 等は Error)。

9. 将来枠

次は扱わない。いずれも実使用を見るまで入れない (YAGNI) 判断である。

  • ローカル時刻・IANA 名前ゾーン・DST (Instant を不変のまま、ゾーン付き別型を新設して対応)。
  • 暦フィールドのオフセット下取得 (今は文字列レンダリングのみ)。
  • 暦期間の値型 (add_months / add_years メソッドで代替)。