Java.sql.date storing previous date instead of given date - java

I am trying to store 2019-07-27 in sql DB but it stores 2019-07-26. Can anyone help me in this.
I am using Java.sql.Date here. I also tried with Java.util.Date and Java.sql.Timestamp, all gives same result.

Time zones, and wrong class
You failed to provide enough info for a definite answer.
But I suspect your issue is due to time zones. The java.sql.Date class is a terrible hack, pretending to represent a date-only but actually hiding a time-of-day and time zone too. Never use this class.
You are using terrible date-time classes that were supplanted years ago with the adoption of JSR 310. Use only classes from the java.time package.
If you want to store a date-only, use a column with a data type akin to the SQL-standard type DATE.
In Java, use LocalDate with JDBC 4.2 or later. This class has only a date, without time-of-day and without time zone.
LocalDate ld = myResultSet.getObject( … , LocalDate.class ) ;
To write to the database:
LocalDate ld = LocalDate.of( 2019 , 1 , 23 ) ;
…
myPreparedStatement.setObject( … , ld ) ;
To capture the current date, specify your desired/expected time zone. For any given moment, the date varies around the globe by time zone.
ZoneId z = ZoneId.of( "Asia/Tokyo" ) ;
LocalDate ld = LocalDate.now( z ) ;

Have a look at the time zone of your JVM and your database. And have a look at the documentation on the method(s) you use to create your java.sql.Date objects.
The JavaDoc for the constructor java.sql.Date(long date) e.g. states (emphasis mine):
Constructs a Date object using the given milliseconds time value. If
the given milliseconds value contains time information, the driver
will set the time components to the time in the default time zone (the
time zone of the Java virtual machine running the application) that
corresponds to zero GMT.
In other words, if you set the time to midnight in your time zone, the constructor will set it to midnight GMT with the same date. But midnight GMT might be another day in your time zone.
That's the best answer I can give without seeing any code and any info about the db used...
Please add more info about the solution for the next person finding your question if my answer helped or give us some code if my answer doesn't lead you to a solution.
The answer on Question 14693148: JPA TemporalType.Date giving wrong date is a bit more elaborate and recommends using GMT/UTC everywhere.
The second answer on Question 21174634: JDBC ResultSet: I need a getDateTime, but there is only getDate and getTimeStamp recommends using the newer date/time classes when reading via JDBC. This might also be applicable to writing.

There are two chances :
If your DB and APP server are in different time zones try Adjusting the time of the day based on the Time difference.
This happens with LEAP years. In this case save the date with time as 12:00 instead of 0:00.
long hours = 12L * 60L * 60L * 1000L;
Date d = new Date(d1.getTime() + hours);

Related

How to store a date in ET with offset when the java code executes in UTC

