【问题标题】:Can my Regex be improved?我的正则表达式可以改进吗?
【发布时间】:2016-03-07 10:40:44
【问题描述】:

是的,另一个正则表达式问题。不客气;-P

这是我第一次在 C# 中为一些简单的字符串验证编写自己的正则表达式。我想我已经成功了,但作为一个学习练习,我想知道它是否可以改进以及我是否犯了任何错误。

字符串看起来都像这样:

T20160307.0001

规则:

  • 以字母 T 开头。
  • 格式为 YYYYMMDD 的日期。
  • 句号。
  • 最后 4 个字符始终是数字。应该正好有 4 个。

这是我的正则表达式 (fiddle):

^(?i)[T]20[0-9]{2}[0-1][0-9][0-3][0-9].\d{4}$

  • ^ 断言字符串的开头。
  • (?i)[T] 检查我们是否有字母 T,不区分大小写。
  • 20 YYYY 以 20 开头(我将在 2100 年前死去,所以我不再关心任何事情:-P)
  • [0-9]{2} 0 到 99 之间的任意数字,表示 YYYY 的第二部分。
  • [0-1][0-9] 0 或 1 表示月初,0-9 表示月初。
  • [0-3][0-9] 0-3 表示第一天,0-9 表示第二天。
  • .句号。
  • \d{4} 4 个数字字符。
  • $ 断言字符串结束。

我已经看到的一个缺陷是日期验证。 20161935(19 月 35 日)视为有效。我已经阅读了 some / other / posts 关于实现这一点的信息,我认为这与数字范围相匹配,但我无法理解格式。

如果有人对 ELI5 的工作原理足够友好,我会接受一个简单地解决日期问题的答案,但其他改进将是一个受欢迎的奖励。

编辑:为避免进一步混淆,我应该声明我知道 DateTime.TryParse 等。如前所述,我以此为契机学习正则表达式,并认为这是一个很好的起点。很抱歉浪费了我的时间,我应该在原帖中说明这一点。

【问题讨论】:

  • 你有一个好的开始。点需要转义:\.,否则它将匹配任何字符。我建议进行以下改进:(?i)T -> [Tt](它更短,我不确定(?i) 是否允许内联);始终使用[0-9]\d。我建议在正则表达式之外验证日期,因为闰年​​规则很复杂,你的正则表达式会变得混乱。
  • 为什么不使用 DateTime.Tryparse 并在日期有效时让它处理。更简单,并且如果您的格式/要求更改更容易调整!另外,您将如何处理正则表达式中的闰年?
  • @Heinzi,这有点矫枉过正,你只需要DateTime.TryParseExact,在捕获的子字符串上使用yyyyMMdd 格式

标签: c# regex validation


【解决方案1】:

你可以做的事情是:

  • 避免使用与所有 unicode 数字匹配的 \d 字符类(因为您只需要 ascii 数字)
  • 你可以写[01]而不是[0-1]
  • 转义点以计算文字点(而不是任何字符)
  • 如果T 是唯一的字符,则无需将其放入字符类中
  • 最终您可以删除内联修饰符并使用[Tt] 代替T


^(?i)T20[0-9]{2}[01][0-9][0-3][0-9]\.[0-9]{4}$

^[Tt]20[0-9]{2}[01][0-9][0-3][0-9]\.[0-9]{4}$

其他事情:您是否真的需要为日期添加额外的检查,因为您无法真正测试日期是否格式正确? (想想闰年)为什么不呢:

^(?i)T(20[0-9]{6})\.[0-9]{4}$

如果您想知道日期是否真的存在,请捕获它并使用DateTime.TryParse 方法对其进行测试。

【讨论】:

【解决方案2】:

为什么还要使用正则表达式,只需使用DateTime.TryParseExact 方法。我会像这样通过对其他字符的额外检查来实现它:

bool IsCorrectFormat(string input)
{
    //14 is a magic number for the length of the expected format
    if (input.Length == 14 && input.StartsWith("T", StringComparison.OrdinalIgnoreCase))
    {
        DateTime dt;
        if (DateTime.TryParseExact(input.Substring(1), "yyyyMMdd.ffff", CultureInfo.InvariantCulture, DateTimeStyles.None, out dt))
        {
            return true;
        }
    }

    return false;
}

我不知道格式是否正确,但您始终可以从 1 到 6 进行子串化以获得 yyyyMMdd,然后检查最后 5 个字符的小数点和数字。

编辑:如果必须使用正则表达式来完成

我过去使用过这个正则表达式。请注意,它不检查闰年

@"^(((0[1-9]{1}|[1-2][0-9]{1}|3[01]{1})(0[13578]{1}|1[12]{1}))" //For a 31 day month
+ @"|"
+ @"((0[1-9]{1}|[1-2][0-9]{1}|30)(0[469]{1}|11))" //For a 30 day month
+ @"|"
+ @"((0[1-9]{1}|1[0-9]{1}|2[0-8]{1})(02)))" //For a 28 day month(feb)
+ @"([0-9]{4})$"; //For the year

【讨论】:

  • 答案都很精彩,但我觉得这个最适合我的要求。谢谢大家。
【解决方案3】:

如前所述,我将此作为学习 Regex 的机会,并认为这是一个很好的起点。

使用正则表达式验证日期当然不是一件容易的事,尤其是考虑到闰年涉及的复杂规则。但这是可能的。

如果以 YYYYMMdd 格式输入有效日期,则以下表达式将匹配:

(?=\p{IsBasicLatin}{8}) # ensures \d matches only 0-9
(?!0000)\d{4} # year any 4-digit year, except 00
(?:0[1-9]\d|1[012]) # month 01-12
(?: 
   # day logic for leap years
   (?:
      (!00)[012]\d # Days 01-29 (we exclude 2/29 later)
      | (?<!02)30  # Day 30 valid for all months except Feb
      | (?<=0[13578]|1[02])31 # Day 31 valid for some months
   )
   # Non-Leap-year logic.  Do not allow 2/29 if the provided year
   # is not a leap year.
   (?<!
     (?:
        [13579] # years ending with odd number are not leap years
        | [02468][26]|[13579][048] # years not divisible by 4
                                     # are not leap years (02, 06, 10, ...)
        | (?:[02468][\d-[048]]|[13579][\d-[26]])00 # years divisible by
                                                 # 100 are not leap years,
                                                 # unless divisible by 400

     )0229)
)

使用RegexOptions.IgnorePatternWhitespace 编译。您可以使用^T~\.\d{4}$ 匹配示例中的完整字符串,将~ 替换为上述表达式。

【讨论】:

    猜你喜欢
    • 2010-09-28
    • 2010-09-29
    • 1970-01-01
    • 1970-01-01
    • 2012-08-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多