No DST in Iran in 1970
The time zone for Iran, Asia/Tehran
, observes Daylight Saving Time (DST) in the summer of 2018 but did not do so back in 1970.
In summer of 2018, Iran is four and a half hours ahead of UTC rather than three and a half. So adjusting from 4:55 in UTC should result in 9:25 on a summer day, not 8:25.
System.out.println(
OffsetDateTime.of(
LocalDate.of( 2018 , Month.JUNE , 1 ) ,
LocalTime.parse( "04:55" ) ,
ZoneOffset.UTC
)
.atZoneSameInstant(
ZoneId.of( "Asia/Tehran" )
)
);
2018-06-01T09:25+04:30[Asia/Tehran]
But I suspect your code is defaulting to the epoch reference date of first moment of 1970 in UTC when parsing your string, 1970-01-01T00:00:00Z, because you are abusing that class in trying to parse a time-of-day without specifying a date.
In 1970, Iran did not observe Daylight Saving Time (DST). So the offset was three and a half hours ahead of UTC in summer of 1970 versus four and a half hours ahead of UTC in summer of 2018.
System.out.println(
OffsetDateTime.of(
LocalDate.EPOCH ,
LocalTime.parse( "04:55" ) ,
ZoneOffset.UTC
)
.atZoneSameInstant(
ZoneId.of( "Asia/Tehran" )
)
);
1970-01-01T08:25+03:30[Asia/Tehran]
Wrong classes
You are using the wrong classes.
You want to represent a time-of-day value. So you should be using a time-of-day class. But you are using a date-with-time-of-day class, java.util.Date
.
You are using troublesome badly-designed date-time classes, java.util.Date
& java.text.SimpleDateFormat
. These were supplanted years ago by the java.time classes. Avoid the legacy classes entirely. Use only the classes found in the java.time package.
Time-of-day
Parse a time-of-day string.
LocalTime.parse( "04:55" )
Get the current time-of-day.
LocalTime.now() // Capture the current time of day per the JVM’s current default time zone.
Better to make explicit your intention to use the JVM’s current default time zone.
LocalTime.now(
ZoneId.systemDefault() // Capture the current time of day by explicitly asking for the JVM’s current default time zone.
)
Or specify a particular time zone.
LocalTime.now(
ZoneId.of( "Asia/Kabul" ) // Capture the current time-of-day as seen in the wall-clock time used by the people of a particular region (a time zone).
)
9:25
Get the current time-of-day in UTC.
LocalTime.now(
ZoneOffset.UTC
)
04:55
LocalTime
If you have an input string such as "04:55", parse as a LocalTime
object. This class represents a time-of-day without a date and without a time zone.
String input = "04:55" ;
LocalTime lt = LocalTime.parse( input ) ;
Terminology
I try convert UTC time to local time
Your phrase “local time” has a specific meaning in date-time handling. Unfortunately that meaning is the opposite of your intention. The word “local” as seen in the *java.time.Local…” classes mean any locality or all localities rather than any one particular locality.
So a LocalTime
object has no real meaning until attached to a date and placed in the context of a time zone (or offset-from-UTC).
Time zones
if UTC time is 04:55 , my phone clock is 9:25
That means your JVM’s current default time zone is using an offset-from-UTC four and a half hours ahead of UTC, +04:30
. According to this list of time zones as managed by the IANA, there are only one time zone currently using that offset: Asia/Kabul
.
To represent the current moment fully, you need a date and a time-of-day and a zone/offset. To capture the current moment, use Instant
. The Instant
class represents a moment on the timeline in UTC with a resolution of nanoseconds (up to nine (9) digits of a decimal fraction).
Instant instant = Instant.now() ; // Capture the current moment in UTC.
To see that same moment in your time zone, apply a ZoneId
to get a ZonedDateTime
.
ZoneId zKabul = ZoneId.of( "Asia/Kabul" ) ;
ZonedDateTime zdt = instant.atZone( zKabul ) ;
If you really want only the time-of-day portion of that value, extract a LocalTime
. This might be useful for rendering in a user-interface, but is not likely useful in your business logic.
LocalTime lt = zdt.toLocalTime() ; // Extract just the time-of-day as seen in the wall-clock time used by the people of this region (this time zone).
As a shortcut, you could call LocalTime.now
:
LocalTime lt = LocalTime.now( zKabul ) ;
Iran time
Later in your comments you explain your intended time zone is Asia/Tehran
, time in Iran. Currently, Iran observes Daylight Saving Time (DST), which may be the source of your confusion. While standard offset if +03:30 (three and half hours ahead of UTC), between March 22 and September 22 the offset is +04:30, one more hour further ahead of UTC.
This is exactly why you should specify your desired/expected time zone. For casual use, you can use the JVM’s current default time zone. But know that default can change at any moment during runtime. And the default may not be what you intend. For critical usage, always confirm with the user their intended time zone.
Let's build up a date-time for June 1st with your example time 4:55 in UTC. We can use the constant ZoneOffset.UTC
. When using merely an offset-from-UTC (an offset of zero in this case), use OffsetDateTime
. An offset-from-UTC is merely a number of hours and minutes, nothing more, nothing less. In contrast, a time zone is a history of past, present, and future changes to the offset used by the people of a specific region.
LocalTime lt = LocalTime.parse( "04:55" ) ;
LocalDate ld = LocalDate.of( 2018 , Month.JUNE , 1 ) ;
OffsetDateTime odt = OffsetDateTime.of( ld , lt , ZoneOffset.UTC ) ;
odt.toString(): 2018-06-01T04:55Z
Adjust into the zone Asia/Tehran
by applying a ZoneId
to get a ZonedDateTime
. Same moment, same point on the timeline, but a different wall-clock time.
ZoneId zTehran = ZoneId.of( "Asia/Tehran" ) ;
ZonedDateTime zdt = odt.atZoneSameInstant( zTehran );
zdt.toString(): 2018-06-01T09:25+04:30[Asia/Tehran]
Note the time-of-day in summer shows as 9 hour, not 8.
Try the same code with month of January, when DST is not in effect.
LocalTime lt = LocalTime.parse( "04:55" );
LocalDate ld = LocalDate.of( 2018 , Month.JANUARY , 1 );
OffsetDateTime odt = OffsetDateTime.of( ld , lt , ZoneOffset.UTC );
ZoneId zTehran = ZoneId.of( "Asia/Tehran" );
ZonedDateTime zdt = odt.atZoneSameInstant( zTehran );
zdt.toString(): 2018-01-01T08:25+03:30[Asia/Tehran]
Now we see an hour of 8.
Zone names
Specify a proper time zone name in the format of continent/region
, such as America/Montreal
, Africa/Casablanca
, or Pacific/Auckland
. Never use the 3-4 letter abbreviation such as EST
or IST
as they are not true time zones, not standardized, and not even unique(!).
ZoneId z = ZoneId.of( "America/Montreal" ) ;
If you want to use the JVM’s current default time zone, ask for it and pass as an argument. If omitted, the JVM’s current default is applied implicitly. Better to be explicit, as the default may be changed at any moment during runtime by any code in any thread of any app within the JVM.
ZoneId z = ZoneId.systemDefault() ; // Get JVM’s current default time zone.
About java.time
The java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date
, Calendar
, & SimpleDateFormat
.
The Joda-Time project, now in maintenance mode, advises migration to the java.time classes.
To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.
You may exchange java.time objects directly with your database. Use a JDBC driver compliant with JDBC 4.2 or later. No need for strings, no need for java.sql.*
classes.
Where to obtain the java.time classes?
The ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval
, YearWeek
, YearQuarter
, and more.