Trabalhando com datas no PHP

Existem muitas discussões em fóruns, comentários em blogs de desenvolvedores web, enfim, muitas dúvidas sobre esta maravilhosa linguagem de programação que é o PHP e em quase todos estes lugares encontramos dúvidas especialmente sobre como trabalhar com datas no PHP, convertê-los para um formato diferente, as questões de fuso horário, etc.

Este tutorial de PHP vai tentar resolver muitos dos problemas mais comuns relacionados com questões de data e hora.

Armazenando a data e hora em um banco de dados

Antes de abordar a questão de como como você lidará com datas em PHP, eu quero falar um pouco sobre como você deve armazenar datas em um banco de dados. Especificamente vou falar sobre o MySQL porque é com este que eu tenho mais experiência. Os outros SGBD também tem formas de se trabalhar com informações de data e hora. Se utiliza outro SGBD diferente do MySQL, você pode verificar o respectivo manual.

Muitas pessoas “inventam” seu próprio modo de armazenar as datas no banco de dados, e isso, inevitavelmente, trará alguns problemas mais tarde. Eles podem, por exemplo, ter um campo de datas com tipo definido como VARCHAR e armazenar desta forma -> DD/MM/AAAA. Este é o jeito errado de fazer isso.

Há uma série de maneiras diferentes que você poderá armazenar datas corretamente em um banco de dados. Uma maneira é ter um campo INT e armazenar no formato timestamp Unix. Eu particularmente não gosto desta abordagem. Faz com que você não consiga usar a data do MySQL e as funções de tempo, e gera questões potenciais em relação ao problema Y2K38.

O MySQL tem um tipo de dados chamado TIMESTAMP. Este tipo mostra a data e hora em um formato ISO 8601, ou seja, da parte mais significativa (ano) para a parte menos significativa (segundo). Um exemplo pode ser 2009-08-18 9:17:21. A formatação extra não é realmente necessário, ao digitar isso, 9081891721 seria a mesma coisa.

Existe também um tipo de dados chamado DATETIME. Na primeira olhada para este tipo de dado, lhe parecerá idêntico ao TIMESTAMP, mas isso não é verdade. Primeiro de tudo, TIMESTAMP tem problemas com o tal Y2K38, por isso só pode armazenar datas que podem ser representados com um número inteiro de 32 bits sem sinal a partir da época Unix (1970-01-01 00:00:00), desta forma é possível um limite superior à data Isso dá um limite superior 2038-1-19 03:14:07 referente ao Y2K38. DATETIME não tem esse problema, pois pode armazenar datas de 1000-01-01 a 9999-12-31. Finalmente, existem também tipos de dados chamados DATE e TIME. Estes são para caso você só precise apenas de data ou a hora.

Minha recomendação é usar o DATETIME, DATE ou TIME dependendo de suas necessidades, claro. Atualmente há um tipo de dado chamado YEAR também. Utilizando os próprios tipos de dados do MySQL para armazenamento de data e hora permite que você use as funções de data e hora do Mysql.

Eu não vou mais abordar bases de dados para o restante deste tutorial. Os exemplos vão assumir que você já tem alguma string contendo uma data.

TIMEZONE – Fuso-horário

Este é um problema muito freqüente. Ao usar a função date() no PHP estará sempre a usar o tempo do servidor para a geração de data e hora. A menos que você esteja em um fuso horário diferente do seu servidor, este não será um problema.

Exemplificando: Se eu tenho um VPS (Virtual Private Server), na Inglaterra, mas vivo na Dinamarca terei aí uma diferença de tempo de uma hora.
Como posso obter com o PHP que seja exibido a hora certa para mim?

Por eu ser o administrador do servidor, eu posso alterar o relógio do servidor, mas a maioria das pessoas não pode fazer essa alteração no servidor, então vamos ter que trabalhar algumas soluções alternativas para esse problema.

Se você tem acesso ao php.ini (ou pode mudar suas diretrizes usando um arquivo htaccess para manipular o APACHE) você pode mudar a diretiva date.timezone (na verdade, isso precisa ser definido para algo que você não queira erros a partir do PHP ). Caso contrário, você pode usar a função date_default_timezone_set(). O manual tem uma lista de fusos horários – TIMEZONES válidos que você pode usar para essas duas situações.

Vamos testá-lo:

echo date('H:i:s');

Se a hora local do meu computador por exemplo for 9:34:08, minha saída em relação à este código acima também será 9:34:08.

Agora vamos tentar mudar o fuso horário:

date_default_timezone_set('America/New_York');
echo date('H:i:s');

E nós teremos 03:34:08. Esta é uma boa forma de fazê-lo.

E se precisarmos de várias datas em diferentes fusos horários, mas na mesma página?
Bem, isso é bastante simples.

Considere o seguinte exemplo:

date_default_timezone_set('Europe/Copenhagen');
echo 'The time in Copenhagen is: ' . date('H:i:s') . PHP_EOL;

date_default_timezone_set('America/New_York');
echo 'The time in New York is: ' . date('H:i:s') . PHP_EOL;

date_default_timezone_set('Europe/Moscow');
echo 'The time in Moscow is: ' . date('H:i:s') . PHP_EOL;

A saída seria assim:

The time in Copenhagen is: 09:45:56
The time in New York is: 03:45:56
The time in Moscow is: 11:45:56

E se você tem usuários de vários fusos horários distintos? Ainda assim é simples!
Pergunte-lhes o seu fuso horário e configure adequadamente. A função timezone_identifiers_list() te dá uma matriz de todos os identificadores de fuso horário, então você pode usar isso para gerar uma lista suspensa que os usuários poderão escolher. Você também pode experimentar geolocalização, mas isso está além do escopo deste tutorial.

Por último, o PHP tem uma função chamada gmdate(), que sempre irá  formatar a data em GMT.

Se você souber o deslocamento em segundos você pode fazer algo como:

gmdate('H:i:s', time() + $offset);

Então você pode fazer isso…

gmdate('H:i:s', time() + 3600);

… para um deslocamento de uma hora.

No entanto, isso exige que você leve DST em consideração. Nem todos os países observam DST, e alguns fazem isso de forma diferente do que outros. Minha recomendação seria a utilização de suporte construído do próprio PHP para fusos horários.

Conversão de um formato para outro

“Eu tenho uma data no formato dd/MM/AAAA, mas eu preciso MM-DD-AAAA. AJUDA!”

Como podemos ajudar essa pessoa?
Bem, vamos analisar o problema um pouco.
Cada data é constituído por três componentes: dia, mês e ano
Então, basicamente o que precisamos fazer é reorganizar-lo e usar hífens em vez de barras.

Existem algumas maneiras que nós podemos fazer isso. Vamos primeiro tentar dividir os três componentes. Nós temos uma função chamada explode () para isso.

$oldDate = '18/08/2009'; // DD/MM/YYYY

$parts = explode('/', $oldDate);

/**
 * Now we have:
 *   $parts[0]: the day
 *   $parts[1]: the month
 *   $parts[2]: the year
 * We could also have done:
 *   list($day, $month, $year) = explode('/', $oldDate);
 */

$newDate = "{$parts[1]}-{$parts[0]}-{$parts[2]}"; // MM-DD-YYYY

echo $newDate; // 08-18-2009

Isso parece funcionar perfeitamente.
Nós também podemos usar algo chamado de ER (expressões regulares ou ainda regex).

Esta será essencialmente sempre em uma linha contínua, ou seja, sem quebras de linha:

$oldDate = '18/08/2009';
$newDate = preg_replace('#^(\d{2})/(\d{2})/(\d{4})$#', '$2-$1-$3', $oldDate);

Não vou explicar as ERs neste tutorial.
Se quiser se aventurar mais nesse  undo das ERs, sugiro que vá direto ao melhor lugar para se iniciar -> Guia rápido de expressões regulares (regex).

Mas há alguma outra forma? Sim, existe!

Como geralmente acontece com programação, existem muitas maneiras de se fazer a mesma coisa. Supondo que já temos dividido a nossa data em três variáveis $day, $month e $year, podemos usar uma função chamada mktime() para gerar um timestamp Unix e usar date() para formatá-lo desta maneira:

$timestamp = mktime(0, 0, 0, $month, $day, $year);
echo date('m-d-Y', $timestamp);

Contudo uma outra maneira é usar strtotime() para converter uma string formatada dentro de um timestamp Unix. EXCEPT, strtotime() não reconhece DD/MM/AAAA como um formato de data válido, então ele retorna falso. Há também problemas com a ambiguidade.
Considere a sequência de 2009-07-08.
É 07 de agosto, ou é a 08 de julho? Ele será o primeiro.

Data validação

Se você pegar uma data como entrada (por exemplo, um aniversário), seria muito útil saber se é realmente uma data válida. Há duas condições que devem ser cumpridas antes de uma data poder ser considerada válida:

