Commits

Cat's Eye Technologies  committed c07aad5

Initial import of crone version 1.0 revision 2004.0301 sources.

  • Participants
  • Tags rel_1_0

Comments (0)

Files changed (6)

File doc/crone.html

+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html>
+<head>
+<title>Module crone</title>
+
+</head>
+<body bgcolor="white">
+<h1>Module crone</h1>
+<ul><li>
+<a href="#index">Function index</a></li><li>
+<a href="#exported">Exported functions</a></li><li>
+<a href="#types">Data Types</a></li><li>
+<a href="#internal">Documented Internal Functions</a></li></ul>
+
+<h2>Description</h2>
+Simple task scheduler for Erlang/OTP.  This application aims to
+  one day be a viable replacement for <code>cron</code>.
+ 
+  <p><code>crone</code> is a simple utility which schedules tasks to be
+  run periodically at given times.</p>
+ 
+  <p><code>crone</code> differs from <code>cron</code> in the following
+  ways:</p>
+ 
+  <ul>
+  <li><code>crone</code> does not support multiple users.
+  Of course, individual users may run their own copies of <code>crone</code>
+  as desired.</li>
+ 
+  <li>No configuration files are used.  <code>crone</code> is simply
+  started with a list of task descriptions.  These could easily be stored
+  in a file to be read with <code>file:consult/1</code> if desired.</li>
+ 
+  <li><code>crone</code> launches, not system executables, but Erlang
+  functions.  Of course, system programs can be launched with the Erlang
+  function <code>os:cmd/1</code>.</li>
+ 
+  <li>The synax of a task description is quite different from
+  <code>crontab</code>.  It is (in this author's opinion) easier to read
+  and is much more in keeping with the Erlang tradition.  It is not
+  quite as expressive as <code>cron</code> but this can be compensated
+  for by adding multiple tasks.</li>
+ 
+  <li>No output is logged or mailed to anyone.  If you want output to
+  be logged or mailed, you must explicitly specify that as part of the
+  task.</li>
+ 
+  <li><code>crone</code> does not poll the system on a minute-by-minute
+  basis like <code>cron</code> does.  Each task is assigned to a single
+  (Erlang) process.  The time until it is to run next is calculated,
+  and the process sleeps for exactly that long.</li>
+ 
+  <li>Unlike <code>cron</code>'s one-minute resolution, <code>crone</code>
+  has a 2-second resolution (actually 1 second, but after running the
+  task, the process waits an extra second to avoid accidentally running it
+  more than once.)</li>
+ 
+  <li>Because it does not wake up every minute, and because it does not
+  have a fixed configuration file, <code>crone</code> must be stopped and
+  restarted if the user wishes to change the scheduled tasks.</li>
+ 
+  <li><code>crone</code> does not handle Daylight Savings Time (or other
+  cases when the system clock is altered) gracefully, and it is recommended
+  that it be stopped and restarted on such occasions.</li>
+  </ul>
+ 
+
+<h2><a name="index">Function Index</a></h2>
+
+<table width="100%" border="1"><tr><th colspan="2" align="left">Exported Functions</th></tr>
+<tr><td><a href="#format_time-1">format_time/1</a></td><td>Returns human-readable string from seconds past midnight.</td></tr>
+<tr><td><a href="#loop_task-1">loop_task/1</a></td><td>Used by <code>start/1</code> to wait until the next time
+  each task is scheduled to run, run it, and repeat.</td></tr>
+<tr><td><a href="#start-1">start/1</a></td><td>Starts <code>crone</code>, spawning a process for each task.</td></tr>
+<tr><td><a href="#stop-1">stop/1</a></td><td>Stops all monitoring processes started by <code>crone</code>.</td></tr>
+<tr><th colspan="2" align="left">Internal Documented Functions</th></tr>
+<tr><td><a href="#current_time-0">current_time/0</a></td><td>Returns the current time, in seconds past midnight.</td></tr>
+<tr><td><a href="#first_time-1">first_time/1</a></td><td>Calculates the first time in a given period.</td></tr>
+<tr><td><a href="#last_time-1">last_time/1</a></td><td>Calculates the last time in a given period.</td></tr>
+<tr><td><a href="#next_time-2">next_time/2</a></td><td>Calculates the first time in the given period after the given time.</td></tr>
+<tr><td><a href="#resolve_dow-1">resolve_dow/1</a></td><td>Returns the number of the given day of the week.</td></tr>
+<tr><td><a href="#resolve_dur-1">resolve_dur/1</a></td><td>Returns seconds for a given duration.</td></tr>
+<tr><td><a href="#resolve_period-1">resolve_period/1</a></td><td>Returns a list of times given a periodic specification.</td></tr>
+<tr><td><a href="#resolve_time-1">resolve_time/1</a></td><td>Returns seconds past midnight for a given time.</td></tr>
+<tr><td><a href="#run_task-1">run_task/1</a></td><td>Spawns a process to accomplish the given task.</td></tr>
+<tr><td><a href="#until_days_from_now-2">until_days_from_now/2</a></td><td>Calculates the duration in seconds until the given period
+  occurs several days from now.</td></tr>
+<tr><td><a href="#until_next_daytime-1">until_next_daytime/1</a></td><td>Calculates the duration in seconds until the next time this
+  period is to occur during the day.</td></tr>
+<tr><td><a href="#until_next_daytime_or_days_from_now-2">until_next_daytime_or_days_from_now/2</a></td><td>Calculates the duration in seconds until the given period
+  occurs, which may be today or several days from now.</td></tr>
+<tr><td><a href="#until_next_time-1">until_next_time/1</a></td><td>Calculates the duration in seconds until the next time
+  a task is to be run.</td></tr>
+<tr><td><a href="#until_tomorrow-1">until_tomorrow/1</a></td><td>Calculates the duration in seconds until the given time occurs
+  tomorrow.</td></tr>
+</table>
+
+<h2><a name="types">Data Types</a></h2>
+
+<h3><a name="type-task">task()</a> = {<a href="#type-when">when()</a>, <a href="#type-mfa">mfa()</a>}</h3>
+
+
+
+<h3><a name="type-mfa">mfa()</a> = {<a href="#type-module">module()</a>, function(), <a href="#type-args">args()</a>}</h3>
+
+
+
+<h3><a name="type-when">when()</a> = {daily, <a href="#type-period">period()</a>} | {weekly, <a href="#type-dow">dow()</a>, <a href="#type-period">period()</a>} | {monthly, <a href="#type-dom">dom()</a>, <a href="#type-period">period()</a>}</h3>
+
+
+
+<h3><a name="type-dow">dow()</a> = mon | tue | wed | thu | fri | sat | sun</h3>
+
+
+
+<h3><a name="type-dom">dom()</a> = integer()</h3>
+
+
+
+<h3><a name="type-period">period()</a> = <a href="#type-time">time()</a> | [<a href="#type-time">time()</a>] | {every, <a href="#type-duration">duration()</a>, <a href="#type-constraint">constraint()</a>}</h3>
+
+
+
+<h3><a name="type-duration">duration()</a> = {integer(), hr | min | sec}</h3>
+
+
+
+<h3><a name="type-constraint">constraint()</a> = {between, <a href="#type-time">time()</a>, <a href="#type-time">time()</a>}</h3>
+
+
+
+<h3><a name="type-time">time()</a> = {integer(), am | pm} | {integer(), integer(), am | pm}</h3>
+
+
+
+<h2><a name="exported">Exported Functions</a></h2>
+
+<h3><a name="format_time-1">format_time/1</a></h3>
+
+<p><code>format_time(integer()) -> string()</code></p>
+<p>Returns human-readable string from seconds past midnight.</p>
+
+<h3><a name="loop_task-1">loop_task/1</a></h3>
+
+<p><code>loop_task(<a href="#type-task">task()</a>) -> never_returns</code></p>
+<p>Used by <code>start/1</code> to wait until the next time
+  each task is scheduled to run, run it, and repeat.</p>
+
+<h3><a name="start-1">start/1</a></h3>
+
+<p><code>start([<a href="#type-task">task()</a>]) -> [pid()]</code></p>
+<p>Starts <code>crone</code>, spawning a process for each task.</p>
+
+<h3><a name="stop-1">stop/1</a></h3>
+
+<p><code>stop([<a href="#type-task">task()</a>]) -> ok</code></p>
+<p>Stops all monitoring processes started by <code>crone</code>.</p>
+
+<h2><a name="internal">Documented Internal Functions</a></h2>
+
+<h3><a name="current_time-0">current_time/0</a></h3>
+
+<p><code>current_time() -> <a href="#type-seconds">seconds()</a><ul><li><a name="type-seconds">seconds()</a> = integer()</li></ul></code></p>
+<p>Returns the current time, in seconds past midnight.</p>
+
+<h3><a name="first_time-1">first_time/1</a></h3>
+
+<p><code>first_time(<a href="#type-period">period()</a>) -> <a href="#type-seconds">seconds()</a></code></p>
+<p>Calculates the first time in a given period.</p>
+
+<h3><a name="last_time-1">last_time/1</a></h3>
+
+<p><code>last_time(<a href="#type-period">period()</a>) -> <a href="#type-seconds">seconds()</a></code></p>
+<p>Calculates the last time in a given period.</p>
+
+<h3><a name="next_time-2">next_time/2</a></h3>
+
+<p><code>next_time(<a href="#type-period">period()</a>, <a href="#type-seconds">seconds()</a>) -> <a href="#type-seconds">seconds()</a></code></p>
+<p>Calculates the first time in the given period after the given time.</p>
+
+<h3><a name="resolve_dow-1">resolve_dow/1</a></h3>
+
+<p><code>resolve_dow(<a href="#type-dow">dow()</a>) -> integer()</code></p>
+<p>Returns the number of the given day of the week. See the calendar
+  module for day numbers.</p>
+
+<h3><a name="resolve_dur-1">resolve_dur/1</a></h3>
+
+<p><code>resolve_dur(<a href="#type-duration">duration()</a>) -> <a href="#type-seconds">seconds()</a></code></p>
+<p>Returns seconds for a given duration.</p>
+
+<h3><a name="resolve_period-1">resolve_period/1</a></h3>
+
+<p><code>resolve_period(<a href="#type-period">period()</a>) -> [<a href="#type-seconds">seconds()</a>]</code></p>
+<p>Returns a list of times given a periodic specification.</p>
+
+<h3><a name="resolve_time-1">resolve_time/1</a></h3>
+
+<p><code>resolve_time(<a href="#type-time">time()</a>) -> <a href="#type-seconds">seconds()</a></code></p>
+<p>Returns seconds past midnight for a given time.</p>
+
+<h3><a name="run_task-1">run_task/1</a></h3>
+
+<p><code>run_task(<a href="#type-task">task()</a>) -> pid()</code></p>
+<p>Spawns a process to accomplish the given task.</p>
+
+<h3><a name="until_days_from_now-2">until_days_from_now/2</a></h3>
+
+<p><code>until_days_from_now(<a href="#type-period">period()</a>, integer()) -> <a href="#type-seconds">seconds()</a></code></p>
+<p>Calculates the duration in seconds until the given period
+  occurs several days from now.</p>
+
+<h3><a name="until_next_daytime-1">until_next_daytime/1</a></h3>
+
+<p><code>until_next_daytime(<a href="#type-period">period()</a>) -> <a href="#type-seconds">seconds()</a></code></p>
+<p>Calculates the duration in seconds until the next time this
+  period is to occur during the day.</p>
+
+<h3><a name="until_next_daytime_or_days_from_now-2">until_next_daytime_or_days_from_now/2</a></h3>
+
+<p><code>until_next_daytime_or_days_from_now(<a href="#type-period">period()</a>, integer()) -> <a href="#type-seconds">seconds()</a></code></p>
+<p>Calculates the duration in seconds until the given period
+  occurs, which may be today or several days from now.</p>
+
+<h3><a name="until_next_time-1">until_next_time/1</a></h3>
+
+<p><code>until_next_time(<a href="#type-task">task()</a>) -> <a href="#type-seconds">seconds()</a></code></p>
+<p>Calculates the duration in seconds until the next time
+  a task is to be run.</p>
+
+<h3><a name="until_tomorrow-1">until_tomorrow/1</a></h3>
+
+<p><code>until_tomorrow(<a href="#type-seconds">seconds()</a>) -> <a href="#type-seconds">seconds()</a></code></p>
+<p>Calculates the duration in seconds until the given time occurs
+  tomorrow.</p></body>
+</html>

File doc/crone_test.html

+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html>
+<head>
+<title>Module crone_test</title>
+
+</head>
+<body bgcolor="white">
+<h1>Module crone_test</h1>
+<ul><li>
+<a href="#index">Function index</a></li><li>
+<a href="#exported">Exported functions</a></li></ul>
+
+<h2>Description</h2>
+Simple tests for <code>crone</code>.
+ 
+
+
+<h2><a name="index">Function Index</a></h2>
+
+<table width="100%" border="1"><tr><th colspan="2" align="left">Exported Functions</th></tr>
+<tr><td><a href="#start-0">start/0</a></td><td>Runs a simple <code>crone</code> test.</td></tr>
+</table>
+
+<h2><a name="exported">Exported Functions</a></h2>
+
+<h3><a name="start-0">start/0</a></h3>
+
+<p><code>start() -> [pid()]</code></p>
+<p>Runs a simple <code>crone</code> test.
+</p></body>
+</html>

File ebin/crone.beam

Binary file added.

File ebin/crone_test.beam

Binary file added.

File src/crone.erl

+%%% BEGIN crone.erl %%%
+%%%
+%%% crone - Task Scheduler for Erlang/OTP
+%%% Copyright (c)2002 Cat's Eye Technologies.  All rights reserved.
+%%%
+%%% Redistribution and use in source and binary forms, with or without
+%%% modification, are permitted provided that the following conditions
+%%% are met:
+%%%
+%%%   Redistributions of source code must retain the above copyright
+%%%   notice, this list of conditions and the following disclaimer.
+%%%
+%%%   Redistributions in binary form must reproduce the above copyright
+%%%   notice, this list of conditions and the following disclaimer in
+%%%   the documentation and/or other materials provided with the
+%%%   distribution.
+%%%
+%%%   Neither the name of Cat's Eye Technologies nor the names of its
+%%%   contributors may be used to endorse or promote products derived
+%%%   from this software without specific prior written permission.
+%%%
+%%% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+%%% CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+%%% INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+%%% MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+%%% DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
+%%% LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+%%% OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+%%% PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
+%%% OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+%%% ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+%%% OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+%%% OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+%%% POSSIBILITY OF SUCH DAMAGE. 
+
+%% @doc Simple task scheduler for Erlang/OTP.  This application aims to
+%% one day be a viable replacement for <code>cron</code>.
+%%
+%% <p><code>crone</code> is a simple utility which schedules tasks to be
+%% run periodically at given times.</p>
+%%
+%% <p><code>crone</code> differs from <code>cron</code> in the following
+%% ways:</p>
+%%
+%% <ul>
+%% <li><code>crone</code> does not support multiple users.
+%% Of course, individual users may run their own copies of <code>crone</code>
+%% as desired.</li>
+%%
+%% <li>No configuration files are used.  <code>crone</code> is simply
+%% started with a list of task descriptions.  These could easily be stored
+%% in a file to be read with <code>file:consult/1</code> if desired.</li>
+%%
+%% <li><code>crone</code> launches, not system executables, but Erlang
+%% functions.  Of course, system programs can be launched with the Erlang
+%% function <code>os:cmd/1</code>.</li>
+%%
+%% <li>The synax of a task description is quite different from
+%% <code>crontab</code>.  It is (in this author's opinion) easier to read
+%% and is much more in keeping with the Erlang tradition.  It is not
+%% quite as expressive as <code>cron</code> but this can be compensated
+%% for by adding multiple tasks.</li>
+%%
+%% <li>No output is logged or mailed to anyone.  If you want output to
+%% be logged or mailed, you must explicitly specify that as part of the
+%% task.</li>
+%%
+%% <li><code>crone</code> does not poll the system on a minute-by-minute
+%% basis like <code>cron</code> does.  Each task is assigned to a single
+%% (Erlang) process.  The time until it is to run next is calculated,
+%% and the process sleeps for exactly that long.</li>
+%%
+%% <li>Unlike <code>cron</code>'s one-minute resolution, <code>crone</code>
+%% has a 2-second resolution (actually 1 second, but after running the
+%% task, the process waits an extra second to avoid accidentally running it
+%% more than once.)</li>
+%%
+%% <li>Because it does not wake up every minute, and because it does not
+%% have a fixed configuration file, <code>crone</code> must be stopped and
+%% restarted if the user wishes to change the scheduled tasks.</li>
+%%
+%% <li><code>crone</code> does not handle Daylight Savings Time (or other
+%% cases when the system clock is altered) gracefully, and it is recommended
+%% that it be stopped and restarted on such occasions.</li>
+%% </ul>
+%%
+%% @type  task()       = {when(), mfa()}
+%% @type  mfa()        = {module(), function(), args()}
+%% @type  when()       = {daily, period()}
+%%                     | {weekly, dow(), period()} 
+%%                     | {monthly, dom(), period()}
+%% @type  dow()        = mon | tue | wed | thu | fri | sat | sun
+%% @type  dom()        = integer()
+%% @type  period()     = time() | [time()] | {every, duration(), constraint()}
+%% @type  duration()   = {integer(), hr | min | sec}
+%% @type  constraint() = {between, time(), time()}
+%% @type  time()       = {integer(), am | pm} | {integer(), integer(), am | pm}
+%%
+%% @end
+
+-module(crone).
+-vsn('2002.0704').
+-author('catseye@catseye.mb.ca').
+-copyright('Copyright (c)2002 Cat`s Eye Technologies. All rights reserved.').
+
+-export([start/1, stop/1]).
+-export([loop_task/1]).
+-export([format_time/1]).
+
+%% @spec start([task()]) -> [pid()]
+%% @doc Starts <code>crone</code>, spawning a process for each task.
+
+start(Tasks) ->
+  lists:foldl(fun(X, A) ->
+                [spawn(?MODULE, loop_task, [X]) | A]
+              end, [], Tasks).
+
+%% @spec stop([task()]) -> ok
+%% @doc Stops all monitoring processes started by <code>crone</code>.
+
+stop(Tasks) ->
+  lists:foreach(fun(X) ->
+                  exit(X, stopped)
+                end, Tasks),
+  ok.
+
+%% @spec loop_task(task()) -> never_returns
+%% @doc Used by <code>start/1</code> to wait until the next time
+%% each task is scheduled to run, run it, and repeat.
+
+loop_task(Task) ->
+  Duration = until_next_time(Task),
+  % io:fwrite("~p: sleeping for ~s~n", [Task, format_time(Duration)]),
+  timer:sleep(Duration * 1000),
+  run_task(Task),
+  timer:sleep(1000),
+  loop_task(Task).
+
+%% @spec current_time() -> seconds()
+%%         seconds() = integer()
+%% @doc Returns the current time, in seconds past midnight.
+
+current_time() ->
+  {H,M,S} = erlang:time(),
+  S + M * 60 + H * 3600.
+
+%% @spec until_next_time(task()) -> seconds()
+%% @doc Calculates the duration in seconds until the next time
+%% a task is to be run.
+
+until_next_time(Task) ->
+  CurrentTime = current_time(),
+  {When, MFA} = Task,
+  case When of
+    {daily, Period} ->
+      until_next_daytime(Period);
+    {weekly, DoW, Period} ->
+      OnDay = resolve_dow(DoW),
+      Today = calendar:day_of_the_week(erlang:date()),
+      case Today of
+        OnDay ->
+	  until_next_daytime_or_days_from_now(Period, 7);
+        Today when Today < OnDay ->
+	  until_days_from_now(Period, OnDay - Today);
+	Today when Today > OnDay  ->
+	  until_days_from_now(Period, (OnDay+7) - Today)
+      end;
+    {monthly, DoM, Period} ->
+      {ThisYear, ThisMonth, Today} = erlang:date(),
+      {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(Period, Days);
+	_ ->
+	  until_days_from_now(Period, Days)
+      end
+  end.
+
+%% @spec until_next_daytime(period()) -> seconds()
+%% @doc Calculates the duration in seconds until the next time this
+%% period is to occur during the day.
+
+until_next_daytime(Period) ->
+  StartTime = first_time(Period),
+  EndTime = last_time(Period),
+  case current_time() of
+    T when T > EndTime ->
+      until_tomorrow(StartTime);
+    T ->
+      next_time(Period, T) - T
+  end.
+
+%% @spec until_tomorrow(seconds()) -> seconds()
+%% @doc Calculates the duration in seconds until the given time occurs
+%% tomorrow.
+
+until_tomorrow(StartTime) ->
+  (StartTime + 24*3600) - current_time().
+
+%% @spec until_days_from_now(period(), integer()) -> seconds()
+%% @doc Calculates the duration in seconds until the given period
+%% occurs several days from now.
+
+until_days_from_now(Period, Days) ->
+  Days * 24 * 3600 + until_next_daytime(Period).
+
+%% @spec until_next_daytime_or_days_from_now(period(), integer()) -> seconds()
+%% @doc Calculates the duration in seconds until the given period
+%% occurs, which may be today or several days from now.
+
+until_next_daytime_or_days_from_now(Period, Days) ->
+  CurrentTime = current_time(),
+  case last_time(Period) of
+    T when T < CurrentTime ->
+      until_days_from_now(Period, Days-1);
+    T ->
+      until_next_daytime(Period)
+  end.
+
+%% @spec last_time(period()) -> seconds()
+%% @doc Calculates the last time in a given period.
+
+last_time(Period) ->
+  hd(lists:reverse(lists:sort(resolve_period(Period)))).
+
+%% @spec first_time(period()) -> seconds()
+%% @doc Calculates the first time in a given period.
+
+first_time(Period) ->
+  hd(lists:sort(resolve_period(Period))).
+
+%% @spec next_time(period(), seconds()) -> seconds()
+%% @doc Calculates the first time in the given period after the given time.
+
+next_time(Period, Time) ->
+  R = lists:sort(resolve_period(Period)),
+  lists:foldl(fun(X, A) ->
+                case X of
+		  T when T >= Time, T < A -> T;
+		  _ -> A
+		end
+              end, 24*3600, R).
+
+%% @spec resolve_period(period()) -> [seconds()]
+%% @doc Returns a list of times given a periodic specification.
+
+resolve_period([]) -> [];
+resolve_period([H | T]) -> resolve_period(H) ++ resolve_period(T);
+resolve_period({every, Duration, {between, TimeA, TimeB}}) ->
+  Period = resolve_dur(Duration),
+  StartTime = resolve_time(TimeA),
+  EndTime = resolve_time(TimeB),
+  resolve_period0(Period, StartTime, EndTime, []);
+resolve_period(Time) ->
+  [resolve_time(Time)].
+
+resolve_period0(Period, Time, EndTime, Acc) when Time >= EndTime -> Acc;
+resolve_period0(Period, Time, EndTime, Acc) ->
+  resolve_period0(Period, Time + Period, EndTime, [Time | Acc]).
+
+%% @spec resolve_time(time()) -> seconds()
+%% @doc Returns seconds past midnight for a given time.
+
+resolve_time({H, M, S, X}) -> resolve_time({H, X}) + M * 60 + S;
+resolve_time({H, M, X}) -> resolve_time({H, X}) + M * 60;
+resolve_time({12, am}) -> 0;
+resolve_time({H,  am}) -> H * 3600;
+resolve_time({12, pm}) -> 12 * 3600;
+resolve_time({H,  pm}) -> (H + 12) * 3600.
+
+%% @spec resolve_dur(duration()) -> seconds()
+%% @doc Returns seconds for a given duration.
+
+resolve_dur({Hour, hr}) -> Hour * 3600;
+resolve_dur({Min, min}) -> Min * 60;
+resolve_dur({Sec, sec}) -> Sec.
+
+%% @spec resolve_dow(dow()) -> integer()
+%% @doc Returns the number of the given day of the week. See the calendar
+%% module for day numbers.
+
+resolve_dow(mon) -> 1;
+resolve_dow(tue) -> 2;
+resolve_dow(wed) -> 3;
+resolve_dow(thu) -> 4;
+resolve_dow(fri) -> 5;
+resolve_dow(sat) -> 6;
+resolve_dow(sun) -> 7.
+
+%% @spec run_task(task()) -> pid()
+%% @doc Spawns a process to accomplish the given task.
+
+run_task(Task) ->
+  {When, MFA} = Task,
+  {M,F,A} = MFA,
+  spawn(M,F,A).
+
+%% @spec format_time(integer()) -> string()
+%% @doc Returns human-readable string from seconds past midnight.
+
+format_time(N) ->
+  S = N rem 60,
+  M = (N div 60) rem 60,
+  H = (N div 3600),
+  io_lib:format("~w:~2.2.0w:~2.2.0w", [H,M,S]).
+
+%%% END of crone.erl %%%

File src/crone_test.erl

+%%% BEGIN crone_test.erl %%%
+%%%
+%%% crone - Task Scheduler for Erlang/OTP
+%%% Copyright (c)2002 Cat's Eye Technologies.  All rights reserved.
+%%%
+%%% Redistribution and use in source and binary forms, with or without
+%%% modification, are permitted provided that the following conditions
+%%% are met:
+%%%
+%%%   Redistributions of source code must retain the above copyright
+%%%   notice, this list of conditions and the following disclaimer.
+%%%
+%%%   Redistributions in binary form must reproduce the above copyright
+%%%   notice, this list of conditions and the following disclaimer in
+%%%   the documentation and/or other materials provided with the
+%%%   distribution.
+%%%
+%%%   Neither the name of Cat's Eye Technologies nor the names of its
+%%%   contributors may be used to endorse or promote products derived
+%%%   from this software without specific prior written permission.
+%%%
+%%% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+%%% CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+%%% INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+%%% MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+%%% DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
+%%% LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+%%% OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+%%% PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
+%%% OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+%%% ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+%%% OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+%%% OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+%%% POSSIBILITY OF SUCH DAMAGE. 
+
+%% @doc Simple tests for <code>crone</code>.
+%%
+%% @end
+
+-module(crone_test).
+-vsn('2002.0703').
+-author('catseye@catseye.mb.ca').
+-copyright('Copyright (c)2002 Cat`s Eye Technologies. All rights reserved.').
+
+-export([start/0]).
+
+%% @spec start() -> [pid()]
+%% @doc Runs a simple <code>crone</code> test.
+
+start() ->
+  crone:start([
+         {
+           {daily, {every, {23, sec}, {between, {3, pm}, {3, 30, pm}}}},
+           {io, fwrite, ["Hello, world!~n"]}
+         },
+         {
+           {daily, {3, 30, pm}},
+           {io, fwrite, ["It's three thirty~n"]}
+         },
+         {
+           {daily, [{1, 10, am}, {1, 07, 30, am}]},
+           {io, fwrite, ["Bing~n"]}
+         },
+         {
+           {weekly, thu, {2, am}},
+           {io, fwrite, ["It's 2 Thursday morning~n"]}
+         },
+         {
+           {weekly, wed, {2, am}},
+           {io, fwrite, ["It's 2 Wednesday morning~n"]}
+         },
+         {
+           {weekly, fri, {2, am}},
+           {io, fwrite, ["It's 2 Friday morning~n"]}
+         },
+         {
+           {monthly, 1, {2, am}},
+           {io, fwrite, ["First of the month!~n"]}
+         },
+         {
+           {monthly, 4, {2, am}},
+           {io, fwrite, ["Fourth of the month!~n"]}
+         }
+       ]).
+
+%%% END of crone_test.erl %%%