Créer un calendrier dynamique dans Power BI avec Power Query
Une table de dates qui s’étend toute seule à chaque actualisation, sans date de fin codée en dur. Le code complet, puis les colonnes qui servent vraiment.
Mis à jour le
Un modèle Power BI sans table de dates ne va pas loin. Les fonctions de temps de DAX, TOTALYTD, SAMEPERIODLASTYEAR, DATEADD, refusent de fonctionner correctement sans elle. Et tant que la table s’arrête à une date écrite en dur, le rapport cesse d’afficher les données du jour dès qu’on a dépassé cette borne, en général au pire moment.
Ce guide construit une table de dates qui repousse sa borne de fin toute seule à chaque rafraîchissement.
Une requête, à coller telle quelle
Dans Power BI Desktop : Accueil → Transformer les données → Nouvelle source → Requête vide, puis Éditeur avancé. Remplacez tout par ceci.
let
Debut = #date(2020, 1, 1),
Fin = Date.EndOfYear(DateTime.Date(DateTime.FixedLocalNow())),
Nombre = Duration.Days(Fin - Debut) + 1,
Liste = List.Dates(Debut, Nombre, #duration(1, 0, 0, 0)),
Base = Table.FromList(Liste, Splitter.SplitByNothing(), {"Date"}),
Typee = Table.TransformColumnTypes(Base, {{"Date", type date}})
in
Typee
C’est tout pour le squelette. La ligne qui fait le travail est Fin : elle prend la date du jour au moment de l’actualisation et la pousse à la fin de l’année en cours. La table couvre donc toujours l’exercice complet, sans intervention.
Adaptez Debut à votre historique réel. Une table qui démarre dix ans avant vos premières données alourdit le modèle et allonge inutilement les listes déroulantes.
Caler la fin sur vos données plutôt que sur le calendrier
Si vos données s’arrêtent au mois dernier, une table qui va jusqu’au 31 décembre affichera des mois vides dans tous les visuels. Dans ce cas, on prend la date maximale de la table de faits :
Fin = Date.EndOfMonth(List.Max(Ventes[Date_Commande])),
La table suit alors la réalité des données. En contrepartie, elle dépend d’une autre requête, ce qui peut ralentir l’actualisation. Le choix entre les deux se fait au cas par cas ; l’année civile suffit dans la plupart des situations.
Sans écrire de code, pour comprendre ce qui se passe
C’est l’approche à privilégier quand on veut voir le mécanisme plutôt que le subir.
- Créez une table à deux colonnes avec Entrer des données : une colonne « Début », une colonne « Fin », une seule ligne. Vérifiez que les deux sont bien au format Date, pas Texte.
- Ajoutez une colonne personnalisée nommée « Date », avec la formule
{Number.From([Début])..Number.From([Fin])}. Chaque date devient un nombre, et l’opérateur..engendre la liste de tous les entiers entre les deux. - Développez la liste sur de nouvelles lignes avec l’icône à deux flèches en tête de colonne. Une ligne par jour apparaît.
- Convertissez la colonne Date au format Date, puis supprimez « Début » et « Fin » qui ne servent plus.
- Rendez la fin dynamique. Retournez sur l’étape « Personnalisée ajoutée » et remplacez
Number.From([Fin])parNumber.From(DateTime.Date(DateTime.LocalNow())). La table s’arrête désormais au jour de l’actualisation.
Le résultat est identique à la requête plus haut. La version en code reste plus lisible et plus facile à reprendre, mais ces cinq étapes montrent bien d’où sort la liste de dates.
Les colonnes qui servent vraiment
Une colonne de dates seule ne suffit pas : il faut les axes sur lesquels les utilisateurs vont découper. Ajoutez-les à la fin de la requête.
Annee = Table.AddColumn(Typee, "Année", each Date.Year([Date]), Int64.Type),
NumMois = Table.AddColumn(Annee, "N° mois", each Date.Month([Date]), Int64.Type),
NomMois = Table.AddColumn(NumMois, "Mois", each Date.MonthName([Date]), type text),
Trimestre = Table.AddColumn(NomMois, "Trimestre", each "T" & Text.From(Date.QuarterOfYear([Date])), type text),
AnneeMois = Table.AddColumn(Trimestre, "Année-mois", each Date.ToText([Date], "yyyy-MM"), type text)
in
AnneeMois
La colonne « N° mois » n’est pas décorative : sans elle, « Mois » se trie par ordre alphabétique et vos graphiques commencent en avril. Dans la vue de données, sélectionnez la colonne « Mois », puis Trier par colonne → N° mois. C’est l’oubli le plus fréquent sur une table de dates.
La colonne « Année-mois » au format 2026-08 se trie naturellement et rend service dans les axes de graphiques comme dans les segments.
La variante DAX : une table calculée
Le même calendrier se construit côté modèle, sans passer par l’éditeur de requêtes. Dans Power BI Desktop : Modélisation → Nouvelle table, puis ce script.
Calendrier =
ADDCOLUMNS (
CALENDARAUTO (),
"Année", YEAR ( [Date] ),
"NumMois", MONTH ( [Date] ),
"Mois", FORMAT ( [Date], "mmm", "fr-FR" ),
"AnnéeMois", FORMAT ( [Date], "yyyy-MM" ),
"Trimestre", "T" & FORMAT ( [Date], "q" ),
"AnnéeTrimestre", YEAR ( [Date] ) & " T" & FORMAT ( [Date], "q" ),
"NumJourSemaine", WEEKDAY ( [Date], 2 ),
"JourSemaine", FORMAT ( [Date], "ddd", "fr-FR" ),
"EstJourOuvré", IF ( WEEKDAY ( [Date], 2 ) <= 5, 1, 0 )
)
CALENDARAUTO balaie toutes les colonnes de dates du modèle et génère des années civiles complètes. Le calendrier suit donc l’arrivée de nouvelles données sans maintenance, ce qui est son intérêt principal.
C’est aussi sa faiblesse. Une seule date aberrante quelque part, une date de naissance de 1970 oubliée dans une table de personnel, et la table s’étend sur cinquante ans. Pour maîtriser les bornes, remplacez l’appel par CALENDAR ( DATE ( 2022, 1, 1 ), DATE ( YEAR ( TODAY () ), 12, 31 ) ).
Quelle voie choisir ? La version Power Query dès que le calendrier doit être partagé entre plusieurs modèles, via un flux de données par exemple, ou quand vous préférez garder la logique hors de DAX. La table calculée quand vous voulez aller vite sur un modèle unique. Le résultat est équivalent, et les trois réglages décrits ci-dessous restent obligatoires dans les deux cas.
Déclarer la table comme table de dates
Une fois la table chargée, deux gestes restent à faire, et ils conditionnent tout le reste.
Reliez la table à chaque table datée du modèle, de la colonne Date vers la colonne de date de la table de faits, en relation un à plusieurs.
Marquez-la comme table de dates : sélectionnez-la, puis Outils de table → Marquer comme table de dates, et désignez la colonne Date. Sans cette déclaration, les fonctions de temps de DAX donnent des résultats faux au lieu de refuser de s’exécuter, ce qui est bien plus difficile à repérer.
Dernier point : la table doit contenir tous les jours sans exception, du 1er janvier au 31 décembre de chaque année couverte. Un trou dans les dates, et les calculs cumulés deviennent silencieusement inexacts.
Ce qui casse les calendriers
Quatre symptômes reviennent en audit, et ils ont tous la même origine : un réglage oublié, jamais une erreur de formule.
| Symptôme | Cause et correctif |
|---|---|
SAMEPERIODLASTYEAR renvoie vide | Table non marquée comme table de dates, ou visuel construit sur la date de la table de faits au lieu de celle du calendrier. |
| La relation ne filtre rien | Vos faits portent un datetime avec les heures : la jointure sur une colonne date pure ne correspond jamais. Tronquez la date en Power Query et reliez la colonne obtenue. |
| Le calendrier démarre en 1900 | CALENDARAUTO a trouvé une date parasite quelque part dans le modèle. Passez à CALENDAR avec des bornes explicites. |
| Des hiérarchies de dates partout | Désactivez Date/heure automatique dans Options, Chargement des données : ces hiérarchies cachées alourdissent le modèle et court-circuitent votre calendrier. |
Le dernier point mérite d’être fait dès la création du fichier. Power BI crée par défaut une table de dates invisible pour chaque colonne de date du modèle. Sur une vingtaine de colonnes, cela fait vingt tables cachées que personne ne voit et que tout le monde paie.
Ce qu’on nous demande sur la table de dates
Pourquoi ne pas utiliser CALENDARAUTO en DAX ?
CALENDARAUTO dépanne très bien pour un prototype : elle balaie toutes les colonnes de date du modèle et couvre l’amplitude trouvée. Le problème est qu’elle la couvre vraiment toute : une seule date aberrante en 1900 ou en 2099 dans un coin du modèle, et la table explose. En Power Query, les bornes sont explicites et personne ne les élargit par accident.
Comment gérer un exercice fiscal décalé ?
Ajoutez les colonnes propres à l’exercice à côté des colonnes civiles, sans les remplacer. Pour un exercice démarrant en juillet, l’année fiscale s’écrit each if Date.Month([Date]) >= 7 then Date.Year([Date]) + 1 else Date.Year([Date]). Le paramètre de CALENDARAUTO permet la même chose en DAX, mais on garde alors les limites décrites plus haut.
Faut-il une table de dates par table de faits ?
Non, une seule pour tout le modèle. C’est justement son intérêt : un filtre sur l’année agit simultanément sur les commandes, les factures et les stocks. Quand une table de faits porte plusieurs dates, date de commande et date de livraison par exemple, on relie la première et on gère la seconde avec USERELATIONSHIP.
Comment ajouter les jours fériés ?
Le plus solide est une petite table de référence des dates fériées, fusionnée sur la colonne Date. Les listes issues du web sont pratiques mais dépendent d’un site tiers, ce qui finit toujours par casser une actualisation. Pour un calcul de jours ouvrés, cette colonne devient indispensable.
Les fonctions qu’on utilise avec
Date.Year,Date.Month,Date.Day: les briques des colonnes ci-dessus.Table.RemoveRows: nettoyer les lignes parasites d’un export.- La référence complète Power Query et DAX, dont les fonctions de temps DAX.
Sources — documentation officielle de l’éditeur, consultée le 22 août 2026 : CALENDAR, CALENDARAUTO, List.Dates.
Des comparaisons annuelles qui ne tombent pas juste ?
Neuf fois sur dix, la table de dates est en cause. Nous auditons le modèle et corrigeons la structure. Premier retour sous cinq jours ouvrés.