Skip to content

Closing Days

MC0RE edited this page Aug 18, 2026 · 4 revisions

Closing Days

Manage company-wide closing days in Teamleader Focus.

Overview

The Closing Days resource lets you create and delete dates on which your company is closed. Closing days affect scheduling, user availability calculations, and planning across the account.

update() and info() are not supported β€” there are no API endpoints for them.

Access via Teamleader::closingDays().

You write day and read back date. closingDays.add takes a day field; closingDays.list returns each record with id and date. See Response Structure.

This resource returns pagination metadata. It is one of the few that does β€” since v2.2.0 it sends includes=pagination on every list call, so meta.matches gives you a real total count.

Unknown filter keys and sort options throw since v2.2.1. This endpoint has exactly two filters and no sorting at all.

Endpoint

closingDays

Capabilities

Capability Supported
Pagination βœ… Supported, with metadata
Filtering βœ… Supported (date_after, date_before)
Sorting ❌ Not supported
Sideloading ❌ Not supported
Creation βœ… Supported
Update ❌ Not supported
Deletion βœ… Supported

Note on the API endpoint: create() posts to closingDays.add internally, not closingDays.create. The SDK handles this transparently.


Methods

list(array $filters = [], array $options = [])

Returns closing days, optionally filtered by date range. Both filter values are validated as YYYY-MM-DD before the request is sent.

use McoreServices\TeamleaderSDK\Facades\Teamleader;

// All closing days
$closingDays = Teamleader::closingDays()->list();

// Within a date range
$closingDays = Teamleader::closingDays()->list([
    'date_after'  => '2025-01-01',
    'date_before' => '2025-12-31',
]);

// With pagination
$closingDays = Teamleader::closingDays()->list([], [
    'page_size'   => 50,
    'page_number' => 1,
]);

Unknown filter keys and sort options throw before the request is sent:

Teamleader::closingDays()->list(['date' => '2025-12-25']);
// InvalidArgumentException: Unsupported filter key for closingDays.list: date.
// Supported: date_before, date_after.

Teamleader::closingDays()->list([], ['sort' => 'date']);
// InvalidArgumentException: closingDays.list does not support sorting.
// Passed: sort. Records come back in the order the API chooses.

Pagination metadata is always requested. Before v2.2.0 the SDK sent the parameter as include (singular) and only when an include_pagination option was passed. The API silently ignores the singular form, so the metadata never arrived β€” the same defect fixed SDK-wide in v1.2.3. It is now sent as includes on every call, unconditionally. The include_pagination option is redundant but harmless.


create(array $data)

Adds a closing day. The day field must be a valid date in YYYY-MM-DD format β€” validated before the request is sent.

Teamleader::closingDays()->create(['day' => '2025-12-25']);

add(string $day)

Alias for create(['day' => $day]). Accepts the date string directly.

Teamleader::closingDays()->add('2025-12-25');

delete(mixed $id)

Removes a closing day by UUID. Throws if $id is empty.

Teamleader::closingDays()->delete('closing-day-uuid');

Helper Methods

Each of these makes its own API call.

forMonth(string $yearMonth)

Returns closing days for a specific month. Validates the YYYY-MM format.

$closingDays = Teamleader::closingDays()->forMonth('2025-12');
$closingDays = Teamleader::closingDays()->forMonth(date('Y-m'));

forYear(int|string $year)

Returns closing days for a full year. Year must be between 1900 and 2100.

$closingDays = Teamleader::closingDays()->forYear(2025);
$closingDays = Teamleader::closingDays()->forYear(date('Y'));

forDateRange(string $startDate, string $endDate)

Returns closing days between two dates (inclusive). Both dates are validated; start must not be after end.

$closingDays = Teamleader::closingDays()->forDateRange('2025-12-20', '2025-12-31');

upcoming(int $daysAhead = 30)

Returns closing days from today up to $daysAhead days in the future.

$closingDays = Teamleader::closingDays()->upcoming();     // next 30 days
$closingDays = Teamleader::closingDays()->upcoming(90);   // next 90 days

isClosingDay(string $date)

Returns true if the given date is registered as a closing day. Makes one API call, so avoid it in a loop β€” fetch the year once and check against the result instead.

if (Teamleader::closingDays()->isClosingDay('2025-12-25')) {
    // Office is closed
}

bulkAdd(array $dates)

Adds multiple closing days in a loop. Each date is passed through add(). Failures are caught and returned as error objects in the results array β€” they do not throw.

$results = Teamleader::closingDays()->bulkAdd([
    '2025-01-01',
    '2025-04-21',
    '2025-12-25',
    '2025-12-26',
]);

// Each entry is either a success response or:
// ['error' => true, 'date' => '2025-12-25', 'message' => '...']

One API request per date. Adding a year of public holidays costs a dozen or so requests against your rate limit budget.

getCommonHolidays(int $year, string $country = 'BE')

Returns an array of common holiday dates for a year β€” no API call. Useful for seeding bulkAdd().

$holidays = Teamleader::closingDays()->getCommonHolidays(2025);
// ['New Year\'s Day' => '2025-01-01', 'Christmas Day' => '2025-12-25',
//  'Boxing Day' => '2025-12-26', 'Easter Monday' => '2025-04-21']

Three caveats worth knowing before you rely on this:

  • It covers four dates only β€” New Year's Day, Christmas Day, Boxing Day and Easter Monday. It is a starting point, not a Belgian public holiday calendar. Ascension, Whit Monday, Labour Day, the National Holiday, Assumption, All Saints' Day and Armistice Day are all absent.
  • The $country parameter is accepted but ignored. The dates are hard-coded for Belgium regardless of what you pass.
  • Easter Monday needs the calendar PHP extension. It is derived from easter_date(), which lives in ext-calendar and is not present in every PHP build. Since v2.2.1 the method checks for it and returns the three fixed dates without Easter Monday when it is missing. Before v2.2.1 it raised Error: Call to undefined function easter_date().

Check for it if you depend on the Easter date:

$holidays = Teamleader::closingDays()->getCommonHolidays(2025);

if (! isset($holidays['Easter Monday'])) {
    // ext-calendar is not loaded β€” supply the date yourself
}

For anything beyond a rough default, generate the dates yourself and pass them to bulkAdd().


Filters

Filter Type Description
date_after string Start of the period (inclusive). Format YYYY-MM-DD
date_before string End of the period (inclusive). Format YYYY-MM-DD

That is the complete set. Both values are validated as YYYY-MM-DD before the request, and any other key throws.

The API specification's descriptions for these two are swapped β€” it labels date_before as "start of the period" and date_after as "end of the period". The names are the reliable guide: date_after is the lower bound, date_before the upper. The SDK's helpers (forMonth(), forYear(), forDateRange()) use them that way round.

Unknown filter keys throw since v2.2.1. They were previously dropped silently β€” the API ignores keys it does not recognise and returns every closing day with HTTP 200, so a mistyped key produced a plausible but unfiltered result.


Response Structure

list() response

[
    'data' => [
        ['id' => 'closing-day-uuid', 'date' => '2025-12-25'],
        ['id' => 'closing-day-uuid', 'date' => '2025-12-26'],
    ],
    'meta' => [
        'page'    => ['size' => 20, 'number' => 1],
        'matches' => 12,
    ],
    'headers' => ['X-RateLimit-Remaining' => ['199'], /* ... */],
]

The field is date on read, day on write. You pass ['day' => '2025-12-25'] to create(), and read back ['id' => ..., 'date' => '2025-12-25']. Earlier versions of this page showed day in the list response, which is not what the API returns.

meta is real here. Most resources return no pagination metadata at all; this one does, because the SDK sends includes=pagination. meta.matches is a genuine total count, so you can page deterministically rather than waiting for a short page.

create() response

[
    'data'    => ['type' => 'closingDay', 'id' => 'closing-day-uuid'],
    'headers' => [/* ... */],
]

