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 <ae@cy.md> 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 /// Common time format strings. 120 struct TimeFormats 121 { 122 static: 123 const ATOM = `Y-m-d\TH:i:sP` ; /// 124 const COOKIE = `l, d-M-y H:i:s T`; /// 125 const ISO8601 = `Y-m-d\TH:i:sO` ; /// 126 const RFC822 = `D, d M y H:i:s O`; /// 127 const RFC850 = `l, d-M-y H:i:s T`; /// 128 const RFC1036 = `D, d M y H:i:s O`; /// 129 const RFC1123 = `D, d M Y H:i:s O`; /// 130 const RFC2822 = `D, d M Y H:i:s O`; /// 131 const RFC3339 = `Y-m-d\TH:i:sP` ; /// 132 const RSS = `D, d M Y H:i:s O`; /// 133 const W3C = `Y-m-d\TH:i:sP` ; /// 134 const HTTP = `D, d M Y H:i:s \G\M\T`; /// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Date 135 136 const CTIME = `D M d H:i:s Y`; /// ctime/localtime format 137 138 const HTML5DATE = `Y-m-d`; /// As used in HTML type="date" inputs. 139 140 /// Format produced by std.date.toString, e.g. "Tue Jun 07 13:23:19 GMT+0100 2011" 141 const STD_DATE = `D M d H:i:s \G\M\TO Y`; 142 } 143 144 /// We assume that no timezone will have a name longer than this. 145 /// If one does, it is truncated to this length. 146 enum MaxTimezoneNameLength = 256; 147 148 /// Calculate the maximum amount of characters needed to store a time in this format. 149 /// Can be evaluated at compile-time. 150 size_t timeFormatSize(string fmt) 151 { 152 import std.algorithm.iteration : map, reduce; 153 import std.algorithm.comparison : max; 154 155 static size_t maxLength(in string[] names) { return reduce!max(map!`a.length`(WeekdayShortNames)); } 156 157 size_t size = 0; 158 bool escaping = false; 159 foreach (char c; fmt) 160 if (escaping) 161 size++, escaping = false; 162 else 163 switch (c) 164 { 165 case TimeFormatElement.dayOfWeekIndexISO8601: 166 case TimeFormatElement.dayOfWeekIndex: 167 case TimeFormatElement.yearIsLeapYear: 168 case TimeFormatElement.isDST: 169 size++; 170 break; 171 case TimeFormatElement.dayOfMonthZeroPadded: 172 case TimeFormatElement.dayOfMonth: 173 case TimeFormatElement.dayOfMonthOrdinalSuffix: 174 case TimeFormatElement.weekOfYear: 175 case TimeFormatElement.monthZeroPadded: 176 case TimeFormatElement.month: 177 case TimeFormatElement.daysInMonth: 178 case TimeFormatElement.yearOfCentury: 179 case TimeFormatElement.ampmLower: 180 case TimeFormatElement.ampmUpper: 181 case TimeFormatElement.hour12: 182 case TimeFormatElement.hour: 183 case TimeFormatElement.hour12ZeroPadded: 184 case TimeFormatElement.hourZeroPadded: 185 case TimeFormatElement.minute: 186 case TimeFormatElement.second: 187 size += 2; 188 break; 189 case TimeFormatElement.dayOfYear: 190 case TimeFormatElement.milliseconds: 191 case TimeFormatElement.millisecondsAlt: // not standard 192 size += 3; 193 break; 194 case TimeFormatElement.year: 195 size += 4; 196 break; 197 case TimeFormatElement.timezoneOffsetSeconds: // Timezone offset in seconds 198 case TimeFormatElement.timezoneOffsetWithoutColon: 199 size += 5; 200 break; 201 case TimeFormatElement.microseconds: 202 case TimeFormatElement.timezoneOffsetWithColon: 203 size += 6; 204 break; 205 case TimeFormatElement.nanoseconds: 206 size += 9; 207 break; 208 case TimeFormatElement.timezoneAbbreviation: 209 size += 32; 210 break; 211 212 case TimeFormatElement.dayOfWeekNameShort: 213 size += maxLength(WeekdayShortNames); 214 break; 215 case TimeFormatElement.dayOfWeekName: 216 size += maxLength(WeekdayLongNames); 217 break; 218 case TimeFormatElement.monthName: 219 size += maxLength(MonthLongNames); 220 break; 221 case TimeFormatElement.monthNameShort: 222 size += maxLength(MonthShortNames); 223 break; 224 225 case TimeFormatElement.timezoneName: // Timezone name 226 return MaxTimezoneNameLength; 227 228 // Full date/time 229 case TimeFormatElement.dateTimeISO8601: 230 enum ISOExtLength = "-0004-01-05T00:00:02.052092+10:00".length; 231 size += ISOExtLength; 232 break; 233 case TimeFormatElement.dateTimeRFC2822: 234 size += timeFormatSize(TimeFormats.RFC2822); 235 break; 236 case TimeFormatElement.dateTimeUNIX: 237 size += DecimalSize!time_t; 238 break; 239 240 // Escape next character 241 case TimeFormatElement.escapeNextCharacter: 242 escaping = true; 243 break; 244 245 // Other characters (whitespace, delimiters) 246 default: 247 size++; 248 } 249 250 return size; 251 } 252 253 static assert(timeFormatSize(TimeFormats.STD_DATE) == "Tue Jun 07 13:23:19 GMT+0100 2011".length); 254 255 // *************************************************************************** 256 257 /// English short and long weekday and month names, used when parsing and stringifying dates. 258 const WeekdayShortNames = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; 259 const WeekdayLongNames = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]; /// ditto 260 const MonthShortNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; /// ditto 261 const MonthLongNames = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]; /// ditto 262