blumfontein/php-business-time

Carbon 日期的商务时间/工作日扩展

v3.0.0 2021-11-30 12:44 UTC

This package is auto-updated.

Last update: 2024-09-29 05:57:03 UTC


README

Build Status Coverage Status Codacy Badge Scrutinizer Code Quality StyleCI License: MIT

PHP中的“商务时间”逻辑(又称“工作小时”、“工作日”等)。例如,这可以用于计算运输日期。

此库为Carbon日期时间库中的Carbon类提供扩展。

虽然Carbon已经具有diffInWeekendDays()等方法,但此扩展允许您更精确和灵活地处理商务时间。它可以考虑来自WebCal.fi的公众假期,以及您可以直接指定或通过约束匹配指定的自定义时间。

此库的官方音乐视频

TypeScript中的BusinessTime

内容

安装

通过Composer安装

composer require hughgrigg/php-business-time

使用方法

此包中的BusinessTime类扩展了Carbon。这意味着您可以使用所有来自Carbon和本地DateTime的方法,以及此处描述的方法。

工作日

您可能最常处理工作日。

添加或减去工作日

您可以从给定的起始日期添加或减去工作日

$friday = new BusinessTime\BusinessTime('Friday 10am');
$nextBusinessDay = $friday->addBusinessDay();
// = Monday 10am
$threeBusinessDays = $friday->addBusinessDays(3);
// = Wednesday 10am
$monday = new BusinessTime\BusinessTime('Monday 10am');
$previousBusinessDay = $now->subBusinessDay();
// = Friday 10am
$threeBusinessDaysAgo = $now->subBusinessDays(3);
// = Wednesday 10am

工作日差异

除了添加或减去工作日,您还可以计算两个给定日期之间的工作日数量。

$now = BusinessTime\BusinessTime::now();
$nextWeek = $now->addWeek(); // a full 7-day week.
$diff = $now->diffInBusinessDays($nextWeek);
// = 5

完整工作日与部分工作日

上面的例子处理的是完整工作日。您也可以将其描述为整数天。这意味着任何一天的小数部分都不被视为工作日,也不会被计算。

例如,如果我们询问从上午10点的星期五到上午10点的星期六之间有多少个工作日,答案是零

$fridayTenAm = new BusinessTime\BusinessTime('Friday 10am');
$saturdayTenAm = $fridayTenAm->addDay(); // Add a full day.
$fridayTenAm->diffInBusinessDays($saturdayTenAm);
// = 0

如果您期望星期五的工作时间被包括在内,这可能会让您感到惊讶。结果为零的原因是因为在那个时间段内没有通过完整工作日;即使是大部分工作日也不足以被计算。

如果您确实想考虑部分天,您可以使用等效的部分方法来获取浮点值。

$fridayTenAm = new BusinessTime\BusinessTime('Friday 10am');
$fridayTenAm->diffInPartialBusinessDays('Saturday 10am');
// = 0.875

这些被分开是因为通常人们不想处理分数工作时间的概念:要么是一个工作日已经过去,要么没有。这些partial方法允许您在需要时访问浮点数。

工作日长度

要计算部分工作日,我们需要知道一个工作日的总时长。例如,09:00到17:00可能是如果这些是工作时间的话100%的工作日,但如果工作时间是09:00到19:00,则仅占80%的工作日。

默认情况下,BusinessTime将工作日视为8小时长(09:00到17:00)。尽管如此,您可以根据需要调整这一点。

配置此设置的最简单方法是直接设置工作日的长度

$businessTime = new BusinessTime\BusinessTime();
$businessTime->setLengthOfBusinessDay(BusinessTime\Interval::hours(6));

如果您有复杂的工作时间限制(见下文),让BusinessTime为您计算工作日长度可能会有所帮助。您可以通过传递代表您标准工作日的DateTime对象到determineLengthOfBusinessDay()方法来实现。然后,BusinessTime将根据这些限制计算工作日的长度。

$businessTime = new BusinessTime\BusinessTime();
$businessTime->determineLengthOfBusinessDay(new DateTime('Monday'));

工作小时

您还可以按小时进行工作时间计算

