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