DateTime.ParseExact - how to parse single- and double-digit hours with same format string? - c#

I want to be able to parse strings of time (hours, minutes, seconds) where the hours run from 0 to 23, and where the preceding zero for one-digit hours is optional.
Examples of time strings that I want to be able to parse into valid DateTime objects:
212540
061525
94505
I am trying to use the C# method DateTime.ParseExact to manage the parsing, but I cannot for the life of it come up with a format string that can handle the "single-digit hour without preceding zero" scenario.
How should I specify the DateTime.ParseExact format string to sufficiently parse all examples above with the same line of code?
Inspired by the MSDN page on custom date and time formats, I have tried the following approaches:
DateTime.ParseExact(time_string, "Hmmss", CultureInfo.InvariantCulture);
DateTime.ParseExact(time_string, "%Hmmss", CultureInfo.InvariantCulture);
DateTime.ParseExact(time_string, "HHmmss", CultureInfo.InvariantCulture);
All these format strings work for the first two example cases above, but faced with a single-digit hour and no preceding zero, all formulations throw a FormatException.

You can insert delimiters between hours, minutes and seconds like this:
string timeString = "94505";
string formatedTimeString = Regex.Replace(str, #"\d{1,2}(?=(\d{2})+$)", "$&:");
var datetime = DateTime.ParseExact(formatedTimeString, "H:mm:ss", CultureInfo.InvariantCulture);
UPDATE:
I've found the cause of failure when parsing "94505" with format string "Hmmss":
What's happening is that H, m and s actually grabs two digits when they can, even if there won't be enough digits for the rest of the format. So the for example with the format Hmm and the digits 123, H would grab 12 and there would only be a 3 left. And mm requires two digits, so it fails.
So basically you have two options for handling the "single-digit hour without preceding zero" scenario:
Change time format: place hours to the end (for example, "ssmmH" or "mmssH") or use delimiters (for example, "H:mm:ss")
Modify the string like I've suggested earlier or like keyboardP has.

You could pad your input string if you know that you'll always have six characters.
string input = "94505";
if(input.Length < 6)
input = input.PadLeft(6, '0');
(Or use input.Length == 5 if you have other valid formats that are shorter).

What about using:
DateTime.ParseExact(time_string, "Hmmss", CultureInfo.InvariantCulture).ToString("HH:mm:ss")

Related

ParseExact cannot parse a string in RFC 3339 Internet Date/Time format

It seems that C# does not manage to parse a time in a valid RFC 3339 format:
DateTime.ParseExact("2019-12-31T00:00:00.123456789+01:00", "yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'fffffffffzzz", null)
This line throws an exception, while this line works just fine:
DateTime.ParseExact("2019-12-31T00:00:00.1234567+01:00", "yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'fffffffzzz", null)
So it seems there is a limit on milliseconds, but I cannot find out any documentation on that. Is this how it is supposed to be?
The reason want to parse this date is that I have have an input date field. We use OAS (Swagger) date-time format that quite clearly says that any date in RFC 3339 Internet Date/Time format should be valid. Now from the spec here section 5.6
time-secfrac = "." 1*DIGIT
As far as I understand this means that up to 9 digits should be allowed and to be 100% compliant we have to allow these inputs, but it does not seem that C# even supports that.
Any ideas on how to fix it?
Per MSDN specification, you can use only fffffff
The fffffff custom format specifier represents the seven most
significant digits of the seconds fraction; that is, it represents the
ten millionths of a second in a date and time value.
In your first example
DateTime.ParseExact("2019-12-31T00:00:00.123456789+01:00", "yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'fffffffffzzz", null)
you are using fffffffff which is more precise for .NET custom date and time format strings
As far as I know, .NET supports seven most significant digits for milliseconds which is The "fffffff" custom format specifier are for.
The "fffffff" custom format specifier represents the seven most
significant digits of the seconds fraction; that is, it represents the
ten millionths of a second in a date and time value.
Although it's possible to display the ten millionths of a second
component of a time value, that value may not be meaningful. The
precision of date and time values depends on the resolution of the
system clock.
That means you are giving not meaningful data that are not supported for .NET Framework. I strongly suggest not doing that.
In addition to the information in the other answers, if you cannot change your input and you still want to parse it, you may use one of the following solutions:
If your input will always be in the same format (i.e., has 9 seconds-fraction digits), you could just remove the two extra ones and proceed to parse it:
string input = "2019-12-31T00:00:00.123456789+01:00";
input = input.Remove(27, 2);
// TODO: parse `input`.
If you don't know the number of the seconds-fraction digits beforehand, you may use something like this:
string input = "2019-12-31T00:00:00.123456789+01:00";
string format = "yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'FFFFFFFzzz";
var regEx = new Regex(#"^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{1,7})\d*");
input = regEx.Replace(input, "$1");
DateTime parsedDate = DateTime.ParseExact(input, format, null);

TimeSpan.TryParseExact not working

I'm trying to retrieve a timespan from a string, but TryParseExact is returning false (fail).
I can't see what I'm doing wrong, can you help? I've tried 2 versions of my line in the code, both do not work.
TimeSpan.TryParseExact("04:00:01","HH:mm:ss",CultureInfo.CurrentCulture, out aTime)
and
TimeSpan.TryParseExact("04:00:01","HH:mm:ss", null, out aTime)
EDIT:
both responses here are correct, I have the wrong format for my custom timespan format - the mistake I made is to assume that the custom formats for DateTime would work for TimeSpans, but they do not.
The problem is simply in the format string for the TimeSpan, you have specified "HH:mm:ss". The specifier HH (upper case) is not valid for timespan. You should use hh. Format strings are indeed case sensitive.
The colon character (:) also needs to be escaped, so use "hh\\:mm\\:ss", #"hh\:mm\:ss" or "hh':'mm':'ss". All three forms will have the same effect.
You can review a list of valid custom format strings for TimeSpan here. and the standard format strings for TimeSpan are here.
While HH is valid for DateTime and DateTimeOffset where it represents the 24 hour clock and lower case hh represents a 12 hour clock, For TimeSpan - the hours component is always based on 24 hours. You would think that the HH format would be the one chosen, for uniformity, but nope - it's hh.
It's probably should get mentioned that you need to escape the colon character.
TryParseExact("04:00:01", "HH\\:mm\\:ss" ...
The string format which you are passing is wrong.
var res=TimeSpan.TryParseExact("04:00:01", "g", CultureInfo.CurrentCulture, out aTime);
g- General short format and is culture sensitive.
More on this here Standard Timespan Format Strings
Maybe you were using multiple formats.
public const string TimeFormat1 = "hh\\:mm";
public const string TimeFormat2 = "hh\\:mm:\\ss";
var parsed = TimeSpan.TryParseExact(time, new [] { TimeFormat1, TimeFormat2 }, CultureInfo.CurrentCulture, out TimeSpan ts1);
// parsed is always false
You might have thought you escaped your colon; but didn't, actually...
This "hh\\:mm:\\ss" won't work.
Using TimeFormat2 in ParseExact throws a FormatException...
You meant to use this "hh\\:mm\\:ss" instead.

datetime.TryParseExact with different formats of values

I have a json string that contains the values for a datetime and a parsing mechanism that looks like this:
if (DateTime.TryParseExact(TheUserTimeString, "M.d.yyyy.HH.mm", CultureInfo.InvariantCulture, DateTimeStyles.None, out TheUserTime))
{
TheObject.UserDateTime = TheUserTime;
}
The string TheUserTimeString is generated on the client. It can be 12.20.2011.13.21 and the code works fine but when it's 12.20.2011.13.2 the code breaks because the minutes are in one digit. And when the month is also in one digit... who knows.
What would be a better way to rewrite this parsing code so that the string gets parsed correctly every time.
Thanks for your suggestions.
Use the string "M.d.yyyy.HH.m", a single m denotes minutes without the leading 0. Source.
Your DateTime format string just needs to be: "M.d.yyyy.H.m".
This allows for months, days, hours and minutes to be expressed as single digit values.
See here for the MSDN page documenting the valid formats of this string for further information.

Parse date exact in c# with variable year length

I'm trying to find the appropriate format string to parse (exact) the following types of dates:
1-01-01T00:00:00+00:00 - 1 January of 0001
2011-12-14T15:53:40+00:00 - 14 December of 2011
So the year length seems to be variable (1-4 characters).
The format sting I currently use to parse exact is:
c_DateTimeFormatString = "yyyy-MM-ddTHH':'mm':'sszzz"
Obviously this only matches the second string. The first one poped up today. Now we have to match that as well.
Is there a format string to achieve this?
UPDATE #1
I added the actual dates in clear text after the input date strings.
UPDATE #2
Parse exact has an overload that allows for multiple format strings to be passed in. This seems to be the right way.
So the first try was to use:
DateTime.ParseExact("1-01-01T00:00:00+00:00 ", new[] { "yyyy-MM-ddTHH':'mm':'sszzz", "yyy-MM-ddTHH':'mm':'sszzz", "yy-MM-ddTHH':'mm':'sszzz", "y-MM-ddTHH':'mm':'sszzz" }, CultureInfo.CreateSpecificCulture("en-US"), DateTimeStyles.AssumeLocal)
But sadly this does ont give the correct result, the first date string is parsed as:
01.01.2001
rather than
01.01.0001
So the question now is what is the correct parsing string to parse year one which is represented with only one digit?
Updated based on comment:
string y = "yyyy-MM-ddTHH':'mm':'sszzz";
string testDate = "1-01-05T00:00:00+00:00".PadLeft(25, '0');
Console.WriteLine(DateTime.ParseExact(testDate, y, CultureInfo.InvariantCulture));
testDate = "2011-12-14T15:53:40+00:00".PadLeft(25, '0');
Console.WriteLine(DateTime.ParseExact(testDate, y, CultureInfo.InvariantCulture));
The output is:
1/5/0001 00:00:00
12/14/2011 15:53:40
You can use an overload of ParseExact to match multiple formats, I believe.
See MSDN.
If the input formats are not all exactly the same, you'll need either to stop using parse exact, or to call it with different format arguments depending on the format of the input data.
I don't believe you need to match a year from 1 to 4 characters but 2 or 4 characters.
For this example
1-01-05T00:00:00+00:00
you would need something like
d-MM-yyTHH':'mm':'sszzz
Try padding your string so that the year is 4 digits long. You should probably add 2, 20 or 201 and not just 0's.
Since you know that you have to support multiple formats, I suggest that you use the TryParseExact method.
If it fails to parse using one format (i.e. it returns false), then try the next format.

DateTime.TryParseExact not working as expected

Can anyone explain why the following snippet returns true?
According to the docs for The "d" custom format specifier, "A single-digit day is formatted without a leading zero." So why doesn't TryParseExact fail when I give it a single-digit day with a leading zero?
DateTime x;
return DateTime.TryParseExact
(
"01/01/2001",
#"d\/MM\/yyyy",
null,
System.Globalization.DateTimeStyles.None,
out x
);
UPDATE
I think maybe I was unclear originally. What I am really trying to get at is: Why does TryParseExact accept some values that don't match exactly? from all of the documentation I have seen, 'd' matching '01' and '1' is just as much a bug as if 'MM' matched 'March' as well as '03'. The issue here isn't that the values are equivalent, its that they don't match the format.
The relevant snippets of documentation are:
From TryParseExact: The format of the string representation must match a specified format exactly.
From The 'd' Specifier: A single-digit day is formatted without a leading zero.
It seems abundantly clear to me that '01' has a leading 0, and therefore doesn't exactly match 'd'.
From the .NET 4 source in DateTimeParse.ParseByFormat():
case 'd':
// Day & Day of week
tokenLen = format.GetRepeatCount();
if (tokenLen <= 2) {
// "d" & "dd"
if (!ParseDigits(ref str, tokenLen, out tempDay)) {
if (!parseInfo.fCustomNumberParser ||
!parseInfo.parseNumberDelegate(ref str, tokenLen, out tempDay)) {
result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
return (false);
}
}
if (!CheckNewValue(ref result.Day, tempDay, ch, ref result)) {
return (false);
}
}
else
{...}
The parser lumps "d" and "dd" together.
It appears that behavior is by design, and I think it works that way to be consistent with other string formatting options.
Take the following example:
//Convert DateTime to string
string dateFormat = "d/MM/yyyy";
string date1 = new DateTime(2008, 10, 5).ToString(dateFormat);
string date2 = new DateTime(2008, 10, 12).ToString(dateFormat);
//Convert back to DateTime
DateTime x1, x2;
DateTime.TryParseExact(date1, dateFormat, null, System.Globalization.DateTimeStyles.None, out x1);
DateTime.TryParseExact(date2, dateFormat, null, System.Globalization.DateTimeStyles.None, out x2);
Console.WriteLine(x1);
Console.WriteLine(x2);
In the first part, ToString() outputs a two digit day for October 12th, because it wouldn't make much sense to just write out a single digit day (and which digit would it pick, the 1 or the 2?). So since the "d" represents one OR two digit days when converting to a string, it would have to work the same way when converting back to DateTime. If it didn't, the conversion back to DateTime in TryParseExact in my example would fail, and that would definitely not be an expected behavior.
I would say that if you really need to match a d/MM/yyyy format exactly, you could probably use a regex to validate the string and then pass it through Parse, TryParse or TryParseExact (depending on how good your regex is, since it would have to handle leap years, 30/31 days, etc if you wanted to use Parse).
I'd say it doesn't fail because TryParseExact is smart enough to know that '01' == '1'.
TryParseExact is just trying to be flexible in this case I guess. But the "d" vs "dd" should and would work as advertised when you are converting date to string using a format specifier.
Because a single 'd' means that your DateTime value will be converted to as short value as possible, i.e. without leading zero if there's no necessity for it. I suppose it shouldn't fail when you're converting from string to DateTime because the main purpose of TryParseExact's format string is to help to convert to DateTime, i.e. it serves like a hint, it's not intended to validate string format.
You can use RegEx if you still need hardcore string format validation.

Categories

Resources