From 1c77ef0e19ae921e7fcd7309d359dcb47e40f65c Mon Sep 17 00:00:00 2001 From: zachdaniel Date: Thu, 3 Jan 2019 16:42:52 -0500 Subject: [PATCH] feat: render block time with up to one decimal point of accuracy --- .../block_scout_web/channels/block_channel.ex | 2 +- .../templates/chain/show.html.eex | 2 +- .../explorer/counters/average_block_time.ex | 10 +- .../average_block_time_duration_format.ex | 99 +++++++++++++++++++ .../counters/average_block_time_test.exs | 2 + 5 files changed, 108 insertions(+), 7 deletions(-) create mode 100644 apps/explorer/lib/explorer/counters/average_block_time_duration_format.ex diff --git a/apps/block_scout_web/lib/block_scout_web/channels/block_channel.ex b/apps/block_scout_web/lib/block_scout_web/channels/block_channel.ex index 04c7a0aa9d..cea7e44104 100644 --- a/apps/block_scout_web/lib/block_scout_web/channels/block_channel.ex +++ b/apps/block_scout_web/lib/block_scout_web/channels/block_channel.ex @@ -36,7 +36,7 @@ defmodule BlockScoutWeb.BlockChannel do ) push(socket, "new_block", %{ - average_block_time: Timex.format_duration(average_block_time, :humanized), + average_block_time: Timex.format_duration(average_block_time, Explorer.Counters.AverageBlockTimeDurationFormat), chain_block_html: rendered_chain_block, block_html: rendered_block, block_number: block.number, diff --git a/apps/block_scout_web/lib/block_scout_web/templates/chain/show.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/chain/show.html.eex index 93477260e2..aa1621622a 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/chain/show.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/chain/show.html.eex @@ -41,7 +41,7 @@ <%= gettext "Average block time" %> - <%= Timex.format_duration(average_block_time, :humanized) %> + <%= Timex.format_duration(average_block_time, Explorer.Counters.AverageBlockTimeDurationFormat) %> <% end %> diff --git a/apps/explorer/lib/explorer/counters/average_block_time.ex b/apps/explorer/lib/explorer/counters/average_block_time.ex index 333b10a51e..5bfce4082f 100644 --- a/apps/explorer/lib/explorer/counters/average_block_time.ex +++ b/apps/explorer/lib/explorer/counters/average_block_time.ex @@ -26,7 +26,7 @@ defmodule Explorer.Counters.AverageBlockTime do |> Keyword.fetch!(:enabled) if enabled? do - block = if block, do: {block.number, DateTime.to_unix(block.timestamp)} + block = if block, do: {block.number, DateTime.to_unix(block.timestamp, :millisecond)} GenServer.call(__MODULE__, {:average_block_time, block}) else {:error, :disabled} @@ -48,7 +48,7 @@ defmodule Explorer.Counters.AverageBlockTime do timestamps_query |> Repo.all() |> Enum.map(fn {number, timestamp} -> - {number, DateTime.to_unix(timestamp)} + {number, DateTime.to_unix(timestamp, :millisecond)} end) {:ok, %{timestamps: timestamps, average: average_distance(timestamps)}} @@ -73,8 +73,8 @@ defmodule Explorer.Counters.AverageBlockTime do %{state | timestamps: timestamps, average: average_distance(timestamps)} end - defp average_distance([]), do: Duration.from_seconds(0) - defp average_distance([_]), do: Duration.from_seconds(0) + defp average_distance([]), do: Duration.from_milliseconds(0) + defp average_distance([_]), do: Duration.from_milliseconds(0) defp average_distance(timestamps) do durations = durations(timestamps) @@ -88,7 +88,7 @@ defmodule Explorer.Counters.AverageBlockTime do average |> round() - |> Duration.from_seconds() + |> Duration.from_milliseconds() end defp durations(timestamps) do diff --git a/apps/explorer/lib/explorer/counters/average_block_time_duration_format.ex b/apps/explorer/lib/explorer/counters/average_block_time_duration_format.ex new file mode 100644 index 0000000000..26536173df --- /dev/null +++ b/apps/explorer/lib/explorer/counters/average_block_time_duration_format.ex @@ -0,0 +1,99 @@ +defmodule Explorer.Counters.AverageBlockTimeDurationFormat do + @moduledoc """ + A `Timex.Format.Duration.Formatter` that renders the most significant unit out to one decimal point. + """ + + use Timex.Format.Duration.Formatter + alias Timex.Translator + + @millisecond 1 + @second @millisecond * 1000 + @minute @second * 60 + @hour @minute * 60 + @day @hour * 24 + @week @day * 7 + @month @day * 30 + @year @day * 365 + + @unit_term_mapping [ + {@year, "year"}, + {@month, "month"}, + {@week, "week"}, + {@day, "day"}, + {@hour, "hour"}, + {@minute, "minute"}, + {@second, "second"}, + {@millisecond, "millisecond"} + ] + + @doc """ + Formats a duration as a single value and a decimal part. + + See `lformat/2` for more information. + + iex> use Timex + ...> Duration.from_erl({0, 65, 0}) |> #{__MODULE__}.format() + "1.1 minutes" + """ + @spec format(Duration.t()) :: String.t() | {:error, term} + def format(%Duration{} = duration), do: lformat(duration, Translator.default_locale()) + def format(_), do: {:error, :invalid_duration} + + @doc """ + Formats a duration as a single value and a decimal part. + + Chooses the greatest whole unit available from: + + * year + * month + * week + * day + * hour + * minute + * second + * millisecond + + Accepts a translation locale and honors it for the units. + + iex> use Timex + ...> Duration.from_erl({0, 65, 0}) |> #{__MODULE__}.lformat("en") + "1.1 minutes" + + iex> use Timex + ...> Duration.from_erl({0, 0, 0}) |> #{__MODULE__}.lformat("en") + "0 milliseconds" + """ + def lformat(%Duration{} = duration, locale) do + duration + |> Duration.to_milliseconds() + |> round() + |> do_format(locale) + end + + def lformat(_, _locale), do: {:error, :invalid_duration} + + defp do_format(0, locale) do + Translator.translate_plural(locale, "units", "%{count}, millisecond", "%{count} milliseconds", 0) + end + + for {unit, name} <- @unit_term_mapping do + defp do_format(value, locale) when value >= unquote(unit) do + format_unit(locale, unquote(unit), value, unquote(name)) + end + end + + defp format_unit(locale, unit, value, singular) do + decimal_value = value / unit + truncated = trunc(decimal_value) + + # remove any trailing `.0` + formatted_value = + if decimal_value == truncated do + truncated + else + Float.round(decimal_value, 1) + end + + Translator.translate_plural(locale, "units", "%{count} #{singular}", "%{count} #{singular}s", formatted_value) + end +end diff --git a/apps/explorer/test/explorer/counters/average_block_time_test.exs b/apps/explorer/test/explorer/counters/average_block_time_test.exs index 9efabce384..27ef74d551 100644 --- a/apps/explorer/test/explorer/counters/average_block_time_test.exs +++ b/apps/explorer/test/explorer/counters/average_block_time_test.exs @@ -1,6 +1,8 @@ defmodule Explorer.Counters.AverageBlockTimeTest do use Explorer.DataCase + doctest Explorer.Counters.AverageBlockTimeDurationFormat + alias Explorer.Counters.AverageBlockTime defp block(number, last, duration), do: %{number: number, timestamp: Timex.shift(last, seconds: duration)}