Package freemarker.template.utility

Source Code of freemarker.template.utility.DateUtil$TimeZoneOffsetCalculator

/*
* Copyright (c) 2003 The Visigoth Software Society. All rights
* reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
*    notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
*    notice, this list of conditions and the following disclaimer in
*    the documentation and/or other materials provided with the
*    distribution.
*
* 3. The end-user documentation included with the redistribution, if
*    any, must include the following acknowledgement:
*       "This product includes software developed by the
*        Visigoth Software Society (http://www.visigoths.org/)."
*    Alternately, this acknowledgement may appear in the software itself,
*    if and wherever such third-party acknowledgements normally appear.
*
* 4. Neither the name "FreeMarker", "Visigoth", nor any of the names of the
*    project contributors may be used to endorse or promote products derived
*    from this software without prior written permission. For written
*    permission, please contact visigoths@visigoths.org.
*
* 5. Products derived from this software may not be called "FreeMarker" or "Visigoth"
*    nor may "FreeMarker" or "Visigoth" appear in their names
*    without prior written permission of the Visigoth Software Society.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED.  IN NO EVENT SHALL THE VISIGOTH SOFTWARE SOCIETY OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Visigoth Software Society. For more
* information on the Visigoth Software Society, please see
* http://www.visigoths.org/
*/

package freemarker.template.utility;

import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Locale;
import java.util.TimeZone;

public class DateUtil {

    public static final int ACCURACY_HOURS = 4;
    public static final int ACCURACY_MINUTES = 5;
    public static final int ACCURACY_SECONDS = 6;
    public static final int ACCURACY_MILLISECONDS = 7;
   
    public static final TimeZone UTC = TimeZone.getTimeZone("UTC");
   
    private final static TimeZoneOffsetCalculator TIME_ZONE_OFFSET_CALCULATOR
            = getTimeZoneOffsetCalculator();
   
    private static TimeZoneOffsetCalculator getTimeZoneOffsetCalculator() {
        try {
            Class cl = Class.forName(
                    "freemarker.template.utility.J2SE14TimeZoneOffsetCalculator");
            return (TimeZoneOffsetCalculator) cl.newInstance();
        } catch (final Throwable e) {
            return new TimeZoneOffsetCalculator() {
                public int getOffset(TimeZone tz, Date date) {
                    throw new RuntimeException(
                            "Failed to create TimeZoneOffsetCalculator. " +
                            "Note that this feature requires at least " +
                            "Java 1.4.\nCause exception: " + e);
                }
               
            };
        }
    }
   
    private DateUtil() {
        // can't be instantiated
    }
   
    /**
     * Returns the time zone object for the name (or ID). This differs from
     * {@link TimeZone#getTimeZone(String)} in that the latest returns GMT
     * if it doesn't recognize the name, while this throws an
     * {@link UnrecognizedTimeZoneException}.
     *
     * @throws UnrecognizedTimeZoneException
     */
    public static TimeZone getTimeZone(String name)
    throws UnrecognizedTimeZoneException {
        if (isGMTish(name)) {
            if (name.equalsIgnoreCase("UTC")) {
                return UTC;
            }
            return TimeZone.getTimeZone(name);
        }
        TimeZone tz = TimeZone.getTimeZone(name);
        if (isGMTish(tz.getID())) {
            throw new UnrecognizedTimeZoneException(name);
        }
        return tz;
    }

    /**
     * Tells if a offset or time zone is GMT. GMT is a fuzzy term, it used to
     * referred both to UTC and UT1.
     */
    private static boolean isGMTish(String name) {
        if (name.length() < 3) {
            return false;
        }
        char c1 = name.charAt(0);
        char c2 = name.charAt(1);
        char c3 = name.charAt(2);
        if (
                !(
                       (c1 == 'G' || c1 == 'g')
                    && (c2 == 'M' || c2 == 'm')
                    && (c3 == 'T' || c3 == 't')
                )
                &&
                !(
                       (c1 == 'U' || c1 == 'u')
                    && (c2 == 'T' || c2 == 't')
                    && (c3 == 'C' || c3 == 'c')
                )
                &&
                !(
                       (c1 == 'U' || c1 == 'u')
                    && (c2 == 'T' || c2 == 't')
                    && (c3 == '1' || c3 == '1')
                )
                ) {
            return false;
        }
       
        if (name.length() == 3) {
            return true;
        }
       
        String offset = name.substring(3);
        if (offset.startsWith("+")) {
            return offset.equals("+0") || offset.equals("+00")
                    || offset.equals("+00:00");
        } else {
            return offset.equals("-0") || offset.equals("-00")
            || offset.equals("-00:00");
        }
    }
   
