util: fix Y2038 problem in time::toString()

Avoid using time_t, which cannot correctly represent
dates after 19 January 2038 on 32-bit systems.

Change-Id: I04e8d31a1c04a112cbc81810143fe2233e6dcc22
Refs: #3915
diff --git a/src/util/time.cpp b/src/util/time.cpp
index d3e546a..64b43e3 100644
--- a/src/util/time.cpp
+++ b/src/util/time.cpp
@@ -105,7 +105,7 @@
 const system_clock::TimePoint&
 getUnixEpoch()
 {
-  static auto epoch = system_clock::from_time_t(0);
+  static constexpr system_clock::TimePoint epoch(seconds::zero());
   return epoch;
 }
 
@@ -121,23 +121,30 @@
   return getUnixEpoch() + duration;
 }
 
-std::string
-toIsoString(const system_clock::TimePoint& timePoint)
+static boost::posix_time::ptime
+convertToPosixTime(const system_clock::TimePoint& timePoint)
 {
   namespace bpt = boost::posix_time;
   static bpt::ptime epoch(boost::gregorian::date(1970, 1, 1));
 
-#ifdef BOOST_DATE_TIME_POSIX_TIME_STD_CONFIG
-  using BptResolutionUnit = nanoseconds;
+  using BptResolution =
+#if defined(BOOST_DATE_TIME_HAS_NANOSECONDS)
+    nanoseconds;
+#elif defined(BOOST_DATE_TIME_HAS_MICROSECONDS)
+    microseconds;
 #else
-  using BptResolutionUnit = microseconds;
+    milliseconds;
 #endif
-  constexpr auto unitsPerHour = duration_cast<BptResolutionUnit>(hours(1)).count();
+  constexpr auto unitsPerHour = duration_cast<BptResolution>(1_h).count();
 
-  auto sinceEpoch = duration_cast<BptResolutionUnit>(timePoint - getUnixEpoch()).count();
-  bpt::ptime ptime = epoch + bpt::time_duration(sinceEpoch / unitsPerHour, 0, 0, sinceEpoch % unitsPerHour);
+  auto sinceEpoch = duration_cast<BptResolution>(timePoint - getUnixEpoch()).count();
+  return epoch + bpt::time_duration(sinceEpoch / unitsPerHour, 0, 0, sinceEpoch % unitsPerHour);
+}
 
-  return bpt::to_iso_string(ptime);
+std::string
+toIsoString(const system_clock::TimePoint& timePoint)
+{
+  return boost::posix_time::to_iso_string(convertToPosixTime(timePoint));
 }
 
 static system_clock::TimePoint
@@ -146,11 +153,11 @@
   namespace bpt = boost::posix_time;
   static bpt::ptime epoch(boost::gregorian::date(1970, 1, 1));
 
-  // .total_seconds() has issue with large dates until Boost 1.66. See Issue #4478
-  // from_time_t has issues with large dates on 32-bit platforms
-  auto point = system_clock::time_point(seconds((ptime - epoch).ticks() / bpt::time_duration::ticks_per_second()));
-  point += microseconds((ptime - epoch).total_microseconds() % 1000000);
-  return point;
+  // .total_seconds() has an issue with large dates until Boost 1.66, see #4478.
+  // time_t overflows for large dates on 32-bit platforms (Y2038 problem).
+  auto sinceEpoch = ptime - epoch;
+  auto point = system_clock::TimePoint(seconds(sinceEpoch.ticks() / bpt::time_duration::ticks_per_second()));
+  return point + microseconds(sinceEpoch.total_microseconds() % 1000000);
 }
 
 system_clock::TimePoint
