| # SPDX-License-Identifier: LicenseRef-PolyForm-Noncommercial-1.0.0 (see LICENSE)
|
| """Line-by-line port of PrgDayStart / PrgBrokerOffset / PrgZoneOffset (MonEngine.mqh), checked against zoneinfo:
|
| for every hour 2020-2026, on each broker clock and reset zone, the reset instant must be the last moment the zone's
|
| wall clock showed reset_hour:00 (converted back to server time). python3 tests/check_day_start.py"""
|
| from datetime import datetime, timedelta, timezone
|
| from zoneinfo import ZoneInfo
|
|
|
| def sunday_from(y, m, d, h):
|
| t = datetime(y, m, d, tzinfo=timezone.utc)
|
| return t + timedelta(days=(6 - t.weekday()) % 7, hours=h) # MQL day_of_week: Sunday=0; Python Sunday=6
|
|
|
| def us_dst(u): return sunday_from(u.year, 3, 8, 7) <= u < sunday_from(u.year, 11, 1, 6)
|
| def eu_dst(u): return sunday_from(u.year, 3, 25, 1) <= u < sunday_from(u.year, 10, 25, 1)
|
|
|
| def zone_off(z, u):
|
| return {"Prague": 2 if eu_dst(u) else 1, "London": 1 if eu_dst(u) else 0,
|
| "NewYork": -4 if us_dst(u) else -5, "UTC": 0}[z] * 3600
|
|
|
| def broker_off(clock, srv): # srv is a naive "server clock" value stored as UTC-aware datetime
|
| if clock == "EET": return (3 if eu_dst(srv - timedelta(hours=2)) else 2) * 3600
|
| return (3 if us_dst(srv - timedelta(hours=2)) else 2) * 3600
|
|
|
| def floor_to_hour(t, h):
|
| k = int(t.timestamp()) - h * 3600
|
| return t - timedelta(seconds=k % 86400)
|
|
|
| def day_start(clock, zone, h, srv):
|
| utc = srv - timedelta(seconds=broker_off(clock, srv))
|
| loc = utc + timedelta(seconds=zone_off(zone, utc))
|
| rl = floor_to_hour(loc, h)
|
| ru = rl - timedelta(seconds=zone_off(zone, rl - timedelta(seconds=zone_off(zone, utc))))
|
| return ru + timedelta(seconds=broker_off(clock, ru + timedelta(hours=2)))
|
|
|
| TZ = {"Prague": "Europe/Prague", "London": "Europe/London", "NewYork": "America/New_York", "UTC": "UTC"}
|
| SRV = {"NY": "America/New_York", "EET": "Europe/Athens"} # NY close = New York + 7h; EET = Athens
|
|
|
| def truth(clock, zone, h, srv):
|
| # server wall clock -> UTC
|
| if clock == "NY":
|
| ny = srv.replace(tzinfo=None) - timedelta(hours=7)
|
| utc = ny.replace(tzinfo=ZoneInfo("America/New_York")).astimezone(timezone.utc)
|
| else:
|
| utc = srv.replace(tzinfo=None).replace(tzinfo=ZoneInfo("Europe/Athens")).astimezone(timezone.utc)
|
| loc = utc.astimezone(ZoneInfo(TZ[zone]))
|
| r = loc.replace(hour=h, minute=0, second=0, microsecond=0)
|
| if r > loc: r -= timedelta(days=1)
|
| r = r.replace(tzinfo=None).replace(tzinfo=ZoneInfo(TZ[zone])).astimezone(timezone.utc)
|
| # UTC -> server wall clock
|
| if clock == "NY":
|
| s = r.astimezone(ZoneInfo("America/New_York")).replace(tzinfo=None) + timedelta(hours=7)
|
| else:
|
| s = r.astimezone(ZoneInfo("Europe/Athens")).replace(tzinfo=None)
|
| return s.replace(tzinfo=timezone.utc)
|
|
|
| bad = total = 0
|
| for clock in ("NY", "EET"):
|
| for zone, h in (("Prague", 0), ("UTC", 0), ("NewYork", 17), ("London", 22), ("Prague", 23)):
|
| t = datetime(2020, 1, 1, tzinfo=timezone.utc)
|
| n_bad = 0
|
| while t < datetime(2027, 1, 1, tzinfo=timezone.utc):
|
| for minute in (0, 30):
|
| s = t + timedelta(minutes=minute)
|
| total += 1
|
| if day_start(clock, zone, h, s) != truth(clock, zone, h, s):
|
| n_bad += 1
|
| if n_bad <= 3:
|
| print(f" {clock} {zone} {h:02d}h srv {s:%Y-%m-%d %H:%M}: got {day_start(clock, zone, h, s):%m-%d %H:%M} want {truth(clock, zone, h, s):%m-%d %H:%M}")
|
| t += timedelta(hours=1)
|
| bad += n_bad
|
| print(f"{clock:3} {zone:8} {h:02d}:00 mismatches {n_bad}")
|
| print(f"total {total} checks, {bad} mismatches")
|