Created
February 20, 2016 01:20
-
-
Save rcdilorenzo/84e47b451972ef4c6f46 to your computer and use it in GitHub Desktop.
A simple construction of a duration time (e.g. "01:30" or "01:30:23") as a custom ecto type
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
defmodule Duration do | |
@moduledoc """ | |
This duration module parses and formats strings | |
for a time duration in hours and minutes and | |
optionally seconds (e.g. 01:00 for an hour, | |
00:01:10 for one minute and ten seconds). | |
""" | |
@match ~r/^(?<hour>\d{1,2}):(?<min>\d{1,2}):?(?<sec>\d{0,2})$/ | |
def parse(string) do | |
case Regex.named_captures(@match, string) do | |
nil -> :error | |
%{"hour" => hour_str, "min" => min_str, "sec" => sec_str} -> | |
with {:ok, hours} <- parse_integer(hour_str), | |
{:ok, minutes} <- parse_integer(min_str), | |
{:ok, seconds} <- parse_integer(sec_str), | |
do: validate(hours, minutes, seconds) | |
end | |
end | |
def format(integer) when integer >= 0 and integer < 86_400 do | |
hours = div(integer, 3600) | |
minutes = div(integer - (hours * 3600), 60) | |
seconds = integer - (hours * 3600) - (minutes * 60) | |
case seconds do | |
0 -> {:ok, "#{pad(hours)}:#{pad(minutes)}"} | |
_ -> {:ok, "#{pad(hours)}:#{pad(minutes)}:#{pad(seconds)}"} | |
end | |
end | |
def format(_), do: :error | |
defp pad(integer) do | |
to_string(integer) |> String.rjust(2, ?0) | |
end | |
defp parse_integer(""), do: {:ok, 0} | |
defp parse_integer(string) when is_binary(string) do | |
case Integer.parse(string) do | |
{value, _} -> {:ok, value} | |
:error -> :error | |
end | |
end | |
defp validate(hours, minutes, seconds) do | |
cond do | |
hours < 24 and minutes < 60 and seconds < 60 -> | |
{:ok, (hours * 3600) + (minutes * 60) + seconds} | |
true -> :error | |
end | |
end | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
defmodule DurationTest do | |
use ExUnit.Case | |
test "validating hours" do | |
assert Duration.parse("24:00") == :error | |
assert Duration.parse("-4:00") == :error | |
assert Duration.parse("100:00") == :error | |
assert Duration.parse(":") == :error | |
refute Duration.parse("23:00") == :error | |
refute Duration.parse("0:0") == :error | |
end | |
test "validating minutes" do | |
assert Duration.parse("00:60") == :error | |
assert Duration.parse("00:-6") == :error | |
assert Duration.parse("00:-1:00") == :error | |
assert Duration.parse("00:60:00") == :error | |
refute Duration.parse("00:59") == :error | |
refute Duration.parse("00:59:00") == :error | |
refute Duration.parse("00:00:00") == :error | |
end | |
test "validating seconds" do | |
assert Duration.parse("00:00:60") == :error | |
assert Duration.parse("00:00:-9") == :error | |
refute Duration.parse("00:00:34") == :error | |
end | |
test "parsing hours and minutes" do | |
assert Duration.parse("01:59") == {:ok, 7140} | |
assert Duration.parse("20:00") == {:ok, 72_000} | |
assert Duration.parse("00:10") == {:ok, 600} | |
end | |
test "parsing hours and minutes and seconds" do | |
assert Duration.parse("00:00:01") == {:ok, 1} | |
assert Duration.parse("01:10:05") == {:ok, 4205} | |
end | |
test "validate format" do | |
assert Duration.format(86_400) == :error | |
assert Duration.format(-1) == :error | |
refute Duration.format(1_000) == :error | |
end | |
test "formatting" do | |
assert Duration.format(7140) == {:ok, "01:59"} | |
assert Duration.format(7150) == {:ok, "01:59:10"} | |
assert Duration.format(45_296) == {:ok, "12:34:56"} | |
end | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
defmodule MyApp.Type.Duration do | |
@behaviour Ecto.Type | |
def type, do: :integer | |
def cast(string) when is_binary(string) do | |
Duration.parse(string) | |
end | |
def cast(integer) when integer >= 0 and integer < 86_400 do | |
{:ok, integer} | |
end | |
def cast(_), do: :error | |
def load(integer) when is_integer(integer) do | |
Duration.format(integer) | |
end | |
def dump(integer) when integer >= 0 and integer < 86_400 do | |
{:ok, integer} | |
end | |
def dump(_), do: :error | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment