5

在简单的查询语言中,我想识别日期和时间文字,最好不使用分隔符。例如,

CreationDate = 2013-05-13 5:30 PM

我可以使用组合器来检测基本语法(例如,yyyy-MM-dd hh:mm tt),但随后需要将其传递给以DateTime.TryParse进行完全验证。

几个问题:

  • 是否有用于“后处理”解析器结果的组合器,例如,pstring "1/2/2000" |> (fun s -> try OK(DateTime.Parse s) with _ -> Fail("not a date"))
  • 是否可以将谓词应用于字符串(就像satisfyto一样char)?
  • 有没有更好的方法来解析日期/时间?

更新

使用 Guvante 和 Stephan 的例子,我想出了这个:

let dateTimeLiteral =
  let date sep = pipe5 pint32 sep pint32 sep pint32 (fun a _ b _ c -> a, b, c)
  let time = 
    (pint32 .>>. (skipChar ':' >>. pint32)) .>>. 
      (opt (stringCIReturn " am" false <|> stringCIReturn " pm" true))
  (date (pstring "/") <|> date (pstring "-")) .>>. 
    (opt (skipChar ' ' >>. time)) .>> ws
    >>=? (fun ((a, b, c), tt) ->
      let y, m, d = if a > 12 then a, b, c else c, a, b
      let h, n =
        match tt with
        | Some((h, n), tt) ->
          match tt with
          | Some true -> (match h with 12 -> h | _ -> h + 12), n
          | Some false -> (match h with 12 -> h - 12 | _ -> h), n
          | None -> h, n
        | None -> 0, 0
      try preturn (System.DateTime(y, m, d, h, n, 0)) |>> DateTime 
      with _ -> fail "Invalid date/time format")
4

3 回答 3

3

是否有用于“后处理”解析器结果的组合器

这取决于如果你失败了你想做什么。你总是可以做得到|>>DateTime的。失败同样有趣,我认为你的例子可能是(给定一个sp获取正确字符串的解析器,注意它是 type Parser<string,'u>

sp >>= (fun s -> match DateTime.TryParse s with
                 | true,result -> preturn result
                 | false,_ -> fail)

在这里,我接收结果字符串并调用该TryParse方法,并根据它是否成功返回 apreturn或 a 。fail我找不到任何与此完全相同的方法。

请注意,>>=?如果失败,将导致回溯。

是否可以将谓词应用于字符串(如满足字符)?

您必须为每个字符 ( 2, 20, 201) 调用谓词,这通常并不理想。我很确定如果你愿意,你可以做出这样的事情,但我认为这不是理想的原因,更不用说处理部分匹配变得更加困难。

有没有更好的方法来解析日期/时间?

最大的因素是“您对日期/时间了解多少?” 如果您确切知道它在该语法中,那么您应该能够使用后期处理并且没问题(因为希望错误情况很少见)

如果您不确定,例如 ifPM是可选的,但会非常详细,那么您可能希望分解定义并在最后合并它。请注意,这里我稍微放宽了字符数,您可以添加一些opt以获得更轻松,或者替换pint32withdigit和手动转换。

let pipe6 = //Implementation left as an exercise
let dash = skipChar '-'
let space = skipChar ' '
let colon = skipChar ':'
pipe6 (pint32 .>> dash) //Year
      (pint32 .>> dash) //Month
      (pint32 .>> space) //Day
      (pint32 .>> colon) //Hour
      (pint32 .>> space) //Minute
      (anyString) //AM/PM
      (fun year month day hour minute amPm ->
          DateTime(year, month, day,
                   hour + (if amPm.Equals("PM", StringComparison.InvariantCultureIgnoreCase)
                          then 12 else 0),
                   minute, 0)) //No seconds

把这些都写出来我不确定你的情况是好是坏……

于 2013-05-13T21:23:48.147 回答
3

您可以轻松构建自定义组合器或解析器来验证解析的输入。

如果您只想使用组合器(“Haskell-style”),您可以使用

let pDateString = pstring "1/2/2000"

let pDate1 = 
    pDateString 
    >>= fun str ->            
           try preturn (System.DateTime.Parse(str))               
           with _ -> fail "Date format error"

正如古万特刚刚提议的那样。

如果你想避免构建临时解析器(见上文) preturn ...pfail ...你可以让函数接受第二个参数并直接返回Reply值:

let pDate2 = 
    pDateString 
    >>= fun str stream ->            
           try Reply(System.DateTime.Parse(str))               
           with _ -> Reply(Error, messageError "Date format error")

如果您希望错误位置位于格式错误的日期字符串的开头,则可以替换>>=>>=?. 请注意,这也会对错误恢复产生影响。

如果你想拥有完全的控制权,你可以只使用较低级别的 API 编写解析器,从一个基本版本开始,如下所示:

let pDate3 = 
    fun stream ->
        let reply = pDateString stream
        if reply.Status = Ok then        
            try Reply(System.DateTime.Parse(reply.Result))               
            with _ -> Reply(Error, messageError "Date format error")
        else
           Reply(reply.Status, reply.Error)

最后一个版本还允许您将 pDateString 解析器替换为直接访问 CharStream 接口的代码,这可以为您提供一些额外的灵活性或性能。

于 2013-05-13T21:42:47.877 回答
0

我使用下一个代码将给定的日期字符串解析为DataTime对象。

2000-01-01 12:34:56,789

 let pipe7 p1 p2 p3 p4 p5 p6 p7 f =
        p1 >>= fun x1 ->
         p2 >>= fun x2 ->
          p3 >>= fun x3 ->
           p4 >>= fun x4 ->
            p5 >>= fun x5 ->
             p6 >>= fun x6 ->
              p7 >>= fun x7 -> preturn (f x1 x2 x3 x4 x5 x6 x7)

 let int_ac = pint32 .>> anyChar

 let pDateStr : Parser<DateTime, unit> = pipe7 int_ac int_ac int_ac int_ac int_ac int_ac int_ac (fun y m d h mi s mil -> new DateTime(y,m,d,h,mi,s,mil))
于 2017-10-14T08:47:32.650 回答