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 = TimeFormatElement.timezoneOffsetWithColon; 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 TimeFormatElement.dayOfMonthZeroPadded: 77 sink.put(toDecFixed!2(dt.day)); 78 break; 79 case TimeFormatElement.dayOfWeekNameShort: 80 sink.put(WeekdayShortNames[dt.dayOfWeek]); 81 break; 82 case TimeFormatElement.dayOfMonth: 83 putOneOrTwoDigits(dt.day); 84 break; 85 case TimeFormatElement.dayOfWeekName: 86 sink.put(WeekdayLongNames[dt.dayOfWeek]); 87 break; 88 case TimeFormatElement.dayOfWeekIndexISO8601: 89 putOneDigit((dt.dayOfWeek+6)%7 + 1); 90 break; 91 case TimeFormatElement.dayOfMonthOrdinalSuffix: 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 TimeFormatElement.dayOfWeekIndex: 112 putOneDigit(cast(int)dt.dayOfWeek); 113 break; 114 case TimeFormatElement.dayOfYear: 115 sink.put(text(dt.dayOfYear-1)); 116 break; 117 118 // Week 119 case TimeFormatElement.weekOfYear: 120 sink.put(toDecFixed!2(dt.isoWeek)); 121 break; 122 123 // Month 124 case TimeFormatElement.monthName: 125 sink.put(MonthLongNames[dt.month-1]); 126 break; 127 case TimeFormatElement.monthZeroPadded: 128 sink.put(toDecFixed!2(dt.month)); 129 break; 130 case TimeFormatElement.monthNameShort: 131 sink.put(MonthShortNames[dt.month-1]); 132 break; 133 case TimeFormatElement.month: 134 putOneOrTwoDigits(dt.month); 135 break; 136 case TimeFormatElement.daysInMonth: 137 putOneOrTwoDigits(dt.daysInMonth); 138 break; 139 140 // Year 141 case TimeFormatElement.yearIsLeapYear: 142 sink.put(dt.isLeapYear ? '1' : '0'); 143 break; 144 // case TimeFormatElement.yearForWeekNumbering: TODO (ISO 8601 year number) 145 case TimeFormatElement.year: 146 sink.put(toDecFixed!4(cast(uint)dt.year)); // Hack? Assumes years are in 1000-9999 AD range 147 break; 148 case TimeFormatElement.yearOfCentury: 149 sink.put(toDecFixed!2(cast(uint)dt.year % 100)); 150 break; 151 152 // Time 153 case TimeFormatElement.ampmLower: 154 sink.put(dt.hour < 12 ? "am" : "pm"); 155 break; 156 case TimeFormatElement.ampmUpper: 157 sink.put(dt.hour < 12 ? "AM" : "PM"); 158 break; 159 // case TimeFormatElement.swatchInternetTime: TODO (Swatch Internet time) 160 case TimeFormatElement.hour12: 161 putOneOrTwoDigits((dt.hour+11)%12 + 1); 162 break; 163 case TimeFormatElement.hour: 164 putOneOrTwoDigits(dt.hour); 165 break; 166 case TimeFormatElement.hour12ZeroPadded: 167 sink.put(toDecFixed!2(cast(uint)(dt.hour+11)%12 + 1)); 168 break; 169 case TimeFormatElement.hourZeroPadded: 170 sink.put(toDecFixed!2(dt.hour)); 171 break; 172 case TimeFormatElement.minute: 173 sink.put(toDecFixed!2(dt.minute)); 174 break; 175 case TimeFormatElement.second: 176 sink.put(toDecFixed!2(dt.second)); 177 break; 178 case TimeFormatElement.microseconds: 179 sink.put(toDecFixed!6(cast(uint)t.fracSecs.split!"usecs".usecs)); 180 break; 181 case TimeFormatElement.milliseconds: 182 case TimeFormatElement.millisecondsAlt: // not standard 183 sink.put(toDecFixed!3(cast(uint)t.fracSecs.split!"msecs".msecs)); 184 break; 185 case TimeFormatElement.nanoseconds: // not standard 186 sink.put(toDecFixed!9(cast(uint)t.fracSecs.split!"nsecs".nsecs)); 187 break; 188 189 // Timezone 190 case TimeFormatElement.timezoneName: 191 putTimezoneName(t.timezone.name); 192 break; 193 case TimeFormatElement.isDST: 194 sink.put(t.dstInEffect ? '1': '0'); 195 break; 196 case TimeFormatElement.timezoneOffsetWithoutColon: 197 { 198 auto minutes = (t.timezone.utcToTZ(t.stdTime) - t.stdTime) / 10_000_000 / 60; 199 sink.reference.formattedWrite("%+03d%02d", minutes/60, abs(minutes%60)); 200 break; 201 } 202 case TimeFormatElement.timezoneOffsetWithColon: 203 { 204 auto minutes = (t.timezone.utcToTZ(t.stdTime) - t.stdTime) / 10_000_000 / 60; 205 sink.reference.formattedWrite("%+03d:%02d", minutes/60, abs(minutes%60)); 206 break; 207 } 208 case TimeFormatElement.timezoneAbbreviation: 209 putTimezoneName(t.timezone.stdName); 210 break; 211 case TimeFormatElement.timezoneOffsetSeconds: 212 sink.putDecimal((t.timezone.utcToTZ(t.stdTime) - t.stdTime) / 10_000_000); 213 break; 214 215 // Full date/time 216 case TimeFormatElement.dateTimeISO8601: 217 sink.put(dt.toISOExtString()); 218 break; 219 case TimeFormatElement.dateTimeRFC2822: 220 putTime(sink, t, TimeFormats.RFC2822); 221 break; 222 case TimeFormatElement.dateTimeUNIX: 223 sink.putDecimal(t.toUnixTime()); 224 break; 225 226 // Escape next character 227 case TimeFormatElement.escapeNextCharacter: 228 escaping = true; 229 break; 230 231 // Other characters (whitespace, delimiters) 232 default: 233 put(sink, c); 234 } 235 } 236 } 237 238 /// Format a SysTime using the format spec fmt. 239 /// This version generates specialized code for the given fmt. 240 string formatTime(string fmt)(SysTime t) 241 { 242 enum maxSize = timeFormatSize(fmt); 243 auto result = StringBuilder(maxSize); 244 putTime!fmt(result, t); 245 return result.get(); 246 } 247 248 /// ditto 249 void putTime(string fmt, S)(ref S sink, SysTime t) 250 if (isStringSink!S) 251 { 252 putTimeImpl!fmt(sink, t); 253 } 254 255 /// Format a SysTime using the format spec fmt. 256 /// This version parses fmt at runtime. 257 string formatTime(SysTime t, string fmt) 258 { 259 auto result = StringBuilder(timeFormatSize(fmt)); 260 putTime(result, t, fmt); 261 return result.get(); 262 } 263 264 /// ditto 265 deprecated string formatTime(string fmt, SysTime t = Clock.currTime()) 266 { 267 auto result = StringBuilder(48); 268 putTime(result, fmt, t); 269 return result.get(); 270 } 271 272 /// ditto 273 void putTime(S)(ref S sink, SysTime t, string fmt) 274 if (isStringSink!S) 275 { 276 putTimeImpl!fmt(sink, t); 277 } 278 279 /// ditto 280 deprecated void putTime(S)(ref S sink, string fmt, SysTime t = Clock.currTime()) 281 if (isStringSink!S) 282 { 283 putTimeImpl!fmt(sink, t); 284 } 285 286 void putTimeImpl(alias fmt, S)(ref S sink, SysTime t) 287 { 288 FormatContext!(char) context; 289 context.t = t; 290 context.dt = cast(DateTime)t; 291 foreach (c; CTIterate!fmt) 292 putToken!(c, context, sink)(); 293 } 294 295 unittest 296 { 297 assert(SysTime.fromUnixTime(0, UTC()).formatTime!(TimeFormats.STD_DATE) == "Thu Jan 01 00:00:00 GMT+0000 1970"); 298 assert(SysTime(0, new immutable(SimpleTimeZone)(Duration.zero)).formatTime!"T" == "+00:00"); 299 }