天天看點

儒略日與公曆轉換完整代碼下載下傳(C++/Python)公曆轉儒略日儒略日轉公曆時間轉換

Table of Contents

完整代碼下載下傳(C++/Python)

公曆轉儒略日

儒略曆轉儒略日

格裡高利曆轉儒略日

補充說明

簡化儒略日

儒略日計算的python執行個體

儒略日轉公曆

計算分析

已知儒略日求公曆的python執行個體

時間轉換

24小時制

12時辰制

地方時

完整代碼下載下傳(C++/Python)

https://download.csdn.net/download/weixin_42763614/14917365

提供兩種語言的實作。代碼中增加對日期格式的輸入輸出處理。

公曆轉儒略日

《Astronomical Algorithms》中給出了簡便的計算公式。[注2]

儒略日與公曆轉換完整代碼下載下傳(C++/Python)公曆轉儒略日儒略日轉公曆時間轉換

書中并未給出公式的含義,這裡做一個分析。

儒略曆轉儒略日

若求Y年M月D日對應的儒略日,即分别對(Y-曆元年)年(M-1)月D日進行積日。

年:由于公元紀年法沒有0年,為友善計算,天文學中以0表示公元前1年,則公元前4713年表示為-4712。對儒略曆而言,以365.25天為歲長,每年閏餘0.25天,4年閏餘積1天則需置閏。故儒略曆每4年一閏。利用取整運算,可以在一個置閏周期中,對于365.25*(Y-x),每4年前3年向下取整,第四年進位。注意Y可以是任意一年,x用于将年份調節為任一置閏周期的起點。計算Y年的積日,即将從-4712年到Y年的積年乘以一回歸年的長度。

floor((Y+4712)*365.25)    [注3]

月:1.公曆設定大小月的基本思路是每月大小相間隔。相鄰兩月必為61天,如此一年共366天。平均一月為30.5天。故利用取整運算,對于每兩個月的周期規律可以使用int(30.5),從小月開始,為小月時取其整數,為大月時則會進位。這種情況下,M月(積月為M-1)的積日即為int(30.5*(M-1))。而公曆以單數月為31天,雙數月為30天。即歲首(M=1)時為大月,可令上式M=M+1,将該式調節到從大月起算,再減去多加的一月30,則積日為int(30.5*M)-30

2. 公曆一年實際隻有365日,需在某月中減去一天,而公曆設在二月。這時,3~12月份仍然是有規律的(大小月相隔),1月有31天,2月有29天。如此,可假設以3月份作為歲首,2月為年尾。則在2月減去的一天可了解為上年歲末(即今年歲首前)少一天。并以13、14月表示上一年的1、2月(即一年隻有3~14月)。則每年仍然是單數月31天,雙數月為30天,且歲首(M=3)仍為大月,則僅需令上式再減1(2月少一天)。計算M月的積日,則有:int(30.5*M)-31(M>2)

該式隻适用于M>2的情況,是以當為1月或2月時,可設為13、14月,即M=M+12,如此則多算一年,即令Y=Y-1。

一年的狀況(單數月31天,雙數月30天)

3月     4月    5月    6月    7月    8月    9月   10月  11月  12月   1月   2月

31天  30天  31天  30天  31天  30天  31天  30天  31天  30天  31天  30天-1

3. 對于公曆,又将8月增加一天成為大月,以後9~12月則依次改變大小月順序,而二月再減去一日成為28天。

一年的狀況(每5個月循環)

3月     4月    5月    6月    7月    8月    9月   10月  11月  12月   1月   2月

31天  30天  31天  30天  31天  31天  30天  31天  30天  31天  31天  30天-2

如此,7、8成為連大月,12、13也是連大月。其中8月與歲首相差5月,13月與8月也相差5月。此時的每月的大小順序仍然存在某種規律,即每連續5月,大小月按大小大小大循環排序。亦即連續5月的積月為153天(3大2小),平均每月為30.6天。則M月的積日為int(30.6*M)-31,由于該式是以3月為歲首,需調整月份,即令M=M+1,再減去多算的一月30,并需減去2月再減少的1天則有:

int(30.6*(M+1))-62 (M>2)

日:由于儒略日的曆元是以正午12時開始(記為0.0),而公曆一日之始以半夜12時開始(記為1.0),兩種相差0.5天,則對日的積日為實際日數-1.5

D - 1.5

