Default the day of the month to 1 in strptime/1 (#3679)

A format without %d left the day of the month at 0, which mktime and
todate read as the last day of the previous month, and the weekday was
wrong for dates in January and February of 1900 and 2100.

Start parsing from the 1st, so a format without a day now gives the
first of the month, and a time-only format no longer outputs the 8 and
367 placeholders for the weekday and day of the year. This also fixes
the weekday of dates in January and February of 1900 and 2100.
This commit is contained in:
Jay Hemnani authored and GitHub committed 2026-10-25 14:40:52 +09:00
1 parent 864d769d88
commit ac35c7dcbb
2 files changed
+22 -8

No files matched your search

+13 -8
View File
@@ -1608,16 +1608,15 @@ static void set_tm_wday(struct tm *tm) {
* https://en.wikipedia.org/wiki/Determination_of_the_day_of_the_week#Gauss.27s_algorithm
* https://cs.uwaterloo.ca/~alopez-o/math-faq/node73.html
*
* Tested with dates from 1900-01-01 through 2100-01-01. This
* algorithm produces the wrong day-of-the-week number for dates in
* the range 1900-01-01..1900-02-28, and for 2100-01-01..2100-02-28.
* Since this is only needed on OS X and *BSD, we might just document
* this.
* Tested against gmtime() for every date from 1600-01-01 through
* 2400-12-31.
*/
int century = (1900 + tm->tm_year) / 100;
int year = (1900 + tm->tm_year) % 100;
if (tm->tm_mon < 2)
year--;
if (tm->tm_mon < 2 && --year < 0) {
year += 100;
century--;
}
/*
* The month value in the wday computation below is shifted so that
* March is 1, April is 2, .., January is 11, and February is 12.
@@ -1630,7 +1629,10 @@ static void set_tm_wday(struct tm *tm) {
if (wday < 0)
wday += 7;
#if 0
/* See commentary above */
/*
* Off: the bundled strptime() can leave a wrong weekday here, and %a
* or %u can name one that does not match the date.
*/
assert(wday == tm->tm_wday || tm->tm_wday == 8);
#endif
tm->tm_wday = wday;
@@ -1666,6 +1668,7 @@ static jv f_strptime(jq_state *jq, jv a, jv b) {
struct tm tm;
memset(&tm, 0, sizeof(tm));
tm.tm_mday = 1; // a format without a day of the month means the 1st
tm.tm_wday = 8; // sentinel
tm.tm_yday = 367; // sentinel
@@ -1698,6 +1701,8 @@ static jv f_strptime(jq_state *jq, jv a, jv b) {
set_tm_yday(&tm);
#elif defined(WIN32) || !defined(HAVE_STRPTIME)
set_tm_wday(&tm);
if (tm.tm_yday == 367)
set_tm_yday(&tm);
#else
if (tm.tm_wday == 8 && tm.tm_mday != 0 && tm.tm_mon >= 0 && tm.tm_mon <= 11)
set_tm_wday(&tm);
+9
View File
@@ -1988,6 +1988,15 @@ try strflocaltime("%c" * 10) catch .
"2015-03-05T23:51:47Z"
[[2015,2,5,23,51,47,4,63],1425599507]
# a format without a day of the month gives the first of the month
[("2024-06" | strptime("%Y-%m")), ("2024" | strptime("%Y")) | ., todate]
null
[[2024,5,1,0,0,0,6,152],"2024-06-01T00:00:00Z",[2024,0,1,0,0,0,1,0],"2024-01-01T00:00:00Z"]
"10:15" | strptime("%H:%M")
null
[1900,0,1,10,15,0,1,0]
# Check day-of-week and day of year computations
# (should trip an assert if this fails)
last(range(365 * 67)|("1970-03-01T01:02:03Z"|strptime("%Y-%m-%dT%H:%M:%SZ")|mktime) + (86400 * .)|strftime("%Y-%m-%dT%H:%M:%SZ")|strptime("%Y-%m-%dT%H:%M:%SZ"))