- 13 конвенций по осям arch / lang / stack / common; репозитории берут оттуда копии в свой docs/conventions/ и коммитят их у себя - conv — синхронизация копий: add / status / diff / pull / push, локальные регионы исключены из сравнения, поэтому расхождение не даёт шума
4.1 KiB
status, extends
| status | extends |
|---|---|
| рекомендуемая | arch/time.md |
Время: реализация на Go
Единая точка
- «Сейчас» берём у слоя хранилища —
store.Now(), а неtime.Now()по коду. Ценность точки — гарантированный UTC и один формат:Now()возвращаетtime.Now().UTC(), и ни одна ветка кода не может об этом забыть. Побочно это единственное место, которое придётся превратить в переменную или поле, если однажды понадобится подменять часы в тестах, — но само по себе оно тестируемости не даёт. - Форматирование и разбор —
store.FormatTime/store.ParseTimeповерхtime.RFC3339. - Запрет прямого
time.Now()механизируетсяforbidigo. Исключений ровно два, и оба обязаны быть прописаны, иначе конвенция противоречит сама себе: сама точкаNow()и обёртка измерения длительности (ниже).
Точность и разбор
- В БД — секундная точность, ширина 20 символов
(
2026-06-28T11:23:45Z). Она получается сама: layouttime.RFC3339не содержит долей секунды, поэтомуFormatих не выведет. time.RFC3339Nanoне используем: он отбрасывает хвостовые нули и ломает фиксированную ширину.time.Parse(time.RFC3339, …)принимает и доли, и не-Zофсеты, то есть канонический вид гарантирует писатель, а не читатель. Для одного писателя этого достаточно; чужой вход нормализуем явно.- В драйвер отдаём строку из
FormatTime, а неtime.Time: колонка —TEXT, и промежуточное преобразование драйвером нам не нужно.
Логи
slog по умолчанию не даёт UTC: встроенные хендлеры пишут время в зоне
самого time.Time, то есть в локальной зоне процесса, — на ноутбуке
разработчика логи молча поедут в +03:00. UTC ставится ReplaceAttr по
slog.TimeKey:
func utcTime(_ []string, a slog.Attr) slog.Attr {
if a.Key == slog.TimeKey {
a.Value = slog.TimeValue(a.Value.Time().UTC())
}
return a
}
JSONHandler пишет миллисекунды — три знака, фиксированная ширина. Это
другая точность, чем в БД, и это нормально: ширина фиксируется на носитель
(см. базу).
Длительность
Обёртка измерения — легитимное исключение из запрета time.Now(), и
без него не обойтись: store.Now() приводит время к UTC через .UTC(), а
это срезает монотонную составляющую time.Time. Интервал, посчитанный
по таким меткам, зависит от подводки часов. Поэтому обёртка берёт
time.Now() напрямую и считает time.Since — с локальным //nolint.
Зоны
time/tzdata импортируется в main, зона отображения валидируется
загрузчиком конфига — см. lang/go/config.md. Применяется она только в
шаблонах и форматтерах UI; календарные вычисления бизнес-логики берут зону
явно, как описано в базе.