Timezone-Aware Financial Data Handling#
Sure stores a timezone preference at the family level (a nullable string column added in migration 20241207002408) and intends to apply it when converting banking provider timestamps to calendar dates. The goal is to prevent off-by-one day errors for users in UTC+ timezones: when a server running in UTC receives a Unix timestamp for a transaction that occurred at (say) 12:30 AM in a UTC+1 timezone, the naive .to_date conversion resolves to the previous calendar day.
Status (as of 2026-07-11): The timezone field is stored and user-configurable, but the timezone-aware conversion is not yet implemented in provider processors. The bug is tracked in issue #2668.
Family Timezone Configuration#
| Concern | Detail |
|---|---|
| Schema | families.timezone — nullable string, no DB default |
| Set via | Settings → Preferences; permitted as a nested family attribute in UsersController#update |
| Helper | timezone_options dropdown backed by ActiveSupport::TimeZone.all, formatted as (UTC±HH:MM) Name |
| Fallback | Current.family&.timezone.presence || "UTC" used wherever a timezone is required (e.g., in Settings::HostingsController#current_user_timezone) |
The Bug: Timezone-Unaware Date Conversion#
All three banking provider processors share the same date helper that converts provider-supplied values to a Ruby Date. The Unix-timestamp branch is the source of the off-by-one day issue:
| Provider | Processor file | Affected line |
|---|---|---|
| Akahu (NZ) | app/models/akahu_entry/processor.rb | Time.at(value).to_date |
| EnableBanking (EU/PSD2) | app/models/enable_banking_entry/processor.rb | Time.at(date_value).to_date |
| Lunchflow | app/models/lunchflow_entry/processor.rb | Time.at(data[:date]).to_date |
Time.at(timestamp) uses the server's system timezone (UTC). A transaction timestamped at 12:30 AM BST (UTC+1) equals 11:30 PM UTC the prior day, so .to_date returns the wrong date. The same logic flaw exists for any UTC+ offset—New Zealand users (UTC+12/+13 via Akahu) are proportionally more affected since the vulnerable window spans 12–13 hours rather than 1–2.
This bug was confirmed in issue #2668 for a UK user with timezone = Europe/London.
The Fix#
Replace the bare .to_date call with an in_time_zone conversion using the family's stored preference:
# Before (timezone-unaware)
Time.at(data[:date]).to_date
# After (timezone-aware)
Time.at(data[:date]).in_time_zone(Current.family.timezone).to_date
This pattern is already established in the codebase from earlier fixes to price syncing and exchange-rate handling. The fix must be applied consistently to all three processor files listed above.
Note that the string and Date/DateTime branches of each processor's date method do not use Unix timestamps and are less susceptible — EnableBanking typically returns booking_date as an ISO-8601 string , which Date.parse interprets without timezone ambiguity.
Workaround (Until Fixed)#
Manually edit the date on affected transactions. Only transactions timestamped between midnight and the UTC offset hour in the user's local timezone are affected (e.g., midnight–1 AM for BST/UTC+1, midnight–13:00 for NZDT/UTC+13).
Related Files#
- Family timezone migration —
db/migrate/20241207002408_add_family_timezone.rb - Akahu entry processor —
app/models/akahu_entry/processor.rb - EnableBanking entry processor —
app/models/enable_banking_entry/processor.rb - Lunchflow entry processor —
app/models/lunchflow_entry/processor.rb - Hostings controller timezone fallback —
app/controllers/settings/hostings_controller.rb - Issue #2668 — "Transactions posting a day early"