Is the TimeUnit class broken? - java

I noticed a strange behaviour of the TimeUnit class, so I created this minimal example to reproduce it.
long differenceInDays;
Calendar c1 = Calendar.getInstance();
Calendar c2 = Calendar.getInstance();
c1.setTimeInMillis(1466062306000l); // Thu Jun 16 2016 09:31:46 GMT+0200
c2.setTimeInMillis(1466028000000l); // Thu Jun 16 2016 00:00:00 GMT+0200
differenceInDays = TimeUnit.DAYS.convert(c2.getTimeInMillis() - c1.getTimeInMillis(), TimeUnit.MILLISECONDS);
System.out.println(differenceInDays); // obviously zero
c2.add(Calendar.DATE, +1);
differenceInDays = TimeUnit.DAYS.convert(c2.getTimeInMillis() - c1.getTimeInMillis(), TimeUnit.MILLISECONDS);
System.out.println(differenceInDays); // why zero and not one?
c2.add(Calendar.DATE, +1);
differenceInDays = TimeUnit.DAYS.convert(c2.getTimeInMillis() - c1.getTimeInMillis(), TimeUnit.MILLISECONDS);
System.out.println(differenceInDays); // suddenly a 1, but not a 2 like expected
It is obvious that the first time the difference is calculated it is 0, because not a whole day lies between the dates.
But the second time a whole day is added, so how can the difference be still 0?
Output:
001
I don't think this problem is daylight saving time or leap year related because I only do calculations within the same year, even month.
Here is a date to milliseconds calculator for you to check.

You can see better what's going on here with simple math:
c1 = 1466062306000
c2 = 1466028000000
d = 86400000 // one day
c2 - c1 = -34306000 // negative, but less than one day in magnitude
c2 - c1 + d = 52094000 // less than one day
c2 - c1 + d + d = 138494000 // more than one day, less than two days
The correct way to handle this, assuming you're using Java 8, is as follows:
// Decide what time zone you want to work in
ZoneId tz = ZoneId.of("Europe/Berlin");
// If you wanted the local time zone of the system,
// Use this instead:
// ZoneId tz = ZoneId.systemDefault();
// Get instants from the timestamps
Instant i1 = Instant.ofEpochMilli(1466062306000l);
Instant i2 = Instant.ofEpochMilli(1466028000000l);
// Get the calendar date in the specified time zone for each value
LocalDate d1 = i1.atZone(tz).toLocalDate();
LocalDate d2 = i2.atZone(tz).toLocalDate();
// Get the difference in days
long daysBetween = ChronoUnit.DAYS.between(d2, d1);
If your inputs are truly Calendar objects instead of timestamps, I'd suggest Calendar.toInstant() as described in the Legacy Date-Time Code guidance.
If you're using Java 7 or earlier, you will find similar capabilities from the Joda Time library.
if you really don't want to use any of these, and still do things the old (hard) way, then see this example.

Related

How to handle dates when the input dates are in the transition period from PDT to PST?

