-1469913 дней осталось при подсчете повторяющихся событий в Android

Для повторяющихся событий я хочу показать количество дней, оставшихся до следующего события в моем приложении календаря Android.

Пример:

Today: 2012-06-12
Reoccurring event: 19th June
=> 13 days left

Для этого я сохраняю первое вхождение в объект типа данных Calendar:

private Calendar cal;
...
cal = new GregorianCalendar();
cal.set(Calendar.YEAR, USER_INPUT_YEAR);
cal.set(Calendar.MONTH, USER_INPUT_MONTH);
...

Для подсчета оставшихся дней я использую эту функцию:

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

Кто использует эту функцию:

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

Кстати, чтобы получить начальную дату, я ожидаю найти значение ГГГГ-ММ-ДД и проанализировать его следующим образом:

(new SimpleDateFormat("yyyy-MM-dd")).parse(INPUT_DATE_STRING)

В большинстве случаев это работает нормально, но некоторые пользователи сообщают, что они видят числа, такие как -1469913, как «оставшиеся дни». Как это может произойти?

Я думал, что дата (cal) может быть не установлена ​​или недействительна, но тогда будет отображаться -1 или что-то в этом роде, так как во всех частях есть нулевые проверки, верно?

-1469913 означает что-то вроде -4027 лет назад! Поскольку это повторяющееся событие, я подумал, что информация об «оставшихся днях» всегда должна быть в диапазоне от 0 до 366. Что может привести к тому, что этот код выдаст такое число? Означает ли это, что getNextOccurrence() возвращает данные на 4027 лет назад? Я не могу объяснить это поведение.

Я надеюсь, что вы можете мне помочь. Огромное спасибо заранее!

Редактировать: Это может быть полезно: год с неправильными датами всегда выводится как 1 при использовании DateFormat.getDateInstance().format(), например. Jan 3, 1. Тем не менее, результат getDaysLeft() составляет что-то вроде 4к лет.

Правка № 2: я обнаружил, что такая дата, как 1--22199-1, выдает "осталось 4 000 лет". Тем не менее, он успешно разбирается (new SimpleDateFormat("yyyy-MM-dd")).parse(). Точно так же -1-1-1-91- правильно интерпретируется как Jan 1, 2.

Правка №3: оказалось, что такая простая дата, как "0000-01-03", вызывала все проблемы. Когда я вывожу время в миллисекундах, он говорит -62167222800000. Когда я затем вывожу его в строку по Гринвичу, он говорит 0001-01-03 - странно, не так ли? И когда я установил год на 1900, время в миллисекундах внезапно стало -122095040400000. Почему?


person caw    schedule 12.06.2012    source источник
comment
Джода поддержал, но не будет ли это довольно просто отладить?   -  person Dave Newton    schedule 12.06.2012
comment
Почему Йода? Класс Calendar на самом деле весьма полезен и прост в обращении, не так ли? Но мне это нелегко отладить, так как у меня нет входных данных для тестирования. Но это может быть только неверная дата, поскольку повторяющиеся события всегда происходят через 366 дней или меньше, или я нарушаю базовую математику?   -  person caw    schedule 12.06.2012
comment
@МаркоВ. Потому что такие вещи и многое другое тривиальны в Джоде. Я не понимаю, что у меня нет входных данных для тестирования; у вас достаточно, чтобы получить что-то 4000 лет назад, так что может показаться, что это может быть хорошей отправной точкой. Даже если у вас этого не было, я почти уверен, что математика дат поддается проверке при правильном рефакторинге.   -  person Dave Newton    schedule 12.06.2012
comment
Проблема в том, что я знаю только вывод (4 тысячи лет назад), но я не знаю, какой ввод (строка даты) произвел этот вывод.   -  person caw    schedule 14.06.2012
comment
Я сомневаюсь, что это актуально, но: -1469913 на самом деле около 4024 лет назад (потому что год составляет около 365,25 дней). Отмечу, что это 2*2012. Возможно ли, что у вас где-то есть отрицательный номер года с -2012 вместо 2012? (Глядя на ваш код, я не вижу очевидного способа для этого.)   -  person Gareth McCaughan    schedule 15.06.2012
comment
Судя по вашему намеку, кажется совершенно очевидным, что где-то просто отрицательный номер года. Так что это может быть не так уж важно. Пожалуйста, смотрите мое редактирование № 2, так как это может быть той частью, в которой возникает проблема.   -  person caw    schedule 15.06.2012
comment
Изменить № 3 настоятельно предполагает, что где-то в вычислении вы страдаете от целочисленного переполнения, хотя не сразу видно, где именно. (Числа слишком велики для 32-битного переполнения и слишком малы для 64-битного, но перед каким-то масштабированием может произойти переполнение)   -  person Chris Stratton    schedule 15.06.2012
comment
Я тоже думал об этом. Но, как вы говорите, похоже, в коде нет места, где это могло бы произойти. Так что это действительно может быть ошибка в Java Calendar.   -  person caw    schedule 15.06.2012


Ответы (2)


Работая с датами, может быть очень сложно выяснить эти непонятные ошибки до того, как они произойдут с пользователем в дикой природе. Во многих случаях стоит потратить время на небольшой юнит-тест, который запускает в машину несколько десятков миллионов дат и посмотреть, не появятся ли какие-нибудь экстремальные ответы.

Также стоит прочитать это. Вы не поймете, насколько плох класс даты Java, пока не попробуете что-то, что намного лучше. :)

