3

我想捕获按字母顺序指定的日期。可能是以下形式之一

  • 2013 年 1 月 1 日
  • 2013 年 1 月 1 日
  • 1月1日
  • 1月1日
  • 2013 年 1 月 1 日
  • 一月

此外,它们将出现在句子中。例如

“我们能在一月的某个下午见面吗?”

我在java中使用以下正则表达式

((?<month>jan(uary)?|feb(ruary)?|mar(ch)?|apr(il)?|may|jun(e)?|jul(y)?|aug(ust)?|sep(t?|tember)?|oct(ober)?|nov(ember)?|dec(ember)?)((\\s+)?(?<date>\\d+)?(st|nd|rd|th))?(\\s+)?,?(\\s+)?(?<year>(20)\\d\\d)?)

((?<date>\\d+)?(st|nd|rd|th)?\\s+(?<month>jan(uary)?|feb(ruary)?|mar(ch)?|apr(il)?|may|jun(e)?|jul(y)?|aug(ust)?|sep(t?|tember)?|oct(ober)?|nov(ember)?|dec(ember)?)(\\s+)?,?(?<year>(19|20)\\d\\d)?)

捕获正则表达式后,我需要指出字符串中标记的确切位置。

当我查看 Matcher.end() 返回的索引时,我的表达式似乎也捕获了一月之后的空间。我确实想捕获像“Jan 1st”这样的表达式,但前提是下一个捕获组匹配是可能的。

是否可以修改上面的正则表达式来做到这一点?

4

2 回答 2

2

扩展模式以提高可读性:

(
    (?<month>
        jan(uary)?
      | feb(ruary)?
      | mar(ch)?
      | apr(il)?
      | may
      | jun(e)?
      | jul(y)?
      | aug(ust)?
      | sep(t?|tember)?
      | oct(ober)?
      | nov(ember)?
      | dec(ember)?
    )
    (
        (\\s+)?
        (?<date>\\d+)?
        (st|nd|rd|th)
    )?
    (\\s+)?
    ,?
    (\\s+)?
    (?<year>(20)\\d\\d)?
)

即使年份不匹配,年份之前的空格也可以匹配。此外,即使日期不匹配,日期后缀也可以匹配。

清理和修复我得到的模式:

\\b
(?<month>
    jan(uary)?
  | feb(ruary)?
  | mar(ch)?
  | apr(il)?
  | may
  | jun(e)?
  | jul(y)?
  | aug(ust)?
  | sep(t|tember)?
  | oct(ober)?
  | nov(ember)?
  | dec(ember)?
)
(
    \\s*
    (?<date>\\d+)
    (st|nd|rd|th)?
)?
(
    \\s*
    ,?
    \\s*
    (?<year>(19|20)\\d\\d)
)?
\\b

我删除了外部组,因为无论如何您都将其作为组 0。t?insep(t?|tember)?更改为tjust 。所有的(\\s+)?都改成了等价的\\s*。我将?from(?<date>\\d+)?移至(st|nd|rd|th). 我将这一年包装在一个组中,然后将其移至那个?(?<year>20\\d\\d)我添加了单词边界 ( \\b),这样它就不会在单词中间开始或结束。

一行:

\\b(?<month>jan(uary)?|feb(ruary)?|mar(ch)?|apr(il)?|may|jun(e)?|jul(y)?|aug(ust)?|sep(t|tember)?|oct(ober)?|nov(ember)?|dec(ember)?)(\\s*(?<date>\\d+)(st|nd|rd|th)?)?(\\s*,?\\s*(?<year>(19|20)\\d\\d))?\\b

将它与您的第二个模式结合起来:

\\b
(
    (?<month1>
        jan(uary)?
      | feb(ruary)?
      | mar(ch)?
      | apr(il)?
      | may
      | jun(e)?
      | jul(y)?
      | aug(ust)?
      | sep(t|tember)?
      | oct(ober)?
      | nov(ember)?
      | dec(ember)?
    )
    (
        \\s*
        (?<date1>\\d+)
        (st|nd|rd|th)?
    )?
  |
    (?<date2>\\d+)
    (st|nd|rd|th)?
    \\s*
    (?<month2>
        jan(uary)?
      | feb(ruary)?
      | mar(ch)?
      | apr(il)?
      | may
      | jun(e)?
      | jul(y)?
      | aug(ust)?
      | sep(t|tember)?
      | oct(ober)?
      | nov(ember)?
      | dec(ember)?
    )
)
(
    \\s*
    ,?
    \\s*
    (?<year>(19|20)\\d\\d)
)?
\\b

一行:

\\b((?<month1>jan(uary)?|feb(ruary)?|mar(ch)?|apr(il)?|may|jun(e)?|jul(y)?|aug(ust)?|sep(t|tember)?|oct(ober)?|nov(ember)?|dec(ember)?)(\\s*(?<date1>\\d+)(st|nd|rd|th)?)?|(?<date2>\\d+)(st|nd|rd|th)?\\s*(?<month2>jan(uary)?|feb(ruary)?|mar(ch)?|apr(il)?|may|jun(e)?|jul(y)?|aug(ust)?|sep(t|tember)?|oct(ober)?|nov(ember)?|dec(ember)?))(\\s*,?\\s*(?<year>(19|20)\\d\\d))?\\b
于 2013-07-04T07:14:39.507 回答
1

另一个版本:

static private String month = "(?<month>jan(uary)?|feb(ruary)?|mar(ch)?|apr(il)?|may|jun(e)?|jul(y)?|aug(ust)?|sep(t|tember)?|oct(ober)?|nov(ember)?|dec(ember)?)";
static private String suffix = "(?:st|nd|rd|th)";
static private String date = "(?<date>\\d{1,2})";
static private String year = "(?<year>\\d{4})";

// A month name (optionally followed by space followed by a date (optionally
// followed by a suffix or space and a comma) (optionally followed by space
// followed by a year))
static private String order1 = String.format(
        "%s(?:\\s+%s(?:%s|\\s+,)?(?:\\s+%s)?)?", month, date, suffix,
        year);

// A date followed by a suffix followed by a month (optionally followed by
// space and a comma) optionally followed by space and a year
static private String order2 = String.format(
        "%s%s\\s+%s(?:\\s+,)?(?:\\s+%s)?", date, suffix, month, year);

是的,没有太多理由使用String.format,但既然是static,它在性能方面不应该是残酷的,并且它使正则表达式比我在 Java 中能想到的任何其他方式更容易阅读。

它匹配您所有的示例模式(并获得正确的输出,IIRC),包括句子中的版本。您可能遇到的唯一问题是,它会在“让我们在 1 月 1 日见面,好吗?”形式的日期之后立即吃掉逗号,尽管如果写成“让我们在 1 月 1 日见面,好吗?”它不会匹配逗号?” (当我说“匹配逗号”时,我的意思是整个正则表达式将使用逗号,尽管命名的捕获是正确的)。我确实将年份更改为仅匹配四位数。我还将日期更改为仅匹配一位或两位数字。像@MarkusJarderot 一样,我将“september”更改为没有可选的“t”,因为整个后缀是可选的。我尝试编写两个正则表达式,以便添加和删除逻辑块 - 与下面的版本进行比较,需要注意的事情:在某些情况下,两个正则表达式都会匹配(order1 只匹配单个月份,order2 匹配“1st Jan”形式的日期)。您可能想弄清楚在这种情况下如何选择要遵循的表达式。

现在,编写这些正则表达式是为了尽量避免匹配任何不符合所提供格式的日期。我建议修改它们以允许以下形式(# 表示原始列表中的项目):

  • 2013 年 1 月 1 日 #
  • 2013 年 1 月 1 日 // 注意逗号
  • 2013 年 1 月 1 日
  • 2013 年 1 月 1 日 #
  • 2013 年 1 月 1 日 // 注意逗号前没有空格
  • 1月1日#
  • 一月 #
  • Jan //(已经被原始示例支持)

  • 1 月 1 日

  • 1月1日#
  • 2013 年 1 月 1 日
  • 2013 年 1 月 1 日
  • 2013 年 1 月 1 日
  • 2013 年 1 月 1 日
  • 2013 年 1 月 1 日
  • 2013 年 1 月 1 日 #

此版本的代码支持上述形式。这也更好:月份已转换为使用所有非捕获模式(因此不会无缘无故地创建额外的捕获),并且我已经根据@MarkusJarderot 的回答删除了围绕整个正则表达式的捕获。日期格式的扩展数量还允许使用较少扭曲的正则表达式。这些表单引入的一个小问题是,现在v1将尝试将“2013 年 1 月 1 日”形式的日期匹配为“1 月 20 日”,同时v2正确匹配它们。这与我上面提到的“要小心”的问题相同;您可能想弄清楚如何决定使用哪个正则表达式更好(尝试两者并使用匹配更多日期片段的那个)。

static private String month = "(?<month>jan(?:uary)?|feb(?:ruary)?|mar(?:ch)?|apr(?:il)?|may|jun(?:e)?|jul(?:y)?|aug(?:ust)?|sep(?:t|tember)?|oct(?:ober)?|nov(?:ember)?|dec(?:ember)?)";
static private String suffix = "(?:st|nd|rd|th)";
static private String date = "(?<date>\\d{1,2})";
static private String year = "(?<year>\\d{4})";

// A month name (optionally followed by space followed by a date (optionally
// followed by a suffix)(optionally followed by a comma, possibly with space
// before it)(optionally followed by space followed
// by a year))
static private String v1 = String.format(
        "%s(?:\\s+%s%s?(?:\\s*,)?(?:\\s+%s)?)?", month, date, suffix, year);

// A date (optionally followed by a suffix) followed by space followed by a
// month (optionally followed by
// a comma, possibly with space before it) optionally followed by space and
// a year
static private String v2 = String.format(
        "%s%s?\\s+%s(?:\\s*,)?(?:\\s+%s)?", date, suffix, month, year);

或者,作为没有 Java 的正则表达式(的输出format):

(?<month>jan(?:uary)?|feb(?:ruary)?|mar(?:ch)?|apr(?:il)?|may|jun(?:e)?|jul(?:y)?|aug(?:ust)?|sep(?:t|tember)?|oct(?:ober)?|nov(?:ember)?|dec(?:ember)?)(?:\s+(?<date>\d{1,2})(?:st|nd|rd|th)?(?:\s*,)?(?:\s+(?<year>\d{4}))?)?
(?<date>\d{1,2})(?:st|nd|rd|th)?\s+(?<month>jan(?:uary)?|feb(?:ruary)?|mar(?:ch)?|apr(?:il)?|may|jun(?:e)?|jul(?:y)?|aug(?:ust)?|sep(?:t|tember)?|oct(?:ober)?|nov(?:ember)?|dec(?:ember)?)(?:\s*,)?(?:\s+(?<year>\d{4}))?
于 2013-07-04T09:48:07.243 回答