0

I have been trying to convert local time (EST) to UTC and vice versa. So, I present a time picker, user selects the time, I convert the same to UTC and send to server. Here's the code:

        val cal = Calendar.getInstance()
        cal.set(Calendar.HOUR_OF_DAY, mHour) //mHour = 15
        cal.set(Calendar.MINUTE, mMinute)    //mMinute = 00
        cal.set(Calendar.SECOND, 0)
        val formatter = SimpleDateFormat("HH:mm:ss")
        formatter.timeZone = TimeZone.getTimeZone("UTC")
        val cutOffTime = formatter.format(cal.time)  //this gives 19:00:00 which is correct

Above output for cutOffTime is correct as 15:00 EST is 19:00 UTC after considering day light savings.

Now, I fetch this same cutOffTime from server, convert it to local (EST) and display. Here's the code:

        val cutOffTime = jsonObject.get("cutOffTime").getAsString())  //value is 19:00:00
        var cutoffTime: Time? = null
        val format = SimpleDateFormat("hh:mm:ss")
        format.timeZone = TimeZone.getTimeZone("UTC")
        cutoffTime = Time(format.parse(cutOffTime).time)

        //cutoffTime has value 14:00 which is strange, it should be 15:00

So, cutoffTime in above code has value 14:00 which is strange, it should be 15:00. Please note that this code was working before day light savings on March 8, 2020. Any idea what am I doing wrong?

varun
  • 465
  • 6
  • 23

2 Answers2

1

Please don't use the old and badly designed java api for date and times. Use the new java.time api. It's much more robust and is nicer to use.

Here is an example on how to do your code with java.time:

int mHour = 15;
int mMinute = 0;
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm:ss");

ZonedDateTime toFormat = LocalDateTime
    .now() // Current date and time.
    .withHour(mHour).withMinute(mMinute).withSecond(0) // Change hour, minute and second like in your example.
    .truncatedTo(ChronoUnit.SECONDS) // Throw away milliseconds and nanoseconds.
    .atZone(ZoneOffset.UTC); // Say that the time is located at UTC+0.

String formatted = formatter
    .withZone(ZoneId.of("America/New_York")) // Create a new formatter that formats toFormat to the local time America/New_York.
    .format(toFormat);

System.out.println(formatted); // Gives 11:00:00 on my machine now. America/New_York is either UTC-5 or UTC-4.

ZonedDateTime parsed = LocalTime
    .parse(formatted, formatter) // Parse the String. The result is an instance of LocalTime.
    .atDate(LocalDate.now(ZoneId.of("America/New_York"))) // Add the date values which are those of the current local date at America/New_York.
    .atZone(ZoneId.of("America/New_York")); // Create a ZonedDateTime of the LocalDateTime to explicitly define the zone.

Instant pointInTimeA = parsed.toInstant();
Instant pointInTimeB = toFormat.toInstant();

System.out.println(pointInTimeA.equals(pointInTimeB)); // Gives true because both describe the same point in time as they should.

The great benefit is that the api will handle everything regarding summertime and wintertime for you. Have read here for information about the EST and why you shouldn't use it.

The problem you face is probably due to the fact that you used HH in one formatter but hh in the other one. Those are not the same. Have read here about Patterns for Formatting and Parsing.

akuzminykh
  • 4,522
  • 4
  • 15
  • 36
  • Thanks for replying, I will try this. I tried using HH in both formatter. The issue is still there. Also, java.time was added in API level 26. My minimum API level is 21. Any other solution? – varun Mar 10 '20 at 20:33
  • Have a look [here](https://stackoverflow.com/questions/21349475/calendar-getinstancetimezone-gettimezoneutc-is-not-returning-utc-time), this looks related to me. Anyways, I highly recommend you to use the new api. Exactly problems like this are the weak point of the old api. You should read the documentation of the old api regarding day light saving carefully. The problem is most likely because of that. – akuzminykh Mar 10 '20 at 21:23
0

I ended up with below solution. I knew the format of the string from server, so I split it. I didn't want to use the new time API since it is available from API level 26 onwards.

                val cutOffString = jsonObject.get("cutOffTime").getAsString() //UTC date 19:00:00 from server

                val cal = Calendar.getInstance()
                cal.set(Calendar.HOUR_OF_DAY, cutOffString.split(":")[0].toInt())
                cal.set(Calendar.MINUTE, cutOffString.split(":")[1].toInt())
                cal.set(Calendar.SECOND, 0)
                cal.timeZone = TimeZone.getTimeZone("UTC")

                val cutoffTime: Time = Time(cal.time.time) //15:00:00

varun
  • 465
  • 6
  • 23