webuhu
Hi all, as I’m always trying to improve my code.
Is anyone of you Elixir Pros interested into review my following code?
This module will convert duration measurements into a human easy readable scientific format.
What I mean by this.
I always hated if an uptime / or downtime measurement told me e.g. up since 1 y 21 d 5 h 5 min 5 s.
In this case 1 y 21 d would be totally enough for myself.
So the conversion scheme I’m using here is very opinionated.
In case of any unit value below 10, I will additionally add the value of the next unit. Above I don’t.
e.g.
- `1 s _ ms` - `9 s _ ms`
- `10 s ` - `59 s`
I would be very thankful if anybody would give me his opinion to my code below.
If you would do something in Elixir best practice completely different for this use case …
defmodule Tool.Timer do
@moduledoc """
Time measurement tool for performance & uptime testing.
Can convert a duration to a human easy understandable scientific format.
"""
@base_unit :nanosecond
@unit_shift_factor 10
@second 1
@minute @second * 60
@hour @minute * 60
@day @hour * 24
@year @day * 365
@doc """
Returns monotonic system time in @base_unit
"""
@spec now() :: integer()
def now, do: System.monotonic_time(@base_unit)
@doc """
Calculate time difference to a given start time (e.g. from `now/0`).
Return duration in @base_unit.
"""
@spec diff(integer(), integer()) :: integer()
def diff(start_time, end_time \\ now()) do
end_time - start_time
end
@doc """
Calculate time difference to a given start time (e.g. from `now/0`).
Return duration in a human easy understandable scientific format.
In: `-576460349095935368`
Out:
- `1 ns` - `999 ns`
- `1 μs _ ns` - `9 μs _ ns`
- `10 μs` - `999 μs`
- `1 ms _ μs` - `9 ms _ μs`
- `10 ms ` - `999 ms`
- `1 s _ ms` - `9 s _ ms`
- `10 s ` - `59 s`
- `1 min _ s` - `59 min _ s`
- `1 h _ min` - `23 h _ min`
- `1 d _ h` - `364 d _ h`
- `1 y _ d` - `_ y _ d`
"""
@spec diff_human(integer(), integer(), System.time_unit()) :: binary()
def diff_human(start_time, end_time \\ now(), base_unit \\ @base_unit) do
diff_base = diff(start_time, end_time)
diff_nano = System.convert_time_unit(diff_base, base_unit, :nanosecond)
time_conversion(diff_nano)
end
# everything below system precision
defp time_conversion(time) when time === 0,
do: "< #{system_precision()}"
# nanoseconds (ns) only
defp time_conversion(time) when time < 1_000,
do: unit_fraction_string(time, :nanosecond)
# microseconds (μs) + nanoseconds (ns)
defp time_conversion(time) when time < 1_000 * @unit_shift_factor do
list_to_string([
unit_fraction_string(time, :microsecond),
unit_fraction_string(time, :nanosecond)
])
end
# microseconds (μs) only
defp time_conversion(time) when time < 1_000_000,
do: unit_fraction_string(time, :microsecond)
# milliseconds (ms) + microseconds (μs)
defp time_conversion(time) when time < 1_000_000 * @unit_shift_factor do
list_to_string([
unit_fraction_string(time, :millisecond),
unit_fraction_string(time, :microsecond)
])
end
# milliseconds (ms) only
defp time_conversion(time) when time < 1_000_000_000,
do: unit_fraction_string(time, :millisecond)
# seconds (s) + milliseconds (ms)
defp time_conversion(time) when time < 1_000_000_000 * @unit_shift_factor do
list_to_string([
unit_fraction_string(time, :second),
unit_fraction_string(time, :millisecond)
])
end
# seconds (s) only
defp time_conversion(time) when time < @minute * 1_000_000_000,
do: unit_fraction_string(time, :second)
# minutes (min) + seconds (s)
defp time_conversion(time) when time < @hour * 1_000_000_000 do
list_to_string([
unit_subset_fraction_string(time, :minute, @minute, 60),
unit_subset_fraction_string(time, :second, @second, 60)
])
end
# hours (h) + minutes (min)
defp time_conversion(time) when time < @day * 1_000_000_000 do
list_to_string([
unit_subset_fraction_string(time, :hour, @hour, 60),
unit_subset_fraction_string(time, :minute, @minute, 60)
])
end
# days (d) + hours (h)
defp time_conversion(time) when time < @year * 1_000_000_000 do
list_to_string([
unit_subset_fraction_string(time, :day, @day, 365),
unit_subset_fraction_string(time, :hour, @hour, 24)
])
end
# years (y) + days (d)
@spec time_conversion(integer()) :: binary()
defp time_conversion(time) do
list_to_string([
unit_subset_fraction_string(time, :year, @year, 365),
unit_subset_fraction_string(time, :day, @day, 365)
])
end
# will return the human readable system precision
@spec system_precision :: binary
defp system_precision do
parts_per_second = System.convert_time_unit(1, :second, :native)
precision_nano = round(1 / parts_per_second * 1_000_000_000)
# to avoid a infinite loop on value 0
precision =
case precision_nano do
x when x > 0 -> x
_ -> 1
end
time_conversion(precision)
end
# unit fraction for everything below seconds
@spec unit_fraction_string(integer(), atom()) :: binary()
defp unit_fraction_string(time, unit) do
fraction =
time
|> System.convert_time_unit(:nanosecond, unit)
|> Integer.mod(1_000)
human_readable_unit(fraction, unit)
end
# unit fraction for everything above seconds
@spec unit_subset_fraction_string(integer(), atom(), integer(), integer()) :: binary()
defp unit_subset_fraction_string(time, unit, fraction_divisor, subset_divisor) do
fraction =
time
|> System.convert_time_unit(:nanosecond, :second)
|> Integer.floor_div(fraction_divisor)
|> Integer.mod(subset_divisor)
human_readable_unit(fraction, unit)
end
# join value with unit
@spec human_readable_unit(integer(), atom()) :: binary()
defp human_readable_unit(fraction, unit) do
cond do
fraction > 0 -> Integer.to_string(fraction) <> " " <> unit_abbreviation(unit)
true -> nil
end
end
# unit abbreviations
@spec unit_abbreviation(atom()) :: binary()
defp unit_abbreviation(:nanosecond), do: "ns"
defp unit_abbreviation(:microsecond), do: "μs"
defp unit_abbreviation(:millisecond), do: "ms"
defp unit_abbreviation(:second), do: "s"
defp unit_abbreviation(:minute), do: "min"
defp unit_abbreviation(:hour), do: "h"
defp unit_abbreviation(:day), do: "d"
defp unit_abbreviation(:year), do: "y"
# join list to string
@spec list_to_string(list()) :: binary()
defp list_to_string(list) do
list
# remove nil values from list
|> Enum.filter(& &1)
|> Enum.join(" ")
end
end
Edit:
- Updated with the already mentioned changes from
david_ex.
Trending in Questions
I’m working on a project that simulates the bumbl example in the programming phoenix book. It acts almost like an email client. We have a...
New
Hi everyone,
I am toying with the idea of building a “match maker” for giving personal help to people that wants to start coding.
I sta...
New
Hello,
I know there is an approach for handling lists that allows for optimized traversal, but I can’t recall the specific method (somet...
New
Documentation
While reading the Scoped Routes section, I noticed that the documentation currently refers to a problem without explainin...
New
So my question is quite simple and i have found no conclusive answer on forum, google or AI.
Should we use :erlang.float for Integer to ...
New
I recently noticed that Elixir’s Logger defaults its primary log level to :debug when no :logger, :level application configuration is pre...
New
I’m new to elixir and just tried to install the elixirLS extension for VScode(ium) and it is throwing some errors that I would like help ...
New
Other Trending Topics
Edit: 2026 May 15 - This post is archived.
Mob is alive!!
Main docs: mob v0.7.11 — Documentation
A bit of explanation for the slightly c...
New
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
New
I am happy to introduce the very α version of the new programming language compiled to BEAM.
Welcome Cure.
It has literally three kille...
New
Hi there! We created Gust: A task orchestrator inspired by Airflow.
For those who have never heard about Aiflow, it’s a Python-based wor...
New
Hi everyone!
The first release candidate for the Expert language server project is now available!
We’ve published a press release detai...
New
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #library
- #deployment
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #elixirconf
- #channels
- #exunit
- #discussion
- #code-sync
- #podcasts
- #javascript
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #ai
- #elixirconf-us
- #blog-post
- #elixir-ls
- #phoenix_html
- #iex
- #graphql
- #genstage
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #elixirconf-eu
- #metaprogramming
- #hex










Showing Posts 1 to 6- Show Best Posts
- Show All (oldest first)
- Show All (newest first)
david_ex
Here’s some low-hanging fruit:
Instead of
you can pipe:
Instead of
simply use
Instead of
use
or
(although using
Enum.joinis useful when you have many inputs to join).webuhu
Wow. Thanks a lot - I like the suggested changes.
A-Legg
You might want to look into using the Timex library that supports a duration structure.
webuhu
I know Timex - and there are some similarities to my implementation.
But for the fun of programming and learning Elixir,
and always trying to avoid any dependencies up first,
I decided to try to find my own solution.
kip
Here’s another approach using some of my CLDR libs:
Examples:
webuhu
Good to know - your CLDR units lib is quite impressive!
By the way. I like the way to use
DateTimeto calculate the diff.In case a diff must be calculated with persistent saved
DateTimebeyond an Erlang system restart.As I’m not sure how
System.monotonic_timewill behave then.But I would have to forgo on the precision of nanoseconds.