Le temps est le problème le plus difficile en programmation que personne n'admet être difficile. Années bissextiles. Transitions de l'heure d'été. Décalages de fuseaux horaires qui ne sont pas des heures entières. Mois de 28, 29, 30 ou 31 jours. Systèmes de calendrier qui ne s'accordent pas sur quand l'année commence. La liste des cas limites est infinie, et chacun d'entre eux a causé une panne en production quelque part.
Nous aurions pu esquiver. Nous aurions pu dire aux développeurs FLIN de gérer le temps avec des entiers bruts et des chaînes de formatage. Au lieu de cela, au cours des Sessions 014, 015 et 209, nous avons construit un système temporel complet dans le langage -- 26 fonctions, une syntaxe naturelle de durée, des opérations conscientes des fuseaux horaires et un formatage qui gère chaque locale que FLIN cible. Pas de moment.js. Pas de date-fns. Pas de Luxon. Juste now, today et une poignée de méthodes qui font exactement ce que vous attendez.
Le problème du temps dans tous les autres langages
L'objet Date de JavaScript est un cours magistral en mauvaise conception d'API. Créer une date pour le 15 janvier 2026 nécessite new Date(2026, 0, 15) -- les mois sont indexés à zéro (janvier est 0), mais les jours sont indexés à un (le 15 est 15). Ajouter sept jours à une date nécessite d'extraire les millisecondes, d'ajouter 7 <em> 24 </em> 60 <em> 60 </em> 1000 et de construire un nouveau Date. Formater une date pour l'affichage nécessite soit toLocaleDateString() avec ses implémentations incohérentes entre navigateurs, soit une bibliothèque tierce.
Le module datetime de Python est meilleur mais nécessite l'import de trois classes différentes : datetime pour les horodatages, timedelta pour les durées et timezone pour la gestion des fuseaux horaires. La classe Time de Ruby est excellente mais spécifique au langage. Le time.Parse("2006-01-02", s) de Go utilise une date de référence magique que personne ne peut retenir.
FLIN prend les meilleures idées de tous ces langages et les rend disponibles sans aucun boilerplate.
Heure actuelle : quatre mots-clés
flinright_now = now // Horodatage actuel (UTC)
today_start = today // Aujourd'hui à 00:00:00
yesterday_start = yesterday // Hier à 00:00:00
tomorrow_start = tomorrow // Demain à 00:00:00Ce ne sont pas des fonctions. Ce sont des mots-clés du langage qui se résolvent en valeurs temporelles. now retourne l'horodatage UTC actuel avec une précision à la milliseconde. today retourne minuit du jour courant. yesterday et tomorrow retournent minuit des jours adjacents.
Pourquoi des mots-clés au lieu de fonctions ? Parce que now est utilisé si fréquemment que l'économie de deux caractères -- now vs now() -- s'accumule à travers un codebase entier. Et parce qu'en faire des mots-clés communique l'intention : ce sont des concepts fondamentaux, pas des fonctions utilitaires.
Composantes temporelles
Chaque valeur temporelle expose ses composantes comme des propriétés :
flint = now
t.year // 2026
t.month // 3 (mars)
t.day // 26
t.hour // 14
t.minute // 30
t.second // 45
t.millisecond // 123
t.day_of_week // 3 (mercredi, 0 = dimanche)
t.day_of_year // 85
t.week_of_year // 13
t.is_weekend // false
t.is_leap_year // falseLes mois sont indexés à un. Janvier est 1, décembre est 12. Les jours de la semaine commencent du dimanche (0) au samedi (6). Ces conventions correspondent à ISO 8601 pour les mois et à la convention américaine courante pour les jours de la semaine. Nous avons envisagé d'utiliser lundi comme jour 0 (la convention ISO), mais la majorité des développeurs JavaScript -- le principal public de migration de FLIN -- s'attendent à dimanche comme 0.
is_weekend et is_leap_year sont des propriétés de commodité qui éliminent le genre de logique booléenne que les développeurs se trompent. is_weekend retourne true pour samedi (6) et dimanche (0). is_leap_year implémente la règle complète de l'année bissextile grégorienne (divisible par 4, sauf les siècles, sauf les siècles divisibles par 400).
Syntaxe de durée : la conception qui s'écrit d'elle-même
Le joyau de la couronne du système temporel de FLIN est la syntaxe de durée. Au lieu de multiplier des entiers par des constantes magiques, vous écrivez des durées en anglais naturel :
flin1.second
5.minutes
2.hours
7.days
4.weeks
3.months
1.yearCe ne sont pas des méthodes sur les nombres. Ce sont des littéraux de durée reconnus par le parseur et le vérificateur de types. L'expression 7.days a le type duration, pas int ou float. Vous ne pouvez pas ajouter une durée à une chaîne. Vous ne pouvez pas multiplier une durée par une liste. Le système de types empêche les opérations absurdes.
L'arithmétique de durée avec les valeurs temporelles se lit comme de l'anglais :
flin// Quand l'abonnement expire-t-il ?
expires = now + 30.days
// Quand était-ce il y a 2 heures ?
two_hours_ago = now - 2.hours
// Combien de temps avant la réunion ?
meeting = parse_time("2026-03-26T16:00:00Z")
wait = meeting - now
print("Meeting in {wait.hours} hours and {wait.minutes} minutes")
// Chaîner des durées
total = 1.hour + 30.minutes + 45.secondsL'implémentation est directe. Les durées sont stockées en interne comme un nombre de millisecondes. 1.second est 1 000 millisecondes. 5.minutes est 300 000 millisecondes. Ajouter une durée à une valeur temporelle produit une nouvelle valeur temporelle. Soustraire deux valeurs temporelles produit une durée. Le vérificateur de types impose ces règles à la compilation.
La partie délicate, ce sont les mois et les années. Contrairement aux secondes, minutes, heures et jours, les mois et les années n'ont pas une longueur fixe. Février a 28 ou 29 jours. "Un mois à partir de maintenant" le 31 janvier pourrait être le 28 février, le 29 février ou le 3 mars, selon l'année et votre interprétation. FLIN suit la convention utilisée par la plupart des bibliothèques de dates : ajouter un mois avance le numéro du mois de un, et si le jour résultant n'existe pas, il est limité au dernier jour du mois.
flin// 31 janvier + 1 mois = 28 février (ou 29 les années bissextiles)
jan31 = parse_time("2026-01-31")
feb = jan31 + 1.month
print(feb.format("YYYY-MM-DD")) // "2026-02-28"Comparaison temporelle
flina = parse_time("2026-03-26T10:00:00Z")
b = parse_time("2026-03-26T14:00:00Z")
a.is_before(b) // true
a.is_after(b) // false
a.is_same_day(b) // true
a.is_between(
parse_time("2026-03-01"),
parse_time("2026-03-31")
) // trueis_same_day compare l'année, le mois et le jour, en ignorant la composante temps. C'est essentiel pour les applications de calendrier, où "les événements d'aujourd'hui" signifie tout de minuit à minuit, quel que soit l'horodatage exact.
is_between est inclusif aux deux extrémités. Une valeur temporelle égale à l'une ou l'autre des bornes retourne true. Cela correspond à l'attente intuitive : si la réunion est entre lundi et vendredi, les réunions du lundi et du vendredi sont incluses.
Formatage temporel
Le formatage est le domaine où la plupart des bibliothèques de temps soit brillent soit s'effondrent. FLIN utilise une chaîne de formatage basée sur des tokens inspirée de Moment.js (le format le plus connu) avec quelques ajouts d'Unicode CLDR :
flint = parse_time("2026-12-31T14:30:45Z")
t.format("YYYY-MM-DD") // "2026-12-31"
t.format("HH:mm:ss") // "14:30:45"
t.format("MMMM D, YYYY") // "December 31, 2026"
t.format("dddd") // "Thursday"
t.format("MMM D") // "Dec 31"
t.format("h:mm A") // "2:30 PM"
// Formats standards
t.iso // "2026-12-31T14:30:45.000Z"
t.unix // 1798800645 (secondes)
t.unix_millis // 1798800645000 (millisecondes)Les tokens de formatage :
| Token | Signification | Exemple |
|---|---|---|
YYYY | Année à quatre chiffres | 2026 |
MM | Mois à deux chiffres | 12 |
DD | Jour à deux chiffres | 31 |
HH | Heure au format 24h | 14 |
hh | Heure au format 12h | 02 |
mm | Minutes | 30 |
ss | Secondes | 45 |
A | AM/PM | PM |
MMMM | Nom complet du mois | December |
MMM | Nom abrégé du mois | Dec |
dddd | Nom complet du jour | Thursday |
ddd | Nom abrégé du jour | Thu |
D | Jour sans remplissage | 31 |
M | Mois sans remplissage | 12 |
Les noms de mois et de jours dépendent de la locale. Dans une application en français, MMMM produit "décembre" et dddd produit "jeudi". La locale est définie au niveau de l'application, pas par appel de fonction.
Analyse temporelle
flin// ISO 8601 (auto-détecté)
t1 = parse_time("2026-12-31")
t2 = parse_time("2026-12-31T14:30:00Z")
t3 = parse_time("2026-12-31T14:30:00+01:00")
// Format personnalisé
t4 = parse_time("Dec 31, 2026", "MMM D, YYYY")
t5 = parse_time("31/12/2026", "DD/MM/YYYY")La forme à un argument de parse_time auto-détecte les formats ISO 8601. Elle gère les dates avec et sans heures, avec et sans décalages de fuseau horaire, avec et sans millisecondes. Si la chaîne ne correspond à aucun format connu, elle retourne none au lieu de planter.
La forme à deux arguments accepte les mêmes tokens que format, utilisés en sens inverse. C'est symétrique : si t.format("DD/MM/YYYY") produit "31/12/2026", alors parse_time("31/12/2026", "DD/MM/YYYY") produit la même valeur temporelle. La symétrie entre formatage et analyse est une propriété qu'étonnamment peu de bibliothèques de temps garantissent.
Manipulation temporelle
flint = now
t.start_of_day // Aujourd'hui à 00:00:00
t.end_of_day // Aujourd'hui à 23:59:59.999
t.start_of_week // Lundi à 00:00:00
t.start_of_month // 1er de ce mois à 00:00:00
t.start_of_year // 1er janvier à 00:00:00Ces méthodes de manipulation sont essentielles pour les requêtes de reporting. "Montre-moi toutes les commandes de ce mois" se traduit par order.created_at.is_after(now.start_of_month). "Montre-moi l'activité de cette semaine" se traduit par activity.timestamp.is_after(now.start_of_week).
start_of_week utilise lundi comme début de semaine (convention ISO 8601). C'est configurable au niveau de l'application pour les locales où la semaine commence le dimanche ou le samedi.
Temps relatif
flinpast = now - 3.hours
past.from_now // "3 hours ago"
future = now + 2.days
future.from_now // "in 2 days"
old = parse_time("2025-01-01")
old.from_now // "1 year ago"
// Relatif à un moment spécifique
event_time = parse_time("2026-03-26T10:00:00Z")
event_time.relative_to(now) // "4 hours ago"from_now produit des chaînes de temps relatif lisibles par l'humain. La sortie s'adapte à la durée : "just now" pour moins d'une minute, "5 minutes ago" pour les minutes, "3 hours ago" pour les heures, "2 days ago" pour les jours, "3 months ago" pour les mois, "1 year ago" pour les années.
Les chaînes sont sensibles à la locale. En français : "il y a 3 heures", "dans 2 jours", "il y a 1 an". En anglais : "3 hours ago", "in 2 days", "1 year ago".
Gestion des fuseaux horaires
La gestion des fuseaux horaires a été l'aspect techniquement le plus difficile du système temporel. FLIN stocke toutes les valeurs temporelles en UTC en interne. La conversion vers les fuseaux horaires locaux pour l'affichage se fait explicitement :
flin// Tous les temps sont en UTC en interne
utc_now = now
print(utc_now.format("HH:mm")) // Heure UTC
// Convertir vers un fuseau horaire spécifique
abidjan = utc_now.in_timezone("Africa/Abidjan")
paris = utc_now.in_timezone("Europe/Paris")
new_york = utc_now.in_timezone("America/New_York")
print(abidjan.format("HH:mm")) // GMT+0
print(paris.format("HH:mm")) // CET (GMT+1 ou GMT+2 en été)
print(new_york.format("HH:mm")) // EST (GMT-5 ou GMT-4 en été)La base de données des fuseaux horaires est embarquée dans le runtime FLIN (compilée depuis les données IANA tzdata). Cela signifie que les conversions de fuseaux horaires fonctionnent hors ligne, sans accès réseau, et produisent des résultats cohérents quelle que soit la configuration du fuseau horaire du système d'exploitation hôte.
Les transitions de l'heure d'été sont gérées correctement. Quand Paris passe de CET à CEST en mars, in_timezone("Europe/Paris") ajuste automatiquement le décalage. Quand New York passe de EST à EDT, le décalage change de -5 à -4. Le développeur n'a pas besoin de savoir quand ces transitions se produisent -- la base de données des fuseaux horaires s'en charge.
Exemple concret : planification d'événements
En combinant tout, voici comment le système temporel de FLIN gère un cas d'utilisation réel -- planifier un événement avec un affichage conscient des fuseaux horaires :
flin// Créer un événement en UTC
event = {
title: "Product Launch",
start: parse_time("2026-04-15T18:00:00Z"),
duration: 2.hours
}
// Afficher pour différents publics
end_time = event.start + event.duration
print("For Abidjan: {event.start.in_timezone('Africa/Abidjan').format('MMMM D, h:mm A')} to {end_time.in_timezone('Africa/Abidjan').format('h:mm A')}")
// "For Abidjan: April 15, 6:00 PM to 8:00 PM"
print("For Paris: {event.start.in_timezone('Europe/Paris').format('MMMM D, h:mm A')} to {end_time.in_timezone('Europe/Paris').format('h:mm A')}")
// "For Paris: April 15, 8:00 PM to 10:00 PM"
// Vérifier si l'événement est à venir
{if event.start.is_after(now)}
<Badge variant="info">{event.start.from_now}</Badge>
// "in 20 days"
{else if end_time.is_after(now)}
<Badge variant="success">Happening now</Badge>
{else}
<Badge variant="muted">{event.start.from_now}</Badge>
// "20 days ago"
{/if}Pas d'imports. Pas de bibliothèque de fuseaux horaires. Pas de bibliothèque de formatage de dates. Pas de bibliothèque d'affichage relatif. Six opérations temporelles différentes -- analyse, arithmétique, conversion de fuseau horaire, formatage, comparaison et affichage relatif -- toutes intégrées dans le langage.
Implémentation : Chrono sous le capot
Le système temporel de FLIN est construit sur le crate chrono de Rust, la bibliothèque de date-heure la plus éprouvée de l'écosystème Rust. Nous avons choisi chrono plutôt que le plus récent crate time parce que chrono a un meilleur support des fuseaux horaires et des options de formatage plus étendues.
La représentation interne est un horodatage Unix 64 bits en millisecondes. Cela nous donne une plage d'environ 290 millions d'années dans le passé à 290 millions d'années dans le futur, avec une précision à la milliseconde. Plus que suffisant pour toute application web.
rust// Représentation interne
#[derive(Clone, Copy, Debug)]
pub struct FlinTime {
millis: i64, // Horodatage Unix en millisecondes
}
impl FlinTime {
pub fn now() -> Self {
Self { millis: Utc::now().timestamp_millis() }
}
pub fn format(&self, pattern: &str) -> String {
let dt = Utc.timestamp_millis_opt(self.millis).unwrap();
// Formatage basé sur des tokens utilisant le strftime de chrono
self.format_with_tokens(dt, pattern)
}
}Le formatage basé sur des tokens est une implémentation personnalisée qui traduit les tokens de formatage FLIN (YYYY, MM, DD) en tokens strftime de chrono (%Y, %m, %d). Nous avons écrit le nôtre au lieu d'exposer directement le strftime de chrono parce que les tokens de strftime sont cryptiques (qui se souvient que %B est le nom complet du mois ?) et incohérents entre plateformes.
Vingt-six fonctions, zéro dépendance
L'API temporelle complète :
- 4 mots-clés de temps actuel :
now,today,yesterday,tomorrow - 12 propriétés de composantes :
year,month,day,hour,minute,second,millisecond,day_of_week,day_of_year,week_of_year,is_weekend,is_leap_year - 7 constructeurs de durée :
second,minutes,hours,days,weeks,months,year - 4 méthodes de comparaison :
is_before,is_after,is_same_day,is_between - 5 méthodes de manipulation :
start_of_day,end_of_day,start_of_week,start_of_month,start_of_year - 3 sorties de formatage :
format,iso,unix - 2 fonctions d'analyse :
parse_time(formes à un et deux arguments) - 2 méthodes d'affichage relatif :
from_now,relative_to - 1 conversion de fuseau horaire :
in_timezone
Vingt-six fonctions qui remplacent moment.js (288 Ko minifié), date-fns (75 Ko) et Luxon (67 Ko). Toutes compilées dans le binaire FLIN à un coût quasi nul.
Ceci est la partie 74 de la série "How We Built FLIN", documentant comment un CEO à Abidjan et un CTO IA ont construit un système temporel conscient des fuseaux horaires dans un langage de programmation.
Navigation de la série : - [73] Math, Statistics, and Geometry Functions - [74] Time and Timezone Functions (vous êtes ici) - [75] HTTP Client Built Into the Language - [76] Security Functions: Crypto, JWT, Argon2