Skip to content

Commit

Permalink
Merge pull request #505 from ical4j/develop
Browse files Browse the repository at this point in the history
Support for non-Gregorian recurrences
  • Loading branch information
benfortuna committed Jul 6, 2021
2 parents 85e8539 + 3703831 commit 376e044
Show file tree
Hide file tree
Showing 19 changed files with 616 additions and 72 deletions.
4 changes: 3 additions & 1 deletion README.md
Expand Up @@ -10,6 +10,7 @@
[RFC6868]: https://datatracker.ietf.org/doc/html/rfc6868
[RFC7953]: https://datatracker.ietf.org/doc/html/rfc7953
[RFC7986]: https://datatracker.ietf.org/doc/html/rfc7986
[RFC7529]: https://datatracker.ietf.org/doc/html/rfc7529

[Bintray Releases]: https://bintray.com/ical4j/maven/ical4j

Expand Down Expand Up @@ -48,7 +49,7 @@
- [Install with Gradle]
3. [Usage - The iCal4j object model and how to use it][Usage]
- [Examples - common usage scenarios][Examples]
4. [References][Reference]
4. [References][References]
5. [Configuration options][Configuration]
- [Compatibility Hints]
6. [Limitations - CUA compatibility, etc.][Limitations]
Expand Down Expand Up @@ -130,6 +131,7 @@ is used only to generate version information in the javadoc API documentation.
* [RFC6868] - Parameter Value Encoding in iCalendar and vCard
* [RFC7953] - Calendar Availability
* [RFC7986] - New Properties for iCalendar
* [RFC7529] - Non-Gregorian Recurrence Rules in iCalendar

## Configuration

