Một hàm đổi âm lịch dùng cho công cụ lập lá số cần ba thứ: thuật toán tính ngày sóc và trung khí theo một múi giờ truyền vào làm tham số (UTC+7 cho lịch Việt), cách xử lý tháng nhuận được trả ra rõ ràng thay vì gộp vào số tháng, và một bộ ngày kiểm thử phủ các ca biên như Tết 1985, Tết Mậu Thân 1968, tháng nhuận, tháng Chạp vắt sang năm dương sau. Thiếu một trong ba, công cụ sẽ cho kết quả đúng ở hầu hết ngày nhưng sai đúng ở những ca mà người dùng hay hỏi nhất.
Bài này viết cho người lập trình và người kiểm thử công cụ lá số, ở mức mô tả và mã giả bằng chữ, không đưa mã nguồn dài. Mọi ngày trong bộ kiểm thử đã được tính lại bằng thuật toán điểm sóc, trung khí với hai múi giờ. Một công cụ đổi lịch đúng chỉ bảo đảm dữ liệu vào lá số đúng; nó không biến lời luận trên lá số thành điều có bằng chứng, và giao diện công cụ cũng không nên ngụ ý như vậy.
Các bước của hàm đổi dương sang âm
Phần lớn các cài đặt hiện nay đi theo cùng một khung, dựa trên công thức thiên văn gần đúng cho thời điểm điểm sóc và kinh độ mặt trời. Mô tả bằng chữ như sau.
- Đổi ngày dương lịch sang số ngày Julius (một số nguyên đếm ngày liên tục). Mọi phép so sánh ngày sau đó làm trên số này.
- Tìm điểm sóc gần nhất trước hoặc bằng ngày cần đổi. Thời điểm điểm sóc tính theo giờ quốc tế, rồi cộng múi giờ và lấy phần nguyên để ra ngày sóc theo lịch địa phương. Ngày sóc là mùng Một.
- Tìm tháng Mười Một của năm: tháng âm chứa điểm Đông chí. Làm việc này cho năm hiện tại và năm trước (hoặc năm sau) để có hai mốc tháng Mười Một bao quanh ngày cần đổi.
- Nếu giữa hai tháng Mười Một có mười ba lần sóc, năm đó có tháng nhuận. Tháng nhuận là tháng đầu tiên sau tháng Mười Một mà trong tháng không có trung khí nào.
- Đánh số tháng từ mốc tháng Mười Một, bỏ qua tháng nhuận khi tăng số, và gắn cờ nhuận cho tháng đó.
- Ngày âm là số ngày từ mùng Một tới ngày cần đổi, cộng một. Năm âm lịch là năm của tháng Giêng tương ứng; tháng Mười Một và tháng Chạp thuộc năm âm trước nếu ngày dương đã sang năm mới.
Hàm ngược (âm sang dương) đi theo chiều ngược lại: từ năm âm tìm mốc tháng Mười Một, đếm tới tháng cần tìm (lưu ý tháng nhuận), lấy ngày sóc rồi cộng số ngày. Hàm ngược phải từ chối các đầu vào không tồn tại, như cờ nhuận cho tháng không nhuận hay ngày 30 của tháng thiếu.
Múi giờ là tham số, không phải hằng số ẩn
Lịch Việt và lịch Trung Quốc dùng cùng thuật toán, chỉ khác múi giờ: UTC+7 và UTC+8. Nếu múi giờ được viết cứng trong hàm, công cụ của bạn chỉ phục vụ được một lịch. Nếu nó là tham số, bạn có thể cho người dùng chọn, hoặc tự chọn theo nơi sinh và năm sinh, và quan trọng hơn là viết được kiểm thử so sánh hai lịch.
Có hai lý do thực tế để cần UTC+8 ngay cả trong công cụ cho người Việt. Thứ nhất, theo các tài liệu lịch pháp thường dẫn, âm lịch ở miền Bắc trước 1968 và ở miền Nam trước 1975 tính theo UTC+8; người dùng sinh trong các giai đoạn đó có thể muốn lá số theo lịch gia đình đã dùng. Thứ hai, người dùng hay mang lá số từ sách hay nguồn nước ngoài tới so, và công cụ cần giải thích được vì sao hai bên khác.
Một lỗi phổ biến khác là múi giờ bị "rò" từ môi trường chạy: hàm dùng đối tượng ngày giờ của ngôn ngữ lập trình, đối tượng này tự áp múi giờ của máy chủ hoặc trình duyệt. Một người dùng ở Mỹ mở trang, ngày sinh nhập vào bị chuyển sang ngày hôm trước trước khi tới hàm đổi lịch. Cách tránh: chuyển ngày nhập thành bộ ba số nguyên ngày, tháng, năm ngay từ đầu và chỉ làm việc trên số ngày Julius, không để đối tượng ngày giờ của hệ thống chạm vào.
Bộ ngày kiểm thử tối thiểu
Bảng dưới gồm những ngày đã được tính lại theo cả hai múi giờ. Một hàm đổi lịch Việt đúng phải cho đúng cột UTC+7; nếu hàm nhận múi giờ làm tham số, cột UTC+8 kiểm luôn nhánh đó.
| Ngày dương | Kết quả UTC+7 (lịch Việt) | Kết quả UTC+8 | Kiểm điều gì |
|---|---|---|---|
| 10/02/2024 | Mùng 1 tháng Giêng Giáp Thìn | Như UTC+7 | Tết thường; can chi ngày Giáp Thìn |
| 06/02/2027 | Mùng 1 tháng Giêng Đinh Mùi | Như UTC+7 | Tết năm tới |
| 22/03/2023 | Mùng 1 tháng Hai nhuận Quý Mão | Như UTC+7 | Cờ nhuận |
| 25/07/2025 | Mùng 1 tháng Sáu nhuận Ất Tỵ | Như UTC+7 | Cờ nhuận giữa năm |
| 01/01/2000 | Ngày 25 tháng Mười Một Kỷ Mão | Như UTC+7 | Năm âm của tháng Mười Một; can chi ngày Mậu Ngọ |
| 10/01/1971 | Ngày 14 tháng Chạp Canh Tuất | Như UTC+7 | Tháng Chạp vắt sang năm dương sau |
| 05/02/1985 | Ngày 16 tháng Giêng Ất Sửu | Ngày 16 tháng Chạp Giáp Tý | Lệch năm do tháng nhuận đặt khác chỗ |
| 25/03/1985 | Ngày 5 tháng Hai nhuận Ất Sửu | Ngày 5 tháng Hai Ất Sửu | Tháng nhuận chỉ có ở một bên |
| 29/01/1968 | Mùng 1 tháng Giêng Mậu Thân | Ngày 30 tháng Chạp Đinh Mùi | Điểm sóc sát nửa đêm |
| 17/02/2007 | Mùng 1 tháng Giêng Đinh Hợi | Ngày 30 tháng Chạp Bính Tuất | Lệch Tết một ngày |
| 14/12/2020 | Mùng 1 tháng Mười Một Canh Tý | Ngày 30 tháng Mười Canh Tý | Lệch đầu tháng |
| 15/12/2020 | Ngày 2 tháng Mười Một Canh Tý | Mùng 1 tháng Mười Một Canh Tý | Ngày kế tiếp vẫn lệch |
Với hàm ngược, thêm ba ca: ngày 10 tháng Tư năm Giáp Thân (không nhuận) phải ra 02/05/1944; ngày 10 tháng Tư nhuận năm Giáp Thân phải ra 31/05/1944; ngày 30 tháng Tư năm Giáp Thân không nhuận phải bị từ chối, vì tháng đó chỉ có 29 ngày.
Bộ này nhỏ nhưng phủ đủ các nhánh của thuật toán. Nên chạy nó trong mọi lần sửa mã, và thêm vào bất kỳ ngày nào người dùng từng báo sai.
Kiểm thử theo tính chất trên cả dải năm
Bộ ngày cố định bắt được lỗi ở các ca đã biết. Để bắt lỗi ở những ca chưa ai nghĩ tới, hãy chạy thêm các phép kiểm theo tính chất trên mọi ngày của một dải dài, ví dụ 1900 – 2100. Các tính chất dưới đây phải đúng với mọi lịch âm dương tính đúng quy tắc.
- Đổi đi rồi đổi về: mọi ngày dương đổi sang âm (kèm cờ nhuận) rồi đổi ngược phải ra đúng ngày ban đầu.
- Độ dài tháng: mọi tháng âm dài đúng 29 hoặc 30 ngày, không có tháng 28 hay 31 ngày.
- Số tháng trong năm: mỗi năm âm có 12 tháng, hoặc 13 tháng nếu có đúng một tháng nhuận; không năm nào có hai tháng nhuận.
- Tháng Mười Một chứa Đông chí: với mọi năm, tháng gắn số 11 (không nhuận) phải chứa ngày Đông chí.
- Tết nằm trong khoảng hợp lý: ngày mùng Một tháng Giêng luôn rơi trong khoảng từ khoảng 21/01 đến 20/02 dương lịch.
- Can chi ngày liên tục: hai ngày dương liên tiếp có can chi cách nhau đúng một bước trong vòng sáu mươi.
Mỗi tính chất là vài dòng mã, chạy hết dải hai trăm năm trong vài giây. Khi một tính chất bị vi phạm, in ra ngày đầu tiên vi phạm; thường đó là một lỗi lấy phần nguyên, một lỗi chỉ số tháng nhuận hay một lỗi ở ranh giới năm.
Riêng phép kiểm so hai múi giờ: chạy cả dải với UTC+7 và UTC+8, liệt kê các tháng khác nhau. Danh sách đó vừa là dữ liệu kiểm thử, vừa là thông tin hữu ích để hiển thị dòng nhắc cho người dùng sinh vào những tháng ấy.
Những lỗi biên hay lọt qua
Lấy tròn thay vì lấy phần nguyên. Ngày sóc phải là phần nguyên của thời điểm sóc theo giờ địa phương. Dùng phép làm tròn sẽ dời mọi điểm sóc sau 12 giờ trưa sang ngày hôm sau, làm lệch cả tháng.
Mất cờ nhuận ở giao diện. Hàm trả về cờ nhuận nhưng giao diện chỉ hiện "tháng 2", hoặc khi lưu vào cơ sở dữ liệu chỉ lưu số tháng. Lá số sau đó không biết đây là tháng nhuận, và quy ước tháng nhuận của người dùng không được áp.
Năm âm của tháng Mười Một, tháng Chạp. Ngày 01/01/2000 thuộc năm Kỷ Mão chứ không phải Canh Thìn. Hàm nào gán năm âm bằng năm dương cho mọi ngày trước Tết sẽ sai ở đây.
Ngày hợp lệ ở hàm ngược. Hàm ngược nhận "30 tháng Tư" mà không kiểm tháng đó thiếu sẽ âm thầm trả về mùng Một tháng sau. Người dùng không biết mình vừa nhập một ngày không tồn tại.
Sai số của công thức gần đúng. Công thức thiên văn rút gọn có sai số cỡ vài phút. Khi điểm sóc hay trung khí rơi chỉ cách nửa đêm vài phút, kết quả có thể lệch một ngày so với lịch chính thức. Nên đánh dấu những ngày như vậy và đối chiếu với lịch đã công bố.
Giờ Tý không phải việc của hàm đổi lịch. Chuyện người sinh sau 23 giờ tính sang ngày hôm sau hay không là quy ước của tử vi, nên xử lý ở tầng lập lá số, có tuỳ chọn rõ ràng, chứ không nhét vào hàm đổi lịch.
Hiển thị cho người dùng: để họ tự kiểm được
Một công cụ lá số có trách nhiệm nên hiển thị ngay sau khi nhập: ngày âm lịch đã đổi (có chữ "nhuận" nếu có), năm can chi, can chi ngày, và lịch đã dùng (UTC+7 hay UTC+8). Người dùng nhìn dòng này là kiểm được với ký ức gia đình hoặc tờ lịch cũ, trước khi đọc bất cứ ô nào khác.
Với những ca biên đã biết, như người sinh trong khoảng 23/11/1984 – 19/4/1985, người sinh trong tháng có điểm sóc sát nửa đêm, hay người sinh ở miền Nam trước 1975, công cụ có thể hiện một dòng nhắc: ngày này hai lịch cho kết quả khác nhau, kèm cả hai kết quả. Dòng nhắc ngắn này tiết kiệm cho người dùng nhiều lần thắc mắc vì sao lá số khác nơi khác.
Cuối cùng, công cụ không nên dùng chữ "chính xác" theo nghĩa tiên đoán. Đổi lịch đúng và an sao đúng quy tắc là chính xác về dữ liệu; còn nội dung luận giải là chuyện khác, và giao diện nên tách hai phần đó ra. Nếu bạn muốn so kết quả với một công cụ sẵn có, có thể lập lá số trên AI Huyền Học rồi chạy bộ ngày kiểm thử ở trên với cả hai.
Câu hỏi thường gặp
Có cần tự viết thuật toán âm lịch không, hay dùng bảng tra sẵn?
Bảng tra sẵn tiện nhưng chỉ phủ một khoảng năm và có thể mang lỗi chép. Thuật toán tính từ điểm sóc, trung khí phủ được mọi năm và cho phép đổi múi giờ. Dù chọn cách nào, bộ ngày kiểm thử vẫn là bắt buộc.
Vì sao phải hỗ trợ UTC+8 cho người dùng Việt?
Vì theo các tài liệu lịch pháp, âm lịch ở miền Bắc trước 1968 và ở miền Nam trước 1975 tính theo UTC+8, và nhiều lá số từ sách, nguồn nước ngoài cũng theo lịch này. Có tham số múi giờ, công cụ giải thích được vì sao hai kết quả khác nhau.
Ngày kiểm thử nào bắt được nhiều lỗi nhất?
Ngày 05/02/1985 rất hiệu quả: lịch Việt cho ngày 16 tháng Giêng Ất Sửu, lịch Trung Quốc cho ngày 16 tháng Chạp Giáp Tý. Thêm 01/01/2000 để kiểm năm âm của tháng Mười Một và 22/03/2023 để kiểm cờ nhuận.
Sai số của công thức thiên văn gần đúng có đáng lo không?
Với hầu hết các ngày thì không, vì điểm sóc cách nửa đêm rất xa. Chỉ những tháng có điểm sóc hoặc trung khí cách nửa đêm vài phút mới có nguy cơ lệch. Nên đánh dấu những tháng này và đối chiếu với lịch đã công bố.
Có nên xử lý giờ Tý trong hàm đổi lịch không?
Không nên. Hàm đổi lịch chỉ đổi một ngày dương sang ngày âm. Việc người sinh sau 23 giờ có tính sang ngày hôm sau hay không là quy ước của người lập lá số, nên đặt ở tầng lá số với tuỳ chọn rõ ràng.
Đổi lịch đúng thì công cụ có thể quảng bá là xem tử vi chính xác không?
Không nên. Đổi lịch đúng và an sao đúng quy tắc chỉ là chính xác về dữ liệu. Lời luận trên lá số chưa có bằng chứng về giá trị dự đoán, và giao diện nên nói rõ điều đó thay vì gộp hai thứ vào một chữ "chính xác".