Дней Android между двумя датами

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

Например:

Если я установлю дату back in the past на 127 дней назад:

this.dateEvent = System.currentTimeMillis() - (127 * 24 * 3600 * 1000)

А затем сравните его с текущей датой (дни между)

    Calendar sDate = getDatePart(new Date(this.dateEvent));
    Calendar eDate = getDatePart(new Date(System.currentTimeMillis()));

    int daysBetween = 0;
    while (sDate.before(eDate))
    {
        sDate.add(Calendar.DAY_OF_MONTH, 1);
        daysBetween ++;
    }

    while (sDate.after(eDate))
    {
        eDate.add(Calendar.DAY_OF_MONTH, 1);
        daysBetween ++;
    }

    return daysBetween;

Он вернет 22, что совсем не то, что ожидалось.

Я сделал что-то не так или это проблема с классом Calendar?


person Manitoba    schedule 27.04.2014    source источник
comment
Как выглядит объявление dateEvent?   -  person keyser    schedule 27.04.2014
comment
Вы всегда добавляете DAY_OF_MONTH (поэтому после 31, 30, 28 или 29 он перезапускает отсчет с 1) вместо DAY. И в первом случае я бы добавил ОТРИЦАТЕЛЬНОЕ число (-1)   -  person Phantômaxx    schedule 27.04.2014
comment
Здравствуйте, dateEvent объявлен с private long dateEvent = 0L;. Я также пытался заменить DAY_OF_MONTH на DAY_OF_YEAR, но это не решило проблему.   -  person Manitoba    schedule 27.04.2014
comment
Я рекомендую вам использовать java.time, современный API даты и времени Java, для работы с датами. Класс Calendar плохо разработан и давно устарел, поэтому не используйте его. С java.time не только намного приятнее работать, в отличие от Calendar, у него есть прямая поддержка для определения количества дней между двумя датами.   -  person Ole V.V.    schedule 06.04.2020
comment
я дал очень простое решение здесь stackoverflow.com/a/65551309/10390808   -  person Abhinav Chauhan    schedule 04.01.2021


Ответы (15)


Пожалуйста, обратитесь к этому коду, это может вам помочь.

public String getCountOfDays(String createdDateString, String expireDateString) {
    SimpleDateFormat dateFormat = new SimpleDateFormat("dd/MM/yyyy", Locale.getDefault());

    Date createdConvertedDate = null, expireCovertedDate = null, todayWithZeroTime = null;
    try {
        createdConvertedDate = dateFormat.parse(createdDateString);
        expireCovertedDate = dateFormat.parse(expireDateString);

        Date today = new Date();

        todayWithZeroTime = dateFormat.parse(dateFormat.format(today));
    } catch (ParseException e) {
        e.printStackTrace();
    }

    int cYear = 0, cMonth = 0, cDay = 0;

    if (createdConvertedDate.after(todayWithZeroTime)) {
        Calendar cCal = Calendar.getInstance();
        cCal.setTime(createdConvertedDate);
        cYear = cCal.get(Calendar.YEAR);
        cMonth = cCal.get(Calendar.MONTH);
        cDay = cCal.get(Calendar.DAY_OF_MONTH);

    } else {
        Calendar cCal = Calendar.getInstance();
        cCal.setTime(todayWithZeroTime);
        cYear = cCal.get(Calendar.YEAR);
        cMonth = cCal.get(Calendar.MONTH);
        cDay = cCal.get(Calendar.DAY_OF_MONTH);
    }


    /*Calendar todayCal = Calendar.getInstance();
    int todayYear = todayCal.get(Calendar.YEAR);
    int today = todayCal.get(Calendar.MONTH);
    int todayDay = todayCal.get(Calendar.DAY_OF_MONTH);
    */

    Calendar eCal = Calendar.getInstance();
    eCal.setTime(expireCovertedDate);

    int eYear = eCal.get(Calendar.YEAR);
    int eMonth = eCal.get(Calendar.MONTH);
    int eDay = eCal.get(Calendar.DAY_OF_MONTH);

    Calendar date1 = Calendar.getInstance();
    Calendar date2 = Calendar.getInstance();

    date1.clear();
    date1.set(cYear, cMonth, cDay);
    date2.clear();
    date2.set(eYear, eMonth, eDay);

    long diff = date2.getTimeInMillis() - date1.getTimeInMillis();

    float dayCount = (float) diff / (24 * 60 * 60 * 1000);

    return ("" + (int) dayCount + " Days");
}
person EminenT    schedule 27.04.2014
comment
Здравствуйте, спасибо, ошибка, которая не будет работать, если date1 указывает на ту же дату, что и date2 - person Manitoba; 27.04.2014
comment
Именно то, что я искал. Спасибо! long diff = date2.getTimeInMillis() - date1.getTimeInMillis(); float dayCount = (float) diff / (24 * 60 * 60 * 1000); - person Bram; 03.01.2015
comment
Но это можно подделать, если пользователь вручную изменит дату устройства... как это обойти? - person statosdotcom; 17.04.2017
comment
@statosdotcom устанавливает дату на вашем сервере в формате unix и обновляет ее ежедневно - person Izhan Ali; 11.06.2021

