diff --git a/src/main/java/net/fortuna/ical4j/model/Component.java b/src/main/java/net/fortuna/ical4j/model/Component.java index 69c873287..ff88d91db 100644 --- a/src/main/java/net/fortuna/ical4j/model/Component.java +++ b/src/main/java/net/fortuna/ical4j/model/Component.java @@ -42,6 +42,7 @@ import java.io.Serializable; import java.net.URISyntaxException; import java.text.ParseException; +import java.time.temporal.Temporal; import java.time.temporal.TemporalAmount; import java.util.List; import java.util.stream.Collectors; @@ -322,7 +323,7 @@ public final PeriodList calculateRecurrenceSet(final Period period) { if (end == null && duration == null) { rDuration = java.time.Duration.ZERO; } else if (duration == null) { - rDuration = TemporalAmountAdapter.fromDateRange(start.getDate(), end.getDate()).getDuration(); + rDuration = TemporalAmountAdapter.from(start.getDate(), end.getDate()).getDuration(); } else { rDuration = duration.getDuration(); } @@ -330,34 +331,32 @@ public final PeriodList calculateRecurrenceSet(final Period period) { // add recurrence dates.. List rDates = getProperties(Property.RDATE); recurrenceSet.addAll(rDates.stream().filter(p -> p.getParameter(Parameter.VALUE) == Value.PERIOD) - .map(p -> p.getPeriods()).flatMap(PeriodList::stream).filter(rdatePeriod -> period.intersects(rdatePeriod)) + .map(RDate::getPeriods).flatMap(List::stream).filter(rdatePeriod -> period.intersects(rdatePeriod)) .collect(Collectors.toList())); recurrenceSet.addAll(rDates.stream().filter(p -> p.getParameter(Parameter.VALUE) == Value.DATE_TIME) - .map(p -> p.getDates()).flatMap(DateList::stream).filter(date -> period.includes(date)) - .map(rdateTime -> new Period((DateTime) rdateTime, rDuration)).collect(Collectors.toList())); + .map(DateListProperty::getDates).flatMap(List::stream).filter(date -> period.includes(date)) + .map(rdateTime -> new Period(rdateTime, rDuration)).collect(Collectors.toList())); recurrenceSet.addAll(rDates.stream().filter(p -> p.getParameter(Parameter.VALUE) == Value.DATE) - .map(p -> p.getDates()).flatMap(DateList::stream).filter(date -> period.includes(date)) - .map(rdateDate -> new Period(new DateTime(rdateDate), rDuration)).collect(Collectors.toList())); + .map(DateListProperty::getDates).flatMap(List::stream).filter(date -> period.includes(date)) + .map(rdateDate -> new Period(rdateDate, rDuration)).collect(Collectors.toList())); // allow for recurrence rules that start prior to the specified period // but still intersect with it.. - final DateTime startMinusDuration = new DateTime(period.getStart()); - startMinusDuration.setTime(Date.from(period.getStart().toInstant().minus(rDuration)).getTime()); + final Temporal startMinusDuration = period.getStart().minus(rDuration); // add recurrence rules.. List rRules = getProperties(Property.RRULE); if (!rRules.isEmpty()) { recurrenceSet.addAll(rRules.stream().map(r -> r.getRecur().getDates(start.getDate(), - new Period(startMinusDuration, period.getEnd()), startValue)).flatMap(DateList::stream) - .map(rruleDate -> new Period(new DateTime(rruleDate), rDuration)).collect(Collectors.toList())); + startMinusDuration, period.getEnd())).flatMap(List::stream) + .map(rruleDate -> new Period(rruleDate, rDuration)).collect(Collectors.toList())); } else { // add initial instance if intersection with the specified period.. Period startPeriod; if (end != null) { - startPeriod = new Period(new DateTime(start.getDate()), - new DateTime(end.getDate())); + startPeriod = new Period(start.getDate(), end.getDate()); } else { /* * PeS: Anniversary type has no DTEND nor DUR, define DUR @@ -367,8 +366,7 @@ public final PeriodList calculateRecurrenceSet(final Period period) { duration = new Duration(rDuration); } - startPeriod = new Period(new DateTime(start.getDate()), - duration.getDuration()); + startPeriod = new Period(start.getDate(), duration.getDuration()); } if (period.intersects(startPeriod)) { recurrenceSet.add(startPeriod); @@ -377,23 +375,23 @@ public final PeriodList calculateRecurrenceSet(final Period period) { // subtract exception dates.. List exDateProps = getProperties(Property.EXDATE); - List exDates = exDateProps.stream().map(e -> e.getDates()).flatMap(DateList::stream) + List exDates = exDateProps.stream().map(DateListProperty::getDates).flatMap(List::stream) .collect(Collectors.toList()); recurrenceSet.removeIf(recurrence -> { // for DATE-TIME instances check for DATE-based exclusions also.. - return exDates.contains(recurrence.getStart()) || exDates.contains(new Date(recurrence.getStart())); + return exDates.contains(recurrence.getStart()) || exDates.contains(recurrence.getStart()); }); // subtract exception rules.. List exRules = getProperties(Property.EXRULE); - List exRuleDates = exRules.stream().map(e -> e.getRecur().getDates(start.getDate(), - period, startValue)).flatMap(DateList::stream).collect(Collectors.toList()); + List exRuleDates = exRules.stream().map(e -> e.getRecur().getDates(start.getDate(), + period)).flatMap(List::stream).collect(Collectors.toList()); recurrenceSet.removeIf(recurrence -> { // for DATE-TIME instances check for DATE-based exclusions also.. return exRuleDates.contains(recurrence.getStart()) - || exRuleDates.contains(new Date(recurrence.getStart())); + || exRuleDates.contains(recurrence.getStart()); }); return recurrenceSet; diff --git a/src/main/java/net/fortuna/ical4j/model/ComponentSequenceComparator.java b/src/main/java/net/fortuna/ical4j/model/ComponentSequenceComparator.java index d33ac1212..7575eaf52 100644 --- a/src/main/java/net/fortuna/ical4j/model/ComponentSequenceComparator.java +++ b/src/main/java/net/fortuna/ical4j/model/ComponentSequenceComparator.java @@ -3,6 +3,7 @@ import net.fortuna.ical4j.model.property.DtStamp; import net.fortuna.ical4j.model.property.Sequence; +import java.time.Instant; import java.util.Comparator; import java.util.Optional; @@ -25,7 +26,7 @@ public int compare(Component o1, Component o2) { retVal = sequence1.compareTo(sequence2); if (retVal == 0) { - DtStamp defaultDtStamp = new DtStamp(new DateTime(0)); + DtStamp defaultDtStamp = new DtStamp(Instant.EPOCH); DtStamp dtStamp1 = Optional.ofNullable((DtStamp) o1.getProperty(Property.DTSTAMP)).orElse(defaultDtStamp); DtStamp dtStamp2 = Optional.ofNullable((DtStamp) o2.getProperty(Property.DTSTAMP)).orElse(defaultDtStamp); diff --git a/src/main/java/net/fortuna/ical4j/model/NumberList.java b/src/main/java/net/fortuna/ical4j/model/NumberList.java index cc633c898..4e0329107 100644 --- a/src/main/java/net/fortuna/ical4j/model/NumberList.java +++ b/src/main/java/net/fortuna/ical4j/model/NumberList.java @@ -35,6 +35,7 @@ import java.io.Serializable; import java.util.ArrayList; +import java.util.Arrays; import java.util.StringTokenizer; import java.util.stream.Collectors; @@ -122,4 +123,10 @@ public final boolean add(final Integer aNumber) { public final String toString() { return stream().map(Object::toString).collect(Collectors.joining(",")); } + + public static NumberList parse(String numberString) { + NumberList retVal = new NumberList(); + retVal.addAll(Arrays.stream(numberString.split(",")).map(Numbers::parseInt).collect(Collectors.toList())); + return retVal; + } } diff --git a/src/main/java/net/fortuna/ical4j/model/Recur.java b/src/main/java/net/fortuna/ical4j/model/Recur.java index 09fee040f..33dce8c1c 100644 --- a/src/main/java/net/fortuna/ical4j/model/Recur.java +++ b/src/main/java/net/fortuna/ical4j/model/Recur.java @@ -31,7 +31,6 @@ */ package net.fortuna.ical4j.model; -import net.fortuna.ical4j.model.parameter.Value; import net.fortuna.ical4j.transform.Transformer; import net.fortuna.ical4j.transform.recurrence.*; import net.fortuna.ical4j.util.CompatibilityHints; @@ -43,6 +42,11 @@ import java.io.IOException; import java.io.Serializable; import java.text.ParseException; +import java.time.Instant; +import java.time.ZonedDateTime; +import java.time.temporal.ChronoUnit; +import java.time.temporal.Temporal; +import java.time.temporal.TemporalUnit; import java.util.Calendar; import java.util.*; @@ -155,33 +159,35 @@ public enum Frequency { private transient Logger log = LoggerFactory.getLogger(Recur.class); + private static final Comparator CANDIDATE_SORTER = new TemporalComparator(); + private Frequency frequency; - private Date until; + private Instant until; private Integer count; private Integer interval; - private NumberList secondList; + private final NumberList secondList = new NumberList(0, 59, false); - private NumberList minuteList; + private final NumberList minuteList = new NumberList(0, 59, false); - private NumberList hourList; + private final NumberList hourList = new NumberList(0, 23, false); - private WeekDayList dayList; + private final WeekDayList dayList = new WeekDayList(); - private NumberList monthDayList; + private final NumberList monthDayList = new NumberList(1, 31, true); - private NumberList yearDayList; + private final NumberList yearDayList = new NumberList(1, 366, true); - private NumberList weekNoList; + private final NumberList weekNoList = new NumberList(1, 53, true); - private NumberList monthList; + private final NumberList monthList = new NumberList(1, 12, false); - private NumberList setPosList; + private final NumberList setPosList = new NumberList(1, 366, true); - private Map> transformers; + private Map>> transformers; private WeekDay.Day weekStartDay; @@ -189,8 +195,8 @@ public enum Frequency { private Map experimentalValues = new HashMap(); - // Calendar field we increment based on frequency. - private int calIncField; + // Temporal field we increment based on frequency. + private TemporalUnit calIncField; /** * Default constructor. @@ -198,7 +204,6 @@ public enum Frequency { private Recur() { // default week start is Monday per RFC5545 calendarWeekStartDay = Calendar.MONDAY; - initTransformers(); } /** @@ -218,35 +223,29 @@ public Recur(final String aValue) throws ParseException { frequency = Frequency.valueOf(nextToken(tokens, token)); } else if (UNTIL.equals(token)) { final String untilString = nextToken(tokens, token); - if (untilString != null && untilString.contains("T")) { - until = new DateTime(untilString); - // UNTIL must be specified in UTC time.. - ((DateTime) until).setUtc(true); - } else { - until = new Date(untilString); - } + until = Instant.from(TemporalAdapter.parse(untilString).getTemporal()); } else if (COUNT.equals(token)) { count = Integer.parseInt(nextToken(tokens, token)); } else if (INTERVAL.equals(token)) { interval = Integer.parseInt(nextToken(tokens, token)); } else if (BYSECOND.equals(token)) { - secondList = new NumberList(nextToken(tokens, token), 0, 59, false); + secondList.addAll(NumberList.parse(nextToken(tokens, token))); } else if (BYMINUTE.equals(token)) { - minuteList = new NumberList(nextToken(tokens, token), 0, 59, false); + minuteList.addAll(NumberList.parse(nextToken(tokens, token))); } else if (BYHOUR.equals(token)) { - hourList = new NumberList(nextToken(tokens, token), 0, 23, false); + hourList.addAll(NumberList.parse(nextToken(tokens, token))); } else if (BYDAY.equals(token)) { - dayList = new WeekDayList(nextToken(tokens, token)); + dayList.addAll(new WeekDayList(nextToken(tokens, token))); } else if (BYMONTHDAY.equals(token)) { - monthDayList = new NumberList(nextToken(tokens, token), 1, 31, true); + monthDayList.addAll(NumberList.parse(nextToken(tokens, token))); } else if (BYYEARDAY.equals(token)) { - yearDayList = new NumberList(nextToken(tokens, token), 1, 366, true); + yearDayList.addAll(NumberList.parse(nextToken(tokens, token))); } else if (BYWEEKNO.equals(token)) { - weekNoList = new NumberList(nextToken(tokens, token), 1, 53, true); + weekNoList.addAll(NumberList.parse(nextToken(tokens, token))); } else if (BYMONTH.equals(token)) { - monthList = new NumberList(nextToken(tokens, token), 1, 12, false); + monthList.addAll(NumberList.parse(nextToken(tokens, token))); } else if (BYSETPOS.equals(token)) { - setPosList = new NumberList(nextToken(tokens, token), 1, 366, true); + setPosList.addAll(NumberList.parse(nextToken(tokens, token))); } else if (WKST.equals(token)) { weekStartDay = WeekDay.Day.valueOf(nextToken(tokens, token)); calendarWeekStartDay = WeekDay.getCalendarDay(WeekDay.getWeekDay(weekStartDay)); @@ -261,7 +260,6 @@ public Recur(final String aValue) throws ParseException { } } validateFrequency(); - initTransformers(); } private String nextToken(Iterator tokens, String lastToken) { @@ -277,7 +275,7 @@ private String nextToken(Iterator tokens, String lastToken) { * @param until maximum recurrence date */ @Deprecated - public Recur(final String frequency, final Date until) { + public Recur(final String frequency, final Instant until) { this(Frequency.valueOf(frequency), until); } @@ -285,13 +283,12 @@ public Recur(final String frequency, final Date until) { * @param frequency a recurrence frequency string * @param until maximum recurrence date */ - public Recur(final Frequency frequency, final Date until) { + public Recur(final Frequency frequency, final Instant until) { // default week start is Monday per RFC5545 calendarWeekStartDay = Calendar.MONDAY; this.frequency = frequency; this.until = until; validateFrequency(); - initTransformers(); } /** @@ -313,57 +310,6 @@ public Recur(final Frequency frequency, final int count) { this.frequency = frequency; this.count = count; validateFrequency(); - initTransformers(); - } - - private void initTransformers() { - transformers = new HashMap<>(); - if (secondList != null) { - transformers.put(BYSECOND, new BySecondRule(secondList, frequency, Optional.ofNullable(weekStartDay))); - } else { - secondList = new NumberList(0, 59, false); - } - if (minuteList != null) { - transformers.put(BYMINUTE, new ByMinuteRule(minuteList, frequency, Optional.ofNullable(weekStartDay))); - } else { - minuteList = new NumberList(0, 59, false); - } - if (hourList != null) { - transformers.put(BYHOUR, new ByHourRule(hourList, frequency, Optional.ofNullable(weekStartDay))); - } else { - hourList = new NumberList(0, 23, false); - } - if (monthDayList != null) { - transformers.put(BYMONTHDAY, new ByMonthDayRule(monthDayList, frequency, Optional.ofNullable(weekStartDay))); - } else { - monthDayList = new NumberList(1, 31, true); - } - if (yearDayList != null) { - transformers.put(BYYEARDAY, new ByYearDayRule(yearDayList, frequency, Optional.ofNullable(weekStartDay))); - } else { - yearDayList = new NumberList(1, 366, true); - } - if (weekNoList != null) { - transformers.put(BYWEEKNO, new ByWeekNoRule(weekNoList, frequency, Optional.ofNullable(weekStartDay))); - } else { - weekNoList = new NumberList(1, 53, true); - } - if (monthList != null) { - transformers.put(BYMONTH, new ByMonthRule(monthList, frequency, - Optional.ofNullable(weekStartDay))); - } else { - monthList = new NumberList(1, 12, false); - } - if (dayList != null) { - transformers.put(BYDAY, new ByDayRule(dayList, deriveFilterType(), Optional.ofNullable(weekStartDay))); - } else { - dayList = new WeekDayList(); - } - if (setPosList != null) { - transformers.put(BYSETPOS, new BySetPosRule(setPosList)); - } else { - setPosList = new NumberList(1, 366, true); - } } private Frequency deriveFilterType() { @@ -500,7 +446,7 @@ public final int getInterval() { /** * @return Returns the until or null if there is none. */ - public final Date getUntil() { + public final Instant getUntil() { return until; } @@ -621,12 +567,10 @@ public final String toString() { * * @param periodStart the start of the period * @param periodEnd the end of the period - * @param value the type of dates to generate (i.e. date/date-time) * @return a list of dates */ - public final DateList getDates(final Date periodStart, - final Date periodEnd, final Value value) { - return getDates(periodStart, periodStart, periodEnd, value, -1); + public final List getDates(final T periodStart, final T periodEnd) { + return getDates(periodStart, periodStart, periodEnd, -1); } /** @@ -634,12 +578,10 @@ public final DateList getDates(final Date periodStart, * * @param seed a seed date for generating recurrence instances * @param period the period of returned recurrence dates - * @param value type of dates to generate * @return a list of dates */ - public final DateList getDates(final Date seed, final Period period, - final Value value) { - return getDates(seed, period.getStart(), period.getEnd(), value, -1); + public final List getDates(final T seed, final Period period) { + return getDates(seed, period.getStart(), period.getEnd(), -1); } /** @@ -652,12 +594,11 @@ public final DateList getDates(final Date seed, final Period period, * @param seed the start date of this Recurrence's first instance * @param periodStart the start of the period * @param periodEnd the end of the period - * @param value the type of dates to generate (i.e. date/date-time) * @return a list of dates represented by this recur instance */ - public final DateList getDates(final Date seed, final Date periodStart, - final Date periodEnd, final Value value) { - return getDates(seed, periodStart, periodEnd, value, -1); + public final List getDates(final T seed, final T periodStart, + final T periodEnd) { + return getDates(seed, periodStart, periodEnd, -1); } /** @@ -670,51 +611,38 @@ public final DateList getDates(final Date seed, final Date periodStart, * @param seed the start date of this Recurrence's first instance * @param periodStart the start of the period * @param periodEnd the end of the period - * @param value the type of dates to generate (i.e. date/date-time) * @param maxCount limits the number of instances returned. Up to one years * worth extra may be returned. Less than 0 means no limit * @return a list of dates represented by this recur instance */ - public final DateList getDates(final Date seed, final Date periodStart, - final Date periodEnd, final Value value, - final int maxCount) { - - final DateList dates = new DateList(value); - if (seed instanceof DateTime) { - if (((DateTime) seed).isUtc()) { - dates.setUtc(true); - } else { - dates.setTimeZone(((DateTime) seed).getTimeZone()); - } - } - Calendar cal = getCalendarInstance(seed, true); - final Calendar rootSeed = (Calendar)cal.clone(); - + public final List getDates(final T seed, final T periodStart, + final T periodEnd, final int maxCount) { + + final List dates = new ArrayList<>(); + + T candidateSeed = seed; + // optimize the start time for selecting candidates // (only applicable where a COUNT is not specified) if (count == null) { - Calendar seededCal = (Calendar) cal.clone(); - while (seededCal.getTime().before(periodStart)) { - cal.setTime(seededCal.getTime()); - seededCal = smartIncrement(seededCal); - if (seededCal == null) { + while (Instant.from(candidateSeed).isBefore(Instant.from(periodStart))) { + candidateSeed = (T) smartIncrement(candidateSeed); + if (candidateSeed == null) { return dates; } } } - HashSet invalidCandidates = new HashSet(); + HashSet invalidCandidates = new HashSet<>(); int noCandidateIncrementCount = 0; - Date candidate = null; + T candidate = null; while ((maxCount < 0) || (dates.size() < maxCount)) { - final Date candidateSeed = Dates.getInstance(cal.getTime(), value); - if (getUntil() != null && candidate != null - && candidate.after(getUntil())) { + && Instant.from(candidate).isAfter(getUntil())) { break; } if (periodEnd != null && candidate != null - && candidate.after(periodEnd)) { + && Instant.from(candidate).isAfter(Instant.from(periodEnd))) { break; } if (getCount() >= 1 @@ -722,37 +650,28 @@ public final DateList getDates(final Date seed, final Date periodStart, break; } -// if (Value.DATE_TIME.equals(value)) { - if (candidateSeed instanceof DateTime) { - if (dates.isUtc()) { - ((DateTime) candidateSeed).setUtc(true); - } else { - ((DateTime) candidateSeed).setTimeZone(dates.getTimeZone()); - } - } - // rootSeed = date used for the seed for the RRule at the // start of the first period. // candidateSeed = date used for the start of // the current period. - final DateList candidates = getCandidates(rootSeed, candidateSeed, value); + final List candidates = getCandidates(seed, candidateSeed); if (!candidates.isEmpty()) { noCandidateIncrementCount = 0; // sort candidates for identifying when UNTIL date is exceeded.. - Collections.sort(candidates); - for (Date candidate1 : candidates) { + Collections.sort(candidates, CANDIDATE_SORTER); + for (T candidate1 : candidates) { candidate = candidate1; // don't count candidates that occur before the seed date.. - if (!candidate.before(seed)) { + if (!Instant.from(candidate).isBefore(Instant.from(seed))) { // candidates exclusive of periodEnd.. - if (candidate.before(periodStart) - || candidate.after(periodEnd)) { + if (Instant.from(candidate).isBefore(Instant.from(periodStart)) + || Instant.from(candidate).isAfter(Instant.from(periodEnd))) { invalidCandidates.add(candidate); } else if (getCount() >= 1 && (dates.size() + invalidCandidates.size()) >= getCount()) { break; - } else if (!candidate.before(periodStart) && !candidate.after(periodEnd) - && (getUntil() == null || !candidate.after(getUntil()))) { + } else if (!Instant.from(candidate).isBefore(Instant.from(periodStart)) && !Instant.from(candidate).isAfter(Instant.from(periodEnd)) + && (getUntil() == null || !Instant.from(candidate).isAfter(getUntil()))) { dates.add(candidate); } @@ -764,13 +683,13 @@ public final DateList getDates(final Date seed, final Date periodStart, break; } } - cal = smartIncrement(cal); - if (cal == null) { + candidateSeed = smartIncrement(candidateSeed); + if (candidateSeed == null) { break; } } // sort final list.. - Collections.sort(dates); + Collections.sort(dates, CANDIDATE_SORTER); return dates; } @@ -785,30 +704,24 @@ public final DateList getDates(final Date seed, final Date periodStart, * @param startDate the date to start the search * @return the next date in the recurrence series after startDate */ - public final Date getNextDate(final Date seed, final Date startDate) { + public final T getNextDate(final T seed, final T startDate) { - final Calendar cal = getCalendarInstance(seed, true); - final Calendar rootSeed = (Calendar)cal.clone(); + T candidateSeed = seed; // optimize the start time for selecting candidates // (only applicable where a COUNT is not specified) if (count == null) { - final Calendar seededCal = (Calendar) cal.clone(); - while (seededCal.getTime().before(startDate)) { - cal.setTime(seededCal.getTime()); - increment(seededCal); + while (Instant.from(candidateSeed).isBefore(Instant.from(startDate))) { + candidateSeed = (T) increment(candidateSeed); } } int invalidCandidateCount = 0; int noCandidateIncrementCount = 0; - Date candidate = null; - final Value value = seed instanceof DateTime ? Value.DATE_TIME : Value.DATE; + T candidate = candidateSeed; while (true) { - final Date candidateSeed = Dates.getInstance(cal.getTime(), value); - - if (getUntil() != null && candidate != null && candidate.after(getUntil())) { + if (getUntil() != null && Instant.from(candidate).isAfter(getUntil())) { break; } @@ -816,33 +729,24 @@ public final Date getNextDate(final Date seed, final Date startDate) { break; } - if (Value.DATE_TIME.equals(value)) { - if (((DateTime) seed).isUtc()) { - ((DateTime) candidateSeed).setUtc(true); - } else { - ((DateTime) candidateSeed).setTimeZone(((DateTime) seed).getTimeZone()); - } - } - - final DateList candidates = getCandidates(rootSeed, candidateSeed, value); + final List candidates = getCandidates(seed, candidateSeed); if (!candidates.isEmpty()) { noCandidateIncrementCount = 0; // sort candidates for identifying when UNTIL date is exceeded.. - Collections.sort(candidates); + Collections.sort(candidates, CANDIDATE_SORTER); - for (Date candidate1 : candidates) { + for (T candidate1 : candidates) { candidate = candidate1; // don't count candidates that occur before the seed date.. - if (!candidate.before(seed)) { + if (!Instant.from(candidate).isBefore(Instant.from(seed))) { // Candidate must be after startDate because // we want the NEXT occurrence - if (!candidate.after(startDate)) { + if (!Instant.from(candidate).isAfter(Instant.from(startDate))) { invalidCandidateCount++; - } else if (getCount() > 0 - && invalidCandidateCount >= getCount()) { + } else if (getCount() > 0 && invalidCandidateCount >= getCount()) { break; } else if (!(getUntil() != null - && candidate.after(getUntil()))) { + && Instant.from(candidate).isAfter(getUntil()))) { return candidate; } } @@ -853,29 +757,29 @@ public final Date getNextDate(final Date seed, final Date startDate) { break; } } - increment(cal); + candidateSeed = (T) increment(candidateSeed); } return null; } /** - * Increments the specified calendar according to the frequency and interval specified in this recurrence rule. + * Increments the specified temporal according to the frequency and interval specified in this recurrence rule. * - * @param cal a java.util.Calendar to increment + * @param cal a {@link Temporal} value to increment */ - private void increment(final Calendar cal) { + private T increment(final T cal) { // initialise interval.. final int calInterval = (getInterval() >= 1) ? getInterval() : 1; - cal.add(calIncField, calInterval); + return (T) cal.plus(calInterval, calIncField); } - private Calendar smartIncrement(final Calendar cal) { + private T smartIncrement(final T cal) { // initialise interval.. - Calendar result = null; + Temporal result = null; final int calInterval = (getInterval() >= 1) ? getInterval() : 1; int multiplier = 1; - if (calIncField == 2 || calIncField == 1) { - Calendar seededCal; + if (calIncField == ChronoUnit.MONTHS || calIncField == ChronoUnit.YEARS) { + Temporal seededCal; // increment up to 12 times to check for next valid occurence. // as this loop only increments monthly or yearly, // a monthly occurence will be found in (0,12] increments @@ -883,39 +787,30 @@ private Calendar smartIncrement(final Calendar cal) { // (ex. recurrence on February 29 on a leap year will find the next occurrence on the next leap year). // if none found in these, return null. do { - seededCal = (Calendar) cal.clone(); - seededCal.add(calIncField, calInterval * multiplier); + seededCal = cal.plus(calInterval * multiplier, calIncField); multiplier++; - } while (seededCal.get(Calendar.DAY_OF_MONTH) != cal.get(Calendar.DAY_OF_MONTH) && multiplier <= 12); + } while (ZonedDateTime.from(seededCal).getDayOfMonth() != ZonedDateTime.from(cal).getDayOfMonth() + && multiplier <= 12); if (multiplier <= 12) { - result = (Calendar) seededCal.clone(); + result = seededCal; } } else { - result = (Calendar) cal.clone(); - result.add(calIncField, calInterval); + result = cal.plus(calInterval, calIncField); } - return result; + return (T) result; } /** * Returns a list of possible dates generated from the applicable BY* rules, using the specified date as a seed. * * @param date the seed date - * @param value the type of date list to return - * @return a DateList + * @return a List of Temporal of the same type as the seed date */ - private DateList getCandidates(final Calendar rootSeed, final Date date, final Value value) { - DateList dates = new DateList(value); - if (date instanceof DateTime) { - if (((DateTime) date).isUtc()) { - dates.setUtc(true); - } else { - dates.setTimeZone(((DateTime) date).getTimeZone()); - } - } + private List getCandidates(final T rootSeed, final T date) { + List dates = new ArrayList<>(); dates.add(date); if (transformers.get(BYMONTH) != null) { - dates = transformers.get(BYMONTH).transform(dates); + dates = new ByMonthRule(monthList, frequency, weekStartDay).transform(dates); // debugging.. if (log.isDebugEnabled()) { log.debug("Dates after BYMONTH processing: " + dates); @@ -923,7 +818,7 @@ private DateList getCandidates(final Calendar rootSeed, final Date date, final V } if (transformers.get(BYWEEKNO) != null) { - dates = transformers.get(BYWEEKNO).transform(dates); + dates = new ByWeekNoRule(weekNoList, frequency, weekStartDay).transform(dates); // debugging.. if (log.isDebugEnabled()) { log.debug("Dates after BYWEEKNO processing: " + dates); @@ -931,7 +826,7 @@ private DateList getCandidates(final Calendar rootSeed, final Date date, final V } if (transformers.get(BYYEARDAY) != null) { - dates = transformers.get(BYYEARDAY).transform(dates); + dates = new ByYearDayRule(yearDayList, frequency, weekStartDay).transform(dates); // debugging.. if (log.isDebugEnabled()) { log.debug("Dates after BYYEARDAY processing: " + dates); @@ -939,7 +834,7 @@ private DateList getCandidates(final Calendar rootSeed, final Date date, final V } if (transformers.get(BYMONTHDAY) != null) { - dates = transformers.get(BYMONTHDAY).transform(dates); + dates = new ByMonthDayRule(monthDayList, frequency, weekStartDay).transform(dates); // debugging.. if (log.isDebugEnabled()) { log.debug("Dates after BYMONTHDAY processing: " + dates); @@ -948,13 +843,13 @@ private DateList getCandidates(final Calendar rootSeed, final Date date, final V && weekNoList.isEmpty() && dayList.isEmpty())) { NumberList implicitMonthDayList = new NumberList(); - implicitMonthDayList.add(rootSeed.get(Calendar.DAY_OF_MONTH)); - ByMonthDayRule implicitRule = new ByMonthDayRule(implicitMonthDayList, frequency, Optional.ofNullable(weekStartDay)); + implicitMonthDayList.add(ZonedDateTime.from(rootSeed).getDayOfMonth()); + ByMonthDayRule implicitRule = new ByMonthDayRule<>(implicitMonthDayList, frequency, weekStartDay); dates = implicitRule.transform(dates); } if (transformers.get(BYDAY) != null) { - dates = transformers.get(BYDAY).transform(dates); + dates = new ByDayRule(dayList, frequency, weekStartDay).transform(dates); // debugging.. if (log.isDebugEnabled()) { log.debug("Dates after BYDAY processing: " + dates); @@ -962,13 +857,13 @@ private DateList getCandidates(final Calendar rootSeed, final Date date, final V } else if (frequency == Frequency.WEEKLY || (frequency == Frequency.YEARLY && yearDayList.isEmpty() && !weekNoList.isEmpty() && monthDayList.isEmpty())) { - ByDayRule implicitRule = new ByDayRule(new WeekDayList(WeekDay.getWeekDay(rootSeed)), - deriveFilterType(), Optional.ofNullable(weekStartDay)); + ByDayRule implicitRule = new ByDayRule<>(new WeekDayList(WeekDay.getWeekDay( + ZonedDateTime.from(rootSeed).getDayOfWeek())), deriveFilterType(), weekStartDay); dates = implicitRule.transform(dates); } if (transformers.get(BYHOUR) != null) { - dates = transformers.get(BYHOUR).transform(dates); + dates = new ByHourRule(hourList, frequency, weekStartDay).transform(dates); // debugging.. if (log.isDebugEnabled()) { log.debug("Dates after BYHOUR processing: " + dates); @@ -976,7 +871,7 @@ private DateList getCandidates(final Calendar rootSeed, final Date date, final V } if (transformers.get(BYMINUTE) != null) { - dates = transformers.get(BYMINUTE).transform(dates); + dates = new ByMinuteRule(minuteList, frequency, weekStartDay).transform(dates); // debugging.. if (log.isDebugEnabled()) { log.debug("Dates after BYMINUTE processing: " + dates); @@ -984,7 +879,7 @@ private DateList getCandidates(final Calendar rootSeed, final Date date, final V } if (transformers.get(BYSECOND) != null) { - dates = transformers.get(BYSECOND).transform(dates); + dates = new BySecondRule(secondList, frequency, weekStartDay).transform(dates); // debugging.. if (log.isDebugEnabled()) { log.debug("Dates after BYSECOND processing: " + dates); @@ -992,7 +887,7 @@ private DateList getCandidates(final Calendar rootSeed, final Date date, final V } if (transformers.get(BYSETPOS) != null) { - dates = transformers.get(BYSETPOS).transform(dates); + dates = new BySetPosRule(setPosList).transform(dates); // debugging.. if (log.isDebugEnabled()) { log.debug("Dates after SETPOS processing: " + dates); @@ -1006,19 +901,19 @@ private void validateFrequency() { throw new IllegalArgumentException("A recurrence rule MUST contain a FREQ rule part."); } if (Frequency.SECONDLY.equals(getFrequency())) { - calIncField = Calendar.SECOND; + calIncField = ChronoUnit.SECONDS; } else if (Frequency.MINUTELY.equals(getFrequency())) { - calIncField = Calendar.MINUTE; + calIncField = ChronoUnit.MINUTES; } else if (Frequency.HOURLY.equals(getFrequency())) { - calIncField = Calendar.HOUR_OF_DAY; + calIncField = ChronoUnit.HOURS; } else if (Frequency.DAILY.equals(getFrequency())) { - calIncField = Calendar.DAY_OF_YEAR; + calIncField = ChronoUnit.DAYS; } else if (Frequency.WEEKLY.equals(getFrequency())) { - calIncField = Calendar.WEEK_OF_YEAR; + calIncField = ChronoUnit.WEEKS; } else if (Frequency.MONTHLY.equals(getFrequency())) { - calIncField = Calendar.MONTH; + calIncField = ChronoUnit.MONTHS; } else if (Frequency.YEARLY.equals(getFrequency())) { - calIncField = Calendar.YEAR; + calIncField = ChronoUnit.YEARS; } else { throw new IllegalArgumentException("Invalid FREQ rule part '" + frequency + "' in recurrence rule"); @@ -1059,7 +954,7 @@ public final void setInterval(final int interval) { * @deprecated will be removed in a future version to support immutable pattern. */ @Deprecated - public final void setUntil(final Date until) { + public final void setUntil(final Instant until) { this.until = until; this.count = -1; } @@ -1099,7 +994,7 @@ public static class Builder { private Frequency frequency; - private Date until; + private Instant until; private Integer count; @@ -1130,7 +1025,7 @@ public Builder frequency(Frequency frequency) { return this; } - public Builder until(Date until) { + public Builder until(Instant until) { this.until = until; return this; } @@ -1201,18 +1096,35 @@ public Recur build() { recur.until = until; recur.count = count; recur.interval = interval; - recur.secondList = secondList; - recur.minuteList = minuteList; - recur.hourList = hourList; - recur.dayList = dayList; - recur.monthDayList = monthDayList; - recur.yearDayList = yearDayList; - recur.weekNoList = weekNoList; - recur.monthList = monthList; - recur.setPosList = setPosList; + if (secondList != null) { + recur.secondList.addAll(secondList); + } + if (minuteList != null) { + recur.minuteList.addAll(minuteList); + } + if (hourList != null) { + recur.hourList.addAll(hourList); + } + if (dayList != null) { + recur.dayList.addAll(dayList); + } + if (monthDayList != null) { + recur.monthDayList.addAll(monthDayList); + } + if (yearDayList != null) { + recur.yearDayList.addAll(yearDayList); + } + if (weekNoList != null) { + recur.weekNoList.addAll(weekNoList); + } + if (monthList != null) { + recur.monthList.addAll(monthList); + } + if (setPosList != null) { + recur.setPosList.addAll(setPosList); + } recur.weekStartDay = weekStartDay; recur.validateFrequency(); - recur.initTransformers(); return recur; } } diff --git a/src/main/java/net/fortuna/ical4j/model/TimeZone.java b/src/main/java/net/fortuna/ical4j/model/TimeZone.java index cdf2d09da..6fd507c0b 100644 --- a/src/main/java/net/fortuna/ical4j/model/TimeZone.java +++ b/src/main/java/net/fortuna/ical4j/model/TimeZone.java @@ -37,7 +37,9 @@ import net.fortuna.ical4j.model.property.TzId; import net.fortuna.ical4j.model.property.TzOffsetTo; -import java.util.Calendar; +import java.time.Instant; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; import java.util.Date; /** @@ -84,16 +86,8 @@ public final int getOffset(final int era, final int year, final int month, final final int second = ms / 1000; ms -= second * 1000; - final Calendar cal = Calendar.getInstance(); - cal.clear(); // don't retain current date/time, it may disturb the calculation - - // set date and time - cal.set(Calendar.ERA, era); - cal.set(Calendar.DAY_OF_WEEK, dayOfWeek); - cal.set(year, month, dayOfMonth, hour, minute, second); - cal.set(Calendar.MILLISECOND, ms); - - final Observance observance = vTimeZone.getApplicableObservance(new DateTime(cal.getTime())); + OffsetDateTime date = OffsetDateTime.of(year, month, dayOfMonth, hour, minute, second, ms * 1000, ZoneOffset.UTC); + final Observance observance = vTimeZone.getApplicableObservance(date); if (observance != null) { final TzOffsetTo offset = observance.getProperty(Property.TZOFFSETTO); return (int) (offset.getOffset().getTotalSeconds() * 1000L); @@ -105,7 +99,7 @@ public final int getOffset(final int era, final int year, final int month, final * {@inheritDoc} */ public int getOffset(long date) { - final Observance observance = vTimeZone.getApplicableObservance(new DateTime(date)); + final Observance observance = vTimeZone.getApplicableObservance(Instant.ofEpochMilli(date)); if (observance != null) { final TzOffsetTo offset = observance.getProperty(Property.TZOFFSETTO); if ((offset.getOffset().getTotalSeconds() * 1000L) < getRawOffset()) { @@ -134,7 +128,7 @@ public final int getRawOffset() { * @return true if the specified date is in daylight time, otherwise false */ public final boolean inDaylightTime(final Date date) { - final Observance observance = vTimeZone.getApplicableObservance(new DateTime(date)); + final Observance observance = vTimeZone.getApplicableObservance(date.toInstant()); return (observance instanceof Daylight); } @@ -174,14 +168,13 @@ private static int getRawOffset(VTimeZone vt) { if (seasonalTimes.size() > 1) { // per java spec and when dealing with historical time, // rawoffset is the raw offset at the current date - final DateTime now = new DateTime(); - Date latestOnset = null; + OffsetDateTime latestOnset = null; for (Observance seasonalTime : seasonalTimes) { - Date onset = seasonalTime.getLatestOnset(now); + OffsetDateTime onset = seasonalTime.getLatestOnset(Instant.now()); if (onset == null) { continue; } - if (latestOnset == null || onset.after(latestOnset)) { + if (latestOnset == null || onset.isAfter(latestOnset)) { latestOnset = onset; latestSeasonalTime = seasonalTime; } diff --git a/src/main/java/net/fortuna/ical4j/model/TimeZoneLoader.java b/src/main/java/net/fortuna/ical4j/model/TimeZoneLoader.java index 662c52968..d1f85d6c3 100644 --- a/src/main/java/net/fortuna/ical4j/model/TimeZoneLoader.java +++ b/src/main/java/net/fortuna/ical4j/model/TimeZoneLoader.java @@ -60,7 +60,7 @@ public class TimeZoneLoader { NO_TRANSITIONS.getProperties().add(offsetFrom); NO_TRANSITIONS.getProperties().add(offsetTo); DtStart start = new DtStart(); - start.setDate(new DateTime(0L)); + start.setDate(Instant.EPOCH); NO_TRANSITIONS.getProperties().add(start); // Proxy configuration.. diff --git a/src/main/java/net/fortuna/ical4j/model/component/VEvent.java b/src/main/java/net/fortuna/ical4j/model/component/VEvent.java index b8388d92c..5a8652a59 100644 --- a/src/main/java/net/fortuna/ical4j/model/component/VEvent.java +++ b/src/main/java/net/fortuna/ical4j/model/component/VEvent.java @@ -35,7 +35,6 @@ import net.fortuna.ical4j.model.parameter.Value; import net.fortuna.ical4j.model.property.*; import net.fortuna.ical4j.util.CompatibilityHints; -import net.fortuna.ical4j.util.Dates; import net.fortuna.ical4j.util.Strings; import net.fortuna.ical4j.validate.PropertyValidator; import net.fortuna.ical4j.validate.ValidationException; @@ -46,6 +45,7 @@ import java.io.IOException; import java.net.URISyntaxException; import java.text.ParseException; +import java.time.temporal.Temporal; import java.time.temporal.TemporalAmount; import java.util.Arrays; import java.util.HashMap; @@ -248,7 +248,7 @@ public VEvent(final PropertyList properties, final ComponentList alarms) * @param start the start date of the new event * @param summary the event summary */ - public VEvent(final Date start, final String summary) { + public VEvent(final Temporal start, final String summary) { this(); getProperties().add(new DtStart(start)); getProperties().add(new Summary(summary)); @@ -260,7 +260,7 @@ public VEvent(final Date start, final String summary) { * @param end the end date of the new event * @param summary the event summary */ - public VEvent(final Date start, final Date end, final String summary) { + public VEvent(final Temporal start, final Temporal end, final String summary) { this(); getProperties().add(new DtStart(start)); getProperties().add(new DtEnd(end)); @@ -274,7 +274,7 @@ public VEvent(final Date start, final Date end, final String summary) { * @param duration the duration of the new event * @param summary the event summary */ - public VEvent(final Date start, final TemporalAmount duration, final String summary) { + public VEvent(final Temporal start, final TemporalAmount duration, final String summary) { this(); getProperties().add(new DtStart(start)); getProperties().add(new Duration(duration)); @@ -431,10 +431,10 @@ protected Validator getValidator(Method method) { * @param rangeStart the start of a range * @param rangeEnd the end of a range * @return a normalised list of periods representing consumed time for this event - * @see VEvent#getConsumedTime(Date, Date, boolean) + * @see VEvent#getConsumedTime(Temporal, Temporal) */ - public final PeriodList getConsumedTime(final Date rangeStart, - final Date rangeEnd) { + public final PeriodList getConsumedTime(final Temporal rangeStart, + final Temporal rangeEnd) { return getConsumedTime(rangeStart, rangeEnd, true); } @@ -447,15 +447,14 @@ public final PeriodList getConsumedTime(final Date rangeStart, * @param normalise indicate whether the returned list of periods should be normalised * @return a list of periods representing consumed time for this event */ - public final PeriodList getConsumedTime(final Date rangeStart, - final Date rangeEnd, final boolean normalise) { + public final PeriodList getConsumedTime(final Temporal rangeStart, + final Temporal rangeEnd, final boolean normalise) { PeriodList periods = new PeriodList(); // if component is transparent return empty list.. if (!Transp.TRANSPARENT.equals(getProperty(Property.TRANSP))) { // try { - periods = calculateRecurrenceSet(new Period(new DateTime(rangeStart), - new DateTime(rangeEnd))); + periods = calculateRecurrenceSet(new Period(rangeStart, rangeEnd)); // } // catch (ValidationException ve) { // log.error("Invalid event data", ve); @@ -481,7 +480,7 @@ public final PeriodList getConsumedTime(final Date rangeStart, * @throws URISyntaxException where an invalid URI is encountered * @throws ParseException where an error occurs parsing data */ - public final VEvent getOccurrence(final Date date) throws IOException, + public final VEvent getOccurrence(final Temporal date) throws IOException, URISyntaxException, ParseException { final PeriodList consumedTime = getConsumedTime(date, date); @@ -640,8 +639,7 @@ public final DtEnd getEndDate(final boolean deriveFromDuration) { vEventDuration = new Duration(java.time.Duration.ofDays(1)); } - dtEnd = new DtEnd(Dates.getInstance(Date.from(dtStart.getDate().toInstant().plus(vEventDuration.getDuration())), - dtStart.getParameter(Parameter.VALUE))); + dtEnd = new DtEnd(dtStart.getDate().plus(vEventDuration.getDuration())); if (dtStart.isUtc()) { dtEnd.setUtc(true); } else { diff --git a/src/main/java/net/fortuna/ical4j/model/component/VFreeBusy.java b/src/main/java/net/fortuna/ical4j/model/component/VFreeBusy.java index 422acb0bd..64ad32140 100644 --- a/src/main/java/net/fortuna/ical4j/model/component/VFreeBusy.java +++ b/src/main/java/net/fortuna/ical4j/model/component/VFreeBusy.java @@ -42,6 +42,8 @@ import net.fortuna.ical4j.validate.component.VFreeBusyReplyValidator; import net.fortuna.ical4j.validate.component.VFreeBusyRequestValidator; +import java.time.ZonedDateTime; +import java.time.temporal.Temporal; import java.time.temporal.TemporalAmount; import java.util.Arrays; import java.util.HashMap; @@ -242,7 +244,7 @@ public VFreeBusy(final PropertyList properties) { * @param start the starting boundary for the VFreeBusy * @param end the ending boundary for the VFreeBusy */ - public VFreeBusy(final DateTime start, final DateTime end) { + public VFreeBusy(final Temporal start, final Temporal end) { this(); // 4.8.2.4 Date/Time Start: @@ -268,7 +270,7 @@ public VFreeBusy(final DateTime start, final DateTime end) { * @param end the ending boundary for the VFreeBusy * @param duration the length of the period being requested */ - public VFreeBusy(final DateTime start, final DateTime end, final TemporalAmount duration) { + public VFreeBusy(final Temporal start, final Temporal end, final TemporalAmount duration) { this(); // 4.8.2.4 Date/Time Start: @@ -328,8 +330,8 @@ public VFreeBusy(final VFreeBusy request, final ComponentList if (duration != null) { getProperties().add(new Duration(duration.getDuration())); // Initialise with all free time of at least the specified duration.. - final DateTime freeStart = new DateTime(start.getDate()); - final DateTime freeEnd = new DateTime(end.getDate()); + final ZonedDateTime freeStart = ZonedDateTime.from(start.getDate()); + final ZonedDateTime freeEnd = ZonedDateTime.from(end.getDate()); final FreeBusy fb = new FreeTimeBuilder().start(freeStart) .end(freeEnd) .duration(duration.getDuration()) @@ -341,8 +343,8 @@ public VFreeBusy(final VFreeBusy request, final ComponentList } else { // initialise with all busy time for the specified period.. - final DateTime busyStart = new DateTime(start.getDate()); - final DateTime busyEnd = new DateTime(end.getDate()); + final ZonedDateTime busyStart = ZonedDateTime.from(start.getDate()); + final ZonedDateTime busyEnd = ZonedDateTime.from(end.getDate()); final FreeBusy fb = new BusyTimeBuilder().start(busyStart) .end(busyEnd) .components(components) @@ -361,18 +363,18 @@ public VFreeBusy(final VFreeBusy request, final ComponentList */ private class BusyTimeBuilder { - private DateTime start; + private Temporal start; - private DateTime end; + private Temporal end; private ComponentList components; - public BusyTimeBuilder start(DateTime start) { + public BusyTimeBuilder start(Temporal start) { this.start = start; return this; } - public BusyTimeBuilder end(DateTime end) { + public BusyTimeBuilder end(Temporal end) { this.end = end; return this; } @@ -403,20 +405,20 @@ public FreeBusy build() { */ private class FreeTimeBuilder { - private DateTime start; + private ZonedDateTime start; - private DateTime end; + private ZonedDateTime end; private TemporalAmount duration; private ComponentList components; - public FreeTimeBuilder start(DateTime start) { + public FreeTimeBuilder start(ZonedDateTime start) { this.start = start; return this; } - public FreeTimeBuilder end(DateTime end) { + public FreeTimeBuilder end(ZonedDateTime end) { this.end = end; return this; } @@ -435,15 +437,16 @@ public FreeBusy build() { final FreeBusy fb = new FreeBusy(); fb.getParameters().add(FbType.FREE); final PeriodList periods = getConsumedTime(components, start, end); - final DateRange range = new DateRange(start, end); + final DateRange range = new DateRange<>(start, end); // Add final consumed time to avoid special-case end-of-list processing periods.add(new Period(end, end)); - DateTime lastPeriodEnd = new DateTime(start); + ZonedDateTime lastPeriodEnd = ZonedDateTime.from(start); // where no time is consumed set the last period end as the range start.. for (final Period period : periods) { // check if period outside bounds.. or period intersects with the end of the range.. if (range.contains(period) || - (range.intersects(period) && period.getStart().after(range.getRangeStart()))) { + (range.intersects(period) + && ZonedDateTime.from(period.getRangeStart()).isAfter(range.getRangeStart()))) { // calculate duration between this period start and last period end.. final Duration freeDuration = new Duration(lastPeriodEnd, period.getStart()); @@ -452,8 +455,8 @@ public FreeBusy build() { } } - if (period.getEnd().after(lastPeriodEnd)) { - lastPeriodEnd = period.getEnd(); + if (ZonedDateTime.from(period.getEnd()).isAfter(lastPeriodEnd)) { + lastPeriodEnd = ZonedDateTime.from(period.getEnd()); } } return fb; @@ -465,8 +468,8 @@ public FreeBusy build() { * @param components * @return */ - private PeriodList getConsumedTime(final ComponentList components, final DateTime rangeStart, - final DateTime rangeEnd) { + private PeriodList getConsumedTime(final ComponentList components, final Temporal rangeStart, + final Temporal rangeEnd) { final PeriodList periods = new PeriodList(); // only events consume time.. @@ -539,7 +542,7 @@ public final void validate(final boolean recurse) throws ValidationException { } if (dtStart != null && dtEnd != null - && !dtStart.getDate().before(dtEnd.getDate())) { + && !ZonedDateTime.from(dtStart.getDate()).isBefore(ZonedDateTime.from(dtEnd.getDate()))) { throw new ValidationException("Property [" + Property.DTEND + "] must be later in time than [" + Property.DTSTART + "]"); } diff --git a/src/main/java/net/fortuna/ical4j/model/component/VTimeZone.java b/src/main/java/net/fortuna/ical4j/model/component/VTimeZone.java index 657962988..a4acaf251 100644 --- a/src/main/java/net/fortuna/ical4j/model/component/VTimeZone.java +++ b/src/main/java/net/fortuna/ical4j/model/component/VTimeZone.java @@ -46,6 +46,8 @@ import java.io.IOException; import java.net.URISyntaxException; import java.text.ParseException; +import java.time.OffsetDateTime; +import java.time.temporal.Temporal; import java.util.Objects; /** @@ -244,13 +246,13 @@ public final ComponentList getObservances() { * @return the latest applicable timezone observance for the specified date or null if there are no applicable * observances */ - public final Observance getApplicableObservance(final Date date) { + public final Observance getApplicableObservance(final Temporal date) { Observance latestObservance = null; - Date latestOnset = null; + OffsetDateTime latestOnset = null; for (final Observance observance : getObservances()) { - final Date onset = observance.getLatestOnset(date); + final OffsetDateTime onset = observance.getLatestOnset(date); if (latestOnset == null - || (onset != null && onset.after(latestOnset))) { + || (onset != null && onset.isAfter(latestOnset))) { latestOnset = onset; latestObservance = observance; } diff --git a/src/main/java/net/fortuna/ical4j/model/component/VToDo.java b/src/main/java/net/fortuna/ical4j/model/component/VToDo.java index 825cfb582..35678a8eb 100644 --- a/src/main/java/net/fortuna/ical4j/model/component/VToDo.java +++ b/src/main/java/net/fortuna/ical4j/model/component/VToDo.java @@ -44,6 +44,7 @@ import java.io.IOException; import java.net.URISyntaxException; import java.text.ParseException; +import java.time.temporal.Temporal; import java.time.temporal.TemporalAmount; import java.util.Arrays; import java.util.HashMap; @@ -167,7 +168,7 @@ public VToDo(PropertyList properties, ComponentList alarms) { * @param start the start date of the new todo * @param summary the todo summary */ - public VToDo(final Date start, final String summary) { + public VToDo(final Temporal start, final String summary) { this(); getProperties().add(new DtStart(start)); getProperties().add(new Summary(summary)); @@ -179,7 +180,7 @@ public VToDo(final Date start, final String summary) { * @param due the due date of the new todo * @param summary the todo summary */ - public VToDo(final Date start, final Date due, final String summary) { + public VToDo(final Temporal start, final Temporal due, final String summary) { this(); getProperties().add(new DtStart(start)); getProperties().add(new Due(due)); @@ -193,7 +194,7 @@ public VToDo(final Date start, final Date due, final String summary) { * @param duration the duration of the new todo * @param summary the todo summary */ - public VToDo(final Date start, final TemporalAmount duration, final String summary) { + public VToDo(final Temporal start, final TemporalAmount duration, final String summary) { this(); getProperties().add(new DtStart(start)); getProperties().add(new Duration(duration)); diff --git a/src/main/java/net/fortuna/ical4j/model/property/DateProperty.java b/src/main/java/net/fortuna/ical4j/model/property/DateProperty.java index 518d1637b..960f92dde 100644 --- a/src/main/java/net/fortuna/ical4j/model/property/DateProperty.java +++ b/src/main/java/net/fortuna/ical4j/model/property/DateProperty.java @@ -262,18 +262,19 @@ public void validate() throws ValidationException { throw new ValidationException("VALUE parameter [" + value + "] is invalid for DATE-TIME instance"); } + } + + if (date.getTemporal() instanceof ZonedDateTime) { - final DateTime dateTime = (DateTime) date; + ZonedDateTime dateTime = (ZonedDateTime) date.getTemporal(); // ensure tzid matches date-time timezone.. final Parameter tzId = getParameter(Parameter.TZID); - if (dateTime.getTimeZone() != null - && (tzId == null || !tzId.getValue().equals( - dateTime.getTimeZone().getID()))) { + if (tzId == null || !tzId.getValue().equals(dateTime.getZone().getId())) { throw new ValidationException("TZID parameter [" + tzId + "] does not match the timezone [" - + dateTime.getTimeZone().getID() + "]"); + + dateTime.getZone().getId() + "]"); } } else if (getDate() != null) { diff --git a/src/main/java/net/fortuna/ical4j/model/property/DtEnd.java b/src/main/java/net/fortuna/ical4j/model/property/DtEnd.java index 40f5a69df..a8c7de6b3 100644 --- a/src/main/java/net/fortuna/ical4j/model/property/DtEnd.java +++ b/src/main/java/net/fortuna/ical4j/model/property/DtEnd.java @@ -36,6 +36,7 @@ import java.io.IOException; import java.net.URISyntaxException; import java.text.ParseException; +import java.time.temporal.Temporal; /** * $Id$ @@ -166,7 +167,7 @@ public DtEnd(final ParameterList aList, final String aValue) * * @param aDate a date */ - public DtEnd(final Date aDate) { + public DtEnd(final Temporal aDate) { super(DTEND, new Factory()); setDate(aDate); } @@ -177,7 +178,7 @@ public DtEnd(final Date aDate) { * @param time the time of the DtEnd * @param utc specifies whether time is UTC */ - public DtEnd(final Date time, final boolean utc) { + public DtEnd(final Temporal time, final boolean utc) { super(DTEND, new Factory()); setDate(time); setUtc(utc); @@ -189,7 +190,7 @@ public DtEnd(final Date time, final boolean utc) { * @param aList a list of parameters for this component * @param aDate a date */ - public DtEnd(final ParameterList aList, final Date aDate) { + public DtEnd(final ParameterList aList, final Temporal aDate) { super(DTEND, aList, new Factory()); setDate(aDate); } diff --git a/src/main/java/net/fortuna/ical4j/model/property/DtStart.java b/src/main/java/net/fortuna/ical4j/model/property/DtStart.java index 90ee07dce..d3d5b6e73 100644 --- a/src/main/java/net/fortuna/ical4j/model/property/DtStart.java +++ b/src/main/java/net/fortuna/ical4j/model/property/DtStart.java @@ -36,6 +36,7 @@ import java.io.IOException; import java.net.URISyntaxException; import java.text.ParseException; +import java.time.temporal.Temporal; /** * $Id$ @@ -162,7 +163,7 @@ public DtStart(final ParameterList aList, final String aValue) * * @param aDate a date */ - public DtStart(final Date aDate) { + public DtStart(final Temporal aDate) { super(DTSTART, new Factory()); setDate(aDate); } @@ -173,7 +174,7 @@ public DtStart(final Date aDate) { * @param time the time of the DtStart * @param utc specifies whether time is UTC */ - public DtStart(final Date time, final boolean utc) { + public DtStart(final Temporal time, final boolean utc) { super(DTSTART, new Factory()); setDate(time); setUtc(utc); @@ -185,7 +186,7 @@ public DtStart(final Date time, final boolean utc) { * @param aList a list of parameters for this component * @param aDate a date */ - public DtStart(final ParameterList aList, final Date aDate) { + public DtStart(final ParameterList aList, final Temporal aDate) { super(DTSTART, aList, new Factory()); setDate(aDate); } diff --git a/src/main/java/net/fortuna/ical4j/model/property/Due.java b/src/main/java/net/fortuna/ical4j/model/property/Due.java index a88e36d2e..e08919251 100644 --- a/src/main/java/net/fortuna/ical4j/model/property/Due.java +++ b/src/main/java/net/fortuna/ical4j/model/property/Due.java @@ -36,6 +36,9 @@ import java.io.IOException; import java.net.URISyntaxException; import java.text.ParseException; +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.time.temporal.Temporal; /** * $Id$ @@ -100,7 +103,7 @@ public class Due extends DateProperty { public Due() { super(DUE, new Factory()); // defaults to UTC time.. - setDate(new DateTime(true)); + setDate(LocalDateTime.now(ZoneOffset.UTC)); } /** @@ -152,7 +155,7 @@ public Due(final ParameterList aList, final String aValue) * * @param aDate a date */ - public Due(final Date aDate) { + public Due(final Temporal aDate) { super(DUE, new Factory()); setDate(aDate); } @@ -163,7 +166,7 @@ public Due(final Date aDate) { * @param aList a list of parameters for this component * @param aDate a date */ - public Due(final ParameterList aList, final Date aDate) { + public Due(final ParameterList aList, final Temporal aDate) { super(DUE, aList, new Factory()); setDate(aDate); } diff --git a/src/main/java/net/fortuna/ical4j/model/property/Duration.java b/src/main/java/net/fortuna/ical4j/model/property/Duration.java index 282ce9c56..c79e612f2 100644 --- a/src/main/java/net/fortuna/ical4j/model/property/Duration.java +++ b/src/main/java/net/fortuna/ical4j/model/property/Duration.java @@ -37,6 +37,7 @@ import java.io.IOException; import java.net.URISyntaxException; import java.text.ParseException; +import java.time.temporal.Temporal; import java.time.temporal.TemporalAmount; import java.util.Date; @@ -166,12 +167,25 @@ public Duration(final ParameterList aList, final TemporalAmount duration) { * * @param start the starting time for the duration * @param end the end time for the duration + * @deprecated use {@link Duration#Duration(Temporal, Temporal)} */ + @Deprecated public Duration(final Date start, final Date end) { super(DURATION, new Factory()); setDuration(TemporalAmountAdapter.fromDateRange(start, end).getDuration()); } + /** + * Constructs a new duration representing the time between the specified start date and end date. + * + * @param start the starting time for the duration + * @param end the end time for the duration + */ + public Duration(final Temporal start, final Temporal end) { + super(DURATION, new Factory()); + setDuration(TemporalAmountAdapter.from(start, end).getDuration()); + } + /** * @return Returns the duration. */ diff --git a/src/main/java/net/fortuna/ical4j/model/property/RecurrenceId.java b/src/main/java/net/fortuna/ical4j/model/property/RecurrenceId.java index afd2090f5..c9f497fe7 100644 --- a/src/main/java/net/fortuna/ical4j/model/property/RecurrenceId.java +++ b/src/main/java/net/fortuna/ical4j/model/property/RecurrenceId.java @@ -38,6 +38,9 @@ import java.io.IOException; import java.net.URISyntaxException; import java.text.ParseException; +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.time.temporal.Temporal; /** * $Id$ @@ -134,7 +137,7 @@ public class RecurrenceId extends DateProperty { */ public RecurrenceId() { super(RECURRENCE_ID, new Factory()); - setDate(new DateTime()); + setDate(LocalDateTime.now(ZoneOffset.UTC)); } /** @@ -186,7 +189,7 @@ public RecurrenceId(final ParameterList aList, final String aValue) * * @param aDate a date representation of a date or date-time */ - public RecurrenceId(final Date aDate) { + public RecurrenceId(final Temporal aDate) { super(RECURRENCE_ID, new Factory()); setDate(aDate); } @@ -197,7 +200,7 @@ public RecurrenceId(final Date aDate) { * @param aList a list of parameters for this component * @param aDate a date representation of a date or date-time */ - public RecurrenceId(final ParameterList aList, final Date aDate) { + public RecurrenceId(final ParameterList aList, final Temporal aDate) { super(RECURRENCE_ID, aList, new Factory()); setDate(aDate); } diff --git a/src/main/java/net/fortuna/ical4j/transform/rfc5545/VEventRule.java b/src/main/java/net/fortuna/ical4j/transform/rfc5545/VEventRule.java index 6ff266d97..0e76a9fd2 100644 --- a/src/main/java/net/fortuna/ical4j/transform/rfc5545/VEventRule.java +++ b/src/main/java/net/fortuna/ical4j/transform/rfc5545/VEventRule.java @@ -1,6 +1,5 @@ package net.fortuna.ical4j.transform.rfc5545; -import net.fortuna.ical4j.model.Date; import net.fortuna.ical4j.model.Parameter; import net.fortuna.ical4j.model.Property; import net.fortuna.ical4j.model.component.VEvent; @@ -8,7 +7,7 @@ import net.fortuna.ical4j.model.property.DateProperty; import net.fortuna.ical4j.model.property.DtStamp; -import java.util.Calendar; +import java.time.Period; import java.util.List; /** @@ -49,10 +48,7 @@ public void applyTo(VEvent element) { start.getValue().equals(end.getValue())){ if (end instanceof DateProperty) { DateProperty endDate = (DateProperty) end; - Calendar cal = Calendar.getInstance(); - cal.setTime(endDate.getDate()); - cal.add(Calendar.DATE, 1); - endDate.setDate(new Date(cal.getTime())); + endDate.setDate(endDate.getDate().plus(Period.ofDays(1))); } } } diff --git a/src/test/groovy/net/fortuna/ical4j/model/DateRangeTest.groovy b/src/test/groovy/net/fortuna/ical4j/model/DateRangeTest.groovy index 733916815..b7c6dd728 100644 --- a/src/test/groovy/net/fortuna/ical4j/model/DateRangeTest.groovy +++ b/src/test/groovy/net/fortuna/ical4j/model/DateRangeTest.groovy @@ -6,10 +6,10 @@ class DateRangeTest extends Specification { def 'test hashcode equality'() { given: 'a date range' - DateRange range1 = [new DateTime('20140803T120100'), new DateTime('20140804T120100')] + DateRange range1 = [TemporalAdapter.parse('20140803T120100'), TemporalAdapter.parse('20140804T120100')] and: 'a second identical date range' - DateRange range2 = [new DateTime('20140803T120100'), new DateTime('20140804T120100')] + DateRange range2 = [TemporalAdapter.parse('20140803T120100'), TemporalAdapter.parse('20140804T120100')] expect: 'object equality' range1 == range2