Output RFC 3339 Timestamp in Java
Asked Answered
T

13

49

I want to output a timestamp with a PST offset (e.g., 2008-11-13T13:23:30-08:00). java.util.SimpleDateFormat does not seem to output timezone offsets in the hour:minute format, it excludes the colon. Is there a simple way to get that timestamp in Java?

// I want 2008-11-13T12:23:30-08:00
String timestamp = new SimpleDateFormat("yyyy-MM-dd'T'h:m:ssZ").format(new Date());
System.out.println(timestamp); 
// prints "2008-11-13T12:23:30-0800" See the difference?

Also, SimpleDateFormat cannot properly parse the example above. It throws a ParseException.

// Throws a ParseException
new SimpleDateFormat("yyyy-MM-dd'T'h:m:ssZ").parse("2008-11-13T13:23:30-08:00")
Triptolemus answered 14/11, 2008 at 5:26 Comment(1)
For new readers to this question I recommend you don’t use SimpleDateFormat and Date. Those classes are poorly designed and long outdated, the former in particular notoriously troublesome. Use OffsetDateTime or ZonedDateTime from java.time, the modern Java date and time API. See the answer by Arvind Kumar Avinash.Ukase
T
63

Starting in Java 7, there's the X pattern string for ISO8601 time zone. For strings in the format you describe, use XXX. See the documentation.

Sample:

System.out.println(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX")
        .format(new Date()));

Result:

2014-03-31T14:11:29+02:00
Teddie answered 31/3, 2014 at 12:12 Comment(3)
I am getting ParseException while parsing "2017-12-12T02:01:43.924-08:00" which I got from Google Cloud APIsGoar
@pujanjain That has milliseconds, which this pattern does not include.Teddie
I'd argue that any answer which claims to be RFC3339-compliant must address the optional 1-digit millisecond as per xml2rfc.tools.ietf.org/public/rfc/html/rfc3339.html#anchor14Joshi
P
20

Check out the Joda Time package. They make RFC 3339 date formatting a lot easier.

Joda Example:

DateTime dt = new DateTime(2011,1,2,12,45,0,0, DateTimeZone.UTC);
DateTimeFormatter fmt = ISODateTimeFormat.dateTime();
String outRfc = fmt.print(dt);
Panto answered 14/11, 2008 at 12:28 Comment(2)
This answer would actually be useful with an example.Erumpent
@Panto You can simplify your code example. No need for formatter. Joda-Time automatically defaults to ISO 8601 / RFC 3339 format. Just call the toString method, either explicitly or implicitly. Like this, String output = dt.toString();Stuffy
D
16

I spent quite a lot of time looking for an answer to the same issue and I found something here : http://developer.android.com/reference/java/text/SimpleDateFormat.html

Suggested answer:

String timestamp = new SimpleDateFormat("yyyy-MM-dd'T'h:m:ssZZZZZ").format(new Date());

If you notice I am using 5 'Z' instead of one. This gives the output with a colon in the offset like this: "2008-11-13T12:23:30-08:00". Hope it helps.

Dismount answered 20/5, 2015 at 9:52 Comment(2)
Thanks - your answer is underrated - this is what finally allowed me to output RFC 3339. Using X gave me Caused by: java.lang.IllegalArgumentException: Illegal pattern component: XXX at org.apache.commons.lang.time.FastDateFormat.parsePattern(FastDateFormat.java:691) at org.apache.commons.lang.time.FastDateFormat.init(FastDateFormat.java:558)Ioved
Happy to help :)Dismount
C
14

From the "get it done dept," one solution is to use regexes to fix up the string after SimpleDateFormat has completed. Something like s/(\d{2})(\d{2})$/$1:$2/ in Perl.

If you are even remotely interested in this, I will edit this response with the working Java code.

But, yeah. I am hitting this problem too. RFC3339, I'm looking at you!

EDIT:

This works for me

// As a private class member
private SimpleDateFormat rfc3339 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ");

String toRFC3339(Date d)
{
   return rfc3339.format(d).replaceAll("(\\d\\d)(\\d\\d)$", "$1:$2");
}
Codling answered 16/7, 2012 at 19:29 Comment(0)
D
4

