跳转到主要内容

国会图书馆EDTF(扩展日期时间格式)规范Python实现

项目描述

python-edtf

Python中EDTF格式的实现,包括解析自然语言日期文本的实用函数,以及将EDTF日期转换为相关的Python datestruct_time 对象。

有关最终草案规范的详细信息,请参阅http://www.loc.gov/standards/datetime/

此项目基于python-edtf,并开发以包含最新规范

安装

pip install edtf

使用

>>> from edtf import parse_edtf

# Parse an EDTF string to an EDTFObject
>>>
>>> e = parse_edtf("1979-08~") # approx August 1979
>>> e
UncertainOrApproximate: '1979-08~'

# normalised string representation (some different EDTF strings have identical meanings)
>>>
>>> unicode(e)
u'1979-08~'

# Derive Python date objects

# lower and upper bounds that strictly adhere to the given range
>>>
>>> e.lower_strict()[:3], e.upper_strict()[:3]
((1979, 8, 1), (1979, 8, 31))

# lower and upper bounds that are padded if there's indicated uncertainty
>>>
>>> e.lower_fuzzy()[:3], e.upper_fuzzy()[:3]
((1979, 7, 1), (1979, 9, 30))

# Date intervals
>>>
>>> interval = parse_edtf("1979-08~/..")
>>> interval
Level1Interval: '1979-08~/..'

# Intervals have lower and upper EDTF objects
>>>
>>> interval.lower, interval.upper
(UncertainOrApproximate: '1979-08~', UnspecifiedIntervalSection: '..')
>>> interval.lower.lower_strict()[:3], interval.lower.upper_strict()[:3]
((1979, 8, 1), (1979, 8, 31))
>>> interval.upper.upper_strict() # '..' is interpreted to mean open interval and is returning -/+ math.inf
math.inf

# Date collections
>>>
>>> coll = parse_edtf('{1667,1668, 1670..1672}')
>>> coll
MultipleDates: '{1667, 1668, 1670..1672}'
>>> coll.objects
(Date: '1667', Date: '1668', Consecutives: '1670..1672')

parse_edtf() 返回的对象是 edtf.parser.parser_classes.EDTFObject 子类的实例,具体取决于解析的日期类型。这些类是

# Level 0
Date
DateAndTime
Interval

# Level 1
UncertainOrApproximate
Unspecified
Level1Interval
UnspecifiedIntervalSection
LongYear
Season

# Level 2
PartialUncertainOrApproximate
PartialUnspecified
OneOfASet
MultipleDates
Level2Interval
Level2Season
ExponentialYear

所有这些都实现了 upper/lower_strict/fuzzy() 方法来推导 struct_time 对象,除了 UnspecifiedIntervalSection,它还可以返回 math.inf 值

*Interval 实例具有 upperlower 属性,它们本身也是 EDTFObject 实例。

OneOfASetMultipleDates 实例具有一个 objects 属性,它是该集合或列表中解析的所有EDTF日期的列表。

EDTF规范包含

库包括EDTF规范的0级、1级和2级的实现。

测试覆盖率包括规范特征表中给出的所有示例。

0级ISO 8601功能

  • 日期
>>> parse_edtf('1979-08') # August 1979
Date: '1979-08'
  • 日期和时间
>>> parse_edtf('2004-01-01T10:10:10+05:00')
DateAndTime: '2004-01-01T10:10:10+05:00'
  • 区间(开始/结束)
>>> parse_edtf('1979-08-28/1979-09-25') # From August 28 to September 25 1979
Interval: '1979-08-28/1979-09-25'

1级扩展

  • 不确定/近似日期
>>> parse_edtf('1979-08-28~') # Approximately August 28th 1979
UncertainOrApproximate: '1979-08-28~'
  • 未指定日期
>>> parse_edtf('1979-08-XX') # An unknown day in August 1979
Unspecified: '1979-08-XX'
>>> parse_edtf('1979-XX') # Some month in 1979
Unspecified: '1979-XX'
  • 扩展区间
>>> parse_edtf('1984-06-02?/2004-08-08~')
Level1Interval: '1984-06-02?/2004-08-08~'
  • 超过四位数的年份
>>> parse_edtf('Y-12000') # 12000 years BCE
LongYear: 'Y-12000'
  • 季节
>>> parse_edtf('1979-22') # Summer 1979
Season: '1979-22'

二级扩展

  • 部分不确定/近似
>>> parse_edtf('2004-06~-11') # year certain, month/day approximate.
PartialUncertainOrApproximate: '2004-06~-11'
  • 部分未指定
>>> parse_edtf('1979-XX-28') # The 28th day of an uncertain month in 1979
PartialUnspecified: '1979-XX-28'
  • 一组中的一员
>>> parse_edtf("[..1760-12-03,1762]")
OneOfASet: '[..1760-12-03, 1762]'
  • 多个日期
>>> parse_edtf('{1667,1668, 1670..1672}')
MultipleDates: '{1667, 1668, 1670..1672}'
  • 二级扩展区间
>>> parse_edtf('2004-06-~01/2004-06-~20')
Level2Interval: '2004-06-~01/2004-06-~20'
  • 需要超过4位数的年份 - 指数形式
>>> e = parse_edtf('Y-17E7')
ExponentialYear: 'Y-17E7'
>>> e.estimated()
-170000000
  • 有效数字
# '1950S2': some year between 1900 and 1999, estimated to be 1950
>>> d = parse_edtf('1950S2')
Date: '1950S2'
>>> d.lower_fuzzy()[:3]
(1900, 1, 1)
>>> d.upper_fuzzy()[:3]
(1999, 12, 31)
# 'Y171010000S3': some year between 171000000 and 171999999 estimated to be 171010000, with 3 significant digits.
>>> l = parse_edtf('Y171010000S3')
LongYear: 'Y171010000S3'
>>> l.estimated()
171010000
>>> l.lower_fuzzy()[:3]
(171000000, 1, 1)
>>> l.upper_fuzzy()[:3]
(171999999, 12, 31)
# 'Y3388E2S3': some year in exponential notation between 338000 and 338999, estimated to be 338800
>>> e = parse_edtf('Y3388E2S3')
ExponentialYear: 'Y3388E2S3S3'
>>> e.estimated()
338800
>>> e.lower_fuzzy()[:3]
(338000, 1, 1)
>>> e.upper_fuzzy()[:3]
(338999, 12, 31)

自然语言表示

该库包含一个基本的英文自然语言解析器(它还不够智能,无法处理诸如“复活节”或其它语言的情况)

>>> from edtf import text_to_edtf
>>> text_to_edtf("circa August 1979")
'1979-08~'

请注意,结果是字符串,而不是 ETDFObject

该解析器可以解析如下字符串

'January 12, 1940' => '1940-01-12'
'90' => '1990' #implied century
'January 2008' => '2008-01'
'the year 1800' => '1800'
'10/7/2008' => '2008-10-07' # in a full-specced date, assume US ordering

# uncertain/approximate
'1860?' => '1860?'
'1862 (uncertain)' => '1862?'
'circa Feb 1812' => '1812-02~'
'c.1860' => '1860~' #with or without .
'ca1860' => '1860~'
'approx 1860' => '1860~'
'ca. 1860s' => '186X~'
'circa 1840s' => '184X~'
'ca. 1860s?' => '186X?~'
'c1800s?' => '180X?~' # with uncertainty indicators, use the decade

# unspecified parts
'January 12' => 'XXXX-01-12'
'January' => 'XXXX-01'
'7/2008' => '2008-07'
'month in 1872' => '1872-XX'
'day in January 1872' => '1872-01-XX'
'day in 1872' => '1872-XX-XX'

#seasons
'Autumn 1872' => '1872-23'
'Fall 1872' => '1872-23'

# before/after
'earlier than 1928' => '/1928'
'later than 1928' => '1928/'
'before January 1928' => '/1928-01'
'after about the 1920s' => '192X~/'

#centuries
'1st century' => '00XX'
'10c' => '09XX'
'19th century?' => '18XX?'

# just showing off now...
'a day in about Spring 1849?' => '1849-21-XX?~'

# simple ranges, which aren't as accurate as they could be. The parser is
limited to only picking the first year range it finds.
'1851-1852' => '1851/1852'
'1851-1852; printed 1853-1854' => '1851/1852'
'1851-52' => '1851/1852'
'1856-ca. 1865' => '1856/1865~'
'1860s-1870s' => '186X/187X'
'1920s - early 1930s' => '192X/193X'
'1938, printed 1940s-1950s' => '1938'

从EDTF表示生成自然文本是未来的目标。

自然文本解析器在解释模糊日期时做出哪些假设?

  • "1800s" 可能是世纪或十年。如果给定的日期不确定或近似,则使用十年解释。如果日期确定且精确,则使用世纪解释。

  • 如果世纪未指定(EDTF(natural_text="the '70s")),如果年份大于当前年份,则假定世纪为"19",否则假定世纪为当前世纪。

  • 默认情况下,自然语言假设为美国日期顺序(mm/dd/yyyy)。要更改此设置,请将设置中的 DAY_FIRST 设置为 True。

  • 如果自然语言用 '/' 将日期分组,则它被解释为 "或" 而不是 "和"。生成的EDTF文本是一个由 [] ("其中一个日期") 括起来的列表,而不是由 {} ("所有这些日期") 括起来的。

在Python日期之间转换

由于EDTF日期通常是区域性的,并且通常是模糊的,因此我们需要根据具体情况使用几个不同的Python日期。通常,Python日期用于排序和过滤,并不会直接显示给用户。

struct_time 日期表示

由于Python的 datetime 模块不支持公元1年至9999年之外的日期,我们默认返回 time.struct_time 对象,而不是您可能期望的 datetime.datedatetime.datetime 对象。

struct_time 表示更难处理,但可以按原样排序,这是主要用例,并且可以相对容易地转换为 datedatetime 对象(假设年份在公元1至9999年之间)或转换为更灵活的库(如 astropy.time)中的日期对象,对于超出这些范围的年份。

如果您确定您正在处理Python的 datetime 模块支持的日期范围,您可以使用 edtf.struct_time_to_dateedtf.struct_time_to_datetime 函数获取这些更方便的对象。

[!NOTE] 此库在切换到 struct_time 之前默认通过方法返回 datedatetime 对象。请参阅问题 https://github.com/ixc/python-edtf/issues/26

lower_strictupper_strict

这些日期表示日期范围内最早和最晚的 严格 日期,忽略不确定性或近似性。可以这样考虑:"如果你必须选择一个日期进行排序,那会是什么?"。

在升序排序(最近的最先)中,按 lower_strict 排序以获得自然排序顺序。在降序排序(最近的最先)中,按 upper_strict 排序。

>>> e = parse_edtf('1912-04~')

>>> e.lower_strict()  # Returns struct_time
>>> time.struct_time(tm_year=1912, tm_mon=4, tm_mday=1, tm_hour=0, tm_min=0, tm_sec=0, tm_wday=0, tm_yday=0, tm_isdst=-1)

>>> e.lower_strict()[:3]  # Show only interesting parts of struct_time
(1912, 4, 01)

>>> from edtf import struct_time_to_date
>>> struct_time_to_date(e.lower_strict())  # Convert to date
datetime.date(1912, 4, 01)

>>> e.upper_strict()[:3]
(1912, 4, 30)

>>> struct_time_to_date(e.upper_strict())
datetime.date(1912, 4, 30)

lower_fuzzyupper_fuzzy

这些日期表示日期范围内可能的最早和最晚的 可能 日期,对“可能”有一个相当任意的定义。

这些值对于过滤结果很有用,即测试哪些EDTF日期可能落入或重叠在所需日期范围内。

模糊日期是从严格日期推导出来的,加上或减去一个与日期精确度相关的填充量。对于近似或不确定的日期,我们(任意地)将显然范围增加100%的不确定时间尺度,或者在季节的情况下增加12周。也就是说,如果日期在月份尺度上是近似的,则填充一个月。如果它在年份尺度上是近似的,则填充一年。

>>> e = parse_edtf('1912-04~')
>>> e.lower_fuzzy()[:3]  # padding is 100% of a month
(1912, 3, 1)
>>> e.upper_fuzzy()[:3]
(1912, 5, 30)

>>> e = parse_edtf('1912~')
>>> e.lower_fuzzy()[:3]  # padding is 100% of a year
(1911, 1, 1)
>>> e.upper_fuzzy()[:3]
(1913, 12, 31)

可以将不确定或近似日期解释为“加减[精确度等级]”。

如果日期既不确定又近似,则应用两次填充,即得到100% * 2的填充,或者“加减两个[精确度等级]”。

资格属性

EDTF对象支持提供对象资格概述的属性

  • .is_uncertain (?)
  • .is_approximate (~)
  • .is_uncertain_and_approximate (%)

这些属性表示日期对象中的任何部分是否不确定、近似或不确定且近似。对于范围,如果范围的任何部分(下限或上限)被这样资格化,则属性为真。一个日期不一定是既不确定又近似的,如果它单独都是不确定和近似的 - 它必须有“%”修饰符才能被视为不确定且近似。

>>> parse_edtf("2006-06-11")
Date: '2006-06-11'
>>> parse_edtf("2006-06-11").is_uncertain
False
>>> parse_edtf("2006-06-11").is_approximate
False

