Search code examples
javadatejava-8utcjava-time

Convert string local date with zoneId to UTC Offsetdate JAVA


I'm trying to convert a Brazil local date to UTC format. I have developed my solution but I'm sure that it can be improved. I have been searching for others questions but without success.

My problem is that when I process the Date object with:

Instant endDateTime = questionDate.toInstant();

I receive a UTC date as "2017-11-16T00:00:00Z" but this should be Brazil local date (not correct because it has a trailing "Z") and when I try to convert to UTC, I receive the same output.

In another hand, if I use ZoneDateTime class and build the date with LocalDateTime object I lose the seconds in the output: "2017-11-16T02:00Z". This happens when I use:

LocalTime.of(hour, minutes, seconds);

I search into LocalTime class and I think this is because minutes or seconds are 0 but I'm not sure of it.

Problems of the solution:

  1. The response hasn't seconds
  2. I hope Java 8 has a set of functions to make this more simple and clear

Precondition:

  • I can't use Joda library
  • Result has to be OffsetDateTime class
  • Input: Date "2017-11-16"
  • Output: "2017-11-16T02:00:00Z"

This is my solution:

private static OffsetDateTime processDate(Date questionDate) {
  Instant endDateTime = questionDate.toInstant();
  ZoneId zoneId = ZoneId.of(ZONEID);
  String [] date = endDateTime.toString().split("T");
  LocalDateTime localDateTime = convertLocalTimeToUtc(date);
  ZonedDateTime zonedDateTime = ZonedDateTime.of(localDateTime, zoneId);
  ZonedDateTime utcDate = zonedDateTime.withZoneSameInstant(ZoneOffset.UTC);
  return utcDate.toOffsetDateTime();
}

private static LocalDateTime convertLocalTimeToUtc(String[] dateFromCountry) {
  LocalDate date = processDate(dateFromCountry[0]);
  LocalTime time = processTime(dateFromCountry[1]);
  return  LocalDateTime.of(date, time);
}

private static LocalDate processDate(String dateFromCountry) {
  String [] partsOfDate = dateFromCountry.split("-");
  int year = Integer.parseInt(partsOfDate[0]);
  int month = Integer.parseInt(partsOfDate[1]);
  int day = Integer.parseInt(partsOfDate[2]);
  return LocalDate.of(year, month, day);
}

private static LocalTime processTime(String dateFromCountry) {
  String [] partsOfTime = dateFromCountry.split(":");
  int hour = Integer.parseInt(partsOfTime[0]);
  int minutes = Integer.parseInt(partsOfTime[1]);
  int seconds = Integer.parseInt(partsOfTime[2].substring(0,1));
  return LocalTime.of(hour,minutes,seconds);
}

Solution

  • If your input is a java.util.Date, you can get rid of all the string manipulation:

    //simulate your input
    Instant input = Instant.parse("2017-11-16T00:00:00Z");
    Date d = Date.from(input);    
    
    //transformation code starts here
    Instant instant = d.toInstant();
    
    ZonedDateTime localInstant = instant.atZone(ZoneOffset.UTC);
    ZonedDateTime sameLocalInBrazil = utcInstant.withZoneSameLocal(ZoneId.of("Brazil/East"));
    OffsetDateTime sameInstantUtc = sameLocalInBrazil.toOffsetDateTime()
                                          .withOffsetSameInstant(ZoneOffset.UTC);
    

    This return an OffsetDateTime with value 2017-11-16T02:00Z as you expect.

    Note that an OffsetDateTime has no formatting - so the object does know that its seconds are set to 0 but the default toString method doesn't print them. If you want to print it with seconds, you can use a formatter:

    //Formatting
    System.out.println(sameInstantUtc.format(DateTimeFormatter.ISO_INSTANT));
    

    which prints 2017-11-16T02:00:00Z


    If your input is a java.sql.Date, you can use a slightly different strategy:

    LocalDate d = sqlDate.toLocalDate();
    ZonedDateTime localInstant = d.atStartOfDay(ZoneOffset.UTC);
    

    The rest of the code would be the same.