Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Removes dependency on Timex #300

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .tool-versions
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
elixir 1.14.5-otp-25
erlang 25.3.2
elixir 1.15.6-otp-26
erlang 26.1
20 changes: 9 additions & 11 deletions lib/cocktail/builder/i_calendar.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,18 @@ 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}

@time_format_string "{YYYY}{0M}{0D}T{h24}{m}{s}"
@time_format_string "%Y%m%dT%H%M%S"

@doc ~S"""
Builds an iCalendar format string representation of a `t:Cocktail.Schedule.t/0`.

## Examples

iex> alias Cocktail.Schedule
...> start_time = Timex.to_datetime(~N[2017-01-01 06:00:00], "America/Los_Angeles")
...> start_time = Cocktail.Time.to_datetime(~N[2017-01-01 06:00:00], "America/Los_Angeles")
...> schedule = Schedule.new(start_time)
...> schedule = Schedule.add_recurrence_rule(schedule, :daily, interval: 2, hours: [10, 12])
...> build(schedule)
Expand Down Expand Up @@ -64,7 +62,7 @@ defmodule Cocktail.Builder.ICalendar do
## Examples

iex> alias Cocktail.Schedule
...> start_time = Timex.to_datetime(~N[2017-01-01 06:00:00], "America/Los_Angeles")
...> start_time = Cocktail.Time.to_datetime(~N[2017-01-01 06:00:00], "America/Los_Angeles")
...> schedule = Schedule.new(start_time)
...> schedule = Schedule.add_recurrence_rule(schedule, :daily, interval: 2, hours: [10, 12])
...> build_rule(schedule)
Expand All @@ -91,12 +89,12 @@ defmodule Cocktail.Builder.ICalendar do
@spec build_time(Cocktail.time(), String.t()) :: String.t()
defp build_time(%DateTime{} = time, prefix) do
timezone = time.time_zone
time_string = Timex.format!(time, @time_format_string)
time_string = Calendar.strftime(time, @time_format_string)
"#{prefix};TZID=#{timezone}:#{time_string}"
end

defp build_time(%NaiveDateTime{} = time, prefix) do
time_string = Timex.format!(time, @time_format_string)
time_string = Calendar.strftime(time, @time_format_string)
"#{prefix}:#{time_string}"
end

Expand All @@ -108,17 +106,17 @@ defmodule Cocktail.Builder.ICalendar do

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

@spec build_utc_time(Cocktail.time()) :: String.t()
defp build_utc_time(%NaiveDateTime{} = time), do: Timex.format!(time, @time_format_string)
defp build_utc_time(%NaiveDateTime{} = time), do: Calendar.strftime(time, @time_format_string)

defp build_utc_time(%DateTime{} = time) do
time
|> Timex.to_datetime("UTC")
|> Timex.format!(@time_format_string <> "Z")
|> Cocktail.Time.to_datetime("UTC")
|> Calendar.strftime(@time_format_string <> "Z")
end

@spec do_build_rule(Rule.t()) :: String.t()
Expand Down
19 changes: 11 additions & 8 deletions lib/cocktail/parser/i_calendar.ex
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ defmodule Cocktail.Parser.ICalendar do
alias Cocktail.{Rule, Schedule}

@time_regex ~r/^:?;?(?:TZID=(.+?):)?(.*?)(Z)?$/
@datetime_format "{YYYY}{0M}{0D}T{h24}{m}{s}"
@time_format "{h24}{m}{s}"
@datetime_format "%Y%m%dT%H%M%S"
@time_format "%H%M%S"

@doc ~S"""
Parses a string in iCalendar format into a `t:Cocktail.Schedule.t/0`.
Expand Down Expand Up @@ -38,7 +38,7 @@ defmodule Cocktail.Parser.ICalendar do
|> String.trim()
|> String.split("\n")
|> Enum.map(&String.trim/1)
|> parse_lines(Schedule.new(Timex.now()), 0)
|> parse_lines(Schedule.new(DateTime.utc_now()), 0)
end

@spec parse_lines([String.t()], Schedule.t(), non_neg_integer) :: {:ok, Schedule.t()} | {:error, term}
Expand Down Expand Up @@ -105,15 +105,17 @@ defmodule Cocktail.Parser.ICalendar do
end

@spec parse_naive_datetime(String.t()) :: {:ok, NaiveDateTime.t()} | {:error, term}
defp parse_naive_datetime(time_string), do: Timex.parse(time_string, @datetime_format)
defp parse_naive_datetime(time_string) do
Cocktail.Time.parse(time_string, @datetime_format)
end

@spec parse_utc_datetime(String.t()) :: {:ok, DateTime.t()} | {:error, term}
defp parse_utc_datetime(time_string), do: parse_zoned_datetime(time_string, "UTC")
defp parse_utc_datetime(time_string), do: parse_zoned_datetime(time_string, "Etc/UTC")

