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

Support Specific Dividers #3

Merged
merged 1 commit into from
Dec 22, 2016
Merged
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 README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ iex> Crontab.CronFormatParser.parse "fooo"
```elixir
iex> Crontab.CronFormatWriter.write %Crontab.CronInterval{}
"* * * * * *"
iex> Crontab.CronFormatWriter.write %Crontab.CronInterval{minute: [9, {:-, 4, 6}, {:/, 9}]}
iex> Crontab.CronFormatWriter.write %Crontab.CronInterval{minute: [9, {:-, 4, 6}, {:/, :*, 9}]}
"9,4-6,*/9 * * * * *"
```

Expand All @@ -64,6 +64,6 @@ false
iex> Crontab.CronScheduler.get_next_run_date(%Crontab.CronInterval{}, ~N[2002-01-13 23:00:07])
{:ok, ~N[2002-01-13 23:00:00]}

iex> Crontab.CronScheduler.get_next_run_date(%Crontab.CronInterval{year: [{:/, 9}]}, ~N[2002-01-13 23:00:07])
iex> Crontab.CronScheduler.get_next_run_date(%Crontab.CronInterval{year: [{:/, :*, 9}]}, ~N[2002-01-13 23:00:07])
{:ok, ~N[2007-01-01 00:00:00]}
```
22 changes: 15 additions & 7 deletions lib/crontab/cron_date_checker.ex
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,21 @@ defmodule Crontab.CronDateChecker do
Check a Crontab.CronInterval against a given date.

### Examples
iex> Crontab.CronDateChecker.matches_date :hour, [{:"/", 4}, 7], ~N[2004-04-16 04:07:08]
iex> Crontab.CronDateChecker.matches_date :hour, [{:"/", :*, 4}, 7], ~N[2004-04-16 04:07:08]
true

iex> Crontab.CronDateChecker.matches_date :hour, [8], ~N[2004-04-16 04:07:08]
false

iex> Crontab.CronDateChecker.matches_date %Crontab.CronInterval{minute: [{:"/", 8}]}, ~N[2004-04-16 04:08:08]
iex> Crontab.CronDateChecker.matches_date %Crontab.CronInterval{minute: [{:"/", :*, 8}]}, ~N[2004-04-16 04:08:08]
true

iex> Crontab.CronDateChecker.matches_date %Crontab.CronInterval{minute: [{:"/", 9}]}, ~N[2004-04-16 04:07:08]
iex> Crontab.CronDateChecker.matches_date %Crontab.CronInterval{minute: [{:"/", :*, 9}]}, ~N[2004-04-16 04:07:08]
false
"""
def matches_date(_, [:* | _], _), do: true
def matches_date(_, [], _), do: false
def matches_date(interval, [condition = {:/, _} | tail], execution_date) do
def matches_date(interval, [condition = {:/, _, _} | tail], execution_date) do
values = get_interval_value(interval, execution_date)
if matches_specific_date(interval, values, condition) do
true
Expand Down Expand Up @@ -58,18 +58,26 @@ defmodule Crontab.CronDateChecker do
end

defp matches_specific_date(_, [], _), do: false
defp matches_specific_date(_, _, :*), do: true
defp matches_specific_date(interval, [head_value | tail_values], condition = {:-, from, to}) do
cond do
from > to && (head_value >= from || head_value <= to) -> true
from <= to && head_value >= from && head_value <= to -> true
true -> matches_specific_date(interval, tail_values, condition)
end
end
defp matches_specific_date(:weekday, [0 | tail_values], condition = {:/, _}) do
defp matches_specific_date(:weekday, [0 | tail_values], condition = {:/, _, _}) do
matches_specific_date(:weekday, tail_values, condition)
end
defp matches_specific_date(interval, [head_value | tail_values], condition = {:/, divider}) do
if rem(head_value, divider) == 0 do
defp matches_specific_date(interval, values = [head_value | tail_values], condition = {:/, base = {:-, from, _}, divider}) do
if matches_specific_date(interval, values, base) && rem(head_value - from, divider) == 0 do
true
else
matches_specific_date(interval, tail_values, condition)
end
end
defp matches_specific_date(interval, values = [head_value | tail_values], condition = {:/, base, divider}) do
if matches_specific_date(interval, values, base) && rem(head_value, divider) == 0 do
true
else
matches_specific_date(interval, tail_values, condition)
Expand Down
24 changes: 13 additions & 11 deletions lib/crontab/cron_format_parser.ex
Original file line number Diff line number Diff line change
Expand Up @@ -92,18 +92,11 @@ defmodule Crontab.CronFormatParser do
end

defp tokenize(_, "*"), do: {:ok, :*}
defp tokenize(_, "*/" <> divider) do
case Integer.parse(divider, 10) do
:error -> "Divider " <> divider <> " is not numeric."
{number, _} -> {:ok, {:"/", number}}
end
end
#defp tokenize(_, min <> "-" <> max), do: {:ok, {:-, min, max}}
defp tokenize(interval, other) do
if Regex.match?(~r/^.+-.+$/, other) do
tokenize interval, :-, other
else
tokenize interval, :single_value, other
cond do
String.contains?(other, "/") -> tokenize interval, :complex_divider, other
Regex.match?(~r/^.+-.+$/, other) -> tokenize interval, :-, other
true -> tokenize interval, :single_value, other
end
end
defp tokenize(interval, :-, whole_string) do
Expand All @@ -117,6 +110,15 @@ defmodule Crontab.CronFormatParser do
defp tokenize(interval, :single_value, value) do
clean_value(interval, value)
end
defp tokenize(interval, :complex_divider, value) do
[base, divider] = String.split value, "/"

case {tokenize(interval, base), Integer.parse(divider, 10)} do
{{:ok, clean_base}, {clean_divider, _}} -> {:ok, {:/, clean_base, clean_divider}}
{_, :error} -> {:error, "Can't parse " <> value <> " as interval " <> Atom.to_string(interval) <> "."}
{error = {:error}, _} -> error
end
end

defp clean_value(:weekday, value) do
case {Map.fetch(@weekday_values, String.to_atom(String.upcase(value))), Integer.parse(value, 10)} do
Expand Down
4 changes: 2 additions & 2 deletions lib/crontab/cron_format_writer.ex
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ defmodule Crontab.CronFormatWriter do
### Examples
iex> Crontab.CronFormatWriter.write %Crontab.CronInterval{}
"* * * * * *"
iex> Crontab.CronFormatWriter.write %Crontab.CronInterval{minute: [9, {:-, 4, 6}, {:/, 9}]}
iex> Crontab.CronFormatWriter.write %Crontab.CronInterval{minute: [9, {:-, 4, 6}, {:/, :*, 9}]}
"9,4-6,*/9 * * * * *"
"""
def write(cron_interval = %Crontab.CronInterval{}) do
Expand All @@ -30,6 +30,6 @@ defmodule Crontab.CronFormatWriter do
end
def write(:*), do: "*"
def write(number) when is_number(number), do: Integer.to_string(number)
def write({:/, divider}), do: "*/" <> Integer.to_string(divider)
def write({:/, base, divider}), do: write(base) <> "/" <> Integer.to_string(divider)
def write({:-, min, max}), do: Integer.to_string(min) <> "-" <> Integer.to_string(max)
end
2 changes: 1 addition & 1 deletion lib/crontab/cron_scheduler.ex
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ defmodule Crontab.CronScheduler do
iex> Crontab.CronScheduler.get_next_run_date(%Crontab.CronInterval{}, ~N[2002-01-13 23:00:07])
{:ok, ~N[2002-01-13 23:00:00]}

iex> Crontab.CronScheduler.get_next_run_date(%Crontab.CronInterval{year: [{:/, 9}]}, ~N[2002-01-13 23:00:07])
iex> Crontab.CronScheduler.get_next_run_date(%Crontab.CronInterval{year: [{:/, :*, 9}]}, ~N[2002-01-13 23:00:07])
{:ok, ~N[2007-01-01 00:00:00]}
"""
def get_next_run_date(cron_interval = %Crontab.CronInterval{}, date) do
Expand Down
24 changes: 12 additions & 12 deletions test/crontab/cron_date_checker_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,21 @@ defmodule Crontab.CronDateCheckerTest do

test "2004-04-16 04:04:08 matches */4 */4 */4 */5 */4" do
base_date = ~N[2004-04-16 04:04:08]
assert matches_date(:minute, [{:"/", 4}], base_date) == true
assert matches_date(:hour, [{:"/", 4}], base_date) == true
assert matches_date(:day, [{:"/", 4}], base_date) == true
assert matches_date(:month, [{:"/", 4}], base_date) == true
assert matches_date(:weekday, [{:"/", 5}], base_date) == true
assert matches_date(:year, [{:"/", 4}], base_date) == true
assert matches_date(:minute, [{:"/", :*, 4}], base_date) == true
assert matches_date(:hour, [{:"/", :*, 4}], base_date) == true
assert matches_date(:day, [{:"/", :*, 4}], base_date) == true
assert matches_date(:month, [{:"/", :*, 4}], base_date) == true
assert matches_date(:weekday, [{:"/", :*, 5}], base_date) == true
assert matches_date(:year, [{:"/", :*, 4}], base_date) == true
end

test "2003-04-17 04:04:08 doesn't match */3 */3 */3 */3 */3" do
base_date = ~N[2003-04-17 04:04:08]
assert matches_date(:minute, [{:"/", 3}], base_date) == false
assert matches_date(:hour, [{:"/", 3}], base_date) == false
assert matches_date(:day, [{:"/", 3}], base_date) == false
assert matches_date(:month, [{:"/", 3}], base_date) == false
assert matches_date(:weekday, [{:"/", 3}], base_date) == false
assert matches_date(:year, [{:"/", 3}], base_date) == false
assert matches_date(:minute, [{:"/", :*, 3}], base_date) == false
assert matches_date(:hour, [{:"/", :*, 3}], base_date) == false
assert matches_date(:day, [{:"/", :*, 3}], base_date) == false
assert matches_date(:month, [{:"/", :*, 3}], base_date) == false
assert matches_date(:weekday, [{:"/", :*, 3}], base_date) == false
assert matches_date(:year, [{:"/", :*, 3}], base_date) == false
end
end
4 changes: 2 additions & 2 deletions test/crontab/cron_format_parser_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ defmodule Crontab.CronFormatParserTest do
assert parse("*") == {:ok, %Crontab.CronInterval{minute: [:*], hour: [:*], day: [:*], month: [:*], weekday: [:*], year: [:*]}}
end

test "parse \"*/4,9,1-10\" gives [{:\"/\", 4}, 9, {:\"-\", 1, 10}]" do
assert parse("*/4,9,1-10") == {:ok, %Crontab.CronInterval{minute: [{:"/", 4}, 9, {:-, 1, 10}], hour: [:*], day: [:*], month: [:*], weekday: [:*], year: [:*]}}
test "parse \"*/4,9,1-10\" gives [{:\"/\", :*, 4}, 9, {:\"-\", 1, 10}]" do
assert parse("*/4,9,1-10") == {:ok, %Crontab.CronInterval{minute: [{:"/", :*, 4}, 9, {:-, 1, 10}], hour: [:*], day: [:*], month: [:*], weekday: [:*], year: [:*]}}
end

test "parse \"*/4,9,JAN-DEC\" gives error" do
Expand Down
8 changes: 4 additions & 4 deletions test/crontab/cron_scheduler_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,19 @@ defmodule Crontab.CronSchedulerTest do
import Crontab.CronScheduler

test "check cron expression for year" do
assert get_next_run_date(%Crontab.CronInterval{year: [{:/, 9}]}, ~N[2002-01-13 23:00:07]) == {:ok, ~N[2007-01-01 00:00:00]}
assert get_next_run_date(%Crontab.CronInterval{year: [{:/, :*, 9}]}, ~N[2002-01-13 23:00:07]) == {:ok, ~N[2007-01-01 00:00:00]}
end

test "check cron expression for weekday" do
assert get_next_run_date(%Crontab.CronInterval{weekday: [{:/, 3}]}, ~N[2002-01-13 23:00:07]) == {:ok, ~N[2002-01-16 00:00:00]}
assert get_next_run_date(%Crontab.CronInterval{weekday: [{:/, :*, 3}]}, ~N[2002-01-13 23:00:07]) == {:ok, ~N[2002-01-16 00:00:00]}
end

test "check cron expression for month" do
assert get_next_run_date(%Crontab.CronInterval{month: [{:/, 9}]}, ~N[2002-01-13 23:00:07]) == {:ok, ~N[2002-09-01 00:00:00]}
assert get_next_run_date(%Crontab.CronInterval{month: [{:/, :*, 9}]}, ~N[2002-01-13 23:00:07]) == {:ok, ~N[2002-09-01 00:00:00]}
end

test "check cron expression for day" do
assert get_next_run_date(%Crontab.CronInterval{day: [{:/, 16}]}, ~N[2002-01-13 23:00:07]) == {:ok, ~N[2002-01-16 00:00:00]}
assert get_next_run_date(%Crontab.CronInterval{day: [{:/, :*, 16}]}, ~N[2002-01-13 23:00:07]) == {:ok, ~N[2002-01-16 00:00:00]}
end

test "check cron expression for hour" do
Expand Down
36 changes: 18 additions & 18 deletions test/crontab/functional_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,21 @@ defmodule Crontab.FunctionalTest do
{"*/2 */2 * * *", ~N[2015-08-10 21:47:27], ~N[2015-08-10 22:00:00], false},
{"* * * * *", ~N[2015-08-10 21:50:37], ~N[2015-08-10 21:50:00], true},
{"* 20,21,22 * * *", ~N[2015-08-10 21:50:00], ~N[2015-08-10 21:50:00], true},
# Handles CSV values
# Handles CSV values
{"* 20,22 * * *", ~N[2015-08-10 21:50:00], ~N[2015-08-10 22:00:00], false},
# CSV values can be complex
# CSV values can be complex
{"* 5,21-22 * * *", ~N[2015-08-10 21:50:00], ~N[2015-08-10 21:50:00], true},
{"7-9 * */9 * *", ~N[2015-08-10 22:02:33], ~N[2015-08-18 00:07:00], false},
# 15th minute, of the second hour, every 15 days, in January, every Friday
# 15th minute, of the second hour, every 15 days, in January, every Friday
{"1 * * * 7", ~N[2015-08-10 21:47:27], ~N[2015-08-16 00:01:00], false},
# Test with exact times
# Test with exact times
{"47 21 * * *", ~N[2015-08-10 21:47:30], ~N[2015-08-10 21:47:00], true},
# Test Day of the week (issue #1)
# According cron implementation, 0|7 = sunday, 1 => monday, etc
# Test Day of the week (issue #1)
# According cron implementation, 0|7 = sunday, 1 => monday, etc
{"* * * * 0", ~N[2011-06-15 23:09:00], ~N[2011-06-19 00:00:00], false},
{"* * * * 7", ~N[2011-06-15 23:09:00], ~N[2011-06-19 00:00:00], false},
{"* * * * 1", ~N[2011-06-15 23:09:00], ~N[2011-06-20 00:00:00], false},
# Should return the sunday date as 7 equals 0
# Should return the sunday date as 7 equals 0
{"0 0 * * MON,SUN", ~N[2011-06-15 23:09:00], ~N[2011-06-19 00:00:00], false},
{"0 0 * * 1,7", ~N[2011-06-15 23:09:00], ~N[2011-06-19 00:00:00], false},
{"0 0 * * 0-4", ~N[2011-06-15 23:09:00], ~N[2011-06-16 00:00:00], false},
Expand All @@ -28,22 +28,22 @@ defmodule Crontab.FunctionalTest do
{"0 0 * * 7-3", ~N[2011-06-15 23:09:00], ~N[2011-06-19 00:00:00], false},
{"0 0 * * 3-7", ~N[2011-06-15 23:09:00], ~N[2011-06-16 00:00:00], false},
{"0 0 * * 3-7", ~N[2011-06-18 23:09:00], ~N[2011-06-19 00:00:00], false},
# Test lists of values and ranges (Abhoryo)
# Test lists of values and ranges (Abhoryo)
{"0 0 * * 2-7", ~N[2011-06-20 23:09:00], ~N[2011-06-21 00:00:00], false},
{"0 0 * * 0,2-6", ~N[2011-06-20 23:09:00], ~N[2011-06-21 00:00:00], false},
{"0 0 * * 2-7", ~N[2011-06-18 23:09:00], ~N[2011-06-19 00:00:00], false},
{"0 0 * * 4-7", ~N[2011-07-19 00:00:00], ~N[2011-07-21 00:00:00], false},
# Test increments of ranges
# Test increments of ranges
{"0-12/4 * * * *", ~N[2011-06-20 12:04:00], ~N[2011-06-20 12:04:00], true},
#{"4-59/2 * * * *", ~N[2011-06-20 12:04:00], ~N[2011-06-20 12:04:00], true},
#{"4-59/2 * * * *", ~N[2011-06-20 12:06:00], ~N[2011-06-20 12:06:00], true},
#{"4-59/3 * * * *", ~N[2011-06-20 12:06:00], ~N[2011-06-20 12:07:00], false},
# Test Day of the Week and the Day of the Month (issue #1)
{"4-59/2 * * * *", ~N[2011-06-20 12:04:00], ~N[2011-06-20 12:04:00], true},
{"4-59/2 * * * *", ~N[2011-06-20 12:06:00], ~N[2011-06-20 12:06:00], true},
{"4-59/3 * * * *", ~N[2011-06-20 12:06:00], ~N[2011-06-20 12:07:00], false},
# Test Day of the Week and the Day of the Month (issue #1)
{"0 0 1 1 0", ~N[2011-06-15 23:09:00], ~N[2012-01-01 00:00:00], false},
{"0 0 1 JAN 0", ~N[2011-06-15 23:09:00], ~N[2012-01-01 00:00:00], false},
{"0 0 1 * 0", ~N[2011-06-15 23:09:00], ~N[2012-01-01 00:00:00], false},
#{"0 0 L * *", ~N[2011-07-15 00:00:00], ~N[2011-07-31 00:00:00], false},
# Test the W day of the week modifier for day of the month field
# {"0 0 L * *", ~N[2011-07-15 00:00:00], ~N[2011-07-31 00:00:00], false},
# Test the W day of the week modifier for day of the month field
# {"0 0 2W * *", ~N[2011-07-01 00:00:00], ~N[2011-07-01 00:00:00], true},
# {"0 0 1W * *", ~N[2011-05-01 00:00:00], ~N[2011-05-02 00:00:00], false},
# {"0 0 1W * *", ~N[2011-07-01 00:00:00], ~N[2011-07-01 00:00:00], true},
Expand All @@ -52,16 +52,16 @@ defmodule Crontab.FunctionalTest do
# {"0 0 28W * *", ~N[2011-07-01 00:00:00], ~N[2011-07-28 00:00:00], false},
# {"0 0 30W * *", ~N[2011-07-01 00:00:00], ~N[2011-07-29 00:00:00], false},
# {"0 0 31W * *", ~N[2011-07-01 00:00:00], ~N[2011-07-29 00:00:00], false},
# Test the year field
# Test the year field
{"* * * * * 2012", ~N[2011-05-01 00:00:00], ~N[2012-01-01 00:00:00], false},
# Test the last weekday of a month
# Test the last weekday of a month
# {"* * * * 5L", ~N[2011-07-01 00:00:00], ~N[2011-07-29 00:00:00], false},
# {"* * * * 6L", ~N[2011-07-01 00:00:00], ~N[2011-07-30 00:00:00], false},
# {"* * * * 7L", ~N[2011-07-01 00:00:00], ~N[2011-07-31 00:00:00], false},
# {"* * * * 1L", ~N[2011-07-24 00:00:00], ~N[2011-07-25 00:00:00], false},
# {"* * * * TUEL", ~N[2011-07-24 00:00:00], ~N[2011-07-26 00:00:00], false},
# {"* * * 1 5L", ~N[2011-12-25 00:00:00], ~N[2012-01-27 00:00:00], false},
# # Test the hash symbol for the nth weekday of a given month
# # Test the hash symbol for the nth weekday of a given month
# {"* * * * 5#2", ~N[2011-07-01 00:00:00], ~N[2011-07-08 00:00:00], false},
# {"* * * * 5#1", ~N[2011-07-01 00:00:00], ~N[2011-07-01 00:00:00], true},
# {"* * * * 3#4", ~N[2011-07-01 00:00:00], ~N[2011-07-27 00:00:00], false},
Expand Down