Freitag, 6. April 2012

Ermittlung von Feiertagen per Table Function

Heute geht's um die Ermittlung von Feiertagen in Deutschland; und das wieder mal per Table Function.

Da viele Feiertage (z.B. Rosenmontag) eine Abhängigkeit zum Osterdatum besitzen, benötigt man zunächst eine Funktion zur Berechnung des Osterdatums; genauer gesagt zur Bestimmung von Ostersonntag:
create or replace function easter_day
(
 p_year_in in integer
)
return date
as
 v_k integer;
 v_m integer;
 v_s integer;
 v_a integer;
 v_d integer;
 v_r integer;
 v_og integer;
 v_sz integer;
 v_oe integer;
 v_os integer;
 v_day integer;
 v_month integer;
begin
 v_k := floor(p_year_in / 100);
 v_m := 15 + floor((3 * v_k + 3) / 4) - floor((8 * v_k + 13) / 25);
 v_s := 2 - floor((3 * v_k + 3) / 4);
 v_a := mod(p_year_in, 19);
 v_d := mod((19 * v_a + v_m), 30);
 v_r := floor(v_d / 29) + (floor(v_d / 28) - floor(v_d / 29)) * floor(v_a / 11);
 v_og := 21 + v_d - v_r;
 v_sz := 7 - mod((p_year_in + floor(p_year_in / 4) + v_s), 7);
 v_oe := 7 - mod(v_og - v_sz, 7);
 v_os := v_og + v_oe;
 if (v_os <= 31) then
  v_day := v_os;
  v_month := 3;
 else
  v_day := v_os - 31;
  v_month := 4;
 end if;
 return to_date(v_day || '.' || v_month || '.' || p_year_in, 'DD.MM.YYYY');
end easter_day;
Dabei handelt es sich im Wesentlichen um die ergänzte Osterformel von Carl-Friedrich Gauß; diese wurde durch Hermann Kinkelin und Christian Zeller dahingehend ergänzt, dass Ausnahmeregeln in der Formel berücksichtigt werden. Siehe dazu auch den entsprechenden Eintrag auf der deutschsprachigen Seite von Wikipedia.

Kommen wir nun zur eigentlichen Table-Function, für die zuächst die Objekt-Typen erstellt werden müssen:
create type holiday_t as object
(
 holiday_date date,
 holiday_name varchar2(30),
 holiday_desc varchar2(100)
);
/
create type holiday_tab as table of holiday_t;
/
Die eigentliche Table-Function liefert nun die "Feiertage" der jeweiligen Jahre:
create or replace function german_holidays
(
 p_year_start_in in integer,
 p_year_end_in in integer
) 
return holiday_tab pipelined
as
 v_easter_day date;
