From 591d1c11c54142d807ae6ebcb9c704ee9740ba78 Mon Sep 17 00:00:00 2001 From: Aster Seker Date: Fri, 11 Sep 2026 14:10:40 +0300 Subject: [PATCH 1/6] refactor(headers): organize public API by domain Group public headers into core, conversions, text, datetime, timezone, astronomy, timers, and NTP domains. Add domain umbrellas and compatibility forwarding headers, update include dependencies and documentation, and enforce include policy in CTest. --- .github/workflows/ci.yml | 17 + AGENTS.md | 11 + CMakeLists.txt | 15 +- README-RU.md | 29 +- README.md | 35 +- cmake/check_header_include_policy.cmake | 64 + docs/mainpage.md | 29 +- examples/ntp_client_example.cpp | 6 +- examples/ntp_time_service_example.cpp | 6 +- examples/time_conversions_example.cpp | 2 +- examples/time_formatting_example.cpp | 2 +- examples/time_formatting_showcase_example.cpp | 2 +- examples/time_parser_example.cpp | 8 +- examples/time_utils_example.cpp | 2 +- examples/time_zone_conversions_example.cpp | 4 +- include/time_shield.hpp | 49 +- include/time_shield/CpuTickTimer.hpp | 131 +- include/time_shield/DeadlineTimer.hpp | 238 +- include/time_shield/ElapsedTimer.hpp | 182 +- include/time_shield/MoonPhase.hpp | 397 +-- include/time_shield/TimerScheduler.hpp | 644 +---- include/time_shield/ZonedClock.hpp | 366 +-- include/time_shield/astronomy.hpp | 12 + include/time_shield/astronomy/MoonPhase.hpp | 403 ++++ .../astronomy/astronomy_conversions.hpp | 118 + .../astronomy/julian_conversions.hpp | 190 ++ include/time_shield/astronomy_conversions.hpp | 112 +- include/time_shield/config.hpp | 113 +- include/time_shield/constants.hpp | 163 +- include/time_shield/conversions.hpp | 22 + .../conversions/date_conversions.hpp | 109 + .../conversions/date_time_conversions.hpp | 1196 ++++++++++ .../conversions/detail/fast_date.hpp | 245 ++ .../conversions/detail/floor_math.hpp | 27 + .../time_shield/conversions/detail/mul_hi.hpp | 59 + .../conversions/iso_week_conversions.hpp | 263 ++ .../conversions/legacy_aliases.hpp | 172 ++ .../ole_automation_conversions.hpp | 157 ++ .../conversions/time_conversion_aliases.hpp | 2118 +++++++++++++++++ .../conversions/time_unit_conversions.hpp | 485 ++++ .../conversions/time_zone_offset.hpp | 75 + .../time_zone_offset_conversions.hpp | 68 + .../conversions/unix_time_conversions.hpp | 329 +++ .../conversions/workday_conversions.hpp | 342 +++ include/time_shield/core.hpp | 19 + include/time_shield/core/config.hpp | 119 + include/time_shield/core/constants.hpp | 169 ++ include/time_shield/core/date_struct.hpp | 37 + include/time_shield/core/date_time_struct.hpp | 58 + include/time_shield/core/enums.hpp | 312 +++ include/time_shield/core/initialization.hpp | 29 + include/time_shield/core/iso_week_struct.hpp | 39 + include/time_shield/core/time_struct.hpp | 40 + include/time_shield/core/time_utils.hpp | 346 +++ include/time_shield/core/time_zone_struct.hpp | 121 + include/time_shield/core/types.hpp | 67 + include/time_shield/core/validation.hpp | 397 +++ include/time_shield/date_conversions.hpp | 106 +- include/time_shield/date_struct.hpp | 31 +- include/time_shield/date_time_conversions.hpp | 1197 +--------- include/time_shield/date_time_struct.hpp | 52 +- include/time_shield/datetime.hpp | 8 + .../time_shield/{ => datetime}/DateTime.hpp | 23 +- include/time_shield/detail/fast_date.hpp | 239 +- include/time_shield/detail/floor_math.hpp | 21 +- include/time_shield/detail/mul_hi.hpp | 53 +- include/time_shield/enums.hpp | 306 +-- include/time_shield/initialization.hpp | 23 +- include/time_shield/iso_week_conversions.hpp | 260 +- include/time_shield/iso_week_struct.hpp | 33 +- include/time_shield/julian_conversions.hpp | 187 +- include/time_shield/legacy_aliases.hpp | 165 +- include/time_shield/ntp.hpp | 15 + .../ntp/detail/ntp_client_core.hpp | 87 + include/time_shield/ntp/detail/ntp_packet.hpp | 166 ++ .../time_shield/ntp/detail/udp_transport.hpp | 35 + .../ntp/detail/udp_transport_posix.hpp | 95 + .../ntp/detail/udp_transport_win.hpp | 95 + include/time_shield/ntp/detail/wsa_guard.hpp | 69 + include/time_shield/ntp/ntp_client.hpp | 179 ++ include/time_shield/ntp/ntp_client_pool.hpp | 732 ++++++ .../ntp/ntp_client_pool_runner.hpp | 233 ++ include/time_shield/ntp/ntp_time_service.hpp | 894 +++++++ include/time_shield/ntp_client.hpp | 174 +- .../ntp_client/ntp_client_core.hpp | 81 +- include/time_shield/ntp_client/ntp_packet.hpp | 160 +- .../time_shield/ntp_client/udp_transport.hpp | 29 +- .../ntp_client/udp_transport_posix.hpp | 89 +- .../ntp_client/udp_transport_win.hpp | 89 +- include/time_shield/ntp_client/wsa_guard.hpp | 63 +- include/time_shield/ntp_client_pool.hpp | 727 +----- .../time_shield/ntp_client_pool_runner.hpp | 228 +- include/time_shield/ntp_time_service.hpp | 889 +------ .../ole_automation_conversions.hpp | 153 +- include/time_shield/text.hpp | 12 + .../time_shield/text/time_format_parser.hpp | 1158 +++++++++ include/time_shield/text/time_formatting.hpp | 1006 ++++++++ include/time_shield/text/time_parser.hpp | 1765 ++++++++++++++ .../time_shield/time_conversion_aliases.hpp | 2112 +--------------- include/time_shield/time_conversions.hpp | 28 +- include/time_shield/time_format_parser.hpp | 1158 +-------- include/time_shield/time_formatting.hpp | 1003 +------- include/time_shield/time_parser.hpp | 1764 +------------- include/time_shield/time_struct.hpp | 34 +- include/time_shield/time_unit_conversions.hpp | 481 +--- include/time_shield/time_utils.hpp | 340 +-- include/time_shield/time_zone_conversions.hpp | 1062 +-------- include/time_shield/time_zone_offset.hpp | 71 +- .../time_zone_offset_conversions.hpp | 65 +- include/time_shield/time_zone_struct.hpp | 115 +- include/time_shield/timers.hpp | 12 + include/time_shield/timers/CpuTickTimer.hpp | 137 ++ include/time_shield/timers/DeadlineTimer.hpp | 243 ++ include/time_shield/timers/ElapsedTimer.hpp | 187 ++ include/time_shield/timers/TimerScheduler.hpp | 650 +++++ include/time_shield/timezone.hpp | 16 + include/time_shield/timezone/ZonedClock.hpp | 369 +++ .../timezone/time_zone_conversions.hpp | 1066 +++++++++ include/time_shield/types.hpp | 61 +- include/time_shield/unix_time_conversions.hpp | 326 +-- include/time_shield/validation.hpp | 391 +-- include/time_shield/workday_conversions.hpp | 337 +-- tests/astronomy_ole_conversions_test.cpp | 3 +- tests/constants_test.cpp | 2 +- tests/deadline_elapsed_timer_test.cpp | 4 +- tests/domain_umbrellas_test.cpp | 14 + tests/gmt_time_zone_conversion_test.cpp | 4 +- tests/include_compatibility_test.cpp | 54 + tests/install_consumer/consumer.cpp | 4 + tests/iso8601_negative_test.cpp | 2 +- tests/iso8601_round_trip_test.cpp | 6 +- tests/iso_week_date_test.cpp | 6 +- tests/julian_conversions_test.cpp | 2 +- tests/local_time_resolution_test.cpp | 4 +- tests/moon_phase_test.cpp | 4 +- tests/negative_time_boundaries_test.cpp | 2 +- tests/ntp_client_core_test.cpp | 10 +- tests/ntp_client_pool_runner_test.cpp | 4 +- tests/ntp_client_pool_template_test.cpp | 4 +- tests/ntp_client_pool_test.cpp | 6 +- tests/ntp_client_test.cpp | 6 +- tests/ntp_time_service_concurrency_test.cpp | 4 +- tests/ntp_time_service_late_teardown_test.cpp | 4 +- tests/ntp_time_service_test.cpp | 4 +- tests/odr/ntp_time_service_a.cpp | 4 +- tests/odr/ntp_time_service_b.cpp | 4 +- tests/parse_iso8601_test.cpp | 2 +- tests/test_fast_date64.cpp | 2 +- tests/test_ntp_client_protocol_validation.cpp | 10 +- tests/test_timestamp_ms_pre_epoch.cpp | 4 +- tests/test_year_boundaries_ms.cpp | 4 +- tests/time_boundaries_test.cpp | 2 +- tests/time_conversions_coverage_test.cpp | 4 +- tests/time_conversions_test.cpp | 2 +- tests/time_format_parser_test.cpp | 6 +- tests/time_formatting_test.cpp | 2 +- tests/time_parser_test.cpp | 2 +- tests/time_utils_test.cpp | 4 +- tests/time_zone_conversion_test.cpp | 4 +- tests/time_zone_conversions_us_test.cpp | 4 +- tests/time_zone_matrix_test.cpp | 6 +- tests/time_zone_name_parser_test.cpp | 4 +- tests/time_zone_struct_test.cpp | 2 +- tests/timeframe_parser_test.cpp | 4 +- tests/timer_scheduler_test.cpp | 2 +- tests/win_time_utils_test.cpp | 2 +- tests/workday_boundaries_test.cpp | 4 +- tests/workday_validation_test.cpp | 4 +- tests/zoned_clock_ntp_test.cpp | 4 +- tests/zoned_clock_test.cpp | 2 +- 170 files changed, 17785 insertions(+), 17187 deletions(-) create mode 100644 cmake/check_header_include_policy.cmake create mode 100644 include/time_shield/astronomy.hpp create mode 100644 include/time_shield/astronomy/MoonPhase.hpp create mode 100644 include/time_shield/astronomy/astronomy_conversions.hpp create mode 100644 include/time_shield/astronomy/julian_conversions.hpp create mode 100644 include/time_shield/conversions.hpp create mode 100644 include/time_shield/conversions/date_conversions.hpp create mode 100644 include/time_shield/conversions/date_time_conversions.hpp create mode 100644 include/time_shield/conversions/detail/fast_date.hpp create mode 100644 include/time_shield/conversions/detail/floor_math.hpp create mode 100644 include/time_shield/conversions/detail/mul_hi.hpp create mode 100644 include/time_shield/conversions/iso_week_conversions.hpp create mode 100644 include/time_shield/conversions/legacy_aliases.hpp create mode 100644 include/time_shield/conversions/ole_automation_conversions.hpp create mode 100644 include/time_shield/conversions/time_conversion_aliases.hpp create mode 100644 include/time_shield/conversions/time_unit_conversions.hpp create mode 100644 include/time_shield/conversions/time_zone_offset.hpp create mode 100644 include/time_shield/conversions/time_zone_offset_conversions.hpp create mode 100644 include/time_shield/conversions/unix_time_conversions.hpp create mode 100644 include/time_shield/conversions/workday_conversions.hpp create mode 100644 include/time_shield/core.hpp create mode 100644 include/time_shield/core/config.hpp create mode 100644 include/time_shield/core/constants.hpp create mode 100644 include/time_shield/core/date_struct.hpp create mode 100644 include/time_shield/core/date_time_struct.hpp create mode 100644 include/time_shield/core/enums.hpp create mode 100644 include/time_shield/core/initialization.hpp create mode 100644 include/time_shield/core/iso_week_struct.hpp create mode 100644 include/time_shield/core/time_struct.hpp create mode 100644 include/time_shield/core/time_utils.hpp create mode 100644 include/time_shield/core/time_zone_struct.hpp create mode 100644 include/time_shield/core/types.hpp create mode 100644 include/time_shield/core/validation.hpp create mode 100644 include/time_shield/datetime.hpp rename include/time_shield/{ => datetime}/DateTime.hpp (97%) create mode 100644 include/time_shield/ntp.hpp create mode 100644 include/time_shield/ntp/detail/ntp_client_core.hpp create mode 100644 include/time_shield/ntp/detail/ntp_packet.hpp create mode 100644 include/time_shield/ntp/detail/udp_transport.hpp create mode 100644 include/time_shield/ntp/detail/udp_transport_posix.hpp create mode 100644 include/time_shield/ntp/detail/udp_transport_win.hpp create mode 100644 include/time_shield/ntp/detail/wsa_guard.hpp create mode 100644 include/time_shield/ntp/ntp_client.hpp create mode 100644 include/time_shield/ntp/ntp_client_pool.hpp create mode 100644 include/time_shield/ntp/ntp_client_pool_runner.hpp create mode 100644 include/time_shield/ntp/ntp_time_service.hpp create mode 100644 include/time_shield/text.hpp create mode 100644 include/time_shield/text/time_format_parser.hpp create mode 100644 include/time_shield/text/time_formatting.hpp create mode 100644 include/time_shield/text/time_parser.hpp create mode 100644 include/time_shield/timers.hpp create mode 100644 include/time_shield/timers/CpuTickTimer.hpp create mode 100644 include/time_shield/timers/DeadlineTimer.hpp create mode 100644 include/time_shield/timers/ElapsedTimer.hpp create mode 100644 include/time_shield/timers/TimerScheduler.hpp create mode 100644 include/time_shield/timezone.hpp create mode 100644 include/time_shield/timezone/ZonedClock.hpp create mode 100644 include/time_shield/timezone/time_zone_conversions.hpp create mode 100644 tests/domain_umbrellas_test.cpp create mode 100644 tests/include_compatibility_test.cpp 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..9c5aa735 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`, + `datetime.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..0a7168e3 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) diff --git a/README-RU.md b/README-RU.md index f0ecde61..efe82c58 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/datetime.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..b76779b4 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/datetime.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..585f694f --- /dev/null +++ b/cmake/check_header_include_policy.cmake @@ -0,0 +1,64 @@ +if(NOT DEFINED TIME_SHIELD_SOURCE_DIR) + message(FATAL_ERROR "TIME_SHIELD_SOURCE_DIR is required") +endif() + +file(GLOB_RECURSE TIME_SHIELD_DOMAIN_HEADERS + "${TIME_SHIELD_SOURCE_DIR}/include/time_shield/*/*.hpp" +) + +foreach(header IN LISTS TIME_SHIELD_DOMAIN_HEADERS) + file(RELATIVE_PATH relative_header + "${TIME_SHIELD_SOURCE_DIR}/include/time_shield" + "${header}" + ) + string(REGEX MATCH "^([^/]+)/" domain_match "${relative_header}") + set(domain "${CMAKE_MATCH_1}") + if(relative_header MATCHES "^(detail|ntp_client)/") + 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}") + string(REGEX MATCH "^([^/]+)/" target_domain_match "${target}") + set(target_domain "${CMAKE_MATCH_1}") + if(target MATCHES "^[^/]+\\.hpp$") + set(target_domain "${target}") + endif() + set(known_umbrella FALSE) + foreach(umbrella IN ITEMS core conversions text datetime timezone astronomy timers ntp) + if(target STREQUAL "${umbrella}.hpp") + set(known_umbrella TRUE) + endif() + endforeach() + if(target_domain STREQUAL "${domain}.hpp" AND NOT relative_header MATCHES "(^|/)legacy_aliases\\.hpp$") + message(FATAL_ERROR + "Domain leaf must not include its own umbrella: ${relative_header}: ${line}") + elseif(target MATCHES "^[^/]+/" AND NOT target_domain STREQUAL "${domain}") + message(FATAL_ERROR + "Cross-domain leaf include is forbidden: ${relative_header}: ${line}") + elseif(NOT known_umbrella AND NOT target_domain STREQUAL "${domain}") + message(FATAL_ERROR + "Cross-domain include must use an umbrella: ${relative_header}: ${line}") + endif() + if(NOT known_umbrella AND NOT target MATCHES "^[a-z_]+/") + message(FATAL_ERROR "Invalid Time Shield include: ${relative_header}: ${line}") + 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/") + message(FATAL_ERROR "Nested cross-domain local include: ${relative_header}: ${line}") + endif() + endif() + endforeach() +endforeach() + +message(STATUS "Header include policy passed") diff --git a/docs/mainpage.md b/docs/mainpage.md index 6ffbf047..77041c39 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/datetime.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..fa37a1d2 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/datetime.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..ee7d2530 100644 --- a/include/time_shield/CpuTickTimer.hpp +++ b/include/time_shield/CpuTickTimer.hpp @@ -3,135 +3,6 @@ #ifndef TIME_SHIELD_HEADER_TIME_SHIELD_CPUTICKTIMER_HPP_INCLUDED #define TIME_SHIELD_HEADER_TIME_SHIELD_CPUTICKTIMER_HPP_INCLUDED -/// \file CpuTickTimer.hpp -/// \brief Helper class for measuring CPU time using get_cpu_time(). - -#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 +#include #endif // TIME_SHIELD_HEADER_TIME_SHIELD_CPUTICKTIMER_HPP_INCLUDED diff --git a/include/time_shield/DeadlineTimer.hpp b/include/time_shield/DeadlineTimer.hpp index 57aea3a1..7ebadf0b 100644 --- a/include/time_shield/DeadlineTimer.hpp +++ b/include/time_shield/DeadlineTimer.hpp @@ -3,242 +3,6 @@ #ifndef TIME_SHIELD_HEADER_TIME_SHIELD_DEADLINETIMER_HPP_INCLUDED #define TIME_SHIELD_HEADER_TIME_SHIELD_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 "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 +#include #endif // TIME_SHIELD_HEADER_TIME_SHIELD_DEADLINETIMER_HPP_INCLUDED diff --git a/include/time_shield/ElapsedTimer.hpp b/include/time_shield/ElapsedTimer.hpp index 14ff0094..ba4bd2f2 100644 --- a/include/time_shield/ElapsedTimer.hpp +++ b/include/time_shield/ElapsedTimer.hpp @@ -3,186 +3,6 @@ #ifndef TIME_SHIELD_HEADER_TIME_SHIELD_ELAPSEDTIMER_HPP_INCLUDED #define TIME_SHIELD_HEADER_TIME_SHIELD_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 "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 +#include #endif // TIME_SHIELD_HEADER_TIME_SHIELD_ELAPSEDTIMER_HPP_INCLUDED diff --git a/include/time_shield/MoonPhase.hpp b/include/time_shield/MoonPhase.hpp index 0f7de3ed..1e47ec27 100644 --- a/include/time_shield/MoonPhase.hpp +++ b/include/time_shield/MoonPhase.hpp @@ -3,401 +3,6 @@ #ifndef TIME_SHIELD_HEADER_TIME_SHIELD_MOONPHASE_HPP_INCLUDED #define TIME_SHIELD_HEADER_TIME_SHIELD_MOONPHASE_HPP_INCLUDED -/// \file MoonPhase.hpp -/// \brief Geocentric Moon phase calculator and result helpers. -/// \ingroup time_conversions - -#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 +#include #endif // TIME_SHIELD_HEADER_TIME_SHIELD_MOONPHASE_HPP_INCLUDED diff --git a/include/time_shield/TimerScheduler.hpp b/include/time_shield/TimerScheduler.hpp index 24972383..f68d82bd 100644 --- a/include/time_shield/TimerScheduler.hpp +++ b/include/time_shield/TimerScheduler.hpp @@ -3,648 +3,6 @@ #ifndef TIME_SHIELD_HEADER_TIME_SHIELD_TIMERSCHEDULER_HPP_INCLUDED #define TIME_SHIELD_HEADER_TIME_SHIELD_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 "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 +#include #endif // TIME_SHIELD_HEADER_TIME_SHIELD_TIMERSCHEDULER_HPP_INCLUDED diff --git a/include/time_shield/ZonedClock.hpp b/include/time_shield/ZonedClock.hpp index 0bc6662e..9c7b2efc 100644 --- a/include/time_shield/ZonedClock.hpp +++ b/include/time_shield/ZonedClock.hpp @@ -3,370 +3,6 @@ #ifndef TIME_SHIELD_HEADER_TIME_SHIELD_ZONEDCLOCK_HPP_INCLUDED #define TIME_SHIELD_HEADER_TIME_SHIELD_ZONEDCLOCK_HPP_INCLUDED -/// \file ZonedClock.hpp -/// \brief Header-only clock wrapper for named zones, fixed offsets, and optional NTP-backed UTC time. - -#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 +#include #endif // TIME_SHIELD_HEADER_TIME_SHIELD_ZONEDCLOCK_HPP_INCLUDED diff --git a/include/time_shield/astronomy.hpp b/include/time_shield/astronomy.hpp new file mode 100644 index 00000000..d4aebeb9 --- /dev/null +++ b/include/time_shield/astronomy.hpp @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: MIT +#pragma once +#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_ASTRONOMY_HPP_INCLUDED +#define TIME_SHIELD_HEADER_TIME_SHIELD_ASTRONOMY_HPP_INCLUDED + +#include +#include +#include +#include +#include + +#endif // TIME_SHIELD_HEADER_TIME_SHIELD_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..9a4351c8 --- /dev/null +++ b/include/time_shield/astronomy/MoonPhase.hpp @@ -0,0 +1,403 @@ +// SPDX-License-Identifier: MIT +#pragma once +#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_ASTRONOMY_MOONPHASE_HPP_INCLUDED +#define TIME_SHIELD_HEADER_TIME_SHIELD_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_TIME_SHIELD_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..1bf86da9 --- /dev/null +++ b/include/time_shield/astronomy/astronomy_conversions.hpp @@ -0,0 +1,118 @@ +// SPDX-License-Identifier: MIT +#pragma once +#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_ASTRONOMY_ASTRONOMY_CONVERSIONS_HPP_INCLUDED +#define TIME_SHIELD_HEADER_TIME_SHIELD_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_TIME_SHIELD_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..77319556 --- /dev/null +++ b/include/time_shield/astronomy/julian_conversions.hpp @@ -0,0 +1,190 @@ +// SPDX-License-Identifier: MIT +#pragma once +#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_ASTRONOMY_JULIAN_CONVERSIONS_HPP_INCLUDED +#define TIME_SHIELD_HEADER_TIME_SHIELD_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_TIME_SHIELD_ASTRONOMY_JULIAN_CONVERSIONS_HPP_INCLUDED diff --git a/include/time_shield/astronomy_conversions.hpp b/include/time_shield/astronomy_conversions.hpp index bdef4367..6c5c93ed 100644 --- a/include/time_shield/astronomy_conversions.hpp +++ b/include/time_shield/astronomy_conversions.hpp @@ -3,116 +3,6 @@ #ifndef TIME_SHIELD_HEADER_TIME_SHIELD_ASTRONOMY_CONVERSIONS_HPP_INCLUDED #define TIME_SHIELD_HEADER_TIME_SHIELD_ASTRONOMY_CONVERSIONS_HPP_INCLUDED -/// \file astronomy_conversions.hpp -/// \brief Astronomy entry header with Julian conversions and lunar helpers. -/// \ingroup time_conversions - -#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 +#include #endif // TIME_SHIELD_HEADER_TIME_SHIELD_ASTRONOMY_CONVERSIONS_HPP_INCLUDED diff --git a/include/time_shield/config.hpp b/include/time_shield/config.hpp index 8c10b59b..75bfca94 100644 --- a/include/time_shield/config.hpp +++ b/include/time_shield/config.hpp @@ -3,117 +3,6 @@ #ifndef TIME_SHIELD_HEADER_TIME_SHIELD_CONFIG_HPP_INCLUDED #define TIME_SHIELD_HEADER_TIME_SHIELD_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 -///@} +#include #endif // TIME_SHIELD_HEADER_TIME_SHIELD_CONFIG_HPP_INCLUDED diff --git a/include/time_shield/constants.hpp b/include/time_shield/constants.hpp index 7036075b..fd2e4f1a 100644 --- a/include/time_shield/constants.hpp +++ b/include/time_shield/constants.hpp @@ -3,167 +3,6 @@ #ifndef TIME_SHIELD_HEADER_TIME_SHIELD_CONSTANTS_HPP_INCLUDED #define TIME_SHIELD_HEADER_TIME_SHIELD_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 +#include #endif // TIME_SHIELD_HEADER_TIME_SHIELD_CONSTANTS_HPP_INCLUDED diff --git a/include/time_shield/conversions.hpp b/include/time_shield/conversions.hpp new file mode 100644 index 00000000..cb65ea94 --- /dev/null +++ b/include/time_shield/conversions.hpp @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT +#pragma once +#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_CONVERSIONS_HPP_INCLUDED +#define TIME_SHIELD_HEADER_TIME_SHIELD_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_TIME_SHIELD_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..6cda2a45 --- /dev/null +++ b/include/time_shield/conversions/date_conversions.hpp @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: MIT +#pragma once +#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_CONVERSIONS_DATE_CONVERSIONS_HPP_INCLUDED +#define TIME_SHIELD_HEADER_TIME_SHIELD_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_TIME_SHIELD_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..93da66e8 --- /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_TIME_SHIELD_CONVERSIONS_DATE_TIME_CONVERSIONS_HPP_INCLUDED +#define TIME_SHIELD_HEADER_TIME_SHIELD_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_TIME_SHIELD_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..29c71275 --- /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_TIME_SHIELD_CONVERSIONS_DETAIL_FAST_DATE_HPP_INCLUDED +#define TIME_SHIELD_HEADER_TIME_SHIELD_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_TIME_SHIELD_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..c1fca356 --- /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_TIME_SHIELD_CONVERSIONS_DETAIL_FLOOR_MATH_HPP_INCLUDED +#define TIME_SHIELD_HEADER_TIME_SHIELD_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_TIME_SHIELD_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..72033193 --- /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_TIME_SHIELD_CONVERSIONS_DETAIL_MUL_HI_HPP_INCLUDED +#define TIME_SHIELD_HEADER_TIME_SHIELD_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_TIME_SHIELD_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..148f1485 --- /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_TIME_SHIELD_CONVERSIONS_ISO_WEEK_CONVERSIONS_HPP_INCLUDED +#define TIME_SHIELD_HEADER_TIME_SHIELD_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_TIME_SHIELD_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..7e54aca7 --- /dev/null +++ b/include/time_shield/conversions/legacy_aliases.hpp @@ -0,0 +1,172 @@ +// SPDX-License-Identifier: MIT +#pragma once +#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_CONVERSIONS_LEGACY_ALIASES_HPP_INCLUDED +#define TIME_SHIELD_HEADER_TIME_SHIELD_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 + +#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_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..ef0826f6 --- /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_TIME_SHIELD_CONVERSIONS_OLE_AUTOMATION_CONVERSIONS_HPP_INCLUDED +#define TIME_SHIELD_HEADER_TIME_SHIELD_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_TIME_SHIELD_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..cad78fa7 --- /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_TIME_SHIELD_CONVERSIONS_TIME_CONVERSION_ALIASES_HPP_INCLUDED +#define TIME_SHIELD_HEADER_TIME_SHIELD_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_TIME_SHIELD_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..d12e6bce --- /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_TIME_SHIELD_CONVERSIONS_TIME_UNIT_CONVERSIONS_HPP_INCLUDED +#define TIME_SHIELD_HEADER_TIME_SHIELD_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_TIME_SHIELD_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..db832ae8 --- /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_TIME_SHIELD_CONVERSIONS_TIME_ZONE_OFFSET_HPP_INCLUDED +#define TIME_SHIELD_HEADER_TIME_SHIELD_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_TIME_SHIELD_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..5cb43a04 --- /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_TIME_SHIELD_CONVERSIONS_TIME_ZONE_OFFSET_CONVERSIONS_HPP_INCLUDED +#define TIME_SHIELD_HEADER_TIME_SHIELD_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_TIME_SHIELD_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..1cc96e3e --- /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_TIME_SHIELD_CONVERSIONS_UNIX_TIME_CONVERSIONS_HPP_INCLUDED +#define TIME_SHIELD_HEADER_TIME_SHIELD_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_TIME_SHIELD_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..111c5ccd --- /dev/null +++ b/include/time_shield/conversions/workday_conversions.hpp @@ -0,0 +1,342 @@ +// SPDX-License-Identifier: MIT +#pragma once +#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_CONVERSIONS_WORKDAY_CONVERSIONS_HPP_INCLUDED +#define TIME_SHIELD_HEADER_TIME_SHIELD_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_TIME_SHIELD_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..56609043 --- /dev/null +++ b/include/time_shield/core.hpp @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MIT +#pragma once +#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_CORE_HPP_INCLUDED +#define TIME_SHIELD_HEADER_TIME_SHIELD_CORE_HPP_INCLUDED + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#endif // TIME_SHIELD_HEADER_TIME_SHIELD_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..4c22f476 --- /dev/null +++ b/include/time_shield/core/config.hpp @@ -0,0 +1,119 @@ +// SPDX-License-Identifier: MIT +#pragma once +#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_CORE_CONFIG_HPP_INCLUDED +#define TIME_SHIELD_HEADER_TIME_SHIELD_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_TIME_SHIELD_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..d439b3c2 --- /dev/null +++ b/include/time_shield/core/constants.hpp @@ -0,0 +1,169 @@ +// SPDX-License-Identifier: MIT +#pragma once +#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_CORE_CONSTANTS_HPP_INCLUDED +#define TIME_SHIELD_HEADER_TIME_SHIELD_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_TIME_SHIELD_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..fa7bc34b --- /dev/null +++ b/include/time_shield/core/date_struct.hpp @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: MIT +#pragma once +#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_CORE_DATE_STRUCT_HPP_INCLUDED +#define TIME_SHIELD_HEADER_TIME_SHIELD_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_TIME_SHIELD_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..ad8ad0e5 --- /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_TIME_SHIELD_CORE_DATE_TIME_STRUCT_HPP_INCLUDED +#define TIME_SHIELD_HEADER_TIME_SHIELD_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_TIME_SHIELD_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..3c13bac6 --- /dev/null +++ b/include/time_shield/core/enums.hpp @@ -0,0 +1,312 @@ +// SPDX-License-Identifier: MIT +#pragma once +#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_CORE_ENUMS_HPP_INCLUDED +#define TIME_SHIELD_HEADER_TIME_SHIELD_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_TIME_SHIELD_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..c2e0097f --- /dev/null +++ b/include/time_shield/core/initialization.hpp @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT +#pragma once +#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_CORE_INITIALIZATION_HPP_INCLUDED +#define TIME_SHIELD_HEADER_TIME_SHIELD_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_TIME_SHIELD_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..e4e9a47e --- /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_TIME_SHIELD_CORE_ISO_WEEK_STRUCT_HPP_INCLUDED +#define TIME_SHIELD_HEADER_TIME_SHIELD_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_TIME_SHIELD_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..40f61d54 --- /dev/null +++ b/include/time_shield/core/time_struct.hpp @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: MIT +#pragma once +#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_CORE_TIME_STRUCT_HPP_INCLUDED +#define TIME_SHIELD_HEADER_TIME_SHIELD_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_TIME_SHIELD_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..6915865d --- /dev/null +++ b/include/time_shield/core/time_utils.hpp @@ -0,0 +1,346 @@ +// SPDX-License-Identifier: MIT +#pragma once +#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_CORE_TIME_UTILS_HPP_INCLUDED +#define TIME_SHIELD_HEADER_TIME_SHIELD_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 +# 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_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..024dbebf --- /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_TIME_SHIELD_CORE_TIME_ZONE_STRUCT_HPP_INCLUDED +#define TIME_SHIELD_HEADER_TIME_SHIELD_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_TIME_SHIELD_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..ffe45b6b --- /dev/null +++ b/include/time_shield/core/types.hpp @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: MIT +#pragma once +#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_CORE_TYPES_HPP_INCLUDED +#define TIME_SHIELD_HEADER_TIME_SHIELD_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_TIME_SHIELD_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..bd46b959 --- /dev/null +++ b/include/time_shield/core/validation.hpp @@ -0,0 +1,397 @@ +// SPDX-License-Identifier: MIT +#pragma once +#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_CORE_VALIDATION_HPP_INCLUDED +#define TIME_SHIELD_HEADER_TIME_SHIELD_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_TIME_SHIELD_CORE_VALIDATION_HPP_INCLUDED diff --git a/include/time_shield/date_conversions.hpp b/include/time_shield/date_conversions.hpp index 397bc57b..23a3fe0b 100644 --- a/include/time_shield/date_conversions.hpp +++ b/include/time_shield/date_conversions.hpp @@ -3,110 +3,6 @@ #ifndef TIME_SHIELD_HEADER_TIME_SHIELD_DATE_CONVERSIONS_HPP_INCLUDED #define TIME_SHIELD_HEADER_TIME_SHIELD_DATE_CONVERSIONS_HPP_INCLUDED -/// \file date_conversions.hpp -/// \brief Conversions related to calendar dates and DateStruct helpers. - -#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 +#include #endif // TIME_SHIELD_HEADER_TIME_SHIELD_DATE_CONVERSIONS_HPP_INCLUDED diff --git a/include/time_shield/date_struct.hpp b/include/time_shield/date_struct.hpp index b88db0ca..9e0839ce 100644 --- a/include/time_shield/date_struct.hpp +++ b/include/time_shield/date_struct.hpp @@ -3,35 +3,6 @@ #ifndef TIME_SHIELD_HEADER_TIME_SHIELD_DATE_STRUCT_HPP_INCLUDED #define TIME_SHIELD_HEADER_TIME_SHIELD_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 +#include #endif // TIME_SHIELD_HEADER_TIME_SHIELD_DATE_STRUCT_HPP_INCLUDED diff --git a/include/time_shield/date_time_conversions.hpp b/include/time_shield/date_time_conversions.hpp index 843add70..5e0dc7af 100644 --- a/include/time_shield/date_time_conversions.hpp +++ b/include/time_shield/date_time_conversions.hpp @@ -3,1201 +3,6 @@ #ifndef TIME_SHIELD_HEADER_TIME_SHIELD_DATE_TIME_CONVERSIONS_HPP_INCLUDED #define TIME_SHIELD_HEADER_TIME_SHIELD_DATE_TIME_CONVERSIONS_HPP_INCLUDED -/// \file date_time_conversions.hpp -/// \brief Conversions involving DateTimeStruct and day boundary helpers. - -#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 +#include #endif // TIME_SHIELD_HEADER_TIME_SHIELD_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..753dbcdf 100644 --- a/include/time_shield/date_time_struct.hpp +++ b/include/time_shield/date_time_struct.hpp @@ -3,56 +3,6 @@ #ifndef TIME_SHIELD_HEADER_TIME_SHIELD_DATE_TIME_STRUCT_HPP_INCLUDED #define TIME_SHIELD_HEADER_TIME_SHIELD_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 +#include #endif // TIME_SHIELD_HEADER_TIME_SHIELD_DATE_TIME_STRUCT_HPP_INCLUDED diff --git a/include/time_shield/datetime.hpp b/include/time_shield/datetime.hpp new file mode 100644 index 00000000..d4158a17 --- /dev/null +++ b/include/time_shield/datetime.hpp @@ -0,0 +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 + +#include + +#endif // TIME_SHIELD_HEADER_TIME_SHIELD_DATETIME_HPP_INCLUDED diff --git a/include/time_shield/DateTime.hpp b/include/time_shield/datetime/DateTime.hpp similarity index 97% rename from include/time_shield/DateTime.hpp rename to include/time_shield/datetime/DateTime.hpp index a51265b7..791a240c 100644 --- a/include/time_shield/DateTime.hpp +++ b/include/time_shield/datetime/DateTime.hpp @@ -1,25 +1,14 @@ // 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_TIME_SHIELD_DATETIME_DATETIME_HPP_INCLUDED +#define TIME_SHIELD_HEADER_TIME_SHIELD_DATETIME_DATETIME_HPP_INCLUDED /// \file DateTime.hpp /// \brief Value-type wrapper for timestamps with fixed UTC offset. -#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 #include @@ -711,4 +700,4 @@ namespace time_shield { } // namespace time_shield -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_DATETIME_HPP_INCLUDED +#endif // TIME_SHIELD_HEADER_TIME_SHIELD_DATETIME_DATETIME_HPP_INCLUDED diff --git a/include/time_shield/detail/fast_date.hpp b/include/time_shield/detail/fast_date.hpp index 537ce74e..c117baf0 100644 --- a/include/time_shield/detail/fast_date.hpp +++ b/include/time_shield/detail/fast_date.hpp @@ -3,243 +3,6 @@ #ifndef TIME_SHIELD_HEADER_TIME_SHIELD_DETAIL_FAST_DATE_HPP_INCLUDED #define TIME_SHIELD_HEADER_TIME_SHIELD_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 +#include #endif // TIME_SHIELD_HEADER_TIME_SHIELD_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..27569508 100644 --- a/include/time_shield/detail/floor_math.hpp +++ b/include/time_shield/detail/floor_math.hpp @@ -3,25 +3,6 @@ #ifndef TIME_SHIELD_HEADER_TIME_SHIELD_DETAIL_FLOOR_MATH_HPP_INCLUDED #define TIME_SHIELD_HEADER_TIME_SHIELD_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 +#include #endif // TIME_SHIELD_HEADER_TIME_SHIELD_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..eb66fe77 100644 --- a/include/time_shield/detail/mul_hi.hpp +++ b/include/time_shield/detail/mul_hi.hpp @@ -3,57 +3,6 @@ #ifndef TIME_SHIELD_HEADER_TIME_SHIELD_DETAIL_MUL_HI_HPP_INCLUDED #define TIME_SHIELD_HEADER_TIME_SHIELD_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 +#include #endif // TIME_SHIELD_HEADER_TIME_SHIELD_DETAIL_MUL_HI_HPP_INCLUDED diff --git a/include/time_shield/enums.hpp b/include/time_shield/enums.hpp index 43cd4b56..cb744f83 100644 --- a/include/time_shield/enums.hpp +++ b/include/time_shield/enums.hpp @@ -3,310 +3,6 @@ #ifndef TIME_SHIELD_HEADER_TIME_SHIELD_ENUMS_HPP_INCLUDED #define TIME_SHIELD_HEADER_TIME_SHIELD_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 +#include #endif // TIME_SHIELD_HEADER_TIME_SHIELD_ENUMS_HPP_INCLUDED diff --git a/include/time_shield/initialization.hpp b/include/time_shield/initialization.hpp index 3d15f650..4bbc985a 100644 --- a/include/time_shield/initialization.hpp +++ b/include/time_shield/initialization.hpp @@ -3,27 +3,6 @@ #ifndef TIME_SHIELD_HEADER_TIME_SHIELD_INITIALIZATION_HPP_INCLUDED #define TIME_SHIELD_HEADER_TIME_SHIELD_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 +#include #endif // TIME_SHIELD_HEADER_TIME_SHIELD_INITIALIZATION_HPP_INCLUDED diff --git a/include/time_shield/iso_week_conversions.hpp b/include/time_shield/iso_week_conversions.hpp index 8fa830bd..c06113fa 100644 --- a/include/time_shield/iso_week_conversions.hpp +++ b/include/time_shield/iso_week_conversions.hpp @@ -3,264 +3,6 @@ #ifndef TIME_SHIELD_HEADER_TIME_SHIELD_ISO_WEEK_CONVERSIONS_HPP_INCLUDED #define TIME_SHIELD_HEADER_TIME_SHIELD_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 "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 +#include #endif // TIME_SHIELD_HEADER_TIME_SHIELD_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..11baeef8 100644 --- a/include/time_shield/iso_week_struct.hpp +++ b/include/time_shield/iso_week_struct.hpp @@ -3,37 +3,6 @@ #ifndef TIME_SHIELD_HEADER_TIME_SHIELD_ISO_WEEK_STRUCT_HPP_INCLUDED #define TIME_SHIELD_HEADER_TIME_SHIELD_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 +#include #endif // TIME_SHIELD_HEADER_TIME_SHIELD_ISO_WEEK_STRUCT_HPP_INCLUDED diff --git a/include/time_shield/julian_conversions.hpp b/include/time_shield/julian_conversions.hpp index 62f1a2b8..94ea241e 100644 --- a/include/time_shield/julian_conversions.hpp +++ b/include/time_shield/julian_conversions.hpp @@ -3,191 +3,6 @@ #ifndef TIME_SHIELD_HEADER_TIME_SHIELD_JULIAN_CONVERSIONS_HPP_INCLUDED #define TIME_SHIELD_HEADER_TIME_SHIELD_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 "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 +#include #endif // TIME_SHIELD_HEADER_TIME_SHIELD_JULIAN_CONVERSIONS_HPP_INCLUDED diff --git a/include/time_shield/legacy_aliases.hpp b/include/time_shield/legacy_aliases.hpp index f8361110..90fe2262 100644 --- a/include/time_shield/legacy_aliases.hpp +++ b/include/time_shield/legacy_aliases.hpp @@ -3,169 +3,6 @@ #ifndef TIME_SHIELD_HEADER_TIME_SHIELD_LEGACY_ALIASES_HPP_INCLUDED #define TIME_SHIELD_HEADER_TIME_SHIELD_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 "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) +#include #endif // TIME_SHIELD_HEADER_TIME_SHIELD_LEGACY_ALIASES_HPP_INCLUDED diff --git a/include/time_shield/ntp.hpp b/include/time_shield/ntp.hpp new file mode 100644 index 00000000..45f72fea --- /dev/null +++ b/include/time_shield/ntp.hpp @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: MIT +#pragma once +#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_NTP_HPP_INCLUDED +#define TIME_SHIELD_HEADER_TIME_SHIELD_NTP_HPP_INCLUDED + +#include + +#if TIME_SHIELD_ENABLE_NTP_CLIENT +# include +# include +# include +# include +#endif + +#endif // TIME_SHIELD_HEADER_TIME_SHIELD_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..b1d69993 --- /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_TIME_SHIELD_NTP_DETAIL_NTP_CLIENT_CORE_HPP_INCLUDED +#define TIME_SHIELD_HEADER_TIME_SHIELD_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_TIME_SHIELD_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..163d1ce2 --- /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_TIME_SHIELD_NTP_DETAIL_NTP_PACKET_HPP_INCLUDED +#define TIME_SHIELD_HEADER_TIME_SHIELD_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_TIME_SHIELD_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..a703b075 --- /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_TIME_SHIELD_NTP_DETAIL_UDP_TRANSPORT_HPP_INCLUDED +#define TIME_SHIELD_HEADER_TIME_SHIELD_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_TIME_SHIELD_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..5ac948e9 --- /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_TIME_SHIELD_NTP_DETAIL_UDP_TRANSPORT_POSIX_HPP_INCLUDED +#define TIME_SHIELD_HEADER_TIME_SHIELD_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_TIME_SHIELD_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..47ea9360 --- /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_TIME_SHIELD_NTP_DETAIL_UDP_TRANSPORT_WIN_HPP_INCLUDED +#define TIME_SHIELD_HEADER_TIME_SHIELD_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_TIME_SHIELD_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..4140294e --- /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_TIME_SHIELD_NTP_DETAIL_WSA_GUARD_HPP_INCLUDED +#define TIME_SHIELD_HEADER_TIME_SHIELD_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_TIME_SHIELD_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..31a211dc --- /dev/null +++ b/include/time_shield/ntp/ntp_client.hpp @@ -0,0 +1,179 @@ +// SPDX-License-Identifier: MIT +#pragma once +#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_NTP_NTP_CLIENT_HPP_INCLUDED +#define TIME_SHIELD_HEADER_TIME_SHIELD_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_TIME_SHIELD_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..de45b559 --- /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_TIME_SHIELD_NTP_NTP_CLIENT_POOL_HPP_INCLUDED +#define TIME_SHIELD_HEADER_TIME_SHIELD_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_TIME_SHIELD_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..5ff0401e --- /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_TIME_SHIELD_NTP_NTP_CLIENT_POOL_RUNNER_HPP_INCLUDED +#define TIME_SHIELD_HEADER_TIME_SHIELD_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_TIME_SHIELD_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..ed0e8fc2 --- /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_TIME_SHIELD_NTP_NTP_TIME_SERVICE_HPP_INCLUDED +#define TIME_SHIELD_HEADER_TIME_SHIELD_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_TIME_SHIELD_NTP_NTP_TIME_SERVICE_HPP_INCLUDED diff --git a/include/time_shield/ntp_client.hpp b/include/time_shield/ntp_client.hpp index 9f8631aa..f1a9ec07 100644 --- a/include/time_shield/ntp_client.hpp +++ b/include/time_shield/ntp_client.hpp @@ -3,178 +3,6 @@ #ifndef TIME_SHIELD_HEADER_TIME_SHIELD_NTP_CLIENT_HPP_INCLUDED #define TIME_SHIELD_HEADER_TIME_SHIELD_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 "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 +#include #endif // TIME_SHIELD_HEADER_TIME_SHIELD_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..d732db73 100644 --- a/include/time_shield/ntp_client/ntp_client_core.hpp +++ b/include/time_shield/ntp_client/ntp_client_core.hpp @@ -3,85 +3,6 @@ #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 -#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 +#include #endif // TIME_SHIELD_HEADER_TIME_SHIELD_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..660597c0 100644 --- a/include/time_shield/ntp_client/ntp_packet.hpp +++ b/include/time_shield/ntp_client/ntp_packet.hpp @@ -3,164 +3,6 @@ #ifndef TIME_SHIELD_HEADER_TIME_SHIELD_NTP_CLIENT_NTP_PACKET_HPP_INCLUDED #define TIME_SHIELD_HEADER_TIME_SHIELD_NTP_CLIENT_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 +#include #endif // TIME_SHIELD_HEADER_TIME_SHIELD_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..5d6e5ba4 100644 --- a/include/time_shield/ntp_client/udp_transport.hpp +++ b/include/time_shield/ntp_client/udp_transport.hpp @@ -3,33 +3,6 @@ #ifndef TIME_SHIELD_HEADER_TIME_SHIELD_NTP_CLIENT_UDP_TRANSPORT_HPP_INCLUDED #define TIME_SHIELD_HEADER_TIME_SHIELD_NTP_CLIENT_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 +#include #endif // TIME_SHIELD_HEADER_TIME_SHIELD_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..ffe88886 100644 --- a/include/time_shield/ntp_client/udp_transport_posix.hpp +++ b/include/time_shield/ntp_client/udp_transport_posix.hpp @@ -3,93 +3,6 @@ #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 -#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 +#include #endif // TIME_SHIELD_HEADER_TIME_SHIELD_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..9fc51f69 100644 --- a/include/time_shield/ntp_client/udp_transport_win.hpp +++ b/include/time_shield/ntp_client/udp_transport_win.hpp @@ -3,93 +3,6 @@ #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 -#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 +#include #endif // TIME_SHIELD_HEADER_TIME_SHIELD_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..89c00825 100644 --- a/include/time_shield/ntp_client/wsa_guard.hpp +++ b/include/time_shield/ntp_client/wsa_guard.hpp @@ -3,67 +3,6 @@ #ifndef TIME_SHIELD_HEADER_TIME_SHIELD_NTP_CLIENT_WSA_GUARD_HPP_INCLUDED #define TIME_SHIELD_HEADER_TIME_SHIELD_NTP_CLIENT_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 +#include #endif // TIME_SHIELD_HEADER_TIME_SHIELD_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..a6c1922f 100644 --- a/include/time_shield/ntp_client_pool.hpp +++ b/include/time_shield/ntp_client_pool.hpp @@ -3,731 +3,6 @@ #ifndef TIME_SHIELD_HEADER_TIME_SHIELD_NTP_CLIENT_POOL_HPP_INCLUDED #define TIME_SHIELD_HEADER_TIME_SHIELD_NTP_CLIENT_POOL_HPP_INCLUDED -#include "config.hpp" - -#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 +#include #endif // TIME_SHIELD_HEADER_TIME_SHIELD_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..c512fe4a 100644 --- a/include/time_shield/ntp_client_pool_runner.hpp +++ b/include/time_shield/ntp_client_pool_runner.hpp @@ -3,232 +3,6 @@ #ifndef TIME_SHIELD_HEADER_TIME_SHIELD_NTP_CLIENT_POOL_RUNNER_HPP_INCLUDED #define TIME_SHIELD_HEADER_TIME_SHIELD_NTP_CLIENT_POOL_RUNNER_HPP_INCLUDED -#include "config.hpp" - -#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 +#include #endif // TIME_SHIELD_HEADER_TIME_SHIELD_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..7c992df1 100644 --- a/include/time_shield/ntp_time_service.hpp +++ b/include/time_shield/ntp_time_service.hpp @@ -3,893 +3,6 @@ #ifndef TIME_SHIELD_HEADER_TIME_SHIELD_NTP_TIME_SERVICE_HPP_INCLUDED #define TIME_SHIELD_HEADER_TIME_SHIELD_NTP_TIME_SERVICE_HPP_INCLUDED -#include "config.hpp" - -#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 +#include #endif // TIME_SHIELD_HEADER_TIME_SHIELD_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..0ee74a4e 100644 --- a/include/time_shield/ole_automation_conversions.hpp +++ b/include/time_shield/ole_automation_conversions.hpp @@ -3,157 +3,6 @@ #ifndef TIME_SHIELD_HEADER_TIME_SHIELD_OLE_AUTOMATION_CONVERSIONS_HPP_INCLUDED #define TIME_SHIELD_HEADER_TIME_SHIELD_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 "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 +#include #endif // TIME_SHIELD_HEADER_TIME_SHIELD_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..eb9ab434 --- /dev/null +++ b/include/time_shield/text.hpp @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: MIT +#pragma once +#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_TEXT_HPP_INCLUDED +#define TIME_SHIELD_HEADER_TIME_SHIELD_TEXT_HPP_INCLUDED + +#include +#include +#include +#include +#include + +#endif // TIME_SHIELD_HEADER_TIME_SHIELD_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..37449c66 --- /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_TIME_SHIELD_TEXT_TIME_FORMAT_PARSER_HPP_INCLUDED +#define TIME_SHIELD_HEADER_TIME_SHIELD_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_TIME_SHIELD_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..02bb69a8 --- /dev/null +++ b/include/time_shield/text/time_formatting.hpp @@ -0,0 +1,1006 @@ +// SPDX-License-Identifier: MIT +#pragma once +#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_TEXT_TIME_FORMATTING_HPP_INCLUDED +#define TIME_SHIELD_HEADER_TIME_SHIELD_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_TIME_SHIELD_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..7c793dd8 --- /dev/null +++ b/include/time_shield/text/time_parser.hpp @@ -0,0 +1,1765 @@ +// SPDX-License-Identifier: MIT +#pragma once +#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_TEXT_TIME_PARSER_HPP_INCLUDED +#define TIME_SHIELD_HEADER_TIME_SHIELD_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_TIME_SHIELD_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..265efef0 100644 --- a/include/time_shield/time_conversion_aliases.hpp +++ b/include/time_shield/time_conversion_aliases.hpp @@ -3,2116 +3,6 @@ #ifndef TIME_SHIELD_HEADER_TIME_SHIELD_TIME_CONVERSION_ALIASES_HPP_INCLUDED #define TIME_SHIELD_HEADER_TIME_SHIELD_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 +#include #endif // TIME_SHIELD_HEADER_TIME_SHIELD_TIME_CONVERSION_ALIASES_HPP_INCLUDED diff --git a/include/time_shield/time_conversions.hpp b/include/time_shield/time_conversions.hpp index 455daee0..f2dd3e2c 100644 --- a/include/time_shield/time_conversions.hpp +++ b/include/time_shield/time_conversions.hpp @@ -3,31 +3,7 @@ #ifndef TIME_SHIELD_HEADER_TIME_SHIELD_TIME_CONVERSIONS_HPP_INCLUDED #define TIME_SHIELD_HEADER_TIME_SHIELD_TIME_CONVERSIONS_HPP_INCLUDED -/// \file time_conversions.hpp -/// \brief Umbrella header for time conversion functions. - -#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 +#include +#include #endif // TIME_SHIELD_HEADER_TIME_SHIELD_TIME_CONVERSIONS_HPP_INCLUDED diff --git a/include/time_shield/time_format_parser.hpp b/include/time_shield/time_format_parser.hpp index cfaaab9b..f918e582 100644 --- a/include/time_shield/time_format_parser.hpp +++ b/include/time_shield/time_format_parser.hpp @@ -3,1162 +3,6 @@ #ifndef TIME_SHIELD_HEADER_TIME_SHIELD_TIME_FORMAT_PARSER_HPP_INCLUDED #define TIME_SHIELD_HEADER_TIME_SHIELD_TIME_FORMAT_PARSER_HPP_INCLUDED -/// \file time_format_parser.hpp -/// \brief Header file for fast parsing with formatter-compatible custom patterns. - -#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 +#include #endif // TIME_SHIELD_HEADER_TIME_SHIELD_TIME_FORMAT_PARSER_HPP_INCLUDED diff --git a/include/time_shield/time_formatting.hpp b/include/time_shield/time_formatting.hpp index 2c1037b1..1342ed7f 100644 --- a/include/time_shield/time_formatting.hpp +++ b/include/time_shield/time_formatting.hpp @@ -3,1007 +3,6 @@ #ifndef TIME_SHIELD_HEADER_TIME_SHIELD_TIME_FORMATTING_HPP_INCLUDED #define TIME_SHIELD_HEADER_TIME_SHIELD_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 "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 +#include #endif // TIME_SHIELD_HEADER_TIME_SHIELD_TIME_FORMATTING_HPP_INCLUDED diff --git a/include/time_shield/time_parser.hpp b/include/time_shield/time_parser.hpp index f1d875b2..4677c454 100644 --- a/include/time_shield/time_parser.hpp +++ b/include/time_shield/time_parser.hpp @@ -3,1768 +3,6 @@ #ifndef TIME_SHIELD_HEADER_TIME_SHIELD_TIME_PARSER_HPP_INCLUDED #define TIME_SHIELD_HEADER_TIME_SHIELD_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 "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); - } - -/// \} - -}; +#include #endif // TIME_SHIELD_HEADER_TIME_SHIELD_TIME_PARSER_HPP_INCLUDED diff --git a/include/time_shield/time_struct.hpp b/include/time_shield/time_struct.hpp index f4ff0de8..157ad741 100644 --- a/include/time_shield/time_struct.hpp +++ b/include/time_shield/time_struct.hpp @@ -3,38 +3,6 @@ #ifndef TIME_SHIELD_HEADER_TIME_SHIELD_TIME_STRUCT_HPP_INCLUDED #define TIME_SHIELD_HEADER_TIME_SHIELD_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 +#include #endif // TIME_SHIELD_HEADER_TIME_SHIELD_TIME_STRUCT_HPP_INCLUDED diff --git a/include/time_shield/time_unit_conversions.hpp b/include/time_shield/time_unit_conversions.hpp index 4c2bce14..2f2e3efb 100644 --- a/include/time_shield/time_unit_conversions.hpp +++ b/include/time_shield/time_unit_conversions.hpp @@ -3,485 +3,6 @@ #ifndef TIME_SHIELD_HEADER_TIME_SHIELD_TIME_UNIT_CONVERSIONS_HPP_INCLUDED #define TIME_SHIELD_HEADER_TIME_SHIELD_TIME_UNIT_CONVERSIONS_HPP_INCLUDED -/// \file time_unit_conversions.hpp -/// \brief Helper functions for unit conversions between seconds, minutes, hours, and milliseconds. - -#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 +#include #endif // TIME_SHIELD_HEADER_TIME_SHIELD_TIME_UNIT_CONVERSIONS_HPP_INCLUDED diff --git a/include/time_shield/time_utils.hpp b/include/time_shield/time_utils.hpp index 6ec4fbf9..670ee48d 100644 --- a/include/time_shield/time_utils.hpp +++ b/include/time_shield/time_utils.hpp @@ -3,344 +3,6 @@ #ifndef TIME_SHIELD_HEADER_TIME_SHIELD_TIME_UTILS_HPP_INCLUDED #define TIME_SHIELD_HEADER_TIME_SHIELD_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 -# 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 +#include #endif // TIME_SHIELD_HEADER_TIME_SHIELD_TIME_UTILS_HPP_INCLUDED diff --git a/include/time_shield/time_zone_conversions.hpp b/include/time_shield/time_zone_conversions.hpp index e43a9e6f..59eb5a36 100644 --- a/include/time_shield/time_zone_conversions.hpp +++ b/include/time_shield/time_zone_conversions.hpp @@ -3,1066 +3,6 @@ #ifndef TIME_SHIELD_HEADER_TIME_SHIELD_TIME_ZONE_CONVERSIONS_HPP_INCLUDED #define TIME_SHIELD_HEADER_TIME_SHIELD_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 "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 +#include #endif // TIME_SHIELD_HEADER_TIME_SHIELD_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..4eb7ce5b 100644 --- a/include/time_shield/time_zone_offset.hpp +++ b/include/time_shield/time_zone_offset.hpp @@ -3,75 +3,6 @@ #ifndef TIME_SHIELD_HEADER_TIME_SHIELD_TIME_ZONE_OFFSET_HPP_INCLUDED #define TIME_SHIELD_HEADER_TIME_SHIELD_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 "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 +#include #endif // TIME_SHIELD_HEADER_TIME_SHIELD_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..90b227ce 100644 --- a/include/time_shield/time_zone_offset_conversions.hpp +++ b/include/time_shield/time_zone_offset_conversions.hpp @@ -3,69 +3,6 @@ #ifndef TIME_SHIELD_HEADER_TIME_SHIELD_TIME_ZONE_OFFSET_CONVERSIONS_HPP_INCLUDED #define TIME_SHIELD_HEADER_TIME_SHIELD_TIME_ZONE_OFFSET_CONVERSIONS_HPP_INCLUDED -/// \file time_zone_offset_conversions.hpp -/// \brief Conversions between numeric offsets and TimeZoneStruct. - -#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 +#include #endif // TIME_SHIELD_HEADER_TIME_SHIELD_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..e2e342a1 100644 --- a/include/time_shield/time_zone_struct.hpp +++ b/include/time_shield/time_zone_struct.hpp @@ -3,119 +3,6 @@ #ifndef TIME_SHIELD_HEADER_TIME_SHIELD_TIME_ZONE_STRUCT_HPP_INCLUDED #define TIME_SHIELD_HEADER_TIME_SHIELD_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 +#include #endif // TIME_SHIELD_HEADER_TIME_SHIELD_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..7cb0e77c --- /dev/null +++ b/include/time_shield/timers.hpp @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: MIT +#pragma once +#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_TIMERS_HPP_INCLUDED +#define TIME_SHIELD_HEADER_TIME_SHIELD_TIMERS_HPP_INCLUDED + +#include +#include +#include +#include +#include + +#endif // TIME_SHIELD_HEADER_TIME_SHIELD_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..d1f5c6ac --- /dev/null +++ b/include/time_shield/timers/CpuTickTimer.hpp @@ -0,0 +1,137 @@ +// SPDX-License-Identifier: MIT +#pragma once +#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_TIMERS_CPUTICKTIMER_HPP_INCLUDED +#define TIME_SHIELD_HEADER_TIME_SHIELD_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_TIME_SHIELD_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..8076e538 --- /dev/null +++ b/include/time_shield/timers/DeadlineTimer.hpp @@ -0,0 +1,243 @@ +// SPDX-License-Identifier: MIT +#pragma once +#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_TIMERS_DEADLINETIMER_HPP_INCLUDED +#define TIME_SHIELD_HEADER_TIME_SHIELD_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_TIME_SHIELD_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..0a3c8124 --- /dev/null +++ b/include/time_shield/timers/ElapsedTimer.hpp @@ -0,0 +1,187 @@ +// SPDX-License-Identifier: MIT +#pragma once +#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_TIMERS_ELAPSEDTIMER_HPP_INCLUDED +#define TIME_SHIELD_HEADER_TIME_SHIELD_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_TIME_SHIELD_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..daa63c98 --- /dev/null +++ b/include/time_shield/timers/TimerScheduler.hpp @@ -0,0 +1,650 @@ +// SPDX-License-Identifier: MIT +#pragma once +#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_TIMERS_TIMERSCHEDULER_HPP_INCLUDED +#define TIME_SHIELD_HEADER_TIME_SHIELD_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_TIME_SHIELD_TIMERS_TIMERSCHEDULER_HPP_INCLUDED diff --git a/include/time_shield/timezone.hpp b/include/time_shield/timezone.hpp new file mode 100644 index 00000000..fee28eb6 --- /dev/null +++ b/include/time_shield/timezone.hpp @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT +#pragma once +#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_TIMEZONE_HPP_INCLUDED +#define TIME_SHIELD_HEADER_TIME_SHIELD_TIMEZONE_HPP_INCLUDED + +#include +#include +#include +#include +#if TIME_SHIELD_ENABLE_NTP_CLIENT +# include +#endif +#include +#include + +#endif // TIME_SHIELD_HEADER_TIME_SHIELD_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..08152572 --- /dev/null +++ b/include/time_shield/timezone/ZonedClock.hpp @@ -0,0 +1,369 @@ +// SPDX-License-Identifier: MIT +#pragma once +#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_TIMEZONE_ZONEDCLOCK_HPP_INCLUDED +#define TIME_SHIELD_HEADER_TIME_SHIELD_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_TIME_SHIELD_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..961b26f0 --- /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_TIME_SHIELD_TIMEZONE_TIME_ZONE_CONVERSIONS_HPP_INCLUDED +#define TIME_SHIELD_HEADER_TIME_SHIELD_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_TIME_SHIELD_TIMEZONE_TIME_ZONE_CONVERSIONS_HPP_INCLUDED diff --git a/include/time_shield/types.hpp b/include/time_shield/types.hpp index 06e0407a..6c6c459b 100644 --- a/include/time_shield/types.hpp +++ b/include/time_shield/types.hpp @@ -3,65 +3,6 @@ #ifndef TIME_SHIELD_HEADER_TIME_SHIELD_TYPES_HPP_INCLUDED #define TIME_SHIELD_HEADER_TIME_SHIELD_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 +#include #endif // TIME_SHIELD_HEADER_TIME_SHIELD_TYPES_HPP_INCLUDED diff --git a/include/time_shield/unix_time_conversions.hpp b/include/time_shield/unix_time_conversions.hpp index 6639596d..648b2fa3 100644 --- a/include/time_shield/unix_time_conversions.hpp +++ b/include/time_shield/unix_time_conversions.hpp @@ -3,330 +3,6 @@ #ifndef TIME_SHIELD_HEADER_TIME_SHIELD_UNIX_TIME_CONVERSIONS_HPP_INCLUDED #define TIME_SHIELD_HEADER_TIME_SHIELD_UNIX_TIME_CONVERSIONS_HPP_INCLUDED -/// \file unix_time_conversions.hpp -/// \brief Conversions related to UNIX-based time units and epochs. - -#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 +#include #endif // TIME_SHIELD_HEADER_TIME_SHIELD_UNIX_TIME_CONVERSIONS_HPP_INCLUDED diff --git a/include/time_shield/validation.hpp b/include/time_shield/validation.hpp index 532917b4..921f2e1c 100644 --- a/include/time_shield/validation.hpp +++ b/include/time_shield/validation.hpp @@ -3,395 +3,6 @@ #ifndef TIME_SHIELD_HEADER_TIME_SHIELD_VALIDATION_HPP_INCLUDED #define TIME_SHIELD_HEADER_TIME_SHIELD_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 +#include #endif // TIME_SHIELD_HEADER_TIME_SHIELD_VALIDATION_HPP_INCLUDED diff --git a/include/time_shield/workday_conversions.hpp b/include/time_shield/workday_conversions.hpp index 2d615dad..94cfb479 100644 --- a/include/time_shield/workday_conversions.hpp +++ b/include/time_shield/workday_conversions.hpp @@ -3,341 +3,6 @@ #ifndef TIME_SHIELD_HEADER_TIME_SHIELD_WORKDAY_CONVERSIONS_HPP_INCLUDED #define TIME_SHIELD_HEADER_TIME_SHIELD_WORKDAY_CONVERSIONS_HPP_INCLUDED -/// \file workday_conversions.hpp -/// \brief Helpers for computing workday-related timestamps. - -#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 +#include #endif // TIME_SHIELD_HEADER_TIME_SHIELD_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..a57cd434 --- /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/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 From fa54f257897d1fc5b0c659172461c2c676f154d8 Mon Sep 17 00:00:00 2001 From: Aster Seker Date: Fri, 11 Sep 2026 15:40:05 +0300 Subject: [PATCH 2/6] fix(headers): resolve domain include compatibility issues Restore the DateTime compatibility path without a case-insensitive filename collision, isolate astronomy legacy aliases, and protect Windows headers from min/max macros. Enforce the domain dependency allow-list and compile each public umbrella independently in the test suite. --- AGENTS.md | 2 +- CMakeLists.txt | 12 ++ README-RU.md | 2 +- README.md | 2 +- cmake/check_header_include_policy.cmake | 114 ++++++++++++------ docs/mainpage.md | 2 +- include/time_shield.hpp | 2 +- .../{datetime.hpp => DateTime.hpp} | 0 include/time_shield/astronomy.hpp | 4 + .../time_shield/astronomy/legacy_aliases.hpp | 54 +++++++++ .../conversions/legacy_aliases.hpp | 36 ------ include/time_shield/core/time_utils.hpp | 3 + include/time_shield/date_time.hpp | 8 ++ include/time_shield/legacy_aliases.hpp | 1 + include/time_shield/timezone.hpp | 2 +- include/time_shield/timezone/ZonedClock.hpp | 2 +- tests/domain_umbrellas_test.cpp | 2 +- tests/header_smoke/astronomy.cpp | 3 + tests/header_smoke/astronomy_legacy.cpp | 4 + tests/header_smoke/conversions.cpp | 3 + tests/header_smoke/core.cpp | 3 + tests/header_smoke/date_time.cpp | 3 + tests/header_smoke/ntp.cpp | 3 + tests/header_smoke/text.cpp | 3 + tests/header_smoke/timers.cpp | 3 + tests/header_smoke/timezone.cpp | 3 + 26 files changed, 195 insertions(+), 81 deletions(-) rename include/time_shield/{datetime.hpp => DateTime.hpp} (100%) create mode 100644 include/time_shield/astronomy/legacy_aliases.hpp create mode 100644 include/time_shield/date_time.hpp create mode 100644 tests/header_smoke/astronomy.cpp create mode 100644 tests/header_smoke/astronomy_legacy.cpp create mode 100644 tests/header_smoke/conversions.cpp create mode 100644 tests/header_smoke/core.cpp create mode 100644 tests/header_smoke/date_time.cpp create mode 100644 tests/header_smoke/ntp.cpp create mode 100644 tests/header_smoke/text.cpp create mode 100644 tests/header_smoke/timers.cpp create mode 100644 tests/header_smoke/timezone.cpp diff --git a/AGENTS.md b/AGENTS.md index 9c5aa735..b3f95c64 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -32,7 +32,7 @@ Additional policy: - Public C++ headers are organized by domain under `include/time_shield`. Prefer domain umbrellas (`core.hpp`, `conversions.hpp`, `text.hpp`, - `datetime.hpp`, `timezone.hpp`, `astronomy.hpp`, `timers.hpp`, and + `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 diff --git a/CMakeLists.txt b/CMakeLists.txt index 0a7168e3..bf349beb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -126,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/README-RU.md b/README-RU.md index efe82c58..622a8777 100644 --- a/README-RU.md +++ b/README-RU.md @@ -34,7 +34,7 @@ bool monday = is_workday(now); - `time_shield/core.hpp` — базовые типы, структуры, проверки и утилиты; - `time_shield/conversions.hpp` — преобразования временных и календарных значений; - `time_shield/text.hpp` — разбор и форматирование; -- `time_shield/datetime.hpp` — тип `DateTime`; +- `time_shield/date_time.hpp` — тип `DateTime`; - `time_shield/timezone.hpp` — именованные зоны и фиксированные смещения; - `time_shield/astronomy.hpp` — Julian- и лунные helper-ы; - `time_shield/timers.hpp` — таймеры и планировщик; diff --git a/README.md b/README.md index b76779b4..84d3d18e 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ 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/datetime.hpp` — the `DateTime` value type; +- `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; diff --git a/cmake/check_header_include_policy.cmake b/cmake/check_header_include_policy.cmake index 585f694f..6d791439 100644 --- a/cmake/check_header_include_policy.cmake +++ b/cmake/check_header_include_policy.cmake @@ -2,63 +2,103 @@ if(NOT DEFINED TIME_SHIELD_SOURCE_DIR) message(FATAL_ERROR "TIME_SHIELD_SOURCE_DIR is required") endif() -file(GLOB_RECURSE TIME_SHIELD_DOMAIN_HEADERS - "${TIME_SHIELD_SOURCE_DIR}/include/time_shield/*/*.hpp" -) +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) -foreach(header IN LISTS TIME_SHIELD_DOMAIN_HEADERS) - file(RELATIVE_PATH relative_header - "${TIME_SHIELD_SOURCE_DIR}/include/time_shield" - "${header}" - ) +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(domain "${CMAKE_MATCH_1}") - if(relative_header MATCHES "^(detail|ntp_client)/") + 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) + file(STRINGS "${header}" header_lines) foreach(line IN LISTS header_lines) - if(line MATCHES "#include[ \t]+[\"<][^\">]*\\.\\./") + if(line MATCHES "#include[ \\t]+[\"<][^\">]*\\.\\./") message(FATAL_ERROR "Parent include is forbidden: ${relative_header}: ${line}") endif() - if(line MATCHES "#include[ \t]+]+)>") + if(line MATCHES "#include[ \\t]+]+)>") set(target "${CMAKE_MATCH_1}") - string(REGEX MATCH "^([^/]+)/" target_domain_match "${target}") - set(target_domain "${CMAKE_MATCH_1}") - if(target MATCHES "^[^/]+\\.hpp$") - set(target_domain "${target}") + 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() - set(known_umbrella FALSE) - foreach(umbrella IN ITEMS core conversions text datetime timezone astronomy timers ntp) - if(target STREQUAL "${umbrella}.hpp") - set(known_umbrella TRUE) + + if(target_domain) + 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() - endforeach() - if(target_domain STREQUAL "${domain}.hpp" AND NOT relative_header MATCHES "(^|/)legacy_aliases\\.hpp$") - message(FATAL_ERROR - "Domain leaf must not include its own umbrella: ${relative_header}: ${line}") - elseif(target MATCHES "^[^/]+/" AND NOT target_domain STREQUAL "${domain}") - message(FATAL_ERROR - "Cross-domain leaf include is forbidden: ${relative_header}: ${line}") - elseif(NOT known_umbrella AND NOT target_domain STREQUAL "${domain}") - message(FATAL_ERROR - "Cross-domain include must use an umbrella: ${relative_header}: ${line}") - endif() - if(NOT known_umbrella AND NOT target MATCHES "^[a-z_]+/") - message(FATAL_ERROR "Invalid Time Shield include: ${relative_header}: ${line}") endif() - elseif(line MATCHES "#include[ \t]+\"([^\"]+)\"") + elseif(line MATCHES "#include[ \\t]+\"([^\"]+)\"") set(local_target "${CMAKE_MATCH_1}") - if(local_target MATCHES "^\.\./") + 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/") + 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) + 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 77041c39..8c59161e 100644 --- a/docs/mainpage.md +++ b/docs/mainpage.md @@ -66,7 +66,7 @@ All public symbols are declared inside the `time_shield` namespace. 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/datetime.hpp`, `time_shield/timezone.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. diff --git a/include/time_shield.hpp b/include/time_shield.hpp index fa37a1d2..f2a4bdde 100644 --- a/include/time_shield.hpp +++ b/include/time_shield.hpp @@ -11,7 +11,7 @@ #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/datetime.hpp" ///< Fixed-offset date-time value type. +#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 diff --git a/include/time_shield/datetime.hpp b/include/time_shield/DateTime.hpp similarity index 100% rename from include/time_shield/datetime.hpp rename to include/time_shield/DateTime.hpp diff --git a/include/time_shield/astronomy.hpp b/include/time_shield/astronomy.hpp index d4aebeb9..08a95224 100644 --- a/include/time_shield/astronomy.hpp +++ b/include/time_shield/astronomy.hpp @@ -9,4 +9,8 @@ #include #include +#if defined(TIME_SHIELD_ENABLE_LEGACY_ALIASES) +# include +#endif + #endif // TIME_SHIELD_HEADER_TIME_SHIELD_ASTRONOMY_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..6eaa89a8 --- /dev/null +++ b/include/time_shield/astronomy/legacy_aliases.hpp @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: MIT +#pragma once +#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_ASTRONOMY_LEGACY_ALIASES_HPP_INCLUDED +#define TIME_SHIELD_HEADER_TIME_SHIELD_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_TIME_SHIELD_ASTRONOMY_LEGACY_ALIASES_HPP_INCLUDED diff --git a/include/time_shield/conversions/legacy_aliases.hpp b/include/time_shield/conversions/legacy_aliases.hpp index 7e54aca7..88d471c3 100644 --- a/include/time_shield/conversions/legacy_aliases.hpp +++ b/include/time_shield/conversions/legacy_aliases.hpp @@ -10,7 +10,6 @@ /// `time_conversions.hpp` to make the aliases available. #include -#include #include @@ -128,41 +127,6 @@ namespace time_shield { 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 diff --git a/include/time_shield/core/time_utils.hpp b/include/time_shield/core/time_utils.hpp index 6915865d..a735188c 100644 --- a/include/time_shield/core/time_utils.hpp +++ b/include/time_shield/core/time_utils.hpp @@ -22,6 +22,9 @@ # ifndef WIN32_LEAN_AND_MEAN # define WIN32_LEAN_AND_MEAN # endif +# ifndef NOMINMAX +# define NOMINMAX +# endif # include #elif TIME_SHIELD_PLATFORM_UNIX # include diff --git a/include/time_shield/date_time.hpp b/include/time_shield/date_time.hpp new file mode 100644 index 00000000..490c35dc --- /dev/null +++ b/include/time_shield/date_time.hpp @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: MIT +#pragma once +#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_DATE_TIME_HPP_INCLUDED +#define TIME_SHIELD_HEADER_TIME_SHIELD_DATE_TIME_HPP_INCLUDED + +#include + +#endif // TIME_SHIELD_HEADER_TIME_SHIELD_DATE_TIME_HPP_INCLUDED diff --git a/include/time_shield/legacy_aliases.hpp b/include/time_shield/legacy_aliases.hpp index 90fe2262..1f0cb3cb 100644 --- a/include/time_shield/legacy_aliases.hpp +++ b/include/time_shield/legacy_aliases.hpp @@ -4,5 +4,6 @@ #define TIME_SHIELD_HEADER_TIME_SHIELD_LEGACY_ALIASES_HPP_INCLUDED #include +#include #endif // TIME_SHIELD_HEADER_TIME_SHIELD_LEGACY_ALIASES_HPP_INCLUDED diff --git a/include/time_shield/timezone.hpp b/include/time_shield/timezone.hpp index fee28eb6..f928440e 100644 --- a/include/time_shield/timezone.hpp +++ b/include/time_shield/timezone.hpp @@ -6,7 +6,7 @@ #include #include #include -#include +#include #if TIME_SHIELD_ENABLE_NTP_CLIENT # include #endif diff --git a/include/time_shield/timezone/ZonedClock.hpp b/include/time_shield/timezone/ZonedClock.hpp index 08152572..731b33e1 100644 --- a/include/time_shield/timezone/ZonedClock.hpp +++ b/include/time_shield/timezone/ZonedClock.hpp @@ -9,7 +9,7 @@ #include #include #include -#include +#include #include "time_zone_conversions.hpp" #if TIME_SHIELD_ENABLE_NTP_CLIENT diff --git a/tests/domain_umbrellas_test.cpp b/tests/domain_umbrellas_test.cpp index a57cd434..80c3cba2 100644 --- a/tests/domain_umbrellas_test.cpp +++ b/tests/domain_umbrellas_test.cpp @@ -1,7 +1,7 @@ #include #include #include -#include +#include #include #include #include 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; } From 635e250a4915c6e2afc9ad504f683badcd284a84 Mon Sep 17 00:00:00 2001 From: Aster Seker Date: Fri, 11 Sep 2026 15:43:35 +0300 Subject: [PATCH 3/6] test(headers): enforce cross-domain umbrella boundaries Reject cross-domain leaf includes and accidental self-umbrella includes while allowing domain umbrellas to include their own leaves. Keep compatibility and legacy forwarding headers outside the canonical dependency checks. --- cmake/check_header_include_policy.cmake | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/cmake/check_header_include_policy.cmake b/cmake/check_header_include_policy.cmake index 6d791439..3e67f47b 100644 --- a/cmake/check_header_include_policy.cmake +++ b/cmake/check_header_include_policy.cmake @@ -49,6 +49,14 @@ foreach(header IN LISTS TIME_SHIELD_CANONICAL_HEADERS) 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) @@ -91,6 +99,10 @@ foreach(umbrella IN LISTS TIME_SHIELD_DOMAIN_UMBRELLAS) 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 From 742c20ae9945fc7587012dedb7d9af20cf6afe0a Mon Sep 17 00:00:00 2001 From: Aster Seker Date: Fri, 11 Sep 2026 15:48:30 +0300 Subject: [PATCH 4/6] fix(build): set policy version for header checker Enable the IN_LIST operator used by the standalone include-policy script on GitHub Actions and other CMake script-mode invocations. --- cmake/check_header_include_policy.cmake | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cmake/check_header_include_policy.cmake b/cmake/check_header_include_policy.cmake index 3e67f47b..9cd10a01 100644 --- a/cmake/check_header_include_policy.cmake +++ b/cmake/check_header_include_policy.cmake @@ -1,3 +1,5 @@ +cmake_policy(VERSION 3.15) + if(NOT DEFINED TIME_SHIELD_SOURCE_DIR) message(FATAL_ERROR "TIME_SHIELD_SOURCE_DIR is required") endif() From 4ab12678575ac5fc41b16f7943584fc16be19d3d Mon Sep 17 00:00:00 2001 From: Aster Seker Date: Fri, 11 Sep 2026 17:02:55 +0300 Subject: [PATCH 5/6] refactor(headers): simplify include guard paths Remove the redundant time_shield path component from project header guards while preserving unique path-derived names for compatibility and domain headers. --- include/time_shield/CpuTickTimer.hpp | 6 +++--- include/time_shield/DateTime.hpp | 6 +++--- include/time_shield/DeadlineTimer.hpp | 6 +++--- include/time_shield/ElapsedTimer.hpp | 6 +++--- include/time_shield/MoonPhase.hpp | 6 +++--- include/time_shield/TimerScheduler.hpp | 6 +++--- include/time_shield/ZonedClock.hpp | 6 +++--- include/time_shield/astronomy.hpp | 6 +++--- include/time_shield/astronomy/MoonPhase.hpp | 6 +++--- include/time_shield/astronomy/astronomy_conversions.hpp | 6 +++--- include/time_shield/astronomy/julian_conversions.hpp | 6 +++--- include/time_shield/astronomy/legacy_aliases.hpp | 6 +++--- include/time_shield/astronomy_conversions.hpp | 6 +++--- include/time_shield/config.hpp | 6 +++--- include/time_shield/constants.hpp | 6 +++--- include/time_shield/conversions.hpp | 6 +++--- include/time_shield/conversions/date_conversions.hpp | 6 +++--- include/time_shield/conversions/date_time_conversions.hpp | 6 +++--- include/time_shield/conversions/detail/fast_date.hpp | 6 +++--- include/time_shield/conversions/detail/floor_math.hpp | 6 +++--- include/time_shield/conversions/detail/mul_hi.hpp | 6 +++--- include/time_shield/conversions/iso_week_conversions.hpp | 6 +++--- include/time_shield/conversions/legacy_aliases.hpp | 6 +++--- .../time_shield/conversions/ole_automation_conversions.hpp | 6 +++--- include/time_shield/conversions/time_conversion_aliases.hpp | 6 +++--- include/time_shield/conversions/time_unit_conversions.hpp | 6 +++--- include/time_shield/conversions/time_zone_offset.hpp | 6 +++--- .../conversions/time_zone_offset_conversions.hpp | 6 +++--- include/time_shield/conversions/unix_time_conversions.hpp | 6 +++--- include/time_shield/conversions/workday_conversions.hpp | 6 +++--- include/time_shield/core.hpp | 6 +++--- include/time_shield/core/config.hpp | 6 +++--- include/time_shield/core/constants.hpp | 6 +++--- include/time_shield/core/date_struct.hpp | 6 +++--- include/time_shield/core/date_time_struct.hpp | 6 +++--- include/time_shield/core/enums.hpp | 6 +++--- include/time_shield/core/initialization.hpp | 6 +++--- include/time_shield/core/iso_week_struct.hpp | 6 +++--- include/time_shield/core/time_struct.hpp | 6 +++--- include/time_shield/core/time_utils.hpp | 6 +++--- include/time_shield/core/time_zone_struct.hpp | 6 +++--- include/time_shield/core/types.hpp | 6 +++--- include/time_shield/core/validation.hpp | 6 +++--- include/time_shield/date_conversions.hpp | 6 +++--- include/time_shield/date_struct.hpp | 6 +++--- include/time_shield/date_time.hpp | 6 +++--- include/time_shield/date_time_conversions.hpp | 6 +++--- include/time_shield/date_time_struct.hpp | 6 +++--- include/time_shield/datetime/DateTime.hpp | 6 +++--- include/time_shield/detail/fast_date.hpp | 6 +++--- include/time_shield/detail/floor_math.hpp | 6 +++--- include/time_shield/detail/mul_hi.hpp | 6 +++--- include/time_shield/enums.hpp | 6 +++--- include/time_shield/initialization.hpp | 6 +++--- include/time_shield/iso_week_conversions.hpp | 6 +++--- include/time_shield/iso_week_struct.hpp | 6 +++--- include/time_shield/julian_conversions.hpp | 6 +++--- include/time_shield/legacy_aliases.hpp | 6 +++--- include/time_shield/ntp.hpp | 6 +++--- include/time_shield/ntp/detail/ntp_client_core.hpp | 6 +++--- include/time_shield/ntp/detail/ntp_packet.hpp | 6 +++--- include/time_shield/ntp/detail/udp_transport.hpp | 6 +++--- include/time_shield/ntp/detail/udp_transport_posix.hpp | 6 +++--- include/time_shield/ntp/detail/udp_transport_win.hpp | 6 +++--- include/time_shield/ntp/detail/wsa_guard.hpp | 6 +++--- include/time_shield/ntp/ntp_client.hpp | 6 +++--- include/time_shield/ntp/ntp_client_pool.hpp | 6 +++--- include/time_shield/ntp/ntp_client_pool_runner.hpp | 6 +++--- include/time_shield/ntp/ntp_time_service.hpp | 6 +++--- include/time_shield/ntp_client.hpp | 6 +++--- include/time_shield/ntp_client/ntp_client_core.hpp | 6 +++--- include/time_shield/ntp_client/ntp_packet.hpp | 6 +++--- include/time_shield/ntp_client/udp_transport.hpp | 6 +++--- include/time_shield/ntp_client/udp_transport_posix.hpp | 6 +++--- include/time_shield/ntp_client/udp_transport_win.hpp | 6 +++--- include/time_shield/ntp_client/wsa_guard.hpp | 6 +++--- include/time_shield/ntp_client_pool.hpp | 6 +++--- include/time_shield/ntp_client_pool_runner.hpp | 6 +++--- include/time_shield/ntp_time_service.hpp | 6 +++--- include/time_shield/ole_automation_conversions.hpp | 6 +++--- include/time_shield/text.hpp | 6 +++--- include/time_shield/text/time_format_parser.hpp | 6 +++--- include/time_shield/text/time_formatting.hpp | 6 +++--- include/time_shield/text/time_parser.hpp | 6 +++--- include/time_shield/time_conversion_aliases.hpp | 6 +++--- include/time_shield/time_conversions.hpp | 6 +++--- include/time_shield/time_format_parser.hpp | 6 +++--- include/time_shield/time_formatting.hpp | 6 +++--- include/time_shield/time_parser.hpp | 6 +++--- include/time_shield/time_struct.hpp | 6 +++--- include/time_shield/time_unit_conversions.hpp | 6 +++--- include/time_shield/time_utils.hpp | 6 +++--- include/time_shield/time_zone_conversions.hpp | 6 +++--- include/time_shield/time_zone_offset.hpp | 6 +++--- include/time_shield/time_zone_offset_conversions.hpp | 6 +++--- include/time_shield/time_zone_struct.hpp | 6 +++--- include/time_shield/timers.hpp | 6 +++--- include/time_shield/timers/CpuTickTimer.hpp | 6 +++--- include/time_shield/timers/DeadlineTimer.hpp | 6 +++--- include/time_shield/timers/ElapsedTimer.hpp | 6 +++--- include/time_shield/timers/TimerScheduler.hpp | 6 +++--- include/time_shield/timezone.hpp | 6 +++--- include/time_shield/timezone/ZonedClock.hpp | 6 +++--- include/time_shield/timezone/time_zone_conversions.hpp | 6 +++--- include/time_shield/types.hpp | 6 +++--- include/time_shield/unix_time_conversions.hpp | 6 +++--- include/time_shield/validation.hpp | 6 +++--- include/time_shield/workday_conversions.hpp | 6 +++--- 108 files changed, 324 insertions(+), 324 deletions(-) diff --git a/include/time_shield/CpuTickTimer.hpp b/include/time_shield/CpuTickTimer.hpp index ee7d2530..0db1361f 100644 --- a/include/time_shield/CpuTickTimer.hpp +++ b/include/time_shield/CpuTickTimer.hpp @@ -1,8 +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 #include -#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 d4158a17..8637481e 100644 --- a/include/time_shield/DateTime.hpp +++ b/include/time_shield/DateTime.hpp @@ -1,8 +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 #include -#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 7ebadf0b..1f2c6732 100644 --- a/include/time_shield/DeadlineTimer.hpp +++ b/include/time_shield/DeadlineTimer.hpp @@ -1,8 +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 #include -#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 ba4bd2f2..53147b14 100644 --- a/include/time_shield/ElapsedTimer.hpp +++ b/include/time_shield/ElapsedTimer.hpp @@ -1,8 +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 #include -#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 1e47ec27..2c42443d 100644 --- a/include/time_shield/MoonPhase.hpp +++ b/include/time_shield/MoonPhase.hpp @@ -1,8 +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 #include -#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 f68d82bd..7ae3d21c 100644 --- a/include/time_shield/TimerScheduler.hpp +++ b/include/time_shield/TimerScheduler.hpp @@ -1,8 +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 #include -#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 9c7b2efc..07f1f1a0 100644 --- a/include/time_shield/ZonedClock.hpp +++ b/include/time_shield/ZonedClock.hpp @@ -1,8 +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 #include -#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 index 08a95224..6a108e94 100644 --- a/include/time_shield/astronomy.hpp +++ b/include/time_shield/astronomy.hpp @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_ASTRONOMY_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_ASTRONOMY_HPP_INCLUDED +#ifndef TIME_SHIELD_HEADER_ASTRONOMY_HPP_INCLUDED +#define TIME_SHIELD_HEADER_ASTRONOMY_HPP_INCLUDED #include #include @@ -13,4 +13,4 @@ # include #endif -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_ASTRONOMY_HPP_INCLUDED +#endif // TIME_SHIELD_HEADER_ASTRONOMY_HPP_INCLUDED diff --git a/include/time_shield/astronomy/MoonPhase.hpp b/include/time_shield/astronomy/MoonPhase.hpp index 9a4351c8..ba6f9bec 100644 --- a/include/time_shield/astronomy/MoonPhase.hpp +++ b/include/time_shield/astronomy/MoonPhase.hpp @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_ASTRONOMY_MOONPHASE_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_ASTRONOMY_MOONPHASE_HPP_INCLUDED +#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. @@ -400,4 +400,4 @@ namespace astronomy { } // namespace time_shield -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_ASTRONOMY_MOONPHASE_HPP_INCLUDED +#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 index 1bf86da9..4347586a 100644 --- a/include/time_shield/astronomy/astronomy_conversions.hpp +++ b/include/time_shield/astronomy/astronomy_conversions.hpp @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_ASTRONOMY_ASTRONOMY_CONVERSIONS_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_ASTRONOMY_ASTRONOMY_CONVERSIONS_HPP_INCLUDED +#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. @@ -115,4 +115,4 @@ namespace time_shield { } // namespace time_shield -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_ASTRONOMY_ASTRONOMY_CONVERSIONS_HPP_INCLUDED +#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 index 77319556..65556b78 100644 --- a/include/time_shield/astronomy/julian_conversions.hpp +++ b/include/time_shield/astronomy/julian_conversions.hpp @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_ASTRONOMY_JULIAN_CONVERSIONS_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_ASTRONOMY_JULIAN_CONVERSIONS_HPP_INCLUDED +#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. @@ -187,4 +187,4 @@ namespace time_shield { } // namespace time_shield -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_ASTRONOMY_JULIAN_CONVERSIONS_HPP_INCLUDED +#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 index 6eaa89a8..5c344f63 100644 --- a/include/time_shield/astronomy/legacy_aliases.hpp +++ b/include/time_shield/astronomy/legacy_aliases.hpp @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_ASTRONOMY_LEGACY_ALIASES_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_ASTRONOMY_LEGACY_ALIASES_HPP_INCLUDED +#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. @@ -51,4 +51,4 @@ namespace time_shield { #endif // defined(TIME_SHIELD_ENABLE_LEGACY_ALIASES) -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_ASTRONOMY_LEGACY_ALIASES_HPP_INCLUDED +#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 6c5c93ed..f6c52fd7 100644 --- a/include/time_shield/astronomy_conversions.hpp +++ b/include/time_shield/astronomy_conversions.hpp @@ -1,8 +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 #include -#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 75bfca94..d5305bab 100644 --- a/include/time_shield/config.hpp +++ b/include/time_shield/config.hpp @@ -1,8 +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 #include -#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 fd2e4f1a..c181467e 100644 --- a/include/time_shield/constants.hpp +++ b/include/time_shield/constants.hpp @@ -1,8 +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 #include -#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 index cb65ea94..ac8f0413 100644 --- a/include/time_shield/conversions.hpp +++ b/include/time_shield/conversions.hpp @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_CONVERSIONS_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_CONVERSIONS_HPP_INCLUDED +#ifndef TIME_SHIELD_HEADER_CONVERSIONS_HPP_INCLUDED +#define TIME_SHIELD_HEADER_CONVERSIONS_HPP_INCLUDED #include #include @@ -19,4 +19,4 @@ # include #endif -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_CONVERSIONS_HPP_INCLUDED +#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 index 6cda2a45..379ad8aa 100644 --- a/include/time_shield/conversions/date_conversions.hpp +++ b/include/time_shield/conversions/date_conversions.hpp @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_CONVERSIONS_DATE_CONVERSIONS_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_CONVERSIONS_DATE_CONVERSIONS_HPP_INCLUDED +#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. @@ -106,4 +106,4 @@ namespace time_shield { }; // namespace time_shield -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_CONVERSIONS_DATE_CONVERSIONS_HPP_INCLUDED +#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 index 93da66e8..f1ccd8ac 100644 --- a/include/time_shield/conversions/date_time_conversions.hpp +++ b/include/time_shield/conversions/date_time_conversions.hpp @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_CONVERSIONS_DATE_TIME_CONVERSIONS_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_CONVERSIONS_DATE_TIME_CONVERSIONS_HPP_INCLUDED +#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. @@ -1193,4 +1193,4 @@ namespace time_shield { }; // namespace time_shield -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_CONVERSIONS_DATE_TIME_CONVERSIONS_HPP_INCLUDED +#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 index 29c71275..ad2507c1 100644 --- a/include/time_shield/conversions/detail/fast_date.hpp +++ b/include/time_shield/conversions/detail/fast_date.hpp @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_CONVERSIONS_DETAIL_FAST_DATE_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_CONVERSIONS_DETAIL_FAST_DATE_HPP_INCLUDED +#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. @@ -242,4 +242,4 @@ namespace detail { } // namespace detail } // namespace time_shield -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_CONVERSIONS_DETAIL_FAST_DATE_HPP_INCLUDED +#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 index c1fca356..079c737f 100644 --- a/include/time_shield/conversions/detail/floor_math.hpp +++ b/include/time_shield/conversions/detail/floor_math.hpp @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_CONVERSIONS_DETAIL_FLOOR_MATH_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_CONVERSIONS_DETAIL_FLOOR_MATH_HPP_INCLUDED +#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. @@ -24,4 +24,4 @@ namespace detail { } // namespace detail } // namespace time_shield -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_CONVERSIONS_DETAIL_FLOOR_MATH_HPP_INCLUDED +#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 index 72033193..d33c0782 100644 --- a/include/time_shield/conversions/detail/mul_hi.hpp +++ b/include/time_shield/conversions/detail/mul_hi.hpp @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_CONVERSIONS_DETAIL_MUL_HI_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_CONVERSIONS_DETAIL_MUL_HI_HPP_INCLUDED +#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. @@ -56,4 +56,4 @@ namespace detail { } // namespace detail } // namespace time_shield -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_CONVERSIONS_DETAIL_MUL_HI_HPP_INCLUDED +#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 index 148f1485..43989389 100644 --- a/include/time_shield/conversions/iso_week_conversions.hpp +++ b/include/time_shield/conversions/iso_week_conversions.hpp @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_CONVERSIONS_ISO_WEEK_CONVERSIONS_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_CONVERSIONS_ISO_WEEK_CONVERSIONS_HPP_INCLUDED +#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). @@ -260,4 +260,4 @@ namespace time_shield { }; // namespace time_shield -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_CONVERSIONS_ISO_WEEK_CONVERSIONS_HPP_INCLUDED +#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 index 88d471c3..29ffde06 100644 --- a/include/time_shield/conversions/legacy_aliases.hpp +++ b/include/time_shield/conversions/legacy_aliases.hpp @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_CONVERSIONS_LEGACY_ALIASES_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_CONVERSIONS_LEGACY_ALIASES_HPP_INCLUDED +#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. @@ -133,4 +133,4 @@ namespace time_shield { #endif // defined(TIME_SHIELD_ENABLE_LEGACY_ALIASES) -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_CONVERSIONS_LEGACY_ALIASES_HPP_INCLUDED +#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 index ef0826f6..e8513aa3 100644 --- a/include/time_shield/conversions/ole_automation_conversions.hpp +++ b/include/time_shield/conversions/ole_automation_conversions.hpp @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_CONVERSIONS_OLE_AUTOMATION_CONVERSIONS_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_CONVERSIONS_OLE_AUTOMATION_CONVERSIONS_HPP_INCLUDED +#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. @@ -154,4 +154,4 @@ namespace time_shield { } // namespace time_shield -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_CONVERSIONS_OLE_AUTOMATION_CONVERSIONS_HPP_INCLUDED +#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 index cad78fa7..d49923b3 100644 --- a/include/time_shield/conversions/time_conversion_aliases.hpp +++ b/include/time_shield/conversions/time_conversion_aliases.hpp @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_CONVERSIONS_TIME_CONVERSION_ALIASES_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_CONVERSIONS_TIME_CONVERSION_ALIASES_HPP_INCLUDED +#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. @@ -2115,4 +2115,4 @@ namespace time_shield { }; // namespace time_shield -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_CONVERSIONS_TIME_CONVERSION_ALIASES_HPP_INCLUDED +#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 index d12e6bce..9e903c0f 100644 --- a/include/time_shield/conversions/time_unit_conversions.hpp +++ b/include/time_shield/conversions/time_unit_conversions.hpp @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_CONVERSIONS_TIME_UNIT_CONVERSIONS_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_CONVERSIONS_TIME_UNIT_CONVERSIONS_HPP_INCLUDED +#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. @@ -482,4 +482,4 @@ namespace time_shield { }; // namespace time_shield -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_CONVERSIONS_TIME_UNIT_CONVERSIONS_HPP_INCLUDED +#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 index db832ae8..510da244 100644 --- a/include/time_shield/conversions/time_zone_offset.hpp +++ b/include/time_shield/conversions/time_zone_offset.hpp @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_CONVERSIONS_TIME_ZONE_OFFSET_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_CONVERSIONS_TIME_ZONE_OFFSET_HPP_INCLUDED +#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. @@ -72,4 +72,4 @@ namespace time_shield { } // namespace time_shield -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_CONVERSIONS_TIME_ZONE_OFFSET_HPP_INCLUDED +#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 index 5cb43a04..de5248e3 100644 --- a/include/time_shield/conversions/time_zone_offset_conversions.hpp +++ b/include/time_shield/conversions/time_zone_offset_conversions.hpp @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_CONVERSIONS_TIME_ZONE_OFFSET_CONVERSIONS_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_CONVERSIONS_TIME_ZONE_OFFSET_CONVERSIONS_HPP_INCLUDED +#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. @@ -65,4 +65,4 @@ namespace time_shield { }; // namespace time_shield -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_CONVERSIONS_TIME_ZONE_OFFSET_CONVERSIONS_HPP_INCLUDED +#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 index 1cc96e3e..7618e449 100644 --- a/include/time_shield/conversions/unix_time_conversions.hpp +++ b/include/time_shield/conversions/unix_time_conversions.hpp @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_CONVERSIONS_UNIX_TIME_CONVERSIONS_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_CONVERSIONS_UNIX_TIME_CONVERSIONS_HPP_INCLUDED +#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. @@ -326,4 +326,4 @@ namespace time_shield { }; // namespace time_shield -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_CONVERSIONS_UNIX_TIME_CONVERSIONS_HPP_INCLUDED +#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 index 111c5ccd..cf7b730f 100644 --- a/include/time_shield/conversions/workday_conversions.hpp +++ b/include/time_shield/conversions/workday_conversions.hpp @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_CONVERSIONS_WORKDAY_CONVERSIONS_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_CONVERSIONS_WORKDAY_CONVERSIONS_HPP_INCLUDED +#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. @@ -339,4 +339,4 @@ namespace time_shield { }; // namespace time_shield -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_CONVERSIONS_WORKDAY_CONVERSIONS_HPP_INCLUDED +#endif // TIME_SHIELD_HEADER_CONVERSIONS_WORKDAY_CONVERSIONS_HPP_INCLUDED diff --git a/include/time_shield/core.hpp b/include/time_shield/core.hpp index 56609043..7687e57f 100644 --- a/include/time_shield/core.hpp +++ b/include/time_shield/core.hpp @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_CORE_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_CORE_HPP_INCLUDED +#ifndef TIME_SHIELD_HEADER_CORE_HPP_INCLUDED +#define TIME_SHIELD_HEADER_CORE_HPP_INCLUDED #include #include @@ -16,4 +16,4 @@ #include #include -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_CORE_HPP_INCLUDED +#endif // TIME_SHIELD_HEADER_CORE_HPP_INCLUDED diff --git a/include/time_shield/core/config.hpp b/include/time_shield/core/config.hpp index 4c22f476..65bac7be 100644 --- a/include/time_shield/core/config.hpp +++ b/include/time_shield/core/config.hpp @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_CORE_CONFIG_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_CORE_CONFIG_HPP_INCLUDED +#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. @@ -116,4 +116,4 @@ #endif ///@} -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_CORE_CONFIG_HPP_INCLUDED +#endif // TIME_SHIELD_HEADER_CORE_CONFIG_HPP_INCLUDED diff --git a/include/time_shield/core/constants.hpp b/include/time_shield/core/constants.hpp index d439b3c2..8c841b34 100644 --- a/include/time_shield/core/constants.hpp +++ b/include/time_shield/core/constants.hpp @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_CORE_CONSTANTS_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_CORE_CONSTANTS_HPP_INCLUDED +#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. @@ -166,4 +166,4 @@ namespace time_shield { }; // namespace time_shield -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_CORE_CONSTANTS_HPP_INCLUDED +#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 index fa7bc34b..f0382e12 100644 --- a/include/time_shield/core/date_struct.hpp +++ b/include/time_shield/core/date_struct.hpp @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_CORE_DATE_STRUCT_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_CORE_DATE_STRUCT_HPP_INCLUDED +#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. @@ -34,4 +34,4 @@ namespace time_shield { }; // namespace time_shield -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_CORE_DATE_STRUCT_HPP_INCLUDED +#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 index ad8ad0e5..f29fda80 100644 --- a/include/time_shield/core/date_time_struct.hpp +++ b/include/time_shield/core/date_time_struct.hpp @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_CORE_DATE_TIME_STRUCT_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_CORE_DATE_TIME_STRUCT_HPP_INCLUDED +#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. @@ -55,4 +55,4 @@ namespace time_shield { }; // namespace time_shield -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_CORE_DATE_TIME_STRUCT_HPP_INCLUDED +#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 index 3c13bac6..179812d5 100644 --- a/include/time_shield/core/enums.hpp +++ b/include/time_shield/core/enums.hpp @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_CORE_ENUMS_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_CORE_ENUMS_HPP_INCLUDED +#ifndef TIME_SHIELD_HEADER_CORE_ENUMS_HPP_INCLUDED +#define TIME_SHIELD_HEADER_CORE_ENUMS_HPP_INCLUDED /// \file enums.hpp /// \ingroup time_enums @@ -309,4 +309,4 @@ namespace time_shield { }; // namespace time_shield -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_CORE_ENUMS_HPP_INCLUDED +#endif // TIME_SHIELD_HEADER_CORE_ENUMS_HPP_INCLUDED diff --git a/include/time_shield/core/initialization.hpp b/include/time_shield/core/initialization.hpp index c2e0097f..67bc0668 100644 --- a/include/time_shield/core/initialization.hpp +++ b/include/time_shield/core/initialization.hpp @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_CORE_INITIALIZATION_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_CORE_INITIALIZATION_HPP_INCLUDED +#ifndef TIME_SHIELD_HEADER_CORE_INITIALIZATION_HPP_INCLUDED +#define TIME_SHIELD_HEADER_CORE_INITIALIZATION_HPP_INCLUDED /// \file initialization.hpp /// \ingroup lib_initialization @@ -26,4 +26,4 @@ namespace time_shield { }; // namespace time_shield -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_CORE_INITIALIZATION_HPP_INCLUDED +#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 index e4e9a47e..f48ecff4 100644 --- a/include/time_shield/core/iso_week_struct.hpp +++ b/include/time_shield/core/iso_week_struct.hpp @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_CORE_ISO_WEEK_STRUCT_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_CORE_ISO_WEEK_STRUCT_HPP_INCLUDED +#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. @@ -36,4 +36,4 @@ namespace time_shield { }; // namespace time_shield -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_CORE_ISO_WEEK_STRUCT_HPP_INCLUDED +#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 index 40f61d54..7503834c 100644 --- a/include/time_shield/core/time_struct.hpp +++ b/include/time_shield/core/time_struct.hpp @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_CORE_TIME_STRUCT_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_CORE_TIME_STRUCT_HPP_INCLUDED +#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. @@ -37,4 +37,4 @@ namespace time_shield { }; // namespace time_shield -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_CORE_TIME_STRUCT_HPP_INCLUDED +#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 index a735188c..d3d9aecb 100644 --- a/include/time_shield/core/time_utils.hpp +++ b/include/time_shield/core/time_utils.hpp @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_CORE_TIME_UTILS_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_CORE_TIME_UTILS_HPP_INCLUDED +#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. @@ -346,4 +346,4 @@ namespace time_shield { }; // namespace time_shield -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_CORE_TIME_UTILS_HPP_INCLUDED +#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 index 024dbebf..a2f56b66 100644 --- a/include/time_shield/core/time_zone_struct.hpp +++ b/include/time_shield/core/time_zone_struct.hpp @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_CORE_TIME_ZONE_STRUCT_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_CORE_TIME_ZONE_STRUCT_HPP_INCLUDED +#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. @@ -118,4 +118,4 @@ namespace time_shield { }; // namespace time_shield -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_CORE_TIME_ZONE_STRUCT_HPP_INCLUDED +#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 index ffe45b6b..199a31df 100644 --- a/include/time_shield/core/types.hpp +++ b/include/time_shield/core/types.hpp @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_CORE_TYPES_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_CORE_TYPES_HPP_INCLUDED +#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. @@ -64,4 +64,4 @@ namespace time_shield { }; // namespace time_shield -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_CORE_TYPES_HPP_INCLUDED +#endif // TIME_SHIELD_HEADER_CORE_TYPES_HPP_INCLUDED diff --git a/include/time_shield/core/validation.hpp b/include/time_shield/core/validation.hpp index bd46b959..1b4c4f08 100644 --- a/include/time_shield/core/validation.hpp +++ b/include/time_shield/core/validation.hpp @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_CORE_VALIDATION_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_CORE_VALIDATION_HPP_INCLUDED +#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. @@ -394,4 +394,4 @@ namespace time_shield { } // namespace time_shield -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_CORE_VALIDATION_HPP_INCLUDED +#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 23a3fe0b..083eec5f 100644 --- a/include/time_shield/date_conversions.hpp +++ b/include/time_shield/date_conversions.hpp @@ -1,8 +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 #include -#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 9e0839ce..5cfb60e3 100644 --- a/include/time_shield/date_struct.hpp +++ b/include/time_shield/date_struct.hpp @@ -1,8 +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 #include -#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 index 490c35dc..6553287b 100644 --- a/include/time_shield/date_time.hpp +++ b/include/time_shield/date_time.hpp @@ -1,8 +1,8 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_DATE_TIME_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_DATE_TIME_HPP_INCLUDED +#ifndef TIME_SHIELD_HEADER_DATE_TIME_HPP_INCLUDED +#define TIME_SHIELD_HEADER_DATE_TIME_HPP_INCLUDED #include -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_DATE_TIME_HPP_INCLUDED +#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 5e0dc7af..72451f9e 100644 --- a/include/time_shield/date_time_conversions.hpp +++ b/include/time_shield/date_time_conversions.hpp @@ -1,8 +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 #include -#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 753dbcdf..fbb1398e 100644 --- a/include/time_shield/date_time_struct.hpp +++ b/include/time_shield/date_time_struct.hpp @@ -1,8 +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 #include -#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 index 791a240c..1dccf0e1 100644 --- a/include/time_shield/datetime/DateTime.hpp +++ b/include/time_shield/datetime/DateTime.hpp @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_DATETIME_DATETIME_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_DATETIME_DATETIME_HPP_INCLUDED +#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. @@ -700,4 +700,4 @@ namespace time_shield { } // namespace time_shield -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_DATETIME_DATETIME_HPP_INCLUDED +#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 c117baf0..7526dd5e 100644 --- a/include/time_shield/detail/fast_date.hpp +++ b/include/time_shield/detail/fast_date.hpp @@ -1,8 +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 #include -#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 27569508..55e88647 100644 --- a/include/time_shield/detail/floor_math.hpp +++ b/include/time_shield/detail/floor_math.hpp @@ -1,8 +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 #include -#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 eb66fe77..07873e6a 100644 --- a/include/time_shield/detail/mul_hi.hpp +++ b/include/time_shield/detail/mul_hi.hpp @@ -1,8 +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 #include -#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 cb744f83..29a7536b 100644 --- a/include/time_shield/enums.hpp +++ b/include/time_shield/enums.hpp @@ -1,8 +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 #include -#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 4bbc985a..bcb58943 100644 --- a/include/time_shield/initialization.hpp +++ b/include/time_shield/initialization.hpp @@ -1,8 +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 #include -#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 c06113fa..0b1f083d 100644 --- a/include/time_shield/iso_week_conversions.hpp +++ b/include/time_shield/iso_week_conversions.hpp @@ -1,8 +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 #include -#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 11baeef8..46267c09 100644 --- a/include/time_shield/iso_week_struct.hpp +++ b/include/time_shield/iso_week_struct.hpp @@ -1,8 +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 #include -#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 94ea241e..d261bb86 100644 --- a/include/time_shield/julian_conversions.hpp +++ b/include/time_shield/julian_conversions.hpp @@ -1,8 +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 #include -#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 1f0cb3cb..447d7312 100644 --- a/include/time_shield/legacy_aliases.hpp +++ b/include/time_shield/legacy_aliases.hpp @@ -1,9 +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 #include #include -#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 index 45f72fea..9677b423 100644 --- a/include/time_shield/ntp.hpp +++ b/include/time_shield/ntp.hpp @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_NTP_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_NTP_HPP_INCLUDED +#ifndef TIME_SHIELD_HEADER_NTP_HPP_INCLUDED +#define TIME_SHIELD_HEADER_NTP_HPP_INCLUDED #include @@ -12,4 +12,4 @@ # include #endif -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_NTP_HPP_INCLUDED +#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 index b1d69993..547566a6 100644 --- a/include/time_shield/ntp/detail/ntp_client_core.hpp +++ b/include/time_shield/ntp/detail/ntp_client_core.hpp @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_NTP_DETAIL_NTP_CLIENT_CORE_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_NTP_DETAIL_NTP_CLIENT_CORE_HPP_INCLUDED +#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" @@ -84,4 +84,4 @@ namespace detail { } // namespace detail } // namespace time_shield -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_NTP_DETAIL_NTP_CLIENT_CORE_HPP_INCLUDED +#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 index 163d1ce2..fc69c5f8 100644 --- a/include/time_shield/ntp/detail/ntp_packet.hpp +++ b/include/time_shield/ntp/detail/ntp_packet.hpp @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_NTP_DETAIL_NTP_PACKET_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_NTP_DETAIL_NTP_PACKET_HPP_INCLUDED +#ifndef TIME_SHIELD_HEADER_NTP_DETAIL_NTP_PACKET_HPP_INCLUDED +#define TIME_SHIELD_HEADER_NTP_DETAIL_NTP_PACKET_HPP_INCLUDED #include #include @@ -163,4 +163,4 @@ namespace detail { } // namespace detail } // namespace time_shield -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_NTP_DETAIL_NTP_PACKET_HPP_INCLUDED +#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 index a703b075..fbb6792e 100644 --- a/include/time_shield/ntp/detail/udp_transport.hpp +++ b/include/time_shield/ntp/detail/udp_transport.hpp @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_NTP_DETAIL_UDP_TRANSPORT_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_NTP_DETAIL_UDP_TRANSPORT_HPP_INCLUDED +#ifndef TIME_SHIELD_HEADER_NTP_DETAIL_UDP_TRANSPORT_HPP_INCLUDED +#define TIME_SHIELD_HEADER_NTP_DETAIL_UDP_TRANSPORT_HPP_INCLUDED #include #include @@ -32,4 +32,4 @@ namespace detail { } // namespace detail } // namespace time_shield -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_NTP_DETAIL_UDP_TRANSPORT_HPP_INCLUDED +#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 index 5ac948e9..b3548680 100644 --- a/include/time_shield/ntp/detail/udp_transport_posix.hpp +++ b/include/time_shield/ntp/detail/udp_transport_posix.hpp @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_NTP_DETAIL_UDP_TRANSPORT_POSIX_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_NTP_DETAIL_UDP_TRANSPORT_POSIX_HPP_INCLUDED +#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 @@ -92,4 +92,4 @@ namespace detail { #endif // _TIME_SHIELD_PLATFORM_UNIX -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_NTP_DETAIL_UDP_TRANSPORT_POSIX_HPP_INCLUDED +#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 index 47ea9360..dac0ad89 100644 --- a/include/time_shield/ntp/detail/udp_transport_win.hpp +++ b/include/time_shield/ntp/detail/udp_transport_win.hpp @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_NTP_DETAIL_UDP_TRANSPORT_WIN_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_NTP_DETAIL_UDP_TRANSPORT_WIN_HPP_INCLUDED +#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 @@ -92,4 +92,4 @@ namespace detail { #endif // _TIME_SHIELD_PLATFORM_WINDOWS -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_NTP_DETAIL_UDP_TRANSPORT_WIN_HPP_INCLUDED +#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 index 4140294e..54b19029 100644 --- a/include/time_shield/ntp/detail/wsa_guard.hpp +++ b/include/time_shield/ntp/detail/wsa_guard.hpp @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_NTP_DETAIL_WSA_GUARD_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_NTP_DETAIL_WSA_GUARD_HPP_INCLUDED +#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. @@ -66,4 +66,4 @@ namespace time_shield { } // namespace time_shield -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_NTP_DETAIL_WSA_GUARD_HPP_INCLUDED +#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 index 31a211dc..d1c893b5 100644 --- a/include/time_shield/ntp/ntp_client.hpp +++ b/include/time_shield/ntp/ntp_client.hpp @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_NTP_NTP_CLIENT_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_NTP_NTP_CLIENT_HPP_INCLUDED +#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. @@ -176,4 +176,4 @@ namespace time_shield { #endif // TIME_SHIELD_ENABLE_NTP_CLIENT -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_NTP_NTP_CLIENT_HPP_INCLUDED +#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 index de45b559..c8925811 100644 --- a/include/time_shield/ntp/ntp_client_pool.hpp +++ b/include/time_shield/ntp/ntp_client_pool.hpp @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_NTP_NTP_CLIENT_POOL_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_NTP_NTP_CLIENT_POOL_HPP_INCLUDED +#ifndef TIME_SHIELD_HEADER_NTP_NTP_CLIENT_POOL_HPP_INCLUDED +#define TIME_SHIELD_HEADER_NTP_NTP_CLIENT_POOL_HPP_INCLUDED #include @@ -729,4 +729,4 @@ namespace time_shield { #endif // TIME_SHIELD_ENABLE_NTP_CLIENT -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_NTP_NTP_CLIENT_POOL_HPP_INCLUDED +#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 index 5ff0401e..f45196a7 100644 --- a/include/time_shield/ntp/ntp_client_pool_runner.hpp +++ b/include/time_shield/ntp/ntp_client_pool_runner.hpp @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_NTP_NTP_CLIENT_POOL_RUNNER_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_NTP_NTP_CLIENT_POOL_RUNNER_HPP_INCLUDED +#ifndef TIME_SHIELD_HEADER_NTP_NTP_CLIENT_POOL_RUNNER_HPP_INCLUDED +#define TIME_SHIELD_HEADER_NTP_NTP_CLIENT_POOL_RUNNER_HPP_INCLUDED #include @@ -230,4 +230,4 @@ namespace time_shield { #endif // _TIME_SHIELD_ENABLE_NTP_CLIENT -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_NTP_NTP_CLIENT_POOL_RUNNER_HPP_INCLUDED +#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 index ed0e8fc2..e1e06e0b 100644 --- a/include/time_shield/ntp/ntp_time_service.hpp +++ b/include/time_shield/ntp/ntp_time_service.hpp @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_NTP_NTP_TIME_SERVICE_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_NTP_NTP_TIME_SERVICE_HPP_INCLUDED +#ifndef TIME_SHIELD_HEADER_NTP_NTP_TIME_SERVICE_HPP_INCLUDED +#define TIME_SHIELD_HEADER_NTP_NTP_TIME_SERVICE_HPP_INCLUDED #include @@ -891,4 +891,4 @@ namespace time_shield { #endif // _TIME_SHIELD_ENABLE_NTP_CLIENT -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_NTP_NTP_TIME_SERVICE_HPP_INCLUDED +#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 f1a9ec07..540e5f67 100644 --- a/include/time_shield/ntp_client.hpp +++ b/include/time_shield/ntp_client.hpp @@ -1,8 +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 #include -#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 d732db73..2506cc2c 100644 --- a/include/time_shield/ntp_client/ntp_client_core.hpp +++ b/include/time_shield/ntp_client/ntp_client_core.hpp @@ -1,8 +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 -#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 660597c0..48f5b9c9 100644 --- a/include/time_shield/ntp_client/ntp_packet.hpp +++ b/include/time_shield/ntp_client/ntp_packet.hpp @@ -1,8 +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 -#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 5d6e5ba4..7bf22918 100644 --- a/include/time_shield/ntp_client/udp_transport.hpp +++ b/include/time_shield/ntp_client/udp_transport.hpp @@ -1,8 +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 -#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 ffe88886..39855bee 100644 --- a/include/time_shield/ntp_client/udp_transport_posix.hpp +++ b/include/time_shield/ntp_client/udp_transport_posix.hpp @@ -1,8 +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 #include -#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 9fc51f69..68739995 100644 --- a/include/time_shield/ntp_client/udp_transport_win.hpp +++ b/include/time_shield/ntp_client/udp_transport_win.hpp @@ -1,8 +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 #include -#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 89c00825..1d16dd2c 100644 --- a/include/time_shield/ntp_client/wsa_guard.hpp +++ b/include/time_shield/ntp_client/wsa_guard.hpp @@ -1,8 +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 #include -#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 a6c1922f..adfcb64d 100644 --- a/include/time_shield/ntp_client_pool.hpp +++ b/include/time_shield/ntp_client_pool.hpp @@ -1,8 +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 -#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 c512fe4a..9546b0db 100644 --- a/include/time_shield/ntp_client_pool_runner.hpp +++ b/include/time_shield/ntp_client_pool_runner.hpp @@ -1,8 +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 -#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 7c992df1..6bc02ede 100644 --- a/include/time_shield/ntp_time_service.hpp +++ b/include/time_shield/ntp_time_service.hpp @@ -1,8 +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 -#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 0ee74a4e..78ff4338 100644 --- a/include/time_shield/ole_automation_conversions.hpp +++ b/include/time_shield/ole_automation_conversions.hpp @@ -1,8 +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 #include -#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 index eb9ab434..c1e494b3 100644 --- a/include/time_shield/text.hpp +++ b/include/time_shield/text.hpp @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_TEXT_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_TEXT_HPP_INCLUDED +#ifndef TIME_SHIELD_HEADER_TEXT_HPP_INCLUDED +#define TIME_SHIELD_HEADER_TEXT_HPP_INCLUDED #include #include @@ -9,4 +9,4 @@ #include #include -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_TEXT_HPP_INCLUDED +#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 index 37449c66..fc4278fb 100644 --- a/include/time_shield/text/time_format_parser.hpp +++ b/include/time_shield/text/time_format_parser.hpp @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_TEXT_TIME_FORMAT_PARSER_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_TEXT_TIME_FORMAT_PARSER_HPP_INCLUDED +#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. @@ -1155,4 +1155,4 @@ namespace time_shield { } // namespace time_shield -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_TEXT_TIME_FORMAT_PARSER_HPP_INCLUDED +#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 index 02bb69a8..196cc40d 100644 --- a/include/time_shield/text/time_formatting.hpp +++ b/include/time_shield/text/time_formatting.hpp @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_TEXT_TIME_FORMATTING_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_TEXT_TIME_FORMATTING_HPP_INCLUDED +#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. @@ -1003,4 +1003,4 @@ namespace time_shield { }; // namespace time_shield -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_TEXT_TIME_FORMATTING_HPP_INCLUDED +#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 index 7c793dd8..c64ac2c0 100644 --- a/include/time_shield/text/time_parser.hpp +++ b/include/time_shield/text/time_parser.hpp @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_TEXT_TIME_PARSER_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_TEXT_TIME_PARSER_HPP_INCLUDED +#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. @@ -1762,4 +1762,4 @@ namespace time_shield { }; -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_TEXT_TIME_PARSER_HPP_INCLUDED +#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 265efef0..826fa1a8 100644 --- a/include/time_shield/time_conversion_aliases.hpp +++ b/include/time_shield/time_conversion_aliases.hpp @@ -1,8 +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 #include -#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 f2dd3e2c..0c7b2d6b 100644 --- a/include/time_shield/time_conversions.hpp +++ b/include/time_shield/time_conversions.hpp @@ -1,9 +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 #include #include -#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 f918e582..c0746811 100644 --- a/include/time_shield/time_format_parser.hpp +++ b/include/time_shield/time_format_parser.hpp @@ -1,8 +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 #include -#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 1342ed7f..8a787716 100644 --- a/include/time_shield/time_formatting.hpp +++ b/include/time_shield/time_formatting.hpp @@ -1,8 +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 #include -#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 4677c454..33e671e6 100644 --- a/include/time_shield/time_parser.hpp +++ b/include/time_shield/time_parser.hpp @@ -1,8 +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 #include -#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 157ad741..d58525aa 100644 --- a/include/time_shield/time_struct.hpp +++ b/include/time_shield/time_struct.hpp @@ -1,8 +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 #include -#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 2f2e3efb..7d16c215 100644 --- a/include/time_shield/time_unit_conversions.hpp +++ b/include/time_shield/time_unit_conversions.hpp @@ -1,8 +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 #include -#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 670ee48d..7c3e2c6c 100644 --- a/include/time_shield/time_utils.hpp +++ b/include/time_shield/time_utils.hpp @@ -1,8 +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 #include -#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 59eb5a36..9694da8a 100644 --- a/include/time_shield/time_zone_conversions.hpp +++ b/include/time_shield/time_zone_conversions.hpp @@ -1,8 +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 #include -#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 4eb7ce5b..db627c38 100644 --- a/include/time_shield/time_zone_offset.hpp +++ b/include/time_shield/time_zone_offset.hpp @@ -1,8 +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 #include -#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 90b227ce..d067c9e8 100644 --- a/include/time_shield/time_zone_offset_conversions.hpp +++ b/include/time_shield/time_zone_offset_conversions.hpp @@ -1,8 +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 #include -#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 e2e342a1..8ec47967 100644 --- a/include/time_shield/time_zone_struct.hpp +++ b/include/time_shield/time_zone_struct.hpp @@ -1,8 +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 #include -#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 index 7cb0e77c..1f712056 100644 --- a/include/time_shield/timers.hpp +++ b/include/time_shield/timers.hpp @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_TIMERS_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_TIMERS_HPP_INCLUDED +#ifndef TIME_SHIELD_HEADER_TIMERS_HPP_INCLUDED +#define TIME_SHIELD_HEADER_TIMERS_HPP_INCLUDED #include #include @@ -9,4 +9,4 @@ #include #include -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_TIMERS_HPP_INCLUDED +#endif // TIME_SHIELD_HEADER_TIMERS_HPP_INCLUDED diff --git a/include/time_shield/timers/CpuTickTimer.hpp b/include/time_shield/timers/CpuTickTimer.hpp index d1f5c6ac..8e3d47b6 100644 --- a/include/time_shield/timers/CpuTickTimer.hpp +++ b/include/time_shield/timers/CpuTickTimer.hpp @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_TIMERS_CPUTICKTIMER_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_TIMERS_CPUTICKTIMER_HPP_INCLUDED +#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(). @@ -134,4 +134,4 @@ namespace time_shield { } // namespace time_shield -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_TIMERS_CPUTICKTIMER_HPP_INCLUDED +#endif // TIME_SHIELD_HEADER_TIMERS_CPUTICKTIMER_HPP_INCLUDED diff --git a/include/time_shield/timers/DeadlineTimer.hpp b/include/time_shield/timers/DeadlineTimer.hpp index 8076e538..5e645395 100644 --- a/include/time_shield/timers/DeadlineTimer.hpp +++ b/include/time_shield/timers/DeadlineTimer.hpp @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_TIMERS_DEADLINETIMER_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_TIMERS_DEADLINETIMER_HPP_INCLUDED +#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. @@ -240,4 +240,4 @@ namespace time_shield { } // namespace time_shield -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_TIMERS_DEADLINETIMER_HPP_INCLUDED +#endif // TIME_SHIELD_HEADER_TIMERS_DEADLINETIMER_HPP_INCLUDED diff --git a/include/time_shield/timers/ElapsedTimer.hpp b/include/time_shield/timers/ElapsedTimer.hpp index 0a3c8124..fad7e4e6 100644 --- a/include/time_shield/timers/ElapsedTimer.hpp +++ b/include/time_shield/timers/ElapsedTimer.hpp @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_TIMERS_ELAPSEDTIMER_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_TIMERS_ELAPSEDTIMER_HPP_INCLUDED +#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. @@ -184,4 +184,4 @@ namespace time_shield { } // namespace time_shield -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_TIMERS_ELAPSEDTIMER_HPP_INCLUDED +#endif // TIME_SHIELD_HEADER_TIMERS_ELAPSEDTIMER_HPP_INCLUDED diff --git a/include/time_shield/timers/TimerScheduler.hpp b/include/time_shield/timers/TimerScheduler.hpp index daa63c98..f726f350 100644 --- a/include/time_shield/timers/TimerScheduler.hpp +++ b/include/time_shield/timers/TimerScheduler.hpp @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_TIMERS_TIMERSCHEDULER_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_TIMERS_TIMERSCHEDULER_HPP_INCLUDED +#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. @@ -647,4 +647,4 @@ namespace time_shield { } // namespace time_shield -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_TIMERS_TIMERSCHEDULER_HPP_INCLUDED +#endif // TIME_SHIELD_HEADER_TIMERS_TIMERSCHEDULER_HPP_INCLUDED diff --git a/include/time_shield/timezone.hpp b/include/time_shield/timezone.hpp index f928440e..1cd48c9c 100644 --- a/include/time_shield/timezone.hpp +++ b/include/time_shield/timezone.hpp @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_TIMEZONE_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_TIMEZONE_HPP_INCLUDED +#ifndef TIME_SHIELD_HEADER_TIMEZONE_HPP_INCLUDED +#define TIME_SHIELD_HEADER_TIMEZONE_HPP_INCLUDED #include #include @@ -13,4 +13,4 @@ #include #include -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_TIMEZONE_HPP_INCLUDED +#endif // TIME_SHIELD_HEADER_TIMEZONE_HPP_INCLUDED diff --git a/include/time_shield/timezone/ZonedClock.hpp b/include/time_shield/timezone/ZonedClock.hpp index 731b33e1..783b645e 100644 --- a/include/time_shield/timezone/ZonedClock.hpp +++ b/include/time_shield/timezone/ZonedClock.hpp @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_TIMEZONE_ZONEDCLOCK_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_TIMEZONE_ZONEDCLOCK_HPP_INCLUDED +#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. @@ -366,4 +366,4 @@ namespace time_shield { } // namespace time_shield -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_TIMEZONE_ZONEDCLOCK_HPP_INCLUDED +#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 index 961b26f0..b9879ac0 100644 --- a/include/time_shield/timezone/time_zone_conversions.hpp +++ b/include/time_shield/timezone/time_zone_conversions.hpp @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT #pragma once -#ifndef TIME_SHIELD_HEADER_TIME_SHIELD_TIMEZONE_TIME_ZONE_CONVERSIONS_HPP_INCLUDED -#define TIME_SHIELD_HEADER_TIME_SHIELD_TIMEZONE_TIME_ZONE_CONVERSIONS_HPP_INCLUDED +#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. @@ -1063,4 +1063,4 @@ namespace time_shield { } // namespace time_shield -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_TIMEZONE_TIME_ZONE_CONVERSIONS_HPP_INCLUDED +#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 6c6c459b..eb2f4631 100644 --- a/include/time_shield/types.hpp +++ b/include/time_shield/types.hpp @@ -1,8 +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 #include -#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 648b2fa3..a0aa8158 100644 --- a/include/time_shield/unix_time_conversions.hpp +++ b/include/time_shield/unix_time_conversions.hpp @@ -1,8 +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 #include -#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 921f2e1c..f4c18988 100644 --- a/include/time_shield/validation.hpp +++ b/include/time_shield/validation.hpp @@ -1,8 +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 #include -#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 94cfb479..32c01b41 100644 --- a/include/time_shield/workday_conversions.hpp +++ b/include/time_shield/workday_conversions.hpp @@ -1,8 +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 #include -#endif // TIME_SHIELD_HEADER_TIME_SHIELD_WORKDAY_CONVERSIONS_HPP_INCLUDED +#endif // TIME_SHIELD_HEADER_WORKDAY_CONVERSIONS_HPP_INCLUDED From a6c19eca7ca21b41eeaee66929b4efe701c5ebbc Mon Sep 17 00:00:00 2001 From: Aster Seker Date: Fri, 11 Sep 2026 17:04:32 +0300 Subject: [PATCH 6/6] refactor(mql5): simplify include guard paths Remove the redundant time_shield path component from MQL5 compatibility header guards while preserving unique project-derived identifiers. --- MQL5/Include/time_shield/DateTime.mqh | 6 +++--- MQL5/Include/time_shield/constants.mqh | 6 +++--- MQL5/Include/time_shield/date_struct.mqh | 6 +++--- MQL5/Include/time_shield/date_time_struct.mqh | 6 +++--- MQL5/Include/time_shield/enums.mqh | 6 +++--- MQL5/Include/time_shield/initialization.mqh | 6 +++--- MQL5/Include/time_shield/time_conversions.mqh | 6 +++--- MQL5/Include/time_shield/time_formatting.mqh | 6 +++--- MQL5/Include/time_shield/time_parser.mqh | 6 +++--- MQL5/Include/time_shield/time_struct.mqh | 6 +++--- MQL5/Include/time_shield/time_utils.mqh | 6 +++--- MQL5/Include/time_shield/time_zone_conversions.mqh | 6 +++--- MQL5/Include/time_shield/time_zone_struct.mqh | 6 +++--- MQL5/Include/time_shield/validation.mqh | 6 +++--- 14 files changed, 42 insertions(+), 42 deletions(-) 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