関数型でデータサイエンス番外編：様々な日時文字列を扱えるようにする

（この記事は、[「fukuoka.ex(その2) Elixir Advent Calendar 2017」](https://adventar.org/calendars/2871)の22日目、
　　および[「ディープラーニングのエンジニアリング Advent Calendar 2017」](https://qiita.com/advent-calendar/2017/deep-learning-engineering)の4日目です）

昨日は、@takasehideki さんの[「Erlang/OTPのソースビルドでHiPEが入らなかった時の対処法」](https://qiita.com/takasehideki/items/4a619d869643f8d0e272)でした

----
[fukuoka.ex](https://fukuokaex.fun/)代表の[piacere](https://twitter.com/piacere_ex)です
ご覧いただいて、ありがとうございます:bow:

今回は、次回予定の「関数型でデータサイエンス#3：インプットしたデータを集約する（前処理②）」の前段となる、「様々な日時文字列を扱えるようにする」という小ネタを番外編としてお送りします
<br>
:shamrock::shamrock::shamrock::shamrock:<b>　お礼：各種ランキングに83回のランクインを達成しました　:shamrock::shamrock::shamrock::shamrock:

4/27から、44日間に渡り、毎日お届けしている[「季節外れのfukuoka.ex Elixir Advent Calendar」](https://qiita.com/advent-calendar/2017/elixir-or-phoenix)と[「季節外れのfukuoka.ex(その2) Elixir Advent Calender」](https://adventar.org/calendars/2871)ですが、[Qiitaトップページトレンドランキング](https://qiita.com/trend)に13回入賞、[Elixirウィークリーランキング](https://qiita.com/tags/elixir)では7週連続で１／２／３フィニッシュを飾り、各種ランキング通算で、<font color="red"><b>トータル87回ものランクイン</b></font>を達成しています

みなさまの暖かい応援に励まされ、<font color="red"><b>合計616件ものQiita「いいね」</b></font>もいただき、fukuoka.exアドバイザーズとfukuoka.exキャストの一同、ますます季節外れのfukuoka.ex Advent Calendar、頑張っていきます:rocket:

[![image.png](https://qiita-image-store.s3.amazonaws.com/0/155423/bbe9d349-c978-e9e8-ba7d-f8feda74be91.png)](https://adventar.org/calendars/2871)

# 日時文字列をElixirで扱える日時型に変換

日時文字列を、Elixir内の日時型（NaiveDateTime）として扱うためには、@kobatako さんの[「Slack botで通知したい投稿日のものを通知する with Qiita API」](https://qiita.com/kobatako/items/f4c3cb1b21c499f7614e)でも出てきた[「Timex」](https://github.com/bitwalker/timex)を使うと簡単です

```elixir
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の各種関数](https://hexdocs.pm/elixir/NaiveDateTime.html)が利用可能です

```elixir
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で渡されてきます

たとえば、以下のような日時文字列群です

```elixir
"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"
```

これらのバリエーション違いを一通り収容するには、以下のような、各日時形式に合わせた変換をかける必要があります

```elixir
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で扱える日時型に変換されています

```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」](https://hex.pm/packages/smallex)のDtモジュールにある、to_datetime()という関数にて既に搭載していますので、次回以降のコラムにて実際に利用したいと思います

次回は、<b>[「インプットしたデータの集約」](https://qiita.com/piacere_ex/items/ef434011d154dc73f0cd)</b>について解説します

明日は @tuchiro さんの[「ElixirでSI開発入門 #9 Railsからのモデルの移行2(DDLをパースする)」](https://qiita.com/tuchiro/items/07bc2ddb58812c62da15)です

:stars::stars::stars::stars::stars:<b>　お知らせ：Elixir MeetUpを6月末に開催します　</b>:stars::stars::stars::stars::stars:

[<b>「fukuoka.ex#11：DB／データサイエンスにコネクトするElixir」</b>](https://fukuokaex.connpass.com/event/87241)を6/22（金）19時に開催します

fukuoka.exの発足から、ちょうど1周年となる、記念的なイベントでもあるため、このコラムを気に入っていただいた方と、ぜひ一緒に盛り上がりたいです

福岡近辺にお住まいの方であれば、遊びに来てください

<font color="red"><b>大人気により、一度は満席となりましたが、増枠しましたので、下記URLよりご参加ください</b></font>
https://fukuokaex.connpass.com/event/87241

特別ゲストは、Erlang／Elixirの両面で世界的に有名な「力武 健次さん」と、北九州の飯塚市で「e-ZUKA Tech Night」を6年間主催し続けるハウインターナショナルの「谷口 耕平さん」のお二人と、実に豪華なイベントです

私は、今回のシリーズを踏まえた、「1家に1台、パーソナルなデータ分析AIを全員が持つ2020年を作る」というタイトルで、Elixirによる、ブラウザUI上からサクっとデータ分析プラットフォームを披露するLTをお届けします

[![image.png](https://connpass-tokyo.s3.amazonaws.com/thumbs/cb/13/cb130a03343bc310b07244c308b940db.png)](https://fukuokaex.connpass.com/event/87241)

# p.s.「いいね」よろしくお願いします

よろしければ、ページ左上の ![image.png](https://qiita-image-store.s3.amazonaws.com/0/155423/4d515047-cc48-382e-c2b1-3ad0cc50dbbf.png) や ![image.png](https://qiita-image-store.s3.amazonaws.com/0/155423/a4e3da58-70a3-4197-95a2-6a6906650d01.png) のクリックをお願いしますー:bow: