mirror of https://github.com/shred/acme4j
Add a RFC3339 parser
parent
78ccec7d1d
commit
74750a9f88
|
@ -0,0 +1,86 @@
|
||||||
|
/*
|
||||||
|
* acme4j - Java ACME client
|
||||||
|
*
|
||||||
|
* Copyright (C) 2015 Richard "Shred" Körber
|
||||||
|
* http://acme4j.shredzone.org
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||||
|
*/
|
||||||
|
package org.shredzone.acme4j.util;
|
||||||
|
|
||||||
|
import java.util.Calendar;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.GregorianCalendar;
|
||||||
|
import java.util.TimeZone;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses a timestamp as defined in RFC 3339.
|
||||||
|
*
|
||||||
|
* @see <a href="https://www.ietf.org/rfc/rfc3339.txt">RFC 3339</a>
|
||||||
|
* @author Richard "Shred" Körber
|
||||||
|
*/
|
||||||
|
public class TimestampParser {
|
||||||
|
|
||||||
|
private static final Pattern DATE_PATTERN = Pattern.compile(
|
||||||
|
"^(\\d{4})-(\\d{2})-(\\d{2})T"
|
||||||
|
+ "(\\d{2}):(\\d{2}):(\\d{2})"
|
||||||
|
+ "(?:\\.(\\d{1,3})\\d*)?"
|
||||||
|
+ "(Z|[+-]\\d{2}:?\\d{2})$", Pattern.CASE_INSENSITIVE);
|
||||||
|
|
||||||
|
private static final Pattern TZ_PATTERN = Pattern.compile(
|
||||||
|
"([+-])(\\d{2}):?(\\d{2})$");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses a RFC 3339 formatted date.
|
||||||
|
*
|
||||||
|
* @param str
|
||||||
|
* Date string
|
||||||
|
* @return {@link Date} that was parsed
|
||||||
|
* @throws IllegalArgumentException
|
||||||
|
* if the date string was not RFC 3339 formatted
|
||||||
|
*/
|
||||||
|
public static Date parse(String str) {
|
||||||
|
Matcher m = DATE_PATTERN.matcher(str);
|
||||||
|
if (!m.matches()) {
|
||||||
|
throw new IllegalArgumentException("Illegal date: " + str);
|
||||||
|
}
|
||||||
|
|
||||||
|
int year = Integer.parseInt(m.group(1));
|
||||||
|
int month = Integer.parseInt(m.group(2));
|
||||||
|
int dom = Integer.parseInt(m.group(3));
|
||||||
|
int hour = Integer.parseInt(m.group(4));
|
||||||
|
int minute = Integer.parseInt(m.group(5));
|
||||||
|
int second = Integer.parseInt(m.group(6));
|
||||||
|
|
||||||
|
String msStr = m.group(7);
|
||||||
|
if (msStr == null) {
|
||||||
|
msStr = "000";
|
||||||
|
} else {
|
||||||
|
while (msStr.length() < 3) {
|
||||||
|
msStr += '0';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int ms = Integer.parseInt(msStr);
|
||||||
|
|
||||||
|
String tz = m.group(8);
|
||||||
|
if ("Z".equalsIgnoreCase(tz)) {
|
||||||
|
tz = "GMT";
|
||||||
|
} else {
|
||||||
|
tz = TZ_PATTERN.matcher(tz).replaceAll("GMT$1$2:$3");
|
||||||
|
}
|
||||||
|
|
||||||
|
Calendar cal = GregorianCalendar.getInstance(TimeZone.getTimeZone(tz));
|
||||||
|
cal.clear();
|
||||||
|
cal.set(year, month - 1, dom, hour, minute, second);
|
||||||
|
cal.set(Calendar.MILLISECOND, ms);
|
||||||
|
return cal.getTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,163 @@
|
||||||
|
/*
|
||||||
|
* acme4j - Java ACME client
|
||||||
|
*
|
||||||
|
* Copyright (C) 2015 Richard "Shred" Körber
|
||||||
|
* http://acme4j.shredzone.org
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||||
|
*/
|
||||||
|
package org.shredzone.acme4j.util;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
import static org.shredzone.acme4j.util.TimestampParser.parse;
|
||||||
|
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Calendar;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.TimeZone;
|
||||||
|
|
||||||
|
import org.hamcrest.BaseMatcher;
|
||||||
|
import org.hamcrest.Description;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unit tests for {@link TimestampParser}.
|
||||||
|
*
|
||||||
|
* @author Richard "Shred" Körber
|
||||||
|
*/
|
||||||
|
public class TimestampParserTest {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test valid strings.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testParser() {
|
||||||
|
assertThat(parse("2015-12-27T22:58:35.006769519Z"), isDate(2015, 12, 27, 22, 58, 35, 6));
|
||||||
|
assertThat(parse("2015-12-27T22:58:35.00676951Z"), isDate(2015, 12, 27, 22, 58, 35, 6));
|
||||||
|
assertThat(parse("2015-12-27T22:58:35.0067695Z"), isDate(2015, 12, 27, 22, 58, 35, 6));
|
||||||
|
assertThat(parse("2015-12-27T22:58:35.006769Z"), isDate(2015, 12, 27, 22, 58, 35, 6));
|
||||||
|
assertThat(parse("2015-12-27T22:58:35.00676Z"), isDate(2015, 12, 27, 22, 58, 35, 6));
|
||||||
|
assertThat(parse("2015-12-27T22:58:35.0067Z"), isDate(2015, 12, 27, 22, 58, 35, 6));
|
||||||
|
assertThat(parse("2015-12-27T22:58:35.006Z"), isDate(2015, 12, 27, 22, 58, 35, 6));
|
||||||
|
assertThat(parse("2015-12-27T22:58:35.01Z"), isDate(2015, 12, 27, 22, 58, 35, 10));
|
||||||
|
assertThat(parse("2015-12-27T22:58:35.2Z"), isDate(2015, 12, 27, 22, 58, 35, 200));
|
||||||
|
assertThat(parse("2015-12-27T22:58:35Z"), isDate(2015, 12, 27, 22, 58, 35));
|
||||||
|
assertThat(parse("2015-12-27t22:58:35z"), isDate(2015, 12, 27, 22, 58, 35));
|
||||||
|
|
||||||
|
assertThat(parse("2015-12-27T22:58:35.006769519+02:00"), isDate(2015, 12, 27, 20, 58, 35, 6));
|
||||||
|
assertThat(parse("2015-12-27T22:58:35.006+02:00"), isDate(2015, 12, 27, 20, 58, 35, 6));
|
||||||
|
assertThat(parse("2015-12-27T22:58:35+02:00"), isDate(2015, 12, 27, 20, 58, 35));
|
||||||
|
|
||||||
|
assertThat(parse("2015-12-27T21:58:35.006769519-02:00"), isDate(2015, 12, 27, 23, 58, 35, 6));
|
||||||
|
assertThat(parse("2015-12-27T21:58:35.006-02:00"), isDate(2015, 12, 27, 23, 58, 35, 6));
|
||||||
|
assertThat(parse("2015-12-27T21:58:35-02:00"), isDate(2015, 12, 27, 23, 58, 35));
|
||||||
|
|
||||||
|
assertThat(parse("2015-12-27T22:58:35.006769519+0200"), isDate(2015, 12, 27, 20, 58, 35, 6));
|
||||||
|
assertThat(parse("2015-12-27T22:58:35.006+0200"), isDate(2015, 12, 27, 20, 58, 35, 6));
|
||||||
|
assertThat(parse("2015-12-27T22:58:35+0200"), isDate(2015, 12, 27, 20, 58, 35));
|
||||||
|
|
||||||
|
assertThat(parse("2015-12-27T21:58:35.006769519-0200"), isDate(2015, 12, 27, 23, 58, 35, 6));
|
||||||
|
assertThat(parse("2015-12-27T21:58:35.006-0200"), isDate(2015, 12, 27, 23, 58, 35, 6));
|
||||||
|
assertThat(parse("2015-12-27T21:58:35-0200"), isDate(2015, 12, 27, 23, 58, 35));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test invalid strings.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testInvalid() {
|
||||||
|
try {
|
||||||
|
parse("");
|
||||||
|
fail("accepted empty string");
|
||||||
|
} catch (IllegalArgumentException ex) {
|
||||||
|
// expected
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
parse("abc");
|
||||||
|
fail("accepted nonsense string");
|
||||||
|
} catch (IllegalArgumentException ex) {
|
||||||
|
// expected
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
parse("2015-12-27");
|
||||||
|
fail("accepted year only string");
|
||||||
|
} catch (IllegalArgumentException ex) {
|
||||||
|
// expected
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
parse("2015-12-27T");
|
||||||
|
fail("accepted year only string");
|
||||||
|
} catch (IllegalArgumentException ex) {
|
||||||
|
// expected
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Matches the given time.
|
||||||
|
*/
|
||||||
|
private DateMatcher isDate(int year, int month, int dom, int hour, int minute, int second) {
|
||||||
|
return isDate(year, month, dom, hour, minute, second, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Matches the given time and milliseconds.
|
||||||
|
*/
|
||||||
|
private DateMatcher isDate(int year, int month, int dom, int hour, int minute, int second, int ms) {
|
||||||
|
Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
|
||||||
|
cal.clear();
|
||||||
|
cal.set(year, month - 1, dom, hour, minute, second);
|
||||||
|
cal.set(Calendar.MILLISECOND, ms);
|
||||||
|
return new DateMatcher(cal);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Date matcher that gives a readable output on mismatch.
|
||||||
|
*/
|
||||||
|
private static class DateMatcher extends BaseMatcher<Date> {
|
||||||
|
|
||||||
|
private final Calendar cal;
|
||||||
|
private final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.ENGLISH);
|
||||||
|
|
||||||
|
public DateMatcher(Calendar cal) {
|
||||||
|
this.cal = cal;
|
||||||
|
sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean matches(Object item) {
|
||||||
|
if (!(item instanceof Date)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Date date = (Date) item;
|
||||||
|
return date.equals(cal.getTime());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void describeTo(Description description) {
|
||||||
|
description.appendValue(sdf.format(cal.getTime()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void describeMismatch(Object item, Description description) {
|
||||||
|
if (!(item instanceof Date)) {
|
||||||
|
description.appendText("is not a Date");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Date date = (Date) item;
|
||||||
|
description.appendText("was ").appendValue(sdf.format(date));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue