¿Cómo analizar cadenas en DateTime en C# correctamente?
Tengo la fecha y la hora en una cadena con el formato siguiente:
"2011-03-21 13:26" //year-month-day hour:minute
¿ Cómo puedo analizarlo System.DateTime
?
Quiero usar funciones como DateTime.Parse()
o DateTime.ParseExact()
si es posible, para poder especificar el formato de la fecha manualmente.
DateTime.Parse()
Intentará averiguar el formato de la fecha dada y, por lo general, hace un buen trabajo. Si puede garantizar que las fechas siempre estarán en un formato determinado, puede utilizar ParseExact()
:
string s = "2011-03-21 13:26";
DateTime dt =
DateTime.ParseExact(s, "yyyy-MM-dd HH:mm", CultureInfo.InvariantCulture);
(Pero tenga en cuenta que normalmente es más seguro utilizar uno de los métodos TryParse en caso de que una fecha no tenga el formato esperado)
Asegúrese de marcar las cadenas de formato de fecha y hora personalizadas cuando construya la cadena de formato, especialmente preste atención al número de letras y mayúsculas y minúsculas (es decir, "MM" y "mm" significan cosas muy diferentes).
Otro recurso útil para cadenas en formato C# es el formato de cadenas en C#.
Como explicaré más adelante, siempre favorecería los métodos TryParse
y . TryParseExact
Debido a que su uso es un poco complicado, he escrito un método de extensión que facilita mucho el análisis:
var dtStr = "2011-03-21 13:26";
DateTime? dt = dtStr.ToDate("yyyy-MM-dd HH:mm");
O más simplemente, si desea utilizar implícitamente los patrones de fechas de su cultura actual, puede usarlo como:
DateTime? dt = dtStr.ToDate();
En ese caso, no es necesario especificar ningún patrón específico. Si no se especifica, se utiliza el patrón DateTime predeterminado de la cultura actual (en el hilo actual).
A diferencia de Parse
, ParseExact
etc., no genera una excepción y le permite verificar a través de
if (dt.HasValue) { // continue processing } else { // do error handling }
si la conversión fue exitosa (en este caso dt
tiene un valor al que puede acceder a través de dt.Value
) o no (en este caso lo es null
).
Eso incluso permite utilizar atajos elegantes como el operador "Elvis" ?.
, por ejemplo:
int? year = dtStr?.ToDate("yyyy-MM-dd HH:mm")?.Year;
Aquí también puede utilizar year.HasValue
para comprobar si la conversión se realizó correctamente y, si no se realizó correctamente , year
contendrá null
; de lo contrario, la parte del año de la fecha. No se lanza ninguna excepción si la conversión falla.
Solución: el método de extensión .ToDate()
Pruébelo en .NetFiddle
public static class Extensions
{
/// Extension method parsing a date string to a DateTime? <para/>
/// <summary>
/// </summary>
/// <param name="dateTimeStr">The date string to parse</param>
/// <param name="dateFmt">dateFmt is optional and allows to pass
/// a parsing pattern array or one or more patterns passed
/// as string parameters</param>
/// <returns>Parsed DateTime or null</returns>
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();
}
var result = DateTime.TryParseExact(dateTimeStr, dateFmt, CultureInfo.InvariantCulture,
style, out var dt) ? dt : null as DateTime?;
return result;
}
}
Alguna información sobre el código.
Quizás se pregunte por qué he usado InvariantCulture
la llamada TryParseExact
: Esto es para forzar a la función a tratar los patrones de formato siempre de la misma manera (de lo contrario, por ejemplo "." podría interpretarse como un separador decimal en inglés mientras que es un separador de grupo o un separador de fecha en Alemán). Recuerde que ya hemos consultado las cadenas de formato basadas en la cultura unas líneas antes, por lo que está bien aquí.
Actualización: .ToDate()
(sin parámetros) ahora tiene de forma predeterminada todos los patrones de fecha/hora comunes de la cultura actual del hilo.
Tenga en cuenta que necesitamos result
y dt
juntos, porque TryParseExact
no permite el uso DateTime?
, que pretendemos devolver. En C# Versión 7 puedes simplificar ToDate
un poco la función de la siguiente manera:
// in C#7 only: "DateTime dt;" - no longer required, declare implicitly
if (DateTime.TryParseExact(dateTimeStr, dateFmt,
CultureInfo.InvariantCulture, style, out var dt)) result = dt;
o, si lo prefieres aún más corto:
// 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?;
en cuyo caso no necesita las dos declaraciones DateTime? result = null;
y DateTime dt;
en absoluto; puede hacerlo en una línea de código. (También se permitiría escribir out DateTime dt
en lugar de out var dt
si así lo prefieres).
El estilo antiguo de C# lo habría requerido de la siguiente manera (lo eliminé del código anterior):
// DateTime? result = null;
// DateTime dt;
// if (DateTime.TryParseExact(dateTimeStr, dateFmt,
// CultureInfo.InvariantCulture, style, out dt)) result = dt;
He simplificado aún más el código usando la params
palabra clave: Ahora ya no necesitas el segundo método sobrecargado.
Ejemplo 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 puede ver, este ejemplo solo consulta dt.HasValue
para ver si la conversión fue exitosa o no. Como ventaja adicional, TryParseExact permite especificar estricto DateTimeStyles
para que sepas exactamente si se ha pasado o no una cadena de fecha/hora adecuada.
Más ejemplos de uso
La función sobrecargada le permite pasar una variedad de formatos válidos utilizados para analizar/convertir fechas como se muestra aquí también ( TryParseExact
admite esto directamente), por ejemplo
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);
Si solo tienes unos pocos patrones de plantilla, también puedes escribir:
var dateStr = "2011-03-21 13:26";
var dt = dateStr.ToDate("yyyy-MM-dd HH:mm", "M/d/yyyy h:mm:ss tt");
Ejemplos avanzados
Puede utilizar el ??
operador para establecer de forma predeterminada un formato a prueba de fallos, por ejemplo
var dtStr = "2017-12-30 11:37:00";
var dt = (dtStr.ToDate()) ?? dtStr.ToDate("yyyy-MM-dd HH:mm:ss");
En este caso, .ToDate()
usaría formatos de fecha culturales locales comunes y, si todos fallaran, intentaría usar el formato estándar ISO"yyyy-MM-dd HH:mm:ss"
como alternativa. De esta manera, la función de extensión permite "encadenar" diferentes formatos alternativos fácilmente.
Incluso puedes usar la extensión en LINQ, prueba esto (está en .NetFiddle arriba):
var strDateArray = new[] { "15-01-2019", "15.01.2021" };
var patterns=new[] { "dd-MM-yyyy", "dd.MM.yyyy" };
var dtRange = strDateArray.Select(s => s.ToDate(patterns));
dtRange.Dump();
que convertirá las fechas en la matriz sobre la marcha usando los patrones y las volcará a la consola.
Algunos antecedentes sobre TryParseExact
Finalmente, aquí hay algunos comentarios sobre los antecedentes (es decir, la razón por la que lo escribí de esta manera):
Prefiero TryParseExact en este método de extensión, porque evita el manejo de excepciones ; puede leer en el artículo de Eric Lippert sobre excepciones 3) por qué debería usar TryParse en lugar de Parse, lo cito sobre ese tema: 2)
Esta desafortunada decisión de diseño 1) [anotación: permitir que el método Parse genere una excepción] fue tan desconcertante que, por supuesto, el equipo de marcos implementó TryParse poco después , lo que hace lo correcto.
Lo hace, pero TryParse
ambos TryParseExact
siguen siendo mucho menos cómodos de usar: te obligan a usar una variable no inicializada como out
parámetro que no debe ser anulable y mientras conviertes necesitas evaluar el valor de retorno booleano, ya sea que tengas usar una if
declaración inmediatamente o debe almacenar el valor de retorno en una variable booleana adicional para poder realizar la verificación más tarde. Y no puedes simplemente usar la variable de destino sin saber si la conversión fue exitosa o no.
En la mayoría de los casos, solo desea saber si la conversión fue exitosa o no (y, por supuesto, el valor si fue exitosa) , por lo que una variable de destino anulable que mantenga toda la información sería deseable y mucho más elegante, porque toda la información es simplemente almacenado en un solo lugar: que sea consistente y fácil de usar, y mucho menos propenso a errores.
El método de extensión que he escrito hace exactamente eso (también te muestra qué tipo de código tendrías que escribir cada vez si no lo vas a utilizar).
Creo que el beneficio .ToDate(strDateFormat)
es que parece simple y limpio, tan simple como DateTime.Parse
se suponía que era el original, pero con la capacidad de verificar si la conversión fue exitosa y sin generar excepciones.
1) Lo que se quiere decir aquí es que el manejo de excepciones (es decir, un try { ... } catch(Exception ex) { ...}
bloque), que es necesario cuando se utiliza Parse porque generará una excepción si se analiza una cadena no válida, no solo es innecesario en este caso sino también molesto, y complicando tu código. TryParse evita todo esto como se muestra en el ejemplo de código que proporcioné.
2) Eric Lippert es un famoso miembro de StackOverflow y trabajó en Microsoft como desarrollador principal en el equipo del compilador de C# durante un par de años.
3) Desafortunadamente, Microsoft eliminó este artículo de su sitio MSDN. Si alguien lo encuentra en una URL diferente, hágamelo saber y lo actualizaré.
var dateStr = @"2011-03-21 13:26";
var dateTime = DateTime.ParseExact(dateStr, "yyyy-MM-dd HH:mm", CultureInfo.CurrentCulture);
¡Consulte este enlace para ver otras cadenas de formato!
DateTime.Parse() debería funcionar bien para ese formato de cadena. Referencia:
http://msdn.microsoft.com/en-us/library/1k1skd40.aspx#Y1240
¿Te está lanzando una excepción de formato?
Coloque el valor de una cadena legible por humanos en .NET DateTime con un código como este:
DateTime.ParseExact("April 16, 2011 4:27 pm", "MMMM d, yyyy h:mm tt", null);