Current section
Files
Jump to
Current section
Files
lib/boltx/types_helper.ex
defmodule Bolty.TypesHelper do
@moduledoc false
@doc """
Decompose an amount seconds into the tuple {hours, minutes, seconds}
"""
@spec decompose_in_hms(integer()) :: {integer(), integer(), integer()}
def decompose_in_hms(seconds) do
[{minutes, seconds}, {hours, _}, _] =
[3600, 60]
|> Enum.reduce([{0, seconds}], fn
divisor, acc ->
{_, num} = hd(acc)
[{div(num, divisor), rem(num, divisor)} | acc]
end)
{hours, minutes, seconds}
end
@doc """
Convert NaiveDateTime and timezone into a Calendar.DateTime
Without losing microsecond data!
"""
@spec datetime_with_micro(Calendar.naive_datetime(), String.t()) :: Calendar.datetime()
def datetime_with_micro(%NaiveDateTime{} = naive_dt, timezone) do
DateTime.from_naive!(naive_dt, timezone)
end
@doc """
Convert an amount of seconds in a +hours:minutes offset
"""
@spec formated_time_offset(integer()) :: String.t()
def formated_time_offset(offset_seconds) do
{hours, minutes, _} = offset_seconds |> abs() |> decompose_in_hms()
get_sign_string(offset_seconds) <> format_time_part(hours) <> ":" <> format_time_part(minutes)
end
defp get_sign_string(number) when number >= 0 do
"+"
end
defp get_sign_string(_) do
"-"
end
defp format_time_part(time_part) when time_part < 10 do
"0" <> Integer.to_string(time_part)
end
defp format_time_part(time_part) do
Integer.to_string(time_part)
end
@period_prefix "P"
@time_prefix "T"
@year_suffix "Y"
@month_suffix "M"
@week_suffix "W"
@day_suffix "D"
@hour_suffix "H"
@minute_suffix "M"
@second_suffix "S"
@doc """
Create a Duration struct from the given parameters.
Note that this can be lossy, as Elixir Duration doesn't have nanosecond precision.
"""
@spec create_duration(integer(), integer(), integer(), integer()) :: Duration.t()
def create_duration(months, days, seconds, nanoseconds) do
Duration.new!(
year: div(months, 12),
month: rem(months, 12),
day: days,
hour: div(seconds, 3_600),
minute: div(rem(seconds, 3_600), 60),
second: rem(seconds, 60),
microsecond: {div(nanoseconds, 1_000), 6}
)
end
@doc """
Convert a %Duration in a cypher-compliant string.
To know everything about duration format, please see:
https://neo4j.com/docs/cypher-manual/current/syntax/temporal/#cypher-temporal-durations
"""
@spec format_duration(Duration.t()) :: {:ok, String.t()} | {:error, any()}
def format_duration(
%Duration{
year: y,
month: m,
day: d,
hour: h,
minute: mm,
second: s,
microsecond: us
} = duration
)
when is_integer(y) and is_integer(m) and is_integer(d) and is_integer(h) and
is_integer(mm) and is_integer(s) and is_tuple(us) do
formated = format_date(duration) <> format_time(duration)
param =
case formated do
"" -> ""
formated_duration -> @period_prefix <> formated_duration
end
{:ok, param}
end
def format_duration(param) do
{:error, param}
end
@spec format_date(Duration.t()) :: String.t()
defp format_date(%Duration{year: y, month: m, week: w, day: d}) do
format_duration_part(y, @year_suffix) <>
format_duration_part(m, @month_suffix) <>
format_duration_part(w, @week_suffix) <> format_duration_part(d, @day_suffix)
end
@spec format_time(Duration.t()) :: String.t()
defp format_time(%Duration{
hour: h,
minute: m,
second: s,
microsecond: us
})
when h > 0 or m > 0 or s > 0 or us.microsecond > 0 do
{seconds, nanoseconds} = cap_nanoseconds(s, us)
nanoseconds_f = nanoseconds |> Integer.to_string() |> String.pad_leading(9, "0")
seconds_f = "#{Integer.to_string(seconds)}.#{nanoseconds_f}" |> String.to_float()
@time_prefix <>
format_duration_part(h, @hour_suffix) <>
format_duration_part(m, @minute_suffix) <>
format_duration_part(seconds_f, @second_suffix)
end
defp format_time(_) do
""
end
@spec format_duration_part(number(), String.t()) :: String.t()
defp format_duration_part(duration_part, suffix)
when duration_part > 0 and is_bitstring(suffix) do
"#{stringify_number(duration_part)}#{suffix}"
end
defp format_duration_part(_, _) do
""
end
@spec stringify_number(number()) :: String.t()
defp stringify_number(number) when is_integer(number) do
Integer.to_string(number)
end
defp stringify_number(number) do
Float.to_string(number)
end
@spec cap_nanoseconds(integer(), tuple()) :: {integer(), integer()}
defp cap_nanoseconds(s, us) when is_integer(s) and is_tuple(us) do
{microseconds, _precision} = us
ns = microseconds * 1000
seconds_ = s + div(ns, 1_000_000_000)
nanoseconds_ = rem(ns, 1_000_000_000)
{seconds_, nanoseconds_}
end
end