How to create and use a custom IFormatProvider for DateTime?
Asked Answered
O

3

24

I was trying to create an IFormatProvider implementation that would recognize custom format strings for DateTime objects. Here is my implementation:

 public class MyDateFormatProvider : IFormatProvider, ICustomFormatter
 {
  public object GetFormat(Type formatType)
  {
   if (formatType == typeof(ICustomFormatter))
   {
    return this;
   }
   return null;
  }

  public string Format(string format, object arg, IFormatProvider formatProvider)
  {
   if(arg == null) throw new ArgumentNullException("arg");
   if (arg.GetType() != typeof(DateTime)) return arg.ToString();
   DateTime date = (DateTime)arg;
   switch(format)
   {
    case "mycustomformat":
     switch(CultureInfo.CurrentCulture.Name)
     {
      case "en-GB":
       return date.ToString("ddd dd MMM");
      default:
       return date.ToString("ddd MMM dd");
     }
    default:
     throw new FormatException();
   }
  } 

I was expecting to be able to use it in the DateTime.ToString(string format, IFormatProvider provider) method like so, but :

DateTime d = new DateTime(2000, 1, 2);
string s = d.ToString("mycustomformat", new MyDateFormatProvider());

In that example, running in the US Culture, the result is "00cu0Ao00or0aA", apparently because the standard DateTime format strings are being interpreted.

However, when I use the same class in the following way:

DateTime d = new DateTime(2000, 1, 2);
string s = String.Format(new MyDateFormatProvider(), "{0:mycustomformat}", d);

I get what I expect, namely "Sun Jan 02"

I don't understand the different results. Could someone explain?

Thanks!

Omsk answered 4/3, 2010 at 19:34 Comment(0)
R
11

Checking the DateTime.ToString method with Reflector shows that the DateTime structure uses the DateTimeFormatInfo.GetInstance method to get the provider to be used for formatting. The DateTimeFormatInfo.GetInstance requests a formatter of type DateTimeFormatInfo from the provider passed in, never for ICustomFormmater, so it only returns an instance of a DateTimeFormatInfo or CultureInfo if no provider is found. It seems that the DateTime.ToString method does not honor the ICustomFormatter interface like the StringBuilder.Format method does, as your String.Format example shows.

I agree that the DateTime.ToString method should support the ICustomFormatter interface, but it does not seem to currently. This may all have changed or will change in .NET 4.0.

Ringworm answered 4/3, 2010 at 20:7 Comment(1)
Thanks for the detailed reply. I was thinking I was misunderstanding the interface and expected implementation, so it's good to know that it's the DateTime implementation that's wacky :-)Omsk
B
22

The short explanation is that while

DateTime.ToString(string format, IFormatProvider provider)

lets you pass anything implementing IFormatProvider as one of its parameters, it actually only supports 2 possible types implementing IFormatProvider inside its code:

DateTimeFormatInfo or CultureInfo

If your parameter cannot be casted (using as) as either or those, the method will default to CurrentCulture.

String.Format is not limited by such bounds.

Bottomless answered 4/3, 2010 at 20:26 Comment(0)
R
11

Checking the DateTime.ToString method with Reflector shows that the DateTime structure uses the DateTimeFormatInfo.GetInstance method to get the provider to be used for formatting. The DateTimeFormatInfo.GetInstance requests a formatter of type DateTimeFormatInfo from the provider passed in, never for ICustomFormmater, so it only returns an instance of a DateTimeFormatInfo or CultureInfo if no provider is found. It seems that the DateTime.ToString method does not honor the ICustomFormatter interface like the StringBuilder.Format method does, as your String.Format example shows.

I agree that the DateTime.ToString method should support the ICustomFormatter interface, but it does not seem to currently. This may all have changed or will change in .NET 4.0.

Ringworm answered 4/3, 2010 at 20:7 Comment(1)
Thanks for the detailed reply. I was thinking I was misunderstanding the interface and expected implementation, so it's good to know that it's the DateTime implementation that's wacky :-)Omsk
S
3

Use extension method :)

public static class FormatProviderExtension
    {
        public static string FormatIt(string format, object arg, IFormatProvider formatProvider)
        {
            if (arg == null) throw new ArgumentNullException("arg");
            if (arg.GetType() != typeof(DateTime)) return arg.ToString();
            DateTime date = (DateTime)arg;
            switch (format)
            {
                case "mycustomformat":
                    switch (CultureInfo.CurrentCulture.Name)
                    {
                        case "en-GB":
                            return date.ToString("ddd dd MMM");
                        default:
                            return date.ToString("ddd MMM dd");
                    }
                default:
                    throw new FormatException();
            }
        }

        public static string ToString(this DateTime d, IFormatProvider formatProvider, string format)
        {
            return FormatIt(format, d, formatProvider);
        }
    }
Savoury answered 4/3, 2010 at 20:14 Comment(1)
Yep, looks like extension methods are my only option here. Thanks for the reply!Omsk

© 2022 - 2024 — McMap. All rights reserved.