Mô đun:Âm lịch

Bách khoa toàn thư mở Wikipedia
Tài liệu mô đun[xem] [sửa] [lịch sử] [làm mới]

Quy tắc[sửa mã nguồn]

Lập trình tính lịch âm[sửa mã nguồn]

Đã có nhiều chương trình hoàn chỉnh phục vụ cho việc tính toán Sóc và Trung khí với tính chính xác cao. Tuy vậy, muốn tính càng chính xác thì thuật toán càng phức tạp. Muốn viết một chương trình âm lịch với độ chính xác cao bạn nên sử dụng một thư viện thiên văn có sẵn chứ không nên lập trình từ đầu. Bạn có thể tải mã nguồn mở (C, C++, Java...) hoặc DLL để tính các thông số trên tại nhiều trang. Nếu bạn muốn tự lập trình thì có thể sử dụng những thuật toán sau đây. Chúng có lợi thế là tương đối đơn giản hơn nhưng vẫn cho kết quả khá tốt. (Sai số khi tính điểm Sóc dưới 2 phút). Những thuật toán này được giới thiệu dưới dạng mã Java; chúng đã được một số bạn thực hiện với các ngôn ngữ khác.

Một số công thức hỗ trợ[sửa mã nguồn]

Trong tính toán thiên văn người ta lấy 12:00:00 GMT ngày 1/1/4713 TCN của lịch Julian (tức ngày 24/11/4714 TCN theo lịch Gregorian) làm điểm gốc. Số ngày tính từ điểm gốc này gọi là số ngày Julius (Julian day number) của một thời điểm. Ví dụ, số ngày Julius của 00:00:00 GMT 1/1/2000 là 2451544.5. Ngày Julius 2451544.2083333335 tương ứng với ngày 31/12/1999 17:00:00 GMT, tức 00:00:00 ngày 1/1/2000 theo giờ Việt Nam. (Việt Nam nằm trong múi giờ thứ 7, do vậy giờ Việt Nam sớm hơn GMT 7 giờ, tức 7/24 = 0.29167 ngày.)

Trong các thuật toán sau chúng ta sử dụng mã của ngôn ngữ Java. Các hàm toán học được sử dụng sau hoặc đã có sẵn trong các ngôn ngữ khác hoặc có thể định nghĩa dễ dàng: Math.PI = 3.14159265358979323846, Math.sqrt(x) là căn bậc hai của x, Math.abs(x) là giá trị tuyệt đối của x, Math.floor(x) là số nguyên lớn nhất không lớn hơn x, còn Math.sin, Math.cos, Math.tan, Math.atan và Math.atan2 là các hàm lượng giác quen thuộc. Phép chia a/b được hiểu như sau. Nếu cả a và b là số nguyên (int) thì/là chia số nguyên, bỏ phần dư. Ví dụ: 7/12 = 0; -5/12 = 0; -13/12 = -1. Nếu ít nhất một trong hai số là số thực (real, float, double) thì/là phép chia số thực, ví dụ: 7.25/2.4 = 3.0208333333333335. Chú ý: khi viết 7.0 ta hiểu là tính toán với số thực 7.0, còn nếu chỉ viết 7 sẽ tính với số nguyên 7. INT(x) tính phần nguyên của một số thực x và MOD tính số dư của phép chia số nguyên x/y với một khác biệt nhỏ: nếu số dư là 0 thì MOD trả lại kết quả là y. Trong Java các hàm đó như sau:

	local function INT(double d) local
		return (int)Math.floor(d);
	end

	public static int MOD(int x, int y) local
		int z = x - (int)(y * Math.floor(((double)x / y)));
		if (z == 0) {
		  z = y;
		end
		return z;
	end

Tại Wikipedia:

local function toint(n)
    local s = tostring(n)
    local i, j = s:find('%.')
    if i then
        return tonumber(s:sub(1, i-1))
    else
        return n
    end
end

Đổi ngày dương lịch ra số ngày Julius[sửa mã nguồn]

Viết ngày dương lịch dưới dạng D,M,Y (ngày = D, tháng = M, năm = Y). D là một số nguyên từ 1 đến 31, M từ 1 đến 12 và Y là một số nguyên lớn hơn -4712 (âm 4712). Hàm sau tính số ngày Julius của 00:00:00 GMT ngày D/M/Y.

local function UniversalToJD(D, M, Y)
	local JD
	if (Y > 1582 or (Y == 1582 and M > 10) or (Y == 1582 and M == 10 and D > 14)) then
		JD = 367*Y - toint(7*(Y+toint((M+9)/12))/4) - toint(3*(toint((Y+(M-9)/7)/100)+1)/4) + toint(275*M/9)+D+1721028.5
	else
		JD = 367*Y - toint(7*(Y+5001+toint((M-9)/7))/4) + toint(275*M/9)+D+1729776.5
	end
	return JD
end

Ví dụ: UniversalToJD(1, 1, 2000) = 2451544.5; UniversalToJD(4, 10, 1582) = 2299159.5; UniversalToJD(15, 10, 1582) = 2299160.5;

Đổi số ngày Julius ra ngày dương lịch[sửa mã nguồn]

Cho jd là một số dương. Công thức sau đổi ngày Julius jd ra ngày dương lịch m/d/y (theo giờ GMT):

function UniversalFromJD(JD)
--	local JD = tonumber(frame.args.JD)
	local Z, A, alpha, B, C, D, E, dd, mm, yyyy, F
	Z = toint(JD+0.5)
	F = (JD+0.5)-Z
    if (Z < 2299161) then
    	A = Z
    else
    	alpha = toint((Z-1867216.25)/36524.25)
    	A = Z + 1 + alpha - toint(alpha/4)
    end
    B = A + 1524
	C = toint((B-122.1)/365.25)
	D = toint(365.25*C)
	E = toint((B-D)/30.6001)
	dd = toint(B - D - toint(30.6001*E) + F)
	if E < 14 then
		mm = E - 1
	else
	    mm = E - 13
	end
	if mm < 3 then
	    yyyy = C - 4715
	else
	    yyyy = C - 4716;
	end
	return dd, mm, yyyy
end

Ví dụ: UniversalFromJD(2451544.5) = (1, 1, 2000); UniversalFromJD(2451544.2083333335) = (31, 12, 1999); UniversalFromJD(2299160.5) = (15, 10, 1582); UniversalFromJD(2299159.5) = (4, 10, 1582)

Chuyển đổi số ngày Julius/ngày dương lịch theo giờ địa phương[sửa mã nguồn]

Để chuyển đổi giữa số ngày Julius và ngày dương lịch theo giờ địa phương tại một múi giờ khác, ta phải thêm hoặc bớt khoảng cách (tính bằng ngày) giữa giờ địa phương và GMT. Các hàm LocalFromJD và LocalToJD cho phép làm điều này: LocalFromJD tính ngày tháng năm dương lịch theo giờ địa phương cho một thời điểm, còn LocalToJD tính số ngày Julius cho 0h sáng giờ địa phương ngày D/M/Y. Trong các công thức này, LOCAL_TIMEZONE là số giờ cách biệt giữa giờ địa phương và GMT. (Việt Nam: LOCAL_TIMEZONE = 7.0; Trung Quốc: LOCAL_TIMEZONE = 8.0; Nhật Bản: LOCAL_TIMEZONE = 9.0; California: LOCAL_TIMEZONE = -8.0).

local function LocalFromJD(JD, utc)
	return UniversalFromJD(JD + utc/24.0)
end

local function LocalToJD(D, M, Y, utc)
--	D = tonumber(frame.args.D)
--	M = tonumber(frame.args.M)
--	Y = tonumber(frame.args.Y)
    return UniversalToJD(D, M, Y) - utc/24
end

Ví dụ: với LOCAL_TIMEZONE = 7.0 thì LocalToJD(1, 1, 2000) = 2451544.2083333335 và LocalFromJD(2451544.2083333335) = (1, 1, 2000).

Tính thời điểm Sóc[sửa mã nguồn]

Như trên đã nói, việc quan trọng đầu tiên khi tính lịch âm là tính xem các điểm Sóc (tức Hội diện) rơi vào ngày nào. Thuật toán sau cho phép tính thời điểm (tính bằng số ngày Julius) của Sóc thứ k tính từ điểm Sóc lúc 13:51 GMT ngày 1/1/1900 (ngày Julius 2415021.076998695).

local function NewMoon(k)
	pi = 3.14159265358979323846
	local T = k/1236.85 -- Time in Julian centuries from 1900 January 0.5
	local T2 = T * T
	local T3 = T2 * T
	local dr = math.pi/180
	local Jd1 = 2415020.75933 + 29.53058868*k + 0.0001178*T2 - 0.000000155*T3
	Jd1 = Jd1 + 0.00033*math.sin((166.56 + 132.87*T - 0.009173*T2)*dr) -- Mean new moon
	local M = 359.2242 + 29.10535608*k - 0.0000333*T2 - 0.00000347*T3 -- Sun's mean anomaly
	local Mpr = 306.0253 + 385.81691806*k + 0.0107306*T2 + 0.00001236*T3 -- Moon's mean anomaly
	local F = 21.2964 + 390.67050646*k - 0.0016528*T2 - 0.00000239*T3 -- Moon's argument of latitude
	local C1=(0.1734 - 0.000393*T)*math.sin(M*dr) + 0.0021*math.sin(2*dr*M)
	C1 = C1 - 0.4068*math.sin(Mpr*dr) + 0.0161*math.sin(dr*2*Mpr)
	C1 = C1 - 0.0004*math.sin(dr*3*Mpr)
	C1 = C1 + 0.0104*math.sin(dr*2*F) - 0.0051*math.sin(dr*(M+Mpr))
	C1 = C1 - 0.0074*math.sin(dr*(M-Mpr)) + 0.0004*math.sin(dr*(2*F+M))
	C1 = C1 - 0.0004*math.sin(dr*(2*F-M)) - 0.0006*math.sin(dr*(2*F+Mpr))
	C1 = C1 + 0.0010*math.sin(dr*(2*F-Mpr)) + 0.0005*math.sin(dr*(2*Mpr+M))
	local deltat
	if T < -11 then
		deltat= 0.001 + 0.000839*T + 0.0002261*T2 - 0.00000845*T3 - 0.000000081*T*T3
	else
		deltat= -0.000278 + 0.000265*T + 0.000262*T2
	end
	local JdNew = Jd1 + C1 - deltat
	return JdNew
end

Với hàm này ta có thể tính được: NewMoon(1236) = 2451520.4393767994, UniversalFromJD(2451520.4393767994) = (7, 12, 1999) còn theo múi giờ thứ 7 của Việt Nam là LocalFromJD(2451520.4393767994) = (8, 12, 1999). (Số ngày Julius 2451520.4393767994 tương ứng với 7/12/1999 22:32:42 GMT hay 8/12/1999 5:32:42 giờ Việt Nam). Như thế theo giờ GMT thì Sóc cuối cùng trong năm 1999 (Sóc thứ 1236 tính từ 1/1/1900) xảy ra vào ngày 7/12/1999 nhưng theo múi giờ thứ 7 thì Sóc này rơi vào ngày 8/12/1999. Tương tự, NewMoon(1237) = 2451550.2601371277; LocalFromJD(2451550.2601371277) = (7, 1, 2000); NewMoon(1238) = 2451580.0448043263; LocalFromJD(2451580.0448043263) = (5, 2, 2000); NewMoon(1239) = 2451609.721434823; LocalFromJD(2451609.721434823) = (6, 3, 2000) v.v. Như thế, theo giờ Việt Nam thì Sóc đầu tiên trong năm 2000 rơi vào ngày 7/1/2000, Sóc thứ hai vào ngày 5/2/2000 và Sóc thứ ba vào ngày 6/3/2000. Với những số liệu này ta đã có thể suy đoán được rằng có một tháng âm lịch bắt đầu ngày 8/12/1999 và các tháng sau đó bắt đầu vào ngày 7/1/2000, 5/2/2000 và 6/3/2000.

Tính vị trí mặt trời tại một thời điểm[sửa mã nguồn]

Dùng hàm NewMoon ta đã xác định được các ngày chứa điểm Sóc, như thế ta biết ngày bắt đầu các tháng âm lịch. Để biết tên các tháng này và biết liệu chúng có phải tháng nhuận không chúng ta cần dựa vào các qui tắc: (1) tháng 11 là tháng chứa Đông chí và (2) tháng nhuận là tháng đầu tiên không chứa Trung khí trong năm nhuận. Để tính xem tháng âm lịch nào chứa Đông chí và tháng vào không chứa Trung khí chúng ta tính vị trí mặt trời trên đường hoàng đạo tại các điểm bắt đầu tháng âm lịch và so sánh chúng với các tọa độ định nghĩa các điểm Trung khí. Thuật toán sau cho phép tính vị trí của mặt trời trên quĩ đạo của nó tại một thời điểm bất kỳ (được thể hiện bằng số ngày Julius của thời điểm đó). Kết quả mà thuật toán trả lại là một góc Rađian giữa 0 và 2*PI. Vị trí mặt trời được sử dụng để chia năm thời tiết thành 24 Khí (12 Tiết khí và 12 Trung khí). Một Trung khí là thời điểm mà kinh độ mặt trời có một trong những giá trị sau: 0*PI/6, 1*PI/6, 2*PI/6, 3*PI/6, 4*PI/6, 5*PI/6, 6*PI/6, 7*PI/6, 8*PI/6, 9*PI/6, 10*PI/6, 11*PI/6. Các điểm "Phân-Chí" được định nghĩa bằng các tọa độ sau: Xuân phân: 0, Hạ chí: PI/2, Thu phân: PI, Đông chí: 3*PI/2.

local function SunLongitude(jdn)
	local T = (jdn - 2451545.0 ) / 36525 -- Time in Julian centuries from 2000-01-01 12:00:00 GMT
	local T2 = T*T
	local dr = math.pi/180 -- degree to radian
	local M = 357.52910 + 35999.05030*T - 0.0001559*T2 - 0.00000048*T*T2 -- mean anomaly, degree
	local L0 = 280.46645 + 36000.76983*T + 0.0003032*T2 -- mean longitude, degree
	local DL = (1.914600 - 0.004817*T - 0.000014*T2)*math.sin(dr*M)
	DL = DL + (0.019993 - 0.000101*T)*math.sin(dr*2*M) + 0.000290*math.sin(dr*3*M)
	local L = L0 + DL -- true longitude, degree
	L = L*dr
	L = L - math.pi*2*(toint(L/(math.pi*2))) -- Normalize to (0, 2*PI)
	return L
end

Ví dụ: kinh độ mặt trời vào lúc 00:00 giờ Hà Nội ngày 8/12/1999 (ngày Julius 2451520.2083333335) là SunLongitude(2451520.2083333335) = 4.453168980086705. Kinh độ mặt trời lúc 00:00 ngày 7/1/2000 (giờ Hà Nội) là SunLongitude(2451520.2083333335) = 4.986246180809974. Vì 4.453168980086705 < 3*PI/2 (= 4.71238898038469) < 4.986246180809974 nên tháng âm lịch bắt đầu vào ngày 8/12/1999 chứa Đông chí.

Tính tháng âm lịch chứa ngày Đông chí[sửa mã nguồn]

Đông chí của một năm y thường rơi vào khoảng ngày 20-22 tháng 12 năm đó. Chúng ta nhớ lại rằng Đông chí là thời điểm mà kinh độ của mặt trời trên đường hoàng đạo là 3*PI/2. Như vậy để tính tháng âm lịch chứa Đông chí ta có thể dùng phương pháp sau:

local function LunarMonth11(Y, utc)
	local off = LocalToJD(31, 12, Y, utc) - 2415021.076998695
	local k = toint(off / 29.530588853)
	local jd = NewMoon(k)
	local ret = {LocalFromJD(jd, utc)}
	local sunLong = SunLongitude(LocalToJD(ret[1], ret[2], ret[3], utc)) -- sun longitude at local midnight
	if sunLong > 3*math.pi/2 then
		jd = NewMoon(k-1)
	end
	return LocalFromJD(jd, utc)
end
}

