SDL: Add time and realtime clock functions

From a6fbf0488c63645f90682c4bbe2fb963429bd08a Mon Sep 17 00:00:00 2001
From: Frank Praznik <[EMAIL REDACTED]>
Date: Thu, 29 Feb 2024 13:06:26 -0500
Subject: [PATCH] Add time and realtime clock functions

Adds functions to query the system's realtime clock, convert time intervals to/from a calendar date and time in either UTC or the local time, and perform time related calculations.

An SDL_Time type (a time interval represented in nanoseconds), and SDL_DateTime struct (broken down calendar date and time) were added to facilitate this functionality.

Querying the system time results in a value expressed in nanoseconds since the Unix epoch (Jan 1, 1970) in UTC +0000. Conversions to and from the various platform epochs and units are performed when required.

Any direct handling of timezones and DST were intentionally avoided. The offset from UTC is provided when converting from UTC to a local time by calculating the difference between the original UTC and the resulting local time, but no other timezone or DST information is used.

The preferred date formatting and 12/24 hour time for the system locale can be retrieved via global preferences.

Helper functions for obtaining the day of week or day or year for calendar date, and getting the number of days in a month in a given year are provided for convenience. These are simple, but useful for performing various time related calculations.

An automated test for time conversion is included, as is a simple standalone test to display the current system date and time onscreen along with a calendar, the rendering of which demonstrates the use of the utility functions (press up/down to increment or decrement the current month, and keys 1-5 to change the date and time formats).
---
 Android.mk                                    |   2 +
 CMakeLists.txt                                |  49 ++++
 VisualC-GDK/SDL/SDL.vcxproj                   |   3 +
 VisualC-GDK/SDL/SDL.vcxproj.filters           |   6 +
 VisualC-WinRT/SDL-UWP.vcxproj                 |   3 +
 VisualC-WinRT/SDL-UWP.vcxproj.filters         |  12 +
 VisualC/SDL/SDL.vcxproj                       |   3 +
 VisualC/SDL/SDL.vcxproj.filters               |  12 +
 .../testautomation/testautomation.vcxproj     |   1 +
 Xcode/SDL/SDL.xcodeproj/project.pbxproj       |  24 ++
 include/SDL3/SDL.h                            |   1 +
 include/SDL3/SDL_filesystem.h                 |  36 +--
 include/SDL3/SDL_stdinc.h                     |  10 +
 include/SDL3/SDL_time.h                       | 195 +++++++++++++++
 include/build_config/SDL_build_config.h.cmake |  11 +
 .../build_config/SDL_build_config_android.h   |  10 +
 include/build_config/SDL_build_config_ios.h   |   6 +
 include/build_config/SDL_build_config_macos.h |   3 +
 .../build_config/SDL_build_config_windows.h   |   3 +
 .../build_config/SDL_build_config_wingdk.h    |   3 +
 include/build_config/SDL_build_config_winrt.h |   3 +
 src/SDL.c                                     |   3 +
 src/dynapi/SDL_dynapi.sym                     |  10 +-
 src/dynapi/SDL_dynapi_overrides.h             |  10 +-
 src/dynapi/SDL_dynapi_procs.h                 |  10 +-
 src/filesystem/SDL_filesystem.c               |  32 ---
 src/filesystem/posix/SDL_sysfsops.c           |  14 +-
 src/filesystem/windows/SDL_sysfsops.c         |   6 +-
 src/time/SDL_time.c                           | 232 ++++++++++++++++++
 src/time/SDL_time_c.h                         |  39 +++
 src/time/n3ds/SDL_systime.c                   | 102 ++++++++
 src/time/ps2/SDL_systime.c                    |  65 +++++
 src/time/psp/SDL_systime.c                    | 136 ++++++++++
 src/time/unix/SDL_systime.c                   | 193 +++++++++++++++
 src/time/vita/SDL_systime.c                   | 135 ++++++++++
 src/time/windows/SDL_systime.c                | 139 +++++++++++
 test/CMakeLists.txt                           |   1 +
 test/testautomation.c                         |   1 +
 test/testautomation_suites.h                  |   1 +
 test/testautomation_time.c                    | 201 +++++++++++++++
 test/testtime.c                               | 214 ++++++++++++++++
 41 files changed, 1862 insertions(+), 78 deletions(-)
 create mode 100644 include/SDL3/SDL_time.h
 create mode 100644 src/time/SDL_time.c
 create mode 100644 src/time/SDL_time_c.h
 create mode 100644 src/time/n3ds/SDL_systime.c
 create mode 100644 src/time/ps2/SDL_systime.c
 create mode 100644 src/time/psp/SDL_systime.c
 create mode 100644 src/time/unix/SDL_systime.c
 create mode 100644 src/time/vita/SDL_systime.c
 create mode 100644 src/time/windows/SDL_systime.c
 create mode 100644 test/testautomation_time.c
 create mode 100644 test/testtime.c

