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