Semaine

Tout le monde sait qu’une semaine dure 7 jours ! Et même en Java, c’est simple à vérifier.

LocalDate start = LocalDate.parse("2000-01-01");
System.out.println(
    ChronoUnit.DAYS.between(start, start.plus(1, ChronoUnit.WEEKS))
);
// => 7 jours

Si on prend un peu de recul, on peut se rappeler que le calendrier révolutionnaire français avait une semaine de 10 jours. Ça prouve que la durée de 7 jours est arbitraire et qu’il n’est pas exclu que des calendriers s’en éloignent. Et quand on parle de la durée d’un mois ou d’une année, les exemples sont plus faciles à trouver avec les calendriers lunaires.

Voyons comment ça peut se traduire pour un développeur Java…​

Chronology

En Java, la notion de calendrier est implémentée sous la forme d’instances de Chronology.

Le calendrier grégorien, à la base du standard ISO 8601, est utilisé par défaut en Java. Ainsi, quand on utilise une LocalDate ou une ZonedDateTime, c’est basé sur IsoChronology.

System.out.println(LocalDate.now());
// => 2021-07-13

Java 11 supporte 4 autres chronologies : Hijrah-umalqura, Japanese, Minguo et Thai Buddhist.

System.out.println(HijrahDate.now());
// => Hijrah-umalqura AH 1442-12-03

Et en intégrant la librairie ThreeTen Extra, on récupère une dizaine d’autres chronologies, parfois un peu exotiques.

Combien de temps dure un mois ?

Pour étudier la durée d’un mois ou d’une année, commençons par regarder les différences entre calendrier solaire et lunaire.

LocalDate isoStart = LocalDate.parse("2000-03-01");
System.out.println(
    ChronoUnit.DAYS.between(isoStart, isoStart.plus(1, ChronoUnit.MONTHS))
);
// => 1 mois = 31 jours
System.out.println(
    ChronoUnit.DAYS.between(isoStart, isoStart.plus(1, ChronoUnit.YEARS))
);
// => 1 an = 365 jours


HijrahDate hijrahStart = HijrahDate.from(isoStart);
System.out.println(
    ChronoUnit.DAYS.between(hijrahStart, hijrahStart.plus(1, ChronoUnit.MONTHS))
);
// => 1 mois = 29 jours
System.out.println(
    ChronoUnit.DAYS.between(hijrahStart, hijrahStart.plus(1, ChronoUnit.YEARS))
);
// => 1 an = 354 jours

En comparant des mois et années des calendriers grégorien et hégirien, on trouve directement une différence nette. Par contre, la même comparaison sur la durée d’une semaine n’aurait montré aucune différence. Toutes les chronologies du JDK ont des semaines de 7 jours.

Une semaine dure forcément 7 jours ?

Si les 5 calendriers supportés par le JDK ont des semaines de 7 jours, ça ne veut pas dire que c’est une règle universelle. J’ai déjà cité le cas du calendrier révolutionnaire français, mais il est trop ancien. En cherchant un peu dans la librairie ThreeTen Extra, je suis tombé sur le calendrier discordien.

Calendrier discordien

OK, on n’utilise pas couramment ce calendrier, mais il permet de répondre à la question en titre : combien de temps dure une semaine ?

LocalDate isoStart = LocalDate.parse("2000-03-01");
System.out.println(
    ChronoUnit.DAYS.between(isoStart, isoStart.plus(1, ChronoUnit.WEEKS))
);
// => 1 semaine = 7 jours

DiscordianDate discordianStart = DiscordianDate.from(isoStart);
System.out.println(
    ChronoUnit.DAYS.between(discordianStart, discordianStart.plus(1, ChronoUnit.WEEKS))
);
// => 1 semaine = 5 jours

Donc une semaine dure généralement 7 jours, sauf pour les adeptes du discordianisme pour qui elle dure 5 jours .

Pourquoi c’est important ?

Parce que vraiment, on n’utilise pas souvent le calendrier discordien…​ En réalité la question s’est posée en d’autres termes : pourquoi est-ce qu’on ne peut pas ajouter une semaine à un instant, alors qu’on peut ajouter 7 jours ?

System.out.println(Instant.EPOCH.plus(7, ChronoUnit.DAYS));
// => 1970-01-08T00:00:00Z

System.out.println(Instant.EPOCH.plus(1, ChronoUnit.WEEKS));
// => java.time.temporal.UnsupportedTemporalTypeException: Unsupported unit: Weeks

Une instance d’Instant représente un moment indépendant de tout contexte : calendrier, fuseau horaire, langue,…​ On ne peut lui ajouter que des valeurs dont l’unité est universelle. Le JDK considère que les unités jusqu’au jour sont utilisables, mais que celles au dessus (semaine, mois,…​) sont trop dépendantes du contexte.

Par conséquent, pour ajouter une semaine ou un mois, il faut passer par objet temporel contextualisé. Plus précisément, il doit être associé à une chronologie.

Instant epoch = Instant.EPOCH;
ZoneDateTime utcEpoch = Instant.EPOCH.atZone(ZoneId.of("UTC");
ZoneDateTime utcEpochPlus1Week = utcEpoch.plus(1, ChronoUnit.WEEKS);
System.out.println(utcEpochPlus1Week.toInstant());
// => 1970-01-08T00:00:00Z

Et pourquoi c’est discutable ?

Jusqu’ici, on a parlé de calendriers / chronologies qui peuvent avoir une influence sur la durée de semaines, mois ou années. Mais il n’est pas nécessaire de chercher si loin pour trouver des durées instables.

En effet, la durée d’une journée peut dépendre du fuseau horaire, en fonction des changements d’heure.

Instant instantBeforeWinterTime = Instant.parse("2000-10-29T00:00:00Z");
System.out.println(
    ChronoUnit.HOURS.between(instantBeforeWinterTime,
                             instantBeforeWinterTime.plus(1, ChronoUnit.DAYS))
);
// => 1 jour = 24 heures

ZonedDateTime beforeWinterTimeUtc = ZonedDateTime.ofInstant(
    instantBeforeWinterTime, ZoneId.of("UTC")
);
System.out.println(
    ChronoUnit.HOURS.between(beforeWinterTimeUtc,
                             beforeWinterTimeUtc.plus(1, ChronoUnit.DAYS))
);
// => 1 jour = 24 heures (pas de changement d'heure en UTC)

ZonedDateTime beforeWinterTimeParis = ZonedDateTime.ofInstant(
    instantBeforeWinterTime, ZoneId.of("Europe/Paris")
);
System.out.println(
    ChronoUnit.HOURS.between(beforeWinterTimeParis,
                             beforeWinterTimeParis.plus(1, ChronoUnit.DAYS))
);
// => 1 jour = 25 heures (passage à l'heure d'hiver)

Ajouter une journée à une date / heure est donc aussi dépendant d’un contexte. Ajouter 24 heures et ajouter une journée ne sont pas forcément équivalents.

Conclusion

Il est toujours important de définir le contexte d’une opération sur les dates / heures. Et il est toujours important de savoir ce qui est significatif.

Références :