Giter VIP home page Giter VIP logo

lib-recur's Introduction

Build
codecov
Confidence

lib-recur

A recurrence processor for Java

This library parses recurrence strings as defined in RFC 5545 and RFC 2445 and iterates the instances. In addition it can be used to build valid recurrence strings in a convenient manner.

Check out the "recurrence expansion as a service" demo at http://recurrence-expansion-service.appspot.com

Please note that the interface of the classes in this library is not finalized yet and subject to change. We're going to refactor this to make it more object-oriented and make more classes immutable (in particular the RecurrenceRule class itself).

Requirements

rfc5545-datetime

RSCALE support

The iterator has support for RSCALE. At this time is supports four calendar scales:

  • GREGORIAN
  • JULIAN
  • ISLAMIC-CIVIL (aka ISLAMICC)
  • ISLAMIC-TBLA

RSCALE is supported in all RFC2445 and RFC5545 modes.

Recurrence Set API

In addition to interpreting recurrence rules, this library provides a set of classes to determine the result of any combination of rrules, rdates and exdates (and exrules, for that matter) as specified in RFC 5545.

Version 0.16.0 introduces a new API that is slightly different from the previous one. The new API fixes a few design issues that made the code more complex than necessary.

There is a new interface called RecurrenceSet that is implemented by a couple of adapters, decorators and composites. A RecurrenceSet represents the set of occurrences of a recurrence rule or list or any combination of them (including exclusions).

RecurrenceSet extends the Iterable interface, so it can be used with any Iterable decorator from the jems2 library and in for loops.

Iterating RRules

The most common use case is probably just iterating the occurrences of recurrence rules. Although you still can do this using the RecurrenceRuleIterator returned by RecurrenceRule.iterator(DateTime), you may be better off using the OfRule adapter that implements the Iterable interface.

Examples

RecurrenceSet occurrences = new OfRule(rrule, startDate);

You can combine this with the First or While decorators from the jems2 library to guard against infinite rules and use it to loop over the occurrences.

for (DateTime occurrence:new First<>(1000, // iterate at most/the first 1000 occurrences
    new OfRule(rrule, startDate))) {
    // do something with occurrence
}
for (DateTime occurrence:new While<>(endDate::isAfter, // stop at "endDate"
    new OfRule(rrule, startDate))) {
    // do something with occurrence
}

Handling first instances that don't match the RRULE

Note that OfRule does not iterate the start date if it doesn't match the RRULE. If you want to iterate any non-synchronized first date, use OfRuleAndFirst instead!

new OfRule(
    new RecurrenceRule("FREQ=YEARLY;BYMONTHDAY=24;BYMONTH=5"),
    DateTime.parse("19820523"))

results in

19820524,19830524,19840524,19850524…

Note that 19820523 is not among the results because it doesn't match the rule as it doesn't fall on the 24th.

However,

new OfRuleAndFirst(
    new RecurrenceRule("FREQ=YEARLY;BYMONTHDAY=24;BYMONTH=5"),
    DateTime.parse("19820523"))

results in

19820523,19820524,19830524,19840524,19850524…

Iterating RDates and ExDates

Similarly, iterating comma separated Date or DateTime lists (i.e. RDATE and EXDATE ) can be done with the OfList adapter.

Example

for (DateTime occurrence:new OfList(timeZone, rdates)) {
    // do something with occurrence
}

Combining multiple Rules and/or Lists

You can merge the occurrences of multiple sets with the Merged class. A Merged RecurrenceSet iterates the occurrences of all given RecurrenceSets in chronological order.

Example

RecurrenceSet merged = new Merged(
    new OfRule(rule, start),
    new OfList(timezone, rdates)
);

The result iterates the occurrences of both, the rule and the rdates in chronological order.

Excluding Exceptions

Exceptions can be excluded by composing occurrences and exceptions using Difference like in

RecurrenceSet withoutExceptions = new Difference(
    new OfRule(rule, start),
    new OfList(timezone, exdates));

This RecurrenceSet contains all the occurrences iterated by the given rule, except those in the exdates list. Note that these must be exact matches, i.e. the exdate 20240216 does not result in the exclusion of 20240216T120000 nor of 20240216T000000.

Fast forwarding

Sometimes you might want to skip all the instances prior to a given date. This can be achieved by applying the FastForwarded decorator like in

