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