将儒略曆轉換為儒略日,僅需将年月日的積日相加,有:

floor(365.25*(Y+4712))+int(30.6*(M+1))+D-63.5 (M>2)

當M<=2時:令Y = Y - 1,M = M + 12,可适用該公式

格裡高利曆轉儒略日

閏:儒略曆每400年百閏。而在1582年發現春分點與實際相差10日(歲差22日,而儒略曆回歸年長度較實際值為大,多計算了12天)。故以是年10月4日的次日實行新曆(格裡高利曆),曆元為1582年10月15日,有10日之差。新曆重新測定了回歸年長度并考慮了歲差,歲長改為365.2425,即每400年少3日,合400年97閏。故計算格裡高利曆的儒略日,還需在上式中減去不置閏(能被100整除,但不含能被400整除的年份)的日數,即為-floor((Y-1582)/100)+floor((Y-1582)/400)-10,為簡便算法,假設曆元為0年,則自0年至1582年少算了不置閏的日數共12天,需加上,則計算格裡曆的儒略日數需減去不置閏的日數:

-floor(Y/100)+floor(Y/400)+2

最終有格裡高利曆轉儒略日的公式:

floor(365.25*(Y+4712)) + int(30.6*(M+1)) + D - floor(Y/100)+floor(Y/400)+2 - 63.5  (M>2)

當M<=2時:令Y = Y - 1,M = M + 12,可适用該公式

補充說明

(1)上式的floor()函數表示向下取整,而書中使用的是的int()函數(實際在作者寫作的年代它仍表示向下取整,詳見注3),在現代程式設計語言中它的意義是取整,對于負數運算,如int(-365.25)值為-365而非366。考慮到當計算-4712年3月1日之前的資料時,Y=Y-1即-4713年,對年的積日運算中出現對負數取整的情況。故将曆元推到前一個置閏年即-4716年以避免對負數的取整運算,此時多加了4年,需要在式尾中減去1461天。

(2)同樣考慮計算機對浮點數計算精度的問題,因為計算機對十進制的表示和計算是以二進制進行的,無法得到精确值。故以30.6001替代30.6以避免出現計算誤差。(原書的寫作時間為上世紀90年代,計算精度有限,現代計算機中使用雙精度浮點數,可以直接用30.6而不會出現計算誤差。)

對此二者做出修正後,則有:

JD = floor(365.25*(Y+4716)) + int(30.6001*(M+1)) + D + B - 1524.5  (M>2)

當M<=2時:令Y = Y - 1,M = M + 12,可适用該公式

對儒略曆(公元前1582年10月4日及以前),有B = 0

對格裡高利曆(公元前1582年10月15日及以後),有B = 2 - floor(Y/100)+floor(Y/400)

但對計算機而言,該式仍然不适用于計算-4716年3月1日前的儒略日數(但可以計算從儒略月曆元開始的任意一天的儒略日數)。若使用向下取整函數替換int(),則該式可使用于任意一天的儒略日計算,且不必做曆元調整。

此式即可避免出現計算機取整運算與實際不一緻的情況,進而避免因不同程式設計語言出現的計算誤差。

(3)wiki中提供了另一個公式,其實質與上式相同。[注4]

儒略日與公曆轉換完整代碼下載下傳(C++/Python)公曆轉儒略日儒略日轉公曆時間轉換

以3月為歲首,每5個月符合大小大小大的順序,合計153天,平均每月30.6天。而計算起點為大月,需加上0.4,取整後方得31。故有m月的積日為floor((153m+2)/5)。每年以365計,則需加上置閏年floor(y/4)(365y+floor(y/4)等價于floor(365.25y))。式中将曆元前推到原曆元上一個格裡曆置閏周期起點的年份-4800(公元前4801年1月1日)0時,則需在末尾減去(4800-4712)*365.25即32142天,還需再補上1月和2月合計59天,得32083日。對格裡曆還需補上自-4712年至1582年少置閏的年份,共48天,減去上歲差和回歸年過大引起的誤差共10天。

注意這裡是計算JDN的表達式,它與JD的關系是

JDN = floor(JD + 0.5)

儒略日數的計算公式還有很多,但思路類似,不再舉例說明。

簡化儒略日

為簡化儒略日的表示,國際天文聯合會決定行用簡化儒略日,并定義為

MJD = JD -2400000.5

