La gestion du Temps sur une application client/serveur peut sembler simple de prime abord, mais se révélera beaucoup plus complexe au fur et à mesure des usages : timezones multiples, heures récurrentes, heure d'été/d'hiver, traitements cron, leap seconds sont des exemples parmi tant d'autres.
Saviez-vous même que la Commission Européenne envisage d'abolir le passage en heure d'hiver ? Quels impacts cela aurait sur nos applications ?
Mon objectif : qu'à partir de lundi prochain, vous vous posiez les bonnes questions à chaque fois que vous travaillerez sur une date ou une heure dans vos applications.
Pour cela, reprenons les bases du Temps en informatique : composantes d'une date, norme ISO 8601, Timezones et IANA.
Une fois ces bases posées, nous verrons, au travers d'un certain nombre de cas d'utilisation issus de la vraie vie, les bonnes questions qu'il convient de se poser pour mettre le doigt sur les complexités d'implémentation et éviter de tomber dans une faille spatio-temporelle lors du prochain changement d'heure.
Ce talk est accessible à tous et ne se focalisera pas sur un langage / une API en particulier : les concepts prévaudront sur le code.
Back to basics - Ne perdez plus votre Temps avec les Dates - GDG Tours Edition
1. Ne perdez plus votre
Temps avec les Dates
#RetourAuxSources
Frédéric CAMBLOR
4SH France
@fcamblor
GDG Tours
2019-06-19T19:00:00+02:00
#DateTimeBasics
2. Avertissement
Cette présentation n’aborde pas vraiment des technologies à la mode.
Pire, vous risquez de ressortir de cette présentation avec moins de certitudes
que vous n’en aviez en arrivant.
Acceptez-vous de poursuivre cette présentation sur les Dates et le Temps ?
Si vous voulez savoir comment déployer dans la Blockchain des Microservices
Kotlin codés avec Quarkus sur une plateforme Kubernetes Native dans une
image Docker basée sur GraalVM,il est encore temps de partir :-)
3.
4. Ne perdez plus votre
Temps avec les Dates
#RetourAuxSources
Frédéric CAMBLOR
4SH France
@fcamblor
GDG Tours
2019-06-19T19:00:00+02:00
#DateTimeBasics
17. Coordinated Universal Time (UTC)
• Basé sur le TAI
• Agnostic de la géographie / géopolitique
• Stable/Continu dans le temps
Pas d'heure d'été/hiver
• EPOCH : 1970-01-01T00:00:00Z
30. TIMEZONES: Points clef
• TZ définit des offsets par rapport à UTC
• Ils varient de -12:00 à +14:00
• Les offsets sont "généralement" d'1h
• Offsets variables par timezone (DST)
• TZ Tables : IANA & Windows TimeZones
31. Internet Assigned Numbers Authority (IANA) for Europe
https://github.com/eggert/tz/blob/master/europe
33. En JAVA c'est packagé dans les JRE + TZUpdater tool
http://bit.ly/JRE-IANA
34. Pour MySQL il faut le faire à la main
http://bit.ly/mysql-tzdata
35. Pour NodeJS il faut le gérer à la main
https://www.npmjs.com/package/geo-tz
36. tldr; posez-vous la question !
L'OS peut aussi mettre à disposition ces tables
37. TZ Data: Points clef
• Très lié à la géopolitique
• Les règles changent, régulièrement
• Les règles changent, subitement
• Europe : vers une abolition de DST
• Ne rien considérer comme acquis
⚠ ⚠ ⚠ aux dates dans le futur
38. Lorsque vous stockez une Date+Heure dans le futur :
Stockez l'heure locale & la timezone
de l'utilisateur à l'origine de cette Date
Vous pourrez corriger le problème
(à condition de le détecter)
https://codeblog.jonskeet.uk/2019/03/27/storing-utc-is-not-a-silver-bullet/
39.
40. Daylight Saving Time (DST)
tzDate("2019-03-31T01:10:00", "Europe/Paris")
=> 2019-03-31T01:10:00+01:00
Certaines heures locales n'existent pas (Local Time Gap)
02:00 -> 03:00 pour Europe/Paris
tzDate("2019-03-31T01:10:00", "Europe/Paris").add(1, 'hour')
=> 2019-03-31T03:10:00+02:00
tzDate("2019-03-31T02:10:00", "Europe/Paris")
=> 2019-03-31T03:10:00+02:00
41. Daylight Saving Time (DST)
tzDate("2019-10-27T02:10:00", "Europe/Paris")
=> 2019-10-27T02:10:00+02:00
Certaines heures locales existent 2 fois
02:00 -> 03:00 pour Europe/Paris
tzDate("2019-10-27T02:10:00", "Europe/Paris").add(1, 'hour')
=> 2019-10-27T02:10:00+01:00
42. La date/heure DST varie en fonction de la timezone
http://bit.ly/dst-diffs
43. Fausse bonne idée
"Notre batch de facturation passe tous les jours à 2h30
du matin (heure locale),car il s'agit du moment où le
système est le moins sollicité"
🤦 &
49. 🤔
Qui a raison ?
public ZonedDateTime calculateNextThingAfter(ZonedDateTime d) {
return d.plusDays(1);
}
public ZonedDateTime calculateNextThingAfter(ZonedDateTime d) {
return d.plusHours(24);
}
#scaleMatters
50. Fun fact : aucun 2011-12-30 (Friday) aux Samoa
https://www.timeanddate.com/news/time/samoa-dateline.html
55. Format non ISO => date Locale | Format ISO => date UTC
https://twitter.com/seldo/status/1091861205260500992
56. D'autres blagues
• Les mois commencent à 0
On a trouvé d'où vient le "Java" de "Javascript" !
• Aucune gestion des timezones / internationalisation
• La gestion des "anciens" timezone offsets est (complètement) broken
... chez tout le monde (Chrome, Firefox, IE, Safari, Node)
https://codeofmatt.com/javascript-date-type-is-horribly-broken/
61. Le premier jour de la semaine peut varier...
https://en.wikipedia.org/wiki/Week
62. La notion de Chronology
• ISO 8601 = Calendrier Grégorien de facto
• Il en existe plein d'autres : Julien, Ethiopien, Bouddhiste, Islamiste, Copte...
• Règles différentes sur l'année de départ
• Règles différentes sur les années bissextiles
• Répartition différente des jours dans les mois
65. Les serveurs doivent être en UTC
•Timezone de l'OS
•Timezone de la DB
Il n'est pas tout le temps possible de la changer
•Timezone de votre serveur d'App
sudo timedatectl set-timezone UTC
# Or : sudo dpkg-reconfigure tzdata
-Duser.timezone=UTC
66. L'intervalle de temps implicite
👍 GET /messages?after=2019-02-09T00:00:00+01:00&before=2019-02-09T23:59:59+01:00
👎 GET /messages?date=2019-02-09
👋 GET /messages?date=2019-02-09&tz=Europe/Paris
67. L'intervalle de temps implicite
// Assumes client and server timezones are the same
SELECT * FROM messages WHERE published_on = '2019-02-09';
👍 Expected👎 Result
👎 GET /messages?date=2019-02-09
68. L'intervalle de temps implicite
1/ Convert date to date midnight using Europe/Paris Timezone
2019-02-09 => 2019-02-09T00:00:00+01:00
👍 Expected
👋 GET /messages?date=2019-02-09&tz=Europe/Paris
👎 Result
# Swap 2 & 3 for expected result !
2/ Convert to UTC
=> 2019-02-08T23:00:00Z
3/ Transform to Range using startOf/endOf day
=> [ 2019-02-08T00:00:00Z, 2019-02-08T23:59:59Z ]
69. L'intervalle de temps implicite
1/ Convert date to date midnight using Europe/Paris Timezone
2019-02-09 => 2019-02-09T00:00:00+01:00
2/ Transform to Range using startOf/endOf day
=> [ 2019-02-09T00:00:00+01:00, 2019-02-09T23:59:59+01:00 ]
3/ Convert to UTC
=> [ 2019-02-08T23:00:00Z, 2019-02-09T22:59:59Z ]
👍 Expected
👋 GET /messages?date=2019-02-09&tz=Europe/Paris
👍 Result
70. L'intervalle de temps implicite
// Nothing fancy happening serverside since we're
// working with timestamps
SELECT * FROM messages WHERE published_on
BETWEEN '2019-02-08T23:00:00Z' AND '2019-02-09T23:00:00Z' ;
👍 Expected👍 Result
👍 GET /messages?start=2019-02-09T00:00:00+01:00
&end=2019-02-09T23:59:59+01:00
71. Date search Patterns
• Dans 99% des cas vous allez faire des recherches sur des
intervalles de temps
• Faites (si possible) les conversions coté appelant* (client)
• Stockez* & Recherchez vos datetime en UTC
72. Attention aux"Heures locales"
[On est le 08/03 à Bordeaux]
"Le magasin ouvre à 9h du matin les jours ouvrés"
"J'aimerais planifier une réunion à 15h (Europe/Paris) tous les lundi"
Heure sans Date attachée
73. Attention aux"Heures locales"
[On est le 08/03 à Bordeaux]
"J'aimerais planifier une réunion à 15h (Europe/Paris) tous les lundi"
1/ "Le dernier lundi d'avril"
=> 2019-04-29 (Date Locale)
2/ Ajout de la composante heure + timezone offset
=> 2019-04-29T15:00:00+02:00
Attention aux cas exceptionnels !
👍 "localTime": "15:00 [Europe/Paris]"
3/ Décalage dans une tz cible (ex: America/New_York)
=> 2019-04-29T09:00:00-04:00
74. Time-only Patterns
• Stocker la Timezone de l'utilisateur ayant saisi l'heure
• Évitez de stocker votre Time dans un DateTime
🙈 1970-01-01T15:35:00Z
• Mettez à disposition la Timezone cible
75. Date-only Patterns
• Utilisez une structure de données adhoc
• Évitez de stocker votre Date dans un DateTime
🙈 1732-02-22T00:00:00Z
• Si vous êtes sûr de la Timezone, stockez-la
76. Les"Local Datetime"
Une DateTime sans Timezone
ne peut pas être "identifiée" chronologiquement
À éviter,absolument !
http://blog.schauderhaft.de/2018/03/14/dont-use-localdatetime/