$now = new BusinessTime\BusinessTime();
$now->addBusinessHour();
$now->addBusinessHours(3);
$now = new BusinessTime\BusinessTime();
$now->diffInBusinessHours();
$now->diffInPartialBusinessHours();

默认情况下,一天是包含的最大时间单位,因为人们和组织对更大时间单位的理解不同。没有内置方法来处理这些,可以避免假设并强制显式性,例如使用$now->addBusinessDays(30)

同样,默认情况下不包括小于小时的单位,因为对于大多数用例,“工作分钟”的概念是值得怀疑的。如果确实需要,您可以乘以60来计算分钟。请注意,因为默认精度为每小时,您可能需要调整精度到例如15分钟以获得准确的计算(请参阅关于精度和性能的说明)。

描述工作时间

在某些情况下,为工作时间和非工作时间提供有意义的描述是有用的。例如,您可能想告诉客户,由于周末在中间,您将在下周交付他们的订单。

您可以使用BusinessTimePeriod类来完成此操作。您可以通过以下方式创建一个带有开始和结束时间的实例

$start = new BusinessTime\BusinessTime('today');
$end = $start->addBusinessDays(3);
$timePeriod = new BusinessTime\BusinessTimePeriod($start, $end);

然后,您可以使用时间段的businessDays()nonBusinessDays()方法来获取该信息。例如

$businessDays = $timePeriod->businessDays();
$nonBusinessDays = $timePeriod->nonBusinessDays();

这将返回每个非工作日的一个BusinessTime对象的数组,可以告诉您它们的名称

$nonBusinessDays[0]->businessName();
// = e.g. "the weekend"

您获得的间隔和描述取决于使用了哪些工作时间限制。

您还可以要求BusinessTimePeriod提供其业务和非业务子时间段,例如

$start = new BusinessTime\BusinessTime('today');
$end = new BusinessTime\BusinessTime('tomorrow');
$timePeriod = new BusinessTime\BusinessTimePeriod($start, $end);

$businessPeriods = $timePeriod->businessPeriods();
// = array of BusinessTimePeriod instances for each period of business time.
$nonBusinessPeriods = $timePeriod->nonBusinessPeriods();
// = array of BusinessTimePeriod instances for each period of non-business time.

这使您可以看到组成整个时间段的业务时间。您可以使用businessName()方法询问每个子时间段其与业务相关的名称。

工作日开始和结束

您可以根据工作时间限制获取工作日开始或结束的时间,如下所示

$businessTime = new BusinessTime\BusinessTime();
$businessTime->startOfBusinessDay();
// = BusinessTime instance for e.g. 09:00
$businessTime->endOfBusinessDay();
// = BusinessTime instance for e.g. 17:00

确定工作时间

默认情况下,这个库将周一至周五,上午9点至下午5点视为工作时间。尽管如此,您可以根据需要对其进行配置。

工作时间约束

您可以通过以下方式在BusinessTime类上设置限制以确定工作时间

$businessTime = new BusinessTime\BusinessTime();
$businessTime->setBusinessTimeConstraints(
    new BusinessTime\Constraint\WeekDays(),
    new BusinessTime\Constraint\BetweenHoursOfDay(9, 17),
);

您可以传递所需的任何限制;所有这些限制都必须得到满足,才能将给定时间视为工作时间。

调用setBusinessTimeConstraints()会替换BusinessTime实例上的任何现有限制。

以下是一些内置的限制,其中一些可以通过其构造函数进行自定义

new BusinessTime\Constraint\HoursOfDay(10, 13, 17);
new BusinessTime\Constraint\BetweenHoursOfDay(9, 17);
new BusinessTime\Constraint\BetweenTimesOfDay('08:45', '17:30');
new BusinessTime\Constraint\WeekDays();
new BusinessTime\Constraint\Weekends();
new BusinessTime\Constraint\DaysOfWeek('Monday', 'Wednesday', 'Friday');
new BusinessTime\Constraint\BetweenDaysOfWeek('Monday', 'Friday');
new BusinessTime\Constraint\DaysOfMonth(1, 8, 23);
new BusinessTime\Constraint\BetweenDaysOfMonth(1, 20);
new BusinessTime\Constraint\MonthsOfYear('January', 'March', 'July');
new BusinessTime\Constraint\BetweenMonthsOfYear('January', 'November');
new BusinessTime\Constraint\DaysOfYear('January 8th', 'March 16th', 'July 4th');
new BusinessTime\Constraint\BetweenDaysOfYear('January 1st', 'December 5th');
new BusinessTime\Constraint\Dates('2019-01-17', '2019-09-23', '2020-05-11');
new BusinessTime\Constraint\BetweenDates('2018-01-11', '2018-12-31');
new BusinessTime\Constraint\AnyTime(); // Oh dear.

工作时间约束的逆操作

您可以将任何业务时间限制包装在Not限制中,以反转它。

例如

$decemberOff = new BusinessTime\Constraint\Composite\Not(
    BusinessTime\Constraint\MonthsOfYear('December')
);

这个限制现在匹配任何不在12月份的时间。您可以将所需的其他限制传递给Not构造函数。

工作时间约束的例外

上述限制有一个except()方法,它接受一个或多个其他限制。这创建了一个复合限制,允许您添加到您的业务时间规则中的异常。

例如

$lunchTimeOff = (new BusinessTime\Constraint\BetweenHoursOfDay(9, 17))->except(
    new BusinessTime\Constraint\HoursOfDay(13)
);

这个限制现在匹配上午9点至下午5点之间任何时间,除了下午1点至下午2点之间的一个小时。您可以将所需的其他异常限制传递给except()方法。

注意:您可以使用except()方法在AnyTime限制上作为定义您限制的另一种方法。

(new BusinessTime\Constraint\AnyTime())->except(
    new BusinessTime\Constraint\DaysOfWeek('Friday')
);
// All times except Fridays are considered business time.

如果except()不足以满足您的需求,您还可以使用andAlso()orAlternatively()方法来构建不同类型的复合限制。

自定义工作时间约束

您可以通过实现BusinessTime\Constraint\Constraint接口来自定义自己的限制

interface BusinessTimeConstraint
{
    public function isBusinessTime(DateTimeInterface $time): bool;
}

该限制必须接受一个DateTimeInterface实例并返回是否应将其视为工作时间。

如果您想为自定义约束启用组合逻辑,请使用BusinessTime\Constraint\Composite\Combinations特性。

提示:通常使用多个简单约束比制作一个复杂的大约束更好。

工作时间约束示例

以下是一个使用业务时间约束的较为复杂的示例。

$businessTime = new BusinessTime\BusinessTime();
$businessTime->setBusinessTimeConstraints(
    (new BusinessTime\Constraint\BetweenHoursOfDay(10, 18))->except(
        new BusinessTime\Constraint\BetweenTimesOfDay('13:00', '14:00')
    ), // 9-6 every day, with an hour for lunch.
    (new BusinessTime\Constraint\WeekDays())->except(
        new BusinessTime\Constraint\WeekDays('Thursday')
    ), // Week days, but let's take Thursdays off.
    new BusinessTime\Constraint\BetweenMonthsOfYear('January', 'November'),
    // No-one does any work in December anyway.
    new BusinessTime\Constraint\Composite\Not(
        new BusinessTime\Constraint\DaysOfYear('August 23rd', 'October 20th')
    ) // Why not take off your birthday and wedding anniversary?
);

集成远程源的业务时间数据

虽然您可以尝试设置涵盖您国家所有公共假期的约束,但直接从远程源检索它们可能更简单。

BusinessTime 默认支持 WebCal.fi。

WebCal.fi

WebCal.fi 易于使用,因为它的数据是公开提供的,无需任何身份验证。不过,您需要将Guzzle库包含到您的项目中。

$factory = new BusinessTime\Remote\WebCalFiFactory(
    new GuzzleHttp\Client(),
    'https://www.webcal.fi/cal.php?id=83&format=json' // for example
);
$dates = $factory->getDates();
// = array of date objects from the specified calendar.
$webCalFiConstraint = $factory->makeConstraint();
// = a constraint containing the retrieved dates and their descriptions.

约束将使用您指定的 WebCal.fi 日历中的名称和日期进行设置。

您可以在例如https://www.webcal.fi/en-GB/other_file_formats.php找到满足您需求的 WebCal.fi 日历。

注意,您还可以使用 WebCalFi 约束上的except()方法为其提供的日期添加自定义异常。

自定义远程源

您可以通过实现上述描述的Constraint接口添加任何您喜欢的其他来源。

周期性商务截止日期

除了计算业务时间外,通常还需要计算截止日期或“截止时间”。例如,工作日的发货截止时间可能是上午11点。BusinessTime提供了处理这种情况的逻辑。

您可以使用上述相同的时间约束来创建截止日期。

$deadline = new BusinessTime\Deadline\RecurringDeadline(
    new BusinessTime\Constraint\Weekdays(),
    new BusinessTime\Constraint\HoursOfDay(11)
);

匹配所有约束的时间被视为截止日期的一次发生。这意味着截止日期是定期发生的(它不是一个单一的时间点)。

要找出下一次发生截止日期的时间,您可以使用nextOccurrenceFrom()方法。

$businessTime = new BusinessTime\BusinessTime();
$deadline->nextOccurrenceFrom($businessTime);
// = a new business time instance for the time the deadline next occurs.

在这个示例中,这可能会给您今天是上午11点,或者是下周一上午11点,如果现在是周五下午11点之后。

还有一个previousOccurrenceFrom(),从给定时间向回执行等效操作。

您还可以检查在给定时间段内是否已经过了截止日期。

$deadline->hasPassedToday();
// = true if the deadline has been passed today.
$deadline->hasPassedBetween(
    BusinessTime\BusinessTime::now->subWeek(),
    BusinessTime\BusinessTime::now->addWeek()
);
// = true if the deadline is ever passed in the given time period.

重要:上述截止日期是为处理定期截止日期而设计的。它们不适用于确定单一的时间点。为了与单一时间点进行比较,您只需使用 Carbon 提供的比较方法。

$time = new BusinessTime\BusinessTime();
$deadline = new BusinessTime\BusinessTime('2018-12-08 17:00');
$time->gt($deadline);
// = true if the moment has passed.

商务时间工厂

您可能不想在代码中每个需要使用的地方都设置一个BusinessTime\BusinessTime的实例。

为了避免这种情况,您可以使用一次设置所需约束的BusinessTime\Factory,然后在任何地方使用它。

例如

$factory = new BusinessTime\BusinessTimeFactory();
$factory->setConstraints(
    new BusinessTime\Constraint\DaysOfWeek('Saturday', 'Sunday'),
    new BusinessTime\Constraint\Dates('2018-12-25'),
);

设置好工厂后,您可以以通常共享依赖项的方式共享它。例如,您可以将它添加到像 Laravel 或 Symfony 这样的框架中的容器中。

当您获得工厂的实例时,您可以从它那里获取一个现成的BusinessTime\BusinessTime实例。

$date = $factory->make('2018-03-21');
$now = $factory->now();

BusinessTimeFactory实例可以序列化,这使得它很容易存储在缓存或文件系统中。

精度

默认情况下,BusinessTime使用小时精度。这意味着它计算的业务时间大约准确到一小时。

如果您需要比这更好的精度,您可以将其设置为所需的精度。

$businessTime = new BusinessTime\BusinessTime();
$businessTime->setPrecision(BusinessTime\Interval::minutes(30)); // Half-hour precision.
$businessTime->setPrecision(BusinessTime\Interval::minutes(15)); // Quarter-hour precision.

您还可以以相同的方式在业务时间工厂中设置精度。

请注意,精度越高,性能越低。这是因为 BusinessTime 必须检查您指定的每个大小的时间间隔。例如,在小时精度下,处理一周需要7 * 24 = 168次迭代。在分钟精度下,这变为7 * 24 * 60 = 10080次迭代,这比慢60倍。

始终尝试设置可以满足您需求的最大精度间隔。

测试

您可以使用此处描述的Carbon的测试设施

http://carbon.nesbot.com/docs/#api-testing

例如,您可以像这样模拟当前时间

Carbon::setTestNow($knownDate);

然后在您的测试中继续操作。

要运行BusinessTime包本身的测试,您可以在该目录下运行测试

make test

您还可以阅读测试,以获取库更详细的使用示例。