从一个字符串日期中提取一年中的天数和儒略日

34

我在python中有一个字符串"2012.11.07",我需要将其转换为日期对象,然后获得一年中的第几天儒略日的整数值。这是否可能?


2
如果您只想获取一年中的日期编号,那么它并不被称为儒略日。https://en.wikipedia.org/wiki/Ordinal_date - Douglas G. Allen
10个回答

61

首先,您可以将它转换为一个 datetime.datetime 对象,如下所示:

>>> import datetime
>>> fmt = '%Y.%m.%d'
>>> s = '2012.11.07'
>>> dt = datetime.datetime.strptime(s, fmt)
>>> dt
datetime.datetime(2012, 11, 7, 0, 0)

然后你可以使用 datetime 上的方法来获取想要的内容...除了 datetime 没有你直接想要的函数,因此你需要将其转换为时间元组

>>> tt = dt.timetuple()
>>> tt.tm_yday
312

"儒略日"一词有几种不同的含义。如果您正在寻找2012312,则必须通过间接方式来实现,例如以下之一。

>>> int('%d%03d' % (tt.tm_year, tt.tm_yday))
2012312
>>> tt.tm_year * 1000 + tt.tm_yday
2012312

如果你正在寻找不同的含义,你应该可以从这里弄清楚。例如,如果你想知道“距公元前4713年1月1日以来的天数”这一含义,并且你有一个需要公历年份和年内天数的公式,那么你可以在上面得到这两个值进行计算。(如果你有一个需要公历年、月和日的公式,甚至都不需要进行timetuple转换。)如果你无法从中得出结论,请询问更多详细信息。

如果你没有公式,甚至可能已经有了,你最好的选择可能是在PyPI和ActiveState上寻找现成的模块。例如,快速搜索出了一个叫做jdcal的东西。我以前从未见过它,但是快速进行pip install jdcal并简要浏览了 readme 文件后,我就能够做到这一点:

>>> sum(jdcal.gcal2jd(dt.year, dt.month, dt.day))
2456238.5

这与美国海军儒略日转换器给出的结果相同。

如果你想要整数的儒略日,而不是分数儒略日,你需要决定向哪个方向舍入——向0、负无穷大、将中午舍入到下一天、向偶数日进行舍入等(请注意,儒略日的定义是从公元前4713年1月1日中午开始计算,因此2012年11月7日的一半是2456238,另一半是2456239,只有您知道您想要哪一个……)例如,向0进行舍入:

>>> int(sum(jdcal.gcal2jd(dt.year, dt.month, dt.day)))
2456238

1
@J.F.Sebastian:请阅读链接文档。“儒略日存储在两个浮点数(double)中。” jdcal 返回分数儒略日(如果仅给出日期,则假定为中午)。如果您想争论这些不应该被称为儒略日而是其他名称,请与模块的作者联系。 - abarnert
1
@J.F.Sebastian:好的,我已经加入了调用 “int” 的代码。但是重点不是为 OP 提供可随意使用的代码,也不是推荐特定的库;我使用 gdcal 作为示例,展示您可以在快速谷歌、PyPI 或 ActiveState 搜索中找到哪些类型的库;仍需由 OP 自己进行搜索、评估库,并选择他想要的那个。 - abarnert
最好假设给定日期的中午而不是前一天的午夜,即应该是39,如我的答案中所示,而不是38。您的观点是正确的,但如果可能的话,提供研究结果以避免强制人们重复可能更好。 - jfs
@J.F.Sebastian:研究结果字面上是“搜索PyPI Julian,点击第一行,浏览其自述文件,pip install jdcal,从自述文件中运行一个示例,验证其是否合理。”至于四舍五入……好的,我也会在答案中加上。 - abarnert
微小的挑剔:儒略日编号是整数。儒略日期本质上是分数:任何时刻的儒略日期(JD)都是前一天中午的儒略日编号加上自那时刻以来的一天分数。 - jfs
显示剩余5条评论

9
要获取儒略日,使用datetime.date.toordinal方法并添加固定偏移量。
儒略日是自公元前4713年1月1日中午12:00起在朱利安历中的天数,或者自公元前4714年11月24日中午12:00起在格里高利历中的天数。请注意,每个儒略日都从中午开始,而不是从午夜开始。 toordinal函数返回自公元前1年12月31日00:00以来在朱利安历中的天数(换句话说,公元1年1月1日00:00是第1天的开始,而不是第0天)。请注意,公元前1年直接跟随公元1年,因为零这个数字直到几个世纪后才被发明出来,所以不存在0年。
import datetime

datetime.date(1,1,1).toordinal()
# 1

只需将1721424.5加到toordinal的结果中即可得到儒略日。

另一个答案已经解释了如何解析你开始的字符串并将其转换为datetime.date对象。因此,您可以按以下方式找到儒略日:

import datetime

my_date = datetime.date(2012,11,7)   # time = 00:00:00
my_date.toordinal() + 1721424.5
# 2456238.5

4
为了简化abarnert答案的初始步骤:
from dateutil import parser
s = '2012.11.07'
dt = parser.parse(s)

然后按照abanert的答案继续操作。

这是哪个版本的Python?在我的2.7版本中没有tm_yday。 - Curtis Price
你是否忘记将其转换为timetuple?dt对象没有成员tm_yday,而从dt.timetuple()获取的time.struct_time对象具有该成员。 - K.-Michael Aye

3

我导入了datetime库,并使用strftime提取“儒略日”,年份,月份和日期...

import datetime as dt
my_date = dt.datetime.strptime('2012.11.07', '%Y.%m.%d')
jld_str = my_date.strftime('%j') # '312'
jld_int = int(jld_str)           #  312

