在C#中的特定时区中创建DateTime


162

我正在尝试创建一个单元测试,以测试时区在计算机上更改的情况,因为该时区已被错误地设置然后被更正。

在测试中,我需要能够在非本地时区中创建DateTime对象,以确保运行测试的人员可以成功地进行操作,而不管他们位于何处。

从DateTime构造函数中可以看到,我可以将TimeZone设置为本地时区,UTC时区或未指定。

如何创建具有特定时区(如PST)的DateTime?


Answers:


216

乔恩(Jon)的答案是关于TimeZone的,但我建议改用TimeZoneInfo

我个人喜欢尽可能将事情保留在UTC中(至少在过去;将UTC存储在将来会存在潜在的问题),因此我建议采用这样的结构:

public struct DateTimeWithZone
{
    private readonly DateTime utcDateTime;
    private readonly TimeZoneInfo timeZone;

    public DateTimeWithZone(DateTime dateTime, TimeZoneInfo timeZone)
    {
        var dateTimeUnspec = DateTime.SpecifyKind(dateTime, DateTimeKind.Unspecified);
        utcDateTime = TimeZoneInfo.ConvertTimeToUtc(dateTimeUnspec, timeZone); 
        this.timeZone = timeZone;
    }

    public DateTime UniversalTime { get { return utcDateTime; } }

    public TimeZoneInfo TimeZone { get { return timeZone; } }

    public DateTime LocalTime
    { 
        get 
        { 
            return TimeZoneInfo.ConvertTime(utcDateTime, timeZone); 
        }
    }        
}

您可能希望将“ TimeZone”名称更改为“ TimeZoneInfo”以使内容更清楚-我更喜欢自己的简称。


5
恐怕我不知道任何等效的SQL Server构造。我建议将时区名称作为一列,将UTC值作为另一列。分别获取它们,然后就可以轻松创建实例。
乔恩·斯基特

2
不确定使用带DateTime和TimeZoneInfo的构造函数的预期用途,但是鉴于您正在调用dateTime.ToUniversalTime()方法,我怀疑您猜测它可能“在”本地时间。在这种情况下,我认为您确实应该使用传入的TimeZoneInfo将其转换为UTC,因为他们告诉您它应该在该时区中。
IDisposable

2
@ChrisMoschini:那时,您只是在发明自己的ID方案-世界上其他任何人都不会使用的方案。谢谢,我坚持使用行业标准的zoneinfo。(例如,很难看到“欧洲/伦敦”是什么意思。)
乔恩·斯基特

2
@ChrisMoschini:然后是另一个示例:CST。是UTC-5还是UTC-6?IST-您的数据库中是以色列,印度还是爱尔兰?(即使您现在知道偏移量,观察相同缩写的不同国家/地区在不同时间也可能会发生变化。因此,对于哪个实际时区,它仍然意味着含糊。时区!=偏移量。)回到您的情况:您主张使用缩写最能解决您的问题。使用行业标准时区ID会变得更糟吗?
乔恩·斯基特

6
@ChrisMoschini:好吧,我将继续建议使用行业标准的,明确的zoneinfo ID,而不是模棱两可的缩写。这与谁的库是首选库无关,库的作者身份确实不是问题。如果有人希望使用其他具有良好标识符选择的库,那很好。但是,选择时区的标识符很重要,而且我认为读者必须意识到缩写不明确的,这一点非常重要,正如我在IST示例中所展示的那样。
乔恩·斯凯特

54

正是为这种使用类型创建了DateTimeOffset结构。

请参阅:http//msdn.microsoft.com/en-us/library/system.datetimeoffset.aspx

这是创建具有特定时区的DateTimeOffset对象的示例:

DateTimeOffset do1 = new DateTimeOffset(2008, 8, 22, 1, 0, 0, new TimeSpan(-5, 0, 0));


1
谢谢,这是完成此任务的好方法。在正确的时区中获取DateTimeOffset对象后,可以使用.UtcDateTime属性获取所创建的UTC时间。如果您将日期存储在UTC中,那么将每个用户的日期都转换为本地时间就没什么大不了了:)
Redth 2010年

2
我认为这不能正确处理夏令时,因为某些TimeZones尊重它,而另一些TimeZones不这样做。同样,DST在“一天中”开始/结束,当天中的部分时间将关闭。
crokusek 2015年

14
课。DST是特定时区的规则。DateTimeOffset并非并非没有与任何时区相关联。不要将UTC偏移值(例如-5)与时区混淆。这不是时区,而是偏移量。相同的偏移量通常由许多时区共享,因此这是引用时区的模棱两可的方式。由于DateTimeOffset与偏移量而不是时区相关联,因此它可能无法应用DST规则。因此,一年中的每一天凌晨3点都是凌晨3点,DateTimeOffset结构(例如,在Hours和TimeOfDay属性中)无一例外。
Triynko '16

