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