1 #pragma once
2 
3 #include "logging.hpp"
4 
5 #include <algorithm>
6 #include <charconv>
7 #include <chrono>
8 #include <cstddef>
9 #include <cstdint>
10 #include <optional>
11 #include <ratio>
12 #include <string>
13 #include <string_view>
14 
15 // IWYU pragma: no_include <stddef.h>
16 // IWYU pragma: no_include <stdint.h>
17 
18 namespace redfish
19 {
20 
21 namespace time_utils
22 {
23 
24 namespace details
25 {
26 
27 // Creates a string from an integer in the most efficient way possible without
28 // using std::locale.  Adds an exact zero pad based on the pad input parameter.
29 // Does not handle negative numbers.
30 inline std::string padZeros(int64_t value, size_t pad)
31 {
32     std::string result(pad, '0');
33     for (int64_t val = value; pad > 0; pad--)
34     {
35         result[pad - 1] = static_cast<char>('0' + val % 10);
36         val /= 10;
37     }
38     return result;
39 }
40 
41 } // namespace details
42 
43 /**
44  * @brief Convert string that represents value in Duration Format to its numeric
45  *        equivalent.
46  */
47 inline std::optional<std::chrono::milliseconds>
48     fromDurationString(std::string_view v)
49 {
50     std::chrono::milliseconds out = std::chrono::milliseconds::zero();
51     enum class ProcessingStage
52     {
53         // P1DT1H1M1.100S
54         P,
55         Days,
56         Hours,
57         Minutes,
58         Seconds,
59         Milliseconds,
60         Done,
61     };
62     ProcessingStage stage = ProcessingStage::P;
63 
64     while (!v.empty())
65     {
66         if (stage == ProcessingStage::P)
67         {
68             if (v.front() != 'P')
69             {
70                 return std::nullopt;
71             }
72             v.remove_prefix(1);
73             stage = ProcessingStage::Days;
74             continue;
75         }
76         if (stage == ProcessingStage::Days)
77         {
78             if (v.front() == 'T')
79             {
80                 v.remove_prefix(1);
81                 stage = ProcessingStage::Hours;
82                 continue;
83             }
84         }
85         uint64_t ticks = 0;
86         auto [ptr, ec] = std::from_chars(v.begin(), v.end(), ticks);
87         if (ec != std::errc())
88         {
89             BMCWEB_LOG_ERROR("Failed to convert string \"{}\" to decimal", v);
90             return std::nullopt;
91         }
92         size_t charactersRead = static_cast<size_t>(ptr - v.data());
93         if (ptr >= v.end())
94         {
95             BMCWEB_LOG_ERROR("Missing postfix");
96             return std::nullopt;
97         }
98         if (*ptr == 'D')
99         {
100             if (stage > ProcessingStage::Days)
101             {
102                 return std::nullopt;
103             }
104             out += std::chrono::days(ticks);
105         }
106         else if (*ptr == 'H')
107         {
108             if (stage > ProcessingStage::Hours)
109             {
110                 return std::nullopt;
111             }
112             out += std::chrono::hours(ticks);
113         }
114         else if (*ptr == 'M')
115         {
116             if (stage > ProcessingStage::Minutes)
117             {
118                 return std::nullopt;
119             }
120             out += std::chrono::minutes(ticks);
121         }
122         else if (*ptr == '.')
123         {
124             if (stage > ProcessingStage::Seconds)
125             {
126                 return std::nullopt;
127             }
128             out += std::chrono::seconds(ticks);
129             stage = ProcessingStage::Milliseconds;
130         }
131         else if (*ptr == 'S')
132         {
133             // We could be seeing seconds for the first time, (as is the case in
134             // 1S) or for the second time (in the case of 1.1S).
135             if (stage <= ProcessingStage::Seconds)
136             {
137                 out += std::chrono::seconds(ticks);
138                 stage = ProcessingStage::Milliseconds;
139             }
140             else if (stage > ProcessingStage::Milliseconds)
141             {
142                 BMCWEB_LOG_ERROR("Got unexpected information at end of parse");
143                 return std::nullopt;
144             }
145             else
146             {
147                 // Seconds could be any form of (1S, 1.1S, 1.11S, 1.111S);
148                 // Handle them all milliseconds are after the decimal point,
149                 // so they need right padded.
150                 if (charactersRead == 1)
151                 {
152                     ticks *= 100;
153                 }
154                 else if (charactersRead == 2)
155                 {
156                     ticks *= 10;
157                 }
158                 out += std::chrono::milliseconds(ticks);
159                 stage = ProcessingStage::Milliseconds;
160             }
161         }
162         else
163         {
164             BMCWEB_LOG_ERROR("Unknown postfix {}", *ptr);
165             return std::nullopt;
166         }
167 
168         v.remove_prefix(charactersRead + 1U);
169     }
170     return out;
171 }
172 
173 /**
174  * @brief Convert time value into duration format that is based on ISO 8601.
175  *        Example output: "P12DT1M5.5S"
176  *        Ref: Redfish Specification, Section 9.4.4. Duration values
177  */
178 inline std::string toDurationString(std::chrono::milliseconds ms)
179 {
180     if (ms < std::chrono::milliseconds::zero())
181     {
182         return "";
183     }
184 
185     std::string fmt;
186     fmt.reserve(sizeof("PxxxxxxxxxxxxDTxxHxxMxx.xxxxxxS"));
187 
188     std::chrono::days days = std::chrono::floor<std::chrono::days>(ms);
189     ms -= days;
190 
191     std::chrono::hours hours = std::chrono::floor<std::chrono::hours>(ms);
192     ms -= hours;
193 
194     std::chrono::minutes minutes = std::chrono::floor<std::chrono::minutes>(ms);
195     ms -= minutes;
196 
197     std::chrono::seconds seconds = std::chrono::floor<std::chrono::seconds>(ms);
198     ms -= seconds;
199 
200     fmt = "P";
201     if (days.count() > 0)
202     {
203         fmt += std::to_string(days.count()) + "D";
204     }
205     fmt += "T";
206     if (hours.count() > 0)
207     {
208         fmt += std::to_string(hours.count()) + "H";
209     }
210     if (minutes.count() > 0)
211     {
212         fmt += std::to_string(minutes.count()) + "M";
213     }
214     if (seconds.count() != 0 || ms.count() != 0)
215     {
216         fmt += std::to_string(seconds.count()) + ".";
217         fmt += details::padZeros(ms.count(), 3);
218         fmt += "S";
219     }
220 
221     return fmt;
222 }
223 
224 inline std::optional<std::string>
225     toDurationStringFromUint(const uint64_t timeMs)
226 {
227     static const uint64_t maxTimeMs =
228         static_cast<uint64_t>(std::chrono::milliseconds::max().count());
229 
230     if (maxTimeMs < timeMs)
231     {
232         return std::nullopt;
233     }
234 
235     std::string duration = toDurationString(std::chrono::milliseconds(timeMs));
236     if (duration.empty())
237     {
238         return std::nullopt;
239     }
240 
241     return std::make_optional(duration);
242 }
243 
244 namespace details
245 {
246 // Returns year/month/day triple in civil calendar
247 // Preconditions:  z is number of days since 1970-01-01 and is in the range:
248 //                   [numeric_limits<Int>::min(),
249 //                   numeric_limits<Int>::max()-719468].
250 // Algorithm sourced from
251 // https://howardhinnant.github.io/date_algorithms.html#civil_from_days
252 // All constants are explained in the above
253 template <class IntType>
254 constexpr std::tuple<IntType, unsigned, unsigned>
255     civilFromDays(IntType z) noexcept
256 {
257     z += 719468;
258     IntType era = (z >= 0 ? z : z - 146096) / 146097;
259     unsigned doe = static_cast<unsigned>(z - era * 146097); // [0, 146096]
260     unsigned yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) /
261                    365;                                     // [0, 399]
262     IntType y = static_cast<IntType>(yoe) + era * 400;
263     unsigned doy = doe - (365 * yoe + yoe / 4 - yoe / 100); // [0, 365]
264     unsigned mp = (5 * doy + 2) / 153;                      // [0, 11]
265     unsigned d = doy - (153 * mp + 2) / 5 + 1;              // [1, 31]
266     unsigned m = mp < 10 ? mp + 3 : mp - 9;                 // [1, 12]
267 
268     return std::tuple<IntType, unsigned, unsigned>(y + (m <= 2), m, d);
269 }
270 
271 template <typename IntType, typename Period>
272 std::string toISO8061ExtendedStr(std::chrono::duration<IntType, Period> t)
273 {
274     using seconds = std::chrono::duration<int>;
275     using minutes = std::chrono::duration<int, std::ratio<60>>;
276     using hours = std::chrono::duration<int, std::ratio<3600>>;
277     using days = std::chrono::duration<
278         IntType, std::ratio_multiply<hours::period, std::ratio<24>>>;
279 
280     // d is days since 1970-01-01
281     days d = std::chrono::duration_cast<days>(t);
282 
283     // t is now time duration since midnight of day d
284     t -= d;
285 
286     // break d down into year/month/day
287     int year = 0;
288     int month = 0;
289     int day = 0;
290     std::tie(year, month, day) = details::civilFromDays(d.count());
291     // Check against limits.  Can't go above year 9999, and can't go below epoch
292     // (1970)
293     if (year >= 10000)
294     {
295         year = 9999;
296         month = 12;
297         day = 31;
298         t = days(1) - std::chrono::duration<IntType, Period>(1);
299     }
300     else if (year < 1970)
301     {
302         year = 1970;
303         month = 1;
304         day = 1;
305         t = std::chrono::duration<IntType, Period>::zero();
306     }
307 
308     std::string out;
309     out += details::padZeros(year, 4);
310     out += '-';
311     out += details::padZeros(month, 2);
312     out += '-';
313     out += details::padZeros(day, 2);
314     out += 'T';
315     hours hr = duration_cast<hours>(t);
316     out += details::padZeros(hr.count(), 2);
317     t -= hr;
318     out += ':';
319 
320     minutes mt = duration_cast<minutes>(t);
321     out += details::padZeros(mt.count(), 2);
322     t -= mt;
323     out += ':';
324 
325     seconds se = duration_cast<seconds>(t);
326     out += details::padZeros(se.count(), 2);
327     t -= se;
328 
329     if constexpr (std::is_same_v<typename decltype(t)::period, std::milli>)
330     {
331         out += '.';
332         using MilliDuration = std::chrono::duration<int, std::milli>;
333         MilliDuration subsec = duration_cast<MilliDuration>(t);
334         out += details::padZeros(subsec.count(), 3);
335     }
336     else if constexpr (std::is_same_v<typename decltype(t)::period, std::micro>)
337     {
338         out += '.';
339 
340         using MicroDuration = std::chrono::duration<int, std::micro>;
341         MicroDuration subsec = duration_cast<MicroDuration>(t);
342         out += details::padZeros(subsec.count(), 6);
343     }
344 
345     out += "+00:00";
346     return out;
347 }
348 } // namespace details
349 
350 // Returns the formatted date time string.
351 // Note that the maximum supported date is 9999-12-31T23:59:59+00:00, if
352 // the given |secondsSinceEpoch| is too large, we return the maximum supported
353 // date.
354 inline std::string getDateTimeUint(uint64_t secondsSinceEpoch)
355 {
356     using DurationType = std::chrono::duration<uint64_t>;
357     DurationType sinceEpoch(secondsSinceEpoch);
358     return details::toISO8061ExtendedStr(sinceEpoch);
359 }
360 
361 // Returns the formatted date time string with millisecond precision
362 // Note that the maximum supported date is 9999-12-31T23:59:59+00:00, if
363 // the given |secondsSinceEpoch| is too large, we return the maximum supported
364 // date.
365 inline std::string getDateTimeUintMs(uint64_t milliSecondsSinceEpoch)
366 {
367     using DurationType = std::chrono::duration<uint64_t, std::milli>;
368     DurationType sinceEpoch(milliSecondsSinceEpoch);
369     return details::toISO8061ExtendedStr(sinceEpoch);
370 }
371 
372 // Returns the formatted date time string with microsecond precision
373 inline std::string getDateTimeUintUs(uint64_t microSecondsSinceEpoch)
374 {
375     using DurationType = std::chrono::duration<uint64_t, std::micro>;
376     DurationType sinceEpoch(microSecondsSinceEpoch);
377     return details::toISO8061ExtendedStr(sinceEpoch);
378 }
379 
380 inline std::string getDateTimeStdtime(std::time_t secondsSinceEpoch)
381 {
382     using DurationType = std::chrono::duration<std::time_t>;
383     DurationType sinceEpoch(secondsSinceEpoch);
384     return details::toISO8061ExtendedStr(sinceEpoch);
385 }
386 
387 /**
388  * Returns the current Date, Time & the local Time Offset
389  * information in a pair
390  *
391  * @param[in] None
392  *
393  * @return std::pair<std::string, std::string>, which consist
394  * of current DateTime & the TimeOffset strings respectively.
395  */
396 inline std::pair<std::string, std::string> getDateTimeOffsetNow()
397 {
398     std::time_t time = std::time(nullptr);
399     std::string dateTime = getDateTimeStdtime(time);
400 
401     /* extract the local Time Offset value from the
402      * received dateTime string.
403      */
404     std::string timeOffset("Z00:00");
405     std::size_t lastPos = dateTime.size();
406     std::size_t len = timeOffset.size();
407     if (lastPos > len)
408     {
409         timeOffset = dateTime.substr(lastPos - len);
410     }
411 
412     return std::make_pair(dateTime, timeOffset);
413 }
414 
415 using usSinceEpoch = std::chrono::duration<int64_t, std::micro>;
416 std::optional<usSinceEpoch> dateStringToEpoch(std::string_view datetime);
417 } // namespace time_utils
418 } // namespace redfish
419