lib/datetime/ambiguous.ex

defmodule Timex.AmbiguousDateTime do
  @moduledoc """
  Represents a DateTime which is ambiguous due to timezone rules.

  ## Ambiguity #1 - Non-existent times

  Let's use American daylight savings time rules as our example here,
  using America/Chicago as our example. Central Standard Time for that
  zone ends at 2:00 AM, but Central Daylight Time does not begin until
  3:00 AM, this is because at 2:00 AM, our clocks "spring forward" - which
  is just an easy way of remembering that the offset goes from -6 from UTC,
  to -5 from UTC. Since there is no timezone period associated with the hours
  of 2-3 AM in the America/Chicago zone (it's neither CST nor CDT during that hour),
  one has to decide what the intent is. Timex makes the call that shifting to the
  next period (i.e. "spring forward" using our example above) makes the most logical
  sense when working with non-existent time periods.

  TL;DR - Timex will "spring forward" or "fall back", depending on what the zone change
  happens to be for the non-existent time. Using America/Chicago as an example, if you
  try to create a DateTime for 2 AM on March 13, 2016, Timex will give you back 3 AM on
  March 13, 2016, because the zone is in the middle of changing from CST to CDT, and the
  earliest representable time in CDT is 3 AM.

  ## Ambiguity #2 - Times with more than one valid zone period

  This one is the reason why this module exists. There are times, though rare, where more
  than one zone applies to a given date and time. For example, Asia/Taipei, on December 31st,
  1895, from 23:54:00 to 23:59:59, two timezone periods are active LMT, and JWST, because that
  locale was switching to JWST from LMT. Because of this, it's impossible to know programmatically
  which zone is desired. The programmer must make a choice on which zone they want to use.

  For this use case, Timex will return an AmbiguousDateTime any time you try to create a DateTime,
  or shift a DateTime, to an ambiguous time period. It has two fields, :before, containing a DateTime
  configured in the timezone occurring before the ambiguous period, and :after, containing a DateTime
  configured in the timezone occurring after the ambiguous period. It is up to you as the programmer to
  decide which DateTime is the one to use, but my recommendation is to choose :after, unless you have a
  specific reason to use :before.
  """

  defstruct before: nil,
            after: nil,
            type: :ambiguous

  @type t :: %__MODULE__{
          :before => DateTime.t(),
          :after => DateTime.t(),
          :type => :ambiguous | :gap
        }

  defimpl Inspect do
    alias Timex.AmbiguousDateTime

    def inspect(datetime, %{:structs => false} = opts) do
      Inspect.Algebra.to_doc(datetime, opts)
    end

    def inspect(%AmbiguousDateTime{before: before, after: aft, type: :gap}, _opts) do
      "#<Gap(#{inspect(before)} ~ #{inspect(aft)})>"
    end

    def inspect(%AmbiguousDateTime{before: before, after: aft}, _opts) do
      "#<Ambiguous(#{inspect(before)} ~ #{inspect(aft)})>"
    end
  end
end