1

I've tried all sorts of different conversions with different Java formatters but I'm still not having any luck with something that seems simple.

I have a string that is a date/time in UTC. I'm trying to convert that to another time zone. Is any one able to tell me why the below isn't working? The time zone is changing but it's not changing the right way.

Updated: (though it doesn't seem like I'm setting the time zone to UTC properly as the conversion isn't correct either).

String dateInput = "2021-02-16 20:57:43";

SimpleDateFormat mdyUtc = new SimpleDateFormat ("yyyy-MM-dd hh:mm:ss");
mdyUtc.setTimeZone(TimeZone.getTimeZone("UTC");
Date utcOutput = mdyUtc.parse(dateInput);

SimpleDateFormat mdyOffset = new SimpleDateFormat ("yyyy-MM-dd hh:mm:ss");
mdyOffset.setTimeZone(TimeZone.getTimeZone("GMT-10:00");
Date localOutput = mdyOffset.parse(dateInput);

System.out.print("UTC date = " + utcOutput);

System.out.print("Changed date = " + localOutput);

Output:

UTC date = Tue Feb 16 15:57:43 EST 2021

Changed date = Wed Feb 17 01:57:43 EST 2021

Buster
  • 687
  • 1
  • 7
  • 25
  • Wouldn't you have to set the input timezone to UTC, and format the date-time with the local timezone? – Gilbert Le Blanc Mar 16 '21 at 19:33
  • I tried that and still no luck. I can redo my post to include, I was trying to keep it as minimal as possible. – Buster Mar 16 '21 at 19:35
  • I recommend you don’t use `SimpleDateFormat`, `TimeZone` and `Date`. Those classes are poorly designed and long outdated, the former in particular notoriously troublesome. Instead use `LocalDateTime`, `ZoneId, and `ZonedDateTime`, all from [java.time, the modern Java date and time API](https://docs.oracle.com/javase/tutorial/datetime/). – Ole V.V. Mar 16 '21 at 20:45
  • To me your output does immediately look right. Which output had you expected? – Ole V.V. Mar 16 '21 at 20:47
  • Are you aware that an old-fashioned `Date` object cannot have a time zone or UTC offset? Well, you need not be since as I said you shouldn’t use that class anyway. And the modern `ZonedDateTime` has god a time zone. – Ole V.V. Mar 16 '21 at 20:48
  • 1
    GMT-10:00 is not a time zone. Did you for example want Pacific/Honolulu? Always give time zone in this format, that is, *region/city*. – Ole V.V. Mar 16 '21 at 21:02

3 Answers3

6

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*.

Using the modern date-time API:

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

public class Main {
    public static void main(String[] args) {
        String dateInput = "2021-02-16 20:57:43";
        // Replace ZoneId.systemDefault() with ZoneOffset.UTC if this date-time is in UTC
        DateTimeFormatter dtf = DateTimeFormatter.ofPattern("u-M-d H:m:s", Locale.ENGLISH)
                .withZone(ZoneId.systemDefault());
        ZonedDateTime zdt = ZonedDateTime.parse(dateInput, dtf);
        ZonedDateTime result = zdt.withZoneSameInstant(ZoneId.of("GMT-10:00"));
        System.out.println(result);
    }
}

Output:

2021-02-16T10:57:43-10:00[GMT-10:00]

ONLINE DEMO

Learn more about the modern date-time API from Trail: Date Time.

Can I get java.util.Date from ZonedDateTime?

If at all you need to use java.util.Date, you can convert ZonedDateTime into it as follows:

Date date = Date.from(result.toInstant());

Note that the java.util.Date object is not a real date-time object like the modern date-time types; rather, it represents the number of milliseconds since the standard base time known as "the epoch", namely January 1, 1970, 00:00:00 GMT (or UTC). When you print an object of java.util.Date, its toString method returns the date-time in the JVM's timezone, calculated from this milliseconds value. If you need to print the date-time in a different timezone, you will need to set the timezone to SimpleDateFormat and obtain the formatted string from it.


* 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
  • Thanks for the detailed answer. My new output shows GMT-10:00 at the end but it's only changed by 5 hours. Do you know why this would be? How do I specify the original string as UTC (if that needs to be done)? Thanks – Buster Mar 16 '21 at 20:09
  • Are you printing `date` obtained using `Date.from(result.toInstant())`? – Arvind Kumar Avinash Mar 16 '21 at 20:11
  • No, that was from your first code block printing "result". – Buster Mar 16 '21 at 20:14
  • I am surprised . I've posted a link to an online demo. Please check there. It looks like there is something wrong with your laptop's clock setting. – Arvind Kumar Avinash Mar 16 '21 at 20:20
  • 3
    @Buster Did you follow the comment `Replace ZoneId.systemDefault() with ZoneOffset.UTC if this date-time is in UTC`? – Ole V.V. Mar 16 '21 at 20:52
  • 1
    @Ole that was why the time was changing incorrectly. I was unknowingly declaring that the time was local, not UTC. Thanks! – Buster Mar 18 '21 at 01:46
3

tl;dr

LocalDateTime                                    // Represent a date with time-of-day but lacking the context of a time zone or offset-from-UTC.
.parse(                                          // Interpret some text in order to build a date-time object.
    "2021-02-16 20:57:43".replace( " " , "T" )   // Convert to standard ISO 8601 string to parse by default without needing to specify a formatting pattern.
)                                                // Returns a `LocalDateTime` object.
.atOffset(                                       // Place that date with time into the context of an offset. Determines a moment, a specific point on the timeline.
    ZoneOffset.UTC                               // A constant for an offset of zero hours-minutes-seconds. 
)                                                // Returns an `OffsetDateTime` object.
.atZoneSameInstant(                              // Adjust the view of this moment as seen in the wall-clock time of some other time zone. Still the same moment, same point on the timeline.
    ZoneId.of( "Pacific/Honolulu" )              // Use a time zone, if known, rather than a mere offset. 
)                                                // Returns a `ZonedDateTime` object.
.toString()                                      // Generate text representing this moment in standard ISO 8601 format extended to append the time zone name in square brackets.

See this code run live at IdeOne.com.

2021-02-16T10:57:43-10:00[Pacific/Honolulu]

Details

The Answer by Avinash is correct, using a DateTimeFormatter with an assigned ZoneId. That works, but I prefer keeping the zone assignment separate from the formatter, to be more explicit to someone reading the code. This is only about my preference, not about correctness; both Answers are equally correct.

Parse your input as a LocalDateTime, as the input represents a date with time-of-day but lacks any indication of offset or time zone.

By default, the java.time classes use standard text formats defined in ISO 8601. If an input complies, no need to specify a formatting pattern. To comply, replace your input’s SPACE character in the middle with a T.

String input = "2021-02-16 20:57:43".replace( " " , "T" ) ;
LocalDateTime ldt = LocalDateTime.parse( input ) ;

You said you know for certain that input was meant to represent a date with time as seen in UTC, having an offset-from-UTC of zero hours-minutes-seconds. So we can apply an offset of zero using ZoneOffset to produce a OffsetDateTime.

Also, I suggest you educate the publisher of your data feed about using ISO 8601 formats to communicate that offset-of-zero fact by appending a Z (as well as using T in the middle).

OffsetDateTime odt = ldt.atOffset( ZoneOffset.UTC ) ; // Place date with time into context of an offset of zero.

Lastly, you said you want to adjust that moment to another time zone. Apply a ZoneId to get a ZonedDateTime object.

Actually, you specified an offset of "GMT-10:00". But it is better to use a time zone if known rather than a mere offset. A time zone is a history of past, present, and future changes to the offset used by the people of a particular region.

I will guess you want Hawaii time, Pacific/Honolulu.

ZoneId z = ZoneId.of( "Pacific/Honolulu" ) ;
ZonedDateTime zdt = odt.atZoneSameInstant( z ) ;
Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
0

The java.util.Date API is deprecated; you should look into the new Date and Time APIs around LocalTime et al.

That said, if you want to keep the old code: It is a bit brittle. Your initial date input does not specify a time zone, so you'll probably get the system's time zone. You should specify a time zone --- if the expected input is UTC, say so.

Then you need to specify the time zone either in an hour offset or with a name, not both.

When I change your code to use

mdyOffset.setTimeZone(TimeZone.getTimeZone("-10:00"));

I get

Changed date = Tue Feb 16 14:57:43 CST 2021

which seems to fit, as I'm on CST (currently 6 hours after GMT), so 20:57:43 minus 6 is 14:57:43. Again, this is displayed in my local time zone. You may have to use a DateFormat to adjust the output as needed.

Robert
  • 7,394
  • 40
  • 45
  • 64