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