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