Updated on
C# has four methods for turning a string into a DateTime, and the names tell us what they do. Parse() and TryParse() differ only in how they fail: Parse() reads whatever the culture allows and throws on failure, and TryParse() does the same and returns false instead. ParseExact() requires a format string we supply. TryParseExact() requires that format string and returns false instead of throwing.
Two questions pick one. Do we know the exact format? Then the name contains Exact. Can the input be invalid? Then the name starts with Try. Most real code answers yes to both, which is TryParseExact().
Everything else is overloads. All four take an optional IFormatProvider to fix the culture and a DateTimeStyles value to control how missing time-zone information is treated, and each has a ReadOnlySpan<char> variant that allocates less.
Please refer to the DateTime format strings in C# to learn more about formatting.
Each method has a list of overloads. Let’s see each of these methods and their overloads more closely. But before then, let’s learn more about customizing culture-related information when working with DateTime objects.
What Do CultureInfo and DateTimeStyles Do When Parsing?
When we use the methods in the DateTime class to parse, we need the System.Globalization namespace as it contains classes that define culture-related information, such as the CultureInfo class, and the DateTimeStyles enum, which we will find helpful when parsing.
Current Culture Settings
The combination of a language and a region is known as a culture. The culture settings of our operating system determine the default date and time format.
We can check our current culture using CultureInfo.CurrentCulture and define a different culture using the GetCultureInfo() method if we want to:
DateTime date = new DateTime(2023, 1, 15, 14, 21, 37);
Console.WriteLine($"My current culture: {CultureInfo.CurrentCulture}");
Console.WriteLine($"My date in the current culture({CultureInfo.CurrentCulture}): {date}");
CultureInfo culture = CultureInfo.GetCultureInfo("en-GB");
Console.WriteLine($"My date in the culture({culture}): {date.ToString(culture)}");
The current culture should be en-US if we’re based in the US, and the dates are shown in different formats depending on the culture:
My current culture: en-US My date in the current culture(en-US): 1/15/2023 2:21:37 PM My date in the culture(en-GB): 15/01/2023 14:21:37
Another way to specify a different culture is to use the CultureInfo class that implements the IFormatProvider interface. Let’s replace the GetCultureInfo() method with a new CultureInfo instance and use the type IFormatProvider:
DateTime date = new DateTime(2023, 1, 15, 14, 21, 37);
Console.WriteLine($"My current culture: {CultureInfo.CurrentCulture}");
Console.WriteLine($"My date in the current culture({CultureInfo.CurrentCulture}): {date}");
IFormatProvider provider = new CultureInfo("en-GB");
Console.WriteLine($"My date in the culture({provider}): {date.ToString(culture)}");
The result should be the same:
My current culture: en-US My date in the current culture(en-US): 1/15/2023 2:21:37 PM My date in the culture(en-GB): 15/01/2023 14:21:37
We’ll use the IFormatProvider interface frequently to define a specific culture in some parsing methods later.
The DateTimeStyles
The DateTimeStyles enum provides formatting options that customize string parsing for some date and time parsing methods. We’ll use a few of the DateTimeStyles enum options for demonstrations later.
A DateTime parsed from a string carrying Z or an offset comes back with its Kind set to Local, converted to the machine’s time zone, unless we ask otherwise. DateTimeStyles.RoundtripKind keeps a Z string as UTC, but it does not rescue an arbitrary offset: +05:00 still arrives converted to local time. DateTimeStyles.AdjustToUniversal is the reliable one, because it converts to UTC and marks the value as such. It is worth knowing what Kind means for the value we get back, and parsing into a DateTimeOffset instead avoids the choice entirely.
How Does DateTime.Parse() Work in C#?
DateTime.Parse() reads a string in whatever shape the current culture allows and returns a DateTime. When the string does not fit, it throws a FormatException.
Culture is the whole story here. The string 15/1/2023 parses under en-GB, where 15 is a day, and throws under en-US, where 15 would have to be a month. With no IFormatProvider argument the method uses CultureInfo.CurrentCulture, which is the machine’s regional setting, so the same code can parse on a laptop and throw on a server.
Five overloads cover string and ReadOnlySpan<char> input, each optionally with an IFormatProvider, and the fullest of them also with a DateTimeStyles value.
Use Parse() only when the input is already trusted, such as a value our own code wrote out earlier. For anything arriving from a person, a file, or an API, TryParse() is the method to reach for.
One thing to keep in mind while reading the output of every example below: Console.WriteLine() formats a DateTime with the current culture whatever culture parsed it, so the printed shape is the machine’s, not the parser’s. The outputs shown here assume a current culture of en-US, and the ones that convert a time zone also assume a machine an hour ahead of UTC.
Parse(String)
This Parse(String) overload has a string parameter representing the date and time to be parsed. By default, it uses the operating system’s current culture and the DateTimeStyle.None option.
Let’s assume the default culture is en-US:
string dateString = "1/15/2023 02:21:37"; DateTime parsedDate = DateTime.Parse(dateString); Console.WriteLine(parsedDate); /* Output: 1/15/2023 2:21:37 AM */
The dateString variable stores a string representation of date and time in the format of MM/dd/yyyy hh:mm:ss. We pass this variable to the Parse() method, and it should parse it to a DateTime object with the format of M/dd/yyyy hh:mm:ss tt, corresponding to the en-US culture.
If we try to modify the value in the dateString variable to 15/1/2023, it will throw a FormatException:
String '15/1/2023' was not recognized as a valid DateTime.
This is because the parsed DateTime is not valid in the en-US culture as 15 is not considered a valid month.
Parse(String, IFormatProvider)
The previous overload doesn’t allow us to provide a different culture and uses the operating system’s current culture by default. Thankfully, this overload lets us set a culture-specific format using the IFormatProvider parameter.
Let’s try to set the culture to fr-FR and use the string 15/1/2023 02:21:37 this time. Because the new culture will see 15 as an invalid month if we use 1/15/2023 02:21:37:
string dateString = "15/1/2023 02:21:37";
IFormatProvider provider = new CultureInfo("fr-FR");
DateTime parsedDate = DateTime.Parse(dateString, provider);
Console.WriteLine(parsedDate); /* Output: 15/01/2023 2:21:37 AM */
We should expect a parsed DateTime of 15/01/2023 2:21:37 AM.
Parse(String, IFormatProvider, DateTimeStyles)
This overload has an additional DateTimeStyles parameter that allows us to customize string parsing.
Let’s use the DateTimeStyles.AssumeUniversal for this example:
string dateString = "15/1/2023 02:21:37";
IFormatProvider provider = new CultureInfo("fr-FR");
DateTimeStyles styles = DateTimeStyles.AssumeUniversal;
DateTime parsedDate = DateTime.Parse(dateString, provider, styles);
Console.WriteLine(parsedDate); /* Output: 15/01/2023 1:21:37 PM */
Since we don’t specify the time zone, the string format is assumed to be UTC. The parsed DateTime will be 15/01/2023 1:21:37 PM.
Parse(ReadOnlySpan<Char>, IFormatProvider)
This method’s overload is similar to the Parse(String, IFormatProvider) overload, but it has a ReadOnlySpan<Char> parameter instead of a string parameter.
The only difference here is to create a new read-only span over a string using the AsSpan() method:
IFormatProvider provider = new CultureInfo("fr-FR");
DateTime parsedDate = DateTime.Parse("15/1/2023 02:21:37".AsSpan(), provider);
Console.WriteLine(parsedDate); /* Output: 15/1/2023 2:21:37 AM */
We should have a similar parsed result of 15/01/2023 2:21:37 AM.
This is a great option to allocate less memory and improve performance, but only when the source is already a span or a slice of a larger string. Calling AsSpan() on a string we already hold, as the example above does, saves nothing. To learn more ways of converting a string to a span, please check out How to Convert a String to a Span in C#.
Parse(ReadOnlySpan<Char>, IFormatProvider, DateTimeStyles)
This overload works the same way as the Parse(String, IFormatProvider, DateTimeStyles) overload, but it uses a ReadOnlySpan<Char> parameter instead.
We’ll set a similar example by using the DateTimeStyles.AssumeUniversal but converting the string to a span this time:
ReadOnlySpan<char> dateSpan = "15/1/2023 02:21:37";
IFormatProvider provider = new CultureInfo("fr-FR");
DateTimeStyles styles = DateTimeStyles.AssumeUniversal;
DateTime parsedDate = DateTime.Parse(dateSpan, provider, styles);
Console.WriteLine(parsedDate); /* Output: 15/01/2023 1:21:37 PM */
The parsed DateTime should be similar too, 15/01/2023 1:21:37 PM.
How Does DateTime.TryParse() Work in C#?
DateTime.TryParse() does what Parse() does and reports failure with a false return value instead of an exception.
The parsed value comes back through an out parameter. On success it holds the date; on failure it holds DateTime.MinValue rather than anything it held before, so the boolean is the only thing worth branching on.
Six overloads mirror Parse() across string and ReadOnlySpan<char> input, with the same optional IFormatProvider and DateTimeStyles arguments.
The reason to prefer it is cost, not taste. Throwing and catching a FormatException is expensive on a hot path, and invalid input is normal input once the string comes from outside our program. TryParse() treats a bad date as a result to handle rather than an emergency to unwind.
It is still culture-driven, though, so a date the user typed in their own format on a differently configured server fails silently instead of loudly.
TryParse(String, DateTime)
This is the simplest TryParse() overload with two parameters: a string and a DateTime, which is an out parameter. If we pass a valid string representation of date and time, it will return true along with the converted DateTime object. Otherwise, it will return false with the minimum DateTime value:
string dateString = "1/15/2023";
DateTime parsedDate;
bool isValidDate = DateTime.TryParse(dateString, out parsedDate);
if (isValidDate)
Console.WriteLine(parsedDate); /* Output: 1/15/2023 12:00:00 AM */
else
Console.WriteLine($"Unable to parse date:{dateString}");
Considering the current culture is en-US, the TryParse() method returns true and the parsedDate variable stores the DateTime 1/15/2023 12:00:00 AM.
TryParse(String, IFormatProvider, DateTime)
This overload has an additional IFormatProvider parameter that allows us to set a culture-specific format.
Let’s try to set the culture to en-GB and use the string 15/1/2023 because the new culture will see 15 as an invalid month if we use 1/15/2023:
string dateString = "15/1/2023";
DateTime parsedDate;
IFormatProvider provider = new CultureInfo("en-GB");
bool isValidDate = DateTime.TryParse(dateString, provider, out parsedDate);
if (isValidDate)
Console.WriteLine(parsedDate); /* Output: 15/01/2023 12:00:00 AM */
else
Console.WriteLine($"Unable to parse date:{dateString}");
We should expect the isValidDate variable to contain a true value along with a parsed DateTime of 15/01/2023 12:00:00 AM in the parsedDate variable. In contrast, if we pass the string 1/15/2023, it will return false with a minimum DateTime value.
TryParse(String, IFormatProvider, DateTimeStyles, DateTime)
This TryParse() overload has an additional DateTimeStyles parameter that allows us to customize string parsing.
Let’s use the DateTimeStyles.AdjustToUniversal for this example:
string dateString = "2023/1/15 14:21:37-05:00";
DateTime parsedDate;
IFormatProvider provider = new CultureInfo("en-GB");
DateTimeStyles styles = DateTimeStyles.AdjustToUniversal;
bool isValidDate = DateTime.TryParse(dateString, provider, styles, out parsedDate);
if (isValidDate)
Console.WriteLine(parsedDate); /* Output: 15/01/2023 7:21:37 PM */
else
Console.WriteLine($"Unable to parse date:{dateString}");
Using AdjustToUniversal as the DateTimeStyle option means that the parsedDate variable will receive the date and time in Coordinated Universal Time (UTC) format. If we pass a date and time with the EST time zone, we should get a parsed DateTime 15/01/2012 7:21:37 PM.
TryParse(ReadOnlySpan<Char>, DateTime)
This overload is similar to the TryParse(String, DateTime) overload, but it has a ReadOnlySpan<Char> parameter instead of a string parameter.
The only difference here is to create a new read-only span over a string:
ReadOnlySpan<char> dateSpan = "1/15/2023";
DateTime parsedDate;
bool isValidDate = DateTime.TryParse(dateSpan, out parsedDate);
if (isValidDate)
Console.WriteLine(parsedDate); /* Output: 1/15/2023 12:00:00 AM */
else
Console.WriteLine($"Unable to parse date:{dateSpan}");
Since the current culture is en-US and if the conversion is successful, the parse method returns true and the parsedDate variable stores the DateTime 1/15/2023 12:00:00 AM.
TryParse(ReadOnlySpan<Char>, IFormatProvider, DateTime)
This method’s overload works the same way as the TryParse(String, IFormatProvider, DateTime) overload, but it uses a ReadOnlySpan<Char> parameter instead.
We’ll use the same example but convert the string to a span this time:
ReadOnlySpan<char> dateSpan = "15/1/2023";
DateTime parsedDate;
IFormatProvider provider = new CultureInfo("en-GB");
bool isValidDate = DateTime.TryParse(dateSpan, provider, out parsedDate);
if (isValidDate)
Console.WriteLine(parsedDate); /* Output: 15/01/2023 12:00:00 AM */
else
Console.WriteLine($"Unable to parse date:{dateSpan}");
The parsed DateTime should be similar, 15/01/2023 12:00:00 AM.
TryParse(ReadOnlySpan<Char>, IFormatProvider, DateTimeStyles, DateTime)
Finally, this overload isn’t too different from the TryParse(String, IFormatProvider, DateTimeStyles) overload, the only difference is it uses a ReadOnlySpan<Char> parameter.
We’ll also use the same example but with a span. To keep it simple, we’ll set the DateTimeStyles.None option this time:
ReadOnlySpan<char> dateSpan = "15/1/2023";
DateTime parsedDate;
IFormatProvider provider = new CultureInfo("en-GB");
DateTimeStyles styles = DateTimeStyles.None;
bool isValidDate = DateTime.TryParse(dateSpan, provider, styles, out parsedDate);
if (isValidDate)
Console.WriteLine(parsedDate); /* Output: 15/01/2023 12:00:00 AM */
else
Console.WriteLine($"Unable to parse date:{dateSpan}");
Since we don’t set a specific DateTimeStyle, the parsed DateTime should be 15/01/2023 12:00:00 AM.
How Does DateTime.ParseExact() Work in C#?
DateTime.ParseExact() takes the format string as an argument and accepts nothing else. The input must match it character for character, or the method throws a FormatException.
That strictness is the point. The format dd/M/yyyy hh:mm:ss parses 15/1/2023 10:12:12, and the same input against dd/MM/yyyy hh:mm:ss throws, because a one-digit month does not fill a two-character slot.
The format string is the same vocabulary used for output, so the string that writes a value out also reads it back in.
One overload takes a string[] of formats instead of a single one and tries each in turn until one matches, which is the right tool for a file whose rows were not all written by the same program.
Reach for ParseExact() when the input has a contract: a log line, a fixed-width feed, an ISO 8601 timestamp. Reach for Parse() when it does not.
ParseExact(String, String, IFormatProvider)
This overload has three input parameters: two strings and an IFormatProvider. The first parameter is a string representation of date and time, the second one defines the exact format, and we can set a culture-specific format using the last parameter.
Let’s see how it works:
string dateString = "15/1/2023 10:12:12";
string format = "dd/M/yyyy hh:mm:ss";
DateTime parsedDate;
IFormatProvider provider = new CultureInfo("fr-FR");
try
{
parsedDate = DateTime.ParseExact(dateString, format, provider);
Console.WriteLine(parsedDate); /* Output: 15/01/2023 10:12:12 AM */
}
catch(FormatException ex)
{
Console.WriteLine(ex.Message);
}
The dateString variable receives the string representation of date and time 15/1/2023 10:12:12, with the format of dd/M/yyyy hh:mm:ss. The ParseExact() method parses the string into the same instant the format describes, and the printed form then follows the current culture. The format vocabulary is the same one ToString() uses to write a DateTime out.
Note that the format of the string representation must match the specified format exactly. Else it will throw a FormatException if we try to edit the format slightly differently, for example dd/MM/yyyy hh:mm:ss:
String '15/1/2023 10:12:12' was not recognized as a valid DateTime.
ParseExact(String, String, IFormatProvider, DateTimeStyles)
Unlike the prior overload, we have an additional DateTimeStyles parameter for this ParseExact() overload. Let’s try to round-trip a DateTime value this time:
string dateString = "2023-01-15T14:12:12.0000000Z";
DateTime parsedDate;
IFormatProvider provider = new CultureInfo("fr-FR");
DateTimeStyles styles = DateTimeStyles.RoundtripKind;
try
{
parsedDate = DateTime.ParseExact(dateString, "o", provider, styles);
Console.WriteLine(parsedDate); /* Output: 15/01/2023 2:12:12 PM */
}
catch (FormatException ex)
{
Console.WriteLine(ex.Message);
}
The dateString variable receives the string representation of date and time 2023-01-15T14:12:12.0000000Z. This represents an ISO 8601 string which includes time-zone information.
We use the o round-tripping format specifier to specify the format and the RoundtripKind DateTimeStyle.
We should expect a converted DateTime in fr-FR format 15/01/2023 2:12:12 PM.
ParseExact(ReadOnlySpan<Char>, ReadOnlySpan<Char>, IFormatProvider, DateTimeStyles)
This overload is quite similar to the previous overload, but this uses ReadOnlySpan<char> instead of a string. We’ll create a new read-only span over a string using implicit conversion from a string:
ReadOnlySpan<char> dateSpan = "2023-01-15T14:12:12.0000000Z";
DateTime parsedDate;
IFormatProvider provider = new CultureInfo("fr-FR");
DateTimeStyles styles = DateTimeStyles.RoundtripKind;
try
{
parsedDate = DateTime.ParseExact(dateSpan, "o", provider, styles);
Console.WriteLine(parsedDate); /* Output: 15/01/2023 2:12:12 PM */
}
catch (FormatException ex)
{
Console.WriteLine(ex.Message);
}
It should produce the same result by passing similar arguments as the previous example to this overload. Using span is a good option to allocate less memory and improve performance.
ParseExact(String, String[], IFormatProvider, DateTimeStyles)
This method’s overload is similar to the ParseExact(String, String, IFormatProvider, DateTimeStyles) overload, but the second parameter can accept an array of exact date formats this time:
string[] formats = { "yyyy-MM-dd'T'HH:mm:ss.fff'Z'", "yyyy-MM-dd'T'HH:mm:ss'Z'",
"yyyyMMdd'T'HH:mm:ss.fff'Z'", "yyyyMMdd'T'HH:mm:ss'Z'", "dd-MM-yyyy HH:mm:ss", "dd/MM/yyyy HH:mm:ss" };
IFormatProvider provider = CultureInfo.InvariantCulture;
DateTimeStyles styles = DateTimeStyles.None;
var dateStringsByFormat = new Dictionary<string, string>
{
{ "yyyy-MM-dd'T'HH:mm:ss.fff'Z'", "2023-01-15T14:12:12.000Z" },
{ "yyyy-MM-dd'T'HH:mm:ss'Z'", "2023-01-15T14:12:12Z" },
{ "yyyyMMdd'T'HH:mm:ss.fff'Z'", "20230115T14:12:12.000Z" },
{ "yyyyMMdd'T'HH:mm:ss'Z'", "20230115T14:12:12Z" },
{ "dd-MM-yyyy HH:mm:ss", "15-01-2023 14:12:12" },
{ "dd/MM/yyyy HH:mm:ss", "15/01/2023 14:12:12" }
};
try
{
foreach (string format in formats)
{
string dateString = dateStringsByFormat[format];
DateTime parsedDate = DateTime.ParseExact(dateString, formats, provider, styles);
Console.WriteLine(parsedDate);
}
}
catch (FormatException ex)
{
Console.WriteLine(ex.Message);
}
We provide multiple formats in a string array and then create a collection of key-value pairs representing the format and the string representation of date and time. The list of keys is similar to the ones in the formats array.
To demonstrate a simpler example, we use an InvariantCulture culture and do not specify a DateTimeStyle.
We then loop through the array and get the string representation of date and time from the list of key-value pairs using format as the key. We pass the string as the first argument and the formats array as the second argument to the overload. The parsing should be successful if the array of allowable formats recognizes the string format.
ParseExact(ReadOnlySpan<Char>, String[], IFormatProvider, DateTimeStyles)
For the last overload for the DateTime.ParseExact() method, it’s similar to the previous overload, but the first parameter takes a ReadOnlySpan<char> instead of a string:
string[] formats = { "yyyy-MM-dd'T'HH:mm:ss.fff'Z'", "yyyy-MM-dd'T'HH:mm:ss'Z'",
"yyyyMMdd'T'HH:mm:ss.fff'Z'", "yyyyMMdd'T'HH:mm:ss'Z'", "dd-MM-yyyy HH:mm:ss", "dd/MM/yyyy HH:mm:ss" };
IFormatProvider provider = CultureInfo.InvariantCulture;
DateTimeStyles styles = DateTimeStyles.None;
var dateStringsByFormat = new Dictionary<string, string>
{
{ "yyyy-MM-dd'T'HH:mm:ss.fff'Z'", "2023-01-15T14:12:12.000Z" },
{ "yyyy-MM-dd'T'HH:mm:ss'Z'", "2023-01-15T14:12:12Z" },
{ "yyyyMMdd'T'HH:mm:ss.fff'Z'", "20230115T14:12:12.000Z" },
{ "yyyyMMdd'T'HH:mm:ss'Z'", "20230115T14:12:12Z" },
{ "dd-MM-yyyy HH:mm:ss", "15-01-2023 14:12:12" },
{ "dd/MM/yyyy HH:mm:ss", "15/01/2023 14:12:12" }
};
try
{
foreach (string format in formats)
{
ReadOnlySpan<char> dateSpan = dateStringsByFormat[format];
DateTime parsedDate = DateTime.ParseExact(dateSpan, formats, provider, styles);
Console.WriteLine(parsedDate);
}
}
catch (FormatException ex)
{
Console.WriteLine(ex.Message);
}
We use similar values as the previous example, but this time we’ll first create a new read-only span over a string before parsing.
How Does DateTime.TryParseExact() Work in C#?
DateTime.TryParseExact() is ParseExact() with TryParse()’s failure behaviour: the exact-format requirement, and a false return in place of a FormatException.
It is the method most readers of this article actually want, because it is the only one of the four that is both strict about shape and cheap about failure. Checking that somebody typed a date as dd-MM-yyyy is one call and one boolean.
Four overloads exist and all of them take an IFormatProvider and a DateTimeStyles value; there is no short form. Two take a single format string, two take a string[] of candidates, and each pair covers string and ReadOnlySpan<char> input.
Pair it with CultureInfo.InvariantCulture unless the format deliberately contains culture-specific parts such as month names. The invariant culture makes the result depend on the format string alone, which is the whole reason for choosing this method over TryParse().
The examples below keep the arguments simple, using CultureInfo.InvariantCulture and DateTimeStyles.None throughout.
TryParseExact(String, String, IFormatProvider, DateTimeStyles, DateTime)
The first four parameters in this overload are similar to the ones in the ParseExact(String, String, IFormatProvider, DateTimeStyles) overload, but with an additional out DateTime parameter.
Let’s try to parse using this overload:
string dateString = "15-01-2023";
string format = "dd-MM-yyyy";
DateTime parsedDate;
IFormatProvider provider = CultureInfo.InvariantCulture;
DateTimeStyles styles = DateTimeStyles.None;
bool isValidDate = DateTime.TryParseExact(dateString, format, provider, styles, out parsedDate);
if (isValidDate)
Console.WriteLine(parsedDate); /* Output: 1/15/2023 12:00:00 AM */
else
Console.WriteLine($"Unable to parse date:{dateString}");
The dateString variable stores the string representation of the date 15-1-2023, with the format of dd-MM-yyyy. By using the CultureInfo.InvariantCulture and DateTimeStyle.None, we should expect the parsed DateTime to be 1/15/2023 12:00:00 AM.
TryParseExact(ReadOnlySpan<Char>, ReadOnlySpan<Char>, IFormatProvider, DateTimeStyles, DateTime)
This TryParseExact() overload is similar to the previous overload, except that we use ReadOnlySpan<Char> in the first two parameters:
ReadOnlySpan<char> dateSpan = "15-01-2023";
ReadOnlySpan<char> formatSpan = "dd-MM-yyyy";
DateTime parsedDate;
IFormatProvider provider = CultureInfo.InvariantCulture;
DateTimeStyles styles = DateTimeStyles.None;
bool isValidDate = DateTime.TryParseExact(dateSpan, formatSpan, provider, styles, out parsedDate);
if (isValidDate)
Console.WriteLine(parsedDate); /* 1/15/2023 12:00:00 AM */
else
Console.WriteLine($"Unable to parse date:{dateSpan}");
Using the same example, we should expect a similar result of a valid parsed DateTime of 1/15/2023 12:00:00 AM.
TryParseExact(String, String[], IFormatProvider, DateTimeStyles, DateTime)
We’ve previously introduced the string array parameter that accepts multiple formats in the ParseExact(String, String[], IFormatProvider, DateTimeStyles) overload. This overload is similar but returns an out parameter.
We’ll be using the same example:
string[] formats = { "yyyy-MM-dd'T'HH:mm:ss.fff'Z'", "yyyy-MM-dd'T'HH:mm:ss'Z'",
"yyyyMMdd'T'HH:mm:ss.fff'Z'", "yyyyMMdd'T'HH:mm:ss'Z'", "dd-MM-yyyy HH:mm:ss", "dd/MM/yyyy HH:mm:ss" };
IFormatProvider provider = CultureInfo.InvariantCulture;
DateTimeStyles styles = DateTimeStyles.None;
var dateStringsByFormat = new Dictionary<string, string>
{
{ "yyyy-MM-dd'T'HH:mm:ss.fff'Z'", "2023-01-15T14:12:12.000Z" },
{ "yyyy-MM-dd'T'HH:mm:ss'Z'", "2023-01-15T14:12:12Z" },
{ "yyyyMMdd'T'HH:mm:ss.fff'Z'", "20230115T14:12:12.000Z" },
{ "yyyyMMdd'T'HH:mm:ss'Z'", "20230115T14:12:12Z" },
{ "dd-MM-yyyy HH:mm:ss", "15-01-2023 14:12:12" },
{ "dd/MM/yyyy HH:mm:ss", "15/01/2023 14:12:12" }
};
foreach (var format in formats)
{
string dateString = dateStringsByFormat[format];
DateTime parsedDate;
bool isValidDate = DateTime.TryParseExact(dateString, formats, provider, styles, out parsedDate);
if (isValidDate)
Console.WriteLine(parsedDate);
else
Console.WriteLine($"Unable to parse date:{dateString}");
}
We loop through the formats array, get the string value from the list of key-value pairs, and try to parse the string to a DateTime that is recognizable within the formats array. Note that we pass the string as the first argument and the formats array as the second argument to the overload. If the parsing is successful, the method should return true and a converted DateTime each time it’s triggered.
TryParseExact(ReadOnlySpan<Char>, String[], IFormatProvider, DateTimeStyles, DateTime)
This last overload is similar to the previous overload but uses span for the first parameter. We’ll be using the same example, but we’ll convert the string representation of the date and time to span each time we want to call the overload:
string[] formats = { "yyyy-MM-dd'T'HH:mm:ss.fff'Z'", "yyyy-MM-dd'T'HH:mm:ss'Z'",
"yyyyMMdd'T'HH:mm:ss.fff'Z'", "yyyyMMdd'T'HH:mm:ss'Z'", "dd-MM-yyyy HH:mm:ss", "dd/MM/yyyy HH:mm:ss" };
IFormatProvider provider = CultureInfo.InvariantCulture;
DateTimeStyles styles = DateTimeStyles.None;
var dateStringsByFormat = new Dictionary<string, string>
{
{ "yyyy-MM-dd'T'HH:mm:ss.fff'Z'", "2023-01-15T14:12:12.000Z" },
{ "yyyy-MM-dd'T'HH:mm:ss'Z'", "2023-01-15T14:12:12Z" },
{ "yyyyMMdd'T'HH:mm:ss.fff'Z'", "20230115T14:12:12.000Z" },
{ "yyyyMMdd'T'HH:mm:ss'Z'", "20230115T14:12:12Z" },
{ "dd-MM-yyyy HH:mm:ss", "15-01-2023 14:12:12" },
{ "dd/MM/yyyy HH:mm:ss", "15/01/2023 14:12:12" }
};
foreach (var format in formats)
{
ReadOnlySpan<char> dateSpan = dateStringsByFormat[format];
DateTime parsedDate;
bool isValidDate = DateTime.TryParseExact(dateSpan, formats, provider, styles, out parsedDate);
if (isValidDate)
Console.WriteLine(parsedDate);
else
Console.WriteLine($"Unable to parse date:{dateSpan}");
}
Since each string representation of the date and time passed has a format that is identifiable within the formats array, each TryParseExact() method call should return true and a successfully parsed DateTime.
Which DateTime Parsing Method Should We Use?
Two questions decide it, and between them there are only four answers.
Does the input have a fixed, known format? If it does, the method name contains Exact and we supply the format string. If it does not, the method reads whatever the culture allows.
Can the input be invalid? If it can, the method name starts with Try and we branch on the returned boolean. If it cannot, the plain form is right, and an exception is the correct response to something impossible happening.
Most real code answers yes to both, which lands on TryParseExact() with CultureInfo.InvariantCulture. Most examples answer no to both, which is why Parse() is the method everybody knows and the one that fails in production.
The remaining two are the mixed cases: TryParse() for a date a person typed in their own regional format, and ParseExact() for a contract we are willing to crash on.
| Method | Input format | On failure | Reach for it when |
|---|---|---|---|
DateTime.Parse() | anything the culture allows | throws FormatException | the string came from our own code and cannot be wrong |
DateTime.TryParse() | anything the culture allows | returns false; out parameter is DateTime.MinValue | a person typed a date in their own regional format |
DateTime.ParseExact() | exactly the format string we pass | throws FormatException | the input has a contract and a breach is a bug worth crashing on |
DateTime.TryParseExact() | exactly the format string we pass | returns false; out parameter is DateTime.MinValue | the input has a contract and a breach is expected traffic |
Once a string is parsed, comparing two dates is the usual next step, and DateOnly and TimeOnly carry their own parse methods for the cases where a date has no time of day at all.
Conclusion
In this article, we’ve learned that in C#, we have four different methods Parse(), TryParse(), ParseExact() and TryParseExact() with their overloads in the DateTime class to parse a string representation of date and time to a DateTime object, and we can use them according to our needs.
Tested with .NET 10.0.10.

