001    package ezvcard.types;
002    
003    import java.util.Date;
004    import java.util.List;
005    
006    import ezvcard.VCard;
007    import ezvcard.VCardSubTypes;
008    import ezvcard.VCardVersion;
009    import ezvcard.io.CompatibilityMode;
010    import ezvcard.io.SkipMeException;
011    import ezvcard.parameters.CalscaleParameter;
012    import ezvcard.parameters.ValueParameter;
013    import ezvcard.util.HCardElement;
014    import ezvcard.util.ISOFormat;
015    import ezvcard.util.VCardDateFormatter;
016    import ezvcard.util.VCardStringUtils;
017    import ezvcard.util.XCardElement;
018    
019    /*
020     Copyright (c) 2012, Michael Angstadt
021     All rights reserved.
022    
023     Redistribution and use in source and binary forms, with or without
024     modification, are permitted provided that the following conditions are met: 
025    
026     1. Redistributions of source code must retain the above copyright notice, this
027     list of conditions and the following disclaimer. 
028     2. Redistributions in binary form must reproduce the above copyright notice,
029     this list of conditions and the following disclaimer in the documentation
030     and/or other materials provided with the distribution. 
031    
032     THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
033     ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
034     WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
035     DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
036     ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
037     (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
038     LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
039     ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
040     (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
041     SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
042    
043     The views and conclusions contained in the software and documentation are those
044     of the authors and should not be interpreted as representing official policies, 
045     either expressed or implied, of the FreeBSD Project.
046     */
047    
048    /**
049     * Represents a type that contains a date and/or time (for example, the BDAY
050     * type).
051     * @author Michael Angstadt
052     */
053    public class DateOrTimeType extends VCardType {
054            private String text;
055            private Date date;
056            private String reducedAccuracyDate;
057    
058            /**
059             * True if the "date" or "reduceAccuracyDate" fields have a time component,
060             * false if they just contain a date.
061             */
062            private boolean dateHasTime;
063    
064            /**
065             * @param typeName the name of the type (e.g. "BDAY")
066             */
067            public DateOrTimeType(String typeName) {
068                    super(typeName);
069            }
070    
071            /**
072             * @param typeName the name of the type (e.g. "BDAY")
073             * @param date the date value
074             */
075            public DateOrTimeType(String typeName, Date date) {
076                    super(typeName);
077                    setDate(date, false);
078            }
079    
080            /**
081             * Gets the date value.
082             * @return the date value or null if not set
083             */
084            public Date getDate() {
085                    return date;
086            }
087    
088            /**
089             * Sets the value of this type to a complete date.
090             * @param date the date
091             * @param dateHasTime true if the date contains a time component, false if
092             * it's just a date
093             */
094            public void setDate(Date date, boolean dateHasTime) {
095                    this.date = date;
096                    this.dateHasTime = dateHasTime;
097                    text = null;
098                    reducedAccuracyDate = null;
099            }
100    
101            /**
102             * Gets the reduced accuracy date string. This is only supported by vCard
103             * 4.0.
104             * @return the reduced accuracy date string or null if not set
105             * @see "<a href="
106             * http://tools.ietf.org/html/rfc6350">RFC 6350</a> p.12-14 for examples"
107             */
108            public String getReducedAccuracyDate() {
109                    return reducedAccuracyDate;
110            }
111    
112            /**
113             * Sets the value of this type to a "reduced accuracy" date. This is only
114             * supported by vCard 4.0.
115             * @param reducedAccuracyDate the reduced accuracy date (e.g "--0210" for
116             * "February 10")
117             * @see "<a href="
118             * http://tools.ietf.org/html/rfc6350">RFC 6350</a> p.12-14 for examples"
119             */
120            public void setReducedAccuracyDate(String reducedAccuracyDate) {
121                    this.reducedAccuracyDate = reducedAccuracyDate;
122                    dateHasTime = reducedAccuracyDate.contains("T");
123                    text = null;
124                    date = null;
125            }
126    
127            /**
128             * Gets the text value of this type. This is only supported by vCard 4.0.
129             * @return the text value or null if not set
130             */
131            public String getText() {
132                    return text;
133            }
134    
135            /**
136             * Sets the value of this type to a text string. This is only supported by
137             * vCard 4.0.
138             * @param text the text value
139             */
140            public void setText(String text) {
141                    this.text = text;
142                    date = null;
143                    reducedAccuracyDate = null;
144            }
145    
146            /**
147             * Gets the type of calendar this date uses.
148             * <p>
149             * vCard versions: 4.0
150             * </p>
151             * @return the type of calendar or null if not set
152             */
153            public CalscaleParameter getCalscale() {
154                    return subTypes.getCalscale();
155            }
156    
157            /**
158             * Sets the type of calendar this date uses.
159             * <p>
160             * vCard versions: 4.0
161             * </p>
162             * @param calscale the type of calendar or null to remove
163             */
164            public void setCalsclae(CalscaleParameter calscale) {
165                    subTypes.setCalscale(calscale);
166            }
167    
168            /**
169             * Gets the ALTID.
170             * <p>
171             * vCard versions: 4.0
172             * </p>
173             * @return the ALTID or null if it doesn't exist
174             * @see VCardSubTypes#getAltId
175             */
176            public String getAltId() {
177                    return subTypes.getAltId();
178            }
179    
180            /**
181             * Sets the ALTID.
182             * <p>
183             * vCard versions: 4.0
184             * </p>
185             * @param altId the ALTID or null to remove
186             * @see VCardSubTypes#setAltId
187             */
188            public void setAltId(String altId) {
189                    subTypes.setAltId(altId);
190            }
191    
192            @Override
193            protected void doMarshalSubTypes(VCardSubTypes copy, VCardVersion version, List<String> warnings, CompatibilityMode compatibilityMode, VCard vcard) {
194                    if (version == VCardVersion.V4_0) {
195                            if (date != null || reducedAccuracyDate != null) {
196                                    copy.setValue(ValueParameter.DATE_AND_OR_TIME);
197                                    if (getCalscale() == null) {
198                                            copy.setCalscale(CalscaleParameter.GREGORIAN);
199                                    }
200                            } else if (text != null) {
201                                    copy.setValue(ValueParameter.TEXT);
202                            }
203                    } else {
204                            if (dateHasTime) {
205                                    copy.setValue(ValueParameter.DATE_TIME);
206                            } else {
207                                    copy.setValue(ValueParameter.DATE);
208                            }
209                    }
210            }
211    
212            @Override
213            protected void doMarshalText(StringBuilder sb, VCardVersion version, List<String> warnings, CompatibilityMode compatibilityMode) {
214                    if (version == VCardVersion.V2_1 || version == VCardVersion.V3_0) {
215                            if (text != null) {
216                                    throw new SkipMeException("Text values are not allowed in vCard version " + version + ".");
217                            } else if (reducedAccuracyDate != null) {
218                                    throw new SkipMeException("Reduced accuracy dates are not allowed in vCard version " + version + ".");
219                            } else if (date != null) {
220                                    ISOFormat format = dateHasTime ? ISOFormat.TIME_BASIC : ISOFormat.DATE_BASIC;
221                                    sb.append(VCardDateFormatter.format(date, format));
222                            } else {
223                                    throw new SkipMeException("Property has no date value associated with it.");
224                            }
225                    } else {
226                            if (text != null) {
227                                    sb.append(VCardStringUtils.escape(text));
228                            } else if (reducedAccuracyDate != null) {
229                                    sb.append(reducedAccuracyDate);
230                            } else if (date != null) {
231                                    ISOFormat format = dateHasTime ? ISOFormat.TIME_BASIC : ISOFormat.DATE_BASIC;
232                                    sb.append(VCardDateFormatter.format(date, format));
233                            } else {
234                                    throw new SkipMeException("Property has no date, reduced accuracy date, or text value associated with it.");
235                            }
236                    }
237            }
238    
239            @Override
240            protected void doUnmarshalText(String value, VCardVersion version, List<String> warnings, CompatibilityMode compatibilityMode) {
241                    if (version == VCardVersion.V4_0) {
242                            if (subTypes.getValue() == ValueParameter.TEXT) {
243                                    text = VCardStringUtils.unescape(value);
244                            } else if (value.contains("-")) {
245                                    reducedAccuracyDate = value;
246                            } else {
247                                    try {
248                                            date = VCardDateFormatter.parse(value);
249                                    } catch (IllegalArgumentException e) {
250                                            //not all reduced accuracy dates have dashes (e.g. "2012")
251                                            if (value.matches("\\d+")) {
252                                                    reducedAccuracyDate = value;
253                                            } else {
254                                                    warnings.add("Date string \"" + value + "\" could not be parsed.  Assuming it's a text value.");
255                                                    text = VCardStringUtils.unescape(value);
256                                            }
257                                    }
258                            }
259                    } else {
260                            try {
261                                    date = VCardDateFormatter.parse(value);
262                            } catch (IllegalArgumentException e) {
263                                    warnings.add("Date string \"" + value + "\" for type \"" + typeName + "\" could not be parsed.");
264                            }
265                    }
266            }
267    
268            @Override
269            protected void doMarshalXml(XCardElement parent, List<String> warnings, CompatibilityMode compatibilityMode) {
270                    if (text != null) {
271                            parent.text(text);
272                    } else if (reducedAccuracyDate != null) {
273                            parent.dateAndOrTime(reducedAccuracyDate);
274                    } else if (date != null) {
275                            ISOFormat format = dateHasTime ? ISOFormat.TIME_BASIC : ISOFormat.DATE_BASIC;
276                            String value = VCardDateFormatter.format(date, format);
277                            parent.dateAndOrTime(value);
278                    } else {
279                            throw new SkipMeException("Property has no date, reduced accuracy date, or text value associated with it.");
280                    }
281            }
282    
283            @Override
284            protected void doUnmarshalXml(XCardElement element, List<String> warnings, CompatibilityMode compatibilityMode) {
285                    String value = element.dateAndOrTime();
286                    if (value != null) {
287                            if (value.contains("-")) {
288                                    setReducedAccuracyDate(value);
289                            } else {
290                                    try {
291                                            boolean hasTime = value.contains("T");
292                                            setDate(VCardDateFormatter.parse(value), hasTime);
293                                    } catch (IllegalArgumentException e) {
294                                            //not all reduced accuracy dates have dashes (e.g. "2012")
295                                            if (value.matches("\\d+")) {
296                                                    setReducedAccuracyDate(value);
297                                            } else {
298                                                    warnings.add("Date string \"" + value + "\" could not be parsed.  Assuming it's a text value.");
299                                                    setText(value);
300                                            }
301                                    }
302                            }
303                    } else {
304                            setText(element.text());
305                    }
306            }
307    
308            @Override
309            protected void doUnmarshalHtml(HCardElement element, List<String> warnings) {
310                    String value = null;
311                    if ("time".equals(element.tagName())) {
312                            String datetime = element.attr("datetime");
313                            if (datetime.length() > 0) {
314                                    value = datetime;
315                            }
316                    }
317                    if (value == null) {
318                            value = element.value();
319                    }
320                    doUnmarshalText(value, VCardVersion.V3_0, warnings, CompatibilityMode.RFC);
321            }
322    }