Expand Down
3 changes: 3 additions & 0 deletions build.gradle
Expand Up @@ -48,6 +48,9 @@ dependencies {
// optional timezone caching..
implementation 'javax.cache:cache-api:1.1.1', optional

// optional support for non-Gregorian chronologies..
implementation "org.threeten:threeten-extra:$threetenExtraVersion", optional

// optional groovy DSL for calendar builder..
implementation "org.codehaus.groovy:groovy:$groovyVersion", optional
implementation "org.codehaus.groovy:groovy-dateutil:$groovyVersion", optional
Expand Down
64 changes: 64 additions & 0 deletions src/main/java/net/fortuna/ical4j/model/Month.java
@@ -0,0 +1,64 @@
package net.fortuna.ical4j.model;

import java.io.Serializable;
import java.util.Objects;

/**
* Defines a month of the year, which may be a leap-month in some calendaring systems.
*/
public class Month implements Serializable {

private final int monthOfYear;

private final boolean leapMonth;

public Month(int monthOfYear) {
this(monthOfYear, false);
}

public Month(int monthOfYear, boolean leapMonth) {
this.monthOfYear = monthOfYear;
this.leapMonth = leapMonth;
}

public int getMonthOfYear() {
return monthOfYear;
}

public boolean isLeapMonth() {
return leapMonth;
}

public static Month parse(String monthString) {
if (monthString.endsWith("L")) {
return new Month(Integer.parseInt(monthString.substring(0, monthString.length()-1)), true);
}
return new Month(Integer.parseInt(monthString));
}

public static Month valueOf(int monthOfYear) {
return new Month(monthOfYear);
}

@Override
public String toString() {
if (leapMonth) {
return monthOfYear + "L";
} else {
return String.valueOf(monthOfYear);
}
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Month month = (Month) o;
return monthOfYear == month.monthOfYear && leapMonth == month.leapMonth;
}

@Override
public int hashCode() {
return Objects.hash(monthOfYear, leapMonth);
}
}
56 changes: 56 additions & 0 deletions src/main/java/net/fortuna/ical4j/model/MonthList.java
@@ -0,0 +1,56 @@
package net.fortuna.ical4j.model;

import java.time.temporal.ChronoField;
import java.time.temporal.ValueRange;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Optional;
import java.util.stream.Collectors;

public class MonthList extends ArrayList<Month> {

private final ValueRange valueRange;

public MonthList() {
this(ChronoField.MONTH_OF_YEAR.range());
}

public MonthList(ValueRange range) {
this.valueRange = range;
}

public MonthList(String aString) {
this(aString, ChronoField.MONTH_OF_YEAR.range());
}

public MonthList(String aString, ValueRange valueRange) {
this(valueRange);
addAll(Arrays.stream(aString.split(",")).map(Month::parse).collect(Collectors.toList()));
}

@Override
public final boolean add(final Month month) {
if (!valueRange.isValidValue(month.getMonthOfYear())) {
throw new IllegalArgumentException(
"Value not in range [" + valueRange + "]: " + month);
}
return super.add(month);
}

@Override
public boolean addAll(Collection<? extends Month> c) {
Optional<? extends Month> invalidMonth = c.stream().filter(m -> !valueRange.isValidValue(m.getMonthOfYear()))
.findFirst();
if (invalidMonth.isPresent()) {
throw new IllegalArgumentException(
"Value not in range [" + valueRange + "]: " + invalidMonth);
}
return super.addAll(c);
}

@Override
public final String toString() {
return stream().map(Object::toString).collect(Collectors.joining(","));
}
}
67 changes: 53 additions & 14 deletions src/main/java/net/fortuna/ical4j/model/NumberList.java
Expand Up @@ -34,8 +34,8 @@
import net.fortuna.ical4j.util.Numbers;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.StringTokenizer;
import java.time.temporal.ValueRange;
import java.util.*;
import java.util.stream.Collectors;

/**
Expand All @@ -49,45 +49,68 @@ public class NumberList extends ArrayList<Integer> implements Serializable {

private static final long serialVersionUID = -1667481795613729889L;

private final int minValue;

private final int maxValue;
private final ValueRange valueRange;

private final boolean allowsNegativeValues;

/**
* Default constructor.
*/
public NumberList() {
this(Integer.MIN_VALUE, Integer.MAX_VALUE, true);
this(ValueRange.of(Integer.MIN_VALUE, Integer.MAX_VALUE), true);
}

/**
* Construct a number list restricted by the specified {@link ValueRange}.
* @param valueRange a range defining the lower and upper bounds of allowed values
* @param allowsNegativeValues allow negative values, where abs(value) is within the specified range
*/
public NumberList(ValueRange valueRange, boolean allowsNegativeValues) {
this.valueRange = valueRange;
this.allowsNegativeValues = allowsNegativeValues;
}

/**
* Constructor with limits.
* @param minValue the minimum allowable value
* @param maxValue the maximum allowable value
* @param allowsNegativeValues indicates whether negative values are allowed
*
* @deprecated use {@link NumberList#NumberList(ValueRange, boolean)}
*/
@Deprecated
public NumberList(int minValue, int maxValue, boolean allowsNegativeValues) {
this.minValue = minValue;
this.maxValue = maxValue;
this.allowsNegativeValues = allowsNegativeValues;
this(ValueRange.of(minValue, maxValue), allowsNegativeValues);
}

/**
* Constructor.
* @param aString a string representation of a number list
*/
public NumberList(final String aString) {
this(aString, Integer.MIN_VALUE, Integer.MAX_VALUE, true);
this(aString, ValueRange.of(Integer.MIN_VALUE, Integer.MAX_VALUE), true);
}


/**
* Construct a number list restricted by the specified {@link ValueRange}.
* @param aString a string representation of a list of values
* @param valueRange a range defining the lower and upper bounds of allowed values
* @param allowsNegativeValues allow negative values, where abs(value) is within the specified range
*/
public NumberList(final String aString, ValueRange valueRange, boolean allowsNegativeValues) {
this(valueRange, allowsNegativeValues);
addAll(Arrays.stream(aString.split(",")).map(Numbers::parseInt).collect(Collectors.toList()));
}

/**
* @param aString a string representation of a number list
* @param minValue the minimum allowable value
* @param maxValue the maximum allowable value
* @param allowsNegativeValues indicates whether negative values are allowed
*
* @deprecated use {@link NumberList#NumberList(String, ValueRange, boolean)}
*/
@Deprecated
public NumberList(final String aString, int minValue, int maxValue, boolean allowsNegativeValues) {
this(minValue, maxValue, allowsNegativeValues);
final StringTokenizer t = new StringTokenizer(aString, ",");
Expand All @@ -110,13 +133,29 @@ public final boolean add(final Integer aNumber) {
}
abs = Math.abs(abs);
}
if (abs < minValue || abs > maxValue) {
throw new IllegalArgumentException(
"Value not in range [" + minValue + ".." + maxValue + "]: " + aNumber);
if (!valueRange.isValidIntValue(abs)) {
throw new IllegalArgumentException("Value not in range [" + valueRange + "]: " + aNumber);
}
return super.add(aNumber);
}

@Override
public boolean addAll(Collection<? extends Integer> c) {
Optional<? extends Integer> negativeValue = c.stream().filter(v -> (v >> 31 | -v >>> 31) < 0)
.findFirst();
if (!allowsNegativeValues && negativeValue.isPresent()) {
throw new IllegalArgumentException("Negative value not allowed: " + negativeValue.get());
}

Optional<? extends Integer> invalidValue = c.stream().filter(v -> !valueRange.isValidValue(Math.abs(v)))
.findFirst();
if (invalidValue.isPresent()) {
throw new IllegalArgumentException(
"Value not in range [" + valueRange + "]: " + invalidValue);
}
return super.addAll(c);
}

/**
* {@inheritDoc}
*/
Expand Down

0 comments on commit 376e044

Please sign in to comment.