通过CultureInfo获取一个国家的默认时区

26

有没有一个程序或表格可以为每个国家提供默认的时区?

是的,美国、加拿大和俄罗斯拥有多个时区。(我认为其他每个国家只有一个时区。) 但是如果我们已知某个国家,最好从该国家开始,而不仅仅提供以GMT为起点的列表。

最好用C#,但我会接受任何语言并转换成C#。


3
中国。印度。伊朗。巴西... - Andrew Barber
“默认时区”是什么?是一个国家的首都所在的时区吗? - user2819245
1
澳大利亚拥有至少三个时区,一些州实行夏令时,而另一些则不实行;美国的印第安纳州使用至少两个时区。 - user1864610
要求我们推荐或寻找工具、库或喜爱的外部资源的问题,因为它们往往会吸引有主观意见的答案和垃圾邮件,所以在 Stack Overflow 上是不被允许的。相反,请描述问题以及已经采取的解决方案。 - user1864610
@Andrew,有趣的事实:中国确实只有一个时区。如果你考虑到这个国家的大小,那真是太疯狂了。但蒙古有两个时区,应该可以平衡一下 ;) - user2819245
显示剩余21条评论
6个回答

34

正如问题评论中指出的那样,你无法为每个国家获取单一的时区。因为有太多国家拥有多个时区。

你可以过滤标准IANA/Olson时区列表,只保留特定国家中可用的时区。

C#中实现此操作的一种方法是使用Noda Time

IEnumerable<string> zoneIds = TzdbDateTimeZoneSource.Default.ZoneLocations
    .Where(x => x.CountryCode == countryCode)
    .Select(x => x.ZoneId);

传递两位数字的 ISO-3166 国家代码,例如 "AU" 表示澳大利亚。结果如下:

"Australia/Lord_Howe",
"Australia/Hobart",
"Australia/Currie",
"Australia/Melbourne",
"Australia/Sydney",
"Australia/Broken_Hill",
"Australia/Brisbane",
"Australia/Lindeman",
"Australia/Adelaide",
"Australia/Darwin",
"Australia/Perth",
"Australia/Eucla"

如果您需要与 TimeZoneInfo 对象一起使用的 Windows 时区标识符,Noda Time也可以进行映射:

var source = TzdbDateTimeZoneSource.Default;
IEnumerable<string> windowsZoneIds = source.ZoneLocations
    .Where(x => x.CountryCode == countryCode)
    .Select(tz => source.WindowsMapping.MapZones
        .FirstOrDefault(x => x.TzdbIds.Contains(
                             source.CanonicalIdMap.First(y => y.Value == tz.ZoneId).Key)))
    .Where(x => x != null)
    .Select(x => x.WindowsId)
    .Distinct()

再次调用参数为"AU"的函数,返回:

"Tasmania Standard Time",
"AUS Eastern Standard Time",
"Cen. Australia Standard Time",
"E. Australia Standard Time",
"AUS Central Standard Time",
"W. Australia Standard Time"

如果你对这些数据的可靠性有疑问,那么请注意,国家与tzid映射是IANA时区数据库本身的一部分,位于zone.tab文件中。IANA到Windows映射数据来自Unicode CLDR补充数据。没有比这更“官方”的了。


你能否尝试一下 FO 国家代码呢?(即 法罗群岛)我在文件 (zone.tab) 中找到了该代码,但是在 C# 中却没有得到任何结果。 - Alex
试试我刚刚做的修改。我没有考虑到窗口映射中的ID并不总是TZDB规范的。 - Matt Johnson-Pint
对于Windows时区,它并不适用于所有情况,例如HR。 - Umair
这会产生冗余的时区。例如,美国只有3-4个时区,但这返回了29个时区,其中许多是相同的时区! - A X
你好,我发现代码在国家代码为“AX”的情况下无法正常工作,所以这是我对你的解决方案:IEnumerable<TzdbZoneLocation> ZoneIds = source.ZoneLocations.Where(key => key.CountryCode == countryCode); IReadOnlyDictionary<string, string> WindowsZoneIDs = source.TzdbToWindowsIds; List<string> Timezones = new(); foreach (var timezone in ZoneIds) { if (WindowsZoneIDs.ContainsKey(timezone.ZoneId)) Timezones.Add(WindowsZoneIDs[timezone.ZoneId]); }var TimezonesDistinct = Timezones.Distinct(); - Abo Ali

