Skip to content

[PowerShell] Start-FinOpsCostExport shifts the export period by one day in positive UTC offsets #2255

Description

🐛 Problem

Start-FinOpsCostExport converts its date parameters to UTC before truncating them to a day. For any positive UTC offset that moves the date back by one day, so the exported period is shifted:

# machine in UTC+01:00
Start-FinOpsCostExport -Name 'CostExport' -Scope $scope -StartDate '2026-01-01'
value
requested 2026-01-01
actually exported 2025-12-31 through 2026-01-30

The user asks for January and gets 31 December added while 31 January is missing.

Reproducible without Azure:

$in = [datetime]'2026-01-01'      # how PowerShell parses the parameter: local midnight
$in.ToUniversalTime().Date        # -> 2025-12-31 in UTC+1/+2

🔍 Where

src/powershell/Public/Start-FinOpsCostExport.ps1 applies the same pattern in 7 places, e.g.:

# L102 -- backfill start
$StartDate = (Get-Date -Day 1 -Hour 0 -Minute 0 -Second 0 -Millisecond 0).ToUniversalTime().Date

# L119-L126 -- explicit start/end
$StartDate = $StartDate.ToUniversalTime().Date
$EndDate   = $EndDate.ToUniversalTime().Date
$EndDate   = $StartDate.ToUniversalTime().Date.AddMonths(1).AddDays(-1)

-Backfill is affected the same way: the first of the current month becomes the last day of the previous month, so each backfilled window is off by a day.

No other command in src/powershell/Public/ uses this pattern.

🌍 Why this has not shown up

The defect is asymmetric:

  • Negative offsets (Americas): local midnight converts to a later hour on the same UTC day, so .Date is unchanged. No visible effect.
  • Positive offsets (Europe, Asia): local midnight converts to the previous UTC day. Off by one, always.

CI runs on UTC, so it never reproduces there.

🧪 The unit tests currently encode the behavior

Three tests in src/powershell/Tests/Unit/Start-FinOpsCostExport.Tests.ps1 fail on any machine east of UTC:

[-] Should call /run with default end date
[-] Should call /run for backfill
[-] Should adjust end date if it would be in the future
    Expected Invoke-Rest ... to be called at least 1 times, but was called 0 times

They build their expected values with Get-Date -Month 1 -Day 1 -Hour 0 ... -AsUTC, which converts local midnight to UTC and lands on 31 December, and then compare against a format string with a hardcoded yyyy-01-01. Both sides shift, so the -ParameterFilter never matches.

This is worth calling out because "just fix the tests" would cement the product behavior. The tests are a symptom, not the defect.

🤔 Question before a fix

Are -StartDate / -EndDate meant to be calendar dates or instants?

The help text reads like calendar dates ("Day to start pulling the data for", "Last day to pull data for"), and Cost Management export periods are day-granular, which suggests the intended handling is to keep the day the caller named and not convert time zones:

$StartDate = [datetime]::SpecifyKind($StartDate.Date, [System.DateTimeKind]::Utc)

That is a behavior change for existing users at positive offsets (it would start exporting the day they actually asked for) and a no-op for everyone else, so it seems worth confirming the intent before changing it. Happy to submit a PR covering the command and the three tests together once the direction is confirmed.

🔧 Environment

  • FinOps hub version: current dev; the pattern is unchanged in v13 and v14
  • PowerShell: 7.6.3 on Windows, machine time zone UTC+01:00 (UTC+02:00 during DST)
  • Billing account type: not relevant — the shift happens before any API call

ℹ️ Additional context

Found while verifying the R15 release candidate end to end. The three failing tests are also the reason the local suite is not green outside the CI time zone; they are unrelated to the two workflow-pinning failures fixed by #2252.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions