このところ作業自動化のため「金曜日を見つける」「JSONデータを加工する」機能が必要なツールの作成をやってたんですが、それでふと思いつきました。
「13日の金曜日~ジェイソンは生きていた」
ということでElixirでこれから来る13日の金曜日を探してみようというプログラムを書いてみました。
つまり、すんません、完全にネタです。
(2017/7/1追記)
しばらく放置していたらTimexの仕様がかなり変わっていて動作しなくなっていたため
ソースを修正しました。
おさらい
Elixirで時間・時刻を扱うとなると最もよく使われるのはTimexでしょう。今回もこれを使います。
まずmix new
で作業ディレクトリを作成します。--sup
はいらなかったかも。
--module
はモジュール名にCamelCaseを使いたいので。指定しなくてもFridaythe13thというモジュール名になるだけなので特に問題はないです。
$ mix new fridaythe13th --sup --module FridayThe13th
* creating README.md
* creating .gitignore
* creating mix.exs
* creating config
* creating config/config.exs
* creating lib
* creating lib/fridaythe13th.ex
* creating test
* creating test/test_helper.exs
* creating test/fridaythe13th_test.exs
Your mix project was created successfully.
You can use mix to compile it, test it, and more:
cd fridaythe13th
mix test
Run `mix help` for more commands.
$ cd fridaythe13th
次にmix.exsにtimexを使う設定を追加します。
defmodule FridayThe13th.Mixfile do
use Mix.Project
def project do
[app: :fridaythe13th,
version: "0.1.0",
elixir: "~> 1.4",
build_embedded: Mix.env == :prod,
start_permanent: Mix.env == :prod,
deps: deps()]
end
# Configuration for the OTP application
#
# Type "mix help compile.app" for more information
def application do
# Specify extra applications you'll use from Erlang/Elixir
[extra_applications: [:logger, :timex],
mod: {FridayThe13th.Application, []}]
end
# Dependencies can be Hex packages:
#
# {:my_dep, "~> 0.3.0"}
#
# Or git/path repositories:
#
# {:my_dep, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"}
#
# Type "mix help deps" for more examples and options
defp deps do
[
{:certifi, "~> 1.0"},
{:httpoison, "~> 0.11.1"},
{:timex, "~> 3.0"}
]
end
end
あとは忘れずに依存関係ファイルを落としてきておいてください。
$ mix deps.get
プログラム
今回のは短いので全ソース貼り付けます。
defmodule FridayThe13th do
@moduledoc """
Documentation for FridayThe13th.
Search Fridays the 13th from today within the specified years.
"""
@doc """
Friday the 13th.
## Examples FridayThe13th.main
FridayThe13th.main # Without arguments, it returns the fridays the 13th within 3 yeas.
[ok: "2017/10/13", ok: "2018/04/13", ok: "2018/07/13", ok: "2019/09/13",
ok: "2019/12/13", ok: "2020/03/13"]
FridayThe13th.main 5 # Argument is the years for searching.
[ok: "2017/10/13", ok: "2018/04/13", ok: "2018/07/13", ok: "2019/09/13",
ok: "2019/12/13", ok: "2020/03/13", ok: "2020/11/13", ok: "2021/08/13",
ok: "2022/05/13"]
"""
def thirteens(start_date, years) do
# 指定日(当日)から初めて最初の13日の金曜日が見つかるまで繰り返す(再帰)
if Timex.weekday(start_date) != 5 || start_date.day != 13 do
thirteens(Timex.add(start_date, Timex.Duration.from_days(1)), years)
else
# 金曜日ならば以降は7日飛ばしで探していけばよい
0..years*52 |> Enum.map(fn x ->
Timex.add(start_date, Timex.Duration.from_days(7*x))
end) |> Enum.filter(fn x ->
x.day == 13 # 13日の金曜日だけフィルタする
end) |> Enum.map(fn x ->
# IO.inspect(x)
Timex.Format.DateTime.Formatters.Strftime.format(x, "%Y/%m/%d")
end)
end
end
def main(args) do # 引数がある場合は今日からその年数分先まで探す
thirteens(Timex.now, args)
end
def main do # 引数がない場合は今日から3年先まで探す
thirteens(Timex.now, 3)
end
end
Elixirらしいかな?というと13日の金曜日を探すところでパイプ演算子(>|)を使っているところぐらいでしょうか。
- とにかくなんでもリストにする
- mapやらfilterやらでリストに対して計算する
あたりは定番の関数型ぽいやりかたとも言えるかもしれません。
実行結果
実行するとこんな感じです。
$ iex -S mix
Erlang/OTP 18 [erts-7.0] [source] [64-bit] [async-threads:10] [hipe] [kernel-poll:false]
Interactive Elixir (1.0.5) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> FridayThe13th.main
["2015/11/13", "2016/05/13", "2017/01/13", "2017/10/13", "2018/04/13",
"2018/07/13"]
iex(2)> FridayThe13th.main 20
["2015/11/13", "2016/05/13", "2017/01/13", "2017/10/13", "2018/04/13",
"2018/07/13", "2019/09/13", "2019/12/13", "2020/03/13", "2020/11/13",
"2021/08/13", "2022/05/13", "2023/01/13", "2023/10/13", "2024/09/13",
"2024/12/13", "2025/06/13", "2026/02/13", "2026/03/13", "2026/11/13",
"2027/08/13", "2028/10/13", "2029/04/13", "2029/07/13", "2030/09/13",
"2030/12/13", "2031/06/13", "2032/02/13", "2032/08/13", "2033/05/13",
"2034/01/13", "2034/10/13", "2035/04/13", "2035/07/13"]
iex(3)>
13日の金曜日って思ってたよりも少ないんですね。だいたい2年で3回ちょっとぐらい。直近は2015年11月13日。
おまけ
Timexをいじっててちょっとおもしろいことに気づきました。
残念ながら以下はTimex 3.0では出し方がわからなくなりました。誰か知ってたら教えてください。
$ iex -S mix
Erlang/OTP 18 [erts-7.0] [source] [64-bit] [async-threads:10] [hipe] [kernel-poll:false]
Interactive Elixir (1.0.5) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> Timex.Date.now
%Timex.DateTime{calendar: :gregorian, day: 19, hour: 12, minute: 43, month: 9,
ms: 132, second: 24,
timezone: %Timex.TimezoneInfo{abbreviation: "UTC", from: :min,
full_name: "UTC", offset_std: 0, offset_utc: 0, until: :max}, year: 2015}
iex(2)> Timex.Date.now("Asia/Tokyo")
%Timex.DateTime{calendar: :gregorian, day: 19, hour: 12, minute: 44, month: 9,
ms: 387, second: 8,
timezone: %Timex.TimezoneInfo{abbreviation: "JST",
from: {:saturday, {{1951, 9, 8}, {1, 0, 0}}}, full_name: "Asia/Tokyo",
offset_std: 0, offset_utc: 540, until: :max}, year: 2015}
iex(3)>
Time Zoneを日本(Asia/Tokyo)に指定するとfrom:
に出てくる1951年9月8日(午前1時0分0秒)。
サンフランシスコ平和条約(Treaty of Peace with Japan)の締結日です。
更に調べると「第二次大戦終了後、日本をアメリカが統治していた間はサマータイム制があったがサンフランシスコ平和条約締結後廃止された」と。こういう歴史が今もTime Zoneの情報の中に残ってるんですねえ…何でも見せてくれるiexのおかげでちょっと賢くなりました(笑)。