заведён канон общих конвенций для личных проектов
- 13 конвенций по осям arch / lang / stack / common; репозитории берут оттуда копии в свой docs/conventions/ и коммитят их у себя - conv — синхронизация копий: add / status / diff / pull / push, локальные регионы исключены из сравнения, поэтому расхождение не даёт шума
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
---
|
||||
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`). Она получается сама: layout `time.RFC3339` не
|
||||
содержит долей секунды, поэтому `Format` их не выведет.
|
||||
- `time.RFC3339Nano` не используем: он отбрасывает хвостовые нули и ломает
|
||||
фиксированную ширину.
|
||||
- `time.Parse(time.RFC3339, …)` принимает и доли, и не-`Z` офсеты, то есть
|
||||
канонический вид гарантирует **писатель**, а не читатель. Для одного
|
||||
писателя этого достаточно; чужой вход нормализуем явно.
|
||||
- В драйвер отдаём строку из `FormatTime`, а не `time.Time`: колонка —
|
||||
`TEXT`, и промежуточное преобразование драйвером нам не нужно.
|
||||
|
||||
## Логи
|
||||
|
||||
`slog` по умолчанию **не даёт UTC**: встроенные хендлеры пишут время в зоне
|
||||
самого `time.Time`, то есть в локальной зоне процесса, — на ноутбуке
|
||||
разработчика логи молча поедут в `+03:00`. UTC ставится `ReplaceAttr` по
|
||||
`slog.TimeKey`:
|
||||
|
||||
```go
|
||||
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; календарные вычисления бизнес-логики берут зону
|
||||
явно, как описано в базе.
|
||||
|
||||
<!-- local:механизировано -->
|
||||
<!-- /local -->
|
||||
Reference in New Issue
Block a user