1 /***
2 * Copyright (C) Microsoft. All rights reserved.
3 * Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
4 *
5 * =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
6 *
7 * Utilities
8 *
9 * For the latest on this and related APIs, please see: https://github.com/Microsoft/cpprestsdk
10 *
11 * =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
12 ****/
13
14 #include "stdafx.h"
15
16 #include <algorithm>
17 #include <cpprest/asyncrt_utils.h>
18 #include <stdexcept>
19 #include <string>
20 #include <time.h>
21
22 using namespace web;
23 using namespace utility;
24 using namespace utility::conversions;
25
26 namespace
27 {
28 struct to_lower_ch_impl
29 {
operator ()__anonefd0a8270111::to_lower_ch_impl30 char operator()(char c) const CPPREST_NOEXCEPT
31 {
32 if (c >= 'A' && c <= 'Z') return static_cast<char>(c - 'A' + 'a');
33 return c;
34 }
35
operator ()__anonefd0a8270111::to_lower_ch_impl36 wchar_t operator()(wchar_t c) const CPPREST_NOEXCEPT
37 {
38 if (c >= L'A' && c <= L'Z') return static_cast<wchar_t>(c - L'A' + L'a');
39 return c;
40 }
41 };
42
43 CPPREST_CONSTEXPR to_lower_ch_impl to_lower_ch {};
44
45 struct eq_lower_ch_impl
46 {
47 template<class CharT>
operator ()__anonefd0a8270111::eq_lower_ch_impl48 inline CharT operator()(const CharT left, const CharT right) const CPPREST_NOEXCEPT
49 {
50 return to_lower_ch(left) == to_lower_ch(right);
51 }
52 };
53
54 CPPREST_CONSTEXPR eq_lower_ch_impl eq_lower_ch {};
55
56 struct lt_lower_ch_impl
57 {
58 template<class CharT>
operator ()__anonefd0a8270111::lt_lower_ch_impl59 inline CharT operator()(const CharT left, const CharT right) const CPPREST_NOEXCEPT
60 {
61 return to_lower_ch(left) < to_lower_ch(right);
62 }
63 };
64
65 CPPREST_CONSTEXPR lt_lower_ch_impl lt_lower_ch {};
66 } // namespace
67
68 namespace utility
69 {
70 namespace details
71 {
str_iequal(const std::string & left,const std::string & right)72 _ASYNCRTIMP bool __cdecl str_iequal(const std::string& left, const std::string& right) CPPREST_NOEXCEPT
73 {
74 return left.size() == right.size() && std::equal(left.cbegin(), left.cend(), right.cbegin(), eq_lower_ch);
75 }
76
str_iequal(const std::wstring & left,const std::wstring & right)77 _ASYNCRTIMP bool __cdecl str_iequal(const std::wstring& left, const std::wstring& right) CPPREST_NOEXCEPT
78 {
79 return left.size() == right.size() && std::equal(left.cbegin(), left.cend(), right.cbegin(), eq_lower_ch);
80 }
81
str_iless(const std::string & left,const std::string & right)82 _ASYNCRTIMP bool __cdecl str_iless(const std::string& left, const std::string& right) CPPREST_NOEXCEPT
83 {
84 return std::lexicographical_compare(left.cbegin(), left.cend(), right.cbegin(), right.cend(), lt_lower_ch);
85 }
86
str_iless(const std::wstring & left,const std::wstring & right)87 _ASYNCRTIMP bool __cdecl str_iless(const std::wstring& left, const std::wstring& right) CPPREST_NOEXCEPT
88 {
89 return std::lexicographical_compare(left.cbegin(), left.cend(), right.cbegin(), right.cend(), lt_lower_ch);
90 }
91
inplace_tolower(std::string & target)92 _ASYNCRTIMP void __cdecl inplace_tolower(std::string& target) CPPREST_NOEXCEPT
93 {
94 for (auto& ch : target)
95 {
96 ch = to_lower_ch(ch);
97 }
98 }
99
inplace_tolower(std::wstring & target)100 _ASYNCRTIMP void __cdecl inplace_tolower(std::wstring& target) CPPREST_NOEXCEPT
101 {
102 for (auto& ch : target)
103 {
104 ch = to_lower_ch(ch);
105 }
106 }
107
108 #if !defined(ANDROID) && !defined(__ANDROID__)
109 std::once_flag g_c_localeFlag;
110 std::unique_ptr<scoped_c_thread_locale::xplat_locale, void (*)(scoped_c_thread_locale::xplat_locale*)> g_c_locale(
__anonefd0a8270202(scoped_c_thread_locale::xplat_locale*) 111 nullptr, [](scoped_c_thread_locale::xplat_locale*) {});
c_locale()112 scoped_c_thread_locale::xplat_locale scoped_c_thread_locale::c_locale()
113 {
114 std::call_once(g_c_localeFlag, [&]() {
115 scoped_c_thread_locale::xplat_locale* clocale = new scoped_c_thread_locale::xplat_locale();
116 #ifdef _WIN32
117 *clocale = _create_locale(LC_ALL, "C");
118 if (clocale == nullptr || *clocale == nullptr)
119 {
120 throw std::runtime_error("Unable to create 'C' locale.");
121 }
122 auto deleter = [](scoped_c_thread_locale::xplat_locale* clocale) {
123 _free_locale(*clocale);
124 delete clocale;
125 };
126 #else
127 *clocale = newlocale(LC_ALL_MASK, "C", nullptr);
128 if (clocale == nullptr || *clocale == nullptr)
129 {
130 throw std::runtime_error("Unable to create 'C' locale.");
131 }
132 auto deleter = [](scoped_c_thread_locale::xplat_locale *clocale)
133 {
134 freelocale(*clocale);
135 delete clocale;
136 };
137 #endif
138 g_c_locale =
139 std::unique_ptr<scoped_c_thread_locale::xplat_locale, void (*)(scoped_c_thread_locale::xplat_locale*)>(
140 clocale, deleter);
141 });
142 return *g_c_locale;
143 }
144 #endif
145
146 #ifdef _WIN32
scoped_c_thread_locale()147 scoped_c_thread_locale::scoped_c_thread_locale() : m_prevLocale(), m_prevThreadSetting(-1)
148 {
149 char* prevLocale = setlocale(LC_ALL, nullptr);
150 if (prevLocale == nullptr)
151 {
152 throw std::runtime_error("Unable to retrieve current locale.");
153 }
154
155 if (std::strcmp(prevLocale, "C") != 0)
156 {
157 m_prevLocale = prevLocale;
158 m_prevThreadSetting = _configthreadlocale(_ENABLE_PER_THREAD_LOCALE);
159 if (m_prevThreadSetting == -1)
160 {
161 throw std::runtime_error("Unable to enable per thread locale.");
162 }
163 if (setlocale(LC_ALL, "C") == nullptr)
164 {
165 _configthreadlocale(m_prevThreadSetting);
166 throw std::runtime_error("Unable to set locale");
167 }
168 }
169 }
170
~scoped_c_thread_locale()171 scoped_c_thread_locale::~scoped_c_thread_locale()
172 {
173 if (m_prevThreadSetting != -1)
174 {
175 setlocale(LC_ALL, m_prevLocale.c_str());
176 _configthreadlocale(m_prevThreadSetting);
177 }
178 }
179 #elif (defined(ANDROID) || defined(__ANDROID__))
scoped_c_thread_locale()180 scoped_c_thread_locale::scoped_c_thread_locale() {}
~scoped_c_thread_locale()181 scoped_c_thread_locale::~scoped_c_thread_locale() {}
182 #else
scoped_c_thread_locale()183 scoped_c_thread_locale::scoped_c_thread_locale() : m_prevLocale(nullptr)
184 {
185 char* prevLocale = setlocale(LC_ALL, nullptr);
186 if (prevLocale == nullptr)
187 {
188 throw std::runtime_error("Unable to retrieve current locale.");
189 }
190
191 if (std::strcmp(prevLocale, "C") != 0)
192 {
193 m_prevLocale = uselocale(c_locale());
194 if (m_prevLocale == nullptr)
195 {
196 throw std::runtime_error("Unable to set locale");
197 }
198 }
199 }
200
~scoped_c_thread_locale()201 scoped_c_thread_locale::~scoped_c_thread_locale()
202 {
203 if (m_prevLocale != nullptr)
204 {
205 uselocale(m_prevLocale);
206 }
207 }
208 #endif
209 } // namespace details
210
211 namespace details
212 {
platform_category()213 const std::error_category& __cdecl platform_category()
214 {
215 #ifdef _WIN32
216 return windows_category();
217 #else
218 return linux_category();
219 #endif
220 }
221
222 #ifdef _WIN32
223
224 // Remove once VS 2013 is no longer supported.
225 #if _MSC_VER < 1900
226 static details::windows_category_impl instance;
227 #endif
windows_category()228 const std::error_category& __cdecl windows_category()
229 {
230 #if _MSC_VER >= 1900
231 static details::windows_category_impl instance;
232 #endif
233 return instance;
234 }
235
message(int errorCode) const236 std::string windows_category_impl::message(int errorCode) const CPPREST_NOEXCEPT
237 {
238 const size_t buffer_size = 4096;
239 DWORD dwFlags = FORMAT_MESSAGE_FROM_SYSTEM;
240 LPCVOID lpSource = NULL;
241
242 #if !defined(__cplusplus_winrt)
243 if (errorCode >= 12000)
244 {
245 dwFlags = FORMAT_MESSAGE_FROM_HMODULE;
246 lpSource = GetModuleHandleA("winhttp.dll"); // this handle DOES NOT need to be freed
247 }
248 #endif
249
250 std::wstring buffer(buffer_size, 0);
251
252 const auto result = ::FormatMessageW(dwFlags, lpSource, errorCode, 0, &buffer[0], buffer_size, NULL);
253
254 if (result == 0)
255 {
256 return "Unable to get an error message for error code: " + std::to_string(errorCode) + ".";
257 }
258
259 // strip exceeding characters of the initial resize call
260 buffer.resize(result);
261
262 return utility::conversions::to_utf8string(buffer);
263 }
264
default_error_condition(int errorCode) const265 std::error_condition windows_category_impl::default_error_condition(int errorCode) const CPPREST_NOEXCEPT
266 {
267 // First see if the STL implementation can handle the mapping for common cases.
268 const std::error_condition errCondition = std::system_category().default_error_condition(errorCode);
269 const std::string errConditionMsg = errCondition.message();
270 if (!utility::details::str_iequal(errConditionMsg, "unknown error"))
271 {
272 return errCondition;
273 }
274
275 switch (errorCode)
276 {
277 #ifndef __cplusplus_winrt
278 case ERROR_WINHTTP_TIMEOUT: return std::errc::timed_out;
279 case ERROR_WINHTTP_CANNOT_CONNECT: return std::errc::host_unreachable;
280 case ERROR_WINHTTP_CONNECTION_ERROR: return std::errc::connection_aborted;
281 #endif
282 case INET_E_RESOURCE_NOT_FOUND:
283 case INET_E_CANNOT_CONNECT: return std::errc::host_unreachable;
284 case INET_E_CONNECTION_TIMEOUT: return std::errc::timed_out;
285 case INET_E_DOWNLOAD_FAILURE: return std::errc::connection_aborted;
286 default: break;
287 }
288
289 return std::error_condition(errorCode, *this);
290 }
291
292 #else
293
linux_category()294 const std::error_category& __cdecl linux_category()
295 {
296 // On Linux we are using boost error codes which have the exact same
297 // mapping and are equivalent with std::generic_category error codes.
298 return std::generic_category();
299 }
300
301 #endif
302
303 } // namespace details
304
305 #define LOW_3BITS 0x7
306 #define LOW_4BITS 0xF
307 #define LOW_5BITS 0x1F
308 #define LOW_6BITS 0x3F
309 #define BIT4 0x8
310 #define BIT5 0x10
311 #define BIT6 0x20
312 #define BIT7 0x40
313 #define BIT8 0x80
314 #define L_SURROGATE_START 0xDC00
315 #define L_SURROGATE_END 0xDFFF
316 #define H_SURROGATE_START 0xD800
317 #define H_SURROGATE_END 0xDBFF
318 #define SURROGATE_PAIR_START 0x10000
319
320 // Create a dedicated type for characters to avoid the issue
321 // of different platforms defaulting char to be either signed
322 // or unsigned.
323 using UtilCharInternal_t = signed char;
324
count_utf8_to_utf16(const std::string & s)325 inline size_t count_utf8_to_utf16(const std::string& s)
326 {
327 const size_t sSize = s.size();
328 auto const sData = reinterpret_cast<const UtilCharInternal_t*>(s.data());
329 size_t result {sSize};
330
331 for (size_t index = 0; index < sSize;)
332 {
333 if (sData[index] >= 0)
334 {
335 // use fast inner loop to skip single byte code points (which are
336 // expected to be the most frequent)
337 while ((++index < sSize) && (sData[index] >= 0))
338 ;
339
340 if (index >= sSize) break;
341 }
342
343 // start special handling for multi-byte code points
344 const UtilCharInternal_t c {sData[index++]};
345
346 if ((c & BIT7) == 0)
347 {
348 throw std::range_error("UTF-8 string character can never start with 10xxxxxx");
349 }
350 else if ((c & BIT6) == 0) // 2 byte character, 0x80 to 0x7FF
351 {
352 if (index == sSize)
353 {
354 throw std::range_error("UTF-8 string is missing bytes in character");
355 }
356
357 const UtilCharInternal_t c2 {sData[index++]};
358 if ((c2 & 0xC0) != BIT8)
359 {
360 throw std::range_error("UTF-8 continuation byte is missing leading bit mask");
361 }
362
363 // can't require surrogates for 7FF
364 --result;
365 }
366 else if ((c & BIT5) == 0) // 3 byte character, 0x800 to 0xFFFF
367 {
368 if (sSize - index < 2)
369 {
370 throw std::range_error("UTF-8 string is missing bytes in character");
371 }
372
373 const UtilCharInternal_t c2 {sData[index++]};
374 const UtilCharInternal_t c3 {sData[index++]};
375 if (((c2 | c3) & 0xC0) != BIT8)
376 {
377 throw std::range_error("UTF-8 continuation byte is missing leading bit mask");
378 }
379
380 result -= 2;
381 }
382 else if ((c & BIT4) == 0) // 4 byte character, 0x10000 to 0x10FFFF
383 {
384 if (sSize - index < 3)
385 {
386 throw std::range_error("UTF-8 string is missing bytes in character");
387 }
388
389 const UtilCharInternal_t c2 {sData[index++]};
390 const UtilCharInternal_t c3 {sData[index++]};
391 const UtilCharInternal_t c4 {sData[index++]};
392 if (((c2 | c3 | c4) & 0xC0) != BIT8)
393 {
394 throw std::range_error("UTF-8 continuation byte is missing leading bit mask");
395 }
396
397 const uint32_t codePoint =
398 ((c & LOW_3BITS) << 18) | ((c2 & LOW_6BITS) << 12) | ((c3 & LOW_6BITS) << 6) | (c4 & LOW_6BITS);
399 result -= (3 - (codePoint >= SURROGATE_PAIR_START));
400 }
401 else
402 {
403 throw std::range_error("UTF-8 string has invalid Unicode code point");
404 }
405 }
406
407 return result;
408 }
409
utf8_to_utf16(const std::string & s)410 utf16string __cdecl conversions::utf8_to_utf16(const std::string& s)
411 {
412 // Save repeated heap allocations, use the length of resulting sequence.
413 const size_t srcSize = s.size();
414 auto const srcData = reinterpret_cast<const UtilCharInternal_t*>(s.data());
415 utf16string dest(count_utf8_to_utf16(s), L'\0');
416 utf16string::value_type* const destData = &dest[0];
417 size_t destIndex = 0;
418
419 for (size_t index = 0; index < srcSize; ++index)
420 {
421 UtilCharInternal_t src = srcData[index];
422 switch (src & 0xF0)
423 {
424 case 0xF0: // 4 byte character, 0x10000 to 0x10FFFF
425 {
426 const UtilCharInternal_t c2 {srcData[++index]};
427 const UtilCharInternal_t c3 {srcData[++index]};
428 const UtilCharInternal_t c4 {srcData[++index]};
429 uint32_t codePoint =
430 ((src & LOW_3BITS) << 18) | ((c2 & LOW_6BITS) << 12) | ((c3 & LOW_6BITS) << 6) | (c4 & LOW_6BITS);
431 if (codePoint >= SURROGATE_PAIR_START)
432 {
433 // In UTF-16 U+10000 to U+10FFFF are represented as two 16-bit code units, surrogate pairs.
434 // - 0x10000 is subtracted from the code point
435 // - high surrogate is 0xD800 added to the top ten bits
436 // - low surrogate is 0xDC00 added to the low ten bits
437 codePoint -= SURROGATE_PAIR_START;
438 destData[destIndex++] = static_cast<utf16string::value_type>((codePoint >> 10) | H_SURROGATE_START);
439 destData[destIndex++] =
440 static_cast<utf16string::value_type>((codePoint & 0x3FF) | L_SURROGATE_START);
441 }
442 else
443 {
444 // In UTF-16 U+0000 to U+D7FF and U+E000 to U+FFFF are represented exactly as the Unicode code point
445 // value. U+D800 to U+DFFF are not valid characters, for simplicity we assume they are not present
446 // but will encode them if encountered.
447 destData[destIndex++] = static_cast<utf16string::value_type>(codePoint);
448 }
449 }
450 break;
451 case 0xE0: // 3 byte character, 0x800 to 0xFFFF
452 {
453 const UtilCharInternal_t c2 {srcData[++index]};
454 const UtilCharInternal_t c3 {srcData[++index]};
455 destData[destIndex++] = static_cast<utf16string::value_type>(
456 ((src & LOW_4BITS) << 12) | ((c2 & LOW_6BITS) << 6) | (c3 & LOW_6BITS));
457 }
458 break;
459 case 0xD0: // 2 byte character, 0x80 to 0x7FF
460 case 0xC0:
461 {
462 const UtilCharInternal_t c2 {srcData[++index]};
463 destData[destIndex++] =
464 static_cast<utf16string::value_type>(((src & LOW_5BITS) << 6) | (c2 & LOW_6BITS));
465 }
466 break;
467 default: // single byte character, 0x0 to 0x7F
468 // try to use a fast inner loop for following single byte characters,
469 // since they are quite probable
470 do
471 {
472 destData[destIndex++] = static_cast<utf16string::value_type>(srcData[index++]);
473 } while (index < srcSize && srcData[index] > 0);
474 // adjust index since it will be incremented by the for loop
475 --index;
476 }
477 }
478 return dest;
479 }
480
count_utf16_to_utf8(const utf16string & w)481 inline size_t count_utf16_to_utf8(const utf16string& w)
482 {
483 const utf16string::value_type* const srcData = &w[0];
484 const size_t srcSize = w.size();
485 size_t destSize(srcSize);
486 for (size_t index = 0; index < srcSize; ++index)
487 {
488 const utf16string::value_type ch(srcData[index]);
489 if (ch <= 0x7FF)
490 {
491 if (ch > 0x7F) // 2 bytes needed (11 bits used)
492 {
493 ++destSize;
494 }
495 }
496 // Check for high surrogate.
497 else if (ch >= H_SURROGATE_START && ch <= H_SURROGATE_END) // 4 bytes needed (21 bits used)
498 {
499 ++index;
500 if (index == srcSize)
501 {
502 throw std::range_error("UTF-16 string is missing low surrogate");
503 }
504
505 const auto lowSurrogate = srcData[index];
506 if (lowSurrogate < L_SURROGATE_START || lowSurrogate > L_SURROGATE_END)
507 {
508 throw std::range_error("UTF-16 string has invalid low surrogate");
509 }
510
511 destSize += 2;
512 }
513 else // 3 bytes needed (16 bits used)
514 {
515 destSize += 2;
516 }
517 }
518
519 return destSize;
520 }
521
utf16_to_utf8(const utf16string & w)522 std::string __cdecl conversions::utf16_to_utf8(const utf16string& w)
523 {
524 const size_t srcSize = w.size();
525 const utf16string::value_type* const srcData = &w[0];
526 std::string dest(count_utf16_to_utf8(w), '\0');
527 std::string::value_type* const destData = &dest[0];
528 size_t destIndex(0);
529
530 for (size_t index = 0; index < srcSize; ++index)
531 {
532 const utf16string::value_type src = srcData[index];
533 if (src <= 0x7FF)
534 {
535 if (src <= 0x7F) // single byte character
536 {
537 destData[destIndex++] = static_cast<char>(src);
538 }
539 else // 2 bytes needed (11 bits used)
540 {
541 destData[destIndex++] = static_cast<char>(char((src >> 6) | 0xC0)); // leading 5 bits
542 destData[destIndex++] = static_cast<char>(char((src & LOW_6BITS) | BIT8)); // trailing 6 bits
543 }
544 }
545 // Check for high surrogate.
546 else if (src >= H_SURROGATE_START && src <= H_SURROGATE_END)
547 {
548 const auto highSurrogate = src;
549 const auto lowSurrogate = srcData[++index];
550
551 // To get from surrogate pair to Unicode code point:
552 // - subtract 0xD800 from high surrogate, this forms top ten bits
553 // - subtract 0xDC00 from low surrogate, this forms low ten bits
554 // - add 0x10000
555 // Leaves a code point in U+10000 to U+10FFFF range.
556 uint32_t codePoint = highSurrogate - H_SURROGATE_START;
557 codePoint <<= 10;
558 codePoint |= lowSurrogate - L_SURROGATE_START;
559 codePoint += SURROGATE_PAIR_START;
560
561 // 4 bytes needed (21 bits used)
562 destData[destIndex++] = static_cast<char>((codePoint >> 18) | 0xF0); // leading 3 bits
563 destData[destIndex++] = static_cast<char>(((codePoint >> 12) & LOW_6BITS) | BIT8); // next 6 bits
564 destData[destIndex++] = static_cast<char>(((codePoint >> 6) & LOW_6BITS) | BIT8); // next 6 bits
565 destData[destIndex++] = static_cast<char>((codePoint & LOW_6BITS) | BIT8); // trailing 6 bits
566 }
567 else // 3 bytes needed (16 bits used)
568 {
569 destData[destIndex++] = static_cast<char>((src >> 12) | 0xE0); // leading 4 bits
570 destData[destIndex++] = static_cast<char>(((src >> 6) & LOW_6BITS) | BIT8); // middle 6 bits
571 destData[destIndex++] = static_cast<char>((src & LOW_6BITS) | BIT8); // trailing 6 bits
572 }
573 }
574
575 return dest;
576 }
577
usascii_to_utf16(const std::string & s)578 utf16string __cdecl conversions::usascii_to_utf16(const std::string& s)
579 {
580 // Ascii is a subset of UTF-8 so just convert to UTF-16
581 return utf8_to_utf16(s);
582 }
583
latin1_to_utf16(const std::string & s)584 utf16string __cdecl conversions::latin1_to_utf16(const std::string& s)
585 {
586 // Latin1 is the first 256 code points in Unicode.
587 // In UTF-16 encoding each of these is represented as exactly the numeric code point.
588 utf16string dest;
589 // Prefer resize combined with for-loop over constructor dest(s.begin(), s.end())
590 // for faster assignment.
591 dest.resize(s.size());
592 for (size_t i = 0; i < s.size(); ++i)
593 {
594 dest[i] = utf16char(static_cast<unsigned char>(s[i]));
595 }
596 return dest;
597 }
598
latin1_to_utf8(const std::string & s)599 utf8string __cdecl conversions::latin1_to_utf8(const std::string& s) { return utf16_to_utf8(latin1_to_utf16(s)); }
600
601 #ifndef _UTF16_STRINGS
to_string_t(utf16string && s)602 utility::string_t __cdecl conversions::to_string_t(utf16string&& s) { return utf16_to_utf8(std::move(s)); }
603 #endif
604
605 #ifdef _UTF16_STRINGS
to_string_t(std::string && s)606 utility::string_t __cdecl conversions::to_string_t(std::string&& s) { return utf8_to_utf16(std::move(s)); }
607 #endif
608
609 #ifndef _UTF16_STRINGS
to_string_t(const utf16string & s)610 utility::string_t __cdecl conversions::to_string_t(const utf16string& s) { return utf16_to_utf8(s); }
611 #endif
612
613 #ifdef _UTF16_STRINGS
to_string_t(const std::string & s)614 utility::string_t __cdecl conversions::to_string_t(const std::string& s) { return utf8_to_utf16(s); }
615 #endif
616
to_utf8string(const utf16string & value)617 std::string __cdecl conversions::to_utf8string(const utf16string& value) { return utf16_to_utf8(value); }
618
to_utf16string(const std::string & value)619 utf16string __cdecl conversions::to_utf16string(const std::string& value) { return utf8_to_utf16(value); }
620
621 static const int64_t NtToUnixOffsetSeconds = 11644473600; // diff between windows and unix epochs (seconds)
622
year_is_leap_year(int year)623 static bool year_is_leap_year(int year) { return (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)); }
624
625 static const int SecondsInMinute = 60;
626 static const int SecondsInHour = SecondsInMinute * 60;
627 static const int SecondsInDay = SecondsInHour * 24;
628
629 static const int DaysInYear = 365;
630 static const int DaysIn4Years = DaysInYear * 4 + 1;
631 static const int DaysIn100Years = DaysIn4Years * 25 - 1;
632 static const int DaysIn400Years = DaysIn100Years * 4 + 1;
633
634 static const int SecondsInYear = SecondsInDay * DaysInYear;
635 static const int SecondsIn4Years = SecondsInDay * DaysIn4Years;
636 static const int64_t SecondsIn100Years = static_cast<int64_t>(SecondsInDay) * DaysIn100Years;
637 static const int64_t SecondsIn400Years = static_cast<int64_t>(SecondsInDay) * DaysIn400Years;
638 static const int64_t SecondsFrom1900To2001 = INT64_C(3187296000);
639
640 static const int64_t NtTo1900OffsetInterval = INT64_C(0x014F373BFDE04000);
641
count_leap_years(const int yearsSince1900)642 static int count_leap_years(const int yearsSince1900)
643 {
644 int tmpYears = yearsSince1900 + 299; // shift into 1601, the first 400 year cycle including 1900
645
646 int year400 = tmpYears / 400;
647 tmpYears -= year400 * 400;
648 int result = year400 * 97;
649
650 int year100 = tmpYears / 100;
651 tmpYears -= year100 * 100;
652 result += year100 * 24;
653
654 result += tmpYears / 4;
655
656 // subtract off leap years from 1601
657 result -= 72;
658
659 return result;
660 }
661
662 // The following table assumes no leap year; leap year is added separately
663 static const unsigned short cumulative_days_to_month[12] = {
664 0, // Jan
665 31, // Feb
666 59, // Mar
667 90, // Apr
668 120, // May
669 151, // Jun
670 181, // Jul
671 212, // Aug
672 243, // Sep
673 273, // Oct
674 304, // Nov
675 334 // Dec
676 };
677
678 static const unsigned short cumulative_days_to_month_leap[12] = {
679 0, // Jan
680 31, // Feb
681 60, // Mar
682 91, // Apr
683 121, // May
684 152, // Jun
685 182, // Jul
686 213, // Aug
687 244, // Sep
688 274, // Oct
689 305, // Nov
690 335 // Dec
691 };
692
utc_now()693 datetime __cdecl datetime::utc_now()
694 {
695 #ifdef _WIN32
696 ULARGE_INTEGER largeInt;
697 FILETIME fileTime;
698 GetSystemTimeAsFileTime(&fileTime);
699
700 largeInt.LowPart = fileTime.dwLowDateTime;
701 largeInt.HighPart = fileTime.dwHighDateTime;
702
703 return datetime(largeInt.QuadPart);
704 #else // LINUX
705 struct timeval time;
706 gettimeofday(&time, nullptr);
707 int64_t result = NtToUnixOffsetSeconds + time.tv_sec;
708 result *= _secondTicks; // convert to 10e-7
709 result += time.tv_usec * 10; // convert and add microseconds, 10e-6 to 10e-7
710 return datetime(static_cast<interval_type>(result));
711 #endif
712 }
713
714 static const char dayNames[] = "Sun\0Mon\0Tue\0Wed\0Thu\0Fri\0Sat";
715 static const char monthNames[] = "Jan\0Feb\0Mar\0Apr\0May\0Jun\0Jul\0Aug\0Sep\0Oct\0Nov\0Dec";
716
717 struct compute_year_result
718 {
719 int year;
720 int secondsLeftThisYear;
721 };
722
723 static const int64_t secondsFrom1601To1900 = INT64_C(9435484800);
724
compute_year(int64_t secondsSince1900)725 static compute_year_result compute_year(int64_t secondsSince1900)
726 {
727 int64_t secondsLeft = secondsSince1900 + secondsFrom1601To1900; // shift to start of this 400 year cycle
728
729 int year400 = static_cast<int>(secondsLeft / SecondsIn400Years);
730 secondsLeft -= year400 * SecondsIn400Years;
731
732 int year100 = static_cast<int>(secondsLeft / SecondsIn100Years);
733 secondsLeft -= year100 * SecondsIn100Years;
734
735 int year4 = static_cast<int>(secondsLeft / SecondsIn4Years);
736 int secondsInt = static_cast<int>(secondsLeft - year4 * SecondsIn4Years);
737
738 int year1 = secondsInt / SecondsInYear;
739 secondsInt -= year1 * SecondsInYear;
740
741 // shift back to 1900 base from 1601:
742 return {year400 * 400 + year100 * 100 + year4 * 4 + year1 - 299, secondsInt};
743 }
744
to_string(date_format format) const745 utility::string_t datetime::to_string(date_format format) const
746 {
747 if (m_interval > INT64_C(2650467743990000000))
748 {
749 throw std::out_of_range("The requested year exceeds the year 9999.");
750 }
751
752 const int64_t epochAdjusted = static_cast<int64_t>(m_interval) - NtTo1900OffsetInterval;
753 const int64_t secondsSince1900 = epochAdjusted / _secondTicks; // convert to seconds
754 const int fracSec = static_cast<int>(epochAdjusted % _secondTicks);
755
756 const auto yearData = compute_year(secondsSince1900);
757 const int year = yearData.year;
758 const int yearDay = yearData.secondsLeftThisYear / SecondsInDay;
759 int leftover = yearData.secondsLeftThisYear % SecondsInDay;
760 const int hour = leftover / SecondsInHour;
761 leftover = leftover % SecondsInHour;
762 const int minute = leftover / SecondsInMinute;
763 leftover = leftover % SecondsInMinute;
764
765 const auto& monthTable = year_is_leap_year(year) ? cumulative_days_to_month_leap : cumulative_days_to_month;
766 int month = 0;
767 while (month < 11 && monthTable[month + 1] <= yearDay)
768 {
769 ++month;
770 }
771
772 const auto monthDay = yearDay - monthTable[month] + 1;
773 const auto weekday = static_cast<int>((secondsSince1900 / SecondsInDay + 1) % 7);
774
775 char outBuffer[38]; // Thu, 01 Jan 1970 00:00:00 GMT\0
776 // 1970-01-01T00:00:00.1234567Z\0
777 char* outCursor = outBuffer;
778 switch (format)
779 {
780 case RFC_1123:
781 #ifdef _MSC_VER
782 sprintf_s(outCursor,
783 26,
784 "%s, %02d %s %04d %02d:%02d:%02d",
785 dayNames + 4 * weekday,
786 monthDay,
787 monthNames + 4 * month,
788 year + 1900,
789 hour,
790 minute,
791 leftover);
792 #else // ^^^ _MSC_VER // !_MSC_VER vvv
793 sprintf(outCursor,
794 "%s, %02d %s %04d %02d:%02d:%02d",
795 dayNames + 4 * weekday,
796 monthDay,
797 monthNames + 4 * month,
798 year + 1900,
799 hour,
800 minute,
801 leftover);
802 #endif // _MSC_VER
803 outCursor += 25;
804 memcpy(outCursor, " GMT", 4);
805 outCursor += 4;
806 return utility::string_t(outBuffer, outCursor);
807 case ISO_8601:
808 #ifdef _MSC_VER
809 sprintf_s(outCursor,
810 20,
811 "%04d-%02d-%02dT%02d:%02d:%02d",
812 year + 1900,
813 month + 1,
814 monthDay,
815 hour,
816 minute,
817 leftover);
818 #else // ^^^ _MSC_VER // !_MSC_VER vvv
819 sprintf(
820 outCursor, "%04d-%02d-%02dT%02d:%02d:%02d", year + 1900, month + 1, monthDay, hour, minute, leftover);
821 #endif // _MSC_VER
822 outCursor += 19;
823 if (fracSec != 0)
824 {
825 // Append fractional second, which is a 7-digit value with no trailing zeros
826 // This way, '1200' becomes '00012'
827 #ifdef _MSC_VER
828 size_t appended = sprintf_s(outCursor, 9, ".%07d", fracSec);
829 #else // ^^^ _MSC_VER // !_MSC_VER vvv
830 size_t appended = sprintf(outCursor, ".%07d", fracSec);
831 #endif // _MSC_VER
832 while (outCursor[appended - 1] == '0')
833 {
834 --appended; // trim trailing zeros
835 }
836
837 outCursor += appended;
838 }
839
840 *outCursor = 'Z';
841 ++outCursor;
842 return utility::string_t(outBuffer, outCursor);
843 default: throw std::invalid_argument("Unrecognized date format.");
844 }
845 }
846
847 template<class CharT>
string_starts_with(const CharT * haystack,const char * needle)848 static bool string_starts_with(const CharT* haystack, const char* needle)
849 {
850 while (*needle)
851 {
852 if (*haystack != static_cast<CharT>(*needle))
853 {
854 return false;
855 }
856
857 ++haystack;
858 ++needle;
859 }
860
861 return true;
862 }
863
864 #define ascii_isdigit(c) ((unsigned char)((unsigned char)(c) - '0') <= 9)
865 #define ascii_isdigit6(c) ((unsigned char)((unsigned char)(c) - '0') <= 6)
866 #define ascii_isdigit5(c) ((unsigned char)((unsigned char)(c) - '0') <= 5)
867 #define ascii_isdigit3(c) ((unsigned char)((unsigned char)(c) - '0') <= 3)
868 #define ascii_isdigit2(c) ((unsigned char)((unsigned char)(c) - '0') <= 2)
869 #define ascii_isdigit1(c) ((unsigned char)((unsigned char)(c) - '0') <= 1)
870
871 static const unsigned char max_days_in_month[12] = {
872 31, // Jan
873 00, // Feb, special handling for leap years
874 31, // Mar
875 30, // Apr
876 31, // May
877 30, // Jun
878 31, // Jul
879 31, // Aug
880 30, // Sep
881 31, // Oct
882 30, // Nov
883 31 // Dec
884 };
885
validate_day_month(int day,int month,int year)886 static bool validate_day_month(int day, int month, int year)
887 {
888 int maxDaysThisMonth;
889 if (month == 1)
890 { // Feb needs leap year testing
891 maxDaysThisMonth = 28 + year_is_leap_year(year);
892 }
893 else
894 {
895 maxDaysThisMonth = max_days_in_month[month];
896 }
897
898 return day >= 1 && day <= maxDaysThisMonth;
899 }
900
get_year_day(int month,int monthDay,int year)901 static int get_year_day(int month, int monthDay, int year)
902 {
903 return cumulative_days_to_month[month] + monthDay + (year_is_leap_year(year) && month > 1) - 1;
904 }
905
906 template<class CharT>
atoi2(const CharT * str)907 static int atoi2(const CharT* str)
908 {
909 return (static_cast<unsigned char>(str[0]) - '0') * 10 + (static_cast<unsigned char>(str[1]) - '0');
910 }
911
timezone_adjust(int64_t result,unsigned char chSign,int adjustHours,int adjustMinutes)912 static int64_t timezone_adjust(int64_t result, unsigned char chSign, int adjustHours, int adjustMinutes)
913 {
914 if (adjustHours > 23)
915 {
916 return -1;
917 }
918
919 // adjustMinutes > 59 is impossible due to digit 5 check
920 const int tzAdjust = adjustMinutes * 60 + adjustHours * 60 * 60;
921 if (chSign == '-')
922 {
923 if (INT64_MAX - result < tzAdjust)
924 {
925 return -1;
926 }
927
928 result += tzAdjust;
929 }
930 else
931 {
932 if (tzAdjust > result)
933 {
934 return -1;
935 }
936
937 result -= tzAdjust;
938 }
939
940 return result;
941 }
942
943 /*
944 https://tools.ietf.org/html/rfc822
945 https://tools.ietf.org/html/rfc1123
946
947 date-time = [ day "," ] date time ; dd mm yy
948 ; hh:mm:ss zzz
949
950 day = "Mon" / "Tue" / "Wed" / "Thu"
951 / "Fri" / "Sat" / "Sun"
952
953 date = 1*2DIGIT month 2DIGIT ; day month year
954 ; e.g. 20 Jun 82
955 RFC1123 changes this to:
956 date = 1*2DIGIT month 2*4DIGIT ; day month year
957 ; e.g. 20 Jun 1982
958 This implementation only accepts 4 digit years.
959
960 month = "Jan" / "Feb" / "Mar" / "Apr"
961 / "May" / "Jun" / "Jul" / "Aug"
962 / "Sep" / "Oct" / "Nov" / "Dec"
963
964 time = hour zone ; ANSI and Military
965
966 hour = 2DIGIT ":" 2DIGIT [":" 2DIGIT]
967 ; 00:00:00 - 23:59:59
968
969 zone = "UT" / "GMT" ; Universal Time
970 ; North American : UT
971 / "EST" / "EDT" ; Eastern: - 5/ - 4
972 / "CST" / "CDT" ; Central: - 6/ - 5
973 / "MST" / "MDT" ; Mountain: - 7/ - 6
974 / "PST" / "PDT" ; Pacific: - 8/ - 7
975
976 // military time deleted by RFC 1123
977
978 / ( ("+" / "-") 4DIGIT ) ; Local differential
979 ; hours+min. (HHMM)
980 */
981
982
from_string(const utility::string_t & dateString,date_format format)983 datetime __cdecl datetime::from_string(const utility::string_t& dateString, date_format format)
984 {
985 datetime result;
986 int64_t secondsSince1900;
987 uint64_t fracSec = 0;
988 auto str = dateString.c_str();
989 if (format == RFC_1123)
990 {
991 int parsedWeekday = 0;
992 for (; parsedWeekday < 7; ++parsedWeekday)
993 {
994 if (string_starts_with(str, dayNames + parsedWeekday * 4) && str[3] == _XPLATSTR(',') &&
995 str[4] == _XPLATSTR(' '))
996 {
997 str += 5; // parsed day of week
998 break;
999 }
1000 }
1001
1002 int monthDay;
1003 if (ascii_isdigit3(str[0]) && ascii_isdigit(str[1]) && str[2] == _XPLATSTR(' '))
1004 {
1005 monthDay = atoi2(str); // validity checked later
1006 str += 3; // parsed day
1007 }
1008 else if (ascii_isdigit(str[0]) && str[1] == _XPLATSTR(' '))
1009 {
1010 monthDay = str[0] - _XPLATSTR('0');
1011 str += 2; // parsed day
1012 }
1013 else
1014 {
1015 return result;
1016 }
1017
1018 if (monthDay == 0)
1019 {
1020 return result;
1021 }
1022
1023 int month = 0;
1024 for (;;)
1025 {
1026 if (string_starts_with(str, monthNames + month * 4))
1027 {
1028 break;
1029 }
1030
1031 ++month;
1032 if (month == 12)
1033 {
1034 return result;
1035 }
1036 }
1037
1038 if (str[3] != _XPLATSTR(' '))
1039 {
1040 return result;
1041 }
1042
1043 str += 4; // parsed month
1044
1045 if (!ascii_isdigit(str[0]) || !ascii_isdigit(str[1]) || !ascii_isdigit(str[2]) || !ascii_isdigit(str[3]) ||
1046 str[4] != ' ')
1047 {
1048 return result;
1049 }
1050
1051 int year = (str[0] - _XPLATSTR('0')) * 1000 + (str[1] - _XPLATSTR('0')) * 100 + (str[2] - _XPLATSTR('0')) * 10 +
1052 (str[3] - _XPLATSTR('0'));
1053 if (year < 1900)
1054 {
1055 return result;
1056 }
1057
1058 // days in month validity check
1059 if (!validate_day_month(monthDay, month, year))
1060 {
1061 return result;
1062 }
1063
1064 str += 5; // parsed year
1065 const int yearDay = get_year_day(month, monthDay, year);
1066
1067 if (!ascii_isdigit2(str[0]) || !ascii_isdigit(str[1]) || str[2] != _XPLATSTR(':') || !ascii_isdigit5(str[3]) ||
1068 !ascii_isdigit(str[4]))
1069 {
1070 return result;
1071 }
1072
1073 const int hour = atoi2(str);
1074 if (hour > 23)
1075 {
1076 return result;
1077 }
1078
1079 str += 3; // parsed hour
1080 const int minute = atoi2(str);
1081 str += 2; // parsed mins
1082
1083 int sec;
1084 if (str[0] == ':')
1085 {
1086 if (!ascii_isdigit6(str[1]) || !ascii_isdigit(str[2]) || str[3] != _XPLATSTR(' '))
1087 {
1088 return result;
1089 }
1090
1091 sec = atoi2(str + 1);
1092 str += 4; // parsed seconds
1093 }
1094 else if (str[0] == _XPLATSTR(' '))
1095 {
1096 sec = 0;
1097 str += 1; // parsed seconds
1098 }
1099 else
1100 {
1101 return result;
1102 }
1103
1104 if (sec > 60)
1105 { // 60 to allow leap seconds
1106 return result;
1107 }
1108
1109 year -= 1900;
1110 int daysSince1900 = year * DaysInYear + count_leap_years(year) + yearDay;
1111
1112 if (parsedWeekday != 7)
1113 {
1114 const int actualWeekday = (daysSince1900 + 1) % 7;
1115
1116 if (parsedWeekday != actualWeekday)
1117 {
1118 return result;
1119 }
1120 }
1121
1122 secondsSince1900 =
1123 static_cast<int64_t>(daysSince1900) * SecondsInDay + hour * SecondsInHour + minute * SecondsInMinute + sec;
1124
1125 if (!string_starts_with(str, "GMT") && !string_starts_with(str, "UT"))
1126 {
1127 // some timezone adjustment necessary
1128 auto tzCh = _XPLATSTR('-');
1129 int tzHours;
1130 int tzMinutes = 0;
1131 if (string_starts_with(str, "EDT"))
1132 {
1133 tzHours = 4;
1134 }
1135 else if (string_starts_with(str, "EST") || string_starts_with(str, "CDT"))
1136 {
1137 tzHours = 5;
1138 }
1139 else if (string_starts_with(str, "CST") || string_starts_with(str, "MDT"))
1140 {
1141 tzHours = 6;
1142 }
1143 else if (string_starts_with(str, "MST") || string_starts_with(str, "PDT"))
1144 {
1145 tzHours = 7;
1146 }
1147 else if (string_starts_with(str, "PST"))
1148 {
1149 tzHours = 8;
1150 }
1151 else if ((str[0] == _XPLATSTR('+') || str[0] == _XPLATSTR('-')) && ascii_isdigit2(str[1]) &&
1152 ascii_isdigit(str[2]) && ascii_isdigit5(str[3]) && ascii_isdigit(str[4]))
1153 {
1154 tzCh = str[0];
1155 tzHours = atoi2(str + 1);
1156 tzMinutes = atoi2(str + 3);
1157 }
1158 else
1159 {
1160 return result;
1161 }
1162
1163 secondsSince1900 = timezone_adjust(secondsSince1900, static_cast<unsigned char>(tzCh), tzHours, tzMinutes);
1164 if (secondsSince1900 < 0)
1165 {
1166 return result;
1167 }
1168 }
1169 }
1170 else if (format == ISO_8601)
1171 {
1172 // parse year
1173 if (!ascii_isdigit(str[0]) || !ascii_isdigit(str[1]) || !ascii_isdigit(str[2]) || !ascii_isdigit(str[3]))
1174 {
1175 return result;
1176 }
1177
1178 int year = (str[0] - _XPLATSTR('0')) * 1000 + (str[1] - _XPLATSTR('0')) * 100 + (str[2] - _XPLATSTR('0')) * 10 +
1179 (str[3] - _XPLATSTR('0'));
1180 if (year < 1900)
1181 {
1182 return result;
1183 }
1184
1185 str += 4;
1186 if (*str == _XPLATSTR('-'))
1187 {
1188 ++str;
1189 }
1190
1191 // parse month
1192 if (!ascii_isdigit1(str[0]) || !ascii_isdigit(str[1]))
1193 {
1194 return result;
1195 }
1196
1197 int month = atoi2(str);
1198 if (month < 1 || month > 12)
1199 {
1200 return result;
1201 }
1202
1203 month -= 1;
1204 str += 2;
1205
1206 if (*str == _XPLATSTR('-'))
1207 {
1208 ++str;
1209 }
1210
1211 // parse day
1212 if (!ascii_isdigit3(str[0]) || !ascii_isdigit(str[1]))
1213 {
1214 return result;
1215 }
1216
1217 int monthDay = atoi2(str);
1218 if (!validate_day_month(monthDay, month, year))
1219 {
1220 return result;
1221 }
1222
1223 const int yearDay = get_year_day(month, monthDay, year);
1224
1225 str += 2;
1226 year -= 1900;
1227 int daysSince1900 = year * DaysInYear + count_leap_years(year) + yearDay;
1228
1229 if (str[0] != _XPLATSTR('T') && str[0] != _XPLATSTR('t'))
1230 {
1231 // No time
1232 secondsSince1900 = static_cast<int64_t>(daysSince1900) * SecondsInDay;
1233
1234 result.m_interval =
1235 static_cast<interval_type>(secondsSince1900 * _secondTicks + fracSec + NtTo1900OffsetInterval);
1236 return result;
1237 }
1238
1239 ++str; // skip 'T'
1240
1241 // parse hour
1242 if (!ascii_isdigit2(str[0]) || !ascii_isdigit(str[1]))
1243 {
1244 return result;
1245 }
1246
1247 const int hour = atoi2(str);
1248 str += 2;
1249 if (hour > 23)
1250 {
1251 return result;
1252 }
1253
1254 if (*str == _XPLATSTR(':'))
1255 {
1256 ++str;
1257 }
1258
1259 // parse minute
1260 if (!ascii_isdigit5(str[0]) || !ascii_isdigit(str[1]))
1261 {
1262 return result;
1263 }
1264
1265 const int minute = atoi2(str);
1266 // minute > 59 is impossible because we checked that the first digit is <= 5 in the basic format
1267 // check above
1268
1269 str += 2;
1270
1271 if (*str == _XPLATSTR(':'))
1272 {
1273 ++str;
1274 }
1275
1276 // parse seconds
1277 if (!ascii_isdigit6(str[0]) || !ascii_isdigit(str[1]))
1278 {
1279 return result;
1280 }
1281
1282 const int sec = atoi2(str);
1283 // We allow 60 to account for leap seconds
1284 if (sec > 60)
1285 {
1286 return result;
1287 }
1288
1289 str += 2;
1290 if (str[0] == _XPLATSTR('.') && ascii_isdigit(str[1]))
1291 {
1292 ++str;
1293 int digits = 7;
1294 for (;;)
1295 {
1296 fracSec *= 10;
1297 fracSec += *str - _XPLATSTR('0');
1298 --digits;
1299 ++str;
1300 if (digits == 0)
1301 {
1302 while (ascii_isdigit(*str))
1303 {
1304 // consume remaining fractional second digits we can't use
1305 ++str;
1306 }
1307
1308 break;
1309 }
1310
1311 if (!ascii_isdigit(*str))
1312 {
1313 // no more digits in the input, do the remaining multiplies we need
1314 for (; digits != 0; --digits)
1315 {
1316 fracSec *= 10;
1317 }
1318
1319 break;
1320 }
1321 }
1322 }
1323
1324 secondsSince1900 =
1325 static_cast<int64_t>(daysSince1900) * SecondsInDay + hour * SecondsInHour + minute * SecondsInMinute + sec;
1326
1327 if (str[0] == _XPLATSTR('Z') || str[0] == _XPLATSTR('z'))
1328 {
1329 // no adjustment needed for zulu time
1330 }
1331 else if (str[0] == _XPLATSTR('+') || str[0] == _XPLATSTR('-'))
1332 {
1333 const unsigned char offsetDirection = static_cast<unsigned char>(str[0]);
1334 if (!ascii_isdigit2(str[1]) || !ascii_isdigit(str[2]) || str[3] != _XPLATSTR(':') ||
1335 !ascii_isdigit5(str[4]) || !ascii_isdigit(str[5]))
1336 {
1337 return result;
1338 }
1339
1340 secondsSince1900 = timezone_adjust(secondsSince1900, offsetDirection, atoi2(str + 1), atoi2(str + 4));
1341 if (secondsSince1900 < 0)
1342 {
1343 return result;
1344 }
1345 }
1346 else
1347 {
1348 // the timezone is malformed, but cpprestsdk currently accepts this as no timezone
1349 }
1350 }
1351 else
1352 {
1353 throw std::invalid_argument("unrecognized date format");
1354 }
1355
1356 result.m_interval = static_cast<interval_type>(secondsSince1900 * _secondTicks + fracSec + NtTo1900OffsetInterval);
1357 return result;
1358 }
1359
1360 /// <summary>
1361 /// Converts a timespan/interval in seconds to xml duration string as specified by
1362 /// http://www.w3.org/TR/xmlschema-2/#duration
1363 /// </summary>
seconds_to_xml_duration(utility::seconds durationSecs)1364 utility::string_t __cdecl timespan::seconds_to_xml_duration(utility::seconds durationSecs)
1365 {
1366 auto numSecs = durationSecs.count();
1367
1368 // Find the number of minutes
1369 auto numMins = numSecs / 60;
1370 if (numMins > 0)
1371 {
1372 numSecs = numSecs % 60;
1373 }
1374
1375 // Hours
1376 auto numHours = numMins / 60;
1377 if (numHours > 0)
1378 {
1379 numMins = numMins % 60;
1380 }
1381
1382 // Days
1383 auto numDays = numHours / 24;
1384 if (numDays > 0)
1385 {
1386 numHours = numHours % 24;
1387 }
1388
1389 // The format is:
1390 // PdaysDThoursHminutesMsecondsS
1391 utility::string_t result;
1392 // (approximate mins/hours/secs as 2 digits each + 1 prefix character) + 1 for P prefix + 1 for T
1393 size_t baseReserveSize = ((numHours > 0) + (numMins > 0) + (numSecs > 0)) * 3 + 1;
1394 if (numDays > 0)
1395 {
1396 utility::string_t daysStr = utility::conversions::details::to_string_t(numDays);
1397 result.reserve(baseReserveSize + daysStr.size() + 1);
1398 result += _XPLATSTR('P');
1399 result += daysStr;
1400 result += _XPLATSTR('D');
1401 }
1402 else
1403 {
1404 result.reserve(baseReserveSize);
1405 result += _XPLATSTR('P');
1406 }
1407
1408 result += _XPLATSTR('T');
1409
1410 if (numHours > 0)
1411 {
1412 result += utility::conversions::details::to_string_t(numHours);
1413 result += _XPLATSTR('H');
1414 }
1415
1416 if (numMins > 0)
1417 {
1418 result += utility::conversions::details::to_string_t(numMins);
1419 result += _XPLATSTR('M');
1420 }
1421
1422 if (numSecs > 0)
1423 {
1424 result += utility::conversions::details::to_string_t(numSecs);
1425 result += _XPLATSTR('S');
1426 }
1427
1428 return result;
1429 }
1430
xml_duration_to_seconds(const utility::string_t & timespanString)1431 utility::seconds __cdecl timespan::xml_duration_to_seconds(const utility::string_t& timespanString)
1432 {
1433 // The format is:
1434 // PnDTnHnMnS
1435 // if n == 0 then the field could be omitted
1436 // The final S could be omitted
1437
1438 int64_t numSecs = 0;
1439 auto cursor = timespanString.c_str();
1440 auto c = *cursor++; // skip 'P'
1441 while (c)
1442 {
1443 int val = 0;
1444 c = *cursor++;
1445
1446 while (ascii_isdigit(c))
1447 {
1448 val = val * 10 + (c - _XPLATSTR('0'));
1449 c = *cursor++;
1450
1451 if (c == _XPLATSTR('.'))
1452 {
1453 // decimal point is not handled
1454 do
1455 {
1456 c = *cursor++;
1457 } while (ascii_isdigit(c));
1458 }
1459 }
1460
1461 if (c == _XPLATSTR('D')) numSecs += val * 24 * 3600; // days
1462 if (c == _XPLATSTR('H')) numSecs += val * 3600; // Hours
1463 if (c == _XPLATSTR('M')) numSecs += val * 60; // Minutes
1464 if (c == _XPLATSTR('S') || c == _XPLATSTR('\0'))
1465 {
1466 numSecs += val; // seconds
1467 break;
1468 }
1469 }
1470
1471 return utility::seconds(numSecs);
1472 }
1473
1474 static const char c_allowed_chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
1475 static const int chars_count = static_cast<int>(sizeof(c_allowed_chars) - 1);
1476
generate()1477 utility::string_t nonce_generator::generate()
1478 {
1479 std::uniform_int_distribution<> distr(0, chars_count - 1);
1480 utility::string_t result;
1481 result.reserve(length());
1482 std::generate_n(std::back_inserter(result), length(), [&] {
1483 return static_cast<utility::char_t>(c_allowed_chars[distr(m_random)]);
1484 });
1485 return result;
1486 }
1487
1488 } // namespace utility
1489