@fin.cx/opendata 4.0.0 → 4.2.0
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/.smartconfig.json +7 -2
- package/dist_ts/00_commitinfo_data.js +3 -3
- package/dist_ts/baserate/baserate.d.ts +9 -0
- package/dist_ts/baserate/baserate.js +49 -0
- package/dist_ts/baserate/classes.baserateerror.d.ts +14 -0
- package/dist_ts/baserate/classes.baserateerror.js +10 -0
- package/dist_ts/baserate/data.baserate.d.ts +18 -0
- package/dist_ts/baserate/data.baserate.js +63 -0
- package/dist_ts/baserate/index.d.ts +4 -0
- package/dist_ts/baserate/index.js +7 -0
- package/dist_ts/baserate/interfaces.d.ts +19 -0
- package/dist_ts/baserate/interfaces.js +2 -0
- package/dist_ts/handelsregister/references.d.ts +6 -2
- package/dist_ts/handelsregister/references.js +90 -20
- package/dist_ts/holidays/calendar.d.ts +32 -0
- package/dist_ts/holidays/calendar.js +200 -0
- package/dist_ts/holidays/classes.holidaycalendarerror.d.ts +14 -0
- package/dist_ts/holidays/classes.holidaycalendarerror.js +10 -0
- package/dist_ts/holidays/data.holidays.d.ts +46 -0
- package/dist_ts/holidays/data.holidays.js +124 -0
- package/dist_ts/holidays/index.d.ts +4 -0
- package/dist_ts/holidays/index.js +7 -0
- package/dist_ts/holidays/interfaces.d.ts +42 -0
- package/dist_ts/holidays/interfaces.js +2 -0
- package/package.json +18 -5
- package/readme.md +121 -3
- package/ts/00_commitinfo_data.ts +2 -2
- package/ts/baserate/baserate.ts +57 -0
- package/ts/baserate/classes.baserateerror.ts +20 -0
- package/ts/baserate/data.baserate.ts +81 -0
- package/ts/baserate/index.ts +6 -0
- package/ts/baserate/interfaces.ts +21 -0
- package/ts/handelsregister/references.ts +94 -20
- package/ts/holidays/calendar.ts +261 -0
- package/ts/holidays/classes.holidaycalendarerror.ts +20 -0
- package/ts/holidays/data.holidays.ts +177 -0
- package/ts/holidays/index.ts +18 -0
- package/ts/holidays/interfaces.ts +87 -0
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import { HolidayCalendarError } from './classes.holidaycalendarerror.js';
|
|
2
|
+
import { germanStates, holidayCalendarFirstYear, holidayRules } from './data.holidays.js';
|
|
3
|
+
import type { IHolidayRule, THolidayDateRule } from './data.holidays.js';
|
|
4
|
+
import type {
|
|
5
|
+
IGermanState,
|
|
6
|
+
IPublicHoliday,
|
|
7
|
+
IWorkingDayOptions,
|
|
8
|
+
TGermanStateCode,
|
|
9
|
+
TIsoDate,
|
|
10
|
+
} from './interfaces.js';
|
|
11
|
+
|
|
12
|
+
const dayMs = 86_400_000;
|
|
13
|
+
const lastYear = 9999;
|
|
14
|
+
|
|
15
|
+
interface ICalendarDay {
|
|
16
|
+
year: number;
|
|
17
|
+
/** ms since the epoch at 00:00 UTC of the day */
|
|
18
|
+
utc: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const pad = (valueArg: number, lengthArg: number): string => String(valueArg).padStart(lengthArg, '0');
|
|
22
|
+
|
|
23
|
+
const toIsoDate = (utcArg: number): TIsoDate => {
|
|
24
|
+
const date = new Date(utcArg);
|
|
25
|
+
return `${pad(date.getUTCFullYear(), 4)}-${pad(date.getUTCMonth() + 1, 2)}-${pad(date.getUTCDate(), 2)}`;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const utcOf = (yearArg: number, monthArg: number, dayArg: number): number => {
|
|
29
|
+
const date = new Date(0);
|
|
30
|
+
date.setUTCFullYear(yearArg, monthArg - 1, dayArg);
|
|
31
|
+
date.setUTCHours(0, 0, 0, 0);
|
|
32
|
+
return date.getTime();
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const checkYear = (yearArg: number): void => {
|
|
36
|
+
if (!Number.isInteger(yearArg) || yearArg < holidayCalendarFirstYear || yearArg > lastYear) {
|
|
37
|
+
throw new HolidayCalendarError(
|
|
38
|
+
'unsupported_year',
|
|
39
|
+
`The holiday calendar answers for the years ${holidayCalendarFirstYear} to ${lastYear}, not for ${String(yearArg)}.`,
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const parseIsoDate = (dateArg: TIsoDate): ICalendarDay => {
|
|
45
|
+
const match = typeof dateArg === 'string' ? /^(\d{4})-(\d{2})-(\d{2})$/.exec(dateArg) : null;
|
|
46
|
+
if (!match) {
|
|
47
|
+
throw new HolidayCalendarError('invalid_date', `A date is written YYYY-MM-DD; ${JSON.stringify(dateArg)} is not.`);
|
|
48
|
+
}
|
|
49
|
+
const year = Number(match[1]);
|
|
50
|
+
const month = Number(match[2]);
|
|
51
|
+
const day = Number(match[3]);
|
|
52
|
+
checkYear(year);
|
|
53
|
+
const utc = utcOf(year, month, day);
|
|
54
|
+
if (toIsoDate(utc) !== dateArg) {
|
|
55
|
+
throw new HolidayCalendarError('invalid_date', `${dateArg} is not a date of the calendar.`);
|
|
56
|
+
}
|
|
57
|
+
return { year, utc };
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const checkState = (stateArg: TGermanStateCode): void => {
|
|
61
|
+
if (!germanStates.some((stateEntry) => stateEntry.code === stateArg)) {
|
|
62
|
+
throw new HolidayCalendarError(
|
|
63
|
+
'unknown_state',
|
|
64
|
+
`${JSON.stringify(stateArg)} is not the ISO 3166-2 code of a German federal state, such as DE-BY.`,
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const checkOptions = (optionsArg: IWorkingDayOptions): void => {
|
|
70
|
+
const policy: unknown = optionsArg?.regionalHolidays;
|
|
71
|
+
if (policy !== 'count' && policy !== 'ignore') {
|
|
72
|
+
throw new HolidayCalendarError(
|
|
73
|
+
'invalid_options',
|
|
74
|
+
'Say how regional holidays are treated: { regionalHolidays: "count" } or { regionalHolidays: "ignore" }.',
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Easter Sunday of a year of the Gregorian calendar, by the algorithm of Meeus, Jones and
|
|
81
|
+
* Butcher. The Easter-based holidays follow from it.
|
|
82
|
+
*/
|
|
83
|
+
export const easterSunday = (yearArg: number): TIsoDate => {
|
|
84
|
+
checkYear(yearArg);
|
|
85
|
+
return toIsoDate(easterUtc(yearArg));
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const easterUtc = (yearArg: number): number => {
|
|
89
|
+
const a = yearArg % 19;
|
|
90
|
+
const b = Math.floor(yearArg / 100);
|
|
91
|
+
const c = yearArg % 100;
|
|
92
|
+
const d = Math.floor(b / 4);
|
|
93
|
+
const e = b % 4;
|
|
94
|
+
const f = Math.floor((b + 8) / 25);
|
|
95
|
+
const g = Math.floor((b - f + 1) / 3);
|
|
96
|
+
const h = (19 * a + b - d - g + 15) % 30;
|
|
97
|
+
const i = Math.floor(c / 4);
|
|
98
|
+
const k = c % 4;
|
|
99
|
+
const l = (32 + 2 * e + 2 * i - h - k) % 7;
|
|
100
|
+
const m = Math.floor((a + 11 * h + 22 * l) / 451);
|
|
101
|
+
const month = Math.floor((h + l - 7 * m + 114) / 31);
|
|
102
|
+
const day = ((h + l - 7 * m + 114) % 31) + 1;
|
|
103
|
+
return utcOf(yearArg, month, day);
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
/** the Wednesday before the last Sunday before the first Sunday of Advent (16 to 22 November) */
|
|
107
|
+
const bussUndBettagUtc = (yearArg: number): number => {
|
|
108
|
+
const christmasEve = utcOf(yearArg, 12, 24);
|
|
109
|
+
const fourthAdvent = christmasEve - new Date(christmasEve).getUTCDay() * dayMs;
|
|
110
|
+
const firstAdvent = fourthAdvent - 21 * dayMs;
|
|
111
|
+
return firstAdvent - 11 * dayMs;
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const dateOf = (ruleArg: THolidayDateRule, yearArg: number): number => {
|
|
115
|
+
switch (ruleArg.kind) {
|
|
116
|
+
case 'fixed':
|
|
117
|
+
return utcOf(yearArg, ruleArg.month, ruleArg.day);
|
|
118
|
+
case 'easter':
|
|
119
|
+
return easterUtc(yearArg) + ruleArg.offset * dayMs;
|
|
120
|
+
case 'buss-und-bettag':
|
|
121
|
+
return bussUndBettagUtc(yearArg);
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const appliesIn = (ruleArg: IHolidayRule, yearArg: number): boolean =>
|
|
126
|
+
(ruleArg.onlyYear === undefined || ruleArg.onlyYear === yearArg) &&
|
|
127
|
+
(ruleArg.fromYear === undefined || yearArg >= ruleArg.fromYear);
|
|
128
|
+
|
|
129
|
+
const holidaysByYearAndState = new Map<string, readonly IPublicHoliday[]>();
|
|
130
|
+
|
|
131
|
+
const holidaysOf = (yearArg: number, stateArg: TGermanStateCode): readonly IPublicHoliday[] => {
|
|
132
|
+
const key = `${yearArg}:${stateArg}`;
|
|
133
|
+
const known = holidaysByYearAndState.get(key);
|
|
134
|
+
if (known) {
|
|
135
|
+
return known;
|
|
136
|
+
}
|
|
137
|
+
const holidays = holidayRules
|
|
138
|
+
.filter((ruleArg) => ruleArg.state === stateArg && appliesIn(ruleArg, yearArg))
|
|
139
|
+
.map((ruleArg) => ({ utc: dateOf(ruleArg.date, yearArg), rule: ruleArg }))
|
|
140
|
+
.sort((leftArg, rightArg) => leftArg.utc - rightArg.utc)
|
|
141
|
+
.map(({ utc, rule }): IPublicHoliday => {
|
|
142
|
+
const holiday: IPublicHoliday = {
|
|
143
|
+
date: toIsoDate(utc),
|
|
144
|
+
id: rule.id,
|
|
145
|
+
name: rule.name,
|
|
146
|
+
state: rule.state,
|
|
147
|
+
scope: rule.scope,
|
|
148
|
+
legalBasis: rule.legalBasis,
|
|
149
|
+
};
|
|
150
|
+
if (rule.region !== undefined) {
|
|
151
|
+
holiday.region = rule.region;
|
|
152
|
+
}
|
|
153
|
+
return Object.freeze(holiday);
|
|
154
|
+
});
|
|
155
|
+
const frozen = Object.freeze(holidays);
|
|
156
|
+
holidaysByYearAndState.set(key, frozen);
|
|
157
|
+
return frozen;
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* The statutory public holidays of a state in a year, in the order of their dates. Regional
|
|
162
|
+
* holidays are included with `scope: 'regional'` and the part of the state they apply to.
|
|
163
|
+
*/
|
|
164
|
+
export const getPublicHolidays = (yearArg: number, stateArg: TGermanStateCode): IPublicHoliday[] => {
|
|
165
|
+
checkYear(yearArg);
|
|
166
|
+
checkState(stateArg);
|
|
167
|
+
return [...holidaysOf(yearArg, stateArg)];
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
/** The statutory public holidays of a state on a date, regional ones included. */
|
|
171
|
+
export const getPublicHolidaysOn = (dateArg: TIsoDate, stateArg: TGermanStateCode): IPublicHoliday[] => {
|
|
172
|
+
const day = parseIsoDate(dateArg);
|
|
173
|
+
checkState(stateArg);
|
|
174
|
+
return holidaysOf(day.year, stateArg).filter((holidayArg) => holidayArg.date === dateArg);
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
const isHolidayUtc = (utcArg: number, stateArg: TGermanStateCode, optionsArg: IWorkingDayOptions): boolean => {
|
|
178
|
+
const date = toIsoDate(utcArg);
|
|
179
|
+
const year = new Date(utcArg).getUTCFullYear();
|
|
180
|
+
checkYear(year);
|
|
181
|
+
return holidaysOf(year, stateArg).some(
|
|
182
|
+
(holidayArg) =>
|
|
183
|
+
holidayArg.date === date && (holidayArg.scope === 'state' || optionsArg.regionalHolidays === 'count'),
|
|
184
|
+
);
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
const isWorkingDayUtc = (utcArg: number, stateArg: TGermanStateCode, optionsArg: IWorkingDayOptions): boolean => {
|
|
188
|
+
const weekday = new Date(utcArg).getUTCDay();
|
|
189
|
+
return weekday !== 0 && weekday !== 6 && !isHolidayUtc(utcArg, stateArg, optionsArg);
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
/** Whether a date is a statutory public holiday in a state. */
|
|
193
|
+
export const isPublicHoliday = (
|
|
194
|
+
dateArg: TIsoDate,
|
|
195
|
+
stateArg: TGermanStateCode,
|
|
196
|
+
optionsArg: IWorkingDayOptions,
|
|
197
|
+
): boolean => {
|
|
198
|
+
const day = parseIsoDate(dateArg);
|
|
199
|
+
checkState(stateArg);
|
|
200
|
+
checkOptions(optionsArg);
|
|
201
|
+
return isHolidayUtc(day.utc, stateArg, optionsArg);
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Whether a date is a working day in a state in the sense of § 193 BGB: neither a Saturday, nor a
|
|
206
|
+
* Sunday, nor a statutory public holiday.
|
|
207
|
+
*/
|
|
208
|
+
export const isWorkingDay = (dateArg: TIsoDate, stateArg: TGermanStateCode, optionsArg: IWorkingDayOptions): boolean => {
|
|
209
|
+
const day = parseIsoDate(dateArg);
|
|
210
|
+
checkState(stateArg);
|
|
211
|
+
checkOptions(optionsArg);
|
|
212
|
+
return isWorkingDayUtc(day.utc, stateArg, optionsArg);
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
/** The first working day after a date (§ 193 BGB: not a Saturday, Sunday or public holiday). */
|
|
216
|
+
export const nextWorkingDay = (dateArg: TIsoDate, stateArg: TGermanStateCode, optionsArg: IWorkingDayOptions): TIsoDate => {
|
|
217
|
+
const day = parseIsoDate(dateArg);
|
|
218
|
+
checkState(stateArg);
|
|
219
|
+
checkOptions(optionsArg);
|
|
220
|
+
let utc = day.utc + dayMs;
|
|
221
|
+
while (!isWorkingDayUtc(utc, stateArg, optionsArg)) {
|
|
222
|
+
utc += dayMs;
|
|
223
|
+
}
|
|
224
|
+
return toIsoDate(utc);
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* The date itself when it is a working day, otherwise the next working day: the day that takes the
|
|
229
|
+
* place of a fixed day or of the last day of a period under § 193 BGB.
|
|
230
|
+
*/
|
|
231
|
+
export const workingDayOnOrAfter = (
|
|
232
|
+
dateArg: TIsoDate,
|
|
233
|
+
stateArg: TGermanStateCode,
|
|
234
|
+
optionsArg: IWorkingDayOptions,
|
|
235
|
+
): TIsoDate => {
|
|
236
|
+
const day = parseIsoDate(dateArg);
|
|
237
|
+
checkState(stateArg);
|
|
238
|
+
checkOptions(optionsArg);
|
|
239
|
+
let utc = day.utc;
|
|
240
|
+
while (!isWorkingDayUtc(utc, stateArg, optionsArg)) {
|
|
241
|
+
utc += dayMs;
|
|
242
|
+
}
|
|
243
|
+
return toIsoDate(utc);
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* The federal state for a code or a name: `DE-BY`, `BY` or `Bayern`, compared without regard to
|
|
248
|
+
* case and surrounding whitespace.
|
|
249
|
+
*/
|
|
250
|
+
export const findGermanState = (valueArg: string): IGermanState | undefined => {
|
|
251
|
+
if (typeof valueArg !== 'string') {
|
|
252
|
+
return undefined;
|
|
253
|
+
}
|
|
254
|
+
const value = valueArg.trim().toLocaleLowerCase('de-DE');
|
|
255
|
+
return germanStates.find(
|
|
256
|
+
(stateArg) =>
|
|
257
|
+
stateArg.code.toLowerCase() === value ||
|
|
258
|
+
stateArg.code.slice(3).toLowerCase() === value ||
|
|
259
|
+
stateArg.name.toLocaleLowerCase('de-DE') === value,
|
|
260
|
+
);
|
|
261
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Why a question to the holiday calendar was refused:
|
|
3
|
+
* - `invalid_date`: the value is not a calendar date written `YYYY-MM-DD`;
|
|
4
|
+
* - `unsupported_year`: the year lies before the first year the calendar answers for (2018) or
|
|
5
|
+
* after 9999;
|
|
6
|
+
* - `unknown_state`: the value is not the code of a German federal state;
|
|
7
|
+
* - `invalid_options`: the options do not say how to treat regional holidays.
|
|
8
|
+
*/
|
|
9
|
+
export type THolidayCalendarErrorCode = 'invalid_date' | 'unsupported_year' | 'unknown_state' | 'invalid_options';
|
|
10
|
+
|
|
11
|
+
/** The typed error of the holiday calendar. */
|
|
12
|
+
export class HolidayCalendarError extends Error {
|
|
13
|
+
public readonly code: THolidayCalendarErrorCode;
|
|
14
|
+
|
|
15
|
+
constructor(code: THolidayCalendarErrorCode, message: string) {
|
|
16
|
+
super(message);
|
|
17
|
+
this.name = 'HolidayCalendarError';
|
|
18
|
+
this.code = code;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The statutory public holidays of the German federal states, taken from the holiday laws of the
|
|
3
|
+
* states (Feiertagsgesetze) and, for 3 October, from Art. 2 Abs. 2 of the Unification Treaty
|
|
4
|
+
* (Einigungsvertrag). The provisions were read on 2026-09-24 in the official law portals of the
|
|
5
|
+
* states; the readme lists them with their links.
|
|
6
|
+
*
|
|
7
|
+
* The rules cover the years from 2018 on. Every change of a state's list since then is recorded
|
|
8
|
+
* with the year it first applies: Reformationstag in Bremen, Hamburg, Niedersachsen and
|
|
9
|
+
* Schleswig-Holstein (2018), Frauentag in Berlin (2019) and Mecklenburg-Vorpommern (2023),
|
|
10
|
+
* Weltkindertag in Thüringen (2019), and the one-off holidays of Berlin (2020, 2025, 2028).
|
|
11
|
+
*/
|
|
12
|
+
import type { IGermanState, TGermanStateCode, TPublicHolidayId, TPublicHolidayScope } from './interfaces.js';
|
|
13
|
+
|
|
14
|
+
/** the first year the calendar answers for */
|
|
15
|
+
export const holidayCalendarFirstYear = 2018;
|
|
16
|
+
|
|
17
|
+
/** the day the holiday laws were read; later changes need a new release */
|
|
18
|
+
export const holidayLawsReadOn = '2026-09-24';
|
|
19
|
+
|
|
20
|
+
export const germanStates: readonly IGermanState[] = Object.freeze([
|
|
21
|
+
{ code: 'DE-BW', name: 'Baden-Württemberg' },
|
|
22
|
+
{ code: 'DE-BY', name: 'Bayern' },
|
|
23
|
+
{ code: 'DE-BE', name: 'Berlin' },
|
|
24
|
+
{ code: 'DE-BB', name: 'Brandenburg' },
|
|
25
|
+
{ code: 'DE-HB', name: 'Bremen' },
|
|
26
|
+
{ code: 'DE-HH', name: 'Hamburg' },
|
|
27
|
+
{ code: 'DE-HE', name: 'Hessen' },
|
|
28
|
+
{ code: 'DE-MV', name: 'Mecklenburg-Vorpommern' },
|
|
29
|
+
{ code: 'DE-NI', name: 'Niedersachsen' },
|
|
30
|
+
{ code: 'DE-NW', name: 'Nordrhein-Westfalen' },
|
|
31
|
+
{ code: 'DE-RP', name: 'Rheinland-Pfalz' },
|
|
32
|
+
{ code: 'DE-SL', name: 'Saarland' },
|
|
33
|
+
{ code: 'DE-SN', name: 'Sachsen' },
|
|
34
|
+
{ code: 'DE-ST', name: 'Sachsen-Anhalt' },
|
|
35
|
+
{ code: 'DE-SH', name: 'Schleswig-Holstein' },
|
|
36
|
+
{ code: 'DE-TH', name: 'Thüringen' },
|
|
37
|
+
] as const satisfies readonly IGermanState[]);
|
|
38
|
+
|
|
39
|
+
/** the provision of each state's holiday law that lists its statutory holidays */
|
|
40
|
+
const holidayListOf: Readonly<Record<TGermanStateCode, string>> = {
|
|
41
|
+
'DE-BW': '§ 1 FTG (Baden-Württemberg)',
|
|
42
|
+
'DE-BY': 'Art. 1 Abs. 1 Nr. 1 FTG (Bayern)',
|
|
43
|
+
'DE-BE': '§ 1 Abs. 1 Gesetz über die Sonn- und Feiertage (Berlin)',
|
|
44
|
+
'DE-BB': '§ 2 Abs. 1 FTG (Brandenburg)',
|
|
45
|
+
'DE-HB': '§ 2 Abs. 1 Gesetz über die Sonn-, Gedenk- und Feiertage (Bremen)',
|
|
46
|
+
'DE-HH': '§ 1 Feiertagsgesetz (Hamburg)',
|
|
47
|
+
'DE-HE': '§ 1 Abs. 1 HFeiertagsG',
|
|
48
|
+
'DE-MV': '§ 2 Abs. 1 FTG M-V',
|
|
49
|
+
'DE-NI': '§ 2 Abs. 1 NFeiertagsG',
|
|
50
|
+
'DE-NW': '§ 2 Abs. 1 Feiertagsgesetz NW',
|
|
51
|
+
'DE-RP': '§ 2 Abs. 1 LFtG',
|
|
52
|
+
'DE-SL': '§ 2 Abs. 1 SFG',
|
|
53
|
+
'DE-SN': '§ 1 Abs. 1 SächsSFG',
|
|
54
|
+
'DE-ST': '§ 2 FeiertG LSA',
|
|
55
|
+
'DE-SH': '§ 2 Abs. 1 SFTG',
|
|
56
|
+
'DE-TH': '§ 2 Abs. 1 ThürFGtG',
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/** how the date of a holiday follows from the year */
|
|
60
|
+
export type THolidayDateRule =
|
|
61
|
+
| { kind: 'fixed'; month: number; day: number }
|
|
62
|
+
/** days after Easter Sunday; negative before it */
|
|
63
|
+
| { kind: 'easter'; offset: number }
|
|
64
|
+
/** the Wednesday before the last Sunday before the first Sunday of Advent */
|
|
65
|
+
| { kind: 'buss-und-bettag' };
|
|
66
|
+
|
|
67
|
+
export interface IHolidayRule {
|
|
68
|
+
id: TPublicHolidayId;
|
|
69
|
+
name: string;
|
|
70
|
+
date: THolidayDateRule;
|
|
71
|
+
state: TGermanStateCode;
|
|
72
|
+
scope: TPublicHolidayScope;
|
|
73
|
+
region?: string;
|
|
74
|
+
legalBasis: string;
|
|
75
|
+
/** the first year the holiday applies */
|
|
76
|
+
fromYear?: number;
|
|
77
|
+
/** the only year a one-off holiday applies */
|
|
78
|
+
onlyYear?: number;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const allStates = germanStates.map((stateArg) => stateArg.code);
|
|
82
|
+
|
|
83
|
+
const fixed = (month: number, day: number): THolidayDateRule => ({ kind: 'fixed', month, day });
|
|
84
|
+
const easter = (offset: number): THolidayDateRule => ({ kind: 'easter', offset });
|
|
85
|
+
|
|
86
|
+
/** a holiday in the whole of each listed state, by that state's holiday list */
|
|
87
|
+
const statewide = (
|
|
88
|
+
id: TPublicHolidayId,
|
|
89
|
+
name: string,
|
|
90
|
+
date: THolidayDateRule,
|
|
91
|
+
states: readonly TGermanStateCode[],
|
|
92
|
+
extra: Partial<Pick<IHolidayRule, 'fromYear' | 'onlyYear'>> = {},
|
|
93
|
+
): IHolidayRule[] =>
|
|
94
|
+
states.map((state) => ({ id, name, date, state, scope: 'state', legalBasis: holidayListOf[state], ...extra }));
|
|
95
|
+
|
|
96
|
+
export const holidayRules: readonly IHolidayRule[] = Object.freeze([
|
|
97
|
+
// in every state
|
|
98
|
+
...statewide('neujahr', 'Neujahr', fixed(1, 1), allStates),
|
|
99
|
+
...statewide('karfreitag', 'Karfreitag', easter(-2), allStates),
|
|
100
|
+
...statewide('ostermontag', 'Ostermontag', easter(1), allStates),
|
|
101
|
+
...statewide('tag-der-arbeit', 'Tag der Arbeit', fixed(5, 1), allStates),
|
|
102
|
+
...statewide('christi-himmelfahrt', 'Christi Himmelfahrt', easter(39), allStates),
|
|
103
|
+
...statewide('pfingstmontag', 'Pfingstmontag', easter(50), allStates),
|
|
104
|
+
...allStates.map((state): IHolidayRule => ({
|
|
105
|
+
id: 'tag-der-deutschen-einheit',
|
|
106
|
+
name: 'Tag der Deutschen Einheit',
|
|
107
|
+
date: fixed(10, 3),
|
|
108
|
+
state,
|
|
109
|
+
scope: 'state',
|
|
110
|
+
legalBasis: 'Art. 2 Abs. 2 Einigungsvertrag',
|
|
111
|
+
})),
|
|
112
|
+
...statewide('erster-weihnachtstag', '1. Weihnachtstag', fixed(12, 25), allStates),
|
|
113
|
+
...statewide('zweiter-weihnachtstag', '2. Weihnachtstag', fixed(12, 26), allStates),
|
|
114
|
+
|
|
115
|
+
// in some states
|
|
116
|
+
...statewide('heilige-drei-koenige', 'Heilige Drei Könige', fixed(1, 6), ['DE-BW', 'DE-BY', 'DE-ST']),
|
|
117
|
+
...statewide('frauentag', 'Frauentag', fixed(3, 8), ['DE-BE'], { fromYear: 2019 }),
|
|
118
|
+
...statewide('frauentag', 'Frauentag', fixed(3, 8), ['DE-MV'], { fromYear: 2023 }),
|
|
119
|
+
...statewide('ostersonntag', 'Ostersonntag', easter(0), ['DE-BB']),
|
|
120
|
+
...statewide('pfingstsonntag', 'Pfingstsonntag', easter(49), ['DE-BB']),
|
|
121
|
+
...statewide('fronleichnam', 'Fronleichnam', easter(60), ['DE-BW', 'DE-BY', 'DE-HE', 'DE-NW', 'DE-RP', 'DE-SL']),
|
|
122
|
+
...statewide('mariae-himmelfahrt', 'Mariä Himmelfahrt', fixed(8, 15), ['DE-SL']),
|
|
123
|
+
...statewide('weltkindertag', 'Weltkindertag', fixed(9, 20), ['DE-TH'], { fromYear: 2019 }),
|
|
124
|
+
...statewide('reformationstag', 'Reformationstag', fixed(10, 31), ['DE-BB', 'DE-MV', 'DE-SN', 'DE-ST', 'DE-TH']),
|
|
125
|
+
...statewide('reformationstag', 'Reformationstag', fixed(10, 31), ['DE-HB', 'DE-HH', 'DE-NI', 'DE-SH'], {
|
|
126
|
+
fromYear: 2018,
|
|
127
|
+
}),
|
|
128
|
+
...statewide('allerheiligen', 'Allerheiligen', fixed(11, 1), ['DE-BW', 'DE-BY', 'DE-NW', 'DE-RP', 'DE-SL']),
|
|
129
|
+
...statewide('buss-und-bettag', 'Buß- und Bettag', { kind: 'buss-und-bettag' }, ['DE-SN']),
|
|
130
|
+
|
|
131
|
+
// one-off holidays
|
|
132
|
+
...statewide('tag-der-befreiung', 'Tag der Befreiung', fixed(5, 8), ['DE-BE'], { onlyYear: 2020 }),
|
|
133
|
+
...statewide('tag-der-befreiung', 'Tag der Befreiung', fixed(5, 8), ['DE-BE'], { onlyYear: 2025 }),
|
|
134
|
+
...statewide('jahrestag-17-juni-1953', '75. Jahrestag des Aufstandes vom 17. Juni 1953', fixed(6, 17), ['DE-BE'], {
|
|
135
|
+
onlyYear: 2028,
|
|
136
|
+
}),
|
|
137
|
+
|
|
138
|
+
// in part of a state
|
|
139
|
+
{
|
|
140
|
+
id: 'fronleichnam',
|
|
141
|
+
name: 'Fronleichnam',
|
|
142
|
+
date: easter(60),
|
|
143
|
+
state: 'DE-SN',
|
|
144
|
+
scope: 'regional',
|
|
145
|
+
region:
|
|
146
|
+
'die in der Anlage der FronleichnamsVO genannten Gemeinden der früheren Landkreise Bautzen, Hoyerswerda und Kamenz, nach einer Neugliederung nur der jeweilige Gemeindeteil',
|
|
147
|
+
legalBasis: '§ 1 Abs. 1 SächsSFG i. V. m. § 1 FronleichnamsVO',
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
id: 'fronleichnam',
|
|
151
|
+
name: 'Fronleichnam',
|
|
152
|
+
date: easter(60),
|
|
153
|
+
state: 'DE-TH',
|
|
154
|
+
scope: 'regional',
|
|
155
|
+
region: 'die Teile Thüringens, in denen Fronleichnam 1994 gesetzlicher Feiertag war',
|
|
156
|
+
legalBasis: '§ 2 Abs. 2 i. V. m. § 10 Abs. 1 ThürFGtG',
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
id: 'augsburger-friedensfest',
|
|
160
|
+
name: 'Augsburger Friedensfest',
|
|
161
|
+
date: fixed(8, 8),
|
|
162
|
+
state: 'DE-BY',
|
|
163
|
+
scope: 'regional',
|
|
164
|
+
region: 'Stadt Augsburg',
|
|
165
|
+
legalBasis: 'Art. 1 Abs. 2 FTG (Bayern)',
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
id: 'mariae-himmelfahrt',
|
|
169
|
+
name: 'Mariä Himmelfahrt',
|
|
170
|
+
date: fixed(8, 15),
|
|
171
|
+
state: 'DE-BY',
|
|
172
|
+
scope: 'regional',
|
|
173
|
+
region:
|
|
174
|
+
'Gemeinden mit überwiegend katholischer Bevölkerung, wie das Bayerische Landesamt für Statistik sie nach der letzten Volkszählung feststellt',
|
|
175
|
+
legalBasis: 'Art. 1 Abs. 1 Nr. 2, Abs. 3 FTG (Bayern)',
|
|
176
|
+
},
|
|
177
|
+
] satisfies IHolidayRule[]);
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// This entry point stands alone: it imports nothing from the rest of the package, has no
|
|
2
|
+
// dependencies and needs no storage or network.
|
|
3
|
+
export * from './interfaces.js';
|
|
4
|
+
export {
|
|
5
|
+
HolidayCalendarError,
|
|
6
|
+
type THolidayCalendarErrorCode,
|
|
7
|
+
} from './classes.holidaycalendarerror.js';
|
|
8
|
+
export { germanStates, holidayCalendarFirstYear, holidayLawsReadOn } from './data.holidays.js';
|
|
9
|
+
export {
|
|
10
|
+
easterSunday,
|
|
11
|
+
findGermanState,
|
|
12
|
+
getPublicHolidays,
|
|
13
|
+
getPublicHolidaysOn,
|
|
14
|
+
isPublicHoliday,
|
|
15
|
+
isWorkingDay,
|
|
16
|
+
nextWorkingDay,
|
|
17
|
+
workingDayOnOrAfter,
|
|
18
|
+
} from './calendar.js';
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/** A German federal state, as its ISO 3166-2 subdivision code. */
|
|
2
|
+
export type TGermanStateCode =
|
|
3
|
+
| 'DE-BW'
|
|
4
|
+
| 'DE-BY'
|
|
5
|
+
| 'DE-BE'
|
|
6
|
+
| 'DE-BB'
|
|
7
|
+
| 'DE-HB'
|
|
8
|
+
| 'DE-HH'
|
|
9
|
+
| 'DE-HE'
|
|
10
|
+
| 'DE-MV'
|
|
11
|
+
| 'DE-NI'
|
|
12
|
+
| 'DE-NW'
|
|
13
|
+
| 'DE-RP'
|
|
14
|
+
| 'DE-SL'
|
|
15
|
+
| 'DE-SN'
|
|
16
|
+
| 'DE-ST'
|
|
17
|
+
| 'DE-SH'
|
|
18
|
+
| 'DE-TH';
|
|
19
|
+
|
|
20
|
+
/** A federal state with its name. */
|
|
21
|
+
export interface IGermanState {
|
|
22
|
+
code: TGermanStateCode;
|
|
23
|
+
/** the state's name, for example `Bayern` */
|
|
24
|
+
name: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** A plain calendar date, `YYYY-MM-DD`. */
|
|
28
|
+
export type TIsoDate = string;
|
|
29
|
+
|
|
30
|
+
/** The statutory public holidays the calendar knows. */
|
|
31
|
+
export type TPublicHolidayId =
|
|
32
|
+
| 'neujahr'
|
|
33
|
+
| 'heilige-drei-koenige'
|
|
34
|
+
| 'frauentag'
|
|
35
|
+
| 'karfreitag'
|
|
36
|
+
| 'ostersonntag'
|
|
37
|
+
| 'ostermontag'
|
|
38
|
+
| 'tag-der-arbeit'
|
|
39
|
+
| 'tag-der-befreiung'
|
|
40
|
+
| 'christi-himmelfahrt'
|
|
41
|
+
| 'pfingstsonntag'
|
|
42
|
+
| 'pfingstmontag'
|
|
43
|
+
| 'jahrestag-17-juni-1953'
|
|
44
|
+
| 'fronleichnam'
|
|
45
|
+
| 'augsburger-friedensfest'
|
|
46
|
+
| 'mariae-himmelfahrt'
|
|
47
|
+
| 'weltkindertag'
|
|
48
|
+
| 'tag-der-deutschen-einheit'
|
|
49
|
+
| 'reformationstag'
|
|
50
|
+
| 'allerheiligen'
|
|
51
|
+
| 'buss-und-bettag'
|
|
52
|
+
| 'erster-weihnachtstag'
|
|
53
|
+
| 'zweiter-weihnachtstag';
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Where a holiday applies within its state:
|
|
57
|
+
* - `state`: in the whole state;
|
|
58
|
+
* - `regional`: only in part of the state (some municipalities or parts of municipalities), which
|
|
59
|
+
* `region` describes. The calendar holds no list of those municipalities.
|
|
60
|
+
*/
|
|
61
|
+
export type TPublicHolidayScope = 'state' | 'regional';
|
|
62
|
+
|
|
63
|
+
/** One statutory public holiday on one date in one state. */
|
|
64
|
+
export interface IPublicHoliday {
|
|
65
|
+
date: TIsoDate;
|
|
66
|
+
id: TPublicHolidayId;
|
|
67
|
+
/** the German name, for example `Christi Himmelfahrt` */
|
|
68
|
+
name: string;
|
|
69
|
+
state: TGermanStateCode;
|
|
70
|
+
scope: TPublicHolidayScope;
|
|
71
|
+
/** for a regional holiday, the part of the state where it applies */
|
|
72
|
+
region?: string;
|
|
73
|
+
/** the provision that makes the day a statutory holiday, for example `§ 2 Abs. 1 NFeiertagsG` */
|
|
74
|
+
legalBasis: string;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* How a question about working days treats regional holidays, which apply only in part of a state:
|
|
79
|
+
* - `count`: as holidays, as if the place in question lies in that part. A period then never ends
|
|
80
|
+
* on a day that is a holiday at the place, at the price of ending one day later elsewhere;
|
|
81
|
+
* - `ignore`: as working days, when the place is known to lie outside that part.
|
|
82
|
+
*/
|
|
83
|
+
export type TRegionalHolidayPolicy = 'count' | 'ignore';
|
|
84
|
+
|
|
85
|
+
export interface IWorkingDayOptions {
|
|
86
|
+
regionalHolidays: TRegionalHolidayPolicy;
|
|
87
|
+
}
|