Để xây dựng một trang lịch âm-dương tiếng Việt tương tự mẫu, phần khó nhất không phải giao diện mà là lớp “calendar engine” và lớp “rule engine”. Trang mẫu của Báo Nghệ An[1] cho thấy cấu trúc điển hình của một sản phẩm lịch công khai: tiêu đề ngày hiện tại, cặp ngày dương/âm nổi bật, lịch tháng, chú giải màu ngày tốt/xấu và khối chi tiết ngày gồm Can-Chi, tiết khí, giờ hoàng đạo/hắc đạo, ngũ hành, trực, sao và hướng xuất hành. Hệ thống VNCal của Hồ Ngọc Đức[2] cũng cho thấy web có thể hỗ trợ lịch tháng, lịch năm, chỉ báo tiết khí, đổi ngày, xem chi tiết và in lịch năm/tháng một cách thực dụng. [3]
- 1. Giả định và tiêu chí thiết kế
- 2. Phân tích chức năng và mô hình giao diện
- 3. Thuật toán lịch và nguồn dữ liệu
- 3.1. Nguồn nên ưu tiên
- 3.2. Quy tắc lịch nên áp dụng
- 3.3. Xử lý năm nhuận và độ chính xác
- 4. Kiến trúc hệ thống đề xuất
- 4.1. Bảng so sánh công nghệ
- 4.2. Sơ đồ kiến trúc
- 4.3. API schema đề xuất
- 4.4. Database schema đề xuất
- 5. Mô-đun tính toán và logic nghiệp vụ
- 5.1. Quy đổi ngày dương sang âm
- 5.2. Quy đổi ngày âm sang dương
- 5.3. Tính Can Chi
- 5.4. Tính tiết khí
- 5.5. Tính giờ hoàng đạo, hắc đạo và ngày tốt/xấu
- 6. Bảo mật, quốc tế hóa và vận hành
- 7. Kiểm thử, lộ trình và chi phí
- 7.1. Bộ test tối thiểu nên có
- 7.2. Timeline triển khai đề xuất
- 7.3. Ước tính nhân lực và giờ công
- 7.4. Chi phí hạ tầng tham khảo
Khuyến nghị kỹ thuật tốt nhất cho bài toán này là kiến trúc đọc nhiều, tính trước, cache mạnh: tính toán trước dữ liệu lịch ngày/tháng theo múi giờ Asia/Bangkok (UTC+7, không DST), lưu thành bảng chuẩn nội bộ, rồi phục vụ qua frontend SEO-friendly với cache cạnh biên và cache ứng dụng. Với cách làm này, phần “lịch âm-dương” là dữ liệu xác định, còn các lớp “ngày tốt/xấu”, “giờ hoàng đạo”, “tuổi xung”, “nên làm/kiêng kỵ” phải được quản lý như bộ quy tắc có version, không nên trộn cứng vào thuật toán thiên văn. [4]
Về mặt lịch pháp nên bám theo quy tắc âm-dương lịch Việt Nam hiện đại: tháng âm bắt đầu ở ngày địa phương chứa điểm Sóc, tháng 11 âm phải chứa Đông chí và nếu năm có 13 tháng thì tháng nhuận là tháng đầu tiên sau Đông chí không chứa Trung khí. Tuy nhiên, chính tác giả của bài thuật toán phổ biến cũng lưu ý phiên bản công thức rút gọn có độ chính xác thấp hơn chương trình trực tuyến hoàn chỉnh; vì vậy phương án production nên là: hoặc dùng thư viện/engine đã được kiểm chứng hoặc tính thiên văn chính xác và đối soát với nguồn tham chiếu. [5]
Có một ranh giới quan trọng về độ chính xác: nếu dùng cố định Asia/Bangkok/UTC+7, hệ thống phù hợp cho lịch Việt Nam hiện đại; nhưng nếu mục tiêu bao gồm lịch pháp định lịch sử trước 1976 hoặc các giai đoạn Bắc-Nam dùng múi giờ khác nhau, cần một “historical mode” riêng. Nguồn công khai của VNCal nêu rất rõ sự khác nhau giữa lịch “official/historic” và lịch “astronomical” và các thay đổi múi giờ trước đây tại Việt Nam. Trong phạm vi khảo sát nguồn công khai, không xác định được một API JSON chính thức cấp quốc gia để dùng làm “single source of truth”. [6]
Giả định và tiêu chí thiết kế
Bản báo cáo này dựa trên các giả định sau:
Hạng mục | Giả định triển khai |
Phạm vi nghiệp vụ | Trang công khai hiển thị ngày dương/âm, lịch tháng, chi tiết ngày, tìm ngày tốt, in/chia sẻ |
Ngôn ngữ mặc định | vi-VN, có thể mở rộng en sau |
Múi giờ tính toán | Asia/Bangkok theo yêu cầu; về mặt offset tương đương UTC+7 |
Phạm vi dữ liệu ưu tiên | Giai đoạn hiện đại; nếu cần lịch sử chính xác thì triển khai thêm mode “official/historic” |
Tải hệ thống | Đọc nhiều hơn ghi rất mạnh; đa số truy vấn là theo ngày/tháng |
SEO | Quan trọng; nhiều URL theo ngày/tháng/năm cần index |
“Ngày tốt/xấu” | Xem như lớp quy tắc nghiệp vụ/truyền thống, không phải sự kiện thiên văn thuần túy |
Thiết kế UI | Responsive, không yêu cầu art direction riêng ngoài bố cục rõ ràng |
Ngoài ra nên phân biệt ngay từ đầu giữa ba lớp dữ liệu: thiên văn xác định (Sóc, tiết khí, Can-Chi theo công thức), quy tắc truyền thống tương đối ổn định (một số bảng giờ hoàng đạo, trực, sao) và quy tắc biên tập/điểm số sản phẩm (màu ngày tốt/xấu, bộ lọc “việc nên làm”, gợi ý tìm ngày tốt). Nếu không tách ba lớp này, hệ thống sẽ rất khó kiểm thử, khó hiệu chỉnh và khó giải thích khi đối chiếu với các nguồn bên ngoài. Nhận định này phù hợp với việc các nguồn thiên văn chính thức chỉ mô tả phần lịch pháp, trong khi các trang lịch đại chúng bổ sung thêm nhiều lớp diễn giải dân gian. [7]
Phân tích chức năng và mô hình giao diện
Về chức năng, trang mẫu đang thể hiện gần đúng “bộ tính năng chuẩn” của một cổng lịch âm-dương tiếng Việt: ngày hiện tại, quy đổi dương-âm, Can-Chi, lịch tháng, tiết khí, giờ hoàng đạo, màu tốt/xấu, khối “xem ngày tốt xấu” và các câu hỏi đáp cuối trang. VNCal cũng cho thấy nhu cầu thực tế cho việc hiển thị tháng/năm, đánh dấu tháng nhuận, hiện tiết khí, chọn ngày và in lịch bằng trình duyệt. [3]
Bố cục thông tin khuyến nghị như sau:

