Similar to this question. I have an interface DateRangeModel:
I use this to automatically validate dates in implementers:
public interface DateRangeModel {
#ApiModelProperty(value = "From date. Must be less than to date.")
#Column()
Date getFromDate();
void setFromDate(Date date);
#ApiModelProperty(value = "To date. Must be greater than to date.")
#Column()
Date getToDate();
void setToDate(Date date);
/**
* Checks that if both dates are populated, a valid date range is used.
*
* #return true if the date is a valid range.
*/
#AssertTrue(message = "To date must be greater than or equal to from date.")
#JsonIgnore
default boolean areDatesValid() {
if (getToDate() != null && getFromDate() != null) {
return !getFromDate().after(getToDate());
}
return true;
}
}
I implement it like this:
#EqualsAndHashCode
#Data
#Builder
public class BirthdayParty implements DateRangeModel {
Date fromDate;
Date toDate;
String name;
}
Which compiles and seems to work, but I get that error when running PMD:
Returning a reference to a mutable object value stored in one of the object's fields exposes the internal representation of the object.
How can I either accomplish what I want (an interface with to/from date validation) without having to implement the setDate methods in all implementers (which I think would defeat the purpose)?
The problem is that java.util.Date is mutable and you return it in your getters. So someone could do this:
BirthdayParty party = ...;
Date fromDate = party.getFromDate();
...
Date someDate = fromDate;
...
// you might not be aware that this also changes the fromDate in party
someDate.setTime(12345678);
There are four things you can do:
Disable the PMD rule.
Suppress the warning everywhere you use one of these a classes.
Don't use Lombok and copy the dates in your setters and getters instead of just storing/returning the reference to a date.
Use java.time.ZonedDateTime (or LocalDateTime) instead of Date. ZonedDateTime is immutable and should not lead to this warning.
I suggest the fourth option. Not only because it gets rid of the PMD warning but also because the new time API in Java 8 is so much better and easier to use than java.util.Date.
Related
I have the transaction class where I have variables like Arrival date, departure date, I am not sure how to define the date data types, so that transaction info must be sorted on basis of start date. I want to give the date in an explicit manner to the class. For example, I want to give 5 dates and sort them. Can you please let me know, how to do that.
public class Transaction implements Comparable<Transaction>
{
private Dog dogID;
private Date startDate;
private Date departureDate;
private List<Integer> serviceLevel = Arrays.asList(1,2,3);
private List<Integer> ratePerDay = Arrays.asList(89,129,149);
private double totalcharge;
private double deposit;
}
tl;dr
Use only java.time classes, never legacy Date class.
Implement the one method required by Comparable interface.
#Override
public int compareTo( Transaction other )
{
return this.startDate().compareTo( other.startDate()
}
Details
Both Date classes included with Java are terrible, badly designed by people who did not understand date-time handling.
If you want a date only, without time of day, and without the context of a time zone or offset-from-UTC, use java.time.LocalDate.
private LocalDate startDate;
private LocalDate departureDate;
If the main purpose of your class is to communicate data transparently and immutably, define the class as a record. The compiler implicitly creates the constructor, getters, equals & hashCode, and toString.
And never use a floating-point type like double where accuracy matters such as moment. Use BigDecimal where accuracy trumps speed.
A list should generally be named in the plural, for clarity.
The Java naming convention is camelCase. So totalCharge.
public record Transaction
(
Dog dogID ,
LocalDate startDate ,
LocalDate departureDate ,
List<Integer> serviceLevels ,
List<BigDecimal> ratesPerDay ,
BigDecimal totalCharge ,
BigDecimal deposit
) {}
If you want to usually sort by the start date member field, implement the Comparable interface with its one required method, compareTo. We implement that method by calling upon the compareTo method built into the LocalDate class.
public record Transaction implements Comparable < Transaction >
(
Dog dogID ,
LocalDate startDate ,
LocalDate departureDate ,
List<Integer> serviceLevels ,
List<BigDecimal> ratesPerDay ,
BigDecimal totalCharge ,
BigDecimal deposit
)
{
#Override
public int compareTo( Transaction other )
{
return this.startDate().compareTo( other.startDate()
}
}
I have class
public class Order {
private LocalDateTime creationDate;
private Date creationDateAsDate;
}
The creationDate is an existing field in the Order.
The creationDateAsDate is a new field for some functionality.
I need to set and save this new field in all places where I save the old field.
Unfortunately, I am working with legacy code, and I have 5 places where creationDate field is set.
Now I see 2 solutions:
call creationDateAsDate setter in all places where creationDate setter is called.
call creationDateAsDate setter in the creationDate setter.
I don't like both variants. Can someone answer how to do this? It might be one of my variants, or something else.
I would recommend updating both setters which is no wrong (that's the reason they exist) calling a common private method to avoid infinite recursion.
public void setCreationDate(LocalDateTime creationDate) {
Date creationDateAsDate = Date.from(creationDate.atZone(ZoneId.systemDefault())
.toInstant());
updateDates(creationDate, creationDateAsDate);
}
public void setCreationDateAsDate(Date creationDateAsDate) {
LocalDateTime creationDate = LocalDateTime.ofInstant(
creationDateAsDate.toInstant(),
ZoneId.systemDefault());
updateDates(creationDate, creationDateAsDate);
}
private void updateDates(LocalDateTime creationDate, Date creationDateAsDate) {
this.creationDate = creationDate;
this.creationDateAsDate = creationDateAsDate;
}
Since these are private variables remove one of the variables for example private Date creationDateAsDate and the setter for the vaiable.
Implement getter method as follows.
public Date getCreationDateAsDate(){
// convert the creationDate to date and return.
}
REF: Converting between java.time.LocalDateTime and java.util.Date for converting LocalDateTime to Date
You could add a third method (e.g. setCreationDateAsDateAndcreationDate) and call it whenever you want both to be updated. (as classes should be open for extension, but closed for modification).
Then put both setters in it,
or
you could call the new setter method from setCreationDateAsDate and setCreationDate (where you create the other property within setters, and then call the third setter setCreationDateAsDateAndcreationDate while passing both of dates as arguments to it)
We have a case, where we require only the day and the month and thus would use the java.time.MonthDay (Javadocs) to represent that information.
We are aware that we could create our own JPA object for persistence or just use the java.sql.Date object, but that generally requires an unrequired year information.
Another way is to call the method .atYear(int) (Javadoc) (with a fictitious year) on it and receive a java.time.LocalDate (Javadoc), which can be easily converted to java.sql.Date. But this is prone to missunderstandings in the future (and also persist the year information).
Is there some "elegant"/supposed solution for this case? Or is there a replacement for SQL that supports the whole new date and time API for Persistence.
Another case would be java.time.YearMonth (Javadoc).
Thanks for reading!
Since SQL databases don't have a type compatible with MonthDay, use a VARCHAR columns, and simply use toString() and MonthDay.parse().
Or use a custom DateTimeFormatter, if you don't like the --12-03 format.
The default format will correctly sort, as a string.
here are the code snippets:
// column define:
#Column(name = "date", columnDefinition = "mediumint", nullable = false)
#Convert(converter = MonthDayIntegerAttributeConverter.class)
protected MonthDay date;
// converter define:
public class MonthDayIntegerAttributeConverter implements AttributeConverter<MonthDay, Integer> {
#Override
public Integer convertToDatabaseColumn(MonthDay attribute) {
return (attribute.getMonthValue() * 100) + attribute.getDayOfMonth();
}
#Override
public MonthDay convertToEntityAttribute(Integer dbData) {
int month = dbData / 100;
int day = dbData % 100;
return MonthDay.of(month, day);
}
}
How can I persist a jodatime YearMonth object to a sql database?
#Entity
public class MyEntity {
private YearMonth date; //how to persist?
//some more properties
}
I want later to be able to pick all entities by a specific month.
The better question is how do you intend to use the data in the database? Generally you want to store dates in databases using data types supported by the target database as opposed to strings or composite integers. That would allow you to use the built in database date/time functions and validations as opposed to needing to implement that logic in your application.
You can certainly add methods to your entity to convert to/from jodatime values to your entity for the parts of your application that need it.
You need a javax.persistence.Converter for this (see How to convert java.util.Date to Java8 java.time.YearMonth):
#Entity
public class MyEntity {
#Convert(converter = YearMonthConverter.class)
private YearMonth date; //how to persist?
//some more properties
}
The following is an example converter for Java8 java.time.YearMonth to java.utilDate. You need to change java.time.YearMonth to org.joda.time.YearMonth and use the appropriate methods to convert to/from the desired type:
(imports omitted):
#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);
}
}
I'm generating one date and saving in a database through hibernate, and when I get the value and I compare with the value before it was inserted. The result is not equal!
I created the date as following
Date rightnow = Calendar.getInstance().getTime();
Task t1 = new Task("My task", rightnow);
taskDao.saveOrUpdate(t1);
Task taskR1 = taskDao.get(t1.getIdTask());
assertEquals("They should have to be equal dates",taskR1.getDate(),t1.getDate());
I'm getting this error
<2014-04-11 23:13:13.0> is different to <Fri Apr 11 23:13:13 CEST 2014>
java.lang.AssertionError:
They should have to be equal dates
expected:<2014-04-11 23:13:13.0>
but was:<Fri Apr 11 23:13:13 CEST 2014>
Extra info related with the problem
Class Task
#Entity
#Table(name = "t_task")
public class Task {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "idTask")
private long idTask;
...
#Column(name = "date")
private Date date;
...
Mysql table t_task
CREATE TABLE IF NOT EXISTS `mytask`.`t_task` (
`idTask` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
`date` DATETIME NOT NULL
...
I created a new hashCode() and equals() functions in Task, with only date field and even so it is different.
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((date == null) ? 0 : date.hashCode());
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (!(obj instanceof Task))
return false;
Task other = (Task) obj;
if (date == null) {
if (other.date != null)
return false;
} else if (!date.equals(other.date))
return false;
return true;
}
Any idea?
This is a complete mess caused by the java.sql.Timestamp messed up design, and by Hibernate returning instances of this class. Indeed, you're storing a java.util.Date instance into your entity. Hibernate transforms that to a java.sql.Timestamp to insert it in the database. But when it reads the data from the database, it doesn't trasform back the Timestamp into a java.util.Date. That works fine, because Timestamp extends Date.
But Timestamp should never have extended Date. Indeed, Date is precise up to the millisecond, whereas Timestamp is precise up to the nanosecond. To be able to compare the nanoseconds parts of two Timestamp, Timestamp overrides the equals() method, but breaks its general contract by doing so. The end result is that you can have date.equals(timestamp) being true, but timestamp.equals(date) being false.
My advice: never compare Date instances with equals(). Use compareTo() instead.
Sun's explanation, working with java client level (not with Hibernate), in the javadoc for java.sql.Timestamp, it states:
Quote:
public class Timestamp extends Date
A thin wrapper around java.util.Date that allows the JDBC API to
identify this as an SQL TIMESTAMP value. It adds the ability to hold
the SQL TIMESTAMP nanos value and provides formatting and parsing
operations to support the JDBC escape syntax for timestamp values.
Note: This type is a composite of a java.util.Date and a separate
nanoseconds value. Only integral seconds are stored in the
java.util.Date component. The fractional seconds - the nanos - are
separate. The Timestamp.equals(Object) method never returns true when
passed a value of type java.util.Date because the nanos component of a
date is unknown. As a result, the Timestamp.equals(Object) method is
not symmetric with respect to the java.util.Date.equals(Object)
method. Also, the hashcode method uses the underlying java.util.Date
implementation and therefore does not include nanos in its
computation.
Due to the differences between the Timestamp class and the
java.util.Date class mentioned above, it is recommended that code not
view Timestamp values generically as an instance of java.util.Date.
The inheritance relationship between Timestamp and java.util.Date
really denotes implementation inheritance, and not type inheritance.
#Test
public void testTimestampVsDate() {
java.util.Date date = new java.util.Date();
java.util.Date stamp = new java.sql.Timestamp(date.getTime());
assertTrue("date.equals(stamp)", date.equals(stamp)); //TRUE
assertTrue("stamp.compareTo(date)", stamp.compareTo(date) == 0); //TRUE
assertTrue("date.compareTo(stamp)", date.compareTo(stamp) == 0); //FALSE
assertTrue("stamp.equals(date)", stamp.equals(date)); //FALSE
}
From javadoc we can figure out that:
Timestamp = java.util.Date + nanoseconds
and
The Timestamp.equals(Object) method never returns true when passed a
value of type java.util.Date because the nanos component of a date is
unknown.
Timestamp compareTo() function
public int compareTo(java.util.Date o) {
if(o instanceof Timestamp) {
// When Timestamp instance compare it with a Timestamp
// Hence it is basically calling this.compareTo((Timestamp))o);
// Note typecasting is safe because o is instance of Timestamp
return compareTo((Timestamp)o);
} else {
// When Date doing a o.compareTo(this)
// will give wrong results.
Timestamp ts = new Timestamp(o.getTime());
return this.compareTo(ts);
}
}
I would suggest you look at what type you are using to store the date in the database. For instance, an Oracle DATE only has precision down to the second level while TIMESTAMP can have down to millisecond like you would with Java date.
http://docs.oracle.com/cd/B19306_01/server.102/b14220/datatype.htm#CNCPT413
For those who are looking for an easy unit testing answer to comparing dates, I have used a SimpleDateFormatter to compare dates as Strings. This allows you to specify the precision you are seeking in the comparison without a bunch of math.
SimpleDateFormatter formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.US);
assertEquals(formatter.format(someExpectedDate), formatter.format(someActualDate));
You can modify the format to fit your needs.
The two dates are of different classes (one is a java.util.Date, the other is java.sql.Timestamp), so they are not the same.
Try this to check the date values:
assertEquals(new Date(taskR1.getDate().getTime()), t1.getDate());