RecurrenceSet merged = new FastForwarded(
    fastForwardToDate,
    new Merged(
        new OfRule(rule, start),
        new OfList(timezone, rdates)));

Note, that new FastForwarded(fastForwardTo, new OfRule(rrule, start)) and new OfRule(rrule, fastForwardTo) are not necessarily the same set of occurrences.

Dealing with infinite rules

Be aware that RRULEs are infinite if they specify neither COUNT nor UNTIL. This might easily result in an infinite loop if not taken care of.

As stated above, a simple way to deal with this is by applying a decorator like First or While from the jems2 library:

RecurrenceRule rule = new RecurrenceRule("FREQ=YEARLY;BYMONTHDAY=23;BYMONTH=5");
DateTime start = new DateTime(1982, 4 /* 0-based month numbers! */,23);
for (DateTime occurrence:new First<>(1000, new OfRule(rule, start))) {
    // do something with occurrence    
}

This will always stop iterating after at most 1000 instances.

Limiting RecurrenceSets

You can limit a RecurrenceSet to the instances that precede a certain DateTime using the Preceding decorator. This can also serve as a way to handle infinite rules:

RecurrenceRule rule = new RecurrenceRule("FREQ=MONTHLY;BYMONTHDAY=23");
DateTime start = new DateTime(1982, 4 /* 0-based month numbers! */,23);
for (DateTime occurrence:new Preceding<>(
    new DateTime(1983, 0, 1), // all instances before 1983
    new OfRule(rule, start))) {
    // do something with occurrence    
}

The Within decorator combines Preceding and FastForwarded and only iterates occurrences that fall in the given (right-open) interval.

// a RecurrenceSet that only contains occurrences in 2024
// (assuming the original iterates all-day values)
RecurrenceSet occurrencesOf2024 = new Within(
    DateTime.parse("20240101"),
    DateTime.parse("20250101"),
    recurrenceSet
);

Note, in both cases you must take care that the dates you supply have the same format (floating vs all-day vs absolute) as the occurrences of your recurrence set.

Determining the last instance of a RecurrenceSet

Finite, non-empty RecurrenceSets have a last instance that can be determined with the LastInstance adapter. LastInstance is an Optional of a DateTime value that's present when the given RecurrenceSet is finite and non-empty.

Example

new LastInstance(new OfRule(new RecurrenceRule("FREQ=DAILY;COUNT=10"), startDate));

RFC 5545 Instance Iteration Example

In a recurring VEVENT you might find RRULEs, RDATEs, EXDATEs and (in RFC 2445) EXRULEs. Assuming you have all these in variables with these respective names the RecurrenceSet might be constructed like in

RecurrenceSet occurrences = new Difference(
    new Merged(
        new OfRule(new RecurrenceRule(rrule), dtstart),
        new OfList(timezone, rdates)
    ),
    new Merged(
        new OfRule(new RecurrenceRule(exrule), dtstart),
        new OfList(timezone, exdates)
    )
);

Strict and lax parsing

By default, the parser is very tolerant and accepts all rules that comply with RFC 5545. You can use other modes to ensure a certain compliance level:

	RecurrenceRule rule1 = new RecurrenceRule("FREQ=WEEKLY;BYWEEKNO=1,2,3,4;BYDAY=SU", RfcMode.RFC2445_STRICT);
	// -> will throw an InvalidRecurrenceRuleExceptionException because in RFC 2445 BYWEEKNO is only valid in
	// combination with YEARLY rules

	RecurrenceRule rule2 = new RecurrenceRule("FREQ=WEEKLY;BYWEEKNO=1,2,3,4;BYDAY=SU", RfcMode.RFC2445_LAX);
	// -> will iterate Sunday in the first four weeks of the year

	RecurrenceRule rule3 = new RecurrenceRule("FREQ=WEEKLY;BYWEEKNO=1,2,3,4;BYDAY=SU", RfcMode.RFC5545_STRICT);
	// -> will throw an InvalidRecurrenceRuleExceptionException because in RFC 5545 BYWEEKNO is only valid in
	// combination with YEARLY rules

	RecurrenceRule rule4 = new RecurrenceRule("FREQ=WEEKLY;BYWEEKNO=1,2,3,4;BYDAY=SU", RfcMode.RFC5545_LAX);
	// -> will iterate Sunday in the first four weeks of the year

	RecurrenceRule rule5 = new RecurrenceRule("BYWEEKNO=1,2,3,4;BYDAY=SU;FREQ=WEEKLY", RfcMode.RFC2445_STRICT);
	// -> will throw an InvalidRecurrenceRuleExceptionException because in RFC 2445 the rule must start with "FREQ="

	RecurrenceRule rule6 = new RecurrenceRule("FREQ=MONTHLY;BYMONTH=4;", RfcMode.RFC2445_STRICT);
	// -> will throw an InvalidRecurrenceRuleExceptionException because the trailing ";" is invalid