Вот двухстрочное решение:

long msDiff = Calendar.getInstance().getTimeInMillis() - testCalendar.getTimeInMillis();
long daysDiff = TimeUnit.MILLISECONDS.toDays(msDiff);

В этом примере он получает количество дней между датой «testCalendar» и текущей датой.

person The Berga    schedule 06.06.2016
comment
Ах, как раз то, что я искал. Спасибо! - person Tobias Reich; 31.05.2017
comment
уууууу !! Спасибо :) - person gunjot singh; 10.06.2020
comment
у него есть проблема с часовыми поясами для перехода на летнее время - person Amir Hossein Ghasemi; 30.06.2020

Наконец-то я нашел самый простой способ справиться с этим. Вот мой код:

public int getTimeRemaining()
{
    Calendar sDate = toCalendar(this.dateEvent);
    Calendar eDate = toCalendar(System.currentTimeMillis());

    // Get the represented date in milliseconds
    long milis1 = sDate.getTimeInMillis();
    long milis2 = eDate.getTimeInMillis();

    // Calculate difference in milliseconds
    long diff = Math.abs(milis2 - milis1);

    return (int)(diff / (24 * 60 * 60 * 1000));
}

private Calendar toCalendar(long timestamp)
{
    Calendar calendar = Calendar.getInstance();
    calendar.setTimeInMillis(timestamp);
    calendar.set(Calendar.HOUR_OF_DAY, 0);
    calendar.set(Calendar.MINUTE, 0);
    calendar.set(Calendar.SECOND, 0);
    calendar.set(Calendar.MILLISECOND, 0);
    return calendar;
}

Надеюсь, поможет.

person Manitoba    schedule 27.04.2014
comment
Это сработало для меня после того, как я изменил Calendar.HOUR на Calendar.HOUR_OF_DAY - person ePeace; 18.12.2015
comment
Еще одна запутанная черта плохо спроектированного класса Calendar: HOUR не означает HOUR_OF_DAY. @ePeace - person Ole V.V.; 14.03.2020

Вы никогда не должны использовать такую ​​формулу, как 24 * 60 * 60 * 1000! Почему? Потому что есть дневное время, и не все дни имеют 24 часа, а что касается високосного года, у которого есть +1 день. Вот почему существует класс календаря. Если вы не хотите добавлять в свой проект какую-либо внешнюю библиотеку, например Jodatime, вы можете использовать чистый класс Calendar с очень эффективной функцией:

public static int numDaysBetween(final Calendar c, final long fromTime, final long toTime) {
    int result = 0;
    if (toTime <= fromTime) return result;

    c.setTimeInMillis(toTime);
    final int toYear = c.get(Calendar.YEAR);
    result += c.get(Calendar.DAY_OF_YEAR);

    c.setTimeInMillis(fromTime);
    result -= c.get(Calendar.DAY_OF_YEAR);

    while (c.get(Calendar.YEAR) < toYear) {
        result += c.getActualMaximum(Calendar.DAY_OF_YEAR);
        c.add(Calendar.YEAR, 1);
    }

    return result;
}
person Oleksandr Albul    schedule 09.03.2018
comment
Это лучший ответ! - person Rodrigo Gontijo; 03.07.2018
comment
Это сработало для меня. - person Oru; 27.12.2020

У меня была такая же потребность, я, наконец, остановился на Joda Time, это очень удобно и предлагает множество дополнительных функций, включая ту, которую вы ищете.

Вы можете загрузить файлы с здесь.

После того, как вы включили файл jar в свой проект, вы можете легко сделать, например, следующее:

int daysBetween = Days.daysBetween(new DateTime(sDate), new DateTime(eDate)).getDays();
person Yoann Hercouet    schedule 27.04.2014
comment
Библиотека времени Joda добавит в ваш проект 4744 метода. Выбирайте с умом, если хотите избежать ограничения в 65 000 методов. - person Lior Iluz; 22.12.2015
comment
Добавлять банки действительно неразумно, только для простого расчета. Остерегайтесь ограничения методов, как упоминал @LiorIluz. - person sud007; 13.01.2016

