本文へスキップ

時刻と言語

このページは英語から翻訳されています。 英語のページを読む

日付と時刻は、業務アプリでバグの原因になりやすいものです。ユーザーが移動すると 1 日ずれる誕生日、時計の切り替えの後に 1 時間遅れて始まるシフトなどです。Zeyn は種類ごとに専用の型を用意しているため、コンパイラがそれらを区別します。

4 つの型

型意味例
Dateゾーンのない暦の日誕生日、請求書の日付
Timeゾーンのない壁時計の時刻営業時間
DateTimeUTC で保存される正確な瞬間レコードが作成された時刻
LocalDateTimeある場所での壁時計の日付と時刻イスタンブールの 14:00 の予約

暦の日を瞬間として保存してはいけません。Date はどのゾーンでも同じ日です。DateTime はどこでも同じ瞬間で、ゾーンごとに異なる壁時計の時刻で表示されます。

let day = Date::new(2026, 3, 29).unwrap_or_default();
let at = Time::new(9, 30, 0).unwrap_or_default();
let slot = LocalDateTime::new(day, at);
let zone = TimeZone::named("Europe/Istanbul").unwrap_or(TimeZone::UTC);
let moment = DateTime::from_local(slot, zone)?;
let back = moment.local(zone);

DateTime::from_local は、春の時計の切り替え時のように、そのゾーンに存在しない壁時計の時刻に対しては、推測せずに失敗します。

クロック

システムクロックではなく、ctx.now() と ctx.today() を使ってください。ctx.today() は、ユーザーのゾーンでの今日です。テストでは App::fixed_now(moment) により、すべてのリクエストでクロックを固定します。

タイムゾーン

App::new(t!("app.name"))
    .time_policy(TimePolicy::User { default: TimeZone::named("Europe/Istanbul").unwrap_or(TimeZone::UTC) })
    .time_zone_picker()
ポリシー瞬間が表示されるゾーン
TimePolicy::User { default }ユーザーのブラウザが報告するゾーン。報告があるまでは default
TimePolicy::Organization(zone)全員に共通の 1 つのゾーン

.time_zone_picker() はメニューにピッカーを追加し、ユーザーが手動でゾーンを選べるようにします。シングルサインオンのプロキシが、ヘッダー(ForwardAuth::time_zone)でゾーンを送ることもできます。DateTime 列に対するグリッドのフィルターは、ユーザーのゾーンで解釈されます。

ピッカー

日付、時刻、日時のフィールドには、キーボード操作に適したピッカーが付きます。DateOptions は、選べる範囲を制限します。

Form::of(visit)
    .row(|r| r.field(Visit::day).field(Visit::arrives))
    .range(Visit::stay_from, Visit::stay_to)
    .date_options(Visit::day, DateOptions::new().no_weekends().week_numbers())
    .date_options(Visit::arrives, DateOptions::new().step_minutes(15))

形式: 2024-12-31。 次のような入力もできます: 今日, +3d。

形式: 14:30。 次のような入力もできます: 今, 930。

形式: 2024-12-31 14:30。 次のような入力もできます: 今日, +3d。

形式: 2024-12-31。 次のような入力もできます: 今日, +3d。

言語の選択

サーバーは、zeyn_locale Cookie、次にブラウザの Accept-Language、最後にアプリの既定値から言語を選びます。<html lang> は常にそれに一致します。メニューの言語ピッカーは Cookie を設定し、再読み込みせずにその場でページを差し替えます。

App::locale_paths() を使うと、言語は URL の一部になります。既定の言語は /orders のままで、ほかの言語は /tr/orders や /ar/orders になります。パスだけで言語が決まるため、共有したリンクは同じ言語で開きます。各画面のルートは 1 つのままで、言語は ctx.locale() で読みます。メニューのリンクは言語を保ち、言語の切り替えは同じページを再読み込みなしで別の言語で開きます。アラビア語など右から左に書く言語には <html dir="rtl"> が付きます。公開ページの言語は Screen::alternate("tr", "/tr/orders") で検索エンジンに伝えます。

数値、金額、日付

値は、サーバー上でユーザーの言語向けに書式化されます。トルコ語では 1.234,56、英語では 1,234.56、日付は 29.03.2026 または 3/29/2026、金額は通貨記号付きです。入力欄も、同じ書式を読み戻します。

Money は整数の最小単位と通貨を保持するため、合計で 1 セントも失われることはありません。

let price = Money::tl(2_500_000);
let text = price.format("tr");