PATCH support for DateTime

This commit adds PATCH support for the DateTime property.
To set the BMC time, it uses the
xyz.openbmc_project.Time.EpochTime.Elapsed property.
The BMC time can only be set if the BMC owns it's own time *and*
the time is not automatically synced with NTP server(s).
The input JSON for the PATCH request must speicfy datetime in
extended ISO 8601 format.

Tested:
=======

Precondition: Time owner should be BMC and sync method should be
Manual.

busctl get-property xyz.openbmc_project.Settings
/xyz/openbmc_project/time/owner
xyz.openbmc_project.Time.Owner TimeOwner
s "xyz.openbmc_project.Time.Owner.Owners.BMC"

busctl get-property xyz.openbmc_project.Settings
/xyz/openbmc_project/time/sync_method
xyz.openbmc_project.Time.Synchronization TimeSyncMethod
s "xyz.openbmc_project.Time.Synchronization.Method.Manual"

-- Setting date time:

curl -k -H "X-Auth-Token: $bmc_token" -X PATCH
https://${bmc}:${port}/redfish/v1/Managers/bmc -d
'{"DateTime": "2019-03-20T08:47:30.345+00:00"}'

{
  "DateTime": "2019-03-20T08:47:30.345+00:00"
}

Invalid date time string:
curl -k -H "X-Auth-Token: $bmc_token" -X PATCH
https://${bmc}:${port}/redfish/v1/Managers/bmc -d
'{"DateTime": "2019-03-20T08:47:30.345+ds:00"}'

{
  "DateTime@Message.ExtendedInfo": [
    {
      "@odata.type": "/redfish/v1/$metadata#Message.v1_0_0.Message",
      "Message": "The value 2019-03-20T08:47:30.345+ds:00 for the property
DateTime is of a different format than the property can accept.",
      "MessageArgs": [
        "2019-03-20T08:47:30.345+ds:00",
        "DateTime"
      ],
      "MessageId": "Base.1.4.0.PropertyValueFormatError",
      "Resolution": "Correct the value for the property in the request body and
resubmit the request if the operation failed.",
      "Severity": "Warning"
    }
  ]
}

When the time sync method is NTP, the PATCH request fails as expected:
busctl get-property xyz.openbmc_project.Settings
/xyz/openbmc_project/time/sync_method
xyz.openbmc_project.Time.Synchronization TimeSyncMethod
s "xyz.openbmc_project.Time.Synchronization.Method.NTP"

curl -k -H "X-Auth-Token: $bmc_token" -X PATCH
https://${bmc}:${port}/redfish/v1/Managers/bmc -d '{"DateTime":
"2019-03-20T08:47:30+00:00"}'
{
  "error": {
    "@Message.ExtendedInfo": [
      {
        "@odata.type": "/redfish/v1/$metadata#Message.v1_0_0.Message",
        "Message": "The request failed due to an internal service error.  The
service is still operational.",
        "MessageArgs": [],
        "MessageId": "Base.1.4.0.InternalError",
        "Resolution": "Resubmit the request.  If the problem persists, consider
resetting the service.",
        "Severity": "Critical"
      }
    ],
    "code": "Base.1.4.0.InternalError",
    "message": "The request failed due to an internal service error.  The
service is still operational."
  }
}

When the time sync method is Manual, but the time owner is Host,
PATCH fails as expected again:

busctl get-property xyz.openbmc_project.Settings
/xyz/openbmc_project/time/owner
xyz.openbmc_project.Time.Owner TimeOwner
s "xyz.openbmc_project.Time.Owner.Owners.Host"

busctl get-property xyz.openbmc_project.Settings
/xyz/openbmc_project/time/sync_method
xyz.openbmc_project.Time.Synchronization TimeSyncMethod
s "xyz.openbmc_project.Time.Synchronization.Method.Manual"

curl -k -H "X-Auth-Token: $bmc_token" -X PATCH
https://${bmc}:${port}/redfish/v1/Managers/bmc -d '{"DateTime":
"2019-03-20T08:47:30+00:00"}'
{
  "error": {
    "@Message.ExtendedInfo": [
      {
        "@odata.type": "/redfish/v1/$metadata#Message.v1_0_0.Message",
        "Message": "The request failed due to an internal service error.  The
service is still operational.",
        "MessageArgs": [],
        "MessageId": "Base.1.4.0.InternalError",
        "Resolution": "Resubmit the request.  If the problem persists, consider
resetting the service.",
        "Severity": "Critical"
      }
    ],
    "code": "Base.1.4.0.InternalError",
    "message": "The request failed due to an internal service error.  The
service is still operational."
  }
}

Change-Id: Ie4a71e639b9a6577fae8627f0f69b6179506eb58
Signed-off-by: Santosh Puranik <santosh.puranik@in.ibm.com>
diff --git a/redfish-core/lib/managers.hpp b/redfish-core/lib/managers.hpp
index c73a218..a9efb4c 100644
--- a/redfish-core/lib/managers.hpp
+++ b/redfish-core/lib/managers.hpp
@@ -18,7 +18,9 @@
 #include "node.hpp"
 
 #include <boost/algorithm/string/replace.hpp>
+#include <boost/date_time.hpp>
 #include <dbus_utility.hpp>
+#include <sstream>
 #include <variant>
 
 namespace redfish
@@ -1243,8 +1245,9 @@
                  const std::vector<std::string>& params) override
     {
         std::optional<nlohmann::json> oem;
+        std::optional<std::string> datetime;
 
-        if (!json_util::readJson(req, res, "Oem", oem))
+        if (!json_util::readJson(req, res, "Oem", oem, "DateTime", datetime))
         {
             return;
         }
@@ -1276,6 +1279,61 @@
                 }
             }
         }
+        if (datetime)
+        {
+            setDateTime(response, std::move(*datetime));
+        }
+    }
+
+    void setDateTime(std::shared_ptr<AsyncResp> aResp,
+                     std::string datetime) const
+    {
+        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",
+                std::variant<uint64_t>(durMicroSecs));
+        }
+        else
+        {
+            messages::propertyValueFormatError(aResp->res, datetime,
+                                               "DateTime");
+            return;
+        }
     }
 
     std::string uuid;