1 /**
2  * Boost Software License - Version 1.0 - August 17th, 2003
3  *
4  * Permission is hereby granted, free of charge, to any person or organization
5  * obtaining a copy of the software and accompanying documentation covered by
6  * this license (the "Software") to use, reproduce, display, distribute,
7  * execute, and transmit the Software, and to prepare derivative works of the
8  * Software, and to permit third-parties to whom the Software is furnished to
9  * do so, all subject to the following:
10  *
11  * The copyright notices in the Software and this entire statement, including
12  * the above license grant, this restriction and the following disclaimer,
13  * must be included in all copies of the Software, in whole or in part, and
14  * all derivative works of the Software, unless such copies or derivative
15  * works are solely in the form of machine-executable object code generated by
16  * a source language processor.
17  *
18  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20  * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
21  * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
22  * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
23  * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
24  * DEALINGS IN THE SOFTWARE.
25  */
26 
27 module dateparser.parserinfo;
28 
29 import std.traits;
30 import std.range;
31 import dateparser.parseresult;
32 
33 package:
34 
35 // dfmt off
36 // m from a.m/p.m, t from ISO T separator, order doesn't
37 // matter here, just a presence check
38 enum jumpDefault = [
39     "and":9, "'":6,
40     "at":7, "/":5,
41     "st":14,
42     ";":3, " ":0,
43     "of":13, "nd":15,
44     "rd":16, ".":1,
45     "th":17, "on":8,
46     "m":11, ",":2,
47     "ad":10, "-":4, "t":12
48 ];
49 enum weekdaysDefault = [
50     "mon":0, "monday":0,
51     "tue":1, "tuesday":1,
52     "wed":2, "wednesday":2,
53     "thu":3, "thursday":3,
54     "fri":4, "friday":4,
55     "sat":5, "saturday":5,
56     "sun":6, "sunday":6,
57 ];
58 enum monthsDefault = [
59     "jan":0, "january":0,
60     "feb":1, "february":1,
61     "mar":2, "march":2,
62     "apr":3, "april":3,
63     "may":4,
64     "jun":5, "june":5,
65     "jul":6, "july":6,
66     "aug":7, "august":7,
67     "sep":8, "sept":8, "september":8,
68     "oct":9, "october":9,
69     "nov":10, "november":10,
70     "dec":11, "december":11
71 ];
72 enum hmsDefault = [
73     "h":0, "hour":0, "hours":0,
74     "m":1, "minute":1, "minutes":1,
75     "s":2, "second":2, "seconds":2
76 ];
77 enum ampmDefault = [
78     "am":0, "a":0,
79     "pm":1, "p":1
80 ];
81 enum utcDefault = [
82     "UTC":0, "GMT":0, "Z":0
83 ];
84 enum pertainDefault = [
85     "of":0
86 ];
87 // dfmt on
88 
89 /**
90  * If the century isn't specified, e.g. `"'07"`, then assume that the year
91  * is in the current century and return it as such. Otherwise do nothing
92  *
93  * Params:
94  *     convertYear = year to be converted
95  *     centurySpecified = is the century given in the year
96  *
97  * Returns:
98  *     the converted year
99  */
100 int convertYear(int convertYear, bool centurySpecified = false) @safe
101 {
102     import std.math : abs;
103     import std.datetime : Clock;
104 
105     immutable year = Clock.currTime.year;
106     immutable century = (year / 100) * 100;
107 
108     if (convertYear < 100 && !centurySpecified)
109     {
110         convertYear += century;
111         if (abs(convertYear - year) >= 50)
112         {
113             if (convertYear < year)
114                 convertYear += 100;
115             else
116                 convertYear -= 100;
117         }
118     }
119 
120     return convertYear;
121 }
122 
123 public:
124 
125 /**
126 Class which handles what inputs are accepted. Subclass this to customize
127 the language and acceptable values for each parameter.
128 
129 Params:
130     dayFirst = Whether to interpret the first value in an ambiguous 3-integer date
131         (e.g. 01/05/09) as the day (`true`) or month (`false`). If
132         `yearFirst` is set to `true`, this distinguishes between YDM
133         and YMD. Default is `false`.
134     yearFirst = Whether to interpret the first value in an ambiguous 3-integer date
135         (e.g. 01/05/09) as the year. If `true`, the first number is taken
136         to be the year, otherwise the last number is taken to be the year.
137         Default is `false`.
138 */
139 class ParserInfo
140 {
141 private:
142     bool dayFirst;
143     bool yearFirst;
144 
145 package:
146     /**
147      * Takes and Result and converts it year and checks if the timezone is UTC
148      */
149     final void validate(ref ParseResult res) @safe const
150     {
151         //move to info
152         if (!res.year.isNull)
153             res.year = convertYear(res.year, res.centurySpecified);
154 
155         if ((!res.tzoffset.isNull && res.tzoffset == 0)
156                 && (res.tzname.length == 0 || res.tzname == "Z"))
157         {
158             res.tzname = "UTC";
159             res.tzoffset = 0;
160         }
161         else if (!res.tzoffset.isNull && res.tzoffset != 0 && res.tzname.length > 0
162                  && this.utczone(res.tzname))
163             res.tzoffset = 0;
164     }
165 
166 public:
167     /**
168      * AAs used for matching strings to calendar numbers, e.g. Jan is 1.
169      *
170      * `jumpAA`, `utczoneAA`, and `pertainAA` are only used to check the
171      * presence of a key; the value of the key doesn't matter.
172      */
173     int[string] jumpAA;
174     ///ditto
175     int[string] weekdaysAA;
176     ///ditto
177     int[string] monthsAA;
178     ///ditto
179     int[string] hmsAA;
180     ///ditto
181     int[string] ampmAA;
182     ///ditto
183     int[string] utczoneAA;
184     ///ditto
185     int[string] pertainAA;
186 
187     /**
188      * Take a range of character ranges or a range of ranges of character
189      * ranges and converts it to an associative array that the internal
190      * parser info methods can use.
191      *
192      * Use this method in order to override the default parser info field
193      * values. See the example on the $(REF parse).
194      *
195      * Params:
196      *     list = a range of character ranges
197      *
198      * Returns:
199      *     An associative array of `int`s accessed by strings
200      */
201     static int[string] convert(Range)(Range list) if (isInputRange!Range
202             && isSomeChar!(ElementEncodingType!(ElementEncodingType!(Range)))
203             || isSomeChar!(
204             ElementEncodingType!(ElementEncodingType!(ElementEncodingType!(Range)))))
205     {
206         import std.array : array;
207         import std.conv : to;
208         import std.uni : asLowerCase;
209 
210         int[string] dictionary;
211 
212         foreach (i, value; list)
213         {
214             // tuple of strings or multidimensional string array
215             static if (isInputRange!(ElementType!(ElementType!(Range))))
216                 foreach (item; value)
217                     dictionary[item.asLowerCase.array.to!string] = cast(int) i;
218             else
219                 dictionary[value.asLowerCase.array.to!string] = cast(int) i;
220         }
221 
222         return dictionary;
223     }
224 
225     /// Ctor
226     this(bool dayFirst = false, bool yearFirst = false) @safe
227     {
228         dayFirst = dayFirst;
229         yearFirst = yearFirst;
230 
231         jumpAA = jumpDefault;
232         weekdaysAA = weekdaysDefault;
233         monthsAA = monthsDefault;
234         hmsAA = hmsDefault;
235         ampmAA = ampmDefault;
236         utczoneAA = utcDefault;
237         pertainAA = pertainDefault;
238     }
239 
240     /// Tests for presence of `name` in each of the AAs
241     final bool jump(S)(const S name) const if (isSomeString!S)
242     {
243         import std.uni : toLower;
244         return name.toLower() in jumpAA ? true : false;
245     }
246 
247     /// ditto
248     final int weekday(S)(const S name) const if (isSomeString!S)
249     {
250         import std.uni : toLower;
251 
252         auto key = name.toLower();
253         if (key in weekdaysAA)
254             return weekdaysAA[key];
255         else
256             return -1;
257     }
258 
259     /// ditto
260     final int month(S)(const S name) const if (isSomeString!S)
261     {
262         import std.uni : toLower;
263 
264         auto key = name.toLower();
265         if (key in monthsAA)
266             return monthsAA[key] + 1;
267         else
268             return -1;
269     }
270 
271     /// ditto
272     final int hms(S)(const S name) const if (isSomeString!S)
273     {
274         import std.uni : toLower;
275 
276         auto key = name.toLower();
277         if (key in hmsAA)
278             return hmsAA[key];
279         else
280             return -1;
281     }
282 
283     /// ditto
284     final int ampm(S)(const S name) const if (isSomeString!S)
285     {
286         import std.uni : toLower;
287 
288         auto key = name.toLower();
289         if (key in ampmAA)
290             return ampmAA[key];
291         else
292             return -1;
293     }
294 
295     /// ditto
296     final bool pertain(S)(const S name) const if (isSomeString!S)
297     {
298         import std.uni : toLower;
299 
300         return name.toLower() in pertainAA ? true : false;
301     }
302 
303     /// ditto
304     final bool utczone(S)(const S name) const if (isSomeString!S)
305     {
306         import std.uni : toLower;
307 
308         return name.toLower() in utczoneAA ? true : false;
309     }
310 }