#include #include #include #include #include #include #include #include #ifdef _WIN32 #include #endif #include "prx/libc/include/General.hpp" #include "prx/libc/include/GuestTime.hpp" #include "prx/libc/include/SonyCrt.hpp" namespace { constexpr std::int64_t SECONDS_PER_DAY = 86400; constexpr std::int64_t DAYS_FROM_MARCH_TO_YEAR_END = 306; 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(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::min() || tmYear > std::numeric_limits::max()) return false; result.tm_sec = static_cast(secondOfDay % 60); result.tm_min = static_cast(secondOfDay / 60 % 60); result.tm_hour = static_cast(secondOfDay / 3600); result.tm_mday = static_cast(dayOfMarchYear - (153 * marchMonth + 2) / 5 + 1); result.tm_mon = static_cast(month - 1); result.tm_year = static_cast(tmYear); result.tm_wday = static_cast((days % 7 + 11) % 7); result.tm_yday = static_cast(month <= 2 ? dayOfMarchYear - DAYS_FROM_MARCH_TO_YEAR_END : dayOfMarchYear + 59 + (isLeapYear(year) ? 1 : 0)); result.tm_isdst = 0; return true; } } #ifdef __linux__ #include 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 == '+'; } bool isModifiedPercent(const char* afterPercent) { return (afterPercent[0] == 'E' || afterPercent[0] == 'O') && afterPercent[1] == '%'; } std::size_t freeBsdConversionLength(const char* afterPercent, char& conversion) { const char* cursor = afterPercent; if ((*cursor == 'E' || *cursor == 'O') && isFreeBsdConversion(cursor[1])) ++cursor; if (!isFreeBsdConversion(*cursor)) return 0; conversion = *cursor; return static_cast(cursor - afterPercent) + 1; } bool hasFreeBsdConversion(const char* format) { while (*format != '\0') { if (*format++ != '%' || *format == '\0') continue; char conversion = 0; if (freeBsdConversionLength(format, conversion) != 0) return true; format += isModifiedPercent(format) ? 2 : 1; } 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 != '%' || *format == '\0') { result += next; continue; } if (isModifiedPercent(format)) { result += '%'; result += *format++; result += *format++; continue; } char conversion = 0; const std::size_t length = freeBsdConversionLength(format, conversion); if (length == 0) { result += '%'; result += *format++; continue; } format += length; char text[32]; switch (conversion) { 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(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(t); return static_cast(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(ft.dwHighDateTime) << 32) | ft.dwLowDateTime) - 116444736000000000ULL; return static_cast(t / 10); #else timespec now{}; clock_gettime(CLOCK_REALTIME, &now); return static_cast(now.tv_sec) * 1000000 + now.tv_nsec / 1000; #endif } double APS5_VABI libc_difftime_nid_postfix(int64_t time1, int64_t time0) { return std::difftime(static_cast(time1), static_cast(time0)); } double APS5_VABI difftime_nid_postfix(int64_t time1, int64_t time0) { return std::difftime(static_cast(time1), static_cast(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; const std::time_t t = static_cast(*timer); std::tm host{}; #ifdef _WIN32 if (localtime_s(&host, &t) != 0) return nullptr; 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(standardBias + (daylight ? daylightBias : 0)); result->tm_zone = _tzname[daylight ? 1 : 0]; #else 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); const std::time_t t = std::mktime(&host); if (t == static_cast(-1)) return -1; const int64_t time = static_cast(t); localtime_s_nid_postfix(&time, timeptr); 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 (!hasFreeBsdConversion(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) { const std::tm host = toHostTm(*timeptr); return std::asctime(&host); } 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(time.dwHighDateTime) << 32) | time.dwLowDateTime; }; return static_cast((toTicks(kernel) + toTicks(user)) / 10); #else return static_cast(static_cast(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); } }