メモ:lubridateでAM/PMを含む時刻をパースする時は%pじゃなくて%Opが正しそう

parse_date_time()exact=TRUEを指定したときとしないときの挙動で悩んだのでメモ。

追記(2020/11/3): macはまた違う挙動らしいです(参考

AM/PMの区別を表す時刻フォーマット文字列はp?parse_date_timeのヘルプにはこう書かれています。

p
AM/PM indicator in the locale. Normally used in conjunction with I and not with H. But the lubridate C parser accepts H format as long as hour is not greater than 12. C parser understands only English locale AM/PM indicator.

IじゃなくてHでもいける、という話はいったん置いといて、とりあえず標準的なIpでパースしてみましょう。うまくいきます。

x <- "2020-11-03 10:23:18 PM"
parse_date_time(x, "YmdIMSp")
#> [1] "2020-11-03 22:23:18 UTC"

一方、exact = TRUE をつけると速いという知見(参考: R で文字列を POSIX time に変換するには lubridate::parse_date_time2() がやっぱりちょっぱや - Qiita)があるわけですが、 これをつけるとなぜかうまくいきません...

parse_date_time(x, "%Y-%m-%d %I:%M:%S %p", exact = TRUE)
#> Warning: 1 failed to parse.
#> [1] NA

なんでやねん、と思って試しにbase Rでやってみると、これも失敗しました。ということはどうやらlubridateが悪いわけではなさそうです。

strptime(x, "%Y-%m-%d %I:%M:%S %p")
#> [1] NA

しばらく悩んだあと、同じフォーマットで時刻を文字列に変換してみるとどうなるかな?とやってみて謎が解けました。

strftime(as.POSIXct("2020-11-03 23:23:18"), "%Y-%m-%d %I:%M:%S %p")
#> [1] "2020-11-03 11:23:18 午後"

おわかりいただけただろうか。

午後...

なるほどなーー?、とおもってヘルプを見返すと、AM/PM indicatorについてはlubridate 独自のフォーマットがありました。

Op
Matches AM/PM English indicator.

これを使ってみるとうまくいきました。

parse_date_time(x, "%Y-%m-%d %I:%M:%S %Op", exact = TRUE)
#> [1] "2020-11-03 22:23:18 UTC"

ちなみに、ではPMじゃなくて午後になってる文字列を渡すと?と試してみると、これはpでうまくいきました。

x_ja <- "2020-11-03 10:23:18 午後"

parse_date_time(x_ja, "YmdIMSp")
#> [1] "2020-11-03 22:23:18 UTC"

parse_date_time(x_ja, "%Y-%m-%d %I:%M:%S %p", exact = TRUE)
#> [1] "2020-11-03 22:23:18 UTC"

よくわからないのはEnglish indicatorなはずの Op でもマッチする点ですが...。まあマッチされて困ることは少なそうなので決断的に見なかったことに。

parse_date_time(x_ja, "%Y-%m-%d %I:%M:%S %Op", exact = TRUE)
#> [1] "2020-11-03 10:23:18 UTC"

感想

exact = TRUEつけないときはどっちもいい感じにパースしてくれて偉いなと思いました(小並