diff options
author | Jack Lloyd <[email protected]> | 2017-12-04 16:58:19 -0500 |
---|---|---|
committer | Jack Lloyd <[email protected]> | 2017-12-04 16:58:19 -0500 |
commit | f82ee841f719926e91eb4a5533c964821f08488d (patch) | |
tree | 21c5924b896caa716e9a690a6cc2463bbede82ea /src/lib/utils/calendar.cpp | |
parent | e5f39dd483a08accc8a12e8b322a48037c5b3bf4 (diff) |
Simplify date conversion by avoiding OS utilities
We have to rely on non-portable OS calls to convert UTC times,
and they are not available on many systems (including Solaris and MinGW).
But instead there is a simple algorithm due to Howard Hinnant that
does the same job. Woo.
Diffstat (limited to 'src/lib/utils/calendar.cpp')
-rw-r--r-- | src/lib/utils/calendar.cpp | 139 |
1 files changed, 36 insertions, 103 deletions
diff --git a/src/lib/utils/calendar.cpp b/src/lib/utils/calendar.cpp index db933e648..d39e52823 100644 --- a/src/lib/utils/calendar.cpp +++ b/src/lib/utils/calendar.cpp @@ -1,6 +1,6 @@ /* * Calendar Functions -* (C) 1999-2010 Jack Lloyd +* (C) 1999-2010,2017 Jack Lloyd * (C) 2015 Simon Warta (Kullo GmbH) * * Botan is released under the Simplified BSD License (see license.txt) @@ -11,13 +11,8 @@ #include <ctime> #include <sstream> #include <iomanip> -#include <botan/mutex.h> #include <stdlib.h> -#if defined(BOTAN_HAS_BOOST_DATETIME) -#include <boost/date_time/posix_time/posix_time_types.hpp> -#endif - namespace Botan { namespace { @@ -40,119 +35,57 @@ std::tm do_gmtime(std::time_t time_val) return tm; } -#if !defined(BOTAN_TARGET_OS_HAS_TIMEGM) && !(defined(BOTAN_TARGET_OS_HAS_MKGMTIME) && defined(BOTAN_BUILD_COMPILER_IS_MSVC)) +/* +Portable replacement for timegm, _mkgmtime, etc -#if defined(BOTAN_HAS_BOOST_DATETIME) +Algorithm due to Howard Hinnant -std::time_t boost_timegm(std::tm *tm) +See https://howardhinnant.github.io/date_algorithms.html#days_from_civil +for details and explaination. The code is slightly simplified by our assumption +that the date is at least 1970, which is sufficient for our purposes. +*/ +size_t days_since_epoch(uint32_t year, uint32_t month, uint32_t day) { - const int sec = tm->tm_sec; - const int min = tm->tm_min; - const int hour = tm->tm_hour; - const int day = tm->tm_mday; - const int mon = tm->tm_mon + 1; - const int year = tm->tm_year + 1900; - - using namespace boost::posix_time; - using namespace boost::gregorian; - const auto epoch = ptime(date(1970, 01, 01)); - const auto time = ptime(date(year, mon, day), - hours(hour) + minutes(min) + seconds(sec)); - const time_duration diff(time - epoch); - std::time_t out = diff.ticks() / diff.ticks_per_second(); - - return out; + if(month <= 2) + year -= 1; + const uint32_t era = year / 400; + const uint32_t yoe = year - era * 400; // [0, 399] + const uint32_t doy = (153*(month + (month > 2 ? -3 : 9)) + 2)/5 + day-1; // [0, 365] + const uint32_t doe = yoe * 365 + yoe/4 - yoe/100 + doy; // [0, 146096] + return era * 146097 + doe - 719468; } -#elif defined(BOTAN_OS_TYPE_IS_UNIX) - -#pragma message "Caution! A fallback version of timegm() is used which is not thread-safe" - -mutex_type ENV_TZ; +} -std::time_t fallback_timegm(std::tm *tm) +std::chrono::system_clock::time_point calendar_point::to_std_timepoint() const { - std::time_t out; - std::string tz_backup; - - ENV_TZ.lock(); - - // Store current value of env variable TZ - const char* tz_env_pointer = ::getenv("TZ"); - if (tz_env_pointer != nullptr) - tz_backup = std::string(tz_env_pointer); - - // Clear value of TZ - ::setenv("TZ", "", 1); - ::tzset(); + if(get_year() < 1970) + throw Invalid_Argument("calendar_point::to_std_timepoint() does not support years before 1970"); - out = ::mktime(tm); - - // Restore TZ - if (!tz_backup.empty()) + // 32 bit time_t ends at January 19, 2038 + // https://msdn.microsoft.com/en-us/library/2093ets1.aspx + // Throw after 2037 if 32 bit time_t is used + if(get_year() > 2037 && sizeof(std::time_t) == 4) { - // setenv makes a copy of the second argument - ::setenv("TZ", tz_backup.data(), 1); + throw Invalid_Argument("calendar_point::to_std_timepoint() does not support years after 2037 on this system"); } - else + else if(get_year() >= 2400) { - ::unsetenv("TZ"); + // This upper bound is somewhat arbitrary + throw Invalid_Argument("calendar_point::to_std_timepoint() does not support years after 2400"); } - ::tzset(); - - ENV_TZ.unlock(); - return out; -} -#endif // BOTAN_HAS_BOOST_DATETIME + const uint64_t seconds_64 = (days_since_epoch(get_year(), get_month(), get_day()) * 86400) + + (get_hour() * 60 * 60) + (get_minutes() * 60) + get_seconds(); -#endif + const time_t seconds_time_t = static_cast<time_t>(seconds_64); -} - -std::chrono::system_clock::time_point calendar_point::to_std_timepoint() const - { - if (year < 1970) - throw Invalid_Argument("calendar_point::to_std_timepoint() does not support years before 1970."); - - // 32 bit time_t ends at January 19, 2038 - // https://msdn.microsoft.com/en-us/library/2093ets1.aspx - // Throw after 2037 if 32 bit time_t is used - if (year > 2037 && sizeof(std::time_t) == 4) + if(seconds_64 - seconds_time_t != 0) { - throw Invalid_Argument("calendar_point::to_std_timepoint() does not support years after 2037."); + throw Invalid_Argument("calendar_point::to_std_timepoint time_t overflow"); } - // std::tm: struct without any timezone information - std::tm tm; - tm.tm_isdst = -1; // i.e. no DST information available - tm.tm_sec = seconds; - tm.tm_min = minutes; - tm.tm_hour = hour; - tm.tm_mday = day; - tm.tm_mon = month - 1; - tm.tm_year = year - 1900; - - // Define a function alias `botan_timegm` - #if defined(BOTAN_TARGET_OS_HAS_TIMEGM) - std::time_t (&botan_timegm)(std::tm *tm) = ::timegm; - #elif defined(BOTAN_TARGET_OS_HAS_MKGMTIME) && defined(BOTAN_BUILD_COMPILER_IS_MSVC) - // https://stackoverflow.com/questions/16647819/timegm-cross-platform - std::time_t (&botan_timegm)(std::tm *tm) = ::_mkgmtime; - #elif defined(BOTAN_HAS_BOOST_DATETIME) - std::time_t (&botan_timegm)(std::tm *tm) = boost_timegm; - #elif defined(BOTAN_OS_TYPE_IS_UNIX) - std::time_t (&botan_timegm)(std::tm *tm) = fallback_timegm; - #else - std::time_t (&botan_timegm)(std::tm *tm) = ::mktime; // localtime instead... - #endif - - // Convert std::tm to std::time_t - std::time_t tt = botan_timegm(&tm); - if (tt == -1) - throw Invalid_Argument("calendar_point couldn't be converted: " + to_string()); - - return std::chrono::system_clock::from_time_t(tt); + return std::chrono::system_clock::from_time_t(seconds_time_t); } std::string calendar_point::to_string() const @@ -162,9 +95,9 @@ std::string calendar_point::to_string() const { using namespace std; output << setfill('0') - << setw(4) << year << "-" << setw(2) << month << "-" << setw(2) << day + << setw(4) << get_year() << "-" << setw(2) << get_month() << "-" << setw(2) << get_day() << "T" - << setw(2) << hour << ":" << setw(2) << minutes << ":" << setw(2) << seconds; + << setw(2) << get_hour() << ":" << setw(2) << get_minutes() << ":" << setw(2) << get_seconds(); } return output.str(); } |