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