Elixirový apríl
Chcete někomu připravit pěkné aprílové překvapení v kódu? Jeden den testy fungují a další ráno najednou ne? Zkuste tohle :-)
defmodule April do
def check(date) do
day_before = Date.add(date, -1)
if day_before < date do
"#{date} - The World is OK today."
else
"#{date} - April Fools' Day!"
end
end
end
IO.puts(April.check(~D[2020-03-31]))
IO.puts(April.check(~D[2020-04-01]))
IO.puts(April.check(~D[2020-04-02]))
Kód si zkopírujte a uložte třeba do souboru april.exs
.
Pak ho spusťte:
$ elixir april.exs
2020-03-31 - The World is OK today.
2020-04-01 - April Fools' Day!
2020-04-02 - The World is OK today.
Porovnáváme day_before < date
. Jak je možné, že 1. dubna podmínka najednou neplatí?
Datum v našem kódu je struct.
Příkazem IO.inspect(date, structs: false)
zobrazíme nenaformátovaný tvar
a zjistíme, že v pozadí se skrývá mapa:
iex(1)> IO.inspect(~D[2020-03-31], structs: false)
%{__struct__: Date, calendar: Calendar.ISO, day: 31, month: 3, year: 2020}
~D[2020-03-31]
iex(2)> IO.inspect(~D[2020-04-01], structs: false)
%{__struct__: Date, calendar: Calendar.ISO, day: 1, month: 4, year: 2020}
~D[2020-04-01]
V kódu tedy porovnáváme dvě mapy,
které se v klíčích day
a month
liší. Vše zdánlivě funguje, ale na přelomu měsíce máme problém.
Hodnota v klíči day
(který má v tomto případě díky pravidlům pro porovnávání map přednost
před klíčem month
) kód rozbije: 31 > 1
.
Datumy je nutné vždy porovnávat pomocí funkce
Date.compare/2.
V skriptu tedy day_before < date
nahradíme Date.compare(day_before, date) == :lt
a porovnání bude fungovat bez aprílových překvápek.