The default mode for parsing rules is RfcMode.RFC5545_LAX. To support as many rules as possible use RfcMode.RFC2445_LAX;

Building rules

To build a rule you have to specify a base frequency and optionally an RfcMode. Then you can start adding BY* rules.

	RecurrenceRule rule = new RecurrenceRule(Freq.MONTHLY); // will create a new rule using RfcMode.RFC5545_STRICT mode

	rule.setCount(20);

	// note that unlike with java.util.Calendar the months in this list are 1-based not 0-based
	rule.setByRule(Part.BYMONTH, 1, 3, 5, 7);

	rule.setByRule(Part.BYMONTHDAY, 4, 8, 12);

	/*
	 * Alternatively set the values from a list or an array:
	 */ 
	Integer[] dayArray = new Integer[]{4, 8, 12};
	rule.setByRule(Part.BYMONTHDAY, dayArray);
	
	List<Integer> dayList = Arrays.asList(dayArray);
	rule.setByRule(Part.BYMONTHDAY, dayList);

	String ruleStr = rule.toString(); 
	// ruleStr is "FREQ=MONTHLY;BYMONTH=1,3,5,7;BYMONTHDAY=4,8,12;COUNT=20"

Related work

There are at least two other implentations of recurrence iterators for Java:

TODO

  • Add more tests
  • Add tests for edge cases
  • Add an RRuleBuilder to build RRULEs and make RecurrenceRule immutable
  • Add support for more calendar scales
  • Fix handling of calendar scales with leap months
  • Fix RecurrenceRule.toString() when RSCALE is set
  • Add validator and a validator log

License

Copyright (c) Marten Gajda 2024, licensed under Apache2.

lib-recur's People

Contributors

dmfs avatar e-malagon avatar janusvm avatar kielni avatar showstopper avatar yahrens avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

lib-recur's Issues

FREQ=YEARLY + BYWEEKNO + BYYEARDAY gives wrong results

The following rule

FREQ=YEARLY;BYWEEKNO=1,3,4,5,8,9;BYYEARDAY=1,14,31,60

Returns only January 1st of certain years:

{"instances":["20240101","20290101","20350101","20410101","20460101","20520101","20570101","20630101","20690101","20740101","20800101","20850101","20910101","20970101"],"info":{"nanoseconds_per_instance":79882.07,"number_of_instances_in_result":14,"parser_mode":"RFC5545_LAX","iterator_nanos":1118349,"rule_parser_nanos":77362,"total_nanos":1195711,"lib-recur-version":"0.10.2","has_next":true,"is_infinite":true}}

but we would expect something like

2018-01-01, 2018-01-31, 2018-03-01, 2019-01-01, 2019-01-14, 2019-01-31, 2019-03-01, 2020-01-01, 2020-01-14, 2020-01-31
// Any day that is 1st, 14th, 31st or 60th of a year and also falls in week number 1, 3, 4, 5, 8, or 9.

Invalid characters '+' and '-' don't trigger an exception in Calender.parse

Speaking about

public static Calendar parse(TimeZone timeZone, String string)
.

Integer.parseInt allows signs in the value strings. These are, however, invalid in time stamps. Calendar.parse should throw an IllegalArgumentException.

[EDIT] Testcases:

    @Test(expected = IllegalArgumentException.class)
    public void testInvalidPlusSign()
    {
        new TestDate("+2011122T223344Z").setDate(201, Calendar.NOVEMBER, 22).testEvent();
    }


    @Test(expected = IllegalArgumentException.class)
    public void testInvalidMinusSign()
    {
        new TestDate("-2011122T223344Z").setDate(201, Calendar.NOVEMBER, 22).testEvent();

    }

[RFC5545_Strict] Parser allows an integer in BYDAY with illegal conditions

These conditions are

  • Integer must only be set when FREQ is YEARLY or MONTHLY

The BYDAY rule part MUST NOT be specified with a numeric value when the FREQ rule part is not set to MONTHLY or YEARLY.

  • Integer must not be set when FREQ is YEARLY and BYWEEKNO is set

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.

In RFC5545_Strict mode we have to throw an exception, in all other cases we just drop it.

Obtaining end time or duration of rrule instances

In this example:

DTSTART;TZID=America/Chicago:20160120T060000
DTEND;TZID=America/Chicago:20160120T070000
RRULE:FREQ=WEEKLY;COUNT=10;BYDAY=WE

It seems like using the existing API, we can get a list of start DateTime(s) from RecurrenceRuleIterator.nextDateTime()

However, there is no obvious way to get the end DateTime for each instance. To do that, we would need to either calculate the duration between DTSTART and DTEND manually (is there support for creating a DURATIOn from two DateTime(s)?) or to create two RecurrenceRuleIterators using rRule.iterator(DTSTART) for the first one and rRule.iterator(DTEND) for the second one, then co-iterate them.

Is there a cleaner way to do this? would it be a good idea to return a structure with start,end from RecurrenceRuleIterator.getNextDateTime()

Hamcrest Matchers

In order to write better tests more easily, I want Hamcrest Matchers to validate the recurrence results.

RecurrenceSetIterator.next() returns next date/times out of order.

I discovered that when using two or more recurrence rules within a single RecurrenceSetIterator, calling next() on the iterator sometimes returns the next few date/times out of order.

A reproducible example is below, where we have two recurrence rules (every 5 hours and every day), both with a start time of 1/1/2019 at 12am. When fast-forwarding the iterator to 10pm, next() returns 1/2/2019 at 1am instead of 1/2/2019 at 12am. Calling next() again, however, returns 1/2/2019 at 12am, which is backwards in time from what it returned previously.

import org.junit.Test;

@Test
public void lib_recur_test() throws InvalidRecurrenceRuleException {

  // We are calculating for the next recurrence after 1/1/2019 at 10pm.
  ZoneId localTimeZone = ZoneId.of("UTC");
  final ZonedDateTime timeOfCalculation = ZonedDateTime.of(2019, 1, 1, 22, 0, 0, 0, localTimeZone);

  // Combine all Recurrence Rules into a RecurrenceSet
  RecurrenceSet ruleSet = new RecurrenceSet();
  ruleSet.addInstances(new RecurrenceRuleAdapter(new RecurrenceRule("FREQ=HOURLY;INTERVAL=5")));
  ruleSet.addInstances(new RecurrenceRuleAdapter(new RecurrenceRule("FREQ=DAILY;INTERVAL=1")));

  ZonedDateTime startDateTime = ZonedDateTime.of(2019, 1, 1, 0, 0, 0, 0, localTimeZone);
  long startTimeEpochMillis = startDateTime.toInstant().toEpochMilli();

  // Create an iterator using the RecurrenceSet
  RecurrenceSetIterator it = ruleSet.iterator(TimeZone.getTimeZone(localTimeZone), startTimeEpochMillis);

  // Fast forward to the time of calculation (1/1/2019 at 10pm).
  it.fastForward(timeOfCalculation.toInstant().toEpochMilli());

  if (it.hasNext()) {

    long nextRunTimeEpochMillis = it.next();

    // The next run time should be 1/2/2019 at 12am, due to the Daily recurrence rule defined above.
    ZonedDateTime expectedNextRunTime = ZonedDateTime.of(2019, 1, 2, 0, 0, 0, 0, localTimeZone);

    // However, the calculated next run time returns 1am.
    ZonedDateTime calculatedNextRunTime = ZonedDateTime.ofInstant(Instant.ofEpochMilli(nextRunTimeEpochMillis), localTimeZone);

    // Fails
    Assert.assertEquals(expectedNextRunTime, calculatedNextRunTime);
  }
}

Unknown keywords trigger an IllegalArgumentException

In most parts of parser we throw an InvalidRecurrenceRuleException. In order to stay consistent we should also do so here.

Test: (BYMONHT is unkown)

"FREQ=YEARLY;BYMONHT=2"

Current exception:

java.lang.IllegalArgumentException: No enum constant org.dmfs.rfc5545.recur.RecurrenceRule.Part.BYMONHT

