Skip to content

Commit 1b0aa76

Browse files
committed
feat: add value converters and extensions for DateTime, string, and file size; enhance length and math conversions
1 parent d912e22 commit 1b0aa76

17 files changed

Lines changed: 896 additions & 9 deletions

.github/workflows/docs.yml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ on:
1616
- .github/workflows/docs.yml
1717
workflow_dispatch:
1818

19+
permissions:
20+
contents: write
21+
1922
jobs:
2023
generate-docs:
2124

@@ -38,8 +41,7 @@ jobs:
3841
run: dotnet tool install -g docfx
3942

4043
- name: Restore projects
41-
run: dotnet restore
42-
working-directory: src
44+
run: dotnet restore MADE.NET.sln
4345

4446
- name: Build DocFX site
4547
working-directory: docs

docs/articles/features/data-converters.md

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,30 @@ These can be used to convert any type to another. Whatever data conversion you t
5858

5959
If there is a common value converter you think is missing from MADE.NET, [raise a tracking item on GitHub](https://github.com/MADE-Apps/MADE.NET/issues/new/choose) and we'll get it implemented.
6060

61+
## Converting strings to enums using the StringToEnumValueConverter
62+
63+
The `MADE.Data.Converters.StringToEnumValueConverter<TEnum>` converts between string values and enum types. It supports case-insensitive matching by default.
64+
65+
```csharp
66+
var converter = new StringToEnumValueConverter<DayOfWeek>();
67+
68+
DayOfWeek day = converter.Convert("Monday"); // DayOfWeek.Monday
69+
string name = converter.ConvertBack(DayOfWeek.Friday); // "Friday"
70+
```
71+
72+
Set `IgnoreCase` to `false` if you need exact case matching. The converter throws `InvalidDataConversionException` if the string cannot be parsed as the target enum type.
73+
74+
## Converting DateTime to Unix timestamps using the DateTimeToUnixTimestampValueConverter
75+
76+
The `MADE.Data.Converters.DateTimeToUnixTimestampValueConverter` converts between `DateTime` and Unix timestamps (seconds since 1970-01-01 UTC).
77+
78+
```csharp
79+
var converter = new DateTimeToUnixTimestampValueConverter();
80+
81+
long timestamp = converter.Convert(new DateTime(2024, 1, 1, 0, 0, 0, DateTimeKind.Utc));
82+
DateTime dateTime = converter.ConvertBack(timestamp);
83+
```
84+
6185
## DateTime extensions
6286

6387
The `MADE.Data.Converters.Extensions.DateTimeExtensions` class provides a comprehensive set of extensions for working with `DateTime` values:
@@ -84,6 +108,18 @@ The `MADE.Data.Converters.Extensions.StringExtensions` class provides extensions
84108
- `ToFloat()` / `ToNullableFloat()` - Parses a string to a float.
85109
- `ToDouble()` / `ToNullableDouble()` - Parses a string to a double.
86110
- `ToBoolean()` - Parses a string to a boolean.
111+
- `ToSlug()` - Converts a string to a URL-friendly slug by removing diacritics, replacing non-alphanumeric characters with hyphens, and lowercasing.
112+
113+
```csharp
114+
string slug = "Hello World! Cafe\u0301".ToSlug(); // "hello-world-cafe"
115+
```
116+
117+
## TimeSpan extensions
118+
119+
The `MADE.Data.Converters.Extensions.TimeSpanExtensions` class provides extensions for working with `TimeSpan` values:
120+
121+
- `ToHumanReadableString()` - Converts a TimeSpan to a human-readable string such as "2 hours 30 minutes".
122+
- `TotalWeeks()` - Gets the total number of whole weeks in a TimeSpan.
87123

88124
## Boolean extensions
89125

@@ -96,11 +132,28 @@ string result = isActive.ToFormattedString("Active", "Inactive"); // "Active"
96132

97133
## Math extensions
98134

99-
The `MADE.Data.Converters.Extensions.MathExtensions` class provides extensions for common mathematic expressions including `ToRadians` to convert a degrees value to radians.
135+
The `MADE.Data.Converters.Extensions.MathExtensions` class provides extensions for common mathematic expressions:
136+
137+
- `ToRadians()` - Converts a degrees value to radians.
138+
- `ToDegrees()` - Converts a radians value to degrees.
100139

101140
## Length extensions
102141

103142
The `MADE.Data.Converters.Extensions.LengthExtensions` class provides extensions for converting length values:
104143

105144
- `ToMeters()` - Converts a value from miles to meters.
106145
- `ToMiles()` - Converts a value from meters to miles.
146+
- `KilometersToMeters()` / `ToKilometers()` - Converts between kilometers and meters.
147+
- `FeetToMeters()` / `ToFeet()` - Converts between feet and meters.
148+
- `InchesToMeters()` / `ToInches()` - Converts between inches and meters.
149+
150+
## File size extensions
151+
152+
The `MADE.Data.Converters.Extensions.FileSizeExtensions` class provides extensions for converting byte values to human-readable file size strings:
153+
154+
- `ToHumanReadableFileSize()` - Converts a byte count to a string such as "1.50 MB" or "256 B".
155+
156+
```csharp
157+
long bytes = 1_572_864;
158+
string size = bytes.ToHumanReadableFileSize(); // "1.50 MB"
159+
```

docs/articles/intro.md

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,10 +66,14 @@ It includes features such as:
6666

6767
- BooleanToStringValueConverter, a value converter for converting `bool` values to configurable `String` representations, with the capability to convert back.
6868
- DateTimeToStringValueConverter, a value converter that takes a `DateTime` string format parameter to convert a `DateTime` value to a `String`, with the capability to convert back.
69-
- DateTimeExtensions, a collection of useful extensions for interacting with `DateTime` values including `ToCurrentAge` (to get an age in years based on a given date from today), `StartOfDay`/`EndOfDay`, `StartOfWeek`/`EndOfWeek`, `StartOfMonth`/`EndOfMonth`, `StartOfYear`/`EndOfYear`, and `ToNearestHour`.
70-
- MathExtensions, a collection of extensions for common mathematic expressions including `ToRadians` (to convert a degrees value to radians).
71-
- StringExtensions, a collection of extensions for manipulating `String` values such as `ToTitleCase`, `ToDefaultCase`, `Truncate`, `ToBase64`, `FromBase64`, `ToInt`, `ToBoolean`, `ToFloat`, and `ToDouble`.
72-
- LengthExtensions, for converting length values such as `ToMeters` and `ToMiles`.
69+
- StringToEnumValueConverter, a generic value converter for converting between `String` and any `Enum` type with case-insensitive matching.
70+
- DateTimeToUnixTimestampValueConverter, a value converter between `DateTime` and Unix timestamps.
71+
- DateTimeExtensions, a collection of useful extensions for interacting with `DateTime` values including `ToCurrentAge`, `StartOfDay`/`EndOfDay`, `StartOfWeek`/`EndOfWeek`, `StartOfMonth`/`EndOfMonth`, `StartOfYear`/`EndOfYear`, and `ToNearestHour`.
72+
- TimeSpanExtensions, providing `ToHumanReadableString` and `TotalWeeks`.
73+
- MathExtensions, providing `ToRadians` and `ToDegrees` for angle conversions.
74+
- StringExtensions, a collection of extensions for manipulating `String` values such as `ToTitleCase`, `ToDefaultCase`, `Truncate`, `ToBase64`, `FromBase64`, `ToSlug`, `ToInt`, `ToBoolean`, `ToFloat`, and `ToDouble`.
75+
- LengthExtensions, for converting between miles, meters, kilometers, feet, and inches.
76+
- FileSizeExtensions, for converting byte counts to human-readable file size strings.
7377
- BooleanExtensions, for formatting `bool` values to custom string representations with `ToFormattedString`.
7478

7579
<span class="button">
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// MADE Apps licenses this file to you under the MIT license.
2+
// See the LICENSE file in the project root for more information.
3+
4+
using MADE.Data.Converters.Constants;
5+
6+
namespace MADE.Data.Converters;
7+
8+
/// <summary>
9+
/// Defines a value converter from <see cref="DateTime"/> to a Unix timestamp represented as a <see cref="long"/>.
10+
/// </summary>
11+
public class DateTimeToUnixTimestampValueConverter : IValueConverter<DateTime, long>
12+
{
13+
/// <summary>
14+
/// Converts the <paramref name="value">value</paramref> to a Unix timestamp in seconds.
15+
/// </summary>
16+
/// <param name="value">
17+
/// The <see cref="DateTime"/> value to convert.
18+
/// </param>
19+
/// <param name="parameter">
20+
/// The optional parameter used to help with conversion.
21+
/// </param>
22+
/// <returns>
23+
/// The Unix timestamp in seconds since the Unix epoch (1970-01-01 00:00:00 UTC).
24+
/// </returns>
25+
public long Convert(DateTime value, object? parameter = default)
26+
{
27+
return (long)(value.ToUniversalTime() - DateTimeConstants.UnixEpoch).TotalSeconds;
28+
}
29+
30+
/// <summary>
31+
/// Converts a Unix timestamp in seconds back to a <see cref="DateTime"/> in UTC.
32+
/// </summary>
33+
/// <param name="value">
34+
/// The Unix timestamp in seconds to convert.
35+
/// </param>
36+
/// <param name="parameter">
37+
/// The optional parameter used to help with conversion.
38+
/// </param>
39+
/// <returns>
40+
/// The converted <see cref="DateTime"/> in UTC.
41+
/// </returns>
42+
public DateTime ConvertBack(long value, object? parameter = default)
43+
{
44+
return DateTimeConstants.UnixEpoch.AddSeconds(value);
45+
}
46+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// MADE Apps licenses this file to you under the MIT license.
2+
// See the LICENSE file in the project root for more information.
3+
4+
namespace MADE.Data.Converters.Extensions;
5+
6+
/// <summary>
7+
/// Defines a collection of extensions for converting byte values to human-readable file size representations.
8+
/// </summary>
9+
public static class FileSizeExtensions
10+
{
11+
private static readonly string[] SizeUnits = { "B", "KB", "MB", "GB", "TB", "PB", "EB" };
12+
13+
/// <summary>
14+
/// Converts a byte count to a human-readable file size string using binary units (1 KB = 1024 bytes).
15+
/// </summary>
16+
/// <param name="bytes">The byte count to convert.</param>
17+
/// <param name="decimalPlaces">The number of decimal places to display. Default is 2.</param>
18+
/// <returns>A human-readable file size string such as "1.50 MB" or "256 B".</returns>
19+
public static string ToHumanReadableFileSize(this long bytes, int decimalPlaces = 2)
20+
{
21+
if (bytes < 0)
22+
{
23+
return $"-{(-bytes).ToHumanReadableFileSize(decimalPlaces)}";
24+
}
25+
26+
if (bytes == 0)
27+
{
28+
return "0 B";
29+
}
30+
31+
int unitIndex = (int)Math.Floor(Math.Log(bytes, 1024));
32+
unitIndex = Math.Min(unitIndex, SizeUnits.Length - 1);
33+
34+
double size = bytes / Math.Pow(1024, unitIndex);
35+
36+
return $"{size.ToString($"F{decimalPlaces}")} {SizeUnits[unitIndex]}";
37+
}
38+
39+
/// <summary>
40+
/// Converts a byte count to a human-readable file size string using binary units (1 KB = 1024 bytes).
41+
/// </summary>
42+
/// <param name="bytes">The byte count to convert.</param>
43+
/// <param name="decimalPlaces">The number of decimal places to display. Default is 2.</param>
44+
/// <returns>A human-readable file size string such as "1.50 MB" or "256 B".</returns>
45+
public static string ToHumanReadableFileSize(this double bytes, int decimalPlaces = 2)
46+
{
47+
return ((long)bytes).ToHumanReadableFileSize(decimalPlaces);
48+
}
49+
}

src/MADE.Data.Converters/Extensions/LengthExtensions.cs

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,23 @@ namespace MADE.Data.Converters.Extensions;
88
/// </summary>
99
public static class LengthExtensions
1010
{
11+
private const double MetersPerMile = 1609.344;
12+
private const double MilesPerMeter = 1.0 / MetersPerMile;
13+
private const double MetersPerKilometer = 1000.0;
14+
private const double KilometersPerMeter = 1.0 / MetersPerKilometer;
15+
private const double MetersPerFoot = 0.3048;
16+
private const double FeetPerMeter = 1.0 / MetersPerFoot;
17+
private const double MetersPerInch = 0.0254;
18+
private const double InchesPerMeter = 1.0 / MetersPerInch;
19+
1120
/// <summary>
1221
/// Converts a distance measured in miles to a distance measured in meters.
1322
/// </summary>
1423
/// <param name="miles">The miles to convert to meters.</param>
1524
/// <returns>The meters that represent the miles.</returns>
1625
public static double ToMeters(this double miles)
1726
{
18-
return miles * 1609.344;
27+
return miles * MetersPerMile;
1928
}
2029

2130
/// <summary>
@@ -25,6 +34,66 @@ public static double ToMeters(this double miles)
2534
/// <returns>The miles that represent the meters.</returns>
2635
public static double ToMiles(this double meters)
2736
{
28-
return meters / 1609.344;
37+
return meters * MilesPerMeter;
38+
}
39+
40+
/// <summary>
41+
/// Converts a distance measured in kilometers to a distance measured in meters.
42+
/// </summary>
43+
/// <param name="kilometers">The kilometers to convert to meters.</param>
44+
/// <returns>The meters that represent the kilometers.</returns>
45+
public static double KilometersToMeters(this double kilometers)
46+
{
47+
return kilometers * MetersPerKilometer;
48+
}
49+
50+
/// <summary>
51+
/// Converts a distance measured in meters to a distance measured in kilometers.
52+
/// </summary>
53+
/// <param name="meters">The meters to convert to kilometers.</param>
54+
/// <returns>The kilometers that represent the meters.</returns>
55+
public static double ToKilometers(this double meters)
56+
{
57+
return meters * KilometersPerMeter;
58+
}
59+
60+
/// <summary>
61+
/// Converts a distance measured in feet to a distance measured in meters.
62+
/// </summary>
63+
/// <param name="feet">The feet to convert to meters.</param>
64+
/// <returns>The meters that represent the feet.</returns>
65+
public static double FeetToMeters(this double feet)
66+
{
67+
return feet * MetersPerFoot;
68+
}
69+
70+
/// <summary>
71+
/// Converts a distance measured in meters to a distance measured in feet.
72+
/// </summary>
73+
/// <param name="meters">The meters to convert to feet.</param>
74+
/// <returns>The feet that represent the meters.</returns>
75+
public static double ToFeet(this double meters)
76+
{
77+
return meters * FeetPerMeter;
78+
}
79+
80+
/// <summary>
81+
/// Converts a distance measured in inches to a distance measured in meters.
82+
/// </summary>
83+
/// <param name="inches">The inches to convert to meters.</param>
84+
/// <returns>The meters that represent the inches.</returns>
85+
public static double InchesToMeters(this double inches)
86+
{
87+
return inches * MetersPerInch;
88+
}
89+
90+
/// <summary>
91+
/// Converts a distance measured in meters to a distance measured in inches.
92+
/// </summary>
93+
/// <param name="meters">The meters to convert to inches.</param>
94+
/// <returns>The inches that represent the meters.</returns>
95+
public static double ToInches(this double meters)
96+
{
97+
return meters * InchesPerMeter;
2998
}
3099
}

src/MADE.Data.Converters/Extensions/MathExtensions.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,18 @@ public static double ToRadians(this double degrees)
2121
{
2222
return degrees * (System.Math.PI / 180);
2323
}
24+
25+
/// <summary>
26+
/// Converts a radians value to a degrees value.
27+
/// </summary>
28+
/// <param name="radians">
29+
/// The radians value to convert.
30+
/// </param>
31+
/// <returns>
32+
/// The converted value as degrees.
33+
/// </returns>
34+
public static double ToDegrees(this double radians)
35+
{
36+
return radians * (180 / System.Math.PI);
37+
}
2438
}

src/MADE.Data.Converters/Extensions/StringExtensions.cs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22
// See the LICENSE file in the project root for more information.
33

44
using System;
5+
using System.Globalization;
56
using System.IO;
67
using System.Text;
8+
using System.Text.RegularExpressions;
79
using System.Threading.Tasks;
810

911
namespace MADE.Data.Converters.Extensions;
@@ -269,4 +271,46 @@ public static double ToDouble(this string value)
269271
bool parsed = double.TryParse(value, out double doubleValue);
270272
return parsed ? doubleValue : null;
271273
}
274+
275+
/// <summary>
276+
/// Converts a string to a URL-friendly slug by removing diacritics, replacing non-alphanumeric characters with hyphens, and lowercasing.
277+
/// </summary>
278+
/// <param name="value">The value to convert to a slug.</param>
279+
/// <returns>A URL-friendly slug string.</returns>
280+
public static string ToSlug(this string value)
281+
{
282+
if (string.IsNullOrWhiteSpace(value))
283+
{
284+
return string.Empty;
285+
}
286+
287+
// Normalize to decompose characters (e.g., e with accent -> e + combining accent)
288+
string normalized = value.Normalize(NormalizationForm.FormD);
289+
290+
// Remove non-spacing marks (diacritics/accents)
291+
var stripped = new StringBuilder();
292+
foreach (char c in normalized)
293+
{
294+
if (CharUnicodeInfo.GetUnicodeCategory(c) != UnicodeCategory.NonSpacingMark)
295+
{
296+
stripped.Append(c);
297+
}
298+
}
299+
300+
string result = stripped.ToString().Normalize(NormalizationForm.FormC);
301+
302+
// Lowercase
303+
result = result.ToLowerInvariant();
304+
305+
// Replace non-alphanumeric characters with hyphens
306+
result = Regex.Replace(result, @"[^a-z0-9\s-]", string.Empty);
307+
308+
// Replace whitespace and multiple hyphens with a single hyphen
309+
result = Regex.Replace(result, @"[\s-]+", "-");
310+
311+
// Trim leading and trailing hyphens
312+
result = result.Trim('-');
313+
314+
return result;
315+
}
272316
}

0 commit comments

Comments
 (0)