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.ymd; 28 29 debug(dateparser) import std.stdio; 30 import std.traits; 31 import std.range; 32 33 package: 34 35 struct YMD 36 { 37 private: 38 bool century_specified = false; 39 int[3] data; 40 byte dataPosition; 41 42 public: 43 /** 44 * Params 45 */ 46 static bool couldBeYear(Range, N)(Range token, N year) if (isInputRange!Range 47 && isSomeChar!(ElementEncodingType!Range) && is(NumericTypeOf!N : int)) 48 { 49 import std.algorithm.comparison : equal; 50 import std.algorithm.mutation : stripLeft; 51 import std.ascii : isDigit; 52 import std.conv : toChars; 53 54 if (token.front.isDigit) 55 { 56 return year.toChars.equal(token.stripLeft('0')); 57 } 58 else 59 { 60 return false; 61 } 62 } 63 64 /** 65 * Attempt to deduce if a pre 100 year was lost due to padded zeros being 66 * taken off 67 * 68 * Params: 69 * tokens = a range of tokens 70 * Returns: 71 * the index of the year token. If no probable result was found, then -1 72 * is returned 73 */ 74 int probableYearIndex(Range)(Range tokens) const if (isInputRange!Range 75 && isNarrowString!(ElementType!(Range))) 76 { 77 import std.algorithm.iteration : filter; 78 import std.range : walkLength; 79 80 foreach (int index, ref item; data[]) 81 { 82 auto potentialYearTokens = tokens.filter!(a => YMD.couldBeYear(a, item)); 83 immutable frontLength = potentialYearTokens.front.length; 84 immutable length = potentialYearTokens.walkLength(2); 85 86 if (length == 1 && frontLength > 2) 87 return index; 88 } 89 90 return -1; 91 } 92 93 /// Put a value in that represents a year, month, or day 94 void put(N)(N val) if (isNumeric!N) 95 in 96 { 97 assert(dataPosition <= 3); 98 } 99 body 100 { 101 static if (is(N : int)) 102 { 103 if (val > 100) 104 this.century_specified = true; 105 106 data[dataPosition] = val; 107 ++dataPosition; 108 } 109 else 110 put(cast(int) val); 111 } 112 113 /// ditto 114 void put(S)(const S val) if (isSomeString!S) 115 in 116 { 117 assert(dataPosition <= 3); 118 } 119 body 120 { 121 import std.conv : to; 122 123 data[dataPosition] = to!int(val); 124 ++dataPosition; 125 126 if (val.length > 2) 127 this.century_specified = true; 128 } 129 130 /// length getter 131 size_t length() @property const @safe pure nothrow @nogc 132 { 133 return dataPosition; 134 } 135 136 /// century_specified getter 137 bool centurySpecified() @property const @safe pure nothrow @nogc 138 { 139 return century_specified; 140 } 141 142 /** 143 * Turns the array of ints into a `Tuple` of three, representing the year, 144 * month, and day. 145 * 146 * Params: 147 * mstridx = The index of the month in the data 148 * yearfirst = if the year is first in the string 149 * dayfirst = if the day is first in the string 150 * Returns: 151 * tuple of three ints 152 */ 153 auto resolveYMD(R, N)(R tokens, N mstridx, bool yearfirst, bool dayfirst) if (is(NumericTypeOf!N : size_t)) 154 { 155 import std.algorithm.mutation : remove; 156 import std.typecons : tuple; 157 158 int year = -1; 159 int month; 160 int day; 161 162 if (dataPosition == 1 || (mstridx != -1 && dataPosition == 2)) //One member, or two members with a month string 163 { 164 if (mstridx != -1) 165 { 166 month = data[mstridx]; 167 switch (mstridx) 168 { 169 case 0: 170 data[0] = data[1]; 171 data[1] = data[2]; 172 data[2] = 0; 173 break; 174 case 1: 175 data[1] = data[2]; 176 data[2] = 0; 177 break; 178 case 2: 179 data[2] = 0; 180 break; 181 default: break; 182 } 183 } 184 185 if (dataPosition > 1 || mstridx == -1) 186 { 187 if (data[0] > 31) 188 year = data[0]; 189 else 190 day = data[0]; 191 } 192 } 193 else if (dataPosition == 2) //Two members with numbers 194 { 195 if (data[0] > 31) 196 { 197 //99-01 198 year = data[0]; 199 month = data[1]; 200 } 201 else if (data[1] > 31) 202 { 203 //01-99 204 month = data[0]; 205 year = data[1]; 206 } 207 else if (dayfirst && data[1] <= 12) 208 { 209 //13-01 210 day = data[0]; 211 month = data[1]; 212 } 213 else 214 { 215 //01-13 216 month = data[0]; 217 day = data[1]; 218 } 219 220 } 221 else if (dataPosition == 3) //Three members 222 { 223 if (mstridx == 0) 224 { 225 month = data[0]; 226 day = data[1]; 227 year = data[2]; 228 } 229 else if (mstridx == 1) 230 { 231 if (data[0] > 31 || (yearfirst && data[2] <= 31)) 232 { 233 //99-Jan-01 234 year = data[0]; 235 month = data[1]; 236 day = data[2]; 237 } 238 else 239 { 240 //01-Jan-01 241 //Give precedence to day-first, since 242 //two-digit years is usually hand-written. 243 day = data[0]; 244 month = data[1]; 245 year = data[2]; 246 } 247 } 248 else if (mstridx == 2) 249 { 250 if (data[1] > 31) 251 { 252 //01-99-Jan 253 day = data[0]; 254 year = data[1]; 255 month = data[2]; 256 } 257 else 258 { 259 //99-01-Jan 260 year = data[0]; 261 day = data[1]; 262 month = data[2]; 263 } 264 } 265 else 266 { 267 if (data[0] > 31 || probableYearIndex(tokens) == 0 268 || (yearfirst && data[1] <= 12 && data[2] <= 31)) 269 { 270 //99-01-01 271 year = data[0]; 272 month = data[1]; 273 day = data[2]; 274 } 275 else if (data[0] > 12 || (dayfirst && data[1] <= 12)) 276 { 277 //13-01-01 278 day = data[0]; 279 month = data[1]; 280 year = data[2]; 281 } 282 else 283 { 284 //01-13-01 285 month = data[0]; 286 day = data[1]; 287 year = data[2]; 288 } 289 } 290 } 291 292 return tuple(year, month, day); 293 } 294 }