EXDATEs Don't Seem to Work

TL;DR: Exception Dates added via RecurrenceSet.addExceptions(...) don't seem to actually prevent iteration to recurrences that would normally occur on added dates.

For example, assume I have a RecurrenceSet object named ruleSet. I first call ruleSet.addInstances(...) on my set object to add a simple Recurrence Rule such as FREQ=DAILY;COUNT=5. I then call something like ruleSet.addExceptions(new RecurrenceList("20190125", null)) to add a single Exception Date (January 25, 2019) to my ruleSet.

If I then use ruleSet.iterator(...) to get an iterator, starting on January 24, 2019 (at whatever time the Event starts at), and proceed to iterate over the recurrences it provides, I will still end up being handed a timestamp corresponding to January 25, 2019 (at whatever time the Event starts at) in the time zone I provided.

This seems to be broken functionality. Further, I looked through the tests for this library and couldn't find any that actually test ExDates. I'm wondering if I am using this feature of the library wrong, or if this is a bug?

RecurrenceSetIterator.next() returns next date/times out of order.

I discovered that when using two or more recurrence rules within a single RecurrenceSetIterator, calling next() on the iterator sometimes returns the next few date/times out of order.

A reproducible example is below, where we have two recurrence rules (every 5 hours and every day), both with a start time of 1/1/2019 at 12am. When fast-forwarding the iterator to 10pm, next() returns 1/2/2019 at 1am instead of 1/2/2019 at 12am. Calling next() again, however, returns 1/2/2019 at 12am, which is backwards in time from what it returned previously.

` // We are calculating for the next recurrence after 1/1/2019 at 10pm.
ZoneId localTimeZone = ZoneId.of("UTC");
final ZonedDateTime timeOfCalculation = ZonedDateTime.of(2019, 1, 1, 22, 0, 0, 0, localTimeZone);

// Combine all Recurrence Rules into a RecurrenceSet
RecurrenceSet ruleSet = new RecurrenceSet();
ruleSet.addInstances(new RecurrenceRuleAdapter(new RecurrenceRule("FREQ=HOURLY;INTERVAL=5")));
ruleSet.addInstances(new RecurrenceRuleAdapter(new RecurrenceRule("FREQ=DAILY;INTERVAL=1")));

ZonedDateTime startDateTime = ZonedDateTime.of(2019, 1, 1, 0, 0, 0, 0, localTimeZone);
long startTimeEpochMillis = startDateTime.toInstant().toEpochMilli();

// Create an iterator using the RecurrenceSet
RecurrenceSetIterator it = ruleSet.iterator(TimeZone.getTimeZone(localTimeZone), startTimeEpochMillis);

// Fast forward to the time of calculation (1/1/2019 at 10pm).
it.fastForward(timeOfCalculation.toInstant().toEpochMilli());

if (it.hasNext()) {
  long nextRunTimeEpochMillis = it.next();

  // The next run time should be 1/2/2019 at 12am, due to the Daily recurrence rule defined above.
  ZonedDateTime expectedNextRunTime = ZonedDateTime.of(2019, 1, 2, 0, 0, 0, 0, localTimeZone);

  // However, the calculated next run time returns 1am. 
  ZonedDateTime calculatedNextRunTime = ZonedDateTime.ofInstant(Instant.ofEpochMilli(nextRunTimeEpochMillis), localTimeZone);

  // Fails
  Assert.assertEquals(expectedNextRunTime, calculatedNextRunTime);
}`

Missing date

We expect the following code to generate the 2017-08-07:

RecurrenceRule rule = new RecurrenceRule("FREQ=WEEKLY;INTERVAL=1;BYDAY=MO");
DateTime ruleStartDate = new DateTime(2017, 1 - 1, 13, 0,0,0);
DateTime iteratorStartDate = new DateTime(2017, 8 - 1, 7, 0,0,0);
DateTime iterationEndDate = new DateTime(2017, 8 - 1, 8, 23, 59,59);
rule.setUntil(iterationEndDate);

RecurrenceRuleIterator it = rule.iterator(ruleStartDate);
it.fastForward(iteratorStartDate);

while (it.hasNext()) {
    DateTime nextInstance = it.nextDateTime();
    System.out.println(nextInstance);
}

But unfortunately it doesn't.

