it-swarm.com.de

Wiederkehrende / Wiederholende Kalenderereignisse - Beste Speichermethode

Ich baue ein benutzerdefiniertes Ereignissystem auf, und wenn Sie ein sich wiederholendes Ereignis haben, das so aussieht:

Ereignis A wird ab dem 3. März 2011 alle 4 Tage wiederholt

oder

Event B wird alle 2 Wochen am Dienstag ab dem 1. März 2011 wiederholt

Wie kann ich das in einer Datenbank so speichern, dass es einfach nachzuschlagen ist? Ich möchte keine Leistungsprobleme, wenn es eine große Anzahl von Ereignissen gibt, und ich muss jeden einzelnen beim Rendern des Kalenders durchgehen.

283

Speichern von "einfachen" Wiederholungsmustern

Für meinen PHP/MySQL-basierten Kalender wollte ich Informationen zu wiederkehrenden Ereignissen so effizient wie möglich speichern. Ich wollte keine große Anzahl von Zeilen haben, und ich wollte einfach alle Ereignisse nachschlagen, die an einem bestimmten Datum stattfinden würden.

Die folgende Methode eignet sich hervorragend zum Speichern von sich wiederholenden Informationen, die in regelmäßigen Intervallen auftreten, z. B. jeden Tag, alle n Tage, jede Woche, jeden Monat, jedes Jahr usw. Dies schließt Muster vom Typ "Jeden Dienstag und Donnerstag" ein auch, weil sie wie jede Woche ab einem Dienstag und jede Woche ab einem Donnerstag getrennt gelagert werden.

Angenommen, ich habe zwei Tabellen, eine mit dem Namen events:

ID    NAME
1     Sample Event
2     Another Event

Und eine Tabelle mit dem Namen events_meta Sieht folgendermaßen aus:

ID    event_id      meta_key           meta_value
1     1             repeat_start       1299132000
2     1             repeat_interval_1  432000

Wenn repeat_start ein Datum ohne Uhrzeit als Unix-Zeitstempel ist und repeat_interval eine Anzahl von Sekunden zwischen Intervallen (432000 sind 5 Tage).

repeat_interval_1 geht mit repeat_start der ID 1 einher. Wenn ich also ein Ereignis habe, das sich jeden Dienstag und jeden Donnerstag wiederholt, wäre das repeat_interval 604800 (7 Tage), und es gäbe 2 repeat_starts und 2 repeat_intervals. Die Tabelle würde so aussehen:

ID    event_id      meta_key           meta_value
1     1             repeat_start       1298959200 -- This is for the Tuesday repeat
2     1             repeat_interval_1  604800
3     1             repeat_start       1299132000 -- This is for the Thursday repeat
4     1             repeat_interval_3  604800
5     2             repeat_start       1299132000
6     2             repeat_interval_5  1          -- Using 1 as a value gives us an event that only happens once

Wenn Sie dann einen Kalender haben, der jeden Tag durchläuft und die Ereignisse für den aktuellen Tag aufzeichnet, sieht die Abfrage folgendermaßen aus:

SELECT EV.*
FROM `events` EV
RIGHT JOIN `events_meta` EM1 ON EM1.`event_id` = EV.`id`
RIGHT JOIN `events_meta` EM2 ON EM2.`meta_key` = CONCAT( 'repeat_interval_', EM1.`id` )
WHERE EM1.meta_key = 'repeat_start'
    AND (
        ( CASE ( 1299132000 - EM1.`meta_value` )
            WHEN 0
              THEN 1
            ELSE ( 1299132000 - EM1.`meta_value` )
          END
        ) / EM2.`meta_value`
    ) = 1
LIMIT 0 , 30

Ersetzen Sie {current_timestamp} Durch den Unix-Zeitstempel für das aktuelle Datum (abzüglich der Uhrzeit, sodass die Werte für Stunde, Minute und Sekunde auf 0 gesetzt würden).

Hoffentlich hilft das auch jemand anderem!


Speichern von "komplexen" Wiederholungsmustern

Diese Methode eignet sich besser zum Speichern von komplexen Mustern wie z

Event A repeats every month on the 3rd of the month starting on March 3, 2011

oder

Event A repeats Friday of the 2nd week of the month starting on March 11, 2011

Ich würde empfehlen, dies mit dem obigen System zu kombinieren, um die größtmögliche Flexibilität zu erzielen. Die Tabellen dafür sollten wie folgt aussehen:

ID    NAME
1     Sample Event
2     Another Event

Und eine Tabelle mit dem Namen events_meta Sieht folgendermaßen aus:

ID    event_id      meta_key           meta_value
1     1             repeat_start       1299132000 -- March 3rd, 2011
2     1             repeat_year_1      *
3     1             repeat_month_1     *
4     1             repeat_week_im_1   2
5     1             repeat_weekday_1   6

repeat_week_im Steht für die Woche des aktuellen Monats, die möglicherweise zwischen 1 und 5 liegen kann. repeat_weekday Am Wochentag, 1-7.

Angenommen, Sie durchlaufen die Tage/Wochen, um eine Monatsansicht in Ihrem Kalender zu erstellen, können Sie eine Abfrage wie die folgende erstellen:

SELECT EV . *
FROM `events` AS EV
JOIN `events_meta` EM1 ON EM1.event_id = EV.id
AND EM1.meta_key = 'repeat_start'
LEFT JOIN `events_meta` EM2 ON EM2.meta_key = CONCAT( 'repeat_year_', EM1.id )
LEFT JOIN `events_meta` EM3 ON EM3.meta_key = CONCAT( 'repeat_month_', EM1.id )
LEFT JOIN `events_meta` EM4 ON EM4.meta_key = CONCAT( 'repeat_week_im_', EM1.id )
LEFT JOIN `events_meta` EM5 ON EM5.meta_key = CONCAT( 'repeat_weekday_', EM1.id )
WHERE (
  EM2.meta_value =2011
  OR EM2.meta_value = '*'
)
AND (
  EM3.meta_value =4
  OR EM3.meta_value = '*'
)
AND (
  EM4.meta_value =2
  OR EM4.meta_value = '*'
)
AND (
  EM5.meta_value =6
  OR EM5.meta_value = '*'
)
AND EM1.meta_value >= {current_timestamp}
LIMIT 0 , 30

Dies könnte in Kombination mit der obigen Methode kombiniert werden, um die meisten sich wiederholenden/wiederkehrenden Ereignismuster abzudecken. Wenn ich etwas verpasst habe, hinterlasse bitte einen Kommentar.

195

Die aktuell akzeptierte Antwort war eine große Hilfe für mich, aber ich wollte einige nützliche Änderungen mitteilen, die die Abfragen vereinfachen und auch die Leistung steigern.


"Einfache" Wiederholungsereignisse

Zur Behandlung von Ereignissen, die in regelmäßigen Abständen auftreten, wie zum Beispiel:

Repeat every other day 

oder

Repeat every week on Tuesday 

Sie sollten zwei Tabellen mit dem Namen events wie folgt erstellen:

ID    NAME
1     Sample Event
2     Another Event

Und eine Tabelle mit dem Namen events_meta Sieht folgendermaßen aus:

ID    event_id      repeat_start       repeat_interval
1     1             1369008000         604800            -- Repeats every Monday after May 20th 2013
1     1             1369008000         604800            -- Also repeats every Friday after May 20th 2013

Mit repeat_start Als Unix-Zeitstempeldatum ohne Zeitangabe (1369008000 entspricht dem 20. Mai 2013) und repeat_interval Als Betrag in Sekunden zwischen Intervallen (604800 entspricht 7 Tagen).

Wenn Sie jeden Tag im Kalender durchlaufen, können Sie Ereignisse mit dieser einfachen Abfrage wiederholen:

SELECT EV.*
FROM `events` EV
RIGHT JOIN `events_meta` EM1 ON EM1.`event_id` = EV.`id`
WHERE  (( 1299736800 - repeat_start) % repeat_interval = 0 )

Ersetzen Sie einfach jedes Datum in Ihrem Kalender durch den Unix-Zeitstempel (1299736800).

Beachten Sie die Verwendung des Modulo (% -Zeichen). Dieses Symbol ist wie eine reguläre Division, gibt jedoch den '' Rest '' anstelle des Quotienten zurück und ist daher immer dann 0, wenn das aktuelle Datum ein genaues Vielfaches des Wiederholungsintervalls von repeat_start ist.

Leistungsvergleich

Dies ist erheblich schneller als die zuvor vorgeschlagene Antwort auf der Basis von "meta_keys", die wie folgt lautete:

SELECT EV.*
FROM `events` EV
RIGHT JOIN `events_meta` EM1 ON EM1.`event_id` = EV.`id`
RIGHT JOIN `events_meta` EM2 ON EM2.`meta_key` = CONCAT( 'repeat_interval_', EM1.`id` )
WHERE EM1.meta_key = 'repeat_start'
    AND (
        ( CASE ( 1299132000 - EM1.`meta_value` )
            WHEN 0
              THEN 1
            ELSE ( 1299132000 - EM1.`meta_value` )
          END
        ) / EM2.`meta_value`
    ) = 1

Wenn Sie EXPLAIN this query ausführen, müssen Sie einen Join-Puffer verwenden:

+----+-------------+-------+--------+---------------+---------+---------+------------------+------+--------------------------------+
| id | select_type | table | type   | possible_keys | key     | key_len | ref              | rows | Extra                          |
+----+-------------+-------+--------+---------------+---------+---------+------------------+------+--------------------------------+
|  1 | SIMPLE      | EM1   | ALL    | NULL          | NULL    | NULL    | NULL             |    2 | Using where                    |
|  1 | SIMPLE      | EV    | eq_ref | PRIMARY       | PRIMARY | 4       | bcs.EM1.event_id |    1 |                                |
|  1 | SIMPLE      | EM2   | ALL    | NULL          | NULL    | NULL    | NULL             |    2 | Using where; Using join buffer |
+----+-------------+-------+--------+---------------+---------+---------+------------------+------+--------------------------------+

Die obige Lösung mit 1 Join benötigt keinen solchen Puffer.


"Komplexe" Muster

Sie können Unterstützung für komplexere Typen hinzufügen, um diese Arten von Wiederholungsregeln zu unterstützen:

Event A repeats every month on the 3rd of the month starting on March 3, 2011

oder

Event A repeats second Friday of the month starting on March 11, 2011

Ihre Ereignistabelle kann genau so aussehen:

ID    NAME
1     Sample Event
2     Another Event

Um die Unterstützung für diese komplexen Regeln hinzuzufügen, fügen Sie Spalten zu events_meta Wie folgt hinzu:

ID    event_id      repeat_start       repeat_interval    repeat_year    repeat_month    repeat_day    repeat_week    repeat_weekday
1     1             1369008000         604800             NULL           NULL            NULL          NULL           NULL             -- Repeats every Monday after May 20, 2013
1     1             1368144000         604800             NULL           NULL            NULL          NULL           NULL             -- Repeats every Friday after May 10, 2013
2     2             1369008000         NULL               2013           *               *             2              5                -- Repeats on Friday of the 2nd week in every month    

Beachten Sie, dass Sie einfach entweder einen repeat_interval oder einen Satz von repeat_year, repeat_month Angeben müssen. , repeat_day, repeat_week Und repeat_weekday Daten.

Dies macht die Auswahl beider Typen gleichzeitig sehr einfach. Durchlaufen Sie einfach jeden Tag und geben Sie die richtigen Werte ein (1370563200 für den 7. Juni 2013 und dann Jahr, Monat, Tag, Wochennummer und Wochentag wie folgt):

SELECT EV.*
FROM `events` EV
RIGHT JOIN `events_meta` EM1 ON EM1.`event_id` = EV.`id`
WHERE  (( 1370563200 - repeat_start) % repeat_interval = 0 )
  OR ( 
    (repeat_year = 2013 OR repeat_year = '*' )
    AND
    (repeat_month = 6 OR repeat_month = '*' )
    AND
    (repeat_day = 7 OR repeat_day = '*' )
    AND
    (repeat_week = 2 OR repeat_week = '*' )
    AND
    (repeat_weekday = 5 OR repeat_weekday = '*' )
    AND repeat_start <= 1370563200
  )

Dies gibt alle Ereignisse zurück, die sich am Freitag der 2. Woche wiederholen sowie alle Ereignisse, die sich jeden Freitag wiederholen, sodass sowohl Ereignis-ID 1 als auch Ereignis-ID 2 zurückgegeben werden 2:

ID    NAME
1     Sample Event
2     Another Event

* Nebenanmerkung in der obigen SQL Ich habe PHP Date's Standard-Wochentagsindizes verwendet, also "5" für Freitag


Hoffe, das hilft anderen so sehr, wie mir die ursprüngliche Antwort geholfen hat!

171
ahoffner

Verbesserung: Zeitstempel durch Datum ersetzen

Als kleine Verbesserung der akzeptierten Antwort, die anschließend von ahoffner verfeinert wurde, ist es möglich, ein Datumsformat anstelle eines Zeitstempels zu verwenden. Die Vorteile sind:

  1. lesbare Daten in der Datenbank
  2. kein Problem mit den Jahren> 2038 und Zeitstempel
  3. entfernungen müssen mit Zeitstempeln vorsichtig sein, die auf saisonbereinigten Daten basieren, d. h. in Großbritannien beginnt der 28. Juni eine Stunde vor dem 28. Dezember, sodass das Ableiten eines Zeitstempels von einem Datum den Rekursionsalgorithmus unterbrechen kann.

ändern Sie dazu den DB repeat_start zum Speichern als Typ 'Datum' und repeat_interval Halten Sie jetzt Tage statt Sekunden. 7 für eine Wiederholung von 7 Tagen.

ändern Sie die SQL-Zeile:

WHERE (( 1370563200 - repeat_start) % repeat_interval = 0 )

zu:

WHERE ( DATEDIFF( '2013-6-7', repeat_start ) % repeat_interval = 0)

alles andere bleibt gleich. Simples!

23
user3781087

Für alle, die sich dafür interessieren, können Sie jetzt einfach kopieren und einfügen, um innerhalb von Minuten loszulegen. Ich habe den Rat in den Kommentaren so gut ich konnte genommen. Lassen Sie mich wissen, wenn ich etwas verpasse.

"KOMPLEXE VERSION":

veranstaltungen

 + ---------- + ---------------- + 
 | ID | NAME | 
 + ---------- + ---------------- + 
 | 1 | Beispielereignis 1 | 
 | 2 | Zweites Ereignis | 
 | 3 | Dritte Veranstaltung | 
 + ---------- + ---------------- + 

events_meta

 + ---- + ---------- + -------------- + ------------- ----- + ------------- + -------------- + ------------ + - ----------- + ---------------- + 
 | ID | event_id | repeat_start | repeat_interval | repeat_year | Wiederholungsmonat | repeat_day | Wiederholungswoche | repeat_weekday | 
 + ---- + ---------- + -------------- + ----------- ------- + ------------- + -------------- + ------------ + ------------- + ---------------- + 
 | 1 | 1 | 04.07.2014 | 7 | NULL | NULL | NULL | NULL | NULL | 
 | 2 | 2 | 2014-06-26 | NULL | 2014 | * | * | 2 | 5 | 
 | 3 | 3 | 04.07.2014 | NULL | * | * | * | * | 5 | 
 + ---- + ---------- + -------------- + ----------- ------- + ------------- + -------------- + ------------ + ------------- + ---------------- + 

SQL-Code:

