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

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.

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

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.

Related

Why date formats miss matching when adding months

When i am getting current date 28-Mar-2015 formated into txtActDate then adding months to it then i am not understanding why its getting in this 28-Mar-15 format.
DateTime dateTime = DateTime.UtcNow.Date;
txtActDate.Text = dateTime.ToString("dd/MMM/yyyy");
DateTime firstDate = DateTime.ParseExact(txtActDate.Text, "dd/MMM/yyyy", null);
firstDate = firstDate.AddMonths(0);
txtAccExp.Text = firstDate.ToShortDateString();
It's almost certainly because you're asking it to give you the date in short format:
txtAccExp.Text = firstDate.ToShortDateString();
You can get the current short format from your culture with:
using System.Globalization;
:
var dtfi = CultureInfo.CurrentCulture.DateTimeFormat;
Console.WriteLine(dtfi.ShortDatePattern);
In terms of fixing it, you could probably use the same method you used to populate the text field in the first place, so as to ensure it's the desired format:
txtActExp.Text = firstDate.ToString("dd/MMM/yyyy");

How To Get English Month Name From Hebrew Calendar In C#

I am trying to output a Hebrew calendar date in English with C#. The following outputs the date in Hebrew:
var ci = System.Globalization.CultureInfo.CreateSpecificCulture("he-IL");
ci.DateTimeFormat.Calendar = new System.Globalization.HebrewCalendar();
Response.Write(DateTime.Today.ToString("MMM d, yyyy", ci));
Response.Write(DateTime.Today.ToString("d-M-y", ci));
Gives
כסלו כ"ו, תשע"ה
כ"ו-ג'-תשע"ה
for December 18, 2014. Change the CultureInfo to "en-US" raises an "Not a valid calendar for the given culture." error. I am trying to get
26 Kislev 5775
and
26-09-5775
I could not figure out how to set the array of month names for leap years or the array of day numbers so that they are rendered as English numbers rather than hebrew letters. My solution was:
Globals.cs
public static string[] HebrewMonthNames =
{
"Tishrei",
"Cheshvan",
"Kislev",
"Tevet",
"Shevat",
"Adar",
"Nissan",
"Iyar",
"Sivan",
"Tamuz",
"Av",
"Elul"
};
public static string[] HebrewMonthNamesLeapYear =
{
"Tishrei",
"Cheshvan",
"Kislev",
"Tevet",
"Shevat",
"Adar I",
"Adar II",
"Nissan",
"Iyar",
"Sivan",
"Tamuz",
"Av",
"Elul"
};
Utils.cs
public string FormatHebrewDate(DateTime dtGregorian)
{
System.Globalization.HebrewCalendar hCal = new System.Globalization.HebrewCalendar();
string sDate = hCal.GetDayOfMonth(dtGregorian).ToString() + " ";
if (hCal.IsLeapYear(hCal.GetYear(dtGregorian)))
{
sDate += Globals.HebrewMonthNamesLeapYear[hCal.GetMonth(dtGregorian) - 1];
}
else
{
sDate += Globals.HebrewMonthNames[hCal.GetMonth(dtGregorian) - 1];
}
sDate += " " + hCal.GetYear(dtGregorian).ToString();
return sDate;
}
Option 1:
You can override the DateTimeFormatInfo.MonthNames and MonthGenitiveNames properties as well as their corresponding AbbreviatedMonthNames and AbbreviatedMonthGenitiveNames properties.
They are simple 1-dimensional string[] arrays and have a public setters, which allows you to add your custom translations to the CultureInfo:
When this property is set, the array must be one-dimensional and must
have exactly 13 elements. Calendar objects accommodate calendars with
13 months. The first element (the element at index zero) represents
the first month of the year defined by the Calendar property.
If you set the MonthNames property, you must also set the
MonthGenitiveNames property.
If the custom pattern includes the format pattern "MMMM",
DateTime.ToString displays the value of MonthNames in place of the
"MMMM" in the format pattern.
This property is affected if the value of the Calendar property
changes.
So you could modify your code example to this:
// I am just using German Number representations for the example.
// Use additional string Arrays to suit the abbrevated
// and the Genetive names.
// Replaye with whatever suits your needs.
string[] monthNames =
{
"Eins",
"Zwei",
"Drei",
"Vier",
"Fünf",
"Sechs",
"Sieben",
"Acht",
"Neun",
"Zehn",
"Elf",
"Zwölf",
string.Empty
};
// Assign each string Array to its corresponding property.
// I am using the same Array here just as an example for
// what is possible and because I am lazy... :-)
ci.DateTimeFormat.MonthNames = monthNames;
ci.DateTimeFormat.MonthGenitiveNames = monthNames;
ci.DateTimeFormat.AbbreviatedMonthNames = monthNames;
ci.DateTimeFormat.AbbreviatedMonthGenitiveNames = monthNames;
These names will then be used in with your format string in the output, just as you want it to have.
Each time you change the calendar, these overrides will be lost. So you need to make sure to re-assign the custom values if you need it.
[Update] Option 2:
A more persistent approach might be to use the CultureAndRegionInfoBuilder Class.
Defines a custom culture that is new or based on another culture and
country/region. The custom culture can be installed on a computer and
subsequently used by any application that is running on that computer.
You can either create a complete replacement version of the "he-IL" culture or create a variation with just your custom translations, or anything in between.
Using this approach you do not have to manually make sure that the translations are in place after each Culture-switch in the appliaction like in Option 1. Once the new Custom Culture is registered, you can use it like any other CultureInfo.
Please note that your application will need administrative priviledges to register a new Custom Culture.
The creation of a Custom Culture is not too complicated as the following code snippet shows.
Example from MSDN: CultureAndRegionInfoBuilder
The following example defines a custom ru-US culture that represents
the Russian language in the United States. The example defines the
custom culture by loading settings from the Russian (Russia)
CultureInfo object and the U.S. RegionInfo object, and then sets a
number of CultureAndRegionInfoBuilder properties. The example
registers the custom culture, and then instantiates it and makes it
the current thread culture.
using System;
using System.Globalization;
using System.Threading;
public class Example
{
public static void Main()
{
// Create a custom culture for ru-US.
CultureAndRegionInfoBuilder car1 = new CultureAndRegionInfoBuilder("ru-US",
CultureAndRegionModifiers.None);
car1.LoadDataFromCultureInfo(CultureInfo.CreateSpecificCulture("ru-RU"));
car1.LoadDataFromRegionInfo(new RegionInfo("en-US"));
car1.CultureEnglishName = "Russian (United States)";
car1.CultureNativeName = "русский (США)";
car1.CurrencyNativeName = "Доллар (США)";
car1.RegionNativeName = "США";
// Register the culture.
try {
car1.Register();
}
catch (InvalidOperationException) {
// Swallow the exception: the culture already is registered.
}
// Use the custom culture.
CultureInfo ci = CultureInfo.CreateSpecificCulture("ru-US");
Thread.CurrentThread.CurrentCulture = ci;
Console.WriteLine("Current Culture: {0}",
Thread.CurrentThread.CurrentCulture.Name);
Console.WriteLine("Writing System: {0}",
Thread.CurrentThread.CurrentCulture.TextInfo);
}
}
// The example displays the following output:
// Current Culture: ru-US
// Writing System: TextInfo - ru-US
I know this isn't an ideal answer, but you could manually input the list of Hebrew months, and use DateTime.Today.Month as an index into that list. Similarly, DateTime.Today.Day and .Year give integer output that you can use. Sorry, it seems a bit wrong to roll your own formatting, doesn't it?
You could still use string.format() to ensure it looks the way you want.
You can use this (I know it's not c#, but you should be able to get what I'm doing here):
Dim c As New CultureInfo("he-IL")
c.DateTimeFormat.Calendar.ToDateTime(Now.Year, Now.Month, Now.Day, Now.Hour, Now.Minute, Now.Second, Now.Millisecond).ToString("MMMM", New CultureInfo("en-GB"))
MessageBox.Show(c.DateTimeFormat.Calendar.ToDateTime(Now.Year, Now.Month, Now.Day, Now.Hour, Now.Minute, Now.Second, Now.Millisecond).ToString("MMMM", New CultureInfo("en-GB")))
But it will give you the Gregorian Calendar name (December)
piojo's suggestion to build up a dictionary object that contains the English version of the Hebrew name might work better
I do not believe that .NET has culture information that you want to use. However, you can create your own CultureInfo and modify the DateTimeFormat to suit your needs:
var cultureInfo = CultureInfo.CreateSpecificCulture("he-IL");
cultureInfo.DateTimeFormat.Calendar = new HebrewCalendar();
cultureInfo.DateTimeFormat.AbbreviatedMonthNames = new[] {
"Translation of תשרי",
"Translation of חשון",
// 11 more elements
};
cultureInfo.DateTimeFormat.AbbreviatedMonthGenitiveNames = new[] { ... };
cultureInfo.DateTimeFormat.MonthNames = new[] { ... };
cultureInfo.DateTimeFormat.MonthGenitiveNames = new[] { ... };
(Sorry for not providing the correct translations but I do not know Hebrew.)
You can then use this cultureInfo exactly as you do in your question.
If required you can also modify the day names in a similar fashion.
It is important that the calendar is set before modifying the various month and date name properties. The number of expected entries in the month name arrays changes as the calendar changes.

