diff foosdk/sdk/pfc/filetimetools.cpp @ 1:20d02a178406 default tip

*: check in everything else yay
author Paper <paper@tflc.us>
date Mon, 05 Jan 2026 02:15:46 -0500
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/foosdk/sdk/pfc/filetimetools.cpp	Mon Jan 05 02:15:46 2026 -0500
@@ -0,0 +1,331 @@
+#include "pfc-lite.h"
+
+#include "filetimetools.h"
+
+#include "timers.h"
+
+#include <math.h>
+
+namespace {
+    class exception_time_error {};
+}
+
+using namespace pfc;
+
+#ifndef _WIN32
+namespace {
+    typedef uint16_t WORD;
+
+    typedef struct _SYSTEMTIME {
+        WORD wYear;
+        WORD wMonth;
+        WORD wDayOfWeek;
+        WORD wDay;
+        WORD wHour;
+        WORD wMinute;
+        WORD wSecond;
+        WORD wMilliseconds;
+    } SYSTEMTIME, * PSYSTEMTIME, * LPSYSTEMTIME;
+
+}
+static void SystemTimeToNix(const SYSTEMTIME& st, struct tm& Time) {
+    memset(&Time, 0, sizeof(Time));
+    Time.tm_sec = st.wSecond;
+    Time.tm_min = st.wMinute;
+    Time.tm_hour = st.wHour;
+    Time.tm_mday = st.wDay;
+    Time.tm_mon = st.wMonth - 1;
+    Time.tm_year = st.wYear - 1900;
+}
+
+static t_filetimestamp ExportSystemTime(const SYSTEMTIME& st) {
+    struct tm Time;
+    SystemTimeToNix(st, Time);
+    return pfc::fileTimeUtoW(mktime(&Time));
+}
+
+static t_filetimestamp ExportSystemTimeLocal(const SYSTEMTIME& st) {
+    struct tm Time, Local;
+    SystemTimeToNix(st, Time);
+    time_t t = mktime(&Time);
+    localtime_r(&t, &Local);
+    return pfc::fileTimeUtoW(mktime(&Local));
+}
+static void SystemTimeFromNix(SYSTEMTIME& st, struct tm const& Time, t_filetimestamp origTS) {
+    memset(&st, 0, sizeof(st));
+    st.wSecond = Time.tm_sec;
+    st.wMinute = Time.tm_min;
+    st.wHour = Time.tm_hour;
+    st.wDay = Time.tm_mday;
+    st.wDayOfWeek = Time.tm_wday;
+    st.wMonth = Time.tm_mon + 1;
+    st.wYear = Time.tm_year + 1900;
+    st.wMilliseconds = (origTS % filetimestamp_1second_increment) / (filetimestamp_1second_increment/1000);
+}
+
+static bool MakeSystemTime(SYSTEMTIME& st, t_filetimestamp ts) {
+    time_t t = (time_t)pfc::fileTimeWtoU(ts);
+    struct tm Time;
+    if (gmtime_r(&t, &Time) == NULL) return false;
+    SystemTimeFromNix(st, Time, ts);
+    return true;
+}
+
+static bool MakeSystemTimeLocal(SYSTEMTIME& st, t_filetimestamp ts) {
+    time_t t = (time_t)pfc::fileTimeWtoU(ts);
+    struct tm Time;
+    if (localtime_r(&t, &Time) == NULL) return false;
+    SystemTimeFromNix(st, Time, ts);
+    return true;
+}
+
+#else
+static t_filetimestamp ExportSystemTime(const SYSTEMTIME& st) {
+    t_filetimestamp base;
+    if (!SystemTimeToFileTime(&st, (FILETIME*)&base)) throw exception_time_error();
+    return base;
+}
+static t_filetimestamp ExportSystemTimeLocal(const SYSTEMTIME& st) {
+#ifdef FOOBAR2000_DESKTOP_WINDOWS
+    t_filetimestamp base, out;
+    if (!SystemTimeToFileTime(&st, (FILETIME*)&base)) throw exception_time_error();
+    if (!LocalFileTimeToFileTime((const FILETIME*)&base, (FILETIME*)&out)) throw exception_time_error();
+    return out;
+#else
+    SYSTEMTIME UTC;
+    if (!TzSpecificLocalTimeToSystemTime(NULL, &st, &UTC)) throw exception_time_error();
+    return ExportSystemTime(UTC);
+#endif
+}
+static bool MakeSystemTime(SYSTEMTIME& st, t_filetimestamp ts) {
+    if (ts == filetimestamp_invalid) return false;
+    return !!FileTimeToSystemTime((const FILETIME*)&ts, &st);
+
+}
+static bool MakeSystemTimeLocal(SYSTEMTIME& st, t_filetimestamp ts) {
+    if (ts == filetimestamp_invalid) return false;
+#ifdef FOOBAR2000_DESKTOP_WINDOWS
+    FILETIME ft;
+    if (FileTimeToLocalFileTime((FILETIME*)&ts, &ft)) {
+        if (FileTimeToSystemTime(&ft, &st)) {
+            return true;
+        }
+    }
+    return false;
+#else
+    SYSTEMTIME UTC;
+    if (FileTimeToSystemTime((FILETIME*)&ts, &UTC)) {
+        if (SystemTimeToTzSpecificLocalTime(NULL, &UTC, &st)) return true;
+    }
+    return false;
+#endif
+}
+#endif // _WIN32
+
+static bool is_spacing(char c) { return c == ' ' || c == 10 || c == 13 || c == '\t'; }
+
+static unsigned ParseDateElem(const char* ptr, t_size len) {
+    unsigned ret = 0;
+    for (t_size walk = 0; walk < len; ++walk) {
+        const char c = ptr[walk];
+        if (c < '0' || c > '9') throw exception_time_error();
+        ret = ret * 10 + (unsigned)(c - '0');
+    }
+    return ret;
+}
+
+static bool st_sanity(SYSTEMTIME const& st) {
+    return st.wYear >= 1601 && st.wMonth >= 1 && st.wMonth <= 12 && st.wDay >= 1 && st.wDay <= 31 && st.wHour < 24 && st.wMinute < 60 && st.wSecond < 60 && st.wMilliseconds < 1000;
+}
+static t_filetimestamp filetimestamp_from_string_internal(const char* date, bool local) {
+    // Accepted format
+    // YYYY-MM-DD HH:MM:SS
+    try {
+        SYSTEMTIME st = {};
+        st.wDay = 1; st.wMonth = 1;
+
+        unsigned walk = 0;
+        auto worker = [&](unsigned n) {
+            auto ret = ParseDateElem(date + walk, n);
+            walk += n;
+            if (ret > UINT16_MAX) throw exception_time_error();
+            return (WORD)ret;;
+        };
+        
+        auto skip = [&](char c) {
+            if (date[walk] == c)  ++walk;
+        };
+
+        auto skipSpacing = [&] {
+            while (is_spacing(date[walk])) ++walk;
+        };
+        skipSpacing();
+        st.wYear = worker(4);
+        skip('-');
+        st.wMonth = worker(2);
+        skip('-');
+        st.wDay = worker(2);
+        skipSpacing();
+        st.wHour = worker(2);
+        skip(':');
+        st.wMinute = worker(2);
+        skip(':');
+        st.wSecond = worker(2);
+        if (date[walk] == '.') {
+            double v = pfc::string_to_float(date + walk);
+            st.wMilliseconds = (WORD)floor(v * 1000.f); // don't ever round up, don't want to handle ms of 1000
+        }
+
+        if (!st_sanity(st)) throw exception_time_error();
+
+        if (local) {
+            return ExportSystemTimeLocal(st);
+        } else {
+            return ExportSystemTime(st);
+        }
+    } catch (exception_time_error const &) {
+        return filetimestamp_invalid;
+    }
+}
+
+namespace pfc {
+    t_filetimestamp filetimestamp_from_string(const char* date) {
+        return filetimestamp_from_string_internal(date, true);
+    }
+
+    t_filetimestamp filetimestamp_from_string_utc(const char* date) {
+        return filetimestamp_from_string_internal(date, false);
+    }
+
+    static constexpr char g_invalidMsg[] = "<invalid timestamp>";
+
+    pfc::string_formatter format_filetimestamp(t_filetimestamp p_timestamp) {
+        try {
+            SYSTEMTIME st;
+            if (MakeSystemTimeLocal(st, p_timestamp)) {
+                pfc::string_formatter buffer;
+                buffer
+                    << pfc::format_uint(st.wYear, 4) << "-" << pfc::format_uint(st.wMonth, 2) << "-" << pfc::format_uint(st.wDay, 2) << " "
+                    << pfc::format_uint(st.wHour, 2) << ":" << pfc::format_uint(st.wMinute, 2) << ":" << pfc::format_uint(st.wSecond, 2);
+                return buffer;
+            }
+        } catch (...) {}
+        return g_invalidMsg;
+    }
+
+    pfc::string_formatter format_filetimestamp_ms(t_filetimestamp p_timestamp) {
+        try {
+            SYSTEMTIME st;
+            if (MakeSystemTimeLocal(st, p_timestamp)) {
+                pfc::string_formatter buffer;
+                buffer
+                    << pfc::format_uint(st.wYear, 4) << "-" << pfc::format_uint(st.wMonth, 2) << "-" << pfc::format_uint(st.wDay, 2) << " "
+                    << pfc::format_uint(st.wHour, 2) << ":" << pfc::format_uint(st.wMinute, 2) << ":" << pfc::format_uint(st.wSecond, 2) << "." << pfc::format_uint(st.wMilliseconds, 3);
+                return buffer;
+            }
+        } catch (...) {}
+        return g_invalidMsg;
+    }
+
+    pfc::string_formatter format_filetimestamp_utc(t_filetimestamp p_timestamp) {
+        try {
+            SYSTEMTIME st;
+            if (MakeSystemTime(st, p_timestamp)) {
+                pfc::string_formatter buffer;
+                buffer
+                    << pfc::format_uint(st.wYear, 4) << "-" << pfc::format_uint(st.wMonth, 2) << "-" << pfc::format_uint(st.wDay, 2) << " "
+                    << pfc::format_uint(st.wHour, 2) << ":" << pfc::format_uint(st.wMinute, 2) << ":" << pfc::format_uint(st.wSecond, 2);
+                return buffer;
+            }
+        } catch (...) {}
+        return g_invalidMsg;
+    }
+
+} // namespace foobar2000_io
+
+namespace {
+    struct dateISO_t {
+        unsigned Y, M, D;
+        unsigned h, m, s;
+        double sfrac;
+        int tzdelta;
+    };
+
+    dateISO_t read_ISO_8601(const char* dateISO) {
+        dateISO_t ret = {};
+        // 2022-01-26T13:44:51.200000Z
+        // 2010-02-19T14:54:23.031+08:00
+        // 2022-01-27T11:01:49+00:00
+        // 2022-01-27T11:01:49Z
+        // 20220127T110149Z
+
+        unsigned walk = 0;
+        auto worker = [&](unsigned n) {
+            auto ret = ParseDateElem(dateISO + walk, n);
+            walk += n;
+            return ret;
+        };
+        auto skip = [&](char c) {
+            if (dateISO[walk] == c)  ++walk;
+        };
+        auto expect = [&](char c) {
+            if (dateISO[walk] != c) throw exception_time_error();
+            ++walk;
+        };
+        ret.Y = worker(4);
+        skip('-');
+        ret.M = worker(2);
+        skip('-');
+        ret.D = worker(2);
+        expect('T');
+        ret.h = worker(2);
+        skip(':');
+        ret.m = worker(2);
+        skip(':');
+        ret.s = worker(2);
+        if (dateISO[walk] == '.') {
+            unsigned base = walk;
+            ++walk;
+            while (pfc::char_is_numeric(dateISO[walk])) ++walk;
+            ret.sfrac = pfc::string_to_float(dateISO + base, walk - base);
+        }
+        if (dateISO[walk] == '+' || dateISO[walk] == '-') {
+            bool neg = dateISO[walk] == '-';
+            ++walk;
+            unsigned tz_h = worker(2);
+            if (tz_h >= 24) throw exception_time_error();
+            skip(':');
+            unsigned tz_m = worker(2);
+            if (tz_m >= 60) throw exception_time_error();
+            tz_m += tz_h * 60;
+            ret.tzdelta = neg ? (int)tz_m : -(int)tz_m; // reversed! it's a timezone offset, have to *add* it if timezone has a minus
+        }
+        return ret;
+    }
+}
+
+t_filetimestamp pfc::filetimestamp_from_string_ISO_8601(const char* dateISO) {
+    try {
+        auto elems = read_ISO_8601(dateISO);
+
+        SYSTEMTIME st = {};
+        st.wDay = 1; st.wMonth = 1;
+        st.wYear = (WORD)elems.Y;
+        st.wMonth = (WORD)elems.M;
+        st.wDay = (WORD)elems.D;
+        st.wHour = (WORD)elems.h;
+        st.wMinute = (WORD)elems.m;
+        st.wSecond = (WORD)elems.s;
+        st.wMilliseconds = (WORD)floor(elems.sfrac * 1000.f);
+
+        if (!st_sanity(st)) throw exception_time_error();
+
+        auto ret = ExportSystemTime(st);
+
+        ret += filetimestamp_1second_increment * elems.tzdelta * 60;
+        
+        return ret;
+    } catch (...) {
+        return filetimestamp_invalid;
+    }
+}