I need to make map where Dates are keys. 2 date objects are equals if they have the same value of getTime()
method.
I'm interested only in year, month and day. How can I trim
unnecessary hours and minutes to get 'clear' dates?
I need to make map where Dates are keys. 2 date objects are equals if they have the same value of getTime()
method.
I'm interested only in year, month and day. How can I trim
unnecessary hours and minutes to get 'clear' dates?
You can create a trim
method:
public static Date trim(Date date) {
Calendar cal = Calendar.getInstance();
cal.clear(); // as per BalusC comment.
cal.setTime( date );
cal.set(Calendar.HOUR_OF_DAY, 0);
cal.set(Calendar.MINUTE, 0);
cal.set(Calendar.SECOND, 0);
cal.set(Calendar.MILLISECOND, 0);
return cal.getTime();
}
And use it like:
map.put( trim( aDate ), xyz() );
...
map.get( trim( otherDate ));
Here's a complete working sample:
import java.util.Calendar;
import java.util.Date;
import static java.util.Calendar.*;
import static java.lang.System.out;
public class DateTest {
public static void main( String [] args ) throws InterruptedException {
Date date = new Date();
Thread.sleep(1);
Date other = new Date();
out.printf("equals? = %s, hashCode? = %s %n", (date.equals(other)), (date.hashCode() == other.hashCode()));
Date todayeOne = trim( date );
Date todayTwo = trim( date );
out.printf("equals? = %s, hashCode? = %s %n", (todayeOne.equals(todayTwo)), (todayeOne.hashCode() == todayTwo.hashCode()));
}
public static Date trim(Date date) {
Calendar cal = Calendar.getInstance();
cal.setTime( date );
cal.set(HOUR_OF_DAY, 0);
cal.set(MINUTE, 0);
cal.set(SECOND, 0);
cal.set(MILLISECOND, 0);
return cal.getTime();
}
}
output:
$ java DateTest
equals? = false, hashCode? = false
equals? = true, hashCode? = true
Use a custom Comparator<Date>
for a TreeMap<Date,V>
.
Comparator<Date> ymdComparator = new Comparator<Date>() {
@Override public int compare(Date d1, Date d2) {
return
d1.getYear() < d2.getYear() ? -1 :
d1.getYear() > d2.getYear() ? +1 :
d1.getMonth() < d2.getMonth() ? -1 :
d1.getMonth() > d2.getMonth() ? +1 :
d1.getDay() < d2.getDay() ? -1 :
d1.getDay() > d2.getDay() ? +1 :
0;
}
};
SortedMap<Date,V> map = new TreeMap<Date,V>(ymdComparator);
Oh, java.util.Date
sucks, use Joda Time, etc.
LocalDate ld =
myUtilDate.toInstant()
.atZone( ZoneId.of( "America/Montreal" ) )
.toLocalDate();
The Question and other Answers use outmoded old date-time classes that have proven to be poorly-designed, confusing, and troublesome. Now legacy, supplanted by the java.time classes.
Instant
truncatedTo more directly address the question:
java.util.Date
to java.time.Instant
.Convert via new methods added to the old classes.
Instant instant = myUtilDate.toInstant();
The truncation feature is built into the Instant
class. 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 instantTruncated = instant.truncatedTo( ChronoUnit.DAYS );
ZonedDateTime
& LocalDate
But the approach above has issues. Both java.util.Date
and Instant
represent a moment on the timeline in UTC rather than an particular time zone. So if you drop the time-of-day, or set it to 00:00:00
, you are getting a date that only makes sense in UTC. If you meant the date for Auckland NZ or Montréal Québec, you may have the wrong date.
So a better approach is to apply your desired/expected time zone to the Instant
to get a ZonedDateTime
.
Another problem is that we are inappropriately using a date-time object to represent a date-only value. Instead we should use a date-only class. From the ZonedDateTime
we should extract a LocalDate
if all you want is the date-only.
The LocalDate
class represents a date-only value without time-of-day and without time zone.
ZoneId z = ZoneId.of( "America/Montreal" );
ZonedDateTime zdt = instant.atZone( z );
LocalDate ld = zdt.toLocalDate();
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 java.time.
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?
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.
long time1 = myDate1.getTime()/(1000*60*60*24);
long time2 = myDate2.getTime()/(1000*60*60*24);
if (time1 == time2)
// equal!
This pushes insignificant values below the decimal, then integer division truncates it, so only values significant at the Day level and higher are left.
If you want to make those dates again, just apply the offset back to the truncated values:
myDate1.setTime(time1 * (1000*60*60*24));
myDate2.setTime(time2 * (1000*60*60*24));
Just convert your Date
objects to be equal, if the have the same year, month and day:
public static Date convertDate(Date oldDate) {
final long oneDay = 1000 * 60 * 60 * 24;
long newDate = oldDate.getTime() / oneDay;
return new Date( newDate * oneDay );
}
Proposed solution does not work in Android as it is stuck on Java 7 and so Calendar is buggy. Others solutions also had bugs because they did not take into account TimeZone offset, or have problems with int overflow before they get converted to long.
This solution seems to work:
public static final long MILLISECONDS_PER_DAY=(1000L * 60L * 60L * 24L);
public static long convertDate(Date d) {
TimeZone tz = TimeZone.getDefault();
long val = d.getTime() + tz.getOffset(d.getTime()); /*local timezone offset in ms*/
return val / MILLISECONDS_PER_DAY;
}
public static Date convertDate(long date) {
TimeZone tz = TimeZone.getDefault();
Date d=new Date();
d.setTime(date*MILLISECONDS_PER_DAY-tz.getOffset(d.getTime()));
return d;
}