(properties.size());
+ for (ICalProperty property : properties) {
+ String language = property.getParameters().getLanguage();
+ if (language != null) {
+ language = language.toLowerCase();
+ }
+
+ boolean added = languages.add(language);
+ if (!added) {
+ warnings.add(new ValidationWarning(55, clazz.getSimpleName()));
+ break;
+ }
+ }
+ }
+
+ /**
+ *
+ * Marshals this iCalendar object to its traditional, plain-text
+ * representation.
+ *
+ *
+ * If this iCalendar object contains user-defined property or component
+ * objects, you must use the {@link Biweekly} or {@link ICalWriter} classes
+ * instead in order to register the scribe classes.
+ *
+ * @return the plain text representation
+ * @throws IllegalArgumentException if this iCalendar object contains
+ * user-defined property or component objects
+ */
+ public String write() {
+ ICalVersion version = (this.version == null) ? ICalVersion.V2_0 : this.version;
+ return Biweekly.write(this).version(version).go();
+ }
+
+ /**
+ *
+ * Marshals this iCalendar object to its traditional, plain-text
+ * representation.
+ *
+ *
+ * If this iCalendar object contains user-defined property or component
+ * objects, you must use the {@link Biweekly} or {@link ICalWriter} classes
+ * instead in order to register the scribe classes.
+ *
+ * @param file the file to write to
+ * @throws IllegalArgumentException if this iCalendar object contains
+ * user-defined property or component objects
+ * @throws IOException if there's an problem writing to the file
+ */
+ public void write(File file) throws IOException {
+ ICalVersion version = (this.version == null) ? ICalVersion.V2_0 : this.version;
+ Biweekly.write(this).version(version).go(file);
+ }
+
+ /**
+ *
+ * Marshals this iCalendar object to its traditional, plain-text
+ * representation.
+ *
+ *
+ * If this iCalendar object contains user-defined property or component
+ * objects, you must use the {@link Biweekly} or {@link ICalWriter} classes
+ * instead in order to register the scribe classes.
+ *
+ * @param out the output stream to write to
+ * @throws IllegalArgumentException if this iCalendar object contains
+ * user-defined property or component objects
+ * @throws IOException if there's a problem writing to the output stream
+ */
+ public void write(OutputStream out) throws IOException {
+ ICalVersion version = (this.version == null) ? ICalVersion.V2_0 : this.version;
+ Biweekly.write(this).version(version).go(out);
+ }
+
+ /**
+ *
+ * Marshals this iCalendar object to its traditional, plain-text
+ * representation.
+ *
+ *
+ * If this iCalendar object contains user-defined property or component
+ * objects, you must use the {@link Biweekly} or {@link ICalWriter} classes
+ * instead in order to register the scribe classes.
+ *
+ * @param writer the writer to write to
+ * @throws IllegalArgumentException if this iCalendar object contains
+ * user-defined property or component objects
+ * @throws IOException if there's a problem writing to the writer
+ */
+ public void write(Writer writer) throws IOException {
+ ICalVersion version = (this.version == null) ? ICalVersion.V2_0 : this.version;
+ Biweekly.write(this).version(version).go(writer);
+ }
+
+ /**
+ *
+ * Marshals this iCalendar object to its XML representation (xCal).
+ *
+ *
+ * If this iCalendar object contains user-defined property or component
+ * objects, you must use the {@link Biweekly}, {@link XCalWriter}, or
+ * {@link XCalDocument} classes instead in order to register the scribe
+ * classes.
+ *
+ * @return the XML document
+ * @throws IllegalArgumentException if this iCalendar object contains
+ * user-defined property or component objects
+ */
+ public String writeXml() {
+ return Biweekly.writeXml(this).indent(2).go();
+ }
+
+ /**
+ *
+ * Marshals this iCalendar object to its XML representation (xCal).
+ *
+ *
+ * If this iCalendar object contains user-defined property or component
+ * objects, you must use the {@link Biweekly}, {@link XCalWriter}, or
+ * {@link XCalDocument} classes instead in order to register the scribe
+ * classes.
+ *
+ * @param file the file to write to
+ * @throws IllegalArgumentException if this iCalendar object contains
+ * user-defined property or component objects
+ * @throws TransformerException if there's a problem writing to the file
+ * @throws IOException if there's a problem opening the file
+ */
+ public void writeXml(File file) throws TransformerException, IOException {
+ Biweekly.writeXml(this).indent(2).go(file);
+ }
+
+ /**
+ *
+ * Marshals this iCalendar object to its XML representation (xCal).
+ *
+ *
+ * If this iCalendar object contains user-defined property or component
+ * objects, you must use the {@link Biweekly}, {@link XCalWriter}, or
+ * {@link XCalDocument} classes instead in order to register the scribe
+ * classes.
+ *
+ * @param out the output stream to write to
+ * @throws IllegalArgumentException if this iCalendar object contains
+ * user-defined property or component objects
+ * @throws TransformerException if there's a problem writing to the output
+ * stream
+ */
+ public void writeXml(OutputStream out) throws TransformerException {
+ Biweekly.writeXml(this).indent(2).go(out);
+ }
+
+ /**
+ *
+ * Marshals this iCalendar object to its XML representation (xCal).
+ *
+ *
+ * If this iCalendar object contains user-defined property or component
+ * objects, you must use the {@link Biweekly}, {@link XCalWriter}, or
+ * {@link XCalDocument} classes instead in order to register the scribe
+ * classes.
+ *
+ * @param writer the writer to write to
+ * @throws IllegalArgumentException if this iCalendar object contains
+ * user-defined property or component objects
+ * @throws TransformerException if there's a problem writing to the writer
+ */
+ public void writeXml(Writer writer) throws TransformerException {
+ Biweekly.writeXml(this).indent(2).go(writer);
+ }
+
+ /**
+ *
+ * Marshals this iCalendar object to its JSON representation (jCal).
+ *
+ *
+ * If this iCalendar object contains user-defined property or component
+ * objects, you must use the {@link Biweekly} or {@link JCalWriter} classes
+ * instead in order to register the scribe classes.
+ *
+ * @return the JSON string
+ * @throws IllegalArgumentException if this iCalendar object contains
+ * user-defined property or component objects
+ */
+ public String writeJson() {
+ return Biweekly.writeJson(this).go();
+ }
+
+ /**
+ *
+ * Marshals this iCalendar object to its JSON representation (jCal).
+ *
+ *
+ * If this iCalendar object contains user-defined property or component
+ * objects, you must use the {@link Biweekly} or {@link JCalWriter} classes
+ * instead in order to register the scribe classes.
+ *
+ * @param file the file to write to
+ * @throws IllegalArgumentException if this iCalendar object contains
+ * user-defined property or component objects
+ * @throws IOException if there's a problem writing to the file
+ */
+ public void writeJson(File file) throws IOException {
+ Biweekly.writeJson(this).go(file);
+ }
+
+ /**
+ *
+ * Marshals this iCalendar object to its JSON representation (jCal).
+ *
+ *
+ * If this iCalendar object contains user-defined property or component
+ * objects, you must use the {@link Biweekly} or {@link JCalWriter} classes
+ * instead in order to register the scribe classes.
+ *
+ * @param out the output stream to write to
+ * @throws IllegalArgumentException if this iCalendar object contains
+ * user-defined property or component objects
+ * @throws IOException if there's a problem writing to the output stream
+ */
+ public void writeJson(OutputStream out) throws IOException {
+ Biweekly.writeJson(this).go(out);
+ }
+
+ /**
+ *
+ * Marshals this iCalendar object to its JSON representation (jCal).
+ *
+ *
+ * If this iCalendar object contains user-defined property or component
+ * objects, you must use the {@link Biweekly} or {@link JCalWriter} classes
+ * instead in order to register the scribe classes.
+ *
+ * @param writer the writer to write to
+ * @throws IllegalArgumentException if this iCalendar object contains
+ * user-defined property or component objects
+ * @throws IOException if there's a problem writing to the writer
+ */
+ public void writeJson(Writer writer) throws IOException {
+ Biweekly.writeJson(this).go(writer);
+ }
+
+ @Override
+ protected Map toStringValues() {
+ Map fields = new HashMap();
+ fields.put("version", version);
+ return fields;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = super.hashCode();
+ result = prime * result + ((version == null) ? 0 : version.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!super.equals(obj)) return false;
+ ICalendar other = (ICalendar) obj;
+ if (version != other.version) return false;
+ return true;
+ }
+}
diff --git a/app/src/main/java/biweekly/Messages.java b/app/src/main/java/biweekly/Messages.java
new file mode 100644
index 0000000000..d129758ab2
--- /dev/null
+++ b/app/src/main/java/biweekly/Messages.java
@@ -0,0 +1,100 @@
+package biweekly;
+
+import java.text.MessageFormat;
+import java.util.MissingResourceException;
+import java.util.ResourceBundle;
+
+/*
+ Copyright (c) 2013-2023, Michael Angstadt
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+
+ 1. Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+ 2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * Singleton for accessing the i18n resource bundle.
+ * @author Michael Angstadt
+ */
+public enum Messages {
+ INSTANCE;
+
+ private final transient ResourceBundle messages;
+
+ Messages() {
+ messages = ResourceBundle.getBundle("biweekly/messages");
+ }
+
+ /**
+ * Gets a validation warning message.
+ * @param code the message code
+ * @param args the message arguments
+ * @return the message
+ */
+ public String getValidationWarning(int code, Object... args) {
+ return getMessage("validate." + code, args);
+ }
+
+ /**
+ * Gets a parser warning message.
+ * @param code the message code
+ * @param args the message arguments
+ * @return the message
+ */
+ public String getParseMessage(int code, Object... args) {
+ return getMessage("parse." + code, args);
+ }
+
+ /**
+ * Gets an exception message.
+ * @param code the message code
+ * @param args the message arguments
+ * @return the message or null if not found
+ */
+ public String getExceptionMessage(int code, Object... args) {
+ return getMessage("exception." + code, args);
+ }
+
+ /**
+ * Builds an {@link IllegalArgumentException} from an exception message.
+ * @param code the message code
+ * @param args the message arguments
+ * @return the exception or null if the message was not found
+ */
+ public IllegalArgumentException getIllegalArgumentException(int code, Object... args) {
+ String message = getExceptionMessage(code, args);
+ return (message == null) ? null : new IllegalArgumentException(message);
+ }
+
+ /**
+ * Gets a message.
+ * @param key the message key
+ * @param args the message arguments
+ * @return the message or null if not found
+ */
+ public String getMessage(String key, Object... args) {
+ try {
+ String message = messages.getString(key);
+ return MessageFormat.format(message, args);
+ } catch (MissingResourceException e) {
+ return null;
+ }
+ }
+}
diff --git a/app/src/main/java/biweekly/ValidationWarning.java b/app/src/main/java/biweekly/ValidationWarning.java
new file mode 100644
index 0000000000..6ef1639740
--- /dev/null
+++ b/app/src/main/java/biweekly/ValidationWarning.java
@@ -0,0 +1,78 @@
+package biweekly;
+
+/*
+ Copyright (c) 2013-2023, Michael Angstadt
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+
+ 1. Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+ 2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * Represents a validation warning.
+ * @author Michael Angstadt
+ */
+public class ValidationWarning {
+ private final Integer code;
+ private final String message;
+
+ /**
+ * Creates a new validation warning.
+ * @param code the warning message code
+ * @param args the warning message arguments
+ */
+ public ValidationWarning(int code, Object... args) {
+ this.code = code;
+ this.message = Messages.INSTANCE.getValidationWarning(code, args);
+ }
+
+ /**
+ * Creates a new validation warning.
+ * @param message the warning message
+ */
+ public ValidationWarning(String message) {
+ this.code = null;
+ this.message = message;
+ }
+
+ /**
+ * Gets the validation warning code.
+ * @return the warning code or null if no code was specified
+ */
+ public Integer getCode() {
+ return code;
+ }
+
+ /**
+ * Gets the validation warning message.
+ * @return the warning message
+ */
+ public String getMessage() {
+ return message;
+ }
+
+ @Override
+ public String toString() {
+ if (code == null) {
+ return message;
+ }
+ return "(" + code + ") " + message;
+ }
+}
diff --git a/app/src/main/java/biweekly/ValidationWarnings.java b/app/src/main/java/biweekly/ValidationWarnings.java
new file mode 100644
index 0000000000..2048d97a4e
--- /dev/null
+++ b/app/src/main/java/biweekly/ValidationWarnings.java
@@ -0,0 +1,289 @@
+package biweekly;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import biweekly.ValidationWarnings.WarningsGroup;
+import biweekly.component.ICalComponent;
+import biweekly.property.ICalProperty;
+import biweekly.util.StringUtils;
+import biweekly.util.StringUtils.JoinCallback;
+
+/*
+ Copyright (c) 2013-2023, Michael Angstadt
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+
+ 1. Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+ 2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ *
+ * Holds the validation warnings of an iCalendar object.
+ *
+ *
+ * Examples:
+ *
+ *
+ *
+ * //validate an iCalendar object
+ * ValidationWarnings warnings = ical.validate();
+ *
+ * //print all warnings to a string:
+ * System.out.println(warnings.toString());
+ * //sample output:
+ * //[ICalendar]: ProductId is not set (it is a required property).
+ * //[ICalendar > VEvent > DateStart]: DateStart must come before DateEnd.
+ * //[ICalendar > VEvent > VAlarm]: The trigger must specify which date field its duration is relative to.
+ *
+ * //iterate over each warnings group
+ * //this gives you access to the property/component object and its parent components
+ * for (WarningsGroup group : warnings) {
+ * ICalProperty prop = group.getProperty();
+ * if (prop == null) {
+ * //then it was a component that caused the warnings
+ * ICalComponent comp = group.getComponent();
+ * }
+ *
+ * //get parent components
+ * List<ICalComponent> hierarchy = group.getComponentHierarchy();
+ *
+ * //get warning messages
+ * List<String> messages = group.getMessages();
+ * }
+ *
+ * //you can also get the warnings of specific properties/components
+ * List<WarningsGroup> dtstartWarnings = warnings.getByProperty(DateStart.class);
+ * List<WarningsGroup> veventWarnings = warnings.getByComponent(VEvent.class);
+ *
+ * @author Michael Angstadt
+ * @see ICalendar#validate(ICalVersion)
+ */
+public class ValidationWarnings implements Iterable {
+ private final List warnings;
+
+ /**
+ * Creates a new validation warnings list.
+ * @param warnings the validation warnings
+ */
+ public ValidationWarnings(List warnings) {
+ this.warnings = warnings;
+ }
+
+ /**
+ * Gets all validation warnings of a given property.
+ * @param propertyClass the property (e.g. {@code DateStart.class})
+ * @return the validation warnings
+ */
+ public List getByProperty(Class extends ICalProperty> propertyClass) {
+ List warnings = new ArrayList();
+ for (WarningsGroup group : this.warnings) {
+ ICalProperty property = group.getProperty();
+ if (property == null) {
+ continue;
+ }
+
+ if (propertyClass == property.getClass()) {
+ warnings.add(group);
+ }
+ }
+ return warnings;
+ }
+
+ /**
+ * Gets all validation warnings of a given component.
+ * @param componentClass the component (e.g. {@code VEvent.class})
+ * @return the validation warnings
+ */
+ public List getByComponent(Class extends ICalComponent> componentClass) {
+ List warnings = new ArrayList();
+ for (WarningsGroup group : this.warnings) {
+ ICalComponent component = group.getComponent();
+ if (component == null) {
+ continue;
+ }
+
+ if (componentClass == component.getClass()) {
+ warnings.add(group);
+ }
+ }
+ return warnings;
+ }
+
+ /**
+ * Gets all the validation warnings.
+ * @return the validation warnings
+ */
+ public List getWarnings() {
+ return warnings;
+ }
+
+ /**
+ * Determines whether there are any validation warnings.
+ * @return true if there are none, false if there are one or more
+ */
+ public boolean isEmpty() {
+ return warnings.isEmpty();
+ }
+
+ /**
+ *
+ * Outputs all validation warnings as a newline-delimited string. For
+ * example:
+ *
+ *
+ *
+ * [ICalendar]: ProductId is not set (it is a required property).
+ * [ICalendar > VEvent > DateStart]: DateStart must come before DateEnd.
+ * [ICalendar > VEvent > VAlarm]: The trigger must specify which date field its duration is relative to.
+ *
+ */
+ @Override
+ public String toString() {
+ return StringUtils.join(warnings, StringUtils.NEWLINE);
+ }
+
+ /**
+ * Iterates over each warning group (same as calling
+ * {@code getWarnings().iterator()}).
+ * @return the iterator
+ */
+ public Iterator iterator() {
+ return warnings.iterator();
+ }
+
+ /**
+ * Holds the validation warnings of a property or component.
+ * @author Michael Angstadt
+ */
+ public static class WarningsGroup {
+ private final ICalProperty property;
+ private final ICalComponent component;
+ private final List componentHierarchy;
+ private final List warnings;
+
+ /**
+ * Creates a new set of validation warnings for a property.
+ * @param property the property that caused the warnings
+ * @param componentHierarchy the hierarchy of components that the
+ * property belongs to
+ * @param warning the warnings
+ */
+ public WarningsGroup(ICalProperty property, List componentHierarchy, List warning) {
+ this(null, property, componentHierarchy, warning);
+ }
+
+ /**
+ * Creates a new set of validation warnings for a component.
+ * @param component the component that caused the warnings
+ * @param componentHierarchy the hierarchy of components that the
+ * component belongs to
+ * @param warning the warnings
+ */
+ public WarningsGroup(ICalComponent component, List componentHierarchy, List warning) {
+ this(component, null, componentHierarchy, warning);
+ }
+
+ private WarningsGroup(ICalComponent component, ICalProperty property, List componentHierarchy, List warning) {
+ this.component = component;
+ this.property = property;
+ this.componentHierarchy = componentHierarchy;
+ this.warnings = warning;
+ }
+
+ /**
+ * Gets the property object that caused the validation warnings.
+ * @return the property object or null if a component caused the
+ * warnings.
+ */
+ public ICalProperty getProperty() {
+ return property;
+ }
+
+ /**
+ * Gets the component object that caused the validation warnings.
+ * @return the component object or null if a property caused the
+ * warnings.
+ */
+ public ICalComponent getComponent() {
+ return component;
+ }
+
+ /**
+ * Gets the hierarchy of components that the property or component
+ * belongs to.
+ * @return the component hierarchy
+ */
+ public List getComponentHierarchy() {
+ return componentHierarchy;
+ }
+
+ /**
+ * Gets the warnings that belong to the property or component.
+ * @return the warnings
+ */
+ public List getWarnings() {
+ return warnings;
+ }
+
+ /**
+ *
+ * Outputs each message in this warnings group as a newline-delimited
+ * string. Each line includes the component hierarchy and the name of
+ * the property/component. For example:
+ *
+ *
+ *
+ * [ICalendar > VEvent > VAlarm]: Email alarms must have at least one attendee.
+ * [ICalendar > VEvent > VAlarm]: The trigger must specify which date field its duration is relative to.
+ *
+ */
+ @Override
+ public String toString() {
+ final String prefix = "[" + buildPath() + "]: ";
+ return StringUtils.join(warnings, StringUtils.NEWLINE, new JoinCallback() {
+ public void handle(StringBuilder sb, ValidationWarning warning) {
+ sb.append(prefix).append(warning);
+ }
+ });
+ }
+
+ private String buildPath() {
+ StringBuilder sb = new StringBuilder();
+
+ if (!componentHierarchy.isEmpty()) {
+ String delimitor = " > ";
+
+ StringUtils.join(componentHierarchy, delimitor, sb, new JoinCallback() {
+ public void handle(StringBuilder sb, ICalComponent component) {
+ sb.append(component.getClass().getSimpleName());
+ }
+ });
+ sb.append(delimitor);
+ }
+
+ Object obj = (property == null) ? component : property;
+ sb.append(obj.getClass().getSimpleName());
+
+ return sb.toString();
+ }
+ }
+}
diff --git a/app/src/main/java/biweekly/biweekly.license b/app/src/main/java/biweekly/biweekly.license
new file mode 100644
index 0000000000..7df0740991
--- /dev/null
+++ b/app/src/main/java/biweekly/biweekly.license
@@ -0,0 +1,22 @@
+ Copyright (c) 2013-2021, Michael Angstadt
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+
+ 1. Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+ 2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/app/src/main/java/biweekly/biweekly.properties b/app/src/main/java/biweekly/biweekly.properties
new file mode 100644
index 0000000000..ef804973d6
--- /dev/null
+++ b/app/src/main/java/biweekly/biweekly.properties
@@ -0,0 +1,4 @@
+version=0.6.8-SNAPSHOT
+groupId=net.sf.biweekly
+artifactId=biweekly
+url=http://github.com/mangstadt/biweekly
diff --git a/app/src/main/java/biweekly/commons-codec.license b/app/src/main/java/biweekly/commons-codec.license
new file mode 100644
index 0000000000..75b52484ea
--- /dev/null
+++ b/app/src/main/java/biweekly/commons-codec.license
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ 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.
diff --git a/app/src/main/java/biweekly/component/DaylightSavingsTime.java b/app/src/main/java/biweekly/component/DaylightSavingsTime.java
new file mode 100644
index 0000000000..787c3188a4
--- /dev/null
+++ b/app/src/main/java/biweekly/component/DaylightSavingsTime.java
@@ -0,0 +1,67 @@
+package biweekly.component;
+
+/*
+ Copyright (c) 2013-2023, Michael Angstadt
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+
+ 1. Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+ 2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ *
+ * Defines the date range of a timezone's daylight savings time.
+ *
+ *
+ * Examples:
+ *
+ *
+ *
+ * VTimezone timezone = new VTimezone("Eastern Standard Time");
+ * DaylightSavingsTime daylight = new DaylightSavingsTime();
+ * DateTimeComponents components = new DateTimeComponents(1999, 4, 4, 2, 0, 0, false);
+ * daylight.setDateStart(components);
+ * daylight.setTimezoneOffsetFrom(-5, 0);
+ * daylight.setTimezoneOffsetTo(-4, 0);
+ * timezone.addDaylightSavingsTime(daylight);
+ *
+ * @author Michael Angstadt
+ * @see RFC 5545
+ * p.62-71
+ * @see RFC 2445 p.60-7
+ */
+public class DaylightSavingsTime extends Observance {
+ public DaylightSavingsTime() {
+ //empty
+ }
+
+ /**
+ * Copy constructor.
+ * @param original the component to make a copy of
+ */
+ public DaylightSavingsTime(DaylightSavingsTime original) {
+ super(original);
+ }
+
+ @Override
+ public DaylightSavingsTime copy() {
+ return new DaylightSavingsTime(this);
+ }
+}
diff --git a/app/src/main/java/biweekly/component/ICalComponent.java b/app/src/main/java/biweekly/component/ICalComponent.java
new file mode 100644
index 0000000000..2081899635
--- /dev/null
+++ b/app/src/main/java/biweekly/component/ICalComponent.java
@@ -0,0 +1,833 @@
+package biweekly.component;
+
+import java.lang.reflect.Constructor;
+import java.util.AbstractList;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import biweekly.ICalDataType;
+import biweekly.ICalVersion;
+import biweekly.ICalendar;
+import biweekly.Messages;
+import biweekly.ValidationWarnings.WarningsGroup;
+import biweekly.ValidationWarning;
+import biweekly.property.ICalProperty;
+import biweekly.property.RawProperty;
+import biweekly.property.Status;
+import biweekly.util.ListMultimap;
+import biweekly.util.StringUtils;
+
+/*
+ Copyright (c) 2013-2023, Michael Angstadt
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+
+ 1. Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+ 2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * Base class for all iCalendar component classes.
+ * @author Michael Angstadt
+ */
+public abstract class ICalComponent {
+ protected final ListMultimap, ICalComponent> components;
+ protected final ListMultimap, ICalProperty> properties;
+
+ public ICalComponent() {
+ components = new ListMultimap, ICalComponent>();
+ properties = new ListMultimap, ICalProperty>();
+ }
+
+ /**
+ * Copy constructor. Performs a deep copy of the given component's
+ * properties and sub-components.
+ * @param original the component to make a copy of
+ */
+ protected ICalComponent(ICalComponent original) {
+ this();
+ for (ICalProperty property : original.properties.values()) {
+ addProperty(property.copy());
+ }
+ for (ICalComponent component : original.components.values()) {
+ addComponent(component.copy());
+ }
+ }
+
+ /**
+ * Gets the first property of a given class.
+ * @param clazz the property class
+ * @param the property class
+ * @return the property or null if not found
+ */
+ public T getProperty(Class clazz) {
+ return clazz.cast(properties.first(clazz));
+ }
+
+ /**
+ * Gets all properties of a given class. Changes to the returned list will
+ * update the {@link ICalComponent} object, and vice versa.
+ * @param clazz the property class
+ * @param the property class
+ * @return the properties
+ */
+ public List getProperties(Class clazz) {
+ return new ICalPropertyList(clazz);
+ }
+
+ /**
+ * Gets all the properties associated with this component.
+ * @return the properties
+ */
+ public ListMultimap, ICalProperty> getProperties() {
+ return properties;
+ }
+
+ /**
+ * Adds a property to this component.
+ * @param property the property to add
+ */
+ public void addProperty(ICalProperty property) {
+ properties.put(property.getClass(), property);
+ }
+
+ /**
+ * Replaces all existing properties of the given property instance's class
+ * with the given property instance.
+ * @param property the property
+ * @return the replaced properties (this list is immutable)
+ */
+ public List setProperty(ICalProperty property) {
+ return properties.replace(property.getClass(), property);
+ }
+
+ /**
+ * Replaces all existing properties of the given class with a single
+ * property instance. If the property instance is null, then all instances
+ * of that property will be removed.
+ * @param clazz the property class (e.g. "DateStart.class")
+ * @param property the property or null to remove all properties of the
+ * given class
+ * @param the property class
+ * @return the replaced properties (this list is immutable)
+ */
+ public List setProperty(Class clazz, T property) {
+ List replaced = properties.replace(clazz, property);
+ return castList(replaced, clazz);
+ }
+
+ /**
+ * Removes a specific property instance from this component.
+ * @param property the property to remove
+ * @param the property class
+ * @return true if it was removed, false if it wasn't found
+ */
+ public boolean removeProperty(T property) {
+ return properties.remove(property.getClass(), property);
+ }
+
+ /**
+ * Removes all properties of a given class from this component.
+ * @param clazz the class of the properties to remove (e.g.
+ * "DateStart.class")
+ * @param the property class
+ * @return the removed properties (this list is immutable)
+ */
+ public List removeProperties(Class clazz) {
+ List removed = properties.removeAll(clazz);
+ return castList(removed, clazz);
+ }
+
+ /**
+ * Removes a specific sub-component instance from this component.
+ * @param component the component to remove
+ * @param the component class
+ * @return true if it was removed, false if it wasn't found
+ */
+ public boolean removeComponent(T component) {
+ return components.remove(component.getClass(), component);
+ }
+
+ /**
+ * Removes all sub-components of the given class from this component.
+ * @param clazz the class of the components to remove (e.g. "VEvent.class")
+ * @param the component class
+ * @return the removed components (this list is immutable)
+ */
+ public List removeComponents(Class clazz) {
+ List removed = components.removeAll(clazz);
+ return castList(removed, clazz);
+ }
+
+ /**
+ * Gets the first experimental property with a given name.
+ * @param name the property name (case insensitive, e.g. "X-ALT-DESC")
+ * @return the experimental property or null if none were found
+ */
+ public RawProperty getExperimentalProperty(String name) {
+ for (RawProperty raw : getExperimentalProperties()) {
+ if (raw.getName().equalsIgnoreCase(name)) {
+ return raw;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Gets all experimental properties with a given name.
+ * @param name the property name (case insensitive, e.g. "X-ALT-DESC")
+ * @return the experimental properties (this list is immutable)
+ */
+ public List getExperimentalProperties(String name) {
+ /*
+ * Note: The returned list is not backed by the parent component because
+ * this would allow RawProperty objects without the specified name to be
+ * added to the list.
+ */
+ List toReturn = new ArrayList();
+ for (RawProperty property : getExperimentalProperties()) {
+ if (property.getName().equalsIgnoreCase(name)) {
+ toReturn.add(property);
+ }
+ }
+ return Collections.unmodifiableList(toReturn);
+ }
+
+ /**
+ * Gets all experimental properties associated with this component. Changes
+ * to the returned list will update the {@link ICalComponent} object, and
+ * vice versa.
+ * @return the experimental properties
+ */
+ public List getExperimentalProperties() {
+ return getProperties(RawProperty.class);
+ }
+
+ /**
+ * Adds an experimental property to this component.
+ * @param name the property name (e.g. "X-ALT-DESC")
+ * @param value the property value
+ * @return the property object that was created
+ */
+ public RawProperty addExperimentalProperty(String name, String value) {
+ return addExperimentalProperty(name, null, value);
+ }
+
+ /**
+ * Adds an experimental property to this component.
+ * @param name the property name (e.g. "X-ALT-DESC")
+ * @param dataType the property's data type or null if unknown
+ * @param value the property value
+ * @return the property object that was created
+ */
+ public RawProperty addExperimentalProperty(String name, ICalDataType dataType, String value) {
+ RawProperty raw = new RawProperty(name, dataType, value);
+ addProperty(raw);
+ return raw;
+ }
+
+ /**
+ * Adds an experimental property to this component, removing all existing
+ * properties that have the same name.
+ * @param name the property name (e.g. "X-ALT-DESC")
+ * @param value the property value
+ * @return the property object that was created
+ */
+ public RawProperty setExperimentalProperty(String name, String value) {
+ return setExperimentalProperty(name, null, value);
+ }
+
+ /**
+ * Adds an experimental property to this component, removing all existing
+ * properties that have the same name.
+ * @param name the property name (e.g. "X-ALT-DESC")
+ * @param dataType the property's data type or null if unknown
+ * @param value the property value
+ * @return the property object that was created
+ */
+ public RawProperty setExperimentalProperty(String name, ICalDataType dataType, String value) {
+ removeExperimentalProperties(name);
+ return addExperimentalProperty(name, dataType, value);
+ }
+
+ /**
+ * Removes all experimental properties that have the given name.
+ * @param name the component name (e.g. "X-ALT-DESC")
+ * @return the removed properties (this list is immutable)
+ */
+ public List removeExperimentalProperties(String name) {
+ List all = getExperimentalProperties();
+ List toRemove = new ArrayList();
+ for (RawProperty property : all) {
+ if (property.getName().equalsIgnoreCase(name)) {
+ toRemove.add(property);
+ }
+ }
+
+ all.removeAll(toRemove);
+ return Collections.unmodifiableList(toRemove);
+ }
+
+ /**
+ * Gets the first sub-component of a given class.
+ * @param clazz the component class
+ * @param the component class
+ * @return the sub-component or null if not found
+ */
+ public T getComponent(Class clazz) {
+ return clazz.cast(components.first(clazz));
+ }
+
+ /**
+ * Gets all sub-components of a given class. Changes to the returned list
+ * will update the parent component object, and vice versa.
+ * @param clazz the component class
+ * @param the component class
+ * @return the sub-components
+ */
+ public List getComponents(Class clazz) {
+ return new ICalComponentList(clazz);
+ }
+
+ /**
+ * Gets all the sub-components associated with this component.
+ * @return the sub-components
+ */
+ public ListMultimap, ICalComponent> getComponents() {
+ return components;
+ }
+
+ /**
+ * Adds a sub-component to this component.
+ * @param component the component to add
+ */
+ public void addComponent(ICalComponent component) {
+ components.put(component.getClass(), component);
+ }
+
+ /**
+ * Replaces all sub-components of a given class with the given component.
+ * @param component the component
+ * @return the replaced sub-components (this list is immutable)
+ */
+ public List setComponent(ICalComponent component) {
+ return components.replace(component.getClass(), component);
+ }
+
+ /**
+ * Replaces all sub-components of a given class with the given component. If
+ * the component instance is null, then all instances of that component will
+ * be removed.
+ * @param clazz the component's class
+ * @param component the component or null to remove all components of the
+ * given class
+ * @param the component class
+ * @return the replaced sub-components (this list is immutable)
+ */
+ public List setComponent(Class clazz, T component) {
+ List replaced = components.replace(clazz, component);
+ return castList(replaced, clazz);
+ }
+
+ /**
+ * Gets the first experimental sub-component with a given name.
+ * @param name the component name (case insensitive, e.g. "X-PARTY")
+ * @return the experimental component or null if none were found
+ */
+ public RawComponent getExperimentalComponent(String name) {
+ for (RawComponent component : getExperimentalComponents()) {
+ if (component.getName().equalsIgnoreCase(name)) {
+ return component;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Gets all experimental sub-component with a given name.
+ * @param name the component name (case insensitive, e.g. "X-PARTY")
+ * @return the experimental components (this list is immutable)
+ */
+ public List getExperimentalComponents(String name) {
+ /*
+ * Note: The returned list is not backed by the parent component because
+ * this would allow RawComponent objects without the specified name to
+ * be added to the list.
+ */
+ List toReturn = new ArrayList();
+ for (RawComponent component : getExperimentalComponents()) {
+ if (component.getName().equalsIgnoreCase(name)) {
+ toReturn.add(component);
+ }
+ }
+ return Collections.unmodifiableList(toReturn);
+ }
+
+ /**
+ * Gets all experimental sub-components associated with this component.
+ * Changes to the returned list will update the parent {@link ICalComponent}
+ * object, and vice versa.
+ * @return the experimental components
+ */
+ public List getExperimentalComponents() {
+ return getComponents(RawComponent.class);
+ }
+
+ /**
+ * Adds an experimental sub-component to this component.
+ * @param name the component name (e.g. "X-PARTY")
+ * @return the component object that was created
+ */
+ public RawComponent addExperimentalComponent(String name) {
+ RawComponent raw = new RawComponent(name);
+ addComponent(raw);
+ return raw;
+ }
+
+ /**
+ * Adds an experimental sub-component to this component, removing all
+ * existing components that have the same name.
+ * @param name the component name (e.g. "X-PARTY")
+ * @return the component object that was created
+ */
+ public RawComponent setExperimentalComponent(String name) {
+ removeExperimentalComponents(name);
+ return addExperimentalComponent(name);
+ }
+
+ /**
+ * Removes all experimental sub-components that have the given name.
+ * @param name the component name (e.g. "X-PARTY")
+ * @return the removed sub-components (this list is immutable)
+ */
+ public List removeExperimentalComponents(String name) {
+ List all = getExperimentalComponents();
+ List toRemove = new ArrayList();
+ for (RawComponent property : all) {
+ if (property.getName().equalsIgnoreCase(name)) {
+ toRemove.add(property);
+ }
+ }
+
+ all.removeAll(toRemove);
+ return Collections.unmodifiableList(toRemove);
+ }
+
+ /**
+ *
+ * Checks this component for data consistency problems or deviations from
+ * the specifications.
+ *
+ *
+ * The existence of validation warnings will not prevent the component
+ * object from being written to a data stream. Syntactically-correct output
+ * will still be produced. However, the consuming application may have
+ * trouble interpreting some of the data due to the presence of these
+ * warnings.
+ *
+ *
+ * These problems can largely be avoided by reading the Javadocs of the
+ * component and property classes, or by being familiar with the iCalendar
+ * standard.
+ *
+ * @param hierarchy the hierarchy of components that the component belongs
+ * to
+ * @param version the version to validate against
+ * @see ICalendar#validate(List, ICalVersion)
+ * @return a list of warnings or an empty list if no problems were found
+ */
+ public final List validate(List hierarchy, ICalVersion version) {
+ List warnings = new ArrayList();
+
+ //validate this component
+ List warningsBuf = new ArrayList(0);
+ validate(hierarchy, version, warningsBuf);
+ if (!warningsBuf.isEmpty()) {
+ warnings.add(new WarningsGroup(this, hierarchy, warningsBuf));
+ }
+
+ //add this component to the hierarchy list
+ //copy the list so other validate() calls aren't effected
+ hierarchy = new ArrayList(hierarchy);
+ hierarchy.add(this);
+
+ //validate properties
+ for (ICalProperty property : properties.values()) {
+ List propWarnings = property.validate(hierarchy, version);
+ if (!propWarnings.isEmpty()) {
+ warnings.add(new WarningsGroup(property, hierarchy, propWarnings));
+ }
+ }
+
+ //validate sub-components
+ for (ICalComponent component : components.values()) {
+ warnings.addAll(component.validate(hierarchy, version));
+ }
+
+ return warnings;
+ }
+
+ /**
+ *
+ * Checks the component for data consistency problems or deviations from the
+ * spec.
+ *
+ *
+ * This method should be overridden by child classes that wish to provide
+ * validation logic. The default implementation of this method does nothing.
+ *
+ * @param components the hierarchy of components that the component belongs
+ * to
+ * @param version the version to validate against
+ * @param warnings the list to add the warnings to
+ */
+ protected void validate(List components, ICalVersion version, List warnings) {
+ //do nothing
+ }
+
+ /**
+ * Utility method for validating that there is exactly one instance of each
+ * of the given properties.
+ * @param warnings the list to add the warnings to
+ * @param classes the properties to check
+ */
+ protected void checkRequiredCardinality(List warnings, Class extends ICalProperty>... classes) {
+ for (Class extends ICalProperty> clazz : classes) {
+ List extends ICalProperty> props = getProperties(clazz);
+
+ if (props.isEmpty()) {
+ warnings.add(new ValidationWarning(2, clazz.getSimpleName()));
+ continue;
+ }
+
+ if (props.size() > 1) {
+ warnings.add(new ValidationWarning(3, clazz.getSimpleName()));
+ continue;
+ }
+ }
+ }
+
+ /**
+ * Utility method for validating that there is no more than one instance of
+ * each of the given properties.
+ * @param warnings the list to add the warnings to
+ * @param classes the properties to check
+ */
+ protected void checkOptionalCardinality(List warnings, Class extends ICalProperty>... classes) {
+ for (Class extends ICalProperty> clazz : classes) {
+ List extends ICalProperty> props = getProperties(clazz);
+
+ if (props.size() > 1) {
+ warnings.add(new ValidationWarning(3, clazz.getSimpleName()));
+ continue;
+ }
+ }
+ }
+
+ /**
+ * Utility method for validating the {@link Status} property of a component.
+ * @param warnings the list to add the warnings to
+ * @param allowed the valid statuses
+ */
+ protected void checkStatus(List warnings, Status... allowed) {
+ Status actual = getProperty(Status.class);
+ if (actual == null) {
+ return;
+ }
+
+ List allowedValues = new ArrayList(allowed.length);
+ for (Status status : allowed) {
+ String value = status.getValue().toLowerCase();
+ allowedValues.add(value);
+ }
+
+ String actualValue = actual.getValue().toLowerCase();
+ if (!allowedValues.contains(actualValue)) {
+ warnings.add(new ValidationWarning(13, actual.getValue(), allowedValues));
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ toString(0, sb);
+ return sb.toString();
+ }
+
+ /**
+ *
+ * Gets string representations of any additional fields the component has
+ * (other than sub-components and properties) for the {@link #toString}
+ * method.
+ *
+ *
+ * Meant to be overridden by child classes. The default implementation
+ * returns an empty map.
+ *
+ * @return the values of the component's fields (key = field name, value =
+ * field value)
+ */
+ protected Map toStringValues() {
+ return Collections.emptyMap();
+ }
+
+ private void toString(int depth, StringBuilder sb) {
+ StringUtils.repeat(' ', depth * 2, sb);
+ sb.append(getClass().getName());
+
+ Map fields = toStringValues();
+ if (!fields.isEmpty()) {
+ sb.append(' ').append(fields.toString());
+ }
+
+ sb.append(StringUtils.NEWLINE);
+
+ depth++;
+ for (ICalProperty property : properties.values()) {
+ StringUtils.repeat(' ', depth * 2, sb);
+ sb.append(property).append(StringUtils.NEWLINE);
+ }
+ for (ICalComponent component : components.values()) {
+ component.toString(depth, sb);
+ }
+ }
+
+ /**
+ *
+ * Creates a deep copy of this component object.
+ *
+ *
+ * The default implementation of this method uses reflection to look for a
+ * copy constructor. Child classes SHOULD override this method to avoid the
+ * performance overhead involved in using reflection.
+ *
+ *
+ * The child class's copy constructor, if present, MUST invoke the
+ * {@link #ICalComponent(ICalComponent)} super constructor to ensure that
+ * the component's properties and sub-components are copied.
+ *
+ *
+ * This method MUST be overridden by the child class if the child class does
+ * not have a copy constructor. Otherwise, an
+ * {@link UnsupportedOperationException} will be thrown when an attempt is
+ * made to copy the component (such as in the
+ * {@link ICalendar#ICalendar(ICalendar) ICalendar class's copy constructor}
+ * ).
+ *
+ * @return the copy
+ * @throws UnsupportedOperationException if the class does not have a copy
+ * constructor or there is a problem invoking it
+ */
+ public ICalComponent copy() {
+ Class extends ICalComponent> clazz = getClass();
+
+ try {
+ Constructor extends ICalComponent> copyConstructor = clazz.getConstructor(clazz);
+ return copyConstructor.newInstance(this);
+ } catch (Exception e) {
+ throw new UnsupportedOperationException(Messages.INSTANCE.getExceptionMessage(1, clazz.getName()), e);
+ }
+ }
+
+ /**
+ * Casts all objects in the given list to the given class, adding the casted
+ * objects to a new list.
+ * @param list the list to cast
+ * @param castTo the class to cast to
+ * @param the class to cast to
+ * @return the new list (immutable)
+ */
+ private static List castList(List> list, Class castTo) {
+ List casted = new ArrayList(list.size());
+ for (Object object : list) {
+ casted.add(castTo.cast(object));
+ }
+ return Collections.unmodifiableList(casted);
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+
+ int propertiesHash = 1;
+ for (ICalProperty property : properties.values()) {
+ propertiesHash += property.hashCode();
+ }
+ result = prime * result + propertiesHash;
+
+ int componentsHash = 1;
+ for (ICalComponent component : components.values()) {
+ componentsHash += component.hashCode();
+ }
+ result = prime * result + componentsHash;
+
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) return true;
+ if (obj == null) return false;
+ if (getClass() != obj.getClass()) return false;
+ ICalComponent other = (ICalComponent) obj;
+
+ if (properties.size() != other.properties.size()) return false;
+ if (components.size() != other.components.size()) return false;
+
+ if (!compareMultimaps(properties, other.properties)) return false;
+ if (!compareMultimaps(components, other.components)) return false;
+
+ return true;
+ }
+
+ private static boolean compareMultimaps(ListMultimap map1, ListMultimap map2) {
+ for (Map.Entry> entry : map1) {
+ K key = entry.getKey();
+ List value = entry.getValue();
+ List otherValue = map2.get(key);
+
+ if (value.size() != otherValue.size()) {
+ return false;
+ }
+
+ List otherValueCopy = new ArrayList(otherValue);
+ for (V property : value) {
+ if (!otherValueCopy.remove(property)) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ /**
+ *
+ * A list that automatically casts {@link ICalComponent} instances stored in
+ * this component to a given component class.
+ *
+ *
+ * This list is backed by the {@link ICalComponent} object. Any changes made
+ * to the list will affect the {@link ICalComponent} object and vice versa.
+ *
+ * @param the component class
+ */
+ private class ICalComponentList extends AbstractList {
+ protected final Class componentClass;
+ protected final List components;
+
+ /**
+ * @param componentClass the component class
+ */
+ public ICalComponentList(Class componentClass) {
+ this.componentClass = componentClass;
+ components = ICalComponent.this.components.get(componentClass);
+ }
+
+ @Override
+ public void add(int index, T value) {
+ components.add(index, value);
+ }
+
+ @Override
+ public T remove(int index) {
+ ICalComponent removed = components.remove(index);
+ return cast(removed);
+ }
+
+ @Override
+ public T get(int index) {
+ ICalComponent property = components.get(index);
+ return cast(property);
+ }
+
+ @Override
+ public T set(int index, T value) {
+ ICalComponent replaced = components.set(index, value);
+ return cast(replaced);
+ }
+
+ @Override
+ public int size() {
+ return components.size();
+ }
+
+ protected T cast(ICalComponent value) {
+ return componentClass.cast(value);
+ }
+ }
+
+ /**
+ *
+ * A list that automatically casts {@link ICalProperty} instances stored in
+ * this component to a given property class.
+ *
+ *
+ * This list is backed by the {@link ICalComponent} object. Any changes made
+ * to the list will affect the {@link ICalComponent} object and vice versa.
+ *
+ * @param the property class
+ */
+ private class ICalPropertyList extends AbstractList {
+ protected final Class propertyClass;
+ protected final List properties;
+
+ /**
+ * @param propertyClass the property class
+ */
+ public ICalPropertyList(Class propertyClass) {
+ this.propertyClass = propertyClass;
+ properties = ICalComponent.this.properties.get(propertyClass);
+ }
+
+ @Override
+ public void add(int index, T value) {
+ properties.add(index, value);
+ }
+
+ @Override
+ public T remove(int index) {
+ ICalProperty removed = properties.remove(index);
+ return cast(removed);
+ }
+
+ @Override
+ public T get(int index) {
+ ICalProperty property = properties.get(index);
+ return cast(property);
+ }
+
+ @Override
+ public T set(int index, T value) {
+ ICalProperty replaced = properties.set(index, value);
+ return cast(replaced);
+ }
+
+ @Override
+ public int size() {
+ return properties.size();
+ }
+
+ protected T cast(ICalProperty value) {
+ return propertyClass.cast(value);
+ }
+ }
+}
diff --git a/app/src/main/java/biweekly/component/Observance.java b/app/src/main/java/biweekly/component/Observance.java
new file mode 100644
index 0000000000..838983ebad
--- /dev/null
+++ b/app/src/main/java/biweekly/component/Observance.java
@@ -0,0 +1,409 @@
+package biweekly.component;
+
+import java.util.List;
+
+import biweekly.ICalVersion;
+import biweekly.ValidationWarning;
+import biweekly.property.Comment;
+import biweekly.property.DateStart;
+import biweekly.property.ExceptionDates;
+import biweekly.property.RecurrenceDates;
+import biweekly.property.RecurrenceRule;
+import biweekly.property.TimezoneName;
+import biweekly.property.TimezoneOffsetFrom;
+import biweekly.property.TimezoneOffsetTo;
+import biweekly.util.DateTimeComponents;
+import biweekly.util.ICalDate;
+import biweekly.util.Recurrence;
+import biweekly.util.UtcOffset;
+
+/*
+ Copyright (c) 2013-2023, Michael Angstadt
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+
+ 1. Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+ 2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * Represents a timezone observance (i.e. "daylight savings" and "standard"
+ * times).
+ * @author Michael Angstadt
+ * @see DaylightSavingsTime
+ * @see StandardTime
+ * @see RFC 5545
+ * p.62-71
+ * @see RFC 2445 p.60-7
+ */
+/*
+ * Note: References to the vCal 1.0 spec are omitted from the property
+ * getter/setter method Javadocs because vCal does not use the VTIMEZONE
+ * component.
+ */
+public class Observance extends ICalComponent {
+ public Observance() {
+ //empty
+ }
+
+ /**
+ * Copy constructor.
+ * @param original the component to make a copy of
+ */
+ public Observance(Observance original) {
+ super(original);
+ }
+
+ /**
+ * Gets the date that the timezone observance starts.
+ * @return the start date or null if not set
+ * @see RFC 5545
+ * p.97-8
+ * @see RFC 2445
+ * p.93-4
+ */
+ public DateStart getDateStart() {
+ return getProperty(DateStart.class);
+ }
+
+ /**
+ * Sets the date that the timezone observance starts.
+ * @param dateStart the start date or null to remove
+ * @see RFC 5545
+ * p.97-8
+ * @see RFC 2445
+ * p.93-4
+ */
+ public void setDateStart(DateStart dateStart) {
+ setProperty(DateStart.class, dateStart);
+ }
+
+ /**
+ * Sets the date that the timezone observance starts.
+ * @param date the start date or null to remove
+ * @return the property that was created
+ * @see RFC 5545
+ * p.97-8
+ * @see RFC 2445
+ * p.93-4
+ */
+ public DateStart setDateStart(ICalDate date) {
+ DateStart prop = (date == null) ? null : new DateStart(date);
+ setDateStart(prop);
+ return prop;
+ }
+
+ /**
+ * Sets the date that the timezone observance starts.
+ * @param rawComponents the start date or null to remove
+ * @return the property that was created
+ * @see RFC 5545
+ * p.97-8
+ * @see RFC 2445
+ * p.93-4
+ */
+ public DateStart setDateStart(DateTimeComponents rawComponents) {
+ return setDateStart((rawComponents == null) ? null : new ICalDate(rawComponents, true));
+ }
+
+ /**
+ * Gets the UTC offset that the timezone observance transitions to.
+ * @return the UTC offset or null if not set
+ * @see RFC 5545
+ * p.105-6
+ * @see RFC 2445
+ * p.100-1
+ */
+ public TimezoneOffsetTo getTimezoneOffsetTo() {
+ return getProperty(TimezoneOffsetTo.class);
+ }
+
+ /**
+ * Sets the UTC offset that the timezone observance transitions to.
+ * @param timezoneOffsetTo the UTC offset or null to remove
+ * @see RFC 5545
+ * p.105-6
+ * @see RFC 2445
+ * p.100-1
+ */
+ public void setTimezoneOffsetTo(TimezoneOffsetTo timezoneOffsetTo) {
+ setProperty(TimezoneOffsetTo.class, timezoneOffsetTo);
+ }
+
+ /**
+ * Sets the UTC offset that the timezone observance transitions to.
+ * @param offset the offset
+ * @return the property that was created
+ * @see RFC 5545
+ * p.105-6
+ * @see RFC 2445
+ * p.100-1
+ */
+ public TimezoneOffsetTo setTimezoneOffsetTo(UtcOffset offset) {
+ TimezoneOffsetTo prop = new TimezoneOffsetTo(offset);
+ setTimezoneOffsetTo(prop);
+ return prop;
+ }
+
+ /**
+ * Gets the UTC offset that the timezone observance transitions from.
+ * @return the UTC offset or null if not set
+ * @see RFC 5545
+ * p.104-5
+ * @see RFC 2445
+ * p.99-100
+ */
+ public TimezoneOffsetFrom getTimezoneOffsetFrom() {
+ return getProperty(TimezoneOffsetFrom.class);
+ }
+
+ /**
+ * Sets the UTC offset that the timezone observance transitions from.
+ * @param timezoneOffsetFrom the UTC offset or null to remove
+ * @see RFC 5545
+ * p.104-5
+ * @see RFC 2445
+ * p.99-100
+ */
+ public void setTimezoneOffsetFrom(TimezoneOffsetFrom timezoneOffsetFrom) {
+ setProperty(TimezoneOffsetFrom.class, timezoneOffsetFrom);
+ }
+
+ /**
+ * Sets the UTC offset that the timezone observance transitions from.
+ * @param offset the offset
+ * @return the property that was created
+ * @see RFC 5545
+ * p.104-5
+ * @see RFC 2445
+ * p.99-100
+ */
+ public TimezoneOffsetFrom setTimezoneOffsetFrom(UtcOffset offset) {
+ TimezoneOffsetFrom prop = new TimezoneOffsetFrom(offset);
+ setTimezoneOffsetFrom(prop);
+ return prop;
+ }
+
+ /**
+ * Gets how often the timezone observance repeats.
+ * @return the recurrence rule or null if not set
+ * @see RFC 5545
+ * p.122-32
+ * @see RFC 2445
+ * p.117-25
+ */
+ public RecurrenceRule getRecurrenceRule() {
+ return getProperty(RecurrenceRule.class);
+ }
+
+ /**
+ * Sets how often the timezone observance repeats.
+ * @param recur the recurrence rule or null to remove
+ * @return the property that was created
+ * @see RFC 5545
+ * p.122-32
+ * @see RFC 2445
+ * p.117-25
+ */
+ public RecurrenceRule setRecurrenceRule(Recurrence recur) {
+ RecurrenceRule prop = (recur == null) ? null : new RecurrenceRule(recur);
+ setRecurrenceRule(prop);
+ return prop;
+ }
+
+ /**
+ * Sets how often the timezone observance repeats.
+ * @param recurrenceRule the recurrence rule or null to remove
+ * @see RFC 5545
+ * p.122-32
+ * @see RFC 2445
+ * p.117-25
+ */
+ public void setRecurrenceRule(RecurrenceRule recurrenceRule) {
+ setProperty(RecurrenceRule.class, recurrenceRule);
+ }
+
+ /**
+ * Gets the comments attached to the timezone observance.
+ * @return the comments (any changes made this list will affect the parent
+ * component object and vice versa)
+ * @see RFC 5545
+ * p.83-4
+ * @see RFC 2445
+ * p.80-1
+ */
+ public List getComments() {
+ return getProperties(Comment.class);
+ }
+
+ /**
+ * Adds a comment to the timezone observance.
+ * @param comment the comment to add
+ * @see RFC 5545
+ * p.83-4
+ * @see RFC 2445
+ * p.80-1
+ */
+ public void addComment(Comment comment) {
+ addProperty(comment);
+ }
+
+ /**
+ * Adds a comment to the timezone observance.
+ * @param comment the comment to add
+ * @return the property that was created
+ * @see RFC 5545
+ * p.83-4
+ * @see RFC 2445
+ * p.80-1
+ */
+ public Comment addComment(String comment) {
+ Comment prop = new Comment(comment);
+ addComment(prop);
+ return prop;
+ }
+
+ /**
+ * Gets the list of dates/periods that help define the recurrence rule of
+ * this timezone observance (if one is defined).
+ * @return the recurrence dates (any changes made this list will affect the
+ * parent component object and vice versa)
+ * @see RFC 5545
+ * p.120-2
+ * @see RFC 2445
+ * p.115-7
+ */
+ public List getRecurrenceDates() {
+ return getProperties(RecurrenceDates.class);
+ }
+
+ /**
+ * Adds a list of dates/periods that help define the recurrence rule of this
+ * timezone observance (if one is defined).
+ * @param recurrenceDates the recurrence dates
+ * @see RFC 5545
+ * p.120-2
+ * @see RFC 2445
+ * p.115-7
+ */
+ public void addRecurrenceDates(RecurrenceDates recurrenceDates) {
+ addProperty(recurrenceDates);
+ }
+
+ /**
+ * Gets the traditional, non-standard names for the timezone observance.
+ * @return the timezone observance names (any changes made this list will
+ * affect the parent component object and vice versa)
+ * @see RFC 5545
+ * p.103-4
+ * @see RFC 2445
+ * p.98-9
+ */
+ public List getTimezoneNames() {
+ return getProperties(TimezoneName.class);
+ }
+
+ /**
+ * Adds a traditional, non-standard name for the timezone observance.
+ * @param timezoneName the timezone observance name
+ * @see RFC 5545
+ * p.103-4
+ * @see RFC 2445
+ * p.98-9
+ */
+ public void addTimezoneName(TimezoneName timezoneName) {
+ addProperty(timezoneName);
+ }
+
+ /**
+ * Adds a traditional, non-standard name for the timezone observance.
+ * @param timezoneName the timezone observance name (e.g. "EST")
+ * @return the property that was created
+ * @see RFC 5545
+ * p.103-4
+ * @see RFC 2445
+ * p.98-9
+ */
+ public TimezoneName addTimezoneName(String timezoneName) {
+ TimezoneName prop = new TimezoneName(timezoneName);
+ addTimezoneName(prop);
+ return prop;
+ }
+
+ /**
+ * Gets the list of exceptions to the timezone observance.
+ * @return the list of exceptions (any changes made this list will affect
+ * the parent component object and vice versa)
+ * @see RFC 5545
+ * p.118-20
+ * @see RFC 2445
+ * p.112-4
+ */
+ public List getExceptionDates() {
+ return getProperties(ExceptionDates.class);
+ }
+
+ /**
+ * Adds a list of exceptions to the timezone observance. Note that this
+ * property can contain multiple dates.
+ * @param exceptionDates the list of exceptions
+ * @see RFC 5545
+ * p.118-20
+ * @see RFC 2445
+ * p.112-4
+ */
+ public void addExceptionDates(ExceptionDates exceptionDates) {
+ addProperty(exceptionDates);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ protected void validate(List components, ICalVersion version, List warnings) {
+ if (version == ICalVersion.V1_0) {
+ warnings.add(new ValidationWarning(48, version));
+ }
+
+ checkRequiredCardinality(warnings, DateStart.class, TimezoneOffsetTo.class, TimezoneOffsetFrom.class);
+
+ //BYHOUR, BYMINUTE, and BYSECOND cannot be specified in RRULE if DTSTART's data type is "date"
+ //RFC 5545 p. 167
+ DateStart dateStart = getDateStart();
+ RecurrenceRule rrule = getRecurrenceRule();
+ if (dateStart != null && rrule != null) {
+ ICalDate start = dateStart.getValue();
+ Recurrence recur = rrule.getValue();
+ if (start != null && recur != null) {
+ if (!start.hasTime() && (!recur.getByHour().isEmpty() || !recur.getByMinute().isEmpty() || !recur.getBySecond().isEmpty())) {
+ warnings.add(new ValidationWarning(5));
+ }
+ }
+ }
+
+ //there *should* be only 1 instance of RRULE
+ //RFC 5545 p. 167
+ if (getProperties(RecurrenceRule.class).size() > 1) {
+ warnings.add(new ValidationWarning(6));
+ }
+ }
+
+ @Override
+ public Observance copy() {
+ return new Observance(this);
+ }
+}
diff --git a/app/src/main/java/biweekly/component/RawComponent.java b/app/src/main/java/biweekly/component/RawComponent.java
new file mode 100644
index 0000000000..0ce9db5f3e
--- /dev/null
+++ b/app/src/main/java/biweekly/component/RawComponent.java
@@ -0,0 +1,74 @@
+package biweekly.component;
+
+/*
+ Copyright (c) 2013-2023, Michael Angstadt
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+
+ 1. Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+ 2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * Represents a component that does not have a scribe associated with it.
+ * @author Michael Angstadt
+ */
+public class RawComponent extends ICalComponent {
+ private final String name;
+
+ public RawComponent(String name) {
+ this.name = name;
+ }
+
+ /**
+ * Copy constructor.
+ * @param original the component to make a copy of
+ */
+ public RawComponent(RawComponent original) {
+ super(original);
+ this.name = original.name;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public RawComponent copy() {
+ return new RawComponent(this);
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = super.hashCode();
+ result = prime * result + ((name == null) ? 0 : name.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!super.equals(obj)) return false;
+ RawComponent other = (RawComponent) obj;
+ if (name == null) {
+ if (other.name != null) return false;
+ } else if (!name.equals(other.name)) return false;
+ return true;
+ }
+}
diff --git a/app/src/main/java/biweekly/component/StandardTime.java b/app/src/main/java/biweekly/component/StandardTime.java
new file mode 100644
index 0000000000..24d46a334a
--- /dev/null
+++ b/app/src/main/java/biweekly/component/StandardTime.java
@@ -0,0 +1,67 @@
+package biweekly.component;
+
+/*
+ Copyright (c) 2013-2023, Michael Angstadt
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+
+ 1. Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+ 2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ *
+ * Defines the date range of a timezone's standard time.
+ *
+ *
+ * Examples:
+ *
+ *
+ *
+ * VTimezone timezone = new VTimezone("Eastern Standard Time");
+ * StandardTime standard = new StandardTime();
+ * DateTimeComponents components = new DateTimeComponents(1998, 10, 25, 2, 0, 0, false);
+ * standard.setDateStart(components);
+ * standard.setTimezoneOffsetFrom(-4, 0);
+ * standard.setTimezoneOffsetTo(-5, 0);
+ * timezone.addStandardTime(standard);
+ *
+ * @author Michael Angstadt
+ * @see RFC 5545
+ * p.62-71
+ * @see RFC 2445 p.60-7
+ */
+public class StandardTime extends Observance {
+ public StandardTime() {
+ //empty
+ }
+
+ /**
+ * Copy constructor.
+ * @param original the component to make a copy of
+ */
+ public StandardTime(StandardTime original) {
+ super(original);
+ }
+
+ @Override
+ public StandardTime copy() {
+ return new StandardTime(this);
+ }
+}
diff --git a/app/src/main/java/biweekly/component/VAlarm.java b/app/src/main/java/biweekly/component/VAlarm.java
new file mode 100644
index 0000000000..207cd557b5
--- /dev/null
+++ b/app/src/main/java/biweekly/component/VAlarm.java
@@ -0,0 +1,590 @@
+package biweekly.component;
+
+import java.util.Arrays;
+import java.util.List;
+
+import biweekly.ICalVersion;
+import biweekly.ValidationWarning;
+import biweekly.parameter.Related;
+import biweekly.property.Action;
+import biweekly.property.Attachment;
+import biweekly.property.Attendee;
+import biweekly.property.DateDue;
+import biweekly.property.DateEnd;
+import biweekly.property.DateStart;
+import biweekly.property.Description;
+import biweekly.property.DurationProperty;
+import biweekly.property.Repeat;
+import biweekly.property.Summary;
+import biweekly.property.Trigger;
+import biweekly.util.Duration;
+
+/*
+ Copyright (c) 2013-2023, Michael Angstadt
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+
+ 1. Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+ 2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ *
+ * Defines a reminder for an event or to-do task. This class contains static
+ * factory methods to aid in the construction of valid alarms.
+ *
+ *
+ * Examples:
+ *
+ *
+ *
+ * //audio alarm
+ * Trigger trigger = ...
+ * Attachment sound = ...
+ * VAlarm audio = VAlarm.audio(trigger, sound);
+ *
+ * //display alarm
+ * Trigger trigger = ...
+ * String message = "Meeting at 1pm";
+ * VAlarm display = VAlarm.display(trigger, message);
+ *
+ * //email alarm
+ * Trigger trigger = ...
+ * String subject = "Reminder: Meeting at 1pm";
+ * String body = "Team,\n\nThe team meeting scheduled for 1pm is about to start. Snacks will be served!\n\nThanks,\nJohn";
+ * List<String> to = Arrays.asList("janedoe@example.com", "bobsmith@example.com");
+ * VAlarm email = VAlarm.email(trigger, subject, body, to);
+ *
+ * @author Michael Angstadt
+ * @see RFC 5545 p.71-6
+ * @see RFC 2445
+ * p.67-73
+ */
+/*
+ * Note: References to the vCal 1.0 spec are omitted from the property
+ * getter/setter method Javadocs because vCal does not use the VALARM component.
+ */
+public class VAlarm extends ICalComponent {
+ /**
+ * Creates a new alarm. Consider using one of the static factory methods
+ * instead.
+ * @param action the alarm action (e.g. "email")
+ * @param trigger the trigger
+ */
+ public VAlarm(Action action, Trigger trigger) {
+ setAction(action);
+ setTrigger(trigger);
+ }
+
+ /**
+ * Copy constructor.
+ * @param original the component to make a copy of
+ */
+ public VAlarm(VAlarm original) {
+ super(original);
+ }
+
+ /**
+ * Creates an audio alarm.
+ * @param trigger the trigger
+ * @return the alarm
+ */
+ public static VAlarm audio(Trigger trigger) {
+ return audio(trigger, null);
+ }
+
+ /**
+ * Creates an audio alarm.
+ * @param trigger the trigger
+ * @param sound a sound to play when the alarm triggers
+ * @return the alarm
+ */
+ public static VAlarm audio(Trigger trigger, Attachment sound) {
+ VAlarm alarm = new VAlarm(Action.audio(), trigger);
+ if (sound != null) {
+ alarm.addAttachment(sound);
+ }
+ return alarm;
+ }
+
+ /**
+ * Creates a display alarm.
+ * @param trigger the trigger
+ * @param displayText the display text
+ * @return the alarm
+ */
+ public static VAlarm display(Trigger trigger, String displayText) {
+ VAlarm alarm = new VAlarm(Action.display(), trigger);
+ alarm.setDescription(displayText);
+ return alarm;
+ }
+
+ /**
+ * Creates an email alarm.
+ * @param trigger the trigger
+ * @param subject the email subject
+ * @param body the email body
+ * @param recipients the email address(es) to send the alert to
+ * @return the alarm
+ */
+ public static VAlarm email(Trigger trigger, String subject, String body, String... recipients) {
+ return email(trigger, subject, body, Arrays.asList(recipients));
+ }
+
+ /**
+ * Creates a procedure alarm (vCal 1.0 only).
+ * @param trigger the trigger
+ * @param path the path or name of the procedure
+ * @return the alarm
+ */
+ public static VAlarm procedure(Trigger trigger, String path) {
+ VAlarm alarm = new VAlarm(Action.procedure(), trigger);
+ alarm.setDescription(path);
+ return alarm;
+ }
+
+ /**
+ * Creates an email alarm.
+ * @param trigger the trigger
+ * @param subject the email subject
+ * @param body the email body
+ * @param recipients the email address(es) to send the alert to
+ * @return the alarm
+ */
+ public static VAlarm email(Trigger trigger, String subject, String body, List recipients) {
+ VAlarm alarm = new VAlarm(Action.email(), trigger);
+ alarm.setSummary(subject);
+ alarm.setDescription(body);
+ for (String recipient : recipients) {
+ alarm.addAttendee(new Attendee(null, recipient));
+ }
+ return alarm;
+ }
+
+ /**
+ * Gets any attachments that are associated with the alarm.
+ * @return the attachments (any changes made this list will affect the
+ * parent component object and vice versa)
+ * @see RFC 5545
+ * p.80-1
+ * @see RFC 2445
+ * p.77-8
+ */
+ public List getAttachments() {
+ return getProperties(Attachment.class);
+ }
+
+ /**
+ * Adds an attachment to the alarm. Note that AUDIO alarms should only have
+ * 1 attachment.
+ * @param attachment the attachment to add
+ * @see RFC 5545
+ * p.80-1
+ * @see RFC 2445
+ * p.77-8
+ */
+ public void addAttachment(Attachment attachment) {
+ addProperty(attachment);
+ }
+
+ /**
+ *
+ * Gets a detailed description of the alarm. The description should be more
+ * detailed than the one provided by the {@link Summary} property.
+ *
+ *
+ * This property has different meanings, depending on the alarm action:
+ *
+ *
+ * - DISPLAY - the display text
+ * - EMAIL - the body of the email message
+ * - all others - a general description of the alarm
+ *
+ * @return the description or null if not set
+ * @see RFC 5545
+ * p.84-5
+ * @see RFC 2445
+ * p.81-2
+ */
+ public Description getDescription() {
+ return getProperty(Description.class);
+ }
+
+ /**
+ *
+ * Sets a detailed description of the alarm. The description should be more
+ * detailed than the one provided by the {@link Summary} property.
+ *
+ *
+ * This property has different meanings, depending on the alarm action:
+ *
+ *
+ * - DISPLAY - the display text
+ * - EMAIL - the body of the email message
+ * - all others - a general description of the alarm
+ *
+ * @param description the description or null to remove
+ * @see RFC 5545
+ * p.84-5
+ * @see RFC 2445
+ * p.81-2
+ */
+ public void setDescription(Description description) {
+ setProperty(Description.class, description);
+ }
+
+ /**
+ *
+ * Sets a detailed description of the alarm. The description should be more
+ * detailed than the one provided by the {@link Summary} property.
+ *
+ *
+ * This property has different meanings, depending on the alarm action:
+ *
+ *
+ * - DISPLAY - the display text
+ * - EMAIL - the body of the email message
+ * - all others - a general description of the alarm
+ *
+ * @param description the description or null to remove
+ * @return the property that was created
+ * @see RFC 5545
+ * p.84-5
+ * @see RFC 2445
+ * p.81-2
+ */
+ public Description setDescription(String description) {
+ Description prop = (description == null) ? null : new Description(description);
+ setDescription(prop);
+ return prop;
+ }
+
+ /**
+ *
+ * Gets the summary of the alarm.
+ *
+ *
+ * This property has different meanings, depending on the alarm action:
+ *
+ *
+ * - EMAIL - the subject line of the email
+ * - all others - a one-line summary of the alarm
+ *
+ * @return the summary or null if not set
+ * @see RFC 5545
+ * p.93-4
+ * @see RFC 2445
+ * p.89-90
+ */
+ public Summary getSummary() {
+ return getProperty(Summary.class);
+ }
+
+ /**
+ *
+ * Sets the summary of the alarm.
+ *
+ *
+ * This property has different meanings, depending on the alarm action:
+ *
+ *
+ * - EMAIL - the subject line of the email
+ * - all others - a one-line summary of the alarm
+ *
+ * @param summary the summary or null to remove
+ * @see RFC 5545
+ * p.93-4
+ * @see RFC 2445
+ * p.89-90
+ */
+ public void setSummary(Summary summary) {
+ setProperty(Summary.class, summary);
+ }
+
+ /**
+ *
+ * Sets the summary of the alarm.
+ *
+ *
+ * This property has different meanings, depending on the alarm action:
+ *
+ *
+ * - EMAIL - the subject line of the email
+ * - all others - a one-line summary of the alarm
+ *
+ * @param summary the summary or null to remove
+ * @return the property that was created
+ * @see RFC 5545
+ * p.93-4
+ * @see RFC 2445
+ * p.89-90
+ */
+ public Summary setSummary(String summary) {
+ Summary prop = (summary == null) ? null : new Summary(summary);
+ setSummary(prop);
+ return prop;
+ }
+
+ /**
+ * Gets the people who will be emailed when the alarm fires (only applicable
+ * for EMAIL alarms).
+ * @return the email recipients (any changes made this list will affect the
+ * parent component object and vice versa)
+ * @see RFC 5545
+ * p.107-9
+ * @see RFC 2445
+ * p.102-4
+ */
+ public List getAttendees() {
+ return getProperties(Attendee.class);
+ }
+
+ /**
+ * Adds a person who will be emailed when the alarm fires (only applicable
+ * for EMAIL alarms).
+ * @param attendee the email recipient
+ * @see RFC 5545
+ * p.107-9
+ * @see RFC 2445
+ * p.102-4
+ */
+ public void addAttendee(Attendee attendee) {
+ addProperty(attendee);
+ }
+
+ /**
+ * Gets the type of action to invoke when the alarm is triggered.
+ * @return the action or null if not set
+ * @see RFC 5545
+ * p.132-3
+ * @see RFC 2445
+ * p.126
+ */
+ public Action getAction() {
+ return getProperty(Action.class);
+ }
+
+ /**
+ * Sets the type of action to invoke when the alarm is triggered.
+ * @param action the action or null to remove
+ * @see RFC 5545
+ * p.132-3
+ * @see RFC 2445
+ * p.126
+ */
+ public void setAction(Action action) {
+ setProperty(Action.class, action);
+ }
+
+ /**
+ * Gets the length of the pause between alarm repetitions.
+ * @return the duration or null if not set
+ * @see RFC 5545
+ * p.99
+ * @see RFC 2445
+ * p.94-5
+ */
+ public DurationProperty getDuration() {
+ return getProperty(DurationProperty.class);
+ }
+
+ /**
+ * Sets the length of the pause between alarm repetitions.
+ * @param duration the duration or null to remove
+ * @see RFC 5545
+ * p.99
+ * @see RFC 2445
+ * p.94-5
+ */
+ public void setDuration(DurationProperty duration) {
+ setProperty(DurationProperty.class, duration);
+ }
+
+ /**
+ * Sets the length of the pause between alarm repetitions.
+ * @param duration the duration or null to remove
+ * @return the property that was created
+ * @see RFC 5545
+ * p.99
+ * @see RFC 2445
+ * p.94-5
+ */
+ public DurationProperty setDuration(Duration duration) {
+ DurationProperty prop = (duration == null) ? null : new DurationProperty(duration);
+ setDuration(prop);
+ return prop;
+ }
+
+ /**
+ * Gets the number of times an alarm should be repeated after its initial
+ * trigger.
+ * @return the repeat count or null if not set
+ * @see RFC 5545
+ * p.133
+ * @see RFC 2445
+ * p.126-7
+ */
+ public Repeat getRepeat() {
+ return getProperty(Repeat.class);
+ }
+
+ /**
+ * Sets the number of times an alarm should be repeated after its initial
+ * trigger.
+ * @param repeat the repeat count or null to remove
+ * @see RFC 5545
+ * p.133
+ * @see RFC 2445
+ * p.126-7
+ */
+ public void setRepeat(Repeat repeat) {
+ setProperty(Repeat.class, repeat);
+ }
+
+ /**
+ * Sets the number of times an alarm should be repeated after its initial
+ * trigger.
+ * @param count the repeat count (e.g. "2" to repeat it two more times after
+ * it was initially triggered, for a total of three times) or null to remove
+ * @return the property that was created
+ * @see RFC 5545
+ * p.133
+ * @see RFC 2445
+ * p.126-7
+ */
+ public Repeat setRepeat(Integer count) {
+ Repeat prop = (count == null) ? null : new Repeat(count);
+ setRepeat(prop);
+ return prop;
+ }
+
+ /**
+ * Sets the repetition information for the alarm.
+ * @param count the repeat count (e.g. "2" to repeat it two more times after
+ * it was initially triggered, for a total of three times)
+ * @param pauseDuration the length of the pause between repeats
+ * @see RFC 5545
+ * p.133
+ * @see RFC 2445
+ * p.126-7
+ */
+ public void setRepeat(int count, Duration pauseDuration) {
+ Repeat repeat = new Repeat(count);
+ DurationProperty duration = new DurationProperty(pauseDuration);
+ setRepeat(repeat);
+ setDuration(duration);
+ }
+
+ /**
+ * Gets when the alarm will be triggered.
+ * @return the trigger time or null if not set
+ * @see RFC 5545
+ * p.133-6
+ * @see RFC 2445
+ * p.127-9
+ */
+ public Trigger getTrigger() {
+ return getProperty(Trigger.class);
+ }
+
+ /**
+ * Sets when the alarm will be triggered.
+ * @param trigger the trigger time or null to remove
+ * @see RFC 5545
+ * p.133-6
+ * @see RFC 2445
+ * p.127-9
+ */
+ public void setTrigger(Trigger trigger) {
+ setProperty(Trigger.class, trigger);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ protected void validate(List components, ICalVersion version, List warnings) {
+ checkRequiredCardinality(warnings, Action.class, Trigger.class);
+
+ Action action = getAction();
+ if (action != null) {
+ //AUDIO alarms should not have more than 1 attachment
+ if (action.isAudio()) {
+ if (getAttachments().size() > 1) {
+ warnings.add(new ValidationWarning(7));
+ }
+ }
+
+ //DESCRIPTION is required for DISPLAY alarms
+ if (action.isDisplay()) {
+ checkRequiredCardinality(warnings, Description.class);
+ }
+
+ if (action.isEmail()) {
+ //SUMMARY and DESCRIPTION is required for EMAIL alarms
+ checkRequiredCardinality(warnings, Summary.class, Description.class);
+
+ //EMAIL alarms must have at least 1 ATTENDEE
+ if (getAttendees().isEmpty()) {
+ warnings.add(new ValidationWarning(8));
+ }
+ } else {
+ //only EMAIL alarms can have ATTENDEEs
+ if (!getAttendees().isEmpty()) {
+ warnings.add(new ValidationWarning(9));
+ }
+ }
+
+ if (action.isProcedure()) {
+ checkRequiredCardinality(warnings, Description.class);
+ }
+ }
+
+ Trigger trigger = getTrigger();
+ if (trigger != null) {
+ Related related = trigger.getRelated();
+ if (related != null) {
+ ICalComponent parent = components.get(components.size() - 1);
+
+ //if the TRIGGER is relative to DTSTART, confirm that DTSTART exists
+ if (related == Related.START && parent.getProperty(DateStart.class) == null) {
+ warnings.add(new ValidationWarning(11));
+ }
+
+ //if the TRIGGER is relative to DTEND, confirm that DTEND (or DUE) exists
+ if (related == Related.END) {
+ boolean noEndDate = false;
+
+ if (parent instanceof VEvent) {
+ noEndDate = (parent.getProperty(DateEnd.class) == null && (parent.getProperty(DateStart.class) == null || parent.getProperty(DurationProperty.class) == null));
+ } else if (parent instanceof VTodo) {
+ noEndDate = (parent.getProperty(DateDue.class) == null && (parent.getProperty(DateStart.class) == null || parent.getProperty(DurationProperty.class) == null));
+ }
+
+ if (noEndDate) {
+ warnings.add(new ValidationWarning(12));
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ public VAlarm copy() {
+ return new VAlarm(this);
+ }
+}
diff --git a/app/src/main/java/biweekly/component/VEvent.java b/app/src/main/java/biweekly/component/VEvent.java
new file mode 100644
index 0000000000..b6c3dcfba4
--- /dev/null
+++ b/app/src/main/java/biweekly/component/VEvent.java
@@ -0,0 +1,1726 @@
+package biweekly.component;
+
+import static biweekly.property.ValuedProperty.getValue;
+
+import java.util.Date;
+import java.util.List;
+import java.util.TimeZone;
+
+import biweekly.ICalVersion;
+import biweekly.ValidationWarning;
+import biweekly.property.Attachment;
+import biweekly.property.Attendee;
+import biweekly.property.Categories;
+import biweekly.property.Classification;
+import biweekly.property.Color;
+import biweekly.property.Comment;
+import biweekly.property.Conference;
+import biweekly.property.Contact;
+import biweekly.property.Created;
+import biweekly.property.DateEnd;
+import biweekly.property.DateStart;
+import biweekly.property.DateTimeStamp;
+import biweekly.property.Description;
+import biweekly.property.DurationProperty;
+import biweekly.property.ExceptionDates;
+import biweekly.property.ExceptionRule;
+import biweekly.property.Geo;
+import biweekly.property.Image;
+import biweekly.property.LastModified;
+import biweekly.property.Location;
+import biweekly.property.Method;
+import biweekly.property.Organizer;
+import biweekly.property.Priority;
+import biweekly.property.RecurrenceDates;
+import biweekly.property.RecurrenceId;
+import biweekly.property.RecurrenceRule;
+import biweekly.property.RelatedTo;
+import biweekly.property.RequestStatus;
+import biweekly.property.Resources;
+import biweekly.property.Sequence;
+import biweekly.property.Status;
+import biweekly.property.Summary;
+import biweekly.property.Transparency;
+import biweekly.property.Uid;
+import biweekly.property.Url;
+import biweekly.util.Duration;
+import biweekly.util.Google2445Utils;
+import biweekly.util.ICalDate;
+import biweekly.util.Period;
+import biweekly.util.Recurrence;
+import biweekly.util.com.google.ical.compat.javautil.DateIterator;
+
+/*
+ Copyright (c) 2013-2023, Michael Angstadt
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+
+ 1. Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+ 2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ *
+ * Defines a scheduled activity, such as a two hour meeting.
+ *
+ *
+ * Examples:
+ *
+ *
+ *
+ * VEvent event = new VEvent();
+ * Date start = ...
+ * event.setDateStart(start);
+ * Date end = ...
+ * event.setDateEnd(end);
+ * event.setSummary("Team Meeting");
+ * event.setLocation("Room 21C");
+ * event.setCreated(new Date());
+ * event.setRecurrenceRule(new Recurrence.Builder(Frequency.WEEKLY).build());
+ *
+ * @author Michael Angstadt
+ * @see RFC 5545 p.52-5
+ * @see RFC 2445 p.52-4
+ * @see vCal 1.0 p.13
+ */
+public class VEvent extends ICalComponent {
+ /**
+ *
+ * Creates a new event.
+ *
+ *
+ * The following properties are added to the component when it is created:
+ *
+ *
+ * - {@link Uid}: Set to a UUID.
+ * - {@link DateTimeStamp}: Set to the current time.
+ *
+ */
+ public VEvent() {
+ setUid(Uid.random());
+ setDateTimeStamp(new Date());
+ }
+
+ /**
+ * Copy constructor.
+ * @param original the component to make a copy of
+ */
+ public VEvent(VEvent original) {
+ super(original);
+ }
+
+ /**
+ * Gets the unique identifier for this event. This component object comes
+ * populated with a UID on creation. This is a required property.
+ * @return the UID or null if not set
+ * @see RFC 5545
+ * p.117-8
+ * @see RFC 2445
+ * p.111-2
+ * @see vCal 1.0 p.37
+ */
+ public Uid getUid() {
+ return getProperty(Uid.class);
+ }
+
+ /**
+ * Sets the unique identifier for this event. This component object comes
+ * populated with a UID on creation. This is a required property.
+ * @param uid the UID or null to remove
+ * @see RFC 5545
+ * p.117-8
+ * @see RFC 2445
+ * p.111-2
+ * @see vCal 1.0 p.37
+ */
+ public void setUid(Uid uid) {
+ setProperty(Uid.class, uid);
+ }
+
+ /**
+ * Sets the unique identifier for this event. This component object comes
+ * populated with a UID on creation. This is a required property.
+ * @param uid the UID or null to remove
+ * @return the property that was created
+ * @see RFC 5545
+ * p.117-8
+ * @see RFC 2445
+ * p.111-2
+ * @see vCal 1.0 p.37
+ */
+ public Uid setUid(String uid) {
+ Uid prop = (uid == null) ? null : new Uid(uid);
+ setUid(prop);
+ return prop;
+ }
+
+ /**
+ * Gets either (a) the creation date of the iCalendar object (if the
+ * {@link Method} property is defined) or (b) the date that the event was
+ * last modified (the {@link LastModified} property also holds this
+ * information). This event object comes populated with a
+ * {@link DateTimeStamp} property that is set to the current time. This is a
+ * required property.
+ * @return the date time stamp or null if not set
+ * @see RFC 5545
+ * p.137-8
+ * @see RFC 2445
+ * p.130-1
+ */
+ public DateTimeStamp getDateTimeStamp() {
+ return getProperty(DateTimeStamp.class);
+ }
+
+ /**
+ * Sets either (a) the creation date of the iCalendar object (if the
+ * {@link Method} property is defined) or (b) the date that the event was
+ * last modified (the {@link LastModified} property also holds this
+ * information). This event object comes populated with a
+ * {@link DateTimeStamp} property that is set to the current time. This is a
+ * required property.
+ * @param dateTimeStamp the date time stamp or null to remove
+ * @see RFC 5545
+ * p.137-8
+ * @see RFC 2445
+ * p.130-1
+ */
+ public void setDateTimeStamp(DateTimeStamp dateTimeStamp) {
+ setProperty(DateTimeStamp.class, dateTimeStamp);
+ }
+
+ /**
+ * Sets either (a) the creation date of the iCalendar object (if the
+ * {@link Method} property is defined) or (b) the date that the event was
+ * last modified (the {@link LastModified} property also holds this
+ * information). This event object comes populated with a
+ * {@link DateTimeStamp} property that is set to the current time. This is a
+ * required property.
+ * @param dateTimeStamp the date time stamp or null to remove
+ * @return the property that was created
+ * @see RFC 5545
+ * p.137-8
+ * @see RFC 2445
+ * p.130-1
+ */
+ public DateTimeStamp setDateTimeStamp(Date dateTimeStamp) {
+ DateTimeStamp prop = (dateTimeStamp == null) ? null : new DateTimeStamp(dateTimeStamp);
+ setDateTimeStamp(prop);
+ return prop;
+ }
+
+ /**
+ * Gets the date that the event starts.
+ * @return the start date or null if not set
+ * @see RFC 5545
+ * p.97-8
+ * @see RFC 2445
+ * p.93-4
+ * @see vCal 1.0 p.35
+ */
+ public DateStart getDateStart() {
+ return getProperty(DateStart.class);
+ }
+
+ /**
+ * Sets the date that the event starts (required if no {@link Method}
+ * property is defined).
+ * @param dateStart the start date or null to remove
+ * @see RFC 5545
+ * p.97-8
+ * @see RFC 2445
+ * p.93-4
+ * @see vCal 1.0 p.35
+ */
+ public void setDateStart(DateStart dateStart) {
+ setProperty(DateStart.class, dateStart);
+ }
+
+ /**
+ * Sets the date that the event starts (required if no {@link Method}
+ * property is defined).
+ * @param dateStart the start date or null to remove
+ * @return the property that was created
+ * @see RFC 5545
+ * p.97-8
+ * @see RFC 2445
+ * p.93-4
+ * @see vCal 1.0 p.35
+ */
+ public DateStart setDateStart(Date dateStart) {
+ return setDateStart(dateStart, true);
+ }
+
+ /**
+ * Sets the date that the event starts (required if no {@link Method}
+ * property is defined).
+ * @param dateStart the start date or null to remove
+ * @param hasTime true if the date has a time component, false if it is
+ * strictly a date (if false, the given Date object should be created by a
+ * {@link java.util.Calendar Calendar} object that uses the JVM's default
+ * timezone)
+ * @return the property that was created
+ * @see RFC 5545
+ * p.97-8
+ * @see RFC 2445
+ * p.93-4
+ * @see vCal 1.0 p.35
+ */
+ public DateStart setDateStart(Date dateStart, boolean hasTime) {
+ DateStart prop = (dateStart == null) ? null : new DateStart(dateStart, hasTime);
+ setDateStart(prop);
+ return prop;
+ }
+
+ /**
+ * Gets the level of sensitivity of the event data. If not specified, the
+ * data within the event should be considered "public".
+ * @return the classification level or null if not set
+ * @see RFC 5545
+ * p.82-3
+ * @see RFC 2445
+ * p.79-80
+ * @see vCal 1.0 p.28-9
+ */
+ public Classification getClassification() {
+ return getProperty(Classification.class);
+ }
+
+ /**
+ * Sets the level of sensitivity of the event data. If not specified, the
+ * data within the event should be considered "public".
+ * @param classification the classification level or null to remove
+ * @see RFC 5545
+ * p.82-3
+ * @see RFC 2445
+ * p.79-80
+ * @see vCal 1.0 p.28-9
+ */
+ public void setClassification(Classification classification) {
+ setProperty(Classification.class, classification);
+ }
+
+ /**
+ * Sets the level of sensitivity of the event data. If not specified, the
+ * data within the event should be considered "public".
+ * @param classification the classification level (e.g. "CONFIDENTIAL") or
+ * null to remove
+ * @return the property that was created
+ * @see RFC 5545
+ * p.82-3
+ * @see RFC 2445
+ * p.79-80
+ * @see vCal 1.0 p.28-9
+ */
+ public Classification setClassification(String classification) {
+ Classification prop = (classification == null) ? null : new Classification(classification);
+ setClassification(prop);
+ return prop;
+ }
+
+ /**
+ * Gets a detailed description of the event. The description should be more
+ * detailed than the one provided by the {@link Summary} property.
+ * @return the description or null if not set
+ * @see RFC 5545
+ * p.84-5
+ * @see RFC 2445
+ * p.81-2
+ * @see vCal 1.0 p.30
+ */
+ public Description getDescription() {
+ return getProperty(Description.class);
+ }
+
+ /**
+ * Sets a detailed description of the event. The description should be more
+ * detailed than the one provided by the {@link Summary} property.
+ * @param description the description or null to remove
+ * @see RFC 5545
+ * p.84-5
+ * @see RFC 2445
+ * p.81-2
+ * @see vCal 1.0 p.30
+ */
+ public void setDescription(Description description) {
+ setProperty(Description.class, description);
+ }
+
+ /**
+ * Sets a detailed description of the event. The description should be more
+ * detailed than the one provided by the {@link Summary} property.
+ * @param description the description or null to remove
+ * @return the property that was created
+ * @see RFC 5545
+ * p.84-5
+ * @see RFC 2445
+ * p.81-2
+ * @see vCal 1.0 p.30
+ */
+ public Description setDescription(String description) {
+ Description prop = (description == null) ? null : new Description(description);
+ setDescription(prop);
+ return prop;
+ }
+
+ /**
+ * Gets a set of geographical coordinates.
+ * @return the geographical coordinates or null if not set
+ * @see RFC 5545
+ * p.85-7
+ * @see RFC 2445
+ * p.82-3
+ */
+ //Note: vCal 1.0 spec is omitted because GEO is not used in vCal 1.0 events
+ public Geo getGeo() {
+ return getProperty(Geo.class);
+ }
+
+ /**
+ * Sets a set of geographical coordinates.
+ * @param geo the geographical coordinates or null to remove
+ * @see RFC 5545
+ * p.85-7
+ * @see RFC 2445
+ * p.82-3
+ */
+ //Note: vCal 1.0 spec is omitted because GEO is not used in vCal 1.0 events
+ public void setGeo(Geo geo) {
+ setProperty(Geo.class, geo);
+ }
+
+ /**
+ * Gets the physical location of the event.
+ * @return the location or null if not set
+ * @see RFC 5545
+ * p.87-8
+ * @see RFC 2445
+ * p.84
+ * @see vCal 1.0 p.32
+ */
+ public Location getLocation() {
+ return getProperty(Location.class);
+ }
+
+ /**
+ * Sets the physical location of the event.
+ * @param location the location or null to remove
+ * @see RFC 5545
+ * p.87-8
+ * @see RFC 2445
+ * p.84
+ * @see vCal 1.0 p.32
+ */
+ public void setLocation(Location location) {
+ setProperty(Location.class, location);
+ }
+
+ /**
+ * Sets the physical location of the event.
+ * @param location the location (e.g. "Room 101") or null to remove
+ * @return the property that was created
+ * @see RFC 5545
+ * p.87-8
+ * @see RFC 2445
+ * p.84
+ * @see vCal 1.0 p.32
+ */
+ public Location setLocation(String location) {
+ Location prop = (location == null) ? null : new Location(location);
+ setLocation(prop);
+ return prop;
+ }
+
+ /**
+ * Gets the priority of the event.
+ * @return the priority or null if not set
+ * @see RFC 5545
+ * p.89-90
+ * @see RFC 2445
+ * p.85-7
+ * @see vCal 1.0 p.33
+ */
+ public Priority getPriority() {
+ return getProperty(Priority.class);
+ }
+
+ /**
+ * Sets the priority of the event.
+ * @param priority the priority or null to remove
+ * @see RFC 5545
+ * p.89-90
+ * @see RFC 2445
+ * p.85-7
+ * @see vCal 1.0 p.33
+ */
+ public void setPriority(Priority priority) {
+ setProperty(Priority.class, priority);
+ }
+
+ /**
+ * Sets the priority of the event.
+ * @param priority the priority ("0" is undefined, "1" is the highest, "9"
+ * is the lowest) or null to remove
+ * @return the property that was created
+ * @see RFC 5545
+ * p.89-90
+ * @see RFC 2445
+ * p.85-7
+ * @see vCal 1.0 p.33
+ */
+ public Priority setPriority(Integer priority) {
+ Priority prop = (priority == null) ? null : new Priority(priority);
+ setPriority(prop);
+ return prop;
+ }
+
+ /**
+ * Gets the status of the event.
+ * @return the status or null if not set
+ * @see RFC 5545
+ * p.92-3
+ * @see RFC 2445
+ * p.88-9
+ * @see vCal 1.0 p.35-6
+ */
+ public Status getStatus() {
+ return getProperty(Status.class);
+ }
+
+ /**
+ *
+ * Sets the status of the event.
+ *
+ *
+ * Valid event status codes are:
+ *
+ *
+ * - TENTATIVE
+ * - CONFIRMED
+ * - CANCELLED
+ *
+ * @param status the status or null to remove
+ * @see RFC 5545
+ * p.92-3
+ * @see RFC 2445
+ * p.88-9
+ * @see vCal 1.0 p.35-6
+ */
+ public void setStatus(Status status) {
+ setProperty(Status.class, status);
+ }
+
+ /**
+ * Gets the summary of the event.
+ * @return the summary or null if not set
+ * @see RFC 5545
+ * p.93-4
+ * @see RFC 2445
+ * p.89-90
+ * @see vCal 1.0 p.36
+ */
+ public Summary getSummary() {
+ return getProperty(Summary.class);
+ }
+
+ /**
+ * Sets the summary of the event.
+ * @param summary the summary or null to remove
+ * @see RFC 5545
+ * p.93-4
+ * @see RFC 2445
+ * p.89-90
+ * @see vCal 1.0 p.36
+ */
+ public void setSummary(Summary summary) {
+ setProperty(Summary.class, summary);
+ }
+
+ /**
+ * Sets the summary of the event.
+ * @param summary the summary or null to remove
+ * @return the property that was created
+ * @see RFC 5545
+ * p.93-4
+ * @see RFC 2445
+ * p.89-90
+ * @see vCal 1.0 p.36
+ */
+ public Summary setSummary(String summary) {
+ Summary prop = (summary == null) ? null : new Summary(summary);
+ setSummary(prop);
+ return prop;
+ }
+
+ /**
+ * Gets whether an event is visible to free/busy time searches. If the event
+ * does not have this property, it should be considered visible ("opaque").
+ * @return the transparency or null if not set
+ * @see RFC 5545
+ * p.101-2
+ * @see RFC 2445
+ * p.96-7
+ * @see vCal 1.0 p.36-7
+ */
+ public Transparency getTransparency() {
+ return getProperty(Transparency.class);
+ }
+
+ /**
+ * Sets whether an event is visible to free/busy time searches.
+ * @param transparency the transparency or null to remove
+ * @see RFC 5545
+ * p.101-2
+ * @see RFC 2445
+ * p.96-7
+ * @see vCal 1.0 p.36-7
+ */
+ public void setTransparency(Transparency transparency) {
+ setProperty(Transparency.class, transparency);
+ }
+
+ /**
+ * Sets whether an event is visible to free/busy time searches.
+ * @param transparent true to hide the event, false to make it visible it,
+ * or null to remove the property
+ * @return the property that was created
+ * @see RFC 5545
+ * p.101-2
+ * @see RFC 2445
+ * p.96-7
+ * @see vCal 1.0 p.36-7
+ */
+ public Transparency setTransparency(Boolean transparent) {
+ Transparency prop = null;
+ if (transparent != null) {
+ prop = transparent ? Transparency.transparent() : Transparency.opaque();
+ }
+ setTransparency(prop);
+ return prop;
+ }
+
+ /**
+ * Gets the organizer of the event.
+ * @return the organizer or null if not set
+ * @see RFC 5545
+ * p.111-2
+ * @see RFC 2445
+ * p.106-7
+ */
+ public Organizer getOrganizer() {
+ return getProperty(Organizer.class);
+ }
+
+ /**
+ * Sets the organizer of the event.
+ * @param organizer the organizer or null to remove
+ * @see RFC 5545
+ * p.111-2
+ * @see RFC 2445
+ * p.106-7
+ */
+ public void setOrganizer(Organizer organizer) {
+ setProperty(Organizer.class, organizer);
+ }
+
+ /**
+ * Sets the organizer of the event.
+ * @param email the organizer's email address (e.g. "johndoe@example.com")
+ * or null to remove
+ * @return the property that was created
+ * @see RFC 5545
+ * p.111-2
+ * @see RFC 2445
+ * p.106-7
+ */
+ public Organizer setOrganizer(String email) {
+ Organizer prop = (email == null) ? null : new Organizer(null, email);
+ setOrganizer(prop);
+ return prop;
+ }
+
+ /**
+ * Gets the original value of the {@link DateStart} property if the event is
+ * recurring and has been modified. Used in conjunction with the {@link Uid}
+ * and {@link Sequence} properties to uniquely identify a recurrence
+ * instance.
+ * @return the recurrence ID or null if not set
+ * @see RFC 5545
+ * p.112-4
+ * @see RFC 2445
+ * p.107-9
+ */
+ public RecurrenceId getRecurrenceId() {
+ return getProperty(RecurrenceId.class);
+ }
+
+ /**
+ * Sets the original value of the {@link DateStart} property if the event is
+ * recurring and has been modified. Used in conjunction with the {@link Uid}
+ * and {@link Sequence} properties to uniquely identify a recurrence
+ * instance.
+ * @param recurrenceId the recurrence ID or null to remove
+ * @see RFC 5545
+ * p.112-4
+ * @see RFC 2445
+ * p.107-9
+ */
+ public void setRecurrenceId(RecurrenceId recurrenceId) {
+ setProperty(RecurrenceId.class, recurrenceId);
+ }
+
+ /**
+ * Sets the original value of the {@link DateStart} property if the event is
+ * recurring and has been modified. Used in conjunction with the {@link Uid}
+ * and {@link Sequence} properties to uniquely identify a recurrence
+ * instance.
+ * @param originalStartDate the original start date or null to remove
+ * @return the property that was created
+ * @see RFC 5545
+ * p.112-4
+ * @see RFC 2445
+ * p.107-9
+ */
+ public RecurrenceId setRecurrenceId(Date originalStartDate) {
+ RecurrenceId prop = (originalStartDate == null) ? null : new RecurrenceId(originalStartDate);
+ setRecurrenceId(prop);
+ return prop;
+ }
+
+ /**
+ * Gets a URL to a resource that contains additional information about the
+ * event.
+ * @return the URL or null if not set
+ * @see RFC 5545
+ * p.116-7
+ * @see RFC 2445
+ * p.110-1
+ * @see vCal 1.0 p.37
+ */
+ public Url getUrl() {
+ return getProperty(Url.class);
+ }
+
+ /**
+ * Sets a URL to a resource that contains additional information about the
+ * event.
+ * @param url the URL or null to remove
+ * @see RFC 5545
+ * p.116-7
+ * @see RFC 2445
+ * p.110-1
+ * @see vCal 1.0 p.37
+ */
+ public void setUrl(Url url) {
+ setProperty(Url.class, url);
+ }
+
+ /**
+ * Sets a URL to a resource that contains additional information about the
+ * event.
+ * @param url the URL (e.g. "http://example.com/resource.ics") or null to
+ * remove
+ * @return the property that was created
+ * @see RFC 5545
+ * p.116-7
+ * @see RFC 2445
+ * p.110-1
+ * @see vCal 1.0 p.37
+ */
+ public Url setUrl(String url) {
+ Url prop = (url == null) ? null : new Url(url);
+ setUrl(prop);
+ return prop;
+ }
+
+ /**
+ * Gets how often the event repeats.
+ * @return the recurrence rule or null if not set
+ * @see RFC 5545
+ * p.122-32
+ * @see RFC 2445
+ * p.117-25
+ * @see vCal 1.0 p.34
+ */
+ public RecurrenceRule getRecurrenceRule() {
+ return getProperty(RecurrenceRule.class);
+ }
+
+ /**
+ * Sets how often the event repeats.
+ * @param recur the recurrence rule or null to remove
+ * @return the property that was created
+ * @see RFC 5545
+ * p.122-32
+ * @see RFC 2445
+ * p.117-25
+ * @see vCal 1.0 p.34
+ */
+ public RecurrenceRule setRecurrenceRule(Recurrence recur) {
+ RecurrenceRule prop = (recur == null) ? null : new RecurrenceRule(recur);
+ setRecurrenceRule(prop);
+ return prop;
+ }
+
+ /**
+ * Sets how often the event repeats.
+ * @param recurrenceRule the recurrence rule or null to remove
+ * @see RFC 5545
+ * p.122-32
+ * @see RFC 2445
+ * p.117-25
+ * @see vCal 1.0 p.34
+ */
+ public void setRecurrenceRule(RecurrenceRule recurrenceRule) {
+ setProperty(RecurrenceRule.class, recurrenceRule);
+ }
+
+ /**
+ * Gets the date that the event ends.
+ * @return the end date or null if not set
+ * @see RFC 5545
+ * p.95-6
+ * @see RFC 2445
+ * p.91-2
+ * @see vCal 1.0 p.31
+ */
+ public DateEnd getDateEnd() {
+ return getProperty(DateEnd.class);
+ }
+
+ /**
+ * Sets the date that the event ends. This must NOT be set if a
+ * {@link DurationProperty} is defined.
+ * @param dateEnd the end date or null to remove
+ * @see RFC 5545
+ * p.95-6
+ * @see RFC 2445
+ * p.91-2
+ * @see vCal 1.0 p.31
+ */
+ public void setDateEnd(DateEnd dateEnd) {
+ setProperty(DateEnd.class, dateEnd);
+ }
+
+ /**
+ * Sets the date that the event ends. This must NOT be set if a
+ * {@link DurationProperty} is defined.
+ * @param dateEnd the end date or null to remove
+ * @return the property that was created
+ * @see RFC 5545
+ * p.95-6
+ * @see RFC 2445
+ * p.91-2
+ * @see vCal 1.0 p.31
+ */
+ public DateEnd setDateEnd(Date dateEnd) {
+ return setDateEnd(dateEnd, true);
+ }
+
+ /**
+ * Sets the date that the event ends. This must NOT be set if a
+ * {@link DurationProperty} is defined.
+ * @param dateEnd the end date or null to remove
+ * @param hasTime true if the date has a time component, false if it is
+ * strictly a date (if false, the given Date object should be created by a
+ * {@link java.util.Calendar Calendar} object that uses the JVM's default
+ * timezone)
+ * @return the property that was created
+ * @see RFC 5545
+ * p.95-6
+ * @see RFC 2445
+ * p.91-2
+ * @see vCal 1.0 p.31
+ */
+ public DateEnd setDateEnd(Date dateEnd, boolean hasTime) {
+ DateEnd prop = (dateEnd == null) ? null : new DateEnd(dateEnd, hasTime);
+ setDateEnd(prop);
+ return prop;
+ }
+
+ /**
+ * Gets the duration of the event.
+ * @return the duration or null if not set
+ * @see RFC 5545
+ * p.99
+ * @see RFC 2445
+ * p.94-5
+ */
+ public DurationProperty getDuration() {
+ return getProperty(DurationProperty.class);
+ }
+
+ /**
+ * Sets the duration of the event. This must NOT be set if a {@link DateEnd}
+ * is defined.
+ * @param duration the duration or null to remove
+ * @see RFC 5545
+ * p.99
+ * @see RFC 2445
+ * p.94-5
+ */
+ public void setDuration(DurationProperty duration) {
+ setProperty(DurationProperty.class, duration);
+ }
+
+ /**
+ * Sets the duration of the event. This must NOT be set if a {@link DateEnd}
+ * is defined.
+ * @param duration the duration or null to remove
+ * @return the property that was created
+ * @see RFC 5545
+ * p.99
+ * @see RFC 2445
+ * p.94-5
+ */
+ public DurationProperty setDuration(Duration duration) {
+ DurationProperty prop = (duration == null) ? null : new DurationProperty(duration);
+ setDuration(prop);
+ return prop;
+ }
+
+ /**
+ * Gets the date-time that the event was initially created.
+ * @return the creation date-time or null if not set
+ * @see RFC 5545
+ * p.136
+ * @see RFC 2445
+ * p.129-30
+ * @see vCal 1.0 p.29
+ */
+ public Created getCreated() {
+ return getProperty(Created.class);
+ }
+
+ /**
+ * Sets the date-time that the event was initially created.
+ * @param created the creation date-time or null to remove
+ * @see RFC 5545
+ * p.136
+ * @see RFC 2445
+ * p.129-30
+ * @see vCal 1.0 p.29
+ */
+ public void setCreated(Created created) {
+ setProperty(Created.class, created);
+ }
+
+ /**
+ * Sets the date-time that the event was initially created.
+ * @param created the creation date-time or null to remove
+ * @return the property that was created
+ * @see RFC 5545
+ * p.136
+ * @see RFC 2445
+ * p.129-30
+ * @see vCal 1.0 p.29
+ */
+ public Created setCreated(Date created) {
+ Created prop = (created == null) ? null : new Created(created);
+ setCreated(prop);
+ return prop;
+ }
+
+ /**
+ * Gets the date-time that the event was last changed.
+ * @return the last modified date or null if not set
+ * @see RFC 5545
+ * p.138
+ * @see RFC 2445
+ * p.131
+ * @see vCal 1.0 p.31
+ */
+ public LastModified getLastModified() {
+ return getProperty(LastModified.class);
+ }
+
+ /**
+ * Sets the date-time that event was last changed.
+ * @param lastModified the last modified date or null to remove
+ * @see RFC 5545
+ * p.138
+ * @see RFC 2445
+ * p.131
+ * @see vCal 1.0 p.31
+ */
+ public void setLastModified(LastModified lastModified) {
+ setProperty(LastModified.class, lastModified);
+ }
+
+ /**
+ * Sets the date-time that the event was last changed.
+ * @param lastModified the last modified date or null to remove
+ * @return the property that was created
+ * @see RFC 5545
+ * p.138
+ * @see RFC 2445
+ * p.131
+ * @see vCal 1.0 p.31
+ */
+ public LastModified setLastModified(Date lastModified) {
+ LastModified prop = (lastModified == null) ? null : new LastModified(lastModified);
+ setLastModified(prop);
+ return prop;
+ }
+
+ /**
+ * Gets the revision number of the event. The organizer can increment this
+ * number every time he or she makes a significant change.
+ * @return the sequence number
+ * @see RFC 5545
+ * p.138-9
+ * @see RFC 2445
+ * p.131-3
+ * @see vCal 1.0 p.35
+ */
+ public Sequence getSequence() {
+ return getProperty(Sequence.class);
+ }
+
+ /**
+ * Sets the revision number of the event. The organizer can increment this
+ * number every time he or she makes a significant change.
+ * @param sequence the sequence number
+ * @see RFC 5545
+ * p.138-9
+ * @see RFC 2445
+ * p.131-3
+ * @see vCal 1.0 p.35
+ */
+ public void setSequence(Sequence sequence) {
+ setProperty(Sequence.class, sequence);
+ }
+
+ /**
+ * Sets the revision number of the event. The organizer can increment this
+ * number every time he or she makes a significant change.
+ * @param sequence the sequence number
+ * @return the property that was created
+ * @see RFC 5545
+ * p.138-9
+ * @see RFC 2445
+ * p.131-3
+ * @see vCal 1.0 p.35
+ */
+ public Sequence setSequence(Integer sequence) {
+ Sequence prop = (sequence == null) ? null : new Sequence(sequence);
+ setSequence(prop);
+ return prop;
+ }
+
+ /**
+ * Increments the revision number of the event. The organizer can increment
+ * this number every time he or she makes a significant change.
+ * @see RFC 5545
+ * p.138-9
+ * @see RFC 2445
+ * p.131-3
+ * @see vCal 1.0 p.35
+ */
+ public void incrementSequence() {
+ Sequence sequence = getSequence();
+ if (sequence == null) {
+ setSequence(1);
+ } else {
+ sequence.increment();
+ }
+ }
+
+ /**
+ * Gets any attachments that are associated with the event.
+ * @return the attachments (any changes made this list will affect the
+ * parent component object and vice versa)
+ * @see RFC 5545
+ * p.80-1
+ * @see RFC 2445
+ * p.77-8
+ * @see vCal 1.0 p.25
+ */
+ public List getAttachments() {
+ return getProperties(Attachment.class);
+ }
+
+ /**
+ * Adds an attachment to the event.
+ * @param attachment the attachment to add
+ * @see RFC 5545
+ * p.80-1
+ * @see RFC 2445
+ * p.77-8
+ * @see vCal 1.0 p.25
+ */
+ public void addAttachment(Attachment attachment) {
+ addProperty(attachment);
+ }
+
+ /**
+ * Gets the people who are attending the event.
+ * @return the attendees (any changes made this list will affect the parent
+ * component object and vice versa)
+ * @see RFC 5545
+ * p.107-9
+ * @see RFC 2445
+ * p.102-4
+ * @see vCal 1.0 p.25-7
+ */
+ public List getAttendees() {
+ return getProperties(Attendee.class);
+ }
+
+ /**
+ * Adds a person who is attending the event.
+ * @param attendee the attendee
+ * @see RFC 5545
+ * p.107-9
+ * @see RFC 2445
+ * p.102-4
+ * @see vCal 1.0 p.25-7
+ */
+ public void addAttendee(Attendee attendee) {
+ addProperty(attendee);
+ }
+
+ /**
+ * Adds a person who is attending the event.
+ * @param email the attendee's email address
+ * @return the property that was created
+ * @see RFC 5545
+ * p.107-9
+ * @see RFC 2445
+ * p.102-4
+ * @see vCal 1.0 p.25-7
+ */
+ public Attendee addAttendee(String email) {
+ Attendee prop = new Attendee(null, email);
+ addAttendee(prop);
+ return prop;
+ }
+
+ /**
+ * Gets a list of "tags" or "keywords" that describe the event.
+ * @return the categories (any changes made this list will affect the parent
+ * component object and vice versa)
+ * @see RFC 5545
+ * p.81-2
+ * @see RFC 2445
+ * p.78-9
+ * @see vCal 1.0 p.28
+ */
+ public List getCategories() {
+ return getProperties(Categories.class);
+ }
+
+ /**
+ * Adds a list of "tags" or "keywords" that describe the event. Note that a
+ * single property can hold multiple keywords.
+ * @param categories the categories to add
+ * @see RFC 5545
+ * p.81-2
+ * @see RFC 2445
+ * p.78-9
+ * @see vCal 1.0 p.28
+ */
+ public void addCategories(Categories categories) {
+ addProperty(categories);
+ }
+
+ /**
+ * Adds a list of "tags" or "keywords" that describe the event.
+ * @param categories the categories to add
+ * @return the property that was created
+ * @see RFC 5545
+ * p.81-2
+ * @see RFC 2445
+ * p.78-9
+ * @see vCal 1.0 p.28
+ */
+ public Categories addCategories(String... categories) {
+ Categories prop = new Categories(categories);
+ addCategories(prop);
+ return prop;
+ }
+
+ /**
+ * Adds a list of "tags" or "keywords" that describe the event.
+ * @param categories the categories to add
+ * @return the property that was created
+ * @see RFC 5545
+ * p.81-2
+ * @see RFC 2445
+ * p.78-9
+ * @see vCal 1.0 p.28
+ */
+ public Categories addCategories(List categories) {
+ Categories prop = new Categories(categories);
+ addCategories(prop);
+ return prop;
+ }
+
+ /**
+ * Gets the comments attached to the event.
+ * @return the comments (any changes made this list will affect the parent
+ * component object and vice versa)
+ * @see RFC 5545
+ * p.83-4
+ * @see RFC 2445
+ * p.80-1
+ */
+ public List getComments() {
+ return getProperties(Comment.class);
+ }
+
+ /**
+ * Adds a comment to the event.
+ * @param comment the comment to add
+ * @see RFC 5545
+ * p.83-4
+ * @see RFC 2445
+ * p.80-1
+ */
+ public void addComment(Comment comment) {
+ addProperty(comment);
+ }
+
+ /**
+ * Adds a comment to the event.
+ * @param comment the comment to add
+ * @return the property that was created
+ * @see RFC 5545
+ * p.83-4
+ * @see RFC 2445
+ * p.80-1
+ */
+ public Comment addComment(String comment) {
+ Comment prop = new Comment(comment);
+ addComment(prop);
+ return prop;
+ }
+
+ /**
+ * Gets the contacts associated with the event.
+ * @return the contacts (any changes made this list will affect the parent
+ * component object and vice versa)
+ * @see RFC 5545
+ * p.109-11
+ * @see RFC 2445
+ * p.104-6
+ */
+ public List getContacts() {
+ return getProperties(Contact.class);
+ }
+
+ /**
+ * Adds a contact to the event.
+ * @param contact the contact
+ * @see RFC 5545
+ * p.109-11
+ * @see RFC 2445
+ * p.104-6
+ */
+ public void addContact(Contact contact) {
+ addProperty(contact);
+ }
+
+ /**
+ * Adds a contact to the event.
+ * @param contact the contact (e.g. "ACME Co - (123) 555-1234")
+ * @return the property that was created
+ * @see RFC 5545
+ * p.109-11
+ * @see RFC 2445
+ * p.104-6
+ */
+ public Contact addContact(String contact) {
+ Contact prop = new Contact(contact);
+ addContact(prop);
+ return prop;
+ }
+
+ /**
+ * Gets the list of exceptions to the recurrence rule defined in the event
+ * (if one is defined).
+ * @return the list of exceptions (any changes made this list will affect
+ * the parent component object and vice versa)
+ * @see RFC 5545
+ * p.118-20
+ * @see RFC 2445
+ * p.112-4
+ * @see vCal 1.0 p.31
+ */
+ public List getExceptionDates() {
+ return getProperties(ExceptionDates.class);
+ }
+
+ /**
+ * Adds a list of exceptions to the recurrence rule defined in the event (if
+ * one is defined). Note that this property can contain multiple dates.
+ * @param exceptionDates the list of exceptions
+ * @see RFC 5545
+ * p.118-20
+ * @see RFC 2445
+ * p.112-4
+ * @see vCal 1.0 p.31
+ */
+ public void addExceptionDates(ExceptionDates exceptionDates) {
+ addProperty(exceptionDates);
+ }
+
+ /**
+ * Gets the response to a scheduling request.
+ * @return the response
+ * @see RFC 5546
+ * Section 3.6
+ * @see RFC 5545
+ * p.141-3
+ * @see RFC 2445
+ * p.134-6
+ */
+ public RequestStatus getRequestStatus() {
+ return getProperty(RequestStatus.class);
+ }
+
+ /**
+ * Sets the response to a scheduling request.
+ * @param requestStatus the response
+ * @see RFC 5546
+ * Section 3.6
+ * @see RFC 5545
+ * p.141-3
+ * @see RFC 2445
+ * p.134-6
+ */
+ public void setRequestStatus(RequestStatus requestStatus) {
+ setProperty(RequestStatus.class, requestStatus);
+ }
+
+ /**
+ * Gets the components that the event is related to.
+ * @return the relationships (any changes made this list will affect the
+ * parent component object and vice versa)
+ * @see RFC 5545
+ * p.115-6
+ * @see RFC 2445
+ * p.109-10
+ * @see vCal 1.0 p.33-4
+ */
+ public List getRelatedTo() {
+ return getProperties(RelatedTo.class);
+ }
+
+ /**
+ * Adds a component that the event is related to.
+ * @param relatedTo the relationship
+ * @see RFC 5545
+ * p.115-6
+ * @see RFC 2445
+ * p.109-10
+ * @see vCal 1.0 p.33-4
+ */
+ public void addRelatedTo(RelatedTo relatedTo) {
+ //TODO create a method that accepts a component and make the RelatedTo property invisible to the user
+ //@formatter:off
+ /*
+ * addRelation(RelationshipType relType, ICalComponent component) {
+ * RelatedTo prop = new RelatedTo(component.getUid().getValue());
+ * prop.setRelationshipType(relType);
+ * addProperty(prop);
+ * }
+ */
+ //@formatter:on
+ addProperty(relatedTo);
+ }
+
+ /**
+ * Adds a component that the event is related to.
+ * @param uid the UID of the other component
+ * @return the property that was created
+ * @see RFC 5545
+ * p.115-6
+ * @see RFC 2445
+ * p.109-10
+ * @see vCal 1.0 p.33-4
+ */
+ public RelatedTo addRelatedTo(String uid) {
+ RelatedTo prop = new RelatedTo(uid);
+ addRelatedTo(prop);
+ return prop;
+ }
+
+ /**
+ * Gets the resources that are needed for the event.
+ * @return the resources (any changes made this list will affect the parent
+ * component object and vice versa)
+ * @see RFC 5545
+ * p.91
+ * @see RFC 2445
+ * p.87-8
+ * @see vCal 1.0 p.34-5
+ */
+ public List getResources() {
+ return getProperties(Resources.class);
+ }
+
+ /**
+ * Adds a list of resources that are needed for the event. Note that a
+ * single property can hold multiple resources.
+ * @param resources the resources to add
+ * @see RFC 5545
+ * p.91
+ * @see RFC 2445
+ * p.87-8
+ * @see vCal 1.0 p.34-5
+ */
+ public void addResources(Resources resources) {
+ addProperty(resources);
+ }
+
+ /**
+ * Adds a list of resources that are needed for the event.
+ * @param resources the resources to add (e.g. "easel", "projector")
+ * @return the property that was created
+ * @see RFC 5545
+ * p.91
+ * @see RFC 2445
+ * p.87-8
+ * @see vCal 1.0 p.34-5
+ */
+ public Resources addResources(String... resources) {
+ Resources prop = new Resources(resources);
+ addResources(prop);
+ return prop;
+ }
+
+ /**
+ * Adds a list of resources that are needed for the event.
+ * @param resources the resources to add (e.g. "easel", "projector")
+ * @return the property that was created
+ * @see RFC 5545
+ * p.91
+ * @see RFC 2445
+ * p.87-8
+ * @see vCal 1.0 p.34-5
+ */
+ public Resources addResources(List resources) {
+ Resources prop = new Resources(resources);
+ addResources(prop);
+ return prop;
+ }
+
+ /**
+ * Gets the list of dates/periods that help define the recurrence rule of
+ * this event (if one is defined).
+ * @return the recurrence dates (any changes made this list will affect the
+ * parent component object and vice versa)
+ * @see RFC 5545
+ * p.120-2
+ * @see RFC 2445
+ * p.115-7
+ * @see vCal 1.0 p.34
+ */
+ public List getRecurrenceDates() {
+ return getProperties(RecurrenceDates.class);
+ }
+
+ /**
+ * Adds a list of dates/periods that help define the recurrence rule of this
+ * event (if one is defined).
+ * @param recurrenceDates the recurrence dates
+ * @see RFC 5545
+ * p.120-2
+ * @see RFC 2445
+ * p.115-7
+ * @see vCal 1.0 p.34
+ */
+ public void addRecurrenceDates(RecurrenceDates recurrenceDates) {
+ addProperty(recurrenceDates);
+ }
+
+ /**
+ * Gets the alarms that are assigned to this event.
+ * @return the alarms (any changes made this list will affect the parent
+ * component object and vice versa)
+ * @see RFC 5545
+ * p.71-6
+ * @see RFC 2445
+ * p.67-73
+ */
+ public List getAlarms() {
+ return getComponents(VAlarm.class);
+ }
+
+ /**
+ * Adds an alarm to this event.
+ * @param alarm the alarm
+ * @see RFC 5545
+ * p.71-6
+ * @see RFC 2445
+ * p.67-73
+ */
+ public void addAlarm(VAlarm alarm) {
+ addComponent(alarm);
+ }
+
+ /**
+ *
+ * Gets the exceptions for the {@link RecurrenceRule} property.
+ *
+ *
+ * Note that this property has been removed from the latest version of the
+ * iCal specification. Its use should be avoided.
+ *
+ * @return the exception rules (any changes made this list will affect the
+ * parent component object and vice versa)
+ * @see RFC 2445
+ * p.114-15
+ * @see vCal 1.0 p.31
+ */
+ public List getExceptionRules() {
+ return getProperties(ExceptionRule.class);
+ }
+
+ /**
+ *
+ * Adds an exception for the {@link RecurrenceRule} property.
+ *
+ *
+ * Note that this property has been removed from the latest version of the
+ * iCal specification. Its use should be avoided.
+ *
+ * @param recur the exception rule to add
+ * @return the property that was created
+ * @see RFC 2445
+ * p.114-15
+ * @see vCal 1.0 p.31
+ */
+ public ExceptionRule addExceptionRule(Recurrence recur) {
+ ExceptionRule prop = new ExceptionRule(recur);
+ addExceptionRule(prop);
+ return prop;
+ }
+
+ /**
+ *
+ * Adds an exception for the {@link RecurrenceRule} property.
+ *
+ *
+ * Note that this property has been removed from the latest version of the
+ * iCal specification. Its use should be avoided.
+ *
+ * @param exceptionRule the exception rule to add
+ * @see RFC 2445
+ * p.114-15
+ * @see vCal 1.0 p.31
+ */
+ public void addExceptionRule(ExceptionRule exceptionRule) {
+ addProperty(exceptionRule);
+ }
+
+ /**
+ * Gets the color that clients may use when displaying the event (for
+ * example, a background color).
+ * @return the property or null if not set
+ * @see draft-ietf-calext-extensions-01
+ * p.9
+ */
+ public Color getColor() {
+ return getProperty(Color.class);
+ }
+
+ /**
+ * Sets the color that clients may use when displaying the event (for
+ * example, a background color).
+ * @param color the property or null to remove
+ * @see draft-ietf-calext-extensions-01
+ * p.79
+ */
+ public void setColor(Color color) {
+ setProperty(Color.class, color);
+ }
+
+ /**
+ * Sets the color that clients may use when displaying the event (for
+ * example, a background color).
+ * @param color the color name (case insensitive) or null to remove.
+ * Acceptable values are defined in Section 4.3 of the CSS Color Module Level 3 Recommendation. For
+ * example, "aliceblue", "green", "navy".
+ * @return the property object that was created
+ * @see draft-ietf-calext-extensions-01
+ * p.9
+ */
+ public Color setColor(String color) {
+ Color prop = (color == null) ? null : new Color(color);
+ setColor(prop);
+ return prop;
+ }
+
+ /**
+ * Gets the images that are associated with the event.
+ * @return the images (any changes made this list will affect the parent
+ * component object and vice versa)
+ * @see draft-ietf-calext-extensions-01
+ * p.10
+ */
+ public List getImages() {
+ return getProperties(Image.class);
+ }
+
+ /**
+ * Adds an image that is associated with the event.
+ * @param image the image
+ * @see draft-ietf-calext-extensions-01
+ * p.10
+ */
+ public void addImage(Image image) {
+ addProperty(image);
+ }
+
+ /**
+ * Gets information related to the event's conference system.
+ * @return the conferences (any changes made this list will affect the
+ * parent component object and vice versa)
+ * @see draft-ietf-calext-extensions-01
+ * p.11
+ */
+ public List getConferences() {
+ return getProperties(Conference.class);
+ }
+
+ /**
+ * Adds information related to the event's conference system.
+ * @param conference the conference
+ * @see draft-ietf-calext-extensions-01
+ * p.11
+ */
+ public void addConference(Conference conference) {
+ addProperty(conference);
+ }
+
+ /**
+ *
+ * Creates an iterator that computes the dates defined by the
+ * {@link RecurrenceRule} and {@link RecurrenceDates} properties (if
+ * present), and excludes those dates which are defined by the
+ * {@link ExceptionRule} and {@link ExceptionDates} properties (if present).
+ *
+ *
+ * In order for {@link RecurrenceRule} and {@link ExceptionRule} properties
+ * to be included in this iterator, a {@link DateStart} property must be
+ * defined.
+ *
+ *
+ * {@link Period} values in {@link RecurrenceDates} properties are not
+ * supported and are ignored.
+ *
+ * @param timezone the timezone to iterate in. This is needed in order to
+ * adjust for when the iterator passes over a daylight savings boundary.
+ * This parameter is ignored if the start date does not have a time
+ * component.
+ * @return the iterator
+ */
+ public DateIterator getDateIterator(TimeZone timezone) {
+ return Google2445Utils.getDateIterator(this, timezone);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ protected void validate(List components, ICalVersion version, List warnings) {
+ if (version != ICalVersion.V1_0) {
+ checkRequiredCardinality(warnings, Uid.class, DateTimeStamp.class);
+ checkOptionalCardinality(warnings, Classification.class, Created.class, Description.class, Geo.class, LastModified.class, Location.class, Organizer.class, Priority.class, Status.class, Summary.class, Transparency.class, Url.class, RecurrenceId.class);
+ }
+
+ checkOptionalCardinality(warnings, Color.class);
+
+ Status[] validStatuses;
+ switch (version) {
+ case V1_0:
+ validStatuses = new Status[] { Status.tentative(), Status.confirmed(), Status.declined(), Status.needsAction(), Status.sent(), Status.delegated() };
+ break;
+ default:
+ validStatuses = new Status[] { Status.tentative(), Status.confirmed(), Status.cancelled() };
+ break;
+ }
+ checkStatus(warnings, validStatuses);
+
+ ICalDate dateStart = getValue(getDateStart());
+ ICalDate dateEnd = getValue(getDateEnd());
+
+ //DTSTART is always required, unless there is a METHOD property at the iCal root
+ ICalComponent ical = components.get(0);
+ if (version != ICalVersion.V1_0 && dateStart == null && ical.getProperty(Method.class) == null) {
+ warnings.add(new ValidationWarning(14));
+ }
+
+ //DTSTART is required if DTEND exists
+ if (dateEnd != null && dateStart == null) {
+ warnings.add(new ValidationWarning(15));
+ }
+
+ if (dateStart != null && dateEnd != null) {
+ //DTSTART must come before DTEND
+ if (dateStart.compareTo(dateEnd) > 0) {
+ warnings.add(new ValidationWarning(16));
+ }
+
+ //DTSTART and DTEND must have the same data type
+ if (dateStart.hasTime() != dateEnd.hasTime()) {
+ warnings.add(new ValidationWarning(17));
+ }
+ }
+
+ //DTEND and DURATION cannot both exist
+ if (dateEnd != null && getDuration() != null) {
+ warnings.add(new ValidationWarning(18));
+ }
+
+ //DTSTART and RECURRENCE-ID must have the same data type
+ ICalDate recurrenceId = getValue(getRecurrenceId());
+ if (recurrenceId != null && dateStart != null && dateStart.hasTime() != recurrenceId.hasTime()) {
+ warnings.add(new ValidationWarning(19));
+ }
+
+ //BYHOUR, BYMINUTE, and BYSECOND cannot be specified in RRULE if DTSTART's data type is "date"
+ //RFC 5545 p. 167
+ Recurrence rrule = getValue(getRecurrenceRule());
+ if (dateStart != null && rrule != null) {
+ if (!dateStart.hasTime() && (!rrule.getByHour().isEmpty() || !rrule.getByMinute().isEmpty() || !rrule.getBySecond().isEmpty())) {
+ warnings.add(new ValidationWarning(5));
+ }
+ }
+
+ //there *should* be only 1 instance of RRULE
+ //RFC 5545 p. 167
+ if (getProperties(RecurrenceRule.class).size() > 1) {
+ warnings.add(new ValidationWarning(6));
+ }
+ }
+
+ @Override
+ public VEvent copy() {
+ return new VEvent(this);
+ }
+}
diff --git a/app/src/main/java/biweekly/component/VFreeBusy.java b/app/src/main/java/biweekly/component/VFreeBusy.java
new file mode 100644
index 0000000000..c3afbba9e5
--- /dev/null
+++ b/app/src/main/java/biweekly/component/VFreeBusy.java
@@ -0,0 +1,655 @@
+package biweekly.component;
+
+import static biweekly.property.ValuedProperty.getValue;
+
+import java.util.Date;
+import java.util.List;
+
+import biweekly.ICalVersion;
+import biweekly.ValidationWarning;
+import biweekly.parameter.FreeBusyType;
+import biweekly.property.Attendee;
+import biweekly.property.Comment;
+import biweekly.property.Contact;
+import biweekly.property.DateEnd;
+import biweekly.property.DateStart;
+import biweekly.property.DateTimeStamp;
+import biweekly.property.FreeBusy;
+import biweekly.property.LastModified;
+import biweekly.property.Method;
+import biweekly.property.Organizer;
+import biweekly.property.RequestStatus;
+import biweekly.property.Uid;
+import biweekly.property.Url;
+import biweekly.util.Duration;
+import biweekly.util.ICalDate;
+import biweekly.util.Period;
+
+/*
+ Copyright (c) 2013-2023, Michael Angstadt
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+
+ 1. Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+ 2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ *
+ * Defines a collection of time ranges that describe when a person is available
+ * and unavailable.
+ *
+ *
+ * Examples:
+ *
+ *
+ *
+ * VFreeBusy freebusy = new VFreeBusy();
+ *
+ * Date start = ...
+ * Date end = ...
+ * freebusy.addFreeBusy(FreeBusyType.FREE, start, end);
+ *
+ * start = ...
+ * Duration duration = Duration.builder().hours(2).build();
+ * freebusy.addFreeBusy(FreeBusyType.BUSY, start, duration);
+ *
+ * @author Michael Angstadt
+ * @see RFC 5545
+ * p.59-62
+ * @see RFC 2445
+ * p.58-60
+ */
+/*
+ * Note: References to the vCal 1.0 spec are omitted from the property
+ * getter/setter method Javadocs because vCal does not use the VFREEBUSY
+ * component.
+ */
+public class VFreeBusy extends ICalComponent {
+ /**
+ *
+ * Creates a new free/busy component.
+ *
+ *
+ * The following properties are added to the component when it is created:
+ *
+ *
+ * - {@link Uid}: Set to a UUID.
+ * - {@link DateTimeStamp}: Set to the current time.
+ *
+ */
+ public VFreeBusy() {
+ setUid(Uid.random());
+ setDateTimeStamp(new Date());
+ }
+
+ /**
+ * Copy constructor.
+ * @param original the component to make a copy of
+ */
+ public VFreeBusy(VFreeBusy original) {
+ super(original);
+ }
+
+ /**
+ * Gets the unique identifier for this free/busy entry. This component
+ * object comes populated with a UID on creation. This is a required
+ * property.
+ * @return the UID or null if not set
+ * @see RFC 5545
+ * p.117-8
+ * @see RFC 2445
+ * p.111-2
+ */
+ public Uid getUid() {
+ return getProperty(Uid.class);
+ }
+
+ /**
+ * Sets the unique identifier for this free/busy entry. This component
+ * object comes populated with a UID on creation. This is a required
+ * property.
+ * @param uid the UID or null to remove
+ * @see RFC 5545
+ * p.117-8
+ * @see RFC 2445
+ * p.111-2
+ */
+ public void setUid(Uid uid) {
+ setProperty(Uid.class, uid);
+ }
+
+ /**
+ * Sets the unique identifier for this free/busy entry. This component
+ * object comes populated with a UID on creation. This is a required
+ * property.
+ * @param uid the UID or null to remove
+ * @return the property that was created
+ * @see RFC 5545
+ * p.117-8
+ * @see RFC 2445
+ * p.111-2
+ */
+ public Uid setUid(String uid) {
+ Uid prop = (uid == null) ? null : new Uid(uid);
+ setUid(prop);
+ return prop;
+ }
+
+ /**
+ * Gets either (a) the creation date of the iCalendar object (if the
+ * {@link Method} property is defined) or (b) the date that the free/busy
+ * entry was last modified (the {@link LastModified} property also holds
+ * this information). This free/busy object comes populated with a
+ * {@link DateTimeStamp} property that is set to the current time. This is a
+ * required property.
+ * @return the date time stamp or null if not set
+ * @see RFC 5545
+ * p.137-8
+ * @see RFC 2445
+ * p.130-1
+ */
+ public DateTimeStamp getDateTimeStamp() {
+ return getProperty(DateTimeStamp.class);
+ }
+
+ /**
+ * Sets either (a) the creation date of the iCalendar object (if the
+ * {@link Method} property is defined) or (b) the date that the free/busy
+ * entry was last modified (the {@link LastModified} property also holds
+ * this information). This free/busy object comes populated with a
+ * {@link DateTimeStamp} property that is set to the current time. This is a
+ * required property.
+ * @param dateTimeStamp the date time stamp or null to remove
+ * @see RFC 5545
+ * p.137-8
+ * @see RFC 2445
+ * p.130-1
+ */
+ public void setDateTimeStamp(DateTimeStamp dateTimeStamp) {
+ setProperty(DateTimeStamp.class, dateTimeStamp);
+ }
+
+ /**
+ * Sets either (a) the creation date of the iCalendar object (if the
+ * {@link Method} property is defined) or (b) the date that the free/busy
+ * entry was last modified (the {@link LastModified} property also holds
+ * this information). This free/busy object comes populated with a
+ * {@link DateTimeStamp} property that is set to the current time. This is a
+ * required property.
+ * @param dateTimeStamp the date time stamp or null to remove
+ * @return the property that was created
+ * @see RFC 5545
+ * p.137-8
+ * @see RFC 2445
+ * p.130-1
+ */
+ public DateTimeStamp setDateTimeStamp(Date dateTimeStamp) {
+ DateTimeStamp prop = (dateTimeStamp == null) ? null : new DateTimeStamp(dateTimeStamp);
+ setDateTimeStamp(prop);
+ return prop;
+ }
+
+ /**
+ * Gets the contact associated with the free/busy entry.
+ * @return the contact or null if not set
+ * @see RFC 5545
+ * p.109-11
+ * @see RFC 2445
+ * p.104-6
+ */
+ public Contact getContact() {
+ return getProperty(Contact.class);
+ }
+
+ /**
+ * Sets the contact for the free/busy entry.
+ * @param contact the contact or null to remove
+ * @see RFC 5545
+ * p.109-11
+ * @see RFC 2445
+ * p.104-6
+ */
+ public void setContact(Contact contact) {
+ setProperty(Contact.class, contact);
+ }
+
+ /**
+ * Sets the contact for the free/busy entry.
+ * @param contact the contact (e.g. "ACME Co - (123) 555-1234")
+ * @return the property that was created
+ * @see RFC 5545
+ * p.109-11
+ * @see RFC 2445
+ * p.104-6
+ */
+ public Contact addContact(String contact) {
+ Contact prop = new Contact(contact);
+ setContact(prop);
+ return prop;
+ }
+
+ /**
+ * Gets the date that the free/busy entry starts.
+ * @return the start date or null if not set
+ * @see RFC 5545
+ * p.97-8
+ * @see RFC 2445
+ * p.93-4
+ */
+ public DateStart getDateStart() {
+ return getProperty(DateStart.class);
+ }
+
+ /**
+ * Sets the date that the free/busy entry starts.
+ * @param dateStart the start date or null to remove
+ * @see RFC 5545
+ * p.97-8
+ * @see RFC 2445
+ * p.93-4
+ */
+ public void setDateStart(DateStart dateStart) {
+ setProperty(DateStart.class, dateStart);
+ }
+
+ /**
+ * Sets the date that the free/busy entry starts.
+ * @param dateStart the start date or null to remove
+ * @return the property that was created
+ * @see RFC 5545
+ * p.97-8
+ * @see RFC 2445
+ * p.93-4
+ */
+ public DateStart setDateStart(Date dateStart) {
+ return setDateStart(dateStart, true);
+ }
+
+ /**
+ * Sets the date that the free/busy entry starts.
+ * @param dateStart the start date or null to remove
+ * @param hasTime true if the date has a time component, false if it is
+ * strictly a date (if false, the given Date object should be created by a
+ * {@link java.util.Calendar Calendar} object that uses the JVM's default
+ * timezone)
+ * @return the property that was created
+ * @see RFC 5545
+ * p.97-8
+ * @see RFC 2445
+ * p.93-4
+ */
+ public DateStart setDateStart(Date dateStart, boolean hasTime) {
+ DateStart prop = (dateStart == null) ? null : new DateStart(dateStart, hasTime);
+ setDateStart(prop);
+ return prop;
+ }
+
+ /**
+ * Gets the date that the free/busy entry ends.
+ * @return the end date or null if not set
+ * @see RFC 5545
+ * p.95-6
+ * @see RFC 2445
+ * p.91-2
+ */
+ public DateEnd getDateEnd() {
+ return getProperty(DateEnd.class);
+ }
+
+ /**
+ * Sets the date that the free/busy entry ends.
+ * @param dateEnd the end date or null to remove
+ * @see RFC 5545
+ * p.95-6
+ * @see RFC 2445
+ * p.91-2
+ */
+ public void setDateEnd(DateEnd dateEnd) {
+ setProperty(DateEnd.class, dateEnd);
+ }
+
+ /**
+ * Sets the date that the free/busy entry ends.
+ * @param dateEnd the end date or null to remove
+ * @return the property that was created
+ * @see RFC 5545
+ * p.95-6
+ * @see RFC 2445
+ * p.91-2
+ */
+ public DateEnd setDateEnd(Date dateEnd) {
+ return setDateEnd(dateEnd, true);
+ }
+
+ /**
+ * Sets the date that the free/busy entry ends.
+ * @param dateEnd the end date or null to remove
+ * @param hasTime true if the date has a time component, false if it is
+ * strictly a date (if false, the given Date object should be created by a
+ * {@link java.util.Calendar Calendar} object that uses the JVM's default
+ * timezone)
+ * @return the property that was created
+ * @see RFC 5545
+ * p.95-6
+ * @see RFC 2445
+ * p.91-2
+ */
+ public DateEnd setDateEnd(Date dateEnd, boolean hasTime) {
+ DateEnd prop = (dateEnd == null) ? null : new DateEnd(dateEnd, hasTime);
+ setDateEnd(prop);
+ return prop;
+ }
+
+ /**
+ * Gets the person requesting the free/busy time.
+ * @return the person requesting the free/busy time or null if not set
+ * @see RFC 5545
+ * p.111-2
+ * @see RFC 2445
+ * p.106-7
+ */
+ public Organizer getOrganizer() {
+ return getProperty(Organizer.class);
+ }
+
+ /**
+ * Sets the person requesting the free/busy time.
+ * @param organizer the person requesting the free/busy time or null to
+ * remove
+ * @see RFC 5545
+ * p.111-2
+ * @see RFC 2445
+ * p.106-7
+ */
+ public void setOrganizer(Organizer organizer) {
+ setProperty(Organizer.class, organizer);
+ }
+
+ /**
+ * Sets the person requesting the free/busy time.
+ * @param email the email address of the person requesting the free/busy
+ * time (e.g. "johndoe@example.com") or null to remove
+ * @return the property that was created
+ * @see RFC 5545
+ * p.111-2
+ * @see RFC 2445
+ * p.106-7
+ */
+ public Organizer setOrganizer(String email) {
+ Organizer prop = (email == null) ? null : new Organizer(null, email);
+ setOrganizer(prop);
+ return prop;
+ }
+
+ /**
+ * Gets a URL to a resource that contains additional information about the
+ * free/busy entry.
+ * @return the URL or null if not set
+ * @see RFC 5545
+ * p.116-7
+ * @see RFC 2445
+ * p.110-1
+ */
+ public Url getUrl() {
+ return getProperty(Url.class);
+ }
+
+ /**
+ * Sets a URL to a resource that contains additional information about the
+ * free/busy entry.
+ * @param url the URL or null to remove
+ * @see RFC 5545
+ * p.116-7
+ * @see RFC 2445
+ * p.110-1
+ */
+ public void setUrl(Url url) {
+ setProperty(Url.class, url);
+ }
+
+ /**
+ * Sets a URL to a resource that contains additional information about the
+ * free/busy entry.
+ * @param url the URL (e.g. "http://example.com/resource.ics") or null to
+ * remove
+ * @return the property that was created
+ * @see RFC 5545
+ * p.116-7
+ * @see RFC 2445
+ * p.110-1
+ */
+ public Url setUrl(String url) {
+ Url prop = (url == null) ? null : new Url(url);
+ setUrl(prop);
+ return prop;
+ }
+
+ /**
+ * Gets the people who are involved in the free/busy entry.
+ * @return the attendees (any changes made this list will affect the parent
+ * component object and vice versa)
+ * @see RFC 5545
+ * p.107-9
+ * @see RFC 2445
+ * p.102-4
+ */
+ public List getAttendees() {
+ return getProperties(Attendee.class);
+ }
+
+ /**
+ * Adds a person who is involved in the free/busy entry.
+ * @param attendee the attendee
+ * @see RFC 5545
+ * p.107-9
+ * @see RFC 2445
+ * p.102-4
+ */
+ public void addAttendee(Attendee attendee) {
+ addProperty(attendee);
+ }
+
+ /**
+ * Gets the comments attached to the free/busy entry.
+ * @return the comments (any changes made this list will affect the parent
+ * component object and vice versa)
+ * @see RFC 5545
+ * p.83-4
+ * @see RFC 2445
+ * p.80-1
+ */
+ public List getComments() {
+ return getProperties(Comment.class);
+ }
+
+ /**
+ * Adds a comment to the free/busy entry.
+ * @param comment the comment to add
+ * @see RFC 5545
+ * p.83-4
+ * @see RFC 2445
+ * p.80-1
+ */
+ public void addComment(Comment comment) {
+ addProperty(comment);
+ }
+
+ /**
+ * Adds a comment to the free/busy entry.
+ * @param comment the comment to add
+ * @return the property that was created
+ * @see RFC 5545
+ * p.83-4
+ * @see RFC 2445
+ * p.80-1
+ */
+ public Comment addComment(String comment) {
+ Comment prop = new Comment(comment);
+ addComment(prop);
+ return prop;
+ }
+
+ /**
+ * Gets the person's availabilities over certain time periods (for example,
+ * "free" between 1pm-3pm, but "busy" between 3pm-4pm).
+ * @return the availabilities (any changes made this list will affect the
+ * parent component object and vice versa)
+ * @see RFC 5545
+ * p.100-1
+ * @see RFC 2445
+ * p.95-6
+ */
+ public List getFreeBusy() {
+ return getProperties(FreeBusy.class);
+ }
+
+ /**
+ * Adds a list of time periods for which the person is free or busy (for
+ * example, "free" between 1pm-3pm and 4pm-5pm). Note that a
+ * {@link FreeBusy} property can contain multiple time periods, but only one
+ * availability type (e.g. "busy").
+ * @param freeBusy the availabilities
+ * @see RFC 5545
+ * p.100-1
+ * @see RFC 2445
+ * p.95-6
+ */
+ public void addFreeBusy(FreeBusy freeBusy) {
+ addProperty(freeBusy);
+ }
+
+ /**
+ * Adds a single time period for which the person is free or busy (for
+ * example, "free" between 1pm-3pm). This method will look for an existing
+ * property that has the given {@link FreeBusyType} and add the time period
+ * to it, or create a new property is one cannot be found.
+ * @param type the availability type (e.g. "free" or "busy")
+ * @param start the start date-time
+ * @param end the end date-time
+ * @return the property that was created/modified
+ * @see RFC 5545
+ * p.100-1
+ * @see RFC 2445
+ * p.95-6
+ */
+ public FreeBusy addFreeBusy(FreeBusyType type, Date start, Date end) {
+ FreeBusy found = findByFreeBusyType(type);
+ found.getValues().add(new Period(start, end));
+ return found;
+ }
+
+ /**
+ * Adds a single time period for which the person is free or busy (for
+ * example, "free" for 2 hours after 1pm). This method will look for an
+ * existing property that has the given {@link FreeBusyType} and add the
+ * time period to it, or create a new property is one cannot be found.
+ * @param type the availability type (e.g. "free" or "busy")
+ * @param start the start date-time
+ * @param duration the length of time
+ * @return the property that was created/modified
+ * @see RFC 5545
+ * p.100-1
+ * @see RFC 2445
+ * p.95-6
+ */
+ public FreeBusy addFreeBusy(FreeBusyType type, Date start, Duration duration) {
+ FreeBusy found = findByFreeBusyType(type);
+ found.getValues().add(new Period(start, duration));
+ return found;
+ }
+
+ private FreeBusy findByFreeBusyType(FreeBusyType type) {
+ for (FreeBusy freeBusy : getFreeBusy()) {
+ if (freeBusy.getType() == type) {
+ return freeBusy;
+ }
+ }
+
+ FreeBusy freeBusy = new FreeBusy();
+ freeBusy.setType(type);
+ addFreeBusy(freeBusy);
+ return freeBusy;
+ }
+
+ /**
+ * Gets the response to a scheduling request.
+ * @return the response
+ * @see RFC 5546
+ * Section 3.6
+ * @see RFC 5545
+ * p.141-3
+ * @see RFC 2445
+ * p.134-6
+ */
+ public RequestStatus getRequestStatus() {
+ return getProperty(RequestStatus.class);
+ }
+
+ /**
+ * Sets the response to a scheduling request.
+ * @param requestStatus the response
+ * @see RFC 5546
+ * Section 3.6
+ * @see RFC 5545
+ * p.141-3
+ * @see RFC 2445
+ * p.134-6
+ */
+ public void setRequestStatus(RequestStatus requestStatus) {
+ setProperty(RequestStatus.class, requestStatus);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ protected void validate(List components, ICalVersion version, List warnings) {
+ if (version == ICalVersion.V1_0) {
+ warnings.add(new ValidationWarning(48, version));
+ }
+
+ checkRequiredCardinality(warnings, Uid.class, DateTimeStamp.class);
+ checkOptionalCardinality(warnings, Contact.class, DateStart.class, DateEnd.class, Organizer.class, Url.class);
+
+ ICalDate dateStart = getValue(getDateStart());
+ ICalDate dateEnd = getValue(getDateEnd());
+
+ //DTSTART is required if DTEND exists
+ if (dateEnd != null && dateStart == null) {
+ warnings.add(new ValidationWarning(15));
+ }
+
+ //DTSTART and DTEND must contain a time component
+ if (dateStart != null && !dateStart.hasTime()) {
+ warnings.add(new ValidationWarning(20, DateStart.class.getSimpleName()));
+ }
+ if (dateEnd != null && !dateEnd.hasTime()) {
+ warnings.add(new ValidationWarning(20, DateEnd.class.getSimpleName()));
+ }
+
+ //DTSTART must come before DTEND
+ if (dateStart != null && dateEnd != null && dateStart.compareTo(dateEnd) >= 0) {
+ warnings.add(new ValidationWarning(16));
+ }
+ }
+
+ @Override
+ public VFreeBusy copy() {
+ return new VFreeBusy(this);
+ }
+}
diff --git a/app/src/main/java/biweekly/component/VJournal.java b/app/src/main/java/biweekly/component/VJournal.java
new file mode 100644
index 0000000000..0ad587dff5
--- /dev/null
+++ b/app/src/main/java/biweekly/component/VJournal.java
@@ -0,0 +1,1259 @@
+package biweekly.component;
+
+import static biweekly.property.ValuedProperty.getValue;
+
+import java.util.Date;
+import java.util.List;
+import java.util.TimeZone;
+
+import biweekly.ICalVersion;
+import biweekly.ValidationWarning;
+import biweekly.property.Attachment;
+import biweekly.property.Attendee;
+import biweekly.property.Categories;
+import biweekly.property.Classification;
+import biweekly.property.Color;
+import biweekly.property.Comment;
+import biweekly.property.Contact;
+import biweekly.property.Created;
+import biweekly.property.DateStart;
+import biweekly.property.DateTimeStamp;
+import biweekly.property.Description;
+import biweekly.property.ExceptionDates;
+import biweekly.property.ExceptionRule;
+import biweekly.property.Image;
+import biweekly.property.LastModified;
+import biweekly.property.Method;
+import biweekly.property.Organizer;
+import biweekly.property.RecurrenceDates;
+import biweekly.property.RecurrenceId;
+import biweekly.property.RecurrenceRule;
+import biweekly.property.RelatedTo;
+import biweekly.property.RequestStatus;
+import biweekly.property.Sequence;
+import biweekly.property.Status;
+import biweekly.property.Summary;
+import biweekly.property.Uid;
+import biweekly.property.Url;
+import biweekly.util.Google2445Utils;
+import biweekly.util.ICalDate;
+import biweekly.util.Period;
+import biweekly.util.Recurrence;
+import biweekly.util.com.google.ical.compat.javautil.DateIterator;
+
+/*
+ Copyright (c) 2013-2023, Michael Angstadt
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+
+ 1. Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+ 2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ *
+ * Defines a journal entry.
+ *
+ *
+ * Examples:
+ *
+ *
+ *
+ * VJournal journal = new VJournal();
+ * journal.setSummary("Team Meeting");
+ * journal.setDescription("The following items were discussed: ...");
+ * byte[] slides = ...
+ * journal.addAttachment(new Attachment("application/vnd.ms-powerpoint", slides));
+ *
+ * @author Michael Angstadt
+ * @see RFC 5545 p.57-9
+ * @see RFC 2445 p.56-7
+ */
+/*
+ * Note: References to the vCal 1.0 spec are omitted from the property
+ * getter/setter method Javadocs because vCal does not use the VJOURNAL
+ * component.
+ */
+public class VJournal extends ICalComponent {
+ /**
+ *
+ * Creates a new journal entry.
+ *
+ *
+ * The following properties are added to the component when it is created:
+ *
+ *
+ * - {@link Uid}: Set to a UUID.
+ * - {@link DateTimeStamp}: Set to the current time.
+ *
+ */
+ public VJournal() {
+ setUid(Uid.random());
+ setDateTimeStamp(new Date());
+ }
+
+ /**
+ * Copy constructor.
+ * @param original the component to make a copy of
+ */
+ public VJournal(VJournal original) {
+ super(original);
+ }
+
+ /**
+ * Gets the unique identifier for this journal entry. This component object
+ * comes populated with a UID on creation. This is a required
+ * property.
+ * @return the UID or null if not set
+ * @see RFC 5545
+ * p.117-8
+ * @see RFC 2445
+ * p.111-2
+ */
+ public Uid getUid() {
+ return getProperty(Uid.class);
+ }
+
+ /**
+ * Sets the unique identifier for this journal entry. This component object
+ * comes populated with a UID on creation. This is a required
+ * property.
+ * @param uid the UID or null to remove
+ * @see RFC 5545
+ * p.117-8
+ * @see RFC 2445
+ * p.111-2
+ */
+ public void setUid(Uid uid) {
+ setProperty(Uid.class, uid);
+ }
+
+ /**
+ * Sets the unique identifier for this journal entry. This component object
+ * comes populated with a UID on creation. This is a required
+ * property.
+ * @param uid the UID or null to remove
+ * @return the property that was created
+ * @see RFC 5545
+ * p.117-8
+ * @see RFC 2445
+ * p.111-2
+ */
+ public Uid setUid(String uid) {
+ Uid prop = (uid == null) ? null : new Uid(uid);
+ setUid(prop);
+ return prop;
+ }
+
+ /**
+ * Gets either (a) the creation date of the iCalendar object (if the
+ * {@link Method} property is defined) or (b) the date that the journal
+ * entry was last modified (the {@link LastModified} property also holds
+ * this information). This journal entry object comes populated with a
+ * {@link DateTimeStamp} property that is set to the current time. This is a
+ * required property.
+ * @return the date time stamp or null if not set
+ * @see RFC 5545
+ * p.137-8
+ * @see RFC 2445
+ * p.130-1
+ */
+ public DateTimeStamp getDateTimeStamp() {
+ return getProperty(DateTimeStamp.class);
+ }
+
+ /**
+ * Sets either (a) the creation date of the iCalendar object (if the
+ * {@link Method} property is defined) or (b) the date that the journal
+ * entry was last modified (the {@link LastModified} property also holds
+ * this information). This journal entry object comes populated with a
+ * {@link DateTimeStamp} property that is set to the current time. This is a
+ * required property.
+ * @param dateTimeStamp the date time stamp or null to remove
+ * @see RFC 5545
+ * p.137-8
+ * @see RFC 2445
+ * p.130-1
+ */
+ public void setDateTimeStamp(DateTimeStamp dateTimeStamp) {
+ setProperty(DateTimeStamp.class, dateTimeStamp);
+ }
+
+ /**
+ * Sets either (a) the creation date of the iCalendar object (if the
+ * {@link Method} property is defined) or (b) the date that the journal
+ * entry was last modified (the {@link LastModified} property also holds
+ * this information). This journal entry object comes populated with a
+ * {@link DateTimeStamp} property that is set to the current time. This is a
+ * required property.
+ * @param dateTimeStamp the date time stamp or null to remove
+ * @return the property that was created
+ * @see RFC 5545
+ * p.137-8
+ * @see RFC 2445
+ * p.130-1
+ */
+ public DateTimeStamp setDateTimeStamp(Date dateTimeStamp) {
+ DateTimeStamp prop = (dateTimeStamp == null) ? null : new DateTimeStamp(dateTimeStamp);
+ setDateTimeStamp(prop);
+ return prop;
+ }
+
+ /**
+ * Gets the level of sensitivity of the journal entry. If not specified, the
+ * data within the journal entry should be considered "public".
+ * @return the classification level or null if not set
+ * @see RFC 5545
+ * p.82-3
+ * @see RFC 2445
+ * p.79-80
+ */
+ public Classification getClassification() {
+ return getProperty(Classification.class);
+ }
+
+ /**
+ * Sets the level of sensitivity of the journal entry. If not specified, the
+ * data within the journal entry should be considered "public".
+ * @param classification the classification level or null to remove
+ * @see RFC 5545
+ * p.82-3
+ * @see RFC 2445
+ * p.79-80
+ */
+ public void setClassification(Classification classification) {
+ setProperty(Classification.class, classification);
+ }
+
+ /**
+ * Sets the level of sensitivity of the journal entry. If not specified, the
+ * data within the journal entry should be considered "public".
+ * @param classification the classification level (e.g. "CONFIDENTIAL") or
+ * null to remove
+ * @return the property that was created
+ * @see RFC 5545
+ * p.82-3
+ * @see RFC 2445
+ * p.79-80
+ */
+ public Classification setClassification(String classification) {
+ Classification prop = (classification == null) ? null : new Classification(classification);
+ setClassification(prop);
+ return prop;
+ }
+
+ /**
+ * Gets the date-time that the journal entry was initially created.
+ * @return the creation date-time or null if not set
+ * @see RFC 5545
+ * p.136
+ * @see RFC 2445
+ * p.129-30
+ */
+ public Created getCreated() {
+ return getProperty(Created.class);
+ }
+
+ /**
+ * Sets the date-time that the journal entry was initially created.
+ * @param created the creation date-time or null to remove
+ * @see RFC 5545
+ * p.136
+ * @see RFC 2445
+ * p.129-30
+ */
+ public void setCreated(Created created) {
+ setProperty(Created.class, created);
+ }
+
+ /**
+ * Sets the date-time that the journal entry was initially created.
+ * @param created the creation date-time or null to remove
+ * @return the property that was created
+ * @see RFC 5545
+ * p.136
+ * @see RFC 2445
+ * p.129-30
+ */
+ public Created setCreated(Date created) {
+ Created prop = (created == null) ? null : new Created(created);
+ setCreated(prop);
+ return prop;
+ }
+
+ /**
+ * Gets the date that the journal entry starts.
+ * @return the start date or null if not set
+ * @see RFC 5545
+ * p.97-8
+ * @see RFC 2445
+ * p.93-4
+ */
+ public DateStart getDateStart() {
+ return getProperty(DateStart.class);
+ }
+
+ /**
+ * Sets the date that the journal entry starts.
+ * @param dateStart the start date or null to remove
+ * @see RFC 5545
+ * p.97-8
+ * @see RFC 2445
+ * p.93-4
+ */
+ public void setDateStart(DateStart dateStart) {
+ setProperty(DateStart.class, dateStart);
+ }
+
+ /**
+ * Sets the date that the journal entry starts.
+ * @param dateStart the start date or null to remove
+ * @return the property that was created
+ * @see RFC 5545
+ * p.97-8
+ * @see RFC 2445
+ * p.93-4
+ */
+ public DateStart setDateStart(Date dateStart) {
+ return setDateStart(dateStart, true);
+ }
+
+ /**
+ * Sets the date that the journal entry starts.
+ * @param dateStart the start date or null to remove
+ * @param hasTime true if the date has a time component, false if it is
+ * strictly a date (if false, the given Date object should be created by a
+ * {@link java.util.Calendar Calendar} object that uses the JVM's default
+ * timezone)
+ * @return the property that was created
+ * @see RFC 5545
+ * p.97-8
+ * @see RFC 2445
+ * p.93-4
+ */
+ public DateStart setDateStart(Date dateStart, boolean hasTime) {
+ DateStart prop = (dateStart == null) ? null : new DateStart(dateStart, hasTime);
+ setDateStart(prop);
+ return prop;
+ }
+
+ /**
+ * Gets the date-time that the journal entry was last changed.
+ * @return the last modified date or null if not set
+ * @see RFC 5545
+ * p.138
+ * @see RFC 2445
+ * p.131
+ */
+ public LastModified getLastModified() {
+ return getProperty(LastModified.class);
+ }
+
+ /**
+ * Sets the date-time that the journal entry was last changed.
+ * @param lastModified the last modified date or null to remove
+ * @see RFC 5545
+ * p.138
+ * @see RFC 2445
+ * p.131
+ */
+ public void setLastModified(LastModified lastModified) {
+ setProperty(LastModified.class, lastModified);
+ }
+
+ /**
+ * Sets the date-time that the journal entry was last changed.
+ * @param lastModified the last modified date or null to remove
+ * @return the property that was created
+ * @see RFC 5545
+ * p.138
+ * @see RFC 2445
+ * p.131
+ */
+ public LastModified setLastModified(Date lastModified) {
+ LastModified prop = (lastModified == null) ? null : new LastModified(lastModified);
+ setLastModified(prop);
+ return prop;
+ }
+
+ /**
+ * Gets the organizer of the journal entry.
+ * @return the organizer or null if not set
+ * @see RFC 5545
+ * p.111-2
+ * @see RFC 2445
+ * p.106-7
+ */
+ public Organizer getOrganizer() {
+ return getProperty(Organizer.class);
+ }
+
+ /**
+ * Sets the organizer of the journal entry.
+ * @param organizer the organizer or null to remove
+ * @see RFC 5545
+ * p.111-2
+ * @see RFC 2445
+ * p.106-7
+ */
+ public void setOrganizer(Organizer organizer) {
+ setProperty(Organizer.class, organizer);
+ }
+
+ /**
+ * Sets the organizer of the journal entry.
+ * @param email the organizer's email address (e.g. "johndoe@example.com")
+ * or null to remove
+ * @return the property that was created
+ * @see RFC 5545
+ * p.111-2
+ * @see RFC 2445
+ * p.106-7
+ */
+ public Organizer setOrganizer(String email) {
+ Organizer prop = (email == null) ? null : new Organizer(null, email);
+ setOrganizer(prop);
+ return prop;
+ }
+
+ /**
+ * Gets the original value of the {@link DateStart} property if the event is
+ * recurring and has been modified. Used in conjunction with the {@link Uid}
+ * and {@link Sequence} properties to uniquely identify a recurrence
+ * instance.
+ * @return the recurrence ID or null if not set
+ * @see RFC 5545
+ * p.112-4
+ * @see RFC 2445
+ * p.107-9
+ */
+ public RecurrenceId getRecurrenceId() {
+ return getProperty(RecurrenceId.class);
+ }
+
+ /**
+ * Sets the original value of the {@link DateStart} property if the event is
+ * recurring and has been modified. Used in conjunction with the {@link Uid}
+ * and {@link Sequence} properties to uniquely identify a recurrence
+ * instance.
+ * @param recurrenceId the recurrence ID or null to remove
+ * @see RFC 5545
+ * p.112-4
+ * @see RFC 2445
+ * p.107-9
+ */
+ public void setRecurrenceId(RecurrenceId recurrenceId) {
+ setProperty(RecurrenceId.class, recurrenceId);
+ }
+
+ /**
+ * Sets the original value of the {@link DateStart} property if the journal
+ * entry is recurring and has been modified. Used in conjunction with the
+ * {@link Uid} and {@link Sequence} properties to uniquely identify a
+ * recurrence instance.
+ * @param originalStartDate the original start date or null to remove
+ * @return the property that was created
+ * @see RFC 5545
+ * p.112-4
+ * @see RFC 2445
+ * p.107-9
+ */
+ public RecurrenceId setRecurrenceId(Date originalStartDate) {
+ RecurrenceId prop = (originalStartDate == null) ? null : new RecurrenceId(originalStartDate);
+ setRecurrenceId(prop);
+ return prop;
+ }
+
+ /**
+ * Gets the revision number of the journal entry. The organizer can
+ * increment this number every time he or she makes a significant change.
+ * @return the sequence number
+ * @see RFC 5545
+ * p.138-9
+ * @see RFC 2445
+ * p.131-3
+ */
+ public Sequence getSequence() {
+ return getProperty(Sequence.class);
+ }
+
+ /**
+ * Sets the revision number of the journal entry. The organizer can
+ * increment this number every time he or she makes a significant change.
+ * @param sequence the sequence number
+ * @see RFC 5545
+ * p.138-9
+ * @see RFC 2445
+ * p.131-3
+ */
+ public void setSequence(Sequence sequence) {
+ setProperty(Sequence.class, sequence);
+ }
+
+ /**
+ * Sets the revision number of the journal entry. The organizer can
+ * increment this number every time he or she makes a significant change.
+ * @param sequence the sequence number
+ * @return the property that was created
+ * @see RFC 5545
+ * p.138-9
+ * @see RFC 2445
+ * p.131-3
+ */
+ public Sequence setSequence(Integer sequence) {
+ Sequence prop = (sequence == null) ? null : new Sequence(sequence);
+ setSequence(prop);
+ return prop;
+ }
+
+ /**
+ * Increments the revision number of the journal entry. The organizer can
+ * increment this number every time he or she makes a significant change.
+ * @see RFC 5545
+ * p.138-9
+ * @see RFC 2445
+ * p.131-3
+ */
+ public void incrementSequence() {
+ Sequence sequence = getSequence();
+ if (sequence == null) {
+ setSequence(1);
+ } else {
+ sequence.increment();
+ }
+ }
+
+ /**
+ * Gets the status of the journal entry.
+ * @return the status or null if not set
+ * @see RFC 5545
+ * p.92-3
+ * @see RFC 2445
+ * p.88-9
+ */
+ public Status getStatus() {
+ return getProperty(Status.class);
+ }
+
+ /**
+ *
+ * Sets the status of the journal entry.
+ *
+ *
+ * Valid journal status codes are:
+ *
+ *
+ * - DRAFT
+ * - FINAL
+ * - CANCELLED
+ *
+ * @param status the status or null to remove
+ * @see RFC 5545
+ * p.92-3
+ * @see RFC 2445
+ * p.88-9
+ */
+ public void setStatus(Status status) {
+ setProperty(Status.class, status);
+ }
+
+ /**
+ * Gets the summary of the journal entry.
+ * @return the summary or null if not set
+ * @see RFC 5545
+ * p.93-4
+ * @see RFC 2445
+ * p.89-90
+ */
+ public Summary getSummary() {
+ return getProperty(Summary.class);
+ }
+
+ /**
+ * Sets the summary of the journal entry.
+ * @param summary the summary or null to remove
+ * @see RFC 5545
+ * p.93-4
+ * @see RFC 2445
+ * p.89-90
+ */
+ public void setSummary(Summary summary) {
+ setProperty(Summary.class, summary);
+ }
+
+ /**
+ * Sets the summary of the journal entry.
+ * @param summary the summary or null to remove
+ * @return the property that was created
+ * @see RFC 5545
+ * p.93-4
+ * @see RFC 2445
+ * p.89-90
+ */
+ public Summary setSummary(String summary) {
+ Summary prop = (summary == null) ? null : new Summary(summary);
+ setSummary(prop);
+ return prop;
+ }
+
+ /**
+ * Gets a URL to a resource that contains additional information about the
+ * journal entry.
+ * @return the URL or null if not set
+ * @see RFC 5545
+ * p.116-7
+ * @see RFC 2445
+ * p.110-1
+ */
+ public Url getUrl() {
+ return getProperty(Url.class);
+ }
+
+ /**
+ * Sets a URL to a resource that contains additional information about the
+ * journal entry.
+ * @param url the URL or null to remove
+ * @see RFC 5545
+ * p.116-7
+ * @see RFC 2445
+ * p.110-1
+ */
+ public void setUrl(Url url) {
+ setProperty(Url.class, url);
+ }
+
+ /**
+ * Sets a URL to a resource that contains additional information about the
+ * journal entry.
+ * @param url the URL (e.g. "http://example.com/resource.ics") or null to
+ * remove
+ * @return the property that was created
+ * @see RFC 5545
+ * p.116-7
+ * @see RFC 2445
+ * p.110-1
+ */
+ public Url setUrl(String url) {
+ Url prop = (url == null) ? null : new Url(url);
+ setUrl(prop);
+ return prop;
+ }
+
+ /**
+ * Gets how often the journal entry repeats.
+ * @return the recurrence rule or null if not set
+ * @see RFC 5545
+ * p.122-32
+ * @see RFC 2445
+ * p.117-25
+ */
+ public RecurrenceRule getRecurrenceRule() {
+ return getProperty(RecurrenceRule.class);
+ }
+
+ /**
+ * Sets how often the journal entry repeats.
+ * @param recur the recurrence rule or null to remove
+ * @return the property that was created
+ * @see RFC 5545
+ * p.122-32
+ * @see RFC 2445
+ * p.117-25
+ */
+ public RecurrenceRule setRecurrenceRule(Recurrence recur) {
+ RecurrenceRule prop = (recur == null) ? null : new RecurrenceRule(recur);
+ setRecurrenceRule(prop);
+ return prop;
+ }
+
+ /**
+ * Sets how often the journal entry repeats.
+ * @param recurrenceRule the recurrence rule or null to remove
+ * @see RFC 5545
+ * p.122-32
+ * @see RFC 2445
+ * p.117-25
+ */
+ public void setRecurrenceRule(RecurrenceRule recurrenceRule) {
+ setProperty(RecurrenceRule.class, recurrenceRule);
+ }
+
+ /**
+ * Gets any attachments that are associated with the journal entry.
+ * @return the attachments (any changes made this list will affect the
+ * parent component object and vice versa)
+ * @see RFC 5545
+ * p.80-1
+ * @see RFC 2445
+ * p.77-8
+ */
+ public List getAttachments() {
+ return getProperties(Attachment.class);
+ }
+
+ /**
+ * Adds an attachment to the journal entry.
+ * @param attachment the attachment to add
+ * @see RFC 5545
+ * p.80-1
+ * @see RFC 2445
+ * p.77-8
+ */
+ public void addAttachment(Attachment attachment) {
+ addProperty(attachment);
+ }
+
+ /**
+ * Gets the people who are involved in the journal entry.
+ * @return the attendees (any changes made this list will affect the parent
+ * component object and vice versa)
+ * @see RFC 5545
+ * p.107-9
+ * @see RFC 2445
+ * p.102-4
+ */
+ public List getAttendees() {
+ return getProperties(Attendee.class);
+ }
+
+ /**
+ * Adds a person who is involved in the journal entry.
+ * @param attendee the attendee
+ * @see RFC 5545
+ * p.107-9
+ * @see RFC 2445
+ * p.102-4
+ */
+ public void addAttendee(Attendee attendee) {
+ addProperty(attendee);
+ }
+
+ /**
+ * Adds a person who is involved in the journal entry.
+ * @param email the attendee's email address
+ * @return the property that was created
+ * @see RFC 5545
+ * p.107-9
+ * @see RFC 2445
+ * p.102-4
+ */
+ public Attendee addAttendee(String email) {
+ Attendee prop = new Attendee(null, email);
+ addAttendee(prop);
+ return prop;
+ }
+
+ /**
+ * Gets a list of "tags" or "keywords" that describe the journal entry.
+ * @return the categories (any changes made this list will affect the parent
+ * component object and vice versa)
+ * @see RFC 5545
+ * p.81-2
+ * @see RFC 2445
+ * p.78-9
+ */
+ public List getCategories() {
+ return getProperties(Categories.class);
+ }
+
+ /**
+ * Adds a list of "tags" or "keywords" that describe the journal entry. Note
+ * that a single property can hold multiple keywords.
+ * @param categories the categories to add
+ * @see RFC 5545
+ * p.81-2
+ * @see RFC 2445
+ * p.78-9
+ */
+ public void addCategories(Categories categories) {
+ addProperty(categories);
+ }
+
+ /**
+ * Adds a list of "tags" or "keywords" that describe the journal entry.
+ * @param categories the categories to add
+ * @return the property that was created
+ * @see RFC 5545
+ * p.81-2
+ * @see RFC 2445
+ * p.78-9
+ */
+ public Categories addCategories(String... categories) {
+ Categories prop = new Categories(categories);
+ addCategories(prop);
+ return prop;
+ }
+
+ /**
+ * Adds a list of "tags" or "keywords" that describe the journal entry.
+ * @param categories the categories to add
+ * @return the property that was created
+ * @see RFC 5545
+ * p.81-2
+ * @see RFC 2445
+ * p.78-9
+ */
+ public Categories addCategories(List categories) {
+ Categories prop = new Categories(categories);
+ addCategories(prop);
+ return prop;
+ }
+
+ /**
+ * Gets the comments attached to the journal entry.
+ * @return the comments (any changes made this list will affect the parent
+ * component object and vice versa)
+ * @see RFC 5545
+ * p.83-4
+ * @see RFC 2445
+ * p.80-1
+ */
+ public List getComments() {
+ return getProperties(Comment.class);
+ }
+
+ /**
+ * Adds a comment to the journal entry.
+ * @param comment the comment to add
+ * @see RFC 5545
+ * p.83-4
+ * @see RFC 2445
+ * p.80-1
+ */
+ public void addComment(Comment comment) {
+ addProperty(comment);
+ }
+
+ /**
+ * Adds a comment to the journal entry.
+ * @param comment the comment to add
+ * @return the property that was created
+ * @see RFC 5545
+ * p.83-4
+ * @see RFC 2445
+ * p.80-1
+ */
+ public Comment addComment(String comment) {
+ Comment prop = new Comment(comment);
+ addComment(prop);
+ return prop;
+ }
+
+ /**
+ * Gets the contacts associated with the journal entry.
+ * @return the contacts (any changes made this list will affect the parent
+ * component object and vice versa)
+ * @see RFC 5545
+ * p.109-11
+ * @see RFC 2445
+ * p.104-6
+ */
+ public List getContacts() {
+ return getProperties(Contact.class);
+ }
+
+ /**
+ * Adds a contact to the journal entry.
+ * @param contact the contact
+ * @see RFC 5545
+ * p.109-11
+ * @see RFC 2445
+ * p.104-6
+ */
+ public void addContact(Contact contact) {
+ addProperty(contact);
+ }
+
+ /**
+ * Adds a contact to the journal entry.
+ * @param contact the contact (e.g. "ACME Co - (123) 555-1234")
+ * @return the property that was created
+ * @see RFC 5545
+ * p.109-11
+ * @see RFC 2445
+ * p.104-6
+ */
+ public Contact addContact(String contact) {
+ Contact prop = new Contact(contact);
+ addContact(prop);
+ return prop;
+ }
+
+ /**
+ * Gets the detailed descriptions to the journal entry. The descriptions
+ * should be a more detailed version of the one provided by the
+ * {@link Summary} property.
+ * @return the descriptions (any changes made this list will affect the
+ * parent component object and vice versa)
+ * @see RFC 5545
+ * p.84-5
+ * @see RFC 2445
+ * p.81-2
+ */
+ public List getDescriptions() {
+ return getProperties(Description.class);
+ }
+
+ /**
+ * Adds a detailed description to the journal entry. The description should
+ * be a more detailed version of the one provided by the {@link Summary}
+ * property.
+ * @param description the description
+ * @see RFC 5545
+ * p.84-5
+ * @see RFC 2445
+ * p.81-2
+ */
+ public void addDescription(Description description) {
+ addProperty(description);
+ }
+
+ /**
+ * Adds a detailed description to the journal entry. The description should
+ * be a more detailed version of the one provided by the {@link Summary}
+ * property.
+ * @param description the description
+ * @return the property that was created
+ * @see RFC 5545
+ * p.84-5
+ * @see RFC 2445
+ * p.81-2
+ */
+ public Description addDescription(String description) {
+ Description prop = new Description(description);
+ addDescription(prop);
+ return prop;
+ }
+
+ /**
+ * Gets the list of exceptions to the recurrence rule defined in the journal
+ * entry (if one is defined).
+ * @return the list of exceptions (any changes made this list will affect
+ * the parent component object and vice versa)
+ * @see RFC 5545
+ * p.118-20
+ * @see RFC 2445
+ * p.112-4
+ */
+ public List getExceptionDates() {
+ return getProperties(ExceptionDates.class);
+ }
+
+ /**
+ * Adds a list of exceptions to the recurrence rule defined in the journal
+ * entry (if one is defined). Note that this property can contain multiple
+ * dates.
+ * @param exceptionDates the list of exceptions
+ * @see RFC 5545
+ * p.118-20
+ * @see RFC 2445
+ * p.112-4
+ */
+ public void addExceptionDates(ExceptionDates exceptionDates) {
+ addProperty(exceptionDates);
+ }
+
+ /**
+ * Gets the components that the journal entry is related to.
+ * @return the relationships (any changes made this list will affect the
+ * parent component object and vice versa)
+ * @see RFC 5545
+ * p.115-6
+ * @see RFC 2445
+ * p.109-10
+ */
+ public List getRelatedTo() {
+ return getProperties(RelatedTo.class);
+ }
+
+ /**
+ * Adds a component that the journal entry is related to.
+ * @param relatedTo the relationship
+ * @see RFC 5545
+ * p.115-6
+ * @see RFC 2445
+ * p.109-10
+ */
+ public void addRelatedTo(RelatedTo relatedTo) {
+ //TODO create a method that accepts a component and make the RelatedTo property invisible to the user
+ //@formatter:off
+ /*
+ * addRelation(RelationshipType relType, ICalComponent component) {
+ * RelatedTo prop = new RelatedTo(component.getUid().getValue());
+ * prop.setRelationshipType(relType);
+ * addProperty(prop);
+ * }
+ */
+ //@formatter:on
+ addProperty(relatedTo);
+ }
+
+ /**
+ * Adds a component that the journal entry is related to.
+ * @param uid the UID of the other component
+ * @return the property that was created
+ * @see RFC 5545
+ * p.115-6
+ * @see RFC 2445
+ * p.109-10
+ */
+ public RelatedTo addRelatedTo(String uid) {
+ RelatedTo prop = new RelatedTo(uid);
+ addRelatedTo(prop);
+ return prop;
+ }
+
+ /**
+ * Gets the list of dates/periods that help define the recurrence rule of
+ * this journal entry (if one is defined).
+ * @return the recurrence dates (any changes made this list will affect the
+ * parent component object and vice versa)
+ * @see RFC 5545
+ * p.120-2
+ * @see RFC 2445
+ * p.115-7
+ */
+ public List getRecurrenceDates() {
+ return getProperties(RecurrenceDates.class);
+ }
+
+ /**
+ * Adds a list of dates/periods that help define the recurrence rule of this
+ * journal entry (if one is defined).
+ * @param recurrenceDates the recurrence dates
+ * @see RFC 5545
+ * p.120-2
+ * @see RFC 2445
+ * p.115-7
+ */
+ public void addRecurrenceDates(RecurrenceDates recurrenceDates) {
+ addProperty(recurrenceDates);
+ }
+
+ /**
+ * Gets the response to a scheduling request.
+ * @return the response
+ * @see RFC 5546
+ * Section 3.6
+ * @see RFC 5545
+ * p.141-3
+ * @see RFC 2445
+ * p.134-6
+ */
+ public RequestStatus getRequestStatus() {
+ return getProperty(RequestStatus.class);
+ }
+
+ /**
+ * Sets the response to a scheduling request.
+ * @param requestStatus the response
+ * @see RFC 5546
+ * Section 3.6
+ * @see RFC 5545
+ * p.141-3
+ * @see RFC 2445
+ * p.134-6
+ */
+ public void setRequestStatus(RequestStatus requestStatus) {
+ setProperty(RequestStatus.class, requestStatus);
+ }
+
+ /**
+ *
+ * Gets the exceptions for the {@link RecurrenceRule} property.
+ *
+ *
+ * Note that this property has been removed from the latest version of the
+ * iCal specification. Its use should be avoided.
+ *
+ * @return the exception rules (any changes made this list will affect the
+ * parent component object and vice versa)
+ * @see RFC 2445
+ * p.114-15
+ */
+ public List getExceptionRules() {
+ return getProperties(ExceptionRule.class);
+ }
+
+ /**
+ *
+ * Adds an exception for the {@link RecurrenceRule} property.
+ *
+ *
+ * Note that this property has been removed from the latest version of the
+ * iCal specification. Its use should be avoided.
+ *
+ * @param recur the exception rule to add
+ * @return the property that was created
+ * @see RFC 2445
+ * p.114-15
+ */
+ public ExceptionRule addExceptionRule(Recurrence recur) {
+ ExceptionRule prop = new ExceptionRule(recur);
+ addExceptionRule(prop);
+ return prop;
+ }
+
+ /**
+ *
+ * Adds an exception for the {@link RecurrenceRule} property.
+ *
+ *
+ * Note that this property has been removed from the latest version of the
+ * iCal specification. Its use should be avoided.
+ *
+ * @param exceptionRule the exception rule to add
+ * @see RFC 2445
+ * p.114-15
+ */
+ public void addExceptionRule(ExceptionRule exceptionRule) {
+ addProperty(exceptionRule);
+ }
+
+ /**
+ * Gets the color that clients may use when displaying the journal entry
+ * (for example, a background color).
+ * @return the property or null if not set
+ * @see draft-ietf-calext-extensions-01
+ * p.9
+ */
+ public Color getColor() {
+ return getProperty(Color.class);
+ }
+
+ /**
+ * Sets the color that clients may use when displaying the journal entry
+ * (for example, a background color).
+ * @param color the property or null to remove
+ * @see draft-ietf-calext-extensions-01
+ * p.79
+ */
+ public void setColor(Color color) {
+ setProperty(Color.class, color);
+ }
+
+ /**
+ * Sets the color that clients may use when displaying the journal entry
+ * (for example, a background color).
+ * @param color the color name (case insensitive) or null to remove.
+ * Acceptable values are defined in Section 4.3 of the CSS Color Module Level 3 Recommendation. For
+ * example, "aliceblue", "green", "navy".
+ * @return the property object that was created
+ * @see draft-ietf-calext-extensions-01
+ * p.9
+ */
+ public Color setColor(String color) {
+ Color prop = (color == null) ? null : new Color(color);
+ setColor(prop);
+ return prop;
+ }
+
+ /**
+ * Gets the images that are associated with the journal entry.
+ * @return the images (any changes made this list will affect the parent
+ * component object and vice versa)
+ * @see draft-ietf-calext-extensions-01
+ * p.10
+ */
+ public List getImages() {
+ return getProperties(Image.class);
+ }
+
+ /**
+ * Adds an image that is associated with the journal entry.
+ * @param image the property to add
+ * @see draft-ietf-calext-extensions-01
+ * p.10
+ */
+ public void addImage(Image image) {
+ addProperty(image);
+ }
+
+ /**
+ *
+ * Creates an iterator that computes the dates defined by the
+ * {@link RecurrenceRule} and {@link RecurrenceDates} properties (if
+ * present), and excludes those dates which are defined by the
+ * {@link ExceptionRule} and {@link ExceptionDates} properties (if present).
+ *
+ *
+ * In order for {@link RecurrenceRule} and {@link ExceptionRule} properties
+ * to be included in this iterator, a {@link DateStart} property must be
+ * defined.
+ *
+ *
+ * {@link Period} values in {@link RecurrenceDates} properties are not
+ * supported and are ignored.
+ *
+ * @param timezone the timezone to iterate in. This is needed in order to
+ * adjust for when the iterator passes over a daylight savings boundary.
+ * This parameter is ignored if the start date does not have a time
+ * component.
+ * @return the iterator
+ */
+ public DateIterator getDateIterator(TimeZone timezone) {
+ return Google2445Utils.getDateIterator(this, timezone);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ protected void validate(List components, ICalVersion version, List warnings) {
+ if (version == ICalVersion.V1_0) {
+ warnings.add(new ValidationWarning(48, version));
+ }
+
+ checkRequiredCardinality(warnings, Uid.class, DateTimeStamp.class);
+ checkOptionalCardinality(warnings, Classification.class, Created.class, DateStart.class, LastModified.class, Organizer.class, RecurrenceId.class, Sequence.class, Status.class, Summary.class, Url.class, Color.class);
+ checkStatus(warnings, Status.draft(), Status.final_(), Status.cancelled());
+
+ //DTSTART and RECURRENCE-ID must have the same data type
+ ICalDate recurrenceId = getValue(getRecurrenceId());
+ ICalDate dateStart = getValue(getDateStart());
+ if (recurrenceId != null && dateStart != null && dateStart.hasTime() != recurrenceId.hasTime()) {
+ warnings.add(new ValidationWarning(19));
+ }
+
+ //BYHOUR, BYMINUTE, and BYSECOND cannot be specified in RRULE if DTSTART's data type is "date"
+ //RFC 5545 p. 167
+ Recurrence rrule = getValue(getRecurrenceRule());
+ if (dateStart != null && rrule != null) {
+ if (!dateStart.hasTime() && (!rrule.getByHour().isEmpty() || !rrule.getByMinute().isEmpty() || !rrule.getBySecond().isEmpty())) {
+ warnings.add(new ValidationWarning(5));
+ }
+ }
+
+ //there *should* be only 1 instance of RRULE
+ //RFC 5545 p. 167
+ if (getProperties(RecurrenceRule.class).size() > 1) {
+ warnings.add(new ValidationWarning(6));
+ }
+ }
+
+ @Override
+ public VJournal copy() {
+ return new VJournal(this);
+ }
+}
diff --git a/app/src/main/java/biweekly/component/VTimezone.java b/app/src/main/java/biweekly/component/VTimezone.java
new file mode 100644
index 0000000000..4d51c85721
--- /dev/null
+++ b/app/src/main/java/biweekly/component/VTimezone.java
@@ -0,0 +1,282 @@
+package biweekly.component;
+
+import java.util.Date;
+import java.util.List;
+
+import biweekly.ICalVersion;
+import biweekly.ValidationWarning;
+import biweekly.property.LastModified;
+import biweekly.property.TimezoneId;
+import biweekly.property.TimezoneUrl;
+
+/*
+ Copyright (c) 2013-2023, Michael Angstadt
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+
+ 1. Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+ 2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ *
+ * Defines a timezone's UTC offsets throughout the year.
+ *
+ *
+ *
+ * Examples:
+ *
+ *
+ *
+ * VTimezone timezone = new VTimezone("Eastern Standard Time");
+ *
+ * StandardTime standard = new StandardTime();
+ * DateTimeComponents componentsStandard = new DateTimeComponents(1998, 10, 25, 2, 0, 0, false);
+ * standard.setDateStart(componentsStandard);
+ * standard.setTimezoneOffsetFrom(-4, 0);
+ * standard.setTimezoneOffsetTo(-5, 0);
+ * timezone.addStandardTime(standard);
+ *
+ * DaylightSavingsTime daylight = new DaylightSavingsTime();
+ * DateTimeComponents componentsDaylight = new DateTimeComponents(1999, 4, 4, 2, 0, 0, false);
+ * daylight.setDateStart(componentsDaylight);
+ * daylight.setTimezoneOffsetFrom(-5, 0);
+ * daylight.setTimezoneOffsetTo(-4, 0);
+ * timezone.addDaylightSavingsTime(daylight);
+ *
+ * @author Michael Angstadt
+ * @see RFC 5545
+ * p.62-71
+ * @see RFC 2445 p.60-7
+ */
+/*
+ * Note: References to the vCal 1.0 spec are omitted from the property
+ * getter/setter method Javadocs because vCal does not use the VTIMEZONE
+ * component.
+ */
+public class VTimezone extends ICalComponent {
+ /**
+ * Creates a new timezone component.
+ * @param identifier a unique identifier for this timezone (allows it to be
+ * referenced by date-time properties that support timezones).
+ */
+ public VTimezone(String identifier) {
+ setTimezoneId(identifier);
+ }
+
+ /**
+ * Copy constructor.
+ * @param original the component to make a copy of
+ */
+ public VTimezone(VTimezone original) {
+ super(original);
+ }
+
+ /**
+ * Gets the ID for this timezone. This is a required property.
+ * @return the timezone ID or null if not set
+ * @see RFC 5545
+ * p.102-3
+ * @see RFC 2445
+ * p.97-8
+ */
+ public TimezoneId getTimezoneId() {
+ return getProperty(TimezoneId.class);
+ }
+
+ /**
+ * Sets an ID for this timezone. This is a required property.
+ * @param timezoneId the timezone ID or null to remove
+ * @see RFC 5545
+ * p.102-3
+ * @see RFC 2445
+ * p.97-8
+ */
+ public void setTimezoneId(TimezoneId timezoneId) {
+ setProperty(TimezoneId.class, timezoneId);
+ }
+
+ /**
+ * Sets an ID for this timezone. This is a required property.
+ * @param timezoneId the timezone ID or null to remove
+ * @return the property that was created
+ * @see RFC 5545
+ * p.102-3
+ * @see RFC 2445
+ * p.97-8
+ */
+ public TimezoneId setTimezoneId(String timezoneId) {
+ TimezoneId prop = (timezoneId == null) ? null : new TimezoneId(timezoneId);
+ setTimezoneId(prop);
+ return prop;
+ }
+
+ /**
+ * Gets the date-time that the timezone data was last changed.
+ * @return the last modified date or null if not set
+ * @see RFC 5545
+ * p.138
+ * @see RFC 2445
+ * p.131
+ */
+ public LastModified getLastModified() {
+ return getProperty(LastModified.class);
+ }
+
+ /**
+ * Sets the date-time that the timezone data was last changed.
+ * @param lastModified the last modified date or null to remove
+ * @see RFC 5545
+ * p.138
+ * @see RFC 2445
+ * p.131
+ */
+ public void setLastModified(LastModified lastModified) {
+ setProperty(LastModified.class, lastModified);
+ }
+
+ /**
+ * Sets the date-time that the timezone data was last changed.
+ * @param lastModified the last modified date or null to remove
+ * @return the property that was created
+ * @see RFC 5545
+ * p.138
+ * @see RFC 2445
+ * p.131
+ */
+ public LastModified setLastModified(Date lastModified) {
+ LastModified prop = (lastModified == null) ? null : new LastModified(lastModified);
+ setLastModified(prop);
+ return prop;
+ }
+
+ /**
+ * Gets the timezone URL, which points to an iCalendar object that contains
+ * further information on the timezone.
+ * @return the URL or null if not set
+ * @see RFC 5545
+ * p.106
+ * @see RFC 2445
+ * p.101
+ */
+ public TimezoneUrl getTimezoneUrl() {
+ return getProperty(TimezoneUrl.class);
+ }
+
+ /**
+ * Sets the timezone URL, which points to an iCalendar object that contains
+ * further information on the timezone.
+ * @param url the URL or null to remove
+ * @see RFC 5545
+ * p.106
+ * @see RFC 2445
+ * p.101
+ */
+ public void setTimezoneUrl(TimezoneUrl url) {
+ setProperty(TimezoneUrl.class, url);
+ }
+
+ /**
+ * Sets the timezone URL, which points to an iCalendar object that contains
+ * further information on the timezone.
+ * @param url the timezone URL (e.g.
+ * "http://example.com/America-New_York.ics") or null to remove
+ * @return the property that was created
+ * @see RFC 5545
+ * p.106
+ * @see RFC 2445
+ * p.101
+ */
+ public TimezoneUrl setTimezoneUrl(String url) {
+ TimezoneUrl prop = (url == null) ? null : new TimezoneUrl(url);
+ setTimezoneUrl(prop);
+ return prop;
+ }
+
+ /**
+ * Gets the timezone's "standard" observance time ranges.
+ * @return the "standard" observance time ranges (any changes made this list
+ * will affect the parent component object and vice versa)
+ * @see RFC 5545
+ * p.62-71
+ * @see RFC 2445
+ * p.60-7
+ */
+ public List getStandardTimes() {
+ return getComponents(StandardTime.class);
+ }
+
+ /**
+ * Adds a "standard" observance time range.
+ * @param standardTime the "standard" observance time
+ * @see RFC 5545
+ * p.62-71
+ * @see RFC 2445
+ * p.60-7
+ */
+ public void addStandardTime(StandardTime standardTime) {
+ addComponent(standardTime);
+ }
+
+ /**
+ * Gets the timezone's "daylight savings" observance time ranges.
+ * @return the "daylight savings" observance time ranges (any changes made
+ * this list will affect the parent component object and vice versa)
+ * @see RFC 5545
+ * p.62-71
+ * @see RFC 2445
+ * p.60-7
+ */
+ public List getDaylightSavingsTime() {
+ return getComponents(DaylightSavingsTime.class);
+ }
+
+ /**
+ * Adds a "daylight savings" observance time range.
+ * @param daylightSavingsTime the "daylight savings" observance time
+ * @see RFC 5545
+ * p.62-71
+ * @see RFC 2445
+ * p.60-7
+ */
+ public void addDaylightSavingsTime(DaylightSavingsTime daylightSavingsTime) {
+ addComponent(daylightSavingsTime);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ protected void validate(List components, ICalVersion version, List warnings) {
+ if (version == ICalVersion.V1_0) {
+ warnings.add(new ValidationWarning(48, version));
+ }
+
+ checkRequiredCardinality(warnings, TimezoneId.class);
+ checkOptionalCardinality(warnings, LastModified.class, TimezoneUrl.class);
+
+ //STANDARD or DAYLIGHT must be defined
+ if (getStandardTimes().isEmpty() && getDaylightSavingsTime().isEmpty()) {
+ warnings.add(new ValidationWarning(21));
+ }
+ }
+
+ @Override
+ public VTimezone copy() {
+ return new VTimezone(this);
+ }
+}
diff --git a/app/src/main/java/biweekly/component/VTodo.java b/app/src/main/java/biweekly/component/VTodo.java
new file mode 100644
index 0000000000..87ee266259
--- /dev/null
+++ b/app/src/main/java/biweekly/component/VTodo.java
@@ -0,0 +1,1754 @@
+package biweekly.component;
+
+import static biweekly.property.ValuedProperty.getValue;
+
+import java.util.Date;
+import java.util.List;
+import java.util.TimeZone;
+
+import biweekly.ICalVersion;
+import biweekly.ValidationWarning;
+import biweekly.property.Attachment;
+import biweekly.property.Attendee;
+import biweekly.property.Categories;
+import biweekly.property.Classification;
+import biweekly.property.Color;
+import biweekly.property.Comment;
+import biweekly.property.Completed;
+import biweekly.property.Conference;
+import biweekly.property.Contact;
+import biweekly.property.Created;
+import biweekly.property.DateDue;
+import biweekly.property.DateStart;
+import biweekly.property.DateTimeStamp;
+import biweekly.property.Description;
+import biweekly.property.DurationProperty;
+import biweekly.property.ExceptionDates;
+import biweekly.property.ExceptionRule;
+import biweekly.property.Geo;
+import biweekly.property.Image;
+import biweekly.property.LastModified;
+import biweekly.property.Location;
+import biweekly.property.Method;
+import biweekly.property.Organizer;
+import biweekly.property.PercentComplete;
+import biweekly.property.Priority;
+import biweekly.property.RecurrenceDates;
+import biweekly.property.RecurrenceId;
+import biweekly.property.RecurrenceRule;
+import biweekly.property.RelatedTo;
+import biweekly.property.RequestStatus;
+import biweekly.property.Resources;
+import biweekly.property.Sequence;
+import biweekly.property.Status;
+import biweekly.property.Summary;
+import biweekly.property.Uid;
+import biweekly.property.Url;
+import biweekly.util.Duration;
+import biweekly.util.Google2445Utils;
+import biweekly.util.ICalDate;
+import biweekly.util.Period;
+import biweekly.util.Recurrence;
+import biweekly.util.com.google.ical.compat.javautil.DateIterator;
+
+/*
+ Copyright (c) 2013-2023, Michael Angstadt
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+
+ 1. Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+ 2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ *
+ * Defines a task or assignment that needs to be completed at some point in the
+ * future.
+ *
+ *
+ * Examples:
+ *
+ *
+ *
+ * VTodo todo = new VTodo();
+ * todo.setSummary("Complete report");
+ * Date due = ...
+ * todo.setDateDue(due);
+ * todo.setStatus(Status.confirmed());
+ *
+ * @author Michael Angstadt
+ * @see RFC 5545 p.55-7
+ * @see RFC 2445 p.55-6
+ * @see vCal 1.0 p.14
+ */
+public class VTodo extends ICalComponent {
+ /**
+ *
+ * Creates a new to-do task.
+ *
+ *
+ * The following properties are added to the component when it is created:
+ *
+ *
+ * - {@link Uid}: Set to a UUID.
+ * - {@link DateTimeStamp}: Set to the current time.
+ *
+ */
+ public VTodo() {
+ setUid(Uid.random());
+ setDateTimeStamp(new Date());
+ }
+
+ /**
+ * Copy constructor.
+ * @param original the component to make a copy of
+ */
+ public VTodo(VTodo original) {
+ super(original);
+ }
+
+ /**
+ * Gets the unique identifier for this to-do task. This component object
+ * comes populated with a UID on creation. This is a required
+ * property.
+ * @return the UID or null if not set
+ * @see RFC 5545
+ * p.117-8
+ * @see RFC 2445
+ * p.111-2
+ * @see vCal 1.0 p.37
+ */
+ public Uid getUid() {
+ return getProperty(Uid.class);
+ }
+
+ /**
+ * Sets the unique identifier for this to-do task. This component object
+ * comes populated with a UID on creation. This is a required
+ * property.
+ * @param uid the UID or null to remove
+ * @see RFC 5545
+ * p.117-8
+ * @see RFC 2445
+ * p.111-2
+ * @see vCal 1.0 p.37
+ */
+ public void setUid(Uid uid) {
+ setProperty(Uid.class, uid);
+ }
+
+ /**
+ * Sets the unique identifier for this to-do task. This component object
+ * comes populated with a UID on creation. This is a required
+ * property.
+ * @param uid the UID or null to remove
+ * @return the property that was created
+ * @see RFC 5545
+ * p.117-8
+ * @see RFC 2445
+ * p.111-2
+ * @see vCal 1.0 p.37
+ */
+ public Uid setUid(String uid) {
+ Uid prop = (uid == null) ? null : new Uid(uid);
+ setUid(prop);
+ return prop;
+ }
+
+ /**
+ * Gets either (a) the creation date of the iCalendar object (if the
+ * {@link Method} property is defined) or (b) the date that the to-do task
+ * was last modified (the {@link LastModified} property also holds this
+ * information). This to-do object comes populated with a
+ * {@link DateTimeStamp} property that is set to the current time. This is a
+ * required property.
+ * @return the date time stamp or null if not set
+ * @see RFC 5545
+ * p.137-8
+ * @see RFC 2445
+ * p.130-1
+ */
+ public DateTimeStamp getDateTimeStamp() {
+ return getProperty(DateTimeStamp.class);
+ }
+
+ /**
+ * Sets either (a) the creation date of the iCalendar object (if the
+ * {@link Method} property is defined) or (b) the date that the to-do task
+ * was last modified (the {@link LastModified} property also holds this
+ * information). This to-do object comes populated with a
+ * {@link DateTimeStamp} property that is set to the current time. This is a
+ * required property.
+ * @param dateTimeStamp the date time stamp or null to remove
+ * @see RFC 5545
+ * p.137-8
+ * @see RFC 2445
+ * p.130-1
+ */
+ public void setDateTimeStamp(DateTimeStamp dateTimeStamp) {
+ setProperty(DateTimeStamp.class, dateTimeStamp);
+ }
+
+ /**
+ * Sets either (a) the creation date of the iCalendar object (if the
+ * {@link Method} property is defined) or (b) the date that the to-do task
+ * was last modified (the {@link LastModified} property also holds this
+ * information). This to-do object comes populated with a
+ * {@link DateTimeStamp} property that is set to the current time. This is a
+ * required property.
+ * @param dateTimeStamp the date time stamp or null to remove
+ * @return the property that was created
+ * @see RFC 5545
+ * p.137-8
+ * @see RFC 2445
+ * p.130-1
+ */
+ public DateTimeStamp setDateTimeStamp(Date dateTimeStamp) {
+ DateTimeStamp prop = (dateTimeStamp == null) ? null : new DateTimeStamp(dateTimeStamp);
+ setDateTimeStamp(prop);
+ return prop;
+ }
+
+ /**
+ * Gets the level of sensitivity of the to-do data. If not specified, the
+ * data within the to-do task should be considered "public".
+ * @return the classification level or null if not set
+ * @see RFC 5545
+ * p.82-3
+ * @see RFC 2445
+ * p.79-80
+ * @see vCal 1.0 p.28-9
+ */
+ public Classification getClassification() {
+ return getProperty(Classification.class);
+ }
+
+ /**
+ * Sets the level of sensitivity of the to-do data. If not specified, the
+ * data within the to-do task should be considered "public".
+ * @param classification the classification level or null to remove
+ * @see RFC 5545
+ * p.82-3
+ * @see RFC 2445
+ * p.79-80
+ * @see vCal 1.0 p.28-9
+ */
+ public void setClassification(Classification classification) {
+ setProperty(Classification.class, classification);
+ }
+
+ /**
+ * Sets the level of sensitivity of the to-do data. If not specified, the
+ * data within the to-do task should be considered "public".
+ * @param classification the classification level (e.g. "CONFIDENTIAL") or
+ * null to remove
+ * @return the property that was created
+ * @see RFC 5545
+ * p.82-3
+ * @see RFC 2445
+ * p.79-80
+ * @see vCal 1.0 p.28-9
+ */
+ public Classification setClassification(String classification) {
+ Classification prop = (classification == null) ? null : new Classification(classification);
+ setClassification(prop);
+ return prop;
+ }
+
+ /**
+ * Gets the date and time that the to-do task was completed.
+ * @return the completion date or null if not set
+ * @see RFC 5545
+ * p.94-5
+ * @see RFC 2445
+ * p.90-1
+ * @see vCal 1.0 p.29
+ */
+ public Completed getCompleted() {
+ return getProperty(Completed.class);
+ }
+
+ /**
+ * Sets the date and time that the to-do task was completed.
+ * @param completed the completion date or null to remove
+ * @see RFC 5545
+ * p.94-5
+ * @see RFC 2445
+ * p.90-1
+ * @see vCal 1.0 p.29
+ */
+ public void setCompleted(Completed completed) {
+ setProperty(Completed.class, completed);
+ }
+
+ /**
+ * Sets the date and time that the to-do task was completed.
+ * @param completed the completion date or null to remove
+ * @return the property that was created
+ * @see RFC 5545
+ * p.94-5
+ * @see RFC 2445
+ * p.90-1
+ * @see vCal 1.0 p.29
+ */
+ public Completed setCompleted(Date completed) {
+ Completed prop = (completed == null) ? null : new Completed(completed);
+ setCompleted(prop);
+ return prop;
+ }
+
+ /**
+ * Gets the date-time that the to-do task was initially created.
+ * @return the creation date-time or null if not set
+ * @see RFC 5545
+ * p.136
+ * @see RFC 2445
+ * p.129-30
+ * @see vCal 1.0 p.29
+ */
+ public Created getCreated() {
+ return getProperty(Created.class);
+ }
+
+ /**
+ * Sets the date-time that the to-do task was initially created.
+ * @param created the creation date-time or null to remove
+ * @see RFC 5545
+ * p.136
+ * @see RFC 2445
+ * p.129-30
+ * @see vCal 1.0 p.29
+ */
+ public void setCreated(Created created) {
+ setProperty(Created.class, created);
+ }
+
+ /**
+ * Sets the date-time that the to-do task was initially created.
+ * @param created the creation date-time or null to remove
+ * @return the property that was created
+ * @see RFC 5545
+ * p.136
+ * @see RFC 2445
+ * p.129-30
+ * @see vCal 1.0 p.29
+ */
+ public Created setCreated(Date created) {
+ Created prop = (created == null) ? null : new Created(created);
+ setCreated(prop);
+ return prop;
+ }
+
+ /**
+ * Gets a detailed description of the to-do task. The description should be
+ * more detailed than the one provided by the {@link Summary} property.
+ * @return the description or null if not set
+ * @see RFC 5545
+ * p.84-5
+ * @see RFC 2445
+ * p.81-2
+ * @see vCal 1.0 p.30
+ */
+ public Description getDescription() {
+ return getProperty(Description.class);
+ }
+
+ /**
+ * Sets a detailed description of the to-do task. The description should be
+ * more detailed than the one provided by the {@link Summary} property.
+ * @param description the description or null to remove
+ * @see RFC 5545
+ * p.84-5
+ * @see RFC 2445
+ * p.81-2
+ * @see vCal 1.0 p.30
+ */
+ public void setDescription(Description description) {
+ setProperty(Description.class, description);
+ }
+
+ /**
+ * Sets a detailed description of the to-do task. The description should be
+ * more detailed than the one provided by the {@link Summary} property.
+ * @param description the description or null to remove
+ * @return the property that was created
+ * @see RFC 5545
+ * p.84-5
+ * @see RFC 2445
+ * p.81-2
+ * @see vCal 1.0 p.30
+ */
+ public Description setDescription(String description) {
+ Description prop = (description == null) ? null : new Description(description);
+ setDescription(prop);
+ return prop;
+ }
+
+ /**
+ * Gets the date that the to-do task starts.
+ * @return the start date or null if not set
+ * @see RFC 5545
+ * p.97-8
+ * @see RFC 2445
+ * p.93-4
+ * @see vCal 1.0 p.35
+ */
+ public DateStart getDateStart() {
+ return getProperty(DateStart.class);
+ }
+
+ /**
+ * Sets the date that the to-do task starts.
+ * @param dateStart the start date or null to remove
+ * @see RFC 5545
+ * p.97-8
+ * @see RFC 2445
+ * p.93-4
+ * @see vCal 1.0 p.35
+ */
+ public void setDateStart(DateStart dateStart) {
+ setProperty(DateStart.class, dateStart);
+ }
+
+ /**
+ * Sets the date that the to-do task starts.
+ * @param dateStart the start date or null to remove
+ * @return the property that was created
+ * @see RFC 5545
+ * p.97-8
+ * @see RFC 2445
+ * p.93-4
+ * @see vCal 1.0 p.35
+ */
+ public DateStart setDateStart(Date dateStart) {
+ return setDateStart(dateStart, true);
+ }
+
+ /**
+ * Sets the date that the to-do task starts.
+ * @param dateStart the start date or null to remove
+ * @param hasTime true if the date has a time component, false if it is
+ * strictly a date (if false, the given Date object should be created by a
+ * {@link java.util.Calendar Calendar} object that uses the JVM's default
+ * timezone)
+ * @return the property that was created
+ * @see RFC 5545
+ * p.97-8
+ * @see RFC 2445
+ * p.93-4
+ * @see vCal 1.0 p.35
+ */
+ public DateStart setDateStart(Date dateStart, boolean hasTime) {
+ DateStart prop = (dateStart == null) ? null : new DateStart(dateStart, hasTime);
+ setDateStart(prop);
+ return prop;
+ }
+
+ /**
+ * Gets a set of geographical coordinates.
+ * @return the geographical coordinates or null if not set
+ * @see RFC 5545
+ * p.85-7
+ * @see RFC 2445
+ * p.82-3
+ */
+ //Note: vCal 1.0 spec is omitted because GEO is not used in vCal 1.0 to-dos
+ public Geo getGeo() {
+ return getProperty(Geo.class);
+ }
+
+ /**
+ * Sets a set of geographical coordinates.
+ * @param geo the geographical coordinates or null to remove
+ * @see RFC 5545
+ * p.85-7
+ * @see RFC 2445
+ * p.82-3
+ */
+ //Note: vCal 1.0 spec is omitted because GEO is not used in vCal 1.0 to-dos
+ public void setGeo(Geo geo) {
+ setProperty(Geo.class, geo);
+ }
+
+ /**
+ * Gets the date-time that the to-do task was last changed.
+ * @return the last modified date or null if not set
+ * @see RFC 5545
+ * p.138
+ * @see RFC 2445
+ * p.131
+ * @see vCal 1.0 p.31
+ */
+ public LastModified getLastModified() {
+ return getProperty(LastModified.class);
+ }
+
+ /**
+ * Sets the date-time that the to-do task was last changed.
+ * @param lastModified the last modified date or null to remove
+ * @see RFC 5545
+ * p.138
+ * @see RFC 2445
+ * p.131
+ * @see vCal 1.0 p.31
+ */
+ public void setLastModified(LastModified lastModified) {
+ setProperty(LastModified.class, lastModified);
+ }
+
+ /**
+ * Sets the date-time that the to-do task was last changed.
+ * @param lastModified the last modified date or null to remove
+ * @return the property that was created
+ * @see RFC 5545
+ * p.138
+ * @see RFC 2445
+ * p.131
+ * @see vCal 1.0 p.31
+ */
+ public LastModified setLastModified(Date lastModified) {
+ LastModified prop = (lastModified == null) ? null : new LastModified(lastModified);
+ setLastModified(prop);
+ return prop;
+ }
+
+ /**
+ * Gets the physical location of the to-do task.
+ * @return the location or null if not set
+ * @see RFC 5545
+ * p.87-8
+ * @see RFC 2445
+ * p.84
+ * @see vCal 1.0 p.32
+ */
+ public Location getLocation() {
+ return getProperty(Location.class);
+ }
+
+ /**
+ * Sets the physical location of the to-do task.
+ * @param location the location or null to remove
+ * @see RFC 5545
+ * p.87-8
+ * @see RFC 2445
+ * p.84
+ * @see vCal 1.0 p.32
+ */
+ public void setLocation(Location location) {
+ setProperty(Location.class, location);
+ }
+
+ /**
+ * Sets the physical location of the to-do task.
+ * @param location the location (e.g. "Room 101") or null to remove
+ * @return the property that was created
+ * @see RFC 5545
+ * p.87-8
+ * @see RFC 2445
+ * p.84
+ * @see vCal 1.0 p.32
+ */
+ public Location setLocation(String location) {
+ Location prop = (location == null) ? null : new Location(location);
+ setLocation(prop);
+ return prop;
+ }
+
+ /**
+ * Gets the organizer of the to-do task.
+ * @return the organizer or null if not set
+ * @see RFC 5545
+ * p.111-2
+ * @see RFC 2445
+ * p.106-7
+ */
+ public Organizer getOrganizer() {
+ return getProperty(Organizer.class);
+ }
+
+ /**
+ * Sets the organizer of the to-do task.
+ * @param organizer the organizer or null to remove
+ * @see RFC 5545
+ * p.111-2
+ * @see RFC 2445
+ * p.106-7
+ */
+ public void setOrganizer(Organizer organizer) {
+ setProperty(Organizer.class, organizer);
+ }
+
+ /**
+ * Sets the organizer of the to-do task.
+ * @param email the organizer's email address (e.g. "johndoe@example.com")
+ * or null to remove
+ * @return the property that was created
+ * @see RFC 5545
+ * p.111-2
+ * @see RFC 2445
+ * p.106-7
+ */
+ public Organizer setOrganizer(String email) {
+ Organizer prop = (email == null) ? null : new Organizer(null, email);
+ setOrganizer(prop);
+ return prop;
+ }
+
+ /**
+ * Gets the amount that the to-do task has been completed.
+ * @return the percent complete or null if not set
+ * @see RFC 5545
+ * p.88-9
+ * @see RFC 2445
+ * p.85
+ */
+ public PercentComplete getPercentComplete() {
+ return getProperty(PercentComplete.class);
+ }
+
+ /**
+ * Sets the amount that the to-do task has been completed.
+ * @param percentComplete the percent complete or null to remove
+ * @see RFC 5545
+ * p.88-9
+ * @see RFC 2445
+ * p.85
+ */
+ public void setPercentComplete(PercentComplete percentComplete) {
+ setProperty(PercentComplete.class, percentComplete);
+ }
+
+ /**
+ * Sets the amount that the to-do task has been completed.
+ * @param percent the percent complete (e.g. "50" for 50%) or null to remove
+ * @return the property that was created
+ * @see RFC 5545
+ * p.88-9
+ * @see RFC 2445
+ * p.85
+ */
+ public PercentComplete setPercentComplete(Integer percent) {
+ PercentComplete prop = (percent == null) ? null : new PercentComplete(percent);
+ setPercentComplete(prop);
+ return prop;
+ }
+
+ /**
+ * Gets the priority of the to-do task.
+ * @return the priority or null if not set
+ * @see RFC 5545
+ * p.89-90
+ * @see RFC 2445
+ * p.85-7
+ * @see vCal 1.0 p.33
+ */
+ public Priority getPriority() {
+ return getProperty(Priority.class);
+ }
+
+ /**
+ * Sets the priority of the to-do task.
+ * @param priority the priority or null to remove
+ * @see RFC 5545
+ * p.89-90
+ * @see RFC 2445
+ * p.85-7
+ * @see vCal 1.0 p.33
+ */
+ public void setPriority(Priority priority) {
+ setProperty(Priority.class, priority);
+ }
+
+ /**
+ * Sets the priority of the to-do task.
+ * @param priority the priority ("0" is undefined, "1" is the highest, "9"
+ * is the lowest) or null to remove
+ * @return the property that was created
+ * @see RFC 5545
+ * p.89-90
+ * @see RFC 2445
+ * p.85-7
+ * @see vCal 1.0 p.33
+ */
+ public Priority setPriority(Integer priority) {
+ Priority prop = (priority == null) ? null : new Priority(priority);
+ setPriority(prop);
+ return prop;
+ }
+
+ /**
+ * Gets the original value of the {@link DateStart} property if the to-do
+ * task is recurring and has been modified. Used in conjunction with the
+ * {@link Uid} and {@link Sequence} properties to uniquely identify a
+ * recurrence instance.
+ * @return the recurrence ID or null if not set
+ * @see RFC 5545
+ * p.112-4
+ * @see RFC 2445
+ * p.107-9
+ */
+ public RecurrenceId getRecurrenceId() {
+ return getProperty(RecurrenceId.class);
+ }
+
+ /**
+ * Sets the original value of the {@link DateStart} property if the to-do
+ * task is recurring and has been modified. Used in conjunction with the
+ * {@link Uid} and {@link Sequence} properties to uniquely identify a
+ * recurrence instance.
+ * @param recurrenceId the recurrence ID or null to remove
+ * @see RFC 5545
+ * p.112-4
+ * @see RFC 2445
+ * p.107-9
+ */
+ public void setRecurrenceId(RecurrenceId recurrenceId) {
+ setProperty(RecurrenceId.class, recurrenceId);
+ }
+
+ /**
+ * Sets the original value of the {@link DateStart} property if the to-do
+ * task is recurring and has been modified. Used in conjunction with the
+ * {@link Uid} and {@link Sequence} properties to uniquely identify a
+ * recurrence instance.
+ * @param originalStartDate the original start date or null to remove
+ * @return the property that was created
+ * @see RFC 5545
+ * p.112-4
+ * @see RFC 2445
+ * p.107-9
+ */
+ public RecurrenceId setRecurrenceId(Date originalStartDate) {
+ RecurrenceId prop = (originalStartDate == null) ? null : new RecurrenceId(originalStartDate);
+ setRecurrenceId(prop);
+ return prop;
+ }
+
+ /**
+ * Gets the revision number of the to-do task. The organizer can increment
+ * this number every time he or she makes a significant change.
+ * @return the sequence number
+ * @see RFC 5545
+ * p.138-9
+ * @see RFC 2445
+ * p.131-3
+ * @see vCal 1.0 p.35
+ */
+ public Sequence getSequence() {
+ return getProperty(Sequence.class);
+ }
+
+ /**
+ * Sets the revision number of the to-do task. The organizer can increment
+ * this number every time he or she makes a significant change.
+ * @param sequence the sequence number
+ * @see RFC 5545
+ * p.138-9
+ * @see RFC 2445
+ * p.131-3
+ * @see vCal 1.0 p.35
+ */
+ public void setSequence(Sequence sequence) {
+ setProperty(Sequence.class, sequence);
+ }
+
+ /**
+ * Sets the revision number of the to-do task. The organizer can increment
+ * this number every time he or she makes a significant change.
+ * @param sequence the sequence number
+ * @return the property that was created
+ * @see RFC 5545
+ * p.138-9
+ * @see RFC 2445
+ * p.131-3
+ * @see vCal 1.0 p.35
+ */
+ public Sequence setSequence(Integer sequence) {
+ Sequence prop = (sequence == null) ? null : new Sequence(sequence);
+ setSequence(prop);
+ return prop;
+ }
+
+ /**
+ * Increments the revision number of the to-do task. The organizer can
+ * increment this number every time he or she makes a significant change.
+ * @see RFC 5545
+ * p.138-9
+ * @see RFC 2445
+ * p.131-3
+ * @see vCal 1.0 p.35
+ */
+ public void incrementSequence() {
+ Sequence sequence = getSequence();
+ if (sequence == null) {
+ setSequence(1);
+ } else {
+ sequence.increment();
+ }
+ }
+
+ /**
+ * Gets the status of the to-do task.
+ * @return the status or null if not set
+ * @see RFC 5545
+ * p.92-3
+ * @see RFC 2445
+ * p.88-9
+ * @see vCal 1.0 p.35-6
+ */
+ public Status getStatus() {
+ return getProperty(Status.class);
+ }
+
+ /**
+ *
+ * Sets the status of the to-do task.
+ *
+ *
+ * Valid status codes are:
+ *
+ *
+ * - NEEDS-ACTION
+ * - COMPLETED
+ * - IN-PROGRESS
+ * - CANCELLED
+ *
+ * @param status the status or null to remove
+ * @see RFC 5545
+ * p.92-3
+ * @see RFC 2445
+ * p.88-9
+ * @see vCal 1.0 p.35-6
+ */
+ public void setStatus(Status status) {
+ setProperty(Status.class, status);
+ }
+
+ /**
+ * Gets the summary of the to-do task.
+ * @return the summary or null if not set
+ * @see RFC 5545
+ * p.93-4
+ * @see RFC 2445
+ * p.89-90
+ * @see vCal 1.0 p.36
+ */
+ public Summary getSummary() {
+ return getProperty(Summary.class);
+ }
+
+ /**
+ * Sets the summary of the to-do task.
+ * @param summary the summary or null to remove
+ * @see RFC 5545
+ * p.93-4
+ * @see RFC 2445
+ * p.89-90
+ * @see vCal 1.0 p.36
+ */
+ public void setSummary(Summary summary) {
+ setProperty(Summary.class, summary);
+ }
+
+ /**
+ * Sets the summary of the to-do task.
+ * @param summary the summary or null to remove
+ * @return the property that was created
+ * @see RFC 5545
+ * p.93-4
+ * @see RFC 2445
+ * p.89-90
+ * @see vCal 1.0 p.36
+ */
+ public Summary setSummary(String summary) {
+ Summary prop = (summary == null) ? null : new Summary(summary);
+ setSummary(prop);
+ return prop;
+ }
+
+ /**
+ * Gets a URL to a resource that contains additional information about the
+ * to-do task.
+ * @return the URL or null if not set
+ * @see RFC 5545
+ * p.116-7
+ * @see RFC 2445
+ * p.110-1
+ * @see vCal 1.0 p.37
+ */
+ public Url getUrl() {
+ return getProperty(Url.class);
+ }
+
+ /**
+ * Sets a URL to a resource that contains additional information about the
+ * to-do task.
+ * @param url the URL or null to remove
+ * @see RFC 5545
+ * p.116-7
+ * @see RFC 2445
+ * p.110-1
+ * @see vCal 1.0 p.37
+ */
+ public void setUrl(Url url) {
+ setProperty(Url.class, url);
+ }
+
+ /**
+ * Sets a URL to a resource that contains additional information about the
+ * to-do task.
+ * @param url the URL (e.g. "http://example.com/resource.ics") or null to
+ * remove
+ * @return the property that was created
+ * @see RFC 5545
+ * p.116-7
+ * @see RFC 2445
+ * p.110-1
+ * @see vCal 1.0 p.37
+ */
+ public Url setUrl(String url) {
+ Url prop = (url == null) ? null : new Url(url);
+ setUrl(prop);
+ return prop;
+ }
+
+ /**
+ * Gets how often the to-do task repeats.
+ * @return the recurrence rule or null if not set
+ * @see RFC 5545
+ * p.122-32
+ * @see RFC 2445
+ * p.117-25
+ * @see vCal 1.0 p.34
+ */
+ public RecurrenceRule getRecurrenceRule() {
+ return getProperty(RecurrenceRule.class);
+ }
+
+ /**
+ * Sets how often the to-do task repeats.
+ * @param recur the recurrence rule or null to remove
+ * @return the property that was created
+ * @see RFC 5545
+ * p.122-32
+ * @see RFC 2445
+ * p.117-25
+ * @see vCal 1.0 p.34
+ */
+ public RecurrenceRule setRecurrenceRule(Recurrence recur) {
+ RecurrenceRule prop = (recur == null) ? null : new RecurrenceRule(recur);
+ setRecurrenceRule(prop);
+ return prop;
+ }
+
+ /**
+ * Sets how often the to-do task repeats.
+ * @param recurrenceRule the recurrence rule or null to remove
+ * @see RFC 5545
+ * p.122-32
+ * @see RFC 2445
+ * p.117-25
+ * @see vCal 1.0 p.34
+ */
+ public void setRecurrenceRule(RecurrenceRule recurrenceRule) {
+ setProperty(RecurrenceRule.class, recurrenceRule);
+ }
+
+ /**
+ * Gets the date that a to-do task is due by.
+ * @return the due date or null if not set
+ * @see RFC 5545
+ * p.96-7
+ * @see RFC 2445
+ * p.92-3
+ * @see vCal 1.0 p.30
+ */
+ public DateDue getDateDue() {
+ return getProperty(DateDue.class);
+ }
+
+ /**
+ * Sets the date that a to-do task is due by. This must NOT be set if a
+ * {@link DurationProperty} is defined.
+ * @param dateDue the due date or null to remove
+ * @see RFC 5545
+ * p.96-7
+ * @see RFC 2445
+ * p.92-3
+ * @see vCal 1.0 p.30
+ */
+ public void setDateDue(DateDue dateDue) {
+ setProperty(DateDue.class, dateDue);
+ }
+
+ /**
+ * Sets the date that a to-do task is due by. This must NOT be set if a
+ * {@link DurationProperty} is defined.
+ * @param dateDue the due date or null to remove
+ * @return the property that was created
+ * @see RFC 5545
+ * p.96-7
+ * @see RFC 2445
+ * p.92-3
+ * @see vCal 1.0 p.30
+ */
+ public DateDue setDateDue(Date dateDue) {
+ return setDateDue(dateDue, true);
+ }
+
+ /**
+ * Sets the date that a to-do task is due by. This must NOT be set if a
+ * {@link DurationProperty} is defined.
+ * @param dateDue the due date or null to remove
+ * @param hasTime true if the date has a time component, false if it is
+ * strictly a date (if false, the given Date object should be created by a
+ * {@link java.util.Calendar Calendar} object that uses the JVM's default
+ * timezone)
+ * @return the property that was created
+ * @see RFC 5545
+ * p.96-7
+ * @see RFC 2445
+ * p.92-3
+ * @see vCal 1.0 p.30
+ */
+ public DateDue setDateDue(Date dateDue, boolean hasTime) {
+ DateDue prop = (dateDue == null) ? null : new DateDue(dateDue, hasTime);
+ setDateDue(prop);
+ return prop;
+ }
+
+ /**
+ * Gets the duration of the to-do task.
+ * @return the duration or null if not set
+ * @see RFC 5545
+ * p.99
+ * @see RFC 2445
+ * p.94-5
+ */
+ public DurationProperty getDuration() {
+ return getProperty(DurationProperty.class);
+ }
+
+ /**
+ * Sets the duration of the to-do task. This must NOT be set if a
+ * {@link DateDue} is defined.
+ * @param duration the duration or null to remove
+ * @see RFC 5545
+ * p.99
+ * @see RFC 2445
+ * p.94-5
+ */
+ public void setDuration(DurationProperty duration) {
+ setProperty(DurationProperty.class, duration);
+ }
+
+ /**
+ * Sets the duration of the to-do task. This must NOT be set if a
+ * {@link DateDue} is defined.
+ * @param duration the duration or null to remove
+ * @return the property that was created
+ * @see RFC 5545
+ * p.99
+ * @see RFC 2445
+ * p.94-5
+ */
+ public DurationProperty setDuration(Duration duration) {
+ DurationProperty prop = (duration == null) ? null : new DurationProperty(duration);
+ setDuration(prop);
+ return prop;
+ }
+
+ /**
+ * Gets any attachments that are associated with the to-do task.
+ * @return the attachments (any changes made this list will affect the
+ * parent component object and vice versa)
+ * @see RFC 5545
+ * p.80-1
+ * @see RFC 2445
+ * p.77-8
+ * @see vCal 1.0 p.25
+ */
+ public List getAttachments() {
+ return getProperties(Attachment.class);
+ }
+
+ /**
+ * Adds an attachment to the to-do task.
+ * @param attachment the attachment to add
+ * @see RFC 5545
+ * p.80-1
+ * @see RFC 2445
+ * p.77-8
+ * @see vCal 1.0 p.25
+ */
+ public void addAttachment(Attachment attachment) {
+ addProperty(attachment);
+ }
+
+ /**
+ * Gets the people who are involved in the to-do task.
+ * @return the attendees (any changes made this list will affect the parent
+ * component object and vice versa)
+ * @see RFC 5545
+ * p.107-9
+ * @see RFC 2445
+ * p.102-4
+ * @see vCal 1.0 p.25-7
+ */
+ public List getAttendees() {
+ return getProperties(Attendee.class);
+ }
+
+ /**
+ * Adds a person who is involved in the to-do task.
+ * @param attendee the attendee
+ * @see RFC 5545
+ * p.107-9
+ * @see RFC 2445
+ * p.102-4
+ * @see vCal 1.0 p.25-7
+ */
+ public void addAttendee(Attendee attendee) {
+ addProperty(attendee);
+ }
+
+ /**
+ * Adds a person who is involved in the to-do task.
+ * @param email the attendee's email address
+ * @return the property that was created
+ * @see RFC 5545
+ * p.107-9
+ * @see RFC 2445
+ * p.102-4
+ * @see vCal 1.0 p.25-7
+ */
+ public Attendee addAttendee(String email) {
+ Attendee prop = new Attendee(null, email, null);
+ addAttendee(prop);
+ return prop;
+ }
+
+ /**
+ * Gets a list of "tags" or "keywords" that describe the to-do task.
+ * @return the categories (any changes made this list will affect the parent
+ * component object and vice versa)
+ * @see RFC 5545
+ * p.81-2
+ * @see RFC 2445
+ * p.78-9
+ * @see vCal 1.0 p.28
+ */
+ public List getCategories() {
+ return getProperties(Categories.class);
+ }
+
+ /**
+ * Adds a list of "tags" or "keywords" that describe the to-do task. Note
+ * that a single property can hold multiple keywords.
+ * @param categories the categories to add
+ * @see RFC 5545
+ * p.81-2
+ * @see RFC 2445
+ * p.78-9
+ * @see vCal 1.0 p.28
+ */
+ public void addCategories(Categories categories) {
+ addProperty(categories);
+ }
+
+ /**
+ * Adds a list of "tags" or "keywords" that describe the to-do task.
+ * @param categories the categories to add
+ * @return the property that was created
+ * @see RFC 5545
+ * p.81-2
+ * @see RFC 2445
+ * p.78-9
+ * @see vCal 1.0 p.28
+ */
+ public Categories addCategories(String... categories) {
+ Categories prop = new Categories(categories);
+ addCategories(prop);
+ return prop;
+ }
+
+ /**
+ * Adds a list of "tags" or "keywords" that describe the to-do task.
+ * @param categories the categories to add
+ * @return the property that was created
+ * @see RFC 5545
+ * p.81-2
+ * @see RFC 2445
+ * p.78-9
+ * @see vCal 1.0 p.28
+ */
+ public Categories addCategories(List categories) {
+ Categories prop = new Categories(categories);
+ addCategories(prop);
+ return prop;
+ }
+
+ /**
+ * Gets the comments attached to the to-do task.
+ * @return the comments (any changes made this list will affect the parent
+ * component object and vice versa)
+ * @see RFC 5545
+ * p.83-4
+ * @see RFC 2445
+ * p.80-1
+ */
+ public List getComments() {
+ return getProperties(Comment.class);
+ }
+
+ /**
+ * Adds a comment to the to-do task.
+ * @param comment the comment to add
+ * @see RFC 5545
+ * p.83-4
+ * @see RFC 2445
+ * p.80-1
+ */
+ public void addComment(Comment comment) {
+ addProperty(comment);
+ }
+
+ /**
+ * Adds a comment to the to-do task.
+ * @param comment the comment to add
+ * @return the property that was created
+ * @see RFC 5545
+ * p.83-4
+ * @see RFC 2445
+ * p.80-1
+ */
+ public Comment addComment(String comment) {
+ Comment prop = new Comment(comment);
+ addComment(prop);
+ return prop;
+ }
+
+ /**
+ * Gets the contacts associated with the to-do task.
+ * @return the contacts (any changes made this list will affect the parent
+ * component object and vice versa)
+ * @see RFC 5545
+ * p.109-11
+ * @see RFC 2445
+ * p.104-6
+ */
+ public List getContacts() {
+ return getProperties(Contact.class);
+ }
+
+ /**
+ * Adds a contact to the to-do task.
+ * @param contact the contact
+ * @see RFC 5545
+ * p.109-11
+ * @see RFC 2445
+ * p.104-6
+ */
+ public void addContact(Contact contact) {
+ addProperty(contact);
+ }
+
+ /**
+ * Adds a contact to the to-do task.
+ * @param contact the contact (e.g. "ACME Co - (123) 555-1234")
+ * @return the property that was created
+ * @see RFC 5545
+ * p.109-11
+ * @see RFC 2445
+ * p.104-6
+ */
+ public Contact addContact(String contact) {
+ Contact prop = new Contact(contact);
+ addContact(prop);
+ return prop;
+ }
+
+ /**
+ * Gets the list of exceptions to the recurrence rule defined in the to-do
+ * task (if one is defined).
+ * @return the list of exceptions (any changes made this list will affect
+ * the parent component object and vice versa)
+ * @see RFC 5545
+ * p.118-20
+ * @see RFC 2445
+ * p.112-4
+ * @see vCal 1.0 p.31
+ */
+ public List getExceptionDates() {
+ return getProperties(ExceptionDates.class);
+ }
+
+ /**
+ * Adds a list of exceptions to the recurrence rule defined in the to-do
+ * task (if one is defined). Note that this property can contain multiple
+ * dates.
+ * @param exceptionDates the list of exceptions
+ * @see RFC 5545
+ * p.118-20
+ * @see RFC 2445
+ * p.112-4
+ * @see vCal 1.0 p.31
+ */
+ public void addExceptionDates(ExceptionDates exceptionDates) {
+ addProperty(exceptionDates);
+ }
+
+ /**
+ * Gets the response to a scheduling request.
+ * @return the response
+ * @see RFC 5546
+ * Section 3.6
+ * @see RFC 5545
+ * p.141-3
+ * @see RFC 2445
+ * p.134-6
+ */
+ public RequestStatus getRequestStatus() {
+ return getProperty(RequestStatus.class);
+ }
+
+ /**
+ * Sets the response to a scheduling request.
+ * @param requestStatus the response
+ * @see RFC 5546
+ * Section 3.6
+ * @see RFC 5545
+ * p.141-3
+ * @see RFC 2445
+ * p.134-6
+ */
+ public void setRequestStatus(RequestStatus requestStatus) {
+ setProperty(RequestStatus.class, requestStatus);
+ }
+
+ /**
+ * Gets the components that the to-do task is related to.
+ * @return the relationships (any changes made this list will affect the
+ * parent component object and vice versa)
+ * @see RFC 5545
+ * p.115-6
+ * @see RFC 2445
+ * p.109-10
+ * @see vCal 1.0 p.33-4
+ */
+ public List getRelatedTo() {
+ return getProperties(RelatedTo.class);
+ }
+
+ /**
+ * Adds a component that the to-do task is related to.
+ * @param relatedTo the relationship
+ * @see RFC 5545
+ * p.115-6
+ * @see RFC 2445
+ * p.109-10
+ * @see vCal 1.0 p.33-4
+ */
+ public void addRelatedTo(RelatedTo relatedTo) {
+ //TODO create a method that accepts a component and make the RelatedTo property invisible to the user
+ //@formatter:off
+ /*
+ * addRelation(RelationshipType relType, ICalComponent component) {
+ * RelatedTo prop = new RelatedTo(component.getUid().getValue());
+ * prop.setRelationshipType(relType);
+ * addProperty(prop);
+ * }
+ */
+ //@formatter:on
+ addProperty(relatedTo);
+ }
+
+ /**
+ * Adds a component that the to-do task is related to.
+ * @param uid the UID of the other component
+ * @return the property that was created
+ * @see RFC 5545
+ * p.115-6
+ * @see RFC 2445
+ * p.109-10
+ * @see vCal 1.0 p.33-4
+ */
+ public RelatedTo addRelatedTo(String uid) {
+ RelatedTo prop = new RelatedTo(uid);
+ addRelatedTo(prop);
+ return prop;
+ }
+
+ /**
+ * Gets the resources that are needed for the to-do task.
+ * @return the resources (any changes made this list will affect the parent
+ * component object and vice versa)
+ * @see RFC 5545
+ * p.91
+ * @see RFC 2445
+ * p.87-8
+ * @see vCal 1.0 p.34-5
+ */
+ public List getResources() {
+ return getProperties(Resources.class);
+ }
+
+ /**
+ * Adds a list of resources that are needed for the to-do task. Note that a
+ * single property can hold multiple resources.
+ * @param resources the resources to add
+ * @see RFC 5545
+ * p.91
+ * @see RFC 2445
+ * p.87-8
+ * @see vCal 1.0 p.34-5
+ */
+ public void addResources(Resources resources) {
+ addProperty(resources);
+ }
+
+ /**
+ * Adds a list of resources that are needed for the to-do task.
+ * @param resources the resources to add (e.g. "easel", "projector")
+ * @return the property that was created
+ * @see RFC 5545
+ * p.91
+ * @see RFC 2445
+ * p.87-8
+ * @see vCal 1.0 p.34-5
+ */
+ public Resources addResources(String... resources) {
+ Resources prop = new Resources(resources);
+ addResources(prop);
+ return prop;
+ }
+
+ /**
+ * Adds a list of resources that are needed for the to-do task.
+ * @param resources the resources to add (e.g. "easel", "projector")
+ * @return the property that was created
+ * @see RFC 5545
+ * p.91
+ * @see RFC 2445
+ * p.87-8
+ * @see vCal 1.0 p.34-5
+ */
+ public Resources addResources(List resources) {
+ Resources prop = new Resources(resources);
+ addResources(prop);
+ return prop;
+ }
+
+ /**
+ * Gets the list of dates/periods that help define the recurrence rule of
+ * this to-do task (if one is defined).
+ * @return the recurrence dates (any changes made this list will affect the
+ * parent component object and vice versa)
+ * @see RFC 5545
+ * p.120-2
+ * @see RFC 2445
+ * p.115-7
+ * @see vCal 1.0 p.34
+ */
+ public List getRecurrenceDates() {
+ return getProperties(RecurrenceDates.class);
+ }
+
+ /**
+ * Adds a list of dates/periods that help define the recurrence rule of this
+ * to-do task (if one is defined).
+ * @param recurrenceDates the recurrence dates
+ * @see RFC 5545
+ * p.120-2
+ * @see RFC 2445
+ * p.115-7
+ * @see vCal 1.0 p.34
+ */
+ public void addRecurrenceDates(RecurrenceDates recurrenceDates) {
+ addProperty(recurrenceDates);
+ }
+
+ /**
+ * Gets the alarms that are assigned to this to-do task.
+ * @return the alarms (any changes made this list will affect the parent
+ * component object and vice versa)
+ * @see RFC 5545
+ * p.71-6
+ * @see RFC 2445
+ * p.67-73
+ */
+ public List getAlarms() {
+ return getComponents(VAlarm.class);
+ }
+
+ /**
+ * Adds an alarm to this to-do task.
+ * @param alarm the alarm
+ * @see RFC 5545
+ * p.71-6
+ * @see RFC 2445
+ * p.67-73
+ */
+ public void addAlarm(VAlarm alarm) {
+ addComponent(alarm);
+ }
+
+ /**
+ *
+ * Gets the exceptions for the {@link RecurrenceRule} property.
+ *
+ *
+ * Note that this property has been removed from the latest version of the
+ * iCal specification. Its use should be avoided.
+ *
+ * @return the exception rules (any changes made this list will affect the
+ * parent component object and vice versa)
+ * @see RFC 2445
+ * p.114-15
+ * @see vCal 1.0 p.31
+ */
+ public List getExceptionRules() {
+ return getProperties(ExceptionRule.class);
+ }
+
+ /**
+ *
+ * Adds an exception for the {@link RecurrenceRule} property.
+ *
+ *
+ * Note that this property has been removed from the latest version of the
+ * iCal specification. Its use should be avoided.
+ *
+ * @param recur the exception rule to add
+ * @return the property that was created
+ * @see RFC 2445
+ * p.114-15
+ * @see vCal 1.0 p.31
+ */
+ public ExceptionRule addExceptionRule(Recurrence recur) {
+ ExceptionRule prop = new ExceptionRule(recur);
+ addExceptionRule(prop);
+ return prop;
+ }
+
+ /**
+ *
+ * Adds an exception for the {@link RecurrenceRule} property.
+ *
+ *
+ * Note that this property has been removed from the latest version of the
+ * iCal specification. Its use should be avoided.
+ *
+ * @param exceptionRule the exception rule to add
+ * @see RFC 2445
+ * p.114-15
+ * @see vCal 1.0 p.31
+ */
+ public void addExceptionRule(ExceptionRule exceptionRule) {
+ addProperty(exceptionRule);
+ }
+
+ /**
+ * Gets the color that clients may use when displaying the to-do task (for
+ * example, a background color).
+ * @return the property or null if not set
+ * @see draft-ietf-calext-extensions-01
+ * p.9
+ */
+ public Color getColor() {
+ return getProperty(Color.class);
+ }
+
+ /**
+ * Sets the color that clients may use when displaying the to-do task (for
+ * example, a background color).
+ * @param color the property or null to remove
+ * @see draft-ietf-calext-extensions-01
+ * p.79
+ */
+ public void setColor(Color color) {
+ setProperty(Color.class, color);
+ }
+
+ /**
+ * Sets the color that clients may use when displaying the to-do task (for
+ * example, a background color).
+ * @param color the color name (case insensitive) or null to remove.
+ * Acceptable values are defined in Section 4.3 of the CSS Color Module Level 3 Recommendation. For
+ * example, "aliceblue", "green", "navy".
+ * @return the property object that was created
+ * @see draft-ietf-calext-extensions-01
+ * p.9
+ */
+ public Color setColor(String color) {
+ Color prop = (color == null) ? null : new Color(color);
+ setColor(prop);
+ return prop;
+ }
+
+ /**
+ * Gets the images that are associated with the to-do task.
+ * @return the images (any changes made this list will affect the parent
+ * component object and vice versa)
+ * @see draft-ietf-calext-extensions-01
+ * p.10
+ */
+ public List getImages() {
+ return getProperties(Image.class);
+ }
+
+ /**
+ * Adds an image that is associated with the to-do task.
+ * @param image the image
+ * @see draft-ietf-calext-extensions-01
+ * p.10
+ */
+ public void addImage(Image image) {
+ addProperty(image);
+ }
+
+ /**
+ * Gets information related to the to-do task's conference system.
+ * @return the conferences (any changes made this list will affect the
+ * parent component object and vice versa)
+ * @see draft-ietf-calext-extensions-01
+ * p.11
+ */
+ public List getConferences() {
+ return getProperties(Conference.class);
+ }
+
+ /**
+ * Adds information related to the to-do task's conference system.
+ * @param conference the conference
+ * @see draft-ietf-calext-extensions-01
+ * p.11
+ */
+ public void addConference(Conference conference) {
+ addProperty(conference);
+ }
+
+ /**
+ *
+ * Creates an iterator that computes the dates defined by the
+ * {@link RecurrenceRule} and {@link RecurrenceDates} properties (if
+ * present), and excludes those dates which are defined by the
+ * {@link ExceptionRule} and {@link ExceptionDates} properties (if present).
+ *
+ *
+ * In order for {@link RecurrenceRule} and {@link ExceptionRule} properties
+ * to be included in this iterator, a {@link DateStart} property must be
+ * defined.
+ *
+ *
+ * {@link Period} values in {@link RecurrenceDates} properties are not
+ * supported and are ignored.
+ *
+ * @param timezone the timezone to iterate in. This is needed in order to
+ * adjust for when the iterator passes over a daylight savings boundary.
+ * This parameter is ignored if the start date does not have a time
+ * component.
+ * @return the iterator
+ */
+ public DateIterator getDateIterator(TimeZone timezone) {
+ return Google2445Utils.getDateIterator(this, timezone);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ protected void validate(List components, ICalVersion version, List warnings) {
+ if (version != ICalVersion.V1_0) {
+ checkRequiredCardinality(warnings, Uid.class, DateTimeStamp.class);
+ checkOptionalCardinality(warnings, Classification.class, Completed.class, Created.class, Description.class, DateStart.class, Geo.class, LastModified.class, Location.class, Organizer.class, PercentComplete.class, Priority.class, RecurrenceId.class, Sequence.class, Status.class, Summary.class, Url.class);
+ }
+
+ checkOptionalCardinality(warnings, Color.class);
+
+ Status[] validStatuses;
+ switch (version) {
+ case V1_0:
+ validStatuses = new Status[] { Status.needsAction(), Status.completed(), Status.accepted(), Status.declined(), Status.delegated(), Status.sent() };
+ break;
+ default:
+ validStatuses = new Status[] { Status.needsAction(), Status.completed(), Status.inProgress(), Status.cancelled() };
+ break;
+ }
+ checkStatus(warnings, validStatuses);
+
+ ICalDate dateStart = getValue(getDateStart());
+ ICalDate dateDue = getValue(getDateDue());
+ if (dateStart != null && dateDue != null) {
+ //DTSTART must come before DUE
+ if (dateStart.compareTo(dateDue) > 0) {
+ warnings.add(new ValidationWarning(22));
+ }
+
+ //DTSTART and DUE must have the same data type
+ if (dateStart.hasTime() != dateDue.hasTime()) {
+ warnings.add(new ValidationWarning(23));
+ }
+ }
+
+ //DUE and DURATION cannot both exist
+ DurationProperty duration = getDuration();
+ if (dateDue != null && duration != null) {
+ warnings.add(new ValidationWarning(24));
+ }
+
+ //DTSTART is required if DURATION exists
+ if (dateStart == null && duration != null) {
+ warnings.add(new ValidationWarning(25));
+ }
+
+ //DTSTART and RECURRENCE-ID must have the same data type
+ ICalDate recurrenceId = getValue(getRecurrenceId());
+ if (recurrenceId != null && dateStart != null && dateStart.hasTime() != recurrenceId.hasTime()) {
+ warnings.add(new ValidationWarning(19));
+ }
+
+ //BYHOUR, BYMINUTE, and BYSECOND cannot be specified in RRULE if DTSTART's data type is "date"
+ //RFC 5545 p. 167
+ Recurrence rrule = getValue(getRecurrenceRule());
+ if (dateStart != null && rrule != null) {
+ if (!dateStart.hasTime() && (!rrule.getByHour().isEmpty() || !rrule.getByMinute().isEmpty() || !rrule.getBySecond().isEmpty())) {
+ warnings.add(new ValidationWarning(5));
+ }
+ }
+
+ //there *should* be only 1 instance of RRULE
+ //RFC 5545 p. 167
+ if (getProperties(RecurrenceRule.class).size() > 1) {
+ warnings.add(new ValidationWarning(6));
+ }
+ }
+
+ @Override
+ public VTodo copy() {
+ return new VTodo(this);
+ }
+}
diff --git a/app/src/main/java/biweekly/component/package-info.java b/app/src/main/java/biweekly/component/package-info.java
new file mode 100644
index 0000000000..c672418963
--- /dev/null
+++ b/app/src/main/java/biweekly/component/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Contains DTO classes for each component.
+ */
+package biweekly.component;
\ No newline at end of file
diff --git a/app/src/main/java/biweekly/google-rfc-2445.license b/app/src/main/java/biweekly/google-rfc-2445.license
new file mode 100644
index 0000000000..261eeb9e9f
--- /dev/null
+++ b/app/src/main/java/biweekly/google-rfc-2445.license
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ 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.
diff --git a/app/src/main/java/biweekly/io/CannotParseException.java b/app/src/main/java/biweekly/io/CannotParseException.java
new file mode 100644
index 0000000000..b3c18ca652
--- /dev/null
+++ b/app/src/main/java/biweekly/io/CannotParseException.java
@@ -0,0 +1,79 @@
+package biweekly.io;
+
+import biweekly.Messages;
+
+/*
+ Copyright (c) 2013-2023, Michael Angstadt
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+
+ 1. Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+ 2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * Thrown during the unmarshalling of an iCalendar property to signal that the
+ * property's value could not be parsed (for example, being unable to parse a
+ * date string).
+ * @author Michael Angstadt
+ */
+public class CannotParseException extends RuntimeException {
+ private static final long serialVersionUID = 8299420302297241326L;
+ private final Integer code;
+ private final Object args[];
+
+ /**
+ * Creates a new "cannot parse" exception.
+ * @param code the warning message code
+ * @param args the warning message arguments
+ */
+ public CannotParseException(int code, Object... args) {
+ this.code = code;
+ this.args = args;
+ }
+
+ /**
+ * Creates a new "cannot parse" exception.
+ * @param reason the reason why the property value cannot be parsed
+ */
+ public CannotParseException(String reason) {
+ this(1, reason);
+ }
+
+ /**
+ * Gets the warning message code.
+ * @return the message code
+ */
+ public Integer getCode() {
+ return code;
+ }
+
+ /**
+ * Gets the warning message arguments.
+ * @return the message arguments
+ */
+ public Object[] getArgs() {
+ return args;
+ }
+
+ @Override
+ public String getMessage() {
+ return Messages.INSTANCE.getParseMessage(code, args);
+ }
+}
diff --git a/app/src/main/java/biweekly/io/DataModelConversionException.java b/app/src/main/java/biweekly/io/DataModelConversionException.java
new file mode 100644
index 0000000000..8111fe9be2
--- /dev/null
+++ b/app/src/main/java/biweekly/io/DataModelConversionException.java
@@ -0,0 +1,81 @@
+package biweekly.io;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import biweekly.component.ICalComponent;
+import biweekly.component.VAlarm;
+import biweekly.property.AudioAlarm;
+import biweekly.property.ICalProperty;
+
+/*
+ Copyright (c) 2013-2023, Michael Angstadt
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+
+ 1. Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+ 2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * Thrown when a component or property needs to be converted to a different
+ * component or property when being read or written. For example, converting a
+ * vCal {@link AudioAlarm} property to a {@link VAlarm} component when parsing a
+ * vCal file.
+ * @author Michael Angstadt
+ */
+public class DataModelConversionException extends RuntimeException {
+ private static final long serialVersionUID = -4789186852509057375L;
+ private final ICalProperty originalProperty;
+ private final List components = new ArrayList();
+ private final List properties = new ArrayList();
+
+ /**
+ * Creates a conversion exception.
+ * @param originalProperty the original property object that was parsed or
+ * null if not applicable
+ */
+ public DataModelConversionException(ICalProperty originalProperty) {
+ this.originalProperty = originalProperty;
+ }
+
+ /**
+ * Gets the original property object that was parsed.
+ * @return the original property object or null if not applicable
+ */
+ public ICalProperty getOriginalProperty() {
+ return originalProperty;
+ }
+
+ /**
+ * Gets the components that were converted from the original property.
+ * @return the components
+ */
+ public List getComponents() {
+ return components;
+ }
+
+ /**
+ * Gets the properties that were converted from the original property.
+ * @return the properties
+ */
+ public List getProperties() {
+ return properties;
+ }
+}
diff --git a/app/src/main/java/biweekly/io/DataModelConverter.java b/app/src/main/java/biweekly/io/DataModelConverter.java
new file mode 100644
index 0000000000..317d8282e5
--- /dev/null
+++ b/app/src/main/java/biweekly/io/DataModelConverter.java
@@ -0,0 +1,241 @@
+package biweekly.io;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.TimeZone;
+
+import biweekly.component.DaylightSavingsTime;
+import biweekly.component.Observance;
+import biweekly.component.StandardTime;
+import biweekly.component.VTimezone;
+import biweekly.io.ICalTimeZone.Boundary;
+import biweekly.property.Daylight;
+import biweekly.property.Timezone;
+import biweekly.property.UtcOffsetProperty;
+import biweekly.property.ValuedProperty;
+import biweekly.util.DateTimeComponents;
+import biweekly.util.ICalDate;
+import biweekly.util.UtcOffset;
+import biweekly.util.com.google.ical.values.DateTimeValue;
+
+/*
+ Copyright (c) 2013-2023, Michael Angstadt
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+
+ 1. Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+ 2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * Converts various properties/components into other properties/components for
+ * vCalendar-iCalendar compatibility.
+ * @author Michael Angstadt
+ */
+public final class DataModelConverter {
+ /**
+ * Converts vCalendar timezone information to an iCalendar {@link VTimezone}
+ * component.
+ * @param daylights the DAYLIGHT properties
+ * @param tz the TZ property
+ * @return the VTIMEZONE component
+ */
+ public static VTimezone convert(List daylights, Timezone tz) {
+ UtcOffset tzOffset = ValuedProperty.getValue(tz);
+ if (daylights.isEmpty() && tzOffset == null) {
+ return null;
+ }
+
+ VTimezone timezone = new VTimezone("TZ");
+ if (daylights.isEmpty() && tzOffset != null) {
+ StandardTime st = new StandardTime();
+ st.setTimezoneOffsetFrom(tzOffset);
+ st.setTimezoneOffsetTo(tzOffset);
+ timezone.addStandardTime(st);
+ return timezone;
+ }
+
+ for (Daylight daylight : daylights) {
+ if (!daylight.isDaylight()) {
+ continue;
+ }
+
+ UtcOffset daylightOffset = daylight.getOffset();
+ UtcOffset standardOffset = new UtcOffset(daylightOffset.getMillis() - (1000 * 60 * 60));
+
+ DaylightSavingsTime dst = new DaylightSavingsTime();
+ dst.setDateStart(daylight.getStart());
+ dst.setTimezoneOffsetFrom(standardOffset);
+ dst.setTimezoneOffsetTo(daylightOffset);
+ dst.addTimezoneName(daylight.getDaylightName());
+ timezone.addDaylightSavingsTime(dst);
+
+ StandardTime st = new StandardTime();
+ st.setDateStart(daylight.getEnd());
+ st.setTimezoneOffsetFrom(daylightOffset);
+ st.setTimezoneOffsetTo(standardOffset);
+ st.addTimezoneName(daylight.getStandardName());
+ timezone.addStandardTime(st);
+ }
+
+ return timezone.getComponents().isEmpty() ? null : timezone;
+ }
+
+ /**
+ * Converts an iCalendar {@link VTimezone} component into the appropriate
+ * vCalendar properties.
+ * @param timezone the TIMEZONE component
+ * @param dates the date values in the vCalendar object that are effected by
+ * the timezone.
+ * @return the vCalendar properties
+ */
+ public static VCalTimezoneProperties convert(VTimezone timezone, List dates) {
+ List daylights = new ArrayList();
+ Timezone tz = null;
+ if (dates.isEmpty()) {
+ return new VCalTimezoneProperties(daylights, tz);
+ }
+
+ ICalTimeZone icalTz = new ICalTimeZone(timezone);
+ Collections.sort(dates);
+ Set daylightStartDates = new HashSet();
+ boolean zeroObservanceUsed = false;
+ for (Date date : dates) {
+ Boundary boundary = icalTz.getObservanceBoundary(date);
+ Observance observance = boundary.getObservanceIn();
+ Observance observanceAfter = boundary.getObservanceAfter();
+ if (observance == null && observanceAfter == null) {
+ continue;
+ }
+
+ if (observance == null) {
+ //the date comes before the earliest observance
+ if (observanceAfter instanceof StandardTime && !zeroObservanceUsed) {
+ UtcOffset offset = getOffset(observanceAfter.getTimezoneOffsetFrom());
+ DateTimeValue start = null;
+ DateTimeValue end = boundary.getObservanceAfterStart();
+ String standardName = icalTz.getDisplayName(false, TimeZone.SHORT);
+ String daylightName = icalTz.getDisplayName(true, TimeZone.SHORT);
+
+ Daylight daylight = new Daylight(true, offset, convert(start), convert(end), standardName, daylightName);
+ daylights.add(daylight);
+ zeroObservanceUsed = true;
+ }
+
+ if (observanceAfter instanceof DaylightSavingsTime) {
+ UtcOffset offset = getOffset(observanceAfter.getTimezoneOffsetFrom());
+ if (offset != null) {
+ tz = new Timezone(offset);
+ }
+ }
+
+ continue;
+ }
+
+ if (observance instanceof StandardTime) {
+ UtcOffset offset = getOffset(observance.getTimezoneOffsetTo());
+ if (offset != null) {
+ tz = new Timezone(offset);
+ }
+ continue;
+ }
+
+ if (observance instanceof DaylightSavingsTime && !daylightStartDates.contains(boundary.getObservanceInStart())) {
+ UtcOffset offset = getOffset(observance.getTimezoneOffsetTo());
+ DateTimeValue start = boundary.getObservanceInStart();
+ DateTimeValue end = null;
+ if (observanceAfter != null) {
+ end = boundary.getObservanceAfterStart();
+ }
+
+ String standardName = icalTz.getDisplayName(false, TimeZone.SHORT);
+ String daylightName = icalTz.getDisplayName(true, TimeZone.SHORT);
+
+ Daylight daylight = new Daylight(true, offset, convert(start), convert(end), standardName, daylightName);
+ daylights.add(daylight);
+ daylightStartDates.add(start);
+ continue;
+ }
+ }
+
+ if (tz == null) {
+ int rawOffset = icalTz.getRawOffset();
+ UtcOffset offset = new UtcOffset(rawOffset);
+ tz = new Timezone(offset);
+ }
+
+ if (daylights.isEmpty()) {
+ Daylight daylight = new Daylight();
+ daylight.setDaylight(false);
+ daylights.add(daylight);
+ }
+
+ return new VCalTimezoneProperties(daylights, tz);
+ }
+
+ private static UtcOffset getOffset(UtcOffsetProperty property) {
+ return (property == null) ? null : property.getValue();
+ }
+
+ private static ICalDate convert(DateTimeValue value) {
+ if (value == null) {
+ return null;
+ }
+
+ //@formatter:off
+ DateTimeComponents components = new DateTimeComponents(
+ value.year(),
+ value.month(),
+ value.day(),
+ value.hour(),
+ value.minute(),
+ value.second(),
+ false
+ );
+ //@formatter:on
+
+ return new ICalDate(components, true);
+ }
+
+ public static class VCalTimezoneProperties {
+ private final List daylights;
+ private final Timezone tz;
+
+ public VCalTimezoneProperties(List daylights, Timezone tz) {
+ this.daylights = daylights;
+ this.tz = tz;
+ }
+
+ public List getDaylights() {
+ return daylights;
+ }
+
+ public Timezone getTz() {
+ return tz;
+ }
+ }
+
+ private DataModelConverter() {
+ //hide
+ }
+}
diff --git a/app/src/main/java/biweekly/io/DefaultGlobalTimezoneIdResolver.java b/app/src/main/java/biweekly/io/DefaultGlobalTimezoneIdResolver.java
new file mode 100644
index 0000000000..9648fce9c0
--- /dev/null
+++ b/app/src/main/java/biweekly/io/DefaultGlobalTimezoneIdResolver.java
@@ -0,0 +1,67 @@
+package biweekly.io;
+
+import biweekly.util.ICalDateFormat;
+
+import java.util.TimeZone;
+
+/*
+ Copyright (c) 2013-2023, Michael Angstadt
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+
+ 1. Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+ 2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ *