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 TZ | server date at the run | before | after |
|---|---|---|---|
| UTC | 2027-02-28 | created | created |
| Europe/Berlin | 2027-02-28 | created | created |
| Etc/GMT+12 | 2027-02-28 | created | created |
| Pacific/Kiritimati | 2027-03-01 | skipped | created |
| Etc/GMT-14 | 2027-03-01 | skipped | created |
- 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.
TZ=UTC ok
TZ=Europe/Berlin FAIL
TZ=America/New_York FAIL
TZ=Asia/Kolkata ok
TZ=Pacific/Kiritimati okWhy 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
| item | value |
|---|---|
| Go | 1.27.1 darwin/arm64 |
| storage | SQLite |
| branched from | b2a3f4ed |
| diff | 1 file, +5 -4 |