4
为了获取CountryCode -> TimeZoneInfo映射,我使用了Matt的答案(第二段代码片段),但它并不适用于很多情况。发现更简单和可靠的解决方案(使用相同的Noda Time):TzdbDateTimeZoneSource.Default.WindowsMapping.MapZones基本上包含了所有数据。

代码示例:

Dictionary<string, TimeZoneInfo> GetIsoToTimeZoneMapping()
{
    var source = TzdbDateTimeZoneSource.Default;

    return source.WindowsMapping.MapZones
        .GroupBy(z => z.Territory)
        .ToDictionary(grp => grp.Key, grp => GetTimeZone(source, grp));
}

 TimeZoneInfo GetTimeZone(TzdbDateTimeZoneSource source, IEnumerable<MapZone> territoryLocations)
{
    var result = territoryLocations
        .Select(l => l.WindowsId)
        .Select(TimeZoneInfo.FindSystemTimeZoneById)
        //pick timezone with the minimum offset
        .Aggregate((tz1, tz2) => tz1.BaseUtcOffset < tz2.BaseUtcOffset ? tz1 : tz2);

    return result;
}

4

也许不完全符合您的要求,但可以尝试使用以下链接:http://msdn.microsoft.com/en-us/library/system.timezoneinfo.aspx

获取特定的时区:

TimeZoneInfo tZone = TimeZoneInfo.FindSystemTimeZoneById("E. Australia Standard Time");

查看可用区:

ReadOnlyCollection<TimeZoneInfo> zones = TimeZoneInfo.GetSystemTimeZones();

foreach (TimeZoneInfo zone in zones)
{
     Console.WriteLine(zone.Id);
}

1
一些国家有多个时区,例如俄罗斯。
在我的解决方案中,我使用NodaTime,如果您有经度信息可用,我会选择最适合该国家的时区。
var countryName = "Russia";
var longitude = 40.332206;

var zones = TzdbDateTimeZoneSource.Default.ZoneLocations.Where(x => x.CountryName == countryName).AsQueryable();
if (!double.IsNaN(longitude))
{
    zones = zones.OrderBy(o => this.Distance(o.Latitude, longitude, o.Latitude, o.Longitude, DistanceUnit.Kilometer));
}
var bestZone = zones.FirstOrDefault();
var dateTimeZone = TzdbDateTimeZoneSource.Default.ForId(bestZone.ZoneId);

var newTime = DateTime.UtcNow.AddSeconds(dateTimeZone.MaxOffset.Seconds);

计算地理坐标之间的距离

public enum DistanceUnit { StatuteMile, Kilometer, NauticalMile };

private double Distance(double lat1, double lon1, double lat2, double lon2, DistanceUnit unit)
{
    double rlat1 = Math.PI * lat1 / 180;
    double rlat2 = Math.PI * lat2 / 180;
    double theta = lon1 - lon2;
    double rtheta = Math.PI * theta / 180;
    double dist =
        Math.Sin(rlat1) * Math.Sin(rlat2) + Math.Cos(rlat1) *
        Math.Cos(rlat2) * Math.Cos(rtheta);
    dist = Math.Acos(dist);
    dist = dist * 180 / Math.PI;
    dist = dist * 60 * 1.1515;

    switch (unit)
    {
        case DistanceUnit.Kilometer:
            return dist * 1.609344;
        case DistanceUnit.NauticalMile:
            return dist * 0.8684;
        default:
        case DistanceUnit.StatuteMile: //Miles
            return dist;
    }
}

1

最新的Windows版本包含文件%WINDIR%\Globalization\Time Zone\timezoneMapping.xml,它将Olson映射到Windows时区,您可以像查询常规XML一样查询它。我不知道但也许C#已经有一个可以使用它的类。


可以在这里找到实现的好方法:https://codeofmatt.com/2014/04/07/working-with-time-zone-names-in-net/ - Walter Verhoeven

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接