2012-07-26 4 views
10

Necesito analizar una cadena de fecha que podría tener cualquier formato razonable. Por ejemplo:Establezca el año que se asume al analizar valores de fecha parciales en .NET

  • 2012-12-25
  • 25 december 2012
  • 25 dec
  • 17:35

Algunas de estas cadenas contienen fechas ambiguas que pueden dar lugar a varios posibles DateTime valores (Ej 25 dec puede interpretarse como 2012-12-25, 2011-12-25, 1066-12-25, etc.)

La manera en que DateTime.Parse maneja actualmente estos valores ambiguos es mediante el uso de la fecha actual del sistema para determinar el contexto. Así que si la fecha actual es 26to de julio de 2012 la cadena 25 dec se supone que es en el año en curso y se analiza como 2012-12-25

¿Es posible de alguna manera de cambiar este comportamiento y establecer el contexto fecha actual a mí mismo?

+1

esto está cerca: http://stackoverflow.com/questions/2003088/using-datetime-tryparseexact-without- sabiendo-el-año –

+0

más información buena aquí también; no es exactamente un duplicado http://stackoverflow.com/questions/2024273/convert-a-two-digit-year-to-a-four-digit-year –

Respuesta

2

Lo único que se me ocurre es procesar la fecha. Luego tiene la cadena y tiene el año en el objeto DateTime. Si la cadena no contiene el año, configure el año usted mismo.

