# Project calendar (/docs/concepts/calendar)



Your project calendar is the timezone your community lives in. It decides when "today", "this week" and "this month" start. For example:

* **A daily check-in quest** resets at midnight in Budapest, not in London.
* **A weekly leaderboard** runs Monday to Sunday in New York time.
* **A "3 per month" limit** starts fresh on the 1st, in your timezone.

Limits (budgets), streaks and calendar leaderboards all count periods of this one calendar, so a weekly limit and a weekly leaderboard always cover the same week. Each environment has its own. Until you choose a timezone, the calendar is UTC.

```mermaid
flowchart LR
    C["Project calendar<br/>(one timezone)"] --> B["Quest budgets"]
    C --> E["Engagement budgets"]
    C --> S["Streak days"]
    C --> L["Calendar leaderboards"]
```

## What follows the calendar [#what-follows-the-calendar]

| Feature                                                                                | Uses                                             | Counted at                                                          |
| -------------------------------------------------------------------------------------- | ------------------------------------------------ | ------------------------------------------------------------------- |
| [Named budgets](/docs/concepts/quests#attempts-and-completion)                         | `day`, `week`, `month`, or `lifetime` per budget | When the activity happened (`eligibleAt`)                           |
| [Engagement budgets](/docs/concepts/ugc#engagement-credits-growth)                     | `day`, `week`, `month`, or `lifetime` per budget | When the growth was measured; accounts settle once per calendar day |
| [Streaks](/docs/concepts/quests#calendar-day-streaks)                                  | Days                                             | When the activity happened                                          |
| [Calendar leaderboards](/docs/concepts/leaderboards#scores-are-calculated-from-points) | The current `day`, `week`, or `month`            | When points take effect                                             |

Budgets and leaderboards can count the same completion in different periods. A quest's points are awarded when its attempt is accepted, but its budget is charged in the period of the activity. A tracker activity from Monday that is accepted on Tuesday, after a deferral or review, spends Monday's daily budget and counts on Tuesday's daily leaderboard. Domino does not date a quest's points back to the activity, because that window may already be settled.

* **Days start at local midnight.** On daylight-saving days a day lasts 23 or 25 hours.
* **Weeks start on Monday.** They are ISO weeks.
* **Months are calendar months.**
* **Nothing names its own timezone.** A quest, budget, streak or board cannot override the project calendar. For a season on fixed dates, give a leaderboard fixed dates instead.
* **Some things count plain time.** A tracker's window runs from one moment to another, and a referral bonus's duration counts days since the friend qualified. Neither follows the calendar.

> [!DEV]
> A fixed-dates leaderboard uses a `fixed` window. A tracker's `window` is a start and an optional end instant.

## Use the calendar in an evaluator [#use-the-calendar-in-an-evaluator]

Call `ctx.calendar.day()` to read the project-calendar day containing `ctx.eligibleAt`. It returns a stable key plus the local date, timezone, and epoch-millisecond bounds:

```ts
import { keyed } from "@domino-sdk/relay/authoring"

const daily = keyed(({ calendar }) => calendar.day().key)
// For example: 2026-10-01@Europe/Budapest
```

Pass an instant as `ctx.calendar.day(at)` to read another day. `start` is inclusive and `end` is exclusive. A day can last 23 or 25 hours around daylight saving, and the first day after a timezone change can be shorter. Use the returned bounds; do not calculate them by adding 24 hours. How the calendar is pinned for retries is in [evaluation time and randomness](/docs/reference/quest-rules#evaluation-time-and-randomness).

## Choose the timezone [#choose-the-timezone]

In Console, open **Settings → General → Project calendar**, choose a timezone, give a reason, and save. Pick a city, such as `Europe/Budapest` or `America/New_York`, or `UTC`. Fixed offsets such as `+02:00`, `Etc/GMT-2` and `EST` are refused, because they ignore daylight saving: half the year, their midnight would be an hour off.

Choose the timezone before launch. A change mid-campaign is safe, but the first period after it can be short; see [When the timezone changes](#when-the-timezone-changes).

> [!DEV]
> Timezones are named IANA zones. Other spellings resolve to the canonical name (`US/Eastern` is saved as `America/New_York`, and `Etc/UTC` or `GMT` as `UTC`), so saving another spelling of the current timezone changes nothing.

| Where          | How                                                                                                                                                 |
| -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| relay.json     | `"calendar": { "timeZone": "Europe/Budapest" }`, applied by `domino deploy`                                                                         |
| Console        | **Settings → General → Project calendar**: choose a timezone, give a reason, and save                                                               |
| Management API | `management.calendar.configure({ timeZone, reason })`, or `domino calendar configure --time-zone Europe/Berlin --reason "…"` (`publish` permission) |

> [!DEV] Read the calendar
> Read the calendar with `management.calendar.read()` or `domino calendar get`. It returns the timezone in effect, today's date, when today ends (`nextBoundary`), any pending change, and the timezone relay.json last declared (`declared`). If the project kept a fixed-offset timezone from an older streak quest, `unsupported` names it: days follow UTC until you choose a timezone, and Console shows a warning.

* **The latest decision wins.** A deployment changes the calendar only when relay.json declares a different timezone from the last deployment. A change made in Console stays until someone edits relay.json.
* **A deployment makes the change its preview showed.** The preview's `calendar` shows the change and when it takes effect, or `effectiveAt: null` when the deployment keeps the current timezone and cancels a pending change. If a day boundary passes, or someone changes the calendar so that publishing would make a different change, publishing fails and asks you to preview again.
* **Each environment has its own calendar.** Live refuses Console and management changes to it with `promotion-required`. Live gets test's calendar through a [promotion](/docs/concepts/promotion) that a person approves. See [test and live](/docs/concepts/environments).
* **The audit history records every change**, with who made it and why: **Project timezone changed** for a scheduled change, and **Project timezone change cancelled** for a cancelled one. Saving what is already in effect records nothing.

## When the timezone changes [#when-the-timezone-changes]

A change never rewrites history. It waits for the current day to end in the current timezone, and reads show it as pending until then. Saving another timezone replaces a pending change, and choosing the current timezone cancels it.

| What                                             | During the change                                                                                        |
| ------------------------------------------------ | -------------------------------------------------------------------------------------------------------- |
| Today                                            | Finishes in the old timezone. The next day starts in the new one.                                        |
| Streaks                                          | Keep running. The first new day follows the last old one, even when its local date repeats or skips.     |
| An open week or month (budgets and leaderboards) | Finishes at its end in the old timezone.                                                                 |
| The next week or month                           | Runs from there to the next boundary in the new timezone, so it can be shorter than usual, never longer. |
| Recorded days, budget use and points             | Never change.                                                                                            |

> [!NOTE] One short period per change
> Because the first period after a change can be short, a budget can reset a few hours early once. A large westward change can also make the first day in the new timezone very short: from Pacific/Kiritimati to Pacific/Pago_Pago it lasts one hour. Streaks still need activity on that day to keep running. Choose the timezone before launch to avoid both.

## Where next [#where-next]

- [Quests](/docs/concepts/quests) — Budgets and calendar-day streaks.
- [Leaderboards](/docs/concepts/leaderboards) — Calendar windows.
- [Build leaderboards](/docs/build/leaderboards) — Window options and examples.
