1 module text.time.Convert; 2 3 version(unittest) import dshould; 4 import std.array; 5 import std.datetime; 6 import std..string; 7 import text.time.Lexer; 8 9 /** 10 * This service class provides functions to convert between date and time values and their representations. 11 * 12 * Standards: ISO 8601 13 * "Data elements and interchange formats — Information interchange — Representation of dates and times" 14 * @Immutable 15 */ 16 class Convert 17 { 18 19 /** 20 * Converts the specified representation into its (date and) time value. 21 * 22 * Throws: DateTimeException on syntax error. 23 */ 24 public static T to(T : SysTime)(string value) 25 { 26 return SysTime.fromISOExtString(value); 27 } 28 29 unittest 30 { 31 Convert.to!SysTime("2003-02-01T11:55:00+01:00") 32 .should.equal(SysTime(DateTime(2003, 2, 1, 11, 55), new immutable SimpleTimeZone(1.hours))); 33 34 Convert.to!SysTime("2003-02-01 11:55:00").should.throwA!DateTimeException 35 .because("missing 'T'"); 36 Convert.to!SysTime(null).should.throwA!DateTimeException; 37 } 38 39 /** 40 * Converts the specified representation into its date value. 41 * 42 * Throws: DateTimeException on syntax error. 43 */ 44 public static T to(T : Date)(string value) 45 { 46 return Date.fromISOExtString(value); 47 } 48 49 unittest 50 { 51 Convert.to!Date("2003-02-01").should.equal(Date(2003, 2, 1)); 52 53 Convert.to!Date("01.02.2003").should.throwA!DateTimeException; 54 Convert.to!Date(null).should.throwA!DateTimeException; 55 } 56 57 /** 58 * Converts the specified representation into its time-of-day value. 59 * 60 * Throws: DateTimeException on syntax error. 61 */ 62 public static T to(T : TimeOfDay)(string value) 63 { 64 return TimeOfDay.fromISOExtString(value); 65 } 66 67 unittest 68 { 69 Convert.to!TimeOfDay("01:02:03").should.equal(TimeOfDay(1, 2, 3)); 70 Convert.to!TimeOfDay("23:59:59").should.equal(TimeOfDay(23, 59, 59)); 71 72 Convert.to!TimeOfDay("1:2:3").should.throwA!DateTimeException; 73 Convert.to!TimeOfDay(null).should.throwA!DateTimeException; 74 } 75 76 /** 77 * Converts the specified representation into its duration value. 78 * For decimal fractions, digits representing less than one millisecond are disregarded. 79 * 80 * Throws: TimeException on syntax error. 81 */ 82 public static T to(T : Duration)(string value) 83 { 84 return Lexer.toDuration(value); 85 } 86 87 /** 88 * Converts the specified (date and) time value into its representation. 89 * 90 * Throws: DateTimeException when the specified time is undefined. 91 */ 92 public static string toString(SysTime time) @trusted 93 { 94 return toStringSinkProxy(time); 95 } 96 97 /// ditto 98 public static void toString(SysTime sysTime, scope void delegate(const(char)[]) sink) 99 { 100 if (sysTime == SysTime.init && sysTime.timezone is null) 101 { 102 throw new DateTimeException("time undefined"); 103 } 104 105 sysTime.fracSecs = Duration.zero; 106 sysTime.toISOExtString(sink); 107 } 108 109 unittest 110 { 111 DateTime dateTime = DateTime.fromISOExtString("2003-02-01T11:55:00"); 112 113 Convert.toString(SysTime(dateTime)).should.equal("2003-02-01T11:55:00"); 114 Convert.toString(SysTime(dateTime, UTC())).should.equal("2003-02-01T11:55:00Z"); 115 Convert.toString(SysTime(dateTime, 123.msecs)).should.equal("2003-02-01T11:55:00"); 116 117 DateTime epoch = DateTime.fromISOExtString("0001-01-01T00:00:00"); 118 119 Convert.toString(SysTime(epoch)).should.equal("0001-01-01T00:00:00"); 120 Convert.toString(SysTime(epoch, UTC())).should.equal("0001-01-01T00:00:00Z"); 121 } 122 123 /** 124 * Converts the specified date into its representation. 125 */ 126 public static string toString(Date date) @trusted 127 { 128 return toStringSinkProxy(date); 129 } 130 131 /// ditto 132 public static void toString(Date date, scope void delegate(const(char)[]) sink) 133 { 134 date.toISOExtString(sink); 135 } 136 137 @safe unittest 138 { 139 Convert.toString(Date(2003, 2, 1)).should.equal("2003-02-01"); 140 } 141 142 /** 143 * Converts the specified date and time-of-day value into its representation. 144 */ 145 public static string toString(TimeOfDay timeOfDay) @trusted 146 { 147 return toStringSinkProxy(timeOfDay); 148 } 149 150 /// ditto 151 public static void toString(TimeOfDay timeOfDay, scope void delegate(const(char)[]) sink) 152 { 153 timeOfDay.toISOExtString(sink); 154 } 155 156 @safe unittest 157 { 158 Convert.toString(TimeOfDay(1, 2, 3)).should.equal("01:02:03"); 159 } 160 161 /** 162 * Converts the specified duration value into its canonical representation. 163 */ 164 public static string toString(Duration duration) @trusted 165 { 166 return toStringSinkProxy(duration); 167 } 168 169 /// ditto 170 public static void toString(Duration duration, scope void delegate(const(char)[]) sink) 171 { 172 import std.format : formattedWrite; 173 174 if (duration < Duration.zero) 175 { 176 sink("-"); 177 duration = -duration; 178 } 179 180 auto result = duration.split!("days", "hours", "minutes", "seconds", "msecs"); 181 182 with (result) 183 { 184 sink("P"); 185 186 if (days != 0) 187 { 188 sink.formattedWrite("%sD", days); 189 } 190 191 const bool allTimesNull = hours == 0 && minutes == 0 && seconds == 0 && msecs == 0; 192 const bool allNull = allTimesNull && days == 0; 193 194 if (!allTimesNull || allNull) 195 { 196 sink("T"); 197 if (hours != 0) 198 { 199 sink.formattedWrite("%sH", hours); 200 } 201 if (minutes != 0) 202 { 203 sink.formattedWrite("%sM", minutes); 204 } 205 if (seconds != 0 || msecs != 0 || allNull) 206 { 207 sink.formattedWrite("%s", seconds); 208 sink.writeMillis(msecs); 209 sink("S"); 210 } 211 } 212 } 213 } 214 215 @safe unittest 216 { 217 Convert.toString(1.days + 2.hours + 3.minutes + 4.seconds + 500.msecs).should.equal("P1DT2H3M4.5S"); 218 Convert.toString(1.days).should.equal("P1D"); 219 Convert.toString(Duration.zero).should.equal("PT0S"); 220 Convert.toString(1.msecs).should.equal("PT0.001S"); 221 Convert.toString(-(1.hours + 2.minutes + 3.seconds + 450.msecs)).should.equal("-PT1H2M3.45S"); 222 } 223 } 224 225 private string toStringSinkProxy(T)(T t) 226 { 227 string str = null; 228 229 Convert.toString(t, (fragment) { str ~= fragment; }); 230 231 return str; 232 } 233 234 /** 235 * Converts the specified milliseconds value into a representation with as few digits as possible. 236 */ 237 private void writeMillis(scope void delegate(const(char)[]) sink, long millis) 238 in (0 <= millis && millis < 1000) 239 { 240 import std.format : formattedWrite; 241 242 if (millis == 0) 243 { 244 sink(""); 245 } 246 else if (millis % 100 == 0) 247 { 248 sink.formattedWrite(".%01d", millis / 100); 249 } 250 else if (millis % 10 == 0) 251 { 252 sink.formattedWrite(".%02d", millis / 10); 253 } 254 else 255 { 256 sink.formattedWrite(".%03d", millis); 257 } 258 }