//+------------------------------------------------------------------+
//|                                                 SessionClock.mqh |
//|             DST-aware market session hour calculator for MQL5    |
//|             Place this file in MQL5/Include/SessionClock/        |
//+------------------------------------------------------------------+
#property strict

//+------------------------------------------------------------------+
//| ENUMS                                                            |
//+------------------------------------------------------------------+
enum DST_SCHEDULE_ENUM
  {
   DST_SCHEDULE_NONE = 0, // Broker uses fixed UTC offset year-round
   DST_SCHEDULE_EU   = 1, // Broker follows EU schedule
   DST_SCHEDULE_US   = 2  // Broker follows US schedule
  };

enum SESSION_ENUM
  {
   SESSION_LONDON   = 0,
   SESSION_NEWYORK  = 1,
   SESSION_TOKYO    = 2
  };

//--- Global inputs
input DST_SCHEDULE_ENUM InpBrokerDST    = DST_SCHEDULE_EU; // Broker DST schedule
input int               InpBrokerWinter = 2;               // Broker winter GMT offset

//+------------------------------------------------------------------+
//| GetNthSunday: returns datetime of the Nth Sunday in month/year   |
//+------------------------------------------------------------------+
datetime GetNthSunday(int year,int month,int n)
  {
   const int SECONDS_IN_DAY = 86400;
   MqlDateTime dt = {0};
   dt.year = year;
   dt.mon  = month;
   dt.day  = 1;
   dt.hour = 2;

   datetime first_day = StructToTime(dt);
   TimeToStruct(first_day,dt);

   int days_to_sun = (7-dt.day_of_week)%7;
   datetime first_sun = first_day+days_to_sun*SECONDS_IN_DAY;

   if(n > 0)
     {
      return(first_sun+(n-1)*7*SECONDS_IN_DAY);
     }

   datetime last = first_sun;
   while(true)
     {
      datetime next = last+7*SECONDS_IN_DAY;
      TimeToStruct(next,dt);

      if(dt.mon != month)
        {
         break;
        }
      last = next;
     }

   return(last);
  }

//+------------------------------------------------------------------+
//| IsBrokerOnSummerTime: returns true if broker is in DST           |
//+------------------------------------------------------------------+
bool IsBrokerOnSummerTime(datetime t=0)
  {
   if(t == 0)
     {
      t = TimeCurrent();
     }

   if(InpBrokerDST == DST_SCHEDULE_NONE)
     {
      return(false);
     }

   MqlDateTime dt;
   TimeToStruct(t,dt);
   int y = dt.year;
   datetime s,e;

   if(InpBrokerDST == DST_SCHEDULE_EU)
     {
      s = GetNthSunday(y,3,0);   // Last Sun March
      e = GetNthSunday(y,10,0);  // Last Sun October
     }
   else
     {
      s = GetNthSunday(y,3,2);   // 2nd Sun March
      e = GetNthSunday(y,11,1);  // 1st Sun November
     }

   return(t >= s && t < e);
  }

//+------------------------------------------------------------------+
//| GetBrokerOffset: returns broker's current GMT offset in hours    |
//+------------------------------------------------------------------+
int GetBrokerOffset(datetime t=0)
  {
   if(t == 0)
     {
      t = TimeCurrent();
     }

   int dst_add = 0;
   if(IsBrokerOnSummerTime(t))
     {
      dst_add = 1;
     }

   return(InpBrokerWinter+dst_add);
  }

//+------------------------------------------------------------------+
//| GetMarketSessionHour: returns session open/close in broker time  |
//+------------------------------------------------------------------+
int GetMarketSessionHour(SESSION_ENUM session,bool is_close=false,datetime t=0)
  {
   const int SECONDS_IN_HOUR = 3600;

   if(t == 0)
     {
      t = TimeCurrent();
     }

   int broker_offset = GetBrokerOffset(t);
   datetime utc_now  = t-broker_offset*SECONDS_IN_HOUR;

   MqlDateTime utc_dt;
   TimeToStruct(utc_now,utc_dt);
   int y = utc_dt.year;

//--- EU DST for London
   datetime lon_dst_s  = GetNthSunday(y,3,0);
   datetime lon_dst_e  = GetNthSunday(y,10,0);
   bool     lon_summer = (utc_now >= lon_dst_s && utc_now < lon_dst_e);

//--- US DST for New York
   datetime ny_dst_s = GetNthSunday(y,3,2);
   datetime ny_dst_e = GetNthSunday(y,11,1);
   bool ny_summer = (utc_now >= ny_dst_s && utc_now < ny_dst_e);

   int utc_open,utc_close;

   switch(session)
     {
      case SESSION_LONDON:
         utc_open  = lon_summer ? 7 : 8;
         utc_close = lon_summer ? 16 : 17;
         break;
      case SESSION_NEWYORK:
         utc_open  = ny_summer ? 12 : 13;
         utc_close = ny_summer ? 21 : 22;
         break;
      case SESSION_TOKYO:
      default:
         utc_open  = 0;
         utc_close = 9;
         break;
     }

   int result = (is_close ? utc_close : utc_open)+broker_offset;

//--- Normalise to 0-23
   result = result%24;
   if(result < 0)
     {
      result += 24;
     }

   return(result);
  }

//+------------------------------------------------------------------+
//| IsHourInWindow: true if hour lies in [start, end), also when     |
//| the window crosses midnight (for example start 10, end 0)        |
//+------------------------------------------------------------------+
bool IsHourInWindow(const int hour,const int start,const int end)
  {
   if(start <= end)
     {
      return(hour >= start && hour < end);
     }
   return(hour >= start || hour < end);
  }
//+------------------------------------------------------------------+