mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-10-04 06:15:09 +00:00
fix(sports): scroll and Vegas cards name the printed date's own weekday (#747)
With scroll_card.date_format "weekday", a Friday 8 PM ET game read "Sat Oct 2" on the scroll and Vegas cards. Cause: the extractor prints the "M/D" in the plugin's resolved zone (its own setting, then the global one, then the system zone), but the card is handed only the plugin's config. Its timezone ships as "", so card_tzinfo fell back to UTC and the weekday belonged to the UTC date: the next day for evening games in the Americas, the previous day for morning games east of UTC (Auckland, Kiritimati). Fix: every zone is within a day of UTC, so the printed date is the start's UTC date or a neighbour of it. _format_date_as now takes the game and names the weekday of whichever of those days has the printed month and day, falling back to the zone-based weekday only when the start cannot place the date (no offset, unparseable, or more than a day away). The switch-mode scorebug shares the formatter and passes the game too, so the twins stay identical; it already used the resolved zone and draws what it drew before. Public signatures are unchanged. Tests cover US DST end, New Year's Eve, both sides of the date line, NZ DST start and UTC+14. The twins test's weekday pin is updated: the drawn date now agrees, and only the bare weekday helpers still differ. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -13,6 +13,8 @@ body. That is what let all eight adopt this with byte-identical renders.
|
||||
import logging
|
||||
import json
|
||||
import os
|
||||
from datetime import datetime, timezone
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
import pytest
|
||||
|
||||
@@ -169,6 +171,95 @@ class TestDateAndTime:
|
||||
assert C.card_tzinfo({"timezone": "Not/AZone"}, log) is timezone.utc
|
||||
|
||||
|
||||
class TestWeekdayMatchesThePrintedDate:
|
||||
"""The weekday is the printed date's, whichever zone printed it.
|
||||
|
||||
The extractor prints "M/D" in the plugin's resolved zone (its own
|
||||
setting, else the global one, else the system zone). The card is handed
|
||||
only the plugin's config, whose ``timezone`` ships as "" -- so a weekday
|
||||
taken in card_tzinfo's zone was UTC's, and an evening kickoff in the
|
||||
Americas read "Sat Oct 2" for a Friday game.
|
||||
"""
|
||||
|
||||
WEEKDAY = {"timezone": "", "scroll_card": {"date_format": "weekday"}}
|
||||
|
||||
@staticmethod
|
||||
def _as_printed(start_utc, zone):
|
||||
"""The game dict and the date text, as the extractor builds them."""
|
||||
local = datetime.fromisoformat(start_utc).astimezone(ZoneInfo(zone))
|
||||
game = {"start_time_utc": datetime.fromisoformat(start_utc),
|
||||
"game_date": f"{local.month}/{local.day}"}
|
||||
want = f"{C.WEEKDAY_ABBR[local.weekday()]} {C.MONTH_ABBR[local.month - 1]} {local.day}"
|
||||
return game, want
|
||||
|
||||
@pytest.mark.parametrize("start_utc, zone, want", [
|
||||
# Friday 8 PM EDT is Saturday in UTC.
|
||||
("2026-10-03T00:00:00+00:00", "America/New_York", "Fri Oct 2"),
|
||||
# The night US clocks go back: 8:30 PM EDT Saturday, then 11 PM EST
|
||||
# Sunday, each the next day in UTC.
|
||||
("2026-11-01T00:30:00+00:00", "America/New_York", "Sat Oct 31"),
|
||||
("2026-11-02T04:00:00+00:00", "America/New_York", "Sun Nov 1"),
|
||||
# New Year's Eve on the west coast is New Year's Day in UTC.
|
||||
("2027-01-01T04:00:00+00:00", "America/Los_Angeles", "Thu Dec 31"),
|
||||
# Just east of the date line: Pago Pago's Friday evening.
|
||||
("2026-10-03T05:00:00+00:00", "Pacific/Pago_Pago", "Fri Oct 2"),
|
||||
# Just west of it, the other way: Saturday morning in Auckland is
|
||||
# Friday in UTC -- and the 10 AM game on the day NZ clocks go forward.
|
||||
("2026-10-02T20:00:00+00:00", "Pacific/Auckland", "Sat Oct 3"),
|
||||
("2026-09-26T21:00:00+00:00", "Pacific/Auckland", "Sun Sep 27"),
|
||||
# UTC+14, the furthest any zone sits from UTC.
|
||||
("2026-10-02T11:00:00+00:00", "Pacific/Kiritimati", "Sat Oct 3"),
|
||||
# A zone on UTC's own date needs nothing.
|
||||
("2026-10-02T19:00:00+00:00", "Europe/London", "Fri Oct 2"),
|
||||
])
|
||||
def test_the_shipped_blank_timezone(self, log, start_utc, zone, want):
|
||||
game, printed = self._as_printed(start_utc, zone)
|
||||
assert printed == want # the case says what the extractor prints
|
||||
assert C.format_game_date(self.WEEKDAY, log, game["game_date"], game) == want
|
||||
|
||||
def test_an_iso_string_start_reads_the_same(self, log):
|
||||
game = {"start_time_utc": "2026-10-03T00:00:00Z"}
|
||||
assert C.format_game_date(self.WEEKDAY, log, "10/2", game) == "Fri Oct 2"
|
||||
assert C.format_game_date(self.WEEKDAY, log, "10/02", game) == "Fri Oct 2"
|
||||
|
||||
def test_a_plugin_level_zone_still_agrees(self, log):
|
||||
game, want = self._as_printed("2026-10-03T00:00:00+00:00", "America/Chicago")
|
||||
cfg = dict(self.WEEKDAY, timezone="America/Chicago")
|
||||
assert C.format_game_date(cfg, log, game["game_date"], game) == want == "Fri Oct 2"
|
||||
|
||||
def test_a_date_no_zone_could_print_keeps_the_zone_weekday(self, log):
|
||||
"""More than a day from the start: nothing to anchor to, so the
|
||||
weekday is card_tzinfo's, as it always was."""
|
||||
game = {"start_time_utc": datetime(2026, 10, 3, 0, 0, tzinfo=timezone.utc)}
|
||||
assert C.format_game_date(self.WEEKDAY, log, "10/9", game) == "Sat Oct 9"
|
||||
|
||||
def test_a_start_without_an_offset_keeps_the_zone_weekday(self, log):
|
||||
"""A naive time names no instant, so it cannot place the date."""
|
||||
game = {"start_time_utc": datetime(2026, 10, 2, 20, 0)}
|
||||
assert C.format_game_date(self.WEEKDAY, log, "10/3", game) == \
|
||||
f"{C.weekday_for(self.WEEKDAY, log, game)} Oct 3"
|
||||
|
||||
@pytest.mark.parametrize("game", [None, {}, {"start_time_utc": "garbage"}])
|
||||
def test_no_usable_start_draws_no_weekday(self, log, game):
|
||||
assert C.format_game_date(self.WEEKDAY, log, "10/2", game) == "Oct 2"
|
||||
|
||||
def test_the_scorebug_twin_formats_the_same(self, log):
|
||||
"""Switch mode (SportsCoreSharedMixin) shares the formatter body."""
|
||||
from src.common.sports_shared import SportsCoreSharedMixin
|
||||
|
||||
class Host(SportsCoreSharedMixin):
|
||||
config = {"scroll_card": {"date_format": "weekday",
|
||||
"switch_date_format": "inherit"}}
|
||||
logger = log
|
||||
|
||||
def _get_timezone(self):
|
||||
return ZoneInfo("America/New_York")
|
||||
|
||||
game, want = self._as_printed("2026-11-01T00:30:00+00:00", "America/New_York")
|
||||
assert Host()._format_game_date(game["game_date"], game) == want
|
||||
assert C.format_game_date(Host.config, log, game["game_date"], game) == want
|
||||
|
||||
|
||||
class TestFontSizing:
|
||||
def test_snaps_to_the_faces_pixel_grid(self):
|
||||
assert C.crisp_size("4x6-font.ttf", 6) == 7 # 7px grid
|
||||
|
||||
@@ -561,21 +561,24 @@ class TestPinnedDivergence:
|
||||
assert C.recent_score_color(on, LOG, game, (9, 9, 9)) == (9, 9, 9)
|
||||
|
||||
def test_weekday_zone_source(self):
|
||||
# DIVERGENCE, user-visible: the scorebug asks the plugin's
|
||||
# DIVERGENCE, not drawn: the scorebug asks the plugin's
|
||||
# _get_timezone() (plugin setting -> global setting -> system zone);
|
||||
# the card reads only config["timezone"] and falls back to UTC. The
|
||||
# scoreboards' schemas default that key to "", and the scroll display
|
||||
# hands the renderer the plugin config, so a board that sets only the
|
||||
# global zone gets UTC weekdays in scroll mode: an evening kickoff in
|
||||
# New York is labelled with the next day.
|
||||
# hands the renderer the plugin config, so the bare weekday helpers
|
||||
# still disagree for an evening kickoff in New York.
|
||||
game = {"start_time_utc": "2026-09-20T00:30:00+00:00"} # Sat 20:30 EDT
|
||||
host = _Host({}, tz=ZoneInfo("America/New_York"))
|
||||
assert host._weekday_for(game) == "Sat"
|
||||
assert C.weekday_for({}, LOG, game) == "Sun"
|
||||
# DECIDED: what a card draws is the printed date's own weekday, so
|
||||
# the scroll card no longer labels that kickoff with the next day
|
||||
# ("Sun Sep 19" before). Both formatters place the extractor's "M/D"
|
||||
# against the start time instead of re-deriving the day in a zone.
|
||||
cfg = {"scroll_card": {"date_format": "weekday", "switch_date_format": "inherit"}}
|
||||
host = _Host(cfg, tz=ZoneInfo("America/New_York"))
|
||||
assert host._format_game_date("9/19", game) == "Sat Sep 19"
|
||||
assert C.format_game_date(cfg, LOG, "9/19", game) == "Sun Sep 19"
|
||||
assert C.format_game_date(cfg, LOG, "9/19", game) == "Sat Sep 19"
|
||||
|
||||
def test_weekday_out_of_range_start(self):
|
||||
# DIVERGENCE: the mixin catches OverflowError from astimezone() and
|
||||
|
||||
Reference in New Issue
Block a user