лучший путь :-

        long fromCalender = Calender.getInstance();
        fromCalender.set...// set the from dates
        long toCalender = Calender.getInstance();
        fromCalender.set...// set the to dates

        long diffmili = fromCalender - toCalender;

        long hours = TimeUnit.MILLISECONDS.toHours(diffmili);
        long days = TimeUnit.MILLISECONDS.toDays(diffmili);
        long min = TimeUnit.MILLISECONDS.toMinutes(diffmili);
        long sec = TimeUnit.MILLISECONDS.toSeconds(diffmili);
person Akshay Paliwal    schedule 13.04.2015
comment
Вы должны протестировать свое решение, прежде чем публиковать его, есть ошибка копирования/вставки и опечатка в календаре. - person Bogy; 10.09.2019

Лучшее решение, которое сработало для меня, это:

private static int findDaysDiff(long unixStartTime,long unixEndTime)
    {
        Calendar calendar1 = Calendar.getInstance();
        calendar1.setTimeInMillis(unixStartTime);
        calendar1.set(Calendar.HOUR_OF_DAY, 0);
        calendar1.set(Calendar.MINUTE, 0);
        calendar1.set(Calendar.SECOND, 0);
        calendar1.set(Calendar.MILLISECOND, 0);

        Calendar calendar2 = Calendar.getInstance();
        calendar2.setTimeInMillis(unixEndTime);
        calendar2.set(Calendar.HOUR_OF_DAY, 0);
        calendar2.set(Calendar.MINUTE, 0);
        calendar2.set(Calendar.SECOND, 0);
        calendar2.set(Calendar.MILLISECOND, 0);

        return (int) ((calendar2.getTimeInMillis()-calendar1.getTimeInMillis())/(24 * 60 * 60 * 1000));

    }

Так как он сначала конвертирует Hour, Minute, Second и Millisecond в 0 и теперь разница будет только в днях.

person Pradeep Kumar Kushwaha    schedule 08.03.2017
comment
сработало как шарм так, как я хотел, спасибо! - person coding.cat3; 06.03.2021

fun TimeZone.daysBetween(from: Date, to: Date): Int {
    val offset = rawOffset + dstSavings
    return ((to.time + offset) / 86400000).toInt() - ((from.time + offset) / 86400000).toInt()
}

Попробуй:

    val f = SimpleDateFormat("yyyy-MM-dd HH:mm:ss").apply {
        timeZone = TimeZone.getTimeZone("GMT")
    }
    val df = f.parse("2019-02-28 22:59:59")
    val dt = f.parse("2019-02-28 23:00:00")

    TimeZone.getTimeZone("GMT").daysBetween(df, dt)  // 0
    TimeZone.getTimeZone("GMT+1").daysBetween(df, dt) // 1
person Community    schedule 25.12.2018

java.time и ThreeTenABP

Я хотел бы внести современный ответ: используйте java.time, современный API даты и времени Java для вашей работы с датами. Если разрабатываете для Android API level 25 и ниже, то через бэкпорт для Android, ThreeTenABP (ссылка внизу).

    LocalDate eDate = LocalDate.now(ZoneId.of("Europe/Paris"));
    LocalDate sDate = eDate.minusDays(127);

    long daysBetween = ChronoUnit.DAYS.between(sDate, eDate);
    System.out.println(daysBetween);

Когда я запустил этот код сегодня, результат был ожидаемым:

127

Обратите внимание, что код не только короче, но и всего одна строка для поиска разницы; это также яснее и естественнее для чтения. Классы Date и Calendar, которые вы использовали, плохо спроектированы и давно устарели. Я рекомендую вам не использовать их.

Что пошло не так в вашем коде?

У вас int переполнение при преобразовании 127 дней в миллисекунды. В математике 127 * 24 * 3600 * 1000 равно 10 972 800 000. Поскольку числа, которые вы умножаете, равны int, Java выполняет умножение в int, а наибольшее число, которое может содержать int, равно 2 147 483 647, что далеко не достаточно для ожидаемого результат. В этой ситуации было бы неплохо, если бы Java выдал исключение или каким-то другим образом сообщил нам об ошибке. Это не так. Он неявно отбрасывает старшие биты, что дает нам результат -1 912 101 888. Вычитание этого отрицательного числа из текущего времени эквивалентно добавлению 22 дней и нескольких часов. Это объясняет, почему вы получили 22. Забавно, что было опубликовано 13 ответов, и кажется, что никто этого не заметил…

