| // 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.SimpleDateFormat; |
| |
| import java.text.DateFormat; |
| import java.text.FieldPosition; |
| import java.text.ParsePosition; |
| import java.util.Date; |
| import java.util.Calendar; |
| 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; |
| } |
| |
| |
| |
| } |
| |