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 dateparser2.ymd; 28 29 debug(dateparser2) 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 (index, ref item; data[]) 81 { 82 auto potentialYearTokens = tokens.filter!(a => YMD.couldBeYear(a, item)); 83 if(potentialYearTokens.empty) { 84 return -1; 85 } 86 immutable frontLength = potentialYearTokens.front.length; 87 immutable length = potentialYearTokens.walkLength(2); 88 89 if (length == 1 && frontLength > 2) 90 return cast(int) index; 91 } 92 93 return -1; 94 } 95 96 /// Put a value in that represents a year, month, or day 97 void put(N)(N val) if (isNumeric!N) 98 in 99 { 100 assert(dataPosition <= 3); 101 } 102 do 103 { 104 static if (is(N : int)) 105 { 106 if (val > 100) 107 this.century_specified = true; 108 109 data[dataPosition] = val; 110 ++dataPosition; 111 } 112 else 113 put(cast(int) val); 114 } 115 116 /// ditto 117 void put(S)(const S val) if (isSomeString!S) 118 in 119 { 120 assert(dataPosition <= 3); 121 } 122 do 123 { 124 import std.conv : to; 125 126 data[dataPosition] = to!int(val); 127 ++dataPosition; 128 129 if (val.length > 2) 130 this.century_specified = true; 131 } 132 133 /// length getter 134 size_t length() @property const @safe pure nothrow @nogc 135 { 136 return dataPosition; 137 } 138 139 /// century_specified getter 140 bool centurySpecified() @property const @safe pure nothrow @nogc 141 { 142 return century_specified; 143 } 144 145 /** 146 * Turns the array of ints into a `Tuple` of three, representing the year, 147 * month, and day. 148 * 149 * Params: 150 * mstridx = The index of the month in the data 151 * yearfirst = if the year is first in the string 152 * dayfirst = if the day is first in the string 153 * Returns: 154 * tuple of three ints 155 */ 156 auto resolveYMD(R, N)(R tokens, N mstridx, bool yearfirst, bool dayfirst) if (is(NumericTypeOf!N : size_t)) 157 { 158 import std.algorithm.mutation : remove; 159 import std.typecons : tuple; 160 161 int year = -1; 162 int month; 163 int day; 164 165 if (dataPosition == 1 || (mstridx != -1 && dataPosition == 2)) //One member, or two members with a month string 166 { 167 if (mstridx != -1) 168 { 169 month = data[mstridx]; 170 switch (mstridx) 171 { 172 case 0: 173 data[0] = data[1]; 174 data[1] = data[2]; 175 data[2] = 0; 176 break; 177 case 1: 178 data[1] = data[2]; 179 data[2] = 0; 180 break; 181 case 2: 182 data[2] = 0; 183 break; 184 default: break; 185 } 186 } 187 188 if (dataPosition > 1 || mstridx == -1) 189 { 190 if (data[0] > 31) 191 year = data[0]; 192 else 193 day = data[0]; 194 } 195 } 196 else if (dataPosition == 2) //Two members with numbers 197 { 198 if (data[0] > 31) 199 { 200 //99-01 201 year = data[0]; 202 month = data[1]; 203 } 204 else if (data[1] > 31) 205 { 206 //01-99 207 month = data[0]; 208 year = data[1]; 209 } 210 else if (dayfirst && data[1] <= 12) 211 { 212 //13-01 213 day = data[0]; 214 month = data[1]; 215 } 216 else 217 { 218 //01-13 219 month = data[0]; 220 day = data[1]; 221 } 222 223 } 224 else if (dataPosition == 3) //Three members 225 { 226 if (mstridx == 0) 227 { 228 month = data[0]; 229 day = data[1]; 230 year = data[2]; 231 } 232 else if (mstridx == 1) 233 { 234 if (data[0] > 31 || (yearfirst && data[2] <= 31)) 235 { 236 //99-Jan-01 237 year = data[0]; 238 month = data[1]; 239 day = data[2]; 240 } 241 else 242 { 243 //01-Jan-01 244 //Give precedence to day-first, since 245 //two-digit years is usually hand-written. 246 day = data[0]; 247 month = data[1]; 248 year = data[2]; 249 } 250 } 251 else if (mstridx == 2) 252 { 253 if (data[1] > 31) 254 { 255 //01-99-Jan 256 day = data[0]; 257 year = data[1]; 258 month = data[2]; 259 } 260 else 261 { 262 //99-01-Jan 263 year = data[0]; 264 day = data[1]; 265 month = data[2]; 266 } 267 } 268 else 269 { 270 if (data[0] > 31 || probableYearIndex(tokens) == 0 271 || (yearfirst && data[1] <= 12 && data[2] <= 31)) 272 { 273 //99-01-01 274 year = data[0]; 275 month = data[1]; 276 day = data[2]; 277 } 278 else if (data[0] > 12 || (dayfirst && data[1] <= 12)) 279 { 280 //13-01-01 281 day = data[0]; 282 month = data[1]; 283 year = data[2]; 284 } 285 else 286 { 287 //01-13-01 288 month = data[0]; 289 day = data[1]; 290 year = data[2]; 291 } 292 } 293 } 294 295 return tuple(year, month, day); 296 } 297 }