@fin.cx/opendata 4.0.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.
@@ -0,0 +1,124 @@
1
+ /** the first year the calendar answers for */
2
+ export const holidayCalendarFirstYear = 2018;
3
+ /** the day the holiday laws were read; later changes need a new release */
4
+ export const holidayLawsReadOn = '2026-09-24';
5
+ export const germanStates = Object.freeze([
6
+ { code: 'DE-BW', name: 'Baden-Württemberg' },
7
+ { code: 'DE-BY', name: 'Bayern' },
8
+ { code: 'DE-BE', name: 'Berlin' },
9
+ { code: 'DE-BB', name: 'Brandenburg' },
10
+ { code: 'DE-HB', name: 'Bremen' },
11
+ { code: 'DE-HH', name: 'Hamburg' },
12
+ { code: 'DE-HE', name: 'Hessen' },
13
+ { code: 'DE-MV', name: 'Mecklenburg-Vorpommern' },
14
+ { code: 'DE-NI', name: 'Niedersachsen' },
15
+ { code: 'DE-NW', name: 'Nordrhein-Westfalen' },
16
+ { code: 'DE-RP', name: 'Rheinland-Pfalz' },
17
+ { code: 'DE-SL', name: 'Saarland' },
18
+ { code: 'DE-SN', name: 'Sachsen' },
19
+ { code: 'DE-ST', name: 'Sachsen-Anhalt' },
20
+ { code: 'DE-SH', name: 'Schleswig-Holstein' },
21
+ { code: 'DE-TH', name: 'Thüringen' },
22
+ ]);
23
+ /** the provision of each state's holiday law that lists its statutory holidays */
24
+ const holidayListOf = {
25
+ 'DE-BW': '§ 1 FTG (Baden-Württemberg)',
26
+ 'DE-BY': 'Art. 1 Abs. 1 Nr. 1 FTG (Bayern)',
27
+ 'DE-BE': '§ 1 Abs. 1 Gesetz über die Sonn- und Feiertage (Berlin)',
28
+ 'DE-BB': '§ 2 Abs. 1 FTG (Brandenburg)',
29
+ 'DE-HB': '§ 2 Abs. 1 Gesetz über die Sonn-, Gedenk- und Feiertage (Bremen)',
30
+ 'DE-HH': '§ 1 Feiertagsgesetz (Hamburg)',
31
+ 'DE-HE': '§ 1 Abs. 1 HFeiertagsG',
32
+ 'DE-MV': '§ 2 Abs. 1 FTG M-V',
33
+ 'DE-NI': '§ 2 Abs. 1 NFeiertagsG',
34
+ 'DE-NW': '§ 2 Abs. 1 Feiertagsgesetz NW',
35
+ 'DE-RP': '§ 2 Abs. 1 LFtG',
36
+ 'DE-SL': '§ 2 Abs. 1 SFG',
37
+ 'DE-SN': '§ 1 Abs. 1 SächsSFG',
38
+ 'DE-ST': '§ 2 FeiertG LSA',
39
+ 'DE-SH': '§ 2 Abs. 1 SFTG',
40
+ 'DE-TH': '§ 2 Abs. 1 ThürFGtG',
41
+ };
42
+ const allStates = germanStates.map((stateArg) => stateArg.code);
43
+ const fixed = (month, day) => ({ kind: 'fixed', month, day });
44
+ const easter = (offset) => ({ kind: 'easter', offset });
45
+ /** a holiday in the whole of each listed state, by that state's holiday list */
46
+ const statewide = (id, name, date, states, extra = {}) => states.map((state) => ({ id, name, date, state, scope: 'state', legalBasis: holidayListOf[state], ...extra }));
47
+ export const holidayRules = Object.freeze([
48
+ // in every state
49
+ ...statewide('neujahr', 'Neujahr', fixed(1, 1), allStates),
50
+ ...statewide('karfreitag', 'Karfreitag', easter(-2), allStates),
51
+ ...statewide('ostermontag', 'Ostermontag', easter(1), allStates),
52
+ ...statewide('tag-der-arbeit', 'Tag der Arbeit', fixed(5, 1), allStates),
53
+ ...statewide('christi-himmelfahrt', 'Christi Himmelfahrt', easter(39), allStates),
54
+ ...statewide('pfingstmontag', 'Pfingstmontag', easter(50), allStates),
55
+ ...allStates.map((state) => ({
56
+ id: 'tag-der-deutschen-einheit',
57
+ name: 'Tag der Deutschen Einheit',
58
+ date: fixed(10, 3),
59
+ state,
60
+ scope: 'state',
61
+ legalBasis: 'Art. 2 Abs. 2 Einigungsvertrag',
62
+ })),
63
+ ...statewide('erster-weihnachtstag', '1. Weihnachtstag', fixed(12, 25), allStates),
64
+ ...statewide('zweiter-weihnachtstag', '2. Weihnachtstag', fixed(12, 26), allStates),
65
+ // in some states
66
+ ...statewide('heilige-drei-koenige', 'Heilige Drei Könige', fixed(1, 6), ['DE-BW', 'DE-BY', 'DE-ST']),
67
+ ...statewide('frauentag', 'Frauentag', fixed(3, 8), ['DE-BE'], { fromYear: 2019 }),
68
+ ...statewide('frauentag', 'Frauentag', fixed(3, 8), ['DE-MV'], { fromYear: 2023 }),
69
+ ...statewide('ostersonntag', 'Ostersonntag', easter(0), ['DE-BB']),
70
+ ...statewide('pfingstsonntag', 'Pfingstsonntag', easter(49), ['DE-BB']),
71
+ ...statewide('fronleichnam', 'Fronleichnam', easter(60), ['DE-BW', 'DE-BY', 'DE-HE', 'DE-NW', 'DE-RP', 'DE-SL']),
72
+ ...statewide('mariae-himmelfahrt', 'Mariä Himmelfahrt', fixed(8, 15), ['DE-SL']),
73
+ ...statewide('weltkindertag', 'Weltkindertag', fixed(9, 20), ['DE-TH'], { fromYear: 2019 }),
74
+ ...statewide('reformationstag', 'Reformationstag', fixed(10, 31), ['DE-BB', 'DE-MV', 'DE-SN', 'DE-ST', 'DE-TH']),
75
+ ...statewide('reformationstag', 'Reformationstag', fixed(10, 31), ['DE-HB', 'DE-HH', 'DE-NI', 'DE-SH'], {
76
+ fromYear: 2018,
77
+ }),
78
+ ...statewide('allerheiligen', 'Allerheiligen', fixed(11, 1), ['DE-BW', 'DE-BY', 'DE-NW', 'DE-RP', 'DE-SL']),
79
+ ...statewide('buss-und-bettag', 'Buß- und Bettag', { kind: 'buss-und-bettag' }, ['DE-SN']),
80
+ // one-off holidays
81
+ ...statewide('tag-der-befreiung', 'Tag der Befreiung', fixed(5, 8), ['DE-BE'], { onlyYear: 2020 }),
82
+ ...statewide('tag-der-befreiung', 'Tag der Befreiung', fixed(5, 8), ['DE-BE'], { onlyYear: 2025 }),
83
+ ...statewide('jahrestag-17-juni-1953', '75. Jahrestag des Aufstandes vom 17. Juni 1953', fixed(6, 17), ['DE-BE'], {
84
+ onlyYear: 2028,
85
+ }),
86
+ // in part of a state
87
+ {
88
+ id: 'fronleichnam',
89
+ name: 'Fronleichnam',
90
+ date: easter(60),
91
+ state: 'DE-SN',
92
+ scope: 'regional',
93
+ region: 'die in der Anlage der FronleichnamsVO genannten Gemeinden der früheren Landkreise Bautzen, Hoyerswerda und Kamenz, nach einer Neugliederung nur der jeweilige Gemeindeteil',
94
+ legalBasis: '§ 1 Abs. 1 SächsSFG i. V. m. § 1 FronleichnamsVO',
95
+ },
96
+ {
97
+ id: 'fronleichnam',
98
+ name: 'Fronleichnam',
99
+ date: easter(60),
100
+ state: 'DE-TH',
101
+ scope: 'regional',
102
+ region: 'die Teile Thüringens, in denen Fronleichnam 1994 gesetzlicher Feiertag war',
103
+ legalBasis: '§ 2 Abs. 2 i. V. m. § 10 Abs. 1 ThürFGtG',
104
+ },
105
+ {
106
+ id: 'augsburger-friedensfest',
107
+ name: 'Augsburger Friedensfest',
108
+ date: fixed(8, 8),
109
+ state: 'DE-BY',
110
+ scope: 'regional',
111
+ region: 'Stadt Augsburg',
112
+ legalBasis: 'Art. 1 Abs. 2 FTG (Bayern)',
113
+ },
114
+ {
115
+ id: 'mariae-himmelfahrt',
116
+ name: 'Mariä Himmelfahrt',
117
+ date: fixed(8, 15),
118
+ state: 'DE-BY',
119
+ scope: 'regional',
120
+ region: 'Gemeinden mit überwiegend katholischer Bevölkerung, wie das Bayerische Landesamt für Statistik sie nach der letzten Volkszählung feststellt',
121
+ legalBasis: 'Art. 1 Abs. 1 Nr. 2, Abs. 3 FTG (Bayern)',
122
+ },
123
+ ]);
124
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZGF0YS5ob2xpZGF5cy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3RzL2hvbGlkYXlzL2RhdGEuaG9saWRheXMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBYUEsOENBQThDO0FBQzlDLE1BQU0sQ0FBQyxNQUFNLHdCQUF3QixHQUFHLElBQUksQ0FBQztBQUU3QywyRUFBMkU7QUFDM0UsTUFBTSxDQUFDLE1BQU0saUJBQWlCLEdBQUcsWUFBWSxDQUFDO0FBRTlDLE1BQU0sQ0FBQyxNQUFNLFlBQVksR0FBNEIsTUFBTSxDQUFDLE1BQU0sQ0FBQztJQUNqRSxFQUFFLElBQUksRUFBRSxPQUFPLEVBQUUsSUFBSSxFQUFFLG1CQUFtQixFQUFFO0lBQzVDLEVBQUUsSUFBSSxFQUFFLE9BQU8sRUFBRSxJQUFJLEVBQUUsUUFBUSxFQUFFO0lBQ2pDLEVBQUUsSUFBSSxFQUFFLE9BQU8sRUFBRSxJQUFJLEVBQUUsUUFBUSxFQUFFO0lBQ2pDLEVBQUUsSUFBSSxFQUFFLE9BQU8sRUFBRSxJQUFJLEVBQUUsYUFBYSxFQUFFO0lBQ3RDLEVBQUUsSUFBSSxFQUFFLE9BQU8sRUFBRSxJQUFJLEVBQUUsUUFBUSxFQUFFO0lBQ2pDLEVBQUUsSUFBSSxFQUFFLE9BQU8sRUFBRSxJQUFJLEVBQUUsU0FBUyxFQUFFO0lBQ2xDLEVBQUUsSUFBSSxFQUFFLE9BQU8sRUFBRSxJQUFJLEVBQUUsUUFBUSxFQUFFO0lBQ2pDLEVBQUUsSUFBSSxFQUFFLE9BQU8sRUFBRSxJQUFJLEVBQUUsd0JBQXdCLEVBQUU7SUFDakQsRUFBRSxJQUFJLEVBQUUsT0FBTyxFQUFFLElBQUksRUFBRSxlQUFlLEVBQUU7SUFDeEMsRUFBRSxJQUFJLEVBQUUsT0FBTyxFQUFFLElBQUksRUFBRSxxQkFBcUIsRUFBRTtJQUM5QyxFQUFFLElBQUksRUFBRSxPQUFPLEVBQUUsSUFBSSxFQUFFLGlCQUFpQixFQUFFO0lBQzFDLEVBQUUsSUFBSSxFQUFFLE9BQU8sRUFBRSxJQUFJLEVBQUUs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
@@ -0,0 +1,4 @@
1
+ export * from './interfaces.js';
2
+ export { HolidayCalendarError, type THolidayCalendarErrorCode, } from './classes.holidaycalendarerror.js';
3
+ export { germanStates, holidayCalendarFirstYear, holidayLawsReadOn } from './data.holidays.js';
4
+ export { easterSunday, findGermanState, getPublicHolidays, getPublicHolidaysOn, isPublicHoliday, isWorkingDay, nextWorkingDay, workingDayOnOrAfter, } from './calendar.js';
@@ -0,0 +1,7 @@
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 { HolidayCalendarError, } from './classes.holidaycalendarerror.js';
5
+ export { germanStates, holidayCalendarFirstYear, holidayLawsReadOn } from './data.holidays.js';
6
+ export { easterSunday, findGermanState, getPublicHolidays, getPublicHolidaysOn, isPublicHoliday, isWorkingDay, nextWorkingDay, workingDayOnOrAfter, } from './calendar.js';
7
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90cy9ob2xpZGF5cy9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSx5RkFBeUY7QUFDekYsZ0RBQWdEO0FBQ2hELGNBQWMsaUJBQWlCLENBQUM7QUFDaEMsT0FBTyxFQUNMLG9CQUFvQixHQUVyQixNQUFNLG1DQUFtQyxDQUFDO0FBQzNDLE9BQU8sRUFBRSxZQUFZLEVBQUUsd0JBQXdCLEVBQUUsaUJBQWlCLEVBQUUsTUFBTSxvQkFBb0IsQ0FBQztBQUMvRixPQUFPLEVBQ0wsWUFBWSxFQUNaLGVBQWUsRUFDZixpQkFBaUIsRUFDakIsbUJBQW1CLEVBQ25CLGVBQWUsRUFDZixZQUFZLEVBQ1osY0FBYyxFQUNkLG1CQUFtQixHQUNwQixNQUFNLGVBQWUsQ0FBQyJ9
@@ -0,0 +1,42 @@
1
+ /** A German federal state, as its ISO 3166-2 subdivision code. */
2
+ export type TGermanStateCode = 'DE-BW' | 'DE-BY' | 'DE-BE' | 'DE-BB' | 'DE-HB' | 'DE-HH' | 'DE-HE' | 'DE-MV' | 'DE-NI' | 'DE-NW' | 'DE-RP' | 'DE-SL' | 'DE-SN' | 'DE-ST' | 'DE-SH' | 'DE-TH';
3
+ /** A federal state with its name. */
4
+ export interface IGermanState {
5
+ code: TGermanStateCode;
6
+ /** the state's name, for example `Bayern` */
7
+ name: string;
8
+ }
9
+ /** A plain calendar date, `YYYY-MM-DD`. */
10
+ export type TIsoDate = string;
11
+ /** The statutory public holidays the calendar knows. */
12
+ export type TPublicHolidayId = 'neujahr' | 'heilige-drei-koenige' | 'frauentag' | 'karfreitag' | 'ostersonntag' | 'ostermontag' | 'tag-der-arbeit' | 'tag-der-befreiung' | 'christi-himmelfahrt' | 'pfingstsonntag' | 'pfingstmontag' | 'jahrestag-17-juni-1953' | 'fronleichnam' | 'augsburger-friedensfest' | 'mariae-himmelfahrt' | 'weltkindertag' | 'tag-der-deutschen-einheit' | 'reformationstag' | 'allerheiligen' | 'buss-und-bettag' | 'erster-weihnachtstag' | 'zweiter-weihnachtstag';
13
+ /**
14
+ * Where a holiday applies within its state:
15
+ * - `state`: in the whole state;
16
+ * - `regional`: only in part of the state (some municipalities or parts of municipalities), which
17
+ * `region` describes. The calendar holds no list of those municipalities.
18
+ */
19
+ export type TPublicHolidayScope = 'state' | 'regional';
20
+ /** One statutory public holiday on one date in one state. */
21
+ export interface IPublicHoliday {
22
+ date: TIsoDate;
23
+ id: TPublicHolidayId;
24
+ /** the German name, for example `Christi Himmelfahrt` */
25
+ name: string;
26
+ state: TGermanStateCode;
27
+ scope: TPublicHolidayScope;
28
+ /** for a regional holiday, the part of the state where it applies */
29
+ region?: string;
30
+ /** the provision that makes the day a statutory holiday, for example `§ 2 Abs. 1 NFeiertagsG` */
31
+ legalBasis: string;
32
+ }
33
+ /**
34
+ * How a question about working days treats regional holidays, which apply only in part of a state:
35
+ * - `count`: as holidays, as if the place in question lies in that part. A period then never ends
36
+ * on a day that is a holiday at the place, at the price of ending one day later elsewhere;
37
+ * - `ignore`: as working days, when the place is known to lie outside that part.
38
+ */
39
+ export type TRegionalHolidayPolicy = 'count' | 'ignore';
40
+ export interface IWorkingDayOptions {
41
+ regionalHolidays: TRegionalHolidayPolicy;
42
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW50ZXJmYWNlcy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3RzL2hvbGlkYXlzL2ludGVyZmFjZXMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IiJ9
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@fin.cx/opendata",
3
- "version": "4.0.0",
3
+ "version": "4.1.0",
4
4
  "private": false,
5
- "description": "Open business and financial data for TypeScript: register lookups on the German register portal (handelsregister.de) without storage, and stock and crypto prices and fundamentals from several providers.",
5
+ "description": "Open business and financial data for TypeScript: register lookups on the German register portal (handelsregister.de) without storage, the statutory public holidays of the German federal states, and stock and crypto prices and fundamentals from several providers.",
6
6
  "main": "dist_ts/index.js",
7
7
  "typings": "dist_ts/index.d.ts",
8
8
  "exports": {
@@ -14,6 +14,10 @@
14
14
  "types": "./dist_ts/handelsregister/index.d.ts",
15
15
  "import": "./dist_ts/handelsregister/index.js"
16
16
  },
17
+ "./holidays": {
18
+ "types": "./dist_ts/holidays/index.d.ts",
19
+ "import": "./dist_ts/holidays/index.js"
20
+ },
17
21
  "./package.json": "./package.json"
18
22
  },
