将日期字符串解析为某个时区(支持夏令时)

Posted

技术标签:

【中文标题】将日期字符串解析为某个时区(支持夏令时)【英文标题】:Parse a date string into a certain timezone (supporting daylight saving time) 【发布时间】:2011-08-02 16:47:32 【问题描述】:

好的,过去几周我一直在努力工作,但遇到了一个小问题。我不认为我的头脑现在完全能够胜任这项任务:) 所以我需要一些提示/帮助!这可能很简单,但我的脑袋还没动。

用户将在 AEST 中输入日期和时间。还有一个应用程序设置“默认”时区(可能需要更改),当前设置为“澳大利亚东部标准时间”

所以我们在美国的服务器上有一个没有时区的用户字符串和一个定义的系统时区(所以本地不匹配,不能更改或使用)

现在我需要的是一种方式来表示“使用时区 X 解析此用户输入的字符串”我不能只输入 +10 或 +11 作为偏移量,因为日期可能在夏令时之内或之外;是的,即使在同一时区,它也会在 +10 和 +11 之间改变!

当前 AEST 时间也可能在 DST 之内或之外,因此我不能只将 UTC 日期转换为当前 AEST 时间并获取“zzz”字符串并将其附加,因为日期将相差一个小时在当前 DST 设置之外输入的任何内容。

现在代码实际上就是这样做的:

TimeZoneInfo ConvTo = TimeZoneInfo.FindSystemTimeZoneById(ConfigurationManager.AppSettings["DefaultTimeZone"]);
DateTimeOffset getDate = TimeZoneInfo.ConvertTime(DateTimeOffset.UtcNow, ConvTo);
string TimeZoneId = " " + getDate.ToString("zzz");
DateTimeOffset cvStartDate = DateTimeOffset.MinValue; DateTimeOffset.TryParse(StartDate + TimeZoneId, out cvStartDate);

然后我通过检查日期是否仍然 == DateTimeOffset.MinValue 或将其转换为 UTC 并添加到数据库来检查日期是否无效,当显示时它将被转换回 AEST。然而,有些日期相差一个小时,而另一些则完美(如预期的那样):)

解决这个问题最优雅的方法是什么?

编辑:

为了帮助解释问题,我编写了一些测试代码作为 Windows 测试应用程序:

// User entered date
string EnteredDate = "2011/01/01 10:00:00 AM";

// Get the timezone we want to use
TimeZoneInfo myTimeZone = TimeZoneInfo.FindSystemTimeZoneById("AUS Eastern Standard Time");

// Find the timezone string of the selected timezone to parse the user string
// This is the part that is incorrect and what i need help with.
DateTimeOffset getDate = TimeZoneInfo.ConvertTime(DateTimeOffset.UtcNow, myTimeZone);
string TimeZoneId = " " + getDate.ToString("zzz");

// Parse the string into the date object
DateTimeOffset cvEnteredDate = DateTimeOffset.MinValue; DateTimeOffset.TryParse(EnteredDate + TimeZoneId, out cvEnteredDate);

// Display
textBox1.Text += "Parsed: " + cvEnteredDate.ToString("yyyy/MM/dd HH:mm:ss zzz") + Environment.NewLine;

// Convert to UTC and display
cvEnteredDate = cvEnteredDate.ToUniversalTime();
textBox1.Text += "UTC: " + cvEnteredDate.ToString("yyyy/MM/dd HH:mm:ss zzz") + Environment.NewLine;

// Convert back to AEST and display
cvEnteredDate = TimeZoneInfo.ConvertTime(cvEnteredDate, myTimeZone);
textBox1.Text += "Changed Back: " + cvEnteredDate.ToString("yyyy/MM/dd HH:mm:ss zzz") + Environment.NewLine;

这是什么输出?

解析:2011/01/01 10:00:00 +10:00 UTC: 2011/01/01 00:00:00 +00:00 改回:2011/01/01 11:00:00 +11:00

请注意小时减一并且偏移量不同。此外,如果我们只是将输入的日期更改为:

string EnteredDate = "2011/04/20 10:00:00 AM";

我们得到:

解析:2011/04/20 10:00:00 +10:00 UTC: 2011/04/20 00:00:00 +00:00 改回:2011/04/20 10:00:00 +10:00

这很好,很好,使用相同的代码只是输入不同的日期。

发生这种情况是因为当前 DST 设置和输入日期的 DST 设置不同,这就是我想要的解决方案:)

把它想象成鸡和蛋的问题。在解析输入的字符串之前,我需要正确的时区数据,只有在解析完字符串后才能获得(因此将是一个精心设计的解决方案)

或者我需要 .NET 使用 myTimeZone 对象解析字符串,以便它知道将其设置为自身,但我看不到任何执行此操作的函数,它们 all 已经解析并设置 datetime 或 datetimeoffset 对象

所以我正在寻找其他人可能已经完成的优雅解决方案?我肯定不会是唯一注意到这一点的人吗?

编辑2:

