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