Last active
August 18, 2024 19:18
-
-
Save phineas-pta/774fb001336fe2932cd37048904188c4 to your computer and use it in GitHub Desktop.
chuyển đổi dương lịch sang âm lịch
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
thuật toán gốc: https://www.informatik.uni-leipzig.de/~duc/amlich/calrules.html | |
bản julia này được chuyển từ bản python: https://github.com/quangvinh86/SolarLunarCalendar/blob/master/LunarSolar.py | |
sử dụng đơn vị đo góc = độ thay vì radian để làm gọn các phương trình thuật toán thiên văn | |
""" | |
#= | |
Quy luật của âm lịch Việt Nam: | |
- Ngày đầu tiên của tháng âm lịch là ngày chứa điểm Sóc | |
- Một năm bình thường có 12 tháng âm lịch, một năm nhuận có 13 tháng âm lịch | |
- Đông chí luôn rơi vào tháng 11 âm lịch | |
- Trong một năm nhuận, nếu có 1 tháng không có Trung khí thì tháng đó là tháng nhuận. Nếu nhiều tháng trong năm nhuận đều không có Trung khí thì chỉ tháng đầu tiên sau Đông chí là tháng nhuận | |
- Việc tính toán dựa trên kinh tuyến 105° đông. | |
Sóc là thời điểm hội diện, đó là khi trái đất, mặt trăng và mặt trời nằm trên một đường thẳng và mặt trăng nằm giữa trái đất và mặt trời. (Như thế góc giữa mặt trăng và mặt trời bằng 0 độ). Gọi là "hội diện" vì mặt trăng và mặt trời ở cùng một hướng đối với trái đất. Chu kỳ của điểm Sóc là khoảng 29,5 ngày. Ngày chứa điểm Sóc được gọi là ngày Sóc, và đó là ngày bắt đầu tháng âm lịch. | |
Trung khí là các điểm chia đường hoàng đạo thành 12 phần bằng nhau. Trong đó, bốn Trung khí giữa bốn mùa là đặc biệt nhất: Xuân phân (khoảng 20/3), Hạ chí (khoảng 22/6), Thu phân (khoảng 23/9) và Đông chí (khoảng 22/12). | |
Rules: | |
- The first day of the month is the day on which the New Moon occurs. | |
- An ordinary year has 12 lunar months; an intercalary year has 13 lunar months. | |
- The Winter Solstice always falls in month 11. | |
- In an intercalary year, a month in which there is no Principal Term is the intercalary month. It is assigned the number of the preceding month, with the further designation of intercalary. If two months of an intercalary year contain no Principal Term, only the first such month after the Winter Solstice is considered intercalary. | |
- Calculations are based on the meridian 105° East. | |
=# | |
"""value: 0.5 + time_zone / 24""" | |
const _HẰNG_SỐ = 0.5 + 7 / 24 | |
""" | |
Julian day number: continuous count of days since the beginning of the Julian period (January 1st 4713 BCE) | |
số ngày Julius: số ngày đã trôi qua kể từ ngày 1 tháng 1 năm 4713 trước Công Nguyên | |
""" | |
function _get_julian_day_number(dd::Int, mm::Int, yy::Int)::Int | |
tmp1 = (14 - mm) ÷ 12 | |
tmp_yy = yy + 4800 - tmp1 | |
tmp_mm = mm + 12 * tmp1 - 3 | |
tmp2 = dd + (153*tmp_mm + 2)÷5 + 365*tmp_yy + tmp_yy ÷ 4 | |
jdn = tmp2 - tmp_yy÷100 + tmp_yy÷400 - 32045 | |
if jdn < 2299161 # October 5th 1582 switch to Gregorian | |
jdn = tmp2 - 32083 | |
end | |
return jdn | |
end | |
""" | |
tính ngày Sóc: xác định ngày bắt đầu tháng âm lịch | |
""" | |
function _get_new🌙day(k::Int)::Int | |
# time in julian centuries since epoch 2000 (số thế kỉ Julius) | |
T = k / 1236.85 | |
T², T³, T⁴ = T^2, T^3, T^4 | |
# julian ephemeris days (thời gian trung bình của phase 🌙) | |
jde = ( | |
2415020.75933 | |
+ 29.53058868 * k | |
+ 0.0001178 * T² | |
- 0.000000155 * T³ | |
+ 0.00033 * Base.Math.sind(166.56 + 132.87*T - 0.009173*T²) | |
) | |
# 🌞 mean anomaly (khoảng cách góc trung bình 🌞) | |
M = 359.2242 + 29.10535608*k - 0.0000333*T² - 0.00000347*T³ | |
# 🌙 mean anomaly (khoảng cách góc trung bình 🌙) | |
M′ = 306.0253 + 385.81691806*k + 0.0107306*T² + 0.00001236*T³ | |
# 🌙 argument of latitude (đối số vĩ độ 🌙) | |
F = 21.2964 + 390.67050646*k - 0.0016528*T² - 0.00000239*T³ | |
# 🌙 equation of the center (phương trình trung tâm 🌙): case when phase = 0 (trăng non) & phase = 0.5 (trăng đầy) | |
C = ( | |
(0.1734 - 0.000393*T) * Base.Math.sind(M) | |
+ 0.0021 * Base.Math.sind(2M) | |
- 0.4068 * Base.Math.sind(M′) | |
+ 0.0161 * Base.Math.sind(2M′) | |
- 0.0004 * Base.Math.sind(3M′) | |
+ 0.0104 * Base.Math.sind(2F) | |
- 0.0051 * Base.Math.sind(M + M′) | |
- 0.0074 * Base.Math.sind(M - M′) | |
+ 0.0004 * Base.Math.sind(2F + M) | |
- 0.0004 * Base.Math.sind(2F - M) | |
- 0.0006 * Base.Math.sind(2F + M′) | |
+ 0.0010 * Base.Math.sind(2F - M′) | |
+ 0.0005 * Base.Math.sind(M + 2M′) | |
) | |
if T < -11 | |
Δt = 0.001 + 0.000839*T + 0.0002261*T² - 0.00000845*T³ - 0.000000081*T⁴ | |
else | |
Δt = -0.000278 + 0.000265*T + 0.000262*T² | |
end | |
new_julian_day = jde + C - Δt + _HẰNG_SỐ | |
return trunc(Int, new_julian_day) | |
end | |
""" | |
🌞 longitude with accuracy 0.01° | |
toạ độ hoàng đạo của 🌞: để biết Trung khí nào nằm trong tháng âm lịch nào, ta chỉ cần tính xem mặt trời nằm ở khoảng nào trên đường hoàng đạo vào thời điểm bắt đầu một tháng âm lịch. | |
""" | |
function _get🌞longitude(dayNumber::Int)::Int | |
T = (dayNumber - _HẰNG_SỐ - 2451545) / 36525 # time in julian centuries of 36525 ephemeris days from epoch 2000 | |
T², T³ = T^2, T^3 | |
# 🌞 mean anomaly (khoảng cách góc trung bình 🌞) | |
M = 357.52910 + 35999.05030*T - 0.0001559*T² - 0.00000048*T³ | |
# 🌞 geometric mean longitude | |
L₀ = 280.46645 + 36000.76983*T + 0.0003032*T² | |
# 🌞 equation of the center (phương trình trung tâm 🌞) | |
C = ( | |
(1.914600 - 0.004817*T - 0.000014*T²) * Base.Math.sind(M) | |
+ (0.019993 - 0.000101*T) * Base.Math.sind(2M) | |
+ 0.000290 * Base.Math.sind(3M) | |
) | |
# 🌞 true geometric longitude referred to mean equinox of the date | |
L = L₀ + C | |
return trunc(Int, 12 * (L/180 - 2*trunc(L/360))) # chia thành 24 cung để xác định tiết khí | |
end | |
""" | |
Tìm ngày bắt đầu tháng 11 âm lịch: Đông chí thường nằm vào khoảng 19/12-22/12, như vậy trước hết ta tìm ngày Sóc trước ngày 31/12. Nếu tháng bắt đầu vào ngày đó không chứa Đông chí thì ta phải lùi lại 1 tháng nữa. | |
""" | |
function _get🌙month11(yy::Int)::Int | |
off = _get_julian_day_number(31, 12, yy) - 2415021 | |
k = trunc(Int, off / 29.530588853) | |
nm = _get_new🌙day(k) | |
if _get🌞longitude(nm) ≥ 9 | |
nm = _get_new🌙day(k - 1) | |
end | |
return nm | |
end | |
""" | |
Xác định tháng nhuận: Nếu giữa hai tháng 11 âm lịch (tức tháng có chứa Đông chí) có 13 tháng âm lịch thì năm âm lịch đó có tháng nhuận. | |
a11 = ngày bắt đầu tháng 11 âm lịch mà một trong 13 tháng sau đó là tháng nhuận | |
""" | |
function _get_leap_month_offset(a11::Int)::Int | |
k = trunc(Int, (a11 - 2415021.076998695) / 29.530588853 + 0.5) | |
last = 0 | |
i = 1 # start with the month following lunar month 11 | |
arc = _get🌞longitude(_get_new🌙day(k + i)) | |
while true | |
last = arc | |
i += 1 | |
arc = _get🌞longitude(_get_new🌙day(k + i)) | |
if !(arc ≠ last && i < 14) break end | |
end | |
return i - 1 | |
end | |
function _convert🌞▶🌙(🌞year::Int, jdn::Int)::Tuple{Int, Int, Int, Bool} | |
k = trunc(Int, (jdn - 2415021.076998695) / 29.530588853) | |
month_start = _get_new🌙day(k + 1) | |
if month_start > jdn | |
month_start = _get_new🌙day(k) | |
end | |
a11 = b11 = _get🌙month11(🌞year) | |
if a11 ≥ month_start | |
🌙year = 🌞year | |
a11 = _get🌙month11(🌞year - 1) | |
else | |
🌙year = 🌞year + 1 | |
b11 = _get🌙month11(🌞year + 1) | |
end | |
🌙day = jdn - month_start + 1 | |
diff = (month_start - a11) ÷ 29 | |
🌙leap = false | |
🌙month = diff + 11 | |
if b11 - a11 > 365 | |
leap_month_diff = _get_leap_month_offset(a11) | |
if diff ≥ leap_month_diff | |
🌙month = diff + 10 | |
if diff == leap_month_diff | |
🌙leap = true | |
end | |
end | |
end | |
if 🌙month > 12 | |
🌙month -= 12 | |
end | |
if 🌙month ≥ 11 && diff < 4 | |
🌙year -= 1 | |
end | |
return 🌙year, 🌙month, 🌙day, 🌙leap | |
end | |
const _NGÀY_TRONG_TUẦN = ["Thứ 2", "Thứ 3", "Thứ 4", "Thứ 5", "Thứ 6", "Thứ 7", "Chủ nhật"] | |
"""天干 Heavenly Stems""" | |
const _THIÊN_CAN = ["Giáp", "Ất", "Bính", "Đinh", "Mậu", "Kỷ", "Canh", "Tân", "Nhâm", "Quý"] | |
"""地支 Earthly Branches""" | |
const _ĐỊA_CHI = ["Tí", "Sửu", "Dần", "Mão", "Thìn", "Tị", "Ngọ", "Mùi", "Thân", "Dậu", "Tuất", "Hợi"] | |
"""節氣 24 solar terms""" | |
const _TIẾT_KHÍ = ["Xuân phân", "Thanh minh", "Cốc vũ", "Lập hạ", "Tiểu mãn", "Mang chủng", "Hạ chí", "Tiểu thử", "Đại thử", "Lập thu", "Xử thử", "Bạch lộ", "Thu phân", "Hàn lộ", "Sương giáng", "Lập đông", "Tiểu tuyết", "Đại tuyết", "Đông chí", "Tiểu hàn", "Đại hàn", "Lập xuân", "Vũ Thủy", "Kinh trập"] | |
struct 🌞🌙Date | |
🌞day::Int | |
🌞month::Int | |
🌞year::Int | |
jdn::Int # Julian day number | |
weekday::String | |
🌙day::Int | |
🌙month::Int | |
🌙leap::Bool | |
🌙year::Int | |
💫hour0::String | |
💫day::String | |
💫month::String | |
💫term::String | |
💫year::String | |
function 🌞🌙Date(🌞day::Int, 🌞month::Int, 🌞year::Int) | |
1 ≤ 🌞day ≤ 31 && 1 ≤ 🌞month ≤ 12 || error("invalid date value") | |
1900 ≤ 🌞year || error("work best for dates after 1900") | |
jdn = _get_julian_day_number(🌞day, 🌞month, 🌞year) | |
weekday = _NGÀY_TRONG_TUẦN[jdn % 7 + begin] | |
🌙year, 🌙month, 🌙day, 🌙leap = _convert🌞▶🌙(🌞year, jdn) | |
tmp = _get🌞longitude(jdn+1) | |
if tmp ≥ 0 | |
💫term = _TIẾT_KHÍ[tmp + begin] | |
else | |
💫term = _TIẾT_KHÍ[tmp + end] | |
end | |
天💫hour0 = _THIÊN_CAN[(jdn - 1) * 2 % 10 + begin] | |
💫hour0 = 天💫hour0 * ' ' * _ĐỊA_CHI[begin] | |
天💫day = _THIÊN_CAN[(jdn + 9) % 10 + begin] | |
地💫day = _ĐỊA_CHI[(jdn + 1) % 12 + begin] | |
💫day = 天💫day * ' ' * 地💫day | |
天💫month = _THIÊN_CAN[(12 * 🌙year + 🌙month + 3) % 10 + begin] | |
地💫month = _ĐỊA_CHI[(🌙month + 1) % 12 + begin] | |
💫month = 天💫month * ' ' * 地💫month | |
天💫year = _THIÊN_CAN[(🌞year + 6) % 10 + begin] | |
地💫year = _ĐỊA_CHI[(🌞year + 8) % 12 + begin] | |
💫year = 天💫year * ' ' * 地💫year | |
return new(🌞day, 🌞month, 🌞year, jdn, weekday, 🌙day, 🌙month, 🌙leap, 🌙year, 💫hour0, 💫day, 💫month, 💫term, 💫year) | |
end | |
end | |
function Base.show(io::IO, date::🌞🌙Date) | |
res = "dương lịch: $(date.weekday) - ngày $(date.🌞day)/$(date.🌞month)/$(date.🌞year)\nâm lịch: $(date.🌙day)/$(date.🌙month)" | |
if date.🌙leap | |
res *= " (nhuận)" | |
end | |
res *= ", tiết $(date.💫term)\ngiờ $(date.💫hour0), ngày $(date.💫day), tháng $(date.💫month), năm $(date.💫year)\n" | |
print(io, res) | |
end | |
if abspath(PROGRAM_FILE) == @__FILE__ | |
println(🌞🌙Date(11, 12, 1975)) | |
println(🌞🌙Date( 5, 5, 2466)) | |
# đối chiếu với kết quả trên https://www.informatik.uni-leipzig.de/~duc/amlich/PHP/index.php | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment