package io.gsonfire.util; import java.text.*; import java.util.Date; import java.util.TimeZone; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * @autor: julio */ public final class RFC3339DateFormat extends DateFormat { private static final Pattern TIMEZONE_PATTERN = Pattern.compile("(.*)([+-][0-9][0-9])\\:?([0-9][0-9])$"); private static final Pattern MILLISECONDS_PATTERN = Pattern.compile("(.*)\\.([0-9]+)(.*)"); private static final Pattern DATE_ONLY_PATTERN = Pattern.compile("^[0-9]{1,4}-[0-1][0-9]-[0-3][0-9]$"); private final SimpleDateFormat rfc3339Parser = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ"); private final SimpleDateFormat rfc3339Formatter; private final boolean serializeTime; public RFC3339DateFormat(TimeZone serializationTimezone, boolean serializeTime) { if(serializeTime) { this.rfc3339Formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); } else { this.rfc3339Formatter = new SimpleDateFormat("yyyy-MM-dd"); } this.serializeTime = serializeTime; this.rfc3339Formatter.setTimeZone(serializationTimezone); } public RFC3339DateFormat(TimeZone serializationTimezone) { this(serializationTimezone, true); } public RFC3339DateFormat(boolean serializeTime) { this(TimeZone.getTimeZone("UTC"), serializeTime); } public RFC3339DateFormat() { this(true); } private String generateTimezone(long time, TimeZone serializationTimezone){ if(serializationTimezone.getOffset(time) == 0){ return "Z"; } int offset = (int) (serializationTimezone.getOffset(time) / 1000L); int hours = offset / 3600; int minutes = Math.abs((offset - hours * 3600) / 60); String sign = hours >= 0 ? "+" : "-"; return sign + String.format("%02d", Math.abs(hours)) + ":" + String.format("%02d", minutes); } @Override public StringBuffer format(Date date, StringBuffer toAppendTo, FieldPosition fieldPosition) { StringBuffer formatted = new StringBuffer(); formatted.append(rfc3339Formatter.format(date).toString()); if(this.serializeTime) { //Add milliseconds long time = date.getTime(); if (time % 1000L != 0) { String fraction = Long.toString((time % 1000L)); formatted.append("." + fraction); } //Timezone String timezoneStr = generateTimezone(time, this.rfc3339Formatter.getTimeZone()); formatted.append(timezoneStr); } return formatted; } @Override public Date parse(String source, ParsePosition pos) { //Check if this is only a date if(DATE_ONLY_PATTERN.matcher(source).matches()) { source += "T00:00:00-0000"; } else { if(source.charAt(10) == 't') { source = source.substring(0, 10) + "T" + source.substring(12); } } //Filter milliseconds long millis = 0; if(source.contains(".")){ Matcher matcher = MILLISECONDS_PATTERN.matcher(source); String millisStr = matcher.replaceAll("$2"); millis = Long.parseLong(millisStr); source = matcher.replaceAll("$1") + matcher.replaceAll("$3"); } //Filter ending in Z if(source.endsWith("Z") || source.endsWith("z")){ source = source.substring(0, source.length() -1) + "-0000"; } else { //Check if we have timezone information present Matcher matcher = TIMEZONE_PATTERN.matcher(source); if (matcher.matches()) { //Filter colon in timezone source = matcher.replaceAll("$1") + matcher.replaceAll("$2") + matcher.replaceAll("$3"); } else { //It appears we don't have any timezone info or Z at the end of the date //We will assume it is RFC3339 source += "-0000"; } } try { Date res = rfc3339Parser.parse(source); if(millis > 0){ res = new Date(res.getTime() + millis); } pos.setIndex(source.length()); return res; } catch (ParseException e) { throw new RuntimeException(e); } } }