Skip to content
Merged
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
6 changes: 6 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@ This documents significant changes in the dev branch of ksh 93u+m.
For full details, see the git log at: https://github.com/ksh93/ksh
Uppercase BUG_* IDs are shell bug IDs as used by the Modernish shell library.

2026-07-25:

- For 'printf %T' and the date(1) builtin, multiple problems have been
fixed with the conversion of Daylight Savings Time offsets, as well as
contemporaneous time zones.

2026-07-24:

- Fix for 'read -t': the timeout was not deactivated if the read operation
Expand Down
2 changes: 1 addition & 1 deletion src/cmd/ksh93/data/builtins.c
Original file line number Diff line number Diff line change
Expand Up @@ -1230,7 +1230,7 @@ const char sh_optprint[] =
;

const char sh_optprintf[] =
"[-1c?\n@(#)$Id: printf (ksh 93u+m) 2024-07-31 $\n]"
"[-1c?\n@(#)$Id: printf (ksh 93u+m) 2026-07-25 $\n]"
"[--catalog?" SH_DICT "]"
"[+NAME?printf - write formatted output]"
"[+DESCRIPTION?\bprintf\b writes each \astring\a operand to "
Expand Down
2 changes: 1 addition & 1 deletion src/cmd/ksh93/include/version.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
#include <ast_release.h>
#include "git.h"

#define SH_RELEASE_DATE "2026-07-24" /* must be in this format for $((.sh.version)) */
#define SH_RELEASE_DATE "2026-07-25" /* must be in this format for $((.sh.version)) */
/*
* This comment keeps SH_RELEASE_DATE a few lines away from SH_RELEASE_SVER to avoid
* merge conflicts when cherry-picking dev branch commits onto a release branch.
Expand Down
5 changes: 3 additions & 2 deletions src/cmd/ksh93/tests/attributes.sh
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
# Johnothan King <johnothanking@protonmail.com> #
# hyenias <58673227+hyenias@users.noreply.github.com> #
# Sertonix <sertonix@posteo.net> #
# K. Eugene Carlson <kvngncrlsn@gmail.com> #
# #
########################################################################

Expand Down Expand Up @@ -362,9 +363,9 @@ $SHELL -c 'builtin date' >/dev/null 2>&1 &&
# check env var changes against a builtin that uses the env var

SEC=1234252800
ETZ=EST5EDT
ETZ=EST5EDT,M3.2.0/2:00:00,M11.1.0/2:00:00
EDT=03
PTZ=PST8PDT
PTZ=PST8PDT,M3.2.0/2:00:00,M11.1.0/2:00:00
PDT=00

CMD="date -f%H \\#$SEC"
Expand Down
54 changes: 51 additions & 3 deletions src/cmd/ksh93/tests/printf.sh
Original file line number Diff line number Diff line change
Expand Up @@ -211,14 +211,62 @@ function do_test # 1:LINENO 2:printf-STRING 3:match-string
}

# The first tests require a time zone with one or more historical changes.
format='%Y-%m-%d %H:%M:%S'
export TZ=Europe/Riga

C='Historical changes (bad time)' # https://github.com/ksh93/ksh/issues/669
export TZ=Europe/Riga
format='%Y-%m-%d %H:%M:%S'
T '#236961303' '1977-07-05 17:35:03'
format='%s'
T '1977-07-05 17:35:03' '236961303'

export TZ=Europe/London
format='%Y-%m-%d %H:%M:%S'
T '#0' '1970-01-01 01:00:00'
format='%s'
T '1970-01-01 01:00:00' '0'

C='Before and after a historical change (bad time)'
export TZ=Pacific/Apia
format='%Y-%m-%d %H:%M:%S'
T '#1325239200' '2011-12-31 00:00:00'
T '#1325239199' '2011-12-29 23:59:59'
format='%s'
T '2011-12-31 00:00:00' '1325239200'
T '2011-12-29 23:59:59' '1325239199'

C='Historical change from one DST zone to another (bad time)'
export TZ=Europe/Chisinau
format='%Y-%m-%d %H:%M:%S'
T '#642286800' '1990-05-10 00:00:00'
format='%s'
T '1990-05-10 00:00:00' '642286800'

C='Historical change from DST to standard (bad time)'
export TZ=America/Indiana/Knox
format='%Y-%m-%d %H:%M:%S'
T '#688626000' '1991-10-28 00:00:00'
format='%s'
T '1991-10-28 00:00:00' '688626000'

C='Historical change from standard to DST (bad time)'
export TZ=America/Kentucky/Louisville
format='%Y-%m-%d %H:%M:%S'
T '#126766800' '1974-01-07 00:00:00'
format='%s'
T '1974-01-07 00:00:00' '126766800'

C='Historical change from standard to standard (bad time)'
export TZ=America/Argentina/San_Juan
format='%Y-%m-%d %H:%M:%S'
T '#1086148800' '2004-06-02 00:00:00'
format='%s'
T '2004-06-02 00:00:00' '1086148800'

C='POSIX timezone strings without rules (bad time, musl)' #https://github.com/ksh93/ksh/issues/976
export TZ=EST5EDT
format='%Y-%m-%d %H:%M:%S'
T '#1274252800' '2010-05-19 03:06:40'
format='%s'
T '2010-05-19 03:06:40' '1274252800'

format='%Y-%m-%d'
export TZ=UTC
Expand Down
48 changes: 31 additions & 17 deletions src/lib/libast/tm/tminit.c
Original file line number Diff line number Diff line change
Expand Up @@ -191,12 +191,14 @@ static void
tmlocal(time_t now)
{
Tm_zone_t* zp;
int n;
char* s;
char* e = NULL;
int i;
int m;
int isdst;
int utc_offset; /* offset from Coordinated Universal Time (UTC) */
int utc_offset_old;
int isdst; /* nonzero if daylight saving time */
int isdst_old;
int dst_offset;
int month;
char* t;
struct tm* tp;
char buf[16];
Expand Down Expand Up @@ -236,30 +238,42 @@ tmlocal(time_t now)
*/

