andry81/tacklelib

View on GitHub
src/utility/time.cpp

Summary

Maintainability
Test Coverage
#include <tacklelib/utility/time.hpp>

#ifndef UTILITY_PLATFORM_FEATURE_STD_HAS_GET_TIME
#include <boost/date_time/posix_time/posix_time.hpp>
#endif

#include <inttypes.h>


namespace utility {
namespace time {

    // WORKAROUND:
    //  1. Boost uses `strftime` time string format instead of the `std::get_time` time string format plus biased time base which has to be workarounded:
    //      `from_time_t(t1 + t2) gains incorrect and different results on Windows and Linux`:
    //      https://github.com/boostorg/date_time/issues/157
    //  2. Use base year extraction to renormalize std::tm to the std representation.
    //

    std::time_t convert_time_string_to_std_time(const std::string & time_str_format, const std::string & time_value_str,
                                                convert_time_string_to_std_time_error_func_t error_func)
    {
        std::time_t epoch_time_sec = 0;

#if defined(UTILITY_PLATFORM_FEATURE_STD_HAS_GET_TIME) && !defined(UTILITY_PLATFORM_FEATURE_USE_BOOST_POSIX_TIME_INSTEAD_STD_GET_TIME)
        try
        {
            std::tm t;
            if (utility::time::get_time(t, std::string{}, time_str_format, time_value_str)) {
                if (time_str_format.find("%Y") == std::string::npos) {
                    if (time_str_format.find("%y") == std::string::npos) {
                        // from 1900 year
                        t.tm_year += 70;
                    }
                    else {
                        // [00-68] -> 2000-2068
                        // [69-99] -> 1969-1999
                        if (t.tm_year < 69) {
                            t.tm_year += 100;
                        }
                    }
                }
                if (!t.tm_mday) t.tm_mday = 1;

#if defined(UTILITY_COMPILER_CXX_MSC) && UTILITY_COMPILER_CXX_VERSION < 1910
                time_t epoch_time_correction_sec = 0;
                int epoch_time_correction_from_year = 70;
                if (t.tm_year < 70) {
                    // CAUTION:
                    //   utility::time::timegm will fail if the year component is less than 70, so fix the year and add delta directly to the time_t
                    //
                    epoch_time_correction_from_year = t.tm_year;
                    t.tm_year = 70;
                }
#endif

                errno = 0; // must be to ensure the state change
                epoch_time_sec = utility::time::timegm(t);
                if (epoch_time_sec == -1 && errno != 0) { // error handle
                    epoch_time_sec = 0;
                }
                else {
#if defined(UTILITY_COMPILER_CXX_MSC) && UTILITY_COMPILER_CXX_VERSION < 1910
                    for (int i = epoch_time_correction_from_year; i < 70; i++) {
                        epoch_time_sec -= 365 * 24 * 60 * 60;
                    }
                    epoch_time_sec -= (get_leap_days(1970) - get_leap_days(1900 + epoch_time_correction_from_year)) * 24 * 60 * 60;
#endif
                }
            }
            else {
                if (error_func) {
                    error_func(time_str_format, time_value_str, nullptr);
                }
            }
        }
#else
        try
        {
            namespace boost_ptime = boost::posix_time;

            struct base_time_extractor
            {
                base_time_extractor() {
                    std::istringstream iss{ "%Y-%m-%d" };
                    iss.imbue(std::locale(std::locale::classic(), new boost_ptime::time_input_facet("1970-01-01")));
                    boost_ptime::ptime base_ptime_value;
                    iss >> base_ptime_value;
                    base_time = boost_ptime::to_tm(base_ptime_value);
                    //printf("base tm_year (boost) 1: %i\n", base_time.tm_year);
                }

                std::tm base_time;
            } static s_base_time_extractor;

            std::istringstream iss{ time_value_str };
            iss.imbue(std::locale(std::locale::classic(), new boost_ptime::time_input_facet(time_str_format.c_str())));
            boost_ptime::ptime ptime_value;
            iss >> ptime_value;

            if (!ptime_value.is_not_a_date_time()) {
                std::tm t = boost_ptime::to_tm(ptime_value);

                if (time_str_format.find("%Y") == std::string::npos) {
                    if (time_str_format.find("%y") == std::string::npos) {
                        // biased base, normalize
                        t.tm_year -= s_base_time_extractor.base_time.tm_year;
                        // from 1900 year
                        t.tm_year += 70;
                    }
                    else {
                        // CAUTION:
                        //  Here is a specific boost incompatability versus the std::get_time because the boost has used the
                        //  strftime format which is quite different:
                        //  [00-99] -> 2000-2099

                        // std::get_time format, just in case:
                        //  [00-68] -> 2000-2068
                        //  [69-99] -> 1969-1999
                        if (t.tm_year < 69) {
                            t.tm_year += 100;
                        }
                    }
                }
                if (!t.tm_mday) t.tm_mday = 1; // just in case

                epoch_time_sec = utility::time::timegm(t);
            }
            else {
                if (error_func) {
                    error_func(time_str_format, time_value_str, nullptr);
                }
            }
        }
        catch (const boost::bad_lexical_cast & ex)
        {
            if (error_func) {
                error_func(time_str_format, time_value_str, &ex);
            }
        }
#endif
        catch (const std::out_of_range & ex)
        {
            if (error_func) {
                error_func(time_str_format, time_value_str, &ex);
            }
        }

        return epoch_time_sec;
    }

