diff --git a/.gitignore b/.gitignore index 28aa2d1..19398a8 100644 --- a/.gitignore +++ b/.gitignore @@ -39,4 +39,4 @@ src/packages/** *.ncrunch* *.ndproj *.nupkg -**/project.lock.json \ No newline at end of file +src/ByteSize/pack diff --git a/.travis.yml b/.travis.yml index cef5031..dbdaaf9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,4 +4,4 @@ services: - docker script: - - make build-in-docker + - make test-in-docker diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..a8a939e --- /dev/null +++ b/Dockerfile @@ -0,0 +1,27 @@ +# Adapted from https://github.com/andrewlock/docker-dotnet-mono/blob/master/Dockerfile +FROM mcr.microsoft.com/dotnet/core/sdk:2.1.700 AS builder + +# Install mono +ENV MONO_VERSION 5.18.0.225 + +RUN apt-get update \ + && apt-get install -y --no-install-recommends gnupg dirmngr \ + && rm -rf /var/lib/apt/lists/* \ + && export GNUPGHOME="$(mktemp -d)" \ + && gpg --batch --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF \ + && gpg --batch --export --armor 3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF > /etc/apt/trusted.gpg.d/mono.gpg.asc \ + && gpgconf --kill all \ + && rm -rf "$GNUPGHOME" \ + && apt-key list | grep Xamarin \ + && apt-get purge -y --auto-remove gnupg dirmngr + +RUN echo "deb http://download.mono-project.com/repo/debian stable-stretch/snapshots/$MONO_VERSION main" > /etc/apt/sources.list.d/mono-official-stable.list \ + && apt-get update \ + && apt-get install -y mono-runtime \ + && rm -rf /var/lib/apt/lists/* /tmp/* + +RUN apt-get update \ + && apt-get install -y binutils curl mono-devel ca-certificates-mono fsharp mono-vbnc nuget referenceassemblies-pcl \ + && rm -rf /var/lib/apt/lists/* /tmp/* + +WORKDIR /sln \ No newline at end of file diff --git a/LICENSE b/LICENSE index d458b58..9884ab8 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2013-2017 Omar Khudeira (http://omar.io) +Copyright (c) 2013-2019 Omar Khudeira (http://omar.io) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Makefile b/Makefile index d1294eb..34cd919 100644 --- a/Makefile +++ b/Makefile @@ -1,3 +1,13 @@ +.PHONY: build test pack + +# Check to see if Mono exists. If it does, use that path to build against .NET 4.5 +ifneq ("$(wildcard /usr/local/lib/mono/)","") + # MONO_REFERENCE_ASSEMBLIES is automatically pulled in ByteSizeLib.csproj + # to allow building against .NET Framework on a Mac where I do most of my + # development. + export MONO_REFERENCE_ASSEMBLIES=/usr/local/lib/mono +endif + build: dotnet build src @@ -7,8 +17,12 @@ test: pack: dotnet pack src/ByteSizeLib -c Release -o pack -build-in-docker: - # Use an image with both Mono and .NET Core SDK installed - docker run -td --name bytesize -v $(CURDIR):/bytesize andrewlock/dotnet-mono +test-in-docker: + # Stop and delete conatiner if already exists + docker stop bytesize || true && docker rm bytesize || true + # Use an image with both Mono and .NET Core SDK installed so we can build + # against .NET Framework 4.5 + docker build -t bytesize-build . + docker run -td --name bytesize -v $(CURDIR):/bytesize bytesize-build docker exec bytesize bash -c "cd /bytesize/src && msbuild -t:restore ByteSizeLib.sln" docker exec bytesize bash -c "cd /bytesize/src && dotnet test ByteSizeLib.Tests" diff --git a/README.md b/README.md index 8867f86..956f0c8 100644 --- a/README.md +++ b/README.md @@ -1,46 +1,78 @@ # ByteSize -`ByteSize` is a utility class that makes byte size representation in code easier by removing ambiguity of the value being represented. +`ByteSize` is a utility class that makes byte size representation in code easier +by removing ambiguity of the value being represented. `ByteSize` is to bytes what `System.TimeSpan` is to time. [![](https://travis-ci.org/omar/ByteSize.svg?branch=master)](https://travis-ci.org/omar/ByteSize) [![Stable nuget](https://img.shields.io/nuget/v/ByteSize.svg)](https://www.nuget.org/packages/ByteSize/) -#### Building +#### Development -* Windows: use Visual Studio -* Mac OS X - * Install [Mono](http://www.mono-project.com/download/). - * NOTE: using `brew install mono` will not install the PCL libraries required to build the PCL compatible DLLs. The PCL libraries can be installed by running the installer downloaded from http://www.mono-project.com/download/. - * Run `make build` in terminal. -* Linux - * Install [Mono](http://www.mono-project.com/docs/getting-started/install/linux/) and the reference assemblies (`sudo apt-get referenceassemblies-pcl`). - * Run `make build` in terminal. +- Install [.NET Core SDK](https://dotnet.microsoft.com/download) +- Build: `make build` +- Test: `make test` -## Usage +## v2 Breaking Changes -`ByteSize` assumes `1 kilobyte` = `1024 bytes`. See [why here](http://omar.io/2017/01/16/when-technically-right-is-wrong-kilobytes.html). +### Ratio Changes (HUGE BREAKING CHANGE) + +By default `ByteSize` now assumes `1 KB == 1000 B` and `1 KiB == 1024 B` to +adhere to the IEC and NIST standards (https://en.wikipedia.org/wiki/Binary_prefix). +In version 1 `ByteSize` assumed `1 KB == 1024 B`, that means if you're upgrading +from v1, you'll see differences in values. + +When you upgrade an existing application to v2 your existing code will be using +the decimal representation of bytes (i.e. `1 KB == 1000 B`). If the difference +in calculation is not material to your application, you don't need to change anything. + +However, if you want to use `1 KiB == 1024 B`, then you'll need to change all +`ByteSize` calls to the respective method. For example, calls to +`ByteSize.FromKiloByte` need to be changed to `ByteSize.FromKibiByte`. + +Lastly, `ByteSize` no longer supports the ratio of `1 KB == 1024 B`. Note this +is ***kilo***_bytes_ to _bytes_. The only ratio of `1 == 1024` is ***kibi***_bytes_ +to _bytes_. + +### Other Breaking Changes + +- Renamed property `LargestWholeNumberSymbol` and `LargestWholeNumberValue` to `LargestWholeNumberDecimalSymbol` and `LargestWholeNumberDecimalValue` respectively. +- Drop support for all platforms _except_ `netstandard1.0` and `net45`. + +## Usage + +`ByteSize` adheres to the IEC standard, see this [Wikipedia article](https://en.wikipedia.org/wiki/Kilobyte#Definitions_and_usage). +That means `ByteSize` assumes: + +- `1 kilobyte` = `1000 bytes` with 2 letter abbrevations `b`, `B`,`KB`, `MB`, `GB`, `TB`, `PB`. +- `1 kibibyte` = `1024 bytes` with 3 letter abbrevations `b`, `B`,`KiB`, `MiB`, `GiB`, `TiB`, `PiB`. + +`ByteSize` manages conversion of the values internally and provides methods to create and retrieve the values as needed. See the examples below. + +### Example Without `ByteSize`: ```c# -static double MaxFileSizeMBs = 1.5; +double maxFileSizeMBs = 1.5; -// I need it in KBs! -var kilobytes = MaxFileSizeMBs * 1024; // 1536 +// I need it in KBs and KiBs! +var kilobytes = maxFileSizeMBs * 1000; // 1500 +var kibibytes = maxFileSizeMBs * 1024; // 1536 ``` With `ByteSize`: ```c# -static MaxFileSize = ByteSize.FromMegaBytes(1.5); +var maxFileSize = ByteSize.FromMegaBytes(1.5); -// I have it in KBs! -MaxFileSize.KiloBytes; // 1536 +// I have it in KBs and KiBs!! +maxFileSize.KiloBytes; // 1500 +maxFileSize.KibiBytes; // 1464.84376 ``` -`ByteSize` behaves like any other struct backed by a numerical value. +`ByteSize` behaves like any other struct backed by a numerical value allowing arithmetic operations between two objects. ```c# // Add @@ -62,43 +94,68 @@ delta = delta.AddMegaBytes(-100); You can create a `ByteSize` object from `bits`, `bytes`, `kilobytes`, `megabytes`, `gigabytes`, and `terabytes`. ```c# -new ByteSize(1.5); // Constructor takes in bytes +new ByteSize(15); // Constructor takes in bits (long) +new ByteSize(1.5); // ... or bytes (double) // Static Constructors -ByteSize.FromBits(10); // Bits are whole numbers only +ByteSize.FromBits(10); // Same as constructor ByteSize.FromBytes(1.5); // Same as constructor + +// Decimal: 1 KB = 1000 B ByteSize.FromKiloBytes(1.5); ByteSize.FromMegaBytes(1.5); ByteSize.FromGigaBytes(1.5); ByteSize.FromTeraBytes(1.5); + +// Binary: 1 KiB = 1024 B +ByteSize.FromKibiBytes(1.5); +ByteSize.FromMebiBytes(1.5); +ByteSize.FromGibiBytes(1.5); +ByteSize.FromTebiBytes(1.5); ``` ### Properties -A `ByteSize` object contains representations in `bits`, `bytes`, `kilobytes`, `megabytes`, `gigabytes`, and `terabytes`. +A `ByteSize` object contains representations in: + +- `bits`, `bytes` +- `kilobytes`, `megabytes`, `gigabytes`, and `terabytes` +- `kibibytes`, `mebibytes`, `gibibytes`, and `tebibytes` ```c# var maxFileSize = ByteSize.FromKiloBytes(10); -maxFileSize.Bits; // 81920 -maxFileSize.Bytes; // 10240 +maxFileSize.Bits; // 80000 +maxFileSize.Bytes; // 10000 + +// Decimal maxFileSize.KiloBytes; // 10 -maxFileSize.MegaBytes; // 0.009765625 -maxFileSize.GigaBytes; // 9.53674316e-6 -maxFileSize.TeraBytes; // 9.31322575e-9 +maxFileSize.MegaBytes; // 0.01 +maxFileSize.GigaBytes; // 1E-05 +maxFileSize.TeraBytes; // 1E-08 + +// Binary +maxFileSize.KibiBytes; // 9.765625 +maxFileSize.MebiBytes; // 0.0095367431640625 +maxFileSize.GibiBytes; // 9.31322574615479E-06 +maxFileSize.TebiBytes; // 9.09494701772928E-09 ``` -A `ByteSize` object also contains two properties that represent the largest metric prefix symbol and value. +A `ByteSize` object also contains four properties that represent the largest whole number symbol and value. ```c# var maxFileSize = ByteSize.FromKiloBytes(10); -maxFileSize.LargestWholeNumberSymbol; // "KB" -maxFileSize.LargestWholeNumberValue; // 10 +maxFileSize.LargestWholeNumberDecimalSymbol; // "KB" +maxFileSize.LargestWholeNumberDecimalValue; // 10 +maxFileSize.LargestWholeNumberBinarySymbol; // "KiB" +maxFileSize.LargestWholeNumberBinaryValue; // 9.765625 ``` ### String Representation +By default a `ByteSize` object uses the decimal value for string representation. + All string operations are localized to use the number decimal separator of the culture set in `Thread.CurrentThread.CurrentCulture`. #### ToString @@ -106,20 +163,32 @@ All string operations are localized to use the number decimal separator of the c `ByteSize` comes with a handy `ToString` method that uses the largest metric prefix whose value is greater than or equal to 1. ```c# +// By default the decimal values are used ByteSize.FromBits(7).ToString(); // 7 b ByteSize.FromBits(8).ToString(); // 1 B -ByteSize.FromKiloBytes(.5).ToString(); // 512 B -ByteSize.FromKiloBytes(1000).ToString(); // 1000 KB -ByteSize.FromKiloBytes(1024).ToString(); // 1 MB -ByteSize.FromGigabytes(.5).ToString(); // 512 MB -ByteSize.FromGigabytes(1024).ToString(); // 1 TB +ByteSize.FromKiloBytes(.5).ToString(); // 500 B +ByteSize.FromKiloBytes(999).ToString(); // 999 KB +ByteSize.FromKiloBytes(1000).ToString(); // 1 MB +ByteSize.FromGigabytes(.5).ToString(); // 500 MB +ByteSize.FromGigabytes(1000).ToString(); // 1 TB + +// Binary +ByteSize.Parse("1.55 kb").ToString("kib"); // 1.51 kib ``` #### Formatting -The `ToString` method accepts a single `string` parameter to format the output. The formatter can contain the symbol of the value to display: `b`, `B`, `KB`, `MB`, `GB`, `TB`. The formatter uses the built in [`double.ToString` method](http://msdn.microsoft.com/en-us/library/kfsatb94\(v=vs.110\).aspx). +The `ToString` method accepts a single `string` parameter to format the output. +The formatter can contain the symbol of the value to display. + +- Base: `b`, `B` +- Decimal: `KB`, `MB`, `GB`, `TB` +- Binary: `KiB`, `MiB`, `GiB`, `TiB` -The default number format is `0.##` which rounds the number to two decimal places and outputs only `0` if the value is `0`. +The formatter uses the built in [`double.ToString` method](http://msdn.microsoft.com/en-us/library/kfsatb94\(v=vs.110\).aspx). + +The default number format is `0.##` which rounds the number to two decimal +places and outputs only `0` if the value is `0`. You can include symbol and number formats. @@ -154,11 +223,14 @@ zeroBytes.ToString("0.## mb"); // 0 mb `ByteSize` has a `Parse` and `TryParse` method similar to other base classes. -Like other `TryParse` methods, `ByteSize.TryParse` returns `boolean` value indicating whether or not the parsing was successful. If the value is parsed it is output to the `out` parameter supplied. +Like other `TryParse` methods, `ByteSize.TryParse` returns `boolean` +value indicating whether or not the parsing was successful. If the value is +parsed it is output to the `out` parameter supplied. ```c# ByteSize output; ByteSize.TryParse("1.5mb", out output); +ByteSize.TryParse("1.5mib", out output); // Invalid ByteSize.Parse("1.5 b"); // Can't have partial bits @@ -174,18 +246,17 @@ ByteSize.Parse("1.55 mB"); ByteSize.Parse("1.55 mb"); ByteSize.Parse("1.55 GB"); ByteSize.Parse("1.55 gB"); -ByteSize.Parse("1.55 gb"); -ByteSize.Parse("1.55 TB"); -ByteSize.Parse("1.55 tB"); -ByteSize.Parse("1.55 tb"); -ByteSize.Parse("1,55 kb"); // de-DE culture +ByteSize.Parse("1.55 gib"); +ByteSize.Parse("1.55 TiB"); +ByteSize.Parse("1.55 tiB"); +ByteSize.Parse("1.55 tib"); +ByteSize.Parse("1,55 kib"); // de-DE culture ``` #### Author and License Omar Khudeira ([http://omar.io](http://omar.io)) -Copyright (c) 2013-2016 Omar Khudeira. All rights reserved. +Copyright (c) 2013-2019 Omar Khudeira. All rights reserved. Released under MIT License (see LICENSE file). - diff --git a/docs/binary-byte.md b/docs/binary-byte.md new file mode 100644 index 0000000..95378f1 --- /dev/null +++ b/docs/binary-byte.md @@ -0,0 +1,68 @@ +# Binary Byte + +ByteSize started off with: + +- 1 kilobyte = 1024 bytes +- 2 letter abbreviation (KB, GB etc.) + +See why [here](https://omar.io/2017/01/16/when-technically-right-is-wrong-kilobytes.html). + +Since then, there have been a few requests to support the [IEC binary prefix](https://en.wikipedia.org/wiki/Binary_prefix#kibi), +see [issue#1](https://github.com/omar/ByteSize/issues/1) and [Humanizr/Humanizer/issues/592](https://github.com/Humanizr/Humanizer/issues/592). +This document will include the rational for the design of implementing the IEC standard. + +## Research + +| Vendor | Abbreviation | Ratio | [IEC Standard] | Source +| --- | --- | --- | --- | --- +| ByteSize | KB, GB, etc. | 1 = 1024 | No | +| GCP | KB, GB, etc. | 1 = 1024 | No | https://cloud.google.com/storage/pricing +| AWS | KB, GB, etc. | 1 = 1024 | No | https://aws.amazon.com/ebs/features/ +| AWS Glossary | KB, GB, etc. | 1 = 1000 | Yes | https://docs.aws.amazon.com/general/latest/gr/glos-chap.html +| AWS Glossary | KiB, GiB, etc. | 1 = 1024 | Yes | https://docs.aws.amazon.com/general/latest/gr/glos-chap.html +| Azure (RAM) | KiB, GiB, etc. | 1 = 1024 | Yes | https://azure.microsoft.com/en-gb/blog/largest-vm-in-the-cloud/ +| Azure (Disk) | KB, GB, etc. | 1 = 1000 | Yes | https://azure.microsoft.com/en-gb/blog/largest-vm-in-the-cloud/ +| Mac OS X Leopard + | KB, GB, etc. | 1 = 1000 | Yes | https://support.apple.com/en-us/HT201402 +| iOS 11 + | KB, GB, etc. | 1 = 1000 | Yes | https://support.apple.com/en-us/HT201402 + +In summary, developers are _attempting_ to adhere to the IEC standard by either: + +- Changing the abbreviation and ratio to match the IEC binary standard, or; +- Changing the ratio they've used in the past to match the decimal values + +[IEC Standard]: https://en.wikipedia.org/wiki/Binary_prefix#kibi + +## Implementation Considerations + +1. Two structs that represents the decimal (`DecimalByteSize`) and binary + values (`BinaryByteSize`). + - Pros + - Clear separation of what's being represented by the object. + - Solve the namespace collision that forced the `ByteSize` namespace. + - Cons + - Swapping between binary and decimal representation requires two + objects. Maybe can provide a simple way to go from one to the other: + `BinaryByteSize.FromDecimalByteSize` and + `DecimalByteSize.FromBinaryByteSize`. No idea how valuable or needed + this would be though. + - Breaking change since `BinaryByteSize` has the same ratio as the + original `ByteSize` class, but not the text representation. + (See "Backwards Compatibility Note" below). + - Backwards Compatibility Note + - v1.x of ByteSize could ship with two additional structs + `DecimalByteSize` and `BinaryByteSize` but keep `ByteSize` with it's + current implementation. + - v2.0 of ByteSize could break compatiblity and remove the `ByteSize` + struct in favor of `DecimalByteSize` and `BinaryByteSize`. + +2. Single struct + - Flat properties (e.g. `value.KiloByte` and `value.KibiByte`). + - Pros + - Single object that allows use of both standards. + - Cons + - Autocomplete lists will have twice as many methods and properties + - Nested properties (e.g. `value.Decimal.KiloByte` and `value.Binary.KibiByte`). + - Pros + - Single object that allows use of both standards. + - Cons + - Autocomplete lists will have twice as many methods and properties diff --git a/global.json b/global.json new file mode 100644 index 0000000..c6c08c9 --- /dev/null +++ b/global.json @@ -0,0 +1,8 @@ +{ + "projects": [ + "src" + ], + "sdk": { + "version": "2.1.700" + } +} diff --git a/src/ByteSizeLib.Tests/ArithmeticMethods.cs b/src/ByteSizeLib.Tests/ArithmeticMethods.cs index 1e0802f..1a1bca7 100644 --- a/src/ByteSizeLib.Tests/ArithmeticMethods.cs +++ b/src/ByteSizeLib.Tests/ArithmeticMethods.cs @@ -32,66 +32,6 @@ public void AddBytesMethod() Assert.Equal(16, size.Bits); } - [Fact] - public void AddKiloBytesMethod() - { - var size = ByteSize.FromKiloBytes(2).AddKiloBytes(2); - - Assert.Equal(4 * 1024 * 8, size.Bits); - Assert.Equal(4 * 1024, size.Bytes); - Assert.Equal(4, size.KiloBytes); - } - - [Fact] - public void AddMegaBytesMethod() - { - var size = ByteSize.FromMegaBytes(2).AddMegaBytes(2); - - Assert.Equal(4 * 1024 * 1024 * 8, size.Bits); - Assert.Equal(4 * 1024 * 1024, size.Bytes); - Assert.Equal(4 * 1024, size.KiloBytes); - Assert.Equal(4, size.MegaBytes); - } - - [Fact] - public void AddGigaBytesMethod() - { - var size = ByteSize.FromGigaBytes(2).AddGigaBytes(2); - - Assert.Equal(4d * 1024 * 1024 * 1024 * 8, size.Bits); - Assert.Equal(4d * 1024 * 1024 * 1024, size.Bytes); - Assert.Equal(4d * 1024 * 1024, size.KiloBytes); - Assert.Equal(4d * 1024, size.MegaBytes); - Assert.Equal(4d, size.GigaBytes); - } - - [Fact] - public void AddTeraBytesMethod() - { - var size = ByteSize.FromTeraBytes(2).AddTeraBytes(2); - - Assert.Equal(4d * 1024 * 1024 * 1024 * 1024 * 8, size.Bits); - Assert.Equal(4d * 1024 * 1024 * 1024 * 1024, size.Bytes); - Assert.Equal(4d * 1024 * 1024 * 1024, size.KiloBytes); - Assert.Equal(4d * 1024 * 1024, size.MegaBytes); - Assert.Equal(4d * 1024, size.GigaBytes); - Assert.Equal(4d, size.TeraBytes); - } - - [Fact] - public void AddPetaBytesMethod() - { - var size = ByteSize.FromPetaBytes(2).AddPetaBytes(2); - - Assert.Equal(4d * 1024 * 1024 * 1024 * 1024 * 1024 * 8, size.Bits); - Assert.Equal(4d * 1024 * 1024 * 1024 * 1024 * 1024, size.Bytes); - Assert.Equal(4d * 1024 * 1024 * 1024 * 1024, size.KiloBytes); - Assert.Equal(4d * 1024 * 1024 * 1024, size.MegaBytes); - Assert.Equal(4d * 1024 * 1024, size.GigaBytes); - Assert.Equal(4d * 1024, size.TeraBytes); - Assert.Equal(4d, size.PetaBytes); - } - [Fact] public void SubtractMethod() { @@ -141,5 +81,13 @@ public void DecrementOperator() Assert.Equal(8, size.Bits); Assert.Equal(1, size.Bytes); } + + [Fact] + public void MaxValueBits() + { + var size = ByteSize.FromBits(long.MaxValue); + + Assert.Equal(long.MaxValue, size.Bits); + } } } diff --git a/src/ByteSizeLib.Tests/Binary/ArithmeticMethods.cs b/src/ByteSizeLib.Tests/Binary/ArithmeticMethods.cs new file mode 100644 index 0000000..dff3ac0 --- /dev/null +++ b/src/ByteSizeLib.Tests/Binary/ArithmeticMethods.cs @@ -0,0 +1,67 @@ +using Xunit; + +namespace ByteSizeLib.Tests.Binary +{ + public class ArithmeticMethods + { + [Fact] + public void AddKibiBytesMethod() + { + var size = ByteSize.FromKibiBytes(2).AddKibiBytes(2); + + Assert.Equal(4 * 1024 * 8, size.Bits); + Assert.Equal(4 * 1024, size.Bytes); + Assert.Equal(4, size.KibiBytes); + } + + [Fact] + public void AddMebiBytesMethod() + { + var size = ByteSize.FromMebiBytes(2).AddMebiBytes(2); + + Assert.Equal(4 * 1024 * 1024 * 8, size.Bits); + Assert.Equal(4 * 1024 * 1024, size.Bytes); + Assert.Equal(4 * 1024, size.KibiBytes); + Assert.Equal(4, size.MebiBytes); + } + + [Fact] + public void AddGibiBytesMethod() + { + var size = ByteSize.FromGibiBytes(2).AddGibiBytes(2); + + Assert.Equal(4d * 1024 * 1024 * 1024 * 8, size.Bits); + Assert.Equal(4d * 1024 * 1024 * 1024, size.Bytes); + Assert.Equal(4d * 1024 * 1024, size.KibiBytes); + Assert.Equal(4d * 1024, size.MebiBytes); + Assert.Equal(4d, size.GibiBytes); + } + + [Fact] + public void AddTebiBytesMethod() + { + var size = ByteSize.FromTebiBytes(2).AddTebiBytes(2); + + Assert.Equal(4d * 1024 * 1024 * 1024 * 1024 * 8, size.Bits); + Assert.Equal(4d * 1024 * 1024 * 1024 * 1024, size.Bytes); + Assert.Equal(4d * 1024 * 1024 * 1024, size.KibiBytes); + Assert.Equal(4d * 1024 * 1024, size.MebiBytes); + Assert.Equal(4d * 1024, size.GibiBytes); + Assert.Equal(4d, size.TebiBytes); + } + + [Fact] + public void AddPebiBytesMethod() + { + var size = ByteSize.FromPebiBytes(2).AddPebiBytes(2); + + Assert.Equal(4d * 1024 * 1024 * 1024 * 1024 * 1024 * 8, size.Bits); + Assert.Equal(4d * 1024 * 1024 * 1024 * 1024 * 1024, size.Bytes); + Assert.Equal(4d * 1024 * 1024 * 1024 * 1024, size.KibiBytes); + Assert.Equal(4d * 1024 * 1024 * 1024, size.MebiBytes); + Assert.Equal(4d * 1024 * 1024, size.GibiBytes); + Assert.Equal(4d * 1024, size.TebiBytes); + Assert.Equal(4d, size.PebiBytes); + } + } +} diff --git a/src/ByteSizeLib.Tests/Binary/CreatingMethods.cs b/src/ByteSizeLib.Tests/Binary/CreatingMethods.cs new file mode 100644 index 0000000..e8bd74b --- /dev/null +++ b/src/ByteSizeLib.Tests/Binary/CreatingMethods.cs @@ -0,0 +1,96 @@ +using Xunit; + +namespace ByteSizeLib.Tests.BinaryByteSizeTests +{ + public class CreatingMethods + { + [Fact] + public void Constructor() + { + // Arrange + double bytes = 1125899906842624; + + // Act + var result = new ByteSize(bytes); + + // Assert + Assert.Equal(bytes * 8, result.Bits); + Assert.Equal(bytes, result.Bytes); + Assert.Equal(bytes / 1024, result.KibiBytes); + Assert.Equal(bytes / 1024 / 1024, result.MebiBytes); + Assert.Equal(bytes / 1024 / 1024 / 1024, result.GibiBytes); + Assert.Equal(bytes / 1024 / 1024 / 1024 / 1024, result.TebiBytes); + Assert.Equal(1, result.PebiBytes); + } + + [Fact] + public void FromKibiBytesMethod() + { + // Arrange + double value = 1.5; + + // Act + var result = ByteSize.FromKibiBytes(value); + + // Assert + Assert.Equal(1536, result.Bytes); + Assert.Equal(1.5, result.KibiBytes); + } + + [Fact] + public void FromMebiBytesMethod() + { + // Arrange + double value = 1.5; + + // Act + var result = ByteSize.FromMebiBytes(value); + + // Assert + Assert.Equal(1572864, result.Bytes); + Assert.Equal(1.5, result.MebiBytes); + } + + [Fact] + public void FromGibiBytesMethod() + { + // Arrange + double value = 1.5; + + // Act + var result = ByteSize.FromGibiBytes(value); + + // Assert + Assert.Equal(1610612736, result.Bytes); + Assert.Equal(1.5, result.GibiBytes); + } + + [Fact] + public void FromTebiBytesMethod() + { + // Arrange + double value = 1.5; + + // Act + var result = ByteSize.FromTebiBytes(value); + + // Assert + Assert.Equal(1649267441664, result.Bytes); + Assert.Equal(1.5, result.TebiBytes); + } + + [Fact] + public void FromPebiBytesMethod() + { + // Arrange + double value = 1.5; + + // Act + var result = ByteSize.FromPebiBytes(value); + + // Assert + Assert.Equal(1688849860263936, result.Bytes); + Assert.Equal(1.5, result.PebiBytes); + } + } +} diff --git a/src/ByteSizeLib.Tests/Binary/ParsingMethods.cs b/src/ByteSizeLib.Tests/Binary/ParsingMethods.cs new file mode 100644 index 0000000..4dfb442 --- /dev/null +++ b/src/ByteSizeLib.Tests/Binary/ParsingMethods.cs @@ -0,0 +1,65 @@ +using System; +using System.Globalization; +using System.Threading; +using Xunit; + +namespace ByteSizeLib.Tests.BinaryByteSizeTests +{ + public class ParsingMethods + { + [Fact] + public void ParseKiB() + { + string val = "1020KiB"; + var expected = ByteSize.FromKibiBytes(1020); + + var result = ByteSize.Parse(val); + + Assert.Equal(expected, result); + } + + [Fact] + public void ParseMiB() + { + string val = "1000MiB"; + var expected = ByteSize.FromMebiBytes(1000); + + var result = ByteSize.Parse(val); + + Assert.Equal(expected, result); + } + + [Fact] + public void ParseGiB() + { + string val = "805GiB"; + var expected = ByteSize.FromGibiBytes(805); + + var result = ByteSize.Parse(val); + + Assert.Equal(expected, result); + } + + [Fact] + public void ParseTiB() + { + string val = "100TiB"; + var expected = ByteSize.FromTebiBytes(100); + + var result = ByteSize.Parse(val); + + Assert.Equal(expected, result); + } + + [Fact] + public void ParsePiB() + { + string val = "100PiB"; + var expected = ByteSize.FromPebiBytes(100); + + var result = ByteSize.Parse(val); + + Assert.Equal(expected, result); + } + } +} diff --git a/src/ByteSizeLib.Tests/Binary/ToBinaryStringMethod.cs b/src/ByteSizeLib.Tests/Binary/ToBinaryStringMethod.cs new file mode 100644 index 0000000..3fc2cbe --- /dev/null +++ b/src/ByteSizeLib.Tests/Binary/ToBinaryStringMethod.cs @@ -0,0 +1,21 @@ +using Xunit; + +namespace ByteSizeLib.Tests.BinaryByteSizeTests +{ + public class ToBinaryStringMethod + { + + [Fact] + public void ReturnsDefaultRepresenation() + { + // Arrange + var b = ByteSize.FromKiloBytes(10); + + // Act + var result = b.ToBinaryString(); + + // Assert + Assert.Equal("9.77 KiB", result); + } + } +} diff --git a/src/ByteSizeLib.Tests/Binary/ToStringMethod.cs b/src/ByteSizeLib.Tests/Binary/ToStringMethod.cs new file mode 100644 index 0000000..15c4dd3 --- /dev/null +++ b/src/ByteSizeLib.Tests/Binary/ToStringMethod.cs @@ -0,0 +1,87 @@ +using System.Globalization; +using System.Threading; +using Xunit; + +namespace ByteSizeLib.Tests.BinaryByteSizeTests +{ + public class ToStringMethod + { + [Fact] + public void ReturnsKibiBytes() + { + // Arrange + var b = ByteSize.FromKibiBytes(10); + + // Act + var result = b.ToString("##.#### KiB"); + + // Assert + Assert.Equal("10 KiB", result); + } + + [Fact] + public void ReturnsMebiBytes() + { + // Arrange + var b = ByteSize.FromMebiBytes(10); + + // Act + var result = b.ToString("##.#### MiB"); + + // Assert + Assert.Equal("10 MiB", result); + } + + [Fact] + public void ReturnsGibiBytes() + { + // Arrange + var b = ByteSize.FromGibiBytes(10); + + // Act + var result = b.ToString("##.#### GiB"); + + // Assert + Assert.Equal("10 GiB", result); + } + + [Fact] + public void ReturnsTebiBytes() + { + // Arrange + var b = ByteSize.FromTebiBytes(10); + + // Act + var result = b.ToString("##.#### TiB"); + + // Assert + Assert.Equal("10 TiB", result); + } + + [Fact] + public void ReturnsPebiBytes() + { + // Arrange + var b = ByteSize.FromPebiBytes(10); + + // Act + var result = b.ToString("##.#### PiB"); + + // Assert + Assert.Equal("10 PiB", result); + } + + [Fact] + public void ReturnsSelectedFormat() + { + // Arrange + var b = ByteSize.FromTebiBytes(10); + + // Act + var result = b.ToString("0.0 TiB"); + + // Assert + Assert.Equal(10.ToString("0.0 TiB"), result); + } + } +} diff --git a/src/ByteSizeLib.Tests/ByteSizeLib.Tests.csproj b/src/ByteSizeLib.Tests/ByteSizeLib.Tests.csproj old mode 100644 new mode 100755 index b7b089d..e5a5676 --- a/src/ByteSizeLib.Tests/ByteSizeLib.Tests.csproj +++ b/src/ByteSizeLib.Tests/ByteSizeLib.Tests.csproj @@ -1,18 +1,20 @@ - + netcoreapp2.1 - true + + false - - - + - + + + + - \ No newline at end of file + diff --git a/src/ByteSizeLib.Tests/CreatingMethods.cs b/src/ByteSizeLib.Tests/CreatingMethods.cs index 066ebd2..2749f35 100644 --- a/src/ByteSizeLib.Tests/CreatingMethods.cs +++ b/src/ByteSizeLib.Tests/CreatingMethods.cs @@ -4,25 +4,6 @@ namespace ByteSizeLib.Tests { public class CreatingMethods { - [Fact] - public void Constructor() - { - // Arrange - double byteSize = 1125899906842624; - - // Act - var result = new ByteSize(byteSize); - - // Assert - Assert.Equal(byteSize * 8, result.Bits); - Assert.Equal(byteSize, result.Bytes); - Assert.Equal(byteSize / 1024, result.KiloBytes); - Assert.Equal(byteSize / 1024 / 1024, result.MegaBytes); - Assert.Equal(byteSize / 1024 / 1024 / 1024, result.GigaBytes); - Assert.Equal(byteSize / 1024 / 1024 / 1024 / 1024, result.TeraBytes); - Assert.Equal(1, result.PetaBytes); - } - [Fact] public void FromBitsMethod() { @@ -50,75 +31,5 @@ public void FromBytesMethod() Assert.Equal(12, result.Bits); Assert.Equal(1.5, result.Bytes); } - - [Fact] - public void FromKiloBytesMethod() - { - // Arrange - double value = 1.5; - - // Act - var result = ByteSize.FromKiloBytes(value); - - // Assert - Assert.Equal(1536, result.Bytes); - Assert.Equal(1.5, result.KiloBytes); - } - - [Fact] - public void FromMegaBytesMethod() - { - // Arrange - double value = 1.5; - - // Act - var result = ByteSize.FromMegaBytes(value); - - // Assert - Assert.Equal(1572864, result.Bytes); - Assert.Equal(1.5, result.MegaBytes); - } - - [Fact] - public void FromGigaBytesMethod() - { - // Arrange - double value = 1.5; - - // Act - var result = ByteSize.FromGigaBytes(value); - - // Assert - Assert.Equal(1610612736, result.Bytes); - Assert.Equal(1.5, result.GigaBytes); - } - - [Fact] - public void FromTeraBytesMethod() - { - // Arrange - double value = 1.5; - - // Act - var result = ByteSize.FromTeraBytes(value); - - // Assert - Assert.Equal(1649267441664, result.Bytes); - Assert.Equal(1.5, result.TeraBytes); - } - - [Fact] - public void FromPetaBytesMethod() - { - // Arrange - double value = 1.5; - - // Act - var result = ByteSize.FromPetaBytes(value); - - // Assert - Assert.Equal(1688849860263936, result.Bytes); - Assert.Equal(1.5, result.PetaBytes); - } } } diff --git a/src/ByteSizeLib.Tests/Decimal/ArithmeticMethods.cs b/src/ByteSizeLib.Tests/Decimal/ArithmeticMethods.cs new file mode 100644 index 0000000..7d00c2e --- /dev/null +++ b/src/ByteSizeLib.Tests/Decimal/ArithmeticMethods.cs @@ -0,0 +1,67 @@ +using Xunit; + +namespace ByteSizeLib.Tests.Decimal +{ + public class ArithmeticMethods + { + [Fact] + public void AddKiloBytesMethod() + { + var size = ByteSize.FromKiloBytes(2).AddKiloBytes(2); + + Assert.Equal(4 * 1000 * 8, size.Bits); + Assert.Equal(4 * 1000, size.Bytes); + Assert.Equal(4, size.KiloBytes); + } + + [Fact] + public void AddMegaBytesMethod() + { + var size = ByteSize.FromMegaBytes(2).AddMegaBytes(2); + + Assert.Equal(4 * 1000 * 1000 * 8, size.Bits); + Assert.Equal(4 * 1000 * 1000, size.Bytes); + Assert.Equal(4 * 1000, size.KiloBytes); + Assert.Equal(4, size.MegaBytes); + } + + [Fact] + public void AddGigaBytesMethod() + { + var size = ByteSize.FromGigaBytes(2).AddGigaBytes(2); + + Assert.Equal(4d * 1000 * 1000 * 1000 * 8, size.Bits); + Assert.Equal(4d * 1000 * 1000 * 1000, size.Bytes); + Assert.Equal(4d * 1000 * 1000, size.KiloBytes); + Assert.Equal(4d * 1000, size.MegaBytes); + Assert.Equal(4d, size.GigaBytes); + } + + [Fact] + public void AddTeraBytesMethod() + { + var size = ByteSize.FromTeraBytes(2).AddTeraBytes(2); + + Assert.Equal(4d * 1000 * 1000 * 1000 * 1000 * 8, size.Bits); + Assert.Equal(4d * 1000 * 1000 * 1000 * 1000, size.Bytes); + Assert.Equal(4d * 1000 * 1000 * 1000, size.KiloBytes); + Assert.Equal(4d * 1000 * 1000, size.MegaBytes); + Assert.Equal(4d * 1000, size.GigaBytes); + Assert.Equal(4d, size.TeraBytes); + } + + [Fact] + public void AddPetaBytesMethod() + { + var size = ByteSize.FromPetaBytes(2).AddPetaBytes(2); + + Assert.Equal(4d * 1000 * 1000 * 1000 * 1000 * 1000 * 8, size.Bits); + Assert.Equal(4d * 1000 * 1000 * 1000 * 1000 * 1000, size.Bytes); + Assert.Equal(4d * 1000 * 1000 * 1000 * 1000, size.KiloBytes); + Assert.Equal(4d * 1000 * 1000 * 1000, size.MegaBytes); + Assert.Equal(4d * 1000 * 1000, size.GigaBytes); + Assert.Equal(4d * 1000, size.TeraBytes); + Assert.Equal(4d, size.PetaBytes); + } + } +} diff --git a/src/ByteSizeLib.Tests/Decimal/CreatingMethods.cs b/src/ByteSizeLib.Tests/Decimal/CreatingMethods.cs new file mode 100644 index 0000000..199c4df --- /dev/null +++ b/src/ByteSizeLib.Tests/Decimal/CreatingMethods.cs @@ -0,0 +1,96 @@ +using Xunit; + +namespace ByteSizeLib.Tests.Decimal +{ + public class CreatingMethods + { + [Fact] + public void Constructor() + { + // Arrange + double bytes = 1000000000000000; + + // Act + var result = new ByteSize(bytes); + + // Assert + Assert.Equal(bytes * 8, result.Bits); + Assert.Equal(bytes, result.Bytes); + Assert.Equal(bytes / 1000, result.KiloBytes); + Assert.Equal(bytes / 1000 / 1000, result.MegaBytes); + Assert.Equal(bytes / 1000 / 1000 / 1000, result.GigaBytes); + Assert.Equal(bytes / 1000 / 1000 / 1000 / 1000, result.TeraBytes); + Assert.Equal(1, result.PetaBytes); + } + + [Fact] + public void FromKiloBytesMethod() + { + // Arrange + double value = 1.5; + + // Act + var result = ByteSize.FromKiloBytes(value); + + // Assert + Assert.Equal(1500, result.Bytes); + Assert.Equal(1.5, result.KiloBytes); + } + + [Fact] + public void FromMegaBytesMethod() + { + // Arrange + double value = 1.5; + + // Act + var result = ByteSize.FromMegaBytes(value); + + // Assert + Assert.Equal(1500000, result.Bytes); + Assert.Equal(1.5, result.MegaBytes); + } + + [Fact] + public void FromGigaBytesMethod() + { + // Arrange + double value = 1.5; + + // Act + var result = ByteSize.FromGigaBytes(value); + + // Assert + Assert.Equal(1500000000, result.Bytes); + Assert.Equal(1.5, result.GigaBytes); + } + + [Fact] + public void FromTeraBytesMethod() + { + // Arrange + double value = 1.5; + + // Act + var result = ByteSize.FromTeraBytes(value); + + // Assert + Assert.Equal(1500000000000, result.Bytes); + Assert.Equal(1.5, result.TeraBytes); + } + + [Fact] + public void FromPetaBytesMethod() + { + // Arrange + double value = 1.5; + + // Act + var result = ByteSize.FromPetaBytes(value); + + // Assert + Assert.Equal(1500000000000000, result.Bytes); + Assert.Equal(1.5, result.PetaBytes); + } + } +} diff --git a/src/ByteSizeLib.Tests/Decimal/ParsingMethods.cs b/src/ByteSizeLib.Tests/Decimal/ParsingMethods.cs new file mode 100644 index 0000000..f8f86ac --- /dev/null +++ b/src/ByteSizeLib.Tests/Decimal/ParsingMethods.cs @@ -0,0 +1,79 @@ +using System; +using System.Globalization; +using System.Threading; +using Xunit; + +namespace ByteSizeLib.Tests.Decimal +{ + public class ParsingMethods + { + [Fact] + public void ParseKB() + { + string val = "1020KB"; + var expected = ByteSize.FromKiloBytes(1020); + + var result = ByteSize.Parse(val); + + Assert.Equal(expected, result); + } + + [Fact] + public void ParseMB() + { + string val = "1000MB"; + var expected = ByteSize.FromMegaBytes(1000); + + var result = ByteSize.Parse(val); + + Assert.Equal(expected, result); + } + + [Fact] + public void ParseGB() + { + string val = "805GB"; + var expected = ByteSize.FromGigaBytes(805); + + var result = ByteSize.Parse(val); + + Assert.Equal(expected, result); + } + + [Fact] + public void ParseTB() + { + string val = "100TB"; + var expected = ByteSize.FromTeraBytes(100); + + var result = ByteSize.Parse(val); + + Assert.Equal(expected, result); + } + + [Fact] + public void ParsePB() + { + string val = "100PB"; + var expected = ByteSize.FromPetaBytes(100); + + var result = ByteSize.Parse(val); + + Assert.Equal(expected, result); + } + + [Fact] + public void ParseCultureNumberSeparator() + { + CultureInfo.CurrentCulture = new CultureInfo("de-DE"); + string val = "1.500,5 MB"; + var expected = ByteSize.FromMegaBytes(1500.5); + + var result = ByteSize.Parse(val); + + Assert.Equal(expected, result); + + CultureInfo.CurrentCulture = new CultureInfo("en-US"); + } + } +} diff --git a/src/ByteSizeLib.Tests/Decimal/ToStringMethod.cs b/src/ByteSizeLib.Tests/Decimal/ToStringMethod.cs new file mode 100644 index 0000000..7bdf060 --- /dev/null +++ b/src/ByteSizeLib.Tests/Decimal/ToStringMethod.cs @@ -0,0 +1,236 @@ +using System.Globalization; +using System.Threading; +using Xunit; + +namespace ByteSizeLib.Tests.Decimal +{ + public class ToStringMethod + { + [Fact] + public void ReturnsLargestSuffix() + { + // Arrange + var b = ByteSize.FromKiloBytes(10.5); + + // Act + var result = b.ToString(); + + // Assert + Assert.Equal(10.5.ToString("0.0 KB"), result); + } + + [Fact] + public void ReturnsDefaultNumberFormat() + { + // Arrange + var b = ByteSize.FromKiloBytes(10.5); + + // Act + var result = b.ToString("KB"); + + // Assert + Assert.Equal(10.5.ToString("0.0 KB"), result); + } + + [Fact] + public void ReturnsProvidedNumberFormat() + { + // Arrange + var b = ByteSize.FromKiloBytes(10.1234); + + // Act + var result = b.ToString("#.#### KB"); + + // Assert + Assert.Equal(10.1234.ToString("0.0000 KB"), result); + } + + [Fact] + public void ReturnsBits() + { + // Arrange + var b = ByteSize.FromBits(10); + + // Act + var result = b.ToString("##.#### b"); + + // Assert + Assert.Equal("10 b", result); + } + + [Fact] + public void ReturnsBytes() + { + // Arrange + var b = ByteSize.FromBytes(10); + + // Act + var result = b.ToString("##.#### B"); + + // Assert + Assert.Equal("10 B", result); + } + + [Fact] + public void ReturnsKiloBytes() + { + // Arrange + var b = ByteSize.FromKiloBytes(10); + + // Act + var result = b.ToString("##.#### KB"); + + // Assert + Assert.Equal("10 KB", result); + } + + [Fact] + public void ReturnsMegaBytes() + { + // Arrange + var b = ByteSize.FromMegaBytes(10); + + // Act + var result = b.ToString("##.#### MB"); + + // Assert + Assert.Equal("10 MB", result); + } + + [Fact] + public void ReturnsGigaBytes() + { + // Arrange + var b = ByteSize.FromGigaBytes(10); + + // Act + var result = b.ToString("##.#### GB"); + + // Assert + Assert.Equal("10 GB", result); + } + + [Fact] + public void ReturnsTeraBytes() + { + // Arrange + var b = ByteSize.FromTeraBytes(10); + + // Act + var result = b.ToString("##.#### TB"); + + // Assert + Assert.Equal("10 TB", result); + } + + [Fact] + public void ReturnsPetaBytes() + { + // Arrange + var b = ByteSize.FromPetaBytes(10); + + // Act + var result = b.ToString("##.#### PB"); + + // Assert + Assert.Equal("10 PB", result); + } + + [Fact] + public void ReturnsSelectedFormat() + { + // Arrange + var b = ByteSize.FromTeraBytes(10); + + // Act + var result = b.ToString("0.0 TB"); + + // Assert + Assert.Equal(10.ToString("0.0 TB"), result); + } + + [Fact] + public void ReturnsLargestPrefixLargerThanZero() + { + // Arrange + var b = ByteSize.FromMegaBytes(.5); + + // Act + var result = b.ToString("#.#"); + + // Assert + Assert.Equal("500 KB", result); + } + + [Fact] + public void ReturnsLargestPrefixLargerThanZeroForNegativeValues() + { + // Arrange + var b = ByteSize.FromMegaBytes(-.5); + + // Act + var result = b.ToString("#.#"); + + // Assert + Assert.Equal("-500 KB", result); + } + + [Fact] + public void ReturnsLargestSuffixUsingCurrentCulture() + { + var originalCulture = CultureInfo.CurrentCulture; + CultureInfo.CurrentCulture = new CultureInfo("fr-FR"); + + // Arrange + var b = ByteSize.FromKiloBytes(9770); + + // Act + var result = b.ToString(); + + // Assert + Assert.Equal("9,77 MB", result); + + CultureInfo.CurrentCulture = originalCulture; + } + + [Fact] + public void ReturnsLargestSuffixUsingSpecifiedCulture() + { + // Arrange + var b = ByteSize.FromKiloBytes(9800); + + // Act + var result = b.ToString("#.#", new CultureInfo("fr-FR")); + + // Assert + Assert.Equal("9,8 MB", result); + } + + [Fact] + public void ReturnsCultureSpecificFormat() + { + // Arrange + var b = ByteSize.FromKiloBytes(10.5); + + // Act + var deCulture = new CultureInfo("de-DE"); + var result = b.ToString("0.0 KB", deCulture); + + // Assert + Assert.Equal(10.5.ToString("0.0 KB", deCulture), result); + } + + [Fact] + public void ReturnsZeroBits() + { + // Arrange + var b = ByteSize.FromBits(0); + + // Act + var result = b.ToString(); + + // Assert + Assert.Equal("0 b", result); + } + } +} diff --git a/src/ByteSizeLib.Tests/ParsingMethods.cs b/src/ByteSizeLib.Tests/ParsingMethods.cs index 9cbe04a..a8d4e50 100644 --- a/src/ByteSizeLib.Tests/ParsingMethods.cs +++ b/src/ByteSizeLib.Tests/ParsingMethods.cs @@ -11,8 +11,8 @@ public class ParsingMethods [Fact] public void Parse() { - string val = "1020KB"; - var expected = ByteSize.FromKiloBytes(1020); + string val = "1020KiB"; + var expected = ByteSize.FromKibiBytes(1020); var result = ByteSize.Parse(val); @@ -22,14 +22,14 @@ public void Parse() [Fact] public void TryParse() { - string val = "1020KB"; - var expected = ByteSize.FromKiloBytes(1020); + string val = "1020KiB"; + var expected = ByteSize.FromKibiBytes(1020); - ByteSize resultByteSize; - var resultBool = ByteSize.TryParse(val, out resultByteSize); + ByteSize resultBinaryByteSize; + var resultBool = ByteSize.TryParse(val, out resultBinaryByteSize); Assert.True(resultBool); - Assert.Equal(expected, resultByteSize); + Assert.Equal(expected, resultBinaryByteSize); } [Fact] @@ -49,11 +49,11 @@ public void TryParseReturnsFalseOnBadValue() { string val = "Unexpected Value"; - ByteSize resultByteSize; - var resultBool = ByteSize.TryParse(val, out resultByteSize); + ByteSize resultBinaryByteSize; + var resultBool = ByteSize.TryParse(val, out resultBinaryByteSize); Assert.False(resultBool); - Assert.Equal(new ByteSize(), resultByteSize); + Assert.Equal(new ByteSize(), resultBinaryByteSize); } [Fact] @@ -61,30 +61,30 @@ public void TryParseReturnsFalseOnMissingMagnitude() { string val = "1000"; - ByteSize resultByteSize; - var resultBool = ByteSize.TryParse(val, out resultByteSize); + ByteSize resultBinaryByteSize; + var resultBool = ByteSize.TryParse(val, out resultBinaryByteSize); Assert.False(resultBool); - Assert.Equal(new ByteSize(), resultByteSize); + Assert.Equal(new ByteSize(), resultBinaryByteSize); } [Fact] public void TryParseReturnsFalseOnMissingValue() { - string val = "KB"; + string val = "KiB"; - ByteSize resultByteSize; - var resultBool = ByteSize.TryParse(val, out resultByteSize); + ByteSize resultBinaryByteSize; + var resultBool = ByteSize.TryParse(val, out resultBinaryByteSize); Assert.False(resultBool); - Assert.Equal(new ByteSize(), resultByteSize); + Assert.Equal(new ByteSize(), resultBinaryByteSize); } [Fact] public void TryParseWorksWithLotsOfSpaces() { - string val = " 100 KB "; - var expected = ByteSize.FromKiloBytes(100); + string val = " 100 KiB "; + var expected = ByteSize.FromKibiBytes(100); var result = ByteSize.Parse(val); @@ -102,7 +102,6 @@ public void ParsePartialBits() }); } - // Parse method throws exceptions [Fact] public void ParseThrowsOnInvalid() @@ -148,67 +147,12 @@ public void ParseBytes() Assert.Equal(expected, result); } - [Fact] - public void ParseKB() - { - string val = "1020KB"; - var expected = ByteSize.FromKiloBytes(1020); - - var result = ByteSize.Parse(val); - - Assert.Equal(expected, result); - } - - [Fact] - public void ParseMB() - { - string val = "1000MB"; - var expected = ByteSize.FromMegaBytes(1000); - - var result = ByteSize.Parse(val); - - Assert.Equal(expected, result); - } - - [Fact] - public void ParseGB() - { - string val = "805GB"; - var expected = ByteSize.FromGigaBytes(805); - - var result = ByteSize.Parse(val); - - Assert.Equal(expected, result); - } - - [Fact] - public void ParseTB() - { - string val = "100TB"; - var expected = ByteSize.FromTeraBytes(100); - - var result = ByteSize.Parse(val); - - Assert.Equal(expected, result); - } - - [Fact] - public void ParsePB() - { - string val = "100PB"; - var expected = ByteSize.FromPetaBytes(100); - - var result = ByteSize.Parse(val); - - Assert.Equal(expected, result); - } - [Fact] public void ParseCultureNumberSeparator() { CultureInfo.CurrentCulture = new CultureInfo("de-DE"); - string val = "1.500,5 MB"; - var expected = ByteSize.FromMegaBytes(1500.5); + string val = "1.500,5 MiB"; + var expected = ByteSize.FromMebiBytes(1500.5); var result = ByteSize.Parse(val); diff --git a/src/ByteSizeLib.Tests/ToStringMethod.cs b/src/ByteSizeLib.Tests/ToStringMethod.cs index b309618..ce4b890 100644 --- a/src/ByteSizeLib.Tests/ToStringMethod.cs +++ b/src/ByteSizeLib.Tests/ToStringMethod.cs @@ -7,7 +7,7 @@ namespace ByteSizeLib.Tests public class ToStringMethod { [Fact] - public void ReturnsLargestMetricSuffix() + public void ReturnsLargestSuffix() { // Arrange var b = ByteSize.FromKiloBytes(10.5); @@ -71,71 +71,6 @@ public void ReturnsBytes() Assert.Equal("10 B", result); } - [Fact] - public void ReturnsKiloBytes() - { - // Arrange - var b = ByteSize.FromKiloBytes(10); - - // Act - var result = b.ToString("##.#### KB"); - - // Assert - Assert.Equal("10 KB", result); - } - - [Fact] - public void ReturnsMegaBytes() - { - // Arrange - var b = ByteSize.FromMegaBytes(10); - - // Act - var result = b.ToString("##.#### MB"); - - // Assert - Assert.Equal("10 MB", result); - } - - [Fact] - public void ReturnsGigaBytes() - { - // Arrange - var b = ByteSize.FromGigaBytes(10); - - // Act - var result = b.ToString("##.#### GB"); - - // Assert - Assert.Equal("10 GB", result); - } - - [Fact] - public void ReturnsTeraBytes() - { - // Arrange - var b = ByteSize.FromTeraBytes(10); - - // Act - var result = b.ToString("##.#### TB"); - - // Assert - Assert.Equal("10 TB", result); - } - - [Fact] - public void ReturnsPetaBytes() - { - // Arrange - var b = ByteSize.FromPetaBytes(10); - - // Act - var result = b.ToString("##.#### PB"); - - // Assert - Assert.Equal("10 PB", result); - } - [Fact] public void ReturnsSelectedFormat() { @@ -150,7 +85,7 @@ public void ReturnsSelectedFormat() } [Fact] - public void ReturnsLargestMetricPrefixLargerThanZero() + public void ReturnsLargestPrefixLargerThanZero() { // Arrange var b = ByteSize.FromMegaBytes(.5); @@ -159,11 +94,11 @@ public void ReturnsLargestMetricPrefixLargerThanZero() var result = b.ToString("#.#"); // Assert - Assert.Equal("512 KB", result); + Assert.Equal("500 KB", result); } [Fact] - public void ReturnsLargestMetricPrefixLargerThanZeroForNegativeValues() + public void ReturnsLargestPrefixLargerThanZeroForNegativeValues() { // Arrange var b = ByteSize.FromMegaBytes(-.5); @@ -172,17 +107,17 @@ public void ReturnsLargestMetricPrefixLargerThanZeroForNegativeValues() var result = b.ToString("#.#"); // Assert - Assert.Equal("-512 KB", result); + Assert.Equal("-500 KB", result); } [Fact] - public void ReturnsLargestMetricSuffixUsingCurrentCulture() + public void ReturnsLargestSuffixUsingCurrentCulture() { var originalCulture = CultureInfo.CurrentCulture; CultureInfo.CurrentCulture = new CultureInfo("fr-FR"); // Arrange - var b = ByteSize.FromKiloBytes(10000); + var b = ByteSize.FromKiloBytes(9770); // Act var result = b.ToString(); @@ -194,10 +129,10 @@ public void ReturnsLargestMetricSuffixUsingCurrentCulture() } [Fact] - public void ReturnsLargestMetricSuffixUsingSpecifiedCulture() + public void ReturnsLargestSuffixUsingSpecifiedCulture() { // Arrange - var b = ByteSize.FromKiloBytes(10000); + var b = ByteSize.FromKiloBytes(9800); // Act var result = b.ToString("#.#", new CultureInfo("fr-FR")); diff --git a/src/ByteSizeLib/BinaryByteSize.cs b/src/ByteSizeLib/BinaryByteSize.cs new file mode 100644 index 0000000..dd13efa --- /dev/null +++ b/src/ByteSizeLib/BinaryByteSize.cs @@ -0,0 +1,81 @@ +using System; +using System.Globalization; + +namespace ByteSizeLib +{ + public partial struct ByteSize + { + public const long BytesInKibiByte = 1_024; + public const long BytesInMebiByte = 1_048_576; + public const long BytesInGibiByte = 1_073_741_824; + public const long BytesInTebiByte = 1_099_511_627_776; + public const long BytesInPebiByte = 1_125_899_906_842_624; + + public const string KibiByteSymbol = "KiB"; + public const string MebiByteSymbol = "MiB"; + public const string GibiByteSymbol = "GiB"; + public const string TebiByteSymbol = "TiB"; + public const string PebiByteSymbol = "PiB"; + + public double KibiBytes => Bytes / BytesInKibiByte; + public double MebiBytes => Bytes / BytesInMebiByte; + public double GibiBytes => Bytes / BytesInGibiByte; + public double TebiBytes => Bytes / BytesInTebiByte; + public double PebiBytes => Bytes / BytesInPebiByte; + + public static ByteSize FromKibiBytes(double value) + { + return new ByteSize(value * BytesInKibiByte); + } + + public static ByteSize FromMebiBytes(double value) + { + return new ByteSize(value * BytesInMebiByte); + } + + public static ByteSize FromGibiBytes(double value) + { + return new ByteSize(value * BytesInGibiByte); + } + + public static ByteSize FromTebiBytes(double value) + { + return new ByteSize(value * BytesInTebiByte); + } + + public static ByteSize FromPebiBytes(double value) + { + return new ByteSize(value * BytesInPebiByte); + } + + public ByteSize AddKibiBytes(double value) + { + return this + ByteSize.FromKibiBytes(value); + } + + public ByteSize AddMebiBytes(double value) + { + return this + ByteSize.FromMebiBytes(value); + } + + public ByteSize AddGibiBytes(double value) + { + return this + ByteSize.FromGibiBytes(value); + } + + public ByteSize AddTebiBytes(double value) + { + return this + ByteSize.FromTebiBytes(value); + } + + public ByteSize AddPebiBytes(double value) + { + return this + ByteSize.FromPebiBytes(value); + } + + public string ToBinaryString() + { + return this.ToString("0.##", CultureInfo.CurrentCulture, useBinaryByte: true); + } + } +} diff --git a/src/ByteSizeLib/ByteSize.cs b/src/ByteSizeLib/ByteSize.cs index ab0729f..c199a6e 100644 --- a/src/ByteSizeLib/ByteSize.cs +++ b/src/ByteSizeLib/ByteSize.cs @@ -4,64 +4,101 @@ namespace ByteSizeLib { /// - /// Represents a byte size value. + /// Represents a byte size value with support for decimal (KiloByte) and + /// binary values (KibiByte). /// - public struct ByteSize : IComparable, IEquatable - { - public static readonly ByteSize MinValue = ByteSize.FromBits(0); + public partial struct ByteSize : IComparable, IEquatable + { + public static readonly ByteSize MinValue = ByteSize.FromBits(long.MinValue); public static readonly ByteSize MaxValue = ByteSize.FromBits(long.MaxValue); - public const long BitsInByte = 8; - public const long BytesInKiloByte = 1024; - public const long BytesInMegaByte = 1048576; - public const long BytesInGigaByte = 1073741824; - public const long BytesInTeraByte = 1099511627776; - public const long BytesInPetaByte = 1125899906842624; - public const string BitSymbol = "b"; public const string ByteSymbol = "B"; - public const string KiloByteSymbol = "KB"; - public const string MegaByteSymbol = "MB"; - public const string GigaByteSymbol = "GB"; - public const string TeraByteSymbol = "TB"; - public const string PetaByteSymbol = "PB"; - public long Bits { get; private set; } public double Bytes { get; private set; } - public double KiloBytes => Bytes / BytesInKiloByte; - public double MegaBytes => Bytes / BytesInMegaByte; - public double GigaBytes => Bytes / BytesInGigaByte; - public double TeraBytes => Bytes / BytesInTeraByte; - public double PetaBytes => Bytes / BytesInPetaByte; - public string LargestWholeNumberSymbol + public string LargestWholeNumberBinarySymbol + { + get + { + // Absolute value is used to deal with negative values + if (Math.Abs(this.PebiBytes) >= 1) + return PebiByteSymbol; + + if (Math.Abs(this.TebiBytes) >= 1) + return TebiByteSymbol; + + if (Math.Abs(this.GibiBytes) >= 1) + return GibiByteSymbol; + + if (Math.Abs(this.MebiBytes) >= 1) + return MebiByteSymbol; + + if (Math.Abs(this.KibiBytes) >= 1) + return KibiByteSymbol; + + if (Math.Abs(this.Bytes) >= 1) + return ByteSymbol; + + return BitSymbol; + } + } + + public string LargestWholeNumberDecimalSymbol { get { // Absolute value is used to deal with negative values if (Math.Abs(this.PetaBytes) >= 1) - return ByteSize.PetaByteSymbol; + return PetaByteSymbol; if (Math.Abs(this.TeraBytes) >= 1) - return ByteSize.TeraByteSymbol; + return TeraByteSymbol; if (Math.Abs(this.GigaBytes) >= 1) - return ByteSize.GigaByteSymbol; + return GigaByteSymbol; if (Math.Abs(this.MegaBytes) >= 1) - return ByteSize.MegaByteSymbol; + return MegaByteSymbol; if (Math.Abs(this.KiloBytes) >= 1) - return ByteSize.KiloByteSymbol; + return KiloByteSymbol; + + if (Math.Abs(this.Bytes) >= 1) + return ByteSymbol; + + return BitSymbol; + } + } + + public double LargestWholeNumberBinaryValue + { + get + { + // Absolute value is used to deal with negative values + if (Math.Abs(this.PebiBytes) >= 1) + return this.PebiBytes; + + if (Math.Abs(this.TebiBytes) >= 1) + return this.TebiBytes; + + if (Math.Abs(this.GibiBytes) >= 1) + return this.GibiBytes; + + if (Math.Abs(this.MebiBytes) >= 1) + return this.MebiBytes; + + if (Math.Abs(this.KibiBytes) >= 1) + return this.KibiBytes; if (Math.Abs(this.Bytes) >= 1) - return ByteSize.ByteSymbol; + return this.Bytes; - return ByteSize.BitSymbol; + return this.Bits; } } - public double LargestWholeNumberValue + public double LargestWholeNumberDecimalValue { get { @@ -88,55 +125,38 @@ public double LargestWholeNumberValue } } - public ByteSize(double byteSize) + public ByteSize(long bits) : this() { - // Get ceiling because bits are whole units - Bits = (long)Math.Ceiling(byteSize * BitsInByte); + Bits = bits; - Bytes = byteSize; - } - - public static ByteSize FromBits(long value) - { - return new ByteSize(value / (double)BitsInByte); + Bytes = bits / BitsInByte; } - public static ByteSize FromBytes(double value) - { - return new ByteSize(value); - } - - public static ByteSize FromKiloBytes(double value) - { - return new ByteSize(value * BytesInKiloByte); - } - - public static ByteSize FromMegaBytes(double value) + public ByteSize(double bytes) + : this() { - return new ByteSize(value * BytesInMegaByte); - } + // Get ceiling because bits are whole units + Bits = (long)Math.Ceiling(bytes * BitsInByte); - public static ByteSize FromGigaBytes(double value) - { - return new ByteSize(value * BytesInGigaByte); + Bytes = bytes; } - public static ByteSize FromTeraBytes(double value) + public static ByteSize FromBits(long value) { - return new ByteSize(value * BytesInTeraByte); + return new ByteSize(value); } - public static ByteSize FromPetaBytes(double value) + public static ByteSize FromBytes(double value) { - return new ByteSize(value * BytesInPetaByte); + return new ByteSize(value); } /// - /// Converts the value of the current ByteSize object to a string. - /// The metric prefix symbol (bit, byte, kilo, mega, giga, tera) used is - /// the largest metric prefix such that the corresponding value is greater - // than or equal to one. + /// Converts the value of the current object to a string. + /// The prefix symbol (bit, byte, kilo, mebi, gibi, tebi) used is the + /// largest prefix such that the corresponding value is greater than or + /// equal to one. /// public override string ToString() { @@ -148,7 +168,7 @@ public string ToString(string format) return this.ToString(format, CultureInfo.CurrentCulture); } - public string ToString(string format, IFormatProvider provider) + public string ToString(string format, IFormatProvider provider, bool useBinaryByte = false) { if (!format.Contains("#") && !format.Contains("0")) format = "0.## " + format; @@ -158,6 +178,19 @@ public string ToString(string format, IFormatProvider provider) Func has = s => format.IndexOf(s, StringComparison.CurrentCultureIgnoreCase) != -1; Func output = n => n.ToString(format, provider); + // Binary + if (has("PiB")) + return output(this.PebiBytes); + if (has("TiB")) + return output(this.TebiBytes); + if (has("GiB")) + return output(this.GibiBytes); + if (has("MiB")) + return output(this.MebiBytes); + if (has("KiB")) + return output(this.KibiBytes); + + // Decimal if (has("PB")) return output(this.PetaBytes); if (has("TB")) @@ -175,8 +208,15 @@ public string ToString(string format, IFormatProvider provider) if (format.IndexOf(ByteSize.BitSymbol) != -1) return output(this.Bits); - - return string.Format("{0} {1}", this.LargestWholeNumberValue.ToString(format, provider), this.LargestWholeNumberSymbol); + + if (useBinaryByte) + { + return string.Format("{0} {1}", this.LargestWholeNumberBinaryValue.ToString(format, provider), this.LargestWholeNumberBinarySymbol); + } + else + { + return string.Format("{0} {1}", this.LargestWholeNumberDecimalValue.ToString(format, provider), this.LargestWholeNumberDecimalSymbol); + } } public override bool Equals(object value) @@ -223,31 +263,6 @@ public ByteSize AddBytes(double value) return this + ByteSize.FromBytes(value); } - public ByteSize AddKiloBytes(double value) - { - return this + ByteSize.FromKiloBytes(value); - } - - public ByteSize AddMegaBytes(double value) - { - return this + ByteSize.FromMegaBytes(value); - } - - public ByteSize AddGigaBytes(double value) - { - return this + ByteSize.FromGigaBytes(value); - } - - public ByteSize AddTeraBytes(double value) - { - return this + ByteSize.FromTeraBytes(value); - } - - public ByteSize AddPetaBytes(double value) - { - return this + ByteSize.FromPetaBytes(value); - } - public ByteSize Subtract(ByteSize bs) { return new ByteSize(this.Bytes - bs.Bytes); @@ -321,13 +336,8 @@ public static ByteSize Parse(string s, NumberStyles numberStyles, IFormatProvide { // Arg checking -#if NET35 - if (string.IsNullOrEmpty(s) || s.Trim() == "") - throw new ArgumentNullException("s", "String is null or whitespace"); -#else if (string.IsNullOrWhiteSpace(s)) throw new ArgumentNullException("s", "String is null or whitespace"); -#endif // Get the index of the first non-digit character s = s.TrimStart(); // Protect against leading spaces @@ -372,29 +382,39 @@ public static ByteSize Parse(string s, NumberStyles numberStyles, IFormatProvide case "B": return FromBytes(number); + } + + switch (sizePart.ToLowerInvariant()) + { + // Binary + case "kib": + return FromKibiBytes(number); + + case "mib": + return FromMebiBytes(number); + + case "gib": + return FromGibiBytes(number); + + case "tib": + return FromTebiBytes(number); + + case "pib": + return FromPebiBytes(number); - case "KB": - case "kB": + // Decimal case "kb": return FromKiloBytes(number); - case "MB": - case "mB": case "mb": return FromMegaBytes(number); - case "GB": - case "gB": case "gb": return FromGigaBytes(number); - case "TB": - case "tB": case "tb": return FromTeraBytes(number); - case "PB": - case "pB": case "pb": return FromPetaBytes(number); diff --git a/src/ByteSizeLib/ByteSizeLib.csproj b/src/ByteSizeLib/ByteSizeLib.csproj old mode 100644 new mode 100755 index f23a86e..bbfc70a --- a/src/ByteSizeLib/ByteSizeLib.csproj +++ b/src/ByteSizeLib/ByteSizeLib.csproj @@ -1,17 +1,37 @@ - + ByteSize ByteSize is a utility class that makes byte size representation in code easier by removing ambiguity of the value being represented. ByteSize is to bytes what System.TimeSpan is to time. - Copyright © Omar Khudeira 2013-2018 + Copyright © Omar Khudeira 2013-2019 ByteSize - 1.3.0 - 1.3.0 + 2.0.0-beta1 + 2.0.0 Omar Khudeira - net45;netstandard2.0 + netstandard1.0;net45 true portable + ByteSize + ByteSize bytes + +**HUGE BREAKING CHANGE**: + +By default `ByteSize` now assumes `1 KB == 1000 B` and `1 KiB == 1024 B` to +adhere to the IEC and NIST standards (https://en.wikipedia.org/wiki/Binary_prefix). +In the past `ByteSize` assumed `1 KB == 1024 B`, that means if you're upgrading +from v1, you'll see differences in values. + +Other Breaking Changes: +- Renamed property `LargestWholeNumberSymbol` and `LargestWholeNumberValue` to `LargestWholeNumberDecimalSymbol` and `LargestWholeNumberDecimalValue` respectively. +- Drop support for all platforms _except_ `netstandard1.0` and `net45`. + +New Features: + +- Support for binary and decimal values (e.g. `ByteSize.FromKibiByte` and `ByteSize.FromKiloByte`). +- New constructor that takes a `long` value as the number of bits. + +View all release notes at https://github.com/omar/ByteSize/releases. https://github.com/omar/ByteSize https://raw.githubusercontent.com/omar/ByteSize/master/LICENSE git @@ -19,4 +39,8 @@ en - \ No newline at end of file + + $(MONO_REFERENCE_ASSEMBLIES)/4.5-api + + + diff --git a/src/ByteSizeLib/DecimalByteSize.cs b/src/ByteSizeLib/DecimalByteSize.cs new file mode 100644 index 0000000..6a43471 --- /dev/null +++ b/src/ByteSizeLib/DecimalByteSize.cs @@ -0,0 +1,76 @@ +using System; +using System.Globalization; + +namespace ByteSizeLib +{ + public partial struct ByteSize + { + public const long BytesInKiloByte = 1_000; + public const long BytesInMegaByte = 1_000_000; + public const long BytesInGigaByte = 1_000_000_000; + public const long BytesInTeraByte = 1_000_000_000_000; + public const long BytesInPetaByte = 1_000_000_000_000_000; + + public const string KiloByteSymbol = "KB"; + public const string MegaByteSymbol = "MB"; + public const string GigaByteSymbol = "GB"; + public const string TeraByteSymbol = "TB"; + public const string PetaByteSymbol = "PB"; + + public double KiloBytes => Bytes / BytesInKiloByte; + public double MegaBytes => Bytes / BytesInMegaByte; + public double GigaBytes => Bytes / BytesInGigaByte; + public double TeraBytes => Bytes / BytesInTeraByte; + public double PetaBytes => Bytes / BytesInPetaByte; + + public static ByteSize FromKiloBytes(double value) + { + return new ByteSize(value * BytesInKiloByte); + } + + public static ByteSize FromMegaBytes(double value) + { + return new ByteSize(value * BytesInMegaByte); + } + + public static ByteSize FromGigaBytes(double value) + { + return new ByteSize(value * BytesInGigaByte); + } + + public static ByteSize FromTeraBytes(double value) + { + return new ByteSize(value * BytesInTeraByte); + } + + public static ByteSize FromPetaBytes(double value) + { + return new ByteSize(value * BytesInPetaByte); + } + + public ByteSize AddKiloBytes(double value) + { + return this + ByteSize.FromKiloBytes(value); + } + + public ByteSize AddMegaBytes(double value) + { + return this + ByteSize.FromMegaBytes(value); + } + + public ByteSize AddGigaBytes(double value) + { + return this + ByteSize.FromGigaBytes(value); + } + + public ByteSize AddTeraBytes(double value) + { + return this + ByteSize.FromTeraBytes(value); + } + + public ByteSize AddPetaBytes(double value) + { + return this + ByteSize.FromPetaBytes(value); + } + } +}