简体   繁体   中英

Why is my UTC instant not converting to BST?

I have the following code:

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.UK);
Instant inst = DateTimeUtils.toInstant(sdf.parse("2019-08-13T18:00:00Z"));
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm").withLocale(Locale.UK).withZone(ZoneId.of("Europe/London"));
System.out.println(formatter.format(inst));
//prints 18:00

This is surprising to me as I thought inst would be a GMT/UTC time and the formatter would format it to London time (which is BST (UTC+1:00) for this date), producing 19:00 .

What am I missing here?

I'm guessing this is a generic issue with my code, but if it makes a difference this is using the org.threeten.bp.* classes from the ThreeTen-Backport project, further adapted for early Android in the ThreeTenABP project.

tl;dr

Instant                                    // Represent a moment in UTC.
.parse(                                    // Generate a `Instant` object from the content of text input.
    "2019-08-13T18:00:00Z"                 // String in standard ISO 8601 format.
)                                          // Returns a `Instant` object.
.atZone(                                   // Adjust from UTC to the wall-clock time used by the people of a particular region (a time zone). 
    ZoneId.of( "Europe/London" )           // Specify a time zone using name in proper `Continent/Region` format. Never use 2-4 letter pseudo-zones such as `BST`.
)                                          // Returns a `ZonedDateTime` object.
.toLocalTime()                             // Extract the time-of-day, without a date and without a time zone or offset. Returns a `LocalTime` object.
.format(                                   // Generate text representing the content of this `LocalTime` object. 
    DateTimeFormatter
    .ofLocalizedTime ( FormatStyle.SHORT ) // Automatically localize while generating a `String`.
    .withLocale ( Locale.UK )              // Locale determines the human language and cultural norms to use in localizing.
)                                          // Returns a `String` object.

19:00

Avoid legacy date-time classes

Your are mixing the terrible legacy classes ( SimpleDateFormat , Date ) with the modern java.time classes. Don't do that. Use only java.time .

Instant = moment in UTC

Skip your first two lines of code. Your input string "2019-08-13T18:00:00Z" is in standard ISO 8601 format. These standard formats are used by default by the java.time classes when parsing/generating strings. So no need to specify a formatting pattern.

String input = "2019-08-13T18:00:00Z" ;
Instant instant = Instant.parse( input ) ;

instant.toString(): 2019-08-13T18:00:00Z

Instant is not flexible

I suspect your problem was in your attempt to format the value within a Instant . The Instant class is a basic building-block class within java.time . It merely represents a moment in UTC. It is not intended for things such as flexible generation of strings.

The more flexible classes are OffsetDateTime & ZonedDateTime classes.

ZonedDateTime

Apply a ZoneId to your Instant to adjust into a time zone, rendering a ZonedDateTime object.

ZoneId z = ZoneId.of( "Europe/London" ) ;
ZonedDateTime zdt = instant.atZone( z ) ;

zdt.toString(): 2019-08-13T19:00+01:00[Europe/London]

You seem to want to focus on the time-of-day alone. Extract a LocalTime object.

LocalTime lt = zdt.toLocalTime ();

lt.toString(): 19:00

For the London region in Daylight Saving Time (DST) on that date, the offset-from-UTC is one hour ahead. So we see the time-of-day is 7 PM versus the 6 PM of UTC.

Proper time zone

By the way BST is not a time zone. I suggest you avoid using these pseudo-zones.

Specify a proper time zone name in the format of Continent/Region , such as America/Montreal , Africa/Casablanca , or Pacific/Auckland . Never use the 2-4 letter abbreviation such as BST or EST or IST as they are not true time zones, not standardized, and not even unique(!).

ZoneId z = ZoneId.of( "Africa/Tunis" ) ;  

Use smart objects, not dumb strings

Your example code suggests you are too focused on strings. Use smart objects, not dumb strings.

Get your objects straight, using appropriate types. Generating strings should be the last step, a side-effort, akin to localization. Your business logic should be done by using proper objects, not by manipulating strings.

Localization

And speaking of localization:

Locale locale = Locale.UK;
DateTimeFormatter f = DateTimeFormatter.ofLocalizedTime ( FormatStyle.MEDIUM ).withLocale ( locale );
String output = lt.format ( f );

19:00:00

Switch the locale to Locale.US for a different kind of result:

7:00:00 PM


All the code above was run in Java 13 early-access with the ThreeTen-Backport library per your needs stated in the Question.

import org.threeten.bp.* ;
import org.threeten.bp.format.* ;

Note to the reader: The ThreeTen-Backport library is further adapted for early Android in the ThreeTenABP library. See How to use ThreeTenABP in Android . If using Android 26 and later, the java.time classes are bundled, so you do not need the back-port at all.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM