mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-22 00:22:57 -05:00
fix(libkernel): convert local time with the host time zone
sceKernelConvertUtcToLocaltime and sceKernelConvertLocaltimeToUtc returned the time unchanged while libc localtime follows the host zone, so a title that mixes them shows UTC as local time (#5067). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017iLxaSz4qjmirfqKZdhVQk
This commit is contained in:
3 files changed
+81
-8
No files matched your search
@@ -10,6 +10,7 @@
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <ctime>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <unordered_map>
|
||||
@@ -554,10 +555,38 @@ int APS5_VABI clock_getres_nid_postfix(int clockId, KernelTimespec* res) {
|
||||
// Moved as-is (not yet implemented) from the monolithic libkernel/Export.cpp.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// The sce* clock calls share the POSIX implementations above. The console clock is kept in UTC:
|
||||
// the time zone reported here and by gettimeofday has no offset and no daylight saving.
|
||||
static constexpr int SceKernelErrorEfault = static_cast<int>(0x8002000e);
|
||||
|
||||
struct HostZone {
|
||||
std::int64_t standardEast;
|
||||
std::int64_t daylight;
|
||||
};
|
||||
|
||||
static HostZone HostZoneAt(std::int64_t utcTime) {
|
||||
std::tm local{};
|
||||
#ifdef _WIN32
|
||||
constexpr std::int64_t cycleSeconds = 146097LL * 86400LL;
|
||||
constexpr std::int64_t lastHostSecond = 32535215999LL;
|
||||
const std::int64_t cycles = utcTime >= 0 && utcTime <= lastHostSecond ? 0 : (utcTime >= 0 ? utcTime / cycleSeconds : -((-(utcTime + 1)) / cycleSeconds + 1));
|
||||
const std::time_t t = static_cast<std::time_t>(utcTime - cycles * cycleSeconds);
|
||||
if (localtime_s(&local, &t) != 0) throw std::runtime_error(std::string(__func__) + ": no host local time for " + std::to_string(utcTime));
|
||||
long standardBias = 0;
|
||||
long daylightBias = 0;
|
||||
_get_timezone(&standardBias);
|
||||
_get_dstbias(&daylightBias);
|
||||
return {-static_cast<std::int64_t>(standardBias), local.tm_isdst > 0 ? -static_cast<std::int64_t>(daylightBias) : 0};
|
||||
#else
|
||||
const std::time_t t = static_cast<std::time_t>(utcTime);
|
||||
if (localtime_r(&t, &local) == nullptr) throw std::runtime_error(std::string(__func__) + ": no host local time for " + std::to_string(utcTime));
|
||||
const std::int64_t daylight = local.tm_isdst > 0 ? static_cast<std::int64_t>(local.tm_gmtoff) + ::timezone : 0;
|
||||
return {static_cast<std::int64_t>(local.tm_gmtoff) - daylight, daylight};
|
||||
#endif
|
||||
}
|
||||
|
||||
static KernelTimesec ZoneState(std::int64_t time, const HostZone& zone) {
|
||||
return {time, static_cast<std::uint32_t>(-zone.standardEast), static_cast<std::uint32_t>(zone.daylight)};
|
||||
}
|
||||
|
||||
int APS5_VABI sceKernelClockGetres(KernelClockid clock_id, KernelTimespec* tp) {
|
||||
if (tp == nullptr) return SceKernelErrorEfault;
|
||||
if (clock_getres_nid_postfix(static_cast<int>(clock_id), tp) != 0) return SceKernelError(*__error_nid_postfix());
|
||||
@@ -572,16 +601,19 @@ int APS5_VABI sceKernelClockGettime(KernelClockid clock_id, KernelTimespec* tp)
|
||||
|
||||
int APS5_VABI sceKernelConvertLocaltimeToUtc(int64_t local_time, int64_t reserved, int64_t* utc_time, KernelTimesec* timezone, int32_t* dst_seconds) {
|
||||
(void)reserved;
|
||||
if (utc_time != nullptr) *utc_time = local_time;
|
||||
if (timezone != nullptr) *timezone = {local_time, 0u, 0u};
|
||||
if (dst_seconds != nullptr) *dst_seconds = 0;
|
||||
const HostZone guess = HostZoneAt(local_time);
|
||||
const HostZone zone = HostZoneAt(local_time - guess.standardEast - guess.daylight);
|
||||
if (utc_time != nullptr) *utc_time = local_time - zone.standardEast - zone.daylight;
|
||||
if (timezone != nullptr) *timezone = ZoneState(local_time, zone);
|
||||
if (dst_seconds != nullptr) *dst_seconds = static_cast<int32_t>(zone.daylight);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceKernelConvertUtcToLocaltime(int64_t utc_time, int64_t* local_time, KernelTimesec* st, int32_t* dst_sec) {
|
||||
if (local_time != nullptr) *local_time = utc_time;
|
||||
if (st != nullptr) *st = {utc_time, 0u, 0u};
|
||||
if (dst_sec != nullptr) *dst_sec = 0;
|
||||
const HostZone zone = HostZoneAt(utc_time);
|
||||
if (local_time != nullptr) *local_time = utc_time + zone.standardEast + zone.daylight;
|
||||
if (st != nullptr) *st = ZoneState(utc_time, zone);
|
||||
if (dst_sec != nullptr) *dst_sec = static_cast<int32_t>(zone.daylight);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <ctime>
|
||||
#include <stdexcept>
|
||||
|
||||
extern "C" {
|
||||
@@ -36,6 +37,16 @@ static constexpr std::int64_t GIVE_UP_NANOS = 10 * NANOS_PER_SECOND;
|
||||
|
||||
static void Require(bool value) { if (!value) std::abort(); }
|
||||
|
||||
static void SetTimeZone(const char* zone) {
|
||||
#ifdef _WIN32
|
||||
Require(_putenv_s("TZ", zone) == 0);
|
||||
_tzset();
|
||||
#else
|
||||
Require(setenv("TZ", zone, 1) == 0);
|
||||
tzset();
|
||||
#endif
|
||||
}
|
||||
|
||||
static bool OmittedResolutionSucceeds() {
|
||||
constexpr int clocks[] = {0, 1, 2, 4, 5, 7, 8, 9, 10, 11, 12, 13, 14, 15, 3, 6, 9999, -1, INT_MIN, INT_MAX};
|
||||
for (const int clock : clocks) {
|
||||
@@ -173,6 +184,7 @@ static void XtimeTicksAreGettimeofdayMicroseconds() {
|
||||
}
|
||||
|
||||
static void CalendarConversionsInitializeTheirState() {
|
||||
SetTimeZone("UTC0");
|
||||
constexpr std::uint64_t sentinel = 0xa5a5a5a5a5a5a5a5ull;
|
||||
struct GuardedState {
|
||||
std::uint64_t before;
|
||||
@@ -205,10 +217,38 @@ static void CalendarConversionsInitializeTheirState() {
|
||||
Require(sceKernelConvertUtcToLocaltime(0, nullptr, nullptr, nullptr) == SCE_OK);
|
||||
}
|
||||
|
||||
static void RequireZone(const char* zone, std::int64_t utc, std::int64_t east, std::int32_t daylight) {
|
||||
SetTimeZone(zone);
|
||||
std::int64_t local = -1;
|
||||
KernelTimesec universal{-1, 0xa5a5a5a5u, 0xa5a5a5a5u};
|
||||
std::int32_t universalDst = -1;
|
||||
Require(sceKernelConvertUtcToLocaltime(utc, &local, &universal, &universalDst) == SCE_OK);
|
||||
Require(local == utc + east && universalDst == daylight);
|
||||
Require(universal.t == utc && universal.west_sec == static_cast<std::uint32_t>(daylight - east) && universal.dst_sec == static_cast<std::uint32_t>(daylight));
|
||||
std::int64_t converted = -1;
|
||||
KernelTimesec state{-1, 0xa5a5a5a5u, 0xa5a5a5a5u};
|
||||
std::int32_t dst = -1;
|
||||
Require(sceKernelConvertLocaltimeToUtc(local, 0, &converted, &state, &dst) == SCE_OK);
|
||||
Require(converted == utc && dst == daylight);
|
||||
Require(state.t == local && state.west_sec == universal.west_sec && state.dst_sec == universal.dst_sec);
|
||||
}
|
||||
|
||||
static void CalendarConversionsFollowTheHostZone() {
|
||||
constexpr std::int64_t winter = 1768478400;
|
||||
constexpr std::int64_t summer = 1782907200;
|
||||
RequireZone("UTC0", summer, 0, 0);
|
||||
RequireZone("IST-5:30", winter, 19800, 0);
|
||||
RequireZone("BRT+3", summer, -10800, 0);
|
||||
RequireZone("EST5EDT", winter, -18000, 0);
|
||||
RequireZone("EST5EDT", summer, -14400, 3600);
|
||||
SetTimeZone("UTC0");
|
||||
}
|
||||
|
||||
int main() {
|
||||
if (!OmittedResolutionSucceeds()) return 1;
|
||||
UnsupportedResolutionStillThrows();
|
||||
CalendarConversionsInitializeTheirState();
|
||||
CalendarConversionsFollowTheHostZone();
|
||||
SecondClockReportsWholeSeconds();
|
||||
KernelTimezone zone{-1, -1};
|
||||
Require(gettimeofday_nid_postfix(nullptr, nullptr) == SCE_OK);
|
||||
|
||||
@@ -89,6 +89,7 @@
|
||||
- [sched_get_priority_max, sched_get_priority_min](../../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) - measured on a PS5 (firmware 13.60): `SCHED_FIFO` (1) and `SCHED_RR` (3) report a maximum of 767 and a minimum of 256, and every other policy, `SCHED_OTHER` (2) included, returns -1 with `EINVAL`. `pthread_setschedparam` ignores the policy and does not check the priority, where the console rejects `SCHED_OTHER` and priorities below 256 with `EINVAL`
|
||||
- [inet_pton, inet_ntop](../../../core/libs/prx/libkernel/Socket/src/AddressConversion.cpp) (libkernel) - the text forms are assumed to be those of FreeBSD 11.4 and are unverified on a console: `inet_pton` accepts only four decimal parts without leading zeros for `AF_INET` and the RFC 4291 forms for `AF_INET6` (one `::` standing for at least one group, a dotted-quad tail only as the last 32 bits, no zone id), and `inet_ntop` prints lowercase hex with the first longest run of two or more zero groups compressed, and a dotted tail only for `::a.b.c.d` and `::ffff:a.b.c.d`. They no longer depend on the host resolver library
|
||||
- [sceKernelIsThreadSanitizerEnabled](../../../core/libs/prx/libkernel/System/src/Sanitizer.cpp) (libkernel, NID `LzoM-wVLJDE`) - no source gives the signature; `int (void)` is assumed from `sceKernelIsAddressSanitizerEnabled` and from PPSA23226's bundled `libSceJobManager_nosubmission.prx`, which passes no arguments and tests only the 32-bit result
|
||||
- [sceKernelConvertUtcToLocaltime, sceKernelConvertLocaltimeToUtc](../../../core/libs/prx/libkernel/Time/Time.cpp) (libkernel) convert with the host time zone at the given instant, as libc `localtime` does, instead of the console's time zone setting. `west_sec` is assumed to be the standard-time offset in seconds west of UTC, as the name and FreeBSD's `tz_minuteswest` read, and `dst_sec` the daylight saving seconds in effect; [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/23c41333/src/core/libraries/kernel/time.cpp) stores seconds east in `west_sec` and declares the fourth argument of `sceKernelConvertLocaltimeToUtc` as an 8-byte `timezone`, not the 16-byte `KernelTimesec`. Neither layout is verified on a console. In a repeated or skipped local hour, `sceKernelConvertLocaltimeToUtc` takes the offset in effect one standard offset earlier. `sceKernelGettimezone` and the `gettimeofday` time zone still report no offset and no daylight saving. A host time the C runtime cannot convert throws
|
||||
|
||||
### Functional
|
||||
|
||||
|
||||
Reference in new issue
Block a user