Đổi ngày dương lịch ra âm lịch và ngược lại[sửa mã nguồn]

Tính năm âm lịch[sửa mã nguồn]

Để thuận tiện cho việc chuyển đổi ngày cho năm âm lịch Y (năm mà ngày Tết rơi vào năm dương lịch Y) chúng ta tạo một bảng với cấu trúc như sau: bảng có 5 hàng và 13 cột (cho năm âm lịch thường) hoặc 14 cột (cho năm nhuận). Mỗi cột chứa 5 số nguyên: DD, MM, YY, NM, LEAP với ý nghĩa sau: ngày dương lịch DD/MM/YY là ngày bắt đầu tháng âm lịch; NM là tên của tháng âm đó (1 đến 12), và LEAP là 1 nếu tháng âm lịch đó là tháng nhuận và 1 nếu tháng đó là tháng thường. Cột đầu tiên thể hiện tháng âm lịch chứa Đông chí năm Y-1 và cột cuối cùng là tháng chứa Đông chí năm Y.

local function LunarYear(Y, b, utc)
	local ret1 = {}
	local ret2 = {}
	local ret3 = {}
	local ret4 = {}
	local ret5 = {}
	local month11A = {LunarMonth11(Y-1, utc)}
	local jdMonth11A = LocalToJD(month11A[1], month11A[2], month11A[3], utc)
	local k = math.floor(0.5 + (jdMonth11A - 2415021.076998695) / 29.530588853)
	local month11B = {LunarMonth11(Y, utc)}
	local off = LocalToJD(month11B[1], month11B[2], month11B[3], utc) - jdMonth11A
	local leap
	local retlength
	local i
	if off > 365.0 then
		retlength = 14
		leap = true
	else
		retlength = 13
		leap = false
	end
	ret1[1] = month11A[1]
	ret2[1] = month11A[2]
	ret3[1] = month11A[3]
	ret4[1] = 0
	ret5[1] = 0
	ret1[retlength] = month11B[1]
	ret2[retlength] = month11B[2]
	ret3[retlength] = month11B[3]
	ret4[retlength] = 0
	ret5[retlength] = 0
	for i = 2, (retlength - 1) do
		local nm = NewMoon(k+i-1)
		local a = {LocalFromJD(nm, utc)}
		ret1[i] = a[1]
		ret2[i] = a[2]
		ret3[i] = a[3]
		ret4[i] = 0
		ret5[i] = 0
	end

	for i = 1, retlength do
		ret4[i] = math.mod(i + 10, 12)
	end
	if leap == true then
		local sunLongitudes = {}
    	sunLongitudes[retlength+1] = 0
    	for i = 1, retlength do
    		local a = {ret1[i], ret2[i], ret3[i], ret4[i], ret5[i]}
    		local jdAtMonthBegin = LocalToJD(a[1], a[2], a[3], utc)
    		sunLongitudes[i] = SunLongitude(jdAtMonthBegin)
    	end
    	local found = false
     	for i = 1, retlength do
    		if found == true then
     			ret4[i] = math.mod(i+9, 12)
    		end
    		local sl1 = sunLongitudes[i]
     		local sl2 = sunLongitudes[i+1]
    		local hasMajorTerm
	    	if math.floor(sl1/math.pi*6) == math.floor(sl2/math.pi*6) then
	     		found = true
	     		ret5[i] = 1
	     		ret4[i] = math.mod(i+9, 12)
	    	end
     	end
	end
	if b == 1 then
		return unpack(ret1)
	else
		if b == 2 then
		    return unpack(ret2)
		else
			if b == 3 then
	        	return unpack(ret3)
		    else
		    	if b == 4 then
	            	return unpack(ret4)
	         	else
	         		if b == 5 then
		                return unpack(ret5)
		            else
		                return retlength
		            end
		    	end
			end
		end
    end
