001 package ezvcard.types; 002 003 import java.util.List; 004 import java.util.SimpleTimeZone; 005 import java.util.TimeZone; 006 import java.util.regex.Matcher; 007 import java.util.regex.Pattern; 008 009 import ezvcard.VCard; 010 import ezvcard.VCardSubTypes; 011 import ezvcard.VCardVersion; 012 import ezvcard.io.CompatibilityMode; 013 import ezvcard.io.SkipMeException; 014 import ezvcard.parameters.ValueParameter; 015 import ezvcard.util.HCardElement; 016 import ezvcard.util.VCardDateFormatter; 017 import ezvcard.util.VCardStringUtils; 018 import ezvcard.util.XCardElement; 019 020 /* 021 Copyright (c) 2012, Michael Angstadt 022 All rights reserved. 023 024 Redistribution and use in source and binary forms, with or without 025 modification, are permitted provided that the following conditions are met: 026 027 1. Redistributions of source code must retain the above copyright notice, this 028 list of conditions and the following disclaimer. 029 2. Redistributions in binary form must reproduce the above copyright notice, 030 this list of conditions and the following disclaimer in the documentation 031 and/or other materials provided with the distribution. 032 033 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 034 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 035 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 036 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 037 ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 038 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 039 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 040 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 041 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 042 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 043 044 The views and conclusions contained in the software and documentation are those 045 of the authors and should not be interpreted as representing official policies, 046 either expressed or implied, of the FreeBSD Project. 047 */ 048 049 /** 050 * Contains the timezone that the person lives/works in. 051 * 052 * <pre> 053 * VCard vcard = new VCard(); 054 * TimezoneType tz = new TimezoneType(-5, 0, "America/New_York"); 055 * vcard.addTimezone(tz); 056 * </pre> 057 * 058 * <p> 059 * vCard property name: TZ 060 * </p> 061 * <p> 062 * vCard versions: 2.1, 3.0, 4.0 063 * </p> 064 * 065 * @author Michael Angstadt 066 */ 067 public class TimezoneType extends VCardType { 068 public static final String NAME = "TZ"; 069 070 private Integer hourOffset; 071 private Integer minuteOffset; 072 private String text; 073 074 public TimezoneType() { 075 super(NAME); 076 } 077 078 /** 079 * This is the recommended constructor for version 4.0 vCards. 080 * @param text string representing the timezone from the <a 081 * href="http://en.wikipedia.org/wiki/List_of_tz_database_time_zones">Olson 082 * Database</a> (e.g. "America/New_York") 083 */ 084 public TimezoneType(String text) { 085 this(null, null, text); 086 } 087 088 /** 089 * This is the recommended constructor for version 2.1 and 3.0 vCards. 090 * @param hourOffset the hour offset 091 * @param minuteOffset the minute offset 092 */ 093 public TimezoneType(Integer hourOffset, Integer minuteOffset) { 094 this(hourOffset, minuteOffset, null); 095 } 096 097 /** 098 * This constructor can be used for all vCard versions. 099 * @param hourOffset the hour offset 100 * @param minuteOffset the minute offset 101 * @param text can be anything, but should be a string representing the 102 * timezone from the <a 103 * href="http://en.wikipedia.org/wiki/List_of_tz_database_time_zones">Olson 104 * Database</a> (e.g. "America/New_York") 105 */ 106 public TimezoneType(Integer hourOffset, Integer minuteOffset, String text) { 107 super(NAME); 108 setHourOffset(hourOffset); 109 setMinuteOffset(minuteOffset); 110 setText(text); 111 } 112 113 /** 114 * Gets the hour offset. 115 * @return the hour offset or null if not set 116 */ 117 public Integer getHourOffset() { 118 return hourOffset; 119 } 120 121 /** 122 * Sets the hour offset. 123 * @param hourOffset the hour offset or null to remove 124 */ 125 public void setHourOffset(Integer hourOffset) { 126 this.hourOffset = hourOffset; 127 } 128 129 /** 130 * Gets the minute offset. 131 * @return the minute offset or null if not set 132 */ 133 public Integer getMinuteOffset() { 134 return minuteOffset; 135 } 136 137 /** 138 * Sets the minute offset. 139 * @param minuteOffset the minute offset or null to remove 140 * @throws IllegalArgumentException if the minute offset is not between 0 141 * and 59 142 */ 143 public void setMinuteOffset(Integer minuteOffset) { 144 if (minuteOffset != null && (minuteOffset < 0 || minuteOffset > 59)) { 145 throw new IllegalArgumentException("Minute offset must be between 0 and 59."); 146 } 147 this.minuteOffset = minuteOffset; 148 } 149 150 /** 151 * Gets the text portion of the timezone. 152 * @return the text portion (e.g. "America/New_York") 153 */ 154 public String getText() { 155 return text; 156 } 157 158 /** 159 * Sets the text portion of the timezone. 160 * @param text the text portion (e.g. "America/New_York") 161 */ 162 public void setText(String text) { 163 this.text = text; 164 } 165 166 /** 167 * Creates a {@link java.util.TimeZone} representation of this class. 168 * @return a {@link TimeZone} object or null if this object contains no 169 * offset data 170 */ 171 public TimeZone toTimeZone() { 172 if (hourOffset == null || minuteOffset == null) { 173 return null; 174 } 175 176 int rawHourOffset = hourOffset * 60 * 60 * 1000; 177 int rawMinuteOffset = minuteOffset * 60 * 1000; 178 if (rawHourOffset < 0) { 179 rawMinuteOffset *= -1; 180 } 181 int rawOffset = rawHourOffset + rawMinuteOffset; 182 return new SimpleTimeZone(rawOffset, ""); 183 } 184 185 /** 186 * Gets the TYPE parameter. 187 * <p> 188 * vCard versions: 4.0 189 * </p> 190 * @return the TYPE value (typically, this will be either "work" or "home") 191 * or null if it doesn't exist 192 */ 193 public String getType() { 194 return subTypes.getType(); 195 } 196 197 /** 198 * Sets the TYPE parameter. 199 * <p> 200 * vCard versions: 4.0 201 * </p> 202 * @param type the TYPE value (this should be either "work" or "home") or 203 * null to remove 204 */ 205 public void setType(String type) { 206 subTypes.setType(type); 207 } 208 209 /** 210 * Gets the MEDIATYPE parameter. 211 * <p> 212 * vCard versions: 4.0 213 * </p> 214 * @return the media type or null if not set 215 */ 216 public String getMediaType() { 217 return subTypes.getMediaType(); 218 } 219 220 /** 221 * Sets the MEDIATYPE parameter. 222 * <p> 223 * vCard versions: 4.0 224 * </p> 225 * @param mediaType the media type or null to remove 226 */ 227 public void setMediaType(String mediaType) { 228 subTypes.setMediaType(mediaType); 229 } 230 231 /** 232 * Gets all PID parameter values. 233 * <p> 234 * vCard versions: 4.0 235 * </p> 236 * @return the PID values or empty set if there are none 237 * @see VCardSubTypes#getPids 238 */ 239 public List<Integer[]> getPids() { 240 return subTypes.getPids(); 241 } 242 243 /** 244 * Adds a PID value. 245 * <p> 246 * vCard versions: 4.0 247 * </p> 248 * @param localId the local ID 249 * @param clientPidMapRef the ID used to reference the property's globally 250 * unique identifier in the CLIENTPIDMAP property. 251 * @see VCardSubTypes#addPid(int, int) 252 */ 253 public void addPid(int localId, int clientPidMapRef) { 254 subTypes.addPid(localId, clientPidMapRef); 255 } 256 257 /** 258 * Removes all PID values. 259 * <p> 260 * vCard versions: 4.0 261 * </p> 262 * @see VCardSubTypes#removePids 263 */ 264 public void removePids() { 265 subTypes.removePids(); 266 } 267 268 /** 269 * Gets the preference value. 270 * <p> 271 * vCard versions: 4.0 272 * </p> 273 * @return the preference value or null if it doesn't exist 274 * @see VCardSubTypes#getPref 275 */ 276 public Integer getPref() { 277 return subTypes.getPref(); 278 } 279 280 /** 281 * Sets the preference value. 282 * <p> 283 * vCard versions: 4.0 284 * </p> 285 * @param pref the preference value or null to remove 286 * @see VCardSubTypes#setPref 287 */ 288 public void setPref(Integer pref) { 289 subTypes.setPref(pref); 290 } 291 292 /** 293 * Gets the ALTID. 294 * <p> 295 * vCard versions: 4.0 296 * </p> 297 * @return the ALTID or null if it doesn't exist 298 * @see VCardSubTypes#getAltId 299 */ 300 public String getAltId() { 301 return subTypes.getAltId(); 302 } 303 304 /** 305 * Sets the ALTID. 306 * <p> 307 * vCard versions: 4.0 308 * </p> 309 * @param altId the ALTID or null to remove 310 * @see VCardSubTypes#setAltId 311 */ 312 public void setAltId(String altId) { 313 subTypes.setAltId(altId); 314 } 315 316 @Override 317 protected void doMarshalSubTypes(VCardSubTypes copy, VCardVersion version, List<String> warnings, CompatibilityMode compatibilityMode, VCard vcard) { 318 if (text != null) { 319 copy.setValue(ValueParameter.TEXT); 320 } else if (hourOffset != null && minuteOffset != null && version == VCardVersion.V4_0) { 321 copy.setValue(ValueParameter.UTC_OFFSET); 322 } 323 } 324 325 @Override 326 protected void doMarshalText(StringBuilder sb, VCardVersion version, List<String> warnings, CompatibilityMode compatibilityMode) { 327 if (text != null) { 328 if ((version == VCardVersion.V2_1 || version == VCardVersion.V3_0) && hourOffset != null && minuteOffset != null) { 329 sb.append(VCardDateFormatter.formatTimeZone(hourOffset, minuteOffset, true)); 330 sb.append(';'); 331 } 332 sb.append(VCardStringUtils.escape(text)); 333 } else if (hourOffset != null && minuteOffset != null) { 334 sb.append(VCardDateFormatter.formatTimeZone(hourOffset, minuteOffset, true)); 335 } else { 336 throw new SkipMeException("Property does not have text or a UTC offset associated with it."); 337 } 338 } 339 340 @Override 341 protected void doUnmarshalText(String value, VCardVersion version, List<String> warnings, CompatibilityMode compatibilityMode) { 342 parseValue(value); 343 if (text != null) { 344 text = VCardStringUtils.unescape(text); 345 } 346 } 347 348 @Override 349 protected void doMarshalXml(XCardElement parent, List<String> warnings, CompatibilityMode compatibilityMode) { 350 if (text != null) { 351 parent.text(text); 352 } else if (hourOffset != null && minuteOffset != null) { 353 String offset = VCardDateFormatter.formatTimeZone(hourOffset, minuteOffset, true); 354 parent.append("utc-offset", offset); 355 } else { 356 throw new SkipMeException("Property does not have text or a UTC offset associated with it."); 357 } 358 } 359 360 @Override 361 protected void doUnmarshalXml(XCardElement element, List<String> warnings, CompatibilityMode compatibilityMode) { 362 String value = element.get("text", "uri", "utc-offset"); 363 if (value != null) { 364 parseValue(value); 365 } 366 } 367 368 private void parseValue(String value) { 369 Pattern p = Pattern.compile("^([-\\+]?\\d{1,2}(:?\\d{2})?)(.*)"); 370 Matcher m = p.matcher(value); 371 if (m.find()) { 372 //do some smart parsing--if the value starts with a timezone, then parse it 373 int offsets[] = VCardDateFormatter.parseTimeZone(m.group(1)); 374 hourOffset = offsets[0]; 375 minuteOffset = offsets[1]; 376 377 String text = m.group(3); 378 if (text != null && text.length() == 0) { 379 text = null; 380 } 381 this.text = text; 382 } else { 383 hourOffset = null; 384 minuteOffset = null; 385 text = value; 386 } 387 } 388 389 @Override 390 protected void doUnmarshalHtml(HCardElement element, List<String> warnings) { 391 String value = element.value(); 392 doUnmarshalText(value, VCardVersion.V3_0, warnings, CompatibilityMode.RFC); 393 } 394 }