3

对于快速计算,您可以仅使用stdlib datetime模块找到一年中的日期和儒略日数:

#!/usr/bin/env python3
from datetime import datetime, timedelta

DAY = timedelta(1)
JULIAN_EPOCH = datetime(2000, 1, 1, 12) # noon (the epoch name is unrelated)
J2000_JD = timedelta(2451545) # julian epoch in julian dates

dt = datetime.strptime("2012.11.07", "%Y.%m.%d") # get datetime object
day_of_year = (dt - datetime(dt.year, 1, 1)) // DAY + 1 # Jan the 1st is day 1
julian_day = (dt.replace(hour=12) - JULIAN_EPOCH + J2000_JD) // DAY
print(day_of_year, julian_day)
# 312 2456239

另一种获取day_of_year的方式:
import time

day_of_year = time.strptime("2012.11.07", "%Y.%m.%d").tm_yday

julian_day在上面的代码中是{{link1:"儒略日数,关联太阳日--指从公元前4713年1月1日格林威治平均正午开始连续计数的天数,儒略历(Julian proleptic calendar)将对应的儒略日数为0"}}。

"time模块文档使用术语“儒略日”有所不同:

Jn 儒略日n (1 <= n <= 365)。不计闰日,因此在所有年份中,2月28日是第59天,3月1日是第60天。
n 基于零的儒略日(0 <= n <= 365)。包括闰日,可以表示2月29日。

即基于0的儒略日在此处为day_of_year - 1。而第一个儒略日(Jn)是day_of_year - (calendar.isleap(dt.year) and day_of_year > 60)--从3月1日开始的日期将被移位以排除闰年。

还有一个相关术语:儒略日日期儒略日编号是一个整数。儒略日日期本质上是分数形式: "任何时刻的儒略日日期(JD)都是其前一天中午的儒略日编号加上自那时刻起的一天时间比例。"

通常,为避免自行处理边缘情况,请使用库计算儒略日,如@abarnert所建议的


3
这个功能(将日期字符串转换为儒略日期/时间)也存在于astropy模块中。请参考他们的文档以获取完整的详细信息。Astropy实现对于易于转换为儒略时间(而不仅仅是儒略日期)特别方便。
原问题的示例解决方案:
>>> import astropy.time
>>> import dateutil.parser

>>> dt = dateutil.parser.parse('2012.11.07')
>>> time = astropy.time.Time(dt)
>>> time.jd
2456238.5
>>> int(time.jd)
2456238

2
根据这篇文章,Fliegel和Van Flandern创造了一个未发布的一行公式来计算格里高利日期到儒略日:
JD = 367 * year - 7 * (year + (month + 9)/12)/4 - 3 * ((year + (month - 9)/7)/100 + 1)/4 + 275 * month/9 + day + 1721029

这是由加州帕萨迪纳喷气推进实验室的P·M·穆勒和R·N·温伯里于1900年3月之后的日期进行压缩的。
JD = 367 * year - 7 * (year + (month + 9)/12)/4 + 275 * month/9 + day + 1721014

这些公式有0.5的偏差,所以只需从公式中减去0.5即可。

使用一些字符串操作来实际提取数据,你就可以了。

>>> year, month, day = map(int,"2018.11.02".split("."))
>>> 367 * year - 7 * (year + (month + 9)/12)/4 + 275 * month/9 + day + 1721014 - 0.5
2458424.5

这些计算的一个重要点是,它们假定整数除法的Fortran语义,特别是文章中说:“在上述公式中,整数除法意味着将商截断为整数。”将此公式复制到Python中,就像您在示例中所做的那样,通常会不正确,因为Python对于整数除法向负无穷大四舍五入。例如,第一个公式将在1901年1月31日和1901年2月1日产生2415417,这显然是不正确的。 - reddish
第二个评论是第一个公式只适用于正数年份。公元前1年后是公元1年,并且不存在“0年”。对于公元前的年份(即年份< 0),您需要首先将年份加1。 - reddish

1

从上面的例子中,这是一个一行代码(非儒略日)的示例:

import datetime

doy = datetime.datetime.strptime('2014-01-01', '%Y-%m-%d').timetuple().tm_yday

0
虽然@FGol的答案提供了自包含的公式,但应注意这些公式仅在除法向零舍入(所谓的“截断除法”)时有效,这取决于编程语言。
例如,Python实现向负无穷舍入,这是非常不同的。要使用Python中给出的公式,您可以像这样操作:
def trunc_div(a, b):
    """Implement 'truncated division' in Python."""
    return (a // b) if a >= 0 else -(-a // b)

def formula1(year, month, day):
    """Convert Gregorian date to julian day number."""
    return 367 * year - trunc_div(7 * (year + trunc_div(month + 9, 12)), 4) - trunc_div(3 * (trunc_div(year + trunc_div(month - 9, 7), 100) + 1), 4) + trunc_div(275 * month, 9) + day + 1721029

def formula2(year, month, day):
    """Convert Gregorian date to julian day number (simplified); only valid for dates from March 1900 and beyond."""
    return 367 * year - trunc_div(7 * (year + trunc_div(month + 9, 12)), 4) + trunc_div(275 * month, 9) + day + 1721014

0
def JulianDate_to_date(y, jd):
    month = 1
    while jd - calendar.monthrange(y,month)[1] > 0 and month <= 12:
        jd = jd - calendar.monthrange(y,month)[1]
        month += 1
    date = datetime.date(y,month,jd).strftime("%m/%d/%Y")
    return date

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接