io.smalldata.ohmageomh.surveys.domain.ISOW3CDateTimeFormat.java Source code

Java tutorial

Introduction

Here is the source code for io.smalldata.ohmageomh.surveys.domain.ISOW3CDateTimeFormat.java

Source

/*******************************************************************************
 * Copyright 2013 Open mHealth
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 ******************************************************************************/
/*
 *  Copyright 2012 John Jenkins
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package io.smalldata.ohmageomh.surveys.domain;

import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.format.*;
import org.springframework.stereotype.Component;

import java.io.IOException;

/**
 * <p>
 * Factory that creates instances of DateTimeFormatter for the W3C's profile of
 * the ISO 8601 standard.
 * </p>
 * 
 * <p>
 * ISO8601 is the international standard for data interchange and defines many
 * formats for representing date and time information. W3C has created a
 * shorter, more specific list of formats.
 * </p>
 * 
 * <p>
 * One major difference between this and the ISODateTimeFormatter is that this
 * will always parse non-zone'd values, e.g. year, year-month, and
 * year-month-day, with a default zone of UTC as opposed to the
 * ISODateTimeFormatter which will give them JodaTime's configured default time
 * zone.
 * </p>
 * 
 * @author John Jenkins
 */
@Component
public class ISOW3CDateTimeFormat {
    /**
     * <p>
     * A de-serializer for de-serializing text into a DateTime object.
     * </p> 
     *
     * @author John Jenkins
     */
    public static class Deserializer extends JsonDeserializer<DateTime> {
        /*
         * (non-Javadoc)
         * @see com.fasterxml.jackson.databind.JsonDeserializer#deserialize(com.fasterxml.jackson.core.JsonParser, com.fasterxml.jackson.databind.DeserializationContext)
         */
        @Override
        public DateTime deserialize(final JsonParser parser, final DeserializationContext context)
                throws IOException, JsonProcessingException {

            try {
                return any().parseDateTime(parser.getText());
            } catch (IllegalArgumentException e) {
                throw new JsonParseException("The date-time is invalid.", parser.getCurrentLocation(), e);
            }
        }
    }

    /**
     * <p>
     * A DateTimeParser that implements the W3C profile of the ISO 8601
     * representation of dates and times
     * (<a href="http://www.w3.org/TR/NOTE-datetime">
     * http://www.w3.org/TR/NOTE-datetime
     * </a>).
     * </p>
     * 
     * <p>
     * The most common use-case of this library is through the
     * ISOW3CDateTimeFormat, which will create an instance of this parser.
     * 
     * <pre>ISOW3CDateTimeFormat.dateTime();</pre>
     * 
     * For date-only formats, the zone is set to UTC. Like all DateTimeParsers,
     * the zone information is overridden with JodaTime's default zone. If you
     * are creating this parser yourself and would like to preserve the zone,
     * be sure to call 'withOffsetParsed()' on the resulting parser.
     * 
     * <pre>(new ISOW3CDateTimeParser()).withOffsetParsed();</pre>
     * 
     * </p>
     * 
     * @author John Jenkins
     */
    public static class ISOW3CDateTimeParser implements DateTimeParser {
        /**
         * The date-time formatter for the W3C's specifications for date-only
         * values.
         */
        private static final DateTimeParser ISO_W3C_DATE_PARSER;
        static {
            // The W3C defines 3 formats as valid ISO date values.
            DateTimeParser[] parsers = new DateTimeParser[3];

            // Just the year.
            parsers[0] = ISOW3CDateTimeFormat.year().getParser();

            // The year and month.
            parsers[1] = ISOW3CDateTimeFormat.yearMonth().getParser();

            // The year, month, and day.
            parsers[2] = ISOW3CDateTimeFormat.yearMonthDay().getParser();

            // Build the parser with the 3 sub-parsers.
            DateTimeFormatterBuilder builder = new DateTimeFormatterBuilder();
            builder.append(null, parsers);
            ISO_W3C_DATE_PARSER = builder.toParser();
        }