Однако даже при выполнении умножения с использованием типа long 127 дней по-прежнему вычисляются неправильно. Если 127 дней пересекают переход на летнее время (DST) или обратно, что во Франции имеет место в течение 254 из 365 дней в году, днем ​​перехода будет не 24 часа, а либо 23, либо 25 часов. Что вызывает неправильное количество миллисекунд.

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

Вопрос: Разве для java.time не требуется Android API уровня 26?

java.time прекрасно работает как на старых, так и на новых устройствах Android. Просто требуется как минимум Java 6.

  • В Java 8 и более поздних версиях, а также на более новых устройствах Android (начиная с уровня API 26) современный API встроен.
  • В не-Android Java 6 и 7 получите ThreeTen Backport, бэкпорт современных классов (ThreeTen для JSR 310; см. ссылки внизу).
  • На (старом) Android используйте версию ThreeTen Backport для Android. Он называется ThreeTenABP. И убедитесь, что вы импортируете классы даты и времени из org.threeten.bp с подпакетами.

Ссылки

person Ole V.V.    schedule 14.03.2020
comment
Обновление: java.time теперь доступен для API ‹26 без ThreeTehnABP. Вам просто нужно использовать Android 4.0 и добавить простую конфигурацию: developer.android.com/studio/preview/features#j8-desugar - person 7200rpm; 22.04.2020

Делайте так, он поддерживает все уровни API.

    Calendar cal = Calendar.getInstance();
    SimpleDateFormat sdf = new SimpleDateFormat("MMM dd yyyy HH:mm:ss", 
    Locale.ENGLISH);
    try {
        String datestart="June 14 2018 16:02:37";
        cal.setTime(sdf.parse(datestart));// all done
         Calendar cal1=Calendar.getInstance();
        String formatted = sdf.format(cal1.getTime());//formatted date as i want
        cal1.setTime(sdf.parse(formatted));// all done

        long msDiff = cal1.getTimeInMillis() - cal.getTimeInMillis();
        long daysDiff = TimeUnit.MILLISECONDS.toDays(msDiff);
        Toast.makeText(this, "days="+daysDiff, Toast.LENGTH_SHORT).show();
    } catch (ParseException e) {
        e.printStackTrace();
    }
person Syed Danish Haider    schedule 09.07.2018

ответ неверен в некоторых датах, таких как «2019/02/18», «2019/02/19», но я редактирую и исправляю ошибку

это лучший метод:

 public int getCountOfDays(String createdDateString, String expireDateString) {

        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");

        Date createdConvertedDate = null;
        Date expireCovertedDate = null;
        try {
            createdConvertedDate = dateFormat.parse(createdDateString);
            expireCovertedDate = dateFormat.parse(expireDateString);
        } catch (ParseException e) {
            e.printStackTrace();
        }


        Calendar start = new GregorianCalendar();
        start.setTime(createdConvertedDate);

        Calendar end = new GregorianCalendar();
        end.setTime(expireCovertedDate);

        long diff = end.getTimeInMillis() - start.getTimeInMillis();

        float dayCount = (float) diff / (24 * 60 * 60 * 1000);


        return (int) (dayCount);
    }

Наслаждайтесь и, если это было полезно, + проголосуйте за этот ответ;)

person Erfan    schedule 09.04.2018

Расширение Kotlin:

fun Date?.getDaysBetween(dest: Date?): Int {

    if(this == null || dest == null) return 0

    val diff = abs(this.time - dest.time)
    val dayCount = diff.toFloat() / (24 * 60 * 60 * 1000)
    return dayCount.toInt()
}
person Amir Hossein Ghasemi    schedule 30.06.2020

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

 var calendar=Calendar.getInstance().time
 var dateFormat= SimpleDateFormat("dd/M/yyyy")
 var d2=dateFormat.parse(data.get("date").toString())
 var cd=dateFormat.format(calendar)
 var d1=dateFormat.parse(cd)
 var diff=d2.time-d1.time
 var ddd= TimeUnit.DAYS.convert(diff, TimeUnit.MILLISECONDS)
person Suvidha Malaviya    schedule 01.04.2019

Это версия Java 8 java.time, которая отлично работает для меня. Возможно, вы захотите убедиться, что startDate и endDate установлены на одно и то же время, иначе дни могут отличаться на +-1! Это версии Kotlin, которые я только что скопировал/вставил.

