Different results with LocalDateTime by different calls with same parameter - java

The problem is, that I have to change my code from Calendar object to LocalDateTime object. But I don't get the same timestamp at the end. In the first call I got the same with localDateTime, on the next calls I get other timestamps and I use the same parameter to calculate the timestamps. I don't know why I get different results. It isn't logic for me. What I want to do is: I get a UTC Timestamp. I want to set it on german(Europe/Berlin) time(important about summer and winter season). Then I want to calculate the start of the day(00:00) and the end of the day(23:59). Then I want to get the timestamp for this times.
I build an API with spring-boot. The above described function is invoked by a controller class from spring-boot. The first call after the start of the API calculates the expected results. But all next calls give other results. Always with 7200 difference. I tried other ways with localDateTime, but it never gaves the same timestamp as with calendar.
LocalDateTimeWay:
LocalDateTime localDateTime =
LocalDateTime.ofInstant(Instant.ofEpochSecond(timestamp), ZoneId.systemDefault());
LocalDateTime dayStartLocal = localDateTime.withHour(0)
.withMinute(0)
.withSecond(0)
.withNano(0);
ZonedDateTime startZonedDateTime = dayStartLocal.atZone(ZoneId.systemDefault());
long dayStartTimeStamp = startZonedDateTime.toInstant().getEpochSecond();
LocalDateTime dayEndLocal = localDateTime.withHour(23)
.withMinute(59)
.withSecond(59)
.withNano(999);
ZonedDateTime endZonedDateTime = dayEndLocal.atZone(ZoneId.systemDefault());
long dayEndTimeStamp = endZonedDateTime.toInstant().getEpochSecond();
CalendarWay:
Calendar cal=Calendar.getInstance();
cal.setTimeInMillis(timestamp*1000);
cal.setTimeZone(TimeZone.getTimeZone("Europe/Berlin"));
cal.set(Calendar.HOUR_OF_DAY,0);
cal.set(Calendar.MINUTE,0);
cal.set(Calendar.SECOND,0);
cal.set(Calendar.MILLISECOND,0);
long dayStartTimeStamp = calendar.getTimeInMillis()/1000L;
cal.set(Calendar.HOUR_OF_DAY,23);
cal.set(Calendar.MINUTE,59);
cal.set(Calendar.SECOND,59);
cal.set(Calendar.MILLISECOND,999);
long dayEndTimeStamp = calendar.getTimeInMillis()/1000L;
I want by the param timestamp 1536933600. The result 1536876000 and 1536962399. But I get after the first request by localDateTime method 1536883200 and 1536969599.

You are using system default zone for your java.time code and Europe/Berlin zone for Calendar code. The 7200 is most likely the difference between your system time zone and Europe/Berlin (2 hours).
Replace all ZoneId.systemDefault() with ZoneId.of("Europe/Berlin") and you will get the same values in both versions:
timestamp = 1536933600
dayStartTimeStamp = 1536876000
dayEndTimeStamp = 1536962399

Related

How to convert an Epoch timestamp into an object of type java.time.Instant Accurately in Java?

I have a Spring Boot JPA Application that interacts with a 3rd party API.
The response payload of the API has a key
"created_at": 1591988071
I need to parse this field into java.time.Instant so that I can do some comparisons with the value I have in the Database.
I have learned that I can use the below mentioned piece of code.
Instant instant = Instant.ofEpochSecond(1591988071);
Output :
2020-06-12T18:54:31Z
But to be honest, this output is off by a couple of hours.
I found another approach, wherein if I use
String dateAsText = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
.format(new Date(1591988071 * 1000L));
System.out.println(dateAsText);
I get the desired output but in String format.
2020-06-13 00:24:31
Can someone tell me how to obtain the above String output but converted into type java.time.Instant ?
It is likely you're in a different timezone than UTC. The instant is giving you time in UTC. That's indicated by the Z at the end of your first output.
You would want to look at atZone
Instant instant = Instant.ofEpochSecond(1591988071);
System.out.println(instant);
final ZonedDateTime losAngeles = instant.atZone(ZoneId.of("America/Los_Angeles"));
System.out.println(losAngeles);
final ZonedDateTime mumbai = instant.atZone(ZoneId.of("UTC+0530"));
System.out.println(mumbai);
This gives you something you might expect
2020-06-12T18:54:31Z
2020-06-12T11:54:31-07:00[America/Los_Angeles]
2020-06-13T00:24:31+05:30[UTC+05:30]

