DAW JSON Link
daw_json_parse_iso8601_utils.h
Go to the documentation of this file.
1// Copyright (c) Darrell Wright
2//
3// Distributed under the Boost Software License, Version 1.0. (See accompanying
4// file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
5//
6// Official repository: https://github.com/beached/daw_json_link
7//
8
9#pragma once
10
11#include "version.h"
12
13#include "daw_json_assert.h"
15
16#include <daw/daw_arith_traits.h>
17#include <daw/daw_cpp_feature_check.h>
18#include <daw/daw_string_view.h>
19#include <daw/daw_traits.h>
20#include <daw/daw_uint_buffer.h>
21
22#include <chrono>
23#include <ciso646>
24#include <cstdint>
25
26namespace daw::json {
27 inline namespace DAW_JSON_VER {
28 namespace parse_utils {
29 template<typename Result, std::size_t count>
30 constexpr Result parse_unsigned( char const *digit_str ) {
31 UInt64 result = UInt64( );
32 for( std::size_t n = 0; n < count; ++n ) {
33 result *= 10U;
34 result += to_uint64( json_details::parse_digit( digit_str[n] ) );
35 }
36 return static_cast<Result>( result );
37 }
38
39 template<typename Result>
40 constexpr Result parse_unsigned2( char const *digit_str ) {
41 UInt64 result = UInt64( );
42 unsigned dig = json_details::parse_digit( *digit_str );
43 while( dig < 10 ) {
44 result *= 10U;
45 result += dig;
46 ++digit_str;
47 dig = json_details::parse_digit( *digit_str );
48 }
49 return static_cast<Result>( result );
50 }
51
52 constexpr bool is_number( char c ) {
53 return json_details::parse_digit( c ) < 10U;
54 }
55 } // namespace parse_utils
56
57 namespace datetime {
58 namespace datetime_details {
59
60 template<typename Result, typename Bounds, std::ptrdiff_t Ex>
61 constexpr Result
62 parse_number( daw::basic_string_view<char, Bounds, Ex> sv ) {
63 static_assert( daw::numeric_limits<Result>::digits10 >= 4 );
64 daw_json_assert( not sv.empty( ), ErrorReason::InvalidNumber );
65 Result result = 0;
66 Result sign = 1;
67 if( sv.front( ) == '-' ) {
68 if constexpr( daw::is_signed_v<Result> ) {
69 sign = -1;
70 }
71 sv.remove_prefix( );
72 } else if( sv.front( ) == '+' ) {
73 sv.remove_prefix( );
74 }
75 while( not sv.empty( ) ) {
76 auto const dig = json_details::parse_digit( sv.pop_front( ) );
77 daw_json_assert( dig < 10U, ErrorReason::InvalidNumber );
78 result *= 10;
79 result += static_cast<Result>( dig );
80 }
81 return result * sign;
82 }
83 } // namespace datetime_details
84 // See:
85 // https://stackoverflow.com/questions/16773285/how-to-convert-stdchronotime-point-to-stdtm-without-using-time-t
86 template<typename Clock = std::chrono::system_clock,
87 typename Duration = std::chrono::milliseconds>
88 constexpr std::chrono::time_point<Clock, Duration>
89 civil_to_time_point( std::int_least32_t yr, std::uint_least32_t mo,
90 std::uint_least32_t dy, std::uint_least32_t hr,
91 std::uint_least32_t mn, std::uint_least32_t se,
92 std::uint_least32_t ms ) {
93 constexpr auto calc = []( std::int_least32_t y, std::uint_least32_t m,
94 std::uint_least32_t d, std::uint_least32_t h,
95 std::uint_least32_t min,
96 std::uint_least32_t s,
97 std::uint_least32_t mil ) {
98 y -= static_cast<std::int_least32_t>( m ) <= 2;
99 std::int_least32_t const era = ( y >= 0 ? y : y - 399 ) / 400;
100 auto const yoe = static_cast<std::uint_least32_t>(
101 static_cast<std::int_least32_t>( y ) - era * 400 ); // [0, 399]
102 auto const doy = static_cast<std::uint_least32_t>(
103 ( 153 * ( static_cast<std::int_least32_t>( m ) +
104 ( static_cast<std::int_least32_t>( m ) > 2 ? -3 : 9 ) ) +
105 2 ) /
106 5 +
107 static_cast<std::int_least32_t>( d ) - 1 ); // [0, 365]
108 std::uint_least32_t const doe =
109 yoe * 365 + yoe / 4 - yoe / 100 + doy; // [0, 146096]
110 std::int_least32_t const days_since_epoch =
111 era * 146097 + static_cast<std::int_least32_t>( doe ) - 719468;
112
113 using Days =
114 std::chrono::duration<std::int_least32_t, std::ratio<86400>>;
115 return std::chrono::time_point<std::chrono::system_clock,
116 std::chrono::milliseconds>{ } +
117 ( Days( days_since_epoch ) + std::chrono::hours( h ) +
118 std::chrono::minutes( min ) +
119 std::chrono::seconds(
120 static_cast<std::uint_least32_t>( s ) ) +
121 std::chrono::milliseconds( mil ) );
122 };
123 // Not all clocks have the same epoch. This should account for the
124 // offset and adjust the time_point so that the days prior are in
125 // relation to unix epoch. If system_clock is used, as is the default
126 // for the return value, it will be zero and should be removed by the
127 // compiler
128 auto result = calc( yr, mo, dy, hr, mn, se, ms );
129
130 if constexpr( std::conjunction_v<
131 std::is_same<Duration, std::chrono::milliseconds>,
132 std::is_same<Clock, std::chrono::system_clock>> ) {
133 return result;
134 } else if constexpr( std::is_same_v<Clock,
135 std::chrono::system_clock> ) {
136 return std::chrono::duration_cast<Duration>( result );
137 } else {
138#if defined( __cpp_lib_chrono ) and __cpp_lib_chrono >= 201907
139 // We have clock_cast
140 auto const match_duration =
141 std::chrono::time_point_cast<Duration>( result );
142 auto const match_clock =
143 std::chrono::clock_cast<Clock>( match_duration );
144 return match_clock;
145#else
146 // This is a guess and will not be constexpr
147
148 // System epoch is unix epoch on(gcc/clang/msvc)
149 auto const system_epoch = std::chrono::floor<std::chrono::hours>(
150 std::chrono::system_clock::now( ).time_since_epoch( ) +
151 std::chrono::minutes( 30 ) );
152 auto const clock_epoch = std::chrono::floor<std::chrono::hours>(
153 Clock::now( ).time_since_epoch( ) + std::chrono::minutes( 30 ) );
154
155 constexpr auto offset =
156 std::chrono::duration_cast<std::chrono::milliseconds>(
157 clock_epoch - system_epoch );
158 return std::chrono::duration_cast<Duration>( result + offset );
159#endif
160 }
161 }
162
163 struct date_parts {
164 int_least32_t year;
165 uint_least32_t month;
166 uint_least32_t day;
167 };
168
169 template<typename Bounds, std::ptrdiff_t Ex>
171 daw::basic_string_view<char, Bounds, Ex> timestamp_str ) {
172 auto result = date_parts{ 0, 0, 0 };
173 result.day = parse_utils::parse_unsigned<std::uint_least32_t, 2>(
174 std::data( timestamp_str.pop_back( 2U ) ) );
175 if( not parse_utils::is_number( timestamp_str.back( ) ) ) {
176 timestamp_str.remove_suffix( );
177 }
178 result.month = parse_utils::parse_unsigned<std::uint_least32_t, 2>(
179 std::data( timestamp_str.pop_back( 2U ) ) );
180 if( not parse_utils::is_number( timestamp_str.back( ) ) ) {
181 timestamp_str.remove_suffix( );
182 }
183 result.year =
184 datetime_details::parse_number<std::int_least32_t>( timestamp_str );
185 return result;
186 }
187
188 struct time_parts {
189 uint_least32_t hour;
190 uint_least32_t minute;
191 uint_least32_t second;
192 uint_least32_t millisecond;
193 };
194
195 template<typename Bounds, std::ptrdiff_t Ex>
197 daw::basic_string_view<char, Bounds, Ex> timestamp_str ) {
198 auto result = time_parts{ 0, 0, 0, 0 };
199 result.hour = parse_utils::parse_unsigned<std::uint_least32_t, 2>(
200 std::data( timestamp_str.pop_front( 2 ) ) );
201 if( not parse_utils::is_number( timestamp_str.front( ) ) ) {
202 timestamp_str.remove_prefix( );
203 }
204 result.minute = parse_utils::parse_unsigned<std::uint_least32_t, 2>(
205 std::data( timestamp_str.pop_front( 2 ) ) );
206 if( timestamp_str.empty( ) ) {
207 return result;
208 }
209 if( not parse_utils::is_number( timestamp_str.front( ) ) ) {
210 timestamp_str.remove_prefix( );
211 }
212 result.second = parse_utils::parse_unsigned<std::uint_least32_t, 2>(
213 std::data( timestamp_str.pop_front( 2 ) ) );
214 if( timestamp_str.empty( ) ) {
215 return result;
216 }
217 if( not parse_utils::is_number( timestamp_str.front( ) ) ) {
218 timestamp_str.remove_prefix( );
219 }
220 result.millisecond =
221 datetime_details::parse_number<std::uint_least32_t>(
222 timestamp_str.pop_front( 3 ) );
223 return result;
224 }
225
226 template<typename Bounds, std::ptrdiff_t Ex>
227 constexpr std::chrono::time_point<std::chrono::system_clock,
228 std::chrono::milliseconds>
229 parse_iso8601_timestamp( daw::basic_string_view<char, Bounds, Ex> ts ) {
230 constexpr daw::string_view t_str = "T";
231 auto const date_str = ts.pop_front( t_str );
232 if( ts.empty( ) ) {
234 ErrorReason::InvalidTimestamp ); // Invalid timestamp,
235 // missing T separator
236 }
237
238 date_parts const ymd = parse_iso_8601_date( date_str );
239 auto time_str = ts.pop_front( []( char c ) {
240 return not( parse_utils::is_number( c ) | ( c == ':' ) |
241 ( c == '.' ) );
242 } );
243 // TODO: verify or parse timezone
244 time_parts hms = parse_iso_8601_time( time_str );
245 if( not( ts.empty( ) or ts.front( ) == 'Z' ) ) {
246 daw_json_assert( std::size( ts ) == 5 or std::size( ts ) == 6,
247 ErrorReason::InvalidTimestamp );
248 // The format will be (+|-)hh[:]mm
249 bool const sign = ts.front( ) == '+';
250 ts.remove_prefix( );
251 auto hr_offset = parse_utils::parse_unsigned<std::uint_least32_t, 2>(
252 std::data( ts ) );
253 if( ts.front( ) == ':' ) {
254 ts.remove_prefix( );
255 }
256 auto mn_offset = parse_utils::parse_unsigned<std::uint_least32_t, 2>(
257 std::data( ts ) );
258 // Want to subtract offset from current time, we are converting to UTC
259 if( sign ) {
260 // Positive offset
261 hms.hour -= hr_offset;
262 hms.minute -= mn_offset;
263 } else {
264 // Negative offset
265 hms.hour += hr_offset;
266 hms.minute += mn_offset;
267 }
268 }
269 return civil_to_time_point( ymd.year, ymd.month, ymd.day, hms.hour,
270 hms.minute, hms.second, hms.millisecond );
271 }
272 struct ymdhms {
273 std::int_least32_t year;
274 std::uint_least32_t month;
275 std::uint_least32_t day;
276 std::uint_least32_t hour;
277 std::uint_least32_t minute;
278 std::uint_least32_t second;
279 std::uint_least32_t millisecond;
280 };
281
282 template<typename Clock, typename Duration>
284 std::chrono::time_point<Clock, Duration> const &tp ) {
285 auto dur_from_epoch = tp.time_since_epoch( );
286 using Days =
287 std::chrono::duration<std::int_least32_t, std::ratio<86400>>;
288 auto const days_since_epoch =
289 std::chrono::duration_cast<Days>( dur_from_epoch );
290 std::int_least32_t z = days_since_epoch.count( );
291 z += 719468;
292 std::int_least32_t const era = ( z >= 0 ? z : z - 146096 ) / 146097;
293 auto const doe =
294 static_cast<std::uint_least32_t>( z - era * 146097 ); // [0, 146096]
295 std::uint_least32_t const yoe =
296 ( doe - doe / 1460 + doe / 36524 - doe / 146096 ) / 365; // [0, 399]
297 std::int_least32_t const y =
298 static_cast<std::int_least32_t>( yoe ) + era * 400;
299 std::uint_least32_t const doy =
300 doe - ( 365 * yoe + yoe / 4 - yoe / 100 ); // [0, 365]
301 std::uint_least32_t const mp = ( 5 * doy + 2 ) / 153; // [0, 11]
302 std::uint_least32_t const d = doy - ( 153 * mp + 2 ) / 5 + 1; // [1, 31]
303 auto const m = static_cast<std::uint_least32_t>(
304 static_cast<std::int_least32_t>( mp ) +
305 ( static_cast<std::int_least32_t>( mp ) < 10 ? 3 : -9 ) ); // [1, 12]
306
307 dur_from_epoch -= days_since_epoch;
308 auto const hrs =
309 std::chrono::duration_cast<std::chrono::hours>( dur_from_epoch );
310 dur_from_epoch -= hrs;
311 auto const min =
312 std::chrono::duration_cast<std::chrono::minutes>( dur_from_epoch );
313 dur_from_epoch -= min;
314 auto const sec =
315 std::chrono::duration_cast<std::chrono::seconds>( dur_from_epoch );
316 dur_from_epoch -= sec;
317 auto const ms = std::chrono::duration_cast<std::chrono::milliseconds>(
318 dur_from_epoch );
319 return ymdhms{ y + ( m <= 2 ),
320 m,
321 d,
322 static_cast<std::uint_least32_t>( hrs.count( ) ),
323 static_cast<std::uint_least32_t>( min.count( ) ),
324 static_cast<std::uint_least32_t>( sec.count( ) ),
325 static_cast<std::uint_least32_t>( ms.count( ) ) };
326 }
327
328 constexpr std::string_view month_short_name( unsigned m ) {
329 switch( m ) {
330 case 1:
331 return { "Jan" };
332 case 2:
333 return { "Feb" };
334 case 3:
335 return { "Mar" };
336 case 4:
337 return { "Apr" };
338 case 5:
339 return { "May" };
340 case 6:
341 return { "Jun" };
342 case 7:
343 return { "Jul" };
344 case 8:
345 return { "Aug" };
346 case 9:
347 return { "Sep" };
348 case 10:
349 return { "Oct" };
350 case 11:
351 return { "Nov" };
352 case 12:
353 return { "Dec" };
354 default:
355 daw_json_error( ErrorReason::InvalidTimestamp ); // Invalid month
356 }
357 }
358
359 // Formula from
360 // http://howardhinnant.github.io/date_algorithms.html#weekday_from_days
361 template<typename Duration>
362 constexpr std::string_view short_day_of_week(
363 std::chrono::time_point<std::chrono::system_clock, Duration> tp ) {
364 using days = std::chrono::duration<long, std::ratio<86400>>;
365 auto const z =
366 std::chrono::duration_cast<days>( tp.time_since_epoch( ) ).count( );
367 auto const dow = z >= -4L ? ( z + 4L ) % 7L : ( z + 5L ) % 7L + 6L;
368 switch( dow ) {
369 case 0:
370 return { "Sun" };
371 case 1:
372 return { "Mon" };
373 case 2:
374 return { "Tue" };
375 case 3:
376 return { "Wed" };
377 case 4:
378 return { "Thu" };
379 case 5:
380 return { "Fri" };
381 case 6:
382 return { "Sat" };
383 default:
384 daw_json_error( ErrorReason::InvalidTimestamp ); // Invalid month
385 }
386 }
387 static_assert(
389 std::chrono::time_point<std::chrono::system_clock,
390 std::chrono::milliseconds>( ) ) == "Thu" );
391
392 namespace datetime_details {
393 constexpr std::uint_least32_t month2num( std::string_view ts ) {
394 daw_json_assert( std::size( ts ) >= 3,
395 ErrorReason::InvalidTimestamp );
396 auto const b0 = static_cast<std::uint_least32_t>(
397 static_cast<unsigned char>( ts[0] ) );
398 auto const b1 = static_cast<std::uint_least32_t>(
399 static_cast<unsigned char>( ts[1] ) );
400 auto const b2 = static_cast<std::uint_least32_t>(
401 static_cast<unsigned char>( ts[2] ) );
402 return ( b0 << 16U ) | ( b1 << 8U ) | b2;
403 }
404 } // namespace datetime_details
405
406 constexpr unsigned parse_short_month( std::string_view ts ) {
407 // Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec
408 switch( datetime_details::month2num( ts ) ) {
409 case datetime_details::month2num( "Jan" ):
410 return 1;
411 case datetime_details::month2num( "Feb" ):
412 return 2;
413 case datetime_details::month2num( "Mar" ):
414 return 3;
415 case datetime_details::month2num( "Apr" ):
416 return 4;
417 case datetime_details::month2num( "May" ):
418 return 5;
419 case datetime_details::month2num( "Jun" ):
420 return 6;
421 case datetime_details::month2num( "Jul" ):
422 return 7;
423 case datetime_details::month2num( "Aug" ):
424 return 8;
425 case datetime_details::month2num( "Sep" ):
426 return 9;
427 case datetime_details::month2num( "Oct" ):
428 return 10;
429 case datetime_details::month2num( "Nov" ):
430 return 11;
431 case datetime_details::month2num( "Dec" ):
432 return 12;
433 default:
434 daw_json_error( ErrorReason::InvalidTimestamp ); // Invalid month
435 }
436 }
437 } // namespace datetime
438 } // namespace DAW_JSON_VER
439} // namespace daw::json
#define daw_json_assert(Bool,...)
Definition: daw_json_assert.h:179
constexpr Result parse_number(daw::basic_string_view< char, Bounds, Ex > sv)
Definition: daw_json_parse_iso8601_utils.h:62
constexpr std::uint_least32_t month2num(std::string_view ts)
Definition: daw_json_parse_iso8601_utils.h:393
constexpr time_parts parse_iso_8601_time(daw::basic_string_view< char, Bounds, Ex > timestamp_str)
Definition: daw_json_parse_iso8601_utils.h:196
constexpr date_parts parse_iso_8601_date(daw::basic_string_view< char, Bounds, Ex > timestamp_str)
Definition: daw_json_parse_iso8601_utils.h:170
constexpr unsigned parse_short_month(std::string_view ts)
Definition: daw_json_parse_iso8601_utils.h:406
constexpr ymdhms time_point_to_civil(std::chrono::time_point< Clock, Duration > const &tp)
Definition: daw_json_parse_iso8601_utils.h:283
constexpr std::string_view short_day_of_week(std::chrono::time_point< std::chrono::system_clock, Duration > tp)
Definition: daw_json_parse_iso8601_utils.h:362
constexpr std::chrono::time_point< Clock, Duration > civil_to_time_point(std::int_least32_t yr, std::uint_least32_t mo, std::uint_least32_t dy, std::uint_least32_t hr, std::uint_least32_t mn, std::uint_least32_t se, std::uint_least32_t ms)
Definition: daw_json_parse_iso8601_utils.h:89
constexpr std::chrono::time_point< std::chrono::system_clock, std::chrono::milliseconds > parse_iso8601_timestamp(daw::basic_string_view< char, Bounds, Ex > ts)
Definition: daw_json_parse_iso8601_utils.h:229
constexpr std::string_view month_short_name(unsigned m)
Definition: daw_json_parse_iso8601_utils.h:328
static constexpr DAW_ATTRIB_FLATINLINE unsigned parse_digit(char c)
Definition: daw_json_parse_digit.h:19
constexpr Result parse_unsigned2(char const *digit_str)
Definition: daw_json_parse_iso8601_utils.h:40
constexpr Result parse_unsigned(char const *digit_str)
Definition: daw_json_parse_iso8601_utils.h:30
constexpr bool is_number(char c)
Definition: daw_json_parse_iso8601_utils.h:52
DAW_ATTRIB_NOINLINE void daw_json_error(ErrorReason reason)
Definition: daw_json_assert.h:39
Definition: daw_from_json.h:22
Definition: daw_json_parse_iso8601_utils.h:163
uint_least32_t month
Definition: daw_json_parse_iso8601_utils.h:165
uint_least32_t day
Definition: daw_json_parse_iso8601_utils.h:166
int_least32_t year
Definition: daw_json_parse_iso8601_utils.h:164
Definition: daw_json_parse_iso8601_utils.h:188
uint_least32_t hour
Definition: daw_json_parse_iso8601_utils.h:189
uint_least32_t second
Definition: daw_json_parse_iso8601_utils.h:191
uint_least32_t millisecond
Definition: daw_json_parse_iso8601_utils.h:192
uint_least32_t minute
Definition: daw_json_parse_iso8601_utils.h:190
Definition: daw_json_parse_iso8601_utils.h:272
std::int_least32_t year
Definition: daw_json_parse_iso8601_utils.h:273
std::uint_least32_t second
Definition: daw_json_parse_iso8601_utils.h:278
std::uint_least32_t month
Definition: daw_json_parse_iso8601_utils.h:274
std::uint_least32_t hour
Definition: daw_json_parse_iso8601_utils.h:276
std::uint_least32_t day
Definition: daw_json_parse_iso8601_utils.h:275
std::uint_least32_t minute
Definition: daw_json_parse_iso8601_utils.h:277
std::uint_least32_t millisecond
Definition: daw_json_parse_iso8601_utils.h:279
#define DAW_JSON_VER
The version string used in namespace definitions. Must be a valid namespace name.
Definition: version.h:16