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