@@ -165,17 +172,13 @@
          const std::locale& locale/* = std::locale("C")*/)
 {
   namespace bpt = boost::posix_time;
-  bpt::ptime ptime = bpt::from_time_t(system_clock::to_time_t(timePoint));
 
-  uint64_t micro = duration_cast<microseconds>(timePoint - getUnixEpoch()).count() % 1000000;
-  ptime += bpt::microseconds(micro);
+  std::ostringstream os;
+  auto* facet = new bpt::time_facet(format.data());
+  os.imbue(std::locale(locale, facet));
+  os << convertToPosixTime(timePoint);
 
-  bpt::time_facet* facet = new bpt::time_facet(format.c_str());
-  std::ostringstream formattedTimePoint;
-  formattedTimePoint.imbue(std::locale(locale, facet));
-  formattedTimePoint << ptime;
-
-  return formattedTimePoint.str();
+  return os.str();
 }
 
 system_clock::TimePoint
@@ -185,8 +188,8 @@
 {
   namespace bpt = boost::posix_time;
 
-  bpt::time_input_facet* facet = new bpt::time_input_facet(format);
   std::istringstream is(timePointStr);
+  auto* facet = new bpt::time_input_facet(format);
   is.imbue(std::locale(locale, facet));
   bpt::ptime ptime;
   is >> ptime;
diff --git a/tests/unit-tests/util/time.t.cpp b/tests/unit-tests/util/time.t.cpp
index b2988f2..a60e2d9 100644
--- a/tests/unit-tests/util/time.t.cpp
+++ b/tests/unit-tests/util/time.t.cpp
@@ -52,15 +52,14 @@
 
   BOOST_CHECK_EQUAL(fromIsoString("20140129T034247.032000"), referenceTime);
   BOOST_CHECK_EQUAL(fromIsoString("20140129T034247.032000Z"), referenceTime);
-  BOOST_CHECK_EQUAL(fromString("2014-01-29 03:42:47"),
-                    fromUnixTimestamp(seconds(1390966967)));
+  BOOST_CHECK_EQUAL(fromString("2014-01-29 03:42:47"), fromUnixTimestamp(1390966967_s));
 
   // Unfortunately, not all systems has lv_LV locale installed :(
   // BOOST_CHECK_EQUAL(fromString("2014. gada 29. Janvāris", "%Y. gada %d. %B", std::locale("lv_LV.UTF-8")),
-  //                   fromUnixTimestamp(seconds(1390953600)));
+  //                   fromUnixTimestamp(1390953600_s));
 
   BOOST_CHECK_EQUAL(fromString("2014 -- 29 -- January", "%Y -- %d -- %B", std::locale("C")),
-                    fromUnixTimestamp(seconds(1390953600)));
+                    fromUnixTimestamp(1390953600_s));
 }
 
 BOOST_AUTO_TEST_CASE(SteadyClock)
@@ -80,13 +79,17 @@
 
 BOOST_AUTO_TEST_CASE(LargeDates)
 {
-  auto value = fromUnixTimestamp(milliseconds(1390966967032LL));
+  auto value = fromUnixTimestamp(1390966967032_ms);
   BOOST_CHECK_EQUAL(toIsoString(value), "20140129T034247.032000");
   BOOST_CHECK_EQUAL(fromIsoString("20140129T034247.032000"), value);
+  BOOST_CHECK_EQUAL(toString(value, "%Y-%m-%d %H:%M:%S%F"), "2014-01-29 03:42:47.032000");
+  BOOST_CHECK_EQUAL(fromString("2014-01-29 03:42:47.032000", "%Y-%m-%d %H:%M:%S%F"), value);
 
   value += 36524_days;
   BOOST_CHECK_EQUAL(toIsoString(value), "21140129T034247.032000");
   BOOST_CHECK_EQUAL(fromIsoString("21140129T034247.032000"), value);
+  BOOST_CHECK_EQUAL(toString(value, "%Y-%m-%d %H:%M:%S%F"), "2114-01-29 03:42:47.032000");
+  BOOST_CHECK_EQUAL(fromString("2114-01-29 03:42:47.03200", "%Y-%m-%d %H:%M:%S%F"), value);
 }
 
 BOOST_AUTO_TEST_CASE(Literals)