Skip to content

NewYaroslav/time-shield-cpp

Repository files navigation

Time Shield

Logo

MIT License Platform C++ Standard CI Windows CI Linux CI macOS

Time Shield is a header-only C++ library for working with time. It provides functions for converting time values, formatting dates and many utilities for working with timestamps.

Named after Homura Akemi's "time shield".

See the Russian version for documentation in Russian.

Quick start

#include <time_shield.hpp>

using namespace time_shield;

ts_t now = ts();
std::string iso = to_iso8601(now);
ts_t gmt = cet_to_gmt(now);
bool monday = is_workday(now);

Use #include <time_shield.hpp> for the full API, or include specific headers for a minimal build.

Why Time Shield?

Time Shield was created as a practical tool for handling time in C++ with a focus on application and engineering tasks. Unlike the standard std::chrono or more academic solutions like HowardHinnant/date, the library:

  • uses simple types (int64_t, double) to represent time (ts_t, fts_t) making them easy to log, serialize and pass through JSON, RPC and databases; no overloaded std::chrono classes;
  • supports multiple time representations: Unix time, fractional seconds, milli- and microseconds, OLE Automation (Excel), Julian dates;
  • includes utilities for rounding, formatting, ISO 8601 parsing, working with parts of a timestamp and calculating period boundaries;
  • has an extensible architecture—new formats (Julian, OLE, UTC offset) can be added as separate types and modules;
  • works even in restricted environments such as MQL5/MetaTrader—no exceptions or dynamic memory are required for the core API; optional helpers can use std::string or throw and can be disabled in restricted builds;
  • ships as header-only—a single include without build steps or external dependencies;
  • uses only standard STL headers and system APIs; platform-specific modules (e.g., the socket-based NtpClient) are isolated and do not hinder cross-platform builds.

Features

  • Date & calendar helpers—validation, weekdays/weekends/workdays (including ISO 8601 strings).
  • Time formatting—converts timestamps to strings with standard or custom templates.
  • Conversions—translates between second, millisecond and floating time representations, DateTimeStruct, OLE Automation dates and time zones.
  • Fast date conversions—some timestamp-to-calendar helpers use a fast algorithm inspired by https://www.benjoffe.com/fast-date-64 and implemented from scratch.
  • DateTime value type—fixed-offset wrapper that stores UTC milliseconds, parses/prints ISO 8601, exposes local/UTC components, and provides arithmetic helpers.
  • ISO week dates—conversion helpers, formatting, and parsing for ISO 8601 week-numbering years.
  • Astronomy utilities—computes Julian Date/MJD/JDN values and estimates lunar phase/age from Unix timestamps.
  • Utilities—fetches current timestamps, computes start/end of periods and works with fractions of a second.
  • Time zone conversion—functions for European, US, and selected Asia/EMEA trading zones plus generic zone-to-zone conversion.
  • NTP client and pool—single-client queries plus a configurable pool/runner/service pipeline with offline testing hooks (Windows and Unix).
  • MQL5 support—adapted headers in the MQL5 directory allow using the library in MetaTrader.
  • Compatible with C++11C++17.

Configuration

Compile-time flags in time_shield/config.hpp control optional parts of the library and report platform capabilities:

  • TIME_SHIELD_PLATFORM_WINDOWS / TIME_SHIELD_PLATFORM_UNIX — detected target platform.
  • TIME_SHIELD_HAS_WINSOCK — set when WinSock APIs are available.
  • TIME_SHIELD_ENABLE_NTP_CLIENT — enables the optional NtpClient module (defaults to 1 on supported platforms).

All public headers place their declarations inside the time_shield namespace. Use time_shield:: or using namespace time_shield; to access the API.

Some functions depend on platform APIs and may be limited (for example, obtaining realtime via QueryPerformanceCounter on Windows).

API Invariants

  • ts_t — Unix time in seconds (signed 64-bit). Represents whole seconds.
  • ts_ms_t / ts_us_t — Unix time in milli/microseconds (signed 64-bit).
  • monotonic_sec() / monotonic_ms() / monotonic_us() — monotonic process-local counters for intervals and deadlines, not UTC timestamps.
  • fts_t — Unix time in seconds as double. Fractional precision depends on magnitude; near the modern epoch it typically preserves microseconds, while very large |ts| values can lose lower bits.
  • year_t — signed 64-bit year.
  • dse_t / unix_day_t / unixday_t — count of days since 1970-01-01. The signedness of the type determines correctness for dates before the epoch.
  • ISO 8601 utilities use the proleptic Gregorian calendar and do not account for leap seconds.
  • Core conversions and “hot” functions aim for noexcept and no dynamic allocations; string/parsing routines and some high-level helpers may allocate and/or throw (as documented per function).

Versioning

Time Shield follows Semantic Versioning. Patch releases contain only backward-compatible fixes. Minor versions add backward-compatible features. Major versions may include breaking changes. The public API comprises headers under include/time_shield.

Installation

Install and find_package

After installing the library (e.g., with cmake --install), consume it in a project with CMake:

cmake_minimum_required(VERSION 3.15)
project(app LANGUAGES CXX)

find_package(TimeShield CONFIG REQUIRED)

add_executable(app main.cpp)
target_link_libraries(app PRIVATE time_shield::time_shield)

Git submodule with add_subdirectory

Vendor the library as a submodule:

git submodule add https://github.com/NewYaroslav/time-shield-cpp external/time-shield-cpp

Then include it:

add_subdirectory(external/time-shield-cpp)

add_executable(app main.cpp)
target_link_libraries(app PRIVATE time_shield::time_shield)

vcpkg overlay

Install via a local overlay port:

vcpkg install time-shield-cpp --overlay-ports=./vcpkg-overlay/ports

Use the vcpkg toolchain when configuring CMake:

cmake -B build -S . -DCMAKE_TOOLCHAIN_FILE=/path/to/vcpkg/scripts/buildsystems/vcpkg.cmake

The port is intended to be upstreamed to microsoft/vcpkg.

Examples can be built with the provided scripts:

  • build-examples.bat for Windows;
  • build_examples.sh for Linux/macOS;
  • build-cb.bat to generate a Code::Blocks project.

Use install_mql5.bat to install the MQL5 files.

Integration Notes

  • time_shield::time_shield is a header-only target and can be linked from multiple static libraries in the same program.
  • NtpTimeService is header-only and supports C++11/C++14/C++17.
  • On Windows with TIME_SHIELD_ENABLE_NTP_CLIENT=ON, the exported CMake target propagates the required socket system library transitively.
  • For non-CMake or manual integration, link the platform socket library yourself when the NTP client is enabled.

See docs/library-integration-guidelines.md for the full integration contract.

Tested Platforms

Platform Compilers C++ Standards
Windows MSVC, ClangCL 11, 14, 17
Linux GCC, Clang 11, 14, 17
macOS Apple Clang 11, 14, 17

Usage Examples

Below are small examples from different sections of the library.

Getting and converting time

#include <time_shield.hpp>

using namespace time_shield;

ts_t now = ts();                 // seconds since epoch
fts_t now_f = fts();             // time in seconds with fraction
int ms_part = ms_of_sec(now_f);  // millisecond part
ts_t mono_sec = monotonic_sec(); // monotonic process-local seconds
ts_ms_t mono = monotonic_ms();   // monotonic process-local milliseconds

Use now() / ts_ms() / ts_us() for wall-clock timestamps. Use monotonic_sec() / monotonic_ms() / monotonic_us() for elapsed-time measurement, intervals, and timeout logic.

Date formatting

#include <time_shield.hpp>

std::string iso = to_iso8601(now);          // 2024-06-21T12:00:00
std::string local = to_iso8601(now, 2 * SEC_PER_HOUR + 30 * SEC_PER_MIN);
std::string custom = to_string("%Y-%m-%d %H:%M:%S", now);
std::string custom_local = to_string("%Y-%m-%d %H:%M:%S %z", now, 2 * SEC_PER_HOUR);
std::string filename = to_windows_filename(now);

See examples/time_formatting_example.cpp for a compact cross-platform example covering ISO8601, custom formatting, offset-aware rendering, and filename-safe output. See examples/time_formatting_showcase_example.cpp for a broader formatter showcase with UTC/local variants, millisecond helpers, MQL5, human-readable, and filename-oriented output.

ISO 8601 parsing

#include <time_shield.hpp>

DateTimeStruct dt;
TimeZoneStruct tz;
if (parse_iso8601("2024-11-25T14:30:00-05:30", dt, tz)) {
    ts_t ts_val = to_timestamp(dt) - to_offset(tz);
}

The parser uses a manual fast path without std::regex. Strings with an explicit offset are interpreted canonically: the wall-clock time in the string is converted to the corresponding UTC instant.

Custom format parsing

#include <time_shield.hpp>

ts_t ts_val = 0;
bool ok = try_parse_format_ts(
    "2024-11-25 14:30:00 -0530",
    "%Y-%m-%d %H:%M:%S %z",
    ts_val);

try_parse_format* mirrors the custom formatter grammar used by to_string() and to_string_ms(), stays non-throwing, and avoids regex-based parsing. %z emits compact offsets like +0530 in the formatter and accepts both +HHMM and ISO-style +HH:MM during parsing. See examples/time_parser_example.cpp for ISO8601 parsing, seconds/ms/floating timestamp parsing, formatter round-trips, and a simple failure case.