get tomorrow date 1:00pm in UNIX timestrap

Can't find any solution how to get tomorrow's date 13:00.
For example: today is 16.01.2019, I need to find how in unix timestrap is 17.01.2019 13:00.
tried this:
LocalDateTime tomorrowWithTime =
LocalDateTime.of(LocalDate.now().plusDays(1), 13:00);
to set manually 13:00, add it to tomorrows date and then convert to unix timestrap, but no luck :(
You were very close to it. To get the LocalDateTime it's
LocalDateTime tomorrowWithTime = LocalDateTime.of(LocalDate.now().plusDays(1), LocalTime.of(13, 0));
Then to convert it to unix timestamp please have a look at this question. In summary you have to give a ZoneId: (to give a working answer I'll use your system zoneId):
ZoneId zoneId = ZoneId.systemDefault(); //Or the appropriate zone
long timestamp = tomorrowWithTime.atZone(zoneId).toEpochSecond();
By the way, if your issue was that you didn't know what to give as parameters to LocalDateTime.of(), your first reflex should be to have a look at the API to see what parameters it accepts.

JDK8 adjust time of a java.time.Instant

I'm trying to adjust the time of a java.time.Instant.
I tried this code:
Instant validFrom = //from my method parameter
validFrom = validFrom.with(ChronoField.HOUR_OF_DAY, 19).with(ChronoField.MINUTE_OF_DAY, 00)
.with(ChronoField.SECOND_OF_DAY, 00).with(ChronoField.MILLI_OF_DAY, 00);
But I've an exception:
java.time.temporal.UnsupportedTemporalTypeException: Unsupported field: HourOfDay
at java.time.Instant.with(Instant.java:720)
This is quite expected reading the documentation and checking the source code.
It's not really clear to me why I can't do that. Is there another way to to this operation without incurring in many conversions?
This is because Instant has no timezone information. 19:00 hours doesn't represent anything useful, unless you attach a timezone to it.
You can convert it to a ZonedDateTime object, and then convert it back to an Instant like this:
public void testInstant(){
Instant now = Instant.now();
ZonedDateTime utc = now.atZone(ZoneId.of("UTC"));
ZonedDateTime zonedDateTime = utc.withHour(19)
.withMinute(0)
.withSecond(0);
Instant instant = zonedDateTime.toInstant();
}

Spring Data MongoDB - when saving LocaDate/LocalDateTime the value is being set to yesterday

I've got a problem with saving Java8 Date API values to MongoDB database. Whenever a new LocalDate instance (LocalDate.now()) is being saved, as a result we are getting value with yesterdays date with time set to 23:00 PM.
Example:
dt.getDate().toString()
is giving me "2017-03-17"
but when i look into the database i have a value like this:
"dt" : ISODate("2017-03-16T23:00:00.000Z")
My time zone is UTC+01:00
MongoDB saves the date in UTC time.
LocalTime is your wall clock time.
When you pass it to MongoDb, spring will convert the LocalTime to Instant(UTC time) by using your System Zone.
Consider
LocalDateTime localDateTime = LocalDateTime.parse("2017-03-17T00:00:00")
Something like this happpens
Instant instant = localDateTime.atZone(ZoneId.systemDefault()).toInstant();
This is where your local time is changed to UTC instant by applying offset (+01:00) from your local time.
Output(in UTC) : 2017-03-16T23:00:00.000Z

JDBC Prepared statement Timestamp issues [duplicate]

I have to store UTC dateTime in DB.
I have converted the dateTime given in specific timezone to UTC. for that I followed the below code.
My input dateTime is "20121225 10:00:00 Z" timezone is "Asia/Calcutta"
My Server/DB(oracle) is running in the same timezone(IST) "Asia/Calcutta"
Get the Date object in this specific Timezone
String date = "20121225 10:00:00 Z";
String timeZoneId = "Asia/Calcutta";
TimeZone timeZone = TimeZone.getTimeZone(timeZoneId);
DateFormat dateFormatLocal = new SimpleDateFormat("yyyyMMdd HH:mm:ss z");
//This date object is given time and given timezone
java.util.Date parsedDate = dateFormatLocal.parse(date + " "
+ timeZone.getDisplayName(false, TimeZone.SHORT));
if (timeZone.inDaylightTime(parsedDate)) {
// We need to re-parse because we don't know if the date
// is DST until it is parsed...
parsedDate = dateFormatLocal.parse(date + " "
+ timeZone.getDisplayName(true, TimeZone.SHORT));
}
//assigning to the java.sql.TimeStamp instace variable
obj.setTsSchedStartTime(new java.sql.Timestamp(parsedDate.getTime()));
Store into DB
if (tsSchedStartTime != null) {
stmt.setTimestamp(11, tsSchedStartTime);
} else {
stmt.setNull(11, java.sql.Types.DATE);
}
OUTPUT
DB (oracle) has stored the same given dateTime: "20121225 10:00:00 not in UTC.
I have confirmed from the below sql.
select to_char(sched_start_time, 'yyyy/mm/dd hh24:mi:ss') from myTable
My DB server also running on the same timezone "Asia/Calcutta"
It gives me the below appearances
Date.getTime() is not in UTC
Or Timestamp is has timezone impact while storing into DB
What am I doing wrong here?
One more question:
Will timeStamp.toString() print in local timezone like java.util.date does? Not UTC?
Although it is not explicitly specified for setTimestamp(int parameterIndex, Timestamp x) drivers have to follow the rules established by the setTimestamp(int parameterIndex, Timestamp x, Calendar cal) javadoc:
Sets the designated parameter to the given java.sql.Timestamp value, using the given Calendar object. The driver uses the Calendar object to construct an SQL TIMESTAMP value, which the driver then sends to the database. With a Calendar object, the driver can calculate the timestamp taking into account a custom time zone. If no Calendar object is specified, the driver uses the default time zone, which is that of the virtual machine running the application.
When you call with setTimestamp(int parameterIndex, Timestamp x) the JDBC driver uses the time zone of the virtual machine to calculate the date and time of the timestamp in that time zone. This date and time is what is stored in the database, and if the database column does not store time zone information, then any information about the zone is lost (which means it is up to the application(s) using the database to use the same time zone consistently or come up with another scheme to discern timezone (ie store in a separate column).
For example: Your local time zone is GMT+2. You store "2012-12-25 10:00:00 UTC". The actual value stored in the database is "2012-12-25 12:00:00". You retrieve it again: you get it back again as "2012-12-25 10:00:00 UTC" (but only if you retrieve it using getTimestamp(..)), but when another application accesses the database in time zone GMT+0, it will retrieve the timestamp as "2012-12-25 12:00:00 UTC".
If you want to store it in a different timezone, then you need to use the setTimestamp(int parameterIndex, Timestamp x, Calendar cal) with a Calendar instance in the required timezone. Just make sure you also use the equivalent getter with the same time zone when retrieving values (if you use a TIMESTAMP without timezone information in your database).
So, assuming you want to store the actual GMT timezone, you need to use:
Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
stmt.setTimestamp(11, tsSchedStartTime, cal);
With JDBC 4.2 a compliant driver should support java.time.LocalDateTime (and java.time.LocalTime) for TIMESTAMP (and TIME) through get/set/updateObject. The java.time.Local* classes are without time zones, so no conversion needs to be applied (although that might open a new set of problems if your code did assume a specific time zone).
I think the correct answer should be java.sql.Timestamp is NOT timezone specific. Timestamp is a composite of java.util.Date and a separate nanoseconds value. There is no timezone information in this class. Thus just as Date this class simply holds the number of milliseconds since January 1, 1970, 00:00:00 GMT + nanos.
In PreparedStatement.setTimestamp(int parameterIndex, Timestamp x, Calendar cal)
Calendar is used by the driver to change the default timezone. But Timestamp still holds milliseconds in GMT.
API is unclear about how exactly JDBC driver is supposed to use Calendar. Providers seem to feel free about how to interpret it, e.g. last time I worked with MySQL 5.5 Calendar the driver simply ignored Calendar in both PreparedStatement.setTimestamp and ResultSet.getTimestamp.
The answer is that java.sql.Timestamp is a mess and should be avoided. Use java.time.LocalDateTime instead.
So why is it a mess? From the java.sql.Timestamp JavaDoc, a java.sql.Timestamp is a "thin wrapper around java.util.Date that allows the JDBC API to identify this as an SQL TIMESTAMP value". From the java.util.Date JavaDoc, "the Date class is intended to reflect coordinated universal time (UTC)". From the ISO SQL spec a TIMESTAMP WITHOUT TIME ZONE "is a data type that is datetime without time zone". TIMESTAMP is a short name for TIMESTAMP WITHOUT TIME ZONE. So a java.sql.Timestamp "reflects" UTC while SQL TIMESTAMP is "without time zone".
Because java.sql.Timestamp reflects UTC its methods apply conversions. This causes no end of confusion. From the SQL perspective it makes no sense to convert a SQL TIMESTAMP value to some other time zone as a TIMESTAMP has no time zone to convert from. What does it mean to convert 42 to Fahrenheit? It means nothing because 42 does not have temperature units. It's just a bare number. Similarly you can't convert a TIMESTAMP of 2020-07-22T10:38:00 to Americas/Los Angeles because 2020-07-22T10:30:00 is not in any time zone. It's not in UTC or GMT or anything else. It's a bare date time.
java.time.LocalDateTime is also a bare date time. It does not have a time zone, exactly like SQL TIMESTAMP. None of its methods apply any kind of time zone conversion which makes its behavior much easier to predict and understand. So don't use java.sql.Timestamp. Use java.time.LocalDateTime.
LocalDateTime ldt = rs.getObject(col, LocalDateTime.class);
ps.setObject(param, ldt, JDBCType.TIMESTAMP);
You can use the below method to store the timestamp in database specific to your desired zone/zone Id.
ZonedDateTime zdt = ZonedDateTime.now(ZoneId.of("Asia/Calcutta")) ;
Timestamp timestamp = Timestamp.valueOf(zdt.toLocalDateTime());
A common mistake people do is use LocaleDateTime to get the timestamp of that instant which discards any information specif to your zone even if you try to convert it later. It does not understand the Zone.
Please note Timestamp is of the class java.sql.Timestamp.
For Mysql, we have a limitation.
In the driver Mysql doc, we have :
The following are some known issues and limitations for MySQL
Connector/J: When Connector/J retrieves timestamps for a daylight
saving time (DST) switch day using the getTimeStamp() method on the
result set, some of the returned values might be wrong. The errors can
be avoided by using the following connection options when connecting
to a database:
useTimezone=true
useLegacyDatetimeCode=false
serverTimezone=UTC
So, when we do not use this parameters and we call setTimestamp or getTimestamp with calendar or without calendar, we have the timestamp in the jvm timezone.
Example :
The jvm timezone is GMT+2.
In the database, we have a timestamp : 1461100256 = 19/04/16 21:10:56,000000000 GMT
Properties props = new Properties();
props.setProperty("user", "root");
props.setProperty("password", "");
props.setProperty("useTimezone", "true");
props.setProperty("useLegacyDatetimeCode", "false");
props.setProperty("serverTimezone", "UTC");
Connection con = DriverManager.getConnection(conString, props);
......
Calendar nowGMT = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
Calendar nowGMTPlus4 = Calendar.getInstance(TimeZone.getTimeZone("GMT+4"));
......
rs.getTimestamp("timestampColumn");//Oracle driver convert date to jvm timezone and Mysql convert date to GMT (specified in the parameter)
rs.getTimestamp("timestampColumn", nowGMT);//convert date to GMT
rs.getTimestamp("timestampColumn", nowGMTPlus4);//convert date to GMT+4 timezone
The first method returns : 1461100256000 = 19/04/2016 - 21:10:56 GMT
The second method returns : 1461100256000 = 19/04/2016 - 21:10:56 GMT
The third method returns : 1461085856000 = 19/04/2016 - 17:10:56 GMT
Instead of Oracle, when we use the same calls, we have :
The first method returns : 1461093056000 = 19/04/2016 - 19:10:56 GMT
The second method returns : 1461100256000 = 19/04/2016 - 21:10:56 GMT
The third method returns : 1461085856000 = 19/04/2016 - 17:10:56 GMT
NB :
It is not necessary to specify the parameters for Oracle.
It is specific from your driver. You need to supply a parameter in your Java program to tell it the time zone you want to use.
java -Duser.timezone="America/New_York" GetCurrentDateTimeZone
Further this:
to_char(new_time(sched_start_time, 'CURRENT_TIMEZONE', 'NEW_TIMEZONE'), 'MM/DD/YY HH:MI AM')
May also be of value in handling the conversion properly. Taken from here
If your problem is to get a timestamp of the local zone, you can use this:
Timestamp.from(Instant.now()).toLocalDateTime()

Categories