datetime datetime::from_string()

in Source/Shared/http_utils.cpp [512:718]


    datetime datetime::from_string(const xsapi_internal_string& dateString, date_format format)
    {
        // avoid floating point math to preserve precision
        uint64_t ufrac_second = 0;

#ifdef _WIN32
        datetime result;
        if (format == RFC_1123)
        {
            SYSTEMTIME sysTime = { 0 };

            xsapi_internal_string month(3, '\0');
            xsapi_internal_string unused(3, '\0');

            const char* formatString = "%3c, %2d %3c %4d %2d:%2d:%2d %3c";
            auto n = sscanf_s(dateString.c_str(), formatString,
                unused.data(), unused.size(),
                &sysTime.wDay,
                month.data(), month.size(),
                &sysTime.wYear,
                &sysTime.wHour,
                &sysTime.wMinute,
                &sysTime.wSecond,
                unused.data(), unused.size());

            if (n == 8)
            {
                xsapi_internal_string monthnames[12] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
                auto loc = std::find_if(monthnames, monthnames + 12, [&month](const xsapi_internal_string& m) { return m == month; });

                if (loc != monthnames + 12)
                {
                    sysTime.wMonth = (short)((loc - monthnames) + 1);
                    if (system_type_to_datetime(&sysTime, ufrac_second, &result))
                    {
                        return result;
                    }
                }
            }
        }
        else if (format == ISO_8601)
        {
            // Unlike FILETIME, SYSTEMTIME does not have enough precision to hold seconds in 100 nanosecond
            // increments. Therefore, start with seconds and milliseconds set to 0, then add them separately

            // Try to extract the fractional second from the timestamp
            xsapi_internal_string input;
            extract_fractional_second(dateString, input, ufrac_second);
            {
                SYSTEMTIME sysTime = { 0 };
                const char* formatString = "%4d-%2d-%2dT%2d:%2d:%2dZ";
                auto n = sscanf_s(input.c_str(), formatString,
                    &sysTime.wYear,
                    &sysTime.wMonth,
                    &sysTime.wDay,
                    &sysTime.wHour,
                    &sysTime.wMinute,
                    &sysTime.wSecond);

                if (n == 3 || n == 6)
                {
                    if (system_type_to_datetime(&sysTime, ufrac_second, &result))
                    {
                        return result;
                    }
                }
            }
            {
                SYSTEMTIME sysTime = { 0 };
                DWORD date = 0;

                const char* formatString = "%8dT%2d:%2d:%2dZ";
                auto n = sscanf_s(input.c_str(), formatString,
                    &date,
                    &sysTime.wHour,
                    &sysTime.wMinute,
                    &sysTime.wSecond);

                if (n == 1 || n == 4)
                {
                    sysTime.wDay = date % 100;
                    date /= 100;
                    sysTime.wMonth = date % 100;
                    date /= 100;
                    sysTime.wYear = (WORD)date;

                    if (system_type_to_datetime(&sysTime, ufrac_second, &result))
                    {
                        return result;
                    }
                }
            }
            {
                SYSTEMTIME sysTime = { 0 };
                GetSystemTime(&sysTime);    // Fill date portion with today's information
                sysTime.wSecond = 0;
                sysTime.wMilliseconds = 0;

                const char* formatString = "%2d:%2d:%2dZ";
                auto n = sscanf_s(input.c_str(), formatString,
                    &sysTime.wHour,
                    &sysTime.wMinute,
                    &sysTime.wSecond);

                if (n == 3)
                {
                    if (system_type_to_datetime(&sysTime, ufrac_second, &result))
                    {
                        return result;
                    }
                }
            }
        }

        return datetime();
#else
        xsapi_internal_string input(dateString);

        struct tm output = tm();

        if (format == RFC_1123)
        {
            strptime(input.data(), "%a, %d %b %Y %H:%M:%S GMT", &output);
        }
        else
        {
            // Try to extract the fractional second from the timestamp
            xsapi_internal_string input;
            extract_fractional_second(dateString, input, ufrac_second);

            auto result = strptime(input.data(), "%Y-%m-%dT%H:%M:%SZ", &output);

            if (result == nullptr)
            {
                result = strptime(input.data(), "%Y%m%dT%H:%M:%SZ", &output);
            }
            if (result == nullptr)
            {
                // Fill the date portion with the epoch,
                // strptime will do the rest
                memset(&output, 0, sizeof(struct tm));
                output.tm_year = 70;
                output.tm_mon = 1;
                output.tm_mday = 1;
                result = strptime(input.data(), "%H:%M:%SZ", &output);
            }
            if (result == nullptr)
            {
                result = strptime(input.data(), "%Y-%m-%d", &output);
            }
            if (result == nullptr)
            {
                result = strptime(input.data(), "%Y%m%d", &output);
            }
            if (result == nullptr)
            {
                return datetime();
            }
        }

#if (defined(ANDROID) || defined(__ANDROID__))
        // HACK: The (nonportable?) POSIX function timegm is not available in
        //       bionic. As a workaround[1][2], we set the C library timezone to
        //       UTC, call mktime, then set the timezone back. However, the C
        //       environment is fundamentally a shared global resource and thread-
        //       unsafe. We can protect our usage here, however any other code might
        //       manipulate the environment at the same time.
        //
        // [1] http://linux.die.net/man/3/timegm
        // [2] http://www.gnu.org/software/libc/manual/html_node/Broken_002ddown-Time.html
        time_t time;

        static std::mutex env_var_lock;
        {
            std::lock_guard<std::mutex> lock(env_var_lock);
            xsapi_internal_string prev_env;
            auto prev_env_cstr = getenv("TZ");
            if (prev_env_cstr != nullptr)
            {
                prev_env = prev_env_cstr;
            }
            setenv("TZ", "UTC", 1);

            time = mktime(&output);

            if (prev_env_cstr)
            {
                setenv("TZ", prev_env.c_str(), 1);
            }
            else
            {
                unsetenv("TZ");
            }
            tzset();
        }
#else
        time_t time = timegm(&output);
#endif
        struct timeval tv = timeval();
        tv.tv_sec = time;
        auto result = timeval_to_datetime(tv);

        // fractional seconds are already in correct format so just add them.
        result = result + ufrac_second;
        return result;
#endif
    }