ISO week dates

#include <time_shield.hpp>

using namespace time_shield;

IsoWeekDateStruct iso{};
bool ok_iso = parse_iso_week_date("2025-W512", iso);      // permissive mixed form
bool ok_dt = DateTime::try_parse_iso_week_date("2025w51", iso); // lowercase 'w', Monday default

DateTime monday = DateTime::from_iso_week_date(iso, 9, 30);
std::string custom_iso = to_string_ms("%G-%V-%u", monday.unix_ms(), monday.utc_offset());

ISO week-date parsing accepts canonical forms (YYYY-Www-D, YYYYWwwD) and compatible mixed separator variants (YYYY-WwwD, YYYYWww-D). The parser also accepts uppercase or lowercase W, and missing ISO weekday defaults to Monday. Custom formatting and parsing support %G / %g / %V / %u as ISO week-based tokens. Custom parse formats reject mixes of ISO week-based year/week tokens with Gregorian %Y / %m / %d date tokens.

DateTime value type

DateTime stores UTC milliseconds plus an optional fixed offset for local component access and round-trip formatting.

#include <time_shield.hpp>

using namespace time_shield;

DateTime dt = DateTime::parse_iso8601("2025-12-16T10:20:30.123+02:30");
std::string local = dt.to_iso8601();        // preserves +02:30
std::string utc = dt.to_iso8601_utc();      // prints Z
DateTime tomorrow = dt.add_days(1).start_of_day();
int hour_local = dt.hour();                 // local hour (offset applied)
int hour_utc = dt.utc_hour();               // UTC hour

ZonedClock

ZonedClock stores a reusable local-time context for either a named zone or a fixed UTC offset. Named zones recompute DST-sensitive offsets for the requested UTC instant, while numeric offsets stay fixed.

#include <time_shield.hpp>

using namespace time_shield;

ZonedClock clock(CET);
DateTime berlin_now = clock.now();

TimeZone zone = UNKNOWN;
bool has_zone = parse_time_zone_name("CET", zone);

TimeZoneStruct tz_struct{};
bool has_offset = parse_time_zone("+05:30", tz_struct);

ZonedClock fixed_clock;
bool created_fixed = ZonedClock::try_from_offset(2 * SEC_PER_HOUR, fixed_clock);

clock.try_set_zone("+05:30");
std::string local_iso = clock.to_iso8601();

ZonedClock ntp_tokyo(JST, true);
ts_ms_t tokyo_local_ms = ntp_tokyo.local_time_ms();

parse_time_zone(...) validates timezone syntax and accepts numeric offsets up to 23:59. Semantic support checks for reusable offsets in DateTime, ZonedClock, and related helpers use is_valid_tz_offset(...) with the supported range [-12:00, +14:00].

Checking workdays

#include <time_shield/validation.hpp>
#include <time_shield/time_parser.hpp>

using namespace time_shield;

bool monday = is_workday(1710720000);                    // seconds timestamp
bool monday_ms = is_workday_ms("2024-03-18T09:00:00.500Z"); // ISO 8601 with milliseconds
bool from_date = is_workday(2024, 3, 18);                 // year, month, day components
bool from_str = is_workday("2024-03-18T09:00:00Z");     // ISO 8601 string

The string helpers accept the same ISO 8601 formats as str_to_ts / str_to_ts_ms and evaluate to false when parsing fails or the date falls on a weekend.

Locating first and last workdays

#include <time_shield/time_conversions.hpp>

using namespace time_shield;

ts_t first_open = start_of_first_workday_month(2024, 6);     // 2024-06-03T00:00:00Z
ts_t first_close = end_of_first_workday_month(2024, Month::JUN);
ts_t last_open = start_of_last_workday_month(2024, 3);        // 2024-03-29T00:00:00Z
ts_ms_t last_close_ms = end_of_last_workday_month_ms(2024, Month::MAR);

The helpers reuse the start_of_day / end_of_day semantics and therefore return UTC timestamps. Invalid months or months without workdays produce ERROR_TIMESTAMP to simplify downstream validation.

OLE Automation (OA) date conversions

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.

#include <time_shield/ole_automation_conversions.hpp>

using namespace time_shield;

oadate_t oa = ts_to_oadate(1714608000);                     // 2024-05-02 00:00:00Z
ts_t ts_from_oa = oadate_to_ts(oa);                         // round toward zero
oadate_t pre_base = to_oadate(1899, Month::DEC, 29, 6, 0); // -1.25
oadate_t from_parts = to_oadate(2024, Month::MAY, 2, 12, 0); // 2024-05-02 12:00:00Z

Julian date and lunar helpers

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.

#include <time_shield/julian_conversions.hpp>
#include <time_shield/astronomy_conversions.hpp>

using namespace time_shield;

jd_t jd = gregorian_ymd_to_jd(2024, 5, 2, 12, 0); // Julian Date for a calendar instant
mjd_t mjd = ts_to_mjd(1714608000);                 // Modified Julian Date
jdn_t jdn = gregorian_ymd_to_jdn(2024, 5, 2);     // Julian Day Number
double phase = moon_phase(fts());              // lunar phase fraction [0..1)
double age_days = moon_age_days(fts());        // approximate lunar age
MoonPhaseSineCosine signal = moon_phase_sincos(fts()); // sin/cos of the phase angle (continuous)
MoonQuarterInstants quarters = moon_quarters(fts());   // nearest quarter instants (Unix seconds, double)
bool is_near_new = is_new_moon_window(fts());  // inside +/-12h new moon window

Geocentric Moon phase calculator

MoonPhaseCalculator (time_shield::astronomy::MoonPhase) exposes richer geocentric outputs (illumination, angular diameters, distance, phase angle), sin/cos for a continuous phase signal, quarter instants, and phase event windows. The current math is geocentric (Earth-centered) without topocentric corrections, so phase and illumination are “global” for a given moment. What varies locally:

  • local date/time (timezone conversion),
  • visual orientation of the lit part (inverted between hemispheres),
  • visibility/observability, which depends on atmosphere and altitude above the horizon.

Current math is geocentric (Sun/Moon positions relative to Earth’s center, without topocentric parallax). This means illumination and the phase angle are primarily “global” at a given instant for the Earth as a whole. Locally, what actually differs is:

  • calendar date/time via timezone shifts,
  • apparent orientation of the illuminated side (flipped between northern/southern hemispheres),
  • visibility (e.g., first crescent) driven by atmosphere/horizon/altitude rather than the geocentric phase itself.
#include <time_shield/MoonPhase.hpp>

using namespace time_shield;

MoonPhaseCalculator calculator{};
const double ts = 1704067200.0; // 2024-01-01T00:00:00Z
MoonPhaseResult res = calculator.compute(ts);
MoonPhase::quarters_unix_s_t quarters = calculator.quarter_times_unix(ts); // Unix seconds (double)
MoonQuarterInstants around = moon_quarters(ts);
MoonPhaseSineCosine signal = moon_phase_sincos(ts);
bool is_new = calculator.is_new_moon_window(ts); // +/-12h window by default

Time zone conversion

#include <time_shield.hpp>

ts_t gmt = to_ts(2024, Month::JUN, 21, 12, 0, 0);
ts_t ist = gmt_to_ist(gmt);
ts_t kyiv = gmt_to_kyiv(gmt);
ts_t myt = convert_time_zone(ist, TimeZone::IST, TimeZone::MYT);

Supported fixed-offset additions include IST, MYT, WIB, WITA, WIT, KZT, TRT, BYT, SGT, ICT, PHT, GST, HKT, JST, and KST. Use zone_to_gmt() / gmt_to_zone() / convert_time_zone() for the generic seconds-based API and zone_to_gmt_ms() / gmt_to_zone_ms() / convert_time_zone_ms() for millisecond timestamps.

NTP client, pool, and time service

#include <time_shield/ntp_client_pool.hpp>
#include <time_shield/ntp_time_service.hpp>

using namespace time_shield;

NtpClientPool pool;
pool.set_default_servers();
pool.measure();
int64_t pool_offset = pool.offset_us();

// Background runner + lazy singleton service via wrapper functions:
ntp::init(30000); // 30 sec
int64_t utc_ms = ntp::utc_time_ms();
int64_t offset_us = ntp::offset_us();
int64_t utc_sec = ntp::utc_time_sec();
bool ok = ntp::last_measure_ok();
uint64_t attempts = ntp::measure_count();
ntp::shutdown();

NtpTimeService is header-only and supports C++11/C++14/C++17. The service uses an immortal singleton to avoid static destruction order issues. During normal runtime the singleton getters keep their lazy-start behavior. During process teardown the service does not restart the background runner and falls back to realtime plus the last cached offset. In general, C++17+ allows a simpler singleton-storage pattern with inline variables. For NtpTimeService, the public usage contract is the same in C++11/C++14/C++17.

Documentation

Full API description and additional examples are available at https://newyaroslav.github.io/time-shield-cpp/

Doxygen HTML is published via GitHub Pages.

License

The project is distributed under the MIT license.

About

Header-only C++ time utilities: high-performance timestamp/date conversions, formatting, ISO/UTC helpers, and lightweight time-zone conversions.

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors