/*
* Copyright 2003 Federal Chancellery Austria
*
* 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 at.gv.egovernment.moa.util;
import java.io.StringWriter;
import java.text.ParseException;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.TimeZone;
/**
* Utility for parsing and building XML type dateTime
,
* according to ISO 8601.
*
* @author Patrick Peck
* @version $Id$
* @see http://www.w3.org/2001/XMLSchema-datatypes"
*/
public class DateTimeUtils {
/** Error messages. */
private static MessageProvider msg = MessageProvider.getInstance();
/**
* Builds a dateTime
value from a Calendar
value.
* @param cal the Calendar
value
* @return the dateTime
value
*/
public static String buildDateTime(Calendar cal) {
StringWriter out = new StringWriter();
out.write("" + cal.get(Calendar.YEAR));
out.write("-");
out.write(to2DigitString(cal.get(Calendar.MONTH) + 1));
out.write("-");
out.write(to2DigitString(cal.get(Calendar.DAY_OF_MONTH)));
out.write("T");
out.write(to2DigitString(cal.get(Calendar.HOUR_OF_DAY)));
out.write(":");
out.write(to2DigitString(cal.get(Calendar.MINUTE)));
out.write(":");
out.write(to2DigitString(cal.get(Calendar.SECOND)));
int tzOffsetMilliseconds =
cal.get(Calendar.ZONE_OFFSET) + cal.get(Calendar.DST_OFFSET);
if (tzOffsetMilliseconds != 0) {
int tzOffsetMinutes = tzOffsetMilliseconds / (1000 * 60);
int tzOffsetHours = tzOffsetMinutes / 60;
tzOffsetMinutes -= tzOffsetHours * 60;
if (tzOffsetMilliseconds > 0) {
out.write("+");
out.write(to2DigitString(tzOffsetHours));
out.write(":");
out.write(to2DigitString(tzOffsetMinutes));
} else {
out.write("-");
out.write(to2DigitString(-tzOffsetHours));
out.write(":");
out.write(to2DigitString(-tzOffsetMinutes));
}
}
return out.toString();
}
/**
* Converts month, day, hour, minute, or second value
* to a 2 digit String.
* @param number the month, day, hour, minute, or second value
* @return 2 digit String
*/
private static String to2DigitString(int number) {
if (number < 10)
return "0" + number;
else
return "" + number;
}
/**
* Parse a String
containing a date and time instant, given in
* ISO 8601 format.
*
* @param dateTime The String
to parse.
* @return The Date
representation of the contents of
* dateTime
.
* @throws ParseException Parsing the dateTime
failed.
*/
public static Date parseDateTime(String dateTime) throws ParseException {
GregorianCalendar calendar;
long time;
int yearSign = 1, year, month, day;
int hour, minute, second;
double fraction = 0.0;
int tzSign = 1, tzHour = 0, tzMinute = 0;
int curPos = 0;
String fractStr;
boolean localTime = false;
char c;
// parse year sign
ensureChars(dateTime, curPos, 1);
c = dateTime.charAt(curPos);
if (c == '+' || c == '-') {
yearSign = c == '+' ? 1 : -1;
curPos++;
}
// parse year
year = parseInt(dateTime, curPos, 4);
curPos += 4;
// parse '-'
ensureChar(dateTime, curPos, '-');
curPos++;
// parse month
month = parseInt(dateTime, curPos, 2);
ensureValue(month, 1, 12, curPos);
curPos += 2;
// parse '-'
ensureChar(dateTime, curPos, '-');
curPos++;
// parse day
day = parseInt(dateTime, curPos, 2);
ensureValue(day, 1, 31, curPos);
curPos += 2;
// parse 'T'
ensureChar(dateTime, curPos, 'T');
curPos++;
// parse hour
hour = parseInt(dateTime, curPos, 2);
ensureValue(hour, 0, 23, curPos);
curPos += 2;
// parse ':'
ensureChar(dateTime, curPos, ':');
curPos++;
// parse minute
minute = parseInt(dateTime, curPos, 2);
ensureValue(minute, 0, 59, curPos);
curPos += 2;
// parse ':'
ensureChar(dateTime, curPos, ':');
curPos++;
// parse second
second = parseInt(dateTime, curPos, 2);
ensureValue(second, 0, 59, curPos);
curPos += 2;
// parse a fraction
if (dateTime.length() > curPos && dateTime.charAt(curPos) == '.') {
curPos++;
ensureDigits(dateTime, curPos, 1);
fractStr = "0.";
fractStr
+= dateTime.substring(curPos, curPos + countDigits(dateTime, curPos));
fraction = Double.parseDouble(fractStr);
curPos += countDigits(dateTime, curPos);
}
// parse a time zone
if (dateTime.length() > curPos) {
c = dateTime.charAt(curPos);
if (c == 'Z') {
curPos++;
} else if (c == '+' || c == '-') {
// parse time zone sign
tzSign = c == '+' ? 1 : -1;
curPos++;
// parse time zone hour
tzHour = parseInt(dateTime, curPos, 2);
ensureValue(tzHour, 0, 14, curPos);
curPos += 2;
// parse ':'
ensureChar(dateTime, curPos, ':');
curPos++;
// parse time zone minute
tzMinute = parseInt(dateTime, curPos, 2);
ensureValue(tzMinute, 0, 59, curPos);
curPos += 2;
}
} else {
localTime = true;
}
// if we have characters left, it's an error
if (dateTime.length() != curPos) {
throw new ParseException(msg.getMessage("datetime.00", null), curPos);
}
// build the Date object
year = year * yearSign;
try {
calendar = new GregorianCalendar(TimeZone.getTimeZone("GMT"));
calendar.set(year, month - 1, day, hour, minute, second);
calendar.set(Calendar.MILLISECOND, 0);
time = calendar.getTime().getTime();
time += (long) (fraction * 1000.0);
time -= tzSign * ((tzHour * 60) + tzMinute) * 60 * 1000;
if (localTime) {
time -= TimeZone.getDefault().getRawOffset();
}
return new Date(time);
} catch (IllegalArgumentException e) {
throw new ParseException(msg.getMessage("datetime.00", null), curPos);
}
}
/**
* Parse an integer value.
*
* @param str The String
containing the digits.
* @param curPos The starting position.
* @param digits The number of digist making up the integer value.
* @return int The integer representation of the digits contained in
* str
.
* @throws ParseException Parsing the integer value failed.
*/
private static int parseInt(String str, int curPos, int digits)
throws ParseException {
ensureDigits(str, curPos, digits);
return Integer.parseInt(str.substring(curPos, curPos + digits));
}
/**
* Count the number of digits following curPos
.
*
* @param str The String
in which to count digits.
* @param curPos The starting position.
* @return int The number of digits.
*/
private static int countDigits(String str, int curPos) {
int i;
for (i = curPos; i < str.length() && Character.isDigit(str.charAt(i)); i++);
return i - curPos;
}
/**
* Ensure that a value falls in a given min/max range.
*
* @param value The value to check.
* @param min The minimum allowed value.
* @param max The maximum allowed value.
* @param curPos To indicate the parsing position in the
* ParseException
.
* @throws ParseException Thrown, if value < min || value >
* max
*/
private static void ensureValue(int value, int min, int max, int curPos)
throws ParseException {
if (value < min || value > max) {
throw new ParseException(msg.getMessage("datetime.00", null), curPos);
}
}
/**
* Ensure that the given String
has a number of characters left.
*
* @param str The String
to check for its length.
* @param curPos The starting position.
* @param count The minimum number of characters that str
must
* contain, starting at from curPos
.
* @throws ParseException Thrown, if
* curPos + count > str.length()
.
*/
private static void ensureChars(String str, int curPos, int count)
throws ParseException {
if (curPos + count > str.length()) {
throw new ParseException(msg.getMessage("datetime.00", null), curPos);
}
}
/**
* Ensure that a given String
contains a certain character at a
* certain position.
*
* @param str The String
in which to look up the character.
* @param curPos The position in str
that must contain the
* character.
* @param c The character value that must be contained at position
* curPos
.
* @throws ParseException Thrown, if the characters do not match or
* curPos
is out of range.
*/
private static void ensureChar(String str, int curPos, char c)
throws ParseException {
ensureChars(str, curPos, 1);
if (str.charAt(curPos) != c) {
throw new ParseException(msg.getMessage("datetime.00", null), curPos);
}
}
/**
* Ensure that a given String
contains a number of digits,
* starting at a given position.
*
* @param str The String
to scan for digits.
* @param curPos The starting postion.
* @param count The number of digits that must be contained in
* str
, starting at curPos
.
* @throws ParseException Thrown, if str
is not long enough, or
* one of the characters following curPos
in str
is
* not a digit.
*/
private static void ensureDigits(String str, int curPos, int count)
throws ParseException {
ensureChars(str, curPos, count);
for (int i = curPos; i < curPos + count; i++) {
if (!Character.isDigit(str.charAt(i))) {
throw new ParseException(msg.getMessage("datetime.00", null), curPos);
}
}
}
}