将日期字符串解析为特定时区的时间(支持夏令时)

24

过去几周我一直在努力工作,但是我遇到了一个小问题。我觉得我的头脑现在还没有适应这个任务:)所以我需要一些技巧/帮助!这可能很简单,但我的思维还没有跟上。

用户将输入AEST日期和时间。还有一个应用设置的“默认”时区(因为它可能需要更改),目前设置为“AUS Eastern Standard Time”。

因此,在美国服务器上有一个具有定义系统时区的用户字符串(因此本地不匹配且无法更改或使用)。

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

当前的AEST时间也可能处于夏令时内或外,因此我不能只将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

这是非常好的结果,只是使用不同的输入日期而已。

这种情况发生的原因是当前的夏令时设置和输入日期的夏令时设置不同,我想找到一个解决办法 :)

可以把它想象成鸡和蛋的问题。在解析字符串之前,我需要正确的时区数据,但只有在解析完字符串后才能获得它(因此需要一个复杂的解决方案)。

或者我需要 .NET 使用 myTimeZone 对象来解析字符串,以便它知道如何自行设置它,但我看不到任何执行此操作的函数,它们都需要已经解析并设置了日期时间或日期时间偏移对象。

因此,我正在寻找其他人可能已经解决的优雅解决方案?我肯定不是唯一一个注意到这个问题的人吧?

编辑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

唯一的问题是,如果用户输入的日期恰好在夏令时转换点上,则可能会有问题,因为它只读取当前偏移量并使用它,然后检查它是否应该是夏令时,如果超出范围,则会读取不正确。然而,这比我现在拥有的要好得多。

有人可以帮我整理一下这个函数吗?这是我需要走的最佳路线吗?还有其他想法吗?


1
这是我见过的写得非常出色的问题之一... 做得好。 - Reddog
AEST偏移在夏季不会改变。观察夏令时的地区切换到AEDT。 - thelem
5个回答

17

这里提供一个预定义格式的简单解决方案,同样也可以是动态的。我个人用它来与JavaScript进行交互:

这是一个简单的解决方案,适用于特定的格式,同时也可以是动态的。我个人将其用于与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;
}

1
谢谢!结果相同但更紧凑(我的修改版仍比你的多一行)。在转换小时时仍会有偶尔的变化: 读取1:2012/10/07 02:00:00 +10:00 读取1 - UTC:2012/10/06 16:00:00 +00:00 改回来:2012/10/07 03:00:00 +11:00但除此之外对我来说都很好用。 - White Dragon
@WhiteDragon 如果你需要这样做,那么我会先获取UTC时间,然后根据你所需的时间量回溯,最后再转换回对应的时区。 - André Snede
这似乎只是将偏移量应用于日期时间。如果日期在夏令时期间,会不会有问题? - Fidel
@Fidel 不是的,GetUtcOffset 函数会获取日期时间,并查找在该时区、该日期和时间下应用的偏移量。然后创建一个 DateTimeOffset。使用 GetUtcOffset 是为了避免你所说的问题。但这确实是一个非常合理的担忧。 - André Snede

2

当我需要将已知时区的日期时间字符串解析为本地时区时,这种方法对我很有用。虽然我没有在许多情况下进行测试,但到目前为止,在欧盟某个服务器上解析时间方面它表现得非常出色。

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

我完全忘记了这个小问题!你的解决方案实际上非常接近我现在使用的方法。但是,我需要使用DateTimeOffset对象,并且不能使用服务器的本地时间 - 但是作为对其他人的提示,您可以使用相同的方法将文本直接解析为带有DateTimeOffset对象的时区! :) - White Dragon
1
我想在我的上面的评论中添加一些内容(更清晰),即仅当服务器使用与您希望从中读取用户解析数据的相同时区时,此方法才有效。在我的本地机器上使用上述方法可以正常工作(以及我们当前的服务器),但将代码移动到GoGrid(类似于-9)时,除非我按照下面的函数使用,否则它会失败。 - White Dragon

1

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

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;
    }
}

0

我觉得你没有理解我的意思,那个函数需要一个已经设置好的日期时间对象,我知道如何在时区之间进行转换,但我想知道如何解决我上面描述的问题。字符串->补偿时区的datetimeoffset。 - White Dragon
我可以建议采取不同的方法。请确保将所有日期保存为DateTime值(即使是在数据库中,而不是字符串),然后日期时间转换将为您半自动完成。或者,将其转换为UTC并将所有时间存储为UTC,然后在需要显示它们时,将其从UTC转换为适当的时区。 - David McEwing
我认为你还是有些错误,数据库和代码中所有日期都是DateTimeOffsets,并且已经以UTC格式存储。问题出在用户输入数据的解析上 :),这是需要解决的部分。请检查两个编辑、新功能和代码,了解我在说什么(代码胜过言辞)。 - White Dragon
用户在管理员界面输入一个项目的开始日期和结束日期,这些日期必须以澳大利亚东部标准时间(AEST)输入,但服务器是美国的,所以即使我想使用本地时间,也不行。我们有严格的禁运措施,甚至到小时级别,因此当夏令时更改时间到来时,输入的日期和时间必须正确,并且可以输入未来的日期,这可能是完全不同的偏移量(但是相同的时区!)因为夏令时。 - White Dragon

0

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

new DateTimeOffset(dateTimeLocal.Ticks, timezone.BaseUtcOffset)

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