Ludwig J. Marx

Self-hosted personal finance application.

fix(services): resolve negative monthly schedules in the template time zone

pull request
mayswind/ezbookkeeping#677
language
Go · MIT
diff
1 file, +5 -4
status
merged 2026-09-18

GitHub API, read 2026-09-21 09:10 UTC, at render

Problem

  • A template with a negative monthly frequency value counts back from the end of the month. That value is resolved against the time zone of the server.
  • The day it is checked against comes from the time zone of the template.
  • When the two are in different months at the moment the template is due, the resolved day no longer matches the template day and the transaction is skipped.
  • Nothing is logged as an error. The booking is simply missing.

Reproduction

  • Template in UTC-12, scheduled for the last day of each month, due at 2027-02-28 12:00 UTC. Created through the HTTP API, the job invoked through services.Transactions.CreateScheduledTransactions with that timestamp, the result read back from the database.
server TZserver date at the runbeforeafter
UTC2027-02-28createdcreated
Europe/Berlin2027-02-28createdcreated
Etc/GMT+122027-02-28createdcreated
Pacific/Kiritimati2027-03-01skippedcreated
Etc/GMT-142027-03-01skippedcreated
  • February 2027 has 28 days, so -1 has to resolve to 28. On a server 14 hours ahead it resolved to 31, the length of March, and the check against day 28 failed.
  • After the change all five create the transaction at 2027-02-28 00:00 in UTC-12.

No unit test in this version

  • The first version of this pull request extracted the computation into a function and tested it. Following the review the code is reordered in place instead, so the logic is no longer reachable in isolation.
  • The package has no database-backed tests that could exercise CreateScheduledTransactions as a whole.
  • The evidence is therefore the measurement above and the control run below, both repeated against the reordered version rather than carried over from the earlier one.

Regression check

  • The job was run for 17 template time zones against 6 server time zones, 102 runs in total, each due on the first of the month. All of them create the transaction on the correct day, before and after, so the reordering does not disturb what was already correct.
go test ./... in UTC passes. pkg/models fails in a DST zone, on unmodified b2a3f4ed as well, and is addressed separately in #678. Measured with -count=1, so nothing comes from the test cache.
TZ=UTC                  ok
TZ=Europe/Berlin        FAIL
TZ=America/New_York     FAIL
TZ=Asia/Kolkata         ok
TZ=Pacific/Kiritimati   ok

Why the template zone and not UTC

  • Resolving against the UTC time of the run is equally small and holds for the values the interface offers, which are -1 to -3 via getAvailableMonthDays(28, 3).
  • It does not hold in general: getOrderedFrequencyValues applies no range check, and any integer reaches the database. For a template in UTC+3 due on 2027-05-01 the run is at 2027-04-30 21:00 UTC, so -31 is resolved against April and yields 0 instead of 1.
  • Measured while this branch still carried the unit test: that variant passed the last-day case and failed this one.
  • Taking the time that the day check itself uses removes the question.

Environment

itemvalue
Go1.27.1 darwin/arm64
storageSQLite
branched fromb2a3f4ed
diff1 file, +5 -4