Time Shield Library 2.0.0
C++ library for working with time
Loading...
Searching...
No Matches
time_format_parser.hpp
Go to the documentation of this file.
1// SPDX-License-Identifier: MIT
2#pragma once
3#ifndef TIME_SHIELD_HEADER_TEXT_TIME_FORMAT_PARSER_HPP_INCLUDED
4#define TIME_SHIELD_HEADER_TEXT_TIME_FORMAT_PARSER_HPP_INCLUDED
5
8
11
12#include <cstddef>
13#include <cstdint>
14#include <cstdlib>
15#include <cstring>
16#include <string>
17
18#if __cplusplus >= 201703L
19# include <string_view>
20#endif
21
22namespace time_shield {
23
24 namespace detail {
25 namespace format_parse {
26
67
69 FormatParseState state;
70 state.has_tz = false;
71 state.has_year = false;
72 state.has_century = false;
73 state.has_two_digit_year = false;
74 state.has_iso_week_year = false;
75 state.has_iso_week_two_digit_year = false;
76 state.has_iso_week = false;
77 state.has_month = false;
78 state.has_day = false;
79 state.has_day_of_year = false;
80 state.has_hour24 = false;
81 state.has_hour12 = false;
82 state.has_minute = false;
83 state.has_second = false;
84 state.has_millisecond = false;
85 state.has_meridiem = false;
86 state.is_pm = false;
87 state.has_weekday = false;
88 state.has_iso_weekday = false;
89 state.has_unix_seconds = false;
90 state.year = 0;
91 state.iso_week_year = 0;
92 state.century = 0;
93 state.two_digit_year = 0;
95 state.iso_week = 0;
96 state.month = 0;
97 state.day = 0;
98 state.day_of_year = 0;
99 state.hour24 = 0;
100 state.hour12 = 0;
101 state.minute = 0;
102 state.second = 0;
103 state.millisecond = 0;
104 state.weekday = 0;
105 state.iso_weekday = 0;
106 state.unix_seconds = 0;
107 state.tz = create_time_zone_struct(0, 0, true);
108 return state;
109 }
110
111 TIME_SHIELD_CONSTEXPR inline bool is_ascii_digit(char c) noexcept {
112 return c >= '0' && c <= '9';
113 }
114
115 inline bool match_literal(const char*& p, const char* end, char expected) noexcept {
116 if (p >= end || *p != expected) {
117 return false;
118 }
119 ++p;
120 return true;
121 }
122
123 inline bool match_literal(const char*& p, const char* end, const char* literal) noexcept {
124 if (!literal) {
125 return false;
126 }
127 while (*literal) {
128 if (p >= end || *p != *literal) {
129 return false;
130 }
131 ++p;
132 ++literal;
133 }
134 return true;
135 }
136
137 inline bool parse_exact_2digits(const char*& p, const char* end, int& out) noexcept {
138 if (end - p < 2 || !is_ascii_digit(p[0]) || !is_ascii_digit(p[1])) {
139 return false;
140 }
141 out = (p[0] - '0') * 10 + (p[1] - '0');
142 p += 2;
143 return true;
144 }
145
147 const char*& p,
148 const char* end,
149 int min_digits,
150 int max_digits,
151 int64_t& out) noexcept {
152 const char* start = p;
153 int digits = 0;
154 int64_t value = 0;
155 while (p < end && digits < max_digits && is_ascii_digit(*p)) {
156 value = value * 10 + static_cast<int64_t>(*p - '0');
157 ++p;
158 ++digits;
159 }
160 if (digits < min_digits) {
161 p = start;
162 return false;
163 }
164 out = value;
165 return true;
166 }
167
169 const char*& p,
170 const char* end,
171 int min_digits,
172 int max_digits,
173 int64_t& out) noexcept {
174 const char* start = p;
175 bool negative = false;
176 if (p < end && (*p == '+' || *p == '-')) {
177 negative = (*p == '-');
178 ++p;
179 }
180 int64_t value = 0;
181 if (!parse_unsigned_digits(p, end, min_digits, max_digits, value)) {
182 p = start;
183 return false;
184 }
185 out = negative ? -value : value;
186 return true;
187 }
188
189 inline bool parse_space_padded_2digits(const char*& p, const char* end, int& out) noexcept {
190 if (end - p < 2) {
191 return false;
192 }
193 if (p[0] == ' ' && is_ascii_digit(p[1])) {
194 out = p[1] - '0';
195 p += 2;
196 return true;
197 }
198 return parse_exact_2digits(p, end, out);
199 }
200
201 inline bool parse_meridiem(const char*& p, const char* end, bool uppercase, bool& is_pm) noexcept {
202 if (end - p < 2) {
203 return false;
204 }
205 if (uppercase) {
206 if (p[0] == 'A' && p[1] == 'M') {
207 is_pm = false;
208 } else if (p[0] == 'P' && p[1] == 'M') {
209 is_pm = true;
210 } else {
211 return false;
212 }
213 } else {
214 if (p[0] == 'a' && p[1] == 'm') {
215 is_pm = false;
216 } else if (p[0] == 'p' && p[1] == 'm') {
217 is_pm = true;
218 } else {
219 return false;
220 }
221 }
222 p += 2;
223 return true;
224 }
225
226 inline bool match_name_token(
227 const char*& p,
228 const char* end,
229 const char* const* names,
230 std::size_t count,
231 int index_base,
232 int& out) noexcept {
233 for (std::size_t i = 0; i < count; ++i) {
234 const char* name = names[i];
235 const std::size_t len = std::strlen(name);
236 if (static_cast<std::size_t>(end - p) < len) {
237 continue;
238 }
239 bool matched = true;
240 for (std::size_t k = 0; k < len; ++k) {
241 if (p[k] != name[k]) {
242 matched = false;
243 break;
244 }
245 }
246 if (matched) {
247 p += static_cast<std::ptrdiff_t>(len);
248 out = static_cast<int>(i) + index_base;
249 return true;
250 }
251 }
252 return false;
253 }
254
255 inline bool parse_month_token(const char*& p, const char* end, FormatType format, int& month) noexcept {
256 static const char* const uppercase_names[] = {
257 "JAN", "FEB", "MAR", "APR", "MAY", "JUN",
258 "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"
259 };
260 static const char* const short_names[] = {
261 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
262 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
263 };
264 static const char* const full_names[] = {
265 "January", "February", "March", "April", "May", "June",
266 "July", "August", "September", "October", "November", "December"
267 };
268 switch (format) {
269 case UPPERCASE_NAME:
270 return match_name_token(p, end, uppercase_names, 12, 1, month);
271 case SHORT_NAME:
272 return match_name_token(p, end, short_names, 12, 1, month);
273 case FULL_NAME:
274 return match_name_token(p, end, full_names, 12, 1, month);
275 default:
276 return false;
277 }
278 }
279
280 inline bool parse_weekday_token(const char*& p, const char* end, FormatType format, int& weekday) noexcept {
281 static const char* const uppercase_names[] = {
282 "SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"
283 };
284 static const char* const short_names[] = {
285 "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
286 };
287 static const char* const full_names[] = {
288 "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"
289 };
290 switch (format) {
291 case UPPERCASE_NAME:
292 return match_name_token(p, end, uppercase_names, 7, 0, weekday);
293 case SHORT_NAME:
294 return match_name_token(p, end, short_names, 7, 0, weekday);
295 case FULL_NAME:
296 return match_name_token(p, end, full_names, 7, 0, weekday);
297 default:
298 return false;
299 }
300 }
301
304 inline bool parse_tz_offset_token(const char*& p, const char* end, TimeZoneStruct& tz) noexcept {
305 if (end - p < 5 || (*p != '+' && *p != '-')) {
306 return false;
307 }
308
309 tz.is_positive = (*p == '+');
310 ++p;
311 if (!parse_exact_2digits(p, end, tz.hour)) {
312 return false;
313 }
314 if (p < end && *p == ':') {
315 ++p;
316 }
317 if (!parse_exact_2digits(p, end, tz.min)) {
318 return false;
319 }
320 return is_valid_time_zone_offset(tz);
321 }
322
323 inline bool assign_int_field(bool& has_field, int& field, int value) noexcept {
324 if (has_field && field != value) {
325 return false;
326 }
327 has_field = true;
328 field = value;
329 return true;
330 }
331
332 inline bool assign_year_field(bool& has_field, year_t& field, year_t value) noexcept {
333 if (has_field && field != value) {
334 return false;
335 }
336 has_field = true;
337 field = value;
338 return true;
339 }
340
341 inline bool has_iso_week_date_fields(const FormatParseState& state) noexcept {
342 return state.has_iso_week_year
343 || state.has_iso_week_two_digit_year
344 || state.has_iso_week;
345 }
346
347 inline bool has_gregorian_date_fields(const FormatParseState& state) noexcept {
348 return state.has_year
349 || state.has_century
350 || state.has_two_digit_year
351 || state.has_month
352 || state.has_day
353 || state.has_day_of_year;
354 }
355
356 inline int last_two_digits_of_year(year_t year) noexcept {
357 const int value = static_cast<int>(year % 100);
358 return value < 0 ? -value : value;
359 }
360
361 inline bool resolve_day_of_year(year_t year, int day_of_year, int& month, int& day) noexcept {
362 if (day_of_year < 1) {
363 return false;
364 }
365 const bool is_leap = is_leap_year_date(year);
366 const int max_day = is_leap ? 366 : 365;
367 if (day_of_year > max_day) {
368 return false;
369 }
370
371 static const int days_per_month[] = { 31,28,31,30,31,30,31,31,30,31,30,31 };
372 int remaining = day_of_year;
373 month = 1;
374 while (month <= 12) {
375 int days = days_per_month[month - 1];
376 if (month == 2 && is_leap) {
377 ++days;
378 }
379 if (remaining <= days) {
380 day = remaining;
381 return true;
382 }
383 remaining -= days;
384 ++month;
385 }
386 return false;
387 }
388
389 inline int compute_day_of_year(year_t year, int month, int day) noexcept {
390 static const int day_offsets[] = { 0,31,59,90,120,151,181,212,243,273,304,334 };
391 int result = day_offsets[month - 1] + day;
392 if (month > 2 && is_leap_year_date(year)) {
393 ++result;
394 }
395 return result;
396 }
397
398 inline bool parse_compact_extended_year(const char*& p, const char* end, year_t& out) noexcept {
399 const char* start = p;
400 bool negative = false;
401 if (p < end && (*p == '+' || *p == '-')) {
402 negative = (*p == '-');
403 ++p;
404 }
405
406 int64_t head = 0;
407 if (!parse_unsigned_digits(p, end, 1, 18, head)) {
408 p = start;
409 return false;
410 }
411
412 int64_t year_value = 0;
413 if (p < end && *p == 'M') {
414 ++p;
415 int64_t millennia = 0;
416 if (!parse_unsigned_digits(p, end, 1, 18, millennia)) {
417 p = start;
418 return false;
419 }
420 int64_t tail = 0;
421 if (p < end && *p == 'K') {
422 ++p;
423 if (!parse_unsigned_digits(p, end, 3, 3, tail)) {
424 p = start;
425 return false;
426 }
427 year_value = head * 1000000LL + millennia * 1000LL + tail;
428 } else {
429 year_value = head * 1000000LL + millennia;
430 }
431 } else if (p < end && *p == 'K') {
432 ++p;
433 int64_t tail = 0;
434 if (!parse_unsigned_digits(p, end, 3, 3, tail)) {
435 p = start;
436 return false;
437 }
438 year_value = head * 1000LL + tail;
439 } else {
440 year_value = head;
441 }
442
443 out = static_cast<year_t>(negative ? -year_value : year_value);
444 return true;
445 }
446
447 inline bool parse_format_sequence(
448 const char*& p,
449 const char* end,
450 const char* format_data,
451 std::size_t format_size,
452 FormatParseState& state) noexcept;
453
455 const char*& p,
456 const char* end,
457 char token,
458 std::size_t repeat_count,
459 FormatParseState& state) noexcept {
460 int value = 0;
461 int64_t wide_value = 0;
462 switch (token) {
463 case 'a':
464 return repeat_count == 1
465 && parse_weekday_token(p, end, SHORT_NAME, value)
466 && assign_int_field(state.has_weekday, state.weekday, value);
467 case 'A':
468 return repeat_count == 1
469 && parse_weekday_token(p, end, FULL_NAME, value)
470 && assign_int_field(state.has_weekday, state.weekday, value);
471 case 'b':
472 return repeat_count == 1
473 && parse_month_token(p, end, SHORT_NAME, value)
474 && assign_int_field(state.has_month, state.month, value);
475 case 'B':
476 return repeat_count == 1
477 && parse_month_token(p, end, FULL_NAME, value)
478 && assign_int_field(state.has_month, state.month, value);
479 case 'c':
480 return repeat_count == 1
481 && parse_format_sequence(p, end, "%a %b %e %H:%M:%S %Y", 20, state);
482 case 'C':
483 if (repeat_count != 1 || !parse_unsigned_digits(p, end, 1, 6, wide_value)) {
484 return false;
485 }
486 return assign_int_field(state.has_century, state.century, static_cast<int>(wide_value));
487 case 'd':
488 if (repeat_count >= 2 || !parse_exact_2digits(p, end, value)) {
489 return false;
490 }
491 return assign_int_field(state.has_day, state.day, value);
492 case 'D':
493 if (repeat_count == 1) {
494 return parse_format_sequence(p, end, "%m/%d/%y", 8, state);
495 }
496 if (repeat_count == 2) {
497 if (!parse_exact_2digits(p, end, value)) {
498 return false;
499 }
500 return assign_int_field(state.has_day, state.day, value);
501 }
502 return false;
503 case 'e':
504 if (repeat_count != 1 || !parse_space_padded_2digits(p, end, value)) {
505 return false;
506 }
507 return assign_int_field(state.has_day, state.day, value);
508 case 'F':
509 return repeat_count == 1
510 && parse_format_sequence(p, end, "%Y-%m-%d", 8, state);
511 case 'g':
512 if (repeat_count != 1 || !parse_unsigned_digits(p, end, 2, 2, wide_value)) {
513 return false;
514 }
515 return assign_int_field(state.has_iso_week_two_digit_year, state.iso_week_two_digit_year, static_cast<int>(wide_value));
516 case 'G':
517 if (repeat_count != 1 || !parse_signed_digits(p, end, 1, 18, wide_value)) {
518 return false;
519 }
520 return assign_year_field(state.has_iso_week_year, state.iso_week_year, static_cast<year_t>(wide_value));
521 case 'H':
522 if (repeat_count > 2 || !parse_exact_2digits(p, end, value)) {
523 return false;
524 }
525 return assign_int_field(state.has_hour24, state.hour24, value);
526 case 'h':
527 if (repeat_count == 2) {
528 if (!parse_exact_2digits(p, end, value)) {
529 return false;
530 }
531 return assign_int_field(state.has_hour24, state.hour24, value);
532 }
533 return repeat_count == 1
534 && parse_month_token(p, end, SHORT_NAME, value)
535 && assign_int_field(state.has_month, state.month, value);
536 case 'I':
537 if (repeat_count != 1 || !parse_exact_2digits(p, end, value)) {
538 return false;
539 }
540 return assign_int_field(state.has_hour12, state.hour12, value);
541 case 'j':
542 if (repeat_count != 1 || !parse_unsigned_digits(p, end, 3, 3, wide_value)) {
543 return false;
544 }
545 return assign_int_field(state.has_day_of_year, state.day_of_year, static_cast<int>(wide_value));
546 case 'k':
547 if (repeat_count != 1 || !parse_space_padded_2digits(p, end, value)) {
548 return false;
549 }
550 return assign_int_field(state.has_hour24, state.hour24, value);
551 case 'l':
552 if (repeat_count != 1 || !parse_space_padded_2digits(p, end, value)) {
553 return false;
554 }
555 return assign_int_field(state.has_hour12, state.hour12, value);
556 case 'm':
557 if (repeat_count == 1) {
558 if (!parse_exact_2digits(p, end, value)) {
559 return false;
560 }
561 return assign_int_field(state.has_month, state.month, value);
562 }
563 if (repeat_count == 2) {
564 if (!parse_exact_2digits(p, end, value)) {
565 return false;
566 }
567 return assign_int_field(state.has_minute, state.minute, value);
568 }
569 return false;
570 case 'M':
571 if (repeat_count == 1) {
572 if (!parse_exact_2digits(p, end, value)) {
573 return false;
574 }
575 return assign_int_field(state.has_minute, state.minute, value);
576 }
577 if (repeat_count == 2) {
578 if (!parse_exact_2digits(p, end, value)) {
579 return false;
580 }
581 return assign_int_field(state.has_month, state.month, value);
582 }
583 if (repeat_count == 3) {
584 return parse_month_token(p, end, UPPERCASE_NAME, value)
585 && assign_int_field(state.has_month, state.month, value);
586 }
587 return false;
588 case 'n':
589 return repeat_count == 1 && match_literal(p, end, '\n');
590 case 'p':
591 if (repeat_count != 1 || !parse_meridiem(p, end, true, state.is_pm)) {
592 return false;
593 }
594 state.has_meridiem = true;
595 return true;
596 case 'P':
597 if (repeat_count != 1 || !parse_meridiem(p, end, false, state.is_pm)) {
598 return false;
599 }
600 state.has_meridiem = true;
601 return true;
602 case 'r':
603 return repeat_count == 1
604 && parse_format_sequence(p, end, "%I:%M:%S %p", 11, state);
605 case 'R':
606 return repeat_count == 1
607 && parse_format_sequence(p, end, "%H:%M", 5, state);
608 case 's':
609 if (repeat_count == 1) {
610 if (!parse_signed_digits(p, end, 1, 18, wide_value)) {
611 return false;
612 }
613 state.has_unix_seconds = true;
614 state.unix_seconds = static_cast<ts_t>(wide_value);
615 return true;
616 }
617 if (repeat_count == 3) {
618 if (!parse_unsigned_digits(p, end, 1, 3, wide_value)) {
619 return false;
620 }
621 return assign_int_field(state.has_millisecond, state.millisecond, static_cast<int>(wide_value));
622 }
623 return false;
624 case 'S':
625 if (repeat_count <= 2) {
626 if (!parse_exact_2digits(p, end, value)) {
627 return false;
628 }
629 return assign_int_field(state.has_second, state.second, value);
630 }
631 if (repeat_count == 3) {
632 if (!parse_unsigned_digits(p, end, 1, 3, wide_value)) {
633 return false;
634 }
635 return assign_int_field(state.has_millisecond, state.millisecond, static_cast<int>(wide_value));
636 }
637 return false;
638 case 't':
639 return repeat_count == 1 && match_literal(p, end, '\t');
640 case 'T':
641 return repeat_count == 1
642 && parse_format_sequence(p, end, "%H:%M:%S", 8, state);
643 case 'u':
644 if (repeat_count != 1 || !parse_unsigned_digits(p, end, 1, 1, wide_value)) {
645 return false;
646 }
647 return assign_int_field(state.has_iso_weekday, state.iso_weekday, static_cast<int>(wide_value));
648 case 'w':
649 if (repeat_count == 1) {
650 if (!parse_unsigned_digits(p, end, 1, 1, wide_value)) {
651 return false;
652 }
653 return assign_int_field(state.has_weekday, state.weekday, static_cast<int>(wide_value));
654 }
655 if (repeat_count == 3) {
656 return parse_weekday_token(p, end, SHORT_NAME, value)
657 && assign_int_field(state.has_weekday, state.weekday, value);
658 }
659 return false;
660 case 'W':
661 if (repeat_count == 3) {
662 return parse_weekday_token(p, end, UPPERCASE_NAME, value)
663 && assign_int_field(state.has_weekday, state.weekday, value);
664 }
665 return false;
666 case 'V':
667 if (repeat_count != 1 || !parse_exact_2digits(p, end, value)) {
668 return false;
669 }
670 return assign_int_field(state.has_iso_week, state.iso_week, value);
671 case 'y':
672 if (repeat_count != 1 || !parse_unsigned_digits(p, end, 1, 2, wide_value)) {
673 return false;
674 }
675 return assign_int_field(state.has_two_digit_year, state.two_digit_year, static_cast<int>(wide_value));
676 case 'Y':
677 if (repeat_count == 1) {
678 if (!parse_signed_digits(p, end, 1, 18, wide_value)) {
679 return false;
680 }
681 return assign_year_field(state.has_year, state.year, static_cast<year_t>(wide_value));
682 }
683 if (repeat_count == 2) {
684 if (!parse_unsigned_digits(p, end, 2, 2, wide_value)) {
685 return false;
686 }
687 return assign_int_field(state.has_two_digit_year, state.two_digit_year, static_cast<int>(wide_value));
688 }
689 if (repeat_count == 4) {
690 if (!parse_signed_digits(p, end, 4, 4, wide_value)) {
691 return false;
692 }
693 return assign_year_field(state.has_year, state.year, static_cast<year_t>(wide_value));
694 }
695 if (repeat_count == 6) {
696 year_t parsed_year = 0;
697 if (!parse_compact_extended_year(p, end, parsed_year)) {
698 return false;
699 }
700 return assign_year_field(state.has_year, state.year, parsed_year);
701 }
702 return false;
703 case 'z':
704 if (repeat_count != 1 || !parse_tz_offset_token(p, end, state.tz)) {
705 return false;
706 }
707 state.has_tz = true;
708 return true;
709 case 'Z':
710 if (repeat_count != 1 || !match_literal(p, end, "UTC")) {
711 return false;
712 }
713 state.tz = create_time_zone_struct(0, 0, true);
714 state.has_tz = true;
715 return true;
716 default:
717 return false;
718 }
719 }
720
722 const char*& p,
723 const char* end,
724 const char* format_data,
725 std::size_t format_size,
726 FormatParseState& state) noexcept {
727 bool is_command = false;
728 std::size_t repeat_count = 0;
729 char last_char = 0;
730
731 for (std::size_t i = 0; i < format_size; ++i) {
732 const char current_char = format_data[i];
733 if (!is_command) {
734 if (current_char == '%') {
735 ++repeat_count;
736 if (repeat_count == 2) {
737 if (!match_literal(p, end, '%')) {
738 return false;
739 }
740 repeat_count = 0;
741 }
742 continue;
743 }
744 if (!repeat_count) {
745 if (!match_literal(p, end, current_char)) {
746 return false;
747 }
748 continue;
749 }
750 last_char = current_char;
751 is_command = true;
752 continue;
753 }
754 if (last_char == current_char) {
755 ++repeat_count;
756 continue;
757 }
758 if (!parse_format_token(p, end, last_char, repeat_count, state)) {
759 return false;
760 }
761 repeat_count = 0;
762 is_command = false;
763 --i;
764 }
765
766 if (is_command) {
767 if (!parse_format_token(p, end, last_char, repeat_count, state)) {
768 return false;
769 }
770 }
771 return true;
772 }
773
774 inline bool validate_weekday_constraints(const FormatParseState& state, const DateTimeStruct& dt) noexcept {
775 if (state.has_weekday && day_of_week_date<Weekday>(dt.year, dt.mon, dt.day) != state.weekday) {
776 return false;
777 }
778 if (has_iso_week_date_fields(state) || state.has_iso_weekday) {
779 const IsoWeekDateStruct iso_week = to_iso_week_date(dt.year, dt.mon, dt.day);
780 if (state.has_iso_week_year && iso_week.year != state.iso_week_year) {
781 return false;
782 }
783 if (state.has_iso_week_two_digit_year && last_two_digits_of_year(iso_week.year) != state.iso_week_two_digit_year) {
784 return false;
785 }
786 if (state.has_iso_week && iso_week.week != state.iso_week) {
787 return false;
788 }
789 if (state.has_iso_weekday && iso_week.weekday != state.iso_weekday) {
790 return false;
791 }
792 }
793 return true;
794 }
795
796 inline bool resolve_time_fields(const FormatParseState& state, int& hour, int& minute, int& second, int& millisecond) noexcept {
797 hour = 0;
798 if (state.has_hour24) {
799 hour = state.hour24;
800 if (state.has_meridiem && state.has_hour12) {
801 int expected = state.hour12 % 12;
802 if (state.is_pm) {
803 expected += 12;
804 }
805 if (expected != hour) {
806 return false;
807 }
808 }
809 } else if (state.has_hour12) {
810 if (!state.has_meridiem || state.hour12 < 1 || state.hour12 > 12) {
811 return false;
812 }
813 hour = state.hour12 % 12;
814 if (state.is_pm) {
815 hour += 12;
816 }
817 }
818
819 minute = state.has_minute ? state.minute : 0;
820 second = state.has_second ? state.second : 0;
821 millisecond = state.has_millisecond ? state.millisecond : 0;
822 return true;
823 }
824
825 inline bool finalize_calendar_state(FormatParseState& state, DateTimeStruct& out_dt, TimeZoneStruct& out_tz) noexcept {
826 out_tz = state.has_tz ? state.tz : create_time_zone_struct(0, 0, true);
827
828 if (has_iso_week_date_fields(state)) {
829 if (has_gregorian_date_fields(state)) {
830 return false;
831 }
832
833 year_t iso_week_year = 0;
834 if (state.has_iso_week_year) {
835 iso_week_year = state.iso_week_year;
836 if (state.has_iso_week_two_digit_year
837 && last_two_digits_of_year(iso_week_year) != state.iso_week_two_digit_year) {
838 return false;
839 }
840 } else if (state.has_iso_week_two_digit_year) {
841 iso_week_year = static_cast<year_t>(state.iso_week_two_digit_year);
842 } else {
843 return false;
844 }
845
846 if (!state.has_iso_week) {
847 return false;
848 }
849
851 iso_week_year,
852 state.iso_week,
853 state.has_iso_weekday ? state.iso_weekday : 1);
854 if (!is_valid_iso_week_date(iso_week_date.year, iso_week_date.week, iso_week_date.weekday)) {
855 return false;
856 }
857
858 const DateStruct calendar_date = iso_week_date_to_date(iso_week_date);
859 int hour = 0;
860 int minute = 0;
861 int second = 0;
862 int millisecond = 0;
863 if (!resolve_time_fields(state, hour, minute, second, millisecond)) {
864 return false;
865 }
866
868 calendar_date.year,
869 calendar_date.mon,
870 calendar_date.day,
871 hour,
872 minute,
873 second,
874 millisecond);
875 if (!is_valid_date_time(out_dt)) {
876 return false;
877 }
878 return validate_weekday_constraints(state, out_dt);
879 }
880
881 year_t year = 0;
882 if (state.has_year) {
883 year = state.year;
884 if (state.has_century && year / 100 != state.century) {
885 return false;
886 }
887 const int yy = static_cast<int>(year >= 0 ? (year % 100) : -(year % 100));
888 if (state.has_two_digit_year && yy != state.two_digit_year) {
889 return false;
890 }
891 } else if (state.has_century || state.has_two_digit_year) {
892 year = static_cast<year_t>((state.has_century ? state.century : 0) * 100
893 + (state.has_two_digit_year ? state.two_digit_year : 0));
894 } else {
895 return false;
896 }
897
898 int month = state.has_month ? state.month : 0;
899 int day = state.has_day ? state.day : 0;
900 if (state.has_day_of_year) {
901 int resolved_month = 0;
902 int resolved_day = 0;
903 if (!resolve_day_of_year(year, state.day_of_year, resolved_month, resolved_day)) {
904 return false;
905 }
906 if (state.has_month && state.month != resolved_month) {
907 return false;
908 }
909 if (state.has_day && state.day != resolved_day) {
910 return false;
911 }
912 month = resolved_month;
913 day = resolved_day;
914 }
915 if (month == 0 || day == 0) {
916 return false;
917 }
918
919 int hour = 0;
920 int minute = 0;
921 int second = 0;
922 int millisecond = 0;
923 if (!resolve_time_fields(state, hour, minute, second, millisecond)) {
924 return false;
925 }
926
928 year,
929 month,
930 day,
931 hour,
932 minute,
933 second,
934 millisecond);
935 if (!is_valid_date_time(out_dt)) {
936 return false;
937 }
938 return validate_weekday_constraints(state, out_dt);
939 }
940
942 FormatParseState& state,
943 DateTimeStruct& out_dt,
944 TimeZoneStruct& out_tz) noexcept {
946 return false;
947 }
948
949 if (state.has_unix_seconds) {
950 out_tz = state.has_tz ? state.tz : create_time_zone_struct(0, 0, true);
951 const tz_t offset = time_zone_struct_to_offset(out_tz);
952 const ts_t local_ts = state.has_tz ? static_cast<ts_t>(state.unix_seconds + offset)
953 : state.unix_seconds;
954 out_dt = to_date_time<DateTimeStruct>(local_ts);
955 if (state.has_millisecond) {
956 out_dt.ms = state.millisecond;
957 }
958
959 if (state.has_year && out_dt.year != state.year) return false;
960 if (state.has_month && out_dt.mon != state.month) return false;
961 if (state.has_day && out_dt.day != state.day) return false;
962 if (state.has_hour24 && out_dt.hour != state.hour24) return false;
963 if (state.has_minute && out_dt.min != state.minute) return false;
964 if (state.has_second && out_dt.sec != state.second) return false;
965 if (state.has_millisecond && out_dt.ms != state.millisecond) return false;
966 if (state.has_day_of_year && compute_day_of_year(out_dt.year, out_dt.mon, out_dt.day) != state.day_of_year) return false;
967 return validate_weekday_constraints(state, out_dt);
968 }
969
970 return finalize_calendar_state(state, out_dt, out_tz);
971 }
972
974 const char* data,
975 std::size_t length,
976 const char* format,
977 std::size_t format_length,
978 DateTimeStruct& out_dt,
979 TimeZoneStruct& out_tz) noexcept {
980 if (!data || !format) {
981 return false;
982 }
984 const char* p = data;
985 const char* end = data + length;
986 if (!parse_format_sequence(p, end, format, format_length, state)) {
987 return false;
988 }
989 if (p != end) {
990 return false;
991 }
992 return finalize_format_parse_state(state, out_dt, out_tz);
993 }
994
995 } // namespace format_parse
996 } // namespace detail
997
1003 inline bool try_parse_format(
1004 const char* data,
1005 std::size_t length,
1006 const char* format,
1007 std::size_t format_length,
1008 DateTimeStruct& out_dt,
1009 TimeZoneStruct& out_tz) noexcept {
1010 return detail::format_parse::try_parse_format_core(data, length, format, format_length, out_dt, out_tz);
1011 }
1012
1016 const char* data,
1017 std::size_t length,
1018 const char* format,
1019 std::size_t format_length,
1020 ts_t& out_ts) noexcept {
1021 DateTimeStruct dt;
1022 TimeZoneStruct tz;
1023 if (!try_parse_format(data, length, format, format_length, dt, tz)) {
1024 out_ts = 0;
1025 return false;
1026 }
1027 try {
1029 return true;
1030 } catch (...) {
1031 out_ts = 0;
1032 return false;
1033 }
1034 }
1035
1039 const char* data,
1040 std::size_t length,
1041 const char* format,
1042 std::size_t format_length,
1043 ts_ms_t& out_ts) noexcept {
1044 DateTimeStruct dt;
1045 TimeZoneStruct tz;
1046 if (!try_parse_format(data, length, format, format_length, dt, tz)) {
1047 out_ts = 0;
1048 return false;
1049 }
1050 try {
1052 return true;
1053 } catch (...) {
1054 out_ts = 0;
1055 return false;
1056 }
1057 }
1058
1061 inline bool try_parse_format(
1062 const std::string& data,
1063 const std::string& format,
1064 DateTimeStruct& out_dt,
1065 TimeZoneStruct& out_tz) noexcept {
1066 return try_parse_format(data.data(), data.size(), format.data(), format.size(), out_dt, out_tz);
1067 }
1068
1071 inline bool try_parse_format(
1072 const char* data,
1073 const char* format,
1074 DateTimeStruct& out_dt,
1075 TimeZoneStruct& out_tz) noexcept {
1076 if (!data || !format) {
1077 return false;
1078 }
1079 return try_parse_format(data, std::strlen(data), format, std::strlen(format), out_dt, out_tz);
1080 }
1081
1085 const std::string& data,
1086 const std::string& format,
1087 ts_t& out_ts) noexcept {
1088 return try_parse_format_ts(data.data(), data.size(), format.data(), format.size(), out_ts);
1089 }
1090
1094 const char* data,
1095 const char* format,
1096 ts_t& out_ts) noexcept {
1097 if (!data || !format) {
1098 out_ts = 0;
1099 return false;
1100 }
1101 return try_parse_format_ts(data, std::strlen(data), format, std::strlen(format), out_ts);
1102 }
1103
1107 const std::string& data,
1108 const std::string& format,
1109 ts_ms_t& out_ts) noexcept {
1110 return try_parse_format_ts_ms(data.data(), data.size(), format.data(), format.size(), out_ts);
1111 }
1112
1116 const char* data,
1117 const char* format,
1118 ts_ms_t& out_ts) noexcept {
1119 if (!data || !format) {
1120 out_ts = 0;
1121 return false;
1122 }
1123 return try_parse_format_ts_ms(data, std::strlen(data), format, std::strlen(format), out_ts);
1124 }
1125
1126#if __cplusplus >= 201703L
1129 inline bool try_parse_format(
1130 std::string_view data,
1131 std::string_view format,
1132 DateTimeStruct& out_dt,
1133 TimeZoneStruct& out_tz) noexcept {
1134 return try_parse_format(data.data(), data.size(), format.data(), format.size(), out_dt, out_tz);
1135 }
1136
1140 std::string_view data,
1141 std::string_view format,
1142 ts_t& out_ts) noexcept {
1143 return try_parse_format_ts(data.data(), data.size(), format.data(), format.size(), out_ts);
1144 }
1145
1149 std::string_view data,
1150 std::string_view format,
1151 ts_ms_t& out_ts) noexcept {
1152 return try_parse_format_ts_ms(data.data(), data.size(), format.data(), format.size(), out_ts);
1153 }
1154#endif
1155
1156} // namespace time_shield
1157
1158#endif // TIME_SHIELD_HEADER_TEXT_TIME_FORMAT_PARSER_HPP_INCLUDED
@ SHORT_NAME
Short name.
Definition enums.hpp:21
@ FULL_NAME
Full name.
Definition enums.hpp:22
@ UPPERCASE_NAME
Uppercase short name.
Definition enums.hpp:20
bool is_valid_iso_week_date(year_t iso_year, int week, int weekday)
Validate ISO week date components.
TIME_SHIELD_CONSTEXPR T1 day_of_week_date(T2 year, T3 month, T4 day)
Get the day of the week.
IsoWeekDateStruct to_iso_week_date(Y year, M month, D day)
Convert calendar date to ISO week date.
DateStruct iso_week_date_to_date(const IsoWeekDateStruct &iso_date)
Convert ISO week date to calendar date.
TIME_SHIELD_CONSTEXPR T days(ts_t start, ts_t stop) noexcept
Alias for days_between function.
TIME_SHIELD_CONSTEXPR T1 weekday(year_t year, int month, int day)
Alias for day_of_week_date.
TIME_SHIELD_CONSTEXPR T year(ts_t ts=time_shield::ts())
Alias for year_of function.
TIME_SHIELD_CONSTEXPR T1 sec_to_ms(T2 ts) noexcept
Converts a timestamp from seconds to milliseconds.
bool try_parse_format(const char *data, std::size_t length, const char *format, std::size_t format_length, DateTimeStruct &out_dt, TimeZoneStruct &out_tz) noexcept
Parse input using formatter-compatible custom pattern.
bool try_parse_format_ts_ms(const char *data, std::size_t length, const char *format, std::size_t format_length, ts_ms_t &out_ts) noexcept
Parse input using formatter-compatible custom pattern and convert to UTC milliseconds.
bool try_parse_format_ts(const char *data, std::size_t length, const char *format, std::size_t format_length, ts_t &out_ts) noexcept
Parse input using formatter-compatible custom pattern and convert to UTC seconds.
TIME_SHIELD_CONSTEXPR tz_t time_zone_struct_to_offset(const TimeZoneStruct &tz) noexcept
Convert a TimeZoneStruct to a numeric UTC offset (seconds).
T day_of_year(ts_t ts=time_shield::ts())
Get the day of the year.
TIME_SHIELD_CONSTEXPR bool is_valid_time_zone_offset(const T &time_zone) noexcept
Check if the time zone is valid.
TIME_SHIELD_CONSTEXPR ts_ms_t dt_to_timestamp_ms(const T &date_time)
Converts a date-time structure to a timestamp in milliseconds.
TIME_SHIELD_CONSTEXPR ts_t dt_to_timestamp(const T &date_time)
Converts a date-time structure to a timestamp.
const IsoWeekDateStruct create_iso_week_date_struct(int64_t year, int32_t week=1, int32_t weekday=1)
Creates an IsoWeekDateStruct instance.
const DateTimeStruct create_date_time_struct(int64_t year, int mon=1, int day=1, int hour=0, int min=0, int sec=0, int ms=0)
Creates a DateTimeStruct instance.
T1 to_date_time(T2 ts)
Converts a timestamp to a date-time structure.
TimeZoneStruct create_time_zone_struct(int hour, int min, bool is_positive=true)
Creates a TimeZoneStruct instance.
int64_t ts_t
Unix timestamp in seconds since 1970‑01‑01T00:00:00Z.
Definition types.hpp:48
int32_t tz_t
Time zone offset in minutes from UTC (e.g., +180 = UTC+3).
Definition types.hpp:60
int64_t ts_ms_t
Unix timestamp in milliseconds since epoch.
Definition types.hpp:49
int64_t year_t
Year as an integer (e.g., 2024).
Definition types.hpp:40
TIME_SHIELD_CONSTEXPR bool is_valid_date_time(T1 year, T2 month, T2 day, T2 hour=0, T2 min=0, T2 sec=0, T3 ms=0) noexcept
Checks the correctness of a date and time.
TIME_SHIELD_CONSTEXPR bool is_leap_year_date(T year) noexcept
Checks if the given year is a leap year.
bool parse_signed_digits(const char *&p, const char *end, int min_digits, int max_digits, int64_t &out) noexcept
bool has_gregorian_date_fields(const FormatParseState &state) noexcept
bool validate_weekday_constraints(const FormatParseState &state, const DateTimeStruct &dt) noexcept
bool parse_weekday_token(const char *&p, const char *end, FormatType format, int &weekday) noexcept
bool parse_space_padded_2digits(const char *&p, const char *end, int &out) noexcept
bool finalize_calendar_state(FormatParseState &state, DateTimeStruct &out_dt, TimeZoneStruct &out_tz) noexcept
bool assign_year_field(bool &has_field, year_t &field, year_t value) noexcept
bool parse_compact_extended_year(const char *&p, const char *end, year_t &out) noexcept
FormatParseState create_format_parse_state() noexcept
bool parse_format_sequence(const char *&p, const char *end, const char *format_data, std::size_t format_size, FormatParseState &state) noexcept
bool parse_format_token(const char *&p, const char *end, char token, std::size_t repeat_count, FormatParseState &state) noexcept
bool match_name_token(const char *&p, const char *end, const char *const *names, std::size_t count, int index_base, int &out) noexcept
bool try_parse_format_core(const char *data, std::size_t length, const char *format, std::size_t format_length, DateTimeStruct &out_dt, TimeZoneStruct &out_tz) noexcept
int compute_day_of_year(year_t year, int month, int day) noexcept
bool resolve_day_of_year(year_t year, int day_of_year, int &month, int &day) noexcept
bool parse_unsigned_digits(const char *&p, const char *end, int min_digits, int max_digits, int64_t &out) noexcept
bool parse_meridiem(const char *&p, const char *end, bool uppercase, bool &is_pm) noexcept
bool resolve_time_fields(const FormatParseState &state, int &hour, int &minute, int &second, int &millisecond) noexcept
bool assign_int_field(bool &has_field, int &field, int value) noexcept
bool match_literal(const char *&p, const char *end, char expected) noexcept
int last_two_digits_of_year(year_t year) noexcept
bool parse_tz_offset_token(const char *&p, const char *end, TimeZoneStruct &tz) noexcept
Parse z timezone token in compact or extended ISO-style form.
bool finalize_format_parse_state(FormatParseState &state, DateTimeStruct &out_dt, TimeZoneStruct &out_tz) noexcept
TIME_SHIELD_CONSTEXPR bool is_ascii_digit(char c) noexcept
bool parse_exact_2digits(const char *&p, const char *end, int &out) noexcept
bool parse_month_token(const char *&p, const char *end, FormatType format, int &month) noexcept
bool has_iso_week_date_fields(const FormatParseState &state) noexcept
Main namespace for the Time Shield library.
Structure to represent a date.
int32_t mon
Month component of the date (1-12).
int32_t day
Day component of the date (1-31).
int64_t year
Year component of the date.
Structure to represent date and time.
Structure to represent an ISO week date.
int64_t year
ISO week-numbering year component.
int32_t week
ISO week number component (1-52/53).
int32_t weekday
ISO weekday component (1=Monday .. 7=Sunday).
Structure to represent time zone information.