Time in UNIX system calls counts from the epoch, 1970-01-01. The timespec
struct used in various system calls represents this as a number of seconds and
a number of nanoseconds. Nanoseconds are required to be between 0 and
999_999_999, because the portion outside that range should be represented in
the seconds field; if nanoseconds were larger than 999_999_999, the seconds
field should go up instead.
Suppose you ask for the time 1969-12-31, what time is that? On UNIX systems
that support times before the epoch, that's seconds=-86400, one day before the
epoch. But now, suppose you ask for the time 1969-12-31 23:59:00.1. In other
words, a tenth of a second after one minute before the epoch. On most UNIX
systems, that's represented as seconds=-60, nanoseconds=100_000_000. The macOS
bug is that it returns seconds=-59, nanoseconds=-900_000_000.
While that's in some sense an accurate description of the time (59.9 seconds
before the epoch), that violates the invariant of the timespec data structure:
nanoseconds must be between 0 and 999999999. This causes this assertion in the
Rust standard library.
So, on macOS, if we get a Timespec value with seconds less than or equal to
zero, and nanoseconds between -999_999_999 and -1 (inclusive), we can add
1_000_000_000 to the nanoseconds and subtract 1 from the seconds, and then
convert. The resulting timespec value is still accepted by macOS, and when fed
back into the OS, produces the same results. (If you set a file's mtime with
that timestamp, then read it back, you get back the one with negative
nanoseconds again.)
Co-authored-by: Josh Triplett <josh@joshtriplett.org>