• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

f2calv / CasCap.Common / 22972729257

11 Mar 2026 08:22PM UTC coverage: 3.947% (-0.1%) from 4.045%
22972729257

push

github

web-flow
Merge pull request #248 from f2calv/f2calv/2026-03-updates

F2calv/2026 03 updates

4 of 342 branches covered (1.17%)

Branch coverage included in aggregate %.

0 of 29 new or added lines in 5 files covered. (0.0%)

24 existing lines in 3 files now uncovered.

32 of 570 relevant lines covered (5.61%)

0.37 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/CasCap.Common.Extensions/Extensions/HelperExtensions.cs
1
using Microsoft.Extensions.Hosting;
2
using System.Collections.Concurrent;
3
using System.ComponentModel;
4
using System.Globalization;
5
using System.IO.Compression;
6
using System.Text;
7
using System.Text.RegularExpressions;
8
using System.Xml.Serialization;
9

10
namespace CasCap.Common.Extensions;
11

12
/// <summary>
13
/// General-purpose extension methods for collections, strings, dates and more.
14
/// </summary>
15
public static class HelperExtensions
16
{
17
    /// <summary>
18
    /// Checks if the current host environment name is 'Integration'.
19
    /// </summary>
20
    public static bool IsIntegration(this IHostEnvironment env) => env.IsEnvironment("Integration");
×
21

22
    /// <summary>
23
    /// Checks if the current host environment name is 'Test'.
24
    /// </summary>
25
    public static bool IsTest(this IHostEnvironment env) => env.IsEnvironment("Test");
×
26

27
    #region IsNullOrEmpty & IsNullOrWhiteSpace cannot interpret nullable reference types correctly, needs more research
28
    //https://github.com/dotnet/roslyn/issues/37995
29
    //https://github.com/JamesNK/Newtonsoft.Json/pull/2163/commits/fba64bcf9b8f41500da1c1dd75825f3db99cd3b4
30
    //public static bool IsNullOrWhiteSpace(this string? val)
31
    //{
32
    //    return val is null || val.Trim() == string.Empty;
33
    //    //return string.IsNullOrWhiteSpace(value);
34
    //}
35

36
    //public static bool IsNullOrEmpty([NotNullWhen(false)] string? value)
37
    //{
38
    //    return string.IsNullOrEmpty(value);
39
    //}
40

41
    //public static bool IsNullOrEmpty(string? value)//conflicts with collections extension IsNullOrEmpty
42
    //{
43
    //    return string.IsNullOrEmpty(value);
44
    //    //return input?.Length > 0;
45
    //}
46
    #endregion
47

48
    /// <summary>
49
    /// Deserializes an XML string into an instance of <typeparamref name="T"/>.
50
    /// </summary>
51
    /// <typeparam name="T">The target type.</typeparam>
52
    /// <param name="input">The XML string.</param>
53
    /// <returns>The deserialized object, or <see langword="null"/> if deserialization fails.</returns>
54
    public static T? FromXml<T>(this string input) where T : class
55
    {
56
        var ser = new XmlSerializer(typeof(T));
×
57
        using var sr = new StringReader(input);
×
58
        return (T?)ser.Deserialize(sr);
×
59
    }
×
60

61
    /// <summary>
62
    /// Deserializes a byte array containing XML into an instance of <typeparamref name="T"/>.
63
    /// </summary>
64
    /// <typeparam name="T">The target type.</typeparam>
65
    /// <param name="bytes">The byte array containing XML data.</param>
66
    /// <returns>The deserialized object, or <see langword="null"/> if deserialization fails.</returns>
67
    public static T? FromBytes<T>(this byte[] bytes) where T : class
68
    {
69
        var ser = new XmlSerializer(typeof(T));
×
70
        using var ms = new MemoryStream(bytes);
×
71
        return (T?)ser.Deserialize(ms);
×
72
    }
×
73

74
    /// <summary>
75
    /// Splits a list into batches of the specified size.
76
    /// </summary>
77
    /// <typeparam name="T">The element type.</typeparam>
78
    /// <param name="objects">The source list.</param>
79
    /// <param name="batchSize">The maximum number of items per batch.</param>
80
    /// <returns>A dictionary keyed by batch number.</returns>
81
    public static Dictionary<int, List<T>> GetBatches<T>(this List<T> objects, int batchSize)
82
    {
83
        var batches = new Dictionary<int, List<T>>();
×
84
        for (var i = 0; i < objects.Count; i++)
×
85
        {
86
            var batchNumber = i / batchSize;
×
87
            if (!batches.ContainsKey(batchNumber))
×
88
                batches.Add(batchNumber, []);
×
89
            batches[batchNumber].Add(objects[i]);
×
90
        }
91
        return batches;
×
92
    }
93

94
    /// <summary>
95
    /// Converts a <see cref="Dictionary{TKey, TValue}"/> to a <see cref="ConcurrentDictionary{TKey, TValue}"/>.
96
    /// </summary>
97
    public static ConcurrentDictionary<T, V> ToConcurrentDictionary<T, V>(this Dictionary<T, V> d2) where T : notnull
98
    {
99
        var d1 = new ConcurrentDictionary<T, V>();
×
100
        foreach (var z in d2)
×
101
        {
102
            if (!d1.TryAdd(z.Key, z.Value))
×
103
                throw new GenericException($"AddRange failed due to conflicting key");
×
104
        }
105
        return d1;
×
106
    }
107

108
    /// <summary>
109
    /// Adds all entries from a <see cref="Dictionary{TKey, TValue}"/> to a <see cref="ConcurrentDictionary{TKey, TValue}"/>.
110
    /// </summary>
111
    public static ConcurrentDictionary<T, V> AddRange<T, V>(this ConcurrentDictionary<T, V> d1, Dictionary<T, V> d2) where T : notnull
112
    {
113
        foreach (var z in d2)
×
114
        {
115
            if (!d1.TryAdd(z.Key, z.Value))
×
116
                throw new GenericException("AddRange failed due to conflicting key");
×
117
        }
118
        return d1;
×
119
    }
120

121
    /// <summary>
122
    /// Adds all entries from one <see cref="Dictionary{TKey, TValue}"/> to another.
123
    /// </summary>
124
    public static Dictionary<T, V> AddRange<T, V>(this Dictionary<T, V> d1, Dictionary<T, V> d2) where T : notnull
125
    {
126
        foreach (var z in d2)
×
127
            d1.Add(z.Key, z.Value);
×
128
        return d1;
×
129
    }
130

131
    /// <summary>
132
    /// Adds all elements from a <see cref="List{T}"/> to the <see cref="HashSet{T}"/>.
133
    /// </summary>
134
    public static HashSet<T> AddRange<T>(this HashSet<T> hs, List<T> l)
135
    {
136
        foreach (var z in l)
×
137
            hs.Add(z);
×
138
        return hs;
×
139
    }
140

141
    /// <summary>
142
    /// Adds all elements from an <see cref="IEnumerable{T}"/> to the <see cref="HashSet{T}"/>.
143
    /// </summary>
144
    public static HashSet<T> AddRange<T>(this HashSet<T> hs, IEnumerable<T> l)
145
    {
146
        foreach (var z in l)
×
147
            hs.Add(z);
×
148
        return hs;
×
149
    }
150

151
    /// <summary>
152
    /// Adds all elements from another <see cref="HashSet{T}"/> to the <see cref="HashSet{T}"/>.
153
    /// </summary>
154
    public static HashSet<T> AddRange<T>(this HashSet<T> hs, HashSet<T> l)
155
    {
156
        foreach (var z in l)
×
157
            hs.Add(z);
×
158
        return hs;
×
159
    }
160

161
    /// <summary>
162
    /// Converts an <see cref="IEnumerable{T}"/> to a <see cref="HashSet{T}"/>.
163
    /// </summary>
164
    public static HashSet<T> ToHashSet<T>(this IEnumerable<T> l)//can remove if we use .net standard 2.1?
165
    {
166
        var hs = new HashSet<T>();
×
167
        foreach (var z in l)
×
168
            hs.Add(z);
×
169
        return hs;
×
170
    }
171

172
    /// <summary>
173
    /// Returns all dates between the start and end date (exclusive of start, inclusive of end).
174
    /// </summary>
175
    public static List<DateTime> GetMissingDates(this DateTime dtStart, DateTime dtEnd)
176
    {
177
        //TODO: plug in known holidays dates somehow?
178
        var days = dtEnd.Date.Subtract(dtStart).Days;
×
179
        var missingDates = Enumerable.Range(1, days).Select(p => dtStart.AddDays(p)).ToArray();
×
180
        return missingDates.ToList();
×
181
    }
182

183
    /// <summary>
184
    /// Checks if a struct has been instantiated.
185
    /// </summary>
186
    public static bool IsNull<T>(this T source) where T : struct => source.Equals(default(T));
×
187

188
    /// <summary>
189
    /// Returns the number of seconds remaining until midnight (UTC).
190
    /// </summary>
191
    public static int SecondsTillMidnight(this DateTime dt)
NEW
192
        => dt.SecondsTillMidnight(DateTime.UtcNow);
×
193
    /// <summary>
194
    /// Returns the number of seconds remaining until midnight relative to the specified time.
195
    /// </summary>
196
    public static int SecondsTillMidnight(this DateTime dt, DateTime now)
197
    {
198
        var ts = dt.Date.AddDays(1) - now;
×
199
        return (int)ts.TotalSeconds;//does this round-up?
×
200
    }
201

202
    /// <summary>
203
    /// Returns a human-readable string representing the time difference between two dates.
204
    /// </summary>
205
    public static string GetTimeDifference(this DateTime dtiStart, DateTime dtiEnd,
206
        bool includeSeconds = true, bool includeMinutes = true, bool includeHours = true, bool includeDays = true, bool includeMilliseconds = false)
207
    {
208
        var ts = dtiStart.Subtract(dtiEnd).Duration();
×
NEW
209
        return ts.GetTimeDifference(includeSeconds, includeMinutes, includeHours, includeDays, includeMilliseconds);
×
210
    }
211

212
    /// <summary>
213
    /// Returns a human-readable string representing the specified <see cref="TimeSpan"/>.
214
    /// </summary>
215
    public static string GetTimeDifference(this TimeSpan ts,
216
        bool includeSeconds = true, bool includeMinutes = true, bool includeHours = true, bool includeDays = true, bool includeMilliseconds = false)
217
    {
218
        var sb = new StringBuilder();
×
219
        if (includeDays && ts.Days != 0) sb.Append(ts.Days + "d ");
×
220
        if (includeHours && ts.Hours != 0) sb.Append(ts.Hours + "h ");
×
221
        if (includeMinutes)
×
222
            if (ts.Minutes >= 1)
×
223
                sb.Append(ts.Minutes + "m ");
×
224
            else if (!includeSeconds)
×
225
                sb.Append("<1m");
×
226
        if (includeSeconds && ts.Seconds != 0) sb.Append(ts.Seconds + "s ");
×
227
        if (includeMilliseconds && ts.Milliseconds != 0) sb.Append(ts.Milliseconds + "ms ");
×
228
        return sb.ToString().Trim();
×
229
    }
230

231
    /// <summary>
232
    /// Represent a date in "yyyy-MM-dd" format
233
    /// </summary>
234
    public static string To_yyyy_MM_dd(this DateTime thisDateTime) => thisDateTime.ToString("yyyy-MM-dd");
×
235

236
#if NET8_0_OR_GREATER
237
    /// <summary>
238
    /// Converts a Unix timestamp in seconds to a <see cref="DateTime"/>.
239
    /// </summary>
UNCOV
240
    public static DateTime FromUnixTime(this long seconds) => DateTime.UnixEpoch.AddSeconds(seconds);
×
241

242
    /// <summary>
243
    /// Converts a Unix timestamp in milliseconds to a <see cref="DateTime"/>.
244
    /// </summary>
UNCOV
245
    public static DateTime FromUnixTimeMs(this long milliseconds) => DateTime.UnixEpoch.AddMilliseconds(milliseconds);
×
246

247
    /// <summary>
248
    /// Converts a Unix timestamp in milliseconds (as <see cref="double"/>) to a <see cref="DateTime"/>.
249
    /// </summary>
UNCOV
250
    public static DateTime FromUnixTimeMs(this double milliseconds) => DateTime.UnixEpoch.AddMilliseconds(milliseconds);
×
251
#endif
252

253
    /// <summary>
254
    /// Converts a <see cref="DateTime"/> to a Unix timestamp in seconds.
255
    /// </summary>
UNCOV
256
    public static long ToUnixTime(this DateTime dt) => ((DateTimeOffset)dt).ToUnixTimeSeconds();
×
257

258
    /// <summary>
259
    /// Converts a <see cref="DateTime"/> to a Unix timestamp in milliseconds.
260
    /// </summary>
UNCOV
261
    public static long ToUnixTimeMs(this DateTime dt) => dt.ToUnixTime() * 1000;
×
262

263
    /// <summary>
264
    /// Determines whether the specified date falls on a weekend.
265
    /// </summary>
UNCOV
266
    public static bool IsWeekend(this DateTime date) => date.DayOfWeek == DayOfWeek.Saturday || date.DayOfWeek == DayOfWeek.Sunday;
×
267

268
    /// <summary>
269
    /// Determines whether the specified date falls on a weekday.
270
    /// </summary>
UNCOV
271
    public static bool IsWeekday(this DateTime date) => !date.IsWeekend();
×
272

273
    /// <summary>
274
    /// Sets a <see cref="DateTime"/> to be <see cref="DateTimeKind.Utc"/>.
275
    /// </summary>
276
    public static DateTime ToUtc(this DateTime dt) => DateTime.SpecifyKind(dt, DateTimeKind.Utc);
×
277

278
    /// <summary>
279
    /// Formats a <see cref="DateTime"/> as a time string if today, otherwise as a date string.
280
    /// </summary>
281
    public static string ToDateOrTime(this DateTime thisDateTime, string dateFormat = "yyyy-MM-dd", string timeFormat = "HH:mm:ss")
282
    {
283
        return thisDateTime.ToString(thisDateTime.Date == DateTime.UtcNow.Date ? timeFormat : dateFormat);
×
284
    }
285

286
    /// <summary>
287
    /// truncate milliseconds off a .net datetime
288
    /// </summary>
289
    public static DateTime Truncate(this DateTime dateTime, TimeSpan timeSpan)
290
    {
291
        if (timeSpan == TimeSpan.Zero) return dateTime; // Or could throw an ArgumentException
×
292
        return dateTime.AddTicks(-(dateTime.Ticks % timeSpan.Ticks));
×
293
    }
294

295
    /// <summary>
296
    /// Returns the first day of the week containing the specified date.
297
    /// </summary>
298
    public static DateTime FirstDayOfWeek(this DateTime dt, DayOfWeek startOfWeek)
299
    {
300
        var diff = dt.DayOfWeek - startOfWeek;
×
301
        if (diff < 0) diff += 7;
×
302
        return dt.AddDays(-1 * diff).Date;
×
303
    }
304

305
    /// <summary>
306
    /// Adds the specified number of weekdays (skipping weekends) to the date.
307
    /// </summary>
308
    public static DateTime AddWeekdays(this DateTime date, int days)
309
    {
310
        var sign = days < 0 ? -1 : 1;
×
311
        var unsignedDays = Math.Abs(days);
×
312
        var weekdaysAdded = 0;
×
313
        while (weekdaysAdded < unsignedDays)
×
314
        {
315
            date = date.AddDays(sign);
×
316
            if (date.IsWeekday())
×
317
                weekdaysAdded++;
×
318
        }
319
        return date;
×
320
    }
321

322
    /// <summary>
323
    /// Returns the first day of the month for the specified date.
324
    /// </summary>
325
    public static DateTime FirstDayOfMonth(this DateTime date, DateTimeKind kind = DateTimeKind.Utc)
326
        => new(date.Year, date.Month, 1, 0, 0, 0, kind);
×
327

328
    /// <summary>
329
    /// Returns the last day of the month for the specified date.
330
    /// </summary>
331
    public static DateTime LastDayOfMonth(this DateTime date, DateTimeKind kind = DateTimeKind.Utc)
332
        => new(date.Year, date.Month, DateTime.DaysInMonth(date.Year, date.Month), 0, 0, 0, kind);
×
333

334
    /// <summary>
335
    /// Returns the last day of the year for the specified date.
336
    /// </summary>
337
    public static DateTime LastDayOfYear(this DateTime date, DateTimeKind kind = DateTimeKind.Utc)
338
        => new DateTime(date.Year, 12, 1, 0, 0, 0, kind).LastDayOfMonth();
×
339

340
    /// <summary>
341
    /// Returns the absolute difference in months between two dates.
342
    /// </summary>
343
    public static int MonthDifference(this DateTime lValue, DateTime rValue)
344
        => Math.Abs(lValue.Month - rValue.Month + 12 * (lValue.Year - rValue.Year));
×
345

346
    /// <summary>
347
    /// Converts a nullable <see cref="DateTime"/> to its string representation using current culture info.
348
    /// </summary>
349
    public static string ToString(this DateTime? date)
NEW
350
        => date.ToString(DateTimeFormatInfo.CurrentInfo);
×
351

352
    /// <summary>
353
    /// Converts a nullable <see cref="DateTime"/> to its string representation using the specified format.
354
    /// </summary>
355
    public static string ToString(this DateTime? date, string format)
NEW
356
        => date.ToString(format, DateTimeFormatInfo.CurrentInfo);
×
357

358
    /// <summary>
359
    /// Converts a nullable <see cref="DateTime"/> to its string representation using the specified provider.
360
    /// </summary>
361
    public static string ToString(this DateTime? date, IFormatProvider provider)
362
    {
NEW
363
        if (date.HasValue)
×
NEW
364
            return date.Value.ToString(provider);
×
NEW
365
        return string.Empty;
×
366
    }
367

368
    /// <summary>
369
    /// Converts a nullable <see cref="DateTime"/> to its string representation using the specified format and provider.
370
    /// </summary>
371
    public static string ToString(this DateTime? date, string format, IFormatProvider provider)
372
    {
373
        if (date.HasValue)
×
374
            return date.Value.ToString(format, provider);
×
375
        else
376
            return string.Empty;
×
377
    }
378

379
    /// <summary>
380
    /// Returns a human-readable relative date string (e.g. "2 days ago").
381
    /// </summary>
UNCOV
382
    public static string ToRelativeDateString(this DateTime date) => GetRelativeDateValue(date, DateTime.UtcNow);
×
383

384
    /// <summary>
385
    /// Returns a human-readable relative date string compared to <see cref="DateTime.UtcNow"/>.
386
    /// </summary>
UNCOV
387
    public static string ToRelativeDateStringUtc(this DateTime date) => GetRelativeDateValue(date, DateTime.UtcNow);
×
388

389
    private static string GetRelativeDateValue(DateTime date, DateTime comparedTo)
390
    {
391
        TimeSpan ts = comparedTo.Subtract(date);
×
392
        if (ts.TotalDays >= 365)
×
393
            return string.Concat("on ", date.ToString("MMMM d, yyyy"));
×
394
        if (ts.TotalDays >= 7)
×
395
            return string.Concat("on ", date.ToString("MMMM d"));
×
396
        else if (ts.TotalDays > 1)
×
397
            return string.Format("{0:N0} days ago", ts.TotalDays);
×
398
        else if (ts.TotalDays == 1)
×
399
            return "yesterday";
×
400
        else if (ts.TotalHours >= 2)
×
401
            return string.Format("{0:N0} hours ago", ts.TotalHours);
×
402
        else if (ts.TotalMinutes >= 60)
×
403
            return "more than an hour ago";
×
404
        else if (ts.TotalMinutes >= 5)
×
405
            return string.Format("{0:N0} minutes ago", ts.TotalMinutes);
×
406
        if (ts.TotalMinutes >= 1)
×
407
            return "a few minutes ago";
×
408
        else
409
            return "less than a minute ago";
×
410
    }
411

412
    /// <summary>
413
    /// Generates a sequence of consecutive dates starting from the specified date.
414
    /// </summary>
415
    public static IEnumerable<DateTime> ToArray(this DateTime input, int length = 1)
416
    {
417
        length = length > 0 ? length : 1;
×
418
        return Enumerable.Range(0, length).Select(a => input.AddDays(a));
×
419
    }
420

421
    /// <summary>
422
    /// Combines a base URL with a relative URL, handling leading/trailing slashes.
423
    /// </summary>
424
    public static string UrlCombine(this string baseUrl, string relativeUrl)
425
    {
426
        baseUrl = baseUrl.TrimEnd('/');
×
427
        relativeUrl ??= string.Empty;
×
428
        relativeUrl = relativeUrl.TrimStart('~');
×
429
        relativeUrl = relativeUrl.TrimStart('/');
×
430
        return baseUrl + "/" + relativeUrl;
×
431
    }
432

433
    /// <summary>
434
    /// Joins a list of strings into a single string separated by <see cref="Environment.NewLine"/>.
435
    /// </summary>
436
    public static string List2String(this List<string> input)
437
    {
438
        var sb = new StringBuilder();
×
439
        foreach (var s in input) sb.Append(s + Environment.NewLine);
×
440
        return sb.ToString();
×
441
    }
442

443
    /// <summary>
444
    /// Splits a string by line-break characters, yielding only non-empty lines.
445
    /// </summary>
446
    public static IEnumerable<string> String2List(this string input)
447
    {
448
        foreach (var s in input.Split(['\r', '\n']))
×
449
            if (!string.IsNullOrWhiteSpace(s))
×
450
                yield return s;
×
451
    }
×
452

453
    /// <summary>
454
    /// Gets the <see cref="DescriptionAttribute"/> value for the specified enum member, or its string representation.
455
    /// </summary>
456
    public static string GetDescription<T>(this T enumerationValue)
457
    {
458
        if (enumerationValue is null) throw new ArgumentNullException(nameof(enumerationValue));
×
459
        var type = enumerationValue.GetType();
×
460
        if (!type.IsEnum)
×
461
            throw new ArgumentException("EnumerationValue must be of Enum type", nameof(enumerationValue));
×
462
        //Tries to find a DescriptionAttribute for a potential friendly name for the enum
463
        var memberInfo = type.GetMember(enumerationValue.ToString()!);
×
464
        if (memberInfo.IsAny())
×
465
        {
466
            var attrs = memberInfo[0].GetCustomAttributes(typeof(DescriptionAttribute), false);
×
467
            if (attrs.IsAny())
×
468
            {
469
                //Pull out the description value
470
                return ((DescriptionAttribute)attrs[0]).Description;
×
471
            }
472
        }
473
        //If we have no description attribute, just return the ToString of the enum
474
        return enumerationValue.ToString()!;
×
475
    }
476

477
    private static Dictionary<string, object> dEnumLookup { get; set; } = [];
×
478

479
    /// <summary>
480
    /// UNFINISHED, an expansion of ParseEnum, use a static dictionary for speedy lookups?
481
    /// </summary>
482
#pragma warning disable IDE0060 // Remove unused parameter
483
    public static T ParseEnumFAST<T>(this string value, [CallerMemberName] string caller = "")
484
    {
485
        //TODO: write unit test for this, if you have two different enums with the same value, it'll return the wrong value...
486
        //i.e. enum1.MyVal and enum2.MyVal
487
        if (!dEnumLookup.TryGetValue(value, out object? result))
×
488
        {
489
            var val = (T)Enum.Parse(typeof(T), value, true);
×
490
            dEnumLookup.Add(value, val);
×
491
            return val;
×
492
        }
493
        return (T)result;
×
494
    }
495
#pragma warning restore IDE0060 // Remove unused parameter
496

497
    /// <summary>
498
    /// Parses a string value into the specified <see cref="Enum"/> type (case-insensitive).
499
    /// </summary>
500
    public static T ParseEnum<T>(this string value) where T : struct =>
501
#if NET9_0_OR_GREATER
502
        Enum.Parse<T>(value, true);
503
#else
504
        (T)Enum.Parse(typeof(T), value, true);
505
#endif
506

507
    /// <summary>
508
    /// Attempts to parse a string value into the specified <see cref="Enum"/> type.
509
    /// </summary>
510
    public static bool TryParseEnum<T>(this string value, out T resultInputType, bool ignoreCase = true)
511
        where T : struct
512
    {
513
        resultInputType = default;
×
514
        if (Enum.TryParse(value, ignoreCase, out T result))
×
515
        {
516
            resultInputType = result;
×
517
            return true;
×
518
        }
519
        else
520
            return false;
×
521
    }
522

523
    /// <summary>
524
    /// Returns a random value from the dictionary.
525
    /// </summary>
526
    public static V GetRandomDValue<T, V>(this Dictionary<T, V> d) where T : notnull
527
    {
528
        var keyList = new List<T>(d.Keys);
×
529
        var rand = new Random();
×
530
        var randomKey = keyList[rand.Next(keyList.Count)];
×
531
        return d[randomKey];
×
532
    }
533

534
    /// <summary>
535
    /// Determines whether the <see cref="IEnumerable{T}"/> is <see langword="null"/> or empty.
536
    /// </summary>
537
    public static bool IsNullOrEmpty<T>(this IEnumerable<T>? data) => data is null || !data.Any();
×
538

539
    /// <summary>
540
    /// Determines whether the <see cref="List{T}"/> is <see langword="null"/> or empty.
541
    /// </summary>
542
    public static bool IsNullOrEmpty<T>(this List<T>? data) => data is null || data.Count == 0;
×
543

544
    /// <summary>
545
    /// Determines whether the array is <see langword="null"/> or empty.
546
    /// </summary>
UNCOV
547
    public static bool IsNullOrEmpty<T>(this T[]? data) => data is null || data.Length == 0;
×
548

549
    /// <summary>
550
    /// Determines whether the <see cref="IEnumerable{T}"/> is not <see langword="null"/> and contains elements.
551
    /// </summary>
552
    public static bool IsAny<T>(this IEnumerable<T> data) => data is not null && data.Any();
×
553

554
    /// <summary>
555
    /// Determines whether the <see cref="List{T}"/> is not <see langword="null"/> and contains elements.
556
    /// </summary>
557
    public static bool IsAny<T>(this List<T> data) => data is not null && data.Count > 0;
×
558

559
    /// <summary>
560
    /// Determines whether the array is not <see langword="null"/> and contains elements.
561
    /// </summary>
UNCOV
562
    public static bool IsAny<T>(this T[] data) => data is not null && data.Length > 0;
×
563

564
    /// <summary>
565
    /// Parses a string to a <see cref="bool"/>, treating "1" as <see langword="true"/>.
566
    /// </summary>
567
    public static bool ToBoolean(this string input)
568
    {
569
        if (input == "1") input = "true";
×
570
        if (bool.TryParse(input.Trim(), out var output))
×
571
            return output;
×
572
        else
573
            return false;
×
574
    }
575

576
    #region ParseDecimal
577
    /// <summary>
578
    /// Parses a string to a <see cref="decimal"/>.
579
    /// </summary>
UNCOV
580
    public static decimal ToDecimal(this string input) => ParseDecimal(input);
×
581

582
    /// <summary>
583
    /// Parses a string to a nullable <see cref="decimal"/>.
584
    /// </summary>
585
#pragma warning disable IDE0060 // Remove unused parameter
586
    public static decimal? ToDecimal(this string input, bool nullable) => ParseDecimal(input);
×
587
#pragma warning restore IDE0060 // Remove unused parameter
588

589
    private static decimal ParseDecimal(string input)
590
    {
591
        var output = 0m;
×
592
        if (!string.IsNullOrWhiteSpace(input) && !decimal.TryParse(input, out output))
×
593
            throw new GenericException("TryParse failed");
×
594
        return output;
×
595
    }
596
    #endregion
597

598
    #region ParseInt
599
    //public static int ToInt(this object input) => ParseInt(input);
600

601
    /// <summary>
602
    /// Parses a string to an <see cref="int"/>.
603
    /// </summary>
UNCOV
604
    public static int ToInt(this string input) => ParseInt(input);
×
605

606
    //public static int ToInt(this object input, ref int result)
607
    //{
608
    //    result = ParseInt(input);
609
    //    return result;
610
    //}
611
    //private static int ParseInt(object input) => ParseInt((input ?? string.Empty).ToString()!, 0);
612

613
    private static int ParseInt(string input) => ParseInt(input, 0);
×
614
    private static int ParseInt(string input, int _def)
615
    {
616
        var output = _def;
×
617
        if (string.IsNullOrWhiteSpace(input))
×
618
            output = _def;
×
619
        else
620
        {
621
            try { output = int.Parse(input.Trim()); }
×
622
            catch (Exception ex) { Debug.WriteLine(ex); }
×
623
        }
624
        return output;
×
625
    }
626
    #endregion
627

628
    #region ParseDateTime
629
    /// <summary>
630
    /// Parses an object to a <see cref="DateTime"/>.
631
    /// </summary>
UNCOV
632
    public static DateTime ToDateTime(this object input) => ParseDateTime(input);
×
633

634
    /// <summary>
635
    /// Parses an object to a <see cref="DateTime"/> and returns only the date component.
636
    /// </summary>
UNCOV
637
    public static DateTime ToDate(this object input) => ParseDateTime(input).Date;
×
638

639
    private static DateTime ParseDateTime(object input) => ParseDateTime((input ?? string.Empty).ToString()!);
×
640

641
    private static DateTime ParseDateTime(string input) => ParseDateTime(input, DateTime.MinValue);
×
642

643
    private static DateTime ParseDateTime(string input, DateTime _def)
644
    {
645
        DateTime output = _def;
×
646
        if (string.IsNullOrWhiteSpace(input))
×
647
            output = _def;
×
648
        else
649
        {
650
            try { output = DateTime.Parse(input.Trim()); }
×
651
            catch (Exception ex) { Debug.WriteLine(ex); }
×
652
        }
653
        return output;
×
654
    }
655
    #endregion
656

657
    /// <summary>
658
    /// Splits an array into chunks of the specified size.
659
    /// </summary>
660
    public static IEnumerable<IEnumerable<T>> Split<T>(this T[] array, int size)
661
    {
662
        for (var i = 0; i < (float)array.Length / size; i++)
×
663
        {
664
            yield return array.Skip(i * size).Take(size);
×
665
        }
666
    }
×
667

668
    /// <summary>
669
    /// Returns a substring capped at the specified length, optionally appending trailing dots.
670
    /// </summary>
671
    public static string SubstringSafe(this string thisString, int maxLength, bool includeTrailingDots = false)
672
    {
673
        if (thisString is not null && maxLength > 0)
×
674
        {
675
            var original = thisString;
×
676
            if (includeTrailingDots && maxLength > 3)
×
677
                maxLength += -3;
×
678
            if (maxLength < thisString.Length)
×
679
                thisString = thisString.Substring(0, maxLength);
×
680
            thisString = thisString.Trim();
×
681
            if (thisString.Length > 0 && thisString[thisString.Length - 1] == ',')
×
682
                thisString = thisString.Substring(0, thisString.Length - 1);
×
683
            if (includeTrailingDots && original.ToString().Length > maxLength)
×
684
                thisString += "...";
×
685
            return thisString;
×
686
        }
687
        return string.Empty;
×
688
    }
689

690
    /// <summary>
691
    /// Removes tab, newline and carriage-return characters from the string.
692
    /// </summary>
693
    public static string Clean(this string thisString, string replacement = "")
694
    {
695
        return rgx.Replace(thisString, replacement);
×
696
    }
697

NEW
698
    private static readonly TimeSpan _regexTimeout = TimeSpan.FromSeconds(1);
×
699

NEW
700
    private static readonly Regex rgx = new(cleanPattern, RegexOptions.Compiled, _regexTimeout);
×
701

702
    private const string cleanPattern = @"\t|\n|\r";
703

704
    /// <summary>
705
    /// Determines whether the string is a valid email address.
706
    /// </summary>
707
    public static bool IsEmail(this string thisString)
708
    {
709
        //same as new aspNetEmail.EmailMessage().ValidateRegEx
710
        return thisString is not null && rgxEmail.IsMatch(thisString);
×
711
    }
712

NEW
713
    private static readonly Regex rgxEmail = new(emailPattern, RegexOptions.Compiled, _regexTimeout);
×
714

715
    private const string emailPattern = @"^((\w+)|(\w+[!#$%&'*+\-,./=?^_`{|}~\w]*[!#$%&'*+\-,/=?^_`{|}~\w]))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([a-zA-Z0-9\-]+\.)+))([a-zA-Z]{2,10}|[0-9]{1,3})(\]?)$";
716

717
    /// <summary>
718
    /// GZip using integrated .NET compression library.
719
    /// </summary>
720
    public static byte[] Compress(this byte[] data)
721
    {
722
        if (data.IsNullOrEmpty()) return data;
×
723
        using var compressedStream = new MemoryStream();
×
724
        using var zipStream = new GZipStream(compressedStream, CompressionMode.Compress);
×
725
        zipStream.Write(data, 0, data.Length);
×
726
        zipStream.Close();
×
727
        return compressedStream.ToArray();
×
728
    }
×
729

730
    /// <summary>
731
    /// UnGZip using integrated .NET compression library.
732
    /// </summary>
733
    public static byte[] Decompress(this byte[] data)
734
    {
735
        if (data.IsNullOrEmpty()) return data;
×
736
        using var compressedStream = new MemoryStream(data);
×
737
        using var zipStream = new GZipStream(compressedStream, CompressionMode.Decompress);
×
738
        using var resultStream = new MemoryStream();
×
739
        zipStream.CopyTo(resultStream);
×
740
        return resultStream.ToArray();
×
741
    }
×
742

743
    /// <summary>
744
    /// Converts a UTF-8 string to its Base64 representation.
745
    /// </summary>
746
    public static string ToBase64(this string thisString)
747
    {
748
        var bytes = Encoding.UTF8.GetBytes(thisString);
×
749
        return Convert.ToBase64String(bytes);
×
750
    }
751

752
    /// <summary>
753
    /// Converts a byte count to kilobytes.
754
    /// </summary>
UNCOV
755
    public static double GetSizeInKB(this long bytes) => bytes / 1024d;
×
756

757
    /// <summary>
758
    /// Converts a byte count to megabytes.
759
    /// </summary>
UNCOV
760
    public static double GetSizeInMB(this long bytes) => bytes.GetSizeInKB() / 1024;
×
761

762
    /// <summary>
763
    /// Converts a byte count to gigabytes.
764
    /// </summary>
UNCOV
765
    public static double GetSizeInGB(this long bytes) => bytes.GetSizeInMB() / 1024;
×
766

767
    /// <summary>
768
    /// Split a string by ';' characters. Accepts nulls :)
769
    /// </summary>
770
    public static string[] Split(this string _s, char sep = ';')
771
    {
772
        return (_s ?? string.Empty).Split([sep], StringSplitOptions.RemoveEmptyEntries);
×
773
    }
774

775
    /// <summary>
776
    /// Returns the last <paramref name="N"/> elements of the sequence.
777
    /// </summary>
UNCOV
778
    public static IEnumerable<T> TakeLast<T>(this IEnumerable<T> source, int N) => source.Skip(Math.Max(0, source.Count() - N));
×
779

NEW
780
    private static readonly Regex rgxSanitize = new("[\\~#%&*{}/:<>?|\"-]", RegexOptions.IgnoreCase | RegexOptions.Compiled, _regexTimeout);
×
781

NEW
782
    private static readonly Regex rgxMultipleSpaces = new(@"\s+", RegexOptions.IgnoreCase | RegexOptions.Compiled, _regexTimeout);
×
783

784
    private const string singleSpace = " ";
785

786
    /// <summary>
787
    /// Strips characters that are non-conducive to being in a file name.
788
    /// </summary>
789
    public static string? Sanitize(this string? input, string replacement = singleSpace)
790
    {
791
        if (input is null) return input;
×
792
        var sanitized = rgxSanitize.Replace(input, replacement);
×
793
        return replacement == singleSpace ? rgxMultipleSpaces.Replace(sanitized, replacement) : sanitized;
×
794
    }
795
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc