soff-date
v1.0.3
Published
Lightweight, tree-shakeable holiday calculator with algorithmic date computation
Maintainers
Readme
Zero dependencies · TypeScript · ~3KB per locale
Table of Contents
- Table of Contents
- 🤔 Why?
- 📦 Install
- 🚀 Quick Start
- 🌍 i18n Support
- Available Locales
- Available Languages
- Shift Rules Explained
- Advanced: Create Your Own Locale
- Advanced: Use Algorithms Directly
- Bundle Size
- API Reference
- Business Days Calculation
- Types
- Contributing
- License
- Documentation
- Contributors
🤔 Why?
Most holiday libraries ship giant JSON files with dates until 2050. This library calculates dates algorithmically, supporting:
| Feature | Description | Example | | ---------------------- | ----------------------------- | ------------------------------- | | 📅 Fixed Dates | Static dates every year | December 25 (Christmas) | | 📆 Nth Weekday | Relative weekday calculations | 3rd Monday of January (MLK Day) | | ✨ Easter-relative | Based on Easter calculation | Good Friday = Easter - 2 days | | 🔄 Shift Rules | Move holidays to workdays | Colombia's Emiliani Law |
Result: Tiny bundle size (~3KB) with unlimited year support! 🎉
📦 Install
# npm
npm install soff-date
# pnpm
pnpm add soff-date
# yarn
yarn add soff-date
# bun
bun add soff-date🚀 Quick Start
// Only Colombia included in bundle (~3KB)
import { getHolidays, isHoliday, getNextHoliday } from 'soff-date/locales/co';
// 🏆 Get all holidays for a year
getHolidays(2025);
// → [{ date: '2025-01-01', key: 'newYear', name: 'Año Nuevo' }, ...]
// ❓ Check if a date is a holiday
isHoliday(new Date('2025-01-01'));
// → { date: '2025-01-01', key: 'newYear', name: 'Año Nuevo' }
isHoliday(new Date('2025-01-02'));
// → null
// ➡️ Get next holiday from a date
getNextHoliday(new Date('2025-01-02'));
// → { date: '2025-01-06', key: 'epiphany', name: 'Día de los Reyes Magos' }🌍 i18n Support
import { getHolidays } from 'soff-date/locales/co';
import { en } from 'soff-date/i18n/en';
getHolidays(2025, { lang: en });
// → [{ date: '2025-01-01', key: 'newYear', name: "New Year's Day" }, ...]
// Custom override
getHolidays(2025, { lang: { ...en, newYear: 'Happy New Year!' } });Available Locales
| Locale | Import | Holidays | Shift Rule |
| ------------ | ---------------------- | -------- | ---------- |
| 🇨🇴 Colombia | soff-date/locales/co | 18 | Emiliani |
| 🇺🇸 USA | soff-date/locales/us | 10 | Observed |
| 🇲🇽 México | soff-date/locales/mx | 8 | NthWeekday |
| 🇦🇷 Argentina | soff-date/locales/ar | 16 | NearestMon |
| 🇧🇷 Brasil | soff-date/locales/br | 13 | None |
Available Languages
| Language | Import |
| --------- | ------------------- |
| Español | soff-date/i18n/es |
| English | soff-date/i18n/en |
| Português | soff-date/i18n/pt |
Shift Rules Explained
Emiliani (Colombia, Argentina)
Holidays falling on weekends move to Monday.
// January 6, 2024 = Saturday → Monday January 8
{ date: '2024-01-08', key: 'epiphany', isShifted: true }Observed US (USA, UK)
- Saturday → Friday (before)
- Sunday → Monday (after)
// July 4, 2026 = Saturday → Friday July 3
{ date: '2026-07-03', key: 'independenceDayUS', isShifted: true }Advanced: Create Your Own Locale
import type { HolidayDefinition, Holiday, HolidayNames } from 'soff-date';
import { resolveHolidays, checkIsHoliday, findNextHoliday } from 'soff-date';
const definitions: HolidayDefinition[] = [
// Fixed date
{ key: 'newYear', rule: { type: 'fixed', month: 1, day: 1 } },
// Fixed with shift
{ key: 'christmas', rule: { type: 'fixed', month: 12, day: 25 }, shift: 'observedUS' },
// Nth weekday: 3rd Monday of January
{ key: 'mlkDay', rule: { type: 'nthWeekday', month: 1, weekday: 1, n: 3 } },
// Last Monday of May
{ key: 'memorialDay', rule: { type: 'nthWeekday', month: 5, weekday: 1, n: -1 } },
// Easter relative: Good Friday = Easter - 2
{ key: 'goodFriday', rule: { type: 'easterRelative', offset: -2 } },
// Custom calculation
{
key: 'custom',
rule: {
type: 'custom',
calc: (year) => new Date(year, 5, 15), // June 15
},
},
];
const names: HolidayNames = {
newYear: "New Year's Day",
christmas: 'Christmas',
// ...
};
export function getHolidays(year: number): Holiday[] {
return resolveHolidays(definitions, year, names);
}Advanced: Use Algorithms Directly
import { getEasterSunday } from 'soff-date/core/algorithms/easter';
import { getNthWeekday } from 'soff-date/core/algorithms/nth-weekday';
import { applyShift } from 'soff-date/core/algorithms/shifts';
// Easter Sunday 2025
getEasterSunday(2025); // → Date(2025, 3, 20)
// 4th Thursday of November 2025 (Thanksgiving)
getNthWeekday(2025, 11, 4, 4); // → Date(2025, 10, 27)
// Apply observed shift
applyShift(new Date('2026-07-04'), 'observedUS');
// → { date: Date(2026-07-03), shifted: true }Bundle Size
| Import | Size (minified) |
| ------------ | --------------- |
| locales/co | ~5.8KB |
| locales/us | ~4.5KB |
| i18n/es | ~1.9KB |
| i18n/en | ~1.1KB |
| Core only | ~2.7KB |
Tree-shaking ensures you only ship what you import.
API Reference
getHolidays(year, options?)
Returns all holidays for a given year.
Note: Some holidays might fall on the same date (e.g., a fixed holiday coinciding with a movable one). Always use
holiday.keyas the unique identifier, not the date.
interface GetHolidaysOptions {
lang?: HolidayNames; // Custom translations
}
interface Holiday {
date: string; // ISO date: '2025-01-01'
key: string; // Identifier: 'newYear'
name: string; // Display name: 'Año Nuevo'
isShifted?: boolean; // True if moved by shift rule
}isHoliday(date, options?)
Returns holiday info if the date is a holiday, null otherwise.
getNextHoliday(from?, options?)
Returns the next holiday from a given date (defaults to today).
Business Days Calculation
In addition to holiday calculation, soff-date provides utilities to work with business days (skipping weekends and holidays).
import { isBusinessDay, businessDays, diffBusinessDays } from 'soff-date/locales/co';
// Check if a date is a business day
isBusinessDay(new Date('2025-01-01')); // false (Holiday)
isBusinessDay(new Date('2025-01-04')); // false (Saturday)
isBusinessDay(new Date('2025-01-02')); // true
// Add business days
businessDays(new Date('2025-01-03'), 1);
// → Date('2025-01-06') (Friday + 1 business day = Monday)
// Calculate difference in business days
diffBusinessDays(new Date('2025-01-06'), new Date('2025-01-10'));
// → 4Types
type ShiftRule = 'none' | 'emiliani' | 'observedUS' | 'nextMonday';
type HolidayRule =
| { type: 'fixed'; month: number; day: number }
| { type: 'nthWeekday'; month: number; weekday: number; n: number }
| { type: 'easterRelative'; offset: number }
| { type: 'custom'; calc: (year: number) => Date };
interface HolidayDefinition {
key: string;
rule: HolidayRule;
shift?: ShiftRule;
}Contributing
Please read CONTRIBUTING.md for details on our code of conduct, and the process for submitting pull requests to us.
License
This project is licensed under the MIT License - see the LICENSE file for details.
Documentation
Contributors
Thanks goes to these wonderful people (emoji key):
This project follows the all-contributors specification. Contributions of any kind welcome!
