Además de la respuesta muy útil de @ fyrye, esta es una solución aceptable para el error mencionado ( este ), que DatePeriod resta una hora cuando ingresa al verano, pero no agrega una hora al salir del verano (y por lo tanto, la marcha de Europa / Berlín tiene su 743 horas correctas, pero octubre tiene 744 en lugar de 745 horas):
Contar las horas de un mes (o cualquier período de tiempo), considerando las transiciones DST en ambas direcciones
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
PD: Si marca un período de tiempo (más largo), que conduce a más de esas dos transiciones, mi solución no tocará las horas contadas para reducir el potencial de efectos secundarios divertidos. En tales casos, se debe implementar una solución más complicada. Uno podría iterar sobre todas las transiciones encontradas y comparar la actual con la última y verificar si es una con DST verdadero-> falso.
strftime()y dividir la diferencia por 3600, pero ¿funcionará siempre? ¡Maldito seas, horario de verano!