diff --git a/Android.mk b/Android.mk
index 2b42555596f59..a60a0d3636fa3 100644
--- a/Android.mk
+++ b/Android.mk
@@ -60,6 +60,8 @@ LOCAL_SRC_FILES := \
 	$(wildcard $(LOCAL_PATH)/src/stdlib/*.c) \
 	$(wildcard $(LOCAL_PATH)/src/thread/*.c) \
 	$(wildcard $(LOCAL_PATH)/src/thread/pthread/*.c) \
+	$(wildcard $(LOCAL_PATH)/src/time/*.c) \
+	$(wildcard $(LOCAL_PATH)/src/time/unix/*.c) \
 	$(wildcard $(LOCAL_PATH)/src/timer/*.c) \
 	$(wildcard $(LOCAL_PATH)/src/timer/unix/*.c) \
 	$(wildcard $(LOCAL_PATH)/src/video/*.c) \
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 8017bc2b79881..9b288c932b677 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -497,6 +497,7 @@ sdl_glob_sources(
   "${SDL3_SOURCE_DIR}/src/stdlib/*.c"
   "${SDL3_SOURCE_DIR}/src/storage/*.c"
   "${SDL3_SOURCE_DIR}/src/thread/*.c"
+  "${SDL3_SOURCE_DIR}/src/time/*.c"
   "${SDL3_SOURCE_DIR}/src/timer/*.c"
   "${SDL3_SOURCE_DIR}/src/video/*.c"
   "${SDL3_SOURCE_DIR}/src/video/yuv2rgb/*.c"
@@ -1044,6 +1045,7 @@ if(SDL_LIBC)
     string.h
     strings.h
     sys/types.h
+    time.h
     wchar.h
   )
   foreach(_HEADER IN LISTS headers_to_check)
@@ -1105,6 +1107,9 @@ if(SDL_LIBC)
     check_symbol_exists(sigaction "signal.h" HAVE_SIGACTION)
     check_symbol_exists(setjmp "setjmp.h" HAVE_SETJMP)
     check_symbol_exists(nanosleep "time.h" HAVE_NANOSLEEP)
+    check_symbol_exists(gmtime_r "time.h" HAVE_GMTIME_R)
+    check_symbol_exists(localtime_r "time.h" HAVE_LOCALTIME_R)
+    check_symbol_exists(nl_langinfo "langinfo.h" HAVE_NL_LANGINFO)
     check_symbol_exists(sysconf "unistd.h" HAVE_SYSCONF)
     check_symbol_exists(sysctlbyname "sys/types.h;sys/sysctl.h" HAVE_SYSCTLBYNAME)
     check_symbol_exists(getauxval "sys/auxv.h" HAVE_GETAUXVAL)
@@ -1329,6 +1334,10 @@ if(ANDROID)
   sdl_glob_sources("${SDL3_SOURCE_DIR}/src/locale/android/*.c")
   set(HAVE_SDL_LOCALE TRUE)
 
+  set(SDL_TIME_UNIX 1)
+  sdl_glob_sources("${SDL3_SOURCE_DIR}/src/time/unix/*.c")
+  set(HAVE_SDL_TIME TRUE)
+
   set(SDL_TIMER_UNIX 1)
   sdl_glob_sources("${SDL3_SOURCE_DIR}/src/timer/unix/*.c")
   set(HAVE_SDL_TIMERS TRUE)
@@ -1484,6 +1493,10 @@ elseif(EMSCRIPTEN)
   sdl_glob_sources("${SDL3_SOURCE_DIR}/src/locale/emscripten/*.c")
   set(HAVE_SDL_LOCALE TRUE)
 
+  set(SDL_TIME_UNIX 1)
+  sdl_glob_sources("${SDL3_SOURCE_DIR}/src/time/unix/*.c")
+  set(HAVE_SDL_TIME TRUE)
+
   set(SDL_TIMER_UNIX 1)
   sdl_glob_sources("${SDL3_SOURCE_DIR}/src/timer/unix/*.c")
   set(HAVE_SDL_TIMERS TRUE)
@@ -1783,6 +1796,10 @@ elseif(UNIX AND NOT APPLE AND NOT RISCOS AND NOT HAIKU)
   sdl_sources("${SDL3_SOURCE_DIR}/src/filesystem/posix/SDL_sysfsops.c")
   set(HAVE_SDL_FSOPS TRUE)
 
+  set(SDL_TIME_UNIX 1)
+  sdl_glob_sources("${SDL3_SOURCE_DIR}/src/time/unix/*.c")
+  set(HAVE_SDL_TIME TRUE)
+
   set(SDL_TIMER_UNIX 1)
   sdl_glob_sources("${SDL3_SOURCE_DIR}/src/timer/unix/*.c")
   set(HAVE_SDL_TIMERS TRUE)
@@ -2030,6 +2047,10 @@ elseif(WINDOWS)
     )
   endif()
 
+  set(SDL_TIME_WINDOWS 1)
+  sdl_glob_sources("${SDL3_SOURCE_DIR}/src/time/windows/*.c")
+  set(HAVE_SDL_TIME TRUE)
+
   set(SDL_TIMER_WINDOWS 1)
   sdl_glob_sources("${SDL3_SOURCE_DIR}/src/timer/windows/*.c")
   set(HAVE_SDL_TIMERS TRUE)
@@ -2238,6 +2259,10 @@ elseif(APPLE)
   sdl_glob_sources("${SDL3_SOURCE_DIR}/src/locale/macos/*.m")
   set(HAVE_SDL_LOCALE TRUE)
 
+  set(SDL_TIME_UNIX 1)
+  sdl_glob_sources("${SDL3_SOURCE_DIR}/src/time/unix/*.c")
+  set(HAVE_SDL_TIME TRUE)
+
   set(SDL_TIMER_UNIX 1)
   sdl_glob_sources("${SDL3_SOURCE_DIR}/src/timer/unix/*.c")
   set(HAVE_SDL_TIMERS TRUE)
@@ -2459,6 +2484,10 @@ elseif(HAIKU)
   sdl_sources("${SDL3_SOURCE_DIR}/src/filesystem/posix/SDL_sysfsops.c")
   set(HAVE_SDL_FSOPS TRUE)
 
+  set(SDL_TIME_UNIX 1)
+  sdl_glob_sources("${SDL3_SOURCE_DIR}/src/time/unix/*.c")
+  set(HAVE_SDL_TIME TRUE)
+
   set(SDL_TIMER_HAIKU 1)
   sdl_glob_sources("${SDL3_SOURCE_DIR}/src/timer/haiku/*.c")
   set(HAVE_SDL_TIMERS TRUE)
@@ -2499,6 +2528,10 @@ elseif(RISCOS)
   sdl_sources("${SDL3_SOURCE_DIR}/src/filesystem/posix/SDL_sysfsops.c")
   set(HAVE_SDL_FSOPS TRUE)
 
+  set(SDL_TIME_UNIX 1)
+  sdl_glob_sources("${SDL3_SOURCE_DIR}/src/time/unix/*.c")
+  set(HAVE_SDL_TIME TRUE)
+
   set(SDL_TIMER_UNIX 1)
   sdl_glob_sources("${SDL3_SOURCE_DIR}/src/timer/unix/*.c")
   set(HAVE_SDL_TIMERS TRUE)
@@ -2566,6 +2599,10 @@ elseif(VITA)
   sdl_glob_sources("${SDL3_SOURCE_DIR}/src/locale/vita/*.c")
   set(HAVE_SDL_LOCALE TRUE)
 
+  set(SDL_TIME_VITA 1)
+  sdl_glob_sources("${SDL3_SOURCE_DIR}/src/time/vita/*.c")
+  set(HAVE_SDL_TIME TRUE)
+
   set(SDL_TIMER_VITA 1)
   sdl_glob_sources("${SDL3_SOURCE_DIR}/src/timer/vita/*.c")
   set(HAVE_SDL_TIMERS TRUE)
@@ -2699,6 +2736,10 @@ elseif(PSP)
   )
   set(HAVE_SDL_THREADS TRUE)
 
+  set(SDL_TIME_PSP 1)
+  sdl_glob_sources("${SDL3_SOURCE_DIR}/src/time/psp/*.c")
+  set(HAVE_SDL_TIME TRUE)
+
   set(SDL_TIMER_PSP 1)
   sdl_glob_sources("${SDL3_SOURCE_DIR}/src/timer/psp/*.c")
   set(HAVE_SDL_TIMERS TRUE)
@@ -2761,6 +2802,10 @@ elseif(PS2)
   )
   set(HAVE_SDL_THREADS TRUE)
 
+  set(SDL_TIME_PS2 1)
+  sdl_glob_sources("${SDL3_SOURCE_DIR}/src/time/ps2/*.c")
+  set(HAVE_SDL_TIME TRUE)
+
   set(SDL_TIMER_PS2 1)
   sdl_glob_sources("${SDL3_SOURCE_DIR}/src/timer/ps2/*.c")
   set(HAVE_SDL_TIMERS TRUE)
@@ -2820,6 +2865,10 @@ elseif(N3DS)
   )
   set(HAVE_SDL_THREADS TRUE)
 
+  set(SDL_TIME_N3DS 1)
+  sdl_glob_sources("${SDL3_SOURCE_DIR}/src/time/n3ds/*.c")
+  set(HAVE_SDL_TIME TRUE)
+
   set(SDL_TIMER_N3DS 1)
   sdl_glob_sources("${SDL3_SOURCE_DIR}/src/timer/n3ds/*.c")
   set(HAVE_SDL_TIMERS TRUE)
diff --git a/VisualC-GDK/SDL/SDL.vcxproj b/VisualC-GDK/SDL/SDL.vcxproj
index 948e75578ab4e..53926d49a16d2 100644
--- a/VisualC-GDK/SDL/SDL.vcxproj
+++ b/VisualC-GDK/SDL/SDL.vcxproj
@@ -384,6 +384,7 @@
     <ClInclude Include="..\..\include\SDL3\SDL_test_memory.h" />
     <ClInclude Include="..\..\include\SDL3\SDL_test_random.h" />
     <ClInclude Include="..\..\include\SDL3\SDL_thread.h" />
+    <ClInclude Include="..\..\include\SDL3\SDL_time.h" />
     <ClInclude Include="..\..\include\SDL3\SDL_timer.h" />
     <ClInclude Include="..\..\include\SDL3\SDL_touch.h" />
     <ClInclude Include="..\..\include\SDL3\SDL_types.h" />
@@ -795,6 +796,8 @@
     <ClCompile Include="..\..\src\thread\windows\SDL_systls.c" />
     <ClCompile Include="..\..\src\timer\SDL_timer.c" />
     <ClCompile Include="..\..\src\timer\windows\SDL_systimer.c" />
+    <ClCompile Include="..\..\src\time\SDL_time.c" />
+    <ClCompile Include="..\..\src\time\windows\SDL_systime.c" />
     <ClCompile Include="..\..\src\video\dummy\SDL_nullevents.c" />
     <ClCompile Include="..\..\src\video\dummy\SDL_nullframebuffer.c" />
     <ClCompile Include="..\..\src\video\dummy\SDL_nullvideo.c" />
diff --git a/VisualC-GDK/SDL/SDL.vcxproj.filters b/VisualC-GDK/SDL/SDL.vcxproj.filters
index bc62d7a0ba469..39aac2b531dce 100644
--- a/VisualC-GDK/SDL/SDL.vcxproj.filters
+++ b/VisualC-GDK/SDL/SDL.vcxproj.filters
@@ -171,6 +171,12 @@
     <ClCompile Include="..\..\src\thread\windows\SDL_systls.c" />
     <ClCompile Include="..\..\src\timer\SDL_timer.c" />
     <ClCompile Include="..\..\src\timer\windows\SDL_systimer.c" />
+    <ClCompile Include="..\..\src\time\SDL_time.c">
+      <Filter>time</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\src\time\windows\SDL_systime.c">
+      <Filter>time\windows</Filter>
+    </ClCompile>
     <ClCompile Include="..\..\src\video\dummy\SDL_nullevents.c" />
     <ClCompile Include="..\..\src\video\dummy\SDL_nullframebuffer.c" />
     <ClCompile Include="..\..\src\video\dummy\SDL_nullvideo.c" />
diff --git a/VisualC-WinRT/SDL-UWP.vcxproj b/VisualC-WinRT/SDL-UWP.vcxproj
index 4948abf9b5dc6..75ffad0c47a69 100644
--- a/VisualC-WinRT/SDL-UWP.vcxproj
+++ b/VisualC-WinRT/SDL-UWP.vcxproj
@@ -86,6 +86,7 @@
     <ClInclude Include="..\include\SDL3\SDL_surface.h" />
     <ClInclude Include="..\include\SDL3\SDL_system.h" />
     <ClInclude Include="..\include\SDL3\SDL_thread.h" />
+    <ClInclude Include="..\include\SDL3\SDL_time.h" />
     <ClInclude Include="..\include\SDL3\SDL_timer.h" />
     <ClInclude Include="..\include\SDL3\SDL_touch.h" />
     <ClInclude Include="..\include\SDL3\SDL_types.h" />
@@ -521,6 +522,8 @@
     </ClCompile>
     <ClCompile Include="..\src\timer\SDL_timer.c" />
     <ClCompile Include="..\src\timer\windows\SDL_systimer.c" />
+    <ClCompile Include="..\src\time\SDL_time.c" />
+    <ClCompile Include="..\src\time\windows\SDL_systime.c" />
     <ClCompile Include="..\src\video\dummy\SDL_nullevents.c" />
     <ClCompile Include="..\src\video\dummy\SDL_nullframebuffer.c" />
     <ClCompile Include="..\src\video\dummy\SDL_nullvideo.c" />
diff --git a/VisualC-WinRT/SDL-UWP.vcxproj.filters b/VisualC-WinRT/SDL-UWP.vcxproj.filters
index ca56e1757ea81..144061e656205 100644
--- a/VisualC-WinRT/SDL-UWP.vcxproj.filters
+++ b/VisualC-WinRT/SDL-UWP.vcxproj.filters
@@ -25,6 +25,12 @@
     <Filter Include="filesystem\windows">
       <UniqueIdentifier>{0000bc587ef6c558d75ce2e620cb0000}</UniqueIdentifier>
     </Filter>
+    <Filter Include="time">
+      <UniqueIdentifier>{0000948771d0040a6a55997a7f1e0000}</UniqueIdentifier>
+    </Filter>
+    <Filter Include="time\windows">
+      <UniqueIdentifier>{0000012051ca8361c8e1013aee1d0000}</UniqueIdentifier>
+    </Filter>
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="..\include\SDL3\SDL_begin_code.h">
@@ -792,6 +798,12 @@
     <ClCompile Include="..\src\timer\windows\SDL_systimer.c">
       <Filter>Source Files</Filter>
     </ClCompile>
+    <ClCompile Include="..\src\time\SDL_time.c">
+      <Filter>time</Filter>
+    </ClCompile>
+    <ClCompile Include="..\src\time\windows\SDL_systime.c">
+      <Filter>time\windows</Filter>
+    </ClCompile>
     <ClCompile Include="..\src\video\dummy\SDL_nullevents.c">
       <Filter>Source Files</Filter>
     </ClCompile>
diff --git a/VisualC/SDL/SDL.vcxproj b/VisualC/SDL/SDL.vcxproj
index 24c397472c995..d50987c370a9e 100644
--- a/VisualC/SDL/SDL.vcxproj
+++ b/VisualC/SDL/SDL.vcxproj
@@ -307,6 +307,7 @@
     <ClInclude Include="..\..\include\SDL3\SDL_test_memory.h" />
     <ClInclude Include="..\..\include\SDL3\SDL_test_random.h" />
     <ClInclude Include="..\..\include\SDL3\SDL_thread.h" />
+    <ClInclude Include="..\..\include\SDL3\SDL_time.h" />
     <ClInclude Include="..\..\include\SDL3\SDL_timer.h" />
     <ClInclude Include="..\..\include\SDL3\SDL_touch.h" />
     <ClInclude Include="..\..\include\SDL3\SDL_version.h" />
@@ -653,6 +654,8 @@
     <ClCompile Include="..\..\src\thread\windows\SDL_systls.c" />
     <ClCompile Include="..\..\src\timer\SDL_timer.c" />
     <ClCompile Include="..\..\src\timer\windows\SDL_systimer.c" />
+    <ClCompile Include="..\..\src\time\SDL_time.c" />
+    <ClCompile Include="..\..\src\time\windows\SDL_systime.c" />
     <ClCompile Include="..\..\src\video\dummy\SDL_nullevents.c" />
     <ClCompile Include="..\..\src\video\dummy\SDL_nullframebuffer.c" />
     <ClCompile Include="..\..\src\video\dummy\SDL_nullvideo.c" />
diff --git a/VisualC/SDL/SDL.vcxproj.filters b/VisualC/SDL/SDL.vcxproj.filters
index 5d3e43001a086..766c3bc8d4580 100644
--- a/VisualC/SDL/SDL.vcxproj.filters
+++ b/VisualC/SDL/SDL.vcxproj.filters
@@ -190,6 +190,12 @@
     <Filter Include="render\vulkan">
       <UniqueIdentifier>{5115ba31-20f8-4eab-a8c5-6a572ab78ff7}</UniqueIdentifier>
     </Filter>
+    <Filter Include="time">
+      <UniqueIdentifier>{00003288226ff86b99eee5b443e90000}</UniqueIdentifier>
+    </Filter>
+    <Filter Include="time\windows">
+      <UniqueIdentifier>{0000d7fda065b13b0ca4ab262c380000}</UniqueIdentifier>
+    </Filter>
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="..\..\include\SDL3\SDL_begin_code.h">
@@ -1183,6 +1189,12 @@
     <ClCompile Include="..\..\src\joystick\virtual\SDL_virtualjoystick.c">
       <Filter>joystick\virtual</Filter>
     </ClCompile>
+    <ClCompile Include="..\..\src\time\SDL_time.c">
+      <Filter>time</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\src\time\windows\SDL_systime.c">
+      <Filter>time\windows</Filter>
+    </ClCompile>
     <ClCompile Include="..\..\src\video\SDL_RLEaccel.c">
       <Filter>video</Filter>
     </ClCompile>
diff --git a/VisualC/tests/testautomation/testautomation.vcxproj b/VisualC/tests/testautomation/testautomation.vcxproj
index 7fc14cb70f4e0..84539df4320cd 100644
--- a/VisualC/tests/testautomation/testautomation.vcxproj
+++ b/VisualC/tests/testautomation/testautomation.vcxproj
@@ -225,6 +225,7 @@
     <ClCompile Include="..\..\..\test\testautomation_sdltest.c" />
     <ClCompile Include="..\..\..\test\testautomation_stdlib.c" />
     <ClCompile Include="..\..\..\test\testautomation_surface.c" />
+    <ClCompile Include="..\..\..\test\testautomation_time.c" />
     <ClCompile Include="..\..\..\test\testautomation_timer.c" />
     <ClCompile Include="..\..\..\test\testautomation_video.c" />
     <ClCompile Include="..\..\..\test\testautomation_subsystems.c" />
diff --git a/Xcode/SDL/SDL.xcodeproj/project.pbxproj b/Xcode/SDL/SDL.xcodeproj/project.pbxproj
index 1f066dde064e3..d39f5b860bd24 100644
--- a/Xcode/SDL/SDL.xcodeproj/project.pbxproj
+++ b/Xcode/SDL/SDL.xcodeproj/project.pbxproj
@@ -510,6 +510,8 @@
 		000080903BC03006F24E0000 /* SDL_filesystem.c in Sources */ = {isa = PBXBuildFile; fileRef = 00002B010DB1A70931C20000 /* SDL_filesystem.c */; };
 		00000D60346481EEC8FB0000 /* SDL_filesystem.h in Headers */ = {isa = PBXBuildFile; fileRef = 0000BE1BF5193C6D0F4F0000 /* SDL_filesystem.h */; };
 		0000481D255AF155B42C0000 /* SDL_sysfsops.c in Sources */ = {isa = PBXBuildFile; fileRef = 0000F4E6AA3EF99DA3C80000 /* SDL_sysfsops.c */; };
+		0000494CC93F3E624D3C0000 /* SDL_systime.c in Sources */ = {isa = PBXBuildFile; fileRef = 00003F472C51CE7DF6160000 /* SDL_systime.c */; };
+		000095FA1BDE436CF3AF0000 /* SDL_time.c in Sources */ = {isa = PBXBuildFile; fileRef = 0000641A9BAC11AB3FBE0000 /* SDL_time.c */; };
 /* End PBXBuildFile section */
 
 /* Begin PBXContainerItemProxy section */
@@ -1048,6 +1050,8 @@
 		00002B010DB1A70931C20000 /* SDL_filesystem.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = SDL_filesystem.c; path = SDL_filesystem.c; sourceTree = "<group>"; };
 		0000BE1BF5193C6D0F4F0000 /* SDL_filesystem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SDL_filesystem.h; path = SDL_filesystem.h; sourceTree = "<group>"; };
 		0000F4E6AA3EF99DA3C80000 /* SDL_sysfsops.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = SDL_sysfsops.c; path = SDL_sysfsops.c; sourceTree = "<group>"; };
+		00003F472C51CE7DF6160000 /* SDL_systime.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = SDL_systime.c; path = SDL_systime.c; sourceTree = "<group>"; };
+		0000641A9BAC11AB3FBE0000 /* SDL_time.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = SDL_time.c; path = SDL_time.c; sourceTree = "<group>"; };
 /* End PBXFileReference section */
 
 /* Begin PBXFrameworksBuildPhase section */
@@ -1272,6 +1276,7 @@
 				F386F6E52884663E001840AA /* SDL_utils_c.h */,
 				F386F6E62884663E001840AA /* SDL_utils.c */,
 				A7D8A57123E2513D00DCD162 /* SDL.c */,
+				0000F5E7419220E3A8AB0000 /* time */,
 			);
 			name = "Library Source";
 			path = ../../src;
