# Workdays & Public Holidays Calculator (Poland, Germany) (`danielmichalek/workdays-pl-de`) Actor

Calculate working days, public holidays and crew hours for Poland and all 16 German states. Compare date ranges in batches, add custom days off and export a day-by-day calendar. Offline calculations; no external API or credentials needed.

- **URL**: https://apify.com/danielmichalek/workdays-pl-de.md
- **Developed by:** [Daniel Michałek](https://apify.com/danielmichalek) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$10.00 / 1,000 completed date ranges

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

## Workdays & Public Holidays Calculator (Poland, Germany)

Count working days, public holidays and crew hours for date ranges of up to 3660 days in **Poland** or
**Germany** (nationwide or for any of the 16 German states). Built for payroll and timesheet
checks, project and crew planning, invoicing of day rates, and SLA / delivery-date calculations.

### What it does

For each requested date range the Actor returns:

- the number of calendar days, weekend days, public holidays and **workdays**,
- the list of public holidays in the range (English and local names, national or state scope),
- **hours per worker** and **crew hours** (`billable workdays x hoursPerDay x crewSize`),
- optionally a day-by-day calendar.

Holidays are computed from the statutory rules, with movable feasts derived from the Gregorian
Easter date:

- **Poland** - all nationwide statutory days off (*ustawa o dniach wolnych od pracy*), including
  Epiphany (since 2011), the one-off holiday on 12 November 2018, and Christmas Eve (since 2025). Optionally applies the Labour Code rule
  that a holiday falling on a Saturday grants another day off (*Kodeks pracy art. 130 § 2*).
- **Germany** - nationwide holidays, plus the statewide holidays of the selected state (`BW`,
  `BY`, `BE`, `BB`, `HB`, `HH`, `HE`, `MV`, `NI`, `NW`, `RP`, `SL`, `SN`, `ST`, `SH`, `TH`),
  including one-off days such as Reformation Day 2017, Berlin's Liberation Day 2020/2025,
  and Berlin's 1953 uprising anniversary on 17 June 2028.

### Input

| Field | Type | Default | Description |
| --- | --- | --- | --- |
| `country` | `PL` or `DE` | `PL` | Country whose public holidays apply. |
| `region` | German state code | - | DE only. Adds statewide holidays (inside `ranges` the `DE-BY` form is accepted too). Must be empty for PL. |
| `startDate` | `YYYY-MM-DD` | - | First day of the range (years 2000-2100). |
| `endDate` | `YYYY-MM-DD` | `startDate` | Last day of the range, inclusive (max 3660 days). |
| `workingWeekdays` | array of 1-7 | `[1,2,3,4,5]` | ISO weekdays normally worked (1 = Monday, 7 = Sunday). |
| `hoursPerDay` | string number | `"8"` | Hours per working day per person, > 0 and <= 24, e.g. `"7.5"`. |
| `crewSize` | integer | `1` | Number of people (1-100000). |
| `extraDaysOff` | array of dates | - | Additional non-working days (company holidays, bridge days, the designated day for a Polish Saturday holiday). |
| `compensateSaturdayHolidays` | boolean | `false` | PL only: subtract one billable day for each holiday falling on a non-working Saturday. |
| `includeDays` | boolean | `false` | Add a `days` array with one entry per calendar day. |
| `ranges` | array | - | Batch mode: several ranges in one run. Each item may override any field above (except `includeDays`) and may carry an `id`. |

Example - Polish working time for 2026:

```json
{
  "country": "PL",
  "startDate": "2026-01-01",
  "endDate": "2026-12-31",
  "hoursPerDay": "8",
  "crewSize": 12
}
```

Example - batch, one record per month and one for Bavaria:

```json
{
  "country": "PL",
  "ranges": [
    { "id": "PL-2026-01", "startDate": "2026-01-01", "endDate": "2026-01-31" },
    { "id": "PL-2026-02", "startDate": "2026-02-01", "endDate": "2026-02-28" },
    { "id": "BY-2026", "country": "DE", "region": "BY", "startDate": "2026-01-01", "endDate": "2026-12-31" }
  ]
}
```

### Output

One dataset item per range (shortened; Poland 2026 with default `hoursPerDay` and `crewSize`):

```json
{
  "id": null,
  "rangeIndex": 0,
  "status": "ok",
  "country": "PL",
  "region": null,
  "regionName": null,
  "startDate": "2026-01-01",
  "endDate": "2026-12-31",
  "summary": {
    "calendarDays": 365,
    "weekendDays": 104,
    "holidays": 14,
    "holidaysOnWorkingDays": 8,
    "extraDaysOffApplied": 0,
    "workdays": 253,
    "saturdayHolidays": ["2026-08-15", "2026-12-26"],
    "compensateSaturdayHolidays": false,
    "billableWorkdays": 253,
    "hoursPerDay": 8,
    "crewSize": 1,
    "hoursPerWorker": 2024,
    "crewHours": 2024
  },
  "holidays": [
    { "date": "2026-01-01", "name": "New Year's Day", "localName": "Nowy Rok", "country": "PL", "scope": "national" }
  ],
  "notes": ["Polish holidays falling on Saturday entitle employees to another day off (KP art. 130 § 2); ..."]
}
```

With `includeDays: true` each item also contains `days`:
`{ "date", "weekday", "weekdayName", "isWorkday", "isWeekend", "holiday", "holidayLocal", "extraDayOff" }`.

A range with invalid input (e.g. an impossible date, a region for Poland) is returned with
`status: "error"`, `error` (code) and `message`. If no range can be computed, the run fails.
A run summary is stored in the default key-value store under `OUTPUT`.

### Pricing (pay per event)

| Event | Charged when |
| --- | --- |
| `date-range-computed` | once per range with `status: "ok"` |

Ranges with `status: "error"` are returned but never charged. The price per event is shown on the
Actor's **Pricing** tab in Apify Store. If your maximum charge per run is reached, the Actor
stops after the last paid range and sets `stoppedByChargeLimit: true` in `OUTPUT`.
An explicit zero maximum charge prevents new paid results and charges, including when the SDK interprets zero as an unlimited budget.

### Limitations

- **No scraping, no external API.** Everything is computed offline from statutory rules; results
  do not depend on any website being available.
- **Germany:** holidays that apply only in some municipalities are **not** included - e.g.
  Assumption Day in parts of Bavaria, Corpus Christi in parts of Saxony and Thuringia, and the
  Augsburger Friedensfest. Without `region`, only nationwide German holidays are applied.
- **Poland:** there are no regional public holidays; Saturday-holiday compensation is a simple
  one-day reduction per holiday - the actual designated day off is set by each employer (list it in
  `extraDaysOff` if you know it). Do not combine explicit extra days off with automatic Saturday compensation.
- Years 2000-2100 only; future statutory changes after the release of this version are not
  reflected until the Actor is updated.
- Church or company-specific days off, school holidays and shortened working days are not
  modelled unless you add them via `extraDaysOff`.
- This is a calculation aid, not legal or payroll advice.

### Safe batch limits and recovery

Combined valid ranges are limited to 100,000 calendar days and the input to 1 MB per run. Each full result is durably saved in a `DELIVERY-` record before its custom charge. A restart reconciles existing results and does not blindly charge them again. An ambiguous charge stops for review. The SDK event count is a billing receipt, not proof of creator revenue. Free development runs report zero custom charges.

For a quick synthetic test, January 2026 in Poland with four people and eight hours per day returns 20 working days and 640 crew hours. Calendar rules are an offline snapshot; verify future statutory changes before payroll use.

Legal source for the historical one-off holiday: https://eli.gov.pl/eli/DU/2018/2117/ogl . Christmas Eve amendment: https://eli.gov.pl/eli/DU/2024/1965/ogl .
Berlin's one-off 17 June 2028 holiday: fourth amendment of 10 July 2024, Article 1, GVBl. 2024 p. 460: https://www.berlin.de/sen/justiz/service/gesetze-und-verordnungen/2024/ausgabe-nr-28-vom-2072024-s-457-484.pdf .

### Release packaging

The deployment Dockerfile installs locked dependencies with TLS verification enabled and prints source-file SHA256 values in the build log so the uploaded files can be compared with the reviewed package. It does not require BuildKit secret mounts. A separate local proxy-CA Dockerfile is retained only in verification evidence; that variant passed the local container build and smoke. The production Dockerfile's native Apify build and hosted verification are pending. This packaging adjustment does not change the application code.

# Actor input Schema

## `country` (type: `string`):

PL = Poland (nationwide statutory days off), DE = Germany (nationwide holidays, plus state holidays when a region is selected).

## `region` (type: `string`):

Adds the statewide holidays of this German state. Leave empty for Poland or for nationwide German holidays only.

## `startDate` (type: `string`):

First day of the range (YYYY-MM-DD, years 2000-2100).

## `endDate` (type: `string`):

Last day of the range (YYYY-MM-DD, inclusive). Defaults to the start date. Maximum range: 3660 days.

## `workingWeekdays` (type: `array`):

ISO weekdays that are normally worked: 1 = Monday ... 7 = Sunday. Default Monday-Friday.

## `hoursPerDay` (type: `string`):

Working hours per day per person, greater than 0 and at most 24; decimals allowed, e.g. "7.5".

## `crewSize` (type: `integer`):

Number of people; crew hours = billable workdays x hours per day x crew size.

## `extraDaysOff` (type: `array`):

Additional non-working dates (YYYY-MM-DD), e.g. company holidays or the day designated for a Polish Saturday holiday.

## `compensateSaturdayHolidays` (type: `boolean`):

Poland only: every public holiday falling on a (non-working) Saturday reduces billable workdays by one day (Kodeks pracy art. 130 § 2). Do not combine with extraDaysOff; use one method to avoid double counting.

## `includeDays` (type: `boolean`):

Adds a "days" array with one entry per calendar day (weekday, workday flag, holiday name) to each result.

## `ranges` (type: `array`):

Compute several ranges in one run. Each item may override any field above except includeDays, e.g. \[{ "id": "2026-01", "startDate": "2026-01-01", "endDate": "2026-01-31" }, { "id": "BY-2026", "country": "DE", "region": "BY", "startDate": "2026-01-01", "endDate": "2026-12-31" }]. Leave empty to compute the single range above. Combined valid ranges are limited to 100,000 calendar days per run.

## Actor input object example

```json
{
  "country": "PL",
  "startDate": "2026-01-01",
  "endDate": "2026-12-31",
  "workingWeekdays": [
    1,
    2,
    3,
    4,
    5
  ],
  "hoursPerDay": "8",
  "crewSize": 1,
  "compensateSaturdayHolidays": false,
  "includeDays": false
}
```

# Actor output Schema

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

Complete structured output in the default dataset.

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

No description

# 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 = {
    "startDate": "2026-01-01",
    "endDate": "2026-12-31",
    "workingWeekdays": [
        1,
        2,
        3,
        4,
        5
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("danielmichalek/workdays-pl-de").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 = {
    "startDate": "2026-01-01",
    "endDate": "2026-12-31",
    "workingWeekdays": [
        1,
        2,
        3,
        4,
        5,
    ],
}

# Run the Actor and wait for it to finish
run = client.actor("danielmichalek/workdays-pl-de").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 '{
  "startDate": "2026-01-01",
  "endDate": "2026-12-31",
  "workingWeekdays": [
    1,
    2,
    3,
    4,
    5
  ]
}' |
apify call danielmichalek/workdays-pl-de --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,danielmichalek/workdays-pl-de"
        }
    }
}
```

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/XArYiFeJmoHo2qAWF/builds/ENJyPx09PxdoxeKMe/openapi.json
