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