Hot questions for Using Joda-Time in iso8601

Question:

I am trying to serialize/deserialize a date from/to a JavaScript application.

Server side, I use Java, JodaTime is installed on it. I found out how to serialize to ISO with UTC Time zone, but can't find out how to do the reverse operation.

Here is my code

public static String getIsoDate( Date date )
{
    SimpleDateFormat  dateToIsoDateString = new SimpleDateFormat( ISO_8601_DATE_FORMAT );
    TimeZone tz = TimeZone.getTimeZone("UTC");
    dateToIsoDateString.setTimeZone( tz );
    return dateToIsoDateString.format( date );
}

// this will return a date with GMT timezone
public static Date getDateFromIsoDateString( String iso8601date )
{
    DateTimeFormatter jodaParser = ISODateTimeFormat.dateTimeNoMillis();
    return jodaParser.parseDateTime( iso8601date ).toDate();
}

I don't mind using or not Joda, just need a quick and working solution,


Answer:

If you are using Java 7 or earlier you can refer to this post.

If you are using Java 8 you could do:

    DateTimeFormatter timeFormatter = DateTimeFormatter.ISO_DATE_TIME;
    TemporalAccessor accessor = timeFormatter.parse("2015-10-27T16:22:27.605-07:00");

    Date date = Date.from(Instant.from(accessor));
    System.out.println(date);
Update

As pointed out by @BasilBourque in the comment, TemporalAccessor is java framework level interface, and is not advisable to use in the application code and it is advisable to use concrete classes rather than the interfaces.

This interface is a framework-level interface that should not be widely used in application code. Instead, applications should create and pass around instances of concrete types, such as LocalDate. There are many reasons for this, part of which is that implementations of this interface may be in calendar systems other than ISO. See ChronoLocalDate for a fuller discussion of the issues.

There a few concrete classes available to use, like LocalDate, LocalDateTime, OffsetDateTime, ZonedDateTime and etc..

DateTimeFormatter timeFormatter = DateTimeFormatter.ISO_DATE_TIME;

OffsetDateTime offsetDateTime = OffsetDateTime.parse("2015-10-27T16:22:27.605-07:00", timeFormatter);

Date date = Date.from(Instant.from(offsetDateTime));
System.out.println(date);

Question:

I have a two part issue, or maybe its two different ways to solve this. I receive an ISO string like 2015-11-17T17:10:24-0800. The end goal is to display the string as 11/17/15 5:10 PM in some HTML generated by some Freemarker. The string I receive could be in any timezone, but I always need to display the string in its local timezone as shown above. Currently, our code was just taking the string and passing it into the template and coverting as such:

<#assign mydate = obj.mydate?datetime("yyyy-MM-dd'T'HH:mm:ssz")?string.short>

This is no longer good since I believe Freemarker is using the system's local timezone and now we are getting more than one timezone. I see there is an iso method in freemarker. So I try

<#assign order_date = order.order_date?iso("yyyy-MM-dd'T'HH:mm:ssz")>

but I keep getting error:

For "?iso" left-hand operand: Expected a date, but this evaluated to a string

Ok I need a date. Working with Joda, I try and create a datetime object by:

DateTime dateTime = ISODateTimeFormat.dateTimeNoMillis().parseDateTime("2015-11-17T17:10:24-0800");

But that appears to use my local timezone as well and shows 2015-11-17T20:10:24.000-05:00. I know I could do withZone(...) but I dont know the zone other than the -0800 or whatever zone is passed at the end of the string. So I'm at a loss of what to do now. Oh, and I cannot change the format of the string I receive.


Answer:

DateTime dateTime = ISODateTimeFormat.dateTimeNoMillis().withOffsetParsed().parseDateTime("2015-11-17T17:10:24-0800");

This will create a DateTime with a fixed timezone offset of -08:00.

Question:

Given a ISO string like this

String dateTime = "2016-07-11T16:50:22.00+05:00";

Is there a way to find an offset is present in the specific string or not using joda?

This is what the code i have done so far, to get an offset if one is present

public static String getDateTimeWithTimeZoneOffset(String dateTimeString)
    {
        DateTimeFormatter df = ISODateTimeFormat.dateTimeParser().withOffsetParsed();
        DateTime nDt = df.parseDateTime(dateTimeString);
        DateTimeFormatter dateFormatterWithoutMillis = DateTimeFormat.forPattern("yyyy-MM-dd'T'HH:mm:ss");
        return dateFormatterWithoutMillis.print(nDt) + SPACE + nDt.getZone();
    }

the above code gives the below output

2016-07-11T16:50:22 +05:00

but when I have a string without an offset, like this one below

2016-07-11T16:50:22

The same code takes in a default time zone and prints like this

2016-07-11T16:50:22 America/Chicago

is there anyway i can check if an offset is present in the string or not, if not throw an exception?


Answer:

java.time

The Joda-Time development team advises migration to java.time classes:

Joda-Time is the de facto standard date and time library for Java prior to Java SE 8. Users are now asked to migrate to java.time (JSR-310).

the java.time framework built into Java 8 and later. These classes supplant the old troublesome date-time classes such as java.util.Date as well as the highly successful 3rd-party Joda-Time library. See Oracle Tutorial. Much of the java.time functionality is back-ported to Java 6 & 7 in ThreeTen-Backport and further adapted to Android in ThreeTenABP.

OffsetDateTime

The OffsetDateTime class represents a moment on the timeline with an assigned offset-from-UTC. The offset is represented by the ZoneOffset class.

LocalDateTime

If your input string lacks any offset or time zone info, it is considered to be a "local" date-time. That means it is not a particular moment on the timeline, but rather a rough idea about a possible moment. Has no real meaning until you apply an offset or time zone.

The LocalDateTime class handles this kind of value.

