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