Ответ 2
Как я TryParse
позже, я всегда TryParse
TryParseExact
методы TryParse
и TryParseExact
. Поскольку они немного громоздки в использовании, я написал метод расширения, который значительно упрощает анализ:
var dtStr = "2011-03-21 13:26";
DateTime? dt = dtStr.ToDate("yyyy-MM-dd HH:mm");
В отличие от Parse
, ParseExact
и т.д. Он не ParseExact
исключение и позволяет проверять через
if (dt.HasValue) {//continue processing } else {//do error handling }
было ли преобразование успешным (в этом случае dt
имеет значение, к dt.Value
вы можете получить доступ через dt.Value
) или нет (в этом случае оно равно null
).
Это даже позволяет использовать элегантные ярлыки, такие как "Элвис" -operator ?.
, например:
int? year = dtStr?.ToDate("yyyy-MM-dd HH:mm")?.Year;
Здесь вы также можете использовать year.HasValue
чтобы проверить, year.HasValue
ли выполнено преобразование, и если оно не удалось, то year
будет содержать значение null
, в противном случае - часть года в дате. Нет исключения, если преобразование не удалось.
Решение: метод расширения .ToDate()
Попробуйте это в .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;
}
}
Некоторая информация о коде
Вы можете спросить, почему я использовал InvariantCulture
вызывая TryParseExact
: это заставляет функцию обрабатывать шаблоны формата всегда одинаково (в противном случае, например, "." Может интерпретироваться как десятичный разделитель в английском, в то время как это разделитель группы или дата разделитель на немецком языке). Напомним, что мы уже запросили строки форматирования на основе культуры несколькими строками ранее, так что здесь все в порядке.
Обновление: .ToDate()
(без параметров) теперь по умолчанию использует все распространенные шаблоны даты/времени в текущей культуре потока.
Обратите внимание, что нам нужен result
и dt
вместе, потому что TryParseExact
не позволяет использовать DateTime?
, который мы намерены вернуть. В С# версии 7 вы могли бы немного упростить функцию ToDate
следующим образом:
// in C#7 only: "DateTime dt;" - no longer required, declare implicitly
if (DateTime.TryParseExact(dateTimeStr, dateFmt,
CultureInfo.InvariantCulture, style, out var dt)) result = dt;
или, если вам нравится еще короче
// 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?;
в каком случае вам не нужны два объявления DateTime? result = null;
DateTime? result = null;
и DateTime dt;
вообще - вы можете сделать это в одной строке кода. (Было бы также разрешено записывать out DateTime dt
вместо out var dt
если вы предпочитаете это).
Я упростил код дальше, используя params
ключевое слово: Теперь вам не нужно перегруженный метод 2 - й больше.
Пример использования
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!");
}
Как вы можете видеть, этот пример просто запрашивает dt.HasValue
чтобы увидеть, было ли преобразование успешным или нет. В качестве дополнительного бонуса, TryParseExact позволяет указать строгий DateTimeStyles
чтобы вы точно знали, была ли передана правильная строка даты/времени или нет.
Больше примеров использования
Перегруженная функция позволяет передавать массив допустимых форматов, используемых для разбора/преобразования дат, как показано здесь также (TryParseExact
напрямую поддерживает это), например
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);
Если у вас есть только несколько шаблонов шаблонов, вы также можете написать:
var dateStr = "2011-03-21 13:26";
var dt = dateStr.ToDate("yyyy-MM-dd HH:mm", "M/d/yyyy h:mm:ss tt");
Расширенные примеры
Вы можете использовать ??
оператор по умолчанию в отказоустойчивом формате, например
var dtStr = "2017-12-30 11:37:00";
var dt = (dtStr.ToDate()) ?? dtStr.ToDate("yyyy-MM-dd HH:mm:ss");
В этом случае .ToDate()
будет использовать распространенные локальные форматы дат культуры, и если все это не удастся, он попытается использовать стандартный формат ISO "yyyy-MM-dd HH:mm:ss"
в качестве запасного варианта. Таким образом, функция расширения позволяет легко "связывать" различные резервные форматы.
Вы даже можете использовать расширение в LINQ, попробуйте это (оно в .NetFiddle выше):
var patterns=new[] { "dd-MM-yyyy", "dd.MM.yyyy" };
(new[] { "15-01-2019", "15.01.2019" }).Select(s => s.ToDate(patterns)).Dump();
который преобразует даты в массиве на лету, используя шаблоны, и выводит их на консоль.
Немного предыстории о TryParseExact
Наконец, вот несколько комментариев об истории вопроса (т.е. причина, по которой я написал это так):
Я предпочитаю TryParseExact в этом методе расширения, потому что вы избегаете обработки исключений - вы можете прочитать в статье Эрика Липперта об исключениях, почему вы должны использовать TryParse, а не Parse, я процитирую его по этой теме: 2)
Это неудачное дизайнерское решение 1) [аннотация: позволить методу Parse генерировать исключение] было настолько досадным, что вскоре после этого команда разработчиков интегрировала TryParse, что делает правильную вещь.
Это так, но TryParse
и TryParseExact
по-прежнему намного менее удобны в использовании: они вынуждают вас использовать неинициализированную переменную в качестве параметра out
который не должен иметь значение NULL, и во время преобразования вам нужно оценить логическое возвращаемое значение - либо вы должны немедленно использовать оператор if
либо вы должны сохранить возвращаемое значение в дополнительной логической переменной, чтобы вы могли выполнить проверку позже. И вы не можете просто использовать целевую переменную, не зная, было ли преобразование успешным или нет.
В большинстве случаев вы просто хотите узнать, было ли преобразование успешным или нет (и, конечно, значение, если оно было успешным), поэтому желательной и намного более элегантной будет целевая переменная, которая хранит всю информацию, потому что вся информация просто хранится в одном месте: это согласованно и просто в использовании, и гораздо менее подвержено ошибкам.
Метод расширения, который я написал, делает именно это (он также показывает, какой код вам придется писать каждый раз, если вы не собираетесь его использовать).
Я считаю, что преимущество .ToDate(strDateFormat)
состоит в том, что он выглядит простым и .ToDate(strDateFormat)
- таким же простым, как предполагалось в оригинальном DateTime.Parse
- но с возможностью проверки успешности преобразования и без исключения.
1) Здесь подразумевается обработка исключений (т. try {... } catch(Exception ex) {...}
Блок try {... } catch(Exception ex) {...}
), который необходим при использовании Parse, потому что он выдаст исключение, если недопустимый Анализ строки - это не только не нужно в этом случае, но и раздражает, и усложняет ваш код. TryParse избегает всего этого, поскольку пример кода, который я предоставил, показывает.
2) Эрик Липперт - известный сотрудник StackOverflow, пару лет проработавший в Microsoft в качестве основного разработчика в команде компилятора С#.