|
| 1 | +# ----------------------------------------------------------------------------- |
| 2 | +# Predbat Home Battery System |
| 3 | +# Copyright Trefor Southwell 2026 - All Rights Reserved |
| 4 | +# This application maybe used for personal use only and not for commercial use |
| 5 | +# ----------------------------------------------------------------------------- |
| 6 | +# fmt off |
| 7 | +# pylint: disable=consider-using-f-string |
| 8 | +# pylint: disable=line-too-long |
| 9 | +# pylint: disable=attribute-defined-outside-init |
| 10 | + |
| 11 | +from datetime import datetime |
| 12 | +import pytz |
| 13 | +from utils import localize_yearless_window |
| 14 | + |
| 15 | +TIME_FORMAT = "%m-%d %H:%M:%S" |
| 16 | +LONDON = pytz.timezone("Europe/London") |
| 17 | + |
| 18 | + |
| 19 | +def test_localize_yearless_window(my_predbat=None): |
| 20 | + """ |
| 21 | + Test utils.localize_yearless_window() - anchoring a "%m-%d %H:%M:%S" start/end pair (as |
| 22 | + output.py's car_charging_slot "planned" attribute publishes them, with no year) onto a real |
| 23 | + year close to `now`, including the New Year boundary case that motivated it (#269, and the |
| 24 | + Copilot review on #5120 that found the original gateway.py/ohme.py heuristic could anchor a |
| 25 | + still-current window a year in the future once `now` had itself rolled into January). |
| 26 | + """ |
| 27 | + failed = False |
| 28 | + print("**** Testing localize_yearless_window ****") |
| 29 | + |
| 30 | + # Test 1: an ordinary same-day window, nowhere near a year boundary |
| 31 | + now = LONDON.localize(datetime(2026, 6, 15, 14, 0, 0)) |
| 32 | + start, end = localize_yearless_window("06-15 13:00:00", "06-15 15:00:00", TIME_FORMAT, now, LONDON) |
| 33 | + if (start.year, end.year) != (2026, 2026) or not (start <= now < end): |
| 34 | + print(f"ERROR: Test 1 (ordinary window) failed - got start={start} end={end}") |
| 35 | + failed = True |
| 36 | + |
| 37 | + # Test 2: a window straddling New Year's Eve into New Year's Day, read while it's still |
| 38 | + # underway on Dec 31 - both ends belong to the same current year |
| 39 | + now = LONDON.localize(datetime(2026, 12, 31, 23, 45, 0)) |
| 40 | + start, end = localize_yearless_window("12-31 23:30:00", "01-01 01:30:00", TIME_FORMAT, now, LONDON) |
| 41 | + if start.year != 2026 or end.year != 2027 or not (start <= now < end): |
| 42 | + print(f"ERROR: Test 2 (NYE window, read before midnight) failed - got start={start} end={end}") |
| 43 | + failed = True |
| 44 | + |
| 45 | + # Test 3: the same window, but read back once `now` has itself rolled over into January - the |
| 46 | + # Dec 31 half must anchor to the *previous* year, not now.year, or the window is pushed a full |
| 47 | + # year into the future and stops covering `now` at all (Copilot review on #5120) |
| 48 | + now = LONDON.localize(datetime(2027, 1, 1, 0, 15, 0)) |
| 49 | + start, end = localize_yearless_window("12-31 23:30:00", "01-01 01:30:00", TIME_FORMAT, now, LONDON) |
| 50 | + if start.year != 2026 or end.year != 2027 or not (start <= now < end): |
| 51 | + print(f"ERROR: Test 3 (NYE window, read after midnight rollover) failed - got start={start} end={end}") |
| 52 | + failed = True |
| 53 | + |
| 54 | + # Test 4: a still-active long window whose start is genuinely a couple of days old, nowhere |
| 55 | + # near a year boundary - must not be shifted a year in either direction (#269) |
| 56 | + now = LONDON.localize(datetime(2026, 6, 16, 23, 5, 0)) |
| 57 | + start, end = localize_yearless_window("06-14 00:10:00", "06-17 02:00:00", TIME_FORMAT, now, LONDON) |
| 58 | + if start.year != 2026 or end.year != 2026 or not (start <= now < end): |
| 59 | + print(f"ERROR: Test 4 (long active window) failed - got start={start} end={end}") |
| 60 | + failed = True |
| 61 | + |
| 62 | + # Test 5: a midnight-crossing window with no year-boundary involvement at all (e.g. 23:00-00:30 |
| 63 | + # in mid-June) - end must land on the following calendar day, same year, without needing the |
| 64 | + # New Year handling to fire |
| 65 | + now = LONDON.localize(datetime(2026, 6, 15, 23, 30, 0)) |
| 66 | + start, end = localize_yearless_window("06-15 23:00:00", "06-16 00:30:00", TIME_FORMAT, now, LONDON) |
| 67 | + if (start.year, end.year) != (2026, 2026) or end.day != 16 or not (start <= now < end): |
| 68 | + print(f"ERROR: Test 5 (ordinary midnight-crossing window) failed - got start={start} end={end}") |
| 69 | + failed = True |
| 70 | + |
| 71 | + # Test 6: an unparseable time string raises ValueError, same as the underlying strptime - the |
| 72 | + # gateway.py/ohme.py callers rely on this to skip a malformed planned entry rather than crash |
| 73 | + try: |
| 74 | + localize_yearless_window("not-a-date", "06-15 15:00:00", TIME_FORMAT, now, LONDON) |
| 75 | + print("ERROR: Test 6 (unparseable string) should have raised ValueError") |
| 76 | + failed = True |
| 77 | + except ValueError: |
| 78 | + pass |
| 79 | + |
| 80 | + if not failed: |
| 81 | + print("**** All localize_yearless_window tests PASSED ****") |
| 82 | + else: |
| 83 | + print("**** Some localize_yearless_window tests FAILED ****") |
| 84 | + |
| 85 | + return failed |
0 commit comments