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 | |
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.
-rw-r--r-- | doc/manual/deprecated.rst | 2 | ||||
-rw-r--r-- | src/build-data/os/cygwin.txt | 1 | ||||
-rw-r--r-- | src/build-data/os/darwin.txt | 1 | ||||
-rw-r--r-- | src/build-data/os/freebsd.txt | 1 | ||||
-rw-r--r-- | src/build-data/os/ios.txt | 1 | ||||
-rw-r--r-- | src/build-data/os/linux.txt | 1 | ||||
-rw-r--r-- | src/build-data/os/mingw.txt | 1 | ||||
-rw-r--r-- | src/build-data/os/netbsd.txt | 1 | ||||
-rw-r--r-- | src/build-data/os/openbsd.txt | 1 | ||||
-rw-r--r-- | src/build-data/os/windows.txt | 1 | ||||
-rw-r--r-- | src/build-data/os/winphone.txt | 1 | ||||
-rw-r--r-- | src/lib/asn1/asn1_time.cpp | 12 | ||||
-rw-r--r-- | src/lib/utils/boost/info.txt | 1 | ||||
-rw-r--r-- | src/lib/utils/calendar.cpp | 139 | ||||
-rw-r--r-- | src/lib/utils/calendar.h | 26 | ||||
-rw-r--r-- | src/tests/test_utils.cpp | 28 |
16 files changed, 77 insertions, 141 deletions
diff --git a/doc/manual/deprecated.rst b/doc/manual/deprecated.rst index 8cfe7e590..a7dc4bf12 100644 --- a/doc/manual/deprecated.rst +++ b/doc/manual/deprecated.rst @@ -55,3 +55,5 @@ in the source. - All built in MODP groups < 2048 bits - All pre-created DSA groups + +- Directly accessing the member variables of calendar_point diff --git a/src/build-data/os/cygwin.txt b/src/build-data/os/cygwin.txt index a1234dc13..06f6d60f8 100644 --- a/src/build-data/os/cygwin.txt +++ b/src/build-data/os/cygwin.txt @@ -12,7 +12,6 @@ doc_dir docs <target_features> gettimeofday -timegm readdir threads filesystem diff --git a/src/build-data/os/darwin.txt b/src/build-data/os/darwin.txt index 41716e21a..6568a9783 100644 --- a/src/build-data/os/darwin.txt +++ b/src/build-data/os/darwin.txt @@ -20,7 +20,6 @@ memset_s readdir sockets threads -timegm </target_features> <aliases> diff --git a/src/build-data/os/freebsd.txt b/src/build-data/os/freebsd.txt index 0e60abd2e..fb869dfd0 100644 --- a/src/build-data/os/freebsd.txt +++ b/src/build-data/os/freebsd.txt @@ -9,7 +9,6 @@ posix_mlock gmtime_r dlopen readdir -timegm sockets threads filesystem diff --git a/src/build-data/os/ios.txt b/src/build-data/os/ios.txt index ddd283cd9..96ac73721 100644 --- a/src/build-data/os/ios.txt +++ b/src/build-data/os/ios.txt @@ -16,7 +16,6 @@ memset_s readdir sockets threads -timegm </target_features> <aliases> diff --git a/src/build-data/os/linux.txt b/src/build-data/os/linux.txt index 59f995fc2..80ea3e63a 100644 --- a/src/build-data/os/linux.txt +++ b/src/build-data/os/linux.txt @@ -10,7 +10,6 @@ gmtime_r dlopen getauxval readdir -timegm sockets threads filesystem diff --git a/src/build-data/os/mingw.txt b/src/build-data/os/mingw.txt index bf4333ec0..8d4e94249 100644 --- a/src/build-data/os/mingw.txt +++ b/src/build-data/os/mingw.txt @@ -19,7 +19,6 @@ mingw32 <target_features> cryptgenrandom loadlibrary -mkgmtime win32_virtual_lock win32_get_systemtime threads diff --git a/src/build-data/os/netbsd.txt b/src/build-data/os/netbsd.txt index 4c82e6239..50d97fd8f 100644 --- a/src/build-data/os/netbsd.txt +++ b/src/build-data/os/netbsd.txt @@ -9,7 +9,6 @@ posix_mlock gmtime_r dlopen readdir -timegm threads filesystem </target_features> diff --git a/src/build-data/os/openbsd.txt b/src/build-data/os/openbsd.txt index 5c7ff3259..b858d497c 100644 --- a/src/build-data/os/openbsd.txt +++ b/src/build-data/os/openbsd.txt @@ -13,7 +13,6 @@ posix_mlock gmtime_r dlopen readdir -timegm sockets threads filesystem diff --git a/src/build-data/os/windows.txt b/src/build-data/os/windows.txt index 898dfc65f..639b0512d 100644 --- a/src/build-data/os/windows.txt +++ b/src/build-data/os/windows.txt @@ -20,7 +20,6 @@ doc_dir docs cryptgenrandom gmtime_s loadlibrary -mkgmtime query_perf_counter virtual_lock rtlsecurezeromemory diff --git a/src/build-data/os/winphone.txt b/src/build-data/os/winphone.txt index bedcc9d2c..0b0318fed 100644 --- a/src/build-data/os/winphone.txt +++ b/src/build-data/os/winphone.txt @@ -13,7 +13,6 @@ doc_dir docs crypto_ng gmtime_s loadlibrary -mkgmtime query_perf_counter rtlsecurezeromemory #stl_filesystem_msvc diff --git a/src/lib/asn1/asn1_time.cpp b/src/lib/asn1/asn1_time.cpp index f6a0c414e..daa0c4e7d 100644 --- a/src/lib/asn1/asn1_time.cpp +++ b/src/lib/asn1/asn1_time.cpp @@ -20,12 +20,12 @@ X509_Time::X509_Time(const std::chrono::system_clock::time_point& time) { calendar_point cal = calendar_value(time); - m_year = cal.year; - m_month = cal.month; - m_day = cal.day; - m_hour = cal.hour; - m_minute = cal.minutes; - m_second = cal.seconds; + m_year = cal.get_year(); + m_month = cal.get_month(); + m_day = cal.get_day(); + m_hour = cal.get_hour(); + m_minute = cal.get_minutes(); + m_second = cal.get_seconds(); m_tag = (m_year >= 2050) ? GENERALIZED_TIME : UTC_TIME; } diff --git a/src/lib/utils/boost/info.txt b/src/lib/utils/boost/info.txt index c12b99a28..d54421147 100644 --- a/src/lib/utils/boost/info.txt +++ b/src/lib/utils/boost/info.txt @@ -1,7 +1,6 @@ <defines> BOOST_FILESYSTEM -> 20131228 BOOST_ASIO -> 20131228 -BOOST_DATETIME -> 20150720 </defines> load_on vendor 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(); } diff --git a/src/lib/utils/calendar.h b/src/lib/utils/calendar.h index 665022599..3f6952524 100644 --- a/src/lib/utils/calendar.h +++ b/src/lib/utils/calendar.h @@ -21,25 +21,26 @@ namespace Botan { class BOTAN_PUBLIC_API(2,0) calendar_point { public: + /** The year */ - uint32_t year; + uint32_t get_year() const { return year; } /** The month, 1 through 12 for Jan to Dec */ - uint32_t month; + uint32_t get_month() const { return month; } /** The day of the month, 1 through 31 (or 28 or 30 based on month */ - uint32_t day; + uint32_t get_day() const { return day; } /** Hour in 24-hour form, 0 to 23 */ - uint32_t hour; + uint32_t get_hour() const { return hour; } /** Minutes in the hour, 0 to 60 */ - uint32_t minutes; + uint32_t get_minutes() const { return minutes; } /** Seconds in the minute, 0 to 60, but might be slightly - larger to deal with leap seconds on some systems + larger to deal with leap seconds on some systems */ - uint32_t seconds; + uint32_t get_seconds() const { return seconds; } /** * Initialize a calendar_point @@ -63,6 +64,17 @@ class BOTAN_PUBLIC_API(2,0) calendar_point * Formatting might change over time. Currently it is RFC339 'iso-date-time'. */ std::string to_string() const; + + /* + The member variables are public for historical reasons. Use the get_xxx() functions + defined above. These members will be made private in a future major release. + */ + uint32_t year; + uint32_t month; + uint32_t day; + uint32_t hour; + uint32_t minutes; + uint32_t seconds; }; /** diff --git a/src/tests/test_utils.cpp b/src/tests/test_utils.cpp index b8df9c270..3c4accab2 100644 --- a/src/tests/test_utils.cpp +++ b/src/tests/test_utils.cpp @@ -274,26 +274,26 @@ class Date_Format_Tests final : public Text_Based_Test if(type == "valid" || type == "valid.not_std" || type == "valid.64_bit_time_t") { Botan::calendar_point c(d[0], d[1], d[2], d[3], d[4], d[5]); - result.test_is_eq(date_str + " year", c.year, d[0]); - result.test_is_eq(date_str + " month", c.month, d[1]); - result.test_is_eq(date_str + " day", c.day, d[2]); - result.test_is_eq(date_str + " hour", c.hour, d[3]); - result.test_is_eq(date_str + " minute", c.minutes, d[4]); - result.test_is_eq(date_str + " second", c.seconds, d[5]); - - if(type == "valid.not_std" || (type == "valid.64_bit_time_t" && c.year > 2037 && sizeof(std::time_t) == 4)) + result.test_is_eq(date_str + " year", c.get_year(), d[0]); + result.test_is_eq(date_str + " month", c.get_month(), d[1]); + result.test_is_eq(date_str + " day", c.get_day(), d[2]); + result.test_is_eq(date_str + " hour", c.get_hour(), d[3]); + result.test_is_eq(date_str + " minute", c.get_minutes(), d[4]); + result.test_is_eq(date_str + " second", c.get_seconds(), d[5]); + + if(type == "valid.not_std" || (type == "valid.64_bit_time_t" && c.get_year() > 2037 && sizeof(std::time_t) == 4)) { result.test_throws("valid but out of std::timepoint range", [c]() { c.to_std_timepoint(); }); } else { Botan::calendar_point c2 = Botan::calendar_value(c.to_std_timepoint()); - result.test_is_eq(date_str + " year", c2.year, d[0]); - result.test_is_eq(date_str + " month", c2.month, d[1]); - result.test_is_eq(date_str + " day", c2.day, d[2]); - result.test_is_eq(date_str + " hour", c2.hour, d[3]); - result.test_is_eq(date_str + " minute", c2.minutes, d[4]); - result.test_is_eq(date_str + " second", c2.seconds, d[5]); + result.test_is_eq(date_str + " year", c2.get_year(), d[0]); + result.test_is_eq(date_str + " month", c2.get_month(), d[1]); + result.test_is_eq(date_str + " day", c2.get_day(), d[2]); + result.test_is_eq(date_str + " hour", c2.get_hour(), d[3]); + result.test_is_eq(date_str + " minute", c2.get_minutes(), d[4]); + result.test_is_eq(date_str + " second", c2.get_seconds(), d[5]); } } else if(type == "invalid") |