Java tutorial
/** * Copyright 2011 Caleb Richardson * * 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 com.outerspacecat.icalendar; import com.google.common.base.Optional; import com.google.common.base.Preconditions; import com.google.common.base.Predicate; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import java.time.DayOfWeek; import java.time.temporal.ChronoField; import java.time.temporal.Temporal; import java.time.temporal.TemporalField; import java.time.temporal.ValueRange; /** * Defines utility methods for working with recurrences. * * @author Caleb Richardson */ final class RecurrencePredicates { private RecurrencePredicates() { } /** * Returns a predicate for determining whether or not to keep instances of * {@link FixedSchedule}. The returned predicate will return {@code true} for * a given {@link FixedSchedule} if the value of the field specified by * {@code field} is in {@code values}, {@code false} otherwise. If the * specified field is not applicable or if a value is out of range then the * predicate will return {@code false}. * * @param field the field to check using each value in {@code values} . Must * be non {@code null}. * @param values the values to check supplied fixed schedules against. Must be * non {@code null}, must contain one or more elements, and each * element must be >= 0. * @return a predicate for determining whether or not to keep instances of * {@link FixedSchedule}. Never {@code null}. */ static Predicate<ChronoFixedSchedule> limiter(final TemporalField field, final ImmutableSet<Integer> values) { Preconditions.checkNotNull(field, "field required"); Preconditions.checkNotNull(values, "values required"); Preconditions.checkArgument(!values.isEmpty(), "values must be non empty"); for (Integer value : values) Preconditions.checkArgument(value >= 0, "each values element must be non negative"); return new Predicate<ChronoFixedSchedule>() { @Override public boolean apply(final ChronoFixedSchedule input) { Preconditions.checkNotNull(input, "input required"); Temporal partial = null; ValueRange range = null; if (input.getStartDate() != null) { partial = input.getStartDate(); if (!partial.isSupported(field)) return false; range = input.getStartDate().range(field); } else if (input.getZonedStartDateTime() != null) { partial = input.getZonedStartDateTime().toLocalDateTime(); if (!partial.isSupported(field)) return false; range = input.getZonedStartDateTime().range(field); } else { throw new IllegalStateException("no start date or zoned start date-time"); } for (Long value : Iterables.transform(values, (value) -> value.longValue())) { if (!range.isValidValue(value)) continue; if (partial.get(field) == value) return true; } return false; } }; } /** * Returns a predicate for determining whether or not to keep instances of * {@link FixedSchedule}. The returned predicate will return {@code true} for * a given {@link FixedSchedule} if the value of the field specified by * {@code field} is in {@code values}, {@code false} otherwise. If the * specified field is not applicable or if a value is out of range then the * predicate will return {@code false}. * * @param field the field to check using each value in {@code values} . Must * be non {@code null}. * @param values the values to check supplied fixed schedules against. Must be * non {@code null}, must contain one or more elements. Elements may be * be negative, but must be non zero. * @return a predicate for determining whether or not to keep instances of * {@link FixedSchedule}. Never {@code null}. */ static Predicate<ChronoFixedSchedule> signedLimiter(final TemporalField field, final ImmutableSet<Integer> values) { Preconditions.checkNotNull(field, "field required"); Preconditions.checkNotNull(values, "values required"); Preconditions.checkArgument(!values.isEmpty(), "values must be non empty"); for (Integer value : values) Preconditions.checkArgument(value != 0, "each values element must be non zero"); return new Predicate<ChronoFixedSchedule>() { @Override public boolean apply(final ChronoFixedSchedule input) { Preconditions.checkNotNull(input, "input required"); Temporal partial = null; ValueRange range = null; if (input.getStartDate() != null) { partial = input.getStartDate(); if (!partial.isSupported(field)) return true; range = input.getStartDate().range(field); } else if (input.getZonedStartDateTime() != null) { partial = input.getZonedStartDateTime().toLocalDateTime(); if (!partial.isSupported(field)) return true; range = input.getZonedStartDateTime().range(field); } else { throw new IllegalStateException("no start date or zoned start date-time"); } for (Long value : Iterables.transform(values, (value) -> value.longValue())) { if (value < 0) value = range.getMaximum() + value + 1; if (!range.isValidValue(value)) continue; if (partial.get(field) == value) return true; } return false; } }; } /** * Returns a predicate for determining whether or not to keep instances of * {@link FixedSchedule}. Used to apply BYDAY values to a MONTHLY recurrence. * <p> * May not work for all chronologies. * * @param byDay the values to check supplied fixed schedules against. Must be * non {@code null} and must contain one or more elements. The second * element of each tuple must be non {@code null}. If the first element * is non {@code null}, then it must be >= 1 and <= 31. * @return a predicate for determining whether or not to keep instances of * {@link FixedSchedule}. Never {@code null}. */ static Predicate<ChronoFixedSchedule> monthlyByDayLimiter(final ImmutableSet<DayOfWeekOccurrence> byDay) { Preconditions.checkNotNull(byDay, "byDay required"); Preconditions.checkArgument(!byDay.isEmpty(), "byDay must be non empty"); return new Predicate<ChronoFixedSchedule>() { @Override public boolean apply(final ChronoFixedSchedule input) { Preconditions.checkNotNull(input, "input required"); DayOfWeek dayOfWeek = DayOfWeek.of(input.getDateOfStart().get(ChronoField.DAY_OF_WEEK)); int dayOfMonth = input.getDateOfStart().get(ChronoField.DAY_OF_MONTH); int numericPrefix = dayOfMonth / 7 + (dayOfMonth % 7 == 0 ? 0 : 1); long daysInMonth = input.getDateOfStart().range(ChronoField.DAY_OF_MONTH).getMaximum(); int dayOfWeekOccurrencesInMonth = (int) (numericPrefix + ((daysInMonth - dayOfMonth) / 7)); return byDay.contains(new DayOfWeekOccurrence(Optional.absent(), dayOfWeek)) || byDay.contains(new DayOfWeekOccurrence(Optional.fromNullable(numericPrefix), dayOfWeek)) || byDay.contains(new DayOfWeekOccurrence( Optional.fromNullable(numericPrefix - dayOfWeekOccurrencesInMonth - 1), dayOfWeek)); } }; } /** * Returns a predicate for determining whether or not to keep instances of * {@link FixedSchedule}. Used to apply BYDAY values to a YEARLY recurrence. * <p> * May not work for all chronologies. * * @param byDay the values to check supplied fixed schedules against. Must be * non {@code null} and must contain one or more elements. * @return a predicate for determining whether or not to keep instances of * {@link FixedSchedule}. Never {@code null}. */ static Predicate<ChronoFixedSchedule> yearlyByDayLimiter(final ImmutableSet<DayOfWeekOccurrence> byDay) { Preconditions.checkNotNull(byDay, "byDay required"); Preconditions.checkArgument(!byDay.isEmpty(), "byDay must be non empty"); return new Predicate<ChronoFixedSchedule>() { @Override public boolean apply(final ChronoFixedSchedule input) { Preconditions.checkNotNull(input, "input required"); DayOfWeek dayOfWeek = DayOfWeek.of(input.getDateOfStart().get(ChronoField.DAY_OF_WEEK)); int dayOfYear = input.getDateOfStart().get(ChronoField.DAY_OF_YEAR); int numericPrefix = dayOfYear / 7 + (dayOfYear % 7 == 0 ? 0 : 1); int daysInYear = (int) (input.getDateOfStart().range(ChronoField.DAY_OF_YEAR).getMaximum()); int dayOfWeekOccurrencesInYear = (int) (numericPrefix + ((daysInYear - dayOfYear) / 7)); return byDay.contains(new DayOfWeekOccurrence(Optional.absent(), dayOfWeek)) || byDay.contains(new DayOfWeekOccurrence(Optional.fromNullable(numericPrefix), dayOfWeek)) || byDay.contains(new DayOfWeekOccurrence( Optional.fromNullable(numericPrefix - dayOfWeekOccurrencesInYear - 1), dayOfWeek)); } }; } }