fix(powershell): treat Start-FinOpsCostExport dates as UTC calendar dates - #2261
Open
Roland Krummenacher (RolandKrummenacher) wants to merge 2 commits into
Open
Conversation
…ates Start-FinOpsCostExport converted -StartDate and -EndDate with ToUniversalTime() before truncating them to a day. For any positive UTC offset, local midnight falls on the previous UTC day, so .Date moved the requested period back by one day. The defect is asymmetric: at UTC and negative offsets local midnight converts to a later hour on the same UTC day, so .Date is unchanged and behavior is identical before and after this change. CI runs on UTC, which is why it never reproduced there. Cost Management export periods are UTC and day-granular, and the help text describes both parameters as days, so the parameters are now treated as calendar dates: the day the caller names is tagged as UTC rather than converted. The -Backfill default start date is derived from the current UTC month for the same reason; previously it was one day early, which made -Backfill run one extra month (at UTC+2, -Backfill 3 issued 5 export runs covering April through August instead of 4 covering May through August). The three tests that encoded the shifted behavior built their expected values with the same conversion on both sides, so they failed on any machine east of UTC. They now assert literal dates, which is what makes them time zone independent, and a new test covers each DateTimeKind the parameter binder can produce. Fixes #2255 Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Roland Krummenacher (RolandKrummenacher)
requested review from
Brett Wilson (MSBrett) and
Michael Flanakin (flanakin)
as code owners
August 17, 2026 06:59
Copilot started reviewing on behalf of
Roland Krummenacher (RolandKrummenacher)
August 17, 2026 06:59
View session
Contributor
There was a problem hiding this comment.
Pull request overview
This pull request fixes Start-FinOpsCostExport date handling so -StartDate / -EndDate are treated as UTC calendar dates (day-granular) instead of being time zone converted before truncation, which previously shifted requested periods back by one day for positive UTC offsets.
Changes:
- Tag
-StartDate/-EndDateas UTC calendar dates viaSpecifyKind(..., Utc)to avoid offset-driven day shifts. - Update Pester unit tests to be time zone independent and tighten invocation assertions with
-Exactly. - Update Microsoft Learn reference docs and changelog to document the UTC calendar-date semantics and the bug fix.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| src/powershell/Public/Start-FinOpsCostExport.ps1 | Fixes date normalization to preserve requested calendar days across time zones and adjusts backfill defaulting to be UTC-month based. |
| src/powershell/Tests/Unit/Start-FinOpsCostExport.Tests.ps1 | Rewrites assertions to be time zone independent; adds coverage for different DateTimeKind inputs and uses -Exactly for call counts. |
| docs-mslearn/toolkit/powershell/cost/start-finopscostexport.md | Documents -StartDate / -EndDate as UTC calendar dates and updates ms.date. |
| docs-mslearn/toolkit/changelog.md | Adds a changelog entry describing the bug fix and updates ms.date. |
Suppressed comments (1)
src/powershell/Public/Start-FinOpsCostExport.ps1:123
- In the
-Backfilldefaulting logic,$EndDateis derived usingAddMilliseconds(-1)and then immediately truncated to.Datelater (line 135). Since-StartDate/-EndDateare now treated as UTC calendar dates (day-granular), the millisecond subtraction is unnecessary and makes the intent harder to follow. Consider defaulting the end date directly to the last calendar day of the month.
# If -EndDate is not set, assume 1 month
if (-not $EndDate)
{
$EndDate = $StartDate.AddMonths(1).AddMilliseconds(-1)
}
💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.
…ill end date The -Backfill default end date subtracted a millisecond to land on the last day of the window, then had the time truncated away by .Date a few lines later. For a midnight start date -- always the case here, since both -StartDate sources are midnight -- AddMonths(1).AddMilliseconds(-1).Date and AddMonths(1).AddDays(-1) are identical, so this is a no-op. Using AddDays(-1) also makes the two default-end-date paths read the same; the non-backfill branch below already computes it that way. Note this is not the same as 'the last day of the month': with an explicit -StartDate that is not the first (for example -StartDate '2026-03-15' -Backfill 2), the window stays one month from the start date and ends 2026-04-14, not 2026-03-31. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
🛠️ Description
Fixes #2255.
Start-FinOpsCostExportconverted-StartDateand-EndDatewithToUniversalTime()before truncating them to a day. For any positive UTC offset, local midnight falls on the previous UTC day, so.Datemoved the requested period back by one day.The parameters are now treated as UTC calendar dates: the day the caller names is tagged as UTC rather than converted.
This matches how the parameters are documented ("Day to start pulling the data for", "Last day to pull data for") and how Cost Management export periods actually work — they are UTC and day-granular, so there is no instant to convert.
The defect is asymmetric
2026-01-012026-01-012025-12-31❌2026-01-012025-12-31❌2026-01-012025-12-31❌2026-01-012026-01-012026-01-012026-01-012026-01-01At UTC and negative offsets, local midnight converts to a later hour on the same UTC day, so
.Datewas already unchanged. Behavior there is identical before and after this change. CI runs on UTC, which is why this never reproduced there.-Backfillran an extra monthThe default backfill start date used the same pattern, landing one day early — which pushed the computed month count up by one. On a UTC+02:00 machine,
-Backfill 3issued 5 export runs (April through August) instead of 4 (May through August). The default is now derived from the current UTC month, so the count is correct in every time zone.🧪 Testing
Three tests in
Start-FinOpsCostExport.Tests.ps1encoded the shifted behavior — they built their expected values with the sameToUniversalTime()conversion applied to both sides, so they failed on any machine east of UTC:Fixing only the tests would have cemented the product behavior, so the assertions were rewritten to be time zone independent instead:
'2024-01-01T00:00:00Z') rather than expressions that shift in step with the code under test.(Get-Date).ToUniversalTime().Dateso they hold at any offset.-Exactlyto the invocation counts, which is what catches the extra-Backfillmonth.DateTimeKindthe parameter binder can produce (Local,Unspecified, and theUtcpath via the existing tests).Results on a UTC+02:00 machine (PowerShell 7.6.3, Pester 6.1.0):
Tests Passed: 5, Failed: 3Tests Passed: 9, Failed: 0Tests Passed: 2373, Failed: 0, Skipped: 4Invoke-ScriptAnalyzerclean on both changed PowerShell filesBecause the change is a no-op at UTC, the existing CI run is the control: it should stay green.
📝 Notes
-Backfill nwill runn + 1exports instead ofn + 2. It is a no-op for UTC and negative offsets.-EndDatewithout-StartDateleaves$StartDateatDateTime.MinValue, so the month count becomes ~24,000 and the run loop iterates accordingly. It is unrelated to time zones and is not touched here — happy to open a separate issue.✅ Checklist
🤖 Generated with Claude Code