@@ -6584,6 +6584,77 @@ def rotator(source, dest):
65846584 rh .close ()
65856585
65866586class TimedRotatingFileHandlerTest (BaseFileTest ):
6587+ def assert_rollover_contents (self , when , timestamps , expected , ** kwargs ):
6588+ with tempfile .TemporaryDirectory () as dirname :
6589+ filename = os .path .join (dirname , 'app.log' )
6590+ now = [timestamps [0 ]]
6591+ with patch ('time.time' , lambda : now [0 ]):
6592+ handler = logging .handlers .TimedRotatingFileHandler (
6593+ filename , when = when , delay = True , encoding = 'utf-8' , ** kwargs )
6594+ try :
6595+ for i , timestamp in enumerate (timestamps ):
6596+ now [0 ] = timestamp
6597+ handler .emit (logging .makeLogRecord ({'msg' : str (i )}))
6598+ finally :
6599+ handler .close ()
6600+ actual = {}
6601+ for name in os .listdir (dirname ):
6602+ with open (os .path .join (dirname , name ), encoding = 'utf-8' ) as f :
6603+ actual [name .removeprefix ('app.log' )] = f .read ().splitlines ()
6604+ self .assertEqual (actual , expected )
6605+
6606+ @support .run_with_tz ('EST+05EDT,M3.2.0,M11.1.0' )
6607+ def test_rollover_fixed_interval_dst (self ):
6608+ DT = datetime .datetime
6609+ cases = (
6610+ ('H' , DT (2026 , 3 , 8 , 1 , 30 ), 3 ),
6611+ ('H' , DT (2026 , 11 , 1 , 1 , 30 ), 2 ),
6612+ ('D' , DT (2026 , 3 , 7 , 23 , 30 ), 3 ),
6613+ ('D' , DT (2026 , 11 , 1 , 0 , 30 ), 2 ),
6614+ )
6615+ for when , start , count in cases :
6616+ interval = 3600 if when == 'H' else 86400
6617+ timestamps = [start .timestamp () + i * interval for i in range (count )]
6618+ suffix = '.%Y-%m-%d_%H' if when == 'H' else '.%Y-%m-%d'
6619+ for utc in (False , True ):
6620+ with self .subTest (when = when , start = start , utc = utc ):
6621+ convert = time .gmtime if utc else time .localtime
6622+ expected = {
6623+ time .strftime (suffix , convert (timestamp )): [str (i )]
6624+ for i , timestamp in enumerate (timestamps [:- 1 ])
6625+ }
6626+ expected ['' ] = [str (count - 1 )]
6627+ self .assert_rollover_contents (
6628+ when , timestamps , expected , utc = utc )
6629+
6630+ @support .run_with_tz ('EST+05EDT,M3.2.0,M11.1.0' )
6631+ def test_rollover_delayed_dst (self ):
6632+ DT = datetime .datetime
6633+ cases = (
6634+ ('MIDNIGHT' , None ,
6635+ [DT (2026 , 10 , day , hour ).timestamp ()
6636+ for day in (30 , 31 ) for hour in (9 , 18 )] +
6637+ [DT (2026 , 11 , day , hour ).timestamp ()
6638+ for day in (1 , 2 , 3 ) for hour in (9 , 18 )],
6639+ {'.2026-10-30' : ['0' , '1' ], '.2026-10-31' : ['2' , '3' ],
6640+ '.2026-11-01' : ['4' , '5' ], '.2026-11-02' : ['6' , '7' ],
6641+ '' : ['8' , '9' ]}),
6642+ ('W6' , None ,
6643+ [DT (2026 , 10 , 23 , 9 ).timestamp (),
6644+ DT (2026 , 11 , 1 , 9 ).timestamp (),
6645+ DT (2026 , 11 , 2 , 9 ).timestamp ()],
6646+ {'.2026-10-19' : ['0' ], '.2026-10-26' : ['1' ], '' : ['2' ]}),
6647+ ('MIDNIGHT' , datetime .time (23 , 30 ),
6648+ [DT (2026 , month , 1 , 12 ).timestamp () for month in (3 , 4 , 10 , 12 )],
6649+ {'.2026-02-28' : ['0' ], '.2026-03-31' : ['1' ],
6650+ '.2026-09-30' : ['2' ], '' : ['3' ]}),
6651+ )
6652+ for when , at_time , timestamps , expected in cases :
6653+ for utc in (False , True ):
6654+ with self .subTest (when = when , atTime = at_time , utc = utc ):
6655+ self .assert_rollover_contents (
6656+ when , timestamps , expected , atTime = at_time , utc = utc )
6657+
65876658 @unittest .skipIf (support .is_wasi , "WASI does not have /dev/null." )
65886659 def test_should_not_rollover (self ):
65896660 # See bpo-45401. Should only ever rollover regular files
0 commit comments