diff --git a/.github/workflows/testsuite.yml b/.github/workflows/testsuite.yml index d58a9f53178f..7d366ae504ee 100644 --- a/.github/workflows/testsuite.yml +++ b/.github/workflows/testsuite.yml @@ -353,7 +353,7 @@ jobs: git config diff.renameLimit 999999 - name: Configure run: | - ./Configure -des -Dusedevel ${{ matrix.CONFIGURE_ARGS }} -Dprefix="$HOME/perl-blead" + ./Configure -des -DDEBUGGING -Dusedevel ${{ matrix.CONFIGURE_ARGS }} -Dprefix="$HOME/perl-blead" - name: Build run: | MALLOC_PERTURB_=254 MALLOC_CHECK_=3 make -j2 diff --git a/ext/POSIX/POSIX.xs b/ext/POSIX/POSIX.xs index d2b4167a4a96..4e6d5fdc635b 100644 --- a/ext/POSIX/POSIX.xs +++ b/ext/POSIX/POSIX.xs @@ -1407,9 +1407,6 @@ char *tzname[] = { "" , "" }; # endif # ifdef __MINGW32__ # define mode_t short -# ifndef tzset -# define tzset() not_here("tzset") -# endif # ifndef _POSIX_OPEN_MAX # define _POSIX_OPEN_MAX FOPEN_MAX /* XXX bogus ? */ # endif @@ -3589,7 +3586,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 +3602,13 @@ 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' */ + struct tm mytm; + time_t then = 1741510800 - 1; + mytm = *localtime(&then); + DEBUG_Lv(PerlIO_printf(Perl_debug_log, "Return from localtime for %ld= tm_sec=%d, tm_min=%d, tm_hour=%d, tm_mday=%d, tm_mon=%d, tm_year=%d, isdst=%d gmtoff=%%ld, tm_zone=%%s\n", then, mytm.tm_sec, mytm.tm_min, mytm.tm_hour, mytm.tm_mday, mytm.tm_mon, mytm.tm_year, mytm.tm_isdst)); //, mytm.tm_gmtoff, mytm.tm_zone)); + DEBUG_Lv(PerlIO_printf(Perl_debug_log, "POSIX::strftime: Calling ints_to_tm: tm_sec=%d, tm_min=%d, tm_hour=%d, tm_mday=%d, tm_mon=%d, tm_year=%d, isdst=%d\n", sec, min, hour, mday, mon, year, 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; 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/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"); } diff --git a/ext/POSIX/t/time.t b/ext/POSIX/t/time.t index 0d52ac5b2481..bc386832366c 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. @@ -18,12 +18,11 @@ $ENV{TZ} = "EST5EDT"; SKIP: { # It looks like POSIX.xs claims that only VMS and Mac OS traditional - # don't have tzset(). Win32 works to call the function, but it doesn't + # don't have tzset(). Win32 XXX works to call the function, but it doesn't # actually do anything. Cygwin works in some places, but not others. The # other Win32's below are guesses. skip "No tzset()", 1 - if $^O eq "VMS" || $^O eq "cygwin" || - $^O eq "MSWin32" || $^O eq "interix"; + if $^O eq "VMS" || $^O eq "cygwin" || $^O eq "interix"; tzset(); SKIP: { my @tzname = tzname(); @@ -43,8 +42,7 @@ $ENV{TZ} = "UTC0UTC"; SKIP: { skip "No tzset()", 2 - if $^O eq "VMS" || $^O eq "cygwin" || - $^O eq "MSWin32" || $^O eq "interix"; + if $^O eq "VMS" || $^O eq "cygwin" || $^O eq "interix"; tzset(); my @tzname = tzname(); like($tzname[0], qr/(GMT|UTC)/i, "tzset() to GMT/UTC"); @@ -226,3 +224,93 @@ 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; + print STDERR __FILE__, ": ", __LINE__, ": about to call tzset\n"; + POSIX::tzset(); + print STDERR __FILE__, ": ", __LINE__, ": finished with tzset\n"; + my $from_gmt = 8 * 60 * 60; + my $t = 1741510800 - $from_gmt; # an hour before time should have changed + my $t_minus_12_hours = $t - 12 * 60 * 60;; + print STDERR __FILE__, ": ", __LINE__, ": calling offset for $t_minus_12_hours\n"; + use Data::Dumper; + print STDERR __FILE__, ": ", __LINE__, ": before getting offset\n", Dumper localtime $t_minus_12_hours; + my $offset = POSIX::strftime("%z", localtime $t_minus_12_hours); + print STDERR __FILE__, ": ", __LINE__, ": after getting offset\n", Dumper localtime $t_minus_12_hours; + print STDERR __FILE__, ": ", __LINE__, ": offset is $offset; calling zone\n"; + my $zone_name = POSIX::strftime("%Z", localtime $t_minus_12_hours); + print STDERR __FILE__, ": ", __LINE__, ": zone name is $zone_name\n"; + skip "Platform doesn't recognize timezone '$locale'" + if $zone_name !~ /PST/ || $offset !~ /0800/; + + 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" ], + ); + $^D |= 0x04000000|0x00100000 ; + for (my $i = 0; $i < @spring; $i++) { + my @gmtime = gmtime($t + $spring[$i][0]); + print STDERR __FILE__, ": ", __LINE__, ": calling strftime with\n", Dumper $spring[$i], \@gmtime; + $gmtime[-1] = -1; + my $returned_name = POSIX::strftime("%Z", @gmtime); + print STDERR __FILE__, ": ", __LINE__, ": zone=$zone_name, returned=$returned_name\n"; + if ($zone_name ne $returned_name) { + @gmtime = gmtime($t + 3600 + $spring[$i][0]); + $gmtime[-1] = 1; + } + print STDERR __FILE__, ": ", __LINE__, ": calling strftime with\n", Dumper $spring[$i], \@gmtime; + is(POSIX::strftime("%F %T%z", @gmtime), + $spring[$i][1], $spring[$i][2]); + } + $^D &= ~(67108864|1048576 ); +} diff --git a/locale.c b/locale.c index 5773f9ebd48c..8f2a98d0b714 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,154 @@ 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.) Probably all implementations assume modern time zones go +back forever, before they were actually invented, starting in the last half of +the 19th century. + +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 */ @@ -8242,7 +8327,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 +8335,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; } @@ -8271,15 +8355,13 @@ 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)); + time_t then = 1741510800 - 1; + mytm = *localtime(&then); + DEBUG_Lv(PerlIO_printf(Perl_debug_log, "sv_strftime_ints: Calling ints_to_tm: tm_sec=%d, tm_min=%d, tm_hour=%d, tm_mday=%d, tm_mon=%d, tm_year=%d, isdst=%d\n", sec, min, hour, mday, mon, year, isdst)); + ints_to_tm(&mytm, locale, sec, min, hour, mday, mon, year, isdst); + DEBUG_Lv(PerlIO_printf(Perl_debug_log, "Return from ints_to_tm: tm_sec=%d, tm_min=%d, tm_hour=%d, tm_mday=%d, tm_mon=%d, tm_year=%d, isdst=%d\n", mytm.tm_sec, mytm.tm_min, mytm.tm_hour, mytm.tm_mday, mytm.tm_mon, mytm.tm_year, mytm.tm_isdst)); + DEBUG_Lv(PerlIO_printf(Perl_debug_log, "Calling sv_strftime_common\n")); return sv_strftime_common(fmt, locale, &mytm); } @@ -8321,6 +8403,7 @@ S_sv_strftime_common(pTHX_ SV * fmt, * having to realloc; this is a somewhat educated guess, but feel free to * tweak it. */ SV* sv = newSVpvz(MAX(fmt_cur * 2, 64)); + DEBUG_Lv(PerlIO_printf(Perl_debug_log, "Calling strftime8\n")); if (! strftime8(fmt_str, sv, locale, @@ -8336,6 +8419,7 @@ S_sv_strftime_common(pTHX_ SV * fmt, if (result_utf8ness == UTF8NESS_YES) { SvUTF8_on(sv); } + DEBUG_Lv(PerlIO_printf(Perl_debug_log, "return from strftime8\n")); return sv; } @@ -8363,66 +8447,86 @@ S_ints_to_tm(pTHX_ struct tm * mytm, mytm->tm_year = year; struct tm * which_tm = mytm; - struct tm aux_tm; + DEBUG_Lv(PerlIO_printf(Perl_debug_log, "ints_to_tm entry: tm_sec=%d, tm_min=%d, tm_hour=%d, tm_mday=%d, tm_mon=%d, tm_year=%d, isdst=%d\n", mytm->tm_sec, mytm->tm_min, mytm->tm_hour, mytm->tm_mday, mytm->tm_mon, mytm->tm_year, mytm->tm_isdst)); #ifndef HAS_MKTIME mini_mktime(mytm); + DEBUG_Lv(PerlIO_printf(Perl_debug_log, "Doesn't have mktime\n")); #else +# if defined(HAS_TM_TM_GMTOFF) || defined(HAS_TM_TM_ZONE) +# define ALWAYS_RUN_MKTIME + DEBUG_Lv(PerlIO_printf(Perl_debug_log, "always run mktime\n")); + + 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 * this; they always consider the possibility of dst. But mini_mktime() * never considers dst, so use it under this condition. */ if (isdst == 0) { + DEBUG_Lv(PerlIO_printf(Perl_debug_log, "Before mini_mktime: tm_sec=%d, tm_min=%d, tm_hour=%d, tm_mday=%d, tm_mon=%d, tm_year=%d, isdst=%d\n", mytm->tm_sec, mytm->tm_min, mytm->tm_hour, mytm->tm_mday, mytm->tm_mon, mytm->tm_year, mytm->tm_isdst)); mini_mktime(mytm); + DEBUG_Lv(PerlIO_printf(Perl_debug_log, "After mini_mktime: tm_sec=%d, tm_min=%d, tm_hour=%d, tm_mday=%d, tm_mon=%d, tm_year=%d, isdst=%d\n", mytm->tm_sec, mytm->tm_min, mytm->tm_hour, mytm->tm_mday, mytm->tm_mon, mytm->tm_year, mytm->tm_isdst)); -# 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. */ + DEBUG_Lv(PerlIO_printf(Perl_debug_log, "Before setting isdst: tm_sec=%d, tm_min=%d, tm_hour=%d, tm_mday=%d, tm_mon=%d, tm_year=%d, isdst=%d\n", mytm->tm_sec, mytm->tm_min, mytm->tm_hour, mytm->tm_mday, mytm->tm_mon, mytm->tm_year, mytm->tm_isdst)); + which_tm->tm_isdst = isdst; + DEBUG_Lv(PerlIO_printf(Perl_debug_log, "After setting isdst: tm_sec=%d, tm_min=%d, tm_hour=%d, tm_mday=%d, tm_mon=%d, tm_year=%d, isdst=%d\n", mytm->tm_sec, mytm->tm_min, mytm->tm_hour, mytm->tm_mday, mytm->tm_mon, mytm->tm_year, mytm->tm_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; - /* 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); MKTIME_UNLOCK; restore_toggled_locale_c(LC_TIME, orig_TIME_locale); + DEBUG_Lv(PerlIO_printf(Perl_debug_log, "After mktime: tm_sec=%d, tm_min=%d, tm_hour=%d, tm_mday=%d, tm_mon=%d, tm_year=%d, isdst=%d\n", mytm->tm_sec, mytm->tm_min, mytm->tm_hour, mytm->tm_mday, mytm->tm_mon, mytm->tm_year, mytm->tm_isdst)); + +# ifndef ALWAYS_RUN_MKTIME + } + DEBUG_Lv(PerlIO_printf(Perl_debug_log, "Finished : tm_sec=%d, tm_min=%d, tm_hour=%d, tm_mday=%d, tm_mon=%d, tm_year=%d, isdst=%d\n", mytm->tm_sec, mytm->tm_min, mytm->tm_hour, mytm->tm_mday, mytm->tm_mon, mytm->tm_year, mytm->tm_isdst)); -# if defined(HAS_TM_TM_GMTOFF) || defined(HAS_TM_TM_ZONE) +# else + DEBUG_Lv(PerlIO_printf(Perl_debug_log, "About to set other fields : tm_sec=%d, tm_min=%d, tm_hour=%d, tm_mday=%d, tm_mon=%d, tm_year=%d, isdst=%d\n", mytm->tm_sec, mytm->tm_min, mytm->tm_hour, mytm->tm_mday, mytm->tm_mon, mytm->tm_year, mytm->tm_isdst)); /* And use the saved libc values for tm_gmtoff and tm_zone if we used an * auxiliary struct to get them */ @@ -8441,6 +8545,7 @@ S_ints_to_tm(pTHX_ struct tm * mytm, # undef ALWAYS_RUN_MKTIME #endif + DEBUG_Lv(PerlIO_printf(Perl_debug_log, "returning: tm_sec=%d, tm_min=%d, tm_hour=%d, tm_mday=%d, tm_mon=%d, tm_year=%d, isdst=%d\n", mytm->tm_sec, mytm->tm_min, mytm->tm_hour, mytm->tm_mday, mytm->tm_mon, mytm->tm_year, mytm->tm_isdst)); return; } @@ -8451,6 +8556,7 @@ S_strftime_tm(pTHX_ const char *fmt, const struct tm *mytm) { PERL_ARGS_ASSERT_STRFTIME_TM; + DEBUG_Lv(PerlIO_printf(Perl_debug_log, "entering strftime_tm\n")); /* Execute strftime() based on the input struct tm, and the current LC_TIME * locale. @@ -8526,6 +8632,7 @@ S_strftime_tm(pTHX_ const char *fmt, #else STRFTIME_LOCK; + DEBUG_Lv(PerlIO_printf(Perl_debug_log, "calling strftime\n")); Size_t len = strftime(buf, bufsize, fmt, mytm); STRFTIME_UNLOCK; #endif @@ -8540,6 +8647,7 @@ S_strftime_tm(pTHX_ const char *fmt, if (inRANGE(len, 1, bufsize - 1)) { succeeded = true; SvCUR_set(sv, len); + DEBUG_Lv(PerlIO_printf(Perl_debug_log, "strftime had enough room\n")); goto strftime_return; } @@ -8608,6 +8716,7 @@ S_strftime8(pTHX_ const char * fmt, const bool called_externally) { PERL_ARGS_ASSERT_STRFTIME8; + DEBUG_Lv(PerlIO_printf(Perl_debug_log, "entering strftime8\n")); /* Wrap strftime_tm, taking into account the input and output UTF-8ness */ @@ -8627,9 +8736,11 @@ S_strftime8(pTHX_ const char * fmt, switch (fmt_utf8ness) { case UTF8NESS_IMMATERIAL: + DEBUG_Lv(PerlIO_printf(Perl_debug_log, "immaterial\n")); break; case UTF8NESS_NO: /* Known not to be UTF-8; must not be UTF-8 locale */ + DEBUG_Lv(PerlIO_printf(Perl_debug_log, "calling is_locale_utf8\n")); if (is_locale_utf8(locale)) { SET_EINVAL; return false; @@ -8640,6 +8751,7 @@ S_strftime8(pTHX_ const char * fmt, case UTF8NESS_YES: /* Known to be UTF-8; must be UTF-8 locale if can't downgrade. */ + DEBUG_Lv(PerlIO_printf(Perl_debug_log, "calling is_locale_utf8\n")); if (is_locale_utf8(locale)) { locale_utf8ness = LOCALE_IS_UTF8; } @@ -8657,6 +8769,7 @@ S_strftime8(pTHX_ const char * fmt, break; case UTF8NESS_UNKNOWN: + DEBUG_Lv(PerlIO_printf(Perl_debug_log, "calling is_locale_utf8\n")); if (! is_locale_utf8(locale)) { locale_utf8ness = LOCALE_NOT_UTF8; } @@ -8678,6 +8791,7 @@ S_strftime8(pTHX_ const char * fmt, break; } + DEBUG_Lv(PerlIO_printf(Perl_debug_log, "calling strftime_tm\n")); if (! strftime_tm(fmt, sv, locale, mytm)) { Safefree(free_me); return false; @@ -8688,7 +8802,7 @@ S_strftime8(pTHX_ const char * fmt, locale, INDEX_TO_USE); DEBUG_Lv(PerlIO_printf(Perl_debug_log, - "fmt=%s, retval=%s; utf8ness=%d", + "fmt=%s, retval=%s; utf8ness=%d\n", fmt, ((is_strict_utf8_string((U8 *) SvPVX(sv), 0)) ? SvPVX(sv) diff --git a/pod/perldelta.pod b/pod/perldelta.pod index 37f2fd23163f..36b2096b4f63 100644 --- a/pod/perldelta.pod +++ b/pod/perldelta.pod @@ -368,6 +368,16 @@ manager will later use a regex to expand these into links. XXX +=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 diff --git a/t/op/time.t b/t/op/time.t index d9e06d40dd3b..6dde277dbe68 100644 --- a/t/op/time.t +++ b/t/op/time.t @@ -51,7 +51,6 @@ SKIP: { # This conditional of "No tzset()" is stolen from ext/POSIX/t/time.t skip "No tzset()", 1 if $^O eq "VMS" || $^O eq "cygwin" || - $^O eq "MSWin32" || $^O eq "interix"; # check that localtime respects changes to $ENV{TZ}