The problem is that Z produces the time zone offset without a colon (:) as the separator.

Decurrent answered 2/2, 2009 at 17:7 Comment(1)
Absolutely correct, but doesn't tell us how to fix it. The solution from jjohn fixed it for me.Fieldpiece
V
4

We can simply use ZonedDateTime class and DateTimeFormatter class for this.

DateTimeFormatter format = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssxxx");
ZonedDateTime z2 = ZonedDateTime.now(ZoneOffset.UTC).truncatedTo(ChronoUnit.SECONDS);
System.out.println("format =======> " + z2.format(format));

Output: format =======> 2024-01-30T15:00:07-05:00

Vampire answered 30/3, 2020 at 6:0 Comment(0)
H
3
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'h:m:ss.SZ");

Is not what exactly you need?

Haphazardly answered 14/11, 2008 at 5:39 Comment(1)
No. If you try to parse the timestamp given above, it will throw ParseException.Triptolemus
O
2

i tried this format and worked for me yyyy-MM-dd'T'HH:mm:ss'Z'

Oasis answered 10/6, 2021 at 15:11 Comment(0)
I
1

I found a stray PasteBin that helped me out with the issue: http://pastebin.com/y3TCAikc

Just in case its contents later get deleted:

// I want 2008-11-13T12:23:30-08:00
String timestamp = new SimpleDateFormat("yyyy-MM-dd'T'h:m:ssZ").format(new Date());
System.out.println(timestamp); 
// prints "2008-11-13T12:23:30-0800" See the difference?

// Throws a ParseException
new SimpleDateFormat("yyyy-MM-dd'T'h:m:ssZ").parse("2008-11-13T13:23:30-08:00")

SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'h:m:ss.SZ");
Intercollegiate answered 15/8, 2012 at 2:10 Comment(0)
B
1

I made a InternetDateFormat class for RFC3339.

But source code comment is Japanese.

PS:I created English edition and refactoring a little.

Barner answered 17/5, 2013 at 0:57 Comment(0)
A
1

Using Google Library

new com.google.api.client.util.DateTime(new Date()).toStringRfc3339()
Aimeeaimil answered 14/6, 2023 at 3:35 Comment(0)
S
0

I tested a lot with this one, works well for me... In particular when it comes to parsing (and for formatting too), it is the closest I have found so far

DateTimeFormatter rfc3339Formatter = DateTimeFormatter.ISO_OFFSET_DATE_TIME;

DateTimeFormatter rfc3339Parser = new DateTimeFormatterBuilder()
    .parseCaseInsensitive()
    .appendValue(ChronoField.YEAR, 4)
    .appendLiteral('-')
    .appendValue(ChronoField.MONTH_OF_YEAR, 2)
    .appendLiteral('-')
    .appendValue(ChronoField.DAY_OF_MONTH, 2)
    .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.NANO_OF_SECOND, 2, 9, true) //2nd parameter: 2 for JRE (8, 11 LTS), 1 for JRE (17 LTS)
    .optionalEnd()
    .appendOffset("+HH:MM","Z")
    .toFormatter()
    .withResolverStyle(ResolverStyle.STRICT)
    .withChronology(IsoChronology.INSTANCE);

Test cases at https://github.com/guyplusplus/RFC3339-DateTimeFormatter

Skate answered 13/7, 2021 at 13:46 Comment(4)
It’s correct and it’s using java.time, the modern Java date and time API, which is good. We don’t need this complication. See the answers by Vijay Upadhyay and Arvind Kumar Avinash.Ukase
Thanks for feedback. Actually after rechecking DateTimeFormatter.ISO_OFFSET_DATE_TIME is the best.Skate
This is not entirely correct, that is it won't parse valid RFC3339 values that have 1 digit in the fractional second. Actually, RFC3339 allows any number of digits in the fraction, but Java time only supports up to 9 (even in lenient mode). Therefor I think it should be appendFraction(ChronoField.NANO_OF_SECOND, 0, 9, true)Sites
Actually it does parse 1 digit in the fractional second, tested on line 95. There is a bug in JDK up to 11, where it needs to be 1 or 2 (c.f. line 38) in appendFraction depending on JDK version. If setting second parameter to 0 for appendFraction, date-time such as "1985-04-12T23:20:50.Z" (line 140) is considered valid, but it is not according to RFC3339: if there is a period, at least 1 digit is required. Ok it gets super detailed there! As for max 9 digits, yes it is JAVA limitation.Skate
L
0

