以各种格式呈现时推荐的解析日期的方法

时间:2011-02-17 00:37:30

标签: c# datetime date-parsing

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

  1. 1897年8月21日,第21天。
  2. 1909年5月31日,1月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日,12月1日
  7. 我想在c#中解析这些日期,最终得到DateTime对象集,每天有一个DateTime对象。因此上面的(1)将导致2个DateTime对象,(6)将导致5个DateTime对象。

2 个答案:

答案 0 :(得分:3)

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

例如,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. 我觉得如果这个 是一个可行的解决方案,你应该努力将其移植。 :微笑:
  3. 无论如何,这是源和演示输出。享受。

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

    1897年8月21日20日被解析为包括:

    • 1897年8月20日
    • 1897年8月21日

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

    • 1909年6月1日

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

    • 2007年1月29日

    1954年5月11日,11日,被解析为包括:

    • 1954年5月10日
    • 1954年5月11日
    • 1954年5月12日

    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日,被解析为包括:

    • 2006年12月1日

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

    • 二零一零年十二月三十〇日
    • 2010年12月31日
    • 2011年1月1日
    • 2011年1月2日

    为了证明我的袖子没有,http://www.ideone.com/GGMaH

答案 1 :(得分:0)

我想到了更多关于这一点,解决方案变得明显。对字符串进行标记并以相反的顺序解析标记。这将检索年份,然后是月份和天。这是我的解决方案:

// **** 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
        }
    }

}