begin
 for y in p_year_start_in .. p_year_end_in loop
  
  v_easter_day := easter_day(y);

  pipe row (
   holiday_t(
    to_date('01.01.' || y, 'DD.MM.YYYY'),
    'Neujahrstag',
    'Gesetzlicher Feiertag'));

  pipe row (
   holiday_t(
    to_date('06.01.' || y, 'DD.MM.YYYY'),
    'Heilige Drei Könige',
    'Nur BW, BY und ST'));

  pipe row (
   holiday_t(
    v_easter_day - interval '52' day,
    'Weiberdonnerstag',
    '-'));

  pipe row (
   holiday_t(
    v_easter_day - interval '48' day,
    'Rosenmontag',
    '-'));

  pipe row (
   holiday_t(
    v_easter_day - interval '46' day,
    'Aschermittwoch',
    '-'));

  pipe row (
   holiday_t(
    v_easter_day - interval '3' day,
    'Gründonnerstag',
    '-'));

  pipe row (
   holiday_t(
    v_easter_day - interval '2' day,
    'Karfreitag',
    'Gesetzlicher Feiertag'));

  pipe row (
   holiday_t(
    v_easter_day + interval '1' day,
    'Ostermontag',
    'Gesetzlicher Feiertag'));

  pipe row (
   holiday_t(
    to_date('01.05.' || y, 'DD.MM.YYYY'),
    'Tag der Arbeit',
    'Gesetzlicher Feiertag'));

  pipe row (
   holiday_t(
    v_easter_day + interval '39' day,
    'Christi Himmelfahrt',
    'Gesetzlicher Feiertag'));

  pipe row (
   holiday_t(
    v_easter_day + interval '50' day,
    'Pfingstmontag',
    'Gesetzlicher Feiertag'));

  pipe row (
   holiday_t(
    v_easter_day + interval '60' day,
    'Fronleichnam',
    'Nur BW, BY, HE, NW, RP'));

  pipe row (
   holiday_t(
    to_date('08.08.' || y, 'DD.MM.YYYY'),
    'Augsburger Friedensfest',
    'Nur im Stadtgebiet Augsburg'));

  pipe row (
   holiday_t(
    to_date('08.08.' || y, 'DD.MM.YYYY'),
    'Mariä Himmelfahrt',
    'Nur SL und Teilen von BY'));

  pipe row (
   holiday_t(
    to_date('03.10.' || y, 'DD.MM.YYYY'),
    'Tag der Deutschen Einheit',
    'Gesetzlicher Feiertag'));

  pipe row (
   holiday_t(
    to_date('31.10.' || y, 'DD.MM.YYYY'),
    'Reformationstag',
    'Nur BB, MV, SL, SN und TH'));

  pipe row (
   holiday_t(
    to_date('01.11.' || y, 'DD.MM.YYYY'),
    'Allerheiligen',
    'Nur BW, BY, NW, RP und SL'));

  pipe row (
   holiday_t(
    to_date('11.11.' || y, 'DD.MM.YYYY'),
    'Karnevalsbeginn',
    '-'));

  pipe row (
   holiday_t(
    to_date('25.12.' || y, 'DD.MM.YYYY'),
    '1. Weihnachtstag',
    'Gesetzlicher Feiertag'));

  pipe row (
   holiday_t(
    to_date('26.12.' || y, 'DD.MM.YYYY'),
    '2. Weihnachtstag',
    'Gesetzlicher Feiertag'));

 end loop;
end german_holidays;
Für das Jahr 2012 erhält man dann:
select 
 * 
from 
 table(german_holidays(2012, 2012));
HOLIDAY_DATE  HOLIDAY_NAME                   HOLIDAY_DESC
------------- ------------------------------ ----------------------------
01.01.12      Neujahrstag                    Gesetzlicher Feiertag
06.01.12      Heilige Drei Könige            Nur BW, BY und ST
16.02.12      Weiberdonnerstag               -
20.02.12      Rosenmontag                    -
22.02.12      Aschermittwoch                 -
05.04.12      Gründonnerstag                 -
06.04.12      Karfreitag                     Gesetzlicher Feiertag
09.04.12      Ostermontag                    Gesetzlicher Feiertag
01.05.12      Tag der Arbeit                 Gesetzlicher Feiertag
17.05.12      Christi Himmelfahrt            Gesetzlicher Feiertag
28.05.12      Pfingstmontag                  Gesetzlicher Feiertag
07.06.12      Fronleichnam                   Nur BW, BY, HE, NW, RP
08.08.12      Augsburger Friedensfest        Nur im Stadtgebiet Augsburg
08.08.12      Mariä Himmelfahrt              Nur SL und Teilen von BY
03.10.12      Tag der Deutschen Einheit      Gesetzlicher Feiertag
31.10.12      Reformationstag                Nur BB, MV, SL, SN und TH
01.11.12      Allerheiligen                  Nur BW, BY, NW, RP und SL
11.11.12      Karnevalsbeginn                -
25.12.12      1. Weihnachtstag               Gesetzlicher Feiertag
26.12.12      2. Weihnachtstag               Gesetzlicher Feiertag
Mit Bezug auf das vorherige Posting ergibt sich nun die Möglichkeit, die Zeitdimension um die Feiertage zu erweitern.

Kommentare:

  1. Ich vermisse den Buß- und Bettag....erfordert dieser eine gesonderte Berechnung?

    AntwortenLöschen
    Antworten
    1. Vorletzter Mittwoch vor 1. Advent bzw. o'Ton Wikipedia: elf Tage vor dem ersten Adventssonntag.

      Also ja, rechnen.

      Löschen
  2. Danke Thomas, super Code-Snippets.

    Ein Tippfehler: Mariä Himmelfahrt ist am 15.08.

    AntwortenLöschen