1

I'm trying to take a string in the format of "HH:mm" and convert it from local to utc and vice versa. Here's what I have so far:

    private String doTimeConversion(String time, boolean type) {

    DateFormat dfLocal = new SimpleDateFormat("HH:mm");
    DateFormat dfUTC   = new SimpleDateFormat("HH:mm");
    TimeZone local =  TimeZone.getDefault();
    TimeZone utc   =  TimeZone.getTimeZone("UTC");

    dfLocal.setTimeZone(local);
    dfUTC.setTimeZone(utc);

    Date date;
    String newTime = "00:00";
    try {
        //If true: local to utc/If false: utc to local
        if (type) {
            date = dfLocal.parse(time);
            newTime = dfUTC.format(date);
        } else {
            date = dfUTC.parse(time);
            newTime = dfLocal.format(date);
        }

    } catch (ParseException e) {
        e.printStackTrace();
    }
    return newTime;
}

This seems to work mostly but the issue I'm having is that, although I'm supposed to be grabbing the devices time with TimeZone.getDefault() it is not taking into account Daylight Savings. Instead of changing 12:00(UTC) to 08:00(EDT) it changes it to 07:00(EST) instead.

Overall I just want to be able to take the time that the user has entered (the string) and use the devices time zone to assume what the user has entered is according to their timezone and convert it to utc as well as from utc back to the devices timezone.

Phantômaxx
  • 37,901
  • 21
  • 84
  • 115
