Nested conversions such as the one closing mktime no longer re-read TZ, so
the instant and the normalised fields come from one zone. The race test now
covers mktime and strftime.
Copy the guest TZ under the guest environment lock so a concurrent setenv
cannot free it while it is read, and hold one lock across the zone switch
and the conversion so the fields of two zones are never mixed. Document the
host rejection, Windows zone name and stale tm_zone limits.
Guest setenv, putenv and unsetenv only change the guest environment, while
the time conversions read the host TZ, so a guest that set TZ and called
tzset still got host local time. Copy a changed guest TZ to the host before
each local conversion and call tzset again.
guest_time_conversion now converts nine times, from the year 1 to 1950,
with localtime and back with mktime in America/Los_Angeles,
Europe/Berlin, Asia/Kolkata and America/Argentina/Buenos_Aires, once
with the daylight flag localtime gave and once with -1. The zones'
local mean time and early standard offsets are covered, including the
first day of 1900. It runs on Linux and macOS; the Windows CRT does not
read region zone names.
ES-DE in RetroArch (PPSA99169) imports tzname from libSceLibcInternal and
fails to load without it; ICU's uprv_tzname reads it after localtime. It
points at the host's zone names and tzset refreshes it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
difftime and libc_difftime passed their arguments to the host CRT.
UCRT's _difftime64 returns 0 and sets errno to EINVAL when either
argument is negative (common_difftime in ucrt/time/difftime.cpp), so
on Windows difftime(0, -1) returned 0 instead of 1 and every
difference involving a time before 1970 was 0. C17 7.27.2.2 defines
the result as time1 - time0, and FreeBSD 11's difftime
(contrib/tzcode/stdtime/difftime.c) and glibc on the Linux build
compute it for any arguments.
Subtract the smaller time from the larger as uint64_t, which cannot
overflow, convert the difference to double and negate it when time1
is the smaller one, as FreeBSD and glibc's fallback path do. The
result is time1 - time0 rounded once to double on both hosts.
difftime now forwards to libc_difftime like the other aliases in the
file.
asctime passed the guest tm to the host CRT. UCRT's asctime returns
null and sets errno to EINVAL when tm_year is negative or tm_mday is
past the end of the month (common_asctime_s in ucrt/time/asctime.cpp),
so on Windows asctime of any time in the years 1000 to 1899, which
gmtime converts, or of February 29 in a common year returned null.
C17 7.27.3.1 defines the text for every year from 1000 to 9999 when
each member is in its normal range (tm_mday 1 to 31), and glibc on
the Linux build and FreeBSD 11's asctime
(contrib/tzcode/stdtime/asctime.c) print it.
Format the text with the C17 algorithm's format into a thread-local
buffer that fits any member values. As in glibc and FreeBSD, a
weekday or month outside its range is printed as ???, and the year is
computed in 64 bits.
gmtime, localtime, mktime, strftime and asctime took and returned the
host std::tm. The guest's FreeBSD tm has tm_gmtoff and tm_zone after the
nine calendar fields, so on Windows, where std::tm is 36 bytes, a title
reading those fields read past the thread-local result, and gmtime_s left
them unwritten. Windows gmtime_s also accepts only 1969-12-31 12:00 to
3001-01-19 21:59 UTC, where the guest libc converts any time.
Convert through a GuestTm with the guest layout on both hosts. Compute
the UTC fields from the day count, so every time whose year fits an int
converts, and on Windows take the local offset and zone name from the
CRT. gmtime reports the zone as UTC like the guest libc. localtime and
mktime keep the CRT's range on Windows; recorded in TechnicalDebt.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>