Time Shield Library
C++ library for working with time
Loading...
Searching...
No Matches
time_parser.hpp
Go to the documentation of this file.
1// SPDX-License-Identifier: MIT
2#pragma once
3#ifndef _TIME_SHIELD_TIME_PARSER_HPP_INCLUDED
4#define _TIME_SHIELD_TIME_PARSER_HPP_INCLUDED
5
18
19#include "enums.hpp"
20#include "constants.hpp"
21#include "date_time_struct.hpp"
22#include "time_zone_struct.hpp"
23#include "validation.hpp"
24#include "time_conversions.hpp"
27
28#include <algorithm>
29#include <locale>
30#include <array>
31#include <stdexcept>
32#include <cctype>
33#include <cstring>
34#include <string>
35
36#if __cplusplus >= 201703L
37# include <string_view>
38#endif
39
40namespace time_shield {
41
73
74 namespace detail {
75
77 inline std::string trim_copy_ascii(const std::string& s) {
78 size_t b = 0;
79 size_t e = s.size();
80 while (b < e && std::isspace(static_cast<unsigned char>(s[b])) != 0) ++b;
81 while (e > b && std::isspace(static_cast<unsigned char>(s[e - 1])) != 0) --e;
82 return s.substr(b, e - b);
83 }
84
85# if __cplusplus >= 201703L
87 inline std::string_view trim_view_ascii(std::string_view v) {
88 size_t b = 0;
89 size_t e = v.size();
90 while (b < e && std::isspace(static_cast<unsigned char>(v[b])) != 0) ++b;
91 while (e > b && std::isspace(static_cast<unsigned char>(v[e - 1])) != 0) --e;
92 return v.substr(b, e - b);
93 }
94# endif
95
99 inline void normalise_month_token_lower(const std::string& month, std::string& output) {
100 output = trim_copy_ascii(month);
101 if (output.empty()) return;
102
103 const auto& facet = std::use_facet<std::ctype<char>>(std::locale());
104 std::transform(output.begin(), output.end(), output.begin(),
105 [&facet](char ch) { return facet.tolower(ch); });
106 }
107
108# if __cplusplus >= 201703L
112 inline void normalise_month_token_lower(std::string_view month, std::string& output) {
113 month = trim_view_ascii(month);
114 output.assign(month.begin(), month.end());
115 if (output.empty()) return;
116
117 const auto& facet = std::use_facet<std::ctype<char>>(std::locale());
118 std::transform(output.begin(), output.end(), output.begin(),
119 [&facet](char ch) { return facet.tolower(ch); });
120 }
121# endif
122
127 inline bool try_parse_month_index(const std::string& month, int& value) {
128 if (month.empty()) return false;
129
130 std::string month_copy;
131 normalise_month_token_lower(month, month_copy);
132 if (month_copy.empty()) return false;
133
134 static const std::array<const char*, MONTHS_PER_YEAR> short_names = {
135 "jan", "feb", "mar", "apr", "may", "jun",
136 "jul", "aug", "sep", "oct", "nov", "dec"
137 };
138 static const std::array<const char*, MONTHS_PER_YEAR> full_names = {
139 "january", "february", "march", "april", "may", "june",
140 "july", "august", "september", "october", "november", "december"
141 };
142
143 for (std::size_t i = 0; i < short_names.size(); ++i) {
144 if (month_copy == short_names[i] || month_copy == full_names[i]) {
145 value = static_cast<int>(i) + 1;
146 return true;
147 }
148 }
149
150 return false;
151 }
152
153# if __cplusplus >= 201703L
158 inline bool try_parse_month_index(std::string_view month, int& value) {
159 if (month.empty()) return false;
160
161 std::string month_copy;
162 normalise_month_token_lower(month, month_copy);
163 if (month_copy.empty()) return false;
164
165 static const std::array<const char*, MONTHS_PER_YEAR> short_names = {
166 "jan", "feb", "mar", "apr", "may", "jun",
167 "jul", "aug", "sep", "oct", "nov", "dec"
168 };
169 static const std::array<const char*, MONTHS_PER_YEAR> full_names = {
170 "january", "february", "march", "april", "may", "june",
171 "july", "august", "september", "october", "november", "december"
172 };
173
174 for (std::size_t i = 0; i < short_names.size(); ++i) {
175 if (month_copy == short_names[i] || month_copy == full_names[i]) {
176 value = static_cast<int>(i) + 1;
177 return true;
178 }
179 }
180
181 return false;
182 }
183# endif
184
189 inline int parse_month_index(const std::string& month) {
190 int value = 0;
191 if (!try_parse_month_index(month, value)) {
192 throw std::invalid_argument("Invalid month name");
193 }
194 return value;
195 }
196
197# if __cplusplus >= 201703L
202 inline int parse_month_index(std::string_view month) {
203 int value = 0;
204 if (!try_parse_month_index(month, value)) {
205 throw std::invalid_argument("Invalid month name");
206 }
207 return value;
208 }
209# endif
210
212 const char* name;
214 };
215
217 inline const std::array<ZoneNameEntry, 25>& time_zone_name_entries() noexcept {
218 static const std::array<ZoneNameEntry, 25> entries = {{
219 {"GMT", GMT},
220 {"UTC", UTC},
221 {"EET", EET},
222 {"CET", CET},
223 {"WET", WET},
224 {"EEST", EEST},
225 {"CEST", CEST},
226 {"WEST", WEST},
227 {"ET", ET},
228 {"CT", CT},
229 {"IST", IST},
230 {"MYT", MYT},
231 {"WIB", WIB},
232 {"WITA", WITA},
233 {"WIT", WIT},
234 {"KZT", KZT},
235 {"TRT", TRT},
236 {"BYT", BYT},
237 {"SGT", SGT},
238 {"ICT", ICT},
239 {"PHT", PHT},
240 {"GST", GST},
241 {"HKT", HKT},
242 {"JST", JST},
243 {"KST", KST}
244 }};
245 return entries;
246 }
247
249 inline bool try_parse_time_zone_name_token(const char* data, std::size_t length, TimeZone& zone) noexcept {
250 if (data == nullptr || length == 0) {
251 zone = UNKNOWN;
252 return false;
253 }
254
255 const std::array<ZoneNameEntry, 25>& entries = time_zone_name_entries();
256 for (std::size_t i = 0; i < entries.size(); ++i) {
257 const std::size_t name_length = std::strlen(entries[i].name);
258 if (length == name_length && std::memcmp(data, entries[i].name, name_length) == 0) {
259 zone = entries[i].zone;
260 return true;
261 }
262 }
263
264 zone = UNKNOWN;
265 return false;
266 }
267
268//------------------------------------------------------------------------------
269// Small C-style helpers (no lambdas, no detail namespace)
270//------------------------------------------------------------------------------
271
273 TIME_SHIELD_CONSTEXPR inline bool is_ascii_space(char c) noexcept {
274 return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f' || c == '\v';
275 }
276
278 TIME_SHIELD_CONSTEXPR inline bool is_ascii_digit(char c) noexcept {
279 return c >= '0' && c <= '9';
280 }
281
283 TIME_SHIELD_CONSTEXPR inline void skip_spaces(const char*& p, const char* end) noexcept {
284 while (p < end && is_ascii_space(*p)) {
285 ++p;
286 }
287 }
288
291 TIME_SHIELD_CONSTEXPR inline bool parse_2digits(const char*& p, const char* end, int& out) noexcept {
292 if (end - p < 2) {
293 return false;
294 }
295 const char a = p[0];
296 const char b = p[1];
297 if (!is_ascii_digit(a) || !is_ascii_digit(b)) {
298 return false;
299 }
300 out = (a - '0') * 10 + (b - '0');
301 p += 2;
302 return true;
303 }
304
307 TIME_SHIELD_CONSTEXPR inline bool parse_4digits_year(const char*& p, const char* end, year_t& out) noexcept {
308 if (end - p < 4) {
309 return false;
310 }
311 const char a = p[0], b = p[1], c = p[2], d = p[3];
312 if (!is_ascii_digit(a) || !is_ascii_digit(b) || !is_ascii_digit(c) || !is_ascii_digit(d)) {
313 return false;
314 }
315 const int v = (a - '0') * 1000 + (b - '0') * 100 + (c - '0') * 10 + (d - '0');
316 out = static_cast<year_t>(v);
317 p += 4;
318 return true;
319 }
320
324 TIME_SHIELD_CONSTEXPR inline bool parse_fraction_to_ms(const char*& p, const char* end, int& ms_out) noexcept {
325 if (p >= end || !is_ascii_digit(*p)) {
326 return false;
327 }
328
329 int ms = 0;
330 int digits = 0;
331
332 while (p < end && is_ascii_digit(*p)) {
333 if (digits >= 3) {
334 return false;
335 }
336 ms = ms * 10 + (*p - '0');
337 ++digits;
338 ++p;
339 }
340
341 if (digits == 1) {
342 ms *= 100;
343 } else if (digits == 2) {
344 ms *= 10;
345 }
346
347 ms_out = ms;
348 return true;
349 }
350
351 } // namespace detail
352
353//------------------------------------------------------------------------------
354// Month helpers (public)
355//------------------------------------------------------------------------------
356
357// Canonical API (recommended):
358// - parse_month(...) / try_parse_month(...): return month index as int [1..12]
359// - parse_month_enum(...) / try_parse_month_enum(...): return month as enum Month (or any integral/enum T)
360
365 inline bool try_parse_month(const std::string& month, int& value) {
366 return detail::try_parse_month_index(month, value);
367 }
368
373 inline int parse_month(const std::string& month) {
374 return detail::parse_month_index(month);
375 }
376
377#if __cplusplus >= 201703L
382 inline bool try_parse_month(std::string_view month, int& value) {
383 return detail::try_parse_month_index(month, value);
384 }
385
390 inline int parse_month(std::string_view month) {
391 return detail::parse_month_index(month);
392 }
393#endif
394
395// Canonical: parse month -> enum Month (or any T)
396
402 template<class T = Month>
403 inline T parse_month_enum(const std::string& month) {
404 return static_cast<T>(detail::parse_month_index(month));
405 }
406
412 template<class T = Month>
413 inline bool try_parse_month_enum(const std::string& month, T& value) {
414 int idx = 0;
415 if (!detail::try_parse_month_index(month, idx)) return false;
416 value = static_cast<T>(idx);
417 return true;
418 }
419
420#if __cplusplus >= 201703L
426 template<class T = Month>
427 inline T parse_month_enum(std::string_view month) {
428 return static_cast<T>(detail::parse_month_index(month));
429 }
430
436 template<class T = Month>
437 inline bool try_parse_month_enum(std::string_view month, T& value) {
438 int idx = 0;
439 if (!detail::try_parse_month_index(month, idx)) return false;
440 value = static_cast<T>(idx);
441 return true;
442 }
443#endif
444
445// Index aliases (int)
446
451 inline bool try_get_month_index(const std::string& month, int& value) {
452 return try_parse_month(month, value);
453 }
454
459 inline int get_month_index(const std::string& month) {
460 return parse_month(month);
461 }
462
467 inline Month get_month_index_enum(const std::string& month) {
468 return static_cast<Month>(detail::parse_month_index(month));
469 }
470
471#if __cplusplus >= 201703L
473 inline bool try_get_month_index(std::string_view month, int& value) {
474 return try_parse_month(month, value);
475 }
476
478 inline int get_month_index(std::string_view month) {
479 return parse_month(month);
480 }
481
483 inline Month get_month_index_enum(std::string_view month) {
484 return static_cast<Month>(detail::parse_month_index(month));
485 }
486#endif
487
488// Month number aliases (T)
489
495 template<class T = Month>
496 inline T get_month_number(const std::string& month) {
497 return parse_month_enum<T>(month);
498 }
499
501 template<class T = Month>
502 inline T month_of_year(const std::string& month) {
503 return get_month_number<T>(month);
504 }
505
511 template<class T = Month>
512 inline bool try_get_month_number(const std::string& month, T& value) {
513 return try_parse_month_enum<T>(month, value);
514 }
515
517 template<class T = Month>
518 inline bool get_month_number(const std::string& month, T& value) {
519 return try_get_month_number<T>(month, value);
520 }
521
523 template<class T = Month>
524 inline bool month_of_year(const std::string& month, T& value) {
525 return try_get_month_number<T>(month, value);
526 }
527
528#if __cplusplus >= 201703L
530 template<class T = Month>
531 inline T get_month_number(std::string_view month) {
532 return parse_month_enum<T>(month);
533 }
534
536 template<class T = Month>
537 inline T month_of_year(std::string_view month) {
538 return get_month_number<T>(month);
539 }
540
542 template<class T = Month>
543 inline bool try_get_month_number(std::string_view month, T& value) {
544 return try_parse_month_enum<T>(month, value);
545 }
546
548 template<class T = Month>
549 inline bool get_month_number(std::string_view month, T& value) {
550 return try_get_month_number<T>(month, value);
551 }
552
554 template<class T = Month>
555 inline bool month_of_year(std::string_view month, T& value) {
556 return try_get_month_number<T>(month, value);
557 }
558#endif
559
560// const char* overloads to avoid ambiguity with string vs string_view for literals
561
567 template<class T = Month>
568 inline T get_month_number(const char* month) {
569#if __cplusplus >= 201703L
570 return get_month_number<T>(std::string_view(month));
571#else
572 return get_month_number<T>(std::string(month));
573#endif
574 }
575
581 template<class T = Month>
582 inline bool try_get_month_number(const char* month, T& value) {
583#if __cplusplus >= 201703L
584 return try_get_month_number<T>(std::string_view(month), value);
585#else
586 return try_get_month_number<T>(std::string(month), value);
587#endif
588 }
589
591 template<class T = Month>
592 inline T month_of_year(const char* month) {
593 return get_month_number<T>(month);
594 }
595
597 template<class T = Month>
598 inline bool get_month_number(const char* month, T& value) {
599 return try_get_month_number<T>(month, value);
600 }
601
603 template<class T = Month>
604 inline bool month_of_year(const char* month, T& value) {
605 return try_get_month_number<T>(month, value);
606 }
607
608//------------------------------------------------------------------------------
609// Time zone parsing (C-style, high performance)
610//------------------------------------------------------------------------------
611
626 inline bool parse_time_zone(const char* data, std::size_t length, TimeZoneStruct& tz) noexcept {
627 if (!data) {
628 return false;
629 }
630
631 if (length == 0) {
632 tz.hour = 0;
633 tz.min = 0;
634 tz.is_positive = true;
635 return true;
636 }
637
638 if (length == 1 && (data[0] == 'Z' || data[0] == 'z')) {
639 tz.hour = 0;
640 tz.min = 0;
641 tz.is_positive = true;
642 return true;
643 }
644
645 if (length != 6) {
646 return false;
647 }
648
649 const char sign = data[0];
650 if (sign != '+' && sign != '-') {
651 return false;
652 }
653 if (data[3] != ':') {
654 return false;
655 }
656 if (!detail::is_ascii_digit(data[1]) || !detail::is_ascii_digit(data[2]) ||
657 !detail::is_ascii_digit(data[4]) || !detail::is_ascii_digit(data[5])) {
658 return false;
659 }
660
661 tz.is_positive = (sign == '+');
662 tz.hour = (data[1] - '0') * 10 + (data[2] - '0');
663 tz.min = (data[4] - '0') * 10 + (data[5] - '0');
664
665 return is_valid_time_zone(tz);
666 }
667
670 inline bool parse_time_zone(const std::string& tz_str, TimeZoneStruct& tz) noexcept {
671 return parse_time_zone(tz_str.c_str(), tz_str.size(), tz);
672 }
673
675 inline bool parse_tz(const std::string& tz_str, TimeZoneStruct& tz) noexcept {
676 return parse_time_zone(tz_str, tz);
677 }
678
680 inline bool parse_tz(const char* data, std::size_t length, TimeZoneStruct& tz) noexcept {
681 return parse_time_zone(data, length, tz);
682 }
683
690 inline bool parse_time_zone_name(const char* data, std::size_t length, TimeZone& zone) noexcept {
691 if (!data) {
692 zone = UNKNOWN;
693 return false;
694 }
695
696 std::size_t begin = 0;
697 std::size_t end = length;
698 while (begin < end && std::isspace(static_cast<unsigned char>(data[begin])) != 0) {
699 ++begin;
700 }
701 while (end > begin && std::isspace(static_cast<unsigned char>(data[end - 1])) != 0) {
702 --end;
703 }
704 return detail::try_parse_time_zone_name_token(data + begin, end - begin, zone);
705 }
706
709 inline bool parse_time_zone_name(const std::string& value, TimeZone& zone) noexcept {
710 return parse_time_zone_name(value.c_str(), value.size(), zone);
711 }
712
713#if __cplusplus >= 201703L
716 inline bool parse_time_zone_name(std::string_view value, TimeZone& zone) noexcept {
717 return parse_time_zone_name(value.data(), value.size(), zone);
718 }
719#endif
720
723 inline bool parse_time_zone_name(const char* value, TimeZone& zone) noexcept {
724 if (value == nullptr) {
725 zone = UNKNOWN;
726 return false;
727 }
728 return parse_time_zone_name(value, std::strlen(value), zone);
729 }
730
732 inline bool parse_tz_name(const char* data, std::size_t length, TimeZone& zone) noexcept {
733 return parse_time_zone_name(data, length, zone);
734 }
735
737 inline bool parse_tz_name(const std::string& value, TimeZone& zone) noexcept {
738 return parse_time_zone_name(value, zone);
739 }
740
741#if __cplusplus >= 201703L
743 inline bool parse_tz_name(std::string_view value, TimeZone& zone) noexcept {
744 return parse_time_zone_name(value, zone);
745 }
746#endif
747
749 inline bool parse_tz_name(const char* value, TimeZone& zone) noexcept {
750 return parse_time_zone_name(value, zone);
751 }
752
753//------------------------------------------------------------------------------
754// ISO8601 parsing (C-style, no regex, no allocations)
755//------------------------------------------------------------------------------
756
776 inline bool parse_iso8601(const char* input, std::size_t length,
777 DateTimeStruct& dt, TimeZoneStruct& tz) noexcept {
778 if (!input) {
779 return false;
780 }
781
782 const char* p = input;
783 const char* end = input + length;
784
785 detail::skip_spaces(p, end);
786
788 tz = create_time_zone_struct(0, 0);
789 tz.is_positive = true;
790
791 const char* const date_start = p;
792 const char* date_end = p;
793 while (date_end < end && *date_end != 'T' && *date_end != 't' && !detail::is_ascii_space(*date_end)) {
794 ++date_end;
795 }
796
797 bool parsed_iso_week_date = false;
798 if (date_end > date_start) {
799 IsoWeekDateStruct iso_date{};
800 if (parse_iso_week_date(date_start, static_cast<std::size_t>(date_end - date_start), iso_date)) {
801 const DateStruct calendar_date = iso_week_date_to_date(iso_date);
802 dt.year = calendar_date.year;
803 dt.mon = calendar_date.mon;
804 dt.day = calendar_date.day;
805 p = date_end;
806 parsed_iso_week_date = true;
807 }
808 }
809
810 if (!parsed_iso_week_date) {
811 // ---- Date: YYYY<sep>MM<sep>DD
812 if (!detail::parse_4digits_year(p, end, dt.year)) {
813 return false;
814 }
815 if (p >= end) {
816 return false;
817 }
818 const char sep1 = *p;
819 if (sep1 != '-' && sep1 != '/' && sep1 != '.') {
820 return false;
821 }
822 ++p;
823
824 if (!detail::parse_2digits(p, end, dt.mon)) {
825 return false;
826 }
827 if (p >= end) {
828 return false;
829 }
830 const char sep2 = *p;
831 if (sep2 != '-' && sep2 != '/' && sep2 != '.') {
832 return false;
833 }
834 ++p;
835
836 if (!detail::parse_2digits(p, end, dt.day)) {
837 return false;
838 }
839 }
840
841 if (!is_valid_date(dt.year, dt.mon, dt.day)) {
842 return false;
843 }
844
845 // Date-only?
846 {
847 const char* q = p;
848 detail::skip_spaces(q, end);
849 if (q == end) {
850 // dt already has time=0 ms=0
851 return is_valid_date_time(dt);
852 }
853 }
854
855 // ---- Date/time separator: 'T' or whitespace
856 if (p >= end) {
857 return false;
858 }
859
860 if (*p == 'T' || *p == 't') {
861 ++p;
862 } else
863 if (detail::is_ascii_space(*p)) {
864 // allow one or more spaces
865 detail::skip_spaces(p, end);
866 } else {
867 return false;
868 }
869
870 // ---- Time: hh:mm[:ss][.frac]
871 if (!detail::parse_2digits(p, end, dt.hour)) {
872 return false;
873 }
874 if (p >= end || *p != ':') {
875 return false;
876 }
877 ++p;
878
879 if (!detail::parse_2digits(p, end, dt.min)) {
880 return false;
881 }
882
883 dt.sec = 0;
884 dt.ms = 0;
885 bool has_seconds = false;
886
887 // Optional :ss
888 if (p < end && *p == ':') {
889 ++p;
890 if (!detail::parse_2digits(p, end, dt.sec)) {
891 return false;
892 }
893 has_seconds = true;
894 }
895
896 // Optional .fraction (allowed only if we had seconds in original regex,
897 // but we accept it when seconds are present; for hh:mm (no seconds) we keep it strict).
898 if (p < end && *p == '.') {
899 // require seconds field to exist (avoid accepting YYYY-MM-DDThh:mm.xxx)
900 if (!has_seconds) {
901 // Ambiguous: could be "hh:mm.fff" which is not in your original formats.
902 // Keep strict to preserve behavior.
903 return false;
904 }
905
906 ++p;
907 int ms = 0;
908 if (!detail::parse_fraction_to_ms(p, end, ms)) {
909 return false;
910 }
911 dt.ms = ms;
912 }
913
914 // ---- Optional timezone: [spaces] (Z | ±HH:MM)
915 detail::skip_spaces(p, end);
916
917 if (p < end) {
918 if (*p == 'Z' || *p == 'z') {
919 tz.hour = 0;
920 tz.min = 0;
921 tz.is_positive = true;
922 ++p;
923 } else if (*p == '+' || *p == '-') {
924 // need 6 chars
925 if (static_cast<std::size_t>(end - p) < 6) {
926 return false;
927 }
928 if (!parse_time_zone(p, 6, tz)) {
929 return false;
930 }
931 p += 6;
932 }
933 }
934
935 detail::skip_spaces(p, end);
936 if (p != end) {
937 return false;
938 }
939
940 return is_valid_date_time(dt);
941 }
942
945 inline bool parse_iso8601(const std::string& input, DateTimeStruct& dt, TimeZoneStruct& tz) noexcept {
946 return parse_iso8601(input.c_str(), input.size(), dt, tz);
947 }
948
951 inline bool parse_iso8601(const char* input, DateTimeStruct& dt, TimeZoneStruct& tz) noexcept {
952 if (input == nullptr) {
953 return false;
954 }
955 return parse_iso8601(input, std::strlen(input), dt, tz);
956 }
957
958# if __cplusplus >= 201703L
961 inline bool parse_iso8601(std::string_view input, DateTimeStruct& dt, TimeZoneStruct& tz) noexcept {
962 return parse_iso8601(input.data(), input.size(), dt, tz);
963 }
964# endif
965
966//------------------------------------------------------------------------------
967// ISO8601 -> timestamps
968//------------------------------------------------------------------------------
969
974 inline bool str_to_ts(const std::string& str, ts_t& ts) {
977 if (!parse_iso8601(str, dt, tz)) return false;
978 try {
979 ts = dt_to_timestamp(dt) - to_offset(tz);
980 return true;
981 } catch (...) {}
982 return false;
983 }
984
990 inline bool str_to_ts(const char* data, std::size_t length, ts_t& ts) {
991 if (!data || length == 0) {
992 ts = 0;
993 return false;
994 }
997 if (!parse_iso8601(data, length, dt, tz)) return false;
998 try {
999 ts = dt_to_timestamp(dt) - to_offset(tz);
1000 return true;
1001 } catch (...) {}
1002 return false;
1003 }
1004
1009 inline bool str_to_ts_ms(const std::string& str, ts_ms_t& ts) {
1010 DateTimeStruct dt;
1011 TimeZoneStruct tz;
1012 if (!parse_iso8601(str, dt, tz)) return false;
1013 try {
1014 ts = static_cast<ts_ms_t>(dt_to_timestamp_ms(dt)) - sec_to_ms(to_offset(tz));
1015 return true;
1016 } catch (...) {}
1017 return false;
1018 }
1019
1025 inline bool str_to_ts_ms(const char* data, std::size_t length, ts_ms_t& ts) {
1026 if (!data || length == 0) {
1027 ts = 0;
1028 return false;
1029 }
1030 DateTimeStruct dt;
1031 TimeZoneStruct tz;
1032 if (!parse_iso8601(data, length, dt, tz)) return false;
1033 try {
1034 ts = static_cast<ts_ms_t>(dt_to_timestamp_ms(dt)) - sec_to_ms(to_offset(tz));
1035 return true;
1036 } catch (...) {}
1037 return false;
1038 }
1039
1044 inline bool str_to_fts(const std::string& str, fts_t& ts) {
1045 DateTimeStruct dt;
1046 TimeZoneStruct tz;
1047 if (!parse_iso8601(str, dt, tz)) return false;
1048 try {
1049 ts = dt_to_ftimestamp(dt) - static_cast<fts_t>(to_offset(tz));
1050 return true;
1051 } catch (...) {}
1052 return false;
1053 }
1054
1060 inline bool str_to_fts(const char* data, std::size_t length, fts_t& ts) {
1061 if (!data || length == 0) {
1062 ts = 0;
1063 return false;
1064 }
1065 DateTimeStruct dt;
1066 TimeZoneStruct tz;
1067 if (!parse_iso8601(data, length, dt, tz)) return false;
1068 try {
1069 ts = dt_to_ftimestamp(dt) - static_cast<fts_t>(to_offset(tz));
1070 return true;
1071 } catch (...) {}
1072 return false;
1073 }
1074
1075//------------------------------------------------------------------------------
1076// Convenience string -> predicates (workdays)
1077//------------------------------------------------------------------------------
1078
1080 inline bool is_workday(const std::string& str) {
1081 ts_t ts = 0;
1082 if (!str_to_ts(str, ts)) return false;
1083 return is_workday(ts);
1084 }
1085
1087 inline bool is_workday_ms(const std::string& str) {
1088 ts_ms_t ts = 0;
1089 if (!str_to_ts_ms(str, ts)) return false;
1090 return is_workday_ms(ts);
1091 }
1092
1095 inline bool workday(const std::string& str) {
1096 return is_workday(str);
1097 }
1098
1101 inline bool workday_ms(const std::string& str) {
1102 return is_workday_ms(str);
1103 }
1104
1108 inline bool is_first_workday_of_month(const std::string& str) {
1109 ts_t ts = 0;
1110 if (!str_to_ts(str, ts)) return false;
1112 }
1113
1117 inline bool is_first_workday_of_month_ms(const std::string& str) {
1118 ts_ms_t ts = 0;
1119 if (!str_to_ts_ms(str, ts)) return false;
1121 }
1122
1126 inline bool is_last_workday_of_month(const std::string& str) {
1127 ts_t ts = 0;
1128 if (!str_to_ts(str, ts)) return false;
1130 }
1131
1135 inline bool is_last_workday_of_month_ms(const std::string& str) {
1136 ts_ms_t ts = 0;
1137 if (!str_to_ts_ms(str, ts)) return false;
1139 }
1140
1145 inline bool is_within_first_workdays_of_month(const std::string& str, int count) {
1146 ts_t ts = 0;
1147 if (!str_to_ts(str, ts)) return false;
1149 }
1150
1155 inline bool is_within_first_workdays_of_month_ms(const std::string& str, int count) {
1156 ts_ms_t ts = 0;
1157 if (!str_to_ts_ms(str, ts)) return false;
1159 }
1160
1165 inline bool is_within_last_workdays_of_month(const std::string& str, int count) {
1166 ts_t ts = 0;
1167 if (!str_to_ts(str, ts)) return false;
1168 return is_within_last_workdays_of_month(ts, count);
1169 }
1170
1175 inline bool is_within_last_workdays_of_month_ms(const std::string& str, int count) {
1176 ts_ms_t ts = 0;
1177 if (!str_to_ts_ms(str, ts)) return false;
1179 }
1180
1181//------------------------------------------------------------------------------
1182// Convenience: C-string wrappers (non-throwing, ambiguous on failure)
1183//------------------------------------------------------------------------------
1184
1189 inline ts_t ts(const char* str) {
1190 ts_t out = 0;
1191 str_to_ts(str ? std::string(str) : std::string(), out);
1192 return out;
1193 }
1194
1201 inline ts_t ts(const char* data, std::size_t length) {
1202 ts_t out = 0;
1203 if (!str_to_ts(data, length, out)) {
1204 return 0;
1205 }
1206 return out;
1207 }
1208
1213 inline ts_ms_t ts_ms(const char* str) {
1214 ts_ms_t out = 0;
1215 str_to_ts_ms(str ? std::string(str) : std::string(), out);
1216 return out;
1217 }
1218
1224 inline ts_ms_t ts_ms(const char* data, std::size_t length) {
1225 ts_ms_t out = 0;
1226 if (!str_to_ts_ms(data, length, out)) {
1227 return 0;
1228 }
1229 return out;
1230 }
1231
1236 inline fts_t fts(const char* str) {
1237 fts_t out = 0;
1238 str_to_fts(str ? std::string(str) : std::string(), out);
1239 return out;
1240 }
1241
1247 inline fts_t fts(const char* data, std::size_t length) {
1248 fts_t out = 0;
1249 if (!str_to_fts(data, length, out)) {
1250 return 0.0;
1251 }
1252 return out;
1253 }
1254
1255//------------------------------------------------------------------------------
1256
1262 inline ts_t ts(const std::string& str) {
1263 ts_t ts = 0;
1264 str_to_ts(str, ts);
1265 return ts;
1266 }
1267
1273 inline ts_ms_t ts_ms(const std::string& str) {
1274 ts_ms_t ts = 0;
1275 str_to_ts_ms(str, ts);
1276 return ts;
1277 }
1278
1284 inline fts_t fts(const std::string& str) {
1285 fts_t ts = 0;
1286 str_to_fts(str, ts);
1287 return ts;
1288 }
1289
1290//------------------------------------------------------------------------------
1291
1303 template<class T = int>
1304 inline bool sec_of_day(const std::string& str, T& sec) {
1305 if (str.empty()) return false;
1306
1307 const char* p = str.c_str();
1308 int parts[3] = {0, 0, 0}; // hour, minute, second
1309 int idx = 0;
1310
1311 while (*p && idx < 3) {
1312 // Parse integer
1313 int value = 0;
1314 bool has_digit = false;
1315
1316 while (*p >= '0' && *p <= '9') {
1317 has_digit = true;
1318 value = value * 10 + (*p - '0');
1319 ++p;
1320 }
1321
1322 if (!has_digit) return false;
1323 parts[idx++] = value;
1324
1325 // Expect colon or end
1326 if (*p == ':') {
1327 ++p;
1328 } else if (*p == '\0') {
1329 break;
1330 } else {
1331 return false; // unexpected character
1332 }
1333 }
1334
1335 if (idx == 0) return false;
1336 if (!is_valid_time(parts[0], parts[1], parts[2])) return false;
1337
1338 sec = static_cast<T>(sec_of_day(parts[0], parts[1], parts[2]));
1339 return true;
1340 }
1341
1352 template<class T = int>
1353 inline T sec_of_day(const std::string& str) {
1354 T value{};
1355 if (sec_of_day(str, value))
1356 return value;
1357 return static_cast<T>(SEC_PER_DAY);
1358 }
1359
1361
1362};
1363
1364#endif // _TIME_SHIELD_TIME_PARSER_HPP_INCLUDED
Header file with time-related constants.
Header for date and time structure and related functions.
Header file with enumerations for weekdays, months, and other time-related categories.
constexpr int64_t SEC_PER_DAY
Seconds per day.
TIME_SHIELD_CONSTEXPR bool workday(ts_t ts) noexcept
Alias for is_workday(ts_t).
TIME_SHIELD_CONSTEXPR ts_t ts(year_t year, int month, int day)
Alias for to_timestamp.
DateStruct iso_week_date_to_date(const IsoWeekDateStruct &iso_date)
Convert ISO week date to calendar date.
bool parse_iso_week_date(const char *input, std::size_t length, IsoWeekDateStruct &iso_date) noexcept
Parse ISO week date string buffer.
TIME_SHIELD_CONSTEXPR bool workday_ms(ts_ms_t ts_ms) noexcept
Alias for is_workday(ts_ms_t).
TIME_SHIELD_CONSTEXPR T1 sec_to_ms(T2 ts) noexcept
Converts a timestamp from seconds to milliseconds.
@ JST
Japan Standard Time.
Definition enums.hpp:200
@ GST
Gulf Standard Time.
Definition enums.hpp:198
@ PHT
Philippine Time.
Definition enums.hpp:197
@ BYT
Belarus Time.
Definition enums.hpp:194
@ KST
Korea Standard Time.
Definition enums.hpp:201
@ ET
US Eastern Time.
Definition enums.hpp:185
@ EET
Eastern European Time.
Definition enums.hpp:179
@ CEST
Central European Summer Time.
Definition enums.hpp:183
@ WIT
Eastern Indonesia Time.
Definition enums.hpp:191
@ WEST
Western European Summer Time.
Definition enums.hpp:184
@ WITA
Central Indonesia Time.
Definition enums.hpp:190
@ MYT
Malaysia Time.
Definition enums.hpp:188
@ HKT
Hong Kong Time.
Definition enums.hpp:199
@ WET
Western European Time.
Definition enums.hpp:181
@ KZT
Kazakhstan Time.
Definition enums.hpp:192
@ UNKNOWN
Unknown Time Zone.
Definition enums.hpp:202
@ TRT
Turkey Time.
Definition enums.hpp:193
@ UTC
Coordinated Universal Time.
Definition enums.hpp:178
@ ICT
Indochina Time.
Definition enums.hpp:196
@ GMT
Greenwich Mean Time.
Definition enums.hpp:177
@ SGT
Singapore Time.
Definition enums.hpp:195
@ WIB
Western Indonesia Time.
Definition enums.hpp:189
@ EEST
Eastern European Summer Time.
Definition enums.hpp:182
@ CT
US Central Time.
Definition enums.hpp:186
@ CET
Central European Time.
Definition enums.hpp:180
@ IST
India Standard Time.
Definition enums.hpp:187
bool parse_iso8601(const char *input, std::size_t length, DateTimeStruct &dt, TimeZoneStruct &tz) noexcept
Parse ISO8601 character buffer into DateTimeStruct and TimeZoneStruct.
bool is_last_workday_of_month(const std::string &str)
Parse an ISO8601 string and check if it is the last workday of its month (seconds).
bool str_to_ts_ms(const std::string &str, ts_ms_t &ts)
Convert an ISO8601 string to a millisecond timestamp (ts_ms_t).
bool is_within_last_workdays_of_month_ms(const std::string &str, int count)
Parse ISO8601 string and check if it is within last N workdays of its month (milliseconds).
int parse_month(const std::string &month)
Parse month name token into month index [1..12].
bool try_get_month_index(const std::string &month, int &value)
Try parse month name token into month index [1..12].
bool parse_time_zone_name(const char *data, std::size_t length, TimeZone &zone) noexcept
Parse named time zone character buffer into TimeZone enum.
bool try_parse_month_enum(const std::string &month, T &value)
Try parse month name token into Month enum (or any T).
bool parse_time_zone(const char *data, std::size_t length, TimeZoneStruct &tz) noexcept
Parse timezone character buffer into TimeZoneStruct.
int get_month_index(const std::string &month)
Parse month name token into month index [1..12].
bool is_first_workday_of_month_ms(const std::string &str)
Parse an ISO8601 string and check if it is the first workday of its month (millisecond precision).
bool try_get_month_number(const std::string &month, T &value)
Try get the month number by name, with output parameter.
Month get_month_index_enum(const std::string &month)
Parse month name token into Month enum.
bool is_within_first_workdays_of_month_ms(const std::string &str, int count)
Parse an ISO8601 string and check if it falls within the first N workdays of its month (millisecond p...
bool is_workday(const std::string &str)
Parse ISO8601 string and check if it falls on a workday (seconds precision).
bool sec_of_day(const std::string &str, T &sec)
Parse time of day string to seconds of day.
bool is_last_workday_of_month_ms(const std::string &str)
Parse an ISO8601 string and check if it is the last workday of its month (millisecond).
bool parse_tz(const std::string &tz_str, TimeZoneStruct &tz) noexcept
Alias for parse_time_zone.
bool parse_tz_name(const char *data, std::size_t length, TimeZone &zone) noexcept
Alias for parse_time_zone_name.
bool is_within_first_workdays_of_month(const std::string &str, int count)
Parse an ISO8601 string and check if it falls within the first N workdays of its month.
bool is_first_workday_of_month(const std::string &str)
Parse ISO8601 string and check if it is the first workday of its month (seconds).
bool try_parse_month(const std::string &month, int &value)
Try parse month name token into month index [1..12].
T get_month_number(const std::string &month)
Get the month number by name (throwing).
bool str_to_ts(const std::string &str, ts_t &ts)
Convert an ISO8601 string to a timestamp (ts_t).
bool str_to_fts(const std::string &str, fts_t &ts)
Convert an ISO8601 string to a floating-point timestamp (fts_t).
bool is_within_last_workdays_of_month(const std::string &str, int count)
Parse ISO8601 string and check if it is within last N workdays of its month (seconds).
T parse_month_enum(const std::string &month)
Parse month name token into Month enum (throwing).
bool is_workday_ms(const std::string &str)
Parse ISO8601 string and check if it falls on a workday (milliseconds precision).
TIME_SHIELD_CONSTEXPR tz_t to_offset(const TimeZoneStruct &tz) noexcept
Alias for time_zone_struct_to_offset.
TIME_SHIELD_CONSTEXPR fts_t dt_to_ftimestamp(const T &date_time)
Converts a date-time structure to a floating-point timestamp.
TIME_SHIELD_CONSTEXPR T month_of_year(ts_t ts) noexcept
Get the month of the year.
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 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.
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
int64_t ts_ms_t
Unix timestamp in milliseconds since epoch.
Definition types.hpp:50
double fts_t
Floating-point timestamp (fractional seconds since epoch).
Definition types.hpp:52
int64_t year_t
Year as an integer (e.g., 2024).
Definition types.hpp:41
ts_t ts() noexcept
Get the current UTC timestamp in seconds.
ts_ms_t ts_ms() noexcept
Get the current UTC timestamp in milliseconds.
fts_t fts() noexcept
Get the current UTC timestamp in floating-point seconds.
TIME_SHIELD_CONSTEXPR bool is_valid_time_zone(T hour, T min) noexcept
Check if the time zone is valid.
TIME_SHIELD_CONSTEXPR bool is_valid_date(T1 year, T2 month, T2 day) noexcept
Checks the correctness of the specified date.
TIME_SHIELD_CONSTEXPR bool is_valid_time(T1 hour, T1 min, T1 sec, T2 ms=0) noexcept
Checks the correctness of the specified time.
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.
Conversions and utilities for ISO week dates (ISO 8601).
TIME_SHIELD_CONSTEXPR bool parse_fraction_to_ms(const char *&p, const char *end, int &ms_out) noexcept
Parse fractional seconds (1..9 digits) and convert to milliseconds.
TIME_SHIELD_CONSTEXPR bool is_ascii_digit(char c) noexcept
Check whether character is ASCII digit.
TIME_SHIELD_CONSTEXPR bool parse_2digits(const char *&p, const char *end, int &out) noexcept
Parse exactly 2 digits into int.
TIME_SHIELD_CONSTEXPR bool is_ascii_space(char c) noexcept
Check whether character is ASCII whitespace.
void normalise_month_token_lower(const std::string &month, std::string &output)
Normalize month token to lower-case ASCII using current locale facet.
const std::array< ZoneNameEntry, 25 > & time_zone_name_entries() noexcept
Return supported strict named-zone entries.
std::string trim_copy_ascii(const std::string &s)
Trim ASCII whitespace from both ends.
TIME_SHIELD_CONSTEXPR void skip_spaces(const char *&p, const char *end) noexcept
Skip ASCII whitespace.
bool try_parse_time_zone_name_token(const char *data, std::size_t length, TimeZone &zone) noexcept
Parse strict named-zone token without trimming.
TIME_SHIELD_CONSTEXPR bool parse_4digits_year(const char *&p, const char *end, year_t &out) noexcept
Parse exactly 4 digits into year_t (via int).
std::string_view trim_view_ascii(std::string_view v)
Trim ASCII whitespace from both ends (string_view).
bool try_parse_month_index(const std::string &month, int &value)
Try parse month name token into month index (1..12).
int parse_month_index(const std::string &month)
Parse month name token into month index (1..12).
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.
Structure to represent time zone information.
Umbrella header for time conversion functions.
Header file for fast parsing with formatter-compatible custom patterns.
Header for time zone structure and related functions.
Header file with time-related validation functions.