Skip to content

Commit b11de38

Browse files
committed
feat: enhance ISO 8601 parsing with microsecond and nanosecond support
1 parent aa2c550 commit b11de38

2 files changed

Lines changed: 138 additions & 15 deletions

File tree

include/coinbase/utils.hpp

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,17 +28,57 @@ std::string timestamp_to_string(uint64_t timestamp_ms);
2828
// Parse ISO 8601 string to milliseconds
2929
uint64_t to_milliseconds(const std::string &iso_str);
3030

31+
// Parse ISO 8601 string to microseconds
32+
uint64_t to_microseconds(const std::string &iso_str);
33+
3134
// Parse ISO 8601 string to nanoseconds
3235
uint64_t to_nanoseconds(const std::string &iso_str);
3336

3437
inline uint64_t milliseconds_from_json(const json &j, std::string_view field) {
3538
return j.at(field).is_null() ? 0 : to_milliseconds(j.at(field).get<std::string>());
3639
}
3740

41+
inline uint64_t microseconds_from_json(const json &j, std::string_view field) {
42+
return j.at(field).is_null() ? 0 : to_microseconds(j.at(field).get<std::string>());
43+
}
44+
3845
inline uint64_t nanoseconds_from_json(const json &j, std::string_view field) {
3946
return j.at(field).is_null() ? 0 : to_nanoseconds(j.at(field).get<std::string>());
4047
}
4148

