Detecting the broker's GMT offset and daylight-time rule from price history in MQL5
Introduction
An Expert Advisor that trades the New York open at 09:30, or closes before the CME daily halt at 17:00 New York, needs one number that MQL5 does not give reliably: the offset between the broker's server clock and UTC. Brokers use different clocks. Some run on UTC all year. Many run on GMT+2 in winter and GMT+3 in summer, following either the US or the European daylight-time calendar. Because the US and Europe change their clocks on different dates, a fixed difference between server time and New York time (7 hours on a GMT+2/+3 broker) is right most of the year, but wrong by one hour for about three weeks every spring and one week every autumn.
On a live chart, the terminal can tell us the offset. In the Strategy Tester it cannot. This article shows a method that works the same in both: read the offset and the daylight-time rule from the price history itself, using the weekly close as a clock reference. The result is a small include file with no trading logic, functions that convert New York hours to server time and back, a script that builds test symbols with different server clocks, a test Expert Advisor and a panel indicator, all free to download in the MQL5 CodeBase (the link is at the end).
1. The problem
MQL5 has three time functions that matter here:
- TimeTradeServer() returns the current time of the trade server.
- TimeGMT() returns GMT, computed from the local computer clock and its time zone setting.
- TimeCurrent() returns the time of the last quote received.
On a live chart the offset is simply the difference between the first two, rounded to whole hours:
int LiveClockOffset()
{
datetime srv = TimeTradeServer(), gmt = TimeGMT();
if(srv <= 0 || gmt <= 0) return OFFSET_UNKNOWN;
int h = (int)MathRound(((double)srv - (double)gmt) / 3600.0);
return (h < -12 || h > 14) ? OFFSET_UNKNOWN : h;
} This has two weaknesses. It depends on the computer: a VPS with a wrong clock or a wrong time zone gives a wrong offset, and nothing in the terminal warns about it. And in the Strategy Tester it is useless: the documentation states that during testing TimeGMT() equals the simulated TimeTradeServer(), so the difference is always zero. A program that converts New York hours with this offset trades at the right hour live and at the wrong hour in every backtest on a GMT+2 broker.
The usual answer is an input with a fixed difference to New York time. It is right in winter and in summer, and wrong in the gaps between the US and the European clock changes:

