com.yourmediashelf.fedora.util.DateUtility.java Source code

Java tutorial

Introduction

Here is the source code for com.yourmediashelf.fedora.util.DateUtility.java

Source

/**
 * Copyright (C) 2010 MediaShelf <http://www.yourmediashelf.com/>
 *
 * This file is part of fedora-client.
 *
 * fedora-client is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * fedora-client is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with fedora-client.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.yourmediashelf.fedora.util;

import java.text.DecimalFormat;
import java.util.Date;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.format.DateTimeFormatter;
import org.joda.time.format.DateTimeFormatterBuilder;

/**
 * Date and time utility methods.
 *
 * @author Edwin Shin
 */
public class DateUtility {

    /**
     * <p>
     * Regular expression string for the yearFrag:
     * </p>
     * <p>
     * a numeral consisting of at least four decimal digits, optionally preceded
     * by a minus sign; leading '0' digits are prohibited except to bring the
     * digit count up to four.
     * </p>
     */
    private final static String yearFrag = "(-?([1-9][0-9]{3,}|0[0-9]{3}))";

    /**
     * <p>
     * Regular expression string for the monthFrag:
     * </p>
     * <p>
     * a numeral consisting of exactly two decimal digits.
     * </p>
     */
    private final static String monthFrag = "(0[1-9]|1[0-2])";

    /**
     * <p>
     * Regular expression string for the dayFrag:
     * </p>
     * <p>
     * a numeral consisting of exactly two decimal digits.
     * </p>
     * <p>
     * Note that this regex does not enforce the day-of-month constraint, which
     * states:
     * </p>
     * <p>
     * The day value must be no more than 30 if month is one of 4, 6, 9, or 11;
     * no more than 28 if month is 2 and year is not divisible 4, or is
     * divisible by 100 but not by 400; and no more than 29 if month is 2 and
     * year is divisible by 400, or by 4 but not by 100.
     * </p>
     */
    private final static String dayFrag = "(0[1-9]|[12][0-9]|3[01])";

    /**
     * <p>
     * Regular expression string for the hourFrag:
     * </p>
     * <p>
     * a numeral consisting of exactly two decimal digits.
     * </p>
     */
    private final static String hourFrag = "([01][0-9]|2[0-3])";

    /**
     * <p>
     * Regular expression string for the minuteFrag:
     * </p>
     * <p>
     * a numeral consisting of exactly two decimal digits.
     * </p>
     */
    private final static String minuteFrag = "([0-5][0-9])";

    /**
     * <p>
     * Regular expression string for the secondFrag:
     * </p>
     * <p>
     * a numeral consisting of exactly two decimal digits, or two decimal
     * digits, a decimal point, and one or more trailing digits.
     * </p>
     */
    private final static String secondFrag = "([0-5][0-9])(\\.([0-9]+))?";

    /**
     * <p>
     * Regular expression string for the endOfDayFrag:
     * </p>
     * <p>
     * combines the {@link hourFrag}, {@link minuteFrag}, {@link minuteFrag},
     * and their separators to represent midnight of the day, which is the first
     * moment of the next day.
     * </p>
     */
    private final static String endOfDayFrag = String.format("(%s:%s:%s|(24:00:00(\\.0+)?))", hourFrag, minuteFrag,
            secondFrag);

    /**
     * <p>
     * Regular expression string for the timezoneFrag:
     * </p>
     * <p>
     * if present, specifies an offset between UTC and local time. Time zone
     * offsets are a count of minutes (expressed in timezoneFrag as a count of
     * hours and minutes) that are added or subtracted from UTC time to get the
     * "local" time. 'Z' is an alternative representation of the time zone
     * offset '00:00', which is, of course, zero minutes from UTC.
     * </p>
     */
    private final static String timezoneFrag = "(Z|(\\+|-)((0[0-9]|1[0-3]):[0-5][0-9]|14:00))?";

    /**
     * Concatenation of the {@link yearFrag}, {@link monthFrag}, {@link dayFrag}
     * , {@link endOfDayFrag}, {@link timezoneFrag}, and their separators.
     */
    private final static String combined = String.format("%s-%s-%sT%s%s", yearFrag, monthFrag, dayFrag,
            endOfDayFrag, timezoneFrag);

    private final static Pattern XSD_DATETIME = Pattern.compile(combined);

    private final static ConcurrentMap<String, DateTimeFormatter> formatters = new ConcurrentHashMap<String, DateTimeFormatter>();

    /**
     * Parses lexical representations of xsd:dateTimes, e.g.
     * "2010-01-31T14:21:03.001Z". Fractional seconds and timezone offset are
     * optional.
     *
     * <p>Note: fractional seconds are only supported to three digits of
     * precision.</p>
     *
     * @param input
     *        an XML Schema 1.1 dateTime
     * @return a DateTime representing the input
     * @see "http://www.w3.org/TR/xmlschema11-2/#dateTime"
     */
    public static DateTime parseXSDDateTime(String input) {
        Matcher m = XSD_DATETIME.matcher(input);
        if (!m.find()) {
            throw new IllegalArgumentException(input + " is not a valid XML Schema 1.1 dateTime.");
        }

        int year = Integer.parseInt(m.group(1));
        int month = Integer.parseInt(m.group(3));
        int day = Integer.parseInt(m.group(4));

        int hour = 0, minute = 0, second = 0, millis = 0;
        boolean hasEndOfDayFrag = m.group(11) != null;
        if (!hasEndOfDayFrag) {
            hour = Integer.parseInt(m.group(6));
            minute = Integer.parseInt(m.group(7));
            second = Integer.parseInt(m.group(8));
            // Parse fractional seconds
            // m.group(9), if not null/empty should be Strings such as ".5" or
            // ".050" which convert to 500 and 50, respectively.
            if (m.group(9) != null && !m.group(9).isEmpty()) {
                // parse as Double as a quick hack to drop trailing 0s.
                // e.g. ".0500" becomes 0.05
                double d = Double.parseDouble(m.group(9));

                // Something like the following would allow for int-sized
                // precision, but joda-time 1.6 only supports millis (i.e. <= 999).
                // see: org.joda.time.field.FieldUtils.verifyValueBounds
                //   int digits = String.valueOf(d).length() - 2;
                //   fractionalSeconds = (int) (d * Math.pow(10, digits));
                millis = (int) (d * 1000);
            }
        }

        DateTimeZone zone = null;
        if (m.group(13) != null) {
            String tmp = m.group(13);
            if (tmp.equals("Z")) {
                tmp = "+00:00";
            }
            zone = DateTimeZone.forID(tmp);
        }
        DateTime dt = new DateTime(year, month, day, hour, minute, second, millis, zone);

        if (hasEndOfDayFrag) {
            return dt.plusDays(1);
        }
        return dt;
    }

    /**
     * Convenience method that accepts a {@link java.util.Date}.
     *
     * @param date
     * @return An xsd:dateTime (in UTC) representation of date.
     * @see #getXSDFormatter(DateTime)
     */
    public static String getXSDDateTime(Date date) {
        return getXSDDateTime(new DateTime(date));
    }

    /**
     * Formats a {@link DateTime} as an xsd:dateTime in canonical form.
     *
     * <p>Note: fractional seconds are only supported to a maximum of three
     * digits.</p>
     *
     * @param dateTime
     * @return An xsd:dateTime (in UTC) representation of date.
     * @see "http://www.w3.org/TR/xmlschema11-2/#dateTime"
     */
    public static String getXSDDateTime(DateTime dateTime) {
        return dateTime.withZone(DateTimeZone.UTC).toString(getXSDFormatter(dateTime));
    }

    public static DateTimeFormatter getXSDFormatter(DateTime date) {
        int len = 0;
        int millis = date.getMillisOfSecond();

        if (millis > 0) {
            // 0.050 becomes .05 (up to three digits, dropping trailing 0s)
            DecimalFormat df = new DecimalFormat(".###");
            double d = millis / 1000.0;
            len = String.valueOf(df.format(d)).length() - 1;
        }
        return getXSDFormatter(len);
    }

    /**
     * Returns an xsd:dateTime formatter with the specified millisecond precision.
     *
     * @param millisLength number of digits of millisecond precision. Currently,
     * only 0-3 are valid arguments.
     * @return a formatter for yyyy-MM-dd'T'HH:mm:ss[.SSS]Z
     */
    public static DateTimeFormatter getXSDFormatter(int millisLength) {
        String key = String.valueOf(millisLength);
        if (formatters.get(key) == null) {
            DateTimeFormatterBuilder bldr = new DateTimeFormatterBuilder().appendYear(4, 9).appendLiteral('-')
                    .appendMonthOfYear(2).appendLiteral('-').appendDayOfMonth(2).appendLiteral('T')
                    .appendHourOfDay(2).appendLiteral(':').appendMinuteOfHour(2).appendLiteral(':')
                    .appendSecondOfMinute(2);
            if (millisLength > 0) {
                bldr = bldr.appendLiteral('.').appendFractionOfSecond(millisLength, millisLength);
            }
            bldr = bldr.appendTimeZoneOffset("Z", true, 2, 4);
            formatters.putIfAbsent(key, bldr.toFormatter());
        }
        return formatters.get(key);
    }
}