| // Licensed to the Apache Software Foundation (ASF) under one | |
| // or more contributor license agreements. See the NOTICE file | |
| // distributed with this work for additional information | |
| // regarding copyright ownership. The ASF licenses this file | |
| // to you 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 com.cloud.bridge.util; | |
| import java.text.DateFormat; | |
| import java.text.FieldPosition; | |
| import java.text.ParsePosition; | |
| import java.text.SimpleDateFormat; | |
| import java.util.Calendar; | |
| import java.util.Date; | |
| import java.util.TimeZone; | |
| /** | |
| * Format and parse a date string which is expected to be in ISO 8601 DateTimeFormat especially for | |
| * use in XML documents. | |
| * An example is for use with GMTDateTimeUserType to provide parsing of DateTime format strings into | |
| * accurate Java Date representations based on UTC. | |
| * The purpose of this class is to allow the creation of accurate date time representations following | |
| * the ISO 8601 format YYYY-MM-DDThh:MM:ss | |
| * using the letter "T" as the date/time separator | |
| * This representation may be immediately followed by a "Z" (Zulu i.e. at zero offset from GMT) to indicate UTC | |
| * or, otherwise, to a specific time zone. If a time zone (tz) is encoded then this is held as the difference | |
| * between the local time in the tz and UCT, expressed as a positive(+) or negative(-) offset (hhMM) appended | |
| * to the format. | |
| * The default case holds no tz information and assumes that a date time representation referenced to Zulu | |
| * (i.e. zero offset from GMT) is required. When formatting an existing Date transform it into the Zulu timezone | |
| * so that it is explicitly at GMT with zero offset. This provides the default representation for the encoding | |
| * of AWS datetime values. | |
| * For testing, it may be useful to note that, as at 2012, a city whose time is always in the Zulu timezone is | |
| * Reykjavik, Iceland. | |
| * The parsing and formatting methods provided by this class are GMT-referenced and locale insensitive. | |
| */ | |
| public class ISO8601SimpleDateTimeFormat extends SimpleDateFormat { | |
| private static final long serialVersionUID = 7388260211953189670L; | |
| protected static TimeZone defaultTimeZone = TimeZone.getTimeZone("Z"); | |
| /** | |
| * Construct a new ISO8601DateTimeFormat using the default time zone. | |
| * Initializes calendar inherited from java.text.DateFormat.calendar | |
| * | |
| */ | |
| public ISO8601SimpleDateTimeFormat() { | |
| setCalendar(Calendar.getInstance(defaultTimeZone)); | |
| } | |
| /** | |
| * Construct a new ISO8601DateTimeFormat using a specific time zone. | |
| * Initializes calendar inherited from java.text.DateFormat.calendar | |
| * @param tz The time zone used to format and parse the date. | |
| */ | |
| public ISO8601SimpleDateTimeFormat(TimeZone tz) { | |
| setCalendar(Calendar.getInstance(tz)); | |
| } | |
| /** | |
| * The abstract superclass DateFormat has two business methods to override. These are | |
| * public StringBuffer format(Date arg0, StringBuffer arg1, FieldPosition arg2) | |
| * public Date parse(String arg0, ParsePosition arg1) | |
| */ | |
| /** | |
| * @see DateFormat#format(Date, StringBuffer, FieldPosition) | |
| */ | |
| @Override | |
| public StringBuffer format(Date date, StringBuffer stringBuffer, FieldPosition fieldPosition) { | |
| calendar.setTime(date); | |
| calendar.setTimeZone(defaultTimeZone); | |
| writeYYYYMM(stringBuffer); | |
| stringBuffer.append('T'); | |
| writehhMMss(stringBuffer); | |
| stringBuffer.append(".000Z"); | |
| return stringBuffer; | |
| } | |
| /* @see DateFormat#parse(String, ParsePosition) | |
| * Assigns the values of YYYY-MM-DDThh:MM:ss fields between the delimiters of dateString | |
| * or a near approximation using the superclass SimpleDateFormat if not formatted exactly as ISO8601 | |
| */ | |
| @Override | |
| public Date parse(String dateString, ParsePosition pos) { | |
| ParsePosition startpos = pos; | |
| int p = pos.getIndex(); | |
| // Assign value of YYYY | |
| try { | |
| int YYYY = Integer.valueOf(dateString.substring(p, p + 4)).intValue(); | |
| p += 4; | |
| if (dateString.charAt(p) != '-') { | |
| throw new IllegalArgumentException(); | |
| } | |
| p++; | |
| // Assign value of MM | |
| int MM = Integer.valueOf(dateString.substring(p, p + 2)).intValue() - 1; | |
| p += 2; | |
| if (dateString.charAt(p) != '-') { | |
| throw new IllegalArgumentException(); | |
| } | |
| p++; | |
| // Asign value of dd | |
| int DD = Integer.valueOf(dateString.substring(p, p + 2)).intValue(); | |
| p += 2; | |
| if (dateString.charAt(p) != 'T') { | |
| throw new IllegalArgumentException(); | |
| } | |
| p++; | |
| // Assign value of hh | |
| int hh = Integer.valueOf(dateString.substring(p, p + 2)).intValue(); | |
| p += 2; | |
| if (dateString.charAt(p) != ':') { | |
| throw new IllegalArgumentException(); | |
| } | |
| p++; | |
| // Assign value of mm | |
| int mm = Integer.valueOf(dateString.substring(p, p + 2)).intValue(); | |
| p += 2; | |
| if (dateString.charAt(p) != ':') { | |
| throw new IllegalArgumentException(); | |
| } | |
| p++; | |
| // Assign value of ss | |
| int ss = 0; | |
| // if (p < dateString.length() && dateString.charAt(p) == ':') { | |
| // Allow exactly two ss digits after final : delimiter | |
| ss = Integer.valueOf(dateString.substring(p, p + 2)).intValue(); | |
| p += 2; | |
| // Set calendar inherited from java.text.DateFormat.calendar | |
| calendar.set(YYYY, MM, DD, hh, mm, ss); | |
| calendar.set(Calendar.MILLISECOND, 0); // Since java.util.Date holds none, zeroize milliseconds | |
| // process appended timezone if any or Z otherwise | |
| p = parseTZ(p, dateString); | |
| } catch (IllegalArgumentException ex) { | |
| super.setTimeZone(TimeZone.getTimeZone("GMT")); | |
| super.applyPattern("yyyy-MM-dd HH:mm:ss"); | |
| return super.parse(dateString, startpos); | |
| } catch (Exception ex) { | |
| super.setTimeZone(TimeZone.getTimeZone("GMT")); | |
| return super.parse(dateString, startpos); // default pattern | |
| } finally { | |
| pos.setIndex(p); | |
| } | |
| // Return the Calendar instance's Date representation of its value | |
| return calendar.getTime(); | |
| } | |
| /** | |
| * Write the time zone string. Remember that in the default TimeZone there is no offset and the | |
| * convention to supply the TimeZone string constant "Z" is applicable. As an optimization there | |
| * is no need to call this method where the default TimeZone has been set. | |
| * @param stringBuffer The buffer to append the time zone. | |
| */ | |
| protected final void writeTZ(StringBuffer stringBuffer) { | |
| int offset = calendar.get(Calendar.ZONE_OFFSET) + calendar.get(Calendar.DST_OFFSET); | |
| if (offset == 0) { | |
| stringBuffer.append('Z'); | |
| } else { | |
| int offsetHour = offset / 3600000; | |
| int offsetMin = (offset % 3600000) / 60000; | |
| if (offset >= 0) { | |
| stringBuffer.append('+'); | |
| } else { | |
| stringBuffer.append('-'); | |
| offsetHour = 0 - offsetHour; | |
| offsetMin = 0 - offsetMin; | |
| } | |
| appendInt(stringBuffer, offsetHour, 2); | |
| stringBuffer.append(':'); | |
| appendInt(stringBuffer, offsetMin, 2); | |
| } | |
| } | |
| /** | |
| * Write hour, minutes, and seconds. | |
| * @param stringBuffer The buffer to append the string. | |
| */ | |
| protected final void writehhMMss(StringBuffer stringBuffer) { | |
| int hh = calendar.get(Calendar.HOUR_OF_DAY); | |
| appendInt(stringBuffer, hh, 2); | |
| stringBuffer.append(':'); | |
| int mm = calendar.get(Calendar.MINUTE); | |
| appendInt(stringBuffer, mm, 2); | |
| stringBuffer.append(':'); | |
| int ss = calendar.get(Calendar.SECOND); | |
| appendInt(stringBuffer, ss, 2); | |
| } | |
| /** | |
| * Write YYYY, and MMs. | |
| * @param stringBuffer The buffer to append the string. | |
| */ | |
| protected final void writeYYYYMM(StringBuffer stringBuffer) { | |
| int YYYY = calendar.get(Calendar.YEAR); | |
| appendInt(stringBuffer, YYYY, 4); | |
| String MM; | |
| switch (calendar.get(Calendar.MONTH)) { | |
| case Calendar.JANUARY: | |
| MM = "-01-"; | |
| break; | |
| case Calendar.FEBRUARY: | |
| MM = "-02-"; | |
| break; | |
| case Calendar.MARCH: | |
| MM = "-03-"; | |
| break; | |
| case Calendar.APRIL: | |
| MM = "-04-"; | |
| break; | |
| case Calendar.MAY: | |
| MM = "-05-"; | |
| break; | |
| case Calendar.JUNE: | |
| MM = "-06-"; | |
| break; | |
| case Calendar.JULY: | |
| MM = "-07-"; | |
| break; | |
| case Calendar.AUGUST: | |
| MM = "-08-"; | |
| break; | |
| case Calendar.SEPTEMBER: | |
| MM = "-09-"; | |
| break; | |
| case Calendar.OCTOBER: | |
| MM = "-10-"; | |
| break; | |
| case Calendar.NOVEMBER: | |
| MM = "-11-"; | |
| break; | |
| case Calendar.DECEMBER: | |
| MM = "-12-"; | |
| break; | |
| default: | |
| MM = "-NA-"; | |
| break; | |
| } | |
| stringBuffer.append(MM); | |
| int DD = calendar.get(Calendar.DAY_OF_MONTH); | |
| appendInt(stringBuffer, DD, 2); | |
| } | |
| /** | |
| * Write an integer value with leading zeros. | |
| * @param stringBuffer The buffer to append the string. | |
| * @param value The value to write. | |
| * @param length The length of the string to write. | |
| */ | |
| protected final void appendInt(StringBuffer stringBuffer, int value, int length) { | |
| int len1 = stringBuffer.length(); | |
| stringBuffer.append(value); | |
| int len2 = stringBuffer.length(); | |
| for (int i = len2; i < len1 + length; ++i) { | |
| stringBuffer.insert(len1, '0'); | |
| } | |
| } | |
| /** | |
| * Parse the time zone. | |
| * @param i The position to start parsing. | |
| * @param dateString The dateString to parse. | |
| * @return The position after parsing has finished. | |
| */ | |
| protected final int parseTZ(int i, String dateString) { | |
| if (i < dateString.length()) { | |
| // check and handle the zone/dst offset | |
| int offset = 0; | |
| if (dateString.charAt(i) == 'Z') { | |
| offset = 0; | |
| i++; | |
| } else { | |
| int sign = 1; | |
| if (dateString.charAt(i) == '-') { | |
| sign = -1; | |
| } else if (dateString.charAt(i) != '+') { | |
| throw new IllegalArgumentException(); | |
| } | |
| i++; | |
| int offsetHour = Integer.valueOf(dateString.substring(i, i + 2)).intValue(); | |
| i += 2; | |
| if (dateString.charAt(i) != ':') { | |
| throw new IllegalArgumentException(); | |
| } | |
| i++; | |
| int offsetMin = Integer.valueOf(dateString.substring(i, i + 2)).intValue(); | |
| i += 2; | |
| offset = ((offsetHour * 60) + offsetMin) * 60000 * sign; | |
| } | |
| int offsetCal = calendar.get(Calendar.ZONE_OFFSET) + calendar.get(Calendar.DST_OFFSET); | |
| calendar.add(Calendar.MILLISECOND, offsetCal - offset); | |
| } | |
| return i; | |
| } | |
| @Override | |
| public int hashCode() { | |
| return (calendar.get(2) + calendar.get(16)); | |
| // numberFormat (used by superclass) will not distribute, so use calendar third and penultimate fields | |
| // (i.e. dd and ss) instead | |
| // in Java 6 (Calendar.FIELD_COUNT-1) returns 16 | |
| } | |
| @Override | |
| public boolean equals(Object obj) { | |
| if (this == obj) | |
| return true; | |
| if (obj == null || getClass() != obj.getClass()) | |
| return false; | |
| DateFormat other = (DateFormat)obj; | |
| Calendar otherCalendar = other.getCalendar(); | |
| for (int i = 0; i < Calendar.FIELD_COUNT; i++) | |
| if (calendar.get(i) != (otherCalendar.get(i))) | |
| return false; | |
| return true; | |
| } | |
| } |