Packages
timex
3.7.8
3.7.13
3.7.12
3.7.11
3.7.9
3.7.8
3.7.7
3.7.6
3.7.5
3.7.3
3.7.2
3.7.1
3.7.0
3.6.4
3.6.3
3.6.2
retired
3.6.1
3.6.0
3.5.0
3.4.2
3.4.1
3.3.0
3.2.2
3.2.1
3.2.0
3.1.25
retired
3.1.24
3.1.23
3.1.22
3.1.21
3.1.20
3.1.19
3.1.18
3.1.17
3.1.16
3.1.15
3.1.13
3.1.12
3.1.11
3.1.10
3.1.9
3.1.8
3.1.7
3.1.6
3.1.5
3.1.4
3.1.3
3.1.2
3.1.1
3.1.0
3.0.8
3.0.7
3.0.6
3.0.5
3.0.4
3.0.3
3.0.2
3.0.1
3.0.0
2.2.1
2.1.6
2.1.5
2.1.4
2.1.3
2.1.2
2.1.1
2.1.0
2.0.0
1.0.2
1.0.1
1.0.0
1.0.0-rc4
1.0.0-rc3
1.0.0-rc2
1.0.0-rc1
1.0.0-pre
0.19.5
0.19.4
0.19.3
0.19.2
0.19.1
0.19.0
0.18.2
0.18.1
0.18.0
0.17.0
0.16.2
0.16.1
0.16.0
0.15.0
0.14.3
0.14.2
0.14.1
0.14.0
0.13.5
0.13.4
0.13.3
0.13.2
0.13.1
0.13.0
0.12.9
0.12.8
0.12.7
0.12.6
0.12.5
0.12.4
0.12.3
0.12.2
0.12.1
0.12.0
0.11.0
0.10.2
0.10.1
0.10.0
0.9.0
0.8.0
0.7.1
0.6.0
0.5.0
0.4.8
0.4.7
0.4.6
Timex is a rich, comprehensive Date/Time library for Elixir projects, with full timezone support via the :tzdata package. If you need to manipulate dates, times, datetimes, timestamps, etc., then Timex is for you!
Current section
Files
Jump to
Current section
Files
lib/timezone/posix_timezone.ex
defmodule Timex.PosixTimezone do
@moduledoc """
Used when parsing POSIX-TZ timezone rules.
"""
alias Timex.TimezoneInfo
defstruct name: nil,
std_abbr: nil,
std_offset: 0,
dst_abbr: nil,
dst_offset: nil,
dst_start: nil,
dst_end: nil
@type rule_bound ::
{{:julian_leap, 0..365}, Time.t()}
| {{:julian, 1..365}, Time.t()}
| {{:mwd, {month :: 1..12, week :: 1..5, day_of_week :: 0..6}}, Time.t()}
| nil
@type t :: %__MODULE__{
name: nil | String.t(),
std_abbr: nil | String.t(),
dst_abbr: nil | String.t(),
std_offset: integer(),
dst_offset: integer(),
dst_start: rule_bound(),
dst_end: rule_bound()
}
@doc """
Obtains a `NaiveDateTime` representing the start of DST for this zone.
Returns nil if there is no DST period.
"""
@spec dst_start(t, DateTime.t() | NaiveDateTime.t() | Date.t()) :: NaiveDateTime.t() | nil
def dst_start(posix_tz, date)
def dst_start(%__MODULE__{dst_start: nil}, _), do: nil
def dst_start(%__MODULE__{dst_start: dst_start}, %{year: year}) do
bound_to_naive_datetime(dst_start, year)
end
@doc """
Obtains a `NaiveDateTime` representing the end of DST for this zone.
Returns nil if there is no DST period.
"""
@spec dst_end(t, DateTime.t() | NaiveDateTime.t() | Date.t()) :: NaiveDateTime.t() | nil
def dst_end(posix_tz, date)
def dst_end(%__MODULE__{dst_end: nil}, _), do: nil
def dst_end(%__MODULE__{dst_end: dst_end}, %{year: year}) do
bound_to_naive_datetime(dst_end, year)
end
@doc """
Returns a `TimezoneInfo` struct representing this timezone for the given datetime
"""
@spec to_timezone_info(t, DateTime.t() | NaiveDateTime.t() | Date.t()) :: TimezoneInfo.t()
def to_timezone_info(%__MODULE__{} = tz, date) do
date = to_naive_datetime(date)
if is_dst?(tz, date) do
%TimezoneInfo{
full_name: tz.name,
abbreviation: tz.dst_abbr,
offset_std: tz.dst_offset,
offset_utc: tz.std_offset,
from: :min,
until: :max
}
else
%TimezoneInfo{
full_name: tz.name,
abbreviation: tz.std_abbr,
offset_std: 0,
offset_utc: tz.std_offset,
from: :min,
until: :max
}
end
end
@doc """
Returns a `Calendar.TimeZoneDatabase` compatible map, representing this timezone for the given datetime
"""
def to_period_for_date(%__MODULE__{} = tz, date) do
date = to_naive_datetime(date)
if is_dst?(tz, date) do
std_offset = tz.dst_offset
utc_offset = tz.std_offset
%{
std_offset: std_offset,
utc_offset: utc_offset,
zone_abbr: tz.dst_abbr,
time_zone: tz.name
}
else
%{std_offset: 0, utc_offset: tz.std_offset, zone_abbr: tz.std_abbr, time_zone: tz.name}
end
end
@doc """
Returns a boolean indicating if the datetime provided occurs during DST of the given POSIX timezone.
"""
@spec is_dst?(t, DateTime.t() | NaiveDateTime.t() | Date.t()) :: boolean
def is_dst?(%__MODULE__{} = tz, date) do
with %NaiveDateTime{} = dst_start <- dst_start(tz, date),
%NaiveDateTime{} = dst_end <- dst_end(tz, date) do
cond do
NaiveDateTime.compare(date, dst_start) == :lt ->
false
NaiveDateTime.compare(date, dst_end) == :gt ->
false
:else ->
true
end
else
nil ->
false
end
end
defp bound_to_naive_datetime({{:mwd, month, week, weekday}, time}, year) do
month_start = Timex.Date.new!(year, month, 1)
month_start_dow = Timex.Date.day_of_week(month_start, :sunday) - 1
if weekday == month_start_dow and week == 1 do
# Got lucky, we're done
Timex.NaiveDateTime.new!(month_start, time)
else
first_week_date =
if month_start_dow <= weekday do
# The week starting on the 1st includes our weekday, so it is the first week of the month
%{month_start | day: month_start.day + (weekday - month_start_dow)}
else
# The week starting on the 1st does not include our weekday, so shift forward a week
eow = Timex.Date.end_of_week(month_start)
%{eow | day: eow.day + 1 + weekday}
end
cond do
week == 1 ->
first_week_date
:else ->
day_shift = (week - 1) * 7
day = first_week_date.day + day_shift
ldom = :calendar.last_day_of_the_month(year, month)
date =
if ldom > day do
# Last occurrence is in week 4, so shift back a week
%{first_week_date | day: day - 7}
else
%{first_week_date | day: day}
end
Timex.NaiveDateTime.new!(date, time)
end
end
end
defp bound_to_naive_datetime({{:julian, day}, time}, year) do
date = Timex.Calendar.Julian.date_for_day_of_year(day - 1, year, leaps: false)
Timex.NaiveDateTime.new!(date, time)
end
defp bound_to_naive_datetime({{:julian_leap, day}, time}, year) do
date = Timex.Calendar.Julian.date_for_day_of_year(day, year, leaps: true)
Timex.NaiveDateTime.new!(date, time)
end
defp to_naive_datetime(%NaiveDateTime{} = date), do: date
defp to_naive_datetime(%DateTime{} = date), do: DateTime.to_naive(date)
defp to_naive_datetime(%Date{} = date), do: Timex.NaiveDateTime.new!(date, ~T[12:00:00])
end