1844 lines
50 KiB
MQL5
1844 lines
50 KiB
MQL5
|
|
#include "..\TimeUtils.mqh"
|
||
|
|
|
||
|
|
#define RANDOM_TESTS 60000000
|
||
|
|
#define FIRST_YEAR 1970
|
||
|
|
#define LAST_YEAR 3000
|
||
|
|
|
||
|
|
const datetime MIN_TIME = D'1970.01.01 00:00:00';
|
||
|
|
const datetime MAX_TIME = D'3000.12.31 23:59:59';
|
||
|
|
|
||
|
|
struct TestStatistics
|
||
|
|
{
|
||
|
|
ulong Timestamps; // Number of datetime values tested
|
||
|
|
ulong Core; // ValidateCore() calls
|
||
|
|
ulong Property; // ValidateProperties() calls
|
||
|
|
ulong Assertions; // Total assertions evaluated
|
||
|
|
ulong EdgeCases; // Boundary/special-case timestamps
|
||
|
|
ulong Random; // Random timestamps generated
|
||
|
|
ulong Failed; // Failed assertions
|
||
|
|
ulong StartTick;
|
||
|
|
};
|
||
|
|
|
||
|
|
TestStatistics Stats = {};
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Test offsets |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
|
||
|
|
const int DAY_OFFSETS[] =
|
||
|
|
{
|
||
|
|
-5000,-2000,-1000,-366,-365,-100,-31,-30,-7,-2,-1,
|
||
|
|
1,2,7,30,31,100,365,366,1000,2000,5000
|
||
|
|
};
|
||
|
|
|
||
|
|
const int WEEK_OFFSETS[] =
|
||
|
|
{
|
||
|
|
-520,-260,-104,-52,-26,-12,-4,-1,
|
||
|
|
1,4,12,26,52,104,260,520
|
||
|
|
};
|
||
|
|
|
||
|
|
const int MONTH_OFFSETS[] =
|
||
|
|
{
|
||
|
|
-240,-120,-60,-24,-12,-6,-3,-1,
|
||
|
|
1,3,6,12,24,60,120,240
|
||
|
|
};
|
||
|
|
|
||
|
|
const int QUARTER_OFFSETS[] =
|
||
|
|
{
|
||
|
|
-40,-20,-8,-4,-1,
|
||
|
|
1,4,8,20,40
|
||
|
|
};
|
||
|
|
|
||
|
|
const int YEAR_OFFSETS[] =
|
||
|
|
{
|
||
|
|
-200,-100,-50,-20,-10,-5,-1,
|
||
|
|
1,5,10,20,50,100,200
|
||
|
|
};
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Assertion: boolean |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
bool Assert(const bool condition,
|
||
|
|
const string test,
|
||
|
|
const datetime t,
|
||
|
|
const string expected = "",
|
||
|
|
const string actual = "")
|
||
|
|
{
|
||
|
|
Stats.Assertions++;
|
||
|
|
|
||
|
|
if(condition)
|
||
|
|
return true;
|
||
|
|
|
||
|
|
ReportFailure(test, t, expected, actual);
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Assertion: integer |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
bool AssertEqual(const int expected,
|
||
|
|
const int actual,
|
||
|
|
const string test,
|
||
|
|
const datetime t)
|
||
|
|
{
|
||
|
|
Stats.Assertions++;
|
||
|
|
|
||
|
|
if(expected == actual)
|
||
|
|
return true;
|
||
|
|
|
||
|
|
ReportFailure(test,
|
||
|
|
t,
|
||
|
|
IntegerToString(expected),
|
||
|
|
IntegerToString(actual));
|
||
|
|
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Assertion: datetime |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
bool AssertEqual(const datetime expected,
|
||
|
|
const datetime actual,
|
||
|
|
const string test,
|
||
|
|
const datetime t)
|
||
|
|
{
|
||
|
|
Stats.Assertions++;
|
||
|
|
|
||
|
|
if(expected == actual)
|
||
|
|
return true;
|
||
|
|
|
||
|
|
ReportFailure(test,
|
||
|
|
expected,
|
||
|
|
TimeToString(expected,TIME_DATE|TIME_SECONDS),
|
||
|
|
TimeToString(actual,TIME_DATE|TIME_SECONDS));
|
||
|
|
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
void PrintProgress(const ulong current,const ulong total)
|
||
|
|
{
|
||
|
|
static uint lastPercent=0;
|
||
|
|
|
||
|
|
uint percent=(uint)((100ULL*current)/total);
|
||
|
|
|
||
|
|
if(percent>=lastPercent+10)
|
||
|
|
{
|
||
|
|
lastPercent=percent;
|
||
|
|
Print(percent,"% completed...");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
void ReportFailure(const string function,
|
||
|
|
const datetime t,
|
||
|
|
const string expected,
|
||
|
|
const string actual)
|
||
|
|
{
|
||
|
|
Stats.Failed++;
|
||
|
|
|
||
|
|
Print("");
|
||
|
|
Print("==================================");
|
||
|
|
Print("Validation FAILED");
|
||
|
|
Print("----------------------------------");
|
||
|
|
Print("Function : ",function);
|
||
|
|
Print("Timestamp: ",TimeToString(t,TIME_DATE|TIME_SECONDS));
|
||
|
|
Print("Epoch : ",(long)t);
|
||
|
|
Print("Expected : ",expected);
|
||
|
|
Print("Actual : ",actual);
|
||
|
|
Print("==================================");
|
||
|
|
}
|
||
|
|
|
||
|
|
struct SplitMix64
|
||
|
|
{
|
||
|
|
ulong x;
|
||
|
|
|
||
|
|
void Seed(const ulong seed)
|
||
|
|
{
|
||
|
|
x = seed;
|
||
|
|
}
|
||
|
|
|
||
|
|
ulong Next()
|
||
|
|
{
|
||
|
|
ulong z = (x += 0x9E3779B97F4A7C15);
|
||
|
|
z = (z ^ (z >> 30)) * 0xBF58476D1CE4E5B9;
|
||
|
|
z = (z ^ (z >> 27)) * 0x94D049BB133111EB;
|
||
|
|
return z ^ (z >> 31);
|
||
|
|
}
|
||
|
|
|
||
|
|
SplitMix64()
|
||
|
|
{
|
||
|
|
Seed(GetTickCount64());
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Validate the core conversion algorithms |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
bool ValidateTimestamp(const datetime t)
|
||
|
|
{
|
||
|
|
Stats.Timestamps++;
|
||
|
|
|
||
|
|
// Reference conversion
|
||
|
|
MqlDateTime ref;
|
||
|
|
if(!TimeToStruct(t, ref))
|
||
|
|
{
|
||
|
|
ReportFailure("TimeToStruct()",
|
||
|
|
t,
|
||
|
|
"Success",
|
||
|
|
"Failure");
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Fast conversion
|
||
|
|
MqlDateTime fast;
|
||
|
|
TimeToStructFast(t, fast);
|
||
|
|
|
||
|
|
// Compare every field
|
||
|
|
if(ref.year != fast.year
|
||
|
|
|| ref.mon != fast.mon
|
||
|
|
|| ref.day != fast.day
|
||
|
|
|| ref.hour != fast.hour
|
||
|
|
|| ref.min != fast.min
|
||
|
|
|| ref.sec != fast.sec
|
||
|
|
|| ref.day_of_week != fast.day_of_week
|
||
|
|
|| ref.day_of_year != fast.day_of_year)
|
||
|
|
{
|
||
|
|
Stats.Failed++;
|
||
|
|
|
||
|
|
Print("");
|
||
|
|
Print("========================================");
|
||
|
|
Print("TimeToStructFast() mismatch");
|
||
|
|
Print("----------------------------------------");
|
||
|
|
Print("Timestamp : ", TimeToString(t, TIME_DATE|TIME_SECONDS));
|
||
|
|
Print("Epoch : ", (long)t);
|
||
|
|
Print("");
|
||
|
|
|
||
|
|
Print("Reference:");
|
||
|
|
MqlDateTime temp[1];
|
||
|
|
temp[1] = ref;
|
||
|
|
ArrayPrint(temp);
|
||
|
|
|
||
|
|
Print("");
|
||
|
|
|
||
|
|
Print("Fast:");
|
||
|
|
temp[1] = fast;
|
||
|
|
ArrayPrint(temp);
|
||
|
|
|
||
|
|
Print("========================================");
|
||
|
|
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Round-trip verification
|
||
|
|
const datetime back = StructToTimeFast(fast);
|
||
|
|
|
||
|
|
if(back != t)
|
||
|
|
{
|
||
|
|
ReportFailure("StructToTimeFast()",
|
||
|
|
t,
|
||
|
|
TimeToString(t, TIME_DATE | TIME_SECONDS),
|
||
|
|
TimeToString(back, TIME_DATE | TIME_SECONDS));
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Validate extractor functions |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
bool ValidateExtractors(const datetime t)
|
||
|
|
{
|
||
|
|
MqlDateTime st;
|
||
|
|
TimeToStructFast(t, st);
|
||
|
|
|
||
|
|
const int year = Year(t);
|
||
|
|
const int month = Month(t);
|
||
|
|
const int day = Day(t);
|
||
|
|
const int hour = Hour(t);
|
||
|
|
const int minute = Minute(t);
|
||
|
|
const int second = Second(t);
|
||
|
|
const int dow = DayOfWeek(t);
|
||
|
|
const int doy = DayOfYear(t);
|
||
|
|
|
||
|
|
if(year != st.year)
|
||
|
|
{
|
||
|
|
ReportFailure("Year()",
|
||
|
|
t,
|
||
|
|
IntegerToString(st.year),
|
||
|
|
IntegerToString(year));
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
if(month != st.mon)
|
||
|
|
{
|
||
|
|
ReportFailure("Month()",
|
||
|
|
t,
|
||
|
|
IntegerToString(st.mon),
|
||
|
|
IntegerToString(month));
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
if(day != st.day)
|
||
|
|
{
|
||
|
|
ReportFailure("Day()",
|
||
|
|
t,
|
||
|
|
IntegerToString(st.day),
|
||
|
|
IntegerToString(day));
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
if(hour != st.hour)
|
||
|
|
{
|
||
|
|
ReportFailure("Hour()",
|
||
|
|
t,
|
||
|
|
IntegerToString(st.hour),
|
||
|
|
IntegerToString(hour));
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
if(minute != st.min)
|
||
|
|
{
|
||
|
|
ReportFailure("Minute()",
|
||
|
|
t,
|
||
|
|
IntegerToString(st.min),
|
||
|
|
IntegerToString(minute));
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
if(second != st.sec)
|
||
|
|
{
|
||
|
|
ReportFailure("Second()",
|
||
|
|
t,
|
||
|
|
IntegerToString(st.sec),
|
||
|
|
IntegerToString(second));
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
if(dow != st.day_of_week)
|
||
|
|
{
|
||
|
|
ReportFailure("DayOfWeek()",
|
||
|
|
t,
|
||
|
|
IntegerToString(st.day_of_week),
|
||
|
|
IntegerToString(dow));
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
if(doy != st.day_of_year)
|
||
|
|
{
|
||
|
|
ReportFailure("DayOfYear()",
|
||
|
|
t,
|
||
|
|
IntegerToString(st.day_of_year),
|
||
|
|
IntegerToString(doy));
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Validate one edge case and its neighborhood |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
bool ValidateEdgeCase(const datetime t)
|
||
|
|
{
|
||
|
|
for(int offset = -10; offset <= 10; offset++)
|
||
|
|
{
|
||
|
|
datetime tt = t + offset;
|
||
|
|
|
||
|
|
if(tt < MIN_TIME || tt > MAX_TIME)
|
||
|
|
continue;
|
||
|
|
|
||
|
|
Stats.EdgeCases++;
|
||
|
|
|
||
|
|
if(!ValidateTimestamp(tt))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
if(!ValidateExtractors(tt))
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Assert CalendarDifference() result |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
bool AssertCalendarDifference(const datetime from,
|
||
|
|
const datetime to,
|
||
|
|
const int years,
|
||
|
|
const int months,
|
||
|
|
const int days,
|
||
|
|
const int hours,
|
||
|
|
const int minutes,
|
||
|
|
const int seconds)
|
||
|
|
{
|
||
|
|
MqlDateTime diff;
|
||
|
|
CalendarDifference(from, to, diff);
|
||
|
|
|
||
|
|
if(diff.year != years ||
|
||
|
|
diff.mon != months ||
|
||
|
|
diff.day != days ||
|
||
|
|
diff.hour != hours ||
|
||
|
|
diff.min != minutes ||
|
||
|
|
diff.sec != seconds)
|
||
|
|
{
|
||
|
|
string expected = StringFormat("%dy %dm %dd %02d:%02d:%02d",
|
||
|
|
years, months, days,
|
||
|
|
hours, minutes, seconds);
|
||
|
|
|
||
|
|
string actual = StringFormat("%dy %dm %dd %02d:%02d:%02d",
|
||
|
|
diff.year, diff.mon, diff.day,
|
||
|
|
diff.hour, diff.min, diff.sec);
|
||
|
|
|
||
|
|
ReportFailure("CalendarDifference()",
|
||
|
|
from,
|
||
|
|
expected,
|
||
|
|
actual);
|
||
|
|
|
||
|
|
Print("From : ", TimeToString(from, TIME_DATE | TIME_SECONDS));
|
||
|
|
Print("To : ", TimeToString(to, TIME_DATE | TIME_SECONDS));
|
||
|
|
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Validate CalendarDifference() known edge cases |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
bool ValidateCalendarDifferenceKnownCases()
|
||
|
|
{
|
||
|
|
// Zero interval
|
||
|
|
if(!AssertCalendarDifference(
|
||
|
|
D'1970.01.01 00:00:00',
|
||
|
|
D'1970.01.01 00:00:00',
|
||
|
|
0,0,0,0,0,0))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
// One second
|
||
|
|
if(!AssertCalendarDifference(
|
||
|
|
D'1970.01.01 00:00:00',
|
||
|
|
D'1970.01.01 00:00:01',
|
||
|
|
0,0,0,0,0,1))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
// One minute
|
||
|
|
if(!AssertCalendarDifference(
|
||
|
|
D'1970.01.01 00:00:00',
|
||
|
|
D'1970.01.01 00:01:00',
|
||
|
|
0,0,0,0,1,0))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
// One hour
|
||
|
|
if(!AssertCalendarDifference(
|
||
|
|
D'1970.01.01 00:00:00',
|
||
|
|
D'1970.01.01 01:00:00',
|
||
|
|
0,0,0,1,0,0))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
// One day
|
||
|
|
if(!AssertCalendarDifference(
|
||
|
|
D'1970.01.01 00:00:00',
|
||
|
|
D'1970.01.02 00:00:00',
|
||
|
|
0,0,1,0,0,0))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
// Leap day
|
||
|
|
if(!AssertCalendarDifference(
|
||
|
|
D'2000.02.28',
|
||
|
|
D'2000.02.29',
|
||
|
|
0,0,1,0,0,0))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
// Leap day -> March
|
||
|
|
if(!AssertCalendarDifference(
|
||
|
|
D'2000.02.29',
|
||
|
|
D'2000.03.01',
|
||
|
|
0,0,1,0,0,0))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
// Non-leap century
|
||
|
|
if(!AssertCalendarDifference(
|
||
|
|
D'2100.02.28',
|
||
|
|
D'2100.03.01',
|
||
|
|
0,0,1,0,0,0))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
// Month boundary
|
||
|
|
if(!AssertCalendarDifference(
|
||
|
|
D'2024.01.31',
|
||
|
|
D'2024.02.29',
|
||
|
|
0,1,0,0,0,0))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
// February 29 edge case
|
||
|
|
if(!AssertCalendarDifference(
|
||
|
|
D'2024.02.29',
|
||
|
|
D'2025.02.28',
|
||
|
|
1,0,0,0,0,0))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
// Month + day
|
||
|
|
if(!AssertCalendarDifference(
|
||
|
|
D'2024.01.31',
|
||
|
|
D'2024.03.01',
|
||
|
|
0,1,1,0,0,0))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
// Year boundary
|
||
|
|
if(!AssertCalendarDifference(
|
||
|
|
D'2024.12.31 23:59:59',
|
||
|
|
D'2025.01.01 00:00:00',
|
||
|
|
0,0,0,0,0,1))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Verify CalendarDifference() |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
bool ValidateCalendarDifference(const datetime from,
|
||
|
|
const datetime to)
|
||
|
|
{
|
||
|
|
MqlDateTime diff;
|
||
|
|
CalendarDifference(from, to, diff);
|
||
|
|
|
||
|
|
//---------------------------------------------------------------
|
||
|
|
// Normalization
|
||
|
|
//---------------------------------------------------------------
|
||
|
|
|
||
|
|
if(!Assert(diff.year >= 0,
|
||
|
|
"CalendarDifference().year",
|
||
|
|
from))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
if(!Assert(diff.mon >= 0 && diff.mon < 12,
|
||
|
|
"CalendarDifference().month normalized",
|
||
|
|
from))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
if(!Assert(diff.day >= 0,
|
||
|
|
"CalendarDifference().day",
|
||
|
|
from))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
if(!Assert(diff.hour >= 0 && diff.hour < 24,
|
||
|
|
"CalendarDifference().hour normalized",
|
||
|
|
from))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
if(!Assert(diff.min >= 0 && diff.min < 60,
|
||
|
|
"CalendarDifference().minute normalized",
|
||
|
|
from))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
if(!Assert(diff.sec >= 0 && diff.sec < 60,
|
||
|
|
"CalendarDifference().second normalized",
|
||
|
|
from))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
//---------------------------------------------------------------
|
||
|
|
// Reconstruction
|
||
|
|
//---------------------------------------------------------------
|
||
|
|
|
||
|
|
const int totalMonths = diff.year * 12 + diff.mon;
|
||
|
|
|
||
|
|
datetime reconstructed = from;
|
||
|
|
|
||
|
|
reconstructed = AddMonths(reconstructed, totalMonths);
|
||
|
|
reconstructed = AddDays(reconstructed, diff.day);
|
||
|
|
reconstructed = AddHours(reconstructed, diff.hour);
|
||
|
|
reconstructed = AddMinutes(reconstructed, diff.min);
|
||
|
|
reconstructed = AddSeconds(reconstructed, diff.sec);
|
||
|
|
|
||
|
|
if(reconstructed != to)
|
||
|
|
{
|
||
|
|
ReportFailure(
|
||
|
|
"CalendarDifference() reconstruction",
|
||
|
|
from,
|
||
|
|
TimeToString(to, TIME_DATE | TIME_SECONDS),
|
||
|
|
TimeToString(reconstructed, TIME_DATE | TIME_SECONDS));
|
||
|
|
|
||
|
|
PrintFormat("From : %s",
|
||
|
|
TimeToString(from, TIME_DATE | TIME_SECONDS));
|
||
|
|
|
||
|
|
PrintFormat("To : %s",
|
||
|
|
TimeToString(to, TIME_DATE | TIME_SECONDS));
|
||
|
|
|
||
|
|
PrintFormat("Difference : %d years, %d months, %d days, %02d:%02d:%02d",
|
||
|
|
diff.year,
|
||
|
|
diff.mon,
|
||
|
|
diff.day,
|
||
|
|
diff.hour,
|
||
|
|
diff.min,
|
||
|
|
diff.sec);
|
||
|
|
|
||
|
|
PrintFormat("After AddYears : %s",
|
||
|
|
TimeToString(AddYears(from, diff.year),
|
||
|
|
TIME_DATE | TIME_SECONDS));
|
||
|
|
|
||
|
|
datetime tmp = AddYears(from, diff.year);
|
||
|
|
tmp = AddMonths(tmp, diff.mon);
|
||
|
|
|
||
|
|
PrintFormat("After AddMonths : %s",
|
||
|
|
TimeToString(tmp,
|
||
|
|
TIME_DATE | TIME_SECONDS));
|
||
|
|
|
||
|
|
tmp = AddDays(tmp, diff.day);
|
||
|
|
|
||
|
|
PrintFormat("After AddDays : %s",
|
||
|
|
TimeToString(tmp,
|
||
|
|
TIME_DATE | TIME_SECONDS));
|
||
|
|
|
||
|
|
tmp = AddHours(tmp, diff.hour);
|
||
|
|
tmp = AddMinutes(tmp, diff.min);
|
||
|
|
tmp = AddSeconds(tmp, diff.sec);
|
||
|
|
|
||
|
|
PrintFormat("Final : %s",
|
||
|
|
TimeToString(tmp,
|
||
|
|
TIME_DATE | TIME_SECONDS));
|
||
|
|
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
//---------------------------------------------------------------
|
||
|
|
// Exact reconstruction
|
||
|
|
//---------------------------------------------------------------
|
||
|
|
|
||
|
|
MqlDateTime zero;
|
||
|
|
CalendarDifference(reconstructed, to, zero);
|
||
|
|
|
||
|
|
if(zero.year != 0 ||
|
||
|
|
zero.mon != 0 ||
|
||
|
|
zero.day != 0 ||
|
||
|
|
zero.hour != 0 ||
|
||
|
|
zero.min != 0 ||
|
||
|
|
zero.sec != 0)
|
||
|
|
{
|
||
|
|
ReportFailure(
|
||
|
|
"CalendarDifference() reconstruction",
|
||
|
|
from,
|
||
|
|
"0y 0m 0d 00:00:00",
|
||
|
|
CalendarDifferenceToString(reconstructed, to));
|
||
|
|
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Validate CalendarDifference() properties |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
bool ValidateCalendarDifferenceProperties(const datetime t)
|
||
|
|
{
|
||
|
|
//---------------------------------------------------------------
|
||
|
|
// Identity
|
||
|
|
//---------------------------------------------------------------
|
||
|
|
|
||
|
|
MqlDateTime zero;
|
||
|
|
CalendarDifference(t, t, zero);
|
||
|
|
|
||
|
|
if(zero.year != 0 ||
|
||
|
|
zero.mon != 0 ||
|
||
|
|
zero.day != 0 ||
|
||
|
|
zero.hour != 0 ||
|
||
|
|
zero.min != 0 ||
|
||
|
|
zero.sec != 0)
|
||
|
|
{
|
||
|
|
ReportFailure(
|
||
|
|
"CalendarDifference() identity",
|
||
|
|
t,
|
||
|
|
"0 seconds",
|
||
|
|
CalendarDifferenceToString(t, t));
|
||
|
|
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
//---------------------------------------------------------------
|
||
|
|
// Day offsets
|
||
|
|
//---------------------------------------------------------------
|
||
|
|
|
||
|
|
for(int i = 0; i < ArraySize(DAY_OFFSETS); i++)
|
||
|
|
{
|
||
|
|
datetime t2 = AddDays(t, DAY_OFFSETS[i]);
|
||
|
|
|
||
|
|
if(t2 < MIN_TIME || t2 > MAX_TIME)
|
||
|
|
continue;
|
||
|
|
|
||
|
|
if(!ValidateCalendarDifference(MathMin(t, t2),
|
||
|
|
MathMax(t, t2)))
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
//---------------------------------------------------------------
|
||
|
|
// Week offsets
|
||
|
|
//---------------------------------------------------------------
|
||
|
|
|
||
|
|
for(int i = 0; i < ArraySize(WEEK_OFFSETS); i++)
|
||
|
|
{
|
||
|
|
datetime t2 = AddWeeks(t, WEEK_OFFSETS[i]);
|
||
|
|
|
||
|
|
if(t2 < MIN_TIME || t2 > MAX_TIME)
|
||
|
|
continue;
|
||
|
|
|
||
|
|
if(!ValidateCalendarDifference(MathMin(t, t2),
|
||
|
|
MathMax(t, t2)))
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
//---------------------------------------------------------------
|
||
|
|
// Month offsets
|
||
|
|
//---------------------------------------------------------------
|
||
|
|
|
||
|
|
for(int i = 0; i < ArraySize(MONTH_OFFSETS); i++)
|
||
|
|
{
|
||
|
|
datetime t2 = AddMonths(t, MONTH_OFFSETS[i]);
|
||
|
|
|
||
|
|
if(t2 < MIN_TIME || t2 > MAX_TIME)
|
||
|
|
continue;
|
||
|
|
|
||
|
|
if(!ValidateCalendarDifference(MathMin(t, t2),
|
||
|
|
MathMax(t, t2)))
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
//---------------------------------------------------------------
|
||
|
|
// Quarter offsets
|
||
|
|
//---------------------------------------------------------------
|
||
|
|
|
||
|
|
for(int i = 0; i < ArraySize(QUARTER_OFFSETS); i++)
|
||
|
|
{
|
||
|
|
datetime t2 = AddQuarters(t, QUARTER_OFFSETS[i]);
|
||
|
|
|
||
|
|
if(t2 < MIN_TIME || t2 > MAX_TIME)
|
||
|
|
continue;
|
||
|
|
|
||
|
|
if(!ValidateCalendarDifference(MathMin(t, t2),
|
||
|
|
MathMax(t, t2)))
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
//---------------------------------------------------------------
|
||
|
|
// Year offsets
|
||
|
|
//---------------------------------------------------------------
|
||
|
|
|
||
|
|
for(int i = 0; i < ArraySize(YEAR_OFFSETS); i++)
|
||
|
|
{
|
||
|
|
datetime t2 = AddYears(t, YEAR_OFFSETS[i]);
|
||
|
|
|
||
|
|
if(t2 < MIN_TIME || t2 > MAX_TIME)
|
||
|
|
continue;
|
||
|
|
|
||
|
|
if(!ValidateCalendarDifference(MathMin(t, t2),
|
||
|
|
MathMax(t, t2)))
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Assert CalendarDifferenceToString() |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
bool AssertCalendarDifferenceString(const datetime from,
|
||
|
|
const datetime to,
|
||
|
|
const string expected)
|
||
|
|
{
|
||
|
|
string actual = CalendarDifferenceToString(from, to);
|
||
|
|
|
||
|
|
if(actual != expected)
|
||
|
|
{
|
||
|
|
ReportFailure(
|
||
|
|
"CalendarDifferenceToString()",
|
||
|
|
from,
|
||
|
|
expected,
|
||
|
|
actual);
|
||
|
|
|
||
|
|
Print("From : ", TimeToString(from, TIME_DATE | TIME_SECONDS));
|
||
|
|
Print("To : ", TimeToString(to, TIME_DATE | TIME_SECONDS));
|
||
|
|
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Validate CalendarDifferenceToString() |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
bool ValidateCalendarDifferenceStringKnownCases()
|
||
|
|
{
|
||
|
|
if(!AssertCalendarDifferenceString(
|
||
|
|
D'1970.01.01',
|
||
|
|
D'1970.01.01',
|
||
|
|
"0 seconds"))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
if(!AssertCalendarDifferenceString(
|
||
|
|
D'1970.01.01',
|
||
|
|
D'1970.01.01 00:00:01',
|
||
|
|
"1 second"))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
if(!AssertCalendarDifferenceString(
|
||
|
|
D'1970.01.01',
|
||
|
|
D'1970.01.01 00:00:02',
|
||
|
|
"2 seconds"))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
if(!AssertCalendarDifferenceString(
|
||
|
|
D'1970.01.01',
|
||
|
|
D'1970.01.01 00:01:00',
|
||
|
|
"1 minute"))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
if(!AssertCalendarDifferenceString(
|
||
|
|
D'1970.01.01',
|
||
|
|
D'1970.01.01 01:00:00',
|
||
|
|
"1 hour"))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
if(!AssertCalendarDifferenceString(
|
||
|
|
D'1970.01.01',
|
||
|
|
D'1970.01.02',
|
||
|
|
"1 day"))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
if(!AssertCalendarDifferenceString(
|
||
|
|
D'2024.01.31',
|
||
|
|
D'2024.02.29',
|
||
|
|
"1 month"))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
if(!AssertCalendarDifferenceString(
|
||
|
|
D'2024.02.29',
|
||
|
|
D'2025.02.28',
|
||
|
|
"1 year"))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
if(!AssertCalendarDifferenceString(
|
||
|
|
D'2024.01.31',
|
||
|
|
D'2024.03.01',
|
||
|
|
"1 month 1 day"))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
if(!AssertCalendarDifferenceString(
|
||
|
|
D'2024.12.31 23:59:59',
|
||
|
|
D'2025.01.01 00:00:00',
|
||
|
|
"1 second"))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
// Each phase has a distinct purpose:
|
||
|
|
//
|
||
|
|
// Phase Purpose
|
||
|
|
// Calendar boundaries Test every transition (month-end, leap day, year-end, century)
|
||
|
|
// Every day Validate every calendar date exactly once
|
||
|
|
// Every hour Validate time decomposition and midnight rollovers
|
||
|
|
// Random stress Detect unexpected regressions over millions of arbitrary timestamps
|
||
|
|
|
||
|
|
/*
|
||
|
|
TestCalendarBoundaries()
|
||
|
|
│
|
||
|
|
├── ValidateLeapYears()
|
||
|
|
├── ValidateSpecialDates()
|
||
|
|
├── ValidateCalendarDifferenceKnownCases()
|
||
|
|
└── ...
|
||
|
|
*/
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Test all leap years and special calendar days |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
bool TestCalendarBoundaries()
|
||
|
|
{
|
||
|
|
Print("Testing calendar boundaries...");
|
||
|
|
|
||
|
|
for(int year = FIRST_YEAR; year <= LAST_YEAR; year++)
|
||
|
|
{
|
||
|
|
//----- New Year's transition
|
||
|
|
if(!ValidateEdgeCase(DateFrom(year, 1, 1, 0, 0, 0)))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
if(!ValidateEdgeCase(DateFrom(year, 12, 31, 23, 59, 59)))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
//----- Every month boundary
|
||
|
|
for(int month = 1; month <= 12; month++)
|
||
|
|
{
|
||
|
|
const int lastDay = DaysInMonth(year, month);
|
||
|
|
|
||
|
|
if(!ValidateEdgeCase(DateFrom(year, month, 1, 0, 0, 0)))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
if(!ValidateEdgeCase(DateFrom(year, month, lastDay, 23, 59, 59)))
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
//----- February transition
|
||
|
|
if(IsLeapYear(year))
|
||
|
|
{
|
||
|
|
if(!ValidateEdgeCase(DateFrom(year, 2, 28, 23, 59, 59)))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
if(!ValidateEdgeCase(DateFrom(year, 2, 29, 0, 0, 0)))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
if(!ValidateEdgeCase(DateFrom(year, 2, 29, 12, 0, 0)))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
if(!ValidateEdgeCase(DateFrom(year, 2, 29, 23, 59, 59)))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
if(!ValidateEdgeCase(DateFrom(year, 3, 1, 0, 0, 0)))
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
if(!ValidateEdgeCase(DateFrom(year, 2, 28, 23, 59, 59)))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
if(!ValidateEdgeCase(DateFrom(year, 3, 1, 0, 0, 0)))
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if(!ValidateCalendarDifferenceKnownCases())
|
||
|
|
return false;
|
||
|
|
|
||
|
|
Print("Calendar boundary tests passed.");
|
||
|
|
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Validate every day in the supported range |
|
||
|
|
//| 376,565 timestamps |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
bool TestEveryDay()
|
||
|
|
{
|
||
|
|
Print("Testing every calendar day...");
|
||
|
|
|
||
|
|
ulong tested = 0;
|
||
|
|
|
||
|
|
for(datetime t = MIN_TIME; t <= MAX_TIME; t += 86400)
|
||
|
|
{
|
||
|
|
if(!ValidateTimestamp(t))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
if(!ValidateExtractors(t))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
tested++;
|
||
|
|
|
||
|
|
if((tested % 50000) == 0)
|
||
|
|
Print(" ", tested, " days checked...");
|
||
|
|
}
|
||
|
|
|
||
|
|
Print("Every day passed (", tested, " days).");
|
||
|
|
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Validate every hour in the supported range |
|
||
|
|
//| 376,565 × 24 ≈ 9,037,560 timestamps |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
bool TestEveryHour()
|
||
|
|
{
|
||
|
|
Print("Testing every hour...");
|
||
|
|
|
||
|
|
ulong tested = 0;
|
||
|
|
|
||
|
|
for(datetime t = MIN_TIME; t <= MAX_TIME; t += 3600)
|
||
|
|
{
|
||
|
|
if(!ValidateTimestamp(t))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
tested++;
|
||
|
|
|
||
|
|
if((tested % 1000000) == 0)
|
||
|
|
Print(" ", tested, " hours checked...");
|
||
|
|
}
|
||
|
|
|
||
|
|
Print("Every hour passed (", tested, " hours).");
|
||
|
|
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Random stress test |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
bool TestRandom()
|
||
|
|
{
|
||
|
|
Print("Random stress test...");
|
||
|
|
|
||
|
|
SplitMix64 rng;
|
||
|
|
|
||
|
|
const ulong range = (ulong)(MAX_TIME - MIN_TIME) + 1;
|
||
|
|
|
||
|
|
for(ulong i = 1; i <= RANDOM_TESTS; i++)
|
||
|
|
{
|
||
|
|
datetime t = MIN_TIME + (datetime)(rng.Next() % range);
|
||
|
|
|
||
|
|
Stats.Random++;
|
||
|
|
|
||
|
|
// Core validation
|
||
|
|
if(!ValidateCore(t))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
// Property validation every 100th sample
|
||
|
|
if((i % 100) == 0)
|
||
|
|
{
|
||
|
|
if(!ValidateProperties(t))
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
PrintProgress(i, RANDOM_TESTS);
|
||
|
|
}
|
||
|
|
|
||
|
|
Print("Random stress test passed.");
|
||
|
|
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
bool ValidateStartEndProperties(const datetime t)
|
||
|
|
{
|
||
|
|
if(!Assert(StartOfMinute(t) <= t && t <= EndOfMinute(t), "Minute range", t))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
if(!Assert(StartOfHour(t) <= t && t <= EndOfHour(t), "Hour range", t))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
if(!Assert(StartOfDay(t) <= t && t <= EndOfDay(t), "Day range", t))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
if(!Assert(StartOfWeek(t) <= t && t <= EndOfWeek(t), "Week range", t))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
if(!Assert(StartOfMonth(t) <= t && t <= EndOfMonth(t), "Month range", t))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
if(!Assert(StartOfQuarter(t) <= t && t <= EndOfQuarter(t), "Quarter range", t))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
if(!Assert(StartOfYear(t) <= t && t <= EndOfYear(t), "Year range", t))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
if(!AssertEqual(
|
||
|
|
StartOfMinute(t),
|
||
|
|
StartOfMinute(EndOfMinute(t)),
|
||
|
|
"StartOfMinute(EndOfMinute())",
|
||
|
|
t))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
if(!AssertEqual(
|
||
|
|
StartOfHour(t),
|
||
|
|
StartOfHour(EndOfHour(t)),
|
||
|
|
"StartOfHour(EndOfHour())",
|
||
|
|
t))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
if(!AssertEqual(
|
||
|
|
StartOfDay(t),
|
||
|
|
StartOfDay(EndOfDay(t)),
|
||
|
|
"StartOfDay(EndOfDay())",
|
||
|
|
t))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
if(!AssertEqual(
|
||
|
|
StartOfWeek(t),
|
||
|
|
StartOfWeek(EndOfWeek(t)),
|
||
|
|
"StartOfWeek(EndOfWeek())",
|
||
|
|
t))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
if(!AssertEqual(
|
||
|
|
StartOfMonth(t),
|
||
|
|
StartOfMonth(EndOfMonth(t)),
|
||
|
|
"StartOfMonth(EndOfMonth())",
|
||
|
|
t))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
if(!AssertEqual(
|
||
|
|
StartOfQuarter(t),
|
||
|
|
StartOfQuarter(EndOfQuarter(t)),
|
||
|
|
"StartOfQuarter(EndOfQuarter())",
|
||
|
|
t))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
|
||
|
|
if(!AssertEqual(
|
||
|
|
StartOfYear(t),
|
||
|
|
StartOfYear(EndOfYear(t)),
|
||
|
|
"StartOfYear(EndOfYear())",
|
||
|
|
t))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
bool ValidateDecompositionProperties(const datetime t)
|
||
|
|
{
|
||
|
|
if(!AssertEqual(t,
|
||
|
|
DateOnly(t) + TimeOnly(t),
|
||
|
|
"DateOnly() + TimeOnly()",
|
||
|
|
t))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
if(!AssertEqual(t,
|
||
|
|
StartOfMinute(t) + Second(t),
|
||
|
|
"Minute decomposition",
|
||
|
|
t))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
if(!AssertEqual(t,
|
||
|
|
StartOfHour(t)
|
||
|
|
+ Minute(t) * 60
|
||
|
|
+ Second(t),
|
||
|
|
"Hour decomposition",
|
||
|
|
t))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
if(!AssertEqual(t,
|
||
|
|
StartOfDay(t)
|
||
|
|
+ Hour(t) * 3600
|
||
|
|
+ Minute(t) * 60
|
||
|
|
+ Second(t),
|
||
|
|
"Day decomposition",
|
||
|
|
t))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
bool ValidateIdempotenceProperties(const datetime t)
|
||
|
|
{
|
||
|
|
if(!AssertEqual(StartOfMinute(StartOfMinute(t)), StartOfMinute(t), "StartOfMinute()", t))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
if(!AssertEqual(StartOfHour(StartOfHour(t)), StartOfHour(t), "StartOfHour()", t))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
if(!AssertEqual(StartOfDay(StartOfDay(t)), StartOfDay(t), "StartOfDay()", t))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
if(!AssertEqual(StartOfWeek(StartOfWeek(t)), StartOfWeek(t), "StartOfWeek()", t))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
if(!AssertEqual(StartOfMonth(StartOfMonth(t)), StartOfMonth(t), "StartOfMonth()", t))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
if(!AssertEqual(StartOfQuarter(StartOfQuarter(t)), StartOfQuarter(t), "StartOfQuarter()", t))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
if(!AssertEqual(StartOfYear(StartOfYear(t)), StartOfYear(t), "StartOfYear()", t))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
bool ValidateOrderingProperties(const datetime t)
|
||
|
|
{
|
||
|
|
if(!Assert(StartOfMinute(t) >= StartOfHour(t), "Minute >= Hour", t))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
if(!Assert(StartOfHour(t) >= StartOfDay(t), "Hour >= Day", t))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
if(!Assert(StartOfDay(t) >= StartOfWeek(t), "Day >= Week", t))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
if(!Assert(StartOfWeek(t) <= t && t <= EndOfWeek(t), "Week contains timestamp", t))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
if(!Assert(StartOfMonth(t) >= StartOfQuarter(t), "Month >= Quarter", t))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
if(!Assert(StartOfQuarter(t) >= StartOfYear(t), "Quarter >= Year", t))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
bool ValidateIndexProperties(const datetime t)
|
||
|
|
{
|
||
|
|
if(!AssertEqual(DayIndex(StartOfDay(t)),
|
||
|
|
DayIndex(t),
|
||
|
|
"DayIndex",
|
||
|
|
t))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
if(!AssertEqual(HourIndex(StartOfHour(t)),
|
||
|
|
HourIndex(t),
|
||
|
|
"HourIndex",
|
||
|
|
t))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
if(!AssertEqual(MinuteIndex(StartOfMinute(t)),
|
||
|
|
MinuteIndex(t),
|
||
|
|
"MinuteIndex",
|
||
|
|
t))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Validate monotonicity |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
bool ValidateMonotonicityProperties(const datetime t)
|
||
|
|
{
|
||
|
|
const datetime next = t + 1;
|
||
|
|
|
||
|
|
if(next > MAX_TIME)
|
||
|
|
return true;
|
||
|
|
|
||
|
|
if(!Assert(DayIndex(next) >= DayIndex(t),
|
||
|
|
"DayIndex monotonic",
|
||
|
|
t))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
if(!Assert(HourIndex(next) >= HourIndex(t),
|
||
|
|
"HourIndex monotonic",
|
||
|
|
t))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
if(!Assert(MinuteIndex(next) >= MinuteIndex(t),
|
||
|
|
"MinuteIndex monotonic",
|
||
|
|
t))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
bool ValidateIntervalProperties(const datetime t)
|
||
|
|
{
|
||
|
|
if(!AssertEqual((datetime)59,
|
||
|
|
EndOfMinute(t)-StartOfMinute(t),
|
||
|
|
"Minute length",
|
||
|
|
t))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
if(!AssertEqual((datetime)3599,
|
||
|
|
EndOfHour(t)-StartOfHour(t),
|
||
|
|
"Hour length",
|
||
|
|
t))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
if(!AssertEqual((datetime)86399,
|
||
|
|
EndOfDay(t)-StartOfDay(t),
|
||
|
|
"Day length",
|
||
|
|
t))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
bool ValidateLeapYearProperties(const datetime t)
|
||
|
|
{
|
||
|
|
const int y = Year(t);
|
||
|
|
|
||
|
|
if(!AssertEqual(365 + (IsLeapYear(y) ? 1 : 0),
|
||
|
|
DaysInYear(y),
|
||
|
|
"DaysInYear()",
|
||
|
|
t))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
if(Month(t) == 12 && Day(t) == 31)
|
||
|
|
{
|
||
|
|
if(!AssertEqual(DaysInYear(y) - 1,
|
||
|
|
DayOfYear(t),
|
||
|
|
"DayOfYear(Dec31)",
|
||
|
|
t))
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
bool ValidateSuccessorProperties(const datetime t)
|
||
|
|
{
|
||
|
|
if(t >= MAX_TIME - 86400)
|
||
|
|
return true;
|
||
|
|
|
||
|
|
const datetime tomorrow = AddDays(t, 1);
|
||
|
|
|
||
|
|
if(!AssertEqual(1,
|
||
|
|
DifferenceInDays(t, tomorrow),
|
||
|
|
"AddDays()/DifferenceInDays()",
|
||
|
|
t))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
if(!AssertEqual(t,
|
||
|
|
AddDays(tomorrow, -1),
|
||
|
|
"AddDays inverse",
|
||
|
|
t))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Validate difference functions |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
bool ValidateDifferenceProperties(const datetime t)
|
||
|
|
{
|
||
|
|
// Identity
|
||
|
|
if(!AssertEqual(0, DifferenceInDays(t, t), "DifferenceInDays()", t))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
if(!AssertEqual(0, DifferenceInWeeks(t, t), "DifferenceInWeeks()", t))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
if(!AssertEqual(0, DifferenceInMonths(t, t), "DifferenceInMonths()", t))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
if(!AssertEqual(0, DifferenceInQuarters(t, t), "DifferenceInQuarters()", t))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
if(!AssertEqual(0, DifferenceInYears(t, t), "DifferenceInYears()", t))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
if(!AssertEqual(0, DifferenceInBusinessDays(t, t), "DifferenceInBusinessDays()", t))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
if(!AssertEqual(0, DifferenceInCalendarDays(t, t), "DifferenceInCalendarDays()", t))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
if(!AssertEqual(0, DifferenceInCalendarMonths(t, t), "DifferenceInCalendarMonths()", t))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
if(!AssertEqual(0, DifferenceInCalendarYears(t, t), "DifferenceInCalendarYears()", t))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
//----------------------------------------------------------------
|
||
|
|
// Day offsets
|
||
|
|
//----------------------------------------------------------------
|
||
|
|
for(int i = 0; i < ArraySize(DAY_OFFSETS); i++)
|
||
|
|
{
|
||
|
|
const int n = DAY_OFFSETS[i];
|
||
|
|
const datetime t2 = AddDays(t, n);
|
||
|
|
|
||
|
|
if(t2 < MIN_TIME || t2 > MAX_TIME)
|
||
|
|
continue;
|
||
|
|
|
||
|
|
if(!AssertEqual(n,
|
||
|
|
DifferenceInDays(t, t2),
|
||
|
|
"DifferenceInDays(AddDays())",
|
||
|
|
t))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
if(!AssertEqual(-n,
|
||
|
|
DifferenceInDays(t2, t),
|
||
|
|
"DifferenceInDays() symmetry",
|
||
|
|
t))
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
//----------------------------------------------------------------
|
||
|
|
// Week offsets
|
||
|
|
//----------------------------------------------------------------
|
||
|
|
for(int i = 0; i < ArraySize(WEEK_OFFSETS); i++)
|
||
|
|
{
|
||
|
|
const int n = WEEK_OFFSETS[i];
|
||
|
|
const datetime t2 = AddWeeks(t, n);
|
||
|
|
|
||
|
|
if(t2 < MIN_TIME || t2 > MAX_TIME)
|
||
|
|
continue;
|
||
|
|
|
||
|
|
if(!AssertEqual(n,
|
||
|
|
DifferenceInWeeks(t, t2),
|
||
|
|
"DifferenceInWeeks(AddWeeks())",
|
||
|
|
t))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
if(!AssertEqual(-n,
|
||
|
|
DifferenceInWeeks(t2, t),
|
||
|
|
"DifferenceInWeeks() symmetry",
|
||
|
|
t))
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
//----------------------------------------------------------------
|
||
|
|
// Month offsets
|
||
|
|
//----------------------------------------------------------------
|
||
|
|
for(int i = 0; i < ArraySize(MONTH_OFFSETS); i++)
|
||
|
|
{
|
||
|
|
const int n = MONTH_OFFSETS[i];
|
||
|
|
const datetime t2 = AddMonths(t, n);
|
||
|
|
|
||
|
|
if(t2 < MIN_TIME || t2 > MAX_TIME)
|
||
|
|
continue;
|
||
|
|
|
||
|
|
if(!AssertEqual(n,
|
||
|
|
DifferenceInMonths(t, t2),
|
||
|
|
"DifferenceInMonths(AddMonths())",
|
||
|
|
t))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
if(!AssertEqual(-n,
|
||
|
|
DifferenceInMonths(t2, t),
|
||
|
|
"DifferenceInMonths() symmetry",
|
||
|
|
t))
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
//----------------------------------------------------------------
|
||
|
|
// Quarter offsets
|
||
|
|
//----------------------------------------------------------------
|
||
|
|
for(int i = 0; i < ArraySize(QUARTER_OFFSETS); i++)
|
||
|
|
{
|
||
|
|
const int n = QUARTER_OFFSETS[i];
|
||
|
|
const datetime t2 = AddQuarters(t, n);
|
||
|
|
|
||
|
|
if(t2 < MIN_TIME || t2 > MAX_TIME)
|
||
|
|
continue;
|
||
|
|
|
||
|
|
if(!AssertEqual(n,
|
||
|
|
DifferenceInQuarters(t, t2),
|
||
|
|
"DifferenceInQuarters(AddQuarters())",
|
||
|
|
t))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
if(!AssertEqual(-n,
|
||
|
|
DifferenceInQuarters(t2, t),
|
||
|
|
"DifferenceInQuarters() symmetry",
|
||
|
|
t))
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
//----------------------------------------------------------------
|
||
|
|
// Year offsets
|
||
|
|
//----------------------------------------------------------------
|
||
|
|
for(int i = 0; i < ArraySize(YEAR_OFFSETS); i++)
|
||
|
|
{
|
||
|
|
const int n = YEAR_OFFSETS[i];
|
||
|
|
const datetime t2 = AddYears(t, n);
|
||
|
|
|
||
|
|
if(t2 < MIN_TIME || t2 > MAX_TIME)
|
||
|
|
continue;
|
||
|
|
|
||
|
|
if(!AssertEqual(n,
|
||
|
|
DifferenceInYears(t, t2),
|
||
|
|
"DifferenceInYears(AddYears())",
|
||
|
|
t))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
if(!AssertEqual(-n,
|
||
|
|
DifferenceInYears(t2, t),
|
||
|
|
"DifferenceInYears() symmetry",
|
||
|
|
t))
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
//---------------------------------------------------------------
|
||
|
|
// Round-trip: AddXXX() <-> DifferenceInXXX()
|
||
|
|
//---------------------------------------------------------------
|
||
|
|
|
||
|
|
for(int i = 0; i < ArraySize(DAY_OFFSETS); i++)
|
||
|
|
{
|
||
|
|
const int n = DAY_OFFSETS[i];
|
||
|
|
datetime t2 = AddDays(t, n);
|
||
|
|
|
||
|
|
if(t2 < MIN_TIME || t2 > MAX_TIME)
|
||
|
|
continue;
|
||
|
|
|
||
|
|
if(!AssertEqual(n,
|
||
|
|
DifferenceInDays(t, t2),
|
||
|
|
"AddDays() <-> DifferenceInDays()",
|
||
|
|
t))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
if(!AssertEqual(t,
|
||
|
|
AddDays(t2, -n),
|
||
|
|
"AddDays() inverse",
|
||
|
|
t))
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
for(int i = 0; i < ArraySize(WEEK_OFFSETS); i++)
|
||
|
|
{
|
||
|
|
const int n = WEEK_OFFSETS[i];
|
||
|
|
datetime t2 = AddWeeks(t, n);
|
||
|
|
|
||
|
|
if(t2 < MIN_TIME || t2 > MAX_TIME)
|
||
|
|
continue;
|
||
|
|
|
||
|
|
if(!AssertEqual(n,
|
||
|
|
DifferenceInWeeks(t, t2),
|
||
|
|
"AddWeeks() <-> DifferenceInWeeks()",
|
||
|
|
t))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
if(!AssertEqual(t,
|
||
|
|
AddWeeks(t2, -n),
|
||
|
|
"AddWeeks() inverse",
|
||
|
|
t))
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
for(int i = 0; i < ArraySize(MONTH_OFFSETS); i++)
|
||
|
|
{
|
||
|
|
const int n = MONTH_OFFSETS[i];
|
||
|
|
datetime t2 = AddMonths(t, n);
|
||
|
|
|
||
|
|
if(t2 < MIN_TIME || t2 > MAX_TIME)
|
||
|
|
continue;
|
||
|
|
|
||
|
|
if(!AssertEqual(n,
|
||
|
|
DifferenceInMonths(t, t2),
|
||
|
|
"AddMonths() <-> DifferenceInMonths()",
|
||
|
|
t))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
// Inverse only when round-trip is lossless (avoid month-end clamping)
|
||
|
|
if(Day(t) == Day(t2))
|
||
|
|
{
|
||
|
|
if(!AssertEqual(t,
|
||
|
|
AddMonths(t2, -n),
|
||
|
|
"AddMonths() inverse",
|
||
|
|
t))
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
for(int i = 0; i < ArraySize(QUARTER_OFFSETS); i++)
|
||
|
|
{
|
||
|
|
const int n = QUARTER_OFFSETS[i];
|
||
|
|
datetime t2 = AddQuarters(t, n);
|
||
|
|
|
||
|
|
if(t2 < MIN_TIME || t2 > MAX_TIME)
|
||
|
|
continue;
|
||
|
|
|
||
|
|
if(!AssertEqual(n,
|
||
|
|
DifferenceInQuarters(t, t2),
|
||
|
|
"AddQuarters() <-> DifferenceInQuarters()",
|
||
|
|
t))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
if(Day(t) == Day(t2))
|
||
|
|
{
|
||
|
|
if(!AssertEqual(t,
|
||
|
|
AddQuarters(t2, -n),
|
||
|
|
"AddQuarters() inverse",
|
||
|
|
t))
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
for(int i = 0; i < ArraySize(YEAR_OFFSETS); i++)
|
||
|
|
{
|
||
|
|
const int n = YEAR_OFFSETS[i];
|
||
|
|
datetime t2 = AddYears(t, n);
|
||
|
|
|
||
|
|
if(t2 < MIN_TIME || t2 > MAX_TIME)
|
||
|
|
continue;
|
||
|
|
|
||
|
|
if(!AssertEqual(n,
|
||
|
|
DifferenceInYears(t, t2),
|
||
|
|
"AddYears() <-> DifferenceInYears()",
|
||
|
|
t))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
// Skip Feb-29 and end-of-month clamping cases
|
||
|
|
if(Month(t) == Month(t2) &&
|
||
|
|
Day(t) == Day(t2))
|
||
|
|
{
|
||
|
|
if(!AssertEqual(t,
|
||
|
|
AddYears(t2, -n),
|
||
|
|
"AddYears() inverse",
|
||
|
|
t))
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Validate week functions |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
bool ValidateWeekProperties(const datetime t)
|
||
|
|
{
|
||
|
|
if(!Assert(StartOfWeek(t) <= t &&
|
||
|
|
t <= EndOfWeek(t),
|
||
|
|
"Week range",
|
||
|
|
t))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
if(!AssertEqual(StartOfWeek(StartOfWeek(t)),
|
||
|
|
StartOfWeek(t),
|
||
|
|
"StartOfWeek()",
|
||
|
|
t))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
if(!AssertEqual(WeekOfMonth(StartOfMonth(t)),
|
||
|
|
1,
|
||
|
|
"WeekOfMonth()",
|
||
|
|
t))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
if(!AssertEqual((datetime)(604800 - 1),
|
||
|
|
EndOfWeek(t) - StartOfWeek(t),
|
||
|
|
"Week length",
|
||
|
|
t))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Validate quarter functions |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
bool ValidateQuarterProperties(const datetime t)
|
||
|
|
{
|
||
|
|
int q = Quarter(t);
|
||
|
|
|
||
|
|
if(!Assert(q >= 1 && q <= 4,
|
||
|
|
"Quarter range",
|
||
|
|
t))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
if(!Assert(StartOfQuarter(t) <= t &&
|
||
|
|
t <= EndOfQuarter(t),
|
||
|
|
"Quarter boundaries",
|
||
|
|
t))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
if(!AssertEqual(Quarter(StartOfQuarter(t)),
|
||
|
|
q,
|
||
|
|
"Quarter(StartOfQuarter)",
|
||
|
|
t))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
if(!AssertEqual(Quarter(EndOfQuarter(t)),
|
||
|
|
q,
|
||
|
|
"Quarter(EndOfQuarter)",
|
||
|
|
t))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
if(!AssertEqual(StartOfQuarter(StartOfQuarter(t)),
|
||
|
|
StartOfQuarter(t),
|
||
|
|
"StartOfQuarter()",
|
||
|
|
t))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Validate business day functions |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
bool ValidateBusinessDayProperties(const datetime t)
|
||
|
|
{
|
||
|
|
if(!Assert(IsWeekend(t) != IsWeekday(t),
|
||
|
|
"Weekend/Weekday",
|
||
|
|
t))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
int dow = DayOfWeek(t);
|
||
|
|
|
||
|
|
if(dow == SATURDAY || dow == SUNDAY)
|
||
|
|
{
|
||
|
|
if(!Assert(IsWeekend(t), "Weekend", t))
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
if(!Assert(IsWeekday(t), "Weekday", t))
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
if(!AssertEqual(0,
|
||
|
|
DifferenceInBusinessDays(t, t),
|
||
|
|
"Business days",
|
||
|
|
t))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
// Introduce Test Suites
|
||
|
|
bool ValidateCore(const datetime t)
|
||
|
|
{
|
||
|
|
Stats.Core++;
|
||
|
|
|
||
|
|
return
|
||
|
|
ValidateTimestamp(t) &&
|
||
|
|
ValidateExtractors(t);
|
||
|
|
}
|
||
|
|
|
||
|
|
bool ValidateProperties(const datetime t)
|
||
|
|
{
|
||
|
|
Stats.Property++;
|
||
|
|
|
||
|
|
return
|
||
|
|
ValidateStartEndProperties(t) &&
|
||
|
|
ValidateDecompositionProperties(t) &&
|
||
|
|
ValidateIdempotenceProperties(t) &&
|
||
|
|
ValidateLeapYearProperties(t) &&
|
||
|
|
ValidateOrderingProperties(t) &&
|
||
|
|
ValidateDifferenceProperties(t) &&
|
||
|
|
ValidateIndexProperties(t) &&
|
||
|
|
ValidateIntervalProperties(t) &&
|
||
|
|
ValidateMonotonicityProperties(t) &&
|
||
|
|
ValidateWeekProperties(t) &&
|
||
|
|
ValidateQuarterProperties(t) &&
|
||
|
|
ValidateBusinessDayProperties(t) &&
|
||
|
|
ValidateCalendarDifferenceProperties(t);
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Script entry point |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
void OnStart()
|
||
|
|
{
|
||
|
|
Stats.StartTick = GetTickCount64();
|
||
|
|
|
||
|
|
Print("");
|
||
|
|
Print("============================================================");
|
||
|
|
Print(" TimeUtils Validation Suite");
|
||
|
|
Print("============================================================");
|
||
|
|
Print("");
|
||
|
|
|
||
|
|
//===============================================================
|
||
|
|
// Calendar boundary tests
|
||
|
|
//===============================================================
|
||
|
|
if(!TestCalendarBoundaries())
|
||
|
|
return;
|
||
|
|
|
||
|
|
//===============================================================
|
||
|
|
// Exhaustive daily validation
|
||
|
|
//===============================================================
|
||
|
|
if(!TestEveryDay())
|
||
|
|
return;
|
||
|
|
|
||
|
|
//===============================================================
|
||
|
|
// Exhaustive hourly validation
|
||
|
|
//===============================================================
|
||
|
|
if(!TestEveryHour())
|
||
|
|
return;
|
||
|
|
|
||
|
|
//===============================================================
|
||
|
|
// Random stress test
|
||
|
|
//===============================================================
|
||
|
|
if(!TestRandom())
|
||
|
|
return;
|
||
|
|
|
||
|
|
//===============================================================
|
||
|
|
// Summary
|
||
|
|
//===============================================================
|
||
|
|
ulong elapsed = GetTickCount64() - Stats.StartTick;
|
||
|
|
|
||
|
|
Print("");
|
||
|
|
Print("============================================================");
|
||
|
|
Print(" ALL TESTS PASSED");
|
||
|
|
Print("============================================================");
|
||
|
|
Print("Core validations : ", Stats.Core);
|
||
|
|
Print("Property validations : ", Stats.Property);
|
||
|
|
Print("Edge cases tested : ", Stats.EdgeCases);
|
||
|
|
Print("Random timestamps : ", Stats.Random);
|
||
|
|
Print("Failures : ", Stats.Failed);
|
||
|
|
Print("Elapsed : ", elapsed, " ms");
|
||
|
|
Print("============================================================");
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
/*
|
||
|
|
|
||
|
|
validate.mq5
|
||
|
|
│
|
||
|
|
├── Configuration
|
||
|
|
│
|
||
|
|
├── Statistics
|
||
|
|
│
|
||
|
|
├── Assertions
|
||
|
|
│
|
||
|
|
├── Reporting
|
||
|
|
│
|
||
|
|
├── Random Generator
|
||
|
|
│
|
||
|
|
├── Core Validation
|
||
|
|
│ ├── ValidateTimestamp
|
||
|
|
│ └── ValidateExtractors
|
||
|
|
│
|
||
|
|
├── Property Validation
|
||
|
|
│ ├── Calendar
|
||
|
|
│ ├── Arithmetic
|
||
|
|
│ ├── Specialized
|
||
|
|
│ └── Dispatcher
|
||
|
|
│
|
||
|
|
├── Test Suites
|
||
|
|
│ ├── Calendar Boundaries
|
||
|
|
│ ├── Every Day
|
||
|
|
│ ├── Every Hour
|
||
|
|
│ ├── Random Stress
|
||
|
|
│ └── Benchmark
|
||
|
|
│
|
||
|
|
└── OnStart()
|
||
|
|
|
||
|
|
*/
|
||
|
|
|
||
|
|
|
||
|
|
/*
|
||
|
|
|
||
|
|
============================================================
|
||
|
|
TimeUtils Validation Suite
|
||
|
|
============================================================
|
||
|
|
|
||
|
|
Testing calendar boundaries...
|
||
|
|
Calendar boundary tests passed.
|
||
|
|
Testing every calendar day...
|
||
|
|
50000 days checked...
|
||
|
|
100000 days checked...
|
||
|
|
150000 days checked...
|
||
|
|
200000 days checked...
|
||
|
|
250000 days checked...
|
||
|
|
300000 days checked...
|
||
|
|
350000 days checked...
|
||
|
|
Every day passed (376565 days).
|
||
|
|
Testing every hour...
|
||
|
|
1000000 hours checked...
|
||
|
|
2000000 hours checked...
|
||
|
|
3000000 hours checked...
|
||
|
|
4000000 hours checked...
|
||
|
|
5000000 hours checked...
|
||
|
|
6000000 hours checked...
|
||
|
|
7000000 hours checked...
|
||
|
|
8000000 hours checked...
|
||
|
|
9000000 hours checked...
|
||
|
|
Every hour passed (9037560 hours).
|
||
|
|
Random stress test...
|
||
|
|
10% completed...
|
||
|
|
20% completed...
|
||
|
|
30% completed...
|
||
|
|
40% completed...
|
||
|
|
50% completed...
|
||
|
|
60% completed...
|
||
|
|
70% completed...
|
||
|
|
80% completed...
|
||
|
|
90% completed...
|
||
|
|
100% completed...
|
||
|
|
Random stress test passed.
|
||
|
|
|
||
|
|
============================================================
|
||
|
|
ALL TESTS PASSED
|
||
|
|
============================================================
|
||
|
|
Core validations : 60000000
|
||
|
|
Property validations : 600000
|
||
|
|
Edge cases tested : 621938
|
||
|
|
Random timestamps : 60000000
|
||
|
|
Failures : 0
|
||
|
|
Elapsed : 6484 ms
|
||
|
|
============================================================
|
||
|
|
|
||
|
|
*/
|