使用FParsec解析日期和时间

时间:2013-05-13 20:21:35

标签: parsing datetime f# fparsec

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

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"))
  • 是否可以将谓词应用于字符串(satisfychar执行)?
  • 是否有更好的方法来解析日期/时间?

更新

使用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")

3 个答案:

答案 0 :(得分:3)

  

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

这取决于你失败时想要做什么。您始终可以|>>DateTime。失败也同样有趣,我认为你的例子可能是(给定一个解析器sp得到正确的字符串,注意它的类型为Parser<string,'u>

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

这里我接收结果字符串并调用TryParse方法,并返回preturnfail,具体取决于它是否成功。我找不到任何完全相同的方法。

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

  

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

您必须为每个字符(220201)调用谓词,这通常不理想。如果你愿意,我很确定你可以掀起这样的事情,但我不认为这是理想的,更不用说处理部分匹配变得更难了。

  

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

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

如果您不确定,例如PM是否可选,但是会明确详细,那么您可能希望分解定义并在最后将其合并。请注意,我在这里稍微放宽了角色数量,您可以添加一些opt以更轻松,或者将pint32替换为digit和手动转换。

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

写出所有这些我不确定你是好还是更糟......

答案 1 :(得分: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"

正如Guvante所提议的那样。

如果你想避免使用构造临时解析器(参见上面的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)

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

答案 2 :(得分: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))