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.Objects; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import java.io.Serializable; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.ZoneId; import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.time.zone.ZoneRulesException; import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.ThreadSafe; /** * A representation of a schedule. * <p> * Based on iCalendar concepts defined in <a * href="http://tools.ietf.org/html/rfc5545">RFC 5545</a>. * * @author Caleb Richardson */ @Immutable @ThreadSafe public final class Schedule implements Serializable { private final static long serialVersionUID = 1L; private final TypedProperty<LocalDate> startDate; private final TypedProperty<LocalDateTime> startDateTime; private final TypedProperty<ZonedDateTime> zonedStartDateTime; private final TypedProperty<LocalDate> endDate; private final TypedProperty<LocalDateTime> endDateTime; private final TypedProperty<ZonedDateTime> zonedEndDateTime; private final TypedProperty<DurationType> duration; private Schedule(final TypedProperty<LocalDate> startDate, final TypedProperty<LocalDateTime> startDateTime, final TypedProperty<ZonedDateTime> zonedStartDateTime, final TypedProperty<LocalDate> endDate, final TypedProperty<LocalDateTime> endDateTime, final TypedProperty<ZonedDateTime> zonedEndDateTime, final TypedProperty<DurationType> duration) { this.startDate = startDate; this.startDateTime = startDateTime; this.zonedStartDateTime = zonedStartDateTime; this.endDate = endDate; this.endDateTime = endDateTime; this.zonedEndDateTime = zonedEndDateTime; this.duration = duration; } /** * Creates a new schedule. * * @param startDate the start date of the schedule. Must be non {@code null}. * @return a new schedule. Never {@code null}. */ public static Schedule fromDate(final TypedProperty<LocalDate> startDate) { Preconditions.checkNotNull(startDate, "startDate required"); return new Schedule(startDate, null, null, null, null, null, null); } /** * Creates a new schedule. The end date must be on or after the start date. * * @param startDate the start date of the schedule. Must be non {@code null} . * @param endDate the end date of the schedule. Must be non {@code null}. * @return a new schedule. Never {@code null}. */ public static Schedule fromDates(final TypedProperty<LocalDate> startDate, final TypedProperty<LocalDate> endDate) { Preconditions.checkNotNull(startDate, "startDate required"); Preconditions.checkNotNull(endDate, "endDate required"); Preconditions.checkArgument(!endDate.getValue().isBefore(startDate.getValue()), "end date must be on or after start date"); return new Schedule(startDate, null, null, endDate, null, null, null); } /** * Creates a new schedule. * * @param startDate the start date of the schedule. Must be non {@code null} . * @param duration the duration of the schedule. Must be non {@code null}. * Must return {@code false} for {@link DurationType#isNegative()} and * {@code true} for {@link DurationType#isDayOrWeekOnly()}. * @return a new schedule. Never {@code null}. */ public static Schedule fromDateAndDuration(final TypedProperty<LocalDate> startDate, final TypedProperty<DurationType> duration) { Preconditions.checkNotNull(startDate, "startDate required"); Preconditions.checkNotNull(duration, "duration required"); Preconditions.checkArgument(!duration.getValue().isNegative(), "duration must be non negative"); Preconditions.checkArgument(duration.getValue().isDayOrWeekOnly(), "duration must be day or week only"); return new Schedule(startDate, null, null, null, null, null, duration); } /** * Creates a new schedule. * * @param startDateTime the start date-time of the schedule. Must be non * {@code null}. * @return a new schedule. Never {@code null}. */ public static Schedule fromDateTime(final TypedProperty<LocalDateTime> startDateTime) { Preconditions.checkNotNull(startDateTime, "startDateTime required"); return new Schedule(null, startDateTime, null, null, null, null, null); } /** * Creates a new schedule. The end date-time must be on or after the start * date-time. * * @param startDateTime the start date-time of the schedule. Must be non * {@code null}. * @param endDateTime the end date-time of the schedule. Must be non * {@code null}. * @return a new schedule. Never {@code null}. */ public static Schedule fromDateTimes(final TypedProperty<LocalDateTime> startDateTime, final TypedProperty<LocalDateTime> endDateTime) { Preconditions.checkNotNull(startDateTime, "startDateTime required"); Preconditions.checkNotNull(endDateTime, "endDateTime required"); Preconditions.checkArgument(!endDateTime.getValue().isBefore(startDateTime.getValue()), "end date-time must be on or after start date-time"); return new Schedule(null, startDateTime, null, null, startDateTime, null, null); } /** * Creates a new schedule. * * @param startDateTime the start date-time of the schedule. Must be non * {@code null}. * @param duration the duration of the schedule. Must be non {@code null}. * Must return {@code false} for {@link DurationType#isNegative()}. * @return a new schedule. Never {@code null}. */ public static Schedule fromDateTimeAndDuration(final TypedProperty<LocalDateTime> startDateTime, final TypedProperty<DurationType> duration) { Preconditions.checkNotNull(startDateTime, "startDateTime required"); Preconditions.checkNotNull(duration, "duration required"); Preconditions.checkArgument(!duration.getValue().isNegative(), "duration must be non negative"); return new Schedule(null, startDateTime, null, null, null, null, duration); } /** * Creates a new schedule. * * @param zonedStartDateTime the zoned start date-time of the schedule. Must * be non {@code null}. * @return a new schedule. Never {@code null}. */ public static Schedule fromZonedDateTime(final TypedProperty<ZonedDateTime> zonedStartDateTime) { Preconditions.checkNotNull(zonedStartDateTime, "zonedStartDateTime required"); return new Schedule(null, null, zonedStartDateTime, null, null, null, null); } /** * Creates a new schedule. The end instant must be on or after the start * instant. * * @param zonedStartDateTime the zoned start date-time of the schedule. Must * be non {@code null}. * @param zonedEndDateTime the zoned end date-time of the schedule. Must be * non {@code null}. * @return a new schedule. Never {@code null}. */ public static Schedule fromZonedDateTimes(final TypedProperty<ZonedDateTime> zonedStartDateTime, final TypedProperty<ZonedDateTime> zonedEndDateTime) { Preconditions.checkNotNull(zonedStartDateTime, "zonedStartDateTime required"); Preconditions.checkNotNull(zonedEndDateTime, "zonedEndDateTime required"); Preconditions.checkArgument(!zonedEndDateTime.getValue().isBefore(zonedStartDateTime.getValue()), "end must be on or after start"); return new Schedule(null, null, zonedStartDateTime, null, null, zonedEndDateTime, null); } /** * Creates a new schedule. * * @param zonedStartDateTime the zoned start date-time of the schedule. Must * be non {@code null}. * @param duration the duration of the schedule. Must be non {@code null}. * Must return {@code false} for {@link DurationType#isNegative()}. * @return a new schedule. Never {@code null}. */ public static Schedule fromZonedDateTimeAndDuration(final TypedProperty<ZonedDateTime> zonedStartDateTime, final TypedProperty<DurationType> duration) { Preconditions.checkNotNull(zonedStartDateTime, "zonedStartDateTime required"); Preconditions.checkNotNull(duration, "duration required"); Preconditions.checkArgument(!duration.getValue().isNegative(), "duration must be non negative"); return new Schedule(null, null, zonedStartDateTime, null, null, null, duration); } /** * Parses a schedule. * * @param startProperty the DTSTART property. Must be non {@code null}. * @param endProperty the DTEND property. May be {@code null}. * @param durationProperty the DURATION property. May be {@code null}. * @return a schedule. Never {@code null}. * @throws CalendarParseException if a valid schedule cannot be parsed */ public static Schedule parse(final Property startProperty, final Property endProperty, final Property durationProperty) throws CalendarParseException { Preconditions.checkNotNull(startProperty, "start required"); Preconditions.checkArgument(startProperty.getName().getName().equals("DTSTART"), "invalid start property name: " + startProperty.getName().getName()); if (endProperty != null) { Preconditions.checkArgument(endProperty.getName().getName().equals("DTEND"), "invalid end property name: " + endProperty.getName().getName()); } if (durationProperty != null) { Preconditions.checkArgument(durationProperty.getName().getName().equals("DURATION"), "invalid duration property name: " + durationProperty.getName().getName()); } LocalDate startDate = null; ParsedDateTime startDateTime = null; ZoneId startTimeZone = null; ImmutableMap<String, Parameter> startParameters = null; LocalDate endDate = null; ParsedDateTime endDateTime = null; ZoneId endTimeZone = null; ImmutableMap<String, Parameter> endParameters = null; DurationType duration = null; ImmutableMap<String, Parameter> durationParameters = null; String startValue = startProperty.getParameterValue("VALUE").getValue(); if (startValue.equals("DATE")) { startDate = startProperty.asDate(); } else if (startValue.equals("DATE-TIME")) { startDateTime = startProperty.asDateTime(); String startTzId = startProperty.getParameterValue("TZID").getValue(); if (startTzId != null) { try { startTimeZone = ZoneId.of(startTzId); } catch (ZoneRulesException e) { throw new CalendarParseException("no time zone for DTSTART TZID: " + startTzId, e); } } if (startTimeZone == null) { if (startDateTime.isUtc()) startTimeZone = ZoneOffset.UTC; } else { if (startDateTime.isUtc()) throw new CalendarParseException("DTSTART specified as UTC with TZID"); } } else { throw new CalendarParseException("invalid DSTART VALUE parameter: " + startValue); } startParameters = startProperty.getParametersExcept(ImmutableSet.of("VALUE", "TZID")); if (endProperty != null) { if (durationProperty != null) throw new CalendarParseException("both DTEND and DURATION specified"); String endValue = endProperty.getParameterValue("VALUE").getValue(); if (endValue.equals("DATE")) { if (startDate == null) { throw new CalendarParseException("DATE-TIME DSTART specified with DATE DTEND"); } endDate = endProperty.asDate(); } else if (endValue.equals("DATE-TIME")) { if (startDateTime == null) { throw new CalendarParseException("DATE DSTART specified with DATE-TIME DTEND"); } endDateTime = endProperty.asDateTime(); String endTzId = endProperty.getParameterValue("TZID").getValue(); if (endTzId != null) { try { endTimeZone = ZoneId.of(endTzId); } catch (ZoneRulesException e) { throw new CalendarParseException("no time zone for DTEND TZID: " + endTzId, e); } } if (endTimeZone == null) { if (endDateTime.isUtc()) endTimeZone = ZoneOffset.UTC; } else { if (endDateTime.isUtc()) throw new CalendarParseException("DTEND specified as UTC with TZID"); } if (endTimeZone == null) { if (startTimeZone != null) { throw new CalendarParseException("Floating DTEND paired with non-floating DTSTART"); } } else { if (startTimeZone == null) { throw new CalendarParseException("Non-float DTEND paired with floating DTSTART"); } } } endParameters = endProperty.getParametersExcept(ImmutableSet.of("VALUE", "TZID")); } if (durationProperty != null) { if (endProperty != null) throw new CalendarParseException("both DTEND and DURATION specified"); duration = durationProperty.asDuration(); if (startDate != null) { if (!duration.isDayOrWeekOnly()) { throw new CalendarParseException("DATE DTSTART can only be paired with a day or week DURATION"); } } durationParameters = durationProperty.getParametersExcept(ImmutableSet.of()); } if (startDate != null) { if (endDate != null) { return Schedule.fromDates(new TypedProperty<LocalDate>(startDate, startParameters.values()), new TypedProperty<LocalDate>(endDate, endParameters.values())); } else if (duration != null) { return Schedule.fromDateAndDuration( new TypedProperty<LocalDate>(startDate, startParameters.values()), new TypedProperty<DurationType>(duration, durationParameters.values())); } else { return Schedule.fromDate(new TypedProperty<LocalDate>(startDate, startParameters.values())); } } if (startDateTime != null) { if (endDateTime != null) { if (startTimeZone != null) { return Schedule.fromZonedDateTimes( new TypedProperty<ZonedDateTime>(startDateTime.getDateTime().atZone(startTimeZone), startParameters.values()), new TypedProperty<ZonedDateTime>(endDateTime.getDateTime().atZone(endTimeZone), endParameters.values())); } else { return Schedule.fromDateTimes( new TypedProperty<LocalDateTime>(startDateTime.getDateTime(), startParameters.values()), new TypedProperty<LocalDateTime>(endDateTime.getDateTime(), endParameters.values())); } } else if (duration != null) { if (startTimeZone != null) { return Schedule.fromZonedDateTimeAndDuration( new TypedProperty<ZonedDateTime>(startDateTime.getDateTime().atZone(startTimeZone), startParameters.values()), new TypedProperty<DurationType>(duration, durationParameters.values())); } else { return Schedule.fromDateTimeAndDuration( new TypedProperty<LocalDateTime>(startDateTime.getDateTime(), startParameters.values()), new TypedProperty<DurationType>(duration, durationParameters.values())); } } else { return Schedule.fromDateTime( new TypedProperty<LocalDateTime>(startDateTime.getDateTime(), startParameters.values())); } } throw new IllegalStateException("unexpected combination, startDate=" + startDate + ", startDateTime=" + startDateTime + ", startTimeZone=" + startTimeZone + ", endDate=" + endDate + ", endDateTime=" + endDateTime + ", endTimeZone=" + endTimeZone + ", duration=" + duration); } /** * Returns the start date of this schedule. * * @return the start date of this schedule. May be {@code null}. */ public TypedProperty<LocalDate> getStartDate() { return startDate; } /** * Returns the start date-time of this schedule. * * @return the start date-time of this schedule. May be {@code null}. */ public TypedProperty<LocalDateTime> getStartDateTime() { return startDateTime; } /** * Returns the zoned start date-time of this schedule. * * @return the zoned start date-time of this schedule. May be {@code null}. */ public TypedProperty<ZonedDateTime> getZonedStartDateTime() { return zonedStartDateTime; } /** * Returns the date of the start of this schedule. * * @return the date of the start of this schedule. Never {@code null} */ public LocalDate getDateOfStart() { if (getStartDate() != null) { return getStartDate().getValue(); } else if (getStartDateTime() != null) { return getStartDateTime().getValue().toLocalDate(); } else if (getZonedStartDateTime() != null) { return getZonedStartDateTime().getValue().toLocalDate(); } else { throw new IllegalStateException("no start date or start date-time or zoned start date-time"); } } /** * Returns the end date of this schedule. * * @return the end date of this schedule. May be {@code null}. */ public TypedProperty<LocalDate> getEndDate() { return endDate; } /** * Returns the end date-time of this schedule. * * @return the end date-time of this schedule. May be {@code null}. */ public TypedProperty<LocalDateTime> getEndDateTime() { return endDateTime; } /** * Returns the zoned end date-time of this schedule. * * @return the zoned end date-time of this schedule. May be {@code null}. */ public TypedProperty<ZonedDateTime> getZonedEndDateTime() { return zonedEndDateTime; } /** * Returns the duration of this schedule. * * @return the duration of this schedule. May be {@code null}. */ public TypedProperty<DurationType> getDuration() { return duration; } /** * Returns whether or not this schedule is floating. * * @return whether or not this schedule is floating */ public boolean isFloating() { return getStartDateTime() != null; } /** * Returns a {@link FixedSchedule} based on this schedule. * * @param fallbackTimeZone a fallback time zone. Must be non {@code null}. * @return a {@link FixedSchedule} based on this schedule. Never {@code null}. */ public FixedSchedule toFixed(final ZoneId fallbackTimeZone) { if (getStartDate() != null) { if (getEndDate() != null) { return new FixedSchedule(getStartDate().getValue(), getEndDate().getValue()); } else if (getDuration() != null) { return new FixedSchedule(getStartDate().getValue(), getDuration().getValue()); } else { return new FixedSchedule(getStartDate().getValue()); } } else if (getStartDateTime() != null) { if (getEndDateTime() != null) { return new FixedSchedule(getStartDateTime().getValue().atZone(fallbackTimeZone), getEndDateTime().getValue().atZone(fallbackTimeZone)); } else if (getDuration() != null) { return new FixedSchedule(getStartDateTime().getValue().atZone(fallbackTimeZone), getDuration().getValue()); } else { return new FixedSchedule(getStartDateTime().getValue().atZone(fallbackTimeZone)); } } else if (getZonedStartDateTime() != null) { if (getZonedEndDateTime() != null) { return new FixedSchedule(getZonedStartDateTime().getValue(), getZonedEndDateTime().getValue()); } else if (getDuration() != null) { return new FixedSchedule(getZonedStartDateTime().getValue(), getDuration().getValue()); } else { return new FixedSchedule(getZonedStartDateTime().getValue()); } } else { throw new IllegalStateException("no start date or start date-time or zoned start date-time"); } } @Override public int hashCode() { return Objects.hashCode(getStartDate(), getStartDateTime(), getZonedStartDateTime(), getEndDate(), getEndDateTime(), getZonedEndDateTime(), getDuration()); } @Override public boolean equals(final Object obj) { return obj instanceof Schedule && Objects.equal(getStartDate(), ((Schedule) obj).getStartDate()) && Objects.equal(getStartDateTime(), ((Schedule) obj).getStartDateTime()) && Objects.equal(getZonedStartDateTime(), ((Schedule) obj).getZonedStartDateTime()) && Objects.equal(getEndDate(), ((Schedule) obj).getEndDate()) && Objects.equal(getEndDateTime(), ((Schedule) obj).getEndDateTime()) && Objects.equal(getZonedEndDateTime(), ((Schedule) obj).getZonedEndDateTime()) && Objects.equal(getDuration(), ((Schedule) obj).getDuration()); } }