Search code examples
javautc

java conversion from/to asn1 dateTimes


In my framework I need to convert to/from Java Dates and ASN1 UTC Times (Generalized Times). I have tried SimpleDateFormat and OffsetDateTime. Is there a clear simple way to get both directions? The bytes in asn1 are character bytes. For instance below, however I need "YYMMDDhhmmssZ" in bytes.

The decode test passes, due to changes from the first answer, is below:

@Test
public void testUTCTimeDecode() throws IOException {
    byte[] bytes = new byte[] {48, 55, 48, 51, 50, 50, 49, 53, 53, 56, 49, 55, 90};
    ByteArrayInputStream derStream = new ByteArrayInputStream(bytes);

    Date testDate = new Date(OffsetDateTime.parse("2007-03-22T15:58:17+00:00").toEpochSecond() * 1000);

    byte[] decodeBytes = new byte[bytes.length];
    derStream.read(decodeBytes);

    OffsetDateTime actual = OffsetDateTime.parse(
            new String(decodeBytes),
            DateTimeFormatter.ofPattern("uuMMddHHmmssX"));

    Date date = Date.from(actual.toInstant());
    assertTrue(date.equals(testDate));
}

I am still having an issue with encoding. Here is the exception thrown and the test method:

java.time.temporal.UnsupportedTemporalTypeException: Unsupported field: Year
at java.time.Instant.getLong(Instant.java:603)
at java.time.format.DateTimePrintContext.getValue(DateTimePrintContext.java:298)
at java.time.format.DateTimeFormatterBuilder$NumberPrinterParser.format(DateTimeFormatterBuilder.java:2540)
at java.time.format.DateTimeFormatterBuilder$CompositePrinterParser.format(DateTimeFormatterBuilder.java:2179)
at java.time.format.DateTimeFormatter.formatTo(DateTimeFormatter.java:1746)
at java.time.format.DateTimeFormatter.format(DateTimeFormatter.java:1720)
at club.callistohouse.asn1.ASN1TestModel.testUTCTimeEncode(ASN1TestModel.java:47)

Here is the test method I am using which is now fixed.

    @Test
public void testUTCTimeEncode() throws IOException {
    byte[] bytes = new byte[] {48, 55, 48, 51, 50, 50, 49, 53, 53, 56, 49, 55, 90};
    ByteArrayOutputStream derStream = new ByteArrayOutputStream();

    Date testDate = new Date(OffsetDateTime.parse("2007-03-22T15:58:17+00:00").toEpochSecond() * 1000);

    Instant instant = Instant.ofEpochMilli(testDate.getTime());
    DateTimeFormatter utcFormatter = DateTimeFormatter
               .ofPattern ( "uuMMddHHmmssX" ) 
               .withLocale( Locale.US )
               .withZone( ZoneId.of("UTC"));
    String formattedDate = utcFormatter.format(instant);
    derStream.write(formattedDate.getBytes());
    assertTrue(Arrays.equals(bytes, derStream.toByteArray()));
}

Solution

  • TL;DR

        OffsetDateTime expected = OffsetDateTime.of(2007, 3, 22, 15, 58, 17, 0, ZoneOffset.UTC);
        DateTimeFormatter asn1Formatter = DateTimeFormatter.ofPattern("uuMMddHHmmssX");
        OffsetDateTime actual = OffsetDateTime.parse(dateString, asn1Formatter);
        assertEquals(expected, actual);
    

    Avoid the outdated date and time classes

    I recommend you avoid the outdated classes Date, SimpleDateFormat and TimeZone. The modern Java date and time API, from which you are already using OFfsetDateTime, is so much nicer to work with. In particular mixing the two APIs, though possible, is bound to create confusing code.

    If for instance in the end you need an oldfashioned Date object for a legacy API, only convert from Instant to Date in the last moment to minimize your use of the old API.

    The bugs in you format pattern string

    There are two bugs in your format pattern string:

    1. You cannot use uppercase YY. This is for week-based year and only useful with week numbers. Use either lowercase yy or uu, and beware that two-digit year will be interpreted as in the range 2000 through 2099.
    2. Format pattern letter Z does not match zone offset Z. Use X in your format pattern instead.