From 174b3b424508a2fc7e5b48dff35fc8645114f4c6 Mon Sep 17 00:00:00 2001 From: George Hahn Date: Sat, 5 Jul 2014 20:19:24 -0400 Subject: [PATCH] Added byte rate calculation functons, closes #296 closes #314 --- readme.md | 18 +++++ release_notes.md | 1 + ...provalTest.approve_public_api.approved.txt | 11 ++- src/Humanizer.Tests/Bytes/ByteRateTests.cs | 67 +++++++++++++++++++ src/Humanizer.Tests/Humanizer.Tests.csproj | 1 + src/Humanizer/Bytes/ByteRate.cs | 66 ++++++++++++++++++ src/Humanizer/Bytes/ByteSizeExtensions.cs | 14 +++- src/Humanizer/Humanizer.csproj | 1 + 8 files changed, 177 insertions(+), 2 deletions(-) create mode 100644 src/Humanizer.Tests/Bytes/ByteRateTests.cs create mode 100644 src/Humanizer/Bytes/ByteRate.cs diff --git a/readme.md b/readme.md index fbd7c08c7..ff4bceb5f 100644 --- a/readme.md +++ b/readme.md @@ -762,6 +762,24 @@ ByteSize.Parse("1.55 tB"); ByteSize.Parse("1.55 tb"); ``` +Finally, if you need to calculate the rate at which a quantity of bytes has been transferred, you can use the `Per` method of `ByteSize`. The `Per` method accepts one argument - the measurement interval for the bytes; this is the amount of time it took to transfer the bytes. + +The `Per` method returns a `ByteRate` class which has a `Humanize` method. By default, rates are given in seconds (eg, MB/s). However, if desired, a TimeUnit may be passed to `Humanize` for an alternate interval. Valid intervals are `TimeUnit.Second`, `TimeUnit.Minute`, and `TimeUnit.Hour`. Examples of each interval and example byte rate usage is below. + +``` +var size = ByteSize.FromMegabytes(10); +var measurementInterval = TimeSpan.FromSeconds(1); + +var text = size.Per(measurementInterval).Humanize(); +// 10 MB/s + +text = size.Per(measurementInterval).Humanize(TimeUnit.Minute); +// 600 MB/min + +text = size.Per(measurementInterval).Humanize(TimeUnit.Hour); +// 35.15625 GB/hour +``` + ##Mix this into your framework to simplify your life This is just a baseline and you can use this to simplify your day to day job. For example, in Asp.Net MVC we keep chucking `Display` attribute on ViewModel properties so `HtmlHelper` can generate correct labels for us; but, just like enums, in vast majority of cases we just need a space between the words in property name - so why not use `"string".Humanize` for that?! diff --git a/release_notes.md b/release_notes.md index e4a48d143..e9d0f5ae4 100644 --- a/release_notes.md +++ b/release_notes.md @@ -1,6 +1,7 @@ ###In Development - [#320](https://github.com/MehdiK/Humanizer/pull/320): Fixed Dehumanize actually humanizing an already dehumanized string - [#322](https://github.com/MehdiK/Humanizer/pull/322): DefaultFormatter.TimeSpanHumanize throws a more meaningful exception when called with TimeUnits larger than TimeUnit.Week + - [#314](https://github.com/MehdiK/Humanizer/pull/314): Added ByteRate class and supporting members to facilitate calculation of byte transfer rates [Commits](https://github.com/MehdiK/Humanizer/compare/v1.28.0...master) diff --git a/src/Humanizer.Tests/ApiApprover/PublicApiApprovalTest.approve_public_api.approved.txt b/src/Humanizer.Tests/ApiApprover/PublicApiApprovalTest.approve_public_api.approved.txt index 145de1445..658efb17e 100644 --- a/src/Humanizer.Tests/ApiApprover/PublicApiApprovalTest.approve_public_api.approved.txt +++ b/src/Humanizer.Tests/ApiApprover/PublicApiApprovalTest.approve_public_api.approved.txt @@ -1,4 +1,12 @@ -public struct ByteSize +public class ByteRate +{ + public ByteRate(Humanizer.Bytes.ByteSize size, System.TimeSpan interval) { } + public System.TimeSpan Interval { get; set; } + public Humanizer.Bytes.ByteSize Size { get; set; } + public string Humanize(Humanizer.Localisation.TimeUnit timeUnit) { } +} + +public struct ByteSize { public long BitsInByte; public string BitSymbol; @@ -85,6 +93,7 @@ public class ByteSizeExtensions public Humanizer.Bytes.ByteSize Megabytes(int input) { } public Humanizer.Bytes.ByteSize Megabytes(uint input) { } public Humanizer.Bytes.ByteSize Megabytes(double input) { } + public Humanizer.Bytes.ByteRate Per(Humanizer.Bytes.ByteSize size, System.TimeSpan interval) { } public Humanizer.Bytes.ByteSize Terabytes(byte input) { } public Humanizer.Bytes.ByteSize Terabytes(sbyte input) { } public Humanizer.Bytes.ByteSize Terabytes(short input) { } diff --git a/src/Humanizer.Tests/Bytes/ByteRateTests.cs b/src/Humanizer.Tests/Bytes/ByteRateTests.cs new file mode 100644 index 000000000..16ba5277b --- /dev/null +++ b/src/Humanizer.Tests/Bytes/ByteRateTests.cs @@ -0,0 +1,67 @@ +using System; +using Humanizer.Bytes; +using Humanizer.Localisation; +using Xunit; +using Xunit.Extensions; + +namespace Humanizer.Tests.Bytes +{ + public class ByteRateTests : AmbientCulture + { + public ByteRateTests() : base("en") { } + + [Theory] + [InlineData(400, 1, "400 B/s")] + [InlineData(4 * 1024, 1, "4 KB/s")] + [InlineData(4 * 1024 * 1024, 1, "4 MB/s")] + [InlineData(4 * 2 * 1024 * 1024, 2, "4 MB/s")] + [InlineData(4 * 1024, 0.1, "40 KB/s")] + [InlineData(15 * 60 * 1024 * 1024, 60, "15 MB/s")] + public void HumanizesRates(long inputBytes, double perSeconds, string expectedValue) + { + var size = new ByteSize(inputBytes); + var interval = TimeSpan.FromSeconds(perSeconds); + + var rate = size.Per(interval).Humanize(); + + Assert.Equal(expectedValue, rate); + } + + [Theory] + [InlineData(1, 1, TimeUnit.Second, "1 MB/s")] + [InlineData(1, 60, TimeUnit.Minute, "1 MB/min")] + [InlineData(1, 60 * 60, TimeUnit.Hour, "1 MB/hour")] + [InlineData(10, 1, TimeUnit.Second, "10 MB/s")] + [InlineData(10, 60, TimeUnit.Minute, "10 MB/min")] + [InlineData(10, 60 * 60, TimeUnit.Hour, "10 MB/hour")] + [InlineData(1, 10 * 1, TimeUnit.Second, "102.4 KB/s")] + [InlineData(1, 10 * 60, TimeUnit.Minute, "102.4 KB/min")] + [InlineData(1, 10 * 60 * 60, TimeUnit.Hour, "102.4 KB/hour")] + public void TimeUnitTests(long megabytes, double measurementIntervalSeconds, TimeUnit displayInterval, string expectedValue) + { + var size = ByteSize.FromMegabytes(megabytes); + var measurementInterval = TimeSpan.FromSeconds(measurementIntervalSeconds); + + var rate = size.Per(measurementInterval); + var text = rate.Humanize(displayInterval); + + Assert.Equal(expectedValue, text); + } + + [Theory] + [InlineData(TimeUnit.Millisecond)] + [InlineData(TimeUnit.Day)] + [InlineData(TimeUnit.Month)] + [InlineData(TimeUnit.Week)] + [InlineData(TimeUnit.Year)] + public void ThowsOnUnsupportedData(TimeUnit units) + { + var dummyRate = ByteSize.FromBits(1).Per(TimeSpan.FromSeconds(1)); + + Assert.Throws(() => + { + dummyRate.Humanize(units); + }); + } + } +} diff --git a/src/Humanizer.Tests/Humanizer.Tests.csproj b/src/Humanizer.Tests/Humanizer.Tests.csproj index 213ede989..2d6937c42 100644 --- a/src/Humanizer.Tests/Humanizer.Tests.csproj +++ b/src/Humanizer.Tests/Humanizer.Tests.csproj @@ -56,6 +56,7 @@ + diff --git a/src/Humanizer/Bytes/ByteRate.cs b/src/Humanizer/Bytes/ByteRate.cs new file mode 100644 index 000000000..8e8dcc519 --- /dev/null +++ b/src/Humanizer/Bytes/ByteRate.cs @@ -0,0 +1,66 @@ +using System; +using Humanizer.Localisation; + +namespace Humanizer.Bytes +{ + + /// + /// Class to hold a ByteSize and a measurement interval, for the purpose of calculating the rate of transfer + /// + public class ByteRate + { + /// + /// Quantity of bytes + /// + /// + public ByteSize Size { get; private set;} + + /// + /// Interval that bytes were transferred in + /// + /// + public TimeSpan Interval { get; private set; } + + /// + /// Create a ByteRate with given quantity of bytes across an interval + /// + /// + /// + public ByteRate(ByteSize size, TimeSpan interval) + { + this.Size = size; + this.Interval = interval; + } + + /// + /// Calculate rate for the quantity of bytes and interval defined by this instance + /// + /// Unit of time to calculate rate for (defaults is per second) + /// + public string Humanize(TimeUnit timeUnit = TimeUnit.Second) + { + TimeSpan displayInterval; + string displayUnit; + + if (timeUnit == TimeUnit.Second) + { + displayInterval = TimeSpan.FromSeconds(1); + displayUnit = "s"; + } + else if (timeUnit == TimeUnit.Minute) + { + displayInterval = TimeSpan.FromMinutes(1); + displayUnit = "min"; + } + else if (timeUnit == TimeUnit.Hour) + { + displayInterval = TimeSpan.FromHours(1); + displayUnit = "hour"; + } + else + throw new NotSupportedException("timeUnit must be Second, Minute, or Hour"); + + return (new ByteSize(Size.Bytes / Interval.TotalSeconds * displayInterval.TotalSeconds)).Humanize() + '/' + displayUnit; + } + } +} diff --git a/src/Humanizer/Bytes/ByteSizeExtensions.cs b/src/Humanizer/Bytes/ByteSizeExtensions.cs index d057dc9e3..2fbd7a784 100644 --- a/src/Humanizer/Bytes/ByteSizeExtensions.cs +++ b/src/Humanizer/Bytes/ByteSizeExtensions.cs @@ -1,4 +1,5 @@ -using Humanizer.Bytes; +using System; +using Humanizer.Bytes; // ReSharper disable once CheckNamespace namespace Humanizer @@ -438,5 +439,16 @@ public static string Humanize(this ByteSize input, string format = null) { return string.IsNullOrWhiteSpace(format) ? input.ToString() : input.ToString(format); } + + /// + /// Turns a quantity of bytes in a given interval into a rate that can be manipulated + /// + /// Quantity of bytes + /// Interval to create rate for + /// + public static ByteRate Per(this ByteSize size, TimeSpan interval) + { + return new ByteRate(size, interval); + } } } diff --git a/src/Humanizer/Humanizer.csproj b/src/Humanizer/Humanizer.csproj index c74acba60..0af4bf994 100644 --- a/src/Humanizer/Humanizer.csproj +++ b/src/Humanizer/Humanizer.csproj @@ -49,6 +49,7 @@ Humanizer.snk +