Current section
Files
Jump to
Current section
Files
lib/stopwatch/watch.ex
defmodule Stopwatch.Watch do
@moduledoc """
Methods for creating, working and accessing a Watch struct
All methods that starts with a **get_*** reads the watch status, while the others manipulate it.
For every method that accepts a unit parameter refer to the [timex library docs](https://hexdocs.pm/timex/extra-working-with-time.html)
"""
use Timex
use Stopwatch
defstruct start_time: nil, laps: [], finish_time: nil
@doc """
create a new Watch
"""
@spec new(Time) :: Stopwatch.Watch
def new(start_time \\ Time.now) do
%Watch{start_time: start_time}
end
@doc """
create a lap by givint it a name and an optional end time
"""
@spec lap(Stopwatch.Watch, any, Time) :: Stopwatch.Watch
def lap(watch = %Watch{laps: laps}, name, at \\ Time.now) do
%{watch | laps: [{name, {last_lap_finish_time(watch), at}} | laps]}
end
@doc """
create the last lap by giving it a name, and then stop the watch
"""
@spec last_lap(Stopwatch.Watch, any, Time) :: Stopwatch.Watch
def last_lap(watch, name, at \\ Time.now)
def last_lap(watch, name, at) do
watch
|> lap(name, at)
|> stop(at)
end
@doc """
stop the watch, if the watch has already been stopped don't do nothing
"""
@spec stop(Stopwatch.Watch, Time) :: Stopwatch.Watch
def stop(watch, at \\ Time.now)
def stop(watch = %Watch{finish_time: nil}, at) do
%{watch | finish_time: at }
|> add_stop_lap(at)
end
def stop(watch, _), do: watch
@doc """
stop the watch and throws and exception if the watch is already stopped
"""
@spec stop!(Stopwatch.Watch, Time) :: Stopwatch.Watch
def stop!(watch, at \\ Time.now)
def stop!(watch = %Watch{finish_time: finish_time}, at) do
case finish_time do
nil -> watch |> stop(at)
_ -> raise ArgumentError, "you cannot stop an already stopped watch"
end
end
defp add_stop_lap(watch, at) do
case last_lap_finish_time(watch) === at do
true -> watch
false -> lap(watch, :stop, at)
end
end
@doc """
gets a timer total time
"""
@spec get_total_time(Stopwatch.Watch, atom) :: float | integer
def get_total_time(watch, unit \\ :msecs)
def get_total_time(%Watch{start_time: start, finish_time: nil}, unit) do
calculate_diff(start, Time.now, unit)
end
def get_total_time(%Watch{start_time: start, finish_time: finish}, unit) do
calculate_diff(start, finish, unit)
end
@doc """
extract all laps from a watch
the output is a list of tuples with {name, {start_time, finish_time}}
"""
@spec get_laps(Stopwatch.Watch, atom) :: [{binary, {integer, integer, integer}}]
def get_laps(watch, unit \\ :msecs)
def get_laps(%Watch{laps: []}, _), do: []
def get_laps(%Watch{laps: laps}, unit) do
laps
|> Enum.map(fn({name, {from, to}}) -> {name, calculate_diff(from, to, unit)} end)
|> Enum.reverse
end
@doc """
extract lap names from a watch
## Examples
iex> Stopwatch.Watch.new |> Stopwatch.Watch.lap(:test) |> Stopwatch.Watch.lap(:test2) |> Stopwatch.Watch.get_lap_names
[:test, :test2]
iex> Stopwatch.Watch.new |> Stopwatch.Watch.get_lap_names
[]
"""
@spec get_lap_names(Stopwatch.Watch) :: [binary]
def get_lap_names(%Watch{laps: []}), do: []
def get_lap_names(watch) do
Enum.map(get_laps(watch), &lap_name/1)
end
@doc """
get a single lap time by its name, if the name do not exists returns nil
## Examples
iex> use Stopwatch
...> watch = Watch.new({0, 0, 100_000}) |> Watch.last_lap(:test, {0, 0, 200000})
...> Watch.get_lap_time(watch, :not_existent)
nil
iex> use Stopwatch
...> watch = Watch.new({0, 0, 100_000}) |> Watch.lap(:first, {0, 0, 150_000}) |> Watch.last_lap(:test, {0, 0, 200_000})
...> Watch.get_lap_time(watch, :test, :secs)
0.05
"""
@spec get_lap_time(Stopwatch.Watch, any, atom) :: number | nil
def get_lap_time(watch, name, unit \\ :msecs)
def get_lap_time(%Watch{laps: []}, _, _), do: nil
def get_lap_time(%Watch{laps: laps}, name, unit) do
case Enum.find(laps, nil, lap_matcher(name)) do
nil -> nil
{_, {from, to}} -> calculate_diff(from, to, unit)
end
end
@doc """
get a single lap time by its name, if the name do not exists raises an exception
## Examples
iex> Stopwatch.Watch.new |> Stopwatch.Watch.last_lap(:test) |> Stopwatch.Watch.get_lap_time!(:not_existent)
** (ArgumentError) the lap named not_existent was not found
"""
@spec get_lap_time!(Stopwatch.Watch, any, atom) :: number
def get_lap_time!(%Watch{laps: laps}, name, unit \\ :msecs) do
case Enum.find(laps, nil, lap_matcher(name)) do
nil -> raise ArgumentError, "the lap named #{name} was not found"
{_, {from, to}} -> calculate_diff(from, to, unit)
end
end
defp calculate_diff(from, to, unit) do
Time.sub(to, from)
|> convert_time(unit)
end
defp convert_time(time, unit) do
method = "to_#{to_string(unit)}"
apply(Timex.Time, String.to_atom(method), [time])
end
defp last_lap_finish_time(%Watch{start_time: start_time, laps: []}), do: start_time
defp last_lap_finish_time(%Watch{laps: [last_lap | _]}) do
{_, {_, finish_time}} = last_lap
finish_time
end
defp lap_matcher(name) do
fn
{^name, _} -> true
_ -> false
end
end
defp lap_name({name, _}), do: name
end