Introduction
(If you can use .NET 3.5, then this code has been superceded, by a built-in class. Thanks to neilio for pointing this out.)
Elsewhere on this site is a long winded explanation about how Windows actually knows about more than UTC and your local timezone (and a class TimeZoneInformation
that implements conversion to/from any timezone to UTC). My problem was that my server is in the central timezone, but the users can be anywhere in the world, and I wanted them to see the time of their transactions in their local time. I wrote this utility class that uses TimeZoneInformation
but presents a simpler interface.
GetTZList(System.Collections.ArrayList Indexes, System.Collections.ArrayList Names)
This method returns a list of timezone names and timezone IDs in a sorted order, just like the Windows timezone picker when you are setting your clock.
In a web application, I use this function to populate a dropdown list to allow the user to pick his/her local timezone.
System.DateTime MakeDateTime(int TimeZoneID, string s)
Use this function to convert a date in the format "2005/03/16 08:43am" (in the timezone specified by TimeZoneID
) into the equivalent UTC.
If the time string is already in UTC, set TimeZoneID
to -1.
DisplayDateTime(int TimeZoneID, System.DateTime dt, bool IncludeDayName)
Convert a UTC to the specified timezone. Set TimeZoneID
to -1 if you want the display in UTC. You can have the three letter abbreviation of the day name tacked on the end if IncludeDayName
is true
.
DisplayXmlDateTime(System.DateTime dt)
If you are creating XML documents, this function will return the datetime in the "approved" XML format.
DisplayLocalTimeByBrowser(System.DateTime dt, bool IncludeDayName)
In the discussions with "S Rijken", it is understood that there seems to be some merit in letting the browser figure out the local time. Use this method only if the target browser supports JavaScript (i.e., not your phone browser). The advantage is that you never have to ask the viewer in what time zone he/she resides in. Usage:
<%=TXConvert.DisplayLocalTimeByBrowser(dt, true)%>
DisplayLocalTimeByBrowserHelper()
This function generates some JavaScript code which you need to inject into the <head>
section of the page. Only insert this code once. Usage:
<head>... <%=TZConvertDisplayLocalTimeByBrowserHelper()%>....</head>
The namespace below includes the code previously published on this site, so this is all you need to cut and paste to get up and running:
using System;
using System.Collections;
using System.Runtime.InteropServices;
using Microsoft.Win32;
namespace PTimeZoneInformation
{
class TimeZoneInformation
{
private TimeZoneInformation()
{
}
private static TimeZoneInformation[] s_zones = null;
private static readonly object s_lockZones = new object();
public static TimeZoneInformation CurrentTimeZone
{
get
{
TIME_ZONE_INFORMATION tziNative;
TimeZoneInformation[] zones = EnumZones();
NativeMethods.GetTimeZoneInformation(out tziNative);
for (int idx = 0; idx < zones.Length; ++idx)
{
if (zones[idx].m_tzi.bias == tziNative.Bias &&
zones[idx].m_tzi.daylightBias == tziNative.DaylightBias &&
zones[idx].m_tzi.standardBias == tziNative.StandardBias &&
zones[idx].m_standardName == tziNative.StandardName &&
zones[idx].m_daylightName == tziNative.DaylightName)
{
return zones[idx];
}
}
return null;
}
}
public static TimeZoneInformation FromIndex(int index)
{
TimeZoneInformation[] zones = EnumZones();
for (int i = 0; i < zones.Length; ++i)
{
if (zones[i].Index == index)
return zones[i];
}
throw new ArgumentOutOfRangeException("index",
index, "Unknown time zone index");
}
public static TimeZoneInformation[] EnumZones()
{
if (s_zones == null)
{
lock (s_lockZones)
{
if (s_zones == null)
{
ArrayList zones = new ArrayList();
using (RegistryKey key =
Registry.LocalMachine.OpenSubKey(
@"SOFTWARE\Microsoft\Windows NT" +
@"\CurrentVersion\Time Zones"))
{
string[] zoneNames = key.GetSubKeyNames();
foreach (string zoneName in zoneNames)
{
using (RegistryKey subKey = key.OpenSubKey(zoneName))
{
TimeZoneInformation tzi = new TimeZoneInformation();
tzi.m_name = zoneName;
tzi.m_displayName = (string)subKey.GetValue("Display");
tzi.m_standardName = (string)subKey.GetValue("Std");
tzi.m_daylightName = (string)subKey.GetValue("Dlt");
tzi.m_index = (int)(subKey.GetValue("Index"));
tzi.InitTzi((byte[])subKey.GetValue("Tzi"));
zones.Add(tzi);
}
}
}
s_zones = new TimeZoneInformation[zones.Count];
zones.CopyTo(s_zones);
}
}
}
return s_zones;
}
public string Name
{
get { return m_name; }
}
public string DisplayName
{
get { return m_displayName; }
}
public int Index
{
get { return m_index; }
}
public string StandardName
{
get { return m_standardName; }
}
public string DaylightName
{
get { return m_daylightName; }
}
public override string ToString()
{
return m_displayName;
}
[StructLayout(LayoutKind.Sequential)]
private struct SYSTEMTIME
{
public UInt16 wYear;
public UInt16 wMonth;
public UInt16 wDayOfWeek;
public UInt16 wDay;
public UInt16 wHour;
public UInt16 wMinute;
public UInt16 wSecond;
public UInt16 wMilliseconds;
}
[StructLayout(LayoutKind.Sequential)]
private struct TZI
{
public int bias;
public int standardBias;
public int daylightBias;
public SYSTEMTIME standardDate;
public SYSTEMTIME daylightDate;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private struct TIME_ZONE_INFORMATION
{
[MarshalAs(UnmanagedType.I4)]
public Int32 Bias;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
public string StandardName;
public SYSTEMTIME StandardDate;
[MarshalAs(UnmanagedType.I4)]
public Int32 StandardBias;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
public string DaylightName;
public SYSTEMTIME DaylightDate;
[MarshalAs(UnmanagedType.I4)]
public Int32 DaylightBias;
}
private struct NativeMethods
{
private const string KERNEL32 = "kernel32.dll";
[DllImport(KERNEL32)]
public static extern uint
GetTimeZoneInformation(out TIME_ZONE_INFORMATION
lpTimeZoneInformation);
[DllImport(KERNEL32)]
public static extern bool SystemTimeToTzSpecificLocalTime(
[In] ref TIME_ZONE_INFORMATION lpTimeZone,
[In] ref SYSTEMTIME lpUniversalTime,
out SYSTEMTIME lpLocalTime);
[DllImport(KERNEL32)]
public static extern bool SystemTimeToFileTime(
[In] ref SYSTEMTIME lpSystemTime,
out FILETIME lpFileTime);
[DllImport(KERNEL32)]
public static extern bool FileTimeToSystemTime(
[In] ref FILETIME lpFileTime,
out SYSTEMTIME lpSystemTime);
[DllImport(KERNEL32)]
public static extern bool TzSpecificLocalTimeToSystemTime(
[In] ref TIME_ZONE_INFORMATION lpTimeZone,
[In] ref SYSTEMTIME lpLocalTime,
out SYSTEMTIME lpUniversalTime);
}
private void InitTzi(byte[] info)
{
if (info.Length != Marshal.SizeOf(m_tzi))
{
throw new ArgumentException("Information size is incorrect",
"info");
}
GCHandle h = GCHandle.Alloc(info, GCHandleType.Pinned);
try
{
m_tzi = (TZI)Marshal.PtrToStructure(h.AddrOfPinnedObject(),
typeof(TZI));
}
finally
{
h.Free();
}
}
public int Bias
{
get { return -m_tzi.bias; }
}
public int StandardBias
{
get { return -(m_tzi.bias + m_tzi.standardBias); }
}
public int DaylightBias
{
get { return -(m_tzi.bias + m_tzi.daylightBias); }
}
private TIME_ZONE_INFORMATION TziNative()
{
TIME_ZONE_INFORMATION tziNative = new TIME_ZONE_INFORMATION();
tziNative.Bias = m_tzi.bias;
tziNative.StandardDate = m_tzi.standardDate;
tziNative.StandardBias = m_tzi.standardBias;
tziNative.DaylightDate = m_tzi.daylightDate;
tziNative.DaylightBias = m_tzi.daylightBias;
return tziNative;
}
public DateTime FromUniversalTime(DateTime utc)
{
SYSTEMTIME stUTC = DateTimeToSystemTime(utc);
TIME_ZONE_INFORMATION tziNative = TziNative();
SYSTEMTIME stLocal;
NativeMethods.SystemTimeToTzSpecificLocalTime(ref
tziNative, ref stUTC, out stLocal);
return SystemTimeToDateTime(ref stLocal);
}
public static DateTime FromUniversalTime(int index, DateTime utc)
{
TimeZoneInformation tzi = FromIndex(index);
return tzi.FromUniversalTime(utc);
}
public DateTime ToUniversalTime(DateTime local)
{
SYSTEMTIME stLocal = DateTimeToSystemTime(local);
TIME_ZONE_INFORMATION tziNative = TziNative();
SYSTEMTIME stUTC;
try
{
NativeMethods.TzSpecificLocalTimeToSystemTime(ref
tziNative, ref stLocal, out stUTC);
return SystemTimeToDateTime(ref stUTC);
}
catch (EntryPointNotFoundException e)
{
throw new NotSupportedException("This method" +
" is not supported on this operating system", e);
}
}
public static DateTime ToUniversalTime(int index, DateTime local)
{
TimeZoneInformation tzi = FromIndex(index);
return tzi.ToUniversalTime(local);
}
private static SYSTEMTIME DateTimeToSystemTime(DateTime dt)
{
SYSTEMTIME st;
FILETIME ft = new FILETIME();
ft.dwHighDateTime = (int)(dt.Ticks >> 32);
ft.dwLowDateTime = (int)(dt.Ticks & 0xFFFFFFFFL);
NativeMethods.FileTimeToSystemTime(ref ft, out st);
return st;
}
private static DateTime SystemTimeToDateTime(ref SYSTEMTIME st)
{
FILETIME ft = new FILETIME();
NativeMethods.SystemTimeToFileTime(ref st, out ft);
DateTime dt = new DateTime((((long)ft.dwHighDateTime)
<< 32) | (uint)ft.dwLowDateTime);
return dt;
}
private TZI m_tzi;
private string m_name;
private string m_displayName;
private int m_index;
private string m_standardName;
private string m_daylightName;
}
public class TZConvert
{
static private float ExtractGMTOffset(string s)
{
string tmp = s.Substring(4);
int i = tmp.IndexOf(")");
tmp = tmp.Substring(0, i);
tmp = tmp.Replace(":", ".");
if (tmp == "")
return (0.0F);
return (System.Single.Parse(tmp));
}
public static void GetTZList(System.Collections.ArrayList Indexes,
System.Collections.ArrayList Names)
{
TimeZoneInformation[] zones = TimeZoneInformation.EnumZones();
float[] GMTOffsets = new float[zones.Length];
for (int i = 0; i < zones.Length; i++)
GMTOffsets[i] = ExtractGMTOffset(zones[i].DisplayName);
Array.Sort(GMTOffsets, zones);
for (int i = 0; i < zones.Length; i++)
{
Indexes.Add(zones[i].Index);
Names.Add(zones[i].DisplayName);
}
}
public static void GetSpecificTZList(string[] Indexes, string[] Names)
{
TimeZoneInformation[] zones = TimeZoneInformation.EnumZones();
int[] iIndexes = new int[Indexes.Length];
for (int i = 0; i < Indexes.Length; i++)
iIndexes[i] = System.Int32.Parse(Indexes[i]);
int j = 0;
for (int i = 0; i < zones.Length; i++)
{
j = Array.IndexOf(iIndexes, zones[i].Index);
if (j >= 0)
Names[j] = zones[i].DisplayName;
}
}
private static System.DateTime MakeDateHelper(int Year,
int Month, int Day, int Hour, int Min)
{
System.DateTime dt = new System.DateTime(Year,
Month, Day, Hour, Min, 0, 0);
return (dt);
}
public static System.DateTime MakeDateTime(int Index, string s)
{
if (!(s.Length == 16 || s.Length == 18))
throw (new System.FormatException());
if (s[4] != '/') throw (new System.FormatException());
if (s[7] != '/') throw (new System.FormatException());
if (s[10] != ' ') throw (new System.FormatException());
if (s[13] != ':') throw (new System.FormatException());
int Year = System.Int32.Parse(s.Substring(0, 4));
int Month = System.Int32.Parse(s.Substring(5, 2));
int Day = System.Int32.Parse(s.Substring(8, 2));
int Hour = System.Int32.Parse(s.Substring(11, 2));
int Minute = System.Int32.Parse(s.Substring(14, 2));
bool IsPM = false;
if (s.Length == 18)
IsPM = (s.Substring(16).ToLower() == "pm") ? true : false;
if (IsPM)
{
if (Hour != 12)
Hour += 12;
}
else
{
if (Hour == 12)
Hour -= 12;
}
System.DateTime dt = new System.DateTime(Year,
Month, Day, Hour, Minute, 0, 0);
if (Index != -1)
return (TimeZoneInformation.ToUniversalTime(Index, dt));
return (dt);
}
public static string DisplayDateTime(int Index,
System.DateTime dt, bool IncludeDayName)
{
if (Index != -1)
dt = TimeZoneInformation.FromUniversalTime(Index, dt);
string t = dt.ToString("yyyy/MM/dd\\ HH:mmtt\\ dddd",
System.Globalization.DateTimeFormatInfo.InvariantInfo);
System.Text.StringBuilder s = new
System.Text.StringBuilder(t.Substring(0, 16));
string DayName = t.Substring(17, 3);
if (IncludeDayName)
{
s.Append(" ");
s.Append(DayName);
}
return (s.ToString());
}
public static string DisplayDateTime(int Index, System.DateTime dt)
{
return (DisplayDateTime(Index, dt, false));
}
public static string DisplayLocalTimeByBrowserHelper()
{
return (
@"<script>
function FormatLocalTime(dd, IncludeDayName)
{
function TwoDigits(s)
{
var ss = s.toString();
if(ss.length == 1)
return('0'+ss);
return(ss);
}
var d=new Date(dd);
var Year = d.getFullYear().toString();
var Month = TwoDigits(d.getMonth());
var Day = TwoDigits(d.getDate());
var Hours = d.getHours();
var Minutes = TwoDigits(d.getMinutes());
var AmPm = 'am';
if(Hours == 12)
{
AmPm = 'pm';
}
if(Hours == 0)
{
Hours = 12;
}
if(Hours > 12)
{
Hours -= 12;
AmPm = 'pm';
}
return(Year+'/'+Month+'/'+Day+' '+
TwoDigits(Hours)+':'+Minutes+AmPm);
}
</script>
");
}
public static string DisplayLocalTimeByBrowser(System.DateTime dt,
bool IncludeDayName)
{
return("<script>document.write(FormatLocalTime("
+"Date.UTC("
+dt.Year.ToString() + ","
+ dt.Month.ToString() + ","
+ dt.Day.ToString() + ","
+ dt.Hour.ToString() + ","
+ dt.Minute.ToString() + ",0)"
+", "
+(IncludeDayName==true?"true":"false")
+"));</script>"
);
}
public static string DisplayXmlDateTime(System.DateTime dt)
{
return (dt.ToString("yyyy-MM-dd\\Thh:mm:ss.fff",
System.Globalization.DateTimeFormatInfo.InvariantInfo));
}
public static void Main(string[] args)
{
ArrayList Indexes = new ArrayList();
ArrayList Names = new ArrayList();
GetTZList(Indexes, Names);
for (int i = 0; i < Indexes.Count; i++)
{
System.Console.WriteLine(
Indexes[i] + " " +
Names[i]);
}
System.DateTime dt = MakeDateTime(35, "2003/03/02 03:43pm");
System.Console.WriteLine(TZConvert.DisplayDateTime(35, dt));
string s = DisplayLocalTimeByBrowserHelper()+
DisplayLocalTimeByBrowser(dt, true);
System.Console.WriteLine(s);
}
}
}
I met my goal of getting this out of the door in 10 minutes.