defmodule ExTeal.Metric.Ranges do @moduledoc """ Helper functions for building time intervals and queries based on the params of a request. """ use Timex import Ecto.Query alias ExTeal.Metric.Request @doc """ Parse the start and end params """ @spec get_aggregate_datetimes(request :: Request.t()) :: {start_dt :: DateTime.t(), end_dt :: DateTime.t()} def get_aggregate_datetimes(%Request{unit: unit} = request) do request |> to_datetimes() |> aggregate_as(unit) end defp to_datetimes(%Request{start_at: start_param, end_at: end_param}) do { Timex.parse!(start_param, "{ISO:Extended}"), Timex.parse!(end_param, "{ISO:Extended}") } end defp aggregate_as({start_at, end_at}, "year") do { Timex.beginning_of_year(start_at), Timex.end_of_year(end_at) } end defp aggregate_as({start_at, end_at}, "month") do { Timex.beginning_of_month(start_at), Timex.end_of_month(end_at) } end defp aggregate_as({start_at, end_at}, "week") do { Timex.beginning_of_week(start_at), Timex.end_of_week(end_at) } end defp aggregate_as({start_at, end_at}, "day") do { Timex.beginning_of_day(start_at), Timex.end_of_day(end_at) } end defp aggregate_as({start_at, end_at}, "hour") do { beginning_of_hour(start_at), end_of_hour(end_at) } end defp aggregate_as({start_at, end_at}, "minute") do { beginning_of_minute(start_at), end_of_minute(end_at) } end @type between_options :: [start_dt: DateTime.t(), end_dt: DateTime.t(), metric: module()] @doc """ Set the boundaries of an aggregate queries """ @spec between(Ecto.Queryable.t(), between_options()) :: Ecto.Queryable.t() def between(query, start_dt: start, end_dt: end_dt, metric: metric) do dt_field = metric.date_field() field_type = metric.date_field_type() start_time = to_dt_field_type(start, field_type) end_time = to_dt_field_type(end_dt, field_type) query |> where([q], field(q, ^dt_field) >= ^start_time) |> where([q], field(q, ^dt_field) <= ^end_time) end @spec parse_dt(String.t(), String.t(), String.t()) :: DateTime.t() def parse_dt(param, format, timezone) do param |> Timex.parse!(format) |> DateTime.from_naive!(timezone) end defp beginning_of_hour(datetime) do {date, {h, _m, _s}} = Timex.to_erl(datetime) Timex.to_datetime({date, {h, 0, 0}}, datetime.time_zone) end defp end_of_hour(datetime) do {date, {h, _m, _s}} = Timex.to_erl(datetime) Timex.to_datetime({date, {h, 59, 59}}, datetime.time_zone) end defp beginning_of_minute(datetime) do {date, {h, m, _s}} = Timex.to_erl(datetime) Timex.to_datetime({date, {h, m, 0}}, datetime.time_zone) end defp end_of_minute(datetime) do {date, {h, m, _s}} = Timex.to_erl(datetime) Timex.to_datetime({date, {h, m, 59}}, datetime.time_zone) end defp to_dt_field_type(value, :naive_datetime) do utc = Timezone.get("Etc/UTC", value) Timezone.convert(value, utc) end defp to_dt_field_type(value, :utc_datetime) do value end end