    std::string convert_time_string_from_std_time(const std::string & time_str_format, const std::time_t & time_value,
                                                  convert_time_string_from_std_time_error_func_t error_func)
    {
#if defined(UTILITY_PLATFORM_FEATURE_STD_HAS_GET_TIME) && !defined(UTILITY_PLATFORM_FEATURE_USE_BOOST_POSIX_TIME_INSTEAD_STD_GET_TIME)
        std::string time_str;

        try
        {
#if !defined(UTILITY_COMPILER_CXX_MSC) || UTILITY_COMPILER_CXX_VERSION >= 1910
            std::tm t = utility::time::gmtime(time_value);
#else
            std::tm t = utility::time::gmtime(time_value >= 0 ? time_value : 0); // avoid exception code 0xc0000409
#endif

            if (time_str_format.find("%Y") == std::string::npos) {
                if (time_str_format.find("%y") == std::string::npos) {
                    // from 1900 year
                    t.tm_year += 70;
                }
                else {
                    // std::get_time format:
                    //  [00-68] -> 2000-2068
                    //  [69-99] -> 1969-1999
                    if (t.tm_year < 69) {
                        t.tm_year += 100;
                    }
                }
            }
            if (!t.tm_mday) t.tm_mday = 1; // just in case

            if (utility::time::get_time(time_str, time_str_format, t)) {
                return time_str;
            }
            else {
                if (error_func) {
                    error_func(time_str_format, time_value, nullptr);
                }
            }
        }
#else
        try
        {
            namespace boost_ptime = boost::posix_time;

            std::tm t = utility::time::gmtime(time_value);

            if (!t.tm_mday) t.tm_mday = 1; // just in case

            std::ostringstream oss;
            oss.imbue(std::locale(std::locale::classic(), new boost_ptime::time_facet(time_str_format.c_str())));
            boost_ptime::ptime ptime_value = boost_ptime::ptime_from_tm(t);
            oss << ptime_value;

            if (!oss.fail()) {
                return oss.str();
            }
            else {
                if (error_func) {
                    error_func(time_str_format, time_value, nullptr);
                }
            }
        }
        catch (const boost::bad_lexical_cast & ex)
        {
            if (error_func) {
                error_func(time_str_format, time_value, &ex);
            }
        }
#endif
        catch (const std::out_of_range & ex)
        {
            if (error_func) {
                error_func(time_str_format, time_value, &ex);
            }
        }

#if defined(UTILITY_PLATFORM_FEATURE_STD_HAS_GET_TIME) && !defined(UTILITY_PLATFORM_FEATURE_USE_BOOST_POSIX_TIME_INSTEAD_STD_GET_TIME)
        return time_str;
#else
        return std::string{};
#endif
    }

}
}