Merge pull request #804 from adriadam10/feat/rtc-format-rfc2822

feat(rtc): implement sceRtcFormatRFC2822 and the RFC 2822/3339 local-time formatters
This commit is contained in:
Dean 2026-10-06 22:31:59 +13:00 committed by GitHub
commit 1e6b1cb7eb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 90 additions and 0 deletions

View File

@ -108,6 +108,13 @@ std::int64_t localOffsetSeconds(std::uint64_t utcTick) {
return localSeconds - static_cast<std::int64_t>(seconds);
}
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"};
int localOffsetMinutes(const RtcTick& utc) {
return static_cast<int>(localOffsetSeconds(utc.tick) / 60);
}
int addTicks(RtcTick* dst, const RtcTick* src, std::int64_t count, std::int64_t unit) {
if (!dst || !src) return SCE_RTC_ERROR_INVALID_POINTER;
if (src->tick > MAX_TICK) return SCE_RTC_ERROR_INVALID_VALUE;
@ -378,6 +385,28 @@ int APS5_VABI sceRtcFormatRFC3339(char* date_time, const RtcTick* utc, int time_
return 0;
}
int APS5_VABI sceRtcFormatRFC3339LocalTime(char* date_time, const RtcTick* utc) {
if (!date_time || !utc) return SCE_RTC_ERROR_INVALID_POINTER;
return sceRtcFormatRFC3339(date_time, utc, localOffsetMinutes(*utc));
}
int APS5_VABI sceRtcFormatRFC2822(char* date_time, const RtcTick* utc, int time_zone_minutes) {
if (!date_time || !utc) return SCE_RTC_ERROR_INVALID_POINTER;
if (time_zone_minutes < -1439 || time_zone_minutes > 1439) return SCE_RTC_ERROR_INVALID_VALUE;
RtcTick local{};
if (const int result = addTicks(&local, utc, time_zone_minutes, TICKS_PER_MINUTE); result != 0) return result;
const RtcDateTime time = fromTick(local.tick);
const int offset = time_zone_minutes < 0 ? -time_zone_minutes : time_zone_minutes;
std::snprintf(date_time, 32, "%s, %02u %s %04u %02u:%02u:%02u %c%02d%02d", WEEKDAY_NAMES[(local.tick / TICKS_PER_DAY + 1) % 7], time.day,
MONTH_NAMES[time.month - 1], time.year, time.hour, time.minute, time.second, time_zone_minutes < 0 ? '-' : '+', offset / 60, offset % 60);
return 0;
}
int APS5_VABI sceRtcFormatRFC2822LocalTime(char* date_time, const RtcTick* utc) {
if (!date_time || !utc) return SCE_RTC_ERROR_INVALID_POINTER;
return sceRtcFormatRFC2822(date_time, utc, localOffsetMinutes(*utc));
}
int APS5_VABI sceRtcParseRFC3339(RtcTick* utc, const char* date_time) {
if (!utc || !date_time) return SCE_RTC_ERROR_INVALID_POINTER;
const char* cursor = date_time;

View File

@ -3,6 +3,7 @@
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <exception>
#include <initializer_list>
#include <limits>
@ -30,10 +31,23 @@ int APS5_VABI sceRtcTickAddSeconds(RtcTick*, const RtcTick*, std::int64_t);
int APS5_VABI sceRtcTickAddDays(RtcTick*, const RtcTick*, std::int32_t);
int APS5_VABI sceRtcTickAddMonths(RtcTick*, const RtcTick*, std::int32_t);
int APS5_VABI sceRtcTickAddYears(RtcTick*, const RtcTick*, std::int16_t);
int APS5_VABI sceRtcFormatRFC3339LocalTime(char*, const RtcTick*);
int APS5_VABI sceRtcFormatRFC2822(char*, const RtcTick*, int);
int APS5_VABI sceRtcFormatRFC2822LocalTime(char*, const RtcTick*);
}
static void Require(bool value) { if (!value) std::abort(); }
static void SetTimeZone(const char* zone) {
#ifdef _WIN32
_putenv_s("TZ", zone);
_tzset();
#else
setenv("TZ", zone, 1);
tzset();
#endif
}
static bool Equal(const RtcDateTime& left, const RtcDateTime& right) {
return left.year == right.year && left.month == right.month && left.day == right.day && left.hour == right.hour
&& left.minute == right.minute && left.second == right.second && left.microsecond == right.microsecond;
@ -241,4 +255,50 @@ int main() {
RtcTick back{};
Require(sceRtcConvertUtcToLocalTime(&now, &local) == 0);
Require(sceRtcConvertLocalTimeToUtc(&local, &back) == 0 && back.tick == now.tick);
char rfc2822[32];
tick.tick = leapDayTick;
Require(sceRtcFormatRFC2822(rfc2822, &tick, 0) == 0 && std::strcmp(rfc2822, "Thu, 29 Feb 2024 12:34:56 +0000") == 0);
Require(sceRtcFormatRFC2822(rfc2822, &tick, 90) == 0 && std::strcmp(rfc2822, "Thu, 29 Feb 2024 14:04:56 +0130") == 0);
Require(sceRtcFormatRFC2822(rfc2822, &tick, -300) == 0 && std::strcmp(rfc2822, "Thu, 29 Feb 2024 07:34:56 -0500") == 0);
Require(sceRtcFormatRFC2822(rfc2822, &tick, -30) == 0 && std::strcmp(rfc2822, "Thu, 29 Feb 2024 12:04:56 -0030") == 0);
Require(sceRtcFormatRFC2822(rfc2822, &tick, 65) == 0 && std::strcmp(rfc2822, "Thu, 29 Feb 2024 13:39:56 +0105") == 0);
Require(sceRtcFormatRFC2822(rfc2822, &tick, 1439) == 0 && std::strcmp(rfc2822, "Fri, 01 Mar 2024 12:33:56 +2359") == 0);
Require(sceRtcFormatRFC2822(rfc2822, &tick, -1439) == 0 && std::strcmp(rfc2822, "Wed, 28 Feb 2024 12:35:56 -2359") == 0);
const char* week[] = {"Sun, 03 Mar", "Mon, 04 Mar", "Tue, 05 Mar", "Wed, 06 Mar", "Thu, 07 Mar", "Fri, 08 Mar", "Sat, 09 Mar"};
for (int day = 0; day < 7; ++day) {
const RtcDateTime date{2024, 3, static_cast<std::uint16_t>(3 + day), 0, 0, 0, 0};
Require(sceRtcGetTick(&date, &tick) == 0 && sceRtcFormatRFC2822(rfc2822, &tick, 0) == 0);
Require(std::strncmp(rfc2822, week[day], std::strlen(week[day])) == 0);
}
tick.tick = 0;
Require(sceRtcFormatRFC2822(rfc2822, &tick, 0) == 0 && std::strcmp(rfc2822, "Mon, 01 Jan 0001 00:00:00 +0000") == 0);
Require(sceRtcFormatRFC2822(rfc2822, &tick, -1) == invalidValue);
tick.tick = maxTick;
Require(sceRtcFormatRFC2822(rfc2822, &tick, 0) == 0 && std::strcmp(rfc2822, "Fri, 31 Dec 9999 23:59:59 +0000") == 0);
Require(sceRtcFormatRFC2822(rfc2822, &tick, 1) == invalidValue);
tick.tick = leapDayTick;
for (int offset : {1440, -1440, std::numeric_limits<int>::min(), std::numeric_limits<int>::max()}) {
std::memset(rfc2822, 'x', sizeof(rfc2822));
char original[sizeof(rfc2822)];
std::memcpy(original, rfc2822, sizeof(rfc2822));
Require(sceRtcFormatRFC2822(rfc2822, &tick, offset) == invalidValue);
Require(std::memcmp(rfc2822, original, sizeof(rfc2822)) == 0);
}
Require(sceRtcFormatRFC2822(nullptr, &tick, 0) == invalidPointer);
Require(sceRtcFormatRFC2822(rfc2822, nullptr, 0) == invalidPointer);
SetTimeZone("XXX-5:30");
Require(sceRtcFormatRFC2822LocalTime(rfc2822, &tick) == 0 && std::strcmp(rfc2822, "Thu, 29 Feb 2024 18:04:56 +0530") == 0);
Require(sceRtcFormatRFC3339LocalTime(text, &tick) == 0 && std::strcmp(text, "2024-02-29T18:04:56.78+05:30") == 0);
SetTimeZone("XXX+3");
Require(sceRtcFormatRFC2822LocalTime(rfc2822, &tick) == 0 && std::strcmp(rfc2822, "Thu, 29 Feb 2024 09:34:56 -0300") == 0);
Require(sceRtcFormatRFC3339LocalTime(text, &tick) == 0 && std::strcmp(text, "2024-02-29T09:34:56.78-03:00") == 0);
SetTimeZone("UTC0");
Require(sceRtcFormatRFC2822LocalTime(rfc2822, &tick) == 0 && std::strcmp(rfc2822, "Thu, 29 Feb 2024 12:34:56 +0000") == 0);
Require(sceRtcFormatRFC3339LocalTime(text, &tick) == 0 && std::strcmp(text, "2024-02-29T12:34:56.78Z") == 0);
Require(sceRtcFormatRFC2822LocalTime(nullptr, &tick) == invalidPointer);
Require(sceRtcFormatRFC2822LocalTime(rfc2822, nullptr) == invalidPointer);
Require(sceRtcFormatRFC3339LocalTime(nullptr, &tick) == invalidPointer);
Require(sceRtcFormatRFC3339LocalTime(text, nullptr) == invalidPointer);
}

View File

@ -119,6 +119,7 @@ Throughout the project, every function at every stage either **does exactly what
- [sceSystemServiceReenableMediaPlay](../../core/libs/prx/libSceSystemService/Export.cpp) (libSceSystemService) - unknown signature
- [sceSystemServiceParamGetString](../../core/libs/prx/libSceSystemService/Export.cpp) (libSceSystemService) - the system name (parameter 6) is the fixed `PS5`, not the console's name; other parameters and buffers of 1 to 64 bytes throw
- [sceVoiceSetMuteFlag](../../core/libs/prx/libSceVoice/Export.cpp) (libSceVoice) - unknown signature
- [sceRtcFormatRFC2822, sceRtcFormatRFC2822LocalTime, sceRtcFormatRFC3339LocalTime](../../core/libs/prx/libSceRtc/Export.cpp) (libSceRtc) - only shadPS4 (PS4) has bodies. A null tick returns `INVALID_POINTER` like the existing `sceRtcFormatRFC3339`, where shadPS4 formats the current time instead; offsets outside ±1439 minutes return `INVALID_VALUE` like `sceRtcFormatRFC3339`; the local-time variants use the host's offset at the given instant (shadPS4 uses `sceKernelGettimezone`, the current offset)
- [snwprintf_s](../../core/libs/prx/libc/src/FormattingWide.cpp) (libc) - follows C11 K.3.9.1.3 with `RSIZE_MAX` assumed to be `SIZE_MAX >> 1`. A runtime-constraint violation only returns a negative value: `set_constraint_handler_s` is not exported and the console's default handler is unknown. An invalid multibyte `%s` argument is copied byte by byte instead of being reported as an encoding error, as in `vswprintf`
- [libSceUlt queues](../../core/libs/prx/libSceUlt/Export.cpp) - the added `sceUltQueuePop`, `sceUltQueueTryPush`, `sceUltQueueDestroy` and `sceUltQueueDataResourcePoolDestroy` signatures are inferred from the existing push/pop and object APIs. Queue destruction is assumed to reject blocked callers with `ULT_ERROR_BUSY` and otherwise discard queued data; pool exhaustion returns `ULT_ERROR_AGAIN`. These rules, alignment checks and shared slot limits have not been confirmed on PS5 hardware or a title. Non-null queue and data-pool option parameters throw because their layouts are unknown.
- [_sceUlobjmgrRegisterObject](../../core/libs/prx/ulobjmgr/Export.cpp) (ulobjmgr) - parameters taken from shadPS4 (PS4): a nonzero 64-bit object, a nonzero 32-bit kind and a 32-bit id output; the meaning of the first two and the PS5 signature are unverified. The id bound of `_sceUlobjmgrUnregisterObject` (below 0x4000) and the raw `EINVAL` (22) return also come from shadPS4