# Public Holiday Calendar By Country (`first_watch/public-holiday-calendar-by-country`) Actor

Operational teams must validate and adjust thousands of country-specific payment, shipment, payroll, staffing, reporting, and contractual dates around public holidays. Generic holiday-calendar feeds do not apply business-day rules to customer records or return results that can be joined directly...

- **URL**: https://apify.com/first_watch/public-holiday-calendar-by-country.md
- **Developed by:** [Jordan Nabbe](https://apify.com/first_watch) (community)
- **Categories:** Automation, Business, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$2.00 / 1,000 adjusted records

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Public Holiday Calendar By Country

Public Holiday Calendar By Country helps payroll teams, logistics operators, finance operations teams, workforce planners, and automation developers apply country holiday calendars and business-day rules to their own CSV, JSON, or Excel records. Every successful record comes back as one flat, import-ready row that keeps your opaque record ID and a source reference, so it joins straight back into your payroll, shipping, invoicing, or reporting system.

### What it does

- Reads inline records, pasted CSV or JSON, base64 CSV/JSON, and genuine XLSX workbook bytes. Optionally downloads a document over HTTPS from `apify.com` or `date.nager.at`.
- Flags Saturday/Sunday weekends and public holidays on the original date, with holiday names.
- Applies an adjustment convention (`none`, `next`, `previous`, `nearest`) and then a signed business-day offset from -366 to 366.
- Offline mode works with built-in US federal holiday rules (including weekend observance) or with complete country-year calendars that you supply. Online mode (`holidaySource: nager`) uses the public Nager.Date API, one request per country-year, cached during the run, with retries.
- Accepts only unambiguous ISO dates (`YYYY-MM-DD`). Values such as `03/04/2025` are rejected with `INVALID_DATE` instead of being guessed.
- Reports problems per record: successful rows go to the dataset, failed records are listed with an error code and a retryable flag in the `OUTPUT` record. One bad row never aborts the batch.
- Optionally writes a CSV or XLSX `RESULTS` file with the successful rows.

### How to use it

The prefilled input runs without any network access. Copy this into the Actor input:

```json
{
  "inputMode": "records",
  "holidaySource": "offline",
  "records": [
    { "recordId": "invoice-1042", "countryCode": "US", "date": "2025-07-04", "adjustmentConvention": "next", "businessDayOffset": 0 },
    { "recordId": "shipment-208", "countryCode": "US", "date": "2025-07-03", "adjustmentConvention": "none", "businessDayOffset": 2 }
  ],
  "outputFormat": "json"
}
```

For CSV use `inputMode: text` with `format: csv`; for Excel use `inputMode: base64` with `format: xlsx` and real workbook bytes (one worksheet, headers in row 1). Required columns: `recordId`, `countryCode`, `date`; optional: `regionCode`, `adjustmentConvention`, `businessDayOffset`. Header names are matched case-insensitively and ignore spaces, underscores and hyphens (`Record ID` works). Unknown columns are rejected per row, so remove unrelated export columns first. Formula cells are not evaluated and fail their row.

Supplying your own calendar: each `offlineCalendars` entry has `countryCode`, `year`, `complete: true`, `regionCodes`, and `holidays` (`date`, `name`, optional `regionCode`). The Actor trusts your completeness declaration. If you supply a country, include every year a calculation might enter; a missing year fails the row rather than falling back.

### Output example

| recordId | inputDate | isPublicHoliday | adjustedDate | calendarDaysShifted | sourceRef |
|---|---|---|---|---|---|
| invoice-1042 | 2025-07-04 | true | 2025-07-07 | 3 | input:records#row=1 |
| shipment-208 | 2025-07-03 | false | 2025-07-08 | 5 | input:records#row=2 |

Each dataset row also has `rowNumber`, `status` (always `ok`), country and region, weekday, weekend flag, holiday names, adjustment settings, `businessDaysShifted`, source attribution, and warnings. Holiday flags describe the original date. `OUTPUT` contains `rows`, `failures`, `summary`, and an `outcome` block (status, counts, limit flag).

### Pricing

Pay per event. The main charge is per adjusted record; everything else listed below is free.

| Event | Price (USD) | Charged when |
|---|---|---|
| Adjusted record (`apify-default-dataset-item`) | $0.002 | A successful result row is pushed to the default dataset. Failed rows, summaries and manifests are never charged. |

Not charged:

- OUTPUT.failures
- OUTPUT.summary
- OUTPUT.outcome
- RESULTS export

Examples (default 512 MB memory, one start event):

- 100 valid records: 100 × $0.002 = **$0.20**
- 10,000 valid records: 10000 × $0.002 = **$20.00**

Spending limit: when you set a maximum cost per run, the Actor stops before it would exceed it, keeps every adjusted record it already delivered and reports `limitReached` in the `OUTPUT` record. It never delivers results beyond the limit.

The Store's Pricing tab shows the prices in force. If this section and the Pricing tab ever differ, the Pricing tab applies.

### Use cases

- Flag payments and reporting deadlines that fall on public holidays.
- Move shipment and contract dates to the next, previous, or nearest working day.
- Compute business-day lead times with positive or negative offsets.
- Annotate multi-country CSV, Excel, or JSON records before a database import.
- Re-run repeatable checks against an internally approved calendar.

### Limits

- 1-1,000 records per run; decoded documents up to 25 MiB; JSON nesting up to 8 levels.
- Dates must be real ISO dates from 2020 to 2035.
- At most 50 distinct country-year calendars per run; each search examines at most 1,100 calendar days.
- Weekends are always Saturday and Sunday. Built-in offline rules cover US federal holidays only; other countries need your own calendar or the Nager source. Nager.Date coverage and observed-date conventions depend on that service.
- A region is accepted only if the calendar explicitly covers it; without a region only national holidays apply (a warning is added when regional data exists).
- Requests time out after 10 seconds, follow at most 3 validated redirects, and the run has a 120-second processing budget.

### FAQ

#### Does offline mode use the internet?

No. Inline records, text, and base64 documents with the offline source make no document or holiday requests.

#### What happens if some rows are invalid?

They are listed in `OUTPUT.failures` with a row number, source reference, error code, and message. Valid rows continue. If no row succeeds, the run fails after writing `OUTPUT`. Set `failOnAnyError` to also fail the run when any row failed (successful rows are still published).

#### How is nearest calculated?

The closest working day wins; on a tie the following day is chosen. The offset then counts working days starting from the adjusted date, not counting the start date.

#### What if the holiday service is down?

Affected rows fail with `SOURCE_UNAVAILABLE` and `retryable: true`. Empty or malformed upstream data is never treated as a holiday-free calendar.

#### Are my files stored?

The Actor writes no extra copy of your documents and logs no source rows. Apify input and output storage follows your account settings, so use opaque IDs.

# Actor input Schema

## `inputMode` (type: `string`):

Choose direct records, pasted CSV/JSON, a base64 document, or a public HTTPS document.

## `records` (type: `array`):

1-1,000 objects. Required fields: recordId (1-200 characters), countryCode (two letters), date (YYYY-MM-DD, 2020-2035). Optional: regionCode, adjustmentConvention (none/next/previous/nearest; default next), businessDayOffset (-366 to 366; default 0). Invalid rows are reported individually in OUTPUT.failures.

## `text` (type: `string`):

Required for text mode. UTF-8 CSV with headers, a JSON array, or a JSON object containing only a records array. Maximum 25 MiB.

## `contentBase64` (type: `string`):

Required for base64 mode. Canonical base64 CSV, JSON, or actual XLSX bytes without a data URL prefix. Decoded maximum: 25 MiB.

## `url` (type: `string`):

Required for URL mode. HTTPS on exactly apify.com or date.nager.at. Credentials, query strings, fragments, custom ports, and redirects to other hosts are rejected.

## `format` (type: `string`):

Required for text, base64, or URL input. XLSX requires base64 or URL mode.

## `holidaySource` (type: `string`):

Offline uses supplied complete calendars or built-in US federal rules. Nager uses the public Nager.Date API and requires network access.

## `offlineCalendars` (type: `array`):

At most 50 objects: {countryCode, year, complete:true, regionCodes:\[], holidays:\[{date,name,regionCode?}]}. Years 2020-2035. At most 100 regions and 1,000 holidays per calendar. Region codes start with the country and a hyphen. Include every year the calculation may enter. Cannot be combined with the Nager source.

## `outputFormat` (type: `string`):

JSON rows and OUTPUT are always stored. CSV or XLSX also creates a downloadable RESULTS record with the successful rows.

## `failOnAnyError` (type: `boolean`):

Publish all successful rows and OUTPUT first, then exit unsuccessfully if any row failed.

## Actor input object example

```json
{
  "inputMode": "records",
  "records": [
    {
      "recordId": "invoice-1042",
      "countryCode": "US",
      "date": "2025-07-04",
      "adjustmentConvention": "next",
      "businessDayOffset": 0
    },
    {
      "recordId": "shipment-208",
      "countryCode": "US",
      "date": "2025-07-03",
      "adjustmentConvention": "none",
      "businessDayOffset": 2
    },
    {
      "recordId": "payroll-17",
      "countryCode": "US",
      "date": "2025-01-01",
      "adjustmentConvention": "previous",
      "businessDayOffset": -1
    }
  ],
  "holidaySource": "offline",
  "offlineCalendars": [],
  "outputFormat": "json",
  "failOnAnyError": false
}
```

# Actor output Schema

## `results` (type: `string`):

Every result row stored in the default dataset.

## `summary` (type: `string`):

Summary written to the OUTPUT record.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "inputMode": "records",
    "records": [
        {
            "recordId": "invoice-1042",
            "countryCode": "US",
            "date": "2025-07-04",
            "adjustmentConvention": "next",
            "businessDayOffset": 0
        },
        {
            "recordId": "shipment-208",
            "countryCode": "US",
            "date": "2025-07-03",
            "adjustmentConvention": "none",
            "businessDayOffset": 2
        },
        {
            "recordId": "payroll-17",
            "countryCode": "US",
            "date": "2025-01-01",
            "adjustmentConvention": "previous",
            "businessDayOffset": -1
        }
    ],
    "holidaySource": "offline",
    "outputFormat": "json"
};