        /**
         * The date-time formatter for the W3C's specifications for date-time 
         * values.
         */
        private static final DateTimeParser ISO_W3C_DATE_TIME_PARSER;
        static {
            // The W3C defines 3 formats as valid ISO date-time values.
            DateTimeParser[] parsers = new DateTimeParser[3];

            // The year, month, day, hour, minute, and time zone.
            // Build the parser from the existing ISODateTimeParser for the 
            // year, month, day, hour, and minute and add the time zone.
            parsers[0] = ISOW3CDateTimeFormat.dateHourMinuteZone().getParser();

            // The year, month, day, hour, minute, second, and time zone.
            parsers[1] = ISOW3CDateTimeFormat.dateHourMinuteSecondZone().getParser();

            // The year, month, day, hour, minute, and time zone.
            // Build the parser from the existing ISODateTimeParser for the 
            // year, month, day, hour, and minute and add the time zone.
            parsers[2] = ISOW3CDateTimeFormat.dateTime().getParser();

            // Build the parser with the 3 sub-parsers.
            DateTimeFormatterBuilder builder = new DateTimeFormatterBuilder();
            builder.append(null, parsers);
            ISO_W3C_DATE_TIME_PARSER = builder.toParser();
        }

        /*
         * (non-Javadoc)
         * @see org.joda.time.format.DateTimeParser#estimateParsedLength()
         */
        @Override
        public int estimateParsedLength() {
            return Math.max(ISO_W3C_DATE_PARSER.estimateParsedLength(),
                    ISO_W3C_DATE_TIME_PARSER.estimateParsedLength());
        }

        /*
         * (non-Javadoc)
         * @see org.joda.time.format.DateTimeParser#parseInto(org.joda.time.format.DateTimeParserBucket, java.lang.String, int)
         */
        @Override
        public int parseInto(final DateTimeParserBucket bucket, final String text, final int position) {

            // Save the current state of the bucket.
            Object bucketState = bucket.saveState();

            // Attempt to parse the text with the date-only parser.
            int newPosition = ISO_W3C_DATE_PARSER.parseInto(bucket, text, position);

            // If the parser returned a negative number, that represents an
            // error and should be propagated.
            if (newPosition < 0) {
                return newPosition;
            }
            // If the new position is the length of the text, then the entire
            // string was parsed. We must set the time zone to UTC for these
            // values.
            else if (newPosition == text.length()) {
                bucket.setZone(DateTimeZone.UTC);
                return newPosition;
            }
            // Otherwise, this parser is inadequate and we must reset the
            // bucket and use the other parser.
            else {
                // FIXME: This shouldn't be necessary, and we should be able to
                // just continue parsing where we left off. If that were the 
                // case, then this Parser could be removed as could the any()
                // function in the ISOW3CDateTimeFormat function, and users
                // could create any combination of the ISOW3CDateTimeFormats.
                bucket.restoreState(bucketState);
                return ISO_W3C_DATE_TIME_PARSER.parseInto(bucket, text, position);
            }
        }
    }

    // Lazily instantiate the internal formatters.
    private static DateTimeFormatter year, yearMonth, yearMonthDay, yearMonthDayHourMinuteZone,
            yearMonthDayHourMinuteSecondZone, yearMonthDayHourMinuteSecondMillisZone, any;

    /**
     * Default constructor. Does nothing.
     */
    protected ISOW3CDateTimeFormat() {
        super();
    }

    /**
     * Returns a formatter for the four digit year with a time zone of UTC.
     * 
     * @return A formatter for the year with a time zone of UTC.
     */
    public static DateTimeFormatter year() {
        if (year == null) {
            year = ISODateTimeFormat.year().withZoneUTC();
        }
        return year;
    }

    /**
     * Returns a formatter for the four digit year and two digit month of
     * the year with a time zone of UTC.
     * 
     * @return A formatter for the year and month with a time zone of UTC.
     */
    public static DateTimeFormatter yearMonth() {
        if (yearMonth == null) {
            yearMonth = ISODateTimeFormat.yearMonth().withZoneUTC();
        }
        return yearMonth;
    }

