package com.opentrust.spi.helpers; import java.security.AccessController; import java.sql.Timestamp; import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; import java.util.SimpleTimeZone; import java.util.TimeZone; import java.util.regex.Matcher; import java.util.regex.Pattern; import sun.security.action.GetPropertyAction; public class DateHelper { public final static SimpleTimeZone GMT_TIMEZONE = new SimpleTimeZone(0, "GMT"); public final static TimeZone LOCAL_TIMEZONE = getSystemTimeZone(); public static enum DateFormatType { ISO8601("ISO8601","yyyy-MM-ddTHH:mm:ssZ", "yyyy-MM-dd'T'HH:mm:ss'Z'", GMT_TIMEZONE), ISO8601_LONG("ISO8601_LONG","yyyy-MM-ddTHH:mm:ss.SSSZ", "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", GMT_TIMEZONE), ISO8601_TZ("ISO8601_TZ","yyyy-MM-ddTHH:mm:ss+hh:mm", "yyyy-MM-dd'T'HH:mm:ssz", LOCAL_TIMEZONE), ISO8601_TZ_LONG("ISO8601_TZ_LONG","yyyy-MM-ddTHH:mm:ss.SSS+hh:mm", "yyyy-MM-dd'T'HH:mm:ss.SSSz", LOCAL_TIMEZONE), SIMPLE("SIMPLE","yyyy/MM/dd HH:mm", "yyyy/MM/dd HH:mm", LOCAL_TIMEZONE), SIMPLE_LONG("SIMPLE","yyyy/MM/dd HH:mm:ss", "yyyy/MM/dd HH:mm:ss", LOCAL_TIMEZONE), DOTISO8601("DOTISO8601","yyyy.MM.ddTHH:mm:ssZ", "yyyy.MM.dd'T'HH:mm:ss'Z'", GMT_TIMEZONE), CONCAT("CONCAT","yyyyMMddHHmmss", "yyyyMMddHHmmss", GMT_TIMEZONE), HALFDAYFORMAT("HALFDAYFORMAT","yyyy/MM/dd a","yyyy/MM/dd a", LOCAL_TIMEZONE) ; private final String tag; private final String help; private final String formatString; private TimeZone timeZone; private DateFormatType(String tag, String help, String format, TimeZone timezone) { this.tag = tag ; this.help = help; this.formatString = format; if (timezone != null) this.timeZone = timezone; } public final String getTag (){return this.tag;} public final String getHelp (){return this.help;} public final String getFormatString (){return this.formatString;} public final DateFormat getFormat (){ DateFormat format = new SimpleDateFormat(this.formatString); format.setTimeZone(this.timeZone); return format; } public Date parse(String value) throws ParseException { DateFormat format = new SimpleDateFormat (this.formatString); format.setTimeZone(this.timeZone); return format.parse(value); } public static final DateFormatType valueOfTag(String s){ DateFormatType result = null; for (DateFormatType t : DateFormatType.values()){ if (t.tag.equals(s)) { result = t; break; } } return result; } } // @Deprecated // public final static String ISO8601_DATETIME_FORMAT_TEMPLATE = "yyyy-MM-ddTHH:mm:ssZ"; // @Deprecated // private final static DateFormat ISO8601_DATETIME_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); // // @Deprecated // public final static String SIMPLE_DATETIME_FORMAT_TEMPLATE = "yyyy-MM-dd HH:mm"; // @Deprecated // private final static DateFormat SIMPLE_DATETIME_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm"); // // static { // ISO8601_DATETIME_FORMAT.setTimeZone(GMT_TIMEZONE); // } /** * Tries to retrieve system original timezone even if the default timezone ( TimeZone.getDefault() ) * has been altered * @return System timezone */ private static TimeZone getSystemTimeZone() { TimeZone tz = TimeZone.getDefault(); if (tz.getID().equals("GMT")) { // Default time zone has been set to GMT, maybe it is not the real local time zone // get the time zone ID from the system properties String zoneID = (String) AccessController.doPrivileged( new GetPropertyAction("user.timezone")); // Get the time zone for zoneID if (zoneID != null && zoneID.length()>0) { tz = TimeZone.getTimeZone(zoneID); } } assert tz != null; return tz; } public static String toDateString(DateFormatType format, Date date) { return format.getFormat().format(date); } public static Date parseDateString(DateFormatType format, String value) throws ParseException { Date result = null; try { result = format.parse(value); } catch (ParseException e) { // re throw a new ParseException with extended message rather than SPIException to simplified returned stack trace StringBuffer buf = new StringBuffer(80); buf.append(e.toString()).append(", not ").append(format.getTag()).append(" format "); buf.append(format.getHelp()); throw new ParseException(buf.toString(), e.getErrorOffset()); } catch (Exception e) { StringBuffer buf = new StringBuffer(96); buf.append("Failed parsing ").append(value).append(", "); buf.append(e.toString()).append(", not ").append(format.getTag()).append(" format "); buf.append(format.getHelp()); throw new ParseException(buf.toString(), 0); } return result; } public static String toISO8601String(Date date) { return DateFormatType.ISO8601.getFormat().format(date); } public static Date parseISO8601String(String value) throws ParseException { Date result = null; try { result = DateFormatType.ISO8601.parse(value); } catch (ParseException e) { // re throw a new ParseException with extended message rather than SPIException to simplified returned stack trace StringBuffer buf = new StringBuffer(80); buf.append(e.toString()).append(", not ISO8601 format ").append(DateFormatType.ISO8601.getHelp()); throw new ParseException(buf.toString(), e.getErrorOffset()); } catch (Exception e) { StringBuffer buf = new StringBuffer(96); buf.append("Failed parsing '").append(value).append("', "); buf.append(e.toString()).append(", not ISO8601 format ").append(DateFormatType.ISO8601.getHelp()); throw new ParseException(buf.toString(), 0); } return result; } /** * Parse multiple forms of ISO8601 extended (ie with separator) date string * (with or without timezone suffix, milliseconds). * * Supported formats are : * - UTC short : yyyy-MM-ddTHH:mm:ssZ * - UTC long : yyyy-MM-ddTHH:mm:ss.SSSZ * - TZ short : yyyy-MM-ddTHH:mm:ss+hh:mm * - TZ long : yyyy-MM-ddTHH:mm:ss.SSS+hh:mm * * @param value date string in ISO to parse * @return Date object * @throws ParseException */ public static Date parseISO8601StringEx(String value) throws ParseException { Date result = null; //Separates common base part from suffix part final Pattern isoPartsPattern = Pattern.compile("^(\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2})(.+)"); Matcher isoParts = isoPartsPattern.matcher(value); if (!isoParts.matches()) { StringBuffer buf = new StringBuffer(80); buf.append("Not ISO8601 format date, '"); buf.append(value); buf.append("' not of the form 'yyyy-MM-ddTHH:mm:ss(.+)'"); throw new ParseException(buf.toString(), 0); } String base = isoParts.group(1); String suffix = isoParts.group(2); //Determine the right date parser format type final Pattern utcPattern = Pattern.compile("Z"); final Pattern utcLongPattern = Pattern.compile("\\.\\d{1,3}Z"); final Pattern tzPattern = Pattern.compile("[+-]\\d{2}:\\d{2}"); final Pattern tzLongPattern = Pattern.compile("\\.\\d{1,3}[+-]\\d{2}:\\d{2}"); DateFormatType format; String formattedValue = value; if (utcPattern.matcher(suffix).matches()) { format = DateFormatType.ISO8601; } else if (utcLongPattern.matcher(suffix).matches()) { format = DateFormatType.ISO8601_LONG; } else if (tzPattern.matcher(suffix).matches()) { format = DateFormatType.ISO8601_TZ; //Hack: Java SimpleDateFormat parser cannot parse time zone of the form +01:00 without //GMT string prefix formattedValue = base + suffix.replaceFirst("([\\+\\-])", "GMT$1"); } else if (tzLongPattern.matcher(suffix).matches()) { format = DateFormatType.ISO8601_TZ_LONG; formattedValue = base + suffix.replaceFirst("([\\+\\-])", "GMT$1"); } else { StringBuffer buf = new StringBuffer(80); buf.append("Not ISO8601 format date, '"); buf.append(value); buf.append("' suffix not of supported form (ex: 'Z', '.123Z', '+02:00', '.123+02:00')"); throw new ParseException(buf.toString(), 19); } try { result = format.parse(formattedValue); } catch (ParseException e) { // re throw a new ParseException with extended message rather than SPIException to simplified returned stack trace StringBuffer buf = new StringBuffer(80); buf.append(e.getMessage()).append(", not ISO8601 format ").append(format.getHelp()); throw new ParseException(buf.toString(), e.getErrorOffset()); } catch (Exception e) { StringBuffer buf = new StringBuffer(96); buf.append("Failed parsing '").append(value).append("', "); buf.append(e.getMessage()).append(", not ISO8601 format ").append(format.getHelp()); throw new ParseException(buf.toString(), 0); } return result; } public static String toSimpleString(Date date) { return DateFormatType.SIMPLE.getFormat().format(date); } public static Date parseSimpleString(String value) throws ParseException { Date result = null; try { result = DateFormatType.SIMPLE.parse(value); } catch (ParseException e) { // re throw a new ParseException with extended message rather than SPIException to simplified returned stack trace StringBuffer buf = new StringBuffer(80); buf.append(e.toString()).append(", not simple format ").append(DateFormatType.SIMPLE.getHelp()); throw new ParseException(buf.toString(), e.getErrorOffset()); } catch (Exception e) { StringBuffer buf = new StringBuffer(96); buf.append("Failed parsing '").append(value).append("', "); buf.append(e.toString()).append(", not simple format ").append(DateFormatType.SIMPLE.getHelp()); throw new ParseException(buf.toString(), 0); } return result; } public static String toSimpleLongString(Date date) { return DateFormatType.SIMPLE_LONG.getFormat().format(date); } public static Date parseSimpleLongString(String value) throws ParseException { Date result = null; try { result = DateFormatType.SIMPLE_LONG.parse(value); } catch (ParseException e) { // re throw a new ParseException with extended message rather than SPIException to simplified returned stack trace StringBuffer buf = new StringBuffer(80); buf.append(e.toString()).append(", not simple long format ").append(DateFormatType.SIMPLE_LONG.getHelp()); throw new ParseException(buf.toString(), e.getErrorOffset()); } catch (Exception e) { StringBuffer buf = new StringBuffer(96); buf.append("Failed parsing '").append(value).append("', "); buf.append(e.toString()).append(", not simple long format ").append(DateFormatType.SIMPLE_LONG.getHelp()); throw new ParseException(buf.toString(), 0); } return result; } public static Date toDate(Timestamp timestamp) { long milliseconds = timestamp.getTime() + timestamp.getNanos()/1000000; return new Date(milliseconds); } public static Calendar round(Calendar date, int field) { if (date == null) { throw new IllegalArgumentException("The date must not be null"); } Calendar rounded = (Calendar) date.clone(); modify(rounded, field, true); return rounded; } public final static int SEMI_MONTH = 1001; private static final int[][] fields = { {Calendar.MILLISECOND}, {Calendar.SECOND}, {Calendar.MINUTE}, {Calendar.HOUR_OF_DAY, Calendar.HOUR}, {Calendar.DATE, Calendar.DAY_OF_MONTH, Calendar.AM_PM /* Calendar.DAY_OF_YEAR, Calendar.DAY_OF_WEEK, Calendar.DAY_OF_WEEK_IN_MONTH */ }, {Calendar.MONTH, SEMI_MONTH}, {Calendar.YEAR}, {Calendar.ERA}}; private static void modify(Calendar val, int field, boolean round) { if (val.get(Calendar.YEAR) > 280000000) { throw new ArithmeticException("Calendar value too large for accurate calculations"); } if (field == Calendar.MILLISECOND) { return; } // ----------------- Fix for LANG-59 ---------------------- START --------------- // see http://issues.apache.org/jira/browse/LANG-59 // // Manually truncate milliseconds, seconds and minutes, rather than using // Calendar methods. Date date = val.getTime(); long time = date.getTime(); boolean done = false; // truncate milliseconds int millisecs = val.get(Calendar.MILLISECOND); if (!round || millisecs < 500) { time = time - millisecs; } if (field == Calendar.SECOND) { done = true; } // truncate seconds int seconds = val.get(Calendar.SECOND); if (!done && (!round || seconds < 30)) { time = time - (seconds * 1000L); } if (field == Calendar.MINUTE) { done = true; } // truncate minutes int minutes = val.get(Calendar.MINUTE); if (!done && (!round || minutes < 30)) { time = time - (minutes * 60000L); } // reset time if (date.getTime() != time) { date.setTime(time); val.setTime(date); } // ----------------- Fix for LANG-59 ----------------------- END ---------------- boolean roundUp = false; for (int i = 0; i < fields.length; i++) { for (int j = 0; j < fields[i].length; j++) { if (fields[i][j] == field) { //This is our field... we stop looping if (round && roundUp) { if (field == SEMI_MONTH) { //This is a special case that's hard to generalize //If the date is 1, we round up to 16, otherwise // we subtract 15 days and add 1 month if (val.get(Calendar.DATE) == 1) { val.add(Calendar.DATE, 15); } else { val.add(Calendar.DATE, -15); val.add(Calendar.MONTH, 1); } } else { //We need at add one to this field since the // last number causes us to round up val.add(fields[i][0], 1); } } return; } } //We have various fields that are not easy roundings int offset = 0; boolean offsetSet = false; //These are special types of fields that require different rounding rules switch (field) { case SEMI_MONTH: if (fields[i][0] == Calendar.DATE) { //If we're going to drop the DATE field's value, // we want to do this our own way. //We need to subtrace 1 since the date has a minimum of 1 offset = val.get(Calendar.DATE) - 1; //If we're above 15 days adjustment, that means we're in the // bottom half of the month and should stay accordingly. if (offset >= 15) { offset -= 15; } //Record whether we're in the top or bottom half of that range roundUp = offset > 7; offsetSet = true; } break; case Calendar.AM_PM: if (fields[i][0] == Calendar.HOUR_OF_DAY) { //If we're going to drop the HOUR field's value, // we want to do this our own way. offset = val.get(Calendar.HOUR_OF_DAY); if (offset >= 12) { offset -= 12; } roundUp = offset > 6; offsetSet = true; } break; } if (!offsetSet) { int min = val.getActualMinimum(fields[i][0]); int max = val.getActualMaximum(fields[i][0]); //Calculate the offset from the minimum allowed value offset = val.get(fields[i][0]) - min; //Set roundUp if this is more than half way between the minimum and maximum roundUp = offset > ((max - min) / 2); } //We need to remove this field if (offset != 0) { val.set(fields[i][0], val.get(fields[i][0]) - offset); } } throw new IllegalArgumentException("The field " + field + " is not supported"); } }