+
+
+
+
+
+
+
+  |
+
+
+
+ $projectname
+
+
+
+ $projectbrief
+ |
+
+
+
+
+ $projectbrief
+ |
+
+
+
+
+
+ $searchbox |
+
+
+
+
+
+
+
+ $searchbox |
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/doc/main.md.in b/doc/main.md.in
new file mode 100644
index 0000000..8770e74
--- /dev/null
+++ b/doc/main.md.in
@@ -0,0 +1,5 @@
+# Main page
+
+parse date and times with {fmt} style intostd::chrono::time_point {WIP} written in C++>=11
+
+Version: @PROJECT_VERSION@
\ No newline at end of file
diff --git a/doc/version_selector_handler.js b/doc/version_selector_handler.js
new file mode 100644
index 0000000..7f0d1fc
--- /dev/null
+++ b/doc/version_selector_handler.js
@@ -0,0 +1,20 @@
+// SPDX-License-Identifier: MIT
+// Copyright (c) 2024, Oleksandr Koval
+
+$(function () {
+ var repoName = window.location.pathname.split('/')[1];
+ $.get('/' + repoName + '/version_selector.html', function (data) {
+ // Inject version selector HTML into the page
+ $('#projectnumber').html(data);
+
+ // Event listener to handle version selection
+ document.getElementById('versionSelector').addEventListener('change', function () {
+ var selectedVersion = this.value;
+ window.location.href = '/' + repoName + '/' + selectedVersion + '/index.html';
+ });
+
+ // Set the selected option based on the current version
+ var currentVersion = window.location.pathname.split('/')[2];
+ $('#versionSelector').val(currentVersion);
+ });
+});
\ No newline at end of file
diff --git a/example/main.cpp b/example/main.cpp
index 12dc12f..a01f81c 100644
--- a/example/main.cpp
+++ b/example/main.cpp
@@ -1,11 +1,13 @@
-#include "chrono_parse.hpp"
+#include "mgutility/chrono/parse.hpp"
// {fmt} for printing
#include
#include
int main() {
- const auto chrono_time = mgutility::chrono::parse("{:%FT%T.%f%z}", "2023-04-16T00:05:23.999+0100");
+ const auto chrono_time =
+ mgutility::chrono::parse("{:%FT%T.%f%z}", "2023-04-16T00:05:23.999+0100");
- fmt::print("{:%F %T}\n", chrono_time); // prints 2023-04-15 23:05:23.999000000 ({fmt} trunk version)
+ fmt::print("{:%F %T}\n", chrono_time); // prints 2023-04-15 23:05:23.999000000
+ // ({fmt} trunk version)
}
\ No newline at end of file
diff --git a/include/chrono_parse.hpp b/include/chrono_parse.hpp
deleted file mode 100644
index ddd155d..0000000
--- a/include/chrono_parse.hpp
+++ /dev/null
@@ -1,283 +0,0 @@
-/*
- * MIT License
- *
- * Copyright (c) 2023 Muhammed Galib Uludag
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-#ifndef MGUTILITY_CHRONO_PARSE_HPP
-#define MGUTILITY_CHRONO_PARSE_HPP
-
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-
-namespace mgutility {
-namespace chrono{
-namespace detail {
-
-struct tm : std::tm {
- uint32_t tm_ms;
-};
-
-inline auto parse_integer(std::string_view str, uint32_t len, uint32_t& next,
- uint32_t begin_offset = 0) -> int32_t {
- int32_t result{0};
- if (str.size() < len + next)
- throw std::invalid_argument("value is not convertible!");
- for (auto it{str.begin() + next + begin_offset};
- it != str.begin() + len + next - 2; ++it) {
- if (!std::isdigit(*it))
- throw std::invalid_argument("value is not convertible!");
- }
- auto error = std::from_chars(str.data() + next + begin_offset,
- str.data() + len + next, result);
-
- next = ++len + next;
-
- if (error.ec != std::errc())
- throw std::invalid_argument("value is not convertible!");
- return result;
-}
-
-inline constexpr auto check_range(int32_t value, int32_t min, int32_t max) {
- if (value < min || value > max) {
- throw std::out_of_range("value is out of range!");
- }
-}
-
-// inspired from https://sources.debian.org/src/tdb/1.2.1-2/libreplace/timegm.c/
-inline constexpr auto mktime(std::tm& tm) -> std::time_t {
- auto constexpr is_leap_year = [](uint32_t year) {
- return (year % 4) == 0 && ((year % 100) != 0 || (year % 400) == 0);
- };
-
- constexpr std::array, 2> num_of_days{
- {{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}, // 365
- {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}}}; // 366
-
- std::time_t result{0};
-
- if (tm.tm_mon > 12 || tm.tm_mon < 0 || tm.tm_mday > 31 || tm.tm_min > 60 ||
- tm.tm_sec > 60 || tm.tm_hour > 24) {
- throw std::out_of_range("value is out of range!");
- }
-
- tm.tm_year += 1900;
-
- for (auto i{1970}; i < tm.tm_year; ++i) {
- result += is_leap_year(i) ? 366 : 365;
- }
-
- for (auto i{0}; i < tm.tm_mon; ++i) {
- result += num_of_days[is_leap_year(tm.tm_year)][i];
- }
-
- result += tm.tm_mday - 1; // nth day since 1970
- result *= 24;
- result += tm.tm_hour;
- result *= 60;
- result += tm.tm_min;
- result *= 60;
- result += tm.tm_sec;
-
- return result;
-}
-
-template
-std::enable_if_t, T> constexpr get_time(
- std::string_view format, std::string_view date_str) {
- int32_t count{0};
- uint32_t beg{0}, end{0};
- for (auto i{0}; i < format.size(); ++i) {
- switch (format[i]) {
- case '{':
- beg = i;
- ++count;
- break;
- case '}':
- end = i;
- --count;
- break;
- }
- if (beg < end)
- break;
- else if (count != 0 && end < beg)
- break;
- }
-
- if (format[beg + 1] != ':' && end - beg < 3 || count != 0)
- throw std::invalid_argument("invalid format string!");
-
- T tm{};
-
- auto handle_timezone = [&tm](int32_t offset) {
- const auto minute = offset % 100;
- const auto hour = offset / 100;
- if (offset < 0) {
- tm.tm_min + minute < 0 ? tm.tm_hour - 1 < 0 ? --tm.tm_mday,
- tm.tm_hour = 23 - tm.tm_hour : --tm.tm_hour : 0;
- tm.tm_min -= minute;
- tm.tm_min %= 60;
-
- tm.tm_hour + hour < 0 ? --tm.tm_mday, tm.tm_hour = 24 + hour
- : tm.tm_hour += hour;
- } else {
- tm.tm_min + minute > 59 ? tm.tm_hour + 1 > 23 ? ++tm.tm_mday,
- ++tm.tm_hour %= 24 : ++tm.tm_hour : 0;
- tm.tm_min += minute;
- tm.tm_min %= 60;
-
- tm.tm_hour + hour > 23 ? ++tm.tm_mday,
- tm.tm_hour = (tm.tm_hour + hour) % 24,
- (tm.tm_mon > 10 ? ++tm.tm_mon : 0) : tm.tm_hour += hour;
- }
- };
-
- uint32_t next{0};
-
- for (auto i{beg}; i < end; ++i) {
- switch (format[i]) {
- case '%': {
- if (i + 1 >= format.size())
- throw std::invalid_argument("invalid format string!");
- switch (format[i + 1]) {
- case 'Y':
- tm.tm_year = parse_integer(date_str, 4, next) % 1900;
- break;
- case 'm':
- tm.tm_mon = parse_integer(date_str, 2, next) - 1;
- check_range(tm.tm_mon, 0, 11);
- break;
- case 'd':
- tm.tm_mday = parse_integer(date_str, 2, next);
- check_range(tm.tm_mday, 1, 31);
- break;
- case 'F': {
- tm.tm_year = parse_integer(date_str, 4, next) % 1900;
- tm.tm_mon = parse_integer(date_str, 2, next) - 1;
- tm.tm_mday = parse_integer(date_str, 2, next);
- check_range(tm.tm_mon, 0, 11);
- check_range(tm.tm_mday, 1, 31);
- } break;
- case 'H':
- tm.tm_hour = parse_integer(date_str, 2, next);
- check_range(tm.tm_hour, 0, 23);
- break;
- case 'M':
- tm.tm_min = parse_integer(date_str, 2, next);
- check_range(tm.tm_min, 0, 59);
- break;
- case 'S':
- tm.tm_sec = parse_integer(date_str, 2, next);
- check_range(tm.tm_sec, 0, 59);
- break;
- case 'T': {
- tm.tm_hour = parse_integer(date_str, 2, next);
- tm.tm_min = parse_integer(date_str, 2, next);
-
- tm.tm_sec = parse_integer(date_str, 2, next);
-
- check_range(tm.tm_hour, 0, 23);
- check_range(tm.tm_min, 0, 59);
-
- check_range(tm.tm_sec, 0, 59);
- } break;
- case 'f': {
- tm.tm_ms = parse_integer(date_str, 3, next);
- check_range(tm.tm_ms, 0, 999);
- break;
- }
- case 'z': {
- if (*(date_str.begin() + next - 1) != 'Z') {
- char sign{};
- auto diff{0};
- for (auto j{next - 1}; j < date_str.size(); ++j) {
- if (date_str[j] == '-' || date_str[j] == '+') {
- sign = date_str[j];
- diff = j - next + 1;
- break;
- }
- }
- auto hour_offset_str =
- std::string_view{date_str.data() + next + diff,
- date_str.size() - 1};
- auto pos = hour_offset_str.find(':');
- auto offset{0};
- if (pos < 3 && pos > 0) {
- next = 0;
- auto hour_offset =
- parse_integer(hour_offset_str, 2, next);
- auto min_offset =
- parse_integer(hour_offset_str, 2, next);
- offset = hour_offset * 100 + min_offset;
- } else {
- if (date_str.size() - next > 4 + diff)
- throw std::invalid_argument(
- "value is not convertible!");
- offset = parse_integer(date_str,
- date_str.size() - next,
- next, diff);
- }
- check_range(offset, 0, 1200);
- switch (sign) {
- case '+':
- handle_timezone(offset * -1);
- break;
- case '-':
- handle_timezone(offset);
- break;
- }
- }
- } break;
- }
- } break;
- case ' ':
- case '-':
- case '/':
- case '.':
- case ':':
- if (i > 1 && format[i] != date_str[next - 1])
- throw std::invalid_argument("value is not convertible!");
- break;
- }
- }
-
- return tm;
-}
-
-} // namespace detail
-
-auto parse(std::string_view format, std::string_view date_str)
- -> std::chrono::system_clock::time_point {
- auto tm = detail::get_time(format, date_str);
- auto time_t = detail::mktime(tm);
- std::chrono::system_clock::time_point clock =
- std::chrono::system_clock::from_time_t(time_t);
- clock += std::chrono::milliseconds(tm.tm_ms);
- return clock;
-}
-} // namespace chrono
-} // namespace mgutility
-
-#endif // MGUTILITY_CHRONO_PARSE_HPP
diff --git a/include/mgutility/_common/definitions.hpp b/include/mgutility/_common/definitions.hpp
new file mode 100644
index 0000000..021dcea
--- /dev/null
+++ b/include/mgutility/_common/definitions.hpp
@@ -0,0 +1,85 @@
+/*
+MIT License
+
+Copyright (c) 2024 mguludag
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+#ifndef MGUTILITY_COMMON_DEFINITIONS_HPP
+#define MGUTILITY_COMMON_DEFINITIONS_HPP
+
+/**
+ * @file definitions.hpp
+ * @brief Defines macros for compiler and standard support detection.
+ */
+
+/**
+ * @brief Defines the MGUTILITY_CPLUSPLUS macro for MSVC and other compilers.
+ *
+ * For MSVC, it uses _MSVC_LANG. For other compilers, it uses __cplusplus.
+ */
+#ifdef _MSC_VER
+#define MGUTILITY_CPLUSPLUS _MSVC_LANG
+#else
+#define MGUTILITY_CPLUSPLUS __cplusplus
+#endif
+
+/**
+ * @brief Defines the MGUTILITY_CNSTXPR macro based on the C++ standard.
+ *
+ * If the C++ standard is C++11, MGUTILITY_CNSTXPR is defined as empty.
+ * If the C++ standard is newer than C++11, MGUTILITY_CNSTXPR is defined as
+ * constexpr. If the C++ standard is older than C++11, an error is raised.
+ */
+#if MGUTILITY_CPLUSPLUS == 201103L
+#define MGUTILITY_CNSTXPR
+#elif MGUTILITY_CPLUSPLUS > 201103L
+#define MGUTILITY_CNSTXPR constexpr
+#elif MGUTILITY_CPLUSPLUS < 201103L
+#error "Standards older than C++11 is not supported!"
+#endif
+
+/**
+ * @brief Defines the MGUTILITY_CNSTXPR_CLANG_WA macro based on the C++
+ * standard.
+ *
+ * If the C++ standard is newer than C++17 and the compiler is not Clang,
+ * MGUTILITY_CNSTXPR_CLANG_WA is defined as constexpr. Otherwise, it is defined
+ * as empty.
+ */
+#if MGUTILITY_CPLUSPLUS > 201703L
+#define MGUTILITY_CNSTXPR_CLANG_WA constexpr
+#else
+#define MGUTILITY_CNSTXPR_CLANG_WA
+#endif
+
+/**
+ * @brief Defines the MGUTILITY_CNSTEVL macro based on the C++ standard.
+ *
+ * If the C++ standard is newer than C++17, MGUTILITY_CNSTEVL is defined as
+ * consteval. Otherwise, it is defined as empty.
+ */
+#if MGUTILITY_CPLUSPLUS > 201703L
+#define MGUTILITY_CNSTEVL consteval
+#else
+#define MGUTILITY_CNSTEVL
+#endif
+
+#endif // MGUTILITY_COMMON_DEFINITIONS_HPP
\ No newline at end of file
diff --git a/include/mgutility/chrono/parse.hpp b/include/mgutility/chrono/parse.hpp
new file mode 100644
index 0000000..c4ebced
--- /dev/null
+++ b/include/mgutility/chrono/parse.hpp
@@ -0,0 +1,358 @@
+/*
+ * MIT License
+ *
+ * (c) 2023 Muhammed Galib Uludag
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#ifndef MGUTILITY_CHRONO_PARSE_HPP
+#define MGUTILITY_CHRONO_PARSE_HPP
+
+#include "mgutility/std/charconv.hpp"
+#include "mgutility/std/string_view.hpp"
+
+#include
+#include
+#include
+#include
+#include
+
+namespace mgutility {
+namespace chrono {
+namespace detail {
+
+/**
+ * @brief Extended tm structure with milliseconds.
+ */
+struct tm : std::tm {
+ uint32_t tm_ms; ///< Milliseconds.
+};
+
+/**
+ * @brief Parses an integer from a string view.
+ *
+ * @param str The string view to parse.
+ * @param len The length of the string view.
+ * @param next The position of the next character to parse.
+ * @param begin_offset The offset to begin parsing from.
+ * @return int32_t The parsed integer.
+ * @throws std::invalid_argument if the value is not convertible.
+ */
+MGUTILITY_CNSTXPR auto parse_integer(mgutility::string_view str, uint32_t len,
+ uint32_t &next, uint32_t begin_offset = 0) -> int32_t {
+ int32_t result{0};
+
+ auto error = mgutility::from_chars(str.data() + next + begin_offset,
+ str.data() + len + next, result);
+
+ next = ++len + next;
+
+ if (error.ec != std::errc()) {
+ throw std::invalid_argument("value is not convertible!");
+ }
+
+ return result;
+}
+
+/**
+ * @brief Checks if a value is within a given range.
+ *
+ * @param value The value to check.
+ * @param min The minimum acceptable value.
+ * @param max The maximum acceptable value.
+ * @throws std::out_of_range if the value is out of range.
+ */
+constexpr auto check_range(int32_t value, int32_t min, int32_t max) -> void {
+ if (value < min || value > max) {
+ throw std::out_of_range("value is out of range!");
+ }
+}
+
+/**
+ * @brief Determines if a year is a leap year.
+ *
+ * @param year The year to check.
+ * @return bool True if the year is a leap year, false otherwise.
+ */
+auto MGUTILITY_CNSTXPR is_leap_year(uint32_t year) -> bool {
+ return (year % 4) == 0 && ((year % 100) != 0 || (year % 400) == 0);
+}
+
+/**
+ * @brief Converts a tm structure to a time_t value.
+ *
+ * @param tm The tm structure to convert.
+ * @return std::time_t The corresponding time_t value.
+ * @throws std::out_of_range if any tm value is out of valid range.
+ */
+MGUTILITY_CNSTXPR auto mktime(std::tm &tm) -> std::time_t {
+ MGUTILITY_CNSTXPR std::array, 2> num_of_days{
+ {{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}, // 365 days in a common year
+ {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}}}; // 366 days in a leap year
+
+ std::time_t result{0};
+
+ // Check for out of range values in tm structure
+ if (tm.tm_mon > 12 || tm.tm_mon < 0 || tm.tm_mday > 31 || tm.tm_min > 60 ||
+ tm.tm_sec > 60 || tm.tm_hour > 24) {
+ throw std::out_of_range("value is out of range!");
+ }
+
+ tm.tm_year += 1900;
+
+ // Calculate the number of days since 1970
+ for (auto i{1970}; i < tm.tm_year; ++i) {
+ result += is_leap_year(i) ? 366 : 365;
+ }
+
+ // Add the days for the current year
+ for (auto i{0}; i < tm.tm_mon; ++i) {
+ result += num_of_days[is_leap_year(tm.tm_year)][i];
+ }
+
+ result += tm.tm_mday - 1; // nth day since 1970
+ result *= 24;
+ result += tm.tm_hour;
+ result *= 60;
+ result += tm.tm_min;
+ result *= 60;
+ result += tm.tm_sec;
+
+ return result;
+}
+
+/**
+ * @brief Adjusts the tm structure for a given timezone offset.
+ *
+ * @param tm The tm structure to adjust.
+ * @param offset The timezone offset in hours and minutes.
+ */
+MGUTILITY_CNSTXPR auto handle_timezone(tm &tm, int32_t offset) -> void {
+ const auto minute = offset % 100;
+ const auto hour = offset / 100;
+
+ if (offset < 0) {
+ // Adjust minutes
+ if (tm.tm_min + minute < 0) {
+ tm.tm_min += 60 - minute;
+ tm.tm_hour -= 1;
+ if (tm.tm_hour < 0) {
+ tm.tm_hour += 24;
+ tm.tm_mday -= 1;
+ }
+ } else {
+ tm.tm_min += minute;
+ }
+
+ // Adjust hours
+ if (tm.tm_hour + hour < 0) {
+ tm.tm_hour += 24 + hour;
+ tm.tm_mday -= 1;
+ } else {
+ tm.tm_hour += hour;
+ }
+ } else {
+ // Adjust minutes
+ if (tm.tm_min + minute >= 60) {
+ tm.tm_min -= 60 - minute;
+ tm.tm_hour += 1;
+ if (tm.tm_hour >= 24) {
+ tm.tm_hour -= 24;
+ tm.tm_mday += 1;
+ }
+ } else {
+ tm.tm_min += minute;
+ }
+
+ // Adjust hours
+ if (tm.tm_hour + hour >= 24) {
+ tm.tm_hour += hour - 24;
+ tm.tm_mday += 1;
+ if (tm.tm_mon == 11 && tm.tm_mday > 31) { // Handle December overflow
+ tm.tm_mday = 1;
+ tm.tm_mon = 0;
+ } else if (tm.tm_mday > 30) { // Handle month overflow for other months
+ tm.tm_mday = 1;
+ tm.tm_mon += 1;
+ }
+ } else {
+ tm.tm_hour += hour;
+ }
+ }
+}
+
+/**
+ * @brief Parses a date and time string according to a specified format.
+ *
+ * @param format The format string.
+ * @param date_str The date and time string to parse.
+ * @return detail::tm The parsed time structure.
+ * @throws std::invalid_argument if the format string is invalid or parsing fails.
+ */
+MGUTILITY_CNSTXPR auto get_time(string_view format, string_view date_str) -> detail::tm {
+ int32_t count{0};
+ uint32_t begin{0}, end{0};
+
+ // Find the positions of format specifiers
+ for (auto i{0}; i < format.size(); ++i) {
+ switch (format[i]) {
+ case '{':
+ begin = i;
+ ++count;
+ break;
+ case '}':
+ end = i;
+ --count;
+ break;
+ }
+ if (begin < end) break;
+ else if (count != 0 && end < begin) break;
+ }
+
+ if (format[begin + 1] != ':' && end - begin < 3 || count != 0)
+ throw std::invalid_argument("invalid format string!");
+
+ detail::tm tm{};
+ uint32_t next{0};
+
+ // Parse the date and time string based on the format specifiers
+ for (auto i{begin}; i < end; ++i) {
+ switch (format[i]) {
+ case '%': {
+ if (i + 1 >= format.size())
+ throw std::invalid_argument("invalid format string!");
+ switch (format[i + 1]) {
+ case 'Y': // Year with century (4 digits)
+ tm.tm_year = parse_integer(date_str, 4, next) % 1900;
+ break;
+ case 'm': // Month (01-12)
+ tm.tm_mon = parse_integer(date_str, 2, next) - 1;
+ check_range(tm.tm_mon, 0, 11);
+ break;
+ case 'd': // Day of the month (01-31)
+ tm.tm_mday = parse_integer(date_str, 2, next);
+ check_range(tm.tm_mday, 1, 31);
+ break;
+ case 'F': { // Full date (YYYY-MM-DD)
+ tm.tm_year = parse_integer(date_str, 4, next) % 1900;
+ tm.tm_mon = parse_integer(date_str, 2, next) - 1;
+ tm.tm_mday = parse_integer(date_str, 2, next);
+ check_range(tm.tm_mon, 0, 11);
+ check_range(tm.tm_mday, 1, 31);
+ } break;
+ case 'H': // Hour (00-23)
+ tm.tm_hour = parse_integer(date_str, 2, next);
+ check_range(tm.tm_hour, 0, 23);
+ break;
+ case 'M': // Minute (00-59)
+ tm.tm_min = parse_integer(date_str, 2, next);
+ check_range(tm.tm_min, 0, 59);
+ break;
+ case 'S': // Second (00-59)
+ tm.tm_sec = parse_integer(date_str, 2, next);
+ check_range(tm.tm_sec, 0, 59);
+ break;
+ case 'T': { // Full time (HH:MM:SS)
+ tm.tm_hour = parse_integer(date_str, 2, next);
+ tm.tm_min = parse_integer(date_str, 2, next);
+ tm.tm_sec = parse_integer(date_str, 2, next);
+ check_range(tm.tm_hour, 0, 23);
+ check_range(tm.tm_min, 0, 59);
+ check_range(tm.tm_sec, 0, 59);
+ } break;
+ case 'f': // Milliseconds (000-999)
+ tm.tm_ms = parse_integer(date_str, 3, next);
+ check_range(tm.tm_ms, 0, 999);
+ break;
+ case 'z': { // Timezone offset (+/-HHMM)
+ if (*(date_str.begin() + next - 1) != 'Z') {
+ char sign{};
+ auto diff{0};
+ for (auto j{next - 1}; j < date_str.size(); ++j) {
+ if (date_str[j] == '-' || date_str[j] == '+') {
+ sign = date_str[j];
+ diff = j - next + 1;
+ break;
+ }
+ }
+ auto hour_offset_str = string_view{date_str.data() + next + diff, date_str.size() - 1};
+ auto pos = hour_offset_str.find(':');
+ auto offset{0};
+ if (pos < 3 && pos > 0) {
+ next = 0;
+ auto hour_offset = parse_integer(hour_offset_str, 2, next);
+ auto min_offset = parse_integer(hour_offset_str, 2, next);
+ offset = hour_offset * 100 + min_offset;
+ } else {
+ if (date_str.size() - next > 4 + diff)
+ throw std::invalid_argument("value is not convertible!");
+ offset = parse_integer(date_str, date_str.size() - next, next, diff);
+ }
+ check_range(offset, 0, 1200);
+ switch (sign) {
+ case '+':
+ handle_timezone(tm, offset * -1);
+ break;
+ case '-':
+ handle_timezone(tm, offset);
+ break;
+ }
+ }
+ } break;
+ default:
+ throw std::invalid_argument("unsupported format specifier!");
+ }
+ } break;
+ case ' ': // Space separator
+ case '-': // Dash separator
+ case '/': // Slash separator
+ case '.': // Dot separator
+ case ':': // Colon separator
+ if (i > 1 && format[i] != date_str[next - 1])
+ throw std::invalid_argument("value is not convertible!");
+ break;
+ }
+ }
+
+ return tm;
+}
+
+} // namespace detail
+
+/**
+ * @brief Parses a date and time string into a std::chrono::system_clock::time_point.
+ *
+ * @param format The format string.
+ * @param date_str The date and time string to parse.
+ * @return std::chrono::system_clock::time_point The parsed time point.
+ */
+auto parse(string_view format, string_view date_str) -> std::chrono::system_clock::time_point {
+ auto tm = detail::get_time(format, date_str);
+ auto time_t = detail::mktime(tm);
+ std::chrono::system_clock::time_point clock =
+ std::chrono::system_clock::from_time_t(time_t);
+ clock += std::chrono::milliseconds(tm.tm_ms);
+ return clock;
+}
+
+} // namespace chrono
+} // namespace mgutility
+
+#endif // MGUTILITY_CHRONO_PARSE_HPP
diff --git a/include/mgutility/std/charconv.hpp b/include/mgutility/std/charconv.hpp
new file mode 100644
index 0000000..21031e5
--- /dev/null
+++ b/include/mgutility/std/charconv.hpp
@@ -0,0 +1,104 @@
+/*
+MIT License
+
+Copyright (c) 2024 mguludag
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+#ifndef MGUTILITY_STD_CHARCONV_HPP
+#define MGUTILITY_STD_CHARCONV_HPP
+#include "mgutility/_common/definitions.hpp"
+#include
+
+namespace mgutility {
+
+#if MGUTILITY_CPLUSPLUS < 201703L
+
+/**
+ * @brief Result structure for from_chars function.
+ */
+struct from_chars_result {
+ const char
+ *ptr; ///< Pointer to the character after the last parsed character.
+ std::errc ec; ///< Error code indicating success or failure.
+};
+
+/**
+ * @brief Converts a character to its integer equivalent.
+ *
+ * @param c The character to convert.
+ * @return int The integer value of the character, or -1 if the character is not
+ * a digit.
+ */
+constexpr auto char_to_int(char c) noexcept -> int {
+ return (c >= '0' && c <= '9') ? c - '0' : -1;
+}
+
+/**
+ * @brief Parses an integer from a character range.
+ *
+ * @param first Pointer to the first character of the range.
+ * @param last Pointer to one past the last character of the range.
+ * @param value Reference to an integer where the parsed value will be stored.
+ * @return from_chars_result The result of the parsing operation.
+ */
+MGUTILITY_CNSTXPR auto from_chars(const char *first, const char *last,
+ int &value) noexcept -> from_chars_result {
+ int result = 0;
+ bool negative = false;
+ const char *it = first;
+
+ if (it == last) {
+ return {first, std::errc::invalid_argument};
+ }
+
+ if (*it == '-') {
+ negative = true;
+ ++it;
+ if (it == last) {
+ return {first, std::errc::invalid_argument};
+ }
+ }
+
+ for (; it != last; ++it) {
+ int digit = char_to_int(*it);
+ if (digit == -1) {
+ break;
+ }
+ result = result * 10 + digit;
+ }
+
+ if (it == first || (negative && it == first + 1)) {
+ return {first, std::errc::invalid_argument};
+ }
+
+ value = negative ? -result : result;
+ return {it, std::errc{}};
+}
+#else
+#include
+
+using from_chars_result = std::from_chars_result;
+using from_chars = std::from_chars;
+
+#endif
+
+} // namespace mgutility
+
+#endif // MGUTILITY_STD_CHARCONV_HPP
\ No newline at end of file
diff --git a/include/mgutility/std/string_view.hpp b/include/mgutility/std/string_view.hpp
new file mode 100644
index 0000000..a839770
--- /dev/null
+++ b/include/mgutility/std/string_view.hpp
@@ -0,0 +1,314 @@
+/*
+MIT License
+
+Copyright (c) 2024 mguludag
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+#ifndef STRING_STRING_VIEW_HPP
+#define STRING_STRING_VIEW_HPP
+
+#include
+#include
+#include
+
+#include "mgutility/_common/definitions.hpp"
+
+namespace mgutility {
+
+#if MGUTILITY_CPLUSPLUS < 201703L
+
+namespace detail {
+/**
+ * @brief Computes the length of a C-string at compile-time.
+ *
+ * @param str The C-string.
+ * @param sz The initial size, default is 0.
+ * @return The length of the C-string.
+ */
+constexpr auto strlen_constexpr(const char *str, size_t sz = 0) noexcept
+ -> size_t {
+ return str[sz] == '\0' ? sz : strlen_constexpr(str, ++sz);
+}
+} // namespace detail
+
+/**
+ * @brief A basic string view class template.
+ *
+ * @tparam Char The character type, default is char.
+ */
+template class basic_string_view {
+public:
+ /**
+ * @brief Default constructor.
+ */
+ constexpr inline basic_string_view() noexcept : data_(""), size_(0) {}
+
+ /**
+ * @brief Constructs a basic_string_view from a C-string.
+ *
+ * @param str The C-string.
+ */
+ constexpr inline basic_string_view(const Char *str) noexcept
+ : data_(str), size_(detail::strlen_constexpr(str)) {}
+
+ /**
+ * @brief Constructs a basic_string_view from a std::string.
+ *
+ * @param str The std::string.
+ */
+ constexpr inline basic_string_view(
+ const std::basic_string &str) noexcept
+ : data_(str.c_str()), size_(str.size()) {}
+
+ /**
+ * @brief Constructs a basic_string_view from a C-string and length.
+ *
+ * @param str The C-string.
+ * @param len The length of the string.
+ */
+ constexpr inline basic_string_view(const Char *str, size_t len) noexcept
+ : data_(str), size_(len) {}
+
+ /**
+ * @brief Copy constructor.
+ *
+ * @param other The other basic_string_view to copy.
+ */
+ constexpr inline basic_string_view(const basic_string_view &other)
+ : data_(other.data_), size_(other.size_) {}
+
+ /**
+ * @brief Move constructor.
+ *
+ * @param other The other basic_string_view to move.
+ */
+ constexpr inline basic_string_view(basic_string_view &&other) noexcept
+ : data_(std::move(other.data_)), size_(std::move(other.size_)) {}
+
+ /**
+ * @brief Copy assignment operator.
+ *
+ * @param other The other basic_string_view to copy.
+ * @return A reference to this object.
+ */
+ MGUTILITY_CNSTXPR inline basic_string_view &
+ operator=(const basic_string_view &other) noexcept {
+ data_ = other.data_;
+ size_ = other.size_;
+ return *this;
+ }
+
+ /**
+ * @brief Move assignment operator.
+ *
+ * @param other The other basic_string_view to move.
+ * @return A reference to this object.
+ */
+ MGUTILITY_CNSTXPR inline basic_string_view &
+ operator=(basic_string_view &&other) noexcept {
+ data_ = std::move(other.data_);
+ size_ = std::move(other.size_);
+ return *this;
+ }
+
+ /**
+ * @brief Accesses the character at the given index.
+ *
+ * @param index The index.
+ * @return The character at the index.
+ */
+ constexpr inline const Char operator[](size_t index) const noexcept {
+ return data_[index];
+ }
+
+ /**
+ * @brief Returns an iterator to the beginning of the string.
+ *
+ * @return A pointer to the first character.
+ */
+ constexpr inline const Char *begin() const noexcept { return data_; }
+
+ /**
+ * @brief Returns an iterator to the end of the string.
+ *
+ * @return A pointer to one past the last character.
+ */
+ constexpr inline const Char *end() const noexcept { return (data_ + size_); }
+
+ /**
+ * @brief Checks if the string is empty.
+ *
+ * @return True if the string is empty, otherwise false.
+ */
+ constexpr inline bool empty() const noexcept { return size_ < 1; }
+
+ /**
+ * @brief Returns the size of the string.
+ *
+ * @return The size of the string.
+ */
+ constexpr inline size_t size() const noexcept { return size_; }
+
+ /**
+ * @brief Returns a pointer to the underlying data.
+ *
+ * @return A pointer to the data.
+ */
+ constexpr inline const Char *data() const noexcept { return data_; }
+
+ /**
+ * @brief Returns a substring view.
+ *
+ * @param begin The starting position.
+ * @param len The length of the substring.
+ * @return A basic_string_view representing the substring.
+ */
+ constexpr inline basic_string_view
+ substr(size_t begin, size_t len = 0U) const noexcept {
+ return basic_string_view(data_ + begin,
+ len == 0U ? size_ - begin : len);
+ }
+
+ /**
+ * @brief Finds the last occurrence of a character.
+ *
+ * @param c The character to find.
+ * @param pos The position to start from, default is npos.
+ * @return The position of the character or npos if not found.
+ */
+ constexpr inline size_t rfind(Char c, size_t pos = npos) const noexcept {
+ return (pos == npos ? pos = size_ : pos = pos), c == data_[pos] ? pos
+ : pos == 0U
+ ? npos
+ : rfind(c, --pos);
+ }
+
+ /**
+ * @brief Finds the first occurrence of a character.
+ *
+ * @param c The character to find.
+ * @param pos The position to start from, default is 0.
+ * @return The position of the character or npos if not found.
+ */
+ constexpr inline size_t find(Char c, size_t pos = 0) const noexcept {
+ return c == data_[pos] ? pos : pos < size_ ? find(c, ++pos) : npos;
+ }
+
+ /**
+ * @brief Equality operator.
+ *
+ * @param lhs The left-hand side basic_string_view.
+ * @param rhs The right-hand side basic_string_view.
+ * @return True if the strings are equal, otherwise false.
+ */
+ constexpr friend inline bool
+ operator==(basic_string_view lhs,
+ basic_string_view rhs) noexcept {
+ return (lhs.size_ == rhs.size_) &&
+ std::strncmp(lhs.data_, rhs.data_, lhs.size_) == 0;
+ }
+
+ /**
+ * @brief Equality operator.
+ *
+ * @param lhs The left-hand side basic_string_view.
+ * @param rhs The right-hand side C-string.
+ * @return True if the strings are equal, otherwise false.
+ */
+ constexpr friend inline bool operator==(basic_string_view lhs,
+ const Char *rhs) noexcept {
+ return (lhs.size_ == detail::strlen_constexpr(rhs)) &&
+ std::strncmp(lhs.data_, rhs, lhs.size_) == 0;
+ }
+
+ /**
+ * @brief Inequality operator.
+ *
+ * @param lhs The left-hand side basic_string_view.
+ * @param rhs The right-hand side basic_string_view.
+ * @return True if the strings are not equal, otherwise false.
+ */
+ constexpr friend inline bool
+ operator!=(basic_string_view lhs,
+ basic_string_view rhs) noexcept {
+ return !(lhs == rhs);
+ }
+
+ /**
+ * @brief Inequality operator.
+ *
+ * @param lhs The left-hand side basic_string_view.
+ * @param rhs The right-hand side C-string.
+ * @return True if the strings are not equal, otherwise false.
+ */
+ constexpr friend inline bool operator!=(basic_string_view lhs,
+ const Char *rhs) noexcept {
+ return !(lhs == rhs);
+ }
+
+ /**
+ * @brief Converts the string view to an std::string.
+ *
+ * @return An std::string representing the same string.
+ */
+ inline operator std::string() { return std::string(data_, size_); }
+
+ /**
+ * @brief Converts the string view to an std::string (const version).
+ *
+ * @return An std::string representing the same string.
+ */
+ inline operator std::string() const { return std::string(data_, size_); }
+
+ /**
+ * @brief Stream insertion operator.
+ *
+ * @param os The output stream.
+ * @param sv The basic_string_view.
+ * @return A reference to the output stream.
+ */
+ friend inline std::ostream &operator<<(std::ostream &os,
+ const basic_string_view &sv) {
+ for (auto c : sv) {
+ os << c;
+ }
+ return os;
+ }
+
+ static constexpr auto npos = -1;
+
+private:
+ size_t size_;
+ const Char *data_;
+};
+
+using string_view = basic_string_view;
+
+#else
+#include
+
+using string_view = std::string_view;
+
+#endif
+
+} // namespace mgutility
+
+#endif // STRING_STRING_VIEW_HPP
\ No newline at end of file
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
deleted file mode 100644
index 5f7ad2f..0000000
--- a/test/CMakeLists.txt
+++ /dev/null
@@ -1,11 +0,0 @@
-cmake_minimum_required(VERSION 3.14)
-project(chrono_parse_test VERSION 0.1 LANGUAGES CXX)
-
-set(CMAKE_CXX_STANDARD 17)
-
-include_directories(AFTER PUBLIC ${CMAKE_SOURCE_DIR}/include)
-
-add_executable(${PROJECT_NAME} chrono_parse_test.cpp)
-target_link_libraries(${PROJECT_NAME} doctest)
-
-add_test(NAME chrono_parse_test COMMAND chrono_parse_test)
\ No newline at end of file
diff --git a/test/chrono_parse_test.cpp b/tests/test_chrono_parse.cpp
similarity index 74%
rename from test/chrono_parse_test.cpp
rename to tests/test_chrono_parse.cpp
index 0ca83aa..26cee8f 100644
--- a/test/chrono_parse_test.cpp
+++ b/tests/test_chrono_parse.cpp
@@ -1,8 +1,8 @@
#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
#include "doctest/doctest.h"
-#include "chrono_parse.hpp"
+#include "mgutility/chrono/parse.hpp"
-#if defined(__GNUC__) && !defined(__clang__)
+#if defined(__GNUC__) //&& !defined(__clang__)
#define FACTOR 1000
#else
#define FACTOR 1
@@ -15,5 +15,8 @@ TEST_CASE("testing the iso8601 parsing") {
CHECK(mgutility::chrono::parse("{:%FT%T%z}", "2023-04-30T16:22:18-0200").time_since_epoch().count() == (1682878938000000 * FACTOR));
CHECK(mgutility::chrono::parse("{:%FT%T.%f}", "2023-04-30T16:22:18.500").time_since_epoch().count() == (1682871738500000 * FACTOR));
CHECK(mgutility::chrono::parse("{:%FT%T.%f%z}", "2023-04-30T16:22:18.500+0100").time_since_epoch().count() == (1682868138500000 * FACTOR));
+ REQUIRE_THROWS(mgutility::chrono::parse("{:%FT%T", "2023-04-30T16:22:18"));
+ REQUIRE_THROWS(mgutility::chrono::parse("%FT%T}", "2023-04-30T16:22:18"));
+ REQUIRE_THROWS(mgutility::chrono::parse("{%F %T}", "2023-04-30T16:22:18"));
REQUIRE_THROWS(mgutility::chrono::parse("{:%F %T}", "2023-04-30T16:22:18"));
}
\ No newline at end of file