Repository navigation
std/temporal: split Y - 1 into century and year in Date.dayOfWeek - #608
notactuallytreyanastasio wants to merge 1 commit into
Conversation
`Date.dayOfWeek` is wrong on the interpreter and on Rust for every year
ending in 00 that is not a multiple of 400 (1700, 1800, 1900, 2100, ...),
all year long. js, py, java, cpp and csharp connect `dayOfWeek` to their
own date code, so they were right; the interpreter, Rust and Lua run
std's Temper. The program:
let { Date } = import("std/temporal");
for (let y of [1600, 1700, 1800, 1900, 2000, 2100, 2200, 2300, 2400]) {
console.log("${y}-01-01 ${new Date(y, 1, 1).dayOfWeek} ${y}-03-01 ${new Date(y, 3, 1).dayOfWeek} ${y}-12-31 ${new Date(y, 12, 31).dayOfWeek}");
}
at origin/main, `temper run -b rust` against `-b js` (py and java print
the same as js, and so does Python's `datetime.isoweekday`):
rust js
1600-01-01 6 1600-03-01 3 1600-12-31 7 1600-01-01 6 1600-03-01 3 1600-12-31 7
1700-01-01 4 1700-03-01 7 1700-12-31 4 1700-01-01 5 1700-03-01 1 1700-12-31 5
1800-01-01 2 1800-03-01 5 1800-12-31 2 1800-01-01 3 1800-03-01 6 1800-12-31 3
1900-01-01 7 1900-03-01 3 1900-12-31 7 1900-01-01 1 1900-03-01 4 1900-12-31 1
2000-01-01 6 2000-03-01 3 2000-12-31 7 2000-01-01 6 2000-03-01 3 2000-12-31 7
2100-01-01 4 2100-03-01 7 2100-12-31 4 2100-01-01 5 2100-03-01 1 2100-12-31 5
and the interpreter, through `temper repl`:
1900-01-01 7 1900-03-01 3 2100-01-01 4 1700-01-01 4 2000-01-01 6
1900-01-01 was a Monday (1), not a Sunday (7).
Gauss's formula gives January first of year Y as
(1 + 5((Y-1) mod 4) + 4((Y-1) mod 100) + 6((Y-1) mod 400)) mod 7. std
split Y itself into century and year-of-century and made up for the
missing - 1 with offsets. That works until the year of the century is
00, because then Y - 1 is in the previous century. 2000 came out right
only because 2000 mod 400 is 0, and the existing tests (1596,
2010-2012, 2023) never hit a bad year.
It now takes the three remainders of Y - 1 directly, each made
non-negative by adding its divisor, since `%` keeps the dividend's sign,
so years before 1 still work. The month tables are unchanged. The
divisors are literals on purpose: a helper taking the divisor as a
parameter makes `%` able to bubble, and be-rust then refuses it
("Cannot translate Invalid").
After, the interpreter, rust, js, py and java all print the js column
above. On rust, every year from -1199 to 2999, for January 1, the last
day of February, March 1 and December 31, matches Python's `datetime`
(years before 1 compared through the 400-year cycle): 4199 lines, no
difference.
types/date now logs January and March first of 1700, 1800, 1900, 2000,
2100 and 2400. With the temporal change reverted it fails on the
interpreter and on rust with the left column, and passes on js. With it,
typesDate passes on the interpreter (InterpreterFunctionalTests), js,
py, rust, java 17 and cpp. :frontend:jvmTest (703 tests), ktlintCheck
and detekt pass.
What does not work: Lua is not installed here, so Lua, which also runs
this code, was not run. Separately, be-cpp's own `Date::getDayOfWeek`
returns 0 for every Sunday instead of 7 (2023-01-01 is 0 on cpp, 7 on
js); the new test has no Sunday in it, and this change does not touch
cpp.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: Bobby Grayson <bobbbygrayson+github@gmail.com>
| // `%` keeps the dividend's sign, so each remainder adds its | ||
| // divisor and takes it again, to stay in range for years before 1. | ||
| let y = year; | ||
| let c = if (y >= 0) { y / 100 } else { -(-y / 100) }; |
There was a problem hiding this comment.
Ugh. I probably meant to do something with ~ instead of - here.
| 1900: Jan 1 weekday 1, Mar 1 weekday 4 | ||
| 2000: Jan 1 weekday 6, Mar 1 weekday 3 | ||
| 2100: Jan 1 weekday 5, Mar 1 weekday 1 | ||
| 2400: Jan 1 weekday 6, Mar 1 weekday 3 |
There was a problem hiding this comment.
For the record, here's an attempt to use date to double check these.
$ for y in {17,18,19,20,21,24}00; do echo "$y Jan 1 weekday $(date -j -f "%Y-%m-%d" $y-01-01 +%u), Mar 1 weekday $(date -j -f "%Y-%m-%d" $y-03-01 +%u)"; done
date: nonexistent time
date: nonexistent time
1700 Jan 1 weekday , Mar 1 weekday
date: nonexistent time
date: nonexistent time
1800 Jan 1 weekday , Mar 1 weekday
1900 Jan 1 weekday 1, Mar 1 weekday 4
2000 Jan 1 weekday 6, Mar 1 weekday 3
2100 Jan 1 weekday 5, Mar 1 weekday 1
2400 Jan 1 weekday 6, Mar 1 weekday 3I'm guessing the nonexistent time thing is due to a failure to just use a proleptic Gregorian calendar.
My man cal says the below, so I'll not muck with that.
BUGS
The assignment of Julian–Gregorian switching dates to country codes is historically naive for many countries.
Java's java.time.LocalDate says:
The ISO-8601 calendar system is the modern civil calendar system used today in most of the world. It is equivalent to the proleptic Gregorian calendar system
And its DayOfWeek.getValue returns the ISO's 1-indexed weekday number starting at 1 = Monday.
Here's the output of jshell using that.
$ jshell
| Welcome to JShell -- Version 21.0.4
| For an introduction type: /help intro
jshell> import java.time.LocalDate;
jshell> for (int year : new int[] { 1700, 1800, 1900, 2000, 2100, 2400}) {
...> LocalDate jan1 = LocalDate.of(year, 1, 1);
...> LocalDate mar1 = LocalDate.of(year, 3, 1);
...> System.err.println(year + ": Jan 1 weekday " + (jan1.getDayOfWeek().getValue()) + ", Mar 1 weekday " + (mar1.getDayOfWeek().getValue()));
...> }
1700: Jan 1 weekday 5, Mar 1 weekday 1
1800: Jan 1 weekday 3, Mar 1 weekday 6
1900: Jan 1 weekday 1, Mar 1 weekday 4
2000: Jan 1 weekday 6, Mar 1 weekday 3
2100: Jan 1 weekday 5, Mar 1 weekday 1
2400: Jan 1 weekday 6, Mar 1 weekday 3
jshell>
Date.dayOfWeekis wrong on the interpreter and on Rust for every yearending in 00 that is not a multiple of 400 (1700, 1800, 1900, 2100, ...),
all year long. js, py, java, cpp and csharp connect
dayOfWeekto theirown date code, so they were right; the interpreter, Rust and Lua run
std's Temper. The program:
at origin/main,
temper run -b rustagainst-b js(py and java printthe same as js, and so does Python's
datetime.isoweekday):and the interpreter, through
temper repl:1900-01-01 was a Monday (1), not a Sunday (7).
Gauss's formula gives January first of year Y as
(1 + 5((Y-1) mod 4) + 4((Y-1) mod 100) + 6((Y-1) mod 400)) mod 7. std
split Y itself into century and year-of-century and made up for the
missing - 1 with offsets. That works until the year of the century is
00, because then Y - 1 is in the previous century. 2000 came out right
only because 2000 mod 400 is 0, and the existing tests (1596,
2010-2012, 2023) never hit a bad year.
It now takes the three remainders of Y - 1 directly, each made
non-negative by adding its divisor, since
%keeps the dividend's sign,so years before 1 still work. The month tables are unchanged. The
divisors are literals on purpose: a helper taking the divisor as a
parameter makes
%able to bubble, and be-rust then refuses it("Cannot translate Invalid").
After, the interpreter, rust, js, py and java all print the js column
above. On rust, every year from -1199 to 2999, for January 1, the last
day of February, March 1 and December 31, matches Python's
datetime(years before 1 compared through the 400-year cycle): 4199 lines, no
difference.
types/date now logs January and March first of 1700, 1800, 1900, 2000,
2100 and 2400. With the temporal change reverted it fails on the
interpreter and on rust with the left column, and passes on js. With it,
typesDate passes on the interpreter (InterpreterFunctionalTests), js,
py, rust, java 17 and cpp. :frontend:jvmTest (703 tests), ktlintCheck
and detekt pass.
What does not work: Lua is not installed here, so Lua, which also runs
this code, was not run. Separately, be-cpp's own
Date::getDayOfWeekreturns 0 for every Sunday instead of 7 (2023-01-01 is 0 on cpp, 7 on
js); the new test has no Sunday in it, and this change does not touch
cpp.
🤖 Generated with Claude Code