end

Đổi ngày dương lịch ra âm lịch[sửa mã nguồn]

Sau khi đã tính được tất cả các tháng âm lịch trong một năm ta có thể dễ dàng tìm ra tháng âm lịch chứa một ngày dương lịch d/m/y bất kỳ. Hàm Solar2Lunar đổi ngày D/M/Y dương lịch ra ngày âm lịch (dd,mm,yy,leap) tương ứng: ngày dd tháng mm năm yy âm lịch; mm là tháng nhuận nếu leap=1.

local function Solar2Lunar(D, M, Y, utc)
	local yy = Y
	local ly1 = {LunarYear(Y,1, utc)}
	local ly2 = {LunarYear(Y,2, utc)}
	local ly3 = {LunarYear(Y,3, utc)}
	local ly4 = {LunarYear(Y,4, utc)}
	local ly5 = {LunarYear(Y,5, utc)}
	local month11length = LunarYear(Y,6, utc)
	local month11 = {ly1[month11length], ly2[month11length], ly3[month11length]}
	local jdToday = LocalToJD(D, M, Y, utc)
	local jdMonth11 = LocalToJD(month11[1], month11[2], month11[3], utc)
	if (jdToday > jdMonth11 or jdToday == jdMonth11) then
		ly1 = {LunarYear(Y+1,1, utc)}
	    ly2 = {LunarYear(Y+1,2, utc)}
        ly3 = {LunarYear(Y+1,3, utc)}
	    ly4 = {LunarYear(Y+1,4, utc)}
	    ly5 = {LunarYear(Y+1,5, utc)}
	    yy = Y + 1
	end
	local i = LunarYear(Y,6, utc)
	while jdToday < LocalToJD(ly1[i], ly2[i], ly3[i], utc) and i > 1 do
		i = i -1
	end
	local dd = toint(jdToday - LocalToJD(ly1[i], ly2[i], ly3[i], utc)) + 1
	local mm = ly4[i]
	if mm >= 11 or mm == 0 then
		yy = yy -1
	end
	return unpack{dd, mm, yy, ly5[i]}
end

Đổi ngày âm lịch ra dương lịch[sửa mã nguồn]

Hàm Lunar2Solar chuyển ngược lại, từ ngày D tháng M (thường nếu leap = 0 hay nhuận nếu leap = 1) năm Y âm lịch ra ngày dd/mm/yy dương lịch.

function p.Lunar2Solar(frame)
	D = tonumber(frame.args.D)
	M = tonumber(frame.args.M)
	Y = tonumber(frame.args.Y)
	utc = tonumber(frame.args.utc)
	DF = frame.args.DF
	local leap = tonumber(frame.args.nhuan)
    local yy = Y
    if M>= 11 then
    	yy = Y + 1
    end
    local lm = {}
    local lm1 = {LunarYear(yy, 1, utc)}
    local lm2 = {LunarYear(yy, 2, utc)}
    local lm3 = {LunarYear(yy, 3, utc)}
    local lm4 = {LunarYear(yy, 4, utc)}
    local lm5 = {LunarYear(yy, 5, utc)}
    local length = LunarYear(yy, 6, utc)
    local lunarMonth = {}
    local jd
    lunarMonth[1] = {"null"}
    local i
    if M == 12 then
    	M = 0
    end
    for i = 1, length do
    	lm = {lm1[i], lm2[i], lm3[i], lm4[i], lm5[i]}
        if lm[4] == M and lm[5] == leap then
            lunarMonth = {lm[1], lm[2], lm[3]}
            break
        end
    end
	if unpack{lunarMonth} == "null" then
		return "Lỗi nhập!"
	else
		jd = LocalToJD(lunarMonth[1], lunarMonth[2], lunarMonth[3], utc)
		local dmy = {LocalFromJD(jd + D - 1, utc)}
		if DF == 'full' then
    	    return string.format ('ngày %d tháng %d năm %d', dmy[1], dmy[2], dmy[3])
		else
		if DF == 'jMY' then
    	    return string.format ('%d tháng %d năm %d', dmy[1], dmy[2], dmy[3])
		else
		if DF == 'dFY' then
    	    return string.format ('%02d tháng %02d năm %d', dmy[1], dmy[2], dmy[3])
		else
    	if DF == 'dm' then
    	    return string.format ('%02d%02d', dmy[1], dmy[2])
    	else
    	if DF == 'd-m-y' then
    	    return string.format ('%02d-%02d-%02d', dmy[1], dmy[2], dmy[3])
    	else
    	if DF == 'jM' then
    		return string.format ('%d tháng %d', dmy[1], dmy[2])
	    else
    	if DF == 'dM' then
    		return string.format ('%02d tháng %d', dmy[1], dmy[2])
    	else
    	if DF == 'Y' then
    		return string.format ('%02d', dmy[3])
    	else
    	if DF == 'j' then
    		return string.format ('%d', dmy[1])
    	else
    	if DF == 'd' then
    		return string.format ('%02d', dmy[1])
    	else
    	if DF == 'n' then
    		return string.format ('%d', dmy[2])
    	else
    	if DF == 'm' then
    		return string.format ('%02d', dmy[2])
    	else
    	if DF == 'M' then
    		return string.format ('tháng %d', dmy[2])
    	else
    	if DF == 'F' then
    		return string.format ('tháng %02d', dmy[2])
    	end end end end end end end end end end end end end end
	end
end

Tính ngày, tháng và năm Can-Chi cho âm lịch[sửa mã nguồn]

Hàm canchi gồm 10 can và 12 chi[sửa mã nguồn]

local function canchi (x)
	local a = x%10
	local b = x%12
	local can, chi
	if a == 1 then
		can = "Giáp"
    else
	if a == 2 then
    	can = "Ất"
    else
	if a == 3 then
    	can = "Bính"
    else
	if a == 4 then
    	can = "Đinh"
    else
	if a == 5 then
    	can = "Mậu"
    else
	if a == 6 then
    	can = "Kỷ"
    else
	if a == 7 then
		can = "Canh"
	else
	if a == 8 then
    	can = "Tân"
    else
	if a == 9 then
	    can = "Nhâm"
    else
    if a == 0 then
		can = "Quý"
    end end end end end end end end end end
	if b == 1 then
		chi = "Tý"
    else
	if b == 2 then
    	chi = "Sửu"
    else
	if b == 3 then
    	chi = "Dần"
    else
	if b == 4 then
    	chi = "Mão"
    else
	if b == 5 then
    	chi = "Thìn"
    else
	if b == 6 then
    	chi = "Tỵ" 
    else
	if b == 7 then
    	chi = "Ngọ"
    else
	if b == 8 then
    	chi = "Mùi"
    else
	if b == 9 then
		chi = "Thân"
	else
	if b == 10 then
    	chi = "Dậu"
    else
	if b == 11 then
	    chi = "Tuất"
    else
    if b == 0 then
		chi = "Hợi"
    end end end end end end end end end end end end
	return string.format ('%s %s', can, chi)
end

Tính ngày tháng và năm Can-Chi cho Âm lịch[sửa mã nguồn]

function p.main(frame)
	D = tonumber(frame.args.D)
	M = tonumber(frame.args.M)
	Y = tonumber(frame.args.Y)
	DF = frame.args.DF
	utc = tonumber(frame.args.utc)
	local ngay, thang, nam, nhuan
	local LunarDM = {Solar2Lunar(D, M, Y, utc)}
	ngay = LunarDM[1]
	if LunarDM[2] == 0 then
		thang = 12
	else
		thang = LunarDM[2]
	end
	nam = LunarDM[3]
	local canchinam = canchi (nam+57)
	local canchithang = canchi (nam*12+thang+14)
	local canchingay = canchi (toint(LocalToJD(D, M, Y, utc)+51.5))
	if LunarDM[4] == 1 then
		nhuan = " (nhuận)"
	else
		nhuan = ""
	end
    if DF == 'full' and ngay < 11 then
        return string.format ('mồng %d tháng %d%s năm %s', ngay, thang, nhuan, canchinam)
    else
    if DF == 'full' and ngay > 10 then
        return string.format ('ngày %d tháng %d%s năm %s', ngay, thang, nhuan, canchinam)
    else
    if DF == 'dm' then
        return string.format ('%02d%02d', ngay, thang)
    else
    if DF == 'jM' then
    	return string.format ('%d tháng %02d%s', ngay, thang, nhuan)
    else
    if DF == 'dM' then
    	return string.format ('%02d tháng %d%s', ngay, thang, nhuan)
    else
    if DF == 'Y' then
    	return string.format ('%s', canchi)
    else
    if DF == 'j' then
    	return string.format ('%d', ngay)
    else
    if DF == 'd' then
    	return string.format ('%02d', ngay)
    else
    if DF == 'n' then
    	return string.format ('%d', thang)
    else
    if DF == 'm' then
    	return string.format ('%02d', thang)
    else
    if DF == 'M' then
    	return string.format ('tháng %d', thang)
    else
    if DF == 'F' then
    	return string.format ('tháng %02d', thang)
    else
    if DF == 'YY' then
    	return string.format ('%s', canchinam)
    else
    if DF == 'MM' then
    	return string.format ('%s', canchithang)
    else
    if DF == 'DD' then
    	return string.format ('%s', canchingay)
    else
    if DF == 'DDMMYY' then
        return string.format (ngày '%s' tháng '%s' năm '%s', canchingay, canchithang, canchinam)
    end end end end end end end end end end end end end end end end
