{"id":46094921,"url":"https://github.com/zalgonoise/micron","last_synced_at":"2026-03-01T18:32:23.125Z","repository":{"id":208680176,"uuid":"722222307","full_name":"zalgonoise/micron","owner":"zalgonoise","description":"This is my cron (micron); there are others like it but this one is mine. A cron library in Go.","archived":false,"fork":false,"pushed_at":"2025-11-17T15:31:56.000Z","size":414,"stargazers_count":3,"open_issues_count":2,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-11-17T17:17:06.551Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/zalgonoise.png","metadata":{"files":{"readme":"README.md","changelog":null,"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,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2023-11-22T17:40:53.000Z","updated_at":"2025-11-17T15:31:13.000Z","dependencies_parsed_at":"2023-11-27T01:38:21.378Z","dependency_job_id":"dec46828-fb25-4ea7-a7c1-690ba7d0de57","html_url":"https://github.com/zalgonoise/micron","commit_stats":null,"previous_names":["zalgonoise/micron"],"tags_count":11,"template":false,"template_full_name":null,"purl":"pkg:github/zalgonoise/micron","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zalgonoise%2Fmicron","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zalgonoise%2Fmicron/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zalgonoise%2Fmicron/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zalgonoise%2Fmicron/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/zalgonoise","download_url":"https://codeload.github.com/zalgonoise/micron/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zalgonoise%2Fmicron/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29978566,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-01T16:35:47.903Z","status":"ssl_error","status_checked_at":"2026-03-01T16:35:44.899Z","response_time":124,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":[],"created_at":"2026-03-01T18:32:22.609Z","updated_at":"2026-03-01T18:32:23.113Z","avatar_url":"https://github.com/zalgonoise.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"## micron\n\n### _This is my cron (micron); there are others like it but this one is mine. A cron-scheduler library in Go_\n\n_______\n\n### Concept\n\n`cron` is a Go library that allows adding cron-like scheduler(s) to Go apps, compatible with \n[Unix's cron time/date strings](https://en.wikipedia.org/wiki/Cron), to execute actions within the context of the app.\n\nBy itself, `cron` is a fantastic tool released in the mid-70's, written in C, where the user defines a specification in \na crontab, a file listing jobs with a reference of a time/date specification and a (Unix) command to execute.\n\nWithin Go, it should provide the same set of features as the original binary, but served in a library as a \n(blocking / background) pluggable service. This means full compatibility with cron strings for scheduling, support for \nmultiple executable types or functions, and support for multiple job scheduling (with different time/date \nspecifications). Additionally, the library extends this functionality to support definition of seconds' frequency in \ncron-strings, context support, error returns, and full observability coverage (with added support for metrics, logs and \ntraces decorators).   \n\n_______\n\n### Motivation\n\nIn a work environment, we see cron many times, in different scenarios. From cron installations in bare-metal Linux \nservers to one-shot containers configured in Kubernetes deployments. Some use-cases are very simple, others complex, but\nthe point is that is a tool used for nearly 50 years at the time of writing.\n\nBut the original tool is a C binary that executes a Unix command. If we want to explore schedulers for Go applications \n(e.g. a script executed every # hours), this means that the app needs to be compiled as a binary, and then to configure\na cron-job to execute the app in a command.\n\nWhile this is fine, it raises the question -- what if I want to include it _within_ the application? This should make a \nlot of sense to you if you're a fan of SQLite like me.\n\nThere were already two libraries with different implementations, in Go:\n- [`robfig/cron`](https://github.com/robfig/cron) with 12k GitHub stars\n- [`go-co-op/gocron`](https://github.com/go-co-op/gocron) with 4.3k GitHub stars\n\nDon't get me wrong -- there is nothing inherently wrong about these implementations; I went through them carefully both \nfor insight and to understand what could I explore differently. A very obvious change would be a more _\"modern\"_ \napproach including a newer Go version (as these required Go 1.12 and 1.16 respectively); which by itself includes\n`log/slog` and all the other observability-related decorators that also leverage `context.Context`.\n\nAnother more obvious exploration path would be the parser logic, as I could use my \n[generic lexer](https://github.com/zalgonoise/lex) and [generic parser](https://github.com/zalgonoise/parse) in order to \npotentially improve it.\n\nLastly I could try to split the cron service's components to be more configurable even in future iterations, once I had\ndecided on the general API for the library. There was enough ground to explore and to give it a go. :)\n\nA personal project that I have [for a Steam CLI app](https://github.com/zalgonoise/x/tree/master/steam) is currently \nusing this cron library to regularly check for discounts in the Steam Store, for certain products on a certain \nfrequency, as configured by the user.\n\n_______\n\n### Usage\n\nUsing `cron` is as layered and modular as you want it to be. This chapter describes how to use the library effectively.\n\n#### Getting `cron`\n\nYou're able to fetch `cron` as a Go module by importing it in your project and running `go mod tidy`:\n\n```go\npackage main\n\nimport (\n\t\"fmt\"\n\t\"context\"\n\t\n\t\"github.com/zalgonoise/micron/v3\"\n\t\"github.com/zalgonoise/micron/v3/executor\"\n)\n\nfunc main() {\n\tfn := func(context.Context) error {\n\t\tfmt.Println(\"done!\")\n\n\t\treturn nil\n\t}\n\t\n\tc, err := micron.New(micron.WithJob(\"my-job\", \"* * * * *\", executor.Runnable(fn)))\n\t// ...\n}\n```\n_______\n\n\n#### Cron Runtime\n\nThe runtime is the component that will control (like the name implies) how the module runs -- that is, controlling the \nflow of job selection and execution. The runtime will allow cron to be executed as a goroutine, as its \n[`Runtime.Run`](./cron.go#L61) method has no returns, and errors are channeled via its [`Runtime.Err`](./cron.go#L78) \nmethod (which returns an error channel). The actual runtime of the cron is still managed with a `context.Context` that \nis provided when calling [`Runtime.Run`](./cron.go#L61) -- which can impose a cancellation or timeout strategy.\n\nJust like the simple example above, creating a cron runtime starts with the \n[`cron.New` constructor function](./cron.go#L87).\n\nThis function only has [a variadic parameter for `cfg.Option[cron.Config]`](./cron.go#L87). This allows full modularity\non the way you build your cron runtime, to be as simple or as detailed as you want it to be -- provided that it complies \nwith the minimum requirements to create one; to supply either:\n- a [`selector.Selector`](./selector/selector.go#L37) \n- or, a (set of) [`executor.Runner`](./executor/executor.go#L41). This can be supplied as \n[`executor.Runnable`](./executor/executor.go#L54) as well.\n\n```go\nfunc New(options ...cfg.Option[*Config]) (Runtime, error)\n```\n\nBelow is a table with all the options available for creating a cron runtime:\n\n|                   Function                    |                                    Input Parameters                                    |                                                                                               Description                                                                                               |\n|:---------------------------------------------:|:--------------------------------------------------------------------------------------:|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:|\n|    [`WithSelector`](./cron_config.go#L33)     |                 [`sel selector.Selector`](./selector/selector.go#L37)                  |                                                            Configures the  with the input [`selector.Selector`](./selector/selector.go#L37).                                                            |\n|       [`WithJob`](./cron_config.go#L55)       | `id string`, `cron string`, [`runners ...executor.Runner`](./executor/executor.go#L41) | Adds a new [`executor.Executor`](./executor/executor.go#L85) to the [`Runtime`](./cron.go#L34) configuration from the input ID, cron string and set of [`executor.Runner`](./executor/executor.go#L41). |\n| [`WithErrorBufferSize`](./cron_config.go#L85) |                                       `size int`                                       |                                   Defines the capacity of the error channel that the [`Runtime`](./cron.go#L34) exposes in its [`Runtime.Err`](./cron.go#L77) method.                                   |\n|     [`WithMetrics`](./cron_config.go#L98)     |                     [`m cron.Metrics`](./cron_with_metrics.go#L10)                     |                                                               Configures the [`Runtime`](./cron.go#L34) with the input metrics registry.                                                                |\n|     [`WithLogger`](./cron_config.go#L111)     |              [`logger *slog.Logger`](https://pkg.go.dev/log/slog#Logger)               |                                                                    Configures the [`Runtime`](./cron.go#L34) with the input logger.                                                                     |\n|   [`WithLogHandler`](./cron_config.go#L124)   |             [`handler slog.Handler`](https://pkg.go.dev/log/slog#Handler)              |                                                           Configures the [`Runtime`](./cron.go#L34) with logging using the input log handler.                                                           |\n|     [`WithTrace`](./cron_config.go#L137)      |   [`tracer trace.Tracer`](https://pkg.go.dev/go.opentelemetry.io/otel/trace#Tracer)    |                                                                 Configures the [`Runtime`](./cron.go#L34) with the input trace.Tracer.                                                                  |\n\nThe simplest possible cron runtime could be the result of a call to [`cron.New`](./cron.go#L87) with a single \n[`cron.WithJob`](./cron_config.go#L55) option. This creates all the components that a cron runtime needs with the most\nminimal setup. It creates the underlying selector and executors.\n\nOtherwise, the caller must use the [`WithSelector`](./cron_config.go#L33) option, and configure a \n[`selector.Selector`](./selector/selector.go#L37) manually when doing so. This results in more _boilerplate_ to get the\nruntime set up, but provides deeper control on how the cron should be composed. The next chapter covers what is a\n[`selector.Selector`](./selector/selector.go#L37) and how to create one.\n\n_______\n\n#### Cron Selector\n\nThis component is responsible for picking up the next job to execute, according to their schedule frequency. For this, \nthe [`Selector`](./selector/selector.go#L37) is configured with a set of \n[`executor.Executor`](./executor/executor.go#L85), which in turn will expose a \n[`Next` method](./executor/executor.go#L93). With this information, the [`Selector`](./selector/selector.go#L37) cycles \nthrough its [`executor.Executor`](./executor/executor.go#L85) and picks up the next task(s) to run.\n\nWhile the [`Selector`](./selector/selector.go#L37) calls the \n[`executor.Executor`'s `Exec` method](./executor/executor.go#L91), the actual waiting is within the\n[`executor.Executor`'s](./executor/executor.go#L85) logic.\n\nYou're able to create a [`Selector`](./selector/selector.go#L37) through \n[its constructor function](./selector/selector.go#L143):\n\n```go\nfunc New(options ...cfg.Option[*Config]) (Selector, error)\n```\n\n\nBelow is a table with all the options available for creating a cron job selector:\n\n\n|                        Function                        |                                 Input Parameters                                  |                                                                                    Description                                                                                     |\n|:------------------------------------------------------:|:---------------------------------------------------------------------------------:|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:|\n|  [`WithExecutors`](./selector/selector_config.go#L27)  |          [`executors ...executor.Executor`](./executor/executor.go#L85)           |                            Configures the [`Selector`](./selector/selector.go#L37) with the input [`executor.Executor`(s)](./executor/executor.go#L85).                            |\n|    [`WithBlock`](./selector/selector_config.go#L62)    |                                                                                   |       Configures the [`Selector`](./selector/selector.go#L37) to block (wait) for the underlying [`executor.Executor`(s)](./executor/executor.go#L85) to complete the task.        |\n|   [`WithTimeout`](./selector/selector_config.go#L75)   |                                `dur time.Duration`                                | Configures a (non-blocking) [`Selector`](./selector/selector.go#L37) to wait a certain duration before detaching of the executable task, before continuing to select the next one. |\n|   [`WithMetrics`](./selector/selector_config.go#L88)   |          [`m selector.Metrics`](./selector/selector_with_metrics.go#L10)          |                                              Configures the [`Selector`](./selector/selector.go#L37) with the input metrics registry.                                              |\n|   [`WithLogger`](./selector/selector_config.go#L101)   |            [`logger *slog.Logger`](https://pkg.go.dev/log/slog#Logger)            |                                                   Configures the [`Selector`](./selector/selector.go#L37) with the input logger.                                                   |\n| [`WithLogHandler`](./selector/selector_config.go#L114) |           [`handler slog.Handler`](https://pkg.go.dev/log/slog#Handler)           |                                         Configures the [`Selector`](./selector/selector.go#L37) with logging using the input log handler.                                          |\n|   [`WithTrace`](./selector/selector_config.go#L127)    | [`tracer trace.Tracer`](https://pkg.go.dev/go.opentelemetry.io/otel/trace#Tracer) |                                                Configures the [`Selector`](./selector/selector.go#L37) with the input trace.Tracer.                                                |\n\nThere is a catch to the [`Selector`](./selector/selector.go#L37), which is the actual job's execution time. While the \n[`Selector`](./selector/selector.go#L37) cycles through its [`executor.Executor`](./executor/executor.go#L85) list, it \nwill execute the task while waiting for it to return with or without an error. This may cause issues when a given \nrunning task takes too long to complete when there are other, very frequent tasks. If there is a situation where the \nlong-running task overlaps the execution time for another scheduled job, that job's execution is potentially skipped -- \nas the next task would only be picked up and waited for once the long-running one exits.\n\nFor this reason, there are two implementations of [`Selector`](./selector/selector.go#L37): \n- A blocking one, that waits for every job to run and return an error, accurately returning the correct outcome in its\n`Next` call. This implementation is great for fast and snappy jobs, or less frequent / non-overlapping schedules and \nexecutions. There is less resource overhead to it, and the error returns are fully accurate with the actual outcome.\n- A non-blocking one, that waits for a job to raise an error in a goroutine, with a set timeout (either set by the \ncaller or a default one). This implementation is great if the jobs are too frequent and / or the tasks too long, when it\nrisks skipping executions due to stuck long-running tasks. It relies more heavily on having configured Observability at\nleast on the [`executor.Executor`](./executor/executor.go#L85) level to underline those events (which get detached from \nthe [`Selector`](./selector/selector.go#L37) after timing out).\n\nIt is important to have a good idea of how your cron jobs will execute and how often, or simply ensure that there is at \nleast logging enabled for the configured [`executor.Executor`(s)](./executor/executor.go#L85).\n_______\n\n#### Cron Executor\n\nLike the name implies, the [`Executor`](./executor/executor.go#L85) is the component that actually executes the job, on \nits next scheduled time.\n\nThe [`Executor`](./executor/executor.go#L85) is composed of a [cron schedule](#cron-schedule) and a (set of) \n[`Runner`(s)](./executor/executor.go#L41). Also, the [`Executor`](./executor/executor.go#L85) stores an ID that is used \nto identify this particular job.\n\nHaving these 3 components in mind, it's natural that the [`Executor`](./executor/executor.go#L85) exposes three methods:\n- [`Exec`](./executor/executor.go#L91) - runs the task when on its scheduled time.\n- [`Next`](./executor/executor.go#L93) - calls the underlying \n[`schedule.Scheduler` Next method](./schedule/scheduler.go#L30).\n- [`ID`](./executor/executor.go#L95) - returns the ID.\n\nConsidering that the [`Executor`](./executor/executor.go#L85) holds a specific \n[`schedule.Scheduler`](./schedule/scheduler.go#L28), it is also responsible for managing any waiting time before the \njob is executed. The strategy employed by the [`Executable`](./executor/executor.go#L99) type is one that calculates the\nduration until the next job, and sleeps until that time is reached (instead of, for example, calling the\n[`schedule.Scheduler` Next method](./schedule/scheduler.go#L30) every second).\n\n\nTo create an [`Executor`](./executor/executor.go#L85), you can use the [`New`](./executor/executor.go#L162) function \nthat serves as a constructor. Note that the minimum requirements to creating an [`Executor`](./executor/executor.go#L85)\nare to include both a [`schedule.Scheduler`](./schedule/scheduler.go#L28) with the \n[`WithScheduler`](./executor/executor_config.go#L62) option (or a cron string, using the \n[`WithSchedule`](./executor/executor_config.go#L79) option), \nand at least one [`Runner`](./executor/executor.go#L41) with the [`WithRunners`](./executor/executor_config.go#L29) \noption.\n\nThe [`Runner`](./executor/executor.go#L41) itself is an interface with a single method \n([`Run`](./executor/executor.go#L48)), that takes in a `context.Context` and returns an error. If your implementation is\nso simple that you have it as a function and don't need to create a type for this \n[`Runner`](./executor/executor.go#L41), then you can use the [`Runnable` type](./executor/executor.go#L54) instead, \nwhich is a type alias to a function of the same signature, but implements [`Runner`](./executor/executor.go#L41) by \ncalling itself as a function, in its [`Run`](./executor/executor.go#L61) method.\n\nCreating an [`Executor`](./executor/executor.go#L85) is as easy as calling\n[its constructor function](./executor/executor.go#L162):\n\n```go\nfunc New(id string, options ...cfg.Option[*Config]) (Executor, error)\n```\n\n\nBelow is a table with all the options available for creating a cron job executor:\n\n\n\n|                        Function                        |                                 Input Parameters                                  |                                                                     Description                                                                     |\n|:------------------------------------------------------:|:---------------------------------------------------------------------------------:|:---------------------------------------------------------------------------------------------------------------------------------------------------:|\n|   [`WithRunners`](./executor/executor_config.go#L29)   |                 [`runners ...Runner`](./executor/executor.go#L41)                 |                  Configures the [`Executor`](./executor/executor.go#L85) with the input [`Runner`(s)](./executor/executor.go#L41).                  |\n|  [`WithScheduler`](./executor/executor_config.go#L62)  |             [`sched schedule.Scheduler`](./schedule/scheduler.go#L28)             |             Configures the [`Executor`](./executor/executor.go#L85) with the input [`schedule.Scheduler`](./schedule/scheduler.go#L28).             |\n|  [`WithSchedule`](./executor/executor_config.go#L79)   |                                   `cron string`                                   |   Configures the [`Executor`](./executor/executor.go#L85) with a [`schedule.Scheduler`](./schedule/scheduler.go#L28) using the input cron string.   |\n|  [`WithLocation`](./executor/executor_config.go#L97)   |                               `loc *time.Location`                                | Configures the [`Executor`](./executor/executor.go#L85) with a [`schedule.Scheduler`](./schedule/scheduler.go#L28) using the input `time.Location`. |\n|  [`WithMetrics`](./executor/executor_config.go#L110)   |          [`m executor.Metrics`](./executor/executor_with_metrics.go#L11)          |                              Configures the [`Executor`](./executor/executor.go#L85) with the input metrics registry.                               |\n|   [`WithLogger`](./executor/executor_config.go#L123)   |            [`logger *slog.Logger`](https://pkg.go.dev/log/slog#Logger)            |                                   Configures the [`Executor`](./executor/executor.go#L85) with the input logger.                                    |\n| [`WithLogHandler`](./executor/executor_config.go#L136) |           [`handler slog.Handler`](https://pkg.go.dev/log/slog#Handler)           |                          Configures the [`Executor`](./executor/executor.go#L85) with logging using the input log handler.                          |\n|   [`WithTrace`](./executor/executor_config.go#L149)    | [`tracer trace.Tracer`](https://pkg.go.dev/go.opentelemetry.io/otel/trace#Tracer) |                                Configures the [`Executor`](./executor/executor.go#L85) with the input trace.Tracer.                                 |\n\n\n_______\n\n#### Cron Scheduler\n\nThe [`Scheduler`](./schedule/scheduler.go#L28) is responsible for keeping schedule state (for example, derived from a \ncron string), and calculating the next job's execution time, with the context of the input timestamp. As such, the\n[`Scheduler` interface only exposes one method, `Next`](./schedule/scheduler.go#L30) which is responsible of making such \ncalculations.\n\nThe default implementation of [`Scheduler`](./schedule/scheduler.go#L28), [`CronSchedule`](./schedule/scheduler.go#L36), \nwill be created from parsing a cron string, and is nothing but a data structure with a \n[`cronlex.Schedule`](./schedule/cronlex/process.go#L56) bounded to a `time.Location`.\n\nWhile the [`CronSchedule`](./schedule/scheduler.go#L36) leverages different schedule elements with \n[`cronlex.Resolver` interfaces](./schedule/cronlex/process.go#L49), the [`Scheduler`](./schedule/scheduler.go#L28) uses \nthese values as a difference from the input timestamp, to create a new date with a \n[`time.Date()`](https://pkg.go.dev/time#Date) call. This call merely adds the difference until the next job to the \ncurrent time, on different elements of the timestamp, with added logic to calculate weekdays if set.\n\nFortunately, Go's `time` package is super solid and allows date overflows, calculating them accordingly. This makes the \nlogic of the base implementation a total breeze, and simple enough to be pulled off as opposed to ticking every second, \nchecking for new jobs.\n\nYou're able to create a [`Scheduler`](./schedule/scheduler.go#L28) by calling\n[its constructor function](./schedule/scheduler.go#L98), with the mandatory minimum of supplying a cron string through \nits [`WithSchedule`](./schedule/scheduler_config.go#L23) option.\n\n```go\nfunc New(options ...cfg.Option[Config]) (Scheduler, error)\n```\n\nBelow is a table with all the options available for creating a cron job scheduler:\n\n\n|                        Function                        |                                 Input Parameters                                  |                                             Description                                             |\n|:------------------------------------------------------:|:---------------------------------------------------------------------------------:|:---------------------------------------------------------------------------------------------------:|\n|  [`WithSchedule`](./schedule/scheduler_config.go#L23)  |                                   `cron string`                                   |        Configures the [`Scheduler`](./schedule/scheduler.go#L28) with the input cron string.        |\n|  [`WithLocation`](./schedule/scheduler_config.go#L38)  |                               `loc *time.Location`                                |      Configures the [`Scheduler`](./schedule/scheduler.go#L28) with the input `time.Location`.      |\n|  [`WithMetrics`](./schedule/scheduler_config.go#L51)   |         [`m executor.Metrics`](./schedule/scheduler_with_metrics.go#L11)          |     Configures the [`Scheduler`](./schedule/scheduler.go#L28) with the input metrics registry.      |\n|   [`WithLogger`](./schedule/scheduler_config.go#L64)   |            [`logger *slog.Logger`](https://pkg.go.dev/log/slog#Logger)            |          Configures the [`Scheduler`](./schedule/scheduler.go#L28) with the input logger.           |\n| [`WithLogHandler`](./schedule/scheduler_config.go#L77) |           [`handler slog.Handler`](https://pkg.go.dev/log/slog#Handler)           | Configures the [`Scheduler`](./schedule/scheduler.go#L28) with logging using the input log handler. |\n|   [`WithTrace`](./schedule/scheduler_config.go#L90)    | [`tracer trace.Tracer`](https://pkg.go.dev/go.opentelemetry.io/otel/trace#Tracer) |       Configures the [`Scheduler`](./schedule/scheduler.go#L28) with the input trace.Tracer.        |\n\n\n\n_______\n\n##### Cron Schedule\n\n[`Schedule`](./schedule/cronlex/process.go#L56) is a data structure that holds a set of \n[`Resolver`s](./schedule/cronlex/process.go#L49), for each node, segment or unit of the schedule. This implementation \nfocuses on the cron string specification with added support for a seconds definition (instead of the usual minutes, \nhours, days-of-the-month, months and weekdays). Each of these elements are \n[`Resolver`s](./schedule/cronlex/process.go#L49) interfaces that will calculate the difference until the target value(s)\nis reached. More information on [`Resolver`s](./schedule/cronlex/process.go#L49) in \n[its own section](#schedule-resolver).\n\nThe [`Schedule`](./schedule/cronlex/process.go#L56) only holds the state of a parsed cron string, and its elements are \nmade public so that implementations of [`Scheduler`](./schedule/scheduler.go#L28) can leverage it to calculate the \njob's next execution time.\n\nTo create a new [`Schedule`](./schedule/cronlex/process.go#L56) type, you're able to use the\n[`Parse` function](./schedule/cronlex/process.go#L69), that consumes the input cron string, returning a \n[`Schedule`](./schedule/cronlex/process.go#L56) and an error if raised. More details on the actual parsing of the string\n[in its own section](#schedule-parser).\n\nOnce created, the elements of [`Schedule`](./schedule/cronlex/process.go#L56) are accessed directly, where the caller \ncan use the [`Resolver`](./schedule/cronlex/process.go#L49) interface:\n\n```go\ntype Schedule struct {\n\tSec      Resolver\n\tMin      Resolver\n\tHour     Resolver\n\tDayMonth Resolver\n\tMonth    Resolver\n\tDayWeek  Resolver\n}\n```\n_______\n\n##### Schedule Resolver\n\nThis component calculates the difference between the input value and the (set of) value(s) it is configured to be \ntriggered on, also given a certain maximum value for that [`Resolver`'s](./schedule/cronlex/process.go#L49) range.\n\nThis difference is useful on a [`Scheduler`](./schedule/scheduler.go#L28), where \n[`time.Date()`](https://pkg.go.dev/time#Date) sums the input time to the difference until the next execution. As such, \ngiven each node in a [`Schedule`](./schedule/cronlex/process.go#L56), it is possible to derive the next time per node,\nfollowing this logic.\n\nTake a minute as an example. It is an element that spans from 0 to 59; and consider all elements start at zero. A\nminutes [`Resolver`](./schedule/cronlex/process.go#L49) is configured with a maximum value of 59, and this example is \nconfigured to trigger at minute 15. If the input time is 2:03 PM, the [`Resolver`](./schedule/cronlex/process.go#L49)\nreturns a difference of 12. When the [`Scheduler`](./schedule/scheduler.go#L28) gets this information, it adds up the 12\nminutes to the input time, and returns the resulting datetime value.\n\nThis also makes the [`Resolver`](./schedule/cronlex/process.go#L49) very flexible, in tandem with the corresponding\n[`Schedule`](./schedule/cronlex/process.go#L56) and [`Scheduler`](./schedule/scheduler.go#L28), providing customizable \nlevels of precision. The seconds [`Resolver`](./schedule/cronlex/process.go#L49) in \n[`Schedule`](./schedule/cronlex/process.go#L56) is an example of this.\n\nThe implementations of [`Resolver`](./schedule/cronlex/process.go#L49) can be found in the \n[`resolve` package](./schedule/resolve/resolve.go). These are derived from parsing a cron string and assigned \nautomatically when calling the [`Parse` function](./schedule/cronlex/process.go#L69).\n\nTo explore the different implementations, it's good to have in mind the modularity in cron schedules:\n- it supports \"every value\" sequences when using a star (`*`)\n- it supports range value sequences when using a dash (`-`, e.g. `0-15`)\n- it supports step value sequences when using a slash (`/`, e.g. `*/3`)\n- it supports separated range value elements when using commas (`,`, e.g. `0,20,25,30`) \n- it supports a combination of range values and steps (e.g. `0-15/3`)\n- it supports overrides, for certain configurations (e.g. `@weekly`)\n\nHaving this in mind, this could technically be achieved with a single [`Resolver`](./schedule/cronlex/process.go#L49) \ntype (that you will find for step values), but to maximize performance and reduce complexity where it is not needed, \nthere are four types of [`Resolver`](./schedule/cronlex/process.go#L49):\n\n\n###### `Everytime` Resolver\n\n```go\ntype Everytime struct{}\n```\n\nThe [`Everytime Resolver`](./schedule/resolve/resolve.go#L4) always returns zero, meaning that it resolves to the current time \nalways (_trigger now_ type of action). This is the default [`Resolver`](./schedule/cronlex/process.go#L49) whenever a \nstar (`*`) node is found, for example.\n\n\n###### `FixedSchedule` Resolver\n\n```go\ntype FixedSchedule struct {\n\tMax int\n\tAt  int\n}\n```\n\nThe [`FixedSchedule Resolver`](./schedule/resolve/resolve.go#L13) resolves at a specific value within the entire range. \nIt is especially useful when a node only contains a non-star (`*`) alphanumeric value, such as the cron string \n`0 0 * * *`, which has both minutes and hours as [`FixedSchedule Resolver`s](./schedule/resolve/resolve.go#L13).\n\n\n\n###### `RangeSchedule` Resolver\n\n```go\ntype RangeSchedule struct {\n    Max  int\n    From int\n    To   int\n}\n```\n\nThe [`RangeSchedule Resolver`](./schedule/resolve/resolve.go#L25) resolves within a portion of the entire range. It is \nused when a range is provided without any gaps. This means that the schedule element does not contain any slashes (`/`)\nor commas (`,`), and is only a range with a dash (`-`), such as the cron string `0-15 * * * *`, which contains a\n[`RangeSchedule Resolver`](./schedule/resolve/resolve.go#L25) for its minutes' node.\n\n\n\n###### `StepSchedule` Resolver\n\n```go\ntype StepSchedule struct {\n    Max   int\n    Steps []int\n}\n```\n\nThe [`StepSchedule Resolver`](./schedule/resolve/resolve.go#L42) is the most complex of all -- that could potentially \nserve all the other implementations, however it can be the most resource-expensive of all \n[`Resolver`s](./schedule/cronlex/process.go#L49).\n\nThis implementation stores the defined values for the schedule and returns the difference of the closest value ahead of \nit. This involves scanning all the steps in the sequence as it requires looking into values that are less than the \ninput, e.g., when the input is value 57, the maximum is 59, and the closest step is 0, with a difference of 3.\n\nDoing this for complex cron setups can be more complex, and that is the major reason for having several other \nimplementations. Regardless, if your cron string is one that involves many individual steps separated by commas (`,`), \nor contains a given frequency delimited by a slash (`/`), it surely will have an underlying \n[`StepSchedule Resolver`](./schedule/resolve/resolve.go#L42) to resolve that / those node(s). \n\nThe [`StepSchedule Resolver`](./schedule/resolve/resolve.go#L42) is the only\n[`Resolver`s](./schedule/cronlex/process.go#L49) which exposes a constructor, with the \n[`NewStepSchedule` function](./schedule/resolve/resolve.go#L77), that takes _from_ and _to_ values, \na maximum, and a certain frequency (which should be 1 if no custom frequency is desired). Any further additions to \nthe `Steps` in the [`StepSchedule Resolver`](./schedule/resolve/resolve.go#L42), should be added to the data structure,\nseparately:\n\n```go\nfunc NewStepSchedule(from, to, maximum, frequency int) StepSchedule\n```\n\nIn the [Schedule Parser section](#schedule-parser), we explore how its processor will create the\n[`Schedule`](./schedule/cronlex/process.go#L56) types following some rules, when working with the abstract syntax tree \nfrom parsing the cron string.\n_______\n\n##### Schedule Parser\n\nTo consume a cron string and make something meaningful of it, we must parse it. This step is most important to \nnail down accurately as it is the main source of user input within this library's logic. The executed jobs are of the \ncaller's responsibility as they pass into it whatever they want. But having a correct understanding of the input \nschedule as well as calculating the times for the jobs' execution is fundamentally most important.\n\nAs mentioned before, this package exposes a [`Parse` function](./schedule/cronlex/process.go#L69) that consumes a cron \nstring returning a [`Schedule`](./schedule/cronlex/process.go#L56) and an error:\n\n```go\nfunc Parse(cron string) (s Schedule, err error)\n```\n\nThis is a process broken down in three phases that can be explored individually, having into consideration that the \nlexer and parser components work in tandem:\n- A lexer, that consumes individual bytes from the cron string, emitting meaningful tokens about what they represent. \nThe tokens also hold the value (bytes) that represent them. \n- A parser, which consumes the tokens as they are emitted from the lexer, and progressively builds an abstract syntax \ntree that is the cron schedule and its different nodes.\n- A processor, which consumes the finalized abstract syntax tree created by the parser, validates its contents and \ncreates the appropriate [`Schedule`](./schedule/cronlex/process.go#L56).\n\nThe implementations of the underlying lexer and parser logic are taken from Go's standard library, from its \n[`go/token`](https://pkg.go.dev/go/token) and [`text/template`](https://pkg.go.dev/text/template) packages. There is \nalso [a fantastic talk from Rob Pike](https://www.youtube.com/watch?v=HxaD_trXwRE) not only describing how to \nwrite a lexer and parser as state machines in Go, but also carrying the viewer through the details of the \n`text/template` implementation. With this in mind, I had released the same general concept of a lexer and parser, but \nsupporting generic types, allowing consumers of the library to write their parsers for anything, with any type \n(both for input, tokens and output). These implementations can be further explored below:\n- [`zalgonoise/lex`](https://github.com/zalgonoise/lex)\n- [`zalgonoise/parse`](https://github.com/zalgonoise/parse)\n\nTaking this as a kick-off point, it's only required to implement 3 (major) functions for the lexer, parser and processor\nphases of this pipeline. These functions are broken down with their own individual handler functions, as required.\n\nIntroducing the [`Token` type](./schedule/cronlex/token.go#L4), we can see how different symbols are set to trigger \ndifferent tokens, while having a few general ones like [`TokenAlphaNum`](./schedule/cronlex/token.go#L9) for numbers and\ncharacters, [`TokenEOF`](./schedule/cronlex/token.go#L7) when the end of the string is reached, etc.  \n\nStarting with the lexer, it exposes a [`StateFunc` function](./schedule/cronlex/lexer.go#L19). This is the starting \npoint for the state machine that consumes individual bytes. This function basically emits a token for a given character \n(if supported), except for alphanumeric bytes (that are collected and a single token emitted with them), and at the end \nof the string.\n\nAt the same time that the lexer is emitting these tokens, the parser's \n[`ParseFunc` function](./schedule/cronlex/parser.go#L18) is consuming them to build an abstract syntax tree. This tree \nhas a root (top-level) node that is branched for how many nodes are there in a cron string -- say, `* * * * *` contains \n5 nodes, `* * * * * *` contains 6 nodes, and `@weekly` contains 1 node.\n\nIf these nodes are more complex than a single value, then the node will store the value while aggregating all following\n(chained) values by their symbol as children. These symbol nodes must contain a value as child (a comma cannot be left \nby itself at the end of a node). Take the following node in a cron string: `0-15/3,20,25`. The node in the tree should\nlook like:\n\n```\n- (root)\n  +- alphanum --\u003e value: 0\n    +- dash   --\u003e value: 15\n    +- slash  --\u003e value: 3\n    +- comma  --\u003e value: 20\n    +- comma  --\u003e value: 25\n```\n\nHaving created the cron's abstract syntax tree we arrive to the last phase, the \n[`ProcessFunc` function](./schedule/cronlex/process.go#L82). It starts off by validating the contents in the abstract \nsyntax tree to ensure there are no unsupported values like greater than the maximum, etc.\n\nOnce ensured it is valid, the function checks how many nodes are children of the root node in the tree, with support for\n3 types of lengths:\n- 1 child node means this should be an exception (like `@weekly`).\n- 5 child nodes represent a _classic_ cron string ranging from minutes to weekdays (e.g. `* * * * *`).\n- 6 child nodes represent an _extended_ cron string supporting seconds to weekdays (e.g. `* * * * * *`).\n\nHandling the exceptions is very simple as the function only switches on the supported values looking for a match. The \nswitch statement is the fastest algorithm to perform this check. \n\nA _classic_ cron string with 5 nodes will still have a seconds [`Resolver`](./schedule/cronlex/process.go#L49) in its \n[`Schedule`](./schedule/cronlex/process.go#L56), by configuring it as a\n[`FixedSchedule` type](./schedule/resolve/resolve.go#L13), triggering at value 0 with a maximum of 59 (for seconds). \n\nGenerally, the Resolver types for each node from both _classic_ and _extended_ cron strings are built by checking\nif it's a star (`*`) or alphanumeric node, creating the appropriate [`Resolver`](./schedule/cronlex/process.go#L49). \nNote that step values are sorted and compacted before being returned, for optimal efficiency when being used.\n\nLastly, considering the weekday support for 0 and 7 as Sundays, if the weekday \n[`Resolver`](./schedule/cronlex/process.go#L49) is a \n[`StepSchedule` type](./schedule/resolve/resolve.go#L42), it is normalized as a 0 value.\n\n\n_______\n\n### Example\n\nA working example is the [Steam CLI app](https://github.com/zalgonoise/x/tree/master/steam) mentioned in the \n[Motivation](#motivation) section above. This application exposes some commands, one of them being \n[`monitor`](https://github.com/zalgonoise/x/blob/master/steam/cmd/steam/monitor/monitor.go). This file provides some \ninsight on how the cron service is set up from a `main.go` / script-like approach.\n\nYou can also take a look \n[at its `runner.go` file](https://github.com/zalgonoise/x/blob/master/steam/cmd/steam/monitor/runner.go), that \nimplements the [`executor.Runner`](./executor/executor.go#L41) interface.\n\n_______\n\n### Disclaimer\n\nThis is not a one-size-fits-all solution! Please take your time to evaluate it for your own needs with due diligence.\nWhile having _a library for this and a library for that_ is pretty nice, it could potentially be only overhead hindering\nthe true potential of your app! Be sure to read the code that you are using to be a better judge if it is a good fit for\nyour project. With that in mind, I hope you enjoy this library. Feel free to contribute by filing either an issue or a\npull request.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzalgonoise%2Fmicron","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fzalgonoise%2Fmicron","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzalgonoise%2Fmicron/lists"}