日期和时间
datetime 类型的值(旨在用于存储 日期和/或时间 )通常经过数种类型的转换:
- 与字符串相互转换,以向用户显示数据,以及从外部源读取数据
- 转换为特殊结构体 MqlDateTime(见下文),以处理单独日期和时间分量
- 转换为自 01/01/1970 以来的秒数,对应于 datetime 的内部表示,并等效于整数类型 long
对于最后一项,使用 datetime 到 (long) 强制转换或 long 到 (datetime) 反向强制转换,但请注意支持的日期范围从 1970 年 1 月 1 日(值 0)到 3000 年 12 月 31 日(32535215999 秒)。
对于前两个选项,MQL5 API 提供了以下函数。
string TimeToString(datetime value, int mode = TIME_DATE | TIME_MINUTES)
TimeToString 函数可根据 mode 参数(可在该参数中指定任意的标志组合),将 datetime 类型的值转换为包含日期和时间分量的字符串:
- TIME_DATE 格式为 "YYYY.MM.DD" 的日期
- TIME_MINUTES 格式为 "hh:mm" 的时间,即带小时和分钟
- TIME_SECONDS 格式为 "hh:mm:ss" 的时间,即带小时、分钟和秒
要完整输出日期和时间数据,可将 mode 设置为等于 TIME_DATE | TIME_SECONDS(TIME_DATE | TIME_MINUTES | TIME_SECONDS 选项同样有效,但多余)。这等效于将类型 datetime 的值强制转换为 (string)。
使用示例在 ConversionTime.mq5 文件中提供。
#define PRT(A) Print(#A, "=", (A))
void OnStart()
{
datetime time = D'2021.01.21 23:00:15';
PRT((string)time);
PRT(TimeToString(time));
PRT(TimeToString(time, TIME_DATE | TIME_MINUTES | TIME_SECONDS));
PRT(TimeToString(time, TIME_MINUTES | TIME_SECONDS));
PRT(TimeToString(time, TIME_DATE | TIME_SECONDS));
PRT(TimeToString(time, TIME_DATE));
PRT(TimeToString(time, TIME_MINUTES));
PRT(TimeToString(time, TIME_SECONDS));
}
|
脚本将打印以下日志:
(string)time=2021.01.21 23:00:15
TimeToString(time)=2021.01.21 23:00
TimeToString(time,TIME_DATE|TIME_MINUTES|TIME_SECONDS)=2021.01.21 23:00:15
TimeToString(time,TIME_MINUTES|TIME_SECONDS)=23:00:15
TimeToString(time,TIME_DATE|TIME_SECONDS)=2021.01.21 23:00:15
TimeToString(time,TIME_DATE)=2021.01.21
TimeToString(time,TIME_MINUTES)=23:00
TimeToString(time,TIME_SECONDS)=23:00:15
|
datetime StringToTime(string value)
StringToTime 函数将包含日期和/或时间的字符串转换为 datetime 类型的值。字符串可仅包含日期、仅包含时间,也可以同时包含日期和时间。
以下格式被视为日期:
- "YYYY.MM.DD"
- "YYYYMMDD"
- "YYYY/MM/DD"
- "YYYY-MM-DD"
- "DD.MM.YYYY"
- "DD/MM/YYYY"
- "DD-MM-YYYY"
以下格式支持时间:
- "hh:mm"
- "hh:mm:ss"
- "hhmmss"
日期和时间之间必须有至少一个空格。
如果字符串中仅有时间,则在结果中将替换为当前日期。如果字符串中仅有日期,则时间将设为 00:00:00。
如果字符串中支持的语法出现错误,则结果为当前日期。
函数使用示例在脚本 ConversionTime.mq5 中提供。
void OnStart()
{
string timeonly = "21:01"; // time only
PRT(timeonly);
PRT((datetime)timeonly);
PRT(StringToTime(timeonly));
string date = "2000-10-10"; // date only
PRT((datetime)date);
PRT(StringToTime(date));
PRT((long)(datetime)date);
long seconds = 60;
PRT((datetime)seconds); // 1 minute from the beginning of 1970
string ddmmyy = "15/01/2012 01:02:03"; // date and time, and the date in
PRT(StringToTime(ddmmyy)); // in "forward" order, still ok
string wrong = "January 2-nd";
PRT(StringToTime(wrong));
}
|
在日志,将看到类似如下所示的内容(####.##.## 是启动脚本的当前日期):
timeonly=21:01
(datetime)timeonly=####.##.## 21:01:00
StringToTime(timeonly)=####.##.## 21:01:00
(datetime)date=2000.10.10 00:00:00
StringToTime(date)=2000.10.10 00:00:00
(long)(datetime)date=971136000
(datetime)seconds=1970.01.01 00:01:00
StringToTime(ddmmyy)=2012.01.15 01:02:03
(datetime)wrong=####.##.## 00:00:00
|
除了 StringToTime,还可以使用强制转换运算符 (datetime) 将字符串转换为日期和时间。然而,函数的优势在于,当检测到不正确的源字符串时,函数会设置错误代码为 _LastError 的内部变量(该错误代码也可通过函数 GetLastError获取)。根据字符串的哪部分包含未解读数据,错误代码可能是 ERR_WRONG_STRING_DATE (5031)、ERR_WRONG_STRING_TIME (5032),或者是列表中与从字符串获取日期和时间相关的其它选项。
bool TimeToStruct(datetime value, MqlDateTime &struct)
为单独解析日期和时间,MQL5 API 提供了 TimeToStruct 函数,该函数将 datetime 类型的值转换为 MqlDateTime 结构体:
struct MqlDateTime
{
int year; // year
int mon; // month
int day; // day
int hour; // hour
int min; // minutes
int sec; // seconds
int day_of_week; // day of the week
int day_of_year; // the number of the day in a year (January 1 has number 0)
};
|
周天数以美国格式编号:0 代表周日,1 代表周一,以此类推,直至 6 代表周六。可使用内置的 ENUM_DAY_OF_WEEK 枚举识别。
如果成功,则函数返回 true,如果出错,则返回 false,尤其是在传递了不正确日期的情况下。
让我们使用 ConversionTimeStruct.mq5 脚本检查函数的执行。为此,我们创建包含测试值的 time 数组(类型为 datetime)。我们将在循环中为每个测试值调用 TimeToStruct。
结果将添加至结构体数组 MqlDateTime mdt[]。我们将首先将其初始化为零,但由于内置函数 ArrayInitialize 不知道如何处理这些结构体,我们必须为其编写一个重载(以后我们将在以下章节学习以更方便的方式以零填充数组: 将对象和数组清零 将介绍 ZeroMemory 函数)。
int ArrayInitialize(MqlDateTime &mdt[], MqlDateTime &init)
{
const int n = ArraySize(mdt);
for(int i = 0; i < n; ++i)
{
mdt[i] = init;
}
return n;
}
|
在该过程之后,我们将使用内置函数 ArrayPrint将结构体数组输出到日志。这是实现良好数据格式的最简便方法(即使只有一个结构体也可以使用:只需将其放入一个大小为 1 的数组即可)。
void OnStart()
{
// fill the array with tests
datetime time[] =
{
D'2021.01.28 23:00:15', // valid datetime value
D'3000.12.31 23:59:59', // the largest supported date and time
LONG_MAX // invalid date: will cause an error ERR_INVALID_DATETIME (4010)
};
// calculate the size of the array at compile time
const int n = sizeof(time) / sizeof(datetime);
MqlDateTime null = {}; // example with zeros
MqlDateTime mdt[];
// allocating memory for an array of structures with results
ArrayResize(mdt, n);
// call our ArrayInitialize overload
ArrayInitialize(mdt, null);
// run tests
for(int i = 0; i < n; ++i)
{
PRT(time[i]); // displaying initial data
if(!TimeToStruct(time[i], mdt[i])) // if an error occurs, output its code
{
Print("error: ", _LastError);
mdt[i].year = _LastError;
}
}
// output the results to the log
ArrayPrint(mdt);
...
}
|
结果,我们在日志中获得以下字符串:
time[i]=2021.01.28 23:00:15
time[i]=3000.12.31 23:59:59
time[i]=wrong datetime
wrong datetime -> 4010
[year] [mon] [day] [hour] [min] [sec] [day_of_week] [day_of_year]
[0] 2021 1 28 23 0 15 4 27
[1] 3000 12 31 23 59 59 3 364
[2] 4010 0 0 0 0 0 0 0
|
你可确保所有字段已接收到适当的值。对于不正确初始日期,我们将错误代码保存在 year 字段(在此情况下,仅有一个此类错误:4010, ERR_INVALID_DATETIME)。
别忘了,针对 MQL5 中最大日期值,引入了 DATETIME_MAX 常量,等于整数值 0x793406fff,其对应于 3000 年 12 月 31 日 23:59:59。
使用 TimeToStruct 函数解决的最常见问题,是获取特定日期/时间分量的值。因此,应准备一个具有就绪实施选项的辅助头文件 (MQL5Book/DateTime.mqh)。该文件具有 datetime 类。
class DateTime
{
private:
MqlDateTime mdtstruct;
datetime origin;
DateTime() : origin(0)
{
TimeToStruct(0, mdtstruct);
}
void convert(const datetime &dt)
{
if(origin != dt)
{
origin = dt;
TimeToStruct(dt, mdtstruct);
}
}
public:
static DateTime *assign(const datetime dt)
{
_DateTime.convert(dt);
return &_DateTime;
}
ENUM_DAY_OF_WEEK timeDayOfWeek() const
{
return (ENUM_DAY_OF_WEEK)mdtstruct.day_of_week;
}
int timeDayOfYear() const
{
return mdtstruct.day_of_year;
}
int timeYear() const
{
return mdtstruct.year;
}
int timeMonth() const
{
return mdtstruct.mon;
}
int timeDay() const
{
return mdtstruct.day;
}
int timeHour() const
{
return mdtstruct.hour;
}
int timeMinute() const
{
return mdtstruct.min;
}
int timeSeconds() const
{
return mdtstruct.sec;
}
static DateTime _DateTime;
};
static DateTime DateTime::_DateTime;
|
该类具有若干个宏,更方便调用其方法。
#define TimeDayOfWeek(T) DateTime::assign(T).timeDayOfWeek()
#define TimeDayOfYear(T) DateTime::assign(T).timeDayOfYear()
#define TimeYear(T) DateTime::assign(T).timeYear()
#define TimeMonth(T) DateTime::assign(T).timeMonth()
#define TimeDay(T) DateTime::assign(T).timeDay()
#define TimeHour(T) DateTime::assign(T).timeHour()
#define TimeMinute(T) DateTime::assign(T).timeMinute()
#define TimeSeconds(T) DateTime::assign(T).timeSeconds()
#define _TimeDayOfWeek DateTime::_DateTime.timeDayOfWeek
#define _TimeDayOfYear DateTime::_DateTime.timeDayOfYear
#define _TimeYear DateTime::_DateTime.timeYear
#define _TimeMonth DateTime::_DateTime.timeMonth
#define _TimeDay DateTime::_DateTime.timeDay
#define _TimeHour DateTime::_DateTime.timeHour
#define _TimeMinute DateTime::_DateTime.timeMinute
#define _TimeSeconds DateTime::_DateTime.timeSeconds
|
该类具有 MqlDateTime 结构体类型的 mdtstruct 字段。该字段用于所有内部转换中。结构体字段使用 getter 方法读取:为每个字段分配了对应方法。
类中定义了一个静态实例:_DateTime(一个对象已足够,因为所有 MQL 程序均为单线程)。构造函数为私有,因此试图创建其它 datetime 对象将会失败。
使用宏,我们可以从 datetime 方便地获取单独的组成部分,如年 (TimeYear(T))、月 (TimeMonth(T))、日 (TimeDay(T)),或星期几 (TimeDayOfWeek(T))。
如果从一个 datetime 值获取,则需要获取若干个字段,最好在所有调用中使用相似的宏(第一个不带参数并以下划线符号开头的调用除外):它们从结构体中读取所需的字段,无需重置日期/时间,也无需调用 TimeToStruct 函数。例如:
// use the DateTime class from MQL5Book/DateTime.mqh:
// first get the day of the week for the specified datetime value
PRT(EnumToString(TimeDayOfWeek(time[0])));
// then read year, month and day for the same value
PRT(_TimeYear());
PRT(_TimeMonth());
PRT(_TimeDay());
|
以下字符串应出现在日志中。
EnumToString(DateTime::_DateTime.assign(time[0]).__TimeDayOfWeek())=THURSDAY
DateTime::_DateTime.__TimeYear()=2021
DateTime::_DateTime.__TimeMonth()=1
DateTime::_DateTime.__TimeDay()=28
|
内置函数 EnumToString 可将任何一个枚举元素转换为一个字符串。这将在 单独章节中介绍。
Datetime StructToTime(MqlDateTime &struct)
StructToTime 函数可将包含日期和时间组成部分的 MqlDateTime 结构体(参见上述 TimeToStruct 函数)转换为 datetime 类型的值。day_of_week 和 day_of_year 字段未使用。
如果其余字段的状态为无效(对应于不存在或不支持的日期),则函数可能返回不正确的值,或者返回 WRONG_VALUE(long 类型表示的 -1),视问题而定。因此,应基于全局变量 _LastError的状态检查是否存在错误。成功完成的转换显示代码 0。在转换之前,应重置 _LastError 中的可能失败状态(作为某些先前指令执行的遗留物),可使用 ResetLastError 函数进行重置。
StructToTime 函数测试也在脚本 ConversionTimeStruct.mq5 中提供。结构体数组 parts 转换为循环中的 datetime。
MqlDateTime parts[] =
{
{0, 0, 0, 0, 0, 0, 0, 0},
{100, 0, 0, 0, 0, 0, 0, 0},
{2021, 2, 30, 0, 0, 0, 0, 0},
{2021, 13, -5, 0, 0, 0, 0, 0},
{2021, 50, 100, 0, 0, 0, 0, 0},
{2021, 10, 20, 15, 30, 155, 0, 0},
{2021, 10, 20, 15, 30, 55, 0, 0},
};
ArrayPrint(parts);
Print("");
// convert all elements in the loop
for(int i = 0; i < sizeof(parts) / sizeof(MqlDateTime); ++i)
{
ResetLastError();
datetime result = StructToTime(parts[i]);
Print("[", i, "] ", (long)result, " ", result, " ", _LastError);
}
|
对于每个元素,显示结果值和错误代码。
[year] [mon] [day] [hour] [min] [sec] [day_of_week] [day_of_year]
[0] 0 0 0 0 0 0 0 0
[1] 100 0 0 0 0 0 0 0
[2] 2021 2 30 0 0 0 0 0
[3] 2021 13 -5 0 0 0 0 0
[4] 2021 50 100 0 0 0 0 0
[5] 2021 10 20 15 30 155 0 0
[6] 2021 10 20 15 30 55 0 0
[0] -1 wrong datetime 4010
[1] 946684800 2000.01.01 00:00:00 4010
[2] 1614643200 2021.03.02 00:00:00 0
[3] 1638316800 2021.12.01 00:00:00 4010
[4] 1640908800 2021.12.31 00:00:00 4010
[5] 1634743859 2021.10.20 15:30:59 4010
[6] 1634743855 2021.10.20 15:30:55 0
|
请注意,该函数会在不生成错误标记的情况下更正某些值。在第 2 个元素中,我们将日期 2021 年 2 月 30 日传递进函数,函数会将其转换为 2021 年 3 月 2 日,并且 _LastError = 0。