Skip to content

Commit 695a9a8

Browse files
committed
Add fractional seconds support for Linux
1 parent b4df8dd commit 695a9a8

File tree

2 files changed

+106
-42
lines changed

2 files changed

+106
-42
lines changed

Release/src/utilities/asyncrt_utils.cpp

Lines changed: 67 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ using namespace Windows::Storage::Streams;
3333
#if !defined(_MS_WINDOWS)
3434
#include <boost/date_time/posix_time/posix_time.hpp>
3535
#include <boost/date_time/posix_time/posix_time_io.hpp>
36+
// GCC 4.8 does not support regex, use boost. TODO: switch to std::regex in GCC 4.9
37+
#include <boost/regex.hpp>
3638
using namespace boost::locale::conv;
3739
#endif
3840

@@ -472,8 +474,8 @@ datetime datetime::timeval_to_datetime(struct timeval time)
472474
{
473475
const uint64_t epoch_offset = 11644473600LL; // diff between windows and unix epochs (seconds)
474476
uint64_t result = epoch_offset + time.tv_sec;
475-
result *= 10000000LL; // convert to 10e-7
476-
result += time.tv_usec*10; //add microseconds (in 10e-7)
477+
result *= _secondTicks; // convert to 10e-7
478+
result += time.tv_usec; //add microseconds (in 10e-7)
477479
return datetime(result);
478480
}
479481
#endif
@@ -594,17 +596,36 @@ utility::string_t datetime::to_string(date_format format) const
594596
return outStream.str();
595597
#else //LINUX
596598
uint64_t input = m_interval;
597-
input /= 10000000LL; // convert to seconds
599+
uint64_t frac_sec = input % _secondTicks;
600+
input /= _secondTicks; // convert to seconds
598601
time_t time = (time_t)input - (time_t)11644473600LL;// diff between windows and unix epochs (seconds)
599602

600603
struct tm datetime;
601604
gmtime_r(&time, &datetime);
602605

603-
const int max_dt_length = 29;
604-
char output[max_dt_length+1]; output[max_dt_length] = '\0';
605-
strftime(output, max_dt_length+1,
606-
format == RFC_1123 ? "%a, %d %b %Y %H:%M:%S GMT" : "%Y-%m-%dT%H:%M:%SZ",
607-
&datetime);
606+
const int max_dt_length = 64;
607+
char output[max_dt_length+1] = {0};
608+
609+
if (format != RFC_1123 && frac_sec > 0)
610+
{
611+
// Append fractional second, which is a 7-digit value with no trailing zeros
612+
// This way, '1200' becomes '00012'
613+
char buf[9] = { 0 };
614+
snprintf(buf, sizeof(buf), ".%07ld", (long int)frac_sec);
615+
// trim trailing zeros
616+
for (int i = 7; buf[i] == '0'; i--) buf[i] = '\0';
617+
// format the datetime into a separate buffer
618+
char datetime_str[max_dt_length+1] = {0};
619+
strftime(datetime_str, sizeof(datetime_str), "%Y-%m-%dT%H:%M:%S", &datetime);
620+
// now print this buffer into the output buffer
621+
snprintf(output, sizeof(output), "%s%sZ", datetime_str, buf);
622+
}
623+
else
624+
{
625+
strftime(output, sizeof(output),
626+
format == RFC_1123 ? "%a, %d %b %Y %H:%M:%S GMT" : "%Y-%m-%dT%H:%M:%SZ",
627+
&datetime);
628+
}
608629

609630
return std::string(output);
610631
#endif
@@ -633,6 +654,23 @@ bool __cdecl datetime::system_type_to_datetime(void* pvsysTime, double seconds,
633654
}
634655
#endif
635656

