pwm / datetime-period
用于处理时间间隔的datetime period类型的实现
Requires
- php: >=7.1.0
Requires (Dev)
- infection/infection: ^0.8.2
- phpstan/phpstan: ^0.7.0
- phpunit/phpunit: ^6.1
- squizlabs/php_codesniffer: ^3.0
This package is auto-updated.
Last update: 2024-09-29 02:02:28 UTC
README
用于处理时间间隔的datetime period类型的实现。该库包括由Allen的时间间隔代数定义的间隔关系全集。有关更多信息,请参阅“用法”和“工作原理”段落。
目录
需求
PHP 7.1+
安装
$ composer require pwm/datetime-period
用法
创建
$start = new DateTimeImmutable('2010-10-10T10:10:10+00:00'); $end = new DateTimeImmutable('2011-11-11T11:11:11+00:00'); $period = new DateTimePeriod($start, $end); // Start and end instants (see the definition of instant under "How it works") $start = $period->getStart(); // DateTimeImmutable('2010-10-10T10:10:10+00:00') $end = $period->getEnd(); // DateTimeImmutable('2011-11-11T11:11:11+00:00')
限制
// Throws TimeZoneMismatch exception new DateTimePeriod( new DateTimeImmutable('2017-10-10T10:10:10+02:00'), new DateTimeImmutable('2017-10-10T10:10:10-05:00') ); // Throws NegativeDateTimePeriod exception new DateTimePeriod( new DateTimeImmutable('+1 day'), new DateTimeImmutable('-1 day') );
两个时间段之间的全部关系集
$a = new DateTimePeriod(new DateTimeImmutable('...'), new DateTimeImmutable('...')); $b = new DateTimePeriod(new DateTimeImmutable('...'), new DateTimeImmutable('...')); // |--a--| // |--b--| $a->precedes($b); // |--a--| // |--b--| $a->meets($b); // |--a--| // |--b--| $a->overlaps($b); // |----a----| // |--b--| $a->finishedBy($b); // |----a----| // |--b--| $a->contains($b); // |--a--| // |----b----| $a->starts($b); // |--a--| // |--b--| $a->equals($b); // |----a----| // |--b--| $a->startedBy($b); // |--a--| // |----b----| $a->during($b); // |--a--| // |----b----| $a->finishes($b); // |--a--| // |--b--| $a->overlappedBy($b); // |--a--| // |--b--| $a->metBy($b); // |--a--| // |--b--| $a->precededBy($b);
可扩展性
DateTimePeriod
对象本身是不可变的,这意味着一旦创建,就无法更改对象的状态,即其属性的值。然而,属性已被定义为protected
,以便在需要时可以在项目中继承该类型。
处理不同的粒度
以下两个时间段在按小时粒度的时间线上相交,但在更精细的按分钟粒度的时间线上不相交。
$aStart = '2017-01-01T12:12:09.829462+00:00'; $aEnd = '2017-01-01T14:23:34.534678+00:00'; $bStart = '2017-01-01T14:41:57.657388+00:00'; $bEnd = '2017-01-01T16:19:03.412832+00:00'; $hourGranule = 'Y-m-d\TH'; $a = new DateTimePeriod( DateTimeImmutable::createFromFormat($hourGranule, (new DateTimeImmutable($aStart))->format($hourGranule)), DateTimeImmutable::createFromFormat($hourGranule, (new DateTimeImmutable($aEnd))->format($hourGranule)) ); $b = new DateTimePeriod( DateTimeImmutable::createFromFormat($hourGranule, (new DateTimeImmutable($bStart))->format($hourGranule)), DateTimeImmutable::createFromFormat($hourGranule, (new DateTimeImmutable($bEnd))->format($hourGranule)) ); assert($a->meets($b) === true); // a meets b by the hour granule $minuteGranule = 'Y-m-d\TH:i'; $a = new DateTimePeriod( DateTimeImmutable::createFromFormat($minuteGranule, (new DateTimeImmutable($aStart))->format($minuteGranule)), DateTimeImmutable::createFromFormat($minuteGranule, (new DateTimeImmutable($aEnd))->format($minuteGranule)) ); $b = new DateTimePeriod( DateTimeImmutable::createFromFormat($minuteGranule, (new DateTimeImmutable($bStart))->format($minuteGranule)), DateTimeImmutable::createFromFormat($minuteGranule, (new DateTimeImmutable($bEnd))->format($minuteGranule)) ); assert($a->meets($b) === false); // a does not meet b by the minute granule
杂项
这是一个可以节省您时间的小助手方法列表,当您需要它们的特定功能时。
- 获取时间段内的天数
$start = new DateTimeImmutable('2016-01-01T11:11:11+00:00'); $end = new DateTimeImmutable('2018-01-01T11:11:11+00:00'); $period = new DateTimePeriod($start, $end); assert(366 + 365 === $period->getNumberOfDays()); // 2016 was a leap year
工作原理
为了能够讨论时间段,首先让我们就以下定义达成一致
定义
1. 瞬时
时间线上的一个锚点,即离散点。最基本的时态类型。一个“真实”的时间瞬时是理论上的,就像几何线上的一个点。然而,瞬时的表示总是具有一个持续时间,称为粒度。因此,我们可以使用不同粒度的各种离散时间线来表示相同的瞬时。例如,“2017-10-10”和“2017-10-10 10:10:10”可以表示相同的瞬时。
2. 间隔
时间线上的一个未锚定、有向的部分。未锚定意味着它没有与时间线的绝对关系。例如,“2周”或“1天,2小时和3分钟”。有向意味着可以说“-3天”是完全有效的。
3. 时间段
时间线上的一个锚定间隔。有几种可能的表示方式,最常见的是具有相同粒度的两个有序瞬时的对。根据表示方式,时间段的时间间隔可以在其开始和结束处都打开或关闭。一种常见的方式是使用闭开区间,即[start, end),这有助于简化计算。例如,时间段["2017-10-10", "2017-11-11")包括瞬时"2017-10-10",但不包括瞬时"2017-11-11"。
我们已经到达了时间段的定义。现在继续...
关系
在时间段上定义关系有些复杂,因为不存在全序。1983年,James F. Allen撰写了一篇论文,在其中他定义了13个联合穷尽且相互排斥的二进制关系,这意味着任何两个间隔都正好有一种关系。您可以在“用法”部分中看到上述13种关系中的每一种。这些关系及其操作形成了所谓的Allen的时间间隔代数。
复杂性
处理日历时间很困难,有很多特殊性。这反过来意味着处理派生实体,如DateTimePeriod
,也很复杂。
以下是一个涉及时区的示例
如果您尝试从2018-03-26T08:00:00
开始,到2019-03-26T08:00:00
结束,创建一个为期一年的DateTimePeriod
,对于时区Europe/London
,则会因为UTCOffsetMismatch
异常而失败。这是因为对于开始时刻,时区Europe/London
等于BST
(等于UTC+01:00
),而对于结束时刻,Europe/London
等于GMT
(等于UTC+00:00
)。这是因为夏令时在不同年份的日期不同。
如果您使用UTC偏移量创建上述时间段,即从2018-03-26T08:00:00+01:00
到2019-03-26T08:00:00+01:00
,则不会抛出UTCOffsetMismatch
异常,但是结束时刻将不是一个有效的Europe/London
日期时间,因此您需要从您的UTC+01:00
偏移量计算出正确的Europe/London
时间。
提供的DateTimePeriod::getUtcOffset()
函数可以通过将您的当前时区映射到其UTC偏移量来帮助解决这个问题。
测试
$ vendor/bin/phpunit
$ composer phpcs
$ composer phpstan
$ composer infection
变更日志
待办事项
- 为UTC偏移量/时区问题找到一个更用户友好的解决方案。