uisce stores and computes in UTC, which is right. But it also cuts its months and days at UTC midnight and prints UTC calendar dates. From late March to late October, Irish time is UTC+1, so anything that happens between midnight and 01:00 Irish time lands on the previous day, and on the last day of a month, in the previous month.
esb had the same bug and fixed it in baz8080/esb#51. lifts fixed it on 2026-08-18. The rule both now follow: store and compute in UTC; bucket and display in Europe/Dublin.
Line numbers are at a10bf29.
Where uisce uses UTC
Buckets
src/uisce/site.py:404: parse_dt turns every instant into UTC, which is correct for storage.
site.py:408-412: month_bounds builds datetime(year, month, 1, tzinfo=timezone.utc). Every per-month figure uses it: counts, person-hours, availability, the grade and the top ten.
site.py:2228-2235: day cells step from that UTC midnight. The comment says so on purpose: "both sides read UTC dates, so they agree on the boundary". This is the decision to revisit.
site.py:2314: current = now.strftime("%Y-%m") is the UTC month.
Displayed dates, all strftime("%Y-%m-%d") on a UTC datetime:
since at site.py:1148 and 1159;
- history
start at 1421 and end at 1432;
start at 2129.
Mixed zones in one field: closed_on (site.py:195-205) returns a Dublin date (end_local_date) for an observed completion, but a UTC date for a lift or a closed_at.
JS: site.html:326 const today = new Date().toISOString().slice(0, 10) is the UTC date. It decides "still to come" and "so far".
Tests that pin UTC midnight: tests/test_site.py:341-343 (month_bounds), 2014 ("1 May 00:00 to 2 May 00:00") and 2021 ("midnight on the 10th").
A worked example
A notice published at 00:30 Irish time on 1 July 2026 is 23:30 UTC on 30 June:
- the reader sees "since Tue 30 Jun" and "from Tue 30 Jun";
- it is filed under June in the area history, and counts in June's median and June's person-hours;
- it colours the 30 June cell;
- the Atom feed says "published 2026-06-30".
Already right
- Notice ends are Irish wall-clock and are converted properly (
build.py:38-61, reported_end_utc).
- Recurring windows are expanded in Dublin.
Proposed fix
The same shape as esb#51. See esb's notes/grading.md § Months and days are Dublin's (2026-09-24) and lifts' notes/site.md § Displayed instants are Dublin wall-clock, and so are the day buckets.
- Months and days are cut at Dublin midnight, held as UTC instants. Don't keep Dublin-zoned datetimes and subtract them: two datetimes that share a zone subtract by wall clock and lose the hour at a clock change.
- The cells per month come from
calendar.monthrange, never from subtracting the bounds, because a Dublin March is 23 hours short and October is 25 hours long.
- Every printed date is converted to Dublin:
since, start, end, closed, the Atom summary and the JS today.
closed_on returns one zone.
- The tests that pin UTC midnight move to Dublin midnight, plus a new case for the last day of a summer month.
uisce stores and computes in UTC, which is right. But it also cuts its months and days at UTC midnight and prints UTC calendar dates. From late March to late October, Irish time is UTC+1, so anything that happens between midnight and 01:00 Irish time lands on the previous day, and on the last day of a month, in the previous month.
esb had the same bug and fixed it in baz8080/esb#51. lifts fixed it on 2026-08-18. The rule both now follow: store and compute in UTC; bucket and display in Europe/Dublin.
Line numbers are at
a10bf29.Where uisce uses UTC
Buckets
src/uisce/site.py:404:parse_dtturns every instant into UTC, which is correct for storage.site.py:408-412:month_boundsbuildsdatetime(year, month, 1, tzinfo=timezone.utc). Every per-month figure uses it: counts, person-hours, availability, the grade and the top ten.site.py:2228-2235: day cells step from that UTC midnight. The comment says so on purpose: "both sides read UTC dates, so they agree on the boundary". This is the decision to revisit.site.py:2314:current = now.strftime("%Y-%m")is the UTC month.Displayed dates, all
strftime("%Y-%m-%d")on a UTC datetime:sinceatsite.py:1148and 1159;startat 1421 andendat 1432;startat 2129.Mixed zones in one field:
closed_on(site.py:195-205) returns a Dublin date (end_local_date) for an observed completion, but a UTC date for a lift or aclosed_at.JS:
site.html:326const today = new Date().toISOString().slice(0, 10)is the UTC date. It decides "still to come" and "so far".Tests that pin UTC midnight:
tests/test_site.py:341-343(month_bounds), 2014 ("1 May 00:00 to 2 May 00:00") and 2021 ("midnight on the 10th").A worked example
A notice published at 00:30 Irish time on 1 July 2026 is 23:30 UTC on 30 June:
Already right
build.py:38-61,reported_end_utc).Proposed fix
The same shape as esb#51. See esb's
notes/grading.md§ Months and days are Dublin's (2026-09-24) and lifts'notes/site.md§ Displayed instants are Dublin wall-clock, and so are the day buckets.calendar.monthrange, never from subtracting the bounds, because a Dublin March is 23 hours short and October is 25 hours long.since,start,end,closed, the Atom summary and the JStoday.closed_onreturns one zone.