1253 lines
121 KiB
MQL5
1253 lines
121 KiB
MQL5
//+------------------------------------------------------------------+
|
|
//| TimeZoneInfo.mqh |
|
|
//| Copyright © 2018, Amr Ali |
|
|
//| https://www.mql5.com/en/users/amrali |
|
|
//+------------------------------------------------------------------+
|
|
#ifndef TIMEZONEINFO_UNIQUE_HEADER_ID_H
|
|
#define TIMEZONEINFO_UNIQUE_HEADER_ID_H
|
|
#property version "2.16"
|
|
|
|
// Updates:
|
|
// 2024.03.01 - v.1.40 : Removed redundant code in CTimeZoneInfo class (used during testing), added more methods to CSessionHours class, updated TestIndi.mq5 to reflect the new changes.
|
|
// 2024.03.03 - v.1.45 : Updated the example code for "Working with Local Session Hours - CTimeZoneInfo Class".
|
|
// 2024.03.08 - v.1.50 : Added two internal methods HistoryBrokerOffset() and FirstBarOfWeek() to CTimeZoneInfo class. Handling of server time in the past (UTC offset, text formatting, conversions, etc..).
|
|
// 2024.03.15 - v.1.56 : Added script "SydneySession_Script.mq5" to to show how the session hours in Sydney are fluctuating throughout the year.
|
|
// 2024.03.30 - v.1.65 : Fixed broker GMT offset. Currently, the library scans H1 bars only on the GOLD chart as it has the most accurate start times on all brokers that I have tested.
|
|
// 2024.04.01 - v.1.67 : Fixed potential issue in the calculation of the broker GMT offset during the first hour of the trading week.
|
|
// 2024.04.03 - v.1.70 : Caching of broker GMT offsets into a hash map for faster retrieval (e.g., from indicators). The broker offset cache will contain one entry per trading week.
|
|
// 2024.04.08 - v.1.72 : Improved the performance of the library by at least 2-3x times. Now using StructToTime instead of StringToTime inside the GetNthSunday method.
|
|
// 2024.04.09 - v.1.75 : Fixed potential issue in the calculation of the broker GMT offset during the Christmas holidays on GMT+0 brokers.
|
|
// 2024.04.11 - v.1.77 : Speed-up of the GetDaylightSwitchTimes mehod. Now using a static array to memorize the switch times for the current year.
|
|
// 2024.04.12 - v.1.80 : Fixed issue in the calculation of the broker GMT offset on brokers that do not provide gold trading.
|
|
// 2024.04.15 - v.1.82 : Added SetCustomTimeZone method to CTimeZoneInfo class, which you can use to configure the built-in custom time zone with a specified name, GMT offset and DST identifier. The custom time zone can be accessed via ZONE_ID_CUSTOM.
|
|
// 2024.04.16 - v.1.85 : Replaced the GetNthSunday internal method with the more optimized GetMonthTime method.
|
|
// 2024.04.17 - v.1.87 : Replaced the TimeYear internal method with the more optimized GetYear method.
|
|
// 2024.04.18 - v.1.88 : Added the CreateDateTime internal method to construct datetime values from date components (year, month and day). This is 100-120x times faster than calling the StructToTime function.
|
|
// 2024.10.21 - v.1.90 : Improved detection of the GOLD symbol with fallback to EURUSD symbol.
|
|
// 2024.10.22 - v.1.93 : Faster determination of the server's GMT offset during weekend in the live trading.
|
|
// 2024.10.26 - v.1.95 : Added two new static methods: TimeGMTOffset() and TimeDaylightSavings(). Renamed the HistoryBrokerOffset() method to TimeServerGMTOffset().
|
|
// 2024.10.28 - v.1.97 : Converted all macros for dealing with time to functions to avoid double evaluation of parameters inside macro body. More code clean-up in other lines.
|
|
// 2024.10.30 - v.1.98 : Fixed issue of wrong estimation of GMT offset from XAUEUR quotes on some brokers.
|
|
// 2024.11.01 - v.1.99 : Added an option to switch off the default loading of Gold symbol for estimation of the server's TZ/DST. Call CTimeZoneInfo::SetUsingGoldSymbol() with 'false' to use the current chart's symbol, instead.
|
|
// 2024.11.13 - v.2.00 : Improved estimation of online server tz/dst and in the strategy strategy (TimeGMT library is no longer needed). Constructor accepts place and time parameters. New public methods for working with data of datetime type. Improved error handling and debugging support.
|
|
// 2024.11.14 - v.2.02 : Fixed error ERR_HISTORY_NOT_FOUND (4401) when trying to access the XAUUSD,H1 quotes history by the server timezone object.
|
|
// 2024.11.17 - v.2.03 : Minor bug fixes.
|
|
// 2024.11.23 - v.2.04 : Minor improvements and bug fixes.
|
|
// 2024.12.05 - v.2.05 : Added new static method DaylightSavingsSupported(placeId).
|
|
// 2024.12.12 - v.2.10 : Improved performance of HistoryServerGMTOffset() and HistoryServerDaylightSavings() functions, and other minor code changes.
|
|
// 2024.12.14 - v.2.11 : Improved performance of FindSymbol() function.
|
|
// 2024.12.17 - v.2.12 : Further optimization of HistoryServerGMTOffset() and HistoryServerDaylightSavings() functions.
|
|
// 2024.12.24 - v.2.13 : Fixed potential issue in HistoryServerGMTOffset() and HistoryServerDaylightSavings() functions.
|
|
// 2025.01.12 - v.2.15 : Fixed issue in HistoryServerDaylightSavings() of searching for quotes history earlier than the loaded history in the strategy tester.
|
|
|
|
#include <Generic\HashMap.mqh>
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Time zone identifiers |
|
|
//+------------------------------------------------------------------+
|
|
enum ENUM_ZONE_ID
|
|
{
|
|
/* caution: lookup tables depends on the order of enum */
|
|
ZONE_ID_SYDNEY, // Sydney
|
|
ZONE_ID_TOKYO, // Tokyo
|
|
ZONE_ID_FRANKFURT, // Frankfurt
|
|
ZONE_ID_LONDON, // London
|
|
ZONE_ID_NEWYORK, // New York
|
|
ZONE_ID_UTC, // UTC
|
|
ZONE_ID_LOCAL, // LOCAL
|
|
ZONE_ID_BROKER, // BROKER
|
|
ZONE_ID_CUSTOM // CUSTOM
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| User-defined errors |
|
|
//+------------------------------------------------------------------+
|
|
enum ENUM_ERR_USER_TIMEZONE
|
|
{
|
|
ERR_USER_TIME_OK = 0,
|
|
ERR_USER_TIME_INVALID = 1,
|
|
ERR_USER_WRONG_ZONEID = 2,
|
|
ERR_USER_WRONG_PARAMETERS = 3
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Structure to keep the times. |
|
|
//+------------------------------------------------------------------+
|
|
struct Time
|
|
{
|
|
datetime localtime;
|
|
datetime utctime;
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Structure to keep the time zone offset information. |
|
|
//+------------------------------------------------------------------+
|
|
struct TimeZone
|
|
{
|
|
int offset; // base utc offset
|
|
int dst; // dst adjustment
|
|
int totaloffset; // total utc offset (includes dst)
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Structure to keep the time zone daylight saving times. |
|
|
//+------------------------------------------------------------------+
|
|
struct TimeChange
|
|
{
|
|
int dstbias;
|
|
int timedelta;
|
|
datetime oldlocaltime;
|
|
datetime newlocaltime;
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Macros |
|
|
//+------------------------------------------------------------------+
|
|
#define TIME_NOW (0)
|
|
#define WRAP24(H) ((H + 24) % 24) // wrap value around 24 hours
|
|
#define Debug(message) PrintFormat(" <%s> %s(){ line %d: %s }", __FILE__, __FUNCTION__, __LINE__, message)
|
|
|
|
#define CHKERR(expression, action) \
|
|
ResetLastError(); \
|
|
expression; \
|
|
if(_LastError) { \
|
|
Debug(StringFormat("Error in %s. Error code = %d", #expression, _LastError)); \
|
|
action; \
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Class CTimeZoneInfo. |
|
|
//| Purpose: Class to access to the local time for the specified |
|
|
//| location, as well as time zone information, time |
|
|
//| changes for the current year. |
|
|
//| |
|
|
//| Offset notation used in the library: |
|
|
//| Please note, that the library denotes positive time |
|
|
//| zones by positive offsets, and negative time zones |
|
|
//| by negative offsets. |
|
|
//| This is opposite of built-in TimeGMTOffset() function |
|
|
//| which denotes positive time zones, such as GMT+3, by |
|
|
//| negative offsets, such as -10800, and vice versa. |
|
|
//| |
|
|
//| Symbol for Estimation of The Server TZ/DST: |
|
|
//| By default, library uses XAUUSD as it provides reliable |
|
|
//| results (esp., brokers that follow EU DST schedule). |
|
|
//| To use the current chart's symbol instead of XAUUSD, |
|
|
//| call CTimeZoneInfo::SetUsingGoldSymbol() with 'false'. |
|
|
//| |
|
|
//| Compatibility with The Strategy Tester: |
|
|
//| The library estimates the proper times in time zones |
|
|
//| based on "true" GMT by analysis of H1 quotes history, |
|
|
//| and not based on the time returned by calling built-in |
|
|
//| TimeGMT() function. |
|
|
//| If the XAUUSD symbol is used for estimation of the |
|
|
//| server's TZ/DST then dst switches will occur one hour |
|
|
//| later in the strategy tester. |
|
|
//+------------------------------------------------------------------+
|
|
class CTimeZoneInfo
|
|
{
|
|
protected:
|
|
string m_name; // timezone name
|
|
ENUM_ZONE_ID m_id; // timezone integer id
|
|
Time time; // timezone local time and utc time
|
|
TimeZone timezone; // timezone offset information
|
|
TimeChange dststart; // daylight savings time start
|
|
TimeChange dstend; // daylight savings time end
|
|
|
|
public:
|
|
CTimeZoneInfo(const ENUM_ZONE_ID placeId, const datetime pLocalTime = TIME_NOW);
|
|
~CTimeZoneInfo(void) { }
|
|
|
|
//--- methods to access to protected data
|
|
string Name(void) const { return(m_name); } // Returns the name of time zone
|
|
string ToString(bool secs = true, bool tzname = true) const; // Returns a string of local time formatted with TZ/DST offset and tzname
|
|
|
|
//--- methods to populate timezone information
|
|
bool RefreshTime(void); // Refresh the current local time and populate timezone information
|
|
bool SetLocalTime(const datetime pLocalTime = TIME_NOW); // Set the local time for this location to the specified time
|
|
|
|
//--- methods to access time zone time (each object has 2 times + 2 offsets)
|
|
datetime TimeLocal(void) const { return(time.localtime); } // Returns the local time in timezone
|
|
datetime TimeUTC(void) const { return(time.utctime); } // Returns the UTC time (the same in all time zones)
|
|
int TimeGMTOffset(void) const { return(timezone.totaloffset); } // Positive value for positive timezones (eg, GMT+3), otherwise negative. (includes DST)
|
|
int TimeDaylightSavings(void) const { return(timezone.dst); } // Returns DST correction (in seconds) for timezone, at the set local time.
|
|
|
|
//--- methods to access other services
|
|
datetime ConvertLocalTime(const ENUM_ZONE_ID destinationId) const; // Convert local time in this time zone to a different time zone
|
|
bool GetDaylightSwitchTimes(datetime &dst_start, datetime &dst_end) const; // Get the Daylight Savings Time start and end times for the year
|
|
datetime GetDaylightNextSwitch(void) const; // Get the local time of the next Daylight Savings Time switch
|
|
void PrintObject(void) const;
|
|
|
|
public:
|
|
//--- static methods that do not require the creation of an object.
|
|
static datetime GetCurrentTimeForPlace ( const ENUM_ZONE_ID placeId ); // Get the current local time for the specified time zone
|
|
static string FormatTimeForPlace ( const datetime time, const ENUM_ZONE_ID placeId, bool secs = true, bool tzname = true );
|
|
static datetime ConvertTimeForPlace ( const datetime time, const ENUM_ZONE_ID placeId, const ENUM_ZONE_ID destinationId );
|
|
static int TimeGMTOffset ( const ENUM_ZONE_ID placeId, const datetime time = TIME_NOW ); // Returns total tz offset (UTC+DST) from GMT, for a timezone at given local time
|
|
static int TimeDaylightSavings ( const ENUM_ZONE_ID placeId, const datetime time = TIME_NOW ); // Returns dst correction in seconds, for a timezone at given local time
|
|
|
|
static bool IsDaylightSavingsTime ( const ENUM_ZONE_ID placeId, const datetime time = TIME_NOW ); // Checks if a specified time falls in the Daylight Savings Time
|
|
static bool DaylightSavingsSupported( const ENUM_ZONE_ID placeId); // Checks if the given timezone supports the Daylight Savings Time policy
|
|
static bool GetDaylightSwitchTimes ( const ENUM_ZONE_ID placeId, const int iYear, datetime &dst_start, datetime &dst_end );
|
|
static bool GetDaylightSwitchDeltas( const ENUM_ZONE_ID placeId, const int iYear, int &delta_start, int &delta_end );
|
|
|
|
static bool SetCustomTimeZone(const string name, const int baseGMTOffset = 0, // Defines a time zone that is not found in the library.
|
|
const ENUM_ZONE_ID dstSchedule = ZONE_ID_UTC);
|
|
static void SetUsingGoldSymbol(const bool enabled = true); // Sets the option to use Gold symbol for estimation of server TZ/DST
|
|
|
|
protected:
|
|
//--- internal methods
|
|
static int HistoryServerGMTOffset(const datetime srvtime); // estimate server TZ offset (UTC+DST) from GMT at a given time
|
|
static int HistoryServerDaylightSavings(const datetime srvtime); // estimate server time DST correction from H1 quotes history
|
|
static datetime FirstBarOfWeek(datetime &weekend, int depthLimit = 8); // Returns the time of the first H1 bar after a given weekend (skips abnormal weeks)
|
|
static string FindSymbol(string symbol);
|
|
static bool CheckLoadHistory(const string symbol, const ENUM_TIMEFRAMES period); // Checks presence of the history for symbol and timeframe
|
|
//--- static variables
|
|
static CHashMap<datetime,int>m_serveroffset; // hashmap of server tz offset
|
|
static CHashMap<datetime,int>m_serverdst; // hashmap of server dst correction
|
|
|
|
protected:
|
|
//--- methods for working with data of datetime type
|
|
static datetime Date(const int year, const int mon, const int day, // Create a datetime value from date/time components
|
|
const int hour = 0, const int min = 0, const int sec = 0);
|
|
static datetime StartOfWeek(const datetime time, const bool StartsOnMonday = false); // Return the start of a week for the given date
|
|
static int WeekIndex(const datetime time, const bool StartsOnMonday = false); // Returns the zero-based absolute week number since 1 Jan 1970
|
|
static int DayOfWeek(const datetime time); // Day of week as integer (0-Sunday, 1-Monday, ... ,6-Saturday)
|
|
static int GetYear(const datetime time); // Returns year as integer of the given date (e.g., 2019)
|
|
static int GetHour(const datetime time); // Return hour of day as integer (0..23) of the given date
|
|
static string t2s(const datetime time, const int mode = TIME_DATE | TIME_MINUTES); // Formats time with the weekday name => "Wed, 2023.02.14 01:59"
|
|
|
|
//--- time unit constants
|
|
enum ENUM_TIME_UNITS
|
|
{
|
|
MINSECS = 60,
|
|
HOURSECS = 3600, // = 60 * 60,
|
|
DAYSECS = 86400, // = 60 * 60 * 24,
|
|
WEEKSECS = 604800, // = 60 * 60 * 24 * 7,
|
|
YEARSECS = 31536000 // = 60 * 60 * 24 * 365
|
|
};
|
|
|
|
private:
|
|
static string m_symbol;
|
|
static bool m_using_xauusd;
|
|
static bool m_suspend_errors;
|
|
//--- variables for the custom timezone
|
|
static string m_custom_name;
|
|
static int m_custom_offset;
|
|
static ENUM_ZONE_ID m_custom_rule_id;
|
|
//---
|
|
static string m_tbl_city_names[];
|
|
static int m_tbl_utc_offset[];
|
|
static int m_tbl_dst_start[];
|
|
static int m_tbl_dst_end[];
|
|
};
|
|
//--- Initialize static variables
|
|
CHashMap<datetime,int> CTimeZoneInfo::m_serveroffset;
|
|
CHashMap<datetime,int> CTimeZoneInfo::m_serverdst;
|
|
string CTimeZoneInfo::m_symbol=NULL;
|
|
bool CTimeZoneInfo::m_using_xauusd=true; // search for and load Gold symbol (default)
|
|
bool CTimeZoneInfo::m_suspend_errors=false;
|
|
string CTimeZoneInfo::m_custom_name="Custom";
|
|
int CTimeZoneInfo::m_custom_offset=0; // base tz offset in seconds
|
|
ENUM_ZONE_ID CTimeZoneInfo::m_custom_rule_id=ZONE_ID_UTC;
|
|
string CTimeZoneInfo::m_tbl_city_names[] = { "Sydney", "Tokyo", "Frankfurt", "London", "New York", "UTC" };
|
|
int CTimeZoneInfo::m_tbl_utc_offset[] = { 10, 9, 1, 0, -5, 0 }; // base utc offsets in hours
|
|
int CTimeZoneInfo::m_tbl_dst_start[] = { 0, 0, 1, 1, 1, 0 }; // dst bias during spring
|
|
int CTimeZoneInfo::m_tbl_dst_end[] = { 1, 0, 0, 0, 0, 0 }; // dst bias during autumn
|
|
//+------------------------------------------------------------------+
|
|
//| Constructor. |
|
|
//| CTimeZoneInfo objects, by default, instantiate with the current |
|
|
//| local time and time zone information for the specified location. |
|
|
//+------------------------------------------------------------------+
|
|
CTimeZoneInfo::CTimeZoneInfo(const ENUM_ZONE_ID placeId, const datetime pLocalTime = TIME_NOW) : m_name(NULL), m_id(WRONG_VALUE)
|
|
{
|
|
ZeroMemory(time);
|
|
ZeroMemory(timezone);
|
|
ZeroMemory(dststart);
|
|
ZeroMemory(dstend);
|
|
//--- fill zone id, name, UTC offset and DST bias.
|
|
m_id = placeId;
|
|
switch(m_id)
|
|
{
|
|
case ZONE_ID_SYDNEY:
|
|
case ZONE_ID_TOKYO:
|
|
case ZONE_ID_FRANKFURT:
|
|
case ZONE_ID_LONDON:
|
|
case ZONE_ID_NEWYORK:
|
|
case ZONE_ID_UTC:
|
|
m_name = m_tbl_city_names[m_id];
|
|
timezone.offset = m_tbl_utc_offset[m_id] * HOURSECS; // convert utc offsets to seconds
|
|
dststart.dstbias = m_tbl_dst_start[m_id] * HOURSECS; // convert dst bias to seconds
|
|
dstend.dstbias = m_tbl_dst_end[m_id] * HOURSECS;
|
|
dststart.timedelta = dststart.dstbias - dstend.dstbias;
|
|
dstend.timedelta = dstend.dstbias - dststart.dstbias;
|
|
break;
|
|
|
|
case ZONE_ID_LOCAL:
|
|
m_name = "Local-PC";
|
|
break;
|
|
|
|
case ZONE_ID_BROKER:
|
|
m_name = ::AccountInfoString(ACCOUNT_SERVER);
|
|
break;
|
|
|
|
case ZONE_ID_CUSTOM:
|
|
m_name = m_custom_name;
|
|
timezone.offset = m_custom_offset;
|
|
dststart.dstbias = m_tbl_dst_start[m_custom_rule_id] * HOURSECS;
|
|
dstend.dstbias = m_tbl_dst_end[m_custom_rule_id] * HOURSECS;
|
|
dststart.timedelta = dststart.dstbias - dstend.dstbias;
|
|
dstend.timedelta = dstend.dstbias - dststart.dstbias;
|
|
break;
|
|
|
|
default:
|
|
Print(">>Error: CTimeZoneInfo constructor cannot properly instantiate an object with placeId = ", placeId);
|
|
SetUserError(ERR_USER_WRONG_ZONEID);
|
|
return;
|
|
}
|
|
//--- instantiate with the specified local time
|
|
SetLocalTime(pLocalTime);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Refresh the current local time and populate timezone information |
|
|
//+------------------------------------------------------------------+
|
|
bool CTimeZoneInfo::RefreshTime(void)
|
|
{
|
|
return(SetLocalTime(TIME_NOW));
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Sets the local time for this location to the specified time, |
|
|
//| and populates other timezone information, accordingly. |
|
|
//+------------------------------------------------------------------+
|
|
//| Returns FALSE, if the specified time local falls into a non- |
|
|
//| existing hour during the transition to Daylight Savings Time. |
|
|
//| |
|
|
//| NB: LocalTime = 0 means the "current" moment in time zone now, |
|
|
//| based on the current GMT: tLOC = TimeGMT() + OFF_LOC + DST_LOC |
|
|
//+------------------------------------------------------------------+
|
|
bool CTimeZoneInfo::SetLocalTime(const datetime pLocalTime = TIME_NOW)
|
|
{
|
|
datetime tts = ::TimeTradeServer();
|
|
datetime tGMT = ::TimeGMT();
|
|
|
|
//--- In the strategy tester, TimeGMT() is always equal to TimeTradeServer() simulated server time.
|
|
//--- However, the "true" GMT can be estimated by analysis of H1 quotes history to get the server
|
|
//--- timezone offset. The server GMT offset is subtracted from server time to get the true GMT.
|
|
//--- (e.g., TimeTradeServer() = 23:00, and offset from H1 bars = GMT+3, then true GMT = 20:00).
|
|
|
|
#ifndef TIMEGMT_UNIQUE_HEADER_ID_H // apply fix only in tester (update v2.00: 'TimeGMT library' is no longer needed.)
|
|
if(MQLInfoInteger(MQL_TESTER) && pLocalTime == TIME_NOW)
|
|
{
|
|
tGMT = tts - HistoryServerGMTOffset(tts);
|
|
}
|
|
#endif
|
|
|
|
switch(m_id)
|
|
{
|
|
case ZONE_ID_LOCAL:
|
|
timezone.dst = -(int)(::TimeDaylightSavings());
|
|
timezone.offset = -(int)(::TimeGMTOffset());
|
|
//--- the dst correction has to subtracted from the "timezone.offset"
|
|
//--- so that, the field holds the "base" utc offset (excluding dst)
|
|
timezone.offset -= timezone.dst;
|
|
break;
|
|
|
|
case ZONE_ID_BROKER:
|
|
{
|
|
datetime srvtime = pLocalTime;
|
|
if(srvtime == TIME_NOW || srvtime > fmax(tts, TimeCurrent()))
|
|
{
|
|
srvtime = fmax(tts, TimeCurrent());
|
|
}
|
|
|
|
ResetLastError();
|
|
|
|
//--- determine the server's TZ/DST from H1 quotes history
|
|
int DST = HistoryServerDaylightSavings(srvtime);
|
|
int utc = HistoryServerGMTOffset(srvtime); // tz offset from bars
|
|
|
|
if(DST == INT_MIN || utc == INT_MIN)
|
|
{
|
|
Debug(StringFormat("Error in int DST = HistoryServerDaylightSavings(%s). Error code = %d", t2s(srvtime), _LastError)); // ERR_HISTORY_NOT_FOUND (4401)
|
|
timezone.offset = timezone.dst = INT_MIN;
|
|
time.localtime = time.utctime = WRONG_VALUE;
|
|
return(false);
|
|
}
|
|
|
|
#ifdef PRINT_TZ_DETAILS
|
|
Debug(StringFormat("Server time offset: UTC%+g %s", utc/(double)HOURSECS, DST ? "DST" : "STD"));
|
|
#endif
|
|
|
|
//--- if called for online time, we can check against built-in functions for refinement
|
|
const int OnlineServerGMTOffset = (int)(tts - tGMT); // online tz offset
|
|
|
|
if(WeekIndex(tts) == WeekIndex(srvtime) && OnlineServerGMTOffset != utc
|
|
&& TerminalInfoInteger(TERMINAL_CONNECTED) && !MQLInfoInteger(MQL_TESTER))
|
|
{
|
|
if(DST && OnlineServerGMTOffset == utc - 1*HOURSECS)
|
|
{
|
|
utc = OnlineServerGMTOffset;
|
|
DST = 0; // switch to winter
|
|
#ifdef PRINT_TZ_DETAILS
|
|
Debug(StringFormat("[Online time zone change: UTC%+g STD", utc/(double)HOURSECS));
|
|
#endif
|
|
//--- update current week's tz/dst in the cache
|
|
m_serveroffset.TrySetValue(StartOfWeek(srvtime), utc);
|
|
m_serverdst.TrySetValue(StartOfWeek(srvtime), DST);
|
|
}
|
|
else
|
|
if(!DST && OnlineServerGMTOffset == utc + 1*HOURSECS)
|
|
{
|
|
utc = OnlineServerGMTOffset;
|
|
DST = 1*HOURSECS; // summer
|
|
#ifdef PRINT_TZ_DETAILS
|
|
Debug(StringFormat("Online time zone change: UTC%+g DST", utc/(double)HOURSECS));
|
|
#endif
|
|
//--- update current week's tz/dst in the cache
|
|
m_serveroffset.TrySetValue(StartOfWeek(srvtime), utc);
|
|
m_serverdst.TrySetValue(StartOfWeek(srvtime), DST);
|
|
}
|
|
}
|
|
|
|
timezone.dst = DST;
|
|
timezone.offset = utc;
|
|
//--- the dst correction has to subtracted from the "timezone.offset"
|
|
//--- so that, the field holds the "base" tz offset (excluding dst)
|
|
timezone.offset -= timezone.dst;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
time.localtime = pLocalTime == TIME_NOW ? tGMT + timezone.offset : pLocalTime;
|
|
|
|
#ifdef PRINT_TZ_DETAILS
|
|
Debug(StringFormat("%s: Setting timezone's local time to %s.", m_name, t2s(time.localtime)));
|
|
#endif
|
|
|
|
//--- determine the daylight savings switch times for this year
|
|
if(GetDaylightSwitchTimes(dststart.oldlocaltime, dstend.oldlocaltime))
|
|
{
|
|
//--- only for time zones that observe daylight time
|
|
#ifdef PRINT_TZ_DETAILS
|
|
Debug(StringFormat("%s: DST starts on %s (%+d) and ends on %s (%+d).",
|
|
m_name, t2s(dststart.oldlocaltime), dststart.timedelta/HOURSECS,
|
|
t2s(dstend.oldlocaltime), dstend.timedelta/HOURSECS));
|
|
#endif
|
|
|
|
//--- dst struct
|
|
dststart.newlocaltime = dststart.oldlocaltime + dststart.timedelta;
|
|
dstend.newlocaltime = dstend.oldlocaltime + dstend.timedelta;
|
|
|
|
/**
|
|
* DST Transitions:
|
|
* https://www.timeanddate.com/time/dst/transition.html
|
|
*
|
|
* ("Summer Skip", "Winter Wind-back")
|
|
* Switch to summer time (DST+1): clock skips the switch hour (non-existing).
|
|
* Switch to winter time (DST+0): clock repeats the hour before the switch again.
|
|
*/
|
|
|
|
//--- Switch to summer time in the northern hemisphere of earth
|
|
//--- if the time does not exist in timezone, because DST skipped 1 hour
|
|
if(pLocalTime>=dststart.oldlocaltime && pLocalTime<dststart.newlocaltime)
|
|
{
|
|
if(!m_suspend_errors)
|
|
{
|
|
PrintFormat(">>Error: The time %s does not exist in %s. This is because Daylight Savings Time skipped one hour.", t2s(pLocalTime), m_name);
|
|
SetUserError(ERR_USER_TIME_INVALID);
|
|
}
|
|
time.localtime = time.utctime = WRONG_VALUE;
|
|
return(false);
|
|
}
|
|
|
|
//--- Switch to summer time in the southern hemisphere of earth
|
|
if(pLocalTime>=dstend.oldlocaltime && pLocalTime<dstend.newlocaltime)
|
|
{
|
|
if(!m_suspend_errors)
|
|
{
|
|
PrintFormat(">>Error: The time %s does not exist in %s. This is because Daylight Savings Time skipped one hour.", t2s(pLocalTime), m_name);
|
|
SetUserError(ERR_USER_TIME_INVALID);
|
|
}
|
|
time.localtime = time.utctime = WRONG_VALUE;
|
|
return(false);
|
|
}
|
|
|
|
timezone.dst = ( time.localtime >= MathMin(dststart.oldlocaltime, dststart.newlocaltime)
|
|
&& time.localtime < MathMin(dstend.oldlocaltime, dstend.newlocaltime) ) ? dststart.dstbias : dstend.dstbias;
|
|
}
|
|
|
|
//--- timezone struct
|
|
timezone.totaloffset = timezone.offset + timezone.dst;
|
|
|
|
#ifdef PRINT_TZ_DETAILS
|
|
Debug(StringFormat("%s: Time zone base offset UTC%+g hours | DST %+g hours"+
|
|
" | Total offset UTC%+g hours.", m_name, timezone.offset/(double)HOURSECS, // double div as tz could be (GMT+5.5)
|
|
timezone.dst/(double)HOURSECS, timezone.totaloffset/(double)HOURSECS));
|
|
#endif
|
|
|
|
//--- time struct
|
|
time.localtime = pLocalTime == TIME_NOW ? tGMT + timezone.totaloffset : pLocalTime;
|
|
time.utctime = time.localtime - timezone.totaloffset;
|
|
|
|
#ifdef PRINT_TZ_DETAILS
|
|
Debug(StringFormat("%s: Local time is %s | corresponding UTC Time is %s.",
|
|
m_name, this.ToString(true,false), t2s(time.utctime, TIME_DATE|TIME_SECONDS)));
|
|
#endif
|
|
|
|
return(true);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Get the Daylight Savings Time start and end times for the year. |
|
|
//| Returns FALSE, if the given time zone does not observe DST. |
|
|
//+------------------------------------------------------------------+
|
|
bool CTimeZoneInfo::GetDaylightSwitchTimes(datetime &dst_start, datetime &dst_end) const
|
|
{
|
|
//--- formulae from https://www.webexhibits.org/daylightsaving/i.html
|
|
const int iYear = GetYear(time.localtime);
|
|
const int Y = 5 * iYear / 4;
|
|
|
|
switch(m_id)
|
|
{
|
|
case ZONE_ID_SYDNEY:
|
|
///
|
|
/// Sydney: https://www.timeanddate.com/time/change/australia/sydney
|
|
/// DST begins at 03:00 local time on the first Sunday of April (-1),
|
|
/// and ending at 02:00 local time on the first Sunday of October (+1)
|
|
///
|
|
dst_start = Date(iYear, 04, (7 - (4 + Y) % 7), 03, 00);
|
|
dst_end = Date(iYear, 10, (7 - (5 + Y) % 7), 02, 00);
|
|
break;
|
|
|
|
case ZONE_ID_FRANKFURT:
|
|
///
|
|
/// Frankfurt: https://www.timeanddate.com/time/change/germany/frankfurt
|
|
/// DST begins at 02:00 local time on the last Sunday of March (+1),
|
|
/// and ending at 03:00 local time on the last Sunday of October (-1)
|
|
///
|
|
dst_start = Date(iYear, 03, (31 - (4 + Y) % 7), 02, 00);
|
|
dst_end = Date(iYear, 10, (31 - (1 + Y) % 7), 03, 00);
|
|
break;
|
|
|
|
case ZONE_ID_LONDON:
|
|
///
|
|
/// London: https://www.timeanddate.com/time/change/uk/london
|
|
/// DST begins at 01:00 local time on the last Sunday of March (+1),
|
|
/// and ending at 02:00 local time on the last Sunday of October (-1)
|
|
///
|
|
dst_start = Date(iYear, 03, (31 - (4 + Y) % 7), 01, 00);
|
|
dst_end = Date(iYear, 10, (31 - (1 + Y) % 7), 02, 00);
|
|
break;
|
|
|
|
case ZONE_ID_NEWYORK:
|
|
///
|
|
/// New York: https://www.timeanddate.com/time/change/usa/new-york
|
|
/// DST begins at 02:00 local time on the second Sunday of March (+1),
|
|
/// and ending at 02:00 local time on the first Sunday of November (-1)
|
|
///
|
|
dst_start = Date(iYear, 03, (14 - (1 + Y) % 7), 02, 00);
|
|
dst_end = Date(iYear, 11, (07 - (1 + Y) % 7), 02, 00);
|
|
break;
|
|
|
|
case ZONE_ID_CUSTOM:
|
|
|
|
return(CTimeZoneInfo::GetDaylightSwitchTimes(m_custom_rule_id, iYear, dst_start, dst_end));
|
|
|
|
case ZONE_ID_TOKYO: // no DST
|
|
case ZONE_ID_UTC: // no DST
|
|
case ZONE_ID_LOCAL:
|
|
case ZONE_ID_BROKER:
|
|
dst_start = 0;
|
|
dst_end = 0;
|
|
|
|
default:
|
|
return(false);
|
|
}
|
|
|
|
return(true);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Get the local time of the next Daylight Savings Time switch. |
|
|
//+------------------------------------------------------------------+
|
|
datetime CTimeZoneInfo::GetDaylightNextSwitch(void) const
|
|
{
|
|
if(dststart.oldlocaltime == 0)
|
|
return(0);
|
|
|
|
if(time.localtime < dststart.oldlocaltime)
|
|
return(dststart.oldlocaltime);
|
|
|
|
if(time.localtime < dstend.oldlocaltime)
|
|
return(dstend.oldlocaltime);
|
|
|
|
//--- past the end of the current year
|
|
datetime dst_start, dst_end;
|
|
int y = GetYear(time.localtime);
|
|
GetDaylightSwitchTimes(m_id, ++y, dst_start, dst_end);
|
|
|
|
return(dst_start);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Get the Daylight Savings Time start and end times for the year. |
|
|
//| Returns FALSE, if the given time zone does not observe DST. |
|
|
//+------------------------------------------------------------------+
|
|
bool CTimeZoneInfo::GetDaylightSwitchTimes(const ENUM_ZONE_ID placeId, const int iYear, datetime &dst_start, datetime &dst_end)
|
|
{
|
|
//--- populate DST switch times for the current year
|
|
CHKERR(CTimeZoneInfo timezone(placeId, Date(iYear,1,1)),
|
|
return(false));
|
|
return(timezone.GetDaylightSwitchTimes(dst_start, dst_end));
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Get the Daylight Savings Time clock changes in sec for the year. |
|
|
//+------------------------------------------------------------------+
|
|
bool CTimeZoneInfo::GetDaylightSwitchDeltas(const ENUM_ZONE_ID placeId, const int iYear, int &delta_start, int &delta_end)
|
|
{
|
|
CHKERR(CTimeZoneInfo timezone(placeId),
|
|
return(false));
|
|
delta_start = timezone.dststart.timedelta;
|
|
delta_end = timezone.dstend.timedelta;
|
|
return(true);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Checks if a specified time falls in the Daylight Savings Time. |
|
|
//+------------------------------------------------------------------+
|
|
bool CTimeZoneInfo::IsDaylightSavingsTime(const ENUM_ZONE_ID placeId,const datetime time = TIME_NOW)
|
|
{
|
|
CHKERR(CTimeZoneInfo timezone(placeId, time),
|
|
return(false));
|
|
return(timezone.TimeDaylightSavings() != 0);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Checks if the given timezone supports the Daylight Savings Time. |
|
|
//+------------------------------------------------------------------+
|
|
bool CTimeZoneInfo::DaylightSavingsSupported(const ENUM_ZONE_ID placeId)
|
|
{
|
|
CHKERR(CTimeZoneInfo timezone(placeId, TIME_NOW),
|
|
return(false));
|
|
const int lastYear = GetYear(timezone.TimeLocal()) - 1;
|
|
CHKERR(CTimeZoneInfo winter(placeId, Date(lastYear,1,1)),
|
|
return(false));
|
|
CHKERR(CTimeZoneInfo summer(placeId, Date(lastYear,7,1)),
|
|
return(false));
|
|
return(summer.TimeDaylightSavings() != winter.TimeDaylightSavings());
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Get the current local time for the specified time zone. |
|
|
//+------------------------------------------------------------------+
|
|
datetime CTimeZoneInfo::GetCurrentTimeForPlace(const ENUM_ZONE_ID placeId)
|
|
{
|
|
CHKERR(CTimeZoneInfo place(placeId, TIME_NOW),
|
|
return(INT_MIN));
|
|
return(place.TimeLocal());
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Return a string of the local time with TZ/DST and timezone name, |
|
|
//| formatted like "Mon, 2024.02.26 20:24:17 GMT+11 DST [Sydney]". |
|
|
//| If 'secs' is FALSE time seconds will not be added to the result. |
|
|
//| If 'tzname' is FALSE timezone name is not added to the result. |
|
|
//+------------------------------------------------------------------+
|
|
string CTimeZoneInfo::FormatTimeForPlace(const datetime time, const ENUM_ZONE_ID placeId, bool secs = true, bool tzname = true)
|
|
{
|
|
CHKERR(CTimeZoneInfo place(placeId, time),
|
|
return(NULL));
|
|
return(place.ToString(secs, tzname));
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the total tz offset (UTC+DST) from GMT, for the given |
|
|
//| timezone, at the specified local time in timezone. |
|
|
//| Note: time = 0, means the "current" local time in timezone, now. |
|
|
//+------------------------------------------------------------------+
|
|
int CTimeZoneInfo::TimeGMTOffset(const ENUM_ZONE_ID placeId, const datetime time = TIME_NOW)
|
|
{
|
|
//--- the given local time may be invalid and does not correspond to
|
|
//--- any existing time in this place e.g., due to DST transitions.
|
|
CHKERR(CTimeZoneInfo place(placeId, time),
|
|
return(INT_MIN));
|
|
return(place.TimeGMTOffset());
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns correction for Daylight Savings Time in seconds, for the |
|
|
//| specified timezone at the specified local time, if the switch |
|
|
//| to summer time has been made. It depends on the time settings |
|
|
//| of that timezone. If switch to winter (standard) time has been |
|
|
//| made, or if the timezone does not observe DST, it returns 0. |
|
|
//+------------------------------------------------------------------+
|
|
int CTimeZoneInfo::TimeDaylightSavings(const ENUM_ZONE_ID placeId, const datetime time = TIME_NOW)
|
|
{
|
|
CHKERR(CTimeZoneInfo place(placeId, time),
|
|
return(INT_MIN));
|
|
return(place.TimeDaylightSavings());
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Convert local time from one time zone to another time zone. |
|
|
//+------------------------------------------------------------------+
|
|
datetime CTimeZoneInfo::ConvertTimeForPlace(const datetime time, const ENUM_ZONE_ID placeId, const ENUM_ZONE_ID destinationId)
|
|
{
|
|
CHKERR(CTimeZoneInfo place(placeId, time),
|
|
return(INT_MIN));
|
|
return(place.ConvertLocalTime(destinationId));
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Convert local time in this time zone to a different time zone. |
|
|
//+------------------------------------------------------------------+
|
|
datetime CTimeZoneInfo::ConvertLocalTime(const ENUM_ZONE_ID destinationId) const
|
|
{
|
|
/**
|
|
* To convert time in New York to corresponding time in London:
|
|
* tGMT = tNYC - (OFF_NYC + DST_NYC);
|
|
* tLON = tGMT + (OFF_LON + DST_LON);
|
|
*/
|
|
const datetime tGMT = this.TimeUTC();
|
|
|
|
CHKERR(CTimeZoneInfo dest(destinationId),
|
|
return(INT_MIN));
|
|
|
|
//--- objects automatically instantiated with UTC offsets
|
|
datetime tDest = tGMT + dest.timezone.offset;
|
|
|
|
//--- suspend errors before SetLocalTime() call
|
|
m_suspend_errors = true;
|
|
|
|
//--- populate utc and dst offsets in destination for this local time
|
|
if(!dest.SetLocalTime(tDest))
|
|
{
|
|
//--- forward invalid time if falls within a DST+1 skipped hour
|
|
dest.SetLocalTime(tDest + 1*HOURSECS);
|
|
}
|
|
|
|
//--- resume errors after SetLocalTime() call
|
|
m_suspend_errors = false;
|
|
|
|
//--- add utc + dst offsets to get the adjusted local time for destination
|
|
tDest = tGMT + dest.timezone.totaloffset;
|
|
//---
|
|
return(tDest);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Defines a time zone that is not found in the library, and sets |
|
|
//| its name, an offset from GMT and a daylight schedule identifier. |
|
|
//| baseGMTOffset : must be a whole multiple of 30 minutes. |
|
|
//| dstSchedule : must be less than or equal to ZONE_ID_UTC |
|
|
//| NB: dstSchedule = ZONE_ID_UTC means the custom timezone does |
|
|
//| not observe Daylight Savings Time, i.e., DST isn't not applied. |
|
|
//| |
|
|
//| The custom timezone can be useful for certain time conversions, |
|
|
//| in which ZONE_ID_CUSTOM is passed as parameter to class methods. |
|
|
//+------------------------------------------------------------------+
|
|
bool CTimeZoneInfo::SetCustomTimeZone(
|
|
const string name = "Custom",
|
|
const int baseGMTOffset = 0,
|
|
const ENUM_ZONE_ID dstSchedule = ZONE_ID_UTC
|
|
)
|
|
{
|
|
//--- fails if the baseGMTOffset parameter is greater than 14 hours or less
|
|
//--- than -14 hours, or does not represent a whole multiple of 30 minutes.
|
|
if(!StringLen(name)
|
|
|| (MathAbs(baseGMTOffset) > 14 * HOURSECS)
|
|
|| (baseGMTOffset % 1800 != 0)
|
|
|| (dstSchedule > ZONE_ID_UTC))
|
|
{
|
|
PrintFormat(">>Error: SetCustomTimeZone cannot properly set custom timezone with baseGMTOffset"+
|
|
" = %i and dstSchedule = %s", baseGMTOffset, EnumToString(dstSchedule));
|
|
SetUserError(ERR_USER_WRONG_PARAMETERS);
|
|
return(false);
|
|
}
|
|
//---
|
|
m_custom_name = name;
|
|
m_custom_offset = baseGMTOffset;
|
|
m_custom_rule_id = dstSchedule;
|
|
return(true);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Sets the option of using XAUUSD (Gold) symbol to estimate the |
|
|
//| server's TZ/DST by analysis of H1 quotes history. |
|
|
//| TRUE : search for and load Gold symbol (default behavior). |
|
|
//| FALSE : use the symbol of the current chart. |
|
|
//| |
|
|
//| XAUUSD can provide more reliable results (esp., for brokers |
|
|
//| that follow EU DST schedule) on weeks during US DST and EU |
|
|
//| DST schedules are out of sync (March and late October). |
|
|
//| if your broker follows the US DST schedule, or no schedule |
|
|
//| at all, then using the chart symbol is Ok. |
|
|
//| |
|
|
//| Note: as a side effect that XAUUSD starts an hour after Forex, |
|
|
//| dst switches occur one hour later (only in the strategy tester). |
|
|
//+------------------------------------------------------------------+
|
|
void CTimeZoneInfo::SetUsingGoldSymbol(const bool enabled = true)
|
|
{
|
|
m_using_xauusd = enabled;
|
|
//--- removes all keys and values from the map.
|
|
m_serveroffset.Clear();
|
|
m_serverdst.Clear();
|
|
//--- force a new search for the symbol
|
|
m_symbol = enabled ? FindSymbol("XAUUSD") : _Symbol;
|
|
//---
|
|
Print("CTimeZoneInfo >> Using Gold symbol option: ", m_using_xauusd);
|
|
Print("CTimeZoneInfo >> Loaded symbol: ", m_symbol, ",H1");
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Return a string of the local time with TZ/DST and timezone name, |
|
|
//| formatted like "Mon, 2024.02.26 20:24:17 GMT+11 DST [Sydney]". |
|
|
//| If 'secs' is FALSE time seconds will not be added to the result. |
|
|
//| If 'tzname' is FALSE timezone name is not added to the result. |
|
|
//+------------------------------------------------------------------+
|
|
string CTimeZoneInfo::ToString(bool secs = true, bool tzname = true) const
|
|
{
|
|
return(StringFormat("%s GMT%+g %s%s", t2s(time.localtime, TIME_DATE + (secs ? TIME_SECONDS : TIME_MINUTES)),
|
|
timezone.totaloffset / (double)HOURSECS,
|
|
timezone.dst ? "DST" : "STD",
|
|
tzname ? " ["+m_name+"]" : ""));
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Estimates server TZ offset (UTC+DST) from GMT at a given time. |
|
|
//| see https://www.mql5.com/en/code/48291 |
|
|
//+------------------------------------------------------------------+
|
|
int CTimeZoneInfo::HistoryServerGMTOffset(const datetime srvtime)
|
|
{
|
|
static datetime last_sunday = -1;
|
|
static int last_result = INT_MIN;
|
|
|
|
#ifdef PRINT_TZ_DETAILS
|
|
Debug(StringFormat("** Estimating server GMT offset of '%s' server for %s from %s,H1 quotes", AccountInfoString(ACCOUNT_SERVER), t2s(srvtime), m_symbol));
|
|
#endif
|
|
|
|
if(StartOfWeek(srvtime) == last_sunday && m_serveroffset.Count() > 0)
|
|
{
|
|
#ifdef PRINT_TZ_DETAILS
|
|
Debug(StringFormat("last_sunday: %s -> %d (GMT%+g)", t2s(last_sunday), last_result, last_result/(double)HOURSECS));
|
|
#endif
|
|
|
|
return(last_result);
|
|
}
|
|
|
|
//--- start at the last bar if 'srvtime' falls in the last weekend or holidays (no FstBarWk yet)
|
|
const datetime lastbar = (datetime)SeriesInfoInteger(m_symbol, PERIOD_H1, SERIES_LASTBAR_DATE);
|
|
|
|
datetime curtime = srvtime;
|
|
if(curtime == 0 || curtime > lastbar)
|
|
{
|
|
curtime = lastbar;
|
|
}
|
|
|
|
const datetime sunday = StartOfWeek(curtime); // the weekend "Sunday 00:00" before this time
|
|
|
|
//--- search this week in the broker gmt offset cache
|
|
int serveroffset = INT_MIN;
|
|
if(!m_serveroffset.TryGetValue(sunday, serveroffset))
|
|
{
|
|
const datetime firstbar = (datetime) SeriesInfoInteger(m_symbol, PERIOD_H1, SERIES_FIRSTDATE);
|
|
|
|
//--- weekend could be modified, if recursion had skipped to previous Sundays
|
|
datetime weekend = sunday;
|
|
|
|
if(weekend >= firstbar)
|
|
{
|
|
//--- find the time of the first hourly bar after this weekend "Sunday 00:00"
|
|
const datetime FstBarWk = FirstBarOfWeek(weekend);
|
|
if(FstBarWk == WRONG_VALUE)
|
|
{
|
|
Debug(StringFormat("Error in FirstBarOfWeek() = WRONG_VALUE, srvtime = %s", t2s(sunday), t2s(srvtime)));
|
|
last_result = serveroffset = INT_MIN;
|
|
last_sunday = -1;
|
|
}
|
|
else
|
|
{
|
|
//--- Forex pairs start Sun 17:00 NY. Gold starts an hour later.
|
|
int beginNYC = 17 * HOURSECS;
|
|
if(StringFind(m_symbol, "XAU") == 0 || StringFind(m_symbol, "GOLD") == 0)
|
|
{
|
|
beginNYC = 18 * HOURSECS;
|
|
}
|
|
//--- US summer starts on the second Sunday of March (GMT-4),
|
|
//--- and ends on the first Sunday of November (GMT-5).
|
|
int iYear = GetYear(weekend);
|
|
datetime dst_start = Date(iYear, 03, 08); // the second Sunday of March for the US switch
|
|
datetime dst_end = Date(iYear, 11, 01); // the first Sunday of November for the US switch
|
|
datetime tNYC = weekend + beginNYC; // Sun, 18:00 NYC should co-incide with broker's 'FstBarWk' on GOLD chart.
|
|
datetime tGMT = tNYC - ((weekend>=dst_start && weekend<dst_end) ? -4 : -5) * HOURSECS;
|
|
serveroffset = (int)(FstBarWk - tGMT);
|
|
serveroffset = (int) MathRound(serveroffset/1800.)*1800; // round offset to multiples of 30-minutes
|
|
|
|
#ifdef PRINT_TZ_DETAILS
|
|
Debug(StringFormat("FstBarWk = %s", t2s(FstBarWk)));
|
|
Debug(StringFormat("tNYC = %s (GMT%+d)", t2s(tNYC), (tNYC>=dst_start && tNYC<dst_end) ? -4 : -5));
|
|
Debug(StringFormat("tGMT = %s", t2s(tGMT)));
|
|
Debug(StringFormat("m_serveroffset.Add(%s, %d (GMT%+g))", t2s(sunday), serveroffset, serveroffset/(double)HOURSECS));
|
|
#endif
|
|
|
|
//--- add this week to the server gmt offset cache
|
|
m_serveroffset.Add(sunday, serveroffset);
|
|
}
|
|
}
|
|
}
|
|
#ifdef PRINT_TZ_DETAILS
|
|
else
|
|
{
|
|
Debug(StringFormat("m_serveroffset.TryGetValue(%s) -> %d (GMT%+g)", t2s(sunday), serveroffset, serveroffset/(double)HOURSECS));
|
|
}
|
|
#endif
|
|
|
|
//--- remember the server gmt offset of this week
|
|
if(serveroffset != INT_MIN)
|
|
{
|
|
last_sunday = sunday;
|
|
last_result = serveroffset;
|
|
}
|
|
|
|
return(serveroffset);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Estimate server time DST mode correction from H1 quotes history |
|
|
//| Courtesy of Stanislav Korotky https://www.mql5.com/en/code/52557 |
|
|
//+------------------------------------------------------------------+
|
|
int CTimeZoneInfo::HistoryServerDaylightSavings(const datetime srvtime)
|
|
{
|
|
static datetime last_sunday = -1;
|
|
static int last_result = INT_MIN;
|
|
|
|
#ifdef PRINT_TZ_DETAILS
|
|
Debug(StringFormat("** Estimating DST adjustment of '%s' server for %s from %s,H1 quotes", AccountInfoString(ACCOUNT_SERVER), t2s(srvtime), m_symbol));
|
|
#endif
|
|
|
|
if(StartOfWeek(srvtime) == last_sunday && m_serverdst.Count() > 0)
|
|
{
|
|
#ifdef PRINT_TZ_DETAILS
|
|
Debug(StringFormat("last_sunday: %s -> %d (%s)", t2s(last_sunday), last_result, last_result ? "DST" : "STD"));
|
|
#endif
|
|
|
|
return(last_result);
|
|
}
|
|
|
|
//--- start at the last bar if 'srvtime' falls in the last weekend or holidays (no FstBarWk yet)
|
|
const datetime lastbar = (datetime)SeriesInfoInteger(m_symbol, PERIOD_H1, SERIES_LASTBAR_DATE);
|
|
|
|
datetime curtime = srvtime;
|
|
if(curtime == 0 || curtime > lastbar)
|
|
{
|
|
curtime = lastbar;
|
|
}
|
|
|
|
const datetime sunday = StartOfWeek(curtime); // the weekend "Sunday 00:00" before this time
|
|
|
|
//--- search this week in the broker dst correction cache
|
|
int serverDST = INT_MIN;
|
|
if(!m_serverdst.TryGetValue(sunday, serverDST))
|
|
{
|
|
const datetime firstbar = (datetime) SeriesInfoInteger(m_symbol, PERIOD_H1, SERIES_FIRSTDATE);
|
|
const datetime firstdate = (datetime) fmax(firstbar, (sunday - 1 * YEARSECS));
|
|
const int threshold = 52 / 4; // a quarter of weeks in a year
|
|
int hours[24] = {};
|
|
int current = INT_MIN;
|
|
|
|
//--- for each weekend "Sunday 00:00" starting at last Sunday and going one year back.
|
|
for(datetime weekend = sunday; weekend >= firstdate; weekend -= WEEKSECS)
|
|
{
|
|
//--- find the time of the first hourly bar after that weekend "Sunday 00:00"
|
|
datetime FstBarWk = FirstBarOfWeek(weekend);
|
|
if(FstBarWk == WRONG_VALUE)
|
|
{
|
|
Debug(StringFormat("Error in FirstBarOfWeek() = WRONG_VALUE, srvtime = %s", t2s(sunday), t2s(srvtime)));
|
|
|
|
last_result = serverDST = INT_MIN;
|
|
last_sunday = -1;
|
|
return(INT_MIN);
|
|
}
|
|
|
|
//--- lets analyze the first H1 bar of the trading week (the first bar after weekend)
|
|
//--- now, check if the server time falls in the US Daylight Savings Time
|
|
if(CTimeZoneInfo::IsDaylightSavingsTime(ZONE_ID_NEWYORK, FstBarWk))
|
|
{
|
|
//--- compensate the effect of US switch on the server time by adding +1 hour
|
|
FstBarWk += 1*HOURSECS;
|
|
}
|
|
|
|
//--- find out an open hour for the first bar after weekend
|
|
const int open_hour = GetHour(FstBarWk);
|
|
|
|
//--- collect stats for opening hours
|
|
hours[open_hour]++;
|
|
|
|
//--- save the opening hour of the current week only
|
|
if(current == INT_MIN)
|
|
{
|
|
current = open_hour;
|
|
}
|
|
}
|
|
|
|
#ifdef PRINT_TZ_DETAILS
|
|
Debug(StringFormat("Week opening hours stats [%s - %s]:", t2s(firstdate), t2s(sunday)));
|
|
ArrayPrint(hours);
|
|
#endif
|
|
|
|
if(current != INT_MIN)
|
|
{
|
|
serverDST = 0;
|
|
if(hours[current] > threshold && hours[WRAP24(current - 1)] > threshold)
|
|
{
|
|
//--- DST is probably enabled for current week
|
|
serverDST = 1*HOURSECS;
|
|
}
|
|
|
|
#ifdef PRINT_TZ_DETAILS
|
|
Debug(StringFormat("Current week opening hour: %d", current));
|
|
Debug(StringFormat("m_serverdst.Add(%s, %d (%s))", t2s(sunday), serverDST, serverDST ? "DST" : "STD"));
|
|
#endif
|
|
|
|
//--- add this week to the server dst correction cache
|
|
m_serverdst.Add(sunday, serverDST);
|
|
}
|
|
}
|
|
#ifdef PRINT_TZ_DETAILS
|
|
else
|
|
{
|
|
Debug(StringFormat("m_serverdst.TryGetValue(%s) -> %d (%s)", t2s(sunday), serverDST, serverDST ? "DST" : "STD"));
|
|
}
|
|
#endif
|
|
|
|
//--- remember the server dst correction of this week
|
|
if(serverDST != INT_MIN)
|
|
{
|
|
last_sunday = sunday;
|
|
last_result = serverDST;
|
|
}
|
|
|
|
return(serverDST);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the time of the first H1 bar after the given weekend. |
|
|
//| The function skips abnormal trading weeks around holidays. |
|
|
//+------------------------------------------------------------------+
|
|
datetime CTimeZoneInfo::FirstBarOfWeek(datetime &weekend, int depthLimit = 8)
|
|
{
|
|
if(m_symbol==NULL)
|
|
{
|
|
//--- Gold has accurate start of trading weeks (esp. EU DST brokers)
|
|
m_symbol = m_using_xauusd ? FindSymbol("XAUUSD") : _Symbol;
|
|
|
|
#ifdef PRINT_TZ_DETAILS
|
|
Print("CTimeZoneInfo >> Using Gold symbol option: ", m_using_xauusd);
|
|
Print("CTimeZoneInfo >> Loaded symbol: ", m_symbol, ",H1");
|
|
#endif
|
|
}
|
|
|
|
//--- find the first hourly bar after the specified weekend "Sunday 00:00"
|
|
const int barindex = iBarShift(m_symbol, PERIOD_H1, weekend, false);
|
|
const datetime bartime = iTime(m_symbol, PERIOD_H1, barindex - 1);
|
|
|
|
#ifdef PRINT_TZ_DETAILS
|
|
Debug(StringFormat("iBarShift(%s, %s)=%d, iTime(%d) = %s", m_symbol, t2s(weekend), barindex, barindex-1, t2s(bartime)));
|
|
#endif
|
|
|
|
if(barindex < 0 || depthLimit < 0)
|
|
{
|
|
if(MQLInfoInteger(MQL_PROGRAM_TYPE)!=PROGRAM_INDICATOR)
|
|
{
|
|
PrintFormat(">> iBarShift() FAILED: The requested date %s for %s,H1 is not found in the available history", t2s(weekend), m_symbol);
|
|
}
|
|
return(WRONG_VALUE);
|
|
}
|
|
|
|
//--- Skip long weekends or holidays (bartime > Monday 12:00 PM)
|
|
if(barindex == 0 || bartime > weekend + 36 * HOURSECS)
|
|
{
|
|
#ifdef PRINT_TZ_DETAILS
|
|
Debug(StringFormat("bartime = %s", t2s(bartime)));
|
|
Debug(StringFormat("weekend = %s", t2s(weekend)));
|
|
Debug("Detected long weekend / Christmas holidays, skip to previous weekend:");
|
|
#endif
|
|
weekend -= WEEKSECS; // change 'weekend' to the previous Sunday 00:00
|
|
return(FirstBarOfWeek(weekend, depthLimit - 1));
|
|
}
|
|
//---
|
|
return(bartime);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Find and select a symbol for further working with information. |
|
|
//+------------------------------------------------------------------+
|
|
string CTimeZoneInfo::FindSymbol(string symbol)
|
|
{
|
|
bool symbol_found = false; // Store if we have found a valid symbol or not yet
|
|
|
|
//--- if the symbol is found in the list of standard symbols on the server
|
|
bool custom = false;
|
|
if(SymbolExist(symbol, custom) && !custom)
|
|
{
|
|
symbol_found = true;
|
|
}
|
|
|
|
//--- First try to find if the passed symbol exists
|
|
if(symbol_found == false)
|
|
for(int i=0; i < SymbolsTotal(0); i++)
|
|
if(StringFind(SymbolName(i,0),symbol)==0)
|
|
{
|
|
symbol = SymbolName(i,0); // symbol may have suffix
|
|
symbol_found = true;
|
|
break;
|
|
}
|
|
//--- Then try to find a symbol equal to "GOLD"
|
|
if(symbol_found == false)
|
|
for(int i=0; i < SymbolsTotal(0); i++)
|
|
if(SymbolName(i,0)=="GOLD")
|
|
{
|
|
symbol = SymbolName(i,0); // symbol may have suffix
|
|
symbol_found = true;
|
|
break;
|
|
}
|
|
//--- Then try to find a symbol that starts with GOLD
|
|
if(symbol_found == false)
|
|
for(int i=0; i < SymbolsTotal(0); i++)
|
|
if(StringFind(SymbolName(i,0),"GOLD")==0)
|
|
{
|
|
symbol = SymbolName(i,0); // symbol may have suffix
|
|
symbol_found = true;
|
|
break;
|
|
}
|
|
//--- And finally use the current chart's symbol for brokers that do not provide gold
|
|
if(symbol_found == false)
|
|
{
|
|
symbol = Symbol();
|
|
}
|
|
|
|
//--- we have found a valid symbol
|
|
SymbolSelect(symbol, true);
|
|
|
|
//--- Check the H1 history is loaded
|
|
CheckLoadHistory(symbol, PERIOD_H1);
|
|
|
|
return(symbol);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Checks presence of the history for symbol and timeframe. |
|
|
//| http://www.mql5.com/en/docs/series/timeseries_access |
|
|
//+------------------------------------------------------------------+
|
|
bool CTimeZoneInfo::CheckLoadHistory(const string symbol, const ENUM_TIMEFRAMES period)
|
|
{
|
|
//--- indicator shouldn't load its own symbol and timeframe
|
|
if(MQLInfoInteger(MQL_PROGRAM_TYPE)==PROGRAM_INDICATOR && Period()==period && Symbol()==symbol)
|
|
return(false);
|
|
//---
|
|
datetime times[];
|
|
datetime first_date=0;
|
|
int copied=0;
|
|
uint tick=GetTickCount();
|
|
//--- wait for timeseries build
|
|
while(!SeriesInfoInteger(symbol,period,SERIES_SYNCHRONIZED) && !IsStopped() && (GetTickCount()-tick<=6000))
|
|
Sleep(5);
|
|
//--- second attempt
|
|
return(SeriesInfoInteger(symbol,period,SERIES_FIRSTDATE,first_date)
|
|
&& first_date>0
|
|
&& CopyTime(symbol,period,first_date+PeriodSeconds(period),1,times));
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Create a datetime value from the given year, month and day. |
|
|
//| https://www.mql5.com/en/forum/393227/page254#comment_53104384 |
|
|
//| Limit: Year must be <= 2100 |
|
|
//+------------------------------------------------------------------+
|
|
datetime CTimeZoneInfo::Date(
|
|
const int year, // Year
|
|
const int mon, // Month
|
|
const int day, // Day
|
|
const int hour = 0, // Hour
|
|
const int min = 0, // Minutes
|
|
const int sec = 0 // Seconds
|
|
)
|
|
{
|
|
// MqlDateTime dt = {year, mon, day, hour, min, sec}
|
|
// return StructToTime(dt);
|
|
|
|
static const uint Months[] = {0, 11512676, 11512180, 11511728, 11511232, 11510750, 11510256,
|
|
11509774, 11509280, 11508781, 11508302, 11507806, 11507326
|
|
};
|
|
return (((year * 5844 - Months[mon]) >> 4) + day - 1) * DAYSECS + (hour * HOURSECS + min * MINSECS + sec);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Return the start of a week for the given date. |
|
|
//| By default, the week starts on Sunday, unless superseded. |
|
|
//| |
|
|
//| StartOfWeek (D'2017.08.03 11:30') => 2017.07.30 00:00:00 |
|
|
//+------------------------------------------------------------------+
|
|
datetime CTimeZoneInfo::StartOfWeek(const datetime t, const bool StartsOnMonday = false)
|
|
{
|
|
return (t - (t + (4 - StartsOnMonday) * DAYSECS) % WEEKSECS);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the zero-based absolute week number since 1 Jan 1970 |
|
|
//| By default, the week starts on Sunday, unless superseded. |
|
|
//+------------------------------------------------------------------+
|
|
int CTimeZoneInfo::WeekIndex(const datetime t, const bool StartsOnMonday = false)
|
|
{
|
|
return (int)((t + (4 - StartsOnMonday) * DAYSECS) / WEEKSECS); // adding duration of 4 days to get weeks aligned by Sundays
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Day of the week as integer (0-Sunday, 1-Monday, ... ,6-Saturday) |
|
|
//+------------------------------------------------------------------+
|
|
int CTimeZoneInfo::DayOfWeek(const datetime t)
|
|
{
|
|
// MqlDateTime st;
|
|
// TimeToStruct(t, st);
|
|
// return(st.day_of_week);
|
|
return ((int)((uint)t / DAYSECS + 4) % 7);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns year as integer of the given date (e.g., 2019) |
|
|
//| https://www.mql5.com/ru/forum/170952/page251#comment_53071746 |
|
|
//+------------------------------------------------------------------+
|
|
int CTimeZoneInfo::GetYear(const datetime t)
|
|
{
|
|
// MqlDateTime st;
|
|
// TimeToStruct(t, st);
|
|
// return(st.year);
|
|
return ((int)(((uint)t / DAYSECS * 4 + 2) / 1461) + 1970);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Return hour of day as integer (0..23) |
|
|
//+------------------------------------------------------------------+
|
|
int CTimeZoneInfo::GetHour(const datetime t)
|
|
{
|
|
// MqlDateTime st;
|
|
// TimeToStruct(t, st);
|
|
// return(st.hour);
|
|
return (int)((uint)t / HOURSECS) % 24;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Formats time with the weekday name => "Wed, 2023.02.14 01:59" |
|
|
//+------------------------------------------------------------------+
|
|
string CTimeZoneInfo::t2s(const datetime t, const int mode = TIME_DATE | TIME_MINUTES)
|
|
{
|
|
const string days[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
|
|
const int i = DayOfWeek(t);
|
|
return days[i % 7] + ", " + TimeToString(t, mode);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Debugging support |
|
|
//+------------------------------------------------------------------+
|
|
void CTimeZoneInfo::PrintObject(void) const
|
|
{
|
|
#define PRINT(A) Print(#A + " = ", (A), " ["+typename(A)+"]")
|
|
|
|
PRINT(m_name);
|
|
PRINT(m_id);
|
|
//--- time struct
|
|
PRINT(time.utctime);
|
|
PRINT(time.localtime);
|
|
//--- timezone struct
|
|
PRINT(timezone.offset);
|
|
PRINT(timezone.dst);
|
|
PRINT(timezone.totaloffset);
|
|
//--- dststart struct
|
|
PRINT(dststart.dstbias);
|
|
PRINT(dststart.timedelta);
|
|
PRINT(dststart.oldlocaltime);
|
|
PRINT(dststart.newlocaltime);
|
|
//--- dstend struct
|
|
PRINT(dstend.dstbias);
|
|
PRINT(dstend.timedelta);
|
|
PRINT(dstend.oldlocaltime);
|
|
PRINT(dstend.newlocaltime);
|
|
|
|
#undef PRINT
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
|
|
#undef TIME_NOW
|
|
#undef WRAP24
|
|
#undef Debug
|
|
#undef CHKERR
|
|
|
|
|
|
#endif // #ifndef TIMEZONEINFO_UNIQUE_HEADER_ID_H
|