@@ -2254,6 +2259,23 @@
 			path = posix;
 			sourceTree = "<group>";
 		};
+		0000F5E7419220E3A8AB0000 /* time */ = {
+			isa = PBXGroup;
+			children = (
+				000004752BA2F77DECDF0000 /* unix */,
+				0000641A9BAC11AB3FBE0000 /* SDL_time.c */,
+			);
+			path = time;
+			sourceTree = "<group>";
+		};
+		000004752BA2F77DECDF0000 /* unix */ = {
+			isa = PBXGroup;
+			children = (
+				00003F472C51CE7DF6160000 /* SDL_systime.c */,
+			);
+			path = unix;
+			sourceTree = "<group>";
+		};
 /* End PBXGroup section */
 
 /* Begin PBXHeadersBuildPhase section */
@@ -2843,6 +2865,8 @@
 				00002B20A48E055EB0350000 /* SDL_camera_coremedia.m in Sources */,
 				000080903BC03006F24E0000 /* SDL_filesystem.c in Sources */,
 				0000481D255AF155B42C0000 /* SDL_sysfsops.c in Sources */,
+				0000494CC93F3E624D3C0000 /* SDL_systime.c in Sources */,
+				000095FA1BDE436CF3AF0000 /* SDL_time.c in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
diff --git a/include/SDL3/SDL.h b/include/SDL3/SDL.h
index 4a25af8c19a69..8dac3e9f97bf9 100644
--- a/include/SDL3/SDL.h
+++ b/include/SDL3/SDL.h
@@ -75,6 +75,7 @@
 #include <SDL3/SDL_surface.h>
 #include <SDL3/SDL_system.h>
 #include <SDL3/SDL_thread.h>
+#include <SDL3/SDL_time.h>
 #include <SDL3/SDL_timer.h>
 #include <SDL3/SDL_touch.h>
 #include <SDL3/SDL_version.h>
diff --git a/include/SDL3/SDL_filesystem.h b/include/SDL3/SDL_filesystem.h
index f39a9e10cbbf4..239a6513d0e6f 100644
--- a/include/SDL3/SDL_filesystem.h
+++ b/include/SDL3/SDL_filesystem.h
@@ -245,19 +245,13 @@ typedef enum SDL_PathType
     SDL_PATHTYPE_OTHER      /**< something completely different like a device node (not a symlink, those are always followed) */
 } SDL_PathType;
 
