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

ecrn_agent: fix monthly and weekly cron jobs parsing #45

Open
wants to merge 30 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
38caafc
move ecrn_test.erl to test/
Ledest Sep 3, 2016
494f281
remove internal.hrl
Ledest Jul 22, 2017
b74c44e
ecrn_agent: fix monthly and weekly cron jobs parsing
Ledest Jul 22, 2017
29ad658
erlcron.app: set VSN
Ledest Jun 16, 2015
5006395
rebar.config: remove deps
Ledest Jun 16, 2015
b75bea2
rebar.config: remove plugins
Ledest Jun 16, 2015
65d1eae
update .travis.yml
Ledest Jul 22, 2017
86de4b3
README.md: add travis-ci icon
Ledest Jul 22, 2017
9a6a08b
ecrn_agent: update until_days_from_now/5
Ledest Jul 26, 2017
218aa21
update .travis.yml
Ledest Dec 16, 2017
6d17ede
update .travis.yml
Ledest Dec 16, 2017
6b9d507
update .travis.yml
Ledest Mar 20, 2018
28b7bd5
improve specs
Ledest Mar 20, 2018
93382a5
.gitignore: add doc
Ledest Mar 20, 2018
6213a03
update .travis.yml
Ledest Mar 22, 2018
7f3bab2
0.3.3
Ledest Apr 2, 2018
8c007fc
erlcron.app: add ecrn_control, ecrn_cron_sup, ecrn_reg, ecrn_sup to r…
Ledest Apr 17, 2018
006c4b9
ecrn_agent: remove unneeded stacktrace displaying
Ledest Nov 23, 2018
d864910
update .travis.yml
Ledest Nov 23, 2018
a2047f7
0.3.4
Ledest Nov 23, 2018
31205c0
update .travis.yml
Ledest May 12, 2019
bb6b384
.travis.yml add 22.0 to otp_release
Ledest Jun 5, 2019
ce6cb82
remove .travis.yml
Ledest Jan 2, 2023
2afb99d
Update .gitignore
Ledest Jan 2, 2023
887ae33
Add .github/workflows/
Ledest Jan 2, 2023
531b6eb
rebar.config: add xref_checks
Ledest Jan 2, 2023
584e7b5
erlang.yml: update actions/checkout version
Ledest Jan 2, 2023
5f46175
erlang.yml: update version
Ledest May 20, 2023
c612aac
README: update build status badge
Ledest May 21, 2023
3dd3947
Update .gitignore
saleyn May 21, 2023
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
27 changes: 27 additions & 0 deletions .github/workflows/erlang.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: Erlang CI

on:
push:
branches:
- master
pull_request:
branches:
- master

