
date() formata um timestamp, DateTimeImmutable serve assim que há um cálculo e IntlDateFormatter devolve os nomes de dias e de meses em francês. Define o fuso de forma explícita: sem date.timezone, o PHP usa UTC. strftime() está obsoleta desde o PHP 8.1 e o seu substituto não é date(), que não traduz nada.
Mostrar uma data em PHP é simples. Mostrar a data certa, no fuso horário certo, em francês, e calcular um intervalo sem te enganares na mudança da hora já nem por isso. Este artigo cobre as funções de base e depois as classes DateTimeImmutable e IntlDateFormatter, que resolvem os casos que date() não sabe tratar. Todos os resultados foram obtidos em PHP 8.5.10.
O fuso horário, antes de tudo o resto
É a fonte de erros número um, e é silenciosa. Se date.timezone não estiver definido no php.ini, o PHP usa UTC. Um site francês mostra então uma hora desfasada de uma ou duas horas conforme a estação, sem o menor aviso.
echo ini_get('date.timezone'), "\n"; // (vazio) em muitas instalações
echo date_default_timezone_get(), "\n"; // UTCA definição faz-se no php.ini, ou logo no arranque da aplicação:
date_default_timezone_set('Europe/Paris');A regra de fundo: guardar em UTC, mostrar no fuso do utilizador. Uma base de dados que contém horas locais fica inutilizável assim que um utilizador muda de fuso ou entra a hora de verão.
date() e time()
time() devolve o número de segundos decorridos desde 1.º de janeiro de 1970 à meia-noite UTC. date() formata um timestamp desses, ou o instante atual se não lhe passares nenhum.
date_default_timezone_set('Europe/Paris');
echo date('d/m/Y'), "\n"; // 02/09/2026
echo date('d/m/Y H:i:s'), "\n"; // 02/09/2026 21:47:03
echo time(), "\n"; // 1788378423
echo date('d/m/Y', 1645303037), "\n"; // 19/02/2022Os caracteres de formato mais usados:
| Data | Hora | ||
|---|---|---|---|
d | dia com 2 dígitos (01 a 31) | H | hora em formato 24 h (00 a 23) |
j | dia sem zero à esquerda (1 a 31) | h | hora em formato 12 h (01 a 12) |
m | mês com 2 dígitos (01 a 12) | i | minutos (00 a 59) |
n | mês sem zero à esquerda (1 a 12) | s | segundos (00 a 59) |
Y | ano com 4 dígitos | A | AM ou PM |
N | dia da semana (1 = segunda-feira) | P | desvio, por exemplo +02:00 |
t | número de dias do mês | U | timestamp Unix |
L | 1 se o ano for bissexto | c | formato ISO 8601 completo |
Para inserir uma letra que também é um código de formato, tens de a escapar com uma barra invertida:
echo date('\L\e d/m/Y'), "\n"; // Le 02/09/2026
// sem a barra invertida, «L» daria 0 ou 1 e «e» o nome do fuso horáriodate() não fala francês
É a segunda armadilha clássica. Os nomes de dias e de meses devolvidos por date() estão sempre em inglês, e setlocale() não muda nada.
$ts = mktime(0, 0, 0, 4, 1, 2022);
echo date('l', $ts), "\n"; // Friday
setlocale(LC_TIME, 'fr_FR.UTF-8', 'fr_FR');
echo date('l', $ts), "\n"; // Friday — sem alteraçãoA solução é o IntlDateFormatter, da extensão intl:
$formateur = new IntlDateFormatter(
'fr_FR',
IntlDateFormatter::FULL,
IntlDateFormatter::NONE,
'Europe/Paris',
IntlDateFormatter::GREGORIAN,
);
echo $formateur->format($ts), "\n"; // vendredi 1 avril 2022Para um formato à medida, a classe aceita um padrão ICU, cujos códigos não são os de date():
$formateur->setPattern("EEEE d MMMM y '\u{e0}' HH'h'mm");
echo $formateur->format(new DateTimeImmutable('now', new DateTimeZone('Europe/Paris')));
// mercredi 2 septembre 2026 à 21h47Se o intl não estiver disponível, uma tabela de correspondência safa-te em francês, mas não escala para várias línguas.
DateTimeImmutable, a classe a usar
Para tudo o que vai além da apresentação, as classes de data valem mais do que as funções. DateTimeImmutable é preferível a DateTime: os seus métodos devolvem um novo objeto em vez de alterarem o objeto atual, o que elimina as modificações à distância.
$paris = new DateTimeZone('Europe/Paris');
$date = new DateTimeImmutable('2026-09-02 14:30:00', $paris);
echo $date->format('d/m/Y H:i P'), "\n"; // 02/09/2026 14:30 +02:00
echo $date->modify('+3 days')->format('d/m/Y'), "\n"; // 05/09/2026
echo $date->format('d/m/Y'), "\n"; // 02/09/2026 — sem alteração
// Com DateTime (mutável), a terceira linha mostraria 05/09/2026// Acrescentar e retirar durações
$dans30Mois = $date->add(new DateInterval('P30M'));
$ilYa2Sem = $date->sub(new DateInterval('P2W'));
// Diferença entre duas datas
$ecart = $date->diff(new DateTimeImmutable('2026-12-25', $paris));
echo $ecart->days, " jours\n";
echo $ecart->format('%m mois et %d jours'), "\n";
// Uma série de datas
$periode = new DatePeriod(
$date,
new DateInterval('P1D'),
new DateTimeImmutable('2026-09-07', $paris),
);
foreach ($periode as $jour) {
echo $jour->format('D d/m'), "\n";
}A armadilha do último dia do mês
Somar um mês a um fim de mês não dá aquilo que esperas, e o comportamento é o mesmo com DateInterval e com a aritmética sobre mktime().
$fin = new DateTimeImmutable('2026-01-31');
echo $fin->add(new DateInterval('P1M'))->format('d/m/Y'), "\n"; // 03/03/2026O 31 de fevereiro não existe: o PHP deixa-o transbordar para março. Para obter o último dia do mês seguinte, é preciso pedi-lo de forma explícita:
echo $fin->modify('last day of next month')->format('d/m/Y'), "\n"; // 28/02/2026Um dia nem sempre tem 24 horas
Na passagem à hora de verão, um dia tem 23, na hora de inverno, tem 25. Somar 86400 segundos a um timestamp dá então uma hora errada.
$paris = new DateTimeZone('Europe/Paris');
$avant = new DateTimeImmutable('2026-03-28 12:00:00', $paris);
$apres = $avant->add(new DateInterval('P1D'));
echo $apres->format('d/m/Y H:i P'), "\n"; // 29/03/2026 12:00 +02:00
echo ($apres->getTimestamp() - $avant->getTimestamp()) / 3600, " heures\n"; // 23DateInterval raciocina em dias de calendário e devolve mesmo o meio-dia seguinte, embora só tenham passado 23 horas. É o comportamento correto para um lembrete «amanhã ao meio-dia», e o errado para uma duração faturada. São duas noções distintas: P1D para um dia de calendário, PT24H para vinte e quatro horas.
Ler uma data escrita pelo utilizador
strtotime() percebe muita coisa, mas interpreta os formatos ambíguos à americana: 03/04/2026 passa a 4 de março, e não 3 de abril. Para um formato conhecido, createFromFormat() não deixa margem para dúvidas.
$date = DateTimeImmutable::createFromFormat(
'!d/m/Y', // o «!» repõe a hora a 00:00:00
'03/04/2026',
new DateTimeZone('Europe/Paris'),
);
echo $date->format('d F Y'), "\n"; // 03 April 2026Sem o ! inicial, os campos não fornecidos ficam com o valor do instante atual: duas execuções do mesmo código dão duas horas diferentes.
O ponto mais perigoso: uma data inválida não falha, transborda em silêncio.
$saisie = '31/02/2026';
$date = DateTimeImmutable::createFromFormat('!d/m/Y', $saisie, $paris);
echo $date->format('d/m/Y'), "\n"; // 03/03/2026
print_r(DateTimeImmutable::getLastErrors()); // ['warnings' => [10 => 'The parsed date was invalid'], …]
var_dump(checkdate(2, 31, 2026)); // bool(false)É preciso, portanto, verificar getLastErrors() depois de cada análise, ou comparar a data reformatada com aquilo que foi escrito:
function lireDate(string $saisie, DateTimeZone $fuseau): ?DateTimeImmutable
{
$date = DateTimeImmutable::createFromFormat('!d/m/Y', $saisie, $fuseau);
if ($date === false) {
return null;
}
$erreurs = DateTimeImmutable::getLastErrors();
if ($erreurs !== false && ($erreurs['warning_count'] || $erreurs['error_count'])) {
return null;
}
return $date->format('d/m/Y') === $saisie ? $date : null;
}Desde o PHP 8.2, getLastErrors() devolve false quando não há nada a assinalar, em vez de um array com contadores a zero. O teste tem de contar com isso.
Novidades recentes
O PHP 8.4 acrescentou createFromTimestamp(), que aceita timestamps fracionários:
$d = DateTimeImmutable::createFromTimestamp(1645303037.5);
echo $d->format('d/m/Y H:i:s.u P'), "\n"; // 19/02/2022 20:37:17.500000 +00:00Antes era preciso passar por '@' . $ts ou setTimestamp(), e os microssegundos perdiam-se.
As funções obsoletas que já não deves usar
Várias funções de data ainda presentes emitem um aviso de obsolescência. Funcionam em PHP 8.5.10, mas a sua remoção já está marcada.
| Função | Obsoleta desde | Substituto |
|---|---|---|
strftime() | PHP 8.1 | IntlDateFormatter::format() |
gmstrftime() | PHP 8.1 | IntlDateFormatter::format() |
strptime() | PHP 8.2 | date_parse_from_format() ou IntlDateFormatter::parse() |
date_sunrise() / date_sunset() | PHP 8.1 | date_sun_info() |
utf8_encode() | PHP 8.2 | mb_convert_encoding() |
As mensagens exatas, recolhidas em PHP 8.5.10 tal como em PHP 8.4.25:
Deprecated: Function strftime() is deprecated since 8.1,
use IntlDateFormatter::format() instead
Deprecated: Function strptime() is deprecated since 8.2,
use date_parse_from_format() (for locale-independent parsing),
or IntlDateFormatter::parse() (for locale-dependent parsing) instead
Deprecated: Constant SUNFUNCS_RET_STRING is deprecated since 8.4,
as date_sunrise() and date_sunset() were deprecated in 8.1strftime() era a forma habitual de obter uma data em francês. O seu substituto não é date(), que não traduz nada, mas sim IntlDateFormatter.
Pelo contrário, mktime(), checkdate(), getdate() e idate() não estão obsoletas e funcionam normalmente. Continuam úteis para casos simples, mesmo que as classes cubram tudo o que elas fazem.
A reter
- Define o fuso de forma explícita, guarda em UTC, mostra em hora local.
date()para formatar um instante,DateTimeImmutablepara qualquer cálculo.IntlDateFormatterpara os nomes de dias e de meses em francês.- Depois de
createFromFormat(), verificagetLastErrors(): uma data inválida transborda em silêncio. - Um dia de calendário não são 24 horas.
P1DePT24Hnão são intercambiáveis.
Para analisar datas aos milhões num ficheiro de importação, vê ler ficheiros grandes com PHP, e a ligação a uma base de dados para as guardares em UTC.
Vê também validar uma data com uma expressão regular, gerir datas com Carbon no Laravel e o hub Desenvolvimento web.
Erros frequentes
date.timezone no php.ini, o PHP cai em UTC e mostra uma hora desfasada sem o menor aviso.setlocale() não tem qualquer efeito sobre date(). O substituto de strftime() é IntlDateFormatter, não date().31/02/2026 passa a 03/03/2026 com um simples aviso em getLastErrors(). Tens de o verificar depois de cada análise.03/04/2026 é 4 de março, não 3 de abril.31/01/2026 + P1M dá 03/03/2026. Para o último dia do mês, usa modify('last day of next month').P1D avança mesmo um dia de calendário embora só tenham passado 23 horas. P1D e PT24H não são intercambiáveis.false quando não há nada a assinalar, em vez de um array de contadores a zero.

