Before Java-8 I got accustomed to always keep anything date/time related as milliseconds since Epoch and only ever deal with human readable dates/times on the way out, i.e. in a UI or a log file, or when parsing user generated input.
I think this is still safe with Java-8, and now I am looking for the most concise way to get a formatted date out of a milliseconds time stamp. I tried
df = Dateformatter.ofPattern("...pattern...");
df.format(Instant.ofEpochMilli(timestamp))
but it bombs out with Unsupported field: YearOfEra in Instant.getLong(...) which I half understand. Now what to use instead of Instant?
LocalDateTime.ofEpoch(Instant, ZoneId) seems wrong, since I don't care to have local time. I just want to see the local time zone when applying the formatter. Internally it should be just the Instant.
The same goes for ZonedDateTime.ofInstant(Instant, ZoneId), I thought to apply the ZoneId only when formatting. But I notice that the DateTimeFormatter does not itself deal anymore with time zones, it seems, so I reckon I need to use one of the above.
Which one is preferred and why? Or should I use yet another way to format an epoch-millis time stamp as a date/time with time zone?
An Instant does not contain any information about the time-zone, and unlike in other places, the default time-zone is not automatically used. As such, the formatter cannot figure out what the year is, hence the error message.
Thus, to format the instant, you must add the time-zone. This can be directly added to the formatter using withZone(ZoneId) - there is no need to manually convert to ZonedDateTime *:
ZoneId zone = ZoneId.systemDefault();
DateTimeFormatter df = DateTimeFormatter.ofPattern("...pattern...").withZone(zone);
df.format(Instant.ofEpochMilli(timestamp))
* regrettably, in early Java 8 versions, the DateTimeformatter.withZone(ZoneId) method did not work, however this has now been fixed, so if the code above doesn't work, upgrade to the latest Java 8 patch release.
Edit: Just to add that Instant is the right class to use when you want to store an instant in time without any other context.
The error you have when formatting an Instant using a formatter built with a year or other fields is expected; an Instant does not know which year or month or day it is, it only knows how much milliseconds have elapsed since the Epoch. For the same instant, it could be 2 different days on 2 different places of the Earth.
So you need to add a time zone information if you want to print the day. With an Instant, you can call atZone(zone) to combine it with a ZoneId in order to form a ZonedDateTime. This is very much like an instant, only that it has a time zone information. If you want to use the system time zone (the one of the running VM), you can get it with ZoneId.systemDefault().
To print it, you can use the two built-in formatter ISO_OFFSET_DATE_TIME or ISO_ZONED_DATE_TIME. The difference between the two is that the zoned date time formatter will add the zone id to the output.
Instant instant = Instant.now();
DateTimeFormatter formatter = DateTimeFormatter.ISO_OFFSET_DATE_TIME;
System.out.println(formatter.format(instant.atZone(ZoneId.systemDefault())));
System.out.println(formatter.format(instant.atZone(ZoneId.of("America/Los_Angeles"))));
when run on my machine, which has a system time zone of "Europe/Paris", you'll get:
2016-07-31T18:58:54.108+02:00
2016-07-31T09:58:54.108-07:00
You can of course build your own formatter if those one do not suit you, using ofPattern or the builder DateTimeFormatterBuilder.
I agree that this is somewhat confusing, especially when compared with it's predecessor Joda DateTime.
The most confusing thing is that the documentation for LocalDateTime says that it is "A date-time without a time-zone", and yet LocalDateTime.ofInstant method takes both an instant and a timezone as parameters.
That said, I think that you can achieve what you want by using Instant and LocalDateTime.ofInstant by using the UTC timezone.
public LocalDateTime millisToDateTime(long millis) {
return LocalDateTime.ofInstant(Instant.ofEpochMilli(millis), ZoneId.of("Z");
}
Related
I'm having a hard time understanding java.time between ZoneDateTime - Instant - LocalDateTime
, so far, the only thing I know of is:
Instant works in-between the two
Instant (in my understanding), is a Stamp of time from the moment of time (UTC), a stamp of time that is relevant to the flow of human time, but without a time zone
Zone Date time has TimeZone
Instant does not have Time Zone but can deal with it given that a Zone information is supplied
LocalDate time does not have time zone and cannot deal with zones, it's a Date Time without any relevance on the continuation of entire flow of time (global).
So I have this conversion below
val seoul = "Asia/Seoul"
val zoneId = ZoneId.of(seoul)
val now = ZonedDateTime.now()
val convertedZoneDateTIme = ZonedDateTime.of(now.toLocalDateTime(), zoneId).withZoneSameInstant(ZoneOffset.UTC)
val convertedInstant = now.toInstant().atZone(zoneId)
// expected output
println(convertedInstant.format(DateTimeFormatter.ofPattern(format)))
// not expected output
println(converted.format(DateTimeFormatter.ofPattern(format)))
Output
2021-05-02 03:15:13
2021-05-02 09:15:13
I'm trying to convert a given time to another Time Zone, a use-case where a user moved to a different timezone and I need to update any information about a stored date.
Why am I getting an incorrect value on the second one..? Why do I have to convert it to Instant first and proceed with conversion?
Thank you in advance
Most of your bullets are fully correct. Only you should not use Instant for working between LocalDateTime and ZonedDateTime as you said in your first bullet. Converting between Instant and LocalDateTime requires a time zone (or at least an offset from UTC), so should go through a ZonedDateTime. So ZonedDateTime is the one to use between the two others. As I said, the rest is correct.
You are not being perfectly clear about what you had expected from your code nor how more specifically observed result differs. Assuming you wanted to use the same point in time throughout, this line is where your surprise arises:
val convertedZoneDateTIme = ZonedDateTime.of(now.toLocalDateTime(), zoneId).withZoneSameInstant(ZoneOffset.UTC)
now is a ZonedDateTime in your own time zone (the default time zone of your JVM to be precise). By taking only the date and time of day from it and combining them with a different time zone you are keeping the time of day but in that way (probably) changing the point in the flow of time. Next you are converting to UTC keeping the point in time (the instant), thereby (probably) changing the time of day and possibly the date. You have got nothing left from the ZonedDateTime that was your starting point, and I can’t see that the operation makes sense. To convert now to UTC keeping the point on the timeline use the simpler:
val convertedZoneDateTIme = now.withZoneSameInstant(ZoneOffset.UTC)
With this change your two outputs agree about the point in time. Example output:
2021-05-07 02:30:16 +09:00 Korean Standard Time
2021-05-06 17:30:16 +00:00 Z
I used a format of uuuu-MM-dd HH:mm:ss xxx zzzz.
Also for you other conversion I would prefer to use withZoneSameInstant(). Then we don’t need to go through an Instant.
val convertedInstant = now.withZoneSameInstant(zoneId)
It gives the same result as your code.
A short overview of what is in each of the classes discussed:
Class
Date and time of day
Point in time
Time zone
ZonedDateTime
Yes
Yes
Yes
Instant
-
Yes
-
LocalDateTime
Yes
-
-
Basically you don’t have any use for LocalDateTime for your purpose, and also Instant, while useable, isn’t necessary. ZonedDateTime alone fulfils your needs.
I have seen a lot of debates on the following date conversion:
timeStamp.toLocalDateTime().toLocalDate();
Some people say that it is not appropriate because the timezone has to be specified for proper conversion, otherwise the result may be unexpected. My requirement is that I have an object that contains Timestamp fields and another object that contains LocalDate fields. I have to take the date difference between both so I think that the best common type to use is LocalDate. I don't see why the timezone has to be specified as either timestamp or LocalDate just represent dates. The timezone is already implied. Can someone give an example when this conversion fails?.
It’s more complicated than that. While it’s true that a Timestamp is a point in time, it also tends to have a dual nature where it sometimes pretends to be a date and time of day instead.
BTW, you probably already know, the Timestamp class is poorly designed and long outdated. Best if you can avoid it completely. If you are getting a Timestamp from a legacy API, you are doing the right thing: immediately converting it to a type from java.time, the modern Java date and time API.
Timestamp is a point in time
To convert a point in time (however represented) to a date you need to decide on a time zone. It is never the same date in all time zones. So the choice of time zone will always make a difference. So one correct conversion would be:
ZoneId zone = ZoneId.of("Africa/Cairo");
LocalDate date = timestamp.toInstant().atZone(zone).toLocalDate();
The Timestamp class was designed for use with your SQL database. If your datatype in SQL is timestamp with time zone, then it unambiguously denotes a point in time, and you need to see it as a point in time as just described. Even when to most database engines timestamp with time zone really just means “timestamp in UTC”, it’s still a point in time.
And then again: sometimes to be thought of as date and time of day
From the documentation of Timestamp:
A Timestamp also provides formatting and parsing operations to support
the JDBC escape syntax for timestamp values.
The JDBC escape syntax is defined as
yyyy-mm-dd hh:mm:ss.fffffffff, where fffffffff indicates
nanoseconds.
This doesn’t define any point in time. It’s a mere date and time of day. What the documentation doesn’t even tell you is that the date and time of day is understood in the default time zone of the JVM.
I suppose that the reason for seeing a Timestamp in this way comes from the SQL Timestamp datatype. In most database engines this is a date and time without time zone. It’s not a timestamp, despite the name! It doesn’t define a point in time, which is the purpose of and is in the definition of timestamp.
I have seen a number of cases where the Timestamp prints the same date and time as in the database, but doesn’t represent the point in time implied in the database. For example, there may be a decision that “timestamps” in the database are in UTC, while the JVM uses the time zone of the place where it’s running. It’s a bad practice, but it is not one that will go away within a few years.
This must also have been the reason why Timestamp was fitted with the toLocalDateTime method that you used in the question. It gives you that date and time that were in the database, right? So in this case your conversion in the question ought to be correct, or…?
Where this can fail miserably without us having a chance to notice is, as others have mentioned already, when the default time zone of the JVM is changed. The JVM’s default time zone can be changed at any time from any place in your program or any other program running in the same JVM. When this happens, your Timestamp objects don’t change their point in time, but they do tacitly change their time of day, sometimes also their date. I’ve read horror stories — in Stack Overflow questions and elsewhere — about the wrong results and the confusion coming out of this.
Solution: don’t use Timestamp
Since JDBC 4.2 you can retrieve java.time types out of your SQL database. If your SQL datatype is timestamp with time zone (recommended for timestamps), fetch an OffsetDateTime. Some JDBC drivers also let you fetch an Instant, that’s fine too. In both cases no time zone change will play any trick on you. If the SQL type is timestamp without time zone (discouraged and all too common), fetch a LocalDateTime. Again you can be sure that your object doesn’t change its date and time no matter if the JVM time zone setting changes. Only your LocalDateTime never defined a point in time. Conversion to LocalDate is trivial, as you have already demonstrated in the question.
Links
java.sql.Timestamp documentation
Wikipedia article: Timestamp
Question: Getting the date from a ResultSet for use with java.time classes
Question: Java - Convert java.time.Instant to java.sql.Timestamp without Zone offset
As you can see here(taken from https://stackoverflow.com/a/32443004/1398418):
Timestamp represents a moment in UTC and is the equivalent of the modern Instant.
When you do:
timeStamp.toLocalDateTime().toLocalDate();
the timeStamp is converted from UTC to the system timezone. It's the same as doing:
timeStamp.toInstant().atZone(ZoneId.systemDefault()).toLocalDate()
For example:
Timestamp stamp = new Timestamp(TimeUnit.HOURS.toMillis(-1)); // UTC 1969-12-31
System.setProperty("user.timezone", "EET"); // Set system time zone to Eastern European EET - UTC+2
stamp.toLocalDateTime().toLocalDate(); // represents EET 1970-01-01
stamp.toInstant().atZone(ZoneId.systemDefault()).toLocalDate(); // represents EET 1970-01-01
That result (getting the date in the system time zone) is expected and if that's what you want, doing timeStamp.toLocalDateTime().toLocalDate() is appropriate and correct.
You're saying that you have a LocalDate field in some object and you want to get a period between it and a Timestamp, well that's just not possible without aditional information. LocalDate just represents a date, it has no time zone information, you need to know how it was created and what time zone was used.
If it represent a date in the system time zone then getting the period by using timeStamp.toLocalDateTime().toLocalDate() would be correct, if it represents a date in UTC or any other time zone then you might get a wrong result.
For example if the LocalDate field represents a date in UTC you will need to use:
timeStamp.toInstant().atZone(ZoneId.of("UTC")).toLocalDate();
Example: the 23rd of January becomes the 24th
You asked:
Can someone give an example when this conversion fails?.
Yes, I can.
Start with the 23rd of January.
LocalDate ld = LocalDate.of( 2020 , Month.JANUARY , 23 );
LocalTime lt = LocalTime.of( 23 , 0 );
ZoneId zMontreal = ZoneId.of( "America/Montreal" );
ZonedDateTime zdt = ZonedDateTime.of( ld , lt , zMontreal );
Instant instant = zdt.toInstant();
zdt.toString() = 2020-01-23T23:00-05:00[America/Montreal]
instant.toString() = 2020-01-24T04:00:00Z
The Instant class represents a moment as seen in UTC. Let's convert to the terribly legacy class java.sql.Timestamp using the new conversion method added to that old class.
// Convert from modern class to troubled legacy class `Timestamp`.
java.sql.Timestamp ts = Timestamp.from( instant );
ts.toString() = 2020-01-23 20:00:00.0
Unfortunately, the Timestamp::toString method dynamically applies the JVM’s current default time zone while generating text.
ZoneOffset defaultOffset = ZoneId.systemDefault().getRules().getOffset( ts.toInstant() );
System.out.println( "JVM’s current default time zone: " + ZoneId.systemDefault() + " had an offset then of: " + defaultOffset );
JVM’s current default time zone: America/Los_Angeles had an offset then of: -08:00
So Timestamp::toString misreports the object’s UTC value after adjusting back eight hours from 4 AM to 8 PM. This anti-feature is one of several severe problems with this poorly designed class. For more discussion of the screwy behavior of Timestamp, see the correct Answer by Ole V.V.
Let's run your code. Imagine at runtime the JVM’s current default time zone is Asia/Tokyo.
TimeZone.setDefault( TimeZone.getTimeZone( "Asia/Tokyo" ) );
LocalDate localDate = ts.toLocalDateTime().toLocalDate();
Test for equality. Oops! We ended up with the 24th rather than the 23rd.
boolean sameDate = ld.isEqual( localDate );
System.out.println( "sameDate = " + sameDate + " | ld: " + ld + " localDate: " + localDate );
sameDate = false | ld: 2020-01-23 localDate: 2020-01-24
See this code run live at IdeOne.com.
So what is wrong with your code?
Never use java.sql.Timestamp. It is one of several terrible date-time classes shipped with the earliest versions of Java. Never use these legacy classes. They have been supplanted entirely by the modern java.time classes defined in JSR 310.
You called toLocalDateTime which strips away vital information. Any time zone or offset-from-UTC is removed, leaving only a date and a time-of-day. So this class cannot be used to represent a moment, is not a point on the timeline. Ex: 2020-12-25 at noon — is that noon in Delhi, noon in Düsseldorf, or noon in Detroit, three different moments several hours apart? A LocalDateTime is inherently ambiguous.
You ignored the crucial issue of time zone in determining a date. For any given moment, the date varies around the globe. At one moment it may be “tomorrow” in Australia while simultaneously “yesterday” in Mexico.
The problem lies in what is being represented by these objects. Your question forgets a crucial aspect, which is: What is the type of timeStamp?
I'm guessing it's a java.sql.Timestamp object.
Timestamp, just like java.util.Date, is old API equivalent to Instant.
It represents an instant in time, in the sense that it is milliseconds since jan 1st 1970 UTC. The system has no idea which timezone that was supposed to be in. You're supposed to know; the error, if an error is going to occur here, already occurred before you get to this code. Here's a trivial explanation of how it COULD go wrong:
you start off with a user entering a date in a date field on a webform; it's 2020-04-01.
Your server, running in Amsterdam, saves it to a DB column that is internally represented as UTC, no zone. This is a mistake (you're not saving an instant in time, you're saving a date, these two are not the same thing). What is actually stored in the DB is the exact moment in time that it is midnight, 2020-04-01 in amsterdam (in UTC, that'd be 22:00 the previous day!).
Later, you query this moment in time back into a java.sql.Timestamp object, and you're doing this when the server's tz is elsewhere (say, London time). You then convert this to a localdatetime, and from there to a localdate, and.... you get 2020-03-31 out.
Whoops.
Dates should remain dates. Never convert LocalX (be it Time, Date, or DateTime) to Instant (or anything that effectively is an instant, including j.s.Timestamp, or j.u.Date - yes, j.u.Date does NOT represent a date, it is very badly named), or vice versa, or pain will ensue. If you must because of backward APIs take extreme care; it's hard to test that 'moving the server's timezone around' breaks stuff!
I am aware that SimpleDateFormat.parse rely on the Calendar API which depends on local JVM timezone (computer's). Assume JVM timezone is IST.
SimpleDateFormat srcDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
srcDateFormat.setTimeZone(TimeZone.getTimeZone("EST"));
Date objDt = srcDateFormat.parse("2018-10-16 11:28:25"); //Time : 21:58:25
From the output it seems it converts from EST to IST(JVM local timezone).
SimpleDateFormat srcDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
srcDateFormat.setTimeZone(TimeZone.getTimeZone("IST"));
Date objDt = srcDateFormat.parse("2018-10-16 11:28:25"); //Time : 11:28:25
It keeps time unmodified in this case. In this case I set timezone same as JVM local timezone.
Please help me to understand the behavior of the parse method. Nevertheless, I am curious to know the reason behind such behavior.
I know that java.util.Date and java.text.SimpleDateFormat legacy classes are obsolete now.
References:
Why SimpleDateFormat.format() and SimpleDateFormat.parse() are giving different time though setting only one TimeZone?
How do I convert date/time from one timezone to another?
SimpleDateFormat parses a string to wrong time
https://www.codeproject.com/Tips/1190426/When-Parsing-Formatting-Dates-in-Java-Make-Sure-Yo
First, you are correct that Date and SimpleDateFormat are legacy classes and now obsolete. So I recommend you don’t use them and use java.time, the modern Java date and time API, instead. Among many advantages it is much more explicit about conversions between time zones, which I would expect to help you understand what the code does.
Second, you are doing a number of things incorrectly or at least inadequately:
Don’t store date-times as strings. Always store them as date-time objects in Java. When you do this, you will never need to convert a date-time string from one zone to another. Instant (a class from java.time) is a point in time and as far as I can see the one you should use here.
Don’t rely in three letter time zone abbreviations. Very many of them are ambiguous, including both of IST and EST, and the latter isn’t a true time zone, so what you get at this time of year (when America/New_York zone uses EDT rather than EST), I don’t know.
And repeating myself, use the modern classes, not the obsolete ones.
Out of curiosity what happened?
An old-fashioned Date represents a point in time independently of time zone (internally it stores its value as a count of milliseconds since the epoch, but this is an implementation detail that we need not know or concern ourselves with).
In your first example your string is parsed into a point in time that corresponds to 16:28:25 UTC, 21:58:25 in India, 12:28:25 in New York or 11:28:25 in Jamaica. I mention Jamaica because it’s one of the few places that happens to use Eastern Standard Time (EST) all year. Most of the locations that use EST only do so in winter, not at this time of year. When you look at the Date in your debugger, the debugger calls toString on the Date to get a string to show you. toString in turn uses the JVM’s time zone for generating the string. In your case it’s Asia/Kolkata, which is why you get 21:58:25.
In the second case the same string is parsed into a point in time that corresponds to 05:58:25 UTC, 11:28:25 in India, 01:58:25 in New York or 00:58:25 in Jamaica. Your debugger again calls toString, which again uses your JVM’s time zone and converts back into 11:28:25 IST. When you parse and print in the same time zone, you get the same time of day back.
Links
EST – Eastern Standard Time / Eastern Time (Standard Time)
Oracle tutorial: Date Time explaining how to use java.time.
Time Zone Converter – Time Difference Calculator, online, practical for converting between Asia/Kolkata, UTC, Kingston (Jamaica), New York and other time zones.
How can I convert the date object which is already in UTC to an OffsetDateTime Object in UTC itself in Java? This logic should be written on a microservice where the timezone can be entirely different. So .now() and other things are ruled out, I guess. Also, I don't want to pass Timezone as params anywhere.
Sample code:
public OffsetDateTime convertFrom(Date source) {
LOGGER.info("source: " + source.toString());
LOGGER.info("instant: " + source.toInstant().toString());
LOGGER.info("response: " + source.toInstant().atOffset(ZoneOffset.UTC).toString());
return source.toInstant().atOffset(ZoneOffset.UTC);
}
and the output I get is:
source: 2018-07-11 15:45:13.0
instant: 2018-07-11T19:45:13Z
response: 2018-07-11T19:45:13Z
I want my output return to be 2018-07-11 15:45:13Z for input 2018-07-11 15:45:13.0
tl;dr
A java.util.Date and a Instant both represent a moment in UTC. Other time zones and offsets are irrelevant.
Instant instant = myJavaUtilDate.toInstant()
How can I convert the date object which is already in UTC to an OffsetDateTime Object in UTC itself in Java?
You don’t need OffsetDateTime. Use Instant as shown above.
Use ZonedDateTime, not OffsetDateTime
You do not need 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 the past, present, and future changes to the offset used by the people of a particular region. So a time zone, if known, is always preferable to a mere offset. So use ZonedDateTime rather than OffsetDateTime wherever possible.
Use OffsetDateTime only when given an offset-from-UTC, such as +02:00, without the context of a specific time zone, such as Europe/Paris.
Convert Date to Instant
If given a java.util.Date, concert to the modern class (Instant) that replaced that troublesome old class. Both represent a moment in UTC as a count from the same epoch reference of first moment of 1970 in UTC. The modern class resolves to nanoseconds rather than milliseconds. To convert, call new methods added to the old class.
Instant instant = myJavaUtilDate.toInstant() ;
Remember that both java.util.Date and Instant always represent a moment in UTC.
Capture current moment, “now”
Capture the current moment in UTC.
Instant instant = Instant.now() ;
now() and other things are ruled out, I guess.
No, you can always capture the current moment by calling Instant.now() on any machine at any time. The JVM’s current default time zone is irrelevant as Instant is always in UTC.
Adjust from UTC into another time zone. Same moment, same point on the timeline, different wall-clock time. <— That is the most important concept to comprehend in this discussion!
ZoneId z = ZoneId.of( "Africa/Tunis" ) ;
ZonedDateTime zdt = instant.atZone() ;
As a shortcut, you can skip the Instant when capturing current moment.
ZonedDateTime zdt = ZonedDateTime.now( z ) ;
Move back to UTC by extracting a Instant object.
Instant instant = zdt.toInstant() ;
Tip: Focus on UTC
Usually best to have most of your work in UTC. When storing, logging, debugging, or exchanging moments, use UTC. Forget about your own parochial time zone while on the job as a programmer or sysadmin; learn to think in UTC. Keep a second click in your office set to UTC.
Avoid flipping between time zones all the time. Stick with UTC. Adjust to a time zone only when presenting to the user or when business logic demands.
It is already working as intended, the problem is that Date.toString is "helpfully" converting the internal timestamp to your local timezone. Using Date.toGMTString would result in the exact same timestamp for each of the values.
If the resulting timestamp is wrong then the problem lies in the creation of the Date instance. Using the constructor like new Date(2018, 7, 11, 15, 45, 11) would result in that date being calculated for the system timezone, not UTC. To create it for UTC there is Date.UTC but all these APIs have been deprecated since Java 1.1 because they are so confusing.
public static OffsetDateTime convertFrom(Date source) {
if (source instanceof Timestamp) {
return ((Timestamp) source).toLocalDateTime()
.atOffset(ZoneOffset.UTC);
}
return source.toInstant().atOffset(ZoneOffset.UTC);
}
The object that was passed to your method was a java.sql.Timestamp, not a Date. We can see this fact from the way it was printed: 2018-07-11 15:45:13.0 is the return value from Timestamp.toString(). The Timestamp class is implemented as a subclass of Date, but this doesn’t mean that we can nor should handle it as a Date. The documentation warns us:
Due to the differences between the Timestamp class and the
java.util.Date class mentioned above, it is recommended that code
not view Timestamp values generically as an instance of
java.util.Date. The inheritance relationship between Timestamp and
java.util.Date really denotes implementation inheritance, and not
type inheritance.
In the implementation above I have assumed that you cannot mitigate the possibility of getting a Timestamp argument, so I am handling the possibility the best I can. The code is still fragile, though, because sometimes a Timestamp denotes a point in time (I should say that this is the point), at other times it denotes a date and hour of day. Granted that the Timestamp does not hold a time zone in it, the two are not the same. I understand that your sample Timestamp denotes a date and time of 2018-07-11 15:45:13.0, and you want this interpreted in UTC. My code does that (your code in the question, on the other hand, correctly handles the situation where the Timestamp denotes a point in time). Also, even though no time zone is passed in my code, its behaviour still depends on the time zone setting of your JVM.
When I pass a Timestamp of 2018-07-11 15:45:13.0 to my method above, it returns an OffsetDateTime of 2018-07-11T15:45:13Z.
The double nature of Timestamp is unfortunate and confusing, and the only real solution would be if you could avoid that class completely. The Date class too is poorly designed, and both are outdated and replaced by java.time, the modern Java date and time API. If you cannot avoid the old classes in your code, I certainly understand your desire to convert to the modern OffsetDateTime first thing. If on the other hand I understand correctly that the date and time comes through JSON, you may be able to parse it on your side without any of the old date and time classes, which would be a good solution to your problem. And under all circumstances, if your real goal is to represent the point in time in a time zone neutral way, I agree with Basil Bourque in preferring an Instant over an OffsetDateTime in UTC.
Link: Documentation of java.sql.Timestamp
This question already has answers here:
Unix epoch time to Java Date object
(7 answers)
Convert timestamp in milliseconds to string formatted time in Java
(10 answers)
Closed 5 years ago.
How to change milliseconds to Date object in yyyy-MM-dd HH:mm:ss format like 2017-04-12 23:14:52?
You cannot do that. For a couple of reasons.
TL;DR: Don’t use Date, use Instant. Neither of these can have a format in them. Formatting into a string is dependent on time zone, so you need to choose a time zone.
First, I understand from the discussion that you are asking for a java.util.Date object having the format yyyy-MM-dd HH:mm:ss. A Date object does not have and cannot have a format in it. The thing you should try to understand here is the difference between data itself and presentation of data to a user. An int may hold the value 25389, but it doesn’t hold it in the format 25389 (in fact the internal representation is quite different from 25389). The same int may be presented to a user as 25389, 000025389, 25,389 or +25389, just to mention a few out of many possibilities. The formatting happens outside the int while the int stays just the same.
Similarly, a Date object holds a point in time. The same date may be formatted into for example 2017-04-12 23:14:52 or April 12, 2017 11:14:52 PM. It may even be formatted for different time zones, which would be a good idea if the system has users in different time zones. Alternatively we may show the user a calendar leaf and/or a clock showing the time. Again, formatting happens outside of the Date while the Date stays just the same.
Elaborating on the time zone issue, the same point in time represented by the same millisecond value could be formatted to 2017-04-12 17:44:52 in UTC, 2017-04-12 19:44:52 in my time zone, 2017-04-12 23:14:52 in Asia/Kolkata time zone or even 2017-04-13 05:44:52 in Pacific/Auckland time zone. Note that in the last case not even the date is the same. So there is not just one way to change your milliseconds into the format you asked for. We need to know which time zone you want it for before we can help you.
So what I believe you need is not one thing, but two
A way to store your point in time in your program.
A way to format your point in time into a string in yyyy-MM-dd HH:mm:ss format for a user in some time zone.
For storing your point in time, use either of
A long for the milliseconds value you already have
A java.time.Instant object.
Why didn’t I mention java.util.Date? Because this class is long outdated. Its design turned out to be troublesome very quickly. They tried to repair it by deprecating most of the methods and introducing java.util.Calendar, but that didn’t work very well either. Finally, drawing on the experiences from a library known as Joda-Time they introduced the java.time classes in Java 8 in 2014. That’s three years ago as of writing, and counting. So IMHO we should by now have thrown Date and friends overboard and started using the newer classes. So prefer Instant over Date.
Changing your milliseconds to an Instant is straightforward:
long milliseconds = 1492019092000L;
Instant pointInTime = Instant.ofEpochMilli(milliseconds);
For formatting your instant into a string for the user, as I said, we require a time zone. Then do
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm:ss");
String formattedDateTimeString = pointInTime.atZone(ZoneId.of("Asia/Kolkata"))
.format(formatter);
So you need to fill in the desired time zone where I put ZoneId.of("Asia/Kolkata"). If you want to use the JVM’s current time zone setting, just fill in ZoneId.systemDefault(). Beware, though, that the time zone setting may be changed, even by an unrelated program running in the same JVM, so relying on this may be fragile.
The result of the above code snippet is a string like
2017-04-12 23:14:52
PS If after reading the above you really insist, here’s how to get a java.util.Date from the above:
Date myOutdatedDateInstance = Date.from(pointInTime);
(and excuse me for repeating, it still doesn’t have the desired format, that is not possible).
You can try this sample code.
public class MillDateConverter {
public static String dFormat = "yyy-MM-dd HH:mm:ss";
private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat(dFormat);
public static void main(String[] args) {
Calendar calendar = Calendar.getInstance();
long milliSec=Long.parseLong("1086073200000");
System.out.println(milliSec);
calendar.setTimeInMillis(milliSec);
System.out.println(simpleDateFormat.format(calendar.getTime()));
}
}