19
23
  "type": "module",
@@ -70,7 +74,10 @@
70
74
  "data retrieval",
71
75
  "stock prices",
72
76
  "cryptocurrency",
73
- "fundamentals"
77
+ "fundamentals",
78
+ "public holidays",
79
+ "Feiertage",
80
+ "working days"
74
81
  ],
75
82
  "scripts": {
76
83
  "test": "(tstest test/ --verbose)",
package/readme.md CHANGED
@@ -1,12 +1,13 @@
1
1
  # @fin.cx/opendata
2
2
 
3
- `@fin.cx/opendata` is a TypeScript toolbox for open business and market data: stock and crypto prices, SEC fundamentals, and lookups in the German registers through the register portal of the federal states. It is built for programmers who want scriptable APIs and real workflows instead of a thin wrapper around one remote endpoint.
3
+ `@fin.cx/opendata` is a TypeScript toolbox for open business and market data: stock and crypto prices, SEC fundamentals, lookups in the German registers through the register portal of the federal states, and the statutory public holidays of the German federal states. It is built for programmers who want scriptable APIs and real workflows instead of a thin wrapper around one remote endpoint.
4
4
 
5
- It gives you three practical lanes in one package:
5
+ It gives you four practical lanes in one package:
6
6
 
7
7
  - `StockPriceService` for stock and crypto prices with caching, retries, failover, historical ranges, and intraday support
8
8
  - `FundamentalsService` and `StockDataService` for SEC fundamentals and combined price + fundamentals payloads
9
9
  - `HandelsregisterClient`, from `@fin.cx/opendata/handelsregister`, for single lookups in the commercial, cooperative, partnership, association and civil-law partnership registers over plain HTTP, without any storage
10
+ - the holiday calendar, from `@fin.cx/opendata/holidays`, for the statutory public holidays of each German federal state and the working days of § 193 BGB, computed per year without data files or requests
10
11
 
11
12
  ## Issue Reporting and Security
12
13
 
@@ -48,6 +49,16 @@ Everything here comes from `@fin.cx/opendata/handelsregister`. That entry point
48
49
  - `registerCourts`, `findRegisterCourt()`, `parseRegisterReferenceText()`, `formatRegisterReference()`
49
50
  The 150 register courts with their XJustiz codes, and helpers for register references as businesses print them (`Amtsgericht Bremen HRB 35230 HB`).
50
51
 
52
+ ### German Public Holidays
53
+
54
+ Everything here comes from `@fin.cx/opendata/holidays`. That entry point imports nothing else of the package, has no dependencies, and makes no request.
55
+
56
+ - `getPublicHolidays(year, state)`, `getPublicHolidaysOn(date, state)`
57
+ The statutory public holidays of a federal state in a year or on a date, each with its legal basis.
58
+ - `isPublicHoliday()`, `isWorkingDay()`, `nextWorkingDay()`, `workingDayOnOrAfter()`
59
+ Holidays and working days in the sense of § 193 BGB: neither a Saturday, nor a Sunday, nor a statutory public holiday.
60
+ - `easterSunday(year)`, `germanStates`, `findGermanState()`
61
+
51
62
  ## Before You Start
52
63
 
53
64
  - No stock or fundamentals providers are pre-registered. You must register them before calling the services.
@@ -312,6 +323,57 @@ import { parseStructuredContent } from '@fin.cx/opendata/handelsregister';
312
323
  const content = parseStructuredContent(xmlText);
313
324
  ```
314
325
 
326
+ #### Printed court names
327
+
328
+ `findRegisterCourt()` takes a court's code or its name as the portal or a business prints it: `AG Bremen`, `Amtsgericht Bremen – Registergericht`, `Registergericht Bremen`, `Handelsregister B des Amtsgerichts Bremen`, `Amtsgericht Frankfurt a. M.`, `Frankfurt/O.`, `Kempten` for `Kempten (Allgäu)`, `Freiburg i. Br.`, and `AG Berlin` or `Charlottenburg` for the register court of Berlin. Umlauts may be spelled out (`Muenchen`). It returns a court only when exactly one fits: `Frankfurt` alone could be either Frankfurt am Main or Frankfurt (Oder) and names no court.
329
+
330
+ ### Public Holidays
331
+
332
+ ```ts
333
+ import {
334
+ getPublicHolidays,
335
+ isWorkingDay,
336
+ nextWorkingDay,
337
+ workingDayOnOrAfter,
338
+ } from '@fin.cx/opendata/holidays';
339
+
340
+ getPublicHolidays(2026, 'DE-NI');
341
+ // [{ date: '2026-01-01', id: 'neujahr', name: 'Neujahr', state: 'DE-NI', scope: 'state',
342
+ // legalBasis: '§ 2 Abs. 1 NFeiertagsG' }, …, { date: '2026-10-31', id: 'reformationstag', … }, …]
343
+
344
+ isWorkingDay('2026-06-04', 'DE-HE', { regionalHolidays: 'ignore' }); // false: Fronleichnam
345
+ nextWorkingDay('2026-04-02', 'DE-BY', { regionalHolidays: 'ignore' }); // '2026-04-07'
346
+
347
+ // § 193 BGB: a deadline that ends on a Saturday, Sunday or holiday ends on the next working day
348
+ workingDayOnOrAfter('2026-05-14', 'DE-BY', { regionalHolidays: 'ignore' }); // '2026-05-15'
349
+ ```
350
+
351
+ States are ISO 3166-2 codes (`DE-BY`); `findGermanState('Bayern')` and `findGermanState('BY')` find one. Dates are plain calendar dates written `YYYY-MM-DD`, so no time zone can move them.
352
+
353
+ #### Regional holidays
354
+
355
+ Four holidays apply only in part of a state:
356
+
357
+ | Holiday | State | Where | Legal basis |
358
+ | --- | --- | --- | --- |
359
+ | Augsburger Friedensfest (8 August) | Bayern | Stadt Augsburg | Art. 1 Abs. 2 FTG |
360
+ | Mariä Himmelfahrt (15 August) | Bayern | municipalities with a predominantly Catholic population, as the Bayerisches Landesamt für Statistik determines them after the last census | Art. 1 Abs. 1 Nr. 2, Abs. 3 FTG |
361
+ | Fronleichnam | Sachsen | the municipalities listed in the annex of the FronleichnamsVO (former districts Bautzen, Hoyerswerda and Kamenz), after a merger only the respective part of the municipality | § 1 Abs. 1 SächsSFG, § 1 FronleichnamsVO |
362
+ | Fronleichnam | Thüringen | the parts of Thüringen where Fronleichnam was a statutory holiday in 1994 | § 2 Abs. 2, § 10 Abs. 1 ThürFGtG |
363
+
364
+ `getPublicHolidays()` lists them with `scope: 'regional'` and the `region` they apply to. The calendar holds no list of these municipalities: the Bavarian list follows the census and the Saxon and Thuringian ones name parts of municipalities, so a place cannot be matched to them reliably by a key. Every working-day question therefore says how to treat them:
365
+
366
+ - `{ regionalHolidays: 'count' }` treats them as holidays. A period then never ends on a day that is a holiday at the place in question, at the price of ending one day later where it is not. Use it when the place is unknown.
367
+ - `{ regionalHolidays: 'ignore' }` treats them as working days, when the place is known to lie outside that part.
368
+
369
+ #### What the calendar covers
370
+
371
+ - The statutory public holidays of the 16 federal states from 2018 on, and 3 October from Art. 2 Abs. 2 of the Unification Treaty. Every change since 2018 applies from its year: Reformationstag in Bremen, Hamburg, Niedersachsen and Schleswig-Holstein (2018), Frauentag in Berlin (2019) and Mecklenburg-Vorpommern (2023), Weltkindertag in Thüringen (2019), and the one-off holidays of Berlin on 8 May 2020, 8 May 2025 and 17 June 2028. Years before 2018 end in `HolidayCalendarError` with the code `unsupported_year`.
372
+ - Later years follow the laws as they stood when this version read them (`holidayLawsReadOn`). Several states allow their government to declare one-off holidays by ordinance; one declared after that day needs a new version.
373
+ - The Easter-based holidays follow Easter Sunday of the Gregorian calendar; Buß- und Bettag (Sachsen) is the Wednesday before the last Sunday before the first Sunday of Advent.
374
+ - The laws of Berlin and Hessen also count every Sunday as a public holiday, and Brandenburg names Ostersonntag and Pfingstsonntag. The calendar lists the Sundays Brandenburg names; for working days every Sunday is excluded anyway.
375
+ - Religious holidays that protect only church services or give employees time off (for example Fronleichnam outside its region in Sachsen, § 3 SächsSFG) and memorial days (Gedenktage) are no statutory public holidays and are not listed.
376
+
315
377
  ## API At A Glance
316
378
 
317
379
  ### `StockPriceService`
@@ -375,11 +437,24 @@ const content = parseStructuredContent(xmlText);
375
437
  - `formatRegisterReference(reference)`
376
438
  - `structuredContentNotice`
377
439
 
440
+ ### Holiday calendar (`@fin.cx/opendata/holidays`)
441
+
442
+ - `getPublicHolidays(year, state)`
443
+ - `getPublicHolidaysOn(date, state)`
444
+ - `isPublicHoliday(date, state, { regionalHolidays })`
445
+ - `isWorkingDay(date, state, { regionalHolidays })`
446
+ - `nextWorkingDay(date, state, { regionalHolidays })`
447
+ - `workingDayOnOrAfter(date, state, { regionalHolidays })`
448
+ - `easterSunday(year)`
449
+ - `germanStates`, `findGermanState(codeOrName)`
450
+ - `holidayCalendarFirstYear`, `holidayLawsReadOn`
451
+ - `HolidayCalendarError` with the codes `invalid_date`, `unsupported_year`, `unknown_state` and `invalid_options`
452
+
378
453
  ## Data Sources
379
454
 
380
455
  The package ships no market or register data. Prices and fundamentals come at run time from the providers you register (Marketstack, CoinGecko, SEC EDGAR), and register entries come at run time from the register portal of the federal states (handelsregister.de), one lookup at a time.
381
456
 
382
- The only data the package carries are the code tables that `@fin.cx/opendata/handelsregister` uses to decode the structured register content (`ts/handelsregister/data.codelists.ts`). They are official code lists, published on XRepository (https://www.xrepository.de), and were taken on 2026-09-23:
457
+ The package carries two kinds of data: the holiday rules of `@fin.cx/opendata/holidays` (`ts/holidays/data.holidays.ts`), described at the end of this section, and the code tables that `@fin.cx/opendata/handelsregister` uses to decode the structured register content (`ts/handelsregister/data.codelists.ts`). The code tables are official code lists, published on XRepository (https://www.xrepository.de), and were taken on 2026-09-23:
383
458
 
384
459
  | Table | Code list | Issued by |
385
460
  | --- | --- | --- |
@@ -399,6 +474,29 @@ Codes and designations are reproduced as published, without changes. The tables
399
474
 
400
475
  A later version of a code list may add codes. The parser keeps a code it does not know as a code, without a designation, and never guesses one.
401
476
 
477
+ The holiday calendar is derived from the holiday laws of the federal states, read on 2026-09-24 in their official portals, from Art. 2 Abs. 2 of the Unification Treaty for 3 October, and it answers the question of § 193 BGB:
478
+
479
+ | State | Law | Provision |
480
+ | --- | --- | --- |
481
+ | all | [Einigungsvertrag](https://www.gesetze-im-internet.de/einigvtr/art_2.html) | Art. 2 Abs. 2 (Tag der Deutschen Einheit) |
482
+ | Baden-Württemberg | [Feiertagsgesetz (FTG)](https://www.landesrecht-bw.de/perma?j=FeiertG_BW_!_1) | § 1 |
483
+ | Bayern | [Feiertagsgesetz (FTG)](https://www.gesetze-bayern.de/Content/Document/BayFTG-1) | Art. 1 |
484
+ | Berlin | [Gesetz über die Sonn- und Feiertage](https://gesetze.berlin.de/perma?j=FeiertG_BE_!_1) | § 1 Abs. 1 |
485
+ | Brandenburg | [Feiertagsgesetz (FTG)](https://bravors.brandenburg.de/gesetze/ftg_2015) | § 2 Abs. 1 |
486
+ | Bremen | [Gesetz über die Sonn-, Gedenk- und Feiertage](https://www.transparenz.bremen.de/sixcms/detail.php?gsid=bremen2014_tp.c.145882.de) | § 2 Abs. 1 |
487
+ | Hamburg | [Feiertagsgesetz](https://www.landesrecht-hamburg.de/perma?d=jlr-NNLHH00006CB9NN00000000002) | § 1 |
488
+ | Hessen | [Hessisches Feiertagsgesetz (HFeiertagsG)](https://www.rv.hessenrecht.hessen.de/perma?j=FeiertG_HE_!_1) | § 1 Abs. 1 |
489
+ | Mecklenburg-Vorpommern | [Feiertagsgesetz (FTG M-V)](https://www.landesrecht-mv.de/perma?d=jlr-NNLMV00007A2FNN00000000003) | § 2 Abs. 1 |
490
+ | Niedersachsen | [Niedersächsisches Gesetz über die Feiertage (NFeiertagsG)](https://voris.wolterskluwer-online.de/browse/document/f74bc6e7-6ded-3c2c-9afb-b34620456e56) | § 2 Abs. 1 |
491
+ | Nordrhein-Westfalen | [Gesetz über die Sonn- und Feiertage (Feiertagsgesetz NW)](https://recht.nrw.de/lrgv/gesetz/01012000-bekanntmachung-der-neufassung-des-gesetzes-ueber-die-sonn-und-feiertage) | § 2 Abs. 1 |
492
+ | Rheinland-Pfalz | [Feiertagsgesetz (LFtG)](https://landesrecht.rlp.de/perma?d=jlr-NNLRP00006125NN00000000004) | § 2 Abs. 1 |
493
+ | Saarland | [Feiertagsgesetz (SFG)](https://recht.saarland.de/perma?d=jlr-NNLSL0000A2D6NN00000000007) | § 2 Abs. 1 |
494
+ | Sachsen | [Sächsisches Sonn- und Feiertagsgesetz (SächsSFG)](https://www.revosax.sachsen.de/vorschrift/3997-SaechsSFG), [FronleichnamsVO](https://www.revosax.sachsen.de/vorschrift/4142) | § 1 Abs. 1; § 1 and annex |
495
+ | Sachsen-Anhalt | [Gesetz über die Sonn- und Feiertage (FeiertG LSA)](https://www.landesrecht.sachsen-anhalt.de/perma?d=jlr-NNLST0000411CNN00000000003) | § 2 |
496
+ | Schleswig-Holstein | [Gesetz über Sonn- und Feiertage (SFTG)](https://www.gesetze-rechtsprechung.sh.juris.de/perma?j=FeiertG_SH_!_2) | § 2 Abs. 1 |
497
+ | Thüringen | [Thüringer Feier- und Gedenktagsgesetz (ThürFGtG)](https://landesrecht.thueringen.de/perma?j=FeiertG_TH_!_2) | § 2, § 10 Abs. 1 |
498
+ | all | [Bürgerliches Gesetzbuch](https://www.gesetze-im-internet.de/bgb/__193.html) | § 193 (Sonntag, Feiertag, Sonnabend) |
499
+
402
500
  ## Behavior And Caveats
403
501
 
404
502
  - Historical market data is cached aggressively because it is treated as immutable.
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@fin.cx/opendata',
6
- version: '4.0.0',
7
- description: 'Open business and financial data for TypeScript: register lookups on the German register portal (handelsregister.de) without storage, and stock and crypto prices and fundamentals from several providers.'
6
+ version: '4.1.0',
7
+ description: 'Open business and financial data for TypeScript: register lookups on the German register portal (handelsregister.de) without storage, the statutory public holidays of the German federal states, and stock and crypto prices and fundamentals from several providers.'
8
8
  }
@@ -21,25 +21,103 @@ export interface IParsedRegisterReferenceText {
21
21
 
22
22
  const collapse = (textArg: string): string => textArg.replace(/\s+/g, ' ').trim();
23
23
 
24
- const courtKey = (textArg: string): string =>
25
- collapse(textArg)
26
- .replace(/^(?:amtsgericht|registergericht|ag)\s+/i, '')
27
- .toLocaleLowerCase('de-DE');
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' };
28
26
 
29
- /** the alternative name the portal puts in brackets: `Berlin (Charlottenburg)` → `charlottenburg` */
30
- const courtAliasKey = (nameArg: string): string | undefined => {
31
- const bracket = /\(([^()]+)\)\s*$/.exec(nameArg);
32
- return bracket ? courtKey(bracket[1]) : undefined;
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(' ');
33
65
  };
34
66
 
35
67
  /**
36
- * Finds a register court by its code or its name: `H1101`, `Bremen`, `Amtsgericht Bremen`,
37
- * `Berlin (Charlottenburg)` or `Charlottenburg`. Returns undefined unless exactly one court fits.
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.
38
113
  */
39
114
  export const findRegisterCourt = (
40
115
  codeOrNameArg: string,
41
116
  courtsArg: readonly IRegisterCourt[] = registerCourts
42
117
  ): IRegisterCourt | undefined => {
118
+ if (typeof codeOrNameArg !== 'string') {
119
+ return undefined;
120
+ }
43
121
  const text = collapse(codeOrNameArg);
44
122
  if (!text) {
45
123
  return undefined;
@@ -48,16 +126,12 @@ export const findRegisterCourt = (
48
126
  if (byCode) {
49
127
  return byCode;
50
128
  }
51
- const key = courtKey(text);
52
- const byName = courtsArg.filter((courtArg) => courtKey(courtArg.name) === key);
53
- if (byName.length === 1) {
54
- return byName[0];
55
- }
56
- if (byName.length > 1) {
57
- return undefined;
58
- }
59
- const byAlias = courtsArg.filter((courtArg) => courtAliasKey(courtArg.name) === key);
60
- return byAlias.length === 1 ? byAlias[0] : undefined;
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;
61
135
  };
62
136
 
63
137
  /** returns the canonical register type for `hrb`, `HRB`, `gnr` …, or undefined */