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

org.dmfs.rfc5545.recur.RecurrenceRuleIterator Maven / Gradle / Ivy

/*
 * Copyright (C) 2013 Marten Gajda 
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package org.dmfs.rfc5545.recur;

import org.dmfs.rfc5545.DateTime;
import org.dmfs.rfc5545.Instance;
import org.dmfs.rfc5545.InstanceIterator;
import org.dmfs.rfc5545.calendarmetrics.CalendarMetrics;
import org.eclipse.jdt.annotation.NonNull;

import java.util.TimeZone;


/**
 * An iterator for recurrence rules. 

Note: Some rules may recur forever, so be sure to add some limitation to your code that stops * iterating after a certain number of instances or at a certain date.

* * @author Marten Gajda */ public final class RecurrenceRuleIterator implements InstanceIterator { /** * The previous iterator instance. This is null for the {@link FreqIterator}. */ private final RuleIterator mRuleIterator; /** * The first event to iterate; */ /** * The upcoming instance, if any. */ private long mNextInstance; /** * Whether the start instance is an all day instance. */ private final boolean mAllDay; /** * The time zone oft the start instance or null if it's a floating time */ private final TimeZone mTimeZone; /** * Caches the upcoming millis after a call to {@link #peekMillis()}. This may be {@link Long#MIN_VALUE} if the millis have not been calculated yet. */ private long mNextMillis = Long.MIN_VALUE; /** * Caches the upcoming {@link DateTime} value after a call to {@link #peekDateTime()}, so we don't have to build it twice. */ private DateTime mNextDateTime = null; /** * The {@link CalendarMetrics} of the calendar scale to use. */ private final CalendarMetrics mCalendarMetrics; /** * Creates a new {@link RecurrenceRuleIterator} that gets its input from ruleIterator. * * @param ruleIterator The last {@link RuleIterator} in the chain of iterators. * @param start The first instance to iterate. */ RecurrenceRuleIterator(RuleIterator ruleIterator, DateTime start, CalendarMetrics calendarMetrics) { mRuleIterator = ruleIterator; mAllDay = start.isAllDay(); mCalendarMetrics = calendarMetrics; mTimeZone = start.isFloating() ? null : start.getTimeZone(); fetchNextInstance(); } private void fetchNextInstance() { mNextInstance = mRuleIterator.next(); // invalidate mNextMillis mNextMillis = Long.MIN_VALUE; mNextDateTime = null; } /** * Get the next instance. The instances are guaranteed to be strictly increasing in time. * * @return A time stamp of the next instance. */ public long nextMillis() { if (mNextInstance == Long.MIN_VALUE) { throw new ArrayIndexOutOfBoundsException("No more instances to iterate."); } long result = mNextMillis; if (result == Long.MIN_VALUE) { result = mCalendarMetrics.toMillis(mNextInstance, mTimeZone); } fetchNextInstance(); return result; } /** * Get the next instance. The instances are guaranteed to be strictly increasing in time. * * @return A {@link DateTime} object for the next instance. */ public DateTime nextDateTime() { if (mNextInstance == Long.MIN_VALUE) { throw new ArrayIndexOutOfBoundsException("No more instances to iterate."); } long nextInstance = mNextInstance; DateTime nextDateTime = mNextDateTime; fetchNextInstance(); if (nextDateTime != null) { return nextDateTime; } if (mAllDay) { return new DateTime(mCalendarMetrics, Instance.year(nextInstance), Instance.month(nextInstance), Instance.dayOfMonth(nextInstance)); } else { return new DateTime(mCalendarMetrics, mTimeZone, Instance.year(nextInstance), Instance.month(nextInstance), Instance.dayOfMonth(nextInstance), Instance.hour(nextInstance), Instance.minute(nextInstance), Instance.second(nextInstance)); } } public boolean hasNext() { return mNextInstance != Long.MIN_VALUE; } @Override public DateTime next() { return nextDateTime(); } /** * Peek at the next instance to be returned by {@link #nextMillis()} without actually iterating it. Calling this method (even multiple times) won't affect * the instances returned by {@link #nextMillis()}. * * @return the upcoming instance or null if there are no more instances. */ public long peekMillis() { if (mNextInstance == Long.MIN_VALUE) { throw new ArrayIndexOutOfBoundsException("No more instances to iterate."); } long result = mNextMillis; if (result == Long.MIN_VALUE) { // next millis not calculated yet result = mNextMillis = mCalendarMetrics.toMillis(mNextInstance, mTimeZone); } return result; } /** * Peek at the next instance to be returned by {@link #nextDateTime()} without actually iterating it. Calling this method (even multiple times) won't affect * the instances returned by {@link #nextDateTime()}. * * @return the upcoming instance or null if there are no more instances. */ public DateTime peekDateTime() { if (mNextInstance == Long.MIN_VALUE) { throw new ArrayIndexOutOfBoundsException("No more instances to iterate."); } long nextInstance = mNextInstance; if (mAllDay) { return mNextDateTime = new DateTime(mCalendarMetrics, Instance.year(nextInstance), Instance.month(nextInstance), Instance.dayOfMonth(nextInstance)); } else { return mNextDateTime = new DateTime(mCalendarMetrics, mTimeZone, Instance.year(nextInstance), Instance.month(nextInstance), Instance.dayOfMonth(nextInstance), Instance.hour(nextInstance), Instance.minute(nextInstance), Instance.second(nextInstance)); } } /** * Skip the given number of instances.

Note: After calling this method you should call {@link #hasNext()} before you continue because * there might be less than skip instances left when you call this.

* * @param skip The number of instances to skip. */ public void skip(int skip) { if (skip == 0) { return; } if (skip < 0) { throw new IllegalArgumentException("Can not skip backbards"); } RuleIterator iterator = mRuleIterator; long instance; do { instance = iterator.next(); } while (--skip > 0); mNextInstance = instance; // invalidate mNextMillis mNextMillis = Long.MIN_VALUE; mNextDateTime = null; } /** * Skip all instances up to a specific date.

Note: 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.

* * @param until The time stamp of earliest date to be returned by the next call to {@link #nextMillis()} or {@link #nextDateTime()}. */ public void fastForward(long until) { if (!hasNext()) { return; } // convert until to an instance long untilInstance = mCalendarMetrics.toInstance(until, mTimeZone); long next = Instance.maskWeekday(mNextInstance); if (untilInstance <= next) { // nothing to do return; } RuleIterator iterator = mRuleIterator; iterator.fastForward(untilInstance); while (next != Long.MIN_VALUE && next < untilInstance) { next = iterator.next(); } mNextInstance = next; // invalidate mNextMillis mNextMillis = Long.MIN_VALUE; mNextDateTime = null; } /** * Skip all instances up to a specific date.

Note: 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.

* * @param until The earliest date to be returned by the next call to {@link #nextMillis()} or {@link #nextDateTime()}. */ public void fastForward(@NonNull DateTime until) { if (!hasNext()) { return; } DateTime untilDate = until.isAllDay() ? until.startOfDay().shiftTimeZone(mTimeZone) : until.shiftTimeZone(mTimeZone); // convert until to an instance long untilInstance = untilDate.getInstance(); long next = Instance.maskWeekday(mNextInstance); if (untilInstance <= next) { // nothing to do return; } RuleIterator iterator = mRuleIterator; iterator.fastForward(untilInstance); while (next != Long.MIN_VALUE && next < untilInstance) { next = iterator.next(); } mNextInstance = next; // invalidate mNextMillis mNextMillis = Long.MIN_VALUE; mNextDateTime = null; } /** * Skips all instances except for the last one. Ensure to call {@link #hasNext()} before calling {@link #nextMillis()} or {@link #nextDateTime()} after you * called this.

Note: At present this will loop infinitely when called on an infinite rule. So better check {@link * RecurrenceRule#isInfinite()} first.

*/ public void skipAllButLast() { long prevInstance; long instance = mNextInstance; RuleIterator iterator = mRuleIterator; do { prevInstance = instance; instance = iterator.next(); } while (instance != Long.MIN_VALUE); mNextInstance = prevInstance; // invalidate mNextMillis mNextMillis = Long.MIN_VALUE; mNextDateTime = null; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy