001package biweekly.io; 002 003import java.util.Collection; 004import java.util.HashMap; 005import java.util.HashSet; 006import java.util.Map; 007import java.util.Set; 008import java.util.TimeZone; 009 010import biweekly.component.VTimezone; 011import biweekly.property.ICalProperty; 012import biweekly.property.TimezoneId; 013 014/* 015 Copyright (c) 2013-2015, Michael Angstadt 016 All rights reserved. 017 018 Redistribution and use in source and binary forms, with or without 019 modification, are permitted provided that the following conditions are met: 020 021 1. Redistributions of source code must retain the above copyright notice, this 022 list of conditions and the following disclaimer. 023 2. Redistributions in binary form must reproduce the above copyright notice, 024 this list of conditions and the following disclaimer in the documentation 025 and/or other materials provided with the distribution. 026 027 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 028 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 029 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 030 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 031 ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 032 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 033 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 034 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 035 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 036 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 037 */ 038 039/** 040 * Holds the timezone-related settings of an iCalendar object. 041 * @author Michael Angstadt 042 */ 043public class TimezoneInfo { 044 private final Map<VTimezone, TimeZone> assignments = new HashMap<VTimezone, TimeZone>(); 045 private final Map<TimeZone, VTimezone> assignmentsReverse = new HashMap<TimeZone, VTimezone>(); 046 private final Map<String, TimeZone> timezonesById = new HashMap<String, TimeZone>(); 047 048 private final Map<ICalProperty, TimeZone> propertyTimeZones = new HashMap<ICalProperty, TimeZone>(); 049 private final Set<ICalProperty> hasSolidusTimezone = new HashSet<ICalProperty>(); 050 private final Set<ICalProperty> floatingProperties = new HashSet<ICalProperty>(); 051 052 private VTimezoneGenerator generator = new TzUrlDotOrgGenerator(false); 053 054 private TimeZone defaultTimezone; 055 private boolean globalFloatingTime = false; 056 057 /** 058 * Assigns a user-defined {@link VTimezone} component to its Java 059 * {@link TimeZone} equivalent. 060 * @param component the timezone component 061 * @param timezone the timezone object 062 */ 063 public void assign(VTimezone component, TimeZone timezone) { 064 checkForId(component); 065 assignments.put(component, timezone); 066 assignmentsReverse.put(timezone, component); 067 timezonesById.put(component.getTimezoneId().getValue(), timezone); 068 } 069 070 /** 071 * Sets the timezone to format all date/time values in. An attempt will be 072 * made to generate a {@link VTimezone} component if one has not already 073 * been assigned to the given timezone (see 074 * {@link #assign(VTimezone, TimeZone) assign()}). 075 * @param timezone the timezone object or null for UTC (default) 076 * @throws IllegalArgumentException if a {@link VTimezone} component could 077 * not be generated 078 */ 079 public void setDefaultTimeZone(TimeZone timezone) { 080 if (timezone == null) { 081 defaultTimezone = null; 082 return; 083 } 084 085 VTimezone component = assignmentsReverse.get(timezone); 086 if (component == null) { 087 component = generator.generate(timezone); 088 assign(component, timezone); 089 } 090 defaultTimezone = timezone; 091 } 092 093 /** 094 * Sets whether to format all date/time values as floating times. A floating 095 * time value does not have a timezone associated with it, and is to be 096 * interpreted as being in the local timezone of the parsing computer. 097 * @param enable true to enable, false to disable (default) 098 */ 099 public void setGlobalFloatingTime(boolean enable) { 100 globalFloatingTime = enable; 101 } 102 103 /** 104 * Instructs the writer to format an individual property's date/time value 105 * in a specific timezone. 106 * @param property the property 107 * @param timezone the timezone or null to format the property according to 108 * the default timezone (default) 109 */ 110 public void setTimeZone(ICalProperty property, TimeZone timezone) { 111 setTimeZone(property, timezone, true); 112 } 113 114 /** 115 * Instructs the writer to format an individual property's date/time value 116 * in a specific timezone. 117 * @param property the property 118 * @param timezone the timezone or null to format the property according to 119 * the default timezone (default) 120 * @param generateComponent true to associate the property with a 121 * {@link VTimezone} component containing the timezone definition 122 * (recommended), false not to. If the "timezone" parameter is null, then 123 * this parameter has no effect 124 */ 125 public void setTimeZone(ICalProperty property, TimeZone timezone, boolean generateComponent) { 126 if (timezone == null) { 127 propertyTimeZones.remove(property); 128 hasSolidusTimezone.remove(property); 129 return; 130 } 131 132 if (generateComponent) { 133 VTimezone component = assignmentsReverse.get(timezone); 134 if (component == null) { 135 component = generator.generate(timezone); 136 assign(component, timezone); 137 } 138 } else { 139 hasSolidusTimezone.add(property); 140 } 141 142 propertyTimeZones.put(property, timezone); 143 } 144 145 /* package */void setTimeZoneReader(ICalProperty property, TimeZone timezone, boolean solidus) { 146 if (solidus) { 147 hasSolidusTimezone.add(property); 148 } 149 propertyTimeZones.put(property, timezone); 150 } 151 152 /** 153 * Gets the timezone that is assigned to a property. 154 * @param property the property 155 * @return the timezone or null if no timezone is assigned to the property 156 */ 157 public TimeZone getTimeZone(ICalProperty property) { 158 return propertyTimeZones.get(property); 159 } 160 161 /** 162 * Determines if the given property has a solidus timezone (a globally 163 * unique timezone ID). 164 * @param property the property 165 * @return true if the property has a solidus timezone, false if not 166 */ 167 public boolean hasSolidusTimezone(ICalProperty property) { 168 return hasSolidusTimezone.contains(property); 169 } 170 171 /** 172 * Gets the timezone that a property should be formatted in when written. 173 * You should call {@link #isFloating} first, to check to see if the 174 * property's value is floating (without a timezone). 175 * @param property the property 176 * @return the timezone or null for UTC 177 */ 178 public TimeZone getTimeZoneToWriteIn(ICalProperty property) { 179 TimeZone timezone = getTimeZone(property); 180 return (timezone == null) ? defaultTimezone : timezone; 181 } 182 183 /** 184 * Gets a timezone with a given ID. 185 * @param id the ID 186 * @return the timezone or null if not found 187 */ 188 public TimeZone getTimeZoneById(String id) { 189 return timezonesById.get(id); 190 } 191 192 /** 193 * Gets the {@link TimeZone} object that is assigned to a {@link VTimezone} 194 * component. 195 * @param component the component 196 * @return the timezone object or null if none were found 197 */ 198 public TimeZone getTimeZoneByComponent(VTimezone component) { 199 return assignments.get(component); 200 } 201 202 /** 203 * Gets the {@link VTimezone} component that a property is assigned to. 204 * @param property the property 205 * @return the component or null if it is not assigned to one 206 */ 207 public VTimezone getComponent(ICalProperty property) { 208 if (hasSolidusTimezone.contains(property)) { 209 return null; 210 } 211 212 TimeZone timezone = getTimeZone(property); 213 return assignmentsReverse.get(timezone); 214 } 215 216 /** 217 * Instructs the writer to format a particular property's date/time value in 218 * floating time. A floating time value does not have a timezone associated 219 * with it, and is to be interpreted as being in the local timezone of the 220 * parsing computer. 221 * @param property the property whose value should be formatted as a 222 * floating time value 223 * @param enable true to enable floating time for this property, false to 224 * disable (default) 225 */ 226 public void setFloating(ICalProperty property, boolean enable) { 227 if (enable) { 228 floatingProperties.add(property); 229 } else { 230 floatingProperties.remove(property); 231 } 232 } 233 234 /** 235 * Determines if a property value should be formatted in floating time or 236 * not. 237 * @param property the property 238 * @return true to format in floating time, false not to 239 */ 240 public boolean isFloating(ICalProperty property) { 241 if (floatingProperties.contains(property)) { 242 return true; 243 } 244 245 if (propertyTimeZones.containsKey(property)) { 246 return false; 247 } 248 249 return globalFloatingTime; 250 } 251 252 /** 253 * Gets all of the iCalendar {@link VTimezone} components that have been 254 * registered or generated by this class. 255 * @return the timezone components 256 */ 257 public Collection<VTimezone> getComponents() { 258 return assignments.keySet(); 259 } 260 261 /** 262 * Gets the timezone generator. 263 * @return the timezone generator 264 */ 265 public VTimezoneGenerator getGenerator() { 266 return generator; 267 } 268 269 /** 270 * Sets the timezone generator. 271 * @param generator the timezone generator 272 */ 273 public void setGenerator(VTimezoneGenerator generator) { 274 this.generator = generator; 275 } 276 277 /** 278 * Checks a {@link VTimezone} component to see if it has a valid 279 * {@link TimezoneId} property. 280 * @param timezone the timezone component 281 * @throws IllegalArgumentException if the component does not have a valid 282 * {@link TimezoneId} property 283 */ 284 private void checkForId(VTimezone timezone) { 285 TimezoneId id = timezone.getTimezoneId(); 286 if (id == null || id.getValue() == null || id.getValue().trim().length() == 0) { 287 throw new IllegalArgumentException("VTimezone component must have a non-empty TimezoneId property"); 288 } 289 } 290}