Хранение времени: RFC 3339 (UTC) + таймзона отображения в конфиге
Метки времени в SQLite переведены с формата datetime('now')
(«2006-01-02 15:04:05») на RFC 3339 всегда-UTC («2006-01-02T15:04:05Z»):
самоописываемое хранилище (зона в значении), валидный ISO 8601, единый
формат с логами. Фиксированная ширина сохраняет лексикографическую
сортировку TEXT = хронологию (COALESCE(source_added_at, created_at)).
- Единая точка генерации времени в Go: store.Now()/FormatTime; DEFAULT
(datetime('now')) снят со всех колонок — время всегда пишет приложение
(зеркально ident.NewID для id), fail-loud при забытой вставке (NOT NULL).
Все INSERT-сайты в store передают created_at/updated_at явно.
- Миграция 0008 (rebuild 7 таблиц без DEFAULT + backfill strftime, FK/PK/
индексы сохранены байт-в-байт по образцу 0006); симметричная down.
- Новая секция конфига [general] с полем timezone (дефолт UTC) — зона
ОТОБРАЖЕНИЯ в веб-UI; хранение остаётся UTC. Жёсткая валидация зоны на
старте; zoneinfo встроен (time/tzdata), заменён зашитый Europe/Moscow.
- Тесты: round-trip миграции (up/down, NULL source_added_at), валидация
зоны, сдвиг даты по зоне; обновлены фикстуры и TestUlidMigration.
- Docs: конвенции database/config, ER-схема; спека web-ui (таймзона).
OpenSpec change time-storage-rfc3339 (заархивирован).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
## Why
|
||||
|
||||
Временные метки в БД хранятся как TEXT в формате `datetime('now')` —
|
||||
`2006-01-02 15:04:05`: пробел вместо `T`, без суффикса зоны. UTC гарантируется
|
||||
только конвенцией и кодом, но не самим значением — тот, кто откроет базу
|
||||
напрямую (sqlite3 CLI, дамп, сторонний инструмент), не видит зону, а строка не
|
||||
является валидным ISO 8601 (footgun для `new Date()` в веб-UI, требует
|
||||
переформатирования для JSON API / `<time datetime>`). Одновременно зона
|
||||
отображения зашита в код (`Europe/Moscow`), хотя серверы часто настроены по UTC.
|
||||
|
||||
## What Changes
|
||||
|
||||
- Формат хранения меток времени переходит на **RFC 3339, всегда UTC с суффиксом
|
||||
`Z`** (`2006-01-02T15:04:05Z`). Секундная точность сохраняется. Фиксированная
|
||||
ширина сохраняет лексикографическую сортировку TEXT = хронологию.
|
||||
- **Единая точка генерации времени в Go** (зеркально принципу «ULID генерит
|
||||
только `internal/ident`»): `DEFAULT (datetime('now'))` **убирается** со всех
|
||||
колонок; метки времени всегда пишет приложение через `store.FormatTime`.
|
||||
- Go-хелперы `FormatTime`/`ParseTime` переводятся на stdlib `time.RFC3339`.
|
||||
- **BREAKING (хранение):** одноразовая goose-миграция пересобирает таблицы без
|
||||
`DEFAULT` и backfill-ит существующие строки в новый формат. Backfill + смена
|
||||
схемы + смена кода — один change, чтобы не возникло окна со смешанными
|
||||
форматами (иначе ломается лексикографическая сортировка `COALESCE`).
|
||||
- Добавляется секция конфига **`[general]`** (общие настройки) с явной настройкой
|
||||
**таймзоны отображения** (`timezone`, дефолт `UTC`), валидируемой на старте через
|
||||
`time.LoadLocation`. Хранение остаётся всегда UTC — настройка влияет только на
|
||||
рендеринг времени в веб-UI, заменяя зашитый `Europe/Moscow`.
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
|
||||
_Нет._ Формат хранения времени — кросс-каттинг **конвенция** (`how we store`),
|
||||
по правилам проекта живёт в `docs/conventions/database.md`, а не в OpenSpec.
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
- `web-ui`: временные метки в интерфейсе SHALL отображаться в таймзоне из
|
||||
конфига (`[general].timezone`, дефолт UTC), а не в жёстко зашитой `Europe/Moscow`.
|
||||
|
||||
## Impact
|
||||
|
||||
- **Код:** `internal/store/download.go` (`sqliteTimeLayout`→`time.RFC3339`,
|
||||
`FormatTime`/`ParseTime`, инлайн `updated_at = datetime('now')`); Create-методы
|
||||
`store` (`download.go`, `recognition.go`) — все INSERT, полагавшиеся на
|
||||
`DEFAULT`, теперь явно передают `created_at`/`updated_at`; новый `store.Now()`
|
||||
как единая тестируемая точка.
|
||||
- **Миграции:** новая goose-миграция — rebuild 7 таблиц без `DEFAULT` + backfill
|
||||
колонок `created_at`/`updated_at`/`source_added_at`.
|
||||
- **Конфиг:** новая секция `[general]` с полем `timezone` в `internal/config`,
|
||||
жёсткая валидация на старте, проброс зоны в `internal/httpapi` (замена `mskLoc`
|
||||
в `live.go`). Встраивание zoneinfo — `import _ "time/tzdata"` в `cmd/jellybit`
|
||||
(устраняет latent silent-UTC на контейнере без tzdata; +~450КБ к бинарю).
|
||||
- **Docs:** `docs/conventions/database.md`, `docs/conventions/config.md`,
|
||||
`docs/specs/database.md` (комментарий формата в ER-схеме).
|
||||
- **Совместимость:** ULID-корреляция и хронология сохраняются (значения меток
|
||||
эквивалентны по моменту); секунды не теряются.
|
||||
Reference in New Issue
Block a user