From c183155124d3ec5feb25b13878e5476139109eff Mon Sep 17 00:00:00 2001 From: Karl Williamson Date: Fri, 14 Nov 2025 07:04:29 -0700 Subject: [PATCH 01/10] posix.t: Properly populate optional fields to strtime The next commits that fix some bugs showed these were not properly getting initialized. --- ext/POSIX/t/posix.t | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/ext/POSIX/t/posix.t b/ext/POSIX/t/posix.t index 999197aa916b..b4b82e7fb1e0 100644 --- a/ext/POSIX/t/posix.t +++ b/ext/POSIX/t/posix.t @@ -273,7 +273,12 @@ print POSIX::strftime("ok $test # %H:%M, on %m/%d/%y\n", localtime()); # input fields to strftime(). sub try_strftime { my $expect = shift; - my $got = POSIX::strftime("%a %b %d %H:%M:%S %Y %j", @_); + my @input = @_; + + # Add zeros to missing parameters. The final 0 is for isdst, and the zero + # forces use of mini_mktime (unless the code changes). + push @input, 0 while @input < 9; + my $got = POSIX::strftime("%a %b %d %H:%M:%S %Y %j", @input); is($got, $expect, "validating mini_mktime() and strftime(): $expect"); } From ae167fbe754b2c2c4b01de4fb1896a93a426a0be Mon Sep 17 00:00:00 2001 From: Karl Williamson Date: Sun, 9 Nov 2025 13:41:59 -0700 Subject: [PATCH 02/10] locale.c: Silence unused variable compiler warning On some systems this was unused. Now that we have C99, we can move the declaration and some #ifdef's and not declare it unless it is going to be used. --- locale.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/locale.c b/locale.c index 5773f9ebd48c..00fd2354b4d2 100644 --- a/locale.c +++ b/locale.c @@ -8363,20 +8363,22 @@ S_ints_to_tm(pTHX_ struct tm * mytm, mytm->tm_year = year; struct tm * which_tm = mytm; - struct tm aux_tm; #ifndef HAS_MKTIME mini_mktime(mytm); #else +# if defined(HAS_TM_TM_GMTOFF) || defined(HAS_TM_TM_ZONE) +# define ALWAYS_RUN_MKTIME + + struct tm aux_tm; + +# endif /* On platforms that have either of these two fields, we have to run the * libc mktime() in order to set them, as mini_mktime() doesn't deal with * them. [perl #18238] */ -# if defined(HAS_TM_TM_GMTOFF) || defined(HAS_TM_TM_ZONE) -# define ALWAYS_RUN_MKTIME -# endif /* When isdst is 0, it means to consider daylight savings time as never * being in effect. Many libc implementations of mktime() do not allow From 0d9d7cbef5ce1502d8de0a7497b4cae7853b2d66 Mon Sep 17 00:00:00 2001 From: Karl Williamson Date: Sun, 9 Nov 2025 14:42:00 -0700 Subject: [PATCH 03/10] locale.c: Slight comment clarification --- locale.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locale.c b/locale.c index 00fd2354b4d2..58eced9ce647 100644 --- a/locale.c +++ b/locale.c @@ -8415,7 +8415,7 @@ S_ints_to_tm(pTHX_ struct tm * mytm, const char * orig_TIME_locale = toggle_locale_c(LC_TIME, locale); MKTIME_LOCK; - /* which_tm points to an auxiliary copy if we ran mini_mktime(). + /* 'which_tm' points to an auxiliary copy if we ran mini_mktime(). * Otherwise it points to the passed-in one which now gets populated * directly. */ (void) mktime(which_tm); From 5579443d4d7a3ef038676cba70f39cb8a9cf0d1a Mon Sep 17 00:00:00 2001 From: Karl Williamson Date: Sun, 9 Nov 2025 14:44:58 -0700 Subject: [PATCH 04/10] locale.c: ints_to_tm: Fix #if's tl;dr: Fixes GH #23878 I botched this in Perl 5.42. These conditional compilation statements were just plain wrong, causing code to be skipped that should have been compiled. It only affected the few hours of the year when daylight savings time is removed, so that the hour value is repeated. We didn't have a good test for that. gory details: libc uses 'struct tm' to hold information about a given instant in time, containing fields for things like the year, month, hour, etc. The libc function mktime() is used to normalize the structure, adjusting, say, an input Nov 31 to be Dec 01. One of the fields in the structure, 'is_dst', indicates if daylight savings is in effect, or whether that fact is unknown. If unknown, mktime() is supposed to calculate the answer and to change 'is_dst' accordingly. Some implementations appear to always do this calculation even when the input value says the result is known. Others appear to honor it. Some libc implementations have extra fields in 'struct tm'. Perl has a stripped down version of mktime(), called mini_mktime(), written by Larry Wall a long time ago. I don't know why. This crippled version ignores locale and daylight time. It also doesn't know about the extra fields in 'struct tm' that some implementations have. Nor can it be extended to know about those fields, as they are dependent on timezone and daylight time, which it deliberately doesn't consider. The botched #ifdef's were supposed to compensate for both the extra fields in the struct and that some libc implementations always recalculate 'is_dst'. On systems with these fields, the botched #if's caused only mini_mktime() to be called. This meant that these extra fields didn't get populated, and daylight time is never considered to be in effect. And 'is_dst' does not get changed from the input. On systems without these fields, the regular libc mktime() would be called appropriately. The bottom line is that for the portion of the year when daylight savings is not in effect, that portion worked properly. The two extra fields would not be populated, so if some code were to read them, it would only get the proper values by chance. We got no reports of this. I attribute that to the fact that the use of these is not portable, so code wouldn't tend to use them. There are portable ways to access the information they contain. Tests were failing for the portions of the year when daylight savings is in effect; see GH #22351. The code looked correct just reading it (not seeing the flaw in the #ifdef's), so I assumed that it was an issue in the libc implementations and instituted a workaround. (I can't now think of a platform where there hasn't been a problem with a libc with something regarding locales, so that was a reasonable assumption.) Among other things (fixed in the next commit), that workaround overrode the 'is_dst' field after the call to mini_mktime(), so that the value actually passed to libc strftime() indicated that daylight is in effect. What happens next depends on the libc strftime() implementation. It could conceivably itself call mktime() which might choose to override is_dst to be the correct value, and everything would always work. The more likely possibility is that it just takes the values in the struct as-is. Remember that those values on systems with the extra fields were calculated as if daylight savings wasn't in effect, but now we're telling strftime() to use those values as if it were in effect. This is a discrepancy. I'd have to trace through some libc implementations to understand why this discrepancy seems to not matter except at the transition time. But the bottom line is this commit removes that discrepancy, and causes mktime() to be called appropriately on systems where it wasn't, so strftime() should now function properly. --- locale.c | 47 ++++++++++++++++++++++++----------------------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/locale.c b/locale.c index 58eced9ce647..e8d75b7df7b7 100644 --- a/locale.c +++ b/locale.c @@ -8271,15 +8271,8 @@ Perl_sv_strftime_ints(pTHX_ SV * fmt, int sec, int min, int hour, const char * locale = "C"; #endif - /* A negative 'isdst' triggers backwards compatibility mode for - * POSIX::strftime(), in which 0 is always passed to ints_to_tm() so that - * the possibility of daylight savings time is never considered, But, a 1 - * is eventually passed to libc strftime() so that it returns the results - * it always has for a non-zero 'isdst'. See GH #22351 */ struct tm mytm; - ints_to_tm(&mytm, locale, sec, min, hour, mday, mon, year, - MAX(0, isdst)); - mytm.tm_isdst = MIN(1, abs(isdst)); + ints_to_tm(&mytm, locale, sec, min, hour, mday, mon, year, isdst); return sv_strftime_common(fmt, locale, &mytm); } @@ -8387,30 +8380,35 @@ S_ints_to_tm(pTHX_ struct tm * mytm, if (isdst == 0) { mini_mktime(mytm); -# ifndef ALWAYS_RUN_MKTIME - - /* When we don't always need libc mktime(), we call it only when we didn't - * call mini_mktime() */ - } else { +# ifdef ALWAYS_RUN_MKTIME -# else /* Here will have to run libc mktime() in order to get the values of * some fields that mini_mktime doesn't populate. We don't want - * mktime's side effect of looking for dst, so we have to have a - * separate tm structure from which we copy just those fields into the - * returned structure. Initialize its values. mytm should now be a - * normalized version of the input. */ + * mktime's side effect of looking for dst (because isdst==0), so we + * have to have a separate tm structure from which we copy just those + * fields into the structure we return. Initialize its values, which + * have now been normalized by mini_mktime. */ aux_tm = *mytm; - aux_tm.tm_isdst = isdst; which_tm = &aux_tm; +# endif + + } + +# ifndef ALWAYS_RUN_MKTIME + + else { /* Don't run libc mktime if both: + 1) we ran mini_mktime above; and + 2) we don't have to always run libc mktime */ + # endif /* Here, we need to run libc mktime(), either because we want to take * dst into consideration, or because it calculates one or two fields - * that we need that mini_mktime() doesn't handle. - * - * Unlike mini_mktime(), it does consider the locale, so have to switch + * that we need that mini_mktime() doesn't handle. */ + which_tm->tm_isdst = isdst; + + /* Unlike mini_mktime(), it does consider the locale, so have to switch * to the correct one. */ const char * orig_TIME_locale = toggle_locale_c(LC_TIME, locale); MKTIME_LOCK; @@ -8422,9 +8420,12 @@ S_ints_to_tm(pTHX_ struct tm * mytm, MKTIME_UNLOCK; restore_toggled_locale_c(LC_TIME, orig_TIME_locale); + +# ifndef ALWAYS_RUN_MKTIME + } -# if defined(HAS_TM_TM_GMTOFF) || defined(HAS_TM_TM_ZONE) +# else /* And use the saved libc values for tm_gmtoff and tm_zone if we used an * auxiliary struct to get them */ From d2226d2b6792f4b983fcb9005309f68d8904a395 Mon Sep 17 00:00:00 2001 From: Karl Williamson Date: Fri, 14 Nov 2025 06:54:48 -0700 Subject: [PATCH 05/10] my_strftime(): Properly take daylight savings into account Because of the bug fixed in the previous commit, this function was changed in 5.42 to have a work around, which is no longer needed. --- locale.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/locale.c b/locale.c index e8d75b7df7b7..e1af38e0a36c 100644 --- a/locale.c +++ b/locale.c @@ -8242,7 +8242,6 @@ Perl_my_strftime(pTHX_ const char *fmt, int sec, int min, int hour, PERL_ARGS_ASSERT_MY_STRFTIME; PERL_UNUSED_ARG(wday); PERL_UNUSED_ARG(yday); - PERL_UNUSED_ARG(isdst); #ifdef USE_LOCALE_TIME const char * locale = querylocale_c(LC_TIME); @@ -8251,7 +8250,7 @@ Perl_my_strftime(pTHX_ const char *fmt, int sec, int min, int hour, #endif struct tm mytm; - ints_to_tm(&mytm, locale, sec, min, hour, mday, mon, year, 0); + ints_to_tm(&mytm, locale, sec, min, hour, mday, mon, year, isdst); if (! strftime_tm(fmt, PL_scratch_langinfo, locale, &mytm)) { return NULL; } From 02ab42751d852722ade7a2db84c307786905d2e8 Mon Sep 17 00:00:00 2001 From: Karl Williamson Date: Fri, 14 Nov 2025 06:57:29 -0700 Subject: [PATCH 06/10] POSIX.xs: Properly take daylight savings into account Because of the bug fixed two commits ago, this function was changed in 5.42 to have a work around, which is no longer needed. --- ext/POSIX/POSIX.xs | 6 ++---- ext/POSIX/lib/POSIX.pm | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/ext/POSIX/POSIX.xs b/ext/POSIX/POSIX.xs index d2b4167a4a96..a0faa37bb817 100644 --- a/ext/POSIX/POSIX.xs +++ b/ext/POSIX/POSIX.xs @@ -3589,7 +3589,7 @@ difftime(time1, time2) # sv_setpv(TARG, ...) could be used rather than # ST(0) = sv_2mortal(newSVpv(...)) void -strftime(fmt, sec, min, hour, mday, mon, year, wday = -1, yday = -1, isdst = 0) +strftime(fmt, sec, min, hour, mday, mon, year, wday = -1, yday = -1, isdst = -1) SV * fmt int sec int min @@ -3605,10 +3605,8 @@ strftime(fmt, sec, min, hour, mday, mon, year, wday = -1, yday = -1, isdst = 0) PERL_UNUSED_ARG(wday); PERL_UNUSED_ARG(yday); - /* -isdst triggers backwards compatibility mode for non-zero - * 'isdst' */ SV *sv = sv_strftime_ints(fmt, sec, min, hour, mday, mon, year, - -abs(isdst)); + isdst); if (sv) { sv = sv_2mortal(sv); } diff --git a/ext/POSIX/lib/POSIX.pm b/ext/POSIX/lib/POSIX.pm index 48abaf971d02..a49ac5501eaa 100644 --- a/ext/POSIX/lib/POSIX.pm +++ b/ext/POSIX/lib/POSIX.pm @@ -4,7 +4,7 @@ use warnings; our ($AUTOLOAD, %SIGRT); -our $VERSION = '2.24'; +our $VERSION = '2.25'; require XSLoader; From 00f3325fe28f425bae958489642066c2d5fbda1b Mon Sep 17 00:00:00 2001 From: Karl Williamson Date: Tue, 11 Nov 2025 16:11:19 -0700 Subject: [PATCH 07/10] POSIX/t/time.t: Add tests for DST changes --- ext/POSIX/t/time.t | 65 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 64 insertions(+), 1 deletion(-) diff --git a/ext/POSIX/t/time.t b/ext/POSIX/t/time.t index 0d52ac5b2481..f840baedec45 100644 --- a/ext/POSIX/t/time.t +++ b/ext/POSIX/t/time.t @@ -9,7 +9,7 @@ use strict; use Config; use POSIX; -use Test::More tests => 31; +use Test::More tests => 49; # For the first go to UTC to avoid DST issues around the world when testing. SUS3 says that # null should get you UTC, but some environments want the explicit names. @@ -226,3 +226,66 @@ SKIP: { is(strftime(undef, CORE::localtime), '', "strftime() works if format is undef"); like($warnings, qr/^Use of uninitialized value in subroutine entry /, "strftime(undef, ...) produces expected warning"); } + +SKIP: { # GH #23878; test that dst fall back works properly + my $skip_count = 9; + skip "No mktime()", $skip_count if $Config{d_mktime} ne 'define'; + my $locale = "Europe/Paris"; + $ENV{TZ} = $locale; + my $t = 1761436800; # an hour before time should have changed + + # The time in the first test case is such that UTC gives a different day. + # If the locale above is unknown, libc is supposed to use UTC; so that's + # how we check if the system knows about the rules for Paris; which on + # some systems differ from plain CET-1CEST. + skip "'$locale' not understood", $skip_count if + POSIX::strftime("%F %T%z", localtime($t - 1)) !~ /2025-10-26/; + + # On Windows, the documentation says that it won't understand what our + # $locale is set to, but instead of falling back to UTC, it uses the + # system timezone, or U.S. Pacific time. The trick above therefore + # doesn't work, so just skip this batch of tests. + skip "'$locale' not understood", $skip_count if $^O eq "MSWin32"; + + my @fall = ( + [ -1, "2025-10-26 01:59:59+0200", "Chg -1 hr, 1 sec" ], + [ 0, "2025-10-26 02:00:00+0200", "Chg -1 hr, 0 sec" ], + [ 1, "2025-10-26 02:00:01+0200", "Chg -59 min, 59 sec" ], + [ 3599, "2025-10-26 02:59:59+0200", "Chg -1 sec" ], + [ 3600, "2025-10-26 02:00:00+0100", "At Paris DST fallback" ], + [ 3601, "2025-10-26 02:00:01+0100", "Chg +1 sec" ], + [ 7199, "2025-10-26 02:59:59+0100", "Chg +1 hr, 59m, 59s" ], + [ 7200, "2025-10-26 03:00:00+0100", "Chg +1 hr" ], + [ 7201, "2025-10-26 03:00:01+0100", "Chg +1 hr, 1 sec" ], + ); + for (my $i = 0; $i < @fall; $i++) { + is(POSIX::strftime("%F %T%z", localtime $t + $fall[$i][0]), + $fall[$i][1], $fall[$i][2]); + } +} + +SKIP: { # GH #23878: test that dst spring forward works properly; use a + # locale that MS narcissism should be able to handle + my $skip_count = 9; + skip "No mktime()", $skip_count if $Config{d_mktime} ne 'define'; + my $locale = "PST8PDT"; + $ENV{TZ} = $locale; + my $t = 1741510800; # an hour before time should have changed + + my @spring = ( + [ -1, "2025-03-09 00:59:59-0800", "Chg -1 hr, 1 sec" ], + [ 0, "2025-03-09 01:00:00-0800", "Chg -1 hr, 0 sec" ], + [ 1, "2025-03-09 01:00:01-0800", "Chg -59 min, 59 sec" ], + [ 3599, "2025-03-09 01:59:59-0800", "Chg -1 sec" ], + [ 3600, "2025-03-09 03:00:00-0700", + "At Redmond DST spring forward" ], + [ 3601, "2025-03-09 03:00:01-0700", "Chg +1 sec" ], + [ 7199, "2025-03-09 03:59:59-0700", "Chg +1 hr, 59m, 59s" ], + [ 7200, "2025-03-09 04:00:00-0700", "Chg +1 hr" ], + [ 7201, "2025-03-09 04:00:01-0700", "Chg +1 hr, 1 sec" ], + ); + for (my $i = 0; $i < @spring; $i++) { + is(POSIX::strftime("%F %T%z", localtime $t + $spring[$i][0]), + $spring[$i][1], $spring[$i][2]); + } +} From add3ea7c86e37f1b9689ac1aadd27001582c60af Mon Sep 17 00:00:00 2001 From: Karl Williamson Date: Sun, 9 Nov 2025 14:49:01 -0700 Subject: [PATCH 08/10] perlapi: Add extensive strftime documentation Due to the differences in various systems' implementations, I think it is a good idea to more fully document the vagaries I have discovered, and how perl resolves them. --- ext/POSIX/lib/POSIX.pod | 44 ++---------- locale.c | 155 ++++++++++++++++++++++++++++++---------- 2 files changed, 125 insertions(+), 74 deletions(-) diff --git a/ext/POSIX/lib/POSIX.pod b/ext/POSIX/lib/POSIX.pod index 696ef8a80a06..ae6a02ea837b 100644 --- a/ext/POSIX/lib/POSIX.pod +++ b/ext/POSIX/lib/POSIX.pod @@ -1866,49 +1866,17 @@ Identical to the string form of C<$!>, see L. =item C Convert date and time information to string based on the current -underlying locale of the program (except for any daylight savings time). -Returns the string. +underlying locale of the program. +Returns the string in a mortalized SV; set to an empty string on error. -Synopsis: - - strftime(fmt, sec, min, hour, mday, mon, year, - wday = -1, yday = -1, isdst = 0) - -The month (C) begins at zero, -I, January is 0, not 1. The -year (C) is given in years since 1900, I, the year 1995 is 95; the -year 2001 is 101. Consult your system's C manpage for details -about these and the other arguments. + my $sv = strftime(fmt, sec, min, hour, mday, mon, year, + wday = -1, yday = -1, isdst = -1) The C and C parameters are both ignored. Their values are always determinable from the other parameters. -C should be C<1> or C<0>, depending on whether or not daylight -savings time is in effect for the given time or not. - -If you want your code to be portable, your format (C) argument -should use only the conversion specifiers defined by the ANSI C -standard (C99, to play safe). These are C. -But even then, the B of some of the conversion specifiers are -non-portable. For example, the specifiers C change according -to the locale settings of the user, and both how to set locales (the -locale names) and what output to expect are non-standard. -The specifier C changes according to the timezone settings of the -user and the timezone computation rules of the operating system. -The C specifier is notoriously unportable since the names of -timezones are non-standard. Sticking to the numeric specifiers is the -safest route. - -The arguments, except for C, are made consistent as though by -calling C before calling your system's C function. -To get correct results, you must set C to be the proper value. -When omitted, the function assumes daylight savings is not in effect. - -The string for Tuesday, December 12, 1995 in the C locale. - - $str = POSIX::strftime( "%A, %B %d, %Y", - 0, 0, 0, 12, 11, 95, 2 ); - print "$str\n"; +More details on the behavior and the specification of the other +parameters are described in L. =item C diff --git a/locale.c b/locale.c index e1af38e0a36c..4f0a255f26ad 100644 --- a/locale.c +++ b/locale.c @@ -8154,11 +8154,12 @@ S_maybe_override_codeset(pTHX_ const char * codeset, /* =for apidoc_section $time -=for apidoc sv_strftime_tm -=for apidoc_item sv_strftime_ints +=for apidoc sv_strftime_ints +=for apidoc_item sv_strftime_tm =for apidoc_item my_strftime -These implement the libc strftime(). +These implement libc strftime(), overcoming various deficiencies it has; you +will come to regret sooner or later using it directly instead of these. On failure, they return NULL, and set C to C. @@ -8167,70 +8168,152 @@ handle the UTF-8ness of the current locale, the input C, and the returned result. Only if the current C locale is a UTF-8 one (and S> is not in effect) will the result be marked as UTF-8. +For these, the caller assumes ownership of the returned SV with a reference +count of 1. + C is kept for backwards compatibility. Knowing if its result should be considered UTF-8 or not requires significant extra logic. Note that all three functions are always executed in the underlying C locale of the program, giving results based on that locale. -The functions differ as follows: - -C takes a pointer to a filled-in S> parameter. It -ignores the values of the C and C fields in it. The other fields -give enough information to accurately calculate these values, and are used for -that purpose. +The stringified C parameter in all is the same as the system libc +C. The available conversion specifications vary by platform. These +days, every specification listed in the ANSI C99 standard should be usable +everywhere. These are C. -The caller assumes ownership of the returned SV with a reference count of 1. +But note that the B of some of the conversion specifiers are +non-portable. For example, the specifiers C change according +to the locale settings of the user, and both how to set locales (the +locale names) and what output to expect are not standardized. +The specifier C changes according to the timezone settings of the +user and the timezone computation rules of the operating system. +The C specifier is notoriously unportable since the names of +timezones are not standardized. Sticking to the numeric specifiers is the +safest route. -C takes a bunch of integer parameters that together -completely define a given time. It calculates the S> to pass to -libc strftime(), and calls that function. +At the time of this writing, for example, C<%s> is not available on +Windows-like systems. -The value of C is used as follows: +The functions differ as follows: =over -=item 0 +=item * -No daylight savings time is in effect +The C parameter and the return from C are S> +instead of the S> in the other two functions. This means the +UTF-8ness of the format and result are unspecified. The result MUST be +arranged to be FREED BY THE CALLER). -=item E0 +=item * -Check if daylight savings time is in effect, and adjust the results -accordingly. +C and C take a bunch of integer parameters that +together completely define a given time. They calculate the S> +to pass to libc strftime(), and call that function. See below for the meaning +of the parameters. -=item E0 +C takes a pointer to an already filled-in S> +parameter, so avoids that calculation. -This value is reserved for internal use by the L module for backwards -compatibility purposes. +=item * -=back +C takes two extra parameters that are ignored, being kept only +for historical reasons. These are C and C. -The caller assumes ownership of the returned SV with a reference count of 1. +=back -C is like C except that: +The C99 Standard calls for S> to contain at least these fields: + + int tm_sec; // seconds after the minute — [0, 60] + int tm_min; // minutes after the hour — [0, 59] + int tm_hour; // hours since midnight — [0, 23] + int tm_mday; // day of the month — [1, 31] + int tm_mon; // months since January — [0, 11] + int tm_year; // years since 1900 + int tm_wday; // days since Sunday — [0, 6] + int tm_yday; // days since January 1 — [0, 365] + int tm_isdst; // Daylight Saving Time flag + +C and C are output only; the other fields give enough +information to accurately calculate these, and are internally used for that +purpose. + +The numbers enclosed in the square brackets above give the maximum legal +ranges for values in the corresponding field. Those ranges are restricted for +some inputs. For example, not all months have 31 days, but all hours have 60 +minutes. If you set a number that is outside the corresponding range, perl +and the libc functions will automatically normalize it to be inside the range, +adjusting other values as necessary. For example, specifying February 29, is +the same as saying March 1 for non-leap years; and using a minute value of 60 +will instead change that to a 0, and increment the hour, which in turn, if the +hour was 23, will roll it over to 0 it and increment the day, and so on. + +Each parameter to C and C populates the +similarly-named field in this structure. + +A value of 60 is legal for C, but only for those moments when an +official leap second has been declared. It is undefined behavior to use them +otherwise, and the behavior does vary depending on the implementation. +Some implementations take your word for it that this is a leap second, leaving +it as the 61st second of the given minute; some roll it over to be the 0th +second of the following minute; some treat it as 0. Some non-conforming +implementations always roll it over to the next minute, regardless of whether +an actual leap second is occurring or not. (And yes, it is a real problem +that different computers have a different conception of what the current time +is; you can search the internet for details.) + +There is no limit (outside the size of C) for the value of C, +but sufficiently negative values (for earlier than 1900) may have different +results on different systems and locales. Some libc implementations may know +when a given locale adopted the Greorian calendar, and adjust for that. +Others will not. (And some countries didn't adopt the Gregorian calendar +until after 1900.) + +The treatment of the C field has varied over previous Perl versions, +and has been buggy (both by perl and by some libc implementations), but is now +aligned, as best we can, with the POSIX Standard, as follows: =over -=item The C parameter and the return are S> instead of -S>. +=item C is 0 -This means the UTF-8ness of the result is unspecified. The result MUST be -arranged to be FREED BY THE CALLER). +The function is to assume that daylight savings time is not in effect. This +should now always work properly, as perl uses its own implementation in this +case, avoiding non-conforming libc ones. -=item The C parameter is ignored. +=item C is E0 -Daylight savings time is never considered to be in effect. +The function is to assume that daylight savings time is in effect, though some +underlying libc implementations treat this as a hint instead of a mandate. -=item It has extra parameters C and C that are ignored. +=item C is E0 -These exist only for historical reasons; the values for the corresponding -fields in S> are calculated from the other arguments. +The function is to itself try to calculate if daylight savings time is in +effect. More recent libc implementations are better at this than earlier +ones. =back -Note that all three functions are always executed in the underlying C -locale of the program, giving results based on that locale. +Some libc implementations have extra fields in S>. The two that +perl handles are: + + int tm_gmtoff; // Seconds East of UTC [%z] + const char * tm_zone; // Timezone abbreviation [%Z] + +These are both output only. Using the respective conversion specifications +(enclosed in the square brackets) in the C parameter is a portable way to +gain access to these values, working both on systems that have and don't have +these fields. + +Example, in the C locale: + + my_strftime( "%A, %B %d, %Y", 0, 0, 0, 12, 11, 95, 0, 0, -1 ); + +returns + + "Tuesday, December 12, 1995" + =cut */ From 0cb1f8d567b0e3111ec9aa8061e20f760bf0b93a Mon Sep 17 00:00:00 2001 From: Karl Williamson Date: Tue, 11 Nov 2025 16:20:33 -0700 Subject: [PATCH 09/10] perldelta for GH #23878 --- pod/perldelta.pod | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/pod/perldelta.pod b/pod/perldelta.pod index 6f2168f0ec52..1da388c93cd6 100644 --- a/pod/perldelta.pod +++ b/pod/perldelta.pod @@ -396,6 +396,16 @@ the first branch would correctly autovivify C<$h{foo}> to an array ref, but the second branch might incorrectly autovivify C<$h{bar}> to a hash ref. [GH #18669]. +=item * + +Perl 5.42.0 does not handle the transition to/from daylight savings time +properly. The time and/or timezone can be off by an hour in the +intervals surrounding such transitions. This is a regression from +earlier releases, and is now fixed. This bug was evident from perl +space in the L function, and in XS code with any of +L, L, or +L. + =back =head1 Known Problems From 61bab2c12a3133bcb925542a4f3ed0a9c38c11e5 Mon Sep 17 00:00:00 2001 From: Karl Williamson Date: Sat, 15 Nov 2025 14:41:51 -0700 Subject: [PATCH 10/10] tzset --- ext/POSIX/t/time.t | 1 + 1 file changed, 1 insertion(+) diff --git a/ext/POSIX/t/time.t b/ext/POSIX/t/time.t index f840baedec45..86f370d4d8aa 100644 --- a/ext/POSIX/t/time.t +++ b/ext/POSIX/t/time.t @@ -270,6 +270,7 @@ SKIP: { # GH #23878: test that dst spring forward works properly; use a skip "No mktime()", $skip_count if $Config{d_mktime} ne 'define'; my $locale = "PST8PDT"; $ENV{TZ} = $locale; + POSIX::tzset(); my $t = 1741510800; # an hour before time should have changed my @spring = (