| | | 1 | | using System; |
| | | 2 | | |
| | | 3 | | namespace AmbientServices; |
| | | 4 | | |
| | | 5 | | /// <summary> |
| | | 6 | | /// A static class that provides utility functions for managing a time-based sampling window. |
| | | 7 | | /// </summary> |
| | | 8 | | /// <remarks> |
| | | 9 | | /// <pitch>Compact, human-readable labels for time windows: <see cref="WindowId"/> names which window a moment falls in |
| | | 10 | | /// <pledge>Window identifiers are derived from the invariant-culture universal sortable format of the given <see cref=" |
| | | 11 | | /// <plan>Pure string slicing of the fixed-position "u" format at per-resolution-bucket positions; durations pick their |
| | | 12 | | /// </remarks> |
| | | 13 | | public static class WindowScope |
| | | 14 | | { |
| | | 15 | | /// <summary> |
| | | 16 | | /// Gets a timestamp for the specified <see cref="DateTime"/>, with a resolution appropriate for the units near the |
| | | 17 | | /// </summary> |
| | | 18 | | /// <param name="dateTime">The <see cref="DateTime"/> to get an window identifier for.</param> |
| | | 19 | | /// <param name="resolution">A <see cref="TimeSpan"/> indicating what type of resolution is needed.</param> |
| | | 20 | | /// <returns>A string containing a timespan that will be distinguishable from timestamps for other <see cref="DateTi |
| | | 21 | | public static string WindowId(DateTime dateTime, TimeSpan resolution) |
| | | 22 | | { |
| | 2 | 23 | | string timestamp = dateTime.ToString("u", System.Globalization.CultureInfo.InvariantCulture).TrimEnd('Z').Replac |
| | 2 | 24 | | if (resolution > TimeSpan.FromDays(365)) |
| | | 25 | | { |
| | 2 | 26 | | timestamp = timestamp.Substring(0, 7); |
| | | 27 | | } |
| | 2 | 28 | | else if (resolution > TimeSpan.FromDays(30)) |
| | | 29 | | { |
| | 2 | 30 | | timestamp = timestamp.Substring(5, 5); |
| | | 31 | | } |
| | 2 | 32 | | else if (resolution > TimeSpan.FromDays(1)) |
| | | 33 | | { |
| | 2 | 34 | | timestamp = timestamp.Substring(8, 5); |
| | | 35 | | } |
| | 2 | 36 | | else if (resolution > TimeSpan.FromHours(1)) |
| | | 37 | | { |
| | 2 | 38 | | timestamp = timestamp.Substring(11, 5); |
| | | 39 | | } |
| | 2 | 40 | | else if (resolution > TimeSpan.FromMinutes(1)) |
| | | 41 | | { |
| | 2 | 42 | | timestamp = timestamp.Substring(11, 8); |
| | | 43 | | } |
| | 2 | 44 | | else if (resolution > TimeSpan.FromSeconds(1)) |
| | | 45 | | { |
| | 2 | 46 | | timestamp = string.Concat(timestamp.Substring(14), ".", dateTime.Millisecond.ToString(System.Globalization.C |
| | | 47 | | } |
| | | 48 | | else |
| | | 49 | | { |
| | 2 | 50 | | timestamp = string.Concat(timestamp.Substring(17), ".", dateTime.Millisecond.ToString(System.Globalization.C |
| | | 51 | | } |
| | 2 | 52 | | return timestamp; |
| | | 53 | | } |
| | | 54 | | private static string UnitString(string prefix, double count, string postfix) |
| | | 55 | | { |
| | 2 | 56 | | double rounded = Math.Round(count, 1); |
| | 2 | 57 | | int intPart = (int)rounded; |
| | 2 | 58 | | int firstDecimal = (int)((rounded * 10) - (intPart * 10)); |
| | 2 | 59 | | if (intPart < 10 && firstDecimal != 0) |
| | | 60 | | { |
| | 2 | 61 | | return prefix + intPart.ToString(System.Globalization.CultureInfo.InvariantCulture) + "." + firstDecimal.ToS |
| | | 62 | | } |
| | 2 | 63 | | return prefix + Math.Round(count, 0).ToString(System.Globalization.CultureInfo.InvariantCulture) + postfix; |
| | | 64 | | } |
| | | 65 | | /// <summary> |
| | | 66 | | /// Gets a short string representing the specified timespan. |
| | | 67 | | /// </summary> |
| | | 68 | | /// <param name="duration">The <see cref="TimeSpan"/> whose string representation is to be generated</param> |
| | | 69 | | /// <returns>An easily human-readable string representing the time span with a postfix character indicating the unit |
| | | 70 | | public static string WindowSize(TimeSpan duration) |
| | | 71 | | { |
| | | 72 | | string sign; |
| | | 73 | | TimeSpan absTimeSpan; |
| | 2 | 74 | | if (duration.Ticks < 0) |
| | | 75 | | { |
| | 2 | 76 | | sign = "-"; |
| | 2 | 77 | | absTimeSpan = new TimeSpan(-duration.Ticks); |
| | | 78 | | } |
| | | 79 | | else |
| | | 80 | | { |
| | 2 | 81 | | sign = ""; |
| | 2 | 82 | | absTimeSpan = new TimeSpan(duration.Ticks); |
| | | 83 | | } |
| | 2 | 84 | | if (absTimeSpan.TotalDays > 1827) |
| | | 85 | | { |
| | 2 | 86 | | return UnitString(sign, absTimeSpan.TotalDays / 365.25, "Y"); |
| | | 87 | | } |
| | 2 | 88 | | if (absTimeSpan.TotalDays > 160) |
| | | 89 | | { |
| | 2 | 90 | | return UnitString(sign, absTimeSpan.TotalDays / 30.5, "M"); |
| | | 91 | | } |
| | 2 | 92 | | if (absTimeSpan.TotalDays > 5) |
| | | 93 | | { |
| | 2 | 94 | | return UnitString(sign, absTimeSpan.TotalDays, "D"); |
| | | 95 | | } |
| | 2 | 96 | | if (absTimeSpan.TotalHours > 5) |
| | | 97 | | { |
| | 2 | 98 | | return UnitString(sign, absTimeSpan.TotalHours, "h"); |
| | | 99 | | } |
| | 2 | 100 | | if (absTimeSpan.TotalMinutes > 5) |
| | | 101 | | { |
| | 2 | 102 | | return UnitString(sign, absTimeSpan.TotalMinutes, "m"); |
| | | 103 | | } |
| | 2 | 104 | | if (absTimeSpan.TotalSeconds > 5) |
| | | 105 | | { |
| | 2 | 106 | | return UnitString(sign, absTimeSpan.TotalSeconds, "s"); |
| | | 107 | | } |
| | 2 | 108 | | return UnitString(sign, absTimeSpan.TotalMilliseconds, "ms"); |
| | | 109 | | } |
| | | 110 | | } |