1"use strict";(self.webpackChunkpages=self.webpackChunkpages||[]).push([["6789"],{9309(e,t,s){s.r(t),s.d(t,{metadata:()=>n,default:()=>h,frontMatter:()=>i,contentTitle:()=>o,toc:()=>r,assets:()=>d});var n=JSON.parse('{"id":"Abstractions/time-system/stopwatch","title":"Stopwatch","description":"MockTimeSystem.Stopwatch wraps System.Diagnostics.Stopwatch. It exposes both the instance API (IStopwatch Frequency, IsHighResolution, GetTimestamp, StartNew).","source":"@site/docs/Abstractions/time-system/stopwatch.mdx","sourceDirName":"Abstractions/time-system","slug":"/Abstractions/time-system/stopwatch","permalink":"/Abstractions/time-system/stopwatch","draft":false,"unlisted":false,"editUrl":"https://github.com/Testably/Testably.Site/tree/main/Docs/pages/docs/Abstractions/time-system/stopwatch.mdx","tags":[],"version":"current","sidebarPosition":3,"frontMatter":{"sidebar_position":3,"title":"Stopwatch"},"sidebar":"abstractionsSidebar","previous":{"title":"Auto-advance","permalink":"/Abstractions/time-system/auto-advance"},"next":{"title":"Timer","permalink":"/Abstractions/time-system/timers"}}'),c=s(4848),a=s(8453);let i={sidebar_position:3,title:"Stopwatch"},o="Stopwatch",d={},r=[{value:"Basic usage",id:"basic-usage",level:2},{value:"Wall clock vs monotonic clock",id:"wall-clock-vs-monotonic-clock",level:2},{value:"Frequency, GetTimestamp and GetElapsedTime",id:"frequency-gettimestamp-and-getelapsedtime",level:2},{value:"Pause, resume, restart",id:"pause-resume-restart",level:2}];function l(e){let t={code:"code",h1:"h1",h2:"h2",header:"header",p:"p",pre:"pre",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",...(0,a.R)(),...e.components};return(0,c.jsxs)(c.Fragment,{children:[(0,c.jsx)(t.header,{children:(0,c.jsx)(t.h1,{id:"stopwatch",children:"Stopwatch"})}),"\n",(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:"MockTimeSystem.Stopwatch"})," wraps ",(0,c.jsx)(t.code,{children:"System.Diagnostics.Stopwatch"}),". It exposes both the instance API (",(0,c.jsx)(t.code,{children:"IStopwatch"}),": ",(0,c.jsx)(t.code,{children:"Start"}),", ",(0,c.jsx)(t.code,{children:"Stop"}),", ",(0,c.jsx)(t.code,{children:"Elapsed"}),", ",(0,c.jsx)(t.code,{children:"ElapsedMilliseconds"}),", ",(0,c.jsx)(t.code,{children:"ElapsedTicks"}),", ",(0,c.jsx)(t.code,{children:"Restart"}),", ",(0,c.jsx)(t.code,{children:"Reset"}),", ",(0,c.jsx)(t.code,{children:"IsRunning"}),") and the static API on the factory itself (",(0,c.jsx)(t.code,{children:"IStopwatchFactory"}),": ",(0,c.jsx)(t.code,{children:"Frequency"}),", ",(0,c.jsx)(t.code,{children:"IsHighResolution"}),", ",(0,c.jsx)(t.code,{children:"GetTimestamp"}),", ",(0,c.jsx)(t.code,{children:"StartNew"}),")."]}),"\n",(0,c.jsx)(t.h2,{id:"basic-usage",children:"Basic usage"}),"\n",(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:"language-csharp",children:"ITimeSystem timeSystem = new MockTimeSystem();\nIStopwatch sw = timeSystem.Stopwatch.StartNew();\n\n// Simulate work that takes 5 seconds - Sleep auto-advances the simulated clock\ntimeSystem.Thread.Sleep(TimeSpan.FromSeconds(5));\n\nsw.Stop();\nawait Expect.That(sw.Elapsed).IsEqualTo(TimeSpan.FromSeconds(5));\n"})}),"\n",(0,c.jsx)(t.h2,{id:"wall-clock-vs-monotonic-clock",children:"Wall clock vs monotonic clock"}),"\n",(0,c.jsxs)(t.p,{children:["A real ",(0,c.jsx)(t.code,{children:"Stopwatch"})," reads the OS ",(0,c.jsx)(t.strong,{children:"monotonic clock"})," - it measures elapsed real time, completely independent of the wall-clock date. Setting the system clock backwards mid-measurement doesn't subtract from ",(0,c.jsx)(t.code,{children:"Elapsed"}),"; only actual time passing does."]}),"\n",(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:"MockTimeSystem"})," mirrors that distinction. The two knobs on ",(0,c.jsx)(t.code,{children:"TimeProvider"})," behave differently:"]}),"\n",(0,c.jsxs)(t.table,{children:[(0,c.jsx)(t.thead,{children:(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.th,{children:"Method"}),(0,c.jsx)(t.th,{children:(0,c.jsx)(t.code,{children:"DateTime.UtcNow"})}),(0,c.jsxs)(t.th,{children:["Running ",(0,c.jsx)(t.code,{children:"Stopwatch.Elapsed"})]})]})}),(0,c.jsxs)(t.tbody,{children:[(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:"AdvanceBy(TimeSpan)"})}),(0,c.jsx)(t.td,{children:"Moves forward by the span"}),(0,c.jsx)(t.td,{children:"Moves forward by the span"})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:"SetTo(DateTime)"})}),(0,c.jsx)(t.td,{children:"Jumps to the given instant"}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.strong,{children:"Unchanged"})})]})]})]}),"\n",(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:"language-csharp",children:"MockTimeSystem timeSystem = new(new DateTime(2026, 1, 1, 0, 0, 0, DateTimeKind.Utc));\nIStopwatch sw = timeSystem.Stopwatch.StartNew();\n\n// Jump the wall clock backwards by a year - Stopwatch is unaffected\ntimeSystem.TimeProvider.SetTo(new DateTime(2025, 1, 1));\nawait Expect.That(sw.Elapsed).IsEqualTo(TimeSpan.Zero);\n\n// AdvanceBy moves both the wall clock and the monotonic clock\ntimeSystem.TimeProvider.AdvanceBy(TimeSpan.FromMinutes(5));\nawait Expect.That(sw.Elapsed).IsEqualTo(TimeSpan.FromMinutes(5));\n"})}),"\n",(0,c.jsxs)(t.p,{children:["This means tests that mix ",(0,c.jsx)(t.code,{children:"Stopwatch"})," measurements with manual ",(0,c.jsx)(t.code,{children:"SetTo"}),' calls (e.g. simulating a clock change at midnight, NTP correction, daylight-saving switch) don\'t produce nonsensical elapsed values. The Stopwatch always reflects "real" simulated elapsed time.']}),"\n",(0,c.jsxs)(t.p,{children:["The same monotonic clock backs ",(0,c.jsx)(t.code,{children:"Timer"}),", ",(0,c.jsx)(t.code,{children:"PeriodicTimer"})," and ",(0,c.jsx)(t.code,{children:"Task.Delay"})," - none of them are affected by ",(0,c.jsx)(t.code,{children:"SetTo"})," either."]}),"\n",(0,c.jsx)(t.h2,{id:"frequency-gettimestamp-and-getelapsedtime",children:"Frequency, GetTimestamp and GetElapsedTime"}),"\n",(0,c.jsxs)(t.p,{children:["The static surface lives on the factory (",(0,c.jsx)(t.code,{children:"timeSystem.Stopwatch"}),"), not on individual ",(0,c.jsx)(t.code,{children:"IStopwatch"})," instances:"]}),"\n",(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:"language-csharp",children:"long t1 = timeSystem.Stopwatch.GetTimestamp();\ntimeSystem.Thread.Sleep(TimeSpan.FromSeconds(2));\nlong t2 = timeSystem.Stopwatch.GetTimestamp();\n\ndouble seconds = (t2 - t1) / (double)timeSystem.Stopwatch.Frequency; // 2.0\n"})}),"\n",(0,c.jsxs)(t.p,{children:["On .NET 8+, ",(0,c.jsx)(t.code,{children:"GetElapsedTime"})," does the math for you:"]}),"\n",(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:"language-csharp",children:"long start = timeSystem.Stopwatch.GetTimestamp();\ntimeSystem.Thread.Sleep(TimeSpan.FromSeconds(2));
1\n\nTimeSpan elapsed = timeSystem.Stopwatch.GetElapsedTime(start); // 00:00:02\nTimeSpan span = timeSystem.Stopwatch.GetElapsedTime(t1, t2); // 00:00:02\n"})}),"\n",(0,c.jsxs)(t.p,{children:["On the mock, ",(0,c.jsx)(t.code,{children:"Frequency"})," is fixed at ",(0,c.jsx)(t.code,{children:"TimeSpan.TicksPerSecond * 10"})," = 100,000,000 (100 MHz, i.e. 10 ns per timestamp tick) and ",(0,c.jsx)(t.code,{children:"IsHighResolution"})," is always ",(0,c.jsx)(t.code,{children:"true"}),". The real ",(0,c.jsx)(t.code,{children:"Stopwatch.Frequency"})," varies by hardware and OS - the mock pins it to a stable value so timestamp arithmetic in tests is deterministic."]}),"\n",(0,c.jsx)(t.h2,{id:"pause-resume-restart",children:"Pause, resume, restart"}),"\n",(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:"Start"}),"/",(0,c.jsx)(t.code,{children:"Stop"}),"/",(0,c.jsx)(t.code,{children:"Reset"}),"/",(0,c.jsx)(t.code,{children:"Restart"})," behave exactly as on the BCL:"]}),"\n",(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:"language-csharp",children:"IStopwatch sw = timeSystem.Stopwatch.StartNew();\n\ntimeSystem.Thread.Sleep(TimeSpan.FromSeconds(3));\nsw.Stop(); // Elapsed = 3s\ntimeSystem.Thread.Sleep(TimeSpan.FromSeconds(10)); // paused, ignored\nsw.Start(); // resume\ntimeSystem.Thread.Sleep(TimeSpan.FromSeconds(2));\nawait Expect.That(sw.Elapsed).IsEqualTo(TimeSpan.FromSeconds(5));\n\nsw.Restart(); // Elapsed = 0, running\nsw.Reset(); // Elapsed = 0, stopped\n"})}),"\n",(0,c.jsxs)(t.p,{children:["A second ",(0,c.jsx)(t.code,{children:"Start()"})," on an already-running stopwatch is a no-op, matching the BCL."]})]})}function h(e={}){let{wrapper:t}={...(0,a.R)(),...e.components};return t?(0,c.jsx)(t,{...e,children:(0,c.jsx)(l,{...e})}):l(e)}},8453(e,t,s){s.d(t,{R:()=>i,x:()=>o});var n=s(6540);let c={},a=n.createContext(c);function i(e){let t=n.useContext(a);return n.useMemo(function(){return"function"==typeof e?e(t):{...t,...e}},[t,e])}function o(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(c):e.components||c:i(e.components),n.createElement(a.Provider,{value:t},e.children)}}}]);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.