@fin.cx/opendata 3.6.0 → 4.1.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 +23 -20
- package/dist_ts/00_commitinfo_data.js +3 -3
- package/dist_ts/handelsregister/classes.handelsregisterclient.d.ts +64 -0
- package/dist_ts/handelsregister/classes.handelsregisterclient.js +397 -0
- package/dist_ts/handelsregister/classes.handelsregistererror.d.ts +56 -0
- package/dist_ts/handelsregister/classes.handelsregistererror.js +37 -0
- package/dist_ts/handelsregister/classes.portalsession.d.ts +46 -0
- package/dist_ts/handelsregister/classes.portalsession.js +160 -0
- package/dist_ts/handelsregister/classes.slidingwindowgate.d.ts +32 -0
- package/dist_ts/handelsregister/classes.slidingwindowgate.js +55 -0
- package/dist_ts/handelsregister/constants.d.ts +13 -0
- package/dist_ts/handelsregister/constants.js +19 -0
- package/dist_ts/handelsregister/data.codelists.d.ts +29 -0
- package/dist_ts/handelsregister/data.codelists.js +616 -0
- package/dist_ts/handelsregister/forms.d.ts +27 -0
- package/dist_ts/handelsregister/forms.js +77 -0
- package/dist_ts/handelsregister/html.d.ts +26 -0
- package/dist_ts/handelsregister/html.js +82 -0
- package/dist_ts/handelsregister/index.d.ts +9 -0
- package/dist_ts/handelsregister/index.js +10 -0
- package/dist_ts/handelsregister/interfaces.d.ts +240 -0
- package/dist_ts/handelsregister/interfaces.js +2 -0
- package/dist_ts/handelsregister/parse.searchresults.d.ts +35 -0
- package/dist_ts/handelsregister/parse.searchresults.js +175 -0
- package/dist_ts/handelsregister/parse.structuredcontent.d.ts +6 -0
- package/dist_ts/handelsregister/parse.structuredcontent.js +306 -0
- package/dist_ts/handelsregister/plugins.d.ts +4 -0
- package/dist_ts/handelsregister/plugins.js +8 -0
- package/dist_ts/handelsregister/references.d.ts +38 -0
- package/dist_ts/handelsregister/references.js +146 -0
- 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/dist_ts/index.d.ts +0 -2
- package/dist_ts/index.js +1 -3
- package/dist_ts/plugins.d.ts +2 -20
- package/dist_ts/plugins.js +3 -22
- package/package.json +40 -37
- package/readme.md +250 -119
- package/ts/00_commitinfo_data.ts +2 -2
- package/ts/handelsregister/classes.handelsregisterclient.ts +474 -0
- package/ts/handelsregister/classes.handelsregistererror.ts +82 -0
- package/ts/handelsregister/classes.portalsession.ts +191 -0
- package/ts/handelsregister/classes.slidingwindowgate.ts +74 -0
- package/ts/handelsregister/constants.ts +30 -0
- package/ts/handelsregister/data.codelists.ts +640 -0
- package/ts/handelsregister/forms.ts +99 -0
- package/ts/handelsregister/html.ts +113 -0
- package/ts/handelsregister/index.ts +27 -0
- package/ts/handelsregister/interfaces.ts +272 -0
- package/ts/handelsregister/parse.searchresults.ts +241 -0
- package/ts/handelsregister/parse.structuredcontent.ts +338 -0
- package/ts/handelsregister/plugins.ts +15 -0
- package/ts/handelsregister/references.ts +189 -0
- 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
- package/ts/index.ts +0 -2
- package/ts/plugins.ts +0 -39
- package/dist_ts/classes.businessrecord.d.ts +0 -38
- package/dist_ts/classes.businessrecord.js +0 -90
- package/dist_ts/classes.handelsregister.d.ts +0 -134
- package/dist_ts/classes.handelsregister.js +0 -307
- package/dist_ts/classes.jsonldata.d.ts +0 -43
- package/dist_ts/classes.jsonldata.js +0 -68
- package/dist_ts/classes.main.opendata.d.ts +0 -25
- package/dist_ts/classes.main.opendata.js +0 -91
- package/dist_ts/laws/classes.lawrecord.d.ts +0 -24
- package/dist_ts/laws/classes.lawrecord.js +0 -181
- package/dist_ts/laws/classes.lawservice.d.ts +0 -52
- package/dist_ts/laws/classes.lawservice.js +0 -740
- package/dist_ts/laws/index.d.ts +0 -3
- package/dist_ts/laws/index.js +0 -4
- package/dist_ts/laws/interfaces.law.d.ts +0 -35
- package/dist_ts/laws/interfaces.law.js +0 -2
- package/readme.hints.md +0 -89
- package/ts/classes.businessrecord.ts +0 -67
- package/ts/classes.handelsregister.ts +0 -385
- package/ts/classes.jsonldata.ts +0 -114
- package/ts/classes.main.opendata.ts +0 -120
- package/ts/laws/classes.lawrecord.ts +0 -81
- package/ts/laws/classes.lawservice.ts +0 -1010
- package/ts/laws/index.ts +0 -3
- package/ts/laws/interfaces.law.ts +0 -48
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { registerTypes } from './constants.js';
|
|
2
|
+
import { registerCourts } from './data.codelists.js';
|
|
3
|
+
import type { IRegisterCourt, IRegisterReference, TRegisterType } from './interfaces.js';
|
|
4
|
+
|
|
5
|
+
/** a register number split into its digits and its location suffix */
|
|
6
|
+
export interface IRegisterNumberParts {
|
|
7
|
+
digits: string;
|
|
8
|
+
/** upper case, for example `HB` */
|
|
9
|
+
suffix?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** a register reference read from text; the court is absent when the text names none */
|
|
13
|
+
export interface IParsedRegisterReferenceText {
|
|
14
|
+
court?: IRegisterCourt;
|
|
15
|
+
/** the court as the text names it, when it names one */
|
|
16
|
+
courtText?: string;
|
|
17
|
+
type: TRegisterType;
|
|
18
|
+
/** digits and location suffix, for example `35230 HB` */
|
|
19
|
+
number: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const collapse = (textArg: string): string => textArg.replace(/\s+/g, ' ').trim();
|
|
23
|
+
|
|
24
|
+
/** German letters as a text without them spells them: `Muenchen` is `München` */
|
|
25
|
+
const spelledOut: Readonly<Record<string, string>> = { 'ä': 'ae', 'ö': 'oe', 'ü': 'ue', 'ß': 'ss' };
|
|
26
|
+
|
|
27
|
+
/** words that name a court or a register rather than its place */
|
|
28
|
+
const courtWords: ReadonlySet<string> = new Set([
|
|
29
|
+
'amtsgericht',
|
|
30
|
+
'amtsgerichts',
|
|
31
|
+
'ag',
|
|
32
|
+
'registergericht',
|
|
33
|
+
'registergerichts',
|
|
34
|
+
'handelsregister',
|
|
35
|
+
'genossenschaftsregister',
|
|
36
|
+
'partnerschaftsregister',
|
|
37
|
+
'vereinsregister',
|
|
38
|
+
'gesellschaftsregister',
|
|
39
|
+
'abteilung',
|
|
40
|
+
'beim',
|
|
41
|
+
'bei',
|
|
42
|
+
'des',
|
|
43
|
+
]);
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* The place a printed court name names, as courts are compared: lower case, German letters spelled
|
|
47
|
+
* out, punctuation as spaces, and without the words that name a court or a register or the
|
|
48
|
+
* division of the commercial register. `Handelsregister B des Amtsgerichts Frankfurt a. M.` is
|
|
49
|
+
* `frankfurt a m`.
|
|
50
|
+
*/
|
|
51
|
+
const placeKey = (textArg: string): string => {
|
|
52
|
+
const words = textArg
|
|
53
|
+
.normalize('NFC')
|
|
54
|
+
.toLocaleLowerCase('de-DE')
|
|
55
|
+
.replace(/[äöüß]/g, (letterArg) => spelledOut[letterArg] ?? letterArg)
|
|
56
|
+
.split(/[^\p{L}\p{N}]+/u)
|
|
57
|
+
.filter((wordArg) => wordArg !== '');
|
|
58
|
+
return words
|
|
59
|
+
.filter((wordArg, indexArg) => {
|
|
60
|
+
const previous = words[indexArg - 1];
|
|
61
|
+
const division = (previous === 'handelsregister' || previous === 'abteilung') && (wordArg === 'a' || wordArg === 'b');
|
|
62
|
+
return !courtWords.has(wordArg) && !division;
|
|
63
|
+
})
|
|
64
|
+
.join(' ');
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Other names under which the register courts are printed, for courts whose name in the official
|
|
69
|
+
* list carries a qualifier that prints leave out, abbreviate or spell out. Each name here is a name
|
|
70
|
+
* of that court alone. `Frankfurt` without `am Main` or `(Oder)` is a name of neither of the two.
|
|
71
|
+
*/
|
|
72
|
+
const printedCourtNames: Readonly<Record<string, readonly string[]>> = {
|
|
73
|
+
'Bad Homburg v.d.H.': ['Bad Homburg', 'Bad Homburg vor der Höhe', 'Bad Homburg v. d. Höhe'],
|
|
74
|
+
'Berlin (Charlottenburg)': ['Berlin', 'Charlottenburg', 'Charlottenburg (Berlin)'],
|
|
75
|
+
'Frankfurt am Main': ['Frankfurt a. M.', 'Frankfurt a. Main', 'Frankfurt/M.', 'Frankfurt/Main', 'Frankfurt (Main)'],
|
|
76
|
+
'Frankfurt (Oder)': ['Frankfurt/O.', 'Frankfurt a. d. O.', 'Frankfurt an der Oder', 'Frankfurt a. d. Oder'],
|
|
77
|
+
'Freiburg': ['Freiburg im Breisgau', 'Freiburg i. Br.', 'Freiburg/Breisgau', 'Freiburg (Breisgau)'],
|
|
78
|
+
'Kempten (Allgäu)': ['Kempten'],
|
|
79
|
+
'Königstein': ['Königstein im Taunus', 'Königstein i. Ts.'],
|
|
80
|
+
'Landau': ['Landau in der Pfalz', 'Landau i. d. Pfalz', 'Landau/Pfalz'],
|
|
81
|
+
'Limburg': ['Limburg an der Lahn', 'Limburg a. d. Lahn'],
|
|
82
|
+
'Ludwigshafen a.Rhein (Ludwigshafen)': ['Ludwigshafen', 'Ludwigshafen am Rhein'],
|
|
83
|
+
'Offenbach am Main': ['Offenbach', 'Offenbach a. M.'],
|
|
84
|
+
'Oldenburg (Oldenburg)': ['Oldenburg', 'Oldenburg (Oldb.)', 'Oldenburg i. O.'],
|
|
85
|
+
'St. Ingbert (St Ingbert)': ['St. Ingbert', 'Sankt Ingbert'],
|
|
86
|
+
'St. Wendel (St Wendel)': ['St. Wendel', 'Sankt Wendel'],
|
|
87
|
+
'Weiden i. d. OPf.': ['Weiden', 'Weiden in der Oberpfalz'],
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
/** every place key of a list of courts with the courts it names */
|
|
91
|
+
const courtsByPlaceKey = (courtsArg: readonly IRegisterCourt[]): ReadonlyMap<string, readonly IRegisterCourt[]> => {
|
|
92
|
+
const byKey = new Map<string, IRegisterCourt[]>();
|
|
93
|
+
for (const court of courtsArg) {
|
|
94
|
+
const keys = new Set([court.name, ...(printedCourtNames[court.name] ?? [])].map(placeKey));
|
|
95
|
+
for (const key of keys) {
|
|
96
|
+
if (key !== '') {
|
|
97
|
+
byKey.set(key, [...(byKey.get(key) ?? []), court]);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return byKey;
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
let officialCourtKeys: ReadonlyMap<string, readonly IRegisterCourt[]> | undefined;
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Finds a register court by its code or by its name as the portal or a business prints it: `H1101`,
|
|
108
|
+
* `Bremen`, `AG Bremen`, `Amtsgericht Bremen – Registergericht`, `Registergericht Bremen`,
|
|
109
|
+
* `Handelsregister B des Amtsgerichts Bremen`, `Amtsgericht Frankfurt a. M.`, `Frankfurt (Oder)`,
|
|
110
|
+
* `Kempten (Allgäu)` or `Kempten`, `Freiburg i. Br.`, `AG Berlin` or `Charlottenburg`. Umlauts may
|
|
111
|
+
* be spelled out (`Muenchen`). Returns undefined unless exactly one court fits: a place the list
|
|
112
|
+
* does not know, and `Frankfurt` alone, name no court.
|
|
113
|
+
*/
|
|
114
|
+
export const findRegisterCourt = (
|
|
115
|
+
codeOrNameArg: string,
|
|
116
|
+
courtsArg: readonly IRegisterCourt[] = registerCourts
|
|
117
|
+
): IRegisterCourt | undefined => {
|
|
118
|
+
if (typeof codeOrNameArg !== 'string') {
|
|
119
|
+
return undefined;
|
|
120
|
+
}
|
|
121
|
+
const text = collapse(codeOrNameArg);
|
|
122
|
+
if (!text) {
|
|
123
|
+
return undefined;
|
|
124
|
+
}
|
|
125
|
+
const byCode = courtsArg.find((courtArg) => courtArg.code.toUpperCase() === text.toUpperCase());
|
|
126
|
+
if (byCode) {
|
|
127
|
+
return byCode;
|
|
128
|
+
}
|
|
129
|
+
const keys =
|
|
130
|
+
courtsArg === registerCourts
|
|
131
|
+
? (officialCourtKeys ??= courtsByPlaceKey(registerCourts))
|
|
132
|
+
: courtsByPlaceKey(courtsArg);
|
|
133
|
+
const courts = keys.get(placeKey(text)) ?? [];
|
|
134
|
+
return courts.length === 1 ? courts[0] : undefined;
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
/** returns the canonical register type for `hrb`, `HRB`, `gnr` …, or undefined */
|
|
138
|
+
export const toRegisterType = (textArg: string): TRegisterType | undefined =>
|
|
139
|
+
registerTypes.find((typeArg) => typeArg.toLowerCase() === collapse(textArg).toLowerCase());
|
|
140
|
+
|
|
141
|
+
/** splits `35230 HB`, `35230HB` or `35230` into digits and location suffix */
|
|
142
|
+
export const parseRegisterNumber = (textArg: string): IRegisterNumberParts | undefined => {
|
|
143
|
+
const match = /^(\d{1,9})\s*([A-Za-zÄÖÜäöü]{1,5})?$/.exec(collapse(textArg));
|
|
144
|
+
if (!match) {
|
|
145
|
+
return undefined;
|
|
146
|
+
}
|
|
147
|
+
return match[2] ? { digits: match[1], suffix: match[2].toLocaleUpperCase('de-DE') } : { digits: match[1] };
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
export const formatRegisterNumber = (partsArg: IRegisterNumberParts): string =>
|
|
151
|
+
partsArg.suffix ? `${partsArg.digits} ${partsArg.suffix}` : partsArg.digits;
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Reads a register reference as businesses print it: `Amtsgericht Bremen HRB 35230 HB`,
|
|
155
|
+
* `AG Bremen, HRB 35230`, `HRB 35230 HB`. Returns undefined for text it cannot read. A court name
|
|
156
|
+
* that fits no register court leaves `court` absent and keeps the text in `courtText`.
|
|
157
|
+
*/
|
|
158
|
+
export const parseRegisterReferenceText = (
|
|
159
|
+
textArg: string,
|
|
160
|
+
courtsArg: readonly IRegisterCourt[] = registerCourts
|
|
161
|
+
): IParsedRegisterReferenceText | undefined => {
|
|
162
|
+
const text = collapse(textArg);
|
|
163
|
+
const match =
|
|
164
|
+
/^(.*?)[\s,;:]*\b(HRA|HRB|GnR|PR|VR|GsR)\b\.?\s*(?:Nr\.?\s*)?(\d{1,9}(?:\s*[A-Za-zÄÖÜäöü]{1,5})?)(?=\s|$)(?:\s+früher\s+Amtsgericht\s+.+)?$/i.exec(
|
|
165
|
+
text
|
|
166
|
+
);
|
|
167
|
+
if (!match) {
|
|
168
|
+
return undefined;
|
|
169
|
+
}
|
|
170
|
+
const type = toRegisterType(match[2]);
|
|
171
|
+
const number = parseRegisterNumber(match[3]);
|
|
172
|
+
if (!type || !number) {
|
|
173
|
+
return undefined;
|
|
174
|
+
}
|
|
175
|
+
const courtText = collapse(match[1].replace(/[,;:]+$/, ''));
|
|
176
|
+
const result: IParsedRegisterReferenceText = { type, number: formatRegisterNumber(number) };
|
|
177
|
+
if (courtText) {
|
|
178
|
+
result.courtText = courtText;
|
|
179
|
+
const court = findRegisterCourt(courtText, courtsArg);
|
|
180
|
+
if (court) {
|
|
181
|
+
result.court = court;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return result;
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
/** `Amtsgericht Bremen HRB 35230 HB` */
|
|
188
|
+
export const formatRegisterReference = (referenceArg: IRegisterReference): string =>
|
|
189
|
+
`Amtsgericht ${referenceArg.court.name} ${referenceArg.type} ${referenceArg.number}`;
|
|
@@ -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';
|