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); } } } }