根據儒略日轉公曆的計算函數,可得MJD的的起點為儒略日2400000.5,即公元1858年11月17日世界時0時。

儒略日計算的python執行個體

from datetime import *
import math

def ce2jd(Year,Month,D):
    if Month in [1,2]:
        M = Month + 12
        Y = Year - 1
    else:
        Y = Year
        M = Month
    B = 0
    if Y>1582 or (Y==1582 and M>10) or (Y==1582 and M==10 and D>=15):
        B = 2 - int(Y/100) + int(Y/400)  #公元1582年10月15日以後每400年減少3閏
    JD = math.floor(365.25*(Y+4716))+int(30.6*(M+1))+D+B-1524.5
    #JD = math.floor(365.25*(Y+4712))+int(30.6*(M+1))+D+B-63.5
    print("{}年{}月{}日的儒略日為:{:.5f}".format(gyjn(Year),Month,D,JD))
    if Y>1858 or (Y==1858 and M>11) or (Y==1858 and M==11 and D>=17):
        MJD = int(JD - 2400000.5)
        print("簡化儒略日為:{}".format(MJD))
    return JD

#示例:計算目前時間的儒略日
year = datetime.now().year
month = datetime.now().month
day = datetime.now().day
ce2jd(year,month,day)
           

程式使用了一個将天文計算年份表達為公元紀年法年份的函數:

def gyjn(year):
    if year > 1:
        ce = "公元" + str(year)
    elif year == 1:
        ce = "公元元"
    elif year <= 0:
        year -= 1
        ce = "公元前" + str(-year)
    return ce
           

該程式沒有對1582年10月5日至14日這失去的10日進行處理,如果輸入這10日,其計算結果與10月15日至24日相同。如果需要擷取使用者輸入,應給出相應的錯誤提示。

儒略日轉公曆

 儒略日轉公曆即将上述表達式作為方程,進行求解。

《Astronomical Algorithms》同一章節給出了求算方法。這裡分别說明每一個表達式的含義。

儒略日與公曆轉換完整代碼下載下傳(C++/Python)公曆轉儒略日儒略日轉公曆時間轉換

計算分析

1. 由于儒略日的曆元為正午12時,公曆曆元為半夜12時,為統一計算,将儒略曆曆元前推至0.5日。即為:JD = JD + 0.5

2. 其中整數部分為日數,小數部分為時刻,隻對整數部分進行運算。則令:

Z = floor(JD)

F = JD - Z 

Z即為所求日到-4712年1月1日0時的日數。

3. 由于儒略曆和格裡曆的歲長不同,需分别處理。即自1582年10月15日0時前适用儒略曆(歲長365.25),此後适用格裡曆(歲長365.2425)。為統一計算,可将格裡曆轉換為儒略曆,即假設自-4712年1月1日0時起一直使用的是儒略曆。則針對格裡曆相對儒略曆少置閏(400年3閏)的部分給予補上。對于使用取整公式計算置閏,隻能以一個400年置閏周期的起點為歲首,如1600年3月1日,根據儒略日計算公式,得該日的儒略日為2305507.5。

格裡曆除能被400整除的百年為36525日外,其餘每百年僅36524日,此時分母較大,故分子每百年應增加0.25日,300年計0.75日。前對JD取整後所得Z值,仍以12時為曆元,故僅需再增0.25日。

a = floor((Z - 2305507.25) / 36524.25)

再補上1582年10月4日到10月15日跳過的10天,即為自-4712年1月1日0時到所求日以儒略曆計算的總積日。

A = Z + 10 + a - floor(a/4) 

(注:書中a和A的表達式與此略有差異,是由于書中将曆元推至公元元年1月1日,則至1582年間多置閏了12次,去除跳過的10天,共多了2日,反映在置閏公式中,相當于多計算了200年,由于在置閏公式(floor(y/100)-floor(y/400))中,對200年間的置閏可能有2次或1次2種情況,宜推算到一次置閏周期即400年,則必然置閏3次,而多算了一次,則在表達式A中補足。即為調整"儒略曆和格裡曆的置閏誤差"以及"不識歲差和回歸年過大的誤差"再次将曆元從公元元年推至公元400年。這種情況思慮比較複雜,可不使用書中的表達式。)

同樣地,為避免對負數取整的情況,将曆元前推至-4716年3月1日0時,需補上相差的日數,合1524日。即有:

B = A + 1524

對所得的積日(儒略曆),除以歲長,即為積年(表達式C),加上曆元即為所求公曆年份。其中整數部分為年的積日(表達式D),小數部分為月與日的積日(表達式B-D)。

C = floor((B-122.1)/365.25)

D = floor(365.25*C)

将B-D除以每月平均日數30.6為積月(表達式E),其中整數部分為月數,小數部分為日數(day)。

最後調整歲首的情況可得month和year。

宜需考慮,day的結果小于0.5時,即在上月末日,此時亦可能導緻E值小于1,即在上年末月。需要分别判斷處理。但也可先求JD-1日,得到正常結果,在結果上加回減去的1日即可。

已知儒略日求公曆的python執行個體

def jd2ce(JD):
	JD = JD + 0.5  # 以BC4713年1月1日0時為曆元
	Z = math.floor(JD)
	F = JD - Z  # 日的小數部分
	if Z < 2299161:  # 儒略曆
		A = Z
	else:  # 格裡曆
		a = math.floor((Z - 2305507.25) / 36524.25)
		A = Z + 10 + a - math.floor(a/4)
	k = 0
	while True:
		B = A + 1524  # 以BC4717年3月1日0時為曆元
		C = math.floor((B-122.1)/365.25)  # 積年
		D = math.floor(365.25 * C)  # 積年的日數
		E = math.floor((B-D)/30.6)  # B-D為年内積日,E即月數
		day = B - D - math.floor(30.6*E) + F
		if day >= 1: break  # 否則即在上一月,可前置一日重新計算
		A -= 1
		k += 1
	month = E - 1 if E < 14 else E - 13
	year = C - 4716 if month > 2 else C - 4715
	day += k
	if int(day) == 0: day += 1
	ce = gyjn(year)
	print("儒略日{}對應的公曆日期為{}年{}月{}日".format(JD-0.5,ce,month,day),'\n')
	return year, month, day

# 計算示例
jd2ce(2400000.5)    # print:1858年11月17.0日(簡化儒略月曆元) 
jd2ce(2299160.5)    # print:1582年10月15.0日(格裡曆曆元)
jd2ce(2299160.5-1)  # print:1582年10月4.0日(儒略曆最後一日)
           

注意:傳回的year是天文計算年而不是公曆年,如-1000表示公元前1001年。也可以再利用上述公元紀年表達函數進行輸出。

測試以上兩個程式,可用以下代碼:

jd2ce(ce2jd(year, month, day))

對于任意的year,month,day,輸出值與輸入值相同,可以證明兩個程式的正确性,不必分别測試。

時間轉換

24小時制

以上計算獲得的day含有小數,即該日的具體時間。一分鐘60秒,一小時3600秒,一日86400秒。簡單的做法是,先将小數轉為秒數,整除3600即小時數,餘數再整除60即分鐘數。

但是,如果day的精度要求較高,就必須考慮每次加減乘除對不确定尾數的影響,

def day2hms(day):  # 12h起算的日數轉時分秒
    day += 0.5
    d = day - math.floor(day)  # 取出一日的小數部分
    h = int(d * 24)
    m = int(round((d * 24 - h) * 60, 4))
    if m == 60:
        m = 0
        h += 1
    s = d * 86400 - h * 3600 - m * 60
    if abs(s) < 0.001: s = 0
    return h, m, round(s, 2)
           

測試兩個特殊日期,-1095.499999999與-1095.500000001

使用jd2ce()和day2hms()分别計算年月日和時分秒,如果使用簡單方法,結果分别是-4715年1月1日0時0分0秒和-4716年12月31日0時0分0秒,顯然二者相差應該不足1秒。

處理後的程式方可得到正确結果。

如果傳回的h是24,表示實際已經是第二天,應該将h改為0,而日期多加一天。因為如開篇所言,曆法的本質是積日,曆法轉換,通常是日的轉換,而不去考慮時間。2000年1月1日24:00和2000年1月2日00:00易導緻不同的結果。比如在幹支紀日的曆法中,前者被寫作戊午日,而後者是己未日。

加一天後又必須考慮月底甚至年底跨月或跨年的情況,是以應該放到前面jd2ce()函數中處理,方法是在該函數中判斷循環退出的條件上一行(第20行前)增加如下代碼:

