Reputation: 49
I am trying to convert UTC time to local time, including daylight saving. Localtime (Stockholm), in summer, should be 2 hours ahead of UTC, but when I convert it in Java it only adds one hour.
public class TimeConverter {
public static void main(String[] args) throws ParseException {
getLocalTime("2:36:10 AM");
}
public static String getLocalTime(String utcTime) throws ParseException {
DateFormat utc = new SimpleDateFormat("hh:mm:ss a");
utc.setTimeZone(TimeZone.getTimeZone("UTC"));
Date date = utc.parse(utcTime);
DateFormat local = new SimpleDateFormat("HH:mm:ss");
local.setTimeZone(TimeZone.getDefault());
System.out.println(utc.getTimeZone());
System.out.println(local.getTimeZone());
System.out.println("UTC TIME\t" + utcTime);
System.out.println("LOCAL TIME\t" + local.format(date));
return local.format(date);
}
}
Output:
sun.util.calendar.ZoneInfo[id="UTC",offset=0,dstSavings=0,useDaylight=false,transitions=0,lastRule=null]
sun.util.calendar.ZoneInfo[id="Europe/Stockholm",offset=3600000,dstSavings=3600000,useDaylight=true,transitions=143,lastRule=java.util.SimpleTimeZone[id=Europe/Stockholm,offset=3600000,dstSavings=3600000,useDaylight=true,startYear=0,startMode=2,startMonth=2,startDay=-1,startDayOfWeek=1,startTime=3600000,startTimeMode=2,endMode=2,endMonth=9,endDay=-1,endDayOfWeek=1,endTime=3600000,endTimeMode=2]]
UTC TIME 2:36:10 AM
LOCAL TIME 03:36:10
Upvotes: 0
Views: 1451
Reputation: 338564
Your code inadvertently used the moment of 1970-01-01T02:36:10Z, 2 AM on the first day of 1970 as seen in UTC. Then you adjusted to Stockholm time, still in 1970 when no DST was in effect. Thus an offset of one hour (+01:00) rather than the two hours (+02:00) that you expected.
Use modern java.time classes instead.
OffsetDateTime // Represent a date, a time-of-day, and an offset-from-UTC.
.of( // Static factory method.
LocalDate.now( ZoneOffset.UTC ) , // Date
LocalTime.parse( "02:36:10" ) , // Time-of-day
ZoneOffset.UTC // A constant representing an offset from UTC of zero hours-minutes-seconds.
) // Returns an `OffsetDateTime` object.
.atZoneSameInstant( // Adjust to a specific time zone. Same moment, different wall-clock time/calendar.
ZoneId.of( "Europe/Stockholm" )
) // Returns a `ZonedDateTime` object.
.toString() // Returns a `String` object containing text in standard ISO 8601 format wisely extended by appending the name of the time zone in square brackets.
2023-05-06T04:36:10+02:00[Europe/Stockholm]
We see your 2 hour offset, as expected.
You are using terrible date-time classes that are now legacy, supplanted years ago by the modern java.time classes defined in JSR 310.
Asking about a time of day alone in UTC makes no sense. You need a date as well as a time to determine a moment, to represent a point on the timeline.
When communicating data textually to represent a time, use standard ISO 8601 format. For a time of day that means simply 24-hour clock with padding zeros.
LocalTime lt = LocalTime.parse( "02:36:10" ) ;
LocalDate
& OffsetDateTime
Apply a date to determine a moment. Perhaps you intended the current date as seen in UTC.
LocalDate todayUtc = LocalDate.now( ZoneOffset.UTC ) ;
OffsetDateTime odt = OffsetDateTime.of( todayUtc , lt , ZoneOffset.UTC ) ;
ZonedDateTime
Adjust to a time zone. Same moment, different wall-clock time & calendar.
ZoneId zStockholm = ZoneId.of( "Europe/Stockholm" ) ;
ZonedDateTime zdtStockholm = odt.atZoneSameInstant( zStockholm ) ;
Generate text in standard ISO 8601 format.
String output = zdtStockholm.toString() ;
2023-05-06T04:36:10+02:00[Europe/Stockholm]
There we see the two-hour offset you expected.
Your code failed because your java.util.Date
object represents 2 AM on the first day of 1970 as seen in UTC.
Avoid calling java.util.Date#toString
. That method unfortunately applies the JVM‘s current default time zone while generating its text. That poor design decision muddies the water.
For convenience, we convert your legacy java.util.Date
object to its modern replacement, a java.time.Instant
object — both represent a moment as seen with an offset from UTC of zero hours-minutes-seconds. Fortunately, Instant#toString
tells the truth, unlike Date#toString
.
System.out.println( new SimpleDateFormat( "hh:mm:ss a" ).parse( "2:36:10 AM" ).toInstant() );
1970-01-01T10:36:10Z
Let's look at the time zone rules for Sweden for that moment.
Instant instant = new SimpleDateFormat( "hh:mm:ss a" ).parse( "2:36:10 AM" ).toInstant();
ZoneRules rules = ZoneId.of( "Europe/Stockholm" ).getRules();
boolean isDst = rules.isDaylightSavings( instant );
System.out.println( "isDst = " + isDst );
isDst = false
So there was 👉 no Daylight Saving Time (DST) in effect in that Sweden time zone at that moment in 1970.
rules.getTransitions().toString()
We can see in that list of transitions that between 1949-10-02 and 1980-04-06 the offset was +01:00. So no +02:00 as you expected.
Upvotes: 6
Reputation: 18568
java.time
is a better choice than older packages/classes.
See this example, which takes today as date and converts from UTC to Europe/Stockholm:
public static void main(String[] args) {
// input time as String
String someTime = "2:36:10 AM";
// create a formatter for those time Strings
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("h:m:s a", Locale.ENGLISH);
// parse the time (time only, no date or zone involved so far)
LocalTime localTime = LocalTime.parse(someTime, dtf);
// involve a date (today here)
LocalDateTime localDateTime = LocalDateTime.of(LocalDate.now(), localTime);
// create a zone to involve
ZoneId stockholm = ZoneId.of("Europe/Stockholm");
// compose the (previously composed) date and time, and the zone
ZonedDateTime utcTime = ZonedDateTime.of(localDateTime, ZoneOffset.UTC);
// then use that moment in time and express it in a different zone (UTC here)
ZonedDateTime stockholmTime = utcTime.withZoneSameInstant(stockholm);
// print both zoned datetimes
System.out.println("UTC time: " + utcTime);
System.out.println("local time: " + stockholmTime);
}
Output:
UTC time: 2023-05-07T02:36:10Z
local time: 2023-05-07T04:36:10+02:00[Europe/Stockholm]
Upvotes: 1
Reputation: 320
Maybe you have not updated the IANNA time zones database for your Java version, read this go to the index on the page for java time.(html page) https://drive.google.com/file/d/1gjHmdC-BW0Q2vXiQYmp1rzPU497sybNy/view?usp=drivesdk
And that's also a truth Calendar and TimeZone in java.util are thereabout completely deprecated since Sun/Oracle Java 1.8.0 Calendar TimeZones Offset has two different offsets "Raw" (geographic official iso) and actual, and are set in seconds +/- .
Second, Calendar for most European purpose is actually the Gregorian sub class instance, Calendar itself is simply a top level abstraction class.
Upvotes: -3
Reputation: 86774
This answers the OP's question (why is this happening), as others have already recommended to use the modern date/time classes.
The definition of SimpleDateFormat#parse(...)
includes:
public Date parse(String text, ParsePosition pos)
Parses text from a string to produce a Date.
The method attempts to parse text starting at the index given by pos.
...
This parsing operation uses the calendar to produce a Date. All of the calendar's date-time fields are cleared before parsing, and the calendar's default values of the date-time fields are used for any missing date-time information. For example, the year value of the parsed Date is 1970 with GregorianCalendar if no year value is given from the parsing operation. The TimeZone value may be overwritten, depending on the given pattern and the time zone value in text. Any TimeZone value that has previously been set by a call to setTimeZone may need to be restored for further operations.
In 1970 Stockholm did not observer DST and was therefore +01:00
Upvotes: 1