-/* SDL file times are 64-bit integers representing nanoseconds since the Unix epoch (Jan 1, 1970)
- *
- * They can be converted between to POSIX time_t values with SDL_NS_TO_SECONDS() and SDL_SECONDS_TO_NS(), and between Windows FILETIME values with SDL_FileTimeToWindows() and SDL_FileTimeFromWindows()
- */
-typedef Sint64 SDL_FileTime;
-
 typedef struct SDL_PathInfo
 {
     SDL_PathType type;          /* the path type */
     Uint64 size;                /* the file size in bytes */
-    SDL_FileTime create_time;   /* the time when the path was created */
-    SDL_FileTime modify_time;   /* the last time the path was modified */
-    SDL_FileTime access_time;   /* the last time the path was read */
+    SDL_Time create_time;   /* the time when the path was created */
+    SDL_Time modify_time;   /* the last time the path was modified */
+    SDL_Time access_time;   /* the last time the path was read */
 } SDL_PathInfo;
 
 /**
@@ -323,30 +317,6 @@ extern DECLSPEC int SDLCALL SDL_RenamePath(const char *oldpath, const char *newp
  */
 extern DECLSPEC int SDLCALL SDL_GetPathInfo(const char *path, SDL_PathInfo *info);
 
-/* Converts an SDL file time into a Windows FILETIME (100-nanosecond intervals since January 1, 1601).
- *
- * This function fills in the two 32-bit values of the FILETIME structure.
- *
- * \param ftime the time to convert
- * \param dwLowDateTime a pointer filled in with the low portion of the Windows FILETIME value
- * \param dwHighDateTime a pointer filled in with the high portion of the Windows FILETIME value
- *
- * \since This function is available since SDL 3.0.0.
- */
-extern DECLSPEC void SDLCALL SDL_FileTimeToWindows(SDL_FileTime ftime, Uint32 *dwLowDateTime, Uint32 *dwHighDateTime);
-
-/* Converts a Windows FILETIME (100-nanosecond intervals since January 1, 1601) to an SDL file time
- *
- * This function takes the two 32-bit values of the FILETIME structure as parameters.
- *
- * \param dwLowDateTime the low portion of the Windows FILETIME value
- * \param dwHighDateTime the high portion of the Windows FILETIME value
- * \returns the converted file time
- *
- * \since This function is available since SDL 3.0.0.
- */
-extern DECLSPEC SDL_FileTime SDLCALL SDL_FileTimeFromWindows(Uint32 dwLowDateTime, Uint32 dwHighDateTime);
-
 /* Ends C function definitions when using C++ */
 #ifdef __cplusplus
 }
