Skip to content

Commit

Permalink
Merge pull request #3 from sk-t/feature/specific_divider
Browse files Browse the repository at this point in the history
Support Specific Dividers
  • Loading branch information
maennchen authored Dec 22, 2016
2 parents 7a5bbe3 + 01e539b commit 9587c23
Show file tree
Hide file tree
Showing 9 changed files with 69 additions and 59 deletions.
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

0 comments on commit 9587c23

Please sign in to comment.