diff --git a/readme.md b/readme.md
index 621ad4f86..0a1168c0d 100644
--- a/readme.md
+++ b/readme.md
@@ -754,6 +754,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 0e20cf130..66374a14c 100644
--- a/release_notes.md
+++ b/release_notes.md
@@ -8,6 +8,7 @@
- [#307](https://github.com/MehdiK/Humanizer/pull/307): Added support to string.FormatWith for the explicit culture parameter
- [#312](https://github.com/MehdiK/Humanizer/pull/312): Added Turkish ToWord, ToOrdinalWord and Ordinalize implementation
- [#173](https://github.com/MehdiK/Humanizer/pull/173): Added support for Window Phone 8.1
+ - [#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.27.0...v1.28.0)
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..c17415171 100644
--- a/src/Humanizer.Tests/ApiApprover/PublicApiApprovalTest.approve_public_api.approved.txt
+++ b/src/Humanizer.Tests/ApiApprover/PublicApiApprovalTest.approve_public_api.approved.txt
@@ -92,6 +92,12 @@ public class ByteSizeExtensions
public Humanizer.Bytes.ByteSize Terabytes(int input) { }
public Humanizer.Bytes.ByteSize Terabytes(uint input) { }
public Humanizer.Bytes.ByteSize Terabytes(double input) { }
+ public Humanizer.Bytes.ByteRate Per(Humanizer.Bytes.ByteSize size, System.TimeSpan interval) { }
+}
+
+public class ByteRate
+{
+ public string Humanize(Humanizer.Bytes.TimeUnit timeUnit) { }
}
public class CasingExtensions
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 b39cce5b1..68e40ad4c 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
+