Skip to content

fix(datetime): consistent timezone view; feat(migrate): auto-load models - #55

Merged
solisoft merged 1 commit into
mainfrom
fix/datetime-tz-view-and-migration-model-preamble
Aug 26, 2026
Merged

fix(datetime): consistent timezone view; feat(migrate): auto-load models#55
solisoft merged 1 commit into
mainfrom
fix/datetime-tz-view-and-migration-model-preamble

Conversation

@solisoft

Copy link
Copy Markdown
Owner

Two independent changes.

DateTime component accessors were inconsistent about timezone: hour and minute returned UTC while year/month/day/second/format/to_string used the local zone, so composing parts of one value could print a wall time that never existed.

Value::DateTime(i64) becomes Value::DateTime(i64, bool), where the bool is a display/component view (false = process-local, true = UTC). Every component accessor now reads the same view. .utc() returns a same-instant value whose components are UTC, .local() switches back; equality, ordering, to_unix and to_iso ignore the flag and compare by instant. Static DateTime.utc() returns "now" in the UTC view, other constructors stay local.

Breaking for code relying on bare .hour()/.minute() being UTC outside a UTC timezone.

soli db:migrate now auto-loads app/models and app/services as an interpreter preamble, so data migrations can call User.create(...) or iterate User.all() without explicit imports. The walk is recursive and top-down (files before subdirectories, alphabetical) so a base class at an equal-or-shallower depth is defined first, matching soli serve. This holds on every backend: the SoliDB and SQL runners are separate code paths, and both load the preamble. Engine migrations load engine models the same way, and db:seed drops its hand-rolled non-recursive copy in favour of the shared helper. The preamble loop itself is now one run_preamble_files used by the test runner, seeds, and both migration runners.

Also documents that Duration.humanize is magnitude-only — it never appends " ago", even for negative Duration.between results; time_ago is the relative-past phrasing.

Two independent changes.

DateTime component accessors were inconsistent about timezone: `hour` and
`minute` returned UTC while `year`/`month`/`day`/`second`/`format`/`to_string`
used the local zone, so composing parts of one value could print a wall time
that never existed.

`Value::DateTime(i64)` becomes `Value::DateTime(i64, bool)`, where the bool is
a display/component view (`false` = process-local, `true` = UTC). Every
component accessor now reads the same view. `.utc()` returns a same-instant
value whose components are UTC, `.local()` switches back; equality, ordering,
`to_unix` and `to_iso` ignore the flag and compare by instant. Static
`DateTime.utc()` returns "now" in the UTC view, other constructors stay local.

Breaking for code relying on bare `.hour()`/`.minute()` being UTC outside a
UTC timezone.

`soli db:migrate` now auto-loads `app/models` and `app/services` as an
interpreter preamble, so data migrations can call `User.create(...)` or
iterate `User.all()` without explicit imports. The walk is recursive and
top-down (files before subdirectories, alphabetical) so a base class at an
equal-or-shallower depth is defined first, matching `soli serve`. This holds
on every backend: the SoliDB and SQL runners are separate code paths, and both
load the preamble. Engine migrations load engine models the same way, and
`db:seed` drops its hand-rolled non-recursive copy in favour of the shared
helper. The preamble loop itself is now one `run_preamble_files` used by the
test runner, seeds, and both migration runners.

Also documents that `Duration.humanize` is magnitude-only — it never appends
" ago", even for negative `Duration.between` results; `time_ago` is the
relative-past phrasing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
solisoft pushed a commit that referenced this pull request Aug 26, 2026
…t 241

Two things that made the fuzz job red for reasons unrelated to the code under
test.

LeakSanitizer. cargo-fuzz builds with AddressSanitizer, which enables LSan too,
and nothing set ASAN_OPTIONS. These targets exist to catch panics, stack
overflows and UB in the surfaces that take untrusted input — not to audit
allocation lifetimes. The interpreter is `Rc<RefCell<…>>`-based, so a closure
capturing its defining environment forms a reference cycle that LSan correctly
calls an unreachable "direct leak"; it is bounded and harmless in a process
that exits. On PR #55 that failed the job *after* a completely clean fuzz —
`Done 72817 runs in 21 second(s)`, then 446 bytes in 36 allocations — so every
PR touching a fuzzed path inherited a red X that had nothing to do with its
diff. Leak detection is now off for the fuzz step.

The backtick guard in `template_parse_render` predates this and was partly
justified by the same LSan behaviour; its other reason — rendering a backtick
tag runs `sh -c <fuzzer bytes>` on the host — stands on its own, so the guard
stays and the comment now says why.

Unwrap ratchet. The regression tests added in the previous commit pushed
`src/template` from 241 to 244 `.unwrap()`/`.expect()` calls and failed
`scripts/lint_unwraps.sh`. The ratchet is meant to move in one direction, so
the tests were rewritten to not need them (`let Ok(…) else`, and comparing
against `Ok("ADA".to_string())`) rather than raising the baseline.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@solisoft
solisoft merged commit bee66a4 into main Aug 26, 2026
14 of 15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant