Repository navigation
Lite shows server times right across a daylight saving change, in the desktop and in MCP tools (#4766, #4793) - #4841
Merged
Conversation
…ht saving change (#4766) ServerTimeHelper used one offset for every time, so a time on the far side of a clock change showed an hour off the server's wall clock. It now converts through a ServerClock (the zone id where the engine reported one, else the fixed offset): Server mode, the picker's inverse and the UTC and Local displays all go through it, a skipped local time moves forward by the gap and a repeated one takes its first occurrence. The int-offset overloads stay and delegate to a fixed-offset clock. Each server tab reads its clock again on every refresh (time_zone_id with utc_offset_minutes from the same newest server_properties row); a failed read or nothing collected yet keeps the clock in use. The tab's chart X values and axis ranges convert each time through the clock instead of adding one offset to all of them, and selecting a tab installs that tab's clock.
…ow through the server clock (#4766) The Default Trace read subtracted the newest utc offset from every server-local event_time, so a row on the far side of a daylight saving change came back an hour off in UTC. It now resolves the window to exact UTC bounds through the server's clock, uses the server-local bounds only as a SQL pre-filter widened by one hour on each side, converts each row through the clock, and holds the converted rows to the exact window. The MCP tool passes the server's own clock. Job History and Agent status render the stored server wall clock with no offset applied, so they are unchanged.
…job, index usage and version store times (fails until the fix in the next commit) (#4793)
…n store times with the server's clock instead of one offset (#4793)
#4766) ServerTimeHelper gained ActiveServerClock and ServerTimeToUtc, and UtcOffsetMinutes now reads and writes that same clock, but the guard's member pattern did not name the two new members. A test class that used only those could have run beside a writer without the server-time collection. The pattern now names both, and the summary names the shared settings as they are now: the server clock and CurrentDisplayMode. A new test fails when a public static member of ServerTimeHelper is missing from the pattern, so the next member added cannot slip past the guard the same way. No existing test file is newly flagged.
…server clock (#4766, #4793) Lite's blocking, running job, index usage and version store MCP tools now convert each stamp with the server's ServerClock (McpServerLocalWindow. ClockForAsync and serverClock.ToUtc, through a file-local UtcOrNull for the nullable stamps), and the Default Trace read converts each row with the clock. Five source pins in Darling.Tests still looked for the old shape (OffsetForAsync and AddMinutes(-utcOffsetMinutes)) and failed: - McpPayloadClockFrameDisciplineTests: the Lite tool sites must resolve the clock, must not subtract one offset, and each affected stamp must go through the clock; the UtcOrNull body is pinned too, so a call site alone proves nothing. The matcher's own cases now accept the three shipped forms and reject the bare stamp and the single subtracted offset. - ConsumedTimestampFrameDisciplineTests: the payload-site scan counts a UtcOrNull call as the stamp, ToUtc and UtcOrNull are declared as conversions rather than renderers, and the twelve Lite conversion strings name the new forms. - ServerLocalReadFrameDisciplineTests: Lite's Default Trace read is converted per row with the clock, not with one subtracted offset.
…er-clock # Conflicts: # Darling/Darling.Tests/McpPayloadClockFrameDisciplineTests.cs
erikdarlingdata
marked this pull request as ready for review
September 29, 2026 17:22
…change (fails until the fix in the next commit) (#4766) GetTimeRange and GetTimeRangeServerLocal shift every bound by one offset, so a custom range that spans a clock change is an hour off at the far bound and an hours-back server-local window across a change is an hour long or short. These tests pin the per-bound behaviour with US Eastern dates on both sides of both changes, plus one read (the alert badge counts) end to end through the DuckDB fixture. Compile-only overloads that still use the one-offset shape stand in for the clock overloads so the tests build; six of the eleven fail on them.
…in force at each bound, so a range across a daylight saving change is right at both ends (#4766) GetTimeRange, GetTimeRangeServerLocal and GetQueriesTabWindowUtc take the server's clock instead of one offset. A custom range converts each bound on its own, and an hours-back server-local window is the server-local rendering of its two UTC ends. The six reads that took an offset take a clock in the same position (alert counts, CPU utilization, top queries, top procedures, CPU buckets; System Events keeps its optional clock and loses the int). The 82 reads that used the selected tab's offset use its clock, and the calls that passed zero pass ServerClock.Utc. The offset that still feeds SQL over many rows is taken at the window end (CPU pre-v63 fallback, CPU buckets); the last_execution_time floor in the top-queries and top-procedures reads converts one bound, so it uses the offset at the window start.
… server's clock across a daylight saving change (#4766)
…for the last-run floor on the server's clock (fails until the fix in the next commit) (#4793)
…st-run floor uses the server's clock, not UTC (#4793)
…odes, and the docs describe the server's clock (#4766) A round-trip test drives the Queries tab pickers' conversion and the window read together, in every display mode and in ranges inside each season and across each clock change, so a window that applies one offset to both bounds fails whatever the season the test runs in. The System Events, window-pairing, slicer-range and axis-label comments now describe the conversion through the server's clock by the date of each bound.
…ting through the server's clock, and the one-offset scan over every control and window (fail until the fix in the next commit) (#4766)
…the server's clock, so a sample from before a daylight saving change is not an hour off the axis (#4766)
…s across a clock change in the display zone (#4766)
…ion its census requires (#4766)
…ce tool and ServerTimeHelper name the server's clock, not one offset (#4766)
…lock's doc describe the server's clock, not one offset (#4766)
…ross a daylight saving change, so the first hour is no longer cropped (#4766)
erikdarlingdata
marked this pull request as draft
September 29, 2026 19:46
erikdarlingdata
marked this pull request as ready for review
September 29, 2026 19:53
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.
Part of #4766 (Lite's half, plus one axis fix in the Darling viewer; the rest of the viewer's half was #4783). Fixes #4793 (Lite's MCP tools; Darling's were #4829).
Why
In Server time mode, the default, Lite converted every time with one UTC offset. For a server in a zone with daylight saving, chart times and the time-range picker on the far side of a clock change showed an hour off the server's wall clock. Lite now follows the same rule as the Darling viewer (
ServerClock): the zone id when the server reported one (SQL Server 2022 and later), else the fixed offset, else UTC.ServerClock.ToUtcnever throws: a skipped local time moves forward by the gap and a repeated one takes its first occurrence.The display and its partners have to change together. The UTC and Local displays convert a server-local value back through the clock, and a picked range goes into a read that turns it back into UTC. A partner left on one offset stops cancelling, and shows an hour off where it was exact before. So this change moves every partner: the chart X values, the pickers, the windowed reads, the Overview lanes and the three History windows. The windowed reads also had the flaw on their own.
GetTimeRangeturned a custom range's server-local bounds into UTC with one offset, andGetTimeRangeServerLocalbuilt "now" in server time with the same offset. A custom range across a clock change was an hour off at its far bound, and an hours-back window across a change was an hour long or short.Four Lite MCP tools return a server-local stamp converted to UTC, and each converted every stamp with the one offset the server last reported, so a stamp from before a daylight saving change came back an hour off. A US Eastern server whose newest offset is the daylight one (-240) returned 05:30 UTC for a stamp taken at 01:30 EST on 8 March 2026, where 06:30 UTC is right. A stamp after the change was already right, which is why it went unnoticed. The four tools and the stamps they convert:
get_blocked_process_reports: the six blocked_/blocking_ transaction, batch-start and batch-completed stamps.get_running_jobs:start_time.get_index_usage:last_user_access.get_pvs_stats: the four cleaner times.Two more,
get_top_queries_by_cpuandget_top_procedures_by_cpu, passed no clock, so theirlast_execution_timefloor used a zero offset on every server.What changes
#4766: Server time mode and System Events
Lite/Services/ServerTimeHelper.csholds aServerClock(ActiveServerClock).ToServerTime,ConvertForDisplay,DisplayTimeToServerTime,FormatServerTimeand the timezone label go through it. New clock overloads (ConvertForDisplay(.., ServerClock),DisplayTimeToServerTime(.., ServerClock),ToServerTime(.., ServerClock)) plusServerTimeToUtc; theintoffset overloads stay and delegate to a fixed-offset clock.UtcOffsetMinutesstill exists: the getter is the offset in force now, the setter installs a fixed-offset clock.LocalDataService.GetServerClockAsyncreadstime_zone_idwithutc_offset_minutesfrom the same newestserver_propertiesrow (skipping NULL offsets, likeGetServerUtcOffsetMinutesAsync). It returns null when nothing is collected yet.ServerTabholds a clock instead of a constructor-time offset.RefreshAllDataAsynccallsRefreshServerClockAsyncfirst on every refresh; a failed read or a null result keeps the clock in use, and a visible tab also installs it as the active one. Selecting a tab installs that tab's clock (MainWindow).x.AddMinutes(UtcOffsetMinutes)chart X value, axis range and slicer conversions becameToServerLocal(x)/ToUtcFromServerLocal(x)(clock-based).GetCurrentWindowtakes a clock; the picker value converts through it.GetDefaultTraceEventsAsyncresolves the window to exact UTC bounds through the clock, uses the server-local bounds only as a SQL pre-filter widened by one hour on each side, converts each row'sevent_timethrough the clock, and holds the converted rows to the exact window (inclusive at both ends, as before). It takes the clock only; theintoffset parameter is gone.get_default_trace_events(MCP) passes the server's own clock (McpServerLocalWindow.ClockForAsync).ServerClock.Resolve(time_zone_id, utc_offset_minutes), the sharedServerClockclass Darling uses.time_zone_idis collected only where SQL Server reports one: the collector readsCURRENT_TIMEZONE_ID()in its own batch inside TRY/CATCH, gated on SQL Server 2022 and later (or Azure SQL Database and Managed Instance), and writes NULL elsewhere. A NULL or unresolvable id falls back to the fixed offset, so an older server keeps the fixed offset.#4766: the windowed reads
GetTimeRange,GetTimeRangeServerLocalandGetQueriesTabWindowUtctake aServerClockin place ofint utcOffsetMinutes. The first two are nowinternalso the tests can call them.serverClock.ToUtc, so a winter start and a summer end use -300 and -240 (US Eastern).GetTimeRangeServerLocalrenders both UTC ends on the clock: end isToServerLocal(anchor), start isToServerLocal(anchor - hoursBack). Its custom range stays as given.SelectedServerTabUtcOffsetMinutesis nowSelectedServerTabServerClock => ServerTimeHelper.ActiveServerClock. Its doc comment keeps the pairing rule withServerTab.GetCurrentWindow: the picker's conversion and the window's must name the same server's clock, or they stop cancelling.utcOffsetMinutes: 0and now passServerClock.Utc, 7 were the Queries tab window calls inServerTab.Comparison.csandServerTab.Refresh.csand now passServerTimeHelper.ActiveServerClock, and the rest are the definitions and the reads below that pass their own parameter.ServerClock serverClockin the same position (nine signatures changed with the three window methods):GetAlertCountsAsync(required),GetCpuBucketsAsync(required).GetCpuUtilizationAsync,GetTopQueriesByCpuAsync,GetTopProceduresByCpuAsync: optional. Null means the selected-tab clock for CPU utilization (as before) and UTC for the two top-N reads (the old zero default).GetDefaultTraceEventsAsync, above.ServerClock(they passed the tab's own offset); the MCPget_cpu_utilizationtool resolvesMcpServerLocalWindow.ClockForAsync.GetCpuUtilizationAsyncandGetCpuBucketsAsynctake their UTC bounds fromGetTimeRangeon one anchor instead of converting the server-local bounds back with one offset. A round trip through server-local time would put an anchor inside a repeated hour an hour early. The CPU read's bucket width now comes from the window's real length, not its wall-clock span, which a change moves by an hour.sample_time_utc.GetCpuUtilizationAsyncandGetCpuBucketsAsynccompare such a row's server-localsample_timewith the window's server-local bounds fromGetTimeRangeServerLocal, and every other row'ssample_time_utcwith the UTC bounds. The predicate no longer takes an offset, so no row is shifted by one offset for the whole window.ServerTimeHelper.ToServerTime(DateTime, int)andDisplayTimeToServerTime(DateTime, TimeDisplayMode, int)had only test callers. They are deleted with their two tests, andAlertBadgeServerOffsetTestsbuilds its clock withServerClock.FixedOffsetinstead.ComparisonWindowUtcTests(the source pin now expectsActiveServerClock), and the callers inAlertBadgeServerOffsetTests,QueryWindowTruncationTests,OverviewLaneBucketingTests,TimeHonestyRungTestsandDefaultTraceServerClockTests.#4766: the Overview lanes and the History windows
CorrelatedTimelineLanesControl):GetCurrentWindowServerLocal,GetBaselineReferenceTimeUtc,GetOverviewComparisonRangeandRefreshAsynctake aServerClockin place ofint utcOffsetMinutes.ToServerLocal(utcNow)back toToServerLocal(utcNow - hoursBack), so it ishoursBackreal hours long across a change (the same rule asGetTimeRangeServerLocal). With onlytoDategiven, the start isToServerLocal(ToUtc(toDate) - hoursBack).serverClock.ToUtc(fromDate), so afromDateon the far side of a change converts with its own offset.ServerTab.RefreshOverviewAsyncpassesServerTimeHelper.ActiveServerClock(it passedServerTimeHelper.UtcOffsetMinutes).utc.AddMinutes(ServerTimeHelper.UtcOffsetMinutes), and the three History windows (ProcedureHistoryWindow,QueryStatsHistoryWindow,QueryStoreHistoryWindow) did the same for theirCollectionTimeX values and their "samples from ... to ..." summary line. That is today's offset, but the axis and crosshair convert back through the clock, so a US Eastern sample at 15:00 UTC on 1 March viewed in September would have been labelled 16:00 UTC. All of them now useServerTimeHelper.ToServerTime(...).SyncXAxestakes its window from a newGetXAxisWindow, which reusesGetCurrentWindowServerLocalfor a preset range, so the axis ishoursBackreal hours and does not cut off the first hour when a change falls inside it.ServerTab_ConvertsThroughTheClock_AndRereadsItOnEveryRefreshscanned onlyLite/Controls/ServerTab*.cs, and its regex missedServices.ServerTimeHelper.UtcOffsetMinutesand autcOffsetlocal. It now scans every file inLite/ControlsandLite/Windowswith\.AddMinutes\(-?\s*((Services\.)?ServerTimeHelper\.)?[uU]tcOffset(Minutes)?\). A hit has to be listed inOneOffsetExceptionswith its reason, and a listed file that no longer hits fails too. The list is empty.ChartTimeConversionClockTestsadds pins for the lanes and the three windows, and a pure round trip: a plotted X reads back as the sample's own instant in UTC and Local display, for samples on both sides of both changes.#4766: the ServerTab charts and drills
ServerTab.GetChartWindow. They are inServerTab.Charts.cs,BlockingStats,CpuScheduler,LatchSpinlock,Pickers,PlanCache,SessionStatsandSystemHealthCharts. It returns the Overview lanes'GetCurrentWindowServerLocal, so the charts and the lanes share one window ofhoursBackreal hours. Each row plots at the server's clock on its own date. An axis start on the wall clock (the server-local end minushoursBack) cuts off the first real hour when the window spans the spring change. The CPU chart plots its stored server-local stamps, and its read covers the same real hours, so the same window fits it.ServerTab.GetDrillWindowtakes that instant in UTC: the clicked server-local time through the clock, or the heatmap bucket's ownbucketTimeUtc. It renders 30 minutes either side (the heatmap: 5 before, 10 after) on the server's clock. Adding the minutes to the wall clock put one end of a drill just after the spring change inside the skipped hour. That end converts to the same instant as the other end, so the window came back empty. The heatmap's log line shows the offset in force at the bucket (OffsetMinutesAtBucket) instead of today's.#4793: Lite's MCP tools
serverClock = await McpServerLocalWindow.ClockForAsync(dataService, resolved.ServerId)and converts each stamp withserverClock.ToUtc(...). No read passes the offset into a query; the conversion is in the output only, as before.ToUtcnever throws on a saved local time that was skipped (02:30 on the spring-forward day reads as 03:30 EDT) or repeated (01:30 on the fall-back day reads as its first occurrence, the daylight one)."o"strings with noZ, as before.get_top_queries_by_cpuandget_top_procedures_by_cpuresolveMcpServerLocalWindow.ClockForAsyncand pass it, so thelast_execution_timefloor is the window start on the server's clock.McpServerLocalWindow.OffsetForAsyncis removed. Its last caller was the CPU tool, and its rationale moved ontoClockForAsync.Darling viewer: the Overview lanes time axis
The viewer's lanes window (
startUtc/endUtc) and baselinereferenceTimeare naive UTC and add no offset, and every plotted sample goes throughViewerTimeHelper.ForDisplay, so those needed no change. ButSyncXAxesbuilt a preset axis start by subtractinghoursBackfrom the display end as wall clock. When a clock change in the display time zone fell inside the window, the axis started an hour off, and after a spring change it cut the first hour of samples off the chart. It now takes the window from a newGetXAxisWindow: the display time ofutcNow - hoursBackto the display time ofutcNow.CorrelatedLanesXAxisWindowTestscovers it.The server-time collection guard
ServerTimeHelperCollectionTestslists the members that read or write the settings shared by the whole test process, so a test class that uses them must carry the server-time collection. It did not knowActiveServerClockorServerTimeToUtc, so a class using only those could have run beside a writer.UtcOffsetMinutesnow reads and writes) andCurrentDisplayMode.ServerTimeHelperis missing from the pattern, so the next member added cannot slip past the guard the same way.QueriesTabWindowRoundTripTestsuses those members and carries the collection.Darling.Tests source pins that read Lite's sources
Five Darling.Tests source pins still looked for the old shapes (
OffsetForAsync,AddMinutes(-utcOffsetMinutes),GetDateTime(0).AddMinutes(-offset)) and failed on this branch. Updated, not weakened:McpPayloadClockFrameDisciplineTests: the Lite tool sites must resolve the clock, must not subtract one offset, and each affected stamp must go through the clock. TheUtcOrNullbody is pinned too, so a call site alone proves nothing. The matcher's own cases accept the three shipped forms and reject the bare stamp and the single subtracted offset.ConsumedTimestampFrameDisciplineTests(three tests): the payload-site scan counts aUtcOrNullcall as the stamp,ToUtcandUtcOrNullare declared as conversions rather than renderers, and the twelve Lite conversion strings name the new forms.ServerLocalReadFrameDisciplineTests: Lite's Default Trace read is converted per row with the clock.Doc comments
These now describe the server's clock where they described one offset:
LocalDataService.csand the window notes inServerTab.TimeRange.cs,ServerTab.Refresh.csandServerTab.Charts.cs.LocalDataService.SystemEvents.cs: the read resolves the window to exact UTC through the clock, uses the local bounds widened by an hour only as a SQL pre-filter, converts each row'sevent_timeat the row's own date, and keeps only the rows inside the exact window.PerformanceMonitor.Ui/AxesExtensions.cs: Lite's converter follows the date, so converted values can decrease across the spring gap (ToUtc(02:30)is 07:30Z andToUtc(03:00)is 07:00Z). The pass-reset comparison counts an equal or lower value as "did not increase".DuckDbInitializer.cssays the same.ServerTimeHelper:FormatServerClock(Darling's twin also converts through the server's clock).McpDefaultTraceTools.cs.ServerTab.BlockingStats.csandServerTab.Charts.csname the server's clock where they namedUtcOffsetMinutes. TheStatsChartRangesummary says server-local where it said display-local. A drill comment that said 15 minutes above 30-minute code now says 30.Reads checked for other Lite offset uses
GetJobHistoryAsync): windowsrun_datetime, the server's wall clock, against the desktop's local now (DateTime.Now). Its comment gives that as the design for a desktop in the server's time zone. No offset is applied in SQL or C#. Unchanged.agent_status.next_scheduled_run): rendered as saved, no offset. Unchanged.FindingStore.GetPriorOccurrencesSqlsubtracts the newest offset and documents that as its approximation. Unchanged.What is left
The repeated autumn hour, a known limit. When the clocks go back, one hour of server time happens twice (01:00 to 02:00 in US zones).
ServerClock.ToUtcresolves a time in that hour to its first occurrence. On dev, UTC and Local display were exact in that hour, because the one offset went out and came back and cancelled. A drill there always opened 60 minutes. Here, two things are wrong in that hour, and Server-time mode uses one UTC offset for the whole window, so times across a DST change are an hour off #4766 stays open for both:The fix is a frame per display mode, as the Darling viewer has, and it includes the bounds sent to the reads. It is due before the first autumn change, on October 25.
GetTopQueriesByCpuAsyncandGetTopProceduresByCpuAsyncalso pass one offset ($5inlast_execution_time >= $2 + $5 * INTERVAL '1' MINUTE). It shifts a single bound, so it is exact with the offset at the window start, which is what it gets. Listed only because it looks like the one-offset fallback the CPU reads had.The Recommendations tab's "Ask AI" prompt shows a finding's window in server time with one offset (
LiteRecommendationsViewModel~160-162, fedServerTimeHelper.UtcOffsetMinutesbyRecommendationsTab~177 and ~269), as dev does. It is not changed here, and Server-time mode uses one UTC offset for the whole window, so times across a DST change are an hour off #4766 stays open for it.Test plan
Lite.TestsandDarling.Testsbuild with 0 warnings, 0 errors, after merging origin/dev.ServerTimeHelper.cswere made to apply one shift (clock.OffsetMinutesAt(DateTime.UtcNow)), run, and put back (git diffclean afterwards).ServerTimeHelperClockTests: 3 of 10 fail. These areServerMode_ShowsTheServersWallClock_OnEachSideOfSpringForward(expected 2026-03-08T01:30, got 02:30),ServerMode_ShowsTheServersWallClock_OnEachSideOfFallBack(expected 2026-11-01T01:30, got 02:30) andPickerInverse_SkippedLocalTime_MovesForwardByTheGap_AndDoesNotThrow(expected 2026-03-08T07:30, got 06:30).PickerInverse_RepeatedLocalTime_TakesTheFirstOccurrence_AndDoesNotThrowpasses in September, because September's offset is the first occurrence's; it would fail in winter. With the Default Trace per-row conversion made one offset the same way,DefaultTraceServerClockTests: 4 of 5 fail. These areDefaultTrace_RowsOnBothSidesOfSpringForward_ComeBackWithTheRightUtcTimes(expected 06:30, got 05:30),DefaultTrace_RowsAcrossFallBack_ComeBackWithTheRightUtcTimes(expected 07:30, got 06:30),DefaultTrace_StoredSkippedAndRepeatedLocalTimes_DoNotThrow(expected 07:30, got 06:30) andDefaultTrace_TheExactUtcWindowHolds_InsideThePreFilterMargin(expected["at-the-end","inside"], got["at-the-end"]).DefaultTrace_FixedOffsetServer_ReadsTheSameRowsAsBeforeandUtcDisplay_RoundTripsTheServerClock_OnBothSidesOfTheChangepass by design. When the tests were first written, the old code did not compile against them (36 errors), which is a compile failure, not a behavioral one.WindowedReadServerClockTestsTotal: 11, Failed: 6. The six that fail are the winter-to-summer range, the summer-to-winter range, the winter-only range, the two hours-back windows just after the spring and fall changes, and the end-to-end alert count (3 rows kept, expected 2). The five that pass are the summer-only range,ServerClock.UtcandFixedOffset(-240), the preset range, and the custom range that stays as given. After the fix: 11 of 11.GetTimeRange(clock.OffsetMinutesAt(DateTime.UtcNow)for both bounds), run, and put back.QueriesTabWindowRoundTripTests.UtcPickers_ComeBackAsTheSameUtcRange_ThroughTheWindow: 6 of 7 rows fail, for example(from: "2026-03-01 09:00", to: "2026-03-02 09:00"), expected2026-03-01 09:00 to 2026-03-02 09:00, actual2026-03-01 08:00 to 2026-03-02 08:00. The summer-only row passes in September and would fail in winter; the rows across a change fail in any season. The class also checks that every display mode reads the instant the picker shows (EveryDisplayMode_ReadsTheInstantThePickerShows).ChartTimeConversionClockTestsplusServerTimeHelperClockTestsTotal: 20, Failed: 5. These are the lanes pin, the three History window pins, and the scan ofLite/ControlsandLite/Windows(it namesCorrelatedTimelineLanesControl.xaml.cs). The two pure round-trip tests pass on both shapes, as they test the conversions the chart composes. After the fix (commit 1c08b0d) withOverviewComparisonWindowOffsetTests:Total: 42, Failed: 0.OverviewComparisonWindowOffsetTests(every old case moved to the clock, plus US Eastern cases: a preset window across the spring and the fall change, atoDate-only window, a customfromDateon each side of the spring change, a fixed-offset clock, andGetXAxisWindow):Total: 22, Failed: 0.Lite.Tests Total: 6, Errors: 0, Failed: 5, Skipped: 0, Not Run: 0. The five failures are the blocking, running job (both), index usage and PVS tests, each with a pre-change stamp an hour off (Expected: 2026-03-08T06:30:00,Actual: 2026-03-08T05:30:00). The sixth test (fixed-offset and no-offset servers) passes on the old code by design.LiteMcpServerClockTestscalls each tool end to end and reads the JSON it returns, on a US Eastern server (Eastern Standard Time) whose newest collected offset is the post-change one (-240), with one row before the 2026-03-08 change and one after; a row with every nullable stamp NULL asserts each stays null.LiteMcpServerClockTestsTotal: 10, Failed: 4. On the US Eastern server the query and the procedure last run 1 hour into a 4-hour window are missing, and on the UTC+5:30 server the row from before the window is returned. After the fix (commit ddf3b71): 10 of 10.hoursBack) was planted inGetXAxisWindow, run, and put back.CorrelatedLanesXAxisWindowTestsTotal: 4, Failed: 1:PresetRange_ServerTime_AcrossSpringForward_StartsHoursBackRealHoursBeforeNow, expected2026-03-07T22:00, actual2026-03-07T23:00. The other three (PresetRange_UtcDisplay_IsHoursBackOfUtc,CustomRange_ConvertsEachBoundWithItsOwnOffset,OneBoundOnly_FallsBackToThePresetWindow) pass on both shapes.ActiveServerClockandServerTimeToUtctaken back out of the pattern, the new test fails namingServerTimeToUtc, ActiveServerClock. With the pattern restored, a scratch test class that used onlyServerTimeHelper.ActiveServerClockandServerTimeHelper.ServerTimeToUtcand had no collection made the scan fail naming that file (the scratch class was deleted).ServerTimeHelperCollectionTests: 3 of 3 pass.McpPayloadClockFrameDisciplineTests.EveryLiteToolSite_ResolvesThisServersOffset_AndDeSkewsEveryAffectedField,ServerLocalReadFrameDisciplineTests.EveryKnownReader_CarriesItsDeSkew_AndNoBareEventTime, and three inConsumedTimestampFrameDisciplineTests). After the update the three classes pass: 31 of 31. The other seven Darling classes that read Lite or shared sources pass too (CollectorTimestampFrameTests,McpPayloadContractCensusTests,PerfmonCounterTypeRungTests,ProductiveZeroBandingTests,TsqlConventionGuardTests,ViewerPerfmonShapingParityTests,ViewerTimeHelperTests).ServerTimeHelperClockTests,DefaultTraceServerClockTests,WindowedReadServerClockTests, every class that namesGetQueriesTabWindowUtcorAxesExtensions); Darling 112 passed (the three discipline classes andDocCommentHygieneTests). On 9746d79, before the last merge:CorrelatedLanesXAxisWindowTestswith the three discipline classes,DocCommentHygieneTests,LaneAxisAlignerTestsandViewerTimeHelperTests,Total: 142, Failed: 0.ServerTabChartWindowTests(6 tests) cover four cases. A US Eastern 24-hour preset window across the spring change leaves no row left of the axis and starts at the first row. Across the autumn change it leaves no empty hour at the left. A UTC server, and a daylight saving server with no change in the window, get exactly 24 hours. A custom range passes through as given. A source pin fails if anyServerTab*.csfile takeshoursBackoff anything butDateTime.UtcNow. RED, each planted and put back: with the old wall-clock start inGetChartWindow,ASpringChangeInsideThePresetWindow_LeavesNoRowLeftOfTheAxisfails ("4 of 97 rows plot outside the axis"). With one site put back,NoServerTabFile_TakesHoursBackOffAnEndThatIsNotUtcNowfails, namingServerTab.CpuScheduler.cs.ServerTabDrillWindowTests(12 tests): a click at 03:10 just after the spring change keeps 60 real minutes (01:40 to 03:40). An ordinary click gets 30 minutes either side. At the spring change the heatmap keeps 5 minutes before and 10 after (07:02Z and 07:05Z). A source pin requires every drill site to build its window throughGetDrillWindow, and allowsAddMinutes(nowhere else in the file. RED: with wall-clock arithmetic inGetDrillWindow,AClickJustAfterTheSpringForward_KeepsSixtyRealMinutesfails (expected 01:40, actual 02:40). WithOnDeadlockDrillDownput back,EveryDrillSite_BuildsItsWindowThroughGetDrillWindowandTheDrillFile_AddsMinutesOnlyInsideGetDrillWindowfail.sample_time_utc. A custom range across the spring change keeps them from its first instant to its last.GetCpuBucketsAsynckeeps them on both sides of the change. A row with a UTC stamp outside the range stays out, which covers theIS NULLguard. RED, with the old predicate restored:TheCpuWindow_CustomRangeAcrossASpringForward_KeepsPreRungRowsFromTheFirstInstantToTheLastfails (expected 189, actual 185), andTheCpuBuckets_WindowAcrossASpringForward_KeepPreRungRowsOnBothSidesOfTheChangefails (expected 25, actual 21).APlottedX_InTheRepeatedAutumnHour_ReadsBackAsTheFirstOccurrence_KnownLimitinChartTimeConversionClockTests,ARangeStartingInTheRepeatedAutumnHour_StartsAtTheFirstOccurrence_KnownLimitinQueriesTabWindowRoundTripTests,AClickInTheRepeatedAutumnHour_ResolvesAroundTheFirstOccurrence_KnownLimitinServerTabDrillWindowTests(it also pins the empty window), and a 06:30Z case inServerTimeHelperClockTests.Lite.Testssuite, once, on 8ec6d3b, which has every code change here (the one commit after it edits a comment):Total: 5910, Failed: 0. An earlier run under full-suite load had one failure inDuckDbSentinelConnectionTests.RunMemoryTrimCycle_OverThreshold_IncrementsCompletedCycleCount, a test this change does not touch. It passed alone (14 of 14) and in the final run.Darling.Testssuite, once, withoutDARLING_TEST_PG, before the ServerTab chart, drill and CPU fallback changes:Total: 18161, Failed: 0, Skipped: 1169. The skips are the live PostgreSQL classes. Those changes touch no Darling code (the sharedAxesExtensions.cschanged a comment only).Darling.Testsbuilds with 0 warnings on them, andDocCommentHygieneTestspasses, 77 of 77.CHANGELOG
SECTION: Fixed
ENTRY:
get_default_trace_eventsshow the right time across a daylight saving change ([Lite shows server times right across a daylight saving change, in the desktop and in MCP tools (#4766, #4793) #4841]): each event's server-local time was converted to UTC with one offset. An event on the far side of a clock change came back an hour off. Each event is now converted with the server's clock and held to the exact time window. This applies where SQL Server reports its time zone (SQL Server 2022 and later). Older servers keep the fixed offset.get_blocked_process_reports,get_running_jobs,get_index_usageandget_pvs_statsconverted every server-local time with one offset. Times from before a clock change were an hour off. Each time is now converted with the server's clock. This applies where SQL Server reports its time zone (SQL Server 2022 and later). Older servers keep the fixed offset.get_top_queries_by_cpuandget_top_procedures_by_cpucompared last run times with a UTC window start. Those times are on the server's own clock. On a server west of UTC, a query last run early in the window was dropped. On a server east of UTC, one from before the window was kept. They now compare on the server's own clock.REF:
[Lite shows server times right across a daylight saving change, in the desktop and in MCP tools (#4766, #4793) #4841]: Lite shows server times right across a daylight saving change, in the desktop and in MCP tools (#4766, #4793) #4841