views:

216

answers:

7

.Net has the built in ToShortTimeString() function for DateTime that uses the CultureInfo.CurrentCulture.DateTimeFormat.ShortTimePattern format. It returns something like this for en-US: "5:00 pm". For a 24 hour culture such as de-DE it would return "17:00".

What I want is a way to just return just the hour (So "5 pm" and "17" in the cases above) that works with every culture. What's the best/cleanest way to do this?

Thanks!

A: 

you may use DateTime.ToString() and provide format tyou want as an argument.

Arseny
A: 
var culture = CultureInfo.CurrentCulture;
bool uses24HourClock = string.IsNullOrEmpty(culture.DateTimeFormat.AMDesignator);

var dt = DateTime.Now;
string formatString = uses24HourClock ? "HH" : "h tt";
Console.WriteLine(dt.ToString(formatString, culture));

Sam's edit:

Here's code to prove this doesn't work.

var date = new DateTime(2010, 1, 1, 16, 0, 0);

foreach (CultureInfo cultureInfo in CultureInfo.GetCultures(CultureTypes.InstalledWin32Cultures))
{
    bool amMethod = String.IsNullOrEmpty(cultureInfo.DateTimeFormat.AMDesignator);
    bool formatMethod = cultureInfo.DateTimeFormat.ShortTimePattern.Contains("H");

    if (amMethod != formatMethod)
    {
        Console.WriteLine("**** {0} AM: {1} Format: {2} Designator: {3}  Time: {4}",
                          cultureInfo.Name,
                          amMethod,
                          formatMethod,
                          cultureInfo.DateTimeFormat.AMDesignator,
                          date.ToString("t", cultureInfo.DateTimeFormat));
    }
}
simendsjo
This does not work in many cultures. Often AMDesignator is defined as '?????????' instead of being null or empty in cultures that use 24 hour time. Also many cultures that use 24 hour time still have AMDesignator specified.
Sam
Ah. I just checked one culture and found out AM/PM was blank. Just assumed this was consistent on all cultures.
simendsjo
Some cultures are even weirder. Example: af-ZA has no AMDesignator, but has a PMDesignator, and ShortTimePattern of HH:mm tt...
code4life
+3  A: 

I would check to see whether CultureInfo.CurrentCulture.DateTimeFormat.ShortDatePattern contains "h", "hh", "H", "HH", "t" or "tt", and in what order, and then build your own custom format string from those.

e.g.

  • en-US: map "h:mm tt" to "h tt"
  • ja-JP: map "H:mm" to "H"
  • fr-FR: map "HH:mm" to "HH"

Then use .ToString(), passing in the string you built.

Example code - this basically strips out everything that's not t, T, h, H, and multiple spaces. But, as pointed out below, just a string of "H" could fail...

string full = System.Globalization.CultureInfo.CurrentCulture.DateTimeFormat.ShortTimePattern;
string sh = String.Empty;
for (int k = 0; k < full.Length; k++)
{
    char i = full[k];
    if (i == 'h' || i == 'H' || i == 't' || i == 'T' || (i == ' ' && (sh.Length == 0 || sh[sh.Length - 1] != ' ')))
    {
        sh = sh + i;
    }
}
if (sh.Length == 1)
{
  sh = sh + ' ';
  string rtnVal = DateTime.Now.ToString(sh);
  return rtnVal.Substring(0, rtnVal.Length - 1);
{
else
{
    return DateTime.Now.ToString(sh);
}
Rawling
The trouble I'm running into is that DateTime.Now.ToString("H") throws a format exception and I'm not sure why.
Greg
Yeah, I just mocked up some code and you're right :-/
Rawling
Yeah I noticed that too. Very strange. I found adding a space to the end fixes it: DateTime.Now.ToString("H ").TrimEnd(' '). But that's pretty ugly..
InvisibleBacon
Well, I've just put that in. Ugly, but if it works...
Rawling
So have you verified that the format exception has to do with the format string having a length of 1 then? Do you have a source?
InvisibleBacon
There's a format string of "h:mm.tt" in the system. I'm not sure if the correct output is "h tt" or "h.tt". maybe it doesn't matter.
Greg
The MSDN article on the DateTime.ToString(string) function says a FormatException is thrown if (a) The length of format is 1, and it is not one of the format specifier characters defined for DateTimeFormatInfo or (b) format does not contain a valid custom format pattern. (a) seems to cover it.
Rawling
Single character format string refers to standard format. e.g. d=Short date, D=Long date. There is no standard "H" format so you get an exception.
adrianm
Greg: good spot. Maybe something more complex is needed to decide which 'separators' to include and which to ignore.
Rawling
A: 

Use this:

bool use2fHour =
    CultureInfo
        .CurrentCulture
        .DateTimeFormat
        .ShortTimePattern.Contains("H");
Sam
+2  A: 
// displays "15" because my current culture is en-GB
Console.WriteLine(DateTime.Now.ToHourString());

// displays "3 pm"
Console.WriteLine(DateTime.Now.ToHourString(new CultureInfo("en-US")));

// displays "15"
Console.WriteLine(DateTime.Now.ToHourString(new CultureInfo("de-DE")));

// ...

public static class DateTimeExtensions
{
    public static string ToHourString(this DateTime dt)
    {
        return dt.ToHourString(null);
    }

    public static string ToHourString(this DateTime dt, IFormatProvider provider)
    {
        DateTimeFormatInfo dtfi = DateTimeFormatInfo.GetInstance(provider);

        string format =
            Regex.Replace(dtfi.ShortTimePattern, @"[^hHt\s]|\s(?=\s)", "");

        if (format.Length == 1)
            format += ' ';

        return dt.ToString(format, dtfi).Trim();
    }
}
LukeH
+1, this is very clean. Be aware, as has been pointed out, that a FormatException is thrown if "the length of format is 1, and it is not one of the format specifier characters defined for DateTimeFormatInfo". Also maybe strip out repeat spaces. Also (just querying, I've not seen it before) do you need the null check on provider - from the documentation I think .GetInstance returns CurrentInfo if provider is null?
Rawling
This is basically the same solution that Rawling suggested, but using Regex. Is there a good way to ensure no repeated spaces using the regex?
InvisibleBacon
@Rawling, @InvisibleBacon: Fixed. Now normalises repeated whitespace and handles the case where `format.Length` is 1. I also removed the unnecessary null check.
LukeH
Ooh, I've not seen ?= before. Nifty. But does your Trim() take into account the situation where the actual output format string begins or ends with a space, in the case that this needs to be preserved? </nitpick> (Honestly, I'd rather he goes for your solution, it's a lot cleaner, presented as extension methods, etc.)
Rawling
@Rawling: It doesn't handle that, but would you ever want to preserve those leading/trailing spaces?
LukeH
As per Rawling's suggestion, I've marked this as the answer. This should work fine for my purposes. I think I'll split the function into two operations: one that gets the current hour format, and one that uses it because I will performing this operation on many DateTimes.
InvisibleBacon
A: 

Ugh, I didn't want to be interested but now I am! Here's the code which respects all cultures and renders the AM/PM designators in the correct position, as well as recognizing 24-hour format, all depending on the culture.

Basically, this static extension method is overloaded to take the current culture (no parameters) or a specified culture.

DateTime.Now.ToTimeString()
DateTime.Now.ToTimeString(someCultureInfo)

Code is below, includes sample program:

    public static class DateTimeStaticExtensions
    {
        private static int GetDesignatorIndex(CultureInfo info)
        {
            if (info.DateTimeFormat
                .ShortTimePattern.StartsWith("tt"))
            {
                return 0;
            }
            else if (info.DateTimeFormat
                .ShortTimePattern.EndsWith("tt"))
            {
                return 1;
            }
            else
            {
                return -1;
            }
        }

        private static string GetFormattedString(int hour, 
            CultureInfo info)
        {
            string designator = (hour > 12 ? 
                info.DateTimeFormat.PMDesignator : 
                info.DateTimeFormat.AMDesignator);

            if (designator != "")
            {
                switch (GetDesignatorIndex(info))
                {
                    case 0:
                        return string.Format("{0} {1}",
                            designator, 
                            (hour > 12 ? 
                                (hour - 12).ToString() : 
                                hour.ToString()));
                    case 1:
                        return string.Format("{0} {1}",
                            (hour > 12 ? 
                                (hour - 12).ToString() :
                                hour.ToString()), 
                            designator);
                    default:
                        return hour.ToString();
                }
            }
            else
            {
                return hour.ToString();
            }
        }

        public static string ToTimeString(this DateTime target, 
            CultureInfo info)
        {
            return GetFormattedString(target.Hour, info);
        }

        public static string ToTimeString(this DateTime target)
        {
            return GetFormattedString(target.Hour, 
                CultureInfo.CurrentCulture);
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            var dt = new DateTime(2010, 6, 10, 6, 0, 0, 0);

            CultureInfo[] cultures = 
                CultureInfo.GetCultures(CultureTypes.SpecificCultures);
            foreach (CultureInfo culture in cultures)
            {
                Console.WriteLine(
                    "{0}: {1} ({2}, {3}) [Sample AM: {4} / Sample PM: {5}",
                    culture.Name, culture.DateTimeFormat.ShortTimePattern,
                    (culture.DateTimeFormat.AMDesignator == "" ? 
                        "[No AM]": 
                        culture.DateTimeFormat.AMDesignator),
                    (culture.DateTimeFormat.PMDesignator == "" ? 
                        "[No PM]": 
                        culture.DateTimeFormat.PMDesignator),
                    dt.ToTimeString(culture),  // AM sample
                    dt.AddHours(12).ToTimeString(culture) // PM sample
                    );
            }

            // pause program execution to review results...
            Console.WriteLine("Press enter to exit");
            Console.ReadLine();
        }
    }
code4life
A: 

Try using DateTime.Hour property.

Brian