项目中需要通过程序获取和设置时区,在网上搜了半天,这方面的资料很少,中文几乎没有完整的方案,只是有人提到用下面两个API,具体怎么用,没有找到完整的例子。英文资料也很少,找到一个,思路没问题,但代码有很大问题。只能自己研究实现,下面我就具体说说我是怎么做的。
获取和设置时区信息需要用到下面两个操作系统 API
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
private static extern int GetTimeZoneInformation(out TimeZoneInformation lpTimeZoneInformation);
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
private static extern bool SetTimeZoneInformation(ref TimeZoneInformation lpTimeZoneInformation);
TimeZoneInformation 结构如下:
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public 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;
}
SYSTEMTIME 结构如下:
[StructLayout(LayoutKind.Sequential)]
public struct SYSTEMTIME
{
public short wYear;
public short wMonth;
public short wDayOfWeek;
public short wDay;
public short wHour;
public short wMinute;
public short wSecond;
public short wMilliseconds;
public SYSTEMTIME(byte[] buf, int index)
{
wYear = BitConverter.ToInt16(buf, index);
index += 2;
wMonth = BitConverter.ToInt16(buf, index);
index += 2;
wDayOfWeek = BitConverter.ToInt16(buf, index);
index += 2;
wDay = BitConverter.ToInt16(buf, index);
index += 2;
wHour = BitConverter.ToInt16(buf, index);
index += 2;
wMinute = BitConverter.ToInt16(buf, index);
index += 2;
wSecond = BitConverter.ToInt16(buf, index);
index += 2;
wMilliseconds = BitConverter.ToInt16(buf, index);
}
}
获取时区的显示名
我们在设置时区的界面上看到的名称实际上是时区的显示名,我们获取和设置时区信息时一般也需要使用这个名字,而不是standardName 或者 daylightName. 那两个名字只的是标准名和夏令时名称,这两个名字是操作系统自己使用的所有操作系统不管使用什么语言,这两个名字都是一样的。但显示名,不同语言的操作系统是不一样的。这个显示名我们无法在 TimeZoneInformation 这个结构中获取,只能从注册表中获取。存放所有时区信息的注册表位置在下面代码所述位置
Registry.LocalMachine.OpenSubKey("Software\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones");
这个注册表项下面所有的子项中存放的就是操作系统中所有的时区信息。
我们需要把这些时区信息读取到一个字典结构中,这个字典的键是显示名,值是时区信息。代码如下:
/// <summary>
/// Get all time zone informations
/// </summary>
/// <returns></returns>
private static TimeZoneCollection GetTimeZones()
{
lock (_SyncRoot)
{
if (_Zones != null)
{
return _Zones;
}
//open key where all time zones are located in the registry
RegistryKey timeZoneKeys = Registry.LocalMachine.OpenSubKey("Software\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones");
//create a new hashtable which will store the name
//of the timezone and the associate time zone information struct
_Zones = new TimeZoneCollection();
//iterate through each time zone in the registry and add it to the hash table
foreach (string zonekey in timeZoneKeys.GetSubKeyNames())
{
//get current time zone key
RegistryKey individualZone = timeZoneKeys.OpenSubKey(zonekey);
//create new TZI struct and populate it with values from key
TimeZoneInformation TZI = new TimeZoneInformation();
TZI.standardName = individualZone.GetValue("Std").ToString();
string displayName = individualZone.GetValue("Display").ToString();
TZI.daylightName = individualZone.GetValue("Dlt").ToString();
//read binary TZI data, convert to byte array
byte[] b = (byte[])individualZone.GetValue("TZI");
TZI.bias = BitConverter.ToInt32(b, 0);
TZI.standardBias = BitConverter.ToInt32(b, 4);
TZI.daylightBias = BitConverter.ToInt32(b, 8);
TZI.standardDate = new SYSTEMTIME(b, 12);
TZI.daylightDate = new SYSTEMTIME(b, 28);
//Marshal.PtrToStructure(
//add the name and TZI struct to hash table
_Zones.Add(displayName, TZI);
}
return _Zones;
}
}
有了这个字典后,我们就可以通过显示名来获取时区信息了。
/// <summary>
/// Get the Time zone by display name
/// </summary>
/// <param name="displayName">part of display name</param>
/// <param name="wholeDisplayName">whole display name</param>
/// <returns>time zone</returns>
public static TimeZoneInformation GetTimeZone(string displayName, out string wholeDisplayName)
{
TimeZoneCollection tzc = TimeZone.GetTimeZones();
foreach (string key in tzc.Keys)
{
if (key.IndexOf(displayName, 0, StringComparison.CurrentCultureIgnoreCase) >= 0)
{
wholeDisplayName = key;
return tzc[key];
}
}
throw new Exception(string.Format("Can't find the display name : {0}", displayName));
}
上面这个函数输入时区显示名的一部分,然后获取和这部分时区信息匹配的第一个时区信息。并返回这个时区完整的显示名。
调用示例
OS.TimeZone.TimeZoneInformation tzi = TimeZone.GetTimeZone("Sydney", out displayName);
Console.WriteLine(displayName);
结果:(GMT+10:00) Canberra, Melbourne, Sydney
注意:如果是中文操作系统,这里应该输入 “悉尼”
我们还可以获取当前的时区名
/// <summary>
/// Get current time zone display name
/// </summary>
/// <returns></returns>
public static string GetCurrentTimeZoneDisplayName()
{
TimeZoneInformation tzi;
GetTimeZoneInformation(out tzi);
TimeZoneCollection tzc = TimeZone.GetTimeZones();
foreach (string displayName in tzc.Keys)
{
TimeZoneInformation tz = tzc[displayName];
if (tz.standardName.Equals(tzi.standardName, StringComparison.CurrentCultureIgnoreCase))
{
return displayName;
}
}
throw new Exception(string.Format("Can't find the display name of {0}", tzi.standardName));
}
我们还可以通过时区名来设置时区
/// <summary>
/// set time zone by display name
/// </summary>
/// <param name="displayName">part of display name</param>
/// <param name="wholeDisplayName">whole display name</param>
/// <returns></returns>
public static bool SetTimeZone(string displayName, out string wholeDisplayName)
{
//ComputerManager.EnableToken("SeTimeZonePrivilege", Process.GetCurrentProcess().Handle);
// set local system timezone
TimeZoneInformation tzi = GetTimeZone(displayName, out wholeDisplayName);
return SetTimeZoneInformation(ref tzi);
}
调用示例:
OS.TimeZone.SetTimeZone("Sydney", out displayName)
上面代码将时区设置为悉尼时间。
注意:如果是中文操作系统,这里应该输入 “悉尼”
下面给出完整代码
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.Diagnostics;
using Microsoft.Win32;
namespace OS
{
using TimeZoneCollection = System.Collections.Generic.Dictionary<string, TimeZone.TimeZoneInformation>;
public class TimeZone
{
#region DLL Imports
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
private static extern int GetTimeZoneInformation(out TimeZoneInformation lpTimeZoneInformation);
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
private static extern bool SetTimeZoneInformation(ref TimeZoneInformation lpTimeZoneInformation);
[StructLayout(LayoutKind.Sequential)]
public struct SYSTEMTIME
{
public short wYear;
public short wMonth;
public short wDayOfWeek;
public short wDay;
public short wHour;
public short wMinute;
public short wSecond;
public short wMilliseconds;
public SYSTEMTIME(byte[] buf, int index)
{
wYear = BitConverter.ToInt16(buf, index);
index += 2;
wMonth = BitConverter.ToInt16(buf, index);
index += 2;
wDayOfWeek = BitConverter.ToInt16(buf, index);
index += 2;
wDay = BitConverter.ToInt16(buf, index);
index += 2;
wHour = BitConverter.ToInt16(buf, index);
index += 2;
wMinute = BitConverter.ToInt16(buf, index);
index += 2;
wSecond = BitConverter.ToInt16(buf, index);
index += 2;
wMilliseconds = BitConverter.ToInt16(buf, index);
}
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public 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;
}
#endregion
static object _SyncRoot = new object();
static TimeZoneCollection _Zones = null;
/// <summary>
/// Get all time zone informations
/// </summary>
/// <returns></returns>
private static TimeZoneCollection GetTimeZones()
{
lock (_SyncRoot)
{
if (_Zones != null)
{
return _Zones;
}
//open key where all time zones are located in the registry
RegistryKey timeZoneKeys = Registry.LocalMachine.OpenSubKey("Software\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones");
//create a new hashtable which will store the name
//of the timezone and the associate time zone information struct
_Zones = new TimeZoneCollection();
//iterate through each time zone in the registry and add it to the hash table
foreach (string zonekey in timeZoneKeys.GetSubKeyNames())
{
//get current time zone key
RegistryKey individualZone = timeZoneKeys.OpenSubKey(zonekey);
//create new TZI struct and populate it with values from key
TimeZoneInformation TZI = new TimeZoneInformation();
TZI.standardName = individualZone.GetValue("Std").ToString();
string displayName = individualZone.GetValue("Display").ToString();
TZI.daylightName = individualZone.GetValue("Dlt").ToString();
//read binary TZI data, convert to byte array
byte[] b = (byte[])individualZone.GetValue("TZI");
TZI.bias = BitConverter.ToInt32(b, 0);
TZI.standardBias = BitConverter.ToInt32(b, 4);
TZI.daylightBias = BitConverter.ToInt32(b, 8);
TZI.standardDate = new SYSTEMTIME(b, 12);
TZI.daylightDate = new SYSTEMTIME(b, 28);
//Marshal.PtrToStructure(
//add the name and TZI struct to hash table
_Zones.Add(displayName, TZI);
}
return _Zones;
}
}
/// <summary>
/// Get the Time zone by display name
/// </summary>
/// <param name="displayName">part of display name</param>
/// <param name="wholeDisplayName">whole display name</param>
/// <returns>time zone</returns>
public static TimeZoneInformation GetTimeZone(string displayName, out string wholeDisplayName)
{
TimeZoneCollection tzc = TimeZone.GetTimeZones();
foreach (string key in tzc.Keys)
{
if (key.IndexOf(displayName, 0, StringComparison.CurrentCultureIgnoreCase) >= 0)
{
wholeDisplayName = key;
return tzc[key];
}
}
throw new Exception(string.Format("Can't find the display name : {0}", displayName));
}
/// <summary>
/// Get current time zone
/// </summary>
/// <returns></returns>
public static TimeZoneInformation GetCurrentTimeZone()
{
TimeZoneInformation tzi;
GetTimeZoneInformation(out tzi);
return tzi;
}
/// <summary>
/// Get current time zone display name
/// </summary>
/// <returns></returns>
public static string GetCurrentTimeZoneDisplayName()
{
TimeZoneInformation tzi;
GetTimeZoneInformation(out tzi);
TimeZoneCollection tzc = TimeZone.GetTimeZones();
foreach (string displayName in tzc.Keys)
{
TimeZoneInformation tz = tzc[displayName];
if (tz.standardName.Equals(tzi.standardName, StringComparison.CurrentCultureIgnoreCase))
{
return displayName;
}
}
throw new Exception(string.Format("Can't find the display name of {0}", tzi.standardName));
}
/// <summary>
/// Set time zone
/// </summary>
/// <param name="tzi"></param>
/// <returns></returns>
public static bool SetTimeZone(TimeZoneInformation tzi)
{
//ComputerManager.EnableToken("SeTimeZonePrivilege", Process.GetCurrentProcess().Handle);
// set local system timezone
return SetTimeZoneInformation(ref tzi);
}
/// <summary>
/// set time zone by display name
/// </summary>
/// <param name="displayName">part of display name</param>
/// <param name="wholeDisplayName">whole display name</param>
/// <returns></returns>
public static bool SetTimeZone(string displayName, out string wholeDisplayName)
{
//ComputerManager.EnableToken("SeTimeZonePrivilege", Process.GetCurrentProcess().Handle);
// set local system timezone
TimeZoneInformation tzi = GetTimeZone(displayName, out wholeDisplayName);
return SetTimeZoneInformation(ref tzi);
}
}
}