Rules / DAX Expressions

Hardcoded period in DAX

info HARDCODED_PERIOD_IN_DAX · built in · model layer · scope: Measure, CalculatedColumn, CalculationItem, CalculatedTable

What it checks

Measures, calculated columns, and calculation items whose DAX fixes a year or a date, and date tables whose CALENDAR ends on a fixed date.

Each finding names the object, as [Current Year Sales] for a measure, 'Sales'[Is Recent] for a calculated column, 'Date' for a date table, or a calculation item by its name, and points at the line where the first fixed period is written rather than the line where the object starts. Its detail says what the object fixes, as fixed year 2025 or fixed dates January 1, 2024 and December 31, 2024, or, for a date table, ends on a fixed date, December 31, 2026. A calculation item's detail adds its group, as fixed year 2025 in calculation group 'Time Calc'.

The rule reads three forms:

Example

Fires the rule
table Sales
	column Amount
		dataType: decimal
		sourceColumn: Amount
	column 'Order Date'
		dataType: dateTime
		sourceColumn: Order Date
	measure 'Current Year Sales' = CALCULATE(SUM(Sales[Amount]), YEAR(Sales[Order Date]) = 2025)
		formatString: #,0
After the fix
table Sales
	column Amount
		dataType: decimal
		sourceColumn: Amount
	column 'Order Date'
		dataType: dateTime
		sourceColumn: Order Date
	measure 'Current Year Sales' = CALCULATE(SUM(Sales[Amount]), YEAR(Sales[Order Date]) = YEAR(TODAY()))
		formatString: #,0

Why it matters

A year typed into DAX is right for the year it was written in and quietly wrong after it. A measure named Current Year Sales that filters on 2025 still shows 2025's sales all through 2026, under the same name, with no error, and a reader has no way to tell.

A date table built with CALENDAR(DATE(2020, 1, 1), DATE(2026, 12, 31)) has no rows after December 31, 2026. From January 1, 2027, new rows in a table related to it find no date there. A visual that groups by the date table's columns shows them under a blank value: the blank virtual row Power BI adds when a value on a relationship's many side has no match on its one side. A filter or slicer on the date table leaves them out, and time intelligence stops at the table's last day. Microsoft's guidance on date tables says CALENDAR's start and end can come from other DAX functions, like MAX(Sales[OrderDate]). An end taken from the data moves with it.

How to fix it

Take the period from something that moves with time.

A filter argument of CALCULATE written as a comparison, as in the example, can't reference a measure or use a nested CALCULATE, so put the latest year or the parameter's value in a variable first:

Latest Year Sales =
VAR LatestYear = YEAR ( CALCULATE ( MAX ( Sales[Order Date] ), REMOVEFILTERS () ) )
RETURN
    CALCULATE ( SUM ( Sales[Amount] ), YEAR ( Sales[Order Date] ) = LatestYear )

For a date table, end CALENDAR on the data rather than on a day:

Date = CALENDAR ( DATE ( 2020, 1, 1 ), DATE ( YEAR ( MAX ( Sales[Order Date] ) ), 12, 31 ) )

This ends on the last day of the latest year in Sales, so the table spans full years, as Microsoft's guidance asks of a date table, and grows when a refresh brings a new year. CALENDARAUTO does the same from every date in the model outside calculated columns and tables, so a birth date or a placeholder such as December 31, 9999 stretches it too. To reach the end of the current year whether or not the data gets there yet, end CALENDAR on DATE ( YEAR ( TODAY () ), 12, 31 ) instead.

In Power BI Desktop, select a measure, calculated column, or calculated table in the Data pane and edit its DAX in the formula bar. A calculation item is edited in Model view: select Model at the top of the Data pane to open Model explorer, then select the calculation item under its calculation group, and its DAX opens in the DAX formula bar (calculation groups). In TMDL, edit the expression after the object's =, or, for a date table, the source of its calculated partition.

When to ignore it

A fixed period is sometimes the point: a baseline year a measure compares against, a known event such as a change of data source or a day of bad data, a cohort such as customers whose first purchase was in 2023, a rule that changed in a given year, or sample data that never changes. A date table can end on purpose too, such as one that must stop at a contract's last day.

Often the better move is a name that says so. An object whose name carries its year, such as Sales 2024 or Growth from 19/20, reads as deliberate to anyone who opens the model, and the rule leaves it alone.

To ignore this rule on one object, add annotation pbiplint.ignore = HARDCODED_PERIOD_IN_DAX under the object in its TMDL file. Power BI Desktop keeps the annotation. To turn the rule off for a whole project, set "HARDCODED_PERIOD_IN_DAX": "off" under rules in pbiplint.config.json.

Quirks

Check a model for this Improve this page