657+
// Take a string that represents a fractional second and return the number of ticks
658+
// This is equivalent to doing atof on the string and multiplying by 10000000,
659+
// but does not lose precision
660+
uint64_t timeticks_from_second(const utility::string_t& str)
661+
{
662+
_ASSERTE(str.size()>1);
663+
_ASSERTE(str[0]==U('.'));
664+
uint64_t ufrac_second = 0;
665+
for(int i=1; i<=7; ++i)
666+
{
667+
ufrac_second *= 10;
668+
auto add = i < (int)str.size() ? str[i] - U('0') : 0;
669+
ufrac_second += add;
670+
}
671+
return ufrac_second;
672+
}
673+
636674
/// <summary>
637675
/// Returns a string representation of the datetime. The string is formatted based on RFC 1123 or ISO 8601
638676
/// </summary>
@@ -753,12 +791,26 @@ datetime __cdecl datetime::from_string(const utility::string_t& dateString, date
753791

754792
struct tm output = tm();
755793

794+
// avoid floating point math to preserve precision
795+
uint64_t ufrac_second = 0;
796+
756797
if ( format == RFC_1123 )
757798
{
758799
strptime(input.data(), "%a, %d %b %Y %H:%M:%S GMT", &output);
759800
}
760801
else
761802
{
803+
// Try to extract the fractional second from the timestamp
804+
boost::regex r_frac_second("(.+)(\\.\\d+)(Z$)");
805+
boost::smatch m;
806+
807+
if(boost::regex_search(input,m,r_frac_second))
808+
{
809+
auto frac = m[2].str(); // this is the fractional second
810+
ufrac_second = timeticks_from_second(frac);
811+
input = m[1].str() + m[3].str();
812+
}
813+
762814
auto result = strptime(input.data(), "%Y-%m-%dT%H:%M:%SZ", &output);
763815

764816
if ( result == nullptr )
@@ -767,6 +819,12 @@ datetime __cdecl datetime::from_string(const utility::string_t& dateString, date
767819
}
768820
if ( result == nullptr )
769821
{
822+
// Fill the date portion with the epoch,
823+
// strptime will do the rest
824+
memset(&output, 0, sizeof(struct tm));
825+
output.tm_year = 70;
826+
output.tm_mon = 1;
827+
output.tm_mday = 1;
770828
result = strptime(input.data(), "%H:%M:%SZ", &output);
771829
}
772830
if ( result == nullptr )
@@ -787,6 +845,7 @@ datetime __cdecl datetime::from_string(const utility::string_t& dateString, date
787845

788846
struct timeval tv = timeval();
789847
tv.tv_sec = time;
848+
tv.tv_usec = (__suseconds_t)ufrac_second;
790849
return timeval_to_datetime(tv);
791850
#endif
792851
}

Release/tests/Functional/utils/datetime.cpp

Lines changed: 39 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -93,55 +93,60 @@ TEST(parsing_time_extended)
9393
}
9494
}
9595

96-
TEST(parsing_time_roundtrip_datetime)
96+
TEST(parsing_time_roundtrip_datetime01)
9797
{
9898
// 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-
}
99+
utility::string_t str = _XPLATSTR("2013-11-19T14:30:59.1234567Z");
100+
auto dt = utility::datetime::from_string(str, utility::datetime::ISO_8601);
101+
utility::string_t str2 = dt.to_string(utility::datetime::ISO_8601);
102+
VERIFY_ARE_EQUAL(str2, str);
103+
}
104+
105+
TEST(parsing_time_roundtrip_datetime02)
106+
{
105107
// lose the last '999' without rounding up
106-
{
107108
utility::string_t str = _XPLATSTR("2013-11-19T14:30:59.1234567999Z");
108109
auto dt = utility::datetime::from_string(str, utility::datetime::ISO_8601);
109110
utility::string_t str2 = dt.to_string(utility::datetime::ISO_8601);
110111
VERIFY_ARE_EQUAL(str2, _XPLATSTR("2013-11-19T14:30:59.1234567Z"));
111-
}
112+
}
113+
114+
TEST(parsing_time_roundtrip_datetime03)
115+
{
112116
// 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-
}
117+
utility::string_t str = _XPLATSTR("2013-11-19T14:30:59.00123Z");
118+
auto dt = utility::datetime::from_string(str, utility::datetime::ISO_8601);
119+
utility::string_t str2 = dt.to_string(utility::datetime::ISO_8601);
120+
VERIFY_ARE_EQUAL(str2, str);
121+
}
122+
123+
TEST(parsing_time_roundtrip_datetime4)
124+
{
119125
// 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+
utility::string_t str = _XPLATSTR("2013-11-19T14:30:59.0000001Z");
127+
auto dt = utility::datetime::from_string(str, utility::datetime::ISO_8601);
128+
utility::string_t str2 = dt.to_string(utility::datetime::ISO_8601);
129+
VERIFY_ARE_EQUAL(str2, str);
130+
}
131+
132+
TEST(parsing_time_roundtrip_datetime05)
133+
{
126134
// 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-
}
135+
utility::string_t str = _XPLATSTR("2013-11-19T14:30:59.00000001Z");
136+
auto dt = utility::datetime::from_string(str, utility::datetime::ISO_8601);
137+
utility::string_t str2 = dt.to_string(utility::datetime::ISO_8601);
138+
VERIFY_ARE_EQUAL(str2, _XPLATSTR("2013-11-19T14:30:59Z"));
133139
}
134140

135141
TEST(parsing_time_roundtrip_time)
136142
{
137143
// 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-
}
144+
utility::string_t str = _XPLATSTR("14:30:59.1234567Z");
145+
//utility::string_t str = _XPLATSTR("14:30:01Z");
146+
auto dt = utility::datetime::from_string(str, utility::datetime::ISO_8601);
147+
utility::string_t str2 = dt.to_string(utility::datetime::ISO_8601);
148+
// Must look for a substring now, since the date part is filled with today's date
149+
VERIFY_IS_TRUE(str2.find(str) != std::string::npos);
145150
}
146151

147152
} // SUITE(datetime)

0 commit comments

Comments
 (0)