Packages
timex
0.12.1
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/timezone_dst.ex
defmodule Timex.Timezone.Dst do
@moduledoc """
Rules for determining if a datetime falls within a daylight savings period.
"""
alias Timex.Date, as: Date
alias Timex.DateTime, as: DateTime
alias Timex.TimezoneInfo, as: TimezoneInfo
@doc """
Check if the provided datetime is in daylight savings time
"""
@spec is_dst?(DateTime.t) :: true | false | :ambiguous_time | :doesnt_exist
def is_dst?(%DateTime{:timezone => %TimezoneInfo{:dst_start_day => :undef}}), do: false
def is_dst?(%DateTime{:year => year, :month => month, :day => day, :hour => hour, :minute => min, :second => sec, :timezone => tz}) do
%TimezoneInfo{
:gmt_offset_dst => dst_shift,
:dst_start_day => dst_start_rule, :dst_start_time => dst_start_time,
:dst_end_day => dst_end_rule, :dst_end_time => dst_end_time
} = tz
dst_start_day = get_dst_day_of_year(dst_start_rule, year)
dst_end_day = get_dst_day_of_year(dst_end_rule, year)
current_day = get_day_of_year({year, month, day})
case is_dst_date(dst_start_day, dst_end_day, current_day) do
:equal_to_start ->
is_dst_start_time(time_to_minutes({hour, min, sec}), time_to_minutes(dst_start_time), dst_shift)
:equal_to_end ->
is_dst_end_time(time_to_minutes({hour, min, sec}), time_to_minutes(dst_end_time), dst_shift)
result ->
cond do
# If DST ends the next day at 00:00, then after 23:00 the day before, we're ambiguous
dst_end_day - 1 === current_day and time_to_minutes(dst_end_time) == 0 ->
cond do
time_to_minutes({hour, min, sec}) >= (23 * 60) -> :ambiguous_time
true -> result
end
# Likewise, if DST started the night before at midnight (24:00), then before 01:00 of the current day, the time doesn't exist
dst_start_day + 1 === current_day and time_to_minutes(dst_start_time) == (24 * 60) ->
cond do
time_to_minutes({hour, min, sec}) < 60 -> :doesnt_exist
true -> result
end
true ->
result
end
end
end
defp is_dst_start_time(current_time, start_time, _shift) when current_time < start_time, do: false
defp is_dst_start_time(current_time, start_time, shift) do
case (start_time + shift) do
# When start time is late, say 2400, it rolls over in to the next day with the shift
shifted when shifted < start_time and current_time < start_time ->
(current_time + shift) >= shifted
# When it doesn't roll over, normal checks apply
shifted when shifted > start_time and current_time >= shifted ->
true
shifted when shifted > start_time and current_time < shifted and current_time >= start_time ->
:doesnt_exist
_ ->
:doesnt_exist
end
end
defp is_dst_end_time(current_time, end_time, shift) do
# Ambigous for the hour before it ends
case (end_time - shift) do
# When the end is at 0:00 and the current is >= 0:00, we're out of DST
shifted when shifted < 0 and current_time >= 0 -> false
# When the end is at any other point in the day, and the current time is between the shifted and end time, ambigous
shifted when current_time >= shifted and current_time < end_time ->
:ambiguous_time
# When the end is at any other point in the day, and we're less than the shifted time, we're in dst
shifted when current_time < shifted ->
true
# When the end is at any other point in the day, and we're greater than the end_time, we're out of dst
_ ->
false
end
end
defp is_dst_date(start_day, _, current_day) when current_day == start_day, do: :equal_to_start
defp is_dst_date(_, end_day, current_day) when current_day == end_day, do: :equal_to_end
defp is_dst_date(start_day, end_day, current_day) when start_day < end_day and (current_day > start_day and current_day < end_day), do: true
defp is_dst_date(start_day, end_day, current_day) when start_day < end_day and (current_day < start_day or current_day > end_day), do: false
defp is_dst_date(start_day, end_day, current_day) when start_day > end_day and (current_day < start_day and current_day > end_day), do: false
defp is_dst_date(start_day, end_day, current_day) when start_day > end_day and (current_day > start_day or current_day < end_day), do: true
defp get_dst_day_of_year({weekday, day, month}, year) when (weekday == :last) or (weekday == 5) do
month_num = Date.month_to_num(month)
day_num = Date.day_to_num(day)
get_last_dst(day_num, month_num, year)
end
defp get_dst_day_of_year({weekday, day, month}, year) when (weekday > 0) and (weekday <= 4) do
month_num = Date.month_to_num(month)
day_num = Date.day_to_num(day)
dst_days = get_day_of_year({year, month_num, 1})
dst_day = :calendar.day_of_the_week({year, month_num, 1})
case (dst_day === day_num) and (weekday === 1) do
true -> dst_days
false ->
adjusted_dst_days = case day_num >= dst_day do
true ->
dst_days + (day_num - dst_day)
false ->
dst_days + (7 - dst_day) + day_num
end
adjusted_dst_days + (weekday - 1) * 7
end
end
defp get_dst_day_of_year(_, _), do: raise(:error, "Invalid weekday")
defp get_last_dst(day_num, month_num, year) do
month_last_days = :calendar.date_to_gregorian_days(year, month_num, 1) + :calendar.last_day_of_the_month(year, month_num)
month_last_date = :calendar.gregorian_days_to_date(month_last_days)
month_last_dayofweek = :calendar.day_of_the_week(month_last_date)
case month_last_dayofweek > day_num do
true ->
month_last_days - (month_last_dayofweek - day_num) - :calendar.date_to_gregorian_days(if year == 0 do 0 else year - 1 end, 12, 31)
false ->
month_last_days - month_last_dayofweek - (7 - day_num) - :calendar.date_to_gregorian_days(if year == 0 do 0 else year - 1 end, 12, 31)
end
end
defp get_day_of_year({year, _, _} = date) do
:calendar.date_to_gregorian_days(date) - :calendar.date_to_gregorian_days(if year == 0 do 0 else year - 1 end, 12, 31)
end
defp time_to_minutes({hours, minutes}), do: (hours * 60) + minutes
defp time_to_minutes({hours, minutes, _}), do: (hours * 60) + minutes
end