AnyPS5/core/libs/prx/libc/src/Time.cpp

418 lines
15 KiB
C++

#include <cstdint>
#include <cstddef>
#include <cerrno>
#include <ctime>
#include <cstring>
#include <cstdio>
#include <limits>
#include <string>
#ifdef _WIN32
#include <windows.h>
#endif
#include "prx/libc/include/General.hpp"
#include "prx/libc/include/GuestTime.hpp"
#include "prx/libc/include/SonyCrt.hpp"
namespace {
constexpr const char* WEEKDAY_NAMES[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
constexpr const char* MONTH_NAMES[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
constexpr std::size_t ASCTIME_SIZE = sizeof("Sun Jan-2147483648 -2147483648:-2147483648:-2147483648 -2147483648\n");
constexpr std::int64_t SECONDS_PER_DAY = 86400;
constexpr std::int64_t DAYS_FROM_MARCH_TO_YEAR_END = 306;
#ifdef _WIN32
constexpr std::int64_t CALENDAR_CYCLE_YEARS = 400;
constexpr std::int64_t CALENDAR_CYCLE_SECONDS = 146097 * SECONDS_PER_DAY;
constexpr std::int64_t HOST_LAST_SECOND = 32535215999;
std::int64_t calendarCycles(std::int64_t value, std::int64_t cycle) {
return value >= 0 ? value / cycle : -((-(value + 1)) / cycle + 1);
}
std::int64_t utcSeconds(const std::tm& time) {
const std::int64_t years = calendarCycles(time.tm_mon, 12);
const std::int64_t month = time.tm_mon - years * 12 + 1;
const std::int64_t year = std::int64_t{time.tm_year} + 1900 + years - (month <= 2 ? 1 : 0);
const std::int64_t era = calendarCycles(year, CALENDAR_CYCLE_YEARS);
const std::int64_t yearOfEra = year - era * CALENDAR_CYCLE_YEARS;
const std::int64_t dayOfYear = (153 * (month > 2 ? month - 3 : month + 9) + 2) / 5;
const std::int64_t dayOfEra = yearOfEra * 365 + yearOfEra / 4 - yearOfEra / 100 + dayOfYear;
const std::int64_t days = era * 146097 + dayOfEra - 719468 + time.tm_mday - 1;
return days * SECONDS_PER_DAY + std::int64_t{time.tm_hour} * 3600 + std::int64_t{time.tm_min} * 60 + time.tm_sec;
}
#endif
void copyCalendarFields(const std::tm& host, GuestTm& guest) {
guest.tm_sec = host.tm_sec;
guest.tm_min = host.tm_min;
guest.tm_hour = host.tm_hour;
guest.tm_mday = host.tm_mday;
guest.tm_mon = host.tm_mon;
guest.tm_year = host.tm_year;
guest.tm_wday = host.tm_wday;
guest.tm_yday = host.tm_yday;
guest.tm_isdst = host.tm_isdst;
}
std::tm toHostTm(const GuestTm& guest) {
std::tm host{};
host.tm_sec = guest.tm_sec;
host.tm_min = guest.tm_min;
host.tm_hour = guest.tm_hour;
host.tm_mday = guest.tm_mday;
host.tm_mon = guest.tm_mon;
host.tm_year = guest.tm_year;
host.tm_wday = guest.tm_wday;
host.tm_yday = guest.tm_yday;
host.tm_isdst = guest.tm_isdst;
#ifndef _WIN32
host.tm_gmtoff = static_cast<long>(guest.tm_gmtoff);
host.tm_zone = guest.tm_zone;
#endif
return host;
}
bool isLeapYear(std::int64_t year) {
return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);
}
bool utcCalendarFields(std::int64_t time, GuestTm& result) {
std::int64_t days = time / SECONDS_PER_DAY;
std::int64_t secondOfDay = time % SECONDS_PER_DAY;
if (secondOfDay < 0) {
secondOfDay += SECONDS_PER_DAY;
--days;
}
const std::int64_t shifted = days + 719468;
const std::int64_t era = (shifted >= 0 ? shifted : shifted - 146096) / 146097;
const std::int64_t dayOfEra = shifted - era * 146097;
const std::int64_t yearOfEra = (dayOfEra - dayOfEra / 1460 + dayOfEra / 36524 - dayOfEra / 146096) / 365;
const std::int64_t dayOfMarchYear = dayOfEra - (365 * yearOfEra + yearOfEra / 4 - yearOfEra / 100);
const std::int64_t marchMonth = (5 * dayOfMarchYear + 2) / 153;
const std::int64_t month = marchMonth < 10 ? marchMonth + 3 : marchMonth - 9;
const std::int64_t year = yearOfEra + era * 400 + (month <= 2 ? 1 : 0);
const std::int64_t tmYear = year - 1900;
if (tmYear < std::numeric_limits<int>::min() || tmYear > std::numeric_limits<int>::max()) return false;
result.tm_sec = static_cast<int>(secondOfDay % 60);
result.tm_min = static_cast<int>(secondOfDay / 60 % 60);
result.tm_hour = static_cast<int>(secondOfDay / 3600);
result.tm_mday = static_cast<int>(dayOfMarchYear - (153 * marchMonth + 2) / 5 + 1);
result.tm_mon = static_cast<int>(month - 1);
result.tm_year = static_cast<int>(tmYear);
result.tm_wday = static_cast<int>((days % 7 + 11) % 7);
result.tm_yday = static_cast<int>(month <= 2
? dayOfMarchYear - DAYS_FROM_MARCH_TO_YEAR_END
: dayOfMarchYear + 59 + (isLeapYear(year) ? 1 : 0));
result.tm_isdst = 0;
return true;
}
}
#ifdef __linux__
#include <cstdlib>
namespace {
const bool timeZoneFixed = [] {
if (std::getenv("TZ") == nullptr) ::setenv("TZ", ":/etc/localtime", 0);
::tzset();
return true;
}();
} // namespace
#endif
namespace {
bool isFreeBsdConversion(char specifier) {
return specifier == 'k' || specifier == 'l' || specifier == 's' || specifier == 'v' || specifier == '+';
}
struct FormatConversion {
std::size_t length;
char specifier;
bool literal;
bool padded;
};
FormatConversion parseConversion(const char* afterPercent) {
const char* cursor = afterPercent;
bool alternative = false;
bool padded = false;
while (*cursor != '\0') {
if (*cursor == 'E' || *cursor == 'O') {
if (alternative) break;
alternative = true;
} else if (*cursor == '-' || *cursor == '_' || *cursor == '0') {
if (padded) break;
padded = true;
} else {
break;
}
++cursor;
}
if (*cursor == '\0')
return {static_cast<std::size_t>(cursor - afterPercent), cursor[-1], true, padded};
const bool literal = *cursor == '%' || std::strchr("aAbBcCdDeFgGhHIjklmMnprRsStTuUVvWwXxyYzZ+", *cursor) == nullptr;
return {static_cast<std::size_t>(cursor - afterPercent) + 1, *cursor, literal, padded};
}
bool requiresFreeBsdExpansion(const char* format) {
while (*format != '\0') {
if (*format++ != '%') continue;
const auto conversion = parseConversion(format);
if (conversion.literal || (!conversion.padded && isFreeBsdConversion(conversion.specifier))) return true;
format += conversion.length;
}
return false;
}
const char* guestZoneName(const GuestTm& guest) {
if (guest.tm_zone != nullptr) return guest.tm_zone;
#ifdef _WIN32
return _tzname[guest.tm_isdst != 0 ? 1 : 0];
#else
return tzname[guest.tm_isdst != 0 ? 1 : 0];
#endif
}
std::string expandFreeBsdConversions(const char* format, const GuestTm& guest) {
std::string result;
while (*format != '\0') {
const char next = *format++;
if (next != '%') {
result += next;
continue;
}
const auto conversion = parseConversion(format);
if (conversion.literal) {
if (conversion.specifier == '%') result += '%';
result += conversion.specifier;
format += conversion.length;
continue;
}
if (conversion.padded || !isFreeBsdConversion(conversion.specifier)) {
result.append(format - 1, conversion.length + 1);
format += conversion.length;
continue;
}
format += conversion.length;
char text[32];
switch (conversion.specifier) {
case 'k':
std::snprintf(text, sizeof(text), "%2d", guest.tm_hour);
result += text;
break;
case 'l':
std::snprintf(text, sizeof(text), "%2d", guest.tm_hour % 12 == 0 ? 12 : guest.tm_hour % 12);
result += text;
break;
case 's': {
std::tm host = toHostTm(guest);
std::snprintf(text, sizeof(text), "%lld", static_cast<long long>(std::mktime(&host)));
result += text;
break;
}
case 'v':
result += "%e-%b-%Y";
break;
default:
result += "%a %b %e %H:%M:%S ";
for (const char* zone = guestZoneName(guest); *zone != '\0'; ++zone) {
if (*zone == '%') result += '%';
result += *zone;
}
result += " %Y";
break;
}
}
return result;
}
}
extern "C" {
GuestTm* APS5_VABI localtime_s_nid_postfix(const int64_t* timer, GuestTm* result);
GuestTm* APS5_VABI gmtime_s_nid_postfix(const int64_t* timer, GuestTm* result);
void APS5_VABI tzset_nid_postfix(void) {
#ifdef _WIN32
_tzset();
#else
::tzset();
#endif
}
int64_t APS5_VABI libc_time_nid_postfix(int64_t* timer) {
std::time_t t = std::time(nullptr);
if (timer != nullptr) *timer = static_cast<int64_t>(t);
return static_cast<int64_t>(t);
}
int64_t APS5_VABI time_nid_postfix(int64_t* timer) {
return libc_time_nid_postfix(timer);
}
int64_t APS5_VABI _Xtime_get_ticks_nid_postfix() {
#ifdef _WIN32
FILETIME ft{};
GetSystemTimePreciseAsFileTime(&ft);
const uint64_t t = ((static_cast<uint64_t>(ft.dwHighDateTime) << 32) | ft.dwLowDateTime) - 116444736000000000ULL;
return static_cast<int64_t>(t / 10);
#else
timespec now{};
clock_gettime(CLOCK_REALTIME, &now);
return static_cast<int64_t>(now.tv_sec) * 1000000 + now.tv_nsec / 1000;
#endif
}
double APS5_VABI libc_difftime_nid_postfix(int64_t time1, int64_t time0) {
if (time1 >= time0) return static_cast<double>(static_cast<uint64_t>(time1) - static_cast<uint64_t>(time0));
return -static_cast<double>(static_cast<uint64_t>(time0) - static_cast<uint64_t>(time1));
}
double APS5_VABI difftime_nid_postfix(int64_t time1, int64_t time0) {
return libc_difftime_nid_postfix(time1, time0);
}
GuestTm* APS5_VABI libc_gmtime_nid_postfix(const int64_t* timer) {
static thread_local GuestTm result;
return gmtime_s_nid_postfix(timer, &result);
}
GuestTm* APS5_VABI libc_localtime_nid_postfix(const int64_t* timer) {
static thread_local GuestTm result;
return localtime_s_nid_postfix(timer, &result);
}
GuestTm* APS5_VABI localtime_nid_postfix(const int64_t* timer) {
return libc_localtime_nid_postfix(timer);
}
GuestTm* APS5_VABI localtime_s_nid_postfix(const int64_t* timer, GuestTm* result) {
if (timer == nullptr || result == nullptr) return nullptr;
std::tm host{};
#ifdef _WIN32
const std::int64_t cycles = *timer >= 0 && *timer <= HOST_LAST_SECOND ? 0 : calendarCycles(*timer, CALENDAR_CYCLE_SECONDS);
const std::time_t t = static_cast<std::time_t>(*timer - cycles * CALENDAR_CYCLE_SECONDS);
if (localtime_s(&host, &t) != 0) return nullptr;
const std::int64_t year = host.tm_year + cycles * CALENDAR_CYCLE_YEARS;
if (year < std::numeric_limits<int>::min() || year > std::numeric_limits<int>::max()) {
errno = EINVAL;
return nullptr;
}
host.tm_year = static_cast<int>(year);
copyCalendarFields(host, *result);
long standardBias = 0;
long daylightBias = 0;
_get_timezone(&standardBias);
_get_dstbias(&daylightBias);
const bool daylight = host.tm_isdst > 0;
result->tm_gmtoff = -static_cast<std::int64_t>(standardBias + (daylight ? daylightBias : 0));
result->tm_zone = _tzname[daylight ? 1 : 0];
#else
const std::time_t t = static_cast<std::time_t>(*timer);
if (localtime_r(&t, &host) == nullptr) return nullptr;
copyCalendarFields(host, *result);
result->tm_gmtoff = host.tm_gmtoff;
result->tm_zone = host.tm_zone;
#endif
return result;
}
GuestTm* APS5_VABI gmtime_s_nid_postfix(const int64_t* timer, GuestTm* result) {
if (timer == nullptr || result == nullptr) return nullptr;
if (!utcCalendarFields(*timer, *result)) return nullptr;
result->tm_gmtoff = 0;
result->tm_zone = "UTC";
return result;
}
GuestTm* APS5_VABI gmtime_nid_postfix(const int64_t* timer) {
return libc_gmtime_nid_postfix(timer);
}
int64_t APS5_VABI libc_mktime_nid_postfix(GuestTm* timeptr) {
std::tm host = toHostTm(*timeptr);
host.tm_wday = -1;
int64_t moved = 0;
#ifdef _WIN32
const bool ordinary = host.tm_year > 70 && host.tm_year < 1100 && host.tm_mon >= 0 && host.tm_mon < 12 && host.tm_mday >= 1 && host.tm_mday <= 31
&& host.tm_hour >= 0 && host.tm_hour < 24 && host.tm_min >= 0 && host.tm_min < 60 && host.tm_sec >= 0 && host.tm_sec <= 60;
const std::int64_t approximate = ordinary ? SECONDS_PER_DAY : utcSeconds(host);
if (approximate < SECONDS_PER_DAY || approximate > HOST_LAST_SECOND - SECONDS_PER_DAY) {
moved = calendarCycles(approximate - SECONDS_PER_DAY, CALENDAR_CYCLE_SECONDS) * CALENDAR_CYCLE_SECONDS;
GuestTm window{};
if (!utcCalendarFields(approximate - moved, window)) return -1;
window.tm_isdst = host.tm_isdst;
host = toHostTm(window);
host.tm_wday = -1;
}
#endif
const std::time_t t = std::mktime(&host);
if (t == static_cast<std::time_t>(-1) && host.tm_wday == -1) return -1;
const int64_t time = static_cast<int64_t>(t) + moved;
GuestTm normalized{};
if (localtime_s_nid_postfix(&time, &normalized) == nullptr) return -1;
*timeptr = normalized;
return time;
}
int64_t APS5_VABI mktime_nid_postfix(GuestTm* timeptr) {
return libc_mktime_nid_postfix(timeptr);
}
size_t APS5_VABI libc_strftime_nid_postfix(char* str, size_t count, const char* format, const GuestTm* timeptr) {
const std::tm host = toHostTm(*timeptr);
if (!requiresFreeBsdExpansion(format)) return std::strftime(str, count, format, &host);
const std::string expanded = expandFreeBsdConversions(format, *timeptr);
return std::strftime(str, count, expanded.c_str(), &host);
}
char* APS5_VABI asctime_nid_postfix(const GuestTm* timeptr) {
static thread_local char result[ASCTIME_SIZE];
const char* weekday = timeptr->tm_wday >= 0 && timeptr->tm_wday < 7 ? WEEKDAY_NAMES[timeptr->tm_wday] : "???";
const char* month = timeptr->tm_mon >= 0 && timeptr->tm_mon < 12 ? MONTH_NAMES[timeptr->tm_mon] : "???";
std::snprintf(result, sizeof(result), "%s %s%3d %.2d:%.2d:%.2d %lld\n", weekday, month, timeptr->tm_mday,
timeptr->tm_hour, timeptr->tm_min, timeptr->tm_sec, 1900LL + timeptr->tm_year);
return result;
}
char* APS5_VABI ctime_nid_postfix(const int64_t* timer) {
if (const GuestTm* local = localtime_nid_postfix(timer)) return asctime_nid_postfix(local);
static char unknown[26];
std::memcpy(unknown, "??? ??? ?? ??:??:?? ????\n", sizeof(unknown));
errno = EINVAL;
return unknown;
}
size_t APS5_VABI strftime_nid_postfix(char* str, size_t count, const char* format, const GuestTm* timeptr) {
return libc_strftime_nid_postfix(str, count, format, timeptr);
}
// The guest's CLOCKS_PER_SEC is 1000000: clock() reports process CPU time in microseconds.
int64_t APS5_VABI clock_nid_postfix() {
#ifdef _WIN32
FILETIME creation, exit, kernel, user;
if (!GetProcessTimes(GetCurrentProcess(), &creation, &exit, &kernel, &user)) return -1;
const auto toTicks = [](const FILETIME& time) { return (static_cast<uint64_t>(time.dwHighDateTime) << 32) | time.dwLowDateTime; };
return static_cast<int64_t>((toTicks(kernel) + toTicks(user)) / 10);
#else
return static_cast<int64_t>(static_cast<double>(std::clock()) * 1000000.0 / CLOCKS_PER_SEC);
#endif
}
int APS5_VABI ctime_s_nid_postfix(char* buffer, std::size_t size, const std::int64_t* time) {
return SonyCtimeS(buffer, size, time);
}
int APS5_VABI asctime_s_nid_postfix(char* buffer, std::size_t size, const GuestTm* time) {
return SonyAsctimeS(buffer, size, time);
}
}