49+
// Automatically detect timestamp precision and parse accordingly
50+
inline uint64_t timestamp_from_json(const json &j, std::string_view field) {
51+
if (j.at(field).is_null()) {
52+
return 0;
53+
}
54+
55+
auto timestamp_str = j.at(field).get<std::string>();
56+
57+
// Find fractional seconds
58+
size_t dot_pos = timestamp_str.find('.');
59+
if (dot_pos == std::string::npos) {
60+
// No fractional seconds, use milliseconds
61+
return to_milliseconds(timestamp_str);
62+
}
63+
64+
// Count fractional digits
65+
size_t end_pos = timestamp_str.find_first_of("Z+", dot_pos);
66+
if (end_pos == std::string::npos) end_pos = timestamp_str.length();
67+
size_t frac_digits = end_pos - dot_pos - 1;
68+
69+
// Choose precision based on fractional digits:
70+
// 1-3 digits: milliseconds (e.g., .123)
71+
// 4-6 digits: microseconds (e.g., .123456)
72+
// 7-9 digits: nanoseconds (e.g., .123456789)
73+
if (frac_digits <= 3) {
74+
return to_milliseconds(timestamp_str);
75+
} else if (frac_digits <= 6) {
76+
return to_microseconds(timestamp_str);
77+
} else {
78+
return to_nanoseconds(timestamp_str);
79+
}
80+
}
81+
4282
inline double double_from_json(const json &j, std::string_view field) {
4383
try
4484
{
@@ -108,7 +148,7 @@ inline std::string to_string(double value, Side side, double min_increment) {
108148
return oss.str();
109149
}
110150

111-
#define TIMESTAMP_FROM_JSON(j, o, field) if (j.contains(#field)) o.field = milliseconds_from_json(j, #field)
151+
#define TIMESTAMP_FROM_JSON(j, o, field) if (j.contains(#field)) o.field = timestamp_from_json(j, #field)
112152
#define NANOSECONDS_FROM_JSON(j, o, field) if (j.contains(#field)) o.field = nanoseconds_from_json(j, #field)
113153
#define DOUBLE_FROM_JSON(j, o, field) if (j.contains(#field)) o.field = double_from_json(j, #field)
114154
#define INT_FROM_JSON(j, o, field) if (j.contains(#field)) o.field = int_from_json(j, #field)

src/utils.cpp

Lines changed: 97 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -56,12 +56,27 @@ std::string timestamp_to_string(uint64_t timestamp_ms) {
5656
uint64_t to_milliseconds(const std::string &iso_str) {
5757
// Parse ISO 8601 format: "YYYY-MM-DDTHH:MM:SS.sssZ"
5858
std::tm tm = {};
59-
int milliseconds = 0;
60-
61-
// Parse the date and time parts
62-
std::sscanf(iso_str.c_str(), "%d-%d-%dT%d:%d:%d.%d",
63-
&tm.tm_year, &tm.tm_mon, &tm.tm_mday,
64-
&tm.tm_hour, &tm.tm_min, &tm.tm_sec, &milliseconds);
59+
double fractional_seconds = 0.0;
60+
61+
// Find the decimal point position
62+
size_t dot_pos = iso_str.find('.');
63+
if (dot_pos != std::string::npos) {
64+
// Parse base timestamp
65+
std::sscanf(iso_str.c_str(), "%d-%d-%dT%d:%d:%d",
66+
&tm.tm_year, &tm.tm_mon, &tm.tm_mday,
67+
&tm.tm_hour, &tm.tm_min, &tm.tm_sec);
68+
69+
// Extract fractional seconds (e.g., ".718887" -> 0.718887)
70+
size_t end_pos = iso_str.find_first_of("Z+", dot_pos);
71+
if (end_pos == std::string::npos) end_pos = iso_str.length();
72+
std::string frac_str = "0" + iso_str.substr(dot_pos, end_pos - dot_pos);
73+
fractional_seconds = std::stod(frac_str);
74+
} else {
75+
// No fractional seconds
76+
std::sscanf(iso_str.c_str(), "%d-%d-%dT%d:%d:%d",
77+
&tm.tm_year, &tm.tm_mon, &tm.tm_mday,
78+
&tm.tm_hour, &tm.tm_min, &tm.tm_sec);
79+
}
6580

6681
tm.tm_year -= 1900; // tm_year is years since 1900
6782
tm.tm_mon -= 1; // tm_mon is 0-11
@@ -75,21 +90,88 @@ uint64_t to_milliseconds(const std::string &iso_str) {
7590
#endif
7691

7792
// Convert to milliseconds and add the fractional part
93+
uint64_t milliseconds = static_cast<uint64_t>(fractional_seconds * 1000.0);
7894
return static_cast<uint64_t>(time) * 1000 + milliseconds;
7995
}
8096

97+
uint64_t to_microseconds(const std::string &iso_str) {
98+
// Parse ISO 8601 format: "YYYY-MM-DDTHH:MM:SS.ssssssZ"
99+
std::tm tm = {};
100+
double fractional_seconds = 0.0;
101+
102+
// Find the decimal point position
103+
size_t dot_pos = iso_str.find('.');
104+
if (dot_pos != std::string::npos) {
105+
// Parse base timestamp
106+
int parsed = std::sscanf(iso_str.c_str(), "%d-%d-%dT%d:%d:%d",
107+
&tm.tm_year, &tm.tm_mon, &tm.tm_mday,
108+
&tm.tm_hour, &tm.tm_min, &tm.tm_sec);
109+
110+
if (parsed < 6) {
111+
return 0;
112+
}
113+
114+
// Extract fractional seconds (e.g., ".718887" -> 0.718887)
115+
size_t end_pos = iso_str.find_first_of("Z+", dot_pos);
116+
if (end_pos == std::string::npos) end_pos = iso_str.length();
117+
std::string frac_str = "0" + iso_str.substr(dot_pos, end_pos - dot_pos);
118+
fractional_seconds = std::stod(frac_str);
119+
} else {
120+
// No fractional seconds
121+
int parsed = std::sscanf(iso_str.c_str(), "%d-%d-%dT%d:%d:%d",
122+
&tm.tm_year, &tm.tm_mon, &tm.tm_mday,
123+
&tm.tm_hour, &tm.tm_min, &tm.tm_sec);
124+
if (parsed < 6) {
125+
return 0;
126+
}
127+
}
128+
129+
tm.tm_year -= 1900; // tm_year is years since 1900
130+
tm.tm_mon -= 1; // tm_mon is 0-11
131+
tm.tm_isdst = 0; // Not daylight saving time
132+
133+
// Convert to time_t (seconds since epoch, UTC)
134+
#ifdef _WIN32
135+
auto time = _mkgmtime(&tm);
136+
#else
137+
auto time = timegm(&tm);
138+
#endif
139+
140+
// Convert to microseconds and add the fractional part
141+
uint64_t microseconds = static_cast<uint64_t>(fractional_seconds * 1000000.0);
142+
return static_cast<uint64_t>(time) * 1000000ULL + microseconds;
143+
}
144+
81145
uint64_t to_nanoseconds(const std::string &iso_str) {
82146
// Parse ISO 8601 format: "YYYY-MM-DDTHH:MM:SS.sssssssssZ"
83147
std::tm tm = {};
84-
int nanoseconds = 0;
85-
86-
// Parse the date and time parts
87-
int parsed = std::sscanf(iso_str.c_str(), "%d-%d-%dT%d:%d:%d.%d",
88-
&tm.tm_year, &tm.tm_mon, &tm.tm_mday,
89-
&tm.tm_hour, &tm.tm_min, &tm.tm_sec, &nanoseconds);
148+
double fractional_seconds = 0.0;
149+
150+
// Find the decimal point position
151+
size_t dot_pos = iso_str.find('.');
152+
if (dot_pos != std::string::npos) {
153+
// Parse base timestamp
154+
int parsed = std::sscanf(iso_str.c_str(), "%d-%d-%dT%d:%d:%d",
155+
&tm.tm_year, &tm.tm_mon, &tm.tm_mday,
156+
&tm.tm_hour, &tm.tm_min, &tm.tm_sec);
157+
158+
if (parsed < 6) {
159+
return 0;
160+
}
90161

91-
if (parsed < 6) {
92-
return 0;
162+
// Extract fractional seconds (e.g., ".718887475" -> 0.718887475)
163+
size_t end_pos = iso_str.find_first_of("Z+", dot_pos);
164+
if (end_pos == std::string::npos) end_pos = iso_str.length();
165+
std::string frac_str = "0" + iso_str.substr(dot_pos, end_pos - dot_pos);
166+
fractional_seconds = std::stod(frac_str);
167+
} else {
168+
// No fractional seconds
169+
int parsed = std::sscanf(iso_str.c_str(), "%d-%d-%dT%d:%d:%d",
170+
&tm.tm_year, &tm.tm_mon, &tm.tm_mday,
171+
&tm.tm_hour, &tm.tm_min, &tm.tm_sec);
172+
if (parsed < 6) {
173+
return 0;
174+
}
93175
}
94176

95177
tm.tm_year -= 1900; // tm_year is years since 1900
@@ -104,6 +186,7 @@ uint64_t to_nanoseconds(const std::string &iso_str) {
104186
#endif
105187

106188
// Convert to nanoseconds and add the fractional part
189+
uint64_t nanoseconds = static_cast<uint64_t>(fractional_seconds * 1000000000.0);
107190
return static_cast<uint64_t>(time) * 1000000000ULL + nanoseconds;
108191
}
109192

0 commit comments

Comments
 (0)