1 /** 2 * Time formatting functions. 3 * 4 * License: 5 * This Source Code Form is subject to the terms of 6 * the Mozilla Public License, v. 2.0. If a copy of 7 * the MPL was not distributed with this file, You 8 * can obtain one at http://mozilla.org/MPL/2.0/. 9 * 10 * Authors: 11 * Vladimir Panteleev <vladimir@thecybershadow.net> 12 */ 13 14 module ae.utils.time.format; 15 16 import std.algorithm.comparison; 17 import std.conv : text; 18 import std.datetime; 19 import std.format; 20 import std.math : abs; 21 22 import ae.utils.meta; 23 import ae.utils.text; 24 import ae.utils.textout; 25 import ae.utils.time.common; 26 27 private struct FormatContext(Char) 28 { 29 SysTime t; 30 DateTime dt; 31 bool escaping; 32 } 33 34 private void putToken(alias c, alias context, alias sink)() 35 { 36 with (context) 37 { 38 void putOneDigit(uint i) 39 { 40 debug assert(i < 10); 41 sink.put(cast(char)('0' + i)); 42 } 43 44 void putOneOrTwoDigits(uint i) 45 { 46 debug assert(i < 100); 47 if (i >= 10) 48 { 49 sink.put(cast(char)('0' + (i / 10))); 50 sink.put(cast(char)('0' + (i % 10))); 51 } 52 else 53 sink.put(cast(char)('0' + i )); 54 } 55 56 void putTimezoneName(string tzStr) 57 { 58 if (tzStr.length) 59 sink.put(tzStr[0..min($, MaxTimezoneNameLength)]); 60 else 61 // if (t.timezone.utcToTZ(t.stdTime) == t.stdTime) 62 // sink.put("UTC"); 63 // else 64 { 65 enum fmt = 'P'; 66 putToken!(fmt, context, sink)(); 67 } 68 } 69 70 if (escaping) 71 sink.put(c), escaping = false; 72 else 73 switch (c) 74 { 75 // Day 76 case 'd': 77 sink.put(toDecFixed!2(dt.day)); 78 break; 79 case 'D': 80 sink.put(WeekdayShortNames[dt.dayOfWeek]); 81 break; 82 case 'j': 83 putOneOrTwoDigits(dt.day); 84 break; 85 case 'l': 86 sink.put(WeekdayLongNames[dt.dayOfWeek]); 87 break; 88 case 'N': 89 putOneDigit((dt.dayOfWeek+6)%7 + 1); 90 break; 91 case 'S': 92 switch (dt.day) 93 { 94 case 1: 95 case 21: 96 case 31: 97 sink.put("st"); 98 break; 99 case 2: 100 case 22: 101 sink.put("nd"); 102 break; 103 case 3: 104 case 23: 105 sink.put("rd"); 106 break; 107 default: 108 sink.put("th"); 109 } 110 break; 111 case 'w': 112 putOneDigit(cast(int)dt.dayOfWeek); 113 break; 114 case 'z': 115 sink.put(text(dt.dayOfYear-1)); 116 break; 117 118 // Week 119 case 'W': 120 sink.put(toDecFixed!2(dt.isoWeek)); 121 break; 122 123 // Month 124 case 'F': 125 sink.put(MonthLongNames[dt.month-1]); 126 break; 127 case 'm': 128 sink.put(toDecFixed!2(dt.month)); 129 break; 130 case 'M': 131 sink.put(MonthShortNames[dt.month-1]); 132 break; 133 case 'n': 134 putOneOrTwoDigits(dt.month); 135 break; 136 case 't': 137 putOneOrTwoDigits(dt.daysInMonth); 138 break; 139 140 // Year 141 case 'L': 142 sink.put(dt.isLeapYear ? '1' : '0'); 143 break; 144 // case 'o': TODO (ISO 8601 year number) 145 case 'Y': 146 sink.put(toDecFixed!4(cast(uint)dt.year)); // Hack? Assumes years are in 1000-9999 AD range 147 break; 148 case 'y': 149 sink.put(toDecFixed!2(cast(uint)dt.year % 100)); 150 break; 151 152 // Time 153 case 'a': 154 sink.put(dt.hour < 12 ? "am" : "pm"); 155 break; 156 case 'A': 157 sink.put(dt.hour < 12 ? "AM" : "PM"); 158 break; 159 // case 'B': TODO (Swatch Internet time) 160 case 'g': 161 putOneOrTwoDigits((dt.hour+11)%12 + 1); 162 break; 163 case 'G': 164 putOneOrTwoDigits(dt.hour); 165 break; 166 case 'h': 167 sink.put(toDecFixed!2(cast(uint)(dt.hour+11)%12 + 1)); 168 break; 169 case 'H': 170 sink.put(toDecFixed!2(dt.hour)); 171 break; 172 case 'i': 173 sink.put(toDecFixed!2(dt.minute)); 174 break; 175 case 's': 176 sink.put(toDecFixed!2(dt.second)); 177 break; 178 case 'u': 179 sink.put(toDecFixed!6(cast(uint)t.fracSecs.split!"usecs".usecs)); 180 break; 181 case 'E': // not standard 182 sink.put(toDecFixed!3(cast(uint)t.fracSecs.split!"msecs".msecs)); 183 break; 184 185 // Timezone 186 case 'e': 187 putTimezoneName(t.timezone.name); 188 break; 189 case 'I': 190 sink.put(t.dstInEffect ? '1': '0'); 191 break; 192 case 'O': 193 { 194 auto minutes = (t.timezone.utcToTZ(t.stdTime) - t.stdTime) / 10_000_000 / 60; 195 sink.reference.formattedWrite("%+03d%02d", minutes/60, abs(minutes%60)); 196 break; 197 } 198 case 'P': 199 { 200 auto minutes = (t.timezone.utcToTZ(t.stdTime) - t.stdTime) / 10_000_000 / 60; 201 sink.reference.formattedWrite("%+03d:%02d", minutes/60, abs(minutes%60)); 202 break; 203 } 204 case 'T': 205 putTimezoneName(t.timezone.stdName); 206 break; 207 case 'Z': 208 sink.putDecimal((t.timezone.utcToTZ(t.stdTime) - t.stdTime) / 10_000_000); 209 break; 210 211 // Full date/time 212 case 'c': 213 sink.put(dt.toISOExtString()); 214 break; 215 case 'r': 216 putTime(sink, t, TimeFormats.RFC2822); 217 break; 218 case 'U': 219 sink.putDecimal(t.toUnixTime()); 220 break; 221 222 // Escape next character 223 case '\\': 224 escaping = true; 225 break; 226 227 // Other characters (whitespace, delimiters) 228 default: 229 put(sink, c); 230 } 231 } 232 } 233 234 /// Format a SysTime using the format spec fmt. 235 /// This version generates specialized code for the given fmt. 236 string formatTime(string fmt)(SysTime t) 237 { 238 enum maxSize = timeFormatSize(fmt); 239 auto result = StringBuilder(maxSize); 240 putTime!fmt(result, t); 241 return result.get(); 242 } 243 244 /// ditto 245 void putTime(string fmt, S)(ref S sink, SysTime t) 246 if (IsStringSink!S) 247 { 248 putTimeImpl!fmt(sink, t); 249 } 250 251 /// Format a SysTime using the format spec fmt. 252 /// This version parses fmt at runtime. 253 string formatTime(SysTime t, string fmt) 254 { 255 auto result = StringBuilder(timeFormatSize(fmt)); 256 putTime(result, t, fmt); 257 return result.get(); 258 } 259 260 /// ditto 261 deprecated string formatTime(string fmt, SysTime t = Clock.currTime()) 262 { 263 auto result = StringBuilder(48); 264 putTime(result, fmt, t); 265 return result.get(); 266 } 267 268 /// ditto 269 void putTime(S)(ref S sink, SysTime t, string fmt) 270 if (IsStringSink!S) 271 { 272 putTimeImpl!fmt(sink, t); 273 } 274 275 /// ditto 276 deprecated void putTime(S)(ref S sink, string fmt, SysTime t = Clock.currTime()) 277 if (IsStringSink!S) 278 { 279 putTimeImpl!fmt(sink, t); 280 } 281 282 void putTimeImpl(alias fmt, S)(ref S sink, SysTime t) 283 { 284 FormatContext!(char) context; 285 context.t = t; 286 context.dt = cast(DateTime)t; 287 foreach (c; CTIterate!fmt) 288 putToken!(c, context, sink)(); 289 } 290 291 unittest 292 { 293 assert(SysTime.fromUnixTime(0, UTC()).formatTime!(TimeFormats.STD_DATE) == "Thu Jan 01 00:00:00 GMT+0000 1970"); 294 assert(SysTime(0, new immutable(SimpleTimeZone)(Duration.zero)).formatTime!"T" == "+00:00"); 295 }