# Múi giờ và ngày chốt sổ: đơn ghi lúc 23:30 thuộc ngày nào

> Một đơn ghi lúc 23:30 trong cơ sở dữ liệu thuộc ngày 25 hay ngày 26 tùy múi giờ dùng để cắt ngày. Nếu máy chủ ghi giờ UTC (giờ phối hợp quốc tế, không cộng

URL: https://mona.software/mui-gio-ngay-chot-so-don-luc-2330-thuoc-ngay-nao/
Ngày đăng: 2026-10-26T10:34:19+07:00
Cập nhật: 2026-10-26T10:34:25+07:00
Tác giả: monamedia

Một đơn ghi lúc 23:30 trong cơ sở dữ liệu thuộc ngày 25 hay ngày 26 tùy múi giờ dùng để cắt ngày. Nếu máy chủ ghi giờ UTC (giờ phối hợp quốc tế, không cộng trừ), đơn đó xảy ra lúc 06:30 sáng ngày 26 theo giờ Việt Nam (UTC+7), và báo cáo cắt ngày theo UTC xếp nó vào ngày 25. Trong thử nghiệm chạy thật, 4 đơn tổng 550.000 đồng thành một ngày duy nhất theo UTC, nhưng thành 420.000 đồng ngày 25 và 130.000 đồng ngày 26 theo giờ Việt Nam. Chênh 130.000 đồng giữa báo cáo của lập trình viên và sổ của kế toán nằm đúng ở đó.

Mọi câu [SQL](https://mona.software/sql-la-gi/) và kết quả trong bài chạy trên MySQL 8.4.8, ngày 26/10/2026, trong một cơ sở dữ liệu thử riêng đã xóa sau khi chạy. Bản dữ liệu múi giờ IANA (cơ sở dữ liệu múi giờ dùng chung của hầu hết hệ điều hành) là 2026e.

## Một thời điểm chỉ có ngày khi đã chọn múi giờ để cắt

Thời điểm 23:30 UTC ngày 25/10/2026 là một khoảnh khắc duy nhất trên đồng hồ thế giới. Khoảnh khắc đó có ngày ghi trên lịch chỉ sau khi chọn múi giờ: ngày 25 ở UTC, ngày 26 ở UTC+7. Mỗi báo cáo doanh thu theo ngày vì vậy có một múi giờ ngầm, dù người viết câu truy vấn có nghĩ đến nó hay không.

Bảng dưới liệt kê 4 đơn thử, ghi theo giờ UTC như một máy chủ đặt ở UTC vẫn làm, cùng ngày của mỗi đơn khi cắt theo từng múi giờ:

| Mã đơn | Số tiền (đồng) | Giờ ghi trong cơ sở dữ liệu (UTC) | Giờ Việt Nam (UTC+7) | Ngày theo UTC | Ngày theo giờ Việt Nam |
| --- | --- | --- | --- | --- | --- |
| 1 | 300.000 | 25/10 10:00 | 25/10 17:00 | 25 | 25 |
| 2 | 120.000 | 25/10 16:30 | 25/10 23:30 | 25 | 25 |
| 3 | 80.000 | 25/10 17:20 | 26/10 00:20 | 25 | 26 |
| 4 | 50.000 | 25/10 23:30 | 26/10 06:30 | 25 | 26 |

Đơn 2 khách đặt lúc 23:30 giờ Việt Nam thuộc ngày 25 theo cả hai cách cắt. Chỉ các đơn đặt từ 00:00 đến 07:00 sáng giờ Việt Nam mới đổi ngày, tức 7 tiếng mỗi ngày mà hai báo cáo có thể bất đồng. Múi giờ nghiệp vụ của cửa hàng ở Việt Nam là Asia/Ho_Chi_Minh. Dòng cuối khai báo của [IANA](https://github.com/eggert/tz/blob/main/asia) cho tên này ghi UTC+7 từ ngày 13/06/1975 đến nay, cột quy tắc là dấu gạch ngang nên không có giờ mùa hè. Vì vậy một độ lệch cố định `+07:00` cho cùng ngày với tên múi giờ, với dữ liệu từ 1975 trở đi.

![Tệp asia của IANA tz database khai báo múi giờ Asia/Ho_Chi_Minh với giờ UTC+7 từ năm 1975](https://mona.software/wp-content/uploads/2026/10/iana-tzdb-asia-ho-chi-minh-mona.webp)

Khai báo Zone Asia/Ho_Chi_Minh trong tệp asia của IANA tz database, dòng cuối là UTC+7 không kèm quy tắc giờ mùa hè. Chụp ngày 26/10/2026.

## TIMESTAMP đổi giờ theo phiên kết nối, DATETIME giữ nguyên con số đã ghi

MySQL dùng 2 kiểu cột cho thời điểm và chúng xử lý múi giờ khác nhau. Theo [tài liệu MySQL 8.4 về múi giờ](https://dev.mysql.com/doc/refman/8.4/en/time-zone-support.html), giá trị TIMESTAMP được đổi từ múi giờ của phiên (session, tức một kết nối) sang UTC khi lưu, rồi đổi ngược từ UTC sang múi giờ của phiên khi đọc. DATETIME không qua phép đổi nào: ghi con số gì thì đọc ra con số đó.

![Trang tài liệu MySQL 8.4 nói TIMESTAMP đổi từ múi giờ phiên sang UTC khi lưu](https://mona.software/wp-content/uploads/2026/10/mysql-84-tz-timestamp-utc-mona.webp)

Đoạn tài liệu MySQL 8.4 về ảnh hưởng của múi giờ phiên lên TIMESTAMP, DATE, TIME và DATETIME. Chụp ngày 26/10/2026.

Thử với bảng có một cột mỗi kiểu, máy chủ ứng dụng ghi giờ UTC (phiên đặt `+00:00`), rồi đọc lại từ phiên đặt `+07:00`:

```
CREATE TABLE orders (
  id INT PRIMARY KEY,
  amount INT NOT NULL,
  created_ts TIMESTAMP NOT NULL,
  created_dt DATETIME NOT NULL
);
SET SESSION time_zone = '+00:00';
INSERT INTO orders VALUES
 (1, 300000, '2026-10-25 10:00:00', '2026-10-25 10:00:00'),
 (2, 120000, '2026-10-25 16:30:00', '2026-10-25 16:30:00'),
 (3,  80000, '2026-10-25 17:20:00', '2026-10-25 17:20:00'),
 (4,  50000, '2026-10-25 23:30:00', '2026-10-25 23:30:00');
SET SESSION time_zone = '+07:00';
SELECT id, amount, created_ts, created_dt FROM orders ORDER BY id;
+----+--------+---------------------+---------------------+
| id | amount | created_ts          | created_dt          |
+----+--------+---------------------+---------------------+
|  1 | 300000 | 2026-10-25 17:00:00 | 2026-10-25 10:00:00 |
|  2 | 120000 | 2026-10-25 23:30:00 | 2026-10-25 16:30:00 |
|  3 |  80000 | 2026-10-26 00:20:00 | 2026-10-25 17:20:00 |
|  4 |  50000 | 2026-10-26 06:30:00 | 2026-10-25 23:30:00 |
+----+--------+---------------------+---------------------+
```

Cùng 4 hàng, cột `created_ts` hiện giờ Việt Nam còn cột `created_dt` vẫn hiện giờ UTC đã ghi. Hai cột chênh nhau đúng 7 tiếng, bằng độ lệch giữa phiên ghi và phiên đọc. Báo cáo gộp ngày cho thấy hậu quả.

```
SET SESSION time_zone = '+00:00';
SELECT DATE(created_ts) AS ngay, SUM(amount) AS tong FROM orders GROUP BY DATE(created_ts);
+------------+--------+
| ngay       | tong   |
+------------+--------+
| 2026-10-25 | 550000 |
+------------+--------+
SET SESSION time_zone = '+07:00';
SELECT DATE(created_ts) AS ngay, SUM(amount) AS tong FROM orders GROUP BY DATE(created_ts);
+------------+--------+
| ngay       | tong   |
+------------+--------+
| 2026-10-25 | 420000 |
| 2026-10-26 | 130000 |
+------------+--------+
SELECT DATE(created_dt) AS ngay, SUM(amount) AS tong FROM orders GROUP BY DATE(created_dt);
+------------+--------+
| ngay       | tong   |
+------------+--------+
| 2026-10-25 | 550000 |
+------------+--------+
SET SESSION time_zone = '+00:00';
SELECT DATE(created_dt) AS ngay, SUM(amount) AS tong FROM orders GROUP BY DATE(created_dt);
+------------+--------+
| ngay       | tong   |
+------------+--------+
| 2026-10-25 | 550000 |
+------------+--------+
```

Một câu truy vấn trên cột TIMESTAMP cho kết quả khác nhau theo phiên chạy nó: công cụ báo cáo kết nối ở `+00:00` thấy 550.000 đồng, kế toán mở cùng bảng từ phiên `+07:00` thấy 420.000 đồng. Cột DATETIME thì mọi phiên đều thấy 550.000 đồng ngày 25, nhưng ngày 25 đó tính theo UTC vì ứng dụng ghi giờ UTC. Hai kiểu sai theo hai cách khác nhau, và cả hai chỉ đúng khi biết múi giờ của dữ liệu. Phải ghi múi giờ đó ra.

Bảng sau tổng hợp từ các kết quả chạy trên cùng máy, đổi phiên từ `+00:00` sang `+07:00`:

| Thành phần | Giá trị khi phiên là +00:00 | Giá trị khi phiên là +07:00 | Theo múi giờ phiên? |
| --- | --- | --- | --- |
| Cột TIMESTAMP (đơn 4) | 2026-10-25 23:30:00 | 2026-10-26 06:30:00 | Có |
| Cột DATETIME (đơn 4) | 2026-10-25 23:30:00 | 2026-10-25 23:30:00 | Không |
| NOW() | 2026-10-26 03:24:12 | 2026-10-26 10:24:12 | Có |
| UTC_TIMESTAMP() | 2026-10-26 03:24:12 | 2026-10-26 03:24:12 | Không |

![Màn hình thống kê trong phần mềm MONA làm cho The IELTS Workshop](https://mona.software/wp-content/themes/monatheme/home-v2/assets/img/cs/ielts-thongke.webp)

Phần mềm cho việc này

Quy trình đang chạy mà phần mềm có sẵn không làm được?

MONA khảo sát nghiệp vụ trước dòng code đầu tiên.

[Viết phần mềm theo yêu cầu](https://mona.software/viet-phan-mem-theo-yeu-cau/)[Xem case study](https://mona.software/#case-study)

## Cắt ngày một lần, bằng múi giờ nghiệp vụ viết rõ trong câu truy vấn

Cách sửa là lưu thời điểm ở một dạng không mơ hồ, rồi cắt ngày bằng múi giờ nghiệp vụ nêu ngay trong truy vấn, không dựa vào cấu hình phiên. Có 3 cách, sắp theo mức can thiệp tăng dần, và cả 3 chạy trên bảng `orders` vừa tạo. Mỗi cách hợp một hoàn cảnh khác nhau của cột. Cách 1, dùng khi cột là DATETIME lưu UTC: đổi sang múi giờ nghiệp vụ bằng `CONVERT_TZ` trước khi lấy ngày.

```
SELECT DATE(CONVERT_TZ(created_dt,'+00:00','+07:00')) AS ngay_vn, SUM(amount) AS tong
FROM orders GROUP BY 1;
+------------+--------+
| ngay_vn    | tong   |
+------------+--------+
| 2026-10-25 | 420000 |
| 2026-10-26 | 130000 |
+------------+--------+
```

Cách 2, dùng khi cột là TIMESTAMP: lọc bằng khoảng nửa mở có kèm độ lệch trong chính chữ ngày. Khoảng này cho cùng kết quả ở phiên `+00:00` lẫn `+07:00`, và `EXPLAIN` cho thấy MySQL vẫn dùng chỉ mục (`type = range`, `key = idx_ts`) vì cột không bị bọc trong hàm. Cách này không cần thêm cột nào.

```
ALTER TABLE orders ADD KEY idx_ts (created_ts);
SET SESSION time_zone = '+00:00';
SELECT SUM(amount) AS ngay_25 FROM orders
WHERE created_ts >= '2026-10-25 00:00:00+07:00'
  AND created_ts <  '2026-10-26 00:00:00+07:00';
SET SESSION time_zone = '+07:00';
SELECT SUM(amount) AS ngay_25 FROM orders
WHERE created_ts >= '2026-10-25 00:00:00+07:00'
  AND created_ts <  '2026-10-26 00:00:00+07:00';
EXPLAIN SELECT SUM(amount) AS ngay_25 FROM orders
WHERE created_ts >= '2026-10-25 00:00:00+07:00'
  AND created_ts <  '2026-10-26 00:00:00+07:00';
+---------+
| ngay_25 |
+---------+
|  420000 |
+---------+
+---------+
| ngay_25 |
+---------+
|  420000 |
+---------+
+----+-------------+--------+------------+-------+---------------+--------+---------+------+------+----------+-----------------------+
| id | select_type | table  | partitions | type  | possible_keys | key    | key_len | ref  | rows | filtered | Extra                 |
+----+-------------+--------+------------+-------+---------------+--------+---------+------+------+----------+-----------------------+
|  1 | SIMPLE      | orders | NULL       | range | idx_ts        | idx_ts | 4       | NULL |    2 |   100.00 | Using index condition |
+----+-------------+--------+------------+-------+---------------+--------+---------+------+------+----------+-----------------------+
```

Cột tính sẵn chỉ đáng tin khi cột gốc là DATETIME lưu UTC. MySQL vẫn nhận định nghĩa tương tự trên cột TIMESTAMP, nhưng giá trị tính sẵn phụ thuộc phiên đang ghi. Hai đơn cùng một khoảnh khắc (`unix_ts` giống nhau) được ghi từ hai phiên và ra 2 ngày khác nhau, và kết quả cho thấy phép tính dùng giờ của phiên đang ghi, không dùng khoảnh khắc đã lưu. Dùng tên `Asia/Ho_Chi_Minh` trên máy chưa nạp bảng múi giờ thì cột nhận NULL mà không báo lỗi.

```
CREATE TABLE gc (
  id INT PRIMARY KEY,
  created_ts TIMESTAMP NOT NULL,
  biz_date DATE AS (DATE(CONVERT_TZ(created_ts,'+00:00','+07:00'))) STORED
);
SET SESSION time_zone = '+00:00';
INSERT INTO gc (id, created_ts) VALUES (1, '2026-10-25 16:30:00');
SET SESSION time_zone = '+07:00';
INSERT INTO gc (id, created_ts) VALUES (2, '2026-10-25 23:30:00');
SELECT id, created_ts, UNIX_TIMESTAMP(created_ts) AS unix_ts, biz_date FROM gc;
+----+---------------------+------------+------------+
| id | created_ts          | unix_ts    | biz_date   |
+----+---------------------+------------+------------+
|  1 | 2026-10-25 23:30:00 | 1792945800 | 2026-10-25 |
|  2 | 2026-10-25 23:30:00 | 1792945800 | 2026-10-26 |
+----+---------------------+------------+------------+
SET SESSION time_zone = '+00:00';
SELECT id, created_ts, UNIX_TIMESTAMP(created_ts) AS unix_ts, biz_date FROM gc;
+----+---------------------+------------+------------+
| id | created_ts          | unix_ts    | biz_date   |
+----+---------------------+------------+------------+
|  1 | 2026-10-25 16:30:00 | 1792945800 | 2026-10-25 |
|  2 | 2026-10-25 16:30:00 | 1792945800 | 2026-10-26 |
+----+---------------------+------------+------------+
CREATE TABLE gc2 (
  id INT PRIMARY KEY,
  created_ts TIMESTAMP NOT NULL,
  biz_date DATE AS (DATE(CONVERT_TZ(created_ts,'+00:00','Asia/Ho_Chi_Minh'))) STORED
);
INSERT INTO gc2 (id, created_ts) VALUES (1, '2026-10-25 16:30:00');
SELECT id, created_ts, biz_date FROM gc2;
+----+---------------------+----------+
| id | created_ts          | biz_date |
+----+---------------------+----------+
|  1 | 2026-10-25 16:30:00 | NULL     |
+----+---------------------+----------+
```

[Tài liệu MySQL 8.4](https://dev.mysql.com/doc/refman/8.4/en/date-and-time-literals.html) mô tả độ lệch trong chữ ngày cho lệnh INSERT, và giới hạn độ lệch từ `-13:59` đến `+14:00`. Việc dùng nó trong `WHERE` ở đây được kiểm bằng cách chạy trên 8.4.8, tài liệu không nói riêng về phép so sánh.

Cách 3, dùng khi nhiều báo cáo cùng cần ngày chốt sổ: thêm cột ngày nghiệp vụ tính sẵn và đánh chỉ mục, để mọi báo cáo nhóm theo đúng một cột. Đánh đổi là múi giờ nằm cứng trong định nghĩa cột, nên đổi múi giờ nghiệp vụ nghĩa là dựng lại cột. Đổi lại, cột tính sẵn đánh chỉ mục được, như `idx_biz_date` trong ví dụ.

```
ALTER TABLE orders
  ADD COLUMN biz_date DATE AS (DATE(CONVERT_TZ(created_dt,'+00:00','+07:00'))) STORED,
  ADD KEY idx_biz_date (biz_date);
SELECT biz_date, SUM(amount) AS tong FROM orders GROUP BY biz_date;
+------------+--------+
| biz_date   | tong   |
+------------+--------+
| 2026-10-25 | 420000 |
| 2026-10-26 | 130000 |
+------------+--------+
```

Cả 3 ví dụ dùng độ lệch số `'+07:00'` thay cho tên `'Asia/Ho_Chi_Minh'`, vì máy thử không nạp bảng múi giờ có tên vào MySQL. Cài đặt mặc định có tạo các bảng này nhưng không điền dữ liệu. Khi đó `SET time_zone = 'Asia/Ho_Chi_Minh'` báo `ERROR 1298 (HY000): Unknown or incorrect time zone`, còn `CONVERT_TZ` trả `NULL` mà không báo lỗi (kiểm bằng chạy thật):

```
SELECT DATE(CONVERT_TZ(created_dt,'+00:00','Asia/Ho_Chi_Minh')) AS ngay_vn, SUM(amount) AS tong
FROM orders GROUP BY 1;
+---------+--------+
| ngay_vn | tong   |
+---------+--------+
| NULL    | 550000 |
+---------+--------+
```

Báo cáo vẫn chạy và vẫn ra tổng 550.000 đồng, chỉ có điều tất cả dồn vào một ngày NULL. Đây là cách một báo cáo hỏng mà không ai thấy lỗi. Cách kiểm: `SELECT COUNT(*) FROM mysql.time_zone_name` trả 0 nghĩa là bảng còn trống, nạp bằng `mysql_tzinfo_to_sql` theo hướng dẫn của MySQL rồi khởi động lại máy chủ. Với Việt Nam, UTC+7 cố định từ 1975, nên độ lệch số cho kết quả đúng với mọi dữ liệu hiện hành. Khi hệ thống phục vụ nước có giờ mùa hè, độ lệch số sai trong các tháng giờ mùa hè, và tên múi giờ là lựa chọn bắt buộc.

![Mục CONVERT_TZ trong tài liệu MySQL 8.4 nói hàm trả về NULL khi đối số không hợp lệ](https://mona.software/wp-content/uploads/2026/10/mysql-84-convert-tz-null-mona.webp)

Mục CONVERT_TZ trong tài liệu MySQL 8.4: hàm trả NULL khi một đối số không hợp lệ, và tên múi giờ cần bảng múi giờ đã nạp. Chụp ngày 26/10/2026.

## TIMESTAMP chỉ chứa được đến 03:14:07 UTC ngày 19/01/2038

Sổ sách có cột ngày đáo hạn, hết hạn bảo hành hoặc kỳ hạn hợp đồng thường vượt qua năm 2038, và cột TIMESTAMP không ghi nổi chúng. [Tài liệu MySQL 8.4](https://dev.mysql.com/doc/refman/8.4/en/datetime.html) nêu khoảng của TIMESTAMP từ `'1970-01-01 00:00:01'` UTC đến `'2038-01-19 03:14:07'` UTC, còn DATETIME từ `'1000-01-01 00:00:00'` đến `'9999-12-31 23:59:59'`.

![Trang tài liệu MySQL 8.4 nêu giới hạn TIMESTAMP đến 2038-01-19 03:14:07 UTC](https://mona.software/wp-content/uploads/2026/10/mysql-84-timestamp-gioi-han-2038-mona.webp)

Khoảng giá trị của DATETIME và TIMESTAMP trong tài liệu MySQL 8.4. Chụp ngày 26/10/2026.

Ghi thử một giây sau mốc đó vào cả hai kiểu, ở chế độ nghiêm ngặt (`STRICT_TRANS_TABLES` đang bật trên máy thử). Hai dòng ERROR được rút ngắn, bỏ phần “at line N” của trình khách:

```
CREATE TABLE lim (ts TIMESTAMP NULL, dt DATETIME NULL);
SET SESSION time_zone = '+00:00';
INSERT INTO lim (ts) VALUES ('2038-01-19 03:14:07');
INSERT INTO lim (ts) VALUES ('2038-01-19 03:14:08');
INSERT INTO lim (dt) VALUES ('2038-01-19 03:14:08');
SET SESSION time_zone = '+07:00';
INSERT INTO lim (ts) VALUES ('2038-01-19 10:14:07');
INSERT INTO lim (ts) VALUES ('2038-01-19 10:14:08');
SELECT ts, dt FROM lim;
+---------------------+---------------------+
| ts                  | dt                  |
+---------------------+---------------------+
| 2038-01-19 10:14:07 | NULL                |
| NULL                | 2038-01-19 03:14:08 |
| 2038-01-19 10:14:07 | NULL                |
+---------------------+---------------------+
ERROR 1292 (22007): Incorrect datetime value: '2038-01-19 03:14:08' for column 'ts' at row 1
ERROR 1292 (22007): Incorrect datetime value: '2038-01-19 10:14:08' for column 'ts' at row 1
```

Mốc tính theo UTC, nên ở phiên `+07:00` giới hạn dịch thành 10:14:07 cùng ngày. Hệ thống có ngày đáo hạn xa hơn năm 2038 chọn DATETIME cho các cột ngày đó, và tự giữ quy ước múi giờ cho cột. Cột dùng DATETIME không được MySQL đổi giờ hộ. Cột chỉ ghi thời điểm đã xảy ra, như giờ tạo đơn, ghi bằng TIMESTAMP vẫn đủ đến 2038 và được MySQL đổi múi giờ hộ.

## Hỏi đáp

### Đặt múi giờ toàn máy chủ về +07:00 có giải quyết được không?

Chỉ giải quyết cột TIMESTAMP, và đổi giá trị đã có theo cách đọc. Tài liệu MySQL 8.4 nói đổi múi giờ rồi đọc lại TIMESTAMP cho giá trị khác với lúc lưu. DATETIME đã ghi theo UTC vẫn nằm nguyên, và hai nguồn dữ liệu lệch nhau 7 tiếng. Viết múi giờ trong truy vấn như ba cách trên an toàn hơn, và hãy [sao lưu dữ liệu](https://mona.software/sao-luu-khoi-phuc-du-lieu-phan-mem-doanh-nghiep/) trước khi đổi cấu hình toàn máy chủ. Đổi giá trị toàn máy chủ còn cần quyền `SYSTEM_VARIABLES_ADMIN` theo tài liệu MySQL.

### Nên lưu thời điểm bằng TIMESTAMP hay DATETIME?

TIMESTAMP cho thời điểm đã xảy ra như giờ tạo đơn và giờ thanh toán, vì MySQL tự đổi múi giờ và giá trị luôn là một khoảnh khắc xác định. DATETIME cho ngày giờ không gắn múi giờ (như dữ liệu [chuyển từ Excel](https://mona.software/chuyen-du-lieu-excel-sang-phan-mem-quan-ly/)) hoặc vượt năm 2038, như ngày đáo hạn và giờ mở cửa. Quy ước chung là ghi UTC vào DATETIME và đổi khi đọc. Đặt hậu tố vào tên cột, như `created_at_utc`, để người sau không phải đoán múi giờ.

### Dùng ‘Asia/Ho_Chi_Minh’ hay ‘+07:00’?

Với dữ liệu từ 1975 trở đi, hai cách cho cùng kết quả vì IANA không khai báo quy tắc giờ mùa hè cho tên này. Tên múi giờ cần bảng múi giờ đã nạp, nếu thiếu thì `CONVERT_TZ` trả NULL như kết quả ở trên. Độ lệch số chạy ngay trên máy mới cài. Tên cũ Asia/Saigon là bí danh của Asia/Ho_Chi_Minh trong [tệp backward của IANA](https://github.com/eggert/tz/blob/main/backward), nên hai tên cho cùng kết quả.

### Việt Nam có đổi sang giờ mùa hè không?

Bản tzdb 2026e (kiểm ngày 26/10/2026) không có quy tắc giờ mùa hè nào cho Asia/Ho_Chi_Minh, và cho UTC+7 cố định từ 13/06/1975. Mỗi bản tzdb mới có thể đổi nội dung, nên hệ thống cần cập nhật dữ liệu múi giờ của hệ điều hành theo cảnh báo trong tài liệu MySQL.

### Báo cáo ra một nhóm NULL thì kiểm gì?

Chạy `SELECT COUNT(*) FROM mysql.time_zone_name`. Máy thử trả 0, và khi đó `CONVERT_TZ` với tên múi giờ trả NULL cho mọi hàng. Đổi sang độ lệch `+07:00`, hoặc nạp bảng bằng `mysql_tzinfo_to_sql` rồi khởi động lại máy chủ. Nên thêm ca đơn đặt lúc nửa đêm vào [kịch bản kiểm thử khi nghiệm thu](https://mona.software/nghiem-thu-phan-mem-uat-kich-ban-kiem-thu/) để bắt lỗi này trước khi báo cáo chạy thật.

Hệ thống báo cáo theo ngày với múi giờ nghiệp vụ ghi rõ từ thiết kế là việc của [dịch vụ viết phần mềm theo yêu cầu của MONA](https://mona.software/viet-phan-mem-theo-yeu-cau/), hotline 1900 636 648. Ngày chốt sổ của kế toán cần được ghi lại bên cạnh giờ gốc của từng đơn, như bài [sổ kế toán chạy song song và nhật ký lưu vết](https://mona.software/so-ke-toan-song-song-phan-mem-luu-vet/) mô tả.

## Câu hỏi thường gặp

### Đặt múi giờ toàn máy chủ về +07:00 có giải quyết được không?

Chỉ giải quyết cột TIMESTAMP, và đổi giá trị đã có theo cách đọc. Tài liệu MySQL 8.4 nói đổi múi giờ rồi đọc lại TIMESTAMP cho giá trị khác với lúc lưu. DATETIME đã ghi theo UTC vẫn nằm nguyên, và hai nguồn dữ liệu lệch nhau 7 tiếng. Viết múi giờ trong truy vấn như ba cách trên an toàn hơn, và hãy sao lưu dữ liệu trước khi đổi cấu hình toàn máy chủ. Đổi giá trị toàn máy chủ còn cần quyền SYSTEM_VARIABLES_ADMIN theo tài liệu MySQL.

### Nên lưu thời điểm bằng TIMESTAMP hay DATETIME?

TIMESTAMP cho thời điểm đã xảy ra như giờ tạo đơn và giờ thanh toán, vì MySQL tự đổi múi giờ và giá trị luôn là một khoảnh khắc xác định. DATETIME cho ngày giờ không gắn múi giờ (như dữ liệu chuyển từ Excel) hoặc vượt năm 2038, như ngày đáo hạn và giờ mở cửa. Quy ước chung là ghi UTC vào DATETIME và đổi khi đọc. Đặt hậu tố vào tên cột, như created_at_utc, để người sau không phải đoán múi giờ.

### Dùng 'Asia/Ho_Chi_Minh' hay '+07:00'?

Với dữ liệu từ 1975 trở đi, hai cách cho cùng kết quả vì IANA không khai báo quy tắc giờ mùa hè cho tên này. Tên múi giờ cần bảng múi giờ đã nạp, nếu thiếu thì CONVERT_TZ trả NULL như kết quả ở trên. Độ lệch số chạy ngay trên máy mới cài. Tên cũ Asia/Saigon là bí danh của Asia/Ho_Chi_Minh trong tệp backward của IANA, nên hai tên cho cùng kết quả.

### Việt Nam có đổi sang giờ mùa hè không?

Bản tzdb 2026e (kiểm ngày 26/10/2026) không có quy tắc giờ mùa hè nào cho Asia/Ho_Chi_Minh, và cho UTC+7 cố định từ 13/06/1975. Mỗi bản tzdb mới có thể đổi nội dung, nên hệ thống cần cập nhật dữ liệu múi giờ của hệ điều hành theo cảnh báo trong tài liệu MySQL.

### Báo cáo ra một nhóm NULL thì kiểm gì?

Chạy SELECT COUNT(*) FROM mysql.time_zone_name. Máy thử trả 0, và khi đó CONVERT_TZ với tên múi giờ trả NULL cho mọi hàng. Đổi sang độ lệch +07:00, hoặc nạp bảng bằng mysql_tzinfo_to_sql rồi khởi động lại máy chủ. Nên thêm ca đơn đặt lúc nửa đêm vào kịch bản kiểm thử khi nghiệm thu để bắt lỗi này trước khi báo cáo chạy thật. Hệ thống báo cáo theo ngày với múi giờ nghiệp vụ ghi rõ từ thiết kế là việc của dịch vụ viết phần mềm theo yêu cầu của MONA, hotline 1900 636 648. Ngày chốt sổ của kế toán cần được ghi lại bên cạnh giờ gốc của từng đơn, như bài sổ kế toán chạy song song và nhật ký lưu vết mô tả.
