From b682c225ab39859b6488ad2c327c6a67bbaf5b50 Mon Sep 17 00:00:00 2001 From: Matthias Andreas Benkard Date: Mon, 24 Aug 2026 20:34:59 +0200 Subject: Ein Gesetz tritt nicht notwendig auf einen Schlag in Kraft MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Das Dritte UWG-Änderungsgesetz lässt seinen Artikel 1 Nummer 2 Buchstabe c am 19. Juni 2026 wirksam werden und alles Übrige erst am 27. September; das Infektionsschutz-Änderungsgesetz von 2020 ändert dasselbe Stammgesetz in zwei Artikeln zu zwei Zeitpunkten. Wer alle Befehle zugleich anwendet, erhält dort eine Fassung, die an keinem einzigen Tag gegolten hat. Genau das tat ÄndGgner bisher, und zwar schweigend — die auffälligste Stelle, an der ein Werkzeug, das nie stillschweigend verwerfen will, doch etwas verschwieg. Der Schlussartikel wird jetzt gelesen. Die Artikelwahl erfasst ihn nicht, denn er trägt weder Änderungsformel noch Stammgesetz; maßgeblich ist der erste Artikel hinter dem letzten ändernden, damit in einem Gesetzblatt mit mehreren Gesetzen jedes seine eigene Schlussvorschrift bekommt. Erkannt werden die Grundregel, der Vorbehalt zugunsten späterer Absätze, die abweichende Anordnung für Artikel, Nummern, Buchstaben und Doppelbuchstaben sowie die Rückwirkung („mit Wirkung vom“). Nennt der Wortlaut kein Datum, sondern die Verkündung, so wird keines erfunden: Der Verkündungstag steht nicht im Gesetzestext. Zugeordnet wird über die Herkunft des Befehls — Artikel und Gliederungspfad —, und es gewinnt die besonderste Anordnung. Mit --stichtag (im Vordruck: „Stichtag“) ergibt sich die an jenem Tag geltende Fassung; was noch nicht galt, bleibt unangewandt und steht für sich, nicht unter den Gründen des Scheiterns. Ohne Stichtag bleibt es beim vollen Bestand, aber die Staffelung wird gerügt. Geprüft: 348 Tests (339 + 6 Einheitstests des Lesers + 3 Belegfälle), REUSE 180/180. UWG 1/19 Befehle am 19. Juni gegen 19/19 am 27. September, GEG 115 gegen 116, IfSG 65 gegen 75 — jeweils ohne Zuwachs an manuellen Resten. Co-Authored-By: Claude Opus 5 Change-Id: I887f233c72a4e49b69f9ede3154dcadd704fb0c3 --- src/test/java/eu/mulk/aendggner/EndToEndTest.java | 98 ++++++++++++++ .../aenderung/parse/InkrafttretensLeserTest.java | 143 +++++++++++++++++++++ 2 files changed, 241 insertions(+) create mode 100644 src/test/java/eu/mulk/aendggner/aenderung/parse/InkrafttretensLeserTest.java (limited to 'src/test/java/eu') diff --git a/src/test/java/eu/mulk/aendggner/EndToEndTest.java b/src/test/java/eu/mulk/aendggner/EndToEndTest.java index d9f36d7..10cfb8d 100644 --- a/src/test/java/eu/mulk/aendggner/EndToEndTest.java +++ b/src/test/java/eu/mulk/aendggner/EndToEndTest.java @@ -24,6 +24,7 @@ import eu.mulk.aendggner.synopse.HtmlRenderer; import eu.mulk.aendggner.synopse.SynopseBuilder; import java.nio.file.Files; import java.nio.file.Path; +import java.time.LocalDate; import java.util.List; import org.junit.jupiter.api.Test; @@ -1650,4 +1651,101 @@ class EndToEndTest { .findFirst() .orElseThrow(); } + + /** + * Die Fassung eines bestimmten Tages. Das 3. UWGÄndG tritt gestaffelt in Kraft: Artikel 1 Nummer + * 2 Buchstabe c am 19. Juni 2026, alles Übrige erst am 27. September 2026. Wer alle Befehle auf + * einen Schlag anwendet, erhält eine Fassung, die an keinem einzigen Tag gegolten hat — deshalb + * warnt die Synopse davor, und mit einem Stichtag ergibt sich die wirklich geltende Fassung. + */ + @Test + void uwgTrittGestaffeltInKraft() throws Exception { + var xml = SAMPLEDATA.resolve("UWG/BJNR141400004.xml"); + var pdf = SAMPLEDATA.resolve("UWG/bgbl126s0043_regelungstext.pdf"); + assumeTrue(Files.exists(xml) && Files.exists(pdf), "UWG-Beispieldaten fehlen"); + + // Ohne Stichtag bleibt es beim vollen Bestand — aber nicht stillschweigend. + var ohne = Pipeline.erzeugeSynopse(xml, List.of(pdf), null, false); + assertThat(ohne.anzahlAngewandt()).isEqualTo(19); + assertThat(ohne.html()) + .contains("Das Änderungsgesetz tritt gestaffelt in Kraft") + .contains("Artikel 1 Nummer 2 Buchstabe c tritt am 19. Juni 2026 in Kraft.") + .contains("
Inkrafttreten
27. September 2026 (gestaffelt, siehe unten)
"); + + // Am 19. Juni 2026 galt genau ein Befehl: der Buchstabe c der Nummer 2. + var frueh = Pipeline.erzeugeSynopse(xml, List.of(pdf), null, false, LocalDate.of(2026, 6, 19)); + assertThat(frueh.anzahlAngewandt()).isEqualTo(1); + assertThat(frueh.anzahlManuell()).isZero(); + assertThat(frueh.html()) + .contains("
Stichtag
19. Juni 2026
") + .contains("Am Stichtag noch nicht in Kraft") + .contains("Tritt erst am 27. September 2026 in Kraft"); + // Der Tag davor ändert noch gar nichts. + var davor = Pipeline.erzeugeSynopse(xml, List.of(pdf), null, false, LocalDate.of(2026, 6, 18)); + assertThat(davor.anzahlAngewandt()).isZero(); + + // Am 27. September 2026 ist das Gesetz vollständig in Kraft; dann deckt sich die Fassung mit + // der ungefilterten, und der Abschnitt „noch nicht in Kraft“ entfällt. + var spaet = Pipeline.erzeugeSynopse(xml, List.of(pdf), null, false, LocalDate.of(2026, 9, 27)); + assertThat(spaet.anzahlAngewandt()).isEqualTo(19); + assertThat(spaet.html()).doesNotContain("Am Stichtag noch nicht in Kraft"); + } + + /** + * Dasselbe am großen Fall, und zugleich die Probe auf die Schritt-Ordnung: Im GEG bleibt am 1. + * Januar 2024 allein der Befehl der Nummer 22 zurück. Er steht in keiner Umnummerierungs-Kaskade, + * sodass die übrigen 115 unverändert durchlaufen — die drei liegengebliebenen Befehle sind + * dieselben wie ohne Stichtag und werden von der Auswahl nicht vermehrt. + */ + @Test + void gegStichtagLaesstDieUebrigenBefehleUnberuehrt() throws Exception { + var xml = SAMPLEDATA.resolve("GEG/BJNR172810020-2023.xml"); + var pdf = SAMPLEDATA.resolve("GEG/bgbl123s0280_regelungstext.pdf"); + assumeTrue(Files.exists(xml) && Files.exists(pdf), "GEG-Beispieldaten fehlen"); + + var vollstaendig = Pipeline.erzeugeSynopse(xml, List.of(pdf), null, false); + assertThat(vollstaendig.anzahlAngewandt()).isEqualTo(116); + assertThat(vollstaendig.anzahlManuell()).isEqualTo(3); + + var anfang2024 = + Pipeline.erzeugeSynopse(xml, List.of(pdf), null, false, LocalDate.of(2024, 1, 1)); + assertThat(anfang2024.anzahlAngewandt()).isEqualTo(115); + assertThat(anfang2024.anzahlManuell()).isEqualTo(3); + assertThat(anfang2024.html()) + .contains("Am Stichtag noch nicht in Kraft") + .contains("Tritt erst am 1. Oktober 2024 in Kraft"); + + var oktober2024 = + Pipeline.erzeugeSynopse(xml, List.of(pdf), null, false, LocalDate.of(2024, 10, 1)); + assertThat(oktober2024.anzahlAngewandt()).isEqualTo(116); + } + + /** + * Das IfSG-Gesetz von 2020 ändert dasselbe Stammgesetz in zwei Artikeln zu zwei Zeitpunkten: + * Artikel 1 sogleich, Artikel 2 erst am 1. April 2021. Genau dort ist die auf einen Schlag + * gerechnete Fassung eine, die es nie gab. + * + *

Zugleich der Fall der unbestimmten Grundregel: „am Tag nach der Verkündung“ nennt kein + * Datum, das im Gesetzestext stünde. Erfunden wird keines; die betroffenen Befehle gelten als am + * Stichtag bereits wirksam, und das wird gesagt. + */ + @Test + void ifsgZweiArtikelZuZweiZeitpunkten() throws Exception { + var xml = SAMPLEDATA.resolve("IfSG/BJNR104510000-2020.xml"); + var pdf = SAMPLEDATA.resolve("IfSG/bgbl120s2397_78991.pdf"); + assumeTrue(Files.exists(xml) && Files.exists(pdf), "IfSG-Beispieldaten fehlen"); + + var novemberFassung = + Pipeline.erzeugeSynopse(xml, List.of(pdf), null, false, LocalDate.of(2020, 11, 19)); + assertThat(novemberFassung.anzahlAngewandt()).isEqualTo(65); + assertThat(novemberFassung.html()) + .contains("Am Stichtag noch nicht in Kraft") + .contains("Eine Inkrafttretens-Anordnung nennt kein bestimmtes Datum"); + + // Am 1. April 2021 kommen die zehn Befehle des Artikels 2 und der Doppelbuchstabe hinzu. + var aprilFassung = + Pipeline.erzeugeSynopse(xml, List.of(pdf), null, false, LocalDate.of(2021, 4, 1)); + assertThat(aprilFassung.anzahlAngewandt()).isEqualTo(75); + assertThat(aprilFassung.html()).doesNotContain("Am Stichtag noch nicht in Kraft"); + } } diff --git a/src/test/java/eu/mulk/aendggner/aenderung/parse/InkrafttretensLeserTest.java b/src/test/java/eu/mulk/aendggner/aenderung/parse/InkrafttretensLeserTest.java new file mode 100644 index 0000000..819cd95 --- /dev/null +++ b/src/test/java/eu/mulk/aendggner/aenderung/parse/InkrafttretensLeserTest.java @@ -0,0 +1,143 @@ +// SPDX-FileCopyrightText: 2020 Matthias Andreas Benkard +// SPDX-License-Identifier: AGPL-3.0-or-later +package eu.mulk.aendggner.aenderung.parse; + +import static org.assertj.core.api.Assertions.assertThat; + +import eu.mulk.aendggner.aenderung.Provenienz; +import java.time.LocalDate; +import org.junit.jupiter.api.Test; + +/** Der Schlussartikel, gelesen — die Wortlaute stammen sämtlich aus den Beispieldaten. */ +class InkrafttretensLeserTest { + + private static Provenienz punkt(String artikel, String pfad) { + return new Provenienz(artikel, pfad, ""); + } + + @Test + void schlichtesInkrafttretenOhneStaffelung() { + var inkrafttreten = + InkrafttretensLeser.lies("Dieses Gesetz tritt am 12. Juni 2026 in Kraft.\nBerlin, den 3."); + + assertThat(inkrafttreten).isNotNull(); + assertThat(inkrafttreten.gestaffelt()).isFalse(); + assertThat(inkrafttreten.grundregel().orElseThrow().datum()) + .isEqualTo(LocalDate.of(2026, 6, 12)); + // Ohne eigenen Bezug gilt für jeden Punkt die Grundregel. + assertThat(inkrafttreten.fuer(punkt("1", "3. a)")).orElseThrow().datum()) + .isEqualTo(LocalDate.of(2026, 6, 12)); + } + + /** UWG 2026: Ein einzelner Buchstabe tritt drei Monate vor dem übrigen Gesetz in Kraft. */ + @Test + void gestaffeltMitBuchstabenBezug() { + var inkrafttreten = + InkrafttretensLeser.lies( + """ + (1) Dieses Gesetz tritt vorbehaltlich des Absatzes 2 am 27. September 2026 in Kraft. + (2) Artikel 1 Nummer 2 Buchstabe c tritt am 19. Juni 2026 in Kraft. + Die verfassungsmäßigen Rechte des Bundesrates sind gewahrt. + """); + + assertThat(inkrafttreten).isNotNull(); + assertThat(inkrafttreten.gestaffelt()).isTrue(); + // Der Vorbehalt („vorbehaltlich des Absatzes 2“) verweist auf den Schlussartikel selbst; er + // ist kein Punktbezug und macht die Grundregel nicht zur Sonderregel. + assertThat(inkrafttreten.grundregel().orElseThrow().datum()) + .isEqualTo(LocalDate.of(2026, 9, 27)); + assertThat(inkrafttreten.fuer(punkt("1", "2. c)")).orElseThrow().datum()) + .isEqualTo(LocalDate.of(2026, 6, 19)); + // Der Nachbarbuchstabe ist nicht gemeint, und die Nummer selbst auch nicht. + assertThat(inkrafttreten.fuer(punkt("1", "2. d)")).orElseThrow().datum()) + .isEqualTo(LocalDate.of(2026, 9, 27)); + assertThat(inkrafttreten.fuer(punkt("1", "2.")).orElseThrow().datum()) + .isEqualTo(LocalDate.of(2026, 9, 27)); + } + + /** + * GEG 2023: „Abweichend von Absatz 1 treten Artikel 1 Nummer 22 sowie Artikel 3 am … in Kraft.“ — + * zwei Bezüge in einem Satz, davon einer auf einen ganzen Artikel. Der Schlusssatz über die + * Rechte des Bundesrates klebt im Gesetzblatt unmittelbar am Anordnungssatz. + */ + @Test + void zweiBezuegeInEinemSatzUndAngeklebterSchlusssatz() { + var inkrafttreten = + InkrafttretensLeser.lies( + """ + (1) Dieses Gesetz tritt vorbehaltlich des Absatzes 2 am 1. Januar 2024 in Kraft. + (2) Abweichend von Absatz 1 treten Artikel 1 Nummer 22 sowie Artikel 3 am 1. Oktober \ + 2024 in Kraft. Die verfassungsmäßigen Rechte des Bundesrates sind gewahrt. + """); + + assertThat(inkrafttreten).isNotNull(); + var sonder = inkrafttreten.sonderregeln(); + assertThat(sonder).hasSize(1); + assertThat(sonder.get(0).wortlaut()).endsWith("am 1. Oktober 2024 in Kraft."); + assertThat(inkrafttreten.fuer(punkt("1", "22.")).orElseThrow().datum()) + .isEqualTo(LocalDate.of(2024, 10, 1)); + // Ein ganzer Artikel als Bezug erfasst jeden seiner Punkte. + assertThat(inkrafttreten.fuer(punkt("3", "5. b)")).orElseThrow().datum()) + .isEqualTo(LocalDate.of(2024, 10, 1)); + assertThat(inkrafttreten.fuer(punkt("1", "21.")).orElseThrow().datum()) + .isEqualTo(LocalDate.of(2024, 1, 1)); + } + + /** + * IfSG 2020: Rückwirkendes Inkrafttreten („mit Wirkung vom“), ein Doppelbuchstabe als Bezug und + * eine Grundregel, die kein Datum nennt — der Verkündungstag steht nicht im Gesetzestext, also + * wird auch keiner erfunden. + */ + @Test + void rueckwirkungDoppelbuchstabeUndUnbestimmteGrundregel() { + var inkrafttreten = + InkrafttretensLeser.lies( + """ + (1) Dieses Gesetz tritt vorbehaltlich der Absätze 2 und 3 am Tag nach der Verkündung \ + in Kraft. + (2) Artikel 4a Nummer 1 tritt mit Wirkung vom + 18. November 2020 in Kraft. + (3) Artikel 1 Nummer 11 Buchstabe a Doppelbuchstabe bb und Artikel 2 treten am \ + 1. April 2021 in Kraft. + """); + + assertThat(inkrafttreten).isNotNull(); + var grundregel = inkrafttreten.grundregel().orElseThrow(); + assertThat(grundregel.datum()).isNull(); + assertThat(grundregel.anzeige()).isEqualTo(grundregel.wortlaut()); + // Der Zeilenumbruch mitten im Datum („mit Wirkung vom\n18. November 2020“) stört nicht. + assertThat(inkrafttreten.fuer(punkt("4a", "1.")).orElseThrow().datum()) + .isEqualTo(LocalDate.of(2020, 11, 18)); + assertThat(inkrafttreten.fuer(punkt("1", "11. a) bb)")).orElseThrow().datum()) + .isEqualTo(LocalDate.of(2021, 4, 1)); + assertThat(inkrafttreten.fuer(punkt("1", "11. a) aa)")).orElseThrow().datum()).isNull(); + assertThat(inkrafttreten.fuer(punkt("2", "6. b)")).orElseThrow().datum()) + .isEqualTo(LocalDate.of(2021, 4, 1)); + } + + /** Das Außerkrafttreten sagt nichts darüber, wann eine Änderung wirkt — es bleibt außer Acht. */ + @Test + void ausserkrafttretenIstKeineAnordnungDesInkrafttretens() { + assertThat(InkrafttretensLeser.lies("Diese Verordnung tritt am 31. Dezember 2027 außer Kraft.")) + .isNull(); + } + + /** + * Ein Gesetzblatt trägt mehrere Gesetze mit je eigener Schlussvorschrift. Maßgeblich ist die, die + * auf den eigenen ändernden Artikel folgt — nicht die eines fremden Gesetzes davor. + */ + @Test + void waehltDenSchlussartikelHinterDemEigenenGesetz() { + var bloecke = + java.util.List.of( + "Das Erste Gesetz wird wie folgt geändert: …", + "Dieses Gesetz tritt am 1. März 2026 in Kraft.", + "Das Zweite Gesetz wird wie folgt geändert: …", + "Dieses Gesetz tritt am 1. Juli 2026 in Kraft."); + + assertThat(InkrafttretensLeser.waehle(bloecke, 1).grundregel().orElseThrow().datum()) + .isEqualTo(LocalDate.of(2026, 3, 1)); + assertThat(InkrafttretensLeser.waehle(bloecke, 3).grundregel().orElseThrow().datum()) + .isEqualTo(LocalDate.of(2026, 7, 1)); + } +} -- cgit v1.2.1