PHPackages                             webcito/bs-calendar - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Search &amp; Filtering](/categories/search)
4. /
5. webcito/bs-calendar

ActiveLibrary[Search &amp; Filtering](/categories/search)

webcito/bs-calendar
===================

A jQuery-based Bootstrap calendar plugin with day/week/month/agenda/year views.

2.3.6(2w ago)242926MITJavaScript

Since Mar 11Pushed 1w ago2 watchersCompare

[ Source](https://github.com/ThomasDev-de/bs-calendar)[ Packagist](https://packagist.org/packages/webcito/bs-calendar)[ Docs](https://github.com/ThomasDev-de/bs-calendar)[ Fund](https://www.buymeacoffee.com/thomas81)[ RSS](/packages/webcito-bs-calendar/feed)WikiDiscussions main Synced today

READMEChangelog (10)Dependencies (20)Versions (61)Used By (0)

Bootstrap Calendar Plugin
=========================

[](#bootstrap-calendar-plugin)

[![Version](https://camo.githubusercontent.com/ea1bf67cbeabeab169ca2ef735de92268dbbc7e04a3effb99795e63d0acc5a9b/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f76657273696f6e2d322e332e362d626c7565)](https://camo.githubusercontent.com/ea1bf67cbeabeab169ca2ef735de92268dbbc7e04a3effb99795e63d0acc5a9b/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f76657273696f6e2d322e332e362d626c7565)[![jQuery](https://camo.githubusercontent.com/efce6a9b239f77cbd3635c29e536e7d760d558512489a9ffd64a1ac971ec9918/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6a51756572792d76332e782d6f72616e6765)](https://camo.githubusercontent.com/efce6a9b239f77cbd3635c29e536e7d760d558512489a9ffd64a1ac971ec9918/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6a51756572792d76332e782d6f72616e6765)[![Bootstrap](https://camo.githubusercontent.com/720b83f2edf76717ab18374a868eedb49337f753c44559d45e56cc69f42b6a0d/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f426f6f7473747261702d76352d626c756576696f6c6574)](https://camo.githubusercontent.com/720b83f2edf76717ab18374a868eedb49337f753c44559d45e56cc69f42b6a0d/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f426f6f7473747261702d76352d626c756576696f6c6574)[![License](https://camo.githubusercontent.com/f8df3091bbe1149f398a5369b2c39e896766f9f6efba3477c63e9b4aa940ef14/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d677265656e)](https://camo.githubusercontent.com/f8df3091bbe1149f398a5369b2c39e896766f9f6efba3477c63e9b4aa940ef14/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d677265656e)

`bs-calendar` is a jQuery plugin for Bootstrap 5 calendars with `day`, `4day`, `week`, `month`, `agenda`, and `year` views. It supports remote appointment loading, calendar filters, search, holidays, custom formatting, drag-create, drag-move, tasks, and local appointment add/edit/delete methods.

As of version 2, Bootstrap 4 is no longer supported. Use version `^1` for Bootstrap 4 projects.

[![Calendar Preview](/demo/img/day.png)](/demo/img/day.png)[![Calendar Preview](/demo/img/week.png)](/demo/img/week.png)[![Calendar Preview](/demo/img/month.png)](/demo/img/month.png)[![Calendar Preview](/demo/img/year.png)](/demo/img/year.png)Table of Contents
-----------------

[](#table-of-contents)

- [Requirements](#requirements)
- [Installation](#installation)
- [Quick Start](#quick-start)
- [Run the Demo](#run-the-demo)
- [Core Concepts](#core-concepts)
- [Appointment Data](#appointment-data)
- [Recurring Appointments](#recurring-appointments)
- [Remote Data with `url`](#remote-data-with-url)
- [Add, Edit, and Delete Workflow](#add-edit-and-delete-workflow)
- [Options](#options)
- [Events and Callbacks](#events-and-callbacks)
- [Methods](#methods)
- [Formatters](#formatters)
- [Extras Object](#extras-object)
- [Colors](#colors)
- [Holidays](#holidays)
- [Localization and Translations](#localization-and-translations)
- [Utilities](#utilities)
- [Repository Notes](#repository-notes)
- [Completeness Check](#completeness-check)

Requirements
------------

[](#requirements)

- jQuery `^3`
- Bootstrap `^5` CSS and JavaScript bundle
- Bootstrap Icons `^1`
- PHP and Composer are only needed for running the local demo with the bundled `vendor/` dependencies.

No Node.js build step is required for normal usage. The browser-ready files are shipped in `dist/`.

Installation
------------

[](#installation)

Use CDN/script tags:

```

```

Or install via Composer:

```
composer require webcito/bs-calendar
```

After Composer installation, include `vendor/webcito/bs-calendar/dist/bs-calendar.min.js` together with jQuery, Bootstrap, and Bootstrap Icons.

Quick Start
-----------

[](#quick-start)

```

    $(function () {
        $('#calendar').bsCalendar({
            locale: 'de-DE',
            startView: 'week',
            startWeekOnSunday: false
        });
    });

```

Load appointments from a function:

```
$('#calendar').bsCalendar({
    url(requestData) {
        return fetch('/api/appointments?' + new URLSearchParams(requestData))
            .then(response => response.json());
    }
});
```

Add, edit, and delete appointments locally:

```
const appointment = {
    title: 'New meeting',
    start: '2026-05-08 10:00:00',
    end: '2026-05-08 11:00:00'
};

$('#calendar').bsCalendar('addAppointment', appointment);
$('#calendar').bsCalendar('editAppointment', {id: appointment.id, title: 'Updated meeting'});
$('#calendar').bsCalendar('deleteAppointment', appointment.id);
```

Run the Demo
------------

[](#run-the-demo)

```
composer install
php -S localhost:8000 -t .
```

Open `http://localhost:8000/demo/index.html`.

The demo contains one calendar instance and shows a modal-based add/edit/delete flow using `addAppointment`, `editAppointment`, and `deleteAppointment`.

Core Concepts
-------------

[](#core-concepts)

- `url` controls remote appointment loading. It can be `null`, a URL string, or a function returning a Promise.
- `calendars` defines sidebar filters. Active calendar IDs are always sent as `calendarIds` in remote requests.
- `add.bs.calendar`, `edit.bs.calendar`, and `delete.bs.calendar` are intent events. They tell your application what the user wants; they do not save anything.
- `addAppointment`, `editAppointment`, and `deleteAppointment` mutate only the currently loaded browser-side appointment list. For backend-backed calendars, persist to your backend first or call `refresh` after saving.
- `refresh` reloads data from `url`.
- `render` re-renders already loaded data without calling `url`.
- `year` view uses summary objects (`date`, `total`, optional `content`), not full appointment objects.
- Recurring appointments are normal appointment objects with a `recurrence` object. They are expanded only for loaded appointment views.
- Tasks are normal appointment objects with a `task` object.

Appointment Data
----------------

[](#appointment-data)

For `day`, `4day`, `week`, `month`, `agenda`, and search results, appointments use this shape:

```
{
  "id": 123,
  "title": "Project Kickoff",
  "start": "2026-05-08 10:00:00",
  "end": "2026-05-08 11:00:00",
  "allDay": false,
  "calendarId": "work",
  "description": "Discuss goals and next steps.",
  "color": "primary",
  "icon": "bi bi-briefcase",
  "link": "https://example.com",
  "location": "Room 5A",
  "editable": true,
  "deleteable": true,
  "overlap": false,
  "recurrence": {
    "frequency": "weekly",
    "interval": 1,
    "until": "2026-12-31",
    "daysOfWeek": [5]
  },
  "task": {
    "checked": false,
    "priority": "high",
    "due": "2026-05-08 09:30:00"
  }
}
```

Required fields:

FieldTypeDescription`title``string`Appointment title.`start``string`Start date/time in `YYYY-MM-DD HH:mm:ss`, `YYYY-MM-DD`, or another local date-time format accepted by `parseDateInput`.`end``string`End date/time in `YYYY-MM-DD HH:mm:ss`, `YYYY-MM-DD`, or another local date-time format accepted by `parseDateInput`.Optional fields:

FieldTypeDefaultDescription`id``string` or `number`generated when missingRequired for later `editAppointment` and `deleteAppointment` calls. Missing IDs are generated with `crypto.randomUUID()` when available.`allDay``boolean``false`Treat the appointment as an all-day item.`calendarId``string` or `number`noneUseful for server-side or custom filtering by calendar.`description``string`noneRendered by the default info window formatter as HTML.`color``string``mainColor`Bootstrap color, CSS color, CSS variable, or class combination.`icon``string`appointment/all-day iconBootstrap icon class for this appointment. Task state icons override this for task rendering.`link``string` or `object`noneRendered by the default info window formatter.`location``string`, `array`, or `null`noneRendered by the default info window formatter. Arrays are joined with ``.`editable``boolean`, `string`, or `number``true`Controls whether the info window shows edit/duplicate controls. Boolean-like strings such as `"false"`, `"0"`, and `"no"` are treated as false.`deleteable``boolean``true`Controls whether the info window shows a delete button.`overlap``boolean`, `string`, or `number``false`Day/week/4day view only. Boolean-like `true`, `"true"`, `"1"`, or `"yes"` renders this appointment full-width and stacked instead of side-by-side.`recurrence``object` or `null`noneExpands one source appointment into visible occurrences. See [Recurring appointments](#recurring-appointments).`task``object` or `null`noneIf provided, the appointment is treated as a task. See [Task fields](#task-fields).Reserved field:

FieldDescription`extras`Internal render context generated by bs-calendar. Do not send or persist it as appointment data.All-day appointments:

```
{
  "title": "Conference",
  "start": "2026-05-08",
  "end": "2026-05-09",
  "allDay": true
}
```

The plugin normalizes all-day start/end values to full-day boundaries internally.

Link object:

```
{
  "href": "https://example.com",
  "text": "Open details",
  "target": "_blank",
  "rel": "noopener noreferrer",
  "disabled": false,
  "html": "Open",
  "color": "primary"
}
```

Link object fields:

FieldTypeDefaultDescription`href``string`noneLink URL. Empty links are not rendered.`text``string``href`Link text when `html` is not provided.`target``string``"_blank"`Link target.`rel``string``"noopener noreferrer"`Link `rel` attribute.`disabled``boolean``false`If true, no link is rendered.`html``string`noneRaw HTML content for the link body.`color``string``"primary"`Color used by the default link formatter.### Recurring Appointments

[](#recurring-appointments)

Recurring appointments are stored as one source appointment with a `recurrence` object. bs-calendar expands that source appointment into renderable occurrences for the currently loaded date range. Your database can therefore keep one master record instead of one row per date. In normal appointment views the source appointment itself is not rendered in addition to its occurrences.

Core model:

TermMeaningSource appointmentThe appointment object returned by your backend or added with `addAppointment`. It owns the `recurrence` rule.OccurrenceA generated render item for one matching date. It keeps the source data and adds recurrence metadata.`recurringId`The source appointment ID on a generated occurrence.Minimal weekly example:

```
{
  "id": "weekly-training",
  "title": "Weekly training",
  "start": "2026-06-01 18:00:00",
  "end": "2026-06-01 19:00:00",
  "recurrence": {
    "frequency": "weekly",
    "interval": 1,
    "until": "2026-12-31",
    "daysOfWeek": [1],
    "exceptions": ["2026-07-06"]
  }
}
```

Supported recurrence fields:

FieldTypeDescription`frequency``string``daily`, `weekly`, `monthly`, or `yearly`. Short aliases `day`, `week`, `month`, `year` and `freq` are accepted.`interval``number`Repeat every n frequency units. Missing, invalid, or smaller-than-1 values are treated as `1`.`until``string`Optional inclusive end date. Alias `end` is accepted. Date-only values include the whole day.`count``number`Optional maximum number of series positions. Exceptions still skip their dates.`daysOfWeek``array`Weekly weekdays using JavaScript weekday numbers, `0` Sunday through `6` Saturday. Defaults to the start weekday.`exceptions``array`Date-only values to skip, e.g. `["2026-07-06"]`. Time parts are ignored and normalized to the local date.Occurrence fields generated by bs-calendar:

FieldExampleDescription`id``weekly-training__2026-06-08`Generated from the source ID and occurrence date.`start``2026-06-08 18:00:00`Occurrence start with the source time preserved.`end``2026-06-08 19:00:00`Occurrence end with the source duration preserved.`recurringId``weekly-training`Source appointment ID.`recurrenceDate``2026-06-08`Local date represented by this occurrence.`recurrenceIndex``1`Zero-based index in the generated series positions.`isOccurrence``true`Marks the item as a generated occurrence rather than the source record.`after-load.bs.calendar`, appointment formatters, and intent events receive the expanded occurrence objects. Use `appointment.recurringId` or `extras.recurrence.recurringId` whenever you need to map an occurrence back to the source record.

Frequency behavior:

FrequencyBehavior`daily`Matches every `interval` days from the source start date.`weekly`Matches selected `daysOfWeek` in every `interval` weeks from the source start week.`monthly`Matches the same day of month as the source start date. Months without that day are skipped.`yearly`Matches the same month and day as the source start date.Multiple weekdays:

```
{
  "id": "training",
  "title": "Training",
  "start": "2026-06-01 18:00:00",
  "end": "2026-06-01 19:00:00",
  "recurrence": {
    "frequency": "weekly",
    "daysOfWeek": [1, 3],
    "until": "2026-06-30"
  }
}
```

This creates Monday and Wednesday occurrences from `2026-06-01` onward.

Every second week with one skipped date:

```
{
  "id": "team-sync",
  "title": "Team sync",
  "start": "2026-06-02 09:30:00",
  "end": "2026-06-02 10:00:00",
  "recurrence": {
    "frequency": "weekly",
    "interval": 2,
    "until": "2026-08-31",
    "exceptions": ["2026-07-14"]
  }
}
```

Client-side local API behavior:

```
$('#calendar').on('edit.bs.calendar', function (event, appointment) {
    event.preventDefault();

    const sourceId = appointment.recurringId || appointment.id;

    $('#calendar').bsCalendar('editAppointment', {
        id: sourceId,
        title: 'Updated series title'
    });
});
```

`addAppointment`, `editAppointment`, and `deleteAppointment` work on the source appointment list. If you pass a generated occurrence object to `editAppointment` or `deleteAppointment`, bs-calendar uses `recurringId` and updates/removes the source appointment. This first recurrence API does not create per-occurrence overrides.

Formatter example:

```
$('#calendar').bsCalendar({
    formatter: {
        month(appointment, extras) {
            const marker = extras.recurrence.isOccurrence ? '[recurring] ' : '';
            return `${marker}${appointment.title}`;
        }
    }
});
```

Remote data guidance:

- For `day`, `4day`, `week`, `month`, and `agenda`, your `url` receives `fromDate` and `toDate`. Return recurring source appointments that can have at least one occurrence in that range.
- Do not filter recurring source appointments only by `start` date. A weekly source from January can still produce a June occurrence.
- A practical server-side overlap check is: source `start = fromDate`. If you use `count`, your backend may need its own recurrence check for exact filtering.
- `year` view uses summary rows (`date`, `total`, optional `content`), so recurrence expansion is not applied there. Return already counted year summary data.
- Search is controlled by your `url` response. For broad search across a long-running series, return the matching source or concrete rows your application wants to show.

Current boundaries:

- Supported rules are intentionally simple: daily, weekly, monthly, yearly, interval, until/end, count, weekly days, and date exceptions.
- RRULE strings, nth weekday rules, timezone-specific recurrence sets, moved occurrences, and edited single occurrences are not supported yet.
- Occurrences inherit the source appointment fields. Store exceptions or future per-occurrence overrides in your own backend model.

### Task Fields

[](#task-fields)

An appointment is treated as a task when it contains a truthy `task` object.

FieldTypeDefaultDescription`checked``boolean``false`Whether the task is completed. Completed tasks render muted/struck through.`priority``string``"normal"`Supported values are `"low"`, `"normal"`, and `"high"`. Missing, empty, or unsupported values are normalized to `"normal"`.`due``string` or `null``null`Optional due date/time. If it is in the past and the task is not checked, `task.isOverdue` is generated internally.Task behavior:

- Task icons use `icons.task`, `icons.taskDone`, and `icons.taskOverdue`.
- Clicking a task icon toggles `task.checked` locally and fires `task-status-changed.bs.calendar`.
- The global task sidebar control is shown when `showTasks` is `true`.
- Task visibility state is sent to normal view and search requests as `showTasks`.
- `task.isOverdue` is internal render state. You may read it in callbacks, but you should not persist it as source data.

Remote Data with `url`
----------------------

[](#remote-data-with-url)

`url` is the appointment data source. It accepts three value types:

ValueBehavior`null`No remote appointment request is made. The current appointment list is cleared and `after-load.bs.calendar` fires with an empty array. Holidays can still be loaded.`string`The plugin sends a jQuery AJAX `GET` request to that URL with `requestData` as query data. The response must match the view/search response contract below.`function`The function is called as `url(requestData)` and must return a Promise/thenable resolving to the response data.String URL example:

```
$('#calendar').bsCalendar({
    url: '/api/appointments'
});
```

Function URL example:

```
$('#calendar').bsCalendar({
    url(requestData) {
        return fetch('/api/appointments?' + new URLSearchParams(requestData))
            .then(response => response.json());
    }
});
```

Request data in normal appointment views:

ViewRequest fields`day`, `4day`, `week`, `month`, `agenda``fromDate`, `toDate`, `view`, `showTasks`, `calendarIds``year``year`, `view`, `showTasks`, `calendarIds`Request data in search mode:

FieldDescription`search`Search string from the search input. Empty searches are skipped and return an empty local list.`limit`Page size from `options.search.limit`.`offset`Current search offset.`showTasks`Current task visibility state.`calendarIds`Active calendar IDs, always an array.Normal response for `day`, `4day`, `week`, `month`, and `agenda`:

```
[
  {
    "id": 1,
    "title": "Meeting",
    "start": "2026-05-08 10:00:00",
    "end": "2026-05-08 11:00:00",
    "color": "primary"
  }
]
```

Search response:

```
{
  "rows": [
    {
      "id": 1,
      "title": "Meeting",
      "start": "2026-05-08 10:00:00",
      "end": "2026-05-08 11:00:00"
    }
  ],
  "total": 42
}
```

Year-view response:

```
[
  {
    "date": "2026-05-08",
    "total": 3,
    "content": "3 appointments"
  }
]
```

Year summary fields:

FieldTypeRequiredDescription`date``string`YesDay in `YYYY-MM-DD` format.`total``number`YesBadge number shown in year view. Must be greater than `0`.`content``string`NoPopover body. HTML rendering is enabled. Defaults to `total`.Use `queryParams` to append custom request values:

```
$('#calendar').bsCalendar({
    url: '/api/appointments',
    queryParams(requestData) {
        return {
            userId: $('#user').val(),
            showTasks: requestData.showTasks
        };
    }
});
```

`queryParams` receives the generated `requestData` and should return an object. The returned object is merged into the request. Protected keys `fromDate`, `toDate`, `year`, and `view` cannot be overridden.

You can also change remote loading at runtime:

```
$('#calendar').bsCalendar('refresh', {
    url: '/api/other-appointments',
    queryParams(requestData) {
        return {teamId: 5};
    }
});
```

Add, Edit, and Delete Workflow
------------------------------

[](#add-edit-and-delete-workflow)

`add.bs.calendar`, `edit.bs.calendar`, and `delete.bs.calendar` are intent events. They let your app open a modal, confirm destructive actions, validate input, save to a backend, and then update the calendar.

Callback options receive the same payloads:

EventCallbackPayload`add.bs.calendar``onAdd(data, dragExtras)`Proposed start/end for a new appointment.`edit.bs.calendar``onEdit(appointment, extras, dragExtras)`Current appointment plus render context.`delete.bs.calendar``onDelete(appointment, extras)`Appointment selected for deletion.After a local mutation method has succeeded, the calendar fires completion events:

EventCallbackPayload`added.bs.calendar``onAdded(appointment, extras)`Appointment that was added.`edited.bs.calendar``onEdited(appointment, extras)`Appointment after the local update.`deleted.bs.calendar``onDeleted(appointment, extras)`Appointment that was removed.When drag-create is used, `dragExtras` contains the proposed `start`, `end`, hour-slot rule availability, and appointment duration rule availability. When drag-move or drag-resize is used, `appointment` still contains the original appointment and `dragExtras` contains the proposed new range.

If `hourSlots.rules[].mode` is `blocked` or `exclusive`, interactive creation, drag-moving, and drag-resizing respect those rules. Day/week/4day drag-create, drag-move, and drag-resize clamp to the nearest valid rule edge while dragging; invalid click-create and invalid drop targets do not fire `add.bs.calendar` or `edit.bs.calendar`.

For drag-create, drag-move, and drag-resize, the allowed interval starts as every `exclusive` range for that weekday. If no `exclusive` range exists, the whole visible `hourSlots.start` to `hourSlots.end` range is allowed. `blocked` ranges are then subtracted from those intervals. `preferred` ranges do not block dragging.

For backend-backed calendars, save to your backend first and then either call `refresh` so the updated data is loaded from `url`, or call `addAppointment`, `editAppointment`, or `deleteAppointment` for an immediate local update and ensure the backend returns the same data on the next `refresh`.

Options
-------

[](#options)

All options can be passed during initialization:

```
$('#calendar').bsCalendar({
    locale: 'de-DE',
    startView: 'week'
});
```

Options may also be supplied through jQuery `data-*` attributes. JavaScript options override data attributes. Some options can be changed later with `updateOptions`.

OptionTypeDefaultDescription`showAbout``boolean``true`Shows the About dropdown.`locale``string``"en-GB"`Locale for labels and date formatting. Underscores are normalized to hyphens.`title``string` or `null``null`HTML/string title in the toolbar.`startWeekOnSunday``boolean``true`If `false`, weeks start on Monday.`navigateOnWheel``boolean``true`Enables mouse-wheel navigation over the calendar.`rounded``number``5`Bootstrap rounded level `0` to `5`. Invalid values fall back to `5`.`border``string``"border border-0 rounded-0 shadow"`Bootstrap classes used by bordered calendar UI elements.`search``object` or `null``{limit: 10, offset: 0}`Search config. Set `null` to disable search UI.`search.limit``number``10`Number of search results per page.`search.offset``number``0`Initial search offset.`startDate``Date` or `string``new Date()`Initial reference date. String values are parsed during initialization.`startView``string``"month"`Initial view. Allowed values: `day`, `4day`, `week`, `month`, `agenda`, `year`. Must be enabled in `views`.`mainColor``string``"primary"`Default color used by highlights, controls, and appointments.`views``array` or comma-separated `string``["year", "month", "agenda", "week", "4day", "day"]`Enabled views. Invalid entries are removed; duplicates are removed; empty result falls back to all possible views.`holidays``object` or `null``null`OpenHolidays configuration. See [Holidays](#holidays).`showAddButton``boolean``true`Shows the toolbar add button.`draggable``boolean``false`Enables drag-create in day/week/4day view, drag-move in day/week/4day/month view, and drag-resize from timed appointment edges in day/week/4day view. Touch locks native scrolling while a drag gesture is pending or active.`draggableSnapMinutes``number``5`Snap interval in minutes for drag-create/move/resize in day/week/4day view. Minimum is `1`.`translations``object``{search, searchNoResult}` merged with locale translationsCustom UI translations. See [Localization and Translations](#localization-and-translations).`icons``object`see [Icons](#icons)Bootstrap icon classes.`url``null`, `string`, or `function``null`Appointment data source. See [Remote Data with `url`](#remote-data-with-url).`queryParams``function` or `null``null`Adds custom request params before loading appointments.`topbarAddons`selector, element, jQuery object, or `null``null`Element(s) inserted after the top toolbar.`sidebarAddons`selector, element, jQuery object, or `null``null`Element(s) appended to the sidebar.`formatter``object`see [Formatters](#formatters)Custom render functions.`hourSlots``object``{height: 30, start: 0, end: 24}`Day/week/4day hour grid configuration.`hourSlots.height``number``30`Height in pixels for one hour. Minimum normalized value is `1`.`hourSlots.start``number` or `string``0`First visible hour. Normalized to `0` to `23`. Supports decimals and `HH:mm` strings.`hourSlots.end``number` or `string``24`Last visible hour boundary. Normalized to `1` to `24` and kept greater than `start`. Supports decimals and `HH:mm` strings.`hourSlots.rules``object`, `array`, or `null``null`Highlight and availability rules for specific time slots. Accepts one object or an array of objects.`hourSlots.rules.startTime``string``'08:00'`Start time for each rule range (format `HH:mm`).`hourSlots.rules.endTime``string``'17:00'`End time for each rule range (format `HH:mm`).`hourSlots.rules.daysOfWeek``array``[1,2,3,4,5]`Days of the week (0-6, Sun-Sat) for each rule range.`hourSlots.rules.mode``string``'highlight'``exclusive` allows creation/move only inside the range, `blocked` prevents overlapping creation/move, `preferred` marks preferred work time, omitted mode only highlights.`hourSlots.rules.color``string``rgba(0,0,0,0.05)`Color/styling for each rule range, normalized with `getColors`.`appointmentRules``object``{durationMinutes: null, durationStepMinutes: null, minDurationMinutes: null, maxDurationMinutes: null}`Timed appointment duration rules for click-create, drag-create, drag-move, and drag-resize.`appointmentRules.durationMinutes``number` or `null``null`Exact required duration in minutes. If set, resize handles are hidden for timed appointments.`appointmentRules.durationStepMinutes``number` or `null``null`Allows only durations divisible by this value, e.g. `45` allows 45/90/135-minute appointments.`appointmentRules.minDurationMinutes``number` or `null``null`Minimum allowed timed appointment duration.`appointmentRules.maxDurationMinutes``number` or `null``null`Maximum allowed timed appointment duration.`calendars``array` or `null``null`Sidebar calendar filters.`onAll``function`, function-name `string`, or `null``null`Receives every event name and payload.`onInit``function`, function-name `string`, or `null``null`Same payload as `init.bs.calendar`.`onAdd``function`, function-name `string`, or `null``null`Same payload as `add.bs.calendar`.`onAdded``function`, function-name `string`, or `null``null`Same payload as `added.bs.calendar`.`onEdit``function`, function-name `string`, or `null``null`Same payload as `edit.bs.calendar`.`onEdited``function`, function-name `string`, or `null``null`Same payload as `edited.bs.calendar`.`onDuplicate``function`, function-name `string`, or `null``null`Same payload as `duplicate.bs.calendar`.`onDelete``function`, function-name `string`, or `null``null`Same payload as `delete.bs.calendar`.`onDeleted``function`, function-name `string`, or `null``null`Same payload as `deleted.bs.calendar`.`onView``function`, function-name `string`, or `null``null`Same payload as `view.bs.calendar`.`onBeforeLoad``function`, function-name `string`, or `null``null`Same payload as `before-load.bs.calendar`.`onAfterLoad``function`, function-name `string`, or `null``null`Same payload as `after-load.bs.calendar`.`onTaskStatusChanged``function`, function-name `string`, or `null``null`Same payload as `task-status-changed.bs.calendar`.`onShowInfoWindow``function`, function-name `string`, or `null``null`Same payload as `show-info-window.bs.calendar`.`onHideInfoWindow``function`, function-name `string`, or `null``null`Same payload as `hide-info-window.bs.calendar`.`onNavigateForward``function`, function-name `string`, or `null``null`Same payload as `navigate-forward.bs.calendar`.`onNavigateBack``function`, function-name `string`, or `null``null`Same payload as `navigate-back.bs.calendar`.`storeState``boolean``false`Persists selected view, active calendars, and task visibility in `localStorage`.`showTasks``boolean``true`Enables task UI and the global task toggle in the sidebar.`debug``boolean``false`Enables debug logging.### Hour Slot Rule Priority

[](#hour-slot-rule-priority)

`hourSlots.rules` affect availability in this order:

1. `blocked` wins whenever the requested time range overlaps a blocked range.
2. `exclusive` applies next. If any `exclusive` rule exists for the weekday, work is allowed only when the requested time range is fully contained in an `exclusive` range.
3. `preferred` allows work and sets `isPreferred`.
4. `highlight` or an omitted `mode` is visual only and does not block work.
5. If no rule matches, work is allowed.

This means overlapping `blocked` and `exclusive` rules are treated as blocked. Overlapping `blocked` and `preferred` rules are also treated as blocked.

Slot background colors use the same mode-aware priority. For overlapping colors, the winning availability rule provides the color.

### Appointment Duration Rules

[](#appointment-duration-rules)

`appointmentRules` validates timed appointment durations during interactive creation, moving, and resizing. It is separate from `hourSlots.rules`: `hourSlots.rules` describes when work is allowed, while `appointmentRules` describes how long a timed appointment may be.

Fixed 60-minute appointments:

```
$('#calendar').bsCalendar({
    appointmentRules: {
        durationMinutes: 60
    }
});
```

With `durationMinutes`, click-create and drag-create propose exactly that duration. Resize handles are not rendered for timed appointments because the duration is fixed.

45-minute coaching blocks:

```
$('#calendar').bsCalendar({
    draggableSnapMinutes: 15,
    appointmentRules: {
        durationStepMinutes: 45,
        minDurationMinutes: 45
    }
});
```

This allows 45, 90, 135 minutes, and so on. `draggableSnapMinutes` still controls the pointer grid for start/end times; the duration rule then snaps the appointment length to the nearest valid duration.

Flexible 30-minute blocks between 30 and 120 minutes:

```
$('#calendar').bsCalendar({
    appointmentRules: {
        durationStepMinutes: 30,
        minDurationMinutes: 30,
        maxDurationMinutes: 120
    }
});
```

If `durationMinutes` is set, it wins over step/min/max rules. Otherwise, min/max are applied first and `durationStepMinutes` restricts the result to valid multiples. Drag event payloads expose the validation result as `dragExtras.appointmentRules` with `canWork`, `durationMinutes`, `rules`, and `violations`.

Calendar filters:

```
$('#calendar').bsCalendar({
    calendars: [
        {id: 'personal', title: 'Personal', color: 'primary', active: true},
        {id: 'work', title: 'Work', color: 'danger', active: true}
    ]
});
```

Calendar fields:

FieldTypeRequiredDefaultDescription`id``string` or `number`YesnoneSent in `calendarIds`. Entries without an ID are removed.`title``string`No`Calendar {i}`Sidebar label.`color``string`No`mainColor`Sidebar color, normalized with `getColors`.`active``boolean`No`true`Initial filter state.### Translations

[](#translations)

KeyEnglish defaultDescription`today``"Today"`Text for the Today button.`day``"Day"`Label for the day view.`4day``"4 Days"`Label for the 4-day view.`week``"Week"`Label for the week view.`month``"Month"`Label for the month view.`year``"Year"`Label for the year view.`agenda``"Agenda"`Label for the agenda list view.`allDay``"All day"`Label for all-day appointments.`search``"Type and press Enter"`Search placeholder.`searchNoResult``"No appointment found"`Empty search message.`tasks``"Tasks"`Label for the task toggle and task badge.`taskPriorityHigh``"High"`Label for high-priority task badge.`taskPriorityNormal``"Medium"`Label for normal-priority task badge.`taskPriorityLow``"Low"`Label for low-priority task badge.`duplicate``"Duplicate"`Info-window duplicate action label.### Icons

[](#icons)

KeyDefault`day``"bi bi-calendar-day"``4day``"bi bi-calendar-range"``week``"bi bi-kanban"``month``"bi bi-calendar-month"``year``"bi bi-calendar4"``agenda``"bi bi-list-ul"``about``"bi bi-info-circle"``add``"bi bi-plus-lg"``menu``"bi bi-layout-sidebar-inset"``search``"bi bi-search"``prev``"bi bi-chevron-left"``next``"bi bi-chevron-right"``link``"bi bi-box-arrow-up-right"``appointment``"bi bi-clock"``appointmentAllDay``"bi bi-brightness-high"``task``"bi bi-circle"``taskDone``"bi bi-check2-circle"``taskOverdue``"bi bi-exclamation-circle"`Events and Callbacks
--------------------

[](#events-and-callbacks)

Events use the `.bs.calendar` namespace:

```
$('#calendar').on('view.bs.calendar', function (event, view) {
    console.log(view);
});
```

Callback options receive the same payload as their matching event, without the jQuery event object. Callback options may be functions or global function-name strings.

EventCallback optionjQuery handler payloadDescription`all.bs.calendar``onAll(eventName, ...params)``(event, eventName, ...params)`Fired before every specific event except `all` itself. `eventName` includes `.bs.calendar`.`init.bs.calendar``onInit()``(event)`Calendar initialized.`add.bs.calendar``onAdd(data, dragExtras)``(event, data, dragExtras)`Add intent from toolbar, day/hour click, date click, or drag-create.`added.bs.calendar``onAdded(appointment, extras)``(event, appointment, extras)`Appointment added with `addAppointment`.`edit.bs.calendar``onEdit(appointment, extras, dragExtras)``(event, appointment, extras, dragExtras)`Edit intent from info window or drag-move.`edited.bs.calendar``onEdited(appointment, extras)``(event, appointment, extras)`Appointment updated with `editAppointment`.`duplicate.bs.calendar``onDuplicate(appointment, extras)``(event, appointment, extras)`Duplicate action clicked in the info window.`delete.bs.calendar``onDelete(appointment, extras)``(event, appointment, extras)`Delete intent from info window.`deleted.bs.calendar``onDeleted(appointment, extras)``(event, appointment, extras)`Appointment removed with `deleteAppointment`.`view.bs.calendar``onView(view)``(event, view)`View rendered or changed.`navigate-forward.bs.calendar``onNavigateForward(view, from, to)``(event, view, from, to)`Forward navigation completed. `from` and `to` are `Date` objects.`navigate-back.bs.calendar``onNavigateBack(view, from, to)``(event, view, from, to)`Backward navigation completed. `from` and `to` are `Date` objects.`show-info-window.bs.calendar``onShowInfoWindow(appointment, extras)``(event, appointment, extras)`Info window is about to be shown for a newly created info modal.`hide-info-window.bs.calendar``onHideInfoWindow()``(event)`Info window closed by outside click.`before-load.bs.calendar``onBeforeLoad(requestData)``(event, requestData)`Fired after `requestData` is built and before remote loading starts.`after-load.bs.calendar``onAfterLoad(appointments)``(event, appointments)`Fired after appointment data has been normalized and stored.`task-status-changed.bs.calendar``onTaskStatusChanged(appointment)``(event, appointment)`A task checkbox icon was toggled locally.Methods
-------

[](#methods)

Call methods with the jQuery plugin method syntax:

```
$('#calendar').bsCalendar('refresh');
```

MethodParamsDescription`refresh`optional `{url, view, queryParams}`Reloads and renders. Can update `settings.url`, switch to an enabled `view`, and replace `queryParams` before loading.`render`noneRe-renders current loaded data without fetching.`clear`noneClears rendered appointments and local appointment data. Ignored in search mode.`updateOptions``object`Deep-merges runtime options, normalizes settings, rebuilds affected UI, and fetches data.`addAppointment`appointment objectAdds one local appointment, generates an ID if missing, normalizes it, renders, and fires `added.bs.calendar`. Ignored in search mode and year view.`editAppointment`appointment object with `id`, or `{id, appointment}` / `{id, data}`Deep-merges changes into the currently loaded appointment with the same ID, normalizes it, renders, and fires `edited.bs.calendar`. Ignored in search mode and year view.`editApointment`same as `editAppointment`Backward-compatible misspelled alias.`deleteAppointment`appointment `id` or object with `id`Deletes one currently loaded appointment by ID, renders, and fires `deleted.bs.calendar`. Ignored in search mode and year view.`destroy`noneRemoves generated markup/events, aborts outstanding appointment requests, removes the info modal, and restores the original element state.`setDate`date string, `Date`, or `{date, view}`Sets the visible reference date and optionally switches to an enabled view. Ignored in search mode.`setToday`optional view stringSets the reference date to today and optionally switches to an enabled view. Ignored in search mode.`setView`view stringSwitches to an enabled view and reloads/renders. Ignored in search mode.`setHourSlotRules``object`, `array`, or `null`Updates `hourSlots.rules` and refreshes the grid.`setLocale`locale stringNormalizes the locale and applies it through `updateOptions`. Ignored in search mode.Examples:

```
$('#calendar').bsCalendar('refresh', {url: '/api/appointments'});
$('#calendar').bsCalendar('render');
$('#calendar').bsCalendar('clear');
$('#calendar').bsCalendar('updateOptions', {locale: 'fr-FR'});
$('#calendar').bsCalendar('addAppointment', {title: 'Call', start: '2026-05-08 10:00:00', end: '2026-05-08 10:30:00'});
$('#calendar').bsCalendar('editAppointment', {id: 123, title: 'Updated'});
$('#calendar').bsCalendar('deleteAppointment', 123);
$('#calendar').bsCalendar('setDate', {date: '2026-05-08', view: 'day'});
$('#calendar').bsCalendar('setToday', 'week');
$('#calendar').bsCalendar('setView', 'month');
$('#calendar').bsCalendar('setHourSlotRules', [
    {
        daysOfWeek: [1, 2, 3, 4, 5],
        startTime: '09:00',
        endTime: '17:00',
        mode: 'exclusive',
        color: 'rgba(25, 135, 84, 0.055)'
    },
    {
        daysOfWeek: [6],
        startTime: '10:00',
        endTime: '14:00',
        mode: 'preferred',
        color: 'rgba(13, 110, 253, 0.045)'
    },
    {
        daysOfWeek: [0],
        startTime: '00:00',
        endTime: '23:59',
        mode: 'blocked',
        color: 'rgba(220, 53, 69, 0.06)'
    }
]);
$('#calendar').bsCalendar('setLocale', 'de-DE');
$('#calendar').bsCalendar('destroy');
```

There is no public `getAppointment` method. The plugin only stores the currently loaded view/search appointment slice, so ID lookup would not be a reliable global data access API.

Formatters
----------

[](#formatters)

Formatters customize appointment, search, holiday, info-window, and duration rendering.

```
$('#calendar').bsCalendar({
    formatter: {
        day(appointment, extras) {
            return appointment.title;
        },
        week(appointment, extras) {
            return appointment.title;
        },
        allDay(appointment, extras, view) {
            return appointment.title;
        },
        month(appointment, extras) {
            return appointment.title;
        },
        agenda(appointment, extras) {
            return appointment.title;
        },
        search(appointment, extras) {
            return appointment.title;
        },
        holiday(holiday, view) {
            return holiday.name?.[0]?.text || holiday.title;
        },
        window(appointment, extras) {
            return Promise.resolve(`${appointment.title}`);
        },
        duration(duration) {
            return `${duration.totalMinutes} min`;
        }
    }
});
```

Formatter signatures:

FormatterSignatureReturn`day``(appointment, extras)`HTML/string`week``(appointment, extras)`HTML/string`allDay``(appointment, extras, view)`HTML/string`month``(appointment, extras)`HTML/string`agenda``(appointment, extras)`HTML/string`search``(appointment, extras)`HTML/string`holiday``(holiday, view)`HTML/string`window``(appointment, extras)`Promise resolving to HTML/string`duration``(duration)`stringIn month view, day cells automatically show a small expand button when their appointment list overflows. The expanded month-day overlay uses `formatter.monthExpanded(appointment, extras)` so the compact `formatter.month` output can stay single-line.

Extras Object
-------------

[](#extras-object)

`extras` is generated for each appointment after loading/normalization.

FieldDescription`locale`Locale used for formatting.`icon`Appointment or task icon class used for rendering.`colors.origin`Original color value.`colors.backgroundColor`Computed background color.`colors.backgroundImage`Computed background image/gradient.`colors.color`Computed text color.`colors.classList`Computed Bootstrap classes, if applicable.`colors.hex`Computed hexadecimal color (`#rrggbb`) when resolvable, otherwise `null`.`start.date`Start date in `YYYY-MM-DD`.`start.time`Start time in `HH:MM:SS`.`end.date`End date in `YYYY-MM-DD`.`end.time`End time in `HH:MM:SS`.`duration.days`Full days.`duration.hours`Remaining hours.`duration.minutes`Remaining minutes.`duration.seconds`Remaining seconds.`duration.totalMinutes`Total minutes.`duration.totalSeconds`Total seconds.`duration.formatted`Formatter output from `formatter.duration`.`hourSlotRules`Mode-aware availability object derived from `hourSlots.rules`.`displayDates`Per-day display data used by month/week/day rendering.`allDay`Whether the appointment is all-day.`inADay`Whether it stays within one calendar day.`isToday`Whether the start date is today.`isNow`Whether the current time is between start and end.`recurrence`Recurrence metadata for source appointments and generated occurrences.`extras.recurrence` contains:

FieldDescription`isRecurring`Whether this appointment has a recurrence rule.`isOccurrence`Whether this item is a generated occurrence.`recurringId`Source appointment ID for generated occurrences.`occurrenceId`Current rendered appointment ID.`occurrenceDate`Date represented by this occurrence.`occurrenceIndex`Zero-based occurrence index when available.`frequency`Normalized frequency value.`interval`Normalized interval value.`extras.hourSlotRules` and drag `dragExtras.hourSlotRules` contain:

FieldDescription`canWork``false` for blocked ranges and outside exclusive ranges, otherwise `true`.`mode`Matching mode: `exclusive`, `preferred`, `blocked`, `highlight`, or `null`.`reason`Availability reason: `available`, `blocked`, `exclusive`, `outsideExclusive`, `preferred`, or `highlighted`.`range`The matching `hourSlots.rules` object, or `null`.`inRange`Whether the appointment is fully contained in the matching range.`isBlocked`Whether the range blocks work.`isPreferred`Whether the range marks preferred work time.`isExclusive`Whether exclusive mode affects this appointment.`displayDates[]` entries contain:

FieldDescription`date`Display date.`day`Weekday index.`times.start`Visible start time for that day.`times.end`Visible end time for that day.`visibleInWeek`Whether this date is visible in week/4day view.`visibleInMonth`Whether this date is visible in month view.Year-view summary objects get a smaller `extras` object with `colors`, `isToday`, and `isNow`.

Colors
------

[](#colors)

Supported color inputs:

- Bootstrap theme names or class combinations, e.g. `primary`, `danger opacity-75 gradient`
- Hex colors, e.g. `#ff5733`
- RGB/RGBA values
- CSS variables, e.g. `var(--bs-primary)`
- Named CSS colors, e.g. `steelblue`

Use the public color helper:

```
const colors = $.bsCalendar.utils.getColors('#ff5733', 'primary');

// {
//   origin: '#ff5733',
//   backgroundColor: '#ff5733',
//   backgroundImage: 'none',
//   color: '#000000' or '#FFFFFF',
//   hex: '#ff5733'
// }
```

Holidays
--------

[](#holidays)

`holidays` uses the OpenHolidays API. If `country` or `language` is missing, bs-calendar derives it from `locale`.

```
$('#calendar').bsCalendar({
    holidays: {
        country: 'DE',
        federalState: 'BE',
        language: 'DE'
    }
});
```

KeyTypeDefaultDescription`country``string` or `null`locale countryISO 3166-1 alpha-2 country code.`federalState``string` or `null``null`Subdivision/state code. Required for school holidays.`language``string` or `null`locale languageISO 639-1 language code.If `url` is `null`, holidays can still be loaded and rendered.

Localization and Translations
-----------------------------

[](#localization-and-translations)

The `locale` option has two responsibilities:

- It controls date/time formatting through `Intl.DateTimeFormat`.
- Its language part selects the built-in translation object. For example, `de-DE`, `de-AT`, and `de_CH` all use the `de` translations after locale normalization.

If no matching language exists, bs-calendar falls back to English.

### Built-In Languages

[](#built-in-languages)

CodeLanguage`ar`Arabic`he`Hebrew`zh`Chinese Simplified`en`English`de`German`es`Spanish`fr`French`it`Italian`pt`Portuguese`nl`Dutch`pl`Polish`ru`Russian`uk`Ukrainian`tr`Turkish`ja`Japanese`ko`Korean`hi`Hindi`id`Indonesian`vi`Vietnamese`th`Thai`cs`Czech`sv`Swedish`da`Danish`no`Norwegian`fi`Finnish`ro`Romanian`el`Greek### Translation Keys

[](#translation-keys)

All built-in translation objects currently use these keys:

KeyEnglish defaultUsed for`today``"Today"`Today toolbar button.`day``"Day"`Day view label.`4day``"4 Days"`4-day view label.`week``"Week"`Week view label.`month``"Month"`Month view label.`year``"Year"`Year view label.`agenda``"Agenda"`Agenda list view label.`allDay``"All day"`All-day appointment label.`search``"Type and press Enter"`Search input placeholder.`searchNoResult``"No appointment found"`Empty search result message.`tasks``"Tasks"`Task sidebar toggle and task badge label.`taskPriorityHigh``"High"`High-priority task badge.`taskPriorityNormal``"Medium"`Normal-priority task badge.`taskPriorityLow``"Low"`Low-priority task badge.`duplicate``"Duplicate"`Duplicate action in the info-window dropdown.### Selecting A Locale

[](#selecting-a-locale)

```
$('#calendar').bsCalendar({
    locale: 'de-DE'
});
```

Underscores are normalized, so `de_DE` is treated as `de-DE`.

### Overriding Strings Per Instance

[](#overriding-strings-per-instance)

You can override individual strings without redefining the whole language. Custom `translations` are merged with the selected built-in language.

```
$('#calendar').bsCalendar({
    locale: 'en-GB',
    translations: {
        today: 'Now',
        allDay: 'Full day',
        search: 'Find appointments...',
        taskPriorityNormal: 'Normal',
        duplicate: 'Copy'
    }
});
```

### Registering Or Replacing A Language

[](#registering-or-replacing-a-language)

Use `$.bsCalendar.addTranslation(locale, translation)` before initialization. Only the language code before the hyphen is used as the registry key. For example, `de-CH` registers or replaces `de`, not a separate Swiss-German variant.

```
$.bsCalendar.addTranslation('eo', {
    today: 'Hodiau',
    day: 'Tago',
    '4day': '4 Tagoj',
    week: 'Semajno',
    month: 'Monato',
    year: 'Jaro',
    agenda: 'Agendo',
    allDay: 'Tuttaga',
    search: 'Tajpu kaj premu Enter',
    searchNoResult: 'Neniu rendevuo trovita',
    tasks: 'Taskoj',
    taskPriorityHigh: 'Alta',
    taskPriorityNormal: 'Normala',
    taskPriorityLow: 'Malalta',
    duplicate: 'Duobligi'
});

$('#calendar').bsCalendar({
    locale: 'eo'
});
```

For regional wording differences, keep the regional `locale` for date formatting and override strings per instance:

```
$('#calendar').bsCalendar({
    locale: 'de-CH',
    translations: {
        allDay: 'Ganztägig',
        taskPriorityNormal: 'Normal',
        duplicate: 'Kopieren'
    }
});
```

### Reading Translations Programmatically

[](#reading-translations-programmatically)

These helpers use the language key. If you pass a regional locale like `de-DE`, only `de` is used internally.

```
const de = $.bsCalendar.getTranslations('de');
const today = $.bsCalendar.getTranslation('de', 'today');
const fallback = $.bsCalendar.getTranslation('xx', 'duplicate'); // English fallback
```

### Changing Locale At Runtime

[](#changing-locale-at-runtime)

Use `setLocale` when you only want to change the locale. Use `updateOptions` when you also want to override translation keys at the same time.

```
$('#calendar').bsCalendar('setLocale', 'es-ES');

$('#calendar').bsCalendar('updateOptions', {
    locale: 'fr-FR',
    translations: {
        duplicate: 'Copier'
    }
});
```

Runtime locale changes are ignored while the calendar is in search mode.

Utilities
---------

[](#utilities)

Global API:

```
$.bsCalendar.version;
$.bsCalendar.about;
$.bsCalendar.possibleViews;
$.bsCalendar.setDefaults({locale: 'de-DE'});
$.bsCalendar.getDefaults();
$.bsCalendar.addTranslation('es', {today: 'Hoy'});
$.bsCalendar.getTranslations('de');
$.bsCalendar.getTranslation('de', 'today');
```

Appointment and date helpers:

```
const hourNumber = $.bsCalendar.utils.parseTimeToDecimal('08:30'); // output -> 8.5
const appointments = $.bsCalendar.utils.convertIcsToAppointments(icsString);
const date = $.bsCalendar.utils.parseDateInput('2026-05-08 10:00:00');
const normalized = $.bsCalendar.utils.normalizeDateTime('2026-05-08 10:00');
const time = $.bsCalendar.utils.formatTime(date);
const dateString = $.bsCalendar.utils.formatDateToDateString(date);
const localizedDate = $.bsCalendar.utils.formatDateByLocale(date, 'de-DE');
const week = $.bsCalendar.utils.getCalendarWeek(date);
const weekdays = $.bsCalendar.utils.getShortWeekDayNames('de-DE', false);
const sameDay = $.bsCalendar.utils.datesAreEqual(new Date(), date);
const label = $.bsCalendar.utils.getAppointmentTimespanBeautify(extras, true);
```

Lower-level utility helpers:

```
const computed = $.bsCalendar.utils.computeColor('primary');
const styles = $.bsCalendar.utils.getComputedStyles('danger opacity-75 gradient');
const direct = $.bsCalendar.utils.isDirectColorValid('#ff5733');
const resolved = $.bsCalendar.utils.resolveColor('steelblue');
const dark = $.bsCalendar.utils.isDarkColor('#000000');
const hex = $.bsCalendar.utils.toHex('rgb(255, 87, 51)');
const colors = $.bsCalendar.utils.getColors('primary', 'secondary');
const id = $.bsCalendar.utils.generateRandomString(8);
const localeParts = $.bsCalendar.utils.getLanguageAndCountry('de-DE');
const empty = $.bsCalendar.utils.isValueEmpty('');
const namedHex = $.bsCalendar.utils.colorNameToHex.steelblue;
```

OpenHolidays helpers:

```
$.bsCalendar.utils.openHolidayApi.getCountries('DE');
$.bsCalendar.utils.openHolidayApi.getLanguages('DE');
$.bsCalendar.utils.openHolidayApi.getSubdivisions('DE', 'DE');
$.bsCalendar.utils.openHolidayApi.getSchoolHolidays('DE', 'BE', '2026-01-01', '2026-12-31');
$.bsCalendar.utils.openHolidayApi.getPublicHolidays('DE', 'BE', 'DE', '2026-01-01', '2026-12-31');
```

Repository Notes
----------------

[](#repository-notes)

Key files:

```
.
├── README.md
├── changelog.md
├── composer.json
├── dist/
│   ├── bs-calendar.js
│   └── bs-calendar.min.js
└── demo/
    ├── index.html
    └── img/

```

Development notes:

- No npm build is required.
- `dist/bs-calendar.js` is the unminified browser source.
- `dist/bs-calendar.min.js` should be regenerated after changes to `dist/bs-calendar.js`.
- The demo expects Composer dependencies in `vendor/`.
- No automated test suite is currently included.

Changelog and support:

- [Changelog](changelog.md)
- [Issues](https://github.com/ThomasDev-de/bs-calendar/issues)
- [License](LICENSE)

Completeness Check
------------------

[](#completeness-check)

This README is intended to cover the public surface of version `2.3.6`:

- All `DEFAULTS` options from `dist/bs-calendar.js`
- All public plugin methods in the method switch
- All jQuery events emitted through `trigger()` and matching `on*` callback options
- Appointment object fields, including task fields and generated `id` behavior
- `url` value types, request data, and response contracts
- Formatter signatures
- `extras` fields used by callbacks and formatters
- Global `$.bsCalendar` API and utility helpers
- Localization and custom translation handling

###  Health Score

50

—

FairBetter than 95% of packages

Maintenance98

Actively maintained with recent releases

Popularity27

Limited adoption so far

Community11

Small or concentrated contributor base

Maturity52

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~8 days

Recently: every ~1 days

Total

58

Last Release

17d ago

Major Versions

1.2.12 → 2.0.02025-10-31

1.3.0 → v2.0.162026-05-08

### Community

Maintainers

![](https://www.gravatar.com/avatar/6d5f10c16b4b6bd1ac531ffc39c23c569490ec4630829511692c03ec89d36a11?d=identicon)[thomasK81](/maintainers/thomasK81)

---

Top Contributors

[![ThomasDev-de](https://avatars.githubusercontent.com/u/67106837?v=4)](https://github.com/ThomasDev-de "ThomasDev-de (387 commits)")

---

Tags

bootstrap5calendarcalendar-eventscalendar-viewdragdrag-and-dropdraggabledropholidayjavascriptjquerylocalizationsearchtimeslotsutilsviewsschedulerpluginuieventsjquerycalendarbootstrapbootstrap5Bootstrap Icons

### Embed Badge

![Health badge](/badges/webcito-bs-calendar/health.svg)

```
[![Health](https://phpackages.com/badges/webcito-bs-calendar/health.svg)](https://phpackages.com/packages/webcito-bs-calendar)
```

###  Alternatives

[jason-munro/cypht

Lightweight Open Source webmail written in PHP and JavaScript

1.6k157.9k](/packages/jason-munro-cypht)[datatables.net/datatables.net-bs5

DataTables is a plug-in for the jQuery Javascript library. It is a highly flexible tool, based upon the foundations of progressive enhancement, which will add advanced interaction controls to any HTML table. This is DataTables with styling for \[Bootstrap5\](https://getbootstrap.com/)

2193.4k18](/packages/datatablesnet-datatablesnet-bs5)[keygenqt/yii2-autocomplete-ajax

A simple way to search model id of the attributes model

1016.4k](/packages/keygenqt-yii2-autocomplete-ajax)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