ISO 8601

Your input string happens to comply with the ISO 8601 standard for date-time text formats.

The java.time classes use these standard formats by default when parsing/generating strings that represent date-time values. So, you can directly parse the input string without defining a formatting pattern.

Example code

The strategy here is to try parsing as if the input string includes an offset. If not, an exception (DateTimeParseException) is thrown. In that case, we try parsing again but as a LocalDateTime value. If that second attempt throws a parsing exception, then the input is completely unexpected.

Some coding-fundamentalists will protest this use of nested exception testing. While I understand their concerns as this approach can be abused, in this particular kind of situation I maintain nested exception testing is acceptable, logical, and clear.

String input = "2016-07-11T16:50:22.00"; // "2016-07-11T16:50:22.00+05:00";
Boolean hasOffset = null;
try {
    OffsetDateTime odt = OffsetDateTime.parse ( input );
    hasOffset = Boolean.TRUE;
    ZoneOffset offset = odt.getOffset ();
    System.out.println ( "input: " + input + " | hasOffset: " + hasOffset + " | odt: " + odt + " | offset: " + offset );
} catch ( java.time.format.DateTimeParseException e1 ) {
    // Perhaps input lacks offset-from-UTC. Try parsing as a local date-time.
    try {
        LocalDateTime ldt = LocalDateTime.parse ( input );
        hasOffset = Boolean.FALSE;
        System.out.println ( "input: " + input + " | hasOffset: " + hasOffset + " | ldt: " + ldt );
    } catch ( java.time.format.DateTimeParseException e2 ) {
        System.out.println ( "ERROR - Unexpected format in the input string" ); // FIXME: Handle format exception.
    }
}

When run with 2016-07-11T16:50:22.00+05:00.

input: 2016-07-11T16:50:22.00+05:00 | hasOffset: true | odt: 2016-07-11T16:50:22+05:00 | offset: +05:00

When run with 2016-07-11T16:50:22.00.

input: 2016-07-11T16:50:22.00 | hasOffset: false | ldt: 2016-07-11T16:50:22

Length testing

Of course you could always test the length of the input string. Given your example inputs, those with offsets will be longer than those without. Such length-testing can be brittle or error-prone if you have multiple kinds of input.

Question:

I have a timestamp and offset in string format as shown below in two different variables:

01/14/2016 07:37:36PM
-08:00

I want to convert above timestamp into ISO 8601 compliant String, with milliseconds and timezone so it should look like this after conversion:

2016-01-14T19:37:36-08:00

How can I do that? I am using jodatime library.


Answer:

The newer java.time classes work so well with ISO 8601 strings.

    String dateTimeString = "01/14/2016 07:37:36PM"; 
    String offsetString = "-08:00";

    LocalDateTime dateTime = LocalDateTime.parse(dateTimeString,
            DateTimeFormatter.ofPattern("MM/dd/uuuu hh:mm:ssa"));
    ZoneOffset offset = ZoneOffset.of(offsetString);
    String formattedTimestamp = dateTime.atOffset(offset).toString();
    System.out.println(formattedTimestamp);

This prints

2016-01-14T19:37:36-08:00

Stay away from outdated classes like SimpleDateFormat.

What is offsetString is not present? I understand that in this case you want an offset of Z for UTC. For example like this:

    ZoneOffset offset;
    if (offsetString == null) {
        offset = ZoneOffset.UTC;
    } else {
        offset = ZoneOffset.of(offsetString);
    }
    String formattedTimestamp = dateTime.atOffset(offset).toString();

With a null offsetString we now get

2016-01-14T19:37:36Z

The classes in java.time (of which I’m using but a few) are described in JSR-310 and come built-in with Java 8. What if you would like to use them with Java 6 or 7? You get the ThreeTen Backport (link below). It gives you the majority of the classes for Java 6 and 7. I’m not perfectly happy to tell you you need an external library, but in this case it’s only until you move to Java 8. I hope you will soon.

I am sure it can be done with JodaTime too, but I haven’t got experience with it, so cannot give you the details there. What I do know, I have read the the folks behind JodaTime now recommend you move over to java.time instead. So I am asking you to swap one external library for a newer (and supposedly better) one. In itself I’m not unhappy with that. Only if you already have a codebase that uses JodaTime, it’s not really trivial.

Link: ThreeTen Backport

Question:

I am using JodaTime to create ISO 8601 String.

DateTime jodatime = new DateTime(2016, 04, 05, 23, 59, 59, 999, DateTimeZone.UTC);
String converted = jodatime.toDateTimeISO().toString();

Right now, I am getting the following:

2016-04-06T06:59:59.999Z

However, I want to truncate/remove seconds and milliseconds.

2016-04-05T23:59Z

Does anyone know how to do this with the least hacky way? And can anyone tell me if that shortened version of ISO8601 can be recognized by date parsing libraries?


Answer:

The normal way of formatting a Joda Time value is using a formatter. In this case, the format you want is already available, except for the Z:

DateTimeFormatter formatter = ISODateTimeFormat.dateHourMinute();
String text = formatter.print(value);

The Z is slightly tricky - I don't believe you can specify exactly what you want with a simple pattern (DateTimeFormat.forPattern) but you can use a DateTimeFormatterBuilder:

DateTimeFormatter formatter = new DateTimeFormatterBuilder()
    .appendYear(4, 9)
    .appendLiteral('-')
    .appendMonthOfYear(2)
    .appendLiteral('-')
    .appendDayOfMonth(2)
    .appendLiteral('T')
    .appendHourOfDay(2)
    .appendLiteral(':')
    .appendMinuteOfHour(2)
    .appendTimeZoneOffset("Z", true, 2, 4)
    .toFormatter()
    .withLocale(Locale.US);

I believe that does exactly what you want.