Como explicarei mais adiante, sempre favoreceria os métodos TryParse
e TryParseExact
. Como eles são um pouco volumosos de usar, escrevi um método de extensão que facilita muito a análise:
var dtStr = "2011-03-21 13:26";
DateTime? dt = dtStr.ToDate("yyyy-MM-dd HH:mm");
Diferente de etc. Parse
, ParseExact
ele não gera uma exceção e permite que você verifique via
if (dt.HasValue) { // continue processing } else { // do error handling }
se a conversão foi bem-sucedida (neste caso, dt
tem um valor que você pode acessar via dt.Value
) ou não (nesse caso, é null
).
Isso ainda permite usar atalhos elegantes como o operador "Elvis" ?.
, por exemplo:
int? year = dtStr?.ToDate("yyyy-MM-dd HH:mm")?.Year;
Aqui você também pode usar year.HasValue
para verificar se a conversão foi bem-sucedida e, se não for bem-sucedida, year
conterá null
, caso contrário, a parte do ano da data. Não há exceção lançada se a conversão falhar.
Solução: O método de extensão .ToDate ()
Experimente .NetFiddle
public static class Extensions
{
// Extension method parsing a date string to a DateTime?
// dateFmt is optional and allows to pass a parsing pattern array
// or one or more patterns passed as string parameters
public static DateTime? ToDate(this string dateTimeStr, params string[] dateFmt)
{
// example: var dt = "2011-03-21 13:26".ToDate(new string[]{"yyyy-MM-dd HH:mm",
// "M/d/yyyy h:mm:ss tt"});
// or simpler:
// var dt = "2011-03-21 13:26".ToDate("yyyy-MM-dd HH:mm", "M/d/yyyy h:mm:ss tt");
const DateTimeStyles style = DateTimeStyles.AllowWhiteSpaces;
if (dateFmt == null)
{
var dateInfo = System.Threading.Thread.CurrentThread.CurrentCulture.DateTimeFormat;
dateFmt=dateInfo.GetAllDateTimePatterns();
}
// Commented out below because it can be done shorter as shown below.
// For older C# versions (older than C#7) you need it like that:
// DateTime? result = null;
// DateTime dt;
// if (DateTime.TryParseExact(dateTimeStr, dateFmt,
// CultureInfo.InvariantCulture, style, out dt)) result = dt;
// In C#7 and above, we can simply write:
var result = DateTime.TryParseExact(dateTimeStr, dateFmt, CultureInfo.InvariantCulture,
style, out var dt) ? dt : null as DateTime?;
return result;
}
}
Algumas informações sobre o código
Você pode se perguntar por que usei a InvariantCulture
chamada TryParseExact
: Isso é forçar a função a tratar os padrões de formato sempre da mesma maneira (caso contrário, por exemplo "." Pode ser interpretado como separador decimal em inglês enquanto é um separador de grupo ou um separador de data em Alemão). Lembre-se de que já consultamos as strings de formato baseadas em cultura algumas linhas antes, então tudo bem aqui.
Atualização: .ToDate()
(sem parâmetros) agora assume como padrão todos os padrões de data / hora comuns da cultura atual do encadeamento.
Observe que precisamos do result
e dt
juntos, porque TryParseExact
não permite o uso DateTime?
, que pretendemos retornar. No C # versão 7, você poderia simplificar a ToDate
função um pouco da seguinte maneira:
// in C#7 only: "DateTime dt;" - no longer required, declare implicitly
if (DateTime.TryParseExact(dateTimeStr, dateFmt,
CultureInfo.InvariantCulture, style, out var dt)) result = dt;
ou, se você gosta ainda mais:
// in C#7 only: Declaration of result as a "one-liner" ;-)
var result = DateTime.TryParseExact(dateTimeStr, dateFmt, CultureInfo.InvariantCulture,
style, out var dt) ? dt : null as DateTime?;
nesse caso, você não precisa das duas declarações DateTime? result = null;
e DateTime dt;
pode fazê-lo em uma linha de código. (Também seria permitido escrever em out DateTime dt
vez de, out var dt
se você preferir).
Eu simplificaram o código ainda mais usando a params
palavra-chave: Agora você não precisa do 2 nd método sobrecarregado mais.
Exemplo de uso
var dtStr="2011-03-21 13:26";
var dt=dtStr.ToDate("yyyy-MM-dd HH:mm");
if (dt.HasValue)
{
Console.WriteLine("Successful!");
// ... dt.Value now contains the converted DateTime ...
}
else
{
Console.WriteLine("Invalid date format!");
}
Como você pode ver, este exemplo apenas consulta dt.HasValue
para ver se a conversão foi bem-sucedida ou não. Como um bônus extra, o TryParseExact permite especificar strict DateTimeStyles
para que você saiba exatamente se uma sequência de data / hora adequada foi passada ou não.
Mais exemplos de uso
A função sobrecarregada permite passar uma matriz de formatos válidos usados para analisar / converter datas, como mostrado aqui também ( TryParseExact
suporta diretamente isso), por exemplo
string[] dateFmt = {"M/d/yyyy h:mm:ss tt", "M/d/yyyy h:mm tt",
"MM/dd/yyyy hh:mm:ss", "M/d/yyyy h:mm:ss",
"M/d/yyyy hh:mm tt", "M/d/yyyy hh tt",
"M/d/yyyy h:mm", "M/d/yyyy h:mm",
"MM/dd/yyyy hh:mm", "M/dd/yyyy hh:mm"};
var dtStr="5/1/2009 6:32 PM";
var dt=dtStr.ToDate(dateFmt);
Se você tiver apenas alguns padrões de modelo, também poderá escrever:
var dateStr = "2011-03-21 13:26";
var dt = dateStr.ToDate("yyyy-MM-dd HH:mm", "M/d/yyyy h:mm:ss tt");
Exemplos avançados
Você pode usar o ??
operador para usar como padrão um formato à prova de falhas, por exemplo
var dtStr = "2017-12-30 11:37:00";
var dt = (dtStr.ToDate()) ?? dtStr.ToDate("yyyy-MM-dd HH:mm:ss");
Nesse caso, o .ToDate()
usaria formatos comuns de data da cultura local e, se tudo isso falhar, tentaria usar o formato padrão ISO"yyyy-MM-dd HH:mm:ss"
como substituto. Dessa forma, a função de extensão permite "encadear" diferentes formatos de fallback facilmente.
Você pode até usar a extensão no LINQ, tente isso (está no .NetFiddle acima):
var patterns=new[] { "dd-MM-yyyy", "dd.MM.yyyy" };
(new[] { "15-01-2019", "15.01.2019" }).Select(s => s.ToDate(patterns)).Dump();
que converterá as datas na matriz rapidamente, usando os padrões e despejando-os no console.
Alguns antecedentes sobre o TryParseExact
Finalmente, aqui estão alguns comentários sobre o plano de fundo (ou seja, a razão pela qual escrevi dessa maneira):
Estou preferindo o TryParseExact neste método de extensão, porque você evita o tratamento de exceções - você pode ler no artigo de Eric Lippert sobre exceções por que você deve usar o TryParse em vez de Parse, cito-o sobre esse tópico: 2)
Essa infeliz decisão de design 1) [anotação: deixar o método Parse lançar uma exceção] foi tão irritante que, é claro,
a equipe de estruturas implementou o TryParse logo em seguida, o que faz a coisa certa.
Sim, mas TryParse
e TryParseExact
ambos ainda são muito menos confortáveis de usar: Eles forçam você a usar uma variável não inicializada como um out
parâmetro que não deve ser anulável e, enquanto você está convertendo, precisa avaliar o valor de retorno booleano - ou você para usar uma if
instrução imediatamente ou você deve armazenar o valor de retorno em uma variável booleana adicional para poder fazer a verificação posteriormente. E você não pode simplesmente usar a variável de destino sem saber se a conversão foi bem-sucedida ou não.
Na maioria dos casos, você só quer saber se a conversão foi bem-sucedida ou não (e, é claro, o valor, se foi bem-sucedida) ; portanto, uma variável de destino anulável que mantém todas as informações seria desejável e muito mais elegante - porque toda a informação é apenas armazenado em um local: isso é consistente, fácil de usar e muito menos propenso a erros.
O método de extensão que escrevi faz exatamente isso (também mostra que tipo de código você precisaria escrever toda vez que não quiser usá-lo).
Acredito que o benefício .ToDate(strDateFormat)
é que parece simples e limpo - tão simples quanto o original DateTime.Parse
deveria ser -, mas com a capacidade de verificar se a conversão foi bem-sucedida e sem gerar exceções.
1) O que se entende aqui é que o tratamento de exceções (ou seja, um try { ... } catch(Exception ex) { ...}
bloco) - necessário quando você estiver usando o Parse, porque emitirá uma exceção se uma sequência inválida for analisada - não é apenas desnecessário nesse caso, mas também irritante e complicando seu código. O TryParse evita tudo isso, pois o exemplo de código que forneci está sendo exibido.
2) Eric Lippert é um famoso colega do StackOverflow e trabalha na Microsoft como desenvolvedor principal na equipe do compilador C # há alguns anos.