What is the best way to write the following code snippet in Java 8?
private Timestamp resetTime(Timestamp ts) {
ts.setHours(0);
ts.setMinutes(0);
ts.setSeconds(0);
return ts;
}
I was going to use the Calendar class but then read that it is advisable not to do so in Java 8. Any help would be greatly appreciated. Thanks.
Your code seems to try to adjust the Timestamp object to the start of the day in the default time zone (it doesn’t in all cases do that to perfection).
In the old days we used Timestamp to transfer a value to an SQL timestamp with or without time zone. The latter is somewhat self-contradictory: a time stamp is supposed to define a point in time, but a date and time of day without time zone or UTC offset doesn’t do that. So let’s first assume that you want a value to transfer to an SQL database that needs a timestamp with time zone. The type to use for that in Java 8 (assuming JDBC 4.2 or later driver) is OffsetDateTime (some JDBC drivers also accept Instant). Since the databases I know of always use UTC as time zone, I find it most natural and least confusing to transfer an OffsetDateTime in UTC.
private OffsetDateTime resetTime(LocalDate date) {
return date.atStartOfDay(ZoneId.systemDefault())
.toOffsetDateTime()
.withOffsetSameInstant(ZoneOffset.UTC);
}
Example use:
OffsetDateTime ts = resetTime(LocalDate.of(2019, Month.NOVEMBER, 30));
System.out.println(ts);
Output when running in the Africa/Blantyre time zone (just to pick a time zone at random):
2019-11-29T22:00Z
My method accepts a LocalDate argument. A LocalDate is a date without time of day and all that the method needs since it is setting the time of day to 00:00:00 anyway.
Should your database require a timestamp without time zone (not recommended), you will need a LocalDateTime instead:
private LocalDateTime resetTime(LocalDate date) {
return date.atStartOfDay();
}
I was going to use the Calendar class but then read that it is
advisable not to do so in Java 8.
Your are completely correct. Before the advent of java.time in 2014 the Timestamp class was used with SQL databases and the Calendar class would have been the correct means for you task (with the Joda-Time library as a probably better alternative). Even though both Timestamp and Calendar were poorly designed. Now they are long outdated, we should not use any of them anymore.
Link: Oracle tutorial: Date Time explaining how to use java.time.
You can user java.time.ZonedDateTime and java.sql.Timestamp together
Timestamp.valueOf(ZonedDateTime.now().truncatedTo(ChronoUnit.DAYS).toLocalDateTime());
Related
I should preface this with I use Apache Spark, which uses java.sql.Date, in case anyone suggests I should use dates from java.time. The example below is in Scala.
The API that I use (which is deprecated) to get the month for a date is as follows:
val date: java.sql.Date = ???
val month = date.getMonth()
However if I look at how it appears I should do this based on the deprecation, the above code would be re-written as follows:
val date: java.sql.Date = ???
val cal = Calendar.getInstance()
cal.setTime(date)
cal.get(Calendar.MONTH)
The simplicity and readability of the code is significantly different, and the date being a side effect on the calendar is not terribly nice from a functional programming point of view. Can someone explain why they think this change was made?
Prior to JDK 1.1, the class Date had two additional functions. It
allowed the interpretation of dates as year, month, day, hour, minute,
and second values. It also allowed the formatting and parsing of date
strings. Unfortunately, the API for these functions was not amenable
to internationalization. As of JDK 1.1, the Calendar class should be
used to convert between dates and time fields and the DateFormat class
should be used to format and parse date strings. The corresponding
methods in Date are deprecated.
The JavaDoc explains. Internationalization.
"in case anyone suggests I should use dates from java.time"
There is nothing to stop you from converting to java.time classes as soon as possible, performing whatever calculations/modifications you need and, if you need to re-insert, converting back to java.sql.Date again.
val date: java.sql.Date = ???
val month = date.toLocalDate().getMonthValue()
You said it yourself, and I still think: You should use java.time, the modern Java date and time API. When you get an old-fashioned java.sql.Date from a legacy API not yet upgraded to java.time, convert it to a modern LocalDate and enjoy the natural code writing with java.time.
Why were getMonth() and the other getXxx methods deprecated?
While Michael has already answered the question with respect to java.util.Date, I have something to add when it comes to java.sql.Date. For this class the situation is quite a bit worse than what Michael reported.
What is left undeprecated (apprecated?) of java.util.Date after the deprecations is that a Date is a point in time. java.sql.Date on the other hand was never meant to be a point in time. One way to illustrate this fact is that its toInstant method — which should convert it to an Instant, a point in time — unconditionally throws an UnsupportedOperationException. A java.sql.Date was meant to be a calendar date to be used with an SQL database and its date datatype, which in most cases is also a date, defined by year, month and day of month. Since a Date is no longer year, month and day of month, they have virtually deprecated everything that a java.sql.Date was supposed to be. And they didn’t give us a replacement until with JDBC 4.2 we can exchange LocalDate objects with SQL databases.
The observations that lead to deprecation have got very practical consequences. Let’s try this (in Java because it is what I can write):
void foo(java.sql.Date sqlDate) {
System.out.println(sqlDate);
TimeZone.setDefault(TimeZone.getTimeZone(ZoneId.of("Pacific/Samoa")));
System.out.println(sqlDate.getMonth());
}
In one call the method printed:
2020-11-02
9
So we had the 2nd day of the 11th month, and month prints as 9? There are two things going on:
Confusingly the month number that getMonth() returns is 0-based, so 9 means October.
The Date is internally represented as a count of milliseconds since the epoch to the start of the day in the default time zone of the JVM. 2020-11-02 00:00:00 in my original time zone (set to Pacific/Kiritimati for this demonstration) is the same point in time as 2020-10-31 23:00:00 in Samoa. Therefore we get October.
You don’t have to change the time zone yourself for this to happen. Situations where it can happen include:
The default time zone of the JVM can be changed from any part of your program and from any other program running in the same JVM.
The date may be serialized in a program running in one JVM and deserialized in a different JVM with a different time zone setting.
BTW the first snippet I presented at the top often won’t help against unexpected results in these situations. If things go off track before you convert from java.sql.Date to LocalDate, the conversion too will give you the wrong date. If you can make it, convert to LocalDate before anyone messes with the JVM time zone setting and be on the safe side.
An external API returns an object with a date.
According to their API specification, all dates are always reported in GMT.
However, the generated client classes (which I can't edit) doesn't set the timezone correctly. Instead, it uses the local timezone without converting the date to that timezone.
So, long story short, I have an object with a date that I know to be GMT but it says CET. How can I adjust for this mistake withouth changing my local timezone on the computer or doing something like this:
LocalDateTime.ofInstant(someObject.getDate().toInstant().plus(1, ChronoUnit.HOURS),
ZoneId.of("CET"));
Thank you.
tl;dr ⇒ use ZonedDateTime for conversion
public static void main(String[] args) {
// use your date here, this is just "now"
Date date = new Date();
// parse it to an object that is aware of the (currently wrong) time zone
ZonedDateTime wrongZoneZdt = ZonedDateTime.ofInstant(date.toInstant(), ZoneId.of("CET"));
// print it to see the result
System.out.println(wrongZoneZdt.format(DateTimeFormatter.ISO_ZONED_DATE_TIME));
// extract the information that should stay (only date and time, NOT zone or offset)
LocalDateTime ldt = wrongZoneZdt.toLocalDateTime();
// print it, too
System.out.println(ldt.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
// then take the object without zone information and simply add a zone
ZonedDateTime correctZoneZdt = ldt.atZone(ZoneId.of("GMT"));
// print the result
System.out.println(correctZoneZdt.format(DateTimeFormatter.ISO_ZONED_DATE_TIME));
}
Output:
2020-01-24T09:21:37.167+01:00[CET]
2020-01-24T09:21:37.167
2020-01-24T09:21:37.167Z[GMT]
Explanation:
The reason why your approach did not just correct the zone but also adjusted the time accordingly (which is good when desired) is your use of a LocalDateTime created from an Instant. An Instant represents a moment in time which could have different representations in different zones but it stays the same moment. If you create a LocalDateTime from it and put another zone, the date and time are getting converted to the target zone's. This is not just replacing the zone while keeping the date and time as they are.
If you use a LocalDateTime from a ZonedDateTime, you extract the date and time representation ignoring the zone, which enables you to add a different zone afterwards and keep the date and time as it was.
Edit: If the code is running in the same JVM as the faulty code, you can use ZoneId.systemDefault() to get the same time zone as the faulty code is using. And depending on taste you may use ZoneOffset.UTC instead of ZoneId.of("GMT").
I am afraid you will not get around some calculations here. I'd strongly suggest to follow an approach based on java.time classes, but alternatively you might use the java.util.Calendar class and myCalendar.get(Calendar.ZONE_OFFSET) for those calculations:
https://docs.oracle.com/javase/8/docs/api/java/util/Calendar.html#ZONE_OFFSET
I want to convert a timestamp to a string given a timezone argument (in Java). From the code I'm looking at internally the timestamp has nanosecond precision (or at least one can convert it to nanoseconds and I don't care since the output format I want to generate isn't that precise).
The DateFormat allows an S specifier and will apply a TimeZone, but it seems to do only 3 digits (milliseconds?) precision. Using toString gives 6 digits of precision within the seconds (microseconds?) but doesn't seem to take a TimeZone argument.
I need something that does both, allows a TimeZone specifier and gives 6 digits of precision within a second.
What to use?
I am assuming you mean a java.sql.Timestamp that you are getting from an SQL database where it was a timestamp either with or without time zone.
Use java.time
First, you don’t need that. The Timestamp class is poorly designed and long outdated. Instead prefer to get from your database:
If your database column is a timestamp with time zone, which it should be, then get an OffsetDateTime (with some JDBC drivers an Instant works too).
If your database column is timestamp without time zone (not recommended), then get a LocalDateTime. The problem with this is that a LocalDateTime is a date and time without time zone, so not a unique point in time, so unsuited for a timestamp.
The mentioned types are all from java.time, the modern Java date and time API, and they all have nanosecond precision.
Example:
OffsetDateTime odt = yourResultSet.getObject("my_timestamp_col", OffsetDateTime.class);
System.out.println(odt);
Example output:
2018-11-29T22:34:56.123456789Z
The trailing Z in the output means Zulu time zone, UTC or offset zero.
You wanted to apply a given time zone. So for example:
ZonedDateTime zdt = odt.atZoneSameInstant(ZoneId.of("America/Guyana"));
System.out.println(zdt);
2018-11-29T18:34:56.123456789-04:00[America/Guyana]
Getting a type from java.time from the result set using ResultSet.getObject requires JDBC 4.2 (or a modern JPA implementation or what you use for data access). Most of us have that.
Handling a Timestamp from a legacy API
In case you either haven’t got JDBC 4.2 yet or you are getting the Timestamp from a legacy API that you can’t afford to change just now:
ZonedDateTime zdt = yourTimestamp.toInstant()
.atZone(ZoneId.of("America/Guyana"));
If you have specific requirements for your string output, use a DateTimeFormatter to format your ZonedDateTime. This is described in many places, just search.
Link
Oracle tutorial: Date Time explaining how to use java.time.
Im converting date time string format to Timestamp (java.sql) from different format. I have converted it to LocalDateTime based on different format. Is there anyway to convert LocalDateTime object to Timestamp?
It’s more delicate than you think, so good you asked.
Since you can use java.time, the modern Java date and time API, you should not wish to apply the old-fashioned and poorly designed Timestamp class too.
I am assuming that you were asking for a Timestamp because you need something that you can store into an SQL database column of type timestamp (without time zone). Allow me to mention that for most purposes you don’t want such a column. The standard SQL datatype timestamp doesn’t live up its name. While the idea of a timestamp is that it should identify an unambiguous point in time when something happened, an SQL timestamp does not define such a point in time. It defines a date and time of day, and anyone is free to interpret it in any time zone, allowing for a variation of some 26 hours. Instead for a point in time you should use timestamp with timezone. It doesn’t live up to its name either in that it doesn’t let you choose the time zone. It stores times in UTC, so does identify a unique point in time.
On the Java side use an OffsetDateTime for storing into a timestamp with timezone column. Since you’ve got a LocalDateTime, you need to convert. And for the conversion you need to know the time zone intended behind your LocalDateTime. For example:
final ZoneId zone = ZoneId.of("Asia/Tehran");
LocalDateTime ldt = LocalDateTime.of(2019, 2, 25, 23, 45);
OffsetDateTime odt = ldt.atZone(zone).toOffsetDateTime();
System.out.println("As OffsetDateTime: " + odt);
As OffsetDateTime: 2019-02-25T23:45+03:30
Store into your database:
PreparedStatement ps = yourDatabaseConnection.prepareStatement(
"insert into your_table(your_ts_with_timezone) values (?)");
ps.setObject(1, odt);
ps.executeUpdate();
If either for some reason you do need a timestamp without time zone in your database or you cannot change it, you don’t need any conversion at all. Just store the LocalDateTime you had:
PreparedStatement ps = yourDatabaseConnection.prepareStatement(
"insert into your_table(your_ts_without_timezone) values (?)");
ps.setObject(1, ldt);
If you are programming to a legacy API that requires an old-fashioned java.sql.Timestampobject, things are getting even delicater. A Timestamp does define a point in time. So you will now need to convert from a LocalDateTime that does not define a point in time, to a Timestamp that does define one, then pass the Timestamp to an API that may store it in a database column that again does not define an unambiguous point in time. You will need to be sure which time zones are used for the conversions, and still there may be corner cases that fail or (worse) give incorrect results. However, as the_storyteller mentioned in a comment, the conversion is straightforward enough:
Timestamp ts = Timestamp.valueOf(ldt);
System.out.println("As old-fashioned Timestamp: " + ts);
As old-fashioned Timestamp: 2019-02-25 23:45:00.0
The conversion uses your JVMs default time zone. This setting is also used by TImestamp.toString(), so the output is as expected. This is shaky, though, since any program running in the JVM may change the default time zone setting at any time, so generally you don’t know what you get. To exercise control over the time zone used for conversion:
Instant i = ldt.atZone(zone).toInstant();
Timestamp ts = Timestamp.from(i);
Links
SQL- Difference between TIMESTAMP, DATE AND TIMESTAMP WITH TIMEZONE?
try it out with java 8 built in classes
public class Test {
public static void main(String[] args) {
Timestamp timestamp = Timestamp.valueOf(LocalDateTime.now());
// java timestamp class
System.out.println(timestamp);
// miliseconds
System.out.println(timestamp.getTime());
}
}
I have time 12:00:00 in format HH:mm:ss.
I know that this time comes from server witch is setup with +3 offset.
If i use SimpleDateFormat df = new SimpleDateFormat("HH:mm:ss");, it parses time with regard to device, which can be in a different timezone.
Is there another way to parse it with regard to +3 offset except adding it to the original string?
First, should your server rather send the time in UTC? If clients are everywhere, this would seem more time zone neutral and standardized. However, the way to handle it in code wouldn’t be much different. In any case the server offset form UTC could be constant:
private static final ZoneOffset serverOffset = ZoneOffset.ofHours(3);
In real code you will probably want to make it configurable somehow, though. To parse:
OffsetTime serverTime = LocalTime.parse("12:00:00").atOffset(serverOffset);
System.out.println(serverTime);
This prints
12:00+03:00
Since your time format agrees with LocalTime’s default (ISO 8601), we need no explicit formatter. If a representation of the time with offset is all you need, we’re done. If you need to convert to the user’s local time, to do that reliably you need to decide both a time zone and a date:
LocalTime clientTime = serverTime.atDate(LocalDate.of(2018, Month.JANUARY, 25))
.atZoneSameInstant(ZoneId.of("Indian/Maldives"))
.toLocalTime();
System.out.println(clientTime);
With the chosen day and zone we get
14:00
Please substitute your desired time zone and date.
Just hypothetically, if you knew the user’s offset from UTC, you could use just that:
LocalTime clientTime = serverTime.withOffsetSameInstant(ZoneOffset.of("-08:45"))
.toLocalTime();
The example yields 00:15. However, no one knows when the politicians introduce summer time (DST) or other anomalies in the user’s time zone, so I discourage relying on an offset alone.
And yes, I too am using java.time. SimpleDateFormat is not only long outdated, it is also notoriously troublesome, so java.time is what I warmly recommend.
Set the timezone on your SimpleDateFormat object:
SimpleDateFormat fmt = new SimpleDateFormat("HH:mm:ss");
fmt.setTimeZone(TimeZone.getTimeZone("GMT+03:00"));
I recommend you use the Java 8 date and time API (package java.time) instead of the old API, of which SimpleDateFormat is a part.
Using the Java 8 DateTime API:
DateTimeFormatter formatter = DateTimeFormatter
.ofPattern("HH:mm:ss");
LocalTime clientLocalTime = LocalTime
.parse("12:00:00", formatter)
// Create an OffsetTime object set to the server's +3 offset zone
.atOffset(ZoneOffset.ofHours(3))
// Convert the time from the server timezone to the client's local timezone.
// This expects the time value to be from the same day,
// otherwise the local timezone offset may be incorrect.
.withOffsetSameInstant(ZoneId.systemDefault().getRules().getOffset(Instant.now()))
// Drop the timezone info - not necessary
.toLocalTime();