Usage Examples

Add the year's public holidays

$holidays = Teamleader::closingDays()->getCommonHolidays(2025);
Teamleader::closingDays()->bulkAdd(array_values($holidays));

For a complete Belgian calendar, supply your own dates:

Teamleader::closingDays()->bulkAdd([
    '2025-01-01', // Nieuwjaar
    '2025-04-21', // Paasmaandag
    '2025-05-01', // Dag van de Arbeid
    '2025-05-29', // O.L.H. Hemelvaart
    '2025-06-09', // Pinkstermaandag
    '2025-07-21', // Nationale feestdag
    '2025-08-15', // O.L.V. Hemelvaart
    '2025-11-01', // Allerheiligen
    '2025-11-11', // Wapenstilstand
    '2025-12-25', // Kerstmis
]);

Find and delete a specific closing day

$result = Teamleader::closingDays()->forDateRange('2025-12-25', '2025-12-25');

if (! empty($result['data'])) {
    Teamleader::closingDays()->delete($result['data'][0]['id']);
}

Check several dates without repeated calls

isClosingDay() costs one request each. For more than one date, fetch the year once:

$closingDays = Teamleader::closingDays()->forYear((int) date('Y'));
$closedDates = array_column($closingDays['data'], 'date');

$isClosed = in_array('2025-12-25', $closedDates, true);

Cache closing days for the year

$closedDates = Cache::remember('tl_closing_days_'.date('Y'), 86400, fn () =>
    array_column(
        Teamleader::closingDays()->forYear((int) date('Y'))['data'],
        'date'
    )
);

Error Handling

use InvalidArgumentException;
use McoreServices\TeamleaderSDK\Exceptions\TeamleaderException;

// Invalid date format β€” thrown before the request
try {
    Teamleader::closingDays()->add('25-12-2025'); // wrong format
} catch (InvalidArgumentException $e) {
    // 'The "day" field must be a valid date in YYYY-MM-DD format'
}

// Invalid filter format
try {
    Teamleader::closingDays()->list(['date_after' => '01/01/2025']);
} catch (InvalidArgumentException $e) {
    // 'date_after must be in YYYY-MM-DD format'
}

// Unsupported filter key
try {
    Teamleader::closingDays()->list(['date' => '2025-12-25']);
} catch (InvalidArgumentException $e) {
    // 'Unsupported filter key for closingDays.list: date.
    //  Supported: date_before, date_after.'
}

// Sorting is not supported
try {
    Teamleader::closingDays()->list([], ['sort' => 'date']);
} catch (InvalidArgumentException $e) {
    // 'closingDays.list does not support sorting. Passed: sort. Records come
    //  back in the order the API chooses.'
}

// Invalid month format
try {
    Teamleader::closingDays()->forMonth('2025-1');
} catch (InvalidArgumentException $e) {
    // 'Month format must be YYYY-MM'
}

// Reversed date range
try {
    Teamleader::closingDays()->forDateRange('2025-12-31', '2025-01-01');
} catch (InvalidArgumentException $e) {
    // 'Start date must be before or equal to end date'
}

// bulkAdd() catches failures per-entry β€” check results for errors
$results = Teamleader::closingDays()->bulkAdd(['2025-12-25', 'bad-date']);

foreach ($results as $result) {
    if (isset($result['error'])) {
        Log::warning('Bulk add failure', ['message' => $result['message']]);
    }
}

// API-level errors
try {
    Teamleader::closingDays()->delete('closing-day-uuid');
} catch (TeamleaderException $e) {
    Log::error('Teamleader error', ['message' => $e->getMessage()]);
}

Related Resources

  • Day Off Types β€” Categories of leave used with daysOff
  • Days Off β€” Import/delete individual user leave entries
  • Users β€” listDaysOff() reads user leave records
  • User Availability β€” Closing days affect availability calculations
  • Filtering β€” Filter and pagination reference

Clone this wiki locally