【问题标题】:Recommended way of parsing dates when presented in a variety of formats当以多种格式呈现时,推荐的解析日期的方式
【发布时间】:2011-02-17 00:37:30
【问题描述】:

我有一组日期作为用户在一段时间内输入的字符串。由于这些来自人类几乎没有或根本没有验证。输入的日期格式差异很大。以下是一些例子(前导数字仅供参考):

  1. 1897 年 8 月 20 日,21 日
  2. 1909 年 5 月 31 日,6 月 1 日
  3. 2007 年 1 月 29 日
  4. 1954 年 5 月 10 日、11 日、12 日
  5. 2006 年 3 月 26 日、27 日、28 日、29 日、30 日
  6. 2006 年 11 月 27 日、28 日、29 日、11 月 30 日、12 月 1 日

我想在 c# 中解析这些日期,最终得到一组 DateTime 对象,每天一个 DateTime 对象。所以上面的 (1) 会产生 2 个 DateTime 对象,(6) 会产生 5 个 DateTime 对象。

【问题讨论】:

  • 总是按日、月、年的顺序出现吗?
  • @Jamiegs:鉴于它是用户输入,我不会做出广泛的假设或信仰飞跃。
  • 忍不住玩一下,请看我更新的答案。

标签: c# datetime date-parsing


【解决方案1】:

我建议对它们进行概括处理(基本上删除数字和名称并使其成为占位符),然后按类似格式分组,以便您有一个示例组可供使用。

例如,20th, 21st August 1987 则变为 [number][postfix], [number][postfix] [month] [year](假设 <number><st|th|rd|nd> 被识别为数字和后缀,月份很明显,而年份是 4 位数字)。

