Skip to content

Instantly share code, notes, and snippets.

@phineas-pta
Last active August 18, 2024 19:18
Show Gist options
  • Save phineas-pta/774fb001336fe2932cd37048904188c4 to your computer and use it in GitHub Desktop.
Save phineas-pta/774fb001336fe2932cd37048904188c4 to your computer and use it in GitHub Desktop.
chuyển đổi dương lịch sang âm lịch
"""
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 *
- 0.000000155 *
+ 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*- 0.00000347*
# 🌙 mean anomaly (khoảng cách góc trung bình 🌙)
M′ = 306.0253 + 385.81691806*k + 0.0107306*+ 0.00001236*
# 🌙 argument of latitude (đối số vĩ độ 🌙)
F = 21.2964 + 390.67050646*k - 0.0016528*- 0.00000239*
# 🌙 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*- 0.00000845*- 0.000000081*T⁴
else
Δt = -0.000278 + 0.000265*T + 0.000262*
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*- 0.00000048*
# 🌞 geometric mean longitude
L₀ = 280.46645 + 36000.76983*T + 0.0003032*
# 🌞 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 = ["", "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