Skip to content

Commit b4df8dd

Browse files
committed
Add support for milliseconds in datetime -- Windows only for now
1 parent 3b11ac3 commit b4df8dd

File tree

3 files changed

+115
-42
lines changed

3 files changed

+115
-42
lines changed

Release/include/cpprest/asyncrt_utils.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -389,7 +389,10 @@ class datetime
389389
static const interval_type _dayTicks = 24*60*60*_secondTicks;
390390

391391

392-
#ifndef _MS_WINDOWS
392+
#ifdef _MS_WINDOWS
393+
// void* to avoid pulling in windows.h
394+
static _ASYNCRTIMP bool __cdecl datetime::system_type_to_datetime(/*SYSTEMTIME*/ void* psysTime, double seconds, datetime * pdt);
395+
#else
393396
static datetime timeval_to_datetime(struct timeval time);
394397
#endif
395398

Release/src/utilities/asyncrt_utils.cpp

Lines changed: 60 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -576,7 +576,19 @@ utility::string_t datetime::to_string(date_format format) const
576576
throw utility::details::create_system_error(GetLastError());
577577
}
578578

579-
outStream << dateStr << "T" << timeStr << "Z";
579+
outStream << dateStr << "T" << timeStr;
580+
uint64_t frac_sec = largeInt.QuadPart % _secondTicks;
581+
if (frac_sec > 0)
582+
{
583+
// Append fractional second, which is a 7-digit value with no trailing zeros
584+
// This way, '1200' becomes '00012'
585+
char buf[9] = { 0 };
586+
sprintf_s(buf, sizeof(buf), ".%07d", frac_sec);
587+
// trim trailing zeros
588+
for (int i = 7; buf[i] == '0'; i--) buf[i] = '\0';
589+
outStream << buf;
590+
}
591+
outStream << "Z";
580592
}
581593

582594
return outStream.str();
@@ -598,12 +610,36 @@ utility::string_t datetime::to_string(date_format format) const
598610
#endif
599611
}
600612

613+
#ifdef _MS_WINDOWS
614+
bool __cdecl datetime::system_type_to_datetime(void* pvsysTime, double seconds, datetime * pdt)
615+
{
616+
SYSTEMTIME* psysTime = (SYSTEMTIME*)pvsysTime;
617+
FILETIME fileTime;
618+
619+
if (SystemTimeToFileTime(psysTime, &fileTime))
620+
{
621+
ULARGE_INTEGER largeInt;
622+
largeInt.LowPart = fileTime.dwLowDateTime;
623+
largeInt.HighPart = fileTime.dwHighDateTime;
624+
625+
// Add hundredths of nanoseconds
626+
uint64_t hn = (uint64_t)(seconds * _secondTicks);
627+
largeInt.QuadPart += hn;
628+
629+
*pdt = datetime(largeInt.QuadPart);
630+
return true;
631+
}
632+
return false;
633+
}
634+
#endif
635+
601636
/// <summary>
602637
/// Returns a string representation of the datetime. The string is formatted based on RFC 1123 or ISO 8601
603638
/// </summary>
604639
datetime __cdecl datetime::from_string(const utility::string_t& dateString, date_format format)
605640
{
606641
#ifdef _MS_WINDOWS
642+
datetime result;
607643
if ( format == RFC_1123 )
608644
{
609645
SYSTEMTIME sysTime = {0};
@@ -629,100 +665,83 @@ datetime __cdecl datetime::from_string(const utility::string_t& dateString, date
629665

630666
if (loc != monthnames+12)
631667
{
632-
FILETIME fileTime;
633668
sysTime.wMonth = (short) ((loc - monthnames) + 1);
634-
635-
if (SystemTimeToFileTime(&sysTime, &fileTime))
669+
if (system_type_to_datetime(&sysTime, 0.0, &result))
636670
{
637-
ULARGE_INTEGER largeInt;
638-
largeInt.LowPart = fileTime.dwLowDateTime;
639-
largeInt.HighPart = fileTime.dwHighDateTime;
640-
641-
return datetime(largeInt.QuadPart);
671+
return result;
642672
}
643673
}
644674
}
645675
}
646676
else if ( format == ISO_8601 )
647677
{
678+
// Unlike FILETIME, SYSTEMTIME does not have enough precision to hold seconds in 100 nanosecond
679+
// increments. Therefore, start with seconds and milliseconds set to 0, then add them separately
680+
// from this double:
681+
double seconds;
682+
648683
{
649-
SYSTEMTIME sysTime = {0};
650-
651-
const wchar_t * formatString = L"%4d-%2d-%2dT%2d:%2d:%2dZ";
684+
SYSTEMTIME sysTime = { 0 };
685+
const wchar_t * formatString = L"%4d-%2d-%2dT%2d:%2d:%lfZ";
652686
auto n = swscanf_s(dateString.c_str(), formatString,
653687
&sysTime.wYear,
654688
&sysTime.wMonth,
655689
&sysTime.wDay,
656690
&sysTime.wHour,
657691
&sysTime.wMinute,
658-
&sysTime.wSecond);
692+
&seconds);
659693

660694
if (n == 3 || n == 6)
661695
{
662-
FILETIME fileTime;
663-
664-
if (SystemTimeToFileTime(&sysTime, &fileTime))
696+
if (system_type_to_datetime(&sysTime, seconds, &result))
665697
{
666-
ULARGE_INTEGER largeInt;
667-
largeInt.LowPart = fileTime.dwLowDateTime;
668-
largeInt.HighPart = fileTime.dwHighDateTime;
669-
670-
return datetime(largeInt.QuadPart);
698+
return result;
671699
}
672700
}
673701
}
674702
{
675703
SYSTEMTIME sysTime = {0};
676704
DWORD date = 0;
677705

678-
const wchar_t * formatString = L"%8dT%2d:%2d:%2dZ";
706+
const wchar_t * formatString = L"%8dT%2d:%2d:%lfZ";
679707
auto n = swscanf_s(dateString.c_str(), formatString,
680708
&date,
681709
&sysTime.wHour,
682710
&sysTime.wMinute,
683-
&sysTime.wSecond);
711+
&seconds);
684712

685713
if (n == 1 || n == 4)
686714
{
687-
FILETIME fileTime;
688-
689715
sysTime.wDay = date % 100;
690716
date /= 100;
691717
sysTime.wMonth = date % 100;
692718
date /= 100;
693719
sysTime.wYear = (WORD)date;
694720

695-
if (SystemTimeToFileTime(&sysTime, &fileTime))
721+
if (system_type_to_datetime(&sysTime, seconds, &result))
696722
{
697-
ULARGE_INTEGER largeInt;
698-
largeInt.LowPart = fileTime.dwLowDateTime;
699-
largeInt.HighPart = fileTime.dwHighDateTime;
700-
701-
return datetime(largeInt.QuadPart);
723+
return result;
702724
}
703725
}
704726
}
705727
{
706728
SYSTEMTIME sysTime = {0};
707729
GetSystemTime(&sysTime); // Fill date portion with today's information
730+
// Zero out second -- we will use the double for that
731+
sysTime.wSecond = 0;
732+
sysTime.wMilliseconds = 0;
708733

709-
const wchar_t * formatString = L"%2d:%2d:%2dZ";
734+
const wchar_t * formatString = L"%2d:%2d:%lfZ";
710735
auto n = swscanf_s(dateString.c_str(), formatString,
711736
&sysTime.wHour,
712737
&sysTime.wMinute,
713-
&sysTime.wSecond);
738+
&seconds);
714739

715740
if (n == 3)
716741
{
717-
FILETIME fileTime;
718-
719-
if (SystemTimeToFileTime(&sysTime, &fileTime))
742+
if (system_type_to_datetime(&sysTime, seconds, &result))
720743
{
721-
ULARGE_INTEGER largeInt;
722-
largeInt.LowPart = fileTime.dwLowDateTime;
723-
largeInt.HighPart = fileTime.dwHighDateTime;
724-
725-
return datetime(largeInt.QuadPart);
744+
return result;
726745
}
727746
}
728747
}

Release/tests/Functional/utils/datetime.cpp

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,57 @@ TEST(parsing_time_extended)
9393
}
9494
}
9595

96+
TEST(parsing_time_roundtrip_datetime)
97+
{
98+
// Preserve all 7 digits after the comma:
99+
{
100+
utility::string_t str = _XPLATSTR("2013-11-19T14:30:59.1234567Z");
101+
auto dt = utility::datetime::from_string(str, utility::datetime::ISO_8601);
102+
utility::string_t str2 = dt.to_string(utility::datetime::ISO_8601);
103+
VERIFY_ARE_EQUAL(str2, str);
104+
}
105+
// lose the last '999' without rounding up
106+
{
107+
utility::string_t str = _XPLATSTR("2013-11-19T14:30:59.1234567999Z");
108+
auto dt = utility::datetime::from_string(str, utility::datetime::ISO_8601);
109+
utility::string_t str2 = dt.to_string(utility::datetime::ISO_8601);
110+
VERIFY_ARE_EQUAL(str2, _XPLATSTR("2013-11-19T14:30:59.1234567Z"));
111+
}
112+
// leading 0-s after the comma, tricky to parse correctly
113+
{
114+
utility::string_t str = _XPLATSTR("2013-11-19T14:30:59.00123Z");
115+
auto dt = utility::datetime::from_string(str, utility::datetime::ISO_8601);
116+
utility::string_t str2 = dt.to_string(utility::datetime::ISO_8601);
117+
VERIFY_ARE_EQUAL(str2, str);
118+
}
119+
// another leading 0 test
120+
{
121+
utility::string_t str = _XPLATSTR("2013-11-19T14:30:59.0000001Z");
122+
auto dt = utility::datetime::from_string(str, utility::datetime::ISO_8601);
123+
utility::string_t str2 = dt.to_string(utility::datetime::ISO_8601);
124+
VERIFY_ARE_EQUAL(str2, str);
125+
}
126+
// this is going to be truncated
127+
{
128+
utility::string_t str = _XPLATSTR("2013-11-19T14:30:59.00000001Z");
129+
auto dt = utility::datetime::from_string(str, utility::datetime::ISO_8601);
130+
utility::string_t str2 = dt.to_string(utility::datetime::ISO_8601);
131+
VERIFY_ARE_EQUAL(str2, _XPLATSTR("2013-11-19T14:30:59Z"));
132+
}
133+
}
134+
135+
TEST(parsing_time_roundtrip_time)
136+
{
137+
// time only without date
138+
{
139+
utility::string_t str = _XPLATSTR("14:30:59.1234567Z");
140+
auto dt = utility::datetime::from_string(str, utility::datetime::ISO_8601);
141+
utility::string_t str2 = dt.to_string(utility::datetime::ISO_8601);
142+
// Must look for a substring now, since the date part is filled with today's date
143+
VERIFY_IS_TRUE(str2.find(str) != std::string::npos);
144+
}
145+
}
146+
96147
} // SUITE(datetime)
97148

98149
}}}

0 commit comments

Comments
 (0)