Home > Software design >  Portable way to parse local date/time (with implicit time zone)
Portable way to parse local date/time (with implicit time zone)

Time:01-31

I need to parse strings containing local date and time. The time zone is not explicitly specified, but the timestamps are in local time for a particular place.

Challenges are:

  • I need to be compatible with Java 7, which rules out a bunch of nifty features introduces with Java 8.
  • Java 7 compatibility is mostly needed for older Android versions, but the code will run on both JRE and Android.
  • Times are either in standard time or DST, depending on the time of the year. Hence a simple UTC offset will not work, as it jumps back and forward by an hour per year.

(This also means that timestamps during the DST to standard time switchover are ambiguous, but I can live with an hour of inaccuracy for that particular corner case.)

Here’s how far I got:

/*
 * Let timestampString be the date/time string, examples:
 * "2023-01-28 20:36"
 * "2022-07-07 13:37"
 */
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm zzzz");
Date date = dateFormat.parse(timestampString   " CET");
System.out.println("Timestamp: "   dateFormat.format(date));

Problem: this interprets everything as Central European Standard time, all year round – during the summer season, times are off by one hour.

Using a time zone name such as Europe/Brussels instead of CET (which works with DateTimeFormatter and ZonedDateTime on Java 8) does not work, as the string is not recognized.

Does Java 7 offer anything comparable? (Preferably without having to resort to external libraries.)

CodePudding user response:

Two general options:

Option 1: Use Java 8 classes, with API desugaring on Android

Pros: Future proof; does not suffer from the bugs in Java’s Date and related classes; gives you a number of Java 8 APIs

Cons: Will work on Android prior to API 26 (Android 8) but not on JRE7; may require toolchain upgrades (which you will eventually need to do anyway)

This is the preferred way at least if you don’t have to support old desktop Java versions. Java 8 support on Android (at least in part) is done through a process called desugaring.

First, make sure you are at least on Gradle 6.1.1 and version 4.0.0 of the Android Gradle plugin.

Your build.gradle should not specify a buildToolsVersion unless you need a specific one (which then needs to be 29.0.2 or higher).

Then add the following to your build.gradle:

android {
    defaultConfig {
        multiDexEnabled true
    }

    compileOptions {
        coreLibraryDesugaringEnabled true
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}

dependencies {
    coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.8'
}

Version 1.1.8 is equivalent to 1.2.2 (which is the version Google recommends as of January 2023) but works on older versions of the R8/D8 compiler (I got an error with 1.2.2 but 1.1.8 worked).

All the changes so far only apply to the Android build. For other platforms you may be running the code on, nothing changes.

This allows you to then run the following Java code:

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")
    .withZone(ZoneId.of("Europe/Brussels"));
try {
    ZonedDateTime zonedDate = ZonedDateTime.parse(rawDate, formatter);
    System.out.println("Timestamp: "   zonedDate.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm zzzz")))
} catch (DateTimeParseException e) {
    // do whatever is necessary here
}

If you need to pass this to something that expects a Date, you can convert that using Date.from(zonedDate.toInstant()).

Option 2: Use Java 7-compatible classes

Pros: Works on any Java 7 API, whether desktop or Android

Cons: Not future proof (as you are using deprecated classes); still suffers from all the bugs in Java’s Date and related classes; will solve this particular API incompatibility but not others

DateFormat has a setTimeZone() method, which accepts a TimeZone argument (which, in turn, can be generated from a string such as Europe/Brussels). Setting it will cause date/time strings to be parsed as local time in that time zone, with automatic adjustment for DST. No need to append anything to the string being parsed.

SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm");
dateFormat.setTimeZone(TimeZone.getTimeZone("Europe/Brussels"));
Date date = dateFormat.parse(timestampString);
/*
 * dateFormat no longer contains a time zone, so use a separate format
 * for output (with the time zone)
 */
SimpleDateFormat dateFormatOut = new SimpleDateFormat("yyyy-MM-dd HH:mm zzzz");
dateFormatOut.setTimeZone(TimeZone.getTimeZone("Europe/Brussels"));
System.out.println("Timestamp: "   dateFormatOut.format(date));
  • Related