日期和时间

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(APrint(#A"=", (A))
 
void OnStart()
{
   datetime time = D'2021.01.21 23:00:15';
   PRT((string)time);
   PRT(TimeToString(time));
   PRT(TimeToString(timeTIME_DATE | TIME_MINUTES | TIME_SECONDS));
   PRT(TimeToString(timeTIME_MINUTES | TIME_SECONDS));
   PRT(TimeToString(timeTIME_DATE | TIME_SECONDS));
   PRT(TimeToString(timeTIME_DATE));
   PRT(TimeToString(timeTIME_MINUTES));
   PRT(TimeToString(timeTIME_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 = 0i < 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(mdtn);
   
   // call our ArrayInitialize overload 
   ArrayInitialize(mdtnull);
   
   // run tests
   for(int i = 0i < 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(0mdtstruct);
   }
   
   void convert(const datetime &dt)
   {
      if(origin != dt)
      {
         origin = dt;
         TimeToStruct(dtmdtstruct);
      }
   }
   
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(TDateTime::assign(T).timeDayOfWeek()
#define TimeDayOfYear(TDateTime::assign(T).timeDayOfYear()
#define TimeYear(TDateTime::assign(T).timeYear()
#define TimeMonth(TDateTime::assign(T).timeMonth()
#define TimeDay(TDateTime::assign(T).timeDay()
#define TimeHour(TDateTime::assign(T).timeHour()
#define TimeMinute(TDateTime::assign(T).timeMinute()
#define TimeSeconds(TDateTime::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[] =
   {
      {00000000},
      {1000000000},
      {202123000000},
      {202113, -500000},
      {20215010000000},
      {20211020153015500},
      {2021102015305500},
   };
   ArrayPrint(parts);
   Print("");
   
   // convert all elements in the loop
   for(int i = 0i < 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