I created my PostgreSQL table with a column:
updated_at timestamp(0) with time zone
I'm using clojure.java-time but I can't create a string (or should be an object?) to set the current date-time. I tried with:
(time/format "yyyy-MM-dd HH:ss" (time/local-date-time))
and with that string I tried:
(db/update-answer! {:updated_at "2019-12-25 14:08", :id 102, :answer "Foo"}
but the JDBC tells me that the field "updated_at" doesn't have a valid type. As far as I understand the JDBC is still using the old java API for handle the dates and time zones and not the new in JDK 8. So, in summary I don't know how to create a string or an object valid for Postgresql time-stamp column using clojure.java-time.
Someone advises me about extending the JDBC protocol, I'm reading about it but for now I'm just looking for a way to create a valid object and finish this update.
UPDATED
After followed this page I could give format to Instant with:
(ns zentaur.hiccup.helpers-view
(:require [clojure.tools.logging :as log]
[java-time :as jt])
(:import [java.time ZoneId]))
(defn format-date [date]
(log/info (str ">>> DATE >>>>> " date "und type >>> " (type date)))
(jt/format "yyyy-MM-dd HH:mm" (.atZone date (ZoneId/systemDefault))))
(Java syntax, not Clojure)
tl;dr
Use objects, not Strings. Use java.time classes in Java, never the legacy date-time classes.
myPreparedStatement
.setObject(
… ,
OffsetDateTime.now()
)
Even better would be a trigger to do this automatically.
in summary I don't know how to create … an object valid for Postgresql time-stamp column using clojure.java-time.
Call OffsetDateTime.now() to get a java.time.OffsetDateTime object holding the current moment.
Use a database column of type TIMESTAMP WITH TIME ZONE rather than TIMESTAMP (short for TIMESTAMP WITHOUT TIME ZONE).
Moments
set the current date-time
If you want to track moments, specific points on the timeline, you are using the wrong data type.
You are using TIMESTAMP which is short for TIMESTAMP WITHOUT TIME ZONE. This type lacks any concept of time zone or offset-from-UTC. So this type cannot track moments. I suggest you avoid the short version of this type name in the future, to make your SQL clear. See Postgres doc.
You should be defining your column as TIMESTAMP WITH TIME ZONE. Postgres handles this type by always saving the moment as seen in UTC. Any provided time zone or offset in the input is used to adjust to UTC. Likewise, when retrieved, your value is always in UTC, an offset of zero hours-minutes-seconds.
Beware of middleware and tools that dynamically apply some time zone to the retrieved value. This clouds matters, creating the illusion that the value was stored in that time zone. The value was actually stored in UTC, always in UTC in Postgres.
Smart objects, not dumb strings
but I can't create a string (or should be an object?) to set the current date-time.
Don’t.
You should be exchanging objects between Java and Postgres, rather than mere strings. This avoids the time-zone injection problem mentioned above.
As of JDBC 4.2, you can exchange modern java.time objects with the database. Never use the legacy types such as Calendar, GregorianCalendar, java.util.Date, java.sql.Date, java.sql.Timestamp, and so on. These classes were terribly flawed, designed by people who did not understand date-time handling. They were supplanted as of the adoption of JSR 310.
Capture the current moment as an OffsetDateTime. Your JDBC driver might support Instant & ZonedDateTime but those types are optional, as seen in graphic table above. In contrast, JDBC requires support for OffsetDateTime.
OffsetDateTime odt = OffsetDateTime.now( ZoneOffset.UTC ) ;
myPreparedStatement.setObject( … , odt ) ;
The explicit use of ZoneOffset.UTC in code above is not strictly required. If omitted, your JVM’s current default offset will be applied implicitly. Your JDBC driver and/or Postgres will adjust to UTC as discussed earlier above. My own preference for the sake of debugging/logging is to specify UTC so I can see the value as it will eventually be stored in the database.
And retrieval.
OffsetDateTime odt = myResultSet.getObject( … , OffsetDateTime.class ) ;
Default value
If you are simply recording the moment whenever a row is updated, no need to do that in your SQL. I suggest you write a trigger to be called whenever a row in that table is updated. Then you are guaranteed the value will be written no matter the mechanism by which the row was updated. And less to worry about when writing your SQL statements.
Related
The entities in my application have a lot of Instant fields. I don't care about time zones, everything is in UTC. I am using Postgresql 13.2. Hibernate 5 maps those fields in the CREATE TABLE statements to timestamp, which Postgresql interprets as "timestamp without time zone". This is the desired behavior.
However, after upgrading the app to use Hibernate 6, the fields in the CREATE TABLE statements are now "timestamp(6) with time zone". Liquibase then generates a diff with lot of false changes.
Is there a way to tell Hibernate 6 to continue mapping Instant fields to timestamp (without time zone)?
I tried setting the timezone of the postgresql server to "Etc/UTC", it did not help. Neither did setting
spring.jpa.properties.hibernate.jdbc.time_zone=UTC
in application.properties.
First: the correct data type to use to represent a timestamp in UTC in Postgres is timestamp with time zone, which does not actually store any time zone information, it just stores timestamps normalized to UTC. From the Postgres docs:
For timestamp with time zone, the internally stored value is always in UTC (Universal Coordinated Time...). An input value that has an explicit time zone specified is converted to UTC using the appropriate offset for that time zone. If no time zone is stated in the input string, then it is assumed to be in the time zone indicated by the system's TimeZone parameter, and is converted to UTC using the offset for the timezone zone.
On the other hand, the SQL type timestamp is more like a LocalDateTime in Java, it is not by nature a UTC datetime.
Second: if you don't like the SQL column type that Hibernate uses by default, then you can of course change it, either in the JPA standard way:
#Column(columnDefinition="timestamp")
or in the IMO much better native Hibernate way:
#JdbcTypeCode(TIMESTAMP)
or:
#JdbcType(TimestampJdbcType.class)
There is even an (incubating) global setting defined by AvailableSettings.PREFERRED_INSTANT_JDBC_TYPE:
hibernate.type.preferred_instant_jdbc_type=TIMESTAMP
However, I do not recommend you use any of these settings, since, as I said, the correct SQL type to use on Postgres is the one Hibernate uses by default.
The SpringBoot Query returns null while using TIMESTAMPTZ as the Datatype, but the Query works for other Datatypes like TIMESTAMP etc. My Date formats are like, "2022-07-24 10:11:29.452+00".
The DB screenshot is added below.
Also the date type is defined as follows
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "datem")
private Date datem;
The API calls the below code
Date start = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").parse("2022-07-24 10:11:29.452+00");
Date end = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").parse("2022-07-24 10:11:49.452+00");
List<MqttMessageParsed> sensor_data = messageParsedRepository.findByCreatedAtBetween(start, end);
The Query function is as follows
#Query("SELECT t FROM MqttMessageParsed t WHERE t.datem BETWEEN :startDate AND :endDate") List<MqttMessageParsed> findByCreatedAtBetween(#Param("startDate")Date start, #Param("endDate")Date end);
The API shoud return the data between the above start and end dates, but it is returning null now. Am i missing something?
Thanks
Avoid legacy classes
You are using terrible date-time classes that were years ago supplanted by the modern java.time classes. Avoid Date, SimpleDateFormat, and Timestamp.
java.time
For a column of a type akin to the SQL standard type TIMESTAMP WITH TIME ZONE, use the class OffsetDateTime in JDBC 4.2 and later.
OffsetDateTime odt = myResultSet.getObject( … , OffsetDateTime.class ) ;
Writing:
myPreparedStatement.setObject( … , odt);
Hibernate was years ago updated to support java.time. Ditto for Jakarta Persistence, formerly Java Persistence API (JPA).
ISO 8601
I suggest you educate the publisher of your inputs about the value of strict compliance with the ISO 8601 standard for date-time formats.
Replace SPACE in the middle with a T.
Use full offset with both hours and minutes, separated by a COLON character.
So this:
"2022-07-24 10:11:29.452+00"
… should be:
"2022-07-24T10:11:29.452+00:00"
… or alternatively use a Z as the suffix to indicate an offset of zero:
"2022-07-24T10:11:29.452Z"
If you cannot effect that change, then define a custom formatting pattern to parse that non-standard format. Use DateTimeFormatter class, as has been covered many times already on Stack Overflow.
By the way, know that the other data type, TIMESTAMP WITHOUT TIME ZONE cannot be used to record a moment, a specific point on the timeline. This type stores only a date and a time-of-day without the context of a time zone or offset-from-UTC. So, for example, given the date of last January 23rd at 12:00, we cannot know if that meant noon in Tokyo Japan, noon in Toulouse France, or noon in Toledo Ohio US — three different moments several hours apart.
If you need to track when something happened, always use TIMESTAMP WITH TIME ZONE. In the case of Postgres, any time zone or offset info supplied with an input is used to adjust to UTC (an offset of zero) and then discarded. The moment is always stored in UTC, in Postgres for this type. If you care about the original time zone, store that in a second column.
PostgreSQL 9.3 / postgresql-9.3-1100-jdbc41.jar
I have a table with a column of type timestamp without time zone, this generates my Object with the applicable java.util.Timestamp property.
What I'm seeing, during insert, is jOOQ's binding process converting a java.util.Timestamp into a date with local timezone offset.
eg for a unix timestamp 1421109419 (13 Jan 2015 00:36:59 GMT) the property is set with new Timestamp(1421109419 * 1000).
from the jOOQ logger I see:
2015-01-13 14:14:31,482 DEBUG [http-bio-8002-exec-4] org.jooq.tools.LoggerListener#debug:255 - -> with bind values : insert into "foo"."bar" ("start_date") values (timestamp '2015-01-13 13:36:59.0') returning "foo"."bar"."id"
2015-01-13 14:14:31,483 TRACE [http-bio-8002-exec-4] org.jooq.impl.DefaultBinding#trace:179 - Binding variable 3 : 2015-01-13 13:36:59.0 (class java.sql.Timestamp)
and sure enough in the record is the value "2015-01-13 13:36:59".
The software is running on a machine in NZDT which explains the +13 offset.
Given the time is being supplied in a TimeZone agnostic container (Timestamp) I would have expected that to be honoured when creating the insert statement.
How can I have jOOQ create timestamps NOT in local time?
Unfortunately you have a few things working against you:
The PostgreSQL JDBC driver sets the timezone to your JVM timezone in the Postgres session. So even if your Database Server is running in UTC a TIMESTAMP field will be inserted using the time zone of your JVM. When you insert or query data the database server will always use the JVM time zone.
You are using TIMESTAMP instead of TIMESTAMPTZ. The description of these types do not reflect their actually usage. TIMESTAMPTZ actually means time zone agnostic. Whatever value you insert it will be adjusted to UTC using the session timezone.
Because of these two issues, if you have two different JVMs -- one using Los Angeles time and the other using New York time -- whenever you write a TIMESTAMP with one JVM it will be a different "UTC time" in the other JVM. TIMESTAMP takes the adjusted value and just uses it as given. If you change your TIMESTAMP columns to be TIMESTAMPTZ then the same time in both JVMs will always be the same UTC time.
If you look at the Postgres JDBC Driver's ConnectionFactoryImpl#openConnectionImp you can see where it sets your local JVM's time zone as the time zone for the database server's session zone.
So the only sane way to deal with this is to only ever use TIMESTAMPTZ instead of TIMESTAMP. Here's some more information on this:
PostgreSQL/JDBC and TIMESTAMP vs. TIMESTAMPTZ
http://justatheory.com/computers/databases/postgresql/use-timestamptz.html
The following (very nasty) code works for me:
eventsRecord.setCreatedOn(new Timestamp(System.currentTimeMillis()
- TimeZone.getDefault().getOffset(new Date().getTime())));
Alas jOOQ simply uses the local timezone when saving into PostgreSQL "timestamp without timezone" or MySQL "datetime" fields. The source code evidence for this travesty is here, it does not specify the timezone nor have any facility for the user to override this functionality and specify a timezone. This renders usage of this very basic datatype from jOOQ completely useless, with a multitude of clients all with different timezones writing data to the same field without recording their timezone nor normalizing the data to UTC.
JDBC provides an extra three-argument setTimestamp where the user can specify what timezone is desired (UTC is basically the only value that makes sense). However jOOQ "abstracts" away from JDBC and does not offer this facility.
In our app we're storing datetimes that belong to many different timezones.
We decided to use the Joda LocalDateTime type - so that the user always gets literally whatever they entered in the first place. This is exactly what we need.
Internally we know which timezone the user belongs to - so when they enter a datetime we do a check like this:
dateTimeZone.isLocalDateTimeGap(localDateTime)
If that datetime does not exist in their timezone (it's in the daylight-savings gap) we display an error message that the date is not correct, thus preventing incorrect datetimes from being stored in the DB.
For storing we're using a timestamp column. Problems start when the user-entered datetime exists in their timezone but does not exist in the database timezone (Europe/Berlin). E.g. when I store LocalDateTime 2015-03-29 02:30:00 from the Europe/London timezone (this is valid - in London the gap is between 01:00 and 02:00), PostgreSQL shifts the hour by 1 and saves it as 2015-03-29 03:30:00.
What to do? Is there a way to tell PostgreSQL not do anything regarding timezones and just store datetimes literally as Joda represents them? (other than storing them as strings ;))
In PostgreSQL 7.3 and higher, timestamp is equivalent to timestamp without time zone. That data type is not time zone aware. It stores only a date and time. If you are finding it shifted, then it might be related to the code or tools you are using to store or retrieve the data.
Note that before version 7.3, timestamp was equivalent to timestamp with timezone. This is mentioned in the first note-box in the documentation here.
Postgres offers two date-time types per the SQL standard. The standard barely touches on the topic unfortunately, so the behavior described here is specific to Postgres. Other databases may behave differently.
TIMESTAMP WITHOUT TIME ZONEStores just a date and a time-of-day. Any time zone or offset-from-UTC passed is ignored.
TIMESTAMP WITH TIME ZONEFirst adjusts the passed date+time using its passed zone/offset to get a value in UTC. The passed zone/offset is then discarded after the adjustment is made; if needed, you must store that original zone/offset information in a separate column yourself.
Be aware that TIMESTAMP WITHOUT TIME ZONE does not represent an actual moment, does not store a point on the timeline. Without the context of a zone or offset, it has no real meaning. It represents a range of possible moments over a span of about 26-27 hours. Good for problems such as storing a appointment far enough out in the future that the time zone rules may be changed before its arrival. Also good for problems such as “Christmas starts after midnight on December 25 this year”, where you mean a different moment in time in each zone with each zone westward arriving later and later in succession.
When recording actual moments, specific points on the timeline, use TIMESTAMP WITH TIME ZONE.
The modern approach in Java uses the java.time classes rather than either the Joda-Time library or the troublesome old legacy date-time classes bundled with the earliest versions of Java.
TIMESTAMP WITHOUT TIME ZONE
For TIMESTAMP WITHOUT TIME ZONE, the equivalent class in java.time is LocalDateTime for a date and time-of-day without any offset or zone.
As others pointed out, some tools may dynamically apply a time zone to the retrieved value in a misguided and confusing albeit well-intentioned anti-feature. The following Java code will retrieve your true date-time value sans zone/offset.
Requires a JDBC driver compliant with JDBC 4.2 or later to directly work with java.time types.
LocalDateTime ldt = myResultSet.getObject( … , LocalDateTime.class ) ; // Retrieving a `TIMESTAMP WITHOUT TIME ZONE` value.
To insert/update database:
myPreparedStatement.setObject( … , ldt ) ; // Inserting/updating a `TIMESTAMP WITHOUT TIME ZONE` column.
TIMESTAMP WITH TIME ZONE
Your discussion of time zones suggests you are concerned with actual moments on the timeline. So you should absolutely be using TIMESTAMP WITH TIME ZONE instead of TIMESTAMP WITHOUT TIME ZONE. You should not be messing about with Daylight Saving Time (DST) gaps and such. Let java.time and Postgres do that work for you, with much better code already written and tested.
To retrieve:
Instant instant = myResultSet.getObject( … , Instant.class ) ; // Retrieving a `TIMESTAMP WITH TIME ZONE` value in UTC.
ZonedDateTime zdt = instant.atZone( ZoneId.of( "Africa/Tunis" ) ) ; // Adjusting from a UTC value to a specific time zone.
To insert/update database:
myPreparedStatement.setObject( … , zdt ) ; // Inserting/updating a `TIMESTAMP WITH TIME ZONE` column.
To retrieve from database:
Instant instant = myResultSet.getObject( … , Instant.class ) ;
E.g. when I store LocalDateTime 2015-03-29 02:30:00 from the Europe/London timezone
No, no, no. Do not work this way. You are misusing the types of both Java and Postgres.
If the user entered 2015-03-29 02:30:00 intended to represent a moment in Europe/London time zone, then parse as a LocalDateTime and immediately apply a ZoneId to get a ZonedDateTime.
To parse, replace the SPACE in the middle with a T to comply with ISO 8601 standard formatting used by default in the java.time classes.
String input = "2015-03-29 02:30:00".replace( " " , "T" ) ;
LocalDateTime ldt = LocalDateTime.parse( input ) ;
ZoneId z = ZoneId.of( "Europe/London" ) ;
ZonedDateTime zdt = ldt.atZone( z ) ;
To see that same moment in UTC, extract a Instant. The Instant class represents a moment on the timeline in UTC with a resolution of nanoseconds (up to nine (9) digits of a decimal fraction).
Instant instant = zdt.toInstant() ;
Pass the instant via JDBC for storage in the database in a TIMESTAMP WITH TIME ZONE.
myPreparedStatement.setObject( … , instant ) ;
Use objects, not strings
Note that all my code here is using java.time objects to exchange data with the database. Always use these objects rather than mere strings for exchanging date-time values.
About java.time
The java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date, Calendar, & SimpleDateFormat.
The Joda-Time project, now in maintenance mode, advises migration to the java.time classes.
To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.
Where to obtain the java.time classes?
Java SE 8, Java SE 9, and later
Built-in.
Part of the standard Java API with a bundled implementation.
Java 9 adds some minor features and fixes.
Java SE 6 and Java SE 7
Much of the java.time functionality is back-ported to Java 6 & 7 in ThreeTen-Backport.
Android
The ThreeTenABP project adapts ThreeTen-Backport (mentioned above) for Android specifically.
See How to use ThreeTenABP….
The ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval, YearWeek, YearQuarter, and more.
In a scala program, I receive from client side a specific date for instance:
2013-10-20T23:59:59.999Z
and I really want to keep this date when saving into DB and not convert to local, so this line:
debug("--sql timestamp: " + new Timestamp(reading.timestamp.getMillis()))
is printing out: 2013-10-21 02:59:59.999(I am in Romania).
Is there any way I can ignore timezone?
This is Timestamp.toString() behavior. java.sql.Timestamp extends java.util.Date and in its toString() method it uses, in particular, super.getHours(), which, according to javadoc, returns hours interpreted in local timezone - exactly as you observe.
However, internally Timestamp still holds correct timestamp value. There may be problems with storing it to the database, though. See this answer.
2013-10-20T23:59:59.999Z and 2013-10-21 02:59:59.999 are actually the same time: 2013-10-20T23:59:59.999Z is in the UTC time zone (Z), whereas the second one is relative, and expressed as your local time zone (UTC+3 then in Romania).
In PostgreSQL, you should store your timestamps as TIMESTAMP WITH TIME ZONE (TIMESTAMPTZ) in your database to handle this. You'll always be able to print it out later in the time zone you choose then (e.g. UTC). (You might be interested in this recent question to understand why the storage type matters.)
If you want to print out the timestamp in the UTC/Z time zone again, new DateTime(millis, DateTimeZone.UTC) should help (with Joda Time).