Please your opinion on the following code.
I need to calculate the diff in days between 2 Date objects. It is assured that both Date objects are within the same TimeZone.
public class DateUtils {
public final static long DAY_TIME_IN_MILLIS = 24 * 60 * 60 * 1000;
/**
* Compare between 2 dates in day resolution.
*
* #return positive integer if date1 > date2, negative if date1 < date2. 0 if they are equal.
*/
public static int datesDiffInDays(final Date date1, final Date date2){
long date1DaysMS = date1.getTime() - (date1.getTime() % DAY_TIME_IN_MILLIS);
long date2DaysMS = date2.getTime() - (date2.getTime() % DAY_TIME_IN_MILLIS);
long timeInMillisDiff = (date1DaysMS - date2DaysMS);
int ret = (int) (timeInMillisDiff / DAY_TIME_IN_MILLIS);
return ret;
}
Can you point to a problem that I might have missed ?
EDIT: #mmyers asked if pass my unit test. Well - Yes. But I have no real experience with dates and I know that is a big subject. Posted below the unit test that I'm using.
public class TestMLDateUtils {
#Test
public final void testDatesDiffInDays() {
TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
// 00:00:00.000 1.1.1970
Calendar cal1970 = Calendar.getInstance();
cal1970.setTimeInMillis(0);
Calendar tested = Calendar.getInstance();
tested.setTimeInMillis(0);
// Add 1 millisecond, date = 00:00:00.001 1.1.1970
tested.add(Calendar.MILLISECOND, 1);
assertTrue(DateUtils.datesDiffInDays(cal1970.getTime(), tested.getTime()) == 0);
// Add 1 second, date = 00:00:01.001 1.1.1970
tested.add(Calendar.SECOND, 1);
assertTrue(DateUtils.datesDiffInDays(cal1970.getTime(), tested.getTime()) == 0);
// Add 1 minute, date = 00:01:01.001 1.1.1970
tested.add(Calendar.MINUTE, 1);
assertTrue(DateUtils.datesDiffInDays(cal1970.getTime(), tested.getTime()) == 0);
// Add 1 hour, date = 01:01:01.001 1.1.1970
tested.add(Calendar.HOUR_OF_DAY, 1);
assertTrue(DateUtils.datesDiffInDays(cal1970.getTime(), tested.getTime()) == 0);
// date = 23:59:59.999 1.1.1970
tested.setTimeInMillis(0);
tested.add(Calendar.MILLISECOND, 999);
tested.add(Calendar.SECOND, 59);
tested.add(Calendar.MINUTE, 59);
tested.add(Calendar.HOUR_OF_DAY, 23);
//System.out.println("D: " + tested.getTime());
assertTrue(DateUtils.datesDiffInDays(cal1970.getTime(), tested.getTime()) == 0);
// date = 00:00:00.000 2.1.1970
tested.setTimeInMillis(0);
tested.add(Calendar.DAY_OF_MONTH, 1);
assertTrue(DateUtils.datesDiffInDays(cal1970.getTime(), tested.getTime()) == -1);
assertTrue(DateUtils.datesDiffInDays(tested.getTime(), cal1970.getTime()) == 1);
// date = 00:00:00.001 2.1.1970
tested.add(Calendar.MILLISECOND, 1);
assertTrue(DateUtils.datesDiffInDays(cal1970.getTime(), tested.getTime()) == -1);
assertTrue(DateUtils.datesDiffInDays(tested.getTime(), cal1970.getTime()) == 1);
// date = 00:00:01.001 2.1.1970
tested.add(Calendar.SECOND, 1);
assertTrue(DateUtils.datesDiffInDays(cal1970.getTime(), tested.getTime()) == -1);
assertTrue(DateUtils.datesDiffInDays(tested.getTime(), cal1970.getTime()) == 1);
// date = 00:01:01.001 2.1.1970
tested.add(Calendar.MINUTE, 1);
assertTrue(DateUtils.datesDiffInDays(cal1970.getTime(), tested.getTime()) == -1);
assertTrue(DateUtils.datesDiffInDays(tested.getTime(), cal1970.getTime()) == 1);
// date = 01:01:01.001 2.1.1970
tested.add(Calendar.HOUR_OF_DAY, 1);
assertTrue(DateUtils.datesDiffInDays(cal1970.getTime(), tested.getTime()) == -1);
assertTrue(DateUtils.datesDiffInDays(tested.getTime(), cal1970.getTime()) == 1);
// date = 13:01:01.001 2.1.1970
tested.add(Calendar.HOUR_OF_DAY, 12);
assertTrue(DateUtils.datesDiffInDays(cal1970.getTime(), tested.getTime()) == -1);
assertTrue(DateUtils.datesDiffInDays(tested.getTime(), cal1970.getTime()) == 1);
}
}
Immediate problem: days can have less than or more than 24 hours due to daylight saving time changes.
Secondary problem: normally when people think in days, they really mean "human days" rather than "periods of 24 hours". In other words, many people would say that 7pm-7am the next day is a difference of a day, whereas 7am-7pm the same day is a difference of zero days. Both are 12 hours. At that point, you really need to know the calendar that is being considered.
Of course, this may not matter for your situation, but we don't really know what that is.
Third problem: you're using the built-in calendar API instead of Joda Time. That's almost never a good idea - it's horrible and riddled with gotchas and problems. And yes, the regulars here will tell you that's always part of my answer when it comes to Java dates and times - and for good reason. It's really that important.
EDIT: Your test sets the default time zone to be UTC. That's not really a good idea (especially without resetting it in a finally statement). Time zones are tricky, but you should really think about what values you've got, what they mean, and what time zones are involved.
The time zone, if any, within the Date object is irrelevant, since you're using getTime(); that "[r]eturns the number of milliseconds since January 1, 1970, 00:00:00 GMT represented by this Date object."
However, you aren't accounting for leap seconds, which some implementations may return. Thus, if the day range you give has one or more leap seconds in it, and your times are near enough to the same time of day, your calculation may be wrong by a day. That said, there appears to be no way to see if any particular implementation accounts for leap seconds or not (I expect that most don't), and the difference is pretty darn trivial anyway.
There are many dimensions to a code review; rather than correctness, addressed by others, let me focus a little on style. This will of course be somewhat more subjective than a review concentrating on correctness.
I would inline the "ret" variable. It increases the size of the method without enhancing readability.
I would consider separating the conversion between milliseconds and days into a separate function. Your full class probably performs that division in multiple places. Even if not, it's helpful in that it's easier to name functions that do only one thing.
Speaking of naming, I would rename the function, perhaps to "dayDifference" - abbreviations cause many problems, not least of which is the difficulty of remember which abbreviation was used in which circumstance. If you use none, ever, that particular source of confusion is eliminated. Similarly, I would rename the constant to MILLISECONDS_PER_DAY.
Another problem which hasn't been mentioned yet is leap seconds. Days may have more or less than 24 * 60 * 60 seconds due to adjustments in UTC time to keep it more or less in synch with the mean solar year. Probably not a big deal for your usage, but you should at least be aware of the possibility.
A good API is what you need if you have non-trivial requirements for dealing with dates and times. The link to Joda Time in Jon Skeet's answer appears to be broken, so here is a link that does work.
Date already has this method, look up Date.compareTo(Date) in Javadoc.
Related
I need to compare current time with the time that i am getting from database. I am getting time from database in Java class in java.sql.Time format (16:12:00).
I just need to display a error message if current time matches with the time present in DB.
When dealing with dates and times, you can use one of the many libraries like Joda Time, or you can simply consider a time as a given millisecond since 1/1/1970 (unix epoch), expressed as a normal long.
To convert a java.util.Date, or a java.sql.Time,Date etc.. that extends from java.util.Date, to a simple long, you can call getTime() : http://docs.oracle.com/javase/6/docs/api/java/util/Date.html#getTime()
Current time, expressed as milliseconds from unix epoch so comparable with results of getTime(), can be obtained with System.currentTimeMillis();
Once you have that, comparing it is very easy :
Time dbTime = // the time you obtained from the db
long dbLong = dbTime.getTime();
long now = System.currentTimeMillis();
if (dbLong < now) // data in the db is in the past
if (dbLong > now) // data in the db is in the future
if (dbLong == now) // data in the db is exactly now
Take care of the dbLong == now, cause it's precise to the millisecond, so it will rarely happen in practice, unless you use a range or reduce the precision, say, to the second or minute :
long dbLongSeconds = dbLong / 1000;
long dbLongMinutes = dbLong / (60*1000);
long nowSeconds = now / 1000;
long nowMinutes = now / (60*1000);
if (dbLongSeconds == nowSeconds) // data in the db is in this second
if (dbLongMinutes == nowMinutes) // data in the db is in this minute
If you need more sophisticated comparisons, like day or month, you should use either a library like Joda Time, or built in classes like Calendar, cause the math is way more complex given how western calendar divides the year.
To compare your current time with the time from the database you could simply construct a sql.Time from System.currentTimeMillis() and compare the two toString()s like so:
java.sql.Time serverTime = getServerTime();
java.sql.Time currentTime = new java.sql.Time(System.currentTimeMillis());
if(serverTime.toString().compareTo(currentTime.toString()) == 0)
{
//yay
}
else //nay
You could also compare the two sql.Time's directly using it's compareTo method, but this is trickier.
This is because even though sql.Time's setDate/Year/Month is deprecated and will throw an exception if you use them( which makes sense because they're not a date, only a time) the sql.Time's compareTo uses its superclass implementation, which means it compares not only the time but also the date, which sucks 'cus your database sql.Time object will probably always have the date 1970.01.01 whereas any sql.Time you construct off of System.currentTimeMillis() will have the current date. You can get around this by using a Calendar object as shown.
Calendar tmp = new GregorianCalendar();
tmp.setTimeInMillis(System.currentTimeMillis());
tmp.set(Calendar.YEAR, 1970);
tmp.set(Calendar.MONTH, 0); // 0 == January
tmp.set(Calendar.DATE, 1);
tmp.set(Calendar.MILLISECOND, 0);
java.sql.Time currentTime = new java.sql.Time(c.toInstant().toEpochMilli());
java.sql.Time serverTime = getServerTime();
if(currentTime.compareTo(serverTime) == 0)
{
//yay
}
else //nay
Or you could compare the long times directly as in Simone Gianni's answer, which would probably be the more efficient solution.
I'm newer to Java. I'm using two Timestamp objects dateFrom and dateTo. I want to check whether the dateFrom is 45 days earlier than dateTo. I used this code fragment to compare this
if(dateFrom.compareTo(dateTo) < 45)
{
// do the action;
}
I'm confusing with the 45 given in the code. Can I expect the correct result. will it meets my result.
compareTo() returns a value of -1, 0 or 1, depending on the result.
What you want to do is
long result = dateTo.getTime() - (1000 * 60 * 60 * 24 * 45) - dateFrom.getTime();
if(result >= 0) {
System.out.println("dateFrom is 45 days or more before dateTo");
else {
System.out.println("dateFrom is less than 45 days before dateTo");
}
This is rather ugly though. Is there a specific reason you're not using a Calendar?
You have to think about it a bit logically. First of all you need to get to a timestamp which is 45 days before the dateTo date. Time has various units (seconds, minutes, hours, days) so just checking < 45 is meaningless in this case. The compareTo() method is just there for ordering to know if a timestamp is before or after the other.
You could first create a Calendar for the timestamps, and add() dateFrom by 45 days. Then you can use the before() method to check if dateFrom is before dateTo.
Use Joda Time or Calendar class(add 45 days to dateFrom, compare the result with dateTo).
Do like this
Date dateFrom ="your from date";
Calendar cal = GregorianCalendar.getInstance();
cal .setTime(dateFrom);
cal.add(Calendar.DAY_OF_MONTH, 45);
Date expireDate = cal.getTime();
Date dateTo = new Date();
if(dateTo.after(expireDate)){
}
Since Timestamp in Java is number of milliseconds from UNIX Epoch - change 45 days to number of milliseconds (45d=45*24h=45*24*3600s=45*24*3600*1000ms) so if:
(time_B + 45*24*3600*1000) >= time_A
it means that time_B is 45 days (or more) 'further' in time that time_A
Of course you can use JodaTime and other libs too.
Calculating the difference between two dates (java.util.Date) in terms of no. of days look like very simple and we can find different ways to do that. I used the following code to calculate the date difference:
public static long daysBetween(Calendar startDate, Calendar endDate) {
Calendar date = (Calendar) startDate.clone();
long daysBetween = 0;
while (date.before(endDate)) {
date.add(Calendar.DAY_OF_MONTH, 1);
daysBetween++;
}
return daysBetween;
}
In main(), I used the following two dates :
Calendar c1 = Calendar.getInstance();
c1.set(2011, 1, 1);
Calendar c2 = Calendar.getInstance();
c2.set(2011, 1, 31);
long difference = daysBetween(c1, c2); //
But the value of the variable difference is not consistent. It is sometimes 30 and sometimes 31. So, why that might have happened.
Is there any solution to use the method results a consistent output ?
You're setting the date part of the calendars, but not the time part.
Sometimes the clock will tick between the calls to getInstance() and sometimes it won't, hence the inconsistency.
Options:
Set the time as well as the date, e.g. to midnight
Use a better date/time library - Joda Time - which has a more suitable representation (LocalDate). An important moral here is that if you can find a type which represents the exact information you have, and nothing else, that's likely to be a good fit and cause fewer complications.
Using LocalDate, you wouldn't even have to do the loop as Joda Time has good support for computing the differences between two values anyway.
LocalDate date1 = new LocalDate(2011, 1, 1);
LocalDate date2 = new LocalDate(2011, 1, 31);
Days period = Days.daysBetween(days1, days2);
int days = period.getDays();
You are only setting the year, month and day. The hours, minutes, seconds and milli-seconds are the current time (and thus different every time you run it)
I suggest you use Joda Time's LocalDate instead as it appears to does exactly what you want.
In java, I want to get the number of days between two dates, excluding those two dates.
For example:
If first date = 11 November 2011 and the second date = 13 November 2011
then it should be 1.
This is the code I am using but doesn't work (secondDate and firstDate are Calendar objects):
long diff=secondDate.getTimeInMillis()-firstDate.getTimeInMillis();
float day_count=(float)diff / (24 * 60 * 60 * 1000);
daysCount.setText((int)day_count+"");
I even tried rounding the results but that didn't help.
How do I get the number of days between dates in java excluding the days themselves?
I've just tested on SDK 8 (Android 2.2) the following code snippet:
Calendar date1 = Calendar.getInstance();
Calendar date2 = Calendar.getInstance();
date1.clear();
date1.set(
datePicker1.getYear(),
datePicker1.getMonth(),
datePicker1.getDayOfMonth());
date2.clear();
date2.set(
datePicker2.getYear(),
datePicker2.getMonth(),
datePicker2.getDayOfMonth());
long diff = date2.getTimeInMillis() - date1.getTimeInMillis();
float dayCount = (float) diff / (24 * 60 * 60 * 1000);
textView.setText(Long.toString(diff) + " " + (int) dayCount);
it works perfectly and in both cases (Nov 10,2011 - Nov 8,2011) and (Nov 13,2011 - Nov 11,2011) gives dayCount = 2.0
Get Days between java.util.Dates, ignoring daylight savings time
Quick and dirty hack:
public int get_days_between_dates(Date date1, Date date2)
{
//if date2 is more in the future than date1 then the result will be negative
//if date1 is more in the future than date2 then the result will be positive.
return (int)((date2.getTime() - date1.getTime()) / (1000*60*60*24l));
}
This function will work 99.99% of the time, except when it surprises you later on in the edge cases during leap-seconds, daylight savings, timezone changes leap years and the like. If you are OK with the calculation being off by 1 (or 2) hours once in a while, this will suffice.
Get Days between Dates taking into account leapseconds, daylight savings, timezones, etc
If you are asking this question you need to slap yourself. What does it mean for two dates to be at least 1 day apart? It's very confusing. What if one Date is midnight in one timezone, and the other date is 1AM in another timezone? Depending on how you interpret it, the answer is both 1 and 0.
You think you can just force the dates you pass into the above function as Universal time format; that will fix some of your problems. But then you just relocate the problem into how you convert your local time to a universal time. The logical conversion from your timezone to universal time may not be what is intuitive. In some cases you will get a day difference when the dates passed in are obviously two days apart.
And you think you can deal with that? There are some simplistic calendar systems in the world which are constantly changing depending on the harvest season and installed political rulers. If you want to convert their time to UTC, java.util.Date is going to fail you at the worst moment.
If you need to calculate the days between dates and it is critical that everything come out right, you need to get an external library called Joda Time: (They have taken care of all the details for you, so you can stay blissfully unaware of them): http://joda-time.sourceforge.net/index.html
java.time
The java.time API, released with Java-8 in March 2014, supplanted the error-prone legacy date-time API. Since then, using this modern date-time API has been strongly recommended.
Solution using modern date-time API
Using Calendar#toInstant, convert your java.util.Calendar instances into java.time.Instant and then into java.time.ZonedDateTime instances and then use ChronoUnit.DAYS.between to get the number of days between them.
Demo:
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;
import java.util.Calendar;
public class Main {
public static void main(String[] args) {
// Sample start and end dates as java.util.Date
Calendar startCal = Calendar.getInstance();
startCal.set(2011, 10, 11); // 11 November 2011
Calendar endCal = Calendar.getInstance();
endCal.set(2011, 10, 13); // 13 November 2011
// Convert the java.util.Calendar into java.time.ZonedDateTime
// Replace ZoneId.systemDefault() with the applicable ZoneId
ZonedDateTime startDateTime = startCal.toInstant().atZone(ZoneId.systemDefault());
ZonedDateTime endDateTime = endCal.toInstant().atZone(ZoneId.systemDefault());
// The end date is excluded by default. Subtract 1 to exclude the start date
long days = ChronoUnit.DAYS.between(startDateTime, endDateTime) - 1;
System.out.println(days);
}
}
Output:
1
Learn more about the modern Date-Time API from Trail: Date Time.
Don't use floats for integer calculations.
Are you sure your dates are days? The precision of the Date type is milliseconds. So the first thing you need to do is round the date to something which doesn't have hours. Example: It's just one hour from 23:30 2011-11-01 to 00:30 2011-11-02 but the two dates are on different days.
If you are only going to be dealing with dates between the years 1900 and 2100, there is a simple calculation which will give you the number of days since 1900:
public static int daysSince1900(Date date) {
Calendar c = new GregorianCalendar();
c.setTime(date);
int year = c.get(Calendar.YEAR);
if (year < 1900 || year > 2099) {
throw new IllegalArgumentException("daysSince1900 - Date must be between 1900 and 2099");
}
year -= 1900;
int month = c.get(Calendar.MONTH) + 1;
int days = c.get(Calendar.DAY_OF_MONTH);
if (month < 3) {
month += 12;
year--;
}
int yearDays = (int) (year * 365.25);
int monthDays = (int) ((month + 1) * 30.61);
return (yearDays + monthDays + days - 63);
}
Thus, to get the difference in days between two dates, you calculate their days since 1900 and calc the difference. Our daysBetween method looks like this:
public static Integer getDaysBetween(Date date1, Date date2) {
if (date1 == null || date2 == null) {
return null;
}
int days1 = daysSince1900(date1);
int days2 = daysSince1900(date2);
if (days1 < days2) {
return days2 - days1;
} else {
return days1 - days2;
}
}
In your case you would need to subtract an extra day (if the days are not equal).
And don't ask me where this calculation came from because we've used it since the early '90s.
I have two suggestions:
Make sure your float day_count is calculated correctly
float day_count = ((float)diff) / (24f * 60f * 60f * 1000f);
If it's rounding error, try using floor method
daysCount.setText("" + (int)Math.floor(day_count));
I am calculating the difference between two sql dates, TripStartDate and TripEndDate.
If TripStartDate= 2011-03-04 09:35:00 and TripEndDate = 2011-03-04 10:35:00 then I should get the number of day is 1 (because trip happened on that day).
Like this:
If TripStartDate = 2011-03-04 09:35:00 and TripEndDate = 2011-03-05 09:35:00 then method should return 2 days (because trip happened on both days).
If TripStartDate = 2011-03-04 09:35:00 and TripEndDate = 2011-04-04 09:35:00 then method should return 32 days. (because 28 days in march and 4 days in April).
Calculation should be based on only dates and month of year (not taking time in consideration). Please help me . Thanks in advance...
In Java I guess you would drop the time and calculate the day difference
Calendar cal1 = Calendar.getInstance();
Calendar cal2 = Calendar.getInstance();
cal1.set(2011, 03, 04);
cal2.set(2011, 04, 04);
long milis1 = cal1.getTimeInMillis();
long milis2 = cal2.getTimeInMillis();
long diff = milis2 - milis1;
long days = diff / (24 * 60 * 60 * 1000);
EDIT Quite surprisingly for me, that ^ code really doesn't work always... apparently there are some 'leap seconds' that mess up the maths. There are quite enough links already proposed to you in comments. I would go with joda time library.
FYI, in Groovy this would be something along the lines of:
fourthMarch = Date.parse( 'yyyy-MM-dd', '2011-03-04' )
fifthMarch = Date.parse( 'yyyy-MM-dd', '2011-03-05' )
fourthApril = Date.parse( 'yyyy-MM-dd', '2011-04-04' )
assert 2 == fifthMarch - fourthMarch + 1
assert 32 == fourthApril - fourthMarch + 1
We need to add 1 as the dates are inclusive
Here's the usual way to do this, in Java 8.
LocalDate start = LocalDate.of(2011, 3, 4); // Or whatever - this is Y, M, D
LocalDate end = LocalDate.of(2011, 4, 4);
return ChronoUnit.DAYS.between(start, end) + 1;
// The +1 is for the inclusive reckoning