I need to parse DateTime in "Myy" format, so:
the first number is a month without leading zero (1 to 12), and
the second number is a year with two digits.
Examples:
115 -> January 2015
1016 -> October 2016
When using DateTime.ParseExact with "Myy" as a format, DateTime throws an exception when month is without leading zero.
This code throws an exception:
var date = DateTime.ParseExact("115",
"Myy",
CultureInfo.InvariantCulture); // throws FormatException
While this works fine:
var date = DateTime.ParseExact("1016",
"Myy",
CultureInfo.InvariantCulture); // works fine
MSDN Documentation clearly defines format specifiers:
"M" – The month, from 1 through 12.
"MM" – The month, from 01 through 12.
"yy" – The year, from 00 to 99.
Is there any format which would resolve the above case, i.e. "Myy" date time format in which month is without leading zeros?
EDIT
Just to precise: The question is about using format in ParseExact specifically and not about how to parse it itself by using string manipulation.
This is because the DateTime parser reads from left to right without backtracking.
Since it tries to read a month, it starts taking the first two digits and uses it to parse the month. And then it tries to parse the year but there is only one digit left, so it fails. There is simply not a way to solve this without introducing a separation character:
DateTime.ParseExact("1 15", "M yy", CultureInfo.InvariantCulture)
If you can’t do that, read from the right first and split off the year separately (using string manipulation). Or just add a zero to the beginning and parse it as MMyy:
string s = "115";
if (s.Length < 4)
s = "0" + s;
Console.WriteLine(DateTime.ParseExact(s, "MMyy", CultureInfo.InvariantCulture));
Research!
Since ispiro asked for sources: The parsing is done by the DateTimeParse type. Relevant for us is the ParseDigits method:
internal static bool ParseDigits(ref __DTString str, int digitLen, out int result) {
if (digitLen == 1) {
// 1 really means 1 or 2 for this call
return ParseDigits(ref str, 1, 2, out result);
}
else {
return ParseDigits(ref str, digitLen, digitLen, out result);
}
}
Note that comment there in the case where digitLen equals 1. Know that the first number in that other ParseDigits overload is minDigitLen and the other is maxDigitLen. So basically, for a passed digitLen of 1, the function will also accept a maximum length of 2 (which makes it possible to use a single M to match the 2-digit months).
Now, the other overload that actually does the work contains this loop:
while (tokenLength < maxDigitLen) {
if (!str.GetNextDigit()) {
str.Index--;
break;
}
result = result * 10 + str.GetDigit();
tokenLength++;
}
As you can see, the method keeps taking more digits from the string until it exceeded the maximum digit length. The rest of the method is just error checking and stuff.
Finally, let’s look at the actual parsing in DoStrictParse. There, we have the following loop:
// Scan every character in format and match the pattern in str.
while (format.GetNext()) {
// We trim inner spaces here, so that we will not eat trailing spaces when
// AllowTrailingWhite is not used.
if (parseInfo.fAllowInnerWhite) {
str.SkipWhiteSpaces();
}
if (!ParseByFormat(ref str, ref format, ref parseInfo, dtfi, ref result)) {
return (false);
}
}
So basically, this loops over the characters in the format string, then tries to match the string from left to right using that format. ParseByFormat does additional logic that captures repeated formats (like yy instead of just y) and uses that information to branch into different formats. For our months, this is the relevant part:
if (tokenLen <= 2) {
if (!ParseDigits(ref str, tokenLen, out tempMonth)) {
if (!parseInfo.fCustomNumberParser ||
!parseInfo.parseNumberDelegate(ref str, tokenLen, out tempMonth)) {
result.SetFailure(ParseFailureKind.Format, "Format_BadDateTime", null);
return (false);
}
}
}
So here we close the circle to the ParseDigits which is passed with a token length of 1 for a single M. But as we’ve seen above, it will still match two digits if it can; and all that without validating whether the two digit number it matches makes any sense for a month. So 130 wouldn’t match for January 2030 either. It would match as the 13th month and fail there later.
From MSDN:
If format is a custom format pattern that does not include date or
time separators (such as "yyyyMMdd HHmm"), use the invariant culture
for the provider parameter and the widest form of each custom format
specifier. For example, if you want to specify hours in the format
pattern, specify the wider form, "HH", instead of the narrower form,
"H".
In other words, it's likely that it can't be solved the way you want.
Too simple??
string date = "115";
if (date.Count()==3)
{
date = "0" + date;
}
Related
I have some c# code like this:
string myString = "20180426";
I know how to parse around specific characters (using the string.Split thing), but how do I get it to return 3 strings like this:
2018
04
26
I have several strings that are formatted this way ("YYYYMMDD"), so I don't want code that will only work for this specific string. I tried using
var finNum = myString[0] + myString[1] + myString[2] + myString[3];
Console.Write(finNum);
But I guess it's treating the characters as integers, rather than a text string because it's doing some mathematical operation with them instead of concatenating (it's not addition either because it's returning 203, which isn't the sum of 2, 0, 1 and 8).
I've tried changing var to string, but it won't let me implicitly convert int to string. Why does it think that string myString is an int, rather than a string, which is what I declared it as?
I could also use DateTime.Parse and DateTime.ParseExact, but apparently "20180426" isn't recognized as a valid DateTime:
DateTime myDate = DateTime.ParseExact(myString, "YYYYMMDD", null);
Console.WriteLine(myDate);
Thank you for your help. I know the answer is probably stupidly easy and I feel dumb for asking but I seriously checked all over various websites and can't find a solution that works for my issue here.
I could also use DateTime.Parse and DateTime.ParseExact, but
apparently "20180426" isn't recognized as a valid DateTime.
Yes, because the format string YYYYMMDD is incorrect, years and days are lowercase:
DateTime myDate = DateTime.ParseExact(myString, "yyyyMMdd", null);
If you want the year, month and day:
int year = myDate.Year;
int month = myDate.Month;
int day = myDate.Day;
If you want year, month and day separated by variables you could try:
string mystring = "20180426";
mystring = mystring.Insert(4,"-");
mystring = mystring.Insert(7,"-");
string year = mystring.Split('-')[0];
string month = mystring.Split('-')[1];
string day = mystring.Split('-')[2];
First I add a character "-" to separate year and month, then another to separate month and day. You get something like "2018-04-26"
Then I split the string and save the position 0 that store the first 4 numbers of your string into a variable named year.
Good luck!
I'm having huge problems with solving this problem. I'm trying to parse a string using Datetime.ParseExact().
I have the following code:
DateTime.ParseExact("20151210 832", "yyyyMMdd Hmm", CultureInfo.InvariantCulture);
I get following error:
An unhandled exception of type 'System.FormatException' occurred in
mscorlib.dll Additional information: String was not recognized as a
valid DateTime.
What am I doing wrong? How can I solve this problem?
UPDATE:
I can also get times like this:
00:01 => 1
01:00 => 1
01:10 => 10
Since H specifier can be 2 digit, this method try to parse your 83 with H specifier. Since there is no such an hour, you get FormatException.
For your case, one way to prevent this is putting a leading zero just before your 8.
var s = "20151210 832";
var result = s.Split(' ')[0] + " 0" + s.Split(' ')[1];
var dt = DateTime.ParseExact(result, "yyyyMMdd Hmm", CultureInfo.InvariantCulture);
Be aware, this will not work for all cases. For example, if your hour part already two digit, if your single minute does not have leading zero.. etc.
Or you can put delimiter for your all parts but in such a case, you need to manipulate both your string and format.
.NET Team suggest this way as well.
Just insert a separator before minutes (for example, a whitespace) and then you can parse it like this:
string example = "20151210 832";
example = example.Insert(example.Length - 2, " ");
var dateTime = DateTime.ParseExact(example, "yyyyMMdd H mm", CultureInfo.InvariantCulture);
I assume that the datetime string always contains two digits specifying minutes (check an article about Custom Date and Time Format Strings). If my assumption is wrong then this string cannot be parsed.
I'm having a bit of a issue with this.
What I want to do is take this string 27.0 and convert it to a timespan.
I tried every way I could think of in order to get it to work.
TimeSpan.Parse("27.0") I know it's a format issue but I'm not sure of the format to use.
I basically have 4 values
27.0
52.4
1:24.4
1:43.3
Is there a easy way to handle all these formats?
Thanks!
Sorry these are all seconds except the 1 is minute so 1 minute 24 seconds 4 milliseconds
You can use two different approaches. Use one of the TimeSpan.From...() methods. Those convert numbers to a TimeSpan. For example to convert the double 27 to a TimeSpan with 27 seconds you use
var ts = TimeSpan.FromSeconds(27)
The only problem you will face here is that it does not allow you to specify a string. So you could for example first parse your string as an double. If you do it naivly just like that, it can be you get what you wanted, or not.
var ts = TimeSpan.FromSeconds(double.Parse("27.0"))
But if you run this for example on a system with a German locale you will get a TimeSpan with 4 minutes and 30 seconds. The reason for that is that a dot in German is not a divider for a number, it is the thousand seperator. So that number is parsed as "270". So to be safe you should also provide a NumberFormat. A better way would be.
var culture = new CultureInfo("en-US");
var tsc = TimeSpan.FromSeconds(double.Parse("27.0", culture.NumberFormat));
Now you get your 27 seconds. But the problem is still that it only parses your two first strings correctly. Your other 3 strings will still not parse, because you can't convert them to numbers. But I still added this, to be aware of culture difference if you just go up and try to parse a number to an double und use TimeSpan.FromSeconds() and so on.
Now lets look further how you can parse every string. There exists TimeSpan.Parse() and TimeSpan.ParseExact().
Now you still must knew that TimeSpan.Parse() uses culture specific formatting. In a country where a time is not separated with colons a TimeSpan.Parse() will fail. On Top of that, TimeSpan assumes a format "hh:mm" at minimum. But the Colon in this format is culture-sensitive. You could use the "en-US" Culture once again, but it wouldn't solve the problem because he doesn't accept the format "27.0".
That is the reason why you must use the TimeSpan.ParseExact() method and and provide the formats that this method should be able to parse. It also allows you to specify formats that he should be able to parse. You now should end with something like this.
var culture = new CultureInfo("en-US");
var formats = new string[] {
#"s\.f",
#"ss\.f",
#"ss\.ff",
#"m\:ss\.f",
#"m\:ss\.ff",
#"mm\:ss\.ff"
};
foreach ( var str in new string[] { "27.0", "52.4", "1:24.4", "1:43.3" } ) {
var ts = TimeSpan.ParseExact(str, formats, culture.NumberFormat);
Console.WriteLine(ts.ToString());
}
Note that in this example I added a backslash to escape the dot and the colon. If you don't do this then the formatter itself treats this as a culture-sensitive separator. But what you want is exactly the colon or the dot.
The output of this code will be
00:00:27
00:00:52.4000000
00:01:24.4000000
00:01:43.3000000
try something like this:
var timeString = "1:24.4";
var timeComponents = timeString.Split(':', '.').Reverse().ToList();
var milliseconds = timeComponents.Any() ? int.Parse(timeComponents[0]) : 0;
var seconds = timeComponents.Count() > 1 ? int.Parse(timeComponents[1]) : 0;
var minutes = timeComponents.Count() > 2 ? int.Parse(timeComponents[2]) : 0;
var timeSpan = new TimeSpan(0, 0, minutes, seconds, milliseconds);
this will deal with the milliseconds literally. You may want to pad the string component of the milliseconds with '0's, as pointed out in the comments.
I'm testing a piece of code to see if the rules will work each time, so I just made a short console application that has 1 string as an input value which I can replace at any time.
string titleID = "document for the period ended 31 March 2014";
// the other variation in the input will be "document for the period
// ended March 31 2014"
What I'm doing is I take a specific part from it (depending if it contains a specific word - nvm the details, there is a consistency so I don't worry about this condition). Afterwards I'm taking the rest of the string after a specific position in order to do a DateTime.ParseExact
Ultimately I need to figure out how to check if the first DateTime.ParseExact has failed
to then perform a second attempt with a different custom format.
This is how it looks like:
if(titleID.Contains("ended "))
{
// take the fragment of the input string after the word "ended"
TakeEndPeriod = titleID.Substring(titleID.LastIndexOf("ended "));
// get everything after the 6th char -> should be "31 March 2014"
GetEndPeriod = TakeEndPeriod.Substring(6);
format2 = "dd MMMM yyyy"; // custom date format which is mirroring
// the date from the input string
// parse the date in order to convert it to the required output format
try
{
DateTime ModEndPeriod = DateTime.ParseExact(GetEndPeriod, format2, System.Globalization.CultureInfo.InvariantCulture);
NewEndPeriod = ModEndPeriod.ToString("yyyy-MM-ddT00:00:00Z");
// required target output format of the date
// and it also needs to be a string
}
catch
{
}
}
// show output step by step
Console.WriteLine(TakeEndPeriod);
Console.ReadLine();
Console.WriteLine(GetEndPeriod);
Console.ReadLine();
Console.WriteLine(NewEndPeriod);
Console.ReadLine();
Everything works fine until I try a different input string, f.eg. "document for the period ended March 31 2014"
So in this case if wanted to parse "March 31 2014" I'd have to switch my custom format to
"MMMM dd yyyy" and I do that and it works, but I cannot figure out how to check if the first parse fails in order to perform the second one.
First parse - > success -> change format and .ToString
|-> check if failed , if true do second parse with different format -> change format and .ToString
I've tried
if (String.IsNullOrEmpty(NewEndPeriod))
{ do second parse }
Or
if (NewEndPeriod == null)
{ do second parse }
But I get a blank result at Console.WriteLine(NewEndPeriod);
Any ideas how to approach this?
** EDIT: **
Adding here an alternative answer I got which is using Parse instead of TryParseExact, since Parse will handle both of the format variations without the need to specify them
DateTime DT = DateTime.Parse(GetEndPeriod);
//The variable DT will now have the parsed date.
NewEndPeriod = DT.ToString("yyyy-MM-ddT00:00:00Z");
but I cannot figure out how to check if the first parse fails in order
to perform the second one
Instead of DateTime.ParseExact use DateTime.TryParseExact that will return a bool indicating if parsing was successful or not.
DateTime ModEndPeriod;
if (!DateTime.TryParseExact(GetEndPeriod,
format,
CultureInfo.InvariantCulture,
DateTimeStyles.None,
out ModEndPeriod))
{
//parsing failed
}
You can also use multiple formats for parsing using the DateTime.TryParse overload which takes an array of formats:
string[] formatArray = new [] { "dd MMMM yyyy","MMMM dd yyyy"};
DateTime ModEndPeriod;
if (!DateTime.TryParseExact(GetEndPeriod,
formatArray,
CultureInfo.InvariantCulture,
DateTimeStyles.None,
out ModEndPeriod))
{
//parsing failed
}
I have a date string that is coming in as what I believe to be Mddyyyy. However, TryParseExact doesn't seem to be working. Here's the sample code that fails:
string datestring = "1212012";
DateTime td;
if (DateTime.TryParseExact(datestring, "Mddyyyy", new CultureInfo("en-US"), DateTimeStyles.None, out td))
{
Console.WriteLine(td.ToShortDateString());
}
else
{
Console.WriteLine("Invalid Date String");
}
That same code works if there's a leading zero, but I would think then that the leading zero would only work with a formatting string of MMddyyyy.
Here I propose an explanation and provide evidence for the proposal.
Proposed Explanation: The parser internally uses the format string to create a regular expression that contains a greedy quantifier (which means, in this case, it prefers to match 2-digit months over 1-digit months). The M in the OP's format string becomes something like \d{1,2} (though that would match months numbered from 0 to 99!) in the parser's internal regular expression.
Evidence: If you move the month to the end of both the data and the format string, the greedy quantifier cannot obtain more than 1 digit and so it matches the month as desired:
string datestring = "2120121";
DateTime td;
if (DateTime.TryParseExact(datestring, "ddyyyyM", new CultureInfo("en-US"), DateTimeStyles.None, out td))
{
Console.WriteLine(td.ToShortDateString());
}
else
{
Console.WriteLine("Invalid Date String");
}
Bottom Line: Don't rely on undocumented behavior. Always use unambiguous data, i.e., 2-digit months.