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