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"
26
27#include <algorithm>
28#include <locale>
29#include <array>
30#include <stdexcept>
31#include <cctype>
32#include <string>
33
34#if __cplusplus >= 201703L
35# include <string_view>
36#endif
37
38namespace time_shield {
39
71
72 namespace detail {
73
75 inline std::string trim_copy_ascii(const std::string& s) {
76 size_t b = 0;
77 size_t e = s.size();
78 while (b < e && std::isspace(static_cast<unsigned char>(s[b])) != 0) ++b;
79 while (e > b && std::isspace(static_cast<unsigned char>(s[e - 1])) != 0) --e;
80 return s.substr(b, e - b);
81 }
82
83# if __cplusplus >= 201703L
85 inline std::string_view trim_view_ascii(std::string_view v) {
86 size_t b = 0;
87 size_t e = v.size();
88 while (b < e && std::isspace(static_cast<unsigned char>(v[b])) != 0) ++b;
89 while (e > b && std::isspace(static_cast<unsigned char>(v[e - 1])) != 0) --e;
90 return v.substr(b, e - b);
91 }
92# endif
93
97 inline void normalise_month_token_lower(const std::string& month, std::string& output) {
98 output = trim_copy_ascii(month);
99 if (output.empty()) return;
100
101 const auto& facet = std::use_facet<std::ctype<char>>(std::locale());
102 std::transform(output.begin(), output.end(), output.begin(),
103 [&facet](char ch) { return facet.tolower(ch); });
104 }
105
106# if __cplusplus >= 201703L
110 inline void normalise_month_token_lower(std::string_view month, std::string& output) {
111 month = trim_view_ascii(month);
112 output.assign(month.begin(), month.end());
113 if (output.empty()) return;
114
115 const auto& facet = std::use_facet<std::ctype<char>>(std::locale());
116 std::transform(output.begin(), output.end(), output.begin(),
117 [&facet](char ch) { return facet.tolower(ch); });
118 }
119# endif
120
125 inline bool try_parse_month_index(const std::string& month, int& value) {
126 if (month.empty()) return false;
127
128 std::string month_copy;
129 normalise_month_token_lower(month, month_copy);
130 if (month_copy.empty()) return false;
131
132 static const std::array<const char*, MONTHS_PER_YEAR> short_names = {
133 "jan", "feb", "mar", "apr", "may", "jun",
134 "jul", "aug", "sep", "oct", "nov", "dec"
135 };
136 static const std::array<const char*, MONTHS_PER_YEAR> full_names = {
137 "january", "february", "march", "april", "may", "june",
138 "july", "august", "september", "october", "november", "december"
139 };
140
141 for (std::size_t i = 0; i < short_names.size(); ++i) {
142 if (month_copy == short_names[i] || month_copy == full_names[i]) {
143 value = static_cast<int>(i) + 1;
144 return true;
145 }
146 }
147
148 return false;
149 }
150
151# if __cplusplus >= 201703L
156 inline bool try_parse_month_index(std::string_view month, int& value) {
157 if (month.empty()) return false;
158
159 std::string month_copy;
160 normalise_month_token_lower(month, month_copy);
161 if (month_copy.empty()) return false;
162
163 static const std::array<const char*, MONTHS_PER_YEAR> short_names = {
164 "jan", "feb", "mar", "apr", "may", "jun",
165 "jul", "aug", "sep", "oct", "nov", "dec"
166 };
167 static const std::array<const char*, MONTHS_PER_YEAR> full_names = {
168 "january", "february", "march", "april", "may", "june",
169 "july", "august", "september", "october", "november", "december"
170 };
171
172 for (std::size_t i = 0; i < short_names.size(); ++i) {
173 if (month_copy == short_names[i] || month_copy == full_names[i]) {
174 value = static_cast<int>(i) + 1;
175 return true;
176 }
177 }
178
179 return false;
180 }
181# endif
182
187 inline int parse_month_index(const std::string& month) {
188 int value = 0;
189 if (!try_parse_month_index(month, value)) {
190 throw std::invalid_argument("Invalid month name");
191 }
192 return value;
193 }
194
195# if __cplusplus >= 201703L
200 inline int parse_month_index(std::string_view month) {
201 int value = 0;
202 if (!try_parse_month_index(month, value)) {
203 throw std::invalid_argument("Invalid month name");
204 }
205 return value;
206 }
207# endif
208
209//------------------------------------------------------------------------------
210// Small C-style helpers (no lambdas, no detail namespace)
211//------------------------------------------------------------------------------
212
214 TIME_SHIELD_CONSTEXPR inline bool is_ascii_space(char c) noexcept {
215 return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f' || c == '\v';
216 }
217
219 TIME_SHIELD_CONSTEXPR inline bool is_ascii_digit(char c) noexcept {
220 return c >= '0' && c <= '9';
221 }
222
224 TIME_SHIELD_CONSTEXPR inline void skip_spaces(const char*& p, const char* end) noexcept {
225 while (p < end && is_ascii_space(*p)) {
226 ++p;
227 }
228 }
229
232 TIME_SHIELD_CONSTEXPR inline bool parse_2digits(const char*& p, const char* end, int& out) noexcept {
233 if (end - p < 2) {
234 return false;
235 }
236 const char a = p[0];
237 const char b = p[1];
238 if (!is_ascii_digit(a) || !is_ascii_digit(b)) {
239 return false;
240 }
241 out = (a - '0') * 10 + (b - '0');
242 p += 2;
243 return true;
244 }
245
248 TIME_SHIELD_CONSTEXPR inline bool parse_4digits_year(const char*& p, const char* end, year_t& out) noexcept {
249 if (end - p < 4) {
250 return false;
251 }
252 const char a = p[0], b = p[1], c = p[2], d = p[3];
253 if (!is_ascii_digit(a) || !is_ascii_digit(b) || !is_ascii_digit(c) || !is_ascii_digit(d)) {
254 return false;
255 }
256 const int v = (a - '0') * 1000 + (b - '0') * 100 + (c - '0') * 10 + (d - '0');
257 out = static_cast<year_t>(v);
258 p += 4;
259 return true;
260 }
261
265 TIME_SHIELD_CONSTEXPR inline bool parse_fraction_to_ms(const char*& p, const char* end, int& ms_out) noexcept {
266 if (p >= end || !is_ascii_digit(*p)) {
267 return false;
268 }
269
270 int ms = 0;
271 int digits = 0;
272
273 while (p < end && is_ascii_digit(*p)) {
274 if (digits >= 3) {
275 return false;
276 }
277 ms = ms * 10 + (*p - '0');
278 ++digits;
279 ++p;
280 }
281
282 if (digits == 1) {
283 ms *= 100;
284 } else if (digits == 2) {
285 ms *= 10;
286 }
287
288 ms_out = ms;
289 return true;
290 }
291
292 } // namespace detail
293
294//------------------------------------------------------------------------------
295// Month helpers (public)
296//------------------------------------------------------------------------------
297
298// Canonical API (recommended):
299// - parse_month(...) / try_parse_month(...): return month index as int [1..12]
300// - parse_month_enum(...) / try_parse_month_enum(...): return month as enum Month (or any integral/enum T)
301
306 inline bool try_parse_month(const std::string& month, int& value) {
307 return detail::try_parse_month_index(month, value);
308 }
309
314 inline int parse_month(const std::string& month) {
315 return detail::parse_month_index(month);
316 }
317
318#if __cplusplus >= 201703L
323 inline bool try_parse_month(std::string_view month, int& value) {
324 return detail::try_parse_month_index(month, value);
325 }
326
331 inline int parse_month(std::string_view month) {
332 return detail::parse_month_index(month);
333 }
334#endif
335
336// Canonical: parse month -> enum Month (or any T)
337
343 template<class T = Month>
344 inline T parse_month_enum(const std::string& month) {
345 return static_cast<T>(detail::parse_month_index(month));
346 }
347
353 template<class T = Month>
354 inline bool try_parse_month_enum(const std::string& month, T& value) {
355 int idx = 0;
356 if (!detail::try_parse_month_index(month, idx)) return false;
357 value = static_cast<T>(idx);
358 return true;
359 }
360
361#if __cplusplus >= 201703L
367 template<class T = Month>
368 inline T parse_month_enum(std::string_view month) {
369 return static_cast<T>(detail::parse_month_index(month));
370 }
371
377 template<class T = Month>
378 inline bool try_parse_month_enum(std::string_view month, T& value) {
379 int idx = 0;
380 if (!detail::try_parse_month_index(month, idx)) return false;
381 value = static_cast<T>(idx);
382 return true;
383 }
384#endif
385
386// Index aliases (int)
387
392 inline bool try_get_month_index(const std::string& month, int& value) {
393 return try_parse_month(month, value);
394 }
395
400 inline int get_month_index(const std::string& month) {
401 return parse_month(month);
402 }
403
408 inline Month get_month_index_enum(const std::string& month) {
409 return static_cast<Month>(detail::parse_month_index(month));
410 }
411
412#if __cplusplus >= 201703L
414 inline bool try_get_month_index(std::string_view month, int& value) {
415 return try_parse_month(month, value);
416 }
417
419 inline int get_month_index(std::string_view month) {
420 return parse_month(month);
421 }
422
424 inline Month get_month_index_enum(std::string_view month) {
425 return static_cast<Month>(detail::parse_month_index(month));
426 }
427#endif
428
429// Month number aliases (T)
430
436 template<class T = Month>
437 inline T get_month_number(const std::string& month) {
438 return parse_month_enum<T>(month);
439 }
440
442 template<class T = Month>
443 inline T month_of_year(const std::string& month) {
444 return get_month_number<T>(month);
445 }
446
452 template<class T = Month>
453 inline bool try_get_month_number(const std::string& month, T& value) {
454 return try_parse_month_enum<T>(month, value);
455 }
456
458 template<class T = Month>
459 inline bool get_month_number(const std::string& month, T& value) {
460 return try_get_month_number<T>(month, value);
461 }
462
464 template<class T = Month>
465 inline bool month_of_year(const std::string& month, T& value) {
466 return try_get_month_number<T>(month, value);
467 }
468
469#if __cplusplus >= 201703L
471 template<class T = Month>
472 inline T get_month_number(std::string_view month) {
473 return parse_month_enum<T>(month);
474 }
475
477 template<class T = Month>
478 inline T month_of_year(std::string_view month) {
479 return get_month_number<T>(month);
480 }
481
483 template<class T = Month>
484 inline bool try_get_month_number(std::string_view month, T& value) {
485 return try_parse_month_enum<T>(month, value);
486 }
487
489 template<class T = Month>
490 inline bool get_month_number(std::string_view month, T& value) {
491 return try_get_month_number<T>(month, value);
492 }
493
495 template<class T = Month>
496 inline bool month_of_year(std::string_view month, T& value) {
497 return try_get_month_number<T>(month, value);
498 }
499#endif
500
501// const char* overloads to avoid ambiguity with string vs string_view for literals
502
508 template<class T = Month>
509 inline T get_month_number(const char* month) {
510#if __cplusplus >= 201703L
511 return get_month_number<T>(std::string_view(month));
512#else
513 return get_month_number<T>(std::string(month));
514#endif
515 }
516
522 template<class T = Month>
523 inline bool try_get_month_number(const char* month, T& value) {
524#if __cplusplus >= 201703L
525 return try_get_month_number<T>(std::string_view(month), value);
526#else
527 return try_get_month_number<T>(std::string(month), value);
528#endif
529 }
530
532 template<class T = Month>
533 inline T month_of_year(const char* month) {
534 return get_month_number<T>(month);
535 }
536
538 template<class T = Month>
539 inline bool get_month_number(const char* month, T& value) {
540 return try_get_month_number<T>(month, value);
541 }
542
544 template<class T = Month>
545 inline bool month_of_year(const char* month, T& value) {
546 return try_get_month_number<T>(month, value);
547 }
548
549//------------------------------------------------------------------------------
550// Time zone parsing (C-style, high performance)
551//------------------------------------------------------------------------------
552
562 inline bool parse_time_zone(const char* data, std::size_t length, TimeZoneStruct& tz) noexcept {
563 if (!data) {
564 return false;
565 }
566
567 if (length == 0) {
568 tz.hour = 0;
569 tz.min = 0;
570 tz.is_positive = true;
571 return true;
572 }
573
574 if (length == 1 && (data[0] == 'Z' || data[0] == 'z')) {
575 tz.hour = 0;
576 tz.min = 0;
577 tz.is_positive = true;
578 return true;
579 }
580
581 if (length != 6) {
582 return false;
583 }
584
585 const char sign = data[0];
586 if (sign != '+' && sign != '-') {
587 return false;
588 }
589 if (data[3] != ':') {
590 return false;
591 }
592 if (!detail::is_ascii_digit(data[1]) || !detail::is_ascii_digit(data[2]) ||
593 !detail::is_ascii_digit(data[4]) || !detail::is_ascii_digit(data[5])) {
594 return false;
595 }
596
597 tz.is_positive = (sign == '+');
598 tz.hour = (data[1] - '0') * 10 + (data[2] - '0');
599 tz.min = (data[4] - '0') * 10 + (data[5] - '0');
600
601 return is_valid_time_zone(tz);
602 }
603
606 inline bool parse_time_zone(const std::string& tz_str, TimeZoneStruct& tz) noexcept {
607 return parse_time_zone(tz_str.c_str(), tz_str.size(), tz);
608 }
609
611 inline bool parse_tz(const std::string& tz_str, TimeZoneStruct& tz) noexcept {
612 return parse_time_zone(tz_str, tz);
613 }
614
616 inline bool parse_tz(const char* data, std::size_t length, TimeZoneStruct& tz) noexcept {
617 return parse_time_zone(data, length, tz);
618 }
619
620//------------------------------------------------------------------------------
621// ISO8601 parsing (C-style, no regex, no allocations)
622//------------------------------------------------------------------------------
623
640 inline bool parse_iso8601(const char* input, std::size_t length,
641 DateTimeStruct& dt, TimeZoneStruct& tz) noexcept {
642 if (!input) {
643 return false;
644 }
645
646 const char* p = input;
647 const char* end = input + length;
648
649 detail::skip_spaces(p, end);
650
652 tz = create_time_zone_struct(0, 0);
653 tz.is_positive = true;
654
655 const char* const date_start = p;
656 const char* date_end = p;
657 while (date_end < end && *date_end != 'T' && *date_end != 't' && !detail::is_ascii_space(*date_end)) {
658 ++date_end;
659 }
660
661 bool parsed_iso_week_date = false;
662 if (date_end > date_start) {
663 IsoWeekDateStruct iso_date{};
664 if (parse_iso_week_date(date_start, static_cast<std::size_t>(date_end - date_start), iso_date)) {
665 const DateStruct calendar_date = iso_week_date_to_date(iso_date);
666 dt.year = calendar_date.year;
667 dt.mon = calendar_date.mon;
668 dt.day = calendar_date.day;
669 p = date_end;
670 parsed_iso_week_date = true;
671 }
672 }
673
674 if (!parsed_iso_week_date) {
675 // ---- Date: YYYY<sep>MM<sep>DD
676 if (!detail::parse_4digits_year(p, end, dt.year)) {
677 return false;
678 }
679 if (p >= end) {
680 return false;
681 }
682 const char sep1 = *p;
683 if (sep1 != '-' && sep1 != '/' && sep1 != '.') {
684 return false;
685 }
686 ++p;
687
688 if (!detail::parse_2digits(p, end, dt.mon)) {
689 return false;
690 }
691 if (p >= end) {
692 return false;
693 }
694 const char sep2 = *p;
695 if (sep2 != '-' && sep2 != '/' && sep2 != '.') {
696 return false;
697 }
698 ++p;
699
700 if (!detail::parse_2digits(p, end, dt.day)) {
701 return false;
702 }
703 }
704
705 if (!is_valid_date(dt.year, dt.mon, dt.day)) {
706 return false;
707 }
708
709 // Date-only?
710 {
711 const char* q = p;
712 detail::skip_spaces(q, end);
713 if (q == end) {
714 // dt already has time=0 ms=0
715 return is_valid_date_time(dt);
716 }
717 }
718
719 // ---- Date/time separator: 'T' or whitespace
720 if (p >= end) {
721 return false;
722 }
723
724 if (*p == 'T' || *p == 't') {
725 ++p;
726 } else
727 if (detail::is_ascii_space(*p)) {
728 // allow one or more spaces
729 detail::skip_spaces(p, end);
730 } else {
731 return false;
732 }
733
734 // ---- Time: hh:mm[:ss][.frac]
735 if (!detail::parse_2digits(p, end, dt.hour)) {
736 return false;
737 }
738 if (p >= end || *p != ':') {
739 return false;
740 }
741 ++p;
742
743 if (!detail::parse_2digits(p, end, dt.min)) {
744 return false;
745 }
746
747 dt.sec = 0;
748 dt.ms = 0;
749 bool has_seconds = false;
750
751 // Optional :ss
752 if (p < end && *p == ':') {
753 ++p;
754 if (!detail::parse_2digits(p, end, dt.sec)) {
755 return false;
756 }
757 has_seconds = true;
758 }
759
760 // Optional .fraction (allowed only if we had seconds in original regex,
761 // but we accept it when seconds are present; for hh:mm (no seconds) we keep it strict).
762 if (p < end && *p == '.') {
763 // require seconds field to exist (avoid accepting YYYY-MM-DDThh:mm.xxx)
764 if (!has_seconds) {
765 // Ambiguous: could be "hh:mm.fff" which is not in your original formats.
766 // Keep strict to preserve behavior.
767 return false;
768 }
769
770 ++p;
771 int ms = 0;
772 if (!detail::parse_fraction_to_ms(p, end, ms)) {
773 return false;
774 }
775 dt.ms = ms;
776 }
777
778 // ---- Optional timezone: [spaces] (Z | ±HH:MM)
779 detail::skip_spaces(p, end);
780
781 if (p < end) {
782 if (*p == 'Z' || *p == 'z') {
783 tz.hour = 0;
784 tz.min = 0;
785 tz.is_positive = true;
786 ++p;
787 } else if (*p == '+' || *p == '-') {
788 // need 6 chars
789 if (static_cast<std::size_t>(end - p) < 6) {
790 return false;
791 }
792 if (!parse_time_zone(p, 6, tz)) {
793 return false;
794 }
795 p += 6;
796 }
797 }
798
799 detail::skip_spaces(p, end);
800 if (p != end) {
801 return false;
802 }
803
804 return is_valid_date_time(dt);
805 }
806
809 inline bool parse_iso8601(const std::string& input, DateTimeStruct& dt, TimeZoneStruct& tz) noexcept {
810 return parse_iso8601(input.c_str(), input.size(), dt, tz);
811 }
812
813//------------------------------------------------------------------------------
814// ISO8601 -> timestamps
815//------------------------------------------------------------------------------
816
821 inline bool str_to_ts(const std::string& str, ts_t& ts) {
824 if (!parse_iso8601(str, dt, tz)) return false;
825 try {
826 ts = dt_to_timestamp(dt) + to_offset(tz);
827 return true;
828 } catch (...) {}
829 return false;
830 }
831
837 inline bool str_to_ts(const char* data, std::size_t length, ts_t& ts) {
838 if (!data || length == 0) {
839 ts = 0;
840 return false;
841 }
842 // Без string_view (C++11) просто собираем std::string по длине.
843 return str_to_ts(std::string(data, length), ts);
844 }
845
850 inline bool str_to_ts_ms(const std::string& str, ts_ms_t& ts) {
853 if (!parse_iso8601(str, dt, tz)) return false;
854 try {
855 ts = static_cast<ts_ms_t>(dt_to_timestamp_ms(dt)) + sec_to_ms(to_offset(tz));
856 return true;
857 } catch (...) {}
858 return false;
859 }
860
866 inline bool str_to_ts_ms(const char* data, std::size_t length, ts_ms_t& ts) {
867 if (!data || length == 0) {
868 ts = 0;
869 return false;
870 }
871 return str_to_ts_ms(std::string(data, length), ts);
872 }
873
878 inline bool str_to_fts(const std::string& str, fts_t& ts) {
881 if (!parse_iso8601(str, dt, tz)) return false;
882 try {
883 ts = dt_to_ftimestamp(dt) + static_cast<fts_t>(to_offset(tz));
884 return true;
885 } catch (...) {}
886 return false;
887 }
888
894 inline bool str_to_fts(const char* data, std::size_t length, fts_t& ts) {
895 if (!data || length == 0) {
896 ts = 0;
897 return false;
898 }
899 return str_to_fts(std::string(data, length), ts);
900 }
901
902//------------------------------------------------------------------------------
903// Convenience string -> predicates (workdays)
904//------------------------------------------------------------------------------
905
907 inline bool is_workday(const std::string& str) {
908 ts_t ts = 0;
909 if (!str_to_ts(str, ts)) return false;
910 return is_workday(ts);
911 }
912
914 inline bool is_workday_ms(const std::string& str) {
915 ts_ms_t ts = 0;
916 if (!str_to_ts_ms(str, ts)) return false;
917 return is_workday_ms(ts);
918 }
919
922 inline bool workday(const std::string& str) {
923 return is_workday(str);
924 }
925
928 inline bool workday_ms(const std::string& str) {
929 return is_workday_ms(str);
930 }
931
935 inline bool is_first_workday_of_month(const std::string& str) {
936 ts_t ts = 0;
937 if (!str_to_ts(str, ts)) return false;
939 }
940
944 inline bool is_first_workday_of_month_ms(const std::string& str) {
945 ts_ms_t ts = 0;
946 if (!str_to_ts_ms(str, ts)) return false;
948 }
949
953 inline bool is_last_workday_of_month(const std::string& str) {
954 ts_t ts = 0;
955 if (!str_to_ts(str, ts)) return false;
957 }
958
962 inline bool is_last_workday_of_month_ms(const std::string& str) {
963 ts_ms_t ts = 0;
964 if (!str_to_ts_ms(str, ts)) return false;
966 }
967
972 inline bool is_within_first_workdays_of_month(const std::string& str, int count) {
973 ts_t ts = 0;
974 if (!str_to_ts(str, ts)) return false;
976 }
977
982 inline bool is_within_first_workdays_of_month_ms(const std::string& str, int count) {
983 ts_ms_t ts = 0;
984 if (!str_to_ts_ms(str, ts)) return false;
986 }
987
992 inline bool is_within_last_workdays_of_month(const std::string& str, int count) {
993 ts_t ts = 0;
994 if (!str_to_ts(str, ts)) return false;
996 }
997
1002 inline bool is_within_last_workdays_of_month_ms(const std::string& str, int count) {
1003 ts_ms_t ts = 0;
1004 if (!str_to_ts_ms(str, ts)) return false;
1006 }
1007
1008//------------------------------------------------------------------------------
1009// Convenience: C-string wrappers (non-throwing, ambiguous on failure)
1010//------------------------------------------------------------------------------
1011
1016 inline ts_t ts(const char* str) {
1017 ts_t out = 0;
1018 str_to_ts(str ? std::string(str) : std::string(), out);
1019 return out;
1020 }
1021
1028 inline ts_t ts(const char* data, std::size_t length) {
1029 ts_t out = 0;
1030 if (!str_to_ts(data, length, out)) {
1031 return 0;
1032 }
1033 return out;
1034 }
1035
1040 inline ts_ms_t ts_ms(const char* str) {
1041 ts_ms_t out = 0;
1042 str_to_ts_ms(str ? std::string(str) : std::string(), out);
1043 return out;
1044 }
1045
1051 inline ts_ms_t ts_ms(const char* data, std::size_t length) {
1052 ts_ms_t out = 0;
1053 if (!str_to_ts_ms(data, length, out)) {
1054 return 0;
1055 }
1056 return out;
1057 }
1058
1063 inline fts_t fts(const char* str) {
1064 fts_t out = 0;
1065 str_to_fts(str ? std::string(str) : std::string(), out);
1066 return out;
1067 }
1068
1074 inline fts_t fts(const char* data, std::size_t length) {
1075 fts_t out = 0;
1076 if (!str_to_fts(data, length, out)) {
1077 return 0.0;
1078 }
1079 return out;
1080 }
1081
1082//------------------------------------------------------------------------------
1083
1089 inline ts_t ts(const std::string& str) {
1090 ts_t ts = 0;
1091 str_to_ts(str, ts);
1092 return ts;
1093 }
1094
1100 inline ts_ms_t ts_ms(const std::string& str) {
1101 ts_ms_t ts = 0;
1102 str_to_ts_ms(str, ts);
1103 return ts;
1104 }
1105
1111 inline fts_t fts(const std::string& str) {
1112 fts_t ts = 0;
1113 str_to_fts(str, ts);
1114 return ts;
1115 }
1116
1117//------------------------------------------------------------------------------
1118
1130 template<class T = int>
1131 inline bool sec_of_day(const std::string& str, T& sec) {
1132 if (str.empty()) return false;
1133
1134 const char* p = str.c_str();
1135 int parts[3] = {0, 0, 0}; // hour, minute, second
1136 int idx = 0;
1137
1138 while (*p && idx < 3) {
1139 // Parse integer
1140 int value = 0;
1141 bool has_digit = false;
1142
1143 while (*p >= '0' && *p <= '9') {
1144 has_digit = true;
1145 value = value * 10 + (*p - '0');
1146 ++p;
1147 }
1148
1149 if (!has_digit) return false;
1150 parts[idx++] = value;
1151
1152 // Expect colon or end
1153 if (*p == ':') {
1154 ++p;
1155 } else if (*p == '\0') {
1156 break;
1157 } else {
1158 return false; // unexpected character
1159 }
1160 }
1161
1162 if (idx == 0) return false;
1163 if (!is_valid_time(parts[0], parts[1], parts[2])) return false;
1164
1165 sec = static_cast<T>(sec_of_day(parts[0], parts[1], parts[2]));
1166 return true;
1167 }
1168
1179 template<class T = int>
1180 inline T sec_of_day(const std::string& str) {
1181 T value{};
1182 if (sec_of_day(str, value))
1183 return value;
1184 return static_cast<T>(SEC_PER_DAY);
1185 }
1186
1188
1189};
1190
1191#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).
constexpr T1 sec_to_ms(T2 ts) noexcept
Converts a timestamp from seconds to milliseconds.
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 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 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_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.
TIME_SHIELD_CONSTEXPR ts_t dt_to_timestamp_ms(const T &date_time)
Converts a date-time structure to a timestamp in milliseconds.
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
int64_t ts_ms_t
Unix timestamp in milliseconds since epoch.
Definition types.hpp:49
double fts_t
Floating-point timestamp (fractional seconds since epoch).
Definition types.hpp:51
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.
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.
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 for time zone structure and related functions.
Header file with time-related validation functions.