Метки времени в 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>
4.5 KiB
4.5 KiB
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переводятся на stdlibtime.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-корреляция и хронология сохраняются (значения меток эквивалентны по моменту); секунды не теряются.