{"id":16655354,"url":"https://github.com/zhongwencool/ecron","last_synced_at":"2025-09-08T07:31:11.344Z","repository":{"id":54112553,"uuid":"208550519","full_name":"zhongwencool/ecron","owner":"zhongwencool","description":"A lightweight/efficient cron-like job scheduling library for Erlang.","archived":false,"fork":false,"pushed_at":"2024-04-29T07:55:45.000Z","size":243,"stargazers_count":53,"open_issues_count":0,"forks_count":6,"subscribers_count":6,"default_branch":"master","last_synced_at":"2024-10-13T09:52:37.501Z","etag":null,"topics":["crontab","crontab-task","schedule","scheduled-jobs"],"latest_commit_sha":null,"homepage":"","language":"Erlang","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/zhongwencool.png","metadata":{"files":{"readme":"README.md","changelog":"changelog.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-09-15T06:11:53.000Z","updated_at":"2024-08-24T16:51:32.000Z","dependencies_parsed_at":"2024-10-26T00:29:01.026Z","dependency_job_id":null,"html_url":"https://github.com/zhongwencool/ecron","commit_stats":{"total_commits":84,"total_committers":3,"mean_commits":28.0,"dds":0.0357142857142857,"last_synced_commit":"25aa58442172c0e6d8278b77b407e3f6f272155c"},"previous_names":[],"tags_count":10,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zhongwencool%2Fecron","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zhongwencool%2Fecron/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zhongwencool%2Fecron/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zhongwencool%2Fecron/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/zhongwencool","download_url":"https://codeload.github.com/zhongwencool/ecron/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":232287401,"owners_count":18499949,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["crontab","crontab-task","schedule","scheduled-jobs"],"created_at":"2024-10-12T09:52:42.167Z","updated_at":"2025-01-03T04:14:07.605Z","avatar_url":"https://github.com/zhongwencool.png","language":"Erlang","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003c!-- markdownlint-disable MD013 --\u003e\r\n# ecron [![GitHub Actions][action-img]][action] [![codeCov-img]][codeCov] [![hex-img]][hex]\r\n[action]: https://github.com/zhongwencool/ecron\r\n[action-img]: https://github.com/zhongwencool/ecron/actions/workflows/ci.yml/badge.svg\r\n[codeCov]: https://codecov.io/gh/zhongwencool/ecron\r\n[codeCov-img]: https://codecov.io/gh/zhongwencool/ecron/branch/master/graph/badge.svg?token=FI9WAQ6UG5\r\n[hex]: https://hex.pm/packages/ecron\r\n[hex-img]: https://img.shields.io/hexpm/v/ecron.svg?style=flat\r\n\r\nA lightweight and efficient cron-like job scheduling library for Erlang.\r\n\r\n## Overview\r\nEcron is designed to manage scheduled jobs within a single gen_server process, similar to the standard library's [stdlib's timer](http://erlang.org/doc/man/timer.html). It uses an ordered_set ETS table to organize jobs by the next run time, ensuring efficient execution. Unlike traditional cron, Ecoron does not poll the system every second, which reduces message overhead and process usage.\r\n\r\nmore detail see [Implementation](#Implementation).\r\n\r\n\r\n### Key Features\r\n- Supports both cron-like and interval-based scheduling.\r\n- Well-tested with [PropTest](https://github.com/proper-testing/proper) [![codecov](https://codecov.io/gh/zhongwencool/ecron/branch/master/graph/badge.svg?token=FI9WAQ6UG5)](https://codecov.io/gh/zhongwencool/ecron).\r\n- Utilizes gen_server timeout mechanism for precise timing.\r\n- Efficient process management, avoiding high memory usage.\r\n\r\nYou can find a collection of general practices in [Full Erlang Examples](https://github.com/zhongwencool/ecron/blob/master/examples/titan_erlang) and [Full Elixir Examples](https://github.com/zhongwencool/ecron/blob/master/examples/titan_elixir).\r\n\r\n## Installation\r\n  \r\n**Erlang**\r\n ```erlang\r\n  %% rebar.config\r\n  {deps, [ecron]}\r\n ```\r\n\r\n**Elixir**\r\n ```elixir\r\n  # mix.exs\r\n  def deps do\r\n    [{:ecron, \"~\u003e 1.0.0\"}]\r\n  end\r\n```\r\n\r\n## Basic Usage \r\n\r\nConfigure Ecoron in your sys.config file with timezone and job specifications:\r\n\r\n```erlang\r\n%% sys.config\r\n[\r\n   {ecron, [      \r\n      {time_zone, local}, %% local or utc\r\n      {local_jobs, [\r\n         %% {JobName, CrontabSpec, {M, F, A}}\r\n         %% {JobName, CrontabSpec, {M, F, A}, StartDateTime, EndDateTime}\r\n         %% CrontabSpec\r\n            %%  1. \"Minute Hour DayOfMonth Month DayOfWeek\"\r\n            %%  2. \"Second Minute Hour DayOfMonth Month DayOfWeek\"\r\n            %%  3. @yearly | @annually | @monthly | @weekly | @daily | @midnight | @hourly\r\n            %%  4. @every 1h2m3s\r\n                  \r\n         {crontab_job, \"*/15 * * * *\", {io, format, [\"Runs on 0, 15, 30, 45 minutes~n\"]}},\r\n         {extend_crontab_job, \"0 0 1-6/2,18 * * *\", {io, format, [\"Runs on 1,3,6,18 o'clock:~n\"]}},\r\n         {alphabet_job, \"@hourly\", {io, format, [\"Runs every(0-23) o'clock~n\"]}},    \r\n         {fixed_interval_job, \"@every 30m\", {io, format, [\"Runs every 30 minutes\"]}},\r\n         %% Runs every 15 minutes between {8,20,0} and {23, 59, 59}.\r\n         %% {limit_time_job, \"*/15 * * * *\", {io, format, [\"Runs 0, 15, 30, 45 minutes after 8:20am~n\"]}, {8,20,0}, unlimited}\r\n         %% parallel job         \r\n         {no_singleton_job, \"@minutely\", {timer, sleep, [61000]}, unlimited, unlimited, [{singleton, false}]}            \r\n     ]},\r\n     {global_jobs, []}, %% Global Spec has the same format as local_jobs.\r\n     {global_quorum_size, 1} %%  Minimum number of nodes which run ecron. Global_jobs only run on majority cluster when it \u003e ClusterNode/2.\r\n    }\r\n].\r\n```\r\n\r\n* Default `time_zone` is `local`, the current datetime is [calendar:local_time()](http://erlang.org/doc/man/calendar.html#local_time-0).\r\n* The current datetime is [calendar:universal_time()](http://erlang.org/doc/man/calendar.html#universal_time-0) when `{time_zone, utc}`.\r\n* The job will be auto remove at `EndDateTime`, the default value of `EndDateTime` is `unlimited`. \r\n* Default job is singleton, Each task cannot be executed concurrently. \r\n* If the system clock suddenly alter a lot(such as sleep your laptop for two hours or modify system time manually),\r\n  it will skip the tasks which are supposed be running during the sudden lapse of time,\r\n  then recalculate the next running time by the latest system time.\r\n  You can also reload task manually by `ecron:reload().` when the system time is manually modified.\r\n* Global jobs depend on [global](http://erlang.org/doc/man/global.html), only allowed to be added statically, [check this for more detail](https://github.com/zhongwencool/ecron/blob/master/doc/global.md).\r\n\r\n## Supervisor Tree Usage\r\nEcron can be integrated into your application's supervision tree for better control over its lifecycle:\r\n\r\n```erlang\r\n%%config/sys.config\r\n[{your_app, [{crontab_jobs, [\r\n   {crontab_job, \"*/15 * * * *\", {stateless_cron, inspect, [\"Runs on 0, 15, 30, 45 minutes\"]}}\r\n  ]}\r\n].\r\n\r\n%% src/your_app_sup.erl\r\n-module(your_app_top_sup).\r\n-behaviour(supervisor).\r\n-export([init/1]).\r\n\r\ninit(_Args) -\u003e\r\n    Jobs = application:get_env(your_app, crontab_jobs, []),\r\n    SupFlags = #{\r\n        strategy =\u003e one_for_one,\r\n        intensity =\u003e 100,\r\n        period =\u003e 30\r\n    },\r\n    Name = 'uniqueName',\r\n    CronSpec = #{\r\n        id =\u003e Name,\r\n        start =\u003e {ecron, start_link, [Name, Jobs]},\r\n        restart =\u003e permanent,\r\n        shutdown =\u003e 1000,\r\n        type =\u003e worker\r\n    },\r\n    {ok, {SupFlags, [CronSpec]}}.\r\n\r\n```\r\n## Advanced Usage \r\n\r\nEcron allows for advanced scheduling and manipulation of cron jobs:\r\n\r\n```erlang\r\n%% Same as: Spec = \"0 * 0-5,18 * * 0-5\", \r\nSpec = #{second =\u003e [0], \r\n       minute =\u003e '*',   \r\n       hour =\u003e [{0,5}, 18], %% same as [0,1,2,3,4,5,18]\r\n       month =\u003e '*',\r\n       day_of_month =\u003e '*',\r\n       day_of_week =\u003e [{0,5}]},\r\nCronMFA = {io, format, [\"Runs on 0-5,18 o'clock between Sunday and Firday.~n\"]},\r\n%% with name crontab  \r\n{ok, _} = ecron:add(crontabUniqueName, Spec, CronMFA),\r\n%% or\r\n{ok, _} = ecron:add(crontabUniqueName, \"0 * 0-5,18 * * 0-5\", CronMFA),   \r\nok = ecron:delete(crontabuniqueName),\r\n%% crontab with startTime and endTime\r\nStartDateTime = {{2019,9,19},{0,0,0}},\r\nEndDateTime = {{2020,9,19},{0,0,0}},\r\n{ok, _} = ecron:add(crontabUniqueName, Spec, CronMFA, StartDateTime, EndDateTime),\r\n%% crontab without name\r\n{ok, JobName} = ecron:add(Spec, CronMFA, StartDateTime, EndDateTime), \r\nok = ecron:delete(crontabuniqueName),\r\n%% Runs every 120 second (fixed interval)\r\nEveryMFA = {io, format, [\"Runs every 120 second.~n\"]},\r\n{ok, _} = ecron:add(everyUniqueName, 120, EveryMFA),\r\n```\r\n## Debug Support\r\n\r\nEcron provides functions to assist with debugging:\r\n\r\n````erlang\r\n1\u003e ecron:deactivate(CrontabName).\r\nok\r\n\r\n2\u003e ecron:activate(CrontabName).\r\nok\r\n\r\n3\u003e ecron:statistic(CrontabName).\r\n{ok,\r\n  #{crontab =\u003e\r\n      #{day_of_month =\u003e '*',\r\n       day_of_week =\u003e [{1,5}],hour =\u003e [1,13],\r\n       minute =\u003e [0],month =\u003e '*',second =\u003e [0]},\r\n       start_time =\u003e unlimited,end_time =\u003e unlimited,\r\n       failed =\u003e 0,mfa =\u003e {io,format,[\"ddd\"]},\r\n       name =\u003e test,status =\u003e activate,type =\u003e cron,\r\n       ok =\u003e 1,results =\u003e [ok],run_microsecond =\u003e [12],       \r\n       opts =\u003e [{singleton,true}], node =\u003e 'test@127.0.0.1',\r\n       next =\u003e\r\n          [\"2019-09-27T01:00:00+08:00\",\"2019-09-27T13:00:00+08:00\",\r\n           \"2019-09-30T01:00:00+08:00\",\"2019-09-30T13:00:00+08:00\",\r\n           \"2019-10-01T01:00:00+08:00\",\"2019-10-01T13:00:00+08:00\",\r\n           \"2019-10-02T01:00:00+08:00\",\"2019-10-02T13:00:00+08:00\",\r\n           [...]|...]      \r\n      }\r\n}\r\n\r\n4\u003e ecron:parse_spec(\"0 0 1,13 * * 1-5\", 5).\r\n{ok,\r\n#{crontab =\u003e\r\n      #{day_of_month =\u003e '*',\r\n        day_of_week =\u003e [{1,5}],\r\n        hour =\u003e [1,13],\r\n        minute =\u003e [0],\r\n        month =\u003e '*',\r\n        second =\u003e [0]},\r\n  next =\u003e\r\n      [\"2019-09-16T13:00:00+08:00\",\"2019-09-17T01:00:00+08:00\",\r\n       \"2019-09-17T13:00:00+08:00\",\"2019-09-18T01:00:00+08:00\",\r\n       \"2019-09-18T13:00:00+08:00\"],\r\n  type =\u003e cron}\r\n}\r\n````\r\n## Implementation\r\n\r\nEcron uses an efficient approach to manage job execution times and intervals:\r\n\r\n1. The top supervisor `ecron_sup` starts firstly.\r\n2. then `ecron_sup` starts a child `ecron`(gen_server worker).\r\n3. `ecron` will look for configuration `{local_jobs, Jobs}` at initialization.\r\n4. For each crontab job found, determine the next time in the future that each command must run.\r\n5. Place those commands on the ordered_set ets with their `{NextCorrespondingTime, Name}` to run as key.\r\n6. Enter `ecron`'s main loop:\r\n    * Examine the task entry at the head of the ets, compute how far in the future it must run.\r\n    * Sleep for that period of time by gen_server timeout feature.\r\n    * On awakening and after verifying the correct time, execute the task at the head of the ets (spawn in background).\r\n    * Delete old key in ets.\r\n    * Determine the next time in the future to run this command and place it back on the ets at that time value.\r\n    \r\nAdditionally, you can use `ecron:statistic(Name)` to see the job's latest 16 results and execute times.\r\n```\r\n    ecron_sup-------\u003eecron\r\n                      |\r\n                   |------| ets:new(timer, [ordered_set])\r\n                   | init | ets:insert(timer, [{{NextTriggeredTime,Name}...])\r\n                   |------|\r\n             |-------\u003e|\r\n             |     |------| {NextTriggeredTime,Name} = OldKey = ets:first(timer)\r\n             |     |      | sleep(NextTriggeredTime - current_time())\r\n             |     | loop | spawn a process to execute MFA\r\n             |     |      | ets:delete(timer, OldKey)\r\n             |     |------| ets:insert(timer, {{NewTriggeredTime,Name}...])\r\n             |\u003c-------|\r\n```\r\n\r\n[Check this for global_jobs workflow](https://github.com/zhongwencool/ecron/blob/master/doc/global.md#Implementation).       \r\n\r\n## Telemetry\r\n\r\nEcron publishes events through telemetry, allowing for monitoring and alerting,\r\nYou can handle those events by [this guide](https://github.com/zhongwencool/ecron/blob/master/doc/telemetry.md).\r\n\r\n## CRON Expression Format\r\n\r\nA [cron expression](https://www.wikiwand.com/en/Cron) represents a set of times, using 5-6 space-separated fields.\r\nCurrently, W (nearest weekday), L (last day of month/week), and # (nth weekday of the month) are not supported. \r\n\r\nMost other features supported by popular cron implementations should work just fine.\r\n```shell script\r\n # ┌────────────── second (optional)\r\n # │ ┌──────────── minute\r\n # │ │ ┌────────── hour\r\n # │ │ │ ┌──────── day of month\r\n # │ │ │ │ ┌────── month\r\n # │ │ │ │ │ ┌──── day of week\r\n # │ │ │ │ │ │\r\n # │ │ │ │ │ │\r\n # 0 * * * * *\r\n```\r\n\r\nField name   | Mandatory? | Allowed values  | Allowed special characters\r\n----------   | ---------- | --------------  | --------------------------\r\nSeconds      | No         | 0-59            | * / , -\r\nMinutes      | Yes        | 0-59            | * / , -\r\nHours        | Yes        | 0-23            | * / , -\r\nDay of month | Yes        | 1-31            | * / , -\r\nMonth        | Yes        | 1-12 or JAN-DEC | * / , -\r\nDay of week  | Yes        | 0-6 or SUN-SAT  | * / , -\r\n\r\nNote: Month and Day-of-week field values are case-insensitive. \"SUN\", \"Sun\", and \"sun\" are equally accepted.\r\n\r\nWhen specifying your cron values you'll need to make sure that your values fall within the ranges. \r\nFor instance, some cron's use a 0-7 range for the day of week where both 0 and 7 represent Sunday. We do not.\r\n\r\n### Special Characters\r\n#### Asterisk ( * )\r\nThe asterisk indicates that the cron expression will match for all values of the field. \r\nFor example, using an asterisk in the `month` field would indicate every month.\r\n\r\n#### Slash ( / )\r\nSlashes are used to describe increments of ranges. \r\nFor example, \"3-59/15\" in the `minutes` field would indicate the 3rd minute of the hour and every 15 minutes thereafter. \r\nThe form \"*/...\" is equivalent to the form \"First-Last/...\", that is, an increment over the largest possible range of the field. \r\nThe form \"N/...\" is accepted as meaning \"N-Max/...\", that is, starting at N, use the increment until the end of that specific range. \r\nIt does not wrap around.\r\n\r\n#### Comma ( , )\r\nCommas are used to separate items of a list. \r\nFor example, using \"MON,WED,FRI\" in the `day_of_week` field would mean Mondays, Wednesdays and Fridays.\r\n\r\n#### Hyphen ( - )\r\nHyphens are used to define ranges. \r\nFor example, using \"9-17\" in the `hours`field  would indicate every hour between 9am and 5pm inclusive.\r\n\r\n## Predefined crontab\r\n\r\nYou may use one of several pre-defined crontab in place of a cron expression.\r\n\r\nEntry                  | Description                                | Equivalent To\r\n-----                  | -----------                                | -------------\r\n@yearly (or @annually) | Run once a year, midnight, Jan. 1st        | 0 0 0 1 1 *\r\n@monthly               | Run once a month, midnight, first of month | 0 0 0 1 * *\r\n@weekly                | Run once a week, midnight between Sat/Sun  | 0 0 0 * * 0\r\n@daily (or @midnight)  | Run once a day, midnight                   | 0 0 0 * * *\r\n@hourly                | Run once an hour, beginning of hour        | 0 0 * * * *\r\n@minutely              | Run once an minute, beginning of minute    | 0 * * * * *\r\n\r\n\u003eThere are tools that help when constructing your cronjobs. \r\n\u003eYou might find something like [https://crontab.guru/](https://crontab.guru/) or [https://cronjob.xyz/](https://cronjob.xyz/) helpful. \r\n\u003eBut, note that these don't necessarily accept the exact same syntax as this library, \r\n\u003efor instance, it doesn't accept the seconds field, so keep that in mind.\r\n\u003eThe best way to verify the spec format is `ecron:parse_spec(\"0 0 1 1 1-6 1\", 10).`. \r\n\r\n## Intervals\r\n\r\nEcron also supports scheduling jobs at fixed intervals:\r\n\r\n```shell\r\n@every \u003cduration\u003e\r\n```\r\nFor example, @every 1h30m10s schedules a job to run every 1 hour, 30 minutes, and 10 seconds.\r\n\r\n\u003eNote: The interval doesn't take the job runtime into account. \r\n\u003eFor example, if a job takes 3 minutes to run, and it is scheduled to run every 5 minutes, \r\n\u003eit only has 2 minutes of idle time between each run.\r\n  \r\n## Test\r\n\r\nThis command will run property-based tests, common tests, and generate a coverage report with verbose output.\r\n\r\n```shell\r\n  $ rebar3 do proper -c, ct -c, cover -v\r\n```\r\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzhongwencool%2Fecron","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fzhongwencool%2Fecron","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzhongwencool%2Fecron/lists"}