Trên desktop nên dùng lưới 12 cột với khối “hôm nay” và “thao tác” ở trên, lịch tháng ở giữa, panel chi tiết bên phải hoặc phía dưới. Trên mobile nên xếp dọc: “hôm nay” → “thao tác” → “lịch tháng” → “chi tiết ngày”; đồng thời giữ một action bar nổi cho “đổi ngày / chia sẻ / in”. Nếu muốn chia sẻ gốc trên mobile, có thể dùng navigator.share(); với in web, dùng window.print() và @media print để ẩn menu, banner, điều hướng phụ. [8]
Có thể chia tính năng thành bốn nhóm kỹ thuật:
Nhóm chức năng | Dữ liệu phụ thuộc | Loại xử lý | Gợi ý triển khai |
Hiển thị ngày dương/âm, lịch tháng | calendar_day, lunar_month | Xác định, đọc nhanh | Build sẵn + cache |
Chi tiết ngày | calendar_day, solar_term_event, day_rule_result | Ghép dữ liệu | SSR/ISR |
Tìm ngày tốt / lọc theo mục đích | day_rule_result, purpose_rule_map | Truy vấn + xếp hạng | API + Redis cache |
In / chia sẻ | Snapshot của ngày/tháng | Client + server utility | HTML print + link share + PDF tùy chọn |
Một điểm rất quan trọng là màu “ngày tốt/xấu” không nên được suy ra trực tiếp chỉ từ Can-Chi hay tiết khí. Trong trang mẫu, màu sắc đó là kết quả của một lớp đánh giá tổng hợp. Vì vậy, trong dữ liệu nội bộ nên lưu riêng quality_label, quality_score, quality_rule_version thay vì cố “suy ngược” từ một trường duy nhất. Cách này giúp sau này thay đổi logic mà không phá vỡ dữ liệu lịch lõi. [9]
Thuật toán lịch và nguồn dữ liệu
Nguồn nên ưu tiên
Nguồn | Loại nguồn | Dùng cho | Đánh giá triển khai |
Viện Hàn lâm Khoa học và Công nghệ Việt Nam[10] | Cơ quan khoa học Việt Nam | Diễn giải quy tắc âm lịch, Đông chí, 24 tiết khí, khác biệt do múi giờ | Có giá trị tham chiếu chính thống bằng tiếng Việt nhưng không phải API máy đọc. [11] |
Hong Kong Observatory[12] | Cơ quan thiên văn/khí tượng chính thức | 24 tiết khí, định nghĩa major/minor terms, quy tắc leap month, ví dụ năm thực | Rất mạnh cho phần thiên văn và quy tắc tháng nhuận. [13] |
Hồ Ngọc Đức[2] / VNCal | Nguồn chuyên khảo + mã nguồn mở | Công thức chuyển đổi âm-dương, mốc 105°Đ, mode lịch lịch sử/chính thức | Là nguồn thực hành phổ biến nhất cho lịch Việt Nam trên web; có cảnh báo về bản công thức rút gọn. [14] |
Vietnamese Nôm Preservation Foundation[15] | Tổ chức văn hóa Việt ngữ | Bộ đổi ngày công khai để đối chiếu | Nên dùng làm oracle kiểm thử bên thứ ba. [16] |
Astronomy Engine | Thư viện mã nguồn mở | Tính thời điểm Sóc, kinh độ Mặt Trời, tiết khí độ chính xác cao | Hữu ích khi muốn vươn ra ngoài dải dữ liệu cache sẵn và tăng độ chính xác ephemeris. [17] |
Các thư viện OSS như lunar-date-vn, amlich.js, lunar-calendar, @dqcai/vn-lunar | Mã nguồn mở thực dụng | Port JS/TS/PHP nhanh cho MVP hoặc đối chiếu test | Dùng được nhưng vẫn cần kiểm chứng chéo và version hóa. [18] |
Trong phạm vi kỹ thuật, có thể rút ra một thứ tự ưu tiên hợp lý: nguồn quy tắc từ VAST và HKO, nguồn triển khai Việt hóa từ VNCal/Hồ Ngọc Đức, nguồn đối chiếu độc lập từ Nôm Foundation và nguồn ephemeris chính xác từ Astronomy Engine. Đây là tổ hợp thực dụng nhất để vừa có nền tảng khoa học, vừa có khả năng triển khai web nhanh. [19]
Quy tắc lịch nên áp dụng
Phần lịch lõi nên bám các quy tắc sau. Thứ nhất, tháng âm bắt đầu ở ngày chứa điểm Sóc, tức new moon. Thứ hai, âm lịch Việt Nam hiện đại là một âm-dương lịch: ngoài chu kỳ Mặt Trăng còn phải khớp chu kỳ Mặt Trời. Thứ ba, tháng 11 âm phải chứa Đông chí. Thứ tư, nếu giữa hai tháng 11 âm liên tiếp có 13 tháng thì tháng nhuận là tháng đầu tiên sau Đông chí không chứa Trung khí. Phần giải thích tiếng Việt của VAST và phần giải thích chính thức của HKO đều hỗ trợ logic này; bài toán khác biệt Việt–Trung xuất phát chính từ việc cùng quy tắc nhưng khác múi giờ pháp định. [20]
Về tiết khí, HKO và VAST đều xác nhận 24 tiết khí là các mốc chia đều dọc hoàng đạo, mỗi mốc cách nhau 15 độ; hệ này gồm 12 Trung khí và 12 Tiết khí xen kẽ. Đây là phần hoàn toàn có thể tính thiên văn cục bộ, không bắt buộc lệ thuộc API ngoài. Nếu dùng engine chính xác, cần xác định thời điểm kinh độ Mặt Trời đạt các giá trị 0, 15, 30, …, 345 độ, sau đó quy đổi về ngày địa phương UTC+7. [21]
Xử lý năm nhuận và độ chính xác
Khác với dương lịch có “ngày nhuận” trong tháng Hai, âm lịch xử lý phần chênh lệch bằng tháng nhuận. VAST nêu rõ rằng việc xác định tháng nhuận của âm lịch không thể nhẩm như dương lịch mà phải tính chính xác vị trí Trái Đất, Mặt Trăng và Mặt Trời; chỉ cần lệch thời điểm Sóc qua nửa đêm địa phương là cả tháng âm và tháng nhuận có thể đổi. Chính đây là lý do production không nên tính “naive” theo sơ đồ rút gọn mà không có kiểm chứng. [22]
Về mức độ chính xác cần cam kết nên chia ba mức:
Mức | Mô tả | Khi dùng |
Tương thích web Việt Nam hiện đại | Bám quy tắc VNCal/UTC+7, đối soát với Nôm Foundation và các nguồn công khai Việt ngữ | Đủ cho phần lớn sản phẩm dân dụng |
Thiên văn chính xác cao | Tính Sóc và tiết khí bằng engine ephemeris rồi materialize | Dùng khi cần mở rộng dải năm, giảm sai số biên |
Lịch pháp định lịch sử | Tách chế độ “historic/offical” với múi giờ và quy tắc theo từng thời kỳ | Chỉ cần nếu sản phẩm có vai trò tra cứu lịch sử/hồ sơ cũ |
Nguồn của VNCal nói rõ lịch “official/historic” và lịch “astronomical” là hai thứ khác nhau trước 1976 và lịch lịch sử đáng tin cậy từ năm 1301; đồng thời cho biết các giai đoạn miền Bắc/miền Nam dùng múi giờ và lịch khác nhau. Vì vậy, với yêu cầu hiện tại dùng Asia/Bangkok, báo cáo này đánh giá: độ chính xác cao cho giai đoạn hiện đại; độ chính xác lịch sử không xác định nếu không xây historical mode riêng. [6]
Kiến trúc hệ thống đề xuất
Khuyến nghị production là frontend render trước + backend API tách riêng + dữ liệu đã tiền tính. Với cách này, phần lớn người dùng chỉ đọc dữ liệu ngày/tháng; rất hiếm luồng ghi. Do đó, hiệu năng đạt được không phải bằng “tối ưu tính toán live”, mà bằng tạo sẵn bảng dữ liệu và phân tầng cache. Next.js hỗ trợ revalidation theo thời gian và theo sự kiện; NestJS và FastAPI đều có cơ chế tạo OpenAPI/Swagger; Redis phù hợp cho cache và rate limiting phân tán. [23]
Bảng so sánh công nghệ
Lớp | Phương án | Ưu điểm | Nhược điểm | Khuyến nghị |
Frontend | Next.js + React + Tailwind | Render trước tốt cho SEO, cache/revalidate tốt, dễ làm route ngày/tháng, responsive nhanh | Cần kỷ luật cache và phân biệt route tĩnh/động | Nên chọn |
Frontend | SPA thuần | Đơn giản cho dashboard nội bộ | Bất lợi hơn cho SEO và chia sẻ URL ngày/tháng | Chỉ dùng cho backoffice |
Backend | NestJS | Cùng hệ TS với frontend, module rõ, OpenAPI tốt, hợp đội fullstack TS | Boilerplate nhiều hơn | Nên chọn |
Backend | FastAPI | Phù hợp nếu đội mạnh Python/astronomy, docs API tự động | Tách ngôn ngữ khỏi frontend | Tốt nếu calendar engine làm bằng Python |
Backend | Next.js Route Handlers-only | Nhanh cho MVP | Khó quản rule engine và batch jobs lớn về sau | Dùng cho giai đoạn đầu |
DB | PostgreSQL | Dữ liệu quan hệ rõ, JSONB tốt cho rule payload, bền vững | Cần migration chặt chẽ | Nên chọn |
Cache | Redis | TTL, distributed cache, rate limit | Thêm một thành phần vận hành | Nên chọn |
Nguồn khả năng kỹ thuật trong bảng: Next.js hỗ trợ caching và revalidation theo thời gian/sự kiện; NestJS và FastAPI đều hỗ trợ tài liệu OpenAPI tự động; Redis có các pattern rate limiting và cache phổ biến trong tài liệu chính thức. Tailwind hỗ trợ responsive utility rõ ràng theo breakpoint. [24]
Sơ đồ kiến trúc

Trong sơ đồ này, dữ liệu thiên văn và dữ liệu lịch tháng/ngày được materialize vào PostgreSQL; Redis giữ cache kết quả API và rate limit; frontend ưu tiên dựng trước các URL ngày/tháng. Chỉ các route “hôm nay”, “tìm ngày tốt”, “đổi ngày” mới thật sự động. Đây là mô hình phù hợp với bản chất đọc nhiều của sản phẩm lịch. [25]
API schema đề xuất
Endpoint | Mục đích | Tham số chính | Cache gợi ý | Gợi ý response |
GET /api/v1/days/{date} | Lấy chi tiết một ngày | lang, tz, ruleset | 7–30 ngày hoặc cache versioned | calendar_day + day_rule_result + solar_term |
GET /api/v1/months/{year}/{month} | Lấy grid tháng | lang, tz, ruleset | 30 ngày hoặc cache versioned | danh sách ô ngày + meta tháng |
GET /api/v1/convert | Đổi dương→âm hoặc âm→dương | solar hoặc lunar, tz | 30 ngày | object quy đổi |
GET /api/v1/search-auspicious | Tìm ngày tốt trong khoảng | from, to, purpose, ruleset | 1–6 giờ | danh sách ngày + score |
GET /api/v1/solar-terms/{year} | Lấy 24 tiết khí năm | tz | dài hạn | 24 event tiết khí |
POST /api/v1/share-links | Tạo link chia sẻ ngắn | body ngày/tháng/ruleset | không cache | token/link |
GET /api/v1/print/day/{date} | Bản in/PDF ngày | lang, tz, ruleset | 1 ngày | HTML hoặc PDF |
GET /healthz / GET /readyz | Health check | không | không | trạng thái hạ tầng |
Nếu dùng NestJS hoặc FastAPI, bảng trên có thể được map thẳng sang OpenAPI, giúp frontend, QA và đối tác tích hợp xem schema bằng Swagger/ReDoc. [26]
Database schema đề xuất
Bảng | Cột chính | Chỉ mục đề xuất | Mục đích |
calendar_day | solar_date PK, jd, weekday, lunar_day, lunar_month, lunar_year, is_leap, can_day, chi_day, can_month, chi_month, can_year, chi_year, solar_term_id, tz_id, algo_version | PK solar_date, index (extract(year), extract(month)), index jd | Bảng fact lõi mỗi ngày |
solar_term_event | id, term_index, term_name_vi, longitude_deg, occurred_at_utc, occurred_at_local, local_date, source_version | unique (term_index, occurred_at_local) | 24 tiết khí theo năm |
rule_set_version | id, domain, version, status, effective_from, hash, notes | unique (domain, version) | Version hóa rule tốt/xấu, giờ hoàng đạo… |
day_rule_result | solar_date FK, ruleset_id, quality_label, quality_score, good_hours_json, bad_hours_json, xung_tuoi_json, nen_lam_json, kieng_ky_json, note_json | unique (solar_date, ruleset_id) | Kết quả rule engine cho từng ngày |
share_snapshot | id, scope, target_date, ruleset_id, token, expires_at | unique token | Link chia sẻ cố định |
job_run | id, job_name, started_at, finished_at, status, payload, error_text | index (job_name, started_at desc) | Audit batch job/cron |
Điểm mấu chốt của schema là tách bảng fact lịch khỏi kết quả rule engine. Làm vậy sẽ cho phép thay đổi bộ quy tắc “ngày tốt/xấu” mà không phải tính lại toàn bộ lịch lõi. Nếu cần tìm ngày tốt theo nhiều mục đích, có thể bổ sung materialized view hoặc bảng day_search_projection được cập nhật bởi cron sau mỗi lần publish ruleset. Đây là nơi PostgreSQL và Redis bổ trợ nhau tốt: PostgreSQL giữ sự đúng đắn, Redis giữ tốc độ truy cập. [27]
Mô-đun tính toán và logic nghiệp vụ
Phần dưới đây là bộ pseudo-code ở mức production design. Với lịch Việt Nam hiện đại, có thể triển khai theo luồng tương thích bài “Thuật toán tính âm lịch” của Hồ Ngọc Đức; nếu cần tăng độ chính xác, thay riêng hai hàm newMoonDay và solarLongitude bằng engine ephemeris chính xác hơn, còn khung logic giữ nguyên. [28]
Quy đổi ngày dương sang âm
function solarToLunar(dd, mm, yyyy, tz = +7):
jd = jdFromGregorian(dd, mm, yyyy)
k = floor((jd - 2415021.076998695) / 29.530588853)
monthStart = newMoonDay(k + 1, tz)
if monthStart > jd:
monthStart = newMoonDay(k, tz)
a11 = lunarMonth11(yyyy, tz)
b11 = a11
if a11 >= monthStart:
lunarYear = yyyy
a11 = lunarMonth11(yyyy - 1, tz)
else:
lunarYear = yyyy + 1
b11 = lunarMonth11(yyyy + 1, tz)
lunarDay = jd - monthStart + 1
diff = floor((monthStart - a11) / 29)
lunarMonth = diff + 11
lunarLeap = false
if (b11 - a11) > 365:
leapOffset = leapMonthOffset(a11, tz)
if diff >= leapOffset:
lunarMonth = diff + 10
if diff == leapOffset:
lunarLeap = true
if lunarMonth > 12:
lunarMonth -= 12
if lunarMonth >= 11 and diff < 4:
lunarYear -= 1
return { lunarDay, lunarMonth, lunarYear, lunarLeap, jd }Quy đổi ngày âm sang dương
function lunarToSolar(lDay, lMonth, lYear, isLeap, tz = +7): if lMonth < 11: a11 = lunarMonth11(lYear - 1, tz) b11 = lunarMonth11(lYear, tz) else: a11 = lunarMonth11(lYear, tz) b11 = lunarMonth11(lYear + 1, tz) offset = lMonth - 11 if offset < 0: offset += 12 if (b11 - a11) > 365: leapOffset = leapMonthOffset(a11, tz) leapMonth = leapOffset - 2 if leapMonth < 0: leapMonth += 12 if isLeap and lMonth != leapMonth: throw InvalidLeapMonth if isLeap or offset >= leapOffset: offset += 1 k = floor(0.5 + (a11 - 2415021.076998695) / 29.530588853) monthStart = newMoonDay(k + offset, tz) return gregorianFromJd(monthStart + lDay - 1)
Tính Can Chi
function yearCanChi(lunarYear): canIndex = (lunarYear + 6) % 10 chiIndex = (lunarYear + 8) % 12 return CAN[canIndex] + " " + CHI[chiIndex] function monthCanChi(lunarMonth, lunarYear): canIndex = (lunarYear 12 + lunarMonth + 3) % 10 chiIndex = (lunarMonth + 1) % 12 // month 11 = Tý, 12 = Sửu, 1 = Dần... return CAN[canIndex] + " " + CHI_BY_MONTH[chiIndex] function dayCanChi(jd): canIndex = (jd + 9) % 10 chiIndex = (jd + 1) % 12 return CAN[canIndex] + " " + CHI[chiIndex]
Công thức Can-Chi năm, tháng, ngày ở trên bám bài mô tả thuật toán và khớp với ví dụ công khai của ngày 06/05/2026: Canh Thìn ngày, Nhâm Thìn tháng, Bính Ngọ năm trên cả Nôm Foundation và trang mẫu. [29]
Tính tiết khí
function computeSolarTerms(year, tz = +7):
results = []
for termIndex in 0..23:
targetLongitude = termIndex 15 degrees
[t0, t1] = bracketAroundExpectedTerm(year, termIndex)
while (t1 - t0) > 1 second:
mid = midpoint(t0, t1)
lon = apparentGeocentricSunLongitude(mid)
if normalizedDifference(lon, targetLongitude) <= 0:
t0 = mid
else:
t1 = mid
instantUtc = midpoint(t0, t1)
instantLocal = convertToTimezone(instantUtc, "Asia/Bangkok")
results.push({
termIndex,
longitude: targetLongitude,
occurredAtUtc: instantUtc,
occurredAtLocal: instantLocal,
localDate: datePart(instantLocal)
})
return resultsHKO và VAST đều đặt bản chất của tiết khí ở các mốc kinh độ Mặt Trời trên hoàng đạo, còn Astronomy Engine hỗ trợ ecliptic coordinates và các event thiên văn đủ để hiện thực hóa hàm apparentGeocentricSunLongitude. Production nên lưu cả occurred_at_utc và occurred_at_local, vì việc gán tiết khí vào “ngày nào” là quyết định theo ngày địa phương. [30]
Tính giờ hoàng đạo, hắc đạo và ngày tốt/xấu
Đây là phần nên xem như rule engine, không phải lịch lõi. Trong khảo sát nguồn công khai, không xác định được một chuẩn quốc gia công khai duy nhất cho toàn bộ các lớp như “giờ hoàng đạo/hắc đạo”, “tuổi xung”, “Khổng Minh Lục Diệu”, “Ngọc Hạp Thông Thư”, “Bành Tổ Bách Kỵ” hay score cuối cùng ra “ngày tốt/xấu”. Do đó, thiết kế đúng là tra bảng có version.
function getAuspiciousHours(dayChi, ruleVersion):
pattern = lookupHourPattern(ruleVersion, dayChi)
goodHours = []
badHours = []
for chiHour in ALL_12_HOUR_BRANCHES:
slot = hourSlot(chiHour) // ví dụ Tý = 23:00-00:59
if pattern.includes(chiHour):
goodHours.push({ chiHour, slot })
else:
badHours.push({ chiHour, slot })
return { goodHours, badHours }
function classifyDay(calendarFacts, ruleVersion, purpose = null):
rules = loadRules(ruleVersion, purpose)
score = 0
score += rules.goodStars.match(calendarFacts)
score -= rules.badStars.match(calendarFacts)
score += rules.nguHanh.adjust(calendarFacts)
score += rules.truc.adjust(calendarFacts)
score += rules.nhiThapBatTu.adjust(calendarFacts)
score -= rules.ageConflict.adjust(calendarFacts)
label = score >= rules.goodThreshold ? "tot"
: score <= rules.badThreshold ? "xau"
: "binh"
return { label, score, explanations }Khuyến nghị thực tế là ban đầu chốt một ruleset_v1 bám đúng cách biểu diễn của sản phẩm mẫu, sau đó chỉ thay đổi qua cơ chế publish ruleset mới. Làm vậy sẽ tránh hiện tượng “hôm qua cùng một ngày mà hôm nay lại đổi từ tốt sang xấu” do deploy code. [31]
Bảo mật, quốc tế hóa và vận hành
Về múi giờ và quốc tế hóa, hệ thống nên lưu mốc thiên văn ở UTC nhưng luôn materialize “ngày lịch” theo Asia/Bangkok như yêu cầu. Ở tầng UI, việc hiển thị phải dùng formatter có timeZone và locale tường minh, không dựa vào timezone của trình duyệt cho các giá trị lịch pháp đã chuẩn hóa. API nên lưu stem/branch/solar-term dưới dạng mã chuẩn (0..9, 0..11, 0..23) và để UI map sang tiếng Việt/Anh. Intl.DateTimeFormat hỗ trợ truyền timeZone; W3C cũng nhấn mạnh i18n phải được thiết kế từ đầu thay vì vá sau. [32]
Về bảo mật API, bề mặt public-read thì khá an toàn nhưng backoffice publish ruleset, share snapshot và các API tìm ngày tốt dễ bị lạm dụng nếu không rate-limit. Theo OWASP, các rủi ro lớn của API hiện đại gồm Broken Object Level Authorization, Broken Authentication và Unrestricted Resource Consumption; vì vậy admin API nên dùng JWT hoặc session ký số, RBAC rõ ràng, log đầy đủ và rate limiting phân tán. Redis là lựa chọn tự nhiên cho token bucket/sliding window do hỗ trợ atomic counters và TTL. Ngoài ra nên đặt Content-Security-Policy, chặn frame-ancestors nếu không cần nhúng ngoài và nếu dùng Web Share API thì cấu hình Permissions-Policy tương ứng. [33]
Về triển khai, nhánh tiêu chuẩn là: repository monorepo, pipeline CI/CD bằng GitHub[34] Actions để chạy lint, unit test, snapshot test, contract test, migration dry-run rồi deploy sang staging trước production. Frontend có thể lên Vercel[35] hoặc Cloudflare[36] tùy ưu tiên DX hay edge network; database có thể dùng Neon serverless Postgres; cache/rate-limit có thể dùng Redis[37] managed hoặc serverless Redis. Đối với scale ngang, có thể dùng autoscaling của nền tảng hoặc HPA ở môi trường Kubernetes. [38]
Về giám sát nên có tối thiểu ba lớp. Lớp thứ nhất là synthetic checks cho các URL quan trọng như /hom-nay, /2026/05, /2026/05/06. Lớp thứ hai là application metrics: p95 latency, cache hit ratio, lỗi convert, số lần publish ruleset. Lớp thứ ba là alerting: Grafana Alerting hỗ trợ rule và routing notification trên nhiều nguồn dữ liệu; dùng để cảnh báo khi cron precompute lỗi, khi tỷ lệ sai khác với golden test vượt ngưỡng hoặc khi cache hit giảm bất thường. Backup tối thiểu nên có logical dump định kỳ; tốt hơn là Postgres provider có PITR. [39]
Kiểm thử, lộ trình và chi phí
Chiến lược kiểm thử nên bắt đầu từ golden data, không bắt đầu từ UI. Bộ golden test nên lấy từ ít nhất ba lớp nguồn: quy tắc/thuật toán (VNCal), bộ chuyển đổi công khai độc lập (Nôm Foundation) và một số mốc công khai từ báo/cơ quan nhà nước. Ví dụ, trang mẫu và Nôm Foundation cùng cho 06/05/2026 là ngày 20/03 âm lịch, Canh Thìn, tháng Nhâm Thìn, năm Bính Ngọ; các thông báo nghỉ Tết của cơ quan địa phương công bố rõ 16/02/2026 ứng với 29 tháng Chạp năm Ất Tỵ và 20/02/2026 ứng với mùng 4 tháng Giêng năm Bính Ngọ. Đây là các mốc kiểm chuẩn rất tốt cho regression test. [40]
Bộ test tối thiểu nên có
Nhóm test | Ví dụ | Mục tiêu |
Unit test lịch lõi | solarToLunar, lunarToSolar, yearCanChi, dayCanChi | Đúng công thức |
Boundary test | Ngày quanh Sóc, quanh Đông chí, quanh 00:00 địa phương | Bắt lỗi lệch múi giờ |
Regression test | So với golden cases công khai | Ngăn drift logic |
Integration test | API days, months, convert, search-auspicious | Đúng schema và cache |
UI snapshot | Desktop/mobile cho ngày thường, ngày nhuận, ngày có tiết khí | Ổn định giao diện |
Load test | GET /days/{date}, GET /months/{year}/{month} | Đảm bảo chịu tải |
Bộ regression nên có ít nhất các ca sau trong phase đầu:
Ca kiểm chuẩn | Kỳ vọng | Nguồn đối chiếu |
2026-05-06 | 20/03/2026 âm, ngày Canh Thìn, tháng Nhâm Thìn, năm Bính Ngọ, tiết khí Lập Hạ | Báo Nghệ An + Nôm Foundation [41] |
2026-02-16 | 29 tháng Chạp năm Ất Tỵ | Thông báo nghỉ Tết cơ quan nhà nước địa phương [42] |
2026-02-20 | mùng 4 tháng Giêng năm Bính Ngọ | Thông báo nghỉ Tết cơ quan nhà nước địa phương [42] |
Timeline triển khai đề xuất