hms = day2hms(F)
if hms[0] == 24: # 當夜24點,實為第二日0點
	A += 1
	F = 0
	continue
           

 原理與前面先求JD-1日類似,這裡是求JD+1日。

12時辰制

中國古代将一天分為十二時辰,以地支命名,後又在每個時辰内各分初和正。但時辰的起點子初為23點并非一日的起點24/0點。同時,除梁實行96刻制及清實行120刻制外,一般使用的是百刻制,并不能被12整除。故日轉為時辰時,需将一日的起點轉為23點,且不能直接在時辰内分刻。而需從日數轉為刻數,再減去已有時辰的刻數。

由于轉換為時辰時起點并非跨日的24點,故而無需考慮臨界情況。

dizhi = ["子","醜","寅","卯","辰","巳","午","未","申","酉","戌","亥"]
ke = ['初', '一', '二', '三', '四']

def day2sk(JD):  # 0h起算的日數轉古時刻(百刻制)
	d = JD - math.floor(JD)  # 取出一日的小數部分(<0.0000001超過輸出位數)
	chen = round(d * 12 + 0.5, 14)  # 時辰從上一日23時起
	chen_h = int(chen)
	chen_k = round(d * 100 - int(d * 12) * 100 / 12, 14)  # 該時辰内的刻數
	if chen_k < 100 / 24:
		shike = dizhi[chen_h % 12] + '正' + ke[int(chen_k)] + '刻'
	else:
		chen_k -= 100 / 24
		shike = dizhi[chen_h % 12] + '初' + ke[int(chen_k)] + '刻'
	return shike
           

地方時

對于天文計算,其結果一般以UT為标準。如果用于中國曆法的計算,需要轉換為UT+8h。

亦即東八區某一日的開始即半夜0h,實際在格林尼治地方時上一日的16h。

而如果用于不同曆法的轉換,需注意兩個曆法之間的地方性。

  • 注1:曆元即曆法計算的起點。通過修改曆元以簡便計算是曆法中的基本思想,也是本文計算的重要方法。
  • 注2:Jean Meeus《Astronomical Algorithms》2nd,p59-66。
  • 注3:計算機中,常用floor()表示向下取整函數,int()表示取整函數。在函數内參數小于0的情況下,int()無法代替floor()使用。如對于-1.5運用兩個函數進行計算,有: floor(-1.5) = -2, int(-1.5) = -1

       書中使用的是int()函數,因為該書寫作較早,使用的是BASIC語言,不區分int()和floor(),直接以int()作為向下取整函數。如果用現在常見的程式設計語言編寫,仍使用該函數,易導緻公式不适用于計算曆元(-4716年)前的日期。故本文在公式中改用了floor()函數,這在常見程式設計語言中自帶的标準庫math中都含有。

  • 注4:https://zh.wikipedia.org/wiki/%E5%84%92%E7%95%A5%E6%97%A5
  • 注5:幾個類似術語辨析:
  1. Julian Day:儒略日,天文中用于連續紀日的曆法,曆元為公元前4713年1月1日12時。
  2. Julian Date(JD):儒略日,特指某時的瞬時時刻,如12:00:00可能為00秒-01秒之間或00秒這一時刻,此處特指後者。常轉為日的小數表示。小數部分表示自某日12時至次日12時過去的日數。
  3. Julian Day Number(JDN):儒略日數。一定是整數,它表示某日距JD曆元日相差的日數部分。可以了解為曆元即公元前4713年1月1日0時。二者關系為JDN = floor(JD+0.5)
  4. Julian Calendar:儒略曆,它與儒略日實際沒有直接關系。曆元為公元前45年1月1日夜半12時,歲長為365.25日,平年365日,每4年置閏1日。儒略曆施行早期,出現過多次誤閏。但現代在對于格裡曆曆元(公元1582年10月4日)前的公曆,适用儒略曆的一般規則,即既不依照曆史誤閏,也适用于表示儒略曆曆元前的日期。而部分國家或地區在儒略曆廢除後仍然使用了一段時間的儒略曆。
  5. Gregorian Calendar:格裡高利曆,即現行公曆。它在儒略曆的基礎上修正了回歸年長度,曆元為公元1582年10月15日夜半12時,歲長為365.2425日。平年365日,每4年置閏1日,每400年少閏3日。格裡曆制定時,儒略曆較格裡曆短10日,此10日被跳過,故公曆中1582年10月4日的次日為10月15日。至今年(2018年),已短13日。