1. Ela deve obedecer a um formato específico (que talvez só possa ser AAAA-MM-DD, por exemplo).
2. A data deve realmente existir (2011-12-29 ainda não existe em relação a data de postagem deste tutorial, mas 2010-11-16 existe).

A primeira condição é bastante simples, e podemos fazer isso usando expressões regulares. O segundo é um pouco mais complexo. Teremos de obter a duração de um mês, mas talvez seja um ano bissexto, talvez não… felizmente, não precisamos nos preocupar com isso, PHP tem uma função chamada checkdate() que, uhm … verifica a data. Desta forma então,  regex e checkdate() é o que precisamos.

Um pequeno exemplo:

<?php
if ($_SERVER['REQUEST_METHOD'] == 'POST' && isset($_POST['date'])) {
        if (preg_match('#^(\d{4})-(\d{2})-(\d{2})$#', $_POST['date'], $matches) && checkdate($matches[2], $matches[3], $matches[1])) {
                echo '<p>The date is <strong>valid</strong>!</p>';
        }
        else {
                echo '<p>The date is <strong>invalid</strong>!</p>';
        }

        echo '<hr>';
}
?>

<form action="<?php echo $_SERVER['PHP_SELF'] ?>" method="post">
        <label for="date">Enter an ISO 8601 date:</label>
        <input type="text" name="date" id="date"<?php if (!empty($_POST['date'])): ?> value="<?php echo htmlentities($_POST['date']) ?>"<?php endif ?>>
        <button type="submit">Check validity</button>
</form>

Este exemplo é bastante simples.
Primeiro verificamos o formato da string usando preg_match(), onde também extraímos o dia, mês e ano em referências anteriores. preg_match () retorna true se a sequência corresponde ao padrão. Como a lógica é uma associação da esquerda para a direita, o preg_match() é avaliado primeiro, e o checkdate() só será avaliado se o primeiro operando for true (se o operando da esquerda foi falso, a expressão não poderá retornar true, então avaliar o operando direito seria redundante, daí a razão pela qual não é).

Se você fez campos separados para o dia, mês e ano é ainda mais simples e podemos ignorar a verificação do formato.

Você pode ter condições adicionais de configuração que devem ser atendidos antes que você considere uma data válida. Você pode consultar por um intervalo de data que significa que a primeira deve ser anterior ao passado. Você pode apenas querer datas no passado ou no futuro, ou eles podem ter de ser dentro de um intervalo pré-determinado. A maneira mais fácil de fazer isso seria converter as datas para timestamp Unix. Daí em diante seria comparação rudimentar de inteiros.

Vou deixar isso como um exercício para você. Uma forma mais segura em relação ao chamado Y2K38 é usar DateTime::diff(), para isso, consulte este comentário no manual.

Conclusão e ler mais

Como pode ver, datas não são realmente difíceis de se trabalhar.
As funções próprias do PHP tornam muito fácil para validar e formatar todos os tipos de datas. Elas realmente fazem todo o trabalho pesado para nós.
Por isso, não deve ser uma surpresa que a leitura mais recomendada para este tema é o ponto de referência de data no próprio manual do PHP.

O PHP também tem suporte nativo para outros tipos de calendário, como o calendário judaico. Acho até que alguém escreveu um livro exclusivamente sobre o PHP e datas. Você pode jogar no Google para saber mais sobre isso.


Autor: Daniel Egeberg – phpfreaks.com
Tutorial traduzido do site phpfreaks.com por Roberto Lunelli

Tópicos relacionandos

Publicado por

Razor

Conhecedor das artes gráficas, amante de design web, amigo íntimo do desenvolvimento web, com a pretensão de aprender e ajudar à quem busca conhecimento, me faço presente. =)

2 comentários sobre “Trabalhando com datas no PHP”

  1. Existe alguma função do php que me retorne todas as datas de respectivo mês de respectivo ano que o usuário do site selecionar ?
    Estou montando um calendário de eventos e meu visitante poderá ver os eventos do mês atual, mas também poderá selecionar. Sei que eu poderia fazer isso consultando diretamente do banco, mas gostaria de saber se existe alguma função assim.
    http://www.evbatistagetsemanisp.com.br/index.php?pagina=eventos

    1. Olha… todas as datas acredito que não.
      Mas vc podemontar alguma função que faça isso pra vc.
      Não está ligado ao escopo deste tutorial e por isso não irei me deter.
      Prometo desenvolver um tutorial de calendário de eventos em breve e talvez ajude nos teus estudos.

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *