I'm unsure how YAML "timestamps" are supposed to be represented. From the YAML 1.2 specification, it would seem that YAML just assumes that if you have a value in string format that looks like an ISO 8601 date, then it is parsed as a date unless you say differently. Here are a couple of examples from the spec:
date: 2002-12-14
not-date: !!str 2002-04-28
The Timestamp Language-Independent Type for YAML™ Version 1.1 working draft (from 2005, currently 15 years ago!) seems to indicate that a special tag in the form tag:yaml.org,2002:timestamp should be used. It also indicates a shorthand of !!timestamp.
In Java using SnakeYAML Engine v2.0 (org.snakeyaml:snakeyaml-engine:2.0) I tried parsing the 2002-12-14 form, and got a string as the parsed value, not any sort of date object. I see that the SnakeYAML Engine repository has an example using the !!timestamp approach (e.g. !!timestamp 2020-03-24T12:34:00.333), but this is a recent change and I'm sure if this support has been released yet.
I tried both the form fooBar: !!timestamp 2020-03-24 and also fooBar: !!timestamp 2020-03-24T12:34:00.333, but SnakeYAML Engine reported:
could not determine a constructor for the tag tag:yaml.org,2002:timestamp
So what is the official way to represent a date (specifically a local date with YYYY-MM-DD) in YAML, and is the correct approach reflected in the latest YAML specification? Does SnakeYAML Engine support the official YAML date approach?
From the YAML 1.2 specification, it would seem that YAML just assumes that if you have a value in string format that looks like an ISO 8601 date, then it is parsed as a date unless you say differently.
No. The YAML spec gives three schemas (Failsafe, JSON and Core) that should be supported; none of them includes a timestamp type. However, a scalar looking like a timestamp can be parsed as such if a schema is used that supports it. The spec only tells you that if you want to ensure that a scalar is not loaded as timestamp, prefix it with !!str.
So what is the official way to represent a date (specifically a local date with YYYY-MM-DD) in YAML.
The !!timestamp definition you linked is closest to what an official way would be. However, the tag repository containing it is not part of the spec and implementations are not required to support it. Furthermore, it is defined for outdated YAML 1.1.
This means that SnakeYAML isn't required to support timestamps at all. You can see in the example you give that timestamp support is not included; the example implements loading timestamps itself. You can modify that code to use with the normal public interface:
class TimestampConstructor extends Constructor {
public static final Pattern TIMESTAMP = Pattern
.compile("^(?:[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]|[0-9][0-9][0-9][0-9]-[0-9][0-9]?-[0-9][0-9]?(?:[Tt]|[ \t]+)[0-9][0-9]?:[0-9][0-9]:[0-9][0-9](?:\\.[0-9]*)?(?:[ \t]*(?:Z|[-+][0-9][0-9]?(?::[0-9][0-9])?))?)$");
public static final Tag TAG = new Tag(Tag.PREFIX + "timestamp");
public TimestampConstructor() {
this.yamlConstructors.put(TAG, new ConstructTimestamp());
}
private class ConstructTimestamp extends AbstractConstruct {
public Object construct(Node node) {
String val = (String) constructScalar(node);
return LocalDateTime.parse(val);
}
}
}
Then, use it like this:
Yaml yaml = new Yaml(new TimestampConstructor());
yaml.addImplicitResolver(TimestampConstructor.TAG,
TimestampConstructor.PATTERN, "0123456789");
I'm working on Spring Boot application and I have a date in JSON request with timezone offset (1968-07-21T23:00:00.000+02:00) and its getting mapped to a LocalDateTime object which is part of a wrapper object without any issue. But I want the date to get adjusted to UTC dynamically while it gets mapped to this request object in controller. So after I get the request wrapper object in controller the value of this date should be "1968-07-22T01:00:00.000+00:00", with date and hours adjusted automatically.
I have tried several things for this but nothing worked, few of the things what I tried so far are as below,
Approach 1 : Since I'm using Jackson 2, I annotated the DTO date field as below
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX", timezone="UTC")
Approach 2 : I tried customizing Jackson2ObjectMapperBuilderCustomizer as below.
#Bean
public Jackson2ObjectMapperBuilderCustomizer init() {
return new Jackson2ObjectMapperBuilderCustomizer() {
#Override
public void customize(Jackson2ObjectMapperBuilder builder) {
builder.timeZone(TimeZone.getTimeZone(ZoneOffset.UTC));
}
};
}
But both above didn't work for me. Please let me know if there is any other way to achieve this. Thanks in advance.
LocalDateTime is a DateTime field without timezone.
From Documentation:
A date-time without a time-zone in the ISO-8601 calendar system, such as 2007-12-03T10:15:30.
Use ZonedDateTime instead.
date-time with a time-zone in the ISO-8601 calendar system, such as 2007-12-03T10:15:30+01:00 Europe/Paris.
javadoc
Use the java.text class to do the job.
for example
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX");
String date = format.format(new Date());
And then send it to your usecase.
How can I configure JPA/Hibernate to store a date/time in the database as UTC (GMT) time zone? Consider this annotated JPA entity:
public class Event {
#Id
public int id;
#Temporal(TemporalType.TIMESTAMP)
public java.util.Date date;
}
If the date is 2008-Feb-03 9:30am Pacific Standard Time (PST), then I want the UTC time of 2008-Feb-03 5:30pm stored in the database. Likewise, when the date is retrieved from the database, I want it interpreted as UTC. So in this case 530pm is 530pm UTC. When it's displayed it will be formatted as 9:30am PST.
Since Hibernate 5.2, you can now force the UTC time zone by adding the following configuration property into the properties.xml JPA configuration file:
<property name="hibernate.jdbc.time_zone" value="UTC"/>
If you're using Spring Boot, then add this property to your application.properties file:
spring.jpa.properties.hibernate.jdbc.time_zone=UTC
To the best of my knowledge, you need to put your entire Java app in UTC timezone (so that Hibernate will store dates in UTC), and you'll need to convert to whatever timezone desired when you display stuff (at least we do it this way).
At startup, we do:
TimeZone.setDefault(TimeZone.getTimeZone("Etc/UTC"));
And set the desired timezone to the DateFormat:
fmt.setTimeZone(TimeZone.getTimeZone("Europe/Budapest"))
Hibernate is ignorant of time zone stuff in Dates (because there isn't any), but it's actually the JDBC layer that's causing problems. ResultSet.getTimestamp and PreparedStatement.setTimestamp both say in their docs that they transform dates to/from the current JVM timezone by default when reading and writing from/to the database.
I came up with a solution to this in Hibernate 3.5 by subclassing org.hibernate.type.TimestampType that forces these JDBC methods to use UTC instead of the local time zone:
public class UtcTimestampType extends TimestampType {
private static final long serialVersionUID = 8088663383676984635L;
private static final TimeZone UTC = TimeZone.getTimeZone("UTC");
#Override
public Object get(ResultSet rs, String name) throws SQLException {
return rs.getTimestamp(name, Calendar.getInstance(UTC));
}
#Override
public void set(PreparedStatement st, Object value, int index) throws SQLException {
Timestamp ts;
if(value instanceof Timestamp) {
ts = (Timestamp) value;
} else {
ts = new Timestamp(((java.util.Date) value).getTime());
}
st.setTimestamp(index, ts, Calendar.getInstance(UTC));
}
}
The same thing should be done to fix TimeType and DateType if you use those types. The downside is you'll have to manually specify that these types are to be used instead of the defaults on every Date field in your POJOs (and also breaks pure JPA compatibility), unless someone knows of a more general override method.
UPDATE: Hibernate 3.6 has changed the types API. In 3.6, I wrote a class UtcTimestampTypeDescriptor to implement this.
public class UtcTimestampTypeDescriptor extends TimestampTypeDescriptor {
public static final UtcTimestampTypeDescriptor INSTANCE = new UtcTimestampTypeDescriptor();
private static final TimeZone UTC = TimeZone.getTimeZone("UTC");
public <X> ValueBinder<X> getBinder(final JavaTypeDescriptor<X> javaTypeDescriptor) {
return new BasicBinder<X>( javaTypeDescriptor, this ) {
#Override
protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) throws SQLException {
st.setTimestamp( index, javaTypeDescriptor.unwrap( value, Timestamp.class, options ), Calendar.getInstance(UTC) );
}
};
}
public <X> ValueExtractor<X> getExtractor(final JavaTypeDescriptor<X> javaTypeDescriptor) {
return new BasicExtractor<X>( javaTypeDescriptor, this ) {
#Override
protected X doExtract(ResultSet rs, String name, WrapperOptions options) throws SQLException {
return javaTypeDescriptor.wrap( rs.getTimestamp( name, Calendar.getInstance(UTC) ), options );
}
};
}
}
Now when the app starts, if you set TimestampTypeDescriptor.INSTANCE to an instance of UtcTimestampTypeDescriptor, all timestamps will be stored and treated as being in UTC without having to change the annotations on POJOs. [I haven't tested this yet]
With Spring Boot JPA, use the below code in your application.properties file and obviously you can modify timezone to your choice
spring.jpa.properties.hibernate.jdbc.time_zone = UTC
Then in your Entity class file,
#Column
private LocalDateTime created;
Adding an answer that's completely based on and indebted to divestoclimb with a hint from Shaun Stone. Just wanted to spell it out in detail since it's a common problem and the solution is a bit confusing.
This is using Hibernate 4.1.4.Final, though I suspect anything after 3.6 will work.
First, create divestoclimb's UtcTimestampTypeDescriptor
public class UtcTimestampTypeDescriptor extends TimestampTypeDescriptor {
public static final UtcTimestampTypeDescriptor INSTANCE = new UtcTimestampTypeDescriptor();
private static final TimeZone UTC = TimeZone.getTimeZone("UTC");
public <X> ValueBinder<X> getBinder(final JavaTypeDescriptor<X> javaTypeDescriptor) {
return new BasicBinder<X>( javaTypeDescriptor, this ) {
#Override
protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) throws SQLException {
st.setTimestamp( index, javaTypeDescriptor.unwrap( value, Timestamp.class, options ), Calendar.getInstance(UTC) );
}
};
}
public <X> ValueExtractor<X> getExtractor(final JavaTypeDescriptor<X> javaTypeDescriptor) {
return new BasicExtractor<X>( javaTypeDescriptor, this ) {
#Override
protected X doExtract(ResultSet rs, String name, WrapperOptions options) throws SQLException {
return javaTypeDescriptor.wrap( rs.getTimestamp( name, Calendar.getInstance(UTC) ), options );
}
};
}
}
Then create UtcTimestampType, which uses UtcTimestampTypeDescriptor instead of TimestampTypeDescriptor as the SqlTypeDescriptor in the super constructor call but otherwise delegates everything to TimestampType:
public class UtcTimestampType
extends AbstractSingleColumnStandardBasicType<Date>
implements VersionType<Date>, LiteralType<Date> {
public static final UtcTimestampType INSTANCE = new UtcTimestampType();
public UtcTimestampType() {
super( UtcTimestampTypeDescriptor.INSTANCE, JdbcTimestampTypeDescriptor.INSTANCE );
}
public String getName() {
return TimestampType.INSTANCE.getName();
}
#Override
public String[] getRegistrationKeys() {
return TimestampType.INSTANCE.getRegistrationKeys();
}
public Date next(Date current, SessionImplementor session) {
return TimestampType.INSTANCE.next(current, session);
}
public Date seed(SessionImplementor session) {
return TimestampType.INSTANCE.seed(session);
}
public Comparator<Date> getComparator() {
return TimestampType.INSTANCE.getComparator();
}
public String objectToSQLString(Date value, Dialect dialect) throws Exception {
return TimestampType.INSTANCE.objectToSQLString(value, dialect);
}
public Date fromStringValue(String xml) throws HibernateException {
return TimestampType.INSTANCE.fromStringValue(xml);
}
}
Finally, when you initialize your Hibernate configuration, register UtcTimestampType as a type override:
configuration.registerTypeOverride(new UtcTimestampType());
Now timestamps shouldn't be concerned with the JVM's time zone on their way to and from the database. HTH.
You would think this common problem would be taken care of by Hibernate. But its not! There are a few "hacks" to get it right.
The one I use is to store the Date as a Long in the database. So I am always working with milliseconds after 1/1/70. I then have getters and setters on my Class that return/accept only Dates. So the API remains the same. The down side is that I have longs in the database. SO with SQL I can pretty much only do <,>,= comparisons -- not fancy date operators.
Another approach is to user a custom mapping type as described here:
http://www.hibernate.org/100.html
I think the correct way to deal with this is to use a Calendar instead of a Date though. With the Calendar you can set the TimeZone before persisting.
NOTE: Silly stackoverflow won't let me comment, so here is a response to david a.
If you create this object in Chicago:
new Date(0);
Hibernate persists it as "12/31/1969 18:00:00". Dates should be devoid of timezone, so I'm not sure why the adjustment would be made.
There are several timezones in operation here:
Java's Date classes (util and sql), which have implicit timezones
of UTC
The timezone your JVM is running in, and
the default timezone of your database server.
All of these can be different. Hibernate/JPA has a severe design deficiency in that a user cannot easily ensure that timezone information is preserved in the database server (which allows reconstruction of correct times and dates in the JVM).
Without the ability to (easily) store timezone using JPA/Hibernate then information is lost and once information is lost it becomes expensive to construct it (if at all possible).
I would argue that it is better to always store timezone information (should be the default) and users should then have the optional ability to optimize the timezone away (although it only really affects display, there is still an implicit timezone in any date).
Sorry, this post doesn't provide a work-around (that's been answered elsewhere) but it is a rationalization of why always storing timezone information around is important. Unfortunately it seems many Computer Scientists and programming practitioners argue against the need for timezones simply because they don't appreciate the "loss of information" perspective and how that makes things like internationalization very difficult - which is very important these days with web sites accessible by clients and people in your organization as they move around the world.
Please take a look at my project on Sourceforge which has user types for standard SQL Date and Time types as well as JSR 310 and Joda Time. All of the types try to address the offsetting issue. See http://sourceforge.net/projects/usertype/
EDIT: In response to Derek Mahar's question attached to this comment:
"Chris, do your user types work with Hibernate 3 or greater? – Derek Mahar Nov 7 '10 at 12:30"
Yes these types support Hibernate 3.x versions including Hibernate 3.6.
Date is not in any time zone (it is a millisecond office from a defined moment in time same for everyone), but underlying (R)DBs generally store timestamps in political format (year, month, day, hour, minute, second, ...) that is time-zone sensitive.
To be serious, Hibernate MUST be allow being told within some form of mapping that the DB date is in such-and-such timezone so that when it loads or stores it it does not assume its own...
I encountered just the same problem when I wanted to store the dates in the DB as UTC and avoid using varchar and explicit String <-> java.util.Date conversions, or setting my whole Java app in the UTC time zone (because this could lead to another unexpected issues, if the JVM is shared across many applications).
So, there is an open source project DbAssist, which allows you to easily fix the read/write as UTC date from the database. Since you are using JPA Annotations to map the fields in the entity, all you have to do is to include the following dependency to your Maven pom file:
<dependency>
<groupId>com.montrosesoftware</groupId>
<artifactId>DbAssist-5.2.2</artifactId>
<version>1.0-RELEASE</version>
</dependency>
Then you apply the fix (for Hibernate + Spring Boot example) by adding #EnableAutoConfiguration annotation before the Spring application class. For other setups installation instructions and more use examples, just refer to the project's github.
The good thing is that you don't have to modify the entities at all; you can leave their java.util.Date fields as they are.
5.2.2 has to correspond to the Hibernate version you are using. I am not sure, which version you are using in your project, but the full list of provided fixes is available on the wiki page of the project's github. The reason why the fix is different for various Hibernate versions is because Hibernate creators changed the API a couple of times between the releases.
Internally, the fix uses hints from divestoclimb, Shane and a few other sources in order to create a custom UtcDateType. Then it maps the standard java.util.Date with the custom UtcDateType which handles all the necessary time zone handling.
The mapping of the types is achieved using #Typedef annotation in the provided package-info.java file.
#TypeDef(name = "UtcDateType", defaultForType = Date.class, typeClass = UtcDateType.class),
package com.montrosesoftware.dbassist.types;
You can find an article here which explains why such a time shift occurs at all and what are the approaches to solve it.
Hibernate does not allow for specifying time zones by annotation or any other means. If you use Calendar instead of date, you can implement a workaround using HIbernate property AccessType and implementing the mapping yourself. The more advanced solution is to implement a custom UserType to map your Date or Calendar. Both solutions are explained in my blog post here: http://www.joobik.com/2010/11/mapping-dates-and-time-zones-with.html
Here is the complete checklist for storing dates in your database in a proper way:
first, make sure your OS timezone is st properly: either set TZ env variable in your Dockerfile or start your Java application with -Duser.timezone=“UTC”.
As mentioned earlier: also set the timezone for JPA with hibernate.jdbc.time_zone = UTC
Use OffsetDateTime for storing dates as UTC in your entities. Don’t use Date, Calendar & ZonedDateTime !
I have such string:
{
"debug":"false",
"switchTime":"2017-04-12 17:04:42.896026"
}
I'm trying to get object in such approach:
new ObjectMapper().readValue(string, MyObject.class);
And MyObject class:
class MyObject {
private Boolean debug;
private Timestamp switchTime;
//...getters, setters, constructors
}
I have such exception:
com.fasterxml.jackson.databind.exc.InvalidFormatException:
Can not deserialize value of type java.sql.Timestamp from String
"2017-04-12 17:04:42.896026": not a valid representation (error:
Failed to parse Date value '2017-04-12 17:04:42.896026':
Can not parse date "2017-04-12 17:04:42.896026Z": while it seems
to fit format 'yyyy-MM-dd'T'HH:mm:ss.SSS'Z'',
parsing fails (leniency? null)) at [Source:
{"debug":"false", "switchTime":"2017-04-12 17:04:42.896026"};
I don't understand why...If i use in debug mode Timestamp.valueOf() with "2017-04-12 17:04:42.896026" - i have success
I think you need to set the expected date/time format using #JsonFormat annotation as shown below.
class MyObject {
private Boolean debug;
#JsonFormat(pattern="yyyy-MM-dd HH:mm:ss.SSS")
private Timestamp switchTime;
//...getters, setters, constructors
}
You can also set timezone as #JsonFormat(pattern="yyyy-MM-dd HH:mm:ss.SSS",timezone="PST")
I faced the similar problem when I was using lombok in the POJO class which has #Builder and #Value annotations
I have also added the annotation AllArgsConstructor and then it is working fine with my custom deserializer code
The value that you see in debug mode is "toString()" version of actual value of timestamp, so don't rely on what you inspect in debug mode.
You can use #JsonFormat annotation that helps you to convert your timestamp with specified format. You need to take care of timezones also while converting!
In my Spring Web MVC application, I have a bunch of methods in #Controllers that accept a Date as an input parameter with #RequestParam. Without defining any custom data binders or property editors (I admit I'm still not clear on the difference between those two), what date formats are supported by default? For example, I've noticed that something like '11/12/2012 16:50 PM' works fine, but a plain milis value like '1352815200000' is rejected.
Edit: the specific exception I get is: "Failed to convert value of type 'java.lang.String' to required type 'java.util.Date'; nested exception is java.lang.IllegalStateException: Cannot convert value of type [java.lang.String] to required type [java.util.Date]: no matching editors or conversion strategy found"
I believe that if no converter is specified, Spring convertion system will end up calling the deprecated Date constructor, that takes String as an argument.
/**
* Allocates a <code>Date</code> object and initializes it so that
* it represents the date and time indicated by the string
* <code>s</code>, which is interpreted as if by the
* {#link Date#parse} method.
*
* #param s a string representation of the date.
* #see java.text.DateFormat
* #see java.util.Date#parse(java.lang.String)
* #deprecated As of JDK version 1.1,
* replaced by <code>DateFormat.parse(String s)</code>.
*/
#Deprecated
public Date(String s) {
this(parse(s));
}
usually the applications have different requirements - some work with the local time(also browser specific things and user time zone settings may come into play), some don't need to store the date - so it's better to implement a policy for date conversion both on client and server side.
P.S. please note that Date.parse() is also deprecated.
The allowed date formats probably depend on your current Locale.
You can add a Handler that converts the date for the formats you like.
I'm not really sure which one was it. #InitBinder shows an example of converting a date from whatever format..
hope this works for you.
Create a class Register an editor for your date formate.
import org.springframework.beans.PropertyEditorRegistrar
import org.springframework.beans.PropertyEditorRegistry
import org.springframework.beans.propertyeditors.CustomDateEditor
import java.text.SimpleDateFormat
public class CustomDateEditorRegistrar implements PropertyEditorRegistrar {
public void registerCustomEditors(PropertyEditorRegistry registry) {
String dateFormat = 'yyyy/MM/dd'
registry.registerCustomEditor(Date, new CustomDateEditor(new SimpleDateFormat(dateFormat), true))
}
}