I have a date format stored in DB, for example:
Thu Aug 27 2020 00:00:00 GMT-0400 (Eastern Daylight Time)
I want to display the same date as output. Seems like I am missing something zone. It's evolving to be one day prior to this date.
I did the following:
DateTimeFormatter etFormat = DateTimeFormatter.ofPattern("MM/dd/yyyy 'at' hh:mma 'ET'");
ZoneId zoneId = ZoneId.of("America/New_York");
ZonedDateTime zonedDateTime = ((Timestamp) date).toLocalDateTime().atZone(zoneId);
etFormat.format(zonedDateTime)
Output:
08/26/2020 at 08:00PM ET
What am I doing wrong?
In your database you have the date time with offset UTC-04:40 (which is 4 hr behind from UTC assuming America/New_York timezone). And when it converts into Timestamp it will be stores in UTC without offset which is 08/26/2020 at 08:00PM.
So first convert the Timestamp into Instant of UTC and then convert the Instant into ZonedDateTime with the zone information
ZonedDateTime dateTime = timestamp.toInstant()
.atZone(ZoneOffset.UTC)
.withZoneSameInstant(ZoneId.of("America/New_York"));
etFormat.format(dateTime); //08/27/2020 at 00:00PM ET
The central issue is this:
java.sql.Timestamp, which is what e.g. resultSet.getTimestamp() returns, does not contain any timezone data. It is simply an instant in time, and it is stored as milliseconds since the epoch (jan 1st, 1970), UTC zone.
This does not match what most DBs store, because most DBs do in fact explicitly store the timezone with that. If your DB does not do this, or you picked a column type which does not do this, you should strongly consider changing that.
So, if the database has stored 'midnight in new york, aug 27th', and the database is forced by JDBC to put this in java.sql.Timestamp terms, there's nothing the DB engine can do about it, other than do its best, which is to return that exact time, in UTC terms. If you then print the UTC timestamp in human terms, you end up with '4 at night', and not 'midnight' (because new york is 4 hours earlier than UTC).
You then, with your code say: Okay, take the timestamp, turn it into a local date time (that'd be the notion of '27th of august, 4 o clock at night', without any inkling of in which czone that is in, and by itself not a thing that can ever be turned back into an epoch with more info), and then you put this at the new york zone, giving you '4 at night in new york', which is 4 hours later than where we started.
Okay, but how do I fix this?
Every other answer (so far) is just giving you silly ways to fight the symptoms.
I propose you fix the disease.
The actual error occurs when you ask the DB to transfer the fully timezoned information from its tables into the timezoneless java.sql.Timestamp object. Stop doing that.
Don't call (I assume your column is called 'mark', fill in whatever it might be):
resultSet.getTimestamp("mark").
Call:
resultSet.getObject("mark", ZonedDateTime.class);
or possibly try LocalDateTime.class, or possibly OffsetDateTime.class, but ZDT is preferred.
Then if that does not work, complain to your DB and/or JDBC driver because they're messing up and making it next to impossible to do timezone stuff properly when interacting with that DB from the java side.
Actually, the DB should store just a moment-in-time
If truly the time being stored represents the notion of an 'instant in time' and not so much 'as humans would ever talk to you about it', then there are data types for that too, but convert your java.sql.Timestamp object to a java.time.Instant asap (via .toInstant()), or straight up ask for it: resultSet.getObject("colName", Instant.class) and have java and the db line up the datatypes straight away.
Eh, whatever. Cures are for wussies, just work around it
Eh, well, the only thing you really need to do then is not to magically add 4 hours. This will do it:
ZonedDateTime dateTime = timestamp.toInstant()
.atZone(ZoneOffset.UTC)
.withZoneSameInstant(ZoneId.of("America/New_York"));
even if the tz stored in the DB is something else (it'll then give you that instant in time, but in new york, e.g. if the db has stored 'midnight in amsterdam', this will give you a time 6 hours earlier (or possibly 7 or 5, there are a few days in the year where things go ape due to US and europe having different shift days for daylight savings).
The format that you have used is not correct. I hope you will be able to understand the difference by comparing your pattern with mine. The reason why I've presented the parsing logic is that you have not made it clear the type of date-time. Whatever type it may be, it looks like you have a date-time string, Thu Aug 27 2020 00:00:00 GMT-0400 (Eastern Daylight Time) which you want to parse into ZonedDateTime and display the same into the pattern of the date-time string you have. I guess, the main problem you are having is how to format the ZonedDateTime instance into the same form.
Do it as follows:
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.time.format.TextStyle;
public class Main {
public static void main(String[] args) {
// Given date-time string
String dateStr = "Thu Aug 27 2020 00:00:00 GMT-0400 (Eastern Daylight Time)";
// Define the formatter for parsing
DateTimeFormatter parsingFormat = new DateTimeFormatterBuilder()
.appendPattern("EEE MMM dd uuuu HH:mm:ss zX")
.appendLiteral(" (")
.appendGenericZoneText(TextStyle.FULL)
.appendLiteral(")")
.toFormatter();
// Parse the given date-time into ZonedDateTime
ZonedDateTime zonedDateTime = ZonedDateTime.parse(dateStr, parsingFormat);
// Display in default format [i.e. zonedDateTime.toString()]
System.out.println(zonedDateTime);
// Define the formatter for output
DateTimeFormatter outputFormat = new DateTimeFormatterBuilder()
.appendPattern("EEE MMM dd uuuu HH:mm:ss z")
.appendLiteral(" (")
.appendPattern("zzzz")
.appendLiteral(")")
.toFormatter();
// Get the string representation in the custom format
String strDate = zonedDateTime.format(outputFormat);
// Display the string representation in the custom format
System.out.println(strDate);
}
}
Output:
2020-08-27T00:00-04:00[America/New_York]
Thu Aug 27 2020 00:00:00 GMT-04:00 (Eastern Daylight Time)
Note: By any chance, if you also have difficulty to convert the timestamp into ZonedDateTime, you can refer other answers on this page and use this answer to solve the problem with formatting.
java.time
I recommend that you use java.time, the modern Java date and time API, exclusively for your date work. Instead of getting a Date or Timestamp from your database, since JDBC 4.2 (in the case of MySQL that’s many years now) get a modern LocalDate from your result set. An example:
PreparedStatement ps = yourDatabaseConnection.prepareStatement("select your_date from your_table;");
ResultSet rs = ps.executeQuery();
while (rs.next()) {
LocalDate date = rs.getObject("your_date", LocalDate.class);
// Do something with date
}
A LocalDate is a date without time of day and without time zone. So this will relieve you of all time zone trouble.
If you want to print the start of the day in North American Eastern time zone to the user in the format used in the question:
DateTimeFormatter etFormat = DateTimeFormatter.ofPattern("MM/dd/yyyy 'at' hh:mma v");
ZoneId zoneId = ZoneId.of("America/New_York");
LocalDate date = LocalDate.of(2020, Month.AUGUST, 27);
ZonedDateTime startOfDay = date.atStartOfDay(zoneId);
String result = startOfDay.format(etFormat);
System.out.println(result);
Output from this example is:
08/27/2020 at 12:00AM ET
Do use pattern letter v for time zone in the format pattern rather than hard-coding ET. The latter will produce false and confusing results when one day a junior programmer feeds a ZonedDateTime in an other time zone into the code.
What went wrong in your code?
It’s not clear to me how you got your date from your database. Apparently date even though declared a Date was really a Timestamp (a bad practice since the inheritance relationship between the two classes is really one of implementation, not a conceptual one) denoting the start of the day in UTC. toLocalDateTime() is a dangerous and often meaningless call: it uses the time zone of the JVM for converting the Timestamp to a LocalDateTime. At 0:00 UTC it is 8 PM the evening before in Eastern time zone, so your LocalDateTime becomes 2020-08-26T20:00. Next atZone(zoneId) only gives the correct time because zoneId happens to coincide with the JVM’s time zone used in the previous step.
Link
Oracle tutorial: Date Time explaining how to use java.time.
The date is converted with timezone set to GMT.
final static String datePattern = "EEE MM/dd/yyyy HH:mm:ss 'GMT'Z '('z')'";
DateFormat df = new SimpleDateFormat(datePattern, Locale.getDefault());
simpledateformat.setTimeZone(TimeZone.getTimeZone("GMT"))
simpleDateFormat.format(givenDate)
Related
I'm working on an app where users can timestamp themselves IN or OUT from their workplace. At the moment I'm trying to get the localization of the timestamps done. For example when I make a timestamp in UTC +02:00 at 08:00 02.01.2020, it works correctly and shows the time as 08:00 and right date as well. But when I change to UTC +01:00 in my phone settings, and do the same timestamp, the time becomes 07:00 and date becomes 01.01.2020.
The code I have so far for "parsing" the time looks like this:
String formattedTime = "";
String datetime2 = "1970-01-01T" + returntime;
Log.v("DATE", datetime2);
OffsetDateTime odt2 = OffsetDateTime.parse(datetime2);
Date date2 = Date.from(odt2.toInstant());
SimpleDateFormat sdf2 = new SimpleDateFormat("HH:mm",Locale.getDefault());
formattedTime = sdf2.format(date2);
Log.v("FORMTIME", formattedTime);
I'm using a similar code snippet for "parsing" the date as well.
The output for the two logs (when in UTC +01:00):
V/DATE: 1970-01-01T15:00:00+02:00
V/FORMTIME: 14:00 //SHOULD BE 15:00
V/DATE: 1970-01-01T08:00:00+02:00
V/FORMTIME: 07:00 //SHOULD BE 08:00
V/DATE: 1970-01-01T08:00:00+02:00
V/FORMTIME: 07:00 //SHOULD BE 08:00
It seems like the change in UTC from +02:00 to +01:00 reduce the time and date also with 1...
So is it wrong to use the OffsetDateTime class and "toInstant" (Instant class) for what I'm trying to achieve? What would be the right solution?
OffsetTime
I don’t understand what that offset of +02:00 in your string signifies. In particular it confuses me what you want to do when the offset changes. In any case java.time, the modern Java date and time API, parses and formats your time pretty easily. Let’s first define the formatter that describes your desired output format:
private static final DateTimeFormatter timeFormatter
= DateTimeFormatter.ofPattern("HH:mm");
With this in place you may do:
String returntime = "15:00:00+02:00";
OffsetTime time = OffsetTime.parse(returntime);
String formattedTime = time.format(timeFormatter);
System.out.println(formattedTime);
Output:
15:00
The offset is parsed, but is not used for anything. The output time will always be the same as the time in the string.
I take it that the date 1970-01-01 that you used in your code is arbitrary and without significance. The OffsetTime that I am using hasn’t got a date, so saves us from choosing a date for processing the time.
Word use: There isn’t any localization going on here. Localization is when for an American audience you print 3:00 PM instead of 15:00, for example.
EDIT:
If your string contains a date too, OffsetDateTime is the right class to use, and again we need no explicit formatter for parsing (only for formatting). Your code in the comment is fine (except that you had accidentally reversed the order of day, month and year in the string).
String returnDate1 = "2020-12-05T00:00+02:00";
DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("dd-MM-yyyy");
OffsetDateTime dateTime = OffsetDateTime.parse(returnDate1);
String formattedDate = dateTime.format(dateFormatter);
System.out.println(formattedDate);
05-12-2020
What went wrong in your code?
It seems you were over-complicating things. In particular you were mixing old and modern date-time classes. The old ones, Date and SimpleDateFormat, are poorly and confusingly designed, which no doubt contributed to your unexpected results. And when mixing, you are going to need conversions that are not really needed for your job, again just making your code more complicated than needed.
Your sdf2 was using your default time zone for printing the time. You had got offset +02:00 in the string, so when you set the phone to UTC+01:00, a conversion takes place. When the time is 08:00 at offset +02:00, it is only 07:00 at offset +01:00. So this was the result you got. This in turn means that if the user’s time zone was at offset +01:00 on 1970-01-01, then you were getting the correct times for that time zone.
I am using the below code for epoch to time conversion by using java.util.Date class in Java.
Long scheduledTime = 1602258300000L;
Date date = new Date(scheduledTime);
System.out.println("Date obj :" + date);
Below are the outputs while running the same code on two different timezone server :
On EDT server-
Date obj :Fri Oct 09 11:45:00 EDT 2020
On IST server -
Date obj :Fri Oct 09 21:15:00 IST 2020
Why does this happen? I am only passing milliseconds. This data is supposed to be treated as 21:15 on all servers. Why does Date class change the data?
Please share a sample piece of code for getting the same time data regardless of the timezone of the server.
A Date object represents a specific instant in time, represented by a given number of milliseconds since the Unix epoch.
The toString() method converts that instant in time into a local time based on the default time zone. It's not that the Date value itself "has" a time zone - it's just toString() that uses the default one.
This data is supposed to be treated as 21:15 on all servers.
That suggests you want to use the Indian time zone in all servers, at least when converting the instant in time for display. Without knowing anything more about your application, that's all we can say... other than "don't use java.util.Date or java.util.Calendar; use the java.time classes instead". They're much better designed, and you're less likely to run into problems like this.
java.time
I recommend you use java.time, the modern Java date and time API, for your date and time work.
long scheduledTime = 1_602_258_300_000L;
Instant pointInTime = Instant.ofEpochMilli(scheduledTime);
System.out.println(pointInTime);
Output from this snippet will be the same on all servers in all time zones:
2020-10-09T15:45:00Z
Since you want 21:15, specify the time zone for India:
ZoneId serverTimeZone = ZoneId.of("Asia/Kolkata");
ZonedDateTime dateTime = pointInTime.atZone(serverTimeZone);
System.out.println(dateTime);
2020-10-09T21:15+05:30[Asia/Kolkata]
What went wrong?
The epoch is one point in time independent of time zone. so a count of milliseconds also denotes one point in time. In your case that point in time is Friday 9. October 2020 15:45:00 UTC. And at that point in time it was 21:15 in India and 11:45 on the East coast of North America. It’s a confusing trait of the outdated Date class that on one hand it represents just a point in time, on the other hand its toString method grabs the time zone setting of the JVM and uses it for rendering the string to be returned, thus giving you the false impression that you get different Date objects in different time zones when in fact they are equal.
Links
Oracle tutorial: Date Time explaining how to use java.time.
Epoch & Unix Timestamp Conversion Tools where you can check what’s the equivalent of your milliseconds in UTC/GMT and in your own time zone.
As pointed by others you should now use the java.time package for working with time. If you look at the documentation of the toString() method of java.util.Date, it says that it coverts the Date object to a String of form:
EEE MMM d m dd hh:mm:ss zzz yyyy
It is like the following code is running in the background:
public String toString(){
Date date=this;
SimpleDateFormat simpleDateFormat=new SimpleDateFormat(
"EEE MMM d m dd hh:mm:ss zzz yyyy");
simpleDateFormat.setTimeZone(TimeZone.getDefault()); //This line is important.
return simpleDateFormat.format(date);
}
Now, if you wanna format your Date object for a certain timezone you can do the same including setting the timezone:
Long scheduledTime = 1602258300000L;
Date date = new Date(scheduledTime);
SimpleDateFormat simpleDateFormat=new SimpleDateFormat(
"EEE MMM d m dd hh:mm:ss zzz yyyy");
simpleDateFormat.setTimeZone(TimeZone.getTimeZone("IST"));
String dateStr = simpleDateFormat.format(date);
System.out.println("Date obj :" + dateStr);
I am confused with Timezone conversions in Java. I have a few cases which I will list out below.
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
// sdf.setTimeZone(TimeZone.getTimeZone("Asia/kolkata"));
Date date1 = sdf.parse("2021-01-31");
System.out.println(date1); //. O/P - Sun Jan 31 00:00:00 IST 2021
Now lets uncomment the Timezone part and see the time difference
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
sdf.setTimeZone(TimeZone.getTimeZone("Asia/kolkata"));
Date date1 = sdf.parse("2021-01-31");
System.out.println(date1); // O/P - Sun Jan 31 05:30:00 IST 2021
Now lets set the TimeZone to IST and see the time difference
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
sdf.setTimeZone(TimeZone.getTimeZone("IST"));
Date date1 = sdf.parse("2021-01-31");
System.out.println(date1); // O/P - Sun Jan 31 00:00:00 IST 2021
Now lets set the TimeZone to UTC and see the time difference
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
Date date1 = sdf.parse("2021-01-31");
System.out.println(date1); // O/P - Sun Jan 31 05:30:00 IST 2021
Can anybody please explain me why this shift in time is happening (+- 5:30) when I change the Timezone?
For IST and Asia/Kolkata, time should have remain same because they are same Timezone, but why the shift?
Why When using the UTC Timezone, time gets increased by 5:30 hours? What I understand is IST is 5:30 hrs ahead of UTC, so cnverting to UTC should have decreased the time by 5:30 hrs
Why even after converting to UTC, my time displays IST 2021?
I still have confusion here.
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
Date date1 = sdf.parse("2021-01-31");
System.out.println(date1.getTime()); // 1612051200000
SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy-MM-dd");
sdf1.setTimeZone(TimeZone.getTimeZone("Asia/Kolkata"));
Date date2 = sdf1.parse("2021-01-31");
System.out.println(date2.getTime()); // 1612031400000
Why instant of time in UTC is greater than instant of time in Asia/Kolkata ?
Here are some things for you to note:
When a Date is printed, it will be formatted in your computer's local timezone (that's what Date.toString does). Presumably, your computer is in the Asia/Kolkata timezone, so the output is always displayed as a date & time in that timezone.
A Date represents a point in time (i.e. an instant). It is not a tuple of year, month, day, hour, minute, seconds and timezone
Since there are no time in your input string, the time 00:00:00 is used for the time when parsing.
Just a date and a time is not enough to produce a point in time. You also need a timezone to specify a point in time. Since there is no timezone in your input string, the local timezone of your computer is used, or if you have set it, sdf.getTimeZone().
Although a timezone is used in parsing the date, the timezone is not part of the Date object.
Can anybody please explain me why this shift in time is happening (+- 5:30) when I change the Timezone?
When you use the "IST" timezone (first and third code snippet), sdf gets the following pieces of information:
Date: 2021-01-31
Time: 00:00:00
TimeZone: Asia/Kolkata
With these pieces of information, it can produce a point in time, represented by a number of milliseconds since the Java Epoch - 1970-01-01 00:00:00 UTC. This is the Date object. Then you print the Date object, which gets formatted to your local timezone. Your local timezone just so happens to be the same as the one that sdf is provided with, so you see Sun Jan 31 00:00:00 IST 2021.
When you use UTC (second and fourth code snippets), these information are provided to sdf:
Date: 2021-01-31
Time: 00:00:00
TimeZone: UTC
That represents a different point in time than 2021-01-31T00:00:00 in Kolkata. How different? 2021-01-31T00:00:00 in UTC is exactly 5 and a half hours later than 2021-01-31T00:00:00 in Kolkata. Recall that to convert a UTC time to Kolkata, you add 5 and a half hours.
For IST and Asia/Kolkata, time should have remain same because they are same Timezone, but why the shift?
Because you have misspelled Asia/Kolkata. The first "K" in "Kolkata" should be capitalised. Unknown zone IDs are treated as UTC by the TimeZone class. This is why you should move to the new java.time classes. ZoneId throws an exception if you supply it with an unknown zone ID.
Why When using the UTC Timezone, time gets increased by 5:30 hours? What I understand is IST is 5:30 hrs ahead of UTC, so converting to UTC should have decreased the time by 5:30 hrs
You are thinking about formatting dates, not parsing, because remember that the timezone is not part of Date, but part of SimpleDateFormat. Your code does not format Date, only parses them. Without formatting, Dates are always printed in your local timezone.
To see your desired behaviour using SimpleDateFormat, you'd first parse the date string once, and then format it using SimpleDateFormats with different timezones.
Really though, you should change to java.time. Using that API, your zone changing code could be written like so:
ZonedDateTime zdt = LocalDate.parse("2021-01-31")
.atStartOfDay()
.atZone(ZoneId.of("Asia/Kolkata"));
System.out.println(zdt);
ZonedDateTime utcDateTime = zdt.withZoneSameInstant(ZoneId.of("UTC"));
System.out.println(utcDateTime);
// output:
// 2021-01-31T00:00+05:30[Asia/Kolkata]
// 2021-01-30T18:30Z[UTC]
java time
I recommend you use java.time, the modern Java date and time API, for your date work
LocalDate date = LocalDate.parse("2021-01-31");
System.out.println(date);
Output is:
2021-01-31
A LocalDate is a date without time of day and without time zone or UTC offset, so using it frees you completely from all time zone trouble. Furthermore we don’t need any explicit formatter. Your string is in ISO 8601 format, and LocalDate parses the most common ISO 8601 variant as its default. As you can see, it also prints the same ISO 8601 format back when we print it, implicitly calling its toString method.
What went wrong in your code?
The SimpleDateFormat, TimeZone and Date classes that you are using are poorly designed and long outdated. No wonder that their behaviour confuses you.
I am assuming that Asia/Kolkata (or Asia/Colombo or Asia/Calcutta) is the default time zone of your JVM. In your first example the SimpleDateFormat is using your default time zone and is parsing the string into the first moment of the day in that time zone.
In your second example, as Elavya has spotted so well, you have got a lower case k in Asia/kolkata which causes TimeZone not to recognize the intended time zone. And this is where TimeZone excels in bad design: it just tacitly gives you GMT instead. Next the Date class is poorly designed too and still prints the time in the default time zone of the JVM, giving the illusion that the Date object contains a time zone. This has confused very many. The start of the day in GMT is the same point in time as 05:30:00 IST, so this is what you get.
In your third and fourth example, even though the three letter time zone abbreviations are deprecated, IST (contrary to what Eklavya said) is interpreted as Asia/Kolkata and UTC as Etc/UTC. Even though as Eklavya also said, IST is ambiguous.
So in short:
The change happens because the start of the day is a different point in time in different time zones.
Because of your typo in Asia/kolkata. Time zone IDs are case sensitive.
You are not converting to UTC. You are parsing in UTC thereby converting from UTC, and Date.toString() further converts to Asia/Kolkata (IST) as the output also says.
Because the Date object hasn’t got a time zone and because Date.toString() grabs the default time zone of your JVM and uses it for rendering the string to be returned.
Links
Oracle tutorial: Date Time explaining how to use java.time.
Wikipedia article: ISO 8601
All about java.util.Date
Java doc for getTimeZone
ID - the ID for a TimeZone, either an abbreviation such as "PST", a
full name such as "America/Los_Angeles", or a custom ID such as
"GMT-8:00". Note that the support of abbreviations is for JDK 1.1.x
compatibility only and full names should be used.
TimeZone abbreviation is not supported. So you can't use IST
And in TimeZone Doc for Three-letter time zone IDs
For compatibility with JDK 1.1.x, some other three-letter time zone
IDs (such as "PST", "CTT", "AST") are also supported. However, their
use is deprecated because the same abbreviation is often used for
multiple time zones (for example, "CST" could be U.S. "Central
Standard Time" and "China Standard Time"), and the Java platform can
then only recognize one of them.
Problem is IST abbreviation is used for multiple time zones like Irish Standard Time, Isreal Standrad Time, Indian Standard Time. And you mistyped Asia/Kolkata as Asia/kolkata.
So, the GMT zone will return if the given ID cannot be understood from TimeZone.getTimeZone()
As an addition to the accepted answer, for the last part of your question;
Why instant of time in UTC is greater than instant of time in Asia/Kolkata in below code?
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
Date date1 = sdf.parse("2021-01-31");
System.out.println(date1.getTime()); // 1612051200000
SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy-MM-dd");
sdf1.setTimeZone(TimeZone.getTimeZone("Asia/Kolkata"));
Date date2 = sdf1.parse("2021-01-31");
System.out.println(date2.getTime()); // 1612031400000
First, you have a point T in time regardless of timezone. In our example T=2021-01-31 00:00:00.
When we set timezone as UTC and print the time using java.util.Date.getTime() method, it will print milliseconds since the Unix epoch, which occurred at midnight January 1st 1970, UTC. So it will print 1612051200000. As you see the epoch and our date has the same timezone which is UTC. So the time is printed directly, no adjustment necessary for timezone.
Now, when we set timezone as Asia/Kolkata, during SimpleDateFormat.parse, timezone information will be added to date. That means +5:30h(19800000ms) will be added to time T. Therefore our time T is increased by 19800000ms. However T must be pointing to the same point in time. How do we fix that? It is fixed on SimpleDateFormat.parse method by subtracting 19800000ms from the time 1612051200000ms so that getTime() method will now show 1612031400000ms so that our actual time T will still show the same point in time(which is 1612051200000ms) because in this date object we have an extra 19800000ms which comes from timezone.
I have a date string in CST (24 Hours) and I want to convert it to GMT (12 Hours).
I have a java method like below which works fine when my system time is Kolkata time. (System where I run the java method)
But when my system is in Shanghai time, the GMT time comes incorrect.
String inputDate = "01-19-2017 06:01 CST";
SimpleDateFormat inputFormatter = new SimpleDateFormat("MM-dd-yyyy hh:mm Z");
parsedInput = inputFormatter.parse(inputDate);
// parsedInput -> Tue Jan 19 06:01:00 CST 2017 -> When system time is Shanghai time
// parsedInput -> Thu Jan 19 17:31:00 IST 2017 -> When system time is Kolkata time
SimpleDateFormat formatter = new SimpleDateFormat("MM-dd-yyyy hh:mm a Z");
TimeZone gmt = TimeZone.getTimeZone("GMT");
formatter.setTimeZone(gmt);
String formattedDate = formatter.format(parsedInput);
// formattedDate -> 01-18-2017 10:01 PM +0000 -> When system time is Shanghai time (Incorrect)
// formattedDate -> 01-19-2017 12:01 PM +0000 -> When system time is Kolkata time
Avoid pseudo time zones
The 3-4 letter time zone abbreviations such as CST are not actual time zones. They are not standardized. They are not even unique!
Your CST might be “China Standard Time” or might be “Central Standard Time” in the Americas, or might be something else. There is no way to know which one.
Another example: IST might mean “India Standard Time” or “Ireland Standard Time” or others.
Deciphering these pseudo-zones reliably is impossible. The Joda-Time library has a wise policy of refusing to even try. Unfortunately the java.time classes make a guess when parsing, but the result may not be the zone you expect.
So never use these useless pseudo-zones. Use a proper time zone name in the format of continent/region such as America/Chicago, Asia/Kolkata, Pacific/Auckland.
Avoid legacy date-time classes
You are using the troublesome old date-time classes that are now legacy, supplanted by the java.time classes.
Workaround
If you know all your inputs are intended for the same time zone, then lop off the pseudo-zone and process as a LocalDateTime, apply the intended zone as a ZoneId to produce a ZonedDateDate. From that you can extract an Instant for UTC time.
String input = "01-19-2017 06:01".replace( " " , "T" ) ; // Insert a “T” to comply with standard ISO 8601 format used by default in java.time.
LocalDateTime ldt = LocalDateTime.parse( input ); // Lacks any concept of time zone or offset-from-UTC. So *not* an actual moment on the timeline.
ZoneId z = ZoneId.of( "America/Chicago" ); // Assuming “CST” meant this zone in North America.
ZonedDateTime zdt = ldt.atZone( z ); // Assign a time zone to determine an actual moment on the timeline.
Instant instant = zdt.toInstant(); // Extract a value in UTC.
You may choose to see that same moment through the lens of the wall-clock time in India.
ZonedDateTime zdtKolkata = zdt.withZoneSameInstant( ZoneId.of( "Asia/Kolkata" ) ) ;
Never rely on default zone
Your JVM’s current default time zone can be changed at any moment by any code in any thread of any app executing within that JVM. Since this can change at any moment you cannot rely on a certain value.
When you omit the optional argument for time zone, the date-time classes implicitly silently apply the current default time zone. So the solution is simple: Always specify the expected/desired time zone.
Notice how the code example above specifies the zone at every opportunity.
(Ditto for Locale, by the way. Specify explicitly rather than rely on current default.)
Your problem is the ambiguity of CST. In most cases SimpleDateFormat understands CST as Central Standard Time as you had expected. But when your computer is running Shanghai Time, this becomes the time zone of the formatter’s calendar, and then it suddenly understands CST as China Standard Time, the same as Shanghai time.
Therefore Darshan Mehta’s solution, setting inputFormatter’s time zone to something other than China Standard Time, works on my computer. I don’t know why it didn’t work on yours (I set it to TimeZone.getTimeZone("America/Chicago") to match the intended interpretation of CST).
The correct and good solution, though, is to avoid the three and four letter time zone abbreviations completely. Yours is just one example out of many where they cause trouble. If you can, give your input string with an explicit zone offset from UTC, such is never ambiguous:
String inputDate = "01-19-2017 06:01 -0600";
Now your code works as expected no matter your computer’s time zone setting. The Z in the pattern string you already have matches -0600 nicely.
All of this said, if you can use the Java 8 date and time classes, you can do yourself a favour of switching over to them. I hesitate to call them “Java 8 date and time classes” because they have also been backported to Java 6 and 7, so if you can live with a library dependency until you get to Java 8, you should have every chance. The newer classes are significantly more programmer-friendly and convenient to work with. A simple example to get you started:
String inputDate = "01-19-2017 06:01 -0600";
DateTimeFormatter inputFormatter = DateTimeFormatter.ofPattern("MM-dd-yyyy HH:mm Z");
ZonedDateTime parsedInput = ZonedDateTime.parse(inputDate, inputFormatter);
OffsetDateTime gmtTime = parsedInput.toOffsetDateTime().withOffsetSameInstant(ZoneOffset.UTC);
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MM-dd-yyyy hh:mm a Z");
String formattedDate = gmtTime.format(formatter);
System.out.println(formattedDate); // prints 01-19-2017 12:01 PM +0000
Try setting the TimeZone to inputFormatter as well, e.g.:
SimpleDateFormat inputFormatter = new SimpleDateFormat("MM-dd-yyyy hh:mm Z");
inputFormatter.setTimeZone(TimeZone.getTimeZone("GMT"));
If you don't specify the time zone, it takes the default value into account which is Asia/Shanghai in your case and hence, incorrect result.
You can reproduce it in your current system by replacing GMT with Asia/Shanghai in the above snippett
I am trying to convert Date with GMT +5:30 to EST with java 8 ZonedDateTime.
String inputDate = "2015/04/30 13:00";
DateTimeFormatter sourceFormatter = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm", Locale.US);
LocalDateTime local = LocalDateTime.parse(inputDate, sourceFormatter);
// local : 2015-04-30T13:00
//Combining this local date-time with a time-zone to create a ZonedDateTime.
ZonedDateTime zoned = local.atZone(TimeZone.getTimeZone("GMT+5:30").toZoneId());
// zoned : 2015-04-30T13:00+05:30[GMT+05:30]
ZonedDateTime zonedUS = zoned.withZoneSameInstant(TimeZone.getTimeZone("GMT-5:00").toZoneId());
// zonedUS : 2015-04-30T02:30-05:00[GMT-05:00]
I am expecting 3:30 AM EST but what I am getting is 2:30 AM EST as 1 PM IST= 3:30AM EST. What am I missing?
It seems that whatever service you found was being over-helpful in interpreting what you meant and assumed North American Eastern Daylight Time (EDT) when you specified EST (Eastern Standard Time). Most, not all of the places using EST as standard time are using daylight saving time and hence were on EDT or offset UTC-04:00 on the date you use, April 30, 2015.
If it makes sense in your situation, you should always prefer to give time zone in the region/city format, as Asia/Kolkata and America/New_York. If you intended Eastern Time as in New York or Montréal, one may say that your “time zone” of GMT-5:00 was wrong and the cause of your unexpected result.
So your code becomes for example:
String inputDate = "2015/04/30 13:00";
DateTimeFormatter sourceFormatter = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm", Locale.US);
LocalDateTime local = LocalDateTime.parse(inputDate, sourceFormatter);
// local : 2015-04-30T13:00
//Combining this local date-time with a time-zone to create a ZonedDateTime.
ZonedDateTime zoned = local.atZone(ZoneId.of("Asia/Kolkata"));
// zoned : 2015-04-30T13:00+05:30[Asia/Kolkata]
ZonedDateTime zonedUS = zoned.withZoneSameInstant(ZoneId.of("America/Montreal"));
// zonedUS : 2015-04-30T03:30-04:00[America/Montreal]
I have made one other change: When using the modern classes from java.time, there is no point in also using the outdated TimeZone class, so I have taken that out. The code is slightly simpler, and more importantly, ZoneId.of(String) includes validation of your time zone string so you will discover any spelling error in the time zone name (like when I just happened to type a ( instead of the / in Asia/Kolkata — such happens all the time).
Most of the above has already been said in comments by Jon Skeet and others. I thought it deserved to go into an answer so it’s plain to see that the question has been answered.
Though the question is old, felt like I could add more to the accepted answer.
A ZonedDateTime is different from an OffsetDateTime.
I would prefer to use ZonedDateTime when I'm getting a time for a specific location like "Asia/Kolkata", "Asia/Shanghai", "US/Pacific" (this time zone will change depending on the day of the year because of Daylight savings).
To illustrate with an example,
var pacific = ZonedDateTime.of(2020,11,01,1,59,0,0,ZoneId.of("US/Pacific"))
var afterAnHour = pacific.plusHours(1)
This will give me a time of
2020-November-01 01:59:00.000 AM -07:00[US/Pacific]
And if i add an hour to it, it will give me a time of
2020-November-01 01:59:00.000 AM -08:00[US/Pacific]
You can see that the hour component is same even after adding an hour to the time. This is because the daylight savings time has kicked in and the time zone is shifted from -07:00 to -08:00.
Now if i use an OffsetDateTime look what happens.
var offsetNow = OffsetDateTime.of(2020,11,01,1,59,0,0,ZoneOffset.of("-07:00"))
var offsetAfterAnHour = offsetNow.plusHours(1)
The offsetNow will be,
2020-November-01 01:59:00.000 -07:00
And adding an hour to it will be,
2020-November-01 02:59:00.000 -07:00
you can see that the hour component has become 2 after adding an hour.
The key point is a ZonedDateTime uses ZoneRules to calculate important properties like Daylight savings time so that it can adjust the time zone accordingly.
While the OffsetDateTime will not change the zone offset for anything.