Ước tính nhân lực và giờ công
Bảng dưới đây là ước tính theo giả định: không có thiết kế mỹ thuật riêng, không có CMS biên tập lớn, không có historical mode sâu trước giai đoạn hiện đại.
Vai trò | Giờ ước tính | Ghi chú |
Tech lead / solution architect | 60–90 | Chốt ruleset, kiến trúc, review thuật toán |
Frontend engineer | 140–180 | Responsive UI, routing, print/share |
Backend engineer | 160–220 | API, DB, cache, scheduler, search |
QA engineer | 80–120 | Unit/integration/regression/load |
DevOps / platform | 30–50 | CI/CD, logging, alerts, backup |
PM/BA hoặc content verifier | 30–60 | Chốt golden sources, nghiệm thu nghiệp vụ |
Tổng | 500–720 giờ | MVP production-ready |
Nếu dùng đơn giá blended nội bộ/agency khoảng 300.000–900.000 VNĐ/giờ, chi phí phát triển ước tính rơi vào khoảng 150–648 triệu VNĐ. Khoảng dao động lớn là do chất lượng nhân sự, có hay không historical mode, độ phức tạp của rule engine và mức độ QA/đối soát mong muốn. Nếu cần mức “chuẩn lịch sử” trước 1976 nên cộng thêm một phase nghiên cứu riêng; chi phí ở phần đó hiện không xác định cho đến khi chốt rõ phạm vi năm và phạm vi pháp định cần tái tạo.
Chi phí hạ tầng tham khảo
Phương án | Thành phần | Giá khởi điểm công khai | Nhận xét |
Gọn nhẹ | Cloudflare Workers + Neon + serverless Redis pay-as-you-go | từ khoảng 24 USD/tháng cộng usage | Phù hợp MVP/public read-heavy |
Dễ phát triển | Vercel Pro + Neon Launch + Redis fixed nhỏ | từ khoảng 49 USD/tháng cộng usage | DX tốt, triển khai nhanh |
Tự quản lý/k8s | VM/container + managed Postgres + managed Redis | không xác định | Chỉ nên dùng khi đã có SRE/ops ổn định |
Giá khởi điểm công khai tại thời điểm khảo sát: Vercel Pro từ 20 USD/tháng; Cloudflare Workers từ 5 USD/tháng; Neon Launch xuất hiện ở mức 19 USD/tháng trong tài liệu chính thức của Neon; Upstash dùng mô hình pay-per-request và các fixed plan mới từ 10 USD/tháng cho Redis DB nhỏ. Giá này chưa gồm vượt ngưỡng, băng thông, log retention, VAT hoặc chi phí observability bổ sung. [43]
Kết luận vận hành là khá rõ: nếu mục tiêu là một trang lịch công khai tương tự mẫu, cách làm hiệu quả nhất về kỹ thuật lẫn chi phí là tiền tính dữ liệu lịch, tách rule engine khỏi calendar engine, render/caching tối đa và dùng bộ golden test công khai để khóa độ đúng. Điểm cần chốt sớm nhất với stakeholder không phải giao diện, mà là: phạm vi năm, mức độ chính xác lịch sử và bộ quy tắc nào được coi là chuẩn cho “ngày tốt/xấu”. Khi ba câu đó rõ, phần triển khai còn lại là một bài toán web engineering tương đối chuẩn hóa.
[1] [8] https://developer.mozilla.org/en-US/docs/Web/API/Navigator/share
https://developer.mozilla.org/en-US/docs/Web/API/Navigator/share
[2] [15] [39] https://grafana.com/docs/grafana/latest/alerting/
https://grafana.com/docs/grafana/latest/alerting/
[3] [9] [31] [40] [41] https://baonghean.vn/lich-am-duong-hom-nay
https://baonghean.vn/lich-am-duong-hom-nay
[4] [35] https://www.timeanddate.com/worldclock/thailand/bangkok
https://www.timeanddate.com/worldclock/thailand/bangkok
[5] [7] [10] [11] [12] [19] [20] [21] [22] https://isi.vast.gov.vn/media/bbhn5dxp/bantin02-2015.pdf
https://isi.vast.gov.vn/media/bbhn5dxp/bantin02-2015.pdf
[6] https://www.xemamlich.uhm.vn/vncal_en.html
https://www.xemamlich.uhm.vn/vncal_en.html
[13] [30] https://www.hko.gov.hk/tc/gts/astron2021/files/2021SolarTerms24.pdf
https://www.hko.gov.hk/tc/gts/astron2021/files/2021SolarTerms24.pdf
[14] [28] [29] [34] https://www.xemamlich.uhm.vn/calrules.html
https://www.xemamlich.uhm.vn/calrules.html
[16] https://www.nomfoundation.org/nom-tools/Calendar/Calendar-conversion?uiLang=en
https://www.nomfoundation.org/nom-tools/Calendar/Calendar-conversion?uiLang=en
[17] https://github.com/cosinekitty/astronomy
https://github.com/cosinekitty/astronomy
[18] https://github.com/Hieu-BuiMinh/lunar-date-vn
https://github.com/Hieu-BuiMinh/lunar-date-vn
[23] [24] [25] [36] https://nextjs.org/docs/14/app/building-your-application/data-fetching/fetching-caching-and-revalidating
https://nextjs.org/docs/14/app/building-your-application/data-fetching/fetching-caching-and-revalidating
[26] [37] https://docs.nestjs.com/openapi/introduction
https://docs.nestjs.com/openapi/introduction
[27] https://redis.io/wp-content/uploads/2021/12/caching-at-scale-with-redis-updated-2021-12-04.pdf
https://redis.io/wp-content/uploads/2021/12/caching-at-scale-with-redis-updated-2021-12-04.pdf
[32] https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat
[33] https://owasp.org/www-project-api-security/
https://owasp.org/www-project-api-security/
[38] [43] https://vercel.com/pricing
https://vercel.com/pricing
[42] https://ducthinh.hatinh.gov.vn/api/image/383fe955-d8d9-4397-8ebc-4c2b3477072d
https://ducthinh.hatinh.gov.vn/api/image/383fe955-d8d9-4397-8ebc-4c2b3477072d


Để lại một phản hồi