If we change iteratorStartDate to 2017-07-31 we get the expected list of
2017-07-31
2017-08-07

This seem to happen when the day of month is something less than 8 in every month in 2017. But this is only a guess. Do you have an idea what happens here?

SKIP not properly applied for BYYEARDAY and BYWEEKNO

RFC 7529 states:

Only a few types of recurrence patterns are likely to need the use of
"SKIP". The following is a list of some situations where it might be
needed:

7. A "BYYEARDAY" element in an "RRULE" has an absolute value greater
than the smallest number of days in any year in the specified
calendar system.

  1. A "BYWEEKNO" element in an "RRULE" has an absolute value greater
    than the smallest number of weeks in any year in the specified
    calendar system.

These cases are not properly handled yet:

20150213T000000
FREQ=YEARLY;RSCALE=GREGORIAN;SKIP=BACKWARDS;BYYEARDAY=366
=> "20161231T000000","20201231T000000","20241231T000000","20281231T000000","20321231T000000","20361231T000000"
20150213T000000
FREQ=YEARLY;RSCALE=GREGORIAN;SKIP=BACKWARDS;BYWEEKNO=53
=> "20210101T000000","20260102T000000","20320102T000000","20370102T000000","20430102T000000"

In both cases the results should occur once per year

Rule with timezone specification

Hello,

Here's a rule that we are receiving from an another system/library:
WKST=MO;FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR,SU;INTERVAL=1;BYHOUR=21;BYMINUTE=0;DTSTART=20161022T210000Z;TIMEZONE=Europe/Helsinki

The written version would be something like this: "Repeat on Mo,Tu,We,Th,Fr,Su at 21:00 EEST (+0300), starting from 22 Oct 2016 21:00:00 UTC".

Lib-recur seems to get a bit confused with timezones when parsing this rule and it produces occurrences such as 20161023T210000 (Mon Oct 24 00:00:00 EEST 2016).

Here's an example (Scala) code which I use to initialize the recurrence rule.
val rr = new RecurrenceRule(rrule) val start = new DateTime(starting.getTimeInMillis) val rrIter = rr.iterator(start) if(rrIter.hasNext) rrIter.nextDateTime() ...

br,
Timo

