33from __future__ import annotations
44
55from dataclasses import dataclass
6- from datetime import datetime , timedelta
6+ from datetime import datetime , timedelta , timezone
77from typing import TYPE_CHECKING , Any
88
99if TYPE_CHECKING :
@@ -257,16 +257,33 @@ def prices_today(self) -> dict[datetime, float]:
257257 The prices for today.
258258
259259 """
260- today = self .now_in_timezone ().astimezone (). date ()
260+ today = self .now_in_timezone ().date ()
261261 prices = {
262262 timestamp : price
263263 for timestamp , price in self .prices .items ()
264- if timestamp .date () == today
264+ if timestamp .astimezone ( self . time_zone ). date () == today
265265 }
266266
267- if (self .resolution == timedelta (minutes = 15 ) and len (prices ) == 96 ) or (
268- self .resolution == timedelta (minutes = 60 ) and len (prices ) == 24
269- ):
267+ # Calculate expected intervals accounting for DST transitions.
268+ # On DST transition days, local time spans may be 23 or 25 hours,
269+ # not 24, due to the shifted/repeated hour. We count UTC hours that
270+ # correspond to the local date to handle DST correctly.
271+ day_start = datetime .combine (today , datetime .min .time (), tzinfo = self .time_zone )
272+ day_start_utc = day_start .astimezone (timezone .utc )
273+
274+ # Count UTC hours that fall within this local date
275+ hour_count = 0
276+ current_utc = day_start_utc
277+ while current_utc .astimezone (self .time_zone ).date () == today :
278+ hour_count += 1
279+ current_utc = current_utc + timedelta (hours = 1 )
280+
281+ expected_intervals = max (
282+ 1 ,
283+ int ((hour_count * timedelta (hours = 1 )) / self .resolution ),
284+ )
285+
286+ if len (prices ) == expected_intervals :
270287 return prices
271288
272289 return {}
@@ -279,16 +296,37 @@ def prices_tomorrow(self) -> dict[datetime, float]:
279296 The prices for tomorrow.
280297
281298 """
282- tomorrow = (self .now_in_timezone () + timedelta (days = 1 )).astimezone (). date ()
299+ tomorrow = (self .now_in_timezone () + timedelta (days = 1 )).date ()
283300 prices = {
284301 timestamp : price
285302 for timestamp , price in self .prices .items ()
286- if timestamp .date () == tomorrow
303+ if timestamp .astimezone ( self . time_zone ). date () == tomorrow
287304 }
288305
289- if (self .resolution == timedelta (minutes = 15 ) and len (prices ) == 96 ) or (
290- self .resolution == timedelta (minutes = 60 ) and len (prices ) == 24
291- ):
306+ # Calculate expected intervals accounting for DST transitions.
307+ # On DST transition days, local time spans may be 23 or 25 hours,
308+ # not 24, due to the shifted/repeated hour. We count UTC hours that
309+ # correspond to the local date to handle DST correctly.
310+ day_start = datetime .combine (
311+ tomorrow ,
312+ datetime .min .time (),
313+ tzinfo = self .time_zone ,
314+ )
315+ day_start_utc = day_start .astimezone (timezone .utc )
316+
317+ # Count UTC hours that fall within this local date
318+ hour_count = 0
319+ current_utc = day_start_utc
320+ while current_utc .astimezone (self .time_zone ).date () == tomorrow :
321+ hour_count += 1
322+ current_utc = current_utc + timedelta (hours = 1 )
323+
324+ expected_intervals = max (
325+ 1 ,
326+ int ((hour_count * timedelta (hours = 1 )) / self .resolution ),
327+ )
328+
329+ if len (prices ) == expected_intervals :
292330 return prices
293331
294332 return {}
0 commit comments