diff --git a/conventions/arch/time.md b/conventions/arch/time.md index 349593f..a23c189 100644 --- a/conventions/arch/time.md +++ b/conventions/arch/time.md @@ -60,6 +60,21 @@ разберёт час перехода на зимнее время: этот час идёт дважды, две записи получают одинаковую метку, и порядок между ними не восстанавливается ничем. +### R13. Чужой вход нормализуется при разборе, а не отклоняется + +**ДОЛЖЕН.** Валидное по RFC 3339 значение с офсетом, отличным от `Z`, или с +долями секунды принимается от внешней системы и приводится к каноническому +виду (R1) в точке разбора (R5). + +**Почему.** Канонический вид — обязательство нашего писателя, а не +контракт, наложенный на внешние системы: `…14:23:45+03:00` называет тот же +момент, что `…11:23:45Z`, и отклонять его — значит ломать интеграцию со +стороной, которая стандарт соблюла. Пропущенное же как есть, такое значение +нарушает форму и ширину носителя (R1, R2) и портит сортировку выборочно — +только на записях, пришедших извне, и далеко от места разбора. Нормализация +в единой точке разбора оставляет ровно одно место, где неканонический вид +существует, — по ту сторону границы его уже нет. + ### R5. Единая точка получения «сейчас», форматирования и разбора **ДОЛЖЕН.** Один модуль отдаёт текущий момент, он же форматирует и разбирает diff --git a/conventions/lang/go/time.md b/conventions/lang/go/time.md index 0c5ccc7..63e607e 100644 --- a/conventions/lang/go/time.md +++ b/conventions/lang/go/time.md @@ -53,6 +53,22 @@ layout — а расхождение проявится не на записи, «починены» тем, кто увидит в них дефект, и конвенция начнёт противоречить сама себе. +### R13. Исключение регистрируется директивой на месте вызова, а не в конфиге линтера + +**ДОЛЖЕН.** Исключение из R3 оформляется как `//nolint:forbidigo // <причина>` +на строке вызова; exclude-записи в конфигурации линтера для него не +заводятся. + +**Почему.** Запись в конфиге — второй реестр тех же двух мест: она адресует +исключение путём к файлу, отвязывается при переносе кода и продолжает +разрешать `time.Now()` там, где исключения уже нет, — молча. Директива +переезжает вместе с кодом, и grep по `nolint:forbidigo` даёт весь список — +та исчерпываемость, которой требует R3, проверяется одной командой. Голый +`//nolint` без имени правила глушит на строке все проверки сразу, а без +причины неотличим от заглушенного дефекта; обе деградации штатно ловит +`nolintlint` (`require-specific`, `require-explanation`) — стандартный +способ дисциплинировать директивы в golangci-lint. + ### R4. В БД время хранится с секундной точностью, ширина 20 символов **ДОЛЖЕН.** Каноническое значение в колонке — `2026-06-28T11:23:45Z`. @@ -86,7 +102,9 @@ layout — а расхождение проявится не на записи, офсеты, отличные от `Z`, — разбор шире вывода. Канонический вид гарантирует **писатель**, а не читатель; пока писатель один, этого достаточно, но значение из чужой системы, положенное в базу как пришло, нарушает R4 и -обнаруживается не на записи, а на первой сортировке. +обнаруживается не на записи, а на первой сортировке. Само решение +«нормализовать, а не отклонять» — базовое (`arch/time.md` R13); здесь — +Go-механика, из-за которой его легко нарушить незаметно. ### R7. В драйвер передаётся строка, а не `time.Time` @@ -166,6 +184,6 @@ func utcTime(_ []string, a slog.Attr) slog.Attr { ## Связано -- `arch/time.md` — базовая конвенция: UTC как формат хранения, явная зона в - календарных вычислениях. +- `arch/time.md` — базовая конвенция: UTC как формат хранения, нормализация + чужого входа, явная зона в календарных вычислениях. - `lang/go/config.md` — валидация зоны отображения загрузчиком конфига.