Ngày và Giờ
Giá trị kiểu datetime
được dùng để lưu trữ ngày và/hoặc giờ thường trải qua một số loại chuyển đổi:
- Chuyển thành chuỗi và ngược lại để hiển thị dữ liệu cho người dùng và đọc dữ liệu từ các nguồn bên ngoài.
- Chuyển thành các cấu trúc đặc biệt
MqlDateTime
(xem bên dưới) để làm việc với các thành phần ngày và giờ riêng lẻ. - Chuyển thành số giây trôi qua kể từ ngày 01/01/1970, tương ứng với biểu diễn nội bộ của
datetime
và tương đương với kiểu số nguyênlong
.
Đối với mục cuối cùng, sử dụng ép kiểu từ datetime
sang (long)
, hoặc ngược lại từ long
sang (datetime)
, nhưng lưu ý rằng phạm vi ngày được hỗ trợ là từ ngày 1 tháng 1 năm 1970 (giá trị 0) đến ngày 31 tháng 12 năm 3000 (32535215999 giây).
Đối với hai tùy chọn đầu tiên, MQL5 API cung cấp các hàm sau.
string TimeToString(datetime value, int mode = TIME_DATE | TIME_MINUTES)
Hàm TimeToString
chuyển đổi một giá trị kiểu datetime
thành chuỗi chứa các thành phần ngày và giờ, theo tham số mode
mà bạn có thể đặt bất kỳ tổ hợp nào của các cờ:
TIME_DATE
— ngày theo định dạng "YYYY.MM.DD".TIME_MINUTES
— giờ theo định dạng "hh:mm", tức là bao gồm giờ và phút.TIME_SECONDS
— giờ theo định dạng "hh:mm:ss", tức là bao gồm giờ, phút và giây.
Để xuất dữ liệu ngày và giờ đầy đủ, bạn có thể đặt mode
bằng TIME_DATE | TIME_SECONDS
(tùy chọn TIME_DATE | TIME_MINUTES | TIME_SECONDS
cũng hoạt động, nhưng dư thừa). Điều này tương đương với việc ép kiểu giá trị kiểu datetime
sang (string)
.
Ví dụ sử dụng được cung cấp trong tệp 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));
}
2
3
4
5
6
7
8
9
10
11
12
13
14
Script sẽ in ra nhật ký sau:
(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
2
3
4
5
6
7
8
datetime StringToTime(string value)
Hàm StringToTime
chuyển đổi một chuỗi chứa ngày và/hoặc giờ thành giá trị kiểu datetime
. Chuỗi có thể chỉ chứa ngày, chỉ chứa giờ, hoặc cả ngày và giờ cùng nhau.
Các định dạng được nhận diện cho ngày:
YYYY.MM.DD
YYYYMMDD
YYYY/MM/DD
YYYY-MM-DD
DD.MM.YYYY
DD/MM/YYYY
DD-MM-YYYY
Các định dạng được hỗ trợ cho giờ:
hh:mm
hh:mm:ss
hhmmss
Phải có ít nhất một khoảng trắng giữa ngày và giờ.
Nếu chuỗi chỉ chứa giờ, ngày hiện tại sẽ được thay thế vào kết quả. Nếu chuỗi chỉ chứa ngày, giờ sẽ được đặt thành 00:00:00.
Nếu cú pháp được hỗ trợ trong chuỗi bị sai, kết quả sẽ là ngày hiện tại.
Ví dụ sử dụng hàm được đưa ra trong script ConversionTime.mq5
.
void OnStart()
{
string timeonly = "21:01"; // chỉ giờ
PRT(timeonly);
PRT((datetime)timeonly);
PRT(StringToTime(timeonly));
string date = "2000-10-10"; // chỉ ngày
PRT((datetime)date);
PRT(StringToTime(date));
PRT((long)(datetime)date);
long seconds = 60;
PRT((datetime)seconds); // 1 phút kể từ đầu năm 1970
string ddmmyy = "15/01/2012 01:02:03"; // ngày và giờ, ngày theo thứ tự "tiến"
PRT(StringToTime(ddmmyy)); // vẫn ổn
string wrong = "January 2-nd";
PRT(StringToTime(wrong));
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Trong nhật ký, chúng ta sẽ thấy nội dung như sau (####.##.## là ngày hiện tại khi script được chạy):
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
2
3
4
5
6
7
8
9
Ngoài StringToTime
, bạn có thể sử dụng toán tử ép kiểu (datetime)
để chuyển đổi chuỗi thành ngày và giờ. Tuy nhiên, ưu điểm của hàm là khi phát hiện chuỗi nguồn không đúng, hàm sẽ đặt một biến nội bộ với mã lỗi _LastError
(cũng có thể truy cập qua hàm GetLastError
). Tùy thuộc vào phần nào của chuỗi chứa dữ liệu không thể diễn giải, mã lỗi có thể là ERR_WRONG_STRING_DATE
(5031), ERR_WRONG_STRING_TIME
(5032) hoặc một tùy chọn khác từ danh sách liên quan đến việc lấy ngày và giờ từ chuỗi.
bool TimeToStruct(datetime value, MqlDateTime &struct)
Để phân tích riêng các thành phần ngày và giờ, MQL5 API cung cấp hàm TimeToStruct
chuyển đổi giá trị kiểu datetime
thành cấu trúc MqlDateTime
:
struct MqlDateTime
{
int year; // năm
int mon; // tháng
int day; // ngày
int hour; // giờ
int min; // phút
int sec; // giây
int day_of_week; // ngày trong tuần
int day_of_year; // số ngày trong năm (ngày 1 tháng 1 có số 0)
};
2
3
4
5
6
7
8
9
10
11
Các ngày trong tuần được đánh số theo cách của Mỹ: 0 cho Chủ nhật, 1 cho Thứ hai, và tiếp tục đến 6 cho Thứ bảy. Chúng có thể được xác định bằng cách sử dụng liệt kê tích hợp ENUM_DAY_OF_WEEK
.
Hàm trả về true
nếu thành công và false
khi có lỗi, đặc biệt nếu một ngày không đúng được truyền vào.
Hãy kiểm tra hiệu suất của hàm bằng script ConversionTimeStruct.mq5
. Để làm điều này, chúng ta sẽ tạo mảng time
kiểu datetime
với các giá trị thử nghiệm. Chúng ta sẽ gọi TimeToStruct
cho từng giá trị trong một vòng lặp.
Kết quả sẽ được thêm vào mảng cấu trúc MqlDateTime mdt[]
. Chúng ta sẽ khởi tạo nó với các số 0 trước, nhưng vì hàm tích hợp ArrayInitialize
không biết cách xử lý cấu trúc, chúng ta sẽ phải viết một hàm nạp chồng cho nó (trong tương lai, chúng ta sẽ học cách dễ hơn để điền mảng với số 0: trong phần Xóa dữ liệu đối tượng và mảng, hàm ZeroMemory
sẽ được giới thiệu).
int ArrayInitialize(MqlDateTime &mdt[], MqlDateTime &init)
{
const int n = ArraySize(mdt);
for(int i = 0; i < n; ++i)
{
mdt[i] = init;
}
return n;
}
2
3
4
5
6
7
8
9
Sau quá trình này, chúng ta sẽ xuất mảng cấu trúc ra nhật ký bằng hàm tích hợp ArrayPrint
. Đây là cách dễ nhất để cung cấp định dạng dữ liệu đẹp (nó có thể được sử dụng ngay cả khi chỉ có một cấu trúc: chỉ cần đặt nó vào mảng kích thước 1).
void OnStart()
{
// điền mảng với các thử nghiệm
datetime time[] =
{
D'2021.01.28 23:00:15', // giá trị ngày giờ hợp lệ
D'3000.12.31 23:59:59', // ngày và giờ lớn nhất được hỗ trợ
LONG_MAX // ngày không hợp lệ: sẽ gây lỗi ERR_INVALID_DATETIME (4010)
};
// tính kích thước mảng tại thời điểm biên dịch
const int n = sizeof(time) / sizeof(datetime);
MqlDateTime null = {}; // ví dụ với số 0
MqlDateTime mdt[];
// cấp phát bộ nhớ cho mảng cấu trúc chứa kết quả
ArrayResize(mdt, n);
// gọi hàm nạp chồng ArrayInitialize của chúng ta
ArrayInitialize(mdt, null);
// chạy thử nghiệm
for(int i = 0; i < n; ++i)
{
PRT(time[i]); // hiển thị dữ liệu ban đầu
if(!TimeToStruct(time[i], mdt[i])) // nếu xảy ra lỗi, xuất mã lỗi
{
Print("error: ", _LastError);
mdt[i].year = _LastError;
}
}
// xuất kết quả ra nhật ký
ArrayPrint(mdt);
...
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
Kết quả, chúng ta nhận được các chuỗi sau trong nhật ký:
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
2
3
4
5
6
7
8
Bạn có thể chắc chắn rằng tất cả các trường đã nhận được giá trị phù hợp. Đối với các ngày ban đầu không đúng, chúng ta lưu mã lỗi trong trường year
(trong trường hợp này, chỉ có một lỗi: 4010, ERR_INVALID_DATETIME
).
Hãy nhớ rằng đối với giá trị ngày tối đa trong MQL5, hằng số DATETIME_MAX
được giới thiệu, bằng giá trị số nguyên 0x793406fff
, tương ứng với 23:59:59 ngày 31 tháng 12 năm 3000.
Vấn đề phổ biến nhất được giải quyết bằng hàm TimeToStruct
là lấy giá trị của một thành phần ngày/giờ cụ thể. Do đó, việc chuẩn bị một tệp tiêu đề phụ trợ (MQL5Book/DateTime.mqh
) với một tùy chọn triển khai sẵn là hợp lý. Tệp này có lớp 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;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
Lớp đi kèm với một số macro giúp việc gọi các phương thức của nó dễ dàng hơn.
#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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Lớp có trường mdtstruct
kiểu cấu trúc MqlDateTime
. Trường này được sử dụng trong tất cả các chuyển đổi nội bộ. Các trường của cấu trúc được đọc bằng các phương thức getter: mỗi trường được phân bổ một phương thức tương ứng.
Một thể hiện tĩnh được định nghĩa bên trong lớp: _DateTime
(một đối tượng là đủ, vì tất cả các chương trình MQL đều là đơn luồng). Hàm tạo là riêng tư, vì vậy việc cố gắng tạo các đối tượng datetime
khác sẽ thất bại.
Sử dụng macro, chúng ta có thể dễ dàng nhận các thành phần riêng lẻ từ datetime
, ví dụ: năm (TimeYear(T)
), tháng (TimeMonth(T)
), số (TimeDay(T)
), hoặc ngày trong tuần (TimeDayOfWeek(T)
).
Nếu từ một giá trị datetime
cần lấy nhiều trường, thì tốt hơn nên sử dụng các macro tương tự trong tất cả các lệnh gọi ngoại trừ lệnh đầu tiên mà không có tham số và bắt đầu bằng ký hiệu gạch dưới: chúng đọc trường mong muốn từ cấu trúc mà không cần đặt lại ngày/giờ và gọi hàm TimeToStruct
. Ví dụ:
// sử dụng lớp DateTime từ MQL5Book/DateTime.mqh:
// trước tiên lấy ngày trong tuần cho giá trị datetime được chỉ định
PRT(EnumToString(TimeDayOfWeek(time[0])));
// sau đó đọc năm, tháng và ngày cho cùng giá trị
PRT(_TimeYear());
PRT(_TimeMonth());
PRT(_TimeDay());
2
3
4
5
6
7
Các chuỗi sau nên xuất hiện trong nhật ký:
EnumToString(DateTime::_DateTime.assign(time[0]).__TimeDayOfWeek())=THURSDAY
DateTime::_DateTime.__TimeYear()=2021
DateTime::_DateTime.__TimeMonth()=1
DateTime::_DateTime.__TimeDay()=28
2
3
4
Hàm tích hợp EnumToString
chuyển đổi một phần tử của bất kỳ liệt kê nào thành chuỗi. Nó sẽ được mô tả trong một phần riêng.
datetime StructToTime(MqlDateTime &struct)
Hàm StructToTime
thực hiện chuyển đổi từ cấu trúc MqlDateTime
(xem mô tả của hàm TimeToStruct
ở trên) chứa các thành phần ngày và giờ, thành giá trị kiểu datetime
. Các trường day_of_week
và day_of_year
không được sử dụng.
Nếu trạng thái của các trường còn lại không hợp lệ (tương ứng với ngày không tồn tại hoặc không được hỗ trợ), hàm có thể trả về giá trị đã được sửa hoặc WRONG_VALUE
(-1 trong biểu diễn kiểu long
), tùy thuộc vào vấn đề. Do đó, bạn nên kiểm tra lỗi dựa trên trạng thái của biến toàn cục _LastError
. Chuyển đổi thành công hoàn tất với mã 0. Trước khi chuyển đổi, bạn nên đặt lại trạng thái thất bại có thể có trong _LastError
(được giữ lại như một dấu vết của việc thực thi một số lệnh trước đó) bằng hàm ResetLastError
.
Kiểm tra hàm StructToTime
cũng được cung cấp trong script ConversionTimeStruct.mq5
. Mảng cấu trúc parts
được chuyển đổi thành datetime
trong vòng lặp.
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("");
// chuyển đổi tất cả các phần tử trong vòng lặp
for(int i = 0; i < sizeof(parts) / sizeof(MqlDateTime); ++i)
{
ResetLastError();
datetime result = StructToTime(parts[i]);
Print("[", i, "] ", (long)result, " ", result, " ", _LastError);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Đối với mỗi phần tử, giá trị kết quả và mã lỗi được hiển thị.
[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
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Lưu ý rằng hàm sửa một số giá trị mà không kích hoạt cờ lỗi. Vì vậy, ở phần tử số 2, chúng ta đã truyền ngày 30 tháng 2 năm 2021 vào hàm, và nó được chuyển thành ngày 2 tháng 3 năm 2021, với _LastError = 0
.