如果您查看DateTimeOffset的LocalDateTime属性,可能会感到困惑。该属性不是DateTimeOffset,它是类型为DateTimeKind.Local的DateTime实例。该实例与时区相关联……无论本地系统时区如何。该属性将反映日光节约。
Triynko

4
因此,DateTimeOffset的真正问题在于它没有包含足够的信息。它包括一个偏移量,而不是时区。偏移量与多个时区是不明确的。
Triynko '16

41

这里的其他答案很有用,但它们没有专门介绍如何访问Pacific-在这里您可以:

public static DateTime GmtToPacific(DateTime dateTime)
{
    return TimeZoneInfo.ConvertTimeFromUtc(dateTime,
        TimeZoneInfo.FindSystemTimeZoneById("Pacific Standard Time"));
}

奇怪的是,尽管“太平洋标准时间”通常表示与“太平洋夏令时间”有所不同,但在这种情况下,它通常指太平洋时间。实际上,如果您使用FindSystemTimeZoneById它来获取它,则可用的属性之一是bool,它告诉您该时区当前是否处于夏时制。

您可以在一个库中看到更多通用的示例,我最终根据用户向何处询问,将这些库集中在一起处理不同的TimeZone中所需的DateTime,等等:

https://github.com/b9chris/TimeZoneInfoLib.Net

这在Windows外部(例如Linux上的Mono)不起作用,因为时间列表来自Windows注册表: HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones\

在下面可以找到键(注册表编辑器中的文件夹图标);这些密钥的名称就是您要传递的密钥FindSystemTimeZoneById。在Linux上,您必须使用单独的Linux标准时区定义集,但我没有对此进行充分探讨。


1
此外,还有ConvertTimeBySystemTimeZoneId()例如:TimeZoneInfo.ConvertTimeBySystemTimeZoneId(DateTime.UtcNow,“中央标准时间”)
布伦特

在Windows TimeZone ID列表中也可以看到以下答案:stackoverflow.com/a/24460750/4573839
阳健

7

我用扩展方法将Jon Skeet的答案更改为Web。它还可以像魅力一样在天蓝色上工作。

public static class DateTimeWithZone
{

private static readonly TimeZoneInfo timeZone;

static DateTimeWithZone()
{
//I added web.config <add key="CurrentTimeZoneId" value="Central Europe Standard Time" />
//You can add value directly into function.
    timeZone = TimeZoneInfo.FindSystemTimeZoneById(ConfigurationManager.AppSettings["CurrentTimeZoneId"]);
}


public static DateTime LocalTime(this DateTime t)
{
     return TimeZoneInfo.ConvertTime(t, timeZone);   
}
}

2

您必须为此创建一个自定义对象。您的自定义对象将包含两个值:

  • DateTime值
  • 一个时区对象

不知道是否已经有CLR提供的数据类型具有该数据类型,但是至少TimeZone组件已经可用。


2

我喜欢乔恩·斯基特(Jon Skeet)的回答,但想补充一件事。我不确定Jon是否期望ctor总是在本地时区传递。但我想将其用于除本地以外的其他情况。

我正在从数据库中读取值,并且知道该数据库位于哪个时区。因此在ctor中,我将传入数据库的时区。但是,那么我想以当地时间为准。Jon的LocalTime不会返回转换为本地时区日期的原始日期。它返回转换为原始时区的日期(无论您传递给ctor的是什么)。

我认为这些属性名称可以清除它...

public DateTime TimeInOriginalZone { get { return TimeZoneInfo.ConvertTime(utcDateTime, timeZone); } }
public DateTime TimeInLocalZone    { get { return TimeZoneInfo.ConvertTime(utcDateTime, TimeZoneInfo.Local); } }
public DateTime TimeInSpecificZone(TimeZoneInfo tz)
{
    return TimeZoneInfo.ConvertTime(utcDateTime, tz);
}

0

使用TimeZones类可以轻松创建时区特定的日期。

TimeZoneInfo.ConvertTime(DateTime.Now, TimeZoneInfo.FindSystemTimeZoneById(TimeZones.Paris.Id));

1
对不起,但是在Asp .NET Core 2.2上不可用,VS2017建议我安装Outlook Nuget程序包。
马查多

示例=> TimeZoneInfo.ConvertTime(DateTime.Now,TimeZoneInfo.FindSystemTimeZoneById(“ Pacific Standard Time”))
AZ_
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.