1 /******************************************************************************* 2 3 copyright: Copyright (c) 2005 John Chapman. All rights reserved 4 5 license: BSD style: $(LICENSE) 6 7 version: Initial release: 2005 8 9 author: John Chapman 10 11 ******************************************************************************/ 12 13 module tango.text.locale.Parse; 14 15 private import tango.time.WallClock; 16 17 private import tango.core.Exception; 18 19 private import tango.text.locale.Core; 20 21 private import tango.time.chrono.Calendar; 22 23 private struct DateTimeParseResult { 24 25 int year = -1; 26 int month = -1; 27 int day = -1; 28 int hour; 29 int minute; 30 int second; 31 double fraction; 32 int timeMark; 33 Calendar calendar; 34 TimeSpan timeZoneOffset; 35 Time parsedDate; 36 37 } 38 39 package Time parseTime(const(char)[] s, DateTimeFormat dtf) { 40 DateTimeParseResult result; 41 if (!tryParseExactMultiple(s, dtf.getAllDateTimePatterns(), dtf, result)) 42 throw new IllegalArgumentException("String was not a valid Time."); 43 return result.parsedDate; 44 } 45 46 package Time parseTimeExact(const(char)[] s, const(char)[] format, DateTimeFormat dtf) { 47 DateTimeParseResult result; 48 if (!tryParseExact(s, format, dtf, result)) 49 throw new IllegalArgumentException("String was not a valid Time."); 50 return result.parsedDate; 51 } 52 53 package bool tryParseTime(const(char)[] s, DateTimeFormat dtf, out Time result) { 54 result = Time.min; 55 DateTimeParseResult resultRecord; 56 if (!tryParseExactMultiple(s, dtf.getAllDateTimePatterns(), dtf, resultRecord)) 57 return false; 58 result = resultRecord.parsedDate; 59 return true; 60 } 61 62 package bool tryParseTimeExact(const(char)[] s, const(char)[] format, DateTimeFormat dtf, out Time result) { 63 result = Time.min; 64 DateTimeParseResult resultRecord; 65 if (!tryParseExact(s, format, dtf, resultRecord)) 66 return false; 67 result = resultRecord.parsedDate; 68 return true; 69 } 70 71 private bool tryParseExactMultiple(const(char)[] s, const(char[])[] formats, DateTimeFormat dtf, ref DateTimeParseResult result) { 72 foreach (const(char)[] format; formats) { 73 if (tryParseExact(s, format, dtf, result)) 74 return true; 75 } 76 return false; 77 } 78 79 private bool tryParseExact(const(char)[] s, const(char)[] pattern, DateTimeFormat dtf, ref DateTimeParseResult result) { 80 81 bool doParse() { 82 83 int parseDigits(const(char)[] s, ref int pos, int max) { 84 int result = s[pos++] - '0'; 85 while (max > 1 && pos < s.length && s[pos] >= '0' && s[pos] <= '9') { 86 result = result * 10 + s[pos++] - '0'; 87 --max; 88 } 89 return result; 90 } 91 92 bool parseOne(const(char)[] s, ref int pos, const(char)[] value) { 93 if (s[pos .. pos + value.length] != value) 94 return false; 95 pos += value.length; 96 return true; 97 } 98 99 int parseMultiple(const(char)[] s, ref int pos, const(char[])[] values ...) { 100 int result = -1; 101 size_t max; 102 foreach (i, const(char)[] value; values) { 103 if (value.length == 0 || s.length - pos < value.length) 104 continue; 105 106 if (s[pos .. pos + value.length] == value) { 107 if (result == 0 || value.length > max) { 108 result = cast(int) i + 1; 109 max = value.length; 110 } 111 } 112 } 113 pos += max; 114 return result; 115 } 116 117 TimeSpan parseTimeZoneOffset(const(char)[] s, ref int pos) { 118 bool sign; 119 if (pos < s.length) { 120 if (s[pos] == '-') { 121 sign = true; 122 pos++; 123 } 124 else if (s[pos] == '+') 125 pos++; 126 } 127 int hour = parseDigits(s, pos, 2); 128 int minute; 129 if (pos < s.length && s[pos] == ':') { 130 pos++; 131 minute = parseDigits(s, pos, 2); 132 } 133 //Due to dmd bug, this doesn't compile 134 //TimeSpan result = TimeSpan.hours(hour) + TimeSpan.minutes(minute); 135 TimeSpan result = TimeSpan(TimeSpan.TicksPerHour * hour + TimeSpan.TicksPerMinute * minute); 136 if (sign) 137 result = -result; 138 return result; 139 } 140 141 char[] stringOf(char c, int count = 1) { 142 char[] s = new char[count]; 143 s[0 .. count] = c; 144 return s; 145 } 146 147 result.calendar = dtf.calendar; 148 result.year = result.month = result.day = -1; 149 result.hour = result.minute = result.second = 0; 150 result.fraction = 0.0; 151 152 int pos, i, count; 153 char c; 154 155 while (pos < pattern.length && i < s.length) { 156 c = pattern[pos++]; 157 158 if (c == ' ') { 159 i++; 160 while (i < s.length && s[i] == ' ') 161 i++; 162 if (i >= s.length) 163 break; 164 continue; 165 } 166 167 count = 1; 168 169 switch (c) { 170 case 'd': case 'm': case 'M': case 'y': 171 case 'h': case 'H': case 's': 172 case 't': case 'z': 173 while (pos < pattern.length && pattern[pos] == c) { 174 pos++; 175 count++; 176 } 177 break; 178 case ':': 179 if (!parseOne(s, i, dtf.timeSeparator)) 180 return false; 181 continue; 182 case '/': 183 if (!parseOne(s, i, dtf.dateSeparator)) 184 return false; 185 continue; 186 case '\\': 187 if (pos < pattern.length) { 188 c = pattern[pos++]; 189 if (s[i++] != c) 190 return false; 191 } 192 else 193 return false; 194 continue; 195 case '\'': 196 while (pos < pattern.length) { 197 c = pattern[pos++]; 198 if (c == '\'') 199 break; 200 if (s[i++] != c) 201 return false; 202 } 203 continue; 204 default: 205 if (s[i++] != c) 206 return false; 207 continue; 208 } 209 210 switch (c) { 211 case 'd': // day 212 if (count == 1 || count == 2) 213 result.day = parseDigits(s, i, 2); 214 else if (count == 3) 215 result.day = parseMultiple(s, i, dtf.abbreviatedDayNames); 216 else 217 result.day = parseMultiple(s, i, dtf.dayNames); 218 if (result.day == -1) 219 return false; 220 break; 221 case 'M': // month 222 if (count == 1 || count == 2) 223 result.month = parseDigits(s, i, 2); 224 else if (count == 3) 225 result.month = parseMultiple(s, i, dtf.abbreviatedMonthNames); 226 else 227 result.month = parseMultiple(s, i, dtf.monthNames); 228 if (result.month == -1) 229 return false; 230 break; 231 case 'y': // year 232 if (count == 1 || count == 2) 233 result.year = parseDigits(s, i, 2); 234 else 235 result.year = parseDigits(s, i, 4); 236 if (result.year == -1) 237 return false; 238 break; 239 case 'h': // 12-hour clock 240 case 'H': // 24-hour clock 241 result.hour = parseDigits(s, i, 2); 242 break; 243 case 'm': // minute 244 result.minute = parseDigits(s, i, 2); 245 break; 246 case 's': // second 247 result.second = parseDigits(s, i, 2); 248 break; 249 case 't': // time mark 250 if (count == 1) 251 result.timeMark = parseMultiple(s, i, stringOf(dtf.amDesignator[0]), stringOf(dtf.pmDesignator[0])); 252 else 253 result.timeMark = parseMultiple(s, i, dtf.amDesignator, dtf.pmDesignator); 254 break; 255 case 'z': 256 result.timeZoneOffset = parseTimeZoneOffset(s, i); 257 break; 258 default: 259 break; 260 } 261 } 262 263 if (pos < pattern.length || i < s.length) 264 return false; 265 266 if (result.timeMark == 1) { // am 267 if (result.hour == 12) 268 result.hour = 0; 269 } 270 else if (result.timeMark == 2) { // pm 271 if (result.hour < 12) 272 result.hour += 12; 273 } 274 275 // If the input string didn't specify a date part, try to return something meaningful. 276 if (result.year == -1 || result.month == -1 || result.day == -1) { 277 Time now = WallClock.now; 278 if (result.month == -1 && result.day == -1) { 279 if (result.year == -1) { 280 result.year = result.calendar.getYear(now); 281 result.month = result.calendar.getMonth(now); 282 result.day = result.calendar.getDayOfMonth(now); 283 } 284 else 285 result.month = result.day = 1; 286 } 287 else { 288 if (result.year == -1) 289 result.year = result.calendar.getYear(now); 290 if (result.month == -1) 291 result.month = 1; 292 if (result.day == -1) 293 result.day = 1; 294 } 295 } 296 return true; 297 } 298 299 if (doParse()) { 300 result.parsedDate = result.calendar.toTime(result.year, result.month, result.day, result.hour, result.minute, result.second, 0); 301 return true; 302 } 303 return false; 304 }