РЕДАКТИРОВАТЬ: Если пользователи дают очень большое входное значение, может произойти переполнение числа, когда вы переводите результат в целое число в getDaysLeft(). Просто держите его как можно дольше. Или даже лучше: принимайте только разумное входное значение, предупреждайте пользователя, если он вводит 20120 год или что-то в этом роде :)

EDIT2: я ошибся в своем последнем редактировании, .ceil() защищает от переполнения числа. Честно говоря, я больше не знаю, как эта ошибка может произойти.

EDIT3: Ответ на ваше третье изменение: помните, что Date и Calendar используют время Unix. Это означает, что время, представленное нулем, — это 1970 год. Все, что было до 1970 года, будет представлено отрицательным значением.

EDIT4: Помните, что классы календаря Java — отстой. Этот фрагмент кода демонстрирует, что ошибка на самом деле в классе Calendar:

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

Выход:

-125629491600000
1325545200000

Однако будет очень сложно отследить точную ошибку, которая вызывает это. Причина, по которой все эти ошибки остаются, заключается в том, что их исправление может привести к поломке устаревших систем по всему миру. Я предполагаю, что ошибка возникает из-за невозможности дать отрицательные годы. Это, например, даст вывод «2013»:

Calendar next = new GregorianCalendar();
next.set(Calendar.YEAR, -2012);
System.out.println(next.get(Calendar.YEAR));

Я бы просто рекомендовал вам не допускать таких экстремальных значений в вашем вводе. Определите приемлемый диапазон и выдайте сообщение об ошибке, если значение выходит за эти границы. Если вы хотите обрабатывать все возможные даты в другом приложении, просто используйте время joda. Вы не пожалеете :)

person pgsandstrom    schedule 12.06.2012
comment
Спасибо :) Тестовый блок с десятками миллионов дат вообще хорошая идея. Но поскольку DateFormat.parse() может производить только null или действительный объект Date, неудобный ввод, который вызывает эту ошибку, может быть только строкой даты, которая по ошибке интерпретируется как дата 4k лет назад, верно? - person caw; 14.06.2012
comment
Кажется, что getNextOccurrence() должен возвращать null, если дата в прошлом, поэтому одного этого недостаточно, чтобы объяснить большое отрицательное значение. Это также защищает от переполнения числа, которое происходит, если для cal установлен год Integer.MAX_VALUE. - person pgsandstrom; 14.06.2012
comment
Извиняюсь!!! Этого оператора if не было в исходном коде, я просто добавил его при попытке отладки приложения и забыл удалить его снова. Так что в коде, который выдавал отрицательные числа, этого не было. Таким образом, результат getNextOccurrence() раньше, чем текущее время, определенно является причиной. - person caw; 15.06.2012
comment
Нет, извините, для меня этот вопрос еще не решен ;) Смотрите мое последнее редактирование. - person caw; 15.06.2012
comment
Согласно вашему третьему редактированию... :) Конечно, я знаю, что время UNIX относится к 1970 году. Но если вы внимательно посмотрите на эти числа, вы увидите, что значение для 1900 ближе к отрицательной бесконечности, чем для значение для 0000. Этого не может быть! - person caw; 15.06.2012
comment
Большое спасибо :D Приятно видеть, что это не моя вина. - person caw; 15.06.2012
comment
Не могли бы вы наградить меня щедростью, так как вопрос был решен? :) - person pgsandstrom; 18.06.2012

Вы получили отрицательные значения в днях, которые могут быть связаны с тем, что пользователь ввел дату следующего события, которая является любой предыдущей датой.

Я думаю, что вы должны считать свои оставшиеся дни вот так,

            String inputDateString = "19/06/2012";
            Calendar calCurr = Calendar.getInstance();//current date
            Calendar calNext = Calendar.getInstance();// for next date
            calNext.setTime(new Date(inputDateString)); // or do set Day,Month,Year like in your Question

            if(calNext.after(calCurr)) // if the next date is after current
            {
                long timeDiff = calNext.getTimeInMillis() - calCurr.getTimeInMillis(); // time of next year if today is 15 june and some one enter 16 june for next occurance
                int daysLeft = (int) (timeDiff/DateUtils.DAY_IN_MILLIS); // Days Left
            }
            else
            {
                long timeDiff = calCurr.getTimeInMillis() - calNext.getTimeInMillis();
                timeDiff = DateUtils.YEAR_IN_MILLIS - timeDiff; // time of next year if today is 15 june and some one enter 14 june for next occurance
                int daysLeft = (int) (timeDiff/DateUtils.DAY_IN_MILLIS); // Days Left
            }
person MKJParekh    schedule 15.06.2012
comment
Спасибо! Но если вы посмотрите на мое последнее редактирование вопроса, вы увидите, что это не похоже на решение. - person caw; 15.06.2012
comment
@МаркоВ. По моему мнению, вы получаете отрицательные значения оставшихся дней, это может быть только из-за неправильных расчетов, поэтому я на 100% уверен, что эта проблема заключается только в этой части кода, вы действительно пробовали мой ответ? или просто пропустил это, так как он только выполняет расчеты. В любом случае я попытаюсь проанализировать ваш код/вопрос больше и опубликую что-нибудь, если получу. - person MKJParekh; 16.06.2012
comment
Ну, вы видели мои правки к вопросу? Как видите, класс Calendar, кажется, все портит и вычисляет неправильные временные метки. В противном случае getNextOccurrence() не могло быть раньше сегодняшнего дня. - person caw; 16.06.2012