end

Tham khảo[sửa mã nguồn]

local p = {};

local function toint(n)
    local s = tostring(n)
    local i, j = s:find('%.')
    if i then
        return tonumber(s:sub(1, i-1))
    else
        return n
    end
end

local function UniversalToJD(D, M, Y)
	local JD
	if (Y > 1582 or (Y == 1582 and M > 10) or (Y == 1582 and M == 10 and D > 14)) then
		JD = 367*Y - toint(7*(Y+toint((M+9)/12))/4) - toint(3*(toint((Y+(M-9)/7)/100)+1)/4) + toint(275*M/9)+D+1721028.5
	else
		JD = 367*Y - toint(7*(Y+5001+toint((M-9)/7))/4) + toint(275*M/9)+D+1729776.5
	end
	return JD
end

function UniversalFromJD(JD)
--	local JD = tonumber(frame.args.JD)
	local Z, A, alpha, B, C, D, E, dd, mm, yyyy, F
	Z = toint(JD+0.5)
	F = (JD+0.5)-Z
    if (Z < 2299161) then
    	A = Z
    else
    	alpha = toint((Z-1867216.25)/36524.25)
    	A = Z + 1 + alpha - toint(alpha/4)
    end
    B = A + 1524
	C = toint((B-122.1)/365.25)
	D = toint(365.25*C)
	E = toint((B-D)/30.6001)
	dd = toint(B - D - toint(30.6001*E) + F)
	if E < 14 then
		mm = E - 1
	else
	    mm = E - 13
	end
	if mm < 3 then
	    yyyy = C - 4715
	else
	    yyyy = C - 4716;
	end
	return dd, mm, yyyy
end

local function LocalFromJD(JD, utc)
	return UniversalFromJD(JD + utc/24.0)
end

local function LocalToJD(D, M, Y, utc)
--	D = tonumber(frame.args.D)
--	M = tonumber(frame.args.M)
--	Y = tonumber(frame.args.Y)
    return UniversalToJD(D, M, Y) - utc/24
end

local function NewMoon(k)
	pi = 3.14159265358979323846
	local T = k/1236.85 -- Time in Julian centuries from 1900 January 0.5
	local T2 = T * T
	local T3 = T2 * T
	local dr = math.pi/180
	local Jd1 = 2415020.75933 + 29.53058868*k + 0.0001178*T2 - 0.000000155*T3
	Jd1 = Jd1 + 0.00033*math.sin((166.56 + 132.87*T - 0.009173*T2)*dr) -- Mean new moon
	local M = 359.2242 + 29.10535608*k - 0.0000333*T2 - 0.00000347*T3 -- Sun's mean anomaly
	local Mpr = 306.0253 + 385.81691806*k + 0.0107306*T2 + 0.00001236*T3 -- Moon's mean anomaly
	local F = 21.2964 + 390.67050646*k - 0.0016528*T2 - 0.00000239*T3 -- Moon's argument of latitude
	local C1=(0.1734 - 0.000393*T)*math.sin(M*dr) + 0.0021*math.sin(2*dr*M)
	C1 = C1 - 0.4068*math.sin(Mpr*dr) + 0.0161*math.sin(dr*2*Mpr)
	C1 = C1 - 0.0004*math.sin(dr*3*Mpr)
	C1 = C1 + 0.0104*math.sin(dr*2*F) - 0.0051*math.sin(dr*(M+Mpr))
	C1 = C1 - 0.0074*math.sin(dr*(M-Mpr)) + 0.0004*math.sin(dr*(2*F+M))
	C1 = C1 - 0.0004*math.sin(dr*(2*F-M)) - 0.0006*math.sin(dr*(2*F+Mpr))
	C1 = C1 + 0.0010*math.sin(dr*(2*F-Mpr)) + 0.0005*math.sin(dr*(2*Mpr+M))
	local deltat
	if T < -11 then
		deltat= 0.001 + 0.000839*T + 0.0002261*T2 - 0.00000845*T3 - 0.000000081*T*T3
	else
		deltat= -0.000278 + 0.000265*T + 0.000262*T2
	end
	local JdNew = Jd1 + C1 - deltat
	return JdNew
end

local function SunLongitude(jdn)
	local T = (jdn - 2451545.0 ) / 36525 -- Time in Julian centuries from 2000-01-01 12:00:00 GMT
	local T2 = T*T
	local dr = math.pi/180 -- degree to radian
	local M = 357.52910 + 35999.05030*T - 0.0001559*T2 - 0.00000048*T*T2 -- mean anomaly, degree
	local L0 = 280.46645 + 36000.76983*T + 0.0003032*T2 -- mean longitude, degree
	local DL = (1.914600 - 0.004817*T - 0.000014*T2)*math.sin(dr*M)
	DL = DL + (0.019993 - 0.000101*T)*math.sin(dr*2*M) + 0.000290*math.sin(dr*3*M)
	local L = L0 + DL -- true longitude, degree
	L = L*dr
	L = L - math.pi*2*(toint(L/(math.pi*2))) -- Normalize to (0, 2*PI)
	return L
end

