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 }