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

net.fortuna.ical4j.model.TemporalAdapter Maven / Gradle / Ivy

There is a newer version: 4.0.8
Show newest version
package net.fortuna.ical4j.model;

import net.fortuna.ical4j.model.parameter.TzId;
import net.fortuna.ical4j.util.CompatibilityHints;
import net.fortuna.ical4j.util.TimeZones;

import java.io.Serializable;
import java.time.*;
import java.time.chrono.ChronoZonedDateTime;
import java.time.format.DateTimeParseException;
import java.time.temporal.ChronoField;
import java.time.temporal.Temporal;
import java.util.Objects;

/**
 * The iCalendar specification supports multiple representations of date/time values, as outlined
 * below. This class encapsulates a {@link Temporal} value
 * and provides support for all corresponding representations in the specification.
 *
 * The recommended {@link Temporal} implementations for use with iCal4j are as follows:
 *
 * 
    *
  • {@link LocalDate} - represents an iCalendar DATE value as defined in section 3.3.4 of RFC5545
  • *
  • {@link LocalDateTime} - represents an iCalendar FORM #1: DATE-TIME value as defined in section 3.3.5 of RFC5545
  • *
  • {@link java.time.Instant} - represents an iCalendar FORM #2: DATE-TIME value as defined in section 3.3.5 of RFC5545
  • *
  • {@link ZonedDateTime} - represents an iCalendar FORM #3: DATE-TIME value as defined in section 3.3.5 of RFC5545
  • *
* * Note that where a local (i.e. floating) temporal type is used we need to apply a {@link ZoneId} for calculations such as * recurrence inclusions and other date-based comparisons. Use {@link TemporalAdapter#isFloating(Temporal)} to determine floating * instances. * * @param A concrete implementation of {@link Temporal} */ public class TemporalAdapter implements Serializable { private static final TemporalComparator COMPARATOR = TemporalComparator.INSTANCE; /** * The iCalendar-compliant string representation of a {@link Temporal} value. */ private final String valueString; /** * An iCalendar {@link TzId} parameter representing the {@link ZoneId} for a {@link Temporal} value. * This value may be localized to a specific iCalendar object, and thus is only meaningful when combined with * a {@link TimeZoneRegistry} instance. If no registry is provided it is assumed this represents a global * timezone identifier. */ private final TzId tzId; /** * Provides localized timezone definitions for an iCalendar object. */ private transient final TimeZoneRegistry timeZoneRegistry; private transient volatile T temporal; public TemporalAdapter(TemporalAdapter adapter) { this.temporal = adapter.temporal; this.valueString = adapter.valueString; this.tzId = adapter.tzId; this.timeZoneRegistry = adapter.timeZoneRegistry; } public TemporalAdapter(T temporal) { this(temporal, null); } public TemporalAdapter(T temporal, TimeZoneRegistry timeZoneRegistry) { Objects.requireNonNull(temporal, "temporal"); this.temporal = temporal; this.valueString = toString(temporal); if (temporal instanceof ZonedDateTime && !isFloating(temporal) && !isUtc(temporal)) { //XXX: assume zone id is global for now.. this may need to be resolved via the // timezone registry in future.. var zoneId = ((ZonedDateTime) temporal).getZone(); this.tzId = new TzId(zoneId.getId()); } else { this.tzId = null; } this.timeZoneRegistry = timeZoneRegistry; } private TemporalAdapter(String valueString) { this(valueString, null); } /** * Support lazy parsing of value string using a zone id to allow full initialisation of * {@link java.time.zone.ZoneRulesProvider} instances. * * @param value a string representation of a floating date/time value * @param tzId a zone id to apply to the parsed value */ private TemporalAdapter(String value, TzId tzId) { this(value, tzId, null); } /** * * @param value a string representation of a floating date/time value * @param tzId a zone id to apply to the parsed value * @param timeZoneRegistry timezone definitions */ private TemporalAdapter(String value, TzId tzId, TimeZoneRegistry timeZoneRegistry) { this.valueString = value; this.tzId = tzId; this.timeZoneRegistry = timeZoneRegistry; } @SuppressWarnings("unchecked") public T getTemporal() { if (temporal == null) { synchronized (valueString) { if (temporal == null) { if (tzId != null) { try { temporal = (T) CalendarDateFormat.FLOATING_DATE_TIME_FORMAT.parse(valueString, tzId.toZoneId(timeZoneRegistry)); } catch (DateTimeParseException e) { // TZID specified for non-floating date-time. If relaxed validation enabled fall back // to default parser and ignore TZID.. if (CompatibilityHints.isHintEnabled(CompatibilityHints.KEY_RELAXED_VALIDATION)) { temporal = (T) CalendarDateFormat.DEFAULT_PARSE_FORMAT.parse(valueString); } else { throw e; } } } else { temporal = (T) CalendarDateFormat.DEFAULT_PARSE_FORMAT.parse(valueString); } /* temporal = (T) Proxy.newProxyInstance(ChronoZonedDateTime.class.getClassLoader(), new Class[]{ChronoZonedDateTime.class}, new InvocationHandler() { private ChronoZonedDateTime temporal; @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (temporal == null) { temporal = CalendarDateFormat.FLOATING_DATE_TIME_FORMAT.parse(valueString, tzId.toZoneId()); } return method.invoke(temporal, args); } }); */ } } } return temporal; } @Override public String toString() { return toString(getTemporal()); } public String toString(ZoneId zoneId) { return toString(getTemporal(), zoneId); } private String toString(T temporal) { if (getTemporal() instanceof ChronoZonedDateTime) { return toString(CalendarDateFormat.FLOATING_DATE_TIME_FORMAT, ((ChronoZonedDateTime)temporal).getZone(), temporal); } return toString(getTemporal(), TimeZones.getDefault().toZoneId()); } /** * Return a string representation of the given temporal value, using the specified zone ID * for applicable values (i.e. values that support the DATE-TIME format). * @param temporal a temporal value * @param zoneId the overriding zone id for DATE-TIME instances * @return a string */ private String toString(T temporal, ZoneId zoneId) { if (ZoneOffset.UTC.equals(zoneId)) { return toInstantString(temporal); } else if (!isDateTimePrecision(temporal)) { return toString(CalendarDateFormat.DATE_FORMAT, temporal); } else { if (isFloating(getTemporal())) { return toString(CalendarDateFormat.FLOATING_DATE_TIME_FORMAT, temporal); } else if (isUtc(getTemporal())) { return toString(CalendarDateFormat.UTC_DATE_TIME_FORMAT, temporal); } else { return toString(CalendarDateFormat.FLOATING_DATE_TIME_FORMAT, zoneId, temporal); } } } /** * Return an ISO8601 instant string for the specified temporal value. * @param temporal a temporal value * @return a string */ private String toInstantString(T temporal) { return toString(CalendarDateFormat.UTC_DATE_TIME_FORMAT, temporal); } private String toString(CalendarDateFormat format, T temporal) { return format.format(temporal); } private String toString(CalendarDateFormat format, ZoneId zoneId, T temporal) { return format.format(temporal, zoneId); } public ZonedDateTime toLocalTime() { return toLocalTime(TimeZones.getDefault().toZoneId()); } public ZonedDateTime toLocalTime(ZoneId zoneId) { return toLocalTime(getTemporal(), zoneId); } public static ZonedDateTime toLocalTime(Temporal temporal, ZoneId zoneId) { if (isFloating(temporal)) { if (temporal instanceof LocalDateTime) { return ((LocalDateTime) temporal).atZone(zoneId); } else { return ((LocalDate) temporal).atStartOfDay().atZone(zoneId); } } else if (isUtc(temporal)) { if (temporal instanceof Instant) { return ((Instant) temporal).atZone(zoneId); } else { return ((OffsetDateTime) temporal).atZoneSameInstant(zoneId); } } else { return ZonedDateTime.from(temporal); } } /** * Parse a string representation of a temporal value. * * @param value a string representing a temporal * @return an adapter containing the parsed temporal value and format type * @throws DateTimeParseException if the string cannot be parsed */ public static TemporalAdapter parse(String value) throws DateTimeParseException { return parse(value, CalendarDateFormat.DEFAULT_PARSE_FORMAT); } @SuppressWarnings("unchecked") public static TemporalAdapter parse(String value, CalendarDateFormat parseFormat) throws DateTimeParseException { return new TemporalAdapter<>((T) parseFormat.parse(value)); } /** * Parse a string representation of a temporal value applicable to the specified timezone. * * @param value a string representing a floating temporal value * @param zoneId a timezone applied to the parsed value * @return an adapter containing the parsed temporal value * @throws DateTimeParseException if the string cannot be parsed */ public static TemporalAdapter parse(String value, ZoneId zoneId) { return new TemporalAdapter<>(CalendarDateFormat.FLOATING_DATE_TIME_FORMAT.parse(value, zoneId)); } /** * Parse a string representation of a temporal value applicable to the specified timezone. * * @param value a string representing a floating temporal value * @param tzId a timezone applied to the parsed value * @return an adapter containing the parsed temporal value * @throws DateTimeParseException if the string cannot be parsed */ public static TemporalAdapter parse(String value, TzId tzId) { return new TemporalAdapter<>(value, tzId); } /** * * @param value a string representing a floating temporal value * @param tzId a timezone applied to the parsed value * @param timeZoneRegistry timezone definitions * @return */ public static TemporalAdapter parse(String value, TzId tzId, TimeZoneRegistry timeZoneRegistry) { return new TemporalAdapter<>(value, tzId, timeZoneRegistry); } /** * This method provides support for conversion of legacy {@link Date} and {@link DateTime} instances to temporal * values. * * @param date a date/time instance * @return a temporal adapter instance equivalent to the specified date/time value */ @SuppressWarnings("deprecation") public static TemporalAdapter from(Date date) { Temporal temporal; if (date instanceof DateTime) { var dateTime = (DateTime) date; if (dateTime.isUtc()) { temporal = date.toInstant(); } else if (dateTime.getTimeZone() == null) { temporal = LocalDateTime.ofInstant(date.toInstant(), TimeZones.getDefault().toZoneId()); } else { temporal = ZonedDateTime.ofInstant(date.toInstant(), dateTime.getTimeZone().toZoneId()); } } else { temporal = LocalDate.from(date.toInstant()); } return new TemporalAdapter<>(temporal); } /** * Indicates whether the temporal type represents a "floating" date/time value. * @return true if the temporal type is floating, otherwise false */ public static boolean isFloating(Temporal date) { return !ChronoField.OFFSET_SECONDS.isSupportedBy(date) && !ChronoField.INSTANT_SECONDS.isSupportedBy(date); } /** * Indicates whether the temporal type represents a UTC date/time value. * * A temporal is defined as UTC if it matches any of the following: * - does not have associated zone information (e.g. Instant or OffsetDateTime) * - where offset is supported the offset must be zero * * @return true if the temporal type is in UTC time, otherwise false */ public static boolean isUtc(Temporal date) { return ChronoField.INSTANT_SECONDS.isSupportedBy(date) && (!ChronoField.OFFSET_SECONDS.isSupportedBy(date) || date.get(ChronoField.OFFSET_SECONDS) == 0 && !ChronoZonedDateTime.class.isAssignableFrom(date.getClass())); } /** * Indicates whether the temporal type represents a date/time value. * @return true if the temporal type has DATE-TIME precision, otherwise false */ public static boolean isDateTimePrecision(Temporal date) { return ChronoField.INSTANT_SECONDS.isSupportedBy(date) || ChronoField.HOUR_OF_DAY.isSupportedBy(date); } public static boolean isBefore(T date1, T date2) { return COMPARATOR.compare(date1, date2) < 0; } public static boolean isAfter(T date1, T date2) { return COMPARATOR.compare(date1, date2) > 0; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; TemporalAdapter that = (TemporalAdapter) o; return valueString.equals(that.valueString) && Objects.equals(tzId, that.tzId); } @Override public int hashCode() { return Objects.hash(valueString, tzId); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy