使用PST/CEST/UTC等形式的时区解析日期时间

67

我正在尝试解析类似于国际日期时间字符串:

24-okt-08 21:09:06 CEST

到目前为止,我已经有了类似以下的东西:

CultureInfo culture = CultureInfo.CreateSpecificCulture("nl-BE");
DateTime dt = DateTime.ParseExact("24-okt-08 21:09:06 CEST",
    "dd-MMM-yy HH:mm:ss ...", culture);

问题是在格式字符串中,应该用什么来表示“...”? 查看自定义日期和时间格式字符串的MSDN页面似乎没有列出用于解析PST/CEST/GMT/UTC形式的时区的格式字符串。


13
记录一下,时区缩写如EST、PST、CST等没有标准化,并且有时会产生歧义(例如CST可能既指美国中部标准时间GMT-6,也可能指中国标准时间GMT+8)。 - dbkk
2
@dbkk 和“古巴标准时间”加勒比地区UTC-5以及“中央标准时间”中美洲UTC-6小时。http://www.timeanddate.com/library/abbreviations/timezones/ - Jodrell
我建议您只解析日期、月份、年份、小时、分钟和秒钟,然后将偏移小时(CEST)添加到创建的日期时间对象中。这样会更加标准化。 - Soundararajan
6个回答

44

据我所知,时区缩写是不被识别的。但是,如果您用时区偏移量来代替缩写,则可以解决问题。例如:

DateTime dt1 = DateTime.ParseExact("24-okt-08 21:09:06 CEST".Replace("CEST", "+2"), "dd-MMM-yy HH:mm:ss z", culture);
DateTime dt2 = DateTime.ParseExact("24-okt-08 21:09:06 CEST".Replace("CEST", "+02"), "dd-MMM-yy HH:mm:ss zz", culture);
DateTime dt3 = DateTime.ParseExact("24-okt-08 21:09:06 CEST".Replace("CEST", "+02:00"), "dd-MMM-yy HH:mm:ss zzz", culture);

15
这个能处理夏令时吗? - Vitaliy Ulantikov
6
不会,因为这里是手动指定偏移量。 - gzak
只要您为可能接收到的所有时区进行替换调用,并且源日期使用正确的时区(例如,不使用EST而是使用EDT),那么它就可以实现。 - thelem
1
所以等一下,它是否处理夏令时? - MIKE
1
@MIKE:考虑到中欧夏令时(CEST)和中欧冬令时(CET)并不相同,所以是的。然而,调用replace函数是有风险的,因为你不知道CEST/CET是否出现在文化X的日期字符串中。 - Stefan Steiger
硬编码时区偏移量不应该被使用,因为由于夏令时的原因,它可能会在同一时区发生变化。请参考 https://dev59.com/InVC5IYBdhLWcg3wlyMo - Michael Freidgeim

35

简单的回答是,你无法做到。


原因在于:

世界时区有一个确定性的数据库,可以从IANA这里获取。

问题在于,3个或4个字母的缩写与IANA时区存在一对多的关联。例如,"AMT"的含义会因为文化、所处的世界地区以及应用场景的不同而不同。

AMT "Armenia Time" Asia          UTC + 4 hours 
AMT "Amazon Time"  South America UTC - 4 hours 

如果你真的想解决这个问题,我建议使用Noda Time来表示你的Instance。您将需要编写一些代码将缩写转换为标准的IANA时区。

我们不能为您做到这一点,这取决于您的应用程序上下文。


另一个好例子是"CST"

CST "China Standard Time"   Asia            UTC + 8 hours 
CST "Central Standard Time" Central America UTC - 6 hours 
CST "Cuba Standard Time"    Caribbean       UTC - 5 hours 
CST "Central Standard Time" North America   UTC - 6 hours 