if(! string.contains(DateTime.Year.toString()) { 
    // Set the year yourself 
} 
+2

¿Qué pasa si la cadena es "25 dec 12 '? Entonces el año se establecerá en 2012, pero la cadena no contiene "2012". –

+2

'DateTime.Year' devuelve el año actual como 4 dígitos, por lo que no se aplicará ninguna fecha de entrada con un año de 2 dígitos. La comprobación de solo dos dígitos será ambiguo con meses/fechas en algunos casos. –

+1

No es una mala solución. Mi preocupación es que el año actual podría existir legítimamente en otro lugar de la cadena de fecha. Por ejemplo, "2008-11-01T19: 35: 00.2012000Z" – Wheelie

1

Se podría tratar de hacer frente a IFormatProvider cosas, pero que puede tomar un tiempo. Como una solución rápida que puedo proponer un método de extensión:

public static class MyDateTimeStringExtensions 
{ 
    public static DateTime ToDateTimeWithYear(this string source, int year) 
    { 
     var dateTime = DateTime.Parse(source); 

     return dateTime.AddYears(year - dateTime.Year); 
    } 
} 
.... 
"2/2".ToDateTimeWithYear(2001) // returns 2/2/2001 12:00:00 AM 
1

Si esperas para obtener información de fecha/hora "incompleta" en varios formatos, podría intentar analizar el texto como formatos específicos diferentes menos detalladas a nación más detallado. Por ejemplo:

var text = "June 15"; 
DateTime datetime; 
if(DateTime.TryParseExact(text, "m", CultureInfo.CurrentCulture, DateTimeStyles.AssumeLocal, out datetime)) 
{ 
    // was just month, day, replace year with specific value: 
    datetime = new DateTime(1966, datetime.Month, datetime.Day); 
} 
else 
{ 
    // wasn't just month, day, try parsing a "whole" date/time: 
    datetime = DateTime.Parse(text); 
} 

... este código intenta analizar el formato mes/día en la cultura actual (si tiene uno específico, independiente de la cultura actual, se puede sustituir "CultureInfo.CurrentCulture" con una cultura que tiene el formato que desea). Si eso falla, se supone que el texto es más detallado y pasa a analizarlo como lo haría normalmente.

Si su fecha/hora no son locales, no use DateTimeStyles.AssumeLocal. Siempre recomiendo que los datos de fecha/hora que se almacenan de cualquier manera (como serializados en texto) que siempre use Universal; porque no sabes qué cultura estaba en juego cuando los datos fueron serializados. Universal es la única forma confiable de obtener datos de fecha/hora en un campo de juego nivelado. En cuyo caso use DateTimeStyles.AssumeUnivesal.

1

Tuve un problema muy similar. DateTime.Parse o DateTime.TryParse supondrán que la hora del día es 00:00:00 cuando la cadena no contiene información de hora del día. Al igual que con la suposición de año, no hay forma de especificar una hora del día diferente para usar como predeterminada. Este es un problema real, porque el tiempo para establecer estos valores predeterminados es antes de el método de análisis sigue todos sus pasos detallados. De lo contrario, debe reinventar la rueda con mucho dolor para determinar si la cadena contiene información que anularía la predeterminada.

Miré el código fuente de DateTime.TryParse, y como es de esperar, Microsoft ha hecho todo lo posible para dificultar la extensión de la clase DateTime. Así que he preparado un código que usa la reflexión para aprovechar lo que pueda del código fuente de DateTime. Esto tiene algunos inconvenientes importantes:

  • El código de reflexión que utilizan es incómoda
  • El código reflexión mediante llamadas miembros internos que podrían cambiar si se actualiza el marco .Net
  • La reflexión utilizando el código se ejecutará más lento que una alternativa hipotética que no necesitaba usar el reflejo

En mi caso, consideré que nada podría ser más incómodo que tener que reiniciar DateTime.TryParse desde cero. Tengo pruebas unitarias que indicarán si los miembros internos han cambiado. Y creo que la pena de rendimiento es insignificante en mi caso.

Mi código está por debajo. Este código se usa para anular la hora/minuto/segundo predeterminados, pero creo que podría modificarse o expandirse fácilmente para anular el año predeterminado u otra cosa. El código imita fielmente el código interno de una de las sobrecargas del System.DateTimeParse.TryParse interno (hace el trabajo real de DateTime.TryParse), aunque tuve que usar el reflejo incómodo para hacerlo. Lo único efectivamente diferente de System.DateTimeParse.TryParse es que asigna una hora/minuto/segundo predeterminados en lugar de dejarlos a cero.

Como referencia, este es el método de DateTimeParse clase que estoy imitando

 internal static bool TryParse(String s, DateTimeFormatInfo dtfi, DateTimeStyles styles, out DateTime result) { 
     result = DateTime.MinValue; 
     DateTimeResult resultData = new DateTimeResult(); // The buffer to store the parsing result. 
     resultData.Init(); 
     if (TryParse(s, dtfi, styles, ref resultData)) { 
      result = resultData.parsedDate; 
      return true; 
     } 
     return false; 
    } 

Y aquí es mi código

public static class TimeExtensions 
{ 
    private static Assembly _sysAssembly; 
    private static Type _dateTimeParseType, _dateTimeResultType; 
    private static MethodInfo _tryParseMethod, _dateTimeResultInitMethod; 
    private static FieldInfo _dateTimeResultParsedDateField, 
       _dateTimeResultHourField, _dateTimeResultMinuteField, _dateTimeResultSecondField; 
    /// <summary> 
    /// This private method initializes the private fields that store reflection information 
    /// that is used in this class. The method is designed so that it only needs to be called 
    /// one time. 
    /// </summary> 
    private static void InitializeReflection() 
    { 
     // Get a reference to the Assembly containing the 'System' namespace 
     _sysAssembly = typeof(DateTime).Assembly; 
     // Get non-public types of 'System' namespace 
     _dateTimeParseType = _sysAssembly.GetType("System.DateTimeParse"); 
     _dateTimeResultType = _sysAssembly.GetType("System.DateTimeResult"); 
     // Array of types for matching the proper overload of method System.DateTimeParse.TryParse 
     Type[] argTypes = new Type[] 
     { 
      typeof(String), 
      typeof(DateTimeFormatInfo), 
      typeof(DateTimeStyles), 
      _dateTimeResultType.MakeByRefType() 
     }; 
     _tryParseMethod = _dateTimeParseType.GetMethod("TryParse", 
       BindingFlags.Static | BindingFlags.NonPublic, null, argTypes, null); 
     _dateTimeResultInitMethod = _dateTimeResultType.GetMethod("Init", 
       BindingFlags.Instance | BindingFlags.NonPublic); 
     _dateTimeResultParsedDateField = _dateTimeResultType.GetField("parsedDate", 
       BindingFlags.Instance | BindingFlags.NonPublic); 
     _dateTimeResultHourField = _dateTimeResultType.GetField("Hour", 
       BindingFlags.Instance | BindingFlags.NonPublic); 
     _dateTimeResultMinuteField = _dateTimeResultType.GetField("Minute", 
       BindingFlags.Instance | BindingFlags.NonPublic); 
     _dateTimeResultSecondField = _dateTimeResultType.GetField("Second", 
       BindingFlags.Instance | BindingFlags.NonPublic); 
    } 
    /// <summary> 
    /// This method converts the given string representation of a date and time to its DateTime 
    /// equivalent and returns true if the conversion succeeded or false if no conversion could be 
    /// done. The method is a close imitation of the System.DateTime.TryParse method, with the 
    /// exception that this method takes a parameter that allows the caller to specify what the time 
    /// value should be when the given string contains no time-of-day information. In contrast, 
    /// the method System.DateTime.TryParse will always apply a value of midnight (beginning of day) 
    /// when the given string contains no time-of-day information. 
    /// </summary> 
    /// <param name="s">the string that is to be converted to a DateTime</param> 
    /// <param name="result">the DateTime equivalent of the given string</param> 
    /// <param name="defaultTime">a DateTime object whose Hour, Minute, and Second values are used 
    /// as the default in the 'result' parameter. If the 's' parameter contains time-of-day 
    /// information, then it overrides the value of 'defaultTime'</param> 
    public static Boolean TryParse(String s, out DateTime result, DateTime defaultTime) 
    { 
     // Value of the result if no conversion can be done 
     result = DateTime.MinValue; 
     // Create the buffer that stores the parsed result 
     if (_sysAssembly == null) InitializeReflection(); 
     dynamic resultData = Activator.CreateInstance(_dateTimeResultType); 
     _dateTimeResultInitMethod.Invoke(resultData, new Object[] { }); 
     // Override the default time values of the buffer, using this method's parameter 
     _dateTimeResultHourField.SetValue(resultData, defaultTime.Hour); 
     _dateTimeResultMinuteField.SetValue(resultData, defaultTime.Minute); 
     _dateTimeResultSecondField.SetValue(resultData, defaultTime.Second); 
     // Create array parameters that can be passed (using reflection) to 
     // the non-public method DateTimeParse.TryParse, which does the real work 
     Object[] tryParseParams = new Object[] 
     { 
      s, DateTimeFormatInfo.CurrentInfo, DateTimeStyles.None, resultData 
     }; 
     // Call non-public method DateTimeParse.TryParse 
     Boolean success = (Boolean)_tryParseMethod.Invoke(null, tryParseParams); 
     if (success) 
     { 
      // Because the DateTimeResult object was passed as a 'ref' parameter, we need to 
      // pull its new value out of the array of method parameters 
      result = _dateTimeResultParsedDateField.GetValue((dynamic)tryParseParams[3]); 
      return true; 
     } 
     return false; 
    } 
} 

- EDITAR - posteriormente me di cuenta de que yo necesita hacer lo mismo para el método DateTime.TryParseExact. Sin embargo, el enfoque anterior no funcionó para TryParseExact, lo que me lleva a preocuparme de que el enfoque sea aún más frágil de lo que pensaba. Oh bien. Felizmente, yo era capaz de pensar en un enfoque muy diferente para TryParseExact que no utiliza ninguna reflexión

 public static Boolean TryParseExact(String s, String format, IFormatProvider provider, 
          DateTimeStyles style, out DateTime result, DateTime defaultTime) 
    { 
     // Determine whether the format requires that the time-of-day is in the string to be converted. 
     // We do this by creating two strings from the format, which have the same date but different 
     // time of day. If the two strings are equal, then clearly the format contains no time-of-day 
     // information. 
     Boolean willApplyDefaultTime = false; 
     DateTime testDate1 = new DateTime(2000, 1, 1, 2, 15, 15); 
     DateTime testDate2 = new DateTime(2000, 1, 1, 17, 47, 29); 
     String testString1 = testDate1.ToString(format); 
     String testString2 = testDate2.ToString(format); 
     if (testString1 == testString2) 
      willApplyDefaultTime = true; 

     // Let method DateTime.TryParseExact do all the hard work 
     Boolean success = DateTime.TryParseExact(s, format, provider, style, out result); 

     if (success && willApplyDefaultTime) 
     { 
      DateTime rawResult = result; 
      // If the format contains no time-of-day information, then apply the default from 
      // this method's parameter value. 
      result = new DateTime(rawResult.Year, rawResult.Month, rawResult.Day, 
          defaultTime.Hour, defaultTime.Minute, defaultTime.Second); 
     } 
     return success; 
    } 
Cuestiones relacionadas