1 /++ 2 + Print human readable units (e.g. time in days, hours, minutes or distance in km, m, cm, mm). 3 +/ 4 module unit; 5 6 /++ 7 + A unit allows to easily print mixed resolution values. 8 + Typical examples include time (with hours, minutes, ...) 9 + and distances (with km, m, cm, mm). 10 + The unitclass simplifies definition of such things as well 11 + as transforming a high resolution value, to a supposedly 12 + more human readable representation. 13 + e.g. 3_610_123 would convert to 1h 0m 10s 123ms. 14 +/ 15 public struct Unit 16 { 17 import std.algorithm.iteration; 18 import std.range; 19 20 /++ 21 + A scale is one resolution of a unit. 22 +/ 23 public struct Scale 24 { 25 /// the name of the scale (e.g. h for hour) 26 string name; 27 28 /++ factor to the next higher resolution (e.g. 60 from minutes to seconds) +/ 29 long factor; 30 31 /++ normal renderwidth for the application (e.g. 2 for minutes (00-59)) +/ 32 int digits; 33 } 34 35 /++ factory for Scale 36 + Params: 37 + name = unitname 38 + factor = factor to the next bigger unit 39 + digits = padding digits 40 +/ 41 static auto scale(string name, long factor, int digits = 1) 42 { 43 return Scale(name, factor, digits); 44 } 45 46 /++ 47 + One part of the transformed Unit. 48 + a part of a unit is e.g. the minute resolution of a duration. 49 +/ 50 public struct Part 51 { 52 /// name of the part 53 string name; 54 /// value of the part 55 long value; 56 /// number of digits 57 int digits; 58 /// convenient tostring function. e.g. 10min 59 auto toString() 60 { 61 import std.conv; 62 63 return value.to!(string) ~ name; 64 } 65 } 66 67 /// name of the unit 68 private string name; 69 /// resolutions of the unit 70 private Scale[] scales; 71 72 public this(string name, Scale[] scales) 73 { 74 import std.exception; 75 76 this.name = name; 77 this.scales = cumulativeFold!((result, x) => scale(x.name, 78 result.factor * x.factor, x.digits))(scales).array.retro.array; 79 enforce(__ctfe); 80 } 81 82 /++ 83 + transforms the unit to its parts 84 +/ 85 public auto transform(long v) immutable 86 { 87 import std.array; 88 89 auto res = appender!(Part[]); 90 auto tmp = v; 91 foreach (Scale scale; scales) 92 { 93 auto h = tmp / scale.factor; 94 tmp = v % scale.factor; 95 res.put(Part(scale.name, h, scale.digits)); 96 } 97 return res.data; 98 } 99 } 100 101 @("creatingScales") unittest 102 { 103 import unit_threaded; 104 105 auto s = Unit.scale("ttt", 1, 2); 106 s.digits.shouldEqual(2); 107 108 s = Unit.scale("ttt2", 1); 109 s.digits.shouldEqual(1); 110 } 111 112 /++ 113 + get only relevant parts of an part array. 114 + relevant means all details starting from the first 115 + non 0 part. 116 +/ 117 auto onlyRelevant(Unit.Part[] parts) 118 { 119 import std.array; 120 121 auto res = appender!(Unit.Part[]); 122 bool needed = false; 123 foreach (part; parts) 124 { 125 if (needed || (part.value > 0)) 126 { 127 needed = true; 128 } 129 if (needed) 130 { 131 res.put(part); 132 } 133 } 134 return res.data; 135 } 136 137 /++ 138 + get the first nr of parts (or less if not enough parts are available). 139 +/ 140 auto mostSignificant(Unit.Part[] parts, long nr) 141 { 142 import std.algorithm.comparison; 143 144 auto max = min(parts.length, nr); 145 return parts[0 .. max]; 146 } 147 148 /++ 149 + example for a time unit definition 150 +/ 151 @("basicUsage") unittest 152 { 153 import unit_threaded; 154 155 auto res = TIME.transform(1 + 2 * 1000 + 3 * 1000 * 60 + 4 * 1000 * 60 * 60 156 + 5 * 1000 * 60 * 60 * 24); 157 res.length.shouldEqual(5); 158 res[0].name.shouldEqual("d"); 159 res[0].value.shouldEqual(5); 160 res[1].name.shouldEqual("h"); 161 res[1].value.shouldEqual(4); 162 res[2].name.shouldEqual("m"); 163 res[2].value.shouldEqual(3); 164 res[3].name.shouldEqual("s"); 165 res[3].value.shouldEqual(2); 166 res[4].name.shouldEqual("ms"); 167 res[4].value.shouldEqual(1); 168 169 res = TIME.transform(2001).onlyRelevant; 170 res.length.shouldEqual(2); 171 res[0].name.shouldEqual("s"); 172 res[0].value.shouldEqual(2); 173 res[1].name.shouldEqual("ms"); 174 res[1].value.shouldEqual(1); 175 176 res = TIME.transform(2001).onlyRelevant.mostSignificant(1); 177 res.length.shouldEqual(1); 178 res[0].name.shouldEqual("s"); 179 res[0].value.shouldEqual(2); 180 } 181 182 // dfmt off 183 static immutable TIME = 184 Unit("time", 185 [Unit.Scale("ms", 1), 186 Unit.Scale("s", 1000), 187 Unit.Scale("m", 60), 188 Unit.Scale("h", 60), 189 Unit.Scale("d", 24)]); 190 // dfmt on