>>> parse_edtf("1984?")
UncertainOrApproximate: '1984?'
>>> parse_edtf("1984?").is_approximate
False
>>> parse_edtf("1984?").is_uncertain
True
>>> parse_edtf("1984?").is_uncertain_and_approximate
False

>>> parse_edtf("1984%").is_uncertain
False
>>> parse_edtf("1984%").is_uncertain_and_approximate
True

>>> parse_edtf("1984~/2004-06")
Level1Interval: '1984~/2004-06'
>>> parse_edtf("1984~/2004-06").is_approximate
True
>>> parse_edtf("1984~/2004-06").is_uncertain
False

>>> parse_edtf("2004?-~06-~04")
PartialUncertainOrApproximate: '2004?-~06-~04'
>>> parse_edtf("2004?-~06-~04").is_approximate
True
>>> parse_edtf("2004?-~06-~04").is_uncertain
True
>>> parse_edtf("2004?-~06-~04").is_uncertain_and_approximate
False

季节

[!重要] 默认情况下,季节被解释为北半球。要更改这一点,请覆盖appsettings.py中的月份映射。

比较

如果两个EDTF日期的unicode()表示相同,则认为它们相等。如果EDTF日期的lower_strict值更晚,则认为它比另一个日期大。

Django ORM字段

edtf.fields.EDTFField实现了一个简单的Django字段,用于在数据库中存储EDTF对象。

要存储模型上的自然语言值,定义另一个字段,并设置EDTFFieldnatural_text_field参数。

当您的模型保存时,natural_text_field值将解析以设置date_edtf值,并且底层的EDTF对象将设置模型上的_earliest_latest字段为表示儒略日的浮点值。

[!警告] 儒略日期数值的转换和转换可能不准确,尤其是对于公元前数千年的古老日期。理想情况下,应该仅在不需要完全精确的情况下使用儒略日期值进行范围和排序操作。它们不应该被用于最终存储或用于往返转换后的显示。

示例用法

from django.db import models
from edtf.fields import EDTFField

class MyModel(models.Model):
        date_display = models.CharField(
        "Date of creation (display)",
        blank=True,
        max_length=255,
        )
        date_edtf = EDTFField(
        "Date of creation (EDTF)",
        natural_text_field='date_display',
        lower_fuzzy_field='date_earliest',
        upper_fuzzy_field='date_latest',
        lower_strict_field='date_sort_ascending',
        upper_strict_field='date_sort_descending',
        blank=True,
        null=True,
        )
        # use for filtering
        date_earliest = models.FloatField(blank=True, null=True)
        date_latest = models.FloatField(blank=True, null=True)
        # use for sorting
        date_sort_ascending = models.FloatField(blank=True, null=True)
        date_sort_descending = models.FloatField(blank=True, null=True)

由于EDTFField_earliest_latest字段值是自动设置的,您可能希望将它们设置为只读,或在您的模型管理器中不可见。

为了开发

设置

  • 克隆存储库:git clone https://github.com/ixc/python-edtf.git
  • 设置虚拟环境:python3 -m venv venv
  • 安装依赖项:pip install -r dev-requirements.txt
  • 安装precommit钩子:pre-commit install

运行测试

  • python-edtf,运行单元测试:pytest
  • python-edtf,运行pytest -m benchmark来运行基准测试(发布在此
  • python-edtf/edtf_django_tests,运行集成测试:python manage.py test edtf_integration
  • 要本地运行CI,请使用act,例如act pull_requestact --pull=false --container-architecture linux/amd64。某些步骤可能需要GitHub PAT:act pull_request --container-architecture linux/amd64 --pull=false -s GITHUB_TOKEN=<your PAT>

代码审查和格式化

  • 检查代码审查:ruff check --output-format=github --config pyproject.toml
  • 检查格式:ruff format --check --config pyproject.toml
  • 修复格式: ruff format --config pyproject.toml
  • 如果已安装,则也会作为预提交钩子运行代码检查和格式化检查并尝试修复。

覆盖率与基准测试

覆盖率报告会生成并添加到提交的注释中,同时也在操作日志中可见。基准测试在拉取请求中运行,并在这里发布,同时也在操作日志中可见。

项目详情


下载文件

下载适合您平台的应用文件。如果您不确定该选择哪个,请了解有关安装包的更多信息。

源分布

edtf-5.0.0.tar.gz (45.6 kB 查看哈希值)

上传时间

构建分布

edtf-5.0.0-py3-none-any.whl (40.3 kB 查看哈希值)

上传时间 Python 3

由以下组织支持