    /**
     * Format a date, time or date+time with one of the ISO 8601 extended
     * formats. Examples of possible outputs: {@code "2005-11-27T15:30:00+02"},
     * {@code "2005-11-27"}, {@code "15:30:00Z"}.
     *
     * This method is thread-safe.
     *
     * @param date the date to convert to ISO 8601 string
     * @param datePart whether the date part (year, month, day) will be included
     *        or not
     * @param timePart whether the time part (hours, minutes, seconds,
     *        milliseconds) will be included or not
     * @param offsetPart whether the time zone offset part will be included or
     *        not. This will be shown as an offset to UTC (examples:
     *        {@code "+01"}, {@code "-02"}, {@code "+04:30"}) or as {@code "Z"}
     *        for UTC (and for UT1 and for GMT+00, since the Java platform
     *        doesn't really care about the difference).
     *        Note that this can't be {@code true} when {@code timePart} is
     *        {@code false}, because ISO 8601 (2004) doesn't mention such
     *        patterns.
     * @param accuracy tells which parts of the date/time to drop. The
     *        {@code datePart} and {@code timePart} parameters are stronger than
     *        this. Note that when {@link #ACCURACY_MILLISECONDS} is specified,
     *        the milliseconds part will be displayed as fraction seconds
     *        (like {@code "15:30.00.25"}) with the minimum number of
     *        digits needed to show the milliseconds without precision lose.
     *        Thus, if the milliseconds happen to be exactly 0, no fraction
     *        seconds will be shown at all.
     * @param timeZone the time zone in which the date/time will be shown. (You
     *        may find {@link DateUtil#UTC} handy here.) Note
     *        that although date-only formats has no time zone offset part,
     *        the result still depends on the time zone, as days start and end
     *        at different points in time in different zones.     
     * @param calendarFactory the factory that will create the calendar used
     *        internally for calculations. The point of this parameter is that
     *        creating a new calendar is relatively expensive, so it's desirable
     *        to reuse calendars and only set their time and zone. (This was
     *        tested on Sun JDK 1.6 x86 Win, where it gave 2x-3x speedup.)
     */
    public static String dateToISO8601String(
            Date date,
            boolean datePart, boolean timePart, boolean offsetPart,
            int accuracy,
            TimeZone timeZone,
            DateToISO8601CalendarFactory calendarFactory) {
        if (!timePart && offsetPart) {
            throw new IllegalArgumentException(
                    "ISO 8601:2004 doesn't specify any formats where the "
                    + "offset is shown but the time isn't.");
        }
       
        if (timeZone == null) {
            timeZone = UTC;
        }
       
        GregorianCalendar cal = calendarFactory.get(timeZone, date);

        int maxLength;
        if (!timePart) {
            maxLength = 10// YYYY-MM-DD
        } else {
            if (!datePart) {
                maxLength = 18// HH:MM:SS.mmm+00:00
            } else {
                maxLength = 10 + 1 + 18;
            }
        }
        char[] res = new char[maxLength];
        int dstIdx = 0;
       
        if (datePart) {
            int x = cal.get(Calendar.YEAR);
            if (x > 0 && cal.get(Calendar.ERA) == GregorianCalendar.BC) {
                x = -x + 1;
            }
            if (x >= 0 && x < 9999) {
                res[dstIdx++] = (char) ('0' + x / 1000);
                res[dstIdx++] = (char) ('0' + x % 1000 / 100);
                res[dstIdx++] = (char) ('0' + x % 100 / 10);
                res[dstIdx++] = (char) ('0' + x % 10);
            } else {
                String yearString = String.valueOf(x);
               
                // Re-allocate buffer:
                maxLength = maxLength - 4 + yearString.length();
                res = new char[maxLength];
               
                for (int i = 0; i < yearString.length(); i++) {
                    res[dstIdx++] = yearString.charAt(i);
                }
            }
   
            res[dstIdx++] = '-';
           
            x = cal.get(Calendar.MONTH) + 1;
            dstIdx = append00(res, dstIdx, x);
   
            res[dstIdx++] = '-';
           
            x = cal.get(Calendar.DAY_OF_MONTH);
            dstIdx = append00(res, dstIdx, x);

            if (timePart) {
                res[dstIdx++] = 'T';
            }
        }

        if (timePart) {
            int x = cal.get(Calendar.HOUR_OF_DAY);
            dstIdx = append00(res, dstIdx, x);
   
            if (accuracy >= ACCURACY_MINUTES) {
                res[dstIdx++] = ':';
       
                x = cal.get(Calendar.MINUTE);
                dstIdx = append00(res, dstIdx, x);
       
                if (accuracy >= ACCURACY_SECONDS) {
                    res[dstIdx++] = ':';
           
                    x = cal.get(Calendar.SECOND);
                    dstIdx = append00(res, dstIdx, x);
           
                    if (accuracy >= ACCURACY_MILLISECONDS) {
                        x = cal.get(Calendar.MILLISECOND);
                        if (x != 0) {
                            if (x > 999) {
                                // Shouldn't ever happen...
                                throw new RuntimeException(
                                        "Calendar.MILLISECOND > 999");
                            }
                            res[dstIdx++] = '.';
                            do {
                                res[dstIdx++] = (char) ('0' + (x / 100));
                                x = x % 100 * 10;
                            } while (x != 0);
                        }
                    }
                }
            }
        }

        if (offsetPart) {
            if (timeZone == UTC) {
                res[dstIdx++] = 'Z';
            } else {
                int dt = TIME_ZONE_OFFSET_CALCULATOR.getOffset(timeZone, date);
                boolean positive;
                if (dt < 0) {
                    positive = false;
                    dt = -dt;
                } else {
                    positive = true;
                }
               
                dt /= 1000;
                int offS = dt % 60;
                dt /= 60;
                int offM = dt % 60;
                dt /= 60;
                int offH = dt;
               
                if (offS == 0 && offM == 0 && offH == 0) {
                    res[dstIdx++] = 'Z';
                } else {
                    res[dstIdx++] = positive ? '+' : '-';
                    dstIdx = append00(res, dstIdx, offH);
                    if (offM != 0 || offS != 0) {
                        res[dstIdx++] = ':';
                        dstIdx = append00(res, dstIdx, offM);
                        if (offS != 0) {
                            res[dstIdx++] = ':';
                            dstIdx = append00(res, dstIdx, offS);
                        }
                    }
                }
            }
        }
       
        return new String(res, 0, dstIdx);
    }
   
    /**
     * Appends a number between 0 and 99 padded to 2 digits.
     */
    private static int append00(char[] res, int dstIdx, int x) {
        res[dstIdx++] = (char) ('0' + x / 10);
        res[dstIdx++] = (char) ('0' + x % 10);
        return dstIdx;
    }
   
    /**
     * Used internally by {@link DateUtil}; don't use it's implementations for
     * anything else.
     */
    public interface DateToISO8601CalendarFactory {
       
        /**
         * Returns a {@link GregorianCalendar} with the desired time zone and
         * time and US locale. The returned calendar is used as read-only.
         * It's guaranteed that within a thread the instance returned last time
         * is not in use anymore when this method is called again.
         */
        GregorianCalendar get(TimeZone tz, Date date);
    }
   
    /**
     * Non-thread-safe factory that hard-references a calendar internally.
     */
    public static final class TrivialDateToISO8601CalendarFactory
            implements DateToISO8601CalendarFactory {
       
        private GregorianCalendar calendar;

        public GregorianCalendar get(TimeZone tz, Date date) {
            if (calendar == null) {
                calendar = new GregorianCalendar(tz, Locale.US);
            } else {
                calendar.setTimeZone(tz);
            }
            calendar.setTime(date);
            return calendar;
        }
       
       
    }
   
    interface TimeZoneOffsetCalculator {
        int getOffset(TimeZone tz, Date date);
    }

}
TOP

Related Classes of freemarker.template.utility.DateUtil$TimeZoneOffsetCalculator

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.