Azure C++ Utils 1.5.2+3
Azure REST API Helpers for Modern C++
C:/Users/maas/source/repos/siddiqsoft/azure-cpp-utils/src/date-utils.hpp
Go to the documentation of this file.
1/*
2 azure-cpp-utils : Azure REST API Utilities for Modern C++
3
4 BSD 3-Clause License
5
6 Copyright (c) 2021, Siddiq Software LLC
7 All rights reserved.
8
9 Redistribution and use in source and binary forms, with or without
10 modification, are permitted provided that the following conditions are met:
11
12 1. Redistributions of source code must retain the above copyright notice, this
13 list of conditions and the following disclaimer.
14
15 2. Redistributions in binary form must reproduce the above copyright notice,
16 this list of conditions and the following disclaimer in the documentation
17 and/or other materials provided with the distribution.
18
19 3. Neither the name of the copyright holder nor the names of its
20 contributors may be used to endorse or promote products derived from
21 this software without specific prior written permission.
22
23 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
24 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
26 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
27 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
29 SERVICES; LOSS OF USE, d_, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
30 CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
31 OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
32 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33 */
34
35#pragma once
36
37#ifndef DATE_UTILS_HPP
38#define DATE_UTILS_HPP
39
40
41#include <iostream>
42#include <chrono>
43#include <string>
44#include <concepts>
45#include <format>
46
47
49namespace siddiqsoft
50{
51#if !defined(_NORW)
54 template <typename _NorWT>
55 requires std::same_as<_NorWT, char> || std::same_as<_NorWT, wchar_t>
56 [[nodiscard]] constexpr const _NorWT* NorW_1(const char* const _Str, const wchar_t* const _WStr) noexcept
57 {
58 if constexpr (std::is_same_v<_NorWT, char>) {
59 return _Str;
60 }
61 else {
62 return _WStr;
63 }
64 }
65#define _NORW(_NorWT, _Literal) NorW_1<_NorWT>(_Literal, L##_Literal)
66#endif
67
69 struct DateUtils
70 {
76 template <typename T = char>
77 requires std::same_as<T, char> || std::same_as<T, wchar_t>
78 static std::basic_string<T> ISO8601(const std::chrono::system_clock::time_point& rawtp = std::chrono::system_clock::now())
79 {
80 const auto rawtime = std::chrono::system_clock::to_time_t(rawtp);
81 tm timeInfo {};
82 // We need to get the fractional milliseconds from the raw time point.
83 auto msTime = std::chrono::duration_cast<std::chrono::milliseconds>(rawtp.time_since_epoch()).count() % 1000;
84 // Get the UTC time packet.
85 const auto ec = gmtime_s(&timeInfo, &rawtime);
86
87 if constexpr (std::is_same_v<T, char>) {
88 // https://en.wikipedia.org/wiki/ISO_8601
89 // yyyy-mm-ddThh:mm:ss.mmmZ
90 std::vector<char> buff(32, 0);
91
92 if (ec != EINVAL) strftime(buff.data(), buff.capacity(), "%FT%T", &timeInfo);
93 return std::format("{}.{:03}Z", buff.data(), msTime);
94 }
95 else if constexpr (std::is_same_v<T, wchar_t>) {
96 // yyyy-mm-ddThh:mm:ss.mmmZ
97 std::vector<wchar_t> buff(32, 0);
98 if (ec != EINVAL) wcsftime(buff.data(), buff.capacity(), L"%FT%T", &timeInfo);
99 return std::format(L"{}.{:03}Z", buff.data(), msTime);
100 }
101
102 return {};
103 }
104
105
110 template <typename T = char>
111 requires std::same_as<T, char> || std::same_as<T, wchar_t>
112 static std::basic_string<T> RFC7231(const std::chrono::system_clock::time_point& rawtp = std::chrono::system_clock::now())
113 {
114 auto rawtime = std::chrono::system_clock::to_time_t(rawtp);
115 tm timeInfo {};
116
117 // Get the UTC time packet.
118 auto ec = gmtime_s(&timeInfo, &rawtime);
119
120 // HTTP-date as per RFC 7231: Tue, 01 Nov 1994 08:12:31 GMT
121 // Note that since we are getting the UTC time we should not use the %z or %Z in the strftime format
122 // as it returns the local timezone and not GMT.
123 if constexpr (std::is_same_v<T, char>) {
124 std::vector<char> buff(32, 0);
125 if (ec != EINVAL) strftime(buff.data(), buff.capacity(), "%a, %d %h %Y %T GMT", &timeInfo);
126
127 return buff.data();
128 }
129
130 if constexpr (std::is_same_v<T, wchar_t>) {
131 std::vector<wchar_t> buff(32, 0);
132 if (ec != EINVAL) wcsftime(buff.data(), buff.capacity(), L"%a, %d %h %Y %T GMT", &timeInfo);
133
134 return buff.data();
135 }
136 }
137
138
143 template <typename T = char>
144 requires std::same_as<T, char> || std::same_as<T, wchar_t>
145 static std::basic_string<T> toTimespan(const std::chrono::seconds& arg)
146 {
147 auto asSeconds = arg.count();
148 // https://www.epochconverter.com/
149 // Human-readable time Seconds
150 // 1 hour 3600 seconds
151 // 1 day 86400 seconds
152 // 1 week 604800 seconds
153 // 1 month (30.44 days) 2629743 seconds
154 // 1 year (365.24 days) 31556926 seconds
155 auto hours = (asSeconds / 3600) % 24;
156 auto days = (asSeconds / 86400);
157 auto months = (asSeconds / 2629743);
158 auto years = asSeconds / 31556926;
159 auto weeks = (asSeconds / 604800);
160 auto minutes = (asSeconds / 60) % 60;
161 auto seconds = asSeconds % 60;
162
163 if constexpr (std::is_same_v<T, char>)
164 return std::format("{}.{:02}:{:02}:{:02}", days, hours, minutes, seconds);
165 else if constexpr (std::is_same_v<T, wchar_t>)
166 return std::format(L"{}.{:02}:{:02}:{:02}", days, hours, minutes, seconds);
167 }
168
169
175 template <class T = std::string>
176 requires std::same_as<T, std::string> || std::same_as<T, std::wstring> || std::same_as<T, uint64_t>
177 static std::chrono::system_clock::time_point parseEpoch(const T& arg)
178 {
179 tm epoch1tm {};
180 uint64_t epoch1ntp {0};
181 uint64_t epoch1millis {0};
182 __time64_t epoch1 {0};
183 std::chrono::system_clock::time_point ret_tp {};
184
185 // Convert the argument to unsigned long; this will drop the decimal portion if persent
186 // and yields the number of seconds since epoch.
187 if constexpr (std::is_same_v<T, uint64_t>)
188 epoch1ntp = arg;
189 else if constexpr (std::is_same_v<T, std::string>) {
190 epoch1ntp = std::stoull(arg.data());
191 // Check if we have high-resolution part.
192 if (auto locMilli = arg.find("."); locMilli != std::string::npos) {
193 epoch1millis = std::stoull(arg.substr(locMilli + 1).data());
194 epoch1millis *= 1000000; // offset to allow us to avoid the decimals
195 epoch1millis >>= 32; // divide by 2^32 => milliseconds.
196 }
197 }
198 else if constexpr (std::is_same_v<T, std::wstring>) {
199 epoch1ntp = std::stoull(arg.data());
200 // Check if we have high-resolution part.
201 if (auto locMilli = arg.find(L"."); locMilli != std::string::npos) {
202 epoch1millis = std::stoull(arg.substr(locMilli + 1).data());
203 epoch1millis *= 1000000; // offset to allow us to avoid the decimals
204 epoch1millis >>= 32; // divide by 2^32 => milliseconds.
205 }
206 }
207
208 // Guard against empty argument
209 if (epoch1ntp > 0) {
210 // Just in case, we should handle the NTP and the epoch case..
211 // The EPOC time is from Jan 1 1970 whereas NTP starts from 1/1/1900 which necessitates this subtraction
212 epoch1 = (epoch1ntp > 2208988800ULL) ? epoch1ntp - 2208988800ULL : epoch1ntp;
213
214 // Convert to tm structure
215 _gmtime64_s(&epoch1tm, &epoch1);
216
217 // Create the timepoint
218 ret_tp = std::chrono::system_clock::from_time_t(epoch1);
219 // Add the milliseconds
220 ret_tp += std::chrono::milliseconds(epoch1millis);
221 }
222
223 return ret_tp;
224 }
225
226
233 template <typename T = char>
234 requires std::same_as<T, char> || std::same_as<T, wchar_t>
235 static std::tuple<std::chrono::milliseconds, std::basic_string<T>>
236 diff(const std::chrono::time_point<std::chrono::system_clock>& end,
237 const std::chrono::time_point<std::chrono::system_clock>& start)
238 {
239 using namespace std;
240
241 auto delta = end - start;
242 uint64_t uptimeMilliseconds = chrono::duration_cast<chrono::milliseconds>(delta).count();
243 uint64_t uptimeSeconds = chrono::duration_cast<chrono::seconds>(delta).count();
244 uint64_t uptimeMinutes = chrono::duration_cast<chrono::minutes>(delta).count();
245 uint64_t uptimeHours = chrono::duration_cast<chrono::hours>(delta).count();
246
247 // Account for hours
248 uptimeMinutes = uptimeMinutes % 60i64;
249 // Account for hours and minutes
250 uptimeSeconds = (uptimeSeconds - (uptimeMinutes * 60i64)) % 60i64;
251 // Account for hours, minutes and seconds
252 uptimeMilliseconds = (uptimeMilliseconds) % 1000i64;
253
254 // Clients would find the following useful:
255 // {milliseconds, "HH:MM:SS.mmm"}
256 if constexpr (std::is_same_v<T, char>)
257 return {std::chrono::duration_cast<std::chrono::milliseconds>(delta),
258 std::format("{:02}:{:02}:{:02}.{:03}", uptimeHours, uptimeMinutes, uptimeSeconds, uptimeMilliseconds)};
259 else if constexpr (std::is_same_v<T, wchar_t>)
260 return {std::chrono::duration_cast<std::chrono::milliseconds>(delta),
261 std::format(L"{:02}:{:02}:{:02}.{:03}", uptimeHours, uptimeMinutes, uptimeSeconds, uptimeMilliseconds)};
262 }
263
264
277 template <typename T = char, typename D = std::chrono::microseconds>
278 requires std::same_as<T, char> || std::same_as<T, wchar_t>
279 static std::basic_string<T> durationString(const D& arg)
280 {
281 using namespace std;
282
283 std::chrono::days days(std::chrono::duration_cast<std::chrono::days>(arg));
284 std::chrono::months months(std::chrono::duration_cast<std::chrono::months>(arg));
285 std::chrono::years years(std::chrono::duration_cast<std::chrono::years>(arg));
286 std::chrono::weeks weeks(std::chrono::duration_cast<std::chrono::weeks>(arg));
287
288 // We want the "remainder" hours, minutes
289 std::chrono::hours hours(std::chrono::duration_cast<std::chrono::hours>(arg));
290 std::chrono::minutes minutes((std::chrono::duration_cast<std::chrono::minutes>(arg) / 60s));
291 std::chrono::seconds seconds(std::chrono::duration_cast<std::chrono::seconds>(arg) % 60s);
292 std::chrono::milliseconds millis(std::chrono::duration_cast<std::chrono::milliseconds>(arg) % 1000ms);
293 hours %= std::chrono::days(1);
294 minutes %= std::chrono::hours(1);
295 days -= std::chrono::duration_cast<std::chrono::days>(weeks);
296
297 if (years > std::chrono::years(0)) {
298 // Round "up" the seconds if we have excess milliseconds
299 if (millis > 500ms) seconds += 1s;
300 return std::format(_NORW(T, "{}years / {}months / {}weeks {} {} {} {}"),
301 years.count(),
302 months.count(),
303 weeks.count(),
304 days,
305 hours,
306 minutes,
307 seconds);
308 }
309 else if (months > std::chrono::months(0)) {
310 // Round "up" the seconds if we have excess milliseconds
311 if (millis > 500ms) seconds += 1s;
312 return std::format(
313 _NORW(T, "{}months / {}weeks {} {} {} {}"), months.count(), weeks.count(), days, hours, minutes, seconds);
314 }
315 else if (weeks > std::chrono::weeks(0)) {
316 // Round "up" the seconds if we have excess milliseconds
317 if (millis > 500ms) seconds += 1s;
318 return std::format(_NORW(T, "{}weeks {} {} {} {}"), weeks.count(), days, hours, minutes, seconds);
319 }
320 else if (days > std::chrono::days(0)) {
321 // Round "up" the seconds if we have excess milliseconds
322 if (millis > 500ms) seconds += 1s;
323 return std::format(_NORW(T, "{} {} {} {}"), days, hours, minutes, seconds);
324 }
325 else if (hours > std::chrono::hours(0)) {
326 // Round "up" the seconds if we have excess milliseconds
327 if (millis > 500ms) seconds += 1s;
328 return std::format(_NORW(T, "{} {} {}"), hours, minutes, seconds);
329 }
330 else if (millis > std::chrono::milliseconds(0)) {
331 return std::format(_NORW(T, "{} {} {}"), minutes, seconds, millis);
332 }
333 else {
334 return std::format(_NORW(T, "{} {}"), minutes, seconds);
335 }
336 }
337
338
343 template <class T = char>
344 requires std::same_as<T, char> || std::same_as<T, wchar_t>
345 static std::chrono::system_clock::time_point parseISO8601(const std::basic_string<T>& arg)
346 {
347 uint32_t yearPart = 0, monthPart = 0, dayPart = 0, hourPart = 0, minutePart = 0, secondPart = 0, millisecondPart = 0;
348
349 if constexpr (std::is_same_v<T, char>) {
350 sscanf_s(arg.data(),
351 "%d-%d-%dT%d:%d:%d.%ldZ",
352 &yearPart,
353 &monthPart,
354 &dayPart,
355 &hourPart,
356 &minutePart,
357 &secondPart,
358 &millisecondPart);
359 }
360 else if constexpr (std::is_same_v<T, wchar_t>) {
361 swscanf_s(arg.data(),
362 L"%d-%d-%dT%d:%d:%d.%ldZ",
363 &yearPart,
364 &monthPart,
365 &dayPart,
366 &hourPart,
367 &minutePart,
368 &secondPart,
369 &millisecondPart);
370 }
371 else {
372 throw std::invalid_argument("Type is not supported; must be std::string[_view] or std::wstring[_view]");
373 }
374
375 if (yearPart > 0 && monthPart > 0 && dayPart > 0) {
376 tm retTime;
377 retTime.tm_year = yearPart - 1900; // Year since 1900
378 retTime.tm_mon = monthPart - 1; // 0-11
379 retTime.tm_mday = dayPart; // 1-31
380 retTime.tm_hour = hourPart; // 0-23
381 retTime.tm_min = minutePart; // 0-59
382 retTime.tm_sec = (int)secondPart; // 0-61 (0-60 in C++11)
383
384 auto tp = std::chrono::system_clock::from_time_t(_mkgmtime(&retTime));
385 tp += std::chrono::milliseconds(millisecondPart);
386 return tp;
387 }
388
389 return {};
390 }
391 };
392} // namespace siddiqsoft
393
394#endif // !AZURECPPUTILS_HPP
#define _NORW(_NorWT, _Literal)
Definition: date-utils.hpp:65
SiddiqSoft.
constexpr const _NorWT * NorW_1(const char *const _Str, const wchar_t *const _WStr) noexcept
In support of the macro NORW which allows us to declare/use narrow/wide strings as needed....
Definition: date-utils.hpp:56
Date Time utilities for REST API.
Definition: date-utils.hpp:70
static std::basic_string< T > durationString(const D &arg)
Return string with weeks days hours minutes seconds for the provided duration (default microseconds)
Definition: date-utils.hpp:279
static std::basic_string< T > ISO8601(const std::chrono::system_clock::time_point &rawtp=std::chrono::system_clock::now())
Converts the argument to ISO8601 format.
Definition: date-utils.hpp:78
static std::basic_string< T > RFC7231(const std::chrono::system_clock::time_point &rawtp=std::chrono::system_clock::now())
Build a time and date string compliant with the RFC7231.
Definition: date-utils.hpp:112
static std::chrono::system_clock::time_point parseEpoch(const T &arg)
Converts the epoch time into a time_point. The reason for string is due to the permissibility of epoc...
Definition: date-utils.hpp:177
static std::tuple< std::chrono::milliseconds, std::basic_string< T > > diff(const std::chrono::time_point< std::chrono::system_clock > &end, const std::chrono::time_point< std::chrono::system_clock > &start)
Returns a tuple where the first is the chrono::duration<Z> and the second is std::basic_string<T> as.
Definition: date-utils.hpp:236
static std::basic_string< T > toTimespan(const std::chrono::seconds &arg)
Returns D.HH:MM:SS ; days.hours:minutes:seconds.
Definition: date-utils.hpp:145
static std::chrono::system_clock::time_point parseISO8601(const std::basic_string< T > &arg)
Converts from ISO8601 format string into time_point.
Definition: date-utils.hpp:345