Datas e horas em PHP
LidoEsta aula explora o uso de datas e horas em PHP, abordando as classes DateTime e DateTimeImmutable, formatação de datas, intervalos de tempo e fusos horários. Você aprenderá a manipular datas de forma robusta, considerando boas práticas e casos comuns.
Datas e horas são elementos essenciais em praticamente qualquer aplicação web, desde a exibição de timestamps até o cálculo de prazos e agendamentos. O PHP oferece um conjunto de classes poderosas e orientadas a objetos para lidar com esses dados, superando as antigas funções baseadas em strings como date() e strtotime().
Nesta aula, vamos mergulhar nas principais classes: DateTime e DateTimeImmutable, entender como formatar essas datas de maneira flexível, trabalhar com intervalos de tempo (diferenças entre datas) e lidar corretamente com fusos horários, um desafio comum em aplicações globais.
DateTime e DateTimeImmutable
A classe DateTime representa um ponto no tempo, armazenando data e hora juntamente com informações de fuso horário. Ela fornece métodos para manipular e comparar datas. Já a classe DateTimeImmutable funciona de forma semelhante, mas é imutável: qualquer método que modifica a data retorna um novo objeto, deixando o original intacto. Essa imutabilidade evita bugs sutis em que uma data é alterada inadvertidamente.
Para criar um objeto DateTime, você pode usar o construtor com uma string de data/hora ou usar o método estático createFromFormat() para formatos específicos. O construtor aceita formatos relativos e absolutos, como 'now', 'tomorrow' ou '2025-03-15 10:30:00'. Também é possível passar um objeto DateTimeZone como segundo argumento.
// Criando objetos DateTime
$agora = new DateTime(); // data e hora atuais
$dataFixa = new DateTime('2025-03-15 10:30:00');
$dataComFuso = new DateTime('2025-03-15 10:30:00', new DateTimeZone('America/Sao_Paulo'));
// Usando createFromFormat para formatos não padrão
$dataFormatada = DateTime::createFromFormat('d/m/Y H:i', '15/03/2025 10:30');
echo $dataFormatada->format('Y-m-d H:i:s'); // 2025-03-15 10:30:00
?>A diferença crucial entre DateTime e DateTimeImmutable é observada ao modificar valores. Com DateTime, métodos como modify(), add() e setDate() alteram o objeto original. Com DateTimeImmutable, esses métodos retornam um novo objeto e o original permanece inalterado. Isso é particularmente útil ao passar objetos por referência ou ao armazenar em estruturas de dados, pois evita efeitos colaterais.
// Mutabilidade do DateTime
$date = new DateTime('2025-03-15');
$date->modify('+1 day');
echo $date->format('Y-m-d'); // 2025-03-16
// Imutabilidade do DateTimeImmutable
$dateImutavel = new DateTimeImmutable('2025-03-15');
$novoDate = $dateImutavel->modify('+1 day');
echo $dateImutavel->format('Y-m-d'); // 2025-03-15 (inalterado)
echo $novoDate->format('Y-m-d'); // 2025-03-16
?>Formatação
Formatar datas é uma das tarefas mais comuns. O método format() aceita uma string de formato que segue os mesmos caracteres da função date(). Por exemplo, Y representa o ano com quatro dígitos, m o mês com dois dígitos, d o dia, H a hora no formato 24h, i os minutos e s os segundos. Além disso, é possível usar caracteres para nomes de meses e dias da semana, como F (nome completo do mês) e l (nome do dia da semana).
Para localização, o PHP oferece a classe IntlDateFormatter (se a extensão intl estiver habilitada) que permite formatar datas de acordo com um idioma e país específicos. Isso é essencial para aplicações multilíngues. Você pode definir o locale, o tipo de data e hora, e até o fuso horário.
$date = new DateTime('2025-03-15 10:30:00');
// Formatação básica
echo $date->format('d/m/Y H:i:s'); // 15/03/2025 10:30:00
echo $date->format('F j, Y, g:i a'); // March 15, 2025, 10:30 am
// Usando IntlDateFormatter (requer extensão intl)
if (class_exists('IntlDateFormatter')) {
$formatter = new IntlDateFormatter('pt_BR', IntlDateFormatter::LONG, IntlDateFormatter::SHORT);
$formatter->setTimeZone(new DateTimeZone('America/Sao_Paulo'));
echo $formatter->format($date); // 15 de março de 2025, 10:30
}
?>Intervalos
Intervalos representam a diferença entre duas datas ou uma duração específica. A classe DateInterval armazena uma quantidade de anos, meses, dias, horas, minutos e segundos. Para calcular a diferença entre duas datas, usamos o método diff(), que retorna um objeto DateInterval. Esse objeto possui propriedades como y, m, d, h, i, s, além de days que fornece o total de dias da diferença.
Também podemos adicionar ou subtrair intervalos de uma data usando os métodos add() e sub() (ambos retornam o próprio objeto em DateTime ou um novo em DateTimeImmutable). Para criar um intervalo de duração específica, usamos o construtor de DateInterval que aceita uma string no formato ISO 8601, como 'P1Y' (um ano), 'P2M' (dois meses), 'P3DT4H' (três dias e quatro horas).
$inicio = new DateTime('2025-01-01');
$fim = new DateTime('2025-03-15');
$intervalo = $inicio->diff($fim);
echo $intervalo->m . ' meses e ' . $intervalo->d . ' dias'; // 2 meses e 14 dias
echo $intervalo->days; // 73 (total de dias)
// Adicionando um intervalo
$data = new DateTime('2025-03-15');
$data->add(new DateInterval('P1M10D')); // adiciona 1 mês e 10 dias
echo $data->format('Y-m-d'); // 2025-04-25
// Subtraindo
$data->sub(new DateInterval('P2W')); // subtrai 2 semanas
echo $data->format('Y-m-d'); // 2025-04-11
?>Além disso, podemos comparar datas facilmente usando operadores de comparação (<, >, ==) pois os objetos DateTime são comparáveis. Também podemos usar o método getTimestamp() para obter o timestamp Unix, facilitando cálculos aritméticos simples.
Fusos horários
Fusos horários são fundamentais para aplicações que atendem usuários em diferentes regiões. O PHP suporta a lista completa de fusos do banco de dados IANA (como America/Sao_Paulo, Europe/Lisbon, Asia/Tokyo). Podemos definir o fuso horário padrão usando date_default_timezone_set() ou especificar um fuso ao criar objetos DateTime. A classe DateTimeZone encapsula as informações de fuso.
Ao trabalhar com fusos, é importante converter datas entre eles. O método setTimezone() permite alterar o fuso de um objeto DateTime (ou retornar um novo em DateTimeImmutable). Isso é útil para exibir a mesma hora em diferentes regiões. Lembre-se de que a data/hora interna é armazenada em UTC, e o fuso apenas altera a exibição.
// Definindo o fuso padrão
date_default_timezone_set('America/Sao_Paulo');
$data = new DateTime('2025-03-15 12:00:00', new DateTimeZone('America/Sao_Paulo'));
echo $data->format('Y-m-d H:i:s'); // 2025-03-15 12:00:00
// Convertendo para outro fuso
$data->setTimezone(new DateTimeZone('UTC'));
echo $data->format('Y-m-d H:i:s'); // 2025-03-15 15:00:00 (diferença de 3 horas)
// Listando fusos disponíveis (exemplo)
$zones = DateTimeZone::listIdentifiers();
echo count($zones) . ' fusos disponíveis'; // 425 fusos disponíveis
?>Para evitar problemas com horário de verão, o PHP já lida automaticamente com as transições, desde que os fusos estejam corretos. Sempre que possível, armazene datas no banco em UTC e converta para o fuso do usuário apenas na exibição, a fim de manter a consistência.
Boas práticas e observações finais
Ao trabalhar com datas e horas, prefira as classes DateTimeImmutable para evitar efeitos colaterais, especialmente em funções que recebem datas como parâmetro. Use sempre fusos horários explícitos, armazenando em UTC e convertendo somente na saída. Evite manipular timestamps manualmente; utilize os métodos fornecidos pelas classes. Para cálculos complexos, como dias úteis, considere bibliotecas como Carbon, que estendem as funcionalidades nativas.
Além disso, lembre-se de que o formato ISO 8601 (Y-m-d H:i:s) é amplamente aceito e evita ambiguidades. Sempre valide entradas de datas usando createFromFormat() e verifique se a conversão foi bem-sucedida, pois datas inválidas podem gerar erros silenciosos.
Exercícios
- Exercício 1: Crie um objeto
DateTimeImmutablepara a data '2025-12-31 23:59:59' e adicione 1 segundo. Exiba a nova data no formato 'Y-m-d H:i:s'.✓ Resposta:$data = new DateTimeImmutable('2025-12-31 23:59:59'); $dataModificada = $data->modify('+1 second'); echo $dataModificada->format('Y-m-d H:i:s'); // 2026-01-01 00:00:00 ?> - Exercício 2: Escreva uma função que receba uma string de data no formato 'd/m/Y' e retorne a data por extenso em português, usando
IntlDateFormatter(ex.: '15/03/2025' → '15 de março de 2025').✓ Resposta:function dataPorExtenso($dataStr) { $data = DateTime::createFromFormat('d/m/Y', $dataStr); if (!$data) return 'Data inválida'; $formatter = new IntlDateFormatter('pt_BR', IntlDateFormatter::LONG, IntlDateFormatter::NONE); return $formatter->format($data); } echo dataPorExtenso('15/03/2025'); // 15 de março de 2025 ?> - Exercício 3: Calcule a diferença em dias, meses e anos entre duas datas: '2000-01-01' e a data atual. Exiba no formato 'X anos, Y meses e Z dias'.✓ Resposta:
$inicio = new DateTime('2000-01-01'); $fim = new DateTime(); // hoje $diff = $inicio->diff($fim); echo $diff->y . ' anos, ' . $diff->m . ' meses e ' . $diff->d . ' dias'; ?> - Exercício 4: Converta a data/hora '2025-03-15 10:00:00' do fuso 'America/New_York' para 'Asia/Tokyo' e exiba o resultado.✓ Resposta:
$data = new DateTime('2025-03-15 10:00:00', new DateTimeZone('America/New_York')); $data->setTimezone(new DateTimeZone('Asia/Tokyo')); echo $data->format('Y-m-d H:i:s'); // 2025-03-15 23:00:00 (diferença de 13 horas) ?> - Exercício 5: Crie uma função que receba uma data e um número de dias e retorne a data após adicionar esses dias, ignorando fins de semana (sábado e domingo). Dica: use um loop ou calcule diretamente considerando semanas.✓ Resposta:
function adicionarDiasUteis($dataInicial, $diasUteis) { $data = new DateTime($dataInicial); $adicionados = 0; while ($adicionados < $diasUteis) { $data->modify('+1 day'); $diaSemana = (int)$data->format('N'); // 1 (segunda) a 7 (domingo) if ($diaSemana <= 5) { // se for dia útil $adicionados++; } } return $data; } $resultado = adicionarDiasUteis('2025-03-14', 5); // sexta-feira echo $resultado->format('Y-m-d'); // 2025-03-21 (sexta seguinte) ?>