bitwalker / timex

A complete date/time library for Elixir projects.
https://hexdocs.pm/timex
MIT License
1.76k stars 379 forks source link

Half/Quarter hour timezones are treated as invalid timezones #394

Open marpo60 opened 7 years ago

marpo60 commented 7 years ago

Thanks for this great library!

Steps to reproduce

iex(1)> time = "2017-01-02T02:00:00+08:45"
"2017-01-02T02:00:00+08:45"
iex(2)> {:ok, date } = Timex.parse(time, "{ISO:Extended:Z}")
{:ok, #<DateTime(2017-01-02T02:00:00+08:45 Etc/GMT-8:45)>}
iex(3)> Timex.shift(date, hours: -2)
** (FunctionClauseError) no function clause matching in Timex.Timezone.parse_offset/1
    (timex) lib/timezone/timezone.ex:232: Timex.Timezone.parse_offset("8:45")
    (timex) lib/timezone/timezone.ex:204: Timex.Timezone.do_get/3
    (timex) lib/timezone/timezone.ex:353: Timex.Timezone.resolve/3
    (timex) lib/datetime/datetime.ex:406: Timex.Protocol.DateTime.shift_by/3
    (timex) lib/datetime/datetime.ex:249: Timex.Protocol.DateTime.apply_shifts/2

Description of issue

Thanks!

lucas-nelson commented 6 years ago

I've hit this too, coming out of Timex.Timezone.convert/2. Various different formats work until:

  1. there's a leading + or -, and
  2. there's a 0 prefixed on the hour, and
  3. the minutes are something other than 00

(examples run with Timex version 3.2.1, have also seen on 3.1.24)

iex(5)> Timex.Timezone.convert(Timex.now(), "+9:00")
#DateTime<2018-03-08 07:58:18.550392+09:00 +09 Etc/GMT-9>
iex(6)> Timex.Timezone.convert(Timex.now(), "9:00")
{:error, {:invalid_timezone, {:error, {:invalid_timezone, "9:00"}}}}
iex(7)> Timex.Timezone.convert(Timex.now(), "09:00")
{:error, {:invalid_timezone, {:error, {:invalid_timezone, "09:00"}}}}
iex(8)> Timex.Timezone.convert(Timex.now(), "-9:00")
#DateTime<2018-03-07 13:58:27.650314-09:00 -09 Etc/GMT+9>
iex(9)> Timex.Timezone.convert(Timex.now(), "-09:00")
#DateTime<2018-03-07 13:58:29.797884-09:00 -09 Etc/GMT+9>
iex(10)> Timex.Timezone.convert(Timex.now(), "+09:00")
#DateTime<2018-03-08 07:58:34.237682+09:00 +09 Etc/GMT-9>
iex(11)> Timex.Timezone.convert(Timex.now(), "+9:30")
#DateTime<2018-03-08 07:58:42.181430+09:00 +09 Etc/GMT-9>
iex(12)> Timex.Timezone.convert(Timex.now(), "9:30")
{:error, {:invalid_timezone, {:error, {:invalid_timezone, "9:30"}}}}
iex(13)> Timex.Timezone.convert(Timex.now(), "-9:30")
#DateTime<2018-03-07 13:58:51.213119-09:00 -09 Etc/GMT+9>
iex(14)> Timex.Timezone.convert(Timex.now(), "-09:30")
** (FunctionClauseError) no function clause matching in Timex.Timezone.parse_offset/1

    The following arguments were given to Timex.Timezone.parse_offset/1:

        # 1
        "9:30"

    Attempted function clauses (showing 10 out of 10):

        defp parse_offset(<<48::integer(), h2::utf8(), 58::integer(), m1::utf8(), m2::utf8(), 58::integer(), s1::utf8(), s2::utf8()>>)
        defp parse_offset(<<h1::utf8(), h2::utf8(), 58::integer(), m1::utf8(), m2::utf8(), 58::integer(), s1::utf8(), s2::utf8()>> = suffix)
        defp parse_offset(<<48::integer(), h2::utf8(), 58::integer(), m1::utf8(), m2::utf8()>>)
        defp parse_offset(<<h1::utf8(), h2::utf8(), 58::integer(), m1::utf8(), m2::utf8()>> = suffix)
        defp parse_offset(<<48::integer(), h2::utf8()>>)
        defp parse_offset(<<h1::utf8(), h2::utf8()>> = suffix)
        defp parse_offset(<<h1::utf8(), h2::utf8(), 46::integer(), rest::binary()>>)
        defp parse_offset(<<h2::utf8(), 46::integer(), rest::binary()>>)
        defp parse_offset("0")
        defp parse_offset(<<h1::utf8()>> = suffix)

    (timex) lib/timezone/timezone.ex:232: Timex.Timezone.parse_offset/1
    (timex) lib/timezone/timezone.ex:193: Timex.Timezone.do_get/3
    (timex) lib/timezone/timezone.ex:420: Timex.Timezone.convert/2
iex(14)> Timex.Timezone.convert(Timex.now(), "+09:30")
** (FunctionClauseError) no function clause matching in Timex.Timezone.parse_offset/1

    The following arguments were given to Timex.Timezone.parse_offset/1:

        # 1
        "9:30"

    Attempted function clauses (showing 10 out of 10):

        defp parse_offset(<<48::integer(), h2::utf8(), 58::integer(), m1::utf8(), m2::utf8(), 58::integer(), s1::utf8(), s2::utf8()>>)
        defp parse_offset(<<h1::utf8(), h2::utf8(), 58::integer(), m1::utf8(), m2::utf8(), 58::integer(), s1::utf8(), s2::utf8()>> = suffix)
        defp parse_offset(<<48::integer(), h2::utf8(), 58::integer(), m1::utf8(), m2::utf8()>>)
        defp parse_offset(<<h1::utf8(), h2::utf8(), 58::integer(), m1::utf8(), m2::utf8()>> = suffix)
        defp parse_offset(<<48::integer(), h2::utf8()>>)
        defp parse_offset(<<h1::utf8(), h2::utf8()>> = suffix)
        defp parse_offset(<<h1::utf8(), h2::utf8(), 46::integer(), rest::binary()>>)
        defp parse_offset(<<h2::utf8(), 46::integer(), rest::binary()>>)
        defp parse_offset("0")
        defp parse_offset(<<h1::utf8()>> = suffix)

    (timex) lib/timezone/timezone.ex:232: Timex.Timezone.parse_offset/1
    (timex) lib/timezone/timezone.ex:204: Timex.Timezone.do_get/3
    (timex) lib/timezone/timezone.ex:420: Timex.Timezone.convert/2
chengyin commented 6 years ago

I suggest changing the title of the issue to: "Half/Quarter hour timezones are treated as invalid timezones".

My example:

> {:ok, dt} = Timex.parse("2018-05-21T23:59:59.999+09:30", "{ISO:Extended:Z}")
{:ok, #DateTime<2018-05-21 23:59:59.999+09:30 +09:30 Etc/GMT-9:30>}

> Timex.to_datetime(~N[2018-05-21 00:00:00], dt.time_zone)
{:error, {:invalid_timezone, "Etc/GMT-9:30"}}
codeadict commented 6 years ago

Was this fixed? Is this a problem in the tzdata package or Timex itself?

lucas-nelson commented 4 years ago

Something seems to have been fixed. I can't reproduce my original error now:

iex(2)> Timex.Timezone.convert(Timex.now(), "-09:30")
#DateTime<2020-08-10 15:44:14.712366-09:30 -09:30 Etc/GMT+9:30>
iex(3)> Timex.Timezone.convert(Timex.now(), "+09:30")
#DateTime<2020-08-11 10:44:23.840286+09:30 +09:30 Etc/GMT-9:30>