All Downloads are FREE. Search and download functionalities are using the official Maven repository.

de.svws_nrw.davapi.util.icalendar.recurrence.RRule Maven / Gradle / Ivy

Go to download

Diese Bibliothek enthält die Java-Server-Definition der CalDAV und CardDAV-Schnittstelle für die Schulverwaltungssoftware in NRW

The newest version!
package de.svws_nrw.davapi.util.icalendar.recurrence;

import java.time.Instant;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import de.svws_nrw.davapi.util.icalendar.DateTimeUtil;
import de.svws_nrw.davapi.util.icalendar.IProperty;
import de.svws_nrw.davapi.util.icalendar.PropertyKeys;
import de.svws_nrw.davapi.util.icalendar.VCalendar;

/**
 * Diese Klasse repräsentiert eine Regel für wiederkehrende Ereignisse gemäß
 * RFC
 * 5545 
* Description: This value type is a structured value consisting of a
list of one or more recurrence grammar parts. Each rule part is
defined by a NAME=VALUE pair. The rule parts are separated from
each other by the SEMICOLON character. The rule parts are not
ordered in any particular sequence. Individual rule parts MUST
only be specified once. Compliant applications MUST accept rule
parts ordered in any sequence, but to ensure backward
compatibility with applications that pre-date this revision of
iCalendar the FREQ rule part MUST be the first rule part specified
in a RECUR value.

The FREQ rule part identifies the type of recurrence rule. This
rule part MUST be specified in the recurrence rule. Valid values
include SECONDLY, to specify repeating events based on an interval
of a second or more; MINUTELY, to specify repeating events based
on an interval of a minute or more; HOURLY, to specify repeating
events based on an interval of an hour or more; DAILY, to specify
repeating events based on an interval of a day or more; WEEKLY, to
specify repeating events based on an interval of a week or more;
MONTHLY, to specify repeating events based on an interval of a
month or more; and YEARLY, to specify repeating events based on an
interval of a year or more.

The INTERVAL rule part contains a positive integer representing at
which intervals the recurrence rule repeats. The default value is
"1", meaning every second for a SECONDLY rule, every minute for a
MINUTELY rule, every hour for an HOURLY rule, every day for a
DAILY rule, every week for a WEEKLY rule, every month for a
MONTHLY rule, and every year for a YEARLY rule. For example,
within a DAILY rule, a value of "8" means every eight days.

The BYSECOND rule part specifies a COMMA-separated list of seconds
within a minute. Valid values are 0 to 60. The BYMINUTE rule
part specifies a COMMA-separated list of minutes within an hour.
Valid values are 0 to 59. The BYHOUR rule part specifies a COMMA-
separated list of hours of the day. Valid values are 0 to 23.
The BYSECOND, BYMINUTE and BYHOUR rule parts MUST NOT be specified
when the associated "DTSTART" property has a DATE value type.
These rule parts MUST be ignored in RECUR value that violate the
above requirement (e.g., generated by applications that pre-date
this revision of iCalendar).

The BYDAY rule part specifies a COMMA-separated list of days of
the week; SU indicates Sunday; MO indicates Monday; TU indicates
Tuesday; WE indicates Wednesday; TH indicates Thursday; FR
indicates Friday; and SA indicates Saturday.

Each BYDAY value can also be preceded by a positive (+n) or
negative (-n) integer. If present, this indicates the nth
occurrence of a specific day within the MONTHLY or YEARLY "RRULE".

For example, within a MONTHLY rule, +1MO (or simply 1MO)
represents the first Monday within the month, whereas -1MO
represents the last Monday of the month. The numeric value in a
BYDAY rule part with the FREQ rule part set to YEARLY corresponds
to an offset within the month when the BYMONTH rule part is
present, and corresponds to an offset within the year when the
BYWEEKNO or BYMONTH rule parts are present. If an integer
modifier is not present, it means all days of this type within the
specified frequency. For example, within a MONTHLY rule, MO
represents all Mondays within the month. The BYDAY rule part MUST
NOT be specified with a numeric value when the FREQ rule part is
not set to MONTHLY or YEARLY. Furthermore, the BYDAY rule part
MUST NOT be specified with a numeric value with the FREQ rule part
set to YEARLY when the BYWEEKNO rule part is specified.

The BYMONTHDAY rule part specifies a COMMA-separated list of days
of the month. Valid values are 1 to 31 or -31 to -1. For
example, -10 represents the tenth to the last day of the month.
The BYMONTHDAY rule part MUST NOT be specified when the FREQ rule
part is set to WEEKLY.

The BYYEARDAY rule part specifies a COMMA-separated list of days
of the year. Valid values are 1 to 366 or -366 to -1. For
example, -1 represents the last day of the year (December 31st)
and -306 represents the 306th to the last day of the year (March
1st). The BYYEARDAY rule part MUST NOT be specified when the FREQ
rule part is set to DAILY, WEEKLY, or MONTHLY.

The BYWEEKNO rule part specifies a COMMA-separated list of
ordinals specifying weeks of the year. Valid values are 1 to 53
or -53 to -1. This corresponds to weeks according to week
numbering as defined in [ISO.8601.2004]. A week is defined as a
seven day period, starting on the day of the week defined to be
the week start (see WKST). Week number one of the calendar year
is the first week that contains at least four (4) days in that
calendar year. This rule part MUST NOT be used when the FREQ rule
part is set to anything other than YEARLY. For example, 3
represents the third week of the year.

Note: Assuming a Monday week start, week 53 can only occur when
Thursday is January 1 or if it is a leap year and Wednesday is
January 1.

The BYMONTH rule part specifies a COMMA-separated list of months of the year. Valid values are 1 to 12.
The WKST rule part specifies the day on which the workweek starts.
Valid values are MO, TU, WE, TH, FR, SA, and SU. This is
significant when a WEEKLY "RRULE" has an interval greater than 1,
and a BYDAY rule part is specified. This is also significant when
in a YEARLY "RRULE" when a BYWEEKNO rule part is specified. The
default value is MO.

The BYSETPOS rule part specifies a COMMA-separated list of values
that corresponds to the nth occurrence within the set of
recurrence instances specified by the rule. BYSETPOS operates on
a set of recurrence instances in one interval of the recurrence
rule. For example, in a WEEKLY rule, the interval would be one
week A set of recurrence instances starts at the beginning of the
interval defined by the FREQ rule part. Valid values are 1 to 366
or -366 to -1. It MUST only be used in conjunction with another
BYxxx rule part. For example "the last work day of the month"
could be represented as:

FREQ=MONTHLY;BYDAY=MO,TU,WE,TH,FR;BYSETPOS=-1

Each BYSETPOS value can include a positive (+n) or negative (-n)
integer. If present, this indicates the nth occurrence of the
specific occurrence within the set of occurrences specified by the
rule.

Recurrence rules may generate recurrence instances with an invalid
date (e.g., February 30) or nonexistent local time (e.g., 1:30 AM
on a day where the local time is moved forward by an hour at 1:00
AM). Such recurrence instances MUST be ignored and MUST NOT be
counted as part of the recurrence set.

Information, not contained in the rule, necessary to determine the
various recurrence instance start time and dates are derived from
the Start Time ("DTSTART") component attribute. For example,
"FREQ=YEARLY;BYMONTH=1" doesn't specify a specific day within the
month or a time. This information would be the same as what is
specified for "DTSTART".

BYxxx rule parts modify the recurrence in some manner. BYxxx rule
parts for a period of time that is the same or greater than the
frequency generally reduce or limit the number of occurrences of
the recurrence generated. For example, "FREQ=DAILY;BYMONTH=1"
reduces the number of recurrence instances from all days (if
BYMONTH rule part is not present) to all days in January. BYxxx
rule parts for a period of time less than the frequency generally
increase or expand the number of occurrences of the recurrence.
For example, "FREQ=YEARLY;BYMONTH=1,2" increases the number of
days within the yearly recurrence set from 1 (if BYMONTH rule part
is not present) to 2.

If multiple BYxxx rule parts are specified, then after evaluating
the specified FREQ and INTERVAL rule parts, the BYxxx rule parts
are applied to the current set of evaluated occurrences in the
following order: BYMONTH, BYWEEKNO, BYYEARDAY, BYMONTHDAY, BYDAY,
BYHOUR, BYMINUTE, BYSECOND and BYSETPOS; then COUNT and UNTIL are
evaluated.

The table below summarizes the dependency of BYxxx rule part
expand or limit behavior on the FREQ rule part value.

The term "N/A" means that the corresponding BYxxx rule part MUST
NOT be used with the corresponding FREQ value.

BYDAY has some special behavior depending on the FREQ value and
this is described in separate notes below the table.

+----------+--------+--------+-------+-------+------+-------+------+
|..........|SECONDLY|MINUTELY|HOURLY.|DAILY..|WEEKLY|MONTHLY|YEARLY|
+----------+--------+--------+-------+-------+------+-------+------+
|BYMONTH...|Limit...|Limit...|Limit..|Limit..|Limit.|Limit..|Expand|
+----------+--------+--------+-------+-------+------+-------+------+
|BYWEEKNO..|N/A.....|N/A.....|N/A....|N/A....|N/A...|N/A....|Expand|
+----------+--------+--------+-------+-------+------+-------+------+
|BYYEARDAY.|Limit...|Limit...|Limit..|N/A....|N/A...|N/A....|Expand|
+----------+--------+--------+-------+-------+------+-------+------+
|BYMONTHDAY|Limit...|Limit...|Limit..|Limit..|N/A...|Expand.|Expand|
+----------+--------+--------+-------+-------+------+-------+------+
|BYDAY.....|Limit...|Limit...|Limit..|Limit..|Expand|Note.1.|Note.2|
+----------+--------+--------+-------+-------+------+-------+------+
|BYHOUR....|Limit...|Limit...|Limit..|Expand.|Expand|Expand.|Expand|
+----------+--------+--------+-------+-------+------+-------+------+
|BYMINUTE..|Limit...|Limit...|Expand.|Expand.|Expand|Expand.|Expand|
+----------+--------+--------+-------+-------+------+-------+------+
|BYSECOND..|Limit...|Expand..|Expand.|Expand.|Expand|Expand.|Expand|
+----------+--------+--------+-------+-------+------+-------+------+
|BYSETPOS..|Limit...|Limit...|Limit..|Limit..|Limit.|Limit..|Limit.|
+----------+--------+--------+-------+-------+------+-------+------+

Note 1: Limit if BYMONTHDAY is present; otherwise, special expand
for MONTHLY.

Note 2: Limit if BYYEARDAY or BYMONTHDAY is present; otherwise,
special expand for WEEKLY if BYWEEKNO present; otherwise,
special expand for MONTHLY if BYMONTH present; otherwise,
special expand for YEARLY.
*
*/ public final class RRule implements IProperty { /** Property Keyword für BYDAY */ private static final String BYDAY_KEY = "BYDAY"; /** Property KEyword für INTERVAL */ private static final String INTERVAL_KEY = "INTERVAL"; /** Die Frequenz, mit der das wiederkehrende Ereignis auftritt */ private Frequency freq; /** * Ein Limit für Wiederholungen oder einen Zeitpunkt, bis zu dem Wiederholungen * nach den definierten Regeln auftreten. */ private RecurrencyLimit limit; /** * Das Intervall mit dem die Wiederholungen innerhalb der gegebenen * {@link Frequency} auftreten */ private int interval = 1; /** Die Liste der Sekunden, zu denen ein Ereignis auftreten soll */ private final Set bySeconds = new HashSet<>(); /** Die Liste der Minuten, zu denen ein Ereignis auftreten soll */ private final Set byMinutes = new HashSet<>(); /** Die Liste der Stunden, zu denen ein Ereignis auftreten soll */ private final Set byHours = new HashSet<>(); /** Die Liste der Wochentage, zu denen ein Ereignis auftreten soll */ private final Set byDays = new HashSet<>(); /** * Die Liste der Tage eines Monats, zu denen ein Ereignis auftreten soll. * Negative Integer geben den nt-letzten Tag des Monats an */ private final Set byMonthDays = new HashSet<>(); /** * Die Liste der Tage eines Jahres, zu denen ein Ereignis auftreten soll. * Negative Integer geben den nt-letzten Tag des Jahres an. */ private final Set byYearDays = new HashSet<>(); /** * Die Liste der Wochen eines Jahres, zu denen ein Ereignis auftreten soll. * Negative Integer geben die nt-letzte Woche des Jahres an. */ private final Set byWeekNumbers = new HashSet<>(); /** * Die Liste der Monate eines Jahres, zu denen ein Ereignis auftreten soll. * Negative Integer geben den nt-letzte Monat des Jahres an. */ private final Set byMonths = new HashSet<>(); /** Die Liste der Positionen innerhalb einer angegebenen Frequenz. */ private final Set bySetPos = new HashSet<>(); /** Der Wochentag mit dem die Woche starten soll */ private WeekDay weekStart = WeekDay.MONDAY; /** * Öffentlicher Konstruktor für die Recurrence Rule mit gegebener Frequenz. * * @param freq Die Frequenz, mit der das wiederkehrende Ereignis auftritt */ public RRule(final Frequency freq) { this.freq = freq; } /** * getter für die Frequenz, mit der das wiederkehrende Ereignis auftritt * * @return Die Frequenz, mit der das wiederkehrende Ereignis auftritt */ public Frequency getFreq() { return freq; } /** * setter für die Frequenz, mit der das wiederkehrende Ereignis auftritt * * @param freq Die Frequenz, mit der das wiederkehrende Ereignis auftritt */ public void setFreq(final Frequency freq) { this.freq = freq; } /** * getter für das Limit für Wiederholungen * * @return das Limit für Wiederholungen */ public RecurrencyLimit getLimit() { return limit; } /** * setter für das Limit für Wiederholungen * * @param limit das Limit für Wiederholungen */ public void setLimit(final RecurrencyLimit limit) { this.limit = limit; } /** * getter für das Intervall mit dem die Wiederholungen innerhalb der gegebenen * Frequenz auftreten * * @return das Intervall mit dem die Wiederholungen innerhalb der gegebenen * Frequenz auftreten */ public int getInterval() { return interval; } /** * setter für das Intervall mit dem die Wiederholungen innerhalb der gegebenen * Frequenz auftreten * * @param interval das Intervall mit dem die Wiederholungen innerhalb der * gegebenen Frequenz auftreten */ public void setInterval(final int interval) { this.interval = interval; } /** * getter für den Wochentag, mit dem die Woche starten soll * * @return der Wochentag, mit dem die Woche starten soll */ public WeekDay getWeekStart() { return weekStart; } /** * setter für den Wochentag mit dem die Woche starten soll * * @param weekStart der Wochentag mit dem die Woche starten soll */ public void setWeekStart(final WeekDay weekStart) { this.weekStart = weekStart; } /** * getter für die Liste der Sekunden, zu denen ein Ereignis stattfinden soll. * Valide Werte sind von 0 bis 59. * * @return die Liste der Sekunden, zu denen ein Ereignis stattfinden soll */ public Set getBySeconds() { return bySeconds; } /** * getter für die Liste der Minuten, zu denen ein Ereignis stattfinden soll. * Valide Werte sind von 0 bis 59. * * @return die Liste der Minuten, zu denen ein Ereignis stattfinden soll */ public Set getByMinutes() { return byMinutes; } /** * getter für die Liste der Stunden, zu denen ein Ereignis stattfinden soll. * Valide Werte sind von 0 bis 23. * * @return die Liste der Stunden, zu denen ein Ereignis stattfinden soll */ public Set getByHours() { return byHours; } /** * getter für die Liste der Wochentage, zu denen ein Ereignis stattfinden soll * * @return die Liste der Wochentage, zu denen ein Ereignis stattfinden soll */ public Set getByDays() { return byDays; } /** * getter für die Liste der Tage im Monat, zu denen ein Ereignis stattfinden * soll. Valide Werte sind zwischen -31 und -1 sowie zwischen 1 und 31. Negative * Werte geben den nt-letzten Wert des Monats an. * * @return die Liste der Tage im Monat, zu denen ein Ereignis stattfinden soll */ public Set getByMonthDays() { return byMonthDays; } /** * getter für die Liste der Tage im Jahr, zu denen ein Ereignis stattfinden * soll. Valide Werte sind zwischen -366 und -1 sowie 1 und 366. Negative Werte * geben den nt-letzten Tag des Jahres an. * * @return die Liste der Tage im Jahr, zu denen ein Ereignis stattfinden soll */ public Set getByYearDays() { return byYearDays; } /** * getter für die Liste der Wochen eines Jahres, in denen ein Ereignis * stattfinden soll.
* The BYWEEKNO rule part specifies a COMMA-separated list of
* ordinals specifying weeks of the year. Valid values are 1 to 53
* or -53 to -1. * * @return die Liste der Wochen eines Jahres, in denen ein Ereignis stattfinden * soll. */ public Set getByWeekNumbers() { return byWeekNumbers; } /** * getter für die Liste der Monate, in denen ein Ereignis stattfinden soll. * Valid values are 1 to 12. * * @return die Liste der Monate, in denen ein Ereignis stattfinden soll. */ public Set getByMonths() { return byMonths; } /** * getter für die Liste der Einschränkungen der Vorkommen innerhalb einer * angegebenen Frequenz. Diese Werte schränken abhängig von der Frequenz und der * gegebenen Regel das nte Auftreten der aus der Regel auftretenden Ereignisse * innerhalb der gegebenen Frequenz an. Negative Werte geben das n-letzte * Vorkommen an.
* Valid values are 1 to 366 or -366 to -1.
* Beispiel: FREQ=MONTHLY;BYDAY=MO,TU,WE,TH,FR;BYSETPOS=-1 als * Regel gibt monatlich wiederholende Termine an Werktagen an. Durch BYSETPOS * werden diese Termine auf den letzten Werktag eines Monats eingeschränkt. * * @return die Liste der Einschränkungen der Vorkommen innerhalb einer * angegebenen Frequenz. */ public Set getBySetPos() { return bySetPos; } @Override public String getKey() { return PropertyKeys.RRULE.name(); } @Override public String getValue() { final StringBuilder sb = new StringBuilder(); append(sb, "FREQ", this.freq.toString()); if (this.interval != 1) { sb.append(";"); append(sb, INTERVAL_KEY, String.valueOf(interval)); } if (this.limit != null) { sb.append(";"); this.limit.append(sb); } append(sb, "BYSECOND", this.bySeconds); append(sb, "BYMINUTE", this.byMinutes); append(sb, "BYHOUR", this.byHours); if (!this.byDays.isEmpty()) { sb.append(";"); append(sb, BYDAY_KEY, this.byDays.stream().sorted().map(ByDay::toString)); } append(sb, "BYMONTHDAY", this.byMonthDays); append(sb, "BYYEARDAY", this.byYearDays); append(sb, "BYWEEKNO", this.byWeekNumbers); append(sb, "BYMONTH", this.byMonths); append(sb, "BYSETPOS", this.bySetPos); if (this.weekStart != WeekDay.MONDAY) { sb.append(";"); append(sb, "WKST", this.weekStart.getStringRep()); } return sb.toString(); } /** * Hilfsmethode: Fügt einem StringBuilder den key und das zugehörige Set von * Integern als sortierte, kommagetrennte Werte hinzu nach dem Muster * ;KEY=LIST. Greift dazu auf * {@link #append(StringBuilder, String, Stream)} zurück. Ist das gegebene Set * nicht leer, wird ein führendes Semikolon zugefügt. * * @param sb der Stringbuilder * @param key der Schlüsselwert des Key-Value-Paars * @param ints das Set mit Integern, welches den Value des Key-Value-Paars * darstellen */ private static void append(final StringBuilder sb, final String key, final Set ints) { if (!ints.isEmpty()) { sb.append(";"); append(sb, key, ints.stream().sorted().map(String::valueOf)); } } /** * Hilfsmethode: Fügt einem StringBuilder den Key und die kommagetrennten Werte * des Streams nach dem Muster KEY=VALUE1,VALUE2... hinzu. Greift * auf {@link #append(StringBuilder, String, String)} zurück * * @param sb der StringBuilder * @param key der Schlüsselwert des Key-Value-Paars * @param stream ein Stream mit den Werten des Key-Value-Paars */ private static void append(final StringBuilder sb, final String key, final Stream stream) { append(sb, key, stream.collect(Collectors.joining(","))); } /** * Hilfsmethode: Fügt einem StringBuilder den Key und den Value nach dem Muster * KEY=VALUE hinzu. * * @param sb * @param key * @param value */ private static void append(final StringBuilder sb, final String key, final String value) { sb.append(key); sb.append("="); sb.append(value); } @Override public void serialize(final StringBuilder sb) { sb.append(getKey()); sb.append(COLON_CHAR); sb.append(getValue()); sb.append(VCalendar.LINEBREAK); } /** * Erstellt ein RRule aus einem gegebenen PropertyString wie er in einem .ics * geschrieben steht * * @param pProp das Property als String * @return das geparste RRule Objekt welches diesen String repräsentiert */ public static RRule fromString(final String pProp) { String prop = pProp; if (prop.endsWith(VCalendar.LINEBREAK)) { prop = pProp.replace(VCalendar.LINEBREAK, ""); } final String rule = prop.substring(prop.indexOf(COLON_CHAR) + 1); final String[] rules = rule.split(";"); int idxOfComp = -1; final Map keyValueMap = new HashMap<>(); for (final String s : rules) { idxOfComp = s.indexOf('='); if (idxOfComp != -1) { keyValueMap.put(s.substring(0, idxOfComp), s.substring(idxOfComp + 1)); } } final Frequency frequency = Frequency.valueOf(keyValueMap.get("FREQ")); final RRule r = new RRule(frequency); if (keyValueMap.containsKey(INTERVAL_KEY)) { r.setInterval(Integer.valueOf(keyValueMap.get(INTERVAL_KEY))); } if (keyValueMap.containsKey("COUNT")) { r.setLimit(new RecurrencyLimit(Integer.valueOf(keyValueMap.get("COUNT")))); } if (keyValueMap.containsKey("UNTIL")) { r.setLimit(new RecurrencyLimit(DateTimeUtil.parseCalDav(keyValueMap.get("UNTIL")))); } addIntegersToSetIfKeyContained("BYSECOND", keyValueMap, r.getBySeconds()); addIntegersToSetIfKeyContained("BYMINUTE", keyValueMap, r.getByMinutes()); addIntegersToSetIfKeyContained("BYHOUR", keyValueMap, r.getByHours()); if (keyValueMap.containsKey(BYDAY_KEY)) { final String[] values = keyValueMap.get(BYDAY_KEY).split(","); r.getByDays().addAll(Arrays.asList(values).stream().map(ByDay::fromString).toList()); } addIntegersToSetIfKeyContained("BYMONTHDAY", keyValueMap, r.getByMonthDays()); addIntegersToSetIfKeyContained("BYYEARDAY", keyValueMap, r.getByYearDays()); addIntegersToSetIfKeyContained("BYWEEKNO", keyValueMap, r.getByWeekNumbers()); addIntegersToSetIfKeyContained("BYMONTH", keyValueMap, r.getByMonths()); addIntegersToSetIfKeyContained("BYSETPOS", keyValueMap, r.getBySetPos()); if (keyValueMap.containsKey("WKST")) { r.setWeekStart(WeekDay.fromStringRep(keyValueMap.get("WKST"))); } return r; } /** * Hilfsmethode, welche die den zum Key passenden Value der KeyValueMap zu einem * Set zufügt. Erwartet wird eine kommagetrennte Liste von Integern. Prüft ob * die Map den Key enthält * * @param key der Key für die KeyValueMap * @param keyValueMap die KeyValueMap, welche dem Key zugeordnete Zeichenkette * mit kommagetrennten Integern enthält * @param set das set, dem die Integer zugefügt werden sollen */ private static void addIntegersToSetIfKeyContained(final String key, final Map keyValueMap, final Set set) { if (keyValueMap.containsKey(key)) { final String valueStr = keyValueMap.get(key); final String[] values = valueStr.split(","); set.addAll(Arrays.asList(values).stream().map(Integer::valueOf).toList()); } } /** * Gibt das Maximale Datum wieder, wenn dieser Regelsatz mit dem gegebenen * Startpunkt berechnet wird. * * @param dtStart der Startzeitpunkt * @return das maximale Datum entsprechend der hier definierten Regeln. */ public Instant getMaxInstant(final Instant dtStart) { if ((this.limit == null) || (this.limit.getUntil() != null)) { return Instant.MAX; // cutoff, Recurrence Rules auswerten nicht funktional. } return this.limit.getUntil(); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy