From e0dd7b3618e09caa3094bee2e802c6d0da37e7e6 Mon Sep 17 00:00:00 2001 From: Ben Fortuna Date: Fri, 27 Mar 2020 16:52:43 +1100 Subject: [PATCH] Resolve timezone in date property so that TzId param conforms to immutability --- .../ical4j/data/DefaultContentHandler.java | 11 ++----- .../fortuna/ical4j/model/PropertyBuilder.java | 13 ++++++++ .../fortuna/ical4j/model/TemporalAdapter.java | 33 ++++++++++++++++++- .../fortuna/ical4j/model/parameter/TzId.java | 20 +++++------ .../ical4j/model/property/DateProperty.java | 21 +++++++----- 5 files changed, 68 insertions(+), 30 deletions(-) diff --git a/src/main/java/net/fortuna/ical4j/data/DefaultContentHandler.java b/src/main/java/net/fortuna/ical4j/data/DefaultContentHandler.java index 9fa743ef0..0a423f21c 100644 --- a/src/main/java/net/fortuna/ical4j/data/DefaultContentHandler.java +++ b/src/main/java/net/fortuna/ical4j/data/DefaultContentHandler.java @@ -3,7 +3,6 @@ import net.fortuna.ical4j.model.*; import net.fortuna.ical4j.model.component.CalendarComponent; import net.fortuna.ical4j.model.component.VTimeZone; -import net.fortuna.ical4j.model.parameter.TzId; import net.fortuna.ical4j.util.Constants; import java.io.IOException; @@ -96,7 +95,8 @@ public void endComponent(String name) { @Override public void startProperty(String name) { - propertyBuilder = new PropertyBuilder().factories(propertyFactorySupplier.get()).name(name); + propertyBuilder = new PropertyBuilder().factories(propertyFactorySupplier.get()) + .name(name).timeZoneRegistry(tzRegistry); } @Override @@ -129,13 +129,6 @@ public void parameter(String name, String value) throws URISyntaxException { Parameter parameter = new ParameterBuilder().factories(parameterFactorySupplier.get()) .name(name).value(value).build(); - if (parameter instanceof TzId && tzRegistry != null) { - // VTIMEZONE may be defined later, so so keep - // track of dates until all components have been - // parsed, and then try again later - ((TzId) parameter).setTimeZoneRegistry(tzRegistry); - } - propertyBuilder.parameter(parameter); } diff --git a/src/main/java/net/fortuna/ical4j/model/PropertyBuilder.java b/src/main/java/net/fortuna/ical4j/model/PropertyBuilder.java index 40d4e6e5d..1f583a579 100644 --- a/src/main/java/net/fortuna/ical4j/model/PropertyBuilder.java +++ b/src/main/java/net/fortuna/ical4j/model/PropertyBuilder.java @@ -1,5 +1,6 @@ package net.fortuna.ical4j.model; +import net.fortuna.ical4j.model.property.DateProperty; import net.fortuna.ical4j.model.property.XProperty; import net.fortuna.ical4j.util.Strings; @@ -19,6 +20,8 @@ public class PropertyBuilder extends AbstractContentBuilder { private List parameters = new ArrayList<>(); + private TimeZoneRegistry timeZoneRegistry; + public PropertyBuilder factories(List> factories) { this.factories.addAll(factories); return this; @@ -41,6 +44,11 @@ public PropertyBuilder parameter(Parameter parameter) { return this; } + public PropertyBuilder timeZoneRegistry(TimeZoneRegistry timeZoneRegistry) { + this.timeZoneRegistry = timeZoneRegistry; + return this; + } + public Property build() throws ParseException, IOException, URISyntaxException { Property property = null; for (PropertyFactory factory : factories) { @@ -49,6 +57,11 @@ public Property build() throws ParseException, IOException, URISyntaxException { if (property instanceof Escapable) { property.setValue(Strings.unescape(value)); } + + if (property instanceof DateProperty) { + ((DateProperty) property).setTimeZoneRegistry(timeZoneRegistry); + property.setValue(value); + } } } diff --git a/src/main/java/net/fortuna/ical4j/model/TemporalAdapter.java b/src/main/java/net/fortuna/ical4j/model/TemporalAdapter.java index 90cb6b2f3..47c19e585 100644 --- a/src/main/java/net/fortuna/ical4j/model/TemporalAdapter.java +++ b/src/main/java/net/fortuna/ical4j/model/TemporalAdapter.java @@ -42,15 +42,22 @@ public class TemporalAdapter implements Serializable { private final TzId tzId; + private transient TimeZoneRegistry timeZoneRegistry; + private transient 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, ZoneId.systemDefault()); @@ -59,6 +66,7 @@ public TemporalAdapter(T temporal) { } else { this.tzId = null; } + this.timeZoneRegistry = timeZoneRegistry; } private TemporalAdapter(String valueString) { @@ -73,8 +81,19 @@ private TemporalAdapter(String valueString) { * @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; } public T getTemporal() { @@ -82,7 +101,8 @@ public T getTemporal() { synchronized (valueString) { if (temporal == null) { if (tzId != null) { - temporal = (T) CalendarDateFormat.FLOATING_DATE_TIME_FORMAT.parse(valueString, tzId.toZoneId()); + temporal = (T) CalendarDateFormat.FLOATING_DATE_TIME_FORMAT.parse(valueString, + tzId.toZoneId(timeZoneRegistry)); } else { temporal = (T) PARSE_FORMAT.parse(valueString); } @@ -188,6 +208,17 @@ 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. diff --git a/src/main/java/net/fortuna/ical4j/model/parameter/TzId.java b/src/main/java/net/fortuna/ical4j/model/parameter/TzId.java index 17390c829..f05388643 100644 --- a/src/main/java/net/fortuna/ical4j/model/parameter/TzId.java +++ b/src/main/java/net/fortuna/ical4j/model/parameter/TzId.java @@ -53,25 +53,14 @@ public class TzId extends Parameter implements Escapable { private final String value; - private transient TimeZoneRegistry timeZoneRegistry; - /** * @param aValue a string representation of a time zone identifier */ public TzId(final String aValue) { - this(aValue, null); - } - - public TzId(final String aValue, TimeZoneRegistry timeZoneRegistry) { super(TZID); // parameter values may be quoted if they contain characters in the // set [:;,].. this.value = Strings.unquote(aValue); - this.timeZoneRegistry = timeZoneRegistry; - } - - public void setTimeZoneRegistry(TimeZoneRegistry timeZoneRegistry) { - this.timeZoneRegistry = timeZoneRegistry; } /** @@ -81,6 +70,15 @@ public void setTimeZoneRegistry(TimeZoneRegistry timeZoneRegistry) { * @return a zone id represented by this instance */ public ZoneId toZoneId() { + return toZoneId(null); + } + + /** + * + * @param timeZoneRegistry + * @return + */ + public ZoneId toZoneId(TimeZoneRegistry timeZoneRegistry) { if (timeZoneRegistry != null && !timeZoneRegistry.getZoneRules().isEmpty()) { return timeZoneRegistry.getZoneId(getValue()); } else { diff --git a/src/main/java/net/fortuna/ical4j/model/property/DateProperty.java b/src/main/java/net/fortuna/ical4j/model/property/DateProperty.java index c2b4ea2e3..7a6e611ba 100644 --- a/src/main/java/net/fortuna/ical4j/model/property/DateProperty.java +++ b/src/main/java/net/fortuna/ical4j/model/property/DateProperty.java @@ -31,10 +31,7 @@ */ package net.fortuna.ical4j.model.property; -import net.fortuna.ical4j.model.Parameter; -import net.fortuna.ical4j.model.Property; -import net.fortuna.ical4j.model.PropertyFactory; -import net.fortuna.ical4j.model.TemporalAdapter; +import net.fortuna.ical4j.model.*; import net.fortuna.ical4j.model.parameter.TzId; import net.fortuna.ical4j.model.parameter.Value; import net.fortuna.ical4j.util.Strings; @@ -77,6 +74,8 @@ public abstract class DateProperty extends Property { private TemporalAdapter date; + private transient TimeZoneRegistry timeZoneRegistry; + /** * @param name the property name * @param parameters a list of initial parameters @@ -104,7 +103,7 @@ public T getDate() { if (date != null) { Optional tzId = getParameter(Parameter.TZID); if (tzId.isPresent()) { - return (T) date.toLocalTime(tzId.get().toZoneId()); + return (T) date.toLocalTime(tzId.get().toZoneId(timeZoneRegistry)); } else { return date.getTemporal(); } @@ -121,7 +120,7 @@ public T getDate() { */ public void setDate(T date) { if (date != null) { - this.date = new TemporalAdapter<>(date); + this.date = new TemporalAdapter<>(date, timeZoneRegistry); } else { this.date = null; } @@ -142,7 +141,7 @@ public void setValue(final String value) throws DateTimeParseException { // value can be either a date-time or a date.. if (value != null && !value.isEmpty()) { Optional tzId = getParameter(Parameter.TZID); - this.date = tzId.map(id -> (TemporalAdapter) TemporalAdapter.parse(value, id)) + this.date = tzId.map(id -> (TemporalAdapter) TemporalAdapter.parse(value, id, timeZoneRegistry)) .orElseGet(() -> TemporalAdapter.parse(value)); } else { this.date = null; @@ -155,12 +154,16 @@ public void setValue(final String value) throws DateTimeParseException { public String getValue() { Optional tzId = getParameter(Parameter.TZID); if (tzId.isPresent()) { - return date.toString(tzId.get().toZoneId()); + return date.toString(tzId.get().toZoneId(timeZoneRegistry)); } else { return Strings.valueOf(date); } } + public void setTimeZoneRegistry(TimeZoneRegistry timeZoneRegistry) { + this.timeZoneRegistry = timeZoneRegistry; + } + /** * {@inheritDoc} */ @@ -222,7 +225,7 @@ public void validate() throws ValidationException { // ensure tzid matches date-time timezone.. final Optional tzId = getParameter(Parameter.TZID); - if (!tzId.isPresent() || !tzId.get().toZoneId().equals(dateTime.getZone())) { + if (!tzId.isPresent() || !tzId.get().toZoneId(timeZoneRegistry).equals(dateTime.getZone())) { throw new ValidationException("TZID parameter [" + tzId.get() + "] does not match the timezone [" + dateTime.getZone() + "]"); }