Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does AD3AD08 represent a valid date in the .NET framework?

Tags:

c#

.net

datetime

DateTime.Parse("AD3AD08")

[2017-08-03 12:00:00 AM]

Why does that string (which looks like just a normal hex string to me) get parsed successfully as a date? I can see the 3 and the 8 get parsed as months and days. But otherwise it doesn't make sense to me.

like image 250
Francois Botha Avatar asked Apr 11 '17 07:04

Francois Botha


2 Answers

tl;dr: You can use what DateTimeFormatInfo.GetEraName/GetAbbreviatedEraName return as delimiter, ignoring the case. The order is: day, month, year (optional).


It seems you can always use the calendar's current era's abbreviated name or full era-name as delimiter for the DateTime tokens. For english cultures it is AD or A.D., e.g. for german cultures it is n. Chr..

var enCulture = new CultureInfo("en-GB");
System.Threading.Thread.CurrentThread.CurrentCulture = enCulture;
var fi = enCulture.DateTimeFormat;
int currentEra = enCulture.Calendar.GetEra(DateTime.Now);
var eraName = fi.GetEraName(currentEra);
var shortEra = fi.GetAbbreviatedEraName(currentEra);
var date = DateTime.Parse($"{shortEra}3{shortEra}08"); // AD or A.D. works

var deCulture = new CultureInfo("de-DE");
System.Threading.Thread.CurrentThread.CurrentCulture = deCulture;
fi = deCulture.DateTimeFormat;
currentEra = deCulture.Calendar.GetEra(DateTime.Now);
eraName = fi.GetEraName(currentEra);
shortEra = fi.GetAbbreviatedEraName(currentEra);
date = DateTime.Parse($"{shortEra}3{shortEra}08");  // n. Chr. works

Interestingly it is case-insensitive, so ad works also. That is documented in DateTimeFormatInfo.GetEra:

The era name is the name a calendar uses to refer to a period of time reckoned from a fixed point or event. For example, "A.D." or "C.E." is the current era in the Gregorian calendar. The comparison with eraName is case-insensitive, for example, "A.D." is equivalent to "a.d.".

The gregorian calendar has only one era, so Calendar.GetEra(DateTime.Now) isn't really necessary. I haven't found any further documentation yet.

Here are some samples that all work and will be parsed to christmas 2017:

DateTime christmas  = DateTime.Parse("ad25ad12ad2017ad");
christmas = DateTime.Parse("AD25ad12ad2017");
christmas = DateTime.Parse("25ad12ad2017AD");
christmas = DateTime.Parse("25ad12ad2017");
christmas = DateTime.Parse("A.D.25ad12ad2017");
christmas = DateTime.Parse("A.D.25ad12ad");  // current year is used
christmas = DateTime.Parse("A.D.25ad12");    // current year is used
like image 74
Tim Schmelter Avatar answered Nov 06 '22 12:11

Tim Schmelter


You can confirm that this is era and not some UTF encoded character by modifying culture abbreviated era name (era name is stored in DateTimeFormatInfo.m_abbrevEraNames and DateTimeFormatInfo.m_abbrevEnglishEraNames private fields, and for invariant culture abbreviated era name is string array with just one value - "AD"). m_eraNames field also stores full (non-abbreviated) era name ("A.D." for invariant culture) which can also be used instead of "AD".

var cul = (CultureInfo) CultureInfo.InvariantCulture.Clone();                        
// set DateTimeFormatInfo.AbbreviatedEraNames to "BLA"
typeof(DateTimeFormatInfo).GetField("m_abbrevEraNames", BindingFlags.Instance | BindingFlags.NonPublic)
    .SetValue(cul.DateTimeFormat, new string[] {"BLA"});
// set DateTimeFormatInfo.AbbreviatedEnglishEraNames to "BLA"
typeof(DateTimeFormatInfo).GetField("m_abbrevEnglishEraNames", BindingFlags.Instance | BindingFlags.NonPublic)
    .SetValue(cul.DateTimeFormat, new string[] { "BLA" });

var date = DateTime.Parse("AD03AD08", cul); // now it fails
var date = DateTime.Parse("A.D.03A.D.08", cul); // still works because we
// did not modify non-abbreviated era name
var date = DateTime.Parse("BLA03BLA08", cul); // this one works

Now why it treats era name like that is not quite obvious... Probably after meeting such token it sets date era and continues parsing, so it serves as separator in a sense it just moves to parsing next token after this one. Documentation for DateTime.Parse states that:

This method attempts to parse string completely and avoid throwing a FormatException. It ignores unrecognized data if possible and fills in missing month, day, and year information with the current date

While this does not mention anything about eras - such behavior aligns with "avoid throwing FormatException whenever possible" design.

like image 34
Evk Avatar answered Nov 06 '22 14:11

Evk