在.Net 3.5下有一個TimeZoneInfo類,可以很方便的轉換時區和進行時間轉換.但是在.Net 2.0下,只能對當前服務器時區進行處理,十分不方便.特別系統是世界范圍使用的,更需要考慮當地時區特別是夏令時的問題,不然時間就會錯亂.如何解決這個問題,就要通過自己手動處理了.
其實Windows的時區信息都存放在Windows注冊表的"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones"下.只要讀取相關信息出來.就能實現和TimeZoneInfo相同的功能.下邊我們就通過一個小demo.讀取世界時區信息,計算時區偏移量和夏令時偏移量.並根據用戶選擇的時區和輸入時間,判斷該時間是否屬於該時區的夏令時范圍.
首先,我們需要創建兩個Struct來記錄相關信息,一個是SytemTime,一個TimeZoneInformation.代碼如下:
[StructLayout(LayoutKind.Sequential)]
struct SYSTEMTIME
{
public ushort wYear;
public ushort wMonth;
public ushort wDayOfWeek;
public ushort wDay;
public ushort wHour;
public ushort wMinute;
public ushort wSecond;
public ushort wMilliseconds;
public void SetInfo(byte[] info)
{
if (info.Length != Marshal.SizeOf(this))
{
throw new ArgumentException("Information size is incorrect", "info");
}
else
{
this.wYear = BitConverter.ToUInt16(info, 0);
this.wMonth = BitConverter.ToUInt16(info, 2);
this.wDayOfWeek = BitConverter.ToUInt16(info, 4);
this.wDay = BitConverter.ToUInt16(info, 6);
this.wHour = BitConverter.ToUInt16(info, 8);
this.wMinute = BitConverter.ToUInt16(info, 10);
this.wSecond = BitConverter.ToUInt16(info, 12);
this.wMilliseconds = BitConverter.ToUInt16(info, 14);
}
}
public override bool Equals(object obj)
{
if (this.GetType() == obj.GetType())
{
try
{
SYSTEMTIME objSt = (SYSTEMTIME)obj;
if (this.wYear != objSt.wYear
|| this.wMonth != objSt.wMonth
|| this.wDayOfWeek != objSt.wDayOfWeek
|| this.wDay != objSt.wDay
|| this.wHour != objSt.wHour
|| this.wMinute != objSt.wMinute
|| this.wSecond != objSt.wSecond
|| this.wMilliseconds != objSt.wMilliseconds)
return false;
else
return true;
}
catch
{
return false;
}
}
else
return false;
}
public override int GetHashCode()
{
return base.GetHashCode();
}
}
TimeZoneInformation Struct
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
struct TimeZoneInformation
{
public int bias;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
public string standardName;
public SYSTEMTIME standardDate;
public int standardBias;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
public string daylightName;
public SYSTEMTIME daylightDate;
public int daylightBias;
public void SetBytes(byte[] info)
{
if (info.Length != 44)
throw new ArgumentException("Information size is incorrect", "info");
else
{
this.bias = BitConverter.ToInt32(info, 0);
this.standardBias = BitConverter.ToInt32(info, 4);
this.daylightBias = BitConverter.ToInt32(info, 8);
byte[] helper = new byte[16];
Array.Copy(info, 12, helper, 0, 16);
this.standardDate.SetInfo(helper);
Array.Copy(info, 28, helper, 0, 16);
this.daylightDate.SetInfo(helper);
}
}
public override bool Equals(object obj)
{
if (this.GetType() != obj.GetType())
{
try
{
TimeZoneInformation objTzi = (TimeZoneInformation)obj;
if (this.bias != objTzi.bias
|| this.daylightBias != objTzi.daylightBias
|| this.daylightName != objTzi.daylightName
|| this.standardBias != objTzi.standardBias
|| this.standardName != objTzi.standardName
|| !this.daylightDate.Equals(objTzi.daylightDate)
|| !this.standardDate.Equals(objTzi.standardDate)
)
return false;
else
return true;
}
catch
{
return false;
}
}
else
return false;
}
public override int GetHashCode()
{
return base.GetHashCode();
}
}
定義好相關Structs後,我們就可以讀取注冊表了.先定義幾個讀取注冊表的方法:
RegistryKey Code
private static RegistryKey GetTimeZoneRegistryKey()
{
RegistryKey key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones", false);
if (key == null)
throw new KeyNotFoundException(@"Cannot find the windows registry key (Time Zone).");
else
return key;
}
private static RegistryKey GetTimeZoneRegistrySubKey(string zoneId)
{
RegistryKey key = GetTimeZoneRegistryKey();
RegistryKey subKey = key.OpenSubKey(zoneId, false);
if (subKey == null)
throw new Exception("Unknown time zone.");
else
return subKey;
}
private static RegistryKey GetTimeZoneRegistryDynamicSubKey(TimeZoneInfo tzi)
{
RegistryKey subKey = GetTimeZoneRegistrySubKey(tzi._id);
return subKey.OpenSubKey("Dynamic DST", false);
}
下邊我們開始讀取注冊表各個TimeZone信息
TimeZones Code
public static TimeZoneInfo[] GetTimeZones()
{
List<TimeZoneInfo> tzInfos = new List<TimeZoneInfo>();
RegistryKey key = GetTimeZoneRegistryKey();
foreach (string zoneName in key.GetSubKeyNames())
{
TimeZoneInfo tzi = new TimeZoneInfo();
tzi._id = zoneName;
tzi.SetValues();
tzInfos.Add(tzi);
}
TimeZoneInfo.Sort(tzInfos);
return tzInfos.ToArray();
}
可以看到上述代碼是循環讀取注冊表裡面的信息,通過SetVales方法放到一個List:
SetValues
private void SetValues()
{
RegistryKey subKey = GetTimeZoneRegistrySubKey(this._id);
this._displayName = subKey.GetValue("Display").ToString();
this._tzi.daylightName = subKey.GetValue("Dlt").ToString();
this._tzi.standardName = subKey.GetValue("Std").ToString();
this._tzi.SetBytes(subKey.GetValue("Tzi") as byte[]);
}
通過簡單的讀取信息填入TimeZoneInfo List,在頁面級別我們就可以簡單用下面的方法來顯示給用戶選擇:
UI1
TimeZoneInfo[]TimeZones=TimeZoneInfo.GetTimeZones(); this.drpList.DataSource=TimeZones; this.drpList.DataTextField="DisplayName"; this.drpList.DataValueField="Id"; this.drpList.DataBind();
當用選擇了某一個時區,我們就可以根據用戶選擇的時區和輸入的日期顯示相關時區信息:
UI2
if (tzi.DisplayName == this.drpList.SelectedItem.Text)
{
DateTime dtDate = DateTime.Parse(this.txtDate.Text.Trim());
this.lblInfo.Text += string.Format("<br />時區ID: {0} ", tzi.Id);
this.lblInfo.Text += string.Format("<br />顯示名稱: {0} ", tzi.DisplayName);
this.lblInfo.Text += string.Format("<br />標准名稱: {0} ", tzi.StandardName);
this.lblInfo.Text += string.Format("<br /><br />當前時間是否夏令時: {0} ", tzi.IsDaylightSavingTime(dtDate).ToString());
this.lblInfo.Text += string.Format("<br />夏令時名稱: {0} ", tzi.DaylightName(dtDate));
this.lblInfo.Text += string.Format("<br />基本偏移量: {0} ", tzi.StandardUtcOffset.ToString());
this.lblInfo.Text += string.Format("<br />當前偏移量: {0} ", tzi.GetCurrentUtcOffset(dtDate).ToString());
DaylightTime dt = tzi.GetDaylightChanges(dtDate.Year);
this.lblInfo.Text += string.Format("<br />夏令時開始