Break out set time function and unit test it

This function is something that's easily unit tested.  Do it.

Signed-off-by: Ed Tanous <edtanous@google.com>
Change-Id: I8d664c77ec4b3a9886128597449c5f9c041b86b3
diff --git a/redfish-core/include/utils/time_utils.hpp b/redfish-core/include/utils/time_utils.hpp
index 9b13147..f4fb0c0 100644
--- a/redfish-core/include/utils/time_utils.hpp
+++ b/redfish-core/include/utils/time_utils.hpp
@@ -2,6 +2,8 @@
 
 #include "logging.hpp"
 
+#include <boost/date_time.hpp>
+
 #include <algorithm>
 #include <charconv>
 #include <chrono>
@@ -414,5 +416,31 @@
     return std::make_pair(dateTime, timeOffset);
 }
 
+using usSinceEpoch = std::chrono::duration<uint64_t, std::micro>;
+inline std::optional<usSinceEpoch> dateStringToEpoch(std::string_view datetime)
+{
+    std::string date(datetime);
+    std::stringstream stream(date);
+    // Convert from ISO 8601 to boost local_time
+    // (BMC only has time in UTC)
+    boost::posix_time::ptime posixTime;
+    boost::posix_time::ptime epoch(boost::gregorian::date(1970, 1, 1));
+    // Facet gets deleted with the stringsteam
+    auto ifc = std::make_unique<boost::local_time::local_time_input_facet>(
+        "%Y-%m-%d %H:%M:%S%F %ZP");
+    stream.imbue(std::locale(stream.getloc(), ifc.release()));
+
+    boost::local_time::local_date_time ldt(boost::local_time::not_a_date_time);
+
+    if (!(stream >> ldt))
+    {
+        return std::nullopt;
+    }
+    posixTime = ldt.utc_time();
+    boost::posix_time::time_duration dur = posixTime - epoch;
+    uint64_t durMicroSecs = static_cast<uint64_t>(dur.total_microseconds());
+    return std::chrono::duration<uint64_t, std::micro>{durMicroSecs};
+}
+
 } // namespace time_utils
 } // namespace redfish
diff --git a/redfish-core/lib/managers.hpp b/redfish-core/lib/managers.hpp
index 74008cb..e8fa14b 100644
--- a/redfish-core/lib/managers.hpp
+++ b/redfish-core/lib/managers.hpp
@@ -27,7 +27,6 @@
 #include "utils/systemd_utils.hpp"
 #include "utils/time_utils.hpp"
 
-#include <boost/date_time.hpp>
 #include <sdbusplus/asio/property.hpp>
 #include <sdbusplus/unpack_properties.hpp>
 
@@ -1887,46 +1886,30 @@
 {
     BMCWEB_LOG_DEBUG << "Set date time: " << datetime;
 
-    std::stringstream stream(datetime);
-    // Convert from ISO 8601 to boost local_time
-    // (BMC only has time in UTC)
-    boost::posix_time::ptime posixTime;
-    boost::posix_time::ptime epoch(boost::gregorian::date(1970, 1, 1));
-    // Facet gets deleted with the stringsteam
-    auto ifc = std::make_unique<boost::local_time::local_time_input_facet>(
-        "%Y-%m-%d %H:%M:%S%F %ZP");
-    stream.imbue(std::locale(stream.getloc(), ifc.release()));
-
-    boost::local_time::local_date_time ldt(boost::local_time::not_a_date_time);
-
-    if (stream >> ldt)
-    {
-        posixTime = ldt.utc_time();
-        boost::posix_time::time_duration dur = posixTime - epoch;
-        uint64_t durMicroSecs = static_cast<uint64_t>(dur.total_microseconds());
-        crow::connections::systemBus->async_method_call(
-            [aResp{std::move(aResp)}, datetime{std::move(datetime)}](
-                const boost::system::error_code ec) {
-            if (ec)
-            {
-                BMCWEB_LOG_DEBUG << "Failed to set elapsed time. "
-                                    "DBUS response error "
-                                 << ec;
-                messages::internalError(aResp->res);
-                return;
-            }
-            aResp->res.jsonValue["DateTime"] = datetime;
-            },
-            "xyz.openbmc_project.Time.Manager", "/xyz/openbmc_project/time/bmc",
-            "org.freedesktop.DBus.Properties", "Set",
-            "xyz.openbmc_project.Time.EpochTime", "Elapsed",
-            dbus::utility::DbusVariantType(durMicroSecs));
-    }
-    else
+    std::optional<redfish::time_utils::usSinceEpoch> us =
+        redfish::time_utils::dateStringToEpoch(datetime);
+    if (!us)
     {
         messages::propertyValueFormatError(aResp->res, datetime, "DateTime");
         return;
     }
+    crow::connections::systemBus->async_method_call(
+        [aResp{std::move(aResp)},
+         datetime{std::move(datetime)}](const boost::system::error_code ec) {
+        if (ec)
+        {
+            BMCWEB_LOG_DEBUG << "Failed to set elapsed time. "
+                                "DBUS response error "
+                             << ec;
+            messages::internalError(aResp->res);
+            return;
+        }
+        aResp->res.jsonValue["DateTime"] = datetime;
+        },
+        "xyz.openbmc_project.Time.Manager", "/xyz/openbmc_project/time/bmc",
+        "org.freedesktop.DBus.Properties", "Set",
+        "xyz.openbmc_project.Time.EpochTime", "Elapsed",
+        dbus::utility::DbusVariantType(us->count()));
 }
 
 inline void requestRoutesManager(App& app)
diff --git a/test/redfish-core/include/utils/time_utils_test.cpp b/test/redfish-core/include/utils/time_utils_test.cpp
index 035d8ce..873a385 100644
--- a/test/redfish-core/include/utils/time_utils_test.cpp
+++ b/test/redfish-core/include/utils/time_utils_test.cpp
@@ -139,5 +139,37 @@
               "1970-01-01T00:00:00.000000+00:00");
 }
 
+TEST(Utility, DateStringToEpoch)
+{
+    EXPECT_EQ(dateStringToEpoch("2021-11-30T22:41:35.123456+00:00"),
+              usSinceEpoch{1638312095123456});
+    // no timezone
+    EXPECT_EQ(dateStringToEpoch("2021-11-30T22:41:35.123456"),
+              usSinceEpoch{1638312095123456});
+    // Milliseconds precision
+    EXPECT_EQ(dateStringToEpoch("2021-11-30T22:41:35.123"),
+              usSinceEpoch{1638312095123000});
+    // Seconds precision
+    EXPECT_EQ(dateStringToEpoch("2021-11-30T22:41:35"),
+              usSinceEpoch{1638312095000000});
+
+    // Non zero timezone
+    EXPECT_EQ(dateStringToEpoch("2021-11-30T22:41:35.123456+04:00"),
+              usSinceEpoch{1638297695123456});
+
+    // Epoch
+    EXPECT_EQ(dateStringToEpoch("1970-01-01T00:00:00.000000+00:00"),
+              usSinceEpoch{0});
+
+    // Max time
+    EXPECT_EQ(dateStringToEpoch("9999-12-31T23:59:59.999999+00:00"),
+              usSinceEpoch{253402300799999999});
+
+    // Underflow
+    // Currently gives wrong result
+    // EXPECT_EQ(dateStringToEpoch("1969-12-30T23:59:59.999999+00:00"),
+    //          std::nullopt);
+}
+
 } // namespace
 } // namespace redfish::time_utils