Skip to content

Commit

Permalink
Adds wrapper around timex shift to prevent microsecond precision
Browse files Browse the repository at this point in the history
  • Loading branch information
brianberlin committed Sep 22, 2023
1 parent 988bd3d commit 64cd329
Show file tree
Hide file tree
Showing 8 changed files with 41 additions and 16 deletions.
4 changes: 2 additions & 2 deletions .tool-versions
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
elixir 1.12.1-otp-24
erlang 24.0.2
elixir 1.14.5-otp-25
erlang 25.3.2
4 changes: 3 additions & 1 deletion lib/cocktail/builder/i_calendar.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ defmodule Cocktail.Builder.ICalendar do
TODO: write long description
"""

import Cocktail.Util

alias Cocktail.{Rule, Schedule, Validation}
alias Cocktail.Validation.{Day, DayOfMonth, HourOfDay, Interval, MinuteOfHour, SecondOfMinute, TimeOfDay, TimeRange}

Expand Down Expand Up @@ -106,7 +108,7 @@ defmodule Cocktail.Builder.ICalendar do

defp build_end_time(%Schedule{start_time: start_time, duration: duration}) do
start_time
|> Timex.shift(seconds: duration)
|> shift_time(seconds: duration)
|> build_time("DTEND")
end

Expand Down
6 changes: 3 additions & 3 deletions lib/cocktail/schedule_state.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ defmodule Cocktail.ScheduleState do
@moduledoc false

alias Cocktail.{RuleState, Schedule, Span}

import Cocktail.Util
@type t :: %__MODULE__{
recurrence_rules: [RuleState.t()],
recurrence_times: [Cocktail.time()],
Expand Down Expand Up @@ -114,15 +114,15 @@ defmodule Cocktail.ScheduleState do
| recurrence_rules: rules,
recurrence_times: times,
exception_times: exceptions,
current_time: Timex.shift(time, seconds: 1)
current_time: shift_time(time, seconds: 1)
}

{occurrence, new_state}
end

@spec span_or_time(Cocktail.time() | nil, pos_integer | nil) :: Cocktail.occurrence()
defp span_or_time(time, nil), do: time
defp span_or_time(time, duration), do: Span.new(time, Timex.shift(time, seconds: duration))
defp span_or_time(time, duration), do: Span.new(time, shift_time(time, seconds: duration))

@spec min_time_for_rules([RuleState.t()]) :: Cocktail.time() | nil
defp min_time_for_rules([]), do: nil
Expand Down
22 changes: 22 additions & 0 deletions lib/cocktail/util.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,26 @@ defmodule Cocktail.Util do

def next_gte([], _), do: nil
def next_gte([x | rest], search), do: if(x >= search, do: x, else: next_gte(rest, search))

def beginning_of_day(time) do
time
|> Timex.beginning_of_day()
|> no_ms()
end

def beginning_of_month(time) do
time
|> Timex.beginning_of_month()
|> no_ms()
end

def shift_time(datetime, opts) do
datetime
|> Timex.shift(opts)
|> no_ms()
end

def no_ms(time) do
Map.put(time, :microsecond, {0, 0})
end
end
6 changes: 2 additions & 4 deletions lib/cocktail/validation/day_of_month.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ defmodule Cocktail.Validation.DayOfMonth do
@moduledoc false

import Cocktail.Validation.Shift
import Cocktail.Util, only: [next_gte: 2]
import Cocktail.Util

# assumed that DST can not "take" more than 4 hours between any 2 consecutive days
@min_dst_resultant_hours 20
Expand All @@ -28,9 +28,7 @@ defmodule Cocktail.Validation.DayOfMonth do
case next_gte(normalized_days, current_day_of_month) do
# go to next month
nil ->
next_month_time =
time
|> Timex.shift(months: 1)
next_month_time = shift_time(time, months: 1)

next_month_normalized_days = Enum.map(days, &normalize_day_of_month(&1, next_month_time))
next_month_earliest_day = Timex.set(next_month_time, day: hd(Enum.sort(next_month_normalized_days)))
Expand Down
5 changes: 3 additions & 2 deletions lib/cocktail/validation/interval.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ defmodule Cocktail.Validation.Interval do

import Integer, only: [mod: 2, floor_div: 2]
import Cocktail.Validation.Shift
import Cocktail.Util

@typep iso_week :: {Timex.Types.year(), Timex.Types.weeknum()}

Expand All @@ -20,8 +21,8 @@ defmodule Cocktail.Validation.Interval do

def next_time(%__MODULE__{type: :monthly, interval: interval}, time, start_time) do
start_time
|> Timex.beginning_of_month()
|> Timex.diff(Timex.beginning_of_month(time), :months)
|> beginning_of_month()
|> Timex.diff(beginning_of_month(time), :months)
|> mod(interval)
|> shift_by(:months, time)
end
Expand Down
6 changes: 4 additions & 2 deletions lib/cocktail/validation/schedule_lock.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ defmodule Cocktail.Validation.ScheduleLock do

import Integer, only: [mod: 2]
import Cocktail.Validation.Shift
import Cocktail.Util

@type lock :: :second | :minute | :hour | :wday | :mday

Expand Down Expand Up @@ -34,7 +35,7 @@ defmodule Cocktail.Validation.ScheduleLock do

def next_time(%__MODULE__{type: :mday}, time, start_time) do
if start_time.day > Calendar.ISO.days_in_month(time.year, time.month) do
next_time(%__MODULE__{type: :mday}, Timex.shift(time, months: 1), start_time)
next_time(%__MODULE__{type: :mday}, shift_time(time, months: 1), start_time)
else
next_mday_time(%__MODULE__{type: :mday}, time, start_time)
end
Expand All @@ -56,7 +57,8 @@ defmodule Cocktail.Validation.ScheduleLock do
|> Timex.diff(time, :days)

start_time_day_of_month ->
next_month_date = Timex.shift(time, months: 1)

next_month_date = shift_time(time, months: 1)
# Timex.set already handle the marginal case like setting a day of month more than the month contains
next_month_date
|> Timex.set(day: start_time_day_of_month)
Expand Down
4 changes: 2 additions & 2 deletions lib/cocktail/validation/shift.ex
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ defmodule Cocktail.Validation.Shift do

@typep option :: nil | :beginning_of_day | :beginning_of_hour | :beginning_of_minute

import Timex, only: [shift: 2, beginning_of_day: 1]
import Cocktail.Util

@spec shift_by(integer, shift_type, Cocktail.time(), option) :: result
def shift_by(amount, type, time, option \\ nil)
Expand All @@ -18,7 +18,7 @@ defmodule Cocktail.Validation.Shift do
def shift_by(amount, type, time, option) do
new_time =
time
|> shift("#{type}": amount)
|> shift_time("#{type}": amount)
|> apply_option(option)

{:change, new_time}
Expand Down

0 comments on commit 64cd329

Please sign in to comment.