Let's play the "feature or creature" game again. It works like this - I'll describe a software problem and we'll try to decide if the behaviour was intentional (a feature) or a bug (a creature).
Today's problem - a hospital in Italy has some software that doesn't like the Italian time format. Italy uses a period as the separator when time is displayed.
So they change the time separator from the default period (.) to a colon (:).
Now their software is happy. But ours isn't. We still display the original format.
And to make it worse, when we try to sort our table by date or time, we crash!
What's going on? There are a few things here. First of all, the crash happens because we're taking the time string (e.g., "8.41") and trying to turn it back into a DateTime, using Convert.ToDateTime(), and the format doesn't match. The Convert is expecting "8:41" and we've got "8.41".
We're displaying the time by using the .NET DateTime.ToShortTimeString() method. This doesn't pick up the change in the time separator. So I try using ToString() with the short time format from Thread.CurrentThread.CurrentCulture.DateTimeFormat.ShortTimePattern, but it has the same problem. What strike me as odd, though, is that the LongTimePattern does pick up the correct time separator. I'm going to vote "creature" on that one. I think the time separator should be reflected in both the Short and Long time patterns.
Anyway, it's not, so how do we get this to work? The ideal situation would be to display the time according to the user's customized short time format. That will also fix our crash. But ToShortTimeString() and ToString() don't work. Time to hit the Google. And we stumble upon Michael Kaplan's fine blog, where he tells us all about the problem in these posts.
So now we know what to do - use GetTimeFormat() to yank out the LongTimePattern, but ignoring the seconds. There's one other catch - sometimes we want to show the AM/PM marker, and sometimes we don't. In the case where we are using English (US), and the user has changed the time format to use the 24 hour clock, we'd prefer to show 18:00 instead of 18:00 PM. Anything to keep me out of Guantanamo Bay a little longer...
Here's the code:
/// <summary>
/// the user locale
/// </summary>
private const int LOCALE_USER_DEFAULT = 0x400;
/// <summary>
/// we don't care about seconds (we're trying to mimic the short time)
/// </summary>
private const int TIME_NOSECONDS = 0x2;
/// <summary>
/// leave off the AM/PM marker
/// </summary>
private const int TIME_NOTIMEMARKER = 0x4;
/// <summary>
/// the win32 SYSTEMTIME structure
/// </summary>
[StructLayout(LayoutKind.Sequential)]
private struct SystemTime
{
[MarshalAs(UnmanagedType.U2)]
public ushort Year;
[MarshalAs(UnmanagedType.U2)]
public ushort Month;
[MarshalAs(UnmanagedType.U2)]
public ushort DayOfWeek;
[MarshalAs(UnmanagedType.U2)]
public ushort Day;
[MarshalAs(UnmanagedType.U2)]
public ushort Hour;
[MarshalAs(UnmanagedType.U2)]
public ushort Minute;
[MarshalAs(UnmanagedType.U2)]
public ushort Second;
[MarshalAs(UnmanagedType.U2)]
public ushort Milliseconds;
}
/// <summary>
/// formats time as a time string for the locale specified
/// </summary>
/// <param name="locale">the locale</param>
/// <param name="dwFlags">options</param>
/// <param name="time">the time information to format</param>
/// <param name="format">format picture to use to format the time string</param>
/// <param name="sb">a buffer in which this function retrieves the formatted time string</param>
/// <param name="sbSize">size of the buffer</param>
/// <returns>number of characters in the buffer</returns>
[DllImport("kernel32.dll")]
static extern int GetTimeFormat(uint locale, uint dwFlags, ref SystemTime time, string format, StringBuilder sb, int sbSize);
/// <summary>
/// format a time to a nice display time
/// </summary>
/// <param name="hour">hour</param>
/// <param name="minute">minute</param>
/// <param name="second">second</param>
/// <returns>nicely formatted time, according to the user's regional settings</returns>
public static string FormatTime(int hour, int minute, int second)
{
// fill in the system time structure with the values we were given
SystemTime systemTime = new SystemTime();
systemTime.Hour = (ushort) hour;
systemTime.Minute = (ushort) minute;
systemTime.Second = (ushort) second;
StringBuilder sb = new StringBuilder();
sb.Append('\0', 64);
// if we're using 24 hour time, leave off the AM/PM marker
string pattern = Thread.CurrentThread.CurrentCulture.DateTimeFormat.LongTimePattern;
uint flags = TIME_NOSECONDS;
if (pattern.StartsWith("H"))
{
flags = TIME_NOSECONDS | TIME_NOTIMEMARKER;
}
GetTimeFormat(LOCALE_USER_DEFAULT, flags, ref systemTime, pattern, sb, sb.Length);
return sb.ToString();
}
And finally, the finished product.
0 comments:
Post a Comment