    /**
     * Returns a formatter for the four digit year, two digit month of the
     * year, and two digit day of the month with a time zone of UTC.
     * 
     * @return A formatter for the year, month, and day with a time zone of
     *          UTC.
     */
    public static DateTimeFormatter yearMonthDay() {
        if (yearMonthDay == null) {
            yearMonthDay = ISODateTimeFormat.yearMonthDay().withZoneUTC();
        }
        return yearMonthDay;
    }

    /**
     * Returns a formatter for the four digit year, two digit month of the
     * year, and two digit day of the month with a time zone of UTC.
     * 
     * @return A formatter for the year, month, and day with a time zone of
     *          UTC.
     */
    public static DateTimeFormatter date() {
        return yearMonthDay();
    }

    /**
     * Returns a formatter that combines a full date, two digit hour of the
     * day, two digit minute of the hour and a time zone.
     * 
     * @return A formatter for the date, hour, minute, and time zone.
     */
    public static DateTimeFormatter dateHourMinuteZone() {
        if (yearMonthDayHourMinuteZone == null) {
            DateTimeFormatterBuilder dateHourMinuteTimezone = new DateTimeFormatterBuilder();
            dateHourMinuteTimezone.append(ISODateTimeFormat.dateHourMinute());
            dateHourMinuteTimezone.append(DateTimeFormat.forPattern("ZZ"));
            yearMonthDayHourMinuteZone = dateHourMinuteTimezone.toFormatter().withOffsetParsed();
        }
        return yearMonthDayHourMinuteZone;
    }

    /**
     * Returns a formatter that combines a full date, two digit hour of the
     * day, two digit minute of the hour, two digit second of the minute
     * and a time zone.
     * 
     * @return A formatter for the date, hour, minute, second, and time
     *          zone.
     */
    public static DateTimeFormatter dateHourMinuteSecondZone() {
        if (yearMonthDayHourMinuteSecondZone == null) {
            yearMonthDayHourMinuteSecondZone = ISODateTimeFormat.dateTimeNoMillis().withOffsetParsed();
        }
        return yearMonthDayHourMinuteSecondZone;
    }

    /**
     * Returns a formatter that combines a full date, two digit hour of the
     * day, two digit minute of the hour, two digit second of the minute
     * and a time zone.
     * 
     * @return A formatter for the date, hour, minute, second, and time
     *          zone.
     */
    public static DateTimeFormatter dateTimeNoMillis() {
        return dateHourMinuteSecondZone();
    }

    /**
     * Returns a formatter that combines a full date, two digit hour of the
     * day, two digit minute of the hour, two digit second of the minute,
     * three digit milliseconds of the second and a time zone.
     * 
     * @return A formatter for the date, hour, minute, second, millisecond,
     *          and time zone.
     */
    public static DateTimeFormatter dateHourMinuteSecondMillisZone() {
        if (yearMonthDayHourMinuteSecondMillisZone == null) {
            yearMonthDayHourMinuteSecondMillisZone = ISODateTimeFormat.dateTime().withOffsetParsed();
        }
        return yearMonthDayHourMinuteSecondMillisZone;
    }

    /**
     * Returns a formatter that combines a full date, two digit hour of the
     * day, two digit minute of the hour, two digit second of the minute,
     * three digit milliseconds of the second and a time zone.
     * 
     * @return A formatter for the date, hour, minute, second, millisecond,
     *          and time zone.
     */
    public static DateTimeFormatter dateTime() {
        return dateHourMinuteSecondMillisZone();
    }

    /**
     * Returns a formatter that combines all ISOW3CDateTimeFormats. This
     * formatter will correctly parse any of the other formatters and will only
     * fail if the value doesn't match any of them.
     * 
     * @return A universal DateTimeFormatter for all ISO W3C date-time formats.
     * 
     * @see #year()
     * @see #yearMonth()
     * @see #yearMonthDay()
     * @see #dateHourMinuteZone()
     * @see #dateHourMinuteSecondZone()
     * @see #dateHourMinuteSecondMillisZone()
     */
    public static DateTimeFormatter any() {
        if (any == null) {
            any = new DateTimeFormatter(ISODateTimeFormat.dateTime().getPrinter(), new ISOW3CDateTimeParser())
                    .withOffsetParsed();
        }
        return any;
    }
}