(この記事は、「fukuoka.ex(その2) Elixir Advent Calendar 2017」の22日目、
および「ディープラーニングのエンジニアリング Advent Calendar 2017」の4日目です)
昨日は、@takasehideki さんの「Erlang/OTPのソースビルドでHiPEが入らなかった時の対処法」でした
fukuoka.ex代表のpiacereです
ご覧いただいて、ありがとうございます
今回は、次回予定の「関数型でデータサイエンス#3:インプットしたデータを集約する(前処理②)」の前段となる、「様々な日時文字列を扱えるようにする」という小ネタを番外編としてお送りします
お礼:各種ランキングに83回のランクインを達成しました
4/27から、44日間に渡り、毎日お届けしている「季節外れのfukuoka.ex Elixir Advent Calendar」と「季節外れのfukuoka.ex(その2) Elixir Advent Calender」ですが、Qiitaトップページトレンドランキングに13回入賞、Elixirウィークリーランキングでは7週連続で1/2/3フィニッシュを飾り、各種ランキング通算で、トータル87回ものランクインを達成しています
みなさまの暖かい応援に励まされ、合計616件ものQiita「いいね」もいただき、fukuoka.exアドバイザーズとfukuoka.exキャストの一同、ますます季節外れのfukuoka.ex Advent Calendar、頑張っていきます
日時文字列をElixirで扱える日時型に変換
日時文字列を、Elixir内の日時型(NaiveDateTime)として扱うためには、@kobatako さんの「Slack botで通知したい投稿日のものを通知する with Qiita API」でも出てきた「Timex」を使うと簡単です
iex> { :ok, dt } = "2018/6/11 1:35:4" |> Timex.parse( "%Y/%_m/%_d %_H:%_M:%_S", :strftime )
{:ok, ~N[2018-06-11 01:35:04]}
iex> dt
~N[2018-06-11 01:35:04]
なお、NaiveDateTimeは、キーがアトムのマップ(正しくは構造体)なので、ドット「.」で年月日や時分秒にアクセスできますし、NaiveDateTimeの各種関数が利用可能です
iex> is_map( dt )
true
iex> dt.year
2018
iex> "#{ dt.month }/#{ dt.day }"
"6/11"
iex> dt |> NaiveDateTime.to_date
~D[2018-06-11]
iex> dt |> NaiveDateTime.to_time
~T[01:35:04]
dt |> NaiveDateTime.to_erl
{{2018, 6, 11}, {1, 35, 4}}
様々なバリエーションの日時文字列を一括で変換するには?
インプットデータには、様々な形式の日付文字列が、CSVやAPIで渡されてきます
たとえば、以下のような日時文字列群です
"2018/1"
"2018/ 1"
"2018/01"
"2018/1/2"
"2018/1/2 3:4"
"2018/1/2 3:4:5"
"2018/1/2 03:04"
"2018/1/2 03:04:05"
"2018/ 1/ 2"
"2018/1/ 2"
"2018/01/ 2"
"2018/01/02"
"2018/01/02 23:44"
"2018/01/02 23:44:09"
"2018/01/02 23:44:09.005"
"2018-01-02"
"2018-01-02 23:44"
"2018-01-02 23:44:09"
"2018-01-02 23:44:09.005"
"Jan-02-2018"
"Jan-02-2018 23:44"
"Jan-02-2018 23:44:09"
"Jan-02-2018 23:44:09.005"
"Jan-02-18"
"Jan-02-18 23:44"
"Jan-02-18 23:44:09"
"Jan-02-18 23:44:09.005"
"January-02-2018"
"January-02-2018 23:44"
"January-02-2018 23:44:09"
"January-02-2018 23:44:09.005"
"2018-01-02 23:44:09Z"
"2018-01-02T23:44:09Z"
これらのバリエーション違いを一通り収容するには、以下のような、各日時形式に合わせた変換をかける必要があります
defmodule Dt do
def to_datetime( str ) when is_binary( str ) do
[
"%Y/%_m",
"%Y/%_m/%_d",
"%Y/%_m/%_d %_H:%_M",
"%Y/%_m/%_d %_H:%_M:%_S",
"%Y/%_m/%_d %_H:%_M:%_S.%_L",
"%Y-%_m-%_d",
"%Y-%_m-%_d %_H:%_M",
"%Y-%_m-%_d %_H:%_M:%_S",
"%Y-%_m-%_d %_H:%_M:%_S.%_L",
"%b-%_d-%Y",
"%b-%_d-%Y %_H:%_M",
"%b-%_d-%Y %_H:%_M:%_S",
"%b-%_d-%Y %_H:%_M:%_S.%_L",
"%b-%_d-%y",
"%b-%_d-%y %_H:%_M",
"%b-%_d-%y %_H:%_M:%_S",
"%b-%_d-%y %_H:%_M:%_S.%_L",
"%B-%_d-%Y",
"%B-%_d-%Y %_H:%_M",
"%B-%_d-%Y %_H:%_M:%_S",
"%B-%_d-%Y %_H:%_M:%_S.%_L",
"%Y-%_m-%_d %_H:%_M:%_SZ",
"%Y-%_m-%_d %_H:%_M:%_S.%_LZ",
"%Y-%_m-%_dT%_H:%_M:%_SZ",
"%Y-%_m-%_dT%_H:%_M:%_S.%_LZ",
]
|> Enum.map( &( str |> Timex.parse( &1, :strftime ) ) )
|> Enum.filter( &elem( &1, 0 ) == :ok )
|> List.first
|> Tpl.ok
end
end
上記関数を使い、日時文字列群を各自変換した結果が以下です
いずれも、Elixirで扱える日時型に変換されています
iex> Dt.to_datetime( "2018/1" )
~N[2018-01-01 00:00:00]
iex> Dt.to_datetime( "2018/ 1" )
~N[2018-01-01 00:00:00]
iex> Dt.to_datetime( "2018/01" )
~N[2018-01-01 00:00:00]
iex> Dt.to_datetime( "2018/1/2" )
~N[2018-01-02 00:00:00]
iex> Dt.to_datetime( "2018/1/2 3:4" )
~N[2018-01-02 03:04:00]
iex> Dt.to_datetime( "2018/1/2 3:4:5" )
~N[2018-01-02 03:04:05]
iex> Dt.to_datetime( "2018/1/2 03:04" )
~N[2018-01-02 03:04:00]
iex> Dt.to_datetime( "2018/1/2 03:04:05" )
~N[2018-01-02 03:04:05]
iex> Dt.to_datetime( "2018/ 1/ 2" )
~N[2018-01-02 00:00:00]
iex> Dt.to_datetime( "2018/1/ 2" )
~N[2018-01-02 00:00:00]
iex> Dt.to_datetime( "2018/01/ 2" )
~N[2018-01-02 00:00:00]
iex> Dt.to_datetime( "2018/01/02" )
~N[2018-01-02 00:00:00]
iex> Dt.to_datetime( "2018/01/02 23:44" )
~N[2018-01-02 23:44:00]
iex> Dt.to_datetime( "2018/01/02 23:44:09" )
~N[2018-01-02 23:44:09]
iex> Dt.to_datetime( "2018/01/02 23:44:09.005" )
~N[2018-01-02 23:44:09.005]
iex> Dt.to_datetime( "2018-01-02" )
~N[2018-01-02 00:00:00]
iex> Dt.to_datetime( "2018-01-02 23:44" )
~N[2018-01-02 23:44:00]
iex> Dt.to_datetime( "2018-01-02 23:44:09" )
~N[2018-01-02 23:44:09]
iex> Dt.to_datetime( "2018-01-02 23:44:09.005" )
~N[2018-01-02 23:44:09.005]
iex> Dt.to_datetime( "Jan-02-2018" )
~N[2018-01-02 00:00:00]
iex> Dt.to_datetime( "Jan-02-2018 23:44" )
~N[2018-01-02 23:44:00]
iex> Dt.to_datetime( "Jan-02-2018 23:44:09" )
~N[2018-01-02 23:44:09]
iex> Dt.to_datetime( "Jan-02-2018 23:44:09.005" )
~N[2018-01-02 23:44:09.005]
iex> Dt.to_datetime( "Jan-02-18" )
~N[2018-01-02 00:00:00]
iex> Dt.to_datetime( "Jan-02-18 23:44" )
~N[2018-01-02 23:44:00]
iex> Dt.to_datetime( "Jan-02-18 23:44:09" )
~N[2018-01-02 23:44:09]
iex> Dt.to_datetime( "Jan-02-18 23:44:09.005" )
~N[2018-01-02 23:44:09.005]
iex> Dt.to_datetime( "January-02-2018" )
~N[2018-01-02 00:00:00]
iex> Dt.to_datetime( "January-02-2018 23:44" )
~N[2018-01-02 23:44:00]
iex> Dt.to_datetime( "January-02-2018 23:44:09" )
~N[2018-01-02 23:44:09]
iex> Dt.to_datetime( "January-02-2018 23:44:09.005" )
~N[2018-01-02 23:44:09.005]
iex> Dt.to_datetime( "2018-01-02 23:44:09Z" )
~N[2018-01-02 23:44:09]
iex> Dt.to_datetime( "2018-01-02T23:44:09Z" )
~N[2018-01-02 23:44:09]
終わり
今回は、「データ変換」で使える、様々なバリエーションの日時文字列を扱うテクニックをお伝えしました
なお今回の実装内容は、小粒でピリリなユーティリティライブラリ「smallex」のDtモジュールにある、to_datetime()という関数にて既に搭載していますので、次回以降のコラムにて実際に利用したいと思います
次回は、「インプットしたデータの集約」について解説します
明日は @tuchiro さんの「ElixirでSI開発入門 #9 Railsからのモデルの移行2(DDLをパースする)」です
お知らせ:Elixir MeetUpを6月末に開催します
「fukuoka.ex#11:DB/データサイエンスにコネクトするElixir」を6/22(金)19時に開催します
fukuoka.exの発足から、ちょうど1周年となる、記念的なイベントでもあるため、このコラムを気に入っていただいた方と、ぜひ一緒に盛り上がりたいです
福岡近辺にお住まいの方であれば、遊びに来てください
大人気により、一度は満席となりましたが、増枠しましたので、下記URLよりご参加ください
https://fukuokaex.connpass.com/event/87241
特別ゲストは、Erlang/Elixirの両面で世界的に有名な「力武 健次さん」と、北九州の飯塚市で「e-ZUKA Tech Night」を6年間主催し続けるハウインターナショナルの「谷口 耕平さん」のお二人と、実に豪華なイベントです
私は、今回のシリーズを踏まえた、「1家に1台、パーソナルなデータ分析AIを全員が持つ2020年を作る」というタイトルで、Elixirによる、ブラウザUI上からサクっとデータ分析プラットフォームを披露するLTをお届けします