@@ -564,6 +564,40 @@ describe("Cron", () => {
564564 deepStrictEqual ( prev ( cron , from ) , new Date ( "2024-01-31T00:00:00.000Z" ) )
565565 } )
566566
567+ it ( "prev skips an invalid day in the immediately preceding month" , ( ) => {
568+ const tz = DateTime . zoneMakeNamedUnsafe ( "UTC" )
569+ const cron = Cron . parseUnsafe ( "0 0 31 * *" , tz )
570+ const from = new Date ( "2024-03-15T00:00:00.000Z" )
571+ deepStrictEqual ( prev ( cron , from ) , new Date ( "2024-01-31T00:00:00.000Z" ) )
572+ } )
573+
574+ it ( "prev finds the previous leap day" , ( ) => {
575+ const tz = DateTime . zoneMakeNamedUnsafe ( "UTC" )
576+ const cron = Cron . parseUnsafe ( "0 0 29 2 *" , tz )
577+ const from = new Date ( "2024-01-01T00:00:00.000Z" )
578+ deepStrictEqual ( prev ( cron , from ) , new Date ( "2020-02-29T00:00:00.000Z" ) )
579+ } )
580+
581+ it ( "prev day-of-month rollovers preserve match and ordering invariants" , ( ) => {
582+ const tz = DateTime . zoneMakeNamedUnsafe ( "UTC" )
583+ const cases = [
584+ [ Cron . parseUnsafe ( "0 0 31 * *" , tz ) , new Date ( "2024-03-15T00:00:00.000Z" ) ] ,
585+ [ Cron . parseUnsafe ( "0 0 29 2 *" , tz ) , new Date ( "2024-01-01T00:00:00.000Z" ) ]
586+ ] as const
587+
588+ for ( const [ cron , from ] of cases ) {
589+ const result = prev ( cron , from )
590+ assertTrue ( Cron . match ( cron , result ) )
591+ assertTrue ( result < from )
592+ }
593+ } )
594+
595+ it ( "prev terminates for an impossible day and month combination" , ( ) => {
596+ const tz = DateTime . zoneMakeNamedUnsafe ( "UTC" )
597+ const cron = Cron . parseUnsafe ( "0 0 30 2 *" , tz )
598+ throws ( ( ) => prev ( cron , new Date ( "2024-03-01T00:00:00.000Z" ) ) )
599+ } )
600+
567601 it ( "prev clamps to the last valid day when rolling back a month with only month constraints" , ( ) => {
568602 const tz = DateTime . zoneMakeNamedUnsafe ( "UTC" )
569603 const cron = Cron . parseUnsafe ( "0 0 0 * FEB *" , tz )
0 commit comments