3#ifndef TIME_SHIELD_HEADER_TEXT_TIME_PARSER_HPP_INCLUDED
4#define TIME_SHIELD_HEADER_TEXT_TIME_PARSER_HPP_INCLUDED
33#if __cplusplus >= 201703L
34# include <string_view>
77 while (b < e && std::isspace(
static_cast<unsigned char>(s[b])) != 0) ++b;
78 while (e > b && std::isspace(
static_cast<unsigned char>(s[e - 1])) != 0) --e;
79 return s.substr(b, e - b);
82# if __cplusplus >= 201703L
87 while (b < e && std::isspace(
static_cast<unsigned char>(v[b])) != 0) ++b;
88 while (e > b && std::isspace(
static_cast<unsigned char>(v[e - 1])) != 0) --e;
89 return v.substr(b, e - b);
98 if (output.empty())
return;
100 const auto& facet = std::use_facet<std::ctype<char>>(std::locale());
101 std::transform(output.begin(), output.end(), output.begin(),
102 [&facet](
char ch) { return facet.tolower(ch); });
105# if __cplusplus >= 201703L
111 output.assign(month.begin(), month.end());
112 if (output.empty())
return;
114 const auto& facet = std::use_facet<std::ctype<char>>(std::locale());
115 std::transform(output.begin(), output.end(), output.begin(),
116 [&facet](
char ch) { return facet.tolower(ch); });
125 if (month.empty())
return false;
127 std::string month_copy;
129 if (month_copy.empty())
return false;
131 static const std::array<const char*, MONTHS_PER_YEAR> short_names = {
132 "jan",
"feb",
"mar",
"apr",
"may",
"jun",
133 "jul",
"aug",
"sep",
"oct",
"nov",
"dec"
135 static const std::array<const char*, MONTHS_PER_YEAR> full_names = {
136 "january",
"february",
"march",
"april",
"may",
"june",
137 "july",
"august",
"september",
"october",
"november",
"december"
140 for (std::size_t i = 0; i < short_names.size(); ++i) {
141 if (month_copy == short_names[i] || month_copy == full_names[i]) {
142 value =
static_cast<int>(i) + 1;
150# if __cplusplus >= 201703L
156 if (month.empty())
return false;
158 std::string month_copy;
160 if (month_copy.empty())
return false;
162 static const std::array<const char*, MONTHS_PER_YEAR> short_names = {
163 "jan",
"feb",
"mar",
"apr",
"may",
"jun",
164 "jul",
"aug",
"sep",
"oct",
"nov",
"dec"
166 static const std::array<const char*, MONTHS_PER_YEAR> full_names = {
167 "january",
"february",
"march",
"april",
"may",
"june",
168 "july",
"august",
"september",
"october",
"november",
"december"
171 for (std::size_t i = 0; i < short_names.size(); ++i) {
172 if (month_copy == short_names[i] || month_copy == full_names[i]) {
173 value =
static_cast<int>(i) + 1;
189 throw std::invalid_argument(
"Invalid month name");
194# if __cplusplus >= 201703L
202 throw std::invalid_argument(
"Invalid month name");
215 static const std::array<ZoneNameEntry, 25> entries = {{
247 if (data ==
nullptr || length == 0) {
253 for (std::size_t i = 0; i < entries.size(); ++i) {
254 const std::size_t name_length = std::strlen(entries[i].name);
255 if (length == name_length && std::memcmp(data, entries[i].name, name_length) == 0) {
256 zone = entries[i].zone;
271 return c ==
' ' || c ==
'\t' || c ==
'\n' || c ==
'\r' || c ==
'\f' || c ==
'\v';
276 return c >=
'0' && c <=
'9';
281 return (c >=
'A' && c <=
'Z') || (c >=
'a' && c <=
'z');
286 return (c >=
'A' && c <=
'Z') ?
static_cast<char>(c -
'A' +
'a') : c;
290 inline bool ascii_iequals(
const char* data, std::size_t length,
const char* literal)
noexcept {
291 if (data ==
nullptr || literal ==
nullptr) {
295 for (std::size_t i = 0; i < length; ++i) {
301 return literal[length] ==
'\0';
307 if (data ==
nullptr || length == 0) {
311 const int64_t max_value = (std::numeric_limits<int64_t>::max)();
312 for (std::size_t i = 0; i < length; ++i) {
317 const int digit = data[i] -
'0';
318 if (value > (max_value - digit) / 10) {
322 value = value * 10 + digit;
330 if (lhs <= 0 || rhs <= 0) {
334 if (lhs > (std::numeric_limits<int64_t>::max)() / rhs) {
345 if (data ==
nullptr || length == 0) {
351 case 's': unit_seconds = 1;
return true;
355 case 'w': unit_seconds =
SEC_PER_DAY * 7;
return true;
356 case 'q': unit_seconds =
SEC_PER_DAY * 90;
return true;
358 default:
return false;
375 if (data ==
nullptr || length == 0) {
379 struct TimeframeUnitEntry {
384 static const TimeframeUnitEntry entries[] = {
406 for (std::size_t i = 0; i <
sizeof(entries) /
sizeof(entries[0]); ++i) {
408 unit_seconds = entries[i].seconds;
419 if (data ==
nullptr || length == 0) {
423 const char* begin = data;
424 const char* end = data + length;
436 int64_t multiplier = 1;
437 int64_t unit_seconds = 0;
441 const char* cursor = begin;
457 for (
const char* p = cursor; p < end; ++p) {
471 seconds =
static_cast<ts_t>(result);
479 const char* cursor = begin;
489 seconds =
static_cast<ts_t>(unit_seconds);
497 for (
const char* p = cursor; p < end; ++p) {
513 seconds =
static_cast<ts_t>(result);
518 TIME_SHIELD_CONSTEXPR
inline void skip_spaces(
const char*& p,
const char* end)
noexcept {
526 TIME_SHIELD_CONSTEXPR
inline bool parse_2digits(
const char*& p,
const char* end,
int& out)
noexcept {
535 out = (a -
'0') * 10 + (b -
'0');
546 const char a = p[0], b = p[1], c = p[2], d = p[3];
550 const int v = (a -
'0') * 1000 + (b -
'0') * 100 + (c -
'0') * 10 + (d -
'0');
551 out =
static_cast<year_t>(v);
559 TIME_SHIELD_CONSTEXPR
inline bool parse_fraction_to_ms(
const char*& p,
const char* end,
int& ms_out)
noexcept {
571 ms = ms * 10 + (*p -
'0');
578 }
else if (digits == 2) {
612#if __cplusplus >= 201703L
637 template<
class T = Month>
647 template<
class T = Month>
651 value =
static_cast<T
>(idx);
655#if __cplusplus >= 201703L
661 template<
class T = Month>
671 template<
class T = Month>
675 value =
static_cast<T
>(idx);
706#if __cplusplus >= 201703L
730 template<
class T = Month>
736 template<
class T = Month>
746 template<
class T = Month>
752 template<
class T = Month>
758 template<
class T = Month>
763#if __cplusplus >= 201703L
765 template<
class T = Month>
771 template<
class T = Month>
777 template<
class T = Month>
783 template<
class T = Month>
789 template<
class T = Month>
802 template<
class T = Month>
804#if __cplusplus >= 201703L
816 template<
class T = Month>
818#if __cplusplus >= 201703L
826 template<
class T = Month>
832 template<
class T = Month>
838 template<
class T = Month>
869 tz.is_positive =
true;
873 if (length == 1 && (data[0] ==
'Z' || data[0] ==
'z')) {
876 tz.is_positive =
true;
884 const char sign = data[0];
885 if (sign !=
'+' && sign !=
'-') {
888 if (data[3] !=
':') {
896 tz.is_positive = (sign ==
'+');
897 tz.hour = (data[1] -
'0') * 10 + (data[2] -
'0');
898 tz.min = (data[4] -
'0') * 10 + (data[5] -
'0');
931 std::size_t begin = 0;
932 std::size_t end = length;
933 while (begin < end && std::isspace(
static_cast<unsigned char>(data[begin])) != 0) {
936 while (end > begin && std::isspace(
static_cast<unsigned char>(data[end - 1])) != 0) {
948#if __cplusplus >= 201703L
959 if (value ==
nullptr) {
976#if __cplusplus >= 201703L
1017 const char* p = input;
1018 const char* end = input + length;
1024 tz.is_positive =
true;
1026 const char*
const date_start = p;
1027 const char* date_end = p;
1032 bool parsed_iso_week_date =
false;
1033 if (date_end > date_start) {
1035 if (
parse_iso_week_date(date_start,
static_cast<std::size_t
>(date_end - date_start), iso_date)) {
1037 dt.year = calendar_date.
year;
1038 dt.mon = calendar_date.
mon;
1039 dt.day = calendar_date.
day;
1041 parsed_iso_week_date =
true;
1045 if (!parsed_iso_week_date) {
1053 const char sep1 = *p;
1054 if (sep1 !=
'-' && sep1 !=
'/' && sep1 !=
'.') {
1065 const char sep2 = *p;
1066 if (sep2 !=
'-' && sep2 !=
'/' && sep2 !=
'.') {
1095 if (*p ==
'T' || *p ==
't') {
1109 if (p >= end || *p !=
':') {
1120 bool has_seconds =
false;
1123 if (p < end && *p ==
':') {
1133 if (p < end && *p ==
'.') {
1153 if (*p ==
'Z' || *p ==
'z') {
1156 tz.is_positive =
true;
1158 }
else if (*p ==
'+' || *p ==
'-') {
1160 if (
static_cast<std::size_t
>(end - p) < 6) {
1187 if (input ==
nullptr) {
1193# if __cplusplus >= 201703L
1226 if (!data || length == 0) {
1261 if (!data || length == 0) {
1296 if (!data || length == 0) {
1426 str_to_ts(str ? std::string(str) : std::string(), out);
1436 inline ts_t ts(
const char* data, std::size_t length) {
1450 str_to_ts_ms(str ? std::string(str) : std::string(), out);
1473 str_to_fts(str ? std::string(str) : std::string(), out);
1482 inline fts_t fts(
const char* data, std::size_t length) {
1540 if (str ==
nullptr) {
1547# if __cplusplus >= 201703L
1568 int64_t milliseconds_value = 0;
1574 milliseconds =
static_cast<ts_ms_t>(milliseconds_value);
1583 if (str ==
nullptr) {
1594 int64_t milliseconds_value = 0;
1600 milliseconds =
static_cast<ts_ms_t>(milliseconds_value);
1604# if __cplusplus >= 201703L
1616 int64_t milliseconds_value = 0;
1622 milliseconds =
static_cast<ts_ms_t>(milliseconds_value);
1647# if __cplusplus >= 201703L
1666 return milliseconds;
1676 return milliseconds;
1679# if __cplusplus >= 201703L
1687 return milliseconds;
1704 template<
class T =
int>
1706 if (str.empty())
return false;
1708 const char* p = str.c_str();
1709 int parts[3] = {0, 0, 0};
1712 while (*p && idx < 3) {
1715 bool has_digit =
false;
1717 while (*p >=
'0' && *p <=
'9') {
1719 value = value * 10 + (*p -
'0');
1723 if (!has_digit)
return false;
1724 parts[idx++] = value;
1729 }
else if (*p ==
'\0') {
1736 if (idx == 0)
return false;
1737 if (!
is_valid_time(parts[0], parts[1], parts[2]))
return false;
1739 sec =
static_cast<T
>(
sec_of_day(parts[0], parts[1], parts[2]));
1753 template<
class T =
int>
constexpr int64_t SEC_PER_YEAR
Seconds per year (365 days)
constexpr int64_t SEC_PER_HOUR
Seconds per hour.
constexpr int64_t MS_PER_SEC
Milliseconds per second.
constexpr int64_t SEC_PER_DAY
Seconds per day.
constexpr int64_t SEC_PER_MIN
Seconds per minute.
TIME_SHIELD_CONSTEXPR bool is_first_workday_of_month_ms(ts_ms_t ts_ms) noexcept
Checks whether millisecond timestamp is the first workday of the month.
TIME_SHIELD_CONSTEXPR bool is_within_last_workdays_of_month_ms(ts_ms_t ts_ms, int count) noexcept
Checks if millisecond timestamp falls within the last N workdays of the month.
TIME_SHIELD_CONSTEXPR bool is_within_first_workdays_of_month(year_t year, int month, int day, int count) noexcept
Checks if date falls within the first N workdays of the month.
TIME_SHIELD_CONSTEXPR bool workday(ts_t ts) noexcept
Alias for is_workday(ts_t).
TIME_SHIELD_CONSTEXPR bool is_within_first_workdays_of_month_ms(ts_ms_t ts_ms, int count) noexcept
Checks if millisecond timestamp falls within the first N workdays of the month.
TIME_SHIELD_CONSTEXPR bool is_last_workday_of_month(year_t year, int month, int day) noexcept
Checks whether date is the last workday of the month.
DateStruct iso_week_date_to_date(const IsoWeekDateStruct &iso_date)
Convert ISO week date to calendar date.
TIME_SHIELD_CONSTEXPR bool is_first_workday_of_month(year_t year, int month, int day) noexcept
Checks whether date is the first workday of the month.
TIME_SHIELD_CONSTEXPR bool is_last_workday_of_month_ms(ts_ms_t ts_ms) noexcept
Checks whether millisecond timestamp is the last workday of the month.
TIME_SHIELD_CONSTEXPR bool is_within_last_workdays_of_month(year_t year, int month, int day, int count) noexcept
Checks if date falls within the last N workdays of the month.
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.
@ KST
Korea Standard Time.
@ EET
Eastern European Time.
@ CEST
Central European Summer Time.
@ WIT
Eastern Indonesia Time.
@ WEST
Western European Summer Time.
@ WITA
Central Indonesia Time.
@ WET
Western European Time.
@ UNKNOWN
Unknown Time Zone.
@ UTC
Coordinated Universal Time.
@ GMT
Greenwich Mean Time.
@ WIB
Western Indonesia Time.
@ EEST
Eastern European Summer Time.
@ CET
Central European Time.
@ IST
India Standard Time.
bool parse_iso8601(const char *input, std::size_t length, DateTimeStruct &dt, TimeZoneStruct &tz) noexcept
Parse ISO8601 character buffer into DateTimeStruct and TimeZoneStruct.
bool str_to_ts_ms(const std::string &str, ts_ms_t &ts)
Convert an ISO8601 string to a millisecond timestamp (ts_ms_t).
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 try_get_month_number(const std::string &month, T &value)
Try get the month number by name, with output parameter.
bool str_to_timeframe_ms(const std::string &str, ts_ms_t &milliseconds) noexcept
Parse timeframe string into fixed milliseconds.
Month get_month_index_enum(const std::string &month)
Parse month name token into Month enum.
bool str_to_timeframe_sec(const std::string &str, ts_t &seconds) noexcept
Parse timeframe string into fixed seconds.
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 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).
ts_t timeframe_sec(const std::string &str) noexcept
Convert timeframe string to fixed seconds.
ts_ms_t timeframe_ms(const std::string &str) noexcept
Convert timeframe string to fixed milliseconds.
T parse_month_enum(const std::string &month)
Parse month name token into Month enum (throwing).
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.
int64_t ts_ms_t
Unix timestamp in milliseconds since epoch.
double fts_t
Floating-point timestamp (fractional seconds since epoch).
int64_t year_t
Year as an integer (e.g., 2024).
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.
TIME_SHIELD_CONSTEXPR bool is_workday(ts_t ts) noexcept
Check if a given timestamp corresponds to a workday (Monday to Friday).
TIME_SHIELD_CONSTEXPR bool is_workday_ms(ts_ms_t ts_ms) noexcept
Check if a given timestamp in milliseconds corresponds to a workday (Monday to Friday).
bool try_parse_timeframe_seconds(const char *data, std::size_t length, ts_t &seconds) noexcept
Parse timeframe string into fixed seconds.
bool try_get_timeframe_unit_seconds_compact(const char *data, std::size_t length, int64_t &unit_seconds) noexcept
Resolve compact trading unit token to seconds.
TIME_SHIELD_CONSTEXPR bool is_ascii_alpha(char c) noexcept
Check whether character is ASCII letter.
bool try_parse_positive_int64(const char *data, std::size_t length, int64_t &value) noexcept
Parse positive int64 value from ASCII digits.
TIME_SHIELD_CONSTEXPR bool try_multiply_positive_int64(int64_t lhs, int64_t rhs, int64_t &value) noexcept
Multiply positive int64 values with overflow check.
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 char ascii_to_lower(char c) noexcept
Convert ASCII letter to lower-case.
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.
bool try_get_timeframe_unit_seconds_word(const char *data, std::size_t length, int64_t &unit_seconds) noexcept
Resolve word timeframe unit token to seconds.
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).
bool ascii_iequals(const char *data, std::size_t length, const char *literal) noexcept
Compare ASCII token to literal case-insensitively.
int parse_month_index(const std::string &month)
Parse month name token into month index (1..12).
Main namespace for the Time Shield library.
TIME_SHIELD_CONSTEXPR T sec_of_day(ts_t ts=time_shield::ts()) noexcept
Get the second of the day.
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.