web-dev-qa-db-fra.com

Comment obtenir le nombre de jours entre deux instances de calendrier?

Je veux trouver la différence entre deux objets Calendar en nombre de jours s'il y a un changement de date comme Si l'horloge a coché de 23: 59-0: 00 il devrait y avoir une différence de jour.

j'ai écrit ceci

public static int daysBetween(Calendar startDate, Calendar endDate) {  
    return Math.abs(startDate.get(Calendar.DAY_OF_MONTH)-endDate.get(Calendar.DAY_OF_MONTH));  
} 

mais cela ne fonctionne pas car il ne donne de différence entre les jours que s'il y a une différence de mois ce n'est pas la peine.

30
Akram

Dans Java 8 et versions ultérieures, nous pourrions simplement utiliser les classes Java.time .

hoursBetween = ChronoUnit.HOURS.between(calendarObj.toInstant(), calendarObj.toInstant());

daysBetween = ChronoUnit.DAYS.between(calendarObj.toInstant(), calendarObj.toInstant());
22
Mohamed Anees A

Essayez l'approche suivante:

public static long daysBetween(Calendar startDate, Calendar endDate) {
    long end = endDate.getTimeInMillis();
    long start = startDate.getTimeInMillis();
    return TimeUnit.MILLISECONDS.toDays(Math.abs(end - start));
}
30
Jk1

Cette fonction calcule le nombre de jours entre deux calendriers comme le nombre de jours calendaires du mois qui se trouvent entre eux, ce que l'OP voulait. Le calcul est effectué en comptant le nombre de multiples de 86 400 000 millisecondes entre les calendriers après que les deux ont été définis à minuit de leurs jours respectifs.

Par exemple, ma fonction calculera la différence d'un jour entre un calendrier le 1er janvier 23 h 59 et le 2 janvier 12 h 01.

import Java.util.concurrent.TimeUnit;

/**
 * Compute the number of calendar days between two Calendar objects. 
 * The desired value is the number of days of the month between the
 * two Calendars, not the number of milliseconds' worth of days.
 * @param startCal The earlier calendar
 * @param endCal The later calendar
 * @return the number of calendar days of the month between startCal and endCal
 */
public static long calendarDaysBetween(Calendar startCal, Calendar endCal) {

    // Create copies so we don't update the original calendars.

    Calendar start = Calendar.getInstance();
    start.setTimeZone(startCal.getTimeZone());
    start.setTimeInMillis(startCal.getTimeInMillis());

    Calendar end = Calendar.getInstance();
    end.setTimeZone(endCal.getTimeZone());
    end.setTimeInMillis(endCal.getTimeInMillis());

    // Set the copies to be at midnight, but keep the day information.

    start.set(Calendar.HOUR_OF_DAY, 0);
    start.set(Calendar.MINUTE, 0);
    start.set(Calendar.SECOND, 0);
    start.set(Calendar.MILLISECOND, 0);

    end.set(Calendar.HOUR_OF_DAY, 0);
    end.set(Calendar.MINUTE, 0);
    end.set(Calendar.SECOND, 0);
    end.set(Calendar.MILLISECOND, 0);

    // At this point, each calendar is set to midnight on 
    // their respective days. Now use TimeUnit.MILLISECONDS to
    // compute the number of full days between the two of them.

    return TimeUnit.MILLISECONDS.toDays(
            Math.abs(end.getTimeInMillis() - start.getTimeInMillis()));
}
8