tm_info.zone = tm_info.local = &local;
n = tzwest(&now, &isdst);
utc_offset = tzwest(&now, &isdst);
utc_offset_old = utc_offset;
isdst_old = isdst;

/*
* compute local DST offset by roaming
* through the last 12 months until tzwest() changes
* with a system-recognized DST change
*/

for (i = 0; i < 12; i++)
for (month = 1; month <= 12; month++)
{
now -= 31 * 24 * 60 * 60;
if ((m = tzwest(&now, &isdst)) != n)
/* only break offset calculations for a DST shift */
if ((dst_offset = tzwest(&now, &isdst)) != utc_offset && ((!isdst && isdst_old) || (isdst && !isdst_old)))
{
dst_offset -= (utc_offset - utc_offset_old);
utc_offset = utc_offset_old;
if (!isdst)
{
isdst = n;
n = m;
m = isdst;
utc_offset = dst_offset;
dst_offset = utc_offset_old;
}
m -= n;
break;
}
utc_offset = dst_offset;
if (month == 12)
{
utc_offset = utc_offset_old;
dst_offset = utc_offset;
}
}
local.west = (short)n;
local.dst = (short)m;
dst_offset -= utc_offset;
isdst = isdst_old;
local.west = (short)utc_offset;
local.dst = (short)dst_offset;

/*
* now get the time zone names
Expand Down Expand Up @@ -322,7 +336,7 @@ tmlocal(time_t now)
{
if (zp->type)
t = zp->type;
if (zp->west == n && zp->dst == m)
if (zp->west == utc_offset && zp->dst == dst_offset)
{
local.type = t;
if (!local.standard)
Expand All @@ -334,7 +348,7 @@ tmlocal(time_t now)
if (s < e - 1)
{
*s++ = ' ';
tmpoff(s, (size_t)(e - s), tm_info.format[TM_DT], m, TM_DST);
tmpoff(s, (size_t)(e - s), tm_info.format[TM_DT], dst_offset, TM_DST);
}
s = buf;
}
Expand All @@ -350,13 +364,13 @@ tmlocal(time_t now)
*/

e = (s = buf) + sizeof(buf);
s = tmpoff(s, (size_t)(e - s), tm_info.format[TM_UT], n, 0);
s = tmpoff(s, (size_t)(e - s), tm_info.format[TM_UT], utc_offset, 0);
if (!local.standard)
local.standard = strdup(buf);
if (s < e - 1)
{
*s++ = ' ';
tmpoff(s, (size_t)(e - s), tm_info.format[TM_UT], m, TM_DST);
tmpoff(s, (size_t)(e - s), tm_info.format[TM_UT], dst_offset, TM_DST);
if (!local.daylight)
local.daylight = strdup(buf);
}
Expand Down
28 changes: 28 additions & 0 deletions src/lib/libast/tm/tmxdate.c
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,10 @@ tmxdate(const char* s, char** e, Time_t now)
int dir;
int dst;
int zone;
int offset;
int dst_old;
int west_old;
int isdst_old;
int c;
int f;
int i;
Expand Down Expand Up @@ -206,6 +210,11 @@ tmxdate(const char* s, char** e, Time_t now)
*/

tm = tmxtm(&ts, now, NULL, 1);

dst_old = tm->tm_zone->dst;
west_old = tm->tm_zone->west;
isdst_old = tm->tm_isdst;

tm_info.date = tm->tm_zone;
day = -1;
dir = 0;
Expand Down Expand Up @@ -1779,6 +1788,17 @@ tmxdate(const char* s, char** e, Time_t now)
tm->tm_mday += j;
}
}

/*
* Apply an offset to correct for historical time zone changes.
*/
tm = tmxtm(tm, tmxtime(tm, zone), tm->tm_zone, 1);
offset = tm->tm_zone->west - west_old;
if (tm->tm_isdst)
offset += tm->tm_zone->dst - dst_old;
tm->tm_min += offset;
tmfix(tm);

now = tmxtime(tm, zone);
if (tm->tm_year <= 70 && tmxsec(now) > 31536000)
{
Expand All @@ -1787,5 +1807,13 @@ tmxdate(const char* s, char** e, Time_t now)
}
if (e)
*e = last;

/*
* Ensure that a proper time zone has been chosen. If the time
* zones differ, this means that a historical change has occurred.
*/
if (!(dst_old == tm->tm_zone->dst && west_old == tm->tm_zone->west && isdst_old == tm->tm_isdst))
goto reset;

return now;
}
2 changes: 1 addition & 1 deletion src/lib/libcmd/date.c
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
#include <times.h>

static const char usage[] =
"[-?\n@(#)$Id: date (AT&T Research) 2011-01-27 $\n]"
"[-?\n@(#)$Id: date (ksh 93u+m) 2026-07-25 $\n]"
"[--catalog?" ERROR_CATALOG "]"
"[+NAME?date - set/list/convert dates]"
"[+DESCRIPTION?\bdate\b sets the current date and time (with appropriate"
Expand Down