import { snippets } from "@/lib/generated/snippets";
import { Snippet } from "@/components/code";
import { Tabs, Callout } from "nextra/components";
import UniversalTabs from "@/components/UniversalTabs";

# Recurring Runs with Cron

A [Cron](https://en.wikipedia.org/wiki/Cron) is a time-based job scheduler that allows you to define when a task should be executed automatically on a pre-determined schedule.

Some example use cases for cron-style tasks might include:

1. Running a daily report at a specific time.
2. Sending weekly digest emails to users about their activity from the past week.
3. Running a monthly billing process to generate invoices for customers.

Hatchet supports cron triggers to run on a schedule defined in a few different ways:

- [Task Definitions](/v1/cron-runs#defining-a-cron-in-your-task-definition): Define a cron expression in your task definition to trigger the task on a predefined schedule.
- [Dynamic Programmatically](/v1/cron-runs#programmatically-creating-cron-triggers): Use the Hatchet SDKs to dynamically set the cron schedule of a task.
- [Hatchet Dashboard](/v1/cron-runs#managing-cron-jobs-in-the-hatchet-dashboard): Manually create cron triggers from the Hatchet Dashboard.

<Callout type="warning">
  The expression is when Hatchet **enqueues** the task, not when the run starts.
  Scheduling constraints like concurrency limits, rate limits, and retry
  policies can affect run start times.
</Callout>

### Cron Expression Syntax

Cron expressions in Hatchet follow the standard cron syntax. Hatchet supports both 5-field and 6-field expressions.
A cron expression consists of 5 to 6 fields separated by spaces. If there are 6 fields, the first field is seconds; if
there are 5 fields, the first field is minutes.

```
┌───────────── second (0 - 59) (optional)
│ ┌───────────── minute (0 - 59)
│ │ ┌───────────── hour (0 - 23)
│ │ │ ┌───────────── day of the month (1 - 31)
│ │ │ │ ┌───────────── month (1 - 12)
│ │ │ │ │ ┌───────────── day of the week (0 - 6) (Sunday to Saturday)
* * * * * *
```

Each field can contain a specific value, an asterisk (`*`) to represent all possible values, or a range of values. Here are some examples of cron expressions:

- `0 0 * * *`: Run every day at midnight
- `*/15 * * * *`: Run every 15 minutes
- `0 9 * * 1`: Run every Monday at 9 AM
- `0 0 1 * *`: Run on the first day of every month at midnight
- `30 * * * * *`: Run at 30 seconds past every minute (6-field)

<Callout type="info">
  Keep in mind, Hatchet Cloud meters by Task runs so use seconds wisely. Have
  questions about pricing? [Contact us](https://hatchet.run/office-hours)
</Callout>

## Defining a Cron in Your Task Definition

You can define a task with a cron schedule by configuring the cron expression as part of the task definition:

<UniversalTabs
  items={["Python-Sync", "Python-Async", "Typescript", "Go", "Ruby"]}
>
  <Tabs.Tab title="Python-Sync">
    <Snippet
      src={
        snippets.python.cron.workflow_definition
          .workflow_definition_cron_trigger
      }
    />
  </Tabs.Tab>
  <Tabs.Tab title="Python-Async">
    <Snippet
      src={
        snippets.python.cron.workflow_definition
          .workflow_definition_cron_trigger
      }
    />
  </Tabs.Tab>
  <Tabs.Tab title="Typescript">
    <Snippet src={snippets.typescript.on_cron.workflow.run_workflow_on_cron} />
  </Tabs.Tab>
  <Tabs.Tab title="Go">
    <Snippet src={snippets.go.cron.main.workflow_definition_cron_trigger} />
  </Tabs.Tab>
  <Tabs.Tab title="Ruby">
    <Snippet src={snippets.ruby.cron.worker.cron_workflow_definition} />
  </Tabs.Tab>
</UniversalTabs>

In the examples above, we set the `on cron` property of the task. The property specifies the cron expression that determines when the task should be triggered.

<Callout>
  Note: When modifying a cron in your task definition, it will override any cron
  schedule for previous crons defined in previous task definitions, but crons
  created via the API or Dashboard will still be respected.
</Callout>

## Programmatically Creating Cron Triggers

### Create a Cron Trigger

You can create dynamic cron triggers programmatically via the API. This is useful if you want to create a cron trigger that is not known at the time of task definition,

Here's an example of creating a a cron to trigger a report for a specific customer every day at noon:

<UniversalTabs
  items={["Python-Sync", "Python-Async", "Typescript", "Go", "Ruby"]}
  variant="hidden"
>
  <Tabs.Tab title="Python-Sync">
    <Snippet src={snippets.python.cron.programatic_sync.create} />
  </Tabs.Tab>
  <Tabs.Tab title="Python-Async">
    <Snippet src={snippets.python.cron.programatic_async.create} />
  </Tabs.Tab>
  <Tabs.Tab title="Typescript">
    <Snippet src={snippets.typescript.simple.cron.create} />
  </Tabs.Tab>
  <Tabs.Tab title="Go">
    <Snippet src={snippets.go.cron.main.create} />
  </Tabs.Tab>
  <Tabs.Tab title="Ruby">
    <Snippet src={snippets.ruby.cron.programatic_sync.create} />
  </Tabs.Tab>
</UniversalTabs>

In this example you can have different expressions for different customers, or dynamically set the expression based on some other business logic.

When creating a cron via the API, you will receive a cron trigger object with a metadata property containing the id of the cron trigger. This id can be used to reference the cron trigger when deleting the cron trigger and is often stored in a database or other persistence layer.

<Callout>
  Note: Cron Name and Expression are required fields when creating a cron
  trigger and we enforce a unique constraint on the two.
</Callout>

### Delete a Cron Trigger

You can delete a cron trigger by passing the cron object or a cron trigger id to the delete method.

<UniversalTabs
  items={["Python-Sync", "Python-Async", "Typescript", "Go", "Ruby"]}
  variant="hidden"
>
  <Tabs.Tab title="Python-Sync">
    <Snippet src={snippets.python.cron.programatic_sync.delete} />
  </Tabs.Tab>
  <Tabs.Tab title="Python-Async">
    <Snippet src={snippets.python.cron.programatic_async.delete} />
  </Tabs.Tab>
  <Tabs.Tab title="Typescript">
    <Snippet src={snippets.typescript.simple.cron.delete} />
  </Tabs.Tab>
  <Tabs.Tab title="Go">
    <Snippet src={snippets.go.cron.main.delete} />
  </Tabs.Tab>
  <Tabs.Tab title="Ruby">
    <Snippet src={snippets.ruby.cron.programatic_sync.delete} />
  </Tabs.Tab>
</UniversalTabs>

<Callout>
  Note: Deleting a cron trigger will not cancel any currently running instances
  of the task. It will simply stop the cron trigger from triggering the task
  again.
</Callout>

### List Cron Triggers

Retrieves a list of all task cron triggers matching the criteria.

<UniversalTabs
  items={["Python-Sync", "Python-Async", "Typescript", "Go", "Ruby"]}
  variant="hidden"
>
  <Tabs.Tab title="Python-Sync">
    <Snippet src={snippets.python.cron.programatic_sync.list} />
  </Tabs.Tab>
  <Tabs.Tab title="Python-Async">
    <Snippet src={snippets.python.cron.programatic_async.list} />
  </Tabs.Tab>
  <Tabs.Tab title="Typescript">
    <Snippet src={snippets.typescript.simple.cron.list} />
  </Tabs.Tab>
  <Tabs.Tab title="Go">
    <Snippet src={snippets.go.cron.main.list} />
  </Tabs.Tab>
  <Tabs.Tab title="Ruby">
    <Snippet src={snippets.ruby.cron.programatic_sync.list} />
  </Tabs.Tab>
</UniversalTabs>

## Managing Cron Triggers in the Hatchet Dashboard

In the Hatchet Dashboard, you can view and manage cron triggers for your tasks.

Navigate to "Triggers" > "Cron Jobs" in the left sidebar and click "Create Cron Job" at the top right.

You can specify run parameters such as Input, Additional Metadata, and the Expression.

![Create Cron Job](../../public/cron-dash.gif)

## Cron Considerations

When using cron triggers, there are a few considerations to keep in mind:

1. **Time Zone**: Cron schedules are UTC. Make sure to consider the time zone when defining your cron expressions.

2. **Execution Time**: The actual execution time of a cron-triggered task may vary slightly from the scheduled time. Hatchet makes a best-effort attempt to enqueue the task as close to the scheduled time as possible, but there may be slight delays due to system load or other factors.

3. **Missed Schedules**: If a scheduled task is missed (e.g., due to system downtime), Hatchet will **not** automatically run the missed instances. It will wait for the next scheduled time to trigger the task.

4. **Overlapping Schedules**: If a task is still running when the next scheduled time arrives, Hatchet will start a new instance of the task or respect the [concurrency](/v1/concurrency) policy.
