Установите год, который предполагается при анализе значений частичной даты в .NET.
Мне нужно проанализировать строку даты, которая может быть в любом разумном формате. Например:
-
2012-12-25
-
25 december 2012
-
25 dec
-
17:35
Некоторые из этих строк содержат неоднозначные даты, которые могут привести к нескольким возможным значениям DateTime
(например, 25 dec
можно интерпретировать как 2012-12-25
, 2011-12-25
, 1066-12-25
и т.д.).
Способ DateTime.Parse
в настоящее время обрабатывает эти неоднозначные значения, используя текущую системную дату для определения контекста. Поэтому, если текущая дата 26 июля 2012 года, строка 25 dec
предполагается в текущем году и анализируется как 2012-12-25
Как можно изменить это поведение и установить текущий контекст даты?
Ответы
Ответ 1
Единственное, что я могу придумать, это обработать дату. После этого у вас есть строка, и у вас есть год в объекте DateTime. Если строка не содержит год, то установите год самостоятельно.
if(! string.contains(DateTime.Year.toString() ) {
// Set the year yourself
}
Ответ 2
Вы можете попытаться разобраться с вещами IFormatProvider
, но это может занять некоторое время. В качестве быстрого решения я могу предложить метод расширения:
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
Ответ 3
Если вы ожидаете получить "неполную" информацию о дате/времени в различных форматах, вы можете попробовать разбор текста как конкретные разные форматы, наименее подробные для самых подробных. Например:
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);
}
... этот код пытается проанализировать формат месяца/дня в текущей культуре (если у вас есть определенный, независимо от текущей культуры, вы можете заменить "CultureInfo.CurrentCulture" культурой, которая имеет желаемый формат), Если это не удается, предполагается, что текст более подробный и продолжает анализировать его, как обычно.
Если ваша дата/время не является локальной, не используйте DateTimeStyles.AssumeLocal
. Я всегда рекомендую данные даты/времени, которые хранятся в любом виде (например, сериализованные для текста), которые вы всегда используете Universal; потому что вы не знаете, какая культура играла, когда данные были сериализованы. Universal - единственный надежный способ получить данные о дате/времени на уровне игрового поля. В этом случае используйте DateTimeStyles.AssumeUnivesal
.
Ответ 4
У меня была очень похожая проблема. DateTime.Parse или DateTime.TryParse будет считать, что время дня 00:00:00, когда строка не содержит информации о времени суток. Как и в случае с допущением года, нет способа указать другое время суток для использования по умолчанию. Это реальная проблема, так как время для установки этих значений по умолчанию - before метод разбора проходит через все его подробные шаги. В противном случае вам нужно очень мучительно изобрести колесо, чтобы определить, содержит ли строка информацию, которая бы переопределяла значение по умолчанию.
Я посмотрел исходный код DateTime.TryParse, и, как и следовало ожидать, Microsoft уклонилась, чтобы затруднить расширение класса DateTime. Поэтому я подготовил код, который использует отражение, чтобы использовать то, что может, из исходного кода DateTime. Это имеет некоторые существенные недостатки:
- Код, использующий отражение, неудобен
- Код, использующий отражение, вызывает внутренние элементы, которые могут измениться, если обновлена инфраструктура .NET.
- Код, использующий отражение, будет работать медленнее, чем гипотетическая альтернатива, которая не должна была бы использовать отражение
В моем случае я судил, что ничто не может быть более неудобным, чем перерабатывать DateTime.TryParse с нуля. У меня есть модульные тесты, которые будут указывать, изменились ли внутренние члены. И я считаю, что штраф за исполнение в моем случае незначителен.
Мой код ниже. Этот код используется для переопределения часа/минуты/секунды по умолчанию, но я думаю, что его можно легко изменить или расширить, чтобы переопределить год по умолчанию или что-то еще. Код точно подражает внутреннему коду одной из перегрузок внутренней System.DateTimeParse.TryParse(он выполняет реальную работу DateTime.TryParse), хотя для этого мне пришлось использовать неудобную рефлексию. Единственная вещь, отличная от System.DateTimeParse.TryParse, заключается в том, что она назначает по умолчанию час/минута/секунду вместо того, чтобы оставить их в нуле.
Для справки, это метод класса DateTimeParse, который я имитирую
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;
}
И вот мой код
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 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;
}
}
- ИЗМЕНИТЬ -
Впоследствии я понял, что мне нужно сделать то же самое для метода DateTime.TryParseExact. Однако вышеупомянутый подход не работал для TryParseExact, что заставляет меня беспокоиться о том, что этот подход еще более хрупкий, чем я думал. Ну что ж. К счастью, я смог подумать о совершенно другом подходе к TryParseExact, который не использует никакого отражения
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 parameter value.
result = new DateTime(rawResult.Year, rawResult.Month, rawResult.Day,
defaultTime.Hour, defaultTime.Minute, defaultTime.Second);
}
return success;
}