Power BI Lesson 37 – Time Intelligence | Dataplexa
DAX · Lesson 37

Time Intelligence

Time intelligence is the ability to compare values across time periods — this year vs last year, this month vs the prior month, year-to-date vs the same period last year. DAX provides a dedicated set of functions for these calculations, but they all depend on one prerequisite: a properly built Calendar table connected to every fact table through a Date-type relationship. Get the Calendar table right and the time intelligence functions work simply. Skip it, and nothing works.

The Prerequisite — Calendar Table Setup

Before any time intelligence function will work, three things must be true. All three are required — missing any one causes every time intelligence measure to return BLANK or wrong values without any error message.

1
Calendar table has a continuous, unbroken date sequence
One row per date, no gaps, covering the full date range of your fact tables. A missing date causes time intelligence calculations to produce wrong results for any period containing that gap.
2
Calendar[Date] column is type Date — not DateTime, not Text
If the fact table's date column is DateTime and the Calendar is Date, the relationship will not match any rows. Change both to Date type in Power Query before building the relationship.
3
Relationship: Orders[OrderDate] → Calendar[Date] (Many-to-One)
The fact table is on the "many" side (multiple orders per date). Calendar is on the "one" side (one row per date). All time intelligence functions navigate this relationship to find matching date ranges.

The Working Dataset

Orders — 12 months of data used for all examples
MonthRevenuePrior Year Same MonthNotes
Jan 20234,200Start of dataset
Feb 20233,800
Mar 20235,100
Q1 2023 Total13,100
Apr–Dec 202338,900Full year 2023 = 52,000
Jan 20245,1004,200+21% YoY
Feb 20244,6003,800+21% YoY
Mar 20246,2005,100+22% YoY
Q1 2024 Total15,90013,100+21.4% YoY

Year-to-Date — TOTALYTD and DATESYTD

Year-to-date accumulates revenue from January 1 of the current year through the last date in the current filter context. Use TOTALYTD for a single-expression form, or CALCULATE with DATESYTD when you need to combine with other CALCULATE modifiers.

// TOTALYTD — simplest YTD form
Revenue YTD =
    TOTALYTD(
        SUM(Orders[Revenue]),
        Calendar[Date]
    )

// CALCULATE + DATESYTD — same result, more composable
Revenue YTD =
    CALCULATE(
        SUM(Orders[Revenue]),
        DATESYTD(Calendar[Date])
    )

// Fiscal year YTD — use the year-end-date argument for non-calendar years
// Fiscal year ending March 31 (April start):
Revenue FY YTD =
    TOTALYTD(
        SUM(Orders[Revenue]),
        Calendar[Date],
        "3/31"          -- year end date; YTD resets April 1
    )
Revenue YTD — monthly view, 2024
Month Monthly Revenue Revenue YTD
Jan 20245,1005,100
Feb 20244,6009,700
Mar 20246,20015,900
Apr 20244,80020,700
Dec 20246,10061,400
YTD accumulates from Jan 1 through each month · resets to zero on Jan 1 of the next year · Dec YTD = full year total

Same Period Last Year — SAMEPERIODLASTYEAR

SAMEPERIODLASTYEAR returns the dates in the current filter context shifted back by exactly one year. Use it inside CALCULATE to evaluate any measure over the prior year's equivalent period — whether the context is a month, a quarter, or a year.

// Prior Year Revenue — same period one year ago
Revenue PY =
    CALCULATE(
        SUM(Orders[Revenue]),
        SAMEPERIODLASTYEAR(Calendar[Date])
    )

// Year-over-Year growth amount
Revenue YoY Change =
    SUM(Orders[Revenue])
    - CALCULATE(SUM(Orders[Revenue]), SAMEPERIODLASTYEAR(Calendar[Date]))

// Year-over-Year growth percentage — with VAR for readability
Revenue YoY % =
VAR CurrentRev = SUM(Orders[Revenue])
VAR PriorRev   =
    CALCULATE(
        SUM(Orders[Revenue]),
        SAMEPERIODLASTYEAR(Calendar[Date])
    )
RETURN
    DIVIDE(CurrentRev - PriorRev, PriorRev)
Revenue PY, YoY Change and YoY % — monthly view Q1 2024
Month Revenue Revenue PY YoY Change YoY %
Jan 20245,1004,200+900+21.4%
Feb 20244,6003,800+800+21.1%
Mar 20246,2005,100+1,100+21.6%
Q1 2024 Total15,90013,100+2,800+21.4%
Total row correctly aggregates the quarter — SAMEPERIODLASTYEAR shifts Q1 2024 context back to Q1 2023 for the denominator

Prior Year YTD Comparison

Combining DATESYTD with SAMEPERIODLASTYEAR gives a YTD figure for the same point in the prior year — the most common executive dashboard comparison.

// Revenue YTD for the prior year — at the same point in the year
Revenue PY YTD =
    CALCULATE(
        SUM(Orders[Revenue]),
        DATESYTD(SAMEPERIODLASTYEAR(Calendar[Date]))
    )

// YTD vs Prior YTD growth %
Revenue YTD vs PY YTD % =
VAR CurrentYTD  = CALCULATE(SUM(Orders[Revenue]), DATESYTD(Calendar[Date]))
VAR PriorYTD    =
    CALCULATE(
        SUM(Orders[Revenue]),
        DATESYTD(SAMEPERIODLASTYEAR(Calendar[Date]))
    )
RETURN
    DIVIDE(CurrentYTD - PriorYTD, PriorYTD)
YTD vs Prior Year YTD — at each month end in 2024
Month Revenue YTD (2024) Revenue PY YTD (2023) YTD Growth %
Jan 20245,1004,200+21.4%
Feb 20249,7008,000+21.3%
Mar 202415,90013,100+21.4%
Jun 202431,20025,800+20.9%
Both YTD measures accumulate from Jan 1 of their respective years · comparing them shows whether this year is running ahead or behind at the same point last year

DATEADD — Flexible Period Shifting

DATEADD shifts the current date context by any number of days, months, quarters, or years in either direction. It is more flexible than SAMEPERIODLASTYEAR because you control both the interval type and the direction.

// DATEADD(dates, number_of_intervals, interval)
// interval: DAY, MONTH, QUARTER, YEAR
// Positive = forward in time, Negative = backward

// Prior month revenue
Revenue Prior Month =
    CALCULATE(
        SUM(Orders[Revenue]),
        DATEADD(Calendar[Date], -1, MONTH)
    )

// Prior quarter revenue
Revenue Prior Quarter =
    CALCULATE(
        SUM(Orders[Revenue]),
        DATEADD(Calendar[Date], -1, QUARTER)
    )

// Two years ago revenue
Revenue 2Y Ago =
    CALCULATE(
        SUM(Orders[Revenue]),
        DATEADD(Calendar[Date], -2, YEAR)
    )

// Month-over-Month growth %
Revenue MoM % =
VAR CurrentRev = SUM(Orders[Revenue])
VAR PriorRev   =
    CALCULATE(
        SUM(Orders[Revenue]),
        DATEADD(Calendar[Date], -1, MONTH)
    )
RETURN
    DIVIDE(CurrentRev - PriorRev, PriorRev)
DATEADD — Prior Month and MoM % — Jan through Apr 2024
Month Revenue Prior Month MoM %
Jan 20245,1004,900+4.1%
Feb 20244,6005,100-9.8%
Mar 20246,2004,600+34.8%
Apr 20244,8006,200-22.6%
DATEADD(-1, MONTH) shifts the filter context back one calendar month · Dec 2023 (4,900) becomes Jan 2024's prior month

Running Total

A running total accumulates all revenue from the beginning of time (or the beginning of the year) through the current date. It is different from YTD because it does not reset at the year boundary — it keeps growing indefinitely.

// All-time running total — accumulates from the beginning of the dataset
Revenue Running Total =
    CALCULATE(
        SUM(Orders[Revenue]),
        FILTER(
            ALL(Calendar[Date]),
            Calendar[Date] <= MAX(Calendar[Date])
        )
    )
// MAX(Calendar[Date]) = the last date in the current filter context
// FILTER on ALL(Calendar[Date]) = all dates up to and including that date
// CALCULATE evaluates SUM for that expanded date range

// Running total within the current year only
Revenue Running Total YTD =
    CALCULATE(
        SUM(Orders[Revenue]),
        FILTER(
            ALL(Calendar),
            Calendar[Year] = MAX(Calendar[Year])
            && Calendar[Date] <= MAX(Calendar[Date])
        )
    )
// Same as DATESYTD but written explicitly — shows the logic clearly
Revenue Running Total — cumulative from Jan 2023 onwards
Month Monthly Revenue Running Total (all-time)
Jan 20234,2004,200
Feb 20233,8008,000
Dec 20235,40052,000
Jan 20245,10057,100
Feb 20244,60061,700
Mar 20246,20067,900
Running total does NOT reset at year boundary (unlike YTD) · Jan 2024 running total = 52,000 + 5,100 = 57,100 · use for cumulative sales, lifetime customer value, or total units shipped

Quarter-to-Date and Month-to-Date

// Month-to-Date
Revenue MTD =
    TOTALMTD(SUM(Orders[Revenue]), Calendar[Date])
// Equivalent: CALCULATE(SUM(Orders[Revenue]), DATESMTD(Calendar[Date]))

// Quarter-to-Date
Revenue QTD =
    TOTALQTD(SUM(Orders[Revenue]), Calendar[Date])
