001package ezvcard.property;
002
003import java.time.DateTimeException;
004import java.time.OffsetDateTime;
005import java.time.ZoneId;
006import java.time.ZoneOffset;
007import java.util.LinkedHashMap;
008import java.util.List;
009import java.util.Map;
010import java.util.Objects;
011
012import ezvcard.VCard;
013import ezvcard.VCardVersion;
014import ezvcard.ValidationWarning;
015import ezvcard.parameter.Pid;
016
017/*
018 Copyright (c) 2012-2026, 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 The views and conclusions contained in the software and documentation are those
042 of the authors and should not be interpreted as representing official policies, 
043 either expressed or implied, of the FreeBSD Project.
044 */
045
046/**
047 * <p>
048 * Defines the timezone that the person lives/works in.
049 * </p>
050 * 
051 * <p>
052 * <b>Code sample</b>
053 * </p>
054 * 
055 * <pre class="brush:java">
056 * VCard vcard = new VCard();
057 * 
058 * Timezone tz = new Timezone(ZoneOffset.ofHours(-5), "America/New_York");
059 * vcard.addTimezone(tz);
060 * 
061 * //using a Java "ZoneId" object
062 * ZoneId zoneId = ZoneId.of("America/New_York");
063 * tz = new Timezone(zoneId);
064 * vcard.addTimezone(tz);
065 * </pre>
066 * 
067 * <p>
068 * <b>Property name:</b> {@code TZ}
069 * </p>
070 * <p>
071 * <b>Supported versions:</b> {@code 2.1, 3.0, 4.0}
072 * </p>
073 * 
074 * @author Michael Angstadt
075 * @see <a href="http://tools.ietf.org/html/rfc6350#page-22">RFC 6350 p.22</a>
076 * @see <a href="http://tools.ietf.org/html/rfc2426#page-16">RFC 2426 p.16</a>
077 * @see <a href="http://www.imc.org/pdi/vcard-21.doc">vCard 2.1 p.16</a>
078 */
079public class Timezone extends VCardProperty implements HasAltId {
080        private ZoneOffset offset;
081        private String text;
082
083        /**
084         * Creates a timezone property.
085         * @param text a free-form string representing the timezone, preferably a
086         * timezone ID from the <a
087         * href="http://en.wikipedia.org/wiki/List_of_tz_database_time_zones">Olson
088         * Database</a> (e.g. "America/New_York")
089         */
090        public Timezone(String text) {
091                this(null, text);
092        }
093
094        /**
095         * Creates a timezone property.
096         * @param offset the UTC offset
097         */
098        public Timezone(ZoneOffset offset) {
099                this(offset, null);
100        }
101
102        /**
103         * Creates a timezone property.
104         * @param offset the UTC offset
105         * @param text a free-form string representing the timezone, preferably a
106         * timezone ID from the <a
107         * href="http://en.wikipedia.org/wiki/List_of_tz_database_time_zones">Olson
108         * Database</a> (e.g. "America/New_York")
109         */
110        public Timezone(ZoneOffset offset, String text) {
111                setOffset(offset);
112                setText(text);
113        }
114
115        /**
116         * Creates a timezone property.
117         * @param timezone the timezone
118         */
119        public Timezone(ZoneId timezone) {
120                this(OffsetDateTime.now(timezone).getOffset(), timezone.getId());
121        }
122
123        /**
124         * Copy constructor.
125         * @param original the property to make a copy of
126         */
127        public Timezone(Timezone original) {
128                super(original);
129                offset = original.offset;
130                text = original.text;
131        }
132
133        /**
134         * Gets the UTC offset.
135         * @return the UTC offset or null if not set
136         */
137        public ZoneOffset getOffset() {
138                return offset;
139        }
140
141        /**
142         * Sets the UTC offset.
143         * @param offset the UTC offset
144         */
145        public void setOffset(ZoneOffset offset) {
146                this.offset = offset;
147        }
148
149        /**
150         * Gets the text portion of the timezone.
151         * @return the free-form string representing the timezone, such as a
152         * timezone ID from the <a
153         * href="http://en.wikipedia.org/wiki/List_of_tz_database_time_zones">Olson
154         * Database</a> (e.g. "America/New_York")
155         */
156        public String getText() {
157                return text;
158        }
159
160        /**
161         * Sets the text portion of the timezone.
162         * @param text a free-form string representing the timezone, preferably a
163         * timezone ID from the <a
164         * href="http://en.wikipedia.org/wiki/List_of_tz_database_time_zones">Olson
165         * Database</a> (e.g. "America/New_York")
166         */
167        public void setText(String text) {
168                this.text = text;
169        }
170
171        /**
172         * Creates a {@link ZoneId} representation of this class.
173         * @return a {@link ZoneId} object or null if this object contains no
174         * offset data
175         */
176        public ZoneId toTimeZone() {
177                if (text != null) {
178                        try {
179                                return ZoneId.of(text);
180                        } catch (DateTimeException ignore) {
181                                //not a recognized timezone
182                        }
183                }
184
185                if (offset != null) {
186                        return offset;
187                }
188
189                return null;
190        }
191
192        /**
193         * Gets the TYPE parameter.
194         * <p>
195         * <b>Supported versions:</b> {@code 4.0}
196         * </p>
197         * @return the TYPE value (typically, this will be either "work" or "home")
198         * or null if it doesn't exist
199         */
200        public String getType() {
201                return parameters.getType();
202        }
203
204        /**
205         * Sets the TYPE parameter.
206         * <p>
207         * <b>Supported versions:</b> {@code 4.0}
208         * </p>
209         * @param type the TYPE value (this should be either "work" or "home") or
210         * null to remove
211         */
212        public void setType(String type) {
213                parameters.setType(type);
214        }
215
216        /**
217         * Gets the MEDIATYPE parameter.
218         * <p>
219         * <b>Supported versions:</b> {@code 4.0}
220         * </p>
221         * @return the media type or null if not set
222         */
223        public String getMediaType() {
224                return parameters.getMediaType();
225        }
226
227        /**
228         * Sets the MEDIATYPE parameter.
229         * <p>
230         * <b>Supported versions:</b> {@code 4.0}
231         * </p>
232         * @param mediaType the media type or null to remove
233         */
234        public void setMediaType(String mediaType) {
235                parameters.setMediaType(mediaType);
236        }
237
238        @Override
239        public List<Pid> getPids() {
240                return super.getPids();
241        }
242
243        @Override
244        public Integer getPref() {
245                return super.getPref();
246        }
247
248        @Override
249        public void setPref(Integer pref) {
250                super.setPref(pref);
251        }
252
253        //@Override
254        public String getAltId() {
255                return parameters.getAltId();
256        }
257
258        //@Override
259        public void setAltId(String altId) {
260                parameters.setAltId(altId);
261        }
262
263        @Override
264        protected void _validate(List<ValidationWarning> warnings, VCardVersion version, VCard vcard) {
265                if (offset == null && text == null) {
266                        warnings.add(new ValidationWarning(8));
267                }
268                if (offset == null && version == VCardVersion.V2_1) {
269                        warnings.add(new ValidationWarning(20));
270                }
271        }
272
273        @Override
274        protected Map<String, Object> toStringValues() {
275                Map<String, Object> values = new LinkedHashMap<>();
276                values.put("offset", offset);
277                values.put("text", text);
278                return values;
279        }
280
281        @Override
282        public Timezone copy() {
283                return new Timezone(this);
284        }
285
286        @Override
287        public int hashCode() {
288                final int prime = 31;
289                int result = super.hashCode();
290                result = prime * result + Objects.hash(offset, text);
291                return result;
292        }
293
294        @Override
295        public boolean equals(Object obj) {
296                if (this == obj) return true;
297                if (!super.equals(obj)) return false;
298                if (getClass() != obj.getClass()) return false;
299                Timezone other = (Timezone) obj;
300                return Objects.equals(offset, other.offset) && Objects.equals(text, other.text);
301        }
302}