Перейти к содержимому

Время и языки

Эта страница переведена с английского. Читать страницу на английском

Даты и время часто становятся источником ошибок в бизнес-приложениях: день рождения, который сдвигается на сутки, когда пользователь путешествует, смена, которая начинается на час позже после перевода часов. Zeyn даёт каждому виду значений собственный тип, так что компилятор держит их раздельно.

Четыре типа

ТипЧто этоПример
DateКалендарный день без поясаДень рождения, дата счёта
TimeВремя по настенным часам без поясаЧасы работы
DateTimeТочный момент, хранится в UTCКогда была создана запись
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)Одном поясе для всех

.time_zone_picker() добавляет в меню выбор пояса, чтобы пользователи могли выбрать его вручную. Прокси единого входа может также передавать пояс в заголовке (ForwardAuth::time_zone). Фильтры таблиц по столбцам DateTime читаются в поясе пользователя.

Средства выбора

Поля даты, времени и даты со временем получают удобные для клавиатуры средства выбора (pickers). 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))

Формат: 31.12.2024. Можно также ввести: сегодня, +3д.

Формат: 14:30. Можно также ввести: сейчас, 930.

Формат: 31.12.2024 14:30. Можно также ввести: сегодня, +3д.

Формат: 31.12.2024. Можно также ввести: сегодня, +3д.

Выбор языка

Сервер выбирает язык по cookie zeyn_locale, затем по заголовку браузера Accept-Language, затем по языку приложения по умолчанию. <html lang> всегда совпадает с ним. Выбор языка в меню устанавливает cookie и подменяет страницу на месте, без перезагрузки.

С App::locale_paths() язык становится частью URL: язык по умолчанию остаётся на /orders, остальные получают /tr/orders или /ar/orders. Решает только путь, поэтому ссылка, которой поделились, открывается на том же языке, а у каждого экрана по-прежнему один маршрут, и язык читается из ctx.locale(). Ссылки меню сохраняют язык, а переключатель открывает ту же страницу на другом языке без перезагрузки. Арабский и другие языки с письмом справа налево получают <html dir="rtl">. Языки публичной страницы перечисляются для поисковых систем через Screen::alternate("tr", "/tr/orders").

Числа, деньги и даты

Значения форматируются для языка пользователя на сервере: 1.234,56 по-турецки и 1,234.56 по-английски, даты как 29.03.2026 или 3/29/2026, а деньги с символом валюты. Поля ввода читают те же форматы обратно.

Money хранит целые минимальные единицы и валюту, поэтому суммы никогда не теряют ни одной копейки:

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