从那里,您可以找出有多少遵循该模式,然后找到您需要匹配多少独特的模式。然后,您至少可以有一个样本来测试您希望使用的任何类型的算法(正则表达式可能是您最好的选择,因为它可以检测重复的模式 (#th[, $th[, ...]]) 和日期名称。)


看来您可能想按模式分解它(鉴于您提供的内容)。因此,例如首先打破年度信息:

(.*?)([0-9]{4})(?:, |$)

然后你需要把它分解成几个月

(.*?)(January|February|...)(?:, |$)

那么你想要那个月内的天数:

(?:([0-9]{1,2})(?:st|nd|rd|th)(?:, )?)*(?:, |$)

然后是编译信息。但同样,这只是在使用你在我面前的东西。最终,您需要知道您正在处理什么样的数据以及您想如何处理它。


更新

所以,我忍不住尝试自己解决这个问题。我想知道我使用的方法有些准确,而且我没有把烟吹到你的裙子上。话虽如此,这就是我想出的。请注意,这是在 PHP 中的几个原因:

  1. PHP 更容易上手
  2. 我觉得如果这个是一个可行的解决方案,你应该努力把它移植过来。 :咧嘴:

无论如何,这是源代码和演示输出。享受吧。

<?php
  $samples = array(
    '20th, 21st August 1897',
    '31st May, 1st June 1909',
    '29th January 2007',
    '10th, 11th, 12th May 1954',
    '26th, 27th, 28th, 29th, 30th March 2006',
    '27th, 28th, 29th, 30th November, 1st December 2006',
    '30th, 31st, December 2010, 1st, 2nd January 2011'
  );

  //header('Content-Type: text/plain');

  $months = array('january','february','march','april','may','june','july','august','september','october','november','december');

  foreach ($samples as $sample)
  {
    $dates = array();

    // find yearly information first
    $yearly = null;
    if (preg_match_all('/(?:^|\s)(?<month>.*?)\s?(?<year>[0-9]{4})(?:$|,)/',$sample,$yearly))
    {//var_dump($yearly);
      for ($y = 0; $y < count($yearly[0]); $y++)
      {
        $year = $yearly['year'][$y];
        //echo "year: {$year}\r\n";

        $monthly = null;
        if (preg_match_all('/(?<days>(?:(?:^|\s)[0-9]{1,2}(?:st|nd|rd|th),?)*)\s?(?<month>'.implode('|',$months).')$/i',$yearly['month'][$y],$monthly))
        {//var_dump($monthly);
          for ($m = 0; $m < count($monthly[0]); $m++)
          {
            $month = $monthly['month'][$m];
            //echo "month: {$month}\r\n";

            $daily = null;
            if (preg_match_all('/(?:^|\s)(?<day>[0-9]{1,2})(?:st|nd|rd|th)(?:,|$)/i',$monthly['days'][$m],$daily))
            {//var_dump($daily);
              for ($d = 0; $d < count($daily[0]); $d++)
              {
                $day = $daily['day'][$d];
                //echo "day: {$day}\r\n";

                $dates[] = sprintf("%d-%d-%d", array_search(strtolower($month),$months)+1, $day, $year);
              }
            }
          }
        }
        $data = $yearly[1];
      }
    }

    echo "<p><b>{$sample}</b> was parsed to include:</p><ul>\r\n";
    foreach ($dates as $date)
      echo "<li>{$date}</li>\r\n";
    echo "</ul>\r\n";
  }
?>

20th, 21st August 1897 被解析为包括:

  • 8-20-1897
  • 8-21-1897

1909 年 5 月 31 日,6 月 1 日 被解析为包括:

  • 6-1-1909

2007 年 1 月 29 日 被解析为包括:

  • 1-29-2007

1954 年 5 月 10 日、11 日、12 日 被解析为包括:

  • 5-10-1954
  • 5-11-1954
  • 5-12-1954

2006 年 3 月 26 日、27 日、28 日、29 日、30 日 被解析为包括:

  • 2006 年 3 月 26 日
  • 2006 年 3 月 27 日
  • 2006 年 3 月 28 日
  • 2006 年 3 月 29 日
  • 2006 年 3 月 30 日

2006 年 11 月 27 日、28 日、29 日、30 日、12 月 1 日被解析为包括:

  • 2006 年 12 月 1 日

2010 年 12 月 30 日、31 日、2011 年 1 月 1 日、2 日 被解析为包括:

  • 12-30-2010
  • 12-31-2010
  • 2011 年 1 月 1 日
  • 2011 年 1 月 2 日

为了证明我无所不能,http://www.ideone.com/GGMaH

【讨论】:

  • 这确实是一个很好的答案 - 谢谢。如果用户输入月份作为缩写,我总是可以将它们添加到月份数组中(例如 Jan、Feb、Mar)。只是一个小问题——上面的输出跳过了字符串“31st May, 1st June 1909”中的 31st May。是不是打错字了?
  • @Andrew:睡前做的,难免有bug。该错误在第二个正则表达式中;它不是重复比赛,而是抓住最后一场比赛。
【解决方案2】:

我对此进行了更多思考,解决方案变得显而易见。标记字符串并以相反的顺序解析标记。这将检索年份,然后是月份,然后是日期。这是我的解决方案:

// **** Start definition of the class bcdb_Globals ****
public static class MyGlobals
{
    static Dictionary<string, int> _month2Int = new Dictionary<string, int>
    {
        {"January", 1},
        {"February", 2},
        {"March", 3},
        {"April", 4},
        {"May", 5},
        {"June", 6},
        {"July", 7},
        {"August", 8},
        {"September", 9},
        {"October", 10},
        {"November", 11},
        {"December", 12}
    };
    static public int GetMonthAsInt(string month)
    {
        return( _month2Int[month] );
    }
}


public class MyClass
{
    static char[] gDateSeparators = new char[2] { ',', ' ' };

    static Regex gDayRegex = new Regex("[0-9][0-9]?(st|nd|rd|th)");
    static Regex gMonthRegex = new Regex("January|February|March|April|May|June|July|August|September|October|November|December");
    static Regex gYearRegex = new Regex("[0-9]{4}");

    public void ParseMatchDate(string matchDate)
    {
        Stack matchDateTimes = new Stack();
        string[] tokens = matchDate.Split(gDateSeparators,StringSplitOptions.RemoveEmptyEntries);
        int curYear = int.MinValue;
        int curMonth = int.MinValue;
        int curDay = int.MinValue;

        for (int pos = tokens.Length-1; pos >= 0; --pos)
        {
            if (gYearRegex.IsMatch(tokens[pos]))
            {
                curYear = int.Parse(tokens[pos]);
            }
            else if (gMonthRegex.IsMatch(tokens[pos]))
            {
                curMonth = MyGlobals.GetMonthAsInt(tokens[pos]);
            }
            else if (gDayRegex.IsMatch(tokens[pos]))
            {
                string tok = tokens[pos];
                curDay = int.Parse(tok.Substring(0,(tok.Length-2)));
                // Dates are in reverse order, so using a stack means we'll pull em off in the correct order
                matchDateTimes.Push(new DateTime(curYear, curMonth, curDay));
            }
        }

        // Now get the datetimes
        while (matchDateTimes.Count > 0)
        {
            // Do something with dates here
        }
    }

}

【讨论】:

    猜你喜欢
    • 2013-08-25
    • 2020-09-14
    • 2013-01-09
    • 2020-07-04
    • 1970-01-01
    • 2018-02-07
    • 1970-01-01
    • 1970-01-01
    • 2012-12-24
    相关资源
    最近更新 更多