22import pytest
33import numpy as np
44import pandas as pd
5+ from pydsm .analysis .postpro import convert_index_to_timestamps , merge
56
67
78def test_postprocessor ():
@@ -23,3 +24,72 @@ def test_postprocessor():
2324 assert p2 .df .index [0 ] == pd .to_datetime ("03OCT2016 0000" )
2425 assert p2 .df .index [- 1 ] == pd .to_datetime ("05OCT2016 0000" )
2526 # assert_frame_equal(p.df, p2.df,check_names=False, check_column_type=False, check_like=False)
27+
28+
29+ # ---------------------------------------------------------------------------
30+ # convert_index_to_timestamps — datetime resolution normalisation
31+ # ---------------------------------------------------------------------------
32+
33+ class TestConvertIndexToTimestamps :
34+ """Regression: convert_index_to_timestamps must coerce datetime64[us] to
35+ datetime64[ns] — previously it returned early without coercing because the
36+ index was already a DatetimeIndex."""
37+
38+ def _make (self , dtype , n = 10 ):
39+ idx = pd .date_range ("2020-01-01" , periods = n , freq = "15min" ).astype (dtype )
40+ return pd .DataFrame ({"v" : range (n )}, index = idx )
41+
42+ def test_ns_index_unchanged (self ):
43+ df = self ._make ("datetime64[ns]" )
44+ convert_index_to_timestamps (df )
45+ assert df .index .dtype == np .dtype ("datetime64[ns]" )
46+
47+ def test_us_index_coerced_to_ns (self ):
48+ """Before the fix this was a no-op; the index stayed as datetime64[us]."""
49+ df = self ._make ("datetime64[us]" )
50+ convert_index_to_timestamps (df )
51+ assert df .index .dtype == np .dtype ("datetime64[ns]" ), (
52+ f"Expected datetime64[ns], got { df .index .dtype } "
53+ )
54+
55+ def test_period_index_converted (self ):
56+ idx = pd .period_range ("2020-01-01" , periods = 10 , freq = "15min" )
57+ df = pd .DataFrame ({"v" : range (10 )}, index = idx )
58+ convert_index_to_timestamps (df )
59+ assert isinstance (df .index , pd .DatetimeIndex )
60+
61+
62+ # ---------------------------------------------------------------------------
63+ # merge() — mixed datetime resolution (ns vs us)
64+ # ---------------------------------------------------------------------------
65+
66+ class TestMergeMixedResolution :
67+ """merge() calls combine_first() which requires aligned indices.
68+ Both inputs must be normalised to the same resolution first."""
69+
70+ def _make (self , dtype , start = "2020-01-01" , n = 20 , col = "v" ):
71+ idx = pd .date_range (start , periods = n , freq = "15min" ).astype (dtype )
72+ return pd .DataFrame ({col : np .random .default_rng (0 ).random (n )}, index = idx )
73+
74+ def test_ns_plus_us_does_not_raise (self ):
75+ df_ns = self ._make ("datetime64[ns]" , start = "2020-01-01" )
76+ df_us = self ._make ("datetime64[us]" , start = "2020-01-01 05:00" ) # overlapping
77+ result = merge ([df_ns , df_us ])
78+ assert result is not None
79+ assert isinstance (result .index , pd .DatetimeIndex )
80+ assert result .index .dtype == np .dtype ("datetime64[ns]" )
81+
82+ def test_both_us_does_not_raise (self ):
83+ df1 = self ._make ("datetime64[us]" , start = "2020-01-01" )
84+ df2 = self ._make ("datetime64[us]" , start = "2020-01-01 05:00" )
85+ result = merge ([df1 , df2 ])
86+ assert result is not None
87+ assert result .index .dtype == np .dtype ("datetime64[ns]" )
88+
89+ def test_result_spans_both_inputs (self ):
90+ df_ns = self ._make ("datetime64[ns]" , start = "2020-01-01" , n = 4 )
91+ df_us = self ._make ("datetime64[us]" , start = "2020-01-01 01:00" , n = 4 )
92+ result = merge ([df_ns , df_us ])
93+ # Result should cover the full range of both inputs
94+ assert result .index .min () <= pd .Timestamp ("2020-01-01" )
95+ assert result .index .max () >= pd .Timestamp ("2020-01-01 01:45" )
0 commit comments