How can I best convert a java.util.Date to a Java 8 java.time.YearMonth?
Unfortunately the following throws a DateTimeException:
YearMonth yearMonth = YearMonth.from(date.toInstant());
results in:
java.time.DateTimeException: Unable to obtain YearMonth from TemporalAccessor: 2015-01-08T14:28:39.183Z of type java.time.Instant
at java.time.YearMonth.from(YearMonth.java:264)
...
I need this functionality since I want to store YearMonth values in a database using JPA. Currently JPA does not support YearMonth's, so I've come up with the following YearMonthConverter (imports omitted):
// TODO (future): delete when next version of JPA (i.e. Java 9?) supports YearMonth. See https://java.net/jira/browse/JPA_SPEC-63
#Converter(autoApply = true)
public class YearMonthConverter implements AttributeConverter<YearMonth, Date> {
#Override
public Date convertToDatabaseColumn(YearMonth attribute) {
// uses default zone since in the end only dates are needed
return attribute == null ? null : Date.from(attribute.atDay(1).atStartOfDay(ZoneId.systemDefault()).toInstant());
}
#Override
public YearMonth convertToEntityAttribute(Date dbData) {
// TODO: check if Date -> YearMonth can't be done in a better way
if (dbData == null) return null;
Calendar calendar = Calendar.getInstance();
calendar.setTime(dbData);
return YearMonth.of(calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH) + 1);
}
}
Isn't there a better (cleaner, shorter) solution (for both directions)?
Short answer:
// From Date to YearMonth
YearMonth yearMonth =
YearMonth.from(date.toInstant()
.atZone(ZoneId.systemDefault())
.toLocalDate());
// From YearMonth to Date
// The same as the OP:s answer
final Date convertedFromYearMonth =
Date.from(yearMonth.atDay(1).atStartOfDay(ZoneId.systemDefault()).toInstant());
Explanation:
The JavaDoc of the YearMonth.from(TemporalAccessor)-method says:
The conversion extracts the YEAR and MONTH_OF_YEAR fields. The extraction is only permitted if the temporal object has an ISO chronology, or can be converted to a LocalDate.
So, you need to either be able to:
extract the YEAR and MONTH_OF_YEAR fields, or
you should use something that can be converted to a LocalDate.
Lets try it!
final Date date = new Date();
final Instant instant = date.toInstant();
instant.get(ChronoField.YEAR); // causes an error
This is not possible, an exception is thrown:
java.time.temporal.UnsupportedTemporalTypeException: Unsupported field: Year
at java.time.Instant.get(Instant.java:571)
...
This means that alternative 1 goes out the window. The reason for is explained in this excellent answer about how to convert Date to LocalDate.
Despite its name, java.util.Date represents an instant on the time-line, not a "date". The actual data stored within the object is a long count of milliseconds since 1970-01-01T00:00Z (midnight at the start of 1970 GMT/UTC).
The equivalent class to java.util.Date in JSR-310 is Instant, thus there is a convenient method toInstant() to provide the conversion.
So, a Date can be converted to an Instant but that did not help us, did it?
Alternative 2 however proves to be successful. Convert the Instant to a LocalDate and then use the YearMonth.from(TemporalAccessor)-method.
Date date = new Date();
LocalDate localDate = date.toInstant()
.atZone(ZoneId.systemDefault())
.toLocalDate();
YearMonth yearMonth = YearMonth.from(localDate);
System.out.println("YearMonth: " + yearMonth);
The output is (since the code was executed in January 2015 ;):
YearMonth: 2015-01
Related
I just do not quite understand which one of those two I should use for the following example:
We have an OfferEntity which has a member availableDay which is the date at which the offer is available.
Now, the table will look something like this:
CREATE TABLE IF NOT EXISTS offer (
created timestamp with time zone NOT NULL DEFAULT NOW(),
id BIGSERIAL PRIMARY KEY,
available timestamp with time zone
);
From the PostgreSQL docs we know that:
For timestamp with time zone, the internally stored value is always in UTC (Universal Coordinated Time, traditionally known as Greenwich Mean Time, GMT). 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.
Which means I should be fine when it comes to persisting any date/time information.
But what does this mean for my OfferEntity and the REST endpoints I define in OfferController?
#Entity
#Table(name = "offer")
public class OfferEntity {
#Column(name = "available", nullable = false)
private ZonedDateTime availableDay;
}
vs
#Entity
#Table(name = "offer")
public class OfferEntity {
#Column(name = "available", nullable = false)
private Instant availableDay;
}
From what I understood - this should not make a difference. PostgreSQL stores everything as UTC anyway so I should be able to take Instant or ZonedDateTime right? Write something -> UTC. Read it again -> still UTC.
Even the client won't be able to tell the difference:
#RequestMapping(value = "/hello", method = RequestMethod.GET)
public Object hello() {
class Hello {
public Instant instant = Instant.now();
public ZonedDateTime zonedDateTime = ZonedDateTime.now();
public ZonedDateTime viennaTime = ZonedDateTime.now(ZoneId.of("GMT+2"));
public LocalDateTime localDateTime = LocalDateTime.now();
}
return new Hello();
}
Will return:
{
"instant": "2018-10-07T15:30:08.579Z",
"zonedDateTime": "2018-10-07T15:30:08.579Z",
"viennaTime": "2018-10-07T17:30:08.579+02:00",
"localDateTime": "2018-10-07T15:30:08.579",
}
But there must be a crucial difference which I am apparently not seeing.
There are two differences I can make out. It seems to be that Spring has no problem with converting "2018-10-07T15:30:08.579Z" to an Instant object, but fails to do so if I change the type to ZonedDateTime. At least out of the box.
#RequestMapping("/places/{placeId}/offers", method = RequestMethod.GET)
public List<OfferDto> getOffers(
#PathVariable(name = "placeId") Long placeId,
#RequestParam(name = "date") ZonedDateTime date) {
return this.offerService.getOffers(placeId, date);
}
The other difference is the fact that if I use Instant I am forcing my clients to convert all their date/time strings to UTC first. So any client will have to myDate.toUTCString() first. ZonedDateTime would take anything as long as it has a time zone set but why would we care?
So which of the two is the better choice and why would I chose one over the other?
The answer in the following link explains it better than I ever could. The answer goes into all the different date/time classes in Java, as well as their relation to sql types.
What's the difference between Instant and LocalDateTime?
A short summary:
The classes Instant and ZonedDateTime (as well as OffsetDateTime)
represent the same thing: a moment in time. The difference is that
ZonedDateTime and OffsetDateTime offer extra context and functionality
about timezones or time offsets, whereas Instant has no timezone or
offset specified. This can lead to differences especially when Daylight Saving Time is involved. For instance, take the following snippet of code:
ZonedDateTime z1 = zonedDateTime.of(LocalDateTime.of(2019,10,26,6,0,0),ZoneId.of("Europe/Amsterdam"));
Instant i1 = z1.plus(1,ChronoUnit.DAYS).toInstant();
Instant i2 = z1.toInstant().plus(1,ChronoUnit.DAYS);
System.out.println(i1);
System.out.println(i2);
The result will be this:
2019-10-27T05:00:00Z
2019-10-27T04:00:00Z
The difference stems from the fact that in the Amsterdam timezone, the 27th of October has an extra hour. When we convert to Instant the timezone information is lost, so adding a day will add only 24 hours.
LocalDateTime is a different beast alltogether. It represents a date
and time without timezone information. It does not represent a
moment in Time. It is useful for writing things such as "Christmas
morning starts at december 25th 00:00:00". This is true regardless of
timezone, and as such a ZonedDateTime or Instant would not be appropriate.
Java.util.date package is auto correcting date. For ex: if we pass date as "2018-02-35", it automatically changes it to "2018-03-07", which is a valid date.
Basically, the requirement is to validate the user entered date but as the date is getting auto corrected the module was never able to find an incorrect date. (Note: UI validation can't be done due to some special restrictions so the validation has to be done by the middleware system).
Is there a way i can handle this with the same util package or can this be handled through any 3rd party jar? pls advise
Even i faced same issue. But after some research i found that there is method (setLenient()) in the DateFormat class to disable this behaviour.
DateFormat df = new SimpleDateFormat(DATE_FORMAT);
df.setLenient(false);
Java Docs:
Specify whether or not date/time parsing is to be lenient. With lenient parsing, the parser may use heuristics to interpret inputs that do not precisely match this object's format. With strict parsing, inputs must match this object's format.
you can write DateDeserializer
public class DateDeserializer extends JsonDeserializer<Date> {
#Override
public Date deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd", getLocale());
format.setLenient(false); // if true, will auto correct the date
// to the next possible valid date
String date = jp.getText();
try {
return format.parse(date);
} catch (ParseException e) {
throw new RuntimeException(e);
}
}
private Locale getLocale() {
Locale locale = (LocaleContextHolder.getLocale() != null) ? LocaleContextHolder.getLocale() : Locale.getDefault();
return locale;
}
}
And then annotate your date property in the media class with this DateDeserializer. You can also use dateSerializer to serialize the object back to Json format. Example below
#JsonSerialize(using=DateSerializer.class)
#JsonDeserialize(using=DateDeserializer.class)
private Date startDate;
Problem: Native queries with Spring Data returning dates return java.sql.Date not java.time.LocalDate, despite the setup.
Context: A new project with Spring Boot 2.0.0.M5 (the latest), Hibernate 5.2.11, Hibernate-Java8 5.2.12 (which gives support for JSR310 classes as long as it's on the classpath).
Anonymized example below (the app is not really about birthdays):
public interface BirthdayRepository<T, ID extends Serializable> extends Repository<T, ID> {
#Query(value = "select day from birthdays", nativeQuery = true)
Iterable<java.sql.Date> getBirthdays(); //the return type should ideally be java.time.LocalDate
}
In the database (SQL Server), the day field is DATE and values are like 2017-10-24.
The problem is that at runtime, the Spring Data repository (whose implementation I cannot control, or is there a way?) returns java.sql.Date not java.time.LocalDate (Clarification: the return type appears to be decided by Spring Data and remains java.sql.Date even if I change the return type to be java.time.LocalDate, which is how I started to).
Isn't there a way to get LocalDate directly? I can convert it later, but (1) that's inefficient and (2) the repository methods have to return the old date/time classes, which is something I'd like to avoid. I read the Spring Data documentation, but there's nothing about this.
EDIT: for anyone having the same question, below is the solution, the converter suggested by Jens.
public class LocalDateTypeConverter {
#Converter(autoApply = true)
public static class LocalDateConverter implements AttributeConverter<LocalDate, Date> {
#Nullable
#Override
public Date convertToDatabaseColumn(LocalDate date) {
return date == null ? null : new Date(LocalDateToDateConverter.INSTANCE.convert(date).getTime());
}
#Nullable
#Override
public LocalDate convertToEntityAttribute(Date date) {
return date == null ? null : DateToLocalDateConverter.INSTANCE.convert(date);
}
}
It looks like you found a gap in the converters. Spring Data converts out of the box between java.util.Date and java.time.LocalDate but not between java.time.LocalDate and java.sql.Date and other date and time-related types in the java.sql package.
You can create your own converter to do that. You can use Jsr310JpaConverters as a template.
Also, you might want to create a feature request and if you build a converter for your use, you might even submit a pull request.
I know this is an older question, but my solution to this problem does not require a custom converter.
public interface BirthdayRepository<T, ID extends Serializable> extends Repository<T, ID> {
#Query(value = "select cast(day as date) from birthdays", nativeQuery = true)
Iterable<java.time.LocalDate> getBirthdays();
}
The CAST tells JPQL to use available java date\time types rather than java.sql.Date
Today I'm parsing epoch time to a String, like this:
private String convertEpochTime(Long timeInEpoch) {
return new SimpleDateFormat("MM/dd/yyyy HH:mm")
.format(new java.util.Date (timeInEpoch * 1000));
}
I want to use Java 8 new LocalDate API, instead of using a String, but didn't find a way of doing it.
I wish I just could do:
private LocalDate convertEpochTime(Long timeInEpoch) {
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("MM/dd/yyyy HH:mm").format(new java.util.Date (timeInEpoch * 1000));
return LocalDate.parse(dateTimeFormatter);
}
Is there any way of doing it?
You can use Instant.ofEpochSecond and then convert the instant to a LocalDate:
Instant instant = Instant.ofEpochSecond(timeInEpoch);
LocalDate localDate = instant.atZone(ZoneId.systemDefault()).toLocalDate();
or if you want to have time as well, convert to a LocalDateTime:
LocalDateTime localDateTime = instant.atZone(ZoneId.systemDefault()).toLocalDateTime();
Instead of ZoneId.systemDefault() you may need to use a different zone depending on your needs.
So there is no need to use a string as intermediary.
I am using the Play! framework, version 1. I have a form with 3 different select elements for day, month and year. I want to bind these to the birth date of a user (public Date birthDate defined in class User). How can I do this? Thanks.
You can create three setters getters in your class for day, month and year and update your date with these values. The best way to do that is to use joda date classes
public class MyClass {
public DateMidnight birthDate;
public int getBirthDateYear() {
return birthDate.getYear();
}
public void setBirthDateYear(int year) {
birthDate = birthDate.withYear(year);
}
}
and same thing with "monthOfYear" and "dayOfMonth"
I don't think it's worth fussing about with anything in the model, play can do this all in the controller, it's a bit of logic but should be no big deal in a smaller app. Assuming your select boxes POST numbers in your controller and you send other user stuff that mapped properly by name to user properties:
public static void save(User user, String day, String month, String year) {
DateFormat formatter = new SimpleDateFormat("MMddyy");
Date birthDate = formatter.parse(month + day + year);
user.birthDate = birthDate;
user.save();
}