Python 中 Wikidata api 的日期比较
Comparison of dates from the Wikidata api in Python
我想对维基数据给出的日期进行日期比较 API。
起初我虽然使用Python的datetime模块,但遇到了两个问题:
- 维基数据在儒略历和公历中处理过去或未来数十亿年的日期,datetime 仅适用于 1 年到 9999 年之间的公历日期。
- 当精度为 (9) 年或更低时,月份和日期呈现为“00-00”,datetime.strptime 无法处理。
例如在这个关于巴黎的sample query中,这个日期可以转换为日期时间:
datetime.strptime("+1968-01-01T00:00:00Z","+%Y-%m-%dT%H:%M:%SZ")
datetime.datetime(1968, 1, 1, 0, 0)
这个不能:
datetime.strptime("+2012-00-00T00:00:00Z","+%Y-%m-%dT%H:%M:%SZ")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib/python3.5/_strptime.py", line 510, in _strptime_datetime
tt, fraction = _strptime(data_string, format)
File "/usr/lib/python3.5/_strptime.py", line 343, in _strptime
(data_string, format))
ValueError: time data '+2012-00-00T00:00:00Z' does not match format '+%Y-%m-%dT%H:%M:%SZ'
更不用说“-0300-00-00T00:00:00Z”(公元前 300 年)
我不能简单地用年份来比较,因为对于公元前发生的事情的项目,同一个负年份可能有几个日期。
我不太确定处理此问题的最佳方法。我可以使用另一个库吗?
tl;dr : datetime 不能处理那种事情,所以不要尝试。你有琴弦,保留它们并按原样对待它们。
您可以简单地将它们排序为字符串,前提是它们具有一致的长度(否则根据需要填充)和格式。这将允许对“extended”ISO8601:2004 时间戳进行排序(按照标准 00
不允许月份和日期)。
假设Python3,此代码:
import urllib.request,json
url = urllib.request.urlopen("https://www.wikidata.org/w/api.php?action=wbgetentities&format=json&ids=Q90&props=info%7Caliases%7Clabels%7Cdescriptions%7Cclaims%7Cdatatype%7Csitelinks%2Furls&languages=fr&languagefallback=1&formatversion=2")
data = json.loads(url.read().decode())
P6 = sorted(data['entities']['Q90']['claims']['P6'], key=lambda claim: claim['qualifiers']['P580'][0]['datavalue']['value']['time'])
for x in P6:
print(x['mainsnak']['datavalue']['value']['numeric-id'])
产生此结果集:
1685301
947901
656015
2596877
3131449
1986521
1685102
1684642
601266
677730
289303
959708
2105
1685859
256294
2851133
此外,您需要将列表分成两部分:
- 以
-
符号开头的项目
- 以
+
符号开头的项目
然后按月份-日期-时间升序对第一个列表进行排序,然后按字符串表示的年份的无符号整数值排序(因为 sort()
和 sorted()
是 guaranteed "stable" ),简单地对第二个进行排序,然后再次将它们连接起来。这将允许对 signed ISO8601 时间戳进行正确排序。
neg = [x for x in P6 if x['qualifiers']['P580'][0]['datavalue']['value']['time'].startswith('-') ]
pos = [x for x in P6 if x['qualifiers']['P580'][0]['datavalue']['value']['time'].startswith('+') ]
neg.sort(key=lambda claim: claim['qualifiers']['P580'][0]['datavalue']['value']['time'][5:])
neg.sort(key=lambda claim: claim['qualifiers']['P580'][0]['datavalue']['value']['time'][1:5])
pos.sort(key=lambda claim: claim['qualifiers']['P580'][0]['datavalue']['value']['time'])
P6sorted = neg+pos
至于填充,如果需要的话,使用 string.rjust()
就足够了(尽管您必须稍微改变排序以反映 "new" 时间戳的长度;string.zfill()
不是该工作的正确工具,因为您要更改的字符串不是数字,具有 'T'、'Z'、'-' 和 ':') :
maxlength = max( map( lambda claim: len( claim['qualifiers']['P580'][0]['datavalue']['value']['time'] ), P6 ) )
for claim in P6:
claim['qualifiers']['P580'][0]['datavalue']['value']['time'] = claim['qualifiers']['P580'][0]['datavalue']['value']['time'][0] + claim['qualifiers']['P580'][0]['datavalue']['value']['time'][1:].rjust(maxlength-1, "0");
neg = [x for x in P6 if x['qualifiers']['P580'][0]['datavalue']['value']['time'].startswith('-') ]
pos = [x for x in P6 if x['qualifiers']['P580'][0]['datavalue']['value']['time'].startswith('+') ]
neg.sort(key=lambda claim: claim['qualifiers']['P580'][0]['datavalue']['value']['time'][maxlength-16:])
neg.sort(key=lambda claim: claim['qualifiers']['P580'][0]['datavalue']['value']['time'][maxlength-22:maxlength-16], reverse=True)
pos.sort(key=lambda claim: claim['qualifiers']['P580'][0]['datavalue']['value']['time'])
P6sorted = neg+pos
for claim in P6sorted:
print([claim['mainsnak']['datavalue']['value']['id'],claim['qualifiers']['P580'][0]['datavalue']['value']['time']])
顺便说一句,为了便于阅读,您可能需要“Decorate-Sort-Undecorate”(执行 Schwartzian 变换)。
最后,如果您担心儒略历与公历的区别,则必须通过添加相应的天数将儒略日期转换为基于国家和年份的公历日期,然后应用上述方法。但请记住 Julian 日期 (YYYY)-(MM)-(DD) 早于 Gregorian 日期 "that seems one day ahead",所以真的不应该太担心。
我想对维基数据给出的日期进行日期比较 API。
起初我虽然使用Python的datetime模块,但遇到了两个问题:
- 维基数据在儒略历和公历中处理过去或未来数十亿年的日期,datetime 仅适用于 1 年到 9999 年之间的公历日期。
- 当精度为 (9) 年或更低时,月份和日期呈现为“00-00”,datetime.strptime 无法处理。
例如在这个关于巴黎的sample query中,这个日期可以转换为日期时间:
datetime.strptime("+1968-01-01T00:00:00Z","+%Y-%m-%dT%H:%M:%SZ")
datetime.datetime(1968, 1, 1, 0, 0)
这个不能:
datetime.strptime("+2012-00-00T00:00:00Z","+%Y-%m-%dT%H:%M:%SZ")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib/python3.5/_strptime.py", line 510, in _strptime_datetime
tt, fraction = _strptime(data_string, format)
File "/usr/lib/python3.5/_strptime.py", line 343, in _strptime
(data_string, format))
ValueError: time data '+2012-00-00T00:00:00Z' does not match format '+%Y-%m-%dT%H:%M:%SZ'
更不用说“-0300-00-00T00:00:00Z”(公元前 300 年)
我不能简单地用年份来比较,因为对于公元前发生的事情的项目,同一个负年份可能有几个日期。
我不太确定处理此问题的最佳方法。我可以使用另一个库吗?
tl;dr : datetime 不能处理那种事情,所以不要尝试。你有琴弦,保留它们并按原样对待它们。
您可以简单地将它们排序为字符串,前提是它们具有一致的长度(否则根据需要填充)和格式。这将允许对“extended”ISO8601:2004 时间戳进行排序(按照标准 00
不允许月份和日期)。
假设Python3,此代码:
import urllib.request,json
url = urllib.request.urlopen("https://www.wikidata.org/w/api.php?action=wbgetentities&format=json&ids=Q90&props=info%7Caliases%7Clabels%7Cdescriptions%7Cclaims%7Cdatatype%7Csitelinks%2Furls&languages=fr&languagefallback=1&formatversion=2")
data = json.loads(url.read().decode())
P6 = sorted(data['entities']['Q90']['claims']['P6'], key=lambda claim: claim['qualifiers']['P580'][0]['datavalue']['value']['time'])
for x in P6:
print(x['mainsnak']['datavalue']['value']['numeric-id'])
产生此结果集:
1685301
947901
656015
2596877
3131449
1986521
1685102
1684642
601266
677730
289303
959708
2105
1685859
256294
2851133
此外,您需要将列表分成两部分:
- 以
-
符号开头的项目 - 以
+
符号开头的项目
然后按月份-日期-时间升序对第一个列表进行排序,然后按字符串表示的年份的无符号整数值排序(因为 sort()
和 sorted()
是 guaranteed "stable" ),简单地对第二个进行排序,然后再次将它们连接起来。这将允许对 signed ISO8601 时间戳进行正确排序。
neg = [x for x in P6 if x['qualifiers']['P580'][0]['datavalue']['value']['time'].startswith('-') ]
pos = [x for x in P6 if x['qualifiers']['P580'][0]['datavalue']['value']['time'].startswith('+') ]
neg.sort(key=lambda claim: claim['qualifiers']['P580'][0]['datavalue']['value']['time'][5:])
neg.sort(key=lambda claim: claim['qualifiers']['P580'][0]['datavalue']['value']['time'][1:5])
pos.sort(key=lambda claim: claim['qualifiers']['P580'][0]['datavalue']['value']['time'])
P6sorted = neg+pos
至于填充,如果需要的话,使用 string.rjust()
就足够了(尽管您必须稍微改变排序以反映 "new" 时间戳的长度;string.zfill()
不是该工作的正确工具,因为您要更改的字符串不是数字,具有 'T'、'Z'、'-' 和 ':') :
maxlength = max( map( lambda claim: len( claim['qualifiers']['P580'][0]['datavalue']['value']['time'] ), P6 ) )
for claim in P6:
claim['qualifiers']['P580'][0]['datavalue']['value']['time'] = claim['qualifiers']['P580'][0]['datavalue']['value']['time'][0] + claim['qualifiers']['P580'][0]['datavalue']['value']['time'][1:].rjust(maxlength-1, "0");
neg = [x for x in P6 if x['qualifiers']['P580'][0]['datavalue']['value']['time'].startswith('-') ]
pos = [x for x in P6 if x['qualifiers']['P580'][0]['datavalue']['value']['time'].startswith('+') ]
neg.sort(key=lambda claim: claim['qualifiers']['P580'][0]['datavalue']['value']['time'][maxlength-16:])
neg.sort(key=lambda claim: claim['qualifiers']['P580'][0]['datavalue']['value']['time'][maxlength-22:maxlength-16], reverse=True)
pos.sort(key=lambda claim: claim['qualifiers']['P580'][0]['datavalue']['value']['time'])
P6sorted = neg+pos
for claim in P6sorted:
print([claim['mainsnak']['datavalue']['value']['id'],claim['qualifiers']['P580'][0]['datavalue']['value']['time']])
顺便说一句,为了便于阅读,您可能需要“Decorate-Sort-Undecorate”(执行 Schwartzian 变换)。
最后,如果您担心儒略历与公历的区别,则必须通过添加相应的天数将儒略日期转换为基于国家和年份的公历日期,然后应用上述方法。但请记住 Julian 日期 (YYYY)-(MM)-(DD) 早于 Gregorian 日期 "that seems one day ahead",所以真的不应该太担心。