local function LunarMonth11(Y, utc)
	local off = LocalToJD(31, 12, Y, utc) - 2415021.076998695
	local k = toint(off / 29.530588853)
	local jd = NewMoon(k)
	local ret = {LocalFromJD(jd, utc)}
	local sunLong = SunLongitude(LocalToJD(ret[1], ret[2], ret[3], utc)) -- sun longitude at local midnight
	if sunLong > 3*math.pi/2 then
		jd = NewMoon(k-1)
	end
	return LocalFromJD(jd, utc)
end

local function LunarYear(Y, b, utc)
	local ret1 = {}
	local ret2 = {}
	local ret3 = {}
	local ret4 = {}
	local ret5 = {}
	local month11A = {LunarMonth11(Y-1, utc)}
	local jdMonth11A = LocalToJD(month11A[1], month11A[2], month11A[3], utc)
	local k = math.floor(0.5 + (jdMonth11A - 2415021.076998695) / 29.530588853)
	local month11B = {LunarMonth11(Y, utc)}
	local off = LocalToJD(month11B[1], month11B[2], month11B[3], utc) - jdMonth11A
	local leap
	local retlength
	local i
	if off > 365.0 then
		retlength = 14
		leap = true
	else
		retlength = 13
		leap = false
	end
	ret1[1] = month11A[1]
	ret2[1] = month11A[2]
	ret3[1] = month11A[3]
	ret4[1] = 0
	ret5[1] = 0
	ret1[retlength] = month11B[1]
	ret2[retlength] = month11B[2]
	ret3[retlength] = month11B[3]
	ret4[retlength] = 0
	ret5[retlength] = 0
	for i = 2, (retlength - 1) do
		local nm = NewMoon(k+i-1)
		local a = {LocalFromJD(nm, utc)}
		ret1[i] = a[1]
		ret2[i] = a[2]
		ret3[i] = a[3]
		ret4[i] = 0
		ret5[i] = 0
	end

	for i = 1, retlength do
		ret4[i] = math.mod(i + 10, 12)
	end
	if leap == true then
		local sunLongitudes = {}
    	sunLongitudes[retlength+1] = 0
    	for i = 1, retlength do
    		local a = {ret1[i], ret2[i], ret3[i], ret4[i], ret5[i]}
    		local jdAtMonthBegin = LocalToJD(a[1], a[2], a[3], utc)
    		sunLongitudes[i] = SunLongitude(jdAtMonthBegin)
    	end
    	local found = false
     	for i = 1, retlength do
    		if found == true then
     			ret4[i] = math.mod(i+9, 12)
    		end
    		local sl1 = sunLongitudes[i]
     		local sl2 = sunLongitudes[i+1]
    		local hasMajorTerm
	    	if math.floor(sl1/math.pi*6) == math.floor(sl2/math.pi*6) then
	     		found = true
	     		ret5[i] = 1
	     		ret4[i] = math.mod(i+9, 12)
	    	end
     	end
	end
	if b == 1 then
		return unpack(ret1)
	else
		if b == 2 then
		    return unpack(ret2)
		else
			if b == 3 then
	        	return unpack(ret3)
		    else
		    	if b == 4 then
	            	return unpack(ret4)
	         	else
	         		if b == 5 then
		                return unpack(ret5)
		            else
		                return retlength
		            end
		    	end
			end
		end
    end
end

local function Solar2Lunar(D, M, Y, utc)
	local yy = Y
	local ly1 = {LunarYear(Y,1, utc)}
	local ly2 = {LunarYear(Y,2, utc)}
	local ly3 = {LunarYear(Y,3, utc)}
	local ly4 = {LunarYear(Y,4, utc)}
	local ly5 = {LunarYear(Y,5, utc)}
	local month11length = LunarYear(Y,6, utc)
	local month11 = {ly1[month11length], ly2[month11length], ly3[month11length]}
	local jdToday = LocalToJD(D, M, Y, utc)
	local jdMonth11 = LocalToJD(month11[1], month11[2], month11[3], utc)
	if (jdToday > jdMonth11 or jdToday == jdMonth11) then
		ly1 = {LunarYear(Y+1,1, utc)}
	    ly2 = {LunarYear(Y+1,2, utc)}
        ly3 = {LunarYear(Y+1,3, utc)}
	    ly4 = {LunarYear(Y+1,4, utc)}
	    ly5 = {LunarYear(Y+1,5, utc)}
	    yy = Y + 1
	end
	local i = LunarYear(Y,6, utc)
	while jdToday < LocalToJD(ly1[i], ly2[i], ly3[i], utc) and i > 1 do
		i = i -1
	end
	local dd = toint(jdToday - LocalToJD(ly1[i], ly2[i], ly3[i], utc)) + 1
	local mm = ly4[i]
	if mm >= 11 or mm == 0 then
		yy = yy -1
	end
	return unpack{dd, mm, yy, ly5[i]}
end

