turtleboy
turtleboy

Reputation: 7572

Create Joda datetime UTC from String

I have the following String that I would like to change to UTC:

Thu Aug 24 07:38:32 GMT+01:00 2017

I'm using Joda-Time library.

I know how to create a new Datetime eg new dateTime(DateTimeZone.UTC) but how can I create a DateTime object from the above String?

I have tried the following but get an exception. Surely there must be another way to create a DT obect without chopping the original String up? What if the external API changes how it sends my app the orignal String, my String manipulation code would fail.

DateTimeFormatter df = DateTimeFormat.forPattern("dd-MMM-YYYY HH:mm");
        String strOrigTime = "Thu Aug 24 07:38:32 GMT+01:00 2017";
        DateTime dt = DateTime.parse(strOrigTime, df);

        Log.e(TAG, "dt after parse = " + dt.toString());

Error:

Caused by: java.lang.IllegalArgumentException: Invalid format: "Thu Aug 24 07:38:32 GMT+01:00 2017"
    at org.joda.time.format.DateTimeFormatter.parseDateTime(DateTimeFormatter.java:866)
    at org.joda.time.DateTime.parse(DateTime.java:144)

Upvotes: 1

Views: 4228

Answers (1)

user7605325
user7605325

Reputation:

The format used (dd-MMM-YYYY HH:mm) means: day (dd) followed by -, followed by month (MMM), followed by -, followed by year (YYYY) and so on (check the javadoc for more details).

This format doesn't match the input string (which has day-of-week followed by month, followed by day, then hour/minute/second, etc). So the first thing is to use a format that matches the input, otherwise you'll always get "Invalid format" errors.

Another detail is that day of week and month names are in English, so you must also use a java.util.Locale to specify the language you're using to parse the input. If you don´t use a locale, the system default will be used, and it's not guaranteed to always be English (and it can also be changed, even at runtime, so it's always better to specify one).

I also had to add "GMT" as a literal and call withOffsetParsed() to make it include the offset (+01:00) in the parsed object:

DateTimeFormatter df = DateTimeFormat
    // use a pattern that matches input
    .forPattern("EEE MMM dd HH:mm:ss 'GMT'Z yyyy")
    // use English locale for day of week and month
    .withLocale(Locale.ENGLISH)
    // include the offset (+01:00) in the parsed object
    .withOffsetParsed();
String strOrigTime = "Thu Aug 24 07:38:32 GMT+01:00 2017";
DateTime dt = DateTime.parse(strOrigTime, df);
System.out.println(dt.toString());

The output is:

2017-08-24T07:38:32.000+01:00

Then, you can set the UTC timezone to this object:

dt = dt.withZone(DateTimeZone.UTC);
System.out.println(dt.toString());

The output will be:

2017-08-24T06:38:32.000Z

Note that withZone method preserves the same instant (both dates represent the same point in time), just the timezone used in the output is changed. But both dates are equivalent (they represent the same instant, as 07:38 in offset +01:00 is the same as 06:38 in UTC).

If you want all dates to be converted to UTC, you can also set this in the formatter:

// set UTC to the formatter
df = df.withZone(DateTimeZone.UTC);

Then you don't need to call withZone in the DateTime objects: all parsed dates will be converted to UTC.


You also told that "if the external API changes how it sends my app the orignal String, my String manipulation code would fail".

Well, if the input String changes, you'll have to change your format as well - there's no other way, Joda-Time can't just guess what's the format, you have to tell it.

If you want to parse more than one format, there's a way to create a formatter that uses lots of different patterns and try to parse each one, until one of them works (or throw exception if none works). You could do something like that:

// format 1
DateTimeFormatter f1 = DateTimeFormat
    // use a pattern that matches input
    .forPattern("EEE MMM dd HH:mm:ss 'GMT'Z yyyy")
    // use English locale for day of week and month
    .withLocale(Locale.ENGLISH)
    // include the offset (+01:00) in the parsed object
    .withOffsetParsed();

// format 2
DateTimeFormatter f2 = DateTimeFormat.forPattern("dd/MM/yyyy HH:mm:ss Z");
// array of all possible formats
DateTimeParser[] parsers = { f1.getParser(), f2.getParser() };
// formatter that uses all the possible formats
DateTimeFormatter formatter = new DateTimeFormatterBuilder()
    // append array of possible formats
    .append(null, parsers)
    // create formatter
    .toFormatter().withLocale(Locale.ENGLISH).withOffsetParsed()
    // set all parsed objects to UTC
    .withZone(DateTimeZone.UTC);

// parse first format
System.out.println(DateTime.parse("Thu Aug 24 07:38:32 GMT+01:00 2017", formatter));
// parse second format
System.out.println(DateTime.parse("24/08/2017 07:38:32 +01:00", formatter));

Both dates will be parsed to:

2017-08-24T06:38:32.000Z

Then you can add new formats to the array, as needed.


Java new Date/Time API

Joda-Time is in maintainance mode and is being replaced by the new APIs, so I don't recommend start a new project with it. Even in joda's website it says: "Note that Joda-Time is considered to be a largely “finished” project. No major enhancements are planned. If using Java SE 8, please migrate to java.time (JSR-310).".

If you can't (or don't want to) migrate from Joda-Time to the new API, you can ignore this section.

If you're using Java 8, consider using the new java.time API. It's easier, less bugged and less error-prone than the old APIs.

If you're using Java <= 7, you can use the ThreeTen Backport, a great backport for Java 8's new date/time classes. And for Android, there's the ThreeTenABP (more on how to use it here).

The code below works for both. The only difference is the package names (in Java 8 is java.time and in ThreeTen Backport (or Android's ThreeTenABP) is org.threeten.bp), but the classes and methods names are the same.

The code to parse the inputs is very similar, with minor changes in the format. And I'm using the Instant class, because you want the output in UTC, and Instant represents a UTC instant:

// format 1
DateTimeFormatter f1 = DateTimeFormatter.ofPattern("EEE MMM dd HH:mm:ss O yyyy", Locale.ENGLISH);
// format 2
DateTimeFormatter f2 = DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm:ss XXX");
// formatter with both formats
DateTimeFormatter formatter = new DateTimeFormatterBuilder()
    // add format 1
    .appendOptional(f1)
    // add format 2
    .appendOptional(f2)
    // create formatter
    .toFormatter(Locale.ENGLISH);
// parse first format
System.out.println(Instant.from(formatter.parse("Thu Aug 24 07:38:32 GMT+01:00 2017")));
// parse second format
System.out.println(Instant.from(formatter.parse("24/08/2017 07:38:32 +01:00")));

This will output:

2017-08-24T06:38:32Z
2017-08-24T06:38:32Z

Upvotes: 2

Related Questions