diff --git a/.gitattributes b/.gitattributes index 631303f..3617168 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,3 +1,6 @@ -# Не включать эти папки в GitHub source archive (tar.gz/zip) +# Exclude these directories from GitHub source archives (tar.gz/zip). vcpkg-overlay/ export-ignore .github/ export-ignore +*.mq4 text diff +*.mq5 text diff +*.mqh text diff diff --git a/MQL4/Include/TimeShield.mqh b/MQL4/Include/TimeShield.mqh new file mode 100644 index 0000000..d4e789b --- /dev/null +++ b/MQL4/Include/TimeShield.mqh @@ -0,0 +1,168 @@ +//+------------------------------------------------------------------+ +//| TimeShield.mqh | +//| Copyright 2025, NewYaroslav | +//| https://github.com/NewYaroslav/time-shield-cpp | +//+------------------------------------------------------------------+ +#ifndef TIME_SHIELD_MQL4_MQH_INCLUDED +#define TIME_SHIELD_MQL4_MQH_INCLUDED + +/// \file TimeShield.mqh +/// \brief Main MQL4 header file for the Time Shield library. + +#property copyright "Copyright 2025, NewYaroslav" +#property link "https://github.com/NewYaroslav/time-shield-cpp" +#property strict + +/// \class TimeShield +/// \brief Provides static time helpers for MQL4. +class TimeShield { +public: + enum Constants { + MS_PER_SEC = 1000, ///< Milliseconds per second. + US_PER_SEC = 1000000, ///< Microseconds per second. + SEC_PER_MIN = 60, ///< Seconds per minute. + SEC_PER_HOUR = 3600, ///< Seconds per hour. + SEC_PER_DAY = 86400 ///< Seconds per day. + }; + + /// \brief Get raw terminal tick counter in milliseconds. + /// \return Wrapped 32-bit terminal tick counter. + static uint tick_count_ms() { + return GetTickCount(); + } + + /// \brief Get monotonic milliseconds since terminal start with wrap handling. + /// \return Monotonic millisecond counter. + static ulong monotonic_ms() { + static bool initialized = false; + static uint last_raw = 0; + static ulong high = 0; + + const uint raw = GetTickCount(); + if(!initialized) { + initialized = true; + last_raw = raw; + return (ulong)raw; + } + + if(raw < last_raw) { + high += 4294967296; + } + last_raw = raw; + + return high + raw; + } + + /// \brief Get current UTC timestamp in seconds. + /// \return Current UTC timestamp in seconds. + static long ts() { + return (long)TimeGMT(); + } + + /// \brief Alias for ts(). + /// \return Current UTC timestamp in seconds. + static long timestamp() { + return ts(); + } + + /// \brief Get current UTC timestamp in milliseconds. + /// \return Current UTC timestamp in milliseconds. + static long ts_ms() { + static bool initialized = false; + static long offset = 0; + + if(!initialized) { + const long start_ts = ts(); + long next_ts = start_ts; + while((next_ts = ts()) == start_ts) { + } + + offset = next_ts * MS_PER_SEC - (long)monotonic_ms(); + initialized = true; + } + + return (long)monotonic_ms() + offset; + } + + /// \brief Alias for ts_ms(). + /// \return Current UTC timestamp in milliseconds. + static long timestamp_ms() { + return ts_ms(); + } + + /// \brief Alias for ts_ms(). + /// \return Current UTC timestamp in milliseconds. + static long now() { + return ts_ms(); + } + + /// \brief Get second of day from timestamp. + /// \param timestamp Timestamp in seconds. + /// \return Second of day. + static int sec_of_day(const long timestamp) { + return (int)(timestamp % SEC_PER_DAY); + } + + /// \brief Get second of day from milliseconds timestamp. + /// \param timestamp_ms Timestamp in milliseconds. + /// \return Second of day. + static int sec_of_day_ms(const long timestamp_ms) { + return sec_of_day(timestamp_ms / MS_PER_SEC); + } + + /// \brief Get second of day from hours, minutes and seconds. + /// \param hour Hour value. + /// \param minute Minute value. + /// \param second Second value. + /// \return Second of day. + static int sec_of_day(const int hour, const int minute, const int second) { + return hour * SEC_PER_HOUR + minute * SEC_PER_MIN + second; + } + + /// \brief Check time-of-day components. + /// \param hour Hour value. + /// \param minute Minute value. + /// \param second Second value. + /// \return True when the time is valid. + static bool is_valid_time(const int hour, const int minute, const int second) { + return hour >= 0 && hour < 24 + && minute >= 0 && minute < 60 + && second >= 0 && second < 60; + } + + /// \brief Convert string with time of day to second of day. + /// \param value Time string in HH, HH:MM, or HH:MM:SS format. + /// \return Second of day or SEC_PER_DAY on parse failure. + static int sec_of_day(const string value) { + string parts[]; + const ushort separator = StringGetCharacter(":", 0); + const int count = StringSplit(value, separator, parts); + + if(count < 1 || count > 3) { + ArrayFree(parts); + return SEC_PER_DAY; + } + + int hour = 0; + int minute = 0; + int second = 0; + + hour = (int)StringToInteger(parts[0]); + if(count > 1) { + minute = (int)StringToInteger(parts[1]); + } + if(count > 2) { + second = (int)StringToInteger(parts[2]); + } + + ArrayFree(parts); + + if(!is_valid_time(hour, minute, second)) { + return SEC_PER_DAY; + } + + return sec_of_day(hour, minute, second); + } +}; + +#endif // TIME_SHIELD_MQL4_MQH_INCLUDED diff --git a/README.md b/README.md index d583b7d..30cb3d2 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ Logo ![MIT License](https://img.shields.io/badge/license-MIT-green.svg) -![Platform](https://img.shields.io/badge/platform-Windows%20%7C%20Linux%20%7C%20macOS%20%7C%20MQL5-blue) +![Platform](https://img.shields.io/badge/platform-Windows%20%7C%20Linux%20%7C%20macOS%20%7C%20MQL4%20%7C%20MQL5-blue) ![C++ Standard](https://img.shields.io/badge/C++-11--17-orange) ![CI Windows](https://img.shields.io/github/actions/workflow/status/newyaroslav/time-shield-cpp/ci.yml?branch=main&label=Windows&logo=windows) ![CI Linux](https://img.shields.io/github/actions/workflow/status/newyaroslav/time-shield-cpp/ci.yml?branch=main&label=Linux&logo=linux) @@ -47,7 +47,7 @@ more academic solutions like `HowardHinnant/date`, the library: 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 +- **works even in restricted environments** such as MQL4/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 @@ -76,7 +76,7 @@ more academic solutions like `HowardHinnant/date`, the library: 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 +- **MQL4/MQL5 support**—adapted headers in the `MQL4` and `MQL5` directories allow using the library in MetaTrader. - Compatible with `C++11`–`C++17`. @@ -92,8 +92,10 @@ library and report platform capabilities: - `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. +C++ and MQL5 public headers place their declarations inside the `time_shield` +namespace. Use `time_shield::` or `using namespace time_shield;` to access that +API. MQL4 uses the `TimeShield` class with static methods and constants because +the MQL4 language does not provide namespaces. > Some functions depend on platform APIs and may be limited (for example, > obtaining realtime via `QueryPerformanceCounter` on Windows). @@ -179,7 +181,15 @@ Examples can be built with the provided scripts: - `build_examples.sh` for Linux/macOS; - `build-cb.bat` to generate a Code::Blocks project. -Use `install_mql5.bat` to install the MQL5 files. +Use `install_mql5.bat` to install the MQL5 files. For MQL4, copy +`MQL4/Include` into the terminal `Include` directory and include the header: + +```mql +#include + +long now_ms = TimeShield::now(); +int second_day = TimeShield::sec_of_day(TimeCurrent()); +``` ### Integration Notes