Kent J.
  • 53
  • 7
  • So should we assume today’s date, or which date? In order to know whether we’re in summer time (DST). (`SimpleDateFormat` assumes Jan 1, 1970.) – Ole V.V. Oct 16 '18 at 04:16
  • FYI, the troublesome old date-time classes such as `java.util.Date`, `java.util.Calendar`, and `java.text.SimpleDateFormat` are now legacy, supplanted by the [*java.time*](https://docs.oracle.com/javase/10/docs/api/java/time/package-summary.html) classes. Most of the *java.time* functionality is back-ported to Java 6 & Java 7 in the [***ThreeTen-Backport***](http://www.threeten.org/threetenbp/) project. Further adapted for earlier Android in the [***ThreeTenABP***](https://github.com/JakeWharton/ThreeTenABP) project. See [*How to use ThreeTenABP…*](http://stackoverflow.com/q/38922754/642706). – Basil Bourque Oct 16 '18 at 22:00

2 Answers2

1

java.time

import org.threeten.bp.LocalDate;
import org.threeten.bp.LocalDateTime;
import org.threeten.bp.LocalTime;
import org.threeten.bp.ZoneId;
import org.threeten.bp.ZoneOffset;
import org.threeten.bp.ZonedDateTime;

public class ConvertTimeToAndFromUtc {

    /** @param type If true: local to utc/If false: utc to local */
    private static String doTimeConversion(String time, boolean type) {
        ZoneId localZone = ZoneId.systemDefault();
        LocalTime lt = LocalTime.parse(time);
        LocalDateTime ldt = LocalDate.now(localZone).atTime(lt);
        ZonedDateTime resultTime;
        if (type) {
            resultTime = ldt.atZone(localZone).withZoneSameInstant(ZoneOffset.UTC);
        } else {
            resultTime = ldt.atOffset(ZoneOffset.UTC).atZoneSameInstant(localZone);
        }
        LocalTime newTime = resultTime.toLocalTime();
        return newTime.toString();
    }

    public static void main(String[] args) {
        System.setProperty("user.timezone", "Asia/Gaza");

        System.out.println("09.21 local to UTC: " + doTimeConversion("09:21", true));
        System.out.println("23:13 UTC to local: " + doTimeConversion("23:13", false));
    }

}

Today the above program printed (tested on Java 7):

09.21 local to UTC: 06:21
23:13 UTC to local: 02:13

The Gaza strip is at offset +03:00 at this time of year and +02:00 during standard/normal time, so it seems the conversion is taking summer time (DST) into account (the latter conversion is passing midnight).

I am using java.time, the modern Java date and time API. The date-time classes you used, DateFormat, SimpleDateFormat, TimeZone and Date, are all long outdated and come with a range of design problems. And they are not really suited for the problem at hand either.

Question: Can I use java.time on Android?

Yes, java.time works nicely on older and newer Android devices. It just requires at least Java 6.

  • In Java 8 and later and on newer Android devices (from API level 26) the modern API comes built-in. Only in this case import the date-time classes from the java.time package (not org.threeten.bp).
  • In Java 6 and 7 get the ThreeTen Backport, the backport of the new classes (ThreeTen for JSR 310; see the links at the bottom).
  • On (older) Android use the Android edition of ThreeTen Backport. It’s called ThreeTenABP. And make sure you import the date and time classes from org.threeten.bp with subpackages.

Links

Ole V.V.
  • 81,772
  • 15
  • 137
  • 161
  • This seems to work well and I've now implemented ThreeTenABP but I'm having a strange issue where the offset for "America/New_York" is appearing exactly as -04:56:02[America/New_York] when I'm quite positive it should be simply -04:00:00. The time zone in your example, however, works fine. – Kent J. Oct 17 '18 at 16:18
  • @KentJ. New York was at offset -4:56:02 until 1882! Could your date-time fall in this period of history? [Time Zone in New York, New York, USA](https://www.timeanddate.com/time/zone/usa/new-york), select eiher 1800–1849 or 1850–1899 from the dropdown. – Ole V.V. Oct 17 '18 at 17:01
  • Yep, when passing the year to my LocalDateTime I was giving it a 2-digit number (parsed from a string in the format of MM/dd/yy) which I guess caused it to believe the year was 0018 and not 2018. Incredible. Thank you for your help. – Kent J. Oct 17 '18 at 18:04
1

Using the ThreeTenABP I was able to convert my code into the following:

    /**
     * @param d      int arr that holds date vals
     * @param from   Time zone to convert from
     * @param to     Time zone to convert to
     * @return       Converted date as String
     */
    protected String doConvertTime(int[] d, ZoneId from, ZoneId to, DateTimeFormatter format){
        LocalDateTime before = LocalDateTime.of(d[0], d[1], d[2], d[3], d[4]);
        ZonedDateTime beforeTime = ZonedDateTime.of(before, from);
        ZonedDateTime afterTime = beforeTime.withZoneSameInstant(to);
        return afterTime.format(format);
    }

int[ ] d:

I'm parsing pieces of the Strings that are holding the values and putting them into an integer array that is passed to the function shown above. This seemed easier than parsing directly from the String because I'm working with multiple Strings. This way I'm able to make sure all of the data is condensed into one area.

ZoneId from & ZoneId to:

I initialized two ZoneId's at the start of the main function this way I can use them interchangeably. E.g. local to utc: from = local, to = utc / utc to local: from = utc, to = local. In reality any two timezones could be used.

DateTimeFormatter format:

I only wanted to return a String in the format of either "MM/dd/yy" or "HH:mm" because that's how my Strings holding these values prior are separated. I need all the data regarding the date, however, to ensure that the proper time zone is being used as well as the day, month, or even year are not being changed due to the timezone change. E.g. 2018-12-31 22:00 (EST) "America/New_York" --> 2019-01-01 03:00 (UTC) (-05:00 offset) If I only had the time (HH:mm) value then the date would not have changed.

Kent J.
  • 53
  • 7
  • Thanks for sharing your solution. Parsing pieces of strings into `int`s is non-standard, and I am not necessarily convinced that it’s the best solution, but whatever works for you and makes you happy… – Ole V.V. Oct 18 '18 at 01:46
  • 1
    Without a doubt, parsing my strings into `int`s is not the best solution and I'm certainly not recommending anyone else do this. It's just what's working for me at the moment and perhaps at a later time I will come back and find a better way. – Kent J. Oct 18 '18 at 15:07