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