В дополнение к @ очень полезный ответ fyrye в это okayish обходной путь для Названные ошибки ( это один ), что DatePeriod вычитает один час при переходе в летнее время, но не добавляет один час при выходе из летнего времени (и, таким образом, в марте в Европе / Берлине есть правильно 743 часа, но в октябре 744 вместо 745 часов):
Подсчет часов месяца (или любого промежутка времени) с учетом перехода на летнее время в обоих направлениях
function getMonthHours(string $year, string $month, \DateTimeZone $timezone): int
{
// or whatever start and end \DateTimeInterface objects you like
$start = new \DateTimeImmutable($year . '-' . $month . '-01 00:00:00', $timezone);
$end = new \DateTimeImmutable((new \DateTimeImmutable($year . '-' . $month . '-01 23:59:59', $timezone))->format('Y-m-t H:i:s'), $timezone);
// count the hours just utilizing \DatePeriod, \DateInterval and iterator_count, hell yeah!
$hours = iterator_count(new \DatePeriod($start, new \DateInterval('PT1H'), $end));
// find transitions and check, if there is one that leads to a positive offset
// that isn't added by \DatePeriod
// this is the workaround for https://bugs.php.net/bug.php?id=75685
$transitions = $timezone->getTransitions((int)$start->format('U'), (int)$end->format('U'));
if (2 === count($transitions) && $transitions[0]['offset'] - $transitions[1]['offset'] > 0) {
$hours += (round(($transitions[0]['offset'] - $transitions[1]['offset'])/3600));
}
return $hours;
}
$myTimezoneWithDST = new \DateTimeZone('Europe/Berlin');
var_dump(getMonthHours('2020', '01', $myTimezoneWithDST)); // 744
var_dump(getMonthHours('2020', '03', $myTimezoneWithDST)); // 743
var_dump(getMonthHours('2020', '10', $myTimezoneWithDST)); // 745, finally!
$myTimezoneWithoutDST = new \DateTimeZone('UTC');
var_dump(getMonthHours('2020', '01', $myTimezoneWithoutDST)); // 744
var_dump(getMonthHours('2020', '03', $myTimezoneWithoutDST)); // 744
var_dump(getMonthHours('2020', '10', $myTimezoneWithoutDST)); // 744
PS Если вы проверите (более длительный) интервал времени, который приводит к большему количеству этих двух переходов, мой обходной путь не коснется подсчитанных часов, чтобы уменьшить вероятность забавных побочных эффектов. В таких случаях необходимо применять более сложное решение. Можно было перебрать все найденные переходы и сравнить текущий с последним и проверить, соответствует ли он переходу с DST true-> false.
strftime()
и разделить разницу на 3600, но всегда ли это будет работать? Черт тебя побери, летнее время!