diff --git a/Startwatch.Library/ExtensionMethods.cs b/Startwatch.Library/ExtensionMethods.cs
deleted file mode 100644
index 82502f6..0000000
--- a/Startwatch.Library/ExtensionMethods.cs
+++ /dev/null
@@ -1,51 +0,0 @@
-using System;
-
-namespace Startwatch.Library;
-
-public static class ExtensionMethods
-{
- ///
- /// Get a short, friendly, human-readable string representation of a TimeSpan.
- ///
- public static string ElapsedFriendly(this TimeSpan timeSpan)
- {
- if (timeSpan.TotalMilliseconds < 1000)
- {
- return timeSpan switch
- {
- { TotalMilliseconds: <1 } => $"{timeSpan.TotalNanoseconds:#,##0}ns",
- _ => $"{timeSpan.TotalMilliseconds:#,##0}ms"
- };
- }
-
- int days = timeSpan.Days;
- int hours = timeSpan.Hours;
- int mins = timeSpan.Minutes;
- int secs = timeSpan.Seconds;
-
- var prependText = secs == 0 ? "exactly " : string.Empty;
-
- var hoursText = (days, hours) switch
- {
- (>0, _) or (_, >0) => $"{hours + (days * 24):#,##0}h",
- _ => string.Empty
- };
-
- var minsText = (hours, mins) switch
- {
- (>0, >0) => $"{mins:00}m",
- (_, >0) => $"{mins:0}m",
- _ => string.Empty
- };
-
- var secsText = (hours, mins, secs) switch
- {
- (0, 0, >0) => $"{timeSpan:s\\.ff}s",
- (>0, _, >0) or (_, >0, >0) => $"{timeSpan:ss}s",
- (_, _, >0) => $"{timeSpan:s}s",
- _ => string.Empty
- };
-
- return $"{prependText}{hoursText}{minsText}{secsText}";
- }
-}
diff --git a/Startwatch.Library/Library.fs b/Startwatch.Library/Library.fs
new file mode 100644
index 0000000..30125e5
--- /dev/null
+++ b/Startwatch.Library/Library.fs
@@ -0,0 +1,65 @@
+namespace Startwatch.Library
+
+open System
+open System.Diagnostics
+
+module Logic =
+ let format (timespan: TimeSpan) =
+ match timespan with
+ | t when t.Ticks < 0 ->
+ raise <| NotSupportedException("Negative TimeSpans are currently not supported.")
+ | t when t.Ticks = 0 ->
+ "no time"
+ | t when t.TotalMilliseconds < 0.01 ->
+ sprintf "%s (%s)"
+ (t.TotalMilliseconds.ToString("#,##0.#####ms"))
+ (t.TotalNanoseconds.ToString("#,##0ns"))
+ | t when t.TotalMilliseconds < 1 ->
+ sprintf "%s" (t.TotalMilliseconds.ToString("#,##0.#####ms"))
+ | t when t.TotalMilliseconds < 1000 ->
+ sprintf "%s" (t.TotalMilliseconds.ToString("#,##0ms"))
+ | t ->
+ let days = t.Days
+ let hours = t.Hours
+ let mins = t.Minutes
+ let secs = t.Seconds
+
+ let prependText = if secs = 0 then "exactly " else String.Empty
+
+ let hourText =
+ match (days, hours) with
+ | d, _ when d > 0 -> sprintf "%s" ((hours + (days * 24)).ToString("#,##0h"))
+ | _, h when h > 0 -> sprintf "%s" ((hours + (days * 24)).ToString("#,##0h"))
+ | _ -> String.Empty
+
+ let minText =
+ match (hours, mins) with
+ | h, m when h > 0 && m > 0 -> sprintf "%s" (m.ToString("00m"))
+ | _, m when m > 0 -> sprintf "%s" (m.ToString("0m"))
+ | _ -> String.Empty
+
+ let secText =
+ match (days, hours, mins, secs) with
+ | d, _, 0, s when d > 0 && s > 0 -> sprintf "%ss" (t.ToString("ss"))
+ | _, 0, 0, s when s > 0 -> sprintf "%ss" (t.ToString("s\\.ff"))
+ | _, h, _, s when h > 0 && s > 0 -> sprintf "%ss" (t.ToString("ss"))
+ | _, _, m, s when m > 0 && s > 0 -> sprintf "%ss" (t.ToString("ss"))
+ | _, _, _, s when s > 0 -> sprintf "%s" (t.ToString("s"))
+ | _ -> String.Empty
+
+ $"{prependText}{hourText}{minText}{secText}"
+
+open Logic
+
+/// A simple wrapper for `System.Diagnostic.Stopwatch` class saves its start time
+/// and provides friendly representations of the elapsed time.
+type Watch() =
+ let mutable startedAt = Stopwatch.GetTimestamp()
+
+ /// Returns a formatted "friendly" version of the elapsed time.
+ member _.ElapsedFriendly =
+ format <| Stopwatch.GetElapsedTime(startedAt)
+
+ /// Sets the start time for this instance to the current time.
+ member _.Restart =
+ startedAt <- Stopwatch.GetTimestamp()
diff --git a/Startwatch.Library/Startwatch.Library.csproj b/Startwatch.Library/Startwatch.Library.csproj
deleted file mode 100644
index e3fc4dd..0000000
--- a/Startwatch.Library/Startwatch.Library.csproj
+++ /dev/null
@@ -1,20 +0,0 @@
-
-
- net9.0
- disable
- enable
- CodeConscious.Startwatch
- 0.0.4
- CodeConscious
- CodeConscious
- A simple Stopwatch wrapper.
- https://github.com/codeconscious/startwatch
- https://github.com/codeconscious/startwatch
- readme.md
- MIT
- true
-
-
-
-
-
diff --git a/Startwatch.Library/Startwatch.Library.fsproj b/Startwatch.Library/Startwatch.Library.fsproj
new file mode 100644
index 0000000..8a2c2de
--- /dev/null
+++ b/Startwatch.Library/Startwatch.Library.fsproj
@@ -0,0 +1,30 @@
+
+
+
+ net9.0
+ true
+ CodeConscious.Startwatch
+ 1.0.0
+ CodeConscious
+ CodeConscious
+ A simple wrapper for System.Diagnostics.Stopwatch.
+ https://github.com/codeconscious/startwatch
+ https://github.com/codeconscious/startwatch
+ readme.md
+ MIT
+ true
+ true
+ Rewrote in F# and changed the display of very short times.
+ startwatch stopwatch timer fsharp codeconscious
+ Startwatch.Library
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Startwatch.Library/Watch.cs b/Startwatch.Library/Watch.cs
deleted file mode 100644
index e8c8151..0000000
--- a/Startwatch.Library/Watch.cs
+++ /dev/null
@@ -1,42 +0,0 @@
-using System;
-using System.Diagnostics;
-
-namespace Startwatch.Library;
-
-///
-/// A `Stopwatch` wrapper. Starts the enclosed `Stopwatch` upon instantiation.
-///
-public sealed class Watch
-{
- private readonly Stopwatch _stopwatch = new();
-
- ///
- /// Returns a formatted version of the elapsed time since the timer was started.
- ///
- ///
- /// Using ticks because .ElapsedMilliseconds can be wildly inaccurate.
- /// (Reference: https://stackoverflow.com/q/5113750/11767771)
- ///
- /// Also, use `Stopwatch.Elapsed.Ticks` over `Stopwatch.ElapsedTicks`.
- /// For reasons of which I'm unware, the latter returns unexpected values.
- ///
- public string ElapsedFriendly =>
- TimeSpan.FromTicks(_stopwatch.Elapsed.Ticks).ElapsedFriendly();
-
- ///
- /// A constructor that automatically starts the enclosed `Stopwatch`.
- ///
- public Watch()
- {
- _stopwatch.Start();
- }
-
- ///
- /// Stops time interval measurement, resets the elapsed time to zero,
- /// and starts measuring elapsed time.
- ///
- public void Restart()
- {
- _stopwatch.Restart();
- }
-}
diff --git a/Startwatch.Library/docs/readme.md b/Startwatch.Library/docs/readme.md
index 8e8b148..035a69c 100644
--- a/Startwatch.Library/docs/readme.md
+++ b/Startwatch.Library/docs/readme.md
@@ -1,7 +1,7 @@
# Startwatch
-This is a very simple wrapper around `System.Diagnostics.Stopwatch`. Since its stopwatch starts automatically, its name is "Startwatch." ^_^
+This is a very simple wrapper around the `System.Diagnostics.Stopwatch` class that just shows "friendly" versions of elapsed times via its `GetElapsedTime` property.
-I largely created this to get some experience uploading a package to Nuget.org, but also to use in some of my personal projects.
+Since it starts tracking time at class instantiation, its name is "Startwatch." ^_^
-Note that the API of this library could change significantly at any moment.
+I largely created this to get some experience uploading packages to Nuget.org, but I also use it in some of my personal projects.
diff --git a/Startwatch.Tests/Program.fs b/Startwatch.Tests/Program.fs
new file mode 100644
index 0000000..52113d0
--- /dev/null
+++ b/Startwatch.Tests/Program.fs
@@ -0,0 +1,4 @@
+module Program
+
+[]
+let main _ = 0
\ No newline at end of file
diff --git a/Startwatch.Tests/Startwatch.Tests.csproj b/Startwatch.Tests/Startwatch.Tests.csproj
deleted file mode 100644
index 85fe16c..0000000
--- a/Startwatch.Tests/Startwatch.Tests.csproj
+++ /dev/null
@@ -1,24 +0,0 @@
-
-
- net9.0
- disable
- enable
- false
- true
-
-
-
-
-
- runtime; build; native; contentfiles; analyzers; buildtransitive
- all
-
-
- runtime; build; native; contentfiles; analyzers; buildtransitive
- all
-
-
-
-
-
-
diff --git a/Startwatch.Tests/Startwatch.Tests.fsproj b/Startwatch.Tests/Startwatch.Tests.fsproj
new file mode 100644
index 0000000..7859d12
--- /dev/null
+++ b/Startwatch.Tests/Startwatch.Tests.fsproj
@@ -0,0 +1,25 @@
+
+
+
+ net9.0
+ false
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Startwatch.Tests/StartwatchTests.cs b/Startwatch.Tests/StartwatchTests.cs
deleted file mode 100644
index 8a8f626..0000000
--- a/Startwatch.Tests/StartwatchTests.cs
+++ /dev/null
@@ -1,242 +0,0 @@
-using System;
-using Startwatch.Library;
-
-namespace Startwatch.Tests;
-
-public sealed class StartwatchTests
-{
- [Fact]
- public void Nanoseconds_ThreeDigits_FormatsCorrectly()
- {
- var timeSpan = TimeSpan.FromTicks(1); // 1 tick == 100 nanoseconds
- string actual = timeSpan.ElapsedFriendly();
- const string expected = "100ns";
- Assert.Equal(expected, actual);
- }
-
- [Fact]
- public void Nanoseconds_FiveDigits_FormatsCorrectly()
- {
- var timeSpan = TimeSpan.FromTicks(100); // 1 tick == 100 nanoseconds
- string actual = timeSpan.ElapsedFriendly();
- const string expected = "10,000ns";
- Assert.Equal(expected, actual);
- }
-
- [Fact]
- public void Milliseconds_OneDigit_FormatsCorrectly()
- {
- var timeSpan = TimeSpan.FromMilliseconds(1);
- string actual = timeSpan.ElapsedFriendly();
- const string expected = "1ms";
- Assert.Equal(expected, actual);
- }
-
- [Fact]
- public void Milliseconds_TwoDigits_FormatsCorrectly()
- {
- var timeSpan = TimeSpan.FromMilliseconds(99);
- string actual = timeSpan.ElapsedFriendly();
- const string expected = "99ms";
- Assert.Equal(expected, actual);
- }
-
- [Fact]
- public void Milliseconds_ThreeDigits_FormatsCorrectly()
- {
- var timeSpan = TimeSpan.FromMilliseconds(999);
- string actual = timeSpan.ElapsedFriendly();
- const string expected = "999ms";
- Assert.Equal(expected, actual);
- }
-
- [Fact]
- public void Milliseconds_ThreeDigitsWithSmallDecimal_FormatsCorrectly()
- {
- var timeSpan = TimeSpan.FromMilliseconds(999.3);
- string actual = timeSpan.ElapsedFriendly();
- const string expected = "999ms";
- Assert.Equal(expected, actual);
- }
-
- [Fact]
- public void Milliseconds_ThreeDigitsWithLargeDecimal_FormatsCorrectly()
- {
- var timeSpan = TimeSpan.FromMilliseconds(999.9);
- string actual = timeSpan.ElapsedFriendly();
- const string expected = "1,000ms";
- Assert.Equal(expected, actual);
- }
-
- [Fact]
- public void SecondsOnly_FormatsCorrectly()
- {
- TimeSpan timeSpan = new(0, 0, 3);
- string actual = timeSpan.ElapsedFriendly();
- const string expected = "3.00s";
- Assert.Equal(expected, actual);
- }
-
- [Fact]
- public void Seconds_With500Milliseconds_FormatsCorrectly()
- {
- TimeSpan timeSpan = new(0, 0, 0, 3, 500);
- string actual = timeSpan.ElapsedFriendly();
- const string expected = "3.50s";
- Assert.Equal(expected, actual);
- }
-
- [Fact]
- public void Seconds_With520Milliseconds_FormatsCorrectly()
- {
- TimeSpan timeSpan = new(0, 0, 0, 3, 520);
- string actual = timeSpan.ElapsedFriendly();
- const string expected = "3.52s";
- Assert.Equal(expected, actual);
- }
-
- [Fact]
- public void Minute_NoSeconds_FormatsCorrectly()
- {
- TimeSpan timeSpan = new(0, 1, 0);
- string actual = timeSpan.ElapsedFriendly();
- const string expected = "exactly 1m";
- Assert.Equal(expected, actual);
- }
-
- [Fact]
- public void Minute_30Seconds_FormatsCorrectly()
- {
- TimeSpan timeSpan = new(0, 1, 30);
- string actual = timeSpan.ElapsedFriendly();
- const string expected = "1m30s";
- Assert.Equal(expected, actual);
- }
-
- [Fact]
- public void Minutes_TwoDigits_NoSeconds_FormatsCorrectly()
- {
- TimeSpan timeSpan = new(0, 59, 0);
- string actual = timeSpan.ElapsedFriendly();
- const string expected = "exactly 59m";
- Assert.Equal(expected, actual);
- }
-
- [Fact]
- public void Minutes_TwoDigits_NoSeconds_WithSeconds_FormatsCorrectly()
- {
- TimeSpan timeSpan = new(0, 59, 30);
- string actual = timeSpan.ElapsedFriendly();
- const string expected = "59m30s";
- Assert.Equal(expected, actual);
- }
-
-
- [Fact]
- public void SingleDigitHour_NoMinutes_NoSeconds_FormatsCorrectly()
- {
- TimeSpan timeSpan = new(7, 0, 0);
- string actual = timeSpan.ElapsedFriendly();
- const string expected = "exactly 7h";
- Assert.Equal(expected, actual);
- }
-
- [Fact]
- public void SingleDigitHour_WithMinutes_NoSeconds_FormatsCorrectly()
- {
- TimeSpan timeSpan = new(7, 20, 0);
- string actual = timeSpan.ElapsedFriendly();
- const string expected = "exactly 7h20m";
- Assert.Equal(expected, actual);
- }
-
- [Fact]
- public void SingleDigitHour_NoMinutes_WithSeconds_FormatsCorrectly()
- {
- TimeSpan timeSpan = new(7, 0, 47);
- string actual = timeSpan.ElapsedFriendly();
- const string expected = "7h47s";
- Assert.Equal(expected, actual);
- }
-
- [Fact]
- public void DoubleDigitHour_NoMinutes_NoSeconds_FormatsCorrectly()
- {
- TimeSpan timeSpan = new(13, 0, 0);
- string actual = timeSpan.ElapsedFriendly();
- const string expected = "exactly 13h";
- Assert.Equal(expected, actual);
- }
-
- [Fact]
- public void SingleDigitHour_SingleDigitMinutes_NoSeconds_FormatsCorrectly()
- {
- TimeSpan timeSpan = new(1, 5, 0);
- string actual = timeSpan.ElapsedFriendly();
- const string expected = "exactly 1h05m";
- Assert.Equal(expected, actual);
- }
-
- [Fact]
- public void SingleDigitHour_DoubleDigitMinutes_NoSeconds_FormatsCorrectly()
- {
- TimeSpan timeSpan = new(1, 55, 0);
- string actual = timeSpan.ElapsedFriendly();
- const string expected = "exactly 1h55m";
- Assert.Equal(expected, actual);
- }
-
- [Fact]
- public void SingleDigitHour_DoubleDigitMinutes_SingleDigitSeconds_FormatsCorrectly()
- {
- TimeSpan timeSpan = new(1, 55, 8);
- string actual = timeSpan.ElapsedFriendly();
- const string expected = "1h55m08s";
- Assert.Equal(expected, actual);
- }
-
- [Fact]
- public void DoubleDigitHour_SingleDigitMinutes_SingleDigitSeconds_FormatsCorrectly()
- {
- TimeSpan timeSpan = new(12, 5, 8);
- string actual = timeSpan.ElapsedFriendly();
- const string expected = "12h05m08s";
- Assert.Equal(expected, actual);
- }
-
- [Fact]
- public void TripleDigitHour_SingleDigitMinutes_SingleDigitSeconds_FormatsCorrectly()
- {
- TimeSpan timeSpan = new(36, 59, 59);
- string actual = timeSpan.ElapsedFriendly();
- const string expected = "36h59m59s";
- Assert.Equal(expected, actual);
- }
-
- [Fact]
- public void SingleDigitDay_NoHoursMinutesOrSeconds_FormatsCorrectly()
- {
- TimeSpan timeSpan = new(10, 0, 0, 0);
- string actual = timeSpan.ElapsedFriendly();
- const string expected = "exactly 240h";
- Assert.Equal(expected, actual);
- }
-
- [Fact]
- public void SingleDigitDay_WithHoursMinutesOrSeconds_FormatsCorrectly()
- {
- TimeSpan timeSpan = new(4, 4, 59, 59);
- string actual = timeSpan.ElapsedFriendly();
- const string expected = "100h59m59s";
- Assert.Equal(expected, actual);
- }
-
- [Fact]
- public void TripleDigitDay_WithHoursMinutesOrSeconds_FormatsCorrectly()
- {
- TimeSpan timeSpan = new(100, 23, 59, 59);
- string actual = timeSpan.ElapsedFriendly();
- const string expected = "2,423h59m59s";
- Assert.Equal(expected, actual);
- }
-}
diff --git a/Startwatch.Tests/Tests.fs b/Startwatch.Tests/Tests.fs
new file mode 100644
index 0000000..dc85600
--- /dev/null
+++ b/Startwatch.Tests/Tests.fs
@@ -0,0 +1,277 @@
+module Tests
+
+open System
+open Xunit
+open Startwatch.Library.Logic
+
+[]
+let ``Throws for negative TimeSpans`` () =
+ let timeSpan = TimeSpan.FromTicks(-8001)
+ Assert.Throws(fun _ -> format timeSpan :> obj)
+
+[]
+let ``Zero timespans`` () =
+ let timeSpan = TimeSpan.Zero
+ let actual = format timeSpan
+ let expected = "no time"
+ Assert.Equal(expected, actual)
+
+[]
+let ``Millisecond ten-thousandths (with nanoseconds)`` () =
+ let timeSpan = TimeSpan.FromMilliseconds(0.0001)
+ let actual = format timeSpan
+ let expected = "0.0001ms (100ns)"
+ Assert.Equal(expected, actual)
+
+
+[]
+let ``Milliseconds with 4 decimals`` () =
+ let timeSpan = TimeSpan.FromMilliseconds(0.9598)
+ let actual = format timeSpan
+ let expected = "0.9598ms"
+ Assert.Equal(expected, actual)
+
+[]
+let ``Millisecond thousandths (with nanoseconds)`` () =
+ let timeSpan = TimeSpan.FromMilliseconds(0.001)
+ let actual = format timeSpan
+ let expected = "0.001ms (1,000ns)"
+ Assert.Equal(expected, actual)
+
+[]
+let ``Millisecond hundredths`` () =
+ let timeSpan = TimeSpan.FromMilliseconds(0.01)
+ let actual = format timeSpan
+ let expected = "0.01ms"
+ Assert.Equal(expected, actual)
+
+[]
+let ``Millisecond tenths`` () =
+ let timeSpan = TimeSpan.FromMilliseconds(0.1)
+ let actual = format timeSpan
+ let expected = "0.1ms"
+ Assert.Equal(expected, actual)
+
+[]
+let ``One millisecond`` () =
+ let timeSpan = TimeSpan.FromMilliseconds(1)
+ let actual = format timeSpan
+ let expected = "1ms"
+ Assert.Equal(expected, actual)
+
+[]
+let ``Double-digit milliseconds`` () =
+ let timeSpan = TimeSpan.FromMilliseconds(99.0)
+ let actual = format timeSpan
+ let expected = "99ms"
+ Assert.Equal(expected, actual)
+
+[]
+let ``Double-digit milliseconds with a small decimal`` () =
+ let timeSpan = TimeSpan.FromMilliseconds(99.2)
+ let actual = format timeSpan
+ let expected = "99ms"
+ Assert.Equal(expected, actual)
+
+[]
+let ``Double-digit milliseconds with a large decimal`` () =
+ let timeSpan = TimeSpan.FromMilliseconds(99.9)
+ let actual = format timeSpan
+ let expected = "100ms"
+ Assert.Equal(expected, actual)
+
+
+[]
+let ``Triple-digit milliseconds`` () =
+ let timeSpan = TimeSpan.FromMilliseconds(999.0)
+ let actual = format timeSpan
+ let expected = "999ms"
+ Assert.Equal(expected, actual)
+
+[]
+let ``Triple-digit milliseconds with a small decimal`` () =
+ let timeSpan = TimeSpan.FromMilliseconds(999.3)
+ let actual = format timeSpan
+ let expected = "999ms"
+ Assert.Equal(expected, actual)
+
+[]
+let ``Triple-digit milliseconds with a large decimal`` () =
+ let timeSpan = TimeSpan.FromMilliseconds(999.9)
+ let actual = format timeSpan
+ let expected = "1,000ms"
+ Assert.Equal(expected, actual)
+
+[]
+let ``Single-digit seconds`` () =
+ let timeSpan = TimeSpan(0, 0, 3)
+ let actual = format timeSpan
+ let expected = "3.00s"
+ Assert.Equal(expected, actual)
+
+[]
+let ``Single-digit seconds with 500 milliseconds`` () =
+ let timeSpan = TimeSpan(0, 0, 0, 3, 500)
+ let actual = format timeSpan
+ let expected = "3.50s"
+ Assert.Equal(expected, actual)
+
+[]
+let ``Single-digit seconds with 520 milliseconds`` () =
+ let timeSpan = TimeSpan(0, 0, 0, 3, 520)
+ let actual = format timeSpan
+ let expected = "3.52s"
+ Assert.Equal(expected, actual)
+
+[]
+let ``Single-digit minute with no seconds`` () =
+ let timeSpan = TimeSpan(0, 1, 0)
+ let actual = format timeSpan
+ let expected = "exactly 1m"
+ Assert.Equal(expected, actual)
+
+[]
+let ``Single-digit minute with single-digit seconds`` () =
+ let timeSpan = TimeSpan(0, 1, 5)
+ let actual = format timeSpan
+ let expected = "1m05s"
+ Assert.Equal(expected, actual)
+
+[]
+let ``Single-digit minute with double-digit seconds`` () =
+ let timeSpan = TimeSpan(0, 1, 30)
+ let actual = format timeSpan
+ let expected = "1m30s"
+ Assert.Equal(expected, actual)
+
+[]
+let ``Single-digit minute with maximum seconds`` () =
+ let timeSpan = TimeSpan(0, 1, 59)
+ let actual = format timeSpan
+ let expected = "1m59s"
+ Assert.Equal(expected, actual)
+
+[]
+let ``Two-digit minutes with no seconds`` () =
+ let timeSpan = TimeSpan(0, 59, 0)
+ let actual = format timeSpan
+ let expected = "exactly 59m"
+ Assert.Equal(expected, actual)
+
+[]
+let ``Two-digit minutes with 30 seconds`` () =
+ let timeSpan = TimeSpan(0, 59, 30)
+ let actual = format timeSpan
+ let expected = "59m30s"
+ Assert.Equal(expected, actual)
+
+[]
+let ``Single-digit hour with no minutes or seconds`` () =
+ let timeSpan = TimeSpan(7, 0, 0)
+ let actual = format timeSpan
+ let expected = "exactly 7h"
+ Assert.Equal(expected, actual)
+
+[]
+let ``Single-digit hour with single-digit minutes but no seconds`` () =
+ let timeSpan = TimeSpan(7, 7, 0)
+ let actual = format timeSpan
+ let expected = "exactly 7h07m"
+ Assert.Equal(expected, actual)
+
+[]
+let ``Single-digit hour with single-digit minutes and single-digit seconds`` () =
+ let timeSpan = TimeSpan(7, 7, 4)
+ let actual = format timeSpan
+ let expected = "7h07m04s"
+ Assert.Equal(expected, actual)
+
+let ``Single-digit hour with double-digit minutes but no seconds`` () =
+ let timeSpan = TimeSpan(7, 20, 0)
+ let actual = format timeSpan
+ let expected = "exactly 7h20m"
+ Assert.Equal(expected, actual)
+
+[]
+let ``Single-digit hour without minutes but with seconds`` () =
+ let timeSpan = TimeSpan(7, 0, 47)
+ let actual = format timeSpan
+ let expected = "7h47s"
+ Assert.Equal(expected, actual)
+
+[]
+let ``Double-digit hour with no minutes or seconds`` () =
+ let timeSpan = TimeSpan(13, 0, 0)
+ let actual = format timeSpan
+ let expected = "exactly 13h"
+ Assert.Equal(expected, actual)
+
+[]
+let ``Single-digit hour with single-digit minutes and with seconds`` () =
+ let timeSpan = TimeSpan(1, 3, 8)
+ let actual = format timeSpan
+ let expected = "1h03m08s"
+ Assert.Equal(expected, actual)
+
+[]
+let ``Single-digit hour with double-digit minutes and with seconds`` () =
+ let timeSpan = TimeSpan(1, 55, 8)
+ let actual = format timeSpan
+ let expected = "1h55m08s"
+ Assert.Equal(expected, actual)
+
+[]
+let ``Double-digit hour with single-digit minutes and with seconds`` () =
+ let timeSpan = TimeSpan(12, 5, 8)
+ let actual = format timeSpan
+ let expected = "12h05m08s"
+ Assert.Equal(expected, actual)
+
+[]
+let ``Double-digit hour over 24 hours with maximum minutes and seconds`` () =
+ let timeSpan = TimeSpan(36, 59, 59)
+ let actual = format timeSpan
+ let expected = "36h59m59s"
+ Assert.Equal(expected, actual)
+
+[]
+let ``Single-digit day with no minutes, hours, or seconds`` () =
+ let timeSpan = TimeSpan(10, 0, 0, 0)
+ let actual = format timeSpan
+ let expected = "exactly 240h"
+ Assert.Equal(expected, actual)
+
+[]
+let ``Single-digit day with no minutes, hours, but with single-digit seconds`` () =
+ let timeSpan = TimeSpan(10, 0, 0, 2)
+ let actual = format timeSpan
+ let expected = "240h02s"
+ Assert.Equal(expected, actual)
+
+[]
+let ``Single-digit day with no hours, but with single-digit minutes seconds`` () =
+ let timeSpan = TimeSpan(10, 0, 0, 2)
+ let actual = format timeSpan
+ let expected = "240h02s"
+ Assert.Equal(expected, actual)
+
+[]
+let ``Single-digit day with minutes, hours, and seconds`` () =
+ let timeSpan = TimeSpan(4, 4, 59, 59)
+ let actual = format timeSpan
+ let expected = "100h59m59s"
+ Assert.Equal(expected, actual)
+
+[]
+let ``Triple-digit day with minutes, hours, and seconds`` () =
+ let timeSpan = TimeSpan(100, 23, 59, 59)
+ let actual = format timeSpan
+ let expected = "2,423h59m59s"
+ Assert.Equal(expected, actual)
+
+[]
+let ``Maximum TimeSpan`` () =
+ let timeSpan = TimeSpan.MaxValue
+ let actual = format timeSpan
+ let expected = "256,204,778h48m05s"
+ Assert.Equal(expected, actual)
diff --git a/Startwatch.Tests/Usings.cs b/Startwatch.Tests/Usings.cs
deleted file mode 100644
index 9df1d42..0000000
--- a/Startwatch.Tests/Usings.cs
+++ /dev/null
@@ -1 +0,0 @@
-global using Xunit;
diff --git a/Startwatch.sln b/Startwatch.sln
index 09434bb..7f52969 100644
--- a/Startwatch.sln
+++ b/Startwatch.sln
@@ -3,9 +3,9 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 25.0.1706.0
MinimumVisualStudioVersion = 10.0.40219.1
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Startwatch.Library", ".\Startwatch.Library\Startwatch.Library.csproj", "{598A49D6-9724-400C-853A-8B950297DEBD}"
+Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Startwatch.Library", "Startwatch.Library\Startwatch.Library.fsproj", "{2D3CE51D-304B-41F5-B86A-C6794E5364E8}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Startwatch.Tests", ".\Startwatch.Tests\Startwatch.Tests.csproj", "{295D954F-453F-4311-8D72-1EFF57261A38}"
+Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Startwatch.Tests", "Startwatch.Tests\Startwatch.Tests.fsproj", "{88AB89DE-6161-48E8-A95B-5E49FC2F1BED}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -13,14 +13,14 @@ Global
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {598A49D6-9724-400C-853A-8B950297DEBD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {598A49D6-9724-400C-853A-8B950297DEBD}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {598A49D6-9724-400C-853A-8B950297DEBD}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {598A49D6-9724-400C-853A-8B950297DEBD}.Release|Any CPU.Build.0 = Release|Any CPU
- {295D954F-453F-4311-8D72-1EFF57261A38}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {295D954F-453F-4311-8D72-1EFF57261A38}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {295D954F-453F-4311-8D72-1EFF57261A38}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {295D954F-453F-4311-8D72-1EFF57261A38}.Release|Any CPU.Build.0 = Release|Any CPU
+ {2D3CE51D-304B-41F5-B86A-C6794E5364E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {2D3CE51D-304B-41F5-B86A-C6794E5364E8}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {2D3CE51D-304B-41F5-B86A-C6794E5364E8}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {2D3CE51D-304B-41F5-B86A-C6794E5364E8}.Release|Any CPU.Build.0 = Release|Any CPU
+ {88AB89DE-6161-48E8-A95B-5E49FC2F1BED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {88AB89DE-6161-48E8-A95B-5E49FC2F1BED}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {88AB89DE-6161-48E8-A95B-5E49FC2F1BED}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {88AB89DE-6161-48E8-A95B-5E49FC2F1BED}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE