Skip to content

Commit 897a49f

Browse files
test: verify arrow ZZZ token correctly distinguishes CST and CDT
Add 9 unit tests confirming arrow ZZZ format token produces the correct timezone abbreviation (CST vs CDT) for America/Chicago across standard time, daylight saving time, and DST transition boundaries. No code change needed -- arrow handles this correctly. Filed TASK-020 for the separate issue of stale module-level time computation in app/main.py. Closes TASK-015 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent c83de20 commit 897a49f

File tree

3 files changed

+151
-21
lines changed

3 files changed

+151
-21
lines changed

backlog/tasks/task-015 - Fix-timezone-display-to-distinguish-CST-and-CDT.md

Lines changed: 60 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
---
22
id: TASK-015
33
title: Fix timezone display to distinguish CST and CDT
4-
status: To Do
4+
status: Done
55
assignee: []
66
created_date: '2026-02-27 00:54'
7-
updated_date: '2026-03-17 02:09'
7+
updated_date: '2026-03-21 01:00'
88
labels:
99
- bug
1010
dependencies: []
@@ -22,7 +22,62 @@ The /api/check-schedule endpoint formats times with ZZZ (e.g. "Monday 14:00 CST"
2222

2323
## Acceptance Criteria
2424
<!-- AC:BEGIN -->
25-
- [ ] #1 Verify arrow ZZZ output is correct for both CST and CDT with America/Chicago tz
26-
- [ ] #2 Add tests covering schedule time display in both standard and daylight saving periods
27-
- [ ] #3 Document findings if no code change is needed
25+
- [x] #1 Verify arrow ZZZ output is correct for both CST and CDT with America/Chicago tz
26+
- [x] #2 Add tests covering schedule time display in both standard and daylight saving periods
27+
- [x] #3 Document findings if no code change is needed
2828
<!-- AC:END -->
29+
30+
## Implementation Plan
31+
32+
<!-- SECTION:PLAN:BEGIN -->
33+
## Findings
34+
35+
Arrow's `ZZZ` token correctly outputs "CST" during standard time and "CDT" during daylight saving time when the Arrow datetime has `America/Chicago` timezone. Verified empirically:
36+
- January 2026: "CST"
37+
- June 2026: "CDT"
38+
- March 8 (DST spring-forward): "CDT"
39+
- November 1 (DST fall-back): "CST"
40+
41+
No code change needed for the ZZZ formatting itself.
42+
43+
## Plan
44+
45+
1. AC #1: Verified — no code change needed
46+
2. AC #2: Add unit tests in `tests/test_unit.py` that assert arrow `ZZZ` produces correct abbreviations for CST and CDT periods, including DST transition boundaries
47+
3. AC #3: Document findings in task notes
48+
49+
## Related issue (out of scope)
50+
51+
`current_time_local` in `app/main.py:49` is computed at module load time, not per-request. This means the check-schedule endpoint uses a stale timestamp. Filed as separate task.
52+
<!-- SECTION:PLAN:END -->
53+
54+
## Implementation Notes
55+
56+
<!-- SECTION:NOTES:BEGIN -->
57+
## Findings
58+
59+
Arrow's `ZZZ` format token correctly outputs timezone abbreviations based on the actual DST state of the datetime object. When using `America/Chicago`:
60+
- Standard time (Nov-Mar): outputs "CST"
61+
- Daylight saving time (Mar-Nov): outputs "CDT"
62+
- DST transition days: correctly reflects the post-transition state
63+
64+
No code change was needed. The existing `format("dddd HH:mm ZZZ")` calls in `app/main.py:464-465` work correctly.
65+
66+
A separate issue was identified: `current_time_local` is computed at module load time (TASK-020).
67+
<!-- SECTION:NOTES:END -->
68+
69+
## Final Summary
70+
71+
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
72+
Verified that arrow's `ZZZ` format token correctly distinguishes CST and CDT when using `America/Chicago` timezone. No code change needed.
73+
74+
Added 9 unit tests in `TestArrowTimezoneAbbreviation` covering:
75+
- Standard time months (Jan, Feb, Dec) -> CST
76+
- Daylight saving months (Jun, Jul) -> CDT
77+
- Spring-forward transition day (Mar 8) -> CDT
78+
- Fall-back transition day (Nov 1) -> CST
79+
- Format string shape matching check-schedule endpoint
80+
- UTC-to-local conversion preserving correct abbreviation
81+
82+
Filed TASK-020 for the separate issue of stale module-level time computation in `app/main.py`.
83+
<!-- SECTION:FINAL_SUMMARY:END -->
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
---
2+
id: TASK-020
3+
title: Fix stale module-level current_time_local in main.py
4+
status: To Do
5+
assignee: []
6+
created_date: '2026-03-21 00:58'
7+
labels:
8+
- bug
9+
dependencies: []
10+
references:
11+
- 'app/main.py:49-51'
12+
- 'app/main.py:435-472'
13+
priority: medium
14+
---
15+
16+
## Description
17+
18+
<!-- SECTION:DESCRIPTION:BEGIN -->
19+
`current_time_local` (line 49), `current_time_utc` (line 50), and `current_day` (line 51) in `app/main.py` are computed once at module load time. The `/api/check-schedule` endpoint uses these values, so it always reflects the server startup time rather than the actual request time. This causes incorrect time comparisons and stale timezone abbreviations if the server runs across a DST boundary.
20+
<!-- SECTION:DESCRIPTION:END -->
21+
22+
## Acceptance Criteria
23+
<!-- AC:BEGIN -->
24+
- [ ] #1 current_time_local, current_time_utc, and current_day are computed per-request in endpoints that use them
25+
- [ ] #2 Existing tests continue to pass
26+
- [ ] #3 Add test verifying time values reflect request time, not server startup time
27+
<!-- AC:END -->

tests/test_unit.py

Lines changed: 64 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1113,14 +1113,16 @@ def test_sort_json_human_readable_year_boundary(tmp_path):
11131113
@pytest.mark.unit
11141114
def test_prepare_events_human_readable_year_boundary():
11151115
"""prepare_events with human-readable dates near year boundary should not guess wrong year."""
1116-
df = pd.DataFrame({
1117-
"name": ["Group A", "Group B"],
1118-
"date": ["Thu 1/2 6:00 pm", "Mon 12/30 10:00 am"],
1119-
"title": ["Jan Event", "Dec Event"],
1120-
"description": ["desc", "desc"],
1121-
"city": ["OKC", "OKC"],
1122-
"eventUrl": ["url1", "url2"],
1123-
})
1116+
df = pd.DataFrame(
1117+
{
1118+
"name": ["Group A", "Group B"],
1119+
"date": ["Thu 1/2 6:00 pm", "Mon 12/30 10:00 am"],
1120+
"title": ["Jan Event", "Dec Event"],
1121+
"description": ["desc", "desc"],
1122+
"city": ["OKC", "OKC"],
1123+
"eventUrl": ["url1", "url2"],
1124+
}
1125+
)
11241126
with patch("arrow.now", return_value=arrow.get("2024-12-29")):
11251127
result = prepare_events(df)
11261128

@@ -1132,14 +1134,16 @@ def test_prepare_events_human_readable_year_boundary():
11321134
@pytest.mark.unit
11331135
def test_prepare_events_year_boundary():
11341136
"""prepare_events preserves year from ISO 8601 dates near year boundaries."""
1135-
df = pd.DataFrame({
1136-
"name": ["Group A", "Group B"],
1137-
"date": ["2025-01-02T18:00:00", "2024-12-30T10:00:00"],
1138-
"title": ["Jan Event", "Dec Event"],
1139-
"description": ["desc", "desc"],
1140-
"city": ["OKC", "OKC"],
1141-
"eventUrl": ["url1", "url2"],
1142-
})
1137+
df = pd.DataFrame(
1138+
{
1139+
"name": ["Group A", "Group B"],
1140+
"date": ["2025-01-02T18:00:00", "2024-12-30T10:00:00"],
1141+
"title": ["Jan Event", "Dec Event"],
1142+
"description": ["desc", "desc"],
1143+
"city": ["OKC", "OKC"],
1144+
"eventUrl": ["url1", "url2"],
1145+
}
1146+
)
11431147
with patch("arrow.now", return_value=arrow.get("2024-12-29")):
11441148
result = prepare_events(df)
11451149

@@ -1367,3 +1371,47 @@ def test_partial_failure(self, group_events_fragment):
13671371
def test_empty_list(self):
13681372
results = send_batched_group_request("fake_token", [])
13691373
assert results == []
1374+
1375+
1376+
@pytest.mark.unit
1377+
class TestArrowTimezoneAbbreviation:
1378+
"""Verify arrow ZZZ token produces correct CST/CDT for America/Chicago."""
1379+
1380+
@pytest.mark.parametrize(
1381+
"month, day, expected_abbr",
1382+
[
1383+
(1, 15, "CST"),
1384+
(2, 15, "CST"),
1385+
(6, 15, "CDT"),
1386+
(7, 15, "CDT"),
1387+
(12, 15, "CST"),
1388+
],
1389+
ids=["jan-cst", "feb-cst", "jun-cdt", "jul-cdt", "dec-cst"],
1390+
)
1391+
def test_standard_and_daylight_periods(self, month, day, expected_abbr):
1392+
dt = arrow.Arrow(2026, month, day, 14, 0, tzinfo="America/Chicago")
1393+
formatted = dt.format("dddd HH:mm ZZZ")
1394+
assert formatted.endswith(expected_abbr)
1395+
1396+
def test_spring_forward_transition(self):
1397+
"""March 8, 2026 is spring-forward day — afternoon is CDT."""
1398+
dt = arrow.Arrow(2026, 3, 8, 14, 0, tzinfo="America/Chicago")
1399+
assert dt.format("ZZZ") == "CDT"
1400+
1401+
def test_fall_back_transition(self):
1402+
"""November 1, 2026 is fall-back day — afternoon is CST."""
1403+
dt = arrow.Arrow(2026, 11, 1, 14, 0, tzinfo="America/Chicago")
1404+
assert dt.format("ZZZ") == "CST"
1405+
1406+
def test_format_matches_check_schedule_pattern(self):
1407+
"""The format string used in check-schedule produces expected shape."""
1408+
dt = arrow.Arrow(2026, 1, 15, 14, 0, tzinfo="America/Chicago")
1409+
result = dt.format("dddd HH:mm ZZZ")
1410+
assert result == "Thursday 14:00 CST"
1411+
1412+
def test_utc_converted_to_local_preserves_abbr(self):
1413+
"""Converting a UTC time to America/Chicago yields the correct abbreviation."""
1414+
utc_dt = arrow.Arrow(2026, 6, 15, 19, 0, tzinfo="UTC")
1415+
local_dt = utc_dt.to("America/Chicago")
1416+
assert local_dt.format("ZZZ") == "CDT"
1417+
assert local_dt.format("dddd HH:mm ZZZ") == "Monday 14:00 CDT"

0 commit comments

Comments
 (0)