BI analyst validating Power Query date formats to prevent day and month swaps in Power BI

Stop Power Query From Silently Swapping Day and Month

Sat, Sep 26, 2026

A Power Query refresh can be technically successful and still put valid records in the wrong calendar month.

Suppose a source contract says dates are dd/MM/yyyy. The source sends 03/04/2025 for £10 and 04/03/2025 for £20. Under the declared convention, those rows mean April 3 and March 4. Under an explicit en-US interpretation, however, both strings remain parseable while their month/day identities reverse: March 4 and April 3. The two-row amount still totals 30. No aggregate reconciliation exposes the defect.

That is the acceptance problem addressed here: does the typed date represent the date the source declared, rather than merely a date the parser could construct?

Microsoft documents both sides of this boundary. Power Query uses locale when interpreting text during type conversion, and Microsoft specifically recommends inspecting year, month and day components when a displayed date is in doubt. Date.FromText also supports explicit Format and Culture options.

This is a date-only laboratory. There are no timestamps, time zones, gateways, accounts, external connectors or service refreshes. The examples use local Power BI Desktop blank queries and self-contained #table fixtures. The laboratory was not executed during commissioning research: outputs below are therefore expected outcomes derived from the documented contracts and authored test fixture, not measurements from an installed Desktop build. Acceptance remains pending until the protocol is run and the actual environment is recorded.

Define what each source date is supposed to mean

The first control is not Power Query code. It is a source contract.

For this fixture, the authoritative convention is:

Contract field

Declared value

Source date representation

Text

Pattern

dd/MM/yyyy

Culture

en-GB

Target BI type

date

Time component

None

Time-zone semantics

None

Null rule

null permitted only where explicitly classified as an unknown date

Invalid calendar date

Reject from accepted dated population

Unknown convention

Hold; do not infer

The contract owner must be the party that can authoritatively state what the source text means: for example, the upstream application owner, data-product owner or formally approved interface specification. A BI developer can implement the rule but should not create the rule by looking at ambiguous values and choosing the interpretation that seems plausible.

That distinction is consistent with the broader need for semantic guardrails in governed self-service BI: a downstream interface is only trustworthy when business meaning is defined separately from the mechanism that happens to produce a value.

Write the expected calendar identity before running the conversion:

Row

Raw text

Contract expectation

Amount

Acceptance role

R1

03/04/2025

#date(2025, 4, 3)

10

Core

R2

04/03/2025

#date(2025, 3, 4)

20

Core

R3

13/02/2025

#date(2025, 2, 13)

not part of core total

Convention control

R4

null

permitted unknown date

not part of core total

Null-policy control

R5

31/02/2025

invalid calendar date

not part of core total

Quarantine control

The core amount reconciliation is deliberately restricted to R1 and R2: 10 + 20 = 30. R3–R5 are controls, not additional members of that aggregate population.

“Parsed successfully” is therefore an implementation observation, not an acceptance verdict. A successful parser has answered, “Can I construct a date?” The business test asks, “Did I construct the source-declared date?”

Pin the Desktop environment and keep the source as text

Culture defects are difficult to investigate retrospectively when the environment record says only “Power BI Desktop” or “Windows.” The test record should capture the exact installed Desktop version/build, operating-system edition/version/build, OS regional format, current-file Power Query locale, query revision and the types entering every conversion step.

Microsoft documents that Power Query Desktop normally uses the operating system’s regional format to interpret values for data-type conversion unless the current file’s regional settings override it. The same documentation identifies automatic type detection and generated Changed Type steps as behavior worth inspecting.

For this laboratory, do not fill missing environment fields with a release number found on the web. A Microsoft documentation date or current public Desktop release is not evidence of the build actually used to execute a PBIX file.

The pre-run ledger should look like this:

Environment item

Required execution evidence

Commissioning status

Power BI Desktop exact version/build

Transcribe from installed Desktop

Not recorded; execution pending

Windows edition/version/OS build

Transcribe from test machine

Not recorded; execution pending

Windows regional format

Record exact test-machine setting

Not recorded; execution pending

Power Query current-file regional setting

Record before execution

Not recorded; execution pending

Query revision

DC-DMY-2026-09-25-r1

Authored

RawDate source type

nullable text

Authored

Target parsed type

date

Authored

External connection

None

Authored

Gateway/service refresh

None

Authored

That separation matters. The Microsoft Learn pages used here are living references. As accessed September 25, 2026, the Date.FromText and Date.From pages showed September 16, 2025 update dates, Table.TransformColumnTypes showed April 30, 2026, and the Power Query error-handling page showed July 16, 2025. Those publication/update dates document the pages consulted; they do not lock an M engine or Desktop executable to a version.

Create a new local PBIX, open Power Query, choose a blank query, and create DateSource with the following complete fixture. No file, database, API or cloud endpoint is involved:

let
    QueryRevision = "DC-DMY-2026-09-25-r1",

    Source =
        #table(
            type table [
                RowID = text,
                TestPopulation = text,
                RawDate = nullable text,
                Amount = nullable number
            ],
            {
                {"R1", "CORE",    "03/04/2025", 10},
                {"R2", "CORE",    "04/03/2025", 20},
                {"R3", "CONTROL", "13/02/2025", null},
                {"R4", "CONTROL", null,         null},
                {"R5", "CONTROL", "31/02/2025", null}
            }
        ),

    WithRevision =
        Table.AddColumn(
            Source,
            "QueryRevision",
            each QueryRevision,
            type text
        )
in
    WithRevision

RawDate remains immutable nullable text. Any candidate date is created in another column or query. That is the local equivalent of protecting analytics ingestion and transformation boundaries: once a text representation has been destructively overwritten, downstream code may no longer contain enough evidence to reconstruct what happened.

Before proceeding, inspect Applied Steps for an automatically generated or hand-authored Changed Type operation. With this typed #table, there is no reason to let an unexplained conversion become the authority for RawDate.

Construct dates that expose a silent convention error

Keep the expected dates outside the conversion under test. Create a second blank query named ExpectedDates:

let
    Expected =
        #table(
            type table [
                RowID = text,
                ExpectedDate = nullable date,
                ContractState = text
            ],
            {
                {"R1", #date(2025, 4, 3),  "VALID"},
                {"R2", #date(2025, 3, 4),  "VALID"},
                {"R3", #date(2025, 2, 13), "VALID"},
                {"R4", null,               "ALLOWED_NULL"},
                {"R5", null,               "INVALID_QUARANTINE"}
            }
        )
in
    Expected

This table is the oracle. It must not be generated by parsing RawDate, because doing so would make the test circular. R1’s expected date is authored independently as April 3, 2025; R2’s is March 4, 2025; R3’s is February 13, 2025.

The distinction between R4 and R5 is equally important. Both can ultimately have no accepted date value, but for entirely different reasons. R4 is an explicitly permitted unknown. R5 is rejected because February 31 is not a valid calendar date.

Use ambiguous values as the principal counterexample

R1 and R2 are more valuable than obvious failures because both strings can form valid dates under both day/month conventions.

For 03/04/2025:

  •         declared dd/MM/yyyy identity: year 2025, month 4, day 3;

  •         U.S.-style month/day identity: year 2025, month 3, day 4.

For 04/03/2025, the relationship reverses.

The expected period evidence is therefore:

Interpretation

March

April

Core grand total

Contract-correct

R2 = 20

R1 = 10

30

Wrong en-US interpretation

R1 = 10

R2 = 20

30

The total does not move. The record identity does.

That is why an acceptance procedure based only on error count, row count or total sales can pass while the report is semantically wrong.

Separate invalid dates from permitted missing dates

R3 is intentionally asymmetric: 13/02/2025 is valid under the declared day/month pattern but is incompatible with interpreting 13 as a month. Microsoft’s Power Query data-type guidance uses the same class of example: a U.S. locale applied to U.K.-style text can produce an error when the would-be month is outside the calendar range.

R3 therefore helps distinguish a wrong parser convention from corrupt source data.

R4 is different again. null is allowed by the authored contract. It should remain null, be visibly classified as an allowed unknown date, and not acquire March or April membership.

R5 is a true invalid-date control. Under dd/MM/yyyy, 31/02/2025 expresses a nonexistent February date. It belongs in quarantine with its raw value and error evidence retained.

These three controls should never be silently appended to the core amount reconciliation. Whenever this article says “core total = 30,” the population is explicitly TestPopulation = "CORE".

Run the deliberately wrong culture conversion

Now create Wrong_enUS_Core. This query filters the two core rows, duplicates RawDate, and deliberately converts the duplicate with the wrong culture:

let
    Source = DateSource,

    CoreOnly =
        Table.SelectRows(
            Source,
            each [TestPopulation] = "CORE"
        ),

    PreserveRawText =
        Table.DuplicateColumn(
            CoreOnly,
            "RawDate",
            "TypedDate"
        ),

    WrongCultureConversion =
        Table.TransformColumnTypes(
            PreserveRawText,
            {{"TypedDate", type date}},
            "en-US"
        )
in
    WrongCultureConversion

The culture argument is explicit, so the counterexample does not depend on whether the author’s machine happens to use a U.S., U.K. or another regional configuration.

Microsoft documents that Table.TransformColumnTypes accepts an optional culture and normally performs a conversion using the .From function corresponding to the destination type.

Because this commissioning exercise was not executed, the following is an expected transcript, not an observed result:

Row

RawDate preserved

Expected wrong typed result

Parser status

Semantic status

R1

03/04/2025

#date(2025,3,4)

Success

Fail

R2

04/03/2025

#date(2025,4,3)

Success

Fail

Both parser operations are expected to succeed. Both business meanings are expected to be wrong.

This is the dangerous class of Power Query day/month swap: there is no malformed value in the two-row core population. The conversion can produce a perfectly legitimate date type and still violate the declared source contract.

The equivalent operational mistake can arise when a Changed Type step is accepted without inspecting its culture assumptions. The important point is not whether Power Query generated the step automatically or a developer wrote it manually. Acceptance must be based on row-to-date meaning.

Validate calendar components, not visual formatting

After conversion, stop looking only at how the date is rendered in the preview.

Microsoft explicitly advises extracting the year, month and day components when there is doubt about a converted date, because globalization settings can affect how date values appear. A principal-level acceptance check should compare those components with an independent oracle.

For R1, acceptance requires:

ActualYear  = 2025
ActualMonth = 4
ActualDay   = 3
Expected    = #date(2025, 4, 3)

For R2:

ActualYear  = 2025
ActualMonth = 3
ActualDay   = 4
Expected    = #date(2025, 3, 4)

If R1 evaluates as year 2025, month 3, day 4, formatting it as 03/04/2025, 3/4/2025, Mar 4, 2025 or anything else does not satisfy the contract. The underlying calendar identity is wrong.

This boundary matters to the insights that depend on prepared BI data: downstream analytical sophistication cannot make a March row become an April row if the preparation layer has already assigned the wrong date.

Keep parser success separate from semantic success

Use separate evidence fields:

Field

Question answered

ParseHasError

Could the chosen conversion construct a date?

ParsedDate

What typed date was constructed?

ExpectedDate

What date does the contract require?

MatchesExpectedDate

Does actual calendar identity equal expected identity?

FinalDisposition

What operational action follows?

Microsoft’s Power Query error-handling guidance documents try as producing a record that indicates whether an error occurred and exposes either a value or an error record. Expanding a try result exposes HasError, Value and Error.

That means a row can legitimately have:

ParseHasError = false
MatchesExpectedDate = false

R1 and R2 under the explicit wrong culture are exactly that class.

Treating HasError = false as “good date” collapses syntax and semantics into one test. The acceptance ledger must keep them independent.

Show why matching totals cannot certify date identity

For the core population, calculate amount only after explicitly selecting R1 and R2.

The contract-correct mapping is:

Period

Row

Amount

March 2025

R2

20

April 2025

R1

10

Core total

R1 + R2

30

The wrong en-US mapping is:

Period

Row

Amount

March 2025

R1

10

April 2025

R2

20

Core total

R1 + R2

30

Nothing is wrong with the amount field. Nothing is wrong with the arithmetic. Rewriting a measure would therefore repair the wrong layer.

The defect sits at the text-to-date boundary. The proper evidence is whether each RowID occupies the calendar date declared by its source contract.

Reparse raw text with an explicit format and culture

The repair begins from the retained text.

Microsoft’s Date.FromText accepts an options record containing Format and Culture. The documentation also states that omitting the format causes best-effort parsing, while culture affects the interpretation used when a format is absent and controls culture-sensitive format specifiers when a format is supplied.

For this contract, do not rely on best effort:

Date.FromText(
    [RawDate],
    [Format = "dd/MM/yyyy", Culture = "en-GB"]
)

Create CorrectedEvidence with the following complete query. It verifies RowID uniqueness independently in both tables, parses the retained raw text, captures errors, compares with the separate oracle and assigns an expected disposition:

let
    Source = DateSource,
    Expected = ExpectedDates,

    SourceCounts =
        Table.Group(
            Source,
            {"RowID"},
            {{"RowCount", each Table.RowCount(_), Int64.Type}}
        ),

    SourceDuplicates =
        Table.SelectRows(
            SourceCounts,
            each [RowCount] <> 1
        ),

    CheckedSource =
        if Table.RowCount(SourceDuplicates) > 0 then
            error Error.Record(
                "DuplicateRowID",
                "DateSource contains non-unique RowID values.",
                SourceDuplicates
            )
        else
            Source,

    ExpectedCounts =
        Table.Group(
            Expected,
            {"RowID"},
            {{"RowCount", each Table.RowCount(_), Int64.Type}}
        ),

    ExpectedDuplicates =
        Table.SelectRows(
            ExpectedCounts,
            each [RowCount] <> 1
        ),

    CheckedExpected =
        if Table.RowCount(ExpectedDuplicates) > 0 then
            error Error.Record(
                "DuplicateExpectedRowID",
                "ExpectedDates contains non-unique RowID values.",
                ExpectedDuplicates
            )
        else
            Expected,

    WithParseAttempt =
        Table.AddColumn(
            CheckedSource,
            "ParseAttempt",
            each
                if [RawDate] = null then
                    [HasError = false, Value = null, Error = null]
                else
                    try Date.FromText(
                        [RawDate],
                        [
                            Format = "dd/MM/yyyy",
                            Culture = "en-GB"
                        ]
                    ),
            type record
        ),

    ExpandedAttempt =
        Table.ExpandRecordColumn(
            WithParseAttempt,
            "ParseAttempt",
            {"HasError", "Value", "Error"},
            {"ParseHasError", "ParsedDate", "ParseError"}
        ),

    TypedParsedDate =
        Table.TransformColumnTypes(
            ExpandedAttempt,
            {{"ParsedDate", type date}}
        ),

    WithErrorReason =
        Table.AddColumn(
            TypedParsedDate,
            "ErrorReason",
            each
                if [ParseError] = null then
                    null
                else
                    Record.FieldOrDefault(
                        [ParseError],
                        "Reason",
                        null
                    ),
            type nullable text
        ),

    WithErrorMessage =
        Table.AddColumn(
            WithErrorReason,
            "ErrorMessage",
            each
                if [ParseError] = null then
                    null
                else
                    Record.FieldOrDefault(
                        [ParseError],
                        "Message",
                        null
                    ),
            type nullable text
        ),

    JoinedExpected =
        Table.NestedJoin(
            WithErrorMessage,
            {"RowID"},
            CheckedExpected,
            {"RowID"},
            "Expected",
            JoinKind.LeftOuter
        ),

    ExpandedExpected =
        Table.ExpandTableColumn(
            JoinedExpected,
            "Expected",
            {"ExpectedDate", "ContractState"},
            {"ExpectedDate", "ContractState"}
        ),

    WithActualYear =
        Table.AddColumn(
            ExpandedExpected,
            "ActualYear",
            each
                if [ParsedDate] = null
                then null
                else Date.Year([ParsedDate]),
            type nullable number
        ),

    WithActualMonth =
        Table.AddColumn(
            WithActualYear,
            "ActualMonth",
            each
                if [ParsedDate] = null
                then null
                else Date.Month([ParsedDate]),
            type nullable number
        ),

    WithActualDay =
        Table.AddColumn(
            WithActualMonth,
            "ActualDay",
            each
                if [ParsedDate] = null
                then null
                else Date.Day([ParsedDate]),
            type nullable number
        ),

    WithExpectedYear =
        Table.AddColumn(
            WithActualDay,
            "ExpectedYear",
            each
                if [ExpectedDate] = null
                then null
                else Date.Year([ExpectedDate]),
            type nullable number
        ),

    WithExpectedMonth =
        Table.AddColumn(
            WithExpectedYear,
            "ExpectedMonth",
            each
                if [ExpectedDate] = null
                then null
                else Date.Month([ExpectedDate]),
            type nullable number
        ),

    WithExpectedDay =
        Table.AddColumn(
            WithExpectedMonth,
            "ExpectedDay",
            each
                if [ExpectedDate] = null
                then null
                else Date.Day([ExpectedDate]),
            type nullable number
        ),

    WithMatch =
        Table.AddColumn(
            WithExpectedDay,
            "MatchesExpectedDate",
            each
                if [ContractState] = "VALID" then
                    [ParseHasError] = false
                    and [ParsedDate] = [ExpectedDate]
                else if [ContractState] = "ALLOWED_NULL" then
                    [ParseHasError] = false
                    and [ParsedDate] = null
                else if [ContractState] = "INVALID_QUARANTINE" then
                    [ParseHasError] = true
                else
                    false,
            type logical
        ),

    WithDisposition =
        Table.AddColumn(
            WithMatch,
            "FinalDisposition",
            each
                if [ContractState] = "INVALID_QUARANTINE"
                    and [ParseHasError] = true then
                    "QUARANTINE"
                else if [ContractState] = "ALLOWED_NULL"
                    and [ParseHasError] = false
                    and [ParsedDate] = null then
                    "ACCEPT_ALLOWED_NULL"
                else if [ContractState] = "VALID"
                    and [ParseHasError] = false
                    and [ParsedDate] = [ExpectedDate] then
                    "ACCEPT"
                else
                    "HOLD",
            type text
        )
in
    WithDisposition

Expected, not measured: R1–R3 should become valid contract dates, R4 should remain an accepted null, and R5 should expose an error and be quarantined. The exact error text for R5 should be captured from the executed environment rather than hard-coded into an expected transcript.

Test the repair that arrives after text has been lost

A common attempted repair comes too late: the wrong date has already been materialized, the source text was overwritten, and another culture is applied to the typed date.

That is not equivalent to reparsing raw text.

Microsoft’s Date.From contract states that when the input is already a date, the value is returned. Culture participates when text needs interpretation; an already typed date no longer contains the original 03/04 token ordering to reinterpret.

Create this deliberately misleading core query:

let
    Source = DateSource,

    CoreOnly =
        Table.SelectRows(
            Source,
            each [TestPopulation] = "CORE"
        ),

    KeepTestColumns =
        Table.SelectColumns(
            CoreOnly,
            {"RowID", "RawDate", "Amount"}
        ),

    WrongTypedDate =
        Table.TransformColumnTypes(
            KeepTestColumns,
            {{"RawDate", type date}},
            "en-US"
        ),

    TextNowLost =
        Table.RenameColumns(
            WrongTypedDate,
            {{"RawDate", "WrongDate"}}
        ),

    AttemptedLateRepair =
        Table.TransformColumns(
            TextNowLost,
            {
                {
                    "WrongDate",
                    each Date.From(_, "en-GB"),
                    type date
                }
            }
        )
in
    AttemptedLateRepair

Expected outcome: R1 remains #date(2025,3,4) and R2 remains #date(2025,4,3). The en-GB argument has no source text left to reinterpret.

Applying another Table.TransformColumnTypes(..., "en-GB") to a column that is already date has the same conceptual limitation. The M reference says type transformation normally invokes the corresponding .From method, while Date.From says an input already of type date is returned.

Similarly, choosing a different visual or model display pattern may change representation, but it does not supply the missing original text. That conclusion follows from the documented distinction between typed-date values, globalization-driven display and text parsing.

The recovery path is to return to RawDate or reacquire an authoritative raw representation. Do not invent an inverse transformation from an ambiguous date.

Capture errors without concealing data loss

An error is evidence. Converting every error to null, deleting error rows or using try ... otherwise null without retaining the error record destroys information needed to distinguish permitted missing data from rejected input.

Microsoft’s Power Query error-handling documentation describes try as producing a record with HasError plus either the successful Value or an Error record; the error can in turn expose reason, message and detail fields.

That mechanism supports three different states in this fixture:

State

Example

HasError

Accepted date

Meaning

Successful valid parse

R1 after corrected parser

false

April 3

Contract satisfied

Permitted missing value

R4

false

null

Unknown allowed by contract

Invalid source value

R5

true

none

Quarantine

R4 and R5 must never collapse to the same evidence row merely because both lack an accepted date.

The quarantine record should retain, at minimum, RowID, RawDate, query revision, declared format/culture, HasError, error reason/message, expected contract state and final disposition. Do not make downstream reviewers reverse-engineer why a value disappeared.

Produce separate accepted and quarantined outputs

With CorrectedEvidence created, an accepted output can be a separate query:

let
    Source = CorrectedEvidence,

    Accepted =
        Table.SelectRows(
            Source,
            each
                [FinalDisposition] = "ACCEPT"
                or [FinalDisposition] = "ACCEPT_ALLOWED_NULL"
        )
in
    Accepted

And the quarantine output:

let
    Source = CorrectedEvidence,

    Quarantined =
        Table.SelectRows(
            Source,
            each [FinalDisposition] = "QUARANTINE"
        )
in
    Quarantined

Expected accepted population: R1, R2, R3 and R4. Expected quarantine: R5.

“Accepted population” does not mean every accepted record belongs to a reporting month. R4 is allowed through the contractual quality gate while retaining a null date, so it should not be silently attributed to February, March or April.

Likewise, the core total comparison continues to refer only to R1 and R2. The control rows exist to test date handling, not to redefine the amount population.

Hold mixed or undocumented source conventions

The parser is not an authority on an undocumented source.

If one file contains 03/04/2025 and no trustworthy metadata says whether that means March 4 or April 3, both interpretations may be syntactically valid. Choosing whichever result “looks right,” matches the most rows or produces fewer errors is not semantic validation.

The correct disposition is hold until an authoritative convention is established.

A genuinely mixed source can be supported only when the distinction itself is trustworthy, for example through an approved source-system field identifying the format for each record or partition. Then the BI contract can map that declared field to an explicit parser.

Without such evidence, automatic guessing converts uncertainty into false precision.

Reconcile the full row-to-date evidence table

The acceptance ledger should be reviewable without opening the report canvas.

For the corrected parser, the following table represents the expected evidence. It remains to be replaced or confirmed by an executed transcript from the pinned Desktop/Windows environment.

Row

Raw value

Input type

Conversion

Format / culture

Expected parse

Actual Y-M-D expected

Contract Y-M-D

Expected final disposition

R1

03/04/2025

nullable text

Date.FromText

dd/MM/yyyy, en-GB

Success

2025-4-3

2025-4-3

ACCEPT

R2

04/03/2025

nullable text

Date.FromText

dd/MM/yyyy, en-GB

Success

2025-3-4

2025-3-4

ACCEPT

R3

13/02/2025

nullable text

Date.FromText

dd/MM/yyyy, en-GB

Success

2025-2-13

2025-2-13

ACCEPT

R4

null

nullable text

explicit null branch

contract permits null

No error

null

permitted null

ACCEPT_ALLOWED_NULL

R5

31/02/2025

nullable text

Date.FromText

dd/MM/yyyy, en-GB

Error expected

none

invalid date

QUARANTINE

The wrong core conversion should produce a separate expected ledger:

Row

Wrong actual

Expected

Parse error?

Semantic match?

Action

R1

2025-3-4

2025-4-3

No

No

REPARSE_FROM_RAW

R2

2025-4-3

2025-3-4

No

No

REPARSE_FROM_RAW

R3 under a U.S. interpretation is also expected to expose a parser failure rather than prove that the source value is invalid. Because the source contract independently declares R3 as February 13, its wrong-parser disposition is reparse from raw, not quarantine.

RowID uniqueness should be checked independently in the source fixture and expected-date table before comparison. That is why the corrected query explicitly fails on duplicate RowIDs before joining the oracle. The point here is not a general lesson about table joins; it is to ensure that one evidence row means one source record.

A five-row input count, a completed refresh or an unchanged amount total is not a substitute for this ledger.

Recover an affected report from approved raw inputs

Once a wrong date conversion is confirmed, identify the earliest representation that still preserves authoritative source meaning.

In this laboratory, that is DateSource[RawDate]. In a real report it may be an earlier Power Query step, a retained staging field or a reproducible raw extract. The recovery sequence is:

1.      Preserve the affected PBIX and wrong query revision for evidence.

2.      Locate the earliest trustworthy text representation.

3.      Confirm the source owner’s format and culture contract.

4.      Replace the incorrect conversion with an explicit parser.

5.      Re-run row-level expected-date tests.

6.      Reconcile affected month membership before republishing.

7.      Record which historical outputs were produced under the defective conversion.

Do not begin with the report visual and attempt to compensate for swapped months there. The repair belongs where text first became a semantic date.

This complements broader report freshness and data-quality checks: knowing that data is recent does not establish that its calendar dimensions are semantically correct.

For the fixture, recovery should restore:

R1 -> #date(2025, 4, 3) -> April
R2 -> #date(2025, 3, 4) -> March
R3 -> #date(2025, 2, 13) -> February
R4 -> null              -> allowed unknown
R5 -> quarantine        -> no accepted month

The March/April core total remains 30 both before and after repair, so the acceptance proof must include period membership rather than only the grand total.

Changing the current PBIX also does not establish that previously exported PDFs, spreadsheets, screenshots, presentations or management packs have somehow become correct. Those outputs require a separate impact review based on their production dates and affected reporting periods.

Keep rollback and semantic repair distinct

A deployment rollback answers a different question from semantic acceptance.

Suppose query revision r2 introduces a technical failure and the team rolls back to r1. If r1 already interpreted day and month incorrectly, the rollback can restore operational stability while restoring the original semantic defect.

Therefore maintain two controls:

Control

Purpose

Implementation rollback

Restore a known prior query/file state

Semantic acceptance

Prove the resulting row-to-date mapping satisfies the source contract

Keep the original failing query, corrected query, expected-date table and test fixture together. A reviewer should be able to reproduce both the wrong and corrected paths without relying on memory of what an earlier Changed Type step contained.

The strongest recovery position exists when RawDate has been preserved. If only #date(2025,3,4) survives and the source text plus authoritative expected date have both been lost, there may be no reliable way to decide whether the original text meant March 4 or April 3.

That is a hold condition.

Do not “repair” such values by swapping month and day globally. Some dates may have originated unambiguously; other rows may have been converted under another rule; dates where day equals month provide no diagnostic signal; and dates such as the 13th cannot be reversed under the same assumptions.

The evidence requirement is provenance, not a clever inverse formula.

The test PBIX itself should also remain local for this protocol. No gateway, tenant, account or service refresh is needed. After acceptance evidence is captured, archive the test artifact according to the team’s normal controlled-test retention policy or remove it if the policy requires disposal; there are no external credentials or resources to clean up.

Decide accept, reparse, quarantine or hold

A useful BI repair playbook ends with actions, not merely diagnostics.

Decision

Trigger

Minimum evidence

Primary owner

ACCEPT

Known contract; parsed date equals expected calendar identity

Raw value, parser specification, typed value, expected Y/M/D, matching row evidence

BI reviewer

ACCEPT_ALLOWED_NULL

Contract explicitly permits missing date and input is null

Raw null, null policy, no parse error, disposition

Source owner + BI reviewer

REPARSE_FROM_RAW

Contract is known; current typed value violates it; trustworthy raw text remains

Raw text, wrong conversion evidence, source contract, proposed explicit parser

BI developer

QUARANTINE

Input violates a known date contract

RowID, raw value, failed parse/error evidence, declared contract

BI developer + source owner

HOLD

Convention is unknown, contradictory, or trustworthy provenance has been lost

Documented uncertainty and missing authority/evidence

Data/source owner

For the deliberately wrong en-US path:

  •        R1: REPARSE_FROM_RAW. It parses, but March 4 is not the declared April 3.

  •         R2: REPARSE_FROM_RAW. It parses, but April 3 is not the declared March 4.

  •         R3: REPARSE_FROM_RAW. Failure under the wrong convention does not make a contract-valid February 13 corrupt.

  •        R4: ACCEPT_ALLOWED_NULL. The source contract explicitly permits this unknown.

  •        R5: QUARANTINE. February 31 violates the known contract.

After the corrected explicit dd/MM/yyyy, en-GB conversion is executed and independently verified, expected dispositions become R1–R3 ACCEPT, R4 ACCEPT_ALLOWED_NULL, R5 QUARANTINE.

A sixth hypothetical row whose source convention was never documented would be HOLD, even if both en-GB and en-US produced valid dates. Parser success is not evidence of source intent.

Likewise, a row whose wrong typed date survives but whose raw text and authoritative expectation have both been lost is HOLD. A date is not recoverable merely because somebody can propose a plausible interpretation.

These decisions preserve an important separation: syntactic invalidity, contract-valid nullability, known convention mismatch and unknown provenance are different states and require different operational responses.

Assign source-convention and BI-model ownership

Date correctness crosses an organizational boundary, so ownership should be explicit.

The source owner declares what textual dates mean: pattern, culture, null policy and any approved variation. That declaration must exist independently of Power Query.

The BI developer implements that contract, preserves raw evidence, makes conversion input types explicit and prevents an implicit locale assumption from becoming the only record of the rule.

The reviewer verifies the row-level evidence: parser error status, actual year/month/day, expected year/month/day, period membership and final disposition. The reviewer also checks that the core amount population has not been quietly widened by diagnostic controls.

The report owner assesses affected reporting periods and previously distributed outputs when a semantic defect is discovered.

This responsibility split is more useful here than revisiting Power BI and the wider BI tool landscape: choosing a BI platform does not establish whether an ambiguous source date was interpreted according to its contract.

Re-run the acceptance suite when any of these material inputs change: source format, declared culture, source application revision, RawDate type, conversion expression, Changed Type step, current-file regional setting, Desktop build or query revision.

That does not mean every change will alter the result. It means a previously captured result is evidence for the environment and implementation that produced it, not universal proof for a later configuration.

The same discipline applies to documentation. Microsoft’s Power Query references are evidence of documented function contracts, but the article should not call their “last updated” dates product version locks. An executed test needs its own Desktop and operating-system build record.

Build the BI foundations behind trustworthy date preparation

Reliable reporting begins before a dashboard calculation: analysts need to understand typed data, transformation boundaries, source contracts and the evidence required to accept prepared data.

Refonte Learning’s published Business Intelligence Essentials page currently describes a three-month program at 8–10 hours per week covering areas including data analysis, reporting/dashboard creation, visualization, SQL for BI, warehousing fundamentals and KPI monitoring. Its FAQ names Power BI, Tableau, Excel and SQL-based platforms, practical projects and personalized mentorship, and describes the program as beginner-friendly.

The published page does not establish that this specific Power Query date-culture acceptance laboratory is part of the curriculum, so it should not be presented as such. The appropriate invitation is simply to inspect the published curriculum for the broader BI foundations behind exercises like this.

The acceptance standard remains narrower and stricter: preserve the raw text, obey the declared dd/MM/yyyy contract, prove each RowID’s calendar identity, reparse known convention mistakes, quarantine invalid dates, allow only contract-approved nulls, and hold any value whose meaning cannot be established from trustworthy evidence.