// Equivalent: CALCULATE(SUM(Orders[Revenue]), DATESQTD(Calendar[Date]))

// Prior month MTD — MTD at the same point in the prior month
Revenue Prior MTD =
    CALCULATE(
        TOTALMTD(SUM(Orders[Revenue]), Calendar[Date]),
        DATEADD(Calendar[Date], -1, MONTH)
    )
MTD and QTD — daily view, first week of Feb 2024
Date Daily Revenue Revenue MTD Revenue QTD
Feb 11801805,280
Feb 22103905,490
Feb 31655555,655
MTD resets at the start of each month · QTD includes Jan 2024 (5,100) plus Feb days so far · QTD resets at the start of each quarter

Time Intelligence Function Reference

Function Returns Common use
TOTALYTDYear-to-date from Jan 1 to current dateRunning YTD total on a chart or card
TOTALMTDMonth-to-date from 1st of monthCurrent month running total for ops dashboards
TOTALQTDQuarter-to-date from 1st of quarterQuarterly progress tracking
DATESYTDTable of dates from Jan 1 to current — use inside CALCULATEWhen you need to combine YTD with other CALCULATE modifiers
SAMEPERIODLASTYEARDates shifted back exactly one yearYoY comparison — prior year same month / quarter / year
DATEADDDates shifted by N days/months/quarters/yearsPrior month/quarter, rolling N-month windows, forecasting
PARALLELPERIODComplete parallel period (full month/quarter/year) N periods agoPrior full quarter when current context is mid-quarter
PREVIOUSMONTH / YEARAll dates in the previous complete month or yearPrior full period comparison when context is always complete
DATESMTD / QTD / YTDTable of dates MTD / QTD / YTD — use inside CALCULATEComposable versions of TOTALMTD / TOTALQTD / TOTALYTD

Common Time Intelligence Gotchas

No Calendar
table
All time intelligence functions return BLANK with no error. The function silently fails. Always verify the Calendar table exists, has a Date-type column, and is connected via a relationship before debugging time intelligence measures.
DateTime vs
Date mismatch
If Orders[OrderDate] is DateTime and Calendar[Date] is Date, no rows match in the relationship. Time intelligence returns BLANK for all dates. Fix by changing OrderDate to type Date in Power Query before the relationship is created.
Calendar
gaps
A Calendar table missing dates causes DATESYTD to skip those dates — the YTD measure jumps incorrectly. Always generate the Calendar from List.Dates in Power Query (Lesson 28) to guarantee no gaps.
SPLY at year
boundary
If the Calendar table does not extend back far enough, SAMEPERIODLASTYEAR returns BLANK for any period whose prior year falls outside the Calendar range. Make the Calendar dynamic (Lesson 28) and always extend it at least one year before the earliest date in the fact table.

Teacher's Note: When a time intelligence measure returns BLANK, the first thing to check is not the DAX formula — it is the Calendar table setup. Ninety percent of time intelligence failures come from one of three things: the Calendar is missing, the date column type is wrong, or the Calendar does not cover the full date range of the fact table. Open Model View, confirm the relationship exists and points the right way (Orders → Calendar, not the reverse), click on Calendar[Date] and verify the column type is Date in the Data pane, then check the min and max dates in the Calendar cover all dates in Orders. Only after confirming all three should you look at the measure formula itself.

Practice

Practice 1 of 3

To calculate revenue accumulated from January 1 of the current year through the latest date in the current filter context, you use ___(SUM(Orders[Revenue]), Calendar[Date]) — the simplest single-expression YTD function.

Practice 2 of 3

To shift the current date context back by exactly three months — for example, to show Q1 revenue when Q2 is currently selected — you use CALCULATE(SUM([Revenue]), ___(Calendar[Date], -3, MONTH)).

Practice 3 of 3

If all time intelligence measures return BLANK with no error message, the most likely cause is that the ___ table is either missing, has a DateTime rather than Date column, or does not cover the full date range of the fact table.

Lesson Quiz

Quiz 1 of 3

A Revenue YTD measure works correctly for all months except January, which always shows the same value as the monthly revenue rather than accumulating. What is the most likely cause?

Quiz 2 of 3

Your SAMEPERIODLASTYEAR measure returns BLANK for January and February 2024 but works correctly for all other months. Your Calendar table covers 2023-01-01 to 2024-12-31. What is the problem?

Quiz 3 of 3

An executive wants a report showing both the current year YTD revenue and the prior year YTD revenue at the same point — so in April, both measures show Jan–April figures for their respective years. Which pair of measures is correct?

Next up — Lesson 38 covers Iterator Functions, explaining how SUMX, AVERAGEX, COUNTX, MAXX, MINX, and RANKX evaluate row-by-row expressions before aggregating, when they produce different results from their simpler counterparts, and the patterns where they are the only correct solution.