If you deal with dates and cater to a world-wide audience, chances are that you understand the importance of properly formatted dates based both on the culture and using a universal, culturally-insensitive, or invariant, culture.
Much of the time you’ll be dealing with the DateTimeFormatInfo class when formatting dates. It implements the IFormatProvider interface, so an instance can be provided to any of the plethora of formatting methods that accept that interface.
There are a number of ways to get an instance of DateTimeFormatInfo, and we’re going to look at those different ways right now. The impact of this information is not significant, it is being presented to merely satisfy the reader’s nagging curiosity about some .NET internals, or perhaps assist in the solving of some very strange problem that is occurring due to advanced use of the framework.
I hope I’m not surprising you with this, but dates are formatted differently based on what part of the world you live in. During the creation of a DateTimeFormatInfo instance, it is given a specific culture it will represent throughout its life-cycle.
What if we initialize an object on our own?
DateTimeFormatInfo dtfi = new DateTimeFormatInfo();
This creates a culturally-invariant DateTimeFormatInfo that adheres to a Gregorian calendar. You’re pretty much stuck after this, you won’t be able to change the culture of this object directly.
An overload for the DateTimeFormatInfo constructor exists that allows you to provide the culture and calendar, however it is internal. This is a good thing however, as that would be a painful way to get this object.
It hopefully won’t take you long until you notice that the DateTimeFormatInfo class offers several static properties that are of some interest to us: CurrentInfo and InvariantInfo.
This looks a bit similar to the CultureInfo class, specifically the CurrentCulture and InvariantCulture properties.
Another similarity to the CultureInfo class is that CultureInfo also implements the IFormatProvider interface. On top of that, CultureInfo provides an instance of DateTimeFormatInfo with the DateTimeFormat property.
The fact that both CultureInfo and DateTimeFormatInfo implement the IFormatProvider interface may have prompted you in the past to wonder which object should be used when dealing with a method that accepts the IFormatProvider interface. What are the implications of using one over the other, and how are the four different ways of ultimately getting a DateTimeFormatInfo object that are listed above different from eachother?
DateTimeFormatInfo.CurrentInfo
This will directly return another DateTimeFormatInfo instance that is set to the current culture. It does the following in order to do this:
- Gets the CultureInfo from the current thread’s CurrentCulture property (Thread.CurrentThread.CurrentCulture). Note that when you are accessing the current thread’s CurrentCulture property, you are basically accessing the CultureInfo.CurrentCulture property (which simply returns Thread.CurrentThread.CurrentCulture).
- Returns a direct reference to the date time format if the thread’s current culture instance is the base CultureInfo type itself, and not a derivation. The DateTimeFormat property of the CultureInfo object does not come into play here; instead, what we’re directly reading from the CultureInfo object’s dateTimeInfo private field.
- If the thread’s current culture instance has null assigned to its date time information field, then the CultureInfo‘s GetFormat method is invoked, which returns a DateTimeFormatInfo instance which is then returned.
- If the current thread’s culture instance is actually an object type that derives from CultureInfo, then it will always make a call to CultureInfo.GetFormat, and never read from the CultureInfo.dateTimeInfo field directly.
So, if you’re ever dealing with objects deriving from CultureInfo and setting the thread’s current culture to use them, be aware that you can modify the way the DateTimeFormatInfo is returned by overriding the CultureInfo’s GetFormat method.
DateTimeFormatInfo.InvariantInfo
This will return another DateTimeFormatInfo instance set to the culturally-insensitive culture. It does the following to do this:
- If the current Application Domain already has created a culturally-insensitive DateTimeFormatInfo instance, this is returned.
- Otherwise, a new instance is created using the public default constructor of DateTimeFormatInfo.
- The Calendar exposed by new DateTimeFormatInfo instance is set to read only.
- The new DateTimeFormatInfo instance itself is set to readonly, and then returned.
So, unlike DateTimeFormatInfo.CurrentInfo, the InvariantInfo property is initialized once per application domain, not per thread. Not a very big surprise, but may be important to know just in the event you’re doing anything adventurous and (most likely) irresponsible.
CultureInfo.CurrentCulture.DateTimeFormat
This will return a DateTimeFormatInfo object set to use the current culture. This is achieved by the following:
- Because we’re accessing CultureInfo.CurrentCulture, the current thread’s CurrentCulture is used as the culture (Thread.CurrentThread.CurrentCulture).
- If the current thread’s culture instance already has a DateTimeFormatInfo instance initialized, that’ll be returned (i.e. if the dateTimeInfo is not null, then that is returned).
- Otherwise, a new DateTimeFormatInfo object is created by directly initializing a new DateTimeFormatInfo instance and passing the culture data to the constructor.
- The DateTimeFormatInfo instance is set to readonly.
- A call is made to Thread.MemoryBarrier, and then the newly initialized DateTimeFormatInfo instance is assigned to the dateTimeInfo field.
How Does CultureInfo.CurrentCulture.DateTimeFormat Relate to DateTimeFormat.CurrentInfo?
- The CultureInfo.CurrentCulture.DateTimeFormat property is what is responsible for actually initializing the DateTimeFormatInfo instance that gets returned; calling DateTimeFormat.CurrentInfo results in a call made to the culture’s GetFormat method, which itself will return its own CultureInfo.DateTimeFormat property. So, regardless of where you start, you seem to end up in the same place. Technically, calling CultureInfo.CurrentCulture.DateTimeFormat is more direct than calling DateTimeFormat.CurrentInfo, which will end up calling just that.
- Differences begin to arise when one starts talking about object types derived from CultureInfo. Let’s say we wish for our custom CultureInfo class to return a DateTimeFormatInfo instance which is modified in some fashion that differs from the norm. By override CultureInfo.GetFormat, which is a virtual method, we can achieve this for calls made to DateTimeFormat.CurrentInfo, which will call this method.
- However, because CultureInfo.DateTimeFormat is a static property, and because the instantiation of the DateTimeFormatInfo object is done directly inside this property, there is no way to override this behavior. Therefore, we can easily end up in a situation where DateTimeFormatInfo.CurrentInfo returns a differently composed DateTimeFormatInfo instance than what we get with CultureInfo.CurrentCulture.DateTimeFormat (assuming the current culture is our custom culture class).
- This makes our desire of ours one that may be impossible to fulfill (customizing the creation-related logic behind a custom culture’s date time format). Hopefully no one out there needs to do this.
CultureInfo.InvariantCulture.DateTimeFormat
- This will return a culturally-insensitive DateTimeFormatInfo.
- The invariant culture is created one per application domain, inside (indirectly) the static constructor of the CultureInfo class.
- Once the invariant CultureInfo instance is created, the rest of the process of creating the DateTimeFormatInfo mirrors that of the previous section.
How Does CultureInfo.InvariantCulture.DateTimeFormat Relate to DateTimeFormat.InvariantInfo?
- These two properties, unlike their CurrentCulture-ish brethren, are completely unrelated in how they initialize the DateTimeFormatInfo instance that they return.
- The DateTimeFormat.InvariantInfo property does not actually deal with culture at all. It simply initializes a new DateTimeFormatInfo instance using that class’s default constructor.
- Although their creation mechanisms are unlinked, they are similar in that one cannot exert any influence in the creation of these constructs from derived classes, as far as I am aware.
So what should I use, and when!?!?
Whether you should use the invariant or culturally-sensitive variant of DateTimeFormatInfo is outside the scope of this article, and has plenty of coverage.
That aside, the question you may have is, is it preferable to get a DateTimeFormatInfo instance using the static properties of the DateTimeFormatInfo class, or the CultureInfo class?
Basically, if you are primarily dealing with culture as far as what is set on the thread, you might as well just stick to using the CultureInfo static properties if you’re looking to be consistent. I find the presence of these static properties on the DateTimeFormatInfo class to be slightly redundant.
The difference in performance between the two is laughable, and barely worth discussing; but, technically speaking, again, the CultureInfo static properties are a fine choice. Things do change if you are deriving the CultureInfo class, but then again, this article should be a good case study in why that may be a bad idea.
What About Methods that Accept IFormatProvider?
Since both CultureInfo and DateTimeFormatInfo implement this interface, does it matter which one you provide?
Well, that obviously depends on the specific method that is accepting the format provider. In the case of DateTime.ToString(IFormatProvider), it invokes the static GetInstance method belonging to the DateTimeFormatInfo class, using what that returns as the format provider.
The GetInstance simply determines if it is dealing with a CultureInfo instance (and specifically a non-derived type) or a DateTimeFormatInfo instance.
If it determines that the provided IFormatProvider is neither a strictly base type CultureInfo or DateTimeFormatInfo instance, it will invoke the GetFormat method of the provided IFormatProvider in an attempt to have that return a DateTimeFormatInfo instance. If that fails to happen (returns null), then it will default to the DateTimeFormatInfo.CurrentInfo property.
This process obviously may not be consistent across different objects.
Regardless, at least as far as DateTime is concerned, you can feel free to pass in the requirements demand. Obviously, if you are trying to use a DateTimeFormatInfo which differs from either the current thread’s culture or whatever culture you have on hand, you will want to pass in that DateTimeFormatInfo instance. Otherwise, you may as well just pass in the CultureInfo as that is typically the first thing checked anyway, and less typing is involved for that in most circumstances.
That’s it. It is good to understand what you are actually working with; better than being a fool, right?