$string_date = '2010-08-07 08:39:00';
[$y, $m, $d] = [null, null, null];
switch(true) {
case preg_match('/^(\d{4})-(\d{2})-(\d{2})/', $string_date, $fragments):
list($y, $m, $d) = [$fragments[1], $fragments[2], $fragments[3]];
break;
case preg_match('/^(\d{4})\/(\d{2})\/(\d{2})/', $string_date, $fragments):
list($y, $m, $d) = [$fragments[1], $fragments[2], $fragments[3]];
break;
case preg_match('/^(\d{2})-(\d{2})-(\d{4})/', $string_date, $fragments):
list($d, $m, $y) = [$fragments[1], $fragments[2], $fragments[3]];
break;
case preg_match('/^(\d{2})\/(\d{2})\/(\d{4})/', $string_date, $fragments):
list($m, $d, $y) = [$fragments[1], $fragments[2], $fragments[3]];
break;
default:
throw new Exception('String Date supplied with unknown format');
}
if (!checkdate($m, $d, $y)) {
throw new Exception ('String Date supplied with invalid date content');
}
try {
$object_datetime = new DateTime($string_date);
} catch (\Exception $exception) {
echo 'String Date supplied impossible to convert to Date';
$object_datetime = null;
}
null !== $object_date
? $object_date->setTime(0,0)
; null;
var_dump($object_datetime);
Pessoal, este código surgiu da necessidade de se converter um campo de data fornecido como string numa integração entre sistemas.
O fato é que quando se depende de informações externas o processo pode facilmente quebrar quando as regras estabelecidas não forem estritamente respeitadas.
No meu caso um campo que deveria ser enviado no formato date Y-m-d acabou sendo enviado num formato DateTime Y-m-d H:i:s. O problema se agravou pelo fato de que apenas um dos fornecedores acabou enviando desta forma e os demais fornecedores enviavam a informação no formato acordado.
Dúvida cruel… solicitar ao fornecedor para corrigir o envio da informação? Ou flexibilizar a entrada do dados de forma inteligente?
Opção dois na veia! O fato é que todos os fornecedores continuavam enviando uma data válida pra nós, só que com formatos ou máscaras diferentes, mas ainda assim eram datas válidas.
Baseado neste preceito a solução foi substituir a criação do objeto DateTime calcada no createFromFormat a partir de máscaras previamente conhecidas por simplesmente passar a string date recebida para o construtor do DateTime.
// stop creating date based on known format, this will fail when datetime content is sent
$object_datetime = DateTime::createFromFormat('Y-m-d', $string_date);
// start creating datetime based on constructor, this will work with any valid date/time content
$object_datetime = new DateTime($string_date);
A grande sacada consiste em validar se uma data válida foi enviada, no caso fazemos esta validação para a porção Y-m-d somente. Se a validação passar neste nível a probabilidade da informação ser uma data válida inclusive na sua porção hora será muito grande.
Para exemplificar a flexibilidade de entrada desta informação, segue abaixo alguns exemplos de formatos válidos que irão funcionar com o construtor.
$object_datetime = new DateTime('2010-08');
var_dump($object_datetime); //2010-08-01 00:00:00.000000
$object_datetime = new DateTime('2010-08 08:39');
var_dump($object_datetime); //2010-08-01 08:39:00.000000
$object_datetime = new DateTime('2010-08 08:39:53');
var_dump($object_datetime); //2010-08-01 08:39:53.000000
$object_datetime = new DateTime('2010-08T08:39:53Z');
var_dump($object_datetime); //2010-08-01 08:39:53.000000
$object_datetime = new DateTime('2010-08-07');
var_dump($object_datetime); //2010-08-07 00:00:00.000000
$object_datetime = new DateTime('2010-08-07 08:39');
var_dump($object_datetime); //2010-08-07 08:39:00.000000
$object_datetime = new DateTime('2010-08-07 08:39:53');
var_dump($object_datetime); //2010-08-07 08:39:53.000000
$object_datetime = new DateTime('2010-08-07T08:39:53Z');
var_dump($object_datetime); //2010-08-07 08:39:53.000000
Considere ainda que podemos receber barras ao invés de hífen como separador que irá funcionar corretamente com todas as variações acima apresentadas.
$object_datetime = new DateTime('2010/08/07 08:39:53');
var_dump($object_datetime); //2010-08-07 08:39:53.000000
$object_datetime = new DateTime('2010/08/07T08:39:53Z');
var_dump($object_datetime); //2010-08-07 08:39:53.000000
Outra grande vantagem de se utilizar o construtor do DateTime é que ele vai funcionar com o formato nativo americano m/d/Y quando separado por barras e com o formato nativo britânico m-d-Y quando separado por hífens.
$object_datetime = new DateTime('08/07/2010 08:39:53');
var_dump($object_datetime); //2010-08-07 08:39:53.000000
$object_datetime = new DateTime('07-08-2010 08:39:53');
var_dump($object_datetime); //2010-08-07 08:39:53.000000
Lembrando que em nenhum momento tivemos que informar máscaras de formatação para efetivamente criar o objeto DateTime. Simplesmente mandamos a string date para o construtor e pronto.
E caso a informação final deva ser uma data sem a porção hora, simplesmente zeramos esta porção depois do objeto criado, assim:
$object_datetime = new DateTime('08/07/2010 08:39:53');
var_dump($object_datetime); //2010-08-07 08:39:53.000000
$object_datetime->setTime(0,0);
var_dump($object_datetime); //2010-08-07 00:00:00.000000
Depois desta demonstração de flexibilidade de formatos acredito que as vantagens de criação do DateTime utilizando o construtor tenham ficado claras.
E é claro que o código do início do artigo merece um refactoring para ser mais CleanCode e também para suportar os demais formatos aceitos pelo construtor do DateTime, mas para fins didáticos preferi mantê-lo mais simples mesmo.
E agora? Troca ou não troca o DateTime::createFromFormat pelo new DateTime constructor? Você decide…
Happy code!