001 package ezvcard.io.scribe; 002 003 import java.util.List; 004 import java.util.TimeZone; 005 006 import ezvcard.Messages; 007 import ezvcard.VCardDataType; 008 import ezvcard.VCardVersion; 009 import ezvcard.io.CannotParseException; 010 import ezvcard.io.html.HCardElement; 011 import ezvcard.io.json.JCardValue; 012 import ezvcard.io.xml.XCardElement; 013 import ezvcard.parameter.VCardParameters; 014 import ezvcard.property.Timezone; 015 import ezvcard.util.UtcOffset; 016 017 /* 018 Copyright (c) 2013, Michael Angstadt 019 All rights reserved. 020 021 Redistribution and use in source and binary forms, with or without 022 modification, are permitted provided that the following conditions are met: 023 024 1. Redistributions of source code must retain the above copyright notice, this 025 list of conditions and the following disclaimer. 026 2. Redistributions in binary form must reproduce the above copyright notice, 027 this list of conditions and the following disclaimer in the documentation 028 and/or other materials provided with the distribution. 029 030 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 031 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 032 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 033 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 034 ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 035 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 036 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 037 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 038 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 039 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 040 */ 041 042 /** 043 * Marshals {@link Timezone} properties. 044 * @author Michael Angstadt 045 */ 046 public class TimezoneScribe extends VCardPropertyScribe<Timezone> { 047 public TimezoneScribe() { 048 super(Timezone.class, "TZ"); 049 } 050 051 @Override 052 protected VCardDataType _defaultDataType(VCardVersion version) { 053 switch (version) { 054 case V2_1: 055 case V3_0: 056 return VCardDataType.UTC_OFFSET; 057 case V4_0: 058 return VCardDataType.TEXT; 059 } 060 return null; 061 } 062 063 @Override 064 protected VCardDataType _dataType(Timezone property, VCardVersion version) { 065 String text = property.getText(); 066 UtcOffset offset = property.getOffset(); 067 068 switch (version) { 069 case V2_1: 070 return VCardDataType.UTC_OFFSET; 071 case V3_0: 072 if (offset != null) { 073 return VCardDataType.UTC_OFFSET; 074 } 075 if (text != null) { 076 return VCardDataType.TEXT; 077 } 078 break; 079 case V4_0: 080 if (text != null) { 081 return VCardDataType.TEXT; 082 } 083 if (offset != null) { 084 return VCardDataType.UTC_OFFSET; 085 } 086 break; 087 } 088 089 return _defaultDataType(version); 090 } 091 092 @Override 093 protected String _writeText(Timezone property, VCardVersion version) { 094 String text = property.getText(); 095 UtcOffset offset = property.getOffset(); 096 097 switch (version) { 098 case V2_1: 099 if (offset != null) { 100 return offset.toString(false); //2.1 allows either basic or extended 101 } 102 103 if (text != null) { 104 //attempt to find the offset by treating the text as a timezone ID, like "America/New_York" 105 TimeZone timezone = timezoneFromId(text); 106 if (timezone != null) { 107 UtcOffset tzOffset = offsetFromTimezone(timezone); 108 return tzOffset.toString(false); 109 } 110 } 111 break; 112 case V3_0: 113 if (offset != null) { 114 return offset.toString(true); //3.0 only allows extended 115 } 116 117 if (text != null) { 118 return escape(text); 119 } 120 break; 121 case V4_0: 122 if (text != null) { 123 return escape(text); 124 } 125 126 if (offset != null) { 127 return offset.toString(false); //4.0 only allows basic 128 } 129 break; 130 } 131 132 return ""; 133 } 134 135 @Override 136 protected Timezone _parseText(String value, VCardDataType dataType, VCardVersion version, VCardParameters parameters, List<String> warnings) { 137 value = unescape(value); 138 return parse(value, dataType, version, warnings); 139 } 140 141 @Override 142 protected void _writeXml(Timezone property, XCardElement parent) { 143 String text = property.getText(); 144 if (text != null) { 145 parent.append(VCardDataType.TEXT, text); 146 return; 147 } 148 149 UtcOffset offset = property.getOffset(); 150 if (offset != null) { 151 parent.append(VCardDataType.UTC_OFFSET, offset.toString(false)); 152 return; 153 } 154 155 parent.append(VCardDataType.TEXT, ""); 156 } 157 158 @Override 159 protected Timezone _parseXml(XCardElement element, VCardParameters parameters, List<String> warnings) { 160 String text = element.first(VCardDataType.TEXT); 161 if (text != null) { 162 return new Timezone(text); 163 } 164 165 String utcOffset = element.first(VCardDataType.UTC_OFFSET); 166 if (utcOffset != null) { 167 try { 168 return new Timezone(UtcOffset.parse(utcOffset)); 169 } catch (IllegalArgumentException e) { 170 throw new CannotParseException(19); 171 } 172 } 173 174 throw missingXmlElements(VCardDataType.TEXT, VCardDataType.UTC_OFFSET); 175 } 176 177 @Override 178 protected Timezone _parseHtml(HCardElement element, List<String> warnings) { 179 return parse(element.value(), null, VCardVersion.V3_0, warnings); 180 } 181 182 @Override 183 protected JCardValue _writeJson(Timezone property) { 184 String text = property.getText(); 185 if (text != null) { 186 return JCardValue.single(text); 187 } 188 189 UtcOffset offset = property.getOffset(); 190 if (offset != null) { 191 return JCardValue.single(offset.toString(true)); 192 } 193 194 return JCardValue.single(""); 195 } 196 197 @Override 198 protected Timezone _parseJson(JCardValue value, VCardDataType dataType, VCardParameters parameters, List<String> warnings) { 199 String valueStr = value.asSingle(); 200 return parse(valueStr, dataType, VCardVersion.V4_0, warnings); 201 } 202 203 private Timezone parse(String value, VCardDataType dataType, VCardVersion version, List<String> warnings) { 204 if (value == null || value.length() == 0) { 205 return new Timezone((String) null); 206 } 207 208 switch (version) { 209 case V2_1: 210 //e.g. "-05:00" 211 try { 212 return new Timezone(UtcOffset.parse(value)); 213 } catch (IllegalArgumentException e) { 214 throw new CannotParseException(19); 215 } 216 case V3_0: 217 case V4_0: 218 try { 219 return new Timezone(UtcOffset.parse(value)); 220 } catch (IllegalArgumentException e) { 221 if (dataType == VCardDataType.UTC_OFFSET) { 222 warnings.add(Messages.INSTANCE.getParseMessage(20)); 223 } 224 return new Timezone(value); 225 } 226 } 227 228 return new Timezone((String) null); 229 } 230 231 private UtcOffset offsetFromTimezone(TimeZone timezone) { 232 long offsetMs = timezone.getOffset(System.currentTimeMillis()); 233 int hours = (int) (offsetMs / 1000 / 60 / 60); 234 int minutes = (int) ((offsetMs / 1000 / 60) % 60); 235 if (minutes < 0) { 236 minutes *= -1; 237 } 238 return new UtcOffset(hours, minutes); 239 } 240 241 private TimeZone timezoneFromId(String id) { 242 TimeZone timezone = TimeZone.getTimeZone(id); 243 return "GMT".equals(timezone.getID()) ? null : timezone; 244 } 245 }