在简单的查询语言中,我想识别日期和时间文字,最好不使用分隔符。例如,
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"))
satisfy
对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")
答案 0 :(得分:3)
是否有用于“后处理”解析器结果的组合器
这取决于你失败时想要做什么。您始终可以|>>
来DateTime
。失败也同样有趣,我认为你的例子可能是(给定一个解析器sp
得到正确的字符串,注意它的类型为Parser<string,'u>
)
sp >>= (fun s -> match DateTime.TryParse s with
| true,result -> preturn result
| false,_ -> fail)
这里我接收结果字符串并调用TryParse
方法,并返回preturn
或fail
,具体取决于它是否成功。我找不到任何完全相同的方法。
请注意,如果失败,>>=?
会导致回溯。
是否可以将谓词应用于字符串(对于char的满足)?
您必须为每个字符(2
,20
,201
)调用谓词,这通常不理想。如果你愿意,我很确定你可以掀起这样的事情,但我不认为这是理想的,更不用说处理部分匹配变得更难了。
是否有更好的方法来解析日期/时间?
最大的因素是“你对日期/时间了解多少?”如果您确切知道它的语法完全正确,那么您应该能够使用后期处理并且没问题(因为希望错误情况很少见)
如果您不确定,例如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))