File size: 2,274 Bytes
8da2481
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
defmodule Plausible.Stats.DateTimeRange do
  @moduledoc """
  Defines a struct similar `Date.Range`, but with `DateTime` instead of `Date`.

  The structs should be created with the `new!/2` function.
  """

  @enforce_keys [:first, :last]
  defstruct [:first, :last]

  @type t() :: %__MODULE__{
          first: %DateTime{},
          last: %DateTime{}
        }

  @doc """
  Creates a `DateTimeRange` struct from the given `%Date{}` structs.

  The first datetime will become the first date at 00:00:00, and the last datetime
  will become the last date at 23:59:59. Both dates will be turned into `%DateTime{}`
  structs in the given timezone.
  """
  def new!(%Date{} = first, last, timezone) do
    first =
      case DateTime.new(first, ~T[00:00:00], timezone) do
        {:ok, datetime} -> datetime
        {:gap, _just_before, just_after} -> just_after
        {:ambiguous, _first_datetime, second_datetime} -> second_datetime
      end

    new!(first, last, timezone)
  end

  def new!(first, %Date{} = last, timezone) do
    last =
      case DateTime.new(last, ~T[23:59:59], timezone) do
        {:ok, datetime} -> datetime
        {:gap, just_before, _just_after} -> just_before
        {:ambiguous, first_datetime, _second_datetime} -> first_datetime
      end

    new!(first, last, timezone)
  end

  def new!(%DateTime{} = first, %DateTime{} = last, _timezone) do
    new!(first, last)
  end

  def new!(%DateTime{} = first, %DateTime{} = last) do
    first = DateTime.truncate(first, :second)
    last = DateTime.truncate(last, :second)

    if DateTime.before?(first, last) do
      %__MODULE__{first: first, last: last}
    else
      %__MODULE__{first: last, last: first}
    end
  end

  def to_timezone(%__MODULE__{first: first, last: last}, timezone) do
    first = DateTime.shift_zone!(first, timezone)
    last = DateTime.shift_zone!(last, timezone)

    %__MODULE__{first: first, last: last}
  end

  def to_date_range(datetime_range, timezone) do
    %__MODULE__{first: first, last: last} = to_timezone(datetime_range, timezone)

    first = DateTime.to_date(first)
    last = DateTime.to_date(last)

    Date.range(first, last)
  end

  def length(%__MODULE__{first: first, last: last}, unit) do
    DateTime.diff(last, first, unit)
  end
end