org.eclipse.uomo.util.impl.Iso8601Date.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.uomo.util.impl.Iso8601Date.java

Source

/*******************************************************************************
 * Crown Copyright (c) 2006, 2007, Copyright (c) 2006, 2007 Jiva Medical.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 * 
 * Contributors:
 *    Jiva Medical - initial API and implementation
 *******************************************************************************/
package org.eclipse.uomo.util.impl;

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

import org.apache.commons.lang.StringUtils;
import org.eclipse.osgi.util.NLS;
import org.eclipse.uomo.core.UOMoException;
import org.eclipse.uomo.util.internal.Messages;

/**
 * ISO 8601 Dates
 * 
 * HL7 uses a date format which is conforms to the constrained ISO 8601 that is
 * defined in ISO 8824 (ASN.1) under clause 32 (generalized time).
 * 
 * Because of the ubiquity of HL7 interfaces, and the general utility of this
 * time format representation, it's encountered fairly often in healthcare
 * 
 * This Date class represents the notion of a date time that may have years,
 * months, days, hours, minutes, seconds, milliseconds and timezones specified.
 * 
 * There is three ways to interact with this date - read and write the component
 * fields directly - read and write the string form using parse() and render() -
 * get and set the time as a java Date object using getAsDate and setAsDate
 * 
 * It's possible to set the component fields to non-sensical values. Use
 * validate() to check whether the values are okay.
 * 
 * Note about parsing:
 * 
 * There is several entry points to the parser. The simplest takes only the
 * actual string to parse. If the string is a proper value according to the
 * specifications referenced above, it will be read in.
 * 
 * In addition, there is another entry point that allows the caller to specify a
 * mask that describes what parts of the date are required, and what is allowed,
 * Consult the ISO_DATE_VALIDATION_FULL constant for an example of how to use
 * the mask. In addition, the caller can specify what to do with the timezone
 * 
 * Finally, there is a static routine that takes the same parameters and returns
 * a date or throws an exception
 * 
 */
public class Iso8601Date {

    // public constants FIXME change to enum values
    /**
     * timezone information will be ignored
     */
    public static final int IGNORE = 0;
    /**
     * timezone is required
     */
    public static final int REQUIRED = 1;
    /**
     * timezone is optional
     */
    public static final int OPTIONAL = 2;
    /**
     * timezone is prohibited
     */
    public static final int PROHIBITED = 3;
    /**
     * parsing: timezone information will be cleared
     */
    public static final int CLEAR = 4;
    /**
     * timezone information will be set to local value
     */
    public static final int SETLOCAL = 5;
    /**
     * use timezone information if specified
     */
    public static final int IF_PRESENT = 6;
    /**
     * parsing mask for a fully optional date other than the year, which is
     * required
     */
    public static final String ISO_DATE_VALIDATION_FULL = Messages.Iso8601Date_ISO_DATE_VALIDATION_FULL;
    /**
     * just the date, with optional month and day
     */
    public static final String ISO_DATE_VALIDATION_DATE = Messages.Iso8601Date_ISO_DATE_VALIDATION_DATE;
    /**
     * default format for writing
     */
    public static final String DEF_OUTPUT_FORMAT = Messages.Iso8601Date_DEF_OUTPUT_FORMAT;

    // private fields

    private int year;
    private int month;
    private int day;
    private int hour;
    private int minute;
    private int second;
    private int milli;
    private int tzHour;
    private int tzMinute;
    private Boolean tzNegative = null; // null means no timezone specified

    // administration
    public Iso8601Date() {
        super();
        reset(true);
    }

    private void reset(boolean timezoneToo) {
        year = -1;
        month = -1;
        day = -1;
        hour = -1;
        minute = -1;
        second = -1;
        milli = -1;
        if (timezoneToo) {
            tzHour = 0;
            tzMinute = 0;
            tzNegative = null;
        }
    }

    /**
     * 
     * @return a string describing any errors with the component field values
     */
    public String validate(boolean checkYearReasonable) {
        StringBuffer s = new StringBuffer();

        // TODO use Units where possible
        boolean content = false;
        content = checkField(s, milli, content, 0, 999, Messages.Iso8601Date_MILIS);
        content = checkField(s, second, content, 0, 59, Messages.Iso8601Date_SECONDS);
        content = checkField(s, minute, content, 0, 59, Messages.Iso8601Date_MINUTES);
        content = checkField(s, hour, content, 0, 23, Messages.Iso8601Date_HOURS);
        content = checkField(s, day, content, 1, 31, Messages.Iso8601Date_DAYS);
        content = checkField(s, month, content, 1, 12, Messages.Iso8601Date_MONTHS);
        if (checkYearReasonable)
            content = checkField(s, year, content, 1000, 2500, Messages.Iso8601Date_YEARS);
        else
            content = checkField(s, year, content, Integer.MIN_VALUE, Integer.MAX_VALUE, "years"); //$NON-NLS-1$

        if (!content)
            s.append(Messages.Iso8601Date_NO_VALUES_SET);

        if (year != -1 && month != -1 && day != -1 && day > daysForMonth(month, year))
            s.append(Messages.Iso8601Date_THE_DAY + Integer.toString(day) + " is not valid for the month "
                    + Integer.toString(month) + " in the year " + Integer.toString(year) + "\r\n"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-4$

        if (tzNegative != null) {
            content = checkField(s, tzMinute, content, 0, 59, Messages.Iso8601Date_TZ_MINUTES);
            content = checkField(s, tzHour, content, 0, 13, Messages.Iso8601Date_TZ_HOURS);
        }
        if (s.length() > 0)
            return s.toString();
        else
            return null;
    };

    private boolean checkField(StringBuffer s, int value, boolean required, int min, int max, String desc) {
        if (value == -1) {
            if (required)
                s.append(Messages.Iso8601Date_VALUE_MISSING + desc + " is missing and required in this context"); //$NON-NLS-2$
            return required;
        } else {
            if (value < min || value > max)
                s.append(Messages.Iso8601Date_VALUE_INVALID + desc + " is invalid (is " + Integer.toString(value)
                        + " should be " + Integer.toString(min) + " - " + Integer.toString(max) + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-5$
            return true;
        }
    }

    // component field access

    /**
     * @return the year. -1 means not set
     */
    public int getYear() {
        return year;
    }

    /**
     * @param year
     *            the year to set. -1 means not set
     */
    public void setYear(int year) {
        this.year = year;
    }

    /**
     * @return the month. -1 means not set
     */
    public int getMonth() {
        return month;
    }

    /**
     * @param month
     *            the month to set. -1 means not set
     */
    public void setMonth(int month) {
        this.month = month;
    }

    /**
     * @return the day. -1 means not set
     */
    public int getDay() {
        return day;
    }

    /**
     * @param day
     *            the day to set. -1 means not set
     */
    public void setDay(int day) {
        this.day = day;
    }

    /**
     * @return the hour. -1 means not set
     */
    public int getHour() {
        return hour;
    }

    /**
     * @param hour
     *            the hour to set. -1 means not set
     */
    public void setHour(int hour) {
        this.hour = hour;
    }

    /**
     * @return the minute. -1 means not set
     */
    public int getMinute() {
        return minute;
    }

    /**
     * @param minute
     *            the minute to set. -1 means not set
     */
    public void setMinute(int minute) {
        this.minute = minute;
    }

    /**
     * @return the second. -1 means not set
     */
    public int getSecond() {
        return second;
    }

    /**
     * @param second
     *            the second to set. -1 means not set
     */
    public void setSecond(int second) {
        this.second = second;
    }

    /**
     * @return the milliseconds. -1 means not set
     */
    public int getMilli() {
        return milli;
    }

    /**
     * @param milli
     *            the milliseconds to set. -1 means not set
     */
    public void setMilli(int milli) {
        this.milli = milli;
    }

    /**
     * @return the timezone Hour. use tzNegative to see whether timezone is set
     */
    public int getTzHour() {
        return tzHour;
    }

    /**
     * @param tzHour
     *            the timezone Hour to set. use tzNegative to see whether
     *            timezone is set
     */
    public void setTzHour(int tzHour) {
        this.tzHour = tzHour;
    }

    /**
     * @return the timezone Minute. use tzNegative to see whether timezone is
     *         set
     */
    public int getTzMinute() {
        return tzMinute;
    }

    /**
     * @param tzMinute
     *            the timezone Minute to set. use tzNegative to see whether
     *            timezone is set
     */
    public void setTzMinute(int tzMinute) {
        this.tzMinute = tzMinute;
    }

    /**
     * @return the tzNegative - true if the timezone is negative, false it it's
     *         positive, and null if there's no timezone info
     */
    public Boolean getTzNegative() {
        return tzNegative;
    }

    /**
     * @param tzNegative
     *            the tzNegative to set - true if the timezone is negative,
     *            false it it's positive, and null if there's no timezone info
     */
    public void setTzNegative(Boolean tzNegative) {
        this.tzNegative = tzNegative;
    }

    // Utilities
    public Iso8601Date correctForTZ() {
        Iso8601Date result = new Iso8601Date();
        result.year = year;
        result.month = month;
        result.day = day;
        result.hour = hour;
        result.minute = minute;
        result.second = second;
        result.milli = milli;

        if (tzNegative != null) {
            if (tzNegative.booleanValue()) {
                result.addMin(-(tzHour * 60 + tzMinute));
            } else {
                result.addMin((tzHour * 60 + tzMinute));
            }
        }
        return result;
    }

    // TODO: remove if nolonger used
    // private void addSec(int i) {
    // if (i != 0) {
    // if (second == -1)
    // addMin(i / 60);
    // else {
    // second = second + i;
    // int min = 0;
    // while (second < 0) {
    // second = second + 60;
    // min = min - 1;
    // }
    // while (second >= 60) {
    // second = second - 60;
    // min = min + 1;
    // }
    // addMin(min);
    // }
    // }
    // }

    private void addMin(int i) {
        if (i != 0) {
            if (minute == -1)
                addHour(i / 60);
            else {
                minute = minute + i;
                int hr = 0;
                while (minute < 0) {
                    minute = minute + 60;
                    hr = hr - 1;
                }
                while (minute >= 60) {
                    minute = minute - 60;
                    hr = hr + 1;
                }
                addHour(hr);
            }
        }
    }

    private void addHour(int i) {
        if (i != 0) {
            if (hour == -1)
                addDay(i / 24);
            else {
                hour = hour + i;
                int d = 0;
                while (hour < 0) {
                    hour = hour + 24;
                    d = d - 1;
                }
                while (hour >= 24) {
                    hour = hour - 24;
                    d = d + 1;
                }
                addDay(d);
            }
        }
    }

    private void addDay(int i) {
        if (i != 0 && day != -1) {
            day = day + i;
            while (day < 1 || day > daysForMonth(month, year)) {
                if (day < 1) {
                    day = day + daysForMonth(month, year);
                    month = month - 1;
                    if (month == 0) {
                        month = 12;
                        year = year - 1;
                    }
                }

                if (day > daysForMonth(month, year)) {
                    month = month + 1;
                    if (month == 13) {
                        month = 1;
                        year = year + 1;
                    }
                    day = day - daysForMonth(month, year);
                }
            }
        }
    }

    // Date access

    /**
     * get the date specified as a Date.
     * 
     * @param timezone
     *            - REQUIRED, IF_PRESENT or IGNORE
     * @throws OHFException
     */
    public Date getAsDate(int timezone) throws UOMoException {
        GregorianCalendar cal = new GregorianCalendar();
        cal.clear();
        if (year != -1)
            cal.set(Calendar.YEAR, year);
        if (month != -1)
            cal.set(Calendar.MONTH, month - 1); // Calendar month start with 0
        if (day != -1)
            cal.set(Calendar.DAY_OF_MONTH, day);
        if (hour != -1)
            cal.set(Calendar.HOUR_OF_DAY, hour);
        if (minute != -1)
            cal.set(Calendar.MINUTE, minute);
        if (second != -1)
            cal.set(Calendar.SECOND, second);
        if (milli != -1)
            cal.set(Calendar.MILLISECOND, milli);

        if (timezone == REQUIRED || timezone == IF_PRESENT || timezone == OPTIONAL) {
            if (timezone == REQUIRED && tzNegative == null)
                throw new UOMoException(Messages.Iso8601Date_TZ_NOT_DEFINED);
            if (tzNegative != null) {
                TimeZone tzMsg = TimeZone.getTimeZone(Messages.Iso8601Date_GMT + prepTimezone());
                cal.setTimeZone(tzMsg);
            }
        }
        return cal.getTime();
    }

    /**
     * 
     * @param date
     *            the date to set the component fields to
     * @param timezone
     *            - what to do about timezone (CLEAR, SETLOCAL or IGNORE)
     */
    public void setAsDate(Date date, int timezone) {
        GregorianCalendar cal = new GregorianCalendar();
        cal.setTime(date);
        reset(timezone != IGNORE);
        year = cal.get(Calendar.YEAR);
        month = cal.get(Calendar.MONTH) + 1; // java Month starts from 0, but
        // ours starts from 1
        day = cal.get(Calendar.DAY_OF_MONTH);
        hour = cal.get(Calendar.HOUR_OF_DAY);
        minute = cal.get(Calendar.MINUTE);
        second = cal.get(Calendar.SECOND);
        milli = cal.get(Calendar.MILLISECOND);

        if (timezone == CLEAR) {
            tzHour = 0;
            tzMinute = 0;
            tzNegative = null;
        } else if (timezone == SETLOCAL) {
            TimeZone tzLcl = TimeZone.getDefault();
            int offset = tzLcl.getOffset(date.getTime());
            if (offset < 0) {
                tzNegative = new Boolean(true);
                offset = -offset;
            } else {
                tzNegative = new Boolean(true);
            }
            tzHour = offset / (1000 * 60 * 60);
            tzMinute = (offset - (tzHour * 1000 * 60 * 60)) / (1000 * 60);
        }
    }

    // String format

    /**
     * get the date as a formatted string
     * 
     * @param format
     *            - the format to use. Must be consistent with the components
     *            provided. Will be cross-checked
     * @param timezone
     *            - what to do about timezone (REQUIRED, IF_PRESENT, PROHIBITED,
     *            IGNORE)
     * @return
     * @throws OHFException
     *             if cross check fails
     */
    public String render(String format, boolean checkValues, int timezone) throws UOMoException {

        String v = validate(false);
        if (v != null)
            throw new UOMoException(v);

        StringBuffer str = new StringBuffer();
        render(str, checkValues,
                format.length() >= 4 && Messages.Iso8601Date_yyyy.equalsIgnoreCase(format.substring(0, 4)), 4, year,
                format, Messages.Iso8601Date_year, null);
        render(str, checkValues,
                format.length() >= 6 && Messages.Iso8601Date_mm.equalsIgnoreCase(format.substring(4, 6)), 2, month,
                format, Messages.Iso8601Date_month, null);
        render(str, checkValues,
                format.length() >= 8 && Messages.Iso8601Date_dd.equalsIgnoreCase(format.substring(6, 8)), 2, day,
                format, Messages.Iso8601Date_day, null);
        render(str, checkValues,
                format.length() >= 10 && Messages.Iso8601Date_hh.equalsIgnoreCase(format.substring(8, 10)), 2, hour,
                format, Messages.Iso8601Date_hour, null);
        render(str, checkValues,
                format.length() >= 12 && Messages.Iso8601Date_nn.equalsIgnoreCase(format.substring(10, 12)), 2,
                minute, format, Messages.Iso8601Date_minute, null);
        render(str, checkValues,
                format.length() >= 14 && Messages.Iso8601Date_ss.equalsIgnoreCase(format.substring(12, 14)), 2,
                second, format, Messages.Iso8601Date_second, null);

        if (format.length() >= 18 && Messages.Iso8601Date_sss.equalsIgnoreCase(format.substring(15, 18))) {
            render(str, checkValues, true, 3, milli, format, Messages.Iso8601Date_milisecond,
                    Messages.Iso8601Date_DOT);
        } else if (format.length() == 17 && "ss".equalsIgnoreCase(format.substring(15, 17))) { //$NON-NLS-1$
            render(str, checkValues, true, 2, milli / 10, format, "millisecond", Messages.Iso8601Date_DOT); //$NON-NLS-1$
        } else if (format.length() == 16 && Messages.Iso8601Date_s.equalsIgnoreCase(format.substring(15, 16))) {
            render(str, checkValues, true, 1, milli / 100, format, "millisecond", "."); //$NON-NLS-1$ //$NON-NLS-2$
        }

        if (timezone == REQUIRED || timezone == IF_PRESENT) {
            if (timezone == REQUIRED && tzNegative == null)
                throw new UOMoException(Messages.Iso8601Date_TZ_REQUIRED_BUT_NOT_DEFINED);
            if (tzNegative != null) {
                if (tzNegative.booleanValue())
                    str.append('-');
                else
                    str.append('+');
                render(str, checkValues, true, 2, tzHour, format, Messages.Iso8601Date_tz_hour, null);
                render(str, checkValues, true, 2, tzMinute, format, Messages.Iso8601Date_tz_minutes, null);
            }
        }
        if (timezone == PROHIBITED && tzNegative != null)
            throw new UOMoException(Messages.Iso8601Date_TZ_PROHIBITED_BUT_DEFINED);

        return str.toString();
    }

    private void render(StringBuffer str, boolean checkValues, boolean render, int len, int value, String format,
            String desc, String prefix) throws UOMoException {
        if (render) {
            if (value != -1) {
                if (prefix != null)
                    str.append(prefix);
                str.append(StringUtils.leftPad(Integer.toString(value), len, Messages.Iso8601Date_ZERO));
            } else if (checkValues)
                throw new UOMoException(Messages.Iso8601Date_NO_SUCH_VALUE_PROVIDED + format + "' specifies " + desc
                        + " but no such value has been provided"); //$NON-NLS-1$ //$NON-NLS-3$
        }
    }

    /**
     * Parse the value. no rules other than the base format rules
     * 
     * @param value
     *            - the value to parse
     */
    public void parse(String value) {
        parse(value, ISO_DATE_VALIDATION_FULL, OPTIONAL);
    }

    /**
     * Parse the value. check that it conforms to the specified format and
     * timezone rules
     * 
     * @param value
     *            - the value to parse
     * @param format
     *            - the format rules to adhere to
     * @param timezone
     *            - rules for timezone (REQUIRED, OPTIONAL, PROHIBITED, IGNORE)
     * @return - String description of parsing error, or null if no error
     *         encountered
     */
    public String parse(String value, String format, int timezone) {
        reset(timezone != IGNORE);
        String[] parts = { value };
        tzNegative = null;

        if (value.indexOf('+') > 0) {
            parts = StringUtils.split(value, "+", 2); //$NON-NLS-1$
            tzNegative = new Boolean(false);
        } else if (value.indexOf('-') > 0) {
            parts = StringUtils.split(value, "-", 2); //$NON-NLS-1$
            tzNegative = new Boolean(true);
        }

        String error = checkSections(parts[0], value, format);
        if (error != null)
            return error;

        if (parts.length > 1 && timezone != IGNORE)
            error = checkSections(parts[1], value, Messages.Iso8601Date_58);
        if (error != null)
            return error;

        if (timezone == REQUIRED) {
            if (parts.length < 2)
                return Messages.Iso8601Date_59;
        } else if (timezone == PROHIBITED && parts.length > 1)
            return Messages.Iso8601Date_60;
        return null;

    }

    /**
     * Parse the value to a Date. check that it conforms to the specified format
     * and timezone rules
     * 
     * @param value
     *            - the value to parse
     * @param format
     *            - the format rules to adhere to
     * @param timezone
     *            - rules for timezone (REQUIRED, OPTIONAL, PROHIBITED, IGNORE)
     * @return - Date (or throw exception if conversion not successful
     */
    public static Date parseToDate(String value, String format, int timezone) throws UOMoException {
        Iso8601Date d = new Iso8601Date();
        String err = d.parse(value, format, timezone);
        if (err != null)
            throw new UOMoException(Messages.Iso8601Date_61 + value + Messages.Iso8601Date_62 + err);
        return d.getAsDate(timezone);
    }

    public static String renderFromDate(Date date, String format, int timezone) throws UOMoException {
        Iso8601Date d = new Iso8601Date();
        d.setAsDate(date, timezone == REQUIRED ? SETLOCAL : CLEAR);
        return d.render(format, true, timezone);
    }

    // private parsing routines

    private boolean inFraction;

    private String checkSections(String content, String whole, String mask) {
        String workingMask = StringUtils.strip(mask, Messages.Iso8601Date_63);
        String[] parts = { "", workingMask }; //$NON-NLS-1$
        boolean first = true;
        inFraction = false;

        do {
            parts = StringUtils.splitPreserveAllTokens(parts[1], Messages.Iso8601Date_65, 2);
            String token = parts[0];
            if (token != null) { // support use of [ at first point to make
                                 // everything optional
                String section = content == null || content.length() < token.length() ? null
                        : content.substring(0, token.length()); // sSection =
                // copy(sContent, 1,
                // length(sToken));
                if (section == null) { // if sSection = '' then
                    if (!first) {
                        if (content != null && content.length() < token.length())
                            return Messages.Iso8601Date_66 + content + Messages.Iso8601Date_67 + token;
                        else
                            return Messages.Iso8601Date_68 + token + Messages.Iso8601Date_69 + mask
                                    + Messages.Iso8601Date_70 + whole;
                    }
                } else if (section.length() < token.length()) {
                    return Messages.Iso8601Date_71 + token + Messages.Iso8601Date_72 + mask
                            + Messages.Iso8601Date_73 + whole + Messages.Iso8601Date_74 + section;
                } else {
                    String error = checkSection(token, section);
                    if (error != null)
                        return error;
                    else if (section.length() >= content.length())
                        content = null;
                    else
                        content = content.substring(section.length());
                }
            }
            first = false;
        } while (parts.length > 1 && content != null); // until not result or
        // (sFormat = '') or
        // (sContent = '');
        if (content != null) {
            return Messages.Iso8601Date_75 + content + Messages.Iso8601Date_76 + whole + Messages.Iso8601Date_77
                    + mask;
        } else
            return null;
    }

    private boolean starts(String source, String test) {
        return source == null || source.length() < test.length() ? false
                : source.substring(0, test.length()).equals(test);
    }

    private String checkSection(String token, String content) {
        String error = null;

        if (starts(token, Messages.Iso8601Date_YYYY)) {
            error = checkYear(content.substring(0, 4));
            if (error != null)
                return error;
            token = token.substring(4, token.length());
            content = content.substring(4, content.length());
        }
        ;

        if (starts(token, Messages.Iso8601Date_MM)) {
            error = checkMonth(content.substring(0, 2));
            if (error != null)
                return error;
            token = token.substring(2, token.length());
            content = content.substring(2, content.length());
        }
        ;

        if (starts(token, Messages.Iso8601Date_DD)) {
            error = checkDay(content.substring(0, 2));
            if (error != null)
                return error;
            token = token.substring(2, token.length());
            content = content.substring(2, content.length());
        }
        ;

        if (starts(token, Messages.Iso8601Date_HH)) {
            error = checkHour(content.substring(0, 2), false);
            if (error != null)
                return error;
            token = token.substring(2, token.length());
            content = content.substring(2, content.length());
        }
        ;

        if (starts(token, Messages.Iso8601Date_TT)) {
            error = checkHour(content.substring(0, 2), true);
            if (error != null)
                return error;
            token = token.substring(2, token.length());
            content = content.substring(2, content.length());
        }
        ;

        if (starts(token, Messages.Iso8601Date_NN)) {
            error = checkMinute(content.substring(0, 2), false);
            if (error != null)
                return error;
            token = token.substring(2, token.length());
            content = content.substring(2, content.length());
        }
        ;

        if (starts(token, Messages.Iso8601Date_tt)) {
            error = checkMinute(content.substring(0, 2), true);
            if (error != null)
                return error;
            token = token.substring(2, token.length());
            content = content.substring(2, content.length());
        }
        ;

        if (starts(token, Messages.Iso8601Date_SS) && !inFraction) {
            error = checkSecond(content.substring(0, 2));
            if (error != null)
                return error;
            token = token.substring(2, token.length());
            content = content.substring(2, content.length());
        }
        ;

        if (starts(token, ".") && !inFraction) { //$NON-NLS-1$
            error = checkDot(content.substring(0, 1));
            if (error != null)
                return error;
            token = token.substring(1, token.length());
            content = content.substring(1, content.length());
        }
        ;

        while (starts(token, Messages.Iso8601Date_U) && inFraction) {
            error = checkFraction(content.substring(0, 1));
            if (error != null)
                return error;
            token = token.substring(1, token.length());
            content = content.substring(1, content.length());
        }
        ;

        if (!(token == null || token.equals(""))) //$NON-NLS-1$
            return Messages.Iso8601Date_DATE_FRAGMENT_NOT_KNOWN + token + " is not known"; //$NON-NLS-2$
        return null;
    }

    private String checkYear(String value) {
        if (value.length() != 4)
            return "Year Value " + value + " is not 4 digits in length"; //$NON-NLS-1$ //$NON-NLS-2$
        else if (!StringUtils.isNumeric(value))
            return Messages.Iso8601Date_YEAR_VALUE_NOT_NUMERICAL + value + " is not numerical"; //$NON-NLS-2$
        else {
            year = Integer.parseInt(value);
            if (year <= 0)
                return Messages.Iso8601Date_YEAR_VALUE_NEGATIVE_NOT_SUPPORTED + value
                        + ": negative numbers are not supported"; //$NON-NLS-2$
            else
                return null;
        }
    }

    private String checkMonth(String value) {
        if (value.length() != 2)
            return Messages.Iso8601Date_MONTH_VALUE_NOT_2_DIGITS + value + " is not 2 digits in length"; //$NON-NLS-2$
        else if (!StringUtils.isNumeric(value))
            return Messages.Iso8601Date_MONTH_VALUE_NOT_NUMERICAL + value + " is not numerical"; //$NON-NLS-2$
        else {
            month = Integer.parseInt(value);
            if (month <= 0 || month > 12)
                return Messages.Iso8601Date_MONTH_VALUE_MUST_BE_1_12 + value + ": month must be 1 - 12"; //$NON-NLS-2$
            else
                return null;
        }
    }

    private String checkDay(String value) {
        if (value.length() != 2)
            return Messages.Iso8601Date_DAY_VALUE_NOT_2_DIGITS + value + " is not 2 digits in length"; //$NON-NLS-2$
        else if (!StringUtils.isNumeric(value))
            return Messages.Iso8601Date_DAY_VALUE_NOT_NUMERICAL + value + " is not numerical"; //$NON-NLS-2$
        else {
            day = Integer.parseInt(value);
            if (day <= 0)
                return Messages.Iso8601Date_DAY_VALUE_MUST_BE_POSITIVE + value + ": Day must be >= 1"; //$NON-NLS-2$
            else if (month == 0)
                return Messages.Iso8601Date_DAY_VALUE_MONTH_MUST_BE_KNOWN + value + ": Month must be known"; //$NON-NLS-2$
            else if (year == 0)
                return Messages.Iso8601Date_DAY_VALUE_YEAR_MUST_BE_KNOWN + value + ": Year must be known"; //$NON-NLS-2$
            else if (day > daysForMonth(month, year))
                return Messages.Iso8601Date_DAY_VALUE_ILLEGAL_FOR_MONTH + value + ": is illegal for the month "
                        + Integer.toString(month) + "-" + Integer.toString(year); //$NON-NLS-1$ //$NON-NLS-3$
            else
                return null;
        }
    }

    private int daysForMonth(int m, int y) {
        int[] daysInMonth = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
        if (m < 1 || m > 12)
            return 30;
        else if (m == 2 && new GregorianCalendar().isLeapYear(y))
            return 29;
        else
            return daysInMonth[m - 1];
    }

    private String checkHour(String value, boolean inTimezone) {
        if (value.length() != 2)
            return Messages.Iso8601Date_HOUR_VALUE_NOT_2_DIGITS + value + " is not 2 digits in length"; //$NON-NLS-2$
        else if (!StringUtils.isNumeric(value))
            return Messages.Iso8601Date_HOUR_VALUE_NOT_NUMERICAL + value + " is not numerical"; //$NON-NLS-2$
        else if (inTimezone) {
            tzHour = Integer.parseInt(value);
            if (tzHour < 0)
                return Messages.Iso8601Date_TZ_HOUR_VALUE_MUST_BE_0_OR_POSITIVE + value + ": Hour must be >= 0"; //$NON-NLS-2$
            else if (tzHour > 12)
                return Messages.Iso8601Date_TZ_HOUR_VALUE_MUST_BE_12_OR_LESS + value + ": Hour must be <= 12"; //$NON-NLS-2$
        } else {
            hour = Integer.parseInt(value);
            if (hour < 0)
                return Messages.Iso8601Date_HOUR_VALUE_MUST_BE_0_OR_POSITIVE + value + ": Hour must be >= 0"; //$NON-NLS-2$
            else if (inTimezone && hour > 12)
                return Messages.Iso8601Date_HOUR_VALUE_MUST_BE_12_OR_LESS + value + ": Hour must be <= 12"; //$NON-NLS-2$
            else if (hour > 23)
                return Messages.Iso8601Date_TZ_HOUR_VALUE_MUST_BE_23_OR_LESS + value + ": Hour must be <= 23"; //$NON-NLS-2$
        }
        return null;
    }

    private String checkMinute(String value, boolean inTimezone) {
        if (value.length() != 2)
            return Messages.Iso8601Date_MINUTE_VALUE_NOT_2_DIGITS_LONG + value + " is not 2 digits in length"; //$NON-NLS-2$
        else if (!StringUtils.isNumeric(value))
            return Messages.Iso8601Date_MINUTE_VALUE_NOT_NUMERICAL + value + " is not numerical"; //$NON-NLS-2$
        else if (inTimezone) {
            tzMinute = Integer.parseInt(value);
            if (tzMinute != 0 && tzMinute != 30)
                return Messages.Iso8601Date_TZ_MINUTE_VALUE_MUST_BE_0_OR_30 + value + ": Minute must be 0 or 30"; //$NON-NLS-2$
        } else {
            minute = Integer.parseInt(value);
            if (minute < 0 || minute > 59)
                return Messages.Iso8601Date_MINUTE_VALUE_MUST_BE_0_AND_59 + value + ": Minute must be 0 and 59"; //$NON-NLS-2$
        }
        return null;
    }

    private String checkSecond(String value) {
        if (value.length() != 2)
            return Messages.Iso8601Date_SECOND_VALUE_NOT_2_DIGITS_LONG + value + " is not 2 digits in length"; //$NON-NLS-2$
        else if (!StringUtils.isNumeric(value))
            return Messages.Iso8601Date_SECOND_VALUE_NOT_NUMERICAL + value + " is not numerical"; //$NON-NLS-2$
        else {
            second = Integer.parseInt(value);
            if (second < 0 || second > 59)
                return NLS.bind(Messages.Iso8601Date_SECOND_VALUE_MUST_BE_0_AND_59, value);
            else
                return null;
        }
    }

    private String checkDot(String value) {
        if (value.equals(".")) { //$NON-NLS-1$
            inFraction = true;
            return null;
        } else
            return Messages.Iso8601Date_EXPECTED_DOT;
    }

    private String checkFraction(String value) {
        // TODO - read milliseconds
        if (!StringUtils.isNumeric(value))
            return Messages.Iso8601Date_FRACT_VAL_NOT_NUMERICAL + value + " is not numerical"; //$NON-NLS-2$
        else
            return null;
    }

    private String prepTimezone() {
        return (tzNegative.booleanValue() ? Messages.Iso8601Date_MINUS : Messages.Iso8601Date_PLUS)
                + StringUtils.leftPad(Integer.toString(tzHour), 2, '0')
                + StringUtils.leftPad(Integer.toString(tzMinute), 2, '0');
    }

}