Як я пояснюю пізніше, я завжди віддав би перевагу методам TryParse
та TryParseExact
методам. Оскільки вони трохи громіздкі у використанні, я написав метод розширення, який значно спрощує аналіз
var dtStr = "2011-03-21 13:26";
DateTime? dt = dtStr.ToDate("yyyy-MM-dd HH:mm");
В відміну Parse
, і ParseExact
т.д. , це не кидає виняток, і дозволяє перевірити з допомогою
if (dt.HasValue) { // continue processing } else { // do error handling }
чи вдала конверсія (у цьому випадку dt
має значення, до якого можна отримати доступ dt.Value
), чи ні (в даному випадку це так null
).
Це навіть дозволяє використовувати елегантні ярлики на зразок оператора "Елвіс" ?.
, наприклад:
int? year = dtStr?.ToDate("yyyy-MM-dd HH:mm")?.Year;
Тут ви також 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?
, які ми маємо намір повернути. У C # Версії 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 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
параметр, який не повинен бути нульовим, і під час перетворення вам потрібно оцінити булеве значення повернення - або у вас є негайно використовувати if
оператор або вам потрібно зберегти повернене значення в додатковій булевій змінній, щоб ви змогли зробити перевірку пізніше. І ви не можете просто використовувати цільову змінну, не знаючи, успішна чи ні конверсія.
У більшості випадків ви просто хочете дізнатись, успішна чи ні конверсія (і, звичайно, значення, якщо вона була успішною) , тому нульова цільова змінна, яка зберігає всю інформацію, була б бажаною і набагато більш елегантною - адже вся інформація є просто зберігається в одному місці: Це стійкий і простий у використанні і набагато менше схильний до помилок.
Написаний нами метод розширення робить саме це (він також показує, який код вам доведеться писати кожен раз, якщо ви не збираєтесь його використовувати).
Я вважаю, що користь .ToDate(strDateFormat)
полягає в тому, що вона виглядає простою і чистою - такою ж простою, як і оригінал DateTime.Parse
повинна була бути - але з можливістю перевірити, чи конверсія була успішною, і не кидаючи винятків.
1) Мається на увазі, що обробка винятків (тобто try { ... } catch(Exception ex) { ...}
блок), яка необхідна під час використання Parse, оскільки вона буде викидати виняток, якщо неправомірний рядок буде розібраний - в цьому випадку є не тільки непотрібним, але й дратує, і ускладнення вашого коду. TryParse уникає всього цього, як показує зразок коду, який я надав.
2) Ерік Ліпперт - відомий співробітник StackOverflow і пару років працював у Microsoft головним розробником над командою компілятора C #.