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    }