function p.Lunar2Solar(frame)
	D = tonumber(frame.args.D)
	M = tonumber(frame.args.M)
	Y = tonumber(frame.args.Y)
	utc = tonumber(frame.args.utc)
	DF = frame.args.DF
	local leap = tonumber(frame.args.nhuan)
    local yy = Y
    if M>= 11 then
    	yy = Y + 1
    end
    local lm = {}
    local lm1 = {LunarYear(yy, 1, utc)}
    local lm2 = {LunarYear(yy, 2, utc)}
    local lm3 = {LunarYear(yy, 3, utc)}
    local lm4 = {LunarYear(yy, 4, utc)}
    local lm5 = {LunarYear(yy, 5, utc)}
    local length = LunarYear(yy, 6, utc)
    local lunarMonth = {}
    local jd
    lunarMonth[1] = {"null"}
    local i
    if M == 12 then
    	M = 0
    end
    for i = 1, length do
    	lm = {lm1[i], lm2[i], lm3[i], lm4[i], lm5[i]}
        if lm[4] == M and lm[5] == leap then
            lunarMonth = {lm[1], lm[2], lm[3]}
            break
        end
    end
	if unpack{lunarMonth} == "null" then
		return "Lỗi nhập!"
	else
		jd = LocalToJD(lunarMonth[1], lunarMonth[2], lunarMonth[3], utc)
		local dmy = {LocalFromJD(jd + D - 1, utc)}
		if DF == 'full' then
    	    return string.format ('ngày %d tháng %d năm %d', dmy[1], dmy[2], dmy[3])
		else
		if DF == 'jMY' then
    	    return string.format ('%d tháng %d năm %d', dmy[1], dmy[2], dmy[3])
		else
		if DF == 'dFY' then
    	    return string.format ('%02d tháng %02d năm %d', dmy[1], dmy[2], dmy[3])
		else
    	if DF == 'dm' then
    	    return string.format ('%02d%02d', dmy[1], dmy[2])
    	else
    	if DF == 'd-m-y' then
    	    return string.format ('%02d-%02d-%02d', dmy[1], dmy[2], dmy[3])
    	else
    	if DF == 'jM' then
    		return string.format ('%d tháng %d', dmy[1], dmy[2])
	    else
    	if DF == 'dM' then
    		return string.format ('%02d tháng %d', dmy[1], dmy[2])
    	else
    	if DF == 'Y' then
    		return string.format ('%02d', dmy[3])
    	else
    	if DF == 'j' then
    		return string.format ('%d', dmy[1])
    	else
    	if DF == 'd' then
    		return string.format ('%02d', dmy[1])
    	else
    	if DF == 'n' then
    		return string.format ('%d', dmy[2])
    	else
    	if DF == 'm' then
    		return string.format ('%02d', dmy[2])
    	else
    	if DF == 'M' then
    		return string.format ('tháng %d', dmy[2])
    	else
    	if DF == 'F' then
    		return string.format ('tháng %02d', dmy[2])
    	end end end end end end end end end end end end end end
	end
end

local function canchi (x)
	local a = x%10
	local b = x%12
	local can, chi
	if a == 1 then
		can = "Giáp"
    else
	if a == 2 then
    	can = "Ất"
    else
	if a == 3 then
    	can = "Bính"
    else
	if a == 4 then
    	can = "Đinh"
    else
	if a == 5 then
    	can = "Mậu"
    else
	if a == 6 then
    	can = "Kỷ"
    else
	if a == 7 then
		can = "Canh"
	else
	if a == 8 then
    	can = "Tân"
    else
	if a == 9 then
	    can = "Nhâm"
    else
    if a == 0 then
		can = "Quý"
    end end end end end end end end end end
	if b == 1 then
		chi = "Tý"
    else
	if b == 2 then
    	chi = "Sửu"
    else
	if b == 3 then
    	chi = "Dần"
    else
	if b == 4 then
    	chi = "Mão"
    else
	if b == 5 then
    	chi = "Thìn"
    else
	if b == 6 then
    	chi = "Tỵ" 
    else
	if b == 7 then
    	chi = "Ngọ"
    else
	if b == 8 then
    	chi = "Mùi"
    else
	if b == 9 then
		chi = "Thân"
	else
	if b == 10 then
    	chi = "Dậu"
    else
	if b == 11 then
	    chi = "Tuất"
    else
    if b == 0 then
		chi = "Hợi"
    end end end end end end end end end end end end
	return string.format ('%s %s', can, chi)
end

function p.main(frame)
	D = tonumber(frame.args.D)
	M = tonumber(frame.args.M)
	Y = tonumber(frame.args.Y)
	DF = frame.args.DF
	utc = tonumber(frame.args.utc)
	local ngay, thang, nam, nhuan
	local LunarDM = {Solar2Lunar(D, M, Y, utc)}
	ngay = LunarDM[1]
	if LunarDM[2] == 0 then
		thang = 12
	else
		thang = LunarDM[2]
	end
	nam = LunarDM[3]
	local canchinam = canchi (nam+57)
	local canchithang = canchi (nam*12+thang+14)
	local canchingay = canchi (toint(LocalToJD(D, M, Y, utc)+51.5))
	if LunarDM[4] == 1 then
		nhuan = " (nhuận)"
	else
		nhuan = ""
	end
    if DF == 'full' and ngay < 11 then
        return string.format ('mồng %d tháng %d%s năm %s', ngay, thang, nhuan, canchinam)
    else
    if DF == 'full' and ngay > 10 then
        return string.format ('ngày %d tháng %d%s năm %s', ngay, thang, nhuan, canchinam)
    else
    if DF == 'dm' then
        return string.format ('%02d%02d', ngay, thang)
    else
    if DF == 'jM' then
    	return string.format ('%d tháng %02d%s', ngay, thang, nhuan)
    else
    if DF == 'dM' then
    	return string.format ('%02d tháng %d%s', ngay, thang, nhuan)
    else
    if DF == 'Y' then
    	return string.format ('%s', canchi)
    else
    if DF == 'j' then
    	return string.format ('%d', ngay)
    else
    if DF == 'd' then
    	return string.format ('%02d', ngay)
    else
    if DF == 'n' then
    	return string.format ('%d', thang)
    else
    if DF == 'm' then
    	return string.format ('%02d', thang)
    else
    if DF == 'M' then
    	return string.format ('tháng %d', thang)
    else
    if DF == 'F' then
    	return string.format ('tháng %02d', thang)
    else
    if DF == 'YY' then
    	return string.format ('%s', canchinam)
    else
    if DF == 'MM' then
    	return string.format ('%s', canchithang)
    else
    if DF == 'DD' then
    	return string.format ('%s', canchingay)
    else
    if DF == 'DDMMYY' then
    	return string.format ('ngày %s tháng %s năm %s', canchingay, canchithang, canchinam)
    end end end end end end end end end end end end end end end end
end
return p