// Run the Actor and wait for it to finish
const run = await client.actor("first_watch/public-holiday-calendar-by-country").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "inputMode": "records",
    "records": [
        {
            "recordId": "invoice-1042",
            "countryCode": "US",
            "date": "2025-07-04",
            "adjustmentConvention": "next",
            "businessDayOffset": 0,
        },
        {
            "recordId": "shipment-208",
            "countryCode": "US",
            "date": "2025-07-03",
            "adjustmentConvention": "none",
            "businessDayOffset": 2,
        },
        {
            "recordId": "payroll-17",
            "countryCode": "US",
            "date": "2025-01-01",
            "adjustmentConvention": "previous",
            "businessDayOffset": -1,
        },
    ],
    "holidaySource": "offline",
    "outputFormat": "json",
}

# Run the Actor and wait for it to finish
run = client.actor("first_watch/public-holiday-calendar-by-country").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "inputMode": "records",
  "records": [
    {
      "recordId": "invoice-1042",
      "countryCode": "US",
      "date": "2025-07-04",
      "adjustmentConvention": "next",
      "businessDayOffset": 0
    },
    {
      "recordId": "shipment-208",
      "countryCode": "US",
      "date": "2025-07-03",
      "adjustmentConvention": "none",
      "businessDayOffset": 2
    },
    {
      "recordId": "payroll-17",
      "countryCode": "US",
      "date": "2025-01-01",
      "adjustmentConvention": "previous",
      "businessDayOffset": -1
    }
  ],
  "holidaySource": "offline",
  "outputFormat": "json"
}' |
apify call first_watch/public-holiday-calendar-by-country --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,first_watch/public-holiday-calendar-by-country"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/t0sscaldioOdbqLgb/builds/QyPYIYzR5cWcEi0ec/openapi.json