We store a date in a sqlserver db table as varchar.
When this is read in the java code as a String and then parsed to a Date, it gets read as UTC (java code is in servers that are in UT). And on reconverting the date to ET, it goes 4 hours behind. How do I handle storing the date in ET in this db column so it gets read as ET in the java code.
We are researching around offsets, but not understanding exactly what to do.
Varchar date in table 03/29/2019 23:23:03 //we want this date to be in ET
SimpleDateFormat sdf = new SimpleDateFormat("MM/dd/yyyy HH:mm:ss");
Date beginDate = sdf.parse("03/29/2019 23:23:03");
//The problem is when this code executes, the server is in UTC. So beginDate //is read as 03/29/2019 23:23:03 UTC instead of 03/29/2019 23:23:03 ET
Expected 03/29/2019 23:23:03 ET
Actual 03/29/2019 23:23:03 UTC
First, you need to be aware that a Date object doesn't have a time zone at all. It's just an instant in time. So even when you've parsed the value correctly, it'll represent the right instant in time, but you may need to convert it back to Eastern Time later on.
Second, you need to be aware that storing values like this introduces ambiguity - if you store a value of (say) 11/03/2019 01:30, that local time occurs twice - once before the daylight saving transition and once afterwards. If you're always storing times in the past, you should at least consider storing UTC instead - although that's not always the right answer, particularly not if you're storing future date/time values.
For the parsing part, you just need to set the time zone in the Calendar used by SimpleDateFormat. For example:
SimpleDateFormat sdf = new SimpleDateFormat("MM/dd/yyyy HH:mm:ss", Locale.US);
sdf.setTimeZone(TimeZone.getTimeZone("America/New_York");
Date beginDate = sdf.parse("03/29/2019 23:23:03");
Finally, I'd strongly advise you to start migrating your code to use java.time if at all possible. It's a much nicer API than the java.util.Date etc API.
Use a datetime datatype of your database engine (DBMS), not varchar for date and/or time. With many DBMSs, the timestamp with timezone type defined in the SQL standard is the correct type to use. I don’t know SQL Server well enough to tell precisely what you should use there. Make sure you store date and time in UTC.
If you cannot get around the requirement to store as varchar, store in ISO 8601 format in UTC. For example 2019-03-30T03:23:03Z.
If you also cannot get around the requirement to store in Eastern Time, make sure you are clear whether North American Eastern Time or Australian Eastern Time is intended. Store date and time with UTC offset, for example 2019-03-29T23:23:03-04:00.
If you also cannot get around the requirement to store in the format 03/29/2019 23:23:03 (without offset), be aware that in the fall when summer time (DST) ends and the clocks are moved back, you are storing ambiguous times.
Under all circumstances prefer java.time, the modern Java date and time API. The answer by Basil Bourque shows you how, I don’t need to repeat that.
java.time
The Answer by Jon Skeet is correct. As he mentioned, you should be using the java.time classes defined by JSR 310. These modern classes years ago supplanted the terrible date-time classes such as Date and SimpleDateFormat. Here is some example code in that direction.
Varchar date in table 03/29/2019 23:23:03 //we want this date to be in ET
Parse the string as a LocalDateTime because our input lacks an indicator of offset-from-UTC or time zone.
Define a formatting pattern to match the input.
DateTimeFormatter f = DateTimeFormatter.ofPattern( "MM/dd/uuuu HH:mm:ss" ) ;
Parse.
LocalDateTime ldt = LocalDateTime.parse( input , f ) ;
If you are absolutely certain this date and time was intended for a particular time zone, apply a ZoneId to get a ZonedDateTime.
ZoneId z = ZoneId.of( "America/New_York" ) ;
ZonedDateTime zdt = ldt.atZone( z ) ;
Adjust to UTC by extracting an Instant.
Instant instant = zdt.toInstant() ;
Your JDBC driver may not accept a Instant. So convert to an OffsetDateTime where the offset is set to zero hours-minutes-seconds (in other words, UTC itself).
OffsetDateTime odt = instant.atOffset( ZoneOffset.UTC ) ;
Write to a column of type TIMESTAMP WITH TIME ZONE in your database. As of JDBC 4.2 and later, we can directly exchange java.time objects with a database.
myPreparedStatement.setObject( … , odt ) ;
And retrieval.
OffsetDateTime odt = myResultSet.getObject( … , OffsetDateTime.class ) ;
Adjust to your desired time zone.
ZonedDateTime zdt = odt.atZoneSameInstant( z ) ;
Now you have the pieces needed to do some database-refactoring, to replace that varchar column with a proper TIMESTAMP WITH TIME ZONE column.

Mysql server saves LocalDateTime with 3 hours beyond OS time

During the process of the service writing the LocaDateTime to the database, in my case, in a spring boot API with java 8, when saving the attribute configured as TIMESTAMP on the database side and LocaDateTime in the API, that date is being saved with 3 hours beyond the current operating system date.
Suppose I try to do this exactly at 10 in the morning, the date saved to the database should be 11 hours, but in my case it is not working that way ...
private LocalDateTime dataLimite;
#PrePersist
public void prepareToSave() {
String str = "1986-04-08 10:00";
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
LocalDateTime dateTime = LocalDateTime.parse(str, formatter);
this.dataLimite = dateTime.plusHours(1);
}
Apparently it may be noted that it is a problem on the side of the Mysql configuration, but when I test ...
SELECT NOW();
The result is exactly the date and time of the operating system, so what is the real problem that is occurring? How to fix this issue?
LocalDateTime
Read the documentation more closely.
This class does not store or represent a time-zone. Instead, it is a description of the date, as used for birthdays, combined with the local time as seen on a wall clock. It cannot represent an instant on the time-line without additional information such as an offset or time-zone.
The LocalDateTime class in Java cannot be used to represent a moment. It represents potential moments along a range of about 26-27 hours, the range of time zones around the globe.
This class contains a date and a time-of-day, but purposely lacks any concept of time zone or offset-from-UTC. So if you store, for example, noon on the 23rd of January this year, we do not know if you meant noon in Tokyo, Kolkata, Paris, or Montréal… all different moments, hours apart, happening earlier in the east and later in the west.
So you are using the wrong class. For a moment, use Instant, OffsetDateTime, or ZonedDateTime.
TIMESTAMP
Read the documentation more carefully.
The TIMESTAMP type in MySQL 8 is akin to the SQL-standard type TIMESTAMP WITH TIME ZONE. This type represents a moment, a specific point on the timeline.
The documentation explains that a moment submitted to the database is adjusted to UTC for storage.
MySQL converts TIMESTAMP values from the current time zone to UTC for storage, and back from UTC to the current time zone for retrieval.
This explains your problem. You were implicitly relying on the current default time zone to be assigned to an object of the wrong type in Java that lacked any time zone. Never write code that relies on the current default time zone (or locale) of your server, as that lies outside your control as a programmer. Instead, always specify explicitly your desired/expected time zone. Or better, just work in UTC whenever possible.
For best results stick to UTC when exchanging values with the database.
OffsetDateTime odt = OffsetDateTime.now( ZoneOffset.UTC ) ;
myPreparedStatement.setObject( … , odt ) ;
Retrieval.
OffsetDateTime odt = myResultSet.getObject( … , OffsetDateTime.class ) ;
To view that moment through the wall-clock time used by the people of a particular region, apply a ZoneId to get a ZonedDateTime.
ZoneId z = ZoneId.of( "Australia/Sydney" ) ;
ZonedDateTime zdt = odt.atZoneSameInstant( z ) ;

Java ZonedDateTime.toInstant() behavior

I'm running the below expressions on December 7th, 2018.
I'm seeing a discrepancy whereby this:
ZonedDateTime.now(ZoneId.of("America/New_York")).minusDays(30)
returns (correctly):
2018-11-07T22:44:11.242576-05:00[America/New_York]
whereas conversion to an instant:
ZonedDateTime.now(ZoneId.of("America/New_York")).minusDays(30).toInstant()
seems to mess up the result by adding an extra day to it:
2018-11-08T03:58:01.724728Z
I need an instant conversion to use its result in the following code as Date:
... = Date.from(t.toInstant())
An equivalent Python code (Django) works correctly:
datetime.datetime.now('America/New_York')+datetime.timedelta(days=-30)
evaluating to: datetime: 2018-11-07 20:13:55.063888-05:00
What's causing the discrepancy?
What should I use so that Java conversion to Date resulted in the November 7th being returned, just like in Python's case? Basically, I'm looking to an equivalent translation of that Python code into Java, or in pseudocode:
`datetime.X = datetime.now(deployment_zone) - (N_days)`,
where `deployment_zone` is configurable (i.e. `America/New_York`)
`N_days` is configurable (i.e. 30)
Update for #Basil Bourque:
When I formulated the original question, I (per SO rules) tried to simplify it to a digestible form which probably destroyed most of the necessary context making it vague. Let me try again.
As I explained in the comments, I'm converting the existing Python code (which is more actively maintained and which client wants to keep intact) to existing Java code (legacy that has not been properly maintained and strayed away from the Python's logic some time back). Both code bases need to be functionally on par with each other. Java needs to do what Python is already doing.
Python code is as follows (I'm lumping all into one place for succinctness, in reality it's distributed across a couple of files):
analytics.time_zone=America/New_York
TIME_ZONE = props.getProperty('analytics.time_zone', 'UTC')
TZ = pytz.timezone(TIME_ZONE)
def days_back(num_days=0):
adjusted_datetime = datetime.datetime.now(TZ)+datetime.timedelta(days=-num_days)
return DateRangeUtil.get_start_of_day(adjusted_datetime)
class DateRangeUtil():
#staticmethod
def get_start_of_day(date):
return date.astimezone(TZ).replace(hour=0, minute=0, second=0, microsecond=0)
which basically takes the configured time zone, in which it obtains the current instant, subtracts a specified number of days from it, converts it to the beginning of that date and thus receives the lower bound of the range to use while querying the DB, something like Start time: datetime: 2018-11-07 20:13:55.063888-05:00
When I started on the Java side, it had:
public final static DateRange parse(String dateRange) {
//.....
int days = ...
return new DateRange(toBeginningOfDay(daysBack(days)), toEndOfDay(daysBack(0)));
private final static Date daysBack(int days) {
return toDate(LocalDateTime.now().minusDays(days));
}
private final static Date toBeginningOfDay(Date d)
{
Calendar c=Calendar.getInstance();
c.setTime(d);
c.set(HOUR_OF_DAY,0);
c.set(MINUTE,0);
c.set(SECOND,0);
c.set(MILLISECOND, 0);
return c.getTime();
}
private final static Date toDate(LocalDateTime t) {
return Date.from(t.atZone(ZoneId.systemDefault()).toInstant());
}
That code didn't work and introduced the discrepancy which I describe in my original question. I started experimenting and introduced ZonedDateTime into the picture. While investigating, I found that it's the call to .toInstant() that seems to be a culprit and wanted to understand what's behind it in more depth.
In his answer, #ernest_k suggested a solution which seemed to have worked, but I still didn't quite understood which is clear from questions in the comments to his response.
The changes I made based on #ernest_k response are as follows:
private final static Date daysBack(int days) {
return toDate(ZonedDateTime.now(ZoneId.of("America/New_York")).minusDays(days).toLocalDateTime());
private final static Date toDate(LocalDateTime t) {
return Date.from(t.toInstant(ZoneOffset.UTC));
}
This seems to produce the desired outcome: However conversion from local to zoned and then back again seemed too much, so I experimented a bit more and found that simply the LocalDateTime does the trick as well:
private final static Date toDate(LocalDateTime t) {
return Date.from(t.toInstant(ZoneOffset.UTC));
}
private final static Date daysBack(int days) {
return toDate(LocalDateTime.now().minusDays(days));
}
I can see that LocalDate (and perhaps LocalDateTime) has a convenient atStartOfDay() which seems to be a fitting candidate for elimination of Dates out of the picture while replacing the legacy toBeginningOfDay(Date d) method above. Not sure it's doing the same thing - I haven't yet experimented with that idea, so the suggestions are most welcome.
So, with all of the tribulations above, my question started around toInstant() behavior, and when it's passed a zone id, whether it converts TO an instant in that zone, or FROM it, or what?
I guess for the situation I'm describing we only care that the lower time bound in the DB query is formed by comparing some consistent marker of current time (its upper bound) to what it was in the same place (time zone?) in the past N days, so comparing it with UTC should server the purpose.
Does that then make passing the zone in unnecessary?
Now, that a solution seems to have been found, the question revolves around the soundness of the approach described above and the solution that's been stumbled upon - is it the most optimal one, best practices around Java timing libs, etc. The code needs to work for any time zone in which the code bases will end up being deployed, that's why the zone is passed in via configuration.
Also, I wonder if things change when/if the DB itself is deployed off-premise from the rest of the codebase and is configured to persist data in some other time zone. But that might be another question.
tl;dr
ZonedDateTime.toInstant() adjusts a moment from a time zone to UTC. You end up with the same moment, different wall-clock time, and possibly a different date for the same simultaneous point on the timeline. What you are seeing is not a problem, not a discrepancy.
Your problem is not with subtracting 30 days. The real problems:
Not understanding that time zone affects the date
Conflating dates with days
Furthermore, your Question is vague. Saying “30 days ago” can mean at least three different things:
30 * 24 hours
A range from 22:44 thirty calendar days ago in New York time zone to 22:44 now in New York time
The entire day today as seen in New York and the entire days going back 30 days on the calendar as seen in New York.
All three possibilities are covered below, with example code, labeled with ➥.
⑦🕥 🇺🇸📞 ↔ 📞🇮🇸 ⑧🕞
On the 7th of December, shortly before midnight (22:44), Alice in her New York apartment decides to call her friend Bob in Reykjavík, Iceland. Bob can't believe his phone is ringing, and looking over at the clock on his bedside table sees the time is almost 4 AM (03:44). And Bob's fancy digital clock shows the date as the 8th of December, not the 7th. Same simultaneous moment, same point on the timeline, different wall-clock time, different date.
The people of Iceland use UTC as their time zone, year-round. New York is five hours behind UTC in December 2018, and so five hours behind Iceland. In New York it is “yesterday” the 7th while in Iceland it is “tomorrow” the 8th. Different dates, same moment.
So forget about subtracting the thirty days. Any time you take a moment in New York that is close to midnight, and then adjust to UTC, you will be moving the date forward.
No discrepancy, no extra day added. For any given moment, the date varies around the globe by time zone. With a range in time zones of about 26-27 hours, it is always “tomorrow” and “yesterday” somewhere.
Another Answer suggests involving LocalDateTime into this problem. That is ill-advised. That class purposely lacks any concept of time zone or offset-from-UTC. That means a LocalDateTime cannot represent a moment. A LocalDateTime represents potential moments along the range of 26-27 hours mentioned above. Makes no sense to involve that class here.
Instead, use OffsetDateTime for a moment viewed with an offset-from-UTC, versus [ZonedDateTime][2] which uses a time zone.
What is the difference between an offset and zone? An offset is merely a number of hours-minutes-seconds, nothing more, nothing less. A zone, in contrast, is much more. A zone is a history of the past, present, and future changes to the offset used by the people of particular region. So a time zone is always preferable to a mere offset, as it brings more information. If you want UTC specifically, you need only an offset, an offset of zero hours-minutes-seconds.
OffsetDateTime odt = zdt.toOffsetDateTime().withOffsetSameInstant( ZoneOffset.UTC ) ; // Adjust from a time zone to UTC.
The zdt and odt seen here both represent the same moment, the same point on the timeline, different wall-clock time, like Alice and Bob example above.
Days != Dates
If you want to query for a range of thirty days ago, you must define what you mean by “days”.
Days
➥ Do you mean 30 chunks of 24-hour long spans of time? If so, work with Instant. This class represents a moment in UTC, always in UTC.
ZoneId z = ZoneId.of( "America/New_York" ) ;
ZonedDateTime zdtNow = ZonedDateTime.now( z ) ;
Instant instantNow = zdt.toInstant() ; // Adjust from time zone to UTC. Same moment, different wall-clock time.
Instant instantThirtyDaysAgo = instantNow.minus( 30 , ChronoUnit.DAYS ) ; // Subtract ( 30 * 24 hours ) without regard for dates.
You may be able to exchange an Instant with your database via your JDBC driver. But Instant is optional, while support for OffsetDateTime is required by JDBC 4.2 and later. If that is the case, let's re-write that code.
ZoneId z = ZoneId.of( "America/New_York" ) ;
ZonedDateTime zdtNow = ZonedDateTime.now( z ) ;
OffsetDateTime odtNow = zdt.toOffsetDateTime().withOffsetSameInstant( ZoneOffset.UTC ) ; // Adjust from time zone to UTC. Same moment, different wall-clock time.
OffsetDateTime odtThirtyDaysAgo = odtNow.minusDays( 30 ) ;
Your SQL might be something like the following.
Note what we are using the Half-Open approach to defining a span-of-time, where the beginning is inclusive while the ending is exclusive. This is generally best practice, as it avoid the problem of finding the infinitely divisible last moment, and it provides for neatly abutting spans without gaps. So we do not use the SQL command BETWEEN, being fully-closed (inclusive on both ends).
SELECT * FROM event_ WHERE when_ >= ? AND when_ < ? ;
Set values for the placeholders in your prepared statement.
myPreparedStatement.setObject( 1 , odtThirtyDaysAgo ) ;
myPreparedStatement.setObject( 2 , odtNow ) ;
Dates
➥ If by “30 days ago” you meant 30 boxes on the calendar hanging on the wall in a New York office, that is a very different problem.
Same time-of-day
And if so, do you mean from the current moment and moving back 30 days to the same time-of-day?
ZoneId z = ZoneId.of( "America/New_York" ) ;
ZonedDateTime zdtNow = ZonedDateTime.now( z ) ;
ZonedDateTime zdtThirtyDaysAgo = zdtNow.minusDays( 30 ) ; // `ZonedDateTime` will try to keep the same time-of-day but will adjust if that time on that date in that zone is not valid.
With the code seen above, the ZonedDateTime class will try to use the same time-of-day on the earlier date. But that time may not be valid on that date in that zone, because of anomalies such as Daylight Saving Time (DST) cutover. In such an anomaly, the ZonedDateTime class adjusts to a valid time. Be sure to study the JavaDoc to understand the algorithm and to see if it suits your business rules.
Pass to your prepared statement.
myPreparedStatement.setObject( 1 , zdtThirtyDaysAgo ) ;
myPreparedStatement.setObject( 2 , zdtNow ) ;
Entire day
➥ Or by “30 days ago” do you mean dates, and by dates you mean all-day-long?
If so, we need to focus on the date-only value, by using LocalDate class, without a time-of-day and without a time zone.
ZoneId z = ZoneId.of( "America/New_York" ) ;
LocalDate today = LocalDate.now( z ) ;
LocalDate tomorrow = today.plusDays( 1 ) ;
LocalDate thirtyDaysAgo = tomorrow.minusDays( 30 ) ;
Now we need to go from the date to a specific moment by assigning a time-of-day and a time zone. We want the time to be the first moment of the day. Do not assume that means 00:00. Because of anomalies such as DST, the day may start at another time such as 01:00. Let java.time determine the first moment of the day on that date in that zone.
ZonedDateTime zdtStart = thirtyDaysAgo.atStartOfDay( z ) ;
ZonedDateTime zdtStop = tomorrow.atStartOfDay( z ) ;
Pass to your prepared statement.
myPreparedStatement.setObject( 1 , zdtStart ) ;
myPreparedStatement.setObject( 2 , zdtStop ) ;
That "extra day" is not really an extra day. 2018-11-07T22:44:11 in New York is equivalent to 2018-11-08T03:58:01 in UTC (it's the same point in time). The difference is just 5 hours, not a day (and when I google this, I see New York is GMT-5).
ZonedDateTime#toInstant returns an Instant instance representing the same point in time (in UTC):
Converts this date-time to an Instant.
This returns an Instant representing the same point on the time-line as this date-time. The calculation combines the local date-time and offset.
If you want to not use the offset when converting to instant, then you should perhaps use LocalDateTime:
ZonedDateTime.now(ZoneId.of("America/New_York"))
.toLocalDateTime()
.toInstant(ZoneOffset.UTC)
This tells it to convert as though it were already UTC time (but a warning is appropriate here: this changes the date/time value)
First, avoid the need for an old-fashioned Date if you can. java.time, the modern Java date and time API, gives you all the functionality you need.
Sometimes we do need a Date for a legacy API that we cannot change or don’t want to upgrade just now. Java is giving you what I think you want. Demonstration:
ZonedDateTime nov7 = ZonedDateTime.of(2018, 11, 7, 22, 44, 0, 0,
ZoneId.of("America/New_York"));
Instant inst = nov7.toInstant();
System.out.println("As Instant: " + inst);
Date oldFashionedDate = Date.from(inst);
System.out.println("As Date: " + oldFashionedDate);
Output from this was:
As Instant: 2018-11-08T03:44:00Z
As Date: Wed Nov 07 22:44:00 EST 2018
Admitted, to get this output I had to change my JVM’s default time zone to America/New_York first.
Date and Instant are roughly equivalent but print differently. Meaning their toString methods behave differently, which may be confusing. Each is a point in time, none of them is a date (despite the name of one of them). It is never the same date in all time zones.
Date.toString picks up your JVM’s time zone setting and uses it for generating the string it returns. Instant.toString on the other hand always uses UTC for this purpose. This is why the same point in time is printed with different date and time. Fortunately they both also print a bit of time zone information so the difference is at least visible. Date prints EST, which, albeit ambiguous, in this case means Eastern Standard Time. Instant prints Z for offset zero from UTC or “Zulu time”.

Converting Date from ZoneDateTime gives local times instead of ZonedTime

I am trying to convert the ZonedDateTime to a Date. Looks like in the conversion, it looses the time zone and gets my local time.
System.out.println("zoneDate1::::::::::"+ZonedDateTime.now(ZoneId.of("America/Chicago")));
System.out.println("zoneDate1:Date::::::::::"+Date.from(ZonedDateTime.now(ZoneId.of("America/Chicago")).toInstant()));
The above outputs as below:
zoneDate1::::::::::2016-04-15T17:35:06.357-05:00[America/Chicago]
zoneDate1:Date::::::::::Fri Apr 15 18:35:06 EDT 2016
Is this because this is a Date type? How would i go about doing this kind of conversion and conserve the zoned time?
What is the problem? What did you expect? I see no misbehavior.
Your java.time type (ZonedDateTime) is assigned a time zone of America/Chicago.
Your JVM apparently has an assigned time zone related to east coast of North America, the clue being the EDT value seen in string output. The toString method on java.util.Date applies your JVM’s current default time zone when generating its textual representation of the date-time value. Poorly designed, this behavior is trying to be helpful but is actually confusing because you cannot actually get or set this time zone on the java.util.Date object.
At any rate, the east coast of North America (such as America/New_York time zone) is an hour ahead of America/Chicago. So you are seeing 17:xx:xx time for Chicago and 18:xx:xx for Eastern Daylight Saving Time. These values are correct.
You should call java.util.TimeZone.getDefault() when investigating the behavior of the old date-time classes.
java.time
The bigger problem is that you are even using these old date-time classes such as java.util.Date/.Calendar. They are poorly designed, confusing, and troublesome. Avoid these old classes altogether. They have been supplanted in Java 8 and later by the java.time framework.
Also, avoid using 3-4 letter zone abbreviations like EDT. These are neither standardized nor unique. Use proper time zone names in continent/region format.
Instant
To capture the current date-time in java.time, just use Instant. This class captures a moment on the timeline in UTC. Do most of your work in UTC. No need for time zones unless expected by your user when displayed in the user interface.
Instant now = Instant.now();
Database
To send to your database, first make sure you have defined the column in the table as something along the line of the SQL standard TIMESTAMP WITH TIME ZONE. By the way, support for date-time types various among databases with some doing a much better job than others.
Hopefully JDBC drivers will be updated someday to directly handle the java.time types. Until then, we must convert into a java.sql type when transferring data to/from a database. The old java.sql classes have new methods to facilitate these conversions.
java.sql.Timestamp
For a date-time value like Instant, we need the java.sql.Timestamp class and its from( Instant ) method.
java.sql.Timestamp ts = java.sql.Timestamp.from( now );
Avoid working in java.sql.Timestamp as it is part of the old poorly-designed mess that is the early Java date-time classes. Use them only for database transfer, then shift into java.time immediately.
Instant instant = ts.toInstant();
So simple, no time zones or offset-from-UTC involved. The Instant, java.sql.Timestamp, and database storage are all in UTC.
ZonedDateTime
When you do need to shift into some locality’s wall-clock time, apply a time zone.
ZoneId zoneId = ZoneId.of( "America/Chicago" ); // Or "America/New_York" and so on.
ZonedDateTime zdt = ZonedDateTime.ofInstant( instant , zoneId );
Huh? Date doesn't have time zones so, this is probably why it's failing. Maybe this is what you're looking for:
Date.from(java.time.ZonedDateTime.now().toInstant());
If your database allows you to store the timestamp along with the timezone, you should go ahead and save it as a timestamp.
If not, I would recommend that you store the date-time as per your timezone (or GMT). Add an additional column in the table to hold the value of the user's timezone.
When you fetch the value from the database, you can convert it to the user's timezone. Avoid storing just the date.

Timestamp with timezone in PostgreSQL + Java?

I have a column in the database (PostgreSQL).
I want to insert the current time in GMT in this column.
When getting the current time and inserting it into the DB
it's inserted in the server timezone GMT-5 although that time was in GMT+0.
Any ideas how to insert this time in the database in GMT timezone?
Always UTC in Postgres
Understand that Postgres always stores values in a column of type TIMESTAMP WITH TIME ZONE in UTC, that is, with an offset from UTC of zero hours-minutes-seconds.
So the name is bit of a misnomer, in that no time zone is actually being stored. Instead, any time zone or offset indicator arriving with an input is used by Postgres to adjust to UTC. After adjustment, the UTC value is stored to the table while the zone/offset indicator is discarded. If you care about the original time zone, you must write that yourself to a second column.
What makes things confusing is tooling and middleware such as the psql console client app. These often are opinionated, choosing to inject some default time zone adjustment onto retrieved values. So while the Postgres database always retrieves a date-time in UTC from any TIMESTAMP WITH TIME ZONE column, you may see otherwise on your receiving end. Such a feature, while well-intentioned, is an unfortunate anti-feature to my mind.
Fortunately, I have not seen any JDBC driver that performs such an adjustment. You should always get a UTC value when retrieving an OffsetDateTime from a TIMESTAMP WITH TIME ZONE column (described below). But test your particular JDBC driver to be sure.
JDBC and the java.time classes
The modern solution for date-time handling in Java is the java.time classes.
To capture the current moment, use Instant. This class represents a moment as seen in UTC.
Instant instant = Instant.now() ; // Capture the current moment as seen in UTC.
However, despite Instant being the most basic and probably the most commonly used java.time class, the JDBC 4.2 team made the inexplicable decision to not require its support. (Nor is ZonedDateTime support required, by the way.)
Instead, the JDBC 4.2 spec requires support for OffsetDateTime. Fortunately, conversion between Instant and OffsetDateTime is trivial.
OffsetDateTime odt = instant.atOffset( ZoneOffset.UTC ) ;
Or you can skip the Instant class is this case.
OffsetDateTime odt = OffsetDateTime.now( ZoneOffset.UTC ) ;
Write to the database.
myPreparedStatement.setObject( … , odt ) ;
Retrieve from the database.
OffsetDateTime odt = myResultSet.getObject( … , OffsetDateTime.class ) ;
This OffsetDateTime object will carry an assigned offset from UTC of zero hours-minutes-seconds — commonly called just “UTC” as an abbreviation.
Extract an Instant if needed.
Instant instant = odt.toInstant() ;
Or adjust into a time zone.
ZoneId z = ZoneId.of( "Asia/Tokyo" ) ;
ZonedDateTime zdt = odt.atZoneSameInstant( z ) ;
A caution regarding possible data-loss: The resolution in java.time is nanoseconds. Capturing the current moment is likely limited to microseconds because today’s commonly-used hardware clocks go no finer. Nevertheless, a java.time object such as OffsetDateTime may carry nanoseconds. Meanwhile, Postgres date-time values have a resolution of microseconds. So if using a java.time object with any nanos present, writing that value with Postgres will truncate the nanos, resulting in a different value to be retrieved later.
I think paragraph 8.5.1.2 of the manual might be enlightening. It states that by default time is assumed to be without timezone and if one is given it is silently ignored.
In order to make things clear I think it is best to explicitely cast the time :
pti=> select timestamp with time zone '20100610T180000-5';
timestamptz
------------------------
2010-06-11 01:00:00+02
(1 row)
pti=> select timestamp with time zone '20100610T180000PST';
timestamptz
------------------------
2010-06-11 04:00:00+02
(1 row)
As is evident the time with time zone is properly converted from localtime to server time.
SELECT current_timestamp AT TIME ZONE 'gmt-5';

Categories

Resources