多格式解析日期时间

34

我创建了一个API终端点。调用者可以使用POST方法传递相关参数调用API。

在这些参数中,有一个参数是datetime格式。

问题是,在调用此API时,调用者可以使用3种不同的格式传递datetime

  1. long int - 例如1374755180
  2. 美国格式 - 例如"7/25/2013 6:37:31 PM"(作为string
  3. 时间戳格式 - 例如"2013-07-25 14:26:00"(作为string

我必须解析datetime值并将其转换为DateTime或时间戳格式的string

我尝试过使用DateTime.TryParse()DateTime.Parse()Convert.ToDateTime()Convert.ToDouble(),但对我来说都没有确切的工作。

所需的输出格式必须是en-GB格式。

编辑:

我想使用if-else if-else块,三次使用TryParse,然后使用一个else语句表示无法解析该字符串。这是最好的解决方案吗?还有比这更好的解决方案吗?

请帮忙!


也许你可以使用 DateTime.TryParseExact ? - Kamil Budziewski
如果我不知道输入字符串的格式,这怎么有帮助呢? - Vivek Jain
1
@theghostofc,但是你知道,你有3个可能的输入字符串,一个不包含空格或任何特殊字符,另一个有“/”分隔符,另一个有“-”。你可以使用3个解析器来预验证确切的格式,但只需测试它是否为数字,或者它是否包含您期望的分隔符,然后尝试使用适当的格式进行解析即可。如果无法解析,则输入字段无效... - Martin
这将有所帮助,因为它会先在一个格式上失败,然后再在下一个格式上尝试,希望最终能够成功。DateTime.Parse()(或TryParse)内部可能也是类似的处理方式,尝试多种格式。让我看看能否给出一个答案。 - Randy James
7个回答

36

您应该考虑需要设置时区。 #2和#3需要,但#1不需要。

public DateTime ParseRequestDate()
{
    // https://dev59.com/HnE85IYBdhLWcg3wNw14

    CultureInfo enUS = new CultureInfo("en-US");

    var dt = "1374755180";
    //var dt = "7/25/2013 6:37:31 PM";
    //var dt = "2013-07-25 14:26:00";

    DateTime dateValue;
    long dtLong;

    // Scenario #1
    if (long.TryParse(dt, out dtLong))
        return dtLong.FromUnixTime();

    // Scenario #2
    if (DateTime.TryParseExact(dt, "MM/dd/yyyy hh:mm:ss tt", enUS, DateTimeStyles.None, out dateValue))
        return dateValue;

    // Scenario #3
    if (DateTime.TryParseExact(dt, "yyyy-MM-dd hh:mm:ss", enUS, DateTimeStyles.None, out dateValue))
        return dateValue;

    throw new SomeException("Don't know how to parse...");
}

编辑 正如Matt Johnson所指出的,DateTime.TryParseExact接受格式字符串数组。 2和3可以合并。

public DateTime ParseRequestDate()
{
    // https://dev59.com/HnE85IYBdhLWcg3wNw14

    CultureInfo enUS = new CultureInfo("en-US");

    var dt = "1374755180";
    //var dt = "7/25/2013 6:37:31 PM";
    //var dt = "2013-07-25 14:26:00";

    DateTime dateValue;
    long dtLong;

    // Scenario #1
    if (long.TryParse(dt, out dtLong))
        return dtLong.FromUnixTime();

    // Scenario #2 & #3
    var formatStrings = new string[] { "MM/dd/yyyy hh:mm:ss tt", "yyyy-MM-dd hh:mm:ss" };
    if (DateTime.TryParseExact(dt, formatStrings, enUS, DateTimeStyles.None, out dateValue))
        return dateValue;

    throw new SomeException("Don't know how to parse...");
}

我从另一个问题中借来的时代转换方法。(一种扩展方法)

public static class MyExtensions
{
    public static DateTime FromUnixTime(this long unixTime)
    {
        var epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
        return epoch.AddSeconds(unixTime);
    }
}

7
由于“TryParseExact”可以接受格式数组,因此您可以将#2和#3合并。而且时区不是必需的,只是那些格式会有“.Kind == DateTimeKinds.Unspecified”。 - Matt Johnson-Pint
1
@RandyJames,我不确定为什么时间戳格式的解析没有起作用,但是你的答案帮助我决定以不同的方式解析long int和其他两个。谢谢! - Vivek Jain
@RandyJames,我选择了你的答案,因为它让我的思路朝着正确的方向发展,我找到了解决方案。再次感谢! - Vivek Jain

34
您正在寻找 DateTime.ParseExact (MSDN文章),这适用于以下情况:
string[] formats= { "MM/dd/yyyy hh:mm:ss tt", "yyyy-MM-dd hh:mm:ss" }
var dateTime = DateTime.ParseExact("07/25/2013 6:37:31 PM", formats, new CultureInfo("en-GB"), DateTimeStyles.None);

这允许您将尽可能多的DateTime格式添加到数组中,而方法会在不需要if...else语句的情况下进行转换。

如果您的整数是自Unix纪元以来的秒数,则将该秒数加到纪元(01/01/1970)的DateTime中即可(.Net没有针对此的开箱即用方法,但逻辑是自“纪元”以来的秒数):

new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(seconds);

来自这个问题


我不确定为什么时间戳格式的解析没有起作用。谢谢你的回答! - Vivek Jain
这段代码会出现“字符串无法识别为有效的 DateTime”错误。需要添加前导0以满足预期格式 :) - Leszek P
当解析一个精确的格式时,为什么要提供特定的文化格式?这有什么意义,因为严格的格式已经在字符串中指定,文化如何可能影响结果? - PandaWood

3

我在一个项目中遇到了同样的问题,我的代码将在各种文化格式的不同环境中运行。

谷歌给我展示了这个隐藏的宝石。这个辅助函数是必不可少的,可以自动解析日期时间,无论文化格式如何。

使用示例:

string str = @"The last round was June 10, 2005; this time the unbroken record was held.";
DateTimeRoutines.ParsedDateTime pdt;
if (DateTimeRoutines.TryParseDate(str, DateTimeRoutines.DateTimeFormat.USA_DATE, out pdt))
    Console.WriteLine("Date was found: " + pdt.DateTime.ToString());

根据作者所述,这段代码能够解析各种情况:

@"Member since:      10-Feb-2008"
@"Last Update: 18:16 11 Feb '08 "
@"date    Tue, Feb 10, 2008 at 11:06 AM"
@"see at 12/31/2007 14:16:32"
@"sack finish 14:16:32 November 15 2008, 1-144 app"
@"Genesis Message - Wed 04 Feb 08 - 19:40"
@"The day 07/31/07 14:16:32 is "
@"Shipping is on us until December 24, 2008 within the U.S." 
@" 2008 within the U.S. at 14:16:32"
@"5th November, 1994, 8:15:30 pm"
@"7 boxes January 31 , 14:16:32."
@"the blue sky of Sept  30th  2008 14:16:32"
@" e.g. 1997-07-16T19:20:30+01:00"
@"Apr 1st, 2008 14:16:32 tufa 6767"
@"wait for 07/31/07 14:16:32"
@"later 12.31.08 and before 1.01.09"
@"Expires: Sept  30th  2008 14:16:32"
@"Offer expires Apr 1st, 2007, 14:16:32"
@"Expires  14:16:32 January 31."
@"Expires  14:16:32 January 31-st."
@"Expires 23rd January 2010."
@"Expires January 22nd, 2010."
@"Expires DEC 22, 2010."

2

解决这个问题的一种方式是设置一个工厂方法,它可以“理解”不同的格式并相应地解析它们。

您可以创建一系列的if-then-else来解决这个问题,但您也可以采用"基于表格的"实现方法:您需要的是一个委托数组,它接受一个字符串,并告诉您两件事情:

  • 这个委托是否能够解析传入的字符串
  • 如果是,则将其解析结果表示为DateTime

这里是一个样例实现:

private static readonly DateParsers = new Func<string,Tuple<DateTime,bool>>[] {
    (s) => {
        long res;
        if (long.TryParse(s, out res)) {
            // The format was correct - make a DateTime,
            // and return true to indicate a successful parse
            return Tuple.Create(new DateTime(res), true);
        } else {
            // It does not matter what you put in the Item1
            // when Item2 of the tuple is set to false
            return Tuple.Create(DateTime.MinValue, false);
        }
    }
    ...
    // Add similar delegates for other formats here
};

现在你的工厂方法可以按照以下方式实现:
private static bool TryParseMultiformat(string s, out DateTime res) {
    // Check all parsers in turn, looking for one returning success
    foreach (var p in DateParsers) {
        var tmp = p(s);
        if (tmp.Item2) {
            res = tmp.Item1;
            return true;
        }
    }
    res = DateTime.MinValue;
    return false;
}

你的解决方案非常有趣。但我想要一个简单的解决方案。感谢您的回答! - Vivek Jain

1
如果可能的格式是固定的,那么您可以使用TryParseExact
一种可能的解决方案是使用TryParse,如果无法获取正确的日期,则回退到已知的格式并使用TryPraseExact

0

感谢您的回答。我尝试了几个答案中提出的选项,并发现了一个对我有效的非常简单的方法。

public static bool ParseDate(string dateString, out DateTime dateValue)
{
    long dtLong = 0L;
    bool result = false;

    if (long.TryParse(dateString, out dtLong))
    {
        // I copied the epoch code here for simplicity
        dateValue = new DateTime(1970, 1, 1, 0, 0, 0).AddSeconds(dtLong);
        result = true;
    }

    // Working for US and Timestamp formats
    else if (DateTime.TryParse(dateString, out dateValue))
        result = true;

    return result;
}

之前我尝试使用TryParse来处理所有三种格式,但是没有成功。

不知何故,TryParseExact无法处理时间戳格式。它可以处理美国格式。这就是我不得不自己编写代码的原因。


0
如果您使用TryParseExact,只有上帝和微软开发人员知道它在放弃之前会尝试解析多少可能的日期时间格式。也许更好的解决方案是使用快速正则表达式,然后再使用适当的解析器。我试图使正则表达式尽可能简单,但您可能需要稍微调整一下。
    private  static readonly Regex R1
        = new Regex(@"^\d+$", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline);
    private static readonly Regex R2
        = new Regex(@"M$", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline);
    private static readonly Regex R3
        = new Regex(@"^\d{4}-", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline);

    private static void Main(string[] args)
    {
        string[] stringDates = new[]
            {
                "1374755180",
                "2013-07-25 14:26:00",
                "7/25/2013 6:37:31 PM"
            };


        foreach (var s in stringDates)
        {
            DateTime date = default(DateTime);
            if (R1.IsMatch(s))
                date = new DateTime(long.Parse(s));
            else if (R2.IsMatch(s))
                date = DateTime.Parse(s);
            else if (R3.IsMatch(s))
                date = DateTime.Parse(s);

            if (date != default(DateTime))
                Console.WriteLine("{0}", date);
        }

        Console.WriteLine("Press ENTER to continue...");
        Console.ReadLine();
    }

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