I am calling an API which takes two dates as input.The API checks if the difference between the two date is greater than 60 min, then it throws an exception.My input dates are startDate=11-06-2016T00:57:01 and endDate=11-06-2016T01:56:01.These two dates are saved in java.util.Date object.
Now the issue is though the two dates have a difference of 59 min which is less than 60 min, still the API throws exception.Looks like this isssue is due to DayLightSaving.On Nov 6,once 2 am is reached , DayLightSaving ends (PDT time zone ends), time is moved backward by 1 hr due to which time again become 1 am but in PST time zone now.This means on Nov 6 , there would be 1-2 am twice one in PDT and one in PST zone.
When this API is called on NOV 7, the time zone would be PST.So when the two dates are passed without the timezone specified, it takes the startDate in PDT zone and enddate in PST zone.Since PDT and PST itself have a difference of 1 hour, this would get added to the 59 min differnce and exception is being thrown.
How to handle this case when the input dates are in the transition period from PDT to PST?
sample code
SimpleDateFormat formatter1 = new SimpleDateFormat("MM-dd-yyyy hh:mm:ss");
String start="11-06-2016 00:57:01";
String end ="11-06-2016 01:56:01";
Date startdate = formatter1.parse(start);
Date enddate = formatter1.parse(end);
System.out.println("startDate is :" + startdate);
System.out.println("endDate is :" +enddate);
long dateRange = enddate.getTime() - startdate.getTime();
//if the difference between the two dates is > than 60 min i.e 3600000 ms, then throw exception.
System.out.println(dateRange);
if (dateRange > (60 * 60 * 1000)){
throw new Exception("Date time range cannot be greater than 60 minutes.(calculated using millisecond difference)");
}
Output
[Date Range is = 7140000
Exception in thread "main" java.lang.Exception: Date time range cannot be greater than 60 minutes.(calculated using millisecond difference).
at Datetest.main(Datetest.java:28)][1]
The above snippet throws exception when called in PST time zone.
Neither SimpleDateFormat nor the underlying Calendar specifies what happens when parsing a datetime string without timezone for a time in the overlapping hour between daylight savings time and standard time.
You have observed that it will return the later time, i.e. it seems to prefer standard over daylight savings time. But, the behavior is undefined, so...
The new java.time classes do however specify exactly what happens, and how to choose the other "hour" of the overlap.
In the new API, since your datetime string is without timezone, you'd likely first parse using LocalDateTime, then apply time zone to get a ZonedDateTime, e.g.
LocalDateTime ldtEnd = LocalDateTime.parse("2016-11-06T01:56:01");
ZonedDateTime zdtEnd = ldtEnd.atZone(ZoneId.of("America/Los_Angeles"));
// zdtEnd is now: 2016-11-06T01:56:01-07:00[America/Los_Angeles]
To see the overlap, you can try adding an hour:
ZonedDateTime zdtEnd2 = zdtEnd.plusHours(1);
// zdtEnd2 is now: 2016-11-06T01:56:01-08:00[America/Los_Angeles]
The behavior is well-defined, see javadoc of atZone():
In most cases, there is only one valid offset for a local date-time. In the case of an overlap, where clocks are set back, there are two valid offsets. This method uses the earlier offset typically corresponding to "summer".
In the case of a gap, where clocks jump forward, there is no valid offset. Instead, the local date-time is adjusted to be later by the length of the gap. For a typical one hour daylight savings change, the local date-time will be moved one hour later into the offset typically corresponding to "summer".
To obtain the later offset during an overlap, call ZonedDateTime.withLaterOffsetAtOverlap() on the result of this method. To throw an exception when there is a gap or overlap, use ZonedDateTime.ofStrict(LocalDateTime, ZoneOffset, ZoneId).
As you can see, it will always return the earlier time in an overlap, which is opposite of the observed behavior of SimpleDateFormat. If you want the later time in an overlap, call withLaterOffsetAtOverlap().
If you don't want to rely on documented default, you can always be explicit:
ZoneId PT = ZoneId.of("America/Los_Angeles");
LocalDateTime ldtStart = LocalDateTime.parse("2016-11-06T00:57:01");
ZonedDateTime zdtStartEarly = ldtStart.atZone(PT).withEarlierOffsetAtOverlap();
ZonedDateTime zdtStartLater = ldtStart.atZone(PT).withLaterOffsetAtOverlap();
System.out.println(zdtStartEarly); // 2016-11-06T00:57:01-07:00[America/Los_Angeles]
System.out.println(zdtStartLater); // 2016-11-06T00:57:01-07:00[America/Los_Angeles]
LocalDateTime ldtEnd = LocalDateTime.parse("2016-11-06T01:56:01");
ZonedDateTime zdtEndEarly = ldtEnd.atZone(PT).withEarlierOffsetAtOverlap();
ZonedDateTime zdtEndLater = ldtEnd.atZone(PT).withLaterOffsetAtOverlap();
System.out.println(zdtEndEarly); // 2016-11-06T01:56:01-07:00[America/Los_Angeles]
System.out.println(zdtEndLater); // 2016-11-06T01:56:01-08:00[America/Los_Angeles]
As you can see, for the 00:57 time, it makes no difference, because that time is not in the overlap hour.
What you can do here get the difference between the 2 dates using timezone offset. something like below
private int getDSTdifferenceDateAdjustment(Date startDate, Date endDate, TimeZone timeZone)
{
if (startDate == null || endDate == null) return 0;
int baseOffset = timeZone.getOffset(startDate.getTime());
int newOffSet = timeZone.getOffset(endDate.getTime());
return (newOffSet - baseOffset);
}
Have something like this in your method
int dstDifference = getDSTdifferenceDateAdjustment(startdate, enddate, TimeZone.getDefault());
// The dstDifference will get in the negative, so we are adding to the dateRange variable
dateRange += dstDifference;
Try this one and even check when the DST starts next year. Mostly this will work in all these cases

Error while calculating java Date difference

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.

Java, get days between two dates

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));

How do I increment a java.sql.Timestamp by 14 days?

I have an app that takes a Timestamp as a boundary for the start date and end date of a sql selection, I want to populate a hashmap with weeks this year since the first monday of the year as the values and the week number as the keys. I'm finding it really hard to work with timestamps and I don't feel very good about adding 86,400,000 seconds to it to increment the day, as this doesn't account for the leap days, hours, seconds.
I plan on adding 13 days 23 hours, 59 minutes and 59 seconds to it so that I can lookup the start date in the map by the week as the key, then use the start date to get the end date.
So I'm looking to try to get something like this:
Week startDate endDate
1 2011-01-03 00:00:00 2011-01-16 23:59:59
2 2011-01-17 00:00:00 2011-01-30 23:59:59
With the first two columns in the Map and the last one being calculated after looking it up. How do I safely increment a java.sql.Timestamp?
java.sql.Timestamp ts = ...
Calendar cal = Calendar.getInstance();
cal.setTime(ts);
cal.add(Calendar.DAY_OF_WEEK, 14);
ts.setTime(cal.getTime().getTime()); // or
ts = new Timestamp(cal.getTime().getTime());
This will correctly cater for daylight-time transitions in your default Timezone. You can tell the Calendar class to use a different Timezone if need be.
It worth noting that 14 days is not always 14 * 24 * 3600 seconds. When you have daylight savings, this can be an hour shorter or longer. Historically it can be much more complex than that.
Instead I would suggest using JodaTime or the Calendar to perform the time zone dependant calculation.
Java 8
Timestamp old;
ZonedDateTime zonedDateTime = old.toInstant().atZone(ZoneId.of("UTC"));
Timestamp new = Timestamp.from(zonedDateTime.plus(14, ChronoUnit.DAYS).toInstant());
private Long dayToMiliseconds(int days){
Long result = Long.valueOf(days * 24 * 60 * 60 * 1000);
return result;
}
public Timestamp addDays(int days, Timestamp t1) throws Exception{
if(days < 0){
throw new Exception("Day in wrong format.");
}
Long miliseconds = dayToMiliseconds(days);
return new Timestamp(t1.getTime() + miliseconds);
}
Timestamp my14DaysAfter = Timestamp.valueOf(myTimestamp.toLocalDateTime().plusDays(14));

How to get Exact day difference between two dates in java or groovy?

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

Categories