xref: /openbmc/bmcweb/features/redfish/src/utils/time_utils.cpp (revision a93e9c77c09e4429b5c447dbc1480278bb2a28e3)
140e9b92eSEd Tanous // SPDX-License-Identifier: Apache-2.0
240e9b92eSEd Tanous // SPDX-FileCopyrightText: Copyright OpenBMC Authors
31b8b02a4SEd Tanous #include "utils/time_utils.hpp"
41b8b02a4SEd Tanous 
5*a93e9c77SEd Tanous #include "error_messages.hpp"
6*a93e9c77SEd Tanous #include "http_response.hpp"
7*a93e9c77SEd Tanous #include "logging.hpp"
8*a93e9c77SEd Tanous 
967b2e53bSEd Tanous #include <version>
1067b2e53bSEd Tanous 
11a8770740SEd Tanous #if __cpp_lib_chrono < 201907L
121b8b02a4SEd Tanous #include "utils/extern/date.h"
13a8770740SEd Tanous #endif
141b8b02a4SEd Tanous #include <array>
15*a93e9c77SEd Tanous #include <charconv>
161b8b02a4SEd Tanous #include <chrono>
17*a93e9c77SEd Tanous #include <cstddef>
18*a93e9c77SEd Tanous #include <cstdint>
19*a93e9c77SEd Tanous #include <ctime>
20*a93e9c77SEd Tanous #include <format>
211b8b02a4SEd Tanous #include <optional>
22*a93e9c77SEd Tanous #include <ratio>
231b8b02a4SEd Tanous #include <sstream>
241b8b02a4SEd Tanous #include <string>
251b8b02a4SEd Tanous #include <string_view>
26*a93e9c77SEd Tanous #include <system_error>
27*a93e9c77SEd Tanous #include <utility>
281b8b02a4SEd Tanous 
291b8b02a4SEd Tanous namespace redfish::time_utils
301b8b02a4SEd Tanous {
31c51afd54SEd Tanous 
32*a93e9c77SEd Tanous /**
33*a93e9c77SEd Tanous  * @brief Convert string that represents value in Duration Format to its numeric
34*a93e9c77SEd Tanous  *        equivalent.
35*a93e9c77SEd Tanous  */
36*a93e9c77SEd Tanous std::optional<std::chrono::milliseconds> fromDurationString(std::string_view v)
37*a93e9c77SEd Tanous {
38*a93e9c77SEd Tanous     std::chrono::milliseconds out = std::chrono::milliseconds::zero();
39*a93e9c77SEd Tanous     enum class ProcessingStage
40*a93e9c77SEd Tanous     {
41*a93e9c77SEd Tanous         // P1DT1H1M1.100S
42*a93e9c77SEd Tanous         P,
43*a93e9c77SEd Tanous         Days,
44*a93e9c77SEd Tanous         Hours,
45*a93e9c77SEd Tanous         Minutes,
46*a93e9c77SEd Tanous         Seconds,
47*a93e9c77SEd Tanous         Milliseconds,
48*a93e9c77SEd Tanous         Done,
49*a93e9c77SEd Tanous     };
50*a93e9c77SEd Tanous     ProcessingStage stage = ProcessingStage::P;
51*a93e9c77SEd Tanous 
52*a93e9c77SEd Tanous     while (!v.empty())
53*a93e9c77SEd Tanous     {
54*a93e9c77SEd Tanous         if (stage == ProcessingStage::P)
55*a93e9c77SEd Tanous         {
56*a93e9c77SEd Tanous             if (v.front() != 'P')
57*a93e9c77SEd Tanous             {
58*a93e9c77SEd Tanous                 return std::nullopt;
59*a93e9c77SEd Tanous             }
60*a93e9c77SEd Tanous             v.remove_prefix(1);
61*a93e9c77SEd Tanous             stage = ProcessingStage::Days;
62*a93e9c77SEd Tanous             continue;
63*a93e9c77SEd Tanous         }
64*a93e9c77SEd Tanous         if (stage == ProcessingStage::Days)
65*a93e9c77SEd Tanous         {
66*a93e9c77SEd Tanous             if (v.front() == 'T')
67*a93e9c77SEd Tanous             {
68*a93e9c77SEd Tanous                 v.remove_prefix(1);
69*a93e9c77SEd Tanous                 stage = ProcessingStage::Hours;
70*a93e9c77SEd Tanous                 continue;
71*a93e9c77SEd Tanous             }
72*a93e9c77SEd Tanous         }
73*a93e9c77SEd Tanous         uint64_t ticks = 0;
74*a93e9c77SEd Tanous         auto [ptr, ec] = std::from_chars(v.begin(), v.end(), ticks);
75*a93e9c77SEd Tanous         if (ec != std::errc())
76*a93e9c77SEd Tanous         {
77*a93e9c77SEd Tanous             BMCWEB_LOG_ERROR("Failed to convert string \"{}\" to decimal", v);
78*a93e9c77SEd Tanous             return std::nullopt;
79*a93e9c77SEd Tanous         }
80*a93e9c77SEd Tanous         size_t charactersRead = static_cast<size_t>(ptr - v.data());
81*a93e9c77SEd Tanous         if (ptr >= v.end())
82*a93e9c77SEd Tanous         {
83*a93e9c77SEd Tanous             BMCWEB_LOG_ERROR("Missing postfix");
84*a93e9c77SEd Tanous             return std::nullopt;
85*a93e9c77SEd Tanous         }
86*a93e9c77SEd Tanous         if (*ptr == 'D')
87*a93e9c77SEd Tanous         {
88*a93e9c77SEd Tanous             if (stage > ProcessingStage::Days)
89*a93e9c77SEd Tanous             {
90*a93e9c77SEd Tanous                 return std::nullopt;
91*a93e9c77SEd Tanous             }
92*a93e9c77SEd Tanous             out += std::chrono::days(ticks);
93*a93e9c77SEd Tanous         }
94*a93e9c77SEd Tanous         else if (*ptr == 'H')
95*a93e9c77SEd Tanous         {
96*a93e9c77SEd Tanous             if (stage > ProcessingStage::Hours)
97*a93e9c77SEd Tanous             {
98*a93e9c77SEd Tanous                 return std::nullopt;
99*a93e9c77SEd Tanous             }
100*a93e9c77SEd Tanous             out += std::chrono::hours(ticks);
101*a93e9c77SEd Tanous         }
102*a93e9c77SEd Tanous         else if (*ptr == 'M')
103*a93e9c77SEd Tanous         {
104*a93e9c77SEd Tanous             if (stage > ProcessingStage::Minutes)
105*a93e9c77SEd Tanous             {
106*a93e9c77SEd Tanous                 return std::nullopt;
107*a93e9c77SEd Tanous             }
108*a93e9c77SEd Tanous             out += std::chrono::minutes(ticks);
109*a93e9c77SEd Tanous         }
110*a93e9c77SEd Tanous         else if (*ptr == '.')
111*a93e9c77SEd Tanous         {
112*a93e9c77SEd Tanous             if (stage > ProcessingStage::Seconds)
113*a93e9c77SEd Tanous             {
114*a93e9c77SEd Tanous                 return std::nullopt;
115*a93e9c77SEd Tanous             }
116*a93e9c77SEd Tanous             out += std::chrono::seconds(ticks);
117*a93e9c77SEd Tanous             stage = ProcessingStage::Milliseconds;
118*a93e9c77SEd Tanous         }
119*a93e9c77SEd Tanous         else if (*ptr == 'S')
120*a93e9c77SEd Tanous         {
121*a93e9c77SEd Tanous             // We could be seeing seconds for the first time, (as is the case in
122*a93e9c77SEd Tanous             // 1S) or for the second time (in the case of 1.1S).
123*a93e9c77SEd Tanous             if (stage <= ProcessingStage::Seconds)
124*a93e9c77SEd Tanous             {
125*a93e9c77SEd Tanous                 out += std::chrono::seconds(ticks);
126*a93e9c77SEd Tanous                 stage = ProcessingStage::Milliseconds;
127*a93e9c77SEd Tanous             }
128*a93e9c77SEd Tanous             else if (stage > ProcessingStage::Milliseconds)
129*a93e9c77SEd Tanous             {
130*a93e9c77SEd Tanous                 BMCWEB_LOG_ERROR("Got unexpected information at end of parse");
131*a93e9c77SEd Tanous                 return std::nullopt;
132*a93e9c77SEd Tanous             }
133*a93e9c77SEd Tanous             else
134*a93e9c77SEd Tanous             {
135*a93e9c77SEd Tanous                 // Seconds could be any form of (1S, 1.1S, 1.11S, 1.111S);
136*a93e9c77SEd Tanous                 // Handle them all milliseconds are after the decimal point,
137*a93e9c77SEd Tanous                 // so they need right padded.
138*a93e9c77SEd Tanous                 if (charactersRead == 1)
139*a93e9c77SEd Tanous                 {
140*a93e9c77SEd Tanous                     ticks *= 100;
141*a93e9c77SEd Tanous                 }
142*a93e9c77SEd Tanous                 else if (charactersRead == 2)
143*a93e9c77SEd Tanous                 {
144*a93e9c77SEd Tanous                     ticks *= 10;
145*a93e9c77SEd Tanous                 }
146*a93e9c77SEd Tanous                 out += std::chrono::milliseconds(ticks);
147*a93e9c77SEd Tanous                 stage = ProcessingStage::Milliseconds;
148*a93e9c77SEd Tanous             }
149*a93e9c77SEd Tanous         }
150*a93e9c77SEd Tanous         else
151*a93e9c77SEd Tanous         {
152*a93e9c77SEd Tanous             BMCWEB_LOG_ERROR("Unknown postfix {}", *ptr);
153*a93e9c77SEd Tanous             return std::nullopt;
154*a93e9c77SEd Tanous         }
155*a93e9c77SEd Tanous 
156*a93e9c77SEd Tanous         v.remove_prefix(charactersRead + 1U);
157*a93e9c77SEd Tanous     }
158*a93e9c77SEd Tanous     return out;
159*a93e9c77SEd Tanous }
160*a93e9c77SEd Tanous 
161*a93e9c77SEd Tanous /**
162*a93e9c77SEd Tanous  * @brief Convert time value into duration format that is based on ISO 8601.
163*a93e9c77SEd Tanous  *        Example output: "P12DT1M5.5S"
164*a93e9c77SEd Tanous  *        Ref: Redfish Specification, Section 9.4.4. Duration values
165*a93e9c77SEd Tanous  */
166*a93e9c77SEd Tanous std::string toDurationString(std::chrono::milliseconds ms)
167*a93e9c77SEd Tanous {
168*a93e9c77SEd Tanous     if (ms < std::chrono::milliseconds::zero())
169*a93e9c77SEd Tanous     {
170*a93e9c77SEd Tanous         return "";
171*a93e9c77SEd Tanous     }
172*a93e9c77SEd Tanous 
173*a93e9c77SEd Tanous     std::chrono::days days = std::chrono::floor<std::chrono::days>(ms);
174*a93e9c77SEd Tanous     ms -= days;
175*a93e9c77SEd Tanous 
176*a93e9c77SEd Tanous     std::chrono::hours hours = std::chrono::floor<std::chrono::hours>(ms);
177*a93e9c77SEd Tanous     ms -= hours;
178*a93e9c77SEd Tanous 
179*a93e9c77SEd Tanous     std::chrono::minutes minutes = std::chrono::floor<std::chrono::minutes>(ms);
180*a93e9c77SEd Tanous     ms -= minutes;
181*a93e9c77SEd Tanous 
182*a93e9c77SEd Tanous     std::chrono::seconds seconds = std::chrono::floor<std::chrono::seconds>(ms);
183*a93e9c77SEd Tanous     ms -= seconds;
184*a93e9c77SEd Tanous     std::string daysStr;
185*a93e9c77SEd Tanous     if (days.count() > 0)
186*a93e9c77SEd Tanous     {
187*a93e9c77SEd Tanous         daysStr = std::format("{}D", days.count());
188*a93e9c77SEd Tanous     }
189*a93e9c77SEd Tanous     std::string hoursStr;
190*a93e9c77SEd Tanous     if (hours.count() > 0)
191*a93e9c77SEd Tanous     {
192*a93e9c77SEd Tanous         hoursStr = std::format("{}H", hours.count());
193*a93e9c77SEd Tanous     }
194*a93e9c77SEd Tanous     std::string minStr;
195*a93e9c77SEd Tanous     if (minutes.count() > 0)
196*a93e9c77SEd Tanous     {
197*a93e9c77SEd Tanous         minStr = std::format("{}M", minutes.count());
198*a93e9c77SEd Tanous     }
199*a93e9c77SEd Tanous     std::string secStr;
200*a93e9c77SEd Tanous     if (seconds.count() != 0 || ms.count() != 0)
201*a93e9c77SEd Tanous     {
202*a93e9c77SEd Tanous         secStr = std::format("{}.{:03}S", seconds.count(), ms.count());
203*a93e9c77SEd Tanous     }
204*a93e9c77SEd Tanous 
205*a93e9c77SEd Tanous     return std::format("P{}T{}{}{}", daysStr, hoursStr, minStr, secStr);
206*a93e9c77SEd Tanous }
207*a93e9c77SEd Tanous 
208*a93e9c77SEd Tanous std::optional<std::string> toDurationStringFromUint(uint64_t timeMs)
209*a93e9c77SEd Tanous {
210*a93e9c77SEd Tanous     constexpr uint64_t maxTimeMs =
211*a93e9c77SEd Tanous         static_cast<uint64_t>(std::chrono::milliseconds::max().count());
212*a93e9c77SEd Tanous 
213*a93e9c77SEd Tanous     if (maxTimeMs < timeMs)
214*a93e9c77SEd Tanous     {
215*a93e9c77SEd Tanous         return std::nullopt;
216*a93e9c77SEd Tanous     }
217*a93e9c77SEd Tanous 
218*a93e9c77SEd Tanous     std::string duration = toDurationString(std::chrono::milliseconds(timeMs));
219*a93e9c77SEd Tanous     if (duration.empty())
220*a93e9c77SEd Tanous     {
221*a93e9c77SEd Tanous         return std::nullopt;
222*a93e9c77SEd Tanous     }
223*a93e9c77SEd Tanous 
224*a93e9c77SEd Tanous     return std::make_optional(duration);
225*a93e9c77SEd Tanous }
226*a93e9c77SEd Tanous 
227*a93e9c77SEd Tanous namespace details
228*a93e9c77SEd Tanous {
229*a93e9c77SEd Tanous // This code is left for support of gcc < 13 which didn't have support for
230*a93e9c77SEd Tanous // timezones. It should be removed at some point in the future.
231*a93e9c77SEd Tanous #if __cpp_lib_chrono < 201907L
232*a93e9c77SEd Tanous 
233*a93e9c77SEd Tanous // Returns year/month/day triple in civil calendar
234*a93e9c77SEd Tanous // Preconditions:  z is number of days since 1970-01-01 and is in the range:
235*a93e9c77SEd Tanous //                   [numeric_limits<Int>::min(),
236*a93e9c77SEd Tanous //                   numeric_limits<Int>::max()-719468].
237*a93e9c77SEd Tanous // Algorithm sourced from
238*a93e9c77SEd Tanous // https://howardhinnant.github.io/date_algorithms.html#civil_from_days
239*a93e9c77SEd Tanous // All constants are explained in the above
240*a93e9c77SEd Tanous template <class IntType>
241*a93e9c77SEd Tanous constexpr std::tuple<IntType, unsigned, unsigned>
242*a93e9c77SEd Tanous     civilFromDays(IntType z) noexcept
243*a93e9c77SEd Tanous {
244*a93e9c77SEd Tanous     z += 719468;
245*a93e9c77SEd Tanous     IntType era = (z >= 0 ? z : z - 146096) / 146097;
246*a93e9c77SEd Tanous     unsigned doe = static_cast<unsigned>(z - era * 146097); // [0, 146096]
247*a93e9c77SEd Tanous     unsigned yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) /
248*a93e9c77SEd Tanous                    365; // [0, 399]
249*a93e9c77SEd Tanous     IntType y = static_cast<IntType>(yoe) + era * 400;
250*a93e9c77SEd Tanous     unsigned doy = doe - (365 * yoe + yoe / 4 - yoe / 100); // [0, 365]
251*a93e9c77SEd Tanous     unsigned mp = (5 * doy + 2) / 153; // [0, 11]
252*a93e9c77SEd Tanous     unsigned d = doy - (153 * mp + 2) / 5 + 1; // [1, 31]
253*a93e9c77SEd Tanous     unsigned m = mp < 10 ? mp + 3 : mp - 9; // [1, 12]
254*a93e9c77SEd Tanous 
255*a93e9c77SEd Tanous     return std::tuple<IntType, unsigned, unsigned>(y + (m <= 2), m, d);
256*a93e9c77SEd Tanous }
257*a93e9c77SEd Tanous 
258*a93e9c77SEd Tanous template <typename IntType, typename Period>
259*a93e9c77SEd Tanous std::string toISO8061ExtendedStr(std::chrono::duration<IntType, Period> t)
260*a93e9c77SEd Tanous {
261*a93e9c77SEd Tanous     using seconds = std::chrono::duration<int>;
262*a93e9c77SEd Tanous     using minutes = std::chrono::duration<int, std::ratio<60>>;
263*a93e9c77SEd Tanous     using hours = std::chrono::duration<int, std::ratio<3600>>;
264*a93e9c77SEd Tanous     using days = std::chrono::duration<
265*a93e9c77SEd Tanous         IntType, std::ratio_multiply<hours::period, std::ratio<24>>>;
266*a93e9c77SEd Tanous 
267*a93e9c77SEd Tanous     // d is days since 1970-01-01
268*a93e9c77SEd Tanous     days d = std::chrono::duration_cast<days>(t);
269*a93e9c77SEd Tanous 
270*a93e9c77SEd Tanous     // t is now time duration since midnight of day d
271*a93e9c77SEd Tanous     t -= d;
272*a93e9c77SEd Tanous 
273*a93e9c77SEd Tanous     // break d down into year/month/day
274*a93e9c77SEd Tanous     int year = 0;
275*a93e9c77SEd Tanous     int month = 0;
276*a93e9c77SEd Tanous     int day = 0;
277*a93e9c77SEd Tanous     std::tie(year, month, day) = details::civilFromDays(d.count());
278*a93e9c77SEd Tanous     // Check against limits.  Can't go above year 9999, and can't go below epoch
279*a93e9c77SEd Tanous     // (1970)
280*a93e9c77SEd Tanous     if (year >= 10000)
281*a93e9c77SEd Tanous     {
282*a93e9c77SEd Tanous         year = 9999;
283*a93e9c77SEd Tanous         month = 12;
284*a93e9c77SEd Tanous         day = 31;
285*a93e9c77SEd Tanous         t = days(1) - std::chrono::duration<IntType, Period>(1);
286*a93e9c77SEd Tanous     }
287*a93e9c77SEd Tanous     else if (year < 1970)
288*a93e9c77SEd Tanous     {
289*a93e9c77SEd Tanous         year = 1970;
290*a93e9c77SEd Tanous         month = 1;
291*a93e9c77SEd Tanous         day = 1;
292*a93e9c77SEd Tanous         t = std::chrono::duration<IntType, Period>::zero();
293*a93e9c77SEd Tanous     }
294*a93e9c77SEd Tanous 
295*a93e9c77SEd Tanous     hours hr = std::chrono::duration_cast<hours>(t);
296*a93e9c77SEd Tanous     t -= hr;
297*a93e9c77SEd Tanous 
298*a93e9c77SEd Tanous     minutes mt = std::chrono::duration_cast<minutes>(t);
299*a93e9c77SEd Tanous     t -= mt;
300*a93e9c77SEd Tanous 
301*a93e9c77SEd Tanous     seconds se = std::chrono::duration_cast<seconds>(t);
302*a93e9c77SEd Tanous 
303*a93e9c77SEd Tanous     t -= se;
304*a93e9c77SEd Tanous 
305*a93e9c77SEd Tanous     std::string subseconds;
306*a93e9c77SEd Tanous     if constexpr (std::is_same_v<typename decltype(t)::period, std::milli>)
307*a93e9c77SEd Tanous     {
308*a93e9c77SEd Tanous         using MilliDuration = std::chrono::duration<int, std::milli>;
309*a93e9c77SEd Tanous         MilliDuration subsec = std::chrono::duration_cast<MilliDuration>(t);
310*a93e9c77SEd Tanous         subseconds = std::format(".{:03}", subsec.count());
311*a93e9c77SEd Tanous     }
312*a93e9c77SEd Tanous     else if constexpr (std::is_same_v<typename decltype(t)::period, std::micro>)
313*a93e9c77SEd Tanous     {
314*a93e9c77SEd Tanous         using MicroDuration = std::chrono::duration<int, std::micro>;
315*a93e9c77SEd Tanous         MicroDuration subsec = std::chrono::duration_cast<MicroDuration>(t);
316*a93e9c77SEd Tanous         subseconds = std::format(".{:06}", subsec.count());
317*a93e9c77SEd Tanous     }
318*a93e9c77SEd Tanous 
319*a93e9c77SEd Tanous     return std::format("{:04}-{:02}-{:02}T{:02}:{:02}:{:02}{}+00:00", year,
320*a93e9c77SEd Tanous                        month, day, hr.count(), mt.count(), se.count(),
321*a93e9c77SEd Tanous                        subseconds);
322*a93e9c77SEd Tanous }
323*a93e9c77SEd Tanous 
324*a93e9c77SEd Tanous #else
325*a93e9c77SEd Tanous 
326*a93e9c77SEd Tanous template <typename IntType, typename Period>
327*a93e9c77SEd Tanous 
328*a93e9c77SEd Tanous std::string toISO8061ExtendedStr(std::chrono::duration<IntType, Period> dur)
329*a93e9c77SEd Tanous {
330*a93e9c77SEd Tanous     using namespace std::literals::chrono_literals;
331*a93e9c77SEd Tanous 
332*a93e9c77SEd Tanous     using SubType = std::chrono::duration<IntType, Period>;
333*a93e9c77SEd Tanous 
334*a93e9c77SEd Tanous     // d is days since 1970-01-01
335*a93e9c77SEd Tanous     std::chrono::days days = std::chrono::floor<std::chrono::days>(dur);
336*a93e9c77SEd Tanous     std::chrono::sys_days sysDays(days);
337*a93e9c77SEd Tanous     std::chrono::year_month_day ymd(sysDays);
338*a93e9c77SEd Tanous 
339*a93e9c77SEd Tanous     // Enforce 3 constraints
340*a93e9c77SEd Tanous     // the result cant under or overflow the calculation
341*a93e9c77SEd Tanous     // the resulting string needs to be representable as 4 digits
342*a93e9c77SEd Tanous     // The resulting string can't be before epoch
343*a93e9c77SEd Tanous     if (dur.count() <= 0)
344*a93e9c77SEd Tanous     {
345*a93e9c77SEd Tanous         BMCWEB_LOG_WARNING("Underflow from value {}", dur.count());
346*a93e9c77SEd Tanous         ymd = 1970y / std::chrono::January / 1d;
347*a93e9c77SEd Tanous         dur = std::chrono::duration<IntType, Period>::zero();
348*a93e9c77SEd Tanous     }
349*a93e9c77SEd Tanous     else if (dur > SubType::max() - std::chrono::days(1))
350*a93e9c77SEd Tanous     {
351*a93e9c77SEd Tanous         BMCWEB_LOG_WARNING("Overflow from value {}", dur.count());
352*a93e9c77SEd Tanous         ymd = 9999y / std::chrono::December / 31d;
353*a93e9c77SEd Tanous         dur = std::chrono::days(1) - SubType(1);
354*a93e9c77SEd Tanous     }
355*a93e9c77SEd Tanous     else if (ymd.year() >= 10000y)
356*a93e9c77SEd Tanous     {
357*a93e9c77SEd Tanous         BMCWEB_LOG_WARNING("Year {} not representable", ymd.year());
358*a93e9c77SEd Tanous         ymd = 9999y / std::chrono::December / 31d;
359*a93e9c77SEd Tanous         dur = std::chrono::days(1) - SubType(1);
360*a93e9c77SEd Tanous     }
361*a93e9c77SEd Tanous     else if (ymd.year() < 1970y)
362*a93e9c77SEd Tanous     {
363*a93e9c77SEd Tanous         BMCWEB_LOG_WARNING("Year {} not representable", ymd.year());
364*a93e9c77SEd Tanous         ymd = 1970y / std::chrono::January / 1d;
365*a93e9c77SEd Tanous         dur = SubType::zero();
366*a93e9c77SEd Tanous     }
367*a93e9c77SEd Tanous     else
368*a93e9c77SEd Tanous     {
369*a93e9c77SEd Tanous         // t is now time duration since midnight of day d
370*a93e9c77SEd Tanous         dur -= days;
371*a93e9c77SEd Tanous     }
372*a93e9c77SEd Tanous     std::chrono::hh_mm_ss<SubType> hms(dur);
373*a93e9c77SEd Tanous 
374*a93e9c77SEd Tanous     return std::format("{}T{}+00:00", ymd, hms);
375*a93e9c77SEd Tanous }
376*a93e9c77SEd Tanous 
377*a93e9c77SEd Tanous #endif
378*a93e9c77SEd Tanous } // namespace details
379*a93e9c77SEd Tanous 
380*a93e9c77SEd Tanous // Returns the formatted date time string.
381*a93e9c77SEd Tanous // Note that the maximum supported date is 9999-12-31T23:59:59+00:00, if
382*a93e9c77SEd Tanous // the given |secondsSinceEpoch| is too large, we return the maximum supported
383*a93e9c77SEd Tanous // date.
384*a93e9c77SEd Tanous std::string getDateTimeUint(uint64_t secondsSinceEpoch)
385*a93e9c77SEd Tanous {
386*a93e9c77SEd Tanous     using DurationType = std::chrono::duration<uint64_t>;
387*a93e9c77SEd Tanous     DurationType sinceEpoch(secondsSinceEpoch);
388*a93e9c77SEd Tanous     return details::toISO8061ExtendedStr(sinceEpoch);
389*a93e9c77SEd Tanous }
390*a93e9c77SEd Tanous 
391*a93e9c77SEd Tanous // Returns the formatted date time string with millisecond precision
392*a93e9c77SEd Tanous // Note that the maximum supported date is 9999-12-31T23:59:59+00:00, if
393*a93e9c77SEd Tanous // the given |secondsSinceEpoch| is too large, we return the maximum supported
394*a93e9c77SEd Tanous // date.
395*a93e9c77SEd Tanous std::string getDateTimeUintMs(uint64_t milliSecondsSinceEpoch)
396*a93e9c77SEd Tanous {
397*a93e9c77SEd Tanous     using DurationType = std::chrono::duration<uint64_t, std::milli>;
398*a93e9c77SEd Tanous     DurationType sinceEpoch(milliSecondsSinceEpoch);
399*a93e9c77SEd Tanous     return details::toISO8061ExtendedStr(sinceEpoch);
400*a93e9c77SEd Tanous }
401*a93e9c77SEd Tanous 
402*a93e9c77SEd Tanous // Returns the formatted date time string with microsecond precision
403*a93e9c77SEd Tanous std::string getDateTimeUintUs(uint64_t microSecondsSinceEpoch)
404*a93e9c77SEd Tanous {
405*a93e9c77SEd Tanous     using DurationType = std::chrono::duration<uint64_t, std::micro>;
406*a93e9c77SEd Tanous     DurationType sinceEpoch(microSecondsSinceEpoch);
407*a93e9c77SEd Tanous     return details::toISO8061ExtendedStr(sinceEpoch);
408*a93e9c77SEd Tanous }
409*a93e9c77SEd Tanous 
410*a93e9c77SEd Tanous std::string getDateTimeStdtime(std::time_t secondsSinceEpoch)
411*a93e9c77SEd Tanous {
412*a93e9c77SEd Tanous     using DurationType = std::chrono::duration<std::time_t>;
413*a93e9c77SEd Tanous     DurationType sinceEpoch(secondsSinceEpoch);
414*a93e9c77SEd Tanous     return details::toISO8061ExtendedStr(sinceEpoch);
415*a93e9c77SEd Tanous }
416*a93e9c77SEd Tanous 
417*a93e9c77SEd Tanous /**
418*a93e9c77SEd Tanous  * Returns the current Date, Time & the local Time Offset
419*a93e9c77SEd Tanous  * information in a pair
420*a93e9c77SEd Tanous  *
421*a93e9c77SEd Tanous  * @param[in] None
422*a93e9c77SEd Tanous  *
423*a93e9c77SEd Tanous  * @return std::pair<std::string, std::string>, which consist
424*a93e9c77SEd Tanous  * of current DateTime & the TimeOffset strings respectively.
425*a93e9c77SEd Tanous  */
426*a93e9c77SEd Tanous std::pair<std::string, std::string> getDateTimeOffsetNow()
427*a93e9c77SEd Tanous {
428*a93e9c77SEd Tanous     std::time_t time = std::time(nullptr);
429*a93e9c77SEd Tanous     std::string dateTime = getDateTimeStdtime(time);
430*a93e9c77SEd Tanous 
431*a93e9c77SEd Tanous     /* extract the local Time Offset value from the
432*a93e9c77SEd Tanous      * received dateTime string.
433*a93e9c77SEd Tanous      */
434*a93e9c77SEd Tanous     std::string timeOffset("Z00:00");
435*a93e9c77SEd Tanous     std::size_t lastPos = dateTime.size();
436*a93e9c77SEd Tanous     std::size_t len = timeOffset.size();
437*a93e9c77SEd Tanous     if (lastPos > len)
438*a93e9c77SEd Tanous     {
439*a93e9c77SEd Tanous         timeOffset = dateTime.substr(lastPos - len);
440*a93e9c77SEd Tanous     }
441*a93e9c77SEd Tanous 
442*a93e9c77SEd Tanous     return std::make_pair(dateTime, timeOffset);
443*a93e9c77SEd Tanous }
444*a93e9c77SEd Tanous 
445*a93e9c77SEd Tanous using usSinceEpoch = std::chrono::duration<int64_t, std::micro>;
446*a93e9c77SEd Tanous 
447*a93e9c77SEd Tanous /**
448*a93e9c77SEd Tanous  * @brief Returns the datetime in ISO 8601 format
449*a93e9c77SEd Tanous  *
450*a93e9c77SEd Tanous  * @param[in] std::string_view the date of item manufacture in ISO 8601 format,
451*a93e9c77SEd Tanous  *            either as YYYYMMDD or YYYYMMDDThhmmssZ
452*a93e9c77SEd Tanous  * Ref: https://github.com/openbmc/phosphor-dbus-interfaces/blob/master/yaml/
453*a93e9c77SEd Tanous  *      xyz/openbmc_project/Inventory/Decorator/Asset.interface.yaml#L16
454*a93e9c77SEd Tanous  *
455*a93e9c77SEd Tanous  * @return std::string which consist the datetime
456*a93e9c77SEd Tanous  */
457*a93e9c77SEd Tanous std::optional<std::string> getDateTimeIso8601(std::string_view datetime)
458*a93e9c77SEd Tanous {
459*a93e9c77SEd Tanous     std::optional<usSinceEpoch> us = dateStringToEpoch(datetime);
460*a93e9c77SEd Tanous     if (!us)
461*a93e9c77SEd Tanous     {
462*a93e9c77SEd Tanous         return std::nullopt;
463*a93e9c77SEd Tanous     }
464*a93e9c77SEd Tanous     auto secondsDuration =
465*a93e9c77SEd Tanous         std::chrono::duration_cast<std::chrono::seconds>(*us);
466*a93e9c77SEd Tanous 
467*a93e9c77SEd Tanous     return std::make_optional(
468*a93e9c77SEd Tanous         getDateTimeUint(static_cast<uint64_t>(secondsDuration.count())));
469*a93e9c77SEd Tanous }
470*a93e9c77SEd Tanous 
471*a93e9c77SEd Tanous /**
472*a93e9c77SEd Tanous  * @brief ProductionDate report
473*a93e9c77SEd Tanous  */
474*a93e9c77SEd Tanous void productionDateReport(crow::Response& res, const std::string& buildDate)
475*a93e9c77SEd Tanous {
476*a93e9c77SEd Tanous     std::optional<std::string> valueStr = getDateTimeIso8601(buildDate);
477*a93e9c77SEd Tanous     if (!valueStr)
478*a93e9c77SEd Tanous     {
479*a93e9c77SEd Tanous         messages::internalError();
480*a93e9c77SEd Tanous         return;
481*a93e9c77SEd Tanous     }
482*a93e9c77SEd Tanous     res.jsonValue["ProductionDate"] = *valueStr;
483*a93e9c77SEd Tanous }
484*a93e9c77SEd Tanous 
4851b8b02a4SEd Tanous std::optional<usSinceEpoch> dateStringToEpoch(std::string_view datetime)
4861b8b02a4SEd Tanous {
487352e3b78SHieu Huynh     for (const char* format : std::to_array(
488352e3b78SHieu Huynh              {"%FT%T%Ez", "%FT%TZ", "%FT%T", "%Y%m%d", "%Y%m%dT%H%M%SZ"}))
4891b8b02a4SEd Tanous     {
4901b8b02a4SEd Tanous         // Parse using signed so we can detect negative dates
491c51afd54SEd Tanous         std::chrono::sys_time<usSinceEpoch> date;
4921b8b02a4SEd Tanous         std::istringstream iss(std::string{datetime});
4931b8b02a4SEd Tanous #if __cpp_lib_chrono >= 201907L
4941b8b02a4SEd Tanous         namespace chrono_from_stream = std::chrono;
4951b8b02a4SEd Tanous #else
4961b8b02a4SEd Tanous         namespace chrono_from_stream = date;
4971b8b02a4SEd Tanous #endif
4981b8b02a4SEd Tanous         if (chrono_from_stream::from_stream(iss, format, date))
4991b8b02a4SEd Tanous         {
5001b8b02a4SEd Tanous             if (date.time_since_epoch().count() < 0)
5011b8b02a4SEd Tanous             {
5021b8b02a4SEd Tanous                 return std::nullopt;
5031b8b02a4SEd Tanous             }
5041b8b02a4SEd Tanous             if (iss.rdbuf()->in_avail() != 0)
5051b8b02a4SEd Tanous             {
5061b8b02a4SEd Tanous                 // More information left at end of string.
5071b8b02a4SEd Tanous                 continue;
5081b8b02a4SEd Tanous             }
509c51afd54SEd Tanous             return date.time_since_epoch();
5101b8b02a4SEd Tanous         }
5111b8b02a4SEd Tanous     }
5121b8b02a4SEd Tanous     return std::nullopt;
5131b8b02a4SEd Tanous }
5141b8b02a4SEd Tanous } // namespace redfish::time_utils
515