2

Question

Why is my Android app unable to parse String str1= "Tue Jun 20 15:56:29 CEST 2017"?

I found some similar questions, but none of them helped me.

Sidenotes

In my project I have some Java applications which are running on a computer and some Android applications. They are able to communicate to each other.

In the messages are timestamps. However my Java applications are sending timestamps in a format like String str1= "Tue Jun 20 15:56:29 CEST 2017" and my Android apps like String str2 = "Tue Jun 20 13:40:37 GMT+02:00 2017". To save the message including the time I have to parse the incoming time to a date.

My Android-App

In my Android app I can't parse the String str1= "Tue Jun 20 15:56:29 CEST 2017" correctly:

java.text.ParseException: Unparseable date: "Tue Jun 20 15:56:29 CEST 2017"

String str2 = "Tue Jun 20 13:40:37 GMT+02:00 2017"is working fine.

Code:

    String str1 = "Tue Jun 20 14:53:08 CEST 2017";
    SimpleDateFormat formatter = new SimpleDateFormat("EE MMM dd HH:mm:ss zzzz yyyy", Locale.US);
    try {
        Date date = formatter.parse(str1);
    } catch (ParseException e) {
        e.printStackTrace();
    }
    // test
    String str2 = "Tue Jun 20 13:40:37 GMT+02:00 2017";
    formatter = new SimpleDateFormat("EE MMM dd HH:mm:ss zzzz yyyy", Locale.US);
    try {
        Date date = formatter.parse(str2);
    } catch (ParseException e) {
        e.printStackTrace();
    }

My Java-App

However, my Java application can parse both strings correctly.

Code:

    String str = "Tue Jun 20 14:53:08 CEST 2017";
    SimpleDateFormat formatter = new SimpleDateFormat("EE MMM dd HH:mm:ss zzzz yyyy", Locale.US);
    try {
        Date date = formatter.parse(str);
    } catch (ParseException e) {
        e.printStackTrace();
    }
    // test
    str = "Tue Jun 20 13:40:37 GMT+02:00 2017";
    formatter = new SimpleDateFormat("EE MMM dd HH:mm:ss zzzz yyyy", Locale.US);
    try {
        Date date = formatter.parse(str);
    } catch (ParseException e) {
        e.printStackTrace();
    }

ThreeTen Solution

String to LocalDateTime

To convert my incoming string I'm using the following code:

    String time = "Mon Jun 26 15:42:51 GMT 2017";
    DateTimeFormatter gmtDateTimeFormatter = DateTimeFormatter.ofPattern("EE MMM dd HH:mm:ss 'GMT' yyyy", Locale.ENGLISH));
    LocalDateTime timestamp = LocalDateTime.parse(time, gmtDateTimeFormatter);

LocalDateTime to String

To convert my LocalDateTime to a string I used this:

    LocalDateTime timestamp = LocalDateTime.now();
    DateTimeFormatter gmtDateTimeFormatter = DateTimeFormatter.ofPattern("EE MMM dd HH:mm:ss 'GMT' yyyy", Locale.ENGLISH));
    String time = gmtDateTimeFormatter.format(timestamp); 
Deweird
  • 49
  • 1
  • 1
  • 8
  • FYI, the troublesome old date-time classes such as [`java.util.Date`](https://docs.oracle.com/javase/8/docs/api/java/util/Date.html), [`java.util.Calendar`](https://docs.oracle.com/javase/8/docs/api/java/util/Calendar.html), and `java.text.SimpleTextFormat` are now [legacy](https://en.wikipedia.org/wiki/Legacy_system), supplanted by the [java.time](https://docs.oracle.com/javase/8/docs/api/java/time/package-summary.html) classes. See [Tutorial by Oracle](https://docs.oracle.com/javase/tutorial/datetime/TOC.html). For Android, see the ThreeTen-Backport project. – Basil Bourque Jun 20 '17 at 21:56
  • 2
    It would really be best if you could get a string that doesn’t include a four letter time zone abbreviation. The three and four letter abbreviations are generally ambiguous and sometimes — as with CEST — only half time zones. For CEST I wouldn’t know what result to expect if the date was in winter where CEST isn’t used. – Ole V.V. Jun 21 '17 at 08:55

2 Answers2

2

Maybe there's a difference on how Android handles the zzzz pattern (probably Java's implementation handles it better than Android, so it "guesses" the correct timezone in a way that Android doesn't). I don't know.

Anyway, may I suggest you to avoid using those old classes? These old classes (Date, Calendar and SimpleDateFormat) have lots of problems and design issues, and they're being replaced by the new APIs.

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.

To parse both formats, you can use a DateTimeFormatter with optional sections. That's because CEST is a timezone short name and GMT+02:00 is an UTC offset, so if you want to parse both with the same formatter, you'll need to use one optional section for each format.

Another detail is that short names like CET or CEST are ambiguous and not standard. The new API uses IANA timezones names (always in the format Continent/City, like America/Sao_Paulo or Europe/Berlin).

So, you need to choose one timezone that suits your needs. In the example below, I've just picked a timezone that's in CEST (Europe/Berlin), but you can change it according to what you need - you can get a list of all names using ZoneId.getAvailableZoneIds().

As the new API doesn't resolve CEST (because of its ambiguity), I need to create a set with the prefered timezone in order to correctly parse the input:

// when parsing, if finds ambiguous CET or CEST, it uses Berlin as prefered timezone
Set<ZoneId> set = new HashSet<>();
set.add(ZoneId.of("Europe/Berlin"));

DateTimeFormatter fmt = new DateTimeFormatterBuilder()
    // your pattern (weekday, month, day, hour/minute/second)
    .appendPattern("EE MMM dd HH:mm:ss ")
    // optional timezone short name (like "CST" or "CEST")
    .optionalStart().appendZoneText(TextStyle.SHORT, set).optionalEnd()
    // optional GMT offset (like "GMT+02:00")
    .optionalStart().appendPattern("OOOO").optionalEnd()
    // year
    .appendPattern(" yyyy")
    // create formatter (using English locale to make sure it parses weekday and month names correctly)
    .toFormatter(Locale.US);

To parse Tue Jun 20 14:53:08 CEST 2017, just use the formatter:

ZonedDateTime z1 = ZonedDateTime.parse("Tue Jun 20 14:53:08 CEST 2017", fmt);
System.out.println(z1);

The output is:

2017-06-20T14:53:08+02:00[Europe/Berlin]

Note that CEST was mapped to Europe/Berlin, according to the set we created.

To parse Tue Jun 20 13:40:37 GMT+02:00 2017, we can use the same formatter. But GMT+02:00 can be in a lot of different regions, so the API can't map it to a single timezone. To convert it to the correct timezone, I need to use withZoneSameInstant() method:

// parse with UTC offset
ZonedDateTime z2 = ZonedDateTime.parse("Tue Jun 20 13:40:37 GMT+02:00 2017", fmt)
    // convert to Berlin timezone
    .withZoneSameInstant(ZoneId.of("Europe/Berlin"));
System.out.println(z2);

The output is:

2017-06-20T13:40:37+02:00[Europe/Berlin]


PS: the first case (z1) works in Java 8, but in ThreeTen Backport it's not setting the timezone to Berlin. To fix it, just call .withZoneSameInstant(ZoneId.of("Europe/Berlin")) as we did with z2.


If you still need to use java.util.Date, you can convert from and to the new API.

In java.time, new methods were added to Date class:

// convert ZonedDateTime to Date
Date date = Date.from(z1.toInstant());

// convert back to ZonedDateTime (using Berlin timezone)
ZonedDateTime z = date.toInstant().atZone(ZoneId.of("Europe/Berlin")); 

In ThreeTen backport (and Android), you can use the org.threeten.bp.DateTimeUtils class:

// convert ZonedDateTime to Date
Date date = DateTimeUtils.toDate(z1.toInstant());

// convert back to ZonedDateTime (using Berlin timezone)
ZonedDateTime z = DateTimeUtils.toInstant(date).atZone(ZoneId.of("Europe/Berlin"));
  • Thanks for your help. So far I implemented in my Android-App ThreeTenABP and in my Java-App ThreeTen. Now there are still small problems. – Deweird Jun 26 '17 at 12:42
  • The Android-App print the date with an dot and the Java-App not. However, I guess there is no easy solution. I have to work with regular expressions and build my own string. – Deweird Jun 26 '17 at 12:53
  • Do you mean a dot after the seconds? In my examples I call `println` with the date object (which calls `toString` and it prints the fraction of seconds). If you want to change the output format, use a `DateTimeFormatter`. –  Jun 26 '17 at 12:55
  • No the dot appears behind the day. But only in the Android-App. (See my edited Question -> new problems) – Deweird Jun 26 '17 at 13:25
  • 1
    You're creating a formatter without a locale, so it uses the JVM's default. Try to use `DateTimeFormatter.ofPattern("EE MMM dd HH:mm:ss 'GMT' yyyy", Locale.ENGLISH)` instead (or `Locale.US`, probably both will work). –  Jun 26 '17 at 13:31
  • That's the solution. Thanks! – Deweird Jun 26 '17 at 13:43
1

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.

Do not use a fixed text for the timezone:

Do not use a fixed text (e.g. 'GMT') for the timezone as you have done because that approach may fail for other locales.

Solution using java.time, the modern Date-Time API:

import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Locale;

public class Main {
    public static void main(String[] args) {
        // Test
        System.out.println(parse("Tue Jun 20 14:53:08 CEST 2017"));
        System.out.println(parse("Tue Jun 20 13:40:37 GMT+02:00 2017"));
    }

    static ZonedDateTime parse(String strDateTime) {
        DateTimeFormatter dtf = DateTimeFormatter.ofPattern("E MMM d H:m:s z u", Locale.ENGLISH);
        return ZonedDateTime.parse(strDateTime, dtf);
    }
}

Output:

2017-06-20T14:53:08+02:00[Europe/Paris]
2017-06-20T13:40:37+02:00[GMT+02:00]

ONLINE DEMO

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.

Arvind Kumar Avinash
  • 71,965
  • 6
  • 74
  • 110