[~ # ~] mise à jour [~ # ~] Le projet Joda-Time , maintenant en mode maintenance , conseille la migration vers les classes Java.time . Voir la réponse par Anees A pour le calcul des heures écoulées, et voir mon nouvelle réponse pour utiliser Java.time pour calculer les jours écoulés dans le respect du calendrier.

Joda-Time

Les anciennes classes Java.util.Date/.Calendar sont notoirement gênantes et doivent être évitées.

Utilisez plutôt la bibliothèque Joda-Time . Sauf si vous disposez de la technologie Java 8, auquel cas utilisez son successeur, le framework Java.time intégré (pas dans Android à partir de 2015).

Puisque vous ne vous souciez que des "jours" définis comme des dates (pas des périodes de 24 heures), concentrons-nous sur les dates. Joda-Time propose la classe LocalDate pour représenter une valeur de date uniquement sans heure ni fuseau horaire.

Bien qu'il manque un fuseau horaire, notez que le fuseau horaire est crucial pour déterminer une date telle que "aujourd'hui". Un nouveau jour se lève plus tôt à l'est qu'à l'ouest. La date n'est donc pas la même dans le monde à un moment donné, la date dépend de votre fuseau horaire.

DateTimeZone zone = DateTimeZone.forID ( "America/Montreal" );
LocalDate today = LocalDate.now ( zone );

Comptons le nombre de jours jusqu'à la semaine prochaine, qui devrait bien sûr être sept.

LocalDate weekLater = today.plusWeeks ( 1 );
int elapsed = Days.daysBetween ( today , weekLater ).getDays ();

Le getDays à la fin extrait un simple int nombre de l'objet Days retourné par daysBetween.

Dump sur console.

System.out.println ( "today: " + today + " to weekLater: " + weekLater + " is days: " + days );

aujourd'hui: 22/12/2015 à semaineDernier: 29/12/2015 est jours: 7

Vous avez des objets Calendrier. Nous devons les convertir en objets Joda-Time. En interne, les objets Calendar ont un long entier qui suit le nombre de millisecondes depuis l'époque du premier moment de 1970 en UTC . Nous pouvons extraire ce nombre et le transmettre à Joda-Time. Nous devons également attribuer le fuseau horaire souhaité par lequel nous avons l'intention de déterminer une date.

long startMillis = myStartCalendar.getTimeInMillis();
DateTime startDateTime = new DateTime( startMillis , zone );

long stopMillis = myStopCalendar.getTimeInMillis();
DateTime stopDateTime = new DateTime( stopMillis , zone );

Convertissez des objets DateTime en LocalDate.

LocalDate start = startDateTime.toLocalDate();
LocalDate stop = stopDateTime.toLocalDate();

Maintenant, faites le même calcul écoulé que nous avons vu plus tôt.

int elapsed = Days.daysBetween ( start , stop ).getDays ();
3
Basil Bourque

Extension à @ JK1 excellente réponse:

public static long daysBetween(Calendar startDate, Calendar endDate) {

    //Make sure we don't change the parameter passed
    Calendar newStart = Calendar.getInstance();
    newStart.setTimeInMillis(startDate.getTimeInMillis());
    newStart.set(Calendar.HOUR_OF_DAY, 0)
    newStart.set(Calendar.MINUTE, 0)
    newStart.set(Calendar.SECOND, 0)
    newStart.set(Calendar.MILLISECOND, 0)

    Calendar newEnd = Calendar.getInstance();
    newEnd.setTimeInMillis(endDate.getTimeInMillis());
    newEnd.set(Calendar.HOUR_OF_DAY, 0)
    newEnd.set(Calendar.MINUTE, 0)
    newEnd.set(Calendar.SECOND, 0)
    newEnd.set(Calendar.MILLISECOND, 0)

    long end = newEnd.getTimeInMillis();
    long start = newStart.getTimeInMillis();
    return TimeUnit.MILLISECONDS.toDays(Math.abs(end - start));
}
2
Steve Lukis

Java.time

--- Réponse de Mohamed Anees A est correct pour les heures mais faux pour les jours. Le comptage des jours nécessite un fuseau horaire . Cette autre réponse utilise le Instant qui est un moment en UTC, toujours en UTC. Donc vous pas obtenez le nombre correct de calendrier jours écoulés.

Pour compter les jours selon le calendrier, convertissez votre ancien Calendar en ZonedDateTime, puis alimentez en ChronoUnit.DAYS.between.

Fuseau horaire

Un fuseau horaire est crucial pour déterminer une date. Pour un moment donné, la date varie dans le monde par zone. Par exemple, quelques minutes après minuit à Paris France est un nouveau jour alors qu'il est encore "hier" à Montréal Québec .

Si aucun fuseau horaire n'est spécifié, la JVM applique implicitement son fuseau horaire par défaut actuel. Cette valeur par défaut peut changer à tout moment pendant l'exécution (!), Donc vos résultats peuvent varier. Mieux vaut spécifier votre fuseau horaire souhaité/attendu explicitement comme argument. Si critique, confirmez la zone avec votre utilisateur.

Spécifiez un nom de fuseau horaire correct au format Continent/Region, tel que America/Montreal, Africa/Casablanca, ou Pacific/Auckland. N'utilisez jamais les abréviations de 2 à 4 lettres telles que EST ou IST car elles sont pas vrais fuseaux horaires, non standardisés et même pas uniques (!) .

ZoneId z = ZoneId.of( "America/Montreal" ) ;  
LocalDate today = LocalDate.now( z ) ;           // Capture the current date as seen through the wall-clock time used by the people of a certain region (a time zone).

Si vous souhaitez utiliser le fuseau horaire par défaut de la JVM, demandez-le et passez-le en argument. S'il est omis, le code devient ambigu à lire car nous ne savons pas avec certitude si vous aviez l'intention d'utiliser la valeur par défaut ou si, comme tant de programmeurs, vous n'étiez pas au courant du problème.

ZoneId z = ZoneId.systemDefault() ;              // Get JVM’s current default time zone.

Convertir de GregorianCalendar en ZonedDateTime

Le terrible GregorianCalendar est probablement la classe concrète derrière votre Calendar. Si c'est le cas, convertissez de cette classe héritée en classe moderne, ZonedDateTime.

GregorianCalendar gc = null ;                    // Legacy class representing a moment in a time zone. Avoid this class as it is terribly designed.
if( myCal instanceof GregorianCalendar ) {       // See if your `Calendar` is backed by a `GregorianCalendar` class.
    gc = (GregorianCalendar) myCal ;             // Cast from the more general class to the concrete class.
    ZonedDateTime zdt = gc.toZonedDateTime() ;   // Convert from legacy class to modern class.
}

L'objet ZonedDateTime résultant porte un objet ZoneId pour le fuseau horaire. Avec cette zone en place, vous pouvez ensuite calculer les jours calendaires écoulés.

Calculer les jours écoulés

Pour calculer le temps écoulé en termes d'années-mois-jours, utilisez la classe Period.

Period p = Period.between( zdtStart , zdtStop ) ;

Si vous voulez le nombre total de jours comme temps écoulé, us e ChronoUnit .

 long days = ChronoUnit.DAYS.between( zdtStart , zdtStop ) ;

Table of types of date-time classes in modern Java.time versus legacy


À propos de Java.time

Le cadre Java.time est intégré à Java 8 et versions ultérieures. Ces classes supplantent l'ancien hérité) gênant classes date-heure telles que Java.util.Date , Calendar , & SimpleDateFormat .

Pour en savoir plus, consultez le Tutoriel Oracle . Et recherchez Stack Overflow pour de nombreux exemples et explications. La spécification est JSR 31 .

Le projet Joda-Time , maintenant en mode maintenance , conseille la migration vers les classes Java.time .

Vous pouvez échanger des objets Java.time directement avec votre base de données. Utilisez un pilote JDBC compatible avec JDBC 4.2 ou version ultérieure. Pas besoin de chaînes, pas besoin de Java.sql.* Des classes.

Où obtenir les classes Java.time?

Le projet ThreeTen-Extra étend Java.time avec des classes supplémentaires. Ce projet est un terrain d'essai pour de futurs ajouts possibles à Java.time. Vous pouvez trouver ici des classes utiles telles que Interval , YearWeek , YearQuarter , et plus .

1
Basil Bourque

Voici ma solution en utilisant de bons vieux objets Calendar:

public static int daysApart(Calendar d0,Calendar d1)
{
    int days=d0.get(Calendar.DAY_OF_YEAR)-d1.get(Calendar.DAY_OF_YEAR);
    Calendar d1p=Calendar.getInstance();
    d1p.setTime(d1.getTime());
    for (;d1p.get(Calendar.YEAR)<d0.get(Calendar.YEAR);d1p.add(Calendar.YEAR,1))
    {
        days+=d1p.getActualMaximum(Calendar.DAY_OF_YEAR);
    }
    return days;
}

Cela suppose que d0 est postérieur à d1. Si ce n'est pas garanti, vous pouvez toujours les tester et les échanger.

Le principe de base est de prendre la différence entre le jour de l'année de chacun. S'ils sont dans la même année, ce serait tout.

Mais ce pourrait être des années différentes. Je passe donc en revue toutes les années entre elles, en ajoutant le nombre de jours dans une année. Notez que getActualMaximum renvoie 366 années bissextiles et 365 années non bissextiles. C'est pourquoi nous avons besoin d'une boucle, vous ne pouvez pas simplement multiplier la différence entre les années par 365, car il pourrait y avoir une année bissextile. (Mon premier projet utilisait getMaximum, mais cela ne fonctionne pas car il renvoie 366 quelle que soit l'année. GetMaximum est le maximum pour N'IMPORTE QUELLE année, pas cette année particulière.)

Comme ce code ne fait aucune hypothèse sur le nombre d'heures dans une journée, il n'est pas dupe de l'heure d'été.

1
Jay

Si votre projet ne prend pas en charge les nouvelles classes Java 8 (comme réponse sélectionnée), vous pouvez ajouter cette méthode pour calculer les jours sans être influencé par les fuseaux horaires ou d'autres faits.

Elle n'est pas aussi rapide (plus grande complexité temporelle) que d'autres méthodes mais elle est fiable, de toute façon les comparaisons de dates sont rarement plus grandes que des centaines ou des milliers d'années.

(Kotlin)

/**
     * Returns the number of DAYS between two dates. Days are counted as calendar days
     * so that tomorrow (from today date reference) will be 1 ,  the day after 2 and so on
     * independent on the hour of the day.
     *
     * @param date - reference date, normally Today
     * @param selectedDate - date on the future
     */
fun getDaysBetween(date: Date, selectedDate: Date): Int {

            val d = initCalendar(date)
            val s = initCalendar(selectedDate)
            val yd = d.get(Calendar.YEAR)
            val ys = s.get(Calendar.YEAR)

            if (ys == yd) {
                return s.get(Calendar.DAY_OF_YEAR) - d.get(Calendar.DAY_OF_YEAR)
            }

            //greater year
            if (ys > yd) {
                val endOfYear = Calendar.getInstance()
                endOfYear.set(yd, Calendar.DECEMBER, 31)
                var daysToFinish = endOfYear.get(Calendar.DAY_OF_YEAR) - d.get(Calendar.DAY_OF_YEAR)
                while (endOfYear.get(Calendar.YEAR) < s.get(Calendar.YEAR)-1) {
                    endOfYear.add(Calendar.YEAR, 1)
                    daysToFinish += endOfYear.get(Calendar.DAY_OF_YEAR)
                }
                return daysToFinish + s.get(Calendar.DAY_OF_YEAR)
            }

            //past year
            else {
                val endOfYear = Calendar.getInstance()
                endOfYear.set(ys, Calendar.DECEMBER, 31)
                var daysToFinish = endOfYear.get(Calendar.DAY_OF_YEAR) - s.get(Calendar.DAY_OF_YEAR)
                while (endOfYear.get(Calendar.YEAR) < d.get(Calendar.YEAR)-1) {
                    endOfYear.add(Calendar.YEAR, 1)
                    daysToFinish += endOfYear.get(Calendar.DAY_OF_YEAR)
                }
                return daysToFinish + d.get(Calendar.DAY_OF_YEAR)
            }
        }

Tests unitaires, vous pouvez les améliorer, je n'ai pas eu besoin des jours négatifs, donc je n'ai pas testé autant:

@Test
    fun `Test days between on today and following days`() {
        val future = Calendar.getInstance()
        calendar.set(2019, Calendar.AUGUST, 26)

        future.set(2019, Calendar.AUGUST, 26)
        Assert.assertEquals(0, manager.getDaysBetween(calendar.time, future.time))

        future.set(2019, Calendar.AUGUST, 27)
        Assert.assertEquals(1, manager.getDaysBetween(calendar.time, future.time))

        future.set(2019, Calendar.SEPTEMBER, 1)
        Assert.assertEquals(6, manager.getDaysBetween(calendar.time, future.time))

        future.set(2020, Calendar.AUGUST, 26)
        Assert.assertEquals(366, manager.getDaysBetween(calendar.time, future.time)) //leap year

        future.set(2022, Calendar.AUGUST, 26)
        Assert.assertEquals(1096, manager.getDaysBetween(calendar.time, future.time))

        calendar.set(2019, Calendar.DECEMBER, 31)
        future.set(2020, Calendar.JANUARY, 1)
        Assert.assertEquals(1, manager.getDaysBetween(calendar.time, future.time))
    }

    @Test
    fun `Test days between on previous days`() {
        val future = Calendar.getInstance()
        calendar.set(2019, Calendar.AUGUST, 26)

        future.set(2019,Calendar.AUGUST,25)
        Assert.assertEquals(-1, manager.getDaysBetween(calendar.time, future.time))
    }

    @Test
    fun `Test days between hour doesn't matter`() {
        val future = Calendar.getInstance()
        calendar.set(2019, Calendar.AUGUST, 26,9,31,15)

        future.set(2019,Calendar.AUGUST,28, 7,0,0)
        Assert.assertEquals(2, manager.getDaysBetween(calendar.time, future.time))

        future.set(2019,Calendar.AUGUST,28, 9,31,15)
        Assert.assertEquals(2, manager.getDaysBetween(calendar.time, future.time))

        future.set(2019,Calendar.AUGUST,28, 23,59,59)
        Assert.assertEquals(2, manager.getDaysBetween(calendar.time, future.time))
    }

    @Test
    fun `Test days between with time saving change`() {
        val future = Calendar.getInstance()
        calendar.set(2019, Calendar.OCTOBER, 28)

        future.set(2019, Calendar.OCTOBER,29)
        Assert.assertEquals(1, manager.getDaysBetween(calendar.time, future.time))

        future.set(2019, Calendar.OCTOBER,30)
        Assert.assertEquals(2, manager.getDaysBetween(calendar.time, future.time))
    }
0
htafoya

La solution Kotlin, repose uniquement sur Calendar. À la fin, donne le nombre exact de jours de différence. Inspiré par @ Jk1

 private fun daysBetween(startDate: Calendar, endDate: Calendar): Long {
        val start = Calendar.getInstance().apply {
            timeInMillis = 0
            set(Calendar.DAY_OF_YEAR, startDate.get(Calendar.DAY_OF_YEAR))
            set(Calendar.YEAR, startDate.get(Calendar.YEAR))
        }.timeInMillis
        val end = Calendar.getInstance().apply {
            timeInMillis = 0
            set(Calendar.DAY_OF_YEAR, endDate.get(Calendar.DAY_OF_YEAR))
            set(Calendar.YEAR, endDate.get(Calendar.YEAR))
        }.timeInMillis
        val differenceMillis = end - start
        return TimeUnit.MILLISECONDS.toDays(differenceMillis)
    }

J'ai l'approche similaire (pas exactement la même) donnée ci-dessus par https://stackoverflow.com/a/31800947/3845798 .

Et j'ai écrit des cas de test autour de l'API, pour moi, cela a échoué si j'ai passé le 8 mars 2017 - comme date de début et le 8 avril 2017 comme date de fin.

Il y a peu de dates où vous verrez la différence d'ici 1 jour. Par conséquent, j'ai en quelque sorte apporté quelques petites modifications à mon API et mon API actuelle ressemble maintenant à ceci

   public long getDays(long currentTime, long endDateTime) {

Calendar endDateCalendar;
Calendar currentDayCalendar;


//expiration day
endDateCalendar = Calendar.getInstance(TimeZone.getTimeZone("EST"));
endDateCalendar.setTimeInMillis(endDateTime);
endDateCalendar.set(Calendar.MILLISECOND, 0);
endDateCalendar.set(Calendar.MINUTE, 0);
endDateCalendar.set(Calendar.HOUR, 0);
endDateCalendar.set(Calendar.HOUR_OF_DAY, 0);

//current day
currentDayCalendar = Calendar.getInstance(TimeZone.getTimeZone("EST"));
currentDayCalendar.setTimeInMillis(currentTime);
currentDayCalendar.set(Calendar.MILLISECOND, 0);
currentDayCalendar.set(Calendar.MINUTE, 0);
currentDayCalendar.set(Calendar.HOUR,0);
currentDayCalendar.set(Calendar.HOUR_OF_DAY, 0);


long remainingDays = (long)Math.ceil((float) (endDateCalendar.getTimeInMillis() - currentDayCalendar.getTimeInMillis()) / (24 * 60 * 60 * 1000));

return remainingDays;}

Je n'utilise pas TimeUnit.MILLISECONDS.toDays qui me posaient des problèmes.

0
Arpit