Jak wyjaśnię później, zawsze preferowałbym metody TryParse
i TryParseExact
. Ponieważ są trochę nieporęczne w użyciu, napisałem metodę rozszerzającą, która znacznie ułatwia analizowanie:
var dtStr = "2011-03-21 13:26";
DateTime? dt = dtStr.ToDate("yyyy-MM-dd HH:mm");
W przeciwieństwie Parse
, ParseExact
itd. To nie wyjątek, i pozwala sprawdzić poprzez
if (dt.HasValue) { // continue processing } else { // do error handling }
czy konwersja powiodła się (w tym przypadku dt
ma wartość, do której można uzyskać dostęp przez dt.Value
), czy nie (w tym przypadku jest null
).
Pozwala to nawet na używanie eleganckich skrótów, takich jak operator „Elvis” ?.
, na przykład:
int? year = dtStr?.ToDate("yyyy-MM-dd HH:mm")?.Year;
Tutaj możesz również użyć year.HasValue
do sprawdzenia, czy konwersja się powiodła, a jeśli się nie powiodła, year
będzie zawierać null
, w przeciwnym razie część roku z daty. Nie ma żadnego wyjątku, jeśli konwersja nie powiodła się.
Rozwiązanie: metoda rozszerzenia .ToDate ()
Wypróbuj w .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;
}
}
Trochę informacji o kodzie
Możesz się zastanawiać, dlaczego użyłem InvariantCulture
wywołania TryParseExact
: ma to na celu wymuszenie na funkcji traktowania wzorców formatu zawsze w ten sam sposób (w przeciwnym razie na przykład „.” Może być interpretowane jako separator dziesiętny w języku angielskim, podczas gdy jest to separator grupy lub separator daty w Niemiecki). Przypomnijmy, że już kilka wierszy wcześniej przeszukiwaliśmy ciągi formatu opartego na kulturze, więc tutaj jest w porządku.
Aktualizacja: .ToDate()
(bez parametrów) teraz domyślnie wszystkie typowe wzorce daty / godziny bieżącej kultury wątku.
Zauważ , że potrzebujemy result
i dt
razem, ponieważ TryParseExact
nie pozwala na użycie DateTime?
, które zamierzamy zwrócić. W C # w wersji 7 można ToDate
nieco uprościć funkcję w następujący sposób:
// in C#7 only: "DateTime dt;" - no longer required, declare implicitly
if (DateTime.TryParseExact(dateTimeStr, dateFmt,
CultureInfo.InvariantCulture, style, out var dt)) result = dt;
lub jeśli wolisz jeszcze krócej:
// 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?;
w takim przypadku nie potrzebujesz tych dwóch deklaracji DateTime? result = null;
iw DateTime dt;
ogóle - możesz to zrobić w jednej linii kodu. ( Jeśli wolisz, możesz również pisać out DateTime dt
zamiast out var dt
tego).
Mam uproszczony kod dalej za pomocą params
słowa kluczowego: Teraz już nie potrzebujesz 2 nd przeciążona metoda dłużej.
Przykład użycia
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!");
}
Jak widać, ten przykład po prostu pyta, dt.HasValue
czy konwersja się powiodła, czy nie. Jako dodatkowy bonus TryParseExact pozwala określić ścisłe, DateTimeStyles
dzięki czemu wiesz dokładnie, czy prawidłowy ciąg daty / czasu został przekazany, czy nie.
Więcej przykładów użycia
Przeciążona funkcja umożliwia przekazanie tablicy prawidłowych formatów używanych do analizowania / konwertowania dat, jak pokazano tutaj ( TryParseExact
bezpośrednio to obsługuje), np.
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);
Jeśli masz tylko kilka wzorów szablonów, możesz również napisać:
var dateStr = "2011-03-21 13:26";
var dt = dateStr.ToDate("yyyy-MM-dd HH:mm", "M/d/yyyy h:mm:ss tt");
Zaawansowane przykłady
Możesz użyć ??
operatora, aby domyślnie ustawić format awaryjny, np
var dtStr = "2017-12-30 11:37:00";
var dt = (dtStr.ToDate()) ?? dtStr.ToDate("yyyy-MM-dd HH:mm:ss");
W takim przypadku .ToDate()
użyje typowych formatów dat kultury lokalnej, a jeśli wszystkie te zawiodą, spróbuje użyć standardowego formatu ISO"yyyy-MM-dd HH:mm:ss"
jako rezerwy. W ten sposób funkcja rozszerzenia umożliwia łatwe łączenie różnych formatów rezerwowych.
Możesz nawet użyć rozszerzenia w LINQ, wypróbuj to (jest w .NetFiddle powyżej):
var patterns=new[] { "dd-MM-yyyy", "dd.MM.yyyy" };
(new[] { "15-01-2019", "15.01.2019" }).Select(s => s.ToDate(patterns)).Dump();
który konwertuje daty w tablicy w locie przy użyciu wzorców i zrzuca je do konsoli.
Trochę informacji o TryParseExact
Na koniec kilka komentarzy na temat tła (tj. Powodu, dla którego napisałem to w ten sposób):
Preferuję TryParseExact w tej metodzie rozszerzenia, ponieważ unikasz obsługi wyjątków - możesz przeczytać w artykule Erica Lipperta o wyjątkach, dlaczego warto używać TryParse zamiast Parse, cytuję go na ten temat: 2)
Ta niefortunna decyzja projektowa 1) [adnotacja: pozwolić metodzie Parse zgłosić wyjątek] była tak irytująca, że oczywiście
zespół frameworka zaimplementował TryParse wkrótce potem, co robi dobrze.
Tak, ale TryParse
i TryParseExact
oba są nadal o wiele mniej niż wygodne w użyciu: zmuszają cię do użycia niezainicjowanej zmiennej jako out
parametru, który nie może mieć wartości null, a podczas konwersji musisz ocenić wartość logiczną zwracaną - albo masz aby użyć if
instrukcji natychmiast lub musisz zapisać zwracaną wartość w dodatkowej zmiennej boolowskiej, abyś mógł sprawdzić później. Nie możesz po prostu użyć zmiennej docelowej, nie wiedząc, czy konwersja się powiodła, czy nie.
W większości przypadków chcesz tylko wiedzieć, czy konwersja się powiodła, czy nie (i oczywiście wartość, jeśli się powiodła) , więc zmienna docelowa dopuszczająca wartość null, która przechowuje wszystkie informacje, byłaby pożądana i znacznie bardziej elegancka - ponieważ cała informacja jest po prostu przechowywane w jednym miejscu: jest spójne i łatwe w użyciu oraz znacznie mniej podatne na błędy.
Metoda rozszerzenia, którą napisałem, robi dokładnie to (pokazuje również, jaki rodzaj kodu musiałbyś napisać za każdym razem, jeśli nie zamierzasz go używać).
Uważam, że zaletą .ToDate(strDateFormat)
jest to, że wygląda prosto i DateTime.Parse
przejrzyście - tak prosto, jak miał być oryginał - ale z możliwością sprawdzenia, czy konwersja się powiodła, i bez rzucania wyjątków.
1) Chodzi tutaj o to, że obsługa wyjątków (tj. try { ... } catch(Exception ex) { ...}
Blok) - która jest konieczna, gdy używasz Parse, ponieważ zgłosi wyjątek, jeśli analizowany jest nieprawidłowy ciąg - jest nie tylko niepotrzebna w tym przypadku, ale także irytująca, i komplikowanie kodu. TryParse unika tego wszystkiego, ponieważ pokazuje przykładowy kod, który podałem.
2) Eric Lippert jest znanym pracownikiem StackOverflow i przez kilka lat pracował w firmie Microsoft jako główny programista w zespole kompilatora C #.