2
@gzak,是的,如果你想要将缩写本地化到传递的文化中,你可以找出映射关系。 - Jodrell
2
不,你做不到。Culture info会告诉你关于用户语言和文化偏好的信息,但它并不能告诉你用户所在的时区。即使我身处日本,我的文化可能仍然是en-US - Matt Johnson-Pint
2
@MattJohnson 但是如果你的文化是 en-US,即使你的时区是 UTC-5,我们也可以推断出 CST 对你来说意味着“北美中部标准时间 UTC-6”。当然,对于一个应用程序来说适合的可能不适合另一个应用程序。抽象地说,就像我的答案所述,你无法确定。有了足够的上下文,其中一部分将是文化,我们就可以知道。 - Jodrell
1
“en-US” 的意思是“美国英语”。换句话说,US 指的是方言,而不是地点。为什么因为我说美式英语就可以正确地推断出中央标准时间呢?当然,美国人会说“CST”,但如果我们恰好在中国,我们也可能用这个词来指代中国。 :) - Matt Johnson-Pint
1
@MattJohnson 因为作为一个在日本的美国英语使用者,使用配置为您正常文化的设备,您可能不会在应用程序中键入CST以表示除美国中部之外的其他内容。诚然,这并不是最终确定的,但作为一种启发式方法是可以接受的。 - jpmc26
显示剩余2条评论

16

如果您决定采用搜索和替换的方法(就像我一样),这是缩写词典。

Dictionary<string, string> _timeZones = new Dictionary<string, string>() {
            {"ACDT", "+1030"},
            {"ACST", "+0930"},
            {"ADT", "-0300"},
            {"AEDT", "+1100"},
            {"AEST", "+1000"},
            {"AHDT", "-0900"},
            {"AHST", "-1000"},
            {"AST", "-0400"},
            {"AT", "-0200"},
            {"AWDT", "+0900"},
            {"AWST", "+0800"},
            {"BAT", "+0300"},
            {"BDST", "+0200"},
            {"BET", "-1100"},
            {"BST", "-0300"},
            {"BT", "+0300"},
            {"BZT2", "-0300"},
            {"CADT", "+1030"},
            {"CAST", "+0930"},
            {"CAT", "-1000"},
            {"CCT", "+0800"},
            {"CDT", "-0500"},
            {"CED", "+0200"},
            {"CET", "+0100"},
            {"CEST", "+0200"},
            {"CST", "-0600"},
            {"EAST", "+1000"},
            {"EDT", "-0400"},
            {"EED", "+0300"},
            {"EET", "+0200"},
            {"EEST", "+0300"},
            {"EST", "-0500"},
            {"FST", "+0200"},
            {"FWT", "+0100"},
            {"GMT", "GMT"},
            {"GST", "+1000"},
            {"HDT", "-0900"},
            {"HST", "-1000"},
            {"IDLE", "+1200"},
            {"IDLW", "-1200"},
            {"IST", "+0530"},
            {"IT", "+0330"},
            {"JST", "+0900"},
            {"JT", "+0700"},
            {"MDT", "-0600"},
            {"MED", "+0200"},
            {"MET", "+0100"},
            {"MEST", "+0200"},
            {"MEWT", "+0100"},
            {"MST", "-0700"},
            {"MT", "+0800"},
            {"NDT", "-0230"},
            {"NFT", "-0330"},
            {"NT", "-1100"},
            {"NST", "+0630"},
            {"NZ", "+1100"},
            {"NZST", "+1200"},
            {"NZDT", "+1300"},
            {"NZT", "+1200"},
            {"PDT", "-0700"},
            {"PST", "-0800"},
            {"ROK", "+0900"},
            {"SAD", "+1000"},
            {"SAST", "+0900"},
            {"SAT", "+0900"},
            {"SDT", "+1000"},
            {"SST", "+0200"},
            {"SWT", "+0100"},
            {"USZ3", "+0400"},
            {"USZ4", "+0500"},
            {"USZ5", "+0600"},
            {"USZ6", "+0700"},
            {"UT", "-0000"},
            {"UTC", "-0000"},
            {"UZ10", "+1100"},
            {"WAT", "-0100"},
            {"WET", "-0000"},
            {"WST", "+0800"},
            {"YDT", "-0800"},
            {"YST", "-0900"},
            {"ZP4", "+0400"},
            {"ZP5", "+0500"},
            {"ZP6", "+0600"}
        };

14
抱歉,但这种方法不可靠。任何硬编码的缩写偏移列表都具有歧义性,并且只是时间点上的快照,会带有主观看法。 - Matt Johnson-Pint
9
当然。使用这些非标准化的时区本身就不可靠,因此这种方法同样(不)可靠。我的回答只是支持已接受的答案,请别忘了给那个答案点踩。 - Jussi Palo
6
有些人(甚至可以说大多数人)只接收与单一文化相关的数据。尽管这种方法并不完美,但也足以应付。 - Vincent Vancalbergh
3
即使对于一个单一的文化来说,这也是不可靠的。在过去的4年里,俄罗斯已经多次更改了夏令时规则。将缩写映射到IANA或Windows时区名称并查找当前规则要可靠得多。 - Panagiotis Kanavos
1
如果在正确的时间和从不正确之间做出选择,针对我现在正在处理的项目,我会选择“有时”正确。现在,如果没有使用可能会出错的辅助工具,我将不得不放弃解决问题。 - boatcoder

3
我有两个答案,因为我不确定你具体在问什么。
1) 我看到你正在使用CultureInfo,如果你只想要将日期和时间格式化为特定文化,我建议先将日期/时间和时区分离,然后对日期/时间应用Culture方法并附加时区。如果“CEST”在不同文化中是不同的,则必须通过列出所有选项(可能在case语句中)来更改它。
2) 如果你想将日期/时间转换为另一个时区,则无法使用CultureInfo。我建议阅读:http://msdn.microsoft.com/en-us/library/ms973825.aspx。你也可以使用.NET框架3.5类TimeZoneInfo(不同于TimeZone)使生活更轻松。

http://msdn.microsoft.com/en-us/library/system.timezoneinfo.aspx


2
这是如何做到的:
  1. 获取字符串(前提条件:格式为ddd,dd MMM yyyy HH:mm:ss zzz)
  2. 获取最后一个空格
  3. 从字符串中删除zzz,但保存zzz的值
  4. 查找zzz的偏移量
  5. 将偏移量添加到字符串中
string dateString = reader.ReadContentAsString();
int timeZonePos = dateString.LastIndexOf(' ') + 1;
string tz = dateString.Substring(timeZonePos);
dateString = dateString.Substring(0, dateString.Length - tz.Length );
dateString += s_timeZoneOffsets[tz];

// https://msdn.microsoft.com/en-us/library/w2sa9yss(v=vs.110).aspx
//string es = reader.ReadElementString("pubDate");
this.m_value = System.DateTime.ParseExact(dateString, "ddd, dd MMM yyyy HH:mm zzz", System.Globalization.CultureInfo.InvariantCulture);
带着。
private static System.Collections.Generic.Dictionary<string, string> s_timeZoneOffsets =
    new System.Collections.Generic.Dictionary<string, string>() {
    {"ACDT", "+10:30"},
    {"ACST", "+09:30"},
    {"ADT", "-03:00"},
    {"AEDT", "+11:00"},
    {"AEST", "+10:00"},
    {"AHDT", "-09:00"},
    {"AHST", "-10:00"},
    {"AST", "-04:00"},
    {"AT", "-02:00"},
    {"AWDT", "+09:00"},
    {"AWST", "+08:00"},
    {"BAT", "+03:00"},
    {"BDST", "+02:00"},
    {"BET", "-11:00"},
    {"BST", "-03:00"},
    {"BT", "+03:00"},
    {"BZT2", "-03:00"},
    {"CADT", "+10:30"},
    {"CAST", "+09:30"},
    {"CAT", "-10:00"},
    {"CCT", "+08:00"},
    {"CDT", "-05:00"},
    {"CED", "+02:00"},
    {"CET", "+01:00"},
    {"CEST", "+02:00"},
    {"CST", "-06:00"},
    {"EAST", "+10:00"},
    {"EDT", "-04:00"},
    {"EED", "+03:00"},
    {"EET", "+02:00"},
    {"EEST", "+03:00"},
    {"EST", "-05:00"},
    {"FST", "+02:00"},
    {"FWT", "+01:00"},
    {"GMT", "+00:00"},
    {"GST", "+10:00"},
    {"HDT", "-09:00"},
    {"HST", "-10:00"},
    {"IDLE", "+12:00"},
    {"IDLW", "-12:00"},
    {"IST", "+05:30"},
    {"IT", "+03:30"},
    {"JST", "+09:00"},
    {"JT", "+07:00"},
    {"MDT", "-06:00"},
    {"MED", "+02:00"},
    {"MET", "+01:00"},
    {"MEST", "+02:00"},
    {"MEWT", "+01:00"},
    {"MST", "-07:00"},
    {"MT", "+08:00"},
    {"NDT", "-02:30"},
    {"NFT", "-03:30"},
    {"NT", "-11:00"},
    {"NST", "+06:30"},
    {"NZ", "+11:00"},
    {"NZST", "+12:00"},
    {"NZDT", "+13:00"},
    {"NZT", "+12:00"},
    {"PDT", "-07:00"},
    {"PST", "-08:00"},
    {"ROK", "+09:00"},
    {"SAD", "+10:00"},
    {"SAST", "+09:00"},
    {"SAT", "+09:00"},
    {"SDT", "+10:00"},
    {"SST", "+02:00"},
    {"SWT", "+01:00"},
    {"USZ3", "+04:00"},
    {"USZ4", "+05:00"},
    {"USZ5", "+06:00"},
    {"USZ6", "+07:00"},
    {"UT", "-00:00"},
    {"UTC", "-00:00"},
    {"UZ10", "+11:00"},
    {"WAT", "-01:00"},
    {"WET", "-00:00"},
    {"WST", "+08:00"},
    {"YDT", "-08:00"},
    {"YST", "-09:00"},
    {"ZP4", "+04:00"},
    {"ZP5", "+05:00"},
    {"ZP6", "+06:00"}
};

谢谢。是的,有很多不好的答案。 - JohnB
2
这仍然是一个糟糕的答案,原因在本页面其他地方已经讨论过。您正在硬编码假设缩写诸如“AMT”或“CST”的含义。问题在于这个问题没有好的答案;这些代码是模棱两可的,没有查找表可以确定。这些假设可能足以解决OP的问题,但它们没有在问题中说明。 - Ryan Bemrose
3
不要在您的帖子中添加“Necromancing”声明。这些只会让内容杂乱无章;SO并没有关于对旧问题活动的政策。如果有什么,那么它有点鼓励,因为随着技术的变化,新的答案会出现。 - jpmc26
@jpmc26:当我回答一个旧问题时,我会在我的帖子中加入这些声明。这样做的想法是让人们看到低投票数与其他答案相比是因为答案是新的,而不是答案不好。虽然,当查看答案日期时,人们已经可以看到这一点 - 因此,这种声明总是多余的。 - Stefan Steiger
@Ryan Bemrose:实际上,我假设它们是Windows时区,因此缩写是明确的。然而,如果是IANA时区,则会存在歧义,因此没有解决方案。假设输入值来自Windows机器,在95%的桌面机器情况下,这是最好的选择。但是,如果您在Linux机器上运行代码,则首先必须将从C#生成的posix时区映射到Windows时区以进行查找。我有这个代码的副本,但是否明确是一个好问题。 - Stefan Steiger
任何人,你可能会对 https://github.com/ststeiger/ews-managed-api/blob/master/Microsoft.Exchange.WebServices.Data.NetFramework4/TimeZoneData/TimeZoneTranslator.cs 感兴趣。 - Stefan Steiger

-1
这是我所做的事情。
我从JavaScript接收日期时间,然后将其传递给ASP.NET以存储在Oracle数据库中。以下是我的C#代码,适用于东部和中部时区。
string datetimevalue = hidfileDateTime.Value; 

datetimevalue= datetimevalue.Replace("EDT", "EST"); 
datetimevalue = datetimevalue.Replace("CDT", "CST");
if (datetimevalue.Contains("CST"))
{
    filedt = DateTime.ParseExact(datetimevalue, "ddd MMM d HH:mm:ss CST yyyy", provider).ToUniversalTime().AddHours(1).ToLocalTime();
}
else
{
    filedt = DateTime.ParseExact(datetimevalue, "ddd MMM d HH:mm:ss EST yyyy", provider);
}

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