Question

I get a time format string passed to me in a string. It could be a standard string like "t", but could be a custom one (containing "HH", etc). How can I find out, that if displayed it'll be a 12-hour or 24-hour format? (depending on culture and everything...)

Was it helpful?

Solution

The easiest way is to try a date that you can then examine for a given number in the string, that you'd only expect that way. E.g. whether 2000-01-01T19:00:00Z resulted in a string containing a 7 or a 9 or both.

To be certain in the face of even strange strings like "'This valid string will mess you up 79'", then you need to examine the string, after obtaining the full version for the given culture info if needed.

The following would also make sense as an extension method on CultureInfo or DateTimeFormatInfo.

In practice, you can simplify by putting those standard formats that normally include only date-info in the group that returns None immediately, but I decided to catch even strangeness there:

[Flags]
public enum HourRepType
{
  None = 0,
  Twelve = 1,
  TwentyFour = 2,
  Both = Twelve | TwentyFour
}
public static HourRepType FormatStringHourType(string format, CultureInfo culture = null)
{
  if(string.IsNullOrEmpty(format))
    format = "G";//null or empty is treated as general, long time.
  if(culture == null)
    culture = CultureInfo.CurrentCulture;//allow null as a shortcut for this
  if(format.Length == 1)
    switch(format)
    {
      case "O": case "o": case "R": case "r": case "s": case "u":
        return HourRepType.TwentyFour;//always the case for these formats.
      case "m": case "M": case "y": case "Y":
        return HourRepType.None;//always the case for these formats.
      case "d":
          return CustomFormatStringHourType(culture.DateTimeFormat.ShortDatePattern);
      case "D":
        return CustomFormatStringHourType(culture.DateTimeFormat.LongDatePattern);
      case "f":
        return CustomFormatStringHourType(culture.DateTimeFormat.LongDatePattern + " " + culture.DateTimeFormat.ShortTimePattern);
      case "F":
        return CustomFormatStringHourType(culture.DateTimeFormat.FullDateTimePattern);
      case "g":
        return CustomFormatStringHourType(culture.DateTimeFormat.ShortDatePattern + " " + culture.DateTimeFormat.ShortTimePattern);
      case "G":
        return CustomFormatStringHourType(culture.DateTimeFormat.ShortDatePattern + " " + culture.DateTimeFormat.LongTimePattern);
      case "t":
        return CustomFormatStringHourType(culture.DateTimeFormat.ShortTimePattern);
      case "T":
        return CustomFormatStringHourType(culture.DateTimeFormat.LongTimePattern);
      default:
        throw new FormatException();
    }
  return CustomFormatStringHourType(format);
}
private static HourRepType CustomFormatStringHourType(string format)
{
  format = new Regex(@"('.*')|("".*"")|(\\.)").Replace(format, "");//remove literals
  if(format.Contains("H"))
    return format.Contains("h") ? HourRepType.Both : HourRepType.TwentyFour;
  return  format.Contains("h") ? HourRepType.Twelve : HourRepType.None;
}

OTHER TIPS

You could format a known date using the string, one which you know will only contain a particular value if it's using 24 hour time:

if ((new DateTime(1, 1, 1, 23, 1, 1)).ToString(formatString).Contains("23"))
    Console.WriteLine("24");
else
    Console.WriteLine("12");

Step 1: If necessary, turn a standard format string into the selected culture's equivalent custom format string. This uses reflected logic to mimic DateTime.ToString.

if (formatString.Length == 1)
{        
    // Get formatter for the culture of your choice - e.g. the current culture
    DateTimeFormatInfo fi = CultureInfo.CurrentCulture.DateTimeFormat;

    formatString = epf.Invoke(
        null,
        new object[]{
            formatString,
            DateTime.MinValue,
            fi,
            TimeSpan.MinValue});
}

Step 2: parse the string to see if it contains 12- or 24- hour specifiers. This is an unfortunate mix of using reflected logic to mimic DateTime.ToString as far as possible and custom logic where not possible, but it seems to work OK.

for (int i = 0; i < formatString.Length; i++)
{
    char current = formatString[i];
    if (current == '"' || current == '\'') // Skip literal quoted sections
    {
        i += (int)pqs.Invoke(
            null,
            new object[] {
                formatString,
                i,
                new StringBuilder()});
    }
    else if (current == '\\') // Skip escaped characters
    {
        i+= 1;
    }
    else if (current == 'h')
    {
        is12Hour = true;
    }
    else if (current == 'H')
    {
        is24Hour = true;
    }
}

This relies on the following two MethodInfos gained by reflection:

var t = Assembly
    .GetAssembly(typeof(System.DateTime))
    .GetType("System.DateTimeFormat");
var epf = t.GetMethod(
    "ExpandPredefinedFormat",
    BindingFlags.Static | BindingFlags.NonPublic);
var pqs = t.GetMethod(
    "ParseQuoteString",
    BindingFlags.Static | BindingFlags.NonPublic);

The first handles turning a single-character standard format specifier into a multiple-character custom format specifier for a given culture. The second handles quoted literals inside a custom format specifier.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top