Báo cáo kỹ thuật xây dựng trang lịch âm dương tiếng Việt

Báo cáo kỹ thuật xây dựng trang lịch âm dương tiếng Việt
Báo cáo kỹ thuật xây dựng trang lịch âm dương tiếng Việt

Để 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]

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:

Bố cục thông tin
Bố cục thông tin

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ệuphâ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

Sơ đồ kiến trúc
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 results

HKO 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

Timeline triển khai đề xuất
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 đadù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ử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

Bạn cảm thấy nội dung này thế nào?

Đã có 1061 lượt đánh giá với điểm trung bình là 5/5.

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

Vui lòng liên hệ hotline: 1900.0164 nếu cần hỗ trợ khẩn cấp!