yyyy/MM/dd HH:mm:ssな文字列をISO8601の形式にして格納したい
概要
日付、Datetimeを示すフォーマットは多々ありますが、yyyy-MM-ddが標準なのか、yyyy/MM/ddが標準なのか、どっちだったっけ?ということはよくありますね。
今回は、Elasticseardchのフィールドの定義でdate型のフィールドなのだけども、格納時にはISO8601の形式にしておきたいが、Inputする文字列はそうでない(yyyy/MM/dd HH:mm:ssなときは)どうしようか、という話です。
確認環境
- Elasticsearch 7.5.0
Input対象の文字列をそのまま入れるなら
2019/12/12 09:00:00 といった文字列を想定します。
これをdate型のフィールドに入れたければ、mappingの定義においてformatを指定しておくと良いです。
{ "forum1212" : { "mappings" : { "properties" : { "testdate" : { "type" : "date", "format" : "yyyy/MM/dd HH:mm:ss" } } } }
こうすることによって、文字列 -> 日付型 で格納がされますね。
ISO8601形式に変換する方法1
上のようなフォーマットを指定したものではなく、普通のdate(つまりISO8601形式)のものが良い場合は、どうしたらよいでしょうか。
フォーマットとしては、下のhogeフィールドのような定義のところに 2019/12/12 00:00:00といった文字列から生成されるdateを入れたいです。
{ "forum1212" : { "mappings" : { "properties" : { "testdate" : { "type" : "date", "format" : "yyyy/MM/dd HH:mm:ss" }, "hoge": { "type": "date" } } } }
Ingest Pipeline
もっとも簡単な方法はIngest Pipelineでdate processorを使うことでしょう。
Date Processor | Elasticsearch Reference [master] | Elastic
dateプロセッサのformatsのところに文字列にあうフォーマット形式を書けば良いです。
PUT _ingest/pipeline/test { "description": "test", "processors": [ { "date" : { "field" : "testdate", "target_field" : "hoge", "formats" : ["yyyy/MM/dd HH:mm:ss"], "timezone" : "UTC" } } ] }
Simulate
どのようになるかは、simulateをしてみましょう。
POST _ingest/pipeline/_simulate { "pipeline": { "processors": [ { "date" : { "field" : "testdate", "target_field" : "hoge", "formats" : ["yyyy/MM/dd HH:mm:ss"], "timezone" : "UTC" } } ] }, "docs": [ { "_index": "aaa", "_id": "id1", "_source": { "testdate": "2019/12/12 00:00:00" } } ] }
そうしますと、結果はこのようになります。
{ "docs" : [ { "doc" : { "_index" : "aaa", "_type" : "_doc", "_id" : "id1", "_source" : { "testdate" : "2019/12/12 00:00:00", "hoge" : "2019-12-12T00:00:00.000Z" }, "_ingest" : { "timestamp" : "2019-12-12T13:13:11.994933Z" } } } ] }
hogeフィールドが、ISO8601形式になっていることが確認できましたね。
ISO8601形式に変換する方法2
dateプロセッサのアプローチが簡単かと思いますが、日付の加減算があるとか何らかの理由でスクリプトでアプローチする場合もあろうかと思います。
そんなときは、こうすれば良いでしょう。
PUT _ingest/pipeline/test { "description": "test", "processors": [ { "script": { "lang": "painless", "source": """ String testdate = ctx['testdate']; DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss"); LocalDateTime date = LocalDateTime.parse(testdate, dtf); ctx.hoge = date.format(DateTimeFormatter.ISO_DATE_TIME); """ } } ] }
ここでのポイントは2つあります。
- DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss") で文字列をパースするフォーマッターを作るところ
- LocalDateTimeを使うこと
ZonedDateTimeを使うとトラップにはまる
今回の文字列は yyyy/MM/dd HH:mm:ssという形式なので、タイムゾーンを表すものがありません。
そのため、ZonedDateTimeをうっかりつかってしまうとフォーマットの形式はあっているように見えても、ずっとparseでエラーになります。
タイムゾーンがないので、LocalDateTimeを使ってください。ここがポイントです。
"caused_by" : { "type" : "date_time_parse_exception", "reason" : "Text '2019/12/12 09:00:00' could not be parsed: Unable to obtain ZonedDateTime from TemporalAccessor: {},ISO resolved to 2019-12-12T09:00 of type java.time.format.Parsed", "caused_by" : { "type" : "date_time_exception", "reason" : "Unable to obtain ZonedDateTime from TemporalAccessor: {},ISO resolved to 2019-12-12T09:00 of type java.time.format.Parsed", "caused_by" : { "type" : "date_time_exception", "reason" : "Unable to obtain ZoneId from TemporalAccessor: {},ISO resolved to 2019-12-12T09:00 of type java.time.format.Parsed" } } }
Simulate
LocalDateTimeを指定していることを再度確認して、Simulateを実行してみましょう。
POST _ingest/pipeline/_simulate { "pipeline": { "processors": [ { "script": { "source": """ String testdate = ctx['testdate']; DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss"); LocalDateTime date = LocalDateTime.parse(testdate, dtf); ctx.hoge = date.format(DateTimeFormatter.ISO_DATE_TIME); """ } } ] }, "docs": [ { "_index": "aaa", "_id": "id1", "_source": { "testdate": "2019/12/12 00:00:00" } } ] }
そうしますと、結果はこのように確認できます。
{ "docs" : [ { "doc" : { "_index" : "aaa", "_type" : "_doc", "_id" : "id1", "_source" : { "testdate" : "2019/12/12 00:00:00", "hoge" : "2019-12-12T00:00:00" }, "_ingest" : { "timestamp" : "2019-12-12T13:26:01.755883Z" } } } ] }
良い感じにyyyy-MM-ddの形式に変わりましたね。 もともとタイムゾーン指定がないので、UTCの日付である、という前提ですが必要があればscriptの中で9時間引くとか、足すとかあっても良いかと思います。
まとめ
dateプロセッサを使うのが楽、scriptを使うアプローチはLocalDateTimeなのか、ZonedDateTimeなのか、自分がどっちを使う(使える)のかをよく考えるのが大事。
では、Advent Calendar2019 n日目の記事をこれで終わります。(嘘)