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;
}
esto está cerca: http://stackoverflow.com/questions/2003088/using-datetime-tryparseexact-without- sabiendo-el-año –
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 –