Time Intelligence in DAX
Make every measure answer 'now vs then'
YTD, MTD, period-over-period, rolling windows: DAX time intelligence shifts the date context so a single measure answers 'now vs then' correctly.
What you will learn
- Apply YTD/MTD and prior-period functions
- Explain why a proper date table is mandatory
- Build rolling and period-over-period measures
What is this?
Time intelligence functions (TOTALYTD, TOTALMTD, DATESYTD, SAMEPERIODLASTYEAR, PREVIOUSMONTH, DATESINPERIOD) rewrite the date filter so one base measure (like [Revenue]) becomes a family: current month, prior month, YTD, rolling 90 days. They all assume a standalone date/dim-date table marked as the model's date table — no gaps, no duplicates — otherwise every period function returns garbage.
Why do you need it?
- Executives compare: this month vs last, YTD vs target — same base number.
- Well-formed date tables make CALCULATE-based period math trivial.
- It kills the 'hard-coded date in every measure' anti-pattern.
- Interview correctness: YTD and year-over-year patterns are standard.
Mental model
A family calendar on the wall
The date table
The calendar is fixed — every day is on it, every month exactly once. DAX time functions clip the current slice (YTD = through today) and shift windows (last year = same span a year earlier) like turning the calendar's pages from the same day one year back. No missing days, no twin months — the calendar is trusted.
How it works
- The date table covers the whole analysis span, marked 'Date' in the model.
- TOTALYTD(expr, datecol, filter?) sums expr from period start to the context end.
- SAMEPERIODLASTYEAR shifts the date column by exactly one year.
- DATESBETWEEN/DATESINPERIOD build raw custom windows.
- A 'relative period' measure composes TOTALYTD with CALCULATE + date shifts.
Time-intelligence context shifts on the calendar.
Examples
The standard time family
Revenue = SUM(fact_orders[amount]) Revenue_YTD = TOTALYTD([Revenue], dim_date[date])Revenue_MTD = TOTALMTD([Revenue], dim_date[date])PLS = SAMEPERIODLASTYEAR(dim_date[date]) Revenue_PY = CALCULATE([Revenue], PLS)Revenue_YoY = DIVIDE([Revenue] - [Revenue_PY], [Revenue_PY]) Revenue_90d = CALCULATE([Revenue], DATESINPERIOD(dim_date[date], LASTDATE(dim_date[date]), 90, DAY))One base measure plus window-shipping CALCULATE calls produces YTD, prior-year and rolling-90-days — all consistent with the same revenue definition.
In production
- A monthly ops deck shows MTD, LYMTD and the delta from one base revenue measure.
- YoY growth tiles use SAMEPERIODLASTYEAR composited on the current visibility.
- A rolling 30/60/90 retention curve relies on DATESINPERIOD over a join date dim.
Best practices
- Always mark a dense date table; never 'date' from the fact's own column.
- Keep the base measure filterless and lean; push windows into computation.
- Compose time windows in a measure, not column hard-coded boundaries.
- Verify YTD against a month-end control total before publishing.
Common mistakes
- Time-intel functions with an un-contiguous date column → wrong windows.
- Forgetting marking the date table → functions silently fail.
- Slicing by 'month number' instead of the true date → YTD mixes years.
- Hard-coding 'since Jan 1' strings into measures.
Debugging tips
- YTD number wrong → check the date table's last date matches the calendar.
- Prior period blank → verify the date column is continuous for the shift.
- Rolling window drifting → confirm LASTDATE and the base filter recalc together.
Interview checkpoint
Hands-on lab
- Build YTD with a date spine equivalent
- Reproduce prior-year + YoY in SQL
- Prove the need for a contiguous date
Replicate YTD and Prior-Year in SQL
40 min · advanced
Quick quiz
Which function shifts the date context exactly one year back?
Time intelligence works fine using the fact table's own date column.