java.time

The java.util Date-Time API and their formatting API, SimpleDateFormat are outdated and error-prone. It is recommended to stop using them completely and switch to the modern Date-Time API*.

Also, quoted below is a notice from the home page of Joda-Time:

Note that from Java SE 8 onwards, users are asked to migrate to java.time (JSR-310) - a core part of the JDK which replaces this project.

Solution using java.time, the modern Date-Time API: The largest city in the Pacific Time Zone is Los Angeles whose timezone name is America/Los_Angeles. Using ZoneId.of("America/Los_Angeles"), you can create an instance of ZonedDateTime which has been designed to adjust the timezone offset automatically on DST transitions.

If you need timezone offset but not the timezone name, you can convert a ZonedDateTime into OffsetDateTime using ZonedDateTime#toOffsetDateTime. Some other uses of OffsetDateTime are to create a Date-Time instance with a fixed timezone offset (e.g. Instant.now().atOffset(ZoneOffset.of("+05:30")), and to parse a Date-Time string with timezone offset.

Demo:

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;

public class Main {
    public static void main(String[] args) {
        ZoneId zoneIdLosAngeles = ZoneId.of("America/Los_Angeles");
        ZonedDateTime zdtNowLosAngeles = ZonedDateTime.now(zoneIdLosAngeles);
        System.out.println(zdtNowLosAngeles);

        // With zone offset but without time zone name
        OffsetDateTime odtNowLosAngeles = zdtNowLosAngeles.toOffsetDateTime();
        System.out.println(odtNowLosAngeles);

        // Truncated up to seconds
        odtNowLosAngeles = odtNowLosAngeles.truncatedTo(ChronoUnit.SECONDS);
        System.out.println(odtNowLosAngeles);

        // ################ A winter date-time ################
        ZonedDateTime zdtLosAngelesWinter = ZonedDateTime
                .of(LocalDateTime.of(LocalDate.of(2021, 11, 20), LocalTime.of(10, 20)), zoneIdLosAngeles);
        System.out.println(zdtLosAngelesWinter); // 2021-11-20T10:20-08:00[America/Los_Angeles]
        System.out.println(zdtLosAngelesWinter.toOffsetDateTime()); // 2021-11-20T10:20-08:00

        // ################ Parsing a date-time string with zone offset ################
        String strDateTime = "2008-11-13T13:23:30-08:00";
        OffsetDateTime odt = OffsetDateTime.parse(strDateTime);
        System.out.println(odt); // 2008-11-13T13:23:30-08:00
    }
}

Output from a sample run:

2021-07-18T03:27:15.578028-07:00[America/Los_Angeles]
2021-07-18T03:27:15.578028-07:00
2021-07-18T03:27:15-07:00
2021-11-20T10:20-08:00[America/Los_Angeles]
2021-11-20T10:20-08:00
2008-11-13T13:23:30-08:00

ONLINE DEMO

You must have noticed that I have not used a DateTimeFormatter to parse the Date-Time string of your question. It is because your Date-Time string is compliant with ISO-8601 standards. The modern Date-Time API is based on ISO 8601 and does not require using a DateTimeFormatter object explicitly as long as the Date-Time string conforms to the ISO 8601 standards.

Learn more about the modern Date-Time API from Trail: Date Time.


* For any reason, if you have to stick to Java 6 or Java 7, you can use ThreeTen-Backport which backports most of the java.time functionality to Java 6 & 7. If you are working for an Android project and your Android API level is still not compliant with Java-8, check Java 8+ APIs available through desugaring and How to use ThreeTenABP in Android Project.

Libra answered 18/7, 2021 at 10:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.