001/* ***** BEGIN LICENSE BLOCK *****
002 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
003 *
004 * The contents of this file are subject to the Mozilla Public License Version
005 * 1.1 (the "License"); you may not use this file except in compliance with
006 * the License. You may obtain a copy of the License at
007 * http://www.mozilla.org/MPL/
008 *
009 * Software distributed under the License is distributed on an "AS IS" basis,
010 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
011 * for the specific language governing rights and limitations under the
012 * License.
013 *
014 * The Original Code is part of dcm4che, an implementation of DICOM(TM) in
015 * Java(TM), hosted at https://github.com/gunterze/dcm4che.
016 *
017 * The Initial Developer of the Original Code is
018 * Agfa Healthcare.
019 * Portions created by the Initial Developer are Copyright (C) 2011
020 * the Initial Developer. All Rights Reserved.
021 *
022 * Contributor(s):
023 * See @authors listed below
024 *
025 * Alternatively, the contents of this file may be used under the terms of
026 * either the GNU General Public License Version 2 or later (the "GPL"), or
027 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
028 * in which case the provisions of the GPL or the LGPL are applicable instead
029 * of those above. If you wish to allow use of your version of this file only
030 * under the terms of either the GPL or the LGPL, and not to allow others to
031 * use your version of this file under the terms of the MPL, indicate your
032 * decision by deleting the provisions above and replace them with the notice
033 * and other provisions required by the GPL or the LGPL. If you do not delete
034 * the provisions above, a recipient may use your version of this file under
035 * the terms of any one of the MPL, the GPL or the LGPL.
036 *
037 * ***** END LICENSE BLOCK ***** */
038
039package org.dcm4che3.util;
040
041import java.util.Calendar;
042import java.util.Date;
043import java.util.GregorianCalendar;
044import java.util.TimeZone;
045
046import org.dcm4che3.data.DatePrecision;
047
048/**
049 * @author Gunter Zeilinger <gunterze@gmail.com>
050 */
051public class DateUtils {
052
053    public static final Date[] EMPTY_DATES = {};
054
055    private static TimeZone cachedTimeZone;
056
057    private static Calendar cal(TimeZone tz) {
058        Calendar cal = (tz != null)
059                ? new GregorianCalendar(tz)
060                : new GregorianCalendar();
061        cal.clear();
062        return cal;
063    }
064
065    private static Calendar cal(TimeZone tz, Date date) {
066        Calendar cal = (tz != null)
067                ? new GregorianCalendar(tz)
068                : new GregorianCalendar();
069        cal.setTime(date);
070        return cal;
071    }
072
073    private static void ceil(Calendar cal, int field) {
074        cal.add(field, 1);
075        cal.add(Calendar.MILLISECOND, -1);
076    }
077
078    public static String formatDA(TimeZone tz, Date date) {
079        return formatDA(tz, date, new StringBuilder(8)).toString();
080    }
081
082    public static StringBuilder formatDA(TimeZone tz, Date date,
083            StringBuilder toAppendTo) {
084        return formatDT(cal(tz, date), toAppendTo, Calendar.DAY_OF_MONTH);
085    }
086
087    public static String formatTM(TimeZone tz, Date date) {
088        return formatTM(tz, date, new DatePrecision());
089    }
090
091    public static String formatTM(TimeZone tz, Date date, DatePrecision precision) {
092        return formatTM(cal(tz, date), new StringBuilder(10),
093                precision.lastField).toString();
094    }
095
096    private static StringBuilder formatTM(Calendar cal, 
097            StringBuilder toAppendTo, int lastField) {
098        appendXX(cal.get(Calendar.HOUR_OF_DAY), toAppendTo);
099        if (lastField > Calendar.HOUR_OF_DAY) {
100            appendXX(cal.get(Calendar.MINUTE), toAppendTo);
101            if (lastField > Calendar.MINUTE) {
102                appendXX(cal.get(Calendar.SECOND), toAppendTo);
103                if (lastField > Calendar.SECOND) {
104                    toAppendTo.append('.');
105                    appendXXX(cal.get(Calendar.MILLISECOND), toAppendTo);
106                }
107            }
108        }
109        return toAppendTo;
110    }
111
112    public static String formatDT(TimeZone tz, Date date) {
113        return formatDT(tz, date, new DatePrecision());
114    }
115
116    public static String formatDT(TimeZone tz, Date date, DatePrecision precision) {
117        return formatDT(tz, date, new StringBuilder(23), precision).toString();
118    }
119
120    public static StringBuilder formatDT(TimeZone tz, Date date,
121            StringBuilder toAppendTo, DatePrecision precision) {
122        Calendar cal = cal(tz, date);
123        formatDT(cal, toAppendTo, precision.lastField);
124        if (precision.includeTimezone) {
125            int offset = cal.get(Calendar.ZONE_OFFSET)
126                    + cal.get(Calendar.DST_OFFSET);
127            appendZZZZZ(offset, toAppendTo);
128        }
129        return toAppendTo;
130    }
131
132    private static StringBuilder appendZZZZZ(int offset, StringBuilder sb) {
133        if (offset < 0) {
134            offset = -offset;
135            sb.append('-');
136        } else
137            sb.append('+');
138        int min = offset / 60000;
139        appendXX(min / 60, sb);
140        appendXX(min % 60, sb);
141        return sb;
142    }
143
144
145    /**
146     * Returns Timezone Offset From UTC in format {@code (+|i)HHMM} of specified
147     * Timezone without concerning Daylight saving time (DST).
148     *
149     * @param tz Timezone
150     * @return Timezone Offset From UTC in format {@code (+|i)HHMM}
151     */
152    public static String formatTimezoneOffsetFromUTC(TimeZone tz) {
153        return appendZZZZZ(tz.getRawOffset(), new StringBuilder(5)).toString();
154    }
155
156    /**
157     * Returns Timezone Offset From UTC in format {@code (+|i)HHMM} of specified
158     * Timezone on specified date. If no date is specified, DST is considered
159     * for the current date.
160     *
161     * @param tz Timezone
162     * @param date Date or {@code null}
163     * @return Timezone Offset From UTC in format {@code (+|i)HHMM}
164     */
165    public static String formatTimezoneOffsetFromUTC(TimeZone tz, Date date) {
166        return appendZZZZZ(tz.getOffset(date == null 
167                ? System.currentTimeMillis() : date.getTime()), 
168                new StringBuilder(5)).toString();
169    }
170
171    private static StringBuilder formatDT(Calendar cal, StringBuilder toAppendTo,
172            int lastField) {
173        appendXXXX(cal.get(Calendar.YEAR), toAppendTo);
174        if (lastField > Calendar.YEAR) {
175            appendXX(cal.get(Calendar.MONTH) + 1, toAppendTo);
176            if (lastField > Calendar.MONTH) {
177                appendXX(cal.get(Calendar.DAY_OF_MONTH), toAppendTo);
178                if (lastField > Calendar.DAY_OF_MONTH) {
179                    formatTM(cal, toAppendTo, lastField);
180                }
181            }
182        }
183        return toAppendTo;
184    }
185
186    private static void appendXXXX(int i, StringBuilder toAppendTo) {
187        if (i < 1000)
188            toAppendTo.append('0');
189        appendXXX(i, toAppendTo);
190    }
191
192    private static void appendXXX(int i, StringBuilder toAppendTo) {
193        if (i < 100)
194            toAppendTo.append('0');
195        appendXX(i, toAppendTo);
196    }
197
198    private static void appendXX(int i, StringBuilder toAppendTo) {
199        if (i < 10)
200            toAppendTo.append('0');
201        toAppendTo.append(i);
202    }
203
204    public static Date parseDA(TimeZone tz, String s) {
205        return parseDA(tz, s, false);
206    }
207
208    public static Date parseDA(TimeZone tz, String s, boolean ceil) {
209        Calendar cal = cal(tz);
210        int length = s.length();
211        if (!(length == 8 || length == 10 && !Character.isDigit(s.charAt(4))))
212            throw new IllegalArgumentException(s);
213        try {
214            int pos = 0;
215            cal.set(Calendar.YEAR,
216                    Integer.parseInt(s.substring(pos, pos + 4)));
217            pos += 4;
218            if (!Character.isDigit(s.charAt(pos)))
219                pos++;
220            cal.set(Calendar.MONTH,
221                    Integer.parseInt(s.substring(pos, pos + 2)) - 1);
222            pos += 2;
223            if (!Character.isDigit(s.charAt(pos)))
224                pos++;
225            cal.set(Calendar.DAY_OF_MONTH,
226                    Integer.parseInt(s.substring(pos)));
227            if (ceil)
228                ceil(cal, Calendar.DAY_OF_MONTH);
229        } catch (NumberFormatException e) {
230            throw new IllegalArgumentException(s);
231        }
232        return cal.getTime();
233    }
234
235    public static Date parseTM(TimeZone tz, String s, DatePrecision precision) {
236        return parseTM(tz, s, false, precision);
237    }
238
239    public static Date parseTM(TimeZone tz, String s, boolean ceil,
240            DatePrecision precision) {
241        return parseTM(cal(tz), s, ceil, precision);
242    }
243
244    private static Date parseTM(Calendar cal, String s, boolean ceil,
245            DatePrecision precision) {
246        int length = s.length();
247        int pos = 0;
248        if (pos + 2 > length)
249            throw new IllegalArgumentException(s);
250
251        try {
252            cal.set(precision.lastField = Calendar.HOUR_OF_DAY,
253                    Integer.parseInt(s.substring(pos, pos + 2)));
254            pos += 2;
255            if (pos < length) {
256                if (!Character.isDigit(s.charAt(pos)))
257                    pos++;
258                if (pos + 2 > length)
259                    throw new IllegalArgumentException(s);
260
261                cal.set(precision.lastField = Calendar.MINUTE,
262                        Integer.parseInt(s.substring(pos, pos + 2)));
263                pos += 2;
264                if (pos < length) {
265                    if (!Character.isDigit(s.charAt(pos)))
266                        pos++;
267                    if (pos + 2 > length)
268                        throw new IllegalArgumentException(s);
269                    cal.set(precision.lastField = Calendar.SECOND,
270                            Integer.parseInt(s.substring(pos, pos + 2)));
271                    pos += 2;
272                    if (pos < length) {
273                        float f = Float.parseFloat(s.substring(pos));
274                        if (f >= 1 || f < 0)
275                            throw new IllegalArgumentException(s);
276                        cal.set(precision.lastField = Calendar.MILLISECOND, 
277                                (int) (f * 1000));
278                        return cal.getTime();
279                    }
280                }
281            }
282            if (ceil)
283                ceil(cal, precision.lastField);
284        } catch (NumberFormatException e) {
285            throw new IllegalArgumentException(s);
286        }
287        return cal.getTime();
288    }
289
290    public static Date parseDT(TimeZone tz, String s, DatePrecision precision) {
291        return parseDT(tz, s, false, precision);
292    }
293
294    public static TimeZone timeZone(String s) {
295        TimeZone tz;
296        if (s.length() != 5 || (tz = safeTimeZone(s)) == null)
297            throw new IllegalArgumentException("Illegal Timezone Offset: " + s);
298        return tz;
299    }
300
301    private static TimeZone safeTimeZone(String s) {
302        String tzid = tzid(s);
303        if (tzid == null)
304            return null;
305
306        TimeZone tz = cachedTimeZone;
307        if (tz == null || !tz.getID().equals(tzid))
308            cachedTimeZone = tz = TimeZone.getTimeZone(tzid);
309
310        return tz;
311    }
312
313    private static String tzid(String s) {
314        int length = s.length();
315        if (length > 4) {
316            char[] tzid = { 'G', 'M', 'T', 0, 0, 0, ':', 0, 0 };
317            s.getChars(length-5, length-2, tzid, 3);
318            s.getChars(length-2, length, tzid, 7);
319            if ((tzid[3] == '+' || tzid[3] == '-')
320                    && Character.isDigit(tzid[4])
321                    && Character.isDigit(tzid[5])
322                    && Character.isDigit(tzid[7])
323                    && Character.isDigit(tzid[8])) {
324                return new String(tzid);
325            }
326        }
327        return null;
328    }
329
330    public static Date parseDT(TimeZone tz, String s, boolean ceil,
331            DatePrecision precision) {
332        int length = s.length();
333        TimeZone tz1 = safeTimeZone(s);
334        if (precision.includeTimezone = tz1 != null) {
335            length -= 5;
336            tz = tz1;
337        }
338        Calendar cal = cal(tz);
339        try {
340            int pos = 0;
341            if (pos + 4 > length)
342                throw new IllegalArgumentException(s);
343            cal.set(precision.lastField = Calendar.YEAR,
344                    Integer.parseInt(s.substring(pos, pos + 4)));
345            pos += 4;
346            if (pos < length) {
347                if (!Character.isDigit(s.charAt(pos)))
348                    pos++;
349                if (pos + 2 > length)
350                    throw new IllegalArgumentException(s);
351                cal.set(precision.lastField = Calendar.MONTH,
352                        Integer.parseInt(s.substring(pos,  pos + 2)) - 1);
353                pos += 2;
354                if (pos < length) {
355                    if (!Character.isDigit(s.charAt(pos)))
356                        pos++;
357                    if (pos + 2 > length)
358                        throw new IllegalArgumentException(s);
359                    cal.set(precision.lastField = Calendar.DAY_OF_MONTH,
360                            Integer.parseInt(s.substring(pos, pos + 2)));
361                    pos += 2;
362                    if (pos < length)
363                        return parseTM(cal, s.substring(pos, length), ceil,
364                                precision);
365                }
366            }
367        } catch (NumberFormatException e) {
368            throw new IllegalArgumentException(s);
369        }
370        if (ceil)
371            ceil(cal, precision.lastField);
372        return cal.getTime();
373    }
374
375}