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"
14 #include "daw_json_parse_digit.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 
26 namespace 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 #if false and /* TODO Fix */ defined( __cpp_lib_chrono ) and __cpp_lib_chrono >= 201907
130  // We have clock_cast
131  return std::chrono::duration_cast<Duration>(
132  std::chrono::clock_cast<Clock>( result ) );
133 #else
134  if constexpr( std::is_same<Clock, std::chrono::system_clock>::value ) {
135  return result;
136  } else {
137  // This is a guess and will not be constexpr
138 
139  // System epoch is unix epoch on(gcc/clang/msvc)
140  auto const system_epoch = std::chrono::floor<std::chrono::hours>(
141  std::chrono::system_clock::now( ).time_since_epoch( ) +
142  std::chrono::minutes( 30 ) );
143  auto const clock_epoch = std::chrono::floor<std::chrono::hours>(
144  Clock::now( ).time_since_epoch( ) + std::chrono::minutes( 30 ) );
145 
146  constexpr auto offset =
147  std::chrono::duration_cast<std::chrono::milliseconds>(
148  clock_epoch - system_epoch );
149  return std::chrono::duration_cast<Duration>( result + offset );
150  }
151 #endif
152  }
153 
154  struct date_parts {
155  int_least32_t year;
156  uint_least32_t month;
157  uint_least32_t day;
158  };
159 
160  template<typename Bounds, std::ptrdiff_t Ex>
162  daw::basic_string_view<char, Bounds, Ex> timestamp_str ) {
163  auto result = date_parts{ 0, 0, 0 };
164  result.day = parse_utils::parse_unsigned<std::uint_least32_t, 2>(
165  std::data( timestamp_str.pop_back( 2U ) ) );
166  if( not parse_utils::is_number( timestamp_str.back( ) ) ) {
167  timestamp_str.remove_suffix( );
168  }
169  result.month = parse_utils::parse_unsigned<std::uint_least32_t, 2>(
170  std::data( timestamp_str.pop_back( 2U ) ) );
171  if( not parse_utils::is_number( timestamp_str.back( ) ) ) {
172  timestamp_str.remove_suffix( );
173  }
174  result.year =
175  datetime_details::parse_number<std::int_least32_t>( timestamp_str );
176  return result;
177  }
178 
179  struct time_parts {
180  uint_least32_t hour;
181  uint_least32_t minute;
182  uint_least32_t second;
183  uint_least32_t millisecond;
184  };
185 
186  template<typename Bounds, std::ptrdiff_t Ex>
188  daw::basic_string_view<char, Bounds, Ex> timestamp_str ) {
189  auto result = time_parts{ 0, 0, 0, 0 };
190  result.hour = parse_utils::parse_unsigned<std::uint_least32_t, 2>(
191  std::data( timestamp_str.pop_front( 2 ) ) );
192  if( not parse_utils::is_number( timestamp_str.front( ) ) ) {
193  timestamp_str.remove_prefix( );
194  }
195  result.minute = parse_utils::parse_unsigned<std::uint_least32_t, 2>(
196  std::data( timestamp_str.pop_front( 2 ) ) );
197  if( timestamp_str.empty( ) ) {
198  return result;
199  }
200  if( not parse_utils::is_number( timestamp_str.front( ) ) ) {
201  timestamp_str.remove_prefix( );
202  }
203  result.second = parse_utils::parse_unsigned<std::uint_least32_t, 2>(
204  std::data( timestamp_str.pop_front( 2 ) ) );
205  if( timestamp_str.empty( ) ) {
206  return result;
207  }
208  if( not parse_utils::is_number( timestamp_str.front( ) ) ) {
209  timestamp_str.remove_prefix( );
210  }
211  result.millisecond =
212  datetime_details::parse_number<std::uint_least32_t>(
213  timestamp_str.pop_front( 3 ) );
214  return result;
215  }
216 
217  template<typename Bounds, std::ptrdiff_t Ex>
218  constexpr std::chrono::time_point<std::chrono::system_clock,
219  std::chrono::milliseconds>
220  parse_iso8601_timestamp( daw::basic_string_view<char, Bounds, Ex> ts ) {
221  constexpr daw::string_view t_str = "T";
222  auto const date_str = ts.pop_front( t_str );
223  if( ts.empty( ) ) {
225  ErrorReason::InvalidTimestamp ); // Invalid timestamp,
226  // missing T separator
227  }
228 
229  date_parts const ymd = parse_iso_8601_date( date_str );
230  auto time_str = ts.pop_front( []( char c ) {
231  return not( parse_utils::is_number( c ) | ( c == ':' ) |
232  ( c == '.' ) );
233  } );
234  // TODO: verify or parse timezone
235  time_parts hms = parse_iso_8601_time( time_str );
236  if( not( ts.empty( ) or ts.front( ) == 'Z' ) ) {
237  daw_json_assert( std::size( ts ) == 5 or std::size( ts ) == 6,
238  ErrorReason::InvalidTimestamp );
239  // The format will be (+|-)hh[:]mm
240  bool const sign = ts.front( ) == '+';
241  ts.remove_prefix( );
242  auto hr_offset = parse_utils::parse_unsigned<std::uint_least32_t, 2>(
243  std::data( ts ) );
244  if( ts.front( ) == ':' ) {
245  ts.remove_prefix( );
246  }
247  auto mn_offset = parse_utils::parse_unsigned<std::uint_least32_t, 2>(
248  std::data( ts ) );
249  // Want to subtract offset from current time, we are converting to UTC
250  if( sign ) {
251  // Positive offset
252  hms.hour -= hr_offset;
253  hms.minute -= mn_offset;
254  } else {
255  // Negative offset
256  hms.hour += hr_offset;
257  hms.minute += mn_offset;
258  }
259  }
260  return civil_to_time_point( ymd.year, ymd.month, ymd.day, hms.hour,
261  hms.minute, hms.second, hms.millisecond );
262  }
263  struct ymdhms {
264  std::int_least32_t year;
265  std::uint_least32_t month;
266  std::uint_least32_t day;
267  std::uint_least32_t hour;
268  std::uint_least32_t minute;
269  std::uint_least32_t second;
270  std::uint_least32_t millisecond;
271  };
272 
273  template<typename Clock, typename Duration>
275  std::chrono::time_point<Clock, Duration> const &tp ) {
276  auto dur_from_epoch = tp.time_since_epoch( );
277  using Days =
278  std::chrono::duration<std::int_least32_t, std::ratio<86400>>;
279  auto const days_since_epoch =
280  std::chrono::duration_cast<Days>( dur_from_epoch );
281  std::int_least32_t z = days_since_epoch.count( );
282  z += 719468;
283  std::int_least32_t const era = ( z >= 0 ? z : z - 146096 ) / 146097;
284  auto const doe =
285  static_cast<std::uint_least32_t>( z - era * 146097 ); // [0, 146096]
286  std::uint_least32_t const yoe =
287  ( doe - doe / 1460 + doe / 36524 - doe / 146096 ) / 365; // [0, 399]
288  std::int_least32_t const y =
289  static_cast<std::int_least32_t>( yoe ) + era * 400;
290  std::uint_least32_t const doy =
291  doe - ( 365 * yoe + yoe / 4 - yoe / 100 ); // [0, 365]
292  std::uint_least32_t const mp = ( 5 * doy + 2 ) / 153; // [0, 11]
293  std::uint_least32_t const d = doy - ( 153 * mp + 2 ) / 5 + 1; // [1, 31]
294  auto const m = static_cast<std::uint_least32_t>(
295  static_cast<std::int_least32_t>( mp ) +
296  ( static_cast<std::int_least32_t>( mp ) < 10 ? 3 : -9 ) ); // [1, 12]
297 
298  dur_from_epoch -= days_since_epoch;
299  auto const hrs =
300  std::chrono::duration_cast<std::chrono::hours>( dur_from_epoch );
301  dur_from_epoch -= hrs;
302  auto const min =
303  std::chrono::duration_cast<std::chrono::minutes>( dur_from_epoch );
304  dur_from_epoch -= min;
305  auto const sec =
306  std::chrono::duration_cast<std::chrono::seconds>( dur_from_epoch );
307  dur_from_epoch -= sec;
308  auto const ms = std::chrono::duration_cast<std::chrono::milliseconds>(
309  dur_from_epoch );
310  return ymdhms{ y + ( m <= 2 ),
311  m,
312  d,
313  static_cast<std::uint_least32_t>( hrs.count( ) ),
314  static_cast<std::uint_least32_t>( min.count( ) ),
315  static_cast<std::uint_least32_t>( sec.count( ) ),
316  static_cast<std::uint_least32_t>( ms.count( ) ) };
317  }
318 
319  constexpr std::string_view month_short_name( unsigned m ) {
320  switch( m ) {
321  case 1:
322  return { "Jan" };
323  case 2:
324  return { "Feb" };
325  case 3:
326  return { "Mar" };
327  case 4:
328  return { "Apr" };
329  case 5:
330  return { "May" };
331  case 6:
332  return { "Jun" };
333  case 7:
334  return { "Jul" };
335  case 8:
336  return { "Aug" };
337  case 9:
338  return { "Sep" };
339  case 10:
340  return { "Oct" };
341  case 11:
342  return { "Nov" };
343  case 12:
344  return { "Dec" };
345  default:
346  daw_json_error( ErrorReason::InvalidTimestamp ); // Invalid month
347  }
348  }
349 
350  // Formula from
351  // http://howardhinnant.github.io/date_algorithms.html#weekday_from_days
352  template<typename Duration>
353  constexpr std::string_view short_day_of_week(
354  std::chrono::time_point<std::chrono::system_clock, Duration> tp ) {
355  using days = std::chrono::duration<long, std::ratio<86400>>;
356  auto const z =
357  std::chrono::duration_cast<days>( tp.time_since_epoch( ) ).count( );
358  auto const dow = z >= -4L ? ( z + 4L ) % 7L : ( z + 5L ) % 7L + 6L;
359  switch( dow ) {
360  case 0:
361  return { "Sun" };
362  case 1:
363  return { "Mon" };
364  case 2:
365  return { "Tue" };
366  case 3:
367  return { "Wed" };
368  case 4:
369  return { "Thu" };
370  case 5:
371  return { "Fri" };
372  case 6:
373  return { "Sat" };
374  default:
375  daw_json_error( ErrorReason::InvalidTimestamp ); // Invalid month
376  }
377  }
378  static_assert(
380  std::chrono::time_point<std::chrono::system_clock,
381  std::chrono::milliseconds>( ) ) == "Thu" );
382 
383  namespace datetime_details {
384  constexpr std::uint_least32_t month2num( std::string_view ts ) {
385  daw_json_assert( std::size( ts ) >= 3,
386  ErrorReason::InvalidTimestamp );
387  auto const b0 = static_cast<std::uint_least32_t>(
388  static_cast<unsigned char>( ts[0] ) );
389  auto const b1 = static_cast<std::uint_least32_t>(
390  static_cast<unsigned char>( ts[1] ) );
391  auto const b2 = static_cast<std::uint_least32_t>(
392  static_cast<unsigned char>( ts[2] ) );
393  return ( b0 << 16U ) | ( b1 << 8U ) | b2;
394  }
395  } // namespace datetime_details
396 
397  constexpr unsigned parse_short_month( std::string_view ts ) {
398  // Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec
399  switch( datetime_details::month2num( ts ) ) {
400  case datetime_details::month2num( "Jan" ):
401  return 1;
402  case datetime_details::month2num( "Feb" ):
403  return 2;
404  case datetime_details::month2num( "Mar" ):
405  return 3;
406  case datetime_details::month2num( "Apr" ):
407  return 4;
408  case datetime_details::month2num( "May" ):
409  return 5;
410  case datetime_details::month2num( "Jun" ):
411  return 6;
412  case datetime_details::month2num( "Jul" ):
413  return 7;
414  case datetime_details::month2num( "Aug" ):
415  return 8;
416  case datetime_details::month2num( "Sep" ):
417  return 9;
418  case datetime_details::month2num( "Oct" ):
419  return 10;
420  case datetime_details::month2num( "Nov" ):
421  return 11;
422  case datetime_details::month2num( "Dec" ):
423  return 12;
424  default:
425  daw_json_error( ErrorReason::InvalidTimestamp ); // Invalid month
426  }
427  }
428  } // namespace datetime
429  } // namespace DAW_JSON_VER
430 } // namespace daw::json
#define daw_json_assert(Bool,...)
Definition: daw_json_assert.h:178
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:384
constexpr time_parts parse_iso_8601_time(daw::basic_string_view< char, Bounds, Ex > timestamp_str)
Definition: daw_json_parse_iso8601_utils.h:187
constexpr date_parts parse_iso_8601_date(daw::basic_string_view< char, Bounds, Ex > timestamp_str)
Definition: daw_json_parse_iso8601_utils.h:161
constexpr unsigned parse_short_month(std::string_view ts)
Definition: daw_json_parse_iso8601_utils.h:397
constexpr ymdhms time_point_to_civil(std::chrono::time_point< Clock, Duration > const &tp)
Definition: daw_json_parse_iso8601_utils.h:274
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::string_view short_day_of_week(std::chrono::time_point< std::chrono::system_clock, Duration > tp)
Definition: daw_json_parse_iso8601_utils.h:353
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:220
constexpr std::string_view month_short_name(unsigned m)
Definition: daw_json_parse_iso8601_utils.h:319
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:154
uint_least32_t month
Definition: daw_json_parse_iso8601_utils.h:156
uint_least32_t day
Definition: daw_json_parse_iso8601_utils.h:157
int_least32_t year
Definition: daw_json_parse_iso8601_utils.h:155
Definition: daw_json_parse_iso8601_utils.h:179
uint_least32_t hour
Definition: daw_json_parse_iso8601_utils.h:180
uint_least32_t second
Definition: daw_json_parse_iso8601_utils.h:182
uint_least32_t millisecond
Definition: daw_json_parse_iso8601_utils.h:183
uint_least32_t minute
Definition: daw_json_parse_iso8601_utils.h:181
Definition: daw_json_parse_iso8601_utils.h:263
std::int_least32_t year
Definition: daw_json_parse_iso8601_utils.h:264
std::uint_least32_t second
Definition: daw_json_parse_iso8601_utils.h:269
std::uint_least32_t month
Definition: daw_json_parse_iso8601_utils.h:265
std::uint_least32_t hour
Definition: daw_json_parse_iso8601_utils.h:267
std::uint_least32_t day
Definition: daw_json_parse_iso8601_utils.h:266
std::uint_least32_t minute
Definition: daw_json_parse_iso8601_utils.h:268
std::uint_least32_t millisecond
Definition: daw_json_parse_iso8601_utils.h:270
#define DAW_JSON_VER
Definition: version.h:11