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 }