@@ -349,9 +349,8 @@ def test_aggregate_daily_with_date_filter(
349349 ) -> None :
350350 """Test daily aggregation with date filters."""
351351 start_date = datetime (2024 , 1 , 15 , tzinfo = timezone .utc )
352- end_date = datetime (
353- 2024 , 1 , 31 , 23 , 59 , 59 , tzinfo = timezone .utc
354- ) # Include the whole day
352+ # end_date is inclusive - pass Jan 31 to include all of Jan 31
353+ end_date = datetime (2024 , 1 , 31 , tzinfo = timezone .utc )
355354
356355 result = aggregator .aggregate_daily (sample_entries , start_date , end_date )
357356
@@ -644,7 +643,8 @@ def test_aggregate_daily_with_date_filters(
644643
645644 # Filter for days 3-7 (Jan 3 to Jan 7)
646645 start_date = datetime (2024 , 1 , 3 , tzinfo = timezone .utc )
647- end_date = datetime (2024 , 1 , 8 , tzinfo = timezone .utc ) # End is exclusive
646+ # end_date is inclusive - to get Jan 3-7, pass Jan 7
647+ end_date = datetime (2024 , 1 , 7 , tzinfo = timezone .utc )
648648
649649 result = aggregator .aggregate_daily (entries , start_date , end_date )
650650
@@ -729,7 +729,8 @@ def test_aggregate_with_date_filters(
729729
730730 # Test with date filters
731731 start_date = datetime (2024 , 1 , 2 , tzinfo = timezone .utc )
732- end_date = datetime (2024 , 1 , 4 , tzinfo = timezone .utc )
732+ # end_date is inclusive - to get Jan 2-3, pass Jan 3
733+ end_date = datetime (2024 , 1 , 3 , tzinfo = timezone .utc )
733734
734735 result = aggregator .aggregate (start_date = start_date , end_date = end_date )
735736
@@ -741,3 +742,49 @@ def test_aggregate_with_date_filters(
741742 # Test without filters - should return all
742743 result_all = aggregator .aggregate ()
743744 assert len (result_all ) == 5
745+
746+ def test_timezone_grouping_and_filters (self , tmp_path ) -> None :
747+ """Entries should be grouped and filtered using the selected timezone."""
748+ from claude_monitor .core .models import UsageEntry
749+
750+ # Two entries around the UTC day boundary
751+ e1 = UsageEntry (
752+ timestamp = datetime (2023 , 12 , 31 , 23 , 30 , tzinfo = timezone .utc ),
753+ input_tokens = 100 ,
754+ output_tokens = 50 ,
755+ cache_creation_tokens = 0 ,
756+ cache_read_tokens = 0 ,
757+ cost_usd = 0.001 ,
758+ model = "m" ,
759+ message_id = "a" ,
760+ request_id = "a" ,
761+ )
762+ e2 = UsageEntry (
763+ timestamp = datetime (2024 , 1 , 1 , 0 , 30 , tzinfo = timezone .utc ),
764+ input_tokens = 200 ,
765+ output_tokens = 100 ,
766+ cache_creation_tokens = 0 ,
767+ cache_read_tokens = 0 ,
768+ cost_usd = 0.002 ,
769+ model = "m" ,
770+ message_id = "b" ,
771+ request_id = "b" ,
772+ )
773+
774+ entries = [e1 , e2 ]
775+
776+ # Under UTC they should fall into different dates (2023-12-31 and 2024-01-01)
777+ agg_utc = UsageAggregator (data_path = str (tmp_path ), timezone = "UTC" )
778+ res_utc = agg_utc .aggregate_daily (entries )
779+ assert len (res_utc ) == 2
780+ assert res_utc [0 ]["date" ] == "2023-12-31"
781+ assert res_utc [1 ]["date" ] == "2024-01-01"
782+
783+ # Under America/New_York (UTC-5) both timestamps belong to 2023-12-31
784+ agg_est = UsageAggregator (data_path = str (tmp_path ), timezone = "America/New_York" )
785+ res_est = agg_est .aggregate_daily (entries )
786+ assert len (res_est ) == 1
787+ assert res_est [0 ]["date" ] == "2023-12-31"
788+ # Validate totals
789+ assert res_est [0 ]["input_tokens" ] == 300
790+ assert res_est [0 ]["output_tokens" ] == 150
0 commit comments