1 /** 2 * Time formats for string formatting and parsing. 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.common; 15 16 import ae.utils.text; 17 18 /// Based on php.net/date 19 enum TimeFormatElement : char 20 { 21 /// Year, all digits 22 year = 'Y', 23 /// Year, last 2 digits 24 yearOfCentury = 'y', 25 // /// ISO-8601 week-numbering year 26 // yearForWeekNumbering = 'o', 27 /// '1' if the year is a leap year, '0' otherwise 28 yearIsLeapYear = 'L', 29 30 /// Month index, 1 or 2 digits (1 = January) 31 month = 'n', 32 /// Month index, 2 digits with leading zeroes (01 = January) 33 monthZeroPadded = 'm', 34 /// Month name, full ("January", "February" ...) 35 monthName = 'F', 36 /// Month name, three letters ("Jan", "Feb" ...) 37 monthNameShort = 'M', 38 /// Number of days within the month, 2 digits 39 daysInMonth = 't', 40 41 /// ISO-8601 week index 42 weekOfYear = 'W', 43 44 /// Day of year (January 1st = 0) 45 dayOfYear = 'z', 46 47 /// Day of month, 1 or 2 digits 48 dayOfMonth = 'j', 49 /// Day of month, 2 digits with leading zeroes 50 dayOfMonthZeroPadded = 'd', 51 /// English ordinal suffix for the day of month, 2 characters 52 dayOfMonthOrdinalSuffix = 'S', 53 54 /// Weekday index (0 = Sunday, 1 = Monday, ... 6 = Saturday) 55 dayOfWeekIndex = 'w', 56 /// Weekday index, ISO-8601 numerical representation (1 = Monday, 2 = Tuesday, ... 7 = Sunday) 57 dayOfWeekIndexISO8601 = 'N', 58 /// Weekday name, three letters ("Mon", "Tue", ...) 59 dayOfWeekNameShort = 'D', 60 /// Weekday name, full ("Monday", "Tuesday", ...) 61 dayOfWeekName = 'l', 62 63 // /// Swatch Internet time 64 // swatchInternetTime = 'B', 65 66 /// "am" / "pm" 67 ampmLower = 'a', 68 /// "AM" / "PM" 69 ampmUpper = 'A', 70 71 /// Hour (24-hour format), 1 or 2 digits 72 hour = 'G', 73 /// Hour (24-hour format), 2 digits with leading zeroes 74 hourZeroPadded = 'H', 75 /// Hour (12-hour format), 1 or 2 digits (12 = midnight/noon) 76 hour12 = 'g', 77 /// Hour (12-hour format), 2 digits with leading zeroes (12 = midnight/noon) 78 hour12ZeroPadded = 'h', 79 80 /// Minute, 2 digits with leading zeroes 81 minute = 'i', 82 /// Second, 2 digits with leading zeroes 83 second = 's', 84 /// Milliseconds within second, 3 digits 85 milliseconds = 'v', 86 /// Milliseconds within second, 3 digits (ae extension) 87 millisecondsAlt = 'E', 88 /// Microseconds within second, 6 digits 89 microseconds = 'u', 90 91 /// Timezone identifier 92 timezoneName = 'e', 93 /// Timezone abbreviation (e.g. "EST") 94 timezoneAbbreviation = 'T', 95 /// Difference from GMT, with colon (e.g. "+02:00") 96 timezoneOffsetWithColon = 'P', 97 /// Difference from GMT, without colon (e.g. "+0200") 98 timezoneOffsetWithoutColon = 'O', 99 /// Difference from GMT in seconds 100 timezoneOffsetSeconds = 'Z', 101 /// '1' if DST is in effect, '0' otherwise 102 isDST = 'I', 103 104 /// Full ISO 8601 date/time (e.g. "2004-02-12T15:19:21+00:00") 105 dateTimeISO8601 = 'c', 106 /// Full RFC 2822 date/time (e.g. "Thu, 21 Dec 2000 16:01:07 +0200") 107 dateTimeRFC2822 = 'r', 108 /// UNIX time (seconds since January 1 1970 00:00:00 UTC) 109 dateTimeUNIX = 'U', 110 111 /// Treat the next character verbatim (copy when formatting, expect when parsing) 112 escapeNextCharacter = '\\', 113 } 114 115 struct TimeFormats 116 { 117 static: 118 const ATOM = `Y-m-d\TH:i:sP`; 119 const COOKIE = `l, d-M-y H:i:s T`; 120 const ISO8601 = `Y-m-d\TH:i:sO`; 121 const RFC822 = `D, d M y H:i:s O`; 122 const RFC850 = `l, d-M-y H:i:s T`; 123 const RFC1036 = `D, d M y H:i:s O`; 124 const RFC1123 = `D, d M Y H:i:s O`; 125 const RFC2822 = `D, d M Y H:i:s O`; 126 const RFC3339 = `Y-m-d\TH:i:sP`; 127 const RSS = `D, d M Y H:i:s O`; 128 const W3C = `Y-m-d\TH:i:sP`; 129 130 const CTIME = `D M d H:i:s Y`; /// ctime/localtime format 131 132 const HTML5DATE = `Y-m-d`; 133 134 /// Format produced by std.date.toString, e.g. "Tue Jun 07 13:23:19 GMT+0100 2011" 135 const STD_DATE = `D M d H:i:s \G\M\TO Y`; 136 } 137 138 /// We assume that no timezone will have a name longer than this. 139 /// If one does, it is truncated to this length. 140 enum MaxTimezoneNameLength = 256; 141 142 /// Calculate the maximum amount of characters needed to store a time in this format. 143 /// Can be evaluated at compile-time. 144 size_t timeFormatSize(string fmt) 145 { 146 import std.algorithm.iteration : map, reduce; 147 import std.algorithm.comparison : max; 148 149 static size_t maxLength(in string[] names) { return reduce!max(map!`a.length`(WeekdayShortNames)); } 150 151 size_t size = 0; 152 bool escaping = false; 153 foreach (char c; fmt) 154 if (escaping) 155 size++, escaping = false; 156 else 157 switch (c) 158 { 159 case TimeFormatElement.dayOfWeekIndexISO8601: 160 case TimeFormatElement.dayOfWeekIndex: 161 case TimeFormatElement.yearIsLeapYear: 162 case TimeFormatElement.isDST: 163 size++; 164 break; 165 case TimeFormatElement.dayOfMonthZeroPadded: 166 case TimeFormatElement.dayOfMonth: 167 case TimeFormatElement.dayOfMonthOrdinalSuffix: 168 case TimeFormatElement.weekOfYear: 169 case TimeFormatElement.monthZeroPadded: 170 case TimeFormatElement.month: 171 case TimeFormatElement.daysInMonth: 172 case TimeFormatElement.yearOfCentury: 173 case TimeFormatElement.ampmLower: 174 case TimeFormatElement.ampmUpper: 175 case TimeFormatElement.hour12: 176 case TimeFormatElement.hour: 177 case TimeFormatElement.hour12ZeroPadded: 178 case TimeFormatElement.hourZeroPadded: 179 case TimeFormatElement.minute: 180 case TimeFormatElement.second: 181 size += 2; 182 break; 183 case TimeFormatElement.dayOfYear: 184 case TimeFormatElement.milliseconds: 185 case TimeFormatElement.millisecondsAlt: // not standard 186 size += 3; 187 break; 188 case TimeFormatElement.year: 189 size += 4; 190 break; 191 case TimeFormatElement.timezoneOffsetSeconds: // Timezone offset in seconds 192 case TimeFormatElement.timezoneOffsetWithoutColon: 193 size += 5; 194 break; 195 case TimeFormatElement.microseconds: 196 case TimeFormatElement.timezoneOffsetWithColon: 197 size += 6; 198 break; 199 case TimeFormatElement.timezoneAbbreviation: 200 size += 32; 201 break; 202 203 case TimeFormatElement.dayOfWeekNameShort: 204 size += maxLength(WeekdayShortNames); 205 break; 206 case TimeFormatElement.dayOfWeekName: 207 size += maxLength(WeekdayLongNames); 208 break; 209 case TimeFormatElement.monthName: 210 size += maxLength(MonthLongNames); 211 break; 212 case TimeFormatElement.monthNameShort: 213 size += maxLength(MonthShortNames); 214 break; 215 216 case TimeFormatElement.timezoneName: // Timezone name 217 return MaxTimezoneNameLength; 218 219 // Full date/time 220 case TimeFormatElement.dateTimeISO8601: 221 enum ISOExtLength = "-0004-01-05T00:00:02.052092+10:00".length; 222 size += ISOExtLength; 223 break; 224 case TimeFormatElement.dateTimeRFC2822: 225 size += timeFormatSize(TimeFormats.RFC2822); 226 break; 227 case TimeFormatElement.dateTimeUNIX: 228 size += DecimalSize!int; 229 break; 230 231 // Escape next character 232 case TimeFormatElement.escapeNextCharacter: 233 escaping = true; 234 break; 235 236 // Other characters (whitespace, delimiters) 237 default: 238 size++; 239 } 240 241 return size; 242 } 243 244 static assert(timeFormatSize(TimeFormats.STD_DATE) == "Tue Jun 07 13:23:19 GMT+0100 2011".length); 245 246 // *************************************************************************** 247 248 const WeekdayShortNames = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; 249 const WeekdayLongNames = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]; 250 const MonthShortNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; 251 const MonthLongNames = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]; 252