I can't reproduce “Invalid argument” for exactly the epoch on an older system, but:
# strace date -s '@-1'
…
clock_settime(CLOCK_REALTIME, {4294967295, 0}) = -1 EINVAL (Invalid argument)
settimeofday({4294967295, 0}, NULL) = -1 EINVAL (Invalid argument)
write(2, "date: ", 6date: ) = 6
write(2, "cannot set date", 15cannot set date) = 15
write(2, ": Invalid argument", 18: Invalid argument) = 18
write(2, "\n", 1
(And then date
goes on to display the date that it wanted to set, even though it hasn't actually set it.)
In plain English, date
is calling the kernel to set the date to one second before the epoch, and the kernel tells it that the proposed date is invalid.
Now in the trace above, you see 4294967295 as the number of seconds instead of -1. That number is 2^32-1 and I ran this on a 32-bit machine. This is actually not the source of the problem: it's a display problem in strace
, which doesn't know whether the value is supposed to be signed or not. In fact the number of seconds is a signed integer of type time_t
. Glibc 2.23 on Linux defines time_t
as __time_t
in /usr/include/time.h
, __time_t
as __TIME_T_TYPE
in /usr/include/bits/types.h
__TIME_T_TYPE
as __SYSCALL_SLONG_TYPE
which is __SQUAD_TYPE
in /usr/include/bits/typesizes.h
, and __SQUAD_TYPE
as a 64-bit signed type in /usr/include/bits/types.h
. On the kernel side, as of 4.4, the definition of the type of the argument of the settimeofday
call is either struct timespec
where the seconds are a __kernel_time_t
which is a signed 64-bit type on 64-bit machines, or a time64_t
which is a signed 64-bit type on 32-bit machines. So the kernel does see -1 as -1 and not some large positive number.
The reason you can't go back before the epoch is that the Linux kernel explicitly refuses to do so. The settimeofday
system call returns EINVAL (“Invalid argument”) if timeval_valid
rejects the given time structure, and that function rejects number of seconds that are negative.
Different versions of the kernel have code that's organized differently, but I don't think the behavior has changed.
I couldn't see any reason why a time of exactly 0 would be rejected and it worked in the VM where I tested it.
The reason for putting this sanity check is presumably that there's code out there that stores the time since the epoch as an unsigned integer, and thus cannot cope with dates that are before the epoch. Some code might interpret 0 as “the date isn't known”, so it would make sense to reject 0 too, but Linux doesn't actually do that, and it would only last for a second anyway.
Times before the epoch can certainly be represented and manipulated. A lot of software needs to track the time of past events. The timezone database that virtually every operating system uses has historical data going back to when timezones were codified (around the time railways started to take on). But you can't tell a Linux computer that the current time is before 1970.