Fix FRU BuildDate DBus/IPMI exporting

When BMC exports the date into DBUS it translates/expands the 3 bytes FRU
format date into an easy to read string as in "2015-11-06 - 22:23:00", when
exporting the DBUS date back to IPMI it needs to translate back this date
into the 3 bytes FRU format, without this fix we get a build date in 1996
that is the epoch for the FRU date format.  This change reconstructs the
FRU date from the easy to read string for exporting into IPMI.

Tested: Manual check that BuildDate from busctl and ipmitool match

Resolves openbmc/openbmc#3013

Change-Id: I0fd1dd8f945f18b53c14bff321c9e233fdb2d742
Signed-off-by: Andres Oportus <andresoportus@google.com>
diff --git a/ipmi_fru_info_area.cpp b/ipmi_fru_info_area.cpp
index 386b164..1951f8c 100644
--- a/ipmi_fru_info_area.cpp
+++ b/ipmi_fru_info_area.cpp
@@ -1,6 +1,9 @@
 #include <algorithm>
 #include <map>
 #include <numeric>
+
+#include <ctime>
+
 #include "ipmi_fru_info_area.hpp"
 #include <phosphor-logging/elog.hpp>
 namespace ipmi
@@ -36,6 +39,9 @@
 static constexpr uint8_t typeASCII             = 0xC0;
 static constexpr auto maxRecordAttributeValue  = 0x1F;
 
+static constexpr auto secs_from_1970_1996 = 820454400;
+static constexpr auto secs_per_min = 60;
+
 /**
  * @brief Format Beginning of Individual IPMI FRU Data Section
  *
@@ -156,13 +162,28 @@
     auto iter = propMap.find(buildDate);
     if (iter != propMap.end())
     {
-        auto& value = iter->second;
-        if (value.length() == manufacturingDateSize)
+        tm time = {};
+        strptime(iter->second.c_str(), "%F - %H:%M:%S", &time);
+        time_t raw = mktime(&time);
+
+        // From FRU Spec:
+        // "Mfg. Date / Time
+        // Number of minutes from 0:00 hrs 1/1/96.
+        // LSbyte first (little endian)
+        // 00_00_00h = unspecified."
+        if (raw > secs_from_1970_1996)
         {
-            std::copy(
-                value.begin(), value.end(), std::back_inserter(data));
+            raw -= secs_from_1970_1996;
+            raw /= secs_per_min;
+            uint8_t fru_raw[3];
+            fru_raw[0] = raw & 0xFF;
+            fru_raw[1] = (raw >> 8) & 0xFF;
+            fru_raw[2] = (raw >> 16) & 0xFF;
+            std::copy(fru_raw, fru_raw + 3, std::back_inserter(data));
             return;
         }
+        fprintf(stderr, "MgfDate invalid date: %u secs since UNIX epoch\n",
+                static_cast<unsigned int>(raw));
     }
     //Blank date
     data.emplace_back(0);