Figure 1. The server time of the 09:30 New York open in 2026, on three broker clocks. The US-rule and EU-rule clocks agree except in the weeks marked in red.
In 2026 the US moves to summer time on 8 March and back on 1 November; Europe moves on 29 March and 25 October. A GMT+2/+3 broker that follows the European calendar shows the New York open at 16:30 server time in winter and in summer, but at 15:30 from 8 to 29 March and from 25 October to 1 November. An EA with a fixed "New York = server minus 7 hours" waits for 16:30 and opens one hour late in those weeks, every year.
2. Daylight-time rules without external data
The rules themselves are simple enough to compute from the calendar. Two helpers do the work: the n-th Sunday of a month and the last Sunday of a month, both returned at a given UTC hour.
datetime NthSundayUTC(int year, int month, int nth, int hourUTC)
{
MqlDateTime d; ZeroMemory(d); d.year = year; d.mon = month; d.day = 1;
datetime first = StructToTime(d);
MqlDateTime f; TimeToStruct(first, f);
int add = (7 - f.day_of_week) % 7;
return first + (add + 7 * (nth - 1)) * 86400 + hourUTC * 3600;
}
datetime LastSundayUTC(int year, int month, int hourUTC)
{
MqlDateTime d; ZeroMemory(d); d.year = year; d.mon = month + 1; d.day = 1;
if(month == 12) { d.year = year + 1; d.mon = 1; }
datetime next = StructToTime(d);
MqlDateTime f; TimeToStruct(next - 86400, f);
return next - 86400 - f.day_of_week * 86400 + hourUTC * 3600;
} With them:
- United States: summer time from the second Sunday of March at 02:00 local time (07:00 UTC) to the first Sunday of November at 02:00 local time (06:00 UTC).
- European Union and the United Kingdom: from the last Sunday of March at 01:00 UTC to the last Sunday of October at 01:00 UTC, the same instant everywhere in Europe.
- Sydney (New South Wales): from the first Sunday of October at 02:00 standard time to the first Sunday of April at 03:00 daylight time, both 16:00 UTC on the Saturday before. (Queensland, Western Australia and the Northern Territory do not change their clocks.)
int NewYorkOffset(datetime utc)
{
MqlDateTime d; TimeToStruct(utc, d);
return (utc >= NthSundayUTC(d.year, 3, 2, 7) && utc < NthSundayUTC(d.year, 11, 1, 6)) ? -4 : -5;
}
bool EUSummer(datetime utc)
{
MqlDateTime d; TimeToStruct(utc, d);
return (utc >= LastSundayUTC(d.year, 3, 1) && utc < LastSundayUTC(d.year, 10, 1));
}
int LondonOffset(datetime utc) { return EUSummer(utc) ? 1 : 0; } These rules have been stable since 2007 in the US, since 1996 in Europe and since 2008 in New South Wales. In this article "summer time" and "daylight time" mean the same thing. They are hard-coded on purpose: an include file that downloads a time zone table would need WebRequest permissions and would not work in the tester or in the Market's automatic validation.
3. The weekly close as a clock reference
The trick is to find, in the price history, an event that happens at a known UTC instant every week. For CME index futures, and for most FX symbols and index CFDs that follow the futures session, that event is the weekly close: Friday 17:00 New York. Any weekly close after 16:00 New York ends in the same H1 bar, so small differences between brokers do not matter. The market then stays closed until Sunday evening, so the history shows a gap of about two days.
In the H1 history (read with CopyRates, whatever the chart's own timeframe), the last bar before the gap opens at 16:00 New York and ends at 17:00 New York. Its open time is in server time. Comparing the end of that bar with 17:00 New York, converted to UTC, gives the broker's offset for that week:
datetime weekClose = r[i + 1].time + 3600; // end of the last bar before the weekend MqlDateTime d; TimeToStruct(weekClose, d); d.hour = 17; d.min = 0; d.sec = 0; datetime ny17 = StructToTime(d); // the same date, 17:00 datetime utc17 = ny17 - NewYorkOffset(ny17 + 5 * 3600) * 3600; // 17:00 New York in UTC (ny17 + 5 h is close enough: clocks change on Sundays) long diff = (long)weekClose - (long)utc17; while(diff > 12 * 3600) diff -= 86400; // keep it within one day while(diff < -12 * 3600) diff += 86400; hw[k] = (int)MathRound((double)diff / 3600.0); // the offset of this weekend
Three details matter. The gap test (r[i].time - r[i + 1].time >= 36 hours) skips normal overnight halts and short holiday breaks. The while loops fold the difference into one day, because the server date of the close can be Friday or Saturday depending on the offset. And for each weekend we also record whether US and European summer time were in force at that instant, because the rule fitting in the next step needs it.
4. Fitting the rule
One weekend gives one offset. A year of weekends gives the rule. The include file tests every base offset from -12 to +14 with three rules (in practice -12 to +12: the folding step above limits each measured offset to plus or minus 12 hours, which covers every broker in use):
- fixed: the same offset all year;
- US: base, plus one hour during US summer time;
- EU: base, plus one hour during European summer time.
For each candidate it counts how many weekends it explains, separately for the 8 most recent weekends and for all of them (up to 60, a little over a year):
int bestRule = 0, bestBase = hw[0], bestScore = -1;
for(int rule = 0; rule < 3; rule++)
for(int base = -12; base <= 14; base++)
{
int fitRecent = 0, fitAll = 0;
for(int w = 0; w < k; w++)
if(base + (rule == 1 ? (usw[w] ? 1 : 0) : (rule == 2 ? (euw[w] ? 1 : 0) : 0)) == hw[w])
{
fitAll++;
if(w < 8) fitRecent++;
}
if(fitRecent == 0) continue;
int pref = (rule == 0 && base == 0) ? 3 : (rule == 1 ? 2 : (rule == 2 ? 1 : 0));
int score = fitRecent * 1000 + fitAll * 10 + pref;
if(score > bestScore) { bestScore = score; bestRule = rule; bestBase = base; }
} The score puts the recent weekends first, so a broker that changes its clock is followed once the new clock explains most of the last eight weekends (about five weekends later), and it uses the whole year to choose between rules that the recent weeks cannot separate. In mid-winter or mid-summer each of the three rules can explain the recent weekends with a suitable base; only the weekends in the gaps tell US from EU, and only the weekends on either side of a change tell a daylight-time rule from a fixed one. When everything is still tied, a small preference decides: UTC first (a common clock among futures brokers), then the US rule, then the EU rule.
The current offset then follows from the winning rule, evaluated at the current instant:
datetime utcNow = srv - RuleOffset(bestRule, bestBase, srv) * 3600; return RuleOffset(bestRule, bestBase, utcNow);
The global string g_histRule keeps the name of the rule ("fixed all year", "follows US summer time", "follows European summer time") for display.
5. Live cross-check
On a live chart we now have two independent answers: the terminal's (LiveClockOffset) and the history's (HistoryClockOffset). When they agree, the program can trust either. When they disagree, the most common cause is not the broker: it is a computer clock that is wrong, or a time zone set to something other than what the clock shows. The example indicator shows both and prints a warning:
if(live != OFFSET_UNKNOWN && g_hist != OFFSET_UNKNOWN && live != g_hist) txt += "WARNING: the two disagree. Check the computer's clock and time zone.\n";
HistoryClockOffset copies up to 10,416 H1 bars (62 weeks of a symbol that trades around the clock) and stops at 60 weekends, so it should not run on every tick. Once an hour on a timer is plenty, and once a minute while there is no answer yet; the offset changes twice a year.
6. Using the offset in an Expert Advisor
Knowing the offset is only half of the job. An Expert Advisor works in server time: TimeCurrent(), bar times, order expiration and the trade history are all server time. The trading rules, however, are usually written in New York time: open at 09:30, stop opening new trades at 15:30, and be flat before the daily halt at 17:00. Three small functions in the include file do the conversion:
// offset = the broker's GMT offset in hours (from LiveClockOffset or HistoryClockOffset)
datetime ServerToNY(datetime srv, int offset)
{
datetime utc = srv - offset * 3600;
return utc + NewYorkOffset(utc) * 3600;
}
// The server time of hour:minute New York, on the New York day that contains server time srv.
datetime NYClockToServer(datetime srv, int hour, int minute, int offset)
{
MqlDateTime d; TimeToStruct(ServerToNY(srv, offset), d);
d.hour = hour; d.min = minute; d.sec = 0;
datetime nyLocal = StructToTime(d);
datetime utc = nyLocal - NewYorkOffset(nyLocal + 5 * 3600) * 3600; // close enough: clocks change at 02:00 on Sundays
return utc + offset * 3600;
}
// True between fromH:fromM and toH:toM New York time (the same New York day).
bool InNYWindow(datetime srv, int offset, int fromH, int fromM, int toH, int toM)
{
datetime ny = ServerToNY(srv, offset);
MqlDateTime d; TimeToStruct(ny, d);
int m = d.hour * 60 + d.min;
return (m >= fromH * 60 + fromM && m < toH * 60 + toM);
} ServerToNY goes from server time to UTC with the broker offset, then from UTC to New York with the US calendar. NYClockToServer goes the other way: it takes the New York date of a server instant, sets the hour and minute, converts that New York time to UTC and adds the broker offset. InNYWindow is the test most programs need on every tick.
The conversion from New York local time to UTC has one subtle point. NewYorkOffset expects a UTC instant, but at that moment we only have the New York local time. Adding five hours turns the local time into something close enough to UTC: the error is at most one hour, and it can only matter in the repeated hour from 01:00 to 02:00 New York on the Sunday in November when the clocks go back. No index or currency market opens in that hour, so it does not affect trading rules.
A minimal pattern for an Expert Advisor caches the offset, refreshes it once an hour, and refuses to trade while it is unknown:
#include <BrokerClock.mqh>
int g_offset = OFFSET_UNKNOWN;
datetime g_offsetAt = 0;
int BrokerOffset()
{
datetime srv = TimeCurrent();
if(g_offsetAt > 0 && srv - g_offsetAt < (g_offset == OFFSET_UNKNOWN ? 60 : 3600))
return g_offset;
int hist = HistoryClockOffset(srv);
int live = MQLInfoInteger(MQL_TESTER) ? OFFSET_UNKNOWN : LiveClockOffset();
if(hist != OFFSET_UNKNOWN && live != OFFSET_UNKNOWN && hist != live)
PrintFormat("Broker clock: history says GMT%+d, terminal says GMT%+d. Check the computer clock.", hist, live);
g_offset = (hist != OFFSET_UNKNOWN) ? hist : live;
g_offsetAt = srv;
return g_offset;
}
void OnTick()
{
int off = BrokerOffset();
if(off == OFFSET_UNKNOWN)
return; // no clock, no trading
datetime srv = TimeCurrent();
if(InNYWindow(srv, off, 16, 55, 17, 0))
{
// close open positions before the daily halt
return;
}
if(!InNYWindow(srv, off, 9, 30, 15, 30))
return; // outside the entry window
datetime expiry = NYClockToServer(srv, 16, 0, off); // a pending order valid until 16:00 New York
// ... entry logic, using expiry as the order expiration (server time)
} Three choices in this pattern are deliberate. The history answer comes first, because it works the same way in the tester and live and does not depend on the computer. The terminal answer is only a fallback and a cross-check, and it is skipped in the tester, where it is always zero. And the offset is cached for an hour, which keeps the copy of 10,416 bars off the tick path; the first tick after a weekend always recomputes it, so a clock change on Sunday is in use from the first tick of the new week. (The panel indicator, which only runs live, shows both answers and prefers the terminal's.)
The detection reads the H1 history of the chart symbol (_Symbol). In an Expert Advisor that trades several symbols, attach it to a chart whose weekly close is at 17:00 New York (or give HistoryClockOffset a symbol parameter for CopyRates), as explained in the limits below, and use the same offset for all symbols of the same server.
7. Building test symbols with other server clocks
A clock detection that has only been tested on one broker has not really been tested. The Strategy Tester can run on custom symbols, so the same prices can be replayed on any server clock we want. The script ClockSymbols.mq5 (in the CodeBase) copies the M1 bars of a symbol from a broker that runs on UTC and writes three custom symbols with the same prices and shifted bar times: one on UTC, one on GMT+2/+3 following the US calendar and one on GMT+2/+3 following the European calendar.
//+------------------------------------------------------------------+
//| ClockSymbols.mq5: builds test symbols with other server clocks. |
//| Copies the M1 bars of a source symbol whose server runs on UTC |
//| and writes three custom symbols with the same prices: |
//| <prefix>.utc UTC all year |
//| <prefix>.us GMT+2 winter, GMT+3 during US summer time |
//| <prefix>.eu GMT+2 winter, GMT+3 during European summer time |
//| A program that reads the broker clock correctly must then trade |
//| at the same New York times on all three. |
//+------------------------------------------------------------------+
#property copyright "Article example"
#property version "1.00"
#property script_show_inputs
#include <BrokerClock.mqh>
input string InpSource = ""; // Source symbol on a UTC server ("" = chart symbol)
input datetime InpFrom = D'2025.09.15 00:00'; // From (should include a clock change)
input datetime InpTo = D'2026.05.16 00:00'; // To
input string InpPrefix = "CLOCK"; // Name prefix of the custom symbols
int TargetOffset(int rule, datetime utc)
{
if(rule == 1) return (NewYorkOffset(utc) == -4) ? 3 : 2;
if(rule == 2) return EUSummer(utc) ? 3 : 2;
return 0;
}
bool Build(string src, string dst, int rule, MqlRates &bars[])
{
bool isCustom = false;
if(SymbolExist(dst, isCustom))
{
if(!isCustom) { Print(dst, " exists and is not a custom symbol"); return false; }
SymbolSelect(dst, false);
if(!CustomSymbolDelete(dst)) { Print("cannot delete ", dst, ", error ", GetLastError()); return false; }
}
if(!CustomSymbolCreate(dst, "ClockTest", src)) { Print("cannot create ", dst, ", error ", GetLastError()); return false; }
// around-the-clock sessions: the gaps in the data are what the clock detection reads
for(int d = 0; d <= 6; d++)
{
CustomSymbolSetSessionQuote(dst, (ENUM_DAY_OF_WEEK)d, 0, 0, 24 * 3600);
CustomSymbolSetSessionTrade(dst, (ENUM_DAY_OF_WEEK)d, 0, 0, 24 * 3600);
}
int n = ArraySize(bars);
MqlRates r[];
ArrayResize(r, n);
for(int i = 0; i < n; i++)
{
r[i] = bars[i];
r[i].time += TargetOffset(rule, bars[i].time) * 3600; // source time is UTC
}
int put = CustomRatesReplace(dst, r[0].time, r[n - 1].time + 60, r);
PrintFormat("%s: %d bars written (%s to %s)", dst, put, TimeToString(r[0].time), TimeToString(r[n - 1].time));
SymbolSelect(dst, true);
return (put == n);
}
void OnStart()
{
string src = (InpSource == "") ? _Symbol : InpSource;
SymbolSelect(src, true);
MqlRates bars[];
ArraySetAsSeries(bars, false);
int n = -1, prev = -2;
for(int tries = 0; tries < 30 && !(n > 0 && n == prev); tries++) // wait until the history is loaded
{
prev = n;
n = CopyRates(src, PERIOD_M1, InpFrom, InpTo, bars);
Sleep(1000);
}
if(n <= 0) { Print("no M1 history for ", src); return; }
Build(src, InpPrefix + ".utc", 0, bars);
Build(src, InpPrefix + ".us", 1, bars);
Build(src, InpPrefix + ".eu", 2, bars);
} Notes on using it:
- The source must come from a server that runs on UTC, because the script takes the source bar times as UTC. For a source on another clock, subtract its own offset first.
- The period should include at least one clock change in spring or autumn; otherwise all three rules look the same.
- CustomRatesReplace writes the bars in one call. The sessions are set to the whole day because the custom symbol only needs to accept every bar; the weekend gap stays in the data, and the gap is what the detection reads.
- Close the terminal normally after the script has run. A terminal that is killed may not save the new custom symbols, and the tester then reports that the symbol does not exist.
- In the tester, use "1 minute OHLC" on these symbols: they have bars but no real ticks.
Run on the M1 bars of a futures contract from a UTC broker, from September 2025 to March 2026, it wrote 134,256 bars to each symbol. The last bar shows the shift: 20:59 on the UTC symbol, 23:59 on the US-rule symbol (US summer time had started on 8 March) and 22:59 on the EU-rule symbol (still winter time in Europe).
8. Verification
Two things need checking: the daylight-time math, and the detection.
The math. The test Expert Advisor writes the New York, London and Sydney offsets for every hour from 2024 through 2027 to a CSV file in the common Files folder, 35,064 instants in all. A short Python script compares them with the IANA time zone database (zoneinfo; on Windows it needs the tzdata package):
# check_tz.py: compares clock_tz.csv (written by ClockTest.mq5) with the IANA time zone database.
# Usage: python check_tz.py "<path to the common Files folder>\clock_tz.csv"
import csv, sys
from datetime import datetime, timezone
from zoneinfo import ZoneInfo
zones = {"new_york": ZoneInfo("America/New_York"),
"london": ZoneInfo("Europe/London"),
"sydney": ZoneInfo("Australia/Sydney")}
rows = bad = 0
with open(sys.argv[1], newline="") as f:
for r in csv.DictReader(f):
t = datetime.fromtimestamp(int(r["utc_unix"]), tz=timezone.utc)
rows += 1
for name, z in zones.items():
want = int(t.astimezone(z).utcoffset().total_seconds() // 3600)
if int(r[name]) != want:
bad += 1
print(t.isoformat(), name, "MQL5", r[name], "IANA", want)
print(rows, "instants,", bad, "differences") The result was zero differences, including the hours on both sides of every change.
The conversion. The same Expert Advisor converts 09:30 New York to server time and back for every day from 2024 through 2027, on three clocks (GMT+0, +2 and +3), and checks that the result is 09:30 again and inside the 09:30 to 16:00 window:
// round trip: 09:30 New York -> server -> New York, every day of 2024-2027, on three clocks
int bad = 0, n = 0;
int offs[3] = {0, 2, 3};
for(datetime t = D'2024.01.01 12:00'; t < D'2028.01.01'; t += 86400)
for(int j = 0; j < 3; j++)
{
datetime srv = NYClockToServer(t, 9, 30, offs[j]);
MqlDateTime ny; TimeToStruct(ServerToNY(srv, offs[j]), ny);
n++;
if(ny.hour != 9 || ny.min != 30 || !InNYWindow(srv, offs[j], 9, 30, 16, 0)) bad++;
}
PrintFormat("New York round trip: %d cases, %d errors", n, bad); Result: 4,383 cases, 0 errors.
The detection. The same Expert Advisor, run in the Strategy Tester, prints the detected clock once a week. It was run on three custom symbols built from the same Nasdaq-100 prices with three different server clocks (UTC all year, GMT+2/+3 on the US calendar, GMT+2/+3 on the European calendar), from February to May 2026, and on a real CME futures contract from a broker that runs on UTC:

Figure 2. Clock detected each week in the Strategy Tester, February to May 2026. Each series switches exactly in the week of its own calendar.
| Server clock | Detected | Change detected |
|---|---|---|
| UTC all year | GMT+0, fixed all year | none, correct |
| GMT+2/+3, US calendar | GMT+2, then GMT+3 | from the week of 9 March 2026, correct |
| GMT+2/+3, EU calendar | GMT+2, then GMT+3 | from the week of 30 March 2026, correct |
| Real futures contract, UTC broker | GMT+0, fixed all year | none, correct |
The UTC rows matter less, since UTC is also the tie-break default; the two GMT+2/+3 rows are the real test.
The symbols from section 7, built from a different contract, give the same answer. From February to 13 March 2026, the US-rule symbol is detected as GMT+2 until the week of 2 March and as GMT+3 from the week of 9 March; the EU-rule symbol stays at GMT+2, as it should, since its change only comes on 29 March. Both rules are named correctly from the first week ("follows US summer time", "follows European summer time"), because the history before February already contains the autumn change.
9. Limits
- Short histories. The method needs weekends that straddle a clock change to tell the rules apart. A symbol with only a few weeks of history, such as a quarterly futures contract tested on its own, may hold fewer than eight weekends and none in a gap. The tie-break then decides, and for an EU-rule broker in the gap weeks, or a fixed broker other than UTC in summer, it can be one hour off until more history exists. For backtests on such symbols, set the offset by hand.
- Early closes. On days when the market closes early, for example, the Friday after Thanksgiving, the last bar before the weekend ends in the early afternoon New York time instead of at 17:00. That weekend gives a wrong offset, but it is one weekend against seven or more, so the recent majority still wins.
- Symbols that close at another hour. A symbol that closes at 16:00 New York (stock CFDs, some cash indices) gives an offset one hour too low every weekend, and the vote cannot correct that. Crypto and some exotic symbols trade through the weekend and give no answer. Attach the panel to the broker's index or major FX chart instead.
- Future rule changes. If the US, the EU or New South Wales changes its daylight-time law, the calendar functions (NewYorkOffset, EUSummer, SydneyOffset) need updating. Changes have been discussed for years; none has happened since 2007, 1996 and 2008.
Conclusion
The broker's clock does not have to be an input that users get wrong. A year of H1 bars contains about fifty weekly closes at a known instant, which is enough to find both the base offset and the daylight-time rule, the same way live and in the Strategy Tester. The live terminal offset then becomes a cross-check that catches a wrong computer clock.
The library, the test Expert Advisor and the test-symbol script are free in the CodeBase: Broker GMT offset and daylight-time rule from price history. The panel indicator is listed in full below.
Appendix: complete listings
The detection function in full, with the helper that applies a rule:
int RuleOffset(int rule, int base, datetime utc)
{
if(rule == 1) return base + (NewYorkOffset(utc) == -4 ? 1 : 0);
if(rule == 2) return base + (EUSummer(utc) ? 1 : 0);
return base;
}
// The last H1 bar before a weekend ends at the weekly close, 17:00 New York.
// The clock rule (fixed, US or EU daylight time, any base) that explains most
// of the last 8 weekends wins; ties are broken by the last year of weekends.
int HistoryClockOffset(datetime srv)
{
MqlRates r[];
ArraySetAsSeries(r, true);
int n = CopyRates(_Symbol, PERIOD_H1, srv, 24 * 7 * 62, r);
if(n < 48) return OFFSET_UNKNOWN;
int hw[60]; bool usw[60], euw[60]; int k = 0;
for(int i = 0; i + 1 < n && k < 60; i++)
{
if(r[i].time - r[i + 1].time < 36 * 3600) continue;
datetime weekClose = r[i + 1].time + 3600;
MqlDateTime d; TimeToStruct(weekClose, d);
d.hour = 17; d.min = 0; d.sec = 0;
datetime ny17 = StructToTime(d);
datetime utc17 = ny17 - NewYorkOffset(ny17 + 5 * 3600) * 3600; // ny17 + 5 h is close enough to UTC: clocks change on Sundays
long diff = (long)weekClose - (long)utc17;
while(diff > 12 * 3600) diff -= 86400;
while(diff < -12 * 3600) diff += 86400;
hw[k] = (int)MathRound((double)diff / 3600.0);
usw[k] = (NewYorkOffset(utc17) == -4);
euw[k] = EUSummer(utc17);
k++;
}
if(k == 0) return OFFSET_UNKNOWN;
int bestRule = 0, bestBase = hw[0], bestScore = -1;
for(int rule = 0; rule < 3; rule++)
for(int base = -12; base <= 14; base++)
{
int fitRecent = 0, fitAll = 0;
for(int w = 0; w < k; w++)
if(base + (rule == 1 ? (usw[w] ? 1 : 0) : (rule == 2 ? (euw[w] ? 1 : 0) : 0)) == hw[w])
{
fitAll++;
if(w < 8) fitRecent++;
}
if(fitRecent == 0) continue;
int pref = (rule == 0 && base == 0) ? 3 : (rule == 1 ? 2 : (rule == 2 ? 1 : 0));
int score = fitRecent * 1000 + fitAll * 10 + pref;
if(score > bestScore) { bestScore = score; bestRule = rule; bestBase = base; }
}
g_histRule = (bestRule == 0) ? "fixed all year" : (bestRule == 1 ? "follows US summer time" : "follows European summer time");
datetime utcNow = srv - RuleOffset(bestRule, bestBase, srv) * 3600;
return RuleOffset(bestRule, bestBase, utcNow);
} The Sydney rule, for programs that trade the Asian session:
// Sydney: daylight time from the 1st Sunday of October to the 1st Sunday of April (16:00 UTC the day before).
int SydneyOffset(datetime utc)
{
MqlDateTime d; TimeToStruct(utc, d);
datetime endDst = NthSundayUTC(d.year, 4, 1, 0) - 8 * 3600;
datetime startDst = NthSundayUTC(d.year, 10, 1, 0) - 8 * 3600;
return (utc < endDst || utc >= startDst) ? 11 : 10;
} The panel indicator:
//+------------------------------------------------------------------+
//| ClockPanel.mq5: a minimal example of BrokerClock.mqh on a chart. |
//| Shows the broker offset from the terminal and from the price |
//| history, warns when they disagree, and shows New York time. |
//+------------------------------------------------------------------+
#property copyright "Article example"
#property version "1.00"
#property indicator_chart_window
#property indicator_plots 0
#include <BrokerClock.mqh>
int g_hist = OFFSET_UNKNOWN;
datetime g_histAt = 0;
int OnInit()
{
EventSetTimer(1);
return INIT_SUCCEEDED;
}
void OnDeinit(const int reason)
{
EventKillTimer();
Comment("");
}
void OnTimer()
{
datetime srv = TimeTradeServer();
if(srv <= 0)
return;
// the history check copies many bars: once an hour, or once a minute while it has no answer yet
if(g_histAt == 0 || srv - g_histAt >= (g_hist == OFFSET_UNKNOWN ? 60 : 3600))
{
g_hist = HistoryClockOffset(srv);
g_histAt = srv;
}
int live = LiveClockOffset();
int use = (live != OFFSET_UNKNOWN) ? live : g_hist;
string txt = "Broker clock\n";
txt += "terminal: " + (live == OFFSET_UNKNOWN ? "unknown" : StringFormat("GMT%+d", live)) + "\n";
txt += "price history: " + (g_hist == OFFSET_UNKNOWN ? "not enough history" : StringFormat("GMT%+d, %s", g_hist, g_histRule)) + "\n";
if(live != OFFSET_UNKNOWN && g_hist != OFFSET_UNKNOWN && live != g_hist)
txt += "WARNING: the two disagree. Check the computer's clock and time zone.\n";
if(use != OFFSET_UNKNOWN)
{
datetime utc = srv - use * 3600;
datetime ny = utc + NewYorkOffset(utc) * 3600;
txt += "New York: " + TimeToString(ny, TIME_DATE | TIME_MINUTES) + StringFormat(" (UTC%+d)", NewYorkOffset(utc));
}
Comment(txt);
}
int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
{
return rates_total;
}


