 
   Only supports converting datetimes which are in the ISO calendar,
   attempting to convert datetimes from other calendars will raise.
+  You can also optionally specify an offset for the formatted string.
 
   WARNING: the ISO 8601 datetime format does not contain the time zone nor
   its abbreviation, which means information is lost when converting to such

      iex> DateTime.to_iso8601(dt, :basic)
      "20000229T230007-0400"

  """
  @spec to_iso8601(Calendar.datetime(), :extended | :basic) :: String.t()
  def to_iso8601(datetime, format \\ :extended)

  def to_iso8601(%{calendar: Calendar.ISO} = datetime, format)
      when format in [:extended, :basic] do
    %{
      year: year,

      std_offset: std_offset
    } = datetime

    datetime_to_string(year, month, day, hour, minute, second, microsecond, format) <>
      Calendar.ISO.offset_to_string(utc_offset, std_offset, time_zone, format)
  end

  def to_iso8601(
        %{calendar: Calendar.ISO, microsecond: {_, precision}, time_zone: "Etc/UTC"} = datetime,
        format,
        0
      )
      when format in [:extended, :basic] do
    {year, month, day, hour, minute, second, {microsecond, _}} = shift_by_offset(datetime, 0)

    datetime_to_string(year, month, day, hour, minute, second, {microsecond, precision}, format) <>
      "Z"
  end

  def to_iso8601(%{calendar: Calendar.ISO} = datetime, format, offset)
      when format in [:extended, :basic] do
    {_, precision} = datetime.microsecond
    {year, month, day, hour, minute, second, {microsecond, _}} = shift_by_offset(datetime, offset)

    datetime_to_string(year, month, day, hour, minute, second, {microsecond, precision}, format) <>
      Calendar.ISO.offset_to_string(offset, 0, nil, format)
  end

  def to_iso8601(%{calendar: _} = datetime, format, offset) when format in [:extended, :basic] do
    datetime
    |> convert!(Calendar.ISO)
    |> to_iso8601(format, offset)
  end

  defp shift_by_offset(%{calendar: calendar} = datetime, offset) do
    total_offset = datetime.utc_offset + datetime.std_offset

    datetime
    |> to_iso_days()
    # Subtract total original offset in order to get UTC and add the new offset
    |> Calendar.ISO.add_day_fraction_to_iso_days(offset - total_offset, 86400)
    |> calendar.naive_datetime_from_iso_days()
  end

  defp datetime_to_string(year, month, day, hour, minute, second, microsecond, format) do
    Calendar.ISO.date_to_string(year, month, day, format) <>
      "T" <>
      Calendar.ISO.time_to_string(hour, minute, second, microsecond, format)
  end

  @doc """
