Toda vez que preciso imprimir “31 de agosto de 2026” aparece de novo aquele array com os doze meses no meio do template. Dá para apagar essa herança: a extensão intl já sabe escrever data em português, e o IntlDateFormatter aceita um pattern seu no lugar dos formatos prontos.
$tz = new DateTimeZone('America/Sao_Paulo');
$fmt = new IntlDateFormatter(
'pt_BR',
IntlDateFormatter::NONE,
IntlDateFormatter::NONE,
$tz,
IntlDateFormatter::GREGORIAN,
"d 'de' MMMM 'de' y"
);
echo $fmt->format(new DateTimeImmutable('2026-08-31', $tz));
// 31 de agosto de 2026As aspas simples dentro do pattern marcam texto literal do ICU, por isso o “de” sai como palavra e não como código de campo. MMMM é o mês por extenso, MMM abrevia para “ago.” (com ponto, em pt-BR) e EEEE traz o dia da semana. Os dois NONE no meio existem porque, com pattern explícito, os estilos prontos de data e hora ficam fora do jogo. O mês vem minúsculo: se precisar de “Agosto” no começo da frase, resolva com mb_convert_case() na saída, não trocando o pattern.
A pegadinha do fuso
date_default_timezone_get(); // UTC na maioria dos servidores
$fmt->format(new DateTimeImmutable('2026-08-31'));
// 30 de agosto de 2026 <- um dia a menos
$fmt->format(new DateTimeImmutable('2026-08-31', $tz));
// 31 de agosto de 2026Data sem hora vira meia-noite no fuso padrão do PHP. Se o servidor roda em UTC e o formatter converte para São Paulo, aquelas três horas a menos jogam o resultado para o dia anterior. Passe o mesmo DateTimeZone na criação e na formatação e o problema some. Vale o mesmo cuidado ao formatar um post_date vindo do MySQL, que é hora local sem fuso nenhum embutido.
Antes de subir isso para produção rode php -m | grep intl. Sem a extensão instalada a classe nem existe e o deploy quebra com Error: Class “IntlDateFormatter” not found.