diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a3452940..80f97cf2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -132,3 +132,20 @@ jobs: run: cmake -B build -S . -DCMAKE_TOOLCHAIN_FILE=./vcpkg/scripts/buildsystems/vcpkg.cmake - name: Build consumer project run: cmake --build build + + install-no-ntp: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Configure without NTP + run: cmake -S . -B build -DTIME_SHIELD_CPP_BUILD_TESTS=ON -DTIME_SHIELD_ENABLE_NTP_CLIENT=OFF -DCMAKE_CXX_STANDARD=11 + - name: Build without NTP + run: cmake --build build + - name: Test without NTP + run: ctest --test-dir build --output-on-failure + - name: Install without NTP + run: cmake --install build --prefix install + - name: Configure installed consumer + run: cmake -S tests/install_consumer -B build-consumer -DCMAKE_PREFIX_PATH=${{ github.workspace }}/install -DCMAKE_CXX_STANDARD=11 + - name: Build installed consumer + run: cmake --build build-consumer diff --git a/AGENTS.md b/AGENTS.md index 79a735cc..b3f95c64 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -30,6 +30,17 @@ Global rules: Additional policy: +- Public C++ headers are organized by domain under `include/time_shield`. + Prefer domain umbrellas (`core.hpp`, `conversions.hpp`, `text.hpp`, + `date_time.hpp`, `timezone.hpp`, `astronomy.hpp`, `timers.hpp`, and + optional `ntp.hpp`) for cross-domain includes. Same-domain leaf includes may + use local paths. Do not use `../` includes or include another domain's leaf + header directly. Root-level compatibility headers and documented legacy + paths are forwarding shells and are exempt from the domain dependency rule. +- Keep domain dependencies acyclic: lower-level domains must not include + higher-level domain headers. Detail headers belong to their owning domain + and are not public cross-domain dependencies. + - For reusable `.hpp` / `.ipp` / `.tpp` ownership and include-structure policy, prefer: - developer doc: `docs/header-implementation-guidelines.md` - agent playbook: `agents/header-implementation-guidelines.md` diff --git a/CMakeLists.txt b/CMakeLists.txt index 10f8fb61..bf349beb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -39,7 +39,14 @@ install(TARGETS time_shield EXPORT TimeShieldTargets if(TIME_SHIELD_ENABLE_NTP_CLIENT) install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) else() - install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} PATTERN "ntp_client*" EXCLUDE) + install( + DIRECTORY include/ + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} + PATTERN "ntp" EXCLUDE + PATTERN "ntp.hpp" EXCLUDE + PATTERN "ntp_client*" EXCLUDE + PATTERN "ntp_time_service.hpp" EXCLUDE + ) endif() install( EXPORT TimeShieldTargets @@ -102,6 +109,12 @@ endif() if(TIME_SHIELD_CPP_BUILD_TESTS) enable_testing() + add_test( + NAME header_include_policy + COMMAND ${CMAKE_COMMAND} + -DTIME_SHIELD_SOURCE_DIR=${CMAKE_CURRENT_SOURCE_DIR} + -P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/check_header_include_policy.cmake + ) file(GLOB TEST_SOURCES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} tests/*.cpp) foreach(test_src ${TEST_SOURCES}) get_filename_component(test_name ${test_src} NAME_WE) @@ -113,5 +126,17 @@ if(TIME_SHIELD_CPP_BUILD_TESTS) add_test(NAME ${test_name} COMMAND ${test_name}) endforeach() + file(GLOB HEADER_SMOKE_SOURCES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} tests/header_smoke/*.cpp) + foreach(test_src ${HEADER_SMOKE_SOURCES}) + get_filename_component(test_name ${test_src} NAME_WE) + set(target_name header_smoke_${test_name}) + add_executable(${target_name} ${test_src}) + target_link_libraries(${target_name} PRIVATE time_shield::time_shield) + if(COMMON_WARN_FLAGS) + target_compile_options(${target_name} PRIVATE ${COMMON_WARN_FLAGS}) + endif() + add_test(NAME ${target_name} COMMAND ${target_name}) + endforeach() + add_subdirectory(tests/odr) endif() diff --git a/MQL5/Include/time_shield/DateTime.mqh b/MQL5/Include/time_shield/DateTime.mqh index 5f3bb51b..6f70e876 100644 --- a/MQL5/Include/time_shield/DateTime.mqh +++ b/MQL5/Include/time_shield/DateTime.mqh @@ -4,8 +4,8 @@ //| Copyright 2025, NewYaroslav | //| https://github.com/NewYaroslav/time-shield-cpp | //+------------------------------------------------------------------+ -#ifndef TIME_SHIELD_MQL5_HEADER_TIME_SHIELD_DATETIME_MQH_INCLUDED -#define TIME_SHIELD_MQL5_HEADER_TIME_SHIELD_DATETIME_MQH_INCLUDED +#ifndef TIME_SHIELD_MQL5_HEADER_DATETIME_MQH_INCLUDED +#define TIME_SHIELD_MQL5_HEADER_DATETIME_MQH_INCLUDED /// \file DateTime.mqh /// \ingroup mql5 @@ -149,4 +149,4 @@ namespace time_shield { } // namespace time_shield -#endif // TIME_SHIELD_MQL5_HEADER_TIME_SHIELD_DATETIME_MQH_INCLUDED +#endif // TIME_SHIELD_MQL5_HEADER_DATETIME_MQH_INCLUDED diff --git a/MQL5/Include/time_shield/constants.mqh b/MQL5/Include/time_shield/constants.mqh index ff9a811e..3f7e723c 100644 --- a/MQL5/Include/time_shield/constants.mqh +++ b/MQL5/Include/time_shield/constants.mqh @@ -4,8 +4,8 @@ //| Copyright 2025, NewYaroslav | //| https://github.com/NewYaroslav/time-shield-cpp | //+------------------------------------------------------------------+ -#ifndef TIME_SHIELD_MQL5_HEADER_TIME_SHIELD_CONSTANTS_MQH_INCLUDED -#define TIME_SHIELD_MQL5_HEADER_TIME_SHIELD_CONSTANTS_MQH_INCLUDED +#ifndef TIME_SHIELD_MQL5_HEADER_CONSTANTS_MQH_INCLUDED +#define TIME_SHIELD_MQL5_HEADER_CONSTANTS_MQH_INCLUDED /// \file constants.mqh /// \ingroup mql5 @@ -172,4 +172,4 @@ namespace time_shield { }; // namespace time_shield -#endif // TIME_SHIELD_MQL5_HEADER_TIME_SHIELD_CONSTANTS_MQH_INCLUDED +#endif // TIME_SHIELD_MQL5_HEADER_CONSTANTS_MQH_INCLUDED diff --git a/MQL5/Include/time_shield/date_struct.mqh b/MQL5/Include/time_shield/date_struct.mqh index 0f766521..7ecd99e7 100644 --- a/MQL5/Include/time_shield/date_struct.mqh +++ b/MQL5/Include/time_shield/date_struct.mqh @@ -4,8 +4,8 @@ //| Copyright 2025, NewYaroslav | //| https://github.com/NewYaroslav/time-shield-cpp | //+------------------------------------------------------------------+ -#ifndef TIME_SHIELD_MQL5_HEADER_TIME_SHIELD_DATE_STRUCT_MQH_INCLUDED -#define TIME_SHIELD_MQL5_HEADER_TIME_SHIELD_DATE_STRUCT_MQH_INCLUDED +#ifndef TIME_SHIELD_MQL5_HEADER_DATE_STRUCT_MQH_INCLUDED +#define TIME_SHIELD_MQL5_HEADER_DATE_STRUCT_MQH_INCLUDED /// \file date_struct.mqh /// \ingroup mql5 @@ -44,4 +44,4 @@ namespace time_shield { }; // namespace time_shield -#endif // TIME_SHIELD_MQL5_HEADER_TIME_SHIELD_DATE_STRUCT_MQH_INCLUDED +#endif // TIME_SHIELD_MQL5_HEADER_DATE_STRUCT_MQH_INCLUDED diff --git a/MQL5/Include/time_shield/date_time_struct.mqh b/MQL5/Include/time_shield/date_time_struct.mqh index c8b136d3..19950826 100644 --- a/MQL5/Include/time_shield/date_time_struct.mqh +++ b/MQL5/Include/time_shield/date_time_struct.mqh @@ -4,8 +4,8 @@ //| Copyright 2025, NewYaroslav | //| https://github.com/NewYaroslav/time-shield-cpp | //+------------------------------------------------------------------+ -#ifndef TIME_SHIELD_MQL5_HEADER_TIME_SHIELD_DATE_TIME_STRUCT_MQH_INCLUDED -#define TIME_SHIELD_MQL5_HEADER_TIME_SHIELD_DATE_TIME_STRUCT_MQH_INCLUDED +#ifndef TIME_SHIELD_MQL5_HEADER_DATE_TIME_STRUCT_MQH_INCLUDED +#define TIME_SHIELD_MQL5_HEADER_DATE_TIME_STRUCT_MQH_INCLUDED /// \file date_time_struct.mqh /// \ingroup mql5 @@ -64,4 +64,4 @@ namespace time_shield { }; // namespace time_shield -#endif // TIME_SHIELD_MQL5_HEADER_TIME_SHIELD_DATE_TIME_STRUCT_MQH_INCLUDED +#endif // TIME_SHIELD_MQL5_HEADER_DATE_TIME_STRUCT_MQH_INCLUDED diff --git a/MQL5/Include/time_shield/enums.mqh b/MQL5/Include/time_shield/enums.mqh index 69e402b3..b50afc43 100644 --- a/MQL5/Include/time_shield/enums.mqh +++ b/MQL5/Include/time_shield/enums.mqh @@ -4,8 +4,8 @@ //| Copyright 2025, NewYaroslav | //| https://github.com/NewYaroslav/time-shield-cpp | //+------------------------------------------------------------------+ -#ifndef TIME_SHIELD_MQL5_HEADER_TIME_SHIELD_ENUMS_MQH_INCLUDED -#define TIME_SHIELD_MQL5_HEADER_TIME_SHIELD_ENUMS_MQH_INCLUDED +#ifndef TIME_SHIELD_MQL5_HEADER_ENUMS_MQH_INCLUDED +#define TIME_SHIELD_MQL5_HEADER_ENUMS_MQH_INCLUDED /// \file enums.mqh /// \ingroup mql5 @@ -226,4 +226,4 @@ namespace time_shield { }; // namespace time_shield -#endif // TIME_SHIELD_MQL5_HEADER_TIME_SHIELD_ENUMS_MQH_INCLUDED +#endif // TIME_SHIELD_MQL5_HEADER_ENUMS_MQH_INCLUDED diff --git a/MQL5/Include/time_shield/initialization.mqh b/MQL5/Include/time_shield/initialization.mqh index 3fb106a0..09d30fb4 100644 --- a/MQL5/Include/time_shield/initialization.mqh +++ b/MQL5/Include/time_shield/initialization.mqh @@ -4,8 +4,8 @@ //| Copyright 2025, NewYaroslav | //| https://github.com/NewYaroslav/time-shield-cpp | //+------------------------------------------------------------------+ -#ifndef TIME_SHIELD_MQL5_HEADER_TIME_SHIELD_INITIALIZATION_MQH_INCLUDED -#define TIME_SHIELD_MQL5_HEADER_TIME_SHIELD_INITIALIZATION_MQH_INCLUDED +#ifndef TIME_SHIELD_MQL5_HEADER_INITIALIZATION_MQH_INCLUDED +#define TIME_SHIELD_MQL5_HEADER_INITIALIZATION_MQH_INCLUDED /// \file initialization.mqh /// \ingroup mql5 @@ -35,4 +35,4 @@ namespace time_shield { }; // namespace time_shield -#endif // TIME_SHIELD_MQL5_HEADER_TIME_SHIELD_INITIALIZATION_MQH_INCLUDED +#endif // TIME_SHIELD_MQL5_HEADER_INITIALIZATION_MQH_INCLUDED diff --git a/MQL5/Include/time_shield/time_conversions.mqh b/MQL5/Include/time_shield/time_conversions.mqh index 36ab6e31..ff4dd82a 100644 --- a/MQL5/Include/time_shield/time_conversions.mqh +++ b/MQL5/Include/time_shield/time_conversions.mqh @@ -4,8 +4,8 @@ //| Copyright 2025, NewYaroslav | //| https://github.com/NewYaroslav/time-shield-cpp | //+------------------------------------------------------------------+ -#ifndef TIME_SHIELD_MQL5_HEADER_TIME_SHIELD_TIME_CONVERSIONS_MQH_INCLUDED -#define TIME_SHIELD_MQL5_HEADER_TIME_SHIELD_TIME_CONVERSIONS_MQH_INCLUDED +#ifndef TIME_SHIELD_MQL5_HEADER_TIME_CONVERSIONS_MQH_INCLUDED +#define TIME_SHIELD_MQL5_HEADER_TIME_CONVERSIONS_MQH_INCLUDED /// \file time_conversions.mqh /// \ingroup mql5 @@ -1559,4 +1559,4 @@ double sec_to_fhour(long sec) { }; // namespace time_shield -#endif // TIME_SHIELD_MQL5_HEADER_TIME_SHIELD_TIME_CONVERSIONS_MQH_INCLUDED +#endif // TIME_SHIELD_MQL5_HEADER_TIME_CONVERSIONS_MQH_INCLUDED diff --git a/MQL5/Include/time_shield/time_formatting.mqh b/MQL5/Include/time_shield/time_formatting.mqh index 86a9ceee..2c81301c 100644 --- a/MQL5/Include/time_shield/time_formatting.mqh +++ b/MQL5/Include/time_shield/time_formatting.mqh @@ -4,8 +4,8 @@ //| Copyright 2025, NewYaroslav | //| https://github.com/NewYaroslav/time-shield-cpp | //+------------------------------------------------------------------+ -#ifndef TIME_SHIELD_MQL5_HEADER_TIME_SHIELD_TIME_FORMATTING_MQH_INCLUDED -#define TIME_SHIELD_MQL5_HEADER_TIME_SHIELD_TIME_FORMATTING_MQH_INCLUDED +#ifndef TIME_SHIELD_MQL5_HEADER_TIME_FORMATTING_MQH_INCLUDED +#define TIME_SHIELD_MQL5_HEADER_TIME_FORMATTING_MQH_INCLUDED /// \file time_formatting.mqh /// \ingroup mql5 @@ -483,4 +483,4 @@ namespace time_shield { }; // namespace time_shield -#endif // TIME_SHIELD_MQL5_HEADER_TIME_SHIELD_TIME_FORMATTING_MQH_INCLUDED +#endif // TIME_SHIELD_MQL5_HEADER_TIME_FORMATTING_MQH_INCLUDED diff --git a/MQL5/Include/time_shield/time_parser.mqh b/MQL5/Include/time_shield/time_parser.mqh index e236b7c3..962bee24 100644 --- a/MQL5/Include/time_shield/time_parser.mqh +++ b/MQL5/Include/time_shield/time_parser.mqh @@ -4,8 +4,8 @@ //| Copyright 2025, NewYaroslav | //| https://github.com/NewYaroslav/time-shield-cpp | //+------------------------------------------------------------------+ -#ifndef TIME_SHIELD_MQL5_HEADER_TIME_SHIELD_TIME_PARSER_MQH_INCLUDED -#define TIME_SHIELD_MQL5_HEADER_TIME_SHIELD_TIME_PARSER_MQH_INCLUDED +#ifndef TIME_SHIELD_MQL5_HEADER_TIME_PARSER_MQH_INCLUDED +#define TIME_SHIELD_MQL5_HEADER_TIME_PARSER_MQH_INCLUDED /// \file time_parser.mqh /// \ingroup mql5 @@ -690,4 +690,4 @@ namespace time_shield { }; // namespace time_shield -#endif // TIME_SHIELD_MQL5_HEADER_TIME_SHIELD_TIME_PARSER_MQH_INCLUDED +#endif // TIME_SHIELD_MQL5_HEADER_TIME_PARSER_MQH_INCLUDED diff --git a/MQL5/Include/time_shield/time_struct.mqh b/MQL5/Include/time_shield/time_struct.mqh index 6d0979ed..4cd8b9f8 100644 --- a/MQL5/Include/time_shield/time_struct.mqh +++ b/MQL5/Include/time_shield/time_struct.mqh @@ -4,8 +4,8 @@ //| Copyright 2025, NewYaroslav | //| https://github.com/NewYaroslav/time-shield-cpp | //+------------------------------------------------------------------+ -#ifndef TIME_SHIELD_MQL5_HEADER_TIME_SHIELD_TIME_STRUCT_MQH_INCLUDED -#define TIME_SHIELD_MQL5_HEADER_TIME_SHIELD_TIME_STRUCT_MQH_INCLUDED +#ifndef TIME_SHIELD_MQL5_HEADER_TIME_STRUCT_MQH_INCLUDED +#define TIME_SHIELD_MQL5_HEADER_TIME_STRUCT_MQH_INCLUDED /// \file time_struct.mqh /// \ingroup mql5 @@ -52,4 +52,4 @@ namespace time_shield { }; // namespace time_shield -#endif // TIME_SHIELD_MQL5_HEADER_TIME_SHIELD_TIME_STRUCT_MQH_INCLUDED +#endif // TIME_SHIELD_MQL5_HEADER_TIME_STRUCT_MQH_INCLUDED diff --git a/MQL5/Include/time_shield/time_utils.mqh b/MQL5/Include/time_shield/time_utils.mqh index 6458a55a..456f4b0f 100644 --- a/MQL5/Include/time_shield/time_utils.mqh +++ b/MQL5/Include/time_shield/time_utils.mqh @@ -4,8 +4,8 @@ //| Copyright 2025, NewYaroslav | //| https://github.com/NewYaroslav/time-shield-cpp | //+------------------------------------------------------------------+ -#ifndef TIME_SHIELD_MQL5_HEADER_TIME_SHIELD_TIME_UTILS_MQH_INCLUDED -#define TIME_SHIELD_MQL5_HEADER_TIME_SHIELD_TIME_UTILS_MQH_INCLUDED +#ifndef TIME_SHIELD_MQL5_HEADER_TIME_UTILS_MQH_INCLUDED +#define TIME_SHIELD_MQL5_HEADER_TIME_UTILS_MQH_INCLUDED /// \file time_utils.mqh /// \ingroup mql5 @@ -112,4 +112,4 @@ namespace time_shield { }; // namespace time_shield -#endif // TIME_SHIELD_MQL5_HEADER_TIME_SHIELD_TIME_UTILS_MQH_INCLUDED +#endif // TIME_SHIELD_MQL5_HEADER_TIME_UTILS_MQH_INCLUDED diff --git a/MQL5/Include/time_shield/time_zone_conversions.mqh b/MQL5/Include/time_shield/time_zone_conversions.mqh index 8b4fc534..e7bf1db2 100644 --- a/MQL5/Include/time_shield/time_zone_conversions.mqh +++ b/MQL5/Include/time_shield/time_zone_conversions.mqh @@ -4,8 +4,8 @@ //| Copyright 2025, NewYaroslav | //| https://github.com/NewYaroslav/time-shield-cpp | //+------------------------------------------------------------------+ -#ifndef TIME_SHIELD_MQL5_HEADER_TIME_SHIELD_TIME_ZONE_CONVERSIONS_MQH_INCLUDED -#define TIME_SHIELD_MQL5_HEADER_TIME_SHIELD_TIME_ZONE_CONVERSIONS_MQH_INCLUDED +#ifndef TIME_SHIELD_MQL5_HEADER_TIME_ZONE_CONVERSIONS_MQH_INCLUDED +#define TIME_SHIELD_MQL5_HEADER_TIME_ZONE_CONVERSIONS_MQH_INCLUDED /// \file time_zone_conversions.mqh /// \ingroup mql5 @@ -445,4 +445,4 @@ namespace time_shield { }; // namespace time_shield -#endif // TIME_SHIELD_MQL5_HEADER_TIME_SHIELD_TIME_ZONE_CONVERSIONS_MQH_INCLUDED +#endif // TIME_SHIELD_MQL5_HEADER_TIME_ZONE_CONVERSIONS_MQH_INCLUDED diff --git a/MQL5/Include/time_shield/time_zone_struct.mqh b/MQL5/Include/time_shield/time_zone_struct.mqh index be61e8f8..0909d01d 100644 --- a/MQL5/Include/time_shield/time_zone_struct.mqh +++ b/MQL5/Include/time_shield/time_zone_struct.mqh @@ -4,8 +4,8 @@ //| Copyright 2025, NewYaroslav | //| https://github.com/NewYaroslav/time-shield-cpp | //+------------------------------------------------------------------+ -#ifndef TIME_SHIELD_MQL5_HEADER_TIME_SHIELD_TIME_ZONE_STRUCT_MQH_INCLUDED -#define TIME_SHIELD_MQL5_HEADER_TIME_SHIELD_TIME_ZONE_STRUCT_MQH_INCLUDED +#ifndef TIME_SHIELD_MQL5_HEADER_TIME_ZONE_STRUCT_MQH_INCLUDED +#define TIME_SHIELD_MQL5_HEADER_TIME_ZONE_STRUCT_MQH_INCLUDED /// \file time_zone_struct.mqh /// \ingroup mql5 @@ -138,4 +138,4 @@ namespace time_shield { }; // namespace time_shield -#endif // TIME_SHIELD_MQL5_HEADER_TIME_SHIELD_TIME_ZONE_STRUCT_MQH_INCLUDED +#endif // TIME_SHIELD_MQL5_HEADER_TIME_ZONE_STRUCT_MQH_INCLUDED diff --git a/MQL5/Include/time_shield/validation.mqh b/MQL5/Include/time_shield/validation.mqh index 0e7453a4..cf343759 100644 --- a/MQL5/Include/time_shield/validation.mqh +++ b/MQL5/Include/time_shield/validation.mqh @@ -4,8 +4,8 @@ //| Copyright 2025, NewYaroslav | //| https://github.com/NewYaroslav/time-shield-cpp | //+------------------------------------------------------------------+ -#ifndef TIME_SHIELD_MQL5_HEADER_TIME_SHIELD_VALIDATION_MQH_INCLUDED -#define TIME_SHIELD_MQL5_HEADER_TIME_SHIELD_VALIDATION_MQH_INCLUDED +#ifndef TIME_SHIELD_MQL5_HEADER_VALIDATION_MQH_INCLUDED +#define TIME_SHIELD_MQL5_HEADER_VALIDATION_MQH_INCLUDED /// \file validation.mqh /// \ingroup mql5 @@ -303,4 +303,4 @@ namespace time_shield { }; // namespace time_shield -#endif // TIME_SHIELD_MQL5_HEADER_TIME_SHIELD_VALIDATION_MQH_INCLUDED +#endif // TIME_SHIELD_MQL5_HEADER_VALIDATION_MQH_INCLUDED diff --git a/README-RU.md b/README-RU.md index f0ecde61..622a8777 100644 --- a/README-RU.md +++ b/README-RU.md @@ -28,6 +28,21 @@ bool monday = is_workday(now); Используйте `#include ` для полного API или подключайте отдельные заголовки для минимальной сборки. +Публичные заголовки сгруппированы по доменам. Для целевого подключения +рекомендуются следующие umbrella-заголовки: + +- `time_shield/core.hpp` — базовые типы, структуры, проверки и утилиты; +- `time_shield/conversions.hpp` — преобразования временных и календарных значений; +- `time_shield/text.hpp` — разбор и форматирование; +- `time_shield/date_time.hpp` — тип `DateTime`; +- `time_shield/timezone.hpp` — именованные зоны и фиксированные смещения; +- `time_shield/astronomy.hpp` — Julian- и лунные helper-ы; +- `time_shield/timers.hpp` — таймеры и планировщик; +- `time_shield/ntp.hpp` — необязательные NTP-клиент и сервис времени. + +Прежние пути корневых заголовков сохраняются как compatibility-forwarders. +Переход на доменные пути не требует изменения имён API. + ## Зачем Time Shield? **Time Shield** создавался как практичный инструмент для работы с временем в C++, ориентированный на прикладные и инженерные задачи. В отличие от стандартной `std::chrono` или более академичных решений вроде `HowardHinnant/date`, библиотека: @@ -57,7 +72,7 @@ bool monday = is_workday(now); ## Конфигурация -Компиляционные флаги в `time_shield/config.hpp` позволяют адаптировать библиотеку под платформу и отключать необязательные модули: +Компиляционные флаги в `time_shield/core/config.hpp` позволяют адаптировать библиотеку под платформу и отключать необязательные модули: - `TIME_SHIELD_PLATFORM_WINDOWS` / `TIME_SHIELD_PLATFORM_UNIX` — определение целевой платформы. - `TIME_SHIELD_HAS_WINSOCK` — наличие WinSock API. @@ -287,7 +302,7 @@ ts_ms_t tokyo_local_ms = ntp_tokyo.local_time_ms(); Преобразования OA совместимы с Excel/COM (базовая дата 1899-12-30), выполняются в UTC и корректно обрабатывают специальную семантику отрицательных дробных OA serials до базовой даты. ```cpp -#include +#include using namespace time_shield; @@ -302,8 +317,8 @@ oadate_t from_parts = to_oadate(2024, Month::MAY, 2, 12, 0); // 2024-05-02 12:00 Хелперы Julian Date используют пролептический григорианский календарь и ориентированы на аналитические значения (JD, MJD, JDN), а не на высокоточные эфемериды. Лунные helper-ы по-прежнему доступны через astronomy entry header. ```cpp -#include -#include +#include +#include using namespace time_shield; @@ -331,7 +346,7 @@ bool is_near_new = is_new_moon_window(fts()); // попадание в окно > - видимость (первый серп/наблюдаемость) — уже про атмосферу/высоту над горизонтом и т.п. ```cpp -#include +#include using namespace time_shield; @@ -364,8 +379,8 @@ ts_t myt = convert_time_zone(ist, TimeZone::IST, TimeZone::MYT); ### NTP‑клиент, пул и сервис времени ```cpp -#include -#include +#include +#include using namespace time_shield; diff --git a/README.md b/README.md index 1cd31ca9..84d3d18e 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,21 @@ bool monday = is_workday(now); Use `#include ` for the full API, or include specific headers for a minimal build. +Public headers are grouped by domain. Domain umbrellas provide the preferred +entry points for focused use: + +- `time_shield/core.hpp` — core types, structures, validation, and utilities; +- `time_shield/conversions.hpp` — timestamp, calendar, and offset conversions; +- `time_shield/text.hpp` — parsing and formatting; +- `time_shield/date_time.hpp` — the `DateTime` value type; +- `time_shield/timezone.hpp` — named-zone and fixed-offset clocks; +- `time_shield/astronomy.hpp` — Julian and lunar helpers; +- `time_shield/timers.hpp` — timers and scheduling; +- `time_shield/ntp.hpp` — optional NTP client and time service. + +The former root-level header paths remain available as compatibility forwarding +headers. New code can migrate to domain paths without changing API symbols. + ## Why Time Shield? **Time Shield** was created as a practical tool for handling time in C++ with a @@ -83,7 +98,7 @@ more academic solutions like `HowardHinnant/date`, the library: ## Configuration -Compile-time flags in `time_shield/config.hpp` control optional parts of the +Compile-time flags in `time_shield/core/config.hpp` control optional parts of the library and report platform capabilities: - `TIME_SHIELD_PLATFORM_WINDOWS` / `TIME_SHIELD_PLATFORM_UNIX` — detected @@ -408,8 +423,8 @@ ts_ms_t tokyo_local_ms = ntp_tokyo.local_time_ms(); ### Checking workdays ```cpp -#include -#include +#include +#include using namespace time_shield; @@ -424,7 +439,7 @@ The string helpers accept the same ISO 8601 formats as `str_to_ts` / `str_to_ts_ ### Locating first and last workdays ```cpp -#include +#include using namespace time_shield; @@ -441,7 +456,7 @@ The helpers reuse the `start_of_day` / `end_of_day` semantics and therefore retu OA conversions are Excel/COM compatible (base date 1899-12-30), operate in UTC, and preserve the special negative-fraction semantics used by OA serials before the base date. ```cpp -#include +#include using namespace time_shield; @@ -456,8 +471,8 @@ oadate_t from_parts = to_oadate(2024, Month::MAY, 2, 12, 0); // 2024-05-02 12:00 The Julian helpers use the proleptic Gregorian calendar and provide lightweight analytics-oriented values (JD, MJD, JDN) rather than high-precision ephemerides. The lunar helpers remain analytics-oriented and are exposed through the astronomy entry header. ```cpp -#include -#include +#include +#include using namespace time_shield; @@ -485,7 +500,7 @@ bool is_near_new = is_new_moon_window(fts()); // inside +/-12h new moon window > - visibility (e.g., first crescent) driven by atmosphere/horizon/altitude rather than the geocentric phase itself. ```cpp -#include +#include using namespace time_shield; @@ -545,8 +560,8 @@ occurrence/offset was used. ### NTP client, pool, and time service ```cpp -#include -#include +#include +#include using namespace time_shield; diff --git a/cmake/check_header_include_policy.cmake b/cmake/check_header_include_policy.cmake new file mode 100644 index 00000000..9cd10a01 --- /dev/null +++ b/cmake/check_header_include_policy.cmake @@ -0,0 +1,118 @@ +cmake_policy(VERSION 3.15) + +if(NOT DEFINED TIME_SHIELD_SOURCE_DIR) + message(FATAL_ERROR "TIME_SHIELD_SOURCE_DIR is required") +endif() + +set(TIME_SHIELD_INCLUDE_DIR "${TIME_SHIELD_SOURCE_DIR}/include/time_shield") +set(TIME_SHIELD_DOMAIN_UMBRELLAS + core.hpp conversions.hpp text.hpp date_time.hpp astronomy.hpp timers.hpp ntp.hpp timezone.hpp) + +set(TIME_SHIELD_DOMAIN_DEPENDENCIES_core "") +set(TIME_SHIELD_DOMAIN_DEPENDENCIES_conversions core) +set(TIME_SHIELD_DOMAIN_DEPENDENCIES_text core conversions) +set(TIME_SHIELD_DOMAIN_DEPENDENCIES_datetime core conversions text) +set(TIME_SHIELD_DOMAIN_DEPENDENCIES_astronomy core conversions) +set(TIME_SHIELD_DOMAIN_DEPENDENCIES_timers core) +set(TIME_SHIELD_DOMAIN_DEPENDENCIES_ntp core) +set(TIME_SHIELD_DOMAIN_DEPENDENCIES_timezone core conversions text datetime ntp) + +file(GLOB_RECURSE TIME_SHIELD_CANONICAL_HEADERS + "${TIME_SHIELD_INCLUDE_DIR}/*/*.hpp") + +foreach(header IN LISTS TIME_SHIELD_CANONICAL_HEADERS) + file(RELATIVE_PATH relative_header "${TIME_SHIELD_INCLUDE_DIR}" "${header}") + string(REPLACE "\\" "/" relative_header "${relative_header}") + string(REGEX MATCH "^([^/]+)/" domain_match "${relative_header}") + set(source_domain "${CMAKE_MATCH_1}") + + if(source_domain STREQUAL "detail" OR source_domain STREQUAL "ntp_client") + continue() + endif() + if(relative_header MATCHES "(^|/)legacy_aliases\\.hpp$") + continue() + endif() + + file(STRINGS "${header}" header_lines) + foreach(line IN LISTS header_lines) + if(line MATCHES "#include[ \\t]+[\"<][^\">]*\\.\\./") + message(FATAL_ERROR "Parent include is forbidden: ${relative_header}: ${line}") + endif() + + if(line MATCHES "#include[ \\t]+]+)>") + set(target "${CMAKE_MATCH_1}") + set(target_domain "") + if(target STREQUAL "date_time.hpp") + set(target_domain datetime) + elseif(target IN_LIST TIME_SHIELD_DOMAIN_UMBRELLAS) + string(REGEX REPLACE "\\.hpp$" "" target_domain "${target}") + elseif(target MATCHES "^([^/]+)/") + set(target_domain "${CMAKE_MATCH_1}") + endif() + + if(target_domain) + if(target MATCHES "/" AND NOT target_domain STREQUAL source_domain) + message(FATAL_ERROR + "Cross-domain leaf include is forbidden: ${relative_header}: ${line}") + endif() + if(target MATCHES "^[^/]+\\.hpp$" AND target_domain STREQUAL source_domain) + message(FATAL_ERROR + "Domain leaf must not include its own umbrella: ${relative_header}: ${line}") + endif() + set(allowed FALSE) + set(allowed_domains "${TIME_SHIELD_DOMAIN_DEPENDENCIES_${source_domain}}") + if(target_domain STREQUAL source_domain OR target_domain IN_LIST allowed_domains) + set(allowed TRUE) + endif() + if(NOT allowed) + message(FATAL_ERROR + "Forbidden domain dependency ${source_domain} -> ${target_domain}: ${relative_header}: ${line}") + endif() + endif() + elseif(line MATCHES "#include[ \\t]+\"([^\"]+)\"") + set(local_target "${CMAKE_MATCH_1}") + if(local_target MATCHES "^\\.\\./") + message(FATAL_ERROR "Parent include is forbidden: ${relative_header}: ${line}") + endif() + if(local_target MATCHES "/" AND NOT local_target MATCHES "^detail/" AND NOT local_target MATCHES "^ntp/") + message(FATAL_ERROR "Nested cross-domain local include: ${relative_header}: ${line}") + endif() + endif() + endforeach() +endforeach() + +foreach(umbrella IN LISTS TIME_SHIELD_DOMAIN_UMBRELLAS) + if(umbrella STREQUAL "date_time.hpp") + set(source_domain datetime) + else() + string(REGEX REPLACE "\\.hpp$" "" source_domain "${umbrella}") + endif() + file(STRINGS "${TIME_SHIELD_INCLUDE_DIR}/${umbrella}" header_lines) + foreach(line IN LISTS header_lines) + if(line MATCHES "#include[ \\t]+]+)>") + set(target "${CMAKE_MATCH_1}") + if(target STREQUAL "date_time.hpp") + set(target_domain datetime) + elseif(target IN_LIST TIME_SHIELD_DOMAIN_UMBRELLAS) + string(REGEX REPLACE "\\.hpp$" "" target_domain "${target}") + elseif(target MATCHES "^([^/]+)/") + set(target_domain "${CMAKE_MATCH_1}") + else() + set(target_domain "") + endif() + if(target_domain) + if(target MATCHES "/" AND NOT target_domain STREQUAL source_domain) + message(FATAL_ERROR + "Umbrella must include domain umbrellas, not leaves: ${umbrella}: ${line}") + endif() + set(allowed_domains "${TIME_SHIELD_DOMAIN_DEPENDENCIES_${source_domain}}") + if(NOT target_domain STREQUAL source_domain AND NOT target_domain IN_LIST allowed_domains) + message(FATAL_ERROR + "Forbidden umbrella dependency ${source_domain} -> ${target_domain}: ${umbrella}: ${line}") + endif() + endif() + endif() + endforeach() +endforeach() + +message(STATUS "Header include policy passed") diff --git a/docs/mainpage.md b/docs/mainpage.md index 6ffbf047..8c59161e 100644 --- a/docs/mainpage.md +++ b/docs/mainpage.md @@ -36,7 +36,7 @@ portable, and suitable for scenarios like logging, serialization, MQL5 usage, an \section config_sec Configuration -Compile-time macros in `time_shield/config.hpp` allow adapting the library to +Compile-time macros in `time_shield/core/config.hpp` allow adapting the library to the target platform and toggling optional modules: - `TIME_SHIELD_PLATFORM_WINDOWS` / `TIME_SHIELD_PLATFORM_UNIX` — platform @@ -62,6 +62,15 @@ rename, or an established replacement. All public symbols are declared inside the `time_shield` namespace. +\section headers_sec Public header layout + +Public headers are grouped by domain. The preferred entry points are +`time_shield/core.hpp`, `time_shield/conversions.hpp`, `time_shield/text.hpp`, +`time_shield/date_time.hpp`, `time_shield/timezone.hpp`, +`time_shield/astronomy.hpp`, `time_shield/timers.hpp`, and the optional +`time_shield/ntp.hpp`. Root-level header paths remain available as compatibility +forwarders. + \section invariants_sec API Invariants - `ts_t` represents Unix time in seconds as a signed 64-bit integer with @@ -177,8 +186,8 @@ selection with optional MAD trimming and exponential smoothing. ### Basic usage \code{.cpp} -#include -#include +#include +#include using namespace time_shield; @@ -209,9 +218,9 @@ Convert between Unix timestamps and Excel/COM OA dates, or derive basic astronomical values from calendar inputs: \code{.cpp} -#include -#include -#include +#include +#include +#include using namespace time_shield; @@ -234,7 +243,7 @@ The `MoonPhaseCalculator` class (`time_shield::astronomy::MoonPhase`) builds on Basic class usage for bespoke calculations: \code{.cpp} -#include +#include using namespace time_shield; @@ -253,8 +262,8 @@ bool near_full = calculator.is_full_moon_window(ts, 3600.0); Check whether a moment falls on a business day using timestamps, calendar components, or ISO8601 strings: \code{.cpp} -#include -#include +#include +#include using namespace time_shield; @@ -287,7 +296,7 @@ The string overloads recognise the same ISO8601 formats handled by \ref time_shi Locate the boundaries of the first and last workdays when preparing trading windows or settlement cutoffs: \code{.cpp} -#include +#include using namespace time_shield; diff --git a/examples/ntp_client_example.cpp b/examples/ntp_client_example.cpp index 80516380..f591cf9a 100644 --- a/examples/ntp_client_example.cpp +++ b/examples/ntp_client_example.cpp @@ -9,10 +9,10 @@ #include #include #include -#include +#include #if TIME_SHIELD_ENABLE_NTP_CLIENT -# include -# include +# include +# include int main() { using namespace time_shield; diff --git a/examples/ntp_time_service_example.cpp b/examples/ntp_time_service_example.cpp index a953664c..a19b4f88 100644 --- a/examples/ntp_time_service_example.cpp +++ b/examples/ntp_time_service_example.cpp @@ -4,11 +4,11 @@ #include #include -#include +#include #if TIME_SHIELD_ENABLE_NTP_CLIENT -# include -# include +# include +# include int main() { // Initialize library internals before using time-related helpers. diff --git a/examples/time_conversions_example.cpp b/examples/time_conversions_example.cpp index 05be8151..63b31571 100644 --- a/examples/time_conversions_example.cpp +++ b/examples/time_conversions_example.cpp @@ -8,7 +8,7 @@ #include #if defined(_WIN32) -#include +#include int main() { using namespace time_shield; diff --git a/examples/time_formatting_example.cpp b/examples/time_formatting_example.cpp index 16338164..ab96d51f 100644 --- a/examples/time_formatting_example.cpp +++ b/examples/time_formatting_example.cpp @@ -1,7 +1,7 @@ /// \file time_formatting_example.cpp /// \brief Demonstrates functions from time_shield::time_formatting. -#include +#include #include #include diff --git a/examples/time_formatting_showcase_example.cpp b/examples/time_formatting_showcase_example.cpp index d917e31a..1ddf8dcc 100644 --- a/examples/time_formatting_showcase_example.cpp +++ b/examples/time_formatting_showcase_example.cpp @@ -1,7 +1,7 @@ /// \file time_formatting_showcase_example.cpp /// \brief Demonstrates a broader set of formatter helpers. -#include +#include #include diff --git a/examples/time_parser_example.cpp b/examples/time_parser_example.cpp index adc73650..b242b4f7 100644 --- a/examples/time_parser_example.cpp +++ b/examples/time_parser_example.cpp @@ -1,10 +1,10 @@ /// \file time_parser_example.cpp /// \brief Demonstrates ISO8601, ISO week-date, and custom-format parsing helpers. -#include -#include -#include -#include +#include +#include +#include +#include #include diff --git a/examples/time_utils_example.cpp b/examples/time_utils_example.cpp index 2cf0d8b2..03144131 100644 --- a/examples/time_utils_example.cpp +++ b/examples/time_utils_example.cpp @@ -6,7 +6,7 @@ #include -#include +#include int main() { using namespace time_shield; diff --git a/examples/time_zone_conversions_example.cpp b/examples/time_zone_conversions_example.cpp index afb49cfa..c41db504 100644 --- a/examples/time_zone_conversions_example.cpp +++ b/examples/time_zone_conversions_example.cpp @@ -3,8 +3,8 @@ #include -#include -#include +#include +#include int main() { using namespace time_shield; diff --git a/include/time_shield.hpp b/include/time_shield.hpp index bbab8b18..f2a4bdde 100644 --- a/include/time_shield.hpp +++ b/include/time_shield.hpp @@ -6,40 +6,18 @@ /// \file time_shield.hpp /// \brief Main header file for the Time Shield library. /// \details -/// Includes all public headers of the Time Shield library, so the entire API -/// can be used via a single include directive. -/// -/// \note This header is intended for convenience. If you care about compile time, -/// include only the specific headers you need. +/// Includes all domain umbrellas of the Time Shield library. -#include "time_shield/config.hpp" ///< Configuration settings for the Time Shield library. -#include "time_shield/types.hpp" ///< Type definitions used throughout the library. -#include "time_shield/constants.hpp" ///< Constants used in time calculations. -#include "time_shield/enums.hpp" ///< Enumerations used in time representations. -#include "time_shield/time_struct.hpp" ///< Structures representing time components. -#include "time_shield/date_struct.hpp" ///< Structures representing date components. -#include "time_shield/time_zone_struct.hpp" ///< Structure representing a time zone. -#include "time_shield/date_time_struct.hpp" ///< Structure representing date and time components. -#include "time_shield/DateTime.hpp" ///< Value-type wrapper for timestamp with fixed UTC offset. -#include "time_shield/ZonedClock.hpp" ///< Clock wrapper for local time in named zones or fixed offsets. -#include "time_shield/iso_week_struct.hpp" ///< Structure representing ISO week date components. -#include "time_shield/validation.hpp" ///< Functions for validation of time-related values. -#include "time_shield/time_utils.hpp" ///< Utility functions for time manipulation. -#include "time_shield/time_conversions.hpp" ///< Functions for converting between different time representations. -#include "time_shield/iso_week_conversions.hpp" ///< Functions for ISO week date conversions and formatting. -#include "time_shield/time_conversion_aliases.hpp" ///< Convenient conversion aliases. -#include "time_shield/MoonPhase.hpp" ///< Geocentric lunar phase calculator. -#include "time_shield/time_zone_conversions.hpp" ///< Functions for converting between time zones. -#include "time_shield/time_zone_offset.hpp" ///< UTC offset arithmetic helpers (UTC <-> local) and offset extraction. -#include "time_shield/time_formatting.hpp" ///< Functions for formatting time in various standard formats. -#include "time_shield/time_parser.hpp" ///< Functions for parsing time in various standard formats. +#include "time_shield/core.hpp" ///< Core types, constants, validation, and utilities. +#include "time_shield/conversions.hpp" ///< Timestamp, calendar, and offset conversions. +#include "time_shield/text.hpp" ///< Parsing and formatting helpers. +#include "time_shield/date_time.hpp" ///< Fixed-offset date-time value type. +#include "time_shield/astronomy.hpp" ///< Julian and lunar astronomy helpers. +#include "time_shield/timers.hpp" ///< Timer and scheduling utilities. #if TIME_SHIELD_ENABLE_NTP_CLIENT -# include "time_shield/ntp_client.hpp" ///< NTP client for time offset queries. +# include "time_shield/ntp.hpp" ///< NTP client and time service. #endif -#include "time_shield/initialization.hpp" ///< Library initialization helpers. -#include "time_shield/TimerScheduler.hpp" ///< Timer scheduler utilities. -#include "time_shield/DeadlineTimer.hpp" ///< Monotonic deadline timer helper. -#include "time_shield/ElapsedTimer.hpp" ///< Monotonic elapsed time measurement helper. +#include "time_shield/timezone.hpp" ///< Named-zone and fixed-offset clocks. /// \namespace tsh /// \brief Alias for the namespace time_shield. @@ -51,15 +29,6 @@ namespace tshield = time_shield; /// \namespace time_shield /// \brief Main namespace for the Time Shield library. -/// \details -/// Contains all public types, constants, and functions of the library. -/// API coverage includes: -/// - time and date structures -/// - parsing and formatting -/// - conversions between representations (seconds/milliseconds, calendar fields, etc.) -/// - timezone utilities (named zones, UTC offsets) -/// - validation helpers -/// - timers and scheduling utilities namespace time_shield {}; #endif // TIME_SHIELD_HEADER_TIME_SHIELD_HPP_INCLUDED diff --git a/include/time_shield/CpuTickTimer.hpp b/include/time_shield/CpuTickTimer.hpp index 0c00ed24..0db1361f 100644 --- a/include/time_shield/CpuTickTimer.hpp +++ b/include/time_shield/CpuTickTimer.hpp @@ -1,137 +1,8 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_CPUTICKTIMER_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_CPUTICKTIMER_HPP_INCLUDED +#ifndef TIME_SHIELD_HEADER_CPUTICKTIMER_HPP_INCLUDED +#define TIME_SHIELD_HEADER_CPUTICKTIMER_HPP_INCLUDED -/// \file CpuTickTimer.hpp -/// \brief Helper class for measuring CPU time using get_cpu_time(). +#include -#include "time_utils.hpp" - -#include -#include - -namespace time_shield { - - /// \ingroup time_utils - /// \brief Timer that measures CPU time ticks using get_cpu_time(). - /// \details Class is intended for single-threaded use and assumes that - /// get_cpu_time() is monotonic within the current process or thread. For - /// long-running measurements (for example, durations longer than a day) it - /// is recommended to periodically call record_sample() or restart() to - /// limit floating-point precision loss. - /// \note All reported durations are expressed in CPU tick units provided - /// by get_cpu_time(). - class CpuTickTimer { - public: - /// \brief Construct timer and optionally start it immediately. - /// \param is_start_immediately Indicates whether the timer should start right away. - explicit CpuTickTimer(bool is_start_immediately = true) noexcept { - if (is_start_immediately) { - start(); - } - } - - /// \brief Start measuring CPU time. - void start() noexcept { - m_start_ticks = get_cpu_time(); - m_end_ticks = m_start_ticks; - m_is_running = true; - } - - /// \brief Restart timer and reset collected statistics. - void restart() noexcept { - reset_samples(); - start(); - } - - /// \brief Stop measuring CPU time and freeze elapsed ticks. - void stop() noexcept { - if (m_is_running) { - m_end_ticks = get_cpu_time(); - m_is_running = false; - } - } - - /// \brief Get elapsed CPU ticks since the last start. - /// \return Elapsed CPU tick units produced by get_cpu_time(). - TIME_SHIELD_NODISCARD double elapsed() const noexcept { - const double final_ticks = m_is_running ? get_cpu_time() : m_end_ticks; - return final_ticks - m_start_ticks; - } - - /// \brief Record sample using elapsed ticks and restart timer. - /// \return Collected sample value in CPU tick units or 0.0 when the - /// timer is not running. - double record_sample() noexcept { - if (!m_is_running) { - start(); - m_last_sample_ticks = 0.0; - return 0.0; - } - - const double now_ticks = get_cpu_time(); - m_last_sample_ticks = now_ticks - m_start_ticks; - m_start_ticks = now_ticks; - - accumulate_ticks(m_last_sample_ticks); - ++m_sample_count; - - return m_last_sample_ticks; - } - - /// \brief Reset collected samples without touching running state. - void reset_samples() noexcept { - m_total_ticks = 0.0; - m_total_compensation = 0.0; - m_last_sample_ticks = 0.0; - m_sample_count = 0; - } - - /// \brief Get the number of recorded samples. - /// \return Count of recorded samples. - TIME_SHIELD_NODISCARD std::size_t sample_count() const noexcept { - return m_sample_count; - } - - /// \brief Get total recorded CPU ticks across samples. - /// \return Sum of recorded CPU tick units. - TIME_SHIELD_NODISCARD double total_ticks() const noexcept { - return m_total_ticks; - } - - /// \brief Get average CPU ticks per sample. - /// \return Average CPU tick units or NaN if there are no samples. - TIME_SHIELD_NODISCARD double average_ticks() const noexcept { - if (m_sample_count == 0U) { - return std::numeric_limits::quiet_NaN(); - } - return m_total_ticks / static_cast(m_sample_count); - } - - /// \brief Get ticks collected during the last recorded sample. - /// \return Ticks from the most recent sample in CPU tick units. - TIME_SHIELD_NODISCARD double last_sample_ticks() const noexcept { - return m_last_sample_ticks; - } - - private: - void accumulate_ticks(double sample_ticks) noexcept { - const double compensated = sample_ticks - m_total_compensation; - const double updated_total = m_total_ticks + compensated; - m_total_compensation = (updated_total - m_total_ticks) - compensated; - m_total_ticks = updated_total; - } - - double m_start_ticks { 0.0 }; - double m_end_ticks { 0.0 }; - double m_total_ticks { 0.0 }; - double m_total_compensation { 0.0 }; - double m_last_sample_ticks { 0.0 }; - std::size_t m_sample_count { 0 }; - bool m_is_running { false }; - }; - -} // namespace time_shield - -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_CPUTICKTIMER_HPP_INCLUDED +#endif // TIME_SHIELD_HEADER_CPUTICKTIMER_HPP_INCLUDED diff --git a/include/time_shield/DateTime.hpp b/include/time_shield/DateTime.hpp index a51265b7..8637481e 100644 --- a/include/time_shield/DateTime.hpp +++ b/include/time_shield/DateTime.hpp @@ -1,714 +1,8 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_DATETIME_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_DATETIME_HPP_INCLUDED +#ifndef TIME_SHIELD_HEADER_DATETIME_HPP_INCLUDED +#define TIME_SHIELD_HEADER_DATETIME_HPP_INCLUDED -/// \file DateTime.hpp -/// \brief Value-type wrapper for timestamps with fixed UTC offset. +#include -#include "config.hpp" -#include "constants.hpp" -#include "date_conversions.hpp" -#include "date_time_conversions.hpp" -#include "date_time_struct.hpp" -#include "iso_week_conversions.hpp" -#include "time_formatting.hpp" -#include "time_parser.hpp" -#include "time_struct.hpp" -#include "time_utils.hpp" -#include "time_zone_offset_conversions.hpp" -#include "time_zone_struct.hpp" -#include "types.hpp" -#include "validation.hpp" - -#include -#include -#include -#include -#ifdef TIME_SHIELD_CPP17 -#include -#endif - -namespace time_shield { - - /// \brief Represents a moment in time with optional fixed UTC offset. - /// - /// Equality and ordering compare the UTC instant only and ignore the stored offset. - class DateTime { - public: - /// \brief Default constructor sets epoch with zero offset. - DateTime() noexcept - : m_utc_ms(0) - , m_offset(0) {} - - /// \brief Create instance from UTC milliseconds. - /// \param utc_ms Timestamp in milliseconds since Unix epoch (UTC). - /// \param offset Fixed UTC offset in seconds. - /// \return Constructed DateTime. - static DateTime from_unix_ms(ts_ms_t utc_ms, tz_t offset = 0) noexcept { - return DateTime(utc_ms, offset); - } - - /// \brief Create instance from UTC seconds. - /// \param utc_s Timestamp in seconds since Unix epoch (UTC). - /// \param offset Fixed UTC offset in seconds. - /// \return Constructed DateTime. - static DateTime from_unix_s(ts_t utc_s, tz_t offset = 0) noexcept { - return DateTime(sec_to_ms(utc_s), offset); - } - - /// \brief Construct instance for current UTC time. - /// \param offset Fixed UTC offset in seconds. - /// \return DateTime for the current UTC time. - static DateTime now_utc(tz_t offset = 0) noexcept { - return DateTime(ts_ms(), offset); - } - - /// \brief Build from calendar components interpreted in provided offset. - static DateTime from_components( - year_t year, - int month, - int day, - int hour = 0, - int min = 0, - int sec = 0, - int ms = 0, - tz_t offset = 0) { - const ts_ms_t local_ms = to_timestamp_ms(year, month, day, hour, min, sec, ms); - const ts_ms_t utc_ms = local_ms - offset_to_ms(offset); - return DateTime(utc_ms, offset); - } - - /// \brief Try to build from calendar components interpreted in provided offset. - /// \param year Year component. - /// \param month Month component. - /// \param day Day component. - /// \param hour Hour component. - /// \param min Minute component. - /// \param sec Second component. - /// \param ms Millisecond component. - /// \param offset Fixed UTC offset in seconds. - /// \param out Output DateTime on success. - /// \return True when components form a valid date-time and offset. - static bool try_from_components( - year_t year, - int month, - int day, - int hour, - int min, - int sec, - int ms, - tz_t offset, - DateTime& out) noexcept { - if (!is_valid_date_time(year, month, day, hour, min, sec, ms)) { - return false; - } - if (!is_valid_tz_offset(offset)) { - return false; - } - const ts_ms_t local_ms = to_timestamp_ms(year, month, day, hour, min, sec, ms); - out = DateTime(local_ms - offset_to_ms(offset), offset); - return true; - } - - /// \brief Build from DateTimeStruct interpreted in provided offset. - static DateTime from_date_time_struct(const DateTimeStruct& local_dt, tz_t offset = 0) { - const ts_ms_t local_ms = dt_to_timestamp_ms(local_dt); - const ts_ms_t utc_ms = local_ms - offset_to_ms(offset); - return DateTime(utc_ms, offset); - } - - /// \brief Try to build from DateTimeStruct interpreted in provided offset. - /// \param local_dt Local date-time structure. - /// \param offset Fixed UTC offset in seconds. - /// \param out Output DateTime on success. - /// \return True when structure and offset are valid. - static bool try_from_date_time_struct( - const DateTimeStruct& local_dt, - tz_t offset, - DateTime& out) noexcept { - if (!is_valid_date_time(local_dt)) { - return false; - } - if (!is_valid_tz_offset(offset)) { - return false; - } - const ts_ms_t local_ms = dt_to_timestamp_ms(local_dt); - out = DateTime(local_ms - offset_to_ms(offset), offset); - return true; - } - - /// \brief Convert to date-time structure using stored offset. - DateTimeStruct to_date_time_struct_local() const { - return to_date_time_ms(local_ms()); - } - - /// \brief Convert to UTC date-time structure. - DateTimeStruct to_date_time_struct_utc() const { - return to_date_time_ms(m_utc_ms); - } - - /// \brief Build instance from ISO week date interpreted in provided offset. - static DateTime from_iso_week_date( - const IsoWeekDateStruct& iso, - int hour = 0, - int min = 0, - int sec = 0, - int ms = 0, - tz_t offset = 0) { - const DateStruct date = iso_week_date_to_date(iso); - return from_components(date.year, date.mon, date.day, hour, min, sec, ms, offset); - } - - /// \brief Try to parse ISO8601 string to DateTime. - /// \param str Input ISO8601 string. - /// \param out Output DateTime when parsing succeeds. - /// \return True on success. - static bool try_parse_iso8601(const std::string& str, DateTime& out) noexcept { - return try_parse_iso8601_buffer(str.data(), str.size(), out); - } - - #ifdef TIME_SHIELD_CPP17 - /// \brief Try to parse ISO8601 string_view to DateTime. - /// \param str Input ISO8601 string_view. - /// \param out Output DateTime when parsing succeeds. - /// \return True on success. - static bool try_parse_iso8601(std::string_view str, DateTime& out) noexcept { - return try_parse_iso8601_buffer(str.data(), str.size(), out); - } - #endif - - /// \brief Try to parse ISO8601 C-string to DateTime. - /// \param str Null-terminated ISO8601 string. - /// \param out Output DateTime when parsing succeeds. - /// \return True on success. - static bool try_parse_iso8601(const char* str, DateTime& out) noexcept { - if (str == nullptr) { - return false; - } - return try_parse_iso8601_buffer(str, std::strlen(str), out); - } - - /// \brief Parse ISO8601 string, throws on failure. - /// \param str Input ISO8601 string. - /// \return Parsed DateTime. - static DateTime parse_iso8601(const std::string& str) { - return parse_iso8601_buffer(str.data(), str.size()); - } - - #ifdef TIME_SHIELD_CPP17 - /// \brief Parse ISO8601 string_view, throws on failure. - /// \param str Input ISO8601 view. - /// \return Parsed DateTime. - static DateTime parse_iso8601(std::string_view str) { - return parse_iso8601_buffer(str.data(), str.size()); - } - #endif - - /// \brief Parse ISO8601 C-string, throws on failure. - /// \param str Null-terminated ISO8601 string. - /// \return Parsed DateTime. - static DateTime parse_iso8601(const char* str) { - if (str == nullptr) { - throw std::invalid_argument("Invalid ISO8601 datetime"); - } - return parse_iso8601_buffer(str, std::strlen(str)); - } - - /// \brief Try to parse ISO week-date string. - /// \param str Input ISO week-date string. - /// \param iso Output ISO week-date structure. - /// \return True on success. - /// \details Parser accepts canonical and compatible mixed separator variants, - /// uppercase or lowercase `W`, and Monday default when weekday is omitted. - static bool try_parse_iso_week_date(const std::string& str, IsoWeekDateStruct& iso) noexcept { - return parse_iso_week_date(str.data(), str.size(), iso); - } - - #ifdef TIME_SHIELD_CPP17 - /// \brief Try to parse ISO week-date string_view. - /// \details Parser accepts canonical and compatible mixed separator variants, - /// uppercase or lowercase `W`, and Monday default when weekday is omitted. - static bool try_parse_iso_week_date(std::string_view str, IsoWeekDateStruct& iso) noexcept { - return parse_iso_week_date(str.data(), str.size(), iso); - } - #endif - - /// \brief Try to parse ISO week-date C-string. - /// \details Parser accepts canonical and compatible mixed separator variants, - /// uppercase or lowercase `W`, and Monday default when weekday is omitted. - static bool try_parse_iso_week_date(const char* str, IsoWeekDateStruct& iso) noexcept { - if (str == nullptr) { - return false; - } - return parse_iso_week_date(str, std::strlen(str), iso); - } - - /// \brief Format to ISO8601 string with stored offset. - std::string to_iso8601() const { - return to_iso8601_ms(m_utc_ms, m_offset); - } - - /// \brief Format to ISO8601 string in UTC. - std::string to_iso8601_utc() const { - return to_iso8601_utc_ms(m_utc_ms); - } - - /// \brief Format using custom pattern. - std::string format(const std::string& fmt) const { - return to_string_ms(fmt, m_utc_ms, m_offset); - } - - #ifdef TIME_SHIELD_CPP17 - /// \brief Format using custom string_view pattern. - std::string format(std::string_view fmt) const { - return to_string_ms(std::string(fmt), m_utc_ms, m_offset); - } - #endif - - /// \brief Format using C-string pattern. - std::string format(const char* fmt) const { - if (fmt == nullptr) { - return std::string(); - } - return to_string_ms(std::string(fmt), m_utc_ms, m_offset); - } - - /// \brief Format to MQL5 date-time string. - std::string to_mql5_date_time() const { - return time_shield::to_mql5_date_time(ms_to_sec(local_ms())); - } - - /// \brief Access UTC milliseconds. - ts_ms_t unix_ms() const noexcept { - return m_utc_ms; - } - - /// \brief Access UTC seconds. - ts_t unix_s() const noexcept { - return ms_to_sec(m_utc_ms); - } - - /// \brief Access stored UTC offset. - tz_t utc_offset() const noexcept { - return m_offset; - } - - /// \brief Get timezone structure from offset. - TimeZoneStruct time_zone() const { - return to_time_zone_struct(m_offset); - } - - /// \brief Local year component. - year_t year() const { - return to_date_time_struct_local().year; - } - - /// \brief Local month component. - int month() const { - return to_date_time_struct_local().mon; - } - - /// \brief Local day component. - int day() const { - return to_date_time_struct_local().day; - } - - /// \brief Local hour component. - int hour() const { - return to_date_time_struct_local().hour; - } - - /// \brief Local minute component. - int minute() const { - return to_date_time_struct_local().min; - } - - /// \brief Local second component. - int second() const { - return to_date_time_struct_local().sec; - } - - /// \brief Local millisecond component. - int millisecond() const { - return to_date_time_struct_local().ms; - } - - /// \brief Local date components. - DateStruct date() const { - const DateTimeStruct local_dt = to_date_time_struct_local(); - return create_date_struct(local_dt.year, local_dt.mon, local_dt.day); - } - - /// \brief Local time-of-day components. - TimeStruct time_of_day() const { - const DateTimeStruct local_dt = to_date_time_struct_local(); - return create_time_struct( - static_cast(local_dt.hour), - static_cast(local_dt.min), - static_cast(local_dt.sec), - static_cast(local_dt.ms)); - } - - /// \brief UTC date components. - DateStruct utc_date() const { - const DateTimeStruct utc_dt = to_date_time_struct_utc(); - return create_date_struct(utc_dt.year, utc_dt.mon, utc_dt.day); - } - - /// \brief UTC time-of-day components. - TimeStruct utc_time_of_day() const { - const DateTimeStruct utc_dt = to_date_time_struct_utc(); - return create_time_struct( - static_cast(utc_dt.hour), - static_cast(utc_dt.min), - static_cast(utc_dt.sec), - static_cast(utc_dt.ms)); - } - - /// \brief UTC year component. - year_t utc_year() const { - return to_date_time_struct_utc().year; - } - - /// \brief UTC month component. - int utc_month() const { - return to_date_time_struct_utc().mon; - } - - /// \brief UTC day component. - int utc_day() const { - return to_date_time_struct_utc().day; - } - - /// \brief UTC hour component. - int utc_hour() const { - return to_date_time_struct_utc().hour; - } - - /// \brief UTC minute component. - int utc_minute() const { - return to_date_time_struct_utc().min; - } - - /// \brief UTC second component. - int utc_second() const { - return to_date_time_struct_utc().sec; - } - - /// \brief UTC millisecond component. - int utc_millisecond() const { - return to_date_time_struct_utc().ms; - } - - /// \brief Local weekday. - Weekday weekday() const { - const DateStruct local_date = date(); - return weekday_of_date(local_date); - } - - /// \brief Local ISO weekday number (1..7). - int iso_weekday() const { - const DateStruct local_date = date(); - return iso_weekday_of_date(local_date.year, local_date.mon, local_date.day); - } - - /// \brief Local ISO week date. - IsoWeekDateStruct iso_week_date() const { - const DateStruct local_date = date(); - return to_iso_week_date(local_date.year, local_date.mon, local_date.day); - } - - /// \brief UTC weekday. - Weekday utc_weekday() const { - const DateStruct utc_dt = utc_date(); - return weekday_of_date(utc_dt); - } - - /// \brief UTC ISO weekday number (1..7). - int utc_iso_weekday() const { - const DateStruct utc_dt = utc_date(); - return iso_weekday_of_date(utc_dt.year, utc_dt.mon, utc_dt.day); - } - - /// \brief UTC ISO week date. - IsoWeekDateStruct utc_iso_week_date() const { - const DateStruct utc_dt = utc_date(); - return to_iso_week_date(utc_dt.year, utc_dt.mon, utc_dt.day); - } - - /// \brief Check if local date is a workday. - bool is_workday() const noexcept { - return is_workday_ms(local_ms()); - } - - /// \brief Check if local date is a weekend. - bool is_weekend() const noexcept { - return time_shield::is_weekend(ms_to_sec(local_ms())); - } - - /// \brief Check if UTC date is a workday. - bool utc_is_workday() const noexcept { - const DateStruct utc_dt = utc_date(); - return time_shield::is_workday(utc_dt.year, utc_dt.mon, utc_dt.day); - } - - /// \brief Check if UTC date is a weekend. - bool utc_is_weekend() const noexcept { - return time_shield::is_weekend(ms_to_sec(m_utc_ms)); - } - - /// \brief Compare equality by UTC instant. - bool operator==(const DateTime& other) const noexcept { - return m_utc_ms == other.m_utc_ms; - } - - /// \brief Compare inequality by UTC instant. - bool operator!=(const DateTime& other) const noexcept { - return !(*this == other); - } - - /// \brief Less-than comparison by UTC instant. - bool operator<(const DateTime& other) const noexcept { - return m_utc_ms < other.m_utc_ms; - } - - /// \brief Less-than-or-equal comparison by UTC instant. - bool operator<=(const DateTime& other) const noexcept { - return m_utc_ms <= other.m_utc_ms; - } - - /// \brief Greater-than comparison by UTC instant. - bool operator>(const DateTime& other) const noexcept { - return m_utc_ms > other.m_utc_ms; - } - - /// \brief Greater-than-or-equal comparison by UTC instant. - bool operator>=(const DateTime& other) const noexcept { - return m_utc_ms >= other.m_utc_ms; - } - - /// \brief Check if local representations match including offset. - bool same_local(const DateTime& other) const noexcept { - return local_ms() == other.local_ms() && m_offset == other.m_offset; - } - - /// \brief Add milliseconds to UTC instant. - DateTime add_ms(int64_t delta_ms) const noexcept { - return DateTime(m_utc_ms + delta_ms, m_offset); - } - - /// \brief Add seconds to UTC instant. - DateTime add_seconds(int64_t seconds) const noexcept { - return add_ms(sec_to_ms(seconds)); - } - - /// \brief Add minutes to UTC instant. - DateTime add_minutes(int64_t minutes) const noexcept { - return add_ms(sec_to_ms(minutes * SEC_PER_MIN)); - } - - /// \brief Add hours to UTC instant. - DateTime add_hours(int64_t hours) const noexcept { - return add_ms(sec_to_ms(hours * SEC_PER_HOUR)); - } - - /// \brief Add days to UTC instant. - DateTime add_days(int64_t days) const noexcept { - return add_ms(days * MS_PER_DAY); - } - - /// \brief Difference in milliseconds to another DateTime. - int64_t diff_ms(const DateTime& other) const noexcept { - return m_utc_ms - other.m_utc_ms; - } - - /// \brief Difference in seconds to another DateTime. - double diff_seconds(const DateTime& other) const noexcept { - return static_cast(diff_ms(other)) / static_cast(MS_PER_SEC); - } - - /// \brief Return copy with new offset preserving instant. - DateTime with_offset(tz_t new_offset) const noexcept { - return DateTime(m_utc_ms, new_offset); - } - - /// \brief Return copy with zero offset. - DateTime to_utc() const noexcept { - return with_offset(0); - } - - /// \brief Start of local day. - DateTime start_of_day() const { - const DateTimeStruct local_dt = to_date_time_struct_local(); - const ts_ms_t local_start_ms = to_timestamp_ms(local_dt.year, local_dt.mon, local_dt.day); - return from_unix_ms(local_start_ms - offset_to_ms(m_offset), m_offset); - } - - /// \brief End of local day. - DateTime end_of_day() const { - const DateTimeStruct local_dt = to_date_time_struct_local(); - const ts_ms_t local_end_ms = to_timestamp_ms( - local_dt.year, - local_dt.mon, - local_dt.day, - 23, - 59, - 59, - static_cast(MS_PER_SEC - 1)); - return from_unix_ms(local_end_ms - offset_to_ms(m_offset), m_offset); - } - - /// \brief Start of local month. - DateTime start_of_month() const { - const DateTimeStruct local_dt = to_date_time_struct_local(); - const ts_ms_t local_start_ms = to_timestamp_ms(local_dt.year, local_dt.mon, 1); - return from_unix_ms(local_start_ms - offset_to_ms(m_offset), m_offset); - } - - /// \brief End of local month. - DateTime end_of_month() const { - const DateTimeStruct local_dt = to_date_time_struct_local(); - const int days = num_days_in_month(local_dt.year, local_dt.mon); - const ts_ms_t local_end_ms = to_timestamp_ms( - local_dt.year, - local_dt.mon, - days, - 23, - 59, - 59, - static_cast(MS_PER_SEC - 1)); - return from_unix_ms(local_end_ms - offset_to_ms(m_offset), m_offset); - } - - /// \brief Start of local year. - DateTime start_of_year() const { - const year_t local_year = year(); - const ts_ms_t local_start_ms = to_timestamp_ms(local_year, 1, 1); - return from_unix_ms(local_start_ms - offset_to_ms(m_offset), m_offset); - } - - /// \brief End of local year. - DateTime end_of_year() const { - const year_t local_year = year(); - const ts_ms_t local_end_ms = to_timestamp_ms( - local_year, - 12, - 31, - 23, - 59, - 59, - static_cast(MS_PER_SEC - 1)); - return from_unix_ms(local_end_ms - offset_to_ms(m_offset), m_offset); - } - - /// \brief Start of UTC day. - DateTime start_of_utc_day() const { - const DateTimeStruct utc_dt = to_date_time_struct_utc(); - return from_unix_ms(to_timestamp_ms(utc_dt.year, utc_dt.mon, utc_dt.day), m_offset); - } - - /// \brief End of UTC day. - DateTime end_of_utc_day() const { - const DateTimeStruct utc_dt = to_date_time_struct_utc(); - return from_unix_ms( - to_timestamp_ms( - utc_dt.year, - utc_dt.mon, - utc_dt.day, - 23, - 59, - 59, - static_cast(MS_PER_SEC - 1)), - m_offset); - } - - /// \brief Start of UTC month. - DateTime start_of_utc_month() const { - const DateTimeStruct utc_dt = to_date_time_struct_utc(); - return from_unix_ms(to_timestamp_ms(utc_dt.year, utc_dt.mon, 1), m_offset); - } - - /// \brief End of UTC month. - DateTime end_of_utc_month() const { - const DateTimeStruct utc_dt = to_date_time_struct_utc(); - const int days = num_days_in_month(utc_dt.year, utc_dt.mon); - return from_unix_ms( - to_timestamp_ms( - utc_dt.year, - utc_dt.mon, - days, - 23, - 59, - 59, - static_cast(MS_PER_SEC - 1)), - m_offset); - } - - /// \brief Start of UTC year. - DateTime start_of_utc_year() const { - const year_t utc_year_value = utc_year(); - return from_unix_ms(to_timestamp_ms(utc_year_value, 1, 1), m_offset); - } - - /// \brief End of UTC year. - DateTime end_of_utc_year() const { - const year_t utc_year_value = utc_year(); - return from_unix_ms( - to_timestamp_ms( - utc_year_value, - 12, - 31, - 23, - 59, - 59, - static_cast(MS_PER_SEC - 1)), - m_offset); - } - - private: - static bool try_parse_iso8601_buffer(const char* data, std::size_t size, DateTime& out) noexcept { - if (data == nullptr) { - return false; - } - DateTimeStruct dt = create_date_time_struct(0); - TimeZoneStruct tz = create_time_zone_struct(0, 0, true); - if (!time_shield::parse_iso8601(data, size, dt, tz)) { - return false; - } - const tz_t offset = time_zone_struct_to_offset(tz); - if (!is_valid_tz_offset(offset)) { - return false; - } - const ts_ms_t utc_ms = dt_to_timestamp_ms(dt) - offset_to_ms(offset); - out = from_unix_ms(utc_ms, offset); - return true; - } - - static DateTime parse_iso8601_buffer(const char* data, std::size_t size) { - DateTime result; - if (!try_parse_iso8601_buffer(data, size, result)) { - throw std::invalid_argument("Invalid ISO8601 datetime"); - } - return result; - } - - DateTime(ts_ms_t utc_ms, tz_t offset) noexcept - : m_utc_ms(utc_ms) - , m_offset(offset) {} - - static TIME_SHIELD_CONSTEXPR ts_ms_t offset_to_ms(tz_t offset) noexcept { - return static_cast(offset) * MS_PER_SEC; - } - - ts_ms_t local_ms() const noexcept { - return m_utc_ms + offset_to_ms(m_offset); - } - - ts_ms_t m_utc_ms; - tz_t m_offset; - }; - -} // namespace time_shield - -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_DATETIME_HPP_INCLUDED +#endif // TIME_SHIELD_HEADER_DATETIME_HPP_INCLUDED diff --git a/include/time_shield/DeadlineTimer.hpp b/include/time_shield/DeadlineTimer.hpp index 57aea3a1..1f2c6732 100644 --- a/include/time_shield/DeadlineTimer.hpp +++ b/include/time_shield/DeadlineTimer.hpp @@ -1,244 +1,8 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_DEADLINETIMER_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_DEADLINETIMER_HPP_INCLUDED +#ifndef TIME_SHIELD_HEADER_DEADLINETIMER_HPP_INCLUDED +#define TIME_SHIELD_HEADER_DEADLINETIMER_HPP_INCLUDED -/// \file DeadlineTimer.hpp -/// \brief Monotonic deadline timer utility similar to Qt's QDeadlineTimer. -/// -/// DeadlineTimer provides a lightweight helper around std::chrono::steady_clock -/// that tracks an absolute deadline and exposes helpers to query the remaining -/// time or whether the deadline has already passed. +#include -#include "config.hpp" -#include "types.hpp" - -#include - -namespace time_shield { - - /// \brief Helper that models a monotonic deadline for timeout management. - /// - /// DeadlineTimer invariants: - /// * Not thread-safe. Access must stay within a single thread. - /// * `start(timeout <= 0)` marks the deadline as already due. - /// * Use `set_forever()` to represent "no timeout" semantics. - class DeadlineTimer { - public: - using clock = std::chrono::steady_clock; - using duration = clock::duration; - using time_point = clock::time_point; - - /// \brief Constructs an inactive timer. - DeadlineTimer() noexcept = default; - - /// \brief Constructs a timer with the specified absolute deadline. - explicit DeadlineTimer(time_point deadline) noexcept { - start(deadline); - } - - /// \brief Constructs a timer that expires after the given timeout. - template - explicit DeadlineTimer(std::chrono::duration timeout) noexcept { - start(timeout); - } - - /// \brief Constructs a timer that expires after the given number of milliseconds. - explicit DeadlineTimer(ts_ms_t timeout_ms) noexcept { - start_ms(timeout_ms); - } - - /// \brief Creates a timer that expires after the specified timeout. - static DeadlineTimer from_timeout(duration timeout) noexcept { - DeadlineTimer timer; - timer.start(timeout); - return timer; - } - - /// \brief Creates a timer that expires after the specified timeout. - template - static DeadlineTimer from_timeout(std::chrono::duration timeout) noexcept { - DeadlineTimer timer; - timer.start(timeout); - return timer; - } - - /// \brief Creates a timer that expires after the specified number of seconds. - static DeadlineTimer from_timeout_sec(ts_t timeout_sec) noexcept { - DeadlineTimer timer; - timer.start_sec(timeout_sec); - return timer; - } - - /// \brief Creates a timer that expires after the specified number of milliseconds. - static DeadlineTimer from_timeout_ms(ts_ms_t timeout_ms) noexcept { - DeadlineTimer timer; - timer.start_ms(timeout_ms); - return timer; - } - - /// \brief Sets the absolute deadline and marks the timer as active. - void start(time_point deadline) noexcept { - m_deadline = deadline; - m_is_running = true; - } - - /// \brief Starts the timer so it expires after the specified timeout. - /// - /// Negative durations result in an immediate expiration. Durations that - /// are shorter than the steady clock tick are rounded up to a single - /// tick to preserve the monotonic nature of the timer. - template - void start(std::chrono::duration timeout) noexcept { - const time_point now = clock::now(); - if (timeout <= decltype(timeout)::zero()) { - start(now); - return; - } - - duration safe_duration = std::chrono::duration_cast(timeout); - if (safe_duration <= duration::zero()) { - safe_duration = duration(1); - } - - const time_point max_time = (time_point::max)(); - const duration max_offset = max_time - now; - if (safe_duration >= max_offset) { - start(max_time); - return; - } - - start(now + safe_duration); - } - - /// \brief Starts the timer so it expires after the specified number of seconds. - void start_sec(ts_t timeout_sec) noexcept { - start(std::chrono::seconds(timeout_sec)); - } - - /// \brief Starts the timer so it expires after the specified number of milliseconds. - void start_ms(ts_ms_t timeout_ms) noexcept { - start(std::chrono::milliseconds(timeout_ms)); - } - - /// \brief Stops the timer and invalidates the stored deadline. - void stop() noexcept { - m_is_running = false; - m_deadline = time_point{}; - } - - /// \brief Marks the timer as running forever (no timeout). - void set_forever() noexcept { - m_is_running = true; - m_deadline = (time_point::max)(); - } - - /// \brief Checks whether the timer tracks a deadline. - TIME_SHIELD_NODISCARD bool is_running() const noexcept { - return m_is_running; - } - - /// \brief Checks whether the timer is configured for an infinite timeout. - TIME_SHIELD_NODISCARD bool is_forever() const noexcept { - return m_is_running && m_deadline == (time_point::max)(); - } - - /// \brief Returns stored deadline. - TIME_SHIELD_NODISCARD time_point deadline() const noexcept { - return m_deadline; - } - - /// \brief Returns stored deadline as milliseconds since the steady epoch. - TIME_SHIELD_NODISCARD ts_ms_t deadline_ms() const noexcept { - return std::chrono::duration_cast(m_deadline.time_since_epoch()).count(); - } - - /// \brief Returns stored deadline as seconds since the steady epoch. - TIME_SHIELD_NODISCARD ts_t deadline_sec() const noexcept { - return std::chrono::duration_cast(m_deadline.time_since_epoch()).count(); - } - - /// \brief Checks if the deadline has already expired. - TIME_SHIELD_NODISCARD bool has_expired() const noexcept { - return has_expired(clock::now()); - } - - /// \brief Checks if the deadline has expired relative to the provided millisecond timestamp. - TIME_SHIELD_NODISCARD bool has_expired_ms(ts_ms_t now_ms) const noexcept { - const duration since_epoch = std::chrono::duration_cast(std::chrono::milliseconds(now_ms)); - return has_expired(time_point(since_epoch)); - } - - /// \brief Checks if the deadline has expired relative to the provided second timestamp. - TIME_SHIELD_NODISCARD bool has_expired_sec(ts_t now_sec) const noexcept { - const duration since_epoch = std::chrono::duration_cast(std::chrono::seconds(now_sec)); - return has_expired(time_point(since_epoch)); - } - - /// \brief Checks if the deadline has expired relative to the provided time point. - TIME_SHIELD_NODISCARD bool has_expired(time_point now) const noexcept { - return m_is_running && now >= m_deadline; - } - - /// \brief Returns time remaining until the deadline. - TIME_SHIELD_NODISCARD duration remaining_time() const noexcept { - return remaining_time(clock::now()); - } - - /// \brief Returns remaining time in milliseconds until the deadline. - TIME_SHIELD_NODISCARD ts_ms_t remaining_time_ms() const noexcept { - return std::chrono::duration_cast(remaining_time()).count(); - } - - /// \brief Returns remaining time in seconds until the deadline. - TIME_SHIELD_NODISCARD ts_t remaining_time_sec() const noexcept { - return std::chrono::duration_cast(remaining_time()).count(); - } - - /// \brief Returns remaining time relative to the provided time point. - /// - /// Non-running timers and already expired timers report zero duration. - TIME_SHIELD_NODISCARD duration remaining_time(time_point now) const noexcept { - if (!m_is_running || now >= m_deadline) { - return duration::zero(); - } - return m_deadline - now; - } - - /// \brief Extends deadline by the specified duration while preventing overflow. - void add(duration extend_by) noexcept { - if (!m_is_running || extend_by <= duration::zero()) { - return; - } - - const time_point now = clock::now(); - const time_point base = m_deadline > now ? m_deadline : now; - const duration max_offset = (time_point::max)() - base; - const duration safe_offset = extend_by < max_offset ? extend_by : max_offset; - m_deadline = base + safe_offset; - } - - /// \brief Extends deadline by the specified number of seconds while preventing overflow. - void add_sec(ts_t extend_by_sec) noexcept { - if (extend_by_sec <= 0) { - return; - } - add(std::chrono::seconds(extend_by_sec)); - } - - /// \brief Extends deadline by the specified number of milliseconds while preventing overflow. - void add_ms(ts_ms_t extend_by_ms) noexcept { - if (extend_by_ms <= 0) { - return; - } - add(std::chrono::milliseconds(extend_by_ms)); - } - - private: - time_point m_deadline{}; - bool m_is_running{false}; - }; - -} // namespace time_shield - -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_DEADLINETIMER_HPP_INCLUDED +#endif // TIME_SHIELD_HEADER_DEADLINETIMER_HPP_INCLUDED diff --git a/include/time_shield/ElapsedTimer.hpp b/include/time_shield/ElapsedTimer.hpp index 14ff0094..53147b14 100644 --- a/include/time_shield/ElapsedTimer.hpp +++ b/include/time_shield/ElapsedTimer.hpp @@ -1,188 +1,8 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_ELAPSEDTIMER_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_ELAPSEDTIMER_HPP_INCLUDED +#ifndef TIME_SHIELD_HEADER_ELAPSEDTIMER_HPP_INCLUDED +#define TIME_SHIELD_HEADER_ELAPSEDTIMER_HPP_INCLUDED -/// \file ElapsedTimer.hpp -/// \brief High-precision elapsed time measurement helper similar to Qt's QElapsedTimer. -/// -/// ElapsedTimer provides lightweight access to std::chrono::steady_clock for -/// precise interval measurements without being affected by system clock -/// adjustments. +#include -#include "config.hpp" -#include "types.hpp" - -#include -#include - -namespace time_shield { - - /// \brief Helper that measures elapsed monotonic time spans. - /// - /// Instances are expected to be used from a single thread without - /// additional synchronization. - class ElapsedTimer { - public: - using clock = std::chrono::steady_clock; - using duration = clock::duration; - using time_point = clock::time_point; - - /// \brief Constructs an invalid timer. - ElapsedTimer() noexcept = default; - - /// \brief Constructs a timer that starts immediately when requested. - explicit ElapsedTimer(bool start_immediately) noexcept { - if (start_immediately) { - start(); - } - } - - /// \brief Starts the timer using the current steady clock time. - void start() noexcept { - m_start_time = clock::now(); - m_is_running = true; - } - - /// \brief Restarts the timer and returns the elapsed duration so far. - TIME_SHIELD_NODISCARD duration restart() noexcept { - const time_point now = clock::now(); - const duration delta = m_is_running ? now - m_start_time : duration::zero(); - m_start_time = now; - m_is_running = true; - return delta; - } - - /// \brief Restarts the timer using a millisecond timestamp and returns elapsed milliseconds. - TIME_SHIELD_NODISCARD ts_ms_t restart_ms(ts_ms_t now_ms) noexcept { - const duration since_epoch = std::chrono::duration_cast(std::chrono::milliseconds(now_ms)); - const time_point now(since_epoch); - const duration delta = m_is_running ? now - m_start_time : duration::zero(); - m_start_time = now; - m_is_running = true; - return std::chrono::duration_cast(delta).count(); - } - - /// \brief Restarts the timer using a second timestamp and returns elapsed seconds. - TIME_SHIELD_NODISCARD ts_t restart_sec(ts_t now_sec) noexcept { - const duration since_epoch = std::chrono::duration_cast(std::chrono::seconds(now_sec)); - const time_point now(since_epoch); - const duration delta = m_is_running ? now - m_start_time : duration::zero(); - m_start_time = now; - m_is_running = true; - return std::chrono::duration_cast(delta).count(); - } - - /// \brief Invalidates the timer so subsequent elapsed() calls return zero. - void invalidate() noexcept { - m_is_running = false; - } - - /// \brief Checks whether the timer measures elapsed time. - TIME_SHIELD_NODISCARD bool is_running() const noexcept { - return m_is_running; - } - - /// \brief Alias for is_running() to match Qt naming conventions. - TIME_SHIELD_NODISCARD bool is_valid() const noexcept { - return m_is_running; - } - - /// \brief Returns start time stored by the timer. - TIME_SHIELD_NODISCARD time_point start_time() const noexcept { - return m_start_time; - } - - /// \brief Returns elapsed duration since the timer was started. - TIME_SHIELD_NODISCARD duration elapsed() const noexcept { - return elapsed(clock::now()); - } - - /// \brief Returns elapsed duration relative to the provided time point. - TIME_SHIELD_NODISCARD duration elapsed(time_point now) const noexcept { - if (!m_is_running) { - return duration::zero(); - } - return now - m_start_time; - } - - /// \brief Returns elapsed nanoseconds since the timer was started. - TIME_SHIELD_NODISCARD std::int64_t elapsed_ns() const noexcept { - return elapsed_count(); - } - - /// \brief Returns elapsed nanoseconds relative to the provided timestamp in nanoseconds. - TIME_SHIELD_NODISCARD std::int64_t elapsed_ns(std::int64_t now_ns) const noexcept { - const duration since_epoch = std::chrono::duration_cast(std::chrono::nanoseconds(now_ns)); - const time_point now(since_epoch); - return std::chrono::duration_cast(elapsed(now)).count(); - } - - /// \brief Returns elapsed milliseconds since the timer was started. - TIME_SHIELD_NODISCARD ts_ms_t elapsed_ms() const noexcept { - return elapsed_count(); - } - - /// \brief Returns elapsed milliseconds relative to the provided timestamp in milliseconds. - TIME_SHIELD_NODISCARD ts_ms_t elapsed_ms(ts_ms_t now_ms) const noexcept { - const duration since_epoch = std::chrono::duration_cast(std::chrono::milliseconds(now_ms)); - const time_point now(since_epoch); - return std::chrono::duration_cast(elapsed(now)).count(); - } - - /// \brief Returns elapsed seconds since the timer was started. - TIME_SHIELD_NODISCARD ts_t elapsed_sec() const noexcept { - return elapsed_count(); - } - - /// \brief Returns elapsed seconds relative to the provided timestamp in seconds. - TIME_SHIELD_NODISCARD ts_t elapsed_sec(ts_t now_sec) const noexcept { - const duration since_epoch = std::chrono::duration_cast(std::chrono::seconds(now_sec)); - const time_point now(since_epoch); - return std::chrono::duration_cast(elapsed(now)).count(); - } - - /// \brief Returns elapsed duration in the desired chrono duration type. - template - TIME_SHIELD_NODISCARD typename Duration::rep elapsed_count() const noexcept { - return std::chrono::duration_cast(elapsed()).count(); - } - - /// \brief Checks if the given timeout in milliseconds has expired. - TIME_SHIELD_NODISCARD bool has_expired(ts_ms_t timeout_ms) const noexcept { - if (!m_is_running) { - return false; - } - if (timeout_ms <= 0) { - return true; - } - return elapsed_ms() >= timeout_ms; - } - - /// \brief Checks if the given timeout in seconds has expired. - TIME_SHIELD_NODISCARD bool has_expired_sec(ts_t timeout_sec) const noexcept { - if (!m_is_running) { - return false; - } - if (timeout_sec <= 0) { - return true; - } - return elapsed() >= std::chrono::seconds(timeout_sec); - } - - /// \brief Returns milliseconds since the internal clock reference. - TIME_SHIELD_NODISCARD std::int64_t ms_since_reference() const noexcept { - if (!m_is_running) { - return 0; - } - return std::chrono::duration_cast(m_start_time.time_since_epoch()).count(); - } - - private: - time_point m_start_time{}; - bool m_is_running{false}; - }; - -} // namespace time_shield - -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_ELAPSEDTIMER_HPP_INCLUDED +#endif // TIME_SHIELD_HEADER_ELAPSEDTIMER_HPP_INCLUDED diff --git a/include/time_shield/MoonPhase.hpp b/include/time_shield/MoonPhase.hpp index 0f7de3ed..2c42443d 100644 --- a/include/time_shield/MoonPhase.hpp +++ b/include/time_shield/MoonPhase.hpp @@ -1,403 +1,8 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_MOONPHASE_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_MOONPHASE_HPP_INCLUDED +#ifndef TIME_SHIELD_HEADER_MOONPHASE_HPP_INCLUDED +#define TIME_SHIELD_HEADER_MOONPHASE_HPP_INCLUDED -/// \file MoonPhase.hpp -/// \brief Geocentric Moon phase calculator and result helpers. -/// \ingroup time_conversions +#include -#include "date_time_conversions.hpp" -#include "types.hpp" - -#include -#include -#include - -namespace time_shield { - -namespace astronomy { - - /// \brief Result of Moon phase computation (geocentric approximation). - struct MoonPhaseResult { - double phase = 0.0; ///< Phase fraction in [0..1). 0=new moon, 0.5=full moon. - double illumination = 0.0; ///< Illuminated fraction in [0..1]. - double age_days = 0.0; ///< Age of the Moon in days since new moon (approx). - double distance_km = 0.0; ///< Distance to Moon in km (approx). - double diameter_deg = 0.0; ///< Angular diameter of Moon in degrees (approx). - double age_deg = 0.0; ///< Phase angle in degrees (0..360). - double phase_angle_rad = 0.0; ///< Phase angle in radians (0..2*pi). - double phase_sin = 0.0; ///< sin(phase_angle_rad) helper for continuous signal. - double phase_cos = 0.0; ///< cos(phase_angle_rad) helper for continuous signal. - double sun_distance_km = 0.0; - double sun_diameter_deg = 0.0; - }; - - /// \brief Lunar quarter instants (Unix UTC seconds, floating). - struct MoonQuarterInstants { - double previous_new_unix_s = 0.0; ///< Previous new moon instant (Unix UTC seconds, double). - double previous_first_quarter_unix_s = 0.0; ///< Previous first quarter instant (Unix UTC seconds, double). - double previous_full_unix_s = 0.0; ///< Previous full moon instant (Unix UTC seconds, double). - double previous_last_quarter_unix_s = 0.0; ///< Previous last quarter instant (Unix UTC seconds, double). - double next_new_unix_s = 0.0; ///< Next new moon instant (Unix UTC seconds, double). - double next_first_quarter_unix_s = 0.0; ///< Next first quarter instant (Unix UTC seconds, double). - double next_full_unix_s = 0.0; ///< Next full moon instant (Unix UTC seconds, double). - double next_last_quarter_unix_s = 0.0; ///< Next last quarter instant (Unix UTC seconds, double). - }; - - /// \brief Moon phase calculator (geocentric approximation). - /// - /// References: - /// - John Walker, "moontool" (Fourmilab). See: https://www.fourmilab.ch/moontoolw/ - /// - solarissmoke/php-moon-phase (port). See: https://github.com/solarissmoke/php-moon-phase/blob/master/Solaris/MoonPhase.php - /// - /// Notes: - /// - Input timestamps are assumed to be UTC Unix seconds (can be floating). - /// - Computation is geocentric (no observer latitude/longitude corrections). - /// - /// Example usage: - /// \code{.cpp} - /// using namespace time_shield; - /// MoonPhase calculator{}; - /// double ts = 1704067200.0; // 2024-01-01T00:00:00Z - /// MoonPhaseResult res = calculator.compute(ts); // illumination, angles, sin/cos - /// MoonPhase::quarters_unix_s_t quarters = calculator.quarter_times_unix(ts); // Unix seconds as double - /// MoonQuarterInstants mapped = calculator.quarter_instants_unix(ts); // structured view - /// bool near_full = calculator.is_full_moon_window(ts, 3600.0); // +/-1h window check - /// \endcode - class MoonPhase { - public: - using quarters_unix_s_t = std::array; ///< Quarter instants as Unix UTC seconds ({new, firstQ, full, lastQ} for previous and next cycles). - static constexpr double kDefaultQuarterWindow_s = 43200.0; ///< Default window around phase events (12h). - - /// \brief Compute full set of Moon phase parameters for given UTC timestamp. - /// \param unix_utc_s Timestamp in Unix UTC seconds (can be fractional). - /// \return Geocentric Moon phase parameters for the given instant. - MoonPhaseResult compute(double unix_utc_s) const noexcept { - MoonPhaseResult out{}; - const double jd = julian_day_from_unix_seconds(unix_utc_s); - - // --- Sun position --- - const double day = jd - kEpochJd; - const double N = fix_angle((360.0 / 365.2422) * day); - const double M = fix_angle(N + kElonge - kElongp); - - double Ec = kepler(M, kEccent); - Ec = std::sqrt((1.0 + kEccent) / (1.0 - kEccent)) * std::tan(Ec / 2.0); - Ec = 2.0 * rad2deg(std::atan(Ec)); - const double lambda_sun = fix_angle(Ec + kElongp); - - const double F = ((1.0 + kEccent * std::cos(deg2rad(Ec))) / (1.0 - kEccent * kEccent)); - const double sun_dist = kSunSmax / F; - const double sun_ang = F * kSunAngSiz; - - // --- Moon position --- - const double ml = fix_angle(13.1763966 * day + kMmLong); - const double MM = fix_angle(ml - 0.1114041 * day - kMmLongp); - - const double Ev = 1.2739 * std::sin(deg2rad(2.0 * (ml - lambda_sun) - MM)); - const double Ae = 0.1858 * std::sin(deg2rad(M)); - const double A3 = 0.37 * std::sin(deg2rad(M)); - const double MmP = MM + Ev - Ae - A3; - - const double mEc = 6.2886 * std::sin(deg2rad(MmP)); - const double A4 = 0.214 * std::sin(deg2rad(2.0 * MmP)); - const double lP = ml + Ev + mEc - Ae + A4; - - const double V = 0.6583 * std::sin(deg2rad(2.0 * (lP - lambda_sun))); - const double lPP = lP + V; - - // --- Phase --- - const double moon_age_deg_wrapped = fix_angle(lPP - lambda_sun); - const double moon_age_rad = deg2rad(moon_age_deg_wrapped); - const double illum = (1.0 - std::cos(moon_age_rad)) / 2.0; - - const double moon_dist = (kMsMax * (1.0 - kMecc * kMecc)) - / (1.0 + kMecc * std::cos(deg2rad(MmP + mEc))); - - const double moon_dfrac = moon_dist / kMsMax; - const double moon_ang = kMAngSiz / moon_dfrac; - - out.phase = moon_age_deg_wrapped / 360.0; - out.illumination = illum; - out.age_days = kSynMonth * out.phase; - out.distance_km = moon_dist; - out.diameter_deg = moon_ang; - out.age_deg = moon_age_deg_wrapped; - out.phase_angle_rad = moon_age_rad; - out.phase_sin = std::sin(moon_age_rad); - out.phase_cos = std::cos(moon_age_rad); - out.sun_distance_km = sun_dist; - out.sun_diameter_deg = sun_ang; - return out; - } - - /// \brief Compute only phase fraction in [0..1) for given UTC timestamp. - /// \param unix_utc_s Timestamp in Unix UTC seconds (can be fractional). - /// \return Phase fraction in the \f$[0, 1)\f$ interval where 0=new moon, 0.5=full moon. - double compute_phase(double unix_utc_s) const noexcept { - return compute(unix_utc_s).phase; - } - - /// \brief Compute quarter/new/full instants around given timestamp. - /// \param unix_utc_s Timestamp in Unix UTC seconds (can be fractional). - /// \return Array of 8 instants (Unix UTC seconds as double): {prev new, prev firstQ, prev full, prev lastQ, next new, next firstQ, next full, next lastQ}. - quarters_unix_s_t quarter_times_unix(double unix_utc_s) const noexcept { - const double sdate = julian_day_from_unix_seconds(unix_utc_s); - double adate = sdate - 45.0; - - const double ats = unix_utc_s - 86400.0 * 45.0; - const int yy = year_from_unix_seconds(ats); - const int mm = month_from_unix_seconds(ats); - - // IMPORTANT: use floating division - double k1 = std::floor((yy + ((mm - 1) * (1.0 / 12.0)) - 1900.0) * 12.3685); - double k2 = 0.0; - - double nt1 = mean_phase_jd(adate, k1); - adate = nt1; - - while (true) { - adate += kSynMonth; - k2 = k1 + 1.0; - - double nt2 = mean_phase_jd(adate, k2); - if (std::abs(nt2 - sdate) < 0.75) { - nt2 = true_phase_jd(k2, 0.0); // new moon correction - } - - if (nt1 <= sdate && nt2 > sdate) { - break; - } - - nt1 = nt2; - k1 = k2; - } - - const double dates_jd[8] = { - true_phase_jd(k1, 0.0), - true_phase_jd(k1, 0.25), - true_phase_jd(k1, 0.5), - true_phase_jd(k1, 0.75), - true_phase_jd(k2, 0.0), - true_phase_jd(k2, 0.25), - true_phase_jd(k2, 0.5), - true_phase_jd(k2, 0.75) - }; - - quarters_unix_s_t out{}; - for (std::size_t i = 0; i < 8; ++i) { - out[i] = jd_to_unix_seconds(dates_jd[i]); - } - return out; - } - - /// \brief Compatibility wrapper returning quarter instants as Unix UTC seconds. - /// \param unix_utc_s Timestamp in Unix UTC seconds (can be fractional). - /// \return Same payload as quarter_times_unix() for compatibility. - quarters_unix_s_t quarter_times(double unix_utc_s) const noexcept { - return quarter_times_unix(unix_utc_s); - } - - /// \brief Quarter instants around the provided timestamp as a structured result (Unix UTC seconds). - /// \param unix_utc_s Timestamp in Unix UTC seconds (can be fractional). - /// \return Previous/next quarter instants mapped into a structured result with Unix UTC seconds. - MoonQuarterInstants quarter_instants_unix(double unix_utc_s) const noexcept { - const auto quarters = quarter_times_unix(unix_utc_s); - MoonQuarterInstants out{}; - out.previous_new_unix_s = quarters[0]; - out.previous_first_quarter_unix_s = quarters[1]; - out.previous_full_unix_s = quarters[2]; - out.previous_last_quarter_unix_s = quarters[3]; - out.next_new_unix_s = quarters[4]; - out.next_first_quarter_unix_s = quarters[5]; - out.next_full_unix_s = quarters[6]; - out.next_last_quarter_unix_s = quarters[7]; - return out; - } - - /// \brief Check whether timestamp is inside a window around new moon. - /// \param unix_utc_s Timestamp in Unix UTC seconds (can be fractional). - /// \param window_seconds Symmetric window size in seconds around the event time. - /// \return true if the timestamp lies within the window of the previous or next new moon. - bool is_new_moon_window(double unix_utc_s, double window_seconds = kDefaultQuarterWindow_s) const noexcept { - const auto instants = quarter_instants_unix(unix_utc_s); - return is_within_window(unix_utc_s, instants.previous_new_unix_s, instants.next_new_unix_s, window_seconds); - } - - /// \brief Check whether timestamp is inside a window around full moon. - /// \param unix_utc_s Timestamp in Unix UTC seconds (can be fractional). - /// \param window_seconds Symmetric window size in seconds around the event time. - /// \return true if the timestamp lies within the window of the previous or next full moon. - bool is_full_moon_window(double unix_utc_s, double window_seconds = kDefaultQuarterWindow_s) const noexcept { - const auto instants = quarter_instants_unix(unix_utc_s); - return is_within_window(unix_utc_s, instants.previous_full_unix_s, instants.next_full_unix_s, window_seconds); - } - - /// \brief Check whether timestamp is inside a window around first quarter. - /// \param unix_utc_s Timestamp in Unix UTC seconds (can be fractional). - /// \param window_seconds Symmetric window size in seconds around the event time. - /// \return true if the timestamp lies within the window of the previous or next first quarter. - bool is_first_quarter_window(double unix_utc_s, double window_seconds = kDefaultQuarterWindow_s) const noexcept { - const auto instants = quarter_instants_unix(unix_utc_s); - return is_within_window(unix_utc_s, instants.previous_first_quarter_unix_s, instants.next_first_quarter_unix_s, window_seconds); - } - - /// \brief Check whether timestamp is inside a window around last quarter. - /// \param unix_utc_s Timestamp in Unix UTC seconds (can be fractional). - /// \param window_seconds Symmetric window size in seconds around the event time. - /// \return true if the timestamp lies within the window of the previous or next last quarter. - bool is_last_quarter_window(double unix_utc_s, double window_seconds = kDefaultQuarterWindow_s) const noexcept { - const auto instants = quarter_instants_unix(unix_utc_s); - return is_within_window(unix_utc_s, instants.previous_last_quarter_unix_s, instants.next_last_quarter_unix_s, window_seconds); - } - - private: - static double julian_day_from_unix_seconds(double unix_utc_s) noexcept { - return 2440587.5 + unix_utc_s / 86400.0; - } - - static int year_from_unix_seconds(double unix_utc_s) noexcept { - return static_cast(year_of(static_cast(unix_utc_s))); - } - - static int month_from_unix_seconds(double unix_utc_s) noexcept { - return static_cast(month_of_year(static_cast(unix_utc_s))); - } - - static double jd_to_unix_seconds(double julian_day) noexcept { - return (julian_day - 2440587.5) * 86400.0; - } - - // --- Constants (1980 January 0.0) --- - static constexpr double kEpochJd = 2444238.5; - - static constexpr double kElonge = 278.833540; - static constexpr double kElongp = 282.596403; - static constexpr double kEccent = 0.016718; - static constexpr double kSunSmax = 1.495985e8; // km - static constexpr double kSunAngSiz= 0.533128; // deg - - static constexpr double kMmLong = 64.975464; - static constexpr double kMmLongp = 349.383063; - TIME_SHIELD_MAYBE_UNUSED static constexpr double kMNode = 151.950429; - TIME_SHIELD_MAYBE_UNUSED static constexpr double kMInc = 5.145396; - static constexpr double kMecc = 0.054900; - static constexpr double kMAngSiz = 0.5181; // deg - static constexpr double kMsMax = 384401.0; // km - TIME_SHIELD_MAYBE_UNUSED static constexpr double kMParallax= 0.9507; // deg - static constexpr double kSynMonth = 29.53058868; - - static constexpr double kPi = 3.14159265358979323846; - - static double deg2rad(double deg) noexcept { return deg * (kPi / 180.0); } - static double rad2deg(double rad) noexcept { return rad * (180.0 / kPi); } - - static double fix_angle(double a) noexcept { - a = std::fmod(a, 360.0); - if (a < 0.0) a += 360.0; - return a; - } - - static double kepler(double m_deg, double ecc) noexcept { - constexpr double eps = 1e-6; - const double m = deg2rad(m_deg); - double e = m; - for (int i = 0; i < 50; ++i) { - const double delta = e - ecc * std::sin(e) - m; - e -= delta / (1.0 - ecc * std::cos(e)); - if (std::abs(delta) <= eps) break; - } - return e; - } - - double mean_phase_jd(double julian_day, double lunation_index) const noexcept { - const double jt = (julian_day - 2415020.0) / 36525.0; - const double t2 = jt * jt; - const double t3 = t2 * jt; - - return 2415020.75933 + kSynMonth * lunation_index - + 0.0001178 * t2 - - 0.000000155 * t3 - + 0.00033 * std::sin(deg2rad(166.56 + 132.87 * jt - 0.009173 * t2)); - } - - double true_phase_jd(double lunation_index, double phase_fraction) const noexcept { - // This algorithm is designed for phase_fraction in {0, 0.25, 0.5, 0.75}. - const double kx = lunation_index + phase_fraction; - const double t = kx / 1236.85; - const double t2 = t * t; - const double t3 = t2 * t; - - double pt = 2415020.75933 - + kSynMonth * kx - + 0.0001178 * t2 - - 0.000000155 * t3 - + 0.00033 * std::sin(deg2rad(166.56 + 132.87 * t - 0.009173 * t2)); - - const double m = 359.2242 + 29.10535608 * kx - 0.0000333 * t2 - 0.00000347 * t3; - const double mprime = 306.0253 + 385.81691806 * kx + 0.0107306 * t2 + 0.00001236 * t3; - const double f = 21.2964 + 390.67050646 * kx - 0.0016528 * t2 - 0.00000239 * t3; - - // Corrections (same structure as common ports of moontool) - if (phase_fraction < 0.01 || std::abs(phase_fraction - 0.5) < 0.01) { - pt += (0.1734 - 0.000393 * t) * std::sin(deg2rad(m)) - + 0.0021 * std::sin(deg2rad(2 * m)) - - 0.4068 * std::sin(deg2rad(mprime)) - + 0.0161 * std::sin(deg2rad(2 * mprime)) - - 0.0004 * std::sin(deg2rad(3 * mprime)) - + 0.0104 * std::sin(deg2rad(2 * f)) - - 0.0051 * std::sin(deg2rad(m + mprime)) - - 0.0074 * std::sin(deg2rad(m - mprime)) - + 0.0004 * std::sin(deg2rad(2 * f + m)) - - 0.0004 * std::sin(deg2rad(2 * f - m)) - - 0.0006 * std::sin(deg2rad(2 * f + mprime)) - + 0.0010 * std::sin(deg2rad(2 * f - mprime)) - + 0.0005 * std::sin(deg2rad(m + 2 * mprime)); - return pt; - } - - if (std::abs(phase_fraction - 0.25) < 0.01 || std::abs(phase_fraction - 0.75) < 0.01) { - pt += (0.1721 - 0.0004 * t) * std::sin(deg2rad(m)) - + 0.0021 * std::sin(deg2rad(2 * m)) - - 0.6280 * std::sin(deg2rad(mprime)) - + 0.0089 * std::sin(deg2rad(2 * mprime)) - - 0.0004 * std::sin(deg2rad(3 * mprime)) - + 0.0079 * std::sin(deg2rad(2 * f)) - - 0.0119 * std::sin(deg2rad(m + mprime)) - - 0.0047 * std::sin(deg2rad(m - mprime)) - + 0.0003 * std::sin(deg2rad(2 * f + m)) - - 0.0004 * std::sin(deg2rad(2 * f - m)) - - 0.0006 * std::sin(deg2rad(2 * f + mprime)) - + 0.0021 * std::sin(deg2rad(2 * f - mprime)) - + 0.0003 * std::sin(deg2rad(m + 2 * mprime)) - + 0.0004 * std::sin(deg2rad(m - 2 * mprime)) - - 0.0003 * std::sin(deg2rad(2 * m + mprime)); - - if (phase_fraction < 0.5) { - pt += 0.0028 - 0.0004 * std::cos(deg2rad(m)) + 0.0003 * std::cos(deg2rad(mprime)); - } else { - pt += -0.0028 + 0.0004 * std::cos(deg2rad(m)) - 0.0003 * std::cos(deg2rad(mprime)); - } - return pt; - } - - // Fallback: return uncorrected estimate - return pt; - } - - static bool is_within_window(double unix_utc_s, double previous_instant, double next_instant, double window_seconds) noexcept { - const double prev_delta = std::abs(unix_utc_s - previous_instant); - const double next_delta = std::abs(unix_utc_s - next_instant); - return (prev_delta <= window_seconds) || (next_delta <= window_seconds); - } - }; - -} // namespace astronomy - - /// \brief Convenience alias for the geocentric Moon phase calculator. - using MoonPhaseCalculator = astronomy::MoonPhase; - -} // namespace time_shield - -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_MOONPHASE_HPP_INCLUDED +#endif // TIME_SHIELD_HEADER_MOONPHASE_HPP_INCLUDED diff --git a/include/time_shield/TimerScheduler.hpp b/include/time_shield/TimerScheduler.hpp index 24972383..7ae3d21c 100644 --- a/include/time_shield/TimerScheduler.hpp +++ b/include/time_shield/TimerScheduler.hpp @@ -1,650 +1,8 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_TIMERSCHEDULER_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_TIMERSCHEDULER_HPP_INCLUDED +#ifndef TIME_SHIELD_HEADER_TIMERSCHEDULER_HPP_INCLUDED +#define TIME_SHIELD_HEADER_TIMERSCHEDULER_HPP_INCLUDED -/// \file TimerScheduler.hpp -/// \brief Timer scheduler that provides Qt-like timer functionality. -/// -/// TimerScheduler manages timers that can be processed either by a dedicated -/// worker thread or manually via process/update calls. Timers are rescheduled -/// using fixed-rate semantics, meaning the next activation time is based on the -/// stored fire time. Cancelled timers are removed lazily from the -/// internal queue, which can temporarily increase the queue size under frequent -/// start/stop cycles. +#include -#include "config.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace time_shield { - - class TimerScheduler; - class Timer; - - namespace detail { - - using TimerClock = std::chrono::steady_clock; - using TimerCallback = std::function; - - /// \brief Internal state shared between Timer and TimerScheduler. - struct TimerState { - TimerScheduler* m_scheduler = nullptr; - std::mutex m_callback_mutex; - TimerCallback m_callback; - std::atomic m_interval_ms{0}; - std::atomic m_is_single_shot{false}; - std::atomic m_is_active{false}; - std::atomic m_is_running{false}; - std::size_t m_id{0}; - std::atomic m_generation{0}; - std::atomic m_has_external_owner{false}; - }; - - inline TimerState*& current_timer_state() { - static TIME_SHIELD_THREAD_LOCAL TimerState* state = nullptr; - return state; - } - - struct RunningTimerScope { - explicit RunningTimerScope(TimerState* state) - : m_previous(current_timer_state()) { - current_timer_state() = state; - } - - ~RunningTimerScope() { - current_timer_state() = m_previous; - } - - private: - TimerState* m_previous; - }; - - /// \brief Data stored in the priority queue of scheduled timers. - struct ScheduledTimer { - ScheduledTimer() = default; - - ScheduledTimer(TimerClock::time_point fire_time, std::size_t timer_id, std::uint64_t generation) - : m_fire_time(fire_time), m_timer_id(timer_id), m_generation(generation) {} - - TimerClock::time_point m_fire_time{}; - std::size_t m_timer_id{0}; - std::uint64_t m_generation{0}; - }; - - /// \brief Comparator that orders timers by earliest fire time. - struct ScheduledComparator { - bool operator()(const ScheduledTimer& lhs, const ScheduledTimer& rhs) const { - return lhs.m_fire_time > rhs.m_fire_time; - } - }; - - /// \brief Helper structure that represents a timer ready to run. - struct DueTimer { - DueTimer() = default; - - DueTimer(TimerClock::time_point fire_time, - std::uint64_t generation, - std::shared_ptr state) - : m_fire_time(fire_time), m_generation(generation), m_state(std::move(state)) {} - - TimerClock::time_point m_fire_time{}; - std::uint64_t m_generation{0}; - std::shared_ptr m_state; - }; - - } // namespace detail - - using timer_state_ptr = std::shared_ptr; - - /// \brief Scheduler that manages timer execution. - class TimerScheduler { - public: - using clock = detail::TimerClock; - - TimerScheduler(); - ~TimerScheduler(); - - TimerScheduler(const TimerScheduler&) = delete; - TimerScheduler& operator=(const TimerScheduler&) = delete; - TimerScheduler(TimerScheduler&&) = delete; - TimerScheduler& operator=(TimerScheduler&&) = delete; - - /// \brief Starts a dedicated worker thread that processes timers. - /// - /// This method is non-blocking. It spawns a background thread that - /// waits for timers to fire and executes their callbacks. While the - /// worker thread is active, manual processing via process() or update() - /// must not be used. - void run(); - - /// \brief Requests the worker thread to stop and waits for it to exit. - void stop(); - - /// \brief Processes all timers that are ready to fire at the moment of the call. - /// - /// Method is non-blocking and does not wait for future timers. - /// It must not be called while the worker thread started by run() is - /// active. - void process(); - - /// \brief Alias for process() for compatibility with update-based loops. - void update(); - - /// \brief Returns number of timer states that remain alive. - /// - /// Method is intended for tests to verify resource cleanup. - std::size_t active_timer_count_for_testing(); - - private: - friend class Timer; - - timer_state_ptr create_timer_state(); - void destroy_timer_state(const timer_state_ptr& state); - void start_timer(const timer_state_ptr& state, clock::time_point when); - void stop_timer(const timer_state_ptr& state); - - void worker_loop(); - void collect_due_timers_locked(std::vector& due, clock::time_point now); - void execute_due_timers(std::vector& due); - void finalize_timer(const detail::DueTimer& due_timer); - - std::mutex m_mutex; - std::condition_variable m_cv; - std::thread m_thread; - bool m_is_worker_running{false}; - bool m_stop_requested{false}; - std::priority_queue, detail::ScheduledComparator> m_queue; - std::unordered_map> m_timers; - std::size_t m_next_id{1}; - }; - - /// \brief Timer that mimics the behavior of Qt timers. - class Timer { - public: - using Callback = detail::TimerCallback; - - explicit Timer(TimerScheduler& scheduler); - ~Timer(); - - Timer(const Timer&) = delete; - Timer& operator=(const Timer&) = delete; - Timer(Timer&&) = delete; - Timer& operator=(Timer&&) = delete; - - /// \brief Sets the interval used by the timer. - /// - /// Negative durations are clamped to zero. An interval of zero means - /// the timer is rescheduled immediately after firing. - template - void set_interval(std::chrono::duration interval) noexcept; - - /// \brief Returns the configured interval. - std::chrono::milliseconds interval() const noexcept; - - /// \brief Starts the timer using the configured interval. - void start(); - - /// \brief Starts the timer with the specified interval. - template - void start(std::chrono::duration interval); - - /// \brief Stops the timer. - /// - /// Operation is non-blocking and does not wait for a - /// running callback to finish. Use stop_and_wait() to synchronously - /// wait for completion. - void stop(); - - /// \brief Stops the timer and waits until an active callback finishes. - /// - /// Must not be called from inside the timer callback itself. - void stop_and_wait(); - - /// \brief Sets whether the timer should fire only once. - void set_single_shot(bool is_single_shot) noexcept; - - /// \brief Returns true if the timer fires only once. - bool is_single_shot() const noexcept; - - /// \brief Returns true if the timer is active. - bool is_active() const noexcept; - - /// \brief Returns true if the timer callback is being executed. - bool is_running() const noexcept; - - /// \brief Sets the callback that should be invoked when the timer fires. - void set_callback(Callback callback); - - /// \brief Creates a single-shot timer that invokes the callback once. - /// - /// Helper keeps the timer alive until the callback finishes. - template - static void single_shot(TimerScheduler& scheduler, - std::chrono::duration interval, - Callback callback); - - private: - TimerScheduler& m_scheduler; - timer_state_ptr m_state; - }; - - // --------------------------------------------------------------------- - // TimerScheduler inline implementation - // --------------------------------------------------------------------- - - inline TimerScheduler::TimerScheduler() = default; - - inline TimerScheduler::~TimerScheduler() { - stop(); - std::lock_guard lock(m_mutex); - for (auto& entry : m_timers) { - if (auto state = entry.second.lock()) { - std::lock_guard callback_lock(state->m_callback_mutex); - state->m_callback = {}; - } - } - m_timers.clear(); - while (!m_queue.empty()) { - m_queue.pop(); - } - } - - inline void TimerScheduler::run() { - std::lock_guard lock(m_mutex); - if (m_is_worker_running) { - return; - } - m_stop_requested = false; - m_is_worker_running = true; - m_thread = std::thread(&TimerScheduler::worker_loop, this); - } - - inline void TimerScheduler::stop() { - std::vector orphan_states; - std::thread worker_to_join; - - { - std::unique_lock lock(m_mutex); - if (m_is_worker_running) { - m_stop_requested = true; - m_cv.notify_all(); - worker_to_join = std::move(m_thread); - } else { - m_stop_requested = false; - } - - for (auto it = m_timers.begin(); it != m_timers.end();) { - auto state = it->second.lock(); - if (!state) { - it = m_timers.erase(it); - continue; - } - - if (!state->m_has_external_owner.load(std::memory_order_relaxed)) { - orphan_states.push_back(state); - it = m_timers.erase(it); - } else { - ++it; - } - } - } - - if (worker_to_join.joinable()) { - worker_to_join.join(); - } - - { - std::lock_guard lock(m_mutex); - m_is_worker_running = false; - m_stop_requested = false; - } - - for (auto& state : orphan_states) { - if (!state) { - continue; - } - std::lock_guard callback_lock(state->m_callback_mutex); - state->m_callback = {}; - state->m_is_active.store(false, std::memory_order_relaxed); - } - } - - inline void TimerScheduler::process() { - std::vector due; - { - std::lock_guard lock(m_mutex); - assert(!m_is_worker_running && "process() must not be called while the worker thread is active"); - const auto now = clock::now(); - collect_due_timers_locked(due, now); - } - execute_due_timers(due); - } - - inline void TimerScheduler::update() { - process(); - } - - inline std::size_t TimerScheduler::active_timer_count_for_testing() { - std::lock_guard lock(m_mutex); - std::size_t count = 0; - for (const auto& entry : m_timers) { - if (!entry.second.expired()) { - ++count; - } - } - return count; - } - - inline timer_state_ptr TimerScheduler::create_timer_state() { - auto state = std::make_shared(); - state->m_scheduler = this; - std::lock_guard lock(m_mutex); - state->m_id = m_next_id++; - m_timers[state->m_id] = state; - return state; - } - - inline void TimerScheduler::destroy_timer_state(const timer_state_ptr& state) { - if (!state) { - return; - } - { - std::lock_guard callback_lock(state->m_callback_mutex); - state->m_callback = {}; - } - std::lock_guard lock(m_mutex); - state->m_is_active.store(false, std::memory_order_relaxed); - state->m_generation.fetch_add(1, std::memory_order_relaxed); - if (state->m_id != 0) { - m_timers.erase(state->m_id); - } - state->m_scheduler = nullptr; - } - - inline void TimerScheduler::start_timer(const timer_state_ptr& state, clock::time_point when) { - if (!state) { - return; - } - std::lock_guard lock(m_mutex); - state->m_is_active.store(true, std::memory_order_relaxed); - const auto generation = state->m_generation.fetch_add(1, std::memory_order_relaxed) + 1; - m_queue.push(detail::ScheduledTimer{when, state->m_id, generation}); - m_cv.notify_all(); - } - - inline void TimerScheduler::stop_timer(const timer_state_ptr& state) { - if (!state) { - return; - } - std::lock_guard lock(m_mutex); - state->m_is_active.store(false, std::memory_order_relaxed); - state->m_generation.fetch_add(1, std::memory_order_relaxed); - m_cv.notify_all(); - } - - inline void TimerScheduler::worker_loop() { - std::vector due; - std::unique_lock lock(m_mutex); - while (!m_stop_requested) { - if (m_queue.empty()) { - m_cv.wait(lock, [this] { return m_stop_requested || !m_queue.empty(); }); - continue; - } - - const auto next_fire_time = m_queue.top().m_fire_time; - const bool woke_by_condition = m_cv.wait_until( - lock, - next_fire_time, - [this, next_fire_time] { - return m_stop_requested || m_queue.empty() || m_queue.top().m_fire_time < next_fire_time; - } - ); - - if (m_stop_requested) { - break; - } - - if (woke_by_condition) { - continue; - } - - const auto now = clock::now(); - collect_due_timers_locked(due, now); - - lock.unlock(); - execute_due_timers(due); - due.clear(); - lock.lock(); - } - } - - inline void TimerScheduler::collect_due_timers_locked(std::vector& due, clock::time_point now) { - while (!m_queue.empty()) { - const auto& top = m_queue.top(); - if (top.m_fire_time > now) { - break; - } - - detail::ScheduledTimer item = top; - m_queue.pop(); - - auto it = m_timers.find(item.m_timer_id); - if (it == m_timers.end()) { - continue; - } - - auto state = it->second.lock(); - if (!state) { - m_timers.erase(it); - continue; - } - - if (!state->m_is_active.load(std::memory_order_relaxed) || - state->m_generation.load(std::memory_order_relaxed) != item.m_generation) { - continue; - } - - state->m_is_running.store(true, std::memory_order_release); - due.push_back(detail::DueTimer{item.m_fire_time, item.m_generation, std::move(state)}); - } - } - - inline void TimerScheduler::execute_due_timers(std::vector& due) { - for (auto& timer : due) { - detail::TimerCallback callback; - if (timer.m_state) { - std::lock_guard callback_lock(timer.m_state->m_callback_mutex); - callback = timer.m_state->m_callback; - } - if (callback) { - detail::RunningTimerScope running_scope(timer.m_state.get()); - try { - callback(); - } catch (...) { - // TODO: integrate with logging once a logging facility is available. - } - } - finalize_timer(timer); - } - } - - inline void TimerScheduler::finalize_timer(const detail::DueTimer& due_timer) { - auto state = due_timer.m_state; - if (!state) { - return; - } - - std::unique_lock lock(m_mutex); - state->m_is_running.store(false, std::memory_order_release); - if (!state->m_is_active.load(std::memory_order_relaxed)) { - return; - } - - if (state->m_is_single_shot.load(std::memory_order_relaxed)) { - state->m_is_active.store(false, std::memory_order_relaxed); - state->m_generation.fetch_add(1, std::memory_order_relaxed); - return; - } - - if (state->m_generation.load(std::memory_order_relaxed) != due_timer.m_generation) { - return; - } - - const auto interval_ms = state->m_interval_ms.load(std::memory_order_relaxed); - const auto next_fire_time = due_timer.m_fire_time + std::chrono::milliseconds(interval_ms); - const auto next_generation = state->m_generation.fetch_add(1, std::memory_order_relaxed) + 1; - m_queue.push(detail::ScheduledTimer{next_fire_time, state->m_id, next_generation}); - m_cv.notify_all(); - } - - // --------------------------------------------------------------------- - // Timer inline implementation - // --------------------------------------------------------------------- - - inline Timer::Timer(TimerScheduler& scheduler) - : m_scheduler(scheduler), m_state(scheduler.create_timer_state()) { - if (m_state) { - m_state->m_has_external_owner.store(true, std::memory_order_relaxed); - } - } - - inline Timer::~Timer() { - if (!m_state) { - return; - } - - if (detail::current_timer_state() != m_state.get()) { - stop_and_wait(); - } else { - m_scheduler.stop_timer(m_state); - } - - m_state->m_has_external_owner.store(false, std::memory_order_relaxed); - m_scheduler.destroy_timer_state(m_state); - } - - template - void Timer::set_interval(std::chrono::duration interval) noexcept { - auto milliseconds = std::chrono::duration_cast(interval).count(); - if (milliseconds < 0) { - milliseconds = 0; - } - m_state->m_interval_ms.store(milliseconds, std::memory_order_relaxed); - } - - inline std::chrono::milliseconds Timer::interval() const noexcept { - const auto milliseconds = m_state->m_interval_ms.load(std::memory_order_relaxed); - return std::chrono::milliseconds(milliseconds); - } - - inline void Timer::start() { - const auto milliseconds = m_state->m_interval_ms.load(std::memory_order_relaxed); - const auto delay = TimerScheduler::clock::now() + std::chrono::milliseconds(milliseconds); - m_scheduler.start_timer(m_state, delay); - } - - template - void Timer::start(std::chrono::duration interval) { - set_interval(interval); - start(); - } - - inline void Timer::stop() { - m_scheduler.stop_timer(m_state); - } - - inline void Timer::stop_and_wait() { - assert(detail::current_timer_state() != m_state.get() - && "stop_and_wait() must not be called from inside callback"); - m_scheduler.stop_timer(m_state); - while (m_state->m_is_running.load(std::memory_order_acquire)) { - std::this_thread::yield(); - } - } - - inline void Timer::set_single_shot(bool is_single_shot) noexcept { - m_state->m_is_single_shot.store(is_single_shot, std::memory_order_relaxed); - } - - inline bool Timer::is_single_shot() const noexcept { - return m_state->m_is_single_shot.load(std::memory_order_relaxed); - } - - inline bool Timer::is_active() const noexcept { - return m_state->m_is_active.load(std::memory_order_relaxed); - } - - inline bool Timer::is_running() const noexcept { - return m_state->m_is_running.load(std::memory_order_relaxed); - } - - inline void Timer::set_callback(Callback callback) { - std::lock_guard lock(m_state->m_callback_mutex); - m_state->m_callback = std::move(callback); - } - - template - void Timer::single_shot(TimerScheduler& scheduler, - std::chrono::duration interval, - Callback callback) { - auto state = scheduler.create_timer_state(); - if (!state) { - return; - } - - auto milliseconds = std::chrono::duration_cast(interval).count(); - if (milliseconds < 0) { - milliseconds = 0; - } - - state->m_is_single_shot.store(true, std::memory_order_relaxed); - state->m_interval_ms.store(milliseconds, std::memory_order_relaxed); - - auto* scheduler_ptr = state->m_scheduler; - - Callback user_callback_local = std::move(callback); - - { - std::lock_guard lock(state->m_callback_mutex); - state->m_callback = [state, scheduler_ptr, user_callback_local]() mutable { - if (user_callback_local) { - user_callback_local(); - } - - auto state_ptr = state; - if (!state_ptr) { - return; - } - - { - std::lock_guard callback_lock(state_ptr->m_callback_mutex); - state_ptr->m_callback = {}; - } - - if (scheduler_ptr) { - scheduler_ptr->destroy_timer_state(state_ptr); - } - }; - } - - const auto fire_time = TimerScheduler::clock::now() + std::chrono::milliseconds(milliseconds); - scheduler.start_timer(state, fire_time); - } - -} // namespace time_shield - -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_TIMERSCHEDULER_HPP_INCLUDED +#endif // TIME_SHIELD_HEADER_TIMERSCHEDULER_HPP_INCLUDED diff --git a/include/time_shield/ZonedClock.hpp b/include/time_shield/ZonedClock.hpp index 0bc6662e..07f1f1a0 100644 --- a/include/time_shield/ZonedClock.hpp +++ b/include/time_shield/ZonedClock.hpp @@ -1,372 +1,8 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_ZONEDCLOCK_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_ZONEDCLOCK_HPP_INCLUDED +#ifndef TIME_SHIELD_HEADER_ZONEDCLOCK_HPP_INCLUDED +#define TIME_SHIELD_HEADER_ZONEDCLOCK_HPP_INCLUDED -/// \file ZonedClock.hpp -/// \brief Header-only clock wrapper for named zones, fixed offsets, and optional NTP-backed UTC time. +#include -#include "config.hpp" -#include "constants.hpp" -#include "DateTime.hpp" -#include "enums.hpp" -#include "time_parser.hpp" -#include "time_utils.hpp" -#include "time_zone_conversions.hpp" -#include "time_zone_offset_conversions.hpp" - -#if TIME_SHIELD_ENABLE_NTP_CLIENT -# include "ntp_time_service.hpp" -#endif - -#include -#include -#include - -namespace time_shield { - - /// \brief Stores a target local-time context backed by a named zone or fixed UTC offset. - /// - /// Class resolves the effective offset on demand. Named zones are recalculated - /// for the requested UTC instant, while numeric offsets remain fixed. Current UTC time - /// can come from the local realtime clock or from the global NTP service. - class ZonedClock final { - public: - /// \brief Construct UTC fixed-offset clock without NTP. - ZonedClock() noexcept - : m_zone(UNKNOWN) - , m_offset(0) - , m_is_named_zone(false) - , m_use_ntp(false) {} - - /// \brief Construct clock for a named zone. - /// \param zone Supported named zone. - /// \param use_ntp Use NTP-backed UTC time when true. - explicit ZonedClock(TimeZone zone, bool use_ntp = false) noexcept - : m_zone(UNKNOWN) - , m_offset(0) - , m_is_named_zone(false) - , m_use_ntp(use_ntp) { - set_zone(zone); - } - - /// \brief Construct clock for a fixed UTC offset. - /// \param utc_offset Fixed UTC offset in seconds. - /// \param use_ntp Use NTP-backed UTC time when true. - /// \throw std::invalid_argument if utc_offset is outside the supported range. - explicit ZonedClock(tz_t utc_offset, bool use_ntp = false) - : m_zone(UNKNOWN) - , m_offset(0) - , m_is_named_zone(false) - , m_use_ntp(use_ntp) { - if (!try_set_offset(utc_offset)) { - throw std::invalid_argument("Invalid UTC offset"); - } - } - - /// \brief Try to build fixed-offset clock without throwing. - /// \param utc_offset Fixed UTC offset in seconds. - /// \param out Output clock on success. - /// \return True when the offset is valid and out is updated. - static bool try_from_offset(tz_t utc_offset, ZonedClock& out) noexcept { - ZonedClock candidate; - if (!candidate.try_set_offset(utc_offset)) { - return false; - } - out = candidate; - return true; - } - - /// \brief Set the stored named zone. - /// \param zone Supported named zone. `UNKNOWN` resets the instance to fixed UTC offset `+00:00`. - void set_zone(TimeZone zone) noexcept { - if (zone == UNKNOWN) { - m_zone = UNKNOWN; - m_offset = 0; - m_is_named_zone = false; - return; - } - - m_zone = zone; - m_offset = 0; - m_is_named_zone = true; - } - - /// \brief Set the stored fixed UTC offset. - /// \param utc_offset Fixed UTC offset in seconds. - /// \return True when the offset is valid. - bool try_set_offset(tz_t utc_offset) noexcept { - if (!is_valid_tz_offset(utc_offset)) { - return false; - } - - m_zone = UNKNOWN; - m_offset = utc_offset; - m_is_named_zone = false; - return true; - } - - /// \brief Parse and set a named zone or numeric offset from string. - /// \param zone_spec Input string with ASCII trimming applied before parsing. - /// \return True when parsing succeeds. - bool try_set_zone(const std::string& zone_spec) noexcept { - const std::string trimmed = trim_ascii(zone_spec); - if (trimmed.empty()) { - return false; - } - - TimeZone parsed_zone = UNKNOWN; - if (parse_time_zone_name(trimmed, parsed_zone)) { - set_zone(parsed_zone); - return true; - } - - TimeZoneStruct parsed_offset = create_time_zone_struct(0, 0, true); - if (!parse_time_zone(trimmed, parsed_offset)) { - return false; - } - - return try_set_offset(time_zone_struct_to_offset(parsed_offset)); - } - - /// \brief Set preferred UTC source. - /// \param use_ntp Use NTP-backed UTC time when true. - void set_use_ntp(bool use_ntp) noexcept { - m_use_ntp = use_ntp; - } - - /// \brief Return true when the instance stores a named zone. - bool has_named_zone() const noexcept { - return m_is_named_zone; - } - - /// \brief Return stored named zone or `UNKNOWN` for fixed-offset mode. - TimeZone zone() const noexcept { - return m_is_named_zone ? m_zone : UNKNOWN; - } - - /// \brief Return the preferred UTC source flag. - bool use_ntp() const noexcept { - return m_use_ntp; - } - - /// \brief Return true when the global NTP service is active for this clock. - bool ntp_active() const noexcept { -#if TIME_SHIELD_ENABLE_NTP_CLIENT - return m_use_ntp && NtpTimeService::instance().running(); -#else - return false; -#endif - } - - /// \brief Return effective UTC offset in seconds for the current UTC instant. - tz_t offset_now() const noexcept { - return offset_at_utc_ms(current_utc_ms()); - } - - /// \brief Return effective UTC offset in seconds for a specific UTC instant. - /// \param utc_ms UTC timestamp in milliseconds. - /// \return Effective UTC offset in seconds. - tz_t offset_at_utc_ms(ts_ms_t utc_ms) const noexcept { - tz_t offset = 0; - return try_offset_at_utc_ms(utc_ms, offset) ? offset : 0; - } - - /// \brief Try to resolve effective UTC offset for a UTC instant. - /// \param utc_ms UTC timestamp in milliseconds. - /// \param out Receives offset in seconds on success. - /// \return True when the offset can be resolved. - bool try_offset_at_utc_ms(ts_ms_t utc_ms, tz_t& out) const noexcept { - if (utc_ms == ERROR_TIMESTAMP) { - return false; - } - - if (!m_is_named_zone) { - out = m_offset; - return true; - } - - return zone_offset_at_utc_ms(utc_ms, m_zone, out); - } - - /// \brief Resolve a local timestamp in this clock's zone. - /// \param local_ms Local civil timestamp in milliseconds. - /// \return Local-time resolution with zero, one, or two UTC candidates. - LocalTimeResolution resolve_local_time_ms(ts_ms_t local_ms) const noexcept { - if (local_ms == ERROR_TIMESTAMP) { - LocalTimeResolution result = { - LocalTimeStatus::unsupported, - ERROR_TIMESTAMP, - ERROR_TIMESTAMP - }; - return result; - } - - if (m_is_named_zone) { - return time_shield::resolve_local_time_ms(local_ms, m_zone); - } - - LocalTimeResolution result = { - LocalTimeStatus::valid, - time_shield::to_utc_ms(local_ms, m_offset), - ERROR_TIMESTAMP - }; - return result; - } - - /// \brief Convert a local timestamp in this clock's zone to UTC. - /// \param local_ms Local civil timestamp in milliseconds. - /// \param ambiguous_policy Policy for DST-fold local times. - /// \param nonexistent_policy Policy for DST-gap local times. - /// \return UTC timestamp in milliseconds, or ERROR_TIMESTAMP. - ts_ms_t to_utc_ms( - ts_ms_t local_ms, - AmbiguousTimePolicy ambiguous_policy = AmbiguousTimePolicy::error, - NonexistentTimePolicy nonexistent_policy = - NonexistentTimePolicy::error) const noexcept { - if (m_is_named_zone) { - return zone_to_gmt_ms(local_ms, - m_zone, - ambiguous_policy, - nonexistent_policy); - } - - return time_shield::to_utc_ms(local_ms, m_offset); - } - - /// \brief Return current UTC time in seconds. - ts_t utc_time_sec() const noexcept { - return static_cast(current_utc_us() / US_PER_SEC); - } - - /// \brief Return current UTC time in milliseconds. - ts_ms_t utc_time_ms() const noexcept { - return current_utc_ms(); - } - - /// \brief Return current UTC time in microseconds. - ts_us_t utc_time_us() const noexcept { - return current_utc_us(); - } - - /// \brief Return current local timestamp in seconds. - ts_t local_time_sec() const noexcept { - const ts_t utc_sec = utc_time_sec(); - return utc_sec + static_cast(offset_at_utc_ms(static_cast(utc_sec) * MS_PER_SEC)); - } - - /// \brief Return current local timestamp in milliseconds. - ts_ms_t local_time_ms() const noexcept { - const ts_ms_t utc_ms = current_utc_ms(); - return utc_ms + static_cast(offset_at_utc_ms(utc_ms)) * MS_PER_SEC; - } - - /// \brief Return current local timestamp in microseconds. - ts_us_t local_time_us() const noexcept { - const ts_us_t utc_us = current_utc_us(); - return utc_us + static_cast(offset_at_utc_ms(static_cast(utc_us / MS_PER_SEC))) * US_PER_SEC; - } - - /// \brief Return current time snapshot with resolved fixed offset. - DateTime now() const noexcept { - return from_utc_ms(current_utc_ms()); - } - - /// \brief Return a snapshot for a specific UTC instant in milliseconds. - /// \param utc_ms UTC timestamp in milliseconds. - /// \return DateTime snapshot with resolved fixed offset. - DateTime from_utc_ms(ts_ms_t utc_ms) const noexcept { - return DateTime::from_unix_ms(utc_ms, offset_at_utc_ms(utc_ms)); - } - - /// \brief Return a snapshot for a specific UTC instant in seconds. - /// \param utc_s UTC timestamp in seconds. - /// \return DateTime snapshot with resolved fixed offset. - DateTime from_utc_s(ts_t utc_s) const noexcept { - return from_utc_ms(static_cast(utc_s) * MS_PER_SEC); - } - - /// \brief Return short name of the stored named zone. - /// \return Zone abbreviation or an empty string in fixed-offset mode. - std::string zone_name() const { - return m_is_named_zone ? std::string(to_cstr(m_zone)) : std::string(); - } - - /// \brief Return human-readable zone label. - /// \return Full zone name for named zones or `UTC+/-HH:MM` for fixed offsets. - std::string zone_full_name() const { - if (m_is_named_zone) { - return to_str(m_zone, FULL_NAME); - } - return std::string("UTC") + offset_string_for_offset(m_offset); - } - - /// \brief Return effective numeric UTC offset as `+HH:MM` or `-HH:MM`. - std::string offset_string() const { - return offset_string_for_offset(offset_now()); - } - - /// \brief Return current local time formatted as ISO8601 with offset. - std::string to_iso8601() const { - return now().to_iso8601(); - } - - /// \brief Return current UTC time formatted as ISO8601 with `Z`. - std::string to_iso8601_utc() const { - return now().to_iso8601_utc(); - } - - /// \brief Format current local time using the custom formatter grammar. - /// \param fmt Formatting pattern. - /// \return Formatted string. - std::string format(const std::string& fmt) const { - return now().format(fmt); - } - - private: - static std::string trim_ascii(const std::string& value) { - std::size_t begin = 0; - std::size_t end = value.size(); - while (begin < end && is_ascii_space(value[begin])) { - ++begin; - } - while (end > begin && is_ascii_space(value[end - 1])) { - --end; - } - return value.substr(begin, end - begin); - } - - static bool is_ascii_space(char ch) noexcept { - return ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r' || ch == '\f' || ch == '\v'; - } - - static std::string offset_string_for_offset(tz_t utc_offset) { - return time_zone_struct_to_string(to_time_zone_struct(utc_offset)); - } - - ts_ms_t current_utc_ms() const noexcept { - return static_cast(current_utc_us() / 1000); - } - - ts_us_t current_utc_us() const noexcept { -#if TIME_SHIELD_ENABLE_NTP_CLIENT - if (m_use_ntp) { - if (!NtpTimeService::instance().running()) { - (void)ntp::init(30000, true); - } - return static_cast(ntp::utc_time_us()); - } -#endif - return static_cast(now_realtime_us()); - } - - private: - TimeZone m_zone; - tz_t m_offset; - bool m_is_named_zone; - bool m_use_ntp; - }; - -} // namespace time_shield - -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_ZONEDCLOCK_HPP_INCLUDED +#endif // TIME_SHIELD_HEADER_ZONEDCLOCK_HPP_INCLUDED diff --git a/include/time_shield/astronomy.hpp b/include/time_shield/astronomy.hpp new file mode 100644 index 00000000..6a108e94 --- /dev/null +++ b/include/time_shield/astronomy.hpp @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT +#pragma once +#ifndef TIME_SHIELD_HEADER_ASTRONOMY_HPP_INCLUDED +#define TIME_SHIELD_HEADER_ASTRONOMY_HPP_INCLUDED + +#include +#include +#include +#include +#include + +#if defined(TIME_SHIELD_ENABLE_LEGACY_ALIASES) +# include +#endif + +#endif // TIME_SHIELD_HEADER_ASTRONOMY_HPP_INCLUDED diff --git a/include/time_shield/astronomy/MoonPhase.hpp b/include/time_shield/astronomy/MoonPhase.hpp new file mode 100644 index 00000000..ba6f9bec --- /dev/null +++ b/include/time_shield/astronomy/MoonPhase.hpp @@ -0,0 +1,403 @@ +// SPDX-License-Identifier: MIT +#pragma once +#ifndef TIME_SHIELD_HEADER_ASTRONOMY_MOONPHASE_HPP_INCLUDED +#define TIME_SHIELD_HEADER_ASTRONOMY_MOONPHASE_HPP_INCLUDED + +/// \file MoonPhase.hpp +/// \brief Geocentric Moon phase calculator and result helpers. +/// \ingroup time_conversions + +#include +#include + +#include +#include +#include + +namespace time_shield { + +namespace astronomy { + + /// \brief Result of Moon phase computation (geocentric approximation). + struct MoonPhaseResult { + double phase = 0.0; ///< Phase fraction in [0..1). 0=new moon, 0.5=full moon. + double illumination = 0.0; ///< Illuminated fraction in [0..1]. + double age_days = 0.0; ///< Age of the Moon in days since new moon (approx). + double distance_km = 0.0; ///< Distance to Moon in km (approx). + double diameter_deg = 0.0; ///< Angular diameter of Moon in degrees (approx). + double age_deg = 0.0; ///< Phase angle in degrees (0..360). + double phase_angle_rad = 0.0; ///< Phase angle in radians (0..2*pi). + double phase_sin = 0.0; ///< sin(phase_angle_rad) helper for continuous signal. + double phase_cos = 0.0; ///< cos(phase_angle_rad) helper for continuous signal. + double sun_distance_km = 0.0; + double sun_diameter_deg = 0.0; + }; + + /// \brief Lunar quarter instants (Unix UTC seconds, floating). + struct MoonQuarterInstants { + double previous_new_unix_s = 0.0; ///< Previous new moon instant (Unix UTC seconds, double). + double previous_first_quarter_unix_s = 0.0; ///< Previous first quarter instant (Unix UTC seconds, double). + double previous_full_unix_s = 0.0; ///< Previous full moon instant (Unix UTC seconds, double). + double previous_last_quarter_unix_s = 0.0; ///< Previous last quarter instant (Unix UTC seconds, double). + double next_new_unix_s = 0.0; ///< Next new moon instant (Unix UTC seconds, double). + double next_first_quarter_unix_s = 0.0; ///< Next first quarter instant (Unix UTC seconds, double). + double next_full_unix_s = 0.0; ///< Next full moon instant (Unix UTC seconds, double). + double next_last_quarter_unix_s = 0.0; ///< Next last quarter instant (Unix UTC seconds, double). + }; + + /// \brief Moon phase calculator (geocentric approximation). + /// + /// References: + /// - John Walker, "moontool" (Fourmilab). See: https://www.fourmilab.ch/moontoolw/ + /// - solarissmoke/php-moon-phase (port). See: https://github.com/solarissmoke/php-moon-phase/blob/master/Solaris/MoonPhase.php + /// + /// Notes: + /// - Input timestamps are assumed to be UTC Unix seconds (can be floating). + /// - Computation is geocentric (no observer latitude/longitude corrections). + /// + /// Example usage: + /// \code{.cpp} + /// using namespace time_shield; + /// MoonPhase calculator{}; + /// double ts = 1704067200.0; // 2024-01-01T00:00:00Z + /// MoonPhaseResult res = calculator.compute(ts); // illumination, angles, sin/cos + /// MoonPhase::quarters_unix_s_t quarters = calculator.quarter_times_unix(ts); // Unix seconds as double + /// MoonQuarterInstants mapped = calculator.quarter_instants_unix(ts); // structured view + /// bool near_full = calculator.is_full_moon_window(ts, 3600.0); // +/-1h window check + /// \endcode + class MoonPhase { + public: + using quarters_unix_s_t = std::array; ///< Quarter instants as Unix UTC seconds ({new, firstQ, full, lastQ} for previous and next cycles). + static constexpr double kDefaultQuarterWindow_s = 43200.0; ///< Default window around phase events (12h). + + /// \brief Compute full set of Moon phase parameters for given UTC timestamp. + /// \param unix_utc_s Timestamp in Unix UTC seconds (can be fractional). + /// \return Geocentric Moon phase parameters for the given instant. + MoonPhaseResult compute(double unix_utc_s) const noexcept { + MoonPhaseResult out{}; + const double jd = julian_day_from_unix_seconds(unix_utc_s); + + // --- Sun position --- + const double day = jd - kEpochJd; + const double N = fix_angle((360.0 / 365.2422) * day); + const double M = fix_angle(N + kElonge - kElongp); + + double Ec = kepler(M, kEccent); + Ec = std::sqrt((1.0 + kEccent) / (1.0 - kEccent)) * std::tan(Ec / 2.0); + Ec = 2.0 * rad2deg(std::atan(Ec)); + const double lambda_sun = fix_angle(Ec + kElongp); + + const double F = ((1.0 + kEccent * std::cos(deg2rad(Ec))) / (1.0 - kEccent * kEccent)); + const double sun_dist = kSunSmax / F; + const double sun_ang = F * kSunAngSiz; + + // --- Moon position --- + const double ml = fix_angle(13.1763966 * day + kMmLong); + const double MM = fix_angle(ml - 0.1114041 * day - kMmLongp); + + const double Ev = 1.2739 * std::sin(deg2rad(2.0 * (ml - lambda_sun) - MM)); + const double Ae = 0.1858 * std::sin(deg2rad(M)); + const double A3 = 0.37 * std::sin(deg2rad(M)); + const double MmP = MM + Ev - Ae - A3; + + const double mEc = 6.2886 * std::sin(deg2rad(MmP)); + const double A4 = 0.214 * std::sin(deg2rad(2.0 * MmP)); + const double lP = ml + Ev + mEc - Ae + A4; + + const double V = 0.6583 * std::sin(deg2rad(2.0 * (lP - lambda_sun))); + const double lPP = lP + V; + + // --- Phase --- + const double moon_age_deg_wrapped = fix_angle(lPP - lambda_sun); + const double moon_age_rad = deg2rad(moon_age_deg_wrapped); + const double illum = (1.0 - std::cos(moon_age_rad)) / 2.0; + + const double moon_dist = (kMsMax * (1.0 - kMecc * kMecc)) + / (1.0 + kMecc * std::cos(deg2rad(MmP + mEc))); + + const double moon_dfrac = moon_dist / kMsMax; + const double moon_ang = kMAngSiz / moon_dfrac; + + out.phase = moon_age_deg_wrapped / 360.0; + out.illumination = illum; + out.age_days = kSynMonth * out.phase; + out.distance_km = moon_dist; + out.diameter_deg = moon_ang; + out.age_deg = moon_age_deg_wrapped; + out.phase_angle_rad = moon_age_rad; + out.phase_sin = std::sin(moon_age_rad); + out.phase_cos = std::cos(moon_age_rad); + out.sun_distance_km = sun_dist; + out.sun_diameter_deg = sun_ang; + return out; + } + + /// \brief Compute only phase fraction in [0..1) for given UTC timestamp. + /// \param unix_utc_s Timestamp in Unix UTC seconds (can be fractional). + /// \return Phase fraction in the \f$[0, 1)\f$ interval where 0=new moon, 0.5=full moon. + double compute_phase(double unix_utc_s) const noexcept { + return compute(unix_utc_s).phase; + } + + /// \brief Compute quarter/new/full instants around given timestamp. + /// \param unix_utc_s Timestamp in Unix UTC seconds (can be fractional). + /// \return Array of 8 instants (Unix UTC seconds as double): {prev new, prev firstQ, prev full, prev lastQ, next new, next firstQ, next full, next lastQ}. + quarters_unix_s_t quarter_times_unix(double unix_utc_s) const noexcept { + const double sdate = julian_day_from_unix_seconds(unix_utc_s); + double adate = sdate - 45.0; + + const double ats = unix_utc_s - 86400.0 * 45.0; + const int yy = year_from_unix_seconds(ats); + const int mm = month_from_unix_seconds(ats); + + // IMPORTANT: use floating division + double k1 = std::floor((yy + ((mm - 1) * (1.0 / 12.0)) - 1900.0) * 12.3685); + double k2 = 0.0; + + double nt1 = mean_phase_jd(adate, k1); + adate = nt1; + + while (true) { + adate += kSynMonth; + k2 = k1 + 1.0; + + double nt2 = mean_phase_jd(adate, k2); + if (std::abs(nt2 - sdate) < 0.75) { + nt2 = true_phase_jd(k2, 0.0); // new moon correction + } + + if (nt1 <= sdate && nt2 > sdate) { + break; + } + + nt1 = nt2; + k1 = k2; + } + + const double dates_jd[8] = { + true_phase_jd(k1, 0.0), + true_phase_jd(k1, 0.25), + true_phase_jd(k1, 0.5), + true_phase_jd(k1, 0.75), + true_phase_jd(k2, 0.0), + true_phase_jd(k2, 0.25), + true_phase_jd(k2, 0.5), + true_phase_jd(k2, 0.75) + }; + + quarters_unix_s_t out{}; + for (std::size_t i = 0; i < 8; ++i) { + out[i] = jd_to_unix_seconds(dates_jd[i]); + } + return out; + } + + /// \brief Compatibility wrapper returning quarter instants as Unix UTC seconds. + /// \param unix_utc_s Timestamp in Unix UTC seconds (can be fractional). + /// \return Same payload as quarter_times_unix() for compatibility. + quarters_unix_s_t quarter_times(double unix_utc_s) const noexcept { + return quarter_times_unix(unix_utc_s); + } + + /// \brief Quarter instants around the provided timestamp as a structured result (Unix UTC seconds). + /// \param unix_utc_s Timestamp in Unix UTC seconds (can be fractional). + /// \return Previous/next quarter instants mapped into a structured result with Unix UTC seconds. + MoonQuarterInstants quarter_instants_unix(double unix_utc_s) const noexcept { + const auto quarters = quarter_times_unix(unix_utc_s); + MoonQuarterInstants out{}; + out.previous_new_unix_s = quarters[0]; + out.previous_first_quarter_unix_s = quarters[1]; + out.previous_full_unix_s = quarters[2]; + out.previous_last_quarter_unix_s = quarters[3]; + out.next_new_unix_s = quarters[4]; + out.next_first_quarter_unix_s = quarters[5]; + out.next_full_unix_s = quarters[6]; + out.next_last_quarter_unix_s = quarters[7]; + return out; + } + + /// \brief Check whether timestamp is inside a window around new moon. + /// \param unix_utc_s Timestamp in Unix UTC seconds (can be fractional). + /// \param window_seconds Symmetric window size in seconds around the event time. + /// \return true if the timestamp lies within the window of the previous or next new moon. + bool is_new_moon_window(double unix_utc_s, double window_seconds = kDefaultQuarterWindow_s) const noexcept { + const auto instants = quarter_instants_unix(unix_utc_s); + return is_within_window(unix_utc_s, instants.previous_new_unix_s, instants.next_new_unix_s, window_seconds); + } + + /// \brief Check whether timestamp is inside a window around full moon. + /// \param unix_utc_s Timestamp in Unix UTC seconds (can be fractional). + /// \param window_seconds Symmetric window size in seconds around the event time. + /// \return true if the timestamp lies within the window of the previous or next full moon. + bool is_full_moon_window(double unix_utc_s, double window_seconds = kDefaultQuarterWindow_s) const noexcept { + const auto instants = quarter_instants_unix(unix_utc_s); + return is_within_window(unix_utc_s, instants.previous_full_unix_s, instants.next_full_unix_s, window_seconds); + } + + /// \brief Check whether timestamp is inside a window around first quarter. + /// \param unix_utc_s Timestamp in Unix UTC seconds (can be fractional). + /// \param window_seconds Symmetric window size in seconds around the event time. + /// \return true if the timestamp lies within the window of the previous or next first quarter. + bool is_first_quarter_window(double unix_utc_s, double window_seconds = kDefaultQuarterWindow_s) const noexcept { + const auto instants = quarter_instants_unix(unix_utc_s); + return is_within_window(unix_utc_s, instants.previous_first_quarter_unix_s, instants.next_first_quarter_unix_s, window_seconds); + } + + /// \brief Check whether timestamp is inside a window around last quarter. + /// \param unix_utc_s Timestamp in Unix UTC seconds (can be fractional). + /// \param window_seconds Symmetric window size in seconds around the event time. + /// \return true if the timestamp lies within the window of the previous or next last quarter. + bool is_last_quarter_window(double unix_utc_s, double window_seconds = kDefaultQuarterWindow_s) const noexcept { + const auto instants = quarter_instants_unix(unix_utc_s); + return is_within_window(unix_utc_s, instants.previous_last_quarter_unix_s, instants.next_last_quarter_unix_s, window_seconds); + } + + private: + static double julian_day_from_unix_seconds(double unix_utc_s) noexcept { + return 2440587.5 + unix_utc_s / 86400.0; + } + + static int year_from_unix_seconds(double unix_utc_s) noexcept { + return static_cast(year_of(static_cast(unix_utc_s))); + } + + static int month_from_unix_seconds(double unix_utc_s) noexcept { + return static_cast(month_of_year(static_cast(unix_utc_s))); + } + + static double jd_to_unix_seconds(double julian_day) noexcept { + return (julian_day - 2440587.5) * 86400.0; + } + + // --- Constants (1980 January 0.0) --- + static constexpr double kEpochJd = 2444238.5; + + static constexpr double kElonge = 278.833540; + static constexpr double kElongp = 282.596403; + static constexpr double kEccent = 0.016718; + static constexpr double kSunSmax = 1.495985e8; // km + static constexpr double kSunAngSiz= 0.533128; // deg + + static constexpr double kMmLong = 64.975464; + static constexpr double kMmLongp = 349.383063; + TIME_SHIELD_MAYBE_UNUSED static constexpr double kMNode = 151.950429; + TIME_SHIELD_MAYBE_UNUSED static constexpr double kMInc = 5.145396; + static constexpr double kMecc = 0.054900; + static constexpr double kMAngSiz = 0.5181; // deg + static constexpr double kMsMax = 384401.0; // km + TIME_SHIELD_MAYBE_UNUSED static constexpr double kMParallax= 0.9507; // deg + static constexpr double kSynMonth = 29.53058868; + + static constexpr double kPi = 3.14159265358979323846; + + static double deg2rad(double deg) noexcept { return deg * (kPi / 180.0); } + static double rad2deg(double rad) noexcept { return rad * (180.0 / kPi); } + + static double fix_angle(double a) noexcept { + a = std::fmod(a, 360.0); + if (a < 0.0) a += 360.0; + return a; + } + + static double kepler(double m_deg, double ecc) noexcept { + constexpr double eps = 1e-6; + const double m = deg2rad(m_deg); + double e = m; + for (int i = 0; i < 50; ++i) { + const double delta = e - ecc * std::sin(e) - m; + e -= delta / (1.0 - ecc * std::cos(e)); + if (std::abs(delta) <= eps) break; + } + return e; + } + + double mean_phase_jd(double julian_day, double lunation_index) const noexcept { + const double jt = (julian_day - 2415020.0) / 36525.0; + const double t2 = jt * jt; + const double t3 = t2 * jt; + + return 2415020.75933 + kSynMonth * lunation_index + + 0.0001178 * t2 + - 0.000000155 * t3 + + 0.00033 * std::sin(deg2rad(166.56 + 132.87 * jt - 0.009173 * t2)); + } + + double true_phase_jd(double lunation_index, double phase_fraction) const noexcept { + // This algorithm is designed for phase_fraction in {0, 0.25, 0.5, 0.75}. + const double kx = lunation_index + phase_fraction; + const double t = kx / 1236.85; + const double t2 = t * t; + const double t3 = t2 * t; + + double pt = 2415020.75933 + + kSynMonth * kx + + 0.0001178 * t2 + - 0.000000155 * t3 + + 0.00033 * std::sin(deg2rad(166.56 + 132.87 * t - 0.009173 * t2)); + + const double m = 359.2242 + 29.10535608 * kx - 0.0000333 * t2 - 0.00000347 * t3; + const double mprime = 306.0253 + 385.81691806 * kx + 0.0107306 * t2 + 0.00001236 * t3; + const double f = 21.2964 + 390.67050646 * kx - 0.0016528 * t2 - 0.00000239 * t3; + + // Corrections (same structure as common ports of moontool) + if (phase_fraction < 0.01 || std::abs(phase_fraction - 0.5) < 0.01) { + pt += (0.1734 - 0.000393 * t) * std::sin(deg2rad(m)) + + 0.0021 * std::sin(deg2rad(2 * m)) + - 0.4068 * std::sin(deg2rad(mprime)) + + 0.0161 * std::sin(deg2rad(2 * mprime)) + - 0.0004 * std::sin(deg2rad(3 * mprime)) + + 0.0104 * std::sin(deg2rad(2 * f)) + - 0.0051 * std::sin(deg2rad(m + mprime)) + - 0.0074 * std::sin(deg2rad(m - mprime)) + + 0.0004 * std::sin(deg2rad(2 * f + m)) + - 0.0004 * std::sin(deg2rad(2 * f - m)) + - 0.0006 * std::sin(deg2rad(2 * f + mprime)) + + 0.0010 * std::sin(deg2rad(2 * f - mprime)) + + 0.0005 * std::sin(deg2rad(m + 2 * mprime)); + return pt; + } + + if (std::abs(phase_fraction - 0.25) < 0.01 || std::abs(phase_fraction - 0.75) < 0.01) { + pt += (0.1721 - 0.0004 * t) * std::sin(deg2rad(m)) + + 0.0021 * std::sin(deg2rad(2 * m)) + - 0.6280 * std::sin(deg2rad(mprime)) + + 0.0089 * std::sin(deg2rad(2 * mprime)) + - 0.0004 * std::sin(deg2rad(3 * mprime)) + + 0.0079 * std::sin(deg2rad(2 * f)) + - 0.0119 * std::sin(deg2rad(m + mprime)) + - 0.0047 * std::sin(deg2rad(m - mprime)) + + 0.0003 * std::sin(deg2rad(2 * f + m)) + - 0.0004 * std::sin(deg2rad(2 * f - m)) + - 0.0006 * std::sin(deg2rad(2 * f + mprime)) + + 0.0021 * std::sin(deg2rad(2 * f - mprime)) + + 0.0003 * std::sin(deg2rad(m + 2 * mprime)) + + 0.0004 * std::sin(deg2rad(m - 2 * mprime)) + - 0.0003 * std::sin(deg2rad(2 * m + mprime)); + + if (phase_fraction < 0.5) { + pt += 0.0028 - 0.0004 * std::cos(deg2rad(m)) + 0.0003 * std::cos(deg2rad(mprime)); + } else { + pt += -0.0028 + 0.0004 * std::cos(deg2rad(m)) - 0.0003 * std::cos(deg2rad(mprime)); + } + return pt; + } + + // Fallback: return uncorrected estimate + return pt; + } + + static bool is_within_window(double unix_utc_s, double previous_instant, double next_instant, double window_seconds) noexcept { + const double prev_delta = std::abs(unix_utc_s - previous_instant); + const double next_delta = std::abs(unix_utc_s - next_instant); + return (prev_delta <= window_seconds) || (next_delta <= window_seconds); + } + }; + +} // namespace astronomy + + /// \brief Convenience alias for the geocentric Moon phase calculator. + using MoonPhaseCalculator = astronomy::MoonPhase; + +} // namespace time_shield + +#endif // TIME_SHIELD_HEADER_ASTRONOMY_MOONPHASE_HPP_INCLUDED diff --git a/include/time_shield/astronomy/astronomy_conversions.hpp b/include/time_shield/astronomy/astronomy_conversions.hpp new file mode 100644 index 00000000..4347586a --- /dev/null +++ b/include/time_shield/astronomy/astronomy_conversions.hpp @@ -0,0 +1,118 @@ +// SPDX-License-Identifier: MIT +#pragma once +#ifndef TIME_SHIELD_HEADER_ASTRONOMY_ASTRONOMY_CONVERSIONS_HPP_INCLUDED +#define TIME_SHIELD_HEADER_ASTRONOMY_ASTRONOMY_CONVERSIONS_HPP_INCLUDED + +/// \file astronomy_conversions.hpp +/// \brief Astronomy entry header with Julian conversions and lunar helpers. +/// \ingroup time_conversions + +#include +#include "julian_conversions.hpp" +#include "MoonPhase.hpp" + +#include + +namespace time_shield { + + /// \brief sin/cos helper for the Moon phase angle. + struct MoonPhaseSineCosine { + double phase_sin = 0.0; ///< sin(phase angle), continuous around 0/2pi. + double phase_cos = 0.0; ///< cos(phase angle), continuous around 0/2pi. + double phase_angle_rad = 0.0; ///< Phase angle in radians [0..2*pi). + constexpr MoonPhaseSineCosine() = default; + constexpr MoonPhaseSineCosine(double phase_sin_value, double phase_cos_value, double phase_angle_rad_value) noexcept + : phase_sin(phase_sin_value), + phase_cos(phase_cos_value), + phase_angle_rad(phase_angle_rad_value) {} + }; + + /// \brief Get lunar phase in range [0..1) using a simple Julian Day approximation. + /// \details This helper mirrors the legacy Julian Day based approximation and is less precise + /// than the geocentric MoonPhase calculator. + /// \param ts Unix timestamp in floating seconds since Unix epoch. + /// \return Approximate lunar phase fraction where 0 is new moon. + inline double moon_phase_jd_approx(fts_t ts) noexcept { + double temp = (static_cast(fts_to_jd(ts)) - 2451550.1) / 29.530588853; + temp = temp - std::floor(temp); + if (temp < 0.0) temp += 1.0; + return temp; + } + + /// \brief Get lunar phase in range [0..1) using the geocentric MoonPhase calculator. + /// \param ts Unix timestamp in floating seconds since Unix epoch. + /// \return Lunar phase fraction where 0 is new moon. + inline double moon_phase(fts_t ts) noexcept { + static const astronomy::MoonPhase calculator{}; + return calculator.compute(static_cast(ts)).phase; + } + + /// \brief Get sin/cos of the lunar phase angle (continuous signal without wrap-around). + /// \param ts Unix timestamp in floating seconds since Unix epoch. + /// \return Structure containing sin/cos and the angle in radians. + inline MoonPhaseSineCosine moon_phase_sincos(fts_t ts) noexcept { + static const astronomy::MoonPhase calculator{}; + const auto result = calculator.compute(static_cast(ts)); + return MoonPhaseSineCosine{result.phase_sin, result.phase_cos, result.phase_angle_rad}; + } + + /// \brief Get illuminated fraction in range [0..1] using the geocentric MoonPhase calculator. + /// \param ts Unix timestamp in floating seconds since Unix epoch. + /// \return Illuminated fraction of the Moon. + inline double moon_illumination(fts_t ts) noexcept { + static const astronomy::MoonPhase calculator{}; + return calculator.compute(static_cast(ts)).illumination; + } + + /// \brief Get lunar age in days (~0..29.53) using a simple Julian Day approximation. + /// \details This helper mirrors the legacy Julian Day based approximation and is less precise + /// than the geocentric MoonPhase calculator. + /// \param ts Unix timestamp in floating seconds since Unix epoch. + /// \return Approximate lunar age in days. + inline double moon_age_days_jd_approx(fts_t ts) noexcept { + return moon_phase_jd_approx(ts) * 29.530588853; + } + + /// \brief Get lunar age in days (~0..29.53). + /// \param ts Unix timestamp in floating seconds since Unix epoch. + /// \return Approximate lunar age in days. + inline double moon_age_days(fts_t ts) noexcept { + static const astronomy::MoonPhase calculator{}; + return calculator.compute(static_cast(ts)).age_days; + } + + /// \brief Quarter instants around the provided timestamp. + /// \param ts Unix timestamp in floating seconds since Unix epoch. + /// \return Quarter windows around the timestamp (Unix seconds as double). + inline astronomy::MoonQuarterInstants moon_quarters(fts_t ts) noexcept { + static const astronomy::MoonPhase calculator{}; + return calculator.quarter_instants_unix(static_cast(ts)); + } + + /// \brief Check if timestamp falls into the new moon window (default \pm12h). + inline bool is_new_moon_window(fts_t ts, double window_seconds = astronomy::MoonPhase::kDefaultQuarterWindow_s) noexcept { + static const astronomy::MoonPhase calculator{}; + return calculator.is_new_moon_window(static_cast(ts), window_seconds); + } + + /// \brief Check if timestamp falls into the full moon window (default \pm12h). + inline bool is_full_moon_window(fts_t ts, double window_seconds = astronomy::MoonPhase::kDefaultQuarterWindow_s) noexcept { + static const astronomy::MoonPhase calculator{}; + return calculator.is_full_moon_window(static_cast(ts), window_seconds); + } + + /// \brief Check if timestamp falls into the first quarter window (default \pm12h). + inline bool is_first_quarter_window(fts_t ts, double window_seconds = astronomy::MoonPhase::kDefaultQuarterWindow_s) noexcept { + static const astronomy::MoonPhase calculator{}; + return calculator.is_first_quarter_window(static_cast(ts), window_seconds); + } + + /// \brief Check if timestamp falls into the last quarter window (default \pm12h). + inline bool is_last_quarter_window(fts_t ts, double window_seconds = astronomy::MoonPhase::kDefaultQuarterWindow_s) noexcept { + static const astronomy::MoonPhase calculator{}; + return calculator.is_last_quarter_window(static_cast(ts), window_seconds); + } + +} // namespace time_shield + +#endif // TIME_SHIELD_HEADER_ASTRONOMY_ASTRONOMY_CONVERSIONS_HPP_INCLUDED diff --git a/include/time_shield/astronomy/julian_conversions.hpp b/include/time_shield/astronomy/julian_conversions.hpp new file mode 100644 index 00000000..65556b78 --- /dev/null +++ b/include/time_shield/astronomy/julian_conversions.hpp @@ -0,0 +1,190 @@ +// SPDX-License-Identifier: MIT +#pragma once +#ifndef TIME_SHIELD_HEADER_ASTRONOMY_JULIAN_CONVERSIONS_HPP_INCLUDED +#define TIME_SHIELD_HEADER_ASTRONOMY_JULIAN_CONVERSIONS_HPP_INCLUDED + +/// \file julian_conversions.hpp +/// \brief Julian Date / MJD / JDN helpers using the proleptic Gregorian calendar. +/// \ingroup time_conversions +/// +/// JD epoch used here: +/// - Unix epoch (1970-01-01 00:00:00 UTC) is JD 2440587.5 +/// +/// Notes: +/// - JD and MJD are returned as double (jd_t/mjd_t). +/// - These functions are intended for utility/analytics, not for high-precision astronomy. + +#include + +#include +#include + +namespace time_shield { + + namespace detail { + + inline jd_t gregorian_dmy_to_jd_unchecked(double day, int64_t month, int64_t year) noexcept { + if (month == 1 || month == 2) { + year -= 1; + month += 12; + } + const double a = std::floor(static_cast(year) / 100.0); + const double b = 2.0 - a + std::floor(a / 4.0); + const double jd = std::floor(365.25 * (static_cast(year) + 4716.0)) + + std::floor(30.6000001 * (static_cast(month) + 1.0)) + + day + b - 1524.5; + return static_cast(jd); + } + + inline jdn_t gregorian_dmy_to_jdn_unchecked(int64_t day, int64_t month, int64_t year) noexcept { + const int64_t a = (14LL - month) / 12LL; + const int64_t y = year + 4800LL - a; + const int64_t m = month + 12LL * a - 3LL; + const int64_t jdn = day + + (153LL * m + 2LL) / 5LL + + 365LL * y + + y / 4LL + - y / 100LL + + y / 400LL + - 32045LL; + return static_cast(jdn); + } + + inline double day_fraction_from_hms( + int hour, + int minute, + int second, + int millisecond) noexcept { + return (static_cast(hour) / 24.0) + + (static_cast(minute) / (24.0 * 60.0)) + + ((static_cast(second) + static_cast(millisecond) / 1000.0) + / static_cast(SEC_PER_DAY)); + } + + } // namespace detail + + /// \brief Convert Unix timestamp (floating seconds) to Julian Date (JD). + /// \param ts Unix timestamp in floating seconds since Unix epoch. + /// \return Julian Date value. + inline jd_t fts_to_jd(fts_t ts) noexcept { + return static_cast(2440587.5) + + static_cast(ts) / static_cast(SEC_PER_DAY); + } + + /// \brief Convert Unix timestamp (seconds) to Julian Date (JD). + /// \param ts Unix timestamp in seconds since Unix epoch. + /// \return Julian Date value. + inline jd_t ts_to_jd(ts_t ts) noexcept { + return fts_to_jd(static_cast(ts)); + } + + /// \brief Convert Gregorian date/time components to Julian Date (JD) using year-first order. + /// \param year Full year in the proleptic Gregorian calendar. + /// \param month Month [1..12]. + /// \param day Day of month [1..31]. + /// \param hour Hour of day [0..23]. + /// \param minute Minute of hour [0..59]. + /// \param second Second of minute [0..59]. + /// \param millisecond Millisecond of second [0..999]. + /// \return Julian Date value. + inline jd_t gregorian_ymd_to_jd( + year_t year, + int month, + int day, + int hour = 0, + int minute = 0, + int second = 0, + int millisecond = 0) noexcept { + return detail::gregorian_dmy_to_jd_unchecked( + static_cast(day) + detail::day_fraction_from_hms(hour, minute, second, millisecond), + static_cast(month), + static_cast(year)); + } + + /// \brief Convert Unix timestamp (floating seconds) to Modified Julian Date (MJD). + /// \param ts Unix timestamp in floating seconds since Unix epoch. + /// \return Modified Julian Date value. + inline mjd_t fts_to_mjd(fts_t ts) noexcept { + return static_cast(fts_to_jd(ts) - 2400000.5); + } + + /// \brief Convert Unix timestamp (seconds) to Modified Julian Date (MJD). + /// \param ts Unix timestamp in seconds since Unix epoch. + /// \return Modified Julian Date value. + inline mjd_t ts_to_mjd(ts_t ts) noexcept { + return static_cast(fts_to_mjd(static_cast(ts))); + } + + /// \brief Convert Gregorian date to Julian Day Number (JDN) using year-first order. + /// \details JDN is an integer day count with no fractional part. + /// \param year Full year in the proleptic Gregorian calendar. + /// \param month Month [1..12]. + /// \param day Day of month [1..31]. + /// \return Julian Day Number value. + inline jdn_t gregorian_ymd_to_jdn(year_t year, int month, int day) noexcept { + return detail::gregorian_dmy_to_jdn_unchecked( + static_cast(day), + static_cast(month), + static_cast(year)); + } + + /// \brief Try converting Gregorian date/time components to Julian Date (JD) using year-first order. + /// \param year Full year in the proleptic Gregorian calendar. + /// \param month Month [1..12]. + /// \param day Day of month [1..31]. + /// \param hour Hour of day [0..23]. + /// \param minute Minute of hour [0..59]. + /// \param second Second of minute [0..59]. + /// \param millisecond Millisecond of second [0..999]. + /// \param out Receives the Julian Date value on success. + /// \return True on success, false when date/time components are invalid. + inline bool try_gregorian_ymd_to_jd( + year_t year, + int month, + int day, + int hour, + int minute, + int second, + int millisecond, + jd_t& out) noexcept { + if (!is_valid_date(year, month, day) || !is_valid_time(hour, minute, second, millisecond)) { + return false; + } + out = gregorian_ymd_to_jd(year, month, day, hour, minute, second, millisecond); + return true; + } + + /// \brief Try converting Gregorian date to Julian Day Number (JDN) using year-first order. + /// \param year Full year in the proleptic Gregorian calendar. + /// \param month Month [1..12]. + /// \param day Day of month [1..31]. + /// \param out Receives the Julian Day Number value on success. + /// \return True on success, false when the date is invalid or produces a negative JDN. + inline bool try_gregorian_ymd_to_jdn( + year_t year, + int month, + int day, + jdn_t& out) noexcept { + if (!is_valid_date(year, month, day)) { + return false; + } + const int64_t a = (14LL - static_cast(month)) / 12LL; + const int64_t y = static_cast(year) + 4800LL - a; + const int64_t m = static_cast(month) + 12LL * a - 3LL; + const int64_t jdn = static_cast(day) + + (153LL * m + 2LL) / 5LL + + 365LL * y + + y / 4LL + - y / 100LL + + y / 400LL + - 32045LL; + if (jdn < 0) { + return false; + } + out = static_cast(jdn); + return true; + } + +} // namespace time_shield + +#endif // TIME_SHIELD_HEADER_ASTRONOMY_JULIAN_CONVERSIONS_HPP_INCLUDED diff --git a/include/time_shield/astronomy/legacy_aliases.hpp b/include/time_shield/astronomy/legacy_aliases.hpp new file mode 100644 index 00000000..5c344f63 --- /dev/null +++ b/include/time_shield/astronomy/legacy_aliases.hpp @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: MIT +#pragma once +#ifndef TIME_SHIELD_HEADER_ASTRONOMY_LEGACY_ALIASES_HPP_INCLUDED +#define TIME_SHIELD_HEADER_ASTRONOMY_LEGACY_ALIASES_HPP_INCLUDED + +/// \file legacy_aliases.hpp +/// \brief Opt-in compatibility aliases for renamed astronomy helpers. + +#include + +#if defined(TIME_SHIELD_ENABLE_LEGACY_ALIASES) + +namespace time_shield { + + /// \brief Legacy day-first Gregorian Julian Date conversion. + /// \details Use gregorian_ymd_to_jd for the preferred year-first API. + inline jd_t gregorian_to_jd(double day, int64_t month, int64_t year) noexcept { + return detail::gregorian_dmy_to_jd_unchecked(day, month, year); + } + + /// \brief Legacy day-first Gregorian Julian Date conversion. + /// \details Use gregorian_ymd_to_jd for the preferred year-first API. + inline jd_t gregorian_to_jd( + uint32_t day, + uint32_t month, + uint32_t year, + uint32_t hour, + uint32_t minute, + uint32_t second = 0, + uint32_t millisecond = 0) noexcept { + return detail::gregorian_dmy_to_jd_unchecked( + static_cast(day) + detail::day_fraction_from_hms( + static_cast(hour), + static_cast(minute), + static_cast(second), + static_cast(millisecond)), + static_cast(month), + static_cast(year)); + } + + /// \brief Legacy day-first Gregorian Julian Day Number conversion. + /// \details Use gregorian_ymd_to_jdn for the preferred year-first API. + inline jdn_t gregorian_to_jdn(uint32_t day, uint32_t month, uint32_t year) noexcept { + return detail::gregorian_dmy_to_jdn_unchecked( + static_cast(day), + static_cast(month), + static_cast(year)); + } + +} // namespace time_shield + +#endif // defined(TIME_SHIELD_ENABLE_LEGACY_ALIASES) + +#endif // TIME_SHIELD_HEADER_ASTRONOMY_LEGACY_ALIASES_HPP_INCLUDED diff --git a/include/time_shield/astronomy_conversions.hpp b/include/time_shield/astronomy_conversions.hpp index bdef4367..f6c52fd7 100644 --- a/include/time_shield/astronomy_conversions.hpp +++ b/include/time_shield/astronomy_conversions.hpp @@ -1,118 +1,8 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_ASTRONOMY_CONVERSIONS_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_ASTRONOMY_CONVERSIONS_HPP_INCLUDED +#ifndef TIME_SHIELD_HEADER_ASTRONOMY_CONVERSIONS_HPP_INCLUDED +#define TIME_SHIELD_HEADER_ASTRONOMY_CONVERSIONS_HPP_INCLUDED -/// \file astronomy_conversions.hpp -/// \brief Astronomy entry header with Julian conversions and lunar helpers. -/// \ingroup time_conversions +#include -#include "config.hpp" -#include "julian_conversions.hpp" -#include "MoonPhase.hpp" - -#include - -namespace time_shield { - - /// \brief sin/cos helper for the Moon phase angle. - struct MoonPhaseSineCosine { - double phase_sin = 0.0; ///< sin(phase angle), continuous around 0/2pi. - double phase_cos = 0.0; ///< cos(phase angle), continuous around 0/2pi. - double phase_angle_rad = 0.0; ///< Phase angle in radians [0..2*pi). - constexpr MoonPhaseSineCosine() = default; - constexpr MoonPhaseSineCosine(double phase_sin_value, double phase_cos_value, double phase_angle_rad_value) noexcept - : phase_sin(phase_sin_value), - phase_cos(phase_cos_value), - phase_angle_rad(phase_angle_rad_value) {} - }; - - /// \brief Get lunar phase in range [0..1) using a simple Julian Day approximation. - /// \details This helper mirrors the legacy Julian Day based approximation and is less precise - /// than the geocentric MoonPhase calculator. - /// \param ts Unix timestamp in floating seconds since Unix epoch. - /// \return Approximate lunar phase fraction where 0 is new moon. - inline double moon_phase_jd_approx(fts_t ts) noexcept { - double temp = (static_cast(fts_to_jd(ts)) - 2451550.1) / 29.530588853; - temp = temp - std::floor(temp); - if (temp < 0.0) temp += 1.0; - return temp; - } - - /// \brief Get lunar phase in range [0..1) using the geocentric MoonPhase calculator. - /// \param ts Unix timestamp in floating seconds since Unix epoch. - /// \return Lunar phase fraction where 0 is new moon. - inline double moon_phase(fts_t ts) noexcept { - static const astronomy::MoonPhase calculator{}; - return calculator.compute(static_cast(ts)).phase; - } - - /// \brief Get sin/cos of the lunar phase angle (continuous signal without wrap-around). - /// \param ts Unix timestamp in floating seconds since Unix epoch. - /// \return Structure containing sin/cos and the angle in radians. - inline MoonPhaseSineCosine moon_phase_sincos(fts_t ts) noexcept { - static const astronomy::MoonPhase calculator{}; - const auto result = calculator.compute(static_cast(ts)); - return MoonPhaseSineCosine{result.phase_sin, result.phase_cos, result.phase_angle_rad}; - } - - /// \brief Get illuminated fraction in range [0..1] using the geocentric MoonPhase calculator. - /// \param ts Unix timestamp in floating seconds since Unix epoch. - /// \return Illuminated fraction of the Moon. - inline double moon_illumination(fts_t ts) noexcept { - static const astronomy::MoonPhase calculator{}; - return calculator.compute(static_cast(ts)).illumination; - } - - /// \brief Get lunar age in days (~0..29.53) using a simple Julian Day approximation. - /// \details This helper mirrors the legacy Julian Day based approximation and is less precise - /// than the geocentric MoonPhase calculator. - /// \param ts Unix timestamp in floating seconds since Unix epoch. - /// \return Approximate lunar age in days. - inline double moon_age_days_jd_approx(fts_t ts) noexcept { - return moon_phase_jd_approx(ts) * 29.530588853; - } - - /// \brief Get lunar age in days (~0..29.53). - /// \param ts Unix timestamp in floating seconds since Unix epoch. - /// \return Approximate lunar age in days. - inline double moon_age_days(fts_t ts) noexcept { - static const astronomy::MoonPhase calculator{}; - return calculator.compute(static_cast(ts)).age_days; - } - - /// \brief Quarter instants around the provided timestamp. - /// \param ts Unix timestamp in floating seconds since Unix epoch. - /// \return Quarter windows around the timestamp (Unix seconds as double). - inline astronomy::MoonQuarterInstants moon_quarters(fts_t ts) noexcept { - static const astronomy::MoonPhase calculator{}; - return calculator.quarter_instants_unix(static_cast(ts)); - } - - /// \brief Check if timestamp falls into the new moon window (default \pm12h). - inline bool is_new_moon_window(fts_t ts, double window_seconds = astronomy::MoonPhase::kDefaultQuarterWindow_s) noexcept { - static const astronomy::MoonPhase calculator{}; - return calculator.is_new_moon_window(static_cast(ts), window_seconds); - } - - /// \brief Check if timestamp falls into the full moon window (default \pm12h). - inline bool is_full_moon_window(fts_t ts, double window_seconds = astronomy::MoonPhase::kDefaultQuarterWindow_s) noexcept { - static const astronomy::MoonPhase calculator{}; - return calculator.is_full_moon_window(static_cast(ts), window_seconds); - } - - /// \brief Check if timestamp falls into the first quarter window (default \pm12h). - inline bool is_first_quarter_window(fts_t ts, double window_seconds = astronomy::MoonPhase::kDefaultQuarterWindow_s) noexcept { - static const astronomy::MoonPhase calculator{}; - return calculator.is_first_quarter_window(static_cast(ts), window_seconds); - } - - /// \brief Check if timestamp falls into the last quarter window (default \pm12h). - inline bool is_last_quarter_window(fts_t ts, double window_seconds = astronomy::MoonPhase::kDefaultQuarterWindow_s) noexcept { - static const astronomy::MoonPhase calculator{}; - return calculator.is_last_quarter_window(static_cast(ts), window_seconds); - } - -} // namespace time_shield - -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_ASTRONOMY_CONVERSIONS_HPP_INCLUDED +#endif // TIME_SHIELD_HEADER_ASTRONOMY_CONVERSIONS_HPP_INCLUDED diff --git a/include/time_shield/config.hpp b/include/time_shield/config.hpp index 8c10b59b..d5305bab 100644 --- a/include/time_shield/config.hpp +++ b/include/time_shield/config.hpp @@ -1,119 +1,8 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_CONFIG_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_CONFIG_HPP_INCLUDED +#ifndef TIME_SHIELD_HEADER_CONFIG_HPP_INCLUDED +#define TIME_SHIELD_HEADER_CONFIG_HPP_INCLUDED -/// \file config.hpp -/// \brief Configuration macros for the library. -/// -/// This header provides compile-time options for C++ standard detection, -/// platform capabilities and optional features. The macros can be used to -/// enable or disable parts of the library depending on the target platform or -/// user preferences. +#include -#include - -#if defined(_MSVC_LANG) -# define TIME_SHIELD_CXX_VERSION _MSVC_LANG -#else -# define TIME_SHIELD_CXX_VERSION __cplusplus -#endif - -// Check and define macros based on the C++ standard version -#if TIME_SHIELD_CXX_VERSION >= 201703L -# define TIME_SHIELD_CPP17 -#elif TIME_SHIELD_CXX_VERSION >= 201402L -# define TIME_SHIELD_CPP14 -#elif TIME_SHIELD_CXX_VERSION >= 201103L -# define TIME_SHIELD_CPP11 -#else -# error "C++11 or newer is required to compile this library." -#endif - -// Configure support for `constexpr` and `if constexpr` based on the C++ standard -#ifdef TIME_SHIELD_CPP11 -# define TIME_SHIELD_IF_CONSTEXPR -# define TIME_SHIELD_CONSTEXPR -#else -#ifdef TIME_SHIELD_CPP14 -# define TIME_SHIELD_IF_CONSTEXPR -# define TIME_SHIELD_CONSTEXPR constexpr -#else -#ifdef TIME_SHIELD_CPP17 -# define TIME_SHIELD_IF_CONSTEXPR constexpr -# define TIME_SHIELD_CONSTEXPR constexpr -#endif -#endif -#endif - -// Configure nodiscard attribute support while keeping compatibility with C++11 compilers -#if defined(__has_cpp_attribute) -# if __has_cpp_attribute(nodiscard) && defined(TIME_SHIELD_CPP17) -# define TIME_SHIELD_NODISCARD [[nodiscard]] -# else -# define TIME_SHIELD_NODISCARD -# endif -#else -# if defined(TIME_SHIELD_CPP17) -# define TIME_SHIELD_NODISCARD [[nodiscard]] -# else -# define TIME_SHIELD_NODISCARD -# endif -#endif - -// Attribute helpers -#if defined(TIME_SHIELD_CPP17) -# define TIME_SHIELD_MAYBE_UNUSED [[maybe_unused]] -#else -# define TIME_SHIELD_MAYBE_UNUSED -#endif - -// Configure thread-local storage handling for compilers with partial support -#if defined(__cpp_thread_local) -# define TIME_SHIELD_THREAD_LOCAL thread_local -#elif defined(_MSC_VER) -# define TIME_SHIELD_THREAD_LOCAL __declspec(thread) -#elif defined(__GNUC__) -# define TIME_SHIELD_THREAD_LOCAL __thread -#else -# define TIME_SHIELD_THREAD_LOCAL -#endif - - -/// \name Platform detection -///@{ -#if defined(_WIN32) -# define TIME_SHIELD_PLATFORM_WINDOWS 1 -#else -# define TIME_SHIELD_PLATFORM_WINDOWS 0 -#endif - -#if defined(__unix__) || defined(__unix) || defined(unix) || \ - (defined(__APPLE__) && defined(__MACH__)) -# define TIME_SHIELD_PLATFORM_UNIX 1 -#else -# define TIME_SHIELD_PLATFORM_UNIX 0 -#endif -///@} - -/// \name Platform capabilities -///@{ -#if TIME_SHIELD_PLATFORM_WINDOWS -# define TIME_SHIELD_HAS_WINSOCK 1 -#else -# define TIME_SHIELD_HAS_WINSOCK 0 -#endif -///@} - -/// \name Optional features -///@{ -#ifndef TIME_SHIELD_ENABLE_NTP_CLIENT -# if TIME_SHIELD_HAS_WINSOCK || TIME_SHIELD_PLATFORM_UNIX -# define TIME_SHIELD_ENABLE_NTP_CLIENT 1 -# else -# define TIME_SHIELD_ENABLE_NTP_CLIENT 0 -# endif -#endif -///@} - -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_CONFIG_HPP_INCLUDED +#endif // TIME_SHIELD_HEADER_CONFIG_HPP_INCLUDED diff --git a/include/time_shield/constants.hpp b/include/time_shield/constants.hpp index 7036075b..c181467e 100644 --- a/include/time_shield/constants.hpp +++ b/include/time_shield/constants.hpp @@ -1,169 +1,8 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_CONSTANTS_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_CONSTANTS_HPP_INCLUDED +#ifndef TIME_SHIELD_HEADER_CONSTANTS_HPP_INCLUDED +#define TIME_SHIELD_HEADER_CONSTANTS_HPP_INCLUDED -/// \file constants.hpp -/// \brief Header file with time-related constants. -/// -/// This file contains various constants used for time calculations and conversions. +#include -#include -#include - -namespace time_shield { - -/// \defgroup time_constants Time Constants -/// \brief A collection of constants for time calculations and conversions. -/// -/// This group includes constants for time units (nanoseconds, microseconds, milliseconds, seconds, minutes, hours, days), -/// and other values related to the representation of time, such as UNIX and OLE epochs. -/// -/// ### Key Features: -/// - Provides constants for common time conversions. -/// - Includes limits and special values like MAX_YEAR and ERROR_YEAR. -/// -/// ### Example Usage: -/// ```cpp -/// int64_t milliseconds_in_a_day = time_shield::MS_PER_DAY; -/// ``` -/// -/// \{ - - // Common millisecond durations - constexpr int64_t MS_1 = 1; ///< 1 millisecond - constexpr int64_t MS_5 = 5; ///< 5 milliseconds - constexpr int64_t MS_10 = 10; ///< 10 milliseconds - constexpr int64_t MS_50 = 50; ///< 50 milliseconds - constexpr int64_t MS_100 = 100; ///< 100 milliseconds - constexpr int64_t MS_250 = 250; ///< 250 milliseconds - constexpr int64_t MS_500 = 500; ///< 500 milliseconds - constexpr int64_t MS_750 = 750; ///< 750 milliseconds - - // Common second durations - constexpr int64_t SEC_1 = 1; ///< 1 second - constexpr int64_t SEC_2 = 2; ///< 2 seconds - constexpr int64_t SEC_3 = 3; ///< 3 seconds - constexpr int64_t SEC_5 = 5; ///< 5 seconds - constexpr int64_t SEC_10 = 10; ///< 10 seconds - constexpr int64_t SEC_15 = 15; ///< 15 seconds - constexpr int64_t SEC_30 = 30; ///< 30 seconds - - // Common minute durations - constexpr int64_t MIN_1 = 1; ///< 1 minute - constexpr int64_t MIN_2 = 2; ///< 2 minutes - constexpr int64_t MIN_5 = 5; ///< 5 minutes - constexpr int64_t MIN_10 = 10; ///< 10 minutes - constexpr int64_t MIN_15 = 15; ///< 15 minutes - constexpr int64_t MIN_30 = 30; ///< 30 minutes - - // Common hour durations - constexpr int64_t HOUR_1 = 1; ///< 1 hour - constexpr int64_t HOUR_2 = 2; ///< 2 hours - constexpr int64_t HOUR_3 = 3; ///< 3 hours - constexpr int64_t HOUR_4 = 4; ///< 4 hours - constexpr int64_t HOUR_5 = 5; ///< 5 hours - constexpr int64_t HOUR_8 = 8; ///< 8 hours - constexpr int64_t HOUR_12 = 12; ///< 12 hours - constexpr int64_t HOUR_24 = 24; ///< 24 hours - - // Nanoseconds and microseconds - constexpr int64_t NS_PER_US = 1000; ///< Nanoseconds per microsecond - constexpr int64_t NS_PER_MS = 1000000; ///< Nanoseconds per millisecond - constexpr int64_t NS_PER_SEC = 1000000000; ///< Nanoseconds per second - - // Microseconds and milliseconds - constexpr int64_t US_PER_SEC = 1000000; ///< Microseconds per second - constexpr int64_t MS_PER_SEC = 1000; ///< Milliseconds per second - constexpr int64_t MS_PER_1_SEC = 1000; ///< Milliseconds per 1 second - constexpr int64_t MS_PER_5_SEC = 5000; ///< Milliseconds per 5 second - constexpr int64_t MS_PER_10_SEC = 10000; ///< Milliseconds per 10 seconds - constexpr int64_t MS_PER_15_SEC = 15000; ///< Milliseconds per 15 second - constexpr int64_t MS_PER_30_SEC = 30000; ///< Milliseconds per 30 second - constexpr int64_t MS_PER_MIN = 60000; ///< Milliseconds per minute - constexpr int64_t MS_PER_1_MIN = 60000; ///< Milliseconds per 1 minute - constexpr int64_t MS_PER_5_MIN = 300000; ///< Milliseconds per 5 minute - constexpr int64_t MS_PER_10_MIN = 600000; ///< Milliseconds per 10 minute - constexpr int64_t MS_PER_15_MIN = 900000; ///< Milliseconds per 15 minute - constexpr int64_t MS_PER_30_MIN = 1800000; ///< Milliseconds per 30 minute - constexpr int64_t MS_PER_HALF_HOUR = 1800000; ///< Milliseconds per half hour - constexpr int64_t MS_PER_HOUR = 3600000; ///< Milliseconds per hour - constexpr int64_t MS_PER_1_HOUR = 3600000; ///< Milliseconds per 1 hour - constexpr int64_t MS_PER_2_HOUR = 7200000; ///< Milliseconds per 2 hour - constexpr int64_t MS_PER_4_HOUR = 14400000; ///< Milliseconds per 4 hour - constexpr int64_t MS_PER_5_HOUR = 18000000; ///< Milliseconds per 5 hour - constexpr int64_t MS_PER_8_HOUR = 28800000; ///< Milliseconds per 8 hour - constexpr int64_t MS_PER_12_HOUR = 43200000; ///< Milliseconds per 12 hour - constexpr int64_t MS_PER_DAY = 86400000; ///< Milliseconds per day - - // Seconds - constexpr int64_t SEC_PER_MIN = 60; ///< Seconds per minute - constexpr int64_t SEC_PER_1_MIN = 60; ///< Seconds per 1 minute - constexpr int64_t SEC_PER_3_MIN = 180; ///< Seconds per 3 minute - constexpr int64_t SEC_PER_5_MIN = 300; ///< Seconds per 5 minute - constexpr int64_t SEC_PER_10_MIN = 600; ///< Seconds per 10 minute - constexpr int64_t SEC_PER_15_MIN = 900; ///< Seconds per 15 minute - constexpr int64_t SEC_PER_HALF_HOUR = 1800; ///< Seconds per half hour - constexpr int64_t SEC_PER_HOUR = 3600; ///< Seconds per hour - constexpr int64_t SEC_PER_1_HOUR = 3600; ///< Seconds per 1 hour - constexpr int64_t SEC_PER_2_HOUR = 7200; ///< Seconds per 2 hour - constexpr int64_t SEC_PER_4_HOUR = 14400; ///< Seconds per 4 hour - constexpr int64_t SEC_PER_5_HOUR = 18000; ///< Seconds per 5 hour - constexpr int64_t SEC_PER_8_HOUR = 28800; ///< Seconds per 8 hour - constexpr int64_t SEC_PER_12_HOUR = 43200; ///< Seconds per 12 hour - constexpr int64_t SEC_PER_DAY = 86400; ///< Seconds per day - constexpr int64_t SEC_PER_YEAR = 31536000; ///< Seconds per year (365 days) - constexpr int64_t AVG_SEC_PER_YEAR = 31557600; ///< Average seconds per year (365.25 days) - constexpr int64_t SEC_PER_LEAP_YEAR = 31622400; ///< Seconds per leap year (366 days) - constexpr int64_t SEC_PER_4_YEARS = 126230400;///< Seconds per 4 years - constexpr int64_t SEC_PER_FIRST_100_YEARS = 3155760000; ///< Seconds per first 100 years - constexpr int64_t SEC_PER_100_YEARS = 3155673600; ///< Seconds per 100 years - constexpr int64_t SEC_PER_400_YEARS = 12622780800; ///< Seconds per 400 years - constexpr int64_t MAX_SEC_PER_DAY = 86399; ///< Maximum seconds per day - - // Minutes - constexpr int64_t MIN_PER_HOUR = 60; ///< Minutes per hour - constexpr int64_t MIN_PER_DAY = 1440; ///< Minutes per day - constexpr int64_t MIN_PER_1_DAY = 1440; ///< Minutes per 1 day - constexpr int64_t MIN_PER_2_DAY = 2*1440; ///< Minutes per 2 day - constexpr int64_t MIN_PER_5_DAY = 5*1440; ///< Minutes per 5 day - constexpr int64_t MIN_PER_7_DAY = 7*1440; ///< Minutes per 7 day - constexpr int64_t MIN_PER_WEEK = 10080; ///< Minutes per week - constexpr int64_t MIN_PER_10_DAY = 10*1440; ///< Minutes per 10 day - constexpr int64_t MIN_PER_15_DAY = 15*1440; ///< Minutes per 15 day - constexpr int64_t MIN_PER_30_DAY = 30*1440; ///< Minutes per 30 day - constexpr int64_t MIN_PER_MONTH = 40320; ///< Minutes per month (28 days) - constexpr int64_t MAX_MOON_MIN = 42523; ///< Maximum lunar minutes - - // Hours and days - constexpr int64_t HOURS_PER_DAY = 24; ///< Hours per day - constexpr int64_t DAYS_PER_WEEK = 7; ///< Days per week - constexpr int64_t DAYS_PER_LEAP_YEAR = 366; ///< Days per leap year - constexpr int64_t DAYS_PER_YEAR = 365; ///< Days per year - constexpr int64_t DAYS_PER_4_YEARS = 1461; ///< Days per 4 years - - // Months and years - const int64_t MONTHS_PER_YEAR = 12; ///< Months per year - const int64_t MAX_DAYS_PER_MONTH = 31; ///< Maximum days per month - const int64_t LEAP_YEAR_PER_100_YEAR = 24; ///< Leap years per 100 years - const int64_t LEAP_YEAR_PER_400_YEAR = 97; ///< Leap years per 400 years - - // Epoch and maximum values - constexpr int64_t UNIX_EPOCH = 1970; ///< Start year of UNIX time - constexpr int64_t OLE_EPOCH = 25569; ///< OLE automation date since UNIX epoch - constexpr int64_t MAX_YEAR = 292277022000LL; ///< Maximum representable year - constexpr int64_t MIN_YEAR = -2967369602200LL; ///< Minimum representable year - constexpr int64_t ERROR_YEAR = 9223372036854770000LL; ///< Error year value - constexpr int64_t MAX_TIMESTAMP = (((std::numeric_limits::max)() - (MS_PER_SEC - 1)) / MS_PER_SEC) - (SEC_PER_YEAR - 1); ///< Maximum timestamp value - constexpr int64_t MIN_TIMESTAMP = -MAX_TIMESTAMP; ///< Minimum timestamp value - constexpr int64_t MAX_TIMESTAMP_MS = MAX_TIMESTAMP * MS_PER_SEC + (MS_PER_SEC - 1); ///< Maximum timestamp value in milliseconds - constexpr int64_t MIN_TIMESTAMP_MS = MIN_TIMESTAMP * MS_PER_SEC; ///< Minimum timestamp value in milliseconds - constexpr int64_t ERROR_TIMESTAMP = 9223372036854770000LL; ///< Error timestamp value - constexpr double MAX_OADATE = (std::numeric_limits::max)(); ///< Maximum representable oadate_t value - constexpr double AVG_DAYS_PER_YEAR = 365.25; ///< Average days per year - -/// \} - -}; // namespace time_shield - -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_CONSTANTS_HPP_INCLUDED +#endif // TIME_SHIELD_HEADER_CONSTANTS_HPP_INCLUDED diff --git a/include/time_shield/conversions.hpp b/include/time_shield/conversions.hpp new file mode 100644 index 00000000..ac8f0413 --- /dev/null +++ b/include/time_shield/conversions.hpp @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT +#pragma once +#ifndef TIME_SHIELD_HEADER_CONVERSIONS_HPP_INCLUDED +#define TIME_SHIELD_HEADER_CONVERSIONS_HPP_INCLUDED + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(TIME_SHIELD_ENABLE_LEGACY_ALIASES) +# include +#endif + +#endif // TIME_SHIELD_HEADER_CONVERSIONS_HPP_INCLUDED diff --git a/include/time_shield/conversions/date_conversions.hpp b/include/time_shield/conversions/date_conversions.hpp new file mode 100644 index 00000000..379ad8aa --- /dev/null +++ b/include/time_shield/conversions/date_conversions.hpp @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: MIT +#pragma once +#ifndef TIME_SHIELD_HEADER_CONVERSIONS_DATE_CONVERSIONS_HPP_INCLUDED +#define TIME_SHIELD_HEADER_CONVERSIONS_DATE_CONVERSIONS_HPP_INCLUDED + +/// \file date_conversions.hpp +/// \brief Conversions related to calendar dates and DateStruct helpers. + +#include +#include "unix_time_conversions.hpp" + +namespace time_shield { + +/// \ingroup time_conversions +/// \{ + + /// \brief Get the year from the timestamp. + /// + /// This function returns the year of the specified timestamp in seconds since the Unix epoch. + /// + /// \tparam T The return type of the function (default is year_t). + /// \param ts Timestamp in seconds (default is current timestamp). + /// \return Year of the specified timestamp. + template + TIME_SHIELD_CONSTEXPR inline T year_of(ts_t ts = time_shield::ts()) { + return years_since_epoch(ts) + static_cast(UNIX_EPOCH); + } + + /// \brief Get the year from the timestamp in milliseconds. + /// + /// This function returns the year of the specified timestamp in milliseconds since the Unix epoch. + /// + /// \tparam T The return type of the function (default is year_t). + /// \param ts_ms Timestamp in milliseconds (default is current timestamp). + /// \return Year of the specified timestamp. + template + TIME_SHIELD_CONSTEXPR inline T year_of_ms(ts_ms_t ts_ms = time_shield::ts_ms()) { + return year_of(ms_to_sec(ts_ms)); + } + + /// \brief Get the number of days in a year. + /// \param year Year. + /// \return Number of days in the given year. + template + TIME_SHIELD_CONSTEXPR inline T1 num_days_in_year(T2 year) noexcept { + if (is_leap_year_date(year)) return DAYS_PER_LEAP_YEAR; + return DAYS_PER_YEAR; + } + + /// \brief Get the number of days in the current year. + /// + /// This function calculates and returns the number of days in the current year based on the provided timestamp. + /// + /// \param ts Timestamp. + /// \return Number of days in the current year. + template + TIME_SHIELD_CONSTEXPR inline T num_days_in_year_ts(ts_t ts = time_shield::ts()) { + if (is_leap_year_ts(ts)) return DAYS_PER_LEAP_YEAR; + return DAYS_PER_YEAR; + } + + /// \brief Get the day of the week. + /// \tparam T1 Return type (default: Weekday). + /// \tparam T2 Year type. + /// \tparam T3 Month type. + /// \tparam T4 Day type. + /// \param year Year. + /// \param month Month. + /// \param day Day. + /// \return Day of the week (SUN = 0, MON = 1, ... SAT = 6). + template + TIME_SHIELD_CONSTEXPR inline T1 day_of_week_date(T2 year, T3 month, T4 day) { + year_t a = 0; + year_t y = 0; + year_t m = 0; + year_t R = 0; + a = (14 - month) / MONTHS_PER_YEAR; + y = year - a; + m = month + MONTHS_PER_YEAR * a - 2; + R = 7000 + ( day + y + (y / 4) - (y / 100) + (y / 400) + (31 * m) / MONTHS_PER_YEAR); + return static_cast(R % DAYS_PER_WEEK); + } + + /// \ingroup time_structures + /// \brief Get the day of the week from a date structure. + /// + /// This function takes a date structure with fields 'year', 'mon', and 'day', + /// and returns the day of the week (SUN = 0, MON = 1, ... SAT = 6). + /// + /// \param date Structure containing year, month, and day. + /// \return Day of the week (SUN = 0, MON = 1, ... SAT = 6). + template + TIME_SHIELD_CONSTEXPR inline T1 weekday_of_date(const T2& date) { + return day_of_week_date(date.year, date.mon, date.day); + } + + /// \ingroup time_structures + /// \brief Alias for weekday_of_date. + /// \copydoc weekday_of_date + template + TIME_SHIELD_CONSTEXPR inline T1 weekday_from_date(const T2& date) { + return weekday_of_date(date); + } + +/// \} + +}; // namespace time_shield + +#endif // TIME_SHIELD_HEADER_CONVERSIONS_DATE_CONVERSIONS_HPP_INCLUDED diff --git a/include/time_shield/conversions/date_time_conversions.hpp b/include/time_shield/conversions/date_time_conversions.hpp new file mode 100644 index 00000000..f1ccd8ac --- /dev/null +++ b/include/time_shield/conversions/date_time_conversions.hpp @@ -0,0 +1,1196 @@ +// SPDX-License-Identifier: MIT +#pragma once +#ifndef TIME_SHIELD_HEADER_CONVERSIONS_DATE_TIME_CONVERSIONS_HPP_INCLUDED +#define TIME_SHIELD_HEADER_CONVERSIONS_DATE_TIME_CONVERSIONS_HPP_INCLUDED + +/// \file date_time_conversions.hpp +/// \brief Conversions involving DateTimeStruct and day boundary helpers. + +#include +#include "date_conversions.hpp" +#include "detail/fast_date.hpp" +#include "detail/floor_math.hpp" +#include "time_unit_conversions.hpp" +#include "unix_time_conversions.hpp" + +#include +#include +#include +#include +#include + +namespace time_shield { + +/// \ingroup time_conversions +/// \{ + + namespace legacy { + + /// \ingroup time_structures + /// \brief Converts a timestamp to a date-time structure. + /// + /// This function converts a timestamp (usually an integer representing seconds since epoch) + /// to a custom date-time structure. The default type for the timestamp is int64_t. + /// + /// \tparam T1 The date-time structure type to be returned. + /// \tparam T2 The type of the timestamp (default is int64_t). + /// \param ts The timestamp to be converted. + /// \return A date-time structure of type T1. + template + T1 to_date_time(T2 ts) { + // 9223372029693630000 reaches year 292277024400 from the 2000 epoch. + // This value overflows the n_400_years * SEC_PER_400_YEARS calculation. + // The supported bound is reduced to 9223371890843040000. + constexpr int64_t BIAS_292277022000 = 9223371890843040000LL; + constexpr int64_t BIAS_2000 = 946684800LL; + + int64_t y = MAX_YEAR; + int64_t secs = -((static_cast(ts) - BIAS_2000) - BIAS_292277022000); + + const int64_t n_400_years = secs / SEC_PER_400_YEARS; + secs -= n_400_years * SEC_PER_400_YEARS; + y -= n_400_years * 400LL; + + const int64_t n_100_years = secs / SEC_PER_100_YEARS; + secs -= n_100_years * SEC_PER_100_YEARS; + y -= n_100_years * 100LL; + + const int64_t n_4_years = secs / SEC_PER_4_YEARS; + secs -= n_4_years * SEC_PER_4_YEARS; + y -= n_4_years * 4LL; + + const int64_t n_1_years = secs / SEC_PER_YEAR; + secs -= n_1_years * SEC_PER_YEAR; + y -= n_1_years; + + T1 date_time; + + if (secs == 0) { + date_time.year = y; + date_time.mon = 1; + date_time.day = 1; + return date_time; + } + + date_time.year = y - 1; + const bool is_leap_year = is_leap_year_date(date_time.year); + secs = is_leap_year ? SEC_PER_LEAP_YEAR - secs : SEC_PER_YEAR - secs; + const int days = static_cast(secs / SEC_PER_DAY); + + constexpr int JAN_AND_FEB_DAY_LEAP_YEAR = 60 - 1; + constexpr int TABLE_MONTH_OF_YEAR[] = { + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // January (31 days) + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, // February (28 days) + 3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3, // March (31 days) + 4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4, // April (30 days) + 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5, + 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11, + 12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12, + }; + constexpr int TABLE_DAY_OF_YEAR[] = { + 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31, // January (31 days) + 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28, // February (28 days) + 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31, // March (31 days) + 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30, // April (30 days) + 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31, + 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30, + 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31, + 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31, + 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30, + 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31, + 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30, + 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31, + }; + + if (is_leap_year) { + const int prev_days = days - 1; + date_time.day = days == JAN_AND_FEB_DAY_LEAP_YEAR ? (TABLE_DAY_OF_YEAR[prev_days] + 1) : + (days > JAN_AND_FEB_DAY_LEAP_YEAR ? TABLE_DAY_OF_YEAR[prev_days] : TABLE_DAY_OF_YEAR[days]); + date_time.mon = days >= JAN_AND_FEB_DAY_LEAP_YEAR ? TABLE_MONTH_OF_YEAR[prev_days] : TABLE_MONTH_OF_YEAR[days]; + } else { + date_time.day = TABLE_DAY_OF_YEAR[days]; + date_time.mon = TABLE_MONTH_OF_YEAR[days]; + } + + ts_t day_secs = static_cast(detail::floor_mod(secs, SEC_PER_DAY)); + date_time.hour = static_cast(day_secs / SEC_PER_HOUR); + ts_t min_secs = static_cast(day_secs - date_time.hour * SEC_PER_HOUR); + date_time.min = static_cast(min_secs / SEC_PER_MIN); + date_time.sec = static_cast(min_secs - date_time.min * SEC_PER_MIN); +# ifdef TIME_SHIELD_CPP17 + if TIME_SHIELD_IF_CONSTEXPR (std::is_floating_point::value) { + date_time.ms = static_cast(std::round(std::fmod(static_cast(ts), static_cast(MS_PER_SEC)))); + } else date_time.ms = 0; +# else + if (std::is_floating_point::value) { + date_time.ms = static_cast(std::round(std::fmod(static_cast(ts), static_cast(MS_PER_SEC)))); + } else date_time.ms = 0; +# endif + return date_time; + } + + } // namespace legacy + + /// \ingroup time_structures + /// \brief Converts a timestamp to a date-time structure. + /// + /// This function converts a timestamp (usually an integer representing seconds since epoch) + /// to a custom date-time structure. The default type for the timestamp is int64_t. + /// \note Inspired by the algorithm described in: + /// https://www.benjoffe.com/fast-date-64 + /// This implementation is written from scratch (no code copied). + /// + /// \tparam T1 The date-time structure type to be returned. + /// \tparam T2 The type of the timestamp (default is int64_t). + /// \param ts The timestamp to be converted. + /// \return A date-time structure of type T1. + template + T1 to_date_time(T2 ts) { + const int64_t whole_sec = static_cast(ts); + const detail::DaySplit split = detail::split_unix_day(whole_sec); + const detail::FastDate date = detail::fast_date_from_days(split.days); + + T1 date_time{}; + date_time.year = static_cast(date.year); + date_time.mon = static_cast(date.month); + date_time.day = static_cast(date.day); + + const ts_t day_secs = static_cast(split.sec_of_day); + date_time.hour = static_cast(day_secs / SEC_PER_HOUR); + const ts_t min_secs = static_cast(day_secs - date_time.hour * SEC_PER_HOUR); + date_time.min = static_cast(min_secs / SEC_PER_MIN); + date_time.sec = static_cast(min_secs - date_time.min * SEC_PER_MIN); +# ifdef TIME_SHIELD_CPP17 + if TIME_SHIELD_IF_CONSTEXPR (std::is_floating_point::value) { + date_time.ms = static_cast(std::round(std::fmod(static_cast(ts), static_cast(MS_PER_SEC)))); + } else date_time.ms = 0; +# else + if (std::is_floating_point::value) { + date_time.ms = static_cast(std::round(std::fmod(static_cast(ts), static_cast(MS_PER_SEC)))); + } else date_time.ms = 0; +# endif + return date_time; + } + + /// \ingroup time_structures + /// \brief Converts a timestamp in milliseconds to a date-time structure with milliseconds. + /// \tparam T The type of the date-time structure to return. + /// \param ts The timestamp in milliseconds to convert. + /// \return T A date-time structure with the corresponding date and time components. + template + inline T to_date_time_ms(ts_ms_t ts) { + const ts_t sec = ms_to_sec(ts); + T date_time = to_date_time(sec); + date_time.ms = ms_of_ts(ts); // Extract and set the ms component + return date_time; + } + + namespace legacy { + + /// \brief Converts a date and time to a timestamp. + /// + /// This function converts a given date and time to a timestamp, which is the number + /// of seconds since the Unix epoch (January 1, 1970). + /// + /// If the `day` is ≥ 1970 and `year` ≤ 31, parameters are assumed to be in DD-MM-YYYY order + /// and are automatically reordered. + /// + /// \tparam T1 The type of the year parameter (default is int64_t). + /// \tparam T2 The type of the other date and time parameters (default is int). + /// \param year The year value. + /// \param month The month value. + /// \param day The day value. + /// \param hour The hour value (default is 0). + /// \param min The minute value (default is 0). + /// \param sec The second value (default is 0). + /// \return Timestamp representing the given date and time. + /// \throws std::invalid_argument if the date-time combination is invalid. + /// + /// \par Aliases: + /// Following function names are provided as aliases: + /// - `ts(...)` + /// - `get_ts(...)` + /// - `get_timestamp(...)` + /// - `timestamp(...)` + /// - `to_ts(...)` + /// + /// These aliases are macro-generated and behave identically to `to_timestamp`. + /// + /// \sa ts() \sa get_ts() \sa get_timestamp() \sa timestamp() \sa to_ts() + template + TIME_SHIELD_CONSTEXPR inline ts_t to_timestamp( + T1 year, + T2 month, + T2 day, + T2 hour = 0, + T2 min = 0, + T2 sec = 0) { + + if (day >= UNIX_EPOCH && year <= 31) { + return to_timestamp((T1)day, month, (T2)year, hour, min, sec); + } + if (!is_valid_date_time(year, month, day, hour, min, sec)) { + throw std::invalid_argument("Invalid date-time combination"); + } + + int64_t secs = 0; + int64_t years = (static_cast(MAX_YEAR) - year); + + const int64_t n_400_years = years / 400LL; + secs += n_400_years * SEC_PER_400_YEARS; + years -= n_400_years * 400LL; + + const int64_t n_100_years = years / 100LL; + secs += n_100_years * SEC_PER_100_YEARS; + years -= n_100_years * 100LL; + + const int64_t n_4_years = years / 4LL; + secs += n_4_years * SEC_PER_4_YEARS; + years -= n_4_years * 4LL; + + secs += years * SEC_PER_YEAR; + + // 9223372029693630000 reaches year 292277024400 from the 2000 epoch. + // This value overflows the n_400_years * SEC_PER_400_YEARS calculation. + // The supported bound is reduced to 9223371890843040000. + constexpr int64_t BIAS_292277022000 = 9223371890843040000LL; + constexpr int64_t BIAS_2000 = 946684800LL; + + secs = BIAS_292277022000 - secs; + secs += BIAS_2000; + + if (month == 1 && day == 1 && + hour == 0 && min == 0 && + sec == 0) { + return secs; + } + + constexpr int lmos[] = {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335}; + constexpr int mos[] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334}; + + secs += (is_leap_year_date(year) ? (lmos[month - 1] + day - 1) : (mos[month - 1] + day - 1)) * SEC_PER_DAY; + secs += SEC_PER_HOUR * hour + SEC_PER_MIN * min + sec; + return secs; + } + + /// \brief Converts a date and time to a timestamp without validation. + /// + /// This function converts a given date and time to a timestamp, which is the number + /// of seconds since the Unix epoch (January 1, 1970). + /// + /// If the `day` is ≥ 1970 and `year` ≤ 31, parameters are assumed to be in DD-MM-YYYY order + /// and are automatically reordered. + /// + /// \tparam T1 The type of the year parameter (default is int64_t). + /// \tparam T2 The type of the other date and time parameters (default is int). + /// \param year The year value. + /// \param month The month value. + /// \param day The day value. + /// \param hour The hour value (default is 0). + /// \param min The minute value (default is 0). + /// \param sec The second value (default is 0). + /// \return Timestamp representing the given date and time. + template + TIME_SHIELD_CONSTEXPR inline ts_t to_timestamp_unchecked( + T1 year, + T2 month, + T2 day, + T2 hour = 0, + T2 min = 0, + T2 sec = 0) noexcept { + + if (day >= UNIX_EPOCH && year <= 31) { + return to_timestamp_unchecked((T1)day, month, (T2)year, hour, min, sec); + } + + int64_t secs = 0; + int64_t years = (static_cast(MAX_YEAR) - year); + + const int64_t n_400_years = years / 400LL; + secs += n_400_years * SEC_PER_400_YEARS; + years -= n_400_years * 400LL; + + const int64_t n_100_years = years / 100LL; + secs += n_100_years * SEC_PER_100_YEARS; + years -= n_100_years * 100LL; + + const int64_t n_4_years = years / 4LL; + secs += n_4_years * SEC_PER_4_YEARS; + years -= n_4_years * 4LL; + + secs += years * SEC_PER_YEAR; + + // 9223372029693630000 reaches year 292277024400 from the 2000 epoch. + // This value overflows the n_400_years * SEC_PER_400_YEARS calculation. + // The supported bound is reduced to 9223371890843040000. + constexpr int64_t BIAS_292277022000 = 9223371890843040000LL; + constexpr int64_t BIAS_2000 = 946684800LL; + + secs = BIAS_292277022000 - secs; + secs += BIAS_2000; + + if (month == 1 && day == 1 && + hour == 0 && min == 0 && + sec == 0) { + return secs; + } + + constexpr int lmos[] = {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335}; + constexpr int mos[] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334}; + + secs += (is_leap_year_date(year) ? (lmos[month - 1] + day - 1) : (mos[month - 1] + day - 1)) * SEC_PER_DAY; + secs += SEC_PER_HOUR * hour + SEC_PER_MIN * min + sec; + return secs; + } + + } // namespace legacy + + /// \brief Converts a date and time to a timestamp without validation. + /// + /// This function converts a given date and time to a timestamp, which is the number + /// of seconds since the Unix epoch (January 1, 1970). + /// + /// If the `day` is ≥ 1970 and `year` ≤ 31, parameters are assumed to be in DD-MM-YYYY order + /// and are automatically reordered. + /// + /// \note Inspired by the algorithm described in: + /// https://www.benjoffe.com/fast-date-64 + /// This implementation is written from scratch (no code copied). + /// + /// \tparam T1 The type of the year parameter (default is int64_t). + /// \tparam T2 The type of the other date and time parameters (default is int). + /// \param year The year value. + /// \param month The month value. + /// \param day The day value. + /// \param hour The hour value (default is 0). + /// \param min The minute value (default is 0). + /// \param sec The second value (default is 0). + /// \return Timestamp representing the given date and time. + template + TIME_SHIELD_CONSTEXPR inline ts_t to_timestamp_unchecked( + T1 year, + T2 month, + T2 day, + T2 hour = 0, + T2 min = 0, + T2 sec = 0) noexcept { + + if (day >= UNIX_EPOCH && year <= 31) { + return to_timestamp_unchecked((T1)day, month, (T2)year, hour, min, sec); + } + + const dse_t unix_day = date_to_unix_day(year, month, day); + return static_cast(unix_day * SEC_PER_DAY + + SEC_PER_HOUR * static_cast(hour) + + SEC_PER_MIN * static_cast(min) + + static_cast(sec)); + } + + /// \brief Converts a date and time to a timestamp. + /// + /// This function converts a given date and time to a timestamp, which is the number + /// of seconds since the Unix epoch (January 1, 1970). + /// + /// If the `day` is ≥ 1970 and `year` ≤ 31, parameters are assumed to be in DD-MM-YYYY order + /// and are automatically reordered. + /// + /// \note Inspired by the algorithm described in: + /// https://www.benjoffe.com/fast-date-64 + /// This implementation is written from scratch (no code copied). + /// + /// \tparam T1 The type of the year parameter (default is int64_t). + /// \tparam T2 The type of the other date and time parameters (default is int). + /// \param year The year value. + /// \param month The month value. + /// \param day The day value. + /// \param hour The hour value (default is 0). + /// \param min The minute value (default is 0). + /// \param sec The second value (default is 0). + /// \return Timestamp representing the given date and time. + /// \throws std::invalid_argument if the date-time combination is invalid. + /// + /// \par Aliases: + /// Following function names are provided as aliases: + /// - `ts(...)` + /// - `get_ts(...)` + /// - `get_timestamp(...)` + /// - `timestamp(...)` + /// - `to_ts(...)` + /// + /// These aliases are macro-generated and behave identically to `to_timestamp`. + /// + /// \sa ts() \sa get_ts() \sa get_timestamp() \sa timestamp() \sa to_ts() + template + TIME_SHIELD_CONSTEXPR inline ts_t to_timestamp( + T1 year, + T2 month, + T2 day, + T2 hour = 0, + T2 min = 0, + T2 sec = 0) { + + if (day >= UNIX_EPOCH && year <= 31) { + return to_timestamp((T1)day, month, (T2)year, hour, min, sec); + } + if (!is_valid_date_time(year, month, day, hour, min, sec)) { + throw std::invalid_argument("Invalid date-time combination"); + } + + return to_timestamp_unchecked(year, month, day, hour, min, sec); + } + + /// \ingroup time_structures + /// \brief Converts a date-time structure to a timestamp. + /// + /// This function converts a given date and time to a timestamp, which is the number + /// of seconds since the Unix epoch (January 1, 1970). + /// + /// \tparam T The type of the date-time structure. + /// \param date_time The date-time structure. + /// \return Timestamp representing the given date and time. + /// \throws std::invalid_argument if the date-time combination is invalid. + template + TIME_SHIELD_CONSTEXPR inline ts_t dt_to_timestamp( + const T& date_time) { + return to_timestamp( + date_time.year, + date_time.mon, + date_time.day, + date_time.hour, + date_time.min, + date_time.sec + ); + } + + /// \ingroup time_structures + /// \brief Converts a std::tm structure to a timestamp. + /// + /// This function converts a given std::tm structure to a timestamp, which is the number + /// of seconds since the Unix epoch (January 1, 1970). + /// + /// \param timeinfo Pointer to a std::tm structure containing the date and time information. + /// \return Timestamp representing the given date and time. + TIME_SHIELD_CONSTEXPR inline ts_t tm_to_timestamp( + const std::tm *timeinfo) { + return to_timestamp( + static_cast(timeinfo->tm_year + 1900), + static_cast(timeinfo->tm_mon + 1), + static_cast(timeinfo->tm_mday), + static_cast(timeinfo->tm_hour), + static_cast(timeinfo->tm_min), + static_cast(timeinfo->tm_sec) + ); + } + + /// \ingroup time_structures + /// \brief Converts a date-time structure to a timestamp in milliseconds. + /// + /// This function converts a given date and time to a timestamp in milliseconds, + /// which is the number of milliseconds since the Unix epoch (January 1, 1970). + /// + /// \tparam T1 The type of the year parameter (default is year_t). + /// \tparam T2 The type of the month, day, hour, minute, and second parameters (default is int). + /// \param year The year value. + /// \param month The month value. + /// \param day The day value. + /// \param hour The hour value (default is 0). + /// \param min The minute value (default is 0). + /// \param sec The second value (default is 0). + /// \param ms The millisecond value (default is 0). + /// \return Timestamp in milliseconds representing the given date and time. + /// \throws std::invalid_argument if the date-time combination is invalid. + template + TIME_SHIELD_CONSTEXPR inline ts_ms_t to_timestamp_ms( + T1 year, + T2 month, + T2 day, + T2 hour = 0, + T2 min = 0, + T2 sec = 0, + T2 ms = 0) { + int64_t sec_value = static_cast(to_timestamp(year, month, day, hour, min, sec)); + int64_t ms_value = static_cast(ms); + sec_value += detail::floor_div(ms_value, static_cast(MS_PER_SEC)); + ms_value = detail::floor_mod(ms_value, static_cast(MS_PER_SEC)); + if ((sec_value > 0 && + sec_value > ((std::numeric_limits::max)() - ms_value) / MS_PER_SEC) || + (sec_value < 0 && + sec_value < (std::numeric_limits::min)() / MS_PER_SEC)) { + return ERROR_TIMESTAMP; + } + return static_cast(sec_value * MS_PER_SEC + ms_value); + } + + /// \ingroup time_structures + /// \brief Converts a date-time structure to a timestamp in milliseconds. + /// + /// This function converts a given date and time structure to a timestamp in milliseconds, + /// which is the number of milliseconds since the Unix epoch (January 1, 1970). + /// + /// \tparam T The type of the date-time structure. + /// \param date_time The date-time structure containing year, month, day, hour, minute, second, and millisecond fields. + /// \return Timestamp in milliseconds representing the given date and time. + /// \throws std::invalid_argument if the date-time combination is invalid. + template + TIME_SHIELD_CONSTEXPR inline ts_ms_t dt_to_timestamp_ms( + const T& date_time) { + int64_t sec_value = static_cast(dt_to_timestamp(date_time)); + int64_t ms_value = static_cast(date_time.ms); + sec_value += detail::floor_div(ms_value, static_cast(MS_PER_SEC)); + ms_value = detail::floor_mod(ms_value, static_cast(MS_PER_SEC)); + if ((sec_value > 0 && + sec_value > ((std::numeric_limits::max)() - ms_value) / MS_PER_SEC) || + (sec_value < 0 && + sec_value < (std::numeric_limits::min)() / MS_PER_SEC)) { + return ERROR_TIMESTAMP; + } + return static_cast(sec_value * MS_PER_SEC + ms_value); + } + + /// \ingroup time_structures + /// \brief Converts a std::tm structure to a timestamp in milliseconds. + /// + /// This function converts a given std::tm structure to a timestamp in milliseconds, + /// which is the number of milliseconds since the Unix epoch (January 1, 1970). + /// + /// \param timeinfo Pointer to a std::tm structure containing the date and time information. + /// \return Timestamp in milliseconds representing the given date and time. + TIME_SHIELD_CONSTEXPR inline ts_t tm_to_timestamp_ms( + const std::tm *timeinfo) { + return sec_to_ms(tm_to_timestamp(timeinfo)); + } + + /// \brief Converts a date and time to a floating-point timestamp. + /// + /// This function converts a given date and time to a floating-point timestamp, + /// which is the number of seconds (with fractional milliseconds) since the Unix epoch + /// (January 1, 1970). + /// + /// \tparam T1 The type of the year parameter (default is year_t). + /// \tparam T2 The type of the month, day, hour, minute, and second parameters (default is int). + /// \tparam T3 The type of the millisecond parameter (default is int). + /// \param year The year value. + /// \param month The month value. + /// \param day The day value. + /// \param hour The hour value (default is 0). + /// \param min The minute value (default is 0). + /// \param sec The second value (default is 0). + /// \param ms The millisecond value (default is 0). + /// \return Floating-point timestamp representing the given date and time. + /// \throws std::invalid_argument if the date-time combination is invalid. + template + TIME_SHIELD_CONSTEXPR inline fts_t to_ftimestamp( + T1 year, + T2 month, + T2 day, + T2 hour = 0, + T2 min = 0, + T2 sec = 0, + T3 ms = 0) { + int64_t sec_value = static_cast(to_timestamp(year, month, day, hour, min, sec)); + int64_t ms_value = static_cast(ms); + sec_value += detail::floor_div(ms_value, static_cast(MS_PER_SEC)); + ms_value = detail::floor_mod(ms_value, static_cast(MS_PER_SEC)); + return static_cast(sec_value) + + static_cast(ms_value) / static_cast(MS_PER_SEC); + } + + /// \ingroup time_structures + /// \brief Converts a date-time structure to a floating-point timestamp. + /// + /// This function converts a given date and time structure to a floating-point timestamp, + /// which is the number of seconds (with fractional milliseconds) since the Unix epoch + /// (January 1, 1970). + /// + /// \tparam T The type of the date-time structure. + /// \param date_time The date-time structure containing year, month, day, hour, minute, second, and millisecond fields. + /// \return Floating-point timestamp representing the given date and time. + /// \throws std::invalid_argument if the date-time combination is invalid. + template + TIME_SHIELD_CONSTEXPR inline fts_t dt_to_ftimestamp( + const T& date_time) { + int64_t sec_value = static_cast(to_timestamp(date_time)); + int64_t ms_value = static_cast(date_time.ms); + sec_value += detail::floor_div(ms_value, static_cast(MS_PER_SEC)); + ms_value = detail::floor_mod(ms_value, static_cast(MS_PER_SEC)); + return static_cast(sec_value) + + static_cast(ms_value) / static_cast(MS_PER_SEC); + } + + /// \brief Converts a std::tm structure to a floating-point timestamp. + /// + /// This function converts a given std::tm structure to a floating-point timestamp, + /// which is the number of seconds (with fractional milliseconds) since the Unix epoch + /// (January 1, 1970). + /// + /// \param timeinfo Pointer to the std::tm structure containing the date and time. + /// \return Floating-point timestamp representing the given date and time. + /// \throws std::invalid_argument if the date-time combination is invalid. + TIME_SHIELD_CONSTEXPR inline fts_t tm_to_ftimestamp( + const std::tm* timeinfo) { + return static_cast(tm_to_timestamp(timeinfo)); + } + + /// \brief Get the start of the day timestamp. + /// + /// This function returns the timestamp at the start of the day. + /// Sets the hours, minutes, and seconds to zero. + /// + /// \param ts Timestamp. + /// \return Start of the day timestamp. + TIME_SHIELD_CONSTEXPR inline ts_t start_of_day(ts_t ts = time_shield::ts()) noexcept { + return ts - detail::floor_mod(ts, SEC_PER_DAY); + } + + /// \brief Get timestamp of the start of the previous day. + /// + /// This function returns the timestamp at the start of the previous day. + /// + /// \param ts Timestamp of the current day. + /// \param days Number of days to go back (default is 1). + /// \return Timestamp of the start of the previous day. + template + TIME_SHIELD_CONSTEXPR ts_t start_of_prev_day(ts_t ts = time_shield::ts(), T days = 1) noexcept { + return ts - detail::floor_mod(ts, SEC_PER_DAY) - SEC_PER_DAY * days; + } + + /// \brief Get the start of the day timestamp in seconds. + /// + /// This function returns the timestamp at the start of the day in seconds. + /// Sets the hours, minutes, and seconds to zero. + /// + /// \param ts_ms Timestamp in milliseconds. + /// \return Start of the day timestamp in seconds. + TIME_SHIELD_CONSTEXPR inline ts_t start_of_day_sec(ts_ms_t ts_ms = time_shield::ts_ms()) noexcept { + return start_of_day(ms_to_sec(ts_ms)); + } + + /// \brief Get the start of the day timestamp in milliseconds. + /// + /// This function returns the timestamp at the start of the day in milliseconds. + /// Sets the hours, minutes, seconds, and milliseconds to zero. + /// + /// \param ts_ms Timestamp in milliseconds. + /// \return Start of the day timestamp in milliseconds. + TIME_SHIELD_CONSTEXPR inline ts_ms_t start_of_day_ms(ts_ms_t ts_ms = time_shield::ts_ms()) noexcept { + return ts_ms - detail::floor_mod(ts_ms, MS_PER_DAY); + } + + /// \brief Get the timestamp of the start of the day after a specified number of days. + /// + /// Calculates the timestamp for the beginning of the day after a specified number of days + /// relative to the given timestamp. + /// + /// \param ts The current timestamp in seconds. + /// \param days The number of days after the current day (default is 1). + /// \return The timestamp in seconds representing the beginning of the specified future day. + template + TIME_SHIELD_CONSTEXPR ts_t start_of_next_day(ts_t ts, T days = 1) noexcept { + return start_of_day(ts) + days * SEC_PER_DAY; + } + + /// \brief Get the timestamp of the start of the day after a specified number of days. + /// + /// Calculates the timestamp for the beginning of the day after a specified number of days + /// relative to the given timestamp in milliseconds. + /// + /// \param ts_ms The current timestamp in milliseconds. + /// \param days The number of days after the current day (default is 1). + /// \return The timestamp in milliseconds representing the beginning of the specified future day. + template + TIME_SHIELD_CONSTEXPR ts_ms_t start_of_next_day_ms(ts_ms_t ts_ms, T days = 1) noexcept { + return start_of_day_ms(ts_ms) + days * MS_PER_DAY; + } + + /// \brief Calculate the timestamp for a specified number of days in the future. + /// + /// Adds the given number of days to the provided timestamp, without adjusting to the start of the day. + /// + /// \param ts The current timestamp in seconds. + /// \param days The number of days to add to the current timestamp (default is 1). + /// \return The timestamp in seconds after adding the specified number of days. + template + TIME_SHIELD_CONSTEXPR ts_t next_day(ts_t ts, T days = 1) noexcept { + return ts + days * SEC_PER_DAY; + } + + /// \brief Calculate the timestamp for a specified number of days in the future (milliseconds). + /// + /// Adds the given number of days to the provided timestamp, without adjusting to the start of the day. + /// + /// \param ts_ms The current timestamp in milliseconds. + /// \param days The number of days to add to the current timestamp (default is 1). + /// \return The timestamp in milliseconds after adding the specified number of days. + template + TIME_SHIELD_CONSTEXPR ts_ms_t next_day_ms(ts_ms_t ts_ms, T days = 1) noexcept { + return ts_ms + days * MS_PER_DAY; + } + + /// \brief Get the timestamp at the end of the day. + /// + /// This function sets the hour to 23, minute to 59, and second to 59. + /// + /// \param ts Timestamp. + /// \return Timestamp at the end of the day. + TIME_SHIELD_CONSTEXPR inline ts_t end_of_day(ts_t ts = time_shield::ts()) noexcept { + return ts - detail::floor_mod(ts, SEC_PER_DAY) + SEC_PER_DAY - 1; + } + + /// \brief Get the timestamp at the end of the day in seconds. + /// + /// This function sets the hour to 23, minute to 59, and second to 59. + /// + /// \param ts_ms Timestamp in milliseconds. + /// \return Timestamp at the end of the day in seconds. + TIME_SHIELD_CONSTEXPR inline ts_t end_of_day_sec(ts_ms_t ts_ms = time_shield::ts_ms()) noexcept { + return end_of_day(ms_to_sec(ts_ms)); + } + + /// \brief Get the timestamp at the end of the day in milliseconds. + /// + /// This function sets the hour to 23, minute to 59, second to 59, and millisecond to 999. + /// + /// \param ts_ms Timestamp in milliseconds. + /// \return Timestamp at the end of the day in milliseconds. + TIME_SHIELD_CONSTEXPR inline ts_ms_t end_of_day_ms(ts_ms_t ts_ms = time_shield::ts_ms()) noexcept { + return ts_ms - detail::floor_mod(ts_ms, MS_PER_DAY) + MS_PER_DAY - 1; + } + + /// \brief Get the timestamp of the start of the year. + /// \param year Year. + /// \return Timestamp at 00:00:00 of the first day of the year. + template + TIME_SHIELD_CONSTEXPR inline ts_t start_of_year_date(T year) { + const ts_t year_ts = to_timestamp(year, 1, 1); + + return start_of_day(year_ts); + } + + /// \brief Get the timestamp in milliseconds of the start of the year. + /// + /// This function returns the timestamp at the start of the specified year in milliseconds. + /// + /// \param year Year. + /// \return Timestamp of the start of the year in milliseconds. + /// \throws std::invalid_argument if the date-time combination is invalid. + template + TIME_SHIELD_CONSTEXPR inline ts_ms_t start_of_year_date_ms(T year) { + return sec_to_ms(start_of_year_date(year)); + } + + /// \brief Get the start of the year timestamp. + /// + /// This function resets the days, months, hours, minutes, and seconds of the given timestamp + /// to the beginning of the year. + /// + /// \param ts Timestamp. + /// \return Start of the year timestamp. + TIME_SHIELD_CONSTEXPR inline ts_t start_of_year(ts_t ts) noexcept { + constexpr ts_t BIAS_2100 = 4102444800; + if (ts >= 0 && ts < BIAS_2100) { + constexpr ts_t SEC_PER_YEAR_X2 = SEC_PER_YEAR * 2; + ts_t year_start_ts = detail::floor_mod(ts, SEC_PER_4_YEARS); + if (year_start_ts < SEC_PER_YEAR) { + return ts - year_start_ts; + } else if (year_start_ts < SEC_PER_YEAR_X2) { + return ts + SEC_PER_YEAR - year_start_ts; + } else if (year_start_ts < (SEC_PER_YEAR_X2 + SEC_PER_LEAP_YEAR)) { + return ts + SEC_PER_YEAR_X2 - year_start_ts; + } + return ts + (SEC_PER_YEAR_X2 + SEC_PER_LEAP_YEAR) - year_start_ts; + } + + constexpr ts_t BIAS_2000 = 946684800; + ts_t secs = ts - BIAS_2000; + + ts_t offset_y400 = detail::floor_mod(secs, SEC_PER_400_YEARS); + ts_t start_ts = secs - offset_y400 + BIAS_2000; + secs = offset_y400; + + if (secs >= SEC_PER_FIRST_100_YEARS) { + secs -= SEC_PER_FIRST_100_YEARS; + start_ts += SEC_PER_FIRST_100_YEARS; + while (secs >= SEC_PER_100_YEARS) { + secs -= SEC_PER_100_YEARS; + start_ts += SEC_PER_100_YEARS; + } + + constexpr ts_t SEC_PER_4_YEARS_V2 = 4 * SEC_PER_YEAR; + if (secs >= SEC_PER_4_YEARS_V2) { + secs -= SEC_PER_4_YEARS_V2; + start_ts += SEC_PER_4_YEARS_V2; + } else { + start_ts += secs - detail::floor_mod(secs, SEC_PER_YEAR); + return start_ts; + } + } + + ts_t offset_4y = detail::floor_mod(secs, SEC_PER_4_YEARS); + start_ts += secs - offset_4y; + secs = offset_4y; + + if (secs >= SEC_PER_LEAP_YEAR) { + secs -= SEC_PER_LEAP_YEAR; + start_ts += SEC_PER_LEAP_YEAR; + start_ts += secs - detail::floor_mod(secs, SEC_PER_YEAR); + return start_ts; + } + + start_ts += secs - detail::floor_mod(secs, SEC_PER_YEAR); + return start_ts; + } + + /// \brief Get the timestamp at the start of the year in milliseconds. + /// \param ts_ms Timestamp in milliseconds. + /// \return Timestamp at 00:00:00.000 of the first day of the year. + TIME_SHIELD_CONSTEXPR inline ts_ms_t start_of_year_ms(ts_ms_t ts_ms = time_shield::ts_ms()) { + return sec_to_ms(start_of_year(ms_to_sec(ts_ms))); + } + + /// \brief Get the end-of-year timestamp. + /// + /// This function finds the last timestamp of the current year. + /// + /// \param ts Timestamp. + /// \return End-of-year timestamp. + TIME_SHIELD_CONSTEXPR inline ts_t end_of_year(ts_t ts = time_shield::ts()) { + const ts_t year_start = start_of_year(ts); + const ts_t year_days = static_cast(num_days_in_year_ts(ts)); + return year_start + year_days * SEC_PER_DAY - 1; + } + + /// \brief Get the timestamp in milliseconds of the end of the year. + /// + /// This function finds the last millisecond of the current year in milliseconds. + /// + /// \param ts_ms Timestamp in milliseconds. + /// \return End-of-year timestamp in milliseconds. + template + TIME_SHIELD_CONSTEXPR inline ts_ms_t end_of_year_ms(ts_ms_t ts_ms = time_shield::ts_ms()) { + return sec_to_ms(end_of_year(ms_to_sec(ts_ms))) + (MS_PER_SEC - 1); + } + + /// \brief Get the day of the year. + /// + /// This function returns the day of the year for the specified timestamp. + /// + /// \param ts Timestamp. + /// \return Day of the year. + template + inline T day_of_year(ts_t ts = time_shield::ts()) { + return static_cast(((ts - start_of_year(ts)) / SEC_PER_DAY) + 1); + } + + /// \brief Get the month of the year. + /// + /// This function returns the month of the year for the specified timestamp. + /// + /// \param ts Timestamp. + /// \return Month of the year. + template + TIME_SHIELD_CONSTEXPR inline T month_of_year(ts_t ts) noexcept { + constexpr int JAN_AND_FEB_DAY_LEAP_YEAR = 60; + constexpr int TABLE_MONTH_OF_YEAR[] = { + 0, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // January (31 days) + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, // February (28 days) + 3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3, // March (31 days) + 4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4, // April (30 days) + 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5, + 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11, + 12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12, + }; + const size_t dy = day_of_year(ts); + return static_cast((is_leap_year(ts) && dy >= JAN_AND_FEB_DAY_LEAP_YEAR) ? TABLE_MONTH_OF_YEAR[dy - 1] : TABLE_MONTH_OF_YEAR[dy]); + } + + /// \brief Get the day of the month. + /// + /// This function returns the day of the month for the specified timestamp. + /// + /// \param ts Timestamp. + /// \return Day of the month. + template + TIME_SHIELD_CONSTEXPR inline T day_of_month(ts_t ts = time_shield::ts()) { + constexpr int JAN_AND_FEB_DAY_LEAP_YEAR = 60; + // Month numbers for a common year. + constexpr int TABLE_DAY_OF_YEAR[] = { + 0, + 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31, // January (31 days) + 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28, // February (28 days) + 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31, // March (31 days) + 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30, // April (30 days) + 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31, + 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30, + 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31, + 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31, + 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30, + 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31, + 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30, + 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31, + }; + const size_t dy = day_of_year(ts); + if(is_leap_year(ts)) { + if(dy == JAN_AND_FEB_DAY_LEAP_YEAR) return TABLE_DAY_OF_YEAR[dy - 1] + 1; + if(dy > JAN_AND_FEB_DAY_LEAP_YEAR) return TABLE_DAY_OF_YEAR[dy - 1]; + } + return TABLE_DAY_OF_YEAR[dy]; + } + + /// \brief Get the number of days in a month. + /// + /// This function calculates and returns the number of days in the specified month and year. + /// + /// \param year Year as an integer. + /// \param month Month as an integer. + /// \return The number of days in the given month and year. + template + TIME_SHIELD_CONSTEXPR T1 num_days_in_month(T2 year, T3 month) noexcept { + constexpr T1 num_days[13] = {0,31,30,31,30,31,30,31,31,30,31,30,31}; + return (month > MONTHS_PER_YEAR || month < 0) + ? static_cast(0) + : (month == FEB ? static_cast(is_leap_year_date(year) ? 29 : 28) : num_days[month]); + } + + /// \brief Get the number of days in the month of the given timestamp. + /// + /// This function calculates and returns the number of days in the month of the specified timestamp. + /// + /// \param ts The timestamp to extract month and year from. + /// \return The number of days in the month of the given timestamp. + template + TIME_SHIELD_CONSTEXPR T1 num_days_in_month_ts(ts_t ts = time_shield::ts()) noexcept { + constexpr T1 num_days[13] = {0,31,28,31,30,31,30,31,31,30,31,30,31}; + const int month = month_of_year(ts); + if (month == FEB) { + return is_leap_year(ts) ? 29 : 28; + } + return num_days[month]; + } + + /// \brief Get the second of the week day from a timestamp. + /// \param ts Timestamp. + /// \return Weekday (SUN = 0, MON = 1, ... SAT = 6). + template + TIME_SHIELD_CONSTEXPR T weekday_of_ts(ts_t ts) noexcept { + const ts_t days = detail::floor_div(ts, SEC_PER_DAY); + return static_cast(detail::floor_mod(days + THU, DAYS_PER_WEEK)); + } + + /// \brief Get the weekday from a timestamp in milliseconds. + /// \param ts_ms Timestamp in milliseconds. + /// \return Weekday (SUN = 0, MON = 1, ... SAT = 6). + template + TIME_SHIELD_CONSTEXPR T weekday_of_ts_ms(ts_ms_t ts_ms) { + return weekday_of_ts(ms_to_sec(ts_ms)); + } + + /// \brief Get the timestamp at the start of the current month. + /// + /// This function returns the timestamp at the start of the current month, + /// setting the day to the first day of the month and the time to 00:00:00. + /// + /// \param ts Timestamp (default is current timestamp) + /// \return Timestamp at the start of the current month + TIME_SHIELD_CONSTEXPR inline ts_t start_of_month(ts_t ts = time_shield::ts()) { + return start_of_day(ts) - (day_of_month(ts) - 1) * SEC_PER_DAY; + } + + /// \brief Get the last timestamp of the current month. + /// + /// This function returns the last timestamp of the current month, + /// setting the day to the last day of the month and the time to 23:59:59. + /// + /// \param ts Timestamp (default is current timestamp) + /// \return Last timestamp of the current month + TIME_SHIELD_CONSTEXPR inline ts_t end_of_month(ts_t ts = time_shield::ts()) { + return end_of_day(ts) + (num_days_in_month_ts(ts) - day_of_month(ts)) * SEC_PER_DAY; + } + + /// \brief Get the timestamp of the last Sunday of the current month. + /// + /// This function returns the timestamp of the last Sunday of the current month, + /// setting the time to 00:00:00. + /// + /// \param ts Timestamp (default is current timestamp) + /// \return Timestamp of the last Sunday of the current month at 00:00:00 + TIME_SHIELD_CONSTEXPR inline ts_t last_sunday_of_month(ts_t ts = time_shield::ts()) { + const ts_t month_end = end_of_month(ts); + return start_of_day(month_end) - weekday_of_ts(month_end) * SEC_PER_DAY; + } + + /// \brief Get the day of the last Sunday of the given month and year. + /// + /// This function returns the day of the last Sunday of the specified month and year. + /// + /// \param year Year + /// \param month Month (1 = January, 12 = December) + /// \return Day of the last Sunday of the given month and year + template + TIME_SHIELD_CONSTEXPR inline T1 last_sunday_month_day(T2 year, T3 month) { + const T1 days = num_days_in_month(year, month); + return days - day_of_week_date(year, month, days); + } + + /// \brief Get the timestamp of the beginning of the week. + /// + /// This function finds the timestamp of the beginning of the week, + /// which corresponds to the start of Sunday. + /// + /// \param ts Timestamp (default: current timestamp). + /// \return Returns the timestamp of the beginning of the week. + TIME_SHIELD_CONSTEXPR inline ts_t start_of_week(ts_t ts = time_shield::ts()) { + return start_of_day(ts) - weekday_of_ts(ts) * SEC_PER_DAY; + } + + /// \brief Get the timestamp of the end of the week. + /// + /// This function finds the timestamp of the end of the week, + /// which corresponds to the end of Saturday. + /// + /// \param ts Timestamp (default: current timestamp). + /// \return Returns the timestamp of the end of the week. + TIME_SHIELD_CONSTEXPR inline ts_t end_of_week(ts_t ts = time_shield::ts()) { + return start_of_day(ts) + (DAYS_PER_WEEK - weekday_of_ts(ts)) * SEC_PER_DAY - 1; + } + + /// \brief Get the timestamp of the start of Saturday. + /// + /// This function finds the timestamp of the beginning of the day on Saturday, + /// which corresponds to the start of Saturday. + /// + /// \param ts Timestamp (default: current timestamp). + /// \return Returns the timestamp of the start of Saturday. + TIME_SHIELD_CONSTEXPR inline ts_t start_of_saturday(ts_t ts = time_shield::ts()) { + return start_of_day(ts) + (SAT - weekday_of_ts(ts)) * SEC_PER_DAY; + } + + + /// \brief Get the timestamp at the start of the hour. + /// + /// This function sets the minute and second to zero. + /// + /// \param ts Timestamp (default: current timestamp). + /// \return Timestamp at the start of the hour. + TIME_SHIELD_CONSTEXPR inline ts_t start_of_hour(ts_t ts = time_shield::ts()) noexcept { + return ts - detail::floor_mod(ts, SEC_PER_HOUR); + } + + /// \brief Get the timestamp at the start of the hour. + /// + /// This function sets the minute and second to zero. + /// + /// \param ts_ms Timestamp in milliseconds (default: current timestamp in milliseconds). + /// \return Timestamp at the start of the hour in seconds. + TIME_SHIELD_CONSTEXPR inline ts_t start_of_hour_sec(ts_ms_t ts_ms = time_shield::ts_ms()) noexcept { + return start_of_hour(ms_to_sec(ts_ms)); + } + + /// \brief Get the timestamp at the start of the hour. + /// This function sets the minute and second to zero. + /// \param ts_ms Timestamp in milliseconds (default: current timestamp in milliseconds). + /// \return Timestamp at the start of the hour in milliseconds. + TIME_SHIELD_CONSTEXPR inline ts_ms_t start_of_hour_ms(ts_ms_t ts_ms = time_shield::ts_ms()) noexcept { + return ts_ms - detail::floor_mod(ts_ms, MS_PER_HOUR); + } + + /// \brief Get the timestamp at the end of the hour. + /// \param ts Timestamp (default: current timestamp). + /// \return Returns the timestamp of the end of the hour. + TIME_SHIELD_CONSTEXPR inline ts_t end_of_hour(ts_t ts = time_shield::ts()) noexcept { + return ts - detail::floor_mod(ts, SEC_PER_HOUR) + SEC_PER_HOUR - 1; + } + + /// \brief Get the timestamp at the end of the hour in seconds. + /// \param ts_ms Timestamp in milliseconds (default: current timestamp). + /// \return Returns the timestamp of the end of the hour in seconds. + TIME_SHIELD_CONSTEXPR inline ts_t end_of_hour_sec(ts_ms_t ts_ms = time_shield::ts_ms()) noexcept { + return end_of_hour(ms_to_sec(ts_ms)); + } + + /// \brief Get the timestamp at the end of the hour in milliseconds. + /// \param ts_ms Timestamp in milliseconds (default: current timestamp). + /// \return Returns the timestamp of the end of the hour in milliseconds. + TIME_SHIELD_CONSTEXPR inline ts_ms_t end_of_hour_ms(ts_ms_t ts_ms = time_shield::ts_ms()) noexcept { + return ts_ms - detail::floor_mod(ts_ms, MS_PER_HOUR) + MS_PER_HOUR - 1; + } + + /// \brief Get the timestamp of the beginning of the minute. + /// \param ts Timestamp (default: current timestamp). + /// \return Returns the timestamp of the beginning of the minute. + TIME_SHIELD_CONSTEXPR inline ts_t start_of_min(ts_t ts = time_shield::ts()) noexcept { + return ts - detail::floor_mod(ts, SEC_PER_MIN); + } + + /// \brief Get the timestamp of the end of the minute. + /// \param ts Timestamp (default: current timestamp). + /// \return Returns the timestamp of the end of the minute. + TIME_SHIELD_CONSTEXPR inline ts_t end_of_min(ts_t ts = time_shield::ts()) noexcept { + return ts - detail::floor_mod(ts, SEC_PER_MIN) + SEC_PER_MIN - 1; + } + + /// \brief Get minute of day. + /// This function returns a value between 0 to 1439 (minute of day). + /// \param ts Timestamp in seconds (default: current timestamp). + /// \return Minute of day. + template + TIME_SHIELD_CONSTEXPR T min_of_day(ts_t ts = time_shield::ts()) noexcept { + const ts_t minutes = detail::floor_div(ts, SEC_PER_MIN); + return static_cast(detail::floor_mod(minutes, MIN_PER_DAY)); + } + + /// \brief Get hour of day. + /// This function returns a value between 0 to 23. + /// \param ts Timestamp in seconds (default: current timestamp). + /// \return Hour of day. + template + TIME_SHIELD_CONSTEXPR T hour_of_day(ts_t ts = time_shield::ts()) noexcept { + const ts_t hours = detail::floor_div(ts, SEC_PER_HOUR); + return static_cast(detail::floor_mod(hours, HOURS_PER_DAY)); + } + + /// \brief Get minute of hour. + /// This function returns a value between 0 to 59. + /// \param ts Timestamp in seconds (default: current timestamp). + /// \return Minute of hour. + template + TIME_SHIELD_CONSTEXPR T min_of_hour(ts_t ts = time_shield::ts()) noexcept { + const ts_t minutes = detail::floor_div(ts, SEC_PER_MIN); + return static_cast(detail::floor_mod(minutes, MIN_PER_HOUR)); + } + + /// \brief Get the timestamp of the start of the period. + /// \param p Positive period duration in seconds. + /// \param ts Timestamp (default: current timestamp). + /// \return Timestamp of the start of the period, or ERROR_TIMESTAMP for invalid period values. + template + TIME_SHIELD_CONSTEXPR ts_t start_of_period(T p, ts_t ts = time_shield::ts()) { + const ts_t period = static_cast(p); + return period <= 0 ? ERROR_TIMESTAMP : ts - detail::floor_mod(ts, period); + } + + /// \brief Get the timestamp of the end of the period. + /// \param p Positive period duration in seconds. + /// \param ts Timestamp (default: current timestamp). + /// \return Timestamp of the end of the period, or ERROR_TIMESTAMP for invalid period values. + template + TIME_SHIELD_CONSTEXPR ts_t end_of_period(T p, ts_t ts = time_shield::ts()) { + const ts_t period = static_cast(p); + return period <= 0 ? ERROR_TIMESTAMP : ts - detail::floor_mod(ts, period) + period - 1; + } + +/// \} + +}; // namespace time_shield + +#endif // TIME_SHIELD_HEADER_CONVERSIONS_DATE_TIME_CONVERSIONS_HPP_INCLUDED diff --git a/include/time_shield/conversions/detail/fast_date.hpp b/include/time_shield/conversions/detail/fast_date.hpp new file mode 100644 index 00000000..ad2507c1 --- /dev/null +++ b/include/time_shield/conversions/detail/fast_date.hpp @@ -0,0 +1,245 @@ +// SPDX-License-Identifier: MIT +#pragma once +#ifndef TIME_SHIELD_HEADER_CONVERSIONS_DETAIL_FAST_DATE_HPP_INCLUDED +#define TIME_SHIELD_HEADER_CONVERSIONS_DETAIL_FAST_DATE_HPP_INCLUDED + +/// \file fast_date.hpp +/// \brief Fast date conversion helpers. + +#include "mul_hi.hpp" + +#include + +namespace time_shield { +namespace detail { + + namespace { + constexpr int16_t k_doy_from_march[12] = { + 0, // Mar + 31, // Apr + 61, // May + 92, // Jun + 122, // Jul + 153, // Aug + 184, // Sep + 214, // Oct + 245, // Nov + 275, // Dec + 306, // Jan + 337 // Feb + }; + } // namespace + + struct DaySplit { + int64_t days; + int64_t sec_of_day; + }; + + /// \brief Split UNIX seconds into whole days and seconds-of-day. + TIME_SHIELD_CONSTEXPR inline DaySplit split_unix_day(ts_t p_ts) noexcept { + int64_t days = p_ts / SEC_PER_DAY; + int64_t sec_of_day = p_ts % SEC_PER_DAY; + if (sec_of_day < 0) { + sec_of_day += SEC_PER_DAY; + days -= 1; + } + return {days, sec_of_day}; + } + + struct FastDate { + int64_t year; + int month; + int day; + }; + + /// \brief Convert date to days since Unix epoch using a fast constexpr algorithm. + /// \note Inspired by the algorithm described in: + /// https://www.benjoffe.com/fast-date-64 + /// This implementation is written from scratch (no code copied). + TIME_SHIELD_CONSTEXPR inline int64_t fast_days_from_date_constexpr( + int64_t p_year, + int p_month, + int p_day) noexcept { + const int month_adjust = (p_month <= 2 ? 1 : 0); + const int64_t y = p_year - month_adjust; + int m = p_month - 3; + if (m < 0) { + m += 12; + } + + if (y >= 0) { + const uint64_t y_u = static_cast(y); + const uint64_t era = y_u / 400U; + const uint64_t yoe = y_u - era * 400U; + const uint64_t doy = static_cast(k_doy_from_march[m]) + static_cast(p_day - 1); + const uint64_t doe = yoe * 365U + yoe / 4U - yoe / 100U + doy; + return static_cast(era * 146097U + doe) - 719468; + } + + const int64_t era = (y - 399) / 400; + const int64_t yoe = y - era * 400; + const int64_t doy = static_cast(k_doy_from_march[m]) + static_cast(p_day - 1); + const int64_t doe = yoe * 365 + yoe / 4 - yoe / 100 + doy; + return era * 146097 + doe - 719468; + } + + /// \brief Convert date to days since Unix epoch using a fast algorithm. + /// \note Inspired by the algorithm described in: + /// https://www.benjoffe.com/fast-date-64 + /// This implementation is written from scratch (no code copied). + inline int64_t fast_days_from_date(int64_t p_year, int p_month, int p_day) noexcept { + const int month_adjust = (p_month <= 2 ? 1 : 0); + const int64_t y = p_year - month_adjust; + int m = p_month - 3; + if (m < 0) { + m += 12; + } + + if (y >= 0) { + const uint64_t y_u = static_cast(y); + const uint64_t era = y_u / 400U; + const uint64_t yoe = y_u - era * 400U; + const uint64_t doy = static_cast(k_doy_from_march[m]) + static_cast(p_day - 1); + const uint64_t doe = yoe * 365U + yoe / 4U - yoe / 100U + doy; + return static_cast(era * 146097U + doe) - 719468; + } + + const int64_t era = (y - 399) / 400; + const int64_t yoe = y - era * 400; + const int64_t doy = static_cast(k_doy_from_march[m]) + static_cast(p_day - 1); + const int64_t doe = yoe * 365 + yoe / 4 - yoe / 100 + doy; + return era * 146097 + doe - 719468; + } + + /// \brief Convert days since Unix epoch to date using a fast constexpr algorithm. + /// \note Inspired by the algorithm described in: + /// https://www.benjoffe.com/fast-date-64 + /// This implementation is written from scratch (no code copied). + TIME_SHIELD_CONSTEXPR inline FastDate fast_date_from_days_constexpr(int64_t p_days) noexcept { + constexpr uint64_t ERAS = 4726498270ULL; + constexpr int64_t D_SHIFT = static_cast(146097ULL * ERAS - 719469ULL); + constexpr int64_t Y_SHIFT = static_cast(400ULL * ERAS - 1ULL); + constexpr uint64_t C1 = 505054698555331ULL; + constexpr uint64_t C2 = 50504432782230121ULL; + constexpr uint64_t C3 = 8619973866219416ULL; + constexpr uint64_t YPT_SCALE = 782432ULL; + constexpr uint64_t YPT_BUMP_THRESHOLD = 126464ULL; + constexpr uint64_t SHIFT_JAN_FEB = 191360ULL; + constexpr uint64_t SHIFT_OTHER = 977792ULL; + + const uint64_t rev = static_cast(D_SHIFT - p_days); + const uint64_t cen = mul_shift_u64_constexpr(rev, C1); + const uint64_t jul = rev + cen - (cen / 4U); + + const uint64_t num_hi = mul_shift_u64_constexpr(jul, C2); + const uint64_t num_low = jul * C2; + const uint64_t yrs = static_cast(Y_SHIFT) - num_hi; + + const uint64_t ypt = mul_shift_u64_constexpr(YPT_SCALE, num_low); + const bool bump = ypt < YPT_BUMP_THRESHOLD; + const uint64_t shift = bump ? SHIFT_JAN_FEB : SHIFT_OTHER; + + const uint64_t N = (yrs & 3ULL) * 512ULL + shift - ypt; + const uint64_t d = mul_shift_u64_constexpr((N & 0xFFFFULL), C3); + + return FastDate{ + static_cast(yrs + (bump ? 1U : 0U)), + static_cast(N >> 16), + static_cast(d + 1U) + }; + } + + /// \brief Convert days since Unix epoch to date using a fast algorithm. + /// \note Inspired by the algorithm described in: + /// https://www.benjoffe.com/fast-date-64 + /// This implementation is written from scratch (no code copied). + inline FastDate fast_date_from_days(int64_t p_days) noexcept { + constexpr uint64_t ERAS = 4726498270ULL; + constexpr int64_t D_SHIFT = static_cast(146097ULL * ERAS - 719469ULL); + constexpr int64_t Y_SHIFT = static_cast(400ULL * ERAS - 1ULL); + constexpr uint64_t C1 = 505054698555331ULL; + constexpr uint64_t C2 = 50504432782230121ULL; + constexpr uint64_t C3 = 8619973866219416ULL; + constexpr uint64_t YPT_SCALE = 782432ULL; + constexpr uint64_t YPT_BUMP_THRESHOLD = 126464ULL; + constexpr uint64_t SHIFT_JAN_FEB = 191360ULL; + constexpr uint64_t SHIFT_OTHER = 977792ULL; + + const uint64_t rev = static_cast(D_SHIFT - p_days); + const uint64_t cen = mul_shift_u64(rev, C1); + const uint64_t jul = rev + cen - (cen / 4U); + + const uint64_t num_hi = mul_shift_u64(jul, C2); + const uint64_t num_low = jul * C2; + const uint64_t yrs = static_cast(Y_SHIFT) - num_hi; + + const uint64_t ypt = mul_shift_u64(YPT_SCALE, num_low); + const bool bump = ypt < YPT_BUMP_THRESHOLD; + const uint64_t shift = bump ? SHIFT_JAN_FEB : SHIFT_OTHER; + + const uint64_t N = (yrs & 3ULL) * 512ULL + shift - ypt; + const uint64_t d = mul_shift_u64((N & 0xFFFFULL), C3); + + FastDate result{}; + result.day = static_cast(d + 1U); + result.month = static_cast(N >> 16); + result.year = static_cast(yrs + (bump ? 1U : 0U)); + return result; + } + + /// \brief Convert days since Unix epoch to year using a fast constexpr algorithm. + /// \note Inspired by the algorithm described in: + /// https://www.benjoffe.com/fast-date-64 + /// This implementation is written from scratch (no code copied). + TIME_SHIELD_CONSTEXPR inline int64_t fast_year_from_days_constexpr(int64_t p_days) noexcept { + constexpr uint64_t ERAS = 4726498270ULL; + constexpr int64_t D_SHIFT = static_cast(146097ULL * ERAS - 719469ULL); + constexpr int64_t Y_SHIFT = static_cast(400ULL * ERAS - 1ULL); + constexpr uint64_t C1 = 505054698555331ULL; + constexpr uint64_t C2 = 50504432782230121ULL; + constexpr uint64_t YPT_SCALE = 782432ULL; + constexpr uint64_t YPT_BUMP_THRESHOLD = 126464ULL; + + const uint64_t rev = static_cast(D_SHIFT - p_days); + const uint64_t cen = mul_shift_u64_constexpr(rev, C1); + const uint64_t jul = rev + cen - (cen / 4U); + + const uint64_t num_hi = mul_shift_u64_constexpr(jul, C2); + const uint64_t num_low = jul * C2; + const uint64_t yrs = static_cast(Y_SHIFT) - num_hi; + + const uint64_t ypt = mul_shift_u64_constexpr(YPT_SCALE, num_low); + const bool bump = ypt < YPT_BUMP_THRESHOLD; + return static_cast(yrs + (bump ? 1U : 0U)); + } + + /// \brief Convert days since Unix epoch to year using a fast algorithm. + /// \note Inspired by the algorithm described in: + /// https://www.benjoffe.com/fast-date-64 + /// This implementation is written from scratch (no code copied). + inline int64_t fast_year_from_days(int64_t p_days) noexcept { + constexpr uint64_t ERAS = 4726498270ULL; + constexpr int64_t D_SHIFT = static_cast(146097ULL * ERAS - 719469ULL); + constexpr int64_t Y_SHIFT = static_cast(400ULL * ERAS - 1ULL); + constexpr uint64_t C1 = 505054698555331ULL; + constexpr uint64_t C2 = 50504432782230121ULL; + constexpr uint64_t YPT_SCALE = 782432ULL; + constexpr uint64_t YPT_BUMP_THRESHOLD = 126464ULL; + + const uint64_t rev = static_cast(D_SHIFT - p_days); + const uint64_t cen = mul_shift_u64(rev, C1); + const uint64_t jul = rev + cen - (cen / 4U); + + const uint64_t num_hi = mul_shift_u64(jul, C2); + const uint64_t num_low = jul * C2; + const uint64_t yrs = static_cast(Y_SHIFT) - num_hi; + + const uint64_t ypt = mul_shift_u64(YPT_SCALE, num_low); + const bool bump = ypt < YPT_BUMP_THRESHOLD; + return static_cast(yrs + (bump ? 1U : 0U)); + } + +} // namespace detail +} // namespace time_shield + +#endif // TIME_SHIELD_HEADER_CONVERSIONS_DETAIL_FAST_DATE_HPP_INCLUDED diff --git a/include/time_shield/conversions/detail/floor_math.hpp b/include/time_shield/conversions/detail/floor_math.hpp new file mode 100644 index 00000000..079c737f --- /dev/null +++ b/include/time_shield/conversions/detail/floor_math.hpp @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT +#pragma once +#ifndef TIME_SHIELD_HEADER_CONVERSIONS_DETAIL_FLOOR_MATH_HPP_INCLUDED +#define TIME_SHIELD_HEADER_CONVERSIONS_DETAIL_FLOOR_MATH_HPP_INCLUDED + +/// \file floor_math.hpp +/// \brief Floor division and modulus helpers. + +namespace time_shield { +namespace detail { + + /// \brief Floor division for positive divisor. + template + TIME_SHIELD_CONSTEXPR inline T floor_div(T a, T b) noexcept { + return static_cast((a / b) - (((a % b) != 0 && a < 0) ? 1 : 0)); + } + + /// \brief Floor-mod for positive modulus (returns r in [0..b)). + template + TIME_SHIELD_CONSTEXPR inline T floor_mod(T a, T b) noexcept { + return static_cast((a % b) + (((a % b) < 0) ? b : 0)); + } + +} // namespace detail +} // namespace time_shield + +#endif // TIME_SHIELD_HEADER_CONVERSIONS_DETAIL_FLOOR_MATH_HPP_INCLUDED diff --git a/include/time_shield/conversions/detail/mul_hi.hpp b/include/time_shield/conversions/detail/mul_hi.hpp new file mode 100644 index 00000000..d33c0782 --- /dev/null +++ b/include/time_shield/conversions/detail/mul_hi.hpp @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: MIT +#pragma once +#ifndef TIME_SHIELD_HEADER_CONVERSIONS_DETAIL_MUL_HI_HPP_INCLUDED +#define TIME_SHIELD_HEADER_CONVERSIONS_DETAIL_MUL_HI_HPP_INCLUDED + +/// \file mul_hi.hpp +/// \brief Helpers for 64-bit multiply-high operations. + +#include + +#if defined(_MSC_VER) +# include +#endif + +namespace time_shield { +namespace detail { + + /// \brief Return the high 64 bits of a 64x64-bit multiplication (constexpr variant). + TIME_SHIELD_CONSTEXPR inline uint64_t mul_hi_u64_constexpr(uint64_t p_a, uint64_t p_b) noexcept { + const uint64_t a_low = p_a & 0xFFFFFFFFULL; + const uint64_t a_high = p_a >> 32; + const uint64_t b_low = p_b & 0xFFFFFFFFULL; + const uint64_t b_high = p_b >> 32; + + const uint64_t p0 = a_low * b_low; + const uint64_t p1 = a_low * b_high; + const uint64_t p2 = a_high * b_low; + const uint64_t p3 = a_high * b_high; + + const uint64_t carry = ((p0 >> 32) + (p1 & 0xFFFFFFFFULL) + (p2 & 0xFFFFFFFFULL)) >> 32; + return p3 + (p1 >> 32) + (p2 >> 32) + carry; + } + + /// \brief Return the high 64 bits of a 64x64-bit multiplication. + inline uint64_t mul_hi_u64(uint64_t p_a, uint64_t p_b) noexcept { +#if defined(_MSC_VER) && (defined(_M_X64) || defined(_M_ARM64)) + uint64_t high = 0; + (void)_umul128(p_a, p_b, &high); + return high; +#else + const __uint128_t product = static_cast<__uint128_t>(p_a) * static_cast<__uint128_t>(p_b); + return static_cast(product >> 64); +#endif + } + + /// \brief Alias for mul_hi_u64 used for shift-by-64 operations. + inline uint64_t mul_shift_u64(uint64_t p_x, uint64_t p_c) noexcept { + return mul_hi_u64(p_x, p_c); + } + + /// \brief Alias for mul_hi_u64_constexpr used for shift-by-64 operations. + TIME_SHIELD_CONSTEXPR inline uint64_t mul_shift_u64_constexpr(uint64_t p_x, uint64_t p_c) noexcept { + return mul_hi_u64_constexpr(p_x, p_c); + } + +} // namespace detail +} // namespace time_shield + +#endif // TIME_SHIELD_HEADER_CONVERSIONS_DETAIL_MUL_HI_HPP_INCLUDED diff --git a/include/time_shield/conversions/iso_week_conversions.hpp b/include/time_shield/conversions/iso_week_conversions.hpp new file mode 100644 index 00000000..43989389 --- /dev/null +++ b/include/time_shield/conversions/iso_week_conversions.hpp @@ -0,0 +1,263 @@ +// SPDX-License-Identifier: MIT +#pragma once +#ifndef TIME_SHIELD_HEADER_CONVERSIONS_ISO_WEEK_CONVERSIONS_HPP_INCLUDED +#define TIME_SHIELD_HEADER_CONVERSIONS_ISO_WEEK_CONVERSIONS_HPP_INCLUDED + +/// \file iso_week_conversions.hpp +/// \brief Conversions and utilities for ISO week dates (ISO 8601). +/// +/// This file provides helpers to convert between calendar dates, timestamps, and ISO week dates, +/// as well as formatting and parsing helpers for ISO week-date strings. + +#include +#include "date_conversions.hpp" +#include "date_time_conversions.hpp" +#include "time_unit_conversions.hpp" + +#include +#include +#include +#include +#include +#include + +namespace time_shield { + +/// \ingroup time_conversions +/// \{ + + /// \brief Convert Weekday enum to ISO weekday (Mon=1 .. Sun=7). + /// \param weekday Weekday enum value. + /// \return ISO weekday number. + TIME_SHIELD_CONSTEXPR inline int iso_weekday_from_weekday(Weekday weekday) noexcept { + return static_cast((static_cast(weekday) + DAYS_PER_WEEK - 1) % DAYS_PER_WEEK) + 1; + } + + /// \brief Get ISO weekday for a calendar date. + /// \param year Year component. + /// \param month Month component. + /// \param day Day component. + /// \return ISO weekday number (1=Monday .. 7=Sunday). + template + TIME_SHIELD_CONSTEXPR inline int iso_weekday_of_date(Y year, M month, D day) { + return iso_weekday_from_weekday(day_of_week_date(year, month, day)); + } + + /// \brief Convert calendar date to ISO week date. + /// \param year Year component. + /// \param month Month component. + /// \param day Day component. + /// \return ISO week date representation. + template + inline IsoWeekDateStruct to_iso_week_date(Y year, M month, D day) { + const int iso_weekday = iso_weekday_of_date(year, month, day); + const dse_t unix_day = date_to_unix_day(year, month, day); + const dse_t thursday_day = unix_day + static_cast(4 - iso_weekday); + + const DateTimeStruct thursday_date = to_date_time(unix_day_to_ts(thursday_day)); + const year_t iso_year = thursday_date.year; + + const dse_t jan4_day = date_to_unix_day(iso_year, 1, 4); + const int jan4_iso_weekday = iso_weekday_of_date(iso_year, 1, 4); + const dse_t first_thursday = jan4_day + static_cast(4 - jan4_iso_weekday); + + const int32_t week = static_cast((thursday_day - first_thursday) / DAYS_PER_WEEK + 1); + return create_iso_week_date_struct(iso_year, week, static_cast(iso_weekday)); + } + + /// \brief Convert DateStruct to ISO week date. + /// \param date DateStruct instance. + /// \return ISO week date representation. + inline IsoWeekDateStruct to_iso_week_date(const DateStruct& date) { + return to_iso_week_date(date.year, date.mon, date.day); + } + + /// \brief Convert timestamp to ISO week date. + /// \tparam T Timestamp type. + /// \param ts Timestamp in seconds. + /// \return ISO week date representation. + template + inline IsoWeekDateStruct to_iso_week_date(T ts) { + const DateTimeStruct date_time = to_date_time(ts); + return to_iso_week_date(date_time.year, date_time.mon, date_time.day); + } + + /// \brief Calculate number of ISO weeks in a year. + /// \param iso_year ISO week-numbering year. + /// \return 52 or 53 depending on the ISO year length. + inline int iso_weeks_in_year(year_t iso_year) { + const IsoWeekDateStruct info = to_iso_week_date(iso_year, 12, 28); + return static_cast(info.week); + } + + /// \brief Validate ISO week date components. + /// \param iso_year ISO week-numbering year. + /// \param week ISO week number. + /// \param weekday ISO weekday (1-7). + /// \return True if components form a valid ISO week date. + inline bool is_valid_iso_week_date(year_t iso_year, int week, int weekday) { + if (iso_year < MIN_YEAR) return false; + if (iso_year > MAX_YEAR) return false; + if (weekday < 1 || weekday > 7) return false; + if (week < 1) return false; + const int max_week = iso_weeks_in_year(iso_year); + return week <= max_week; + } + + /// \brief Convert ISO week date to calendar date. + /// \param iso_date ISO week date structure. + /// \return Calendar date corresponding to the ISO week date. + /// \throws std::invalid_argument if the ISO week date is invalid. + inline DateStruct iso_week_date_to_date(const IsoWeekDateStruct& iso_date) { + if (!is_valid_iso_week_date(iso_date.year, iso_date.week, iso_date.weekday)) { + throw std::invalid_argument("Invalid ISO week date"); + } + + const dse_t jan4_day = date_to_unix_day(iso_date.year, 1, 4); + const int jan4_iso_weekday = iso_weekday_of_date(iso_date.year, 1, 4); + const dse_t first_thursday = jan4_day + static_cast(4 - jan4_iso_weekday); + const dse_t target_thursday = first_thursday + static_cast(iso_date.week - 1) * DAYS_PER_WEEK; + const dse_t target_day = target_thursday + static_cast(iso_date.weekday - 4); + + const DateTimeStruct date_time = to_date_time(unix_day_to_ts(target_day)); + return create_date_struct(date_time.year, date_time.mon, date_time.day); + } + + /// \brief Format ISO week date to string. + /// \param iso_date ISO week date to format. + /// \param extended When true, uses extended format with separators ("YYYY-Www-D"). + /// \param include_weekday When false, omits weekday ("YYYY-Www") and ignores the weekday field. + /// \return Formatted ISO week-date string. + inline std::string format_iso_week_date(const IsoWeekDateStruct& iso_date, bool extended = true, bool include_weekday = true) { + const bool has_valid_year = iso_date.year >= MIN_YEAR && iso_date.year <= MAX_YEAR; + const bool has_valid_week = has_valid_year && iso_date.week >= 1 && iso_date.week <= iso_weeks_in_year(iso_date.year); + const bool has_valid_weekday = iso_date.weekday >= 1 && iso_date.weekday <= 7; + if (!has_valid_year || !has_valid_week || (include_weekday && !has_valid_weekday)) { + throw std::invalid_argument("Invalid ISO week date"); + } + + if (!include_weekday) { + const char* fmt = extended ? "%" PRId64 "-W%.2d" : "%" PRId64 "W%.2d"; + char buffer[32] = {0}; + std::snprintf(buffer, sizeof(buffer), fmt, iso_date.year, iso_date.week); + return std::string(buffer); + } + + const char* fmt = extended ? "%" PRId64 "-W%.2d-%d" : "%" PRId64 "W%.2d%d"; + char buffer[32] = {0}; + std::snprintf(buffer, sizeof(buffer), fmt, iso_date.year, iso_date.week, iso_date.weekday); + return std::string(buffer); + } + + /// \brief Parse ISO week date string buffer. + /// \param input Pointer to character buffer (may be not null-terminated). + /// \param length Length of the buffer. + /// \param iso_date Output ISO week date structure. + /// \return True if parsing succeeded and produced a valid ISO week date; otherwise false. + /// \details Accepted forms include canonical `YYYY-Www-D` and `YYYYWwwD`, + /// compatible mixed separator variants `YYYY-WwwD` and `YYYYWww-D`, + /// uppercase or lowercase `W`, and omitted weekday with Monday default. + inline bool parse_iso_week_date(const char* input, std::size_t length, IsoWeekDateStruct& iso_date) noexcept { + if (input == nullptr) { + return false; + } + + iso_date = create_iso_week_date_struct(0, 0, 0); + + const char* p = input; + const char* const end = input + length; + + bool negative = false; + if (p < end && (*p == '+' || *p == '-')) { + negative = (*p == '-'); + ++p; + } + + const char* start_digits = p; + int64_t value = 0; + while (p < end && std::isdigit(static_cast(*p)) != 0) { + value = value * 10 + static_cast(*p - '0'); + ++p; + } + + if (p == start_digits) return false; + iso_date.year = negative ? -value : value; + + if (p >= end) return false; + + const bool has_dash_after_year = (*p == '-'); + if (has_dash_after_year) { + ++p; + if (p >= end) return false; + } + + if (*p != 'W' && *p != 'w') return false; + ++p; + + int week = 0; + for (int i = 0; i < 2; ++i) { + if (p >= end || std::isdigit(static_cast(*p)) == 0) return false; + week = week * 10 + (*p - '0'); + ++p; + } + + if (week == 0) return false; + + bool has_weekday = false; + if (p < end) { + if ((*p == '-' && has_dash_after_year) || (!has_dash_after_year && std::isdigit(static_cast(*p)) == 0)) { + if (*p == '-') ++p; + if (p >= end) return false; + if (std::isdigit(static_cast(*p)) == 0) return false; + iso_date.weekday = *p - '0'; + ++p; + has_weekday = true; + } else if (std::isdigit(static_cast(*p)) != 0) { + iso_date.weekday = *p - '0'; + ++p; + has_weekday = true; + } + } + + if (!has_weekday) { + iso_date.weekday = 1; + } + + iso_date.week = week; + + if (p != end) return false; + return is_valid_iso_week_date(iso_date.year, iso_date.week, iso_date.weekday); + } + + /// \brief Parse ISO week date string. + /// \param input Input string containing ISO week date. + /// \param iso_date Output ISO week date structure. + /// \return True if parsing succeeded and produced a valid ISO week date; otherwise false. + /// \details Parser accepts canonical and compatible mixed separator variants, + /// uppercase or lowercase `W`, and Monday default when weekday is omitted. + inline bool parse_iso_week_date(const std::string& input, IsoWeekDateStruct& iso_date) noexcept { + return parse_iso_week_date(input.c_str(), input.size(), iso_date); + } + + /// \brief Alias for parse_iso_week_date. + /// \param input Pointer to character buffer (may be not null-terminated). + /// \param length Length of the buffer. + /// \param iso_date Output ISO week date structure. + /// \return True if parsing succeeded and produced a valid ISO week date; otherwise false. + inline bool try_parse_iso_week_date(const char* input, std::size_t length, IsoWeekDateStruct& iso_date) noexcept { + return parse_iso_week_date(input, length, iso_date); + } + + /// \brief Alias for parse_iso_week_date, std::string overload. + /// \param input Input string containing ISO week date. + /// \param iso_date Output ISO week date structure. + /// \return True if parsing succeeded and produced a valid ISO week date; otherwise false. + inline bool try_parse_iso_week_date(const std::string& input, IsoWeekDateStruct& iso_date) noexcept { + return parse_iso_week_date(input, iso_date); + } + +/// \} + +}; // namespace time_shield + +#endif // TIME_SHIELD_HEADER_CONVERSIONS_ISO_WEEK_CONVERSIONS_HPP_INCLUDED diff --git a/include/time_shield/conversions/legacy_aliases.hpp b/include/time_shield/conversions/legacy_aliases.hpp new file mode 100644 index 00000000..29ffde06 --- /dev/null +++ b/include/time_shield/conversions/legacy_aliases.hpp @@ -0,0 +1,136 @@ +// SPDX-License-Identifier: MIT +#pragma once +#ifndef TIME_SHIELD_HEADER_CONVERSIONS_LEGACY_ALIASES_HPP_INCLUDED +#define TIME_SHIELD_HEADER_CONVERSIONS_LEGACY_ALIASES_HPP_INCLUDED + +/// \file legacy_aliases.hpp +/// \brief Opt-in compatibility aliases for renamed time-conversion helpers. +/// +/// Define `TIME_SHIELD_ENABLE_LEGACY_ALIASES` before including this header or +/// `time_conversions.hpp` to make the aliases available. + +#include + +#include + +#if defined(TIME_SHIELD_ENABLE_LEGACY_ALIASES) + +namespace time_shield { + +/// \ingroup time_conversions +/// \{ + + /// \brief Legacy alias for years_since_epoch. + /// \copydoc years_since_epoch + template + TIME_SHIELD_CONSTEXPR T get_unix_year(ts_t ts) noexcept { + return years_since_epoch(ts); + } + + /// \brief Legacy alias for days_since_epoch. + /// \copydoc days_since_epoch + template + TIME_SHIELD_CONSTEXPR T get_unix_day(ts_t ts = time_shield::ts()) noexcept { + return days_since_epoch(ts); + } + + /// \brief Legacy alias for days_since_epoch_ms. + /// \copydoc days_since_epoch_ms + template + TIME_SHIELD_CONSTEXPR T get_unix_day_ms(ts_ms_t ts_ms = time_shield::ts_ms()) noexcept { + return days_since_epoch_ms(ts_ms); + } + + /// \brief Legacy alias for unix_day_to_ts. + /// \copydoc unix_day_to_ts + template + TIME_SHIELD_CONSTEXPR T unix_day_to_timestamp(dse_t unix_day) noexcept { + return unix_day_to_ts(unix_day); + } + + /// \brief Legacy alias for unix_day_to_ts_ms. + /// \copydoc unix_day_to_ts_ms + template + TIME_SHIELD_CONSTEXPR T unix_day_to_timestamp_ms(dse_t unix_day) noexcept { + return unix_day_to_ts_ms(unix_day); + } + + /// \brief Legacy alias for min_since_epoch. + /// \copydoc min_since_epoch + template + TIME_SHIELD_CONSTEXPR T get_unix_min(ts_t ts = time_shield::ts()) { + return min_since_epoch(ts); + } + + /// \brief Legacy alias for year_of. + /// \copydoc year_of + template + TIME_SHIELD_CONSTEXPR T get_year(ts_t ts = time_shield::ts()) { + return year_of(ts); + } + + /// \brief Legacy alias for year_of_ms. + /// \copydoc year_of_ms + template + TIME_SHIELD_CONSTEXPR T get_year_ms(ts_ms_t ts_ms = time_shield::ts_ms()) { + return year_of_ms(ts_ms); + } + + /// \brief Legacy alias for weekday_of_date. + /// \copydoc weekday_of_date + template::value, int>::type = 0> + TIME_SHIELD_CONSTEXPR T1 get_weekday_from_date(const T2& date) { + return weekday_of_date(date); + } + + /// \brief Legacy alias for weekday_of_ts. + /// \copydoc weekday_of_ts + template::value, int>::type = 0> + TIME_SHIELD_CONSTEXPR T get_weekday_from_ts(U ts) noexcept { + return weekday_of_ts(ts); + } + + /// \brief Legacy alias for weekday_of_ts_ms. + /// \copydoc weekday_of_ts_ms + template + TIME_SHIELD_CONSTEXPR T get_weekday_from_ts_ms(ts_ms_t ts_ms) { + return weekday_of_ts_ms(ts_ms); + } + + /// \brief Legacy alias for start_of_next_day_from_unix_day. + /// \copydoc start_of_next_day_from_unix_day + template + TIME_SHIELD_CONSTEXPR T next_day_unix_day(dse_t unix_day) noexcept { + return start_of_next_day_from_unix_day(unix_day); + } + + /// \brief Legacy alias for start_of_next_day_from_unix_day. + /// \copydoc start_of_next_day_from_unix_day + template + TIME_SHIELD_CONSTEXPR T next_day_unixday(dse_t unix_day) noexcept { + return start_of_next_day_from_unix_day(unix_day); + } + + /// \brief Legacy alias for start_of_next_day_from_unix_day_ms. + /// \copydoc start_of_next_day_from_unix_day_ms + template + TIME_SHIELD_CONSTEXPR T next_day_unix_day_ms(dse_t unix_day) noexcept { + return start_of_next_day_from_unix_day_ms(unix_day); + } + + /// \brief Legacy alias for start_of_next_day_from_unix_day_ms. + /// \copydoc start_of_next_day_from_unix_day_ms + template + TIME_SHIELD_CONSTEXPR T next_day_unixday_ms(dse_t unix_day) noexcept { + return start_of_next_day_from_unix_day_ms(unix_day); + } + +/// \} + +} // namespace time_shield + +#endif // defined(TIME_SHIELD_ENABLE_LEGACY_ALIASES) + +#endif // TIME_SHIELD_HEADER_CONVERSIONS_LEGACY_ALIASES_HPP_INCLUDED diff --git a/include/time_shield/conversions/ole_automation_conversions.hpp b/include/time_shield/conversions/ole_automation_conversions.hpp new file mode 100644 index 00000000..e8513aa3 --- /dev/null +++ b/include/time_shield/conversions/ole_automation_conversions.hpp @@ -0,0 +1,157 @@ +// SPDX-License-Identifier: MIT +#pragma once +#ifndef TIME_SHIELD_HEADER_CONVERSIONS_OLE_AUTOMATION_CONVERSIONS_HPP_INCLUDED +#define TIME_SHIELD_HEADER_CONVERSIONS_OLE_AUTOMATION_CONVERSIONS_HPP_INCLUDED + +/// \file ole_automation_conversions.hpp +/// \brief OLE Automation Date (OA date) conversions. +/// \ingroup time_conversions +/// +/// OA date is a floating-point day count where: +/// - 0.0 is 1899-12-30 00:00:00 +/// - the integer part is days offset from that date +/// - the fractional part is time-of-day / 24 +/// - negative fractional values follow Excel/COM serial semantics +/// +/// This header provides conversions between OA date and: +/// - Unix timestamps in seconds (ts_t) +/// - Unix timestamps in milliseconds (ts_ms_t) +/// - floating seconds (fts_t) + +#include +#include "date_time_conversions.hpp" + +#include +#include + +namespace time_shield { + + namespace detail { + + TIME_SHIELD_CONSTEXPR inline bool oadate_can_cast_to_i64(oadate_t value) noexcept { + return value >= static_cast((std::numeric_limits::min)()) + && value <= static_cast((std::numeric_limits::max)()); + } + + TIME_SHIELD_CONSTEXPR inline oadate_t oadate_abs(oadate_t value) noexcept { + return value < 0 ? -value : value; + } + + TIME_SHIELD_CONSTEXPR inline oadate_t oadate_trunc_toward_zero(oadate_t value) noexcept { + if (!oadate_can_cast_to_i64(value)) { + return value; + } + return static_cast(static_cast(value)); + } + + TIME_SHIELD_CONSTEXPR inline oadate_t oadate_floor_value(oadate_t value) noexcept { + const oadate_t truncated = oadate_trunc_toward_zero(value); + if (truncated == value) { + return truncated; + } + if (value < 0) { + return truncated - static_cast(1.0); + } + return truncated; + } + + TIME_SHIELD_CONSTEXPR inline bool oadate_has_fraction(oadate_t value) noexcept { + if (!oadate_can_cast_to_i64(value)) { + return false; + } + return oadate_trunc_toward_zero(value) != value; + } + + TIME_SHIELD_CONSTEXPR inline oadate_t linear_days_to_oadate(oadate_t linear_days) noexcept { + if (linear_days < 0 && oadate_has_fraction(linear_days)) { + const oadate_t whole_days = oadate_floor_value(linear_days); + const oadate_t fraction = linear_days - whole_days; + return whole_days - fraction; + } + return linear_days; + } + + TIME_SHIELD_CONSTEXPR inline oadate_t oadate_to_linear_days(oadate_t oa) noexcept { + if (oa < 0 && oadate_has_fraction(oa)) { + const oadate_t whole_days = oadate_trunc_toward_zero(oa); + const oadate_t fraction = oadate_abs(oa - whole_days); + return whole_days + fraction; + } + return oa; + } + + } // namespace detail + + /// \brief Convert Unix timestamp (seconds) to OA date. + /// \param ts Unix timestamp in seconds (may be negative). + /// \return OA date value. + TIME_SHIELD_CONSTEXPR inline oadate_t ts_to_oadate(ts_t ts) noexcept { + const oadate_t linear_days = static_cast(OLE_EPOCH) + + static_cast(ts) / static_cast(SEC_PER_DAY); + return detail::linear_days_to_oadate(linear_days); + } + + /// \brief Convert Unix timestamp (floating seconds) to OA date. + /// \param ts Unix timestamp in seconds as floating point (may be negative). + /// \return OA date value. + TIME_SHIELD_CONSTEXPR inline oadate_t fts_to_oadate(fts_t ts) noexcept { + const oadate_t linear_days = static_cast(OLE_EPOCH) + + static_cast(ts) / static_cast(SEC_PER_DAY); + return detail::linear_days_to_oadate(linear_days); + } + + /// \brief Convert Unix timestamp (milliseconds) to OA date. + /// \param ts_ms Unix timestamp in milliseconds (may be negative). + /// \return OA date value. + TIME_SHIELD_CONSTEXPR inline oadate_t ts_ms_to_oadate(ts_ms_t ts_ms) noexcept { + const oadate_t linear_days = static_cast(OLE_EPOCH) + + static_cast(ts_ms) / static_cast(MS_PER_DAY); + return detail::linear_days_to_oadate(linear_days); + } + + /// \brief Convert OA date to Unix timestamp (seconds). + /// \param oa OA date value. + /// \return Unix timestamp in seconds (truncated toward zero). + TIME_SHIELD_CONSTEXPR inline ts_t oadate_to_ts(oadate_t oa) noexcept { + const oadate_t linear_days = detail::oadate_to_linear_days(oa); + const oadate_t seconds = (linear_days - static_cast(OLE_EPOCH)) + * static_cast(SEC_PER_DAY); + return static_cast(seconds); + } + + /// \brief Convert OA date to Unix timestamp (floating seconds). + /// \param oa OA date value. + /// \return Unix timestamp in seconds as floating point. + TIME_SHIELD_CONSTEXPR inline fts_t oadate_to_fts(oadate_t oa) noexcept { + const oadate_t linear_days = detail::oadate_to_linear_days(oa); + return static_cast((linear_days - static_cast(OLE_EPOCH)) + * static_cast(SEC_PER_DAY)); + } + + /// \brief Convert OA date to Unix timestamp (milliseconds). + /// \param oa OA date value. + /// \return Unix timestamp in milliseconds (truncated toward zero). + TIME_SHIELD_CONSTEXPR inline ts_ms_t oadate_to_ts_ms(oadate_t oa) noexcept { + const oadate_t linear_days = detail::oadate_to_linear_days(oa); + const oadate_t ms = (linear_days - static_cast(OLE_EPOCH)) + * static_cast(MS_PER_DAY); + return static_cast(ms); + } + + /// \brief Build OA date from calendar components (Gregorian). + /// \tparam T1 Year type. + /// \tparam T2 Month/day/time components type. + /// \tparam T3 Milliseconds type. + /// \return OA date value. + template + TIME_SHIELD_CONSTEXPR inline oadate_t to_oadate( + T1 year, T2 month, T2 day, + T2 hour = 0, T2 min = 0, T2 sec = 0, T3 ms = 0) noexcept { + // Use existing conversion to floating timestamp (seconds). + const fts_t fts = to_ftimestamp(year, month, day, hour, min, sec, ms); + return fts_to_oadate(fts); + } + +} // namespace time_shield + +#endif // TIME_SHIELD_HEADER_CONVERSIONS_OLE_AUTOMATION_CONVERSIONS_HPP_INCLUDED diff --git a/include/time_shield/conversions/time_conversion_aliases.hpp b/include/time_shield/conversions/time_conversion_aliases.hpp new file mode 100644 index 00000000..d49923b3 --- /dev/null +++ b/include/time_shield/conversions/time_conversion_aliases.hpp @@ -0,0 +1,2118 @@ +// SPDX-License-Identifier: MIT +#pragma once +#ifndef TIME_SHIELD_HEADER_CONVERSIONS_TIME_CONVERSION_ALIASES_HPP_INCLUDED +#define TIME_SHIELD_HEADER_CONVERSIONS_TIME_CONVERSION_ALIASES_HPP_INCLUDED + +/// \file time_conversion_aliases.hpp +/// \brief Convenience aliases for the time-conversion API. +/// +/// Definitions provide alternative names for commonly used conversion helpers. +/// Doxygen sees the declarations directly and can index each alias independently. +/// Include this header after the canonical conversion declarations. + +#include + +namespace time_shield { + +/// \ingroup time_conversions +/// \{ + + /// \brief Alias for years_since_epoch function. + /// \copydoc years_since_epoch + template + TIME_SHIELD_CONSTEXPR T unix_year(ts_t ts) noexcept { + return years_since_epoch(ts); + } + + /// \brief Alias for years_since_epoch function. + /// \copydoc years_since_epoch + template + TIME_SHIELD_CONSTEXPR T to_unix_year(ts_t ts) noexcept { + return years_since_epoch(ts); + } + +//------------------------------------------------------------------------------ + + + /// \brief Alias for days_since_epoch function. + /// \copydoc days_since_epoch + template + TIME_SHIELD_CONSTEXPR T get_unixday(ts_t ts = time_shield::ts()) noexcept { + return days_since_epoch(ts); + } + + + /// \brief Alias for days_since_epoch function. + /// \copydoc days_since_epoch + template + TIME_SHIELD_CONSTEXPR T unix_day(ts_t ts = time_shield::ts()) noexcept { + return days_since_epoch(ts); + } + + + /// \brief Short alias for days_since_epoch. + /// \copydoc days_since_epoch + template + TIME_SHIELD_CONSTEXPR T dse(ts_t ts = time_shield::ts()) noexcept { + return days_since_epoch(ts); + } + + + /// \brief Alias for days_since_epoch function. + /// \copydoc days_since_epoch + template + TIME_SHIELD_CONSTEXPR T unixday(ts_t ts = time_shield::ts()) noexcept { + return days_since_epoch(ts); + } + + + /// \brief Alias for days_since_epoch function. + /// \copydoc days_since_epoch + template + TIME_SHIELD_CONSTEXPR T uday(ts_t ts = time_shield::ts()) noexcept { + return days_since_epoch(ts); + } + +//------------------------------------------------------------------------------ + + + /// \brief Alias for days_since_epoch function. + /// \copydoc days_since_epoch + template + TIME_SHIELD_CONSTEXPR T get_unixday_ms(ts_ms_t t_ms = time_shield::ts_ms()) noexcept { + return days_since_epoch_ms(t_ms); + } + + + /// \brief Alias for days_since_epoch function. + /// \copydoc days_since_epoch + template + TIME_SHIELD_CONSTEXPR T unix_day_ms(ts_ms_t t_ms = time_shield::ts_ms()) noexcept { + return days_since_epoch_ms(t_ms); + } + + + /// \brief Short alias for days_since_epoch_ms. + /// \copydoc days_since_epoch_ms + template + TIME_SHIELD_CONSTEXPR T dse_ms(ts_ms_t t_ms = time_shield::ts_ms()) noexcept { + return days_since_epoch_ms(t_ms); + } + + + /// \brief Alias for days_since_epoch function. + /// \copydoc days_since_epoch + template + TIME_SHIELD_CONSTEXPR T unixday_ms(ts_ms_t t_ms = time_shield::ts_ms()) noexcept { + return days_since_epoch_ms(t_ms); + } + + + /// \brief Alias for days_since_epoch function. + /// \copydoc days_since_epoch + template + TIME_SHIELD_CONSTEXPR T uday_ms(ts_ms_t t_ms = time_shield::ts_ms()) noexcept { + return days_since_epoch_ms(t_ms); + } + +//------------------------------------------------------------------------------ + + /// \brief Alias for unix_day_to_ts function. + /// \copydoc unix_day_to_ts + template + TIME_SHIELD_CONSTEXPR T unixday_to_ts(dse_t unix_day) noexcept { + return unix_day_to_ts(unix_day); + } + + /// \brief Short alias for unix_day_to_ts. + /// \copydoc unix_day_to_ts + template + TIME_SHIELD_CONSTEXPR T dse_to_ts(dse_t unix_day) noexcept { + return unix_day_to_ts(unix_day); + } + + /// \brief Alias for unix_day_to_ts function. + /// \copydoc unix_day_to_ts + template + TIME_SHIELD_CONSTEXPR T uday_to_ts(dse_t unix_day) noexcept { + return unix_day_to_ts(unix_day); + } + + /// \brief Alias for unix_day_to_ts function. + /// \copydoc unix_day_to_ts + template + TIME_SHIELD_CONSTEXPR T start_of_day_from_unix_day(dse_t unix_day) noexcept { + return unix_day_to_ts(unix_day); + } + +//------------------------------------------------------------------------------ + + /// \brief Alias for unix_day_to_ts_ms function. + /// \copydoc unix_day_to_ts_ms + template + TIME_SHIELD_CONSTEXPR T unixday_to_ts_ms(dse_t unix_day) noexcept { + return unix_day_to_ts_ms(unix_day); + } + + /// \brief Short alias for unix_day_to_ts_ms. + /// \copydoc unix_day_to_ts_ms + template + TIME_SHIELD_CONSTEXPR T dse_to_ts_ms(dse_t unix_day) noexcept { + return unix_day_to_ts_ms(unix_day); + } + + /// \brief Alias for unix_day_to_ts_ms function. + /// \copydoc unix_day_to_ts_ms + template + TIME_SHIELD_CONSTEXPR T uday_to_ts_ms(dse_t unix_day) noexcept { + return unix_day_to_ts_ms(unix_day); + } + + /// \brief Alias for unix_day_to_ts_ms function. + /// \copydoc unix_day_to_ts_ms + template + TIME_SHIELD_CONSTEXPR T start_of_day_from_unix_day_ms(dse_t unix_day) noexcept { + return unix_day_to_ts_ms(unix_day); + } + +//------------------------------------------------------------------------------ + + /// \brief Alias for start_of_next_day_from_unix_day function. + /// \copydoc start_of_next_day_from_unix_day + template + TIME_SHIELD_CONSTEXPR T next_day_from_unix_day(dse_t unix_day) noexcept { + return start_of_next_day_from_unix_day(unix_day); + } + +//------------------------------------------------------------------------------ + + /// \brief Alias for start_of_next_day_from_unix_day_ms function. + /// \copydoc start_of_next_day_from_unix_day_ms + template + TIME_SHIELD_CONSTEXPR T next_day_from_unix_day_ms(dse_t unix_day) noexcept { + return start_of_next_day_from_unix_day_ms(unix_day); + } + +//------------------------------------------------------------------------------ + + /// \brief Alias for min_since_epoch function. + /// \copydoc min_since_epoch + template + TIME_SHIELD_CONSTEXPR T minutes_since_epoch(ts_t ts = time_shield::ts()) { + return min_since_epoch(ts); + } + + /// \brief Alias for min_since_epoch function. + /// \copydoc min_since_epoch + template + TIME_SHIELD_CONSTEXPR T unix_min(ts_t ts = time_shield::ts()) { + return min_since_epoch(ts); + } + + /// \brief Alias for min_since_epoch function. + /// \copydoc min_since_epoch + template + TIME_SHIELD_CONSTEXPR T to_unix_min(ts_t ts = time_shield::ts()) { + return min_since_epoch(ts); + } + + /// \brief Alias for min_since_epoch function. + /// \copydoc min_since_epoch + template + TIME_SHIELD_CONSTEXPR T umin(ts_t ts = time_shield::ts()) { + return min_since_epoch(ts); + } + +//------------------------------------------------------------------------------ + + /// \ingroup time_structures + /// \brief Alias for dt_to_timestamp. + /// \copydoc dt_to_timestamp + template + TIME_SHIELD_CONSTEXPR inline auto dt_to_ts(const T& date_time) + -> decltype(dt_to_timestamp(date_time)) { + return dt_to_timestamp(date_time); + } + + /// \ingroup time_structures + /// \brief Alias for tm_to_timestamp. + /// \copydoc tm_to_timestamp + TIME_SHIELD_CONSTEXPR inline auto tm_to_ts(const std::tm* timeinfo) + -> decltype(tm_to_timestamp(timeinfo)) { + return tm_to_timestamp(timeinfo); + } + +//------------------------------------------------------------------------------ + + /// \brief Alias for hour24_to_12 function. + /// \copydoc hour24_to_12 + template + TIME_SHIELD_CONSTEXPR inline T h24_to_h12(T hour) noexcept { + return hour24_to_12(hour); + } + +//------------------------------------------------------------------------------ + + /// \ingroup time_structures + /// \brief Alias for to_date_time function. + /// \copydoc to_date_time + template + T1 to_dt(T2 ts) { + return to_date_time(ts); + } + + /// \ingroup time_structures + /// \brief Alias for to_date_time function. + /// \copydoc to_date_time + template + T1 to_dt_struct(T2 ts) { + return to_date_time(ts); + } + + /// \ingroup time_structures + /// \brief Alias for to_date_time function. + /// \copydoc to_date_time + inline auto to_dt(ts_t ts) + -> decltype(to_date_time(ts)) { + return to_date_time(ts); + } + +//------------------------------------------------------------------------------ + + /// \ingroup time_structures + /// \brief Alias for to_date_time_ms function. + /// \copydoc to_date_time_ms + template + inline T to_dt_ms(ts_ms_t ts) { + return to_date_time_ms(ts); + } + + /// \ingroup time_structures + /// \brief Alias for to_date_time_ms function. + /// \copydoc to_date_time_ms + template + inline T to_dt_struct_ms(ts_ms_t ts) { + return to_date_time_ms(ts); + } + + /// \ingroup time_structures + /// \brief Alias for to_date_time_ms function. + /// \copydoc to_date_time_ms + inline auto to_dt_ms(ts_ms_t ts_ms) + -> decltype(to_date_time_ms(ts_ms)) { + return to_date_time_ms(ts_ms); + } + +//------------------------------------------------------------------------------ + + + /// \brief Alias for to_timestamp + /// + /// This function converts a given date and time to a timestamp, which is the number + /// of seconds since the Unix epoch (January 1, 1970). + /// + /// If the `day` is ≥ 1970 and `year` ≤ 31, parameters are assumed to be in DD-MM-YYYY order + /// and are automatically reordered. + /// + /// \param year The year value. + /// \param month The month value. + /// \param day The day value. + /// \return Timestamp representing the given date and time. + /// \throws std::invalid_argument if the date-time combination is invalid. + /// \see to_timestamp + TIME_SHIELD_CONSTEXPR inline ts_t ts(year_t year, int month, int day) { + return to_timestamp(year, month, day); + } + + + /// \brief Alias for to_timestamp + /// + /// This function converts a given date and time to a timestamp, which is the number + /// of seconds since the Unix epoch (January 1, 1970). + /// + /// If the `day` is ≥ 1970 and `year` ≤ 31, parameters are assumed to be in DD-MM-YYYY order + /// and are automatically reordered. + /// + /// \param year The year value. + /// \param month The month value. + /// \param day The day value. + /// \param hour The hour value. + /// \return Timestamp representing the given date and time. + /// \throws std::invalid_argument if the date-time combination is invalid. + /// \see to_timestamp + TIME_SHIELD_CONSTEXPR inline ts_t ts(year_t year, int month, int day, int hour) { + return to_timestamp(year, month, day, hour); + } + + + /// \brief Alias for to_timestamp + /// + /// This function converts a given date and time to a timestamp, which is the number + /// of seconds since the Unix epoch (January 1, 1970). + /// + /// If the `day` is ≥ 1970 and `year` ≤ 31, parameters are assumed to be in DD-MM-YYYY order + /// and are automatically reordered. + /// + /// \param year The year value. + /// \param month The month value. + /// \param day The day value. + /// \param hour The hour value. + /// \param min The minute value. + /// \return Timestamp representing the given date and time. + /// \throws std::invalid_argument if the date-time combination is invalid. + /// \see to_timestamp + TIME_SHIELD_CONSTEXPR inline ts_t ts(year_t year, int month, int day, int hour, int min) { + return to_timestamp(year, month, day, hour, min); + } + + + /// \brief Alias for to_timestamp + /// + /// This function converts a given date and time to a timestamp, which is the number + /// of seconds since the Unix epoch (January 1, 1970). + /// + /// If the `day` is ≥ 1970 and `year` ≤ 31, parameters are assumed to be in DD-MM-YYYY order + /// and are automatically reordered. + /// + /// \param year The year value. + /// \param month The month value. + /// \param day The day value. + /// \param hour The hour value. + /// \param min The minute value. + /// \param sec The second value. + /// \return Timestamp representing the given date and time. + /// \throws std::invalid_argument if the date-time combination is invalid. + /// \see to_timestamp + TIME_SHIELD_CONSTEXPR inline ts_t ts(year_t year, int month, int day, int hour, int min, int sec) { + return to_timestamp(year, month, day, hour, min, sec); + } + + + /// \brief Alias for to_timestamp + /// + /// This function converts a given date and time to a timestamp, which is the number + /// of seconds since the Unix epoch (January 1, 1970). + /// + /// If the `day` is ≥ 1970 and `year` ≤ 31, parameters are assumed to be in DD-MM-YYYY order + /// and are automatically reordered. + /// + /// \param year The year value. + /// \param month The month value. + /// \param day The day value. + /// \return Timestamp representing the given date and time. + /// \throws std::invalid_argument if the date-time combination is invalid. + /// \see to_timestamp + TIME_SHIELD_CONSTEXPR inline ts_t get_ts(year_t year, int month, int day) { + return to_timestamp(year, month, day); + } + + + /// \brief Alias for to_timestamp + /// + /// This function converts a given date and time to a timestamp, which is the number + /// of seconds since the Unix epoch (January 1, 1970). + /// + /// If the `day` is ≥ 1970 and `year` ≤ 31, parameters are assumed to be in DD-MM-YYYY order + /// and are automatically reordered. + /// + /// \param year The year value. + /// \param month The month value. + /// \param day The day value. + /// \param hour The hour value. + /// \return Timestamp representing the given date and time. + /// \throws std::invalid_argument if the date-time combination is invalid. + /// \see to_timestamp + TIME_SHIELD_CONSTEXPR inline ts_t get_ts(year_t year, int month, int day, int hour) { + return to_timestamp(year, month, day, hour); + } + + + /// \brief Alias for to_timestamp + /// + /// This function converts a given date and time to a timestamp, which is the number + /// of seconds since the Unix epoch (January 1, 1970). + /// + /// If the `day` is ≥ 1970 and `year` ≤ 31, parameters are assumed to be in DD-MM-YYYY order + /// and are automatically reordered. + /// + /// \param year The year value. + /// \param month The month value. + /// \param day The day value. + /// \param hour The hour value. + /// \param min The minute value. + /// \return Timestamp representing the given date and time. + /// \throws std::invalid_argument if the date-time combination is invalid. + /// \see to_timestamp + TIME_SHIELD_CONSTEXPR inline ts_t get_ts(year_t year, int month, int day, int hour, int min) { + return to_timestamp(year, month, day, hour, min); + } + + + /// \brief Alias for to_timestamp + /// + /// This function converts a given date and time to a timestamp, which is the number + /// of seconds since the Unix epoch (January 1, 1970). + /// + /// If the `day` is ≥ 1970 and `year` ≤ 31, parameters are assumed to be in DD-MM-YYYY order + /// and are automatically reordered. + /// + /// \param year The year value. + /// \param month The month value. + /// \param day The day value. + /// \param hour The hour value. + /// \param min The minute value. + /// \param sec The second value. + /// \return Timestamp representing the given date and time. + /// \throws std::invalid_argument if the date-time combination is invalid. + /// \see to_timestamp + TIME_SHIELD_CONSTEXPR inline ts_t get_ts(year_t year, int month, int day, int hour, int min, int sec) { + return to_timestamp(year, month, day, hour, min, sec); + } + + + /// \brief Alias for to_timestamp + /// + /// This function converts a given date and time to a timestamp, which is the number + /// of seconds since the Unix epoch (January 1, 1970). + /// + /// If the `day` is ≥ 1970 and `year` ≤ 31, parameters are assumed to be in DD-MM-YYYY order + /// and are automatically reordered. + /// + /// \param year The year value. + /// \param month The month value. + /// \param day The day value. + /// \return Timestamp representing the given date and time. + /// \throws std::invalid_argument if the date-time combination is invalid. + /// \see to_timestamp + TIME_SHIELD_CONSTEXPR inline ts_t get_timestamp(year_t year, int month, int day) { + return to_timestamp(year, month, day); + } + + + /// \brief Alias for to_timestamp + /// + /// This function converts a given date and time to a timestamp, which is the number + /// of seconds since the Unix epoch (January 1, 1970). + /// + /// If the `day` is ≥ 1970 and `year` ≤ 31, parameters are assumed to be in DD-MM-YYYY order + /// and are automatically reordered. + /// + /// \param year The year value. + /// \param month The month value. + /// \param day The day value. + /// \param hour The hour value. + /// \return Timestamp representing the given date and time. + /// \throws std::invalid_argument if the date-time combination is invalid. + /// \see to_timestamp + TIME_SHIELD_CONSTEXPR inline ts_t get_timestamp(year_t year, int month, int day, int hour) { + return to_timestamp(year, month, day, hour); + } + + + /// \brief Alias for to_timestamp + /// + /// This function converts a given date and time to a timestamp, which is the number + /// of seconds since the Unix epoch (January 1, 1970). + /// + /// If the `day` is ≥ 1970 and `year` ≤ 31, parameters are assumed to be in DD-MM-YYYY order + /// and are automatically reordered. + /// + /// \param year The year value. + /// \param month The month value. + /// \param day The day value. + /// \param hour The hour value. + /// \param min The minute value. + /// \return Timestamp representing the given date and time. + /// \throws std::invalid_argument if the date-time combination is invalid. + /// \see to_timestamp + TIME_SHIELD_CONSTEXPR inline ts_t get_timestamp(year_t year, int month, int day, int hour, int min) { + return to_timestamp(year, month, day, hour, min); + } + + + /// \brief Alias for to_timestamp + /// + /// This function converts a given date and time to a timestamp, which is the number + /// of seconds since the Unix epoch (January 1, 1970). + /// + /// If the `day` is ≥ 1970 and `year` ≤ 31, parameters are assumed to be in DD-MM-YYYY order + /// and are automatically reordered. + /// + /// \param year The year value. + /// \param month The month value. + /// \param day The day value. + /// \param hour The hour value. + /// \param min The minute value. + /// \param sec The second value. + /// \return Timestamp representing the given date and time. + /// \throws std::invalid_argument if the date-time combination is invalid. + /// \see to_timestamp + TIME_SHIELD_CONSTEXPR inline ts_t get_timestamp(year_t year, int month, int day, int hour, int min, int sec) { + return to_timestamp(year, month, day, hour, min, sec); + } + + + /// \brief Alias for to_timestamp + /// + /// This function converts a given date and time to a timestamp, which is the number + /// of seconds since the Unix epoch (January 1, 1970). + /// + /// If the `day` is ≥ 1970 and `year` ≤ 31, parameters are assumed to be in DD-MM-YYYY order + /// and are automatically reordered. + /// + /// \param year The year value. + /// \param month The month value. + /// \param day The day value. + /// \return Timestamp representing the given date and time. + /// \throws std::invalid_argument if the date-time combination is invalid. + /// \see to_timestamp + TIME_SHIELD_CONSTEXPR inline ts_t timestamp(year_t year, int month, int day) { + return to_timestamp(year, month, day); + } + + + /// \brief Alias for to_timestamp + /// + /// This function converts a given date and time to a timestamp, which is the number + /// of seconds since the Unix epoch (January 1, 1970). + /// + /// If the `day` is ≥ 1970 and `year` ≤ 31, parameters are assumed to be in DD-MM-YYYY order + /// and are automatically reordered. + /// + /// \param year The year value. + /// \param month The month value. + /// \param day The day value. + /// \param hour The hour value. + /// \return Timestamp representing the given date and time. + /// \throws std::invalid_argument if the date-time combination is invalid. + /// \see to_timestamp + TIME_SHIELD_CONSTEXPR inline ts_t timestamp(year_t year, int month, int day, int hour) { + return to_timestamp(year, month, day, hour); + } + + + /// \brief Alias for to_timestamp + /// + /// This function converts a given date and time to a timestamp, which is the number + /// of seconds since the Unix epoch (January 1, 1970). + /// + /// If the `day` is ≥ 1970 and `year` ≤ 31, parameters are assumed to be in DD-MM-YYYY order + /// and are automatically reordered. + /// + /// \param year The year value. + /// \param month The month value. + /// \param day The day value. + /// \param hour The hour value. + /// \param min The minute value. + /// \return Timestamp representing the given date and time. + /// \throws std::invalid_argument if the date-time combination is invalid. + /// \see to_timestamp + TIME_SHIELD_CONSTEXPR inline ts_t timestamp(year_t year, int month, int day, int hour, int min) { + return to_timestamp(year, month, day, hour, min); + } + + + /// \brief Alias for to_timestamp + /// + /// This function converts a given date and time to a timestamp, which is the number + /// of seconds since the Unix epoch (January 1, 1970). + /// + /// If the `day` is ≥ 1970 and `year` ≤ 31, parameters are assumed to be in DD-MM-YYYY order + /// and are automatically reordered. + /// + /// \param year The year value. + /// \param month The month value. + /// \param day The day value. + /// \param hour The hour value. + /// \param min The minute value. + /// \param sec The second value. + /// \return Timestamp representing the given date and time. + /// \throws std::invalid_argument if the date-time combination is invalid. + /// \see to_timestamp + TIME_SHIELD_CONSTEXPR inline ts_t timestamp(year_t year, int month, int day, int hour, int min, int sec) { + return to_timestamp(year, month, day, hour, min, sec); + } + + + /// \brief Alias for to_timestamp + /// + /// This function converts a given date and time to a timestamp, which is the number + /// of seconds since the Unix epoch (January 1, 1970). + /// + /// If the `day` is ≥ 1970 and `year` ≤ 31, parameters are assumed to be in DD-MM-YYYY order + /// and are automatically reordered. + /// + /// \param year The year value. + /// \param month The month value. + /// \param day The day value. + /// \return Timestamp representing the given date and time. + /// \throws std::invalid_argument if the date-time combination is invalid. + /// \see to_timestamp + TIME_SHIELD_CONSTEXPR inline ts_t to_ts(year_t year, int month, int day) { + return to_timestamp(year, month, day); + } + + + /// \brief Alias for to_timestamp + /// + /// This function converts a given date and time to a timestamp, which is the number + /// of seconds since the Unix epoch (January 1, 1970). + /// + /// If the `day` is ≥ 1970 and `year` ≤ 31, parameters are assumed to be in DD-MM-YYYY order + /// and are automatically reordered. + /// + /// \param year The year value. + /// \param month The month value. + /// \param day The day value. + /// \param hour The hour value. + /// \return Timestamp representing the given date and time. + /// \throws std::invalid_argument if the date-time combination is invalid. + /// \see to_timestamp + TIME_SHIELD_CONSTEXPR inline ts_t to_ts(year_t year, int month, int day, int hour) { + return to_timestamp(year, month, day, hour); + } + + + /// \brief Alias for to_timestamp + /// + /// This function converts a given date and time to a timestamp, which is the number + /// of seconds since the Unix epoch (January 1, 1970). + /// + /// If the `day` is ≥ 1970 and `year` ≤ 31, parameters are assumed to be in DD-MM-YYYY order + /// and are automatically reordered. + /// + /// \param year The year value. + /// \param month The month value. + /// \param day The day value. + /// \param hour The hour value. + /// \param min The minute value. + /// \return Timestamp representing the given date and time. + /// \throws std::invalid_argument if the date-time combination is invalid. + /// \see to_timestamp + TIME_SHIELD_CONSTEXPR inline ts_t to_ts(year_t year, int month, int day, int hour, int min) { + return to_timestamp(year, month, day, hour, min); + } + + + /// \brief Alias for to_timestamp + /// + /// This function converts a given date and time to a timestamp, which is the number + /// of seconds since the Unix epoch (January 1, 1970). + /// + /// If the `day` is ≥ 1970 and `year` ≤ 31, parameters are assumed to be in DD-MM-YYYY order + /// and are automatically reordered. + /// + /// \param year The year value. + /// \param month The month value. + /// \param day The day value. + /// \param hour The hour value. + /// \param min The minute value. + /// \param sec The second value. + /// \return Timestamp representing the given date and time. + /// \throws std::invalid_argument if the date-time combination is invalid. + /// \see to_timestamp + TIME_SHIELD_CONSTEXPR inline ts_t to_ts(year_t year, int month, int day, int hour, int min, int sec) { + return to_timestamp(year, month, day, hour, min, sec); + } + +//------------------------------------------------------------------------------ + + /// \ingroup time_structures + /// \brief Alias for dt_to_timestamp function. + /// \copydoc dt_to_timestamp + template + TIME_SHIELD_CONSTEXPR inline ts_t to_timestamp( + const T& date_time) { + return dt_to_timestamp(date_time); + } + + /// \ingroup time_structures + /// \brief Alias for dt_to_timestamp function. + /// \copydoc dt_to_timestamp + template + TIME_SHIELD_CONSTEXPR inline ts_t to_ts( + const T& date_time) { + return dt_to_timestamp(date_time); + } + + /// \ingroup time_structures + /// \brief Alias for dt_to_timestamp function. + /// \copydoc dt_to_timestamp + template + TIME_SHIELD_CONSTEXPR inline ts_t ts( + const T& date_time) { + return dt_to_timestamp(date_time); + } + + /// \ingroup time_structures + /// \brief Alias for dt_to_timestamp function. + /// \copydoc dt_to_timestamp + template + TIME_SHIELD_CONSTEXPR inline ts_t timestamp( + const T& date_time) { + return dt_to_timestamp(date_time); + } + +//------------------------------------------------------------------------------ + + /// \brief Alias for tm_to_timestamp + /// \copydoc tm_to_timestamp + TIME_SHIELD_CONSTEXPR inline ts_t ts(const std::tm* timeinfo) { + return tm_to_timestamp(timeinfo); + } + + /// \brief Alias for tm_to_timestamp + /// \copydoc tm_to_timestamp + TIME_SHIELD_CONSTEXPR inline ts_t get_ts(const std::tm* timeinfo) { + return tm_to_timestamp(timeinfo); + } + + /// \brief Alias for tm_to_timestamp + /// \copydoc tm_to_timestamp + TIME_SHIELD_CONSTEXPR inline ts_t timestamp(const std::tm* timeinfo) { + return tm_to_timestamp(timeinfo); + } + + /// \brief Alias for tm_to_timestamp + /// \copydoc tm_to_timestamp + TIME_SHIELD_CONSTEXPR inline ts_t get_timestamp(const std::tm* timeinfo) { + return tm_to_timestamp(timeinfo); + } + + /// \brief Alias for tm_to_timestamp + /// \copydoc tm_to_timestamp + TIME_SHIELD_CONSTEXPR inline ts_t to_ts(const std::tm* timeinfo) { + return tm_to_timestamp(timeinfo); + } + + /// \brief Alias for tm_to_timestamp + /// \copydoc tm_to_timestamp + TIME_SHIELD_CONSTEXPR inline ts_t ts_from_tm(const std::tm* timeinfo) { + return tm_to_timestamp(timeinfo); + } + + /// \brief Alias for tm_to_timestamp + /// \copydoc tm_to_timestamp + TIME_SHIELD_CONSTEXPR inline ts_t to_timestamp(const std::tm* timeinfo) { + return tm_to_timestamp(timeinfo); + } + +//------------------------------------------------------------------------------ + + /// \brief Alias for to_timestamp_ms + /// + /// This function converts a given date and time to a timestamp in milliseconds, + /// which is the number of milliseconds since the Unix epoch (January 1, 1970). + /// + /// \tparam T1 The type of the year parameter (default is int64_t). + /// \tparam T2 The type of the other date and time parameters (default is int). + /// \param year The year value. + /// \param month The month value. + /// \param day The day value. + /// \return Timestamp in milliseconds representing the given date and time. + /// \throws std::invalid_argument if the date-time combination is invalid. + /// \see to_timestamp_ms + TIME_SHIELD_CONSTEXPR inline ts_ms_t ts_ms(year_t year, int month, int day) { + return to_timestamp_ms(year, month, day); + } + + /// \brief Alias for to_timestamp_ms + /// + /// This function converts a given date and time to a timestamp in milliseconds, + /// which is the number of milliseconds since the Unix epoch (January 1, 1970). + /// + /// \tparam T1 The type of the year parameter (default is int64_t). + /// \tparam T2 The type of the other date and time parameters (default is int). + /// \param year The year value. + /// \param month The month value. + /// \param day The day value. + /// \param hour The hour value. + /// \return Timestamp in milliseconds representing the given date and time. + /// \throws std::invalid_argument if the date-time combination is invalid. + /// \see to_timestamp_ms + TIME_SHIELD_CONSTEXPR inline ts_ms_t ts_ms(year_t year, int month, int day, int hour) { + return to_timestamp_ms(year, month, day, hour); + } + + /// \brief Alias for to_timestamp_ms + /// + /// This function converts a given date and time to a timestamp in milliseconds, + /// which is the number of milliseconds since the Unix epoch (January 1, 1970). + /// + /// \tparam T1 The type of the year parameter (default is int64_t). + /// \tparam T2 The type of the other date and time parameters (default is int). + /// \param year The year value. + /// \param month The month value. + /// \param day The day value. + /// \param hour The hour value. + /// \param min The minute value. + /// \return Timestamp in milliseconds representing the given date and time. + /// \throws std::invalid_argument if the date-time combination is invalid. + /// \see to_timestamp_ms + TIME_SHIELD_CONSTEXPR inline ts_ms_t ts_ms(year_t year, int month, int day, int hour, int min) { + return to_timestamp_ms(year, month, day, hour, min); + } + + /// \brief Alias for to_timestamp_ms + /// + /// This function converts a given date and time to a timestamp in milliseconds, + /// which is the number of milliseconds since the Unix epoch (January 1, 1970). + /// + /// \tparam T1 The type of the year parameter (default is int64_t). + /// \tparam T2 The type of the other date and time parameters (default is int). + /// \param year The year value. + /// \param month The month value. + /// \param day The day value. + /// \param hour The hour value. + /// \param min The minute value. + /// \param sec The second value. + /// \return Timestamp in milliseconds representing the given date and time. + /// \throws std::invalid_argument if the date-time combination is invalid. + /// \see to_timestamp_ms + TIME_SHIELD_CONSTEXPR inline ts_ms_t ts_ms(year_t year, int month, int day, int hour, int min, int sec) { + return to_timestamp_ms(year, month, day, hour, min, sec); + } + + /// \brief Alias for to_timestamp_ms + /// + /// This function converts a given date and time to a timestamp in milliseconds, + /// which is the number of milliseconds since the Unix epoch (January 1, 1970). + /// + /// \tparam T1 The type of the year parameter (default is int64_t). + /// \tparam T2 The type of the other date and time parameters (default is int). + /// \param year The year value. + /// \param month The month value. + /// \param day The day value. + /// \param hour The hour value. + /// \param min The minute value. + /// \param sec The second value. + /// \param ms The millisecond value. + /// \return Timestamp in milliseconds representing the given date and time. + /// \throws std::invalid_argument if the date-time combination is invalid. + /// \see to_timestamp_ms + TIME_SHIELD_CONSTEXPR inline ts_ms_t ts_ms(year_t year, int month, int day, int hour, int min, int sec, int ms) { + return to_timestamp_ms(year, month, day, hour, min, sec, ms); + } + + /// \brief Alias for to_timestamp_ms + /// + /// This function converts a given date and time to a timestamp in milliseconds, + /// which is the number of milliseconds since the Unix epoch (January 1, 1970). + /// + /// \tparam T1 The type of the year parameter (default is int64_t). + /// \tparam T2 The type of the other date and time parameters (default is int). + /// \param year The year value. + /// \param month The month value. + /// \param day The day value. + /// \return Timestamp in milliseconds representing the given date and time. + /// \throws std::invalid_argument if the date-time combination is invalid. + /// \see to_timestamp_ms + TIME_SHIELD_CONSTEXPR inline ts_ms_t get_ts_ms(year_t year, int month, int day) { + return to_timestamp_ms(year, month, day); + } + + /// \brief Alias for to_timestamp_ms + /// + /// This function converts a given date and time to a timestamp in milliseconds, + /// which is the number of milliseconds since the Unix epoch (January 1, 1970). + /// + /// \tparam T1 The type of the year parameter (default is int64_t). + /// \tparam T2 The type of the other date and time parameters (default is int). + /// \param year The year value. + /// \param month The month value. + /// \param day The day value. + /// \param hour The hour value. + /// \return Timestamp in milliseconds representing the given date and time. + /// \throws std::invalid_argument if the date-time combination is invalid. + /// \see to_timestamp_ms + TIME_SHIELD_CONSTEXPR inline ts_ms_t get_ts_ms(year_t year, int month, int day, int hour) { + return to_timestamp_ms(year, month, day, hour); + } + + /// \brief Alias for to_timestamp_ms + /// + /// This function converts a given date and time to a timestamp in milliseconds, + /// which is the number of milliseconds since the Unix epoch (January 1, 1970). + /// + /// \tparam T1 The type of the year parameter (default is int64_t). + /// \tparam T2 The type of the other date and time parameters (default is int). + /// \param year The year value. + /// \param month The month value. + /// \param day The day value. + /// \param hour The hour value. + /// \param min The minute value. + /// \return Timestamp in milliseconds representing the given date and time. + /// \throws std::invalid_argument if the date-time combination is invalid. + /// \see to_timestamp_ms + TIME_SHIELD_CONSTEXPR inline ts_ms_t get_ts_ms(year_t year, int month, int day, int hour, int min) { + return to_timestamp_ms(year, month, day, hour, min); + } + + /// \brief Alias for to_timestamp_ms + /// + /// This function converts a given date and time to a timestamp in milliseconds, + /// which is the number of milliseconds since the Unix epoch (January 1, 1970). + /// + /// \tparam T1 The type of the year parameter (default is int64_t). + /// \tparam T2 The type of the other date and time parameters (default is int). + /// \param year The year value. + /// \param month The month value. + /// \param day The day value. + /// \param hour The hour value. + /// \param min The minute value. + /// \param sec The second value. + /// \return Timestamp in milliseconds representing the given date and time. + /// \throws std::invalid_argument if the date-time combination is invalid. + /// \see to_timestamp_ms + TIME_SHIELD_CONSTEXPR inline ts_ms_t get_ts_ms(year_t year, int month, int day, int hour, int min, int sec) { + return to_timestamp_ms(year, month, day, hour, min, sec); + } + + /// \brief Alias for to_timestamp_ms + /// + /// This function converts a given date and time to a timestamp in milliseconds, + /// which is the number of milliseconds since the Unix epoch (January 1, 1970). + /// + /// \tparam T1 The type of the year parameter (default is int64_t). + /// \tparam T2 The type of the other date and time parameters (default is int). + /// \param year The year value. + /// \param month The month value. + /// \param day The day value. + /// \param hour The hour value. + /// \param min The minute value. + /// \param sec The second value. + /// \param ms The millisecond value. + /// \return Timestamp in milliseconds representing the given date and time. + /// \throws std::invalid_argument if the date-time combination is invalid. + /// \see to_timestamp_ms + TIME_SHIELD_CONSTEXPR inline ts_ms_t get_ts_ms(year_t year, int month, int day, int hour, int min, int sec, int ms) { + return to_timestamp_ms(year, month, day, hour, min, sec, ms); + } + + /// \brief Alias for to_timestamp_ms + /// + /// This function converts a given date and time to a timestamp in milliseconds, + /// which is the number of milliseconds since the Unix epoch (January 1, 1970). + /// + /// \tparam T1 The type of the year parameter (default is int64_t). + /// \tparam T2 The type of the other date and time parameters (default is int). + /// \param year The year value. + /// \param month The month value. + /// \param day The day value. + /// \return Timestamp in milliseconds representing the given date and time. + /// \throws std::invalid_argument if the date-time combination is invalid. + /// \see to_timestamp_ms + TIME_SHIELD_CONSTEXPR inline ts_ms_t get_timestamp_ms(year_t year, int month, int day) { + return to_timestamp_ms(year, month, day); + } + + /// \brief Alias for to_timestamp_ms + /// + /// This function converts a given date and time to a timestamp in milliseconds, + /// which is the number of milliseconds since the Unix epoch (January 1, 1970). + /// + /// \tparam T1 The type of the year parameter (default is int64_t). + /// \tparam T2 The type of the other date and time parameters (default is int). + /// \param year The year value. + /// \param month The month value. + /// \param day The day value. + /// \param hour The hour value. + /// \return Timestamp in milliseconds representing the given date and time. + /// \throws std::invalid_argument if the date-time combination is invalid. + /// \see to_timestamp_ms + TIME_SHIELD_CONSTEXPR inline ts_ms_t get_timestamp_ms(year_t year, int month, int day, int hour) { + return to_timestamp_ms(year, month, day, hour); + } + + /// \brief Alias for to_timestamp_ms + /// + /// This function converts a given date and time to a timestamp in milliseconds, + /// which is the number of milliseconds since the Unix epoch (January 1, 1970). + /// + /// \tparam T1 The type of the year parameter (default is int64_t). + /// \tparam T2 The type of the other date and time parameters (default is int). + /// \param year The year value. + /// \param month The month value. + /// \param day The day value. + /// \param hour The hour value. + /// \param min The minute value. + /// \return Timestamp in milliseconds representing the given date and time. + /// \throws std::invalid_argument if the date-time combination is invalid. + /// \see to_timestamp_ms + TIME_SHIELD_CONSTEXPR inline ts_ms_t get_timestamp_ms(year_t year, int month, int day, int hour, int min) { + return to_timestamp_ms(year, month, day, hour, min); + } + + /// \brief Alias for to_timestamp_ms + /// + /// This function converts a given date and time to a timestamp in milliseconds, + /// which is the number of milliseconds since the Unix epoch (January 1, 1970). + /// + /// \tparam T1 The type of the year parameter (default is int64_t). + /// \tparam T2 The type of the other date and time parameters (default is int). + /// \param year The year value. + /// \param month The month value. + /// \param day The day value. + /// \param hour The hour value. + /// \param min The minute value. + /// \param sec The second value. + /// \return Timestamp in milliseconds representing the given date and time. + /// \throws std::invalid_argument if the date-time combination is invalid. + /// \see to_timestamp_ms + TIME_SHIELD_CONSTEXPR inline ts_ms_t get_timestamp_ms(year_t year, int month, int day, int hour, int min, int sec) { + return to_timestamp_ms(year, month, day, hour, min, sec); + } + + /// \brief Alias for to_timestamp_ms + /// + /// This function converts a given date and time to a timestamp in milliseconds, + /// which is the number of milliseconds since the Unix epoch (January 1, 1970). + /// + /// \tparam T1 The type of the year parameter (default is int64_t). + /// \tparam T2 The type of the other date and time parameters (default is int). + /// \param year The year value. + /// \param month The month value. + /// \param day The day value. + /// \param hour The hour value. + /// \param min The minute value. + /// \param sec The second value. + /// \param ms The millisecond value. + /// \return Timestamp in milliseconds representing the given date and time. + /// \throws std::invalid_argument if the date-time combination is invalid. + /// \see to_timestamp_ms + TIME_SHIELD_CONSTEXPR inline ts_ms_t get_timestamp_ms(year_t year, int month, int day, int hour, int min, int sec, int ms) { + return to_timestamp_ms(year, month, day, hour, min, sec, ms); + } + + /// \brief Alias for to_timestamp_ms + /// + /// This function converts a given date and time to a timestamp in milliseconds, + /// which is the number of milliseconds since the Unix epoch (January 1, 1970). + /// + /// \tparam T1 The type of the year parameter (default is int64_t). + /// \tparam T2 The type of the other date and time parameters (default is int). + /// \param year The year value. + /// \param month The month value. + /// \param day The day value. + /// \return Timestamp in milliseconds representing the given date and time. + /// \throws std::invalid_argument if the date-time combination is invalid. + /// \see to_timestamp_ms + TIME_SHIELD_CONSTEXPR inline ts_ms_t timestamp_ms(year_t year, int month, int day) { + return to_timestamp_ms(year, month, day); + } + + /// \brief Alias for to_timestamp_ms + /// + /// This function converts a given date and time to a timestamp in milliseconds, + /// which is the number of milliseconds since the Unix epoch (January 1, 1970). + /// + /// \tparam T1 The type of the year parameter (default is int64_t). + /// \tparam T2 The type of the other date and time parameters (default is int). + /// \param year The year value. + /// \param month The month value. + /// \param day The day value. + /// \param hour The hour value. + /// \return Timestamp in milliseconds representing the given date and time. + /// \throws std::invalid_argument if the date-time combination is invalid. + /// \see to_timestamp_ms + TIME_SHIELD_CONSTEXPR inline ts_ms_t timestamp_ms(year_t year, int month, int day, int hour) { + return to_timestamp_ms(year, month, day, hour); + } + + /// \brief Alias for to_timestamp_ms + /// + /// This function converts a given date and time to a timestamp in milliseconds, + /// which is the number of milliseconds since the Unix epoch (January 1, 1970). + /// + /// \tparam T1 The type of the year parameter (default is int64_t). + /// \tparam T2 The type of the other date and time parameters (default is int). + /// \param year The year value. + /// \param month The month value. + /// \param day The day value. + /// \param hour The hour value. + /// \param min The minute value. + /// \return Timestamp in milliseconds representing the given date and time. + /// \throws std::invalid_argument if the date-time combination is invalid. + /// \see to_timestamp_ms + TIME_SHIELD_CONSTEXPR inline ts_ms_t timestamp_ms(year_t year, int month, int day, int hour, int min) { + return to_timestamp_ms(year, month, day, hour, min); + } + + /// \brief Alias for to_timestamp_ms + /// + /// This function converts a given date and time to a timestamp in milliseconds, + /// which is the number of milliseconds since the Unix epoch (January 1, 1970). + /// + /// \tparam T1 The type of the year parameter (default is int64_t). + /// \tparam T2 The type of the other date and time parameters (default is int). + /// \param year The year value. + /// \param month The month value. + /// \param day The day value. + /// \param hour The hour value. + /// \param min The minute value. + /// \param sec The second value. + /// \return Timestamp in milliseconds representing the given date and time. + /// \throws std::invalid_argument if the date-time combination is invalid. + /// \see to_timestamp_ms + TIME_SHIELD_CONSTEXPR inline ts_ms_t timestamp_ms(year_t year, int month, int day, int hour, int min, int sec) { + return to_timestamp_ms(year, month, day, hour, min, sec); + } + + /// \brief Alias for to_timestamp_ms + /// + /// This function converts a given date and time to a timestamp in milliseconds, + /// which is the number of milliseconds since the Unix epoch (January 1, 1970). + /// + /// \tparam T1 The type of the year parameter (default is int64_t). + /// \tparam T2 The type of the other date and time parameters (default is int). + /// \param year The year value. + /// \param month The month value. + /// \param day The day value. + /// \param hour The hour value. + /// \param min The minute value. + /// \param sec The second value. + /// \param ms The millisecond value. + /// \return Timestamp in milliseconds representing the given date and time. + /// \throws std::invalid_argument if the date-time combination is invalid. + /// \see to_timestamp_ms + TIME_SHIELD_CONSTEXPR inline ts_ms_t timestamp_ms(year_t year, int month, int day, int hour, int min, int sec, int ms) { + return to_timestamp_ms(year, month, day, hour, min, sec, ms); + } + + /// \brief Alias for to_timestamp_ms + /// + /// This function converts a given date and time to a timestamp in milliseconds, + /// which is the number of milliseconds since the Unix epoch (January 1, 1970). + /// + /// \tparam T1 The type of the year parameter (default is int64_t). + /// \tparam T2 The type of the other date and time parameters (default is int). + /// \param year The year value. + /// \param month The month value. + /// \param day The day value. + /// \return Timestamp in milliseconds representing the given date and time. + /// \throws std::invalid_argument if the date-time combination is invalid. + /// \see to_timestamp_ms + TIME_SHIELD_CONSTEXPR inline ts_ms_t to_ts_ms(year_t year, int month, int day) { + return to_timestamp_ms(year, month, day); + } + + /// \brief Alias for to_timestamp_ms + /// + /// This function converts a given date and time to a timestamp in milliseconds, + /// which is the number of milliseconds since the Unix epoch (January 1, 1970). + /// + /// \tparam T1 The type of the year parameter (default is int64_t). + /// \tparam T2 The type of the other date and time parameters (default is int). + /// \param year The year value. + /// \param month The month value. + /// \param day The day value. + /// \param hour The hour value. + /// \return Timestamp in milliseconds representing the given date and time. + /// \throws std::invalid_argument if the date-time combination is invalid. + /// \see to_timestamp_ms + TIME_SHIELD_CONSTEXPR inline ts_ms_t to_ts_ms(year_t year, int month, int day, int hour) { + return to_timestamp_ms(year, month, day, hour); + } + + /// \brief Alias for to_timestamp_ms + /// + /// This function converts a given date and time to a timestamp in milliseconds, + /// which is the number of milliseconds since the Unix epoch (January 1, 1970). + /// + /// \tparam T1 The type of the year parameter (default is int64_t). + /// \tparam T2 The type of the other date and time parameters (default is int). + /// \param year The year value. + /// \param month The month value. + /// \param day The day value. + /// \param hour The hour value. + /// \param min The minute value. + /// \return Timestamp in milliseconds representing the given date and time. + /// \throws std::invalid_argument if the date-time combination is invalid. + /// \see to_timestamp_ms + TIME_SHIELD_CONSTEXPR inline ts_ms_t to_ts_ms(year_t year, int month, int day, int hour, int min) { + return to_timestamp_ms(year, month, day, hour, min); + } + + /// \brief Alias for to_timestamp_ms + /// + /// This function converts a given date and time to a timestamp in milliseconds, + /// which is the number of milliseconds since the Unix epoch (January 1, 1970). + /// + /// \tparam T1 The type of the year parameter (default is int64_t). + /// \tparam T2 The type of the other date and time parameters (default is int). + /// \param year The year value. + /// \param month The month value. + /// \param day The day value. + /// \param hour The hour value. + /// \param min The minute value. + /// \param sec The second value. + /// \return Timestamp in milliseconds representing the given date and time. + /// \throws std::invalid_argument if the date-time combination is invalid. + /// \see to_timestamp_ms + TIME_SHIELD_CONSTEXPR inline ts_ms_t to_ts_ms(year_t year, int month, int day, int hour, int min, int sec) { + return to_timestamp_ms(year, month, day, hour, min, sec); + } + + /// \brief Alias for to_timestamp_ms + /// + /// This function converts a given date and time to a timestamp in milliseconds, + /// which is the number of milliseconds since the Unix epoch (January 1, 1970). + /// + /// \tparam T1 The type of the year parameter (default is int64_t). + /// \tparam T2 The type of the other date and time parameters (default is int). + /// \param year The year value. + /// \param month The month value. + /// \param day The day value. + /// \param hour The hour value. + /// \param min The minute value. + /// \param sec The second value. + /// \param ms The millisecond value. + /// \return Timestamp in milliseconds representing the given date and time. + /// \throws std::invalid_argument if the date-time combination is invalid. + /// \see to_timestamp_ms + TIME_SHIELD_CONSTEXPR inline ts_ms_t to_ts_ms(year_t year, int month, int day, int hour, int min, int sec, int ms) { + return to_timestamp_ms(year, month, day, hour, min, sec, ms); + } + +//------------------------------------------------------------------------------ + + /// \ingroup time_structures + /// \brief Alias for dt_to_timestamp_ms function. + /// \copydoc dt_to_timestamp_ms + template + TIME_SHIELD_CONSTEXPR inline ts_t to_timestamp_ms( + const T& date_time) { + return dt_to_timestamp_ms(date_time); + } + + /// \ingroup time_structures + /// \brief Alias for dt_to_timestamp_ms function. + /// \copydoc dt_to_timestamp_ms + template + TIME_SHIELD_CONSTEXPR inline auto dt_to_ts_ms(const T& date_time) + -> decltype(dt_to_timestamp_ms(date_time)) { + return dt_to_timestamp_ms(date_time); + } + + /// \ingroup time_structures + /// \brief Alias for dt_to_timestamp_ms function. + /// \copydoc dt_to_timestamp_ms + template + TIME_SHIELD_CONSTEXPR inline ts_t to_ts_ms( + const T& date_time) { + return dt_to_timestamp_ms(date_time); + } + + /// \ingroup time_structures + /// \brief Alias for dt_to_timestamp_ms function. + /// \copydoc dt_to_timestamp_ms + template + TIME_SHIELD_CONSTEXPR inline ts_t ts_ms( + const T& date_time) { + return dt_to_timestamp_ms(date_time); + } + + /// \ingroup time_structures + /// \brief Alias for dt_to_timestamp_ms function. + /// \copydoc dt_to_timestamp_ms + template + TIME_SHIELD_CONSTEXPR inline ts_t timestamp_ms( + const T& date_time) { + return dt_to_timestamp_ms(date_time); + } + +//------------------------------------------------------------------------------ + + /// \brief Alias for tm_to_timestamp_ms function. + /// \copydoc tm_to_timestamp_ms + TIME_SHIELD_CONSTEXPR inline ts_t to_timestamp_ms( + const std::tm *timeinfo) { + return tm_to_timestamp_ms(timeinfo); + } + + /// \brief Alias for tm_to_timestamp_ms function. + /// \copydoc tm_to_timestamp_ms + TIME_SHIELD_CONSTEXPR inline auto tm_to_ts_ms(const std::tm *timeinfo) + -> decltype(tm_to_timestamp_ms(timeinfo)) { + return tm_to_timestamp_ms(timeinfo); + } + + /// \brief Alias for tm_to_timestamp_ms function. + /// \copydoc tm_to_timestamp_ms + TIME_SHIELD_CONSTEXPR inline ts_t to_ts_ms( + const std::tm *timeinfo) { + return tm_to_timestamp_ms(timeinfo); + } + + /// \brief Alias for tm_to_timestamp_ms function. + /// \copydoc tm_to_timestamp_ms + TIME_SHIELD_CONSTEXPR inline ts_t ts_ms( + const std::tm *timeinfo) { + return tm_to_timestamp_ms(timeinfo); + } + + /// \brief Alias for tm_to_timestamp_ms function. + /// \copydoc tm_to_timestamp_ms + TIME_SHIELD_CONSTEXPR inline ts_t timestamp_ms( + const std::tm *timeinfo) { + return tm_to_timestamp_ms(timeinfo); + } + +//------------------------------------------------------------------------------ + + /// \brief Alias for to_ftimestamp + /// + /// This function converts a given date and time to a floating-point timestamp, + /// which is the number of seconds (with fractional milliseconds) since the Unix epoch + /// (January 1, 1970). + /// + /// \tparam T1 The type of the year parameter (default is year_t). + /// \tparam T2 The type of the month, day, hour, minute, and second parameters (default is int). + /// \tparam T3 The type of the millisecond parameter (default is int). + /// \param year The year value. + /// \param month The month value. + /// \param day The day value. + /// \param hour The hour value (default is 0). + /// \param min The minute value (default is 0). + /// \param sec The second value (default is 0). + /// \param ms The millisecond value (default is 0). + /// \return Floating-point timestamp representing the given date and time. + /// \throws std::invalid_argument if the date-time combination is invalid. + /// \see to_ftimestamp + template + TIME_SHIELD_CONSTEXPR fts_t to_fts(T1 year, T2 month, T2 day, T2 hour = 0, T2 min = 0, T2 sec = 0, T3 ms = 0) { + return to_ftimestamp(year, month, day, hour, min, sec, ms); + } + + /// \brief Alias for to_ftimestamp + /// + /// This function converts a given date and time to a floating-point timestamp, + /// which is the number of seconds (with fractional milliseconds) since the Unix epoch + /// (January 1, 1970). + /// + /// \tparam T1 The type of the year parameter (default is year_t). + /// \tparam T2 The type of the month, day, hour, minute, and second parameters (default is int). + /// \tparam T3 The type of the millisecond parameter (default is int). + /// \param year The year value. + /// \param month The month value. + /// \param day The day value. + /// \param hour The hour value (default is 0). + /// \param min The minute value (default is 0). + /// \param sec The second value (default is 0). + /// \param ms The millisecond value (default is 0). + /// \return Floating-point timestamp representing the given date and time. + /// \throws std::invalid_argument if the date-time combination is invalid. + /// \see to_ftimestamp + template + TIME_SHIELD_CONSTEXPR fts_t fts(T1 year, T2 month, T2 day, T2 hour = 0, T2 min = 0, T2 sec = 0, T3 ms = 0) { + return to_ftimestamp(year, month, day, hour, min, sec, ms); + } + + /// \brief Alias for to_ftimestamp + /// + /// This function converts a given date and time to a floating-point timestamp, + /// which is the number of seconds (with fractional milliseconds) since the Unix epoch + /// (January 1, 1970). + /// + /// \tparam T1 The type of the year parameter (default is year_t). + /// \tparam T2 The type of the month, day, hour, minute, and second parameters (default is int). + /// \tparam T3 The type of the millisecond parameter (default is int). + /// \param year The year value. + /// \param month The month value. + /// \param day The day value. + /// \param hour The hour value (default is 0). + /// \param min The minute value (default is 0). + /// \param sec The second value (default is 0). + /// \param ms The millisecond value (default is 0). + /// \return Floating-point timestamp representing the given date and time. + /// \throws std::invalid_argument if the date-time combination is invalid. + /// \see to_ftimestamp + template + TIME_SHIELD_CONSTEXPR fts_t ftimestamp(T1 year, T2 month, T2 day, T2 hour = 0, T2 min = 0, T2 sec = 0, T3 ms = 0) { + return to_ftimestamp(year, month, day, hour, min, sec, ms); + } + +//------------------------------------------------------------------------------ + + /// \ingroup time_structures + /// \brief Alias for dt_to_ftimestamp + /// \copydoc dt_to_ftimestamp + template + TIME_SHIELD_CONSTEXPR fts_t to_ftimestamp(const T& date_time) { + return dt_to_ftimestamp(date_time); + } + + /// \ingroup time_structures + /// \brief Alias for dt_to_ftimestamp + /// \copydoc dt_to_ftimestamp + template + constexpr auto dt_to_fts(const T& date_time) + -> decltype(dt_to_ftimestamp(date_time)) { + return dt_to_ftimestamp(date_time); + } + + /// \ingroup time_structures + /// \brief Alias for dt_to_ftimestamp + /// \copydoc dt_to_ftimestamp + template + TIME_SHIELD_CONSTEXPR fts_t to_fts(const T& date_time) { + return dt_to_ftimestamp(date_time); + } + + /// \ingroup time_structures + /// \brief Alias for dt_to_ftimestamp + /// \copydoc dt_to_ftimestamp + template + TIME_SHIELD_CONSTEXPR fts_t fts(const T& date_time) { + return dt_to_ftimestamp(date_time); + } + + /// \ingroup time_structures + /// \brief Alias for dt_to_ftimestamp + /// \copydoc dt_to_ftimestamp + template + TIME_SHIELD_CONSTEXPR fts_t ftimestamp(const T& date_time) { + return dt_to_ftimestamp(date_time); + } + +//------------------------------------------------------------------------------ + + /// \ingroup time_structures + /// \brief Alias for tm_to_ftimestamp + /// \copydoc tm_to_ftimestamp(const std::tm*) + TIME_SHIELD_CONSTEXPR inline fts_t to_ftimestamp(const std::tm* timeinfo) { + return tm_to_ftimestamp(timeinfo); + } + + /// \ingroup time_structures + /// \brief Alias for tm_to_ftimestamp + /// \copydoc tm_to_ftimestamp(const std::tm*) + TIME_SHIELD_CONSTEXPR inline auto tm_to_fts(const std::tm* timeinfo) + -> decltype(tm_to_ftimestamp(timeinfo)) { + return tm_to_ftimestamp(timeinfo); + } + + /// \ingroup time_structures + /// \brief Alias for tm_to_ftimestamp + /// \copydoc tm_to_ftimestamp(const std::tm*) + TIME_SHIELD_CONSTEXPR inline fts_t to_fts(const std::tm* timeinfo) { + return tm_to_ftimestamp(timeinfo); + } + + /// \ingroup time_structures + /// \brief Alias for tm_to_ftimestamp + /// \copydoc tm_to_ftimestamp(const std::tm*) + TIME_SHIELD_CONSTEXPR inline fts_t fts(const std::tm* timeinfo) { + return tm_to_ftimestamp(timeinfo); + } + + /// \ingroup time_structures + /// \brief Alias for tm_to_ftimestamp + /// \copydoc tm_to_ftimestamp(const std::tm*) + TIME_SHIELD_CONSTEXPR inline fts_t ftimestamp(const std::tm* timeinfo) { + return tm_to_ftimestamp(timeinfo); + } + +//------------------------------------------------------------------------------ + + /// \brief Alias for days_between function. + /// \copydoc days_between + template + TIME_SHIELD_CONSTEXPR T get_days(ts_t start, ts_t stop) noexcept { + return days_between(start, stop); + } + + /// \brief Alias for days_between function. + /// \copydoc days_between + template + TIME_SHIELD_CONSTEXPR T days(ts_t start, ts_t stop) noexcept { + return days_between(start, stop); + } + + /// \brief Alias for days_between function. + /// \copydoc days_between + template + TIME_SHIELD_CONSTEXPR T get_days_difference(ts_t start, ts_t stop) noexcept { + return days_between(start, stop); + } + + /// \brief Alias for days_between function. + /// \copydoc days_between + template + TIME_SHIELD_CONSTEXPR T diff_in_days(ts_t start, ts_t stop) noexcept { + return days_between(start, stop); + } + +//------------------------------------------------------------------------------ + + /// \brief Alias for year_of function. + /// \copydoc year_of + template + TIME_SHIELD_CONSTEXPR inline T year(ts_t ts = time_shield::ts()) { + return year_of(ts); + } + + /// \brief Alias for year_of function. + /// \copydoc year_of + template + TIME_SHIELD_CONSTEXPR inline T to_year(ts_t ts = time_shield::ts()) { + return year_of(ts); + } + +//------------------------------------------------------------------------------ + + /// \brief Alias for year_of_ms function. + /// \copydoc year_of_ms + template + TIME_SHIELD_CONSTEXPR inline T year_ms(ts_ms_t ts_ms = time_shield::ts_ms()) { + return year_of_ms(ts_ms); + } + + /// \brief Alias for year_of_ms function. + /// \copydoc year_of_ms + template + TIME_SHIELD_CONSTEXPR inline T to_year_ms(ts_ms_t ts_ms = time_shield::ts_ms()) { + return year_of_ms(ts_ms); + } + +//------------------------------------------------------------------------------ + + /// \brief Alias for start_of_year function. + /// \copydoc start_of_year + TIME_SHIELD_CONSTEXPR inline ts_t year_start(ts_t ts = time_shield::ts()) { + return start_of_year(ts); + } + + /// \brief Alias for start_of_year function. + /// \copydoc start_of_year + TIME_SHIELD_CONSTEXPR inline ts_t year_begin(ts_t ts = time_shield::ts()) { + return start_of_year(ts); + } + +//------------------------------------------------------------------------------ + + /// \brief Alias for start_of_year_ms function. + /// \copydoc start_of_year_ms + inline ts_ms_t year_start_ms(ts_ms_t ts_ms = time_shield::ts_ms()) { + return start_of_year_ms(ts_ms); + } + + /// \brief Alias for start_of_year_ms function. + /// \copydoc start_of_year_ms + inline ts_ms_t year_begin_ms(ts_ms_t ts_ms = time_shield::ts_ms()) { + return start_of_year_ms(ts_ms); + } + +//------------------------------------------------------------------------------ + + /// \brief Alias for start_of_year_date function. + /// \copydoc start_of_year_date + template + TIME_SHIELD_CONSTEXPR inline ts_t year_start_date(T year) { + return start_of_year_date(year); + } + + /// \brief Alias for start_of_year_date function. + /// \copydoc start_of_year_date + template + TIME_SHIELD_CONSTEXPR inline ts_t year_begin_date(T year) { + return start_of_year_date(year); + } + +//------------------------------------------------------------------------------ + + /// \brief Alias for start_of_year_date_ms function. + /// \copydoc start_of_year_date_ms + template + TIME_SHIELD_CONSTEXPR inline ts_ms_t year_start_date_ms(T year) { + return start_of_year_date_ms(year); + } + + /// \brief Alias for start_of_year_date_ms function. + /// \copydoc start_of_year_date_ms + template + TIME_SHIELD_CONSTEXPR inline ts_ms_t year_begin_date_ms(T year) { + return start_of_year_date_ms(year); + } + +//------------------------------------------------------------------------------ + + /// \brief Alias for end_of_year function. + /// \copydoc end_of_year + TIME_SHIELD_CONSTEXPR inline ts_t year_end(ts_t ts = time_shield::ts()) { + return end_of_year(ts); + } + +//------------------------------------------------------------------------------ + + /// \brief Alias for end_of_year_ms function. + /// \copydoc end_of_year_ms + TIME_SHIELD_CONSTEXPR inline ts_ms_t year_end_ms(ts_ms_t ts_ms = time_shield::ts_ms()) { + return end_of_year_ms(ts_ms); + } + +//------------------------------------------------------------------------------ + + /// \brief Alias for num_days_in_month function. + /// \param year Year as an integer. + /// \param month Month as an integer. + /// \return The number of days in the given month and year. + template + TIME_SHIELD_CONSTEXPR T1 days_in_month(T2 year, T3 month) noexcept { + return num_days_in_month(year, month); + } + +//------------------------------------------------------------------------------ + + /// \brief Alias for num_days_in_month_ts function. + /// \copydoc num_days_in_month_ts + template + TIME_SHIELD_CONSTEXPR T1 num_days_in_month(ts_t ts = time_shield::ts()) noexcept { + return num_days_in_month_ts(ts); + } + + /// \brief Alias for num_days_in_month_ts function. + /// \copydoc num_days_in_month_ts + template + TIME_SHIELD_CONSTEXPR T1 days_in_month(ts_t ts = time_shield::ts()) noexcept { + return num_days_in_month_ts(ts); + } + +//------------------------------------------------------------------------------ + + /// \brief Alias for num_days_in_year function. + /// \copydoc num_days_in_year + template + TIME_SHIELD_CONSTEXPR T1 days_in_year(T2 year) noexcept { + return num_days_in_year(year); + } + +//------------------------------------------------------------------------------ + + /// \brief Alias for num_days_in_year_ts function. + /// \copydoc num_days_in_year_ts + template + TIME_SHIELD_CONSTEXPR T days_in_year_ts(ts_t ts = time_shield::ts()) { + return num_days_in_year_ts(ts); + } + +//------------------------------------------------------------------------------ + + /// \brief Alias for start_of_day function. + /// \copydoc start_of_day + TIME_SHIELD_CONSTEXPR inline ts_t day_start(ts_t ts = time_shield::ts()) noexcept { + return start_of_day(ts); + } + +//------------------------------------------------------------------------------ + + /// \brief Alias for start_of_prev_day function. + /// \copydoc start_of_prev_day + template + TIME_SHIELD_CONSTEXPR ts_t previous_day_start(ts_t ts = time_shield::ts(), T days = 1) noexcept { + return start_of_prev_day(ts, days); + } + +//------------------------------------------------------------------------------ + + /// \brief Alias for start_of_day_sec function. + /// \copydoc start_of_day_sec + TIME_SHIELD_CONSTEXPR inline ts_t day_start_sec(ts_ms_t ts_ms = time_shield::ts_ms()) noexcept { + return start_of_day(ms_to_sec(ts_ms)); + } + + /// \brief Alias for start_of_day_sec function. + /// \copydoc start_of_day_sec + TIME_SHIELD_CONSTEXPR inline ts_t start_day_sec(ts_ms_t ts_ms = time_shield::ts_ms()) noexcept { + return start_of_day(ms_to_sec(ts_ms)); + } + +//------------------------------------------------------------------------------ + + /// \brief Alias for start_of_day_ms function. + /// \copydoc start_of_day_ms + TIME_SHIELD_CONSTEXPR inline ts_ms_t day_start_ms(ts_ms_t ts_ms = time_shield::ts_ms()) noexcept { + return start_of_day_ms(ts_ms); + } + +//------------------------------------------------------------------------------ + + /// \brief Alias for start_of_next_day function. + /// \copydoc start_of_next_day + template + TIME_SHIELD_CONSTEXPR ts_t next_day_start(ts_t ts, T days = 1) noexcept { + return start_of_next_day(ts, days); + } + +//------------------------------------------------------------------------------ + + /// \brief Alias for start_of_next_day_ms function. + /// \copydoc start_of_next_day_ms + template + TIME_SHIELD_CONSTEXPR ts_ms_t next_day_start_ms(ts_ms_t ts_ms, T days = 1) noexcept { + return start_of_next_day_ms(ts_ms, days); + } + +//------------------------------------------------------------------------------ + + /// \brief Alias for end_of_day function. + /// \copydoc end_of_day + TIME_SHIELD_CONSTEXPR inline ts_t day_end(ts_t ts = time_shield::ts()) noexcept { + return end_of_day(ts); + } + +//------------------------------------------------------------------------------ + + /// \brief Alias for end_of_day_sec function. + /// \copydoc end_of_day_sec + TIME_SHIELD_CONSTEXPR inline ts_t day_end_sec(ts_ms_t ts_ms = time_shield::ts_ms()) noexcept { + return end_of_day_sec(ts_ms); + } + +//------------------------------------------------------------------------------ + + /// \brief Alias for end_of_day_ms function. + /// \copydoc end_of_day_ms + TIME_SHIELD_CONSTEXPR inline ts_ms_t day_end_ms(ts_ms_t ts_ms = time_shield::ts_ms()) noexcept { + return end_of_day_ms(ts_ms); + } + +//------------------------------------------------------------------------------ + + /// \brief Alias for day_of_week_date + /// \copydoc day_of_week_date + template + TIME_SHIELD_CONSTEXPR T1 day_of_week(year_t year, int month, int day) { + return day_of_week_date(year, month, day); + } + + /// \brief Alias for day_of_week_date + /// \copydoc day_of_week_date + template + TIME_SHIELD_CONSTEXPR T1 day_of_week(year_t year, Month month, int day) { + return day_of_week_date(year, static_cast(month), day); + } + + /// \brief Alias for day_of_week_date + /// \copydoc day_of_week_date + template + TIME_SHIELD_CONSTEXPR T1 get_weekday(year_t year, int month, int day) { + return day_of_week_date(year, month, day); + } + + /// \brief Alias for day_of_week_date + /// \copydoc day_of_week_date + template + TIME_SHIELD_CONSTEXPR T1 get_weekday(year_t year, Month month, int day) { + return day_of_week_date(year, static_cast(month), day); + } + + /// \brief Alias for day_of_week_date + /// \copydoc day_of_week_date + template + TIME_SHIELD_CONSTEXPR T1 weekday(year_t year, int month, int day) { + return day_of_week_date(year, month, day); + } + + /// \brief Alias for day_of_week_date + /// \copydoc day_of_week_date + template + TIME_SHIELD_CONSTEXPR T1 weekday(year_t year, Month month, int day) { + return day_of_week_date(year, static_cast(month), day); + } + + /// \brief Alias for day_of_week_date + /// \copydoc day_of_week_date + template + TIME_SHIELD_CONSTEXPR T1 dow(year_t year, int month, int day) { + return day_of_week_date(year, month, day); + } + + /// \brief Alias for day_of_week_date + /// \copydoc day_of_week_date + template + TIME_SHIELD_CONSTEXPR T1 dow(year_t year, Month month, int day) { + return day_of_week_date(year, static_cast(month), day); + } + +//------------------------------------------------------------------------------ + + /// \ingroup time_structures + /// \brief Alias for weekday_of_date + /// \copydoc weekday_of_date + template::value, int>::type = 0> + TIME_SHIELD_CONSTEXPR T1 get_dow(const T2& date) { + return weekday_of_date(date); + } + + /// \ingroup time_structures + /// \brief Alias for weekday_of_date + /// \copydoc weekday_of_date + template::value, int>::type = 0> + TIME_SHIELD_CONSTEXPR T1 dow_from_date(const T2& date) { + return weekday_of_date(date); + } + + /// \ingroup time_structures + /// \brief Alias for weekday_of_date + /// \copydoc weekday_of_date + template::value, int>::type = 0> + TIME_SHIELD_CONSTEXPR T1 weekday_of(const T2& date) { + return weekday_of_date(date); + } + + /// \ingroup time_structures + /// \brief Alias for weekday_of_date + /// \copydoc weekday_of_date + template::value, int>::type = 0> + TIME_SHIELD_CONSTEXPR T1 day_of_week_dt(const T2& date) { + return weekday_of_date(date); + } + + /// \ingroup time_structures + /// \brief Alias for weekday_of_date + /// \copydoc weekday_of_date + template::value, int>::type = 0> + TIME_SHIELD_CONSTEXPR T1 day_of_week(const T2& date) { + return weekday_of_date(date); + } + + /// \ingroup time_structures + /// \brief Alias for weekday_of_date + /// \copydoc weekday_of_date + template::value, int>::type = 0> + TIME_SHIELD_CONSTEXPR T1 dow(const T2& date) { + return weekday_of_date(date); + } + + /// \ingroup time_structures + /// \brief Alias for weekday_of_date + /// \copydoc weekday_of_date + template::value, int>::type = 0> + TIME_SHIELD_CONSTEXPR T1 wd(const T2& date) { + return weekday_of_date(date); + } + +//------------------------------------------------------------------------------ + + + /// \brief Alias for weekday_of_ts + /// \copydoc weekday_of_ts + template::value, int>::type = 0> + TIME_SHIELD_CONSTEXPR T day_of_week(U ts) noexcept { + return weekday_of_ts(ts); + } + + /// \brief Alias for weekday_of_ts + /// \copydoc weekday_of_ts + template::value, int>::type = 0> + TIME_SHIELD_CONSTEXPR T dow_ts(U ts) noexcept { + return weekday_of_ts(ts); + } + + /// \brief Alias for weekday_of_ts + /// \copydoc weekday_of_ts + template::value, int>::type = 0> + TIME_SHIELD_CONSTEXPR T get_dow_from_ts(U ts) noexcept { + return weekday_of_ts(ts); + } + +//------------------------------------------------------------------------------ + + /// \brief Alias for weekday_of_ts + /// \copydoc weekday_of_ts + template::value, int>::type = 0> + TIME_SHIELD_CONSTEXPR T wd_ts(U ts) noexcept { + return weekday_of_ts(ts); + } + +//------------------------------------------------------------------------------ + + /// \brief Alias for weekday_of_ts_ms function. + /// \copydoc weekday_of_ts_ms + template + TIME_SHIELD_CONSTEXPR T day_of_week_ms(ts_ms_t ts_ms) { + return weekday_of_ts_ms(ts_ms); + } + + /// \brief Alias for weekday_of_ts_ms function. + /// \copydoc weekday_of_ts_ms + template + TIME_SHIELD_CONSTEXPR T wd_ms(ts_ms_t ts_ms) { + return weekday_of_ts_ms(ts_ms); + } + +//------------------------------------------------------------------------------ + + /// \brief Alias for start_of_month function. + /// \copydoc start_of_month + TIME_SHIELD_CONSTEXPR inline ts_t month_begin(ts_t ts = time_shield::ts()) { + return start_of_month(ts); + } + +//------------------------------------------------------------------------------ + + /// \brief Alias for end_of_month function. + /// \copydoc end_of_month + TIME_SHIELD_CONSTEXPR inline ts_t last_day_of_month(ts_t ts = time_shield::ts()) { + return end_of_month(ts); + } + +//------------------------------------------------------------------------------ + + /// \brief Alias for last_sunday_of_month function. + /// \copydoc last_sunday_of_month + TIME_SHIELD_CONSTEXPR inline ts_t final_sunday_of_month(ts_t ts = time_shield::ts()) { + return last_sunday_of_month(ts); + } + +//------------------------------------------------------------------------------ + + /// \brief Alias for last_sunday_month_day function. + /// \copydoc last_sunday_month_day + template + TIME_SHIELD_CONSTEXPR inline T1 final_sunday_month_day(T2 year, T3 month) { + return last_sunday_month_day(year, month); + } + +//------------------------------------------------------------------------------ + + /// \brief Alias for start_of_hour function. + /// \copydoc start_of_hour + TIME_SHIELD_CONSTEXPR inline ts_t hour_begin(ts_t ts = time_shield::ts()) noexcept { + return start_of_hour(ts); + } + +//------------------------------------------------------------------------------ + + /// \brief Alias for start_of_hour_sec function. + /// \copydoc start_of_hour_sec + TIME_SHIELD_CONSTEXPR inline ts_t hour_begin_sec(ts_ms_t ts_ms = time_shield::ts_ms()) noexcept { + return start_of_hour_sec(ts_ms); + } + +//------------------------------------------------------------------------------ + + /// \brief Alias for start_of_hour_ms function. + /// \copydoc start_of_hour_ms + TIME_SHIELD_CONSTEXPR inline ts_ms_t hour_begin_ms(ts_ms_t ts_ms = time_shield::ts_ms()) noexcept { + return start_of_hour_ms(ts_ms); + } + +//------------------------------------------------------------------------------ + + /// \brief Alias for end_of_hour function. + /// \copydoc end_of_hour + TIME_SHIELD_CONSTEXPR inline ts_t finish_of_hour(ts_t ts = time_shield::ts()) noexcept { + return end_of_hour(ts); + } + +//------------------------------------------------------------------------------ + + /// \brief Alias for end_of_hour_sec function. + /// \copydoc end_of_hour_sec + TIME_SHIELD_CONSTEXPR inline ts_t finish_of_hour_sec(ts_ms_t ts_ms = time_shield::ts_ms()) noexcept { + return end_of_hour_sec(ts_ms); + } + +//------------------------------------------------------------------------------ + + /// \brief Alias for end_of_hour_ms function. + /// \copydoc end_of_hour_ms + TIME_SHIELD_CONSTEXPR inline ts_ms_t finish_of_hour_ms(ts_ms_t ts_ms = time_shield::ts_ms()) noexcept { + return end_of_hour_ms(ts_ms); + } + +//------------------------------------------------------------------------------ + + /// \brief Alias for hour_of_day function. + /// \copydoc hour_of_day + template + TIME_SHIELD_CONSTEXPR T hour_in_day(ts_t ts = time_shield::ts()) noexcept { + return hour_of_day(ts); + } + +//------------------------------------------------------------------------------ + + /// \brief Alias for start_of_week function. + /// \copydoc start_of_week + TIME_SHIELD_CONSTEXPR inline ts_t week_begin(ts_t ts = time_shield::ts()) { + return start_of_week(ts); + } + +//------------------------------------------------------------------------------ + + /// \brief Alias for end_of_week function. + /// \copydoc end_of_week + TIME_SHIELD_CONSTEXPR inline ts_t finish_of_week(ts_t ts = time_shield::ts()) { + return end_of_week(ts); + } + +//------------------------------------------------------------------------------ + + /// \brief Alias for start_of_saturday function. + /// \copydoc start_of_saturday + TIME_SHIELD_CONSTEXPR inline ts_t saturday_begin(ts_t ts = time_shield::ts()) { + return start_of_saturday(ts); + } + +//------------------------------------------------------------------------------ + + /// \brief Alias for start_of_min function. + /// \copydoc start_of_min + TIME_SHIELD_CONSTEXPR inline ts_t min_begin(ts_t ts = time_shield::ts()) noexcept { + return start_of_min(ts); + } + +//------------------------------------------------------------------------------ + + /// \brief Alias for end_of_min function. + /// \copydoc end_of_min + TIME_SHIELD_CONSTEXPR inline ts_t finish_of_min(ts_t ts = time_shield::ts()) noexcept { + return end_of_min(ts); + } + +//------------------------------------------------------------------------------ + + /// \brief Alias for is_workday(ts_t). + /// \copydoc is_workday(ts_t) + TIME_SHIELD_CONSTEXPR inline bool workday(ts_t ts) noexcept { + return is_workday(ts); + } + + /// \brief Alias for is_workday(ts_ms_t). + /// \copydoc is_workday(ts_ms_t) + TIME_SHIELD_CONSTEXPR inline bool workday_ms(ts_ms_t ts_ms) noexcept { + return is_workday_ms(ts_ms); + } + + /// \brief Alias for is_workday(year_t, int, int). + /// \copydoc is_workday(year_t, int, int) + TIME_SHIELD_CONSTEXPR inline bool workday(year_t year, int month, int day) noexcept { + return is_workday(year, month, day); + } + + /// \brief Alias for to_tz_offset. + /// \copydoc to_tz_offset + template + TIME_SHIELD_CONSTEXPR inline tz_t tz_offset(const T& tz) noexcept { + return to_tz_offset(tz); + } + + /// \brief Alias for tz_offset_hm. + /// \copydoc tz_offset_hm + TIME_SHIELD_CONSTEXPR inline tz_t offset_hm(int hour, int min = 0) noexcept { + return tz_offset_hm(hour, min); + } + + /// \brief Alias for is_valid_tz_offset. + /// \copydoc is_valid_tz_offset + TIME_SHIELD_CONSTEXPR inline bool valid_tz_offset(tz_t off) noexcept { + return is_valid_tz_offset(off); + } + +/// \} + +}; // namespace time_shield + +#endif // TIME_SHIELD_HEADER_CONVERSIONS_TIME_CONVERSION_ALIASES_HPP_INCLUDED diff --git a/include/time_shield/conversions/time_unit_conversions.hpp b/include/time_shield/conversions/time_unit_conversions.hpp new file mode 100644 index 00000000..9e903c0f --- /dev/null +++ b/include/time_shield/conversions/time_unit_conversions.hpp @@ -0,0 +1,485 @@ +// SPDX-License-Identifier: MIT +#pragma once +#ifndef TIME_SHIELD_HEADER_CONVERSIONS_TIME_UNIT_CONVERSIONS_HPP_INCLUDED +#define TIME_SHIELD_HEADER_CONVERSIONS_TIME_UNIT_CONVERSIONS_HPP_INCLUDED + +/// \file time_unit_conversions.hpp +/// \brief Helper functions for unit conversions between seconds, minutes, hours, and milliseconds. + +#include +#include "detail/floor_math.hpp" + +#include +#include + +namespace time_shield { + +/// \ingroup time_conversions +/// \{ + + /// \brief Get the nanosecond part of the second from a floating-point timestamp. + /// \tparam T Type of the returned value (default is int). + /// \param ts Timestamp in floating-point seconds. + /// \return T Nanosecond part of the second. + template + TIME_SHIELD_CONSTEXPR T ns_of_sec(fts_t ts) noexcept { + const int64_t ns = static_cast(std::floor(ts * static_cast(NS_PER_SEC))); + return static_cast(detail::floor_mod(ns, NS_PER_SEC)); + } + + /// \brief Get the microsecond part of the second from a floating-point timestamp. + /// \tparam T Type of the returned value (default is int). + /// \param ts Timestamp in floating-point seconds. + /// \return T Microsecond part of the second. + template + TIME_SHIELD_CONSTEXPR T us_of_sec(fts_t ts) noexcept { + const int64_t us = static_cast(std::floor(ts * static_cast(US_PER_SEC))); + return static_cast(detail::floor_mod(us, US_PER_SEC)); + } + + /// \brief Get the millisecond part of the second from a floating-point timestamp. + /// \tparam T Type of the returned value (default is int). + /// \param ts Timestamp in floating-point seconds. + /// \return T Millisecond part of the second. + template + TIME_SHIELD_CONSTEXPR T ms_of_sec(fts_t ts) noexcept { + const int64_t ms = static_cast(std::floor(ts * static_cast(MS_PER_SEC))); + return static_cast(detail::floor_mod(ms, MS_PER_SEC)); + } + + /// \brief Get the nanosecond part of the second from a floating-point timestamp (truncating). + /// \tparam T Type of the returned value (default is int). + /// \param ts Timestamp in floating-point seconds. + /// \return T Nanosecond part of the second, truncating toward zero. + template + TIME_SHIELD_CONSTEXPR T ns_of_sec_signed(fts_t ts) noexcept { + fts_t temp = 0; + return static_cast(std::round(std::modf(ts, &temp) * static_cast(NS_PER_SEC))); + } + + /// \brief Get the microsecond part of the second from a floating-point timestamp (truncating). + /// \tparam T Type of the returned value (default is int). + /// \param ts Timestamp in floating-point seconds. + /// \return T Microsecond part of the second, truncating toward zero. + template + TIME_SHIELD_CONSTEXPR T us_of_sec_signed(fts_t ts) noexcept { + fts_t temp = 0; + return static_cast(std::round(std::modf(ts, &temp) * static_cast(US_PER_SEC))); + } + + /// \brief Get the millisecond part of the second from a floating-point timestamp (truncating). + /// \tparam T Type of the returned value (default is int). + /// \param ts Timestamp in floating-point seconds. + /// \return T Millisecond part of the second, truncating toward zero. + template + TIME_SHIELD_CONSTEXPR T ms_of_sec_signed(fts_t ts) noexcept { + fts_t temp = 0; + return static_cast(std::round(std::modf(ts, &temp) * static_cast(MS_PER_SEC))); + } + + /// \brief Get the millisecond part of the timestamp. + /// \tparam T Type of the returned value (default is int). + /// \param ts Timestamp in milliseconds. + /// \return T Millisecond part of the timestamp. + template + TIME_SHIELD_CONSTEXPR T ms_part(ts_ms_t ts) noexcept { + return static_cast(detail::floor_mod(static_cast(ts), MS_PER_SEC)); + } + + /// \brief Alias for ms_part. + /// \tparam T Type of the returned value (default is int). + /// \param ts Timestamp in milliseconds. + /// \return T Millisecond part of the timestamp. + template + TIME_SHIELD_CONSTEXPR T ms_of_ts(ts_ms_t ts) noexcept { + return ms_part(ts); + } + + /// \brief Get the microsecond part of the timestamp. + /// \tparam T Type of the returned value (default is int). + /// \param ts Timestamp in microseconds. + /// \return T Microsecond part of the timestamp. + template + TIME_SHIELD_CONSTEXPR T us_part(ts_us_t ts) noexcept { + return static_cast(detail::floor_mod(static_cast(ts), US_PER_SEC)); + } + + /// \brief Alias for us_part. + /// \tparam T Type of the returned value (default is int). + /// \param ts Timestamp in microseconds. + /// \return T Microsecond part of the timestamp. + template + TIME_SHIELD_CONSTEXPR T us_of_ts(ts_us_t ts) noexcept { + return us_part(ts); + } + + /// \brief Get the nanosecond part of the timestamp. + /// \tparam T Type of the returned value (default is int). + /// \param ts Timestamp in nanoseconds. + /// \return T Nanosecond part of the timestamp. + template + TIME_SHIELD_CONSTEXPR T ns_part(T2 ts) noexcept { + return static_cast(detail::floor_mod(static_cast(ts), NS_PER_SEC)); + } + +# ifndef TIME_SHIELD_CPP17 + /// \brief Helper function for converting seconds to milliseconds (floating-point version). + /// \tparam T Type of the input timestamp. + /// \param t Timestamp in seconds. + /// \param tag std::true_type indicates a floating-point type. + /// \return ts_ms_t Timestamp in milliseconds. + template + TIME_SHIELD_CONSTEXPR ts_ms_t sec_to_ms_impl(T t, std::true_type) noexcept { + return static_cast(std::round(t * static_cast(MS_PER_SEC))); + } + + /// \brief Helper function for converting seconds to milliseconds (integral version). + /// \tparam T Type of the input timestamp. + /// \param t Timestamp in seconds. + /// \param tag std::false_type indicates a non-floating-point type. + /// \return ts_ms_t Timestamp in milliseconds. + template + TIME_SHIELD_CONSTEXPR ts_ms_t sec_to_ms_impl(T t, std::false_type) noexcept { + return static_cast(t) * static_cast(MS_PER_SEC); + } +# endif // TIME_SHIELD_CPP17 + + /// \brief Converts a timestamp from seconds to milliseconds. + /// \tparam T1 The type of the output timestamp (default is ts_ms_t). + /// \tparam T2 The type of the input timestamp. + /// \param ts Timestamp in seconds. + /// \return T1 Timestamp in milliseconds. + template + TIME_SHIELD_CONSTEXPR T1 sec_to_ms(T2 ts) noexcept { +# ifdef TIME_SHIELD_CPP17 + if constexpr (std::is_floating_point_v) { + return static_cast(std::round(ts * static_cast(MS_PER_SEC))); + } else { + return static_cast(ts) * static_cast(MS_PER_SEC); + } +# else + return static_cast(sec_to_ms_impl(ts, typename std::conditional< + (std::is_same::value || std::is_same::value), + std::true_type, + std::false_type + >::type{})); +# endif + } + + /// \brief Converts a floating-point timestamp from seconds to milliseconds. + /// \param ts Timestamp in floating-point seconds. + /// \return ts_ms_t Timestamp in milliseconds. + inline ts_ms_t fsec_to_ms(fts_t ts) noexcept { + return static_cast(std::round(ts * static_cast(MS_PER_SEC))); + } + + /// \brief Converts a timestamp from milliseconds to seconds. + /// \tparam T1 The type of the output timestamp (default is ts_t). + /// \tparam T2 The type of the input timestamp (default is ts_ms_t). + /// \param ts_ms Timestamp in milliseconds. + /// \return T1 Timestamp in seconds. + template + TIME_SHIELD_CONSTEXPR T1 ms_to_sec(T2 ts_ms) noexcept { + return static_cast(detail::floor_div( + static_cast(ts_ms), + static_cast(MS_PER_SEC))); + } + + /// \brief Converts a timestamp from milliseconds to floating-point seconds. + /// \tparam T The type of the input timestamp (default is ts_ms_t). + /// \param ts_ms Timestamp in milliseconds. + /// \return fts_t Timestamp in floating-point seconds. + template + TIME_SHIELD_CONSTEXPR fts_t ms_to_fsec(T ts_ms) noexcept { + return static_cast(ts_ms) / static_cast(MS_PER_SEC); + } + +//----------------------------------------------------------------------------// +// Minutes -> Milliseconds +//----------------------------------------------------------------------------// +# ifndef TIME_SHIELD_CPP17 + /// \brief Helper function for converting minutes to milliseconds (floating-point version). + /// \tparam T Type of the input timestamp. + /// \param t Timestamp in minutes. + /// \param tag std::true_type indicates a floating-point type (double or float). + /// \return ts_ms_t Timestamp in milliseconds. + template + TIME_SHIELD_CONSTEXPR ts_ms_t min_to_ms_impl(T t, std::true_type) noexcept { + return static_cast(std::round(t * static_cast(MS_PER_MIN))); + } + + /// \brief Helper function for converting minutes to milliseconds (integral version). + /// \tparam T Type of the input timestamp. + /// \param t Timestamp in minutes. + /// \param tag std::false_type indicates a non-floating-point type. + /// \return ts_ms_t Timestamp in milliseconds. + template + TIME_SHIELD_CONSTEXPR ts_ms_t min_to_ms_impl(T t, std::false_type) noexcept { + return static_cast(t) * static_cast(MS_PER_MIN); + } +# endif // TIME_SHIELD_CPP17 + + /// \brief Converts a timestamp from minutes to milliseconds. + /// \tparam T1 The type of the output timestamp (default is ts_ms_t). + /// \tparam T2 The type of the input timestamp. + /// \param ts Timestamp in minutes. + /// \return T1 Timestamp in milliseconds. + template + TIME_SHIELD_CONSTEXPR T1 min_to_ms(T2 ts) noexcept { +# ifdef TIME_SHIELD_CPP17 + if constexpr (std::is_floating_point_v) { + return static_cast(std::round(ts * static_cast(MS_PER_MIN))); + } else { + return static_cast(ts) * static_cast(MS_PER_MIN); + } +# else + return static_cast(min_to_ms_impl(ts, typename std::conditional< + (std::is_same::value || std::is_same::value), + std::true_type, + std::false_type + >::type{})); +# endif + } + + /// \brief Converts a timestamp from milliseconds to minutes. + /// \tparam T1 The type of the output timestamp (default is int). + /// \tparam T2 The type of the input timestamp (default is ts_ms_t). + /// \param ts Timestamp in milliseconds. + /// \return T1 Timestamp in minutes. + template + TIME_SHIELD_CONSTEXPR T1 ms_to_min(T2 ts) noexcept { + return static_cast(detail::floor_div( + static_cast(ts), + static_cast(MS_PER_MIN))); + } + +//----------------------------------------------------------------------------// +// Minutes -> Seconds +//----------------------------------------------------------------------------// +# ifndef TIME_SHIELD_CPP17 + /// \brief Helper function for converting minutes to seconds (floating-point version). + /// \tparam T Type of the input timestamp. + /// \param t Timestamp in minutes. + /// \param tag std::true_type indicates a floating-point type (double or float). + /// \return ts_t Timestamp in seconds. + template + TIME_SHIELD_CONSTEXPR ts_t min_to_sec_impl(T t, std::true_type) noexcept { + return static_cast(std::round(t * static_cast(SEC_PER_MIN))); + } + + /// \brief Helper function for converting minutes to seconds (integral version). + /// \tparam T Type of the input timestamp. + /// \param t Timestamp in minutes. + /// \param tag std::false_type indicates a non-floating-point type. + /// \return ts_t Timestamp in seconds. + template + TIME_SHIELD_CONSTEXPR ts_t min_to_sec_impl(T t, std::false_type) noexcept { + return static_cast(t) * static_cast(SEC_PER_MIN); + } +# endif // TIME_SHIELD_CPP17 + + /// \brief Converts a timestamp from minutes to seconds. + /// \tparam T1 The type of the output timestamp (default is ts_t). + /// \tparam T2 The type of the input timestamp. + /// \param ts Timestamp in minutes. + /// \return T1 Timestamp in seconds. + template + TIME_SHIELD_CONSTEXPR T1 min_to_sec(T2 ts) noexcept { +# ifdef TIME_SHIELD_CPP17 + if constexpr (std::is_floating_point_v) { + return static_cast(std::round(ts * static_cast(SEC_PER_MIN))); + } else { + return static_cast(ts) * static_cast(SEC_PER_MIN); + } +# else + return static_cast(min_to_sec_impl(ts, typename std::conditional< + (std::is_same::value || std::is_same::value), + std::true_type, + std::false_type + >::type{})); +# endif + } + + /// \brief Converts a timestamp from seconds to minutes. + /// \tparam T1 The type of the output timestamp (default is int). + /// \tparam T2 The type of the input timestamp (default is ts_t). + /// \param ts Timestamp in seconds. + /// \return T1 Timestamp in minutes. + template + TIME_SHIELD_CONSTEXPR T1 sec_to_min(T2 ts) noexcept { + return static_cast(detail::floor_div( + static_cast(ts), + static_cast(SEC_PER_MIN))); + } + + /// \brief Converts a timestamp from minutes to floating-point seconds. + /// \tparam T The type of the input timestamp (default is int). + /// \param min Timestamp in minutes. + /// \return fts_t Timestamp in floating-point seconds. + template + TIME_SHIELD_CONSTEXPR fts_t min_to_fsec(T min) noexcept { + return static_cast(min) * static_cast(SEC_PER_MIN); + } + + /// \brief Converts a timestamp from seconds to floating-point minutes. + /// \tparam T The type of the input timestamp (default is ts_t). + /// \param ts Timestamp in seconds. + /// \return double Timestamp in floating-point minutes. + template + TIME_SHIELD_CONSTEXPR double sec_to_fmin(T ts) noexcept { + return static_cast(ts) / static_cast(SEC_PER_MIN); + } + +//----------------------------------------------------------------------------// +// Hours -> Milliseconds +//----------------------------------------------------------------------------// + +# ifndef TIME_SHIELD_CPP17 + /// \brief Helper function for converting hours to milliseconds (floating-point version). + /// \tparam T Type of the input timestamp. + /// \param t Timestamp in hours. + /// \param tag std::true_type indicates a floating-point type (double or float). + /// \return ts_ms_t Timestamp in milliseconds. + template + TIME_SHIELD_CONSTEXPR ts_ms_t hour_to_ms_impl(T t, std::true_type) noexcept { + return static_cast(std::round(t * static_cast(MS_PER_HOUR))); + } + + /// \brief Helper function for converting hours to milliseconds (integral version). + /// \tparam T Type of the input timestamp. + /// \param t Timestamp in hours. + /// \param tag Type tag used to select the integral overload (must be std::false_type). + /// \return ts_ms_t Timestamp in milliseconds. + template + TIME_SHIELD_CONSTEXPR ts_ms_t hour_to_ms_impl(T t, std::false_type) noexcept { + return static_cast(t) * static_cast(MS_PER_HOUR); + } +# endif // TIME_SHIELD_CPP17 + + /// \brief Converts a timestamp from hours to milliseconds. + /// \tparam T1 The type of the output timestamp (default is ts_ms_t). + /// \tparam T2 The type of the input timestamp. + /// \param ts Timestamp in hours. + /// \return T1 Timestamp in milliseconds. + template + TIME_SHIELD_CONSTEXPR T1 hour_to_ms(T2 ts) noexcept { +# ifdef TIME_SHIELD_CPP17 + if constexpr (std::is_floating_point_v) { + return static_cast(std::round(ts * static_cast(MS_PER_HOUR))); + } else { + return static_cast(ts) * static_cast(MS_PER_HOUR); + } +# else + return static_cast(hour_to_ms_impl(ts, typename std::conditional< + (std::is_same::value || std::is_same::value), + std::true_type, + std::false_type + >::type{})); +# endif + } + + /// \brief Converts a timestamp from milliseconds to hours. + /// \tparam T1 The type of the output timestamp (default is int). + /// \tparam T2 The type of the input timestamp (default is ts_ms_t). + /// \param ts Timestamp in milliseconds. + /// \return T1 Timestamp in hours. + template + TIME_SHIELD_CONSTEXPR T1 ms_to_hour(T2 ts) noexcept { + return static_cast(detail::floor_div( + static_cast(ts), + static_cast(MS_PER_HOUR))); + } + +//----------------------------------------------------------------------------// +// Hours -> Seconds +//----------------------------------------------------------------------------// + +# ifndef TIME_SHIELD_CPP17 + /// \brief Helper function for converting hours to seconds (floating-point version). + /// \tparam T Type of the input timestamp. + /// \param t Timestamp in hours. + /// \param tag std::true_type indicates a floating-point type (double or float). + /// \return ts_t Timestamp in seconds. + template + TIME_SHIELD_CONSTEXPR ts_t hour_to_sec_impl(T t, std::true_type) noexcept { + return static_cast(std::round(t * static_cast(SEC_PER_HOUR))); + } + + /// \brief Helper function for converting hours to seconds (integral version). + /// \tparam T Type of the input timestamp. + /// \param t Timestamp in hours. + /// \param tag std::false_type indicates a non-floating-point type. + /// \return ts_t Timestamp in seconds. + template + TIME_SHIELD_CONSTEXPR ts_t hour_to_sec_impl(T t, std::false_type) noexcept { + return static_cast(t) * static_cast(SEC_PER_HOUR); + } +# endif // TIME_SHIELD_CPP17 + + /// \brief Converts a timestamp from hours to seconds. + /// \tparam T1 The type of the output timestamp (default is ts_t). + /// \tparam T2 The type of the input timestamp. + /// \param ts Timestamp in hours. + /// \return T1 Timestamp in seconds. + template + TIME_SHIELD_CONSTEXPR T1 hour_to_sec(T2 ts) noexcept { +# ifdef TIME_SHIELD_CPP17 + if constexpr (std::is_floating_point_v) { + return static_cast(std::round(ts * static_cast(SEC_PER_HOUR))); + } else { + return static_cast(ts) * static_cast(SEC_PER_HOUR); + } +# else + return static_cast(hour_to_sec_impl(ts, typename std::conditional< + (std::is_same::value || std::is_same::value), + std::true_type, + std::false_type + >::type{})); +# endif + } + + /// \brief Converts a timestamp from seconds to hours. + /// \tparam T1 The type of the output timestamp (default is int). + /// \tparam T2 The type of the input timestamp (default is ts_t). + /// \param ts Timestamp in seconds. + /// \return T1 Timestamp in hours. + template + TIME_SHIELD_CONSTEXPR T1 sec_to_hour(T2 ts) noexcept { + return static_cast(detail::floor_div( + static_cast(ts), + static_cast(SEC_PER_HOUR))); + } + + /// \brief Converts a timestamp from hours to floating-point seconds. + /// \tparam T The type of the input timestamp (default is int). + /// \param hr Timestamp in hours. + /// \return fts_t Timestamp in floating-point seconds. + template + TIME_SHIELD_CONSTEXPR fts_t hour_to_fsec(T hr) noexcept { + return static_cast(hr) * static_cast(SEC_PER_HOUR); + } + + /// \brief Converts a timestamp from seconds to floating-point hours. + /// \tparam T The type of the input timestamp (default is ts_t). + /// \param ts Timestamp in seconds. + /// \return double Timestamp in floating-point hours. + template + TIME_SHIELD_CONSTEXPR double sec_to_fhour(T ts) noexcept { + return static_cast(ts) / static_cast(SEC_PER_HOUR); + } + + /// \brief Converts a 24-hour format hour to a 12-hour format. + /// \tparam T Numeric type of the hour (default is int). + /// \param hour The hour in 24-hour format to convert. + /// \return The hour in 12-hour format. + template + TIME_SHIELD_CONSTEXPR inline T hour24_to_12(T hour) noexcept { + if (hour == 0 || hour > 12) return 12; + return hour; + } + +/// \} + +}; // namespace time_shield + +#endif // TIME_SHIELD_HEADER_CONVERSIONS_TIME_UNIT_CONVERSIONS_HPP_INCLUDED diff --git a/include/time_shield/conversions/time_zone_offset.hpp b/include/time_shield/conversions/time_zone_offset.hpp new file mode 100644 index 00000000..510da244 --- /dev/null +++ b/include/time_shield/conversions/time_zone_offset.hpp @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: MIT +#pragma once +#ifndef TIME_SHIELD_HEADER_CONVERSIONS_TIME_ZONE_OFFSET_HPP_INCLUDED +#define TIME_SHIELD_HEADER_CONVERSIONS_TIME_ZONE_OFFSET_HPP_INCLUDED + +/// \file time_zone_offset.hpp +/// \brief UTC offset arithmetic helpers (UTC <-> local) and TimeZoneStruct offset extraction. +/// \ingroup time_zone_conversions +/// +/// This header provides simple, allocation-free conversions between: +/// - UTC timestamp and local timestamp using a numeric UTC offset (in seconds). +/// - UTC milliseconds and local milliseconds using the same offset. +/// +/// \note The offset is interpreted as an UTC offset in seconds, i.e.: +/// local = utc + utc_offset +/// utc = local - utc_offset +/// +/// \note If the input equals ERROR_TIMESTAMP, the functions return ERROR_TIMESTAMP unchanged. + +#include +#include "time_unit_conversions.hpp" + +namespace time_shield { + + /// \ingroup time_conversions_time_zone_conversions + /// \{ + + /// \brief Convert local timestamp (seconds) to UTC using UTC offset. + /// \param local Local timestamp in seconds. + /// \param utc_offset UTC offset in seconds (e.g. CET=+3600, MSK=+10800, EST=-18000). + /// \return UTC timestamp in seconds. If \p local equals ERROR_TIMESTAMP, returns ERROR_TIMESTAMP. + TIME_SHIELD_CONSTEXPR inline ts_t to_utc(ts_t local, tz_t utc_offset) noexcept { + return local == ERROR_TIMESTAMP ? ERROR_TIMESTAMP + : local - static_cast(utc_offset); + } + + /// \brief Convert UTC timestamp (seconds) to local time using UTC offset. + /// \param utc UTC timestamp in seconds. + /// \param utc_offset UTC offset in seconds (e.g. CET=+3600, MSK=+10800, EST=-18000). + /// \return Local timestamp in seconds. If \p utc equals ERROR_TIMESTAMP, returns ERROR_TIMESTAMP. + TIME_SHIELD_CONSTEXPR inline ts_t to_local(ts_t utc, tz_t utc_offset) noexcept { + return utc == ERROR_TIMESTAMP ? ERROR_TIMESTAMP + : utc + static_cast(utc_offset); + } + + /// \brief Convert local timestamp (milliseconds) to UTC using UTC offset. + /// \param local_ms Local timestamp in milliseconds. + /// \param utc_offset UTC offset in seconds (will be converted to milliseconds). + /// \return UTC timestamp in milliseconds. If \p local_ms equals ERROR_TIMESTAMP, returns ERROR_TIMESTAMP. + TIME_SHIELD_CONSTEXPR inline ts_ms_t to_utc_ms(ts_ms_t local_ms, tz_t utc_offset) noexcept { + return local_ms == ERROR_TIMESTAMP ? ERROR_TIMESTAMP + : local_ms - sec_to_ms(utc_offset); + } + + /// \brief Convert UTC timestamp (milliseconds) to local time using UTC offset. + /// \param utc_ms UTC timestamp in milliseconds. + /// \param utc_offset UTC offset in seconds (will be converted to milliseconds). + /// \return Local timestamp in milliseconds. If \p utc_ms equals ERROR_TIMESTAMP, returns ERROR_TIMESTAMP. + TIME_SHIELD_CONSTEXPR inline ts_ms_t to_local_ms(ts_ms_t utc_ms, tz_t utc_offset) noexcept { + return utc_ms == ERROR_TIMESTAMP ? ERROR_TIMESTAMP + : utc_ms + sec_to_ms(utc_offset); + } + + /// \brief Extract numeric UTC offset (in seconds) from TimeZoneStruct. + /// \param tz Time zone descriptor. + /// \return UTC offset in seconds (local = utc + offset). + TIME_SHIELD_CONSTEXPR inline tz_t utc_offset_of(const TimeZoneStruct& tz) noexcept { + return time_zone_struct_to_offset(tz); + } + + /// \} + +} // namespace time_shield + +#endif // TIME_SHIELD_HEADER_CONVERSIONS_TIME_ZONE_OFFSET_HPP_INCLUDED diff --git a/include/time_shield/conversions/time_zone_offset_conversions.hpp b/include/time_shield/conversions/time_zone_offset_conversions.hpp new file mode 100644 index 00000000..de5248e3 --- /dev/null +++ b/include/time_shield/conversions/time_zone_offset_conversions.hpp @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: MIT +#pragma once +#ifndef TIME_SHIELD_HEADER_CONVERSIONS_TIME_ZONE_OFFSET_CONVERSIONS_HPP_INCLUDED +#define TIME_SHIELD_HEADER_CONVERSIONS_TIME_ZONE_OFFSET_CONVERSIONS_HPP_INCLUDED + +/// \file time_zone_offset_conversions.hpp +/// \brief Conversions between numeric offsets and TimeZoneStruct. + +#include + +namespace time_shield { + +/// \ingroup time_conversions +/// \{ + + /// \brief Converts an integer to a time zone structure. + /// \tparam T The type of the time zone structure (default is TimeZoneStruct). + /// \param offset The integer to convert. + /// \return A time zone structure of type T represented by the given integer. + /// \details The function assumes that the type T has members `hour`, `min`, and `is_positive`. + template + inline T to_time_zone(tz_t offset) { + const int64_t off = static_cast(offset); + const int64_t abs_val = (off < 0) ? -off : off; + + T tz; + tz.hour = static_cast(abs_val / static_cast(SEC_PER_HOUR)); + tz.min = static_cast( + (abs_val % static_cast(SEC_PER_HOUR)) / static_cast(SEC_PER_MIN) + ); + tz.is_positive = (off >= 0); + return tz; + } + + /// \brief Convert time zone struct to offset in seconds. + /// \details Expects fields: hour, min, is_positive. + template + TIME_SHIELD_CONSTEXPR inline tz_t to_tz_offset(const T& tz) noexcept { + const int sign = tz.is_positive ? 1 : -1; + const int64_t sec = static_cast(tz.hour) * SEC_PER_HOUR + + static_cast(tz.min) * SEC_PER_MIN; + return static_cast(sign * sec); + } + + /// \brief Build offset in seconds from hours/minutes. + /// \param hour Signed hours (e.g. -3, +5). + /// \param min Minutes (0..59). + TIME_SHIELD_CONSTEXPR inline tz_t tz_offset_hm(int hour, int min = 0) noexcept { + const int sign = (hour < 0) ? -1 : 1; + const int64_t ah = (hour < 0) ? -static_cast(hour) : static_cast(hour); + const int64_t am = (min < 0) ? -static_cast(min) : static_cast(min); + return static_cast(sign * (ah * SEC_PER_HOUR + am * SEC_PER_MIN)); + } + + /// \brief Check if a numeric offset is within supported bounds. + /// \details Conservative range: [-12:00, +14:00]. + TIME_SHIELD_CONSTEXPR inline bool is_valid_tz_offset(tz_t off) noexcept { + // conservative range: [-12:00, +14:00] + return off % 60 == 0 + && off >= -12 * SEC_PER_HOUR + && off <= 14 * SEC_PER_HOUR; + } + +/// \} + +}; // namespace time_shield + +#endif // TIME_SHIELD_HEADER_CONVERSIONS_TIME_ZONE_OFFSET_CONVERSIONS_HPP_INCLUDED diff --git a/include/time_shield/conversions/unix_time_conversions.hpp b/include/time_shield/conversions/unix_time_conversions.hpp new file mode 100644 index 00000000..7618e449 --- /dev/null +++ b/include/time_shield/conversions/unix_time_conversions.hpp @@ -0,0 +1,329 @@ +// SPDX-License-Identifier: MIT +#pragma once +#ifndef TIME_SHIELD_HEADER_CONVERSIONS_UNIX_TIME_CONVERSIONS_HPP_INCLUDED +#define TIME_SHIELD_HEADER_CONVERSIONS_UNIX_TIME_CONVERSIONS_HPP_INCLUDED + +/// \file unix_time_conversions.hpp +/// \brief Conversions related to UNIX-based time units and epochs. + +#include +#include "detail/fast_date.hpp" +#include "time_unit_conversions.hpp" + +namespace time_shield { + +/// \ingroup time_conversions +/// \{ + + namespace legacy { + + /// \brief Converts a UNIX timestamp to a year. + /// \tparam T The type of the year (default is year_t). + /// \param ts UNIX timestamp. + /// \return T Year corresponding to the given timestamp. + template + TIME_SHIELD_CONSTEXPR T years_since_epoch(ts_t ts) noexcept { + // 9223372029693630000 reaches year 292277024400 from the 2000 epoch. + // This value overflows the n_400_years * SEC_PER_400_YEARS calculation. + // The supported bound is reduced to 9223371890843040000. + constexpr int64_t BIAS_292277022000 = 9223371890843040000LL; + constexpr int64_t BIAS_2000 = 946684800LL; + + int64_t y = MAX_YEAR; + int64_t secs = -((ts - BIAS_2000) - BIAS_292277022000); + + const int64_t n_400_years = secs / SEC_PER_400_YEARS; + secs -= n_400_years * SEC_PER_400_YEARS; + y -= n_400_years * 400; + + const int64_t n_100_years = secs / SEC_PER_100_YEARS; + secs -= n_100_years * SEC_PER_100_YEARS; + y -= n_100_years * 100; + + const int64_t n_4_years = secs / SEC_PER_4_YEARS; + secs -= n_4_years * SEC_PER_4_YEARS; + y -= n_4_years * 4; + + const int64_t n_1_years = secs / SEC_PER_YEAR; + secs -= n_1_years * SEC_PER_YEAR; + y -= n_1_years; + + y = secs == 0 ? y : y - 1; + return y - UNIX_EPOCH; + } + + } // namespace legacy + + /// \brief Converts a UNIX timestamp to a year. + /// \tparam T The type of the year (default is year_t). + /// \param ts UNIX timestamp. + /// \return T Year corresponding to the given timestamp. + /// \note Inspired by the algorithm described in: + /// https://www.benjoffe.com/fast-date-64 + /// This implementation is written from scratch (no code copied). + template + TIME_SHIELD_CONSTEXPR T years_since_epoch(ts_t ts) noexcept { + const detail::DaySplit split = detail::split_unix_day(ts); + const int64_t year = detail::fast_year_from_days_constexpr(split.days); + return static_cast(year - UNIX_EPOCH); + } + + namespace legacy { + + /// \brief Convert a calendar date to UNIX day count. + /// + /// Calculates the number of days since the UNIX epoch (January 1, 1970) + /// for the provided calendar date components. + /// + /// \tparam Year Type of the year component. + /// \tparam Month Type of the month component. + /// \tparam Day Type of the day component. + /// \param year Year component of the date. + /// \param month Month component of the date. + /// \param day Day component of the date. + /// \return Number of days since the UNIX epoch. + template + TIME_SHIELD_CONSTEXPR inline dse_t date_to_unix_day( + Year year, + Month month, + Day day) noexcept { + const int64_t y = static_cast(year) - (static_cast(month) <= 2 ? 1 : 0); + const int64_t m = static_cast(month) <= 2 + ? static_cast(month) + 9 + : static_cast(month) - 3; + const int64_t era = (y >= 0 ? y : y - 399) / 400; + const int64_t yoe = y - era * 400; + const int64_t doy = (153 * m + 2) / 5 + static_cast(day) - 1; + const int64_t doe = yoe * 365 + yoe / 4 - yoe / 100 + doy; + return static_cast(era * 146097 + doe - 719468); + } + + } // namespace legacy + + /// \brief Convert a calendar date to UNIX day count. + /// + /// Calculates the number of days since the UNIX epoch (January 1, 1970) + /// for the provided calendar date components. + /// \note Inspired by the algorithm described in: + /// https://www.benjoffe.com/fast-date-64 + /// This implementation is written from scratch (no code copied). + /// + /// \tparam Year Type of the year component. + /// \tparam Month Type of the month component. + /// \tparam Day Type of the day component. + /// \param year Year component of the date. + /// \param month Month component of the date. + /// \param day Day component of the date. + /// \return Number of days since the UNIX epoch. + template + TIME_SHIELD_CONSTEXPR inline dse_t date_to_unix_day( + Year year, + Month month, + Day day) noexcept { + return static_cast( + detail::fast_days_from_date_constexpr( + static_cast(year), + static_cast(month), + static_cast(day))); + } + + /// \brief Get UNIX day. + /// + /// This function returns the number of days elapsed since the UNIX epoch. + /// + /// \tparam T The return type of the function (default is unixday_t). + /// \param ts Timestamp in seconds (default is current timestamp). + /// \return Number of days since the UNIX epoch. + template + TIME_SHIELD_CONSTEXPR T days_since_epoch(ts_t ts = time_shield::ts()) noexcept { + return ts / SEC_PER_DAY; + } + + /// \brief Get UNIX day from milliseconds timestamp. + /// + /// This function returns the number of days elapsed since the UNIX epoch, given a timestamp in milliseconds. + /// + /// \tparam T The return type of the function (default is unixday_t). + /// \param t_ms Timestamp in milliseconds (default is current timestamp in milliseconds). + /// \return Number of days since the UNIX epoch. + template + TIME_SHIELD_CONSTEXPR T days_since_epoch_ms(ts_ms_t t_ms = time_shield::ts_ms()) noexcept { + return days_since_epoch(ms_to_sec(t_ms)); + } + + /// \brief Get the number of days between two timestamps. + /// + /// This function calculates the number of days between two timestamps. + /// + /// \tparam T The type of the return value, defaults to int. + /// \param start The timestamp of the start of the period. + /// \param stop The timestamp of the end of the period. + /// \return The number of days between start and stop. + template + TIME_SHIELD_CONSTEXPR T days_between(ts_t start, ts_t stop) noexcept { + return static_cast((stop - start) / SEC_PER_DAY); + } + + /// \brief Converts a UNIX day to a timestamp in seconds. + /// + /// Converts a number of days since the UNIX epoch (January 1, 1970) to the corresponding + /// timestamp in seconds at the start of the specified day. + /// + /// \tparam T The return type of the function (default is ts_t). + /// \param unix_day Number of days since the UNIX epoch. + /// \return The timestamp in seconds representing the beginning of the specified UNIX day. + template + TIME_SHIELD_CONSTEXPR T unix_day_to_ts(dse_t unix_day) noexcept { + return unix_day * SEC_PER_DAY; + } + + /// \brief Converts a UNIX day to a timestamp in milliseconds. + /// + /// Converts a number of days since the UNIX epoch (January 1, 1970) to the corresponding timestamp + /// in milliseconds at the start of the specified day. + /// + /// \tparam T The return type of the function (default is ts_ms_t). + /// \param unix_day Number of days since the UNIX epoch. + /// \return The timestamp in milliseconds representing the beginning of the specified UNIX day. + template + TIME_SHIELD_CONSTEXPR T unix_day_to_ts_ms(dse_t unix_day) noexcept { + return unix_day * MS_PER_DAY; + } + + /// \brief Converts a UNIX day to a timestamp representing the end of the day in seconds. + /// + /// Converts a number of days since the UNIX epoch (January 1, 1970) to the corresponding + /// timestamp in seconds at the end of the specified day (23:59:59). + /// + /// \tparam T The return type of the function (default is ts_t). + /// \param unix_day The number of days since the UNIX epoch. + /// \return The timestamp in seconds representing the end of the specified UNIX day. + template + TIME_SHIELD_CONSTEXPR T end_of_day_from_unix_day(dse_t unix_day) noexcept { + return unix_day * SEC_PER_DAY + SEC_PER_DAY - 1; + } + + /// \brief Converts a UNIX day to a timestamp representing the end of the day in milliseconds. + /// + /// Converts a number of days since the UNIX epoch (January 1, 1970) to the corresponding + /// timestamp in milliseconds at the end of the specified day (23:59:59.999). + /// + /// \tparam T The return type of the function (default is ts_ms_t). + /// \param unix_day The number of days since the UNIX epoch. + /// \return The timestamp in milliseconds representing the end of the specified UNIX day. + template + TIME_SHIELD_CONSTEXPR T end_of_day_from_unix_day_ms(dse_t unix_day) noexcept { + return unix_day * MS_PER_DAY + MS_PER_DAY - 1; + } + + /// \brief Converts a UNIX day to a timestamp representing the start of the next day in seconds. + /// + /// Converts a number of days since the UNIX epoch (January 1, 1970) to the corresponding + /// timestamp in seconds at the start of the next day (00:00:00). + /// + /// \tparam T The return type of the function (default is ts_t). + /// \param unix_day The number of days since the UNIX epoch. + /// \return The timestamp in seconds representing the beginning of the next UNIX day. + template + TIME_SHIELD_CONSTEXPR T start_of_next_day_from_unix_day(dse_t unix_day) noexcept { + return unix_day * SEC_PER_DAY + SEC_PER_DAY; + } + + /// \brief Converts a UNIX day to a timestamp representing the start of the next day in milliseconds. + /// + /// Converts a number of days since the UNIX epoch (January 1, 1970) to the corresponding + /// timestamp in milliseconds at the start of the next day (00:00:00.000). + /// + /// \tparam T The return type of the function (default is ts_ms_t). + /// \param unix_day The number of days since the UNIX epoch. + /// \return The timestamp in milliseconds representing the beginning of the next UNIX day. + template + TIME_SHIELD_CONSTEXPR T start_of_next_day_from_unix_day_ms(dse_t unix_day) noexcept { + return unix_day * MS_PER_DAY + MS_PER_DAY; + } + + /// \brief Get UNIX minute. + /// + /// This function returns the number of minutes elapsed since the UNIX epoch. + /// + /// \tparam T The return type of the function (default is int64_t). + /// \param ts Timestamp in seconds (default is current timestamp). + /// \return Number of minutes since the UNIX epoch. + template + TIME_SHIELD_CONSTEXPR T min_since_epoch(ts_t ts = time_shield::ts()) { + return ts / SEC_PER_MIN; + } + + /// \brief Get the second of the day. + /// + /// This function returns a value from 0 to MAX_SEC_PER_DAY representing the second of the day. + /// + /// \tparam T The return type of the function (default is int). + /// \param ts Timestamp in seconds (default is current timestamp). + /// \return Second of the day. + template + TIME_SHIELD_CONSTEXPR T sec_of_day(ts_t ts = time_shield::ts()) noexcept { + return static_cast(ts % SEC_PER_DAY); + } + + /// \brief Get the second of the day from milliseconds timestamp. + /// + /// This function returns a value from 0 to MAX_SEC_PER_DAY representing the second of the day, given a timestamp in milliseconds. + /// + /// \tparam T The return type of the function (default is int). + /// \param ts_ms Timestamp in milliseconds. + /// \return Second of the day. + template + TIME_SHIELD_CONSTEXPR T sec_of_day_ms(ts_ms_t ts_ms) noexcept { + return sec_of_day(ms_to_sec(ts_ms)); + } + + /// \brief Get the second of the day. + /// + /// This function returns a value between 0 and MAX_SEC_PER_DAY representing the second of the day, given the hour, minute, and second. + /// + /// \tparam T1 The return type of the function (default is int). + /// \tparam T2 The type of the hour, minute, and second parameters (default is int). + /// \param hour Hour of the day. + /// \param min Minute of the hour. + /// \param sec Second of the minute. + /// \return Second of the day. + template + constexpr T1 sec_of_day( + T2 hour, + T2 min, + T2 sec) noexcept { + return static_cast(hour) * static_cast(SEC_PER_HOUR) + + static_cast(min) * static_cast(SEC_PER_MIN) + + static_cast(sec); + } + + /// \brief Get the second of the minute. + /// + /// This function returns a value between 0 and 59 representing the second of the minute. + /// + /// \tparam T The return type of the function (default is int). + /// \param ts Timestamp in seconds (default is current timestamp). + /// \return Second of the minute. + template + TIME_SHIELD_CONSTEXPR T sec_of_min(ts_t ts = time_shield::ts()) { + return static_cast(ts % SEC_PER_MIN); + } + + /// \brief Get the second of the hour. + /// + /// This function returns a value between 0 and 3599 representing the second of the hour. + /// + /// \tparam T The return type of the function (default is int). + /// \param ts Timestamp in seconds (default is current timestamp). + /// \return Second of the hour. + template + TIME_SHIELD_CONSTEXPR T sec_of_hour(ts_t ts = time_shield::ts()) { + return static_cast(ts % SEC_PER_HOUR); + } + +/// \} + +}; // namespace time_shield + +#endif // TIME_SHIELD_HEADER_CONVERSIONS_UNIX_TIME_CONVERSIONS_HPP_INCLUDED diff --git a/include/time_shield/conversions/workday_conversions.hpp b/include/time_shield/conversions/workday_conversions.hpp new file mode 100644 index 00000000..cf7b730f --- /dev/null +++ b/include/time_shield/conversions/workday_conversions.hpp @@ -0,0 +1,342 @@ +// SPDX-License-Identifier: MIT +#pragma once +#ifndef TIME_SHIELD_HEADER_CONVERSIONS_WORKDAY_CONVERSIONS_HPP_INCLUDED +#define TIME_SHIELD_HEADER_CONVERSIONS_WORKDAY_CONVERSIONS_HPP_INCLUDED + +/// \file workday_conversions.hpp +/// \brief Helpers for computing workday-related timestamps. + +#include +#include "date_conversions.hpp" +#include "date_time_conversions.hpp" +#include "time_unit_conversions.hpp" + +namespace time_shield { + +/// \ingroup time_conversions +/// \{ + + /// \brief Finds the first workday number within a month. + /// \param year Target year. + /// \param month Target month (1-12). + TIME_SHIELD_CONSTEXPR inline int first_workday_day(year_t year, int month) noexcept { + const int days = num_days_in_month(year, month); + if (days <= 0) { + return 0; + } + for (int day = 1; day <= days; ++day) { + if (is_workday(year, month, day)) { + return day; + } + } + return 0; + } + + /// \brief Finds the last workday number within a month. + /// \param year Target year. + /// \param month Target month (1-12). + TIME_SHIELD_CONSTEXPR inline int last_workday_day(year_t year, int month) noexcept { + const int days = num_days_in_month(year, month); + if (days <= 0) { + return 0; + } + for (int day = days; day >= 1; --day) { + if (is_workday(year, month, day)) { + return day; + } + } + return 0; + } + + /// \brief Counts workdays within a month. + /// \param year Target year. + /// \param month Target month (1-12). + TIME_SHIELD_CONSTEXPR inline int count_workdays_in_month(year_t year, int month) noexcept { + const int days = num_days_in_month(year, month); + if (days <= 0) { + return 0; + } + int total = 0; + for (int day = 1; day <= days; ++day) { + if (is_workday(year, month, day)) { + ++total; + } + } + return total; + } + + /// \brief Returns workday position in month starting from 1. + /// \param year Target year. + /// \param month Target month (1-12). + /// \param day Day of month (1-based). + TIME_SHIELD_CONSTEXPR inline int workday_index_in_month(year_t year, int month, int day) noexcept { + if (!is_workday(year, month, day)) { + return 0; + } + const int days = num_days_in_month(year, month); + if (days <= 0) { + return 0; + } + int index = 0; + for (int current = 1; current <= days; ++current) { + if (is_workday(year, month, current)) { + ++index; + if (current == day) { + return index; + } + } + } + return 0; + } + + /// \brief Checks whether date is the first workday of the month. + /// \param year Target year. + /// \param month Target month (1-12). + /// \param day Day of month (1-based). + TIME_SHIELD_CONSTEXPR inline bool is_first_workday_of_month(year_t year, int month, int day) noexcept { + return is_workday(year, month, day) && first_workday_day(year, month) == day; + } + + /// \brief Checks if date falls within the first N workdays of the month. + /// \param year Target year. + /// \param month Target month (1-12). + /// \param day Day of month (1-based). + /// \param count Number of leading workdays to include. + TIME_SHIELD_CONSTEXPR inline bool is_within_first_workdays_of_month(year_t year, int month, int day, int count) noexcept { + if (count <= 0) { + return false; + } + const int total = count_workdays_in_month(year, month); + if (count > total) { + return false; + } + const int index = workday_index_in_month(year, month, day); + return index > 0 && index <= count; + } + + /// \brief Checks whether date is the last workday of the month. + /// \param year Target year. + /// \param month Target month (1-12). + /// \param day Day of month (1-based). + TIME_SHIELD_CONSTEXPR inline bool is_last_workday_of_month(year_t year, int month, int day) noexcept { + return is_workday(year, month, day) && last_workday_day(year, month) == day; + } + + /// \brief Checks if date falls within the last N workdays of the month. + /// \param year Target year. + /// \param month Target month (1-12). + /// \param day Day of month (1-based). + /// \param count Number of trailing workdays to include. + TIME_SHIELD_CONSTEXPR inline bool is_within_last_workdays_of_month(year_t year, int month, int day, int count) noexcept { + if (count <= 0) { + return false; + } + const int total = count_workdays_in_month(year, month); + if (count > total) { + return false; + } + const int index = workday_index_in_month(year, month, day); + return index > 0 && index >= (total - count + 1); + } + + /// \brief Checks whether timestamp is the first workday of the month. + /// \param ts Timestamp in seconds. + TIME_SHIELD_CONSTEXPR inline bool is_first_workday_of_month(ts_t ts) noexcept { + return is_first_workday_of_month(year_of(ts), month_of_year(ts), day_of_month(ts)); + } + + /// \brief Checks whether millisecond timestamp is the first workday of the month. + /// \param ts_ms Timestamp in milliseconds. + TIME_SHIELD_CONSTEXPR inline bool is_first_workday_of_month_ms(ts_ms_t ts_ms) noexcept { + return is_workday_ms(ts_ms) && is_first_workday_of_month(year_of_ms(ts_ms), month_of_year(ms_to_sec(ts_ms)), day_of_month(ms_to_sec(ts_ms))); + } + + /// \brief Checks if timestamp falls within the first N workdays of the month. + /// \param ts Timestamp in seconds. + /// \param count Number of leading workdays to include. + TIME_SHIELD_CONSTEXPR inline bool is_within_first_workdays_of_month(ts_t ts, int count) noexcept { + return is_within_first_workdays_of_month(year_of(ts), month_of_year(ts), day_of_month(ts), count); + } + + /// \brief Checks if millisecond timestamp falls within the first N workdays of the month. + /// \param ts_ms Timestamp in milliseconds. + /// \param count Number of leading workdays to include. + TIME_SHIELD_CONSTEXPR inline bool is_within_first_workdays_of_month_ms(ts_ms_t ts_ms, int count) noexcept { + return is_workday_ms(ts_ms) && is_within_first_workdays_of_month(year_of_ms(ts_ms), month_of_year(ms_to_sec(ts_ms)), day_of_month(ms_to_sec(ts_ms)), count); + } + + /// \brief Checks whether timestamp is the last workday of the month. + /// \param ts Timestamp in seconds. + TIME_SHIELD_CONSTEXPR inline bool is_last_workday_of_month(ts_t ts) noexcept { + return is_last_workday_of_month(year_of(ts), month_of_year(ts), day_of_month(ts)); + } + + /// \brief Checks whether millisecond timestamp is the last workday of the month. + /// \param ts_ms Timestamp in milliseconds. + TIME_SHIELD_CONSTEXPR inline bool is_last_workday_of_month_ms(ts_ms_t ts_ms) noexcept { + return is_workday_ms(ts_ms) && is_last_workday_of_month(year_of_ms(ts_ms), month_of_year(ms_to_sec(ts_ms)), day_of_month(ms_to_sec(ts_ms))); + } + + /// \brief Checks if timestamp falls within the last N workdays of the month. + /// \param ts Timestamp in seconds. + /// \param count Number of trailing workdays to include. + TIME_SHIELD_CONSTEXPR inline bool is_within_last_workdays_of_month(ts_t ts, int count) noexcept { + return is_within_last_workdays_of_month(year_of(ts), month_of_year(ts), day_of_month(ts), count); + } + + /// \brief Checks if millisecond timestamp falls within the last N workdays of the month. + /// \param ts_ms Timestamp in milliseconds. + /// \param count Number of trailing workdays to include. + TIME_SHIELD_CONSTEXPR inline bool is_within_last_workdays_of_month_ms(ts_ms_t ts_ms, int count) noexcept { + return is_workday_ms(ts_ms) && is_within_last_workdays_of_month(year_of_ms(ts_ms), month_of_year(ms_to_sec(ts_ms)), day_of_month(ms_to_sec(ts_ms)), count); + } + + /// \brief Returns start-of-day timestamp for the first workday of month. + /// \param year Target year. + /// \param month Target month (1-12). + TIME_SHIELD_CONSTEXPR inline ts_t start_of_first_workday_month(year_t year, int month) noexcept { + const int day = first_workday_day(year, month); + if (day <= 0) { + return ERROR_TIMESTAMP; + } + return to_timestamp(year, month, day); + } + + /// \brief Returns start-of-day millisecond timestamp for the first workday of month. + /// \param year Target year. + /// \param month Target month (1-12). + TIME_SHIELD_CONSTEXPR inline ts_ms_t start_of_first_workday_month_ms(year_t year, int month) noexcept { + const int day = first_workday_day(year, month); + if (day <= 0) { + return ERROR_TIMESTAMP; + } + const ts_t day_start = to_timestamp(year, month, day); + return sec_to_ms(day_start); + } + + /// \brief Returns start-of-day timestamp for the first workday of month derived from timestamp. + /// \param ts Timestamp in seconds. + TIME_SHIELD_CONSTEXPR inline ts_t start_of_first_workday_month(ts_t ts = time_shield::ts()) noexcept { + return start_of_first_workday_month(year_of(ts), month_of_year(ts)); + } + + /// \brief Returns start-of-day millisecond timestamp for the first workday of month derived from millisecond timestamp. + /// \param ts_ms Timestamp in milliseconds. + TIME_SHIELD_CONSTEXPR inline ts_ms_t start_of_first_workday_month_ms(ts_ms_t ts_ms = time_shield::ts_ms()) noexcept { + return start_of_first_workday_month_ms(year_of_ms(ts_ms), month_of_year(ms_to_sec(ts_ms))); + } + + /// \brief Returns end-of-day timestamp for the first workday of month. + /// \param year Target year. + /// \param month Target month (1-12). + TIME_SHIELD_CONSTEXPR inline ts_t end_of_first_workday_month(year_t year, int month) noexcept { + const int day = first_workday_day(year, month); + if (day <= 0) { + return ERROR_TIMESTAMP; + } + const ts_t day_start = to_timestamp(year, month, day); + return end_of_day(day_start); + } + + /// \brief Returns end-of-day millisecond timestamp for the first workday of month. + /// \param year Target year. + /// \param month Target month (1-12). + TIME_SHIELD_CONSTEXPR inline ts_ms_t end_of_first_workday_month_ms(year_t year, int month) noexcept { + const int day = first_workday_day(year, month); + if (day <= 0) { + return ERROR_TIMESTAMP; + } + const ts_t day_start = to_timestamp(year, month, day); + const ts_ms_t day_start_ms = sec_to_ms(day_start); + return end_of_day_ms(day_start_ms); + } + + /// \brief Returns end-of-day timestamp for the first workday of month derived from timestamp. + /// \param ts Timestamp in seconds. + TIME_SHIELD_CONSTEXPR inline ts_t end_of_first_workday_month(ts_t ts = time_shield::ts()) noexcept { + return end_of_first_workday_month(year_of(ts), month_of_year(ts)); + } + + /// \brief Returns end-of-day millisecond timestamp for the first workday of month derived from millisecond timestamp. + /// \param ts_ms Timestamp in milliseconds. + TIME_SHIELD_CONSTEXPR inline ts_ms_t end_of_first_workday_month_ms(ts_ms_t ts_ms = time_shield::ts_ms()) noexcept { + return end_of_first_workday_month_ms(year_of_ms(ts_ms), month_of_year(ms_to_sec(ts_ms))); + } + + /// \brief Returns start-of-day timestamp for the last workday of month. + /// \param year Target year. + /// \param month Target month (1-12). + TIME_SHIELD_CONSTEXPR inline ts_t start_of_last_workday_month(year_t year, int month) noexcept { + const int day = last_workday_day(year, month); + if (day <= 0) { + return ERROR_TIMESTAMP; + } + return to_timestamp(year, month, day); + } + + /// \brief Returns start-of-day millisecond timestamp for the last workday of month. + /// \param year Target year. + /// \param month Target month (1-12). + TIME_SHIELD_CONSTEXPR inline ts_ms_t start_of_last_workday_month_ms(year_t year, int month) noexcept { + const int day = last_workday_day(year, month); + if (day <= 0) { + return ERROR_TIMESTAMP; + } + const ts_t day_start = to_timestamp(year, month, day); + return sec_to_ms(day_start); + } + + /// \brief Returns start-of-day timestamp for the last workday of month derived from timestamp. + /// \param ts Timestamp in seconds. + TIME_SHIELD_CONSTEXPR inline ts_t start_of_last_workday_month(ts_t ts = time_shield::ts()) noexcept { + return start_of_last_workday_month(year_of(ts), month_of_year(ts)); + } + + /// \brief Returns start-of-day millisecond timestamp for the last workday of month derived from millisecond timestamp. + /// \param ts_ms Timestamp in milliseconds. + TIME_SHIELD_CONSTEXPR inline ts_ms_t start_of_last_workday_month_ms(ts_ms_t ts_ms = time_shield::ts_ms()) noexcept { + return start_of_last_workday_month_ms(year_of_ms(ts_ms), month_of_year(ms_to_sec(ts_ms))); + } + + /// \brief Returns end-of-day timestamp for the last workday of month. + /// \param year Target year. + /// \param month Target month (1-12). + TIME_SHIELD_CONSTEXPR inline ts_t end_of_last_workday_month(year_t year, int month) noexcept { + const int day = last_workday_day(year, month); + if (day <= 0) { + return ERROR_TIMESTAMP; + } + const ts_t day_start = to_timestamp(year, month, day); + return end_of_day(day_start); + } + + /// \brief Returns end-of-day millisecond timestamp for the last workday of month. + /// \param year Target year. + /// \param month Target month (1-12). + TIME_SHIELD_CONSTEXPR inline ts_ms_t end_of_last_workday_month_ms(year_t year, int month) noexcept { + const int day = last_workday_day(year, month); + if (day <= 0) { + return ERROR_TIMESTAMP; + } + const ts_t day_start = to_timestamp(year, month, day); + const ts_ms_t day_start_ms = sec_to_ms(day_start); + return end_of_day_ms(day_start_ms); + } + + /// \brief Returns end-of-day timestamp for the last workday of month derived from timestamp. + /// \param ts Timestamp in seconds. + TIME_SHIELD_CONSTEXPR inline ts_t end_of_last_workday_month(ts_t ts = time_shield::ts()) noexcept { + return end_of_last_workday_month(year_of(ts), month_of_year(ts)); + } + + /// \brief Returns end-of-day millisecond timestamp for the last workday of month derived from millisecond timestamp. + /// \param ts_ms Timestamp in milliseconds. + TIME_SHIELD_CONSTEXPR inline ts_ms_t end_of_last_workday_month_ms(ts_ms_t ts_ms = time_shield::ts_ms()) noexcept { + return end_of_last_workday_month_ms(year_of_ms(ts_ms), month_of_year(ms_to_sec(ts_ms))); + } + +/// \} + +}; // namespace time_shield + +#endif // TIME_SHIELD_HEADER_CONVERSIONS_WORKDAY_CONVERSIONS_HPP_INCLUDED diff --git a/include/time_shield/core.hpp b/include/time_shield/core.hpp new file mode 100644 index 00000000..7687e57f --- /dev/null +++ b/include/time_shield/core.hpp @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MIT +#pragma once +#ifndef TIME_SHIELD_HEADER_CORE_HPP_INCLUDED +#define TIME_SHIELD_HEADER_CORE_HPP_INCLUDED + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#endif // TIME_SHIELD_HEADER_CORE_HPP_INCLUDED diff --git a/include/time_shield/core/config.hpp b/include/time_shield/core/config.hpp new file mode 100644 index 00000000..65bac7be --- /dev/null +++ b/include/time_shield/core/config.hpp @@ -0,0 +1,119 @@ +// SPDX-License-Identifier: MIT +#pragma once +#ifndef TIME_SHIELD_HEADER_CORE_CONFIG_HPP_INCLUDED +#define TIME_SHIELD_HEADER_CORE_CONFIG_HPP_INCLUDED + +/// \file config.hpp +/// \brief Configuration macros for the library. +/// +/// This header provides compile-time options for C++ standard detection, +/// platform capabilities and optional features. The macros can be used to +/// enable or disable parts of the library depending on the target platform or +/// user preferences. + +#include + +#if defined(_MSVC_LANG) +# define TIME_SHIELD_CXX_VERSION _MSVC_LANG +#else +# define TIME_SHIELD_CXX_VERSION __cplusplus +#endif + +// Check and define macros based on the C++ standard version +#if TIME_SHIELD_CXX_VERSION >= 201703L +# define TIME_SHIELD_CPP17 +#elif TIME_SHIELD_CXX_VERSION >= 201402L +# define TIME_SHIELD_CPP14 +#elif TIME_SHIELD_CXX_VERSION >= 201103L +# define TIME_SHIELD_CPP11 +#else +# error "C++11 or newer is required to compile this library." +#endif + +// Configure support for `constexpr` and `if constexpr` based on the C++ standard +#ifdef TIME_SHIELD_CPP11 +# define TIME_SHIELD_IF_CONSTEXPR +# define TIME_SHIELD_CONSTEXPR +#else +#ifdef TIME_SHIELD_CPP14 +# define TIME_SHIELD_IF_CONSTEXPR +# define TIME_SHIELD_CONSTEXPR constexpr +#else +#ifdef TIME_SHIELD_CPP17 +# define TIME_SHIELD_IF_CONSTEXPR constexpr +# define TIME_SHIELD_CONSTEXPR constexpr +#endif +#endif +#endif + +// Configure nodiscard attribute support while keeping compatibility with C++11 compilers +#if defined(__has_cpp_attribute) +# if __has_cpp_attribute(nodiscard) && defined(TIME_SHIELD_CPP17) +# define TIME_SHIELD_NODISCARD [[nodiscard]] +# else +# define TIME_SHIELD_NODISCARD +# endif +#else +# if defined(TIME_SHIELD_CPP17) +# define TIME_SHIELD_NODISCARD [[nodiscard]] +# else +# define TIME_SHIELD_NODISCARD +# endif +#endif + +// Attribute helpers +#if defined(TIME_SHIELD_CPP17) +# define TIME_SHIELD_MAYBE_UNUSED [[maybe_unused]] +#else +# define TIME_SHIELD_MAYBE_UNUSED +#endif + +// Configure thread-local storage handling for compilers with partial support +#if defined(__cpp_thread_local) +# define TIME_SHIELD_THREAD_LOCAL thread_local +#elif defined(_MSC_VER) +# define TIME_SHIELD_THREAD_LOCAL __declspec(thread) +#elif defined(__GNUC__) +# define TIME_SHIELD_THREAD_LOCAL __thread +#else +# define TIME_SHIELD_THREAD_LOCAL +#endif + + +/// \name Platform detection +///@{ +#if defined(_WIN32) +# define TIME_SHIELD_PLATFORM_WINDOWS 1 +#else +# define TIME_SHIELD_PLATFORM_WINDOWS 0 +#endif + +#if defined(__unix__) || defined(__unix) || defined(unix) || \ + (defined(__APPLE__) && defined(__MACH__)) +# define TIME_SHIELD_PLATFORM_UNIX 1 +#else +# define TIME_SHIELD_PLATFORM_UNIX 0 +#endif +///@} + +/// \name Platform capabilities +///@{ +#if TIME_SHIELD_PLATFORM_WINDOWS +# define TIME_SHIELD_HAS_WINSOCK 1 +#else +# define TIME_SHIELD_HAS_WINSOCK 0 +#endif +///@} + +/// \name Optional features +///@{ +#ifndef TIME_SHIELD_ENABLE_NTP_CLIENT +# if TIME_SHIELD_HAS_WINSOCK || TIME_SHIELD_PLATFORM_UNIX +# define TIME_SHIELD_ENABLE_NTP_CLIENT 1 +# else +# define TIME_SHIELD_ENABLE_NTP_CLIENT 0 +# endif +#endif +///@} + +#endif // TIME_SHIELD_HEADER_CORE_CONFIG_HPP_INCLUDED diff --git a/include/time_shield/core/constants.hpp b/include/time_shield/core/constants.hpp new file mode 100644 index 00000000..8c841b34 --- /dev/null +++ b/include/time_shield/core/constants.hpp @@ -0,0 +1,169 @@ +// SPDX-License-Identifier: MIT +#pragma once +#ifndef TIME_SHIELD_HEADER_CORE_CONSTANTS_HPP_INCLUDED +#define TIME_SHIELD_HEADER_CORE_CONSTANTS_HPP_INCLUDED + +/// \file constants.hpp +/// \brief Header file with time-related constants. +/// +/// This file contains various constants used for time calculations and conversions. + +#include +#include + +namespace time_shield { + +/// \defgroup time_constants Time Constants +/// \brief A collection of constants for time calculations and conversions. +/// +/// This group includes constants for time units (nanoseconds, microseconds, milliseconds, seconds, minutes, hours, days), +/// and other values related to the representation of time, such as UNIX and OLE epochs. +/// +/// ### Key Features: +/// - Provides constants for common time conversions. +/// - Includes limits and special values like MAX_YEAR and ERROR_YEAR. +/// +/// ### Example Usage: +/// ```cpp +/// int64_t milliseconds_in_a_day = time_shield::MS_PER_DAY; +/// ``` +/// +/// \{ + + // Common millisecond durations + constexpr int64_t MS_1 = 1; ///< 1 millisecond + constexpr int64_t MS_5 = 5; ///< 5 milliseconds + constexpr int64_t MS_10 = 10; ///< 10 milliseconds + constexpr int64_t MS_50 = 50; ///< 50 milliseconds + constexpr int64_t MS_100 = 100; ///< 100 milliseconds + constexpr int64_t MS_250 = 250; ///< 250 milliseconds + constexpr int64_t MS_500 = 500; ///< 500 milliseconds + constexpr int64_t MS_750 = 750; ///< 750 milliseconds + + // Common second durations + constexpr int64_t SEC_1 = 1; ///< 1 second + constexpr int64_t SEC_2 = 2; ///< 2 seconds + constexpr int64_t SEC_3 = 3; ///< 3 seconds + constexpr int64_t SEC_5 = 5; ///< 5 seconds + constexpr int64_t SEC_10 = 10; ///< 10 seconds + constexpr int64_t SEC_15 = 15; ///< 15 seconds + constexpr int64_t SEC_30 = 30; ///< 30 seconds + + // Common minute durations + constexpr int64_t MIN_1 = 1; ///< 1 minute + constexpr int64_t MIN_2 = 2; ///< 2 minutes + constexpr int64_t MIN_5 = 5; ///< 5 minutes + constexpr int64_t MIN_10 = 10; ///< 10 minutes + constexpr int64_t MIN_15 = 15; ///< 15 minutes + constexpr int64_t MIN_30 = 30; ///< 30 minutes + + // Common hour durations + constexpr int64_t HOUR_1 = 1; ///< 1 hour + constexpr int64_t HOUR_2 = 2; ///< 2 hours + constexpr int64_t HOUR_3 = 3; ///< 3 hours + constexpr int64_t HOUR_4 = 4; ///< 4 hours + constexpr int64_t HOUR_5 = 5; ///< 5 hours + constexpr int64_t HOUR_8 = 8; ///< 8 hours + constexpr int64_t HOUR_12 = 12; ///< 12 hours + constexpr int64_t HOUR_24 = 24; ///< 24 hours + + // Nanoseconds and microseconds + constexpr int64_t NS_PER_US = 1000; ///< Nanoseconds per microsecond + constexpr int64_t NS_PER_MS = 1000000; ///< Nanoseconds per millisecond + constexpr int64_t NS_PER_SEC = 1000000000; ///< Nanoseconds per second + + // Microseconds and milliseconds + constexpr int64_t US_PER_SEC = 1000000; ///< Microseconds per second + constexpr int64_t MS_PER_SEC = 1000; ///< Milliseconds per second + constexpr int64_t MS_PER_1_SEC = 1000; ///< Milliseconds per 1 second + constexpr int64_t MS_PER_5_SEC = 5000; ///< Milliseconds per 5 second + constexpr int64_t MS_PER_10_SEC = 10000; ///< Milliseconds per 10 seconds + constexpr int64_t MS_PER_15_SEC = 15000; ///< Milliseconds per 15 second + constexpr int64_t MS_PER_30_SEC = 30000; ///< Milliseconds per 30 second + constexpr int64_t MS_PER_MIN = 60000; ///< Milliseconds per minute + constexpr int64_t MS_PER_1_MIN = 60000; ///< Milliseconds per 1 minute + constexpr int64_t MS_PER_5_MIN = 300000; ///< Milliseconds per 5 minute + constexpr int64_t MS_PER_10_MIN = 600000; ///< Milliseconds per 10 minute + constexpr int64_t MS_PER_15_MIN = 900000; ///< Milliseconds per 15 minute + constexpr int64_t MS_PER_30_MIN = 1800000; ///< Milliseconds per 30 minute + constexpr int64_t MS_PER_HALF_HOUR = 1800000; ///< Milliseconds per half hour + constexpr int64_t MS_PER_HOUR = 3600000; ///< Milliseconds per hour + constexpr int64_t MS_PER_1_HOUR = 3600000; ///< Milliseconds per 1 hour + constexpr int64_t MS_PER_2_HOUR = 7200000; ///< Milliseconds per 2 hour + constexpr int64_t MS_PER_4_HOUR = 14400000; ///< Milliseconds per 4 hour + constexpr int64_t MS_PER_5_HOUR = 18000000; ///< Milliseconds per 5 hour + constexpr int64_t MS_PER_8_HOUR = 28800000; ///< Milliseconds per 8 hour + constexpr int64_t MS_PER_12_HOUR = 43200000; ///< Milliseconds per 12 hour + constexpr int64_t MS_PER_DAY = 86400000; ///< Milliseconds per day + + // Seconds + constexpr int64_t SEC_PER_MIN = 60; ///< Seconds per minute + constexpr int64_t SEC_PER_1_MIN = 60; ///< Seconds per 1 minute + constexpr int64_t SEC_PER_3_MIN = 180; ///< Seconds per 3 minute + constexpr int64_t SEC_PER_5_MIN = 300; ///< Seconds per 5 minute + constexpr int64_t SEC_PER_10_MIN = 600; ///< Seconds per 10 minute + constexpr int64_t SEC_PER_15_MIN = 900; ///< Seconds per 15 minute + constexpr int64_t SEC_PER_HALF_HOUR = 1800; ///< Seconds per half hour + constexpr int64_t SEC_PER_HOUR = 3600; ///< Seconds per hour + constexpr int64_t SEC_PER_1_HOUR = 3600; ///< Seconds per 1 hour + constexpr int64_t SEC_PER_2_HOUR = 7200; ///< Seconds per 2 hour + constexpr int64_t SEC_PER_4_HOUR = 14400; ///< Seconds per 4 hour + constexpr int64_t SEC_PER_5_HOUR = 18000; ///< Seconds per 5 hour + constexpr int64_t SEC_PER_8_HOUR = 28800; ///< Seconds per 8 hour + constexpr int64_t SEC_PER_12_HOUR = 43200; ///< Seconds per 12 hour + constexpr int64_t SEC_PER_DAY = 86400; ///< Seconds per day + constexpr int64_t SEC_PER_YEAR = 31536000; ///< Seconds per year (365 days) + constexpr int64_t AVG_SEC_PER_YEAR = 31557600; ///< Average seconds per year (365.25 days) + constexpr int64_t SEC_PER_LEAP_YEAR = 31622400; ///< Seconds per leap year (366 days) + constexpr int64_t SEC_PER_4_YEARS = 126230400;///< Seconds per 4 years + constexpr int64_t SEC_PER_FIRST_100_YEARS = 3155760000; ///< Seconds per first 100 years + constexpr int64_t SEC_PER_100_YEARS = 3155673600; ///< Seconds per 100 years + constexpr int64_t SEC_PER_400_YEARS = 12622780800; ///< Seconds per 400 years + constexpr int64_t MAX_SEC_PER_DAY = 86399; ///< Maximum seconds per day + + // Minutes + constexpr int64_t MIN_PER_HOUR = 60; ///< Minutes per hour + constexpr int64_t MIN_PER_DAY = 1440; ///< Minutes per day + constexpr int64_t MIN_PER_1_DAY = 1440; ///< Minutes per 1 day + constexpr int64_t MIN_PER_2_DAY = 2*1440; ///< Minutes per 2 day + constexpr int64_t MIN_PER_5_DAY = 5*1440; ///< Minutes per 5 day + constexpr int64_t MIN_PER_7_DAY = 7*1440; ///< Minutes per 7 day + constexpr int64_t MIN_PER_WEEK = 10080; ///< Minutes per week + constexpr int64_t MIN_PER_10_DAY = 10*1440; ///< Minutes per 10 day + constexpr int64_t MIN_PER_15_DAY = 15*1440; ///< Minutes per 15 day + constexpr int64_t MIN_PER_30_DAY = 30*1440; ///< Minutes per 30 day + constexpr int64_t MIN_PER_MONTH = 40320; ///< Minutes per month (28 days) + constexpr int64_t MAX_MOON_MIN = 42523; ///< Maximum lunar minutes + + // Hours and days + constexpr int64_t HOURS_PER_DAY = 24; ///< Hours per day + constexpr int64_t DAYS_PER_WEEK = 7; ///< Days per week + constexpr int64_t DAYS_PER_LEAP_YEAR = 366; ///< Days per leap year + constexpr int64_t DAYS_PER_YEAR = 365; ///< Days per year + constexpr int64_t DAYS_PER_4_YEARS = 1461; ///< Days per 4 years + + // Months and years + const int64_t MONTHS_PER_YEAR = 12; ///< Months per year + const int64_t MAX_DAYS_PER_MONTH = 31; ///< Maximum days per month + const int64_t LEAP_YEAR_PER_100_YEAR = 24; ///< Leap years per 100 years + const int64_t LEAP_YEAR_PER_400_YEAR = 97; ///< Leap years per 400 years + + // Epoch and maximum values + constexpr int64_t UNIX_EPOCH = 1970; ///< Start year of UNIX time + constexpr int64_t OLE_EPOCH = 25569; ///< OLE automation date since UNIX epoch + constexpr int64_t MAX_YEAR = 292277022000LL; ///< Maximum representable year + constexpr int64_t MIN_YEAR = -2967369602200LL; ///< Minimum representable year + constexpr int64_t ERROR_YEAR = 9223372036854770000LL; ///< Error year value + constexpr int64_t MAX_TIMESTAMP = (((std::numeric_limits::max)() - (MS_PER_SEC - 1)) / MS_PER_SEC) - (SEC_PER_YEAR - 1); ///< Maximum timestamp value + constexpr int64_t MIN_TIMESTAMP = -MAX_TIMESTAMP; ///< Minimum timestamp value + constexpr int64_t MAX_TIMESTAMP_MS = MAX_TIMESTAMP * MS_PER_SEC + (MS_PER_SEC - 1); ///< Maximum timestamp value in milliseconds + constexpr int64_t MIN_TIMESTAMP_MS = MIN_TIMESTAMP * MS_PER_SEC; ///< Minimum timestamp value in milliseconds + constexpr int64_t ERROR_TIMESTAMP = 9223372036854770000LL; ///< Error timestamp value + constexpr double MAX_OADATE = (std::numeric_limits::max)(); ///< Maximum representable oadate_t value + constexpr double AVG_DAYS_PER_YEAR = 365.25; ///< Average days per year + +/// \} + +}; // namespace time_shield + +#endif // TIME_SHIELD_HEADER_CORE_CONSTANTS_HPP_INCLUDED diff --git a/include/time_shield/core/date_struct.hpp b/include/time_shield/core/date_struct.hpp new file mode 100644 index 00000000..f0382e12 --- /dev/null +++ b/include/time_shield/core/date_struct.hpp @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: MIT +#pragma once +#ifndef TIME_SHIELD_HEADER_CORE_DATE_STRUCT_HPP_INCLUDED +#define TIME_SHIELD_HEADER_CORE_DATE_STRUCT_HPP_INCLUDED + +/// \file date_struct.hpp +/// \brief Header for date structure and related functions. +/// +/// This file contains the definition of the DateStruct structure and a function to create DateStruct instances. + +namespace time_shield { + + /// \ingroup time_structures + /// \brief Structure to represent a date. + struct DateStruct { + int64_t year; ///< Year component of the date. + int32_t mon; ///< Month component of the date (1-12). + int32_t day; ///< Day component of the date (1-31). + }; + + /// \ingroup time_structures + /// \brief Creates a DateStruct instance. + /// \param year The year component of the date. + /// \param mon The month component of the date, defaults to 1 (January). + /// \param day The day component of the date, defaults to 1. + /// \return A DateStruct instance with the provided date components. + inline const DateStruct create_date_struct( + int64_t year, + int32_t mon = 1, + int32_t day = 1) { + DateStruct data{year, mon, day}; + return data; + } + +}; // namespace time_shield + +#endif // TIME_SHIELD_HEADER_CORE_DATE_STRUCT_HPP_INCLUDED diff --git a/include/time_shield/core/date_time_struct.hpp b/include/time_shield/core/date_time_struct.hpp new file mode 100644 index 00000000..f29fda80 --- /dev/null +++ b/include/time_shield/core/date_time_struct.hpp @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: MIT +#pragma once +#ifndef TIME_SHIELD_HEADER_CORE_DATE_TIME_STRUCT_HPP_INCLUDED +#define TIME_SHIELD_HEADER_CORE_DATE_TIME_STRUCT_HPP_INCLUDED + +/// \file date_time_struct.hpp +/// \brief Header for date and time structure and related functions. +/// +/// This file contains the definition of the DateTimeStruct structure and a function to create DateTimeStruct instances. + +#include + +namespace time_shield { + + /// \ingroup time_structures + /// \brief Structure to represent date and time. + struct DateTimeStruct { + int64_t year; ///< Year component of the date. + int mon; ///< Month component of the date (1-12). + int day; ///< Day component of the date (1-31). + int hour; ///< Hour component of time (0-23) + int min; ///< Minute component of time (0-59) + int sec; ///< Second component of time (0-59) + int ms; ///< Millisecond component of time (0-999) + }; + + /// \ingroup time_structures + /// \brief Creates a DateTimeStruct instance. + /// \param year The year component of the date. + /// \param mon The month component of the date, defaults to 1 (January). + /// \param day The day component of the date, defaults to 1. + /// \param hour The hour component of the time, defaults to 0. + /// \param min The minute component of the time, defaults to 0. + /// \param sec The second component of the time, defaults to 0. + /// \param ms The millisecond component of the time, defaults to 0. + /// \return A DateTimeStruct instance with the provided date and time components. + inline 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) { + DateTimeStruct date_time; + date_time.year = year; + date_time.mon = mon; + date_time.day = day; + date_time.hour = hour; + date_time.min = min; + date_time.sec = sec; + date_time.ms = ms; + return date_time; + } + +}; // namespace time_shield + +#endif // TIME_SHIELD_HEADER_CORE_DATE_TIME_STRUCT_HPP_INCLUDED diff --git a/include/time_shield/core/enums.hpp b/include/time_shield/core/enums.hpp new file mode 100644 index 00000000..179812d5 --- /dev/null +++ b/include/time_shield/core/enums.hpp @@ -0,0 +1,312 @@ +// SPDX-License-Identifier: MIT +#pragma once +#ifndef TIME_SHIELD_HEADER_CORE_ENUMS_HPP_INCLUDED +#define TIME_SHIELD_HEADER_CORE_ENUMS_HPP_INCLUDED + +/// \file enums.hpp +/// \ingroup time_enums +/// \brief Header file with enumerations for weekdays, months, and other time-related categories. +/// +/// This file contains enum definitions for representing various time-related concepts. + +#include +#include + +namespace time_shield { + + /// \ingroup time_enums + /// Enumeration of the format options for representing a weekday or month. + enum FormatType { + UPPERCASE_NAME = 0, ///< Uppercase short name + SHORT_NAME, ///< Short name + FULL_NAME, ///< Full name + }; + + /// \ingroup time_enums + /// Enumeration of the days of the week. + enum Weekday { + SUN = 0, ///< Sunday + MON, ///< Monday + TUE, ///< Tuesday + WED, ///< Wednesday + THU, ///< Thursday + FRI, ///< Friday + SAT ///< Saturday + }; + + /// \ingroup time_enums + /// \brief Converts a Weekday enum value to a string. + /// + /// \param value The Weekday enum value to convert. + /// \param format The format to use for the string representation (default is UPPERCASE_NAME). + /// \return A const char* pointing to the string representation of the day. + inline const char* to_cstr(Weekday value, FormatType format = UPPERCASE_NAME) { + static const char* const uppercase_names[] = { + "SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT" + }; + static const char* const short_names[] = { + "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" + }; + static const char* const full_names[] = { + "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" + }; + switch (format) { + default: + case UPPERCASE_NAME: + return uppercase_names[static_cast(value)]; + case SHORT_NAME: + return short_names[static_cast(value)]; + case FULL_NAME: + return full_names[static_cast(value)]; + }; + } + + /// \ingroup time_enums + /// \brief Converts a Weekday enum value to a string. + /// + /// \param value The Weekday enum value to convert. + /// \param format The format to use for the string representation (default is UPPERCASE_NAME). + /// \return A const std::string& pointing to the string representation of the day. + inline const std::string& to_str(Weekday value, FormatType format = UPPERCASE_NAME) { + static const std::array uppercase_names = { + "SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT" + }; + static const std::array short_names = { + "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" + }; + static const std::array full_names = { + "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" + }; + switch (format) { + default: + case UPPERCASE_NAME: + return uppercase_names[static_cast(value)]; + case SHORT_NAME: + return short_names[static_cast(value)]; + case FULL_NAME: + return full_names[static_cast(value)]; + }; + } + + /// \ingroup time_enums + /// Enumeration of the months of the year. + enum Month { + JAN = 1, ///< January + FEB, ///< February + MAR, ///< March + APR, ///< April + MAY, ///< May + JUN, ///< June + JUL, ///< July + AUG, ///< August + SEP, ///< September + OCT, ///< October + NOV, ///< November + DEC ///< December + }; + + /// \ingroup time_enums + /// \brief Converts a Month enum value to a string. + /// + /// \param value The Month enum value to convert. + /// \param format The format to use for the string representation (default is UPPERCASE_NAME). + /// \return A const char* pointing to the string representation of the month. + inline const char* to_cstr(Month value, FormatType format = UPPERCASE_NAME) { + static const char* const uppercase_names[] = { + "", + "JAN", "FEB", "MAR", "APR", "MAY", "JUN", + "JUL", "AUG", "SEP", "OCT", "NOV", "DEC" + }; + static const char* const short_names[] = { + "", + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" + }; + static const char* const full_names[] = { + "", + "January", "February", "March", "April", "May", "June", + "July", "August", "September", "October", "November", "December" + }; + switch (format) { + default: + case UPPERCASE_NAME: + return uppercase_names[static_cast(value)]; + case SHORT_NAME: + return short_names[static_cast(value)]; + case FULL_NAME: + return full_names[static_cast(value)]; + }; + } + + /// \ingroup time_enums + /// \brief Converts a Month enum value to a string. + /// + /// \param value The Month enum value to convert. + /// \param format The format to use for the string representation (default is UPPERCASE_NAME). + /// \return A const std::string& pointing to the string representation of the month. + inline const std::string& to_str(Month value, FormatType format = UPPERCASE_NAME) { + static const std::array uppercase_names = { + "", + "JAN", "FEB", "MAR", "APR", "MAY", "JUN", + "JUL", "AUG", "SEP", "OCT", "NOV", "DEC" + }; + static const std::array short_names = { + "", + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" + }; + static const std::array full_names = { + "", + "January", "February", "March", "April", "May", "June", + "July", "August", "September", "October", "November", "December" + }; + switch (format) { + default: + case UPPERCASE_NAME: + return uppercase_names[static_cast(value)]; + case SHORT_NAME: + return short_names[static_cast(value)]; + case FULL_NAME: + return full_names[static_cast(value)]; + }; + } + + /// \ingroup time_enums + /// Enumeration of the time zones. + enum TimeZone { + GMT, ///< Greenwich Mean Time + UTC, ///< Coordinated Universal Time + EET, ///< Eastern European Time + CET, ///< Central European Time + WET, ///< Western European Time + EEST, ///< Eastern European Summer Time + CEST, ///< Central European Summer Time + WEST, ///< Western European Summer Time + ET, ///< US Eastern Time + CT, ///< US Central Time + IST, ///< India Standard Time + MYT, ///< Malaysia Time + WIB, ///< Western Indonesia Time + WITA, ///< Central Indonesia Time + WIT, ///< Eastern Indonesia Time + KZT, ///< Kazakhstan Time + TRT, ///< Turkey Time + BYT, ///< Belarus Time + SGT, ///< Singapore Time + ICT, ///< Indochina Time + PHT, ///< Philippine Time + GST, ///< Gulf Standard Time + HKT, ///< Hong Kong Time + JST, ///< Japan Standard Time + KST, ///< Korea Standard Time + UNKNOWN ///< Unknown Time Zone + }; + + /// \ingroup time_enums + /// \brief Converts a TimeZone enum value to a string. + /// + /// \param value The TimeZone enum value to convert. + /// \param format The format to use for the string representation (default is UPPERCASE_NAME). + /// \return A const char* pointing to the string representation of the time zone. + inline const char* to_cstr(TimeZone value, FormatType format = UPPERCASE_NAME) { + static const char* const uppercase_names[] = { + "GMT", "UTC", "EET", "CET", "WET", "EEST", "CEST", "WEST", + "ET", "CT", "IST", "MYT", "WIB", "WITA", "WIT", "KZT", "TRT", + "BYT", "SGT", "ICT", "PHT", "GST", "HKT", "JST", "KST", "UNKNOWN" + }; + static const char* const short_names[] = { + "GMT", "UTC", "EET", "CET", "WET", "EEST", "CEST", "WEST", + "ET", "CT", "IST", "MYT", "WIB", "WITA", "WIT", "KZT", "TRT", + "BYT", "SGT", "ICT", "PHT", "GST", "HKT", "JST", "KST", "Unknown" + }; + static const char* const full_names[] = { + "Greenwich Mean Time", "Coordinated Universal Time", "Eastern European Time", + "Central European Time", "Western European Time", "Eastern European Summer Time", + "Central European Summer Time", "Western European Summer Time", + "US Eastern Time", "US Central Time", "India Standard Time", + "Malaysia Time", "Western Indonesia Time", "Central Indonesia Time", + "Eastern Indonesia Time", "Kazakhstan Time", "Turkey Time", + "Belarus Time", "Singapore Time", "Indochina Time", + "Philippine Time", "Gulf Standard Time", "Hong Kong Time", + "Japan Standard Time", "Korea Standard Time", "Unknown Time Zone" + }; + switch (format) { + default: + case UPPERCASE_NAME: + return uppercase_names[static_cast(value)]; + case SHORT_NAME: + return short_names[static_cast(value)]; + case FULL_NAME: + return full_names[static_cast(value)]; + } + } + + /// \ingroup time_enums + /// \brief Converts a TimeZone enum value to a string. + /// + /// \param value The TimeZone enum value to convert. + /// \param format The format to use for the string representation (default is UPPERCASE_NAME). + /// \return A const std::string& pointing to the string representation of the time zone. + inline const std::string& to_str(TimeZone value, FormatType format = UPPERCASE_NAME) { + static const std::array uppercase_names = { + "GMT", "UTC", "EET", "CET", "WET", "EEST", "CEST", "WEST", + "ET", "CT", "IST", "MYT", "WIB", "WITA", "WIT", "KZT", "TRT", + "BYT", "SGT", "ICT", "PHT", "GST", "HKT", "JST", "KST", "UNKNOWN" + }; + static const std::array short_names = { + "gmt", "utc", "eet", "cet", "wet", "eest", "cest", "west", + "et", "ct", "ist", "myt", "wib", "wita", "wit", "kzt", "trt", + "byt", "sgt", "ict", "pht", "gst", "hkt", "jst", "kst", "unknown" + }; + static const std::array full_names = { + "Greenwich Mean Time", "Coordinated Universal Time", "Eastern European Time", + "Central European Time", "Western European Time", "Eastern European Summer Time", + "Central European Summer Time", "Western European Summer Time", + "US Eastern Time", "US Central Time", "India Standard Time", + "Malaysia Time", "Western Indonesia Time", "Central Indonesia Time", + "Eastern Indonesia Time", "Kazakhstan Time", "Turkey Time", + "Belarus Time", "Singapore Time", "Indochina Time", + "Philippine Time", "Gulf Standard Time", "Hong Kong Time", + "Japan Standard Time", "Korea Standard Time", "Unknown Time Zone" + }; + switch (format) { + default: + case UPPERCASE_NAME: + return uppercase_names[static_cast(value)]; + case SHORT_NAME: + return short_names[static_cast(value)]; + case FULL_NAME: + return full_names[static_cast(value)]; + } + } + + /// \ingroup time_enums + /// Enumeration of the moon phases. + enum MoonPhase { + WAXING_CRESCENT, ///< Waxing Crescent Moon + FIRST_QUARTER, ///< First Quarter Moon + WAXING_GIBBOUS, ///< Waxing Gibbous Moon + FULL_MOON, ///< Full Moon + WANING_GIBBOUS, ///< Waning Gibbous Moon + LAST_QUARTER, ///< Last Quarter Moon + WANING_CRESCENT, ///< Waning Crescent Moon + NEW_MOON ///< New Moon + }; + + /// \ingroup time_enums + /// Enumeration of time format types. + enum TimeFormatType { + ISO8601_WITH_TZ, ///< ISO8601 format with time zone (e.g., "2024-06-06T12:30:45+03:00") + ISO8601_NO_TZ, ///< ISO8601 format without time zone (e.g., "2024-06-06T12:30:45") + MQL5_FULL, ///< MQL5 time format (e.g., "2024.06.06 12:30:45") + MQL5_DATE_ONLY, ///< MQL5 date format (e.g., "2024.06.06") + MQL5_TIME_ONLY, ///< MQL5 time format (e.g., "12:30:45") + AMERICAN_MONTH_DAY, ///< American date format (e.g., "06/06/2024") + EUROPEAN_MONTH_DAY, ///< European date format (e.g., "06.06.2024") + AMERICAN_TIME, ///< American time format (e.g., "12:30 PM") + EUROPEAN_TIME, ///< European time format (e.g., "12:30") + }; + +}; // namespace time_shield + +#endif // TIME_SHIELD_HEADER_CORE_ENUMS_HPP_INCLUDED diff --git a/include/time_shield/core/initialization.hpp b/include/time_shield/core/initialization.hpp new file mode 100644 index 00000000..67bc0668 --- /dev/null +++ b/include/time_shield/core/initialization.hpp @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT +#pragma once +#ifndef TIME_SHIELD_HEADER_CORE_INITIALIZATION_HPP_INCLUDED +#define TIME_SHIELD_HEADER_CORE_INITIALIZATION_HPP_INCLUDED + +/// \file initialization.hpp +/// \ingroup lib_initialization +/// \brief Initialization helpers for the Time Shield library. +/// +/// This file defines the ::time_shield::init() function, which should be called once +/// before using any other Time Shield features that rely on internal time resolution. + +#include "time_utils.hpp" + +namespace time_shield { + + /// \ingroup lib_initialization + /// \brief Initializes the Time Shield library. + /// + /// This function performs required setup for internal components, + /// such as triggering lazy initialization used by ::time_shield::now_realtime_us(). + /// Call it once at the beginning of your program before using other parts of the library. + inline void init() { + now_realtime_us(); + } + +}; // namespace time_shield + +#endif // TIME_SHIELD_HEADER_CORE_INITIALIZATION_HPP_INCLUDED diff --git a/include/time_shield/core/iso_week_struct.hpp b/include/time_shield/core/iso_week_struct.hpp new file mode 100644 index 00000000..f48ecff4 --- /dev/null +++ b/include/time_shield/core/iso_week_struct.hpp @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: MIT +#pragma once +#ifndef TIME_SHIELD_HEADER_CORE_ISO_WEEK_STRUCT_HPP_INCLUDED +#define TIME_SHIELD_HEADER_CORE_ISO_WEEK_STRUCT_HPP_INCLUDED + +/// \file iso_week_struct.hpp +/// \brief Header for ISO week date structure. +/// +/// This file defines the IsoWeekDateStruct structure used to represent ISO 8601 week dates. + +#include + +namespace time_shield { + + /// \ingroup time_structures + /// \brief Structure to represent an ISO week date. + struct IsoWeekDateStruct { + int64_t year; ///< ISO week-numbering year component. + int32_t week; ///< ISO week number component (1-52/53). + int32_t weekday; ///< ISO weekday component (1=Monday .. 7=Sunday). + }; + + /// \ingroup time_structures + /// \brief Creates an IsoWeekDateStruct instance. + /// \param year ISO week-numbering year component. + /// \param week ISO week number component. + /// \param weekday ISO weekday component (1=Monday .. 7=Sunday). + /// \return An IsoWeekDateStruct instance with the provided components. + inline const IsoWeekDateStruct create_iso_week_date_struct( + int64_t year, + int32_t week = 1, + int32_t weekday = 1) { + IsoWeekDateStruct data{year, week, weekday}; + return data; + } + +}; // namespace time_shield + +#endif // TIME_SHIELD_HEADER_CORE_ISO_WEEK_STRUCT_HPP_INCLUDED diff --git a/include/time_shield/core/time_struct.hpp b/include/time_shield/core/time_struct.hpp new file mode 100644 index 00000000..7503834c --- /dev/null +++ b/include/time_shield/core/time_struct.hpp @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: MIT +#pragma once +#ifndef TIME_SHIELD_HEADER_CORE_TIME_STRUCT_HPP_INCLUDED +#define TIME_SHIELD_HEADER_CORE_TIME_STRUCT_HPP_INCLUDED + +/// \file time_struct.hpp +/// \brief Header for time structure and related functions. +/// +/// This file contains the definition of the TimeStruct structure and a function to create TimeStruct instances. + +namespace time_shield { + + /// \ingroup time_structures + /// \brief Structure to represent time. + struct TimeStruct { + int16_t hour; ///< Hour component of time (0-23) + int16_t min; ///< Minute component of time (0-59) + int16_t sec; ///< Second component of time (0-59) + int16_t ms; ///< Millisecond component of time (0-999) + }; + + /// \ingroup time_structures + /// \brief Creates a TimeStruct instance. + /// \param hour The hour component of the time. + /// \param min The minute component of the time. + /// \param sec The second component of the time, defaults to 0. + /// \param ms The millisecond component of the time, defaults to 0. + /// \return A TimeStruct instance with the provided time components. + inline const TimeStruct create_time_struct( + int16_t hour, + int16_t min, + int16_t sec = 0, + int16_t ms = 0) { + TimeStruct data{hour, min, sec, ms}; + return data; + } + +}; // namespace time_shield + +#endif // TIME_SHIELD_HEADER_CORE_TIME_STRUCT_HPP_INCLUDED diff --git a/include/time_shield/core/time_utils.hpp b/include/time_shield/core/time_utils.hpp new file mode 100644 index 00000000..d3d9aecb --- /dev/null +++ b/include/time_shield/core/time_utils.hpp @@ -0,0 +1,349 @@ +// SPDX-License-Identifier: MIT +#pragma once +#ifndef TIME_SHIELD_HEADER_CORE_TIME_UTILS_HPP_INCLUDED +#define TIME_SHIELD_HEADER_CORE_TIME_UTILS_HPP_INCLUDED + +/// \file time_utils.hpp +/// \brief Header file with time-related utility functions. +/// +/// This file contains various functions used for time calculations and conversions. + +#include "config.hpp" +#include "types.hpp" +#include "constants.hpp" + +#include +#include // For std::numeric_limits +#include // For clock_t and timespec (POSIX) +#include // For clock(), times(), etc. +#include // For std::once_flag + +#if TIME_SHIELD_PLATFORM_WINDOWS +# ifndef WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +# endif +# ifndef NOMINMAX +# define NOMINMAX +# endif +# include +#elif TIME_SHIELD_PLATFORM_UNIX +# include +# include +# include +# include +#else +# error "Unsupported platform for get_cpu_time()" +#endif + +namespace time_shield { + + /// \ingroup time_utils + /// \brief Get the current timespec. + /// \return struct timespec The current timespec. + inline struct timespec get_timespec_impl() noexcept { + // https://en.cppreference.com/w/c/chrono/timespec_get + struct timespec ts; +# if defined(CLOCK_REALTIME) + clock_gettime(CLOCK_REALTIME, &ts); // POSIX implementation +# else + timespec_get(&ts, TIME_UTC); +# endif + return ts; + } + + /// \ingroup time_utils + /// \brief Get current real time in microseconds using a platform-specific method. + /// + /// On Windows this function combines `QueryPerformanceCounter` + /// (high-resolution monotonic clock) with `GetSystemTimeAsFileTime` to compute an accurate, + /// stable UTC timestamp. The base time is initialized only once per process (lazy init). + /// On Unix-like systems a realtime anchor is captured once and combined with a + /// high-resolution monotonic clock to compute stable timestamps. + /// + /// \return Current UTC timestamp in microseconds. + inline int64_t now_realtime_us() { +# if TIME_SHIELD_PLATFORM_WINDOWS + static std::once_flag init_flag; + static int64_t s_perf_freq = 0; + static int64_t s_anchor_perf = 0; + static int64_t s_anchor_realtime_us = 0; + + std::call_once(init_flag, []() { + LARGE_INTEGER freq = {}; + LARGE_INTEGER counter = {}; + ::QueryPerformanceFrequency(&freq); + ::QueryPerformanceCounter(&counter); + + s_perf_freq = static_cast(freq.QuadPart); + s_anchor_perf = static_cast(counter.QuadPart); + + FILETIME ft; + ::GetSystemTimeAsFileTime(&ft); + + ULARGE_INTEGER uli; + uli.LowPart = ft.dwLowDateTime; + uli.HighPart = ft.dwHighDateTime; + + // 100ns ticks since 1601-01-01 to 1970-01-01 (signed constant!) + const int64_t k_epoch_diff_100ns = 116444736000000000LL; + + const int64_t filetime_100ns = static_cast(uli.QuadPart); + // Convert 100ns since 1601 -> us since 1970 + s_anchor_realtime_us = (filetime_100ns - k_epoch_diff_100ns) / 10; + }); + + LARGE_INTEGER now = {}; + ::QueryPerformanceCounter(&now); + + const int64_t now_ticks = static_cast(now.QuadPart); + const int64_t delta_ticks = now_ticks - s_anchor_perf; + + // Avoid overflow of (delta_ticks * 1000000) + const int64_t q = delta_ticks / s_perf_freq; + const int64_t r = delta_ticks % s_perf_freq; + + const int64_t delta_us = + q * 1000000LL + (r * 1000000LL) / s_perf_freq; + + return s_anchor_realtime_us + delta_us; +# else + static std::once_flag init_flag; + static int64_t s_anchor_realtime_us = 0; + static int64_t s_anchor_mono_ns = 0; + + std::call_once(init_flag, []() { + struct timespec realtime_ts{}; + struct timespec mono_ts{}; + +# if defined(CLOCK_MONOTONIC_RAW) + clock_gettime(CLOCK_MONOTONIC_RAW, &mono_ts); +# else + clock_gettime(CLOCK_MONOTONIC, &mono_ts); +# endif + clock_gettime(CLOCK_REALTIME, &realtime_ts); + + s_anchor_realtime_us = static_cast(realtime_ts.tv_sec) * 1000000LL + + realtime_ts.tv_nsec / 1000; + s_anchor_mono_ns = static_cast(mono_ts.tv_sec) * 1000000000LL + + mono_ts.tv_nsec; + }); + + struct timespec mono_now_ts{}; +# if defined(CLOCK_MONOTONIC_RAW) + clock_gettime(CLOCK_MONOTONIC_RAW, &mono_now_ts); +# else + clock_gettime(CLOCK_MONOTONIC, &mono_now_ts); +# endif + + const int64_t mono_now_ns = static_cast(mono_now_ts.tv_sec) * 1000000000LL + + mono_now_ts.tv_nsec; + const int64_t delta_ns = mono_now_ns - s_anchor_mono_ns; + return s_anchor_realtime_us + delta_ns / 1000; +# endif + } + + /// \ingroup time_utils + /// \brief Return monotonic seconds from a process-local reference. + /// + /// Uses `std::chrono::steady_clock` and returns an opaque monotonic value + /// that is only suitable for measuring intervals and deadlines. + /// + /// \return Monotonic seconds from a process-local reference. + inline ts_t monotonic_sec() noexcept { + const auto ticks = std::chrono::steady_clock::now().time_since_epoch(); + return static_cast(std::chrono::duration_cast(ticks).count()); + } + + /// \ingroup time_utils + /// \brief Return monotonic milliseconds from a process-local reference. + /// + /// Uses `std::chrono::steady_clock` and returns an opaque monotonic value + /// that is only suitable for measuring intervals and deadlines. + /// + /// \return Monotonic milliseconds from a process-local reference. + inline ts_ms_t monotonic_ms() noexcept { + const auto ticks = std::chrono::steady_clock::now().time_since_epoch(); + return static_cast(std::chrono::duration_cast(ticks).count()); + } + + /// \ingroup time_utils + /// \brief Return monotonic microseconds from a process-local reference. + /// + /// Uses `std::chrono::steady_clock` and returns an opaque monotonic value + /// that is only suitable for measuring intervals and deadlines. + /// + /// \return Monotonic microseconds from a process-local reference. + inline ts_us_t monotonic_us() noexcept { + const auto ticks = std::chrono::steady_clock::now().time_since_epoch(); + return static_cast(std::chrono::duration_cast(ticks).count()); + } + + /// \ingroup time_utils + /// \brief Get the nanosecond part of the current second. + /// \tparam T Type of the returned value (default is int). + /// \return T Nanosecond part of the current second. + template + inline T ns_of_sec() noexcept { + const struct timespec ts = get_timespec_impl(); + return static_cast(ts.tv_nsec); + } + + /// \ingroup time_utils + /// \brief Get the microsecond part of the current second. + /// \tparam T Type of the returned value (default is int). + /// \return T Microsecond part of the current second. + template + inline T us_of_sec() noexcept { + const struct timespec ts = get_timespec_impl(); + return static_cast(ts.tv_nsec / NS_PER_US); + } + + /// \ingroup time_utils + /// \brief Get the millisecond part of the current second. + /// \tparam T Type of the returned value (default is int). + /// \return T Millisecond part of the current second. + template + inline T ms_of_sec() noexcept { + const struct timespec ts = get_timespec_impl(); + return static_cast(ts.tv_nsec / NS_PER_MS); + } + + /// \brief Get the current UTC timestamp in seconds. + /// \return ts_t Current UTC timestamp in seconds. + inline ts_t ts() noexcept { + const struct timespec ts = get_timespec_impl(); + return ts.tv_sec; + } + + /// \ingroup time_utils + /// \brief Get the current UTC timestamp in seconds. + /// \return ts_t Current UTC timestamp in seconds. + inline ts_t timestamp() noexcept { + const struct timespec ts = get_timespec_impl(); + return ts.tv_sec; + } + + /// \ingroup time_utils + /// \brief Get the current UTC timestamp in floating-point seconds. + /// \return fts_t Current UTC timestamp in floating-point seconds. + inline fts_t fts() noexcept { + const struct timespec ts = get_timespec_impl(); + return static_cast(ts.tv_sec) + static_cast(ts.tv_nsec) / static_cast(NS_PER_SEC); + } + + /// \ingroup time_utils + /// \brief Get the current UTC timestamp in floating-point seconds. + /// \return fts_t Current UTC timestamp in floating-point seconds. + inline fts_t ftimestamp() noexcept { + const struct timespec ts = get_timespec_impl(); + return static_cast(ts.tv_sec) + static_cast(ts.tv_nsec) / static_cast(NS_PER_SEC); + } + + /// \ingroup time_utils + /// \brief Get the current UTC timestamp in milliseconds. + /// \return ts_ms_t Current UTC timestamp in milliseconds. + inline ts_ms_t ts_ms() noexcept { + const struct timespec ts = get_timespec_impl(); + return MS_PER_SEC * ts.tv_sec + ts.tv_nsec / NS_PER_MS; + } + + /// \ingroup time_utils + /// \brief Get the current UTC timestamp in milliseconds. + /// \return ts_ms_t Current UTC timestamp in milliseconds. + inline ts_ms_t timestamp_ms() noexcept { + const struct timespec ts = get_timespec_impl(); + return MS_PER_SEC * ts.tv_sec + ts.tv_nsec / NS_PER_MS; + } + + /// \ingroup time_utils + /// \brief Get the current UTC timestamp in milliseconds. + /// \return ts_ms_t Current UTC timestamp in milliseconds. + inline ts_ms_t now() noexcept { + const struct timespec ts = get_timespec_impl(); + return MS_PER_SEC * ts.tv_sec + ts.tv_nsec / NS_PER_MS; + } + + /// \ingroup time_utils + /// \brief Get the current UTC timestamp in microseconds. + /// \return ts_us_t Current UTC timestamp in microseconds. + inline ts_us_t ts_us() noexcept { + const struct timespec ts = get_timespec_impl(); + return US_PER_SEC * ts.tv_sec + ts.tv_nsec / NS_PER_US; + } + + /// \ingroup time_utils + /// \brief Get the current UTC timestamp in microseconds. + /// \return ts_us_t Current UTC timestamp in microseconds. + inline ts_us_t timestamp_us() noexcept { + const struct timespec ts = get_timespec_impl(); + return US_PER_SEC * ts.tv_sec + ts.tv_nsec / NS_PER_US; + } + + /// \ingroup time_utils + /// \brief Get the CPU time used by the current process. + /// \return CPU time in seconds, or NaN if not available. + /// \note This function attempts multiple fallback methods depending on platform capabilities. + /// \see https://habr.com/ru/articles/282301/ — original implementation idea + inline double get_cpu_time() noexcept { +# if TIME_SHIELD_PLATFORM_WINDOWS + FILETIME create_time{}, exit_time{}, kernel_time{}, user_time{}; + if (GetProcessTimes(GetCurrentProcess(), &create_time, &exit_time, &kernel_time, &user_time)) { + ULARGE_INTEGER li{}; + li.LowPart = user_time.dwLowDateTime; + li.HighPart = user_time.dwHighDateTime; + return static_cast(li.QuadPart) / 10000000.0; + } +# elif TIME_SHIELD_PLATFORM_UNIX + // AIX, BSD, Cygwin, HP-UX, Linux, OSX, and Solaris +# if defined(_POSIX_TIMERS) && (_POSIX_TIMERS > 0) + clockid_t id = (clockid_t)-1; +# if defined(_POSIX_CPUTIME) && (_POSIX_CPUTIME > 0) + if (clock_getcpuclockid(0, &id) != 0) { +# if defined(CLOCK_PROCESS_CPUTIME_ID) + id = CLOCK_PROCESS_CPUTIME_ID; +# elif defined(CLOCK_VIRTUAL) + id = CLOCK_VIRTUAL; +# endif + } +# elif defined(CLOCK_PROCESS_CPUTIME_ID) + id = CLOCK_PROCESS_CPUTIME_ID; +# elif defined(CLOCK_VIRTUAL) + id = CLOCK_VIRTUAL; +# endif + if (id != (clockid_t)-1) { + struct timespec ts; + if (clock_gettime(id, &ts) == 0) { + return static_cast(ts.tv_sec) + static_cast(ts.tv_nsec) / 1e9; + } + } +# endif + +# if defined(RUSAGE_SELF) + struct rusage usage{}; + if (getrusage(RUSAGE_SELF, &usage) == 0) { + return static_cast(usage.ru_utime.tv_sec) + static_cast(usage.ru_utime.tv_usec) / 1e6; + } +# endif + +# if defined(_SC_CLK_TCK) + struct tms t{}; + if (times(&t) != (clock_t)-1) { + return static_cast(t.tms_utime) / static_cast(sysconf(_SC_CLK_TCK)); + } +# endif + +# if defined(CLOCKS_PER_SEC) + clock_t cl = clock(); + if (cl != (clock_t)-1) { + return static_cast(cl) / static_cast(CLOCKS_PER_SEC); + } +# endif +# else +# warning "get_cpu_time() may not work correctly: unsupported platform" +# endif + return std::numeric_limits::quiet_NaN(); + } + +}; // namespace time_shield + +#endif // TIME_SHIELD_HEADER_CORE_TIME_UTILS_HPP_INCLUDED diff --git a/include/time_shield/core/time_zone_struct.hpp b/include/time_shield/core/time_zone_struct.hpp new file mode 100644 index 00000000..a2f56b66 --- /dev/null +++ b/include/time_shield/core/time_zone_struct.hpp @@ -0,0 +1,121 @@ +// SPDX-License-Identifier: MIT +#pragma once +#ifndef TIME_SHIELD_HEADER_CORE_TIME_ZONE_STRUCT_HPP_INCLUDED +#define TIME_SHIELD_HEADER_CORE_TIME_ZONE_STRUCT_HPP_INCLUDED + +/// \file time_zone_struct.hpp +/// \brief Header for time zone structure and related functions. + +#include "config.hpp" +#include "types.hpp" +#include "constants.hpp" + +#include + +namespace time_shield { + + /// \ingroup time_structures + /// \brief Structure to represent time zone information. + /// \details + /// This structure contains the hour and minute components of a time zone offset, + /// as well as a boolean indicating whether the offset is positive or negative. + struct TimeZoneStruct { + int hour; ///< Hour component of time (0-23) + int min; ///< Minute component of time (0-59) + bool is_positive; ///< True if the time zone offset is positive, false if negative + }; + + /// \ingroup time_structures + /// \brief Creates a TimeZoneStruct instance. + /// \param hour The hour component of the time. + /// \param min The minute component of the time. + /// \param is_positive True if the time zone offset is positive, false if negative. + /// \return A TimeZoneStruct instance with the provided time components. + inline TimeZoneStruct create_time_zone_struct( + int hour, + int min, + bool is_positive = true) { + return TimeZoneStruct{hour, min, is_positive}; + } + +//------------------------------------------------------------------------------ + + /// \ingroup time_structures_time_conversions + /// \brief Converts an integer to a TimeZoneStruct. + /// \param offset The integer to convert. + /// \return A TimeZoneStruct represented by the given integer. + inline TimeZoneStruct to_time_zone_struct(tz_t offset) { + const int64_t off = static_cast(offset); + const int64_t abs_val = (off < 0) ? -off : off; + + const int hour = static_cast(abs_val / static_cast(SEC_PER_HOUR)); + const int min = static_cast((abs_val % static_cast(SEC_PER_HOUR)) / + static_cast(SEC_PER_MIN)); + + return TimeZoneStruct{hour, min, off >= 0}; + } + + + /// \ingroup time_structures_time_conversions + /// \brief Alias for to_time_zone_struct function. + /// \copydoc to_time_zone_struct + inline TimeZoneStruct to_tz(tz_t offset) { + return to_time_zone_struct(offset); + } + +//------------------------------------------------------------------------------ + + /// \ingroup time_structures_time_formatting + /// \brief Converts a TimeZoneStruct to a string representation. + /// \param tz The TimeZoneStruct to convert. + /// \return A string representation of the TimeZoneStruct. + inline std::string time_zone_struct_to_string(const TimeZoneStruct& tz) { + char sign = tz.is_positive ? '+' : '-'; + return std::string(1, sign) + (tz.hour < 10 ? "0" : "") + std::to_string(tz.hour) + ":" + (tz.min < 10 ? "0" : "") + std::to_string(tz.min); + } + + /// \ingroup time_structures_time_formatting + /// \brief Alias for time_zone_struct_to_string function. + /// \copydoc time_zone_struct_to_string + inline std::string to_string(const TimeZoneStruct& tz) { + return time_zone_struct_to_string(tz); + } + + /// \ingroup time_structures_time_formatting + /// \brief Alias for time_zone_struct_to_string function. + /// \copydoc time_zone_struct_to_string + inline std::string to_str(const TimeZoneStruct& tz) { + return time_zone_struct_to_string(tz); + } + +//------------------------------------------------------------------------------ + + /// \ingroup time_structures_time_conversions + /// \brief Convert a TimeZoneStruct to a numeric UTC offset (seconds). + /// \param tz Time zone descriptor. + /// \return UTC offset in seconds (local = utc + offset). + TIME_SHIELD_CONSTEXPR inline tz_t time_zone_struct_to_offset(const TimeZoneStruct& tz) noexcept { + return tz.is_positive + ? static_cast( static_cast(tz.hour) * static_cast(SEC_PER_HOUR) + + static_cast(tz.min) * static_cast(SEC_PER_MIN) ) + : static_cast(-( static_cast(tz.hour) * static_cast(SEC_PER_HOUR) + + static_cast(tz.min) * static_cast(SEC_PER_MIN) )); + } + + /// \ingroup time_structures_time_conversions + /// \brief Alias for time_zone_struct_to_offset. + /// \copydoc time_zone_struct_to_offset + TIME_SHIELD_CONSTEXPR inline tz_t tz_to_offset(const TimeZoneStruct& tz) noexcept { + return time_zone_struct_to_offset(tz); + } + + /// \ingroup time_structures_time_conversions + /// \brief Alias for time_zone_struct_to_offset. + /// \copydoc time_zone_struct_to_offset + TIME_SHIELD_CONSTEXPR inline tz_t to_offset(const TimeZoneStruct& tz) noexcept { + return time_zone_struct_to_offset(tz); + } + +}; // namespace time_shield + +#endif // TIME_SHIELD_HEADER_CORE_TIME_ZONE_STRUCT_HPP_INCLUDED diff --git a/include/time_shield/core/types.hpp b/include/time_shield/core/types.hpp new file mode 100644 index 00000000..199a31df --- /dev/null +++ b/include/time_shield/core/types.hpp @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: MIT +#pragma once +#ifndef TIME_SHIELD_HEADER_CORE_TYPES_HPP_INCLUDED +#define TIME_SHIELD_HEADER_CORE_TYPES_HPP_INCLUDED + +/// \file types.hpp +/// \brief Type definitions for time-related units and formats. +/// +/// This file defines standard type aliases used across the Time Shield library. +/// It includes representations for Unix timestamps, Julian dates, automation time, +/// time zone offsets, and other related units. + +#include + +namespace time_shield { + +/// \defgroup time_types Time Types +/// \brief Fundamental type definitions for time-related data. +/// \ingroup cpp +/// +/// This group defines the core time representations used throughout the library, +/// including timestamps, fractional time units, Julian dates, and time zone offsets. +/// +/// ### Type Categories +/// - **Unix-based timestamps**: `ts_t`, `ts_ms_t`, `ts_us_t` +/// - **Fractional and floating-point time**: `fts_t`, `oadate_t`, `jd_t` +/// - **Julian date types**: `jd_t`, `mjd_t`, `jdn_t` +/// - **Utility units**: `year_t`, `dse_t`, `tz_t` +/// +/// ### Example Usage +/// ```cpp +/// time_shield::ts_t now = 1700000000; // Unix timestamp in seconds +/// time_shield::fts_t precise = 1700000000.123; // Time with fractional seconds +/// time_shield::jd_t julian = 2459580.5; // Julian Date +/// time_shield::tz_t offset = 180; // UTC+3 in minutes +/// ``` + +/// \{ + + // --- Calendar & Year Types --- + typedef int64_t year_t; ///< Year as an integer (e.g., 2024). + typedef int64_t dse_t; ///< Unix day count since 1970‑01‑01 (days since epoch). + using unix_day_t = dse_t; ///< Alias for Unix day count type. + using unixday_t = dse_t; ///< Alias for Unix day count type. + typedef int32_t iso_week_t; ///< ISO week number type (1-52/53). + typedef int32_t iso_weekday_t; ///< ISO weekday number type (1=Monday .. 7=Sunday). + + // --- Unix Timestamp Types --- + typedef int64_t ts_t; ///< Unix timestamp in seconds since 1970‑01‑01T00:00:00Z. + typedef int64_t ts_ms_t; ///< Unix timestamp in milliseconds since epoch. + typedef int64_t ts_us_t; ///< Unix timestamp in microseconds since epoch. + typedef double fts_t; ///< Floating-point timestamp (fractional seconds since epoch). + + // --- Automation and Julian Time --- + typedef double oadate_t; ///< OLE Automation date (days since 1899‑12‑30, as `double`). + typedef double jd_t; ///< Julian Date (days since -4713‑11‑24T12:00:00Z). + typedef double mjd_t; ///< Modified Julian Date (JD − 2400000.5). + typedef uint64_t jdn_t; ///< Julian Day Number (whole days since Julian epoch). + + // --- Time zone offset --- + typedef int32_t tz_t; ///< Time zone offset in minutes from UTC (e.g., +180 = UTC+3). + +/// \} + +}; // namespace time_shield + +#endif // TIME_SHIELD_HEADER_CORE_TYPES_HPP_INCLUDED diff --git a/include/time_shield/core/validation.hpp b/include/time_shield/core/validation.hpp new file mode 100644 index 00000000..1b4c4f08 --- /dev/null +++ b/include/time_shield/core/validation.hpp @@ -0,0 +1,397 @@ +// SPDX-License-Identifier: MIT +#pragma once +#ifndef TIME_SHIELD_HEADER_CORE_VALIDATION_HPP_INCLUDED +#define TIME_SHIELD_HEADER_CORE_VALIDATION_HPP_INCLUDED + +/// \file validation.hpp +/// \brief Header file with time-related validation functions. +/// +/// This file contains functions for validating dates, times, and timestamps. + +#include "config.hpp" +#include "types.hpp" +#include "constants.hpp" +#include "enums.hpp" +#include "time_zone_struct.hpp" + +namespace time_shield { + +/// \defgroup time_validation Time Validation +/// \brief A comprehensive set of functions for validating dates, times, leap years, and time zones. +/// +/// This module provides functionalities to validate the correctness of date-time values, +/// leap years, and time zone offsets. It also includes utilities for determining weekends +/// and ensuring the validity of timestamp-based calculations. +/// +/// ### Key Features: +/// - Validate leap years using dates or timestamps. +/// - Ensure the correctness of date components (year, month, day). +/// - Verify the validity of time components (hour, minute, second, millisecond). +/// - Check the validity of time zones and time zone structures. +/// - Determine if a given timestamp or day corresponds to a weekend. +/// +/// ### Usage Examples: +/// - Check if a year is a leap year: +/// \code{.cpp} +/// bool is_leap = time_shield::is_leap_year_date(2024); +/// \endcode +/// +/// - Validate a specific date: +/// \code{.cpp} +/// bool is_valid = time_shield::is_valid_date(2024, 2, 29); +/// \endcode +/// +/// - Check if a timestamp falls on a weekend: +/// \code{.cpp} +/// bool is_weekend = time_shield::is_day_off(1698249600); // Saturday, Oct 26, 2024 +/// \endcode +/// +/// \{ + + /// \brief Checks if the given year is a leap year. + /// \tparam T The type of the year (default is year_t). + /// \param year Year to check. + /// \return true if the year is a leap year, false otherwise. + template + TIME_SHIELD_CONSTEXPR bool is_leap_year_date(T year) noexcept { + return ((year & 3) == 0 && ((year % 25) != 0 || (year & 15) == 0)); + } + + /// \brief Alias for is_leap_year_date function. + /// \copydoc is_leap_year_date + template + TIME_SHIELD_CONSTEXPR bool check_leap_year(T year) noexcept { + return is_leap_year_date(year); + } + + /// \brief Alias for is_leap_year_date function. + /// \copydoc is_leap_year_date + template + TIME_SHIELD_CONSTEXPR bool leap_year(T year) noexcept { + return is_leap_year_date(year); + } + +//------------------------------------------------------------------------------ + + /// \brief Checks if the given year is a leap year. + /// + /// This function determines whether the year corresponding to the provided timestamp + /// is a leap year. + /// + /// \tparam T The type of the year parameter (default is year_t). + /// \param ts Timestamp in seconds since the Unix epoch. + /// \return Returns true if the year is a leap year. + TIME_SHIELD_CONSTEXPR inline bool is_leap_year_ts(ts_t ts) { + // 9223372029693630000 reaches year 292277024400 from the 2000 epoch. + // This value overflows the n_400_years * SEC_PER_400_YEARS calculation. + // The supported bound is reduced to 9223371890843040000. + constexpr int64_t BIAS_292277022000 = 9223371890843040000LL; + constexpr int64_t BIAS_2000 = 946684800LL; + + int64_t y = MAX_YEAR; + int64_t secs = -((ts - BIAS_2000) - BIAS_292277022000); + + const int64_t n_400_years = secs / SEC_PER_400_YEARS; + secs -= n_400_years * SEC_PER_400_YEARS; + y -= n_400_years * 400; + + const int64_t n_100_years = secs / SEC_PER_100_YEARS; + secs -= n_100_years * SEC_PER_100_YEARS; + y -= n_100_years * 100; + + const int64_t n_4_years = secs / SEC_PER_4_YEARS; + secs -= n_4_years * SEC_PER_4_YEARS; + y -= n_4_years * 4; + + const int64_t n_1_years = secs / SEC_PER_YEAR; + secs -= n_1_years * SEC_PER_YEAR; + y -= n_1_years; + + y = secs == 0 ? y : y - 1; + return is_leap_year_date(y); + } + + /// \brief Alias for is_leap_year_ts function. + /// \copydoc is_leap_year_ts + TIME_SHIELD_CONSTEXPR inline bool leap_year_ts(ts_t ts) { + return is_leap_year_ts(ts); + } + + /// \brief Alias for is_leap_year_ts function. + /// \copydoc is_leap_year_ts + TIME_SHIELD_CONSTEXPR inline bool check_leap_year_ts(ts_t ts) { + return is_leap_year_ts(ts); + } + + /// \brief Alias for is_leap_year_ts function. + /// \copydoc is_leap_year_ts + TIME_SHIELD_CONSTEXPR inline bool is_leap_year(ts_t ts) { + return is_leap_year_ts(ts); + } + +//------------------------------------------------------------------------------ + + /// \brief Check if the time zone is valid. + /// \tparam T The type of the time zone components (default is int). + /// \param hour The hour component of the time zone. + /// \param min The minute component of the time zone. + /// \return True if the time zone is valid, false otherwise. + template + TIME_SHIELD_CONSTEXPR inline bool is_valid_time_zone( + T hour, + T min) noexcept { + if (hour < 0 || hour > 23) return false; + if (min < 0 || min > 59) return false; + return true; + } + + /// \brief Alias for is_valid_time_zone function. + /// \copydoc is_valid_time_zone + template + TIME_SHIELD_CONSTEXPR inline bool is_valid_tz( + T hour, + T min) { + return is_valid_time_zone(hour, min); + } + +//------------------------------------------------------------------------------ + + /// \ingroup time_structures + /// \brief Check if the time zone is valid. + /// \tparam T The type of the time zone structure (default is TimeZoneStruct). + /// \param time_zone The time zone structure containing hour and minute components. + /// \return True if the time zone is valid, false otherwise. + template + TIME_SHIELD_CONSTEXPR inline bool is_valid_time_zone_offset( + const T& time_zone) noexcept { + return is_valid_time_zone(time_zone.hour, time_zone.min); + } + + /// \ingroup time_structures + /// \brief Alias for is_valid_time_zone_offset function. + /// \copydoc is_valid_time_zone_offset + template + TIME_SHIELD_CONSTEXPR inline bool is_valid_time_zone( + const T& time_zone) { + return is_valid_time_zone_offset(time_zone); + } + + /// \ingroup time_structures + /// \brief Alias for is_valid_time_zone_offset function. + /// \copydoc is_valid_time_zone_offset + template + TIME_SHIELD_CONSTEXPR inline bool is_valid_tz( + const T& time_zone) { + return is_valid_time_zone_offset(time_zone); + } + +//------------------------------------------------------------------------------ + + /// \brief Checks the correctness of the specified time. + /// \tparam T1 The type of the hour, minute, and second values (default is int). + /// \tparam T2 The type of the millisecond value (default is int). + /// \param hour Hour + /// \param min Minute + /// \param sec Second + /// \param ms Millisecond (default is 0). + /// \return true if the time is valid, false otherwise. + template + TIME_SHIELD_CONSTEXPR inline bool is_valid_time( + T1 hour, + T1 min, + T1 sec, + T2 ms = 0) noexcept { + if (hour < 0 || hour > 23) return false; + if (min < 0 || min > 59) return false; + if (sec < 0 || sec > 59) return false; + if (ms < 0 || ms > 999) return false; + return true; + } + + /// \ingroup time_structures + /// \brief Checks the correctness of the specified time. + /// \tparam T The type of the time structure. + /// \param time Time structure. + /// \return true if the time is valid, false otherwise. + template + TIME_SHIELD_CONSTEXPR inline bool is_valid_time( + const T& time) noexcept { + return is_valid_time(time.hour, time.min, time.sec, time.ms); + } + + /// \brief Checks the correctness of the specified date. + /// \tparam T1 The type of the year or day value (default is year_t). + /// \tparam T2 The type of the month and day values (default is int). + /// \param year Year or day. + /// \param month Month. + /// \param day Day or year. + /// \return true if the date is valid, false otherwise. + template + TIME_SHIELD_CONSTEXPR inline bool is_valid_date( + T1 year, + T2 month, + T2 day) noexcept { + if (day > 31 && year <= 31) { + return is_valid_date((T1)day, month, (T2)year); + } + if (year < MIN_YEAR) return false; + if (year > MAX_YEAR) return false; + if (month < 1 || month > 12) return false; + if (day < 1 || day > 31) return false; + if (month == FEB) { + const bool is_leap_year = is_leap_year_date(year); + if (is_leap_year && day > 29) return false; + if (!is_leap_year && day > 28) return false; + } else { + switch(month) { + case 4: + case 6: + case 9: + case 11: + if (day > 30) return false; + default: + break; + }; + } + return true; + } + + /// \ingroup time_structures + /// \brief Checks the correctness of the specified date. + /// \tparam T The type of the date-time structure. + /// \param date Date-time structure. + /// \return true if the date is valid, false otherwise. + template + TIME_SHIELD_CONSTEXPR inline bool is_valid_date(const T& date) noexcept { + return is_valid_date(date.year, date.mon, date.day); + } + + /// \brief Checks the correctness of a date and time. + /// \tparam T1 The type of the year or day value (default is year_t). + /// \tparam T2 The type of the month and day values (default is int). + /// \tparam T3 The type of the millisecond value (default is int). + /// \param year Year or day. + /// \param month Month. + /// \param day Day or year. + /// \param hour Hour (default is 0). + /// \param min Minute (default is 0). + /// \param sec Second (default is 0). + /// \param ms Millisecond (default is 0). + /// \return true if the date and time are valid, false otherwise. + template + TIME_SHIELD_CONSTEXPR inline bool is_valid_date_time( + T1 year, + T2 month, + T2 day, + T2 hour = 0, + T2 min = 0, + T2 sec = 0, + T3 ms = 0) noexcept { + if (!is_valid_date(year, month, day)) return false; + if (!is_valid_time(hour, min, sec, ms)) return false; + return true; + } + + /// \ingroup time_structures + /// \brief Checks the correctness of a date and time. + /// \tparam T The type of the date-time structure. + /// \param date_time Date-time structure. + /// \return true if the date and time are valid, false otherwise. + template + TIME_SHIELD_CONSTEXPR inline bool is_valid_date_time( + const T &date_time) noexcept { + if (!is_valid_date(date_time)) return false; + if (!is_valid_time(date_time)) return false; + return true; + } + +//------------------------------------------------------------------------------ + + /// \brief Check if a given timestamp corresponds to a weekend day (Saturday or Sunday). + /// + /// This function checks if the given timestamp falls on a weekend day, which is either Saturday or Sunday. + /// + /// \param ts Timestamp to check (default: current timestamp). + /// \return true if the day is a weekend day, false otherwise. + TIME_SHIELD_CONSTEXPR inline bool is_day_off(ts_t ts) noexcept { + const int64_t day = static_cast(ts) / static_cast(SEC_PER_DAY); + int64_t wd64 = (day + static_cast(THU)) % static_cast(DAYS_PER_WEEK); + if (wd64 < 0) wd64 += static_cast(DAYS_PER_WEEK); // for ts < 0 + const int wd = static_cast(wd64); + return (wd == SUN || wd == SAT); + } + + /// \brief Alias for is_day_off function. + /// \copydoc is_day_off + TIME_SHIELD_CONSTEXPR inline bool is_weekend(ts_t ts) noexcept { + return is_day_off(ts); + } + +//------------------------------------------------------------------------------ + + /// \brief Check if a given day (since Unix epoch) corresponds to a weekend day (Saturday or Sunday). + /// This function checks if the given day (number of days since Unix epoch) falls on a weekend day, + /// which is either Saturday or Sunday. + /// \param unix_day Day to check (number of days since Unix epoch). + /// \return true if the day is a weekend day, false otherwise. + template + TIME_SHIELD_CONSTEXPR inline bool is_day_off_unix_day(T unix_day) noexcept { + int64_t wd = (static_cast(unix_day) + THU) % DAYS_PER_WEEK; + wd += (wd < 0) ? DAYS_PER_WEEK : 0; + return (wd == SUN || wd == SAT); + } + + /// \brief Alias for is_day_off_unix_day function. + /// \copydoc is_day_off_unix_day + template + TIME_SHIELD_CONSTEXPR inline bool is_weekend_unix_day(T unix_day) noexcept { + return is_day_off_unix_day(unix_day); + } + +//------------------------------------------------------------------------------ + + /// \brief Check if a given timestamp corresponds to a workday (Monday to Friday). + /// \param ts Timestamp to check. + /// \return true if the day is a workday, false otherwise. + TIME_SHIELD_CONSTEXPR inline bool is_workday(ts_t ts) noexcept { + return !is_day_off(ts); + } + + /// \brief Check if a given timestamp in milliseconds corresponds to a workday (Monday to Friday). + /// \param ts_ms Timestamp in milliseconds to check. + /// \return true if the day is a workday, false otherwise. + TIME_SHIELD_CONSTEXPR inline bool is_workday_ms(ts_ms_t ts_ms) noexcept { + return is_workday(static_cast(ts_ms / MS_PER_SEC)); + } + + /// \brief Check if a calendar date corresponds to a workday (Monday to Friday). + /// \param year Year component of the date. + /// \param month Month component of the date. + /// \param day Day component of the date. + /// \return true if the date is valid and a workday, false otherwise. + TIME_SHIELD_CONSTEXPR inline bool is_workday(year_t year, int month, int day) noexcept { + const auto y = static_cast(year); + const auto m = static_cast(month); + const auto d = static_cast(day); + if (!is_valid_date(y, m, d)) { + return false; + } + + const int64_t adj_y = static_cast(y) - (static_cast(m) <= 2 ? 1 : 0); + const int64_t adj_m = static_cast(m) <= 2 + ? static_cast(m) + 9 + : static_cast(m) - 3; + const int64_t era = (adj_y >= 0 ? adj_y : adj_y - 399) / 400; + const int64_t yoe = adj_y - era * 400; + const int64_t doy = (153 * adj_m + 2) / 5 + static_cast(d) - 1; + const int64_t doe = yoe * 365 + yoe / 4 - yoe / 100 + doy; + const dse_t unix_day = static_cast(era * 146097 + doe - 719468); + + return !is_day_off_unix_day(unix_day); + } + +/// \} + +} // namespace time_shield + +#endif // TIME_SHIELD_HEADER_CORE_VALIDATION_HPP_INCLUDED diff --git a/include/time_shield/date_conversions.hpp b/include/time_shield/date_conversions.hpp index 397bc57b..083eec5f 100644 --- a/include/time_shield/date_conversions.hpp +++ b/include/time_shield/date_conversions.hpp @@ -1,112 +1,8 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_DATE_CONVERSIONS_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_DATE_CONVERSIONS_HPP_INCLUDED +#ifndef TIME_SHIELD_HEADER_DATE_CONVERSIONS_HPP_INCLUDED +#define TIME_SHIELD_HEADER_DATE_CONVERSIONS_HPP_INCLUDED -/// \file date_conversions.hpp -/// \brief Conversions related to calendar dates and DateStruct helpers. +#include -#include "config.hpp" -#include "constants.hpp" -#include "types.hpp" -#include "unix_time_conversions.hpp" -#include "validation.hpp" - -namespace time_shield { - -/// \ingroup time_conversions -/// \{ - - /// \brief Get the year from the timestamp. - /// - /// This function returns the year of the specified timestamp in seconds since the Unix epoch. - /// - /// \tparam T The return type of the function (default is year_t). - /// \param ts Timestamp in seconds (default is current timestamp). - /// \return Year of the specified timestamp. - template - TIME_SHIELD_CONSTEXPR inline T year_of(ts_t ts = time_shield::ts()) { - return years_since_epoch(ts) + static_cast(UNIX_EPOCH); - } - - /// \brief Get the year from the timestamp in milliseconds. - /// - /// This function returns the year of the specified timestamp in milliseconds since the Unix epoch. - /// - /// \tparam T The return type of the function (default is year_t). - /// \param ts_ms Timestamp in milliseconds (default is current timestamp). - /// \return Year of the specified timestamp. - template - TIME_SHIELD_CONSTEXPR inline T year_of_ms(ts_ms_t ts_ms = time_shield::ts_ms()) { - return year_of(ms_to_sec(ts_ms)); - } - - /// \brief Get the number of days in a year. - /// \param year Year. - /// \return Number of days in the given year. - template - TIME_SHIELD_CONSTEXPR inline T1 num_days_in_year(T2 year) noexcept { - if (is_leap_year_date(year)) return DAYS_PER_LEAP_YEAR; - return DAYS_PER_YEAR; - } - - /// \brief Get the number of days in the current year. - /// - /// This function calculates and returns the number of days in the current year based on the provided timestamp. - /// - /// \param ts Timestamp. - /// \return Number of days in the current year. - template - TIME_SHIELD_CONSTEXPR inline T num_days_in_year_ts(ts_t ts = time_shield::ts()) { - if (is_leap_year_ts(ts)) return DAYS_PER_LEAP_YEAR; - return DAYS_PER_YEAR; - } - - /// \brief Get the day of the week. - /// \tparam T1 Return type (default: Weekday). - /// \tparam T2 Year type. - /// \tparam T3 Month type. - /// \tparam T4 Day type. - /// \param year Year. - /// \param month Month. - /// \param day Day. - /// \return Day of the week (SUN = 0, MON = 1, ... SAT = 6). - template - TIME_SHIELD_CONSTEXPR inline T1 day_of_week_date(T2 year, T3 month, T4 day) { - year_t a = 0; - year_t y = 0; - year_t m = 0; - year_t R = 0; - a = (14 - month) / MONTHS_PER_YEAR; - y = year - a; - m = month + MONTHS_PER_YEAR * a - 2; - R = 7000 + ( day + y + (y / 4) - (y / 100) + (y / 400) + (31 * m) / MONTHS_PER_YEAR); - return static_cast(R % DAYS_PER_WEEK); - } - - /// \ingroup time_structures - /// \brief Get the day of the week from a date structure. - /// - /// This function takes a date structure with fields 'year', 'mon', and 'day', - /// and returns the day of the week (SUN = 0, MON = 1, ... SAT = 6). - /// - /// \param date Structure containing year, month, and day. - /// \return Day of the week (SUN = 0, MON = 1, ... SAT = 6). - template - TIME_SHIELD_CONSTEXPR inline T1 weekday_of_date(const T2& date) { - return day_of_week_date(date.year, date.mon, date.day); - } - - /// \ingroup time_structures - /// \brief Alias for weekday_of_date. - /// \copydoc weekday_of_date - template - TIME_SHIELD_CONSTEXPR inline T1 weekday_from_date(const T2& date) { - return weekday_of_date(date); - } - -/// \} - -}; // namespace time_shield - -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_DATE_CONVERSIONS_HPP_INCLUDED +#endif // TIME_SHIELD_HEADER_DATE_CONVERSIONS_HPP_INCLUDED diff --git a/include/time_shield/date_struct.hpp b/include/time_shield/date_struct.hpp index b88db0ca..5cfb60e3 100644 --- a/include/time_shield/date_struct.hpp +++ b/include/time_shield/date_struct.hpp @@ -1,37 +1,8 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_DATE_STRUCT_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_DATE_STRUCT_HPP_INCLUDED +#ifndef TIME_SHIELD_HEADER_DATE_STRUCT_HPP_INCLUDED +#define TIME_SHIELD_HEADER_DATE_STRUCT_HPP_INCLUDED -/// \file date_struct.hpp -/// \brief Header for date structure and related functions. -/// -/// This file contains the definition of the DateStruct structure and a function to create DateStruct instances. +#include -namespace time_shield { - - /// \ingroup time_structures - /// \brief Structure to represent a date. - struct DateStruct { - int64_t year; ///< Year component of the date. - int32_t mon; ///< Month component of the date (1-12). - int32_t day; ///< Day component of the date (1-31). - }; - - /// \ingroup time_structures - /// \brief Creates a DateStruct instance. - /// \param year The year component of the date. - /// \param mon The month component of the date, defaults to 1 (January). - /// \param day The day component of the date, defaults to 1. - /// \return A DateStruct instance with the provided date components. - inline const DateStruct create_date_struct( - int64_t year, - int32_t mon = 1, - int32_t day = 1) { - DateStruct data{year, mon, day}; - return data; - } - -}; // namespace time_shield - -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_DATE_STRUCT_HPP_INCLUDED +#endif // TIME_SHIELD_HEADER_DATE_STRUCT_HPP_INCLUDED diff --git a/include/time_shield/date_time.hpp b/include/time_shield/date_time.hpp new file mode 100644 index 00000000..6553287b --- /dev/null +++ b/include/time_shield/date_time.hpp @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: MIT +#pragma once +#ifndef TIME_SHIELD_HEADER_DATE_TIME_HPP_INCLUDED +#define TIME_SHIELD_HEADER_DATE_TIME_HPP_INCLUDED + +#include + +#endif // TIME_SHIELD_HEADER_DATE_TIME_HPP_INCLUDED diff --git a/include/time_shield/date_time_conversions.hpp b/include/time_shield/date_time_conversions.hpp index 843add70..72451f9e 100644 --- a/include/time_shield/date_time_conversions.hpp +++ b/include/time_shield/date_time_conversions.hpp @@ -1,1203 +1,8 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_DATE_TIME_CONVERSIONS_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_DATE_TIME_CONVERSIONS_HPP_INCLUDED +#ifndef TIME_SHIELD_HEADER_DATE_TIME_CONVERSIONS_HPP_INCLUDED +#define TIME_SHIELD_HEADER_DATE_TIME_CONVERSIONS_HPP_INCLUDED -/// \file date_time_conversions.hpp -/// \brief Conversions involving DateTimeStruct and day boundary helpers. +#include -#include "config.hpp" -#include "constants.hpp" -#include "date_conversions.hpp" -#include "date_struct.hpp" -#include "date_time_struct.hpp" -#include "detail/fast_date.hpp" -#include "detail/floor_math.hpp" -#include "enums.hpp" -#include "time_unit_conversions.hpp" -#include "time_utils.hpp" -#include "types.hpp" -#include "unix_time_conversions.hpp" -#include "validation.hpp" - -#include -#include -#include -#include -#include - -namespace time_shield { - -/// \ingroup time_conversions -/// \{ - - namespace legacy { - - /// \ingroup time_structures - /// \brief Converts a timestamp to a date-time structure. - /// - /// This function converts a timestamp (usually an integer representing seconds since epoch) - /// to a custom date-time structure. The default type for the timestamp is int64_t. - /// - /// \tparam T1 The date-time structure type to be returned. - /// \tparam T2 The type of the timestamp (default is int64_t). - /// \param ts The timestamp to be converted. - /// \return A date-time structure of type T1. - template - T1 to_date_time(T2 ts) { - // 9223372029693630000 reaches year 292277024400 from the 2000 epoch. - // This value overflows the n_400_years * SEC_PER_400_YEARS calculation. - // The supported bound is reduced to 9223371890843040000. - constexpr int64_t BIAS_292277022000 = 9223371890843040000LL; - constexpr int64_t BIAS_2000 = 946684800LL; - - int64_t y = MAX_YEAR; - int64_t secs = -((static_cast(ts) - BIAS_2000) - BIAS_292277022000); - - const int64_t n_400_years = secs / SEC_PER_400_YEARS; - secs -= n_400_years * SEC_PER_400_YEARS; - y -= n_400_years * 400LL; - - const int64_t n_100_years = secs / SEC_PER_100_YEARS; - secs -= n_100_years * SEC_PER_100_YEARS; - y -= n_100_years * 100LL; - - const int64_t n_4_years = secs / SEC_PER_4_YEARS; - secs -= n_4_years * SEC_PER_4_YEARS; - y -= n_4_years * 4LL; - - const int64_t n_1_years = secs / SEC_PER_YEAR; - secs -= n_1_years * SEC_PER_YEAR; - y -= n_1_years; - - T1 date_time; - - if (secs == 0) { - date_time.year = y; - date_time.mon = 1; - date_time.day = 1; - return date_time; - } - - date_time.year = y - 1; - const bool is_leap_year = is_leap_year_date(date_time.year); - secs = is_leap_year ? SEC_PER_LEAP_YEAR - secs : SEC_PER_YEAR - secs; - const int days = static_cast(secs / SEC_PER_DAY); - - constexpr int JAN_AND_FEB_DAY_LEAP_YEAR = 60 - 1; - constexpr int TABLE_MONTH_OF_YEAR[] = { - 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // January (31 days) - 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, // February (28 days) - 3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3, // March (31 days) - 4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4, // April (30 days) - 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5, - 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, - 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, - 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, - 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, - 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, - 11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11, - 12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12, - }; - constexpr int TABLE_DAY_OF_YEAR[] = { - 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31, // January (31 days) - 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28, // February (28 days) - 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31, // March (31 days) - 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30, // April (30 days) - 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31, - 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30, - 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31, - 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31, - 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30, - 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31, - 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30, - 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31, - }; - - if (is_leap_year) { - const int prev_days = days - 1; - date_time.day = days == JAN_AND_FEB_DAY_LEAP_YEAR ? (TABLE_DAY_OF_YEAR[prev_days] + 1) : - (days > JAN_AND_FEB_DAY_LEAP_YEAR ? TABLE_DAY_OF_YEAR[prev_days] : TABLE_DAY_OF_YEAR[days]); - date_time.mon = days >= JAN_AND_FEB_DAY_LEAP_YEAR ? TABLE_MONTH_OF_YEAR[prev_days] : TABLE_MONTH_OF_YEAR[days]; - } else { - date_time.day = TABLE_DAY_OF_YEAR[days]; - date_time.mon = TABLE_MONTH_OF_YEAR[days]; - } - - ts_t day_secs = static_cast(detail::floor_mod(secs, SEC_PER_DAY)); - date_time.hour = static_cast(day_secs / SEC_PER_HOUR); - ts_t min_secs = static_cast(day_secs - date_time.hour * SEC_PER_HOUR); - date_time.min = static_cast(min_secs / SEC_PER_MIN); - date_time.sec = static_cast(min_secs - date_time.min * SEC_PER_MIN); -# ifdef TIME_SHIELD_CPP17 - if TIME_SHIELD_IF_CONSTEXPR (std::is_floating_point::value) { - date_time.ms = static_cast(std::round(std::fmod(static_cast(ts), static_cast(MS_PER_SEC)))); - } else date_time.ms = 0; -# else - if (std::is_floating_point::value) { - date_time.ms = static_cast(std::round(std::fmod(static_cast(ts), static_cast(MS_PER_SEC)))); - } else date_time.ms = 0; -# endif - return date_time; - } - - } // namespace legacy - - /// \ingroup time_structures - /// \brief Converts a timestamp to a date-time structure. - /// - /// This function converts a timestamp (usually an integer representing seconds since epoch) - /// to a custom date-time structure. The default type for the timestamp is int64_t. - /// \note Inspired by the algorithm described in: - /// https://www.benjoffe.com/fast-date-64 - /// This implementation is written from scratch (no code copied). - /// - /// \tparam T1 The date-time structure type to be returned. - /// \tparam T2 The type of the timestamp (default is int64_t). - /// \param ts The timestamp to be converted. - /// \return A date-time structure of type T1. - template - T1 to_date_time(T2 ts) { - const int64_t whole_sec = static_cast(ts); - const detail::DaySplit split = detail::split_unix_day(whole_sec); - const detail::FastDate date = detail::fast_date_from_days(split.days); - - T1 date_time{}; - date_time.year = static_cast(date.year); - date_time.mon = static_cast(date.month); - date_time.day = static_cast(date.day); - - const ts_t day_secs = static_cast(split.sec_of_day); - date_time.hour = static_cast(day_secs / SEC_PER_HOUR); - const ts_t min_secs = static_cast(day_secs - date_time.hour * SEC_PER_HOUR); - date_time.min = static_cast(min_secs / SEC_PER_MIN); - date_time.sec = static_cast(min_secs - date_time.min * SEC_PER_MIN); -# ifdef TIME_SHIELD_CPP17 - if TIME_SHIELD_IF_CONSTEXPR (std::is_floating_point::value) { - date_time.ms = static_cast(std::round(std::fmod(static_cast(ts), static_cast(MS_PER_SEC)))); - } else date_time.ms = 0; -# else - if (std::is_floating_point::value) { - date_time.ms = static_cast(std::round(std::fmod(static_cast(ts), static_cast(MS_PER_SEC)))); - } else date_time.ms = 0; -# endif - return date_time; - } - - /// \ingroup time_structures - /// \brief Converts a timestamp in milliseconds to a date-time structure with milliseconds. - /// \tparam T The type of the date-time structure to return. - /// \param ts The timestamp in milliseconds to convert. - /// \return T A date-time structure with the corresponding date and time components. - template - inline T to_date_time_ms(ts_ms_t ts) { - const ts_t sec = ms_to_sec(ts); - T date_time = to_date_time(sec); - date_time.ms = ms_of_ts(ts); // Extract and set the ms component - return date_time; - } - - namespace legacy { - - /// \brief Converts a date and time to a timestamp. - /// - /// This function converts a given date and time to a timestamp, which is the number - /// of seconds since the Unix epoch (January 1, 1970). - /// - /// If the `day` is ≥ 1970 and `year` ≤ 31, parameters are assumed to be in DD-MM-YYYY order - /// and are automatically reordered. - /// - /// \tparam T1 The type of the year parameter (default is int64_t). - /// \tparam T2 The type of the other date and time parameters (default is int). - /// \param year The year value. - /// \param month The month value. - /// \param day The day value. - /// \param hour The hour value (default is 0). - /// \param min The minute value (default is 0). - /// \param sec The second value (default is 0). - /// \return Timestamp representing the given date and time. - /// \throws std::invalid_argument if the date-time combination is invalid. - /// - /// \par Aliases: - /// Following function names are provided as aliases: - /// - `ts(...)` - /// - `get_ts(...)` - /// - `get_timestamp(...)` - /// - `timestamp(...)` - /// - `to_ts(...)` - /// - /// These aliases are macro-generated and behave identically to `to_timestamp`. - /// - /// \sa ts() \sa get_ts() \sa get_timestamp() \sa timestamp() \sa to_ts() - template - TIME_SHIELD_CONSTEXPR inline ts_t to_timestamp( - T1 year, - T2 month, - T2 day, - T2 hour = 0, - T2 min = 0, - T2 sec = 0) { - - if (day >= UNIX_EPOCH && year <= 31) { - return to_timestamp((T1)day, month, (T2)year, hour, min, sec); - } - if (!is_valid_date_time(year, month, day, hour, min, sec)) { - throw std::invalid_argument("Invalid date-time combination"); - } - - int64_t secs = 0; - int64_t years = (static_cast(MAX_YEAR) - year); - - const int64_t n_400_years = years / 400LL; - secs += n_400_years * SEC_PER_400_YEARS; - years -= n_400_years * 400LL; - - const int64_t n_100_years = years / 100LL; - secs += n_100_years * SEC_PER_100_YEARS; - years -= n_100_years * 100LL; - - const int64_t n_4_years = years / 4LL; - secs += n_4_years * SEC_PER_4_YEARS; - years -= n_4_years * 4LL; - - secs += years * SEC_PER_YEAR; - - // 9223372029693630000 reaches year 292277024400 from the 2000 epoch. - // This value overflows the n_400_years * SEC_PER_400_YEARS calculation. - // The supported bound is reduced to 9223371890843040000. - constexpr int64_t BIAS_292277022000 = 9223371890843040000LL; - constexpr int64_t BIAS_2000 = 946684800LL; - - secs = BIAS_292277022000 - secs; - secs += BIAS_2000; - - if (month == 1 && day == 1 && - hour == 0 && min == 0 && - sec == 0) { - return secs; - } - - constexpr int lmos[] = {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335}; - constexpr int mos[] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334}; - - secs += (is_leap_year_date(year) ? (lmos[month - 1] + day - 1) : (mos[month - 1] + day - 1)) * SEC_PER_DAY; - secs += SEC_PER_HOUR * hour + SEC_PER_MIN * min + sec; - return secs; - } - - /// \brief Converts a date and time to a timestamp without validation. - /// - /// This function converts a given date and time to a timestamp, which is the number - /// of seconds since the Unix epoch (January 1, 1970). - /// - /// If the `day` is ≥ 1970 and `year` ≤ 31, parameters are assumed to be in DD-MM-YYYY order - /// and are automatically reordered. - /// - /// \tparam T1 The type of the year parameter (default is int64_t). - /// \tparam T2 The type of the other date and time parameters (default is int). - /// \param year The year value. - /// \param month The month value. - /// \param day The day value. - /// \param hour The hour value (default is 0). - /// \param min The minute value (default is 0). - /// \param sec The second value (default is 0). - /// \return Timestamp representing the given date and time. - template - TIME_SHIELD_CONSTEXPR inline ts_t to_timestamp_unchecked( - T1 year, - T2 month, - T2 day, - T2 hour = 0, - T2 min = 0, - T2 sec = 0) noexcept { - - if (day >= UNIX_EPOCH && year <= 31) { - return to_timestamp_unchecked((T1)day, month, (T2)year, hour, min, sec); - } - - int64_t secs = 0; - int64_t years = (static_cast(MAX_YEAR) - year); - - const int64_t n_400_years = years / 400LL; - secs += n_400_years * SEC_PER_400_YEARS; - years -= n_400_years * 400LL; - - const int64_t n_100_years = years / 100LL; - secs += n_100_years * SEC_PER_100_YEARS; - years -= n_100_years * 100LL; - - const int64_t n_4_years = years / 4LL; - secs += n_4_years * SEC_PER_4_YEARS; - years -= n_4_years * 4LL; - - secs += years * SEC_PER_YEAR; - - // 9223372029693630000 reaches year 292277024400 from the 2000 epoch. - // This value overflows the n_400_years * SEC_PER_400_YEARS calculation. - // The supported bound is reduced to 9223371890843040000. - constexpr int64_t BIAS_292277022000 = 9223371890843040000LL; - constexpr int64_t BIAS_2000 = 946684800LL; - - secs = BIAS_292277022000 - secs; - secs += BIAS_2000; - - if (month == 1 && day == 1 && - hour == 0 && min == 0 && - sec == 0) { - return secs; - } - - constexpr int lmos[] = {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335}; - constexpr int mos[] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334}; - - secs += (is_leap_year_date(year) ? (lmos[month - 1] + day - 1) : (mos[month - 1] + day - 1)) * SEC_PER_DAY; - secs += SEC_PER_HOUR * hour + SEC_PER_MIN * min + sec; - return secs; - } - - } // namespace legacy - - /// \brief Converts a date and time to a timestamp without validation. - /// - /// This function converts a given date and time to a timestamp, which is the number - /// of seconds since the Unix epoch (January 1, 1970). - /// - /// If the `day` is ≥ 1970 and `year` ≤ 31, parameters are assumed to be in DD-MM-YYYY order - /// and are automatically reordered. - /// - /// \note Inspired by the algorithm described in: - /// https://www.benjoffe.com/fast-date-64 - /// This implementation is written from scratch (no code copied). - /// - /// \tparam T1 The type of the year parameter (default is int64_t). - /// \tparam T2 The type of the other date and time parameters (default is int). - /// \param year The year value. - /// \param month The month value. - /// \param day The day value. - /// \param hour The hour value (default is 0). - /// \param min The minute value (default is 0). - /// \param sec The second value (default is 0). - /// \return Timestamp representing the given date and time. - template - TIME_SHIELD_CONSTEXPR inline ts_t to_timestamp_unchecked( - T1 year, - T2 month, - T2 day, - T2 hour = 0, - T2 min = 0, - T2 sec = 0) noexcept { - - if (day >= UNIX_EPOCH && year <= 31) { - return to_timestamp_unchecked((T1)day, month, (T2)year, hour, min, sec); - } - - const dse_t unix_day = date_to_unix_day(year, month, day); - return static_cast(unix_day * SEC_PER_DAY - + SEC_PER_HOUR * static_cast(hour) - + SEC_PER_MIN * static_cast(min) - + static_cast(sec)); - } - - /// \brief Converts a date and time to a timestamp. - /// - /// This function converts a given date and time to a timestamp, which is the number - /// of seconds since the Unix epoch (January 1, 1970). - /// - /// If the `day` is ≥ 1970 and `year` ≤ 31, parameters are assumed to be in DD-MM-YYYY order - /// and are automatically reordered. - /// - /// \note Inspired by the algorithm described in: - /// https://www.benjoffe.com/fast-date-64 - /// This implementation is written from scratch (no code copied). - /// - /// \tparam T1 The type of the year parameter (default is int64_t). - /// \tparam T2 The type of the other date and time parameters (default is int). - /// \param year The year value. - /// \param month The month value. - /// \param day The day value. - /// \param hour The hour value (default is 0). - /// \param min The minute value (default is 0). - /// \param sec The second value (default is 0). - /// \return Timestamp representing the given date and time. - /// \throws std::invalid_argument if the date-time combination is invalid. - /// - /// \par Aliases: - /// Following function names are provided as aliases: - /// - `ts(...)` - /// - `get_ts(...)` - /// - `get_timestamp(...)` - /// - `timestamp(...)` - /// - `to_ts(...)` - /// - /// These aliases are macro-generated and behave identically to `to_timestamp`. - /// - /// \sa ts() \sa get_ts() \sa get_timestamp() \sa timestamp() \sa to_ts() - template - TIME_SHIELD_CONSTEXPR inline ts_t to_timestamp( - T1 year, - T2 month, - T2 day, - T2 hour = 0, - T2 min = 0, - T2 sec = 0) { - - if (day >= UNIX_EPOCH && year <= 31) { - return to_timestamp((T1)day, month, (T2)year, hour, min, sec); - } - if (!is_valid_date_time(year, month, day, hour, min, sec)) { - throw std::invalid_argument("Invalid date-time combination"); - } - - return to_timestamp_unchecked(year, month, day, hour, min, sec); - } - - /// \ingroup time_structures - /// \brief Converts a date-time structure to a timestamp. - /// - /// This function converts a given date and time to a timestamp, which is the number - /// of seconds since the Unix epoch (January 1, 1970). - /// - /// \tparam T The type of the date-time structure. - /// \param date_time The date-time structure. - /// \return Timestamp representing the given date and time. - /// \throws std::invalid_argument if the date-time combination is invalid. - template - TIME_SHIELD_CONSTEXPR inline ts_t dt_to_timestamp( - const T& date_time) { - return to_timestamp( - date_time.year, - date_time.mon, - date_time.day, - date_time.hour, - date_time.min, - date_time.sec - ); - } - - /// \ingroup time_structures - /// \brief Converts a std::tm structure to a timestamp. - /// - /// This function converts a given std::tm structure to a timestamp, which is the number - /// of seconds since the Unix epoch (January 1, 1970). - /// - /// \param timeinfo Pointer to a std::tm structure containing the date and time information. - /// \return Timestamp representing the given date and time. - TIME_SHIELD_CONSTEXPR inline ts_t tm_to_timestamp( - const std::tm *timeinfo) { - return to_timestamp( - static_cast(timeinfo->tm_year + 1900), - static_cast(timeinfo->tm_mon + 1), - static_cast(timeinfo->tm_mday), - static_cast(timeinfo->tm_hour), - static_cast(timeinfo->tm_min), - static_cast(timeinfo->tm_sec) - ); - } - - /// \ingroup time_structures - /// \brief Converts a date-time structure to a timestamp in milliseconds. - /// - /// This function converts a given date and time to a timestamp in milliseconds, - /// which is the number of milliseconds since the Unix epoch (January 1, 1970). - /// - /// \tparam T1 The type of the year parameter (default is year_t). - /// \tparam T2 The type of the month, day, hour, minute, and second parameters (default is int). - /// \param year The year value. - /// \param month The month value. - /// \param day The day value. - /// \param hour The hour value (default is 0). - /// \param min The minute value (default is 0). - /// \param sec The second value (default is 0). - /// \param ms The millisecond value (default is 0). - /// \return Timestamp in milliseconds representing the given date and time. - /// \throws std::invalid_argument if the date-time combination is invalid. - template - TIME_SHIELD_CONSTEXPR inline ts_ms_t to_timestamp_ms( - T1 year, - T2 month, - T2 day, - T2 hour = 0, - T2 min = 0, - T2 sec = 0, - T2 ms = 0) { - int64_t sec_value = static_cast(to_timestamp(year, month, day, hour, min, sec)); - int64_t ms_value = static_cast(ms); - sec_value += detail::floor_div(ms_value, static_cast(MS_PER_SEC)); - ms_value = detail::floor_mod(ms_value, static_cast(MS_PER_SEC)); - if ((sec_value > 0 && - sec_value > ((std::numeric_limits::max)() - ms_value) / MS_PER_SEC) || - (sec_value < 0 && - sec_value < (std::numeric_limits::min)() / MS_PER_SEC)) { - return ERROR_TIMESTAMP; - } - return static_cast(sec_value * MS_PER_SEC + ms_value); - } - - /// \ingroup time_structures - /// \brief Converts a date-time structure to a timestamp in milliseconds. - /// - /// This function converts a given date and time structure to a timestamp in milliseconds, - /// which is the number of milliseconds since the Unix epoch (January 1, 1970). - /// - /// \tparam T The type of the date-time structure. - /// \param date_time The date-time structure containing year, month, day, hour, minute, second, and millisecond fields. - /// \return Timestamp in milliseconds representing the given date and time. - /// \throws std::invalid_argument if the date-time combination is invalid. - template - TIME_SHIELD_CONSTEXPR inline ts_ms_t dt_to_timestamp_ms( - const T& date_time) { - int64_t sec_value = static_cast(dt_to_timestamp(date_time)); - int64_t ms_value = static_cast(date_time.ms); - sec_value += detail::floor_div(ms_value, static_cast(MS_PER_SEC)); - ms_value = detail::floor_mod(ms_value, static_cast(MS_PER_SEC)); - if ((sec_value > 0 && - sec_value > ((std::numeric_limits::max)() - ms_value) / MS_PER_SEC) || - (sec_value < 0 && - sec_value < (std::numeric_limits::min)() / MS_PER_SEC)) { - return ERROR_TIMESTAMP; - } - return static_cast(sec_value * MS_PER_SEC + ms_value); - } - - /// \ingroup time_structures - /// \brief Converts a std::tm structure to a timestamp in milliseconds. - /// - /// This function converts a given std::tm structure to a timestamp in milliseconds, - /// which is the number of milliseconds since the Unix epoch (January 1, 1970). - /// - /// \param timeinfo Pointer to a std::tm structure containing the date and time information. - /// \return Timestamp in milliseconds representing the given date and time. - TIME_SHIELD_CONSTEXPR inline ts_t tm_to_timestamp_ms( - const std::tm *timeinfo) { - return sec_to_ms(tm_to_timestamp(timeinfo)); - } - - /// \brief Converts a date and time to a floating-point timestamp. - /// - /// This function converts a given date and time to a floating-point timestamp, - /// which is the number of seconds (with fractional milliseconds) since the Unix epoch - /// (January 1, 1970). - /// - /// \tparam T1 The type of the year parameter (default is year_t). - /// \tparam T2 The type of the month, day, hour, minute, and second parameters (default is int). - /// \tparam T3 The type of the millisecond parameter (default is int). - /// \param year The year value. - /// \param month The month value. - /// \param day The day value. - /// \param hour The hour value (default is 0). - /// \param min The minute value (default is 0). - /// \param sec The second value (default is 0). - /// \param ms The millisecond value (default is 0). - /// \return Floating-point timestamp representing the given date and time. - /// \throws std::invalid_argument if the date-time combination is invalid. - template - TIME_SHIELD_CONSTEXPR inline fts_t to_ftimestamp( - T1 year, - T2 month, - T2 day, - T2 hour = 0, - T2 min = 0, - T2 sec = 0, - T3 ms = 0) { - int64_t sec_value = static_cast(to_timestamp(year, month, day, hour, min, sec)); - int64_t ms_value = static_cast(ms); - sec_value += detail::floor_div(ms_value, static_cast(MS_PER_SEC)); - ms_value = detail::floor_mod(ms_value, static_cast(MS_PER_SEC)); - return static_cast(sec_value) + - static_cast(ms_value) / static_cast(MS_PER_SEC); - } - - /// \ingroup time_structures - /// \brief Converts a date-time structure to a floating-point timestamp. - /// - /// This function converts a given date and time structure to a floating-point timestamp, - /// which is the number of seconds (with fractional milliseconds) since the Unix epoch - /// (January 1, 1970). - /// - /// \tparam T The type of the date-time structure. - /// \param date_time The date-time structure containing year, month, day, hour, minute, second, and millisecond fields. - /// \return Floating-point timestamp representing the given date and time. - /// \throws std::invalid_argument if the date-time combination is invalid. - template - TIME_SHIELD_CONSTEXPR inline fts_t dt_to_ftimestamp( - const T& date_time) { - int64_t sec_value = static_cast(to_timestamp(date_time)); - int64_t ms_value = static_cast(date_time.ms); - sec_value += detail::floor_div(ms_value, static_cast(MS_PER_SEC)); - ms_value = detail::floor_mod(ms_value, static_cast(MS_PER_SEC)); - return static_cast(sec_value) + - static_cast(ms_value) / static_cast(MS_PER_SEC); - } - - /// \brief Converts a std::tm structure to a floating-point timestamp. - /// - /// This function converts a given std::tm structure to a floating-point timestamp, - /// which is the number of seconds (with fractional milliseconds) since the Unix epoch - /// (January 1, 1970). - /// - /// \param timeinfo Pointer to the std::tm structure containing the date and time. - /// \return Floating-point timestamp representing the given date and time. - /// \throws std::invalid_argument if the date-time combination is invalid. - TIME_SHIELD_CONSTEXPR inline fts_t tm_to_ftimestamp( - const std::tm* timeinfo) { - return static_cast(tm_to_timestamp(timeinfo)); - } - - /// \brief Get the start of the day timestamp. - /// - /// This function returns the timestamp at the start of the day. - /// Sets the hours, minutes, and seconds to zero. - /// - /// \param ts Timestamp. - /// \return Start of the day timestamp. - TIME_SHIELD_CONSTEXPR inline ts_t start_of_day(ts_t ts = time_shield::ts()) noexcept { - return ts - detail::floor_mod(ts, SEC_PER_DAY); - } - - /// \brief Get timestamp of the start of the previous day. - /// - /// This function returns the timestamp at the start of the previous day. - /// - /// \param ts Timestamp of the current day. - /// \param days Number of days to go back (default is 1). - /// \return Timestamp of the start of the previous day. - template - TIME_SHIELD_CONSTEXPR ts_t start_of_prev_day(ts_t ts = time_shield::ts(), T days = 1) noexcept { - return ts - detail::floor_mod(ts, SEC_PER_DAY) - SEC_PER_DAY * days; - } - - /// \brief Get the start of the day timestamp in seconds. - /// - /// This function returns the timestamp at the start of the day in seconds. - /// Sets the hours, minutes, and seconds to zero. - /// - /// \param ts_ms Timestamp in milliseconds. - /// \return Start of the day timestamp in seconds. - TIME_SHIELD_CONSTEXPR inline ts_t start_of_day_sec(ts_ms_t ts_ms = time_shield::ts_ms()) noexcept { - return start_of_day(ms_to_sec(ts_ms)); - } - - /// \brief Get the start of the day timestamp in milliseconds. - /// - /// This function returns the timestamp at the start of the day in milliseconds. - /// Sets the hours, minutes, seconds, and milliseconds to zero. - /// - /// \param ts_ms Timestamp in milliseconds. - /// \return Start of the day timestamp in milliseconds. - TIME_SHIELD_CONSTEXPR inline ts_ms_t start_of_day_ms(ts_ms_t ts_ms = time_shield::ts_ms()) noexcept { - return ts_ms - detail::floor_mod(ts_ms, MS_PER_DAY); - } - - /// \brief Get the timestamp of the start of the day after a specified number of days. - /// - /// Calculates the timestamp for the beginning of the day after a specified number of days - /// relative to the given timestamp. - /// - /// \param ts The current timestamp in seconds. - /// \param days The number of days after the current day (default is 1). - /// \return The timestamp in seconds representing the beginning of the specified future day. - template - TIME_SHIELD_CONSTEXPR ts_t start_of_next_day(ts_t ts, T days = 1) noexcept { - return start_of_day(ts) + days * SEC_PER_DAY; - } - - /// \brief Get the timestamp of the start of the day after a specified number of days. - /// - /// Calculates the timestamp for the beginning of the day after a specified number of days - /// relative to the given timestamp in milliseconds. - /// - /// \param ts_ms The current timestamp in milliseconds. - /// \param days The number of days after the current day (default is 1). - /// \return The timestamp in milliseconds representing the beginning of the specified future day. - template - TIME_SHIELD_CONSTEXPR ts_ms_t start_of_next_day_ms(ts_ms_t ts_ms, T days = 1) noexcept { - return start_of_day_ms(ts_ms) + days * MS_PER_DAY; - } - - /// \brief Calculate the timestamp for a specified number of days in the future. - /// - /// Adds the given number of days to the provided timestamp, without adjusting to the start of the day. - /// - /// \param ts The current timestamp in seconds. - /// \param days The number of days to add to the current timestamp (default is 1). - /// \return The timestamp in seconds after adding the specified number of days. - template - TIME_SHIELD_CONSTEXPR ts_t next_day(ts_t ts, T days = 1) noexcept { - return ts + days * SEC_PER_DAY; - } - - /// \brief Calculate the timestamp for a specified number of days in the future (milliseconds). - /// - /// Adds the given number of days to the provided timestamp, without adjusting to the start of the day. - /// - /// \param ts_ms The current timestamp in milliseconds. - /// \param days The number of days to add to the current timestamp (default is 1). - /// \return The timestamp in milliseconds after adding the specified number of days. - template - TIME_SHIELD_CONSTEXPR ts_ms_t next_day_ms(ts_ms_t ts_ms, T days = 1) noexcept { - return ts_ms + days * MS_PER_DAY; - } - - /// \brief Get the timestamp at the end of the day. - /// - /// This function sets the hour to 23, minute to 59, and second to 59. - /// - /// \param ts Timestamp. - /// \return Timestamp at the end of the day. - TIME_SHIELD_CONSTEXPR inline ts_t end_of_day(ts_t ts = time_shield::ts()) noexcept { - return ts - detail::floor_mod(ts, SEC_PER_DAY) + SEC_PER_DAY - 1; - } - - /// \brief Get the timestamp at the end of the day in seconds. - /// - /// This function sets the hour to 23, minute to 59, and second to 59. - /// - /// \param ts_ms Timestamp in milliseconds. - /// \return Timestamp at the end of the day in seconds. - TIME_SHIELD_CONSTEXPR inline ts_t end_of_day_sec(ts_ms_t ts_ms = time_shield::ts_ms()) noexcept { - return end_of_day(ms_to_sec(ts_ms)); - } - - /// \brief Get the timestamp at the end of the day in milliseconds. - /// - /// This function sets the hour to 23, minute to 59, second to 59, and millisecond to 999. - /// - /// \param ts_ms Timestamp in milliseconds. - /// \return Timestamp at the end of the day in milliseconds. - TIME_SHIELD_CONSTEXPR inline ts_ms_t end_of_day_ms(ts_ms_t ts_ms = time_shield::ts_ms()) noexcept { - return ts_ms - detail::floor_mod(ts_ms, MS_PER_DAY) + MS_PER_DAY - 1; - } - - /// \brief Get the timestamp of the start of the year. - /// \param year Year. - /// \return Timestamp at 00:00:00 of the first day of the year. - template - TIME_SHIELD_CONSTEXPR inline ts_t start_of_year_date(T year) { - const ts_t year_ts = to_timestamp(year, 1, 1); - - return start_of_day(year_ts); - } - - /// \brief Get the timestamp in milliseconds of the start of the year. - /// - /// This function returns the timestamp at the start of the specified year in milliseconds. - /// - /// \param year Year. - /// \return Timestamp of the start of the year in milliseconds. - /// \throws std::invalid_argument if the date-time combination is invalid. - template - TIME_SHIELD_CONSTEXPR inline ts_ms_t start_of_year_date_ms(T year) { - return sec_to_ms(start_of_year_date(year)); - } - - /// \brief Get the start of the year timestamp. - /// - /// This function resets the days, months, hours, minutes, and seconds of the given timestamp - /// to the beginning of the year. - /// - /// \param ts Timestamp. - /// \return Start of the year timestamp. - TIME_SHIELD_CONSTEXPR inline ts_t start_of_year(ts_t ts) noexcept { - constexpr ts_t BIAS_2100 = 4102444800; - if (ts >= 0 && ts < BIAS_2100) { - constexpr ts_t SEC_PER_YEAR_X2 = SEC_PER_YEAR * 2; - ts_t year_start_ts = detail::floor_mod(ts, SEC_PER_4_YEARS); - if (year_start_ts < SEC_PER_YEAR) { - return ts - year_start_ts; - } else if (year_start_ts < SEC_PER_YEAR_X2) { - return ts + SEC_PER_YEAR - year_start_ts; - } else if (year_start_ts < (SEC_PER_YEAR_X2 + SEC_PER_LEAP_YEAR)) { - return ts + SEC_PER_YEAR_X2 - year_start_ts; - } - return ts + (SEC_PER_YEAR_X2 + SEC_PER_LEAP_YEAR) - year_start_ts; - } - - constexpr ts_t BIAS_2000 = 946684800; - ts_t secs = ts - BIAS_2000; - - ts_t offset_y400 = detail::floor_mod(secs, SEC_PER_400_YEARS); - ts_t start_ts = secs - offset_y400 + BIAS_2000; - secs = offset_y400; - - if (secs >= SEC_PER_FIRST_100_YEARS) { - secs -= SEC_PER_FIRST_100_YEARS; - start_ts += SEC_PER_FIRST_100_YEARS; - while (secs >= SEC_PER_100_YEARS) { - secs -= SEC_PER_100_YEARS; - start_ts += SEC_PER_100_YEARS; - } - - constexpr ts_t SEC_PER_4_YEARS_V2 = 4 * SEC_PER_YEAR; - if (secs >= SEC_PER_4_YEARS_V2) { - secs -= SEC_PER_4_YEARS_V2; - start_ts += SEC_PER_4_YEARS_V2; - } else { - start_ts += secs - detail::floor_mod(secs, SEC_PER_YEAR); - return start_ts; - } - } - - ts_t offset_4y = detail::floor_mod(secs, SEC_PER_4_YEARS); - start_ts += secs - offset_4y; - secs = offset_4y; - - if (secs >= SEC_PER_LEAP_YEAR) { - secs -= SEC_PER_LEAP_YEAR; - start_ts += SEC_PER_LEAP_YEAR; - start_ts += secs - detail::floor_mod(secs, SEC_PER_YEAR); - return start_ts; - } - - start_ts += secs - detail::floor_mod(secs, SEC_PER_YEAR); - return start_ts; - } - - /// \brief Get the timestamp at the start of the year in milliseconds. - /// \param ts_ms Timestamp in milliseconds. - /// \return Timestamp at 00:00:00.000 of the first day of the year. - TIME_SHIELD_CONSTEXPR inline ts_ms_t start_of_year_ms(ts_ms_t ts_ms = time_shield::ts_ms()) { - return sec_to_ms(start_of_year(ms_to_sec(ts_ms))); - } - - /// \brief Get the end-of-year timestamp. - /// - /// This function finds the last timestamp of the current year. - /// - /// \param ts Timestamp. - /// \return End-of-year timestamp. - TIME_SHIELD_CONSTEXPR inline ts_t end_of_year(ts_t ts = time_shield::ts()) { - const ts_t year_start = start_of_year(ts); - const ts_t year_days = static_cast(num_days_in_year_ts(ts)); - return year_start + year_days * SEC_PER_DAY - 1; - } - - /// \brief Get the timestamp in milliseconds of the end of the year. - /// - /// This function finds the last millisecond of the current year in milliseconds. - /// - /// \param ts_ms Timestamp in milliseconds. - /// \return End-of-year timestamp in milliseconds. - template - TIME_SHIELD_CONSTEXPR inline ts_ms_t end_of_year_ms(ts_ms_t ts_ms = time_shield::ts_ms()) { - return sec_to_ms(end_of_year(ms_to_sec(ts_ms))) + (MS_PER_SEC - 1); - } - - /// \brief Get the day of the year. - /// - /// This function returns the day of the year for the specified timestamp. - /// - /// \param ts Timestamp. - /// \return Day of the year. - template - inline T day_of_year(ts_t ts = time_shield::ts()) { - return static_cast(((ts - start_of_year(ts)) / SEC_PER_DAY) + 1); - } - - /// \brief Get the month of the year. - /// - /// This function returns the month of the year for the specified timestamp. - /// - /// \param ts Timestamp. - /// \return Month of the year. - template - TIME_SHIELD_CONSTEXPR inline T month_of_year(ts_t ts) noexcept { - constexpr int JAN_AND_FEB_DAY_LEAP_YEAR = 60; - constexpr int TABLE_MONTH_OF_YEAR[] = { - 0, - 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // January (31 days) - 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, // February (28 days) - 3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3, // March (31 days) - 4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4, // April (30 days) - 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5, - 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, - 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, - 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, - 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, - 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, - 11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11, - 12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12, - }; - const size_t dy = day_of_year(ts); - return static_cast((is_leap_year(ts) && dy >= JAN_AND_FEB_DAY_LEAP_YEAR) ? TABLE_MONTH_OF_YEAR[dy - 1] : TABLE_MONTH_OF_YEAR[dy]); - } - - /// \brief Get the day of the month. - /// - /// This function returns the day of the month for the specified timestamp. - /// - /// \param ts Timestamp. - /// \return Day of the month. - template - TIME_SHIELD_CONSTEXPR inline T day_of_month(ts_t ts = time_shield::ts()) { - constexpr int JAN_AND_FEB_DAY_LEAP_YEAR = 60; - // Month numbers for a common year. - constexpr int TABLE_DAY_OF_YEAR[] = { - 0, - 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31, // January (31 days) - 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28, // February (28 days) - 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31, // March (31 days) - 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30, // April (30 days) - 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31, - 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30, - 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31, - 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31, - 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30, - 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31, - 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30, - 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31, - }; - const size_t dy = day_of_year(ts); - if(is_leap_year(ts)) { - if(dy == JAN_AND_FEB_DAY_LEAP_YEAR) return TABLE_DAY_OF_YEAR[dy - 1] + 1; - if(dy > JAN_AND_FEB_DAY_LEAP_YEAR) return TABLE_DAY_OF_YEAR[dy - 1]; - } - return TABLE_DAY_OF_YEAR[dy]; - } - - /// \brief Get the number of days in a month. - /// - /// This function calculates and returns the number of days in the specified month and year. - /// - /// \param year Year as an integer. - /// \param month Month as an integer. - /// \return The number of days in the given month and year. - template - TIME_SHIELD_CONSTEXPR T1 num_days_in_month(T2 year, T3 month) noexcept { - constexpr T1 num_days[13] = {0,31,30,31,30,31,30,31,31,30,31,30,31}; - return (month > MONTHS_PER_YEAR || month < 0) - ? static_cast(0) - : (month == FEB ? static_cast(is_leap_year_date(year) ? 29 : 28) : num_days[month]); - } - - /// \brief Get the number of days in the month of the given timestamp. - /// - /// This function calculates and returns the number of days in the month of the specified timestamp. - /// - /// \param ts The timestamp to extract month and year from. - /// \return The number of days in the month of the given timestamp. - template - TIME_SHIELD_CONSTEXPR T1 num_days_in_month_ts(ts_t ts = time_shield::ts()) noexcept { - constexpr T1 num_days[13] = {0,31,28,31,30,31,30,31,31,30,31,30,31}; - const int month = month_of_year(ts); - if (month == FEB) { - return is_leap_year(ts) ? 29 : 28; - } - return num_days[month]; - } - - /// \brief Get the second of the week day from a timestamp. - /// \param ts Timestamp. - /// \return Weekday (SUN = 0, MON = 1, ... SAT = 6). - template - TIME_SHIELD_CONSTEXPR T weekday_of_ts(ts_t ts) noexcept { - const ts_t days = detail::floor_div(ts, SEC_PER_DAY); - return static_cast(detail::floor_mod(days + THU, DAYS_PER_WEEK)); - } - - /// \brief Get the weekday from a timestamp in milliseconds. - /// \param ts_ms Timestamp in milliseconds. - /// \return Weekday (SUN = 0, MON = 1, ... SAT = 6). - template - TIME_SHIELD_CONSTEXPR T weekday_of_ts_ms(ts_ms_t ts_ms) { - return weekday_of_ts(ms_to_sec(ts_ms)); - } - - /// \brief Get the timestamp at the start of the current month. - /// - /// This function returns the timestamp at the start of the current month, - /// setting the day to the first day of the month and the time to 00:00:00. - /// - /// \param ts Timestamp (default is current timestamp) - /// \return Timestamp at the start of the current month - TIME_SHIELD_CONSTEXPR inline ts_t start_of_month(ts_t ts = time_shield::ts()) { - return start_of_day(ts) - (day_of_month(ts) - 1) * SEC_PER_DAY; - } - - /// \brief Get the last timestamp of the current month. - /// - /// This function returns the last timestamp of the current month, - /// setting the day to the last day of the month and the time to 23:59:59. - /// - /// \param ts Timestamp (default is current timestamp) - /// \return Last timestamp of the current month - TIME_SHIELD_CONSTEXPR inline ts_t end_of_month(ts_t ts = time_shield::ts()) { - return end_of_day(ts) + (num_days_in_month_ts(ts) - day_of_month(ts)) * SEC_PER_DAY; - } - - /// \brief Get the timestamp of the last Sunday of the current month. - /// - /// This function returns the timestamp of the last Sunday of the current month, - /// setting the time to 00:00:00. - /// - /// \param ts Timestamp (default is current timestamp) - /// \return Timestamp of the last Sunday of the current month at 00:00:00 - TIME_SHIELD_CONSTEXPR inline ts_t last_sunday_of_month(ts_t ts = time_shield::ts()) { - const ts_t month_end = end_of_month(ts); - return start_of_day(month_end) - weekday_of_ts(month_end) * SEC_PER_DAY; - } - - /// \brief Get the day of the last Sunday of the given month and year. - /// - /// This function returns the day of the last Sunday of the specified month and year. - /// - /// \param year Year - /// \param month Month (1 = January, 12 = December) - /// \return Day of the last Sunday of the given month and year - template - TIME_SHIELD_CONSTEXPR inline T1 last_sunday_month_day(T2 year, T3 month) { - const T1 days = num_days_in_month(year, month); - return days - day_of_week_date(year, month, days); - } - - /// \brief Get the timestamp of the beginning of the week. - /// - /// This function finds the timestamp of the beginning of the week, - /// which corresponds to the start of Sunday. - /// - /// \param ts Timestamp (default: current timestamp). - /// \return Returns the timestamp of the beginning of the week. - TIME_SHIELD_CONSTEXPR inline ts_t start_of_week(ts_t ts = time_shield::ts()) { - return start_of_day(ts) - weekday_of_ts(ts) * SEC_PER_DAY; - } - - /// \brief Get the timestamp of the end of the week. - /// - /// This function finds the timestamp of the end of the week, - /// which corresponds to the end of Saturday. - /// - /// \param ts Timestamp (default: current timestamp). - /// \return Returns the timestamp of the end of the week. - TIME_SHIELD_CONSTEXPR inline ts_t end_of_week(ts_t ts = time_shield::ts()) { - return start_of_day(ts) + (DAYS_PER_WEEK - weekday_of_ts(ts)) * SEC_PER_DAY - 1; - } - - /// \brief Get the timestamp of the start of Saturday. - /// - /// This function finds the timestamp of the beginning of the day on Saturday, - /// which corresponds to the start of Saturday. - /// - /// \param ts Timestamp (default: current timestamp). - /// \return Returns the timestamp of the start of Saturday. - TIME_SHIELD_CONSTEXPR inline ts_t start_of_saturday(ts_t ts = time_shield::ts()) { - return start_of_day(ts) + (SAT - weekday_of_ts(ts)) * SEC_PER_DAY; - } - - - /// \brief Get the timestamp at the start of the hour. - /// - /// This function sets the minute and second to zero. - /// - /// \param ts Timestamp (default: current timestamp). - /// \return Timestamp at the start of the hour. - TIME_SHIELD_CONSTEXPR inline ts_t start_of_hour(ts_t ts = time_shield::ts()) noexcept { - return ts - detail::floor_mod(ts, SEC_PER_HOUR); - } - - /// \brief Get the timestamp at the start of the hour. - /// - /// This function sets the minute and second to zero. - /// - /// \param ts_ms Timestamp in milliseconds (default: current timestamp in milliseconds). - /// \return Timestamp at the start of the hour in seconds. - TIME_SHIELD_CONSTEXPR inline ts_t start_of_hour_sec(ts_ms_t ts_ms = time_shield::ts_ms()) noexcept { - return start_of_hour(ms_to_sec(ts_ms)); - } - - /// \brief Get the timestamp at the start of the hour. - /// This function sets the minute and second to zero. - /// \param ts_ms Timestamp in milliseconds (default: current timestamp in milliseconds). - /// \return Timestamp at the start of the hour in milliseconds. - TIME_SHIELD_CONSTEXPR inline ts_ms_t start_of_hour_ms(ts_ms_t ts_ms = time_shield::ts_ms()) noexcept { - return ts_ms - detail::floor_mod(ts_ms, MS_PER_HOUR); - } - - /// \brief Get the timestamp at the end of the hour. - /// \param ts Timestamp (default: current timestamp). - /// \return Returns the timestamp of the end of the hour. - TIME_SHIELD_CONSTEXPR inline ts_t end_of_hour(ts_t ts = time_shield::ts()) noexcept { - return ts - detail::floor_mod(ts, SEC_PER_HOUR) + SEC_PER_HOUR - 1; - } - - /// \brief Get the timestamp at the end of the hour in seconds. - /// \param ts_ms Timestamp in milliseconds (default: current timestamp). - /// \return Returns the timestamp of the end of the hour in seconds. - TIME_SHIELD_CONSTEXPR inline ts_t end_of_hour_sec(ts_ms_t ts_ms = time_shield::ts_ms()) noexcept { - return end_of_hour(ms_to_sec(ts_ms)); - } - - /// \brief Get the timestamp at the end of the hour in milliseconds. - /// \param ts_ms Timestamp in milliseconds (default: current timestamp). - /// \return Returns the timestamp of the end of the hour in milliseconds. - TIME_SHIELD_CONSTEXPR inline ts_ms_t end_of_hour_ms(ts_ms_t ts_ms = time_shield::ts_ms()) noexcept { - return ts_ms - detail::floor_mod(ts_ms, MS_PER_HOUR) + MS_PER_HOUR - 1; - } - - /// \brief Get the timestamp of the beginning of the minute. - /// \param ts Timestamp (default: current timestamp). - /// \return Returns the timestamp of the beginning of the minute. - TIME_SHIELD_CONSTEXPR inline ts_t start_of_min(ts_t ts = time_shield::ts()) noexcept { - return ts - detail::floor_mod(ts, SEC_PER_MIN); - } - - /// \brief Get the timestamp of the end of the minute. - /// \param ts Timestamp (default: current timestamp). - /// \return Returns the timestamp of the end of the minute. - TIME_SHIELD_CONSTEXPR inline ts_t end_of_min(ts_t ts = time_shield::ts()) noexcept { - return ts - detail::floor_mod(ts, SEC_PER_MIN) + SEC_PER_MIN - 1; - } - - /// \brief Get minute of day. - /// This function returns a value between 0 to 1439 (minute of day). - /// \param ts Timestamp in seconds (default: current timestamp). - /// \return Minute of day. - template - TIME_SHIELD_CONSTEXPR T min_of_day(ts_t ts = time_shield::ts()) noexcept { - const ts_t minutes = detail::floor_div(ts, SEC_PER_MIN); - return static_cast(detail::floor_mod(minutes, MIN_PER_DAY)); - } - - /// \brief Get hour of day. - /// This function returns a value between 0 to 23. - /// \param ts Timestamp in seconds (default: current timestamp). - /// \return Hour of day. - template - TIME_SHIELD_CONSTEXPR T hour_of_day(ts_t ts = time_shield::ts()) noexcept { - const ts_t hours = detail::floor_div(ts, SEC_PER_HOUR); - return static_cast(detail::floor_mod(hours, HOURS_PER_DAY)); - } - - /// \brief Get minute of hour. - /// This function returns a value between 0 to 59. - /// \param ts Timestamp in seconds (default: current timestamp). - /// \return Minute of hour. - template - TIME_SHIELD_CONSTEXPR T min_of_hour(ts_t ts = time_shield::ts()) noexcept { - const ts_t minutes = detail::floor_div(ts, SEC_PER_MIN); - return static_cast(detail::floor_mod(minutes, MIN_PER_HOUR)); - } - - /// \brief Get the timestamp of the start of the period. - /// \param p Positive period duration in seconds. - /// \param ts Timestamp (default: current timestamp). - /// \return Timestamp of the start of the period, or ERROR_TIMESTAMP for invalid period values. - template - TIME_SHIELD_CONSTEXPR ts_t start_of_period(T p, ts_t ts = time_shield::ts()) { - const ts_t period = static_cast(p); - return period <= 0 ? ERROR_TIMESTAMP : ts - detail::floor_mod(ts, period); - } - - /// \brief Get the timestamp of the end of the period. - /// \param p Positive period duration in seconds. - /// \param ts Timestamp (default: current timestamp). - /// \return Timestamp of the end of the period, or ERROR_TIMESTAMP for invalid period values. - template - TIME_SHIELD_CONSTEXPR ts_t end_of_period(T p, ts_t ts = time_shield::ts()) { - const ts_t period = static_cast(p); - return period <= 0 ? ERROR_TIMESTAMP : ts - detail::floor_mod(ts, period) + period - 1; - } - -/// \} - -}; // namespace time_shield - -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_DATE_TIME_CONVERSIONS_HPP_INCLUDED +#endif // TIME_SHIELD_HEADER_DATE_TIME_CONVERSIONS_HPP_INCLUDED diff --git a/include/time_shield/date_time_struct.hpp b/include/time_shield/date_time_struct.hpp index 539246db..fbb1398e 100644 --- a/include/time_shield/date_time_struct.hpp +++ b/include/time_shield/date_time_struct.hpp @@ -1,58 +1,8 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_DATE_TIME_STRUCT_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_DATE_TIME_STRUCT_HPP_INCLUDED +#ifndef TIME_SHIELD_HEADER_DATE_TIME_STRUCT_HPP_INCLUDED +#define TIME_SHIELD_HEADER_DATE_TIME_STRUCT_HPP_INCLUDED -/// \file date_time_struct.hpp -/// \brief Header for date and time structure and related functions. -/// -/// This file contains the definition of the DateTimeStruct structure and a function to create DateTimeStruct instances. +#include -#include - -namespace time_shield { - - /// \ingroup time_structures - /// \brief Structure to represent date and time. - struct DateTimeStruct { - int64_t year; ///< Year component of the date. - int mon; ///< Month component of the date (1-12). - int day; ///< Day component of the date (1-31). - int hour; ///< Hour component of time (0-23) - int min; ///< Minute component of time (0-59) - int sec; ///< Second component of time (0-59) - int ms; ///< Millisecond component of time (0-999) - }; - - /// \ingroup time_structures - /// \brief Creates a DateTimeStruct instance. - /// \param year The year component of the date. - /// \param mon The month component of the date, defaults to 1 (January). - /// \param day The day component of the date, defaults to 1. - /// \param hour The hour component of the time, defaults to 0. - /// \param min The minute component of the time, defaults to 0. - /// \param sec The second component of the time, defaults to 0. - /// \param ms The millisecond component of the time, defaults to 0. - /// \return A DateTimeStruct instance with the provided date and time components. - inline 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) { - DateTimeStruct date_time; - date_time.year = year; - date_time.mon = mon; - date_time.day = day; - date_time.hour = hour; - date_time.min = min; - date_time.sec = sec; - date_time.ms = ms; - return date_time; - } - -}; // namespace time_shield - -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_DATE_TIME_STRUCT_HPP_INCLUDED +#endif // TIME_SHIELD_HEADER_DATE_TIME_STRUCT_HPP_INCLUDED diff --git a/include/time_shield/datetime/DateTime.hpp b/include/time_shield/datetime/DateTime.hpp new file mode 100644 index 00000000..1dccf0e1 --- /dev/null +++ b/include/time_shield/datetime/DateTime.hpp @@ -0,0 +1,703 @@ +// SPDX-License-Identifier: MIT +#pragma once +#ifndef TIME_SHIELD_HEADER_DATETIME_DATETIME_HPP_INCLUDED +#define TIME_SHIELD_HEADER_DATETIME_DATETIME_HPP_INCLUDED + +/// \file DateTime.hpp +/// \brief Value-type wrapper for timestamps with fixed UTC offset. + +#include +#include +#include + +#include +#include +#include +#include +#ifdef TIME_SHIELD_CPP17 +#include +#endif + +namespace time_shield { + + /// \brief Represents a moment in time with optional fixed UTC offset. + /// + /// Equality and ordering compare the UTC instant only and ignore the stored offset. + class DateTime { + public: + /// \brief Default constructor sets epoch with zero offset. + DateTime() noexcept + : m_utc_ms(0) + , m_offset(0) {} + + /// \brief Create instance from UTC milliseconds. + /// \param utc_ms Timestamp in milliseconds since Unix epoch (UTC). + /// \param offset Fixed UTC offset in seconds. + /// \return Constructed DateTime. + static DateTime from_unix_ms(ts_ms_t utc_ms, tz_t offset = 0) noexcept { + return DateTime(utc_ms, offset); + } + + /// \brief Create instance from UTC seconds. + /// \param utc_s Timestamp in seconds since Unix epoch (UTC). + /// \param offset Fixed UTC offset in seconds. + /// \return Constructed DateTime. + static DateTime from_unix_s(ts_t utc_s, tz_t offset = 0) noexcept { + return DateTime(sec_to_ms(utc_s), offset); + } + + /// \brief Construct instance for current UTC time. + /// \param offset Fixed UTC offset in seconds. + /// \return DateTime for the current UTC time. + static DateTime now_utc(tz_t offset = 0) noexcept { + return DateTime(ts_ms(), offset); + } + + /// \brief Build from calendar components interpreted in provided offset. + static DateTime from_components( + year_t year, + int month, + int day, + int hour = 0, + int min = 0, + int sec = 0, + int ms = 0, + tz_t offset = 0) { + const ts_ms_t local_ms = to_timestamp_ms(year, month, day, hour, min, sec, ms); + const ts_ms_t utc_ms = local_ms - offset_to_ms(offset); + return DateTime(utc_ms, offset); + } + + /// \brief Try to build from calendar components interpreted in provided offset. + /// \param year Year component. + /// \param month Month component. + /// \param day Day component. + /// \param hour Hour component. + /// \param min Minute component. + /// \param sec Second component. + /// \param ms Millisecond component. + /// \param offset Fixed UTC offset in seconds. + /// \param out Output DateTime on success. + /// \return True when components form a valid date-time and offset. + static bool try_from_components( + year_t year, + int month, + int day, + int hour, + int min, + int sec, + int ms, + tz_t offset, + DateTime& out) noexcept { + if (!is_valid_date_time(year, month, day, hour, min, sec, ms)) { + return false; + } + if (!is_valid_tz_offset(offset)) { + return false; + } + const ts_ms_t local_ms = to_timestamp_ms(year, month, day, hour, min, sec, ms); + out = DateTime(local_ms - offset_to_ms(offset), offset); + return true; + } + + /// \brief Build from DateTimeStruct interpreted in provided offset. + static DateTime from_date_time_struct(const DateTimeStruct& local_dt, tz_t offset = 0) { + const ts_ms_t local_ms = dt_to_timestamp_ms(local_dt); + const ts_ms_t utc_ms = local_ms - offset_to_ms(offset); + return DateTime(utc_ms, offset); + } + + /// \brief Try to build from DateTimeStruct interpreted in provided offset. + /// \param local_dt Local date-time structure. + /// \param offset Fixed UTC offset in seconds. + /// \param out Output DateTime on success. + /// \return True when structure and offset are valid. + static bool try_from_date_time_struct( + const DateTimeStruct& local_dt, + tz_t offset, + DateTime& out) noexcept { + if (!is_valid_date_time(local_dt)) { + return false; + } + if (!is_valid_tz_offset(offset)) { + return false; + } + const ts_ms_t local_ms = dt_to_timestamp_ms(local_dt); + out = DateTime(local_ms - offset_to_ms(offset), offset); + return true; + } + + /// \brief Convert to date-time structure using stored offset. + DateTimeStruct to_date_time_struct_local() const { + return to_date_time_ms(local_ms()); + } + + /// \brief Convert to UTC date-time structure. + DateTimeStruct to_date_time_struct_utc() const { + return to_date_time_ms(m_utc_ms); + } + + /// \brief Build instance from ISO week date interpreted in provided offset. + static DateTime from_iso_week_date( + const IsoWeekDateStruct& iso, + int hour = 0, + int min = 0, + int sec = 0, + int ms = 0, + tz_t offset = 0) { + const DateStruct date = iso_week_date_to_date(iso); + return from_components(date.year, date.mon, date.day, hour, min, sec, ms, offset); + } + + /// \brief Try to parse ISO8601 string to DateTime. + /// \param str Input ISO8601 string. + /// \param out Output DateTime when parsing succeeds. + /// \return True on success. + static bool try_parse_iso8601(const std::string& str, DateTime& out) noexcept { + return try_parse_iso8601_buffer(str.data(), str.size(), out); + } + + #ifdef TIME_SHIELD_CPP17 + /// \brief Try to parse ISO8601 string_view to DateTime. + /// \param str Input ISO8601 string_view. + /// \param out Output DateTime when parsing succeeds. + /// \return True on success. + static bool try_parse_iso8601(std::string_view str, DateTime& out) noexcept { + return try_parse_iso8601_buffer(str.data(), str.size(), out); + } + #endif + + /// \brief Try to parse ISO8601 C-string to DateTime. + /// \param str Null-terminated ISO8601 string. + /// \param out Output DateTime when parsing succeeds. + /// \return True on success. + static bool try_parse_iso8601(const char* str, DateTime& out) noexcept { + if (str == nullptr) { + return false; + } + return try_parse_iso8601_buffer(str, std::strlen(str), out); + } + + /// \brief Parse ISO8601 string, throws on failure. + /// \param str Input ISO8601 string. + /// \return Parsed DateTime. + static DateTime parse_iso8601(const std::string& str) { + return parse_iso8601_buffer(str.data(), str.size()); + } + + #ifdef TIME_SHIELD_CPP17 + /// \brief Parse ISO8601 string_view, throws on failure. + /// \param str Input ISO8601 view. + /// \return Parsed DateTime. + static DateTime parse_iso8601(std::string_view str) { + return parse_iso8601_buffer(str.data(), str.size()); + } + #endif + + /// \brief Parse ISO8601 C-string, throws on failure. + /// \param str Null-terminated ISO8601 string. + /// \return Parsed DateTime. + static DateTime parse_iso8601(const char* str) { + if (str == nullptr) { + throw std::invalid_argument("Invalid ISO8601 datetime"); + } + return parse_iso8601_buffer(str, std::strlen(str)); + } + + /// \brief Try to parse ISO week-date string. + /// \param str Input ISO week-date string. + /// \param iso Output ISO week-date structure. + /// \return True on success. + /// \details Parser accepts canonical and compatible mixed separator variants, + /// uppercase or lowercase `W`, and Monday default when weekday is omitted. + static bool try_parse_iso_week_date(const std::string& str, IsoWeekDateStruct& iso) noexcept { + return parse_iso_week_date(str.data(), str.size(), iso); + } + + #ifdef TIME_SHIELD_CPP17 + /// \brief Try to parse ISO week-date string_view. + /// \details Parser accepts canonical and compatible mixed separator variants, + /// uppercase or lowercase `W`, and Monday default when weekday is omitted. + static bool try_parse_iso_week_date(std::string_view str, IsoWeekDateStruct& iso) noexcept { + return parse_iso_week_date(str.data(), str.size(), iso); + } + #endif + + /// \brief Try to parse ISO week-date C-string. + /// \details Parser accepts canonical and compatible mixed separator variants, + /// uppercase or lowercase `W`, and Monday default when weekday is omitted. + static bool try_parse_iso_week_date(const char* str, IsoWeekDateStruct& iso) noexcept { + if (str == nullptr) { + return false; + } + return parse_iso_week_date(str, std::strlen(str), iso); + } + + /// \brief Format to ISO8601 string with stored offset. + std::string to_iso8601() const { + return to_iso8601_ms(m_utc_ms, m_offset); + } + + /// \brief Format to ISO8601 string in UTC. + std::string to_iso8601_utc() const { + return to_iso8601_utc_ms(m_utc_ms); + } + + /// \brief Format using custom pattern. + std::string format(const std::string& fmt) const { + return to_string_ms(fmt, m_utc_ms, m_offset); + } + + #ifdef TIME_SHIELD_CPP17 + /// \brief Format using custom string_view pattern. + std::string format(std::string_view fmt) const { + return to_string_ms(std::string(fmt), m_utc_ms, m_offset); + } + #endif + + /// \brief Format using C-string pattern. + std::string format(const char* fmt) const { + if (fmt == nullptr) { + return std::string(); + } + return to_string_ms(std::string(fmt), m_utc_ms, m_offset); + } + + /// \brief Format to MQL5 date-time string. + std::string to_mql5_date_time() const { + return time_shield::to_mql5_date_time(ms_to_sec(local_ms())); + } + + /// \brief Access UTC milliseconds. + ts_ms_t unix_ms() const noexcept { + return m_utc_ms; + } + + /// \brief Access UTC seconds. + ts_t unix_s() const noexcept { + return ms_to_sec(m_utc_ms); + } + + /// \brief Access stored UTC offset. + tz_t utc_offset() const noexcept { + return m_offset; + } + + /// \brief Get timezone structure from offset. + TimeZoneStruct time_zone() const { + return to_time_zone_struct(m_offset); + } + + /// \brief Local year component. + year_t year() const { + return to_date_time_struct_local().year; + } + + /// \brief Local month component. + int month() const { + return to_date_time_struct_local().mon; + } + + /// \brief Local day component. + int day() const { + return to_date_time_struct_local().day; + } + + /// \brief Local hour component. + int hour() const { + return to_date_time_struct_local().hour; + } + + /// \brief Local minute component. + int minute() const { + return to_date_time_struct_local().min; + } + + /// \brief Local second component. + int second() const { + return to_date_time_struct_local().sec; + } + + /// \brief Local millisecond component. + int millisecond() const { + return to_date_time_struct_local().ms; + } + + /// \brief Local date components. + DateStruct date() const { + const DateTimeStruct local_dt = to_date_time_struct_local(); + return create_date_struct(local_dt.year, local_dt.mon, local_dt.day); + } + + /// \brief Local time-of-day components. + TimeStruct time_of_day() const { + const DateTimeStruct local_dt = to_date_time_struct_local(); + return create_time_struct( + static_cast(local_dt.hour), + static_cast(local_dt.min), + static_cast(local_dt.sec), + static_cast(local_dt.ms)); + } + + /// \brief UTC date components. + DateStruct utc_date() const { + const DateTimeStruct utc_dt = to_date_time_struct_utc(); + return create_date_struct(utc_dt.year, utc_dt.mon, utc_dt.day); + } + + /// \brief UTC time-of-day components. + TimeStruct utc_time_of_day() const { + const DateTimeStruct utc_dt = to_date_time_struct_utc(); + return create_time_struct( + static_cast(utc_dt.hour), + static_cast(utc_dt.min), + static_cast(utc_dt.sec), + static_cast(utc_dt.ms)); + } + + /// \brief UTC year component. + year_t utc_year() const { + return to_date_time_struct_utc().year; + } + + /// \brief UTC month component. + int utc_month() const { + return to_date_time_struct_utc().mon; + } + + /// \brief UTC day component. + int utc_day() const { + return to_date_time_struct_utc().day; + } + + /// \brief UTC hour component. + int utc_hour() const { + return to_date_time_struct_utc().hour; + } + + /// \brief UTC minute component. + int utc_minute() const { + return to_date_time_struct_utc().min; + } + + /// \brief UTC second component. + int utc_second() const { + return to_date_time_struct_utc().sec; + } + + /// \brief UTC millisecond component. + int utc_millisecond() const { + return to_date_time_struct_utc().ms; + } + + /// \brief Local weekday. + Weekday weekday() const { + const DateStruct local_date = date(); + return weekday_of_date(local_date); + } + + /// \brief Local ISO weekday number (1..7). + int iso_weekday() const { + const DateStruct local_date = date(); + return iso_weekday_of_date(local_date.year, local_date.mon, local_date.day); + } + + /// \brief Local ISO week date. + IsoWeekDateStruct iso_week_date() const { + const DateStruct local_date = date(); + return to_iso_week_date(local_date.year, local_date.mon, local_date.day); + } + + /// \brief UTC weekday. + Weekday utc_weekday() const { + const DateStruct utc_dt = utc_date(); + return weekday_of_date(utc_dt); + } + + /// \brief UTC ISO weekday number (1..7). + int utc_iso_weekday() const { + const DateStruct utc_dt = utc_date(); + return iso_weekday_of_date(utc_dt.year, utc_dt.mon, utc_dt.day); + } + + /// \brief UTC ISO week date. + IsoWeekDateStruct utc_iso_week_date() const { + const DateStruct utc_dt = utc_date(); + return to_iso_week_date(utc_dt.year, utc_dt.mon, utc_dt.day); + } + + /// \brief Check if local date is a workday. + bool is_workday() const noexcept { + return is_workday_ms(local_ms()); + } + + /// \brief Check if local date is a weekend. + bool is_weekend() const noexcept { + return time_shield::is_weekend(ms_to_sec(local_ms())); + } + + /// \brief Check if UTC date is a workday. + bool utc_is_workday() const noexcept { + const DateStruct utc_dt = utc_date(); + return time_shield::is_workday(utc_dt.year, utc_dt.mon, utc_dt.day); + } + + /// \brief Check if UTC date is a weekend. + bool utc_is_weekend() const noexcept { + return time_shield::is_weekend(ms_to_sec(m_utc_ms)); + } + + /// \brief Compare equality by UTC instant. + bool operator==(const DateTime& other) const noexcept { + return m_utc_ms == other.m_utc_ms; + } + + /// \brief Compare inequality by UTC instant. + bool operator!=(const DateTime& other) const noexcept { + return !(*this == other); + } + + /// \brief Less-than comparison by UTC instant. + bool operator<(const DateTime& other) const noexcept { + return m_utc_ms < other.m_utc_ms; + } + + /// \brief Less-than-or-equal comparison by UTC instant. + bool operator<=(const DateTime& other) const noexcept { + return m_utc_ms <= other.m_utc_ms; + } + + /// \brief Greater-than comparison by UTC instant. + bool operator>(const DateTime& other) const noexcept { + return m_utc_ms > other.m_utc_ms; + } + + /// \brief Greater-than-or-equal comparison by UTC instant. + bool operator>=(const DateTime& other) const noexcept { + return m_utc_ms >= other.m_utc_ms; + } + + /// \brief Check if local representations match including offset. + bool same_local(const DateTime& other) const noexcept { + return local_ms() == other.local_ms() && m_offset == other.m_offset; + } + + /// \brief Add milliseconds to UTC instant. + DateTime add_ms(int64_t delta_ms) const noexcept { + return DateTime(m_utc_ms + delta_ms, m_offset); + } + + /// \brief Add seconds to UTC instant. + DateTime add_seconds(int64_t seconds) const noexcept { + return add_ms(sec_to_ms(seconds)); + } + + /// \brief Add minutes to UTC instant. + DateTime add_minutes(int64_t minutes) const noexcept { + return add_ms(sec_to_ms(minutes * SEC_PER_MIN)); + } + + /// \brief Add hours to UTC instant. + DateTime add_hours(int64_t hours) const noexcept { + return add_ms(sec_to_ms(hours * SEC_PER_HOUR)); + } + + /// \brief Add days to UTC instant. + DateTime add_days(int64_t days) const noexcept { + return add_ms(days * MS_PER_DAY); + } + + /// \brief Difference in milliseconds to another DateTime. + int64_t diff_ms(const DateTime& other) const noexcept { + return m_utc_ms - other.m_utc_ms; + } + + /// \brief Difference in seconds to another DateTime. + double diff_seconds(const DateTime& other) const noexcept { + return static_cast(diff_ms(other)) / static_cast(MS_PER_SEC); + } + + /// \brief Return copy with new offset preserving instant. + DateTime with_offset(tz_t new_offset) const noexcept { + return DateTime(m_utc_ms, new_offset); + } + + /// \brief Return copy with zero offset. + DateTime to_utc() const noexcept { + return with_offset(0); + } + + /// \brief Start of local day. + DateTime start_of_day() const { + const DateTimeStruct local_dt = to_date_time_struct_local(); + const ts_ms_t local_start_ms = to_timestamp_ms(local_dt.year, local_dt.mon, local_dt.day); + return from_unix_ms(local_start_ms - offset_to_ms(m_offset), m_offset); + } + + /// \brief End of local day. + DateTime end_of_day() const { + const DateTimeStruct local_dt = to_date_time_struct_local(); + const ts_ms_t local_end_ms = to_timestamp_ms( + local_dt.year, + local_dt.mon, + local_dt.day, + 23, + 59, + 59, + static_cast(MS_PER_SEC - 1)); + return from_unix_ms(local_end_ms - offset_to_ms(m_offset), m_offset); + } + + /// \brief Start of local month. + DateTime start_of_month() const { + const DateTimeStruct local_dt = to_date_time_struct_local(); + const ts_ms_t local_start_ms = to_timestamp_ms(local_dt.year, local_dt.mon, 1); + return from_unix_ms(local_start_ms - offset_to_ms(m_offset), m_offset); + } + + /// \brief End of local month. + DateTime end_of_month() const { + const DateTimeStruct local_dt = to_date_time_struct_local(); + const int days = num_days_in_month(local_dt.year, local_dt.mon); + const ts_ms_t local_end_ms = to_timestamp_ms( + local_dt.year, + local_dt.mon, + days, + 23, + 59, + 59, + static_cast(MS_PER_SEC - 1)); + return from_unix_ms(local_end_ms - offset_to_ms(m_offset), m_offset); + } + + /// \brief Start of local year. + DateTime start_of_year() const { + const year_t local_year = year(); + const ts_ms_t local_start_ms = to_timestamp_ms(local_year, 1, 1); + return from_unix_ms(local_start_ms - offset_to_ms(m_offset), m_offset); + } + + /// \brief End of local year. + DateTime end_of_year() const { + const year_t local_year = year(); + const ts_ms_t local_end_ms = to_timestamp_ms( + local_year, + 12, + 31, + 23, + 59, + 59, + static_cast(MS_PER_SEC - 1)); + return from_unix_ms(local_end_ms - offset_to_ms(m_offset), m_offset); + } + + /// \brief Start of UTC day. + DateTime start_of_utc_day() const { + const DateTimeStruct utc_dt = to_date_time_struct_utc(); + return from_unix_ms(to_timestamp_ms(utc_dt.year, utc_dt.mon, utc_dt.day), m_offset); + } + + /// \brief End of UTC day. + DateTime end_of_utc_day() const { + const DateTimeStruct utc_dt = to_date_time_struct_utc(); + return from_unix_ms( + to_timestamp_ms( + utc_dt.year, + utc_dt.mon, + utc_dt.day, + 23, + 59, + 59, + static_cast(MS_PER_SEC - 1)), + m_offset); + } + + /// \brief Start of UTC month. + DateTime start_of_utc_month() const { + const DateTimeStruct utc_dt = to_date_time_struct_utc(); + return from_unix_ms(to_timestamp_ms(utc_dt.year, utc_dt.mon, 1), m_offset); + } + + /// \brief End of UTC month. + DateTime end_of_utc_month() const { + const DateTimeStruct utc_dt = to_date_time_struct_utc(); + const int days = num_days_in_month(utc_dt.year, utc_dt.mon); + return from_unix_ms( + to_timestamp_ms( + utc_dt.year, + utc_dt.mon, + days, + 23, + 59, + 59, + static_cast(MS_PER_SEC - 1)), + m_offset); + } + + /// \brief Start of UTC year. + DateTime start_of_utc_year() const { + const year_t utc_year_value = utc_year(); + return from_unix_ms(to_timestamp_ms(utc_year_value, 1, 1), m_offset); + } + + /// \brief End of UTC year. + DateTime end_of_utc_year() const { + const year_t utc_year_value = utc_year(); + return from_unix_ms( + to_timestamp_ms( + utc_year_value, + 12, + 31, + 23, + 59, + 59, + static_cast(MS_PER_SEC - 1)), + m_offset); + } + + private: + static bool try_parse_iso8601_buffer(const char* data, std::size_t size, DateTime& out) noexcept { + if (data == nullptr) { + return false; + } + DateTimeStruct dt = create_date_time_struct(0); + TimeZoneStruct tz = create_time_zone_struct(0, 0, true); + if (!time_shield::parse_iso8601(data, size, dt, tz)) { + return false; + } + const tz_t offset = time_zone_struct_to_offset(tz); + if (!is_valid_tz_offset(offset)) { + return false; + } + const ts_ms_t utc_ms = dt_to_timestamp_ms(dt) - offset_to_ms(offset); + out = from_unix_ms(utc_ms, offset); + return true; + } + + static DateTime parse_iso8601_buffer(const char* data, std::size_t size) { + DateTime result; + if (!try_parse_iso8601_buffer(data, size, result)) { + throw std::invalid_argument("Invalid ISO8601 datetime"); + } + return result; + } + + DateTime(ts_ms_t utc_ms, tz_t offset) noexcept + : m_utc_ms(utc_ms) + , m_offset(offset) {} + + static TIME_SHIELD_CONSTEXPR ts_ms_t offset_to_ms(tz_t offset) noexcept { + return static_cast(offset) * MS_PER_SEC; + } + + ts_ms_t local_ms() const noexcept { + return m_utc_ms + offset_to_ms(m_offset); + } + + ts_ms_t m_utc_ms; + tz_t m_offset; + }; + +} // namespace time_shield + +#endif // TIME_SHIELD_HEADER_DATETIME_DATETIME_HPP_INCLUDED diff --git a/include/time_shield/detail/fast_date.hpp b/include/time_shield/detail/fast_date.hpp index 537ce74e..7526dd5e 100644 --- a/include/time_shield/detail/fast_date.hpp +++ b/include/time_shield/detail/fast_date.hpp @@ -1,245 +1,8 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_DETAIL_FAST_DATE_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_DETAIL_FAST_DATE_HPP_INCLUDED +#ifndef TIME_SHIELD_HEADER_DETAIL_FAST_DATE_HPP_INCLUDED +#define TIME_SHIELD_HEADER_DETAIL_FAST_DATE_HPP_INCLUDED -/// \file fast_date.hpp -/// \brief Fast date conversion helpers. +#include -#include "mul_hi.hpp" - -#include - -namespace time_shield { -namespace detail { - - namespace { - constexpr int16_t k_doy_from_march[12] = { - 0, // Mar - 31, // Apr - 61, // May - 92, // Jun - 122, // Jul - 153, // Aug - 184, // Sep - 214, // Oct - 245, // Nov - 275, // Dec - 306, // Jan - 337 // Feb - }; - } // namespace - - struct DaySplit { - int64_t days; - int64_t sec_of_day; - }; - - /// \brief Split UNIX seconds into whole days and seconds-of-day. - TIME_SHIELD_CONSTEXPR inline DaySplit split_unix_day(ts_t p_ts) noexcept { - int64_t days = p_ts / SEC_PER_DAY; - int64_t sec_of_day = p_ts % SEC_PER_DAY; - if (sec_of_day < 0) { - sec_of_day += SEC_PER_DAY; - days -= 1; - } - return {days, sec_of_day}; - } - - struct FastDate { - int64_t year; - int month; - int day; - }; - - /// \brief Convert date to days since Unix epoch using a fast constexpr algorithm. - /// \note Inspired by the algorithm described in: - /// https://www.benjoffe.com/fast-date-64 - /// This implementation is written from scratch (no code copied). - TIME_SHIELD_CONSTEXPR inline int64_t fast_days_from_date_constexpr( - int64_t p_year, - int p_month, - int p_day) noexcept { - const int month_adjust = (p_month <= 2 ? 1 : 0); - const int64_t y = p_year - month_adjust; - int m = p_month - 3; - if (m < 0) { - m += 12; - } - - if (y >= 0) { - const uint64_t y_u = static_cast(y); - const uint64_t era = y_u / 400U; - const uint64_t yoe = y_u - era * 400U; - const uint64_t doy = static_cast(k_doy_from_march[m]) + static_cast(p_day - 1); - const uint64_t doe = yoe * 365U + yoe / 4U - yoe / 100U + doy; - return static_cast(era * 146097U + doe) - 719468; - } - - const int64_t era = (y - 399) / 400; - const int64_t yoe = y - era * 400; - const int64_t doy = static_cast(k_doy_from_march[m]) + static_cast(p_day - 1); - const int64_t doe = yoe * 365 + yoe / 4 - yoe / 100 + doy; - return era * 146097 + doe - 719468; - } - - /// \brief Convert date to days since Unix epoch using a fast algorithm. - /// \note Inspired by the algorithm described in: - /// https://www.benjoffe.com/fast-date-64 - /// This implementation is written from scratch (no code copied). - inline int64_t fast_days_from_date(int64_t p_year, int p_month, int p_day) noexcept { - const int month_adjust = (p_month <= 2 ? 1 : 0); - const int64_t y = p_year - month_adjust; - int m = p_month - 3; - if (m < 0) { - m += 12; - } - - if (y >= 0) { - const uint64_t y_u = static_cast(y); - const uint64_t era = y_u / 400U; - const uint64_t yoe = y_u - era * 400U; - const uint64_t doy = static_cast(k_doy_from_march[m]) + static_cast(p_day - 1); - const uint64_t doe = yoe * 365U + yoe / 4U - yoe / 100U + doy; - return static_cast(era * 146097U + doe) - 719468; - } - - const int64_t era = (y - 399) / 400; - const int64_t yoe = y - era * 400; - const int64_t doy = static_cast(k_doy_from_march[m]) + static_cast(p_day - 1); - const int64_t doe = yoe * 365 + yoe / 4 - yoe / 100 + doy; - return era * 146097 + doe - 719468; - } - - /// \brief Convert days since Unix epoch to date using a fast constexpr algorithm. - /// \note Inspired by the algorithm described in: - /// https://www.benjoffe.com/fast-date-64 - /// This implementation is written from scratch (no code copied). - TIME_SHIELD_CONSTEXPR inline FastDate fast_date_from_days_constexpr(int64_t p_days) noexcept { - constexpr uint64_t ERAS = 4726498270ULL; - constexpr int64_t D_SHIFT = static_cast(146097ULL * ERAS - 719469ULL); - constexpr int64_t Y_SHIFT = static_cast(400ULL * ERAS - 1ULL); - constexpr uint64_t C1 = 505054698555331ULL; - constexpr uint64_t C2 = 50504432782230121ULL; - constexpr uint64_t C3 = 8619973866219416ULL; - constexpr uint64_t YPT_SCALE = 782432ULL; - constexpr uint64_t YPT_BUMP_THRESHOLD = 126464ULL; - constexpr uint64_t SHIFT_JAN_FEB = 191360ULL; - constexpr uint64_t SHIFT_OTHER = 977792ULL; - - const uint64_t rev = static_cast(D_SHIFT - p_days); - const uint64_t cen = mul_shift_u64_constexpr(rev, C1); - const uint64_t jul = rev + cen - (cen / 4U); - - const uint64_t num_hi = mul_shift_u64_constexpr(jul, C2); - const uint64_t num_low = jul * C2; - const uint64_t yrs = static_cast(Y_SHIFT) - num_hi; - - const uint64_t ypt = mul_shift_u64_constexpr(YPT_SCALE, num_low); - const bool bump = ypt < YPT_BUMP_THRESHOLD; - const uint64_t shift = bump ? SHIFT_JAN_FEB : SHIFT_OTHER; - - const uint64_t N = (yrs & 3ULL) * 512ULL + shift - ypt; - const uint64_t d = mul_shift_u64_constexpr((N & 0xFFFFULL), C3); - - return FastDate{ - static_cast(yrs + (bump ? 1U : 0U)), - static_cast(N >> 16), - static_cast(d + 1U) - }; - } - - /// \brief Convert days since Unix epoch to date using a fast algorithm. - /// \note Inspired by the algorithm described in: - /// https://www.benjoffe.com/fast-date-64 - /// This implementation is written from scratch (no code copied). - inline FastDate fast_date_from_days(int64_t p_days) noexcept { - constexpr uint64_t ERAS = 4726498270ULL; - constexpr int64_t D_SHIFT = static_cast(146097ULL * ERAS - 719469ULL); - constexpr int64_t Y_SHIFT = static_cast(400ULL * ERAS - 1ULL); - constexpr uint64_t C1 = 505054698555331ULL; - constexpr uint64_t C2 = 50504432782230121ULL; - constexpr uint64_t C3 = 8619973866219416ULL; - constexpr uint64_t YPT_SCALE = 782432ULL; - constexpr uint64_t YPT_BUMP_THRESHOLD = 126464ULL; - constexpr uint64_t SHIFT_JAN_FEB = 191360ULL; - constexpr uint64_t SHIFT_OTHER = 977792ULL; - - const uint64_t rev = static_cast(D_SHIFT - p_days); - const uint64_t cen = mul_shift_u64(rev, C1); - const uint64_t jul = rev + cen - (cen / 4U); - - const uint64_t num_hi = mul_shift_u64(jul, C2); - const uint64_t num_low = jul * C2; - const uint64_t yrs = static_cast(Y_SHIFT) - num_hi; - - const uint64_t ypt = mul_shift_u64(YPT_SCALE, num_low); - const bool bump = ypt < YPT_BUMP_THRESHOLD; - const uint64_t shift = bump ? SHIFT_JAN_FEB : SHIFT_OTHER; - - const uint64_t N = (yrs & 3ULL) * 512ULL + shift - ypt; - const uint64_t d = mul_shift_u64((N & 0xFFFFULL), C3); - - FastDate result{}; - result.day = static_cast(d + 1U); - result.month = static_cast(N >> 16); - result.year = static_cast(yrs + (bump ? 1U : 0U)); - return result; - } - - /// \brief Convert days since Unix epoch to year using a fast constexpr algorithm. - /// \note Inspired by the algorithm described in: - /// https://www.benjoffe.com/fast-date-64 - /// This implementation is written from scratch (no code copied). - TIME_SHIELD_CONSTEXPR inline int64_t fast_year_from_days_constexpr(int64_t p_days) noexcept { - constexpr uint64_t ERAS = 4726498270ULL; - constexpr int64_t D_SHIFT = static_cast(146097ULL * ERAS - 719469ULL); - constexpr int64_t Y_SHIFT = static_cast(400ULL * ERAS - 1ULL); - constexpr uint64_t C1 = 505054698555331ULL; - constexpr uint64_t C2 = 50504432782230121ULL; - constexpr uint64_t YPT_SCALE = 782432ULL; - constexpr uint64_t YPT_BUMP_THRESHOLD = 126464ULL; - - const uint64_t rev = static_cast(D_SHIFT - p_days); - const uint64_t cen = mul_shift_u64_constexpr(rev, C1); - const uint64_t jul = rev + cen - (cen / 4U); - - const uint64_t num_hi = mul_shift_u64_constexpr(jul, C2); - const uint64_t num_low = jul * C2; - const uint64_t yrs = static_cast(Y_SHIFT) - num_hi; - - const uint64_t ypt = mul_shift_u64_constexpr(YPT_SCALE, num_low); - const bool bump = ypt < YPT_BUMP_THRESHOLD; - return static_cast(yrs + (bump ? 1U : 0U)); - } - - /// \brief Convert days since Unix epoch to year using a fast algorithm. - /// \note Inspired by the algorithm described in: - /// https://www.benjoffe.com/fast-date-64 - /// This implementation is written from scratch (no code copied). - inline int64_t fast_year_from_days(int64_t p_days) noexcept { - constexpr uint64_t ERAS = 4726498270ULL; - constexpr int64_t D_SHIFT = static_cast(146097ULL * ERAS - 719469ULL); - constexpr int64_t Y_SHIFT = static_cast(400ULL * ERAS - 1ULL); - constexpr uint64_t C1 = 505054698555331ULL; - constexpr uint64_t C2 = 50504432782230121ULL; - constexpr uint64_t YPT_SCALE = 782432ULL; - constexpr uint64_t YPT_BUMP_THRESHOLD = 126464ULL; - - const uint64_t rev = static_cast(D_SHIFT - p_days); - const uint64_t cen = mul_shift_u64(rev, C1); - const uint64_t jul = rev + cen - (cen / 4U); - - const uint64_t num_hi = mul_shift_u64(jul, C2); - const uint64_t num_low = jul * C2; - const uint64_t yrs = static_cast(Y_SHIFT) - num_hi; - - const uint64_t ypt = mul_shift_u64(YPT_SCALE, num_low); - const bool bump = ypt < YPT_BUMP_THRESHOLD; - return static_cast(yrs + (bump ? 1U : 0U)); - } - -} // namespace detail -} // namespace time_shield - -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_DETAIL_FAST_DATE_HPP_INCLUDED +#endif // TIME_SHIELD_HEADER_DETAIL_FAST_DATE_HPP_INCLUDED diff --git a/include/time_shield/detail/floor_math.hpp b/include/time_shield/detail/floor_math.hpp index 0fa93df9..55e88647 100644 --- a/include/time_shield/detail/floor_math.hpp +++ b/include/time_shield/detail/floor_math.hpp @@ -1,27 +1,8 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_DETAIL_FLOOR_MATH_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_DETAIL_FLOOR_MATH_HPP_INCLUDED +#ifndef TIME_SHIELD_HEADER_DETAIL_FLOOR_MATH_HPP_INCLUDED +#define TIME_SHIELD_HEADER_DETAIL_FLOOR_MATH_HPP_INCLUDED -/// \file floor_math.hpp -/// \brief Floor division and modulus helpers. +#include -namespace time_shield { -namespace detail { - - /// \brief Floor division for positive divisor. - template - TIME_SHIELD_CONSTEXPR inline T floor_div(T a, T b) noexcept { - return static_cast((a / b) - (((a % b) != 0 && a < 0) ? 1 : 0)); - } - - /// \brief Floor-mod for positive modulus (returns r in [0..b)). - template - TIME_SHIELD_CONSTEXPR inline T floor_mod(T a, T b) noexcept { - return static_cast((a % b) + (((a % b) < 0) ? b : 0)); - } - -} // namespace detail -} // namespace time_shield - -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_DETAIL_FLOOR_MATH_HPP_INCLUDED +#endif // TIME_SHIELD_HEADER_DETAIL_FLOOR_MATH_HPP_INCLUDED diff --git a/include/time_shield/detail/mul_hi.hpp b/include/time_shield/detail/mul_hi.hpp index c13b9dd7..07873e6a 100644 --- a/include/time_shield/detail/mul_hi.hpp +++ b/include/time_shield/detail/mul_hi.hpp @@ -1,59 +1,8 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_DETAIL_MUL_HI_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_DETAIL_MUL_HI_HPP_INCLUDED +#ifndef TIME_SHIELD_HEADER_DETAIL_MUL_HI_HPP_INCLUDED +#define TIME_SHIELD_HEADER_DETAIL_MUL_HI_HPP_INCLUDED -/// \file mul_hi.hpp -/// \brief Helpers for 64-bit multiply-high operations. +#include -#include - -#if defined(_MSC_VER) -# include -#endif - -namespace time_shield { -namespace detail { - - /// \brief Return the high 64 bits of a 64x64-bit multiplication (constexpr variant). - TIME_SHIELD_CONSTEXPR inline uint64_t mul_hi_u64_constexpr(uint64_t p_a, uint64_t p_b) noexcept { - const uint64_t a_low = p_a & 0xFFFFFFFFULL; - const uint64_t a_high = p_a >> 32; - const uint64_t b_low = p_b & 0xFFFFFFFFULL; - const uint64_t b_high = p_b >> 32; - - const uint64_t p0 = a_low * b_low; - const uint64_t p1 = a_low * b_high; - const uint64_t p2 = a_high * b_low; - const uint64_t p3 = a_high * b_high; - - const uint64_t carry = ((p0 >> 32) + (p1 & 0xFFFFFFFFULL) + (p2 & 0xFFFFFFFFULL)) >> 32; - return p3 + (p1 >> 32) + (p2 >> 32) + carry; - } - - /// \brief Return the high 64 bits of a 64x64-bit multiplication. - inline uint64_t mul_hi_u64(uint64_t p_a, uint64_t p_b) noexcept { -#if defined(_MSC_VER) && (defined(_M_X64) || defined(_M_ARM64)) - uint64_t high = 0; - (void)_umul128(p_a, p_b, &high); - return high; -#else - const __uint128_t product = static_cast<__uint128_t>(p_a) * static_cast<__uint128_t>(p_b); - return static_cast(product >> 64); -#endif - } - - /// \brief Alias for mul_hi_u64 used for shift-by-64 operations. - inline uint64_t mul_shift_u64(uint64_t p_x, uint64_t p_c) noexcept { - return mul_hi_u64(p_x, p_c); - } - - /// \brief Alias for mul_hi_u64_constexpr used for shift-by-64 operations. - TIME_SHIELD_CONSTEXPR inline uint64_t mul_shift_u64_constexpr(uint64_t p_x, uint64_t p_c) noexcept { - return mul_hi_u64_constexpr(p_x, p_c); - } - -} // namespace detail -} // namespace time_shield - -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_DETAIL_MUL_HI_HPP_INCLUDED +#endif // TIME_SHIELD_HEADER_DETAIL_MUL_HI_HPP_INCLUDED diff --git a/include/time_shield/enums.hpp b/include/time_shield/enums.hpp index 43cd4b56..29a7536b 100644 --- a/include/time_shield/enums.hpp +++ b/include/time_shield/enums.hpp @@ -1,312 +1,8 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_ENUMS_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_ENUMS_HPP_INCLUDED +#ifndef TIME_SHIELD_HEADER_ENUMS_HPP_INCLUDED +#define TIME_SHIELD_HEADER_ENUMS_HPP_INCLUDED -/// \file enums.hpp -/// \ingroup time_enums -/// \brief Header file with enumerations for weekdays, months, and other time-related categories. -/// -/// This file contains enum definitions for representing various time-related concepts. +#include -#include -#include - -namespace time_shield { - - /// \ingroup time_enums - /// Enumeration of the format options for representing a weekday or month. - enum FormatType { - UPPERCASE_NAME = 0, ///< Uppercase short name - SHORT_NAME, ///< Short name - FULL_NAME, ///< Full name - }; - - /// \ingroup time_enums - /// Enumeration of the days of the week. - enum Weekday { - SUN = 0, ///< Sunday - MON, ///< Monday - TUE, ///< Tuesday - WED, ///< Wednesday - THU, ///< Thursday - FRI, ///< Friday - SAT ///< Saturday - }; - - /// \ingroup time_enums - /// \brief Converts a Weekday enum value to a string. - /// - /// \param value The Weekday enum value to convert. - /// \param format The format to use for the string representation (default is UPPERCASE_NAME). - /// \return A const char* pointing to the string representation of the day. - inline const char* to_cstr(Weekday value, FormatType format = UPPERCASE_NAME) { - static const char* const uppercase_names[] = { - "SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT" - }; - static const char* const short_names[] = { - "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" - }; - static const char* const full_names[] = { - "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" - }; - switch (format) { - default: - case UPPERCASE_NAME: - return uppercase_names[static_cast(value)]; - case SHORT_NAME: - return short_names[static_cast(value)]; - case FULL_NAME: - return full_names[static_cast(value)]; - }; - } - - /// \ingroup time_enums - /// \brief Converts a Weekday enum value to a string. - /// - /// \param value The Weekday enum value to convert. - /// \param format The format to use for the string representation (default is UPPERCASE_NAME). - /// \return A const std::string& pointing to the string representation of the day. - inline const std::string& to_str(Weekday value, FormatType format = UPPERCASE_NAME) { - static const std::array uppercase_names = { - "SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT" - }; - static const std::array short_names = { - "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" - }; - static const std::array full_names = { - "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" - }; - switch (format) { - default: - case UPPERCASE_NAME: - return uppercase_names[static_cast(value)]; - case SHORT_NAME: - return short_names[static_cast(value)]; - case FULL_NAME: - return full_names[static_cast(value)]; - }; - } - - /// \ingroup time_enums - /// Enumeration of the months of the year. - enum Month { - JAN = 1, ///< January - FEB, ///< February - MAR, ///< March - APR, ///< April - MAY, ///< May - JUN, ///< June - JUL, ///< July - AUG, ///< August - SEP, ///< September - OCT, ///< October - NOV, ///< November - DEC ///< December - }; - - /// \ingroup time_enums - /// \brief Converts a Month enum value to a string. - /// - /// \param value The Month enum value to convert. - /// \param format The format to use for the string representation (default is UPPERCASE_NAME). - /// \return A const char* pointing to the string representation of the month. - inline const char* to_cstr(Month value, FormatType format = UPPERCASE_NAME) { - static const char* const uppercase_names[] = { - "", - "JAN", "FEB", "MAR", "APR", "MAY", "JUN", - "JUL", "AUG", "SEP", "OCT", "NOV", "DEC" - }; - static const char* const short_names[] = { - "", - "Jan", "Feb", "Mar", "Apr", "May", "Jun", - "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" - }; - static const char* const full_names[] = { - "", - "January", "February", "March", "April", "May", "June", - "July", "August", "September", "October", "November", "December" - }; - switch (format) { - default: - case UPPERCASE_NAME: - return uppercase_names[static_cast(value)]; - case SHORT_NAME: - return short_names[static_cast(value)]; - case FULL_NAME: - return full_names[static_cast(value)]; - }; - } - - /// \ingroup time_enums - /// \brief Converts a Month enum value to a string. - /// - /// \param value The Month enum value to convert. - /// \param format The format to use for the string representation (default is UPPERCASE_NAME). - /// \return A const std::string& pointing to the string representation of the month. - inline const std::string& to_str(Month value, FormatType format = UPPERCASE_NAME) { - static const std::array uppercase_names = { - "", - "JAN", "FEB", "MAR", "APR", "MAY", "JUN", - "JUL", "AUG", "SEP", "OCT", "NOV", "DEC" - }; - static const std::array short_names = { - "", - "Jan", "Feb", "Mar", "Apr", "May", "Jun", - "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" - }; - static const std::array full_names = { - "", - "January", "February", "March", "April", "May", "June", - "July", "August", "September", "October", "November", "December" - }; - switch (format) { - default: - case UPPERCASE_NAME: - return uppercase_names[static_cast(value)]; - case SHORT_NAME: - return short_names[static_cast(value)]; - case FULL_NAME: - return full_names[static_cast(value)]; - }; - } - - /// \ingroup time_enums - /// Enumeration of the time zones. - enum TimeZone { - GMT, ///< Greenwich Mean Time - UTC, ///< Coordinated Universal Time - EET, ///< Eastern European Time - CET, ///< Central European Time - WET, ///< Western European Time - EEST, ///< Eastern European Summer Time - CEST, ///< Central European Summer Time - WEST, ///< Western European Summer Time - ET, ///< US Eastern Time - CT, ///< US Central Time - IST, ///< India Standard Time - MYT, ///< Malaysia Time - WIB, ///< Western Indonesia Time - WITA, ///< Central Indonesia Time - WIT, ///< Eastern Indonesia Time - KZT, ///< Kazakhstan Time - TRT, ///< Turkey Time - BYT, ///< Belarus Time - SGT, ///< Singapore Time - ICT, ///< Indochina Time - PHT, ///< Philippine Time - GST, ///< Gulf Standard Time - HKT, ///< Hong Kong Time - JST, ///< Japan Standard Time - KST, ///< Korea Standard Time - UNKNOWN ///< Unknown Time Zone - }; - - /// \ingroup time_enums - /// \brief Converts a TimeZone enum value to a string. - /// - /// \param value The TimeZone enum value to convert. - /// \param format The format to use for the string representation (default is UPPERCASE_NAME). - /// \return A const char* pointing to the string representation of the time zone. - inline const char* to_cstr(TimeZone value, FormatType format = UPPERCASE_NAME) { - static const char* const uppercase_names[] = { - "GMT", "UTC", "EET", "CET", "WET", "EEST", "CEST", "WEST", - "ET", "CT", "IST", "MYT", "WIB", "WITA", "WIT", "KZT", "TRT", - "BYT", "SGT", "ICT", "PHT", "GST", "HKT", "JST", "KST", "UNKNOWN" - }; - static const char* const short_names[] = { - "GMT", "UTC", "EET", "CET", "WET", "EEST", "CEST", "WEST", - "ET", "CT", "IST", "MYT", "WIB", "WITA", "WIT", "KZT", "TRT", - "BYT", "SGT", "ICT", "PHT", "GST", "HKT", "JST", "KST", "Unknown" - }; - static const char* const full_names[] = { - "Greenwich Mean Time", "Coordinated Universal Time", "Eastern European Time", - "Central European Time", "Western European Time", "Eastern European Summer Time", - "Central European Summer Time", "Western European Summer Time", - "US Eastern Time", "US Central Time", "India Standard Time", - "Malaysia Time", "Western Indonesia Time", "Central Indonesia Time", - "Eastern Indonesia Time", "Kazakhstan Time", "Turkey Time", - "Belarus Time", "Singapore Time", "Indochina Time", - "Philippine Time", "Gulf Standard Time", "Hong Kong Time", - "Japan Standard Time", "Korea Standard Time", "Unknown Time Zone" - }; - switch (format) { - default: - case UPPERCASE_NAME: - return uppercase_names[static_cast(value)]; - case SHORT_NAME: - return short_names[static_cast(value)]; - case FULL_NAME: - return full_names[static_cast(value)]; - } - } - - /// \ingroup time_enums - /// \brief Converts a TimeZone enum value to a string. - /// - /// \param value The TimeZone enum value to convert. - /// \param format The format to use for the string representation (default is UPPERCASE_NAME). - /// \return A const std::string& pointing to the string representation of the time zone. - inline const std::string& to_str(TimeZone value, FormatType format = UPPERCASE_NAME) { - static const std::array uppercase_names = { - "GMT", "UTC", "EET", "CET", "WET", "EEST", "CEST", "WEST", - "ET", "CT", "IST", "MYT", "WIB", "WITA", "WIT", "KZT", "TRT", - "BYT", "SGT", "ICT", "PHT", "GST", "HKT", "JST", "KST", "UNKNOWN" - }; - static const std::array short_names = { - "gmt", "utc", "eet", "cet", "wet", "eest", "cest", "west", - "et", "ct", "ist", "myt", "wib", "wita", "wit", "kzt", "trt", - "byt", "sgt", "ict", "pht", "gst", "hkt", "jst", "kst", "unknown" - }; - static const std::array full_names = { - "Greenwich Mean Time", "Coordinated Universal Time", "Eastern European Time", - "Central European Time", "Western European Time", "Eastern European Summer Time", - "Central European Summer Time", "Western European Summer Time", - "US Eastern Time", "US Central Time", "India Standard Time", - "Malaysia Time", "Western Indonesia Time", "Central Indonesia Time", - "Eastern Indonesia Time", "Kazakhstan Time", "Turkey Time", - "Belarus Time", "Singapore Time", "Indochina Time", - "Philippine Time", "Gulf Standard Time", "Hong Kong Time", - "Japan Standard Time", "Korea Standard Time", "Unknown Time Zone" - }; - switch (format) { - default: - case UPPERCASE_NAME: - return uppercase_names[static_cast(value)]; - case SHORT_NAME: - return short_names[static_cast(value)]; - case FULL_NAME: - return full_names[static_cast(value)]; - } - } - - /// \ingroup time_enums - /// Enumeration of the moon phases. - enum MoonPhase { - WAXING_CRESCENT, ///< Waxing Crescent Moon - FIRST_QUARTER, ///< First Quarter Moon - WAXING_GIBBOUS, ///< Waxing Gibbous Moon - FULL_MOON, ///< Full Moon - WANING_GIBBOUS, ///< Waning Gibbous Moon - LAST_QUARTER, ///< Last Quarter Moon - WANING_CRESCENT, ///< Waning Crescent Moon - NEW_MOON ///< New Moon - }; - - /// \ingroup time_enums - /// Enumeration of time format types. - enum TimeFormatType { - ISO8601_WITH_TZ, ///< ISO8601 format with time zone (e.g., "2024-06-06T12:30:45+03:00") - ISO8601_NO_TZ, ///< ISO8601 format without time zone (e.g., "2024-06-06T12:30:45") - MQL5_FULL, ///< MQL5 time format (e.g., "2024.06.06 12:30:45") - MQL5_DATE_ONLY, ///< MQL5 date format (e.g., "2024.06.06") - MQL5_TIME_ONLY, ///< MQL5 time format (e.g., "12:30:45") - AMERICAN_MONTH_DAY, ///< American date format (e.g., "06/06/2024") - EUROPEAN_MONTH_DAY, ///< European date format (e.g., "06.06.2024") - AMERICAN_TIME, ///< American time format (e.g., "12:30 PM") - EUROPEAN_TIME, ///< European time format (e.g., "12:30") - }; - -}; // namespace time_shield - -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_ENUMS_HPP_INCLUDED +#endif // TIME_SHIELD_HEADER_ENUMS_HPP_INCLUDED diff --git a/include/time_shield/initialization.hpp b/include/time_shield/initialization.hpp index 3d15f650..bcb58943 100644 --- a/include/time_shield/initialization.hpp +++ b/include/time_shield/initialization.hpp @@ -1,29 +1,8 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_INITIALIZATION_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_INITIALIZATION_HPP_INCLUDED +#ifndef TIME_SHIELD_HEADER_INITIALIZATION_HPP_INCLUDED +#define TIME_SHIELD_HEADER_INITIALIZATION_HPP_INCLUDED -/// \file initialization.hpp -/// \ingroup lib_initialization -/// \brief Initialization helpers for the Time Shield library. -/// -/// This file defines the ::time_shield::init() function, which should be called once -/// before using any other Time Shield features that rely on internal time resolution. +#include -#include "time_utils.hpp" - -namespace time_shield { - - /// \ingroup lib_initialization - /// \brief Initializes the Time Shield library. - /// - /// This function performs required setup for internal components, - /// such as triggering lazy initialization used by ::time_shield::now_realtime_us(). - /// Call it once at the beginning of your program before using other parts of the library. - inline void init() { - now_realtime_us(); - } - -}; // namespace time_shield - -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_INITIALIZATION_HPP_INCLUDED +#endif // TIME_SHIELD_HEADER_INITIALIZATION_HPP_INCLUDED diff --git a/include/time_shield/iso_week_conversions.hpp b/include/time_shield/iso_week_conversions.hpp index 8fa830bd..0b1f083d 100644 --- a/include/time_shield/iso_week_conversions.hpp +++ b/include/time_shield/iso_week_conversions.hpp @@ -1,266 +1,8 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_ISO_WEEK_CONVERSIONS_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_ISO_WEEK_CONVERSIONS_HPP_INCLUDED +#ifndef TIME_SHIELD_HEADER_ISO_WEEK_CONVERSIONS_HPP_INCLUDED +#define TIME_SHIELD_HEADER_ISO_WEEK_CONVERSIONS_HPP_INCLUDED -/// \file iso_week_conversions.hpp -/// \brief Conversions and utilities for ISO week dates (ISO 8601). -/// -/// This file provides helpers to convert between calendar dates, timestamps, and ISO week dates, -/// as well as formatting and parsing helpers for ISO week-date strings. +#include -#include "config.hpp" -#include "constants.hpp" -#include "date_struct.hpp" -#include "date_time_struct.hpp" -#include "iso_week_struct.hpp" -#include "time_conversions.hpp" -#include "unix_time_conversions.hpp" - -#include -#include -#include -#include -#include -#include - -namespace time_shield { - -/// \ingroup time_conversions -/// \{ - - /// \brief Convert Weekday enum to ISO weekday (Mon=1 .. Sun=7). - /// \param weekday Weekday enum value. - /// \return ISO weekday number. - TIME_SHIELD_CONSTEXPR inline int iso_weekday_from_weekday(Weekday weekday) noexcept { - return static_cast((static_cast(weekday) + DAYS_PER_WEEK - 1) % DAYS_PER_WEEK) + 1; - } - - /// \brief Get ISO weekday for a calendar date. - /// \param year Year component. - /// \param month Month component. - /// \param day Day component. - /// \return ISO weekday number (1=Monday .. 7=Sunday). - template - TIME_SHIELD_CONSTEXPR inline int iso_weekday_of_date(Y year, M month, D day) { - return iso_weekday_from_weekday(day_of_week_date(year, month, day)); - } - - /// \brief Convert calendar date to ISO week date. - /// \param year Year component. - /// \param month Month component. - /// \param day Day component. - /// \return ISO week date representation. - template - inline IsoWeekDateStruct to_iso_week_date(Y year, M month, D day) { - const int iso_weekday = iso_weekday_of_date(year, month, day); - const dse_t unix_day = date_to_unix_day(year, month, day); - const dse_t thursday_day = unix_day + static_cast(4 - iso_weekday); - - const DateTimeStruct thursday_date = to_date_time(unix_day_to_ts(thursday_day)); - const year_t iso_year = thursday_date.year; - - const dse_t jan4_day = date_to_unix_day(iso_year, 1, 4); - const int jan4_iso_weekday = iso_weekday_of_date(iso_year, 1, 4); - const dse_t first_thursday = jan4_day + static_cast(4 - jan4_iso_weekday); - - const int32_t week = static_cast((thursday_day - first_thursday) / DAYS_PER_WEEK + 1); - return create_iso_week_date_struct(iso_year, week, static_cast(iso_weekday)); - } - - /// \brief Convert DateStruct to ISO week date. - /// \param date DateStruct instance. - /// \return ISO week date representation. - inline IsoWeekDateStruct to_iso_week_date(const DateStruct& date) { - return to_iso_week_date(date.year, date.mon, date.day); - } - - /// \brief Convert timestamp to ISO week date. - /// \tparam T Timestamp type. - /// \param ts Timestamp in seconds. - /// \return ISO week date representation. - template - inline IsoWeekDateStruct to_iso_week_date(T ts) { - const DateTimeStruct date_time = to_date_time(ts); - return to_iso_week_date(date_time.year, date_time.mon, date_time.day); - } - - /// \brief Calculate number of ISO weeks in a year. - /// \param iso_year ISO week-numbering year. - /// \return 52 or 53 depending on the ISO year length. - inline int iso_weeks_in_year(year_t iso_year) { - const IsoWeekDateStruct info = to_iso_week_date(iso_year, 12, 28); - return static_cast(info.week); - } - - /// \brief Validate ISO week date components. - /// \param iso_year ISO week-numbering year. - /// \param week ISO week number. - /// \param weekday ISO weekday (1-7). - /// \return True if components form a valid ISO week date. - inline bool is_valid_iso_week_date(year_t iso_year, int week, int weekday) { - if (iso_year < MIN_YEAR) return false; - if (iso_year > MAX_YEAR) return false; - if (weekday < 1 || weekday > 7) return false; - if (week < 1) return false; - const int max_week = iso_weeks_in_year(iso_year); - return week <= max_week; - } - - /// \brief Convert ISO week date to calendar date. - /// \param iso_date ISO week date structure. - /// \return Calendar date corresponding to the ISO week date. - /// \throws std::invalid_argument if the ISO week date is invalid. - inline DateStruct iso_week_date_to_date(const IsoWeekDateStruct& iso_date) { - if (!is_valid_iso_week_date(iso_date.year, iso_date.week, iso_date.weekday)) { - throw std::invalid_argument("Invalid ISO week date"); - } - - const dse_t jan4_day = date_to_unix_day(iso_date.year, 1, 4); - const int jan4_iso_weekday = iso_weekday_of_date(iso_date.year, 1, 4); - const dse_t first_thursday = jan4_day + static_cast(4 - jan4_iso_weekday); - const dse_t target_thursday = first_thursday + static_cast(iso_date.week - 1) * DAYS_PER_WEEK; - const dse_t target_day = target_thursday + static_cast(iso_date.weekday - 4); - - const DateTimeStruct date_time = to_date_time(unix_day_to_ts(target_day)); - return create_date_struct(date_time.year, date_time.mon, date_time.day); - } - - /// \brief Format ISO week date to string. - /// \param iso_date ISO week date to format. - /// \param extended When true, uses extended format with separators ("YYYY-Www-D"). - /// \param include_weekday When false, omits weekday ("YYYY-Www") and ignores the weekday field. - /// \return Formatted ISO week-date string. - inline std::string format_iso_week_date(const IsoWeekDateStruct& iso_date, bool extended = true, bool include_weekday = true) { - const bool has_valid_year = iso_date.year >= MIN_YEAR && iso_date.year <= MAX_YEAR; - const bool has_valid_week = has_valid_year && iso_date.week >= 1 && iso_date.week <= iso_weeks_in_year(iso_date.year); - const bool has_valid_weekday = iso_date.weekday >= 1 && iso_date.weekday <= 7; - if (!has_valid_year || !has_valid_week || (include_weekday && !has_valid_weekday)) { - throw std::invalid_argument("Invalid ISO week date"); - } - - if (!include_weekday) { - const char* fmt = extended ? "%" PRId64 "-W%.2d" : "%" PRId64 "W%.2d"; - char buffer[32] = {0}; - std::snprintf(buffer, sizeof(buffer), fmt, iso_date.year, iso_date.week); - return std::string(buffer); - } - - const char* fmt = extended ? "%" PRId64 "-W%.2d-%d" : "%" PRId64 "W%.2d%d"; - char buffer[32] = {0}; - std::snprintf(buffer, sizeof(buffer), fmt, iso_date.year, iso_date.week, iso_date.weekday); - return std::string(buffer); - } - - /// \brief Parse ISO week date string buffer. - /// \param input Pointer to character buffer (may be not null-terminated). - /// \param length Length of the buffer. - /// \param iso_date Output ISO week date structure. - /// \return True if parsing succeeded and produced a valid ISO week date; otherwise false. - /// \details Accepted forms include canonical `YYYY-Www-D` and `YYYYWwwD`, - /// compatible mixed separator variants `YYYY-WwwD` and `YYYYWww-D`, - /// uppercase or lowercase `W`, and omitted weekday with Monday default. - inline bool parse_iso_week_date(const char* input, std::size_t length, IsoWeekDateStruct& iso_date) noexcept { - if (input == nullptr) { - return false; - } - - iso_date = create_iso_week_date_struct(0, 0, 0); - - const char* p = input; - const char* const end = input + length; - - bool negative = false; - if (p < end && (*p == '+' || *p == '-')) { - negative = (*p == '-'); - ++p; - } - - const char* start_digits = p; - int64_t value = 0; - while (p < end && std::isdigit(static_cast(*p)) != 0) { - value = value * 10 + static_cast(*p - '0'); - ++p; - } - - if (p == start_digits) return false; - iso_date.year = negative ? -value : value; - - if (p >= end) return false; - - const bool has_dash_after_year = (*p == '-'); - if (has_dash_after_year) { - ++p; - if (p >= end) return false; - } - - if (*p != 'W' && *p != 'w') return false; - ++p; - - int week = 0; - for (int i = 0; i < 2; ++i) { - if (p >= end || std::isdigit(static_cast(*p)) == 0) return false; - week = week * 10 + (*p - '0'); - ++p; - } - - if (week == 0) return false; - - bool has_weekday = false; - if (p < end) { - if ((*p == '-' && has_dash_after_year) || (!has_dash_after_year && std::isdigit(static_cast(*p)) == 0)) { - if (*p == '-') ++p; - if (p >= end) return false; - if (std::isdigit(static_cast(*p)) == 0) return false; - iso_date.weekday = *p - '0'; - ++p; - has_weekday = true; - } else if (std::isdigit(static_cast(*p)) != 0) { - iso_date.weekday = *p - '0'; - ++p; - has_weekday = true; - } - } - - if (!has_weekday) { - iso_date.weekday = 1; - } - - iso_date.week = week; - - if (p != end) return false; - return is_valid_iso_week_date(iso_date.year, iso_date.week, iso_date.weekday); - } - - /// \brief Parse ISO week date string. - /// \param input Input string containing ISO week date. - /// \param iso_date Output ISO week date structure. - /// \return True if parsing succeeded and produced a valid ISO week date; otherwise false. - /// \details Parser accepts canonical and compatible mixed separator variants, - /// uppercase or lowercase `W`, and Monday default when weekday is omitted. - inline bool parse_iso_week_date(const std::string& input, IsoWeekDateStruct& iso_date) noexcept { - return parse_iso_week_date(input.c_str(), input.size(), iso_date); - } - - /// \brief Alias for parse_iso_week_date. - /// \param input Pointer to character buffer (may be not null-terminated). - /// \param length Length of the buffer. - /// \param iso_date Output ISO week date structure. - /// \return True if parsing succeeded and produced a valid ISO week date; otherwise false. - inline bool try_parse_iso_week_date(const char* input, std::size_t length, IsoWeekDateStruct& iso_date) noexcept { - return parse_iso_week_date(input, length, iso_date); - } - - /// \brief Alias for parse_iso_week_date, std::string overload. - /// \param input Input string containing ISO week date. - /// \param iso_date Output ISO week date structure. - /// \return True if parsing succeeded and produced a valid ISO week date; otherwise false. - inline bool try_parse_iso_week_date(const std::string& input, IsoWeekDateStruct& iso_date) noexcept { - return parse_iso_week_date(input, iso_date); - } - -/// \} - -}; // namespace time_shield - -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_ISO_WEEK_CONVERSIONS_HPP_INCLUDED +#endif // TIME_SHIELD_HEADER_ISO_WEEK_CONVERSIONS_HPP_INCLUDED diff --git a/include/time_shield/iso_week_struct.hpp b/include/time_shield/iso_week_struct.hpp index 0c61cd6c..46267c09 100644 --- a/include/time_shield/iso_week_struct.hpp +++ b/include/time_shield/iso_week_struct.hpp @@ -1,39 +1,8 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_ISO_WEEK_STRUCT_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_ISO_WEEK_STRUCT_HPP_INCLUDED +#ifndef TIME_SHIELD_HEADER_ISO_WEEK_STRUCT_HPP_INCLUDED +#define TIME_SHIELD_HEADER_ISO_WEEK_STRUCT_HPP_INCLUDED -/// \file iso_week_struct.hpp -/// \brief Header for ISO week date structure. -/// -/// This file defines the IsoWeekDateStruct structure used to represent ISO 8601 week dates. +#include -#include - -namespace time_shield { - - /// \ingroup time_structures - /// \brief Structure to represent an ISO week date. - struct IsoWeekDateStruct { - int64_t year; ///< ISO week-numbering year component. - int32_t week; ///< ISO week number component (1-52/53). - int32_t weekday; ///< ISO weekday component (1=Monday .. 7=Sunday). - }; - - /// \ingroup time_structures - /// \brief Creates an IsoWeekDateStruct instance. - /// \param year ISO week-numbering year component. - /// \param week ISO week number component. - /// \param weekday ISO weekday component (1=Monday .. 7=Sunday). - /// \return An IsoWeekDateStruct instance with the provided components. - inline const IsoWeekDateStruct create_iso_week_date_struct( - int64_t year, - int32_t week = 1, - int32_t weekday = 1) { - IsoWeekDateStruct data{year, week, weekday}; - return data; - } - -}; // namespace time_shield - -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_ISO_WEEK_STRUCT_HPP_INCLUDED +#endif // TIME_SHIELD_HEADER_ISO_WEEK_STRUCT_HPP_INCLUDED diff --git a/include/time_shield/julian_conversions.hpp b/include/time_shield/julian_conversions.hpp index 62f1a2b8..d261bb86 100644 --- a/include/time_shield/julian_conversions.hpp +++ b/include/time_shield/julian_conversions.hpp @@ -1,193 +1,8 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_JULIAN_CONVERSIONS_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_JULIAN_CONVERSIONS_HPP_INCLUDED +#ifndef TIME_SHIELD_HEADER_JULIAN_CONVERSIONS_HPP_INCLUDED +#define TIME_SHIELD_HEADER_JULIAN_CONVERSIONS_HPP_INCLUDED -/// \file julian_conversions.hpp -/// \brief Julian Date / MJD / JDN helpers using the proleptic Gregorian calendar. -/// \ingroup time_conversions -/// -/// JD epoch used here: -/// - Unix epoch (1970-01-01 00:00:00 UTC) is JD 2440587.5 -/// -/// Notes: -/// - JD and MJD are returned as double (jd_t/mjd_t). -/// - These functions are intended for utility/analytics, not for high-precision astronomy. +#include -#include "config.hpp" -#include "types.hpp" -#include "constants.hpp" -#include "validation.hpp" - -#include -#include - -namespace time_shield { - - namespace detail { - - inline jd_t gregorian_dmy_to_jd_unchecked(double day, int64_t month, int64_t year) noexcept { - if (month == 1 || month == 2) { - year -= 1; - month += 12; - } - const double a = std::floor(static_cast(year) / 100.0); - const double b = 2.0 - a + std::floor(a / 4.0); - const double jd = std::floor(365.25 * (static_cast(year) + 4716.0)) - + std::floor(30.6000001 * (static_cast(month) + 1.0)) - + day + b - 1524.5; - return static_cast(jd); - } - - inline jdn_t gregorian_dmy_to_jdn_unchecked(int64_t day, int64_t month, int64_t year) noexcept { - const int64_t a = (14LL - month) / 12LL; - const int64_t y = year + 4800LL - a; - const int64_t m = month + 12LL * a - 3LL; - const int64_t jdn = day - + (153LL * m + 2LL) / 5LL - + 365LL * y - + y / 4LL - - y / 100LL - + y / 400LL - - 32045LL; - return static_cast(jdn); - } - - inline double day_fraction_from_hms( - int hour, - int minute, - int second, - int millisecond) noexcept { - return (static_cast(hour) / 24.0) + - (static_cast(minute) / (24.0 * 60.0)) + - ((static_cast(second) + static_cast(millisecond) / 1000.0) - / static_cast(SEC_PER_DAY)); - } - - } // namespace detail - - /// \brief Convert Unix timestamp (floating seconds) to Julian Date (JD). - /// \param ts Unix timestamp in floating seconds since Unix epoch. - /// \return Julian Date value. - inline jd_t fts_to_jd(fts_t ts) noexcept { - return static_cast(2440587.5) - + static_cast(ts) / static_cast(SEC_PER_DAY); - } - - /// \brief Convert Unix timestamp (seconds) to Julian Date (JD). - /// \param ts Unix timestamp in seconds since Unix epoch. - /// \return Julian Date value. - inline jd_t ts_to_jd(ts_t ts) noexcept { - return fts_to_jd(static_cast(ts)); - } - - /// \brief Convert Gregorian date/time components to Julian Date (JD) using year-first order. - /// \param year Full year in the proleptic Gregorian calendar. - /// \param month Month [1..12]. - /// \param day Day of month [1..31]. - /// \param hour Hour of day [0..23]. - /// \param minute Minute of hour [0..59]. - /// \param second Second of minute [0..59]. - /// \param millisecond Millisecond of second [0..999]. - /// \return Julian Date value. - inline jd_t gregorian_ymd_to_jd( - year_t year, - int month, - int day, - int hour = 0, - int minute = 0, - int second = 0, - int millisecond = 0) noexcept { - return detail::gregorian_dmy_to_jd_unchecked( - static_cast(day) + detail::day_fraction_from_hms(hour, minute, second, millisecond), - static_cast(month), - static_cast(year)); - } - - /// \brief Convert Unix timestamp (floating seconds) to Modified Julian Date (MJD). - /// \param ts Unix timestamp in floating seconds since Unix epoch. - /// \return Modified Julian Date value. - inline mjd_t fts_to_mjd(fts_t ts) noexcept { - return static_cast(fts_to_jd(ts) - 2400000.5); - } - - /// \brief Convert Unix timestamp (seconds) to Modified Julian Date (MJD). - /// \param ts Unix timestamp in seconds since Unix epoch. - /// \return Modified Julian Date value. - inline mjd_t ts_to_mjd(ts_t ts) noexcept { - return static_cast(fts_to_mjd(static_cast(ts))); - } - - /// \brief Convert Gregorian date to Julian Day Number (JDN) using year-first order. - /// \details JDN is an integer day count with no fractional part. - /// \param year Full year in the proleptic Gregorian calendar. - /// \param month Month [1..12]. - /// \param day Day of month [1..31]. - /// \return Julian Day Number value. - inline jdn_t gregorian_ymd_to_jdn(year_t year, int month, int day) noexcept { - return detail::gregorian_dmy_to_jdn_unchecked( - static_cast(day), - static_cast(month), - static_cast(year)); - } - - /// \brief Try converting Gregorian date/time components to Julian Date (JD) using year-first order. - /// \param year Full year in the proleptic Gregorian calendar. - /// \param month Month [1..12]. - /// \param day Day of month [1..31]. - /// \param hour Hour of day [0..23]. - /// \param minute Minute of hour [0..59]. - /// \param second Second of minute [0..59]. - /// \param millisecond Millisecond of second [0..999]. - /// \param out Receives the Julian Date value on success. - /// \return True on success, false when date/time components are invalid. - inline bool try_gregorian_ymd_to_jd( - year_t year, - int month, - int day, - int hour, - int minute, - int second, - int millisecond, - jd_t& out) noexcept { - if (!is_valid_date(year, month, day) || !is_valid_time(hour, minute, second, millisecond)) { - return false; - } - out = gregorian_ymd_to_jd(year, month, day, hour, minute, second, millisecond); - return true; - } - - /// \brief Try converting Gregorian date to Julian Day Number (JDN) using year-first order. - /// \param year Full year in the proleptic Gregorian calendar. - /// \param month Month [1..12]. - /// \param day Day of month [1..31]. - /// \param out Receives the Julian Day Number value on success. - /// \return True on success, false when the date is invalid or produces a negative JDN. - inline bool try_gregorian_ymd_to_jdn( - year_t year, - int month, - int day, - jdn_t& out) noexcept { - if (!is_valid_date(year, month, day)) { - return false; - } - const int64_t a = (14LL - static_cast(month)) / 12LL; - const int64_t y = static_cast(year) + 4800LL - a; - const int64_t m = static_cast(month) + 12LL * a - 3LL; - const int64_t jdn = static_cast(day) - + (153LL * m + 2LL) / 5LL - + 365LL * y - + y / 4LL - - y / 100LL - + y / 400LL - - 32045LL; - if (jdn < 0) { - return false; - } - out = static_cast(jdn); - return true; - } - -} // namespace time_shield - -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_JULIAN_CONVERSIONS_HPP_INCLUDED +#endif // TIME_SHIELD_HEADER_JULIAN_CONVERSIONS_HPP_INCLUDED diff --git a/include/time_shield/legacy_aliases.hpp b/include/time_shield/legacy_aliases.hpp index f8361110..447d7312 100644 --- a/include/time_shield/legacy_aliases.hpp +++ b/include/time_shield/legacy_aliases.hpp @@ -1,171 +1,9 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_LEGACY_ALIASES_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_LEGACY_ALIASES_HPP_INCLUDED +#ifndef TIME_SHIELD_HEADER_LEGACY_ALIASES_HPP_INCLUDED +#define TIME_SHIELD_HEADER_LEGACY_ALIASES_HPP_INCLUDED -/// \file legacy_aliases.hpp -/// \brief Opt-in compatibility aliases for renamed time-conversion helpers. -/// -/// Define `TIME_SHIELD_ENABLE_LEGACY_ALIASES` before including this header or -/// `time_conversions.hpp` to make the aliases available. +#include +#include -#include "time_conversions.hpp" - -#include - -#if defined(TIME_SHIELD_ENABLE_LEGACY_ALIASES) - -namespace time_shield { - -/// \ingroup time_conversions -/// \{ - - /// \brief Legacy alias for years_since_epoch. - /// \copydoc years_since_epoch - template - TIME_SHIELD_CONSTEXPR T get_unix_year(ts_t ts) noexcept { - return years_since_epoch(ts); - } - - /// \brief Legacy alias for days_since_epoch. - /// \copydoc days_since_epoch - template - TIME_SHIELD_CONSTEXPR T get_unix_day(ts_t ts = time_shield::ts()) noexcept { - return days_since_epoch(ts); - } - - /// \brief Legacy alias for days_since_epoch_ms. - /// \copydoc days_since_epoch_ms - template - TIME_SHIELD_CONSTEXPR T get_unix_day_ms(ts_ms_t ts_ms = time_shield::ts_ms()) noexcept { - return days_since_epoch_ms(ts_ms); - } - - /// \brief Legacy alias for unix_day_to_ts. - /// \copydoc unix_day_to_ts - template - TIME_SHIELD_CONSTEXPR T unix_day_to_timestamp(dse_t unix_day) noexcept { - return unix_day_to_ts(unix_day); - } - - /// \brief Legacy alias for unix_day_to_ts_ms. - /// \copydoc unix_day_to_ts_ms - template - TIME_SHIELD_CONSTEXPR T unix_day_to_timestamp_ms(dse_t unix_day) noexcept { - return unix_day_to_ts_ms(unix_day); - } - - /// \brief Legacy alias for min_since_epoch. - /// \copydoc min_since_epoch - template - TIME_SHIELD_CONSTEXPR T get_unix_min(ts_t ts = time_shield::ts()) { - return min_since_epoch(ts); - } - - /// \brief Legacy alias for year_of. - /// \copydoc year_of - template - TIME_SHIELD_CONSTEXPR T get_year(ts_t ts = time_shield::ts()) { - return year_of(ts); - } - - /// \brief Legacy alias for year_of_ms. - /// \copydoc year_of_ms - template - TIME_SHIELD_CONSTEXPR T get_year_ms(ts_ms_t ts_ms = time_shield::ts_ms()) { - return year_of_ms(ts_ms); - } - - /// \brief Legacy alias for weekday_of_date. - /// \copydoc weekday_of_date - template::value, int>::type = 0> - TIME_SHIELD_CONSTEXPR T1 get_weekday_from_date(const T2& date) { - return weekday_of_date(date); - } - - /// \brief Legacy alias for weekday_of_ts. - /// \copydoc weekday_of_ts - template::value, int>::type = 0> - TIME_SHIELD_CONSTEXPR T get_weekday_from_ts(U ts) noexcept { - return weekday_of_ts(ts); - } - - /// \brief Legacy alias for weekday_of_ts_ms. - /// \copydoc weekday_of_ts_ms - template - TIME_SHIELD_CONSTEXPR T get_weekday_from_ts_ms(ts_ms_t ts_ms) { - return weekday_of_ts_ms(ts_ms); - } - - /// \brief Legacy alias for start_of_next_day_from_unix_day. - /// \copydoc start_of_next_day_from_unix_day - template - TIME_SHIELD_CONSTEXPR T next_day_unix_day(dse_t unix_day) noexcept { - return start_of_next_day_from_unix_day(unix_day); - } - - /// \brief Legacy alias for start_of_next_day_from_unix_day. - /// \copydoc start_of_next_day_from_unix_day - template - TIME_SHIELD_CONSTEXPR T next_day_unixday(dse_t unix_day) noexcept { - return start_of_next_day_from_unix_day(unix_day); - } - - /// \brief Legacy alias for start_of_next_day_from_unix_day_ms. - /// \copydoc start_of_next_day_from_unix_day_ms - template - TIME_SHIELD_CONSTEXPR T next_day_unix_day_ms(dse_t unix_day) noexcept { - return start_of_next_day_from_unix_day_ms(unix_day); - } - - /// \brief Legacy alias for start_of_next_day_from_unix_day_ms. - /// \copydoc start_of_next_day_from_unix_day_ms - template - TIME_SHIELD_CONSTEXPR T next_day_unixday_ms(dse_t unix_day) noexcept { - return start_of_next_day_from_unix_day_ms(unix_day); - } - - /// \brief Legacy day-first Gregorian Julian Date conversion. - /// \details Use gregorian_ymd_to_jd for the preferred year-first API. - inline jd_t gregorian_to_jd(double day, int64_t month, int64_t year) noexcept { - return detail::gregorian_dmy_to_jd_unchecked(day, month, year); - } - - /// \brief Legacy day-first Gregorian Julian Date conversion. - /// \details Use gregorian_ymd_to_jd for the preferred year-first API. - inline jd_t gregorian_to_jd( - uint32_t day, - uint32_t month, - uint32_t year, - uint32_t hour, - uint32_t minute, - uint32_t second = 0, - uint32_t millisecond = 0) noexcept { - return detail::gregorian_dmy_to_jd_unchecked( - static_cast(day) + detail::day_fraction_from_hms( - static_cast(hour), - static_cast(minute), - static_cast(second), - static_cast(millisecond)), - static_cast(month), - static_cast(year)); - } - - /// \brief Legacy day-first Gregorian Julian Day Number conversion. - /// \details Use gregorian_ymd_to_jdn for the preferred year-first API. - inline jdn_t gregorian_to_jdn(uint32_t day, uint32_t month, uint32_t year) noexcept { - return detail::gregorian_dmy_to_jdn_unchecked( - static_cast(day), - static_cast(month), - static_cast(year)); - } - -/// \} - -} // namespace time_shield - -#endif // defined(TIME_SHIELD_ENABLE_LEGACY_ALIASES) - -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_LEGACY_ALIASES_HPP_INCLUDED +#endif // TIME_SHIELD_HEADER_LEGACY_ALIASES_HPP_INCLUDED diff --git a/include/time_shield/ntp.hpp b/include/time_shield/ntp.hpp new file mode 100644 index 00000000..9677b423 --- /dev/null +++ b/include/time_shield/ntp.hpp @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: MIT +#pragma once +#ifndef TIME_SHIELD_HEADER_NTP_HPP_INCLUDED +#define TIME_SHIELD_HEADER_NTP_HPP_INCLUDED + +#include + +#if TIME_SHIELD_ENABLE_NTP_CLIENT +# include +# include +# include +# include +#endif + +#endif // TIME_SHIELD_HEADER_NTP_HPP_INCLUDED diff --git a/include/time_shield/ntp/detail/ntp_client_core.hpp b/include/time_shield/ntp/detail/ntp_client_core.hpp new file mode 100644 index 00000000..547566a6 --- /dev/null +++ b/include/time_shield/ntp/detail/ntp_client_core.hpp @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: MIT +#pragma once +#ifndef TIME_SHIELD_HEADER_NTP_DETAIL_NTP_CLIENT_CORE_HPP_INCLUDED +#define TIME_SHIELD_HEADER_NTP_DETAIL_NTP_CLIENT_CORE_HPP_INCLUDED + +#include "ntp_packet.hpp" +#include "udp_transport.hpp" + +#include +#include + +namespace time_shield { +namespace detail { + + /// \brief Core NTP query logic that parses packets and computes offsets. + class NtpClientCore { + public: + /// \brief Perform one NTP transaction using a UDP transport. + bool query(IUdpTransport& transport, + const std::string& host, + int port, + int timeout_ms, + int& out_error_code, + int64_t& out_offset_us, + int64_t& out_delay_us, + int& out_stratum) noexcept { + out_error_code = 0; + out_offset_us = 0; + out_delay_us = 0; + out_stratum = -1; + + uint64_t now_us = 0; + if (!get_now_us(now_us)) { + out_error_code = -1; + return false; + } + + NtpPacket pkt{}; + fill_client_packet(pkt, now_us); + + NtpPacket reply{}; + UdpRequest req; + req.host = host; + req.port = port; + req.send_data = &pkt; + req.send_size = sizeof(pkt); + req.recv_data = &reply; + req.recv_size = sizeof(reply); + req.timeout_ms = timeout_ms; + + if (!transport.transact(req, out_error_code)) { + if (out_error_code == 0) { + out_error_code = -1; + } + return false; + } + + uint64_t arrival_us = 0; + if (!get_now_us(arrival_us)) { + out_error_code = -1; + return false; + } + + if (!parse_server_packet(reply, arrival_us, out_offset_us, out_delay_us, out_stratum, out_error_code)) { + if (out_error_code == 0) { + out_error_code = -1; + } + return false; + } + + return true; + } + + private: + /// \brief Read current realtime clock in microseconds. + static bool get_now_us(uint64_t& out) noexcept { + const int64_t v = time_shield::now_realtime_us(); + if (v < 0) return false; + out = static_cast(v); + return true; + } + }; + +} // namespace detail +} // namespace time_shield + +#endif // TIME_SHIELD_HEADER_NTP_DETAIL_NTP_CLIENT_CORE_HPP_INCLUDED diff --git a/include/time_shield/ntp/detail/ntp_packet.hpp b/include/time_shield/ntp/detail/ntp_packet.hpp new file mode 100644 index 00000000..fc69c5f8 --- /dev/null +++ b/include/time_shield/ntp/detail/ntp_packet.hpp @@ -0,0 +1,166 @@ +// SPDX-License-Identifier: MIT +#pragma once +#ifndef TIME_SHIELD_HEADER_NTP_DETAIL_NTP_PACKET_HPP_INCLUDED +#define TIME_SHIELD_HEADER_NTP_DETAIL_NTP_PACKET_HPP_INCLUDED + +#include +#include + +#if TIME_SHIELD_PLATFORM_WINDOWS +# include +#else +# include +#endif + +namespace time_shield { +namespace detail { + + /// \ingroup ntp + /// \brief NTP packet layout (48 bytes). + struct NtpPacket { + uint8_t li_vn_mode; + uint8_t stratum; + uint8_t poll; + uint8_t precision; + uint32_t root_delay; + uint32_t root_dispersion; + uint32_t ref_id; + uint32_t ref_ts_sec; + uint32_t ref_ts_frac; + uint32_t orig_ts_sec; + uint32_t orig_ts_frac; + uint32_t recv_ts_sec; + uint32_t recv_ts_frac; + uint32_t tx_ts_sec; + uint32_t tx_ts_frac; + }; + + static_assert(sizeof(NtpPacket) == 48, "NtpPacket must be 48 bytes"); + + /// \ingroup ntp + /// \brief Protocol-level error codes for NTP parsing. + enum NtpProtoError { + NTP_EPROTO_BASE = -10000, + NTP_E_BAD_MODE = NTP_EPROTO_BASE - 1, + NTP_E_BAD_VERSION = NTP_EPROTO_BASE - 2, + NTP_E_BAD_LI = NTP_EPROTO_BASE - 3, + NTP_E_BAD_STRATUM = NTP_EPROTO_BASE - 4, + NTP_E_KOD = NTP_EPROTO_BASE - 5, + NTP_E_BAD_TS = NTP_EPROTO_BASE - 6 + }; + + /// \brief Extract leap indicator from LI/VN/Mode field. + static inline uint8_t ntp_li(uint8_t li_vn_mode) noexcept { + return static_cast((li_vn_mode >> 6) & 0x03); + } + + /// \brief Extract version number from LI/VN/Mode field. + static inline uint8_t ntp_vn(uint8_t li_vn_mode) noexcept { + return static_cast((li_vn_mode >> 3) & 0x07); + } + + /// \brief Extract mode from LI/VN/Mode field. + static inline uint8_t ntp_mode(uint8_t li_vn_mode) noexcept { + return static_cast(li_vn_mode & 0x07); + } + + /// \brief Convert NTP fractional seconds to microseconds. + static inline uint64_t ntp_frac_to_us(uint32_t frac_net) noexcept { + const uint64_t frac = static_cast(ntohl(frac_net)); + return (frac * 1000000ULL) >> 32; + } + + /// \brief Convert NTP timestamp parts to Unix microseconds. + static inline bool ntp_ts_to_unix_us(uint32_t sec_net, uint32_t frac_net, uint64_t& out_us) noexcept { + static const int64_t NTP_TIMESTAMP_DELTA = 2208988800ll; + const int64_t sec = static_cast(ntohl(sec_net)) - NTP_TIMESTAMP_DELTA; + if (sec < 0) return false; + out_us = static_cast(sec) * 1000000ULL + ntp_frac_to_us(frac_net); + return true; + } + + /// \brief Fill an NTP client request packet using local time. + static inline void fill_client_packet(NtpPacket& pkt, uint64_t now_us) { + std::memset(&pkt, 0, sizeof(pkt)); + pkt.li_vn_mode = static_cast((0 << 6) | (3 << 3) | 3); // LI=0, VN=3, Mode=3 + + const uint64_t sec = now_us / 1000000 + 2208988800ULL; + const uint64_t frac = ((now_us % 1000000) * 0x100000000ULL) / 1000000; + + pkt.tx_ts_sec = htonl(static_cast(sec)); + pkt.tx_ts_frac = htonl(static_cast(frac)); + } + + /// \brief Parse server response and compute offset and delay. + static inline bool parse_server_packet(const NtpPacket& pkt, + uint64_t arrival_us, + int64_t& offset_us, + int64_t& delay_us, + int& stratum, + int& out_error_code) noexcept { + const uint8_t li = ntp_li(pkt.li_vn_mode); + const uint8_t vn = ntp_vn(pkt.li_vn_mode); + const uint8_t mode = ntp_mode(pkt.li_vn_mode); + + if (mode != 4) { + out_error_code = NTP_E_BAD_MODE; + return false; + } + if (vn < 3 || vn > 4) { + out_error_code = NTP_E_BAD_VERSION; + return false; + } + if (li == 3) { + out_error_code = NTP_E_BAD_LI; + return false; + } + if (pkt.stratum == 0) { + out_error_code = NTP_E_KOD; + return false; + } + if (pkt.stratum >= 16) { + out_error_code = NTP_E_BAD_STRATUM; + return false; + } + + uint64_t originate_us = 0; + uint64_t receive_us = 0; + uint64_t transmit_us = 0; + + if (!ntp_ts_to_unix_us(pkt.orig_ts_sec, pkt.orig_ts_frac, originate_us)) { + out_error_code = NTP_E_BAD_TS; + return false; + } + if (!ntp_ts_to_unix_us(pkt.recv_ts_sec, pkt.recv_ts_frac, receive_us)) { + out_error_code = NTP_E_BAD_TS; + return false; + } + if (!ntp_ts_to_unix_us(pkt.tx_ts_sec, pkt.tx_ts_frac, transmit_us)) { + out_error_code = NTP_E_BAD_TS; + return false; + } + + const int64_t t1 = static_cast(originate_us); + const int64_t t2 = static_cast(receive_us); + const int64_t t3 = static_cast(transmit_us); + const int64_t t4 = static_cast(arrival_us); + + if (t3 < t2) { + out_error_code = NTP_E_BAD_TS; + return false; + } + + offset_us = ((t2 - t1) + (t3 - t4)) / 2; + delay_us = (t4 - t1) - (t3 - t2); + if (delay_us < 0) { + out_error_code = NTP_E_BAD_TS; + return false; + } + stratum = pkt.stratum; + return true; + } + +} // namespace detail +} // namespace time_shield + +#endif // TIME_SHIELD_HEADER_NTP_DETAIL_NTP_PACKET_HPP_INCLUDED diff --git a/include/time_shield/ntp/detail/udp_transport.hpp b/include/time_shield/ntp/detail/udp_transport.hpp new file mode 100644 index 00000000..fbb6792e --- /dev/null +++ b/include/time_shield/ntp/detail/udp_transport.hpp @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: MIT +#pragma once +#ifndef TIME_SHIELD_HEADER_NTP_DETAIL_UDP_TRANSPORT_HPP_INCLUDED +#define TIME_SHIELD_HEADER_NTP_DETAIL_UDP_TRANSPORT_HPP_INCLUDED + +#include +#include + +namespace time_shield { +namespace detail { + + /// \brief UDP request parameters for NTP transactions. + struct UdpRequest { + std::string host; ///< Target host name or IP address. + int port = 123; ///< Target port. + const void* send_data = nullptr; ///< Pointer to outgoing payload. + std::size_t send_size = 0; ///< Outgoing payload size in bytes. + void* recv_data = nullptr; ///< Pointer to receive buffer. + std::size_t recv_size = 0; ///< Receive buffer size in bytes. + int timeout_ms = 5000; ///< Receive timeout in milliseconds. + }; + + /// \brief Abstract UDP transport interface for NTP queries. + class IUdpTransport { + public: + /// \brief Virtual destructor. + virtual ~IUdpTransport() {} + /// \brief Send request and receive response over UDP. + virtual bool transact(const UdpRequest& req, int& out_error_code) noexcept = 0; + }; + +} // namespace detail +} // namespace time_shield + +#endif // TIME_SHIELD_HEADER_NTP_DETAIL_UDP_TRANSPORT_HPP_INCLUDED diff --git a/include/time_shield/ntp/detail/udp_transport_posix.hpp b/include/time_shield/ntp/detail/udp_transport_posix.hpp new file mode 100644 index 00000000..b3548680 --- /dev/null +++ b/include/time_shield/ntp/detail/udp_transport_posix.hpp @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: MIT +#pragma once +#ifndef TIME_SHIELD_HEADER_NTP_DETAIL_UDP_TRANSPORT_POSIX_HPP_INCLUDED +#define TIME_SHIELD_HEADER_NTP_DETAIL_UDP_TRANSPORT_POSIX_HPP_INCLUDED + +#if TIME_SHIELD_PLATFORM_UNIX + +#include "udp_transport.hpp" + +#include +#include +#include +#include +#include +#include +#include + +namespace time_shield { +namespace detail { + + /// \brief POSIX UDP transport for NTP queries. + class UdpTransportPosix : public IUdpTransport { + public: + /// \brief Send request and receive response over UDP. + bool transact(const UdpRequest& req, int& out_error_code) noexcept override { + out_error_code = 0; + const int sock = ::socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (sock < 0) { + out_error_code = errno; + return false; + } + + addrinfo hints{}, *res = nullptr; + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_DGRAM; + hints.ai_protocol = IPPROTO_UDP; + + const int resolve_code = getaddrinfo(req.host.c_str(), nullptr, &hints, &res); + if (resolve_code != 0 || !res) { + out_error_code = (resolve_code != 0) ? resolve_code : errno; + ::close(sock); + return false; + } + + sockaddr_in addr{}; + addr.sin_family = AF_INET; + addr.sin_port = htons(static_cast(req.port)); + addr.sin_addr = reinterpret_cast(res->ai_addr)->sin_addr; + freeaddrinfo(res); + res = nullptr; + + const int timeout_ms = req.timeout_ms > 0 ? req.timeout_ms : 5000; + timeval tv; + tv.tv_sec = timeout_ms / 1000; + tv.tv_usec = (timeout_ms % 1000) * 1000; + ::setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); + + const ssize_t sent = ::sendto(sock, + req.send_data, + req.send_size, + 0, + reinterpret_cast(&addr), + sizeof(addr)); + if (sent < 0 || static_cast(sent) != req.send_size) { + out_error_code = errno; + ::close(sock); + return false; + } + + sockaddr_in from{}; + socklen_t from_len = sizeof(from); + const ssize_t received = ::recvfrom(sock, + req.recv_data, + req.recv_size, + 0, + reinterpret_cast(&from), + &from_len); + + if (received < 0 || static_cast(received) != req.recv_size) { + out_error_code = errno; + ::close(sock); + return false; + } + + ::close(sock); + return true; + } + }; + +} // namespace detail +} // namespace time_shield + +#endif // _TIME_SHIELD_PLATFORM_UNIX + +#endif // TIME_SHIELD_HEADER_NTP_DETAIL_UDP_TRANSPORT_POSIX_HPP_INCLUDED diff --git a/include/time_shield/ntp/detail/udp_transport_win.hpp b/include/time_shield/ntp/detail/udp_transport_win.hpp new file mode 100644 index 00000000..dac0ad89 --- /dev/null +++ b/include/time_shield/ntp/detail/udp_transport_win.hpp @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: MIT +#pragma once +#ifndef TIME_SHIELD_HEADER_NTP_DETAIL_UDP_TRANSPORT_WIN_HPP_INCLUDED +#define TIME_SHIELD_HEADER_NTP_DETAIL_UDP_TRANSPORT_WIN_HPP_INCLUDED + +#if TIME_SHIELD_PLATFORM_WINDOWS + +#include "wsa_guard.hpp" +#include "udp_transport.hpp" + +#include +#include +#include + +namespace time_shield { +namespace detail { + + /// \brief Windows UDP transport for NTP queries. + class UdpTransportWin : public IUdpTransport { + public: + /// \brief Send request and receive response over UDP. + bool transact(const UdpRequest& req, int& out_error_code) noexcept override { + out_error_code = 0; + if (!WsaGuard::instance().success()) { + out_error_code = WsaGuard::instance().ret_code(); + return false; + } + + SOCKET sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (sock == INVALID_SOCKET) { + out_error_code = WSAGetLastError(); + return false; + } + + sockaddr_in addr{}; + addr.sin_family = AF_INET; + addr.sin_port = htons(static_cast(req.port)); + + addrinfo hints{}, *res = nullptr; + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_DGRAM; + hints.ai_protocol = IPPROTO_UDP; + + if (getaddrinfo(req.host.c_str(), nullptr, &hints, &res) != 0 || !res) { + out_error_code = WSAGetLastError(); + closesocket(sock); + return false; + } + addr.sin_addr = reinterpret_cast(res->ai_addr)->sin_addr; + + const int timeout_ms = req.timeout_ms > 0 ? req.timeout_ms : 5000; + DWORD timeout = static_cast(timeout_ms); + setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast(&timeout), sizeof(timeout)); + + const int send_res = sendto(sock, + static_cast(req.send_data), + static_cast(req.send_size), + 0, + reinterpret_cast(&addr), + sizeof(addr)); + if (send_res == SOCKET_ERROR || static_cast(send_res) != req.send_size) { + out_error_code = WSAGetLastError(); + freeaddrinfo(res); + closesocket(sock); + return false; + } + + sockaddr_in from{}; + int from_len = sizeof(from); + const int recv_res = recvfrom(sock, + static_cast(req.recv_data), + static_cast(req.recv_size), + 0, + reinterpret_cast(&from), + &from_len); + + if (recv_res == SOCKET_ERROR || static_cast(recv_res) != req.recv_size) { + out_error_code = WSAGetLastError(); + freeaddrinfo(res); + closesocket(sock); + return false; + } + + freeaddrinfo(res); + closesocket(sock); + return true; + } + }; + +} // namespace detail +} // namespace time_shield + +#endif // _TIME_SHIELD_PLATFORM_WINDOWS + +#endif // TIME_SHIELD_HEADER_NTP_DETAIL_UDP_TRANSPORT_WIN_HPP_INCLUDED diff --git a/include/time_shield/ntp/detail/wsa_guard.hpp b/include/time_shield/ntp/detail/wsa_guard.hpp new file mode 100644 index 00000000..54b19029 --- /dev/null +++ b/include/time_shield/ntp/detail/wsa_guard.hpp @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: MIT +#pragma once +#ifndef TIME_SHIELD_HEADER_NTP_DETAIL_WSA_GUARD_HPP_INCLUDED +#define TIME_SHIELD_HEADER_NTP_DETAIL_WSA_GUARD_HPP_INCLUDED + +/// \file wsa_guard.hpp +/// \brief Singleton guard for WinSock initialization. +/// \ingroup ntp + +#if TIME_SHIELD_HAS_WINSOCK +# ifndef WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +# endif +# ifndef NOMINMAX +# define NOMINMAX +# endif +# include // Must be included before windows.h +# include +# include // Optional, but safe if later needed +#else +# error "WsaGuard requires WinSock support" +#endif +#include +#include + +namespace time_shield { + + /// \ingroup ntp + /// \brief Singleton guard for WinSock initialization. + class WsaGuard { + public: + /// \brief Returns the singleton instance, initializing WSA if needed. + static const WsaGuard& instance() { + static WsaGuard instance; + return instance; + } + + /// \brief Returns whether WSAStartup was successful. + bool success() const noexcept { + return m_ret_code == 0; + } + + /// \brief Returns the result code from WSAStartup. + int ret_code() const noexcept { + return m_ret_code; + } + + /// \brief Returns the WSAData structure (valid only if successful). + const WSADATA& data() const noexcept { + return m_wsa_data; + } + + private: + WsaGuard() { + m_ret_code = WSAStartup(MAKEWORD(2, 2), &m_wsa_data); + } + + ~WsaGuard() = default; + + WsaGuard(const WsaGuard&) = delete; + WsaGuard& operator=(const WsaGuard&) = delete; + + WSADATA m_wsa_data{}; + int m_ret_code = -1; + }; + +} // namespace time_shield + +#endif // TIME_SHIELD_HEADER_NTP_DETAIL_WSA_GUARD_HPP_INCLUDED diff --git a/include/time_shield/ntp/ntp_client.hpp b/include/time_shield/ntp/ntp_client.hpp new file mode 100644 index 00000000..d1c893b5 --- /dev/null +++ b/include/time_shield/ntp/ntp_client.hpp @@ -0,0 +1,179 @@ +// SPDX-License-Identifier: MIT +#pragma once +#ifndef TIME_SHIELD_HEADER_NTP_NTP_CLIENT_HPP_INCLUDED +#define TIME_SHIELD_HEADER_NTP_NTP_CLIENT_HPP_INCLUDED + +/// \file ntp_client.hpp +/// \brief Simple NTP client for querying time offset from NTP servers. +/// +/// Feature availability is controlled by `TIME_SHIELD_ENABLE_NTP_CLIENT`. +/// \ingroup ntp + +#include + +#if TIME_SHIELD_ENABLE_NTP_CLIENT + +#include "detail/ntp_client_core.hpp" +#include "detail/ntp_packet.hpp" +#include "detail/udp_transport.hpp" + +#if TIME_SHIELD_PLATFORM_WINDOWS +# include "detail/udp_transport_win.hpp" +#elif TIME_SHIELD_PLATFORM_UNIX +# include "detail/udp_transport_posix.hpp" +#endif + +#include +#include +#include + +namespace time_shield { + +#if TIME_SHIELD_PLATFORM_WINDOWS + namespace detail { using PlatformUdpTransport = UdpTransportWin; } +#elif TIME_SHIELD_PLATFORM_UNIX + namespace detail { using PlatformUdpTransport = UdpTransportPosix; } +#endif + +#if TIME_SHIELD_PLATFORM_WINDOWS || TIME_SHIELD_PLATFORM_UNIX + + /// \ingroup ntp + /// \brief NTP client for measuring time offset. + class NtpClient { + public: + /// \brief Constructs NTP client with specified host and port. + /// \param server NTP server host name. + /// \param port NTP server port. + NtpClient(std::string server = "pool.ntp.org", int port = 123) + : m_host(std::move(server)) + , m_port(port) + , m_offset_us(0) + , m_delay_us(0) + , m_stratum(-1) + , m_is_success(false) { + now_realtime_us(); + } + + /// \brief Queries the NTP server and updates the local offset. + /// \return True when response parsed successfully. + /// \note Requires network connectivity and a reachable server. + bool query() { + last_error_code_slot() = 0; + +#if TIME_SHIELD_PLATFORM_WINDOWS + if (!WsaGuard::instance().success()) { + last_error_code_slot() = WsaGuard::instance().ret_code(); + m_is_success = false; + return false; + } +#endif + + detail::PlatformUdpTransport transport; + detail::NtpClientCore core; + + int error_code = 0; + int64_t offset = 0; + int64_t delay = 0; + int stratum = -1; + + const bool ok = core.query( + transport, + m_host, + m_port, + k_default_timeout_ms, + error_code, + offset, + delay, + stratum + ); + + last_error_code_slot() = error_code; + + if (!ok) { + m_delay_us = 0; + m_stratum = -1; + m_is_success = false; + return false; + } + + m_offset_us = offset; + m_delay_us = delay; + m_stratum = stratum; + m_is_success = true; + return true; + } + + /// \brief Returns whether the last NTP query was successful. + /// \return True when the last query updated internal state. + bool success() const noexcept { return m_is_success.load(); } + + /// \brief Returns the last measured offset in microseconds. + /// \return Offset in microseconds (UTC - local realtime). + int64_t offset_us() const noexcept { return m_offset_us; } + + /// \brief Returns the last measured delay in microseconds. + /// \return Round-trip delay estimate in microseconds. + int64_t delay_us() const noexcept { return m_delay_us; } + + /// \brief Returns the last received stratum value. + /// \return NTP stratum value. + int stratum() const noexcept { return m_stratum; } + + /// \brief Returns current UTC time in microseconds based on last NTP offset. + /// \return UTC time in microseconds using last offset. + int64_t utc_time_us() const noexcept { return now_realtime_us() + m_offset_us.load(); } + + /// \brief Returns current UTC time in milliseconds based on last NTP offset. + /// \return UTC time in milliseconds using last offset. + int64_t utc_time_ms() const noexcept { return utc_time_us() / 1000; } + + /// \brief Returns current UTC time as time_t (seconds since Unix epoch). + /// \return UTC time in seconds since Unix epoch. + time_t utc_time_sec() const noexcept { return static_cast(utc_time_us() / 1000000); } + + /// \brief Returns last socket error code (if any). + /// \return Error code from last query attempt. + int last_error_code() const noexcept { return last_error_code_slot(); } + + private: + std::string m_host; + int m_port; + std::atomic m_offset_us; + std::atomic m_delay_us; + std::atomic m_stratum; + std::atomic m_is_success; + static const int k_default_timeout_ms = 5000; + + static int& last_error_code_slot() noexcept { + static TIME_SHIELD_THREAD_LOCAL int value = 0; + return value; + } + }; + +#else + + class NtpClient { + public: + NtpClient() { + static_assert(sizeof(void*) == 0, "NtpClient is disabled by configuration."); + } + }; + +#endif // platform switch + +} // namespace time_shield + +#else // TIME_SHIELD_ENABLE_NTP_CLIENT + +namespace time_shield { + class NtpClient { + public: + NtpClient() { + static_assert(sizeof(void*) == 0, "NtpClient is disabled by configuration."); + } + }; +} // namespace time_shield + +#endif // TIME_SHIELD_ENABLE_NTP_CLIENT + +#endif // TIME_SHIELD_HEADER_NTP_NTP_CLIENT_HPP_INCLUDED diff --git a/include/time_shield/ntp/ntp_client_pool.hpp b/include/time_shield/ntp/ntp_client_pool.hpp new file mode 100644 index 00000000..c8925811 --- /dev/null +++ b/include/time_shield/ntp/ntp_client_pool.hpp @@ -0,0 +1,732 @@ +// SPDX-License-Identifier: MIT +#pragma once +#ifndef TIME_SHIELD_HEADER_NTP_NTP_CLIENT_POOL_HPP_INCLUDED +#define TIME_SHIELD_HEADER_NTP_NTP_CLIENT_POOL_HPP_INCLUDED + +#include + +#if TIME_SHIELD_ENABLE_NTP_CLIENT + +#include "ntp_client.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace time_shield { + + /// \ingroup ntp + /// \brief NTP measurement sample (one server response). + struct NtpSample { + std::string host; ///< Server host name. + int port = 123; ///< Server port. + bool is_ok = false; ///< Indicates successful response parsing. + int error_code = 0; ///< Error code when query or parsing failed. + int stratum = -1; ///< NTP stratum level reported by server. + int64_t offset_us = 0; ///< Offset between UTC and local realtime, microseconds. + int64_t delay_us = 0; ///< Estimated round-trip delay, microseconds. + int64_t max_delay_us = 0; ///< Maximum acceptable delay for this sample. + }; + + /// \ingroup ntp + /// \brief Per-server configuration. + struct NtpServerConfig { + std::string host; ///< Server host name. + int port = 123; ///< Server port. + + std::chrono::milliseconds min_interval{15000}; ///< Minimum time between queries to the same server. + std::chrono::milliseconds max_delay{250}; ///< Maximum acceptable delay for responses from this server. + + std::chrono::milliseconds backoff_initial{15000}; ///< Initial backoff after failure. + std::chrono::milliseconds backoff_max{std::chrono::minutes(10)}; ///< Maximum backoff interval after repeated failures. + }; + + /// \ingroup ntp + /// \brief Pool configuration. + struct NtpPoolConfig { + std::size_t sample_servers = 5; ///< Number of servers to sample per measurement. + std::size_t min_valid_samples = 3; ///< Minimum number of valid samples required to update offset. + + /// \brief Aggregation strategy for offset estimation. + enum class Aggregation { + Median, + BestDelay, + MedianMadTrim + } aggregation = Aggregation::Median; + + double smoothing_alpha = 1.0; ///< Exponential smoothing factor for offset updates. + std::uint64_t rng_seed = 0; ///< Random seed for server sampling; 0 uses time-based seed. + }; + + /// \ingroup ntp + /// \brief Pool of NTP servers: rate-limited multi-server offset estimation. + /// \tparam ClientT NTP client type with interface: + /// ClientT(const std::string& host, int port); + /// bool query(); // may throw + /// int last_error_code() const; + /// int64_t offset_us() const; + /// int64_t delay_us() const; + /// int stratum() const; + template + class NtpClientPoolT { + public: + /// \brief Construct pool with configuration. + /// \param cfg Pool configuration. + explicit NtpClientPoolT(NtpPoolConfig cfg = {}) + : m_cfg(std::move(cfg)) + , m_offset_us(0) + , m_rng(init_seed(m_cfg.rng_seed)) {} + + NtpClientPoolT(const NtpClientPoolT&) = delete; + NtpClientPoolT& operator=(const NtpClientPoolT&) = delete; + + /// \brief Move-construct pool state. + NtpClientPoolT(NtpClientPoolT&& other) noexcept + : m_cfg() + , m_offset_us(0) + , m_rng(init_seed(other.m_cfg.rng_seed)) { + std::lock_guard lk(other.m_mtx); + m_cfg = other.m_cfg; + m_servers = std::move(other.m_servers); + m_last_samples = std::move(other.m_last_samples); + m_offset_us.store(other.m_offset_us.load()); + m_rng = std::move(other.m_rng); + } + + /// \brief Move-assign pool state. + NtpClientPoolT& operator=(NtpClientPoolT&& other) noexcept { + if (this == &other) { + return *this; + } + std::lock(m_mtx, other.m_mtx); + std::lock_guard lk1(m_mtx, std::adopt_lock); + std::lock_guard lk2(other.m_mtx, std::adopt_lock); + + m_cfg = other.m_cfg; + m_servers = std::move(other.m_servers); + m_last_samples = std::move(other.m_last_samples); + m_offset_us.store(other.m_offset_us.load()); + m_rng = std::move(other.m_rng); + return *this; + } + + /// \brief Replace server list (keeps pool config). + /// \param servers Server configurations to use. + void set_servers(std::vector servers) { + std::lock_guard lk(m_mtx); + m_servers.clear(); + m_servers.reserve(servers.size()); + for (auto& server_cfg : servers) { + ServerState state; + state.cfg = std::move(server_cfg); + m_servers.push_back(std::move(state)); + } + } + + /// \brief Add one server. + /// \param server_cfg Server configuration to add. + void add_server(NtpServerConfig server_cfg) { + std::lock_guard lk(m_mtx); + ServerState state; + state.cfg = std::move(server_cfg); + m_servers.push_back(std::move(state)); + } + + /// \brief Build a conservative default server list. + /// \return Default server list with conservative timing settings. + static std::vector build_default_servers() { + std::vector servers; + servers.reserve(160); + + auto add = [&servers](const char* host) { + NtpServerConfig cfg; + cfg.host = host; + cfg.min_interval = std::chrono::milliseconds{60000}; + cfg.max_delay = std::chrono::milliseconds{500}; + cfg.backoff_initial = std::chrono::milliseconds{120000}; + cfg.backoff_max = std::chrono::minutes(10); + servers.push_back(std::move(cfg)); + }; + + add("time.google.com"); + add("time1.google.com"); + add("time2.google.com"); + add("time3.google.com"); + add("time4.google.com"); + + add("time.cloudflare.com"); + + add("time.facebook.com"); + add("time1.facebook.com"); + add("time2.facebook.com"); + add("time3.facebook.com"); + add("time4.facebook.com"); + add("time5.facebook.com"); + + add("time.windows.com"); + + add("time.apple.com"); + add("time1.apple.com"); + add("time2.apple.com"); + add("time3.apple.com"); + add("time4.apple.com"); + add("time5.apple.com"); + add("time6.apple.com"); + add("time7.apple.com"); + add("time.euro.apple.com"); + + add("time-a-g.nist.gov"); + add("time-b-g.nist.gov"); + add("time-c-g.nist.gov"); + add("time-d-g.nist.gov"); + add("time-a-wwv.nist.gov"); + add("time-b-wwv.nist.gov"); + add("time-c-wwv.nist.gov"); + add("time-d-wwv.nist.gov"); + add("time-a-b.nist.gov"); + add("time-b-b.nist.gov"); + add("time-c-b.nist.gov"); + add("time-d-b.nist.gov"); + add("time.nist.gov"); + add("utcnist.colorado.edu"); + add("utcnist2.colorado.edu"); + + add("ntp1.vniiftri.ru"); + add("ntp2.vniiftri.ru"); + add("ntp3.vniiftri.ru"); + add("ntp4.vniiftri.ru"); + add("ntp1.niiftri.irkutsk.ru"); + add("ntp2.niiftri.irkutsk.ru"); + add("vniiftri.khv.ru"); + add("vniiftri2.khv.ru"); + add("ntp21.vniiftri.ru"); + + add("ntp.mobatime.ru"); + + add("ntp1.stratum1.ru"); + add("ntp2.stratum1.ru"); + add("ntp3.stratum1.ru"); + add("ntp4.stratum1.ru"); + add("ntp5.stratum1.ru"); + add("ntp2.stratum2.ru"); + add("ntp3.stratum2.ru"); + add("ntp4.stratum2.ru"); + add("ntp5.stratum2.ru"); + + add("stratum1.net"); + + add("ntp.time.in.ua"); + add("ntp2.time.in.ua"); + add("ntp3.time.in.ua"); + + add("ntp.ru"); + + add("ts1.aco.net"); + add("ts2.aco.net"); + + add("ntp1.net.berkeley.edu"); + add("ntp2.net.berkeley.edu"); + + add("ntp.gsu.edu"); + + add("tick.usask.ca"); + add("tock.usask.ca"); + + add("ntp.nsu.ru"); + add("ntp.rsu.edu.ru"); + + add("ntp.nict.jp"); + + add("x.ns.gin.ntt.net"); + add("y.ns.gin.ntt.net"); + + add("clock.nyc.he.net"); + add("clock.sjc.he.net"); + + add("ntp.fiord.ru"); + + add("gbg1.ntp.se"); + add("gbg2.ntp.se"); + add("mmo1.ntp.se"); + add("mmo2.ntp.se"); + add("sth1.ntp.se"); + add("sth2.ntp.se"); + add("svl1.ntp.se"); + add("svl2.ntp.se"); + + add("clock.isc.org"); + + add("pool.ntp.org"); + add("0.pool.ntp.org"); + add("1.pool.ntp.org"); + add("2.pool.ntp.org"); + add("3.pool.ntp.org"); + + add("europe.pool.ntp.org"); + add("0.europe.pool.ntp.org"); + add("1.europe.pool.ntp.org"); + add("2.europe.pool.ntp.org"); + add("3.europe.pool.ntp.org"); + + add("asia.pool.ntp.org"); + add("0.asia.pool.ntp.org"); + add("1.asia.pool.ntp.org"); + add("2.asia.pool.ntp.org"); + add("3.asia.pool.ntp.org"); + + add("ru.pool.ntp.org"); + add("0.ru.pool.ntp.org"); + add("1.ru.pool.ntp.org"); + add("2.ru.pool.ntp.org"); + add("3.ru.pool.ntp.org"); + + add("0.gentoo.pool.ntp.org"); + add("1.gentoo.pool.ntp.org"); + add("2.gentoo.pool.ntp.org"); + add("3.gentoo.pool.ntp.org"); + + add("0.arch.pool.ntp.org"); + add("1.arch.pool.ntp.org"); + add("2.arch.pool.ntp.org"); + add("3.arch.pool.ntp.org"); + + add("0.fedora.pool.ntp.org"); + add("1.fedora.pool.ntp.org"); + add("2.fedora.pool.ntp.org"); + add("3.fedora.pool.ntp.org"); + + add("0.opensuse.pool.ntp.org"); + add("1.opensuse.pool.ntp.org"); + add("2.opensuse.pool.ntp.org"); + add("3.opensuse.pool.ntp.org"); + + add("0.centos.pool.ntp.org"); + add("1.centos.pool.ntp.org"); + add("2.centos.pool.ntp.org"); + add("3.centos.pool.ntp.org"); + + add("0.debian.pool.ntp.org"); + add("1.debian.pool.ntp.org"); + add("2.debian.pool.ntp.org"); + add("3.debian.pool.ntp.org"); + + add("0.ubuntu.pool.ntp.org"); + add("1.ubuntu.pool.ntp.org"); + add("2.ubuntu.pool.ntp.org"); + add("3.ubuntu.pool.ntp.org"); + + add("0.askozia.pool.ntp.org"); + add("1.askozia.pool.ntp.org"); + add("2.askozia.pool.ntp.org"); + add("3.askozia.pool.ntp.org"); + + add("0.freebsd.pool.ntp.org"); + add("1.freebsd.pool.ntp.org"); + add("2.freebsd.pool.ntp.org"); + add("3.freebsd.pool.ntp.org"); + + add("0.netbsd.pool.ntp.org"); + add("1.netbsd.pool.ntp.org"); + add("2.netbsd.pool.ntp.org"); + add("3.netbsd.pool.ntp.org"); + + add("0.openbsd.pool.ntp.org"); + add("1.openbsd.pool.ntp.org"); + add("2.openbsd.pool.ntp.org"); + add("3.openbsd.pool.ntp.org"); + + add("0.dragonfly.pool.ntp.org"); + add("1.dragonfly.pool.ntp.org"); + add("2.dragonfly.pool.ntp.org"); + add("3.dragonfly.pool.ntp.org"); + + add("0.pfsense.pool.ntp.org"); + add("1.pfsense.pool.ntp.org"); + add("2.pfsense.pool.ntp.org"); + add("3.pfsense.pool.ntp.org"); + + add("0.opnsense.pool.ntp.org"); + add("1.opnsense.pool.ntp.org"); + add("2.opnsense.pool.ntp.org"); + add("3.opnsense.pool.ntp.org"); + + add("0.smartos.pool.ntp.org"); + add("1.smartos.pool.ntp.org"); + add("2.smartos.pool.ntp.org"); + add("3.smartos.pool.ntp.org"); + + add("0.android.pool.ntp.org"); + add("1.android.pool.ntp.org"); + add("2.android.pool.ntp.org"); + add("3.android.pool.ntp.org"); + + add("0.amazon.pool.ntp.org"); + add("1.amazon.pool.ntp.org"); + add("2.amazon.pool.ntp.org"); + add("3.amazon.pool.ntp.org"); + + return servers; + } + + /// \brief Replace server list with a conservative default set. + void set_default_servers() { + set_servers(build_default_servers()); + } + + /// \brief Clear server list. + void clear_servers() { + std::lock_guard lk(m_mtx); + m_servers.clear(); + } + + /// \brief Perform measurement using current config (queries up to sample_servers). + /// \return True when pool offset updated. + bool measure() { + const auto cfg = config(); + return measure_n(cfg.sample_servers); + } + + /// \brief Perform measurement using a custom number of servers. + /// \param servers_to_sample Number of servers to query in this measurement. + /// \return True when pool offset updated. + bool measure_n(std::size_t servers_to_sample) { + std::vector picked; + NtpPoolConfig cfg; + { + std::lock_guard lk(m_mtx); + cfg = m_cfg; + picked = pick_servers_locked(servers_to_sample); + } + + std::vector samples; + samples.reserve(picked.size()); + + for (std::size_t idx : picked) { + samples.push_back(query_one(idx)); + } + + const bool is_updated = update_from_samples(samples, cfg); + + { + std::lock_guard lk(m_mtx); + m_last_samples = std::move(samples); + } + + return is_updated; + } + + /// \brief Last estimated pool offset (µs). + /// \return Offset in microseconds (UTC - local realtime). + int64_t offset_us() const noexcept { return m_offset_us.load(); } + + /// \brief Current UTC time in microseconds based on pool offset. + /// \return UTC time in microseconds using pool offset. + int64_t utc_time_us() const noexcept { return now_realtime_us() + m_offset_us.load(); } + + /// \brief Current UTC time in milliseconds based on pool offset. + /// \return UTC time in milliseconds using pool offset. + int64_t utc_time_ms() const noexcept { return utc_time_us() / 1000; } + + /// \brief Current UTC time in seconds based on pool offset. + /// \return UTC time in seconds using pool offset. + int64_t utc_time_sec() const noexcept { return utc_time_us() / 1000000; } + + /// \brief Returns last measurement samples (copy). + /// \return Copy of samples from the last measurement. + std::vector last_samples() const { + std::lock_guard lk(m_mtx); + return m_last_samples; + } + + /// \brief Apply pre-collected samples (testing/offline). + /// \param samples Sample list to apply. + /// \return True when pool offset updated. + /// \note Primarily for tests; does not enforce rate limiting or backoff. + bool apply_samples(const std::vector& samples) { + const NtpPoolConfig cfg = config(); + const bool is_updated = update_from_samples(samples, cfg); + std::lock_guard lk(m_mtx); + m_last_samples = samples; + return is_updated; + } + + /// \brief Returns median of values. + /// \param values Values to process in-place. + /// \return Median of the input values. + static int64_t median(std::vector& values) { + using diff_t = std::vector::difference_type; + const diff_t mid_index = static_cast(values.size() / 2); + std::nth_element(values.begin(), values.begin() + mid_index, values.end()); + const int64_t mid = values[static_cast(mid_index)]; + if (values.size() % 2 == 1) { + return mid; + } + + const auto it = std::max_element(values.begin(), values.begin() + mid_index); + return (*it + mid) / 2; + } + + /// \brief Median with MAD trimming. + /// \param offsets Offset list to process in-place. + /// \return Median after MAD-based trimming. + static int64_t median_mad_trim(std::vector& offsets) { + const int64_t med = median(offsets); + + std::vector deviations; + deviations.reserve(offsets.size()); + for (auto value : offsets) { + deviations.push_back(value > med ? (value - med) : (med - value)); + } + + const int64_t mad = median(deviations); + if (mad == 0) { + return med; + } + + const int64_t threshold = mad * 3; + std::vector kept; + kept.reserve(offsets.size()); + for (auto value : offsets) { + const int64_t deviation = value > med ? (value - med) : (med - value); + if (deviation <= threshold) { + kept.push_back(value); + } + } + if (kept.empty()) { + return med; + } + return median(kept); + } + + /// \brief Offset from best (lowest) delay sample. + /// \param samples Sample list to scan. + /// \return Offset from the sample with the lowest delay. + static int64_t best_delay_offset(const std::vector& samples) { + const NtpSample* best = nullptr; + for (const auto& sample : samples) { + if (!sample.is_ok) { + continue; + } + if (sample.max_delay_us > 0 && sample.delay_us > sample.max_delay_us) { + continue; + } + if (best == nullptr) { + best = &sample; + continue; + } + if (sample.delay_us > 0 && best->delay_us > 0 && sample.delay_us < best->delay_us) { + best = &sample; + } + } + return best ? best->offset_us : 0; + } + + /// \brief Access config. + /// \return Current pool configuration. + NtpPoolConfig config() const { + std::lock_guard lk(m_mtx); + return m_cfg; + } + /// \brief Replace pool configuration. + /// \param cfg New pool configuration. + void set_config(NtpPoolConfig cfg) { + std::lock_guard lk(m_mtx); + m_cfg = std::move(cfg); + } + + /// \brief Runtime state for a configured server. + struct ServerState { + NtpServerConfig cfg; + + std::chrono::steady_clock::time_point next_allowed{}; + std::chrono::milliseconds backoff{0}; + + int fail_count = 0; + + int64_t last_offset_us = 0; + int64_t last_delay_us = 0; + int last_error = 0; + bool is_last_ok = false; + }; + + NtpPoolConfig m_cfg; + + mutable std::mutex m_mtx; + std::vector m_servers; + std::vector m_last_samples; + + std::atomic m_offset_us; + + std::mt19937_64 m_rng; + + private: + static std::uint64_t init_seed(std::uint64_t seed) { + if (seed != 0) return seed; + const auto v = static_cast( + std::chrono::high_resolution_clock::now().time_since_epoch().count()); + return v ^ 0x9E3779B97F4A7C15ULL; + } + + std::vector pick_servers_locked(std::size_t servers_to_sample) { + std::vector eligible; + eligible.reserve(m_servers.size()); + + const auto now_point = std::chrono::steady_clock::now(); + for (std::size_t i = 0; i < m_servers.size(); ++i) { + if (now_point >= m_servers[i].next_allowed) { + eligible.push_back(i); + } + } + + if (eligible.empty()) { + return {}; + } + + std::shuffle(eligible.begin(), eligible.end(), m_rng); + if (servers_to_sample < eligible.size()) { + eligible.resize(servers_to_sample); + } + return eligible; + } + + NtpSample query_one(std::size_t server_index) { + NtpServerConfig cfg; + { + std::lock_guard lk(m_mtx); + cfg = m_servers[server_index].cfg; + m_servers[server_index].next_allowed = + std::chrono::steady_clock::now() + cfg.min_interval; + } + + NtpSample out; + out.host = cfg.host; + out.port = cfg.port; + out.max_delay_us = cfg.max_delay.count() > 0 ? cfg.max_delay.count() * 1000 : 0; + + ClientT client(cfg.host, cfg.port); + + bool is_ok = false; + try { + is_ok = client.query(); + } catch (...) { + out.error_code = client.last_error_code(); + } + + if (out.error_code == 0) { + out.error_code = client.last_error_code(); + } + if (out.error_code == 0 && !is_ok) { + out.error_code = -1; + } + + out.is_ok = is_ok; + out.offset_us = client.offset_us(); + out.delay_us = client.delay_us(); + out.stratum = client.stratum(); + + update_server_state_after_query(server_index, out); + return out; + } + + void update_server_state_after_query(std::size_t index, const NtpSample& sample) { + std::lock_guard lk(m_mtx); + auto& state = m_servers[index]; + + state.is_last_ok = sample.is_ok; + state.last_error = sample.error_code; + state.last_offset_us = sample.offset_us; + state.last_delay_us = sample.delay_us; + + if (sample.is_ok) { + state.fail_count = 0; + state.backoff = std::chrono::milliseconds(0); + return; + } + + state.fail_count++; + const auto init = state.cfg.backoff_initial; + const auto max_backoff = state.cfg.backoff_max; + + if (state.backoff.count() == 0) { + state.backoff = init; + } else { + state.backoff = (std::min)(max_backoff, state.backoff * 2); + } + + state.next_allowed = std::chrono::steady_clock::now() + state.backoff; + } + + bool update_from_samples(const std::vector& samples, const NtpPoolConfig& cfg) { + std::vector offsets; + offsets.reserve(samples.size()); + + for (const auto& sample : samples) { + if (!sample.is_ok) { + continue; + } + if (sample.max_delay_us > 0 && sample.delay_us > sample.max_delay_us) { + continue; + } + offsets.push_back(sample.offset_us); + } + + if (offsets.size() < cfg.min_valid_samples) { + return false; + } + + int64_t estimate = 0; + switch (cfg.aggregation) { + case NtpPoolConfig::Aggregation::BestDelay: + estimate = best_delay_offset(samples); + break; + case NtpPoolConfig::Aggregation::MedianMadTrim: + estimate = median_mad_trim(offsets); + break; + case NtpPoolConfig::Aggregation::Median: + default: + estimate = median(offsets); + break; + } + + double alpha = cfg.smoothing_alpha; + if (alpha < 0.0) { + alpha = 0.0; + } else if (alpha > 1.0) { + alpha = 1.0; + } + if (alpha >= 1.0) { + m_offset_us.store(estimate); + } else if (alpha > 0.0) { + const int64_t old_value = m_offset_us.load(); + const double new_value = + (1.0 - alpha) * static_cast(old_value) + alpha * static_cast(estimate); + m_offset_us.store(static_cast(new_value)); + } + return true; + } + + }; + + using NtpClientPool = NtpClientPoolT; +} // namespace time_shield + +#else // TIME_SHIELD_ENABLE_NTP_CLIENT + +namespace time_shield { + class NtpClientPool { + public: + NtpClientPool() { + static_assert(sizeof(void*) == 0, "NtpClientPool is disabled by configuration."); + } + }; +} // namespace time_shield + +#endif // TIME_SHIELD_ENABLE_NTP_CLIENT + +#endif // TIME_SHIELD_HEADER_NTP_NTP_CLIENT_POOL_HPP_INCLUDED diff --git a/include/time_shield/ntp/ntp_client_pool_runner.hpp b/include/time_shield/ntp/ntp_client_pool_runner.hpp new file mode 100644 index 00000000..f45196a7 --- /dev/null +++ b/include/time_shield/ntp/ntp_client_pool_runner.hpp @@ -0,0 +1,233 @@ +// SPDX-License-Identifier: MIT +#pragma once +#ifndef TIME_SHIELD_HEADER_NTP_NTP_CLIENT_POOL_RUNNER_HPP_INCLUDED +#define TIME_SHIELD_HEADER_NTP_NTP_CLIENT_POOL_RUNNER_HPP_INCLUDED + +#include + +#if TIME_SHIELD_ENABLE_NTP_CLIENT + +#include "ntp_client_pool.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace time_shield { + + /// \ingroup ntp + /// \brief Background runner that periodically measures NTP offsets using a pool. + /// + /// \code + /// time_shield::NtpClientPool pool; + /// pool.set_default_servers(); + /// + /// time_shield::NtpClientPoolRunner runner(std::move(pool)); + /// runner.start(std::chrono::seconds(30)); + /// + /// auto now_ms = runner.utc_time_ms(); + /// auto offset = runner.offset_us(); + /// + /// runner.stop(); + /// \endcode + template + class BasicPoolRunner { + public: + /// \brief Construct runner with a pool instance. + /// \param pool Pool instance to use. + explicit BasicPoolRunner(PoolT pool = PoolT{}) + : m_pool(std::move(pool)) {} + + /// \brief Stop background thread on destruction. + ~BasicPoolRunner() { + stop(); + } + + /// \brief Start periodic measurements on a background thread. + /// \param interval Measurement interval. + /// \param measure_immediately Measure before first sleep if true. + /// \return True when background thread started. + bool start(std::chrono::milliseconds interval = std::chrono::seconds(30), + bool measure_immediately = true) { + if (m_is_running.load()) { + return false; + } + if (interval.count() <= 0) { + interval = std::chrono::milliseconds(1); + } + + m_is_stop_requested.store(false); + m_is_force_requested.store(false); + m_is_running.store(true); + + try { + m_thread = std::thread(&BasicPoolRunner::run_loop, this, interval, measure_immediately); + } catch (...) { + m_is_running.store(false); + return false; + } + + return true; + } + + /// \brief Start periodic measurements using milliseconds. + /// \param interval_ms Measurement interval in milliseconds. + /// \param measure_immediately Measure before first sleep if true. + /// \return True when background thread started. + bool start(int interval_ms, bool measure_immediately = true) { + return start(std::chrono::milliseconds(interval_ms), measure_immediately); + } + + /// \brief Stop background measurements. + void stop() { + m_is_stop_requested.store(true); + m_cv.notify_all(); + if (m_thread.joinable()) { + m_thread.join(); + } + m_is_running.store(false); + } + + /// \brief Return true when background thread is running. + /// \return True when background measurements are active. + bool running() const noexcept { return m_is_running.load(); } + + /// \brief Wake the worker thread and request a measurement. + /// \return True when request accepted. + bool force_measure() { + if (!m_is_running.load()) { + return false; + } + m_is_force_requested.store(true); + m_cv.notify_one(); + return true; + } + + /// \brief Perform one measurement immediately. + /// \return True when pool offset updated. + bool measure_now() { + return do_measure(); + } + + /// \brief Return last estimated offset in microseconds. + /// \return Offset in microseconds (UTC - local realtime). + int64_t offset_us() const noexcept { return m_pool.offset_us(); } + /// \brief Return current UTC time in microseconds using pool offset. + /// \return UTC time in microseconds using pool offset. + int64_t utc_time_us() const noexcept { return m_pool.utc_time_us(); } + /// \brief Return current UTC time in milliseconds using pool offset. + /// \return UTC time in milliseconds using pool offset. + int64_t utc_time_ms() const noexcept { return m_pool.utc_time_ms(); } + /// \brief Return current UTC time in seconds using pool offset. + /// \return UTC time in seconds using pool offset. + int64_t utc_time_sec() const noexcept { return utc_time_us() / 1000000; } + + /// \brief Return whether last measurement updated the offset. + /// \return True when last measurement updated the offset. + bool last_measure_ok() const noexcept { return m_last_measure_ok.load(); } + /// \brief Return total number of measurement attempts. + /// \return Number of measurement attempts. + uint64_t measure_count() const noexcept { return m_measure_count.load(); } + /// \brief Return number of failed measurement attempts. + /// \return Number of failed measurement attempts. + uint64_t fail_count() const noexcept { return m_fail_count.load(); } + /// \brief Return realtime timestamp of last measurement attempt. + /// \return Realtime microseconds timestamp for last measurement attempt. + int64_t last_update_realtime_us() const noexcept { return m_last_update_realtime_us.load(); } + /// \brief Return realtime timestamp of last successful measurement. + /// \return Realtime microseconds timestamp for last successful measurement. + int64_t last_success_realtime_us() const noexcept { return m_last_success_realtime_us.load(); } + + /// \brief Return copy of the most recent samples. + /// \return Copy of samples from the last measurement. + std::vector last_samples() const { return m_pool.last_samples(); } + + private: + void run_loop(std::chrono::milliseconds interval, bool measure_immediately) { + bool is_first = measure_immediately; + while (!m_is_stop_requested.load()) { + if (is_first) { + do_measure(); + is_first = false; + } else { + std::unique_lock lk(m_cv_mtx); + m_cv.wait_for(lk, interval, [this]() { + return m_is_stop_requested.load() || m_is_force_requested.load(); + }); + if (m_is_stop_requested.load()) { + break; + } + m_is_force_requested.store(false); + do_measure(); + } + } + m_is_running.store(false); + } + + bool do_measure() { + bool is_ok = false; + try { + std::lock_guard lk(m_pool_mtx); + is_ok = m_pool.measure(); + } catch (...) { + is_ok = false; + } + + m_measure_count.fetch_add(1); + if (!is_ok) { + m_fail_count.fetch_add(1); + } + + m_last_measure_ok.store(is_ok); + + const int64_t now = now_realtime_us(); + m_last_update_realtime_us.store(now); + if (is_ok) { + m_last_success_realtime_us.store(now); + } + + return is_ok; + } + + private: + PoolT m_pool; + mutable std::mutex m_pool_mtx; + + std::thread m_thread; + std::condition_variable m_cv; + std::mutex m_cv_mtx; + + std::atomic m_is_running{false}; + std::atomic m_is_stop_requested{false}; + std::atomic m_is_force_requested{false}; + + std::atomic m_last_measure_ok{false}; + std::atomic m_measure_count{0}; + std::atomic m_fail_count{0}; + std::atomic m_last_update_realtime_us{0}; + std::atomic m_last_success_realtime_us{0}; + }; + + using NtpClientPoolRunner = BasicPoolRunner; + +} // namespace time_shield + +#else // TIME_SHIELD_ENABLE_NTP_CLIENT + +namespace time_shield { + class NtpClientPoolRunner { + public: + NtpClientPoolRunner() { + static_assert(sizeof(void*) == 0, "NtpClientPoolRunner is disabled by configuration."); + } + }; +} // namespace time_shield + +#endif // _TIME_SHIELD_ENABLE_NTP_CLIENT + +#endif // TIME_SHIELD_HEADER_NTP_NTP_CLIENT_POOL_RUNNER_HPP_INCLUDED diff --git a/include/time_shield/ntp/ntp_time_service.hpp b/include/time_shield/ntp/ntp_time_service.hpp new file mode 100644 index 00000000..e1e06e0b --- /dev/null +++ b/include/time_shield/ntp/ntp_time_service.hpp @@ -0,0 +1,894 @@ +// SPDX-License-Identifier: MIT +#pragma once +#ifndef TIME_SHIELD_HEADER_NTP_NTP_TIME_SERVICE_HPP_INCLUDED +#define TIME_SHIELD_HEADER_NTP_NTP_TIME_SERVICE_HPP_INCLUDED + +#include + +#if TIME_SHIELD_ENABLE_NTP_CLIENT + +#include "ntp_client_pool.hpp" +#include "ntp_client_pool_runner.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace time_shield { + + template + class NtpTimeServiceT; + + namespace detail { + template + struct NtpTimeServiceSingleton; + + template + struct NtpTimeServiceTestAccess; + +#ifdef TIME_SHIELD_TEST_FAKE_NTP + /// \brief Fake runner for tests without network access. + class FakeNtpRunner { + public: + /// \brief Construct fake runner. + FakeNtpRunner() = default; + /// \brief Construct fake runner with an unused pool. + explicit FakeNtpRunner(NtpClientPool) {} + + FakeNtpRunner(const FakeNtpRunner&) = delete; + FakeNtpRunner& operator=(const FakeNtpRunner&) = delete; + + /// \brief Stop background thread on destruction. + ~FakeNtpRunner() { + stop(); + } + + /// \brief Start fake measurements on a background thread. + bool start(std::chrono::milliseconds interval = std::chrono::seconds(30), + bool measure_immediately = true) { + if (m_is_running.load()) { + return false; + } + if (interval.count() <= 0) { + interval = std::chrono::milliseconds(1); + } + m_interval = interval; + m_is_stop_requested.store(false); + m_is_force_requested.store(false); + m_is_running.store(true); + m_measure_immediately = measure_immediately; + try { + m_thread = std::thread(&FakeNtpRunner::run_loop, this); + } catch (...) { + m_is_running.store(false); + return false; + } + return true; + } + + /// \brief Start fake measurements using milliseconds. + bool start(int interval_ms, bool measure_immediately = true) { + return start(std::chrono::milliseconds(interval_ms), measure_immediately); + } + + /// \brief Stop background measurements. + void stop() { + m_is_stop_requested.store(true); + m_cv.notify_all(); + if (m_thread.joinable()) { + m_thread.join(); + } + m_is_running.store(false); + } + + /// \brief Return true when background thread is running. + bool running() const noexcept { return m_is_running.load(); } + + /// \brief Wake the worker thread and request a measurement. + bool force_measure() { + if (!m_is_running.load()) { + return false; + } + m_is_force_requested.store(true); + m_cv.notify_one(); + return true; + } + + /// \brief Perform one measurement immediately. + bool measure_now() { + return do_measure(); + } + + /// \brief Return last estimated offset in microseconds. + int64_t offset_us() const noexcept { return m_offset_us.load(); } + /// \brief Return current UTC time in microseconds based on offset. + int64_t utc_time_us() const noexcept { return now_realtime_us() + m_offset_us.load(); } + /// \brief Return current UTC time in milliseconds based on offset. + int64_t utc_time_ms() const noexcept { return utc_time_us() / 1000; } + /// \brief Return current UTC time in seconds based on offset. + int64_t utc_time_sec() const noexcept { return utc_time_us() / 1000000; } + + /// \brief Return whether last measurement updated the offset. + bool last_measure_ok() const noexcept { return m_last_measure_ok.load(); } + /// \brief Return total number of measurement attempts. + uint64_t measure_count() const noexcept { return m_measure_count.load(); } + /// \brief Return number of failed measurement attempts. + uint64_t fail_count() const noexcept { return m_fail_count.load(); } + /// \brief Return realtime timestamp of last measurement attempt. + int64_t last_update_realtime_us() const noexcept { return m_last_update_realtime_us.load(); } + /// \brief Return realtime timestamp of last successful measurement. + int64_t last_success_realtime_us() const noexcept { return m_last_success_realtime_us.load(); } + + /// \brief Return copy of most recent samples. + std::vector last_samples() const { return {}; } + + private: + /// \brief Background loop for fake measurements. + void run_loop() { + const auto sleep_interval = m_interval; + bool is_first = m_measure_immediately; + while (!m_is_stop_requested.load()) { + if (is_first) { + do_measure(); + is_first = false; + } else { + std::unique_lock lk(m_cv_mtx); + m_cv.wait_for(lk, sleep_interval, [this]() { + return m_is_stop_requested.load() || m_is_force_requested.load(); + }); + if (m_is_stop_requested.load()) { + break; + } + m_is_force_requested.store(false); + do_measure(); + } + } + m_is_running.store(false); + } + + /// \brief Update fake offset and stats. + bool do_measure() { + const uint64_t count = m_measure_count.fetch_add(1) + 1; + m_offset_us.store(static_cast(count * 1000)); + m_last_measure_ok.store(true); + const int64_t now = now_realtime_us(); + m_last_update_realtime_us.store(now); + m_last_success_realtime_us.store(now); + return true; + } + + private: + std::chrono::milliseconds m_interval{std::chrono::seconds(30)}; + bool m_measure_immediately{true}; + + std::thread m_thread; + std::condition_variable m_cv; + std::mutex m_cv_mtx; + + std::atomic m_is_running{false}; + std::atomic m_is_stop_requested{false}; + std::atomic m_is_force_requested{false}; + + std::atomic m_last_measure_ok{false}; + std::atomic m_measure_count{0}; + std::atomic m_fail_count{0}; + std::atomic m_last_update_realtime_us{0}; + std::atomic m_last_success_realtime_us{0}; + std::atomic m_offset_us{0}; + }; +#endif // _TIME_SHIELD_TEST_FAKE_NTP + } // namespace detail + + /// \ingroup ntp + /// \brief Singleton service for background NTP measurements. + /// + /// Uses an internal runner to keep offset updated. It exposes UTC time + /// computed as realtime clock plus the latest offset. Configure pool + /// servers and sampling before starting the service. During process + /// shutdown, the singleton stops background work and falls back to the + /// last cached offset without restarting the runner. + template + class NtpTimeServiceT { + friend struct detail::NtpTimeServiceSingleton; + friend struct detail::NtpTimeServiceTestAccess; + + public: + /// \brief Return the singleton instance. + /// \return Singleton instance. + static NtpTimeServiceT& instance() noexcept { + return detail::NtpTimeServiceSingleton::instance(); + } + + NtpTimeServiceT(const NtpTimeServiceT&) = delete; + NtpTimeServiceT& operator=(const NtpTimeServiceT&) = delete; + + /// \brief Start background measurements using stored interval. + /// \return True when background runner started. + bool init() { + return init(m_interval, m_measure_immediately); + } + + /// \brief Start background measurements with interval and immediate flag. + /// \param interval Measurement interval. + /// \param measure_immediately Measure before first sleep if true. + /// \return True when background runner started. + bool init(std::chrono::milliseconds interval, bool measure_immediately = true) { + if (is_process_shutting_down()) { + return false; + } + + std::unique_ptr local_runner; + std::chrono::milliseconds start_interval = interval; + bool use_measure_immediately = measure_immediately; + { + std::unique_lock lk(m_mtx); + while (is_transitioning_locked()) { + m_cv.wait(lk); + } + if (is_process_shutting_down_locked()) { + return false; + } + if (m_state == State::running && is_running_locked()) { + return true; + } + if (start_interval.count() <= 0) { + start_interval = std::chrono::milliseconds(1); + } + m_interval = start_interval; + m_measure_immediately = use_measure_immediately; + m_state = State::starting; + + local_runner = build_runner_locked(); + if (!local_runner) { + m_state = State::stopped; + lk.unlock(); + m_cv.notify_all(); + return false; + } + } + + bool has_started = false; + bool is_ok = false; + try { + has_started = local_runner->start(start_interval, use_measure_immediately); + if (has_started) { + is_ok = local_runner->measure_now(); + } + } catch (...) { + is_ok = false; + } + + if (!has_started || !is_ok || is_process_shutting_down()) { + try { + if (has_started) { + local_runner->stop(); + } + } catch (...) { + // no-throw + } + local_runner.reset(); + } + + { + std::lock_guard lk(m_mtx); + if (local_runner) { + m_last_offset_us.store(local_runner->offset_us(), std::memory_order_relaxed); + m_runner = std::move(local_runner); + m_state = State::running; + } else { + m_runner.reset(); + m_state = State::stopped; + } + } + m_cv.notify_all(); + return is_ok; + } + + /// \brief Start background measurements using milliseconds. + /// \param interval_ms Measurement interval in milliseconds. + /// \param measure_immediately Measure before first sleep if true. + /// \return True when background runner started. + bool init(int interval_ms, bool measure_immediately = true) { + return init(std::chrono::milliseconds(interval_ms), measure_immediately); + } + + /// \brief Stop background measurements and release resources. + void shutdown() { + std::unique_ptr local_runner; + { + std::unique_lock lk(m_mtx); + while (is_transitioning_locked()) { + m_cv.wait(lk); + } + if (m_state == State::stopped || !m_runner) { + return; + } + m_state = State::stopping; + local_runner = std::move(m_runner); + } + try { + local_runner->stop(); + } catch (...) { + // no-throw + } + { + std::lock_guard lk(m_mtx); + m_state = State::stopped; + } + m_cv.notify_all(); + } + + /// \brief Return true when background runner is active. + /// \return True when background runner is active. + bool running() const noexcept { + std::lock_guard lk(m_mtx); + return m_state == State::running && is_running_locked(); + } + + /// \brief Ensure background runner is started with current config. + void ensure_started() noexcept { + if (is_process_shutting_down()) { + return; + } + if (running()) { + return; + } + (void)init(); + } + + /// \brief Return last estimated offset in microseconds. + /// \note During process shutdown, returns the last cached offset without + /// restarting the background runner. + /// \return Offset in microseconds (UTC - local realtime). + int64_t offset_us() noexcept { + if (is_process_shutting_down()) { + return m_last_offset_us.load(std::memory_order_relaxed); + } + ensure_started(); + std::lock_guard lk(m_mtx); + if (!m_runner) return 0; + const int64_t offset = m_runner->offset_us(); + m_last_offset_us.store(offset, std::memory_order_relaxed); + return offset; + } + + /// \brief Return current UTC time in microseconds based on offset. + /// \note During process shutdown, returns realtime plus the last cached + /// offset without restarting the background runner. + /// \return UTC time in microseconds using last offset. + int64_t utc_time_us() noexcept { + if (is_process_shutting_down()) { + return now_realtime_us() + m_last_offset_us.load(std::memory_order_relaxed); + } + ensure_started(); + std::lock_guard lk(m_mtx); + if (!m_runner) return now_realtime_us(); + m_last_offset_us.store(m_runner->offset_us(), std::memory_order_relaxed); + return m_runner->utc_time_us(); + } + + /// \brief Return current UTC time in milliseconds based on offset. + /// \return UTC time in milliseconds using last offset. + int64_t utc_time_ms() noexcept { + return utc_time_us() / 1000; + } + + /// \brief Return current UTC time in seconds based on offset. + /// \return UTC time in seconds using last offset. + int64_t utc_time_sec() noexcept { + return utc_time_us() / 1000000; + } + + /// \brief Return whether last measurement updated the offset. + /// \return True when last measurement updated the offset. + bool last_measure_ok() const noexcept { + std::lock_guard lk(m_mtx); + if (!m_runner) return false; + return m_runner->last_measure_ok(); + } + + /// \brief Return total number of measurement attempts. + /// \return Number of measurement attempts. + uint64_t measure_count() const noexcept { + std::lock_guard lk(m_mtx); + if (!m_runner) return 0; + return m_runner->measure_count(); + } + + /// \brief Return number of failed measurement attempts. + /// \return Number of failed measurement attempts. + uint64_t fail_count() const noexcept { + std::lock_guard lk(m_mtx); + if (!m_runner) return 0; + return m_runner->fail_count(); + } + + /// \brief Return realtime timestamp of last measurement attempt. + /// \return Realtime microseconds timestamp for last measurement attempt. + int64_t last_update_realtime_us() const noexcept { + std::lock_guard lk(m_mtx); + if (!m_runner) return 0; + return m_runner->last_update_realtime_us(); + } + + /// \brief Return realtime timestamp of last successful measurement. + /// \return Realtime microseconds timestamp for last successful measurement. + int64_t last_success_realtime_us() const noexcept { + std::lock_guard lk(m_mtx); + if (!m_runner) return 0; + return m_runner->last_success_realtime_us(); + } + + /// \brief Return true when last measurement is older than max_age. + /// \param max_age Maximum allowed age. + /// \return True when last measurement age exceeds max_age. + bool stale(std::chrono::milliseconds max_age) const noexcept { + const int64_t last = last_update_realtime_us(); + if (last == 0) { + return true; + } + const int64_t age = now_realtime_us() - last; + return age > static_cast(max_age.count()) * 1000; + } + + /// \brief Return true when last measurement is older than max_age_ms. + /// \param max_age_ms Maximum allowed age in milliseconds. + /// \return True when last measurement age exceeds max_age_ms. + bool stale(int max_age_ms) const noexcept { + return stale(std::chrono::milliseconds(max_age_ms)); + } + + /// \brief Replace server list used for new runner instances. + /// \param servers Server configurations to use. + /// \return False when service is already running. + bool set_servers(std::vector servers) { + std::lock_guard lk(m_mtx); + if (!is_reconfigurable_locked()) { + return false; + } + m_has_custom_servers = true; + m_servers = std::move(servers); + return true; + } + + /// \brief Use conservative default servers for new runner instances. + /// \return False when service is already running. + bool set_default_servers() { + std::lock_guard lk(m_mtx); + if (!is_reconfigurable_locked()) { + return false; + } + m_has_custom_servers = true; + m_servers = NtpClientPool::build_default_servers(); + return true; + } + + /// \brief Clear custom server list and return to default behavior. + /// \return False when service is already running. + bool clear_servers() { + std::lock_guard lk(m_mtx); + if (!is_reconfigurable_locked()) { + return false; + } + m_has_custom_servers = false; + m_servers.clear(); + return true; + } + + /// \brief Override pool configuration for new runner instances. + /// \param cfg Pool configuration to apply. + /// \return False when service is already running. + bool set_pool_config(NtpPoolConfig cfg) { + std::lock_guard lk(m_mtx); + if (!is_reconfigurable_locked()) { + return false; + } + m_has_custom_pool_cfg = true; + m_pool_cfg = std::move(cfg); + return true; + } + + /// \brief Return current pool configuration. + /// \return Current pool configuration. + NtpPoolConfig pool_config() const { + std::lock_guard lk(m_mtx); + if (m_has_custom_pool_cfg) { + return m_pool_cfg; + } + return NtpPoolConfig{}; + } + + /// \brief Return copy of last measurement samples. + /// \return Copy of samples from the last measurement. + std::vector last_samples() const { + std::lock_guard lk(m_mtx); + if (!m_runner) return {}; + return m_runner->last_samples(); + } + + /// \brief Construct service. + NtpTimeServiceT() = default; + /// \brief Immortal singleton is stopped via process-shutdown handler. + ~NtpTimeServiceT() = default; + + /// \brief Apply current config by rebuilding the runner. + /// \return True when runner restarted successfully. + bool apply_config_now() { + if (is_process_shutting_down()) { + return false; + } + + std::unique_ptr new_runner; + std::unique_ptr old_runner; + std::chrono::milliseconds interval; + bool measure_immediately = true; + bool was_running = false; + { + std::unique_lock lk(m_mtx); + while (is_transitioning_locked()) { + m_cv.wait(lk); + } + if (is_process_shutting_down_locked()) { + return false; + } + was_running = m_state == State::running && is_running_locked(); + m_state = State::starting; + new_runner = build_runner_locked(); + if (!new_runner) { + m_state = was_running ? State::running : State::stopped; + lk.unlock(); + m_cv.notify_all(); + return false; + } + interval = m_interval; + measure_immediately = m_measure_immediately; + old_runner = std::move(m_runner); + } + + if (old_runner) { + try { + old_runner->stop(); + } catch (...) { + } + } + + bool has_started = false; + bool is_ok = false; + try { + has_started = new_runner->start(interval, measure_immediately); + if (has_started) { + is_ok = new_runner->measure_now(); + } + } catch (...) { + is_ok = false; + } + + if (!has_started || !is_ok || is_process_shutting_down()) { + try { + if (has_started) { + new_runner->stop(); + } + } catch (...) { + // no-throw + } + new_runner.reset(); + } + + { + std::lock_guard lk(m_mtx); + if (new_runner) { + m_last_offset_us.store(new_runner->offset_us(), std::memory_order_relaxed); + m_runner = std::move(new_runner); + m_state = State::running; + } else { + m_runner.reset(); + m_state = State::stopped; + } + } + m_cv.notify_all(); + return is_ok; + } + + private: + /// \brief Global lifetime state of the immortal singleton. + enum class ProcessState : uint8_t { + alive, + shutting_down + }; + + /// \brief Lifecycle state of the singleton runner. + enum class State : uint8_t { + stopped, + starting, + running, + stopping + }; + + /// \brief Register one process-shutdown handler for this specialization. + void register_process_shutdown_handler() noexcept { + if (std::atexit(&detail::NtpTimeServiceSingleton::handle_process_exit) == 0) { + m_atexit_registration_count.fetch_add(1, std::memory_order_relaxed); + } + } + + /// \brief Mark the singleton as shutting down and stop the runner. + void begin_process_shutdown() noexcept { + m_process_state.store(ProcessState::shutting_down, std::memory_order_release); + + std::unique_ptr local_runner; + { + std::unique_lock lk(m_mtx); + while (is_transitioning_locked()) { + m_cv.wait(lk); + } + if (m_runner) { + m_last_offset_us.store(m_runner->offset_us(), std::memory_order_relaxed); + m_state = State::stopping; + local_runner = std::move(m_runner); + } else { + m_state = State::stopped; + } + } + + if (local_runner) { + try { + local_runner->stop(); + } catch (...) { + // no-throw + } + } + + { + std::lock_guard lk(m_mtx); + m_state = State::stopped; + } + m_cv.notify_all(); + } + + /// \brief Return true when process shutdown has started. + bool is_process_shutting_down() const noexcept { + return m_process_state.load(std::memory_order_acquire) == ProcessState::shutting_down; + } + + /// \brief Return true when process shutdown has started. + bool is_process_shutting_down_locked() const noexcept { + return is_process_shutting_down(); + } + + /// \brief Return number of successful atexit registrations. + uint32_t atexit_registration_count() const noexcept { + return m_atexit_registration_count.load(std::memory_order_relaxed); + } + + /// \brief Check runner status under lock. + bool is_running_locked() const noexcept { + return m_runner && m_runner->running(); + } + + /// \brief Return true when a start or stop transition is in progress. + bool is_transitioning_locked() const noexcept { + return m_state == State::starting || m_state == State::stopping; + } + + /// \brief Return true when configuration can be changed safely. + bool is_reconfigurable_locked() const noexcept { + return !is_process_shutting_down_locked() && m_state == State::stopped && !m_runner; + } + + /// \brief Build a runner with current server list and pool config. + std::unique_ptr build_runner_locked() { + std::vector servers; + if (m_has_custom_servers) { + servers = m_servers; + } else { + servers = NtpClientPool::build_default_servers(); + } + + NtpPoolConfig cfg = m_has_custom_pool_cfg ? m_pool_cfg : NtpPoolConfig{}; + NtpClientPool pool(cfg); + pool.set_servers(std::move(servers)); + + std::unique_ptr runner; + try { + runner.reset(new RunnerT(std::move(pool))); + } catch (...) { + return nullptr; + } + return runner; + } + + private: + mutable std::mutex m_mtx; + std::condition_variable m_cv; + State m_state{State::stopped}; + std::atomic m_process_state{ProcessState::alive}; + std::atomic m_last_offset_us{0}; + std::atomic m_atexit_registration_count{0}; + std::chrono::milliseconds m_interval{std::chrono::seconds(30)}; + bool m_measure_immediately{true}; + + bool m_has_custom_servers{false}; + std::vector m_servers; + + bool m_has_custom_pool_cfg{false}; + NtpPoolConfig m_pool_cfg{}; + + std::unique_ptr m_runner; + }; + + namespace detail { + template + struct NtpTimeServiceSingleton final { + static NtpTimeServiceT& instance() noexcept { + static NtpTimeServiceT* p_instance = []() noexcept { + NtpTimeServiceT* p_service = new NtpTimeServiceT{}; + p_service->register_process_shutdown_handler(); + return p_service; + }(); + return *p_instance; + } + + static void handle_process_exit() noexcept { + instance().begin_process_shutdown(); + } + }; + + template + struct NtpTimeServiceTestAccess final { + static void begin_process_shutdown() noexcept { + NtpTimeServiceSingleton::instance().begin_process_shutdown(); + } + + static bool is_process_shutting_down() noexcept { + return NtpTimeServiceSingleton::instance().is_process_shutting_down(); + } + + static uint32_t atexit_registration_count() noexcept { + return NtpTimeServiceSingleton::instance().atexit_registration_count(); + } + }; + } // namespace detail + +#if defined(TIME_SHIELD_TEST_FAKE_NTP) + /// \ingroup ntp + /// \brief NTP time service alias that uses a fake runner for tests. + using NtpTimeService = NtpTimeServiceT; +#else + /// \ingroup ntp + /// \brief NTP time service alias that uses the real pool runner. + using NtpTimeService = NtpTimeServiceT; +#endif + +namespace ntp { + + /// \ingroup ntp + /// \brief Initialize NTP time service and start background measurements. + /// \param interval Measurement interval. + /// \param measure_immediately Measure before first sleep if true. + /// \return True when background runner started. + inline bool init(std::chrono::milliseconds interval = std::chrono::seconds(30), + bool measure_immediately = true) { + return NtpTimeService::instance().init(interval, measure_immediately); + } + + /// \ingroup ntp + /// \brief Initialize NTP time service using milliseconds. + /// \param interval_ms Measurement interval in milliseconds. + /// \param measure_immediately Measure before first sleep if true. + /// \return True when background runner started. + inline bool init(int interval_ms, + bool measure_immediately = true) { + return NtpTimeService::instance().init(std::chrono::milliseconds(interval_ms), measure_immediately); + } + + /// \ingroup ntp + /// \brief Stop NTP time service. + inline void shutdown() { + NtpTimeService::instance().shutdown(); + } + + /// \ingroup ntp + /// \brief Return last estimated offset in microseconds. + /// \return Offset in microseconds (UTC - local realtime). + inline int64_t offset_us() noexcept { + return NtpTimeService::instance().offset_us(); + } + + /// \ingroup ntp + /// \brief Return current UTC time in microseconds based on offset. + /// \return UTC time in microseconds using last offset. + inline int64_t utc_time_us() noexcept { + return NtpTimeService::instance().utc_time_us(); + } + + /// \ingroup ntp + /// \brief Return current UTC time in milliseconds based on offset. + /// \return UTC time in milliseconds using last offset. + inline int64_t utc_time_ms() noexcept { + return NtpTimeService::instance().utc_time_ms(); + } + + /// \ingroup ntp + /// \brief Return current UTC time in seconds based on offset. + /// \return UTC time in seconds using last offset. + inline int64_t utc_time_sec() noexcept { + return NtpTimeService::instance().utc_time_sec(); + } + + /// \ingroup ntp + /// \brief Return whether last measurement updated the offset. + /// \return True when last measurement updated the offset. + inline bool last_measure_ok() noexcept { + return NtpTimeService::instance().last_measure_ok(); + } + + /// \ingroup ntp + /// \brief Return total number of measurement attempts. + /// \return Number of measurement attempts. + inline uint64_t measure_count() noexcept { + return NtpTimeService::instance().measure_count(); + } + + /// \ingroup ntp + /// \brief Return number of failed measurement attempts. + /// \return Number of failed measurement attempts. + inline uint64_t fail_count() noexcept { + return NtpTimeService::instance().fail_count(); + } + + /// \ingroup ntp + /// \brief Return realtime timestamp of last measurement attempt. + /// \return Realtime microseconds timestamp for last measurement attempt. + inline int64_t last_update_realtime_us() noexcept { + return NtpTimeService::instance().last_update_realtime_us(); + } + + /// \ingroup ntp + /// \brief Return realtime timestamp of last successful measurement. + /// \return Realtime microseconds timestamp for last successful measurement. + inline int64_t last_success_realtime_us() noexcept { + return NtpTimeService::instance().last_success_realtime_us(); + } + + /// \ingroup ntp + /// \brief Return true when last measurement is older than max_age. + /// \param max_age Maximum allowed age. + /// \return True when last measurement age exceeds max_age. + inline bool stale(std::chrono::milliseconds max_age) noexcept { + return NtpTimeService::instance().stale(max_age); + } + + /// \ingroup ntp + /// \brief Return true when last measurement is older than max_age_ms. + /// \param max_age_ms Maximum allowed age in milliseconds. + /// \return True when last measurement age exceeds max_age_ms. + inline bool stale(int max_age_ms) noexcept { + return NtpTimeService::instance().stale(max_age_ms); + } + +} // namespace ntp + +} // namespace time_shield + +#else // TIME_SHIELD_ENABLE_NTP_CLIENT + +namespace time_shield { + class NtpTimeService { + public: + static NtpTimeService& instance() { + static_assert(sizeof(void*) == 0, "NtpTimeService is disabled by configuration."); + return *reinterpret_cast(0); + } + }; +} // namespace time_shield + +#endif // _TIME_SHIELD_ENABLE_NTP_CLIENT + +#endif // TIME_SHIELD_HEADER_NTP_NTP_TIME_SERVICE_HPP_INCLUDED diff --git a/include/time_shield/ntp_client.hpp b/include/time_shield/ntp_client.hpp index 9f8631aa..540e5f67 100644 --- a/include/time_shield/ntp_client.hpp +++ b/include/time_shield/ntp_client.hpp @@ -1,180 +1,8 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_NTP_CLIENT_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_NTP_CLIENT_HPP_INCLUDED +#ifndef TIME_SHIELD_HEADER_NTP_CLIENT_HPP_INCLUDED +#define TIME_SHIELD_HEADER_NTP_CLIENT_HPP_INCLUDED -/// \file ntp_client.hpp -/// \brief Simple NTP client for querying time offset from NTP servers. -/// -/// Feature availability is controlled by `TIME_SHIELD_ENABLE_NTP_CLIENT`. -/// \ingroup ntp +#include -#include "config.hpp" - -#if TIME_SHIELD_ENABLE_NTP_CLIENT - -#include "time_utils.hpp" -#include "ntp_client/ntp_client_core.hpp" -#include "ntp_client/ntp_packet.hpp" -#include "ntp_client/udp_transport.hpp" - -#if TIME_SHIELD_PLATFORM_WINDOWS -# include "ntp_client/udp_transport_win.hpp" -#elif TIME_SHIELD_PLATFORM_UNIX -# include "ntp_client/udp_transport_posix.hpp" -#endif - -#include -#include -#include - -namespace time_shield { - -#if TIME_SHIELD_PLATFORM_WINDOWS - namespace detail { using PlatformUdpTransport = UdpTransportWin; } -#elif TIME_SHIELD_PLATFORM_UNIX - namespace detail { using PlatformUdpTransport = UdpTransportPosix; } -#endif - -#if TIME_SHIELD_PLATFORM_WINDOWS || TIME_SHIELD_PLATFORM_UNIX - - /// \ingroup ntp - /// \brief NTP client for measuring time offset. - class NtpClient { - public: - /// \brief Constructs NTP client with specified host and port. - /// \param server NTP server host name. - /// \param port NTP server port. - NtpClient(std::string server = "pool.ntp.org", int port = 123) - : m_host(std::move(server)) - , m_port(port) - , m_offset_us(0) - , m_delay_us(0) - , m_stratum(-1) - , m_is_success(false) { - now_realtime_us(); - } - - /// \brief Queries the NTP server and updates the local offset. - /// \return True when response parsed successfully. - /// \note Requires network connectivity and a reachable server. - bool query() { - last_error_code_slot() = 0; - -#if TIME_SHIELD_PLATFORM_WINDOWS - if (!WsaGuard::instance().success()) { - last_error_code_slot() = WsaGuard::instance().ret_code(); - m_is_success = false; - return false; - } -#endif - - detail::PlatformUdpTransport transport; - detail::NtpClientCore core; - - int error_code = 0; - int64_t offset = 0; - int64_t delay = 0; - int stratum = -1; - - const bool ok = core.query( - transport, - m_host, - m_port, - k_default_timeout_ms, - error_code, - offset, - delay, - stratum - ); - - last_error_code_slot() = error_code; - - if (!ok) { - m_delay_us = 0; - m_stratum = -1; - m_is_success = false; - return false; - } - - m_offset_us = offset; - m_delay_us = delay; - m_stratum = stratum; - m_is_success = true; - return true; - } - - /// \brief Returns whether the last NTP query was successful. - /// \return True when the last query updated internal state. - bool success() const noexcept { return m_is_success.load(); } - - /// \brief Returns the last measured offset in microseconds. - /// \return Offset in microseconds (UTC - local realtime). - int64_t offset_us() const noexcept { return m_offset_us; } - - /// \brief Returns the last measured delay in microseconds. - /// \return Round-trip delay estimate in microseconds. - int64_t delay_us() const noexcept { return m_delay_us; } - - /// \brief Returns the last received stratum value. - /// \return NTP stratum value. - int stratum() const noexcept { return m_stratum; } - - /// \brief Returns current UTC time in microseconds based on last NTP offset. - /// \return UTC time in microseconds using last offset. - int64_t utc_time_us() const noexcept { return now_realtime_us() + m_offset_us.load(); } - - /// \brief Returns current UTC time in milliseconds based on last NTP offset. - /// \return UTC time in milliseconds using last offset. - int64_t utc_time_ms() const noexcept { return utc_time_us() / 1000; } - - /// \brief Returns current UTC time as time_t (seconds since Unix epoch). - /// \return UTC time in seconds since Unix epoch. - time_t utc_time_sec() const noexcept { return static_cast(utc_time_us() / 1000000); } - - /// \brief Returns last socket error code (if any). - /// \return Error code from last query attempt. - int last_error_code() const noexcept { return last_error_code_slot(); } - - private: - std::string m_host; - int m_port; - std::atomic m_offset_us; - std::atomic m_delay_us; - std::atomic m_stratum; - std::atomic m_is_success; - static const int k_default_timeout_ms = 5000; - - static int& last_error_code_slot() noexcept { - static TIME_SHIELD_THREAD_LOCAL int value = 0; - return value; - } - }; - -#else - - class NtpClient { - public: - NtpClient() { - static_assert(sizeof(void*) == 0, "NtpClient is disabled by configuration."); - } - }; - -#endif // platform switch - -} // namespace time_shield - -#else // TIME_SHIELD_ENABLE_NTP_CLIENT - -namespace time_shield { - class NtpClient { - public: - NtpClient() { - static_assert(sizeof(void*) == 0, "NtpClient is disabled by configuration."); - } - }; -} // namespace time_shield - -#endif // TIME_SHIELD_ENABLE_NTP_CLIENT - -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_NTP_CLIENT_HPP_INCLUDED +#endif // TIME_SHIELD_HEADER_NTP_CLIENT_HPP_INCLUDED diff --git a/include/time_shield/ntp_client/ntp_client_core.hpp b/include/time_shield/ntp_client/ntp_client_core.hpp index 53270e5d..2506cc2c 100644 --- a/include/time_shield/ntp_client/ntp_client_core.hpp +++ b/include/time_shield/ntp_client/ntp_client_core.hpp @@ -1,87 +1,8 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_NTP_CLIENT_NTP_CLIENT_CORE_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_NTP_CLIENT_NTP_CLIENT_CORE_HPP_INCLUDED +#ifndef TIME_SHIELD_HEADER_NTP_CLIENT_NTP_CLIENT_CORE_HPP_INCLUDED +#define TIME_SHIELD_HEADER_NTP_CLIENT_NTP_CLIENT_CORE_HPP_INCLUDED -#include "ntp_packet.hpp" -#include "udp_transport.hpp" +#include -#include -#include - -namespace time_shield { -namespace detail { - - /// \brief Core NTP query logic that parses packets and computes offsets. - class NtpClientCore { - public: - /// \brief Perform one NTP transaction using a UDP transport. - bool query(IUdpTransport& transport, - const std::string& host, - int port, - int timeout_ms, - int& out_error_code, - int64_t& out_offset_us, - int64_t& out_delay_us, - int& out_stratum) noexcept { - out_error_code = 0; - out_offset_us = 0; - out_delay_us = 0; - out_stratum = -1; - - uint64_t now_us = 0; - if (!get_now_us(now_us)) { - out_error_code = -1; - return false; - } - - NtpPacket pkt{}; - fill_client_packet(pkt, now_us); - - NtpPacket reply{}; - UdpRequest req; - req.host = host; - req.port = port; - req.send_data = &pkt; - req.send_size = sizeof(pkt); - req.recv_data = &reply; - req.recv_size = sizeof(reply); - req.timeout_ms = timeout_ms; - - if (!transport.transact(req, out_error_code)) { - if (out_error_code == 0) { - out_error_code = -1; - } - return false; - } - - uint64_t arrival_us = 0; - if (!get_now_us(arrival_us)) { - out_error_code = -1; - return false; - } - - if (!parse_server_packet(reply, arrival_us, out_offset_us, out_delay_us, out_stratum, out_error_code)) { - if (out_error_code == 0) { - out_error_code = -1; - } - return false; - } - - return true; - } - - private: - /// \brief Read current realtime clock in microseconds. - static bool get_now_us(uint64_t& out) noexcept { - const int64_t v = time_shield::now_realtime_us(); - if (v < 0) return false; - out = static_cast(v); - return true; - } - }; - -} // namespace detail -} // namespace time_shield - -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_NTP_CLIENT_NTP_CLIENT_CORE_HPP_INCLUDED +#endif // TIME_SHIELD_HEADER_NTP_CLIENT_NTP_CLIENT_CORE_HPP_INCLUDED diff --git a/include/time_shield/ntp_client/ntp_packet.hpp b/include/time_shield/ntp_client/ntp_packet.hpp index ec19b72e..48f5b9c9 100644 --- a/include/time_shield/ntp_client/ntp_packet.hpp +++ b/include/time_shield/ntp_client/ntp_packet.hpp @@ -1,166 +1,8 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_NTP_CLIENT_NTP_PACKET_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_NTP_CLIENT_NTP_PACKET_HPP_INCLUDED +#ifndef TIME_SHIELD_HEADER_NTP_CLIENT_NTP_PACKET_HPP_INCLUDED +#define TIME_SHIELD_HEADER_NTP_CLIENT_NTP_PACKET_HPP_INCLUDED -#include -#include +#include -#if TIME_SHIELD_PLATFORM_WINDOWS -# include -#else -# include -#endif - -namespace time_shield { -namespace detail { - - /// \ingroup ntp - /// \brief NTP packet layout (48 bytes). - struct NtpPacket { - uint8_t li_vn_mode; - uint8_t stratum; - uint8_t poll; - uint8_t precision; - uint32_t root_delay; - uint32_t root_dispersion; - uint32_t ref_id; - uint32_t ref_ts_sec; - uint32_t ref_ts_frac; - uint32_t orig_ts_sec; - uint32_t orig_ts_frac; - uint32_t recv_ts_sec; - uint32_t recv_ts_frac; - uint32_t tx_ts_sec; - uint32_t tx_ts_frac; - }; - - static_assert(sizeof(NtpPacket) == 48, "NtpPacket must be 48 bytes"); - - /// \ingroup ntp - /// \brief Protocol-level error codes for NTP parsing. - enum NtpProtoError { - NTP_EPROTO_BASE = -10000, - NTP_E_BAD_MODE = NTP_EPROTO_BASE - 1, - NTP_E_BAD_VERSION = NTP_EPROTO_BASE - 2, - NTP_E_BAD_LI = NTP_EPROTO_BASE - 3, - NTP_E_BAD_STRATUM = NTP_EPROTO_BASE - 4, - NTP_E_KOD = NTP_EPROTO_BASE - 5, - NTP_E_BAD_TS = NTP_EPROTO_BASE - 6 - }; - - /// \brief Extract leap indicator from LI/VN/Mode field. - static inline uint8_t ntp_li(uint8_t li_vn_mode) noexcept { - return static_cast((li_vn_mode >> 6) & 0x03); - } - - /// \brief Extract version number from LI/VN/Mode field. - static inline uint8_t ntp_vn(uint8_t li_vn_mode) noexcept { - return static_cast((li_vn_mode >> 3) & 0x07); - } - - /// \brief Extract mode from LI/VN/Mode field. - static inline uint8_t ntp_mode(uint8_t li_vn_mode) noexcept { - return static_cast(li_vn_mode & 0x07); - } - - /// \brief Convert NTP fractional seconds to microseconds. - static inline uint64_t ntp_frac_to_us(uint32_t frac_net) noexcept { - const uint64_t frac = static_cast(ntohl(frac_net)); - return (frac * 1000000ULL) >> 32; - } - - /// \brief Convert NTP timestamp parts to Unix microseconds. - static inline bool ntp_ts_to_unix_us(uint32_t sec_net, uint32_t frac_net, uint64_t& out_us) noexcept { - static const int64_t NTP_TIMESTAMP_DELTA = 2208988800ll; - const int64_t sec = static_cast(ntohl(sec_net)) - NTP_TIMESTAMP_DELTA; - if (sec < 0) return false; - out_us = static_cast(sec) * 1000000ULL + ntp_frac_to_us(frac_net); - return true; - } - - /// \brief Fill an NTP client request packet using local time. - static inline void fill_client_packet(NtpPacket& pkt, uint64_t now_us) { - std::memset(&pkt, 0, sizeof(pkt)); - pkt.li_vn_mode = static_cast((0 << 6) | (3 << 3) | 3); // LI=0, VN=3, Mode=3 - - const uint64_t sec = now_us / 1000000 + 2208988800ULL; - const uint64_t frac = ((now_us % 1000000) * 0x100000000ULL) / 1000000; - - pkt.tx_ts_sec = htonl(static_cast(sec)); - pkt.tx_ts_frac = htonl(static_cast(frac)); - } - - /// \brief Parse server response and compute offset and delay. - static inline bool parse_server_packet(const NtpPacket& pkt, - uint64_t arrival_us, - int64_t& offset_us, - int64_t& delay_us, - int& stratum, - int& out_error_code) noexcept { - const uint8_t li = ntp_li(pkt.li_vn_mode); - const uint8_t vn = ntp_vn(pkt.li_vn_mode); - const uint8_t mode = ntp_mode(pkt.li_vn_mode); - - if (mode != 4) { - out_error_code = NTP_E_BAD_MODE; - return false; - } - if (vn < 3 || vn > 4) { - out_error_code = NTP_E_BAD_VERSION; - return false; - } - if (li == 3) { - out_error_code = NTP_E_BAD_LI; - return false; - } - if (pkt.stratum == 0) { - out_error_code = NTP_E_KOD; - return false; - } - if (pkt.stratum >= 16) { - out_error_code = NTP_E_BAD_STRATUM; - return false; - } - - uint64_t originate_us = 0; - uint64_t receive_us = 0; - uint64_t transmit_us = 0; - - if (!ntp_ts_to_unix_us(pkt.orig_ts_sec, pkt.orig_ts_frac, originate_us)) { - out_error_code = NTP_E_BAD_TS; - return false; - } - if (!ntp_ts_to_unix_us(pkt.recv_ts_sec, pkt.recv_ts_frac, receive_us)) { - out_error_code = NTP_E_BAD_TS; - return false; - } - if (!ntp_ts_to_unix_us(pkt.tx_ts_sec, pkt.tx_ts_frac, transmit_us)) { - out_error_code = NTP_E_BAD_TS; - return false; - } - - const int64_t t1 = static_cast(originate_us); - const int64_t t2 = static_cast(receive_us); - const int64_t t3 = static_cast(transmit_us); - const int64_t t4 = static_cast(arrival_us); - - if (t3 < t2) { - out_error_code = NTP_E_BAD_TS; - return false; - } - - offset_us = ((t2 - t1) + (t3 - t4)) / 2; - delay_us = (t4 - t1) - (t3 - t2); - if (delay_us < 0) { - out_error_code = NTP_E_BAD_TS; - return false; - } - stratum = pkt.stratum; - return true; - } - -} // namespace detail -} // namespace time_shield - -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_NTP_CLIENT_NTP_PACKET_HPP_INCLUDED +#endif // TIME_SHIELD_HEADER_NTP_CLIENT_NTP_PACKET_HPP_INCLUDED diff --git a/include/time_shield/ntp_client/udp_transport.hpp b/include/time_shield/ntp_client/udp_transport.hpp index ec588cff..7bf22918 100644 --- a/include/time_shield/ntp_client/udp_transport.hpp +++ b/include/time_shield/ntp_client/udp_transport.hpp @@ -1,35 +1,8 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_NTP_CLIENT_UDP_TRANSPORT_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_NTP_CLIENT_UDP_TRANSPORT_HPP_INCLUDED +#ifndef TIME_SHIELD_HEADER_NTP_CLIENT_UDP_TRANSPORT_HPP_INCLUDED +#define TIME_SHIELD_HEADER_NTP_CLIENT_UDP_TRANSPORT_HPP_INCLUDED -#include -#include +#include -namespace time_shield { -namespace detail { - - /// \brief UDP request parameters for NTP transactions. - struct UdpRequest { - std::string host; ///< Target host name or IP address. - int port = 123; ///< Target port. - const void* send_data = nullptr; ///< Pointer to outgoing payload. - std::size_t send_size = 0; ///< Outgoing payload size in bytes. - void* recv_data = nullptr; ///< Pointer to receive buffer. - std::size_t recv_size = 0; ///< Receive buffer size in bytes. - int timeout_ms = 5000; ///< Receive timeout in milliseconds. - }; - - /// \brief Abstract UDP transport interface for NTP queries. - class IUdpTransport { - public: - /// \brief Virtual destructor. - virtual ~IUdpTransport() {} - /// \brief Send request and receive response over UDP. - virtual bool transact(const UdpRequest& req, int& out_error_code) noexcept = 0; - }; - -} // namespace detail -} // namespace time_shield - -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_NTP_CLIENT_UDP_TRANSPORT_HPP_INCLUDED +#endif // TIME_SHIELD_HEADER_NTP_CLIENT_UDP_TRANSPORT_HPP_INCLUDED diff --git a/include/time_shield/ntp_client/udp_transport_posix.hpp b/include/time_shield/ntp_client/udp_transport_posix.hpp index 945e4e5b..39855bee 100644 --- a/include/time_shield/ntp_client/udp_transport_posix.hpp +++ b/include/time_shield/ntp_client/udp_transport_posix.hpp @@ -1,95 +1,8 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_NTP_CLIENT_UDP_TRANSPORT_POSIX_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_NTP_CLIENT_UDP_TRANSPORT_POSIX_HPP_INCLUDED +#ifndef TIME_SHIELD_HEADER_NTP_CLIENT_UDP_TRANSPORT_POSIX_HPP_INCLUDED +#define TIME_SHIELD_HEADER_NTP_CLIENT_UDP_TRANSPORT_POSIX_HPP_INCLUDED -#if TIME_SHIELD_PLATFORM_UNIX +#include -#include "udp_transport.hpp" - -#include -#include -#include -#include -#include -#include -#include - -namespace time_shield { -namespace detail { - - /// \brief POSIX UDP transport for NTP queries. - class UdpTransportPosix : public IUdpTransport { - public: - /// \brief Send request and receive response over UDP. - bool transact(const UdpRequest& req, int& out_error_code) noexcept override { - out_error_code = 0; - const int sock = ::socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); - if (sock < 0) { - out_error_code = errno; - return false; - } - - addrinfo hints{}, *res = nullptr; - hints.ai_family = AF_INET; - hints.ai_socktype = SOCK_DGRAM; - hints.ai_protocol = IPPROTO_UDP; - - const int resolve_code = getaddrinfo(req.host.c_str(), nullptr, &hints, &res); - if (resolve_code != 0 || !res) { - out_error_code = (resolve_code != 0) ? resolve_code : errno; - ::close(sock); - return false; - } - - sockaddr_in addr{}; - addr.sin_family = AF_INET; - addr.sin_port = htons(static_cast(req.port)); - addr.sin_addr = reinterpret_cast(res->ai_addr)->sin_addr; - freeaddrinfo(res); - res = nullptr; - - const int timeout_ms = req.timeout_ms > 0 ? req.timeout_ms : 5000; - timeval tv; - tv.tv_sec = timeout_ms / 1000; - tv.tv_usec = (timeout_ms % 1000) * 1000; - ::setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); - - const ssize_t sent = ::sendto(sock, - req.send_data, - req.send_size, - 0, - reinterpret_cast(&addr), - sizeof(addr)); - if (sent < 0 || static_cast(sent) != req.send_size) { - out_error_code = errno; - ::close(sock); - return false; - } - - sockaddr_in from{}; - socklen_t from_len = sizeof(from); - const ssize_t received = ::recvfrom(sock, - req.recv_data, - req.recv_size, - 0, - reinterpret_cast(&from), - &from_len); - - if (received < 0 || static_cast(received) != req.recv_size) { - out_error_code = errno; - ::close(sock); - return false; - } - - ::close(sock); - return true; - } - }; - -} // namespace detail -} // namespace time_shield - -#endif // _TIME_SHIELD_PLATFORM_UNIX - -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_NTP_CLIENT_UDP_TRANSPORT_POSIX_HPP_INCLUDED +#endif // TIME_SHIELD_HEADER_NTP_CLIENT_UDP_TRANSPORT_POSIX_HPP_INCLUDED diff --git a/include/time_shield/ntp_client/udp_transport_win.hpp b/include/time_shield/ntp_client/udp_transport_win.hpp index 90ba07e3..68739995 100644 --- a/include/time_shield/ntp_client/udp_transport_win.hpp +++ b/include/time_shield/ntp_client/udp_transport_win.hpp @@ -1,95 +1,8 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_NTP_CLIENT_UDP_TRANSPORT_WIN_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_NTP_CLIENT_UDP_TRANSPORT_WIN_HPP_INCLUDED +#ifndef TIME_SHIELD_HEADER_NTP_CLIENT_UDP_TRANSPORT_WIN_HPP_INCLUDED +#define TIME_SHIELD_HEADER_NTP_CLIENT_UDP_TRANSPORT_WIN_HPP_INCLUDED -#if TIME_SHIELD_PLATFORM_WINDOWS +#include -#include "wsa_guard.hpp" -#include "udp_transport.hpp" - -#include -#include -#include - -namespace time_shield { -namespace detail { - - /// \brief Windows UDP transport for NTP queries. - class UdpTransportWin : public IUdpTransport { - public: - /// \brief Send request and receive response over UDP. - bool transact(const UdpRequest& req, int& out_error_code) noexcept override { - out_error_code = 0; - if (!WsaGuard::instance().success()) { - out_error_code = WsaGuard::instance().ret_code(); - return false; - } - - SOCKET sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); - if (sock == INVALID_SOCKET) { - out_error_code = WSAGetLastError(); - return false; - } - - sockaddr_in addr{}; - addr.sin_family = AF_INET; - addr.sin_port = htons(static_cast(req.port)); - - addrinfo hints{}, *res = nullptr; - hints.ai_family = AF_INET; - hints.ai_socktype = SOCK_DGRAM; - hints.ai_protocol = IPPROTO_UDP; - - if (getaddrinfo(req.host.c_str(), nullptr, &hints, &res) != 0 || !res) { - out_error_code = WSAGetLastError(); - closesocket(sock); - return false; - } - addr.sin_addr = reinterpret_cast(res->ai_addr)->sin_addr; - - const int timeout_ms = req.timeout_ms > 0 ? req.timeout_ms : 5000; - DWORD timeout = static_cast(timeout_ms); - setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast(&timeout), sizeof(timeout)); - - const int send_res = sendto(sock, - static_cast(req.send_data), - static_cast(req.send_size), - 0, - reinterpret_cast(&addr), - sizeof(addr)); - if (send_res == SOCKET_ERROR || static_cast(send_res) != req.send_size) { - out_error_code = WSAGetLastError(); - freeaddrinfo(res); - closesocket(sock); - return false; - } - - sockaddr_in from{}; - int from_len = sizeof(from); - const int recv_res = recvfrom(sock, - static_cast(req.recv_data), - static_cast(req.recv_size), - 0, - reinterpret_cast(&from), - &from_len); - - if (recv_res == SOCKET_ERROR || static_cast(recv_res) != req.recv_size) { - out_error_code = WSAGetLastError(); - freeaddrinfo(res); - closesocket(sock); - return false; - } - - freeaddrinfo(res); - closesocket(sock); - return true; - } - }; - -} // namespace detail -} // namespace time_shield - -#endif // _TIME_SHIELD_PLATFORM_WINDOWS - -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_NTP_CLIENT_UDP_TRANSPORT_WIN_HPP_INCLUDED +#endif // TIME_SHIELD_HEADER_NTP_CLIENT_UDP_TRANSPORT_WIN_HPP_INCLUDED diff --git a/include/time_shield/ntp_client/wsa_guard.hpp b/include/time_shield/ntp_client/wsa_guard.hpp index cc3b2406..1d16dd2c 100644 --- a/include/time_shield/ntp_client/wsa_guard.hpp +++ b/include/time_shield/ntp_client/wsa_guard.hpp @@ -1,69 +1,8 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_NTP_CLIENT_WSA_GUARD_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_NTP_CLIENT_WSA_GUARD_HPP_INCLUDED +#ifndef TIME_SHIELD_HEADER_NTP_CLIENT_WSA_GUARD_HPP_INCLUDED +#define TIME_SHIELD_HEADER_NTP_CLIENT_WSA_GUARD_HPP_INCLUDED -/// \file wsa_guard.hpp -/// \brief Singleton guard for WinSock initialization. -/// \ingroup ntp +#include -#if TIME_SHIELD_HAS_WINSOCK -# ifndef WIN32_LEAN_AND_MEAN -# define WIN32_LEAN_AND_MEAN -# endif -# ifndef NOMINMAX -# define NOMINMAX -# endif -# include // Must be included before windows.h -# include -# include // Optional, but safe if later needed -#else -# error "WsaGuard requires WinSock support" -#endif -#include -#include - -namespace time_shield { - - /// \ingroup ntp - /// \brief Singleton guard for WinSock initialization. - class WsaGuard { - public: - /// \brief Returns the singleton instance, initializing WSA if needed. - static const WsaGuard& instance() { - static WsaGuard instance; - return instance; - } - - /// \brief Returns whether WSAStartup was successful. - bool success() const noexcept { - return m_ret_code == 0; - } - - /// \brief Returns the result code from WSAStartup. - int ret_code() const noexcept { - return m_ret_code; - } - - /// \brief Returns the WSAData structure (valid only if successful). - const WSADATA& data() const noexcept { - return m_wsa_data; - } - - private: - WsaGuard() { - m_ret_code = WSAStartup(MAKEWORD(2, 2), &m_wsa_data); - } - - ~WsaGuard() = default; - - WsaGuard(const WsaGuard&) = delete; - WsaGuard& operator=(const WsaGuard&) = delete; - - WSADATA m_wsa_data{}; - int m_ret_code = -1; - }; - -} // namespace time_shield - -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_NTP_CLIENT_WSA_GUARD_HPP_INCLUDED +#endif // TIME_SHIELD_HEADER_NTP_CLIENT_WSA_GUARD_HPP_INCLUDED diff --git a/include/time_shield/ntp_client_pool.hpp b/include/time_shield/ntp_client_pool.hpp index 3d2917c2..adfcb64d 100644 --- a/include/time_shield/ntp_client_pool.hpp +++ b/include/time_shield/ntp_client_pool.hpp @@ -1,733 +1,8 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_NTP_CLIENT_POOL_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_NTP_CLIENT_POOL_HPP_INCLUDED +#ifndef TIME_SHIELD_HEADER_NTP_CLIENT_POOL_HPP_INCLUDED +#define TIME_SHIELD_HEADER_NTP_CLIENT_POOL_HPP_INCLUDED -#include "config.hpp" +#include -#if TIME_SHIELD_ENABLE_NTP_CLIENT - -#include "ntp_client.hpp" -#include "time_utils.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace time_shield { - - /// \ingroup ntp - /// \brief NTP measurement sample (one server response). - struct NtpSample { - std::string host; ///< Server host name. - int port = 123; ///< Server port. - bool is_ok = false; ///< Indicates successful response parsing. - int error_code = 0; ///< Error code when query or parsing failed. - int stratum = -1; ///< NTP stratum level reported by server. - int64_t offset_us = 0; ///< Offset between UTC and local realtime, microseconds. - int64_t delay_us = 0; ///< Estimated round-trip delay, microseconds. - int64_t max_delay_us = 0; ///< Maximum acceptable delay for this sample. - }; - - /// \ingroup ntp - /// \brief Per-server configuration. - struct NtpServerConfig { - std::string host; ///< Server host name. - int port = 123; ///< Server port. - - std::chrono::milliseconds min_interval{15000}; ///< Minimum time between queries to the same server. - std::chrono::milliseconds max_delay{250}; ///< Maximum acceptable delay for responses from this server. - - std::chrono::milliseconds backoff_initial{15000}; ///< Initial backoff after failure. - std::chrono::milliseconds backoff_max{std::chrono::minutes(10)}; ///< Maximum backoff interval after repeated failures. - }; - - /// \ingroup ntp - /// \brief Pool configuration. - struct NtpPoolConfig { - std::size_t sample_servers = 5; ///< Number of servers to sample per measurement. - std::size_t min_valid_samples = 3; ///< Minimum number of valid samples required to update offset. - - /// \brief Aggregation strategy for offset estimation. - enum class Aggregation { - Median, - BestDelay, - MedianMadTrim - } aggregation = Aggregation::Median; - - double smoothing_alpha = 1.0; ///< Exponential smoothing factor for offset updates. - std::uint64_t rng_seed = 0; ///< Random seed for server sampling; 0 uses time-based seed. - }; - - /// \ingroup ntp - /// \brief Pool of NTP servers: rate-limited multi-server offset estimation. - /// \tparam ClientT NTP client type with interface: - /// ClientT(const std::string& host, int port); - /// bool query(); // may throw - /// int last_error_code() const; - /// int64_t offset_us() const; - /// int64_t delay_us() const; - /// int stratum() const; - template - class NtpClientPoolT { - public: - /// \brief Construct pool with configuration. - /// \param cfg Pool configuration. - explicit NtpClientPoolT(NtpPoolConfig cfg = {}) - : m_cfg(std::move(cfg)) - , m_offset_us(0) - , m_rng(init_seed(m_cfg.rng_seed)) {} - - NtpClientPoolT(const NtpClientPoolT&) = delete; - NtpClientPoolT& operator=(const NtpClientPoolT&) = delete; - - /// \brief Move-construct pool state. - NtpClientPoolT(NtpClientPoolT&& other) noexcept - : m_cfg() - , m_offset_us(0) - , m_rng(init_seed(other.m_cfg.rng_seed)) { - std::lock_guard lk(other.m_mtx); - m_cfg = other.m_cfg; - m_servers = std::move(other.m_servers); - m_last_samples = std::move(other.m_last_samples); - m_offset_us.store(other.m_offset_us.load()); - m_rng = std::move(other.m_rng); - } - - /// \brief Move-assign pool state. - NtpClientPoolT& operator=(NtpClientPoolT&& other) noexcept { - if (this == &other) { - return *this; - } - std::lock(m_mtx, other.m_mtx); - std::lock_guard lk1(m_mtx, std::adopt_lock); - std::lock_guard lk2(other.m_mtx, std::adopt_lock); - - m_cfg = other.m_cfg; - m_servers = std::move(other.m_servers); - m_last_samples = std::move(other.m_last_samples); - m_offset_us.store(other.m_offset_us.load()); - m_rng = std::move(other.m_rng); - return *this; - } - - /// \brief Replace server list (keeps pool config). - /// \param servers Server configurations to use. - void set_servers(std::vector servers) { - std::lock_guard lk(m_mtx); - m_servers.clear(); - m_servers.reserve(servers.size()); - for (auto& server_cfg : servers) { - ServerState state; - state.cfg = std::move(server_cfg); - m_servers.push_back(std::move(state)); - } - } - - /// \brief Add one server. - /// \param server_cfg Server configuration to add. - void add_server(NtpServerConfig server_cfg) { - std::lock_guard lk(m_mtx); - ServerState state; - state.cfg = std::move(server_cfg); - m_servers.push_back(std::move(state)); - } - - /// \brief Build a conservative default server list. - /// \return Default server list with conservative timing settings. - static std::vector build_default_servers() { - std::vector servers; - servers.reserve(160); - - auto add = [&servers](const char* host) { - NtpServerConfig cfg; - cfg.host = host; - cfg.min_interval = std::chrono::milliseconds{60000}; - cfg.max_delay = std::chrono::milliseconds{500}; - cfg.backoff_initial = std::chrono::milliseconds{120000}; - cfg.backoff_max = std::chrono::minutes(10); - servers.push_back(std::move(cfg)); - }; - - add("time.google.com"); - add("time1.google.com"); - add("time2.google.com"); - add("time3.google.com"); - add("time4.google.com"); - - add("time.cloudflare.com"); - - add("time.facebook.com"); - add("time1.facebook.com"); - add("time2.facebook.com"); - add("time3.facebook.com"); - add("time4.facebook.com"); - add("time5.facebook.com"); - - add("time.windows.com"); - - add("time.apple.com"); - add("time1.apple.com"); - add("time2.apple.com"); - add("time3.apple.com"); - add("time4.apple.com"); - add("time5.apple.com"); - add("time6.apple.com"); - add("time7.apple.com"); - add("time.euro.apple.com"); - - add("time-a-g.nist.gov"); - add("time-b-g.nist.gov"); - add("time-c-g.nist.gov"); - add("time-d-g.nist.gov"); - add("time-a-wwv.nist.gov"); - add("time-b-wwv.nist.gov"); - add("time-c-wwv.nist.gov"); - add("time-d-wwv.nist.gov"); - add("time-a-b.nist.gov"); - add("time-b-b.nist.gov"); - add("time-c-b.nist.gov"); - add("time-d-b.nist.gov"); - add("time.nist.gov"); - add("utcnist.colorado.edu"); - add("utcnist2.colorado.edu"); - - add("ntp1.vniiftri.ru"); - add("ntp2.vniiftri.ru"); - add("ntp3.vniiftri.ru"); - add("ntp4.vniiftri.ru"); - add("ntp1.niiftri.irkutsk.ru"); - add("ntp2.niiftri.irkutsk.ru"); - add("vniiftri.khv.ru"); - add("vniiftri2.khv.ru"); - add("ntp21.vniiftri.ru"); - - add("ntp.mobatime.ru"); - - add("ntp1.stratum1.ru"); - add("ntp2.stratum1.ru"); - add("ntp3.stratum1.ru"); - add("ntp4.stratum1.ru"); - add("ntp5.stratum1.ru"); - add("ntp2.stratum2.ru"); - add("ntp3.stratum2.ru"); - add("ntp4.stratum2.ru"); - add("ntp5.stratum2.ru"); - - add("stratum1.net"); - - add("ntp.time.in.ua"); - add("ntp2.time.in.ua"); - add("ntp3.time.in.ua"); - - add("ntp.ru"); - - add("ts1.aco.net"); - add("ts2.aco.net"); - - add("ntp1.net.berkeley.edu"); - add("ntp2.net.berkeley.edu"); - - add("ntp.gsu.edu"); - - add("tick.usask.ca"); - add("tock.usask.ca"); - - add("ntp.nsu.ru"); - add("ntp.rsu.edu.ru"); - - add("ntp.nict.jp"); - - add("x.ns.gin.ntt.net"); - add("y.ns.gin.ntt.net"); - - add("clock.nyc.he.net"); - add("clock.sjc.he.net"); - - add("ntp.fiord.ru"); - - add("gbg1.ntp.se"); - add("gbg2.ntp.se"); - add("mmo1.ntp.se"); - add("mmo2.ntp.se"); - add("sth1.ntp.se"); - add("sth2.ntp.se"); - add("svl1.ntp.se"); - add("svl2.ntp.se"); - - add("clock.isc.org"); - - add("pool.ntp.org"); - add("0.pool.ntp.org"); - add("1.pool.ntp.org"); - add("2.pool.ntp.org"); - add("3.pool.ntp.org"); - - add("europe.pool.ntp.org"); - add("0.europe.pool.ntp.org"); - add("1.europe.pool.ntp.org"); - add("2.europe.pool.ntp.org"); - add("3.europe.pool.ntp.org"); - - add("asia.pool.ntp.org"); - add("0.asia.pool.ntp.org"); - add("1.asia.pool.ntp.org"); - add("2.asia.pool.ntp.org"); - add("3.asia.pool.ntp.org"); - - add("ru.pool.ntp.org"); - add("0.ru.pool.ntp.org"); - add("1.ru.pool.ntp.org"); - add("2.ru.pool.ntp.org"); - add("3.ru.pool.ntp.org"); - - add("0.gentoo.pool.ntp.org"); - add("1.gentoo.pool.ntp.org"); - add("2.gentoo.pool.ntp.org"); - add("3.gentoo.pool.ntp.org"); - - add("0.arch.pool.ntp.org"); - add("1.arch.pool.ntp.org"); - add("2.arch.pool.ntp.org"); - add("3.arch.pool.ntp.org"); - - add("0.fedora.pool.ntp.org"); - add("1.fedora.pool.ntp.org"); - add("2.fedora.pool.ntp.org"); - add("3.fedora.pool.ntp.org"); - - add("0.opensuse.pool.ntp.org"); - add("1.opensuse.pool.ntp.org"); - add("2.opensuse.pool.ntp.org"); - add("3.opensuse.pool.ntp.org"); - - add("0.centos.pool.ntp.org"); - add("1.centos.pool.ntp.org"); - add("2.centos.pool.ntp.org"); - add("3.centos.pool.ntp.org"); - - add("0.debian.pool.ntp.org"); - add("1.debian.pool.ntp.org"); - add("2.debian.pool.ntp.org"); - add("3.debian.pool.ntp.org"); - - add("0.ubuntu.pool.ntp.org"); - add("1.ubuntu.pool.ntp.org"); - add("2.ubuntu.pool.ntp.org"); - add("3.ubuntu.pool.ntp.org"); - - add("0.askozia.pool.ntp.org"); - add("1.askozia.pool.ntp.org"); - add("2.askozia.pool.ntp.org"); - add("3.askozia.pool.ntp.org"); - - add("0.freebsd.pool.ntp.org"); - add("1.freebsd.pool.ntp.org"); - add("2.freebsd.pool.ntp.org"); - add("3.freebsd.pool.ntp.org"); - - add("0.netbsd.pool.ntp.org"); - add("1.netbsd.pool.ntp.org"); - add("2.netbsd.pool.ntp.org"); - add("3.netbsd.pool.ntp.org"); - - add("0.openbsd.pool.ntp.org"); - add("1.openbsd.pool.ntp.org"); - add("2.openbsd.pool.ntp.org"); - add("3.openbsd.pool.ntp.org"); - - add("0.dragonfly.pool.ntp.org"); - add("1.dragonfly.pool.ntp.org"); - add("2.dragonfly.pool.ntp.org"); - add("3.dragonfly.pool.ntp.org"); - - add("0.pfsense.pool.ntp.org"); - add("1.pfsense.pool.ntp.org"); - add("2.pfsense.pool.ntp.org"); - add("3.pfsense.pool.ntp.org"); - - add("0.opnsense.pool.ntp.org"); - add("1.opnsense.pool.ntp.org"); - add("2.opnsense.pool.ntp.org"); - add("3.opnsense.pool.ntp.org"); - - add("0.smartos.pool.ntp.org"); - add("1.smartos.pool.ntp.org"); - add("2.smartos.pool.ntp.org"); - add("3.smartos.pool.ntp.org"); - - add("0.android.pool.ntp.org"); - add("1.android.pool.ntp.org"); - add("2.android.pool.ntp.org"); - add("3.android.pool.ntp.org"); - - add("0.amazon.pool.ntp.org"); - add("1.amazon.pool.ntp.org"); - add("2.amazon.pool.ntp.org"); - add("3.amazon.pool.ntp.org"); - - return servers; - } - - /// \brief Replace server list with a conservative default set. - void set_default_servers() { - set_servers(build_default_servers()); - } - - /// \brief Clear server list. - void clear_servers() { - std::lock_guard lk(m_mtx); - m_servers.clear(); - } - - /// \brief Perform measurement using current config (queries up to sample_servers). - /// \return True when pool offset updated. - bool measure() { - const auto cfg = config(); - return measure_n(cfg.sample_servers); - } - - /// \brief Perform measurement using a custom number of servers. - /// \param servers_to_sample Number of servers to query in this measurement. - /// \return True when pool offset updated. - bool measure_n(std::size_t servers_to_sample) { - std::vector picked; - NtpPoolConfig cfg; - { - std::lock_guard lk(m_mtx); - cfg = m_cfg; - picked = pick_servers_locked(servers_to_sample); - } - - std::vector samples; - samples.reserve(picked.size()); - - for (std::size_t idx : picked) { - samples.push_back(query_one(idx)); - } - - const bool is_updated = update_from_samples(samples, cfg); - - { - std::lock_guard lk(m_mtx); - m_last_samples = std::move(samples); - } - - return is_updated; - } - - /// \brief Last estimated pool offset (µs). - /// \return Offset in microseconds (UTC - local realtime). - int64_t offset_us() const noexcept { return m_offset_us.load(); } - - /// \brief Current UTC time in microseconds based on pool offset. - /// \return UTC time in microseconds using pool offset. - int64_t utc_time_us() const noexcept { return now_realtime_us() + m_offset_us.load(); } - - /// \brief Current UTC time in milliseconds based on pool offset. - /// \return UTC time in milliseconds using pool offset. - int64_t utc_time_ms() const noexcept { return utc_time_us() / 1000; } - - /// \brief Current UTC time in seconds based on pool offset. - /// \return UTC time in seconds using pool offset. - int64_t utc_time_sec() const noexcept { return utc_time_us() / 1000000; } - - /// \brief Returns last measurement samples (copy). - /// \return Copy of samples from the last measurement. - std::vector last_samples() const { - std::lock_guard lk(m_mtx); - return m_last_samples; - } - - /// \brief Apply pre-collected samples (testing/offline). - /// \param samples Sample list to apply. - /// \return True when pool offset updated. - /// \note Primarily for tests; does not enforce rate limiting or backoff. - bool apply_samples(const std::vector& samples) { - const NtpPoolConfig cfg = config(); - const bool is_updated = update_from_samples(samples, cfg); - std::lock_guard lk(m_mtx); - m_last_samples = samples; - return is_updated; - } - - /// \brief Returns median of values. - /// \param values Values to process in-place. - /// \return Median of the input values. - static int64_t median(std::vector& values) { - using diff_t = std::vector::difference_type; - const diff_t mid_index = static_cast(values.size() / 2); - std::nth_element(values.begin(), values.begin() + mid_index, values.end()); - const int64_t mid = values[static_cast(mid_index)]; - if (values.size() % 2 == 1) { - return mid; - } - - const auto it = std::max_element(values.begin(), values.begin() + mid_index); - return (*it + mid) / 2; - } - - /// \brief Median with MAD trimming. - /// \param offsets Offset list to process in-place. - /// \return Median after MAD-based trimming. - static int64_t median_mad_trim(std::vector& offsets) { - const int64_t med = median(offsets); - - std::vector deviations; - deviations.reserve(offsets.size()); - for (auto value : offsets) { - deviations.push_back(value > med ? (value - med) : (med - value)); - } - - const int64_t mad = median(deviations); - if (mad == 0) { - return med; - } - - const int64_t threshold = mad * 3; - std::vector kept; - kept.reserve(offsets.size()); - for (auto value : offsets) { - const int64_t deviation = value > med ? (value - med) : (med - value); - if (deviation <= threshold) { - kept.push_back(value); - } - } - if (kept.empty()) { - return med; - } - return median(kept); - } - - /// \brief Offset from best (lowest) delay sample. - /// \param samples Sample list to scan. - /// \return Offset from the sample with the lowest delay. - static int64_t best_delay_offset(const std::vector& samples) { - const NtpSample* best = nullptr; - for (const auto& sample : samples) { - if (!sample.is_ok) { - continue; - } - if (sample.max_delay_us > 0 && sample.delay_us > sample.max_delay_us) { - continue; - } - if (best == nullptr) { - best = &sample; - continue; - } - if (sample.delay_us > 0 && best->delay_us > 0 && sample.delay_us < best->delay_us) { - best = &sample; - } - } - return best ? best->offset_us : 0; - } - - /// \brief Access config. - /// \return Current pool configuration. - NtpPoolConfig config() const { - std::lock_guard lk(m_mtx); - return m_cfg; - } - /// \brief Replace pool configuration. - /// \param cfg New pool configuration. - void set_config(NtpPoolConfig cfg) { - std::lock_guard lk(m_mtx); - m_cfg = std::move(cfg); - } - - /// \brief Runtime state for a configured server. - struct ServerState { - NtpServerConfig cfg; - - std::chrono::steady_clock::time_point next_allowed{}; - std::chrono::milliseconds backoff{0}; - - int fail_count = 0; - - int64_t last_offset_us = 0; - int64_t last_delay_us = 0; - int last_error = 0; - bool is_last_ok = false; - }; - - NtpPoolConfig m_cfg; - - mutable std::mutex m_mtx; - std::vector m_servers; - std::vector m_last_samples; - - std::atomic m_offset_us; - - std::mt19937_64 m_rng; - - private: - static std::uint64_t init_seed(std::uint64_t seed) { - if (seed != 0) return seed; - const auto v = static_cast( - std::chrono::high_resolution_clock::now().time_since_epoch().count()); - return v ^ 0x9E3779B97F4A7C15ULL; - } - - std::vector pick_servers_locked(std::size_t servers_to_sample) { - std::vector eligible; - eligible.reserve(m_servers.size()); - - const auto now_point = std::chrono::steady_clock::now(); - for (std::size_t i = 0; i < m_servers.size(); ++i) { - if (now_point >= m_servers[i].next_allowed) { - eligible.push_back(i); - } - } - - if (eligible.empty()) { - return {}; - } - - std::shuffle(eligible.begin(), eligible.end(), m_rng); - if (servers_to_sample < eligible.size()) { - eligible.resize(servers_to_sample); - } - return eligible; - } - - NtpSample query_one(std::size_t server_index) { - NtpServerConfig cfg; - { - std::lock_guard lk(m_mtx); - cfg = m_servers[server_index].cfg; - m_servers[server_index].next_allowed = - std::chrono::steady_clock::now() + cfg.min_interval; - } - - NtpSample out; - out.host = cfg.host; - out.port = cfg.port; - out.max_delay_us = cfg.max_delay.count() > 0 ? cfg.max_delay.count() * 1000 : 0; - - ClientT client(cfg.host, cfg.port); - - bool is_ok = false; - try { - is_ok = client.query(); - } catch (...) { - out.error_code = client.last_error_code(); - } - - if (out.error_code == 0) { - out.error_code = client.last_error_code(); - } - if (out.error_code == 0 && !is_ok) { - out.error_code = -1; - } - - out.is_ok = is_ok; - out.offset_us = client.offset_us(); - out.delay_us = client.delay_us(); - out.stratum = client.stratum(); - - update_server_state_after_query(server_index, out); - return out; - } - - void update_server_state_after_query(std::size_t index, const NtpSample& sample) { - std::lock_guard lk(m_mtx); - auto& state = m_servers[index]; - - state.is_last_ok = sample.is_ok; - state.last_error = sample.error_code; - state.last_offset_us = sample.offset_us; - state.last_delay_us = sample.delay_us; - - if (sample.is_ok) { - state.fail_count = 0; - state.backoff = std::chrono::milliseconds(0); - return; - } - - state.fail_count++; - const auto init = state.cfg.backoff_initial; - const auto max_backoff = state.cfg.backoff_max; - - if (state.backoff.count() == 0) { - state.backoff = init; - } else { - state.backoff = (std::min)(max_backoff, state.backoff * 2); - } - - state.next_allowed = std::chrono::steady_clock::now() + state.backoff; - } - - bool update_from_samples(const std::vector& samples, const NtpPoolConfig& cfg) { - std::vector offsets; - offsets.reserve(samples.size()); - - for (const auto& sample : samples) { - if (!sample.is_ok) { - continue; - } - if (sample.max_delay_us > 0 && sample.delay_us > sample.max_delay_us) { - continue; - } - offsets.push_back(sample.offset_us); - } - - if (offsets.size() < cfg.min_valid_samples) { - return false; - } - - int64_t estimate = 0; - switch (cfg.aggregation) { - case NtpPoolConfig::Aggregation::BestDelay: - estimate = best_delay_offset(samples); - break; - case NtpPoolConfig::Aggregation::MedianMadTrim: - estimate = median_mad_trim(offsets); - break; - case NtpPoolConfig::Aggregation::Median: - default: - estimate = median(offsets); - break; - } - - double alpha = cfg.smoothing_alpha; - if (alpha < 0.0) { - alpha = 0.0; - } else if (alpha > 1.0) { - alpha = 1.0; - } - if (alpha >= 1.0) { - m_offset_us.store(estimate); - } else if (alpha > 0.0) { - const int64_t old_value = m_offset_us.load(); - const double new_value = - (1.0 - alpha) * static_cast(old_value) + alpha * static_cast(estimate); - m_offset_us.store(static_cast(new_value)); - } - return true; - } - - }; - - using NtpClientPool = NtpClientPoolT; -} // namespace time_shield - -#else // TIME_SHIELD_ENABLE_NTP_CLIENT - -namespace time_shield { - class NtpClientPool { - public: - NtpClientPool() { - static_assert(sizeof(void*) == 0, "NtpClientPool is disabled by configuration."); - } - }; -} // namespace time_shield - -#endif // TIME_SHIELD_ENABLE_NTP_CLIENT - -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_NTP_CLIENT_POOL_HPP_INCLUDED +#endif // TIME_SHIELD_HEADER_NTP_CLIENT_POOL_HPP_INCLUDED diff --git a/include/time_shield/ntp_client_pool_runner.hpp b/include/time_shield/ntp_client_pool_runner.hpp index fcc964c5..9546b0db 100644 --- a/include/time_shield/ntp_client_pool_runner.hpp +++ b/include/time_shield/ntp_client_pool_runner.hpp @@ -1,234 +1,8 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_NTP_CLIENT_POOL_RUNNER_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_NTP_CLIENT_POOL_RUNNER_HPP_INCLUDED +#ifndef TIME_SHIELD_HEADER_NTP_CLIENT_POOL_RUNNER_HPP_INCLUDED +#define TIME_SHIELD_HEADER_NTP_CLIENT_POOL_RUNNER_HPP_INCLUDED -#include "config.hpp" +#include -#if TIME_SHIELD_ENABLE_NTP_CLIENT - -#include "ntp_client_pool.hpp" -#include "time_utils.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include - -namespace time_shield { - - /// \ingroup ntp - /// \brief Background runner that periodically measures NTP offsets using a pool. - /// - /// \code - /// time_shield::NtpClientPool pool; - /// pool.set_default_servers(); - /// - /// time_shield::NtpClientPoolRunner runner(std::move(pool)); - /// runner.start(std::chrono::seconds(30)); - /// - /// auto now_ms = runner.utc_time_ms(); - /// auto offset = runner.offset_us(); - /// - /// runner.stop(); - /// \endcode - template - class BasicPoolRunner { - public: - /// \brief Construct runner with a pool instance. - /// \param pool Pool instance to use. - explicit BasicPoolRunner(PoolT pool = PoolT{}) - : m_pool(std::move(pool)) {} - - /// \brief Stop background thread on destruction. - ~BasicPoolRunner() { - stop(); - } - - /// \brief Start periodic measurements on a background thread. - /// \param interval Measurement interval. - /// \param measure_immediately Measure before first sleep if true. - /// \return True when background thread started. - bool start(std::chrono::milliseconds interval = std::chrono::seconds(30), - bool measure_immediately = true) { - if (m_is_running.load()) { - return false; - } - if (interval.count() <= 0) { - interval = std::chrono::milliseconds(1); - } - - m_is_stop_requested.store(false); - m_is_force_requested.store(false); - m_is_running.store(true); - - try { - m_thread = std::thread(&BasicPoolRunner::run_loop, this, interval, measure_immediately); - } catch (...) { - m_is_running.store(false); - return false; - } - - return true; - } - - /// \brief Start periodic measurements using milliseconds. - /// \param interval_ms Measurement interval in milliseconds. - /// \param measure_immediately Measure before first sleep if true. - /// \return True when background thread started. - bool start(int interval_ms, bool measure_immediately = true) { - return start(std::chrono::milliseconds(interval_ms), measure_immediately); - } - - /// \brief Stop background measurements. - void stop() { - m_is_stop_requested.store(true); - m_cv.notify_all(); - if (m_thread.joinable()) { - m_thread.join(); - } - m_is_running.store(false); - } - - /// \brief Return true when background thread is running. - /// \return True when background measurements are active. - bool running() const noexcept { return m_is_running.load(); } - - /// \brief Wake the worker thread and request a measurement. - /// \return True when request accepted. - bool force_measure() { - if (!m_is_running.load()) { - return false; - } - m_is_force_requested.store(true); - m_cv.notify_one(); - return true; - } - - /// \brief Perform one measurement immediately. - /// \return True when pool offset updated. - bool measure_now() { - return do_measure(); - } - - /// \brief Return last estimated offset in microseconds. - /// \return Offset in microseconds (UTC - local realtime). - int64_t offset_us() const noexcept { return m_pool.offset_us(); } - /// \brief Return current UTC time in microseconds using pool offset. - /// \return UTC time in microseconds using pool offset. - int64_t utc_time_us() const noexcept { return m_pool.utc_time_us(); } - /// \brief Return current UTC time in milliseconds using pool offset. - /// \return UTC time in milliseconds using pool offset. - int64_t utc_time_ms() const noexcept { return m_pool.utc_time_ms(); } - /// \brief Return current UTC time in seconds using pool offset. - /// \return UTC time in seconds using pool offset. - int64_t utc_time_sec() const noexcept { return utc_time_us() / 1000000; } - - /// \brief Return whether last measurement updated the offset. - /// \return True when last measurement updated the offset. - bool last_measure_ok() const noexcept { return m_last_measure_ok.load(); } - /// \brief Return total number of measurement attempts. - /// \return Number of measurement attempts. - uint64_t measure_count() const noexcept { return m_measure_count.load(); } - /// \brief Return number of failed measurement attempts. - /// \return Number of failed measurement attempts. - uint64_t fail_count() const noexcept { return m_fail_count.load(); } - /// \brief Return realtime timestamp of last measurement attempt. - /// \return Realtime microseconds timestamp for last measurement attempt. - int64_t last_update_realtime_us() const noexcept { return m_last_update_realtime_us.load(); } - /// \brief Return realtime timestamp of last successful measurement. - /// \return Realtime microseconds timestamp for last successful measurement. - int64_t last_success_realtime_us() const noexcept { return m_last_success_realtime_us.load(); } - - /// \brief Return copy of the most recent samples. - /// \return Copy of samples from the last measurement. - std::vector last_samples() const { return m_pool.last_samples(); } - - private: - void run_loop(std::chrono::milliseconds interval, bool measure_immediately) { - bool is_first = measure_immediately; - while (!m_is_stop_requested.load()) { - if (is_first) { - do_measure(); - is_first = false; - } else { - std::unique_lock lk(m_cv_mtx); - m_cv.wait_for(lk, interval, [this]() { - return m_is_stop_requested.load() || m_is_force_requested.load(); - }); - if (m_is_stop_requested.load()) { - break; - } - m_is_force_requested.store(false); - do_measure(); - } - } - m_is_running.store(false); - } - - bool do_measure() { - bool is_ok = false; - try { - std::lock_guard lk(m_pool_mtx); - is_ok = m_pool.measure(); - } catch (...) { - is_ok = false; - } - - m_measure_count.fetch_add(1); - if (!is_ok) { - m_fail_count.fetch_add(1); - } - - m_last_measure_ok.store(is_ok); - - const int64_t now = now_realtime_us(); - m_last_update_realtime_us.store(now); - if (is_ok) { - m_last_success_realtime_us.store(now); - } - - return is_ok; - } - - private: - PoolT m_pool; - mutable std::mutex m_pool_mtx; - - std::thread m_thread; - std::condition_variable m_cv; - std::mutex m_cv_mtx; - - std::atomic m_is_running{false}; - std::atomic m_is_stop_requested{false}; - std::atomic m_is_force_requested{false}; - - std::atomic m_last_measure_ok{false}; - std::atomic m_measure_count{0}; - std::atomic m_fail_count{0}; - std::atomic m_last_update_realtime_us{0}; - std::atomic m_last_success_realtime_us{0}; - }; - - using NtpClientPoolRunner = BasicPoolRunner; - -} // namespace time_shield - -#else // TIME_SHIELD_ENABLE_NTP_CLIENT - -namespace time_shield { - class NtpClientPoolRunner { - public: - NtpClientPoolRunner() { - static_assert(sizeof(void*) == 0, "NtpClientPoolRunner is disabled by configuration."); - } - }; -} // namespace time_shield - -#endif // _TIME_SHIELD_ENABLE_NTP_CLIENT - -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_NTP_CLIENT_POOL_RUNNER_HPP_INCLUDED +#endif // TIME_SHIELD_HEADER_NTP_CLIENT_POOL_RUNNER_HPP_INCLUDED diff --git a/include/time_shield/ntp_time_service.hpp b/include/time_shield/ntp_time_service.hpp index 4c0db67d..6bc02ede 100644 --- a/include/time_shield/ntp_time_service.hpp +++ b/include/time_shield/ntp_time_service.hpp @@ -1,895 +1,8 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_NTP_TIME_SERVICE_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_NTP_TIME_SERVICE_HPP_INCLUDED +#ifndef TIME_SHIELD_HEADER_NTP_TIME_SERVICE_HPP_INCLUDED +#define TIME_SHIELD_HEADER_NTP_TIME_SERVICE_HPP_INCLUDED -#include "config.hpp" +#include -#if TIME_SHIELD_ENABLE_NTP_CLIENT - -#include "ntp_client_pool.hpp" -#include "ntp_client_pool_runner.hpp" -#include "time_utils.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace time_shield { - - template - class NtpTimeServiceT; - - namespace detail { - template - struct NtpTimeServiceSingleton; - - template - struct NtpTimeServiceTestAccess; - -#ifdef TIME_SHIELD_TEST_FAKE_NTP - /// \brief Fake runner for tests without network access. - class FakeNtpRunner { - public: - /// \brief Construct fake runner. - FakeNtpRunner() = default; - /// \brief Construct fake runner with an unused pool. - explicit FakeNtpRunner(NtpClientPool) {} - - FakeNtpRunner(const FakeNtpRunner&) = delete; - FakeNtpRunner& operator=(const FakeNtpRunner&) = delete; - - /// \brief Stop background thread on destruction. - ~FakeNtpRunner() { - stop(); - } - - /// \brief Start fake measurements on a background thread. - bool start(std::chrono::milliseconds interval = std::chrono::seconds(30), - bool measure_immediately = true) { - if (m_is_running.load()) { - return false; - } - if (interval.count() <= 0) { - interval = std::chrono::milliseconds(1); - } - m_interval = interval; - m_is_stop_requested.store(false); - m_is_force_requested.store(false); - m_is_running.store(true); - m_measure_immediately = measure_immediately; - try { - m_thread = std::thread(&FakeNtpRunner::run_loop, this); - } catch (...) { - m_is_running.store(false); - return false; - } - return true; - } - - /// \brief Start fake measurements using milliseconds. - bool start(int interval_ms, bool measure_immediately = true) { - return start(std::chrono::milliseconds(interval_ms), measure_immediately); - } - - /// \brief Stop background measurements. - void stop() { - m_is_stop_requested.store(true); - m_cv.notify_all(); - if (m_thread.joinable()) { - m_thread.join(); - } - m_is_running.store(false); - } - - /// \brief Return true when background thread is running. - bool running() const noexcept { return m_is_running.load(); } - - /// \brief Wake the worker thread and request a measurement. - bool force_measure() { - if (!m_is_running.load()) { - return false; - } - m_is_force_requested.store(true); - m_cv.notify_one(); - return true; - } - - /// \brief Perform one measurement immediately. - bool measure_now() { - return do_measure(); - } - - /// \brief Return last estimated offset in microseconds. - int64_t offset_us() const noexcept { return m_offset_us.load(); } - /// \brief Return current UTC time in microseconds based on offset. - int64_t utc_time_us() const noexcept { return now_realtime_us() + m_offset_us.load(); } - /// \brief Return current UTC time in milliseconds based on offset. - int64_t utc_time_ms() const noexcept { return utc_time_us() / 1000; } - /// \brief Return current UTC time in seconds based on offset. - int64_t utc_time_sec() const noexcept { return utc_time_us() / 1000000; } - - /// \brief Return whether last measurement updated the offset. - bool last_measure_ok() const noexcept { return m_last_measure_ok.load(); } - /// \brief Return total number of measurement attempts. - uint64_t measure_count() const noexcept { return m_measure_count.load(); } - /// \brief Return number of failed measurement attempts. - uint64_t fail_count() const noexcept { return m_fail_count.load(); } - /// \brief Return realtime timestamp of last measurement attempt. - int64_t last_update_realtime_us() const noexcept { return m_last_update_realtime_us.load(); } - /// \brief Return realtime timestamp of last successful measurement. - int64_t last_success_realtime_us() const noexcept { return m_last_success_realtime_us.load(); } - - /// \brief Return copy of most recent samples. - std::vector last_samples() const { return {}; } - - private: - /// \brief Background loop for fake measurements. - void run_loop() { - const auto sleep_interval = m_interval; - bool is_first = m_measure_immediately; - while (!m_is_stop_requested.load()) { - if (is_first) { - do_measure(); - is_first = false; - } else { - std::unique_lock lk(m_cv_mtx); - m_cv.wait_for(lk, sleep_interval, [this]() { - return m_is_stop_requested.load() || m_is_force_requested.load(); - }); - if (m_is_stop_requested.load()) { - break; - } - m_is_force_requested.store(false); - do_measure(); - } - } - m_is_running.store(false); - } - - /// \brief Update fake offset and stats. - bool do_measure() { - const uint64_t count = m_measure_count.fetch_add(1) + 1; - m_offset_us.store(static_cast(count * 1000)); - m_last_measure_ok.store(true); - const int64_t now = now_realtime_us(); - m_last_update_realtime_us.store(now); - m_last_success_realtime_us.store(now); - return true; - } - - private: - std::chrono::milliseconds m_interval{std::chrono::seconds(30)}; - bool m_measure_immediately{true}; - - std::thread m_thread; - std::condition_variable m_cv; - std::mutex m_cv_mtx; - - std::atomic m_is_running{false}; - std::atomic m_is_stop_requested{false}; - std::atomic m_is_force_requested{false}; - - std::atomic m_last_measure_ok{false}; - std::atomic m_measure_count{0}; - std::atomic m_fail_count{0}; - std::atomic m_last_update_realtime_us{0}; - std::atomic m_last_success_realtime_us{0}; - std::atomic m_offset_us{0}; - }; -#endif // _TIME_SHIELD_TEST_FAKE_NTP - } // namespace detail - - /// \ingroup ntp - /// \brief Singleton service for background NTP measurements. - /// - /// Uses an internal runner to keep offset updated. It exposes UTC time - /// computed as realtime clock plus the latest offset. Configure pool - /// servers and sampling before starting the service. During process - /// shutdown, the singleton stops background work and falls back to the - /// last cached offset without restarting the runner. - template - class NtpTimeServiceT { - friend struct detail::NtpTimeServiceSingleton; - friend struct detail::NtpTimeServiceTestAccess; - - public: - /// \brief Return the singleton instance. - /// \return Singleton instance. - static NtpTimeServiceT& instance() noexcept { - return detail::NtpTimeServiceSingleton::instance(); - } - - NtpTimeServiceT(const NtpTimeServiceT&) = delete; - NtpTimeServiceT& operator=(const NtpTimeServiceT&) = delete; - - /// \brief Start background measurements using stored interval. - /// \return True when background runner started. - bool init() { - return init(m_interval, m_measure_immediately); - } - - /// \brief Start background measurements with interval and immediate flag. - /// \param interval Measurement interval. - /// \param measure_immediately Measure before first sleep if true. - /// \return True when background runner started. - bool init(std::chrono::milliseconds interval, bool measure_immediately = true) { - if (is_process_shutting_down()) { - return false; - } - - std::unique_ptr local_runner; - std::chrono::milliseconds start_interval = interval; - bool use_measure_immediately = measure_immediately; - { - std::unique_lock lk(m_mtx); - while (is_transitioning_locked()) { - m_cv.wait(lk); - } - if (is_process_shutting_down_locked()) { - return false; - } - if (m_state == State::running && is_running_locked()) { - return true; - } - if (start_interval.count() <= 0) { - start_interval = std::chrono::milliseconds(1); - } - m_interval = start_interval; - m_measure_immediately = use_measure_immediately; - m_state = State::starting; - - local_runner = build_runner_locked(); - if (!local_runner) { - m_state = State::stopped; - lk.unlock(); - m_cv.notify_all(); - return false; - } - } - - bool has_started = false; - bool is_ok = false; - try { - has_started = local_runner->start(start_interval, use_measure_immediately); - if (has_started) { - is_ok = local_runner->measure_now(); - } - } catch (...) { - is_ok = false; - } - - if (!has_started || !is_ok || is_process_shutting_down()) { - try { - if (has_started) { - local_runner->stop(); - } - } catch (...) { - // no-throw - } - local_runner.reset(); - } - - { - std::lock_guard lk(m_mtx); - if (local_runner) { - m_last_offset_us.store(local_runner->offset_us(), std::memory_order_relaxed); - m_runner = std::move(local_runner); - m_state = State::running; - } else { - m_runner.reset(); - m_state = State::stopped; - } - } - m_cv.notify_all(); - return is_ok; - } - - /// \brief Start background measurements using milliseconds. - /// \param interval_ms Measurement interval in milliseconds. - /// \param measure_immediately Measure before first sleep if true. - /// \return True when background runner started. - bool init(int interval_ms, bool measure_immediately = true) { - return init(std::chrono::milliseconds(interval_ms), measure_immediately); - } - - /// \brief Stop background measurements and release resources. - void shutdown() { - std::unique_ptr local_runner; - { - std::unique_lock lk(m_mtx); - while (is_transitioning_locked()) { - m_cv.wait(lk); - } - if (m_state == State::stopped || !m_runner) { - return; - } - m_state = State::stopping; - local_runner = std::move(m_runner); - } - try { - local_runner->stop(); - } catch (...) { - // no-throw - } - { - std::lock_guard lk(m_mtx); - m_state = State::stopped; - } - m_cv.notify_all(); - } - - /// \brief Return true when background runner is active. - /// \return True when background runner is active. - bool running() const noexcept { - std::lock_guard lk(m_mtx); - return m_state == State::running && is_running_locked(); - } - - /// \brief Ensure background runner is started with current config. - void ensure_started() noexcept { - if (is_process_shutting_down()) { - return; - } - if (running()) { - return; - } - (void)init(); - } - - /// \brief Return last estimated offset in microseconds. - /// \note During process shutdown, returns the last cached offset without - /// restarting the background runner. - /// \return Offset in microseconds (UTC - local realtime). - int64_t offset_us() noexcept { - if (is_process_shutting_down()) { - return m_last_offset_us.load(std::memory_order_relaxed); - } - ensure_started(); - std::lock_guard lk(m_mtx); - if (!m_runner) return 0; - const int64_t offset = m_runner->offset_us(); - m_last_offset_us.store(offset, std::memory_order_relaxed); - return offset; - } - - /// \brief Return current UTC time in microseconds based on offset. - /// \note During process shutdown, returns realtime plus the last cached - /// offset without restarting the background runner. - /// \return UTC time in microseconds using last offset. - int64_t utc_time_us() noexcept { - if (is_process_shutting_down()) { - return now_realtime_us() + m_last_offset_us.load(std::memory_order_relaxed); - } - ensure_started(); - std::lock_guard lk(m_mtx); - if (!m_runner) return now_realtime_us(); - m_last_offset_us.store(m_runner->offset_us(), std::memory_order_relaxed); - return m_runner->utc_time_us(); - } - - /// \brief Return current UTC time in milliseconds based on offset. - /// \return UTC time in milliseconds using last offset. - int64_t utc_time_ms() noexcept { - return utc_time_us() / 1000; - } - - /// \brief Return current UTC time in seconds based on offset. - /// \return UTC time in seconds using last offset. - int64_t utc_time_sec() noexcept { - return utc_time_us() / 1000000; - } - - /// \brief Return whether last measurement updated the offset. - /// \return True when last measurement updated the offset. - bool last_measure_ok() const noexcept { - std::lock_guard lk(m_mtx); - if (!m_runner) return false; - return m_runner->last_measure_ok(); - } - - /// \brief Return total number of measurement attempts. - /// \return Number of measurement attempts. - uint64_t measure_count() const noexcept { - std::lock_guard lk(m_mtx); - if (!m_runner) return 0; - return m_runner->measure_count(); - } - - /// \brief Return number of failed measurement attempts. - /// \return Number of failed measurement attempts. - uint64_t fail_count() const noexcept { - std::lock_guard lk(m_mtx); - if (!m_runner) return 0; - return m_runner->fail_count(); - } - - /// \brief Return realtime timestamp of last measurement attempt. - /// \return Realtime microseconds timestamp for last measurement attempt. - int64_t last_update_realtime_us() const noexcept { - std::lock_guard lk(m_mtx); - if (!m_runner) return 0; - return m_runner->last_update_realtime_us(); - } - - /// \brief Return realtime timestamp of last successful measurement. - /// \return Realtime microseconds timestamp for last successful measurement. - int64_t last_success_realtime_us() const noexcept { - std::lock_guard lk(m_mtx); - if (!m_runner) return 0; - return m_runner->last_success_realtime_us(); - } - - /// \brief Return true when last measurement is older than max_age. - /// \param max_age Maximum allowed age. - /// \return True when last measurement age exceeds max_age. - bool stale(std::chrono::milliseconds max_age) const noexcept { - const int64_t last = last_update_realtime_us(); - if (last == 0) { - return true; - } - const int64_t age = now_realtime_us() - last; - return age > static_cast(max_age.count()) * 1000; - } - - /// \brief Return true when last measurement is older than max_age_ms. - /// \param max_age_ms Maximum allowed age in milliseconds. - /// \return True when last measurement age exceeds max_age_ms. - bool stale(int max_age_ms) const noexcept { - return stale(std::chrono::milliseconds(max_age_ms)); - } - - /// \brief Replace server list used for new runner instances. - /// \param servers Server configurations to use. - /// \return False when service is already running. - bool set_servers(std::vector servers) { - std::lock_guard lk(m_mtx); - if (!is_reconfigurable_locked()) { - return false; - } - m_has_custom_servers = true; - m_servers = std::move(servers); - return true; - } - - /// \brief Use conservative default servers for new runner instances. - /// \return False when service is already running. - bool set_default_servers() { - std::lock_guard lk(m_mtx); - if (!is_reconfigurable_locked()) { - return false; - } - m_has_custom_servers = true; - m_servers = NtpClientPool::build_default_servers(); - return true; - } - - /// \brief Clear custom server list and return to default behavior. - /// \return False when service is already running. - bool clear_servers() { - std::lock_guard lk(m_mtx); - if (!is_reconfigurable_locked()) { - return false; - } - m_has_custom_servers = false; - m_servers.clear(); - return true; - } - - /// \brief Override pool configuration for new runner instances. - /// \param cfg Pool configuration to apply. - /// \return False when service is already running. - bool set_pool_config(NtpPoolConfig cfg) { - std::lock_guard lk(m_mtx); - if (!is_reconfigurable_locked()) { - return false; - } - m_has_custom_pool_cfg = true; - m_pool_cfg = std::move(cfg); - return true; - } - - /// \brief Return current pool configuration. - /// \return Current pool configuration. - NtpPoolConfig pool_config() const { - std::lock_guard lk(m_mtx); - if (m_has_custom_pool_cfg) { - return m_pool_cfg; - } - return NtpPoolConfig{}; - } - - /// \brief Return copy of last measurement samples. - /// \return Copy of samples from the last measurement. - std::vector last_samples() const { - std::lock_guard lk(m_mtx); - if (!m_runner) return {}; - return m_runner->last_samples(); - } - - /// \brief Construct service. - NtpTimeServiceT() = default; - /// \brief Immortal singleton is stopped via process-shutdown handler. - ~NtpTimeServiceT() = default; - - /// \brief Apply current config by rebuilding the runner. - /// \return True when runner restarted successfully. - bool apply_config_now() { - if (is_process_shutting_down()) { - return false; - } - - std::unique_ptr new_runner; - std::unique_ptr old_runner; - std::chrono::milliseconds interval; - bool measure_immediately = true; - bool was_running = false; - { - std::unique_lock lk(m_mtx); - while (is_transitioning_locked()) { - m_cv.wait(lk); - } - if (is_process_shutting_down_locked()) { - return false; - } - was_running = m_state == State::running && is_running_locked(); - m_state = State::starting; - new_runner = build_runner_locked(); - if (!new_runner) { - m_state = was_running ? State::running : State::stopped; - lk.unlock(); - m_cv.notify_all(); - return false; - } - interval = m_interval; - measure_immediately = m_measure_immediately; - old_runner = std::move(m_runner); - } - - if (old_runner) { - try { - old_runner->stop(); - } catch (...) { - } - } - - bool has_started = false; - bool is_ok = false; - try { - has_started = new_runner->start(interval, measure_immediately); - if (has_started) { - is_ok = new_runner->measure_now(); - } - } catch (...) { - is_ok = false; - } - - if (!has_started || !is_ok || is_process_shutting_down()) { - try { - if (has_started) { - new_runner->stop(); - } - } catch (...) { - // no-throw - } - new_runner.reset(); - } - - { - std::lock_guard lk(m_mtx); - if (new_runner) { - m_last_offset_us.store(new_runner->offset_us(), std::memory_order_relaxed); - m_runner = std::move(new_runner); - m_state = State::running; - } else { - m_runner.reset(); - m_state = State::stopped; - } - } - m_cv.notify_all(); - return is_ok; - } - - private: - /// \brief Global lifetime state of the immortal singleton. - enum class ProcessState : uint8_t { - alive, - shutting_down - }; - - /// \brief Lifecycle state of the singleton runner. - enum class State : uint8_t { - stopped, - starting, - running, - stopping - }; - - /// \brief Register one process-shutdown handler for this specialization. - void register_process_shutdown_handler() noexcept { - if (std::atexit(&detail::NtpTimeServiceSingleton::handle_process_exit) == 0) { - m_atexit_registration_count.fetch_add(1, std::memory_order_relaxed); - } - } - - /// \brief Mark the singleton as shutting down and stop the runner. - void begin_process_shutdown() noexcept { - m_process_state.store(ProcessState::shutting_down, std::memory_order_release); - - std::unique_ptr local_runner; - { - std::unique_lock lk(m_mtx); - while (is_transitioning_locked()) { - m_cv.wait(lk); - } - if (m_runner) { - m_last_offset_us.store(m_runner->offset_us(), std::memory_order_relaxed); - m_state = State::stopping; - local_runner = std::move(m_runner); - } else { - m_state = State::stopped; - } - } - - if (local_runner) { - try { - local_runner->stop(); - } catch (...) { - // no-throw - } - } - - { - std::lock_guard lk(m_mtx); - m_state = State::stopped; - } - m_cv.notify_all(); - } - - /// \brief Return true when process shutdown has started. - bool is_process_shutting_down() const noexcept { - return m_process_state.load(std::memory_order_acquire) == ProcessState::shutting_down; - } - - /// \brief Return true when process shutdown has started. - bool is_process_shutting_down_locked() const noexcept { - return is_process_shutting_down(); - } - - /// \brief Return number of successful atexit registrations. - uint32_t atexit_registration_count() const noexcept { - return m_atexit_registration_count.load(std::memory_order_relaxed); - } - - /// \brief Check runner status under lock. - bool is_running_locked() const noexcept { - return m_runner && m_runner->running(); - } - - /// \brief Return true when a start or stop transition is in progress. - bool is_transitioning_locked() const noexcept { - return m_state == State::starting || m_state == State::stopping; - } - - /// \brief Return true when configuration can be changed safely. - bool is_reconfigurable_locked() const noexcept { - return !is_process_shutting_down_locked() && m_state == State::stopped && !m_runner; - } - - /// \brief Build a runner with current server list and pool config. - std::unique_ptr build_runner_locked() { - std::vector servers; - if (m_has_custom_servers) { - servers = m_servers; - } else { - servers = NtpClientPool::build_default_servers(); - } - - NtpPoolConfig cfg = m_has_custom_pool_cfg ? m_pool_cfg : NtpPoolConfig{}; - NtpClientPool pool(cfg); - pool.set_servers(std::move(servers)); - - std::unique_ptr runner; - try { - runner.reset(new RunnerT(std::move(pool))); - } catch (...) { - return nullptr; - } - return runner; - } - - private: - mutable std::mutex m_mtx; - std::condition_variable m_cv; - State m_state{State::stopped}; - std::atomic m_process_state{ProcessState::alive}; - std::atomic m_last_offset_us{0}; - std::atomic m_atexit_registration_count{0}; - std::chrono::milliseconds m_interval{std::chrono::seconds(30)}; - bool m_measure_immediately{true}; - - bool m_has_custom_servers{false}; - std::vector m_servers; - - bool m_has_custom_pool_cfg{false}; - NtpPoolConfig m_pool_cfg{}; - - std::unique_ptr m_runner; - }; - - namespace detail { - template - struct NtpTimeServiceSingleton final { - static NtpTimeServiceT& instance() noexcept { - static NtpTimeServiceT* p_instance = []() noexcept { - NtpTimeServiceT* p_service = new NtpTimeServiceT{}; - p_service->register_process_shutdown_handler(); - return p_service; - }(); - return *p_instance; - } - - static void handle_process_exit() noexcept { - instance().begin_process_shutdown(); - } - }; - - template - struct NtpTimeServiceTestAccess final { - static void begin_process_shutdown() noexcept { - NtpTimeServiceSingleton::instance().begin_process_shutdown(); - } - - static bool is_process_shutting_down() noexcept { - return NtpTimeServiceSingleton::instance().is_process_shutting_down(); - } - - static uint32_t atexit_registration_count() noexcept { - return NtpTimeServiceSingleton::instance().atexit_registration_count(); - } - }; - } // namespace detail - -#if defined(TIME_SHIELD_TEST_FAKE_NTP) - /// \ingroup ntp - /// \brief NTP time service alias that uses a fake runner for tests. - using NtpTimeService = NtpTimeServiceT; -#else - /// \ingroup ntp - /// \brief NTP time service alias that uses the real pool runner. - using NtpTimeService = NtpTimeServiceT; -#endif - -namespace ntp { - - /// \ingroup ntp - /// \brief Initialize NTP time service and start background measurements. - /// \param interval Measurement interval. - /// \param measure_immediately Measure before first sleep if true. - /// \return True when background runner started. - inline bool init(std::chrono::milliseconds interval = std::chrono::seconds(30), - bool measure_immediately = true) { - return NtpTimeService::instance().init(interval, measure_immediately); - } - - /// \ingroup ntp - /// \brief Initialize NTP time service using milliseconds. - /// \param interval_ms Measurement interval in milliseconds. - /// \param measure_immediately Measure before first sleep if true. - /// \return True when background runner started. - inline bool init(int interval_ms, - bool measure_immediately = true) { - return NtpTimeService::instance().init(std::chrono::milliseconds(interval_ms), measure_immediately); - } - - /// \ingroup ntp - /// \brief Stop NTP time service. - inline void shutdown() { - NtpTimeService::instance().shutdown(); - } - - /// \ingroup ntp - /// \brief Return last estimated offset in microseconds. - /// \return Offset in microseconds (UTC - local realtime). - inline int64_t offset_us() noexcept { - return NtpTimeService::instance().offset_us(); - } - - /// \ingroup ntp - /// \brief Return current UTC time in microseconds based on offset. - /// \return UTC time in microseconds using last offset. - inline int64_t utc_time_us() noexcept { - return NtpTimeService::instance().utc_time_us(); - } - - /// \ingroup ntp - /// \brief Return current UTC time in milliseconds based on offset. - /// \return UTC time in milliseconds using last offset. - inline int64_t utc_time_ms() noexcept { - return NtpTimeService::instance().utc_time_ms(); - } - - /// \ingroup ntp - /// \brief Return current UTC time in seconds based on offset. - /// \return UTC time in seconds using last offset. - inline int64_t utc_time_sec() noexcept { - return NtpTimeService::instance().utc_time_sec(); - } - - /// \ingroup ntp - /// \brief Return whether last measurement updated the offset. - /// \return True when last measurement updated the offset. - inline bool last_measure_ok() noexcept { - return NtpTimeService::instance().last_measure_ok(); - } - - /// \ingroup ntp - /// \brief Return total number of measurement attempts. - /// \return Number of measurement attempts. - inline uint64_t measure_count() noexcept { - return NtpTimeService::instance().measure_count(); - } - - /// \ingroup ntp - /// \brief Return number of failed measurement attempts. - /// \return Number of failed measurement attempts. - inline uint64_t fail_count() noexcept { - return NtpTimeService::instance().fail_count(); - } - - /// \ingroup ntp - /// \brief Return realtime timestamp of last measurement attempt. - /// \return Realtime microseconds timestamp for last measurement attempt. - inline int64_t last_update_realtime_us() noexcept { - return NtpTimeService::instance().last_update_realtime_us(); - } - - /// \ingroup ntp - /// \brief Return realtime timestamp of last successful measurement. - /// \return Realtime microseconds timestamp for last successful measurement. - inline int64_t last_success_realtime_us() noexcept { - return NtpTimeService::instance().last_success_realtime_us(); - } - - /// \ingroup ntp - /// \brief Return true when last measurement is older than max_age. - /// \param max_age Maximum allowed age. - /// \return True when last measurement age exceeds max_age. - inline bool stale(std::chrono::milliseconds max_age) noexcept { - return NtpTimeService::instance().stale(max_age); - } - - /// \ingroup ntp - /// \brief Return true when last measurement is older than max_age_ms. - /// \param max_age_ms Maximum allowed age in milliseconds. - /// \return True when last measurement age exceeds max_age_ms. - inline bool stale(int max_age_ms) noexcept { - return NtpTimeService::instance().stale(max_age_ms); - } - -} // namespace ntp - -} // namespace time_shield - -#else // TIME_SHIELD_ENABLE_NTP_CLIENT - -namespace time_shield { - class NtpTimeService { - public: - static NtpTimeService& instance() { - static_assert(sizeof(void*) == 0, "NtpTimeService is disabled by configuration."); - return *reinterpret_cast(0); - } - }; -} // namespace time_shield - -#endif // _TIME_SHIELD_ENABLE_NTP_CLIENT - -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_NTP_TIME_SERVICE_HPP_INCLUDED +#endif // TIME_SHIELD_HEADER_NTP_TIME_SERVICE_HPP_INCLUDED diff --git a/include/time_shield/ole_automation_conversions.hpp b/include/time_shield/ole_automation_conversions.hpp index 5e4c05d6..78ff4338 100644 --- a/include/time_shield/ole_automation_conversions.hpp +++ b/include/time_shield/ole_automation_conversions.hpp @@ -1,159 +1,8 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_OLE_AUTOMATION_CONVERSIONS_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_OLE_AUTOMATION_CONVERSIONS_HPP_INCLUDED +#ifndef TIME_SHIELD_HEADER_OLE_AUTOMATION_CONVERSIONS_HPP_INCLUDED +#define TIME_SHIELD_HEADER_OLE_AUTOMATION_CONVERSIONS_HPP_INCLUDED -/// \file ole_automation_conversions.hpp -/// \brief OLE Automation Date (OA date) conversions. -/// \ingroup time_conversions -/// -/// OA date is a floating-point day count where: -/// - 0.0 is 1899-12-30 00:00:00 -/// - the integer part is days offset from that date -/// - the fractional part is time-of-day / 24 -/// - negative fractional values follow Excel/COM serial semantics -/// -/// This header provides conversions between OA date and: -/// - Unix timestamps in seconds (ts_t) -/// - Unix timestamps in milliseconds (ts_ms_t) -/// - floating seconds (fts_t) +#include -#include "config.hpp" -#include "types.hpp" -#include "constants.hpp" -#include "date_time_conversions.hpp" - -#include -#include - -namespace time_shield { - - namespace detail { - - TIME_SHIELD_CONSTEXPR inline bool oadate_can_cast_to_i64(oadate_t value) noexcept { - return value >= static_cast((std::numeric_limits::min)()) - && value <= static_cast((std::numeric_limits::max)()); - } - - TIME_SHIELD_CONSTEXPR inline oadate_t oadate_abs(oadate_t value) noexcept { - return value < 0 ? -value : value; - } - - TIME_SHIELD_CONSTEXPR inline oadate_t oadate_trunc_toward_zero(oadate_t value) noexcept { - if (!oadate_can_cast_to_i64(value)) { - return value; - } - return static_cast(static_cast(value)); - } - - TIME_SHIELD_CONSTEXPR inline oadate_t oadate_floor_value(oadate_t value) noexcept { - const oadate_t truncated = oadate_trunc_toward_zero(value); - if (truncated == value) { - return truncated; - } - if (value < 0) { - return truncated - static_cast(1.0); - } - return truncated; - } - - TIME_SHIELD_CONSTEXPR inline bool oadate_has_fraction(oadate_t value) noexcept { - if (!oadate_can_cast_to_i64(value)) { - return false; - } - return oadate_trunc_toward_zero(value) != value; - } - - TIME_SHIELD_CONSTEXPR inline oadate_t linear_days_to_oadate(oadate_t linear_days) noexcept { - if (linear_days < 0 && oadate_has_fraction(linear_days)) { - const oadate_t whole_days = oadate_floor_value(linear_days); - const oadate_t fraction = linear_days - whole_days; - return whole_days - fraction; - } - return linear_days; - } - - TIME_SHIELD_CONSTEXPR inline oadate_t oadate_to_linear_days(oadate_t oa) noexcept { - if (oa < 0 && oadate_has_fraction(oa)) { - const oadate_t whole_days = oadate_trunc_toward_zero(oa); - const oadate_t fraction = oadate_abs(oa - whole_days); - return whole_days + fraction; - } - return oa; - } - - } // namespace detail - - /// \brief Convert Unix timestamp (seconds) to OA date. - /// \param ts Unix timestamp in seconds (may be negative). - /// \return OA date value. - TIME_SHIELD_CONSTEXPR inline oadate_t ts_to_oadate(ts_t ts) noexcept { - const oadate_t linear_days = static_cast(OLE_EPOCH) - + static_cast(ts) / static_cast(SEC_PER_DAY); - return detail::linear_days_to_oadate(linear_days); - } - - /// \brief Convert Unix timestamp (floating seconds) to OA date. - /// \param ts Unix timestamp in seconds as floating point (may be negative). - /// \return OA date value. - TIME_SHIELD_CONSTEXPR inline oadate_t fts_to_oadate(fts_t ts) noexcept { - const oadate_t linear_days = static_cast(OLE_EPOCH) - + static_cast(ts) / static_cast(SEC_PER_DAY); - return detail::linear_days_to_oadate(linear_days); - } - - /// \brief Convert Unix timestamp (milliseconds) to OA date. - /// \param ts_ms Unix timestamp in milliseconds (may be negative). - /// \return OA date value. - TIME_SHIELD_CONSTEXPR inline oadate_t ts_ms_to_oadate(ts_ms_t ts_ms) noexcept { - const oadate_t linear_days = static_cast(OLE_EPOCH) - + static_cast(ts_ms) / static_cast(MS_PER_DAY); - return detail::linear_days_to_oadate(linear_days); - } - - /// \brief Convert OA date to Unix timestamp (seconds). - /// \param oa OA date value. - /// \return Unix timestamp in seconds (truncated toward zero). - TIME_SHIELD_CONSTEXPR inline ts_t oadate_to_ts(oadate_t oa) noexcept { - const oadate_t linear_days = detail::oadate_to_linear_days(oa); - const oadate_t seconds = (linear_days - static_cast(OLE_EPOCH)) - * static_cast(SEC_PER_DAY); - return static_cast(seconds); - } - - /// \brief Convert OA date to Unix timestamp (floating seconds). - /// \param oa OA date value. - /// \return Unix timestamp in seconds as floating point. - TIME_SHIELD_CONSTEXPR inline fts_t oadate_to_fts(oadate_t oa) noexcept { - const oadate_t linear_days = detail::oadate_to_linear_days(oa); - return static_cast((linear_days - static_cast(OLE_EPOCH)) - * static_cast(SEC_PER_DAY)); - } - - /// \brief Convert OA date to Unix timestamp (milliseconds). - /// \param oa OA date value. - /// \return Unix timestamp in milliseconds (truncated toward zero). - TIME_SHIELD_CONSTEXPR inline ts_ms_t oadate_to_ts_ms(oadate_t oa) noexcept { - const oadate_t linear_days = detail::oadate_to_linear_days(oa); - const oadate_t ms = (linear_days - static_cast(OLE_EPOCH)) - * static_cast(MS_PER_DAY); - return static_cast(ms); - } - - /// \brief Build OA date from calendar components (Gregorian). - /// \tparam T1 Year type. - /// \tparam T2 Month/day/time components type. - /// \tparam T3 Milliseconds type. - /// \return OA date value. - template - TIME_SHIELD_CONSTEXPR inline oadate_t to_oadate( - T1 year, T2 month, T2 day, - T2 hour = 0, T2 min = 0, T2 sec = 0, T3 ms = 0) noexcept { - // Use existing conversion to floating timestamp (seconds). - const fts_t fts = to_ftimestamp(year, month, day, hour, min, sec, ms); - return fts_to_oadate(fts); - } - -} // namespace time_shield - -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_OLE_AUTOMATION_CONVERSIONS_HPP_INCLUDED +#endif // TIME_SHIELD_HEADER_OLE_AUTOMATION_CONVERSIONS_HPP_INCLUDED diff --git a/include/time_shield/text.hpp b/include/time_shield/text.hpp new file mode 100644 index 00000000..c1e494b3 --- /dev/null +++ b/include/time_shield/text.hpp @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: MIT +#pragma once +#ifndef TIME_SHIELD_HEADER_TEXT_HPP_INCLUDED +#define TIME_SHIELD_HEADER_TEXT_HPP_INCLUDED + +#include +#include +#include +#include +#include + +#endif // TIME_SHIELD_HEADER_TEXT_HPP_INCLUDED diff --git a/include/time_shield/text/time_format_parser.hpp b/include/time_shield/text/time_format_parser.hpp new file mode 100644 index 00000000..fc4278fb --- /dev/null +++ b/include/time_shield/text/time_format_parser.hpp @@ -0,0 +1,1158 @@ +// SPDX-License-Identifier: MIT +#pragma once +#ifndef TIME_SHIELD_HEADER_TEXT_TIME_FORMAT_PARSER_HPP_INCLUDED +#define TIME_SHIELD_HEADER_TEXT_TIME_FORMAT_PARSER_HPP_INCLUDED + +/// \file time_format_parser.hpp +/// \brief Header file for fast parsing with formatter-compatible custom patterns. + +#include +#include + +#include +#include +#include +#include +#include + +#if __cplusplus >= 201703L +# include +#endif + +namespace time_shield { + + namespace detail { + namespace format_parse { + + struct FormatParseState { + bool has_tz; + bool has_year; + bool has_century; + bool has_two_digit_year; + bool has_iso_week_year; + bool has_iso_week_two_digit_year; + bool has_iso_week; + bool has_month; + bool has_day; + bool has_day_of_year; + bool has_hour24; + bool has_hour12; + bool has_minute; + bool has_second; + bool has_millisecond; + bool has_meridiem; + bool is_pm; + bool has_weekday; + bool has_iso_weekday; + bool has_unix_seconds; + year_t year; + year_t iso_week_year; + int century; + int two_digit_year; + int iso_week_two_digit_year; + int iso_week; + int month; + int day; + int day_of_year; + int hour24; + int hour12; + int minute; + int second; + int millisecond; + int weekday; + int iso_weekday; + ts_t unix_seconds; + TimeZoneStruct tz; + }; + + inline FormatParseState create_format_parse_state() noexcept { + FormatParseState state; + state.has_tz = false; + state.has_year = false; + state.has_century = false; + state.has_two_digit_year = false; + state.has_iso_week_year = false; + state.has_iso_week_two_digit_year = false; + state.has_iso_week = false; + state.has_month = false; + state.has_day = false; + state.has_day_of_year = false; + state.has_hour24 = false; + state.has_hour12 = false; + state.has_minute = false; + state.has_second = false; + state.has_millisecond = false; + state.has_meridiem = false; + state.is_pm = false; + state.has_weekday = false; + state.has_iso_weekday = false; + state.has_unix_seconds = false; + state.year = 0; + state.iso_week_year = 0; + state.century = 0; + state.two_digit_year = 0; + state.iso_week_two_digit_year = 0; + state.iso_week = 0; + state.month = 0; + state.day = 0; + state.day_of_year = 0; + state.hour24 = 0; + state.hour12 = 0; + state.minute = 0; + state.second = 0; + state.millisecond = 0; + state.weekday = 0; + state.iso_weekday = 0; + state.unix_seconds = 0; + state.tz = create_time_zone_struct(0, 0, true); + return state; + } + + TIME_SHIELD_CONSTEXPR inline bool is_ascii_digit(char c) noexcept { + return c >= '0' && c <= '9'; + } + + inline bool match_literal(const char*& p, const char* end, char expected) noexcept { + if (p >= end || *p != expected) { + return false; + } + ++p; + return true; + } + + inline bool match_literal(const char*& p, const char* end, const char* literal) noexcept { + if (!literal) { + return false; + } + while (*literal) { + if (p >= end || *p != *literal) { + return false; + } + ++p; + ++literal; + } + return true; + } + + inline bool parse_exact_2digits(const char*& p, const char* end, int& out) noexcept { + if (end - p < 2 || !is_ascii_digit(p[0]) || !is_ascii_digit(p[1])) { + return false; + } + out = (p[0] - '0') * 10 + (p[1] - '0'); + p += 2; + return true; + } + + inline bool parse_unsigned_digits( + const char*& p, + const char* end, + int min_digits, + int max_digits, + int64_t& out) noexcept { + const char* start = p; + int digits = 0; + int64_t value = 0; + while (p < end && digits < max_digits && is_ascii_digit(*p)) { + value = value * 10 + static_cast(*p - '0'); + ++p; + ++digits; + } + if (digits < min_digits) { + p = start; + return false; + } + out = value; + return true; + } + + inline bool parse_signed_digits( + const char*& p, + const char* end, + int min_digits, + int max_digits, + int64_t& out) noexcept { + const char* start = p; + bool negative = false; + if (p < end && (*p == '+' || *p == '-')) { + negative = (*p == '-'); + ++p; + } + int64_t value = 0; + if (!parse_unsigned_digits(p, end, min_digits, max_digits, value)) { + p = start; + return false; + } + out = negative ? -value : value; + return true; + } + + inline bool parse_space_padded_2digits(const char*& p, const char* end, int& out) noexcept { + if (end - p < 2) { + return false; + } + if (p[0] == ' ' && is_ascii_digit(p[1])) { + out = p[1] - '0'; + p += 2; + return true; + } + return parse_exact_2digits(p, end, out); + } + + inline bool parse_meridiem(const char*& p, const char* end, bool uppercase, bool& is_pm) noexcept { + if (end - p < 2) { + return false; + } + if (uppercase) { + if (p[0] == 'A' && p[1] == 'M') { + is_pm = false; + } else if (p[0] == 'P' && p[1] == 'M') { + is_pm = true; + } else { + return false; + } + } else { + if (p[0] == 'a' && p[1] == 'm') { + is_pm = false; + } else if (p[0] == 'p' && p[1] == 'm') { + is_pm = true; + } else { + return false; + } + } + p += 2; + return true; + } + + inline bool match_name_token( + const char*& p, + const char* end, + const char* const* names, + std::size_t count, + int index_base, + int& out) noexcept { + for (std::size_t i = 0; i < count; ++i) { + const char* name = names[i]; + const std::size_t len = std::strlen(name); + if (static_cast(end - p) < len) { + continue; + } + bool matched = true; + for (std::size_t k = 0; k < len; ++k) { + if (p[k] != name[k]) { + matched = false; + break; + } + } + if (matched) { + p += static_cast(len); + out = static_cast(i) + index_base; + return true; + } + } + return false; + } + + inline bool parse_month_token(const char*& p, const char* end, FormatType format, int& month) noexcept { + static const char* const uppercase_names[] = { + "JAN", "FEB", "MAR", "APR", "MAY", "JUN", + "JUL", "AUG", "SEP", "OCT", "NOV", "DEC" + }; + static const char* const short_names[] = { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" + }; + static const char* const full_names[] = { + "January", "February", "March", "April", "May", "June", + "July", "August", "September", "October", "November", "December" + }; + switch (format) { + case UPPERCASE_NAME: + return match_name_token(p, end, uppercase_names, 12, 1, month); + case SHORT_NAME: + return match_name_token(p, end, short_names, 12, 1, month); + case FULL_NAME: + return match_name_token(p, end, full_names, 12, 1, month); + default: + return false; + } + } + + inline bool parse_weekday_token(const char*& p, const char* end, FormatType format, int& weekday) noexcept { + static const char* const uppercase_names[] = { + "SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT" + }; + static const char* const short_names[] = { + "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" + }; + static const char* const full_names[] = { + "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" + }; + switch (format) { + case UPPERCASE_NAME: + return match_name_token(p, end, uppercase_names, 7, 0, weekday); + case SHORT_NAME: + return match_name_token(p, end, short_names, 7, 0, weekday); + case FULL_NAME: + return match_name_token(p, end, full_names, 7, 0, weekday); + default: + return false; + } + } + + /// \brief Parse `%z` timezone token in compact or extended ISO-style form. + /// \details Supported forms are `+HHMM`, `-HHMM`, `+HH:MM`, and `-HH:MM`. + inline bool parse_tz_offset_token(const char*& p, const char* end, TimeZoneStruct& tz) noexcept { + if (end - p < 5 || (*p != '+' && *p != '-')) { + return false; + } + + tz.is_positive = (*p == '+'); + ++p; + if (!parse_exact_2digits(p, end, tz.hour)) { + return false; + } + if (p < end && *p == ':') { + ++p; + } + if (!parse_exact_2digits(p, end, tz.min)) { + return false; + } + return is_valid_time_zone_offset(tz); + } + + inline bool assign_int_field(bool& has_field, int& field, int value) noexcept { + if (has_field && field != value) { + return false; + } + has_field = true; + field = value; + return true; + } + + inline bool assign_year_field(bool& has_field, year_t& field, year_t value) noexcept { + if (has_field && field != value) { + return false; + } + has_field = true; + field = value; + return true; + } + + inline bool has_iso_week_date_fields(const FormatParseState& state) noexcept { + return state.has_iso_week_year + || state.has_iso_week_two_digit_year + || state.has_iso_week; + } + + inline bool has_gregorian_date_fields(const FormatParseState& state) noexcept { + return state.has_year + || state.has_century + || state.has_two_digit_year + || state.has_month + || state.has_day + || state.has_day_of_year; + } + + inline int last_two_digits_of_year(year_t year) noexcept { + const int value = static_cast(year % 100); + return value < 0 ? -value : value; + } + + inline bool resolve_day_of_year(year_t year, int day_of_year, int& month, int& day) noexcept { + if (day_of_year < 1) { + return false; + } + const bool is_leap = is_leap_year_date(year); + const int max_day = is_leap ? 366 : 365; + if (day_of_year > max_day) { + return false; + } + + static const int days_per_month[] = { 31,28,31,30,31,30,31,31,30,31,30,31 }; + int remaining = day_of_year; + month = 1; + while (month <= 12) { + int days = days_per_month[month - 1]; + if (month == 2 && is_leap) { + ++days; + } + if (remaining <= days) { + day = remaining; + return true; + } + remaining -= days; + ++month; + } + return false; + } + + inline int compute_day_of_year(year_t year, int month, int day) noexcept { + static const int day_offsets[] = { 0,31,59,90,120,151,181,212,243,273,304,334 }; + int result = day_offsets[month - 1] + day; + if (month > 2 && is_leap_year_date(year)) { + ++result; + } + return result; + } + + inline bool parse_compact_extended_year(const char*& p, const char* end, year_t& out) noexcept { + const char* start = p; + bool negative = false; + if (p < end && (*p == '+' || *p == '-')) { + negative = (*p == '-'); + ++p; + } + + int64_t head = 0; + if (!parse_unsigned_digits(p, end, 1, 18, head)) { + p = start; + return false; + } + + int64_t year_value = 0; + if (p < end && *p == 'M') { + ++p; + int64_t millennia = 0; + if (!parse_unsigned_digits(p, end, 1, 18, millennia)) { + p = start; + return false; + } + int64_t tail = 0; + if (p < end && *p == 'K') { + ++p; + if (!parse_unsigned_digits(p, end, 3, 3, tail)) { + p = start; + return false; + } + year_value = head * 1000000LL + millennia * 1000LL + tail; + } else { + year_value = head * 1000000LL + millennia; + } + } else if (p < end && *p == 'K') { + ++p; + int64_t tail = 0; + if (!parse_unsigned_digits(p, end, 3, 3, tail)) { + p = start; + return false; + } + year_value = head * 1000LL + tail; + } else { + year_value = head; + } + + out = static_cast(negative ? -year_value : year_value); + return true; + } + + inline bool parse_format_sequence( + const char*& p, + const char* end, + const char* format_data, + std::size_t format_size, + FormatParseState& state) noexcept; + + inline bool parse_format_token( + const char*& p, + const char* end, + char token, + std::size_t repeat_count, + FormatParseState& state) noexcept { + int value = 0; + int64_t wide_value = 0; + switch (token) { + case 'a': + return repeat_count == 1 + && parse_weekday_token(p, end, SHORT_NAME, value) + && assign_int_field(state.has_weekday, state.weekday, value); + case 'A': + return repeat_count == 1 + && parse_weekday_token(p, end, FULL_NAME, value) + && assign_int_field(state.has_weekday, state.weekday, value); + case 'b': + return repeat_count == 1 + && parse_month_token(p, end, SHORT_NAME, value) + && assign_int_field(state.has_month, state.month, value); + case 'B': + return repeat_count == 1 + && parse_month_token(p, end, FULL_NAME, value) + && assign_int_field(state.has_month, state.month, value); + case 'c': + return repeat_count == 1 + && parse_format_sequence(p, end, "%a %b %e %H:%M:%S %Y", 20, state); + case 'C': + if (repeat_count != 1 || !parse_unsigned_digits(p, end, 1, 6, wide_value)) { + return false; + } + return assign_int_field(state.has_century, state.century, static_cast(wide_value)); + case 'd': + if (repeat_count >= 2 || !parse_exact_2digits(p, end, value)) { + return false; + } + return assign_int_field(state.has_day, state.day, value); + case 'D': + if (repeat_count == 1) { + return parse_format_sequence(p, end, "%m/%d/%y", 8, state); + } + if (repeat_count == 2) { + if (!parse_exact_2digits(p, end, value)) { + return false; + } + return assign_int_field(state.has_day, state.day, value); + } + return false; + case 'e': + if (repeat_count != 1 || !parse_space_padded_2digits(p, end, value)) { + return false; + } + return assign_int_field(state.has_day, state.day, value); + case 'F': + return repeat_count == 1 + && parse_format_sequence(p, end, "%Y-%m-%d", 8, state); + case 'g': + if (repeat_count != 1 || !parse_unsigned_digits(p, end, 2, 2, wide_value)) { + return false; + } + return assign_int_field(state.has_iso_week_two_digit_year, state.iso_week_two_digit_year, static_cast(wide_value)); + case 'G': + if (repeat_count != 1 || !parse_signed_digits(p, end, 1, 18, wide_value)) { + return false; + } + return assign_year_field(state.has_iso_week_year, state.iso_week_year, static_cast(wide_value)); + case 'H': + if (repeat_count > 2 || !parse_exact_2digits(p, end, value)) { + return false; + } + return assign_int_field(state.has_hour24, state.hour24, value); + case 'h': + if (repeat_count == 2) { + if (!parse_exact_2digits(p, end, value)) { + return false; + } + return assign_int_field(state.has_hour24, state.hour24, value); + } + return repeat_count == 1 + && parse_month_token(p, end, SHORT_NAME, value) + && assign_int_field(state.has_month, state.month, value); + case 'I': + if (repeat_count != 1 || !parse_exact_2digits(p, end, value)) { + return false; + } + return assign_int_field(state.has_hour12, state.hour12, value); + case 'j': + if (repeat_count != 1 || !parse_unsigned_digits(p, end, 3, 3, wide_value)) { + return false; + } + return assign_int_field(state.has_day_of_year, state.day_of_year, static_cast(wide_value)); + case 'k': + if (repeat_count != 1 || !parse_space_padded_2digits(p, end, value)) { + return false; + } + return assign_int_field(state.has_hour24, state.hour24, value); + case 'l': + if (repeat_count != 1 || !parse_space_padded_2digits(p, end, value)) { + return false; + } + return assign_int_field(state.has_hour12, state.hour12, value); + case 'm': + if (repeat_count == 1) { + if (!parse_exact_2digits(p, end, value)) { + return false; + } + return assign_int_field(state.has_month, state.month, value); + } + if (repeat_count == 2) { + if (!parse_exact_2digits(p, end, value)) { + return false; + } + return assign_int_field(state.has_minute, state.minute, value); + } + return false; + case 'M': + if (repeat_count == 1) { + if (!parse_exact_2digits(p, end, value)) { + return false; + } + return assign_int_field(state.has_minute, state.minute, value); + } + if (repeat_count == 2) { + if (!parse_exact_2digits(p, end, value)) { + return false; + } + return assign_int_field(state.has_month, state.month, value); + } + if (repeat_count == 3) { + return parse_month_token(p, end, UPPERCASE_NAME, value) + && assign_int_field(state.has_month, state.month, value); + } + return false; + case 'n': + return repeat_count == 1 && match_literal(p, end, '\n'); + case 'p': + if (repeat_count != 1 || !parse_meridiem(p, end, true, state.is_pm)) { + return false; + } + state.has_meridiem = true; + return true; + case 'P': + if (repeat_count != 1 || !parse_meridiem(p, end, false, state.is_pm)) { + return false; + } + state.has_meridiem = true; + return true; + case 'r': + return repeat_count == 1 + && parse_format_sequence(p, end, "%I:%M:%S %p", 11, state); + case 'R': + return repeat_count == 1 + && parse_format_sequence(p, end, "%H:%M", 5, state); + case 's': + if (repeat_count == 1) { + if (!parse_signed_digits(p, end, 1, 18, wide_value)) { + return false; + } + state.has_unix_seconds = true; + state.unix_seconds = static_cast(wide_value); + return true; + } + if (repeat_count == 3) { + if (!parse_unsigned_digits(p, end, 1, 3, wide_value)) { + return false; + } + return assign_int_field(state.has_millisecond, state.millisecond, static_cast(wide_value)); + } + return false; + case 'S': + if (repeat_count <= 2) { + if (!parse_exact_2digits(p, end, value)) { + return false; + } + return assign_int_field(state.has_second, state.second, value); + } + if (repeat_count == 3) { + if (!parse_unsigned_digits(p, end, 1, 3, wide_value)) { + return false; + } + return assign_int_field(state.has_millisecond, state.millisecond, static_cast(wide_value)); + } + return false; + case 't': + return repeat_count == 1 && match_literal(p, end, '\t'); + case 'T': + return repeat_count == 1 + && parse_format_sequence(p, end, "%H:%M:%S", 8, state); + case 'u': + if (repeat_count != 1 || !parse_unsigned_digits(p, end, 1, 1, wide_value)) { + return false; + } + return assign_int_field(state.has_iso_weekday, state.iso_weekday, static_cast(wide_value)); + case 'w': + if (repeat_count == 1) { + if (!parse_unsigned_digits(p, end, 1, 1, wide_value)) { + return false; + } + return assign_int_field(state.has_weekday, state.weekday, static_cast(wide_value)); + } + if (repeat_count == 3) { + return parse_weekday_token(p, end, SHORT_NAME, value) + && assign_int_field(state.has_weekday, state.weekday, value); + } + return false; + case 'W': + if (repeat_count == 3) { + return parse_weekday_token(p, end, UPPERCASE_NAME, value) + && assign_int_field(state.has_weekday, state.weekday, value); + } + return false; + case 'V': + if (repeat_count != 1 || !parse_exact_2digits(p, end, value)) { + return false; + } + return assign_int_field(state.has_iso_week, state.iso_week, value); + case 'y': + if (repeat_count != 1 || !parse_unsigned_digits(p, end, 1, 2, wide_value)) { + return false; + } + return assign_int_field(state.has_two_digit_year, state.two_digit_year, static_cast(wide_value)); + case 'Y': + if (repeat_count == 1) { + if (!parse_signed_digits(p, end, 1, 18, wide_value)) { + return false; + } + return assign_year_field(state.has_year, state.year, static_cast(wide_value)); + } + if (repeat_count == 2) { + if (!parse_unsigned_digits(p, end, 2, 2, wide_value)) { + return false; + } + return assign_int_field(state.has_two_digit_year, state.two_digit_year, static_cast(wide_value)); + } + if (repeat_count == 4) { + if (!parse_signed_digits(p, end, 4, 4, wide_value)) { + return false; + } + return assign_year_field(state.has_year, state.year, static_cast(wide_value)); + } + if (repeat_count == 6) { + year_t parsed_year = 0; + if (!parse_compact_extended_year(p, end, parsed_year)) { + return false; + } + return assign_year_field(state.has_year, state.year, parsed_year); + } + return false; + case 'z': + if (repeat_count != 1 || !parse_tz_offset_token(p, end, state.tz)) { + return false; + } + state.has_tz = true; + return true; + case 'Z': + if (repeat_count != 1 || !match_literal(p, end, "UTC")) { + return false; + } + state.tz = create_time_zone_struct(0, 0, true); + state.has_tz = true; + return true; + default: + return false; + } + } + + inline bool parse_format_sequence( + const char*& p, + const char* end, + const char* format_data, + std::size_t format_size, + FormatParseState& state) noexcept { + bool is_command = false; + std::size_t repeat_count = 0; + char last_char = 0; + + for (std::size_t i = 0; i < format_size; ++i) { + const char current_char = format_data[i]; + if (!is_command) { + if (current_char == '%') { + ++repeat_count; + if (repeat_count == 2) { + if (!match_literal(p, end, '%')) { + return false; + } + repeat_count = 0; + } + continue; + } + if (!repeat_count) { + if (!match_literal(p, end, current_char)) { + return false; + } + continue; + } + last_char = current_char; + is_command = true; + continue; + } + if (last_char == current_char) { + ++repeat_count; + continue; + } + if (!parse_format_token(p, end, last_char, repeat_count, state)) { + return false; + } + repeat_count = 0; + is_command = false; + --i; + } + + if (is_command) { + if (!parse_format_token(p, end, last_char, repeat_count, state)) { + return false; + } + } + return true; + } + + inline bool validate_weekday_constraints(const FormatParseState& state, const DateTimeStruct& dt) noexcept { + if (state.has_weekday && day_of_week_date(dt.year, dt.mon, dt.day) != state.weekday) { + return false; + } + if (has_iso_week_date_fields(state) || state.has_iso_weekday) { + const IsoWeekDateStruct iso_week = to_iso_week_date(dt.year, dt.mon, dt.day); + if (state.has_iso_week_year && iso_week.year != state.iso_week_year) { + return false; + } + if (state.has_iso_week_two_digit_year && last_two_digits_of_year(iso_week.year) != state.iso_week_two_digit_year) { + return false; + } + if (state.has_iso_week && iso_week.week != state.iso_week) { + return false; + } + if (state.has_iso_weekday && iso_week.weekday != state.iso_weekday) { + return false; + } + } + return true; + } + + inline bool resolve_time_fields(const FormatParseState& state, int& hour, int& minute, int& second, int& millisecond) noexcept { + hour = 0; + if (state.has_hour24) { + hour = state.hour24; + if (state.has_meridiem && state.has_hour12) { + int expected = state.hour12 % 12; + if (state.is_pm) { + expected += 12; + } + if (expected != hour) { + return false; + } + } + } else if (state.has_hour12) { + if (!state.has_meridiem || state.hour12 < 1 || state.hour12 > 12) { + return false; + } + hour = state.hour12 % 12; + if (state.is_pm) { + hour += 12; + } + } + + minute = state.has_minute ? state.minute : 0; + second = state.has_second ? state.second : 0; + millisecond = state.has_millisecond ? state.millisecond : 0; + return true; + } + + inline bool finalize_calendar_state(FormatParseState& state, DateTimeStruct& out_dt, TimeZoneStruct& out_tz) noexcept { + out_tz = state.has_tz ? state.tz : create_time_zone_struct(0, 0, true); + + if (has_iso_week_date_fields(state)) { + if (has_gregorian_date_fields(state)) { + return false; + } + + year_t iso_week_year = 0; + if (state.has_iso_week_year) { + iso_week_year = state.iso_week_year; + if (state.has_iso_week_two_digit_year + && last_two_digits_of_year(iso_week_year) != state.iso_week_two_digit_year) { + return false; + } + } else if (state.has_iso_week_two_digit_year) { + iso_week_year = static_cast(state.iso_week_two_digit_year); + } else { + return false; + } + + if (!state.has_iso_week) { + return false; + } + + const IsoWeekDateStruct iso_week_date = create_iso_week_date_struct( + iso_week_year, + state.iso_week, + state.has_iso_weekday ? state.iso_weekday : 1); + if (!is_valid_iso_week_date(iso_week_date.year, iso_week_date.week, iso_week_date.weekday)) { + return false; + } + + const DateStruct calendar_date = iso_week_date_to_date(iso_week_date); + int hour = 0; + int minute = 0; + int second = 0; + int millisecond = 0; + if (!resolve_time_fields(state, hour, minute, second, millisecond)) { + return false; + } + + out_dt = create_date_time_struct( + calendar_date.year, + calendar_date.mon, + calendar_date.day, + hour, + minute, + second, + millisecond); + if (!is_valid_date_time(out_dt)) { + return false; + } + return validate_weekday_constraints(state, out_dt); + } + + year_t year = 0; + if (state.has_year) { + year = state.year; + if (state.has_century && year / 100 != state.century) { + return false; + } + const int yy = static_cast(year >= 0 ? (year % 100) : -(year % 100)); + if (state.has_two_digit_year && yy != state.two_digit_year) { + return false; + } + } else if (state.has_century || state.has_two_digit_year) { + year = static_cast((state.has_century ? state.century : 0) * 100 + + (state.has_two_digit_year ? state.two_digit_year : 0)); + } else { + return false; + } + + int month = state.has_month ? state.month : 0; + int day = state.has_day ? state.day : 0; + if (state.has_day_of_year) { + int resolved_month = 0; + int resolved_day = 0; + if (!resolve_day_of_year(year, state.day_of_year, resolved_month, resolved_day)) { + return false; + } + if (state.has_month && state.month != resolved_month) { + return false; + } + if (state.has_day && state.day != resolved_day) { + return false; + } + month = resolved_month; + day = resolved_day; + } + if (month == 0 || day == 0) { + return false; + } + + int hour = 0; + int minute = 0; + int second = 0; + int millisecond = 0; + if (!resolve_time_fields(state, hour, minute, second, millisecond)) { + return false; + } + + out_dt = create_date_time_struct( + year, + month, + day, + hour, + minute, + second, + millisecond); + if (!is_valid_date_time(out_dt)) { + return false; + } + return validate_weekday_constraints(state, out_dt); + } + + inline bool finalize_format_parse_state( + FormatParseState& state, + DateTimeStruct& out_dt, + TimeZoneStruct& out_tz) noexcept { + if (has_iso_week_date_fields(state) && has_gregorian_date_fields(state)) { + return false; + } + + if (state.has_unix_seconds) { + out_tz = state.has_tz ? state.tz : create_time_zone_struct(0, 0, true); + const tz_t offset = time_zone_struct_to_offset(out_tz); + const ts_t local_ts = state.has_tz ? static_cast(state.unix_seconds + offset) + : state.unix_seconds; + out_dt = to_date_time(local_ts); + if (state.has_millisecond) { + out_dt.ms = state.millisecond; + } + + if (state.has_year && out_dt.year != state.year) return false; + if (state.has_month && out_dt.mon != state.month) return false; + if (state.has_day && out_dt.day != state.day) return false; + if (state.has_hour24 && out_dt.hour != state.hour24) return false; + if (state.has_minute && out_dt.min != state.minute) return false; + if (state.has_second && out_dt.sec != state.second) return false; + if (state.has_millisecond && out_dt.ms != state.millisecond) return false; + if (state.has_day_of_year && compute_day_of_year(out_dt.year, out_dt.mon, out_dt.day) != state.day_of_year) return false; + return validate_weekday_constraints(state, out_dt); + } + + return finalize_calendar_state(state, out_dt, out_tz); + } + + inline bool try_parse_format_core( + const char* data, + std::size_t length, + const char* format, + std::size_t format_length, + DateTimeStruct& out_dt, + TimeZoneStruct& out_tz) noexcept { + if (!data || !format) { + return false; + } + FormatParseState state = create_format_parse_state(); + const char* p = data; + const char* end = data + length; + if (!parse_format_sequence(p, end, format, format_length, state)) { + return false; + } + if (p != end) { + return false; + } + return finalize_format_parse_state(state, out_dt, out_tz); + } + + } // namespace format_parse + } // namespace detail + + /// \ingroup time_parsing + /// \brief Parse input using formatter-compatible custom pattern. + /// \details ISO week-based tokens `%G`, `%g`, `%V`, and `%u` follow the + /// same grammar as formatter output. Formats using ISO week-based year/week + /// tokens do not mix with Gregorian `%Y` / `%m` / `%d` date tokens. + inline bool try_parse_format( + const char* data, + std::size_t length, + const char* format, + std::size_t format_length, + DateTimeStruct& out_dt, + TimeZoneStruct& out_tz) noexcept { + return detail::format_parse::try_parse_format_core(data, length, format, format_length, out_dt, out_tz); + } + + /// \ingroup time_parsing + /// \brief Parse input using formatter-compatible custom pattern and convert to UTC seconds. + inline bool try_parse_format_ts( + const char* data, + std::size_t length, + const char* format, + std::size_t format_length, + ts_t& out_ts) noexcept { + DateTimeStruct dt; + TimeZoneStruct tz; + if (!try_parse_format(data, length, format, format_length, dt, tz)) { + out_ts = 0; + return false; + } + try { + out_ts = dt_to_timestamp(dt) - time_zone_struct_to_offset(tz); + return true; + } catch (...) { + out_ts = 0; + return false; + } + } + + /// \ingroup time_parsing + /// \brief Parse input using formatter-compatible custom pattern and convert to UTC milliseconds. + inline bool try_parse_format_ts_ms( + const char* data, + std::size_t length, + const char* format, + std::size_t format_length, + ts_ms_t& out_ts) noexcept { + DateTimeStruct dt; + TimeZoneStruct tz; + if (!try_parse_format(data, length, format, format_length, dt, tz)) { + out_ts = 0; + return false; + } + try { + out_ts = dt_to_timestamp_ms(dt) - sec_to_ms(time_zone_struct_to_offset(tz)); + return true; + } catch (...) { + out_ts = 0; + return false; + } + } + + /// \ingroup time_parsing + /// \brief Parse std::string using formatter-compatible custom pattern. + inline bool try_parse_format( + const std::string& data, + const std::string& format, + DateTimeStruct& out_dt, + TimeZoneStruct& out_tz) noexcept { + return try_parse_format(data.data(), data.size(), format.data(), format.size(), out_dt, out_tz); + } + + /// \ingroup time_parsing + /// \brief Parse null-terminated strings using formatter-compatible custom pattern. + inline bool try_parse_format( + const char* data, + const char* format, + DateTimeStruct& out_dt, + TimeZoneStruct& out_tz) noexcept { + if (!data || !format) { + return false; + } + return try_parse_format(data, std::strlen(data), format, std::strlen(format), out_dt, out_tz); + } + + /// \ingroup time_parsing + /// \brief Parse std::string using formatter-compatible custom pattern and convert to UTC seconds. + inline bool try_parse_format_ts( + const std::string& data, + const std::string& format, + ts_t& out_ts) noexcept { + return try_parse_format_ts(data.data(), data.size(), format.data(), format.size(), out_ts); + } + + /// \ingroup time_parsing + /// \brief Parse null-terminated strings using custom format and convert to UTC seconds. + inline bool try_parse_format_ts( + const char* data, + const char* format, + ts_t& out_ts) noexcept { + if (!data || !format) { + out_ts = 0; + return false; + } + return try_parse_format_ts(data, std::strlen(data), format, std::strlen(format), out_ts); + } + + /// \ingroup time_parsing + /// \brief Parse std::string using formatter-compatible custom pattern and convert to UTC milliseconds. + inline bool try_parse_format_ts_ms( + const std::string& data, + const std::string& format, + ts_ms_t& out_ts) noexcept { + return try_parse_format_ts_ms(data.data(), data.size(), format.data(), format.size(), out_ts); + } + + /// \ingroup time_parsing + /// \brief Parse null-terminated strings using custom format and convert to UTC milliseconds. + inline bool try_parse_format_ts_ms( + const char* data, + const char* format, + ts_ms_t& out_ts) noexcept { + if (!data || !format) { + out_ts = 0; + return false; + } + return try_parse_format_ts_ms(data, std::strlen(data), format, std::strlen(format), out_ts); + } + +#if __cplusplus >= 201703L + /// \ingroup time_parsing + /// \brief Parse std::string_view using formatter-compatible custom pattern. + inline bool try_parse_format( + std::string_view data, + std::string_view format, + DateTimeStruct& out_dt, + TimeZoneStruct& out_tz) noexcept { + return try_parse_format(data.data(), data.size(), format.data(), format.size(), out_dt, out_tz); + } + + /// \ingroup time_parsing + /// \brief Parse std::string_view using formatter-compatible custom pattern and convert to UTC seconds. + inline bool try_parse_format_ts( + std::string_view data, + std::string_view format, + ts_t& out_ts) noexcept { + return try_parse_format_ts(data.data(), data.size(), format.data(), format.size(), out_ts); + } + + /// \ingroup time_parsing + /// \brief Parse std::string_view using formatter-compatible custom pattern and convert to UTC milliseconds. + inline bool try_parse_format_ts_ms( + std::string_view data, + std::string_view format, + ts_ms_t& out_ts) noexcept { + return try_parse_format_ts_ms(data.data(), data.size(), format.data(), format.size(), out_ts); + } +#endif + +} // namespace time_shield + +#endif // TIME_SHIELD_HEADER_TEXT_TIME_FORMAT_PARSER_HPP_INCLUDED diff --git a/include/time_shield/text/time_formatting.hpp b/include/time_shield/text/time_formatting.hpp new file mode 100644 index 00000000..196cc40d --- /dev/null +++ b/include/time_shield/text/time_formatting.hpp @@ -0,0 +1,1006 @@ +// SPDX-License-Identifier: MIT +#pragma once +#ifndef TIME_SHIELD_HEADER_TEXT_TIME_FORMATTING_HPP_INCLUDED +#define TIME_SHIELD_HEADER_TEXT_TIME_FORMATTING_HPP_INCLUDED + +/// \file time_formatting.hpp +/// \brief Header file for time formatting utilities. +/// +/// This file contains functions for converting timestamps to formatted strings. +/// It provides utilities for custom formatting based on user-defined patterns +/// and for standard date-time string representations. + +#include +#include + +#include + +namespace time_shield { + +/// \ingroup time_formatting +/// \{ + + inline void process_format_impl( + char last_char, + size_t repeat_count, + ts_t ts, + tz_t utc_offset, + const DateTimeStruct& dt, + std::string& result) { + switch (last_char) { + case 'a': + if (repeat_count > 1) break; + result += to_str(day_of_week(dt.year, dt.mon, dt.day), FormatType::SHORT_NAME); + break; + case 'A': + if (repeat_count > 1) break; + result += to_str(day_of_week(dt.year, dt.mon, dt.day), FormatType::FULL_NAME); + break; + case 'I': + if (repeat_count == 1) { + char buffer[4] = {0}; + snprintf(buffer, sizeof(buffer), "%.2d", hour24_to_12(dt.hour)); + result += std::string(buffer); + } + break; + case 'H': + if (repeat_count <= 2) { + char buffer[4] = {0}; + snprintf(buffer, sizeof(buffer),"%.2d", dt.hour); + result += std::string(buffer); + } + break; + case 'h': + if (repeat_count == 2) { + char buffer[4] = {0}; + snprintf(buffer, sizeof(buffer),"%.2d", dt.hour); + result += std::string(buffer); + break; + } + + // fallthrough + case 'b': + // %h: Equivalent to %b + if (repeat_count > 1) break; + result += to_str(static_cast(dt.mon), FormatType::SHORT_NAME); + break; + case 'B': + if (repeat_count > 1) break; + result += to_str(static_cast(dt.mon), FormatType::FULL_NAME); + break; + case 'c': + // Preferred date and time representation for the current locale. + // %a %b %e %H:%M:%S %Y + if (repeat_count <= 1){ + char buffer[16]; + result += to_str(day_of_week(dt.year, dt.mon, dt.day), FormatType::SHORT_NAME); + result += " "; + result += to_str(static_cast(dt.mon), FormatType::SHORT_NAME); + result += " "; + // added %e + std::fill(buffer, buffer + sizeof(buffer), '\0'); + snprintf(buffer, sizeof(buffer),"%2d ", dt.day); + result += std::string(buffer); + // added %H:%M:%S + std::fill(buffer, buffer + sizeof(buffer), '\0'); + snprintf(buffer, sizeof(buffer), "%.2d:%.2d:%.2d ", dt.hour, dt.min, dt.sec); + result += std::string(buffer); + // added %Y + result += std::to_string(dt.year); + } + break; + case 'C': + if (repeat_count > 1) break; + result += std::to_string(dt.year/100); + break; + case 'd': + if (repeat_count < 2) { + char buffer[4] = {0}; + snprintf(buffer, sizeof(buffer),"%.2d", dt.day); + result += std::string(buffer); + } + break; + case 'D': + if (repeat_count == 1) { + // %m/%d/%y + char buffer[16] = {0}; + snprintf(buffer, sizeof(buffer), "%.2d/%.2d/%.2d", dt.mon, dt.day, (int)(dt.year % 100LL)); + result += std::string(buffer); + } else + if (repeat_count == 2) { + char buffer[4] = {0}; + snprintf(buffer, sizeof(buffer),"%.2d", dt.day); + result += std::string(buffer); + } + break; + case 'e': + if (repeat_count == 1) { + char buffer[4] = {0}; + snprintf(buffer, sizeof(buffer),"%2d", dt.day); + result += std::string(buffer); + } + break; + case 'E': + // %E: Modifier for alternative ("era-based") format. + // https://help.hcltechsw.com/onedb/1.0.0.1/gug/ids_gug_086.html#ids_gug_086 + break; + case 'F': + if (repeat_count == 1) { + // %Y-%m-%d ISO 8601 date format + char buffer[32] = {0}; + if (dt.year <= 9999 && dt.year >= 0) { + snprintf(buffer, sizeof(buffer), "%.4d-%.2d-%.2d", (int)dt.year, dt.mon, dt.day); + } else + if (dt.year < 0) { + snprintf(buffer, sizeof(buffer), "-%" PRId64 "-%.2d-%.2d", dt.year, dt.mon, dt.day); + } else { + snprintf(buffer, sizeof(buffer), "+%" PRId64 "-%.2d-%.2d", dt.year, dt.mon, dt.day); + } + result += std::string(buffer); + } + break; + case 'g': + // ISO 8601 week-based year without century (2-digit year). + if (repeat_count == 1) { + const IsoWeekDateStruct iso_week = to_iso_week_date(dt.year, dt.mon, dt.day); + const int two_digit_year = static_cast(iso_week.year % 100); + char buffer[4] = {0}; + snprintf(buffer, sizeof(buffer), "%.2d", two_digit_year < 0 ? -two_digit_year : two_digit_year); + result += std::string(buffer); + } + break; + case 'G': + // ISO 8601 week-based year with century as a decimal number. + if (repeat_count == 1) { + const IsoWeekDateStruct iso_week = to_iso_week_date(dt.year, dt.mon, dt.day); + result += std::to_string(iso_week.year); + } + break; + case 'j': + if (repeat_count == 1) { + char buffer[4] = {0}; + snprintf(buffer, sizeof(buffer), "%.3d", day_of_year(ts)); + result += std::string(buffer); + } + break; + case 'k': + if (repeat_count == 1) { + char buffer[4] = {0}; + snprintf(buffer, sizeof(buffer), "%2d", dt.hour); + result += std::string(buffer); + } + break; + case 'l': + if (repeat_count == 1) { + char buffer[4] = {0}; + snprintf(buffer, sizeof(buffer), "%2d", hour24_to_12(dt.hour)); + result += std::string(buffer); + } + break; + case 'm': + if (repeat_count == 1) { + char buffer[4] = {0}; + snprintf(buffer, sizeof(buffer), "%.2d", dt.mon); + result += std::string(buffer); + } else + if (repeat_count == 2) { + char buffer[4] = {0}; + snprintf(buffer, sizeof(buffer), "%.2d", dt.min); + result += std::string(buffer); + } + break; + case 'M': + if (repeat_count == 1) { + char buffer[4] = {0}; + snprintf(buffer, sizeof(buffer), "%.2d", dt.min); + result += std::string(buffer); + } else + if (repeat_count == 2) { + char buffer[4] = {0}; + snprintf(buffer, sizeof(buffer), "%.2d", dt.mon); + result += std::string(buffer); + } else + if (repeat_count == 3) { + result += to_str(static_cast(dt.mon), FormatType::UPPERCASE_NAME); + } + break; + case 'n': + result += "\n"; + break; + case 'O': + // Modifier for using alternative numeric symbols. + break; + case 'p': + if (dt.hour < 12) result += "AM"; + else result += "PM"; + break; + case 'P': + if (dt.hour < 12) result += "am"; + else result += "pm"; + break; + case 'r': + if (repeat_count == 1) { + char buffer[16] = {0}; + if (dt.hour < 12) snprintf(buffer, sizeof(buffer), "%.2d:%.2d:%.2d AM", hour24_to_12(dt.hour), dt.min, dt.sec); + else snprintf(buffer, sizeof(buffer), "%.2d:%.2d:%.2d PM", hour24_to_12(dt.hour), dt.min, dt.sec); + result += std::string(buffer); + break; + } + break; + case 'R': + // %H:%M + if (repeat_count == 1) { + char buffer[8] = {0}; + snprintf(buffer, sizeof(buffer), "%.2d:%.2d", dt.hour, dt.min); + result += std::string(buffer); + } + break; + case 's': + if (repeat_count == 1) { + result += std::to_string(ts); + break; + } + if (repeat_count == 3) { + result += std::to_string(dt.ms); + break; + } + // to '%ss' + + // fallthrough + case 'S': + if (repeat_count <= 2) { + char buffer[4] = {0}; + snprintf(buffer, sizeof(buffer), "%.2d", dt.sec); + result += std::string(buffer); + } + if (repeat_count == 3) { + result += std::to_string(dt.ms); + break; + } + break; + case 't': + if (repeat_count > 1) break; + result += "\t"; + break; + case 'T': + // %H:%M:%S + if (repeat_count == 1) { + char buffer[16] = {0}; + snprintf(buffer, sizeof(buffer), "%.2d:%.2d:%.2d", dt.hour, dt.min, dt.sec); + result += std::string(buffer); + } + break; + case 'u': + if (repeat_count == 1) { + // Day of the week as a decimal number (1 to 7, Monday being 1). + int dw = day_of_week(dt.year, dt.mon, dt.day); + if (dw == 0) dw = 7; + result += std::to_string(dw); + } + break; + case 'U': + // Week number of the current year (00 to 53, starting with the first Sunday as week 01). + break; + case 'V': + // ISO 8601 week number of the current year (01 to 53, with specific rules). + if (repeat_count == 1) { + const IsoWeekDateStruct iso_week = to_iso_week_date(dt.year, dt.mon, dt.day); + char buffer[4] = {0}; + snprintf(buffer, sizeof(buffer), "%.2d", iso_week.week); + result += std::string(buffer); + } + break; + case 'w': + // Day of the week as a decimal number (0 to 6, Sunday being 0). + if (repeat_count == 1) { + result += std::to_string(day_of_week(dt.year, dt.mon, dt.day)); + } else + if (repeat_count == 3) { + result += to_str(day_of_week(dt.year, dt.mon, dt.day), FormatType::SHORT_NAME); + } + break; + case 'W': + // Week number of the current year (00 to 53, starting with the first Monday as week 01). + if (repeat_count == 3) { + result += to_str(day_of_week(dt.year, dt.mon, dt.day), FormatType::UPPERCASE_NAME); + } + break; + case 'x': + // Preferred date representation for the current locale without the time. + break; + case 'X': + // Preferred time representation for the current locale without the date. + break; + case 'y': + if (repeat_count == 1) { + result += std::to_string(dt.year % 100); + } + break; + case 'Y': + if (repeat_count == 1) { + result += std::to_string(dt.year); + } else + if (repeat_count == 6) { + char buffer[32] = {0}; + const int64_t mega_years = dt.year / 1000000; + const int64_t millennia = (dt.year - mega_years * 1000000) / 1000; + const int64_t centuries = dt.year - mega_years * 1000000 - millennia * 1000; + if (mega_years) { + if (millennia) { + snprintf( + buffer, + sizeof(buffer), + "%" PRId64 "M%" PRId64 "K%.3" PRId64, + mega_years, + static_cast(std::abs(millennia)), + static_cast(std::abs(centuries))); + } else { + snprintf( + buffer, + sizeof(buffer), + "%" PRId64 "M%.3" PRId64, + mega_years, + static_cast(std::abs(centuries))); + } + } else + if (millennia) { + snprintf( + buffer, + sizeof(buffer), + "%" PRId64 "K%.3" PRId64, + millennia, + static_cast(std::abs(centuries))); + } else { + snprintf(buffer, sizeof(buffer), "%.4" PRId64, dt.year); + } + result += std::string(buffer); + } else + if (repeat_count == 4) { + char buffer[8] = {0}; + snprintf(buffer, sizeof(buffer), "%.4d", (int)(dt.year % 10000)); + result += std::string(buffer); + } else + if (repeat_count == 2) { + char buffer[8] = {0}; + snprintf(buffer, sizeof(buffer), "%.2d", (int)(dt.year % 100)); + result += std::string(buffer); + } + break; + case 'z': + // +hhmm or -hhmm numeric timezone offset from UTC. + if (repeat_count == 1) { + TimeZoneStruct tz = to_time_zone_struct(utc_offset); + char buffer[16] = {0}; + if (tz.is_positive) snprintf(buffer, sizeof(buffer), "+%.2d%.2d", tz.hour, tz.min); + else snprintf(buffer, sizeof(buffer), "-%.2d%.2d", tz.hour, tz.min); + result += std::string(buffer); + } + break; + case 'Z': + // Timezone name or abbreviation. + + result += "UTC"; + break; + case '+': + // Date and time in date(1) format (not supported in glibc2). + // Tue Jun 4 04:07:43 UTC 2024 + break; + }; + } + + /// \brief Convert timestamp to string with custom format. + /// + /// This function is similar to the strftime function and supports the majority of its specifiers, + /// as well as additional ones: YY, YYYY, YYYYYY, WWW, www, hh, mm, ss, dd, sss. + /// + /// Accepts the following format specifiers as parameters: + /// - %YYYYYY: Year with reduction in the number of millennia. + /// - %YYYY: Year represented by 4 digits. + /// - %YY: Last two digits of the year. + /// - %MM: Month (01-12). + /// - %MMM: Abbreviated month name. + /// - %DD: Day of the month (01-31). + /// - %G: ISO week-based year. + /// - %g: ISO week-based year without century (00-99). + /// - %hh: Hour of the day in 24-hour format (00-23). + /// - %mm: Minute of the hour (00-59). + /// - %ss: Second (00-59). + /// - %sss: Millisecond (000-999). + /// - %V: ISO week number (01-53). + /// - %WWW: Abbreviated day of the week name in uppercase (SUN, MON, TUE, etc.). + /// - %www: Abbreviated day of the week name (Sun, Mon, Tue, etc.). + /// - %u: ISO weekday number (1-7, Monday is 1). + /// + /// For more information, see the strftime specifiers documentation: + /// \sa https://manpages.debian.org/bullseye/manpages-dev/strftime.3.en.html + /// + /// \param format_str Format string with custom parameters, e.g., "%H:%M:%S". + /// \param timestamp Timestamp. + /// \param utc_offset UTC offset in seconds (default is 0). + /// \return Returns a string in the format specified by the user. + template + const std::string to_string( + const std::string& format_str, + T timestamp, + tz_t utc_offset = 0) { + std::string result; + if (format_str.empty()) return result; + const T local_timestamp = static_cast(timestamp + static_cast(utc_offset)); + DateTimeStruct dt = to_date_time(local_timestamp); + + bool is_command = false; + size_t repeat_count = 0; + char last_char = format_str[0]; + if (last_char != '%') result += last_char; + for (size_t i = 0; i < format_str.size(); ++i) { + const char& current_char = format_str[i]; + if (!is_command) { + if (current_char == '%') { + ++repeat_count; + if (repeat_count == 2) { + result += current_char; + repeat_count = 0; + } + continue; + } + if (!repeat_count) { + result += current_char; + continue; + } + last_char = current_char; + is_command = true; + continue; + } + if (last_char == current_char) { + ++repeat_count; + continue; + } + process_format_impl(last_char, repeat_count, timestamp, utc_offset, dt, result); + repeat_count = 0; + is_command = false; + --i; + } + if (is_command) { + process_format_impl(last_char, repeat_count, timestamp, utc_offset, dt, result); + } + return result; + } + + /// \brief Alias for to_string function. + /// \copydoc to_string + template + inline const std::string to_str( + const std::string& format_str, + T timestamp, + tz_t utc_offset = 0) { + return to_string(format_str, timestamp, utc_offset); + } + + /// \brief Convert timestamp in milliseconds to string with custom format. + /// + /// This function is similar to the strftime function and supports the majority of its specifiers, + /// as well as additional ones: YY, YYYY, YYYYYY, WWW, www, hh, mm, ss, dd, sss. + /// + /// Accepts the following format specifiers as parameters: + /// - %YYYYYY: Year with reduction in the number of millennia. + /// - %YYYY: Year represented by 4 digits. + /// - %YY: Last two digits of the year. + /// - %MM: Month (01-12). + /// - %MMM: Abbreviated month name. + /// - %DD: Day of the month (01-31). + /// - %hh: Hour of the day in 24-hour format (00-23). + /// - %mm: Minute of the hour (00-59). + /// - %ss: Second (00-59). + /// - %sss: Millisecond (000-999). + /// - %WWW: Abbreviated day of the week name in uppercase (SUN, MON, TUE, etc.). + /// - %www: Abbreviated day of the week name (Sun, Mon, Tue, etc.). + /// + /// For more information, see the strftime specifiers documentation: + /// \sa https://manpages.debian.org/bullseye/manpages-dev/strftime.3.en.html + /// + /// \param format_str Format string with custom parameters, e.g., "%H:%M:%S". + /// \param timestamp Timestamp in milliseconds. + /// \param utc_offset UTC offset in seconds (default is 0). + /// \return Returns a string in the format specified by the user. + template + const std::string to_string_ms( + const std::string& format_str, + T timestamp, + tz_t utc_offset = 0) { + std::string result; + if (format_str.empty()) return result; + const T local_timestamp = static_cast(timestamp + sec_to_ms(utc_offset)); + DateTimeStruct dt = to_date_time_ms(local_timestamp); + + bool is_command = false; + size_t repeat_count = 0; + char last_char = format_str[0]; + if (last_char != '%') result += last_char; + for (size_t i = 0; i < format_str.size(); ++i) { + const char& current_char = format_str[i]; + if (!is_command) { + if (current_char == '%') { + ++repeat_count; + if (repeat_count == 2) { + result += current_char; + repeat_count = 0; + } + continue; + } + if (!repeat_count) { + result += current_char; + continue; + } + last_char = current_char; + is_command = true; + continue; + } + if (last_char == current_char) { + ++repeat_count; + continue; + } + process_format_impl(last_char, repeat_count, timestamp, utc_offset, dt, result); + repeat_count = 0; + is_command = false; + --i; + } + if (is_command) { + process_format_impl(last_char, repeat_count, timestamp, utc_offset, dt, result); + } + return result; + } + + /// \brief Alias for to_string function. + /// \copydoc to_string + template + inline const std::string to_str_ms( + const std::string& format_str, + T timestamp, + tz_t utc_offset = 0) { + return to_string_ms(format_str, timestamp, utc_offset); + } + + /// \brief Converts a timestamp to an ISO8601 string. + /// + /// This function converts a timestamp to a string in ISO8601 format. + /// + /// \tparam T The type of the timestamp (default is ts_t). + /// \param ts The timestamp to convert. + /// \return A string representing the timestamp in ISO8601 format. + template + inline const std::string to_iso8601(T ts) { + DateTimeStruct dt = to_date_time(ts); + char buffer[32] = {0}; + if TIME_SHIELD_IF_CONSTEXPR (std::is_floating_point::value) { + snprintf( + buffer, + sizeof(buffer), + "%" PRId64 "-%.2d-%.2dT%.2d:%.2d:%.2d.%.3d", + dt.year, + dt.mon, + dt.day, + dt.hour, + dt.min, + dt.sec, + dt.ms); + } else { + snprintf( + buffer, + sizeof(buffer), + "%" PRId64 "-%.2d-%.2dT%.2d:%.2d:%.2d", + dt.year, + dt.mon, + dt.day, + dt.hour, + dt.min, + dt.sec); + } + return std::string(buffer); + } + + /// \brief Converts a timestamp to an ISO8601 date string. + /// + /// This function converts the date part of a timestamp to a string in ISO8601 format. + /// + /// \tparam T The type of the timestamp (default is ts_t). + /// \param ts The timestamp to convert. + /// \return A string representing the date part of the timestamp in ISO8601 format. + template + inline const std::string to_iso8601_date(T ts) { + DateTimeStruct dt = to_date_time(ts); + char buffer[32] = {0}; + snprintf( + buffer, + sizeof(buffer), + "%" PRId64 "-%.2d-%.2d", + dt.year, + dt.mon, + dt.day); + return std::string(buffer); + } + + /// \brief Converts a timestamp to an ISO8601 time string. + /// + /// This function converts the time part of a timestamp to a string in ISO8601 format. + /// + /// \tparam T The type of the timestamp (default is ts_t). + /// \param ts The timestamp to convert. + /// \return A string representing the time part of the timestamp in ISO8601 format. + template + inline const std::string to_iso8601_time(T ts) { + DateTimeStruct dt = to_date_time(ts); + char buffer[32] = {0}; + if TIME_SHIELD_IF_CONSTEXPR (std::is_floating_point::value) { + snprintf(buffer, sizeof(buffer), "%.2d:%.2d:%.2d.%.3d", dt.hour, dt.min, dt.sec, dt.ms); + } else { + snprintf(buffer, sizeof(buffer), "%.2d:%.2d:%.2d", dt.hour, dt.min, dt.sec); + } + return std::string(buffer); + } + + /// \brief Converts a timestamp to an ISO8601 UTC time string. + /// + /// This function converts the time part of a timestamp to a string in ISO8601 format with 'Z' indicating UTC. + /// + /// \tparam T The type of the timestamp (default is ts_t). + /// \param ts The timestamp to convert. + /// \return A string representing the time part of the timestamp in ISO8601 format with 'Z' indicating UTC. + template + inline const std::string to_iso8601_time_utc(T ts) { + DateTimeStruct dt = to_date_time(ts); + char buffer[32] = {0}; + if TIME_SHIELD_IF_CONSTEXPR (std::is_floating_point::value) { + snprintf(buffer, sizeof(buffer), "%.2d:%.2d:%.2d.%.3dZ", dt.hour, dt.min, dt.sec, dt.ms); + } else { + snprintf(buffer, sizeof(buffer), "%.2d:%.2d:%.2dZ", dt.hour, dt.min, dt.sec); + } + return std::string(buffer); + } + + /// \brief Converts a timestamp to an ISO8601 string in UTC format. + /// + /// This function converts a timestamp to a string in ISO8601 format with UTC timezone. + /// + /// \tparam T The type of the timestamp (default is ts_t). + /// \param ts The timestamp to convert. + /// \return A string representing the timestamp in ISO8601 UTC format. + template + inline const std::string to_iso8601_utc(T ts) { + DateTimeStruct dt = to_date_time(ts); + char buffer[32] = {0}; + if TIME_SHIELD_IF_CONSTEXPR (std::is_floating_point::value) { + snprintf( + buffer, + sizeof(buffer), + "%" PRId64 "-%.2d-%.2dT%.2d:%.2d:%.2d.%.3dZ", + dt.year, + dt.mon, + dt.day, + dt.hour, + dt.min, + dt.sec, + dt.ms); + } else { + snprintf( + buffer, + sizeof(buffer), + "%" PRId64 "-%.2d-%.2dT%.2d:%.2d:%.2dZ", + dt.year, + dt.mon, + dt.day, + dt.hour, + dt.min, + dt.sec); + } + return std::string(buffer); + } + + /// \brief Converts a timestamp in milliseconds to an ISO8601 string in UTC format. + /// + /// This function converts a timestamp in milliseconds to a string in ISO8601 format with UTC timezone. + /// + /// \param ts_ms The timestamp in milliseconds to convert. + /// \return A string representing the timestamp in ISO8601 UTC format with milliseconds. + inline const std::string to_iso8601_utc_ms(ts_ms_t ts_ms) { + DateTimeStruct dt = to_date_time_ms(ts_ms); + char buffer[32] = {0}; + snprintf( + buffer, + sizeof(buffer), + "%" PRId64 "-%.2d-%.2dT%.2d:%.2d:%.2d.%.3dZ", + dt.year, + dt.mon, + dt.day, + dt.hour, + dt.min, + dt.sec, + dt.ms); + return std::string(buffer); + } + + /// \brief Converts a timestamp in milliseconds to an ISO8601 string. + /// + /// This function converts a timestamp in milliseconds to a string in ISO8601 format. + /// + /// \param ts_ms The timestamp in milliseconds to convert. + /// \return A string representing the timestamp in ISO8601 format with milliseconds. + inline const std::string to_iso8601_ms(ts_ms_t ts_ms) { + DateTimeStruct dt = to_date_time_ms(ts_ms); + char buffer[32] = {0}; + snprintf( + buffer, + sizeof(buffer), + "%" PRId64 "-%.2d-%.2dT%.2d:%.2d:%.2d.%.3d", + dt.year, + dt.mon, + dt.day, + dt.hour, + dt.min, + dt.sec, + dt.ms); + return std::string(buffer); + } + + /// \brief Converts a timestamp to an ISO8601 string with timezone offset. + /// + /// This function converts a timestamp to a string in ISO8601 format with timezone offset. + /// + /// \tparam T The type of the timestamp (default is ts_t). + /// \param ts The timestamp to convert. + /// \param utc_offset The timezone offset in seconds. + /// \return A string representing the timestamp in ISO8601 format with timezone offset. + template + inline const std::string to_iso8601(T ts, tz_t utc_offset) { + TimeZoneStruct tz = to_time_zone(utc_offset); + const T local_ts = static_cast(ts + static_cast(utc_offset)); + DateTimeStruct dt = to_date_time(local_ts); + char buffer[32] = {0}; + if TIME_SHIELD_IF_CONSTEXPR (std::is_floating_point::value) { + if (tz.is_positive) { + snprintf( + buffer, + sizeof(buffer), + "%" PRId64 "-%.2d-%.2dT%.2d:%.2d:%.2d.%.3d+%.2d:%.2d", + dt.year, + dt.mon, + dt.day, + dt.hour, + dt.min, + dt.sec, + dt.ms, + tz.hour, + tz.min); + } else { + snprintf( + buffer, + sizeof(buffer), + "%" PRId64 "-%.2d-%.2dT%.2d:%.2d:%.2d.%.3d-%.2d:%.2d", + dt.year, + dt.mon, + dt.day, + dt.hour, + dt.min, + dt.sec, + dt.ms, + tz.hour, + tz.min); + } + } else { + if (tz.is_positive) { + snprintf( + buffer, + sizeof(buffer), + "%" PRId64 "-%.2d-%.2dT%.2d:%.2d:%.2d+%.2d:%.2d", + dt.year, + dt.mon, + dt.day, + dt.hour, + dt.min, + dt.sec, + tz.hour, + tz.min); + } else { + snprintf( + buffer, + sizeof(buffer), + "%" PRId64 "-%.2d-%.2dT%.2d:%.2d:%.2d-%.2d:%.2d", + dt.year, + dt.mon, + dt.day, + dt.hour, + dt.min, + dt.sec, + tz.hour, + tz.min); + } + } + return std::string(buffer); + } + + /// \brief Converts a timestamp in milliseconds to an ISO8601 string with timezone offset. + /// + /// This function converts a timestamp in milliseconds to a string in ISO8601 format with timezone offset. + /// + /// \param ts_ms The timestamp in milliseconds to convert. + /// \param utc_offset The timezone offset in seconds. + /// \return A string representing the timestamp in ISO8601 format with timezone offset and milliseconds. + inline const std::string to_iso8601_ms(ts_ms_t ts_ms, tz_t utc_offset) { + TimeZoneStruct tz = to_time_zone(utc_offset); + const ts_ms_t local_ts_ms = ts_ms + sec_to_ms(utc_offset); + DateTimeStruct dt = to_date_time_ms(local_ts_ms); + char buffer[32] = {0}; + if (tz.is_positive) { + snprintf( + buffer, + sizeof(buffer), + "%" PRId64 "-%.2d-%.2dT%.2d:%.2d:%.2d.%.3d+%.2d:%.2d", + dt.year, + dt.mon, + dt.day, + dt.hour, + dt.min, + dt.sec, + dt.ms, + tz.hour, + tz.min); + } else { + snprintf( + buffer, + sizeof(buffer), + "%" PRId64 "-%.2d-%.2dT%.2d:%.2d:%.2d.%.3d-%.2d:%.2d", + dt.year, + dt.mon, + dt.day, + dt.hour, + dt.min, + dt.sec, + dt.ms, + tz.hour, + tz.min); + } + return std::string(buffer); + } + + /// \brief Converts a timestamp to a string in MQL5 date and time format. + /// + /// This function converts a timestamp to a string in MQL5 date and time format (yyyy.mm.dd hh:mm:ss). + /// + /// \param ts The timestamp to convert. + /// \return A string representing the timestamp in MQL5 date and time format. + inline const std::string to_mql5_date_time(ts_t ts) { + DateTimeStruct dt = to_date_time(ts); + char buffer[32] = {0}; + snprintf( + buffer, + sizeof(buffer), + "%" PRId64 ".%.2d.%.2d %.2d:%.2d:%.2d", + dt.year, + dt.mon, + dt.day, + dt.hour, + dt.min, + dt.sec); + return std::string(buffer); + } + + /// \brief Alias for to_mql5_date_time_str function. + /// \copydoc to_mql5_date_time + inline const std::string to_mql5_full(ts_t ts) { + return to_mql5_date_time(ts); + } + + /// \brief Converts a timestamp to a string in MQL5 date format. + /// + /// This function converts a timestamp to a string in MQL5 date format (yyyy.mm.dd). + /// + /// \param ts The timestamp to convert. + /// \return A string representing the date part of the timestamp in MQL5 format. + inline const std::string to_mql5_date(ts_t ts) { + DateTimeStruct dt = to_date_time(ts); + char buffer[32] = {0}; + snprintf( + buffer, + sizeof(buffer), + "%" PRId64 ".%.2d.%.2d", + dt.year, + dt.mon, + dt.day); + return std::string(buffer); + } + + /// \brief Converts a timestamp to a string in MQL5 time format. + /// + /// This function converts a timestamp to a string in MQL5 time format (hh:mm:ss). + /// + /// \param ts The timestamp to convert. + /// \return A string representing the time part of the timestamp in MQL5 format. + inline const std::string to_mql5_time(ts_t ts) { + DateTimeStruct dt = to_date_time(ts); + char buffer[32] = {0}; + snprintf(buffer, sizeof(buffer), "%.2d:%.2d:%.2d", dt.hour, dt.min, dt.sec); + return std::string(buffer); + } + + /// \brief Converts a timestamp in seconds to a Windows-compatible filename format. + /// \param ts The timestamp in seconds. + /// \return A string in the format "YYYY-MM-DD_HH-MM-SS". + inline const std::string to_windows_filename(ts_t ts) { + DateTimeStruct dt = to_date_time(ts); + char buffer[32] = {0}; + snprintf( + buffer, + sizeof(buffer), + "%" PRId64 "-%.2d-%.2d_%.2d-%.2d-%.2d", + dt.year, + dt.mon, + dt.day, + dt.hour, + dt.min, + dt.sec); + return std::string(buffer); + } + + /// \brief Converts a timestamp in milliseconds to a Windows-compatible filename format. + /// \param ts The timestamp in milliseconds. + /// \return A string in the format "YYYY-MM-DD_HH-MM-SS-SSS". + inline const std::string to_windows_filename_ms(ts_ms_t ts) { + DateTimeStruct dt = to_date_time_ms(ts); + char buffer[32] = {0}; + snprintf( + buffer, + sizeof(buffer), + "%" PRId64 "-%.2d-%.2d_%.2d-%.2d-%.2d-%.3d", + dt.year, + dt.mon, + dt.day, + dt.hour, + dt.min, + dt.sec, + dt.ms); + return std::string(buffer); + } + + /// \brief Converts a timestamp in seconds to a human-readable format. + /// \param ts The timestamp in seconds. + /// \return A string in the format "YYYY-MM-DD HH:MM:SS". + inline std::string to_human_readable(ts_t ts) { + DateTimeStruct dt = to_date_time(ts); + char buffer[32] = {0}; + snprintf( + buffer, + sizeof(buffer), + "%" PRId64 "-%.2d-%.2d %.2d:%.2d:%.2d", + dt.year, + dt.mon, + dt.day, + dt.hour, + dt.min, + dt.sec); + return std::string(buffer); + } + + /// \brief Converts a timestamp in milliseconds to a human-readable format. + /// \param ts The timestamp in milliseconds. + /// \return A string in the format "YYYY-MM-DD HH:MM:SS.SSS". + inline std::string to_human_readable_ms(ts_ms_t ts) { + DateTimeStruct dt = to_date_time_ms(ts); + char buffer[32] = {0}; + snprintf( + buffer, + sizeof(buffer), + "%" PRId64 "-%.2d-%.2d %.2d:%.2d:%.2d.%.3d", + dt.year, + dt.mon, + dt.day, + dt.hour, + dt.min, + dt.sec, + dt.ms); + return std::string(buffer); + } + +/// \} + +}; // namespace time_shield + +#endif // TIME_SHIELD_HEADER_TEXT_TIME_FORMATTING_HPP_INCLUDED diff --git a/include/time_shield/text/time_parser.hpp b/include/time_shield/text/time_parser.hpp new file mode 100644 index 00000000..c64ac2c0 --- /dev/null +++ b/include/time_shield/text/time_parser.hpp @@ -0,0 +1,1765 @@ +// SPDX-License-Identifier: MIT +#pragma once +#ifndef TIME_SHIELD_HEADER_TEXT_TIME_PARSER_HPP_INCLUDED +#define TIME_SHIELD_HEADER_TEXT_TIME_PARSER_HPP_INCLUDED + +/// \file time_parser.hpp +/// \brief Header file with functions for parsing dates and times in ISO8601 format and converting them to various timestamp formats. +/// +/// This file contains functions for parsing ISO8601 date and time strings, extracting month numbers from month names, +/// and converting parsed date and time information to different timestamp formats. +/// +/// Provides: +/// - Month name parsing (e.g. "Jan", "January") to month index (1..12). +/// - Timeframe parsing for trading and engineering strings (e.g. "M15", "hour", "2 weeks"). +/// - ISO8601 date/time parsing into DateTimeStruct + TimeZoneStruct. +/// - Convenience functions to convert ISO8601 strings to timestamps (sec/ms/float). +/// +/// \note If you need strict error handling, prefer the `str_to_*` functions that return bool. + +#include +#include +#include "time_format_parser.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +#if __cplusplus >= 201703L +# include +#endif + +namespace time_shield { + +/// \defgroup time_parsing Time Parsing +/// \brief A comprehensive set of functions for parsing and converting date and time strings. +/// +/// This module provides utilities for parsing date and time strings in ISO8601 format, +/// extracting date components, and converting them into various timestamp formats. +/// +/// ### Key Features: +/// - Parse ISO8601 date and time strings. +/// - Extract month numbers from month names. +/// - Convert parsed date and time to timestamp formats (seconds, milliseconds, floating-point). +/// +/// ### Usage Examples: +/// - Parse an ISO8601 string and get a timestamp: +/// \code{.cpp} +/// ts_t timestamp; +/// bool success = time_shield::str_to_ts("2024-11-25T14:30:00Z", timestamp); +/// \endcode +/// +/// - Extract a month number from a string: +/// \code{.cpp} +/// time_shield::Month month = time_shield::get_month_number("March"); +/// \endcode +/// +/// - Parse an ISO8601 string into a DateTimeStruct: +/// \code{.cpp} +/// time_shield::DateTimeStruct dt; +/// time_shield::TimeZoneStruct tz; +/// bool valid = time_shield::parse_iso8601("2024-11-25T14:30:00+01:00", dt, tz); +/// \endcode +/// +/// \{ + + namespace detail { + + /// \brief Trim ASCII whitespace from both ends. + inline std::string trim_copy_ascii(const std::string& s) { + size_t b = 0; + size_t e = s.size(); + while (b < e && std::isspace(static_cast(s[b])) != 0) ++b; + while (e > b && std::isspace(static_cast(s[e - 1])) != 0) --e; + return s.substr(b, e - b); + } + +# if __cplusplus >= 201703L + /// \brief Trim ASCII whitespace from both ends (string_view). + inline std::string_view trim_view_ascii(std::string_view v) { + size_t b = 0; + size_t e = v.size(); + while (b < e && std::isspace(static_cast(v[b])) != 0) ++b; + while (e > b && std::isspace(static_cast(v[e - 1])) != 0) --e; + return v.substr(b, e - b); + } +# endif + + /// \brief Normalize month token to lower-case ASCII using current locale facet. + /// \param month Input token. + /// \param output Output lower-case token (overwritten). + inline void normalise_month_token_lower(const std::string& month, std::string& output) { + output = trim_copy_ascii(month); + if (output.empty()) return; + + const auto& facet = std::use_facet>(std::locale()); + std::transform(output.begin(), output.end(), output.begin(), + [&facet](char ch) { return facet.tolower(ch); }); + } + +# if __cplusplus >= 201703L + /// \brief Normalize month token to lower-case ASCII using current locale facet (string_view). + /// \param month Input token view. + /// \param output Output lower-case token (overwritten). + inline void normalise_month_token_lower(std::string_view month, std::string& output) { + month = trim_view_ascii(month); + output.assign(month.begin(), month.end()); + if (output.empty()) return; + + const auto& facet = std::use_facet>(std::locale()); + std::transform(output.begin(), output.end(), output.begin(), + [&facet](char ch) { return facet.tolower(ch); }); + } +# endif + + /// \brief Try parse month name token into month index (1..12). + /// \param month Month token (e.g. "Jan", "January", case-insensitive). + /// \param value Output month index in range [1..12]. + /// \return True if token matches a supported month name, false otherwise. + inline bool try_parse_month_index(const std::string& month, int& value) { + if (month.empty()) return false; + + std::string month_copy; + normalise_month_token_lower(month, month_copy); + if (month_copy.empty()) return false; + + static const std::array short_names = { + "jan", "feb", "mar", "apr", "may", "jun", + "jul", "aug", "sep", "oct", "nov", "dec" + }; + static const std::array full_names = { + "january", "february", "march", "april", "may", "june", + "july", "august", "september", "october", "november", "december" + }; + + for (std::size_t i = 0; i < short_names.size(); ++i) { + if (month_copy == short_names[i] || month_copy == full_names[i]) { + value = static_cast(i) + 1; + return true; + } + } + + return false; + } + +# if __cplusplus >= 201703L + /// \brief Try parse month name token into month index (1..12), string_view overload. + /// \param month Month token view (e.g. "Jan", "January", case-insensitive). + /// \param value Output month index in range [1..12]. + /// \return True if token matches a supported month name, false otherwise. + inline bool try_parse_month_index(std::string_view month, int& value) { + if (month.empty()) return false; + + std::string month_copy; + normalise_month_token_lower(month, month_copy); + if (month_copy.empty()) return false; + + static const std::array short_names = { + "jan", "feb", "mar", "apr", "may", "jun", + "jul", "aug", "sep", "oct", "nov", "dec" + }; + static const std::array full_names = { + "january", "february", "march", "april", "may", "june", + "july", "august", "september", "october", "november", "december" + }; + + for (std::size_t i = 0; i < short_names.size(); ++i) { + if (month_copy == short_names[i] || month_copy == full_names[i]) { + value = static_cast(i) + 1; + return true; + } + } + + return false; + } +# endif + + /// \brief Parse month name token into month index (1..12). + /// \param month Month token. + /// \return Month index [1..12]. + /// \throw std::invalid_argument if token is invalid. + inline int parse_month_index(const std::string& month) { + int value = 0; + if (!try_parse_month_index(month, value)) { + throw std::invalid_argument("Invalid month name"); + } + return value; + } + +# if __cplusplus >= 201703L + /// \brief Parse month name token into month index (1..12), string_view overload. + /// \param month Month token view. + /// \return Month index [1..12]. + /// \throw std::invalid_argument if token is invalid. + inline int parse_month_index(std::string_view month) { + int value = 0; + if (!try_parse_month_index(month, value)) { + throw std::invalid_argument("Invalid month name"); + } + return value; + } +# endif + + struct ZoneNameEntry { + const char* name; + TimeZone zone; + }; + + /// \brief Return supported strict named-zone entries. + inline const std::array& time_zone_name_entries() noexcept { + static const std::array entries = {{ + {"GMT", GMT}, + {"UTC", UTC}, + {"EET", EET}, + {"CET", CET}, + {"WET", WET}, + {"EEST", EEST}, + {"CEST", CEST}, + {"WEST", WEST}, + {"ET", ET}, + {"CT", CT}, + {"IST", IST}, + {"MYT", MYT}, + {"WIB", WIB}, + {"WITA", WITA}, + {"WIT", WIT}, + {"KZT", KZT}, + {"TRT", TRT}, + {"BYT", BYT}, + {"SGT", SGT}, + {"ICT", ICT}, + {"PHT", PHT}, + {"GST", GST}, + {"HKT", HKT}, + {"JST", JST}, + {"KST", KST} + }}; + return entries; + } + + /// \brief Parse strict named-zone token without trimming. + inline bool try_parse_time_zone_name_token(const char* data, std::size_t length, TimeZone& zone) noexcept { + if (data == nullptr || length == 0) { + zone = UNKNOWN; + return false; + } + + const std::array& entries = time_zone_name_entries(); + for (std::size_t i = 0; i < entries.size(); ++i) { + const std::size_t name_length = std::strlen(entries[i].name); + if (length == name_length && std::memcmp(data, entries[i].name, name_length) == 0) { + zone = entries[i].zone; + return true; + } + } + + zone = UNKNOWN; + return false; + } + +//------------------------------------------------------------------------------ +// Small C-style helpers (no lambdas, no detail namespace) +//------------------------------------------------------------------------------ + + /// \brief Check whether character is ASCII whitespace. + TIME_SHIELD_CONSTEXPR inline bool is_ascii_space(char c) noexcept { + return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f' || c == '\v'; + } + + /// \brief Check whether character is ASCII digit. + TIME_SHIELD_CONSTEXPR inline bool is_ascii_digit(char c) noexcept { + return c >= '0' && c <= '9'; + } + + /// \brief Check whether character is ASCII letter. + TIME_SHIELD_CONSTEXPR inline bool is_ascii_alpha(char c) noexcept { + return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'); + } + + /// \brief Convert ASCII letter to lower-case. + TIME_SHIELD_CONSTEXPR inline char ascii_to_lower(char c) noexcept { + return (c >= 'A' && c <= 'Z') ? static_cast(c - 'A' + 'a') : c; + } + + /// \brief Compare ASCII token to literal case-insensitively. + inline bool ascii_iequals(const char* data, std::size_t length, const char* literal) noexcept { + if (data == nullptr || literal == nullptr) { + return false; + } + + for (std::size_t i = 0; i < length; ++i) { + if (literal[i] == '\0' || ascii_to_lower(data[i]) != ascii_to_lower(literal[i])) { + return false; + } + } + + return literal[length] == '\0'; + } + + /// \brief Parse positive int64 value from ASCII digits. + inline bool try_parse_positive_int64(const char* data, std::size_t length, int64_t& value) noexcept { + value = 0; + if (data == nullptr || length == 0) { + return false; + } + + const int64_t max_value = (std::numeric_limits::max)(); + for (std::size_t i = 0; i < length; ++i) { + if (!is_ascii_digit(data[i])) { + return false; + } + + const int digit = data[i] - '0'; + if (value > (max_value - digit) / 10) { + value = 0; + return false; + } + value = value * 10 + digit; + } + + return value > 0; + } + + /// \brief Multiply positive int64 values with overflow check. + TIME_SHIELD_CONSTEXPR inline bool try_multiply_positive_int64(int64_t lhs, int64_t rhs, int64_t& value) noexcept { + if (lhs <= 0 || rhs <= 0) { + return false; + } + + if (lhs > (std::numeric_limits::max)() / rhs) { + return false; + } + + value = lhs * rhs; + return true; + } + + /// \brief Resolve compact trading unit token to seconds. + inline bool try_get_timeframe_unit_seconds_compact(const char* data, std::size_t length, int64_t& unit_seconds) noexcept { + unit_seconds = 0; + if (data == nullptr || length == 0) { + return false; + } + + if (length == 1) { + switch (ascii_to_lower(data[0])) { + case 's': unit_seconds = 1; return true; + case 'm': unit_seconds = SEC_PER_MIN; return true; + case 'h': unit_seconds = SEC_PER_HOUR; return true; + case 'd': unit_seconds = SEC_PER_DAY; return true; + case 'w': unit_seconds = SEC_PER_DAY * 7; return true; + case 'q': unit_seconds = SEC_PER_DAY * 90; return true; + case 'y': unit_seconds = SEC_PER_YEAR; return true; + default: return false; + } + } + + if (length == 2 + && ascii_to_lower(data[0]) == 'm' + && ascii_to_lower(data[1]) == 'n') { + unit_seconds = SEC_PER_DAY * 30; + return true; + } + + return false; + } + + /// \brief Resolve word timeframe unit token to seconds. + inline bool try_get_timeframe_unit_seconds_word(const char* data, std::size_t length, int64_t& unit_seconds) noexcept { + unit_seconds = 0; + if (data == nullptr || length == 0) { + return false; + } + + struct TimeframeUnitEntry { + const char* name; + int64_t seconds; + }; + + static const TimeframeUnitEntry entries[] = { + {"sec", 1}, + {"second", 1}, + {"seconds", 1}, + {"min", SEC_PER_MIN}, + {"minute", SEC_PER_MIN}, + {"minutes", SEC_PER_MIN}, + {"hr", SEC_PER_HOUR}, + {"hour", SEC_PER_HOUR}, + {"hours", SEC_PER_HOUR}, + {"day", SEC_PER_DAY}, + {"days", SEC_PER_DAY}, + {"week", SEC_PER_DAY * 7}, + {"weeks", SEC_PER_DAY * 7}, + {"month", SEC_PER_DAY * 30}, + {"months", SEC_PER_DAY * 30}, + {"quarter", SEC_PER_DAY * 90}, + {"quarters", SEC_PER_DAY * 90}, + {"year", SEC_PER_YEAR}, + {"years", SEC_PER_YEAR} + }; + + for (std::size_t i = 0; i < sizeof(entries) / sizeof(entries[0]); ++i) { + if (ascii_iequals(data, length, entries[i].name)) { + unit_seconds = entries[i].seconds; + return true; + } + } + + return false; + } + + /// \brief Parse timeframe string into fixed seconds. + inline bool try_parse_timeframe_seconds(const char* data, std::size_t length, ts_t& seconds) noexcept { + seconds = 0; + if (data == nullptr || length == 0) { + return false; + } + + const char* begin = data; + const char* end = data + length; + while (begin < end && is_ascii_space(*begin)) { + ++begin; + } + while (end > begin && is_ascii_space(*(end - 1))) { + --end; + } + + if (begin == end) { + return false; + } + + int64_t multiplier = 1; + int64_t unit_seconds = 0; + int64_t result = 0; + + if (is_ascii_digit(*begin)) { + const char* cursor = begin; + while (cursor < end && is_ascii_digit(*cursor)) { + ++cursor; + } + + if (!try_parse_positive_int64(begin, static_cast(cursor - begin), multiplier)) { + return false; + } + + while (cursor < end && is_ascii_space(*cursor)) { + ++cursor; + } + if (cursor == end) { + return false; + } + + for (const char* p = cursor; p < end; ++p) { + if (!is_ascii_alpha(*p)) { + return false; + } + } + + if (!try_get_timeframe_unit_seconds_word(cursor, static_cast(end - cursor), unit_seconds)) { + return false; + } + + if (!try_multiply_positive_int64(multiplier, unit_seconds, result)) { + return false; + } + + seconds = static_cast(result); + return true; + } + + if (!is_ascii_alpha(*begin)) { + return false; + } + + const char* cursor = begin; + while (cursor < end && is_ascii_alpha(*cursor)) { + ++cursor; + } + + if (cursor == end) { + if (!try_get_timeframe_unit_seconds_word(begin, static_cast(end - begin), unit_seconds)) { + return false; + } + + seconds = static_cast(unit_seconds); + return true; + } + + if (is_ascii_space(*cursor)) { + return false; + } + + for (const char* p = cursor; p < end; ++p) { + if (!is_ascii_digit(*p)) { + return false; + } + } + + if (!try_get_timeframe_unit_seconds_compact(begin, static_cast(cursor - begin), unit_seconds)) { + return false; + } + if (!try_parse_positive_int64(cursor, static_cast(end - cursor), multiplier)) { + return false; + } + if (!try_multiply_positive_int64(multiplier, unit_seconds, result)) { + return false; + } + + seconds = static_cast(result); + return true; + } + + /// \brief Skip ASCII whitespace. + TIME_SHIELD_CONSTEXPR inline void skip_spaces(const char*& p, const char* end) noexcept { + while (p < end && is_ascii_space(*p)) { + ++p; + } + } + + /// \brief Parse exactly 2 digits into int. + /// \return true on success. + TIME_SHIELD_CONSTEXPR inline bool parse_2digits(const char*& p, const char* end, int& out) noexcept { + if (end - p < 2) { + return false; + } + const char a = p[0]; + const char b = p[1]; + if (!is_ascii_digit(a) || !is_ascii_digit(b)) { + return false; + } + out = (a - '0') * 10 + (b - '0'); + p += 2; + return true; + } + + /// \brief Parse exactly 4 digits into year_t (via int). + /// \return true on success. + TIME_SHIELD_CONSTEXPR inline bool parse_4digits_year(const char*& p, const char* end, year_t& out) noexcept { + if (end - p < 4) { + return false; + } + const char a = p[0], b = p[1], c = p[2], d = p[3]; + if (!is_ascii_digit(a) || !is_ascii_digit(b) || !is_ascii_digit(c) || !is_ascii_digit(d)) { + return false; + } + const int v = (a - '0') * 1000 + (b - '0') * 100 + (c - '0') * 10 + (d - '0'); + out = static_cast(v); + p += 4; + return true; + } + + /// \brief Parse fractional seconds (1..9 digits) and convert to milliseconds. + /// \details Uses first 3 digits, scales if fewer. + /// \return true on success. + TIME_SHIELD_CONSTEXPR inline bool parse_fraction_to_ms(const char*& p, const char* end, int& ms_out) noexcept { + if (p >= end || !is_ascii_digit(*p)) { + return false; + } + + int ms = 0; + int digits = 0; + + while (p < end && is_ascii_digit(*p)) { + if (digits >= 3) { + return false; + } + ms = ms * 10 + (*p - '0'); + ++digits; + ++p; + } + + if (digits == 1) { + ms *= 100; + } else if (digits == 2) { + ms *= 10; + } + + ms_out = ms; + return true; + } + + } // namespace detail + +//------------------------------------------------------------------------------ +// Month helpers (public) +//------------------------------------------------------------------------------ + +// Canonical API (recommended): +// - parse_month(...) / try_parse_month(...): return month index as int [1..12] +// - parse_month_enum(...) / try_parse_month_enum(...): return month as enum Month (or any integral/enum T) + + /// \brief Try parse month name token into month index [1..12]. + /// \param month Month token (e.g. "Jan", "January"), case-insensitive. + /// \param value Output month index [1..12]. + /// \return True on success, false otherwise. + inline bool try_parse_month(const std::string& month, int& value) { + return detail::try_parse_month_index(month, value); + } + + /// \brief Parse month name token into month index [1..12]. + /// \param month Month token. + /// \return Month index [1..12]. + /// \throw std::invalid_argument if token is invalid. + inline int parse_month(const std::string& month) { + return detail::parse_month_index(month); + } + +#if __cplusplus >= 201703L + /// \brief Try parse month name token into month index [1..12], string_view overload. + /// \param month Month token view (e.g. "Jan", "January"), case-insensitive. + /// \param value Output month index [1..12]. + /// \return True on success, false otherwise. + inline bool try_parse_month(std::string_view month, int& value) { + return detail::try_parse_month_index(month, value); + } + + /// \brief Parse month name token into month index [1..12], string_view overload. + /// \param month Month token view. + /// \return Month index [1..12]. + /// \throw std::invalid_argument if token is invalid. + inline int parse_month(std::string_view month) { + return detail::parse_month_index(month); + } +#endif + +// Canonical: parse month -> enum Month (or any T) + + /// \brief Parse month name token into Month enum (throwing). + /// \tparam T Return type, default is Month enum. + /// \param month Month token. + /// \return Month number (1..12) converted to T. + /// \throw std::invalid_argument if token is invalid. + template + inline T parse_month_enum(const std::string& month) { + return static_cast(detail::parse_month_index(month)); + } + + /// \brief Try parse month name token into Month enum (or any T). + /// \tparam T Output type, default is Month enum. + /// \param month Month token. + /// \param value Output month number (1..12) converted to T. + /// \return True if month token is valid, false otherwise. + template + inline bool try_parse_month_enum(const std::string& month, T& value) { + int idx = 0; + if (!detail::try_parse_month_index(month, idx)) return false; + value = static_cast(idx); + return true; + } + +#if __cplusplus >= 201703L + /// \brief Parse month name token into Month enum (throwing), string_view overload. + /// \tparam T Return type, default is Month enum. + /// \param month Month token view. + /// \return Month number (1..12) converted to T. + /// \throw std::invalid_argument if token is invalid. + template + inline T parse_month_enum(std::string_view month) { + return static_cast(detail::parse_month_index(month)); + } + + /// \brief Try parse month name token into Month enum (or any T), string_view overload. + /// \tparam T Output type, default is Month enum. + /// \param month Month token view. + /// \param value Output month number (1..12) converted to T. + /// \return True if month token is valid, false otherwise. + template + inline bool try_parse_month_enum(std::string_view month, T& value) { + int idx = 0; + if (!detail::try_parse_month_index(month, idx)) return false; + value = static_cast(idx); + return true; + } +#endif + +// Index aliases (int) + + /// \brief Try parse month name token into month index [1..12]. + /// \param month Month token (e.g. "Jan", "January"), case-insensitive. + /// \param value Output month index [1..12]. + /// \return True on success, false otherwise. + inline bool try_get_month_index(const std::string& month, int& value) { + return try_parse_month(month, value); + } + + /// \brief Parse month name token into month index [1..12]. + /// \param month Month token. + /// \return Month index [1..12]. + /// \throw std::invalid_argument if token is invalid. + inline int get_month_index(const std::string& month) { + return parse_month(month); + } + + /// \brief Parse month name token into Month enum. + /// \param month Month token. + /// \return Month enum value (1..12). + /// \throw std::invalid_argument if token is invalid. + inline Month get_month_index_enum(const std::string& month) { + return static_cast(detail::parse_month_index(month)); + } + +#if __cplusplus >= 201703L + /// \brief Try parse month name token into month index [1..12], string_view overload. + inline bool try_get_month_index(std::string_view month, int& value) { + return try_parse_month(month, value); + } + + /// \brief Parse month name token into month index [1..12], string_view overload. + inline int get_month_index(std::string_view month) { + return parse_month(month); + } + + /// \brief Parse month name token into Month enum, string_view overload. + inline Month get_month_index_enum(std::string_view month) { + return static_cast(detail::parse_month_index(month)); + } +#endif + +// Month number aliases (T) + + /// \brief Get the month number by name (throwing). + /// \tparam T Return type, default is Month enum. + /// \param month Month token. + /// \return Month number (1..12) converted to T. + /// \throw std::invalid_argument if token is invalid. + template + inline T get_month_number(const std::string& month) { + return parse_month_enum(month); + } + + /// \brief Alias for get_month_number (throwing). + template + inline T month_of_year(const std::string& month) { + return get_month_number(month); + } + + /// \brief Try get the month number by name, with output parameter. + /// \tparam T Output type, default is Month enum. + /// \param month Month token. + /// \param value Output month number (1..12) converted to T. + /// \return True if month token is valid, false otherwise. + template + inline bool try_get_month_number(const std::string& month, T& value) { + return try_parse_month_enum(month, value); + } + + /// \brief Alias for try_get_month_number (output parameter). + template + inline bool get_month_number(const std::string& month, T& value) { + return try_get_month_number(month, value); + } + + /// \brief Alias for try_get_month_number (output parameter). + template + inline bool month_of_year(const std::string& month, T& value) { + return try_get_month_number(month, value); + } + +#if __cplusplus >= 201703L + /// \brief Get the month number by name (throwing), string_view overload. + template + inline T get_month_number(std::string_view month) { + return parse_month_enum(month); + } + + /// \brief Alias for get_month_number (throwing), string_view overload. + template + inline T month_of_year(std::string_view month) { + return get_month_number(month); + } + + /// \brief Try get the month number by name, string_view overload. + template + inline bool try_get_month_number(std::string_view month, T& value) { + return try_parse_month_enum(month, value); + } + + /// \brief Alias for try_get_month_number, string_view overload. + template + inline bool get_month_number(std::string_view month, T& value) { + return try_get_month_number(month, value); + } + + /// \brief Alias for try_get_month_number, string_view overload. + template + inline bool month_of_year(std::string_view month, T& value) { + return try_get_month_number(month, value); + } +#endif + +// const char* overloads to avoid ambiguity with string vs string_view for literals + + /// \brief Get the month number by name (throwing), const char* overload. + /// \tparam T Return type, default is Month enum. + /// \param month Month token C-string. + /// \return Month number (1..12) converted to T. + /// \throw std::invalid_argument if token is invalid. + template + inline T get_month_number(const char* month) { +#if __cplusplus >= 201703L + return get_month_number(std::string_view(month)); +#else + return get_month_number(std::string(month)); +#endif + } + + /// \brief Try get the month number by name, const char* overload. + /// \tparam T Output type, default is Month enum. + /// \param month Month token C-string. + /// \param value Output month number (1..12) converted to T. + /// \return True if month token is valid, false otherwise. + template + inline bool try_get_month_number(const char* month, T& value) { +#if __cplusplus >= 201703L + return try_get_month_number(std::string_view(month), value); +#else + return try_get_month_number(std::string(month), value); +#endif + } + + /// \brief Alias for get_month_number (throwing), const char* overload. + template + inline T month_of_year(const char* month) { + return get_month_number(month); + } + + /// \brief Alias for try_get_month_number (output parameter), const char* overload. + template + inline bool get_month_number(const char* month, T& value) { + return try_get_month_number(month, value); + } + + /// \brief Alias for try_get_month_number (output parameter), const char* overload. + template + inline bool month_of_year(const char* month, T& value) { + return try_get_month_number(month, value); + } + +//------------------------------------------------------------------------------ +// Time zone parsing (C-style, high performance) +//------------------------------------------------------------------------------ + + /// \brief Parse timezone character buffer into TimeZoneStruct. + /// \details Supported formats: + /// - "" -> UTC (+00:00) + /// - "Z" -> UTC (+00:00) + /// - "+HH:MM" or "-HH:MM" + /// + /// Parsing is syntax-oriented and accepts offsets up to `23:59`. Semantic + /// support checks for reusable library features use `is_valid_tz_offset(...)` + /// and the supported UTC-offset range `[-12:00, +14:00]`. + /// + /// \param data Pointer to timezone buffer (may be not null-terminated). + /// \param length Number of characters in buffer. + /// \param tz Output time zone struct. + /// \return True if parsing succeeds and tz is valid, false otherwise. + inline bool parse_time_zone(const char* data, std::size_t length, TimeZoneStruct& tz) noexcept { + if (!data) { + return false; + } + + if (length == 0) { + tz.hour = 0; + tz.min = 0; + tz.is_positive = true; + return true; + } + + if (length == 1 && (data[0] == 'Z' || data[0] == 'z')) { + tz.hour = 0; + tz.min = 0; + tz.is_positive = true; + return true; + } + + if (length != 6) { + return false; + } + + const char sign = data[0]; + if (sign != '+' && sign != '-') { + return false; + } + if (data[3] != ':') { + return false; + } + if (!detail::is_ascii_digit(data[1]) || !detail::is_ascii_digit(data[2]) || + !detail::is_ascii_digit(data[4]) || !detail::is_ascii_digit(data[5])) { + return false; + } + + tz.is_positive = (sign == '+'); + tz.hour = (data[1] - '0') * 10 + (data[2] - '0'); + tz.min = (data[4] - '0') * 10 + (data[5] - '0'); + + return is_valid_time_zone(tz); + } + + /// \brief Parse timezone string into TimeZoneStruct. + /// \details Wrapper over parse_time_zone(const char*, std::size_t, TimeZoneStruct&). + inline bool parse_time_zone(const std::string& tz_str, TimeZoneStruct& tz) noexcept { + return parse_time_zone(tz_str.c_str(), tz_str.size(), tz); + } + + /// \brief Alias for parse_time_zone. + inline bool parse_tz(const std::string& tz_str, TimeZoneStruct& tz) noexcept { + return parse_time_zone(tz_str, tz); + } + + /// \brief Alias for parse_time_zone (buffer overload). + inline bool parse_tz(const char* data, std::size_t length, TimeZoneStruct& tz) noexcept { + return parse_time_zone(data, length, tz); + } + + /// \brief Parse named time zone character buffer into TimeZone enum. + /// \details Supported tokens are exact uppercase repo-native abbreviations with ASCII trimming. + /// \param data Pointer to time zone name buffer. + /// \param length Number of characters in buffer. + /// \param zone Output named time zone. + /// \return True when parsing succeeds. + inline bool parse_time_zone_name(const char* data, std::size_t length, TimeZone& zone) noexcept { + if (!data) { + zone = UNKNOWN; + return false; + } + + std::size_t begin = 0; + std::size_t end = length; + while (begin < end && std::isspace(static_cast(data[begin])) != 0) { + ++begin; + } + while (end > begin && std::isspace(static_cast(data[end - 1])) != 0) { + --end; + } + return detail::try_parse_time_zone_name_token(data + begin, end - begin, zone); + } + + /// \brief Parse named time zone string into TimeZone enum. + /// \details Wrapper over parse_time_zone_name(const char*, std::size_t, TimeZone&). + inline bool parse_time_zone_name(const std::string& value, TimeZone& zone) noexcept { + return parse_time_zone_name(value.c_str(), value.size(), zone); + } + +#if __cplusplus >= 201703L + /// \brief Parse named time zone string_view into TimeZone enum. + /// \details Wrapper over parse_time_zone_name(const char*, std::size_t, TimeZone&). + inline bool parse_time_zone_name(std::string_view value, TimeZone& zone) noexcept { + return parse_time_zone_name(value.data(), value.size(), zone); + } +#endif + + /// \brief Parse named time zone C-string into TimeZone enum. + /// \details Wrapper over parse_time_zone_name(const char*, std::size_t, TimeZone&). + inline bool parse_time_zone_name(const char* value, TimeZone& zone) noexcept { + if (value == nullptr) { + zone = UNKNOWN; + return false; + } + return parse_time_zone_name(value, std::strlen(value), zone); + } + + /// \brief Alias for parse_time_zone_name. + inline bool parse_tz_name(const char* data, std::size_t length, TimeZone& zone) noexcept { + return parse_time_zone_name(data, length, zone); + } + + /// \brief Alias for parse_time_zone_name. + inline bool parse_tz_name(const std::string& value, TimeZone& zone) noexcept { + return parse_time_zone_name(value, zone); + } + +#if __cplusplus >= 201703L + /// \brief Alias for parse_time_zone_name. + inline bool parse_tz_name(std::string_view value, TimeZone& zone) noexcept { + return parse_time_zone_name(value, zone); + } +#endif + + /// \brief Alias for parse_time_zone_name. + inline bool parse_tz_name(const char* value, TimeZone& zone) noexcept { + return parse_time_zone_name(value, zone); + } + +//------------------------------------------------------------------------------ +// ISO8601 parsing (C-style, no regex, no allocations) +//------------------------------------------------------------------------------ + + /// \brief Parse ISO8601 character buffer into DateTimeStruct and TimeZoneStruct. + /// \details Supported inputs: + /// - "YYYY-MM-DD" + /// - "YYYY-MM-DDThh:mm" + /// - "YYYY-MM-DDThh:mm:ss" + /// - "YYYY-MM-DDThh:mm:ss.fff" (1..9 digits fraction; milliseconds from first 3 digits, scaled if fewer) + /// - Any of the above time forms with "Z" or "+HH:MM"/"-HH:MM" + /// - Separator between date and time: 'T' or ASCII whitespace. + /// + /// Date separators supported: '-', '/', '.' (as in original regex). + /// ISO week-date forms are also accepted through parse_iso_week_date(), + /// including canonical and compatible mixed separator variants with optional + /// weekday and uppercase or lowercase `W`. + /// + /// \param input Pointer to buffer (may be not null-terminated). + /// \param length Buffer length. + /// \param dt Output DateTimeStruct (filled). On success, dt is always initialized. + /// \param tz Output TimeZoneStruct (filled). If timezone is not present, UTC is used. + /// \return True if parsing succeeds and dt is valid, false otherwise. + inline bool parse_iso8601(const char* input, std::size_t length, + DateTimeStruct& dt, TimeZoneStruct& tz) noexcept { + if (!input) { + return false; + } + + const char* p = input; + const char* end = input + length; + + detail::skip_spaces(p, end); + + dt = create_date_time_struct(0); + tz = create_time_zone_struct(0, 0); + tz.is_positive = true; + + const char* const date_start = p; + const char* date_end = p; + while (date_end < end && *date_end != 'T' && *date_end != 't' && !detail::is_ascii_space(*date_end)) { + ++date_end; + } + + bool parsed_iso_week_date = false; + if (date_end > date_start) { + IsoWeekDateStruct iso_date{}; + if (parse_iso_week_date(date_start, static_cast(date_end - date_start), iso_date)) { + const DateStruct calendar_date = iso_week_date_to_date(iso_date); + dt.year = calendar_date.year; + dt.mon = calendar_date.mon; + dt.day = calendar_date.day; + p = date_end; + parsed_iso_week_date = true; + } + } + + if (!parsed_iso_week_date) { + // ---- Date: YYYYMMDD + if (!detail::parse_4digits_year(p, end, dt.year)) { + return false; + } + if (p >= end) { + return false; + } + const char sep1 = *p; + if (sep1 != '-' && sep1 != '/' && sep1 != '.') { + return false; + } + ++p; + + if (!detail::parse_2digits(p, end, dt.mon)) { + return false; + } + if (p >= end) { + return false; + } + const char sep2 = *p; + if (sep2 != '-' && sep2 != '/' && sep2 != '.') { + return false; + } + ++p; + + if (!detail::parse_2digits(p, end, dt.day)) { + return false; + } + } + + if (!is_valid_date(dt.year, dt.mon, dt.day)) { + return false; + } + + // Date-only? + { + const char* q = p; + detail::skip_spaces(q, end); + if (q == end) { + // dt already has time=0 ms=0 + return is_valid_date_time(dt); + } + } + + // ---- Date/time separator: 'T' or whitespace + if (p >= end) { + return false; + } + + if (*p == 'T' || *p == 't') { + ++p; + } else + if (detail::is_ascii_space(*p)) { + // allow one or more spaces + detail::skip_spaces(p, end); + } else { + return false; + } + + // ---- Time: hh:mm[:ss][.frac] + if (!detail::parse_2digits(p, end, dt.hour)) { + return false; + } + if (p >= end || *p != ':') { + return false; + } + ++p; + + if (!detail::parse_2digits(p, end, dt.min)) { + return false; + } + + dt.sec = 0; + dt.ms = 0; + bool has_seconds = false; + + // Optional :ss + if (p < end && *p == ':') { + ++p; + if (!detail::parse_2digits(p, end, dt.sec)) { + return false; + } + has_seconds = true; + } + + // Optional .fraction (allowed only if we had seconds in original regex, + // but we accept it when seconds are present; for hh:mm (no seconds) we keep it strict). + if (p < end && *p == '.') { + // require seconds field to exist (avoid accepting YYYY-MM-DDThh:mm.xxx) + if (!has_seconds) { + // Ambiguous: could be "hh:mm.fff" which is not in your original formats. + // Keep strict to preserve behavior. + return false; + } + + ++p; + int ms = 0; + if (!detail::parse_fraction_to_ms(p, end, ms)) { + return false; + } + dt.ms = ms; + } + + // ---- Optional timezone: [spaces] (Z | +/-HH:MM) + detail::skip_spaces(p, end); + + if (p < end) { + if (*p == 'Z' || *p == 'z') { + tz.hour = 0; + tz.min = 0; + tz.is_positive = true; + ++p; + } else if (*p == '+' || *p == '-') { + // need 6 chars + if (static_cast(end - p) < 6) { + return false; + } + if (!parse_time_zone(p, 6, tz)) { + return false; + } + p += 6; + } + } + + detail::skip_spaces(p, end); + if (p != end) { + return false; + } + + return is_valid_date_time(dt); + } + + /// \brief Parse ISO8601 string into DateTimeStruct and TimeZoneStruct. + /// \details Wrapper over parse_iso8601(const char*, std::size_t, DateTimeStruct&, TimeZoneStruct&). + inline bool parse_iso8601(const std::string& input, DateTimeStruct& dt, TimeZoneStruct& tz) noexcept { + return parse_iso8601(input.c_str(), input.size(), dt, tz); + } + + /// \brief Parse ISO8601 C-string into DateTimeStruct and TimeZoneStruct. + /// \details Wrapper over parse_iso8601(const char*, std::size_t, DateTimeStruct&, TimeZoneStruct&). + inline bool parse_iso8601(const char* input, DateTimeStruct& dt, TimeZoneStruct& tz) noexcept { + if (input == nullptr) { + return false; + } + return parse_iso8601(input, std::strlen(input), dt, tz); + } + +# if __cplusplus >= 201703L + /// \brief Parse ISO8601 view into DateTimeStruct and TimeZoneStruct. + /// \details Wrapper over parse_iso8601(const char*, std::size_t, DateTimeStruct&, TimeZoneStruct&). + inline bool parse_iso8601(std::string_view input, DateTimeStruct& dt, TimeZoneStruct& tz) noexcept { + return parse_iso8601(input.data(), input.size(), dt, tz); + } +# endif + +//------------------------------------------------------------------------------ +// ISO8601 -> timestamps +//------------------------------------------------------------------------------ + + /// \brief Convert an ISO8601 string to a timestamp (ts_t). + /// \param str ISO8601 string. + /// \param ts Output timestamp (seconds). + /// \return True if parsing and conversion succeed, false otherwise. + inline bool str_to_ts(const std::string& str, ts_t& ts) { + DateTimeStruct dt; + TimeZoneStruct tz; + if (!parse_iso8601(str, dt, tz)) return false; + try { + ts = dt_to_timestamp(dt) - to_offset(tz); + return true; + } catch (...) {} + return false; + } + + /// \brief Parse ISO8601 character buffer and convert to timestamp (seconds). + /// \param data Pointer to character buffer. + /// \param length Buffer length in bytes. + /// \param ts Output timestamp in seconds. + /// \return true if parsing succeeds, false otherwise. + inline bool str_to_ts(const char* data, std::size_t length, ts_t& ts) { + if (!data || length == 0) { + ts = 0; + return false; + } + DateTimeStruct dt; + TimeZoneStruct tz; + if (!parse_iso8601(data, length, dt, tz)) return false; + try { + ts = dt_to_timestamp(dt) - to_offset(tz); + return true; + } catch (...) {} + return false; + } + + /// \brief Convert an ISO8601 string to a millisecond timestamp (ts_ms_t). + /// \param str ISO8601 string. + /// \param ts Output timestamp (milliseconds). + /// \return True if parsing and conversion succeed, false otherwise. + inline bool str_to_ts_ms(const std::string& str, ts_ms_t& ts) { + DateTimeStruct dt; + TimeZoneStruct tz; + if (!parse_iso8601(str, dt, tz)) return false; + try { + ts = static_cast(dt_to_timestamp_ms(dt)) - sec_to_ms(to_offset(tz)); + return true; + } catch (...) {} + return false; + } + + /// \brief Convert ISO8601 character buffer to millisecond timestamp (ts_ms_t). + /// \param data Pointer to character buffer. + /// \param length Number of characters in buffer. + /// \param ts Output timestamp in milliseconds. + /// \return True if parsing and conversion succeed, false otherwise. + inline bool str_to_ts_ms(const char* data, std::size_t length, ts_ms_t& ts) { + if (!data || length == 0) { + ts = 0; + return false; + } + DateTimeStruct dt; + TimeZoneStruct tz; + if (!parse_iso8601(data, length, dt, tz)) return false; + try { + ts = static_cast(dt_to_timestamp_ms(dt)) - sec_to_ms(to_offset(tz)); + return true; + } catch (...) {} + return false; + } + + /// \brief Convert an ISO8601 string to a floating-point timestamp (fts_t). + /// \param str ISO8601 string. + /// \param ts Output timestamp (floating-point seconds). + /// \return True if parsing and conversion succeed, false otherwise. + inline bool str_to_fts(const std::string& str, fts_t& ts) { + DateTimeStruct dt; + TimeZoneStruct tz; + if (!parse_iso8601(str, dt, tz)) return false; + try { + ts = dt_to_ftimestamp(dt) - static_cast(to_offset(tz)); + return true; + } catch (...) {} + return false; + } + + /// \brief Convert ISO8601 character buffer to floating-point timestamp (fts_t). + /// \param data Pointer to character buffer. + /// \param length Number of characters in buffer. + /// \param ts Output timestamp in floating-point seconds. + /// \return True if parsing and conversion succeed, false otherwise. + inline bool str_to_fts(const char* data, std::size_t length, fts_t& ts) { + if (!data || length == 0) { + ts = 0; + return false; + } + DateTimeStruct dt; + TimeZoneStruct tz; + if (!parse_iso8601(data, length, dt, tz)) return false; + try { + ts = dt_to_ftimestamp(dt) - static_cast(to_offset(tz)); + return true; + } catch (...) {} + return false; + } + +//------------------------------------------------------------------------------ +// Convenience string -> predicates (workdays) +//------------------------------------------------------------------------------ + + /// \brief Parse ISO8601 string and check if it falls on a workday (seconds precision). + inline bool is_workday(const std::string& str) { + ts_t ts = 0; + if (!str_to_ts(str, ts)) return false; + return is_workday(ts); + } + + /// \brief Parse ISO8601 string and check if it falls on a workday (milliseconds precision). + inline bool is_workday_ms(const std::string& str) { + ts_ms_t ts = 0; + if (!str_to_ts_ms(str, ts)) return false; + return is_workday_ms(ts); + } + + /// \brief Alias for is_workday(const std::string&). + /// \copydoc is_workday(const std::string&) + inline bool workday(const std::string& str) { + return is_workday(str); + } + + /// \brief Alias for is_workday_ms(const std::string&). + /// \copydoc is_workday_ms(const std::string&) + inline bool workday_ms(const std::string& str) { + return is_workday_ms(str); + } + + /// \brief Parse ISO8601 string and check if it is the first workday of its month (seconds). + /// \param str ISO8601 formatted string. + /// \return true if parsing succeeds and the timestamp corresponds to the first workday of the month, false otherwise. + inline bool is_first_workday_of_month(const std::string& str) { + ts_t ts = 0; + if (!str_to_ts(str, ts)) return false; + return is_first_workday_of_month(ts); + } + + /// \brief Parse an ISO8601 string and check if it is the first workday of its month (millisecond precision). + /// \param str ISO8601 formatted string. + /// \return true if parsing succeeds and the timestamp corresponds to the first workday of the month, false otherwise. + inline bool is_first_workday_of_month_ms(const std::string& str) { + ts_ms_t ts = 0; + if (!str_to_ts_ms(str, ts)) return false; + return is_first_workday_of_month_ms(ts); + } + + /// \brief Parse an ISO8601 string and check if it is the last workday of its month (seconds). + /// \param str ISO8601 formatted string. + /// \return true if parsing succeeds and the timestamp corresponds to the last workday of the month, false otherwise. + inline bool is_last_workday_of_month(const std::string& str) { + ts_t ts = 0; + if (!str_to_ts(str, ts)) return false; + return is_last_workday_of_month(ts); + } + + /// \brief Parse an ISO8601 string and check if it is the last workday of its month (millisecond). + /// \param str ISO8601 formatted string. + /// \return true if parsing succeeds and the timestamp corresponds to the last workday of the month, false otherwise. + inline bool is_last_workday_of_month_ms(const std::string& str) { + ts_ms_t ts = 0; + if (!str_to_ts_ms(str, ts)) return false; + return is_last_workday_of_month_ms(ts); + } + + /// \brief Parse an ISO8601 string and check if it falls within the first N workdays of its month. + /// \param str ISO8601 formatted string. + /// \param count Number of leading workdays to test against. + /// \return true if parsing succeeds and the timestamp corresponds to a workday ranked within the first N positions, false otherwise. + inline bool is_within_first_workdays_of_month(const std::string& str, int count) { + ts_t ts = 0; + if (!str_to_ts(str, ts)) return false; + return is_within_first_workdays_of_month(ts, count); + } + + /// \brief Parse an ISO8601 string and check if it falls within the first N workdays of its month (millisecond precision). + /// \param str ISO8601 formatted string. + /// \param count Number of leading workdays to test against. + /// \return true if parsing succeeds and the timestamp corresponds to a workday ranked within the first N positions, false otherwise. + inline bool is_within_first_workdays_of_month_ms(const std::string& str, int count) { + ts_ms_t ts = 0; + if (!str_to_ts_ms(str, ts)) return false; + return is_within_first_workdays_of_month_ms(ts, count); + } + + /// \brief Parse ISO8601 string and check if it is within last N workdays of its month (seconds). + /// \param str ISO8601 formatted string. + /// \param count Number of trailing workdays to test against. + /// \return true if parsing succeeds and the timestamp corresponds to a workday ranked within the final N positions, false otherwise. + inline bool is_within_last_workdays_of_month(const std::string& str, int count) { + ts_t ts = 0; + if (!str_to_ts(str, ts)) return false; + return is_within_last_workdays_of_month(ts, count); + } + + /// \brief Parse ISO8601 string and check if it is within last N workdays of its month (milliseconds). + /// \param str ISO8601 formatted string. + /// \param count Number of trailing workdays to test against. + /// \return true if parsing succeeds and the timestamp corresponds to a workday ranked within the final N positions, false otherwise. + inline bool is_within_last_workdays_of_month_ms(const std::string& str, int count) { + ts_ms_t ts = 0; + if (!str_to_ts_ms(str, ts)) return false; + return is_within_last_workdays_of_month_ms(ts, count); + } + +//------------------------------------------------------------------------------ +// Convenience: C-string wrappers (non-throwing, ambiguous on failure) +//------------------------------------------------------------------------------ + + /// \brief Convert ISO8601 C-string to timestamp (seconds). + /// \details Returns 0 on failure (ambiguous if epoch is a valid value for your usage). + /// \param str C-style string with ISO8601 timestamp, may be nullptr. + /// \return Timestamp in seconds, or 0 if parsing fails. + inline ts_t ts(const char* str) { + ts_t out = 0; + str_to_ts(str ? std::string(str) : std::string(), out); + return out; + } + + /// \brief Convert ISO8601 character buffer to timestamp (seconds). + /// \details Does not require null terminator. Returns 0 on failure + /// (ambiguous if epoch is a valid value for your usage). + /// \param data Pointer to character buffer. + /// \param length Number of characters in buffer. + /// \return Timestamp in seconds, or 0 if parsing fails. + inline ts_t ts(const char* data, std::size_t length) { + ts_t out = 0; + if (!str_to_ts(data, length, out)) { + return 0; + } + return out; + } + + /// \brief Convert ISO8601 C-string to timestamp (milliseconds). + /// \details Returns 0 on failure (ambiguous if epoch is a valid value for your usage). + /// \param str C-style string with ISO8601 timestamp, may be nullptr. + /// \return Timestamp in milliseconds, or 0 if parsing fails. + inline ts_ms_t ts_ms(const char* str) { + ts_ms_t out = 0; + str_to_ts_ms(str ? std::string(str) : std::string(), out); + return out; + } + + /// \brief Convert ISO8601 character buffer to timestamp (milliseconds). + /// \details Does not require null terminator. Returns 0 on failure. + /// \param data Pointer to character buffer. + /// \param length Number of characters in buffer. + /// \return Timestamp in milliseconds, or 0 if parsing fails. + inline ts_ms_t ts_ms(const char* data, std::size_t length) { + ts_ms_t out = 0; + if (!str_to_ts_ms(data, length, out)) { + return 0; + } + return out; + } + + /// \brief Convert ISO8601 C-string to floating timestamp (seconds). + /// \details Returns 0 on failure. + /// \param str C-style string with ISO8601 timestamp, may be nullptr. + /// \return Timestamp in seconds (floating-point), or 0 if parsing fails. + inline fts_t fts(const char* str) { + fts_t out = 0; + str_to_fts(str ? std::string(str) : std::string(), out); + return out; + } + + /// \brief Convert ISO8601 character buffer to floating timestamp (seconds). + /// \details Does not require null terminator. Returns 0 on failure. + /// \param data Pointer to character buffer. + /// \param length Number of characters in buffer. + /// \return Timestamp in seconds (floating-point), or 0 if parsing fails. + inline fts_t fts(const char* data, std::size_t length) { + fts_t out = 0; + if (!str_to_fts(data, length, out)) { + return 0.0; + } + return out; + } + +//------------------------------------------------------------------------------ + + /// \brief Convert an ISO8601 string to a timestamp (ts_t). + /// \details This function parses a string in ISO8601 format and converts it to a timestamp. + /// If parsing fails, it returns 0. + /// \param str The ISO8601 string. + /// \return The timestamp value. Returns 0 if parsing fails. + inline ts_t ts(const std::string& str) { + ts_t ts = 0; + str_to_ts(str, ts); + return ts; + } + + /// \brief Convert an ISO8601 string to a millisecond timestamp (ts_ms_t). + /// \details This function parses a string in ISO8601 format and converts it to a millisecond timestamp. + /// If parsing fails, it returns 0. + /// \param str The ISO8601 string. + /// \return The parsed millisecond timestamp, or 0 if parsing fails. + inline ts_ms_t ts_ms(const std::string& str) { + ts_ms_t ts = 0; + str_to_ts_ms(str, ts); + return ts; + } + + /// \brief Convert an ISO8601 string to a floating-point timestamp (fts_t). + /// \details This function parses a string in ISO8601 format and converts it to a floating-point timestamp. + /// If the parsing fails, it returns 0. + /// \param str The ISO8601 string. + /// \return The floating-point timestamp if successful, 0 otherwise. + inline fts_t fts(const std::string& str) { + fts_t ts = 0; + str_to_fts(str, ts); + return ts; + } + +//------------------------------------------------------------------------------ + + /// \brief Parse timeframe string into fixed seconds. + /// \param str Timeframe string such as "M15", "hour", or "2 weeks". + /// \param seconds Output duration in seconds. + /// \return True on successful parsing. + inline bool str_to_timeframe_sec(const std::string& str, ts_t& seconds) noexcept { + return detail::try_parse_timeframe_seconds(str.c_str(), str.size(), seconds); + } + + /// \brief Parse timeframe C-string into fixed seconds. + /// \param str Timeframe string such as "M15", "hour", or "2 weeks". + /// \param seconds Output duration in seconds. + /// \return True on successful parsing. + inline bool str_to_timeframe_sec(const char* str, ts_t& seconds) noexcept { + if (str == nullptr) { + seconds = 0; + return false; + } + return detail::try_parse_timeframe_seconds(str, std::strlen(str), seconds); + } + +# if __cplusplus >= 201703L + /// \brief Parse timeframe view into fixed seconds. + /// \param str Timeframe string such as "M15", "hour", or "2 weeks". + /// \param seconds Output duration in seconds. + /// \return True on successful parsing. + inline bool str_to_timeframe_sec(std::string_view str, ts_t& seconds) noexcept { + return detail::try_parse_timeframe_seconds(str.data(), str.size(), seconds); + } +# endif + + /// \brief Parse timeframe string into fixed milliseconds. + /// \param str Timeframe string such as "M15", "hour", or "2 weeks". + /// \param milliseconds Output duration in milliseconds. + /// \return True on successful parsing. + inline bool str_to_timeframe_ms(const std::string& str, ts_ms_t& milliseconds) noexcept { + ts_t seconds = 0; + if (!detail::try_parse_timeframe_seconds(str.c_str(), str.size(), seconds)) { + milliseconds = 0; + return false; + } + + int64_t milliseconds_value = 0; + if (!detail::try_multiply_positive_int64(seconds, MS_PER_SEC, milliseconds_value)) { + milliseconds = 0; + return false; + } + + milliseconds = static_cast(milliseconds_value); + return true; + } + + /// \brief Parse timeframe C-string into fixed milliseconds. + /// \param str Timeframe string such as "M15", "hour", or "2 weeks". + /// \param milliseconds Output duration in milliseconds. + /// \return True on successful parsing. + inline bool str_to_timeframe_ms(const char* str, ts_ms_t& milliseconds) noexcept { + if (str == nullptr) { + milliseconds = 0; + return false; + } + + ts_t seconds = 0; + if (!detail::try_parse_timeframe_seconds(str, std::strlen(str), seconds)) { + milliseconds = 0; + return false; + } + + int64_t milliseconds_value = 0; + if (!detail::try_multiply_positive_int64(seconds, MS_PER_SEC, milliseconds_value)) { + milliseconds = 0; + return false; + } + + milliseconds = static_cast(milliseconds_value); + return true; + } + +# if __cplusplus >= 201703L + /// \brief Parse timeframe view into fixed milliseconds. + /// \param str Timeframe string such as "M15", "hour", or "2 weeks". + /// \param milliseconds Output duration in milliseconds. + /// \return True on successful parsing. + inline bool str_to_timeframe_ms(std::string_view str, ts_ms_t& milliseconds) noexcept { + ts_t seconds = 0; + if (!detail::try_parse_timeframe_seconds(str.data(), str.size(), seconds)) { + milliseconds = 0; + return false; + } + + int64_t milliseconds_value = 0; + if (!detail::try_multiply_positive_int64(seconds, MS_PER_SEC, milliseconds_value)) { + milliseconds = 0; + return false; + } + + milliseconds = static_cast(milliseconds_value); + return true; + } +# endif + + /// \brief Convert timeframe string to fixed seconds. + /// \details Returns 0 if parsing fails. + /// \param str Timeframe string such as "M15", "hour", or "2 weeks". + /// \return Parsed duration in seconds, or 0 on failure. + inline ts_t timeframe_sec(const std::string& str) noexcept { + ts_t seconds = 0; + str_to_timeframe_sec(str, seconds); + return seconds; + } + + /// \brief Convert timeframe C-string to fixed seconds. + /// \details Returns 0 if parsing fails. + /// \param str Timeframe string such as "M15", "hour", or "2 weeks". + /// \return Parsed duration in seconds, or 0 on failure. + inline ts_t timeframe_sec(const char* str) noexcept { + ts_t seconds = 0; + str_to_timeframe_sec(str, seconds); + return seconds; + } + +# if __cplusplus >= 201703L + /// \brief Convert timeframe view to fixed seconds. + /// \details Returns 0 if parsing fails. + /// \param str Timeframe string such as "M15", "hour", or "2 weeks". + /// \return Parsed duration in seconds, or 0 on failure. + inline ts_t timeframe_sec(std::string_view str) noexcept { + ts_t seconds = 0; + str_to_timeframe_sec(str, seconds); + return seconds; + } +# endif + + /// \brief Convert timeframe string to fixed milliseconds. + /// \details Returns 0 if parsing fails. + /// \param str Timeframe string such as "M15", "hour", or "2 weeks". + /// \return Parsed duration in milliseconds, or 0 on failure. + inline ts_ms_t timeframe_ms(const std::string& str) noexcept { + ts_ms_t milliseconds = 0; + str_to_timeframe_ms(str, milliseconds); + return milliseconds; + } + + /// \brief Convert timeframe C-string to fixed milliseconds. + /// \details Returns 0 if parsing fails. + /// \param str Timeframe string such as "M15", "hour", or "2 weeks". + /// \return Parsed duration in milliseconds, or 0 on failure. + inline ts_ms_t timeframe_ms(const char* str) noexcept { + ts_ms_t milliseconds = 0; + str_to_timeframe_ms(str, milliseconds); + return milliseconds; + } + +# if __cplusplus >= 201703L + /// \brief Convert timeframe view to fixed milliseconds. + /// \details Returns 0 if parsing fails. + /// \param str Timeframe string such as "M15", "hour", or "2 weeks". + /// \return Parsed duration in milliseconds, or 0 on failure. + inline ts_ms_t timeframe_ms(std::string_view str) noexcept { + ts_ms_t milliseconds = 0; + str_to_timeframe_ms(str, milliseconds); + return milliseconds; + } +# endif + +//------------------------------------------------------------------------------ + + /// \brief Parse time of day string to seconds of day. + /// + /// Supported formats: + /// - HH:MM:SS + /// - HH:MM + /// - HH + /// + /// \tparam T Return type (default int). + /// \param str Time of day as string. + /// \param sec Parsed seconds of day on success. + /// \return True on successful parsing. + template + inline bool sec_of_day(const std::string& str, T& sec) { + if (str.empty()) return false; + + const char* p = str.c_str(); + int parts[3] = {0, 0, 0}; // hour, minute, second + int idx = 0; + + while (*p && idx < 3) { + // Parse integer + int value = 0; + bool has_digit = false; + + while (*p >= '0' && *p <= '9') { + has_digit = true; + value = value * 10 + (*p - '0'); + ++p; + } + + if (!has_digit) return false; + parts[idx++] = value; + + // Expect colon or end + if (*p == ':') { + ++p; + } else if (*p == '\0') { + break; + } else { + return false; // unexpected character + } + } + + if (idx == 0) return false; + if (!is_valid_time(parts[0], parts[1], parts[2])) return false; + + sec = static_cast(sec_of_day(parts[0], parts[1], parts[2])); + return true; + } + + /// \brief Convert time of day string to seconds of day. + /// + /// Supported formats: + /// - HH:MM:SS + /// - HH:MM + /// - HH + /// + /// \tparam T Return type (default int). + /// \param str Time of day as string. + /// \return Parsed seconds of day or SEC_PER_DAY if parsing fails. + template + inline T sec_of_day(const std::string& str) { + T value{}; + if (sec_of_day(str, value)) + return value; + return static_cast(SEC_PER_DAY); + } + +/// \} + +}; + +#endif // TIME_SHIELD_HEADER_TEXT_TIME_PARSER_HPP_INCLUDED diff --git a/include/time_shield/time_conversion_aliases.hpp b/include/time_shield/time_conversion_aliases.hpp index d95a33e9..826fa1a8 100644 --- a/include/time_shield/time_conversion_aliases.hpp +++ b/include/time_shield/time_conversion_aliases.hpp @@ -1,2118 +1,8 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_TIME_CONVERSION_ALIASES_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_TIME_CONVERSION_ALIASES_HPP_INCLUDED +#ifndef TIME_SHIELD_HEADER_TIME_CONVERSION_ALIASES_HPP_INCLUDED +#define TIME_SHIELD_HEADER_TIME_CONVERSION_ALIASES_HPP_INCLUDED -/// \file time_conversion_aliases.hpp -/// \brief Convenience aliases for the time-conversion API. -/// -/// Definitions provide alternative names for commonly used conversion helpers. -/// Doxygen sees the declarations directly and can index each alias independently. -/// Include this header after the canonical conversion declarations. +#include -#include - -namespace time_shield { - -/// \ingroup time_conversions -/// \{ - - /// \brief Alias for years_since_epoch function. - /// \copydoc years_since_epoch - template - TIME_SHIELD_CONSTEXPR T unix_year(ts_t ts) noexcept { - return years_since_epoch(ts); - } - - /// \brief Alias for years_since_epoch function. - /// \copydoc years_since_epoch - template - TIME_SHIELD_CONSTEXPR T to_unix_year(ts_t ts) noexcept { - return years_since_epoch(ts); - } - -//------------------------------------------------------------------------------ - - - /// \brief Alias for days_since_epoch function. - /// \copydoc days_since_epoch - template - TIME_SHIELD_CONSTEXPR T get_unixday(ts_t ts = time_shield::ts()) noexcept { - return days_since_epoch(ts); - } - - - /// \brief Alias for days_since_epoch function. - /// \copydoc days_since_epoch - template - TIME_SHIELD_CONSTEXPR T unix_day(ts_t ts = time_shield::ts()) noexcept { - return days_since_epoch(ts); - } - - - /// \brief Short alias for days_since_epoch. - /// \copydoc days_since_epoch - template - TIME_SHIELD_CONSTEXPR T dse(ts_t ts = time_shield::ts()) noexcept { - return days_since_epoch(ts); - } - - - /// \brief Alias for days_since_epoch function. - /// \copydoc days_since_epoch - template - TIME_SHIELD_CONSTEXPR T unixday(ts_t ts = time_shield::ts()) noexcept { - return days_since_epoch(ts); - } - - - /// \brief Alias for days_since_epoch function. - /// \copydoc days_since_epoch - template - TIME_SHIELD_CONSTEXPR T uday(ts_t ts = time_shield::ts()) noexcept { - return days_since_epoch(ts); - } - -//------------------------------------------------------------------------------ - - - /// \brief Alias for days_since_epoch function. - /// \copydoc days_since_epoch - template - TIME_SHIELD_CONSTEXPR T get_unixday_ms(ts_ms_t t_ms = time_shield::ts_ms()) noexcept { - return days_since_epoch_ms(t_ms); - } - - - /// \brief Alias for days_since_epoch function. - /// \copydoc days_since_epoch - template - TIME_SHIELD_CONSTEXPR T unix_day_ms(ts_ms_t t_ms = time_shield::ts_ms()) noexcept { - return days_since_epoch_ms(t_ms); - } - - - /// \brief Short alias for days_since_epoch_ms. - /// \copydoc days_since_epoch_ms - template - TIME_SHIELD_CONSTEXPR T dse_ms(ts_ms_t t_ms = time_shield::ts_ms()) noexcept { - return days_since_epoch_ms(t_ms); - } - - - /// \brief Alias for days_since_epoch function. - /// \copydoc days_since_epoch - template - TIME_SHIELD_CONSTEXPR T unixday_ms(ts_ms_t t_ms = time_shield::ts_ms()) noexcept { - return days_since_epoch_ms(t_ms); - } - - - /// \brief Alias for days_since_epoch function. - /// \copydoc days_since_epoch - template - TIME_SHIELD_CONSTEXPR T uday_ms(ts_ms_t t_ms = time_shield::ts_ms()) noexcept { - return days_since_epoch_ms(t_ms); - } - -//------------------------------------------------------------------------------ - - /// \brief Alias for unix_day_to_ts function. - /// \copydoc unix_day_to_ts - template - TIME_SHIELD_CONSTEXPR T unixday_to_ts(dse_t unix_day) noexcept { - return unix_day_to_ts(unix_day); - } - - /// \brief Short alias for unix_day_to_ts. - /// \copydoc unix_day_to_ts - template - TIME_SHIELD_CONSTEXPR T dse_to_ts(dse_t unix_day) noexcept { - return unix_day_to_ts(unix_day); - } - - /// \brief Alias for unix_day_to_ts function. - /// \copydoc unix_day_to_ts - template - TIME_SHIELD_CONSTEXPR T uday_to_ts(dse_t unix_day) noexcept { - return unix_day_to_ts(unix_day); - } - - /// \brief Alias for unix_day_to_ts function. - /// \copydoc unix_day_to_ts - template - TIME_SHIELD_CONSTEXPR T start_of_day_from_unix_day(dse_t unix_day) noexcept { - return unix_day_to_ts(unix_day); - } - -//------------------------------------------------------------------------------ - - /// \brief Alias for unix_day_to_ts_ms function. - /// \copydoc unix_day_to_ts_ms - template - TIME_SHIELD_CONSTEXPR T unixday_to_ts_ms(dse_t unix_day) noexcept { - return unix_day_to_ts_ms(unix_day); - } - - /// \brief Short alias for unix_day_to_ts_ms. - /// \copydoc unix_day_to_ts_ms - template - TIME_SHIELD_CONSTEXPR T dse_to_ts_ms(dse_t unix_day) noexcept { - return unix_day_to_ts_ms(unix_day); - } - - /// \brief Alias for unix_day_to_ts_ms function. - /// \copydoc unix_day_to_ts_ms - template - TIME_SHIELD_CONSTEXPR T uday_to_ts_ms(dse_t unix_day) noexcept { - return unix_day_to_ts_ms(unix_day); - } - - /// \brief Alias for unix_day_to_ts_ms function. - /// \copydoc unix_day_to_ts_ms - template - TIME_SHIELD_CONSTEXPR T start_of_day_from_unix_day_ms(dse_t unix_day) noexcept { - return unix_day_to_ts_ms(unix_day); - } - -//------------------------------------------------------------------------------ - - /// \brief Alias for start_of_next_day_from_unix_day function. - /// \copydoc start_of_next_day_from_unix_day - template - TIME_SHIELD_CONSTEXPR T next_day_from_unix_day(dse_t unix_day) noexcept { - return start_of_next_day_from_unix_day(unix_day); - } - -//------------------------------------------------------------------------------ - - /// \brief Alias for start_of_next_day_from_unix_day_ms function. - /// \copydoc start_of_next_day_from_unix_day_ms - template - TIME_SHIELD_CONSTEXPR T next_day_from_unix_day_ms(dse_t unix_day) noexcept { - return start_of_next_day_from_unix_day_ms(unix_day); - } - -//------------------------------------------------------------------------------ - - /// \brief Alias for min_since_epoch function. - /// \copydoc min_since_epoch - template - TIME_SHIELD_CONSTEXPR T minutes_since_epoch(ts_t ts = time_shield::ts()) { - return min_since_epoch(ts); - } - - /// \brief Alias for min_since_epoch function. - /// \copydoc min_since_epoch - template - TIME_SHIELD_CONSTEXPR T unix_min(ts_t ts = time_shield::ts()) { - return min_since_epoch(ts); - } - - /// \brief Alias for min_since_epoch function. - /// \copydoc min_since_epoch - template - TIME_SHIELD_CONSTEXPR T to_unix_min(ts_t ts = time_shield::ts()) { - return min_since_epoch(ts); - } - - /// \brief Alias for min_since_epoch function. - /// \copydoc min_since_epoch - template - TIME_SHIELD_CONSTEXPR T umin(ts_t ts = time_shield::ts()) { - return min_since_epoch(ts); - } - -//------------------------------------------------------------------------------ - - /// \ingroup time_structures - /// \brief Alias for dt_to_timestamp. - /// \copydoc dt_to_timestamp - template - TIME_SHIELD_CONSTEXPR inline auto dt_to_ts(const T& date_time) - -> decltype(dt_to_timestamp(date_time)) { - return dt_to_timestamp(date_time); - } - - /// \ingroup time_structures - /// \brief Alias for tm_to_timestamp. - /// \copydoc tm_to_timestamp - TIME_SHIELD_CONSTEXPR inline auto tm_to_ts(const std::tm* timeinfo) - -> decltype(tm_to_timestamp(timeinfo)) { - return tm_to_timestamp(timeinfo); - } - -//------------------------------------------------------------------------------ - - /// \brief Alias for hour24_to_12 function. - /// \copydoc hour24_to_12 - template - TIME_SHIELD_CONSTEXPR inline T h24_to_h12(T hour) noexcept { - return hour24_to_12(hour); - } - -//------------------------------------------------------------------------------ - - /// \ingroup time_structures - /// \brief Alias for to_date_time function. - /// \copydoc to_date_time - template - T1 to_dt(T2 ts) { - return to_date_time(ts); - } - - /// \ingroup time_structures - /// \brief Alias for to_date_time function. - /// \copydoc to_date_time - template - T1 to_dt_struct(T2 ts) { - return to_date_time(ts); - } - - /// \ingroup time_structures - /// \brief Alias for to_date_time function. - /// \copydoc to_date_time - inline auto to_dt(ts_t ts) - -> decltype(to_date_time(ts)) { - return to_date_time(ts); - } - -//------------------------------------------------------------------------------ - - /// \ingroup time_structures - /// \brief Alias for to_date_time_ms function. - /// \copydoc to_date_time_ms - template - inline T to_dt_ms(ts_ms_t ts) { - return to_date_time_ms(ts); - } - - /// \ingroup time_structures - /// \brief Alias for to_date_time_ms function. - /// \copydoc to_date_time_ms - template - inline T to_dt_struct_ms(ts_ms_t ts) { - return to_date_time_ms(ts); - } - - /// \ingroup time_structures - /// \brief Alias for to_date_time_ms function. - /// \copydoc to_date_time_ms - inline auto to_dt_ms(ts_ms_t ts_ms) - -> decltype(to_date_time_ms(ts_ms)) { - return to_date_time_ms(ts_ms); - } - -//------------------------------------------------------------------------------ - - - /// \brief Alias for to_timestamp - /// - /// This function converts a given date and time to a timestamp, which is the number - /// of seconds since the Unix epoch (January 1, 1970). - /// - /// If the `day` is ≥ 1970 and `year` ≤ 31, parameters are assumed to be in DD-MM-YYYY order - /// and are automatically reordered. - /// - /// \param year The year value. - /// \param month The month value. - /// \param day The day value. - /// \return Timestamp representing the given date and time. - /// \throws std::invalid_argument if the date-time combination is invalid. - /// \see to_timestamp - TIME_SHIELD_CONSTEXPR inline ts_t ts(year_t year, int month, int day) { - return to_timestamp(year, month, day); - } - - - /// \brief Alias for to_timestamp - /// - /// This function converts a given date and time to a timestamp, which is the number - /// of seconds since the Unix epoch (January 1, 1970). - /// - /// If the `day` is ≥ 1970 and `year` ≤ 31, parameters are assumed to be in DD-MM-YYYY order - /// and are automatically reordered. - /// - /// \param year The year value. - /// \param month The month value. - /// \param day The day value. - /// \param hour The hour value. - /// \return Timestamp representing the given date and time. - /// \throws std::invalid_argument if the date-time combination is invalid. - /// \see to_timestamp - TIME_SHIELD_CONSTEXPR inline ts_t ts(year_t year, int month, int day, int hour) { - return to_timestamp(year, month, day, hour); - } - - - /// \brief Alias for to_timestamp - /// - /// This function converts a given date and time to a timestamp, which is the number - /// of seconds since the Unix epoch (January 1, 1970). - /// - /// If the `day` is ≥ 1970 and `year` ≤ 31, parameters are assumed to be in DD-MM-YYYY order - /// and are automatically reordered. - /// - /// \param year The year value. - /// \param month The month value. - /// \param day The day value. - /// \param hour The hour value. - /// \param min The minute value. - /// \return Timestamp representing the given date and time. - /// \throws std::invalid_argument if the date-time combination is invalid. - /// \see to_timestamp - TIME_SHIELD_CONSTEXPR inline ts_t ts(year_t year, int month, int day, int hour, int min) { - return to_timestamp(year, month, day, hour, min); - } - - - /// \brief Alias for to_timestamp - /// - /// This function converts a given date and time to a timestamp, which is the number - /// of seconds since the Unix epoch (January 1, 1970). - /// - /// If the `day` is ≥ 1970 and `year` ≤ 31, parameters are assumed to be in DD-MM-YYYY order - /// and are automatically reordered. - /// - /// \param year The year value. - /// \param month The month value. - /// \param day The day value. - /// \param hour The hour value. - /// \param min The minute value. - /// \param sec The second value. - /// \return Timestamp representing the given date and time. - /// \throws std::invalid_argument if the date-time combination is invalid. - /// \see to_timestamp - TIME_SHIELD_CONSTEXPR inline ts_t ts(year_t year, int month, int day, int hour, int min, int sec) { - return to_timestamp(year, month, day, hour, min, sec); - } - - - /// \brief Alias for to_timestamp - /// - /// This function converts a given date and time to a timestamp, which is the number - /// of seconds since the Unix epoch (January 1, 1970). - /// - /// If the `day` is ≥ 1970 and `year` ≤ 31, parameters are assumed to be in DD-MM-YYYY order - /// and are automatically reordered. - /// - /// \param year The year value. - /// \param month The month value. - /// \param day The day value. - /// \return Timestamp representing the given date and time. - /// \throws std::invalid_argument if the date-time combination is invalid. - /// \see to_timestamp - TIME_SHIELD_CONSTEXPR inline ts_t get_ts(year_t year, int month, int day) { - return to_timestamp(year, month, day); - } - - - /// \brief Alias for to_timestamp - /// - /// This function converts a given date and time to a timestamp, which is the number - /// of seconds since the Unix epoch (January 1, 1970). - /// - /// If the `day` is ≥ 1970 and `year` ≤ 31, parameters are assumed to be in DD-MM-YYYY order - /// and are automatically reordered. - /// - /// \param year The year value. - /// \param month The month value. - /// \param day The day value. - /// \param hour The hour value. - /// \return Timestamp representing the given date and time. - /// \throws std::invalid_argument if the date-time combination is invalid. - /// \see to_timestamp - TIME_SHIELD_CONSTEXPR inline ts_t get_ts(year_t year, int month, int day, int hour) { - return to_timestamp(year, month, day, hour); - } - - - /// \brief Alias for to_timestamp - /// - /// This function converts a given date and time to a timestamp, which is the number - /// of seconds since the Unix epoch (January 1, 1970). - /// - /// If the `day` is ≥ 1970 and `year` ≤ 31, parameters are assumed to be in DD-MM-YYYY order - /// and are automatically reordered. - /// - /// \param year The year value. - /// \param month The month value. - /// \param day The day value. - /// \param hour The hour value. - /// \param min The minute value. - /// \return Timestamp representing the given date and time. - /// \throws std::invalid_argument if the date-time combination is invalid. - /// \see to_timestamp - TIME_SHIELD_CONSTEXPR inline ts_t get_ts(year_t year, int month, int day, int hour, int min) { - return to_timestamp(year, month, day, hour, min); - } - - - /// \brief Alias for to_timestamp - /// - /// This function converts a given date and time to a timestamp, which is the number - /// of seconds since the Unix epoch (January 1, 1970). - /// - /// If the `day` is ≥ 1970 and `year` ≤ 31, parameters are assumed to be in DD-MM-YYYY order - /// and are automatically reordered. - /// - /// \param year The year value. - /// \param month The month value. - /// \param day The day value. - /// \param hour The hour value. - /// \param min The minute value. - /// \param sec The second value. - /// \return Timestamp representing the given date and time. - /// \throws std::invalid_argument if the date-time combination is invalid. - /// \see to_timestamp - TIME_SHIELD_CONSTEXPR inline ts_t get_ts(year_t year, int month, int day, int hour, int min, int sec) { - return to_timestamp(year, month, day, hour, min, sec); - } - - - /// \brief Alias for to_timestamp - /// - /// This function converts a given date and time to a timestamp, which is the number - /// of seconds since the Unix epoch (January 1, 1970). - /// - /// If the `day` is ≥ 1970 and `year` ≤ 31, parameters are assumed to be in DD-MM-YYYY order - /// and are automatically reordered. - /// - /// \param year The year value. - /// \param month The month value. - /// \param day The day value. - /// \return Timestamp representing the given date and time. - /// \throws std::invalid_argument if the date-time combination is invalid. - /// \see to_timestamp - TIME_SHIELD_CONSTEXPR inline ts_t get_timestamp(year_t year, int month, int day) { - return to_timestamp(year, month, day); - } - - - /// \brief Alias for to_timestamp - /// - /// This function converts a given date and time to a timestamp, which is the number - /// of seconds since the Unix epoch (January 1, 1970). - /// - /// If the `day` is ≥ 1970 and `year` ≤ 31, parameters are assumed to be in DD-MM-YYYY order - /// and are automatically reordered. - /// - /// \param year The year value. - /// \param month The month value. - /// \param day The day value. - /// \param hour The hour value. - /// \return Timestamp representing the given date and time. - /// \throws std::invalid_argument if the date-time combination is invalid. - /// \see to_timestamp - TIME_SHIELD_CONSTEXPR inline ts_t get_timestamp(year_t year, int month, int day, int hour) { - return to_timestamp(year, month, day, hour); - } - - - /// \brief Alias for to_timestamp - /// - /// This function converts a given date and time to a timestamp, which is the number - /// of seconds since the Unix epoch (January 1, 1970). - /// - /// If the `day` is ≥ 1970 and `year` ≤ 31, parameters are assumed to be in DD-MM-YYYY order - /// and are automatically reordered. - /// - /// \param year The year value. - /// \param month The month value. - /// \param day The day value. - /// \param hour The hour value. - /// \param min The minute value. - /// \return Timestamp representing the given date and time. - /// \throws std::invalid_argument if the date-time combination is invalid. - /// \see to_timestamp - TIME_SHIELD_CONSTEXPR inline ts_t get_timestamp(year_t year, int month, int day, int hour, int min) { - return to_timestamp(year, month, day, hour, min); - } - - - /// \brief Alias for to_timestamp - /// - /// This function converts a given date and time to a timestamp, which is the number - /// of seconds since the Unix epoch (January 1, 1970). - /// - /// If the `day` is ≥ 1970 and `year` ≤ 31, parameters are assumed to be in DD-MM-YYYY order - /// and are automatically reordered. - /// - /// \param year The year value. - /// \param month The month value. - /// \param day The day value. - /// \param hour The hour value. - /// \param min The minute value. - /// \param sec The second value. - /// \return Timestamp representing the given date and time. - /// \throws std::invalid_argument if the date-time combination is invalid. - /// \see to_timestamp - TIME_SHIELD_CONSTEXPR inline ts_t get_timestamp(year_t year, int month, int day, int hour, int min, int sec) { - return to_timestamp(year, month, day, hour, min, sec); - } - - - /// \brief Alias for to_timestamp - /// - /// This function converts a given date and time to a timestamp, which is the number - /// of seconds since the Unix epoch (January 1, 1970). - /// - /// If the `day` is ≥ 1970 and `year` ≤ 31, parameters are assumed to be in DD-MM-YYYY order - /// and are automatically reordered. - /// - /// \param year The year value. - /// \param month The month value. - /// \param day The day value. - /// \return Timestamp representing the given date and time. - /// \throws std::invalid_argument if the date-time combination is invalid. - /// \see to_timestamp - TIME_SHIELD_CONSTEXPR inline ts_t timestamp(year_t year, int month, int day) { - return to_timestamp(year, month, day); - } - - - /// \brief Alias for to_timestamp - /// - /// This function converts a given date and time to a timestamp, which is the number - /// of seconds since the Unix epoch (January 1, 1970). - /// - /// If the `day` is ≥ 1970 and `year` ≤ 31, parameters are assumed to be in DD-MM-YYYY order - /// and are automatically reordered. - /// - /// \param year The year value. - /// \param month The month value. - /// \param day The day value. - /// \param hour The hour value. - /// \return Timestamp representing the given date and time. - /// \throws std::invalid_argument if the date-time combination is invalid. - /// \see to_timestamp - TIME_SHIELD_CONSTEXPR inline ts_t timestamp(year_t year, int month, int day, int hour) { - return to_timestamp(year, month, day, hour); - } - - - /// \brief Alias for to_timestamp - /// - /// This function converts a given date and time to a timestamp, which is the number - /// of seconds since the Unix epoch (January 1, 1970). - /// - /// If the `day` is ≥ 1970 and `year` ≤ 31, parameters are assumed to be in DD-MM-YYYY order - /// and are automatically reordered. - /// - /// \param year The year value. - /// \param month The month value. - /// \param day The day value. - /// \param hour The hour value. - /// \param min The minute value. - /// \return Timestamp representing the given date and time. - /// \throws std::invalid_argument if the date-time combination is invalid. - /// \see to_timestamp - TIME_SHIELD_CONSTEXPR inline ts_t timestamp(year_t year, int month, int day, int hour, int min) { - return to_timestamp(year, month, day, hour, min); - } - - - /// \brief Alias for to_timestamp - /// - /// This function converts a given date and time to a timestamp, which is the number - /// of seconds since the Unix epoch (January 1, 1970). - /// - /// If the `day` is ≥ 1970 and `year` ≤ 31, parameters are assumed to be in DD-MM-YYYY order - /// and are automatically reordered. - /// - /// \param year The year value. - /// \param month The month value. - /// \param day The day value. - /// \param hour The hour value. - /// \param min The minute value. - /// \param sec The second value. - /// \return Timestamp representing the given date and time. - /// \throws std::invalid_argument if the date-time combination is invalid. - /// \see to_timestamp - TIME_SHIELD_CONSTEXPR inline ts_t timestamp(year_t year, int month, int day, int hour, int min, int sec) { - return to_timestamp(year, month, day, hour, min, sec); - } - - - /// \brief Alias for to_timestamp - /// - /// This function converts a given date and time to a timestamp, which is the number - /// of seconds since the Unix epoch (January 1, 1970). - /// - /// If the `day` is ≥ 1970 and `year` ≤ 31, parameters are assumed to be in DD-MM-YYYY order - /// and are automatically reordered. - /// - /// \param year The year value. - /// \param month The month value. - /// \param day The day value. - /// \return Timestamp representing the given date and time. - /// \throws std::invalid_argument if the date-time combination is invalid. - /// \see to_timestamp - TIME_SHIELD_CONSTEXPR inline ts_t to_ts(year_t year, int month, int day) { - return to_timestamp(year, month, day); - } - - - /// \brief Alias for to_timestamp - /// - /// This function converts a given date and time to a timestamp, which is the number - /// of seconds since the Unix epoch (January 1, 1970). - /// - /// If the `day` is ≥ 1970 and `year` ≤ 31, parameters are assumed to be in DD-MM-YYYY order - /// and are automatically reordered. - /// - /// \param year The year value. - /// \param month The month value. - /// \param day The day value. - /// \param hour The hour value. - /// \return Timestamp representing the given date and time. - /// \throws std::invalid_argument if the date-time combination is invalid. - /// \see to_timestamp - TIME_SHIELD_CONSTEXPR inline ts_t to_ts(year_t year, int month, int day, int hour) { - return to_timestamp(year, month, day, hour); - } - - - /// \brief Alias for to_timestamp - /// - /// This function converts a given date and time to a timestamp, which is the number - /// of seconds since the Unix epoch (January 1, 1970). - /// - /// If the `day` is ≥ 1970 and `year` ≤ 31, parameters are assumed to be in DD-MM-YYYY order - /// and are automatically reordered. - /// - /// \param year The year value. - /// \param month The month value. - /// \param day The day value. - /// \param hour The hour value. - /// \param min The minute value. - /// \return Timestamp representing the given date and time. - /// \throws std::invalid_argument if the date-time combination is invalid. - /// \see to_timestamp - TIME_SHIELD_CONSTEXPR inline ts_t to_ts(year_t year, int month, int day, int hour, int min) { - return to_timestamp(year, month, day, hour, min); - } - - - /// \brief Alias for to_timestamp - /// - /// This function converts a given date and time to a timestamp, which is the number - /// of seconds since the Unix epoch (January 1, 1970). - /// - /// If the `day` is ≥ 1970 and `year` ≤ 31, parameters are assumed to be in DD-MM-YYYY order - /// and are automatically reordered. - /// - /// \param year The year value. - /// \param month The month value. - /// \param day The day value. - /// \param hour The hour value. - /// \param min The minute value. - /// \param sec The second value. - /// \return Timestamp representing the given date and time. - /// \throws std::invalid_argument if the date-time combination is invalid. - /// \see to_timestamp - TIME_SHIELD_CONSTEXPR inline ts_t to_ts(year_t year, int month, int day, int hour, int min, int sec) { - return to_timestamp(year, month, day, hour, min, sec); - } - -//------------------------------------------------------------------------------ - - /// \ingroup time_structures - /// \brief Alias for dt_to_timestamp function. - /// \copydoc dt_to_timestamp - template - TIME_SHIELD_CONSTEXPR inline ts_t to_timestamp( - const T& date_time) { - return dt_to_timestamp(date_time); - } - - /// \ingroup time_structures - /// \brief Alias for dt_to_timestamp function. - /// \copydoc dt_to_timestamp - template - TIME_SHIELD_CONSTEXPR inline ts_t to_ts( - const T& date_time) { - return dt_to_timestamp(date_time); - } - - /// \ingroup time_structures - /// \brief Alias for dt_to_timestamp function. - /// \copydoc dt_to_timestamp - template - TIME_SHIELD_CONSTEXPR inline ts_t ts( - const T& date_time) { - return dt_to_timestamp(date_time); - } - - /// \ingroup time_structures - /// \brief Alias for dt_to_timestamp function. - /// \copydoc dt_to_timestamp - template - TIME_SHIELD_CONSTEXPR inline ts_t timestamp( - const T& date_time) { - return dt_to_timestamp(date_time); - } - -//------------------------------------------------------------------------------ - - /// \brief Alias for tm_to_timestamp - /// \copydoc tm_to_timestamp - TIME_SHIELD_CONSTEXPR inline ts_t ts(const std::tm* timeinfo) { - return tm_to_timestamp(timeinfo); - } - - /// \brief Alias for tm_to_timestamp - /// \copydoc tm_to_timestamp - TIME_SHIELD_CONSTEXPR inline ts_t get_ts(const std::tm* timeinfo) { - return tm_to_timestamp(timeinfo); - } - - /// \brief Alias for tm_to_timestamp - /// \copydoc tm_to_timestamp - TIME_SHIELD_CONSTEXPR inline ts_t timestamp(const std::tm* timeinfo) { - return tm_to_timestamp(timeinfo); - } - - /// \brief Alias for tm_to_timestamp - /// \copydoc tm_to_timestamp - TIME_SHIELD_CONSTEXPR inline ts_t get_timestamp(const std::tm* timeinfo) { - return tm_to_timestamp(timeinfo); - } - - /// \brief Alias for tm_to_timestamp - /// \copydoc tm_to_timestamp - TIME_SHIELD_CONSTEXPR inline ts_t to_ts(const std::tm* timeinfo) { - return tm_to_timestamp(timeinfo); - } - - /// \brief Alias for tm_to_timestamp - /// \copydoc tm_to_timestamp - TIME_SHIELD_CONSTEXPR inline ts_t ts_from_tm(const std::tm* timeinfo) { - return tm_to_timestamp(timeinfo); - } - - /// \brief Alias for tm_to_timestamp - /// \copydoc tm_to_timestamp - TIME_SHIELD_CONSTEXPR inline ts_t to_timestamp(const std::tm* timeinfo) { - return tm_to_timestamp(timeinfo); - } - -//------------------------------------------------------------------------------ - - /// \brief Alias for to_timestamp_ms - /// - /// This function converts a given date and time to a timestamp in milliseconds, - /// which is the number of milliseconds since the Unix epoch (January 1, 1970). - /// - /// \tparam T1 The type of the year parameter (default is int64_t). - /// \tparam T2 The type of the other date and time parameters (default is int). - /// \param year The year value. - /// \param month The month value. - /// \param day The day value. - /// \return Timestamp in milliseconds representing the given date and time. - /// \throws std::invalid_argument if the date-time combination is invalid. - /// \see to_timestamp_ms - TIME_SHIELD_CONSTEXPR inline ts_ms_t ts_ms(year_t year, int month, int day) { - return to_timestamp_ms(year, month, day); - } - - /// \brief Alias for to_timestamp_ms - /// - /// This function converts a given date and time to a timestamp in milliseconds, - /// which is the number of milliseconds since the Unix epoch (January 1, 1970). - /// - /// \tparam T1 The type of the year parameter (default is int64_t). - /// \tparam T2 The type of the other date and time parameters (default is int). - /// \param year The year value. - /// \param month The month value. - /// \param day The day value. - /// \param hour The hour value. - /// \return Timestamp in milliseconds representing the given date and time. - /// \throws std::invalid_argument if the date-time combination is invalid. - /// \see to_timestamp_ms - TIME_SHIELD_CONSTEXPR inline ts_ms_t ts_ms(year_t year, int month, int day, int hour) { - return to_timestamp_ms(year, month, day, hour); - } - - /// \brief Alias for to_timestamp_ms - /// - /// This function converts a given date and time to a timestamp in milliseconds, - /// which is the number of milliseconds since the Unix epoch (January 1, 1970). - /// - /// \tparam T1 The type of the year parameter (default is int64_t). - /// \tparam T2 The type of the other date and time parameters (default is int). - /// \param year The year value. - /// \param month The month value. - /// \param day The day value. - /// \param hour The hour value. - /// \param min The minute value. - /// \return Timestamp in milliseconds representing the given date and time. - /// \throws std::invalid_argument if the date-time combination is invalid. - /// \see to_timestamp_ms - TIME_SHIELD_CONSTEXPR inline ts_ms_t ts_ms(year_t year, int month, int day, int hour, int min) { - return to_timestamp_ms(year, month, day, hour, min); - } - - /// \brief Alias for to_timestamp_ms - /// - /// This function converts a given date and time to a timestamp in milliseconds, - /// which is the number of milliseconds since the Unix epoch (January 1, 1970). - /// - /// \tparam T1 The type of the year parameter (default is int64_t). - /// \tparam T2 The type of the other date and time parameters (default is int). - /// \param year The year value. - /// \param month The month value. - /// \param day The day value. - /// \param hour The hour value. - /// \param min The minute value. - /// \param sec The second value. - /// \return Timestamp in milliseconds representing the given date and time. - /// \throws std::invalid_argument if the date-time combination is invalid. - /// \see to_timestamp_ms - TIME_SHIELD_CONSTEXPR inline ts_ms_t ts_ms(year_t year, int month, int day, int hour, int min, int sec) { - return to_timestamp_ms(year, month, day, hour, min, sec); - } - - /// \brief Alias for to_timestamp_ms - /// - /// This function converts a given date and time to a timestamp in milliseconds, - /// which is the number of milliseconds since the Unix epoch (January 1, 1970). - /// - /// \tparam T1 The type of the year parameter (default is int64_t). - /// \tparam T2 The type of the other date and time parameters (default is int). - /// \param year The year value. - /// \param month The month value. - /// \param day The day value. - /// \param hour The hour value. - /// \param min The minute value. - /// \param sec The second value. - /// \param ms The millisecond value. - /// \return Timestamp in milliseconds representing the given date and time. - /// \throws std::invalid_argument if the date-time combination is invalid. - /// \see to_timestamp_ms - TIME_SHIELD_CONSTEXPR inline ts_ms_t ts_ms(year_t year, int month, int day, int hour, int min, int sec, int ms) { - return to_timestamp_ms(year, month, day, hour, min, sec, ms); - } - - /// \brief Alias for to_timestamp_ms - /// - /// This function converts a given date and time to a timestamp in milliseconds, - /// which is the number of milliseconds since the Unix epoch (January 1, 1970). - /// - /// \tparam T1 The type of the year parameter (default is int64_t). - /// \tparam T2 The type of the other date and time parameters (default is int). - /// \param year The year value. - /// \param month The month value. - /// \param day The day value. - /// \return Timestamp in milliseconds representing the given date and time. - /// \throws std::invalid_argument if the date-time combination is invalid. - /// \see to_timestamp_ms - TIME_SHIELD_CONSTEXPR inline ts_ms_t get_ts_ms(year_t year, int month, int day) { - return to_timestamp_ms(year, month, day); - } - - /// \brief Alias for to_timestamp_ms - /// - /// This function converts a given date and time to a timestamp in milliseconds, - /// which is the number of milliseconds since the Unix epoch (January 1, 1970). - /// - /// \tparam T1 The type of the year parameter (default is int64_t). - /// \tparam T2 The type of the other date and time parameters (default is int). - /// \param year The year value. - /// \param month The month value. - /// \param day The day value. - /// \param hour The hour value. - /// \return Timestamp in milliseconds representing the given date and time. - /// \throws std::invalid_argument if the date-time combination is invalid. - /// \see to_timestamp_ms - TIME_SHIELD_CONSTEXPR inline ts_ms_t get_ts_ms(year_t year, int month, int day, int hour) { - return to_timestamp_ms(year, month, day, hour); - } - - /// \brief Alias for to_timestamp_ms - /// - /// This function converts a given date and time to a timestamp in milliseconds, - /// which is the number of milliseconds since the Unix epoch (January 1, 1970). - /// - /// \tparam T1 The type of the year parameter (default is int64_t). - /// \tparam T2 The type of the other date and time parameters (default is int). - /// \param year The year value. - /// \param month The month value. - /// \param day The day value. - /// \param hour The hour value. - /// \param min The minute value. - /// \return Timestamp in milliseconds representing the given date and time. - /// \throws std::invalid_argument if the date-time combination is invalid. - /// \see to_timestamp_ms - TIME_SHIELD_CONSTEXPR inline ts_ms_t get_ts_ms(year_t year, int month, int day, int hour, int min) { - return to_timestamp_ms(year, month, day, hour, min); - } - - /// \brief Alias for to_timestamp_ms - /// - /// This function converts a given date and time to a timestamp in milliseconds, - /// which is the number of milliseconds since the Unix epoch (January 1, 1970). - /// - /// \tparam T1 The type of the year parameter (default is int64_t). - /// \tparam T2 The type of the other date and time parameters (default is int). - /// \param year The year value. - /// \param month The month value. - /// \param day The day value. - /// \param hour The hour value. - /// \param min The minute value. - /// \param sec The second value. - /// \return Timestamp in milliseconds representing the given date and time. - /// \throws std::invalid_argument if the date-time combination is invalid. - /// \see to_timestamp_ms - TIME_SHIELD_CONSTEXPR inline ts_ms_t get_ts_ms(year_t year, int month, int day, int hour, int min, int sec) { - return to_timestamp_ms(year, month, day, hour, min, sec); - } - - /// \brief Alias for to_timestamp_ms - /// - /// This function converts a given date and time to a timestamp in milliseconds, - /// which is the number of milliseconds since the Unix epoch (January 1, 1970). - /// - /// \tparam T1 The type of the year parameter (default is int64_t). - /// \tparam T2 The type of the other date and time parameters (default is int). - /// \param year The year value. - /// \param month The month value. - /// \param day The day value. - /// \param hour The hour value. - /// \param min The minute value. - /// \param sec The second value. - /// \param ms The millisecond value. - /// \return Timestamp in milliseconds representing the given date and time. - /// \throws std::invalid_argument if the date-time combination is invalid. - /// \see to_timestamp_ms - TIME_SHIELD_CONSTEXPR inline ts_ms_t get_ts_ms(year_t year, int month, int day, int hour, int min, int sec, int ms) { - return to_timestamp_ms(year, month, day, hour, min, sec, ms); - } - - /// \brief Alias for to_timestamp_ms - /// - /// This function converts a given date and time to a timestamp in milliseconds, - /// which is the number of milliseconds since the Unix epoch (January 1, 1970). - /// - /// \tparam T1 The type of the year parameter (default is int64_t). - /// \tparam T2 The type of the other date and time parameters (default is int). - /// \param year The year value. - /// \param month The month value. - /// \param day The day value. - /// \return Timestamp in milliseconds representing the given date and time. - /// \throws std::invalid_argument if the date-time combination is invalid. - /// \see to_timestamp_ms - TIME_SHIELD_CONSTEXPR inline ts_ms_t get_timestamp_ms(year_t year, int month, int day) { - return to_timestamp_ms(year, month, day); - } - - /// \brief Alias for to_timestamp_ms - /// - /// This function converts a given date and time to a timestamp in milliseconds, - /// which is the number of milliseconds since the Unix epoch (January 1, 1970). - /// - /// \tparam T1 The type of the year parameter (default is int64_t). - /// \tparam T2 The type of the other date and time parameters (default is int). - /// \param year The year value. - /// \param month The month value. - /// \param day The day value. - /// \param hour The hour value. - /// \return Timestamp in milliseconds representing the given date and time. - /// \throws std::invalid_argument if the date-time combination is invalid. - /// \see to_timestamp_ms - TIME_SHIELD_CONSTEXPR inline ts_ms_t get_timestamp_ms(year_t year, int month, int day, int hour) { - return to_timestamp_ms(year, month, day, hour); - } - - /// \brief Alias for to_timestamp_ms - /// - /// This function converts a given date and time to a timestamp in milliseconds, - /// which is the number of milliseconds since the Unix epoch (January 1, 1970). - /// - /// \tparam T1 The type of the year parameter (default is int64_t). - /// \tparam T2 The type of the other date and time parameters (default is int). - /// \param year The year value. - /// \param month The month value. - /// \param day The day value. - /// \param hour The hour value. - /// \param min The minute value. - /// \return Timestamp in milliseconds representing the given date and time. - /// \throws std::invalid_argument if the date-time combination is invalid. - /// \see to_timestamp_ms - TIME_SHIELD_CONSTEXPR inline ts_ms_t get_timestamp_ms(year_t year, int month, int day, int hour, int min) { - return to_timestamp_ms(year, month, day, hour, min); - } - - /// \brief Alias for to_timestamp_ms - /// - /// This function converts a given date and time to a timestamp in milliseconds, - /// which is the number of milliseconds since the Unix epoch (January 1, 1970). - /// - /// \tparam T1 The type of the year parameter (default is int64_t). - /// \tparam T2 The type of the other date and time parameters (default is int). - /// \param year The year value. - /// \param month The month value. - /// \param day The day value. - /// \param hour The hour value. - /// \param min The minute value. - /// \param sec The second value. - /// \return Timestamp in milliseconds representing the given date and time. - /// \throws std::invalid_argument if the date-time combination is invalid. - /// \see to_timestamp_ms - TIME_SHIELD_CONSTEXPR inline ts_ms_t get_timestamp_ms(year_t year, int month, int day, int hour, int min, int sec) { - return to_timestamp_ms(year, month, day, hour, min, sec); - } - - /// \brief Alias for to_timestamp_ms - /// - /// This function converts a given date and time to a timestamp in milliseconds, - /// which is the number of milliseconds since the Unix epoch (January 1, 1970). - /// - /// \tparam T1 The type of the year parameter (default is int64_t). - /// \tparam T2 The type of the other date and time parameters (default is int). - /// \param year The year value. - /// \param month The month value. - /// \param day The day value. - /// \param hour The hour value. - /// \param min The minute value. - /// \param sec The second value. - /// \param ms The millisecond value. - /// \return Timestamp in milliseconds representing the given date and time. - /// \throws std::invalid_argument if the date-time combination is invalid. - /// \see to_timestamp_ms - TIME_SHIELD_CONSTEXPR inline ts_ms_t get_timestamp_ms(year_t year, int month, int day, int hour, int min, int sec, int ms) { - return to_timestamp_ms(year, month, day, hour, min, sec, ms); - } - - /// \brief Alias for to_timestamp_ms - /// - /// This function converts a given date and time to a timestamp in milliseconds, - /// which is the number of milliseconds since the Unix epoch (January 1, 1970). - /// - /// \tparam T1 The type of the year parameter (default is int64_t). - /// \tparam T2 The type of the other date and time parameters (default is int). - /// \param year The year value. - /// \param month The month value. - /// \param day The day value. - /// \return Timestamp in milliseconds representing the given date and time. - /// \throws std::invalid_argument if the date-time combination is invalid. - /// \see to_timestamp_ms - TIME_SHIELD_CONSTEXPR inline ts_ms_t timestamp_ms(year_t year, int month, int day) { - return to_timestamp_ms(year, month, day); - } - - /// \brief Alias for to_timestamp_ms - /// - /// This function converts a given date and time to a timestamp in milliseconds, - /// which is the number of milliseconds since the Unix epoch (January 1, 1970). - /// - /// \tparam T1 The type of the year parameter (default is int64_t). - /// \tparam T2 The type of the other date and time parameters (default is int). - /// \param year The year value. - /// \param month The month value. - /// \param day The day value. - /// \param hour The hour value. - /// \return Timestamp in milliseconds representing the given date and time. - /// \throws std::invalid_argument if the date-time combination is invalid. - /// \see to_timestamp_ms - TIME_SHIELD_CONSTEXPR inline ts_ms_t timestamp_ms(year_t year, int month, int day, int hour) { - return to_timestamp_ms(year, month, day, hour); - } - - /// \brief Alias for to_timestamp_ms - /// - /// This function converts a given date and time to a timestamp in milliseconds, - /// which is the number of milliseconds since the Unix epoch (January 1, 1970). - /// - /// \tparam T1 The type of the year parameter (default is int64_t). - /// \tparam T2 The type of the other date and time parameters (default is int). - /// \param year The year value. - /// \param month The month value. - /// \param day The day value. - /// \param hour The hour value. - /// \param min The minute value. - /// \return Timestamp in milliseconds representing the given date and time. - /// \throws std::invalid_argument if the date-time combination is invalid. - /// \see to_timestamp_ms - TIME_SHIELD_CONSTEXPR inline ts_ms_t timestamp_ms(year_t year, int month, int day, int hour, int min) { - return to_timestamp_ms(year, month, day, hour, min); - } - - /// \brief Alias for to_timestamp_ms - /// - /// This function converts a given date and time to a timestamp in milliseconds, - /// which is the number of milliseconds since the Unix epoch (January 1, 1970). - /// - /// \tparam T1 The type of the year parameter (default is int64_t). - /// \tparam T2 The type of the other date and time parameters (default is int). - /// \param year The year value. - /// \param month The month value. - /// \param day The day value. - /// \param hour The hour value. - /// \param min The minute value. - /// \param sec The second value. - /// \return Timestamp in milliseconds representing the given date and time. - /// \throws std::invalid_argument if the date-time combination is invalid. - /// \see to_timestamp_ms - TIME_SHIELD_CONSTEXPR inline ts_ms_t timestamp_ms(year_t year, int month, int day, int hour, int min, int sec) { - return to_timestamp_ms(year, month, day, hour, min, sec); - } - - /// \brief Alias for to_timestamp_ms - /// - /// This function converts a given date and time to a timestamp in milliseconds, - /// which is the number of milliseconds since the Unix epoch (January 1, 1970). - /// - /// \tparam T1 The type of the year parameter (default is int64_t). - /// \tparam T2 The type of the other date and time parameters (default is int). - /// \param year The year value. - /// \param month The month value. - /// \param day The day value. - /// \param hour The hour value. - /// \param min The minute value. - /// \param sec The second value. - /// \param ms The millisecond value. - /// \return Timestamp in milliseconds representing the given date and time. - /// \throws std::invalid_argument if the date-time combination is invalid. - /// \see to_timestamp_ms - TIME_SHIELD_CONSTEXPR inline ts_ms_t timestamp_ms(year_t year, int month, int day, int hour, int min, int sec, int ms) { - return to_timestamp_ms(year, month, day, hour, min, sec, ms); - } - - /// \brief Alias for to_timestamp_ms - /// - /// This function converts a given date and time to a timestamp in milliseconds, - /// which is the number of milliseconds since the Unix epoch (January 1, 1970). - /// - /// \tparam T1 The type of the year parameter (default is int64_t). - /// \tparam T2 The type of the other date and time parameters (default is int). - /// \param year The year value. - /// \param month The month value. - /// \param day The day value. - /// \return Timestamp in milliseconds representing the given date and time. - /// \throws std::invalid_argument if the date-time combination is invalid. - /// \see to_timestamp_ms - TIME_SHIELD_CONSTEXPR inline ts_ms_t to_ts_ms(year_t year, int month, int day) { - return to_timestamp_ms(year, month, day); - } - - /// \brief Alias for to_timestamp_ms - /// - /// This function converts a given date and time to a timestamp in milliseconds, - /// which is the number of milliseconds since the Unix epoch (January 1, 1970). - /// - /// \tparam T1 The type of the year parameter (default is int64_t). - /// \tparam T2 The type of the other date and time parameters (default is int). - /// \param year The year value. - /// \param month The month value. - /// \param day The day value. - /// \param hour The hour value. - /// \return Timestamp in milliseconds representing the given date and time. - /// \throws std::invalid_argument if the date-time combination is invalid. - /// \see to_timestamp_ms - TIME_SHIELD_CONSTEXPR inline ts_ms_t to_ts_ms(year_t year, int month, int day, int hour) { - return to_timestamp_ms(year, month, day, hour); - } - - /// \brief Alias for to_timestamp_ms - /// - /// This function converts a given date and time to a timestamp in milliseconds, - /// which is the number of milliseconds since the Unix epoch (January 1, 1970). - /// - /// \tparam T1 The type of the year parameter (default is int64_t). - /// \tparam T2 The type of the other date and time parameters (default is int). - /// \param year The year value. - /// \param month The month value. - /// \param day The day value. - /// \param hour The hour value. - /// \param min The minute value. - /// \return Timestamp in milliseconds representing the given date and time. - /// \throws std::invalid_argument if the date-time combination is invalid. - /// \see to_timestamp_ms - TIME_SHIELD_CONSTEXPR inline ts_ms_t to_ts_ms(year_t year, int month, int day, int hour, int min) { - return to_timestamp_ms(year, month, day, hour, min); - } - - /// \brief Alias for to_timestamp_ms - /// - /// This function converts a given date and time to a timestamp in milliseconds, - /// which is the number of milliseconds since the Unix epoch (January 1, 1970). - /// - /// \tparam T1 The type of the year parameter (default is int64_t). - /// \tparam T2 The type of the other date and time parameters (default is int). - /// \param year The year value. - /// \param month The month value. - /// \param day The day value. - /// \param hour The hour value. - /// \param min The minute value. - /// \param sec The second value. - /// \return Timestamp in milliseconds representing the given date and time. - /// \throws std::invalid_argument if the date-time combination is invalid. - /// \see to_timestamp_ms - TIME_SHIELD_CONSTEXPR inline ts_ms_t to_ts_ms(year_t year, int month, int day, int hour, int min, int sec) { - return to_timestamp_ms(year, month, day, hour, min, sec); - } - - /// \brief Alias for to_timestamp_ms - /// - /// This function converts a given date and time to a timestamp in milliseconds, - /// which is the number of milliseconds since the Unix epoch (January 1, 1970). - /// - /// \tparam T1 The type of the year parameter (default is int64_t). - /// \tparam T2 The type of the other date and time parameters (default is int). - /// \param year The year value. - /// \param month The month value. - /// \param day The day value. - /// \param hour The hour value. - /// \param min The minute value. - /// \param sec The second value. - /// \param ms The millisecond value. - /// \return Timestamp in milliseconds representing the given date and time. - /// \throws std::invalid_argument if the date-time combination is invalid. - /// \see to_timestamp_ms - TIME_SHIELD_CONSTEXPR inline ts_ms_t to_ts_ms(year_t year, int month, int day, int hour, int min, int sec, int ms) { - return to_timestamp_ms(year, month, day, hour, min, sec, ms); - } - -//------------------------------------------------------------------------------ - - /// \ingroup time_structures - /// \brief Alias for dt_to_timestamp_ms function. - /// \copydoc dt_to_timestamp_ms - template - TIME_SHIELD_CONSTEXPR inline ts_t to_timestamp_ms( - const T& date_time) { - return dt_to_timestamp_ms(date_time); - } - - /// \ingroup time_structures - /// \brief Alias for dt_to_timestamp_ms function. - /// \copydoc dt_to_timestamp_ms - template - TIME_SHIELD_CONSTEXPR inline auto dt_to_ts_ms(const T& date_time) - -> decltype(dt_to_timestamp_ms(date_time)) { - return dt_to_timestamp_ms(date_time); - } - - /// \ingroup time_structures - /// \brief Alias for dt_to_timestamp_ms function. - /// \copydoc dt_to_timestamp_ms - template - TIME_SHIELD_CONSTEXPR inline ts_t to_ts_ms( - const T& date_time) { - return dt_to_timestamp_ms(date_time); - } - - /// \ingroup time_structures - /// \brief Alias for dt_to_timestamp_ms function. - /// \copydoc dt_to_timestamp_ms - template - TIME_SHIELD_CONSTEXPR inline ts_t ts_ms( - const T& date_time) { - return dt_to_timestamp_ms(date_time); - } - - /// \ingroup time_structures - /// \brief Alias for dt_to_timestamp_ms function. - /// \copydoc dt_to_timestamp_ms - template - TIME_SHIELD_CONSTEXPR inline ts_t timestamp_ms( - const T& date_time) { - return dt_to_timestamp_ms(date_time); - } - -//------------------------------------------------------------------------------ - - /// \brief Alias for tm_to_timestamp_ms function. - /// \copydoc tm_to_timestamp_ms - TIME_SHIELD_CONSTEXPR inline ts_t to_timestamp_ms( - const std::tm *timeinfo) { - return tm_to_timestamp_ms(timeinfo); - } - - /// \brief Alias for tm_to_timestamp_ms function. - /// \copydoc tm_to_timestamp_ms - TIME_SHIELD_CONSTEXPR inline auto tm_to_ts_ms(const std::tm *timeinfo) - -> decltype(tm_to_timestamp_ms(timeinfo)) { - return tm_to_timestamp_ms(timeinfo); - } - - /// \brief Alias for tm_to_timestamp_ms function. - /// \copydoc tm_to_timestamp_ms - TIME_SHIELD_CONSTEXPR inline ts_t to_ts_ms( - const std::tm *timeinfo) { - return tm_to_timestamp_ms(timeinfo); - } - - /// \brief Alias for tm_to_timestamp_ms function. - /// \copydoc tm_to_timestamp_ms - TIME_SHIELD_CONSTEXPR inline ts_t ts_ms( - const std::tm *timeinfo) { - return tm_to_timestamp_ms(timeinfo); - } - - /// \brief Alias for tm_to_timestamp_ms function. - /// \copydoc tm_to_timestamp_ms - TIME_SHIELD_CONSTEXPR inline ts_t timestamp_ms( - const std::tm *timeinfo) { - return tm_to_timestamp_ms(timeinfo); - } - -//------------------------------------------------------------------------------ - - /// \brief Alias for to_ftimestamp - /// - /// This function converts a given date and time to a floating-point timestamp, - /// which is the number of seconds (with fractional milliseconds) since the Unix epoch - /// (January 1, 1970). - /// - /// \tparam T1 The type of the year parameter (default is year_t). - /// \tparam T2 The type of the month, day, hour, minute, and second parameters (default is int). - /// \tparam T3 The type of the millisecond parameter (default is int). - /// \param year The year value. - /// \param month The month value. - /// \param day The day value. - /// \param hour The hour value (default is 0). - /// \param min The minute value (default is 0). - /// \param sec The second value (default is 0). - /// \param ms The millisecond value (default is 0). - /// \return Floating-point timestamp representing the given date and time. - /// \throws std::invalid_argument if the date-time combination is invalid. - /// \see to_ftimestamp - template - TIME_SHIELD_CONSTEXPR fts_t to_fts(T1 year, T2 month, T2 day, T2 hour = 0, T2 min = 0, T2 sec = 0, T3 ms = 0) { - return to_ftimestamp(year, month, day, hour, min, sec, ms); - } - - /// \brief Alias for to_ftimestamp - /// - /// This function converts a given date and time to a floating-point timestamp, - /// which is the number of seconds (with fractional milliseconds) since the Unix epoch - /// (January 1, 1970). - /// - /// \tparam T1 The type of the year parameter (default is year_t). - /// \tparam T2 The type of the month, day, hour, minute, and second parameters (default is int). - /// \tparam T3 The type of the millisecond parameter (default is int). - /// \param year The year value. - /// \param month The month value. - /// \param day The day value. - /// \param hour The hour value (default is 0). - /// \param min The minute value (default is 0). - /// \param sec The second value (default is 0). - /// \param ms The millisecond value (default is 0). - /// \return Floating-point timestamp representing the given date and time. - /// \throws std::invalid_argument if the date-time combination is invalid. - /// \see to_ftimestamp - template - TIME_SHIELD_CONSTEXPR fts_t fts(T1 year, T2 month, T2 day, T2 hour = 0, T2 min = 0, T2 sec = 0, T3 ms = 0) { - return to_ftimestamp(year, month, day, hour, min, sec, ms); - } - - /// \brief Alias for to_ftimestamp - /// - /// This function converts a given date and time to a floating-point timestamp, - /// which is the number of seconds (with fractional milliseconds) since the Unix epoch - /// (January 1, 1970). - /// - /// \tparam T1 The type of the year parameter (default is year_t). - /// \tparam T2 The type of the month, day, hour, minute, and second parameters (default is int). - /// \tparam T3 The type of the millisecond parameter (default is int). - /// \param year The year value. - /// \param month The month value. - /// \param day The day value. - /// \param hour The hour value (default is 0). - /// \param min The minute value (default is 0). - /// \param sec The second value (default is 0). - /// \param ms The millisecond value (default is 0). - /// \return Floating-point timestamp representing the given date and time. - /// \throws std::invalid_argument if the date-time combination is invalid. - /// \see to_ftimestamp - template - TIME_SHIELD_CONSTEXPR fts_t ftimestamp(T1 year, T2 month, T2 day, T2 hour = 0, T2 min = 0, T2 sec = 0, T3 ms = 0) { - return to_ftimestamp(year, month, day, hour, min, sec, ms); - } - -//------------------------------------------------------------------------------ - - /// \ingroup time_structures - /// \brief Alias for dt_to_ftimestamp - /// \copydoc dt_to_ftimestamp - template - TIME_SHIELD_CONSTEXPR fts_t to_ftimestamp(const T& date_time) { - return dt_to_ftimestamp(date_time); - } - - /// \ingroup time_structures - /// \brief Alias for dt_to_ftimestamp - /// \copydoc dt_to_ftimestamp - template - constexpr auto dt_to_fts(const T& date_time) - -> decltype(dt_to_ftimestamp(date_time)) { - return dt_to_ftimestamp(date_time); - } - - /// \ingroup time_structures - /// \brief Alias for dt_to_ftimestamp - /// \copydoc dt_to_ftimestamp - template - TIME_SHIELD_CONSTEXPR fts_t to_fts(const T& date_time) { - return dt_to_ftimestamp(date_time); - } - - /// \ingroup time_structures - /// \brief Alias for dt_to_ftimestamp - /// \copydoc dt_to_ftimestamp - template - TIME_SHIELD_CONSTEXPR fts_t fts(const T& date_time) { - return dt_to_ftimestamp(date_time); - } - - /// \ingroup time_structures - /// \brief Alias for dt_to_ftimestamp - /// \copydoc dt_to_ftimestamp - template - TIME_SHIELD_CONSTEXPR fts_t ftimestamp(const T& date_time) { - return dt_to_ftimestamp(date_time); - } - -//------------------------------------------------------------------------------ - - /// \ingroup time_structures - /// \brief Alias for tm_to_ftimestamp - /// \copydoc tm_to_ftimestamp(const std::tm*) - TIME_SHIELD_CONSTEXPR inline fts_t to_ftimestamp(const std::tm* timeinfo) { - return tm_to_ftimestamp(timeinfo); - } - - /// \ingroup time_structures - /// \brief Alias for tm_to_ftimestamp - /// \copydoc tm_to_ftimestamp(const std::tm*) - TIME_SHIELD_CONSTEXPR inline auto tm_to_fts(const std::tm* timeinfo) - -> decltype(tm_to_ftimestamp(timeinfo)) { - return tm_to_ftimestamp(timeinfo); - } - - /// \ingroup time_structures - /// \brief Alias for tm_to_ftimestamp - /// \copydoc tm_to_ftimestamp(const std::tm*) - TIME_SHIELD_CONSTEXPR inline fts_t to_fts(const std::tm* timeinfo) { - return tm_to_ftimestamp(timeinfo); - } - - /// \ingroup time_structures - /// \brief Alias for tm_to_ftimestamp - /// \copydoc tm_to_ftimestamp(const std::tm*) - TIME_SHIELD_CONSTEXPR inline fts_t fts(const std::tm* timeinfo) { - return tm_to_ftimestamp(timeinfo); - } - - /// \ingroup time_structures - /// \brief Alias for tm_to_ftimestamp - /// \copydoc tm_to_ftimestamp(const std::tm*) - TIME_SHIELD_CONSTEXPR inline fts_t ftimestamp(const std::tm* timeinfo) { - return tm_to_ftimestamp(timeinfo); - } - -//------------------------------------------------------------------------------ - - /// \brief Alias for days_between function. - /// \copydoc days_between - template - TIME_SHIELD_CONSTEXPR T get_days(ts_t start, ts_t stop) noexcept { - return days_between(start, stop); - } - - /// \brief Alias for days_between function. - /// \copydoc days_between - template - TIME_SHIELD_CONSTEXPR T days(ts_t start, ts_t stop) noexcept { - return days_between(start, stop); - } - - /// \brief Alias for days_between function. - /// \copydoc days_between - template - TIME_SHIELD_CONSTEXPR T get_days_difference(ts_t start, ts_t stop) noexcept { - return days_between(start, stop); - } - - /// \brief Alias for days_between function. - /// \copydoc days_between - template - TIME_SHIELD_CONSTEXPR T diff_in_days(ts_t start, ts_t stop) noexcept { - return days_between(start, stop); - } - -//------------------------------------------------------------------------------ - - /// \brief Alias for year_of function. - /// \copydoc year_of - template - TIME_SHIELD_CONSTEXPR inline T year(ts_t ts = time_shield::ts()) { - return year_of(ts); - } - - /// \brief Alias for year_of function. - /// \copydoc year_of - template - TIME_SHIELD_CONSTEXPR inline T to_year(ts_t ts = time_shield::ts()) { - return year_of(ts); - } - -//------------------------------------------------------------------------------ - - /// \brief Alias for year_of_ms function. - /// \copydoc year_of_ms - template - TIME_SHIELD_CONSTEXPR inline T year_ms(ts_ms_t ts_ms = time_shield::ts_ms()) { - return year_of_ms(ts_ms); - } - - /// \brief Alias for year_of_ms function. - /// \copydoc year_of_ms - template - TIME_SHIELD_CONSTEXPR inline T to_year_ms(ts_ms_t ts_ms = time_shield::ts_ms()) { - return year_of_ms(ts_ms); - } - -//------------------------------------------------------------------------------ - - /// \brief Alias for start_of_year function. - /// \copydoc start_of_year - TIME_SHIELD_CONSTEXPR inline ts_t year_start(ts_t ts = time_shield::ts()) { - return start_of_year(ts); - } - - /// \brief Alias for start_of_year function. - /// \copydoc start_of_year - TIME_SHIELD_CONSTEXPR inline ts_t year_begin(ts_t ts = time_shield::ts()) { - return start_of_year(ts); - } - -//------------------------------------------------------------------------------ - - /// \brief Alias for start_of_year_ms function. - /// \copydoc start_of_year_ms - inline ts_ms_t year_start_ms(ts_ms_t ts_ms = time_shield::ts_ms()) { - return start_of_year_ms(ts_ms); - } - - /// \brief Alias for start_of_year_ms function. - /// \copydoc start_of_year_ms - inline ts_ms_t year_begin_ms(ts_ms_t ts_ms = time_shield::ts_ms()) { - return start_of_year_ms(ts_ms); - } - -//------------------------------------------------------------------------------ - - /// \brief Alias for start_of_year_date function. - /// \copydoc start_of_year_date - template - TIME_SHIELD_CONSTEXPR inline ts_t year_start_date(T year) { - return start_of_year_date(year); - } - - /// \brief Alias for start_of_year_date function. - /// \copydoc start_of_year_date - template - TIME_SHIELD_CONSTEXPR inline ts_t year_begin_date(T year) { - return start_of_year_date(year); - } - -//------------------------------------------------------------------------------ - - /// \brief Alias for start_of_year_date_ms function. - /// \copydoc start_of_year_date_ms - template - TIME_SHIELD_CONSTEXPR inline ts_ms_t year_start_date_ms(T year) { - return start_of_year_date_ms(year); - } - - /// \brief Alias for start_of_year_date_ms function. - /// \copydoc start_of_year_date_ms - template - TIME_SHIELD_CONSTEXPR inline ts_ms_t year_begin_date_ms(T year) { - return start_of_year_date_ms(year); - } - -//------------------------------------------------------------------------------ - - /// \brief Alias for end_of_year function. - /// \copydoc end_of_year - TIME_SHIELD_CONSTEXPR inline ts_t year_end(ts_t ts = time_shield::ts()) { - return end_of_year(ts); - } - -//------------------------------------------------------------------------------ - - /// \brief Alias for end_of_year_ms function. - /// \copydoc end_of_year_ms - TIME_SHIELD_CONSTEXPR inline ts_ms_t year_end_ms(ts_ms_t ts_ms = time_shield::ts_ms()) { - return end_of_year_ms(ts_ms); - } - -//------------------------------------------------------------------------------ - - /// \brief Alias for num_days_in_month function. - /// \param year Year as an integer. - /// \param month Month as an integer. - /// \return The number of days in the given month and year. - template - TIME_SHIELD_CONSTEXPR T1 days_in_month(T2 year, T3 month) noexcept { - return num_days_in_month(year, month); - } - -//------------------------------------------------------------------------------ - - /// \brief Alias for num_days_in_month_ts function. - /// \copydoc num_days_in_month_ts - template - TIME_SHIELD_CONSTEXPR T1 num_days_in_month(ts_t ts = time_shield::ts()) noexcept { - return num_days_in_month_ts(ts); - } - - /// \brief Alias for num_days_in_month_ts function. - /// \copydoc num_days_in_month_ts - template - TIME_SHIELD_CONSTEXPR T1 days_in_month(ts_t ts = time_shield::ts()) noexcept { - return num_days_in_month_ts(ts); - } - -//------------------------------------------------------------------------------ - - /// \brief Alias for num_days_in_year function. - /// \copydoc num_days_in_year - template - TIME_SHIELD_CONSTEXPR T1 days_in_year(T2 year) noexcept { - return num_days_in_year(year); - } - -//------------------------------------------------------------------------------ - - /// \brief Alias for num_days_in_year_ts function. - /// \copydoc num_days_in_year_ts - template - TIME_SHIELD_CONSTEXPR T days_in_year_ts(ts_t ts = time_shield::ts()) { - return num_days_in_year_ts(ts); - } - -//------------------------------------------------------------------------------ - - /// \brief Alias for start_of_day function. - /// \copydoc start_of_day - TIME_SHIELD_CONSTEXPR inline ts_t day_start(ts_t ts = time_shield::ts()) noexcept { - return start_of_day(ts); - } - -//------------------------------------------------------------------------------ - - /// \brief Alias for start_of_prev_day function. - /// \copydoc start_of_prev_day - template - TIME_SHIELD_CONSTEXPR ts_t previous_day_start(ts_t ts = time_shield::ts(), T days = 1) noexcept { - return start_of_prev_day(ts, days); - } - -//------------------------------------------------------------------------------ - - /// \brief Alias for start_of_day_sec function. - /// \copydoc start_of_day_sec - TIME_SHIELD_CONSTEXPR inline ts_t day_start_sec(ts_ms_t ts_ms = time_shield::ts_ms()) noexcept { - return start_of_day(ms_to_sec(ts_ms)); - } - - /// \brief Alias for start_of_day_sec function. - /// \copydoc start_of_day_sec - TIME_SHIELD_CONSTEXPR inline ts_t start_day_sec(ts_ms_t ts_ms = time_shield::ts_ms()) noexcept { - return start_of_day(ms_to_sec(ts_ms)); - } - -//------------------------------------------------------------------------------ - - /// \brief Alias for start_of_day_ms function. - /// \copydoc start_of_day_ms - TIME_SHIELD_CONSTEXPR inline ts_ms_t day_start_ms(ts_ms_t ts_ms = time_shield::ts_ms()) noexcept { - return start_of_day_ms(ts_ms); - } - -//------------------------------------------------------------------------------ - - /// \brief Alias for start_of_next_day function. - /// \copydoc start_of_next_day - template - TIME_SHIELD_CONSTEXPR ts_t next_day_start(ts_t ts, T days = 1) noexcept { - return start_of_next_day(ts, days); - } - -//------------------------------------------------------------------------------ - - /// \brief Alias for start_of_next_day_ms function. - /// \copydoc start_of_next_day_ms - template - TIME_SHIELD_CONSTEXPR ts_ms_t next_day_start_ms(ts_ms_t ts_ms, T days = 1) noexcept { - return start_of_next_day_ms(ts_ms, days); - } - -//------------------------------------------------------------------------------ - - /// \brief Alias for end_of_day function. - /// \copydoc end_of_day - TIME_SHIELD_CONSTEXPR inline ts_t day_end(ts_t ts = time_shield::ts()) noexcept { - return end_of_day(ts); - } - -//------------------------------------------------------------------------------ - - /// \brief Alias for end_of_day_sec function. - /// \copydoc end_of_day_sec - TIME_SHIELD_CONSTEXPR inline ts_t day_end_sec(ts_ms_t ts_ms = time_shield::ts_ms()) noexcept { - return end_of_day_sec(ts_ms); - } - -//------------------------------------------------------------------------------ - - /// \brief Alias for end_of_day_ms function. - /// \copydoc end_of_day_ms - TIME_SHIELD_CONSTEXPR inline ts_ms_t day_end_ms(ts_ms_t ts_ms = time_shield::ts_ms()) noexcept { - return end_of_day_ms(ts_ms); - } - -//------------------------------------------------------------------------------ - - /// \brief Alias for day_of_week_date - /// \copydoc day_of_week_date - template - TIME_SHIELD_CONSTEXPR T1 day_of_week(year_t year, int month, int day) { - return day_of_week_date(year, month, day); - } - - /// \brief Alias for day_of_week_date - /// \copydoc day_of_week_date - template - TIME_SHIELD_CONSTEXPR T1 day_of_week(year_t year, Month month, int day) { - return day_of_week_date(year, static_cast(month), day); - } - - /// \brief Alias for day_of_week_date - /// \copydoc day_of_week_date - template - TIME_SHIELD_CONSTEXPR T1 get_weekday(year_t year, int month, int day) { - return day_of_week_date(year, month, day); - } - - /// \brief Alias for day_of_week_date - /// \copydoc day_of_week_date - template - TIME_SHIELD_CONSTEXPR T1 get_weekday(year_t year, Month month, int day) { - return day_of_week_date(year, static_cast(month), day); - } - - /// \brief Alias for day_of_week_date - /// \copydoc day_of_week_date - template - TIME_SHIELD_CONSTEXPR T1 weekday(year_t year, int month, int day) { - return day_of_week_date(year, month, day); - } - - /// \brief Alias for day_of_week_date - /// \copydoc day_of_week_date - template - TIME_SHIELD_CONSTEXPR T1 weekday(year_t year, Month month, int day) { - return day_of_week_date(year, static_cast(month), day); - } - - /// \brief Alias for day_of_week_date - /// \copydoc day_of_week_date - template - TIME_SHIELD_CONSTEXPR T1 dow(year_t year, int month, int day) { - return day_of_week_date(year, month, day); - } - - /// \brief Alias for day_of_week_date - /// \copydoc day_of_week_date - template - TIME_SHIELD_CONSTEXPR T1 dow(year_t year, Month month, int day) { - return day_of_week_date(year, static_cast(month), day); - } - -//------------------------------------------------------------------------------ - - /// \ingroup time_structures - /// \brief Alias for weekday_of_date - /// \copydoc weekday_of_date - template::value, int>::type = 0> - TIME_SHIELD_CONSTEXPR T1 get_dow(const T2& date) { - return weekday_of_date(date); - } - - /// \ingroup time_structures - /// \brief Alias for weekday_of_date - /// \copydoc weekday_of_date - template::value, int>::type = 0> - TIME_SHIELD_CONSTEXPR T1 dow_from_date(const T2& date) { - return weekday_of_date(date); - } - - /// \ingroup time_structures - /// \brief Alias for weekday_of_date - /// \copydoc weekday_of_date - template::value, int>::type = 0> - TIME_SHIELD_CONSTEXPR T1 weekday_of(const T2& date) { - return weekday_of_date(date); - } - - /// \ingroup time_structures - /// \brief Alias for weekday_of_date - /// \copydoc weekday_of_date - template::value, int>::type = 0> - TIME_SHIELD_CONSTEXPR T1 day_of_week_dt(const T2& date) { - return weekday_of_date(date); - } - - /// \ingroup time_structures - /// \brief Alias for weekday_of_date - /// \copydoc weekday_of_date - template::value, int>::type = 0> - TIME_SHIELD_CONSTEXPR T1 day_of_week(const T2& date) { - return weekday_of_date(date); - } - - /// \ingroup time_structures - /// \brief Alias for weekday_of_date - /// \copydoc weekday_of_date - template::value, int>::type = 0> - TIME_SHIELD_CONSTEXPR T1 dow(const T2& date) { - return weekday_of_date(date); - } - - /// \ingroup time_structures - /// \brief Alias for weekday_of_date - /// \copydoc weekday_of_date - template::value, int>::type = 0> - TIME_SHIELD_CONSTEXPR T1 wd(const T2& date) { - return weekday_of_date(date); - } - -//------------------------------------------------------------------------------ - - - /// \brief Alias for weekday_of_ts - /// \copydoc weekday_of_ts - template::value, int>::type = 0> - TIME_SHIELD_CONSTEXPR T day_of_week(U ts) noexcept { - return weekday_of_ts(ts); - } - - /// \brief Alias for weekday_of_ts - /// \copydoc weekday_of_ts - template::value, int>::type = 0> - TIME_SHIELD_CONSTEXPR T dow_ts(U ts) noexcept { - return weekday_of_ts(ts); - } - - /// \brief Alias for weekday_of_ts - /// \copydoc weekday_of_ts - template::value, int>::type = 0> - TIME_SHIELD_CONSTEXPR T get_dow_from_ts(U ts) noexcept { - return weekday_of_ts(ts); - } - -//------------------------------------------------------------------------------ - - /// \brief Alias for weekday_of_ts - /// \copydoc weekday_of_ts - template::value, int>::type = 0> - TIME_SHIELD_CONSTEXPR T wd_ts(U ts) noexcept { - return weekday_of_ts(ts); - } - -//------------------------------------------------------------------------------ - - /// \brief Alias for weekday_of_ts_ms function. - /// \copydoc weekday_of_ts_ms - template - TIME_SHIELD_CONSTEXPR T day_of_week_ms(ts_ms_t ts_ms) { - return weekday_of_ts_ms(ts_ms); - } - - /// \brief Alias for weekday_of_ts_ms function. - /// \copydoc weekday_of_ts_ms - template - TIME_SHIELD_CONSTEXPR T wd_ms(ts_ms_t ts_ms) { - return weekday_of_ts_ms(ts_ms); - } - -//------------------------------------------------------------------------------ - - /// \brief Alias for start_of_month function. - /// \copydoc start_of_month - TIME_SHIELD_CONSTEXPR inline ts_t month_begin(ts_t ts = time_shield::ts()) { - return start_of_month(ts); - } - -//------------------------------------------------------------------------------ - - /// \brief Alias for end_of_month function. - /// \copydoc end_of_month - TIME_SHIELD_CONSTEXPR inline ts_t last_day_of_month(ts_t ts = time_shield::ts()) { - return end_of_month(ts); - } - -//------------------------------------------------------------------------------ - - /// \brief Alias for last_sunday_of_month function. - /// \copydoc last_sunday_of_month - TIME_SHIELD_CONSTEXPR inline ts_t final_sunday_of_month(ts_t ts = time_shield::ts()) { - return last_sunday_of_month(ts); - } - -//------------------------------------------------------------------------------ - - /// \brief Alias for last_sunday_month_day function. - /// \copydoc last_sunday_month_day - template - TIME_SHIELD_CONSTEXPR inline T1 final_sunday_month_day(T2 year, T3 month) { - return last_sunday_month_day(year, month); - } - -//------------------------------------------------------------------------------ - - /// \brief Alias for start_of_hour function. - /// \copydoc start_of_hour - TIME_SHIELD_CONSTEXPR inline ts_t hour_begin(ts_t ts = time_shield::ts()) noexcept { - return start_of_hour(ts); - } - -//------------------------------------------------------------------------------ - - /// \brief Alias for start_of_hour_sec function. - /// \copydoc start_of_hour_sec - TIME_SHIELD_CONSTEXPR inline ts_t hour_begin_sec(ts_ms_t ts_ms = time_shield::ts_ms()) noexcept { - return start_of_hour_sec(ts_ms); - } - -//------------------------------------------------------------------------------ - - /// \brief Alias for start_of_hour_ms function. - /// \copydoc start_of_hour_ms - TIME_SHIELD_CONSTEXPR inline ts_ms_t hour_begin_ms(ts_ms_t ts_ms = time_shield::ts_ms()) noexcept { - return start_of_hour_ms(ts_ms); - } - -//------------------------------------------------------------------------------ - - /// \brief Alias for end_of_hour function. - /// \copydoc end_of_hour - TIME_SHIELD_CONSTEXPR inline ts_t finish_of_hour(ts_t ts = time_shield::ts()) noexcept { - return end_of_hour(ts); - } - -//------------------------------------------------------------------------------ - - /// \brief Alias for end_of_hour_sec function. - /// \copydoc end_of_hour_sec - TIME_SHIELD_CONSTEXPR inline ts_t finish_of_hour_sec(ts_ms_t ts_ms = time_shield::ts_ms()) noexcept { - return end_of_hour_sec(ts_ms); - } - -//------------------------------------------------------------------------------ - - /// \brief Alias for end_of_hour_ms function. - /// \copydoc end_of_hour_ms - TIME_SHIELD_CONSTEXPR inline ts_ms_t finish_of_hour_ms(ts_ms_t ts_ms = time_shield::ts_ms()) noexcept { - return end_of_hour_ms(ts_ms); - } - -//------------------------------------------------------------------------------ - - /// \brief Alias for hour_of_day function. - /// \copydoc hour_of_day - template - TIME_SHIELD_CONSTEXPR T hour_in_day(ts_t ts = time_shield::ts()) noexcept { - return hour_of_day(ts); - } - -//------------------------------------------------------------------------------ - - /// \brief Alias for start_of_week function. - /// \copydoc start_of_week - TIME_SHIELD_CONSTEXPR inline ts_t week_begin(ts_t ts = time_shield::ts()) { - return start_of_week(ts); - } - -//------------------------------------------------------------------------------ - - /// \brief Alias for end_of_week function. - /// \copydoc end_of_week - TIME_SHIELD_CONSTEXPR inline ts_t finish_of_week(ts_t ts = time_shield::ts()) { - return end_of_week(ts); - } - -//------------------------------------------------------------------------------ - - /// \brief Alias for start_of_saturday function. - /// \copydoc start_of_saturday - TIME_SHIELD_CONSTEXPR inline ts_t saturday_begin(ts_t ts = time_shield::ts()) { - return start_of_saturday(ts); - } - -//------------------------------------------------------------------------------ - - /// \brief Alias for start_of_min function. - /// \copydoc start_of_min - TIME_SHIELD_CONSTEXPR inline ts_t min_begin(ts_t ts = time_shield::ts()) noexcept { - return start_of_min(ts); - } - -//------------------------------------------------------------------------------ - - /// \brief Alias for end_of_min function. - /// \copydoc end_of_min - TIME_SHIELD_CONSTEXPR inline ts_t finish_of_min(ts_t ts = time_shield::ts()) noexcept { - return end_of_min(ts); - } - -//------------------------------------------------------------------------------ - - /// \brief Alias for is_workday(ts_t). - /// \copydoc is_workday(ts_t) - TIME_SHIELD_CONSTEXPR inline bool workday(ts_t ts) noexcept { - return is_workday(ts); - } - - /// \brief Alias for is_workday(ts_ms_t). - /// \copydoc is_workday(ts_ms_t) - TIME_SHIELD_CONSTEXPR inline bool workday_ms(ts_ms_t ts_ms) noexcept { - return is_workday_ms(ts_ms); - } - - /// \brief Alias for is_workday(year_t, int, int). - /// \copydoc is_workday(year_t, int, int) - TIME_SHIELD_CONSTEXPR inline bool workday(year_t year, int month, int day) noexcept { - return is_workday(year, month, day); - } - - /// \brief Alias for to_tz_offset. - /// \copydoc to_tz_offset - template - TIME_SHIELD_CONSTEXPR inline tz_t tz_offset(const T& tz) noexcept { - return to_tz_offset(tz); - } - - /// \brief Alias for tz_offset_hm. - /// \copydoc tz_offset_hm - TIME_SHIELD_CONSTEXPR inline tz_t offset_hm(int hour, int min = 0) noexcept { - return tz_offset_hm(hour, min); - } - - /// \brief Alias for is_valid_tz_offset. - /// \copydoc is_valid_tz_offset - TIME_SHIELD_CONSTEXPR inline bool valid_tz_offset(tz_t off) noexcept { - return is_valid_tz_offset(off); - } - -/// \} - -}; // namespace time_shield - -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_TIME_CONVERSION_ALIASES_HPP_INCLUDED +#endif // TIME_SHIELD_HEADER_TIME_CONVERSION_ALIASES_HPP_INCLUDED diff --git a/include/time_shield/time_conversions.hpp b/include/time_shield/time_conversions.hpp index 455daee0..0c7b2d6b 100644 --- a/include/time_shield/time_conversions.hpp +++ b/include/time_shield/time_conversions.hpp @@ -1,33 +1,9 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_TIME_CONVERSIONS_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_TIME_CONVERSIONS_HPP_INCLUDED +#ifndef TIME_SHIELD_HEADER_TIME_CONVERSIONS_HPP_INCLUDED +#define TIME_SHIELD_HEADER_TIME_CONVERSIONS_HPP_INCLUDED -/// \file time_conversions.hpp -/// \brief Umbrella header for time conversion functions. +#include +#include -#include "config.hpp" -#include "types.hpp" -#include "constants.hpp" -#include "enums.hpp" -#include "time_struct.hpp" -#include "date_struct.hpp" -#include "date_time_struct.hpp" -#include "time_zone_struct.hpp" - -#include "time_unit_conversions.hpp" -#include "unix_time_conversions.hpp" -#include "date_conversions.hpp" -#include "date_time_conversions.hpp" -#include "time_zone_offset_conversions.hpp" -#include "workday_conversions.hpp" - -#include "ole_automation_conversions.hpp" -#include "astronomy_conversions.hpp" -#include "time_conversion_aliases.hpp" - -#if defined(TIME_SHIELD_ENABLE_LEGACY_ALIASES) -# include "legacy_aliases.hpp" -#endif - -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_TIME_CONVERSIONS_HPP_INCLUDED +#endif // TIME_SHIELD_HEADER_TIME_CONVERSIONS_HPP_INCLUDED diff --git a/include/time_shield/time_format_parser.hpp b/include/time_shield/time_format_parser.hpp index cfaaab9b..c0746811 100644 --- a/include/time_shield/time_format_parser.hpp +++ b/include/time_shield/time_format_parser.hpp @@ -1,1164 +1,8 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_TIME_FORMAT_PARSER_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_TIME_FORMAT_PARSER_HPP_INCLUDED +#ifndef TIME_SHIELD_HEADER_TIME_FORMAT_PARSER_HPP_INCLUDED +#define TIME_SHIELD_HEADER_TIME_FORMAT_PARSER_HPP_INCLUDED -/// \file time_format_parser.hpp -/// \brief Header file for fast parsing with formatter-compatible custom patterns. +#include -#include "config.hpp" -#include "constants.hpp" -#include "date_time_struct.hpp" -#include "date_time_conversions.hpp" -#include "enums.hpp" -#include "iso_week_conversions.hpp" -#include "time_zone_struct.hpp" -#include "validation.hpp" - -#include -#include -#include -#include -#include - -#if __cplusplus >= 201703L -# include -#endif - -namespace time_shield { - - namespace detail { - namespace format_parse { - - struct FormatParseState { - bool has_tz; - bool has_year; - bool has_century; - bool has_two_digit_year; - bool has_iso_week_year; - bool has_iso_week_two_digit_year; - bool has_iso_week; - bool has_month; - bool has_day; - bool has_day_of_year; - bool has_hour24; - bool has_hour12; - bool has_minute; - bool has_second; - bool has_millisecond; - bool has_meridiem; - bool is_pm; - bool has_weekday; - bool has_iso_weekday; - bool has_unix_seconds; - year_t year; - year_t iso_week_year; - int century; - int two_digit_year; - int iso_week_two_digit_year; - int iso_week; - int month; - int day; - int day_of_year; - int hour24; - int hour12; - int minute; - int second; - int millisecond; - int weekday; - int iso_weekday; - ts_t unix_seconds; - TimeZoneStruct tz; - }; - - inline FormatParseState create_format_parse_state() noexcept { - FormatParseState state; - state.has_tz = false; - state.has_year = false; - state.has_century = false; - state.has_two_digit_year = false; - state.has_iso_week_year = false; - state.has_iso_week_two_digit_year = false; - state.has_iso_week = false; - state.has_month = false; - state.has_day = false; - state.has_day_of_year = false; - state.has_hour24 = false; - state.has_hour12 = false; - state.has_minute = false; - state.has_second = false; - state.has_millisecond = false; - state.has_meridiem = false; - state.is_pm = false; - state.has_weekday = false; - state.has_iso_weekday = false; - state.has_unix_seconds = false; - state.year = 0; - state.iso_week_year = 0; - state.century = 0; - state.two_digit_year = 0; - state.iso_week_two_digit_year = 0; - state.iso_week = 0; - state.month = 0; - state.day = 0; - state.day_of_year = 0; - state.hour24 = 0; - state.hour12 = 0; - state.minute = 0; - state.second = 0; - state.millisecond = 0; - state.weekday = 0; - state.iso_weekday = 0; - state.unix_seconds = 0; - state.tz = create_time_zone_struct(0, 0, true); - return state; - } - - TIME_SHIELD_CONSTEXPR inline bool is_ascii_digit(char c) noexcept { - return c >= '0' && c <= '9'; - } - - inline bool match_literal(const char*& p, const char* end, char expected) noexcept { - if (p >= end || *p != expected) { - return false; - } - ++p; - return true; - } - - inline bool match_literal(const char*& p, const char* end, const char* literal) noexcept { - if (!literal) { - return false; - } - while (*literal) { - if (p >= end || *p != *literal) { - return false; - } - ++p; - ++literal; - } - return true; - } - - inline bool parse_exact_2digits(const char*& p, const char* end, int& out) noexcept { - if (end - p < 2 || !is_ascii_digit(p[0]) || !is_ascii_digit(p[1])) { - return false; - } - out = (p[0] - '0') * 10 + (p[1] - '0'); - p += 2; - return true; - } - - inline bool parse_unsigned_digits( - const char*& p, - const char* end, - int min_digits, - int max_digits, - int64_t& out) noexcept { - const char* start = p; - int digits = 0; - int64_t value = 0; - while (p < end && digits < max_digits && is_ascii_digit(*p)) { - value = value * 10 + static_cast(*p - '0'); - ++p; - ++digits; - } - if (digits < min_digits) { - p = start; - return false; - } - out = value; - return true; - } - - inline bool parse_signed_digits( - const char*& p, - const char* end, - int min_digits, - int max_digits, - int64_t& out) noexcept { - const char* start = p; - bool negative = false; - if (p < end && (*p == '+' || *p == '-')) { - negative = (*p == '-'); - ++p; - } - int64_t value = 0; - if (!parse_unsigned_digits(p, end, min_digits, max_digits, value)) { - p = start; - return false; - } - out = negative ? -value : value; - return true; - } - - inline bool parse_space_padded_2digits(const char*& p, const char* end, int& out) noexcept { - if (end - p < 2) { - return false; - } - if (p[0] == ' ' && is_ascii_digit(p[1])) { - out = p[1] - '0'; - p += 2; - return true; - } - return parse_exact_2digits(p, end, out); - } - - inline bool parse_meridiem(const char*& p, const char* end, bool uppercase, bool& is_pm) noexcept { - if (end - p < 2) { - return false; - } - if (uppercase) { - if (p[0] == 'A' && p[1] == 'M') { - is_pm = false; - } else if (p[0] == 'P' && p[1] == 'M') { - is_pm = true; - } else { - return false; - } - } else { - if (p[0] == 'a' && p[1] == 'm') { - is_pm = false; - } else if (p[0] == 'p' && p[1] == 'm') { - is_pm = true; - } else { - return false; - } - } - p += 2; - return true; - } - - inline bool match_name_token( - const char*& p, - const char* end, - const char* const* names, - std::size_t count, - int index_base, - int& out) noexcept { - for (std::size_t i = 0; i < count; ++i) { - const char* name = names[i]; - const std::size_t len = std::strlen(name); - if (static_cast(end - p) < len) { - continue; - } - bool matched = true; - for (std::size_t k = 0; k < len; ++k) { - if (p[k] != name[k]) { - matched = false; - break; - } - } - if (matched) { - p += static_cast(len); - out = static_cast(i) + index_base; - return true; - } - } - return false; - } - - inline bool parse_month_token(const char*& p, const char* end, FormatType format, int& month) noexcept { - static const char* const uppercase_names[] = { - "JAN", "FEB", "MAR", "APR", "MAY", "JUN", - "JUL", "AUG", "SEP", "OCT", "NOV", "DEC" - }; - static const char* const short_names[] = { - "Jan", "Feb", "Mar", "Apr", "May", "Jun", - "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" - }; - static const char* const full_names[] = { - "January", "February", "March", "April", "May", "June", - "July", "August", "September", "October", "November", "December" - }; - switch (format) { - case UPPERCASE_NAME: - return match_name_token(p, end, uppercase_names, 12, 1, month); - case SHORT_NAME: - return match_name_token(p, end, short_names, 12, 1, month); - case FULL_NAME: - return match_name_token(p, end, full_names, 12, 1, month); - default: - return false; - } - } - - inline bool parse_weekday_token(const char*& p, const char* end, FormatType format, int& weekday) noexcept { - static const char* const uppercase_names[] = { - "SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT" - }; - static const char* const short_names[] = { - "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" - }; - static const char* const full_names[] = { - "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" - }; - switch (format) { - case UPPERCASE_NAME: - return match_name_token(p, end, uppercase_names, 7, 0, weekday); - case SHORT_NAME: - return match_name_token(p, end, short_names, 7, 0, weekday); - case FULL_NAME: - return match_name_token(p, end, full_names, 7, 0, weekday); - default: - return false; - } - } - - /// \brief Parse `%z` timezone token in compact or extended ISO-style form. - /// \details Supported forms are `+HHMM`, `-HHMM`, `+HH:MM`, and `-HH:MM`. - inline bool parse_tz_offset_token(const char*& p, const char* end, TimeZoneStruct& tz) noexcept { - if (end - p < 5 || (*p != '+' && *p != '-')) { - return false; - } - - tz.is_positive = (*p == '+'); - ++p; - if (!parse_exact_2digits(p, end, tz.hour)) { - return false; - } - if (p < end && *p == ':') { - ++p; - } - if (!parse_exact_2digits(p, end, tz.min)) { - return false; - } - return is_valid_time_zone_offset(tz); - } - - inline bool assign_int_field(bool& has_field, int& field, int value) noexcept { - if (has_field && field != value) { - return false; - } - has_field = true; - field = value; - return true; - } - - inline bool assign_year_field(bool& has_field, year_t& field, year_t value) noexcept { - if (has_field && field != value) { - return false; - } - has_field = true; - field = value; - return true; - } - - inline bool has_iso_week_date_fields(const FormatParseState& state) noexcept { - return state.has_iso_week_year - || state.has_iso_week_two_digit_year - || state.has_iso_week; - } - - inline bool has_gregorian_date_fields(const FormatParseState& state) noexcept { - return state.has_year - || state.has_century - || state.has_two_digit_year - || state.has_month - || state.has_day - || state.has_day_of_year; - } - - inline int last_two_digits_of_year(year_t year) noexcept { - const int value = static_cast(year % 100); - return value < 0 ? -value : value; - } - - inline bool resolve_day_of_year(year_t year, int day_of_year, int& month, int& day) noexcept { - if (day_of_year < 1) { - return false; - } - const bool is_leap = is_leap_year_date(year); - const int max_day = is_leap ? 366 : 365; - if (day_of_year > max_day) { - return false; - } - - static const int days_per_month[] = { 31,28,31,30,31,30,31,31,30,31,30,31 }; - int remaining = day_of_year; - month = 1; - while (month <= 12) { - int days = days_per_month[month - 1]; - if (month == 2 && is_leap) { - ++days; - } - if (remaining <= days) { - day = remaining; - return true; - } - remaining -= days; - ++month; - } - return false; - } - - inline int compute_day_of_year(year_t year, int month, int day) noexcept { - static const int day_offsets[] = { 0,31,59,90,120,151,181,212,243,273,304,334 }; - int result = day_offsets[month - 1] + day; - if (month > 2 && is_leap_year_date(year)) { - ++result; - } - return result; - } - - inline bool parse_compact_extended_year(const char*& p, const char* end, year_t& out) noexcept { - const char* start = p; - bool negative = false; - if (p < end && (*p == '+' || *p == '-')) { - negative = (*p == '-'); - ++p; - } - - int64_t head = 0; - if (!parse_unsigned_digits(p, end, 1, 18, head)) { - p = start; - return false; - } - - int64_t year_value = 0; - if (p < end && *p == 'M') { - ++p; - int64_t millennia = 0; - if (!parse_unsigned_digits(p, end, 1, 18, millennia)) { - p = start; - return false; - } - int64_t tail = 0; - if (p < end && *p == 'K') { - ++p; - if (!parse_unsigned_digits(p, end, 3, 3, tail)) { - p = start; - return false; - } - year_value = head * 1000000LL + millennia * 1000LL + tail; - } else { - year_value = head * 1000000LL + millennia; - } - } else if (p < end && *p == 'K') { - ++p; - int64_t tail = 0; - if (!parse_unsigned_digits(p, end, 3, 3, tail)) { - p = start; - return false; - } - year_value = head * 1000LL + tail; - } else { - year_value = head; - } - - out = static_cast(negative ? -year_value : year_value); - return true; - } - - inline bool parse_format_sequence( - const char*& p, - const char* end, - const char* format_data, - std::size_t format_size, - FormatParseState& state) noexcept; - - inline bool parse_format_token( - const char*& p, - const char* end, - char token, - std::size_t repeat_count, - FormatParseState& state) noexcept { - int value = 0; - int64_t wide_value = 0; - switch (token) { - case 'a': - return repeat_count == 1 - && parse_weekday_token(p, end, SHORT_NAME, value) - && assign_int_field(state.has_weekday, state.weekday, value); - case 'A': - return repeat_count == 1 - && parse_weekday_token(p, end, FULL_NAME, value) - && assign_int_field(state.has_weekday, state.weekday, value); - case 'b': - return repeat_count == 1 - && parse_month_token(p, end, SHORT_NAME, value) - && assign_int_field(state.has_month, state.month, value); - case 'B': - return repeat_count == 1 - && parse_month_token(p, end, FULL_NAME, value) - && assign_int_field(state.has_month, state.month, value); - case 'c': - return repeat_count == 1 - && parse_format_sequence(p, end, "%a %b %e %H:%M:%S %Y", 20, state); - case 'C': - if (repeat_count != 1 || !parse_unsigned_digits(p, end, 1, 6, wide_value)) { - return false; - } - return assign_int_field(state.has_century, state.century, static_cast(wide_value)); - case 'd': - if (repeat_count >= 2 || !parse_exact_2digits(p, end, value)) { - return false; - } - return assign_int_field(state.has_day, state.day, value); - case 'D': - if (repeat_count == 1) { - return parse_format_sequence(p, end, "%m/%d/%y", 8, state); - } - if (repeat_count == 2) { - if (!parse_exact_2digits(p, end, value)) { - return false; - } - return assign_int_field(state.has_day, state.day, value); - } - return false; - case 'e': - if (repeat_count != 1 || !parse_space_padded_2digits(p, end, value)) { - return false; - } - return assign_int_field(state.has_day, state.day, value); - case 'F': - return repeat_count == 1 - && parse_format_sequence(p, end, "%Y-%m-%d", 8, state); - case 'g': - if (repeat_count != 1 || !parse_unsigned_digits(p, end, 2, 2, wide_value)) { - return false; - } - return assign_int_field(state.has_iso_week_two_digit_year, state.iso_week_two_digit_year, static_cast(wide_value)); - case 'G': - if (repeat_count != 1 || !parse_signed_digits(p, end, 1, 18, wide_value)) { - return false; - } - return assign_year_field(state.has_iso_week_year, state.iso_week_year, static_cast(wide_value)); - case 'H': - if (repeat_count > 2 || !parse_exact_2digits(p, end, value)) { - return false; - } - return assign_int_field(state.has_hour24, state.hour24, value); - case 'h': - if (repeat_count == 2) { - if (!parse_exact_2digits(p, end, value)) { - return false; - } - return assign_int_field(state.has_hour24, state.hour24, value); - } - return repeat_count == 1 - && parse_month_token(p, end, SHORT_NAME, value) - && assign_int_field(state.has_month, state.month, value); - case 'I': - if (repeat_count != 1 || !parse_exact_2digits(p, end, value)) { - return false; - } - return assign_int_field(state.has_hour12, state.hour12, value); - case 'j': - if (repeat_count != 1 || !parse_unsigned_digits(p, end, 3, 3, wide_value)) { - return false; - } - return assign_int_field(state.has_day_of_year, state.day_of_year, static_cast(wide_value)); - case 'k': - if (repeat_count != 1 || !parse_space_padded_2digits(p, end, value)) { - return false; - } - return assign_int_field(state.has_hour24, state.hour24, value); - case 'l': - if (repeat_count != 1 || !parse_space_padded_2digits(p, end, value)) { - return false; - } - return assign_int_field(state.has_hour12, state.hour12, value); - case 'm': - if (repeat_count == 1) { - if (!parse_exact_2digits(p, end, value)) { - return false; - } - return assign_int_field(state.has_month, state.month, value); - } - if (repeat_count == 2) { - if (!parse_exact_2digits(p, end, value)) { - return false; - } - return assign_int_field(state.has_minute, state.minute, value); - } - return false; - case 'M': - if (repeat_count == 1) { - if (!parse_exact_2digits(p, end, value)) { - return false; - } - return assign_int_field(state.has_minute, state.minute, value); - } - if (repeat_count == 2) { - if (!parse_exact_2digits(p, end, value)) { - return false; - } - return assign_int_field(state.has_month, state.month, value); - } - if (repeat_count == 3) { - return parse_month_token(p, end, UPPERCASE_NAME, value) - && assign_int_field(state.has_month, state.month, value); - } - return false; - case 'n': - return repeat_count == 1 && match_literal(p, end, '\n'); - case 'p': - if (repeat_count != 1 || !parse_meridiem(p, end, true, state.is_pm)) { - return false; - } - state.has_meridiem = true; - return true; - case 'P': - if (repeat_count != 1 || !parse_meridiem(p, end, false, state.is_pm)) { - return false; - } - state.has_meridiem = true; - return true; - case 'r': - return repeat_count == 1 - && parse_format_sequence(p, end, "%I:%M:%S %p", 11, state); - case 'R': - return repeat_count == 1 - && parse_format_sequence(p, end, "%H:%M", 5, state); - case 's': - if (repeat_count == 1) { - if (!parse_signed_digits(p, end, 1, 18, wide_value)) { - return false; - } - state.has_unix_seconds = true; - state.unix_seconds = static_cast(wide_value); - return true; - } - if (repeat_count == 3) { - if (!parse_unsigned_digits(p, end, 1, 3, wide_value)) { - return false; - } - return assign_int_field(state.has_millisecond, state.millisecond, static_cast(wide_value)); - } - return false; - case 'S': - if (repeat_count <= 2) { - if (!parse_exact_2digits(p, end, value)) { - return false; - } - return assign_int_field(state.has_second, state.second, value); - } - if (repeat_count == 3) { - if (!parse_unsigned_digits(p, end, 1, 3, wide_value)) { - return false; - } - return assign_int_field(state.has_millisecond, state.millisecond, static_cast(wide_value)); - } - return false; - case 't': - return repeat_count == 1 && match_literal(p, end, '\t'); - case 'T': - return repeat_count == 1 - && parse_format_sequence(p, end, "%H:%M:%S", 8, state); - case 'u': - if (repeat_count != 1 || !parse_unsigned_digits(p, end, 1, 1, wide_value)) { - return false; - } - return assign_int_field(state.has_iso_weekday, state.iso_weekday, static_cast(wide_value)); - case 'w': - if (repeat_count == 1) { - if (!parse_unsigned_digits(p, end, 1, 1, wide_value)) { - return false; - } - return assign_int_field(state.has_weekday, state.weekday, static_cast(wide_value)); - } - if (repeat_count == 3) { - return parse_weekday_token(p, end, SHORT_NAME, value) - && assign_int_field(state.has_weekday, state.weekday, value); - } - return false; - case 'W': - if (repeat_count == 3) { - return parse_weekday_token(p, end, UPPERCASE_NAME, value) - && assign_int_field(state.has_weekday, state.weekday, value); - } - return false; - case 'V': - if (repeat_count != 1 || !parse_exact_2digits(p, end, value)) { - return false; - } - return assign_int_field(state.has_iso_week, state.iso_week, value); - case 'y': - if (repeat_count != 1 || !parse_unsigned_digits(p, end, 1, 2, wide_value)) { - return false; - } - return assign_int_field(state.has_two_digit_year, state.two_digit_year, static_cast(wide_value)); - case 'Y': - if (repeat_count == 1) { - if (!parse_signed_digits(p, end, 1, 18, wide_value)) { - return false; - } - return assign_year_field(state.has_year, state.year, static_cast(wide_value)); - } - if (repeat_count == 2) { - if (!parse_unsigned_digits(p, end, 2, 2, wide_value)) { - return false; - } - return assign_int_field(state.has_two_digit_year, state.two_digit_year, static_cast(wide_value)); - } - if (repeat_count == 4) { - if (!parse_signed_digits(p, end, 4, 4, wide_value)) { - return false; - } - return assign_year_field(state.has_year, state.year, static_cast(wide_value)); - } - if (repeat_count == 6) { - year_t parsed_year = 0; - if (!parse_compact_extended_year(p, end, parsed_year)) { - return false; - } - return assign_year_field(state.has_year, state.year, parsed_year); - } - return false; - case 'z': - if (repeat_count != 1 || !parse_tz_offset_token(p, end, state.tz)) { - return false; - } - state.has_tz = true; - return true; - case 'Z': - if (repeat_count != 1 || !match_literal(p, end, "UTC")) { - return false; - } - state.tz = create_time_zone_struct(0, 0, true); - state.has_tz = true; - return true; - default: - return false; - } - } - - inline bool parse_format_sequence( - const char*& p, - const char* end, - const char* format_data, - std::size_t format_size, - FormatParseState& state) noexcept { - bool is_command = false; - std::size_t repeat_count = 0; - char last_char = 0; - - for (std::size_t i = 0; i < format_size; ++i) { - const char current_char = format_data[i]; - if (!is_command) { - if (current_char == '%') { - ++repeat_count; - if (repeat_count == 2) { - if (!match_literal(p, end, '%')) { - return false; - } - repeat_count = 0; - } - continue; - } - if (!repeat_count) { - if (!match_literal(p, end, current_char)) { - return false; - } - continue; - } - last_char = current_char; - is_command = true; - continue; - } - if (last_char == current_char) { - ++repeat_count; - continue; - } - if (!parse_format_token(p, end, last_char, repeat_count, state)) { - return false; - } - repeat_count = 0; - is_command = false; - --i; - } - - if (is_command) { - if (!parse_format_token(p, end, last_char, repeat_count, state)) { - return false; - } - } - return true; - } - - inline bool validate_weekday_constraints(const FormatParseState& state, const DateTimeStruct& dt) noexcept { - if (state.has_weekday && day_of_week_date(dt.year, dt.mon, dt.day) != state.weekday) { - return false; - } - if (has_iso_week_date_fields(state) || state.has_iso_weekday) { - const IsoWeekDateStruct iso_week = to_iso_week_date(dt.year, dt.mon, dt.day); - if (state.has_iso_week_year && iso_week.year != state.iso_week_year) { - return false; - } - if (state.has_iso_week_two_digit_year && last_two_digits_of_year(iso_week.year) != state.iso_week_two_digit_year) { - return false; - } - if (state.has_iso_week && iso_week.week != state.iso_week) { - return false; - } - if (state.has_iso_weekday && iso_week.weekday != state.iso_weekday) { - return false; - } - } - return true; - } - - inline bool resolve_time_fields(const FormatParseState& state, int& hour, int& minute, int& second, int& millisecond) noexcept { - hour = 0; - if (state.has_hour24) { - hour = state.hour24; - if (state.has_meridiem && state.has_hour12) { - int expected = state.hour12 % 12; - if (state.is_pm) { - expected += 12; - } - if (expected != hour) { - return false; - } - } - } else if (state.has_hour12) { - if (!state.has_meridiem || state.hour12 < 1 || state.hour12 > 12) { - return false; - } - hour = state.hour12 % 12; - if (state.is_pm) { - hour += 12; - } - } - - minute = state.has_minute ? state.minute : 0; - second = state.has_second ? state.second : 0; - millisecond = state.has_millisecond ? state.millisecond : 0; - return true; - } - - inline bool finalize_calendar_state(FormatParseState& state, DateTimeStruct& out_dt, TimeZoneStruct& out_tz) noexcept { - out_tz = state.has_tz ? state.tz : create_time_zone_struct(0, 0, true); - - if (has_iso_week_date_fields(state)) { - if (has_gregorian_date_fields(state)) { - return false; - } - - year_t iso_week_year = 0; - if (state.has_iso_week_year) { - iso_week_year = state.iso_week_year; - if (state.has_iso_week_two_digit_year - && last_two_digits_of_year(iso_week_year) != state.iso_week_two_digit_year) { - return false; - } - } else if (state.has_iso_week_two_digit_year) { - iso_week_year = static_cast(state.iso_week_two_digit_year); - } else { - return false; - } - - if (!state.has_iso_week) { - return false; - } - - const IsoWeekDateStruct iso_week_date = create_iso_week_date_struct( - iso_week_year, - state.iso_week, - state.has_iso_weekday ? state.iso_weekday : 1); - if (!is_valid_iso_week_date(iso_week_date.year, iso_week_date.week, iso_week_date.weekday)) { - return false; - } - - const DateStruct calendar_date = iso_week_date_to_date(iso_week_date); - int hour = 0; - int minute = 0; - int second = 0; - int millisecond = 0; - if (!resolve_time_fields(state, hour, minute, second, millisecond)) { - return false; - } - - out_dt = create_date_time_struct( - calendar_date.year, - calendar_date.mon, - calendar_date.day, - hour, - minute, - second, - millisecond); - if (!is_valid_date_time(out_dt)) { - return false; - } - return validate_weekday_constraints(state, out_dt); - } - - year_t year = 0; - if (state.has_year) { - year = state.year; - if (state.has_century && year / 100 != state.century) { - return false; - } - const int yy = static_cast(year >= 0 ? (year % 100) : -(year % 100)); - if (state.has_two_digit_year && yy != state.two_digit_year) { - return false; - } - } else if (state.has_century || state.has_two_digit_year) { - year = static_cast((state.has_century ? state.century : 0) * 100 - + (state.has_two_digit_year ? state.two_digit_year : 0)); - } else { - return false; - } - - int month = state.has_month ? state.month : 0; - int day = state.has_day ? state.day : 0; - if (state.has_day_of_year) { - int resolved_month = 0; - int resolved_day = 0; - if (!resolve_day_of_year(year, state.day_of_year, resolved_month, resolved_day)) { - return false; - } - if (state.has_month && state.month != resolved_month) { - return false; - } - if (state.has_day && state.day != resolved_day) { - return false; - } - month = resolved_month; - day = resolved_day; - } - if (month == 0 || day == 0) { - return false; - } - - int hour = 0; - int minute = 0; - int second = 0; - int millisecond = 0; - if (!resolve_time_fields(state, hour, minute, second, millisecond)) { - return false; - } - - out_dt = create_date_time_struct( - year, - month, - day, - hour, - minute, - second, - millisecond); - if (!is_valid_date_time(out_dt)) { - return false; - } - return validate_weekday_constraints(state, out_dt); - } - - inline bool finalize_format_parse_state( - FormatParseState& state, - DateTimeStruct& out_dt, - TimeZoneStruct& out_tz) noexcept { - if (has_iso_week_date_fields(state) && has_gregorian_date_fields(state)) { - return false; - } - - if (state.has_unix_seconds) { - out_tz = state.has_tz ? state.tz : create_time_zone_struct(0, 0, true); - const tz_t offset = time_zone_struct_to_offset(out_tz); - const ts_t local_ts = state.has_tz ? static_cast(state.unix_seconds + offset) - : state.unix_seconds; - out_dt = to_date_time(local_ts); - if (state.has_millisecond) { - out_dt.ms = state.millisecond; - } - - if (state.has_year && out_dt.year != state.year) return false; - if (state.has_month && out_dt.mon != state.month) return false; - if (state.has_day && out_dt.day != state.day) return false; - if (state.has_hour24 && out_dt.hour != state.hour24) return false; - if (state.has_minute && out_dt.min != state.minute) return false; - if (state.has_second && out_dt.sec != state.second) return false; - if (state.has_millisecond && out_dt.ms != state.millisecond) return false; - if (state.has_day_of_year && compute_day_of_year(out_dt.year, out_dt.mon, out_dt.day) != state.day_of_year) return false; - return validate_weekday_constraints(state, out_dt); - } - - return finalize_calendar_state(state, out_dt, out_tz); - } - - inline bool try_parse_format_core( - const char* data, - std::size_t length, - const char* format, - std::size_t format_length, - DateTimeStruct& out_dt, - TimeZoneStruct& out_tz) noexcept { - if (!data || !format) { - return false; - } - FormatParseState state = create_format_parse_state(); - const char* p = data; - const char* end = data + length; - if (!parse_format_sequence(p, end, format, format_length, state)) { - return false; - } - if (p != end) { - return false; - } - return finalize_format_parse_state(state, out_dt, out_tz); - } - - } // namespace format_parse - } // namespace detail - - /// \ingroup time_parsing - /// \brief Parse input using formatter-compatible custom pattern. - /// \details ISO week-based tokens `%G`, `%g`, `%V`, and `%u` follow the - /// same grammar as formatter output. Formats using ISO week-based year/week - /// tokens do not mix with Gregorian `%Y` / `%m` / `%d` date tokens. - inline bool try_parse_format( - const char* data, - std::size_t length, - const char* format, - std::size_t format_length, - DateTimeStruct& out_dt, - TimeZoneStruct& out_tz) noexcept { - return detail::format_parse::try_parse_format_core(data, length, format, format_length, out_dt, out_tz); - } - - /// \ingroup time_parsing - /// \brief Parse input using formatter-compatible custom pattern and convert to UTC seconds. - inline bool try_parse_format_ts( - const char* data, - std::size_t length, - const char* format, - std::size_t format_length, - ts_t& out_ts) noexcept { - DateTimeStruct dt; - TimeZoneStruct tz; - if (!try_parse_format(data, length, format, format_length, dt, tz)) { - out_ts = 0; - return false; - } - try { - out_ts = dt_to_timestamp(dt) - time_zone_struct_to_offset(tz); - return true; - } catch (...) { - out_ts = 0; - return false; - } - } - - /// \ingroup time_parsing - /// \brief Parse input using formatter-compatible custom pattern and convert to UTC milliseconds. - inline bool try_parse_format_ts_ms( - const char* data, - std::size_t length, - const char* format, - std::size_t format_length, - ts_ms_t& out_ts) noexcept { - DateTimeStruct dt; - TimeZoneStruct tz; - if (!try_parse_format(data, length, format, format_length, dt, tz)) { - out_ts = 0; - return false; - } - try { - out_ts = dt_to_timestamp_ms(dt) - sec_to_ms(time_zone_struct_to_offset(tz)); - return true; - } catch (...) { - out_ts = 0; - return false; - } - } - - /// \ingroup time_parsing - /// \brief Parse std::string using formatter-compatible custom pattern. - inline bool try_parse_format( - const std::string& data, - const std::string& format, - DateTimeStruct& out_dt, - TimeZoneStruct& out_tz) noexcept { - return try_parse_format(data.data(), data.size(), format.data(), format.size(), out_dt, out_tz); - } - - /// \ingroup time_parsing - /// \brief Parse null-terminated strings using formatter-compatible custom pattern. - inline bool try_parse_format( - const char* data, - const char* format, - DateTimeStruct& out_dt, - TimeZoneStruct& out_tz) noexcept { - if (!data || !format) { - return false; - } - return try_parse_format(data, std::strlen(data), format, std::strlen(format), out_dt, out_tz); - } - - /// \ingroup time_parsing - /// \brief Parse std::string using formatter-compatible custom pattern and convert to UTC seconds. - inline bool try_parse_format_ts( - const std::string& data, - const std::string& format, - ts_t& out_ts) noexcept { - return try_parse_format_ts(data.data(), data.size(), format.data(), format.size(), out_ts); - } - - /// \ingroup time_parsing - /// \brief Parse null-terminated strings using custom format and convert to UTC seconds. - inline bool try_parse_format_ts( - const char* data, - const char* format, - ts_t& out_ts) noexcept { - if (!data || !format) { - out_ts = 0; - return false; - } - return try_parse_format_ts(data, std::strlen(data), format, std::strlen(format), out_ts); - } - - /// \ingroup time_parsing - /// \brief Parse std::string using formatter-compatible custom pattern and convert to UTC milliseconds. - inline bool try_parse_format_ts_ms( - const std::string& data, - const std::string& format, - ts_ms_t& out_ts) noexcept { - return try_parse_format_ts_ms(data.data(), data.size(), format.data(), format.size(), out_ts); - } - - /// \ingroup time_parsing - /// \brief Parse null-terminated strings using custom format and convert to UTC milliseconds. - inline bool try_parse_format_ts_ms( - const char* data, - const char* format, - ts_ms_t& out_ts) noexcept { - if (!data || !format) { - out_ts = 0; - return false; - } - return try_parse_format_ts_ms(data, std::strlen(data), format, std::strlen(format), out_ts); - } - -#if __cplusplus >= 201703L - /// \ingroup time_parsing - /// \brief Parse std::string_view using formatter-compatible custom pattern. - inline bool try_parse_format( - std::string_view data, - std::string_view format, - DateTimeStruct& out_dt, - TimeZoneStruct& out_tz) noexcept { - return try_parse_format(data.data(), data.size(), format.data(), format.size(), out_dt, out_tz); - } - - /// \ingroup time_parsing - /// \brief Parse std::string_view using formatter-compatible custom pattern and convert to UTC seconds. - inline bool try_parse_format_ts( - std::string_view data, - std::string_view format, - ts_t& out_ts) noexcept { - return try_parse_format_ts(data.data(), data.size(), format.data(), format.size(), out_ts); - } - - /// \ingroup time_parsing - /// \brief Parse std::string_view using formatter-compatible custom pattern and convert to UTC milliseconds. - inline bool try_parse_format_ts_ms( - std::string_view data, - std::string_view format, - ts_ms_t& out_ts) noexcept { - return try_parse_format_ts_ms(data.data(), data.size(), format.data(), format.size(), out_ts); - } -#endif - -} // namespace time_shield - -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_TIME_FORMAT_PARSER_HPP_INCLUDED +#endif // TIME_SHIELD_HEADER_TIME_FORMAT_PARSER_HPP_INCLUDED diff --git a/include/time_shield/time_formatting.hpp b/include/time_shield/time_formatting.hpp index 2c1037b1..8a787716 100644 --- a/include/time_shield/time_formatting.hpp +++ b/include/time_shield/time_formatting.hpp @@ -1,1009 +1,8 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_TIME_FORMATTING_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_TIME_FORMATTING_HPP_INCLUDED +#ifndef TIME_SHIELD_HEADER_TIME_FORMATTING_HPP_INCLUDED +#define TIME_SHIELD_HEADER_TIME_FORMATTING_HPP_INCLUDED -/// \file time_formatting.hpp -/// \brief Header file for time formatting utilities. -/// -/// This file contains functions for converting timestamps to formatted strings. -/// It provides utilities for custom formatting based on user-defined patterns -/// and for standard date-time string representations. +#include -#include "config.hpp" -#include "date_time_struct.hpp" -#include "iso_week_conversions.hpp" -#include "time_zone_struct.hpp" -#include "time_conversions.hpp" - -#include - -namespace time_shield { - -/// \ingroup time_formatting -/// \{ - - inline void process_format_impl( - char last_char, - size_t repeat_count, - ts_t ts, - tz_t utc_offset, - const DateTimeStruct& dt, - std::string& result) { - switch (last_char) { - case 'a': - if (repeat_count > 1) break; - result += to_str(day_of_week(dt.year, dt.mon, dt.day), FormatType::SHORT_NAME); - break; - case 'A': - if (repeat_count > 1) break; - result += to_str(day_of_week(dt.year, dt.mon, dt.day), FormatType::FULL_NAME); - break; - case 'I': - if (repeat_count == 1) { - char buffer[4] = {0}; - snprintf(buffer, sizeof(buffer), "%.2d", hour24_to_12(dt.hour)); - result += std::string(buffer); - } - break; - case 'H': - if (repeat_count <= 2) { - char buffer[4] = {0}; - snprintf(buffer, sizeof(buffer),"%.2d", dt.hour); - result += std::string(buffer); - } - break; - case 'h': - if (repeat_count == 2) { - char buffer[4] = {0}; - snprintf(buffer, sizeof(buffer),"%.2d", dt.hour); - result += std::string(buffer); - break; - } - - // fallthrough - case 'b': - // %h: Equivalent to %b - if (repeat_count > 1) break; - result += to_str(static_cast(dt.mon), FormatType::SHORT_NAME); - break; - case 'B': - if (repeat_count > 1) break; - result += to_str(static_cast(dt.mon), FormatType::FULL_NAME); - break; - case 'c': - // Preferred date and time representation for the current locale. - // %a %b %e %H:%M:%S %Y - if (repeat_count <= 1){ - char buffer[16]; - result += to_str(day_of_week(dt.year, dt.mon, dt.day), FormatType::SHORT_NAME); - result += " "; - result += to_str(static_cast(dt.mon), FormatType::SHORT_NAME); - result += " "; - // added %e - std::fill(buffer, buffer + sizeof(buffer), '\0'); - snprintf(buffer, sizeof(buffer),"%2d ", dt.day); - result += std::string(buffer); - // added %H:%M:%S - std::fill(buffer, buffer + sizeof(buffer), '\0'); - snprintf(buffer, sizeof(buffer), "%.2d:%.2d:%.2d ", dt.hour, dt.min, dt.sec); - result += std::string(buffer); - // added %Y - result += std::to_string(dt.year); - } - break; - case 'C': - if (repeat_count > 1) break; - result += std::to_string(dt.year/100); - break; - case 'd': - if (repeat_count < 2) { - char buffer[4] = {0}; - snprintf(buffer, sizeof(buffer),"%.2d", dt.day); - result += std::string(buffer); - } - break; - case 'D': - if (repeat_count == 1) { - // %m/%d/%y - char buffer[16] = {0}; - snprintf(buffer, sizeof(buffer), "%.2d/%.2d/%.2d", dt.mon, dt.day, (int)(dt.year % 100LL)); - result += std::string(buffer); - } else - if (repeat_count == 2) { - char buffer[4] = {0}; - snprintf(buffer, sizeof(buffer),"%.2d", dt.day); - result += std::string(buffer); - } - break; - case 'e': - if (repeat_count == 1) { - char buffer[4] = {0}; - snprintf(buffer, sizeof(buffer),"%2d", dt.day); - result += std::string(buffer); - } - break; - case 'E': - // %E: Modifier for alternative ("era-based") format. - // https://help.hcltechsw.com/onedb/1.0.0.1/gug/ids_gug_086.html#ids_gug_086 - break; - case 'F': - if (repeat_count == 1) { - // %Y-%m-%d ISO 8601 date format - char buffer[32] = {0}; - if (dt.year <= 9999 && dt.year >= 0) { - snprintf(buffer, sizeof(buffer), "%.4d-%.2d-%.2d", (int)dt.year, dt.mon, dt.day); - } else - if (dt.year < 0) { - snprintf(buffer, sizeof(buffer), "-%" PRId64 "-%.2d-%.2d", dt.year, dt.mon, dt.day); - } else { - snprintf(buffer, sizeof(buffer), "+%" PRId64 "-%.2d-%.2d", dt.year, dt.mon, dt.day); - } - result += std::string(buffer); - } - break; - case 'g': - // ISO 8601 week-based year without century (2-digit year). - if (repeat_count == 1) { - const IsoWeekDateStruct iso_week = to_iso_week_date(dt.year, dt.mon, dt.day); - const int two_digit_year = static_cast(iso_week.year % 100); - char buffer[4] = {0}; - snprintf(buffer, sizeof(buffer), "%.2d", two_digit_year < 0 ? -two_digit_year : two_digit_year); - result += std::string(buffer); - } - break; - case 'G': - // ISO 8601 week-based year with century as a decimal number. - if (repeat_count == 1) { - const IsoWeekDateStruct iso_week = to_iso_week_date(dt.year, dt.mon, dt.day); - result += std::to_string(iso_week.year); - } - break; - case 'j': - if (repeat_count == 1) { - char buffer[4] = {0}; - snprintf(buffer, sizeof(buffer), "%.3d", day_of_year(ts)); - result += std::string(buffer); - } - break; - case 'k': - if (repeat_count == 1) { - char buffer[4] = {0}; - snprintf(buffer, sizeof(buffer), "%2d", dt.hour); - result += std::string(buffer); - } - break; - case 'l': - if (repeat_count == 1) { - char buffer[4] = {0}; - snprintf(buffer, sizeof(buffer), "%2d", hour24_to_12(dt.hour)); - result += std::string(buffer); - } - break; - case 'm': - if (repeat_count == 1) { - char buffer[4] = {0}; - snprintf(buffer, sizeof(buffer), "%.2d", dt.mon); - result += std::string(buffer); - } else - if (repeat_count == 2) { - char buffer[4] = {0}; - snprintf(buffer, sizeof(buffer), "%.2d", dt.min); - result += std::string(buffer); - } - break; - case 'M': - if (repeat_count == 1) { - char buffer[4] = {0}; - snprintf(buffer, sizeof(buffer), "%.2d", dt.min); - result += std::string(buffer); - } else - if (repeat_count == 2) { - char buffer[4] = {0}; - snprintf(buffer, sizeof(buffer), "%.2d", dt.mon); - result += std::string(buffer); - } else - if (repeat_count == 3) { - result += to_str(static_cast(dt.mon), FormatType::UPPERCASE_NAME); - } - break; - case 'n': - result += "\n"; - break; - case 'O': - // Modifier for using alternative numeric symbols. - break; - case 'p': - if (dt.hour < 12) result += "AM"; - else result += "PM"; - break; - case 'P': - if (dt.hour < 12) result += "am"; - else result += "pm"; - break; - case 'r': - if (repeat_count == 1) { - char buffer[16] = {0}; - if (dt.hour < 12) snprintf(buffer, sizeof(buffer), "%.2d:%.2d:%.2d AM", hour24_to_12(dt.hour), dt.min, dt.sec); - else snprintf(buffer, sizeof(buffer), "%.2d:%.2d:%.2d PM", hour24_to_12(dt.hour), dt.min, dt.sec); - result += std::string(buffer); - break; - } - break; - case 'R': - // %H:%M - if (repeat_count == 1) { - char buffer[8] = {0}; - snprintf(buffer, sizeof(buffer), "%.2d:%.2d", dt.hour, dt.min); - result += std::string(buffer); - } - break; - case 's': - if (repeat_count == 1) { - result += std::to_string(ts); - break; - } - if (repeat_count == 3) { - result += std::to_string(dt.ms); - break; - } - // to '%ss' - - // fallthrough - case 'S': - if (repeat_count <= 2) { - char buffer[4] = {0}; - snprintf(buffer, sizeof(buffer), "%.2d", dt.sec); - result += std::string(buffer); - } - if (repeat_count == 3) { - result += std::to_string(dt.ms); - break; - } - break; - case 't': - if (repeat_count > 1) break; - result += "\t"; - break; - case 'T': - // %H:%M:%S - if (repeat_count == 1) { - char buffer[16] = {0}; - snprintf(buffer, sizeof(buffer), "%.2d:%.2d:%.2d", dt.hour, dt.min, dt.sec); - result += std::string(buffer); - } - break; - case 'u': - if (repeat_count == 1) { - // Day of the week as a decimal number (1 to 7, Monday being 1). - int dw = day_of_week(dt.year, dt.mon, dt.day); - if (dw == 0) dw = 7; - result += std::to_string(dw); - } - break; - case 'U': - // Week number of the current year (00 to 53, starting with the first Sunday as week 01). - break; - case 'V': - // ISO 8601 week number of the current year (01 to 53, with specific rules). - if (repeat_count == 1) { - const IsoWeekDateStruct iso_week = to_iso_week_date(dt.year, dt.mon, dt.day); - char buffer[4] = {0}; - snprintf(buffer, sizeof(buffer), "%.2d", iso_week.week); - result += std::string(buffer); - } - break; - case 'w': - // Day of the week as a decimal number (0 to 6, Sunday being 0). - if (repeat_count == 1) { - result += std::to_string(day_of_week(dt.year, dt.mon, dt.day)); - } else - if (repeat_count == 3) { - result += to_str(day_of_week(dt.year, dt.mon, dt.day), FormatType::SHORT_NAME); - } - break; - case 'W': - // Week number of the current year (00 to 53, starting with the first Monday as week 01). - if (repeat_count == 3) { - result += to_str(day_of_week(dt.year, dt.mon, dt.day), FormatType::UPPERCASE_NAME); - } - break; - case 'x': - // Preferred date representation for the current locale without the time. - break; - case 'X': - // Preferred time representation for the current locale without the date. - break; - case 'y': - if (repeat_count == 1) { - result += std::to_string(dt.year % 100); - } - break; - case 'Y': - if (repeat_count == 1) { - result += std::to_string(dt.year); - } else - if (repeat_count == 6) { - char buffer[32] = {0}; - const int64_t mega_years = dt.year / 1000000; - const int64_t millennia = (dt.year - mega_years * 1000000) / 1000; - const int64_t centuries = dt.year - mega_years * 1000000 - millennia * 1000; - if (mega_years) { - if (millennia) { - snprintf( - buffer, - sizeof(buffer), - "%" PRId64 "M%" PRId64 "K%.3" PRId64, - mega_years, - static_cast(std::abs(millennia)), - static_cast(std::abs(centuries))); - } else { - snprintf( - buffer, - sizeof(buffer), - "%" PRId64 "M%.3" PRId64, - mega_years, - static_cast(std::abs(centuries))); - } - } else - if (millennia) { - snprintf( - buffer, - sizeof(buffer), - "%" PRId64 "K%.3" PRId64, - millennia, - static_cast(std::abs(centuries))); - } else { - snprintf(buffer, sizeof(buffer), "%.4" PRId64, dt.year); - } - result += std::string(buffer); - } else - if (repeat_count == 4) { - char buffer[8] = {0}; - snprintf(buffer, sizeof(buffer), "%.4d", (int)(dt.year % 10000)); - result += std::string(buffer); - } else - if (repeat_count == 2) { - char buffer[8] = {0}; - snprintf(buffer, sizeof(buffer), "%.2d", (int)(dt.year % 100)); - result += std::string(buffer); - } - break; - case 'z': - // +hhmm or -hhmm numeric timezone offset from UTC. - if (repeat_count == 1) { - TimeZoneStruct tz = to_time_zone_struct(utc_offset); - char buffer[16] = {0}; - if (tz.is_positive) snprintf(buffer, sizeof(buffer), "+%.2d%.2d", tz.hour, tz.min); - else snprintf(buffer, sizeof(buffer), "-%.2d%.2d", tz.hour, tz.min); - result += std::string(buffer); - } - break; - case 'Z': - // Timezone name or abbreviation. - - result += "UTC"; - break; - case '+': - // Date and time in date(1) format (not supported in glibc2). - // Tue Jun 4 04:07:43 UTC 2024 - break; - }; - } - - /// \brief Convert timestamp to string with custom format. - /// - /// This function is similar to the strftime function and supports the majority of its specifiers, - /// as well as additional ones: YY, YYYY, YYYYYY, WWW, www, hh, mm, ss, dd, sss. - /// - /// Accepts the following format specifiers as parameters: - /// - %YYYYYY: Year with reduction in the number of millennia. - /// - %YYYY: Year represented by 4 digits. - /// - %YY: Last two digits of the year. - /// - %MM: Month (01-12). - /// - %MMM: Abbreviated month name. - /// - %DD: Day of the month (01-31). - /// - %G: ISO week-based year. - /// - %g: ISO week-based year without century (00-99). - /// - %hh: Hour of the day in 24-hour format (00-23). - /// - %mm: Minute of the hour (00-59). - /// - %ss: Second (00-59). - /// - %sss: Millisecond (000-999). - /// - %V: ISO week number (01-53). - /// - %WWW: Abbreviated day of the week name in uppercase (SUN, MON, TUE, etc.). - /// - %www: Abbreviated day of the week name (Sun, Mon, Tue, etc.). - /// - %u: ISO weekday number (1-7, Monday is 1). - /// - /// For more information, see the strftime specifiers documentation: - /// \sa https://manpages.debian.org/bullseye/manpages-dev/strftime.3.en.html - /// - /// \param format_str Format string with custom parameters, e.g., "%H:%M:%S". - /// \param timestamp Timestamp. - /// \param utc_offset UTC offset in seconds (default is 0). - /// \return Returns a string in the format specified by the user. - template - const std::string to_string( - const std::string& format_str, - T timestamp, - tz_t utc_offset = 0) { - std::string result; - if (format_str.empty()) return result; - const T local_timestamp = static_cast(timestamp + static_cast(utc_offset)); - DateTimeStruct dt = to_date_time(local_timestamp); - - bool is_command = false; - size_t repeat_count = 0; - char last_char = format_str[0]; - if (last_char != '%') result += last_char; - for (size_t i = 0; i < format_str.size(); ++i) { - const char& current_char = format_str[i]; - if (!is_command) { - if (current_char == '%') { - ++repeat_count; - if (repeat_count == 2) { - result += current_char; - repeat_count = 0; - } - continue; - } - if (!repeat_count) { - result += current_char; - continue; - } - last_char = current_char; - is_command = true; - continue; - } - if (last_char == current_char) { - ++repeat_count; - continue; - } - process_format_impl(last_char, repeat_count, timestamp, utc_offset, dt, result); - repeat_count = 0; - is_command = false; - --i; - } - if (is_command) { - process_format_impl(last_char, repeat_count, timestamp, utc_offset, dt, result); - } - return result; - } - - /// \brief Alias for to_string function. - /// \copydoc to_string - template - inline const std::string to_str( - const std::string& format_str, - T timestamp, - tz_t utc_offset = 0) { - return to_string(format_str, timestamp, utc_offset); - } - - /// \brief Convert timestamp in milliseconds to string with custom format. - /// - /// This function is similar to the strftime function and supports the majority of its specifiers, - /// as well as additional ones: YY, YYYY, YYYYYY, WWW, www, hh, mm, ss, dd, sss. - /// - /// Accepts the following format specifiers as parameters: - /// - %YYYYYY: Year with reduction in the number of millennia. - /// - %YYYY: Year represented by 4 digits. - /// - %YY: Last two digits of the year. - /// - %MM: Month (01-12). - /// - %MMM: Abbreviated month name. - /// - %DD: Day of the month (01-31). - /// - %hh: Hour of the day in 24-hour format (00-23). - /// - %mm: Minute of the hour (00-59). - /// - %ss: Second (00-59). - /// - %sss: Millisecond (000-999). - /// - %WWW: Abbreviated day of the week name in uppercase (SUN, MON, TUE, etc.). - /// - %www: Abbreviated day of the week name (Sun, Mon, Tue, etc.). - /// - /// For more information, see the strftime specifiers documentation: - /// \sa https://manpages.debian.org/bullseye/manpages-dev/strftime.3.en.html - /// - /// \param format_str Format string with custom parameters, e.g., "%H:%M:%S". - /// \param timestamp Timestamp in milliseconds. - /// \param utc_offset UTC offset in seconds (default is 0). - /// \return Returns a string in the format specified by the user. - template - const std::string to_string_ms( - const std::string& format_str, - T timestamp, - tz_t utc_offset = 0) { - std::string result; - if (format_str.empty()) return result; - const T local_timestamp = static_cast(timestamp + sec_to_ms(utc_offset)); - DateTimeStruct dt = to_date_time_ms(local_timestamp); - - bool is_command = false; - size_t repeat_count = 0; - char last_char = format_str[0]; - if (last_char != '%') result += last_char; - for (size_t i = 0; i < format_str.size(); ++i) { - const char& current_char = format_str[i]; - if (!is_command) { - if (current_char == '%') { - ++repeat_count; - if (repeat_count == 2) { - result += current_char; - repeat_count = 0; - } - continue; - } - if (!repeat_count) { - result += current_char; - continue; - } - last_char = current_char; - is_command = true; - continue; - } - if (last_char == current_char) { - ++repeat_count; - continue; - } - process_format_impl(last_char, repeat_count, timestamp, utc_offset, dt, result); - repeat_count = 0; - is_command = false; - --i; - } - if (is_command) { - process_format_impl(last_char, repeat_count, timestamp, utc_offset, dt, result); - } - return result; - } - - /// \brief Alias for to_string function. - /// \copydoc to_string - template - inline const std::string to_str_ms( - const std::string& format_str, - T timestamp, - tz_t utc_offset = 0) { - return to_string_ms(format_str, timestamp, utc_offset); - } - - /// \brief Converts a timestamp to an ISO8601 string. - /// - /// This function converts a timestamp to a string in ISO8601 format. - /// - /// \tparam T The type of the timestamp (default is ts_t). - /// \param ts The timestamp to convert. - /// \return A string representing the timestamp in ISO8601 format. - template - inline const std::string to_iso8601(T ts) { - DateTimeStruct dt = to_date_time(ts); - char buffer[32] = {0}; - if TIME_SHIELD_IF_CONSTEXPR (std::is_floating_point::value) { - snprintf( - buffer, - sizeof(buffer), - "%" PRId64 "-%.2d-%.2dT%.2d:%.2d:%.2d.%.3d", - dt.year, - dt.mon, - dt.day, - dt.hour, - dt.min, - dt.sec, - dt.ms); - } else { - snprintf( - buffer, - sizeof(buffer), - "%" PRId64 "-%.2d-%.2dT%.2d:%.2d:%.2d", - dt.year, - dt.mon, - dt.day, - dt.hour, - dt.min, - dt.sec); - } - return std::string(buffer); - } - - /// \brief Converts a timestamp to an ISO8601 date string. - /// - /// This function converts the date part of a timestamp to a string in ISO8601 format. - /// - /// \tparam T The type of the timestamp (default is ts_t). - /// \param ts The timestamp to convert. - /// \return A string representing the date part of the timestamp in ISO8601 format. - template - inline const std::string to_iso8601_date(T ts) { - DateTimeStruct dt = to_date_time(ts); - char buffer[32] = {0}; - snprintf( - buffer, - sizeof(buffer), - "%" PRId64 "-%.2d-%.2d", - dt.year, - dt.mon, - dt.day); - return std::string(buffer); - } - - /// \brief Converts a timestamp to an ISO8601 time string. - /// - /// This function converts the time part of a timestamp to a string in ISO8601 format. - /// - /// \tparam T The type of the timestamp (default is ts_t). - /// \param ts The timestamp to convert. - /// \return A string representing the time part of the timestamp in ISO8601 format. - template - inline const std::string to_iso8601_time(T ts) { - DateTimeStruct dt = to_date_time(ts); - char buffer[32] = {0}; - if TIME_SHIELD_IF_CONSTEXPR (std::is_floating_point::value) { - snprintf(buffer, sizeof(buffer), "%.2d:%.2d:%.2d.%.3d", dt.hour, dt.min, dt.sec, dt.ms); - } else { - snprintf(buffer, sizeof(buffer), "%.2d:%.2d:%.2d", dt.hour, dt.min, dt.sec); - } - return std::string(buffer); - } - - /// \brief Converts a timestamp to an ISO8601 UTC time string. - /// - /// This function converts the time part of a timestamp to a string in ISO8601 format with 'Z' indicating UTC. - /// - /// \tparam T The type of the timestamp (default is ts_t). - /// \param ts The timestamp to convert. - /// \return A string representing the time part of the timestamp in ISO8601 format with 'Z' indicating UTC. - template - inline const std::string to_iso8601_time_utc(T ts) { - DateTimeStruct dt = to_date_time(ts); - char buffer[32] = {0}; - if TIME_SHIELD_IF_CONSTEXPR (std::is_floating_point::value) { - snprintf(buffer, sizeof(buffer), "%.2d:%.2d:%.2d.%.3dZ", dt.hour, dt.min, dt.sec, dt.ms); - } else { - snprintf(buffer, sizeof(buffer), "%.2d:%.2d:%.2dZ", dt.hour, dt.min, dt.sec); - } - return std::string(buffer); - } - - /// \brief Converts a timestamp to an ISO8601 string in UTC format. - /// - /// This function converts a timestamp to a string in ISO8601 format with UTC timezone. - /// - /// \tparam T The type of the timestamp (default is ts_t). - /// \param ts The timestamp to convert. - /// \return A string representing the timestamp in ISO8601 UTC format. - template - inline const std::string to_iso8601_utc(T ts) { - DateTimeStruct dt = to_date_time(ts); - char buffer[32] = {0}; - if TIME_SHIELD_IF_CONSTEXPR (std::is_floating_point::value) { - snprintf( - buffer, - sizeof(buffer), - "%" PRId64 "-%.2d-%.2dT%.2d:%.2d:%.2d.%.3dZ", - dt.year, - dt.mon, - dt.day, - dt.hour, - dt.min, - dt.sec, - dt.ms); - } else { - snprintf( - buffer, - sizeof(buffer), - "%" PRId64 "-%.2d-%.2dT%.2d:%.2d:%.2dZ", - dt.year, - dt.mon, - dt.day, - dt.hour, - dt.min, - dt.sec); - } - return std::string(buffer); - } - - /// \brief Converts a timestamp in milliseconds to an ISO8601 string in UTC format. - /// - /// This function converts a timestamp in milliseconds to a string in ISO8601 format with UTC timezone. - /// - /// \param ts_ms The timestamp in milliseconds to convert. - /// \return A string representing the timestamp in ISO8601 UTC format with milliseconds. - inline const std::string to_iso8601_utc_ms(ts_ms_t ts_ms) { - DateTimeStruct dt = to_date_time_ms(ts_ms); - char buffer[32] = {0}; - snprintf( - buffer, - sizeof(buffer), - "%" PRId64 "-%.2d-%.2dT%.2d:%.2d:%.2d.%.3dZ", - dt.year, - dt.mon, - dt.day, - dt.hour, - dt.min, - dt.sec, - dt.ms); - return std::string(buffer); - } - - /// \brief Converts a timestamp in milliseconds to an ISO8601 string. - /// - /// This function converts a timestamp in milliseconds to a string in ISO8601 format. - /// - /// \param ts_ms The timestamp in milliseconds to convert. - /// \return A string representing the timestamp in ISO8601 format with milliseconds. - inline const std::string to_iso8601_ms(ts_ms_t ts_ms) { - DateTimeStruct dt = to_date_time_ms(ts_ms); - char buffer[32] = {0}; - snprintf( - buffer, - sizeof(buffer), - "%" PRId64 "-%.2d-%.2dT%.2d:%.2d:%.2d.%.3d", - dt.year, - dt.mon, - dt.day, - dt.hour, - dt.min, - dt.sec, - dt.ms); - return std::string(buffer); - } - - /// \brief Converts a timestamp to an ISO8601 string with timezone offset. - /// - /// This function converts a timestamp to a string in ISO8601 format with timezone offset. - /// - /// \tparam T The type of the timestamp (default is ts_t). - /// \param ts The timestamp to convert. - /// \param utc_offset The timezone offset in seconds. - /// \return A string representing the timestamp in ISO8601 format with timezone offset. - template - inline const std::string to_iso8601(T ts, tz_t utc_offset) { - TimeZoneStruct tz = to_time_zone(utc_offset); - const T local_ts = static_cast(ts + static_cast(utc_offset)); - DateTimeStruct dt = to_date_time(local_ts); - char buffer[32] = {0}; - if TIME_SHIELD_IF_CONSTEXPR (std::is_floating_point::value) { - if (tz.is_positive) { - snprintf( - buffer, - sizeof(buffer), - "%" PRId64 "-%.2d-%.2dT%.2d:%.2d:%.2d.%.3d+%.2d:%.2d", - dt.year, - dt.mon, - dt.day, - dt.hour, - dt.min, - dt.sec, - dt.ms, - tz.hour, - tz.min); - } else { - snprintf( - buffer, - sizeof(buffer), - "%" PRId64 "-%.2d-%.2dT%.2d:%.2d:%.2d.%.3d-%.2d:%.2d", - dt.year, - dt.mon, - dt.day, - dt.hour, - dt.min, - dt.sec, - dt.ms, - tz.hour, - tz.min); - } - } else { - if (tz.is_positive) { - snprintf( - buffer, - sizeof(buffer), - "%" PRId64 "-%.2d-%.2dT%.2d:%.2d:%.2d+%.2d:%.2d", - dt.year, - dt.mon, - dt.day, - dt.hour, - dt.min, - dt.sec, - tz.hour, - tz.min); - } else { - snprintf( - buffer, - sizeof(buffer), - "%" PRId64 "-%.2d-%.2dT%.2d:%.2d:%.2d-%.2d:%.2d", - dt.year, - dt.mon, - dt.day, - dt.hour, - dt.min, - dt.sec, - tz.hour, - tz.min); - } - } - return std::string(buffer); - } - - /// \brief Converts a timestamp in milliseconds to an ISO8601 string with timezone offset. - /// - /// This function converts a timestamp in milliseconds to a string in ISO8601 format with timezone offset. - /// - /// \param ts_ms The timestamp in milliseconds to convert. - /// \param utc_offset The timezone offset in seconds. - /// \return A string representing the timestamp in ISO8601 format with timezone offset and milliseconds. - inline const std::string to_iso8601_ms(ts_ms_t ts_ms, tz_t utc_offset) { - TimeZoneStruct tz = to_time_zone(utc_offset); - const ts_ms_t local_ts_ms = ts_ms + sec_to_ms(utc_offset); - DateTimeStruct dt = to_date_time_ms(local_ts_ms); - char buffer[32] = {0}; - if (tz.is_positive) { - snprintf( - buffer, - sizeof(buffer), - "%" PRId64 "-%.2d-%.2dT%.2d:%.2d:%.2d.%.3d+%.2d:%.2d", - dt.year, - dt.mon, - dt.day, - dt.hour, - dt.min, - dt.sec, - dt.ms, - tz.hour, - tz.min); - } else { - snprintf( - buffer, - sizeof(buffer), - "%" PRId64 "-%.2d-%.2dT%.2d:%.2d:%.2d.%.3d-%.2d:%.2d", - dt.year, - dt.mon, - dt.day, - dt.hour, - dt.min, - dt.sec, - dt.ms, - tz.hour, - tz.min); - } - return std::string(buffer); - } - - /// \brief Converts a timestamp to a string in MQL5 date and time format. - /// - /// This function converts a timestamp to a string in MQL5 date and time format (yyyy.mm.dd hh:mm:ss). - /// - /// \param ts The timestamp to convert. - /// \return A string representing the timestamp in MQL5 date and time format. - inline const std::string to_mql5_date_time(ts_t ts) { - DateTimeStruct dt = to_date_time(ts); - char buffer[32] = {0}; - snprintf( - buffer, - sizeof(buffer), - "%" PRId64 ".%.2d.%.2d %.2d:%.2d:%.2d", - dt.year, - dt.mon, - dt.day, - dt.hour, - dt.min, - dt.sec); - return std::string(buffer); - } - - /// \brief Alias for to_mql5_date_time_str function. - /// \copydoc to_mql5_date_time - inline const std::string to_mql5_full(ts_t ts) { - return to_mql5_date_time(ts); - } - - /// \brief Converts a timestamp to a string in MQL5 date format. - /// - /// This function converts a timestamp to a string in MQL5 date format (yyyy.mm.dd). - /// - /// \param ts The timestamp to convert. - /// \return A string representing the date part of the timestamp in MQL5 format. - inline const std::string to_mql5_date(ts_t ts) { - DateTimeStruct dt = to_date_time(ts); - char buffer[32] = {0}; - snprintf( - buffer, - sizeof(buffer), - "%" PRId64 ".%.2d.%.2d", - dt.year, - dt.mon, - dt.day); - return std::string(buffer); - } - - /// \brief Converts a timestamp to a string in MQL5 time format. - /// - /// This function converts a timestamp to a string in MQL5 time format (hh:mm:ss). - /// - /// \param ts The timestamp to convert. - /// \return A string representing the time part of the timestamp in MQL5 format. - inline const std::string to_mql5_time(ts_t ts) { - DateTimeStruct dt = to_date_time(ts); - char buffer[32] = {0}; - snprintf(buffer, sizeof(buffer), "%.2d:%.2d:%.2d", dt.hour, dt.min, dt.sec); - return std::string(buffer); - } - - /// \brief Converts a timestamp in seconds to a Windows-compatible filename format. - /// \param ts The timestamp in seconds. - /// \return A string in the format "YYYY-MM-DD_HH-MM-SS". - inline const std::string to_windows_filename(ts_t ts) { - DateTimeStruct dt = to_date_time(ts); - char buffer[32] = {0}; - snprintf( - buffer, - sizeof(buffer), - "%" PRId64 "-%.2d-%.2d_%.2d-%.2d-%.2d", - dt.year, - dt.mon, - dt.day, - dt.hour, - dt.min, - dt.sec); - return std::string(buffer); - } - - /// \brief Converts a timestamp in milliseconds to a Windows-compatible filename format. - /// \param ts The timestamp in milliseconds. - /// \return A string in the format "YYYY-MM-DD_HH-MM-SS-SSS". - inline const std::string to_windows_filename_ms(ts_ms_t ts) { - DateTimeStruct dt = to_date_time_ms(ts); - char buffer[32] = {0}; - snprintf( - buffer, - sizeof(buffer), - "%" PRId64 "-%.2d-%.2d_%.2d-%.2d-%.2d-%.3d", - dt.year, - dt.mon, - dt.day, - dt.hour, - dt.min, - dt.sec, - dt.ms); - return std::string(buffer); - } - - /// \brief Converts a timestamp in seconds to a human-readable format. - /// \param ts The timestamp in seconds. - /// \return A string in the format "YYYY-MM-DD HH:MM:SS". - inline std::string to_human_readable(ts_t ts) { - DateTimeStruct dt = to_date_time(ts); - char buffer[32] = {0}; - snprintf( - buffer, - sizeof(buffer), - "%" PRId64 "-%.2d-%.2d %.2d:%.2d:%.2d", - dt.year, - dt.mon, - dt.day, - dt.hour, - dt.min, - dt.sec); - return std::string(buffer); - } - - /// \brief Converts a timestamp in milliseconds to a human-readable format. - /// \param ts The timestamp in milliseconds. - /// \return A string in the format "YYYY-MM-DD HH:MM:SS.SSS". - inline std::string to_human_readable_ms(ts_ms_t ts) { - DateTimeStruct dt = to_date_time_ms(ts); - char buffer[32] = {0}; - snprintf( - buffer, - sizeof(buffer), - "%" PRId64 "-%.2d-%.2d %.2d:%.2d:%.2d.%.3d", - dt.year, - dt.mon, - dt.day, - dt.hour, - dt.min, - dt.sec, - dt.ms); - return std::string(buffer); - } - -/// \} - -}; // namespace time_shield - -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_TIME_FORMATTING_HPP_INCLUDED +#endif // TIME_SHIELD_HEADER_TIME_FORMATTING_HPP_INCLUDED diff --git a/include/time_shield/time_parser.hpp b/include/time_shield/time_parser.hpp index f1d875b2..33e671e6 100644 --- a/include/time_shield/time_parser.hpp +++ b/include/time_shield/time_parser.hpp @@ -1,1770 +1,8 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_TIME_PARSER_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_TIME_PARSER_HPP_INCLUDED +#ifndef TIME_SHIELD_HEADER_TIME_PARSER_HPP_INCLUDED +#define TIME_SHIELD_HEADER_TIME_PARSER_HPP_INCLUDED -/// \file time_parser.hpp -/// \brief Header file with functions for parsing dates and times in ISO8601 format and converting them to various timestamp formats. -/// -/// This file contains functions for parsing ISO8601 date and time strings, extracting month numbers from month names, -/// and converting parsed date and time information to different timestamp formats. -/// -/// Provides: -/// - Month name parsing (e.g. "Jan", "January") to month index (1..12). -/// - Timeframe parsing for trading and engineering strings (e.g. "M15", "hour", "2 weeks"). -/// - ISO8601 date/time parsing into DateTimeStruct + TimeZoneStruct. -/// - Convenience functions to convert ISO8601 strings to timestamps (sec/ms/float). -/// -/// \note If you need strict error handling, prefer the `str_to_*` functions that return bool. +#include -#include "enums.hpp" -#include "constants.hpp" -#include "date_time_struct.hpp" -#include "time_zone_struct.hpp" -#include "validation.hpp" -#include "time_conversions.hpp" -#include "iso_week_conversions.hpp" -#include "time_format_parser.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include - -#if __cplusplus >= 201703L -# include -#endif - -namespace time_shield { - -/// \defgroup time_parsing Time Parsing -/// \brief A comprehensive set of functions for parsing and converting date and time strings. -/// -/// This module provides utilities for parsing date and time strings in ISO8601 format, -/// extracting date components, and converting them into various timestamp formats. -/// -/// ### Key Features: -/// - Parse ISO8601 date and time strings. -/// - Extract month numbers from month names. -/// - Convert parsed date and time to timestamp formats (seconds, milliseconds, floating-point). -/// -/// ### Usage Examples: -/// - Parse an ISO8601 string and get a timestamp: -/// \code{.cpp} -/// ts_t timestamp; -/// bool success = time_shield::str_to_ts("2024-11-25T14:30:00Z", timestamp); -/// \endcode -/// -/// - Extract a month number from a string: -/// \code{.cpp} -/// time_shield::Month month = time_shield::get_month_number("March"); -/// \endcode -/// -/// - Parse an ISO8601 string into a DateTimeStruct: -/// \code{.cpp} -/// time_shield::DateTimeStruct dt; -/// time_shield::TimeZoneStruct tz; -/// bool valid = time_shield::parse_iso8601("2024-11-25T14:30:00+01:00", dt, tz); -/// \endcode -/// -/// \{ - - namespace detail { - - /// \brief Trim ASCII whitespace from both ends. - inline std::string trim_copy_ascii(const std::string& s) { - size_t b = 0; - size_t e = s.size(); - while (b < e && std::isspace(static_cast(s[b])) != 0) ++b; - while (e > b && std::isspace(static_cast(s[e - 1])) != 0) --e; - return s.substr(b, e - b); - } - -# if __cplusplus >= 201703L - /// \brief Trim ASCII whitespace from both ends (string_view). - inline std::string_view trim_view_ascii(std::string_view v) { - size_t b = 0; - size_t e = v.size(); - while (b < e && std::isspace(static_cast(v[b])) != 0) ++b; - while (e > b && std::isspace(static_cast(v[e - 1])) != 0) --e; - return v.substr(b, e - b); - } -# endif - - /// \brief Normalize month token to lower-case ASCII using current locale facet. - /// \param month Input token. - /// \param output Output lower-case token (overwritten). - inline void normalise_month_token_lower(const std::string& month, std::string& output) { - output = trim_copy_ascii(month); - if (output.empty()) return; - - const auto& facet = std::use_facet>(std::locale()); - std::transform(output.begin(), output.end(), output.begin(), - [&facet](char ch) { return facet.tolower(ch); }); - } - -# if __cplusplus >= 201703L - /// \brief Normalize month token to lower-case ASCII using current locale facet (string_view). - /// \param month Input token view. - /// \param output Output lower-case token (overwritten). - inline void normalise_month_token_lower(std::string_view month, std::string& output) { - month = trim_view_ascii(month); - output.assign(month.begin(), month.end()); - if (output.empty()) return; - - const auto& facet = std::use_facet>(std::locale()); - std::transform(output.begin(), output.end(), output.begin(), - [&facet](char ch) { return facet.tolower(ch); }); - } -# endif - - /// \brief Try parse month name token into month index (1..12). - /// \param month Month token (e.g. "Jan", "January", case-insensitive). - /// \param value Output month index in range [1..12]. - /// \return True if token matches a supported month name, false otherwise. - inline bool try_parse_month_index(const std::string& month, int& value) { - if (month.empty()) return false; - - std::string month_copy; - normalise_month_token_lower(month, month_copy); - if (month_copy.empty()) return false; - - static const std::array short_names = { - "jan", "feb", "mar", "apr", "may", "jun", - "jul", "aug", "sep", "oct", "nov", "dec" - }; - static const std::array full_names = { - "january", "february", "march", "april", "may", "june", - "july", "august", "september", "october", "november", "december" - }; - - for (std::size_t i = 0; i < short_names.size(); ++i) { - if (month_copy == short_names[i] || month_copy == full_names[i]) { - value = static_cast(i) + 1; - return true; - } - } - - return false; - } - -# if __cplusplus >= 201703L - /// \brief Try parse month name token into month index (1..12), string_view overload. - /// \param month Month token view (e.g. "Jan", "January", case-insensitive). - /// \param value Output month index in range [1..12]. - /// \return True if token matches a supported month name, false otherwise. - inline bool try_parse_month_index(std::string_view month, int& value) { - if (month.empty()) return false; - - std::string month_copy; - normalise_month_token_lower(month, month_copy); - if (month_copy.empty()) return false; - - static const std::array short_names = { - "jan", "feb", "mar", "apr", "may", "jun", - "jul", "aug", "sep", "oct", "nov", "dec" - }; - static const std::array full_names = { - "january", "february", "march", "april", "may", "june", - "july", "august", "september", "october", "november", "december" - }; - - for (std::size_t i = 0; i < short_names.size(); ++i) { - if (month_copy == short_names[i] || month_copy == full_names[i]) { - value = static_cast(i) + 1; - return true; - } - } - - return false; - } -# endif - - /// \brief Parse month name token into month index (1..12). - /// \param month Month token. - /// \return Month index [1..12]. - /// \throw std::invalid_argument if token is invalid. - inline int parse_month_index(const std::string& month) { - int value = 0; - if (!try_parse_month_index(month, value)) { - throw std::invalid_argument("Invalid month name"); - } - return value; - } - -# if __cplusplus >= 201703L - /// \brief Parse month name token into month index (1..12), string_view overload. - /// \param month Month token view. - /// \return Month index [1..12]. - /// \throw std::invalid_argument if token is invalid. - inline int parse_month_index(std::string_view month) { - int value = 0; - if (!try_parse_month_index(month, value)) { - throw std::invalid_argument("Invalid month name"); - } - return value; - } -# endif - - struct ZoneNameEntry { - const char* name; - TimeZone zone; - }; - - /// \brief Return supported strict named-zone entries. - inline const std::array& time_zone_name_entries() noexcept { - static const std::array entries = {{ - {"GMT", GMT}, - {"UTC", UTC}, - {"EET", EET}, - {"CET", CET}, - {"WET", WET}, - {"EEST", EEST}, - {"CEST", CEST}, - {"WEST", WEST}, - {"ET", ET}, - {"CT", CT}, - {"IST", IST}, - {"MYT", MYT}, - {"WIB", WIB}, - {"WITA", WITA}, - {"WIT", WIT}, - {"KZT", KZT}, - {"TRT", TRT}, - {"BYT", BYT}, - {"SGT", SGT}, - {"ICT", ICT}, - {"PHT", PHT}, - {"GST", GST}, - {"HKT", HKT}, - {"JST", JST}, - {"KST", KST} - }}; - return entries; - } - - /// \brief Parse strict named-zone token without trimming. - inline bool try_parse_time_zone_name_token(const char* data, std::size_t length, TimeZone& zone) noexcept { - if (data == nullptr || length == 0) { - zone = UNKNOWN; - return false; - } - - const std::array& entries = time_zone_name_entries(); - for (std::size_t i = 0; i < entries.size(); ++i) { - const std::size_t name_length = std::strlen(entries[i].name); - if (length == name_length && std::memcmp(data, entries[i].name, name_length) == 0) { - zone = entries[i].zone; - return true; - } - } - - zone = UNKNOWN; - return false; - } - -//------------------------------------------------------------------------------ -// Small C-style helpers (no lambdas, no detail namespace) -//------------------------------------------------------------------------------ - - /// \brief Check whether character is ASCII whitespace. - TIME_SHIELD_CONSTEXPR inline bool is_ascii_space(char c) noexcept { - return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f' || c == '\v'; - } - - /// \brief Check whether character is ASCII digit. - TIME_SHIELD_CONSTEXPR inline bool is_ascii_digit(char c) noexcept { - return c >= '0' && c <= '9'; - } - - /// \brief Check whether character is ASCII letter. - TIME_SHIELD_CONSTEXPR inline bool is_ascii_alpha(char c) noexcept { - return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'); - } - - /// \brief Convert ASCII letter to lower-case. - TIME_SHIELD_CONSTEXPR inline char ascii_to_lower(char c) noexcept { - return (c >= 'A' && c <= 'Z') ? static_cast(c - 'A' + 'a') : c; - } - - /// \brief Compare ASCII token to literal case-insensitively. - inline bool ascii_iequals(const char* data, std::size_t length, const char* literal) noexcept { - if (data == nullptr || literal == nullptr) { - return false; - } - - for (std::size_t i = 0; i < length; ++i) { - if (literal[i] == '\0' || ascii_to_lower(data[i]) != ascii_to_lower(literal[i])) { - return false; - } - } - - return literal[length] == '\0'; - } - - /// \brief Parse positive int64 value from ASCII digits. - inline bool try_parse_positive_int64(const char* data, std::size_t length, int64_t& value) noexcept { - value = 0; - if (data == nullptr || length == 0) { - return false; - } - - const int64_t max_value = (std::numeric_limits::max)(); - for (std::size_t i = 0; i < length; ++i) { - if (!is_ascii_digit(data[i])) { - return false; - } - - const int digit = data[i] - '0'; - if (value > (max_value - digit) / 10) { - value = 0; - return false; - } - value = value * 10 + digit; - } - - return value > 0; - } - - /// \brief Multiply positive int64 values with overflow check. - TIME_SHIELD_CONSTEXPR inline bool try_multiply_positive_int64(int64_t lhs, int64_t rhs, int64_t& value) noexcept { - if (lhs <= 0 || rhs <= 0) { - return false; - } - - if (lhs > (std::numeric_limits::max)() / rhs) { - return false; - } - - value = lhs * rhs; - return true; - } - - /// \brief Resolve compact trading unit token to seconds. - inline bool try_get_timeframe_unit_seconds_compact(const char* data, std::size_t length, int64_t& unit_seconds) noexcept { - unit_seconds = 0; - if (data == nullptr || length == 0) { - return false; - } - - if (length == 1) { - switch (ascii_to_lower(data[0])) { - case 's': unit_seconds = 1; return true; - case 'm': unit_seconds = SEC_PER_MIN; return true; - case 'h': unit_seconds = SEC_PER_HOUR; return true; - case 'd': unit_seconds = SEC_PER_DAY; return true; - case 'w': unit_seconds = SEC_PER_DAY * 7; return true; - case 'q': unit_seconds = SEC_PER_DAY * 90; return true; - case 'y': unit_seconds = SEC_PER_YEAR; return true; - default: return false; - } - } - - if (length == 2 - && ascii_to_lower(data[0]) == 'm' - && ascii_to_lower(data[1]) == 'n') { - unit_seconds = SEC_PER_DAY * 30; - return true; - } - - return false; - } - - /// \brief Resolve word timeframe unit token to seconds. - inline bool try_get_timeframe_unit_seconds_word(const char* data, std::size_t length, int64_t& unit_seconds) noexcept { - unit_seconds = 0; - if (data == nullptr || length == 0) { - return false; - } - - struct TimeframeUnitEntry { - const char* name; - int64_t seconds; - }; - - static const TimeframeUnitEntry entries[] = { - {"sec", 1}, - {"second", 1}, - {"seconds", 1}, - {"min", SEC_PER_MIN}, - {"minute", SEC_PER_MIN}, - {"minutes", SEC_PER_MIN}, - {"hr", SEC_PER_HOUR}, - {"hour", SEC_PER_HOUR}, - {"hours", SEC_PER_HOUR}, - {"day", SEC_PER_DAY}, - {"days", SEC_PER_DAY}, - {"week", SEC_PER_DAY * 7}, - {"weeks", SEC_PER_DAY * 7}, - {"month", SEC_PER_DAY * 30}, - {"months", SEC_PER_DAY * 30}, - {"quarter", SEC_PER_DAY * 90}, - {"quarters", SEC_PER_DAY * 90}, - {"year", SEC_PER_YEAR}, - {"years", SEC_PER_YEAR} - }; - - for (std::size_t i = 0; i < sizeof(entries) / sizeof(entries[0]); ++i) { - if (ascii_iequals(data, length, entries[i].name)) { - unit_seconds = entries[i].seconds; - return true; - } - } - - return false; - } - - /// \brief Parse timeframe string into fixed seconds. - inline bool try_parse_timeframe_seconds(const char* data, std::size_t length, ts_t& seconds) noexcept { - seconds = 0; - if (data == nullptr || length == 0) { - return false; - } - - const char* begin = data; - const char* end = data + length; - while (begin < end && is_ascii_space(*begin)) { - ++begin; - } - while (end > begin && is_ascii_space(*(end - 1))) { - --end; - } - - if (begin == end) { - return false; - } - - int64_t multiplier = 1; - int64_t unit_seconds = 0; - int64_t result = 0; - - if (is_ascii_digit(*begin)) { - const char* cursor = begin; - while (cursor < end && is_ascii_digit(*cursor)) { - ++cursor; - } - - if (!try_parse_positive_int64(begin, static_cast(cursor - begin), multiplier)) { - return false; - } - - while (cursor < end && is_ascii_space(*cursor)) { - ++cursor; - } - if (cursor == end) { - return false; - } - - for (const char* p = cursor; p < end; ++p) { - if (!is_ascii_alpha(*p)) { - return false; - } - } - - if (!try_get_timeframe_unit_seconds_word(cursor, static_cast(end - cursor), unit_seconds)) { - return false; - } - - if (!try_multiply_positive_int64(multiplier, unit_seconds, result)) { - return false; - } - - seconds = static_cast(result); - return true; - } - - if (!is_ascii_alpha(*begin)) { - return false; - } - - const char* cursor = begin; - while (cursor < end && is_ascii_alpha(*cursor)) { - ++cursor; - } - - if (cursor == end) { - if (!try_get_timeframe_unit_seconds_word(begin, static_cast(end - begin), unit_seconds)) { - return false; - } - - seconds = static_cast(unit_seconds); - return true; - } - - if (is_ascii_space(*cursor)) { - return false; - } - - for (const char* p = cursor; p < end; ++p) { - if (!is_ascii_digit(*p)) { - return false; - } - } - - if (!try_get_timeframe_unit_seconds_compact(begin, static_cast(cursor - begin), unit_seconds)) { - return false; - } - if (!try_parse_positive_int64(cursor, static_cast(end - cursor), multiplier)) { - return false; - } - if (!try_multiply_positive_int64(multiplier, unit_seconds, result)) { - return false; - } - - seconds = static_cast(result); - return true; - } - - /// \brief Skip ASCII whitespace. - TIME_SHIELD_CONSTEXPR inline void skip_spaces(const char*& p, const char* end) noexcept { - while (p < end && is_ascii_space(*p)) { - ++p; - } - } - - /// \brief Parse exactly 2 digits into int. - /// \return true on success. - TIME_SHIELD_CONSTEXPR inline bool parse_2digits(const char*& p, const char* end, int& out) noexcept { - if (end - p < 2) { - return false; - } - const char a = p[0]; - const char b = p[1]; - if (!is_ascii_digit(a) || !is_ascii_digit(b)) { - return false; - } - out = (a - '0') * 10 + (b - '0'); - p += 2; - return true; - } - - /// \brief Parse exactly 4 digits into year_t (via int). - /// \return true on success. - TIME_SHIELD_CONSTEXPR inline bool parse_4digits_year(const char*& p, const char* end, year_t& out) noexcept { - if (end - p < 4) { - return false; - } - const char a = p[0], b = p[1], c = p[2], d = p[3]; - if (!is_ascii_digit(a) || !is_ascii_digit(b) || !is_ascii_digit(c) || !is_ascii_digit(d)) { - return false; - } - const int v = (a - '0') * 1000 + (b - '0') * 100 + (c - '0') * 10 + (d - '0'); - out = static_cast(v); - p += 4; - return true; - } - - /// \brief Parse fractional seconds (1..9 digits) and convert to milliseconds. - /// \details Uses first 3 digits, scales if fewer. - /// \return true on success. - TIME_SHIELD_CONSTEXPR inline bool parse_fraction_to_ms(const char*& p, const char* end, int& ms_out) noexcept { - if (p >= end || !is_ascii_digit(*p)) { - return false; - } - - int ms = 0; - int digits = 0; - - while (p < end && is_ascii_digit(*p)) { - if (digits >= 3) { - return false; - } - ms = ms * 10 + (*p - '0'); - ++digits; - ++p; - } - - if (digits == 1) { - ms *= 100; - } else if (digits == 2) { - ms *= 10; - } - - ms_out = ms; - return true; - } - - } // namespace detail - -//------------------------------------------------------------------------------ -// Month helpers (public) -//------------------------------------------------------------------------------ - -// Canonical API (recommended): -// - parse_month(...) / try_parse_month(...): return month index as int [1..12] -// - parse_month_enum(...) / try_parse_month_enum(...): return month as enum Month (or any integral/enum T) - - /// \brief Try parse month name token into month index [1..12]. - /// \param month Month token (e.g. "Jan", "January"), case-insensitive. - /// \param value Output month index [1..12]. - /// \return True on success, false otherwise. - inline bool try_parse_month(const std::string& month, int& value) { - return detail::try_parse_month_index(month, value); - } - - /// \brief Parse month name token into month index [1..12]. - /// \param month Month token. - /// \return Month index [1..12]. - /// \throw std::invalid_argument if token is invalid. - inline int parse_month(const std::string& month) { - return detail::parse_month_index(month); - } - -#if __cplusplus >= 201703L - /// \brief Try parse month name token into month index [1..12], string_view overload. - /// \param month Month token view (e.g. "Jan", "January"), case-insensitive. - /// \param value Output month index [1..12]. - /// \return True on success, false otherwise. - inline bool try_parse_month(std::string_view month, int& value) { - return detail::try_parse_month_index(month, value); - } - - /// \brief Parse month name token into month index [1..12], string_view overload. - /// \param month Month token view. - /// \return Month index [1..12]. - /// \throw std::invalid_argument if token is invalid. - inline int parse_month(std::string_view month) { - return detail::parse_month_index(month); - } -#endif - -// Canonical: parse month -> enum Month (or any T) - - /// \brief Parse month name token into Month enum (throwing). - /// \tparam T Return type, default is Month enum. - /// \param month Month token. - /// \return Month number (1..12) converted to T. - /// \throw std::invalid_argument if token is invalid. - template - inline T parse_month_enum(const std::string& month) { - return static_cast(detail::parse_month_index(month)); - } - - /// \brief Try parse month name token into Month enum (or any T). - /// \tparam T Output type, default is Month enum. - /// \param month Month token. - /// \param value Output month number (1..12) converted to T. - /// \return True if month token is valid, false otherwise. - template - inline bool try_parse_month_enum(const std::string& month, T& value) { - int idx = 0; - if (!detail::try_parse_month_index(month, idx)) return false; - value = static_cast(idx); - return true; - } - -#if __cplusplus >= 201703L - /// \brief Parse month name token into Month enum (throwing), string_view overload. - /// \tparam T Return type, default is Month enum. - /// \param month Month token view. - /// \return Month number (1..12) converted to T. - /// \throw std::invalid_argument if token is invalid. - template - inline T parse_month_enum(std::string_view month) { - return static_cast(detail::parse_month_index(month)); - } - - /// \brief Try parse month name token into Month enum (or any T), string_view overload. - /// \tparam T Output type, default is Month enum. - /// \param month Month token view. - /// \param value Output month number (1..12) converted to T. - /// \return True if month token is valid, false otherwise. - template - inline bool try_parse_month_enum(std::string_view month, T& value) { - int idx = 0; - if (!detail::try_parse_month_index(month, idx)) return false; - value = static_cast(idx); - return true; - } -#endif - -// Index aliases (int) - - /// \brief Try parse month name token into month index [1..12]. - /// \param month Month token (e.g. "Jan", "January"), case-insensitive. - /// \param value Output month index [1..12]. - /// \return True on success, false otherwise. - inline bool try_get_month_index(const std::string& month, int& value) { - return try_parse_month(month, value); - } - - /// \brief Parse month name token into month index [1..12]. - /// \param month Month token. - /// \return Month index [1..12]. - /// \throw std::invalid_argument if token is invalid. - inline int get_month_index(const std::string& month) { - return parse_month(month); - } - - /// \brief Parse month name token into Month enum. - /// \param month Month token. - /// \return Month enum value (1..12). - /// \throw std::invalid_argument if token is invalid. - inline Month get_month_index_enum(const std::string& month) { - return static_cast(detail::parse_month_index(month)); - } - -#if __cplusplus >= 201703L - /// \brief Try parse month name token into month index [1..12], string_view overload. - inline bool try_get_month_index(std::string_view month, int& value) { - return try_parse_month(month, value); - } - - /// \brief Parse month name token into month index [1..12], string_view overload. - inline int get_month_index(std::string_view month) { - return parse_month(month); - } - - /// \brief Parse month name token into Month enum, string_view overload. - inline Month get_month_index_enum(std::string_view month) { - return static_cast(detail::parse_month_index(month)); - } -#endif - -// Month number aliases (T) - - /// \brief Get the month number by name (throwing). - /// \tparam T Return type, default is Month enum. - /// \param month Month token. - /// \return Month number (1..12) converted to T. - /// \throw std::invalid_argument if token is invalid. - template - inline T get_month_number(const std::string& month) { - return parse_month_enum(month); - } - - /// \brief Alias for get_month_number (throwing). - template - inline T month_of_year(const std::string& month) { - return get_month_number(month); - } - - /// \brief Try get the month number by name, with output parameter. - /// \tparam T Output type, default is Month enum. - /// \param month Month token. - /// \param value Output month number (1..12) converted to T. - /// \return True if month token is valid, false otherwise. - template - inline bool try_get_month_number(const std::string& month, T& value) { - return try_parse_month_enum(month, value); - } - - /// \brief Alias for try_get_month_number (output parameter). - template - inline bool get_month_number(const std::string& month, T& value) { - return try_get_month_number(month, value); - } - - /// \brief Alias for try_get_month_number (output parameter). - template - inline bool month_of_year(const std::string& month, T& value) { - return try_get_month_number(month, value); - } - -#if __cplusplus >= 201703L - /// \brief Get the month number by name (throwing), string_view overload. - template - inline T get_month_number(std::string_view month) { - return parse_month_enum(month); - } - - /// \brief Alias for get_month_number (throwing), string_view overload. - template - inline T month_of_year(std::string_view month) { - return get_month_number(month); - } - - /// \brief Try get the month number by name, string_view overload. - template - inline bool try_get_month_number(std::string_view month, T& value) { - return try_parse_month_enum(month, value); - } - - /// \brief Alias for try_get_month_number, string_view overload. - template - inline bool get_month_number(std::string_view month, T& value) { - return try_get_month_number(month, value); - } - - /// \brief Alias for try_get_month_number, string_view overload. - template - inline bool month_of_year(std::string_view month, T& value) { - return try_get_month_number(month, value); - } -#endif - -// const char* overloads to avoid ambiguity with string vs string_view for literals - - /// \brief Get the month number by name (throwing), const char* overload. - /// \tparam T Return type, default is Month enum. - /// \param month Month token C-string. - /// \return Month number (1..12) converted to T. - /// \throw std::invalid_argument if token is invalid. - template - inline T get_month_number(const char* month) { -#if __cplusplus >= 201703L - return get_month_number(std::string_view(month)); -#else - return get_month_number(std::string(month)); -#endif - } - - /// \brief Try get the month number by name, const char* overload. - /// \tparam T Output type, default is Month enum. - /// \param month Month token C-string. - /// \param value Output month number (1..12) converted to T. - /// \return True if month token is valid, false otherwise. - template - inline bool try_get_month_number(const char* month, T& value) { -#if __cplusplus >= 201703L - return try_get_month_number(std::string_view(month), value); -#else - return try_get_month_number(std::string(month), value); -#endif - } - - /// \brief Alias for get_month_number (throwing), const char* overload. - template - inline T month_of_year(const char* month) { - return get_month_number(month); - } - - /// \brief Alias for try_get_month_number (output parameter), const char* overload. - template - inline bool get_month_number(const char* month, T& value) { - return try_get_month_number(month, value); - } - - /// \brief Alias for try_get_month_number (output parameter), const char* overload. - template - inline bool month_of_year(const char* month, T& value) { - return try_get_month_number(month, value); - } - -//------------------------------------------------------------------------------ -// Time zone parsing (C-style, high performance) -//------------------------------------------------------------------------------ - - /// \brief Parse timezone character buffer into TimeZoneStruct. - /// \details Supported formats: - /// - "" -> UTC (+00:00) - /// - "Z" -> UTC (+00:00) - /// - "+HH:MM" or "-HH:MM" - /// - /// Parsing is syntax-oriented and accepts offsets up to `23:59`. Semantic - /// support checks for reusable library features use `is_valid_tz_offset(...)` - /// and the supported UTC-offset range `[-12:00, +14:00]`. - /// - /// \param data Pointer to timezone buffer (may be not null-terminated). - /// \param length Number of characters in buffer. - /// \param tz Output time zone struct. - /// \return True if parsing succeeds and tz is valid, false otherwise. - inline bool parse_time_zone(const char* data, std::size_t length, TimeZoneStruct& tz) noexcept { - if (!data) { - return false; - } - - if (length == 0) { - tz.hour = 0; - tz.min = 0; - tz.is_positive = true; - return true; - } - - if (length == 1 && (data[0] == 'Z' || data[0] == 'z')) { - tz.hour = 0; - tz.min = 0; - tz.is_positive = true; - return true; - } - - if (length != 6) { - return false; - } - - const char sign = data[0]; - if (sign != '+' && sign != '-') { - return false; - } - if (data[3] != ':') { - return false; - } - if (!detail::is_ascii_digit(data[1]) || !detail::is_ascii_digit(data[2]) || - !detail::is_ascii_digit(data[4]) || !detail::is_ascii_digit(data[5])) { - return false; - } - - tz.is_positive = (sign == '+'); - tz.hour = (data[1] - '0') * 10 + (data[2] - '0'); - tz.min = (data[4] - '0') * 10 + (data[5] - '0'); - - return is_valid_time_zone(tz); - } - - /// \brief Parse timezone string into TimeZoneStruct. - /// \details Wrapper over parse_time_zone(const char*, std::size_t, TimeZoneStruct&). - inline bool parse_time_zone(const std::string& tz_str, TimeZoneStruct& tz) noexcept { - return parse_time_zone(tz_str.c_str(), tz_str.size(), tz); - } - - /// \brief Alias for parse_time_zone. - inline bool parse_tz(const std::string& tz_str, TimeZoneStruct& tz) noexcept { - return parse_time_zone(tz_str, tz); - } - - /// \brief Alias for parse_time_zone (buffer overload). - inline bool parse_tz(const char* data, std::size_t length, TimeZoneStruct& tz) noexcept { - return parse_time_zone(data, length, tz); - } - - /// \brief Parse named time zone character buffer into TimeZone enum. - /// \details Supported tokens are exact uppercase repo-native abbreviations with ASCII trimming. - /// \param data Pointer to time zone name buffer. - /// \param length Number of characters in buffer. - /// \param zone Output named time zone. - /// \return True when parsing succeeds. - inline bool parse_time_zone_name(const char* data, std::size_t length, TimeZone& zone) noexcept { - if (!data) { - zone = UNKNOWN; - return false; - } - - std::size_t begin = 0; - std::size_t end = length; - while (begin < end && std::isspace(static_cast(data[begin])) != 0) { - ++begin; - } - while (end > begin && std::isspace(static_cast(data[end - 1])) != 0) { - --end; - } - return detail::try_parse_time_zone_name_token(data + begin, end - begin, zone); - } - - /// \brief Parse named time zone string into TimeZone enum. - /// \details Wrapper over parse_time_zone_name(const char*, std::size_t, TimeZone&). - inline bool parse_time_zone_name(const std::string& value, TimeZone& zone) noexcept { - return parse_time_zone_name(value.c_str(), value.size(), zone); - } - -#if __cplusplus >= 201703L - /// \brief Parse named time zone string_view into TimeZone enum. - /// \details Wrapper over parse_time_zone_name(const char*, std::size_t, TimeZone&). - inline bool parse_time_zone_name(std::string_view value, TimeZone& zone) noexcept { - return parse_time_zone_name(value.data(), value.size(), zone); - } -#endif - - /// \brief Parse named time zone C-string into TimeZone enum. - /// \details Wrapper over parse_time_zone_name(const char*, std::size_t, TimeZone&). - inline bool parse_time_zone_name(const char* value, TimeZone& zone) noexcept { - if (value == nullptr) { - zone = UNKNOWN; - return false; - } - return parse_time_zone_name(value, std::strlen(value), zone); - } - - /// \brief Alias for parse_time_zone_name. - inline bool parse_tz_name(const char* data, std::size_t length, TimeZone& zone) noexcept { - return parse_time_zone_name(data, length, zone); - } - - /// \brief Alias for parse_time_zone_name. - inline bool parse_tz_name(const std::string& value, TimeZone& zone) noexcept { - return parse_time_zone_name(value, zone); - } - -#if __cplusplus >= 201703L - /// \brief Alias for parse_time_zone_name. - inline bool parse_tz_name(std::string_view value, TimeZone& zone) noexcept { - return parse_time_zone_name(value, zone); - } -#endif - - /// \brief Alias for parse_time_zone_name. - inline bool parse_tz_name(const char* value, TimeZone& zone) noexcept { - return parse_time_zone_name(value, zone); - } - -//------------------------------------------------------------------------------ -// ISO8601 parsing (C-style, no regex, no allocations) -//------------------------------------------------------------------------------ - - /// \brief Parse ISO8601 character buffer into DateTimeStruct and TimeZoneStruct. - /// \details Supported inputs: - /// - "YYYY-MM-DD" - /// - "YYYY-MM-DDThh:mm" - /// - "YYYY-MM-DDThh:mm:ss" - /// - "YYYY-MM-DDThh:mm:ss.fff" (1..9 digits fraction; milliseconds from first 3 digits, scaled if fewer) - /// - Any of the above time forms with "Z" or "+HH:MM"/"-HH:MM" - /// - Separator between date and time: 'T' or ASCII whitespace. - /// - /// Date separators supported: '-', '/', '.' (as in original regex). - /// ISO week-date forms are also accepted through parse_iso_week_date(), - /// including canonical and compatible mixed separator variants with optional - /// weekday and uppercase or lowercase `W`. - /// - /// \param input Pointer to buffer (may be not null-terminated). - /// \param length Buffer length. - /// \param dt Output DateTimeStruct (filled). On success, dt is always initialized. - /// \param tz Output TimeZoneStruct (filled). If timezone is not present, UTC is used. - /// \return True if parsing succeeds and dt is valid, false otherwise. - inline bool parse_iso8601(const char* input, std::size_t length, - DateTimeStruct& dt, TimeZoneStruct& tz) noexcept { - if (!input) { - return false; - } - - const char* p = input; - const char* end = input + length; - - detail::skip_spaces(p, end); - - dt = create_date_time_struct(0); - tz = create_time_zone_struct(0, 0); - tz.is_positive = true; - - const char* const date_start = p; - const char* date_end = p; - while (date_end < end && *date_end != 'T' && *date_end != 't' && !detail::is_ascii_space(*date_end)) { - ++date_end; - } - - bool parsed_iso_week_date = false; - if (date_end > date_start) { - IsoWeekDateStruct iso_date{}; - if (parse_iso_week_date(date_start, static_cast(date_end - date_start), iso_date)) { - const DateStruct calendar_date = iso_week_date_to_date(iso_date); - dt.year = calendar_date.year; - dt.mon = calendar_date.mon; - dt.day = calendar_date.day; - p = date_end; - parsed_iso_week_date = true; - } - } - - if (!parsed_iso_week_date) { - // ---- Date: YYYYMMDD - if (!detail::parse_4digits_year(p, end, dt.year)) { - return false; - } - if (p >= end) { - return false; - } - const char sep1 = *p; - if (sep1 != '-' && sep1 != '/' && sep1 != '.') { - return false; - } - ++p; - - if (!detail::parse_2digits(p, end, dt.mon)) { - return false; - } - if (p >= end) { - return false; - } - const char sep2 = *p; - if (sep2 != '-' && sep2 != '/' && sep2 != '.') { - return false; - } - ++p; - - if (!detail::parse_2digits(p, end, dt.day)) { - return false; - } - } - - if (!is_valid_date(dt.year, dt.mon, dt.day)) { - return false; - } - - // Date-only? - { - const char* q = p; - detail::skip_spaces(q, end); - if (q == end) { - // dt already has time=0 ms=0 - return is_valid_date_time(dt); - } - } - - // ---- Date/time separator: 'T' or whitespace - if (p >= end) { - return false; - } - - if (*p == 'T' || *p == 't') { - ++p; - } else - if (detail::is_ascii_space(*p)) { - // allow one or more spaces - detail::skip_spaces(p, end); - } else { - return false; - } - - // ---- Time: hh:mm[:ss][.frac] - if (!detail::parse_2digits(p, end, dt.hour)) { - return false; - } - if (p >= end || *p != ':') { - return false; - } - ++p; - - if (!detail::parse_2digits(p, end, dt.min)) { - return false; - } - - dt.sec = 0; - dt.ms = 0; - bool has_seconds = false; - - // Optional :ss - if (p < end && *p == ':') { - ++p; - if (!detail::parse_2digits(p, end, dt.sec)) { - return false; - } - has_seconds = true; - } - - // Optional .fraction (allowed only if we had seconds in original regex, - // but we accept it when seconds are present; for hh:mm (no seconds) we keep it strict). - if (p < end && *p == '.') { - // require seconds field to exist (avoid accepting YYYY-MM-DDThh:mm.xxx) - if (!has_seconds) { - // Ambiguous: could be "hh:mm.fff" which is not in your original formats. - // Keep strict to preserve behavior. - return false; - } - - ++p; - int ms = 0; - if (!detail::parse_fraction_to_ms(p, end, ms)) { - return false; - } - dt.ms = ms; - } - - // ---- Optional timezone: [spaces] (Z | +/-HH:MM) - detail::skip_spaces(p, end); - - if (p < end) { - if (*p == 'Z' || *p == 'z') { - tz.hour = 0; - tz.min = 0; - tz.is_positive = true; - ++p; - } else if (*p == '+' || *p == '-') { - // need 6 chars - if (static_cast(end - p) < 6) { - return false; - } - if (!parse_time_zone(p, 6, tz)) { - return false; - } - p += 6; - } - } - - detail::skip_spaces(p, end); - if (p != end) { - return false; - } - - return is_valid_date_time(dt); - } - - /// \brief Parse ISO8601 string into DateTimeStruct and TimeZoneStruct. - /// \details Wrapper over parse_iso8601(const char*, std::size_t, DateTimeStruct&, TimeZoneStruct&). - inline bool parse_iso8601(const std::string& input, DateTimeStruct& dt, TimeZoneStruct& tz) noexcept { - return parse_iso8601(input.c_str(), input.size(), dt, tz); - } - - /// \brief Parse ISO8601 C-string into DateTimeStruct and TimeZoneStruct. - /// \details Wrapper over parse_iso8601(const char*, std::size_t, DateTimeStruct&, TimeZoneStruct&). - inline bool parse_iso8601(const char* input, DateTimeStruct& dt, TimeZoneStruct& tz) noexcept { - if (input == nullptr) { - return false; - } - return parse_iso8601(input, std::strlen(input), dt, tz); - } - -# if __cplusplus >= 201703L - /// \brief Parse ISO8601 view into DateTimeStruct and TimeZoneStruct. - /// \details Wrapper over parse_iso8601(const char*, std::size_t, DateTimeStruct&, TimeZoneStruct&). - inline bool parse_iso8601(std::string_view input, DateTimeStruct& dt, TimeZoneStruct& tz) noexcept { - return parse_iso8601(input.data(), input.size(), dt, tz); - } -# endif - -//------------------------------------------------------------------------------ -// ISO8601 -> timestamps -//------------------------------------------------------------------------------ - - /// \brief Convert an ISO8601 string to a timestamp (ts_t). - /// \param str ISO8601 string. - /// \param ts Output timestamp (seconds). - /// \return True if parsing and conversion succeed, false otherwise. - inline bool str_to_ts(const std::string& str, ts_t& ts) { - DateTimeStruct dt; - TimeZoneStruct tz; - if (!parse_iso8601(str, dt, tz)) return false; - try { - ts = dt_to_timestamp(dt) - to_offset(tz); - return true; - } catch (...) {} - return false; - } - - /// \brief Parse ISO8601 character buffer and convert to timestamp (seconds). - /// \param data Pointer to character buffer. - /// \param length Buffer length in bytes. - /// \param ts Output timestamp in seconds. - /// \return true if parsing succeeds, false otherwise. - inline bool str_to_ts(const char* data, std::size_t length, ts_t& ts) { - if (!data || length == 0) { - ts = 0; - return false; - } - DateTimeStruct dt; - TimeZoneStruct tz; - if (!parse_iso8601(data, length, dt, tz)) return false; - try { - ts = dt_to_timestamp(dt) - to_offset(tz); - return true; - } catch (...) {} - return false; - } - - /// \brief Convert an ISO8601 string to a millisecond timestamp (ts_ms_t). - /// \param str ISO8601 string. - /// \param ts Output timestamp (milliseconds). - /// \return True if parsing and conversion succeed, false otherwise. - inline bool str_to_ts_ms(const std::string& str, ts_ms_t& ts) { - DateTimeStruct dt; - TimeZoneStruct tz; - if (!parse_iso8601(str, dt, tz)) return false; - try { - ts = static_cast(dt_to_timestamp_ms(dt)) - sec_to_ms(to_offset(tz)); - return true; - } catch (...) {} - return false; - } - - /// \brief Convert ISO8601 character buffer to millisecond timestamp (ts_ms_t). - /// \param data Pointer to character buffer. - /// \param length Number of characters in buffer. - /// \param ts Output timestamp in milliseconds. - /// \return True if parsing and conversion succeed, false otherwise. - inline bool str_to_ts_ms(const char* data, std::size_t length, ts_ms_t& ts) { - if (!data || length == 0) { - ts = 0; - return false; - } - DateTimeStruct dt; - TimeZoneStruct tz; - if (!parse_iso8601(data, length, dt, tz)) return false; - try { - ts = static_cast(dt_to_timestamp_ms(dt)) - sec_to_ms(to_offset(tz)); - return true; - } catch (...) {} - return false; - } - - /// \brief Convert an ISO8601 string to a floating-point timestamp (fts_t). - /// \param str ISO8601 string. - /// \param ts Output timestamp (floating-point seconds). - /// \return True if parsing and conversion succeed, false otherwise. - inline bool str_to_fts(const std::string& str, fts_t& ts) { - DateTimeStruct dt; - TimeZoneStruct tz; - if (!parse_iso8601(str, dt, tz)) return false; - try { - ts = dt_to_ftimestamp(dt) - static_cast(to_offset(tz)); - return true; - } catch (...) {} - return false; - } - - /// \brief Convert ISO8601 character buffer to floating-point timestamp (fts_t). - /// \param data Pointer to character buffer. - /// \param length Number of characters in buffer. - /// \param ts Output timestamp in floating-point seconds. - /// \return True if parsing and conversion succeed, false otherwise. - inline bool str_to_fts(const char* data, std::size_t length, fts_t& ts) { - if (!data || length == 0) { - ts = 0; - return false; - } - DateTimeStruct dt; - TimeZoneStruct tz; - if (!parse_iso8601(data, length, dt, tz)) return false; - try { - ts = dt_to_ftimestamp(dt) - static_cast(to_offset(tz)); - return true; - } catch (...) {} - return false; - } - -//------------------------------------------------------------------------------ -// Convenience string -> predicates (workdays) -//------------------------------------------------------------------------------ - - /// \brief Parse ISO8601 string and check if it falls on a workday (seconds precision). - inline bool is_workday(const std::string& str) { - ts_t ts = 0; - if (!str_to_ts(str, ts)) return false; - return is_workday(ts); - } - - /// \brief Parse ISO8601 string and check if it falls on a workday (milliseconds precision). - inline bool is_workday_ms(const std::string& str) { - ts_ms_t ts = 0; - if (!str_to_ts_ms(str, ts)) return false; - return is_workday_ms(ts); - } - - /// \brief Alias for is_workday(const std::string&). - /// \copydoc is_workday(const std::string&) - inline bool workday(const std::string& str) { - return is_workday(str); - } - - /// \brief Alias for is_workday_ms(const std::string&). - /// \copydoc is_workday_ms(const std::string&) - inline bool workday_ms(const std::string& str) { - return is_workday_ms(str); - } - - /// \brief Parse ISO8601 string and check if it is the first workday of its month (seconds). - /// \param str ISO8601 formatted string. - /// \return true if parsing succeeds and the timestamp corresponds to the first workday of the month, false otherwise. - inline bool is_first_workday_of_month(const std::string& str) { - ts_t ts = 0; - if (!str_to_ts(str, ts)) return false; - return is_first_workday_of_month(ts); - } - - /// \brief Parse an ISO8601 string and check if it is the first workday of its month (millisecond precision). - /// \param str ISO8601 formatted string. - /// \return true if parsing succeeds and the timestamp corresponds to the first workday of the month, false otherwise. - inline bool is_first_workday_of_month_ms(const std::string& str) { - ts_ms_t ts = 0; - if (!str_to_ts_ms(str, ts)) return false; - return is_first_workday_of_month_ms(ts); - } - - /// \brief Parse an ISO8601 string and check if it is the last workday of its month (seconds). - /// \param str ISO8601 formatted string. - /// \return true if parsing succeeds and the timestamp corresponds to the last workday of the month, false otherwise. - inline bool is_last_workday_of_month(const std::string& str) { - ts_t ts = 0; - if (!str_to_ts(str, ts)) return false; - return is_last_workday_of_month(ts); - } - - /// \brief Parse an ISO8601 string and check if it is the last workday of its month (millisecond). - /// \param str ISO8601 formatted string. - /// \return true if parsing succeeds and the timestamp corresponds to the last workday of the month, false otherwise. - inline bool is_last_workday_of_month_ms(const std::string& str) { - ts_ms_t ts = 0; - if (!str_to_ts_ms(str, ts)) return false; - return is_last_workday_of_month_ms(ts); - } - - /// \brief Parse an ISO8601 string and check if it falls within the first N workdays of its month. - /// \param str ISO8601 formatted string. - /// \param count Number of leading workdays to test against. - /// \return true if parsing succeeds and the timestamp corresponds to a workday ranked within the first N positions, false otherwise. - inline bool is_within_first_workdays_of_month(const std::string& str, int count) { - ts_t ts = 0; - if (!str_to_ts(str, ts)) return false; - return is_within_first_workdays_of_month(ts, count); - } - - /// \brief Parse an ISO8601 string and check if it falls within the first N workdays of its month (millisecond precision). - /// \param str ISO8601 formatted string. - /// \param count Number of leading workdays to test against. - /// \return true if parsing succeeds and the timestamp corresponds to a workday ranked within the first N positions, false otherwise. - inline bool is_within_first_workdays_of_month_ms(const std::string& str, int count) { - ts_ms_t ts = 0; - if (!str_to_ts_ms(str, ts)) return false; - return is_within_first_workdays_of_month_ms(ts, count); - } - - /// \brief Parse ISO8601 string and check if it is within last N workdays of its month (seconds). - /// \param str ISO8601 formatted string. - /// \param count Number of trailing workdays to test against. - /// \return true if parsing succeeds and the timestamp corresponds to a workday ranked within the final N positions, false otherwise. - inline bool is_within_last_workdays_of_month(const std::string& str, int count) { - ts_t ts = 0; - if (!str_to_ts(str, ts)) return false; - return is_within_last_workdays_of_month(ts, count); - } - - /// \brief Parse ISO8601 string and check if it is within last N workdays of its month (milliseconds). - /// \param str ISO8601 formatted string. - /// \param count Number of trailing workdays to test against. - /// \return true if parsing succeeds and the timestamp corresponds to a workday ranked within the final N positions, false otherwise. - inline bool is_within_last_workdays_of_month_ms(const std::string& str, int count) { - ts_ms_t ts = 0; - if (!str_to_ts_ms(str, ts)) return false; - return is_within_last_workdays_of_month_ms(ts, count); - } - -//------------------------------------------------------------------------------ -// Convenience: C-string wrappers (non-throwing, ambiguous on failure) -//------------------------------------------------------------------------------ - - /// \brief Convert ISO8601 C-string to timestamp (seconds). - /// \details Returns 0 on failure (ambiguous if epoch is a valid value for your usage). - /// \param str C-style string with ISO8601 timestamp, may be nullptr. - /// \return Timestamp in seconds, or 0 if parsing fails. - inline ts_t ts(const char* str) { - ts_t out = 0; - str_to_ts(str ? std::string(str) : std::string(), out); - return out; - } - - /// \brief Convert ISO8601 character buffer to timestamp (seconds). - /// \details Does not require null terminator. Returns 0 on failure - /// (ambiguous if epoch is a valid value for your usage). - /// \param data Pointer to character buffer. - /// \param length Number of characters in buffer. - /// \return Timestamp in seconds, or 0 if parsing fails. - inline ts_t ts(const char* data, std::size_t length) { - ts_t out = 0; - if (!str_to_ts(data, length, out)) { - return 0; - } - return out; - } - - /// \brief Convert ISO8601 C-string to timestamp (milliseconds). - /// \details Returns 0 on failure (ambiguous if epoch is a valid value for your usage). - /// \param str C-style string with ISO8601 timestamp, may be nullptr. - /// \return Timestamp in milliseconds, or 0 if parsing fails. - inline ts_ms_t ts_ms(const char* str) { - ts_ms_t out = 0; - str_to_ts_ms(str ? std::string(str) : std::string(), out); - return out; - } - - /// \brief Convert ISO8601 character buffer to timestamp (milliseconds). - /// \details Does not require null terminator. Returns 0 on failure. - /// \param data Pointer to character buffer. - /// \param length Number of characters in buffer. - /// \return Timestamp in milliseconds, or 0 if parsing fails. - inline ts_ms_t ts_ms(const char* data, std::size_t length) { - ts_ms_t out = 0; - if (!str_to_ts_ms(data, length, out)) { - return 0; - } - return out; - } - - /// \brief Convert ISO8601 C-string to floating timestamp (seconds). - /// \details Returns 0 on failure. - /// \param str C-style string with ISO8601 timestamp, may be nullptr. - /// \return Timestamp in seconds (floating-point), or 0 if parsing fails. - inline fts_t fts(const char* str) { - fts_t out = 0; - str_to_fts(str ? std::string(str) : std::string(), out); - return out; - } - - /// \brief Convert ISO8601 character buffer to floating timestamp (seconds). - /// \details Does not require null terminator. Returns 0 on failure. - /// \param data Pointer to character buffer. - /// \param length Number of characters in buffer. - /// \return Timestamp in seconds (floating-point), or 0 if parsing fails. - inline fts_t fts(const char* data, std::size_t length) { - fts_t out = 0; - if (!str_to_fts(data, length, out)) { - return 0.0; - } - return out; - } - -//------------------------------------------------------------------------------ - - /// \brief Convert an ISO8601 string to a timestamp (ts_t). - /// \details This function parses a string in ISO8601 format and converts it to a timestamp. - /// If parsing fails, it returns 0. - /// \param str The ISO8601 string. - /// \return The timestamp value. Returns 0 if parsing fails. - inline ts_t ts(const std::string& str) { - ts_t ts = 0; - str_to_ts(str, ts); - return ts; - } - - /// \brief Convert an ISO8601 string to a millisecond timestamp (ts_ms_t). - /// \details This function parses a string in ISO8601 format and converts it to a millisecond timestamp. - /// If parsing fails, it returns 0. - /// \param str The ISO8601 string. - /// \return The parsed millisecond timestamp, or 0 if parsing fails. - inline ts_ms_t ts_ms(const std::string& str) { - ts_ms_t ts = 0; - str_to_ts_ms(str, ts); - return ts; - } - - /// \brief Convert an ISO8601 string to a floating-point timestamp (fts_t). - /// \details This function parses a string in ISO8601 format and converts it to a floating-point timestamp. - /// If the parsing fails, it returns 0. - /// \param str The ISO8601 string. - /// \return The floating-point timestamp if successful, 0 otherwise. - inline fts_t fts(const std::string& str) { - fts_t ts = 0; - str_to_fts(str, ts); - return ts; - } - -//------------------------------------------------------------------------------ - - /// \brief Parse timeframe string into fixed seconds. - /// \param str Timeframe string such as "M15", "hour", or "2 weeks". - /// \param seconds Output duration in seconds. - /// \return True on successful parsing. - inline bool str_to_timeframe_sec(const std::string& str, ts_t& seconds) noexcept { - return detail::try_parse_timeframe_seconds(str.c_str(), str.size(), seconds); - } - - /// \brief Parse timeframe C-string into fixed seconds. - /// \param str Timeframe string such as "M15", "hour", or "2 weeks". - /// \param seconds Output duration in seconds. - /// \return True on successful parsing. - inline bool str_to_timeframe_sec(const char* str, ts_t& seconds) noexcept { - if (str == nullptr) { - seconds = 0; - return false; - } - return detail::try_parse_timeframe_seconds(str, std::strlen(str), seconds); - } - -# if __cplusplus >= 201703L - /// \brief Parse timeframe view into fixed seconds. - /// \param str Timeframe string such as "M15", "hour", or "2 weeks". - /// \param seconds Output duration in seconds. - /// \return True on successful parsing. - inline bool str_to_timeframe_sec(std::string_view str, ts_t& seconds) noexcept { - return detail::try_parse_timeframe_seconds(str.data(), str.size(), seconds); - } -# endif - - /// \brief Parse timeframe string into fixed milliseconds. - /// \param str Timeframe string such as "M15", "hour", or "2 weeks". - /// \param milliseconds Output duration in milliseconds. - /// \return True on successful parsing. - inline bool str_to_timeframe_ms(const std::string& str, ts_ms_t& milliseconds) noexcept { - ts_t seconds = 0; - if (!detail::try_parse_timeframe_seconds(str.c_str(), str.size(), seconds)) { - milliseconds = 0; - return false; - } - - int64_t milliseconds_value = 0; - if (!detail::try_multiply_positive_int64(seconds, MS_PER_SEC, milliseconds_value)) { - milliseconds = 0; - return false; - } - - milliseconds = static_cast(milliseconds_value); - return true; - } - - /// \brief Parse timeframe C-string into fixed milliseconds. - /// \param str Timeframe string such as "M15", "hour", or "2 weeks". - /// \param milliseconds Output duration in milliseconds. - /// \return True on successful parsing. - inline bool str_to_timeframe_ms(const char* str, ts_ms_t& milliseconds) noexcept { - if (str == nullptr) { - milliseconds = 0; - return false; - } - - ts_t seconds = 0; - if (!detail::try_parse_timeframe_seconds(str, std::strlen(str), seconds)) { - milliseconds = 0; - return false; - } - - int64_t milliseconds_value = 0; - if (!detail::try_multiply_positive_int64(seconds, MS_PER_SEC, milliseconds_value)) { - milliseconds = 0; - return false; - } - - milliseconds = static_cast(milliseconds_value); - return true; - } - -# if __cplusplus >= 201703L - /// \brief Parse timeframe view into fixed milliseconds. - /// \param str Timeframe string such as "M15", "hour", or "2 weeks". - /// \param milliseconds Output duration in milliseconds. - /// \return True on successful parsing. - inline bool str_to_timeframe_ms(std::string_view str, ts_ms_t& milliseconds) noexcept { - ts_t seconds = 0; - if (!detail::try_parse_timeframe_seconds(str.data(), str.size(), seconds)) { - milliseconds = 0; - return false; - } - - int64_t milliseconds_value = 0; - if (!detail::try_multiply_positive_int64(seconds, MS_PER_SEC, milliseconds_value)) { - milliseconds = 0; - return false; - } - - milliseconds = static_cast(milliseconds_value); - return true; - } -# endif - - /// \brief Convert timeframe string to fixed seconds. - /// \details Returns 0 if parsing fails. - /// \param str Timeframe string such as "M15", "hour", or "2 weeks". - /// \return Parsed duration in seconds, or 0 on failure. - inline ts_t timeframe_sec(const std::string& str) noexcept { - ts_t seconds = 0; - str_to_timeframe_sec(str, seconds); - return seconds; - } - - /// \brief Convert timeframe C-string to fixed seconds. - /// \details Returns 0 if parsing fails. - /// \param str Timeframe string such as "M15", "hour", or "2 weeks". - /// \return Parsed duration in seconds, or 0 on failure. - inline ts_t timeframe_sec(const char* str) noexcept { - ts_t seconds = 0; - str_to_timeframe_sec(str, seconds); - return seconds; - } - -# if __cplusplus >= 201703L - /// \brief Convert timeframe view to fixed seconds. - /// \details Returns 0 if parsing fails. - /// \param str Timeframe string such as "M15", "hour", or "2 weeks". - /// \return Parsed duration in seconds, or 0 on failure. - inline ts_t timeframe_sec(std::string_view str) noexcept { - ts_t seconds = 0; - str_to_timeframe_sec(str, seconds); - return seconds; - } -# endif - - /// \brief Convert timeframe string to fixed milliseconds. - /// \details Returns 0 if parsing fails. - /// \param str Timeframe string such as "M15", "hour", or "2 weeks". - /// \return Parsed duration in milliseconds, or 0 on failure. - inline ts_ms_t timeframe_ms(const std::string& str) noexcept { - ts_ms_t milliseconds = 0; - str_to_timeframe_ms(str, milliseconds); - return milliseconds; - } - - /// \brief Convert timeframe C-string to fixed milliseconds. - /// \details Returns 0 if parsing fails. - /// \param str Timeframe string such as "M15", "hour", or "2 weeks". - /// \return Parsed duration in milliseconds, or 0 on failure. - inline ts_ms_t timeframe_ms(const char* str) noexcept { - ts_ms_t milliseconds = 0; - str_to_timeframe_ms(str, milliseconds); - return milliseconds; - } - -# if __cplusplus >= 201703L - /// \brief Convert timeframe view to fixed milliseconds. - /// \details Returns 0 if parsing fails. - /// \param str Timeframe string such as "M15", "hour", or "2 weeks". - /// \return Parsed duration in milliseconds, or 0 on failure. - inline ts_ms_t timeframe_ms(std::string_view str) noexcept { - ts_ms_t milliseconds = 0; - str_to_timeframe_ms(str, milliseconds); - return milliseconds; - } -# endif - -//------------------------------------------------------------------------------ - - /// \brief Parse time of day string to seconds of day. - /// - /// Supported formats: - /// - HH:MM:SS - /// - HH:MM - /// - HH - /// - /// \tparam T Return type (default int). - /// \param str Time of day as string. - /// \param sec Parsed seconds of day on success. - /// \return True on successful parsing. - template - inline bool sec_of_day(const std::string& str, T& sec) { - if (str.empty()) return false; - - const char* p = str.c_str(); - int parts[3] = {0, 0, 0}; // hour, minute, second - int idx = 0; - - while (*p && idx < 3) { - // Parse integer - int value = 0; - bool has_digit = false; - - while (*p >= '0' && *p <= '9') { - has_digit = true; - value = value * 10 + (*p - '0'); - ++p; - } - - if (!has_digit) return false; - parts[idx++] = value; - - // Expect colon or end - if (*p == ':') { - ++p; - } else if (*p == '\0') { - break; - } else { - return false; // unexpected character - } - } - - if (idx == 0) return false; - if (!is_valid_time(parts[0], parts[1], parts[2])) return false; - - sec = static_cast(sec_of_day(parts[0], parts[1], parts[2])); - return true; - } - - /// \brief Convert time of day string to seconds of day. - /// - /// Supported formats: - /// - HH:MM:SS - /// - HH:MM - /// - HH - /// - /// \tparam T Return type (default int). - /// \param str Time of day as string. - /// \return Parsed seconds of day or SEC_PER_DAY if parsing fails. - template - inline T sec_of_day(const std::string& str) { - T value{}; - if (sec_of_day(str, value)) - return value; - return static_cast(SEC_PER_DAY); - } - -/// \} - -}; - -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_TIME_PARSER_HPP_INCLUDED +#endif // TIME_SHIELD_HEADER_TIME_PARSER_HPP_INCLUDED diff --git a/include/time_shield/time_struct.hpp b/include/time_shield/time_struct.hpp index f4ff0de8..d58525aa 100644 --- a/include/time_shield/time_struct.hpp +++ b/include/time_shield/time_struct.hpp @@ -1,40 +1,8 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_TIME_STRUCT_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_TIME_STRUCT_HPP_INCLUDED +#ifndef TIME_SHIELD_HEADER_TIME_STRUCT_HPP_INCLUDED +#define TIME_SHIELD_HEADER_TIME_STRUCT_HPP_INCLUDED -/// \file time_struct.hpp -/// \brief Header for time structure and related functions. -/// -/// This file contains the definition of the TimeStruct structure and a function to create TimeStruct instances. +#include -namespace time_shield { - - /// \ingroup time_structures - /// \brief Structure to represent time. - struct TimeStruct { - int16_t hour; ///< Hour component of time (0-23) - int16_t min; ///< Minute component of time (0-59) - int16_t sec; ///< Second component of time (0-59) - int16_t ms; ///< Millisecond component of time (0-999) - }; - - /// \ingroup time_structures - /// \brief Creates a TimeStruct instance. - /// \param hour The hour component of the time. - /// \param min The minute component of the time. - /// \param sec The second component of the time, defaults to 0. - /// \param ms The millisecond component of the time, defaults to 0. - /// \return A TimeStruct instance with the provided time components. - inline const TimeStruct create_time_struct( - int16_t hour, - int16_t min, - int16_t sec = 0, - int16_t ms = 0) { - TimeStruct data{hour, min, sec, ms}; - return data; - } - -}; // namespace time_shield - -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_TIME_STRUCT_HPP_INCLUDED +#endif // TIME_SHIELD_HEADER_TIME_STRUCT_HPP_INCLUDED diff --git a/include/time_shield/time_unit_conversions.hpp b/include/time_shield/time_unit_conversions.hpp index 4c2bce14..7d16c215 100644 --- a/include/time_shield/time_unit_conversions.hpp +++ b/include/time_shield/time_unit_conversions.hpp @@ -1,487 +1,8 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_TIME_UNIT_CONVERSIONS_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_TIME_UNIT_CONVERSIONS_HPP_INCLUDED +#ifndef TIME_SHIELD_HEADER_TIME_UNIT_CONVERSIONS_HPP_INCLUDED +#define TIME_SHIELD_HEADER_TIME_UNIT_CONVERSIONS_HPP_INCLUDED -/// \file time_unit_conversions.hpp -/// \brief Helper functions for unit conversions between seconds, minutes, hours, and milliseconds. +#include -#include "config.hpp" -#include "constants.hpp" -#include "detail/floor_math.hpp" -#include "types.hpp" - -#include -#include - -namespace time_shield { - -/// \ingroup time_conversions -/// \{ - - /// \brief Get the nanosecond part of the second from a floating-point timestamp. - /// \tparam T Type of the returned value (default is int). - /// \param ts Timestamp in floating-point seconds. - /// \return T Nanosecond part of the second. - template - TIME_SHIELD_CONSTEXPR T ns_of_sec(fts_t ts) noexcept { - const int64_t ns = static_cast(std::floor(ts * static_cast(NS_PER_SEC))); - return static_cast(detail::floor_mod(ns, NS_PER_SEC)); - } - - /// \brief Get the microsecond part of the second from a floating-point timestamp. - /// \tparam T Type of the returned value (default is int). - /// \param ts Timestamp in floating-point seconds. - /// \return T Microsecond part of the second. - template - TIME_SHIELD_CONSTEXPR T us_of_sec(fts_t ts) noexcept { - const int64_t us = static_cast(std::floor(ts * static_cast(US_PER_SEC))); - return static_cast(detail::floor_mod(us, US_PER_SEC)); - } - - /// \brief Get the millisecond part of the second from a floating-point timestamp. - /// \tparam T Type of the returned value (default is int). - /// \param ts Timestamp in floating-point seconds. - /// \return T Millisecond part of the second. - template - TIME_SHIELD_CONSTEXPR T ms_of_sec(fts_t ts) noexcept { - const int64_t ms = static_cast(std::floor(ts * static_cast(MS_PER_SEC))); - return static_cast(detail::floor_mod(ms, MS_PER_SEC)); - } - - /// \brief Get the nanosecond part of the second from a floating-point timestamp (truncating). - /// \tparam T Type of the returned value (default is int). - /// \param ts Timestamp in floating-point seconds. - /// \return T Nanosecond part of the second, truncating toward zero. - template - TIME_SHIELD_CONSTEXPR T ns_of_sec_signed(fts_t ts) noexcept { - fts_t temp = 0; - return static_cast(std::round(std::modf(ts, &temp) * static_cast(NS_PER_SEC))); - } - - /// \brief Get the microsecond part of the second from a floating-point timestamp (truncating). - /// \tparam T Type of the returned value (default is int). - /// \param ts Timestamp in floating-point seconds. - /// \return T Microsecond part of the second, truncating toward zero. - template - TIME_SHIELD_CONSTEXPR T us_of_sec_signed(fts_t ts) noexcept { - fts_t temp = 0; - return static_cast(std::round(std::modf(ts, &temp) * static_cast(US_PER_SEC))); - } - - /// \brief Get the millisecond part of the second from a floating-point timestamp (truncating). - /// \tparam T Type of the returned value (default is int). - /// \param ts Timestamp in floating-point seconds. - /// \return T Millisecond part of the second, truncating toward zero. - template - TIME_SHIELD_CONSTEXPR T ms_of_sec_signed(fts_t ts) noexcept { - fts_t temp = 0; - return static_cast(std::round(std::modf(ts, &temp) * static_cast(MS_PER_SEC))); - } - - /// \brief Get the millisecond part of the timestamp. - /// \tparam T Type of the returned value (default is int). - /// \param ts Timestamp in milliseconds. - /// \return T Millisecond part of the timestamp. - template - TIME_SHIELD_CONSTEXPR T ms_part(ts_ms_t ts) noexcept { - return static_cast(detail::floor_mod(static_cast(ts), MS_PER_SEC)); - } - - /// \brief Alias for ms_part. - /// \tparam T Type of the returned value (default is int). - /// \param ts Timestamp in milliseconds. - /// \return T Millisecond part of the timestamp. - template - TIME_SHIELD_CONSTEXPR T ms_of_ts(ts_ms_t ts) noexcept { - return ms_part(ts); - } - - /// \brief Get the microsecond part of the timestamp. - /// \tparam T Type of the returned value (default is int). - /// \param ts Timestamp in microseconds. - /// \return T Microsecond part of the timestamp. - template - TIME_SHIELD_CONSTEXPR T us_part(ts_us_t ts) noexcept { - return static_cast(detail::floor_mod(static_cast(ts), US_PER_SEC)); - } - - /// \brief Alias for us_part. - /// \tparam T Type of the returned value (default is int). - /// \param ts Timestamp in microseconds. - /// \return T Microsecond part of the timestamp. - template - TIME_SHIELD_CONSTEXPR T us_of_ts(ts_us_t ts) noexcept { - return us_part(ts); - } - - /// \brief Get the nanosecond part of the timestamp. - /// \tparam T Type of the returned value (default is int). - /// \param ts Timestamp in nanoseconds. - /// \return T Nanosecond part of the timestamp. - template - TIME_SHIELD_CONSTEXPR T ns_part(T2 ts) noexcept { - return static_cast(detail::floor_mod(static_cast(ts), NS_PER_SEC)); - } - -# ifndef TIME_SHIELD_CPP17 - /// \brief Helper function for converting seconds to milliseconds (floating-point version). - /// \tparam T Type of the input timestamp. - /// \param t Timestamp in seconds. - /// \param tag std::true_type indicates a floating-point type. - /// \return ts_ms_t Timestamp in milliseconds. - template - TIME_SHIELD_CONSTEXPR ts_ms_t sec_to_ms_impl(T t, std::true_type) noexcept { - return static_cast(std::round(t * static_cast(MS_PER_SEC))); - } - - /// \brief Helper function for converting seconds to milliseconds (integral version). - /// \tparam T Type of the input timestamp. - /// \param t Timestamp in seconds. - /// \param tag std::false_type indicates a non-floating-point type. - /// \return ts_ms_t Timestamp in milliseconds. - template - TIME_SHIELD_CONSTEXPR ts_ms_t sec_to_ms_impl(T t, std::false_type) noexcept { - return static_cast(t) * static_cast(MS_PER_SEC); - } -# endif // TIME_SHIELD_CPP17 - - /// \brief Converts a timestamp from seconds to milliseconds. - /// \tparam T1 The type of the output timestamp (default is ts_ms_t). - /// \tparam T2 The type of the input timestamp. - /// \param ts Timestamp in seconds. - /// \return T1 Timestamp in milliseconds. - template - TIME_SHIELD_CONSTEXPR T1 sec_to_ms(T2 ts) noexcept { -# ifdef TIME_SHIELD_CPP17 - if constexpr (std::is_floating_point_v) { - return static_cast(std::round(ts * static_cast(MS_PER_SEC))); - } else { - return static_cast(ts) * static_cast(MS_PER_SEC); - } -# else - return static_cast(sec_to_ms_impl(ts, typename std::conditional< - (std::is_same::value || std::is_same::value), - std::true_type, - std::false_type - >::type{})); -# endif - } - - /// \brief Converts a floating-point timestamp from seconds to milliseconds. - /// \param ts Timestamp in floating-point seconds. - /// \return ts_ms_t Timestamp in milliseconds. - inline ts_ms_t fsec_to_ms(fts_t ts) noexcept { - return static_cast(std::round(ts * static_cast(MS_PER_SEC))); - } - - /// \brief Converts a timestamp from milliseconds to seconds. - /// \tparam T1 The type of the output timestamp (default is ts_t). - /// \tparam T2 The type of the input timestamp (default is ts_ms_t). - /// \param ts_ms Timestamp in milliseconds. - /// \return T1 Timestamp in seconds. - template - TIME_SHIELD_CONSTEXPR T1 ms_to_sec(T2 ts_ms) noexcept { - return static_cast(detail::floor_div( - static_cast(ts_ms), - static_cast(MS_PER_SEC))); - } - - /// \brief Converts a timestamp from milliseconds to floating-point seconds. - /// \tparam T The type of the input timestamp (default is ts_ms_t). - /// \param ts_ms Timestamp in milliseconds. - /// \return fts_t Timestamp in floating-point seconds. - template - TIME_SHIELD_CONSTEXPR fts_t ms_to_fsec(T ts_ms) noexcept { - return static_cast(ts_ms) / static_cast(MS_PER_SEC); - } - -//----------------------------------------------------------------------------// -// Minutes -> Milliseconds -//----------------------------------------------------------------------------// -# ifndef TIME_SHIELD_CPP17 - /// \brief Helper function for converting minutes to milliseconds (floating-point version). - /// \tparam T Type of the input timestamp. - /// \param t Timestamp in minutes. - /// \param tag std::true_type indicates a floating-point type (double or float). - /// \return ts_ms_t Timestamp in milliseconds. - template - TIME_SHIELD_CONSTEXPR ts_ms_t min_to_ms_impl(T t, std::true_type) noexcept { - return static_cast(std::round(t * static_cast(MS_PER_MIN))); - } - - /// \brief Helper function for converting minutes to milliseconds (integral version). - /// \tparam T Type of the input timestamp. - /// \param t Timestamp in minutes. - /// \param tag std::false_type indicates a non-floating-point type. - /// \return ts_ms_t Timestamp in milliseconds. - template - TIME_SHIELD_CONSTEXPR ts_ms_t min_to_ms_impl(T t, std::false_type) noexcept { - return static_cast(t) * static_cast(MS_PER_MIN); - } -# endif // TIME_SHIELD_CPP17 - - /// \brief Converts a timestamp from minutes to milliseconds. - /// \tparam T1 The type of the output timestamp (default is ts_ms_t). - /// \tparam T2 The type of the input timestamp. - /// \param ts Timestamp in minutes. - /// \return T1 Timestamp in milliseconds. - template - TIME_SHIELD_CONSTEXPR T1 min_to_ms(T2 ts) noexcept { -# ifdef TIME_SHIELD_CPP17 - if constexpr (std::is_floating_point_v) { - return static_cast(std::round(ts * static_cast(MS_PER_MIN))); - } else { - return static_cast(ts) * static_cast(MS_PER_MIN); - } -# else - return static_cast(min_to_ms_impl(ts, typename std::conditional< - (std::is_same::value || std::is_same::value), - std::true_type, - std::false_type - >::type{})); -# endif - } - - /// \brief Converts a timestamp from milliseconds to minutes. - /// \tparam T1 The type of the output timestamp (default is int). - /// \tparam T2 The type of the input timestamp (default is ts_ms_t). - /// \param ts Timestamp in milliseconds. - /// \return T1 Timestamp in minutes. - template - TIME_SHIELD_CONSTEXPR T1 ms_to_min(T2 ts) noexcept { - return static_cast(detail::floor_div( - static_cast(ts), - static_cast(MS_PER_MIN))); - } - -//----------------------------------------------------------------------------// -// Minutes -> Seconds -//----------------------------------------------------------------------------// -# ifndef TIME_SHIELD_CPP17 - /// \brief Helper function for converting minutes to seconds (floating-point version). - /// \tparam T Type of the input timestamp. - /// \param t Timestamp in minutes. - /// \param tag std::true_type indicates a floating-point type (double or float). - /// \return ts_t Timestamp in seconds. - template - TIME_SHIELD_CONSTEXPR ts_t min_to_sec_impl(T t, std::true_type) noexcept { - return static_cast(std::round(t * static_cast(SEC_PER_MIN))); - } - - /// \brief Helper function for converting minutes to seconds (integral version). - /// \tparam T Type of the input timestamp. - /// \param t Timestamp in minutes. - /// \param tag std::false_type indicates a non-floating-point type. - /// \return ts_t Timestamp in seconds. - template - TIME_SHIELD_CONSTEXPR ts_t min_to_sec_impl(T t, std::false_type) noexcept { - return static_cast(t) * static_cast(SEC_PER_MIN); - } -# endif // TIME_SHIELD_CPP17 - - /// \brief Converts a timestamp from minutes to seconds. - /// \tparam T1 The type of the output timestamp (default is ts_t). - /// \tparam T2 The type of the input timestamp. - /// \param ts Timestamp in minutes. - /// \return T1 Timestamp in seconds. - template - TIME_SHIELD_CONSTEXPR T1 min_to_sec(T2 ts) noexcept { -# ifdef TIME_SHIELD_CPP17 - if constexpr (std::is_floating_point_v) { - return static_cast(std::round(ts * static_cast(SEC_PER_MIN))); - } else { - return static_cast(ts) * static_cast(SEC_PER_MIN); - } -# else - return static_cast(min_to_sec_impl(ts, typename std::conditional< - (std::is_same::value || std::is_same::value), - std::true_type, - std::false_type - >::type{})); -# endif - } - - /// \brief Converts a timestamp from seconds to minutes. - /// \tparam T1 The type of the output timestamp (default is int). - /// \tparam T2 The type of the input timestamp (default is ts_t). - /// \param ts Timestamp in seconds. - /// \return T1 Timestamp in minutes. - template - TIME_SHIELD_CONSTEXPR T1 sec_to_min(T2 ts) noexcept { - return static_cast(detail::floor_div( - static_cast(ts), - static_cast(SEC_PER_MIN))); - } - - /// \brief Converts a timestamp from minutes to floating-point seconds. - /// \tparam T The type of the input timestamp (default is int). - /// \param min Timestamp in minutes. - /// \return fts_t Timestamp in floating-point seconds. - template - TIME_SHIELD_CONSTEXPR fts_t min_to_fsec(T min) noexcept { - return static_cast(min) * static_cast(SEC_PER_MIN); - } - - /// \brief Converts a timestamp from seconds to floating-point minutes. - /// \tparam T The type of the input timestamp (default is ts_t). - /// \param ts Timestamp in seconds. - /// \return double Timestamp in floating-point minutes. - template - TIME_SHIELD_CONSTEXPR double sec_to_fmin(T ts) noexcept { - return static_cast(ts) / static_cast(SEC_PER_MIN); - } - -//----------------------------------------------------------------------------// -// Hours -> Milliseconds -//----------------------------------------------------------------------------// - -# ifndef TIME_SHIELD_CPP17 - /// \brief Helper function for converting hours to milliseconds (floating-point version). - /// \tparam T Type of the input timestamp. - /// \param t Timestamp in hours. - /// \param tag std::true_type indicates a floating-point type (double or float). - /// \return ts_ms_t Timestamp in milliseconds. - template - TIME_SHIELD_CONSTEXPR ts_ms_t hour_to_ms_impl(T t, std::true_type) noexcept { - return static_cast(std::round(t * static_cast(MS_PER_HOUR))); - } - - /// \brief Helper function for converting hours to milliseconds (integral version). - /// \tparam T Type of the input timestamp. - /// \param t Timestamp in hours. - /// \param tag Type tag used to select the integral overload (must be std::false_type). - /// \return ts_ms_t Timestamp in milliseconds. - template - TIME_SHIELD_CONSTEXPR ts_ms_t hour_to_ms_impl(T t, std::false_type) noexcept { - return static_cast(t) * static_cast(MS_PER_HOUR); - } -# endif // TIME_SHIELD_CPP17 - - /// \brief Converts a timestamp from hours to milliseconds. - /// \tparam T1 The type of the output timestamp (default is ts_ms_t). - /// \tparam T2 The type of the input timestamp. - /// \param ts Timestamp in hours. - /// \return T1 Timestamp in milliseconds. - template - TIME_SHIELD_CONSTEXPR T1 hour_to_ms(T2 ts) noexcept { -# ifdef TIME_SHIELD_CPP17 - if constexpr (std::is_floating_point_v) { - return static_cast(std::round(ts * static_cast(MS_PER_HOUR))); - } else { - return static_cast(ts) * static_cast(MS_PER_HOUR); - } -# else - return static_cast(hour_to_ms_impl(ts, typename std::conditional< - (std::is_same::value || std::is_same::value), - std::true_type, - std::false_type - >::type{})); -# endif - } - - /// \brief Converts a timestamp from milliseconds to hours. - /// \tparam T1 The type of the output timestamp (default is int). - /// \tparam T2 The type of the input timestamp (default is ts_ms_t). - /// \param ts Timestamp in milliseconds. - /// \return T1 Timestamp in hours. - template - TIME_SHIELD_CONSTEXPR T1 ms_to_hour(T2 ts) noexcept { - return static_cast(detail::floor_div( - static_cast(ts), - static_cast(MS_PER_HOUR))); - } - -//----------------------------------------------------------------------------// -// Hours -> Seconds -//----------------------------------------------------------------------------// - -# ifndef TIME_SHIELD_CPP17 - /// \brief Helper function for converting hours to seconds (floating-point version). - /// \tparam T Type of the input timestamp. - /// \param t Timestamp in hours. - /// \param tag std::true_type indicates a floating-point type (double or float). - /// \return ts_t Timestamp in seconds. - template - TIME_SHIELD_CONSTEXPR ts_t hour_to_sec_impl(T t, std::true_type) noexcept { - return static_cast(std::round(t * static_cast(SEC_PER_HOUR))); - } - - /// \brief Helper function for converting hours to seconds (integral version). - /// \tparam T Type of the input timestamp. - /// \param t Timestamp in hours. - /// \param tag std::false_type indicates a non-floating-point type. - /// \return ts_t Timestamp in seconds. - template - TIME_SHIELD_CONSTEXPR ts_t hour_to_sec_impl(T t, std::false_type) noexcept { - return static_cast(t) * static_cast(SEC_PER_HOUR); - } -# endif // TIME_SHIELD_CPP17 - - /// \brief Converts a timestamp from hours to seconds. - /// \tparam T1 The type of the output timestamp (default is ts_t). - /// \tparam T2 The type of the input timestamp. - /// \param ts Timestamp in hours. - /// \return T1 Timestamp in seconds. - template - TIME_SHIELD_CONSTEXPR T1 hour_to_sec(T2 ts) noexcept { -# ifdef TIME_SHIELD_CPP17 - if constexpr (std::is_floating_point_v) { - return static_cast(std::round(ts * static_cast(SEC_PER_HOUR))); - } else { - return static_cast(ts) * static_cast(SEC_PER_HOUR); - } -# else - return static_cast(hour_to_sec_impl(ts, typename std::conditional< - (std::is_same::value || std::is_same::value), - std::true_type, - std::false_type - >::type{})); -# endif - } - - /// \brief Converts a timestamp from seconds to hours. - /// \tparam T1 The type of the output timestamp (default is int). - /// \tparam T2 The type of the input timestamp (default is ts_t). - /// \param ts Timestamp in seconds. - /// \return T1 Timestamp in hours. - template - TIME_SHIELD_CONSTEXPR T1 sec_to_hour(T2 ts) noexcept { - return static_cast(detail::floor_div( - static_cast(ts), - static_cast(SEC_PER_HOUR))); - } - - /// \brief Converts a timestamp from hours to floating-point seconds. - /// \tparam T The type of the input timestamp (default is int). - /// \param hr Timestamp in hours. - /// \return fts_t Timestamp in floating-point seconds. - template - TIME_SHIELD_CONSTEXPR fts_t hour_to_fsec(T hr) noexcept { - return static_cast(hr) * static_cast(SEC_PER_HOUR); - } - - /// \brief Converts a timestamp from seconds to floating-point hours. - /// \tparam T The type of the input timestamp (default is ts_t). - /// \param ts Timestamp in seconds. - /// \return double Timestamp in floating-point hours. - template - TIME_SHIELD_CONSTEXPR double sec_to_fhour(T ts) noexcept { - return static_cast(ts) / static_cast(SEC_PER_HOUR); - } - - /// \brief Converts a 24-hour format hour to a 12-hour format. - /// \tparam T Numeric type of the hour (default is int). - /// \param hour The hour in 24-hour format to convert. - /// \return The hour in 12-hour format. - template - TIME_SHIELD_CONSTEXPR inline T hour24_to_12(T hour) noexcept { - if (hour == 0 || hour > 12) return 12; - return hour; - } - -/// \} - -}; // namespace time_shield - -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_TIME_UNIT_CONVERSIONS_HPP_INCLUDED +#endif // TIME_SHIELD_HEADER_TIME_UNIT_CONVERSIONS_HPP_INCLUDED diff --git a/include/time_shield/time_utils.hpp b/include/time_shield/time_utils.hpp index 6ec4fbf9..7c3e2c6c 100644 --- a/include/time_shield/time_utils.hpp +++ b/include/time_shield/time_utils.hpp @@ -1,346 +1,8 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_TIME_UTILS_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_TIME_UTILS_HPP_INCLUDED +#ifndef TIME_SHIELD_HEADER_TIME_UTILS_HPP_INCLUDED +#define TIME_SHIELD_HEADER_TIME_UTILS_HPP_INCLUDED -/// \file time_utils.hpp -/// \brief Header file with time-related utility functions. -/// -/// This file contains various functions used for time calculations and conversions. +#include -#include "config.hpp" -#include "types.hpp" -#include "constants.hpp" - -#include -#include // For std::numeric_limits -#include // For clock_t and timespec (POSIX) -#include // For clock(), times(), etc. -#include // For std::once_flag - -#if TIME_SHIELD_PLATFORM_WINDOWS -# ifndef WIN32_LEAN_AND_MEAN -# define WIN32_LEAN_AND_MEAN -# endif -# include -#elif TIME_SHIELD_PLATFORM_UNIX -# include -# include -# include -# include -#else -# error "Unsupported platform for get_cpu_time()" -#endif - -namespace time_shield { - - /// \ingroup time_utils - /// \brief Get the current timespec. - /// \return struct timespec The current timespec. - inline struct timespec get_timespec_impl() noexcept { - // https://en.cppreference.com/w/c/chrono/timespec_get - struct timespec ts; -# if defined(CLOCK_REALTIME) - clock_gettime(CLOCK_REALTIME, &ts); // POSIX implementation -# else - timespec_get(&ts, TIME_UTC); -# endif - return ts; - } - - /// \ingroup time_utils - /// \brief Get current real time in microseconds using a platform-specific method. - /// - /// On Windows this function combines `QueryPerformanceCounter` - /// (high-resolution monotonic clock) with `GetSystemTimeAsFileTime` to compute an accurate, - /// stable UTC timestamp. The base time is initialized only once per process (lazy init). - /// On Unix-like systems a realtime anchor is captured once and combined with a - /// high-resolution monotonic clock to compute stable timestamps. - /// - /// \return Current UTC timestamp in microseconds. - inline int64_t now_realtime_us() { -# if TIME_SHIELD_PLATFORM_WINDOWS - static std::once_flag init_flag; - static int64_t s_perf_freq = 0; - static int64_t s_anchor_perf = 0; - static int64_t s_anchor_realtime_us = 0; - - std::call_once(init_flag, []() { - LARGE_INTEGER freq = {}; - LARGE_INTEGER counter = {}; - ::QueryPerformanceFrequency(&freq); - ::QueryPerformanceCounter(&counter); - - s_perf_freq = static_cast(freq.QuadPart); - s_anchor_perf = static_cast(counter.QuadPart); - - FILETIME ft; - ::GetSystemTimeAsFileTime(&ft); - - ULARGE_INTEGER uli; - uli.LowPart = ft.dwLowDateTime; - uli.HighPart = ft.dwHighDateTime; - - // 100ns ticks since 1601-01-01 to 1970-01-01 (signed constant!) - const int64_t k_epoch_diff_100ns = 116444736000000000LL; - - const int64_t filetime_100ns = static_cast(uli.QuadPart); - // Convert 100ns since 1601 -> us since 1970 - s_anchor_realtime_us = (filetime_100ns - k_epoch_diff_100ns) / 10; - }); - - LARGE_INTEGER now = {}; - ::QueryPerformanceCounter(&now); - - const int64_t now_ticks = static_cast(now.QuadPart); - const int64_t delta_ticks = now_ticks - s_anchor_perf; - - // Avoid overflow of (delta_ticks * 1000000) - const int64_t q = delta_ticks / s_perf_freq; - const int64_t r = delta_ticks % s_perf_freq; - - const int64_t delta_us = - q * 1000000LL + (r * 1000000LL) / s_perf_freq; - - return s_anchor_realtime_us + delta_us; -# else - static std::once_flag init_flag; - static int64_t s_anchor_realtime_us = 0; - static int64_t s_anchor_mono_ns = 0; - - std::call_once(init_flag, []() { - struct timespec realtime_ts{}; - struct timespec mono_ts{}; - -# if defined(CLOCK_MONOTONIC_RAW) - clock_gettime(CLOCK_MONOTONIC_RAW, &mono_ts); -# else - clock_gettime(CLOCK_MONOTONIC, &mono_ts); -# endif - clock_gettime(CLOCK_REALTIME, &realtime_ts); - - s_anchor_realtime_us = static_cast(realtime_ts.tv_sec) * 1000000LL - + realtime_ts.tv_nsec / 1000; - s_anchor_mono_ns = static_cast(mono_ts.tv_sec) * 1000000000LL - + mono_ts.tv_nsec; - }); - - struct timespec mono_now_ts{}; -# if defined(CLOCK_MONOTONIC_RAW) - clock_gettime(CLOCK_MONOTONIC_RAW, &mono_now_ts); -# else - clock_gettime(CLOCK_MONOTONIC, &mono_now_ts); -# endif - - const int64_t mono_now_ns = static_cast(mono_now_ts.tv_sec) * 1000000000LL - + mono_now_ts.tv_nsec; - const int64_t delta_ns = mono_now_ns - s_anchor_mono_ns; - return s_anchor_realtime_us + delta_ns / 1000; -# endif - } - - /// \ingroup time_utils - /// \brief Return monotonic seconds from a process-local reference. - /// - /// Uses `std::chrono::steady_clock` and returns an opaque monotonic value - /// that is only suitable for measuring intervals and deadlines. - /// - /// \return Monotonic seconds from a process-local reference. - inline ts_t monotonic_sec() noexcept { - const auto ticks = std::chrono::steady_clock::now().time_since_epoch(); - return static_cast(std::chrono::duration_cast(ticks).count()); - } - - /// \ingroup time_utils - /// \brief Return monotonic milliseconds from a process-local reference. - /// - /// Uses `std::chrono::steady_clock` and returns an opaque monotonic value - /// that is only suitable for measuring intervals and deadlines. - /// - /// \return Monotonic milliseconds from a process-local reference. - inline ts_ms_t monotonic_ms() noexcept { - const auto ticks = std::chrono::steady_clock::now().time_since_epoch(); - return static_cast(std::chrono::duration_cast(ticks).count()); - } - - /// \ingroup time_utils - /// \brief Return monotonic microseconds from a process-local reference. - /// - /// Uses `std::chrono::steady_clock` and returns an opaque monotonic value - /// that is only suitable for measuring intervals and deadlines. - /// - /// \return Monotonic microseconds from a process-local reference. - inline ts_us_t monotonic_us() noexcept { - const auto ticks = std::chrono::steady_clock::now().time_since_epoch(); - return static_cast(std::chrono::duration_cast(ticks).count()); - } - - /// \ingroup time_utils - /// \brief Get the nanosecond part of the current second. - /// \tparam T Type of the returned value (default is int). - /// \return T Nanosecond part of the current second. - template - inline T ns_of_sec() noexcept { - const struct timespec ts = get_timespec_impl(); - return static_cast(ts.tv_nsec); - } - - /// \ingroup time_utils - /// \brief Get the microsecond part of the current second. - /// \tparam T Type of the returned value (default is int). - /// \return T Microsecond part of the current second. - template - inline T us_of_sec() noexcept { - const struct timespec ts = get_timespec_impl(); - return static_cast(ts.tv_nsec / NS_PER_US); - } - - /// \ingroup time_utils - /// \brief Get the millisecond part of the current second. - /// \tparam T Type of the returned value (default is int). - /// \return T Millisecond part of the current second. - template - inline T ms_of_sec() noexcept { - const struct timespec ts = get_timespec_impl(); - return static_cast(ts.tv_nsec / NS_PER_MS); - } - - /// \brief Get the current UTC timestamp in seconds. - /// \return ts_t Current UTC timestamp in seconds. - inline ts_t ts() noexcept { - const struct timespec ts = get_timespec_impl(); - return ts.tv_sec; - } - - /// \ingroup time_utils - /// \brief Get the current UTC timestamp in seconds. - /// \return ts_t Current UTC timestamp in seconds. - inline ts_t timestamp() noexcept { - const struct timespec ts = get_timespec_impl(); - return ts.tv_sec; - } - - /// \ingroup time_utils - /// \brief Get the current UTC timestamp in floating-point seconds. - /// \return fts_t Current UTC timestamp in floating-point seconds. - inline fts_t fts() noexcept { - const struct timespec ts = get_timespec_impl(); - return static_cast(ts.tv_sec) + static_cast(ts.tv_nsec) / static_cast(NS_PER_SEC); - } - - /// \ingroup time_utils - /// \brief Get the current UTC timestamp in floating-point seconds. - /// \return fts_t Current UTC timestamp in floating-point seconds. - inline fts_t ftimestamp() noexcept { - const struct timespec ts = get_timespec_impl(); - return static_cast(ts.tv_sec) + static_cast(ts.tv_nsec) / static_cast(NS_PER_SEC); - } - - /// \ingroup time_utils - /// \brief Get the current UTC timestamp in milliseconds. - /// \return ts_ms_t Current UTC timestamp in milliseconds. - inline ts_ms_t ts_ms() noexcept { - const struct timespec ts = get_timespec_impl(); - return MS_PER_SEC * ts.tv_sec + ts.tv_nsec / NS_PER_MS; - } - - /// \ingroup time_utils - /// \brief Get the current UTC timestamp in milliseconds. - /// \return ts_ms_t Current UTC timestamp in milliseconds. - inline ts_ms_t timestamp_ms() noexcept { - const struct timespec ts = get_timespec_impl(); - return MS_PER_SEC * ts.tv_sec + ts.tv_nsec / NS_PER_MS; - } - - /// \ingroup time_utils - /// \brief Get the current UTC timestamp in milliseconds. - /// \return ts_ms_t Current UTC timestamp in milliseconds. - inline ts_ms_t now() noexcept { - const struct timespec ts = get_timespec_impl(); - return MS_PER_SEC * ts.tv_sec + ts.tv_nsec / NS_PER_MS; - } - - /// \ingroup time_utils - /// \brief Get the current UTC timestamp in microseconds. - /// \return ts_us_t Current UTC timestamp in microseconds. - inline ts_us_t ts_us() noexcept { - const struct timespec ts = get_timespec_impl(); - return US_PER_SEC * ts.tv_sec + ts.tv_nsec / NS_PER_US; - } - - /// \ingroup time_utils - /// \brief Get the current UTC timestamp in microseconds. - /// \return ts_us_t Current UTC timestamp in microseconds. - inline ts_us_t timestamp_us() noexcept { - const struct timespec ts = get_timespec_impl(); - return US_PER_SEC * ts.tv_sec + ts.tv_nsec / NS_PER_US; - } - - /// \ingroup time_utils - /// \brief Get the CPU time used by the current process. - /// \return CPU time in seconds, or NaN if not available. - /// \note This function attempts multiple fallback methods depending on platform capabilities. - /// \see https://habr.com/ru/articles/282301/ — original implementation idea - inline double get_cpu_time() noexcept { -# if TIME_SHIELD_PLATFORM_WINDOWS - FILETIME create_time{}, exit_time{}, kernel_time{}, user_time{}; - if (GetProcessTimes(GetCurrentProcess(), &create_time, &exit_time, &kernel_time, &user_time)) { - ULARGE_INTEGER li{}; - li.LowPart = user_time.dwLowDateTime; - li.HighPart = user_time.dwHighDateTime; - return static_cast(li.QuadPart) / 10000000.0; - } -# elif TIME_SHIELD_PLATFORM_UNIX - // AIX, BSD, Cygwin, HP-UX, Linux, OSX, and Solaris -# if defined(_POSIX_TIMERS) && (_POSIX_TIMERS > 0) - clockid_t id = (clockid_t)-1; -# if defined(_POSIX_CPUTIME) && (_POSIX_CPUTIME > 0) - if (clock_getcpuclockid(0, &id) != 0) { -# if defined(CLOCK_PROCESS_CPUTIME_ID) - id = CLOCK_PROCESS_CPUTIME_ID; -# elif defined(CLOCK_VIRTUAL) - id = CLOCK_VIRTUAL; -# endif - } -# elif defined(CLOCK_PROCESS_CPUTIME_ID) - id = CLOCK_PROCESS_CPUTIME_ID; -# elif defined(CLOCK_VIRTUAL) - id = CLOCK_VIRTUAL; -# endif - if (id != (clockid_t)-1) { - struct timespec ts; - if (clock_gettime(id, &ts) == 0) { - return static_cast(ts.tv_sec) + static_cast(ts.tv_nsec) / 1e9; - } - } -# endif - -# if defined(RUSAGE_SELF) - struct rusage usage{}; - if (getrusage(RUSAGE_SELF, &usage) == 0) { - return static_cast(usage.ru_utime.tv_sec) + static_cast(usage.ru_utime.tv_usec) / 1e6; - } -# endif - -# if defined(_SC_CLK_TCK) - struct tms t{}; - if (times(&t) != (clock_t)-1) { - return static_cast(t.tms_utime) / static_cast(sysconf(_SC_CLK_TCK)); - } -# endif - -# if defined(CLOCKS_PER_SEC) - clock_t cl = clock(); - if (cl != (clock_t)-1) { - return static_cast(cl) / static_cast(CLOCKS_PER_SEC); - } -# endif -# else -# warning "get_cpu_time() may not work correctly: unsupported platform" -# endif - return std::numeric_limits::quiet_NaN(); - } - -}; // namespace time_shield - -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_TIME_UTILS_HPP_INCLUDED +#endif // TIME_SHIELD_HEADER_TIME_UTILS_HPP_INCLUDED diff --git a/include/time_shield/time_zone_conversions.hpp b/include/time_shield/time_zone_conversions.hpp index e43a9e6f..9694da8a 100644 --- a/include/time_shield/time_zone_conversions.hpp +++ b/include/time_shield/time_zone_conversions.hpp @@ -1,1068 +1,8 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_TIME_ZONE_CONVERSIONS_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_TIME_ZONE_CONVERSIONS_HPP_INCLUDED +#ifndef TIME_SHIELD_HEADER_TIME_ZONE_CONVERSIONS_HPP_INCLUDED +#define TIME_SHIELD_HEADER_TIME_ZONE_CONVERSIONS_HPP_INCLUDED -/// \file time_zone_conversions.hpp -/// \brief Helpers for converting supported regional time zones and UTC. -/// \ingroup time_zone_conversions +#include -#include "date_time_struct.hpp" -#include "time_conversions.hpp" -#include "time_zone_offset.hpp" -#include "time_unit_conversions.hpp" - -namespace time_shield { - - /// \ingroup time_conversions_time_zone_conversions - /// \{ - - ts_t zone_to_gmt(ts_t local, TimeZone zone); - ts_t gmt_to_zone(ts_t gmt, TimeZone zone); - ts_ms_t zone_to_gmt_ms(ts_ms_t local_ms, TimeZone zone); - ts_ms_t gmt_to_zone_ms(ts_ms_t gmt_ms, TimeZone zone); - - /// \brief Classification of a local civil timestamp in a time zone. - enum class LocalTimeStatus { - valid, ///< Local time maps to exactly one UTC timestamp. - nonexistent, ///< Local time falls into a DST forward gap. - ambiguous, ///< Local time maps to more than one UTC timestamp. - unsupported ///< Zone or timestamp cannot be resolved. - }; - - /// \brief Policy for ambiguous local civil timestamps. - enum class AmbiguousTimePolicy { - first_occurrence, ///< Use the earliest UTC occurrence. - second_occurrence, ///< Use the latest UTC occurrence. - error ///< Return ERROR_TIMESTAMP. - }; - - /// \brief Policy for nonexistent local civil timestamps. - enum class NonexistentTimePolicy { - error, ///< Return ERROR_TIMESTAMP. - shift_forward, ///< Use the earliest valid local instant after the gap. - shift_backward ///< Use the latest valid local instant before the gap. - }; - - /// \brief Result of explicit local-time resolution. - struct LocalTimeResolution { - LocalTimeStatus status; - ts_ms_t first_utc_ms; - ts_ms_t second_utc_ms; - }; - - namespace detail { - - inline ts_t cet_to_gmt_impl(ts_t cet) { - DateTimeStruct dt = to_date_time(cet); - int max_days = num_days_in_month(dt.year, dt.mon); - const int OLD_START_SUMMER_HOUR = 2; - const int OLD_STOP_SUMMER_HOUR = 3; - const int NEW_SUMMER_HOUR = 1; - - if(dt.year < 2002) { - if(dt.mon > MAR && dt.mon < OCT) { - return cet - SEC_PER_HOUR * 2; - } else if(dt.mon == MAR) { - for(int d = max_days; d >= dt.day; --d) { - if(day_of_week_date(dt.year, MAR, d) == SUN) { - if(d == dt.day) { - if(dt.hour >= OLD_START_SUMMER_HOUR) { - return cet - SEC_PER_HOUR * 2; - } - return cet - SEC_PER_HOUR; - } - return cet - SEC_PER_HOUR; - } - } - return cet - SEC_PER_HOUR * 2; - } else if(dt.mon == OCT) { - for(int d = max_days; d >= dt.day; --d) { - if(day_of_week_date(dt.year, OCT, d) == SUN) { - if(d == dt.day) { - if(dt.hour >= OLD_STOP_SUMMER_HOUR) { - return cet - SEC_PER_HOUR; - } - return cet - SEC_PER_HOUR; - } - return cet - SEC_PER_HOUR * 2; - } - } - return cet - SEC_PER_HOUR; - } - return cet - SEC_PER_HOUR; - } - - if(dt.mon > MAR && dt.mon < OCT) { - return cet - SEC_PER_HOUR * 2; - } - if(dt.mon == MAR) { - for(int d = max_days; d >= dt.day; --d) { - if(day_of_week_date(dt.year, MAR, d) == SUN) { - if(d == dt.day) { - if(dt.hour >= (NEW_SUMMER_HOUR + 2)) { - return cet - SEC_PER_HOUR * 2; - } - return cet - SEC_PER_HOUR; - } - return cet - SEC_PER_HOUR; - } - } - return cet - SEC_PER_HOUR * 2; - } - if(dt.mon == OCT) { - for(int d = max_days; d >= dt.day; --d) { - if(day_of_week_date(dt.year, OCT, d) == SUN) { - if(d == dt.day) { - if(dt.hour >= (NEW_SUMMER_HOUR + 1)) { - return cet - SEC_PER_HOUR; - } - return cet - SEC_PER_HOUR * 2; - } - return cet - SEC_PER_HOUR * 2; - } - } - } - return cet - SEC_PER_HOUR; - } - - inline ts_t gmt_to_cet_impl(ts_t gmt) { - DateTimeStruct dt = to_date_time(gmt); - const int SWITCH_HOUR = 1; - - if(dt.mon > MAR && dt.mon < OCT) { - return gmt + SEC_PER_HOUR * 2; - } - if(dt.mon == MAR) { - int last = last_sunday_month_day(dt.year, MAR); - if(dt.day > last) { - return gmt + SEC_PER_HOUR * 2; - } - if(dt.day < last) { - return gmt + SEC_PER_HOUR; - } - if(dt.hour >= SWITCH_HOUR) { - return gmt + SEC_PER_HOUR * 2; - } - return gmt + SEC_PER_HOUR; - } - if(dt.mon == OCT) { - int last = last_sunday_month_day(dt.year, OCT); - if(dt.day > last) { - return gmt + SEC_PER_HOUR; - } - if(dt.day < last) { - return gmt + SEC_PER_HOUR * 2; - } - if(dt.hour >= SWITCH_HOUR) { - return gmt + SEC_PER_HOUR; - } - return gmt + SEC_PER_HOUR * 2; - } - return gmt + SEC_PER_HOUR; - } - - inline ts_t european_local_to_gmt(ts_t local, int standard_offset_hours) { - return cet_to_gmt_impl(local - SEC_PER_HOUR * (standard_offset_hours - 1)); - } - - inline ts_t gmt_to_european_local(ts_t gmt, int standard_offset_hours) { - return gmt_to_cet_impl(gmt) + SEC_PER_HOUR * (standard_offset_hours - 1); - } - - inline bool is_us_eastern_dst_local(const DateTimeStruct& dt) { - const int SWITCH_HOUR = 2; - int start_day = 0; - int end_day = 0; - int start_month = 0; - int end_month = 0; - - if(dt.year >= 2007) { - start_month = MAR; - end_month = NOV; - int first_sunday_march = static_cast( - 1 + (DAYS_PER_WEEK - day_of_week_date(dt.year, MAR, 1)) % DAYS_PER_WEEK); - start_day = first_sunday_march + 7; - end_day = static_cast( - 1 + (DAYS_PER_WEEK - day_of_week_date(dt.year, NOV, 1)) % DAYS_PER_WEEK); - } else { - start_month = APR; - end_month = OCT; - start_day = static_cast( - 1 + (DAYS_PER_WEEK - day_of_week_date(dt.year, APR, 1)) % DAYS_PER_WEEK); - end_day = last_sunday_month_day(dt.year, OCT); - } - - if(dt.mon > start_month && dt.mon < end_month) { - return true; - } - if(dt.mon < start_month || dt.mon > end_month) { - return false; - } - if(dt.mon == start_month) { - if(dt.day > start_day) { - return true; - } - if(dt.day < start_day) { - return false; - } - return dt.hour >= SWITCH_HOUR; - } - if(dt.mon == end_month) { - if(dt.day < end_day) { - return true; - } - if(dt.day > end_day) { - return false; - } - return dt.hour < SWITCH_HOUR; - } - return false; - } - - inline bool fixed_zone_offset(TimeZone zone, tz_t& utc_offset) { - switch(zone) { - case GMT: - case UTC: - case WET: - utc_offset = 0; - return true; - case WEST: - utc_offset = static_cast(SEC_PER_HOUR); - return true; - case CET: - utc_offset = static_cast(SEC_PER_HOUR); - return true; - case CEST: - utc_offset = static_cast(SEC_PER_HOUR * 2); - return true; - case EET: - utc_offset = static_cast(SEC_PER_HOUR * 2); - return true; - case EEST: - utc_offset = static_cast(SEC_PER_HOUR * 3); - return true; - case IST: - utc_offset = static_cast(SEC_PER_HOUR * 5 + SEC_PER_MIN * 30); - return true; - case MYT: - case WITA: - case SGT: - case PHT: - case HKT: - utc_offset = static_cast(SEC_PER_HOUR * 8); - return true; - case WIB: - case ICT: - utc_offset = static_cast(SEC_PER_HOUR * 7); - return true; - case WIT: - case JST: - case KST: - utc_offset = static_cast(SEC_PER_HOUR * 9); - return true; - case KZT: - utc_offset = static_cast(SEC_PER_HOUR * 5); - return true; - case TRT: - case BYT: - utc_offset = static_cast(SEC_PER_HOUR * 3); - return true; - case GST: - utc_offset = static_cast(SEC_PER_HOUR * 4); - return true; - default: - utc_offset = 0; - return false; - } - } - - inline ts_ms_t zone_to_gmt_ms_by_seconds(ts_ms_t local_ms, TimeZone zone) { - if(local_ms == ERROR_TIMESTAMP) { - return ERROR_TIMESTAMP; - } - const ts_t local_sec = ms_to_sec(local_ms); - const ts_ms_t remainder_ms = local_ms - sec_to_ms(local_sec); - const ts_t gmt_sec = zone_to_gmt(local_sec, zone); - if(gmt_sec == ERROR_TIMESTAMP) { - return ERROR_TIMESTAMP; - } - return sec_to_ms(gmt_sec) + remainder_ms; - } - - inline ts_ms_t gmt_to_zone_ms_by_seconds(ts_ms_t gmt_ms, TimeZone zone) { - if(gmt_ms == ERROR_TIMESTAMP) { - return ERROR_TIMESTAMP; - } - const ts_t gmt_sec = ms_to_sec(gmt_ms); - const ts_ms_t remainder_ms = gmt_ms - sec_to_ms(gmt_sec); - const ts_t local_sec = gmt_to_zone(gmt_sec, zone); - if(local_sec == ERROR_TIMESTAMP) { - return ERROR_TIMESTAMP; - } - return sec_to_ms(local_sec) + remainder_ms; - } - - } // namespace detail - - /// \brief Convert Central European Time to Greenwich Mean Time. - /// \param cet Timestamp in seconds in CET/CEST. - /// \return Timestamp in seconds in GMT. - inline ts_t cet_to_gmt(ts_t cet) { - return detail::cet_to_gmt_impl(cet); - } - - /// \brief Convert Eastern European Time to Greenwich Mean Time. - /// \param eet Timestamp in seconds in EET/EEST. - /// \return Timestamp in seconds in GMT. - inline ts_t eet_to_gmt(ts_t eet) { - return detail::european_local_to_gmt(eet, 2); - } - - /// \brief Check if local US Eastern time uses DST. - /// \param dt Local time in ET. - /// \return True if DST applies for the provided local timestamp. - inline bool is_us_eastern_dst_local(const DateTimeStruct& dt) { - return detail::is_us_eastern_dst_local(dt); - } - - /// \brief Convert US Eastern Time (New York, EST/EDT) to GMT (UTC). - /// \param et Timestamp in seconds in ET. - /// \return Timestamp in seconds in GMT (UTC). - inline ts_t et_to_gmt(ts_t et) { - DateTimeStruct dt = to_date_time(et); - bool is_dst = detail::is_us_eastern_dst_local(dt); - return et + SEC_PER_HOUR * (is_dst ? 4 : 5); - } - - /// \brief Convert GMT (UTC) to US Eastern Time (New York, EST/EDT). - /// \param gmt Timestamp in seconds in GMT (UTC). - /// \return Timestamp in seconds in ET. - inline ts_t gmt_to_et(ts_t gmt) { - ts_t et_standard = gmt - SEC_PER_HOUR * 5; - DateTimeStruct dt_local = to_date_time(et_standard); - bool is_dst = detail::is_us_eastern_dst_local(dt_local); - return gmt - SEC_PER_HOUR * (is_dst ? 4 : 5); - } - - /// \brief Convert New York Time to GMT (UTC). - /// \param ny Timestamp in seconds in ET. - /// \return Timestamp in seconds in GMT (UTC). - inline ts_t ny_to_gmt(ts_t ny) { - return et_to_gmt(ny); - } - - /// \brief Convert GMT (UTC) to New York Time. - /// \param gmt Timestamp in seconds in GMT (UTC). - /// \return Timestamp in seconds in ET. - inline ts_t gmt_to_ny(ts_t gmt) { - return gmt_to_et(gmt); - } - - /// \brief Convert US Central Time (America/Chicago, CST/CDT) to GMT (UTC). - /// \param ct Timestamp in seconds in CT. - /// \return Timestamp in seconds in GMT (UTC). - inline ts_t ct_to_gmt(ts_t ct) { - return et_to_gmt(ct + SEC_PER_HOUR); - } - - /// \brief Convert GMT (UTC) to US Central Time (America/Chicago, CST/CDT). - /// \param gmt Timestamp in seconds in GMT (UTC). - /// \return Timestamp in seconds in CT. - inline ts_t gmt_to_ct(ts_t gmt) { - return gmt_to_et(gmt) - SEC_PER_HOUR; - } - - /// \brief Convert Greenwich Mean Time to Central European Time. - /// \param gmt Timestamp in seconds in GMT. - /// \return Timestamp in seconds in CET/CEST. - inline ts_t gmt_to_cet(ts_t gmt) { - return detail::gmt_to_cet_impl(gmt); - } - - /// \brief Convert Greenwich Mean Time to Eastern European Time. - /// \param gmt Timestamp in seconds in GMT. - /// \return Timestamp in seconds in EET/EEST. - inline ts_t gmt_to_eet(ts_t gmt) { - return detail::gmt_to_european_local(gmt, 2); - } - - /// \brief Convert supported local civil time to GMT (UTC). - /// \param local Timestamp in seconds in the source time zone. - /// \param zone Source time zone. - /// \return Timestamp in seconds in GMT, or ERROR_TIMESTAMP for unsupported zones. - inline ts_t zone_to_gmt(ts_t local, TimeZone zone) { - if(local == ERROR_TIMESTAMP) { - return ERROR_TIMESTAMP; - } - - tz_t utc_offset = 0; - switch(zone) { - case GMT: - case UTC: - return local; - case WET: - return detail::european_local_to_gmt(local, 0); - case CET: - return cet_to_gmt(local); - case EET: - return eet_to_gmt(local); - case WEST: - case CEST: - case EEST: - detail::fixed_zone_offset(zone, utc_offset); - return to_utc(local, utc_offset); - case ET: - return et_to_gmt(local); - case CT: - return ct_to_gmt(local); - case UNKNOWN: - return ERROR_TIMESTAMP; - default: - if(detail::fixed_zone_offset(zone, utc_offset)) { - return to_utc(local, utc_offset); - } - return ERROR_TIMESTAMP; - } - } - - /// \brief Convert GMT (UTC) to a supported local civil time zone. - /// \param gmt Timestamp in seconds in GMT (UTC). - /// \param zone Destination time zone. - /// \return Timestamp in seconds in the destination time zone, or ERROR_TIMESTAMP for unsupported zones. - inline ts_t gmt_to_zone(ts_t gmt, TimeZone zone) { - if(gmt == ERROR_TIMESTAMP) { - return ERROR_TIMESTAMP; - } - - tz_t utc_offset = 0; - switch(zone) { - case GMT: - case UTC: - return gmt; - case WET: - return detail::gmt_to_european_local(gmt, 0); - case CET: - return gmt_to_cet(gmt); - case EET: - return gmt_to_eet(gmt); - case WEST: - case CEST: - case EEST: - detail::fixed_zone_offset(zone, utc_offset); - return to_local(gmt, utc_offset); - case ET: - return gmt_to_et(gmt); - case CT: - return gmt_to_ct(gmt); - case UNKNOWN: - return ERROR_TIMESTAMP; - default: - if(detail::fixed_zone_offset(zone, utc_offset)) { - return to_local(gmt, utc_offset); - } - return ERROR_TIMESTAMP; - } - } - - /// \brief Convert a timestamp between two supported local civil time zones. - /// \param local Timestamp in seconds in the source time zone. - /// \param from Source time zone. - /// \param to Destination time zone. - /// \return Timestamp in seconds in the destination time zone, or ERROR_TIMESTAMP on failure. - inline ts_t convert_time_zone(ts_t local, TimeZone from, TimeZone to) { - ts_t gmt = zone_to_gmt(local, from); - return gmt == ERROR_TIMESTAMP ? ERROR_TIMESTAMP - : gmt_to_zone(gmt, to); - } - - /// \brief Convert supported local civil time in milliseconds to GMT (UTC). - /// \param local_ms Timestamp in milliseconds in the source time zone. - /// \param zone Source time zone. - /// \return Timestamp in milliseconds in GMT, or ERROR_TIMESTAMP for unsupported zones. - inline ts_ms_t zone_to_gmt_ms(ts_ms_t local_ms, TimeZone zone) { - if(local_ms == ERROR_TIMESTAMP) { - return ERROR_TIMESTAMP; - } - - tz_t utc_offset = 0; - switch(zone) { - case GMT: - case UTC: - return local_ms; - case WET: - case CET: - case EET: - case ET: - case CT: - return detail::zone_to_gmt_ms_by_seconds(local_ms, zone); - case WEST: - case CEST: - case EEST: - detail::fixed_zone_offset(zone, utc_offset); - return to_utc_ms(local_ms, utc_offset); - case UNKNOWN: - return ERROR_TIMESTAMP; - default: - if(detail::fixed_zone_offset(zone, utc_offset)) { - return to_utc_ms(local_ms, utc_offset); - } - return ERROR_TIMESTAMP; - } - } - - /// \brief Convert GMT (UTC) in milliseconds to a supported local civil time zone. - /// \param gmt_ms Timestamp in milliseconds in GMT (UTC). - /// \param zone Destination time zone. - /// \return Timestamp in milliseconds in the destination time zone, or ERROR_TIMESTAMP for unsupported zones. - inline ts_ms_t gmt_to_zone_ms(ts_ms_t gmt_ms, TimeZone zone) { - if(gmt_ms == ERROR_TIMESTAMP) { - return ERROR_TIMESTAMP; - } - - tz_t utc_offset = 0; - switch(zone) { - case GMT: - case UTC: - return gmt_ms; - case WET: - case CET: - case EET: - case ET: - case CT: - return detail::gmt_to_zone_ms_by_seconds(gmt_ms, zone); - case WEST: - case CEST: - case EEST: - detail::fixed_zone_offset(zone, utc_offset); - return to_local_ms(gmt_ms, utc_offset); - case UNKNOWN: - return ERROR_TIMESTAMP; - default: - if(detail::fixed_zone_offset(zone, utc_offset)) { - return to_local_ms(gmt_ms, utc_offset); - } - return ERROR_TIMESTAMP; - } - } - - /// \brief Convert a millisecond timestamp between two supported local civil time zones. - /// \param local_ms Timestamp in milliseconds in the source time zone. - /// \param from Source time zone. - /// \param to Destination time zone. - /// \return Timestamp in milliseconds in the destination time zone, or ERROR_TIMESTAMP on failure. - inline ts_ms_t convert_time_zone_ms(ts_ms_t local_ms, TimeZone from, TimeZone to) { - ts_ms_t gmt_ms = zone_to_gmt_ms(local_ms, from); - return gmt_ms == ERROR_TIMESTAMP ? ERROR_TIMESTAMP - : gmt_to_zone_ms(gmt_ms, to); - } - - namespace detail { - - inline LocalTimeResolution make_local_time_resolution( - LocalTimeStatus status, - ts_ms_t first_utc_ms = ERROR_TIMESTAMP, - ts_ms_t second_utc_ms = ERROR_TIMESTAMP) { - LocalTimeResolution result = {status, first_utc_ms, second_utc_ms}; - return result; - } - - inline bool dynamic_dst_zone_offsets(TimeZone zone, - tz_t& first_offset, - tz_t& second_offset) { - switch(zone) { - case WET: - first_offset = 0; - second_offset = static_cast(SEC_PER_HOUR); - return true; - case CET: - first_offset = static_cast(SEC_PER_HOUR); - second_offset = static_cast(SEC_PER_HOUR * 2); - return true; - case EET: - first_offset = static_cast(SEC_PER_HOUR * 2); - second_offset = static_cast(SEC_PER_HOUR * 3); - return true; - case ET: - first_offset = static_cast(-SEC_PER_HOUR * 5); - second_offset = static_cast(-SEC_PER_HOUR * 4); - return true; - case CT: - first_offset = static_cast(-SEC_PER_HOUR * 6); - second_offset = static_cast(-SEC_PER_HOUR * 5); - return true; - default: - first_offset = 0; - second_offset = 0; - return false; - } - } - - inline tz_t dynamic_zone_standard_offset(TimeZone zone) { - switch(zone) { - case WET: - return 0; - case CET: - return static_cast(SEC_PER_HOUR); - case EET: - return static_cast(SEC_PER_HOUR * 2); - case ET: - return static_cast(-SEC_PER_HOUR * 5); - case CT: - return static_cast(-SEC_PER_HOUR * 6); - default: - return 0; - } - } - - inline bool is_european_dynamic_zone(TimeZone zone) { - return zone == WET || zone == CET || zone == EET; - } - - inline bool is_us_dynamic_zone(TimeZone zone) { - return zone == ET || zone == CT; - } - - inline bool european_dst_at_utc_ms(ts_ms_t utc_ms) { - const DateTimeStruct dt = to_date_time(ms_to_sec(utc_ms)); - const int start_day = last_sunday_month_day(dt.year, MAR); - const int end_day = last_sunday_month_day(dt.year, OCT); - const ts_ms_t start_ms = - to_timestamp_ms(dt.year, int(MAR), start_day, 1, 0, 0); - const ts_ms_t end_ms = - to_timestamp_ms(dt.year, int(OCT), end_day, 1, 0, 0); - return utc_ms >= start_ms && utc_ms < end_ms; - } - - inline int first_sunday_month_day(int year, int month) { - return static_cast( - 1 + (DAYS_PER_WEEK - day_of_week_date(year, month, 1)) % - DAYS_PER_WEEK); - } - - inline bool us_dst_at_utc_ms(TimeZone zone, ts_ms_t utc_ms) { - const DateTimeStruct dt = to_date_time(ms_to_sec(utc_ms)); - const int year = static_cast(dt.year); - int start_month = MAR; - int end_month = NOV; - int start_day = first_sunday_month_day(year, MAR) + 7; - int end_day = first_sunday_month_day(year, NOV); - - if(dt.year < 2007) { - start_month = APR; - end_month = OCT; - start_day = first_sunday_month_day(year, APR); - end_day = last_sunday_month_day(year, OCT); - } - - const tz_t standard_offset = dynamic_zone_standard_offset(zone); - const tz_t daylight_offset = - static_cast(standard_offset + SEC_PER_HOUR); - const ts_ms_t start_local = - to_timestamp_ms(dt.year, start_month, start_day, 2, 0, 0); - const ts_ms_t end_local = - to_timestamp_ms(dt.year, end_month, end_day, 2, 0, 0); - const ts_ms_t start_utc = to_utc_ms(start_local, standard_offset); - const ts_ms_t end_utc = to_utc_ms(end_local, daylight_offset); - return utc_ms >= start_utc && utc_ms < end_utc; - } - - inline bool dynamic_offset_applies_at_utc_ms(TimeZone zone, - ts_ms_t utc_ms, - tz_t offset) { - if(is_european_dynamic_zone(zone)) { - const tz_t standard_offset = dynamic_zone_standard_offset(zone); - const tz_t expected = - static_cast(standard_offset + - (european_dst_at_utc_ms(utc_ms) - ? SEC_PER_HOUR - : 0)); - return offset == expected; - } - - if(is_us_dynamic_zone(zone)) { - const tz_t standard_offset = dynamic_zone_standard_offset(zone); - const tz_t expected = - static_cast(standard_offset + - (us_dst_at_utc_ms(zone, utc_ms) - ? SEC_PER_HOUR - : 0)); - return offset == expected; - } - - return false; - } - - inline void add_local_time_candidate(LocalTimeResolution& result, - ts_ms_t local_ms, - TimeZone zone, - tz_t offset) { - const ts_ms_t candidate = to_utc_ms(local_ms, offset); - if(candidate == ERROR_TIMESTAMP || - to_local_ms(candidate, offset) != local_ms || - !dynamic_offset_applies_at_utc_ms(zone, candidate, offset) || - result.first_utc_ms == candidate || - result.second_utc_ms == candidate) { - return; - } - - if(result.first_utc_ms == ERROR_TIMESTAMP) { - result.first_utc_ms = candidate; - return; - } - - if(result.second_utc_ms == ERROR_TIMESTAMP) { - result.second_utc_ms = candidate; - } - } - - inline LocalTimeResolution resolve_with_dynamic_offsets( - ts_ms_t local_ms, - TimeZone zone, - tz_t first_offset, - tz_t second_offset) { - LocalTimeResolution result = - make_local_time_resolution(LocalTimeStatus::nonexistent); - - add_local_time_candidate(result, local_ms, zone, first_offset); - add_local_time_candidate(result, local_ms, zone, second_offset); - - if(result.first_utc_ms == ERROR_TIMESTAMP) { - return result; - } - - if(result.second_utc_ms == ERROR_TIMESTAMP) { - result.status = LocalTimeStatus::valid; - return result; - } - - if(result.second_utc_ms < result.first_utc_ms) { - const ts_ms_t tmp = result.first_utc_ms; - result.first_utc_ms = result.second_utc_ms; - result.second_utc_ms = tmp; - } - - result.status = LocalTimeStatus::ambiguous; - return result; - } - - inline bool local_time_status_has_utc(LocalTimeStatus status) { - return status == LocalTimeStatus::valid || - status == LocalTimeStatus::ambiguous; - } - - inline ts_ms_t local_time_resolution_to_utc( - const LocalTimeResolution& resolution, - AmbiguousTimePolicy ambiguous_policy) { - if(resolution.status == LocalTimeStatus::valid) { - return resolution.first_utc_ms; - } - - if(resolution.status == LocalTimeStatus::ambiguous) { - switch(ambiguous_policy) { - case AmbiguousTimePolicy::first_occurrence: - return resolution.first_utc_ms; - case AmbiguousTimePolicy::second_occurrence: - return resolution.second_utc_ms; - case AmbiguousTimePolicy::error: - default: - return ERROR_TIMESTAMP; - } - } - - return ERROR_TIMESTAMP; - } - - } // namespace detail - - /// \brief Resolve the effective UTC offset for a UTC millisecond instant. - /// \param utc_ms UTC timestamp in milliseconds. - /// \param zone Time zone to inspect. - /// \param out Receives offset in seconds on success. - /// \return True when the zone and timestamp can be resolved. - inline bool zone_offset_at_utc_ms(ts_ms_t utc_ms, - TimeZone zone, - tz_t& out) noexcept { - if(utc_ms == ERROR_TIMESTAMP || zone == UNKNOWN) { - return false; - } - - if(zone == GMT || zone == UTC) { - out = 0; - return true; - } - - if(detail::is_european_dynamic_zone(zone)) { - const tz_t standard_offset = detail::dynamic_zone_standard_offset(zone); - out = static_cast(standard_offset + - (detail::european_dst_at_utc_ms(utc_ms) - ? SEC_PER_HOUR - : 0)); - return true; - } - - if(detail::is_us_dynamic_zone(zone)) { - const tz_t standard_offset = detail::dynamic_zone_standard_offset(zone); - out = static_cast(standard_offset + - (detail::us_dst_at_utc_ms(zone, utc_ms) - ? SEC_PER_HOUR - : 0)); - return true; - } - - tz_t utc_offset = 0; - if(detail::fixed_zone_offset(zone, utc_offset)) { - out = utc_offset; - return true; - } - - return false; - } - - /// \brief Resolve the effective UTC offset for a UTC second instant. - /// \param utc UTC timestamp in seconds. - /// \param zone Time zone to inspect. - /// \param out Receives offset in seconds on success. - /// \return True when the zone and timestamp can be resolved. - inline bool zone_offset_at_utc(ts_t utc, - TimeZone zone, - tz_t& out) noexcept { - return utc == ERROR_TIMESTAMP - ? false - : zone_offset_at_utc_ms(sec_to_ms(utc), zone, out); - } - - /// \brief Resolve local civil time to zero, one, or two UTC candidates. - /// \param local_ms Local civil timestamp in milliseconds. - /// \param zone Source time zone. - /// \return Resolution status plus UTC candidates in milliseconds. - inline LocalTimeResolution resolve_local_time_ms(ts_ms_t local_ms, - TimeZone zone) { - if(local_ms == ERROR_TIMESTAMP || zone == UNKNOWN) { - return detail::make_local_time_resolution( - LocalTimeStatus::unsupported); - } - - if(zone == GMT || zone == UTC) { - return detail::make_local_time_resolution(LocalTimeStatus::valid, - local_ms); - } - - tz_t first_offset = 0; - tz_t second_offset = 0; - if(detail::dynamic_dst_zone_offsets(zone, first_offset, second_offset)) { - return detail::resolve_with_dynamic_offsets(local_ms, - zone, - first_offset, - second_offset); - } - - tz_t utc_offset = 0; - if(detail::fixed_zone_offset(zone, utc_offset)) { - return detail::make_local_time_resolution( - LocalTimeStatus::valid, - to_utc_ms(local_ms, utc_offset)); - } - - return detail::make_local_time_resolution(LocalTimeStatus::unsupported); - } - - /// \brief Resolve local civil time given in seconds. - /// - /// UTC candidates are returned in milliseconds in LocalTimeResolution. - inline LocalTimeResolution resolve_local_time(ts_t local, TimeZone zone) { - return local == ERROR_TIMESTAMP - ? detail::make_local_time_resolution( - LocalTimeStatus::unsupported) - : resolve_local_time_ms(sec_to_ms(local), zone); - } - - namespace detail { - - inline ts_ms_t shift_nonexistent_local_time_ms(ts_ms_t local_ms, - TimeZone zone, - int direction) { - const ts_ms_t window = static_cast(MS_PER_DAY) * 2; - const bool forward = direction >= 0; - ts_ms_t low = forward ? local_ms : local_ms - window; - ts_ms_t high = forward ? local_ms + window : local_ms; - - LocalTimeResolution edge = - resolve_local_time_ms(forward ? high : low, zone); - if(!local_time_status_has_utc(edge.status)) { - return ERROR_TIMESTAMP; - } - - while(high - low > 1) { - const ts_ms_t mid = low + (high - low) / 2; - const LocalTimeResolution resolution = - resolve_local_time_ms(mid, zone); - if(local_time_status_has_utc(resolution.status)) { - if(forward) { - high = mid; - } else { - low = mid; - } - } else if(forward) { - low = mid; - } else { - high = mid; - } - } - - const LocalTimeResolution shifted = - resolve_local_time_ms(forward ? high : low, zone); - return local_time_resolution_to_utc( - shifted, - AmbiguousTimePolicy::first_occurrence); - } - - } // namespace detail - - /// \brief Convert local civil time to UTC with explicit DST policies. - inline ts_ms_t zone_to_gmt_ms(ts_ms_t local_ms, - TimeZone zone, - AmbiguousTimePolicy ambiguous_policy, - NonexistentTimePolicy nonexistent_policy) { - const LocalTimeResolution resolution = - resolve_local_time_ms(local_ms, zone); - - if(resolution.status == LocalTimeStatus::nonexistent) { - switch(nonexistent_policy) { - case NonexistentTimePolicy::shift_forward: - return detail::shift_nonexistent_local_time_ms( - local_ms, - zone, - 1); - case NonexistentTimePolicy::shift_backward: - return detail::shift_nonexistent_local_time_ms( - local_ms, - zone, - -1); - case NonexistentTimePolicy::error: - default: - return ERROR_TIMESTAMP; - } - } - - return detail::local_time_resolution_to_utc(resolution, - ambiguous_policy); - } - - /// \brief Convert local civil time in seconds to UTC with explicit DST policies. - inline ts_t zone_to_gmt(ts_t local, - TimeZone zone, - AmbiguousTimePolicy ambiguous_policy, - NonexistentTimePolicy nonexistent_policy) { - const ts_ms_t utc_ms = local == ERROR_TIMESTAMP - ? ERROR_TIMESTAMP - : zone_to_gmt_ms(sec_to_ms(local), - zone, - ambiguous_policy, - nonexistent_policy); - return utc_ms == ERROR_TIMESTAMP ? ERROR_TIMESTAMP - : ms_to_sec(utc_ms); - } - - /// \brief Convert only unambiguous existing local time to UTC. - inline ts_ms_t zone_to_gmt_ms_strict(ts_ms_t local_ms, TimeZone zone) { - return zone_to_gmt_ms(local_ms, - zone, - AmbiguousTimePolicy::error, - NonexistentTimePolicy::error); - } - - /// \brief Convert only unambiguous existing local time in seconds to UTC. - inline ts_t zone_to_gmt_strict(ts_t local, TimeZone zone) { - return zone_to_gmt(local, - zone, - AmbiguousTimePolicy::error, - NonexistentTimePolicy::error); - } - - /// \brief Convert local civil time between zones with explicit DST policies. - inline ts_ms_t convert_time_zone_ms( - ts_ms_t local_ms, - TimeZone from, - TimeZone to, - AmbiguousTimePolicy ambiguous_policy, - NonexistentTimePolicy nonexistent_policy) { - const ts_ms_t gmt_ms = zone_to_gmt_ms(local_ms, - from, - ambiguous_policy, - nonexistent_policy); - return gmt_ms == ERROR_TIMESTAMP ? ERROR_TIMESTAMP - : gmt_to_zone_ms(gmt_ms, to); - } - - /// \brief Convert local civil time in seconds between zones with explicit DST policies. - inline ts_t convert_time_zone( - ts_t local, - TimeZone from, - TimeZone to, - AmbiguousTimePolicy ambiguous_policy, - NonexistentTimePolicy nonexistent_policy) { - const ts_t gmt = zone_to_gmt(local, - from, - ambiguous_policy, - nonexistent_policy); - return gmt == ERROR_TIMESTAMP ? ERROR_TIMESTAMP - : gmt_to_zone(gmt, to); - } - - inline ts_t ist_to_gmt(ts_t ist) { return zone_to_gmt(ist, IST); } - inline ts_t gmt_to_ist(ts_t gmt) { return gmt_to_zone(gmt, IST); } - - inline ts_t myt_to_gmt(ts_t myt) { return zone_to_gmt(myt, MYT); } - inline ts_t gmt_to_myt(ts_t gmt) { return gmt_to_zone(gmt, MYT); } - - inline ts_t wib_to_gmt(ts_t wib) { return zone_to_gmt(wib, WIB); } - inline ts_t gmt_to_wib(ts_t gmt) { return gmt_to_zone(gmt, WIB); } - - inline ts_t wita_to_gmt(ts_t wita) { return zone_to_gmt(wita, WITA); } - inline ts_t gmt_to_wita(ts_t gmt) { return gmt_to_zone(gmt, WITA); } - - inline ts_t wit_to_gmt(ts_t wit) { return zone_to_gmt(wit, WIT); } - inline ts_t gmt_to_wit(ts_t gmt) { return gmt_to_zone(gmt, WIT); } - - inline ts_t kzt_to_gmt(ts_t kzt) { return zone_to_gmt(kzt, KZT); } - inline ts_t gmt_to_kzt(ts_t gmt) { return gmt_to_zone(gmt, KZT); } - - inline ts_t trt_to_gmt(ts_t trt) { return zone_to_gmt(trt, TRT); } - inline ts_t gmt_to_trt(ts_t gmt) { return gmt_to_zone(gmt, TRT); } - - inline ts_t byt_to_gmt(ts_t byt) { return zone_to_gmt(byt, BYT); } - inline ts_t gmt_to_byt(ts_t gmt) { return gmt_to_zone(gmt, BYT); } - - inline ts_t sgt_to_gmt(ts_t sgt) { return zone_to_gmt(sgt, SGT); } - inline ts_t gmt_to_sgt(ts_t gmt) { return gmt_to_zone(gmt, SGT); } - - inline ts_t ict_to_gmt(ts_t ict) { return zone_to_gmt(ict, ICT); } - inline ts_t gmt_to_ict(ts_t gmt) { return gmt_to_zone(gmt, ICT); } - - inline ts_t pht_to_gmt(ts_t pht) { return zone_to_gmt(pht, PHT); } - inline ts_t gmt_to_pht(ts_t gmt) { return gmt_to_zone(gmt, PHT); } - - inline ts_t gst_to_gmt(ts_t gst) { return zone_to_gmt(gst, GST); } - inline ts_t gmt_to_gst(ts_t gmt) { return gmt_to_zone(gmt, GST); } - - inline ts_t hkt_to_gmt(ts_t hkt) { return zone_to_gmt(hkt, HKT); } - inline ts_t gmt_to_hkt(ts_t gmt) { return gmt_to_zone(gmt, HKT); } - - inline ts_t jst_to_gmt(ts_t jst) { return zone_to_gmt(jst, JST); } - inline ts_t gmt_to_jst(ts_t gmt) { return gmt_to_zone(gmt, JST); } - - inline ts_t kst_to_gmt(ts_t kst) { return zone_to_gmt(kst, KST); } - inline ts_t gmt_to_kst(ts_t gmt) { return gmt_to_zone(gmt, KST); } - - /// \brief Convert Kyiv civil time to GMT using the EET/EEST rules. - inline ts_t kyiv_to_gmt(ts_t kyiv) { return eet_to_gmt(kyiv); } - - /// \brief Convert GMT to Kyiv civil time using the EET/EEST rules. - inline ts_t gmt_to_kyiv(ts_t gmt) { return gmt_to_eet(gmt); } - - /// \} - -} // namespace time_shield - -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_TIME_ZONE_CONVERSIONS_HPP_INCLUDED +#endif // TIME_SHIELD_HEADER_TIME_ZONE_CONVERSIONS_HPP_INCLUDED diff --git a/include/time_shield/time_zone_offset.hpp b/include/time_shield/time_zone_offset.hpp index fcaf3e51..db627c38 100644 --- a/include/time_shield/time_zone_offset.hpp +++ b/include/time_shield/time_zone_offset.hpp @@ -1,77 +1,8 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_TIME_ZONE_OFFSET_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_TIME_ZONE_OFFSET_HPP_INCLUDED +#ifndef TIME_SHIELD_HEADER_TIME_ZONE_OFFSET_HPP_INCLUDED +#define TIME_SHIELD_HEADER_TIME_ZONE_OFFSET_HPP_INCLUDED -/// \file time_zone_offset.hpp -/// \brief UTC offset arithmetic helpers (UTC <-> local) and TimeZoneStruct offset extraction. -/// \ingroup time_zone_conversions -/// -/// This header provides simple, allocation-free conversions between: -/// - UTC timestamp and local timestamp using a numeric UTC offset (in seconds). -/// - UTC milliseconds and local milliseconds using the same offset. -/// -/// \note The offset is interpreted as an UTC offset in seconds, i.e.: -/// local = utc + utc_offset -/// utc = local - utc_offset -/// -/// \note If the input equals ERROR_TIMESTAMP, the functions return ERROR_TIMESTAMP unchanged. +#include -#include "config.hpp" -#include "types.hpp" -#include "time_conversions.hpp" -#include "time_zone_struct.hpp" - -namespace time_shield { - - /// \ingroup time_conversions_time_zone_conversions - /// \{ - - /// \brief Convert local timestamp (seconds) to UTC using UTC offset. - /// \param local Local timestamp in seconds. - /// \param utc_offset UTC offset in seconds (e.g. CET=+3600, MSK=+10800, EST=-18000). - /// \return UTC timestamp in seconds. If \p local equals ERROR_TIMESTAMP, returns ERROR_TIMESTAMP. - TIME_SHIELD_CONSTEXPR inline ts_t to_utc(ts_t local, tz_t utc_offset) noexcept { - return local == ERROR_TIMESTAMP ? ERROR_TIMESTAMP - : local - static_cast(utc_offset); - } - - /// \brief Convert UTC timestamp (seconds) to local time using UTC offset. - /// \param utc UTC timestamp in seconds. - /// \param utc_offset UTC offset in seconds (e.g. CET=+3600, MSK=+10800, EST=-18000). - /// \return Local timestamp in seconds. If \p utc equals ERROR_TIMESTAMP, returns ERROR_TIMESTAMP. - TIME_SHIELD_CONSTEXPR inline ts_t to_local(ts_t utc, tz_t utc_offset) noexcept { - return utc == ERROR_TIMESTAMP ? ERROR_TIMESTAMP - : utc + static_cast(utc_offset); - } - - /// \brief Convert local timestamp (milliseconds) to UTC using UTC offset. - /// \param local_ms Local timestamp in milliseconds. - /// \param utc_offset UTC offset in seconds (will be converted to milliseconds). - /// \return UTC timestamp in milliseconds. If \p local_ms equals ERROR_TIMESTAMP, returns ERROR_TIMESTAMP. - TIME_SHIELD_CONSTEXPR inline ts_ms_t to_utc_ms(ts_ms_t local_ms, tz_t utc_offset) noexcept { - return local_ms == ERROR_TIMESTAMP ? ERROR_TIMESTAMP - : local_ms - sec_to_ms(utc_offset); - } - - /// \brief Convert UTC timestamp (milliseconds) to local time using UTC offset. - /// \param utc_ms UTC timestamp in milliseconds. - /// \param utc_offset UTC offset in seconds (will be converted to milliseconds). - /// \return Local timestamp in milliseconds. If \p utc_ms equals ERROR_TIMESTAMP, returns ERROR_TIMESTAMP. - TIME_SHIELD_CONSTEXPR inline ts_ms_t to_local_ms(ts_ms_t utc_ms, tz_t utc_offset) noexcept { - return utc_ms == ERROR_TIMESTAMP ? ERROR_TIMESTAMP - : utc_ms + sec_to_ms(utc_offset); - } - - /// \brief Extract numeric UTC offset (in seconds) from TimeZoneStruct. - /// \param tz Time zone descriptor. - /// \return UTC offset in seconds (local = utc + offset). - TIME_SHIELD_CONSTEXPR inline tz_t utc_offset_of(const TimeZoneStruct& tz) noexcept { - return time_zone_struct_to_offset(tz); - } - - /// \} - -} // namespace time_shield - -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_TIME_ZONE_OFFSET_HPP_INCLUDED +#endif // TIME_SHIELD_HEADER_TIME_ZONE_OFFSET_HPP_INCLUDED diff --git a/include/time_shield/time_zone_offset_conversions.hpp b/include/time_shield/time_zone_offset_conversions.hpp index dbf8c66e..d067c9e8 100644 --- a/include/time_shield/time_zone_offset_conversions.hpp +++ b/include/time_shield/time_zone_offset_conversions.hpp @@ -1,71 +1,8 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_TIME_ZONE_OFFSET_CONVERSIONS_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_TIME_ZONE_OFFSET_CONVERSIONS_HPP_INCLUDED +#ifndef TIME_SHIELD_HEADER_TIME_ZONE_OFFSET_CONVERSIONS_HPP_INCLUDED +#define TIME_SHIELD_HEADER_TIME_ZONE_OFFSET_CONVERSIONS_HPP_INCLUDED -/// \file time_zone_offset_conversions.hpp -/// \brief Conversions between numeric offsets and TimeZoneStruct. +#include -#include "config.hpp" -#include "constants.hpp" -#include "time_zone_struct.hpp" -#include "types.hpp" - -namespace time_shield { - -/// \ingroup time_conversions -/// \{ - - /// \brief Converts an integer to a time zone structure. - /// \tparam T The type of the time zone structure (default is TimeZoneStruct). - /// \param offset The integer to convert. - /// \return A time zone structure of type T represented by the given integer. - /// \details The function assumes that the type T has members `hour`, `min`, and `is_positive`. - template - inline T to_time_zone(tz_t offset) { - const int64_t off = static_cast(offset); - const int64_t abs_val = (off < 0) ? -off : off; - - T tz; - tz.hour = static_cast(abs_val / static_cast(SEC_PER_HOUR)); - tz.min = static_cast( - (abs_val % static_cast(SEC_PER_HOUR)) / static_cast(SEC_PER_MIN) - ); - tz.is_positive = (off >= 0); - return tz; - } - - /// \brief Convert time zone struct to offset in seconds. - /// \details Expects fields: hour, min, is_positive. - template - TIME_SHIELD_CONSTEXPR inline tz_t to_tz_offset(const T& tz) noexcept { - const int sign = tz.is_positive ? 1 : -1; - const int64_t sec = static_cast(tz.hour) * SEC_PER_HOUR - + static_cast(tz.min) * SEC_PER_MIN; - return static_cast(sign * sec); - } - - /// \brief Build offset in seconds from hours/minutes. - /// \param hour Signed hours (e.g. -3, +5). - /// \param min Minutes (0..59). - TIME_SHIELD_CONSTEXPR inline tz_t tz_offset_hm(int hour, int min = 0) noexcept { - const int sign = (hour < 0) ? -1 : 1; - const int64_t ah = (hour < 0) ? -static_cast(hour) : static_cast(hour); - const int64_t am = (min < 0) ? -static_cast(min) : static_cast(min); - return static_cast(sign * (ah * SEC_PER_HOUR + am * SEC_PER_MIN)); - } - - /// \brief Check if a numeric offset is within supported bounds. - /// \details Conservative range: [-12:00, +14:00]. - TIME_SHIELD_CONSTEXPR inline bool is_valid_tz_offset(tz_t off) noexcept { - // conservative range: [-12:00, +14:00] - return off % 60 == 0 - && off >= -12 * SEC_PER_HOUR - && off <= 14 * SEC_PER_HOUR; - } - -/// \} - -}; // namespace time_shield - -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_TIME_ZONE_OFFSET_CONVERSIONS_HPP_INCLUDED +#endif // TIME_SHIELD_HEADER_TIME_ZONE_OFFSET_CONVERSIONS_HPP_INCLUDED diff --git a/include/time_shield/time_zone_struct.hpp b/include/time_shield/time_zone_struct.hpp index a0cd0446..8ec47967 100644 --- a/include/time_shield/time_zone_struct.hpp +++ b/include/time_shield/time_zone_struct.hpp @@ -1,121 +1,8 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_TIME_ZONE_STRUCT_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_TIME_ZONE_STRUCT_HPP_INCLUDED +#ifndef TIME_SHIELD_HEADER_TIME_ZONE_STRUCT_HPP_INCLUDED +#define TIME_SHIELD_HEADER_TIME_ZONE_STRUCT_HPP_INCLUDED -/// \file time_zone_struct.hpp -/// \brief Header for time zone structure and related functions. +#include -#include "config.hpp" -#include "types.hpp" -#include "constants.hpp" - -#include - -namespace time_shield { - - /// \ingroup time_structures - /// \brief Structure to represent time zone information. - /// \details - /// This structure contains the hour and minute components of a time zone offset, - /// as well as a boolean indicating whether the offset is positive or negative. - struct TimeZoneStruct { - int hour; ///< Hour component of time (0-23) - int min; ///< Minute component of time (0-59) - bool is_positive; ///< True if the time zone offset is positive, false if negative - }; - - /// \ingroup time_structures - /// \brief Creates a TimeZoneStruct instance. - /// \param hour The hour component of the time. - /// \param min The minute component of the time. - /// \param is_positive True if the time zone offset is positive, false if negative. - /// \return A TimeZoneStruct instance with the provided time components. - inline TimeZoneStruct create_time_zone_struct( - int hour, - int min, - bool is_positive = true) { - return TimeZoneStruct{hour, min, is_positive}; - } - -//------------------------------------------------------------------------------ - - /// \ingroup time_structures_time_conversions - /// \brief Converts an integer to a TimeZoneStruct. - /// \param offset The integer to convert. - /// \return A TimeZoneStruct represented by the given integer. - inline TimeZoneStruct to_time_zone_struct(tz_t offset) { - const int64_t off = static_cast(offset); - const int64_t abs_val = (off < 0) ? -off : off; - - const int hour = static_cast(abs_val / static_cast(SEC_PER_HOUR)); - const int min = static_cast((abs_val % static_cast(SEC_PER_HOUR)) / - static_cast(SEC_PER_MIN)); - - return TimeZoneStruct{hour, min, off >= 0}; - } - - - /// \ingroup time_structures_time_conversions - /// \brief Alias for to_time_zone_struct function. - /// \copydoc to_time_zone_struct - inline TimeZoneStruct to_tz(tz_t offset) { - return to_time_zone_struct(offset); - } - -//------------------------------------------------------------------------------ - - /// \ingroup time_structures_time_formatting - /// \brief Converts a TimeZoneStruct to a string representation. - /// \param tz The TimeZoneStruct to convert. - /// \return A string representation of the TimeZoneStruct. - inline std::string time_zone_struct_to_string(const TimeZoneStruct& tz) { - char sign = tz.is_positive ? '+' : '-'; - return std::string(1, sign) + (tz.hour < 10 ? "0" : "") + std::to_string(tz.hour) + ":" + (tz.min < 10 ? "0" : "") + std::to_string(tz.min); - } - - /// \ingroup time_structures_time_formatting - /// \brief Alias for time_zone_struct_to_string function. - /// \copydoc time_zone_struct_to_string - inline std::string to_string(const TimeZoneStruct& tz) { - return time_zone_struct_to_string(tz); - } - - /// \ingroup time_structures_time_formatting - /// \brief Alias for time_zone_struct_to_string function. - /// \copydoc time_zone_struct_to_string - inline std::string to_str(const TimeZoneStruct& tz) { - return time_zone_struct_to_string(tz); - } - -//------------------------------------------------------------------------------ - - /// \ingroup time_structures_time_conversions - /// \brief Convert a TimeZoneStruct to a numeric UTC offset (seconds). - /// \param tz Time zone descriptor. - /// \return UTC offset in seconds (local = utc + offset). - TIME_SHIELD_CONSTEXPR inline tz_t time_zone_struct_to_offset(const TimeZoneStruct& tz) noexcept { - return tz.is_positive - ? static_cast( static_cast(tz.hour) * static_cast(SEC_PER_HOUR) - + static_cast(tz.min) * static_cast(SEC_PER_MIN) ) - : static_cast(-( static_cast(tz.hour) * static_cast(SEC_PER_HOUR) - + static_cast(tz.min) * static_cast(SEC_PER_MIN) )); - } - - /// \ingroup time_structures_time_conversions - /// \brief Alias for time_zone_struct_to_offset. - /// \copydoc time_zone_struct_to_offset - TIME_SHIELD_CONSTEXPR inline tz_t tz_to_offset(const TimeZoneStruct& tz) noexcept { - return time_zone_struct_to_offset(tz); - } - - /// \ingroup time_structures_time_conversions - /// \brief Alias for time_zone_struct_to_offset. - /// \copydoc time_zone_struct_to_offset - TIME_SHIELD_CONSTEXPR inline tz_t to_offset(const TimeZoneStruct& tz) noexcept { - return time_zone_struct_to_offset(tz); - } - -}; // namespace time_shield - -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_TIME_ZONE_STRUCT_HPP_INCLUDED +#endif // TIME_SHIELD_HEADER_TIME_ZONE_STRUCT_HPP_INCLUDED diff --git a/include/time_shield/timers.hpp b/include/time_shield/timers.hpp new file mode 100644 index 00000000..1f712056 --- /dev/null +++ b/include/time_shield/timers.hpp @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: MIT +#pragma once +#ifndef TIME_SHIELD_HEADER_TIMERS_HPP_INCLUDED +#define TIME_SHIELD_HEADER_TIMERS_HPP_INCLUDED + +#include +#include +#include +#include +#include + +#endif // TIME_SHIELD_HEADER_TIMERS_HPP_INCLUDED diff --git a/include/time_shield/timers/CpuTickTimer.hpp b/include/time_shield/timers/CpuTickTimer.hpp new file mode 100644 index 00000000..8e3d47b6 --- /dev/null +++ b/include/time_shield/timers/CpuTickTimer.hpp @@ -0,0 +1,137 @@ +// SPDX-License-Identifier: MIT +#pragma once +#ifndef TIME_SHIELD_HEADER_TIMERS_CPUTICKTIMER_HPP_INCLUDED +#define TIME_SHIELD_HEADER_TIMERS_CPUTICKTIMER_HPP_INCLUDED + +/// \file CpuTickTimer.hpp +/// \brief Helper class for measuring CPU time using get_cpu_time(). + +#include + +#include +#include + +namespace time_shield { + + /// \ingroup time_utils + /// \brief Timer that measures CPU time ticks using get_cpu_time(). + /// \details Class is intended for single-threaded use and assumes that + /// get_cpu_time() is monotonic within the current process or thread. For + /// long-running measurements (for example, durations longer than a day) it + /// is recommended to periodically call record_sample() or restart() to + /// limit floating-point precision loss. + /// \note All reported durations are expressed in CPU tick units provided + /// by get_cpu_time(). + class CpuTickTimer { + public: + /// \brief Construct timer and optionally start it immediately. + /// \param is_start_immediately Indicates whether the timer should start right away. + explicit CpuTickTimer(bool is_start_immediately = true) noexcept { + if (is_start_immediately) { + start(); + } + } + + /// \brief Start measuring CPU time. + void start() noexcept { + m_start_ticks = get_cpu_time(); + m_end_ticks = m_start_ticks; + m_is_running = true; + } + + /// \brief Restart timer and reset collected statistics. + void restart() noexcept { + reset_samples(); + start(); + } + + /// \brief Stop measuring CPU time and freeze elapsed ticks. + void stop() noexcept { + if (m_is_running) { + m_end_ticks = get_cpu_time(); + m_is_running = false; + } + } + + /// \brief Get elapsed CPU ticks since the last start. + /// \return Elapsed CPU tick units produced by get_cpu_time(). + TIME_SHIELD_NODISCARD double elapsed() const noexcept { + const double final_ticks = m_is_running ? get_cpu_time() : m_end_ticks; + return final_ticks - m_start_ticks; + } + + /// \brief Record sample using elapsed ticks and restart timer. + /// \return Collected sample value in CPU tick units or 0.0 when the + /// timer is not running. + double record_sample() noexcept { + if (!m_is_running) { + start(); + m_last_sample_ticks = 0.0; + return 0.0; + } + + const double now_ticks = get_cpu_time(); + m_last_sample_ticks = now_ticks - m_start_ticks; + m_start_ticks = now_ticks; + + accumulate_ticks(m_last_sample_ticks); + ++m_sample_count; + + return m_last_sample_ticks; + } + + /// \brief Reset collected samples without touching running state. + void reset_samples() noexcept { + m_total_ticks = 0.0; + m_total_compensation = 0.0; + m_last_sample_ticks = 0.0; + m_sample_count = 0; + } + + /// \brief Get the number of recorded samples. + /// \return Count of recorded samples. + TIME_SHIELD_NODISCARD std::size_t sample_count() const noexcept { + return m_sample_count; + } + + /// \brief Get total recorded CPU ticks across samples. + /// \return Sum of recorded CPU tick units. + TIME_SHIELD_NODISCARD double total_ticks() const noexcept { + return m_total_ticks; + } + + /// \brief Get average CPU ticks per sample. + /// \return Average CPU tick units or NaN if there are no samples. + TIME_SHIELD_NODISCARD double average_ticks() const noexcept { + if (m_sample_count == 0U) { + return std::numeric_limits::quiet_NaN(); + } + return m_total_ticks / static_cast(m_sample_count); + } + + /// \brief Get ticks collected during the last recorded sample. + /// \return Ticks from the most recent sample in CPU tick units. + TIME_SHIELD_NODISCARD double last_sample_ticks() const noexcept { + return m_last_sample_ticks; + } + + private: + void accumulate_ticks(double sample_ticks) noexcept { + const double compensated = sample_ticks - m_total_compensation; + const double updated_total = m_total_ticks + compensated; + m_total_compensation = (updated_total - m_total_ticks) - compensated; + m_total_ticks = updated_total; + } + + double m_start_ticks { 0.0 }; + double m_end_ticks { 0.0 }; + double m_total_ticks { 0.0 }; + double m_total_compensation { 0.0 }; + double m_last_sample_ticks { 0.0 }; + std::size_t m_sample_count { 0 }; + bool m_is_running { false }; + }; + +} // namespace time_shield + +#endif // TIME_SHIELD_HEADER_TIMERS_CPUTICKTIMER_HPP_INCLUDED diff --git a/include/time_shield/timers/DeadlineTimer.hpp b/include/time_shield/timers/DeadlineTimer.hpp new file mode 100644 index 00000000..5e645395 --- /dev/null +++ b/include/time_shield/timers/DeadlineTimer.hpp @@ -0,0 +1,243 @@ +// SPDX-License-Identifier: MIT +#pragma once +#ifndef TIME_SHIELD_HEADER_TIMERS_DEADLINETIMER_HPP_INCLUDED +#define TIME_SHIELD_HEADER_TIMERS_DEADLINETIMER_HPP_INCLUDED + +/// \file DeadlineTimer.hpp +/// \brief Monotonic deadline timer utility similar to Qt's QDeadlineTimer. +/// +/// DeadlineTimer provides a lightweight helper around std::chrono::steady_clock +/// that tracks an absolute deadline and exposes helpers to query the remaining +/// time or whether the deadline has already passed. + +#include + +#include + +namespace time_shield { + + /// \brief Helper that models a monotonic deadline for timeout management. + /// + /// DeadlineTimer invariants: + /// * Not thread-safe. Access must stay within a single thread. + /// * `start(timeout <= 0)` marks the deadline as already due. + /// * Use `set_forever()` to represent "no timeout" semantics. + class DeadlineTimer { + public: + using clock = std::chrono::steady_clock; + using duration = clock::duration; + using time_point = clock::time_point; + + /// \brief Constructs an inactive timer. + DeadlineTimer() noexcept = default; + + /// \brief Constructs a timer with the specified absolute deadline. + explicit DeadlineTimer(time_point deadline) noexcept { + start(deadline); + } + + /// \brief Constructs a timer that expires after the given timeout. + template + explicit DeadlineTimer(std::chrono::duration timeout) noexcept { + start(timeout); + } + + /// \brief Constructs a timer that expires after the given number of milliseconds. + explicit DeadlineTimer(ts_ms_t timeout_ms) noexcept { + start_ms(timeout_ms); + } + + /// \brief Creates a timer that expires after the specified timeout. + static DeadlineTimer from_timeout(duration timeout) noexcept { + DeadlineTimer timer; + timer.start(timeout); + return timer; + } + + /// \brief Creates a timer that expires after the specified timeout. + template + static DeadlineTimer from_timeout(std::chrono::duration timeout) noexcept { + DeadlineTimer timer; + timer.start(timeout); + return timer; + } + + /// \brief Creates a timer that expires after the specified number of seconds. + static DeadlineTimer from_timeout_sec(ts_t timeout_sec) noexcept { + DeadlineTimer timer; + timer.start_sec(timeout_sec); + return timer; + } + + /// \brief Creates a timer that expires after the specified number of milliseconds. + static DeadlineTimer from_timeout_ms(ts_ms_t timeout_ms) noexcept { + DeadlineTimer timer; + timer.start_ms(timeout_ms); + return timer; + } + + /// \brief Sets the absolute deadline and marks the timer as active. + void start(time_point deadline) noexcept { + m_deadline = deadline; + m_is_running = true; + } + + /// \brief Starts the timer so it expires after the specified timeout. + /// + /// Negative durations result in an immediate expiration. Durations that + /// are shorter than the steady clock tick are rounded up to a single + /// tick to preserve the monotonic nature of the timer. + template + void start(std::chrono::duration timeout) noexcept { + const time_point now = clock::now(); + if (timeout <= decltype(timeout)::zero()) { + start(now); + return; + } + + duration safe_duration = std::chrono::duration_cast(timeout); + if (safe_duration <= duration::zero()) { + safe_duration = duration(1); + } + + const time_point max_time = (time_point::max)(); + const duration max_offset = max_time - now; + if (safe_duration >= max_offset) { + start(max_time); + return; + } + + start(now + safe_duration); + } + + /// \brief Starts the timer so it expires after the specified number of seconds. + void start_sec(ts_t timeout_sec) noexcept { + start(std::chrono::seconds(timeout_sec)); + } + + /// \brief Starts the timer so it expires after the specified number of milliseconds. + void start_ms(ts_ms_t timeout_ms) noexcept { + start(std::chrono::milliseconds(timeout_ms)); + } + + /// \brief Stops the timer and invalidates the stored deadline. + void stop() noexcept { + m_is_running = false; + m_deadline = time_point{}; + } + + /// \brief Marks the timer as running forever (no timeout). + void set_forever() noexcept { + m_is_running = true; + m_deadline = (time_point::max)(); + } + + /// \brief Checks whether the timer tracks a deadline. + TIME_SHIELD_NODISCARD bool is_running() const noexcept { + return m_is_running; + } + + /// \brief Checks whether the timer is configured for an infinite timeout. + TIME_SHIELD_NODISCARD bool is_forever() const noexcept { + return m_is_running && m_deadline == (time_point::max)(); + } + + /// \brief Returns stored deadline. + TIME_SHIELD_NODISCARD time_point deadline() const noexcept { + return m_deadline; + } + + /// \brief Returns stored deadline as milliseconds since the steady epoch. + TIME_SHIELD_NODISCARD ts_ms_t deadline_ms() const noexcept { + return std::chrono::duration_cast(m_deadline.time_since_epoch()).count(); + } + + /// \brief Returns stored deadline as seconds since the steady epoch. + TIME_SHIELD_NODISCARD ts_t deadline_sec() const noexcept { + return std::chrono::duration_cast(m_deadline.time_since_epoch()).count(); + } + + /// \brief Checks if the deadline has already expired. + TIME_SHIELD_NODISCARD bool has_expired() const noexcept { + return has_expired(clock::now()); + } + + /// \brief Checks if the deadline has expired relative to the provided millisecond timestamp. + TIME_SHIELD_NODISCARD bool has_expired_ms(ts_ms_t now_ms) const noexcept { + const duration since_epoch = std::chrono::duration_cast(std::chrono::milliseconds(now_ms)); + return has_expired(time_point(since_epoch)); + } + + /// \brief Checks if the deadline has expired relative to the provided second timestamp. + TIME_SHIELD_NODISCARD bool has_expired_sec(ts_t now_sec) const noexcept { + const duration since_epoch = std::chrono::duration_cast(std::chrono::seconds(now_sec)); + return has_expired(time_point(since_epoch)); + } + + /// \brief Checks if the deadline has expired relative to the provided time point. + TIME_SHIELD_NODISCARD bool has_expired(time_point now) const noexcept { + return m_is_running && now >= m_deadline; + } + + /// \brief Returns time remaining until the deadline. + TIME_SHIELD_NODISCARD duration remaining_time() const noexcept { + return remaining_time(clock::now()); + } + + /// \brief Returns remaining time in milliseconds until the deadline. + TIME_SHIELD_NODISCARD ts_ms_t remaining_time_ms() const noexcept { + return std::chrono::duration_cast(remaining_time()).count(); + } + + /// \brief Returns remaining time in seconds until the deadline. + TIME_SHIELD_NODISCARD ts_t remaining_time_sec() const noexcept { + return std::chrono::duration_cast(remaining_time()).count(); + } + + /// \brief Returns remaining time relative to the provided time point. + /// + /// Non-running timers and already expired timers report zero duration. + TIME_SHIELD_NODISCARD duration remaining_time(time_point now) const noexcept { + if (!m_is_running || now >= m_deadline) { + return duration::zero(); + } + return m_deadline - now; + } + + /// \brief Extends deadline by the specified duration while preventing overflow. + void add(duration extend_by) noexcept { + if (!m_is_running || extend_by <= duration::zero()) { + return; + } + + const time_point now = clock::now(); + const time_point base = m_deadline > now ? m_deadline : now; + const duration max_offset = (time_point::max)() - base; + const duration safe_offset = extend_by < max_offset ? extend_by : max_offset; + m_deadline = base + safe_offset; + } + + /// \brief Extends deadline by the specified number of seconds while preventing overflow. + void add_sec(ts_t extend_by_sec) noexcept { + if (extend_by_sec <= 0) { + return; + } + add(std::chrono::seconds(extend_by_sec)); + } + + /// \brief Extends deadline by the specified number of milliseconds while preventing overflow. + void add_ms(ts_ms_t extend_by_ms) noexcept { + if (extend_by_ms <= 0) { + return; + } + add(std::chrono::milliseconds(extend_by_ms)); + } + + private: + time_point m_deadline{}; + bool m_is_running{false}; + }; + +} // namespace time_shield + +#endif // TIME_SHIELD_HEADER_TIMERS_DEADLINETIMER_HPP_INCLUDED diff --git a/include/time_shield/timers/ElapsedTimer.hpp b/include/time_shield/timers/ElapsedTimer.hpp new file mode 100644 index 00000000..fad7e4e6 --- /dev/null +++ b/include/time_shield/timers/ElapsedTimer.hpp @@ -0,0 +1,187 @@ +// SPDX-License-Identifier: MIT +#pragma once +#ifndef TIME_SHIELD_HEADER_TIMERS_ELAPSEDTIMER_HPP_INCLUDED +#define TIME_SHIELD_HEADER_TIMERS_ELAPSEDTIMER_HPP_INCLUDED + +/// \file ElapsedTimer.hpp +/// \brief High-precision elapsed time measurement helper similar to Qt's QElapsedTimer. +/// +/// ElapsedTimer provides lightweight access to std::chrono::steady_clock for +/// precise interval measurements without being affected by system clock +/// adjustments. + +#include + +#include +#include + +namespace time_shield { + + /// \brief Helper that measures elapsed monotonic time spans. + /// + /// Instances are expected to be used from a single thread without + /// additional synchronization. + class ElapsedTimer { + public: + using clock = std::chrono::steady_clock; + using duration = clock::duration; + using time_point = clock::time_point; + + /// \brief Constructs an invalid timer. + ElapsedTimer() noexcept = default; + + /// \brief Constructs a timer that starts immediately when requested. + explicit ElapsedTimer(bool start_immediately) noexcept { + if (start_immediately) { + start(); + } + } + + /// \brief Starts the timer using the current steady clock time. + void start() noexcept { + m_start_time = clock::now(); + m_is_running = true; + } + + /// \brief Restarts the timer and returns the elapsed duration so far. + TIME_SHIELD_NODISCARD duration restart() noexcept { + const time_point now = clock::now(); + const duration delta = m_is_running ? now - m_start_time : duration::zero(); + m_start_time = now; + m_is_running = true; + return delta; + } + + /// \brief Restarts the timer using a millisecond timestamp and returns elapsed milliseconds. + TIME_SHIELD_NODISCARD ts_ms_t restart_ms(ts_ms_t now_ms) noexcept { + const duration since_epoch = std::chrono::duration_cast(std::chrono::milliseconds(now_ms)); + const time_point now(since_epoch); + const duration delta = m_is_running ? now - m_start_time : duration::zero(); + m_start_time = now; + m_is_running = true; + return std::chrono::duration_cast(delta).count(); + } + + /// \brief Restarts the timer using a second timestamp and returns elapsed seconds. + TIME_SHIELD_NODISCARD ts_t restart_sec(ts_t now_sec) noexcept { + const duration since_epoch = std::chrono::duration_cast(std::chrono::seconds(now_sec)); + const time_point now(since_epoch); + const duration delta = m_is_running ? now - m_start_time : duration::zero(); + m_start_time = now; + m_is_running = true; + return std::chrono::duration_cast(delta).count(); + } + + /// \brief Invalidates the timer so subsequent elapsed() calls return zero. + void invalidate() noexcept { + m_is_running = false; + } + + /// \brief Checks whether the timer measures elapsed time. + TIME_SHIELD_NODISCARD bool is_running() const noexcept { + return m_is_running; + } + + /// \brief Alias for is_running() to match Qt naming conventions. + TIME_SHIELD_NODISCARD bool is_valid() const noexcept { + return m_is_running; + } + + /// \brief Returns start time stored by the timer. + TIME_SHIELD_NODISCARD time_point start_time() const noexcept { + return m_start_time; + } + + /// \brief Returns elapsed duration since the timer was started. + TIME_SHIELD_NODISCARD duration elapsed() const noexcept { + return elapsed(clock::now()); + } + + /// \brief Returns elapsed duration relative to the provided time point. + TIME_SHIELD_NODISCARD duration elapsed(time_point now) const noexcept { + if (!m_is_running) { + return duration::zero(); + } + return now - m_start_time; + } + + /// \brief Returns elapsed nanoseconds since the timer was started. + TIME_SHIELD_NODISCARD std::int64_t elapsed_ns() const noexcept { + return elapsed_count(); + } + + /// \brief Returns elapsed nanoseconds relative to the provided timestamp in nanoseconds. + TIME_SHIELD_NODISCARD std::int64_t elapsed_ns(std::int64_t now_ns) const noexcept { + const duration since_epoch = std::chrono::duration_cast(std::chrono::nanoseconds(now_ns)); + const time_point now(since_epoch); + return std::chrono::duration_cast(elapsed(now)).count(); + } + + /// \brief Returns elapsed milliseconds since the timer was started. + TIME_SHIELD_NODISCARD ts_ms_t elapsed_ms() const noexcept { + return elapsed_count(); + } + + /// \brief Returns elapsed milliseconds relative to the provided timestamp in milliseconds. + TIME_SHIELD_NODISCARD ts_ms_t elapsed_ms(ts_ms_t now_ms) const noexcept { + const duration since_epoch = std::chrono::duration_cast(std::chrono::milliseconds(now_ms)); + const time_point now(since_epoch); + return std::chrono::duration_cast(elapsed(now)).count(); + } + + /// \brief Returns elapsed seconds since the timer was started. + TIME_SHIELD_NODISCARD ts_t elapsed_sec() const noexcept { + return elapsed_count(); + } + + /// \brief Returns elapsed seconds relative to the provided timestamp in seconds. + TIME_SHIELD_NODISCARD ts_t elapsed_sec(ts_t now_sec) const noexcept { + const duration since_epoch = std::chrono::duration_cast(std::chrono::seconds(now_sec)); + const time_point now(since_epoch); + return std::chrono::duration_cast(elapsed(now)).count(); + } + + /// \brief Returns elapsed duration in the desired chrono duration type. + template + TIME_SHIELD_NODISCARD typename Duration::rep elapsed_count() const noexcept { + return std::chrono::duration_cast(elapsed()).count(); + } + + /// \brief Checks if the given timeout in milliseconds has expired. + TIME_SHIELD_NODISCARD bool has_expired(ts_ms_t timeout_ms) const noexcept { + if (!m_is_running) { + return false; + } + if (timeout_ms <= 0) { + return true; + } + return elapsed_ms() >= timeout_ms; + } + + /// \brief Checks if the given timeout in seconds has expired. + TIME_SHIELD_NODISCARD bool has_expired_sec(ts_t timeout_sec) const noexcept { + if (!m_is_running) { + return false; + } + if (timeout_sec <= 0) { + return true; + } + return elapsed() >= std::chrono::seconds(timeout_sec); + } + + /// \brief Returns milliseconds since the internal clock reference. + TIME_SHIELD_NODISCARD std::int64_t ms_since_reference() const noexcept { + if (!m_is_running) { + return 0; + } + return std::chrono::duration_cast(m_start_time.time_since_epoch()).count(); + } + + private: + time_point m_start_time{}; + bool m_is_running{false}; + }; + +} // namespace time_shield + +#endif // TIME_SHIELD_HEADER_TIMERS_ELAPSEDTIMER_HPP_INCLUDED diff --git a/include/time_shield/timers/TimerScheduler.hpp b/include/time_shield/timers/TimerScheduler.hpp new file mode 100644 index 00000000..f726f350 --- /dev/null +++ b/include/time_shield/timers/TimerScheduler.hpp @@ -0,0 +1,650 @@ +// SPDX-License-Identifier: MIT +#pragma once +#ifndef TIME_SHIELD_HEADER_TIMERS_TIMERSCHEDULER_HPP_INCLUDED +#define TIME_SHIELD_HEADER_TIMERS_TIMERSCHEDULER_HPP_INCLUDED + +/// \file TimerScheduler.hpp +/// \brief Timer scheduler that provides Qt-like timer functionality. +/// +/// TimerScheduler manages timers that can be processed either by a dedicated +/// worker thread or manually via process/update calls. Timers are rescheduled +/// using fixed-rate semantics, meaning the next activation time is based on the +/// stored fire time. Cancelled timers are removed lazily from the +/// internal queue, which can temporarily increase the queue size under frequent +/// start/stop cycles. + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace time_shield { + + class TimerScheduler; + class Timer; + + namespace detail { + + using TimerClock = std::chrono::steady_clock; + using TimerCallback = std::function; + + /// \brief Internal state shared between Timer and TimerScheduler. + struct TimerState { + TimerScheduler* m_scheduler = nullptr; + std::mutex m_callback_mutex; + TimerCallback m_callback; + std::atomic m_interval_ms{0}; + std::atomic m_is_single_shot{false}; + std::atomic m_is_active{false}; + std::atomic m_is_running{false}; + std::size_t m_id{0}; + std::atomic m_generation{0}; + std::atomic m_has_external_owner{false}; + }; + + inline TimerState*& current_timer_state() { + static TIME_SHIELD_THREAD_LOCAL TimerState* state = nullptr; + return state; + } + + struct RunningTimerScope { + explicit RunningTimerScope(TimerState* state) + : m_previous(current_timer_state()) { + current_timer_state() = state; + } + + ~RunningTimerScope() { + current_timer_state() = m_previous; + } + + private: + TimerState* m_previous; + }; + + /// \brief Data stored in the priority queue of scheduled timers. + struct ScheduledTimer { + ScheduledTimer() = default; + + ScheduledTimer(TimerClock::time_point fire_time, std::size_t timer_id, std::uint64_t generation) + : m_fire_time(fire_time), m_timer_id(timer_id), m_generation(generation) {} + + TimerClock::time_point m_fire_time{}; + std::size_t m_timer_id{0}; + std::uint64_t m_generation{0}; + }; + + /// \brief Comparator that orders timers by earliest fire time. + struct ScheduledComparator { + bool operator()(const ScheduledTimer& lhs, const ScheduledTimer& rhs) const { + return lhs.m_fire_time > rhs.m_fire_time; + } + }; + + /// \brief Helper structure that represents a timer ready to run. + struct DueTimer { + DueTimer() = default; + + DueTimer(TimerClock::time_point fire_time, + std::uint64_t generation, + std::shared_ptr state) + : m_fire_time(fire_time), m_generation(generation), m_state(std::move(state)) {} + + TimerClock::time_point m_fire_time{}; + std::uint64_t m_generation{0}; + std::shared_ptr m_state; + }; + + } // namespace detail + + using timer_state_ptr = std::shared_ptr; + + /// \brief Scheduler that manages timer execution. + class TimerScheduler { + public: + using clock = detail::TimerClock; + + TimerScheduler(); + ~TimerScheduler(); + + TimerScheduler(const TimerScheduler&) = delete; + TimerScheduler& operator=(const TimerScheduler&) = delete; + TimerScheduler(TimerScheduler&&) = delete; + TimerScheduler& operator=(TimerScheduler&&) = delete; + + /// \brief Starts a dedicated worker thread that processes timers. + /// + /// This method is non-blocking. It spawns a background thread that + /// waits for timers to fire and executes their callbacks. While the + /// worker thread is active, manual processing via process() or update() + /// must not be used. + void run(); + + /// \brief Requests the worker thread to stop and waits for it to exit. + void stop(); + + /// \brief Processes all timers that are ready to fire at the moment of the call. + /// + /// Method is non-blocking and does not wait for future timers. + /// It must not be called while the worker thread started by run() is + /// active. + void process(); + + /// \brief Alias for process() for compatibility with update-based loops. + void update(); + + /// \brief Returns number of timer states that remain alive. + /// + /// Method is intended for tests to verify resource cleanup. + std::size_t active_timer_count_for_testing(); + + private: + friend class Timer; + + timer_state_ptr create_timer_state(); + void destroy_timer_state(const timer_state_ptr& state); + void start_timer(const timer_state_ptr& state, clock::time_point when); + void stop_timer(const timer_state_ptr& state); + + void worker_loop(); + void collect_due_timers_locked(std::vector& due, clock::time_point now); + void execute_due_timers(std::vector& due); + void finalize_timer(const detail::DueTimer& due_timer); + + std::mutex m_mutex; + std::condition_variable m_cv; + std::thread m_thread; + bool m_is_worker_running{false}; + bool m_stop_requested{false}; + std::priority_queue, detail::ScheduledComparator> m_queue; + std::unordered_map> m_timers; + std::size_t m_next_id{1}; + }; + + /// \brief Timer that mimics the behavior of Qt timers. + class Timer { + public: + using Callback = detail::TimerCallback; + + explicit Timer(TimerScheduler& scheduler); + ~Timer(); + + Timer(const Timer&) = delete; + Timer& operator=(const Timer&) = delete; + Timer(Timer&&) = delete; + Timer& operator=(Timer&&) = delete; + + /// \brief Sets the interval used by the timer. + /// + /// Negative durations are clamped to zero. An interval of zero means + /// the timer is rescheduled immediately after firing. + template + void set_interval(std::chrono::duration interval) noexcept; + + /// \brief Returns the configured interval. + std::chrono::milliseconds interval() const noexcept; + + /// \brief Starts the timer using the configured interval. + void start(); + + /// \brief Starts the timer with the specified interval. + template + void start(std::chrono::duration interval); + + /// \brief Stops the timer. + /// + /// Operation is non-blocking and does not wait for a + /// running callback to finish. Use stop_and_wait() to synchronously + /// wait for completion. + void stop(); + + /// \brief Stops the timer and waits until an active callback finishes. + /// + /// Must not be called from inside the timer callback itself. + void stop_and_wait(); + + /// \brief Sets whether the timer should fire only once. + void set_single_shot(bool is_single_shot) noexcept; + + /// \brief Returns true if the timer fires only once. + bool is_single_shot() const noexcept; + + /// \brief Returns true if the timer is active. + bool is_active() const noexcept; + + /// \brief Returns true if the timer callback is being executed. + bool is_running() const noexcept; + + /// \brief Sets the callback that should be invoked when the timer fires. + void set_callback(Callback callback); + + /// \brief Creates a single-shot timer that invokes the callback once. + /// + /// Helper keeps the timer alive until the callback finishes. + template + static void single_shot(TimerScheduler& scheduler, + std::chrono::duration interval, + Callback callback); + + private: + TimerScheduler& m_scheduler; + timer_state_ptr m_state; + }; + + // --------------------------------------------------------------------- + // TimerScheduler inline implementation + // --------------------------------------------------------------------- + + inline TimerScheduler::TimerScheduler() = default; + + inline TimerScheduler::~TimerScheduler() { + stop(); + std::lock_guard lock(m_mutex); + for (auto& entry : m_timers) { + if (auto state = entry.second.lock()) { + std::lock_guard callback_lock(state->m_callback_mutex); + state->m_callback = {}; + } + } + m_timers.clear(); + while (!m_queue.empty()) { + m_queue.pop(); + } + } + + inline void TimerScheduler::run() { + std::lock_guard lock(m_mutex); + if (m_is_worker_running) { + return; + } + m_stop_requested = false; + m_is_worker_running = true; + m_thread = std::thread(&TimerScheduler::worker_loop, this); + } + + inline void TimerScheduler::stop() { + std::vector orphan_states; + std::thread worker_to_join; + + { + std::unique_lock lock(m_mutex); + if (m_is_worker_running) { + m_stop_requested = true; + m_cv.notify_all(); + worker_to_join = std::move(m_thread); + } else { + m_stop_requested = false; + } + + for (auto it = m_timers.begin(); it != m_timers.end();) { + auto state = it->second.lock(); + if (!state) { + it = m_timers.erase(it); + continue; + } + + if (!state->m_has_external_owner.load(std::memory_order_relaxed)) { + orphan_states.push_back(state); + it = m_timers.erase(it); + } else { + ++it; + } + } + } + + if (worker_to_join.joinable()) { + worker_to_join.join(); + } + + { + std::lock_guard lock(m_mutex); + m_is_worker_running = false; + m_stop_requested = false; + } + + for (auto& state : orphan_states) { + if (!state) { + continue; + } + std::lock_guard callback_lock(state->m_callback_mutex); + state->m_callback = {}; + state->m_is_active.store(false, std::memory_order_relaxed); + } + } + + inline void TimerScheduler::process() { + std::vector due; + { + std::lock_guard lock(m_mutex); + assert(!m_is_worker_running && "process() must not be called while the worker thread is active"); + const auto now = clock::now(); + collect_due_timers_locked(due, now); + } + execute_due_timers(due); + } + + inline void TimerScheduler::update() { + process(); + } + + inline std::size_t TimerScheduler::active_timer_count_for_testing() { + std::lock_guard lock(m_mutex); + std::size_t count = 0; + for (const auto& entry : m_timers) { + if (!entry.second.expired()) { + ++count; + } + } + return count; + } + + inline timer_state_ptr TimerScheduler::create_timer_state() { + auto state = std::make_shared(); + state->m_scheduler = this; + std::lock_guard lock(m_mutex); + state->m_id = m_next_id++; + m_timers[state->m_id] = state; + return state; + } + + inline void TimerScheduler::destroy_timer_state(const timer_state_ptr& state) { + if (!state) { + return; + } + { + std::lock_guard callback_lock(state->m_callback_mutex); + state->m_callback = {}; + } + std::lock_guard lock(m_mutex); + state->m_is_active.store(false, std::memory_order_relaxed); + state->m_generation.fetch_add(1, std::memory_order_relaxed); + if (state->m_id != 0) { + m_timers.erase(state->m_id); + } + state->m_scheduler = nullptr; + } + + inline void TimerScheduler::start_timer(const timer_state_ptr& state, clock::time_point when) { + if (!state) { + return; + } + std::lock_guard lock(m_mutex); + state->m_is_active.store(true, std::memory_order_relaxed); + const auto generation = state->m_generation.fetch_add(1, std::memory_order_relaxed) + 1; + m_queue.push(detail::ScheduledTimer{when, state->m_id, generation}); + m_cv.notify_all(); + } + + inline void TimerScheduler::stop_timer(const timer_state_ptr& state) { + if (!state) { + return; + } + std::lock_guard lock(m_mutex); + state->m_is_active.store(false, std::memory_order_relaxed); + state->m_generation.fetch_add(1, std::memory_order_relaxed); + m_cv.notify_all(); + } + + inline void TimerScheduler::worker_loop() { + std::vector due; + std::unique_lock lock(m_mutex); + while (!m_stop_requested) { + if (m_queue.empty()) { + m_cv.wait(lock, [this] { return m_stop_requested || !m_queue.empty(); }); + continue; + } + + const auto next_fire_time = m_queue.top().m_fire_time; + const bool woke_by_condition = m_cv.wait_until( + lock, + next_fire_time, + [this, next_fire_time] { + return m_stop_requested || m_queue.empty() || m_queue.top().m_fire_time < next_fire_time; + } + ); + + if (m_stop_requested) { + break; + } + + if (woke_by_condition) { + continue; + } + + const auto now = clock::now(); + collect_due_timers_locked(due, now); + + lock.unlock(); + execute_due_timers(due); + due.clear(); + lock.lock(); + } + } + + inline void TimerScheduler::collect_due_timers_locked(std::vector& due, clock::time_point now) { + while (!m_queue.empty()) { + const auto& top = m_queue.top(); + if (top.m_fire_time > now) { + break; + } + + detail::ScheduledTimer item = top; + m_queue.pop(); + + auto it = m_timers.find(item.m_timer_id); + if (it == m_timers.end()) { + continue; + } + + auto state = it->second.lock(); + if (!state) { + m_timers.erase(it); + continue; + } + + if (!state->m_is_active.load(std::memory_order_relaxed) || + state->m_generation.load(std::memory_order_relaxed) != item.m_generation) { + continue; + } + + state->m_is_running.store(true, std::memory_order_release); + due.push_back(detail::DueTimer{item.m_fire_time, item.m_generation, std::move(state)}); + } + } + + inline void TimerScheduler::execute_due_timers(std::vector& due) { + for (auto& timer : due) { + detail::TimerCallback callback; + if (timer.m_state) { + std::lock_guard callback_lock(timer.m_state->m_callback_mutex); + callback = timer.m_state->m_callback; + } + if (callback) { + detail::RunningTimerScope running_scope(timer.m_state.get()); + try { + callback(); + } catch (...) { + // TODO: integrate with logging once a logging facility is available. + } + } + finalize_timer(timer); + } + } + + inline void TimerScheduler::finalize_timer(const detail::DueTimer& due_timer) { + auto state = due_timer.m_state; + if (!state) { + return; + } + + std::unique_lock lock(m_mutex); + state->m_is_running.store(false, std::memory_order_release); + if (!state->m_is_active.load(std::memory_order_relaxed)) { + return; + } + + if (state->m_is_single_shot.load(std::memory_order_relaxed)) { + state->m_is_active.store(false, std::memory_order_relaxed); + state->m_generation.fetch_add(1, std::memory_order_relaxed); + return; + } + + if (state->m_generation.load(std::memory_order_relaxed) != due_timer.m_generation) { + return; + } + + const auto interval_ms = state->m_interval_ms.load(std::memory_order_relaxed); + const auto next_fire_time = due_timer.m_fire_time + std::chrono::milliseconds(interval_ms); + const auto next_generation = state->m_generation.fetch_add(1, std::memory_order_relaxed) + 1; + m_queue.push(detail::ScheduledTimer{next_fire_time, state->m_id, next_generation}); + m_cv.notify_all(); + } + + // --------------------------------------------------------------------- + // Timer inline implementation + // --------------------------------------------------------------------- + + inline Timer::Timer(TimerScheduler& scheduler) + : m_scheduler(scheduler), m_state(scheduler.create_timer_state()) { + if (m_state) { + m_state->m_has_external_owner.store(true, std::memory_order_relaxed); + } + } + + inline Timer::~Timer() { + if (!m_state) { + return; + } + + if (detail::current_timer_state() != m_state.get()) { + stop_and_wait(); + } else { + m_scheduler.stop_timer(m_state); + } + + m_state->m_has_external_owner.store(false, std::memory_order_relaxed); + m_scheduler.destroy_timer_state(m_state); + } + + template + void Timer::set_interval(std::chrono::duration interval) noexcept { + auto milliseconds = std::chrono::duration_cast(interval).count(); + if (milliseconds < 0) { + milliseconds = 0; + } + m_state->m_interval_ms.store(milliseconds, std::memory_order_relaxed); + } + + inline std::chrono::milliseconds Timer::interval() const noexcept { + const auto milliseconds = m_state->m_interval_ms.load(std::memory_order_relaxed); + return std::chrono::milliseconds(milliseconds); + } + + inline void Timer::start() { + const auto milliseconds = m_state->m_interval_ms.load(std::memory_order_relaxed); + const auto delay = TimerScheduler::clock::now() + std::chrono::milliseconds(milliseconds); + m_scheduler.start_timer(m_state, delay); + } + + template + void Timer::start(std::chrono::duration interval) { + set_interval(interval); + start(); + } + + inline void Timer::stop() { + m_scheduler.stop_timer(m_state); + } + + inline void Timer::stop_and_wait() { + assert(detail::current_timer_state() != m_state.get() + && "stop_and_wait() must not be called from inside callback"); + m_scheduler.stop_timer(m_state); + while (m_state->m_is_running.load(std::memory_order_acquire)) { + std::this_thread::yield(); + } + } + + inline void Timer::set_single_shot(bool is_single_shot) noexcept { + m_state->m_is_single_shot.store(is_single_shot, std::memory_order_relaxed); + } + + inline bool Timer::is_single_shot() const noexcept { + return m_state->m_is_single_shot.load(std::memory_order_relaxed); + } + + inline bool Timer::is_active() const noexcept { + return m_state->m_is_active.load(std::memory_order_relaxed); + } + + inline bool Timer::is_running() const noexcept { + return m_state->m_is_running.load(std::memory_order_relaxed); + } + + inline void Timer::set_callback(Callback callback) { + std::lock_guard lock(m_state->m_callback_mutex); + m_state->m_callback = std::move(callback); + } + + template + void Timer::single_shot(TimerScheduler& scheduler, + std::chrono::duration interval, + Callback callback) { + auto state = scheduler.create_timer_state(); + if (!state) { + return; + } + + auto milliseconds = std::chrono::duration_cast(interval).count(); + if (milliseconds < 0) { + milliseconds = 0; + } + + state->m_is_single_shot.store(true, std::memory_order_relaxed); + state->m_interval_ms.store(milliseconds, std::memory_order_relaxed); + + auto* scheduler_ptr = state->m_scheduler; + + Callback user_callback_local = std::move(callback); + + { + std::lock_guard lock(state->m_callback_mutex); + state->m_callback = [state, scheduler_ptr, user_callback_local]() mutable { + if (user_callback_local) { + user_callback_local(); + } + + auto state_ptr = state; + if (!state_ptr) { + return; + } + + { + std::lock_guard callback_lock(state_ptr->m_callback_mutex); + state_ptr->m_callback = {}; + } + + if (scheduler_ptr) { + scheduler_ptr->destroy_timer_state(state_ptr); + } + }; + } + + const auto fire_time = TimerScheduler::clock::now() + std::chrono::milliseconds(milliseconds); + scheduler.start_timer(state, fire_time); + } + +} // namespace time_shield + +#endif // TIME_SHIELD_HEADER_TIMERS_TIMERSCHEDULER_HPP_INCLUDED diff --git a/include/time_shield/timezone.hpp b/include/time_shield/timezone.hpp new file mode 100644 index 00000000..1cd48c9c --- /dev/null +++ b/include/time_shield/timezone.hpp @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT +#pragma once +#ifndef TIME_SHIELD_HEADER_TIMEZONE_HPP_INCLUDED +#define TIME_SHIELD_HEADER_TIMEZONE_HPP_INCLUDED + +#include +#include +#include +#include +#if TIME_SHIELD_ENABLE_NTP_CLIENT +# include +#endif +#include +#include + +#endif // TIME_SHIELD_HEADER_TIMEZONE_HPP_INCLUDED diff --git a/include/time_shield/timezone/ZonedClock.hpp b/include/time_shield/timezone/ZonedClock.hpp new file mode 100644 index 00000000..783b645e --- /dev/null +++ b/include/time_shield/timezone/ZonedClock.hpp @@ -0,0 +1,369 @@ +// SPDX-License-Identifier: MIT +#pragma once +#ifndef TIME_SHIELD_HEADER_TIMEZONE_ZONEDCLOCK_HPP_INCLUDED +#define TIME_SHIELD_HEADER_TIMEZONE_ZONEDCLOCK_HPP_INCLUDED + +/// \file ZonedClock.hpp +/// \brief Header-only clock wrapper for named zones, fixed offsets, and optional NTP-backed UTC time. + +#include +#include +#include +#include +#include "time_zone_conversions.hpp" + +#if TIME_SHIELD_ENABLE_NTP_CLIENT +# include +#endif + +#include +#include +#include + +namespace time_shield { + + /// \brief Stores a target local-time context backed by a named zone or fixed UTC offset. + /// + /// Class resolves the effective offset on demand. Named zones are recalculated + /// for the requested UTC instant, while numeric offsets remain fixed. Current UTC time + /// can come from the local realtime clock or from the global NTP service. + class ZonedClock final { + public: + /// \brief Construct UTC fixed-offset clock without NTP. + ZonedClock() noexcept + : m_zone(UNKNOWN) + , m_offset(0) + , m_is_named_zone(false) + , m_use_ntp(false) {} + + /// \brief Construct clock for a named zone. + /// \param zone Supported named zone. + /// \param use_ntp Use NTP-backed UTC time when true. + explicit ZonedClock(TimeZone zone, bool use_ntp = false) noexcept + : m_zone(UNKNOWN) + , m_offset(0) + , m_is_named_zone(false) + , m_use_ntp(use_ntp) { + set_zone(zone); + } + + /// \brief Construct clock for a fixed UTC offset. + /// \param utc_offset Fixed UTC offset in seconds. + /// \param use_ntp Use NTP-backed UTC time when true. + /// \throw std::invalid_argument if utc_offset is outside the supported range. + explicit ZonedClock(tz_t utc_offset, bool use_ntp = false) + : m_zone(UNKNOWN) + , m_offset(0) + , m_is_named_zone(false) + , m_use_ntp(use_ntp) { + if (!try_set_offset(utc_offset)) { + throw std::invalid_argument("Invalid UTC offset"); + } + } + + /// \brief Try to build fixed-offset clock without throwing. + /// \param utc_offset Fixed UTC offset in seconds. + /// \param out Output clock on success. + /// \return True when the offset is valid and out is updated. + static bool try_from_offset(tz_t utc_offset, ZonedClock& out) noexcept { + ZonedClock candidate; + if (!candidate.try_set_offset(utc_offset)) { + return false; + } + out = candidate; + return true; + } + + /// \brief Set the stored named zone. + /// \param zone Supported named zone. `UNKNOWN` resets the instance to fixed UTC offset `+00:00`. + void set_zone(TimeZone zone) noexcept { + if (zone == UNKNOWN) { + m_zone = UNKNOWN; + m_offset = 0; + m_is_named_zone = false; + return; + } + + m_zone = zone; + m_offset = 0; + m_is_named_zone = true; + } + + /// \brief Set the stored fixed UTC offset. + /// \param utc_offset Fixed UTC offset in seconds. + /// \return True when the offset is valid. + bool try_set_offset(tz_t utc_offset) noexcept { + if (!is_valid_tz_offset(utc_offset)) { + return false; + } + + m_zone = UNKNOWN; + m_offset = utc_offset; + m_is_named_zone = false; + return true; + } + + /// \brief Parse and set a named zone or numeric offset from string. + /// \param zone_spec Input string with ASCII trimming applied before parsing. + /// \return True when parsing succeeds. + bool try_set_zone(const std::string& zone_spec) noexcept { + const std::string trimmed = trim_ascii(zone_spec); + if (trimmed.empty()) { + return false; + } + + TimeZone parsed_zone = UNKNOWN; + if (parse_time_zone_name(trimmed, parsed_zone)) { + set_zone(parsed_zone); + return true; + } + + TimeZoneStruct parsed_offset = create_time_zone_struct(0, 0, true); + if (!parse_time_zone(trimmed, parsed_offset)) { + return false; + } + + return try_set_offset(time_zone_struct_to_offset(parsed_offset)); + } + + /// \brief Set preferred UTC source. + /// \param use_ntp Use NTP-backed UTC time when true. + void set_use_ntp(bool use_ntp) noexcept { + m_use_ntp = use_ntp; + } + + /// \brief Return true when the instance stores a named zone. + bool has_named_zone() const noexcept { + return m_is_named_zone; + } + + /// \brief Return stored named zone or `UNKNOWN` for fixed-offset mode. + TimeZone zone() const noexcept { + return m_is_named_zone ? m_zone : UNKNOWN; + } + + /// \brief Return the preferred UTC source flag. + bool use_ntp() const noexcept { + return m_use_ntp; + } + + /// \brief Return true when the global NTP service is active for this clock. + bool ntp_active() const noexcept { +#if TIME_SHIELD_ENABLE_NTP_CLIENT + return m_use_ntp && NtpTimeService::instance().running(); +#else + return false; +#endif + } + + /// \brief Return effective UTC offset in seconds for the current UTC instant. + tz_t offset_now() const noexcept { + return offset_at_utc_ms(current_utc_ms()); + } + + /// \brief Return effective UTC offset in seconds for a specific UTC instant. + /// \param utc_ms UTC timestamp in milliseconds. + /// \return Effective UTC offset in seconds. + tz_t offset_at_utc_ms(ts_ms_t utc_ms) const noexcept { + tz_t offset = 0; + return try_offset_at_utc_ms(utc_ms, offset) ? offset : 0; + } + + /// \brief Try to resolve effective UTC offset for a UTC instant. + /// \param utc_ms UTC timestamp in milliseconds. + /// \param out Receives offset in seconds on success. + /// \return True when the offset can be resolved. + bool try_offset_at_utc_ms(ts_ms_t utc_ms, tz_t& out) const noexcept { + if (utc_ms == ERROR_TIMESTAMP) { + return false; + } + + if (!m_is_named_zone) { + out = m_offset; + return true; + } + + return zone_offset_at_utc_ms(utc_ms, m_zone, out); + } + + /// \brief Resolve a local timestamp in this clock's zone. + /// \param local_ms Local civil timestamp in milliseconds. + /// \return Local-time resolution with zero, one, or two UTC candidates. + LocalTimeResolution resolve_local_time_ms(ts_ms_t local_ms) const noexcept { + if (local_ms == ERROR_TIMESTAMP) { + LocalTimeResolution result = { + LocalTimeStatus::unsupported, + ERROR_TIMESTAMP, + ERROR_TIMESTAMP + }; + return result; + } + + if (m_is_named_zone) { + return time_shield::resolve_local_time_ms(local_ms, m_zone); + } + + LocalTimeResolution result = { + LocalTimeStatus::valid, + time_shield::to_utc_ms(local_ms, m_offset), + ERROR_TIMESTAMP + }; + return result; + } + + /// \brief Convert a local timestamp in this clock's zone to UTC. + /// \param local_ms Local civil timestamp in milliseconds. + /// \param ambiguous_policy Policy for DST-fold local times. + /// \param nonexistent_policy Policy for DST-gap local times. + /// \return UTC timestamp in milliseconds, or ERROR_TIMESTAMP. + ts_ms_t to_utc_ms( + ts_ms_t local_ms, + AmbiguousTimePolicy ambiguous_policy = AmbiguousTimePolicy::error, + NonexistentTimePolicy nonexistent_policy = + NonexistentTimePolicy::error) const noexcept { + if (m_is_named_zone) { + return zone_to_gmt_ms(local_ms, + m_zone, + ambiguous_policy, + nonexistent_policy); + } + + return time_shield::to_utc_ms(local_ms, m_offset); + } + + /// \brief Return current UTC time in seconds. + ts_t utc_time_sec() const noexcept { + return static_cast(current_utc_us() / US_PER_SEC); + } + + /// \brief Return current UTC time in milliseconds. + ts_ms_t utc_time_ms() const noexcept { + return current_utc_ms(); + } + + /// \brief Return current UTC time in microseconds. + ts_us_t utc_time_us() const noexcept { + return current_utc_us(); + } + + /// \brief Return current local timestamp in seconds. + ts_t local_time_sec() const noexcept { + const ts_t utc_sec = utc_time_sec(); + return utc_sec + static_cast(offset_at_utc_ms(static_cast(utc_sec) * MS_PER_SEC)); + } + + /// \brief Return current local timestamp in milliseconds. + ts_ms_t local_time_ms() const noexcept { + const ts_ms_t utc_ms = current_utc_ms(); + return utc_ms + static_cast(offset_at_utc_ms(utc_ms)) * MS_PER_SEC; + } + + /// \brief Return current local timestamp in microseconds. + ts_us_t local_time_us() const noexcept { + const ts_us_t utc_us = current_utc_us(); + return utc_us + static_cast(offset_at_utc_ms(static_cast(utc_us / MS_PER_SEC))) * US_PER_SEC; + } + + /// \brief Return current time snapshot with resolved fixed offset. + DateTime now() const noexcept { + return from_utc_ms(current_utc_ms()); + } + + /// \brief Return a snapshot for a specific UTC instant in milliseconds. + /// \param utc_ms UTC timestamp in milliseconds. + /// \return DateTime snapshot with resolved fixed offset. + DateTime from_utc_ms(ts_ms_t utc_ms) const noexcept { + return DateTime::from_unix_ms(utc_ms, offset_at_utc_ms(utc_ms)); + } + + /// \brief Return a snapshot for a specific UTC instant in seconds. + /// \param utc_s UTC timestamp in seconds. + /// \return DateTime snapshot with resolved fixed offset. + DateTime from_utc_s(ts_t utc_s) const noexcept { + return from_utc_ms(static_cast(utc_s) * MS_PER_SEC); + } + + /// \brief Return short name of the stored named zone. + /// \return Zone abbreviation or an empty string in fixed-offset mode. + std::string zone_name() const { + return m_is_named_zone ? std::string(to_cstr(m_zone)) : std::string(); + } + + /// \brief Return human-readable zone label. + /// \return Full zone name for named zones or `UTC+/-HH:MM` for fixed offsets. + std::string zone_full_name() const { + if (m_is_named_zone) { + return to_str(m_zone, FULL_NAME); + } + return std::string("UTC") + offset_string_for_offset(m_offset); + } + + /// \brief Return effective numeric UTC offset as `+HH:MM` or `-HH:MM`. + std::string offset_string() const { + return offset_string_for_offset(offset_now()); + } + + /// \brief Return current local time formatted as ISO8601 with offset. + std::string to_iso8601() const { + return now().to_iso8601(); + } + + /// \brief Return current UTC time formatted as ISO8601 with `Z`. + std::string to_iso8601_utc() const { + return now().to_iso8601_utc(); + } + + /// \brief Format current local time using the custom formatter grammar. + /// \param fmt Formatting pattern. + /// \return Formatted string. + std::string format(const std::string& fmt) const { + return now().format(fmt); + } + + private: + static std::string trim_ascii(const std::string& value) { + std::size_t begin = 0; + std::size_t end = value.size(); + while (begin < end && is_ascii_space(value[begin])) { + ++begin; + } + while (end > begin && is_ascii_space(value[end - 1])) { + --end; + } + return value.substr(begin, end - begin); + } + + static bool is_ascii_space(char ch) noexcept { + return ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r' || ch == '\f' || ch == '\v'; + } + + static std::string offset_string_for_offset(tz_t utc_offset) { + return time_zone_struct_to_string(to_time_zone_struct(utc_offset)); + } + + ts_ms_t current_utc_ms() const noexcept { + return static_cast(current_utc_us() / 1000); + } + + ts_us_t current_utc_us() const noexcept { +#if TIME_SHIELD_ENABLE_NTP_CLIENT + if (m_use_ntp) { + if (!NtpTimeService::instance().running()) { + (void)ntp::init(30000, true); + } + return static_cast(ntp::utc_time_us()); + } +#endif + return static_cast(now_realtime_us()); + } + + private: + TimeZone m_zone; + tz_t m_offset; + bool m_is_named_zone; + bool m_use_ntp; + }; + +} // namespace time_shield + +#endif // TIME_SHIELD_HEADER_TIMEZONE_ZONEDCLOCK_HPP_INCLUDED diff --git a/include/time_shield/timezone/time_zone_conversions.hpp b/include/time_shield/timezone/time_zone_conversions.hpp new file mode 100644 index 00000000..b9879ac0 --- /dev/null +++ b/include/time_shield/timezone/time_zone_conversions.hpp @@ -0,0 +1,1066 @@ +// SPDX-License-Identifier: MIT +#pragma once +#ifndef TIME_SHIELD_HEADER_TIMEZONE_TIME_ZONE_CONVERSIONS_HPP_INCLUDED +#define TIME_SHIELD_HEADER_TIMEZONE_TIME_ZONE_CONVERSIONS_HPP_INCLUDED + +/// \file time_zone_conversions.hpp +/// \brief Helpers for converting supported regional time zones and UTC. +/// \ingroup time_zone_conversions + +#include +#include + +namespace time_shield { + + /// \ingroup time_conversions_time_zone_conversions + /// \{ + + ts_t zone_to_gmt(ts_t local, TimeZone zone); + ts_t gmt_to_zone(ts_t gmt, TimeZone zone); + ts_ms_t zone_to_gmt_ms(ts_ms_t local_ms, TimeZone zone); + ts_ms_t gmt_to_zone_ms(ts_ms_t gmt_ms, TimeZone zone); + + /// \brief Classification of a local civil timestamp in a time zone. + enum class LocalTimeStatus { + valid, ///< Local time maps to exactly one UTC timestamp. + nonexistent, ///< Local time falls into a DST forward gap. + ambiguous, ///< Local time maps to more than one UTC timestamp. + unsupported ///< Zone or timestamp cannot be resolved. + }; + + /// \brief Policy for ambiguous local civil timestamps. + enum class AmbiguousTimePolicy { + first_occurrence, ///< Use the earliest UTC occurrence. + second_occurrence, ///< Use the latest UTC occurrence. + error ///< Return ERROR_TIMESTAMP. + }; + + /// \brief Policy for nonexistent local civil timestamps. + enum class NonexistentTimePolicy { + error, ///< Return ERROR_TIMESTAMP. + shift_forward, ///< Use the earliest valid local instant after the gap. + shift_backward ///< Use the latest valid local instant before the gap. + }; + + /// \brief Result of explicit local-time resolution. + struct LocalTimeResolution { + LocalTimeStatus status; + ts_ms_t first_utc_ms; + ts_ms_t second_utc_ms; + }; + + namespace detail { + + inline ts_t cet_to_gmt_impl(ts_t cet) { + DateTimeStruct dt = to_date_time(cet); + int max_days = num_days_in_month(dt.year, dt.mon); + const int OLD_START_SUMMER_HOUR = 2; + const int OLD_STOP_SUMMER_HOUR = 3; + const int NEW_SUMMER_HOUR = 1; + + if(dt.year < 2002) { + if(dt.mon > MAR && dt.mon < OCT) { + return cet - SEC_PER_HOUR * 2; + } else if(dt.mon == MAR) { + for(int d = max_days; d >= dt.day; --d) { + if(day_of_week_date(dt.year, MAR, d) == SUN) { + if(d == dt.day) { + if(dt.hour >= OLD_START_SUMMER_HOUR) { + return cet - SEC_PER_HOUR * 2; + } + return cet - SEC_PER_HOUR; + } + return cet - SEC_PER_HOUR; + } + } + return cet - SEC_PER_HOUR * 2; + } else if(dt.mon == OCT) { + for(int d = max_days; d >= dt.day; --d) { + if(day_of_week_date(dt.year, OCT, d) == SUN) { + if(d == dt.day) { + if(dt.hour >= OLD_STOP_SUMMER_HOUR) { + return cet - SEC_PER_HOUR; + } + return cet - SEC_PER_HOUR; + } + return cet - SEC_PER_HOUR * 2; + } + } + return cet - SEC_PER_HOUR; + } + return cet - SEC_PER_HOUR; + } + + if(dt.mon > MAR && dt.mon < OCT) { + return cet - SEC_PER_HOUR * 2; + } + if(dt.mon == MAR) { + for(int d = max_days; d >= dt.day; --d) { + if(day_of_week_date(dt.year, MAR, d) == SUN) { + if(d == dt.day) { + if(dt.hour >= (NEW_SUMMER_HOUR + 2)) { + return cet - SEC_PER_HOUR * 2; + } + return cet - SEC_PER_HOUR; + } + return cet - SEC_PER_HOUR; + } + } + return cet - SEC_PER_HOUR * 2; + } + if(dt.mon == OCT) { + for(int d = max_days; d >= dt.day; --d) { + if(day_of_week_date(dt.year, OCT, d) == SUN) { + if(d == dt.day) { + if(dt.hour >= (NEW_SUMMER_HOUR + 1)) { + return cet - SEC_PER_HOUR; + } + return cet - SEC_PER_HOUR * 2; + } + return cet - SEC_PER_HOUR * 2; + } + } + } + return cet - SEC_PER_HOUR; + } + + inline ts_t gmt_to_cet_impl(ts_t gmt) { + DateTimeStruct dt = to_date_time(gmt); + const int SWITCH_HOUR = 1; + + if(dt.mon > MAR && dt.mon < OCT) { + return gmt + SEC_PER_HOUR * 2; + } + if(dt.mon == MAR) { + int last = last_sunday_month_day(dt.year, MAR); + if(dt.day > last) { + return gmt + SEC_PER_HOUR * 2; + } + if(dt.day < last) { + return gmt + SEC_PER_HOUR; + } + if(dt.hour >= SWITCH_HOUR) { + return gmt + SEC_PER_HOUR * 2; + } + return gmt + SEC_PER_HOUR; + } + if(dt.mon == OCT) { + int last = last_sunday_month_day(dt.year, OCT); + if(dt.day > last) { + return gmt + SEC_PER_HOUR; + } + if(dt.day < last) { + return gmt + SEC_PER_HOUR * 2; + } + if(dt.hour >= SWITCH_HOUR) { + return gmt + SEC_PER_HOUR; + } + return gmt + SEC_PER_HOUR * 2; + } + return gmt + SEC_PER_HOUR; + } + + inline ts_t european_local_to_gmt(ts_t local, int standard_offset_hours) { + return cet_to_gmt_impl(local - SEC_PER_HOUR * (standard_offset_hours - 1)); + } + + inline ts_t gmt_to_european_local(ts_t gmt, int standard_offset_hours) { + return gmt_to_cet_impl(gmt) + SEC_PER_HOUR * (standard_offset_hours - 1); + } + + inline bool is_us_eastern_dst_local(const DateTimeStruct& dt) { + const int SWITCH_HOUR = 2; + int start_day = 0; + int end_day = 0; + int start_month = 0; + int end_month = 0; + + if(dt.year >= 2007) { + start_month = MAR; + end_month = NOV; + int first_sunday_march = static_cast( + 1 + (DAYS_PER_WEEK - day_of_week_date(dt.year, MAR, 1)) % DAYS_PER_WEEK); + start_day = first_sunday_march + 7; + end_day = static_cast( + 1 + (DAYS_PER_WEEK - day_of_week_date(dt.year, NOV, 1)) % DAYS_PER_WEEK); + } else { + start_month = APR; + end_month = OCT; + start_day = static_cast( + 1 + (DAYS_PER_WEEK - day_of_week_date(dt.year, APR, 1)) % DAYS_PER_WEEK); + end_day = last_sunday_month_day(dt.year, OCT); + } + + if(dt.mon > start_month && dt.mon < end_month) { + return true; + } + if(dt.mon < start_month || dt.mon > end_month) { + return false; + } + if(dt.mon == start_month) { + if(dt.day > start_day) { + return true; + } + if(dt.day < start_day) { + return false; + } + return dt.hour >= SWITCH_HOUR; + } + if(dt.mon == end_month) { + if(dt.day < end_day) { + return true; + } + if(dt.day > end_day) { + return false; + } + return dt.hour < SWITCH_HOUR; + } + return false; + } + + inline bool fixed_zone_offset(TimeZone zone, tz_t& utc_offset) { + switch(zone) { + case GMT: + case UTC: + case WET: + utc_offset = 0; + return true; + case WEST: + utc_offset = static_cast(SEC_PER_HOUR); + return true; + case CET: + utc_offset = static_cast(SEC_PER_HOUR); + return true; + case CEST: + utc_offset = static_cast(SEC_PER_HOUR * 2); + return true; + case EET: + utc_offset = static_cast(SEC_PER_HOUR * 2); + return true; + case EEST: + utc_offset = static_cast(SEC_PER_HOUR * 3); + return true; + case IST: + utc_offset = static_cast(SEC_PER_HOUR * 5 + SEC_PER_MIN * 30); + return true; + case MYT: + case WITA: + case SGT: + case PHT: + case HKT: + utc_offset = static_cast(SEC_PER_HOUR * 8); + return true; + case WIB: + case ICT: + utc_offset = static_cast(SEC_PER_HOUR * 7); + return true; + case WIT: + case JST: + case KST: + utc_offset = static_cast(SEC_PER_HOUR * 9); + return true; + case KZT: + utc_offset = static_cast(SEC_PER_HOUR * 5); + return true; + case TRT: + case BYT: + utc_offset = static_cast(SEC_PER_HOUR * 3); + return true; + case GST: + utc_offset = static_cast(SEC_PER_HOUR * 4); + return true; + default: + utc_offset = 0; + return false; + } + } + + inline ts_ms_t zone_to_gmt_ms_by_seconds(ts_ms_t local_ms, TimeZone zone) { + if(local_ms == ERROR_TIMESTAMP) { + return ERROR_TIMESTAMP; + } + const ts_t local_sec = ms_to_sec(local_ms); + const ts_ms_t remainder_ms = local_ms - sec_to_ms(local_sec); + const ts_t gmt_sec = zone_to_gmt(local_sec, zone); + if(gmt_sec == ERROR_TIMESTAMP) { + return ERROR_TIMESTAMP; + } + return sec_to_ms(gmt_sec) + remainder_ms; + } + + inline ts_ms_t gmt_to_zone_ms_by_seconds(ts_ms_t gmt_ms, TimeZone zone) { + if(gmt_ms == ERROR_TIMESTAMP) { + return ERROR_TIMESTAMP; + } + const ts_t gmt_sec = ms_to_sec(gmt_ms); + const ts_ms_t remainder_ms = gmt_ms - sec_to_ms(gmt_sec); + const ts_t local_sec = gmt_to_zone(gmt_sec, zone); + if(local_sec == ERROR_TIMESTAMP) { + return ERROR_TIMESTAMP; + } + return sec_to_ms(local_sec) + remainder_ms; + } + + } // namespace detail + + /// \brief Convert Central European Time to Greenwich Mean Time. + /// \param cet Timestamp in seconds in CET/CEST. + /// \return Timestamp in seconds in GMT. + inline ts_t cet_to_gmt(ts_t cet) { + return detail::cet_to_gmt_impl(cet); + } + + /// \brief Convert Eastern European Time to Greenwich Mean Time. + /// \param eet Timestamp in seconds in EET/EEST. + /// \return Timestamp in seconds in GMT. + inline ts_t eet_to_gmt(ts_t eet) { + return detail::european_local_to_gmt(eet, 2); + } + + /// \brief Check if local US Eastern time uses DST. + /// \param dt Local time in ET. + /// \return True if DST applies for the provided local timestamp. + inline bool is_us_eastern_dst_local(const DateTimeStruct& dt) { + return detail::is_us_eastern_dst_local(dt); + } + + /// \brief Convert US Eastern Time (New York, EST/EDT) to GMT (UTC). + /// \param et Timestamp in seconds in ET. + /// \return Timestamp in seconds in GMT (UTC). + inline ts_t et_to_gmt(ts_t et) { + DateTimeStruct dt = to_date_time(et); + bool is_dst = detail::is_us_eastern_dst_local(dt); + return et + SEC_PER_HOUR * (is_dst ? 4 : 5); + } + + /// \brief Convert GMT (UTC) to US Eastern Time (New York, EST/EDT). + /// \param gmt Timestamp in seconds in GMT (UTC). + /// \return Timestamp in seconds in ET. + inline ts_t gmt_to_et(ts_t gmt) { + ts_t et_standard = gmt - SEC_PER_HOUR * 5; + DateTimeStruct dt_local = to_date_time(et_standard); + bool is_dst = detail::is_us_eastern_dst_local(dt_local); + return gmt - SEC_PER_HOUR * (is_dst ? 4 : 5); + } + + /// \brief Convert New York Time to GMT (UTC). + /// \param ny Timestamp in seconds in ET. + /// \return Timestamp in seconds in GMT (UTC). + inline ts_t ny_to_gmt(ts_t ny) { + return et_to_gmt(ny); + } + + /// \brief Convert GMT (UTC) to New York Time. + /// \param gmt Timestamp in seconds in GMT (UTC). + /// \return Timestamp in seconds in ET. + inline ts_t gmt_to_ny(ts_t gmt) { + return gmt_to_et(gmt); + } + + /// \brief Convert US Central Time (America/Chicago, CST/CDT) to GMT (UTC). + /// \param ct Timestamp in seconds in CT. + /// \return Timestamp in seconds in GMT (UTC). + inline ts_t ct_to_gmt(ts_t ct) { + return et_to_gmt(ct + SEC_PER_HOUR); + } + + /// \brief Convert GMT (UTC) to US Central Time (America/Chicago, CST/CDT). + /// \param gmt Timestamp in seconds in GMT (UTC). + /// \return Timestamp in seconds in CT. + inline ts_t gmt_to_ct(ts_t gmt) { + return gmt_to_et(gmt) - SEC_PER_HOUR; + } + + /// \brief Convert Greenwich Mean Time to Central European Time. + /// \param gmt Timestamp in seconds in GMT. + /// \return Timestamp in seconds in CET/CEST. + inline ts_t gmt_to_cet(ts_t gmt) { + return detail::gmt_to_cet_impl(gmt); + } + + /// \brief Convert Greenwich Mean Time to Eastern European Time. + /// \param gmt Timestamp in seconds in GMT. + /// \return Timestamp in seconds in EET/EEST. + inline ts_t gmt_to_eet(ts_t gmt) { + return detail::gmt_to_european_local(gmt, 2); + } + + /// \brief Convert supported local civil time to GMT (UTC). + /// \param local Timestamp in seconds in the source time zone. + /// \param zone Source time zone. + /// \return Timestamp in seconds in GMT, or ERROR_TIMESTAMP for unsupported zones. + inline ts_t zone_to_gmt(ts_t local, TimeZone zone) { + if(local == ERROR_TIMESTAMP) { + return ERROR_TIMESTAMP; + } + + tz_t utc_offset = 0; + switch(zone) { + case GMT: + case UTC: + return local; + case WET: + return detail::european_local_to_gmt(local, 0); + case CET: + return cet_to_gmt(local); + case EET: + return eet_to_gmt(local); + case WEST: + case CEST: + case EEST: + detail::fixed_zone_offset(zone, utc_offset); + return to_utc(local, utc_offset); + case ET: + return et_to_gmt(local); + case CT: + return ct_to_gmt(local); + case UNKNOWN: + return ERROR_TIMESTAMP; + default: + if(detail::fixed_zone_offset(zone, utc_offset)) { + return to_utc(local, utc_offset); + } + return ERROR_TIMESTAMP; + } + } + + /// \brief Convert GMT (UTC) to a supported local civil time zone. + /// \param gmt Timestamp in seconds in GMT (UTC). + /// \param zone Destination time zone. + /// \return Timestamp in seconds in the destination time zone, or ERROR_TIMESTAMP for unsupported zones. + inline ts_t gmt_to_zone(ts_t gmt, TimeZone zone) { + if(gmt == ERROR_TIMESTAMP) { + return ERROR_TIMESTAMP; + } + + tz_t utc_offset = 0; + switch(zone) { + case GMT: + case UTC: + return gmt; + case WET: + return detail::gmt_to_european_local(gmt, 0); + case CET: + return gmt_to_cet(gmt); + case EET: + return gmt_to_eet(gmt); + case WEST: + case CEST: + case EEST: + detail::fixed_zone_offset(zone, utc_offset); + return to_local(gmt, utc_offset); + case ET: + return gmt_to_et(gmt); + case CT: + return gmt_to_ct(gmt); + case UNKNOWN: + return ERROR_TIMESTAMP; + default: + if(detail::fixed_zone_offset(zone, utc_offset)) { + return to_local(gmt, utc_offset); + } + return ERROR_TIMESTAMP; + } + } + + /// \brief Convert a timestamp between two supported local civil time zones. + /// \param local Timestamp in seconds in the source time zone. + /// \param from Source time zone. + /// \param to Destination time zone. + /// \return Timestamp in seconds in the destination time zone, or ERROR_TIMESTAMP on failure. + inline ts_t convert_time_zone(ts_t local, TimeZone from, TimeZone to) { + ts_t gmt = zone_to_gmt(local, from); + return gmt == ERROR_TIMESTAMP ? ERROR_TIMESTAMP + : gmt_to_zone(gmt, to); + } + + /// \brief Convert supported local civil time in milliseconds to GMT (UTC). + /// \param local_ms Timestamp in milliseconds in the source time zone. + /// \param zone Source time zone. + /// \return Timestamp in milliseconds in GMT, or ERROR_TIMESTAMP for unsupported zones. + inline ts_ms_t zone_to_gmt_ms(ts_ms_t local_ms, TimeZone zone) { + if(local_ms == ERROR_TIMESTAMP) { + return ERROR_TIMESTAMP; + } + + tz_t utc_offset = 0; + switch(zone) { + case GMT: + case UTC: + return local_ms; + case WET: + case CET: + case EET: + case ET: + case CT: + return detail::zone_to_gmt_ms_by_seconds(local_ms, zone); + case WEST: + case CEST: + case EEST: + detail::fixed_zone_offset(zone, utc_offset); + return to_utc_ms(local_ms, utc_offset); + case UNKNOWN: + return ERROR_TIMESTAMP; + default: + if(detail::fixed_zone_offset(zone, utc_offset)) { + return to_utc_ms(local_ms, utc_offset); + } + return ERROR_TIMESTAMP; + } + } + + /// \brief Convert GMT (UTC) in milliseconds to a supported local civil time zone. + /// \param gmt_ms Timestamp in milliseconds in GMT (UTC). + /// \param zone Destination time zone. + /// \return Timestamp in milliseconds in the destination time zone, or ERROR_TIMESTAMP for unsupported zones. + inline ts_ms_t gmt_to_zone_ms(ts_ms_t gmt_ms, TimeZone zone) { + if(gmt_ms == ERROR_TIMESTAMP) { + return ERROR_TIMESTAMP; + } + + tz_t utc_offset = 0; + switch(zone) { + case GMT: + case UTC: + return gmt_ms; + case WET: + case CET: + case EET: + case ET: + case CT: + return detail::gmt_to_zone_ms_by_seconds(gmt_ms, zone); + case WEST: + case CEST: + case EEST: + detail::fixed_zone_offset(zone, utc_offset); + return to_local_ms(gmt_ms, utc_offset); + case UNKNOWN: + return ERROR_TIMESTAMP; + default: + if(detail::fixed_zone_offset(zone, utc_offset)) { + return to_local_ms(gmt_ms, utc_offset); + } + return ERROR_TIMESTAMP; + } + } + + /// \brief Convert a millisecond timestamp between two supported local civil time zones. + /// \param local_ms Timestamp in milliseconds in the source time zone. + /// \param from Source time zone. + /// \param to Destination time zone. + /// \return Timestamp in milliseconds in the destination time zone, or ERROR_TIMESTAMP on failure. + inline ts_ms_t convert_time_zone_ms(ts_ms_t local_ms, TimeZone from, TimeZone to) { + ts_ms_t gmt_ms = zone_to_gmt_ms(local_ms, from); + return gmt_ms == ERROR_TIMESTAMP ? ERROR_TIMESTAMP + : gmt_to_zone_ms(gmt_ms, to); + } + + namespace detail { + + inline LocalTimeResolution make_local_time_resolution( + LocalTimeStatus status, + ts_ms_t first_utc_ms = ERROR_TIMESTAMP, + ts_ms_t second_utc_ms = ERROR_TIMESTAMP) { + LocalTimeResolution result = {status, first_utc_ms, second_utc_ms}; + return result; + } + + inline bool dynamic_dst_zone_offsets(TimeZone zone, + tz_t& first_offset, + tz_t& second_offset) { + switch(zone) { + case WET: + first_offset = 0; + second_offset = static_cast(SEC_PER_HOUR); + return true; + case CET: + first_offset = static_cast(SEC_PER_HOUR); + second_offset = static_cast(SEC_PER_HOUR * 2); + return true; + case EET: + first_offset = static_cast(SEC_PER_HOUR * 2); + second_offset = static_cast(SEC_PER_HOUR * 3); + return true; + case ET: + first_offset = static_cast(-SEC_PER_HOUR * 5); + second_offset = static_cast(-SEC_PER_HOUR * 4); + return true; + case CT: + first_offset = static_cast(-SEC_PER_HOUR * 6); + second_offset = static_cast(-SEC_PER_HOUR * 5); + return true; + default: + first_offset = 0; + second_offset = 0; + return false; + } + } + + inline tz_t dynamic_zone_standard_offset(TimeZone zone) { + switch(zone) { + case WET: + return 0; + case CET: + return static_cast(SEC_PER_HOUR); + case EET: + return static_cast(SEC_PER_HOUR * 2); + case ET: + return static_cast(-SEC_PER_HOUR * 5); + case CT: + return static_cast(-SEC_PER_HOUR * 6); + default: + return 0; + } + } + + inline bool is_european_dynamic_zone(TimeZone zone) { + return zone == WET || zone == CET || zone == EET; + } + + inline bool is_us_dynamic_zone(TimeZone zone) { + return zone == ET || zone == CT; + } + + inline bool european_dst_at_utc_ms(ts_ms_t utc_ms) { + const DateTimeStruct dt = to_date_time(ms_to_sec(utc_ms)); + const int start_day = last_sunday_month_day(dt.year, MAR); + const int end_day = last_sunday_month_day(dt.year, OCT); + const ts_ms_t start_ms = + to_timestamp_ms(dt.year, int(MAR), start_day, 1, 0, 0); + const ts_ms_t end_ms = + to_timestamp_ms(dt.year, int(OCT), end_day, 1, 0, 0); + return utc_ms >= start_ms && utc_ms < end_ms; + } + + inline int first_sunday_month_day(int year, int month) { + return static_cast( + 1 + (DAYS_PER_WEEK - day_of_week_date(year, month, 1)) % + DAYS_PER_WEEK); + } + + inline bool us_dst_at_utc_ms(TimeZone zone, ts_ms_t utc_ms) { + const DateTimeStruct dt = to_date_time(ms_to_sec(utc_ms)); + const int year = static_cast(dt.year); + int start_month = MAR; + int end_month = NOV; + int start_day = first_sunday_month_day(year, MAR) + 7; + int end_day = first_sunday_month_day(year, NOV); + + if(dt.year < 2007) { + start_month = APR; + end_month = OCT; + start_day = first_sunday_month_day(year, APR); + end_day = last_sunday_month_day(year, OCT); + } + + const tz_t standard_offset = dynamic_zone_standard_offset(zone); + const tz_t daylight_offset = + static_cast(standard_offset + SEC_PER_HOUR); + const ts_ms_t start_local = + to_timestamp_ms(dt.year, start_month, start_day, 2, 0, 0); + const ts_ms_t end_local = + to_timestamp_ms(dt.year, end_month, end_day, 2, 0, 0); + const ts_ms_t start_utc = to_utc_ms(start_local, standard_offset); + const ts_ms_t end_utc = to_utc_ms(end_local, daylight_offset); + return utc_ms >= start_utc && utc_ms < end_utc; + } + + inline bool dynamic_offset_applies_at_utc_ms(TimeZone zone, + ts_ms_t utc_ms, + tz_t offset) { + if(is_european_dynamic_zone(zone)) { + const tz_t standard_offset = dynamic_zone_standard_offset(zone); + const tz_t expected = + static_cast(standard_offset + + (european_dst_at_utc_ms(utc_ms) + ? SEC_PER_HOUR + : 0)); + return offset == expected; + } + + if(is_us_dynamic_zone(zone)) { + const tz_t standard_offset = dynamic_zone_standard_offset(zone); + const tz_t expected = + static_cast(standard_offset + + (us_dst_at_utc_ms(zone, utc_ms) + ? SEC_PER_HOUR + : 0)); + return offset == expected; + } + + return false; + } + + inline void add_local_time_candidate(LocalTimeResolution& result, + ts_ms_t local_ms, + TimeZone zone, + tz_t offset) { + const ts_ms_t candidate = to_utc_ms(local_ms, offset); + if(candidate == ERROR_TIMESTAMP || + to_local_ms(candidate, offset) != local_ms || + !dynamic_offset_applies_at_utc_ms(zone, candidate, offset) || + result.first_utc_ms == candidate || + result.second_utc_ms == candidate) { + return; + } + + if(result.first_utc_ms == ERROR_TIMESTAMP) { + result.first_utc_ms = candidate; + return; + } + + if(result.second_utc_ms == ERROR_TIMESTAMP) { + result.second_utc_ms = candidate; + } + } + + inline LocalTimeResolution resolve_with_dynamic_offsets( + ts_ms_t local_ms, + TimeZone zone, + tz_t first_offset, + tz_t second_offset) { + LocalTimeResolution result = + make_local_time_resolution(LocalTimeStatus::nonexistent); + + add_local_time_candidate(result, local_ms, zone, first_offset); + add_local_time_candidate(result, local_ms, zone, second_offset); + + if(result.first_utc_ms == ERROR_TIMESTAMP) { + return result; + } + + if(result.second_utc_ms == ERROR_TIMESTAMP) { + result.status = LocalTimeStatus::valid; + return result; + } + + if(result.second_utc_ms < result.first_utc_ms) { + const ts_ms_t tmp = result.first_utc_ms; + result.first_utc_ms = result.second_utc_ms; + result.second_utc_ms = tmp; + } + + result.status = LocalTimeStatus::ambiguous; + return result; + } + + inline bool local_time_status_has_utc(LocalTimeStatus status) { + return status == LocalTimeStatus::valid || + status == LocalTimeStatus::ambiguous; + } + + inline ts_ms_t local_time_resolution_to_utc( + const LocalTimeResolution& resolution, + AmbiguousTimePolicy ambiguous_policy) { + if(resolution.status == LocalTimeStatus::valid) { + return resolution.first_utc_ms; + } + + if(resolution.status == LocalTimeStatus::ambiguous) { + switch(ambiguous_policy) { + case AmbiguousTimePolicy::first_occurrence: + return resolution.first_utc_ms; + case AmbiguousTimePolicy::second_occurrence: + return resolution.second_utc_ms; + case AmbiguousTimePolicy::error: + default: + return ERROR_TIMESTAMP; + } + } + + return ERROR_TIMESTAMP; + } + + } // namespace detail + + /// \brief Resolve the effective UTC offset for a UTC millisecond instant. + /// \param utc_ms UTC timestamp in milliseconds. + /// \param zone Time zone to inspect. + /// \param out Receives offset in seconds on success. + /// \return True when the zone and timestamp can be resolved. + inline bool zone_offset_at_utc_ms(ts_ms_t utc_ms, + TimeZone zone, + tz_t& out) noexcept { + if(utc_ms == ERROR_TIMESTAMP || zone == UNKNOWN) { + return false; + } + + if(zone == GMT || zone == UTC) { + out = 0; + return true; + } + + if(detail::is_european_dynamic_zone(zone)) { + const tz_t standard_offset = detail::dynamic_zone_standard_offset(zone); + out = static_cast(standard_offset + + (detail::european_dst_at_utc_ms(utc_ms) + ? SEC_PER_HOUR + : 0)); + return true; + } + + if(detail::is_us_dynamic_zone(zone)) { + const tz_t standard_offset = detail::dynamic_zone_standard_offset(zone); + out = static_cast(standard_offset + + (detail::us_dst_at_utc_ms(zone, utc_ms) + ? SEC_PER_HOUR + : 0)); + return true; + } + + tz_t utc_offset = 0; + if(detail::fixed_zone_offset(zone, utc_offset)) { + out = utc_offset; + return true; + } + + return false; + } + + /// \brief Resolve the effective UTC offset for a UTC second instant. + /// \param utc UTC timestamp in seconds. + /// \param zone Time zone to inspect. + /// \param out Receives offset in seconds on success. + /// \return True when the zone and timestamp can be resolved. + inline bool zone_offset_at_utc(ts_t utc, + TimeZone zone, + tz_t& out) noexcept { + return utc == ERROR_TIMESTAMP + ? false + : zone_offset_at_utc_ms(sec_to_ms(utc), zone, out); + } + + /// \brief Resolve local civil time to zero, one, or two UTC candidates. + /// \param local_ms Local civil timestamp in milliseconds. + /// \param zone Source time zone. + /// \return Resolution status plus UTC candidates in milliseconds. + inline LocalTimeResolution resolve_local_time_ms(ts_ms_t local_ms, + TimeZone zone) { + if(local_ms == ERROR_TIMESTAMP || zone == UNKNOWN) { + return detail::make_local_time_resolution( + LocalTimeStatus::unsupported); + } + + if(zone == GMT || zone == UTC) { + return detail::make_local_time_resolution(LocalTimeStatus::valid, + local_ms); + } + + tz_t first_offset = 0; + tz_t second_offset = 0; + if(detail::dynamic_dst_zone_offsets(zone, first_offset, second_offset)) { + return detail::resolve_with_dynamic_offsets(local_ms, + zone, + first_offset, + second_offset); + } + + tz_t utc_offset = 0; + if(detail::fixed_zone_offset(zone, utc_offset)) { + return detail::make_local_time_resolution( + LocalTimeStatus::valid, + to_utc_ms(local_ms, utc_offset)); + } + + return detail::make_local_time_resolution(LocalTimeStatus::unsupported); + } + + /// \brief Resolve local civil time given in seconds. + /// + /// UTC candidates are returned in milliseconds in LocalTimeResolution. + inline LocalTimeResolution resolve_local_time(ts_t local, TimeZone zone) { + return local == ERROR_TIMESTAMP + ? detail::make_local_time_resolution( + LocalTimeStatus::unsupported) + : resolve_local_time_ms(sec_to_ms(local), zone); + } + + namespace detail { + + inline ts_ms_t shift_nonexistent_local_time_ms(ts_ms_t local_ms, + TimeZone zone, + int direction) { + const ts_ms_t window = static_cast(MS_PER_DAY) * 2; + const bool forward = direction >= 0; + ts_ms_t low = forward ? local_ms : local_ms - window; + ts_ms_t high = forward ? local_ms + window : local_ms; + + LocalTimeResolution edge = + resolve_local_time_ms(forward ? high : low, zone); + if(!local_time_status_has_utc(edge.status)) { + return ERROR_TIMESTAMP; + } + + while(high - low > 1) { + const ts_ms_t mid = low + (high - low) / 2; + const LocalTimeResolution resolution = + resolve_local_time_ms(mid, zone); + if(local_time_status_has_utc(resolution.status)) { + if(forward) { + high = mid; + } else { + low = mid; + } + } else if(forward) { + low = mid; + } else { + high = mid; + } + } + + const LocalTimeResolution shifted = + resolve_local_time_ms(forward ? high : low, zone); + return local_time_resolution_to_utc( + shifted, + AmbiguousTimePolicy::first_occurrence); + } + + } // namespace detail + + /// \brief Convert local civil time to UTC with explicit DST policies. + inline ts_ms_t zone_to_gmt_ms(ts_ms_t local_ms, + TimeZone zone, + AmbiguousTimePolicy ambiguous_policy, + NonexistentTimePolicy nonexistent_policy) { + const LocalTimeResolution resolution = + resolve_local_time_ms(local_ms, zone); + + if(resolution.status == LocalTimeStatus::nonexistent) { + switch(nonexistent_policy) { + case NonexistentTimePolicy::shift_forward: + return detail::shift_nonexistent_local_time_ms( + local_ms, + zone, + 1); + case NonexistentTimePolicy::shift_backward: + return detail::shift_nonexistent_local_time_ms( + local_ms, + zone, + -1); + case NonexistentTimePolicy::error: + default: + return ERROR_TIMESTAMP; + } + } + + return detail::local_time_resolution_to_utc(resolution, + ambiguous_policy); + } + + /// \brief Convert local civil time in seconds to UTC with explicit DST policies. + inline ts_t zone_to_gmt(ts_t local, + TimeZone zone, + AmbiguousTimePolicy ambiguous_policy, + NonexistentTimePolicy nonexistent_policy) { + const ts_ms_t utc_ms = local == ERROR_TIMESTAMP + ? ERROR_TIMESTAMP + : zone_to_gmt_ms(sec_to_ms(local), + zone, + ambiguous_policy, + nonexistent_policy); + return utc_ms == ERROR_TIMESTAMP ? ERROR_TIMESTAMP + : ms_to_sec(utc_ms); + } + + /// \brief Convert only unambiguous existing local time to UTC. + inline ts_ms_t zone_to_gmt_ms_strict(ts_ms_t local_ms, TimeZone zone) { + return zone_to_gmt_ms(local_ms, + zone, + AmbiguousTimePolicy::error, + NonexistentTimePolicy::error); + } + + /// \brief Convert only unambiguous existing local time in seconds to UTC. + inline ts_t zone_to_gmt_strict(ts_t local, TimeZone zone) { + return zone_to_gmt(local, + zone, + AmbiguousTimePolicy::error, + NonexistentTimePolicy::error); + } + + /// \brief Convert local civil time between zones with explicit DST policies. + inline ts_ms_t convert_time_zone_ms( + ts_ms_t local_ms, + TimeZone from, + TimeZone to, + AmbiguousTimePolicy ambiguous_policy, + NonexistentTimePolicy nonexistent_policy) { + const ts_ms_t gmt_ms = zone_to_gmt_ms(local_ms, + from, + ambiguous_policy, + nonexistent_policy); + return gmt_ms == ERROR_TIMESTAMP ? ERROR_TIMESTAMP + : gmt_to_zone_ms(gmt_ms, to); + } + + /// \brief Convert local civil time in seconds between zones with explicit DST policies. + inline ts_t convert_time_zone( + ts_t local, + TimeZone from, + TimeZone to, + AmbiguousTimePolicy ambiguous_policy, + NonexistentTimePolicy nonexistent_policy) { + const ts_t gmt = zone_to_gmt(local, + from, + ambiguous_policy, + nonexistent_policy); + return gmt == ERROR_TIMESTAMP ? ERROR_TIMESTAMP + : gmt_to_zone(gmt, to); + } + + inline ts_t ist_to_gmt(ts_t ist) { return zone_to_gmt(ist, IST); } + inline ts_t gmt_to_ist(ts_t gmt) { return gmt_to_zone(gmt, IST); } + + inline ts_t myt_to_gmt(ts_t myt) { return zone_to_gmt(myt, MYT); } + inline ts_t gmt_to_myt(ts_t gmt) { return gmt_to_zone(gmt, MYT); } + + inline ts_t wib_to_gmt(ts_t wib) { return zone_to_gmt(wib, WIB); } + inline ts_t gmt_to_wib(ts_t gmt) { return gmt_to_zone(gmt, WIB); } + + inline ts_t wita_to_gmt(ts_t wita) { return zone_to_gmt(wita, WITA); } + inline ts_t gmt_to_wita(ts_t gmt) { return gmt_to_zone(gmt, WITA); } + + inline ts_t wit_to_gmt(ts_t wit) { return zone_to_gmt(wit, WIT); } + inline ts_t gmt_to_wit(ts_t gmt) { return gmt_to_zone(gmt, WIT); } + + inline ts_t kzt_to_gmt(ts_t kzt) { return zone_to_gmt(kzt, KZT); } + inline ts_t gmt_to_kzt(ts_t gmt) { return gmt_to_zone(gmt, KZT); } + + inline ts_t trt_to_gmt(ts_t trt) { return zone_to_gmt(trt, TRT); } + inline ts_t gmt_to_trt(ts_t gmt) { return gmt_to_zone(gmt, TRT); } + + inline ts_t byt_to_gmt(ts_t byt) { return zone_to_gmt(byt, BYT); } + inline ts_t gmt_to_byt(ts_t gmt) { return gmt_to_zone(gmt, BYT); } + + inline ts_t sgt_to_gmt(ts_t sgt) { return zone_to_gmt(sgt, SGT); } + inline ts_t gmt_to_sgt(ts_t gmt) { return gmt_to_zone(gmt, SGT); } + + inline ts_t ict_to_gmt(ts_t ict) { return zone_to_gmt(ict, ICT); } + inline ts_t gmt_to_ict(ts_t gmt) { return gmt_to_zone(gmt, ICT); } + + inline ts_t pht_to_gmt(ts_t pht) { return zone_to_gmt(pht, PHT); } + inline ts_t gmt_to_pht(ts_t gmt) { return gmt_to_zone(gmt, PHT); } + + inline ts_t gst_to_gmt(ts_t gst) { return zone_to_gmt(gst, GST); } + inline ts_t gmt_to_gst(ts_t gmt) { return gmt_to_zone(gmt, GST); } + + inline ts_t hkt_to_gmt(ts_t hkt) { return zone_to_gmt(hkt, HKT); } + inline ts_t gmt_to_hkt(ts_t gmt) { return gmt_to_zone(gmt, HKT); } + + inline ts_t jst_to_gmt(ts_t jst) { return zone_to_gmt(jst, JST); } + inline ts_t gmt_to_jst(ts_t gmt) { return gmt_to_zone(gmt, JST); } + + inline ts_t kst_to_gmt(ts_t kst) { return zone_to_gmt(kst, KST); } + inline ts_t gmt_to_kst(ts_t gmt) { return gmt_to_zone(gmt, KST); } + + /// \brief Convert Kyiv civil time to GMT using the EET/EEST rules. + inline ts_t kyiv_to_gmt(ts_t kyiv) { return eet_to_gmt(kyiv); } + + /// \brief Convert GMT to Kyiv civil time using the EET/EEST rules. + inline ts_t gmt_to_kyiv(ts_t gmt) { return gmt_to_eet(gmt); } + + /// \} + +} // namespace time_shield + +#endif // TIME_SHIELD_HEADER_TIMEZONE_TIME_ZONE_CONVERSIONS_HPP_INCLUDED diff --git a/include/time_shield/types.hpp b/include/time_shield/types.hpp index 06e0407a..eb2f4631 100644 --- a/include/time_shield/types.hpp +++ b/include/time_shield/types.hpp @@ -1,67 +1,8 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_TYPES_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_TYPES_HPP_INCLUDED +#ifndef TIME_SHIELD_HEADER_TYPES_HPP_INCLUDED +#define TIME_SHIELD_HEADER_TYPES_HPP_INCLUDED -/// \file types.hpp -/// \brief Type definitions for time-related units and formats. -/// -/// This file defines standard type aliases used across the Time Shield library. -/// It includes representations for Unix timestamps, Julian dates, automation time, -/// time zone offsets, and other related units. +#include -#include - -namespace time_shield { - -/// \defgroup time_types Time Types -/// \brief Fundamental type definitions for time-related data. -/// \ingroup cpp -/// -/// This group defines the core time representations used throughout the library, -/// including timestamps, fractional time units, Julian dates, and time zone offsets. -/// -/// ### Type Categories -/// - **Unix-based timestamps**: `ts_t`, `ts_ms_t`, `ts_us_t` -/// - **Fractional and floating-point time**: `fts_t`, `oadate_t`, `jd_t` -/// - **Julian date types**: `jd_t`, `mjd_t`, `jdn_t` -/// - **Utility units**: `year_t`, `dse_t`, `tz_t` -/// -/// ### Example Usage -/// ```cpp -/// time_shield::ts_t now = 1700000000; // Unix timestamp in seconds -/// time_shield::fts_t precise = 1700000000.123; // Time with fractional seconds -/// time_shield::jd_t julian = 2459580.5; // Julian Date -/// time_shield::tz_t offset = 180; // UTC+3 in minutes -/// ``` - -/// \{ - - // --- Calendar & Year Types --- - typedef int64_t year_t; ///< Year as an integer (e.g., 2024). - typedef int64_t dse_t; ///< Unix day count since 1970‑01‑01 (days since epoch). - using unix_day_t = dse_t; ///< Alias for Unix day count type. - using unixday_t = dse_t; ///< Alias for Unix day count type. - typedef int32_t iso_week_t; ///< ISO week number type (1-52/53). - typedef int32_t iso_weekday_t; ///< ISO weekday number type (1=Monday .. 7=Sunday). - - // --- Unix Timestamp Types --- - typedef int64_t ts_t; ///< Unix timestamp in seconds since 1970‑01‑01T00:00:00Z. - typedef int64_t ts_ms_t; ///< Unix timestamp in milliseconds since epoch. - typedef int64_t ts_us_t; ///< Unix timestamp in microseconds since epoch. - typedef double fts_t; ///< Floating-point timestamp (fractional seconds since epoch). - - // --- Automation and Julian Time --- - typedef double oadate_t; ///< OLE Automation date (days since 1899‑12‑30, as `double`). - typedef double jd_t; ///< Julian Date (days since -4713‑11‑24T12:00:00Z). - typedef double mjd_t; ///< Modified Julian Date (JD − 2400000.5). - typedef uint64_t jdn_t; ///< Julian Day Number (whole days since Julian epoch). - - // --- Time zone offset --- - typedef int32_t tz_t; ///< Time zone offset in minutes from UTC (e.g., +180 = UTC+3). - -/// \} - -}; // namespace time_shield - -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_TYPES_HPP_INCLUDED +#endif // TIME_SHIELD_HEADER_TYPES_HPP_INCLUDED diff --git a/include/time_shield/unix_time_conversions.hpp b/include/time_shield/unix_time_conversions.hpp index 6639596d..a0aa8158 100644 --- a/include/time_shield/unix_time_conversions.hpp +++ b/include/time_shield/unix_time_conversions.hpp @@ -1,332 +1,8 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_UNIX_TIME_CONVERSIONS_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_UNIX_TIME_CONVERSIONS_HPP_INCLUDED +#ifndef TIME_SHIELD_HEADER_UNIX_TIME_CONVERSIONS_HPP_INCLUDED +#define TIME_SHIELD_HEADER_UNIX_TIME_CONVERSIONS_HPP_INCLUDED -/// \file unix_time_conversions.hpp -/// \brief Conversions related to UNIX-based time units and epochs. +#include -#include "config.hpp" -#include "constants.hpp" -#include "detail/fast_date.hpp" -#include "time_unit_conversions.hpp" -#include "time_utils.hpp" -#include "types.hpp" - -namespace time_shield { - -/// \ingroup time_conversions -/// \{ - - namespace legacy { - - /// \brief Converts a UNIX timestamp to a year. - /// \tparam T The type of the year (default is year_t). - /// \param ts UNIX timestamp. - /// \return T Year corresponding to the given timestamp. - template - TIME_SHIELD_CONSTEXPR T years_since_epoch(ts_t ts) noexcept { - // 9223372029693630000 reaches year 292277024400 from the 2000 epoch. - // This value overflows the n_400_years * SEC_PER_400_YEARS calculation. - // The supported bound is reduced to 9223371890843040000. - constexpr int64_t BIAS_292277022000 = 9223371890843040000LL; - constexpr int64_t BIAS_2000 = 946684800LL; - - int64_t y = MAX_YEAR; - int64_t secs = -((ts - BIAS_2000) - BIAS_292277022000); - - const int64_t n_400_years = secs / SEC_PER_400_YEARS; - secs -= n_400_years * SEC_PER_400_YEARS; - y -= n_400_years * 400; - - const int64_t n_100_years = secs / SEC_PER_100_YEARS; - secs -= n_100_years * SEC_PER_100_YEARS; - y -= n_100_years * 100; - - const int64_t n_4_years = secs / SEC_PER_4_YEARS; - secs -= n_4_years * SEC_PER_4_YEARS; - y -= n_4_years * 4; - - const int64_t n_1_years = secs / SEC_PER_YEAR; - secs -= n_1_years * SEC_PER_YEAR; - y -= n_1_years; - - y = secs == 0 ? y : y - 1; - return y - UNIX_EPOCH; - } - - } // namespace legacy - - /// \brief Converts a UNIX timestamp to a year. - /// \tparam T The type of the year (default is year_t). - /// \param ts UNIX timestamp. - /// \return T Year corresponding to the given timestamp. - /// \note Inspired by the algorithm described in: - /// https://www.benjoffe.com/fast-date-64 - /// This implementation is written from scratch (no code copied). - template - TIME_SHIELD_CONSTEXPR T years_since_epoch(ts_t ts) noexcept { - const detail::DaySplit split = detail::split_unix_day(ts); - const int64_t year = detail::fast_year_from_days_constexpr(split.days); - return static_cast(year - UNIX_EPOCH); - } - - namespace legacy { - - /// \brief Convert a calendar date to UNIX day count. - /// - /// Calculates the number of days since the UNIX epoch (January 1, 1970) - /// for the provided calendar date components. - /// - /// \tparam Year Type of the year component. - /// \tparam Month Type of the month component. - /// \tparam Day Type of the day component. - /// \param year Year component of the date. - /// \param month Month component of the date. - /// \param day Day component of the date. - /// \return Number of days since the UNIX epoch. - template - TIME_SHIELD_CONSTEXPR inline dse_t date_to_unix_day( - Year year, - Month month, - Day day) noexcept { - const int64_t y = static_cast(year) - (static_cast(month) <= 2 ? 1 : 0); - const int64_t m = static_cast(month) <= 2 - ? static_cast(month) + 9 - : static_cast(month) - 3; - const int64_t era = (y >= 0 ? y : y - 399) / 400; - const int64_t yoe = y - era * 400; - const int64_t doy = (153 * m + 2) / 5 + static_cast(day) - 1; - const int64_t doe = yoe * 365 + yoe / 4 - yoe / 100 + doy; - return static_cast(era * 146097 + doe - 719468); - } - - } // namespace legacy - - /// \brief Convert a calendar date to UNIX day count. - /// - /// Calculates the number of days since the UNIX epoch (January 1, 1970) - /// for the provided calendar date components. - /// \note Inspired by the algorithm described in: - /// https://www.benjoffe.com/fast-date-64 - /// This implementation is written from scratch (no code copied). - /// - /// \tparam Year Type of the year component. - /// \tparam Month Type of the month component. - /// \tparam Day Type of the day component. - /// \param year Year component of the date. - /// \param month Month component of the date. - /// \param day Day component of the date. - /// \return Number of days since the UNIX epoch. - template - TIME_SHIELD_CONSTEXPR inline dse_t date_to_unix_day( - Year year, - Month month, - Day day) noexcept { - return static_cast( - detail::fast_days_from_date_constexpr( - static_cast(year), - static_cast(month), - static_cast(day))); - } - - /// \brief Get UNIX day. - /// - /// This function returns the number of days elapsed since the UNIX epoch. - /// - /// \tparam T The return type of the function (default is unixday_t). - /// \param ts Timestamp in seconds (default is current timestamp). - /// \return Number of days since the UNIX epoch. - template - TIME_SHIELD_CONSTEXPR T days_since_epoch(ts_t ts = time_shield::ts()) noexcept { - return ts / SEC_PER_DAY; - } - - /// \brief Get UNIX day from milliseconds timestamp. - /// - /// This function returns the number of days elapsed since the UNIX epoch, given a timestamp in milliseconds. - /// - /// \tparam T The return type of the function (default is unixday_t). - /// \param t_ms Timestamp in milliseconds (default is current timestamp in milliseconds). - /// \return Number of days since the UNIX epoch. - template - TIME_SHIELD_CONSTEXPR T days_since_epoch_ms(ts_ms_t t_ms = time_shield::ts_ms()) noexcept { - return days_since_epoch(ms_to_sec(t_ms)); - } - - /// \brief Get the number of days between two timestamps. - /// - /// This function calculates the number of days between two timestamps. - /// - /// \tparam T The type of the return value, defaults to int. - /// \param start The timestamp of the start of the period. - /// \param stop The timestamp of the end of the period. - /// \return The number of days between start and stop. - template - TIME_SHIELD_CONSTEXPR T days_between(ts_t start, ts_t stop) noexcept { - return static_cast((stop - start) / SEC_PER_DAY); - } - - /// \brief Converts a UNIX day to a timestamp in seconds. - /// - /// Converts a number of days since the UNIX epoch (January 1, 1970) to the corresponding - /// timestamp in seconds at the start of the specified day. - /// - /// \tparam T The return type of the function (default is ts_t). - /// \param unix_day Number of days since the UNIX epoch. - /// \return The timestamp in seconds representing the beginning of the specified UNIX day. - template - TIME_SHIELD_CONSTEXPR T unix_day_to_ts(dse_t unix_day) noexcept { - return unix_day * SEC_PER_DAY; - } - - /// \brief Converts a UNIX day to a timestamp in milliseconds. - /// - /// Converts a number of days since the UNIX epoch (January 1, 1970) to the corresponding timestamp - /// in milliseconds at the start of the specified day. - /// - /// \tparam T The return type of the function (default is ts_ms_t). - /// \param unix_day Number of days since the UNIX epoch. - /// \return The timestamp in milliseconds representing the beginning of the specified UNIX day. - template - TIME_SHIELD_CONSTEXPR T unix_day_to_ts_ms(dse_t unix_day) noexcept { - return unix_day * MS_PER_DAY; - } - - /// \brief Converts a UNIX day to a timestamp representing the end of the day in seconds. - /// - /// Converts a number of days since the UNIX epoch (January 1, 1970) to the corresponding - /// timestamp in seconds at the end of the specified day (23:59:59). - /// - /// \tparam T The return type of the function (default is ts_t). - /// \param unix_day The number of days since the UNIX epoch. - /// \return The timestamp in seconds representing the end of the specified UNIX day. - template - TIME_SHIELD_CONSTEXPR T end_of_day_from_unix_day(dse_t unix_day) noexcept { - return unix_day * SEC_PER_DAY + SEC_PER_DAY - 1; - } - - /// \brief Converts a UNIX day to a timestamp representing the end of the day in milliseconds. - /// - /// Converts a number of days since the UNIX epoch (January 1, 1970) to the corresponding - /// timestamp in milliseconds at the end of the specified day (23:59:59.999). - /// - /// \tparam T The return type of the function (default is ts_ms_t). - /// \param unix_day The number of days since the UNIX epoch. - /// \return The timestamp in milliseconds representing the end of the specified UNIX day. - template - TIME_SHIELD_CONSTEXPR T end_of_day_from_unix_day_ms(dse_t unix_day) noexcept { - return unix_day * MS_PER_DAY + MS_PER_DAY - 1; - } - - /// \brief Converts a UNIX day to a timestamp representing the start of the next day in seconds. - /// - /// Converts a number of days since the UNIX epoch (January 1, 1970) to the corresponding - /// timestamp in seconds at the start of the next day (00:00:00). - /// - /// \tparam T The return type of the function (default is ts_t). - /// \param unix_day The number of days since the UNIX epoch. - /// \return The timestamp in seconds representing the beginning of the next UNIX day. - template - TIME_SHIELD_CONSTEXPR T start_of_next_day_from_unix_day(dse_t unix_day) noexcept { - return unix_day * SEC_PER_DAY + SEC_PER_DAY; - } - - /// \brief Converts a UNIX day to a timestamp representing the start of the next day in milliseconds. - /// - /// Converts a number of days since the UNIX epoch (January 1, 1970) to the corresponding - /// timestamp in milliseconds at the start of the next day (00:00:00.000). - /// - /// \tparam T The return type of the function (default is ts_ms_t). - /// \param unix_day The number of days since the UNIX epoch. - /// \return The timestamp in milliseconds representing the beginning of the next UNIX day. - template - TIME_SHIELD_CONSTEXPR T start_of_next_day_from_unix_day_ms(dse_t unix_day) noexcept { - return unix_day * MS_PER_DAY + MS_PER_DAY; - } - - /// \brief Get UNIX minute. - /// - /// This function returns the number of minutes elapsed since the UNIX epoch. - /// - /// \tparam T The return type of the function (default is int64_t). - /// \param ts Timestamp in seconds (default is current timestamp). - /// \return Number of minutes since the UNIX epoch. - template - TIME_SHIELD_CONSTEXPR T min_since_epoch(ts_t ts = time_shield::ts()) { - return ts / SEC_PER_MIN; - } - - /// \brief Get the second of the day. - /// - /// This function returns a value from 0 to MAX_SEC_PER_DAY representing the second of the day. - /// - /// \tparam T The return type of the function (default is int). - /// \param ts Timestamp in seconds (default is current timestamp). - /// \return Second of the day. - template - TIME_SHIELD_CONSTEXPR T sec_of_day(ts_t ts = time_shield::ts()) noexcept { - return static_cast(ts % SEC_PER_DAY); - } - - /// \brief Get the second of the day from milliseconds timestamp. - /// - /// This function returns a value from 0 to MAX_SEC_PER_DAY representing the second of the day, given a timestamp in milliseconds. - /// - /// \tparam T The return type of the function (default is int). - /// \param ts_ms Timestamp in milliseconds. - /// \return Second of the day. - template - TIME_SHIELD_CONSTEXPR T sec_of_day_ms(ts_ms_t ts_ms) noexcept { - return sec_of_day(ms_to_sec(ts_ms)); - } - - /// \brief Get the second of the day. - /// - /// This function returns a value between 0 and MAX_SEC_PER_DAY representing the second of the day, given the hour, minute, and second. - /// - /// \tparam T1 The return type of the function (default is int). - /// \tparam T2 The type of the hour, minute, and second parameters (default is int). - /// \param hour Hour of the day. - /// \param min Minute of the hour. - /// \param sec Second of the minute. - /// \return Second of the day. - template - constexpr T1 sec_of_day( - T2 hour, - T2 min, - T2 sec) noexcept { - return static_cast(hour) * static_cast(SEC_PER_HOUR) + - static_cast(min) * static_cast(SEC_PER_MIN) + - static_cast(sec); - } - - /// \brief Get the second of the minute. - /// - /// This function returns a value between 0 and 59 representing the second of the minute. - /// - /// \tparam T The return type of the function (default is int). - /// \param ts Timestamp in seconds (default is current timestamp). - /// \return Second of the minute. - template - TIME_SHIELD_CONSTEXPR T sec_of_min(ts_t ts = time_shield::ts()) { - return static_cast(ts % SEC_PER_MIN); - } - - /// \brief Get the second of the hour. - /// - /// This function returns a value between 0 and 3599 representing the second of the hour. - /// - /// \tparam T The return type of the function (default is int). - /// \param ts Timestamp in seconds (default is current timestamp). - /// \return Second of the hour. - template - TIME_SHIELD_CONSTEXPR T sec_of_hour(ts_t ts = time_shield::ts()) { - return static_cast(ts % SEC_PER_HOUR); - } - -/// \} - -}; // namespace time_shield - -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_UNIX_TIME_CONVERSIONS_HPP_INCLUDED +#endif // TIME_SHIELD_HEADER_UNIX_TIME_CONVERSIONS_HPP_INCLUDED diff --git a/include/time_shield/validation.hpp b/include/time_shield/validation.hpp index 532917b4..f4c18988 100644 --- a/include/time_shield/validation.hpp +++ b/include/time_shield/validation.hpp @@ -1,397 +1,8 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_VALIDATION_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_VALIDATION_HPP_INCLUDED +#ifndef TIME_SHIELD_HEADER_VALIDATION_HPP_INCLUDED +#define TIME_SHIELD_HEADER_VALIDATION_HPP_INCLUDED -/// \file validation.hpp -/// \brief Header file with time-related validation functions. -/// -/// This file contains functions for validating dates, times, and timestamps. +#include -#include "config.hpp" -#include "types.hpp" -#include "constants.hpp" -#include "enums.hpp" -#include "time_zone_struct.hpp" - -namespace time_shield { - -/// \defgroup time_validation Time Validation -/// \brief A comprehensive set of functions for validating dates, times, leap years, and time zones. -/// -/// This module provides functionalities to validate the correctness of date-time values, -/// leap years, and time zone offsets. It also includes utilities for determining weekends -/// and ensuring the validity of timestamp-based calculations. -/// -/// ### Key Features: -/// - Validate leap years using dates or timestamps. -/// - Ensure the correctness of date components (year, month, day). -/// - Verify the validity of time components (hour, minute, second, millisecond). -/// - Check the validity of time zones and time zone structures. -/// - Determine if a given timestamp or day corresponds to a weekend. -/// -/// ### Usage Examples: -/// - Check if a year is a leap year: -/// \code{.cpp} -/// bool is_leap = time_shield::is_leap_year_date(2024); -/// \endcode -/// -/// - Validate a specific date: -/// \code{.cpp} -/// bool is_valid = time_shield::is_valid_date(2024, 2, 29); -/// \endcode -/// -/// - Check if a timestamp falls on a weekend: -/// \code{.cpp} -/// bool is_weekend = time_shield::is_day_off(1698249600); // Saturday, Oct 26, 2024 -/// \endcode -/// -/// \{ - - /// \brief Checks if the given year is a leap year. - /// \tparam T The type of the year (default is year_t). - /// \param year Year to check. - /// \return true if the year is a leap year, false otherwise. - template - TIME_SHIELD_CONSTEXPR bool is_leap_year_date(T year) noexcept { - return ((year & 3) == 0 && ((year % 25) != 0 || (year & 15) == 0)); - } - - /// \brief Alias for is_leap_year_date function. - /// \copydoc is_leap_year_date - template - TIME_SHIELD_CONSTEXPR bool check_leap_year(T year) noexcept { - return is_leap_year_date(year); - } - - /// \brief Alias for is_leap_year_date function. - /// \copydoc is_leap_year_date - template - TIME_SHIELD_CONSTEXPR bool leap_year(T year) noexcept { - return is_leap_year_date(year); - } - -//------------------------------------------------------------------------------ - - /// \brief Checks if the given year is a leap year. - /// - /// This function determines whether the year corresponding to the provided timestamp - /// is a leap year. - /// - /// \tparam T The type of the year parameter (default is year_t). - /// \param ts Timestamp in seconds since the Unix epoch. - /// \return Returns true if the year is a leap year. - TIME_SHIELD_CONSTEXPR inline bool is_leap_year_ts(ts_t ts) { - // 9223372029693630000 reaches year 292277024400 from the 2000 epoch. - // This value overflows the n_400_years * SEC_PER_400_YEARS calculation. - // The supported bound is reduced to 9223371890843040000. - constexpr int64_t BIAS_292277022000 = 9223371890843040000LL; - constexpr int64_t BIAS_2000 = 946684800LL; - - int64_t y = MAX_YEAR; - int64_t secs = -((ts - BIAS_2000) - BIAS_292277022000); - - const int64_t n_400_years = secs / SEC_PER_400_YEARS; - secs -= n_400_years * SEC_PER_400_YEARS; - y -= n_400_years * 400; - - const int64_t n_100_years = secs / SEC_PER_100_YEARS; - secs -= n_100_years * SEC_PER_100_YEARS; - y -= n_100_years * 100; - - const int64_t n_4_years = secs / SEC_PER_4_YEARS; - secs -= n_4_years * SEC_PER_4_YEARS; - y -= n_4_years * 4; - - const int64_t n_1_years = secs / SEC_PER_YEAR; - secs -= n_1_years * SEC_PER_YEAR; - y -= n_1_years; - - y = secs == 0 ? y : y - 1; - return is_leap_year_date(y); - } - - /// \brief Alias for is_leap_year_ts function. - /// \copydoc is_leap_year_ts - TIME_SHIELD_CONSTEXPR inline bool leap_year_ts(ts_t ts) { - return is_leap_year_ts(ts); - } - - /// \brief Alias for is_leap_year_ts function. - /// \copydoc is_leap_year_ts - TIME_SHIELD_CONSTEXPR inline bool check_leap_year_ts(ts_t ts) { - return is_leap_year_ts(ts); - } - - /// \brief Alias for is_leap_year_ts function. - /// \copydoc is_leap_year_ts - TIME_SHIELD_CONSTEXPR inline bool is_leap_year(ts_t ts) { - return is_leap_year_ts(ts); - } - -//------------------------------------------------------------------------------ - - /// \brief Check if the time zone is valid. - /// \tparam T The type of the time zone components (default is int). - /// \param hour The hour component of the time zone. - /// \param min The minute component of the time zone. - /// \return True if the time zone is valid, false otherwise. - template - TIME_SHIELD_CONSTEXPR inline bool is_valid_time_zone( - T hour, - T min) noexcept { - if (hour < 0 || hour > 23) return false; - if (min < 0 || min > 59) return false; - return true; - } - - /// \brief Alias for is_valid_time_zone function. - /// \copydoc is_valid_time_zone - template - TIME_SHIELD_CONSTEXPR inline bool is_valid_tz( - T hour, - T min) { - return is_valid_time_zone(hour, min); - } - -//------------------------------------------------------------------------------ - - /// \ingroup time_structures - /// \brief Check if the time zone is valid. - /// \tparam T The type of the time zone structure (default is TimeZoneStruct). - /// \param time_zone The time zone structure containing hour and minute components. - /// \return True if the time zone is valid, false otherwise. - template - TIME_SHIELD_CONSTEXPR inline bool is_valid_time_zone_offset( - const T& time_zone) noexcept { - return is_valid_time_zone(time_zone.hour, time_zone.min); - } - - /// \ingroup time_structures - /// \brief Alias for is_valid_time_zone_offset function. - /// \copydoc is_valid_time_zone_offset - template - TIME_SHIELD_CONSTEXPR inline bool is_valid_time_zone( - const T& time_zone) { - return is_valid_time_zone_offset(time_zone); - } - - /// \ingroup time_structures - /// \brief Alias for is_valid_time_zone_offset function. - /// \copydoc is_valid_time_zone_offset - template - TIME_SHIELD_CONSTEXPR inline bool is_valid_tz( - const T& time_zone) { - return is_valid_time_zone_offset(time_zone); - } - -//------------------------------------------------------------------------------ - - /// \brief Checks the correctness of the specified time. - /// \tparam T1 The type of the hour, minute, and second values (default is int). - /// \tparam T2 The type of the millisecond value (default is int). - /// \param hour Hour - /// \param min Minute - /// \param sec Second - /// \param ms Millisecond (default is 0). - /// \return true if the time is valid, false otherwise. - template - TIME_SHIELD_CONSTEXPR inline bool is_valid_time( - T1 hour, - T1 min, - T1 sec, - T2 ms = 0) noexcept { - if (hour < 0 || hour > 23) return false; - if (min < 0 || min > 59) return false; - if (sec < 0 || sec > 59) return false; - if (ms < 0 || ms > 999) return false; - return true; - } - - /// \ingroup time_structures - /// \brief Checks the correctness of the specified time. - /// \tparam T The type of the time structure. - /// \param time Time structure. - /// \return true if the time is valid, false otherwise. - template - TIME_SHIELD_CONSTEXPR inline bool is_valid_time( - const T& time) noexcept { - return is_valid_time(time.hour, time.min, time.sec, time.ms); - } - - /// \brief Checks the correctness of the specified date. - /// \tparam T1 The type of the year or day value (default is year_t). - /// \tparam T2 The type of the month and day values (default is int). - /// \param year Year or day. - /// \param month Month. - /// \param day Day or year. - /// \return true if the date is valid, false otherwise. - template - TIME_SHIELD_CONSTEXPR inline bool is_valid_date( - T1 year, - T2 month, - T2 day) noexcept { - if (day > 31 && year <= 31) { - return is_valid_date((T1)day, month, (T2)year); - } - if (year < MIN_YEAR) return false; - if (year > MAX_YEAR) return false; - if (month < 1 || month > 12) return false; - if (day < 1 || day > 31) return false; - if (month == FEB) { - const bool is_leap_year = is_leap_year_date(year); - if (is_leap_year && day > 29) return false; - if (!is_leap_year && day > 28) return false; - } else { - switch(month) { - case 4: - case 6: - case 9: - case 11: - if (day > 30) return false; - default: - break; - }; - } - return true; - } - - /// \ingroup time_structures - /// \brief Checks the correctness of the specified date. - /// \tparam T The type of the date-time structure. - /// \param date Date-time structure. - /// \return true if the date is valid, false otherwise. - template - TIME_SHIELD_CONSTEXPR inline bool is_valid_date(const T& date) noexcept { - return is_valid_date(date.year, date.mon, date.day); - } - - /// \brief Checks the correctness of a date and time. - /// \tparam T1 The type of the year or day value (default is year_t). - /// \tparam T2 The type of the month and day values (default is int). - /// \tparam T3 The type of the millisecond value (default is int). - /// \param year Year or day. - /// \param month Month. - /// \param day Day or year. - /// \param hour Hour (default is 0). - /// \param min Minute (default is 0). - /// \param sec Second (default is 0). - /// \param ms Millisecond (default is 0). - /// \return true if the date and time are valid, false otherwise. - template - TIME_SHIELD_CONSTEXPR inline bool is_valid_date_time( - T1 year, - T2 month, - T2 day, - T2 hour = 0, - T2 min = 0, - T2 sec = 0, - T3 ms = 0) noexcept { - if (!is_valid_date(year, month, day)) return false; - if (!is_valid_time(hour, min, sec, ms)) return false; - return true; - } - - /// \ingroup time_structures - /// \brief Checks the correctness of a date and time. - /// \tparam T The type of the date-time structure. - /// \param date_time Date-time structure. - /// \return true if the date and time are valid, false otherwise. - template - TIME_SHIELD_CONSTEXPR inline bool is_valid_date_time( - const T &date_time) noexcept { - if (!is_valid_date(date_time)) return false; - if (!is_valid_time(date_time)) return false; - return true; - } - -//------------------------------------------------------------------------------ - - /// \brief Check if a given timestamp corresponds to a weekend day (Saturday or Sunday). - /// - /// This function checks if the given timestamp falls on a weekend day, which is either Saturday or Sunday. - /// - /// \param ts Timestamp to check (default: current timestamp). - /// \return true if the day is a weekend day, false otherwise. - TIME_SHIELD_CONSTEXPR inline bool is_day_off(ts_t ts) noexcept { - const int64_t day = static_cast(ts) / static_cast(SEC_PER_DAY); - int64_t wd64 = (day + static_cast(THU)) % static_cast(DAYS_PER_WEEK); - if (wd64 < 0) wd64 += static_cast(DAYS_PER_WEEK); // for ts < 0 - const int wd = static_cast(wd64); - return (wd == SUN || wd == SAT); - } - - /// \brief Alias for is_day_off function. - /// \copydoc is_day_off - TIME_SHIELD_CONSTEXPR inline bool is_weekend(ts_t ts) noexcept { - return is_day_off(ts); - } - -//------------------------------------------------------------------------------ - - /// \brief Check if a given day (since Unix epoch) corresponds to a weekend day (Saturday or Sunday). - /// This function checks if the given day (number of days since Unix epoch) falls on a weekend day, - /// which is either Saturday or Sunday. - /// \param unix_day Day to check (number of days since Unix epoch). - /// \return true if the day is a weekend day, false otherwise. - template - TIME_SHIELD_CONSTEXPR inline bool is_day_off_unix_day(T unix_day) noexcept { - int64_t wd = (static_cast(unix_day) + THU) % DAYS_PER_WEEK; - wd += (wd < 0) ? DAYS_PER_WEEK : 0; - return (wd == SUN || wd == SAT); - } - - /// \brief Alias for is_day_off_unix_day function. - /// \copydoc is_day_off_unix_day - template - TIME_SHIELD_CONSTEXPR inline bool is_weekend_unix_day(T unix_day) noexcept { - return is_day_off_unix_day(unix_day); - } - -//------------------------------------------------------------------------------ - - /// \brief Check if a given timestamp corresponds to a workday (Monday to Friday). - /// \param ts Timestamp to check. - /// \return true if the day is a workday, false otherwise. - TIME_SHIELD_CONSTEXPR inline bool is_workday(ts_t ts) noexcept { - return !is_day_off(ts); - } - - /// \brief Check if a given timestamp in milliseconds corresponds to a workday (Monday to Friday). - /// \param ts_ms Timestamp in milliseconds to check. - /// \return true if the day is a workday, false otherwise. - TIME_SHIELD_CONSTEXPR inline bool is_workday_ms(ts_ms_t ts_ms) noexcept { - return is_workday(static_cast(ts_ms / MS_PER_SEC)); - } - - /// \brief Check if a calendar date corresponds to a workday (Monday to Friday). - /// \param year Year component of the date. - /// \param month Month component of the date. - /// \param day Day component of the date. - /// \return true if the date is valid and a workday, false otherwise. - TIME_SHIELD_CONSTEXPR inline bool is_workday(year_t year, int month, int day) noexcept { - const auto y = static_cast(year); - const auto m = static_cast(month); - const auto d = static_cast(day); - if (!is_valid_date(y, m, d)) { - return false; - } - - const int64_t adj_y = static_cast(y) - (static_cast(m) <= 2 ? 1 : 0); - const int64_t adj_m = static_cast(m) <= 2 - ? static_cast(m) + 9 - : static_cast(m) - 3; - const int64_t era = (adj_y >= 0 ? adj_y : adj_y - 399) / 400; - const int64_t yoe = adj_y - era * 400; - const int64_t doy = (153 * adj_m + 2) / 5 + static_cast(d) - 1; - const int64_t doe = yoe * 365 + yoe / 4 - yoe / 100 + doy; - const dse_t unix_day = static_cast(era * 146097 + doe - 719468); - - return !is_day_off_unix_day(unix_day); - } - -/// \} - -} // namespace time_shield - -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_VALIDATION_HPP_INCLUDED +#endif // TIME_SHIELD_HEADER_VALIDATION_HPP_INCLUDED diff --git a/include/time_shield/workday_conversions.hpp b/include/time_shield/workday_conversions.hpp index 2d615dad..32c01b41 100644 --- a/include/time_shield/workday_conversions.hpp +++ b/include/time_shield/workday_conversions.hpp @@ -1,343 +1,8 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_WORKDAY_CONVERSIONS_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_WORKDAY_CONVERSIONS_HPP_INCLUDED +#ifndef TIME_SHIELD_HEADER_WORKDAY_CONVERSIONS_HPP_INCLUDED +#define TIME_SHIELD_HEADER_WORKDAY_CONVERSIONS_HPP_INCLUDED -/// \file workday_conversions.hpp -/// \brief Helpers for computing workday-related timestamps. +#include -#include "config.hpp" -#include "date_conversions.hpp" -#include "date_time_conversions.hpp" -#include "time_unit_conversions.hpp" -#include "validation.hpp" - -namespace time_shield { - -/// \ingroup time_conversions -/// \{ - - /// \brief Finds the first workday number within a month. - /// \param year Target year. - /// \param month Target month (1-12). - TIME_SHIELD_CONSTEXPR inline int first_workday_day(year_t year, int month) noexcept { - const int days = num_days_in_month(year, month); - if (days <= 0) { - return 0; - } - for (int day = 1; day <= days; ++day) { - if (is_workday(year, month, day)) { - return day; - } - } - return 0; - } - - /// \brief Finds the last workday number within a month. - /// \param year Target year. - /// \param month Target month (1-12). - TIME_SHIELD_CONSTEXPR inline int last_workday_day(year_t year, int month) noexcept { - const int days = num_days_in_month(year, month); - if (days <= 0) { - return 0; - } - for (int day = days; day >= 1; --day) { - if (is_workday(year, month, day)) { - return day; - } - } - return 0; - } - - /// \brief Counts workdays within a month. - /// \param year Target year. - /// \param month Target month (1-12). - TIME_SHIELD_CONSTEXPR inline int count_workdays_in_month(year_t year, int month) noexcept { - const int days = num_days_in_month(year, month); - if (days <= 0) { - return 0; - } - int total = 0; - for (int day = 1; day <= days; ++day) { - if (is_workday(year, month, day)) { - ++total; - } - } - return total; - } - - /// \brief Returns workday position in month starting from 1. - /// \param year Target year. - /// \param month Target month (1-12). - /// \param day Day of month (1-based). - TIME_SHIELD_CONSTEXPR inline int workday_index_in_month(year_t year, int month, int day) noexcept { - if (!is_workday(year, month, day)) { - return 0; - } - const int days = num_days_in_month(year, month); - if (days <= 0) { - return 0; - } - int index = 0; - for (int current = 1; current <= days; ++current) { - if (is_workday(year, month, current)) { - ++index; - if (current == day) { - return index; - } - } - } - return 0; - } - - /// \brief Checks whether date is the first workday of the month. - /// \param year Target year. - /// \param month Target month (1-12). - /// \param day Day of month (1-based). - TIME_SHIELD_CONSTEXPR inline bool is_first_workday_of_month(year_t year, int month, int day) noexcept { - return is_workday(year, month, day) && first_workday_day(year, month) == day; - } - - /// \brief Checks if date falls within the first N workdays of the month. - /// \param year Target year. - /// \param month Target month (1-12). - /// \param day Day of month (1-based). - /// \param count Number of leading workdays to include. - TIME_SHIELD_CONSTEXPR inline bool is_within_first_workdays_of_month(year_t year, int month, int day, int count) noexcept { - if (count <= 0) { - return false; - } - const int total = count_workdays_in_month(year, month); - if (count > total) { - return false; - } - const int index = workday_index_in_month(year, month, day); - return index > 0 && index <= count; - } - - /// \brief Checks whether date is the last workday of the month. - /// \param year Target year. - /// \param month Target month (1-12). - /// \param day Day of month (1-based). - TIME_SHIELD_CONSTEXPR inline bool is_last_workday_of_month(year_t year, int month, int day) noexcept { - return is_workday(year, month, day) && last_workday_day(year, month) == day; - } - - /// \brief Checks if date falls within the last N workdays of the month. - /// \param year Target year. - /// \param month Target month (1-12). - /// \param day Day of month (1-based). - /// \param count Number of trailing workdays to include. - TIME_SHIELD_CONSTEXPR inline bool is_within_last_workdays_of_month(year_t year, int month, int day, int count) noexcept { - if (count <= 0) { - return false; - } - const int total = count_workdays_in_month(year, month); - if (count > total) { - return false; - } - const int index = workday_index_in_month(year, month, day); - return index > 0 && index >= (total - count + 1); - } - - /// \brief Checks whether timestamp is the first workday of the month. - /// \param ts Timestamp in seconds. - TIME_SHIELD_CONSTEXPR inline bool is_first_workday_of_month(ts_t ts) noexcept { - return is_first_workday_of_month(year_of(ts), month_of_year(ts), day_of_month(ts)); - } - - /// \brief Checks whether millisecond timestamp is the first workday of the month. - /// \param ts_ms Timestamp in milliseconds. - TIME_SHIELD_CONSTEXPR inline bool is_first_workday_of_month_ms(ts_ms_t ts_ms) noexcept { - return is_workday_ms(ts_ms) && is_first_workday_of_month(year_of_ms(ts_ms), month_of_year(ms_to_sec(ts_ms)), day_of_month(ms_to_sec(ts_ms))); - } - - /// \brief Checks if timestamp falls within the first N workdays of the month. - /// \param ts Timestamp in seconds. - /// \param count Number of leading workdays to include. - TIME_SHIELD_CONSTEXPR inline bool is_within_first_workdays_of_month(ts_t ts, int count) noexcept { - return is_within_first_workdays_of_month(year_of(ts), month_of_year(ts), day_of_month(ts), count); - } - - /// \brief Checks if millisecond timestamp falls within the first N workdays of the month. - /// \param ts_ms Timestamp in milliseconds. - /// \param count Number of leading workdays to include. - TIME_SHIELD_CONSTEXPR inline bool is_within_first_workdays_of_month_ms(ts_ms_t ts_ms, int count) noexcept { - return is_workday_ms(ts_ms) && is_within_first_workdays_of_month(year_of_ms(ts_ms), month_of_year(ms_to_sec(ts_ms)), day_of_month(ms_to_sec(ts_ms)), count); - } - - /// \brief Checks whether timestamp is the last workday of the month. - /// \param ts Timestamp in seconds. - TIME_SHIELD_CONSTEXPR inline bool is_last_workday_of_month(ts_t ts) noexcept { - return is_last_workday_of_month(year_of(ts), month_of_year(ts), day_of_month(ts)); - } - - /// \brief Checks whether millisecond timestamp is the last workday of the month. - /// \param ts_ms Timestamp in milliseconds. - TIME_SHIELD_CONSTEXPR inline bool is_last_workday_of_month_ms(ts_ms_t ts_ms) noexcept { - return is_workday_ms(ts_ms) && is_last_workday_of_month(year_of_ms(ts_ms), month_of_year(ms_to_sec(ts_ms)), day_of_month(ms_to_sec(ts_ms))); - } - - /// \brief Checks if timestamp falls within the last N workdays of the month. - /// \param ts Timestamp in seconds. - /// \param count Number of trailing workdays to include. - TIME_SHIELD_CONSTEXPR inline bool is_within_last_workdays_of_month(ts_t ts, int count) noexcept { - return is_within_last_workdays_of_month(year_of(ts), month_of_year(ts), day_of_month(ts), count); - } - - /// \brief Checks if millisecond timestamp falls within the last N workdays of the month. - /// \param ts_ms Timestamp in milliseconds. - /// \param count Number of trailing workdays to include. - TIME_SHIELD_CONSTEXPR inline bool is_within_last_workdays_of_month_ms(ts_ms_t ts_ms, int count) noexcept { - return is_workday_ms(ts_ms) && is_within_last_workdays_of_month(year_of_ms(ts_ms), month_of_year(ms_to_sec(ts_ms)), day_of_month(ms_to_sec(ts_ms)), count); - } - - /// \brief Returns start-of-day timestamp for the first workday of month. - /// \param year Target year. - /// \param month Target month (1-12). - TIME_SHIELD_CONSTEXPR inline ts_t start_of_first_workday_month(year_t year, int month) noexcept { - const int day = first_workday_day(year, month); - if (day <= 0) { - return ERROR_TIMESTAMP; - } - return to_timestamp(year, month, day); - } - - /// \brief Returns start-of-day millisecond timestamp for the first workday of month. - /// \param year Target year. - /// \param month Target month (1-12). - TIME_SHIELD_CONSTEXPR inline ts_ms_t start_of_first_workday_month_ms(year_t year, int month) noexcept { - const int day = first_workday_day(year, month); - if (day <= 0) { - return ERROR_TIMESTAMP; - } - const ts_t day_start = to_timestamp(year, month, day); - return sec_to_ms(day_start); - } - - /// \brief Returns start-of-day timestamp for the first workday of month derived from timestamp. - /// \param ts Timestamp in seconds. - TIME_SHIELD_CONSTEXPR inline ts_t start_of_first_workday_month(ts_t ts = time_shield::ts()) noexcept { - return start_of_first_workday_month(year_of(ts), month_of_year(ts)); - } - - /// \brief Returns start-of-day millisecond timestamp for the first workday of month derived from millisecond timestamp. - /// \param ts_ms Timestamp in milliseconds. - TIME_SHIELD_CONSTEXPR inline ts_ms_t start_of_first_workday_month_ms(ts_ms_t ts_ms = time_shield::ts_ms()) noexcept { - return start_of_first_workday_month_ms(year_of_ms(ts_ms), month_of_year(ms_to_sec(ts_ms))); - } - - /// \brief Returns end-of-day timestamp for the first workday of month. - /// \param year Target year. - /// \param month Target month (1-12). - TIME_SHIELD_CONSTEXPR inline ts_t end_of_first_workday_month(year_t year, int month) noexcept { - const int day = first_workday_day(year, month); - if (day <= 0) { - return ERROR_TIMESTAMP; - } - const ts_t day_start = to_timestamp(year, month, day); - return end_of_day(day_start); - } - - /// \brief Returns end-of-day millisecond timestamp for the first workday of month. - /// \param year Target year. - /// \param month Target month (1-12). - TIME_SHIELD_CONSTEXPR inline ts_ms_t end_of_first_workday_month_ms(year_t year, int month) noexcept { - const int day = first_workday_day(year, month); - if (day <= 0) { - return ERROR_TIMESTAMP; - } - const ts_t day_start = to_timestamp(year, month, day); - const ts_ms_t day_start_ms = sec_to_ms(day_start); - return end_of_day_ms(day_start_ms); - } - - /// \brief Returns end-of-day timestamp for the first workday of month derived from timestamp. - /// \param ts Timestamp in seconds. - TIME_SHIELD_CONSTEXPR inline ts_t end_of_first_workday_month(ts_t ts = time_shield::ts()) noexcept { - return end_of_first_workday_month(year_of(ts), month_of_year(ts)); - } - - /// \brief Returns end-of-day millisecond timestamp for the first workday of month derived from millisecond timestamp. - /// \param ts_ms Timestamp in milliseconds. - TIME_SHIELD_CONSTEXPR inline ts_ms_t end_of_first_workday_month_ms(ts_ms_t ts_ms = time_shield::ts_ms()) noexcept { - return end_of_first_workday_month_ms(year_of_ms(ts_ms), month_of_year(ms_to_sec(ts_ms))); - } - - /// \brief Returns start-of-day timestamp for the last workday of month. - /// \param year Target year. - /// \param month Target month (1-12). - TIME_SHIELD_CONSTEXPR inline ts_t start_of_last_workday_month(year_t year, int month) noexcept { - const int day = last_workday_day(year, month); - if (day <= 0) { - return ERROR_TIMESTAMP; - } - return to_timestamp(year, month, day); - } - - /// \brief Returns start-of-day millisecond timestamp for the last workday of month. - /// \param year Target year. - /// \param month Target month (1-12). - TIME_SHIELD_CONSTEXPR inline ts_ms_t start_of_last_workday_month_ms(year_t year, int month) noexcept { - const int day = last_workday_day(year, month); - if (day <= 0) { - return ERROR_TIMESTAMP; - } - const ts_t day_start = to_timestamp(year, month, day); - return sec_to_ms(day_start); - } - - /// \brief Returns start-of-day timestamp for the last workday of month derived from timestamp. - /// \param ts Timestamp in seconds. - TIME_SHIELD_CONSTEXPR inline ts_t start_of_last_workday_month(ts_t ts = time_shield::ts()) noexcept { - return start_of_last_workday_month(year_of(ts), month_of_year(ts)); - } - - /// \brief Returns start-of-day millisecond timestamp for the last workday of month derived from millisecond timestamp. - /// \param ts_ms Timestamp in milliseconds. - TIME_SHIELD_CONSTEXPR inline ts_ms_t start_of_last_workday_month_ms(ts_ms_t ts_ms = time_shield::ts_ms()) noexcept { - return start_of_last_workday_month_ms(year_of_ms(ts_ms), month_of_year(ms_to_sec(ts_ms))); - } - - /// \brief Returns end-of-day timestamp for the last workday of month. - /// \param year Target year. - /// \param month Target month (1-12). - TIME_SHIELD_CONSTEXPR inline ts_t end_of_last_workday_month(year_t year, int month) noexcept { - const int day = last_workday_day(year, month); - if (day <= 0) { - return ERROR_TIMESTAMP; - } - const ts_t day_start = to_timestamp(year, month, day); - return end_of_day(day_start); - } - - /// \brief Returns end-of-day millisecond timestamp for the last workday of month. - /// \param year Target year. - /// \param month Target month (1-12). - TIME_SHIELD_CONSTEXPR inline ts_ms_t end_of_last_workday_month_ms(year_t year, int month) noexcept { - const int day = last_workday_day(year, month); - if (day <= 0) { - return ERROR_TIMESTAMP; - } - const ts_t day_start = to_timestamp(year, month, day); - const ts_ms_t day_start_ms = sec_to_ms(day_start); - return end_of_day_ms(day_start_ms); - } - - /// \brief Returns end-of-day timestamp for the last workday of month derived from timestamp. - /// \param ts Timestamp in seconds. - TIME_SHIELD_CONSTEXPR inline ts_t end_of_last_workday_month(ts_t ts = time_shield::ts()) noexcept { - return end_of_last_workday_month(year_of(ts), month_of_year(ts)); - } - - /// \brief Returns end-of-day millisecond timestamp for the last workday of month derived from millisecond timestamp. - /// \param ts_ms Timestamp in milliseconds. - TIME_SHIELD_CONSTEXPR inline ts_ms_t end_of_last_workday_month_ms(ts_ms_t ts_ms = time_shield::ts_ms()) noexcept { - return end_of_last_workday_month_ms(year_of_ms(ts_ms), month_of_year(ms_to_sec(ts_ms))); - } - -/// \} - -}; // namespace time_shield - -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_WORKDAY_CONVERSIONS_HPP_INCLUDED +#endif // TIME_SHIELD_HEADER_WORKDAY_CONVERSIONS_HPP_INCLUDED diff --git a/tests/astronomy_ole_conversions_test.cpp b/tests/astronomy_ole_conversions_test.cpp index cfd03638..f447f542 100644 --- a/tests/astronomy_ole_conversions_test.cpp +++ b/tests/astronomy_ole_conversions_test.cpp @@ -1,4 +1,5 @@ -#include +#include +#include #include "test_assert.hpp" #include #include diff --git a/tests/constants_test.cpp b/tests/constants_test.cpp index f50728bb..59514c60 100644 --- a/tests/constants_test.cpp +++ b/tests/constants_test.cpp @@ -1,4 +1,4 @@ -#include +#include #include diff --git a/tests/deadline_elapsed_timer_test.cpp b/tests/deadline_elapsed_timer_test.cpp index 08302422..40906e95 100644 --- a/tests/deadline_elapsed_timer_test.cpp +++ b/tests/deadline_elapsed_timer_test.cpp @@ -1,5 +1,5 @@ -#include -#include +#include +#include #include "test_assert.hpp" #include diff --git a/tests/domain_umbrellas_test.cpp b/tests/domain_umbrellas_test.cpp new file mode 100644 index 00000000..80c3cba2 --- /dev/null +++ b/tests/domain_umbrellas_test.cpp @@ -0,0 +1,14 @@ +#include +#include +#include +#include +#include +#include +#include +#if TIME_SHIELD_ENABLE_NTP_CLIENT +# include +#endif + +int main() { + return 0; +} diff --git a/tests/gmt_time_zone_conversion_test.cpp b/tests/gmt_time_zone_conversion_test.cpp index b661403c..e5ee2a4c 100644 --- a/tests/gmt_time_zone_conversion_test.cpp +++ b/tests/gmt_time_zone_conversion_test.cpp @@ -1,5 +1,5 @@ -#include -#include +#include +#include #include "test_assert.hpp" /// \brief Round-trip tests for GMT and CET/EET conversions around DST boundaries. diff --git a/tests/header_smoke/astronomy.cpp b/tests/header_smoke/astronomy.cpp new file mode 100644 index 00000000..76e5a709 --- /dev/null +++ b/tests/header_smoke/astronomy.cpp @@ -0,0 +1,3 @@ +#include + +int main() { return 0; } diff --git a/tests/header_smoke/astronomy_legacy.cpp b/tests/header_smoke/astronomy_legacy.cpp new file mode 100644 index 00000000..7c213f37 --- /dev/null +++ b/tests/header_smoke/astronomy_legacy.cpp @@ -0,0 +1,4 @@ +#define TIME_SHIELD_ENABLE_LEGACY_ALIASES +#include + +int main() { return 0; } diff --git a/tests/header_smoke/conversions.cpp b/tests/header_smoke/conversions.cpp new file mode 100644 index 00000000..05745752 --- /dev/null +++ b/tests/header_smoke/conversions.cpp @@ -0,0 +1,3 @@ +#include + +int main() { return 0; } diff --git a/tests/header_smoke/core.cpp b/tests/header_smoke/core.cpp new file mode 100644 index 00000000..ec311c0a --- /dev/null +++ b/tests/header_smoke/core.cpp @@ -0,0 +1,3 @@ +#include + +int main() { return 0; } diff --git a/tests/header_smoke/date_time.cpp b/tests/header_smoke/date_time.cpp new file mode 100644 index 00000000..cc4427ad --- /dev/null +++ b/tests/header_smoke/date_time.cpp @@ -0,0 +1,3 @@ +#include + +int main() { return 0; } diff --git a/tests/header_smoke/ntp.cpp b/tests/header_smoke/ntp.cpp new file mode 100644 index 00000000..6ff34ee2 --- /dev/null +++ b/tests/header_smoke/ntp.cpp @@ -0,0 +1,3 @@ +#include + +int main() { return 0; } diff --git a/tests/header_smoke/text.cpp b/tests/header_smoke/text.cpp new file mode 100644 index 00000000..98b4cc5e --- /dev/null +++ b/tests/header_smoke/text.cpp @@ -0,0 +1,3 @@ +#include + +int main() { return 0; } diff --git a/tests/header_smoke/timers.cpp b/tests/header_smoke/timers.cpp new file mode 100644 index 00000000..8f958db9 --- /dev/null +++ b/tests/header_smoke/timers.cpp @@ -0,0 +1,3 @@ +#include + +int main() { return 0; } diff --git a/tests/header_smoke/timezone.cpp b/tests/header_smoke/timezone.cpp new file mode 100644 index 00000000..8075da5a --- /dev/null +++ b/tests/header_smoke/timezone.cpp @@ -0,0 +1,3 @@ +#include + +int main() { return 0; } diff --git a/tests/include_compatibility_test.cpp b/tests/include_compatibility_test.cpp new file mode 100644 index 00000000..6aad3131 --- /dev/null +++ b/tests/include_compatibility_test.cpp @@ -0,0 +1,54 @@ +#define TIME_SHIELD_ENABLE_LEGACY_ALIASES + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if TIME_SHIELD_ENABLE_NTP_CLIENT +# include +# include +# include +# include +# include +# include +# include +#endif + +int main() { + return 0; +} diff --git a/tests/install_consumer/consumer.cpp b/tests/install_consumer/consumer.cpp index 42541a56..1ed16c57 100644 --- a/tests/install_consumer/consumer.cpp +++ b/tests/install_consumer/consumer.cpp @@ -1,6 +1,10 @@ #include +#include +#include #include #if TIME_SHIELD_ENABLE_NTP_CLIENT +#include +#include #include #endif diff --git a/tests/iso8601_negative_test.cpp b/tests/iso8601_negative_test.cpp index f457cf35..6e7fcf6a 100644 --- a/tests/iso8601_negative_test.cpp +++ b/tests/iso8601_negative_test.cpp @@ -1,4 +1,4 @@ -#include +#include #include "test_assert.hpp" /// \brief Negative parsing tests for malformed ISO8601 strings. diff --git a/tests/iso8601_round_trip_test.cpp b/tests/iso8601_round_trip_test.cpp index 015dc09a..db3b6481 100644 --- a/tests/iso8601_round_trip_test.cpp +++ b/tests/iso8601_round_trip_test.cpp @@ -1,6 +1,6 @@ -#include -#include -#include +#include +#include +#include #include "test_assert.hpp" /// \brief ISO8601 round-trip tests for various offsets and precisions. diff --git a/tests/iso_week_date_test.cpp b/tests/iso_week_date_test.cpp index e4fe3f4f..ad3bf094 100644 --- a/tests/iso_week_date_test.cpp +++ b/tests/iso_week_date_test.cpp @@ -1,6 +1,6 @@ -#include -#include -#include +#include +#include +#include #include "test_assert.hpp" #include diff --git a/tests/julian_conversions_test.cpp b/tests/julian_conversions_test.cpp index 8573a83e..29be6c3c 100644 --- a/tests/julian_conversions_test.cpp +++ b/tests/julian_conversions_test.cpp @@ -1,4 +1,4 @@ -#include +#include #include "test_assert.hpp" #include diff --git a/tests/local_time_resolution_test.cpp b/tests/local_time_resolution_test.cpp index d6c24e56..2f9fe320 100644 --- a/tests/local_time_resolution_test.cpp +++ b/tests/local_time_resolution_test.cpp @@ -1,5 +1,5 @@ -#include -#include +#include +#include #include "test_assert.hpp" int main() { diff --git a/tests/moon_phase_test.cpp b/tests/moon_phase_test.cpp index 69903cfc..11af61f7 100644 --- a/tests/moon_phase_test.cpp +++ b/tests/moon_phase_test.cpp @@ -1,5 +1,5 @@ -#include -#include +#include +#include #include "test_assert.hpp" #include diff --git a/tests/negative_time_boundaries_test.cpp b/tests/negative_time_boundaries_test.cpp index cfd1f5ac..04d21be7 100644 --- a/tests/negative_time_boundaries_test.cpp +++ b/tests/negative_time_boundaries_test.cpp @@ -1,4 +1,4 @@ -#include +#include #include "test_assert.hpp" diff --git a/tests/ntp_client_core_test.cpp b/tests/ntp_client_core_test.cpp index 79e7dc07..702737cb 100644 --- a/tests/ntp_client_core_test.cpp +++ b/tests/ntp_client_core_test.cpp @@ -1,11 +1,11 @@ -#include +#include #if TIME_SHIELD_ENABLE_NTP_CLIENT -#include -#include -#include -#include +#include +#include +#include +#include #include "test_assert.hpp" #include diff --git a/tests/ntp_client_pool_runner_test.cpp b/tests/ntp_client_pool_runner_test.cpp index 2e0dec2b..59b3b4a7 100644 --- a/tests/ntp_client_pool_runner_test.cpp +++ b/tests/ntp_client_pool_runner_test.cpp @@ -1,8 +1,8 @@ -#include +#include #if TIME_SHIELD_ENABLE_NTP_CLIENT -#include +#include #include #include "test_assert.hpp" diff --git a/tests/ntp_client_pool_template_test.cpp b/tests/ntp_client_pool_template_test.cpp index ead41910..4a6bef9d 100644 --- a/tests/ntp_client_pool_template_test.cpp +++ b/tests/ntp_client_pool_template_test.cpp @@ -1,8 +1,8 @@ -#include +#include #if TIME_SHIELD_ENABLE_NTP_CLIENT -#include +#include #include "test_assert.hpp" #include diff --git a/tests/ntp_client_pool_test.cpp b/tests/ntp_client_pool_test.cpp index bdafddff..e1531e51 100644 --- a/tests/ntp_client_pool_test.cpp +++ b/tests/ntp_client_pool_test.cpp @@ -1,9 +1,9 @@ -#include +#include #if TIME_SHIELD_ENABLE_NTP_CLIENT && TIME_SHIELD_PLATFORM_UNIX -#include -#include +#include +#include #include #include diff --git a/tests/ntp_client_test.cpp b/tests/ntp_client_test.cpp index 31b9523e..36d5c8ad 100644 --- a/tests/ntp_client_test.cpp +++ b/tests/ntp_client_test.cpp @@ -1,9 +1,9 @@ -#include +#include #if TIME_SHIELD_ENABLE_NTP_CLIENT && TIME_SHIELD_PLATFORM_UNIX -#include -#include +#include +#include #include #include diff --git a/tests/ntp_time_service_concurrency_test.cpp b/tests/ntp_time_service_concurrency_test.cpp index ad880408..7b30bc87 100644 --- a/tests/ntp_time_service_concurrency_test.cpp +++ b/tests/ntp_time_service_concurrency_test.cpp @@ -1,8 +1,8 @@ -#include +#include #if TIME_SHIELD_ENABLE_NTP_CLIENT #define TIME_SHIELD_TEST_FAKE_NTP -#include +#include #include #include "test_assert.hpp" diff --git a/tests/ntp_time_service_late_teardown_test.cpp b/tests/ntp_time_service_late_teardown_test.cpp index a2d92a22..30c1d445 100644 --- a/tests/ntp_time_service_late_teardown_test.cpp +++ b/tests/ntp_time_service_late_teardown_test.cpp @@ -1,8 +1,8 @@ -#include +#include #if TIME_SHIELD_ENABLE_NTP_CLIENT #define TIME_SHIELD_TEST_FAKE_NTP -#include +#include #include "test_assert.hpp" #include diff --git a/tests/ntp_time_service_test.cpp b/tests/ntp_time_service_test.cpp index f77b8983..fa54d712 100644 --- a/tests/ntp_time_service_test.cpp +++ b/tests/ntp_time_service_test.cpp @@ -1,8 +1,8 @@ -#include +#include #if TIME_SHIELD_ENABLE_NTP_CLIENT #define TIME_SHIELD_TEST_FAKE_NTP -#include +#include #include "test_assert.hpp" #include diff --git a/tests/odr/ntp_time_service_a.cpp b/tests/odr/ntp_time_service_a.cpp index a6610064..ad55305a 100644 --- a/tests/odr/ntp_time_service_a.cpp +++ b/tests/odr/ntp_time_service_a.cpp @@ -1,8 +1,8 @@ -#include +#include #if TIME_SHIELD_ENABLE_NTP_CLIENT #define TIME_SHIELD_TEST_FAKE_NTP -#include +#include #include "../test_assert.hpp" #include diff --git a/tests/odr/ntp_time_service_b.cpp b/tests/odr/ntp_time_service_b.cpp index 0b84cc71..ba5382af 100644 --- a/tests/odr/ntp_time_service_b.cpp +++ b/tests/odr/ntp_time_service_b.cpp @@ -1,8 +1,8 @@ -#include +#include #if TIME_SHIELD_ENABLE_NTP_CLIENT #define TIME_SHIELD_TEST_FAKE_NTP -#include +#include #include diff --git a/tests/parse_iso8601_test.cpp b/tests/parse_iso8601_test.cpp index 9ac1a1b7..6df1db08 100644 --- a/tests/parse_iso8601_test.cpp +++ b/tests/parse_iso8601_test.cpp @@ -1,4 +1,4 @@ -#include +#include #include "test_assert.hpp" #include diff --git a/tests/test_fast_date64.cpp b/tests/test_fast_date64.cpp index f51a6727..994aa6d3 100644 --- a/tests/test_fast_date64.cpp +++ b/tests/test_fast_date64.cpp @@ -1,4 +1,4 @@ -#include +#include #if defined(_WIN32) # ifdef min diff --git a/tests/test_ntp_client_protocol_validation.cpp b/tests/test_ntp_client_protocol_validation.cpp index d22f5f83..00385e67 100644 --- a/tests/test_ntp_client_protocol_validation.cpp +++ b/tests/test_ntp_client_protocol_validation.cpp @@ -1,11 +1,11 @@ -#include +#include #if TIME_SHIELD_ENABLE_NTP_CLIENT -#include -#include -#include -#include +#include +#include +#include +#include #include "test_assert.hpp" #include diff --git a/tests/test_timestamp_ms_pre_epoch.cpp b/tests/test_timestamp_ms_pre_epoch.cpp index 937b9e68..c2a116a1 100644 --- a/tests/test_timestamp_ms_pre_epoch.cpp +++ b/tests/test_timestamp_ms_pre_epoch.cpp @@ -1,5 +1,5 @@ -#include -#include +#include +#include #include "test_assert.hpp" #include diff --git a/tests/test_year_boundaries_ms.cpp b/tests/test_year_boundaries_ms.cpp index 5ca76705..95a3020b 100644 --- a/tests/test_year_boundaries_ms.cpp +++ b/tests/test_year_boundaries_ms.cpp @@ -1,5 +1,5 @@ -#include -#include +#include +#include #include #include "test_assert.hpp" diff --git a/tests/time_boundaries_test.cpp b/tests/time_boundaries_test.cpp index 57e901f3..cba19fe9 100644 --- a/tests/time_boundaries_test.cpp +++ b/tests/time_boundaries_test.cpp @@ -1,4 +1,4 @@ -#include +#include #include "test_assert.hpp" /// \brief Tests for leap years, month/year transitions, and time-of-day extremes. diff --git a/tests/time_conversions_coverage_test.cpp b/tests/time_conversions_coverage_test.cpp index db2298ea..0c133def 100644 --- a/tests/time_conversions_coverage_test.cpp +++ b/tests/time_conversions_coverage_test.cpp @@ -1,6 +1,6 @@ #define TIME_SHIELD_ENABLE_LEGACY_ALIASES -#include -#include +#include +#include #include "test_assert.hpp" #include diff --git a/tests/time_conversions_test.cpp b/tests/time_conversions_test.cpp index f4028986..51e5e9e9 100644 --- a/tests/time_conversions_test.cpp +++ b/tests/time_conversions_test.cpp @@ -1,4 +1,4 @@ -#include +#include #include "test_assert.hpp" #include diff --git a/tests/time_format_parser_test.cpp b/tests/time_format_parser_test.cpp index 94510d1e..b79b450a 100644 --- a/tests/time_format_parser_test.cpp +++ b/tests/time_format_parser_test.cpp @@ -1,6 +1,6 @@ -#include -#include -#include +#include +#include +#include #include "test_assert.hpp" #include diff --git a/tests/time_formatting_test.cpp b/tests/time_formatting_test.cpp index d01e6c05..7182540e 100644 --- a/tests/time_formatting_test.cpp +++ b/tests/time_formatting_test.cpp @@ -1,4 +1,4 @@ -#include +#include #include "test_assert.hpp" /// \brief Basic checks for time formatting helpers. diff --git a/tests/time_parser_test.cpp b/tests/time_parser_test.cpp index 691af35c..9ed404af 100644 --- a/tests/time_parser_test.cpp +++ b/tests/time_parser_test.cpp @@ -1,4 +1,4 @@ -#include +#include #include "test_assert.hpp" /// \brief Basic checks for time parsing helpers. diff --git a/tests/time_utils_test.cpp b/tests/time_utils_test.cpp index 44a3f264..ed2fd1c2 100644 --- a/tests/time_utils_test.cpp +++ b/tests/time_utils_test.cpp @@ -1,5 +1,5 @@ -#include -#include +#include +#include #include "test_assert.hpp" #include diff --git a/tests/time_zone_conversion_test.cpp b/tests/time_zone_conversion_test.cpp index 7c760b44..afedb2a0 100644 --- a/tests/time_zone_conversion_test.cpp +++ b/tests/time_zone_conversion_test.cpp @@ -1,5 +1,5 @@ -#include -#include +#include +#include #include "test_assert.hpp" /// \brief Tests CET/EET conversions to UTC including DST transitions. diff --git a/tests/time_zone_conversions_us_test.cpp b/tests/time_zone_conversions_us_test.cpp index e4250ad4..27b70ffc 100644 --- a/tests/time_zone_conversions_us_test.cpp +++ b/tests/time_zone_conversions_us_test.cpp @@ -1,5 +1,5 @@ -#include -#include +#include +#include #include "test_assert.hpp" /// \brief Tests US ET/CT conversions to UTC including DST transitions. diff --git a/tests/time_zone_matrix_test.cpp b/tests/time_zone_matrix_test.cpp index d56a3080..2953a729 100644 --- a/tests/time_zone_matrix_test.cpp +++ b/tests/time_zone_matrix_test.cpp @@ -1,6 +1,6 @@ -#include -#include -#include +#include +#include +#include #include "test_assert.hpp" #include #include diff --git a/tests/time_zone_name_parser_test.cpp b/tests/time_zone_name_parser_test.cpp index d90e3553..b55f9ac5 100644 --- a/tests/time_zone_name_parser_test.cpp +++ b/tests/time_zone_name_parser_test.cpp @@ -1,5 +1,5 @@ -#include -#include +#include +#include #include "test_assert.hpp" #include diff --git a/tests/time_zone_struct_test.cpp b/tests/time_zone_struct_test.cpp index 0d40ddd2..21008a55 100644 --- a/tests/time_zone_struct_test.cpp +++ b/tests/time_zone_struct_test.cpp @@ -1,4 +1,4 @@ -#include +#include #include "test_assert.hpp" /// \brief Basic checks for time zone conversion helpers. diff --git a/tests/timeframe_parser_test.cpp b/tests/timeframe_parser_test.cpp index ef0dbea6..4f66acb8 100644 --- a/tests/timeframe_parser_test.cpp +++ b/tests/timeframe_parser_test.cpp @@ -1,4 +1,4 @@ -#include +#include #include "test_assert.hpp" @@ -60,4 +60,4 @@ int main() { #endif return 0; -} \ No newline at end of file +} diff --git a/tests/timer_scheduler_test.cpp b/tests/timer_scheduler_test.cpp index 4d19d8da..ca9813e3 100644 --- a/tests/timer_scheduler_test.cpp +++ b/tests/timer_scheduler_test.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include "test_assert.hpp" diff --git a/tests/win_time_utils_test.cpp b/tests/win_time_utils_test.cpp index 3cea6e49..201127a2 100644 --- a/tests/win_time_utils_test.cpp +++ b/tests/win_time_utils_test.cpp @@ -1,4 +1,4 @@ -#include +#include #include "test_assert.hpp" /// \brief Windows specific checks for high resolution timers. diff --git a/tests/workday_boundaries_test.cpp b/tests/workday_boundaries_test.cpp index d3a3d857..11722ec9 100644 --- a/tests/workday_boundaries_test.cpp +++ b/tests/workday_boundaries_test.cpp @@ -1,5 +1,5 @@ -#include -#include +#include +#include #include "test_assert.hpp" #include diff --git a/tests/workday_validation_test.cpp b/tests/workday_validation_test.cpp index 444b5327..6da38fce 100644 --- a/tests/workday_validation_test.cpp +++ b/tests/workday_validation_test.cpp @@ -1,5 +1,5 @@ -#include -#include +#include +#include #include "test_assert.hpp" #include diff --git a/tests/zoned_clock_ntp_test.cpp b/tests/zoned_clock_ntp_test.cpp index 40110b7b..179035b8 100644 --- a/tests/zoned_clock_ntp_test.cpp +++ b/tests/zoned_clock_ntp_test.cpp @@ -1,8 +1,8 @@ -#include +#include #if TIME_SHIELD_ENABLE_NTP_CLIENT #define TIME_SHIELD_TEST_FAKE_NTP -#include +#include #include "test_assert.hpp" #include diff --git a/tests/zoned_clock_test.cpp b/tests/zoned_clock_test.cpp index 50422c9b..02dcb111 100644 --- a/tests/zoned_clock_test.cpp +++ b/tests/zoned_clock_test.cpp @@ -1,4 +1,4 @@ -#include +#include #include "test_assert.hpp" #include