Iso8601Utils { static Date parse(String date) throws JsonParseException { try { int offset = 0; int year = parseInt(date, offset, offset += 4); if (checkOffset(date, offset, '-')) { offset += 1; } int month = parseInt(date, offset, offset += 2); if (checkOffset(date, offset, '-')) { offset += 1; } int day = parseInt(date, offset, offset += 2); int hour = 0; int minutes = 0; int seconds = 0; int milliseconds = 0; boolean hasT = checkOffset(date, offset, 'T'); if (!hasT && (date.length() <= offset)) { Calendar calendar = new GregorianCalendar(year, month - 1, day); return calendar.getTime(); } if (hasT) { hour = parseInt(date, offset += 1, offset += 2); if (checkOffset(date, offset, ':')) { offset += 1; } minutes = parseInt(date, offset, offset += 2); if (checkOffset(date, offset, ':')) { offset += 1; } if (date.length() > offset) { char c = date.charAt(offset); if (c != 'Z' && c != '+' && c != '-') { seconds = parseInt(date, offset, offset += 2); if (seconds > 59 && seconds < 63) seconds = 59; if (checkOffset(date, offset, '.')) { offset += 1; int endOffset = indexOfNonDigit(date, offset + 1); int parseEndOffset = Math.min(endOffset, offset + 3); int fraction = parseInt(date, offset, parseEndOffset); milliseconds = (int) (Math.pow(10, 3 - (parseEndOffset - offset)) * fraction); offset = endOffset; } } } } if (date.length() <= offset) { throw new IllegalArgumentException("No time zone indicator"); } TimeZone timezone; char timezoneIndicator = date.charAt(offset); if (timezoneIndicator == 'Z') { timezone = TIMEZONE_Z; } else if (timezoneIndicator == '+' || timezoneIndicator == '-') { String timezoneOffset = date.substring(offset); if ("+0000".equals(timezoneOffset) || "+00:00".equals(timezoneOffset)) { timezone = TIMEZONE_Z; } else { String timezoneId = GMT_ID + timezoneOffset; timezone = TimeZone.getTimeZone(timezoneId); String act = timezone.getID(); if (!act.equals(timezoneId)) { String cleaned = act.replace(":", ""); if (!cleaned.equals(timezoneId)) { throw new IndexOutOfBoundsException( "Mismatching time zone indicator: " + timezoneId + " given, resolves to " + timezone.getID()); } } } } else { throw new IndexOutOfBoundsException( "Invalid time zone indicator '" + timezoneIndicator + "'"); } Calendar calendar = new GregorianCalendar(timezone); calendar.setLenient(false); calendar.set(Calendar.YEAR, year); calendar.set(Calendar.MONTH, month - 1); calendar.set(Calendar.DAY_OF_MONTH, day); calendar.set(Calendar.HOUR_OF_DAY, hour); calendar.set(Calendar.MINUTE, minutes); calendar.set(Calendar.SECOND, seconds); calendar.set(Calendar.MILLISECOND, milliseconds); return calendar.getTime(); } catch (IndexOutOfBoundsException | IllegalArgumentException e) { throw new JsonParseException("Not an RFC 3339 date: " + date); } }    }
@Test public void fromJsonWithTwoDigitMillis() throws Exception { assertThat(parse("1985-04-12T23:20:50.52Z")) .isEqualTo(newDate(1985, 4, 12, 23, 20, 50, 520, 0)); }
@Test public void fromJson() throws Exception { assertThat(parse("1970-01-01T00:00:00.000Z")).isEqualTo(newDate(1970, 1, 1, 0, 0, 0, 0, 0)); assertThat(parse("1985-04-12T23:20:50.520Z")) .isEqualTo(newDate(1985, 4, 12, 23, 20, 50, 520, 0)); assertThat(parse("1996-12-19T16:39:57-08:00")) .isEqualTo(newDate(1996, 12, 19, 16, 39, 57, 0, -8 * 60)); assertThat(parse("1990-12-31T23:59:60Z")).isEqualTo(newDate(1990, 12, 31, 23, 59, 59, 0, 0)); assertThat(parse("1990-12-31T15:59:60-08:00")) .isEqualTo(newDate(1990, 12, 31, 15, 59, 59, 0, -8 * 60)); assertThat(parse("1937-01-01T12:00:27.870+00:20")) .isEqualTo(newDate(1937, 1, 1, 12, 0, 27, 870, 20)); }