jobs:
build:
runs-on: ubuntu-20.04
strategy:
matrix:
version: [26.0, 25.3, 25.2, 25.1, 25.0, 24.3, 24.2, 24.1, 24.0, 23.3, 23.2, 23.1, 23.0, 22, 21, 20, 19.3, 18.3]
rebar: [rebar, rebar3]
container:
image: erlang:${{ matrix.version }}
steps:
- uses: actions/checkout@v3
- name: Compile
run: ${{ matrix.rebar }} compile
- name: XRef
run: ${{ matrix.rebar }} xref
- name: Tests
run: ${{ matrix.rebar }} eunit
10 changes: 6 additions & 4 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
ebin/*.beam
ebin/*.app
.*
deps/*
/.rebar
/.eunit
/ebin
/deps
/doc
/_build
15 changes: 0 additions & 15 deletions .travis.yml

This file was deleted.

2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
Erlcron
=======

[![Build Status](https://github.com/Ledest/erlcron/actions/workflows/erlang.yml/badge.svg)](https://github.com/Ledest/erlcron/actions/workflows/erlang.yml/badge.svg)

Erlcron provides testable cron like functionality for Erlang
systems, with the ability to arbitrarily set the time and place along
with fastforwarding through tests. See erlcron.erl for more
Expand Down
12 changes: 8 additions & 4 deletions rebar.config
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
%% -*- mode: Erlang; fill-column: 80; comment-column: 75; -*-
%% Dependencies ================================================================
{deps, [{rebar_vsn_plugin, ".*",
{git, "https://github.com/erlware/rebar_vsn_plugin.git",
{tag, "master"}}}]}.
{deps, []}.

%% Rebar Plugins ==============================================================
{plugins, [rebar_vsn_plugin]}.
{plugins, []}.

%% Compiler Options ============================================================
{erl_opts,
Expand All @@ -18,3 +16,9 @@

{cover_enabled, true}.
{cover_print_enabled, true}.

{xref_checks, [undefined_function_calls,
undefined_functions,
locals_not_used,
deprecated_function_calls,
deprecated_functions]}.
103 changes: 33 additions & 70 deletions src/ecrn_agent.erl
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).

-include("internal.hrl").

-record(state, {job,
alarm_ref,
referenced_seconds,
Expand All @@ -45,30 +43,29 @@

%% @doc
%% Starts the server with the apropriate job and the appropriate ref
-spec start_link(erlcron:job_ref(), erlcron:job()) ->
ignore | {error, Reason::term()} | {ok, pid()}.
-spec start_link(JobRef::erlcron:job_ref(), Job::erlcron:job()) -> ignore | {error, term()} | {ok, pid()}.
start_link(JobRef, Job) ->
gen_server:start_link(?MODULE, [JobRef, Job], []).

-spec get_datetime(pid()) -> calendar:datetime().
-spec get_datetime(Pid::pid()) -> calendar:datetime().
get_datetime(Pid) ->
gen_server:call(Pid, get_datetime).

-spec cancel(pid()) -> ok.
-spec cancel(Pid::pid()) -> ok.
cancel(Pid) ->
gen_server:cast(Pid, shutdown).

-spec set_datetime(pid(), calendar:datetime(), erlcron:seconds()) -> ok.
-spec set_datetime(Pid::pid(), DateTime::calendar:datetime(), Actual::erlcron:seconds()) -> ok.
set_datetime(Pid, DateTime, Actual) ->
gen_server:cast(Pid, {set_datetime, DateTime, Actual}).

-spec recalculate(pid()) -> ok.
-spec recalculate(Pid::pid()) -> ok.
recalculate(Pid) ->
gen_server:cast(Pid, recalculate).

%% @doc
%% Validate that a run_when spec specified is correct.
-spec validate(erlcron:run_when()) -> valid | invalid.
-spec validate(Spec::erlcron:run_when()) -> valid | invalid.
validate(Spec) ->
State = #state{job=undefined,
alarm_ref=undefined},
Expand Down Expand Up @@ -164,9 +161,9 @@ do_job_run(State, {_, {M, F, A}})
proc_lib:spawn(M, F, A).

%% @doc Returns the current time, in seconds past midnight.
-spec current_time(state()) -> erlcron:seconds().
-spec current_time(State::state()) -> erlcron:seconds().
current_time(State) ->
{_, {H,M,S}} = current_date(State),
{_, {H, M, S}} = current_date(State),
S + M * 60 + H * 3600.

current_date(State = #state{fast_forward=true}) ->
Expand All @@ -178,8 +175,8 @@ current_date(State) ->

%% @doc Calculates the duration in milliseconds until the next time
%% a job is to be run.
-spec until_next_milliseconds(state(), erlcron:job()) ->
{ok, erlcron:seconds()} | {error, invalid_one_exception}.
-spec until_next_milliseconds(State::state(), Job::erlcron:job()) ->
{ok, erlcron:seconds()} | {error, invalid_one_exception}.
until_next_milliseconds(State, Job) ->
try
Millis = until_next_time(State, Job) * ?MILLISECONDS,
Expand All @@ -194,57 +191,30 @@ normalize_seconds(State, Seconds) ->
Value when Value >= 0 ->
Value;
_ ->
erlang:display(erlang:get_stacktrace()),
throw(invalid_once_exception)
end.

%% @doc Calculates the duration in seconds until the next time
%% a job is to be run.
-spec until_next_time(state(), {erlcron:run_when(), term()}) ->
erlcron:seconds().
-spec until_next_time(State::state(), {erlcron:run_when(), term()}) -> erlcron:seconds().
until_next_time(_State, {{once, Seconds}, _What}) when is_integer(Seconds) ->
Seconds;
until_next_time(State, {{once, {H, M, S}}, _What})
when is_integer(H), is_integer(M), is_integer(S) ->
until_next_time(State, {{once, {H, M, S}}, _What}) when is_integer(H), is_integer(M), is_integer(S) ->
normalize_seconds(State, S + (M + (H * 60)) * 60);
until_next_time(State, {{once, Period}, _What}) ->
until_next_time(State, {{once, Period}, _What}) ->
normalize_seconds(State, resolve_time(Period));
until_next_time(State, {{daily, Period}, _What}) ->
until_next_daytime(State, Period);
until_next_time(State, {{weekly, DoW, Period}, _What}) ->
OnDay = resolve_dow(DoW),
{Date, _} = current_date(State),
Today = calendar:day_of_the_week(Date),
case Today of
OnDay ->
until_next_daytime_or_days_from_now(State, Period, 7);
Today when Today < OnDay ->
until_days_from_now(State, Period, OnDay - Today);
Today when Today > OnDay ->
until_days_from_now(State, Period, (OnDay+7) - Today)
end;
{ThisDate, ThisTime} = current_date(State),
until_days_from_now(calendar:day_of_the_week(ThisDate), resolve_dow(DoW), ThisTime, Period, 7);
until_next_time(State, {{monthly, DoM, Period}, _What}) ->
{{ThisYear, ThisMonth, Today}, _} = current_date(State),
{NextYear, NextMonth} =
case ThisMonth of
12 ->
{ThisYear + 1, 1};
_ ->
{ThisYear, ThisMonth + 1}
end,
D1 = calendar:date_to_gregorian_days(ThisYear, ThisMonth, Today),
D2 = calendar:date_to_gregorian_days(NextYear, NextMonth, DoM),
Days = D2 - D1,
case Today of
DoM ->
until_next_daytime_or_days_from_now(State, Period, Days);
_ ->
until_days_from_now(State, Period, Days)
end.
{{ThisYear, ThisMonth, Today}, ThisTime} = current_date(State),
until_days_from_now(Today, DoM, ThisTime, Period, calendar:last_day_of_the_month(ThisYear, ThisMonth)).

%% @doc Calculates the duration in seconds until the next time this
%% period is to occur during the day.
-spec until_next_daytime(state(), erlcron:period()) -> erlcron:seconds().
-spec until_next_daytime(State::state(), Period::erlcron:period()) -> erlcron:seconds().
until_next_daytime(State, Period) ->
StartTime = first_time(Period),
EndTime = last_time(Period),
Expand All @@ -256,18 +226,17 @@ until_next_daytime(State, Period) ->
end.

%% @doc Calculates the last time in a given period.
-spec last_time(erlcron:period()) -> erlcron:seconds().
-spec last_time(Period::erlcron:period()) -> erlcron:seconds().
last_time(Period) ->
hd(lists:reverse(lists:sort(resolve_period(Period)))).


%% @doc Calculates the first time in a given period.
-spec first_time(erlcron:period()) -> erlcron:seconds().
-spec first_time(Period::erlcron:period()) -> erlcron:seconds().
first_time(Period) ->
hd(lists:sort(resolve_period(Period))).

%% @doc Calculates the first time in the given period after the given time.
-spec next_time(erlcron:period(), erlcron:seconds()) -> erlcron:seconds().
-spec next_time(Period::erlcron:period(), Time::erlcron:seconds()) -> erlcron:seconds().
next_time(Period, Time) ->
R = lists:sort(resolve_period(Period)),
lists:foldl(fun(X, A) ->
Expand Down Expand Up @@ -344,29 +313,23 @@ resolve_dow(sun) ->

%% @doc Calculates the duration in seconds until the given time occurs
%% tomorrow.
-spec until_tomorrow(state(), erlcron:seconds()) -> erlcron:seconds().
-spec until_tomorrow(State::state(), StartTime::erlcron:seconds()) -> erlcron:seconds().
until_tomorrow(State, StartTime) ->
(StartTime + 24*3600) - current_time(State).

%% @doc Calculates the duration in seconds until the given period
%% occurs several days from now.
-spec until_days_from_now(state(), erlcron:period(), integer()) ->
erlcron:seconds().
until_days_from_now(State, Period, Days) ->
Days * 24 * 3600 + until_next_daytime(State, Period).

%% @doc Calculates the duration in seconds until the given period
%% occurs, which may be today or several days from now.
-spec until_next_daytime_or_days_from_now(state(), erlcron:period(), integer()) ->
erlcron:seconds().
until_next_daytime_or_days_from_now(State, Period, Days) ->
CurrentTime = current_time(State),
case last_time(Period) of
T when T < CurrentTime ->
until_days_from_now(State, Period, Days-1);
_ ->
until_next_daytime(State, Period)
end.
-spec until_days_from_now(Today::1..7|1..31, Day::1..7|1..31, ThisTime::calendar:time(),
Period::erlcron:period(), Days::7|28..31) ->
erlcron:seconds().
until_days_from_now(Today, Day, ThisTime, Period, Days) ->
ThisSeconds = calendar:time_to_seconds(ThisTime),
PeriodSeconds = resolve_time(Period),
if
Today =:= Day, ThisSeconds < PeriodSeconds -> PeriodSeconds;
Today < Day -> (Day - Today) * (24 * 3600) + PeriodSeconds;
true -> (Days + Day - Today) * (24 * 3600) + PeriodSeconds
end - ThisSeconds.

set_internal_time(State, RefDate, CurrentSeconds) ->
NewSeconds = calendar:datetime_to_gregorian_seconds(RefDate),
Expand Down
14 changes: 6 additions & 8 deletions src/ecrn_control.erl
Original file line number Diff line number Diff line change
Expand Up @@ -22,20 +22,18 @@

-define(SERVER, ?MODULE).

-include("internal.hrl").

-record(state, {reference_datetime :: calendar:datetime(),
datetime_at_reference :: erlcron:seconds()}).

%%%===================================================================
%%% API
%%%===================================================================

-spec start_link() -> {ok, pid()} | ignore | {error, Error::term()}.
-spec start_link() -> {ok, pid()} | ignore | {error, term()}.
start_link() ->
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).

-spec cancel(erlcron:job_ref()) -> ok | undefined.
-spec cancel(AlarmRef::erlcron:job_ref()) -> ok | undefined.
cancel(AlarmRef) ->
gen_server:call(?SERVER, {cancel, AlarmRef}).

Expand All @@ -44,13 +42,13 @@ datetime() ->
gen_server:call(?SERVER, get_datetime).

%% @doc sets the date-time for the erlcron
-spec set_datetime(calendar:datetime()) -> ok.
set_datetime(DateTime={_,_}) ->
-spec set_datetime(DateTime::calendar:datetime()) -> ok.
set_datetime({_, _} = DateTime) ->
gen_server:call(?SERVER, {set_datetime, DateTime}, infinity).

%% @doc sets the date-time with the erlcron on all nodes
-spec multi_set_datetime([node()], calendar:datetime()) -> ok.
multi_set_datetime(Nodes, DateTime={_,_}) ->
-spec multi_set_datetime(Nodes::[node()], DateTime::calendar:datetime()) -> ok.
multi_set_datetime(Nodes, {_, _} = DateTime) ->
gen_server:multi_call(Nodes, ?SERVER, {set_datetime, DateTime}).

%%%===================================================================
Expand Down
4 changes: 2 additions & 2 deletions src/ecrn_cron_sup.erl
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,14 @@
%%% API functions
%%%===================================================================

-spec start_link() -> {ok, pid()} | ignore | {error, Error::term()}.
-spec start_link() -> {ok, pid()} | ignore | {error, term()}.
start_link() ->
supervisor:start_link({local, ?SERVER}, ?MODULE, []).


%% @doc
%% Add a chron job to be supervised
-spec add_job(erlcron:job_ref(), erlcron:job()) -> erlcron:job_ref().
-spec add_job(JobRef::erlcron:job_ref(), Task::erlcron:job()) -> erlcron:job_ref().
add_job(JobRef, Task) ->
{ok, _} = supervisor:start_child(?SERVER, [JobRef, Task]),
JobRef.
Expand Down
8 changes: 4 additions & 4 deletions src/ecrn_reg.erl
Original file line number Diff line number Diff line change
Expand Up @@ -31,29 +31,29 @@
%%% API
%%%===================================================================

-spec start_link() -> {ok, Pid::pid()} | ignore | {error, Error::term()}.
-spec start_link() -> {ok, pid()} | ignore | {error, term()}.
start_link() ->
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).

%% @doc
%% Register an arbitrary value with the system, under a set of keys
-spec register(term() | [term()], term()) -> ok | {discarded_keys, [term()]}.
-spec register(Keys::term()|[term()], Body::term()) -> ok | {discarded_keys, [term()]}.
register(Keys, Body) when is_list(Keys) ->
gen_server:call(?SERVER, {register, Keys, Body});
register(Key, Body) ->
gen_server:call(?SERVER, {register, [Key], Body}).

%% @doc
%% Remove the value registered under a que or set of keys
-spec unregister(term() | [term()]) -> ok.
-spec unregister(Keys::term()|[term()]) -> ok.
unregister(Keys) when is_list(Keys) ->
gen_server:call(?SERVER, {unregister, Keys});
unregister(Key) ->
gen_server:call(?SERVER, {unregister, [Key]}).

%% @doc
%% Get a value buy key.
-spec get(term()) -> {ok, term()} | undefined.
-spec get(Keys::term()) -> {ok, term()} | undefined.
get(Key) ->
gen_server:call(?SERVER, {get, Key}).

Expand Down
2 changes: 1 addition & 1 deletion src/ecrn_sup.erl
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
%%% API functions
%%%===================================================================

-spec start_link() -> {ok, Pid::term()} | ignore | {error, Error::term()}.
-spec start_link() -> {ok, term()} | ignore | {error, term()}.
start_link() ->
supervisor:start_link({local, ?SERVER}, ?MODULE, []).

Expand Down
4 changes: 2 additions & 2 deletions src/erlcron.app.src
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{application,erlcron,
[{description,"Erlang Implementation of cron"},
{vsn,"semver"},
{vsn,"0.3.4"},
{modules,[]},
{registered,[ecrn_agent]},
{registered,[ecrn_agent,ecrn_control,ecrn_cron_sup,ecrn_reg,ecrn_sup]},
{applications,[kernel,stdlib]},
{mod,{ecrn_app,[]}}]}.
Loading