diff --git a/include/SDL3/SDL_stdinc.h b/include/SDL3/SDL_stdinc.h
index 281aa56d16094..0a5cf33b445ac 100644
--- a/include/SDL3/SDL_stdinc.h
+++ b/include/SDL3/SDL_stdinc.h
@@ -186,6 +186,16 @@ typedef int64_t Sint64;
 #define SDL_MIN_UINT64  ((Uint64)(0x0000000000000000ull))   /* 0 */
 typedef uint64_t Uint64;
 
+/**
+ * SDL times are signed, 64-bit integers representing nanoseconds since the Unix epoch (Jan 1, 1970)
+ *
+ * They can be converted between POSIX time_t values with SDL_NS_TO_SECONDS() and SDL_SECONDS_TO_NS(),
+ * and between Windows FILETIME values with SDL_TimeToWindows() and SDL_TimeFromWindows().
+ */
+#define SDL_MAX_TIME SDL_MAX_SINT64
+#define SDL_MIN_TIME SDL_MIN_SINT64
+typedef Sint64 SDL_Time;
+
 /* @} *//* Basic data types */
 
 /**
diff --git a/include/SDL3/SDL_time.h b/include/SDL3/SDL_time.h
new file mode 100644
index 0000000000000..f87118e4450c2
--- /dev/null
+++ b/include/SDL3/SDL_time.h
@@ -0,0 +1,195 @@
+/*
+Simple DirectMedia Layer
+Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
+
+This software is provided 'as-is', without any express or implied
+warranty.  In no event will the authors be held liable for any damages
+arising from the use of this software.
+
+Permission is granted to anyone to use this software for any purpose,
+including commercial applications, and to alter it and redistribute it
+freely, subject to the following restrictions:
+
+1. The origin of this software must not be misrepresented; you must not
+   claim that you wrote the original software. If you use this software
+   in a product, an acknowledgment in the product documentation would be
+   appreciated but is not required.
+2. Altered source versions must be plainly marked as such, and must not be
+   misrepresented as being the original software.
+3. This notice may not be removed or altered from any source distribution.
+*/
+
+#ifndef SDL_time_h_
+#define SDL_time_h_
+
+/**
+ *  \file SDL_time.h
+ *
+ *  Header for the SDL realtime clock and date/time routines.
+ */
+
+#include <SDL3/SDL_error.h>
+#include <SDL3/SDL_stdinc.h>
+
+#include <SDL3/SDL_begin_code.h>
+/* Set up for C function definitions, even when using C++ */
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * A structure holding a calendar date and time broken down into its components.
+ */
+typedef struct SDL_DateTime
+{
+    int year;                  /**< Year */
+    int month;                 /**< Month [01-12] */
+    int day;                   /**< Day of the month [01-31] */
+    int hour;                  /**< Hour [0-23] */
+    int minute;                /**< Minute [0-59] */
+    int second;                /**< Seconds [0-60] */
+    int nanosecond;            /**< Nanoseconds [0-999999999] */
+    int day_of_week;           /**< Day of the week [0-6] (0 being Sunday) */
+    int utc_offset;            /**< Seconds east of UTC */
+} SDL_DateTime;
+
+/**
+ * The preferred date format of the current system locale.
+ *
+ * \sa SDL_PROP_GLOBAL_SYSTEM_DATE_FORMAT_NUMBER
+ */
+typedef enum SDL_DATE_FORMAT
+{
+    SDL_DATE_FORMAT_YYYYMMDD = 0, /**< Year/Month/Day */
+    SDL_DATE_FORMAT_DDMMYYYY = 1, /**< Day/Month/Year */
+    SDL_DATE_FORMAT_MMDDYYYY = 2, /**< Month/Day/Year */
+} SDL_DATE_FORMAT;
+
+/**
+ * The preferred time format of the current system locale.
+ *
+ * \sa SDL_PROP_GLOBAL_SYSTEM_TIME_FORMAT_NUMBER
+ */
+typedef enum SDL_TIME_FORMAT
+{
+    SDL_TIME_FORMAT_24HR = 0, /**< 24 hour time */
+    SDL_TIME_FORMAT_12HR = 1, /**< 12 hour time */
+} SDL_TIME_FORMAT;
+
+/**
+ *  Global date/time properties
+ *
+ *  - `SDL_PROP_GLOBAL_SYSTEM_DATE_FORMAT_NUMBER`: the SDL_DATE_FORMAT to use as the preferred date display format
+ *    for the current system locale.
+ *  - `SDL_PROP_GLOBAL_SYSTEM_TIME_FORMAT_NUMBER`: the SDL_TIME_FORMAT to use as the preferred time display format
+ *    for the current system locale.
+ */
+#define SDL_PROP_GLOBAL_SYSTEM_DATE_FORMAT_NUMBER "SDL.time.date_format"
+#define SDL_PROP_GLOBAL_SYSTEM_TIME_FORMAT_NUMBER "SDL.time.time_format"
+
+/**
+ * Gets the current value of the system realtime clock in nanoseconds since Jan 1, 1970 in
+ * Universal Coordinated Time (UTC).
+ *
+ * \param ticks the SDL_Time to hold the returned tick count
+ * \returns 0 on success or -1 on error; call SDL_GetError() for more information.
+ *
+ * \since This function is available since SDL 3.0.0 
+ */
+extern DECLSPEC int SDLCALL SDL_GetCurrentTime(SDL_Time *ticks);
+
+/**
+ * Converts an SDL_Time in nanoseconds since the epoch to a calendar time in the SDL_DateTime format.
+ *
+ * \param ticks the SDL_Time to be converted
+ * \param dt the resulting SDL_DateTime
+ * \param localTime the resulting SDL_DateTime will be expressed in local time if true, otherwise
+ *        it will be in Universal Coordinated Time (UTC)
+ * \returns 0 on success or -1 on error; call SDL_GetError() for more information.
+ *
+ * \since This function is available since SDL 3.0.0
+ */
+extern DECLSPEC int SDLCALL SDL_TimeToDateTime(SDL_Time ticks, SDL_DateTime *dt, SDL_bool localTime);
+
+/**
+ * Converts a calendar time to an SDL_Time in nanoseconds since the epoch.
+ * This function ignores the day_of_week member of the SDL_DateTime struct, so it may remain unset.
+ *
+ * \param dt the source SDL_DateTime
+ * \param ticks the resulting SDL_Time
+ * \returns 0 on success or -1 on error; call SDL_GetError() for more information.
+ *
+ * \since This function is available since SDL 3.0.0
+ */
+extern DECLSPEC int SDLCALL SDL_DateTimeToTime(const SDL_DateTime *dt, SDL_Time *ticks);
+
+/**
+ * Converts an SDL time into a Windows FILETIME (100-nanosecond intervals since January 1, 1601).
+ *
+ * This function fills in the two 32-bit values of the FILETIME structure.
+ *
+ * \param ticks the time to convert
+ * \param dwLowDateTime a pointer filled in with the low portion of the Windows FILETIME value
+ * \param dwHighDateTime a pointer filled in with the high portion of the Windows FILETIME value
+ *
+ * \since This function is available since SDL 3.0.0.
+ */
+extern DECLSPEC void SDLCALL SDL_TimeToWindows(SDL_Time ticks, Uint32 *dwLowDateTime, Uint32 *dwHighDateTime);
+
+/**
+ * Converts a Windows FILETIME (100-nanosecond intervals since January 1, 1601) to an SDL time
+ *
+ * This function takes the two 32-bit values of the FILETIME structure as parameters.
+ *
+ * \param dwLowDateTime the low portion of the Windows FILETIME value
+ * \param dwHighDateTime the high portion of the Windows FILETIME value
+ * \returns the converted SDL time
+ *
+ * \since This function is available since SDL 3.0.0.
+ */
+extern DECLSPEC SDL_Time SDLCALL SDL_TimeFromWindows(Uint32 dwLowDateTime, Uint32 dwHighDateTime);
+
+/**
+ * Get the number of days in a month for a given year.
+ *
+ * \param year the year
+ * \param month the month [1-12]
+ * \returns the number of days in the requested month, otherwise -1; call SDL_GetError() for more information.
+ *
+ * \since This function is available since SDL 3.0.0
+ */
+extern DECLSPEC int SDLCALL SDL_GetDaysInMonth(int year, int month);
+
+/**
+ * Get the day of year for a calendar date.
+ *
+ * \param year the year component of the date
+ * \param month the month component of the date
+ * \param day the day component of the date
+ * \returns the day of year [0-365] if the date is valid, otherwise -1; call SDL_GetError()
+ *          for more information.
+ *
+ * \since This function is available since SDL 3.0.0
+ */
+extern DECLSPEC int SDLCALL SDL_GetDayOfYear(int year, int month, int day);
+
+/**
+ * Get the day of week for a calendar date.
+ *
+ * \param ye

(Patch may be truncated, please check the link at the top of this post.)