简体   繁体   中英

java.sql.Date and Time Timezone differs

private GregorianCalendar formatDate(Date dateStatus, Time timeStatus) {
    GregorianCalendar calendar = (GregorianCalendar) GregorianCalendar.getInstance(TimeZone.getTimeZone("UTC"));
    calendar.setTime(new Date(dateStatus.getTime() + timeStatus.getTime()));
    return calendar;
}

The above code returns the Calendar value in milliseconds. But i am getting different value in local and while running the test in jenkins causing the testcase to fail.

Local and Jenkins Server running in different timezones.

Jenkins Error:

Expected: 1554866100000
     got: 1554903900000

How can i handle this?

  1. java.sql.Date is an extremely unfortunate API design messup. That class extends java.util.Date , and that class is a lie . It does not represent a date at all ( check the source code if you are understandably skeptical). It represent a moment in time, devoid of any timezone information, based on milliseconds since UTC new year's 1970. Anything you care to coerce from a Date object that isn't a very large number or a direct swap to a more appropriate type that doesn't lie (such as java.time.Instant ) is therefore suspect : It is picking a timezone implicitly and mixing that in, in order to provide you your answer. This is why most of Date's methods, such as .getYear() , are marked deprecated: In the java core libs, a deprecation marker usually doesn't actually mean "This will be removed soon", it actually means: "This is fundamentally broken and you should never call this method". (See: Thread.stop).

  2. Nevertheless JDBC API (what you use to talk to DBs) was built on top of this; java.sql.Date as well as java.sql.Timestamp extend java.util.Date and thereby inherit the messup. This means date handling in this fashion will convert timestamps in the DB that have full timezone info into timezoneless constructs, and then java-side you can add new timezones , which is a bad way of doing things.

  3. Unfortunately date/time is extremely complicated (see below) and databases have wildly different ways of storing it; usually multiple slightly different date/time types, such as 'TIMESTAMP', 'TIME WITH TIME ZONE', etcetera.

  4. Because of this there is no unique advice that works regardless of context: The right answer depends on your JDBC driver version, DB engine, DB engine version, and needs. This means the best approach is generally to first understand the fundamentals so that you can adapt and figure out what would work best for your specific situation.

  5. java.util.Calendar is even worse. Again a lie (it represents time. Not a calendar,). the API is extremely badly designed (it is very non-java-like). There is a reason this second try at date/time API resulted in yet another date/time library ( java.time ): It's bad. Don't use it.

So, let me try to explain the basics:

You like to wake up at 8 in the morning. It's noon and you check your phone. It says 'the next alarm will ring in 20 hours'. You now hop onto a concorde at Schiphol Airport in Amsterdam, and fly west, to New York. The flight takes 3 hours. When you land, you check your phone. Should it say 'the next alarm will ring in 17 hours' (3 hours of flight time have passed), or should it say: 'the next alarm will ring in 23 hours' (you actually land at 9 in the morning in New York time because its 6 hours earlier there relative to Amsterdam, so it's 23 hours until 8 o' clock in the morning local time). The right answer is presumably: 23 hours. This requires the concept of 'local time': A point in time stated in terms of years, months, days, hours, minutes, and seconds - but no timezone, and not even 'please assume a timezone for me', but the concept '... where-ever you are now'.

Before you jumped in the plane, you made an appointment at a barber's in Amsterdam for when you return. You made it for March 8th, 2022, at 12:00. When you check your phone it reads: "365 days, 0 hours, 0 minutes, and 0 seconds" as you made the appointment. If you fly to new york, that should now read "364 days, 21 hours, 0 minutes, and 0 seconds": The fact that you're currently in new york has no bearing on the situation whatsoever. You'd think that time-as-millis-since-UTC should thus suffice, but no it does not : Imagine that the netherlands abolishes daylight savings time (crazy idea? No, quite likely actually ). The very instant that the gavel hits the desk and the law is adopted that The Netherlands would switch to summer time in 2021, and then stay there forever more, that very moment? Your phone's indicator for 'time until barber appointment' should instantly increment by 1 hour (or it is decrement?). Because that barber appointment isn't going to reschedule itself to be at 11:00 or 13:00.

During your flight, you snapped a pic of the tulip fields before the plane crossed the atlantic. The timestamp of this picture is yet another concept of time: If, somehow, the netherlands decides to apply the timezone change retroactively, then the timestamp in terms of 'this picture was taken on 2021, march 8th, 12:05, Amsterdam time' should instantly pop and now read 13:05 or 11:05 instead. In this way it is unlike the barber appointment.

Before this question can be answered, you need to figure out which of the 3 different concepts of time your situation boils down to.

Only the java.time package fully covers this all. Respectively:

  • Alarm clock times are represented by LocalDate , LocalTime , and LocalDateTime .

  • Appointment times are represented by ZonedDateTime .

  • When did I make the picture times are represented by Instant .

The java.sql.Date type is most equivalent to Instant , which is bizarre, because you'd think that this is more java.sql.Timestamp 's bailiwick.

Pragmatics, how to get the job done

Your first stop is to fully understand your DB engine's data types. For example, in postgres :

concept java.time type postgres type
alarm clocks java.time.LocalTime time without time zone
- java.time.LocalDate date
- java.time.LocalDateTime timestamp without time zone
appointments java.time.ZonedDateTime timestamp with time zone
when i took the picture java.time.Instant no appropriate type available

Kind of silly that the implementation of java.sql.Date and java.sql.Timestamp best matches the very concept postgres cannot express, huh.

Once you ensured that your DB is using the right type for your concept, the next thing to check is if your JDBC driver and other infrastructure is up to date. You can use the right types java-side (from the java.time packages): Use these types ( LocalDate , Instant , ZonedDateTime , etc) in your infrastructre and if making raw JDBC calls, .setObject(x, instanceOfZDT) instead of .setDate when setting, and .getObject(col, LocalDateTime.class) to fetch, which works on many JDBC drivers.

If it doesn't work, you need to work around the issues , because the process is now that the DB is storing eg the year, month, day, hour, minute, second, and complete timezone description (not 'EST' there are way too many zones to cover with 3 letters, but something like 'Europe/Amsterdam'), will then convert this into millis-since-epoch to transfer it to the JDBC server, and then your java code will inject a timezone again, and thus you're going to run into issues. The best bet is to IMMEDIATELY convert ASAP, so that you have an old unwieldy type ( java.sql.Date or java.sql.Timestamp ) as short as possible and can test right at the source that you're 'undoing the damage' done when your ZDT/LDT type arrives as the Instant-esque java.sql type.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM