PEL: Convert a BCDTime to epoch milliseconds

Add a new function to convert a BCDTime value from a PEL to the number
of milliseconds since the epoch.

This will be used to put the PEL creation timestamp on D-Bus.

Signed-off-by: Matt Spinler <spinler@us.ibm.com>
Change-Id: Ic470c9324e959b3e6a7eea29db4162a45dd54fc3
diff --git a/extensions/openpower-pels/bcd_time.cpp b/extensions/openpower-pels/bcd_time.cpp
index c5dd0d8..42e2e73 100644
--- a/extensions/openpower-pels/bcd_time.cpp
+++ b/extensions/openpower-pels/bcd_time.cpp
@@ -15,6 +15,11 @@
  */
 #include "bcd_time.hpp"
 
+#include <fmt/format.h>
+#include <time.h>
+
+#include <phosphor-logging/log.hpp>
+
 namespace openpower
 {
 namespace pels
@@ -66,6 +71,29 @@
     return getBCDTime(time);
 }
 
+uint64_t getMillisecondsSinceEpoch(const BCDTime& bcdTime)
+{
+    // Convert a UTC tm struct to a UTC time_t struct to a timepoint.
+    int year = (fromBCD(bcdTime.yearMSB) * 100) + fromBCD(bcdTime.yearLSB);
+    tm utcTime;
+    utcTime.tm_year = year - 1900;
+    utcTime.tm_mon = fromBCD(bcdTime.month) - 1;
+    utcTime.tm_mday = fromBCD(bcdTime.day);
+    utcTime.tm_hour = fromBCD(bcdTime.hour);
+    utcTime.tm_min = fromBCD(bcdTime.minutes);
+    utcTime.tm_sec = fromBCD(bcdTime.seconds);
+    utcTime.tm_isdst = 0;
+
+    time_t t = timegm(&utcTime);
+    auto timepoint = std::chrono::system_clock::from_time_t(t);
+    int milliseconds = fromBCD(bcdTime.hundredths) * 10;
+    timepoint += std::chrono::milliseconds(milliseconds);
+
+    return std::chrono::duration_cast<std::chrono::milliseconds>(
+               timepoint.time_since_epoch())
+        .count();
+}
+
 Stream& operator>>(Stream& s, BCDTime& time)
 {
     s >> time.yearMSB >> time.yearLSB >> time.month >> time.day >> time.hour;
diff --git a/extensions/openpower-pels/bcd_time.hpp b/extensions/openpower-pels/bcd_time.hpp
index 5679a5b..c2f6611 100644
--- a/extensions/openpower-pels/bcd_time.hpp
+++ b/extensions/openpower-pels/bcd_time.hpp
@@ -57,6 +57,15 @@
 BCDTime getBCDTime(uint64_t milliseconds);
 
 /**
+ * @brief Convert a BCDTime value into the number of
+ *        milliseconds since the epoch (1/1/1970).
+ *
+ * @param[in] bcdTime - The BCD time value
+ * @return uint64_t - The milliseconds value
+ */
+uint64_t getMillisecondsSinceEpoch(const BCDTime& bcdTime);
+
+/**
  * @brief Converts a number to a BCD.
  *
  * For example 32 -> 0x32.
@@ -86,6 +95,19 @@
 }
 
 /**
+ * @brief Converts a BCD byte to a decimal number.
+ *
+ * For example 0x22 -> 22.
+ *
+ * @param[in] bcd - The value in BCD
+ * @return int - The number in decimal
+ */
+inline int fromBCD(uint8_t bcd)
+{
+    return (((bcd & 0xF0) >> 4) * 10) + (bcd & 0xF);
+}
+
+/**
  * @brief Stream extraction operator for BCDTime
  *
  * @param[in] s - the Stream
diff --git a/test/openpower-pels/bcd_time_test.cpp b/test/openpower-pels/bcd_time_test.cpp
index 6fe761c..1f444b3 100644
--- a/test/openpower-pels/bcd_time_test.cpp
+++ b/test/openpower-pels/bcd_time_test.cpp
@@ -103,3 +103,20 @@
 
     ASSERT_EQ(getBCDTime(now), getBCDTime(ms));
 }
+
+TEST(BCDTimeTest, GetMillisecondsSinceEpochTest)
+{
+    // Convert current time to a BCDTime to use
+    auto now = std::chrono::system_clock::now();
+    uint64_t ms = std::chrono::duration_cast<std::chrono::milliseconds>(
+                      now.time_since_epoch())
+                      .count();
+    auto bcdTime = getBCDTime(ms);
+
+    // BCDTime only tracks down to hundredths of a second (10ms),
+    // so some precision will be lost converting back to milliseconds.
+    // e.g. 12345 -> 12340
+    ms = ms - (ms % 10);
+
+    EXPECT_EQ(ms, getMillisecondsSinceEpoch(bcdTime));
+}
diff --git a/test/openpower-pels/pel_manager_test.cpp b/test/openpower-pels/pel_manager_test.cpp
index 36b1d14..4352153 100644
--- a/test/openpower-pels/pel_manager_test.cpp
+++ b/test/openpower-pels/pel_manager_test.cpp
@@ -503,7 +503,7 @@
 // An ESEL from the wild
 const std::string esel{
     "00 00 df 00 00 00 00 20 00 04 12 01 6f aa 00 00 "
-    "50 48 00 30 01 00 33 00 00 00 00 07 5c 69 cc 0d 00 00 00 07 5c d5 50 db "
+    "50 48 00 30 01 00 33 00 20 23 05 11 10 20 20 00 00 00 00 07 5c d5 50 db "
     "42 00 00 10 00 00 00 00 00 00 00 00 00 00 00 00 90 00 00 4e 90 00 00 4e "
     "55 48 00 18 01 00 09 00 8a 03 40 00 00 00 00 00 ff ff 00 00 00 00 00 00 "
     "50 53 00 50 01 01 00 00 02 00 00 09 33 2d 00 48 00 00 00 e0 00 00 10 00 "