lk-holidays 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Readme.md +802 -0
- package/package.json +2 -2
package/Readme.md
ADDED
|
@@ -0,0 +1,802 @@
|
|
|
1
|
+
# lk-holidays 🇱🇰
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/lk-holidays)
|
|
4
|
+
[](https://github.com/YOUR_GITHUB_USERNAME/lk-holidays/actions/workflows/ci.yml)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
|
|
7
|
+
Sri Lankan **public, bank and mercantile holidays**, taken from the
|
|
8
|
+
[Central Bank of Sri Lanka (CBSL)](https://www.cbsl.gov.lk/en/about/about-the-bank/bank-holidays-2027),
|
|
9
|
+
with names in **English, සිංහල and தமிழ்**. Built for attendance systems,
|
|
10
|
+
payroll, leave management, calendars and anything else that needs to know
|
|
11
|
+
whether a day in Sri Lanka is a working day.
|
|
12
|
+
|
|
13
|
+
- **Official data:** dates and the CBSL **B / P / M** (Bank / Public / Mercantile) flags
|
|
14
|
+
- **Three languages:** English, Sinhala and Tamil name for every holiday
|
|
15
|
+
- **Working-day maths:** count, list and add working days, with your own weekends and company holidays
|
|
16
|
+
- **Sri Lanka time zone aware:** `Date` objects are read in Asia/Colombo, so UTC servers get the right day
|
|
17
|
+
- **Updated automatically:** a GitHub Action checks CBSL regularly and publishes new years
|
|
18
|
+
- **Zero dependencies:** works offline, ESM + CommonJS, full TypeScript types
|
|
19
|
+
- **Runs anywhere:** Node 18+, browsers, React, Next.js, React Native, Bun, Deno
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Contents
|
|
24
|
+
|
|
25
|
+
- [Installation](#installation)
|
|
26
|
+
- [Quick start](#quick-start)
|
|
27
|
+
- [Key concepts](#key-concepts)
|
|
28
|
+
- [The Holiday object](#the-holiday-object)
|
|
29
|
+
- [API reference](#api-reference)
|
|
30
|
+
- [Lookups](#lookups)
|
|
31
|
+
- [Working days](#working-days)
|
|
32
|
+
- [Data coverage](#data-coverage)
|
|
33
|
+
- [Custom data: `createHolidayApi`](#custom-data-createholidayapi)
|
|
34
|
+
- [Date helpers](#date-helpers)
|
|
35
|
+
- [Recipes](#recipes)
|
|
36
|
+
- [Using the data without JavaScript](#using-the-data-without-javascript)
|
|
37
|
+
- [Holiday keys](#holiday-keys)
|
|
38
|
+
- [TypeScript](#typescript)
|
|
39
|
+
- [How the data is kept up to date](#how-the-data-is-kept-up-to-date)
|
|
40
|
+
- [FAQ](#faq)
|
|
41
|
+
- [Contributing](#contributing)
|
|
42
|
+
- [Disclaimer](#disclaimer)
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Installation
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
npm install lk-holidays
|
|
50
|
+
# or
|
|
51
|
+
yarn add lk-holidays
|
|
52
|
+
# or
|
|
53
|
+
pnpm add lk-holidays
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Quick start
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
import { isHoliday, getHolidaysOn, getHolidays, countWorkingDays } from 'lk-holidays';
|
|
60
|
+
|
|
61
|
+
isHoliday('2027-04-14'); // true
|
|
62
|
+
isHoliday('2027-04-15'); // false
|
|
63
|
+
|
|
64
|
+
const [holiday] = getHolidaysOn('2027-04-14');
|
|
65
|
+
holiday.name.en; // 'Sinhala & Tamil New Year Day'
|
|
66
|
+
holiday.name.si; // 'සිංහල හා දෙමළ අලුත් අවුරුදු දිනය'
|
|
67
|
+
holiday.name.ta; // 'சிங்கள, தமிழ் புத்தாண்டு'
|
|
68
|
+
|
|
69
|
+
getHolidays(2027).length; // 25
|
|
70
|
+
|
|
71
|
+
// Working days in April 2027 (Sat & Sun off, public holidays off)
|
|
72
|
+
countWorkingDays('2027-04-01', '2027-04-30'); // 19
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
CommonJS:
|
|
76
|
+
|
|
77
|
+
```js
|
|
78
|
+
const { isHoliday } = require('lk-holidays');
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Browser without a build step:
|
|
82
|
+
|
|
83
|
+
```html
|
|
84
|
+
<script type="module">
|
|
85
|
+
import { isHoliday } from 'https://cdn.jsdelivr.net/npm/lk-holidays/+esm';
|
|
86
|
+
console.log(isHoliday('2027-04-14')); // true
|
|
87
|
+
</script>
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## Key concepts
|
|
93
|
+
|
|
94
|
+
### Categories: public, bank, mercantile
|
|
95
|
+
|
|
96
|
+
CBSL tags each holiday with one or more categories. They decide who gets the
|
|
97
|
+
day off:
|
|
98
|
+
|
|
99
|
+
| Code | Category | Who it applies to |
|
|
100
|
+
| --- | --- | --- |
|
|
101
|
+
| **P** | `public` | Government offices, schools and most public services |
|
|
102
|
+
| **B** | `bank` | Banks and financial institutions |
|
|
103
|
+
| **M** | `mercantile` | Private sector (shops, offices, factories) |
|
|
104
|
+
|
|
105
|
+
For example, Full Moon **Poya days are bank and public holidays, but not
|
|
106
|
+
mercantile holidays**. Christmas and Sinhala & Tamil New Year are all three.
|
|
107
|
+
|
|
108
|
+
Most functions take an optional `categories` filter. Leave it out to get every
|
|
109
|
+
holiday CBSL lists.
|
|
110
|
+
|
|
111
|
+
```ts
|
|
112
|
+
getHolidays(2027).length; // 25 (all)
|
|
113
|
+
getHolidays(2027, { categories: 'public' }).length; // 25
|
|
114
|
+
getHolidays(2027, { categories: 'mercantile' }).length; // 8
|
|
115
|
+
getHolidays(2027, { categories: ['bank', 'mercantile'] }); // either category
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Weekends are not listed
|
|
119
|
+
|
|
120
|
+
CBSL notes that *all Saturdays and Sundays are bank holidays*. This package does
|
|
121
|
+
**not** add weekends to the holiday list. Use the [working-day
|
|
122
|
+
functions](#working-days), which handle weekends for you and let you choose
|
|
123
|
+
which days are your weekend.
|
|
124
|
+
|
|
125
|
+
### Poya days
|
|
126
|
+
|
|
127
|
+
Every Full Moon Poya day has `isPoya: true`. A year normally has 12; years
|
|
128
|
+
with an **Adhi** (extra) Poya have 13, as 2026 does with Adhi Poson.
|
|
129
|
+
|
|
130
|
+
### Dates and time zones
|
|
131
|
+
|
|
132
|
+
- Pass dates as `'YYYY-MM-DD'` strings or as `Date` objects.
|
|
133
|
+
- A `Date` is converted to the calendar date **in Sri Lanka (Asia/Colombo)**.
|
|
134
|
+
On a server running in UTC, `new Date('2027-04-13T20:00:00Z')` is already
|
|
135
|
+
14 April in Colombo, so it counts as New Year Day.
|
|
136
|
+
- All returned dates are `'YYYY-MM-DD'` strings.
|
|
137
|
+
|
|
138
|
+
### Missing years throw
|
|
139
|
+
|
|
140
|
+
If you ask about a year that isn't in the bundled data, functions **throw a
|
|
141
|
+
`RangeError`** instead of returning "no holidays". That way an out-of-date
|
|
142
|
+
package can never quietly mark Vesak as a working day in your attendance
|
|
143
|
+
system. Use [`hasYear()`](#data-coverage) to check first.
|
|
144
|
+
|
|
145
|
+
```ts
|
|
146
|
+
getHolidays(2035);
|
|
147
|
+
// RangeError: No holiday data for 2035. Available years: 2024, 2025, 2026, 2027.
|
|
148
|
+
// Update the package (npm update lk-holidays) to get newer years.
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
## The Holiday object
|
|
154
|
+
|
|
155
|
+
```jsonc
|
|
156
|
+
{
|
|
157
|
+
"date": "2027-05-19", // YYYY-MM-DD, Sri Lanka date
|
|
158
|
+
"key": "vesak-poya", // stable id, the same every year
|
|
159
|
+
"name": {
|
|
160
|
+
"en": "Vesak Full Moon Poya Day", // as published by CBSL
|
|
161
|
+
"si": "වෙසක් පුර පසළොස්වක පෝය දිනය",
|
|
162
|
+
"ta": "வெசாக் முழு நோன்மதி தினம்"
|
|
163
|
+
},
|
|
164
|
+
"categories": ["bank", "public"], // CBSL B / P / M flags
|
|
165
|
+
"isPoya": true
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
| Field | Type | Description |
|
|
170
|
+
| --- | --- | --- |
|
|
171
|
+
| `date` | `string` | `YYYY-MM-DD` |
|
|
172
|
+
| `key` | `string` | Stable id such as `vesak-poya` or `christmas`. Use it for icons, colours or your own translations. See [Holiday keys](#holiday-keys). |
|
|
173
|
+
| `name.en` | `string` | English name exactly as CBSL publishes it (spelling can vary slightly between years) |
|
|
174
|
+
| `name.si` | `string` | Sinhala name |
|
|
175
|
+
| `name.ta` | `string` | Tamil name |
|
|
176
|
+
| `categories` | `('bank' \| 'public' \| 'mercantile')[]` | At least one category |
|
|
177
|
+
| `isPoya` | `boolean` | `true` for Full Moon Poya days, including Adhi Poya |
|
|
178
|
+
|
|
179
|
+
Every function returns **copies**, so changing a returned object never affects
|
|
180
|
+
the package's data.
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
## API reference
|
|
185
|
+
|
|
186
|
+
In the signatures below:
|
|
187
|
+
|
|
188
|
+
- `DateInput` is `string | Date` (`'YYYY-MM-DD'` or a `Date`).
|
|
189
|
+
- `FilterOptions` is `{ categories?: HolidayCategory | HolidayCategory[] }`.
|
|
190
|
+
|
|
191
|
+
### Lookups
|
|
192
|
+
|
|
193
|
+
#### `getHolidays(year?, options?)`
|
|
194
|
+
|
|
195
|
+
All holidays in a year, sorted by date.
|
|
196
|
+
|
|
197
|
+
```ts
|
|
198
|
+
getHolidays(year?: number, options?: FilterOptions): Holiday[]
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
- `year` defaults to the current year in Sri Lanka.
|
|
202
|
+
- Throws `RangeError` if the year isn't available.
|
|
203
|
+
|
|
204
|
+
```ts
|
|
205
|
+
getHolidays(2027); // 25 holidays
|
|
206
|
+
getHolidays(2027, { categories: 'mercantile' }); // 8 holidays
|
|
207
|
+
getHolidays(); // current year
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
#### `getHolidaysOn(date, options?)`
|
|
211
|
+
|
|
212
|
+
Holidays that fall on one date. Usually zero or one, but some dates have two,
|
|
213
|
+
such as 1 May 2026 (Vesak and May Day).
|
|
214
|
+
|
|
215
|
+
```ts
|
|
216
|
+
getHolidaysOn(date: DateInput, options?: FilterOptions): Holiday[]
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
```ts
|
|
220
|
+
getHolidaysOn('2026-05-01').map((h) => h.key); // ['may-day', 'vesak-poya']
|
|
221
|
+
getHolidaysOn('2027-04-15'); // []
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
#### `getHolidaysBetween(start, end, options?)`
|
|
225
|
+
|
|
226
|
+
Holidays between two dates, inclusive. Works across years.
|
|
227
|
+
|
|
228
|
+
```ts
|
|
229
|
+
getHolidaysBetween(start: DateInput, end: DateInput, options?: FilterOptions): Holiday[]
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
- Throws `RangeError` if `start` is after `end`.
|
|
233
|
+
- Only returns holidays from years that have data.
|
|
234
|
+
|
|
235
|
+
```ts
|
|
236
|
+
getHolidaysBetween('2027-12-01', '2027-12-31').map((h) => h.key);
|
|
237
|
+
// ['unduvap-poya', 'christmas']
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
#### `getHolidaysInMonth(year, month, options?)`
|
|
241
|
+
|
|
242
|
+
Holidays in one month. `month` is 1–12.
|
|
243
|
+
|
|
244
|
+
```ts
|
|
245
|
+
getHolidaysInMonth(year: number, month: number, options?: FilterOptions): Holiday[]
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
```ts
|
|
249
|
+
getHolidaysInMonth(2027, 4).map((h) => h.name.en);
|
|
250
|
+
// ['Day prior to Sinhala & Tamil New Year Day',
|
|
251
|
+
// 'Sinhala & Tamil New Year Day',
|
|
252
|
+
// 'Bak Full Moon Poya Day']
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
#### `getNextHoliday(from?, options?)`
|
|
256
|
+
|
|
257
|
+
The next holiday on or after a date. `from` defaults to today in Sri Lanka.
|
|
258
|
+
Returns `undefined` if there are no more holidays in the bundled data.
|
|
259
|
+
|
|
260
|
+
```ts
|
|
261
|
+
getNextHoliday(from?: DateInput, options?: FilterOptions): Holiday | undefined
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
```ts
|
|
265
|
+
getNextHoliday('2027-04-15')?.name.en; // 'Bak Full Moon Poya Day'
|
|
266
|
+
getNextHoliday('2027-04-15', { categories: 'mercantile' })?.date; // '2027-05-01'
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
#### `getPoyaDays(year?)`
|
|
270
|
+
|
|
271
|
+
All Full Moon Poya days in a year.
|
|
272
|
+
|
|
273
|
+
```ts
|
|
274
|
+
getPoyaDays(year?: number): Holiday[]
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
```ts
|
|
278
|
+
getPoyaDays(2026).length; // 13 (includes Adhi Poson)
|
|
279
|
+
getPoyaDays(2027).length; // 12
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
#### `isHoliday(date, options?)` and shortcuts
|
|
283
|
+
|
|
284
|
+
```ts
|
|
285
|
+
isHoliday(date: DateInput, options?: FilterOptions): boolean
|
|
286
|
+
isPublicHoliday(date: DateInput): boolean
|
|
287
|
+
isBankHoliday(date: DateInput): boolean
|
|
288
|
+
isMercantileHoliday(date: DateInput): boolean
|
|
289
|
+
isPoyaDay(date: DateInput): boolean
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
These check only the CBSL holiday list; weekends return `false`.
|
|
293
|
+
|
|
294
|
+
```ts
|
|
295
|
+
isHoliday('2027-04-14'); // true
|
|
296
|
+
isPublicHoliday('2027-01-22'); // true (Duruthu Poya)
|
|
297
|
+
isMercantileHoliday('2027-01-22'); // false (Poya days aren't mercantile)
|
|
298
|
+
isBankHoliday('2025-04-15'); // true (Special Bank Holiday)
|
|
299
|
+
isPublicHoliday('2025-04-15'); // false
|
|
300
|
+
isPoyaDay('2027-01-22'); // true
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
#### `getHolidayName(holiday, lang?)`
|
|
304
|
+
|
|
305
|
+
A holiday's name in one language. `lang` is `'en'` (default), `'si'` or `'ta'`.
|
|
306
|
+
|
|
307
|
+
```ts
|
|
308
|
+
getHolidayName(holiday: Holiday, lang?: 'en' | 'si' | 'ta'): string
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
```ts
|
|
312
|
+
const [h] = getHolidaysOn('2027-04-14');
|
|
313
|
+
getHolidayName(h, 'si'); // 'සිංහල හා දෙමළ අලුත් අවුරුදු දිනය'
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
### Working days
|
|
317
|
+
|
|
318
|
+
These answer "is this a working day?" for attendance, payroll, leave and
|
|
319
|
+
deadlines. They all take the same options:
|
|
320
|
+
|
|
321
|
+
```ts
|
|
322
|
+
interface WorkingDayOptions {
|
|
323
|
+
/** Holidays that count as days off. Default: 'public'. */
|
|
324
|
+
categories?: HolidayCategory | HolidayCategory[];
|
|
325
|
+
/** Days of the week that are off (0 = Sunday … 6 = Saturday). Default: [0, 6]. */
|
|
326
|
+
weekendDays?: number[];
|
|
327
|
+
/** Extra days off for your organisation. */
|
|
328
|
+
extraHolidays?: DateInput[];
|
|
329
|
+
/** Days that are working days even if they're a holiday or weekend. */
|
|
330
|
+
extraWorkingDays?: DateInput[];
|
|
331
|
+
}
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
The rules are applied in this order:
|
|
335
|
+
|
|
336
|
+
1. In `extraWorkingDays`: working day.
|
|
337
|
+
2. In `extraHolidays`: day off.
|
|
338
|
+
3. A weekend day: day off.
|
|
339
|
+
4. A holiday in the chosen `categories`: day off.
|
|
340
|
+
5. Otherwise: working day.
|
|
341
|
+
|
|
342
|
+
Each function throws `RangeError` if a date falls in a year without data.
|
|
343
|
+
|
|
344
|
+
| Organisation | Suggested `categories` |
|
|
345
|
+
| --- | --- |
|
|
346
|
+
| Government offices, schools | `'public'` (default) |
|
|
347
|
+
| Banks | `'bank'` |
|
|
348
|
+
| Private sector (Shop & Office Act) | `'mercantile'`, or `'public'` if staff get all public holidays |
|
|
349
|
+
|
|
350
|
+
#### `isWorkingDay(date, options?)`
|
|
351
|
+
|
|
352
|
+
```ts
|
|
353
|
+
isWorkingDay(date: DateInput, options?: WorkingDayOptions): boolean
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
```ts
|
|
357
|
+
isWorkingDay('2027-04-13'); // false (New Year eve)
|
|
358
|
+
isWorkingDay('2027-04-17'); // false (Saturday)
|
|
359
|
+
isWorkingDay('2027-04-20', { categories: 'mercantile' }); // true (Poya isn't mercantile)
|
|
360
|
+
isWorkingDay('2027-04-15', { extraHolidays: ['2027-04-15'] }); // false
|
|
361
|
+
isWorkingDay('2027-04-13', { extraWorkingDays: ['2027-04-13'] }); // true
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
#### `countWorkingDays(start, end, options?)`
|
|
365
|
+
|
|
366
|
+
Number of working days between two dates, inclusive.
|
|
367
|
+
|
|
368
|
+
```ts
|
|
369
|
+
countWorkingDays(start: DateInput, end: DateInput, options?: WorkingDayOptions): number
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
```ts
|
|
373
|
+
countWorkingDays('2027-04-01', '2027-04-30'); // 19
|
|
374
|
+
countWorkingDays('2027-04-01', '2027-04-30', { categories: 'mercantile' }); // 20
|
|
375
|
+
countWorkingDays('2027-04-01', '2027-04-30', { weekendDays: [0] }); // 23 (6-day week)
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
#### `getWorkingDays(start, end, options?)`
|
|
379
|
+
|
|
380
|
+
List of working days between two dates, inclusive.
|
|
381
|
+
|
|
382
|
+
```ts
|
|
383
|
+
getWorkingDays(start: DateInput, end: DateInput, options?: WorkingDayOptions): string[]
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
```ts
|
|
387
|
+
getWorkingDays('2027-04-12', '2027-04-16');
|
|
388
|
+
// ['2027-04-12', '2027-04-15', '2027-04-16']
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
#### `addWorkingDays(date, n, options?)`
|
|
392
|
+
|
|
393
|
+
The date `n` working days after `date`. Use a negative `n` to go backwards.
|
|
394
|
+
`n` must be an integer.
|
|
395
|
+
|
|
396
|
+
```ts
|
|
397
|
+
addWorkingDays(date: DateInput, n: number, options?: WorkingDayOptions): string
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
```ts
|
|
401
|
+
addWorkingDays('2027-04-12', 3); // '2027-04-19' (skips New Year eve, New Year and the weekend)
|
|
402
|
+
addWorkingDays('2027-04-15', -1); // '2027-04-12'
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
#### `nextWorkingDay(date, options?)`
|
|
406
|
+
|
|
407
|
+
The next working day strictly after `date`. Same as `addWorkingDays(date, 1, options)`.
|
|
408
|
+
|
|
409
|
+
```ts
|
|
410
|
+
nextWorkingDay('2027-04-12'); // '2027-04-15'
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
### Data coverage
|
|
414
|
+
|
|
415
|
+
```ts
|
|
416
|
+
getAvailableYears(): number[] // [2024, 2025, 2026, 2027]
|
|
417
|
+
hasYear(year: number): boolean // hasYear(2027) → true
|
|
418
|
+
getYearData(year: number): YearData | undefined
|
|
419
|
+
getDataInfo(): {
|
|
420
|
+
source: string;
|
|
421
|
+
years: number[];
|
|
422
|
+
lastUpdated: string; // 'YYYY-MM-DD'
|
|
423
|
+
sources: Record<number, string>; // CBSL page URL for each year
|
|
424
|
+
}
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
```ts
|
|
428
|
+
getDataInfo().sources[2027];
|
|
429
|
+
// 'https://www.cbsl.gov.lk/en/about/about-the-bank/bank-holidays-2027'
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
### Custom data: `createHolidayApi`
|
|
433
|
+
|
|
434
|
+
Every function above is also available over **your own dataset**. Use this
|
|
435
|
+
for a company calendar with extra days, data loaded from your own server, or
|
|
436
|
+
small datasets in your tests.
|
|
437
|
+
|
|
438
|
+
```ts
|
|
439
|
+
import { createHolidayApi, getYearData } from 'lk-holidays';
|
|
440
|
+
|
|
441
|
+
const cbsl2027 = getYearData(2027)!;
|
|
442
|
+
|
|
443
|
+
const company = createHolidayApi({
|
|
444
|
+
2027: {
|
|
445
|
+
...cbsl2027,
|
|
446
|
+
holidays: [
|
|
447
|
+
...cbsl2027.holidays,
|
|
448
|
+
{
|
|
449
|
+
date: '2027-06-04',
|
|
450
|
+
key: 'company-day',
|
|
451
|
+
name: { en: 'Company Day', si: 'සමාගම් දිනය', ta: 'நிறுவன தினம்' },
|
|
452
|
+
categories: ['mercantile'],
|
|
453
|
+
isPoya: false,
|
|
454
|
+
},
|
|
455
|
+
],
|
|
456
|
+
},
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
company.isHoliday('2027-06-04'); // true
|
|
460
|
+
company.getHolidays(2027).length; // 26
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
```ts
|
|
464
|
+
createHolidayApi(data: Record<number, YearData>, lastUpdated?: string): HolidayApi
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
Keep each year's `holidays` array sorted by date. For one-off days off, the
|
|
468
|
+
`extraHolidays` option of the working-day functions is simpler.
|
|
469
|
+
|
|
470
|
+
### Date helpers
|
|
471
|
+
|
|
472
|
+
```ts
|
|
473
|
+
toISODate(input: DateInput): string // normalise to 'YYYY-MM-DD' in Sri Lanka time
|
|
474
|
+
todayInSriLanka(): string // today's date in Colombo
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
```ts
|
|
478
|
+
toISODate(new Date('2027-04-13T20:00:00Z')); // '2027-04-14'
|
|
479
|
+
toISODate('2027-02-30'); // throws RangeError (no such date)
|
|
480
|
+
toISODate('14/04/2027'); // throws TypeError (use YYYY-MM-DD)
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
---
|
|
484
|
+
|
|
485
|
+
## Recipes
|
|
486
|
+
|
|
487
|
+
### Monthly attendance summary
|
|
488
|
+
|
|
489
|
+
```ts
|
|
490
|
+
import { countWorkingDays, getHolidaysInMonth } from 'lk-holidays';
|
|
491
|
+
|
|
492
|
+
function monthSummary(year: number, month: number) {
|
|
493
|
+
const mm = String(month).padStart(2, '0');
|
|
494
|
+
const lastDay = new Date(Date.UTC(year, month, 0)).getUTCDate();
|
|
495
|
+
const options = { categories: 'mercantile' as const };
|
|
496
|
+
|
|
497
|
+
return {
|
|
498
|
+
workingDays: countWorkingDays(`${year}-${mm}-01`, `${year}-${mm}-${lastDay}`, options),
|
|
499
|
+
holidays: getHolidaysInMonth(year, month, options).map((h) => ({ date: h.date, name: h.name.en })),
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
monthSummary(2027, 4);
|
|
504
|
+
// { workingDays: 20, holidays: [ New Year eve, New Year Day ] }
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
### Mark absences only on working days
|
|
508
|
+
|
|
509
|
+
```ts
|
|
510
|
+
import { isWorkingDay } from 'lk-holidays';
|
|
511
|
+
|
|
512
|
+
function attendanceStatus(date: string, checkedIn: boolean) {
|
|
513
|
+
if (!isWorkingDay(date)) return 'off';
|
|
514
|
+
return checkedIn ? 'present' : 'absent';
|
|
515
|
+
}
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
### Leave request: how many days does it use?
|
|
519
|
+
|
|
520
|
+
```ts
|
|
521
|
+
import { countWorkingDays } from 'lk-holidays';
|
|
522
|
+
|
|
523
|
+
// Leave from Monday 12 April to Friday 16 April 2027
|
|
524
|
+
countWorkingDays('2027-04-12', '2027-04-16'); // 3: New Year eve and New Year Day aren't deducted
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
### Due dates: "within 5 working days"
|
|
528
|
+
|
|
529
|
+
```ts
|
|
530
|
+
import { addWorkingDays, todayInSriLanka } from 'lk-holidays';
|
|
531
|
+
|
|
532
|
+
const dueDate = addWorkingDays(todayInSriLanka(), 5, { categories: 'bank' });
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
### Calendar cell (React)
|
|
536
|
+
|
|
537
|
+
```tsx
|
|
538
|
+
import { getHolidaysOn, type Language } from 'lk-holidays';
|
|
539
|
+
|
|
540
|
+
export function DayCell({ date, lang = 'si' }: { date: string; lang?: Language }) {
|
|
541
|
+
const [holiday] = getHolidaysOn(date);
|
|
542
|
+
return (
|
|
543
|
+
<div className={holiday ? (holiday.isPoya ? 'day poya' : 'day holiday') : 'day'}>
|
|
544
|
+
<span>{Number(date.slice(8))}</span>
|
|
545
|
+
{holiday && <small title={holiday.name.en}>{holiday.name[lang]}</small>}
|
|
546
|
+
</div>
|
|
547
|
+
);
|
|
548
|
+
}
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
### Upcoming holidays widget
|
|
552
|
+
|
|
553
|
+
```ts
|
|
554
|
+
import { getHolidaysBetween, todayInSriLanka, toISODate } from 'lk-holidays';
|
|
555
|
+
|
|
556
|
+
const today = todayInSriLanka();
|
|
557
|
+
const in60Days = toISODate(new Date(Date.now() + 60 * 86_400_000));
|
|
558
|
+
const upcoming = getHolidaysBetween(today, in60Days, { categories: 'public' });
|
|
559
|
+
```
|
|
560
|
+
|
|
561
|
+
### Next.js route handler
|
|
562
|
+
|
|
563
|
+
```ts
|
|
564
|
+
// app/api/holidays/[year]/route.ts
|
|
565
|
+
import { getHolidays, hasYear } from 'lk-holidays';
|
|
566
|
+
|
|
567
|
+
export function GET(_req: Request, { params }: { params: { year: string } }) {
|
|
568
|
+
const year = Number(params.year);
|
|
569
|
+
if (!hasYear(year)) return Response.json({ error: 'Year not available' }, { status: 404 });
|
|
570
|
+
return Response.json(getHolidays(year));
|
|
571
|
+
}
|
|
572
|
+
```
|
|
573
|
+
|
|
574
|
+
### Express API
|
|
575
|
+
|
|
576
|
+
```ts
|
|
577
|
+
import express from 'express';
|
|
578
|
+
import { getHolidays, hasYear, isWorkingDay } from 'lk-holidays';
|
|
579
|
+
|
|
580
|
+
const app = express();
|
|
581
|
+
|
|
582
|
+
app.get('/holidays/:year', (req, res) => {
|
|
583
|
+
const year = Number(req.params.year);
|
|
584
|
+
if (!hasYear(year)) return res.status(404).json({ error: 'Year not available' });
|
|
585
|
+
res.json(getHolidays(year));
|
|
586
|
+
});
|
|
587
|
+
|
|
588
|
+
app.get('/working-day/:date', (req, res) => {
|
|
589
|
+
try {
|
|
590
|
+
res.json({ date: req.params.date, working: isWorkingDay(req.params.date) });
|
|
591
|
+
} catch (err) {
|
|
592
|
+
res.status(400).json({ error: (err as Error).message });
|
|
593
|
+
}
|
|
594
|
+
});
|
|
595
|
+
|
|
596
|
+
app.listen(3000);
|
|
597
|
+
```
|
|
598
|
+
|
|
599
|
+
### Warn before the data runs out
|
|
600
|
+
|
|
601
|
+
Call this at startup or from a scheduled job so you know to update the package
|
|
602
|
+
before next year begins:
|
|
603
|
+
|
|
604
|
+
```ts
|
|
605
|
+
import { hasYear, todayInSriLanka } from 'lk-holidays';
|
|
606
|
+
|
|
607
|
+
const nextYear = Number(todayInSriLanka().slice(0, 4)) + 1;
|
|
608
|
+
if (!hasYear(nextYear)) {
|
|
609
|
+
console.warn(`lk-holidays has no data for ${nextYear} yet. Run: npm update lk-holidays`);
|
|
610
|
+
}
|
|
611
|
+
```
|
|
612
|
+
|
|
613
|
+
---
|
|
614
|
+
|
|
615
|
+
## Using the data without JavaScript
|
|
616
|
+
|
|
617
|
+
Every published version includes the raw JSON, one file per year:
|
|
618
|
+
|
|
619
|
+
```
|
|
620
|
+
https://cdn.jsdelivr.net/npm/lk-holidays/data/2027.json
|
|
621
|
+
https://unpkg.com/lk-holidays/data/2027.json
|
|
622
|
+
```
|
|
623
|
+
|
|
624
|
+
Pin a version for production, for example `https://cdn.jsdelivr.net/npm/lk-holidays@1/data/2027.json`.
|
|
625
|
+
|
|
626
|
+
File format:
|
|
627
|
+
|
|
628
|
+
```jsonc
|
|
629
|
+
{
|
|
630
|
+
"year": 2027,
|
|
631
|
+
"source": "Central Bank of Sri Lanka",
|
|
632
|
+
"sourceUrl": "https://www.cbsl.gov.lk/en/about/about-the-bank/bank-holidays-2027",
|
|
633
|
+
"updatedAt": "2026-10-25",
|
|
634
|
+
"holidays": [ /* Holiday objects, sorted by date */ ]
|
|
635
|
+
}
|
|
636
|
+
```
|
|
637
|
+
|
|
638
|
+
**Python**
|
|
639
|
+
|
|
640
|
+
```python
|
|
641
|
+
import requests
|
|
642
|
+
|
|
643
|
+
data = requests.get("https://cdn.jsdelivr.net/npm/lk-holidays@1/data/2027.json").json()
|
|
644
|
+
holidays = {h["date"]: h for h in data["holidays"]}
|
|
645
|
+
print("2027-04-14" in holidays) # True
|
|
646
|
+
```
|
|
647
|
+
|
|
648
|
+
**PHP**
|
|
649
|
+
|
|
650
|
+
```php
|
|
651
|
+
$data = json_decode(file_get_contents('https://cdn.jsdelivr.net/npm/lk-holidays@1/data/2027.json'), true);
|
|
652
|
+
$dates = array_column($data['holidays'], 'date');
|
|
653
|
+
var_dump(in_array('2027-04-14', $dates)); // bool(true)
|
|
654
|
+
```
|
|
655
|
+
|
|
656
|
+
From JavaScript, you can also import a year's JSON directly:
|
|
657
|
+
|
|
658
|
+
```ts
|
|
659
|
+
import data2027 from 'lk-holidays/data/2027.json' with { type: 'json' };
|
|
660
|
+
```
|
|
661
|
+
|
|
662
|
+
---
|
|
663
|
+
|
|
664
|
+
## Holiday keys
|
|
665
|
+
|
|
666
|
+
`key` stays the same every year, even when CBSL changes the English spelling
|
|
667
|
+
(for example `Deepavali` / `Deepawali`).
|
|
668
|
+
|
|
669
|
+
| Key | Holiday |
|
|
670
|
+
| --- | --- |
|
|
671
|
+
| `thai-pongal` | Tamil Thai Pongal Day |
|
|
672
|
+
| `independence-day` | Independence Day |
|
|
673
|
+
| `mahasivarathri` | Mahasivarathri Day |
|
|
674
|
+
| `eid-ul-fitr` | Id-Ul-Fitr (Ramazan Festival Day) |
|
|
675
|
+
| `good-friday` | Good Friday |
|
|
676
|
+
| `new-year-eve` | Day prior to Sinhala & Tamil New Year Day |
|
|
677
|
+
| `new-year` | Sinhala & Tamil New Year Day |
|
|
678
|
+
| `may-day` | May Day (International Workers' Day) |
|
|
679
|
+
| `day-after-vesak` | Day following Vesak Full Moon Poya Day |
|
|
680
|
+
| `eid-ul-adha` | Id-Ul-Alha (Hadji Festival Day) |
|
|
681
|
+
| `milad-un-nabi` | Milad-Un-Nabi (Holy Prophet's Birthday) |
|
|
682
|
+
| `deepavali` | Deepavali Festival Day |
|
|
683
|
+
| `christmas` | Christmas Day |
|
|
684
|
+
| `special-bank-holiday` | Special Bank Holiday |
|
|
685
|
+
|
|
686
|
+
**Poya days:** `duruthu-poya`, `navam-poya`, `medin-poya`, `bak-poya`,
|
|
687
|
+
`vesak-poya`, `poson-poya`, `esala-poya`, `nikini-poya`, `binara-poya`,
|
|
688
|
+
`vap-poya`, `ill-poya`, `unduvap-poya`. Adhi Poyas add an `adhi-` prefix, such
|
|
689
|
+
as `adhi-poson-poya`.
|
|
690
|
+
|
|
691
|
+
One-off holidays that aren't in this list get a key made from their English
|
|
692
|
+
name, such as `special-public-holiday`.
|
|
693
|
+
|
|
694
|
+
---
|
|
695
|
+
|
|
696
|
+
## TypeScript
|
|
697
|
+
|
|
698
|
+
Types are included. Import them like this:
|
|
699
|
+
|
|
700
|
+
```ts
|
|
701
|
+
import type {
|
|
702
|
+
Holiday,
|
|
703
|
+
HolidayCategory, // 'bank' | 'public' | 'mercantile'
|
|
704
|
+
HolidayName, // { en, si, ta }
|
|
705
|
+
Language, // 'en' | 'si' | 'ta'
|
|
706
|
+
DateInput, // string | Date
|
|
707
|
+
FilterOptions,
|
|
708
|
+
WorkingDayOptions,
|
|
709
|
+
YearData,
|
|
710
|
+
HolidayApi, // return type of createHolidayApi
|
|
711
|
+
} from 'lk-holidays';
|
|
712
|
+
```
|
|
713
|
+
|
|
714
|
+
---
|
|
715
|
+
|
|
716
|
+
## How the data is kept up to date
|
|
717
|
+
|
|
718
|
+
A scheduled GitHub Action:
|
|
719
|
+
|
|
720
|
+
1. Runs on the 1st and 15th of every month, and every Monday from September to December.
|
|
721
|
+
2. Downloads the CBSL English, Sinhala and Tamil holiday pages for the current and next year.
|
|
722
|
+
3. Validates the result: real dates, 12–13 Poya days, key holidays present, a category on every holiday.
|
|
723
|
+
4. If anything changed, commits the new data and publishes a new version to npm.
|
|
724
|
+
|
|
725
|
+
New years are released as **minor** versions (`1.1.0`); corrections are
|
|
726
|
+
**patch** versions (`1.0.1`). To get them:
|
|
727
|
+
|
|
728
|
+
```bash
|
|
729
|
+
npm update lk-holidays
|
|
730
|
+
```
|
|
731
|
+
|
|
732
|
+
Dependabot or Renovate can do this for you automatically.
|
|
733
|
+
|
|
734
|
+
CBSL usually publishes the next year's holidays in September or October, so
|
|
735
|
+
the new year is normally on npm well before 1 January.
|
|
736
|
+
|
|
737
|
+
Dates, English names and categories come straight from CBSL. Sinhala and Tamil
|
|
738
|
+
names come from CBSL's Sinhala and Tamil pages, with spelling kept consistent
|
|
739
|
+
from year to year.
|
|
740
|
+
|
|
741
|
+
---
|
|
742
|
+
|
|
743
|
+
## FAQ
|
|
744
|
+
|
|
745
|
+
**Why does a function throw for a year?**
|
|
746
|
+
That year isn't in your installed version. Run `npm update lk-holidays`. If
|
|
747
|
+
the newest version still doesn't have it, CBSL hasn't published that year yet.
|
|
748
|
+
|
|
749
|
+
**Why isn't Saturday a holiday?**
|
|
750
|
+
Weekends aren't in the holiday list. Use `isWorkingDay` and the other
|
|
751
|
+
working-day functions, and set `weekendDays` to match your organisation.
|
|
752
|
+
|
|
753
|
+
**Which category should my company use?**
|
|
754
|
+
Government and schools: `public`. Banks: `bank`. Private companies usually
|
|
755
|
+
follow `mercantile`, but many give all public holidays, in which case use
|
|
756
|
+
`public`.
|
|
757
|
+
|
|
758
|
+
**A special holiday was announced. When will it show up?**
|
|
759
|
+
On the next scheduled run after CBSL adds it to their page. Until then, add it
|
|
760
|
+
yourself with the `extraHolidays` option.
|
|
761
|
+
|
|
762
|
+
**Does it work in the browser or React Native?**
|
|
763
|
+
Yes. The data is compiled into the package, so there's no file or network
|
|
764
|
+
access at runtime.
|
|
765
|
+
|
|
766
|
+
**How big is it?**
|
|
767
|
+
About 15 kB compressed for all years, with no dependencies.
|
|
768
|
+
|
|
769
|
+
---
|
|
770
|
+
|
|
771
|
+
## Contributing
|
|
772
|
+
|
|
773
|
+
```bash
|
|
774
|
+
git clone https://github.com/Sasitha-Ruvin/lk-holidays.git
|
|
775
|
+
cd lk-holidays
|
|
776
|
+
npm install
|
|
777
|
+
npm test
|
|
778
|
+
```
|
|
779
|
+
|
|
780
|
+
| Command | What it does |
|
|
781
|
+
| --- | --- |
|
|
782
|
+
| `npm test` | Run the tests |
|
|
783
|
+
| `npm run build` | Build `dist/` (ESM + CommonJS + types) |
|
|
784
|
+
| `npm run typecheck` | Type-check everything |
|
|
785
|
+
| `npm run scrape -- 2028` | Download a year from CBSL into `data/2028.json` |
|
|
786
|
+
| `npm run import-text -- 2028 file.txt` | Import a year from text copied off the CBSL page |
|
|
787
|
+
|
|
788
|
+
Found a wrong date or name? Please open an issue with a link to the CBSL page.
|
|
789
|
+
|
|
790
|
+
---
|
|
791
|
+
|
|
792
|
+
## Disclaimer
|
|
793
|
+
|
|
794
|
+
The data comes from the Central Bank of Sri Lanka website. The Government can
|
|
795
|
+
declare special holidays at short notice by gazette, and the package only
|
|
796
|
+
includes them after CBSL updates its page. This is an independent open-source
|
|
797
|
+
project, not affiliated with CBSL. Confirm with official sources for legal or
|
|
798
|
+
financial decisions.
|
|
799
|
+
|
|
800
|
+
## License
|
|
801
|
+
|
|
802
|
+
[MIT](LICENSE)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lk-holidays",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "Sri Lankan public, bank and mercantile holidays from the Central Bank of Sri Lanka (CBSL), with English, Sinhala and Tamil names. Auto-updated every year. Zero dependencies, works offline.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"sri-lanka",
|
|
@@ -70,4 +70,4 @@
|
|
|
70
70
|
"tsx": "^4.20.0",
|
|
71
71
|
"typescript": "^6.0.0"
|
|
72
72
|
}
|
|
73
|
-
}
|
|
73
|
+
}
|