@spec parse_zoned_datetime(String.t(), String.t()) :: {:ok, DateTime.t()} | {:error, term}
defp parse_zoned_datetime(time_string, zone) do
with {:ok, naive_datetime} <- Timex.parse(time_string, @datetime_format),
%DateTime{} = datetime <- Timex.to_datetime(naive_datetime, zone) do
with {:ok, naive_datetime} <- Cocktail.Time.parse(time_string, @datetime_format),
%DateTime{} = datetime <- Cocktail.Time.to_datetime(naive_datetime, zone) do
{:ok, datetime}
end
end
Expand Down Expand Up @@ -421,7 +423,8 @@ defmodule Cocktail.Parser.ICalendar do

@spec parse_time(String.t()) :: {:ok, Time.t()} | {:error, :invalid_time_format}
defp parse_time(time_string) do
case Timex.parse(time_string, @time_format) do
case Cocktail.Time.parse(time_string, @time_format) do
{:ok, %Time{} = time} -> {:ok, time}
{:ok, datetime} -> {:ok, NaiveDateTime.to_time(datetime)}
_error -> {:error, :invalid_time_format}
end
Expand Down
2 changes: 1 addition & 1 deletion lib/cocktail/rule_state.ex
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ defmodule Cocktail.RuleState do
defp new_state(%__MODULE__{until: nil} = rule_state, time), do: %{rule_state | current_time: time}

defp new_state(%__MODULE__{until: until} = rule_state, time) do
if Timex.compare(until, time) == -1 do
if Cocktail.Time.compare(until, time) == :lt do
%{rule_state | current_time: nil}
else
%{rule_state | current_time: time}
Expand Down
6 changes: 3 additions & 3 deletions lib/cocktail/schedule.ex
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ defmodule Cocktail.Schedule do
@doc false
@spec set_end_time(t, Cocktail.time()) :: t
def set_end_time(%__MODULE__{start_time: start_time} = schedule, end_time) do
duration = Timex.diff(end_time, start_time, :seconds)
duration = Cocktail.Time.diff(end_time, start_time, :second)
%{schedule | duration: duration}
end

Expand Down Expand Up @@ -180,9 +180,9 @@ defmodule Cocktail.Schedule do
~N[2017-10-04 10:00:00]]

# using a DateTime with a time zone
iex> start_time = Timex.to_datetime(~N[2017-01-02 10:00:00], "America/Los_Angeles")
iex> start_time = Cocktail.Time.to_datetime(~N[2017-01-02 10:00:00], "America/Los_Angeles")
...> schedule = start_time |> new() |> add_recurrence_rule(:daily)
...> schedule |> occurrences() |> Enum.take(3) |> Enum.map(&Timex.format!(&1, "{ISO:Extended}"))
...> schedule |> occurrences() |> Enum.take(3) |> Enum.map(&DateTime.to_iso8601/1)
["2017-01-02T10:00:00-08:00",
"2017-01-03T10:00:00-08:00",
"2017-01-04T10:00:00-08:00"]
Expand Down
31 changes: 13 additions & 18 deletions lib/cocktail/schedule_state.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ defmodule Cocktail.ScheduleState do
@moduledoc false

alias Cocktail.{RuleState, Schedule, Span}
import Cocktail.Util

@type t :: %__MODULE__{
recurrence_rules: [RuleState.t()],
Expand All @@ -26,19 +25,20 @@ defmodule Cocktail.ScheduleState do

def new(%Schedule{} = schedule, current_time) do
current_time =
if Timex.compare(current_time, schedule.start_time) < 0,
if Cocktail.Time.compare(current_time, schedule.start_time) == :lt,
do: schedule.start_time,
else: current_time

recurrence_times_after_current_time =
schedule.recurrence_times
|> Enum.filter(&(Timex.compare(&1, current_time) >= 0))
|> Enum.sort(&(Timex.compare(&1, &2) <= 0))
|> Enum.filter(&(Cocktail.Time.compare(&1, current_time) in [:gt, :eq]))
|> Enum.sort(&(Cocktail.Time.compare(&1, &2) in [:lt, :eq]))

%__MODULE__{
recurrence_rules: schedule.recurrence_rules |> Enum.map(&RuleState.new/1),
recurrence_times: recurrence_times_after_current_time,
exception_times: schedule.exception_times |> Enum.uniq() |> Enum.sort(&(Timex.compare(&1, &2) <= 0)),
exception_times:
schedule.exception_times |> Enum.uniq() |> Enum.sort(&(Cocktail.Time.compare(&1, &2) in [:lt, :eq])),
start_time: schedule.start_time,
current_time: current_time,
duration: schedule.duration
Expand Down Expand Up @@ -92,7 +92,7 @@ defmodule Cocktail.ScheduleState do
defp next_time_from_recurrence_times([next_time | rest], nil), do: {next_time, rest}

defp next_time_from_recurrence_times([next_time | rest] = times, current_time) do
if Timex.compare(next_time, current_time) <= 0 do
if Cocktail.Time.compare(next_time, current_time) in [:lt, :eq] do
{next_time, rest}
else
{current_time, times}
Expand All @@ -105,15 +105,10 @@ defmodule Cocktail.ScheduleState do
defp apply_exception_time(exceptions, nil), do: {false, exceptions}

defp apply_exception_time([next_exception | rest] = exceptions, current_time) do
case Timex.compare(next_exception, current_time) do
0 ->
{true, rest}

-1 ->
apply_exception_time(rest, current_time)

_ ->
{false, exceptions}
case Cocktail.Time.compare(next_exception, current_time) do
:eq -> {true, rest}
:lt -> apply_exception_time(rest, current_time)
:gt -> {false, exceptions}
end
end

Expand All @@ -135,23 +130,23 @@ defmodule Cocktail.ScheduleState do
| recurrence_rules: rules,
recurrence_times: times,
exception_times: exceptions,
current_time: shift_time(time, seconds: 1)
current_time: Cocktail.Time.shift(time, 1, :second)
}

{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, shift_time(time, seconds: duration))
defp span_or_time(time, duration), do: Span.new(time, Cocktail.Time.shift(time, duration, :second))

@spec min_time_for_rules([RuleState.t()]) :: Cocktail.time() | nil
defp min_time_for_rules([]), do: nil
defp min_time_for_rules([rule]), do: rule.current_time

defp min_time_for_rules(rules) do
rules
|> Enum.min_by(&Timex.to_erl(&1.current_time))
|> Enum.min_by(&Cocktail.Time.to_erl(&1.current_time))
|> Map.get(:current_time)
end

Expand Down
30 changes: 15 additions & 15 deletions lib/cocktail/span.ex
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ defmodule Cocktail.Span do
def new(from, until), do: %__MODULE__{from: from, until: until}

@doc """
Uses `Timex.compare/2` to determine which span comes first.
Uses `Cocktail.Time.compare/2` to determine which span comes first.

Compares `from` first, then, if equal, compares `until`.

Expand All @@ -48,21 +48,21 @@ defmodule Cocktail.Span do
iex> span1 = new(~N[2017-01-01 06:00:00], ~N[2017-01-01 10:00:00])
...> span2 = new(~N[2017-01-01 06:00:00], ~N[2017-01-01 10:00:00])
...> compare(span1, span2)
0
:eq

iex> span1 = new(~N[2017-01-01 06:00:00], ~N[2017-01-01 10:00:00])
...> span2 = new(~N[2017-01-01 07:00:00], ~N[2017-01-01 12:00:00])
...> compare(span1, span2)
-1
:lt

iex> span1 = new(~N[2017-01-01 06:00:00], ~N[2017-01-01 10:00:00])
...> span2 = new(~N[2017-01-01 06:00:00], ~N[2017-01-01 07:00:00])
...> compare(span1, span2)
1
:gt
"""
@spec compare(span_compat, span_compat) :: Timex.Comparable.compare_result()
def compare(%{from: t, until: until1}, %{from: t, until: until2}), do: Timex.compare(until1, until2)
def compare(%{from: from1}, %{from: from2}), do: Timex.compare(from1, from2)
@spec compare(span_compat, span_compat) :: :lt | :eq | :gt
def compare(%{from: t, until: until1}, %{from: t, until: until2}), do: Cocktail.Time.compare(until1, until2)
def compare(%{from: from1}, %{from: from2}), do: Cocktail.Time.compare(from1, from2)

@doc """
Returns an `t:overlap_mode/0` to describe how the first span overlaps the second.
Expand Down Expand Up @@ -94,16 +94,16 @@ defmodule Cocktail.Span do

# credo:disable-for-next-line
def overlap_mode(%{from: from1, until: until1}, %{from: from2, until: until2}) do
from_comp = Timex.compare(from1, from2)
until_comp = Timex.compare(until1, until2)
from_comp = Cocktail.Time.compare(from1, from2)
until_comp = Cocktail.Time.compare(until1, until2)

cond do
from_comp <= 0 && until_comp >= 0 -> :contains
from_comp >= 0 && until_comp <= 0 -> :is_inside
Timex.compare(until1, from2) <= 0 -> :is_before
Timex.compare(from1, until2) >= 0 -> :is_after
from_comp < 0 && until_comp < 0 -> :overlaps_the_start_of
from_comp > 0 && until_comp > 0 -> :overlaps_the_end_of
from_comp in [:lt, :eq] && until_comp in [:gt, :eq] -> :contains
from_comp in [:gt, :eq] && until_comp in [:lt, :eq] -> :is_inside
Cocktail.Time.compare(until1, from2) in [:lt, :eq] -> :is_before
Cocktail.Time.compare(from1, until2) in [:gt, :eq] -> :is_after
from_comp == :lt && until_comp == :lt -> :overlaps_the_start_of
from_comp == :gt && until_comp == :gt -> :overlaps_the_end_of
end
end
end
Loading
Loading