US vs Non US DateTime format

My program has to be able to compare not only us style vs us style format but also us style (mm/dd/yyyy) vs non us style (dd/mm/yyyy). How to do it? So far this is what I have and it only works to compare same style:
DateTime my_dt = new DateTime(); // this can be mm/dd or dd/mm
// depending on where it run
DateTime new_dt = Convert.ToDateTime(us_dt);
int compare = DateTime.Compare(new_dt, my_dt);
when my_dt is dd/mm, I got error :
System.FormatException: String was not recognized as a valid DateTime.
at System.DateTimeParse.Parse(String s, DateTimeFormatInfo dtfi, DateTimeStyles styles)
at update.Program.Process(String ftp_path, String action)
Comparing the DateTime objects isn't the the real problem, it's the parsing. Given you have 2 strict formats here i.e. dd/mm/yyyy or mm/dd/yyyy the following should work
DateTime my_dt = null;
// parse in either US/Non-US format (culture-independant)
DateTime.ParseExact(someDateStr, new[] { "dd/MM/yyyy", "MM/dd/yyyy" }, CultureInfo.InvariantCulture, DateTimeStyles.None out my_dt);
// parse in US format (culture-dependant)
DateTime dt = DateTime.Parse(result3, new CultureInfo("en-US"));
// compare the results
int compare = DateTime.Compare(my_dt, result3);
Format is a property of datetime string representation, i.e. dt.ToString("mm/dd/yyyy").
System.DateTime is format agnostic, independent and unaware. So you can compare any two isntances of it.
Your question doesn't really illustrate what I think is your actual problem. I am guessing you have two date strings in different cultural formats and you want to compare them.
First of all, you need to know the culture or the format of the strings or else you could have unpredictable results.
Cultures can be identified by an LCID. You can find a list here.
So let's say you have a English (US) date string and a English (Canada) string, you could compare them like so:
string americanDateString = "12/31/2013";
string canadianDateString = "31/12/2013";
DateTime americanDate = DateTime.Parse(americanDateString, System.Globalization.CultureInfo.GetCultureInfo(1033); // 1033 = English - United States culture code
DateTime canadianDate = DateTime.Parse(canadianDateString, System.Globalization.CultureInfo.GetCultureInfo(4105); // 4105= English - Canada culture code
int compare = DateTime.Compare(americanDate, canadianDate);
EDIT: You can also use locale short strings (eg. "en-US" or "en-CA") to lookup the CultureInfo as per abatishchev's answer.

Strange Convert.ToDateTime behavior

Why is Convert.ToDateTime behaving strangely for the following values?
The following works just fine:
var value = "08/01/2011";
var dateTime = Convert.ToDateTime(value);
The result is: {08/01/2011 00:00:00} --- which is just expected.
But now, when I do this:
var value = "07/21/2011";
var dateTime = Convert.ToDateTime(value);
I get an exception:
'Convert.ToDateTime("07/21/2011")' threw an exception of type 'System.FormatException'
"07/21/2011";
This is not a valid date, since 21 will be interpreted as the month.
Try explicitly specifying the format instead:
DateTime myDate = DateTime.ParseExact("07/21/2011", "MM/dd/yyyy",
CultureInfo.InvariantCulture);
Edit:
Agreed with #dtb's comment - I just couldn't find a culture where the date you specified is legal. But the general form is:
DateTime myDate = Convert.ToDateTime("07/21/2011", new CultureInfo("XXX"))
where XXX is the name of the culture you want to use (i.e. "en-GB" - which won't work with this format though)
Date/time strings are parsed according to the culture settings for the current thread (which is determined by the regional settings made in the Windows Control Panel).
For example, if the current culture is fr-FR or en-GB, then the input is expected in day/month/year format. If the current culture is en-US, the input is expected in month/day/year format.
You can find the culture settings for the current thread by looking at the Thread.CultureInfo property of Thread.CurrentThread.
If you don't want to parse a date/time string according to the culture settings for the current thread, you have to specify the culture settings explicitly.
Your input seems to be in en-US format, while your system seems to be configured as fr-FR or en-GB. So explicitly specify en-US as culture:
DateTime result = DateTime.Parse("07/21/2011", new CultureInfo("en-US"));
// result.Day == 21
// result.Month == 7
// result.Year == 2011
The reason why your first example works, is because 1 is a valid month, unlike 21.
DateTime result = DateTime.Parse("08/01/2011", new CultureInfo("fr-FR"));
// result.Day == 8
// result.Month == 1
// result.Year == 2011

DateTimeFormatInfo string format for day of week. Thursday becomes Th

Is there a DateTimeFormatInfo format pattern to convert a day of week to two characters? For example Tuesday becomes Tu, Wednesday becomes We. The format string needs to conform to the DateTimeFormatInfo for date formats.
Addition:
Maybe I am looking for a solution to extend DateTimeFormatInfo to include custom formats?
The closes you can get is the "ddd" custom format specifier - this produces three lettered abbreviations, so not exactly what you want. There is nothing built in that does exactly what you want.
You can always take the first two characters of that:
DateTime.Now.ToString("ddd").Substring(0,2);
Unfortunately you can't extend DateTimeFormatInfo since it is declared as sealed.
You need to get the DateTimeFormatInfo of the culture you're working with, then modify the array of strings called AbbreviatedDayNames. After that, ddd will return Th for you.
http://msdn.microsoft.com/en-us/library/system.globalization.datetimeformatinfo.abbreviateddaynames(VS.71).aspx
DateTimeFormatInfo.AbbreviatedDayNames
Gets or sets a one-dimensional array
of type String containing the
culture-specific abbreviated names of
the days of the week.
Here's a sample of how to do it:
class Program
{
static void Main()
{
var dtInfo = new System.Globalization.DateTimeFormatInfo();
Console.WriteLine("Old array of abbreviated dates:");
var dt = DateTime.Today;
for (int i = 0; i < 7; i++)
{
Console.WriteLine(dt.AddDays(i).ToString("ddd", dtInfo));
}
// change the short weekday names array
var newWeekDays =
new string[] { "Su", "Mo", "Tu", "We", "Th", "Fr", "Sa" };
dtInfo.AbbreviatedDayNames = newWeekDays;
Console.WriteLine("New array of abbreviated dates:");
for (int i = 0; i < 7; i++)
{
Console.WriteLine(dt.AddDays(i).ToString("ddd", dtInfo));
}
Console.ReadLine();
}
}
One more note: of course, if you are constrained from providing the IFormatProvider, then you can override the current thread's CultureInfo, for example:
CultureInfo customCulture = CultureInfo.CreateSpecificCulture("en-US");
// ... set up the DateTimeFormatInfo, etc...
System.Threading.Thread.CurrentThread.CurrentCulture = customCulture;
More on CurrentCulture:
http://msdn.microsoft.com/en-us/library/system.threading.thread.currentuiculture.aspx
Thread.CurrentUICulture
Property Gets or sets the
current culture used by the Resource
Manager to look up culture-specific
resources at run time.
To use the DateTimeFormatInfo specifically you can
dtfi.GetShortestDayName(DateTime.Now.DayOfWeek);
however "ddd" is the closest you'll get for a string format
try this
string s = DateVar.ToString("ddd").SubString(0,2);
If it needs to be a FormatPattern, then try this:
var dtFI = new CultureInfo( "en-US", false).DateTimeFormat;
dtFI.DayNames = new[] {"Mo", "Tu", "We", "Th", "Fr", "Sa", "Su" };
string s = DateVar.ToString("ddd", dtFI);

Categories

Resources