@@ -14,7 +14,7 @@ require __DIR__ . '/../bootstrap.php';
1414date_default_timezone_set ('Europe/Prague ' );
1515
1616
17- test ('Basic operations ' , function () {
17+ test ('Basic operations (no DST involved) ' , function () {
1818 $ base = new DateTime ('2024-07-15 10:00:00 ' ); // Summer time (CEST)
1919
2020 $ dt = clone $ base ;
@@ -35,6 +35,68 @@ test('Basic operations', function () {
3535});
3636
3737
38+ test ('Spring DST transition (2025-03-30 02:00 -> 03:00) ' , function () {
39+ $ startSpring = new DateTime ('2025-03-30 01:45:00 ' ); // Before the jump (CET +01:00)
40+
41+ // Modification ending BEFORE the jump
42+ $ dt = clone $ startSpring ;
43+ $ dt ->modify ('+10 minutes ' );
44+ Assert::same ('2025-03-30 01:55:00 CET (+01:00) ' , $ dt ->format ('Y-m-d H:i:s T (P) ' ), '+10 min (ends before jump) ' );
45+
46+ // Modification crossing the jump (duration logic thanks to Nette fix)
47+ $ dt = clone $ startSpring ;
48+ $ dt ->modify ('+30 minutes ' ); // 01:45 CET + 30 min duration = 01:15 UTC = 03:15 CEST
49+ Assert::same ('2025-03-30 03:15:00 CEST (+02:00) ' , $ dt ->format ('Y-m-d H:i:s T (P) ' ), '+30 min (crosses jump) ' );
50+
51+ $ dt = clone $ startSpring ;
52+ $ dt ->modify ('+90 minutes ' ); // 01:45 CET + 90 min duration = 02:15 UTC = 04:15 CEST (Key test!)
53+ Assert::same ('2025-03-30 04:15:00 CEST (+02:00) ' , $ dt ->format ('Y-m-d H:i:s T (P) ' ), '+90 min (crosses jump) ' );
54+
55+ // Adding a day across the jump (day has only 23 hours)
56+ $ dt = clone $ startSpring ;
57+ $ dt ->modify ('+1 day ' );
58+ Assert::same ('2025-03-31 01:45:00 CEST (+02:00) ' , $ dt ->format ('Y-m-d H:i:s T (P) ' ), '+1 day ' );
59+
60+ // Combination of day + hours across the jump
61+ $ dt = clone $ startSpring ;
62+ $ dt ->modify ('+1 day +1 hour ' );
63+ Assert::same ('2025-03-31 02:45:00 CEST (+02:00) ' , $ dt ->format ('Y-m-d H:i:s T (P) ' ), '+1 day + 1 hour ' );
64+
65+ $ dt = clone $ startSpring ;
66+ $ dt ->modify ('+2 hours ' ); // 01:45 CET + 2h duration = 02:45 UTC = 04:45 CEST
67+ Assert::same ('2025-03-30 04:45:00 CEST (+02:00) ' , $ dt ->format ('Y-m-d H:i:s T (P) ' ), '+2 hours (crosses jump) ' );
68+ });
69+
70+
71+ test ('Autumn DST transition (2024-10-27 03:00 -> 02:00) ' , function () {
72+ $ startAutumn = new DateTime ('2024-10-27 01:45:00 ' ); // Before the fallback (CEST +02:00)
73+
74+ // Modification ending BEFORE the fallback (still CEST)
75+ $ dt = clone $ startAutumn ;
76+ $ dt ->modify ('+30 minutes ' );
77+ Assert::same ('2024-10-27 02:15:00 CEST (+02:00) ' , $ dt ->format ('Y-m-d H:i:s T (P) ' ), '+30 min (ends before fallback) ' );
78+
79+ // Modification crossing the fallback (lands in the second 2:xx hour - CET)
80+ $ dt = clone $ startAutumn ;
81+ $ dt ->modify ('+90 minutes ' ); // 01:45 CEST + 90 min duration = 01:15 UTC = 02:15 CET
82+ Assert::same ('2024-10-27 02:15:00 CET (+01:00) ' , $ dt ->format ('Y-m-d H:i:s T (P) ' ), '+90 min (crosses fallback, lands in CET) ' );
83+
84+ $ dt = clone $ startAutumn ;
85+ $ dt ->modify ('+1 hour + 30 minutes ' ); // Same as +90 minutes
86+ Assert::same ('2024-10-27 02:15:00 CET (+01:00) ' , $ dt ->format ('Y-m-d H:i:s T (P) ' ), '+1 hour + 30 minutes (crosses fallback) ' );
87+
88+ // Adding a day across the fallback (day has 25 hours)
89+ $ dt = clone $ startAutumn ;
90+ $ dt ->modify ('+1 day ' );
91+ Assert::same ('2024-10-28 01:45:00 CET (+01:00) ' , $ dt ->format ('Y-m-d H:i:s T (P) ' ), '+1 day ' );
92+
93+ // Combination of day + hours across the fallback
94+ $ dt = clone $ startAutumn ;
95+ $ dt ->modify ('+1 day +2 hours ' );
96+ Assert::same ('2024-10-28 03:45:00 CET (+01:00) ' , $ dt ->format ('Y-m-d H:i:s T (P) ' ), '+1 day + 2 hours ' );
97+ });
98+
99+
38100test ('Complex and varied format strings ' , function () {
39101 $ dt = new DateTime ('2024-04-10 12:00:00 ' ); // CEST
40102 // Expected: -2m -> 2024-02-10 12:00 CET | +7d -> 2024-02-17 12:00 CET | +23h 59m 59s -> 2024-02-18 11:59:59 CET
0 commit comments