CREATE TABLE IF NOT EXISTS `events` (
  `ID` int(11) NOT NULL AUTO_INCREMENT,
  `NAME` varchar(255) NOT NULL,
  PRIMARY KEY (`ID`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=7 ;

--
-- Dumping data for table `events`
--

INSERT INTO `events` (`ID`, `NAME`) VALUES
(1, 'Sample event'),
(2, 'Another event'),
(3, 'Third event...');

CREATE TABLE IF NOT EXISTS `events_meta` (
  `ID` int(11) NOT NULL AUTO_INCREMENT,
  `event_id` int(11) NOT NULL,
  `repeat_start` date NOT NULL,
  `repeat_interval` varchar(255) NOT NULL,
  `repeat_year` varchar(255) NOT NULL,
  `repeat_month` varchar(255) NOT NULL,
  `repeat_day` varchar(255) NOT NULL,
  `repeat_week` varchar(255) NOT NULL,
  `repeat_weekday` varchar(255) NOT NULL,
  PRIMARY KEY (`ID`),
  UNIQUE KEY `ID` (`ID`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=6 ;

--
-- Dumping data for table `events_meta`
--

INSERT INTO `events_meta` (`ID`, `event_id`, `repeat_start`, `repeat_interval`, `repeat_year`, `repeat_month`, `repeat_day`, `repeat_week`, `repeat_weekday`) VALUES
(1, 1, '2014-07-04', '7', 'NULL', 'NULL', 'NULL', 'NULL', 'NULL'),
(2, 2, '2014-06-26', 'NULL', '2014', '*', '*', '2', '5'),
(3, 3, '2014-07-04', 'NULL', '*', '*', '*', '*', '1');

auch verfügbar als MySQL-Export (für einfachen Zugriff)

PHP-Beispielcode index.php:

<?php
    require 'connect.php';    

    $now = strtotime("yesterday");

    $pushToFirst = -11;
    for($i = $pushToFirst; $i < $pushToFirst+30; $i++)
    {
        $now = strtotime("+".$i." day");
        $year = date("Y", $now);
        $month = date("m", $now);
        $day = date("d", $now);
        $nowString = $year . "-" . $month . "-" . $day;
        $week = (int) ((date('d', $now) - 1) / 7) + 1;
        $weekday = date("N", $now);

        echo $nowString . "<br />";
        echo $week . " " . $weekday . "<br />";



        $sql = "SELECT EV.*
                FROM `events` EV
                RIGHT JOIN `events_meta` EM1 ON EM1.`event_id` = EV.`id`
                WHERE ( DATEDIFF( '$nowString', repeat_start ) % repeat_interval = 0 )
                OR ( 
                    (repeat_year = $year OR repeat_year = '*' )
                    AND
                    (repeat_month = $month OR repeat_month = '*' )
                    AND
                    (repeat_day = $day OR repeat_day = '*' )
                    AND
                    (repeat_week = $week OR repeat_week = '*' )
                    AND
                    (repeat_weekday = $weekday OR repeat_weekday = '*' )
                    AND repeat_start <= DATE('$nowString')
                )";
        foreach ($dbConnect->query($sql) as $row) {
            print $row['ID'] . "\t";
            print $row['NAME'] . "<br />";
        }

        echo "<br /><br /><br />";
    }
?>

PHP Beispielcode connect.php:

<?
// ----------------------------------------------------------------------------------------------------
//                                       Connecting to database
// ----------------------------------------------------------------------------------------------------
// Database variables
$username = "";
$password = "";
$hostname = ""; 
$database = ""; 

// Try to connect to database and set charset to UTF8
try {
    $dbConnect = new PDO("mysql:Host=$hostname;dbname=$database;charset=utf8", $username, $password);
    $dbConnect->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

} catch(PDOException $e) {
    echo 'ERROR: ' . $e->getMessage();
}
// ----------------------------------------------------------------------------------------------------
//                                      / Connecting to database
// ----------------------------------------------------------------------------------------------------
?>

Auch der PHP-Code ist hier verfügbar (zur besseren Lesbarkeit):
index.php
und
connect.php
Das Einrichten sollte nun einige Minuten dauern. Nicht Stunden. :)

23
Alex

Ich würde dieser Anleitung folgen: https://github.com/bmoeskau/Extensible/blob/master/recurrence-overview.md

Stellen Sie außerdem sicher, dass Sie das iCal-Format verwenden, um das Rad nicht neu zu erfinden, und merken Sie sich Regel 0: Speichern Sie einzelne wiederkehrende Ereignisinstanzen NICHT als Zeilen in Ihrer Datenbank!

19
Gal Bracha

Während die vorgeschlagenen Lösungen funktionierten, habe ich versucht, sie mit Vollkalender zu implementieren, und es waren über 90 Datenbankaufrufe für jede Ansicht erforderlich (da sie den aktuellen, vorherigen und nächsten Monat lädt), worüber ich nicht allzu begeistert war.

Ich fand eine Rekursionsbibliothek https://github.com/tplaner/When , in der Sie einfach die Regeln in der Datenbank speichern und eine Abfrage ausführen, um alle relevanten Regeln abzurufen.

Hoffentlich hilft das jemand anderem, da ich so viele Stunden damit verbracht habe, eine gute Lösung zu finden.

Bearbeiten: Diese Bibliothek ist für PHP

15
Tim Ramsey

Warum nicht einen ähnlichen Mechanismus wie Apache-Cron-Jobs verwenden? http://en.wikipedia.org/wiki/Cron

Für die Planung von Kalendern verwende ich leicht unterschiedliche Werte für "Bits", um Standardereignisse für die Wiederholung von Kalendern zu berücksichtigen - anstelle von [Wochentag (0 - 7), Monat (1 - 12), Tag des Monats (1 - 31), Stunde (0 - 23), Minute (0 - 59)]

- Ich würde etwas wie [Jahr (alle N Jahre wiederholen), Monat (1 - 12), Tag des Monats (1 - 31), Woche des Monats (1-5), Wochentag (0 - 7) verwenden. ]

Hoffe das hilft.

14
Vladimir

Ich habe eigens für diesen Fall eine esoterische Programmiersprache entwickelt. Das Beste daran ist, dass es schemafrei und plattformunabhängig ist. Sie müssen lediglich ein Auswahlprogramm für Ihren Zeitplan schreiben, dessen Syntax von den hier beschriebenen Regeln bestimmt wird.

https://github.com/tusharmath/sheql/wiki/Rules

Die Regeln sind erweiterbar und Sie können jede Art von Anpassung hinzufügen, die auf der Art der Wiederholungslogik basiert, die Sie ausführen möchten, ohne sich um Schema-Migrationen usw. kümmern zu müssen.

Dies ist ein völlig anderer Ansatz und kann einige eigene Nachteile haben.

4
tusharmath

Klingt sehr nach MySQL-Ereignissen, die in Systemtabellen gespeichert sind. Sie können sich die Struktur ansehen und herausfinden, welche Spalten nicht benötigt werden:

   EVENT_CATALOG: NULL
    EVENT_SCHEMA: myschema
      EVENT_NAME: e_store_ts
         DEFINER: [email protected]
      EVENT_BODY: SQL
EVENT_DEFINITION: INSERT INTO myschema.mytable VALUES (UNIX_TIMESTAMP())
      EVENT_TYPE: RECURRING
      EXECUTE_AT: NULL
  INTERVAL_VALUE: 5
  INTERVAL_FIELD: SECOND
        SQL_MODE: NULL
          STARTS: 0000-00-00 00:00:00
            ENDS: 0000-00-00 00:00:00
          STATUS: ENABLED
   ON_COMPLETION: NOT PRESERVE
         CREATED: 2006-02-09 22:36:06
    LAST_ALTERED: 2006-02-09 22:36:06
   LAST_EXECUTED: NULL
   EVENT_COMMENT:
4
Valentin Kuzub

@ Rogue Coder

Das ist toll!

Sie können einfach die Modulo-Operation (MOD oder% in MySQL) verwenden, um Ihren Code am Ende einfach zu machen:

Anstatt von:

AND (
    ( CASE ( 1299132000 - EM1.`meta_value` )
        WHEN 0
          THEN 1
        ELSE ( 1299132000 - EM1.`meta_value` )
      END
    ) / EM2.`meta_value`
) = 1

Machen:

$current_timestamp = 1299132000 ;

AND ( ('$current_timestamp' - EM1.`meta_value` ) MOD EM2.`meta_value`) = 1")

Um dies weiter zu führen, könnte man Ereignisse einbeziehen, die nicht für immer wiederkehren.

So etwas wie "repeat_interval_1_end", um das Datum des letzten "repeat_interval_1" anzugeben, könnte hinzugefügt werden. Dies macht die Abfrage jedoch komplizierter und ich kann nicht wirklich herausfinden, wie dies zu tun ist ...

Vielleicht könnte jemand helfen!

3
dorogz

Die beiden Beispiele, die Sie angegeben haben, sind sehr einfach. Sie können als einfaches Intervall dargestellt werden (das erste sind vier Tage, das zweite sind 14 Tage). Wie Sie dies modellieren, hängt ganz von der Komplexität Ihrer Wiederholungen ab. Wenn das, was Sie oben haben, wirklich so einfach ist, dann speichern Sie ein Startdatum und die Anzahl der Tage im Wiederholungsintervall.

Wenn Sie jedoch Dinge wie unterstützen müssen

Ereignis A wird jeden Monat am 3. des Monats ab dem 3. März 2011 wiederholt

Oder

Ereignis A wird am zweiten Freitag des Monats ab dem 11. März 2011 wiederholt

Dann ist das ein viel komplexeres Muster.

1
Adam Robinson