(I'm not entirely sure that if the rule we get from the another lib is actually standards compliant)

Split ByDayFilter

In order to make the code simpler (and potentially faster), I want the ByDayFilter to by split into four classes.

Benchmark

In order to judge the effect of changes on performance, as a developer I want a way to benchmark the recurrence iteration.

RecurrenceSet always returns start date as the first DateTime if that date (event) doesn't occur in rrule

Hi, I have the following rule
String rrule="FREQ=DAILY;BYDAY=SA,SU;UNTIL=20171130T000000Z" and my start date is "20171101T000000Z"

Here start date is 1 November 2017 and end date is 30 November 2017 now the event will be occur only on saturday and sunday as per rrule mean (4,5,11,12,18,19,25,26Nov) but Problem is that event is also occuring for 1 November where day is Wednesday which should not be happen. Please tell me why I am getting 1 November event. Below is my code ,please check and provide the best solution for this

   DateTime firstInstance = DateTime.parse("20171101T000000Z");
   RecurrenceRule rRuleForSession = null;
                    try {
                        rRuleForSession = new RecurrenceRule(rrule);
                    } catch (InvalidRecurrenceRuleException e) {
                        e.printStackTrace();
                    }
    RecurrenceSet rset = new RecurrenceSet();
    rset.addInstances(new RecurrenceRuleAdapter(rRuleForSession));
   rset.addExceptions(new RecurrenceList("20171130T000000Z", firstInstance.getTimeZone()));

   RecurrenceSetIterator itrRecurrence = 
   rset.iterator(firstInstance.getTimeZone(),firstInstance.getTimestamp());
                    
             int maxInstance = 1;
                    while (itrRecurrence.hasNext() && --maxInstance >= 0) {
                        long nextInstance = itrRecurrence.next();
                        DateTime dateTime = new DateTime(TimeZone.getDefault(), nextInstance);
                        Toast.makeText(baseActivity,dateTime.toString(),Toast.LENGTH_SHORT).show();
                         }

error in BYWEEKNO expansion

BYWEEKNO expansion does't work correctly when expanding a day which technically belongs to the previous or next year.
Example

RRULE:FREQ=YEARLY;BYWEEKNO=1
DTSTART:20180101

expands to:

"20180101","20191231","20201230","20210104","20220103","20230102","20240101"

Note the instance 20191231 which should be 20181213 because the expansion inherits the day of week, which is Monday in this case. Monday of the first week of 2019 is actually December 12 2018 though.

[RFCxxxx_Lax] Handling multiple ocurrences of the same BYxxx rule

Example:

"FREQ=YEARLY;BYMONTH=2;BYMONTH=3;BYMONTH=4"

We have several options here.

  1. Throw an exception (probably nonsense since we're in Lax mode)
  2. Either pick the first or the last one, ie. "BYMONTH=2" or "BYMONTH=4"
  3. Merge them together into one list, ie. "BYMONTH=2,3,4"

add method to skip all instances before a specific date

For the instance expansion in the task provider we need a method to fast forward instances, i.e. skip all instances until a specific date.

The following method will provide that feature:

/**
 * Skip all instances till a specific date.
 * <p>
 * <strong>Note:</strong> After calling this method you should call {@link #hasNext()} before you continue because there might no more instances left if there is an UNTIL or COUNT part in the rule.
 * </p>
 * 
 * @param until
 *              The earliest date to be returned by {@link #next()}.
 */
public void skip(Calendar until)

Time change isn't handled correctly

When switching to DST ("Sommerzeit") in Europe/Berlin 2:30AM must not occur.

Test-rule:

FREQ=YEARLY;UNTIL=20200101T010101Z;BYMONTH=3;BYDAY=-1SU;BYHOUR=2;BYMINUTE=30

Test-case:

mTestRules.add(new TestRule("FREQ=YEARLY;UNTIL=20200101T010101Z;BYMONTH=3;BYDAY=-1SU;BYHOUR=2;BYMINUTE=30")
            .setStart("20120101T010101", "Europe/Berlin").setInstances(1));

Right now we get 9 instead of 1.

"RRULE:" is missing

It seems that lib-recur is missing substring "RRULE:" before any recurrent rule as in rfc5545 doc

Parser does not accept valid RecurrenceRule (RFC2445_STRICT)

When passing a valid ReccurenceRule, e.g.

"FREQ=WEEKLY;BYDAY=MO"

the parser throws an InvalidRecurrenceRuleException:

org.dmfs.rfc5545.recur.InvalidRecurrenceRuleException: RFC 2445 requires FREQ to be the first part of the rule: FREQ=WEEKLY;BYDAY=MO

Change InvalidRecurrenceRuleException to be a RuntimeException

It would be great if InvalidRecurrenceRuleException could be changed to be a RuntimeException so it does not have to be handled explicitly every time a rule String is parsed. I use validators before processing them, so an invalid rule is an application error in my case.

Help: Can't understand exception thrown

Hi! I found your library and it's just exactly what I need, but for some reason I can seem to create a correct RecurrenceRule.

This is what I'm writing:

RecurrenceRule rule = new RecurrenceRule(Freq.WEEKLY);

try {
    rule.setByPart(Part.INTERVAL, 1);
    Log.d(TAG, rule.toString());
} catch (InvalidRecurrenceRuleException e) {
   e.printStackTrace();
}

But this exception is thrown every time and I can't print a correct rule:

screen shot 2016-07-11 at 2 19 36 pm

I'm using lib-recur-0.9.3.jar and following some sample code from this repo readme, I'll be grateful if you can point to a correct direction. Thanks!

add peek() method to RecurrenceIterator

In some use cases it might come in handy to be able to peek at the next instance without actually iterating it.

The following method will provide that feature:

/**
 * Peek at the next instance to be returned by {@link #next()} without actually iterating it.
 * Calling this method (even multiple times) won't affect the instances returned by {@link #next()}.
 * 
 * @return the upcoming instance or <code>null</code> if there are no more instances.
 */
public Calendar peek()

RecurrenceRuleIterator always returns start date as the first DateTime in the set

Hello!

I have the following rule:

FREQ=MONTHLY;BYDAY=FR;BYMONTH=3,6,9,12;BYSETPOS=-1

The rule describes an event that should occur on last Friday of March, June, September and December. The rule is correct - checked it with various tools.

For the start date of the iterator I set the first day of the 27th week of the year, which is: 07/06/2015.
The first date the iterator returns is the start date, which is clearly not the one described by the rule.

Only the First RecurrenceSet.addException(...) Call's EXDATEs Respected

Assume I have a RecurrenceSet object named set and some arbitrary TimeZone object called timeZone.

If I call set.addExceptions("10290128T030720,2019127T030720", timeZone); and then get an iterator over the recurrence set, occurrences at both instances are excluded.

However, if I call set.addExceptions("10290128T030720", timeZone); followed by set.addExceptions("2019127T030720", timeZone); and then get an iterator over the recurrence set, only the first occurrence (at 10290128T030720) is excluded. The second one (at 10290128T030720) is still returned as an occurrence by the iterator.

Is this intended behavior? If so, how would I go about specifying EXDATEs taking place in multiple different time zones?

additional event if DTSTART hour is different from RRULE byhour

RecurrenceRule r11 = new RecurrenceRule("FREQ=WEEKLY;BYHOUR=10;COUNT=10", RecurrenceRule.RfcMode.RFC5545_LAX);
DateTime start = new DateTime(TimeZone.getTimeZone("America/Chicago"),2016, 1, 1,9,0,0);

RecurrenceRuleIterator rri = r11.iterator(start);
while(rri.hasNext()) {
        System.out.println(rri.nextDateTime());
}

Produces:

20160201T090000
20160201T100000
20160208T100000
20160215T100000
20160222T100000
20160229T100000
20160307T100000
20160314T100000
20160321T100000
20160328T100000

Question: Iterating in reverse

Hello. Is it possible to iterate over the events in reverse order? I don't see any code for this and RecurrenceRuleIterator seems to work only in one direction. If it's not possible, could you give me a hint how I could implement that myself?

RecurrenceRuleIterator returns date with 1 hour difference with relation to TimeZone.

Recurrence rule code that I am using is:
`
RecurrenceRule rule = null;

    TimeZone timeZone = TimeZone.getTimeZone("Asia/Dhaka");

    print(timeZone.toString());


    SimpleDateFormat formatter = new SimpleDateFormat("yyyyMMdd'T'HHmmss", Locale.US);

    String start = "20190603T000000";

    String end = "20190616T000000";

    DateTime startDateTime = new DateTime(DateUtils.getTimeInMilliseconds(start, formatter));

    DateTime endDateTime = new DateTime(DateUtils.getTimeInMilliseconds(end, formatter));

    try {
        rule = new RecurrenceRule("FREQ=WEEKLY;INTERVAL=1;WKST=MO;BYDAY=TU");
    } catch (InvalidRecurrenceRuleException e) {
        print(e.toString());
    }

    rule.setUntil(endDateTime);

    RecurrenceRuleIterator it = rule.iterator(startDateTime);

    int maxInstances = 100; // limit instances for rules that recur forever

    while (it.hasNext() && (!rule.isInfinite() || maxInstances-- > 0)) {
        DateTime nextInstance = it.nextDateTime();
        print(maxInstances + " -- " + nextInstance.toString());
    }

`

Expected results:

1 | Mon, | 03 | Jun | 2019 | 23:30:00 |
2 | Mon, | 10 | Jun | 2019 | 23:30:00 |

Result I am getting:

20190604T003000Z
20190611T003000Z

There is a hour difference between the results. Can you please help me in this regard?

Using a DTSTART per rule with RecurrenceSet

I have a list of rrules. Each rrule may have a DTSTART and UNTIL. But it seems that lib-recur RecurrenceSet does not support this, as it is assumed there is only one DTSTART for the set.

How can I use one DTSTART per rule in RecurrenceSet?

Hour, Minute and Second are not expanded properly

Given a time string with a freq > hourly, the BY[HOUR, MINUTE, SECOND] parameter is not interpreted correctly.

Example:

"FREQ=MONTHLY;UNTIL=20120131T120000;BYHOUR=12"

with the start date

"20120101T000000"

should result in 31 + 1 instances. However, only 2 instances are created.

Seconds value in occurrences is not set according to DTSTART

Hi,

with this rule (BYSECOND missing): WKST=MO;FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR,SU;INTERVAL=1;BYHOUR=21;BYMINUTE=0;DTSTART=20161022T210000Z

the iterated occurrences have timestamps with invalid seconds value, for example 20161024T210048, should be 20161024T210000?

br,
Timo

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.