C#’s built in culture support is great. Just call ToString() on any DateTime object and it will be converted into a localized string with the appropriate formatting for the client’s locale. Take a look:
|English||en||12/15/2014 10:48:51 PM|
|Arabic||ar||23/02/36 10:48:51 م|
Not only is the order different, but we’ve got slashes, dashes and periods. Fortunately it’s all taken care of for us! But while that’s great for the front-end, having the back-end pass around these localized date strings is just asking for trouble.
Of course, the Right Way™ to format DateTime strings for back-end use is with the Round-trip format specifier (“O”), which creates an unambiguous, Culture-invariant, timezone aware and ISO 8601-complaint string representation (which looks something like
But alas, not every application was written to emit/accept ISO 8601-formatted datetime strings. And so, dear reader, I now present one of the many pitfalls to doing custom format strings correctly. Here is the gist of some code I encountered recently:
WebService.ExternalCall(someData, timestamp.ToString("MM/dd/yyyy hh:mm:ss"));
Not too bad really, there might be some issues if the app happens to run in a different timezone than the web service, but at least that custom format string will make sure those timestamps are always in the same format. Except it doesn’t. Soon we start finding entries like this
12-15-2014 22:48:51. Dashes?!! WTF, we clearly specified *slashes*!
Turns out the “/” character in a DateTime format string is actually just a placeholder for the localized date separator. It will be replaced by whatever the client set it to. Keep in mind that users are not locked into the regional defaults and are free to customize their own date/time settings.
To make the “/” to always be a slash character, we have to either escape it or explicitly override the client’s culture (the same goes for the time separator, “:”, although none of the default Cultures/Locales use anything other than “:”):
timestamp.ToString("MM/dd/yyyy hh:mm:ss", DateTimeFormatInfo.InvariantInfo);
Seriously though, just use “O” if at all possible. It’s quick, easy and solves this problem and many others.