For reoccurring events, I want to show the number of days left until the next occurrence in my Android calendar application.
Example:
Today: 2012-06-12
Reoccurring event: 19th June
=> 13 days left
In order to achieve this, I save the first occurrence in an object of data type Calendar
:
private Calendar cal;
...
cal = new GregorianCalendar();
cal.set(Calendar.YEAR, USER_INPUT_YEAR);
cal.set(Calendar.MONTH, USER_INPUT_MONTH);
...
To calculate the days left I use this function:
public int getDaysLeft() {
Date next = this.getNextOccurrence();
if (next == null) {
return -1;
}
else {
long differenceInMilliseconds = next.getTime()-System.currentTimeMillis();
double differenceInDays = (double) differenceInMilliseconds/DateUtils.DAY_IN_MILLIS;
return (int) Math.ceil(differenceInDays);
}
}
Which uses this function:
public Date getNextOccurrence() {
if (this.cal == null) {
return null;
}
else {
Calendar today = new GregorianCalendar();
Calendar next = new GregorianCalendar();
next.setTime(this.cal.getTime());
next.set(Calendar.YEAR, today.get(Calendar.YEAR));
if ((today.get(Calendar.MONTH) > this.cal.get(Calendar.MONTH)) || ((today.get(Calendar.MONTH) == this.cal.get(Calendar.MONTH)) && (today.get(Calendar.DAY_OF_MONTH) > this.cal.get(Calendar.DAY_OF_MONTH)))) {
next.add(Calendar.YEAR, 1);
}
return next.getTime();
}
}
By the way, to get the initial date, I expect to find a YYYY-MM-DD value and parse it like this:
(new SimpleDateFormat("yyyy-MM-dd")).parse(INPUT_DATE_STRING)
This works fine in most cases, but some users report that they see numbers such as -1469913
as "days left". How can this happen?
I thought the date (cal
) might be not set or invalid, but then it would show -1
or something like this, as there are null checks in all parts, right?
-1469913
means something like -4027
years ago! As it is a reoccurring event, I thought the "days left" information should always be between 0 and 366. What could cause this code to produce such a number? Does this mean that getNextOccurrence()
returns a data that is 4027 years in the past? I can't explain this behaviour.
I hope you can help me. Thank you so much in advance!
Edit: As it may be helpful: The wrong dates' year is always output as 1
when using DateFormat.getDateInstance().format()
, e.g. Jan 3, 1
. Nevertheless, the result of getDaysLeft()
is something like 4k years.
Edit #2: I found out a date like 1--22199-1
is one that produces the output of "4k years left". Nevertheless, it is successfully parsed by (new SimpleDateFormat("yyyy-MM-dd")).parse()
. Similarly, -1-1-1-91-
is correctly parsed as Jan 1, 2
.
Edit #3: It turned out that a date as simple as "0000-01-03" was causing all the trouble. When I output the time in milliseconds it says -62167222800000
. When I then output it to a GMT string it says 0001-01-03
- strange, isn't it? And when I set the year to 1900
the time in millis is suddenly -122095040400000
. Why?
Working with dates it can be really difficult to figure out those obscure errors before they happen to a user in the wild. In many cases, it can be worth your time to make a little unit test that throws a few tens of million dates in the machinery and see if any extreme answers pop up.
Also this might be worth reading. You wont realize how bad the java date-class are before you have tried something that is way better. :)
EDIT: If the users give a very high input value, then there can be a number overflow when you throw your result to an integer in getDaysLeft()
. Just keep it as a long. Or even better: Only accept sensible input value, warn the user if they input the year 20120 or something like that :)
EDIT2: I was wrong in my last edit, .ceil()
protects against number overflows. To be honest I have no longer any idea how this bug can happen.
EDIT3: Responding to your third edit: Remember, Date
and Calendar
uses Unix time. That means that the time represented by a zero is 1970. Everything before 1970 will be represented by a negative value.
EDIT4: Remember that javas calendar-classes sucks. This code snippet demonstrates that the error is in fact in the Calendar-class:
Calendar next = new GregorianCalendar();
long date1 = -62167222800000L;
long date2 = -62135600400000L;
next.setTimeInMillis(date1);
next.set(Calendar.YEAR, 2012);
System.out.println(next.getTimeInMillis());
next.setTimeInMillis(date2);
next.set(Calendar.YEAR, 2012);
System.out.println(next.getTimeInMillis());
Output:
-125629491600000
1325545200000
It will however be very hard to track down the exact bug that causes this. The reason all those bugs remains are because fixing them might break legacy systems all over the world. My guess is that the bug originates from the inability to give negative years. This, for example, will give the output "2013":
Calendar next = new GregorianCalendar();
next.set(Calendar.YEAR, -2012);
System.out.println(next.get(Calendar.YEAR));
I would simply recommend you to not allow such extreme values in your input. Decide on an acceptable span and give an error message if the value is outside of those boundaries. If you would like to handle all possible dates in some futher application, just use joda time. You wont regret it :)