/* * ==================================================================== * Copyright (c) 2005-2012 sventon project. All rights reserved. * * This software is licensed as described in the file LICENSE, which * you should have received as part of this distribution. The terms * are also available at http://www.sventon.org. * If newer versions of this license are posted there, you may use a * newer version instead, at your option. * ==================================================================== */ package org.sventon.model; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Misc helper for date operations such as uniform formatting etc */ public class DateUtil { private static final String ISO8601_DATE_TIME_FORMAT_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSSSSS'Z'"; private static final String ISO8601_DATE_FORMAT_PATTERN = "yyyy-MM-dd"; /** * Interface used by pattern matcher to calculate the time and date part */ interface DateCalculator { Calendar calculate(final Matcher matcher); } /** * Interface used by pattern matcher to adjust Time Zone to a given date (as Calendar) */ interface ZoneClosure { void execute(final Matcher matcher, Calendar calendar); } static final ZoneClosure basicZoneCalc = new ZoneClosure() { @Override public void execute(Matcher matcher, Calendar calendar) { int zoneOffsetInMillis = "+".equals(matcher.group(10)) ? +1 : -1; int hoursOffset = Integer.parseInt(matcher.group(11)); int minutesOffset = matcher.group(12) != null ? Integer.parseInt(matcher.group(13)) : 0; zoneOffsetInMillis = zoneOffsetInMillis * ((hoursOffset * 3600 + minutesOffset * 60) * 1000); calendar.set(Calendar.ZONE_OFFSET, zoneOffsetInMillis); } }; static final ZoneClosure logZoneCalc = new ZoneClosure() { @Override public void execute(Matcher matcher, Calendar calendar) { // Check if no TZD is given if (matcher.group(10) == null) { return; } int zoneOffsetInMillis = "+".equals(matcher.group(11)) ? +1 : -1; int hoursOffset = Integer.parseInt(matcher.group(12)); int minutesOffset = matcher.group(13) != null ? Integer.parseInt(matcher.group(13)) : 0; zoneOffsetInMillis = zoneOffsetInMillis * ((hoursOffset * 3600 + minutesOffset * 60) * 1000); calendar.set(Calendar.ZONE_OFFSET, zoneOffsetInMillis); } }; static final ZoneClosure utcZoneCalc = new ZoneClosure() { @Override public void execute(Matcher matcher, Calendar calendar) { if ("Z".equals(matcher.group(10))) { calendar.set(Calendar.ZONE_OFFSET, 0); calendar.set(Calendar.DST_OFFSET, 0); } } }; static final ZoneClosure nullZoneCalc = new ZoneClosure() { @Override public void execute(Matcher matcher, Calendar calendar) { // Let it be. } }; static final DateCalculator dateOnlyCalc = new DateCalculator() { @Override public Calendar calculate(Matcher matcher) { Calendar calendar = Calendar.getInstance(); calendar.clear(); int year = Integer.parseInt(matcher.group(1)); int month = Integer.parseInt(matcher.group(2)); int day = Integer.parseInt(matcher.group(3)); calendar.set(year, month - 1, day); return calendar; } }; static final DateCalculator dateTimeCalc = new DateCalculator() { @Override public Calendar calculate(Matcher matcher) { Calendar calendar = Calendar.getInstance(); calendar.clear(); int year = Integer.parseInt(matcher.group(1)); int month = Integer.parseInt(matcher.group(2)); int day = Integer.parseInt(matcher.group(3)); int hours = Integer.parseInt(matcher.group(4)); int minutes = Integer.parseInt(matcher.group(5)); int seconds = 0; int milliseconds = 0; if (matcher.group(6) != null) { seconds = Integer.parseInt(matcher.group(7)); if (matcher.group(8) != null) { String millis = matcher.group(9); millis = millis.length() <= 3 ? millis : millis.substring(0, 3); milliseconds = Integer.parseInt(millis); } } calendar.set(year, month - 1, day); calendar.set(Calendar.HOUR, hours); calendar.set(Calendar.MINUTE, minutes); calendar.set(Calendar.SECOND, seconds); calendar.set(Calendar.MILLISECOND, milliseconds); return calendar; } }; static final DateCalculator timeOnlyDateCalc = new DateCalculator() { @Override public Calendar calculate(Matcher matcher) { Calendar calendar = Calendar.getInstance(); int hours = Integer.parseInt(matcher.group(1)); int minutes = Integer.parseInt(matcher.group(2)); int seconds = 0; int milliseconds = 0; if (matcher.group(3) != null) { seconds = Integer.parseInt(matcher.group(4)); if (matcher.group(5) != null) { String millis = matcher.group(6); millis = millis.length() <= 3 ? millis : millis.substring(0, 3); milliseconds = Integer.parseInt(millis); } } calendar.set(Calendar.HOUR_OF_DAY, hours); calendar.set(Calendar.MINUTE, minutes); calendar.set(Calendar.SECOND, seconds); calendar.set(Calendar.MILLISECOND, milliseconds); return calendar; } }; /** * DatePattern holds the pattern definition used to check for a match and if found, call the given DateCalculator and * ZoneClosure to parse the date (as Calendar) for a revision string */ private enum DatePattern { ISO8601_DATE_ONLY_DASH_PATTERN("(\\d{4})-(\\d{1,2})-(\\d{1,2})", DateUtil.dateOnlyCalc, DateUtil.nullZoneCalc), ISO8601_DATE_TIME_DASH_UTC_PATTERN("(\\d{4})-(\\d{1,2})-(\\d{1,2})T(\\d{1,2}):(\\d{2})(:(\\d{2})([.,](\\d{1,6}))?)?(Z)?", DateUtil.dateTimeCalc, DateUtil.utcZoneCalc), ISO8601_DATE_TIME_DASH_OFFSET_PATTERN("(\\d{4})-(\\d{1,2})-(\\d{1,2})T(\\d{1,2}):(\\d{2})(:(\\d{2})([.,](\\d{1,6}))?)?([+-])(\\d{2})(:(\\d{2}))?", DateUtil.dateTimeCalc, DateUtil.basicZoneCalc), ISO8601_DATE_ONLY_COMPACT_PATTERN("(\\d{4})(\\d{2})(\\d{2})", DateUtil.dateOnlyCalc, DateUtil.nullZoneCalc), ISO8601_DATE_TIME_COMPACT_UTC_PATTERN("(\\d{4})(\\d{2})(\\d{2})T(\\d{2})(\\d{2})((\\d{2})([.,](\\d{1,6}))?)?(Z)?", DateUtil.dateTimeCalc, DateUtil.utcZoneCalc), ISO8601_DATE_TIME_COMPACT_OFFSET_PATTERN("(\\d{4})(\\d{2})(\\d{2})T(\\d{2})(\\d{2})((\\d{2})([.,](\\d{1,6}))?)?([+-])(\\d{2})((\\d{2}))?", DateUtil.dateTimeCalc, DateUtil.basicZoneCalc), ISO8601_GNU_FORMAT_PATTERN("(\\d{4})-(\\d{1,2})-(\\d{1,2})T(\\d{1,2}):(\\d{2})(:(\\d{2})([.,](\\d{1,6}))?)?([+-])(\\d{2})((\\d{2}))?", DateUtil.dateTimeCalc, DateUtil.basicZoneCalc), SVN_LOG_DATE_FORMAT_PATTERN("(\\d{4})-(\\d{1,2})-(\\d{1,2}) (\\d{1,2}):(\\d{2})(:(\\d{2})([.,](\\d{1,6}))?)?( ([+-])(\\d{2})(\\d{2})?)?", DateUtil.dateTimeCalc, DateUtil.logZoneCalc), TIME_ONLY_PATTERN("(\\d{1,2}):(\\d{2})(:(\\d{2})([.,](\\d{1,6}))?)?", DateUtil.timeOnlyDateCalc, DateUtil.nullZoneCalc); /* 2006-02-17T15:30:00.314Z */ private Pattern pattern; private final DateCalculator dateCalculator; private final ZoneClosure zoneClosure; private DatePattern(final String datePattern, final DateUtil.DateCalculator dateCalc, final DateUtil.ZoneClosure zoneCalc) { dateCalculator = dateCalc; zoneClosure = zoneCalc; this.pattern = Pattern.compile(datePattern); } public Calendar parse(final String date) { final Matcher matcher = pattern.matcher(date); if (!matcher.matches()) { return null; } final Calendar calendar = dateCalculator.calculate(matcher); zoneClosure.execute(matcher, calendar); return calendar; } } public static Date parseISO8601(final String date) { for (DatePattern pattern : DatePattern.values()) { final Calendar calendar = pattern.parse(date); if (calendar != null) { return calendar.getTime(); } } throw new IllegalArgumentException("Could not parse date " + date); } public static String formatISO8601(final Date date) { if (date == null) return ""; SimpleDateFormat dateFormat = new SimpleDateFormat(ISO8601_DATE_FORMAT_PATTERN); return dateFormat.format(date); } }