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 @@

-
+



@@ -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