Skip to content

More DST fixes#46

Merged
enoch85 merged 16 commits intomainfrom
fix/dst-interval-validation
Oct 26, 2025
Merged

More DST fixes#46
enoch85 merged 16 commits intomainfrom
fix/dst-interval-validation

Conversation

@enoch85
Copy link
Copy Markdown
Owner

@enoch85 enoch85 commented Oct 26, 2025

DST Interval Validation Fix

Fixes incorrect interval counting and validation on DST transition days.

Key Changes

  • DST-aware interval counting: TimeInterval.get_expected_intervals_for_date() now returns 92/96/100 intervals based on DST transitions
  • Smart range methods: get_today_range() and get_tomorrow_range() generate correct interval keys for 23/24/25-hour days
  • Time-aware validation: Tomorrow data warnings only appear after publication time (13:00-14:00 UTC), not before
  • HACS compliance: Added CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN) to satisfy HACS validation
  • Code quality: Moved inline imports to top, verified DST best practices compliance

Tests

  • All 296 unit tests passing
  • Added 7 new DST-specific tests for parsers
  • Verified against Python DST best practices (zoneinfo, UTC-first approach)

Fixes

  • Fix Daylight savings bug #45
  • ✅ Correct interval counts on DST fall-back days (100 intervals for 25 hours)
  • ✅ Correct interval counts on DST spring-forward days (92 intervals for 23 hours)
  • ✅ Reduced false warnings for expected partial tomorrow data
  • ✅ Fixed HACS validation warning about missing CONFIG_SCHEMA
  • ✅ All timedelta operations follow recommended patterns (re-localize with .astimezone())

- Added TimeInterval.get_expected_intervals_for_date() method
- Returns 92 (spring forward), 96 (normal), or 100 (fall back) intervals
- Uses DSTHandler to detect DST transition days
- Created comprehensive unit tests covering:
  * Normal days (96 intervals)
  * Spring forward days (92 intervals)
  * Fall back days (100 intervals)
  * Multiple timezones (Europe, US, Australia)
  * Naive datetime handling
- All 9 tests pass
- Code formatted with black

This addresses the issue where validation logic expected a hardcoded
96 intervals on all days, causing false warnings on DST transition days.
@enoch85 enoch85 changed the title Phase 1.1: Add DST-aware interval counting to TimeInterval Fixes Oct 26, 2025
- Created test_energi_data_parser_dst.py with 3 test cases
  * DST fall-back day (100 intervals from 25 hours)
  * DST spring-forward day (92 intervals from 23 hours)
  * Normal day (96 intervals from 24 hours)

- Created test_omie_parser_dst.py with 4 test cases
  * DST fall-back day CSV parsing
  * DST spring-forward day CSV parsing
  * Normal day CSV parsing
  * Interval expansion verification

- All 7 new tests pass
- Verified parsers correctly handle DST when APIs provide correct data
- Confirmed interval_expander.py works correctly (just expands input)

The tests document expected behavior:
- Energi Data Service: Provides 15-minute intervals directly
- OMIE: Provides hourly data that gets expanded to 15-minute intervals
- Both correctly handle DST transitions when source data is correct
- Added get_day_hours() standalone function to timezone_provider.py
  * Returns 24 hours for normal days
  * Returns 25 hours for DST fall-back days (hour 2 with _1 and _2 suffixes)
  * Returns 23 hours for DST spring-forward days (hour 2 skipped)

- Updated TimezoneService.get_today_range() to use get_day_hours()
  * Now returns 96, 100, or 92 interval keys depending on DST
  * Includes proper _1 and _2 suffixes for duplicate hours

- Updated TimezoneService.get_tomorrow_range() to use get_day_hours()
  * Same DST-aware behavior as get_today_range()

This fixes the issue where data_processor.py expected exactly 96 intervals
on all days, causing false warnings on DST transition days.

Tests: All 36 DST-related tests pass
- Added PUBLICATION_TIMES_UTC to const/sources.py
  * Nordpool/ENTSO-E/Energi Data: 13:00 UTC
  * OMIE/Energy-Charts/AEMO/others: 14:00 UTC
  * Added get_publication_time_utc() method

- Updated data_processor.py tomorrow validation logic
  * Before publication time: Log DEBUG (expected behavior)
  * After publication time: Log WARNING (data should be available)
  * Reduces false warnings during normal morning hours

This implements the recommendation from DEBUG_ANALYSIS.md to make
validation time-aware, avoiding false warnings before tomorrow data
is expected to be published.

Tests: All 71 DST and interval tests pass
Fixed inline imports in frequently-used modules:
- coordinator/data_processor.py: Moved all parser imports, TimeInterval, Source, dt_util, data_validity imports
- sensor/base.py: Moved DataValidity import
- sensor/price.py: Removed duplicate dt_util import
- utils/rate_limiter.py: Moved SourceIntervals and TimeInterval imports
- config_flow/options.py: Moved DOMAIN import
- config_flow/implementation.py: Moved GSpotOptionsFlow import
- timezone/service.py: Moved get_day_hours and dt_util imports
- timezone/timezone_provider.py: Moved TimeInterval, DSTTransitionType, DSTHandler, ZoneInfo imports

All 71 DST and interval tests passing
Code formatted with black
Complies with Python code quality standards (PEP 8)
Fixed inline imports in core modules:
- coordinator/data_validity.py: Moved all dt_util, timedelta, ZoneInfo, TimeInterval imports to top
- const/time.py: Moved ZoneInfo to top, kept DSTHandler inline (circular dependency)

Note: DSTHandler import in const/time.py remains inline to avoid circular dependency
(DSTHandler uses TimeInterval, TimeInterval.get_expected_intervals_for_date uses DSTHandler)

All 71 DST and interval tests passing
Changed 4 instances of zoneinfo.ZoneInfo to ZoneInfo since we import
'from zoneinfo import ZoneInfo' at the top of the file.

Fixes GitHub Actions Python Validation failure.
Removed TestDSTAwareIntervalCounting class from test_dst_validation.py as it
duplicates tests already in test_time_interval_dst.py.

Kept production code tests (ApiValidator, DataProcessor) which are unique.

DST test count: 58 tests across 6 well-organized files:
- test_dst_handler.py (23 tests) - DSTHandler infrastructure
- test_time_interval_dst.py (9 tests) - TimeInterval DST methods
- test_dst_validation.py (7 tests) - Production code validation
- test_dst_duplicates.py (4 tests) - Fall-back duplicate handling
- test_energi_data_parser_dst.py (3 tests) - Energi Data parser
- test_omie_parser_dst.py (4 tests) - OMIE parser

All tests passing.
Production code fixes:
1. Define is_today_dst, is_tomorrow_dst, today_dst_type, tomorrow_dst_type
   These variables were referenced in logging but never defined, causing
   NameError in production code (bug introduced in previous DST commits)

2. Add hasattr() guards before .astimezone() calls
   Defensive programming to handle edge cases where timezone might not
   be a proper timezone object

Test fixes:
- Configure mock TimezoneService with real timezone.utc objects
  instead of plain MagicMock to support DST-aware code that checks
  for tzname attribute

Result: 295/296 tests passing (1 pre-existing failure unrelated to DST)
…TransitionType usage

- Format unified_price_manager.py with black
- Add cache cleanup to run_pytest.sh (removes __pycache__ and .pytest_cache before tests)
- Import and use DSTTransitionType enum constants instead of hardcoded strings
- Fix test fixture in test_stale_tomorrow_fix.py to use real timezone.utc objects

All 296 tests passing
- Fixed NameError: name 'area_timezone' is not defined
- Changed to use self._tz_service.area_timezone
- Affects grace period validation logic for incomplete today data
- All 296 tests passing
@enoch85 enoch85 changed the title Fixes More DST fixes Oct 26, 2025
@enoch85 enoch85 marked this pull request as ready for review October 26, 2025 14:28
@enoch85 enoch85 merged commit 903c3c1 into main Oct 26, 2025
10 of 11 checks passed
@enoch85 enoch85 deleted the fix/dst-interval-validation branch October 26, 2025 14:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Daylight savings bug

1 participant