import java.text.ParseException;
import java.time.Instant;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatterBuilder;
import java.time.temporal.ChronoField;
import java.time.temporal.TemporalAccessor;
import java.util.Date;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class JI9049840 {
	public static class DateTimeFormatter { 
		private static final String TIMEZONE_DATE_FORMAT_PATTERN = ".*([+-]\\d{2})(\\d{2})$"; 
		private static final int TIMEZONE_DATE_FORMAT_LENGTH = 5; 

		private static final java.time.format.DateTimeFormatter ISO_DATE_TIME_PARSER = 
				new DateTimeFormatterBuilder() 
				.parseCaseInsensitive() 
				.append(java.time.format.DateTimeFormatter.ISO_LOCAL_DATE) 
				.appendLiteral('T') 
				.appendValue(ChronoField.HOUR_OF_DAY, 2) 
				.appendLiteral(':') 
				.appendValue(ChronoField.MINUTE_OF_HOUR, 2) 
				.appendLiteral(':') 
				.appendValue(ChronoField.SECOND_OF_MINUTE, 2) 
				.optionalStart() 
				.appendFraction(ChronoField.MILLI_OF_SECOND, 3, 3, true) 
				.optionalEnd() 
				.appendOffset("+HH:MM", "Z") 
				.toFormatter() 
				.withZone(ZoneOffset.UTC); 

		private static final java.time.format.DateTimeFormatter ISO_DATE_TIME_FORMATTER_WITH_MILLIS = 
				new DateTimeFormatterBuilder() 
				.parseCaseInsensitive() 
				.append(java.time.format.DateTimeFormatter.ISO_LOCAL_DATE) 
				.appendLiteral('T') 
				.appendValue(ChronoField.HOUR_OF_DAY, 2) 
				.appendLiteral(':') 
				.appendValue(ChronoField.MINUTE_OF_HOUR, 2) 
				.appendLiteral(':') 
				.appendValue(ChronoField.SECOND_OF_MINUTE, 2) 
				.appendFraction(ChronoField.MILLI_OF_SECOND, 3, 3, true) 
				.appendOffset("+HH:MM", "+00:00") 
				.toFormatter() 
				.withZone(ZoneOffset.UTC); 

		private final Pattern javaTimezonePattern = Pattern.compile(TIMEZONE_DATE_FORMAT_PATTERN); 

		/** 
		 * This method parses a given string containing a dateTime in ISO8601 notation into a date. 
		 * 
		 * It can handle an optional millisecond fraction as well as timezone with either explicit '+/-HH:MM' or 'Z' UTC designator. 
		 * 
		 * @param dateTime a string containing a dateTime in ISO8601 notation. 
		 * @return the parsed date 
		 * @throws ParseException If the dateTime string is invalid. 
		 */ 
		public Date parse(String dateTime) 
				throws ParseException { 
			Matcher matcher = javaTimezonePattern.matcher(dateTime); 
			if(matcher.matches()) { 
				// correct +/-hhmm to +/-hh:mm 
				String hh = matcher.group(1); 
				String mm = matcher.group(2); 
				dateTime = dateTime.substring(0, dateTime.length() - TIMEZONE_DATE_FORMAT_LENGTH) + hh + ":" + mm; 
			} 
			TemporalAccessor temporal = ISO_DATE_TIME_PARSER.parse(dateTime); 
			long instantSeconds = temporal.getLong(ChronoField.INSTANT_SECONDS); 
			long offsetSeconds = temporal.getLong(ChronoField.OFFSET_SECONDS); 
			System.out.println("instantSeconds: "+instantSeconds); 
			System.out.println("offsetSeconds: "+offsetSeconds); 
			long seconds = instantSeconds + offsetSeconds; 
			long millis = seconds * 1000 + temporal.getLong(ChronoField.MILLI_OF_SECOND); 

			return new Date(millis); 
		} 

		/** 
		 * Returns a simplified ISO8601 datetime string in UTC. 
		 * 
		 * It will always contain a three-number millisecond field regardless if it is "needed" 
		 * (i.e. MILLI_OF_SECOND != 0) or not. The timezone of the date is always UTC but isn't using 
		 * the UTC designator 'Z'. Instead, it's using an explicit '+00:00'. 
		 * 
		 * That way a date formatted by this method will always have the same number of characters while creating output 
		 * that less intelligent date-parsing frameworks (incapable of the 'Z' notation) are still able to process. 
		 * 
		 * @param date the date to be formatted. 
		 * @return a simplified ISO8601 datetime string in UTC. 
		 */ 
		public String format(Date date) { 
			Instant instant = Instant.ofEpochMilli(date.getTime()); 
			ZonedDateTime zoned = ZonedDateTime.ofInstant(instant, ZoneOffset.UTC); 
			return ISO_DATE_TIME_FORMATTER_WITH_MILLIS.format(zoned); 
		} 
	} 

	public static void main(String[] args) throws Exception { 
		DateTimeFormatter formatter = new DateTimeFormatter(); 
		testcases(formatter); 
	} 


	public static void testcases(DateTimeFormatter formatter) throws Exception { 
		final String[] inputs = new String[]{ 
				"2009-11-15T00:00:00.000+0100", 
				"2009-11-15T00:00:00.000+01:00", 
				"2009-11-15T00:00:00.000+0000", 
				"2009-11-15T00:00:00.000+00:00", 
				"2009-11-15T00:00:00.000-0800", 
				"2009-11-15T00:00:00.000-08:00", 
				"2009-11-15T00:00:00.000Z", 
				"2009-11-15T00:00:00Z", 
				"2009-11-15T00:00:00.017+0100", 
				"2009-11-15T00:00:00.017+01:00", 
				"2009-11-15T00:00:00+0100", 
				"2009-11-15T00:00:00+01:00", 
		}; 

		final long[] expectedMillis = new long[] { 
				1258246800000L, 
				1258246800000L, 
				1258243200000L, 
				1258243200000L, 
				1258214400000L, 
				1258214400000L, 
				1258243200000L, 
				1258243200000L, 
				1258246800017L, 
				1258246800017L, 
				1258246800000L, 
				1258246800000L, 
		}; 

		final String[] expectedResults = new String[]{ 
				"2009-11-15T01:00:00.000+00:00", 
				"2009-11-15T01:00:00.000+00:00", 
				"2009-11-15T00:00:00.000+00:00", 
				"2009-11-15T00:00:00.000+00:00", 
				"2009-11-14T16:00:00.000+00:00", 
				"2009-11-14T16:00:00.000+00:00", 
				"2009-11-15T00:00:00.000+00:00", 
				"2009-11-15T00:00:00.000+00:00", 
				"2009-11-15T01:00:00.017+00:00", 
				"2009-11-15T01:00:00.017+00:00", 
				"2009-11-15T01:00:00.000+00:00", 
				"2009-11-15T01:00:00.000+00:00", 
		}; 

		for(int i=0;i<inputs.length;i++) { 
			Date parsedDate = formatter.parse(inputs[i]); 
			String result = formatter.format(parsedDate); 
			check(inputs[i], result, expectedResults[i], parsedDate.getTime(), expectedMillis[i]); 
		} 
	} 

	public static void check(String input, String result, String expectedResult, long resultMillis, long expectedMillis) { 
		if(result.equals(expectedResult)) { 
			System.out.println("Everything fine for \""+input+"\".\n"); 
		} else { 
			System.out.println("Input \""+input+"\" returned \""+result+"\" instead of \""+expectedResult+"\"!"); 
			System.out.println("Input \""+input+"\" returned "+resultMillis+" instead of "+expectedMillis+"!\n"); 
		} 
	} 
}
