Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/testsuite.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
14 changes: 7 additions & 7 deletions ext/POSIX/POSIX.xs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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);
}
Expand Down
2 changes: 1 addition & 1 deletion ext/POSIX/lib/POSIX.pm
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use warnings;

our ($AUTOLOAD, %SIGRT);

our $VERSION = '2.24';
our $VERSION = '2.25';

require XSLoader;

Expand Down
44 changes: 6 additions & 38 deletions ext/POSIX/lib/POSIX.pod
Original file line number Diff line number Diff line change
Expand Up @@ -1866,49 +1866,17 @@ Identical to the string form of C<$!>, see L<perlvar/$ERRNO>.
=item C<strftime>

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<mon>) begins at zero,
I<e.g.>, January is 0, not 1. The
year (C<year>) is given in years since 1900, I<e.g.>, the year 1995 is 95; the
year 2001 is 101. Consult your system's C<strftime()> 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<wday> and C<yday> parameters are both ignored. Their values are
always determinable from the other parameters.

C<isdst> 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<fmt>) argument
should use only the conversion specifiers defined by the ANSI C
standard (C99, to play safe). These are C<aAbBcdHIjmMpSUwWxXyYZ%>.
But even then, the B<results> of some of the conversion specifiers are
non-portable. For example, the specifiers C<aAbBcpZ> 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<c> changes according to the timezone settings of the
user and the timezone computation rules of the operating system.
The C<Z> 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<isdst>, are made consistent as though by
calling C<mktime()> before calling your system's C<strftime()> function.
To get correct results, you must set C<isdst> 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<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<perlapi/sv_strftime_ints>.

=item C<strlen>

Expand Down
7 changes: 6 additions & 1 deletion ext/POSIX/t/posix.t
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}

Expand Down
100 changes: 94 additions & 6 deletions ext/POSIX/t/time.t
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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();
Expand All @@ -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");
Expand Down Expand Up @@ -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 );
}
Loading
Loading