字符串转换为UTC时间

4

我正在获取带有以下日期的邮件存档。

Wed, 17 Dec 1997 13:36:23 +2
Mon, 16 Jun 1997 15:41:52 EST
Tue, 15 Jul 1997 14:37:00 EDT
Tue, 5 Aug 1997 08:37:56 PST
Tue, 5 Aug 1997 15:46:16 PDT
Thu, 5 Mar 1998 08:44:19 MET
Mon, 8 Nov 1999 17:49:25 GMT
Thu, 24 Feb 94 20:06:06 MST
Mon, 19 Dec 2005 14:17:06 CST
Thu, 14 Sep 95 19:15 CDT
Sat, 22 Feb 1997 05:16:55 UT
Mon, 8 Jul 1996 15:48:54 GMT-5
Mon, 25 Nov 1996 17:10:28 WET
Mon, 6 Jan 1997 23:43:48 UT
Fri, 13 Jun 1997 16:44:03 -0400

要求将此次转换为UTC时间。这是我尝试做到这一点的方式。

static void Main(string[] args)
{
    var possibleValues = new string[] 
    {
        "Mon, 29 Sep 2014 08:33:35 +0200"
        , "Fri, 29 Jun 2001 07:53:01 -0700"
        ,"Fri, 26 Sep 2014 15:57:04 +0000"
        ,"Wed, 17 Dec 1997 13:36:23 +2"
        , "Fri, 13 Jun 1997 16:44:03 -0400"

        , "Mon, 16 Jun 1997 15:41:52 EST"
        , "Tue, 15 Jul 1997 14:37:00 EDT"
        , "Tue, 5 Aug 1997 08:37:56 PST"
        , "Tue, 5 Aug 1997 15:46:16 PDT"
        , "Thu, 5 Mar 1998 08:44:19 MET"
        , "Mon, 8 Nov 1999 17:49:25 GMT"
        , "Thu, 24 Feb 94 20:06:06 MST"
        , "Mon, 19 Dec 2005 14:17:06 CST"
        , "Thu, 14 Sep 95 19:15:00 CDT"
        , "Sat, 22 Feb 1997 05:16:55 UT"
        , "Mon, 8 Jul 1996 15:48:54 GMT-5"
        , "Mon, 25 Nov 1996 17:10:28 WET"
        , "Mon, 6 Jan 1997 23:43:48 UT"

    };

    foreach (var item in possibleValues)
    {
        var dateParts = item.Split(' ');
        var lastItem = dateParts[dateParts.Length - 1];
        if (lastItem.StartsWith("+") || lastItem.StartsWith("-"))
        {
            try
            {
                DateTimeOffset offset = DateTimeOffset.Parse(item, CultureInfo.InvariantCulture);
                Debug.WriteLine("Input: {0}, UTC Time: {1}", item, offset.UtcDateTime);
            }
            catch (Exception exc)
            {
                Debug.WriteLine("Failed - {0}, Error Message: {1}", item, exc.Message);
            }
        }
        else
        {
            //Sometimes year is a two digit number and sometimes it is 4 digit number.
            string dateFormat = string.Format("ddd, {0} MMM {1} {2}:mm:ss {3}", new string('d', dateParts[1].Length), new string('y', dateParts[3].Length), int.Parse(dateParts[4].Substring(0, 2)) > 12 ? "HH" : "hh", lastItem);     
            try
            {
                DateTimeOffset offset = DateTimeOffset.ParseExact(item, dateFormat, CultureInfo.InvariantCulture, DateTimeStyles.None);
                Debug.WriteLine("Input: {0}, UTC Time: {1}", item, offset.UtcDateTime);
            }
            catch (Exception exc)
            {
                Debug.WriteLine("Failed - {0}, DateFormat Tried: {1}, Error Message: {2}", item, dateFormat, exc.Message);
            }
        }
    }
}

我无法处理所有的情况,希望可以使用Noda Time。

我查看了许多来自stackoverflow和Google的链接以找到答案,但是没有能够实现这些链接中任何一个答案。如果您知道类似的问题,请告诉我。

我已经查看了以下链接:

Convert.ToDateTime方法
类型转换
夏令时和时区最佳实践
SO标签时区
在.NET Framework中使用DateTime的编码最佳实践
将UTC日期时间字符串转换为C#


@JohnSaunders,谢谢,我会记住的。 - ndd
字符串似乎大多符合RFC 822/1123标准,但时区缩写"WET"和"MET"是例外。另外,形式为"GMT-5"和"+2"的偏移不符合规范,因为该格式需要像+0100这样的值。 - Matt Johnson-Pint
1
@MattJohnson 根据www.worldtimezone.com/wtz-names/wtz-met网站的资料,MET代表中欧时间(UTC+01)。 - Andrew Morton
@AndrewMorton - 谢谢。我总是忘记检查那个网站。 :) - Matt Johnson-Pint
1
@ndd 你可以寻找非标准时区缩写并将它们转换为标准缩写。但是,请注意Jon Skeet在Vimeo视频中在20分钟处解释的“CST”的问题。您可以通过检查电子邮件是否来自.com或.au地址来部分解决这个问题。 - Andrew Morton
显示剩余6条评论
1个回答

3
这些日期的格式大部分都符合RFC 822 §5.1,并得到RFC 1123 §5.2.14的修订支持。

然而,其中指定的若干个时区不符合标准。

  • "WET"通常是+0000
  • "MET"很罕见,但在这里显示为+0100。
  • "GMT-5"应该写成"-0500"
  • "+2"应该写成"+0200"

该格式仅提供以下定义:

  • "UT" / "GMT" = +0100
  • "EDT" = -0400
  • "EST" / "CDT" = -0500
  • "CST" / "MDT" = -0600
  • "MST" / "PDT" = -0700
  • "PST" = -0800

请注意,在正常情况下,任何时区缩写都可能存在歧义。例如,如您在此列表中所见,"CST"有5种不同的含义。只有在特定格式下,缩写才具有明确的上下文。换句话说,在RFC822/1123格式化值中,虽然"CST"是中国标准时间的一个有效缩写,但您永远不会使用CST。相反,您将使用"+0800"。

现在,在.NET中,RFC822/1123格式由"R"标准格式说明符覆盖。通常情况下,您可以使用DateTimeOffset.ParseExactDateTime.ParseExact来调用"R"格式说明符。但是,在此处,您不能使用它,因为它无法识别除"GMT"以外的任何时区缩写,也不能使用偏移量或两位数的年份。

然而,非精确解析器(DateTimeOffset.ParseDateTime.Parse)似乎可以识别大部分重要信息,并且我们可以利用这一点。您需要进行一些预处理来分配一个可识别的时区偏移量。

private static readonly Dictionary<string,string> TZMap = new Dictionary<string, string>
{
    // Defined by RFC822, but not known to .NET
    {"UT", "+0000"},
    {"EST", "-0500"},
    {"EDT", "-0400"},
    {"CST", "-0600"},
    {"CDT", "-0500"},
    {"MST", "-0700"},
    {"MDT", "-0600"},
    {"PST", "-0800"},
    {"PDT", "-0700"},

    // Extraneous, as found in your data
    {"WET", "+0000"},
    {"MET", "+0100"}
};

public static DateTimeOffset Parse(string s)
{
    // Get the time zone part of the string
    var tz = s.Substring(s.LastIndexOf(' ') + 1);

    // Replace time zones defined in the map
    if (TZMap.ContainsKey(tz))
    {
        s = s.Substring(0, s.Length - tz.Length) + TZMap[tz];
    }

    // Replace time zone offsets with leading characters
    if (tz.StartsWith("GMT+") || tz.StartsWith("GMT-") || tz.StartsWith("UTC+") || tz.StartsWith("UTC-"))
    {
        s = s.Substring(0, s.Length - tz.Length) + tz.Substring(3);
    }
        
    DateTimeOffset dto;
    if (DateTimeOffset.TryParse(s, CultureInfo.InvariantCulture, DateTimeStyles.None, out dto))
    {
        return dto;
    }

    throw new ArgumentException("Could not parse value: " + s);
}

这段代码可以通过你提供的所有示例值,但你可能会发现更多需要添加到映射表中的冗余值。在确定所有特殊情况之前,可能需要对数据进行多次处理。
当然,由于这里返回的是 DateTimeOffset,如果您需要 UTC 值,可以使用 .UtcDateTime.ToUniversalTime()

你知道我怎么才能进一步填充字典吗?我正在解析1993年的档案,如果我必须手动完成这个过程,那会让我崩溃的。 - ndd
很遗憾,对此没有一个好的答案。一般来说,时区缩写是有歧义的。只有通过使用上下文才能肯定地映射回特定的偏移量。在这种情况下,您可能可以信任RFC822规范中定义的那些,但从那里开始就要靠自己了。这只取决于当时电子邮件系统决定使用什么。 - Matt Johnson-Pint
你可以参考这里的列表:http://en.wikipedia.org/wiki/List_of_time_zone_abbreviations,http://www.timeanddate.com/library/abbreviations/timezones/和http://www.worldtimezone.com/wtz-names/timezonenames.html,但你会发现很多不一致和重复。你不能只是导入整个列表。最终,你必须一遍又一遍地运行数据,每次更新映射,直到你通过所有失败为止。 - Matt Johnson-Pint
1
另外,(挑剔一点)我看到你编辑了你的代码为.ToUniversalTime().DateTime - 这也等同于只调用.UtcDateTime - Matt Johnson-Pint
如果不是抛出异常,而是向用户(也就是你,我猜)显示一个带有问题条目的对话框,那么你可以为偏移量输入一个值(比如去查找它),将其添加到TZMap中(当然,在使其不是只读之后),然后再次解析。这样,无法解析的条目将会越来越少,因为你在逐个处理它们。我建议在此之后将TZMap的条目保存到文件中,这样每次运行程序时就不必再浏览所有条目了。 - Andrew Morton
显示剩余6条评论

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