private fun getDawnOfDay(instant: Instant): Temporal =
        LocalDate.from(instant.atZone(ZoneOffset.UTC)).atStartOfDay()

fun getNumberOfDaysInBetween(startDate: Date, endDate: Date) =
        Duration.between(getDawnOfDay(startDate.toInstant()), getDawnOfDay(endDate.toInstant()))
            .toDays()
person postfixNotation    schedule 17.06.2020
comment
Использование java.time - хорошая идея (как я также рекомендую в своем ответе). Однако ваш способ сделать это не всегда будет точным. Duration предполагает, что в сутках всегда 24 часа. В сутках не всегда 24 часа. Типичным контрпримером является форвардная весна, где в сутках всего 23 часа. Если ваш интервал приходится на эту весну вперед, вы будете считать на один день меньше, даже если startDate и endDate имеют одно и то же время суток. - person Ole V.V.; 17.06.2020
comment
Привет и большое спасибо за ваш полезный комментарий! Я только что отредактировал свой ответ, установив для обоих значений значение UTC-зоны. Как вы думаете, это может решить проблему, я имею в виду установку обоих в UTC, чтобы просто получить правильное количество дней между ними? Спасибо! - person postfixNotation; 19.06.2020
comment
Извините, это не нормально. Я установил свой часовой пояс на Европу/Берлин. У меня есть два объекта Date, вс 29 марта 01:30:00 CET 2020 и пн 30 марта 01:30:00 CEST 2020. Вы видите, что время суток одинаковое. Ожидаемое количество дней между: 1. Дней между согласно вашему методу: 0. - person Ole V.V.; 19.06.2020
comment
Вы можете использовать ChronoUnit.DAYS.between(startDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDate(), endDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDate()). Не стесняйтесь публиковать в своем ответе, и я буду голосовать. Конечно, вы можете обернуть преобразование из Date в LocalDate во вспомогательный метод так же, как вы это уже делали. - person Ole V.V.; 19.06.2020
comment
Спасибо за ваши полезные комментарии! Я пробовал следующее: val zoneId = ZoneId.systemDefault(), val date1 = LocalDateTime.of(2020, Month.MARCH, 29, 1, 30, 0, 0), val date2 = LocalDateTime.of(2020, Month.MARCH, 30, 1, 30, 0, 0), val zonedDate1 = ZonedDateTime.of(date1, zoneId), val zonedDate2 = ZonedDateTime.of(date2, zoneId), что составляет 0 дней между ними, так как я в Берлине: Duration.between(zonedDate1, zonedDate2).toDays() - person postfixNotation; 22.06.2020
comment
Но если я сделаю то же самое с ZoneId.of("UTC") как zoneId, я получу правильный результат 1 день. В приведенном выше методе моего ответа instant.atZone(ZoneOffset.UTC) фактически подразумевается как константа (всегда UTC). Поэтому я не собирался использовать это как переменную. - person postfixNotation; 22.06.2020
comment
Насколько я знаю, объект Date вообще не хранит часовой пояс, это в основном момент времени, полностью независимый от часового пояса. Я могу ошибаться, или я просто не понимаю проблему, которую вы описываете. - person postfixNotation; 22.06.2020
comment
Вы правы, Date - это момент времени, не зависящий от часового пояса. - person Ole V.V.; 22.06.2020

Я только что немного изменил самый популярный ответ. Вот мое решение: daysBetween() — возвращает количество дней между двумя датами.

public static long daysBetween(Date date1, Date date2) {
        long msDiff = resetTimeToDateStart(date1).getTime() - resetTimeToDateStart(date2).getTime();
        return TimeUnit.MILLISECONDS.toDays(msDiff);
    }
private static Date resetTimeToDateStart(Date dDate){
        if (Utils.isNull(dDate)){
            return null;
        }
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(dDate);
        calendar.set(Calendar.HOUR_OF_DAY, 0);
        calendar.set(Calendar.MINUTE, 0);
        calendar.set(Calendar.SECOND, 0);
        calendar.set(Calendar.MILLISECOND, 0);
        return calendar.getTime();
    }
person Russia Drone Flights    schedule 18.12.2020
comment
Я, наверное, повторяюсь. (1) классы Calendar и Date плохо спроектированы и давно устарели, не рекомендую их использовать. (2) Ваш расчет предполагает, что в сутках всегда 24 часа. Иногда день может быть короче, например, 23 часа, поэтому ваш код иногда будет считать на 1 день меньше. - person Ole V.V.; 18.12.2020