好的,我已经创建了一个“工作”函数来解决我认为的问题,这是一个示例(将文本框添加到 c# windows 应用程序并使用下面的代码来测试自己):

private void Form1_Load(object sender, EventArgs e)

    TimeZoneInfo myTimeZone = TimeZoneInfo.FindSystemTimeZoneById("AUS Eastern Standard Time");

    DateTimeOffset get1Date = ReadStringWithTimeZone("2011/01/01 10:00:00 AM", myTimeZone);
    textBox1.Text += "Read1: " + get1Date.ToString("yyyy/MM/dd HH:mm:ss zzz") + Environment.NewLine;
    get1Date = get1Date.ToUniversalTime();
    textBox1.Text += "Read1 - UTC: " + get1Date.ToString("yyyy/MM/dd HH:mm:ss zzz") + Environment.NewLine;
    get1Date = TimeZoneInfo.ConvertTime(get1Date, myTimeZone);
    textBox1.Text += "Changed Back: " + get1Date.ToString("yyyy/MM/dd HH:mm:ss zzz") + Environment.NewLine + Environment.NewLine;

    DateTimeOffset get2Date = ReadStringWithTimeZone("2011/04/20 10:00:00 AM", myTimeZone);
    textBox1.Text += "Read2: " + get2Date.ToString("yyyy/MM/dd HH:mm:ss zzz") + Environment.NewLine;
    get2Date = get2Date.ToUniversalTime();
    textBox1.Text += "Read2 - UTC: " + get2Date.ToString("yyyy/MM/dd HH:mm:ss zzz") + Environment.NewLine;
    get2Date = TimeZoneInfo.ConvertTime(get2Date, myTimeZone);
    textBox1.Text += "Changed Back: " + get2Date.ToString("yyyy/MM/dd HH:mm:ss zzz") + Environment.NewLine + Environment.NewLine;


public DateTimeOffset ReadStringWithTimeZone(string EnteredDate, TimeZoneInfo tzi)

    DateTimeOffset cvUTCToTZI = TimeZoneInfo.ConvertTime(DateTimeOffset.UtcNow, tzi);
    DateTimeOffset cvParsedDate = DateTimeOffset.MinValue; DateTimeOffset.TryParse(EnteredDate + " " + cvUTCToTZI.ToString("zzz"), out cvParsedDate);
    if (tzi.SupportsDaylightSavingTime)
    
        TimeSpan getDiff = tzi.GetUtcOffset(cvParsedDate);
        string MakeFinalOffset = (getDiff.Hours < 0 ? "-" : "+") + (getDiff.Hours > 9 ? "" : "0") + getDiff.Hours + ":" + (getDiff.Minutes > 9 ? "" : "0") + getDiff.Minutes;
        textBox1.Text += "Diff: " + MakeFinalOffset + Environment.NewLine;
        DateTimeOffset.TryParse(EnteredDate + " " + MakeFinalOffset, out cvParsedDate);
        return cvParsedDate;
    
    else
    
        return cvParsedDate;
    

还有输出:

差异:+11:00 阅读1:2011/01/01 10:00:00 +11:00 读取 1 - UTC:2010/12/31 23:00:00 +00:00 改回:2011/01/01 10:00:00 +11:00 差异:+10:00 阅读2:2011/04/20 10:00:00 +10:00 读取 2 - UTC:2011/04/20 00:00:00 +00:00 改回:2011/04/20 10:00:00 +10:00

唯一的问题是,如果用户输入的日期在 DST 的更改时间是正确的,它仍然可能是一个小时的时间,因为它只是读取当前的偏移量并使用它,然后检查它是否应该是夏令时与否,如果它在那里,它会读取不正确。但是,它比我现在拥有的要好几英里。

谁能帮我清理这个功能?这是我需要的最佳路线吗?想法?

【问题讨论】:

这是我见过的最令人印象深刻的书面问题之一......做得好。 AEST 偏移量在夏季不会改变。遵守夏令时的区域切换到 AEDT。 【参考方案1】:

最简单的解决方案: 首先将字符串解析为本地日期时间,使用任何解析方法 然后调用

new DateTimeOffset(dateTimeLocal.Ticks, timezone.BaseUtcOffset)

【讨论】:

【参考方案2】:

这是一个针对预定义格式的简单解决方案,它也可以是动态的。我个人用它来与 javascript 交谈:

public DateTimeOffset ParseDateExactForTimeZone(string dateTime, TimeZoneInfo timezone)

    var parsedDateLocal = DateTimeOffset.ParseExact(dateTime, "yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture);
    var tzOffset = timezone.GetUtcOffset(parsedDateLocal.DateTime);
    var parsedDateTimeZone = new DateTimeOffset(parsedDateLocal.DateTime, tzOffset);
    return parsedDateTimeZone;

【讨论】:

谢谢!结果相同,但版本更紧凑(我重新设计的版本仍然比你的多行)。小时变化仍有一个小时的奇数变化: Read1: 2012/10/07 02:00:00 +10:00 Read1 - UTC: 2012/10/06 16:00:00 +00:00 已更改返回:2012/10/07 03:00:00 +11:00 但除此之外对我来说很好。 @WhiteDragon 如果您需要这样做,那么我会采用 UTC 时间,返回您需要的时间量,然后再次将其转换为时区。 这似乎只是将偏移量应用于日期时间。如果日期在夏令时期间不会有问题吗? @Fidel 否,GetUtcOffset 函数获取日期时间,并查找在该时间和日期在该时区应用的偏移量。然后创建一个 DateTimeOffset。 GetUtcOffset 用于避免您正在谈论的问题。但这是一个非常有道理的担忧。【参考方案3】:

当我需要将日期时间字符串从已知时区解析到本地时区时,这似乎对我有用。在很多情况下尚未对其进行测试,但到目前为止,它在欧盟某处的服务器上解析时间非常有效。

TimeZoneInfo.ConvertTime(DateTime.Parse("2012-05-25 23:17:15", CultureInfo.CreateSpecificCulture("en-EU")),TimeZoneInfo.FindSystemTimeZoneById("W. Europe Standard Time"),TimeZoneInfo.Local)

【讨论】:

我完全忘记了这个小问题!您的解决方案实际上非常接近我现在使用的解决方案。但是,我需要使用 DateTimeOffset 对象,而且我永远无法使用服务器的本地时间 - 但作为对其他人的说明,您可以使用相同的方法将文本直接解析为具有 DateTimeOffset 对象的时区! :) 我想在我上面的评论中添加(更清楚一点),这似乎只有在服务器使用与您希望从中读取用户解析数据相同的时区时才有效。在我的本地机器上使用上述内容(以及我们当前的服务器)但将代码移动到 GoGrid(类似于 -9)它会失败,除非我使用下面的函数【参考方案4】:

由于没有人提供任何更好的解决方案(这非常令人惊讶!)我接受我自己的函数作为答案,尽管我可能会使函数更简洁并稍后重新设计:

public DateTimeOffset ReadStringWithTimeZone(string EnteredDate, TimeZoneInfo tzi)

    DateTimeOffset cvUTCToTZI = TimeZoneInfo.ConvertTime(DateTimeOffset.UtcNow, tzi);
    DateTimeOffset cvParsedDate = DateTimeOffset.MinValue;
    DateTimeOffset.TryParse(EnteredDate + " " + cvUTCToTZI.ToString("zzz"), out cvParsedDate);
    if (tzi.SupportsDaylightSavingTime)
    
        TimeSpan getDiff = tzi.GetUtcOffset(cvParsedDate);
        string MakeFinalOffset = (getDiff.Hours < 0 ? "-" : "+") + (getDiff.Hours > 9 ? "" : "0") + getDiff.Hours + ":" + (getDiff.Minutes > 9 ? "" : "0") + getDiff.Minutes;
        DateTimeOffset.TryParse(EnteredDate + " " + MakeFinalOffset, out cvParsedDate);
        return cvParsedDate;
    
    else
    
        return cvParsedDate;
    

【讨论】:

【参考方案5】:

TimeZoneInfo 有一个静态方法ConvertTimeToUtc,可以让您指定日期时间和时区。这将为您进行时间调整,并返回 UTC 日期。

MSDN 文档是http://msdn.microsoft.com/en-us/library/bb495915.aspx,并附有示例。

【讨论】:

我认为你错过了我所说的,该函数需要一个已经设置的日期时间对象,我知道如何在时区之间进行转换,但我想问如何解决我所描述的问题更多。 string -> datetimeoffset 补偿时区 我可以建议一个不同的策略。确保将所有日期保存为 DateTime 值(即使在数据库中,而不是字符串中),那么日期时间转换将为您半自动化。或者交替转换为 UTC 并将所有时间存储在 UTC 中,然后当您需要显示它们时,将它们从 UTC 转换为适当的时区。 我想你还在这里,所有日期都是数据库和代码中的 DateTimeOffsets,已经存储为 UTC。解析来自用户输入的数据:),这就是问题所在。检查 2 个编辑和新功能以及代码,看看我在说什么(代码胜于雄辩) 用户在管理员中输入开始日期和结束日期以使项目上线和结束,这些日期必须输入为 AEST,但服务器是美国的,所以本地时间不是我的东西即使我想也可以使用。我们有严格的禁运时间,因此当 DST 更改时间围绕输入的日期和时间滚动时,必须正确,并且可以为未来输入,这可能是 DST 的完全不同的偏移量(但相同的时区!)原因

以上是关于将日期字符串解析为某个时区(支持夏令时)的主要内容,如果未能解决你的问题,请参考以下文章

在使用利用夏令时的 Python 时区时,应该传递啥 tzinfo?

在指定时区导入日期时间,忽略夏令时

如何在 Dynamics CRM 中获取时区的夏令时开始和结束?

带有时区和夏令时的纪元的日期和时间(以秒为单位)

未来日期/年份的 Java 夏令时 (TimeZone/ZoneId) 支持

如何添加关于夏令时时区的每周时间增量