spreadsheet
typeScript/javascript spreadsheet parser, with formulas.
git clone https://git.vogt.world/spreadsheet.git
Log | Files | README.md
← Commit log
commit
message
Refactoring date string parsing to be a utility because it's required by other functions.
author
Ben Vogt <[email protected]>
date
2017-04-02 18:41:37
stats
3 file(s) changed, 835 insertions(+), 712 deletions(-)
files
src/RawFormulas/Date.ts
src/RawFormulas/Utils.ts
tests/DateFormulasTest.ts
   1diff --git a/src/RawFormulas/Date.ts b/src/RawFormulas/Date.ts
   2index e01db5b..137b65d 100644
   3--- a/src/RawFormulas/Date.ts
   4+++ b/src/RawFormulas/Date.ts
   5@@ -43,54 +43,6 @@ var DATE = function (...values) : ExcelDate {
   6   return excelDate;
   7 };
   8 
   9-
  10-const YEAR_MONTHDIG_DAY = DateRegExBuilder.DateRegExBuilder()
  11-  .start()
  12-  .OPTIONAL_DAYNAME().OPTIONAL_COMMA().YYYY().FLEX_DELIMITER_LOOSEDOT().MM().FLEX_DELIMITER_LOOSEDOT().DD_W_SPACE().OPTIONAL_TIMESTAMP_CAPTURE_GROUP()
  13-  .end()
  14-  .build();
  15-const MONTHDIG_DAY_YEAR = DateRegExBuilder.DateRegExBuilder()
  16-  .start()
  17-  .OPTIONAL_DAYNAME().OPTIONAL_COMMA().MM().FLEX_DELIMITER_LOOSEDOT().DD().FLEX_DELIMITER_LOOSEDOT().YYYY14_W_SPACE().OPTIONAL_TIMESTAMP_CAPTURE_GROUP()
  18-  .end()
  19-  .build();
  20-const DAY_MONTHNAME_YEAR = DateRegExBuilder.DateRegExBuilder()
  21-  .start()
  22-  .OPTIONAL_DAYNAME().OPTIONAL_COMMA().DD().FLEX_DELIMITER_LOOSEDOT().MONTHNAME().FLEX_DELIMITER_LOOSEDOT().YYYY14_W_SPACE().OPTIONAL_TIMESTAMP_CAPTURE_GROUP()
  23-  .end()
  24-  .build();
  25-const MONTHNAME_DAY_YEAR = DateRegExBuilder.DateRegExBuilder()
  26-  .start()
  27-  .OPTIONAL_DAYNAME().OPTIONAL_COMMA().MONTHNAME().FLEX_DELIMITER_LOOSEDOT().DD().FLEX_DELIMITER_LOOSEDOT().YYYY14_W_SPACE().OPTIONAL_TIMESTAMP_CAPTURE_GROUP()
  28-  .end()
  29-  .build();
  30-const YEAR_MONTHDIG = DateRegExBuilder.DateRegExBuilder()
  31-  .start()
  32-  .OPTIONAL_DAYNAME().OPTIONAL_COMMA().YYYY14().FLEX_DELIMITER().MM_W_SPACE().OPTIONAL_TIMESTAMP_CAPTURE_GROUP()
  33-  .end()
  34-  .build();
  35-const MONTHDIG_YEAR = DateRegExBuilder.DateRegExBuilder()
  36-  .start()
  37-  .OPTIONAL_DAYNAME().OPTIONAL_COMMA().MM().FLEX_DELIMITER().YYYY14_W_SPACE().OPTIONAL_TIMESTAMP_CAPTURE_GROUP()
  38-  .end()
  39-  .build();
  40-const YEAR_MONTHNAME = DateRegExBuilder.DateRegExBuilder()
  41-  .start()
  42-  .OPTIONAL_DAYNAME().OPTIONAL_COMMA().YYYY14().FLEX_DELIMITER().MONTHNAME_W_SPACE().OPTIONAL_TIMESTAMP_CAPTURE_GROUP()
  43-  .end()
  44-  .build();
  45-const MONTHNAME_YEAR = DateRegExBuilder.DateRegExBuilder()
  46-  .start()
  47-  .OPTIONAL_DAYNAME().OPTIONAL_COMMA().MONTHNAME().FLEX_DELIMITER().YYYY2_OR_4_W_SPACE().OPTIONAL_TIMESTAMP_CAPTURE_GROUP()
  48-  .end()
  49-  .build();
  50-// For reference: https://regex101.com/r/47GARA/1/
  51-const TIMESTAMP = DateRegExBuilder.DateRegExBuilder()
  52-  .start()
  53-  .TIMESTAMP_UNITS_CAPTURE_GROUP()
  54-  .end()
  55-  .build();
  56-
  57 /**
  58  * Converts a provided date string in a known format to a date value.
  59  * @param values[0] date_string - The string representing the date. Understood formats include any date format which is
  60@@ -100,243 +52,17 @@ const TIMESTAMP = DateRegExBuilder.DateRegExBuilder()
  61  * @constructor
  62  */
  63 var DATEVALUE = function (...values) : number {
  64-  const FIRST_YEAR = 1900;
  65-  const Y2K_YEAR = 2000;
  66   ArgsChecker.checkLength(values, 1);
  67   var dateString = TypeCaster.firstValueAsString(values[0]);
  68-  var m;
  69-
  70-  /**
  71-   * Creates moment object from years, months and days.
  72-   * @param years of moment
  73-   * @param months of moment in number or string format (eg: January)
  74-   * @param days of moment
  75-   * @returns {Moment} created moment
  76-   */
  77-  function createMoment(years, months, days) : moment.Moment {
  78-    var actualYear = years;
  79-    if (years >= 0 && years < 30) {
  80-      actualYear = Y2K_YEAR + years;
  81-    } else if (years >= 30 && years < 100) {
  82-      actualYear = FIRST_YEAR + years;
  83-    }
  84-    var tmpMoment = moment.utc([actualYear]).startOf("year");
  85-    if (typeof months === "string") {
  86-      tmpMoment.month(months);
  87-    } else {
  88-      tmpMoment.set("months", months);
  89-    }
  90-    // If we're specifying more days than there are in this month
  91-    if (days > tmpMoment.daysInMonth() - 1) {
  92-      throw new CellError(VALUE_ERROR, "DATEVALUE parameter '" + dateString + "' cannot be parsed to date/time.");
  93-    }
  94-    return tmpMoment.add(days, 'days');
  95-  }
  96-
  97-  /**
  98-   * Matches a timestamp string, adding the units to the moment passed in.
  99-   * @param timestampString to parse. ok formats: "10am", "10:10", "10:10am", "10:10:10", "10:10:10am", etc.
 100-   * @param momentToMutate to mutate
 101-   * @returns {Moment} mutated and altered.
 102-   */
 103-  function matchTimestampAndMutateMoment(timestampString : string, momentToMutate: moment.Moment) : moment.Moment {
 104-    var matches = timestampString.match(TIMESTAMP);
 105-    if (matches && matches[1] !== undefined) { // 10am
 106-      var hours = parseInt(matches[2]);
 107-      if (hours > 12) {
 108-        throw new CellError(VALUE_ERROR, "DATEVALUE parameter '" + dateString + "' cannot be parsed to date/time.");
 109-      }
 110-      // No op on momentToMutate because you can't overload hours with am/pm.
 111-    } else if (matches && matches[6] !== undefined) { // 10:10
 112-      var hours = parseInt(matches[7]);
 113-      var minutes = parseInt(matches[8]);
 114-      momentToMutate.add(hours, 'hours').add(minutes, 'minutes');
 115-    } else if (matches && matches[11] !== undefined) { // 10:10am
 116-      var hours = parseInt(matches[13]);
 117-      var minutes = parseInt(matches[14]);
 118-      var pmTrue = (matches[16].toLowerCase() === "pm");
 119-      if (hours > 12) {
 120-        throw new CellError(VALUE_ERROR, "DATEVALUE parameter '" + dateString + "' cannot be parsed to date/time.");
 121-      }
 122-      if (pmTrue) {
 123-        // 12pm is just 0am, 4pm is 16, etc.
 124-        momentToMutate.set('hours', hours === 12 ? hours : 12 + hours);
 125-      } else {
 126-        if (hours !== 12) {
 127-          momentToMutate.set('hours', hours);
 128-        }
 129-      }
 130-      momentToMutate.add(minutes, 'minutes');
 131-    } else if (matches && matches[17] !== undefined) { // 10:10:10
 132-      var hours = parseInt(matches[19]);
 133-      var minutes = parseInt(matches[20]);
 134-      var seconds = parseInt(matches[21]);
 135-      momentToMutate.add(hours, 'hours').add(minutes, 'minutes').add(seconds, 'seconds');
 136-    } else if (matches && matches[23] !== undefined) { // // 10:10:10am
 137-      var hours = parseInt(matches[25]);
 138-      var minutes = parseInt(matches[26]);
 139-      var seconds = parseInt(matches[27]);
 140-      var pmTrue = (matches[28].toLowerCase() === "pm");
 141-      if (hours > 12) {
 142-        throw new CellError(VALUE_ERROR, "DATEVALUE parameter '" + dateString + "' cannot be parsed to date/time.");
 143-      }
 144-      if (pmTrue) {
 145-        // 12pm is just 0am, 4pm is 16, etc.
 146-        momentToMutate.set('hours', hours === 12 ? hours : 12 + hours);
 147-      } else {
 148-        if (hours !== 12) {
 149-          momentToMutate.set('hours', hours);
 150-        }
 151-      }
 152-      momentToMutate.add(minutes, 'minutes').add(seconds, 'seconds');
 153-    } else {
 154-      throw new CellError(VALUE_ERROR, "DATEVALUE parameter '" + dateString + "' cannot be parsed to date/time.");
 155-    }
 156-    return momentToMutate.set('hours', 0).set('minutes', 0).set('seconds', 0);
 157-  }
 158-
 159-  // Check YEAR_MONTHDIG, YYYY(fd)MM, '1992/06'
 160-  // NOTE: Must come before YEAR_MONTHDIG_DAY matching.
 161-  if (m === undefined) {
 162-    var matches = dateString.match(YEAR_MONTHDIG);
 163-    if (matches && matches.length >= 6) {
 164-      var years = parseInt(matches[3]);
 165-      var months = parseInt(matches[5]) - 1; // Months are zero indexed.
 166-      var tmpMoment = createMoment(years, months, 0);
 167-      if (matches[6] !== undefined) {
 168-        tmpMoment = matchTimestampAndMutateMoment(matches[6], tmpMoment);
 169-      }
 170-      m = tmpMoment;
 171-    }
 172-  }
 173-
 174-  // Check YEAR_MONTHDIG_DAY, YYYY(fd)MM(fd)DD, "1992/06/24"
 175-  if (m === undefined) {
 176-    var matches = dateString.match(YEAR_MONTHDIG_DAY);
 177-    if (matches && matches.length >= 8) {
 178-      // Check delimiters. If they're not the same, throw error.
 179-      if (matches[4].replace(/\s*/g, '') !== matches[6].replace(/\s*/g, '')) {
 180-        throw new CellError(VALUE_ERROR, "DATEVALUE parameter '" + dateString + "' cannot be parsed to date/time.");
 181-      }
 182-      var years = parseInt(matches[3]);
 183-      var months = parseInt(matches[5]) - 1; // Months are zero indexed.
 184-      var days = parseInt(matches[7]) - 1; // Days are zero indexed.
 185-      var tmpMoment = createMoment(years, months, days);
 186-      if (matches.length >= 9 && matches[8] !== undefined) {
 187-        tmpMoment = matchTimestampAndMutateMoment(matches[8], tmpMoment);
 188-      }
 189-      m = tmpMoment;
 190-    }
 191-  }
 192-
 193-  // Check MONTHDIG_YEAR, MM(fd)YYYY, '06/1992'
 194-  // NOTE: Must come before MONTHDIG_DAY_YEAR matching.
 195-  if (m === undefined) {
 196-    var matches = dateString.match(MONTHDIG_YEAR);
 197-    if (matches && matches.length >= 6) {
 198-      var years = parseInt(matches[5]);
 199-      var months = parseInt(matches[3]) - 1; // Months are zero indexed.
 200-      var tmpMoment = createMoment(years, months, 0);
 201-      if (matches[6] !== undefined) {
 202-        tmpMoment = matchTimestampAndMutateMoment(matches[6], tmpMoment);
 203-      }
 204-      m = tmpMoment;
 205-    }
 206-  }
 207-
 208-  // Check MONTHDIG_DAY_YEAR, MM(fd)DD(fd)YYYY, "06/24/1992"
 209-  if (m === undefined) {
 210-    var matches = dateString.match(MONTHDIG_DAY_YEAR);
 211-    if (matches && matches.length >= 8) {
 212-      // Check delimiters. If they're not the same, throw error.
 213-      if (matches[4].replace(/\s*/g, '') !== matches[6].replace(/\s*/g, '')) {
 214-        throw new CellError(VALUE_ERROR, "DATEVALUE parameter '" + dateString + "' cannot be parsed to date/time.");
 215-      }
 216-      var years = parseInt(matches[7]);
 217-      var months = parseInt(matches[3]) - 1; // Months are zero indexed.
 218-      var days = parseInt(matches[5]) - 1; // Days are zero indexed.
 219-      var tmpMoment = createMoment(years, months, days);
 220-      if (matches.length >= 9 && matches[8] !== undefined) {
 221-        tmpMoment = matchTimestampAndMutateMoment(matches[8], tmpMoment);
 222-      }
 223-      m = tmpMoment;
 224-    }
 225-  }
 226-
 227-  // Check MONTHNAME_YEAR, Month(fd)YYYY, 'Aug 1992'
 228-  // NOTE: Needs to come before DAY_MONTHNAME_YEAR matching.
 229-  if (m === undefined) {
 230-    var matches = dateString.match(MONTHNAME_YEAR);
 231-    if (matches && matches.length >= 6) {
 232-      var years = parseInt(matches[5]);
 233-      var monthName = matches[3];
 234-      var tmpMoment = createMoment(years, monthName, 0);
 235-      if (matches[6] !== undefined) {
 236-        tmpMoment = matchTimestampAndMutateMoment(matches[6], tmpMoment);
 237-      }
 238-      m = tmpMoment;
 239-    }
 240-  }
 241-
 242-  // Check MONTHNAME_DAY_YEAR, Month(fd)DD(fd)YYYY, 'Aug 19 2020'
 243-  if (m === undefined) {
 244-    var matches = dateString.match(MONTHNAME_DAY_YEAR);
 245-    if (matches && matches.length >= 8) {
 246-      // Check delimiters. If they're not the same, throw error.
 247-      if (matches[4].replace(/\s*/g, '') !== matches[6].replace(/\s*/g, '')) {
 248-        throw new CellError(VALUE_ERROR, "DATEVALUE parameter '" + dateString + "' cannot be parsed to date/time.");
 249-      }
 250-      var years = parseInt(matches[7]);
 251-      var monthName = matches[3];
 252-      var days = parseInt(matches[5]) - 1; // Days are zero indexed.
 253-      var tmpMoment = createMoment(years, monthName, days);
 254-      if (matches.length >= 9 && matches[8] !== undefined) {
 255-        tmpMoment = matchTimestampAndMutateMoment(matches[8], tmpMoment);
 256-      }
 257-      m = tmpMoment;
 258-    }
 259-  }
 260-
 261-  // Check DAY_MONTHNAME_YEAR, DD(fd)Month(fd)YYYY, '24/July/1992'
 262-  if (m === undefined) {
 263-    var matches = dateString.match(DAY_MONTHNAME_YEAR);
 264-    if (matches && matches.length >= 8) {
 265-      var years = parseInt(matches[7]);
 266-      var monthName = matches[5];
 267-      var days = parseInt(matches[3]) - 1; // Days are zero indexed.
 268-      var firstDelimiter = matches[4].replace(/\s*/g, '');
 269-      var secondDelimiter = matches[6].replace(/\s*/g, '');
 270-      // Check delimiters. If they're not the same, and the first one isn't a space, throw error.
 271-      if (firstDelimiter !== secondDelimiter && firstDelimiter !== "") {
 272-        throw new CellError(VALUE_ERROR, "DATEVALUE parameter '" + dateString + "' cannot be parsed to date/time.");
 273-      }
 274-      var tmpMoment = createMoment(years, monthName, days);
 275-      if (matches.length >= 9 && matches[8] !== undefined) {
 276-        tmpMoment = matchTimestampAndMutateMoment(matches[8], tmpMoment);
 277-      }
 278-      m = tmpMoment;
 279-    }
 280-  }
 281-
 282-  // Check YEAR_MONTHNAME, YYYY(fd)Month, '1992/Aug'
 283-  if (m === undefined) {
 284-    var matches = dateString.match(YEAR_MONTHNAME);
 285-    if (matches && matches.length >= 6) {
 286-      var years = parseInt(matches[3]);
 287-      var monthName = matches[5];
 288-      var tmpMoment = createMoment(years, monthName, 0);
 289-      if (matches[6] !== undefined) {
 290-        tmpMoment = matchTimestampAndMutateMoment(matches[6], tmpMoment);
 291-      }
 292-      m = tmpMoment;
 293-    }
 294+  var date;
 295+  try {
 296+    date = TypeCaster.stringToExcelDate(dateString);
 297+  } catch (e) {
 298+    throw new CellError(VALUE_ERROR, "DATEVALUE parameter '" + dateString + "' cannot be parsed to date/time.");
 299   }
 300 
 301   // If we've not been able to parse the date by now, then we cannot parse it at all.
 302-  if (m === undefined || !m.isValid()) {
 303-    throw new CellError(VALUE_ERROR, "DATEVALUE parameter '" + dateString + "' cannot be parsed to date/time.");
 304-  }
 305-  return new ExcelDate(m).toNumber();
 306+  return date.toNumber();
 307 };
 308 
 309 
 310diff --git a/src/RawFormulas/Utils.ts b/src/RawFormulas/Utils.ts
 311index 1618a7e..4e4dd5a 100644
 312--- a/src/RawFormulas/Utils.ts
 313+++ b/src/RawFormulas/Utils.ts
 314@@ -1,4 +1,6 @@
 315-import { CellError } from "../Errors"
 316+/// <reference path="../../node_modules/moment/moment.d.ts"/>
 317+import * as moment from "moment";
 318+import {CellError, VALUE_ERROR} from "../Errors"
 319 import * as ERRORS from "../Errors"
 320 import {ExcelDate} from "../ExcelDate";
 321 
 322@@ -21,6 +23,252 @@ function wildCardRegex(c: string) {
 323   return new RegExp("^"+d.join(".*")+"$", "g");
 324 }
 325 
 326+/**
 327+ * Build a regular expression step by step, to make it easier to build and read the resulting regular expressions.
 328+ */
 329+class DateRegExBuilder {
 330+  private regexString = "";
 331+  private static ZERO_OR_MORE_SPACES = "\\s*";
 332+
 333+  static DateRegExBuilder() : DateRegExBuilder {
 334+    return new DateRegExBuilder();
 335+  }
 336+
 337+  /**
 338+   * Start the regular expression builder by matching the start of a line and zero or more spaces.
 339+   * @returns {DateRegExBuilder} builder
 340+   */
 341+  start() : DateRegExBuilder {
 342+    this.regexString += "^" + DateRegExBuilder.ZERO_OR_MORE_SPACES;
 343+    return this;
 344+  }
 345+
 346+  /**
 347+   * End the regular expression builder by matching the end of the line.
 348+   * @returns {DateRegExBuilder} builder
 349+   */
 350+  end(): DateRegExBuilder {
 351+    this.regexString += "$";
 352+    return this;
 353+  }
 354+
 355+  /**
 356+   * Capture all month full name and short names to the regular expression.
 357+   * @returns {DateRegExBuilder} builder
 358+   */
 359+  MONTHNAME() : DateRegExBuilder {
 360+    this.regexString += "(january|february|march|april|may|june|july|august|september|october|november|december|jan|feb|mar|apr|jun|jul|aug|sep|oct|nov|dec)";
 361+    return this;
 362+  }
 363+
 364+  /**
 365+   * Capture all month full name and short names to the regular expression, in addition to any followed by one or more
 366+   * spaces.
 367+   * @returns {DateRegExBuilder} builder
 368+   * @constructor
 369+   */
 370+  MONTHNAME_W_SPACE() : DateRegExBuilder {
 371+    this.regexString += "(january|february|march|april|may|june|july|august|september|october|november|december|jan|feb|mar|apr|jun|jul|aug|sep|oct|nov|dec|january\\s+|february\\s+|march\\s+|april\\s+|may\\s+|june\\s+|july\\s+|august\\s+|september\\s+|october\\s+|november\\s+|december\\s+|jan\\s+|feb\\s+|mar\\s+|apr\\s+|jun\\s+|jul\\s+|aug\\s+|sep\\s+|oct\\s+|nov\\s+|dec\\s+)";
 372+    return this;
 373+  }
 374+
 375+  /**
 376+   * Add capture group for optionally capturing day names.
 377+   * @returns {DateRegExBuilder} builder
 378+   * @constructor
 379+   */
 380+  OPTIONAL_DAYNAME() : DateRegExBuilder {
 381+    this.regexString += "(sunday|monday|tuesday|wednesday|thursday|friday|saturday|sun|mon|tue|wed|thu|fri|sat)?";
 382+    return this;
 383+  }
 384+
 385+  /**
 386+   * Add capture group for optionally capturing a comma followed by one or more spaces.
 387+   * @returns {DateRegExBuilder} builder
 388+   * @constructor
 389+   */
 390+  OPTIONAL_COMMA() : DateRegExBuilder {
 391+    this.regexString += "(,?\\s+)?";
 392+    return this;
 393+  }
 394+
 395+  /**
 396+   * Add capture group for capturing month digits between 01 and 12, inclusively.
 397+   * @returns {DateRegExBuilder} builder
 398+   * @constructor
 399+   */
 400+  MM() : DateRegExBuilder {
 401+    this.regexString += "([1-9]|0[1-9]|1[0-2])";
 402+    return this;
 403+  }
 404+
 405+  /**
 406+   * Add capture group for capturing month digits between 01 and 12, inclusively, in addition to any followed by one or
 407+   * more spaces.
 408+   * @returns {DateRegExBuilder} builder
 409+   * @constructor
 410+   */
 411+  MM_W_SPACE() : DateRegExBuilder {
 412+    this.regexString += "([1-9]|0[1-9]|1[0-2]|[1-9]\\s+|0[1-9]\\s+|1[0-2]\\s+)";
 413+    return this;
 414+  }
 415+
 416+  /**
 417+   * Add capture group for capturing day digits between 01 and 31, inclusively.
 418+   * @returns {DateRegExBuilder} builder
 419+   * @constructor
 420+   */
 421+  DD() : DateRegExBuilder {
 422+    this.regexString += "(0?[0-9]|1[0-9]|2[0-9]|3[0-1])";
 423+    return this;
 424+  }
 425+
 426+  /**
 427+   * Add capture group for capturing day digits between 01 and 31, inclusively, in addition to any followed by one or
 428+   * more spaces.
 429+   * @returns {DateRegExBuilder} builder
 430+   * @constructor
 431+   */
 432+  DD_W_SPACE() : DateRegExBuilder {
 433+    this.regexString += "(0?[0-9]|1[0-9]|2[0-9]|3[0-1]|0?[0-9]\\s+|1[0-9]\\s+|2[0-9]\\s+|3[0-1]\\s+)";
 434+    return this;
 435+  }
 436+
 437+  /**
 438+   * Add capture group for capturing 4 digits or 3 digits starting with 0-9.
 439+   * @returns {DateRegExBuilder} builder
 440+   * @constructor
 441+   */
 442+  YYYY() : DateRegExBuilder {
 443+    this.regexString += "([0-9]{4}|[1-9][0-9][0-9])";
 444+    return this;
 445+  }
 446+
 447+  /**
 448+   * Add capture group for capturing 1 through 4 digits.
 449+   * @returns {DateRegExBuilder} builder
 450+   * @constructor
 451+   */
 452+  YYYY14() : DateRegExBuilder {
 453+    this.regexString += "([0-9]{1,4})";
 454+    return this;
 455+  }
 456+
 457+  /**
 458+   * Add capture group for capturing 1 through 4 digits, in addition to any followed by one or more spaces.
 459+   * @returns {DateRegExBuilder} builder
 460+   * @constructor
 461+   */
 462+  YYYY14_W_SPACE() : DateRegExBuilder {
 463+    this.regexString += "([0-9]{1,4}|[0-9]{1,4}\\s+)";
 464+    return this;
 465+  }
 466+
 467+  YYYY2_OR_4_W_SPACE() : DateRegExBuilder {
 468+    this.regexString += "([0-9]{2}|[0-9]{4}|[0-9]{2}\\s+|[0-9]{4}\\s+)";
 469+    return this;
 470+  }
 471+
 472+  /**
 473+   * Add capture group for a flexible delimiter, including ", ", " ", ". ", "\", "-".
 474+   * @returns {DateRegExBuilder} builder
 475+   * @constructor
 476+   */
 477+  FLEX_DELIMITER() : DateRegExBuilder {
 478+    // this.regexString += "(,?\\s+|\\s*-?\\.?-?\\/?\\s+)";// close to being right
 479+    this.regexString += "(,?\\s+|\\s*\\.\\s+|\\s*-\\s*|\\s*\\/\\s*)";
 480+    return this;
 481+  }
 482+
 483+  /**
 484+   * Add capture group for a flexible delimiter, including ", ", " ", ".", "\", "-". Different from FLEX_DELIMITER
 485+   * in that it will match periods with zero or more spaces on either side.
 486+   * For reference: https://regex101.com/r/q1fp1z/1/
 487+   * @returns {DateRegExBuilder} builder
 488+   * @constructor
 489+   */
 490+  FLEX_DELIMITER_LOOSEDOT() : DateRegExBuilder {
 491+    // this.regexString += "(,?\\s+|\\s*-?\\.?-?\\/?\\s+)";// close to being right
 492+    this.regexString += "(,?\\s+|\\s*\\.\\s*|\\s*-\\s*|\\s*\\/\\s*)";
 493+    return this;
 494+  }
 495+
 496+  /**
 497+   * Add a capture group for capturing timestamps including: "10am", "10:10", "10:10pm", "10:10:10", "10:10:10am", along
 498+   * with zero or more spaces after semi colons, AM or PM, and unlimited number of digits per unit.
 499+   * @returns {DateRegExBuilder} builder
 500+   * @constructor
 501+   */
 502+  OPTIONAL_TIMESTAMP_CAPTURE_GROUP() : DateRegExBuilder {
 503+    this.regexString += "((\\s+[0-9]+\\s*am\\s*$|[0-9]+\\s*pm\\s*$)|(\\s+[0-9]+:\\s*[0-9]+\\s*$)|(\\s+[0-9]+:\\s*[0-9]+\\s*am\\s*$|\\s+[0-9]+:\\s*[0-9]+\\s*pm\\s*$)|(\\s+[0-9]+:\\s*[0-9]+:\\s*[0-9]+\\s*$)|(\\s+[0-9]+:\\s*[0-9]+:\\s*[0-9]+\\s*am\\s*$|[0-9]+:\\s*[0-9]+:\\s*[0-9]+\\s*pm\\s*$))?";
 504+    return this;
 505+  }
 506+
 507+  TIMESTAMP_UNITS_CAPTURE_GROUP() : DateRegExBuilder {
 508+    this.regexString += "(\\s*([0-9]+)()()\\s*(am|pm)\\s*$)|(\\s*([0-9]+):\\s*([0-9]+)()()\\s*$)|(\\s*(([0-9]+):\\s*([0-9]+)()\\s*(am|pm))\\s*$)|(\\s*(([0-9]+):\\s*([0-9]+):\\s*([0-9]+)())\\s*$)|(\\s*(([0-9]+):\\s*([0-9]+):\\s*([0-9]+)\\s*(am|pm))\\s*$)";
 509+    return this;
 510+  }
 511+
 512+  /**
 513+   * Build the regular expression and ignore case.
 514+   * @returns {RegExp}
 515+   */
 516+  build() : RegExp {
 517+    return new RegExp(this.regexString, 'i');
 518+  }
 519+}
 520+
 521+const YEAR_MONTHDIG_DAY = DateRegExBuilder.DateRegExBuilder()
 522+  .start()
 523+  .OPTIONAL_DAYNAME().OPTIONAL_COMMA().YYYY().FLEX_DELIMITER_LOOSEDOT().MM().FLEX_DELIMITER_LOOSEDOT().DD_W_SPACE().OPTIONAL_TIMESTAMP_CAPTURE_GROUP()
 524+  .end()
 525+  .build();
 526+const MONTHDIG_DAY_YEAR = DateRegExBuilder.DateRegExBuilder()
 527+  .start()
 528+  .OPTIONAL_DAYNAME().OPTIONAL_COMMA().MM().FLEX_DELIMITER_LOOSEDOT().DD().FLEX_DELIMITER_LOOSEDOT().YYYY14_W_SPACE().OPTIONAL_TIMESTAMP_CAPTURE_GROUP()
 529+  .end()
 530+  .build();
 531+const DAY_MONTHNAME_YEAR = DateRegExBuilder.DateRegExBuilder()
 532+  .start()
 533+  .OPTIONAL_DAYNAME().OPTIONAL_COMMA().DD().FLEX_DELIMITER_LOOSEDOT().MONTHNAME().FLEX_DELIMITER_LOOSEDOT().YYYY14_W_SPACE().OPTIONAL_TIMESTAMP_CAPTURE_GROUP()
 534+  .end()
 535+  .build();
 536+const MONTHNAME_DAY_YEAR = DateRegExBuilder.DateRegExBuilder()
 537+  .start()
 538+  .OPTIONAL_DAYNAME().OPTIONAL_COMMA().MONTHNAME().FLEX_DELIMITER_LOOSEDOT().DD().FLEX_DELIMITER_LOOSEDOT().YYYY14_W_SPACE().OPTIONAL_TIMESTAMP_CAPTURE_GROUP()
 539+  .end()
 540+  .build();
 541+const YEAR_MONTHDIG = DateRegExBuilder.DateRegExBuilder()
 542+  .start()
 543+  .OPTIONAL_DAYNAME().OPTIONAL_COMMA().YYYY14().FLEX_DELIMITER().MM_W_SPACE().OPTIONAL_TIMESTAMP_CAPTURE_GROUP()
 544+  .end()
 545+  .build();
 546+const MONTHDIG_YEAR = DateRegExBuilder.DateRegExBuilder()
 547+  .start()
 548+  .OPTIONAL_DAYNAME().OPTIONAL_COMMA().MM().FLEX_DELIMITER().YYYY14_W_SPACE().OPTIONAL_TIMESTAMP_CAPTURE_GROUP()
 549+  .end()
 550+  .build();
 551+const YEAR_MONTHNAME = DateRegExBuilder.DateRegExBuilder()
 552+  .start()
 553+  .OPTIONAL_DAYNAME().OPTIONAL_COMMA().YYYY14().FLEX_DELIMITER().MONTHNAME_W_SPACE().OPTIONAL_TIMESTAMP_CAPTURE_GROUP()
 554+  .end()
 555+  .build();
 556+const MONTHNAME_YEAR = DateRegExBuilder.DateRegExBuilder()
 557+  .start()
 558+  .OPTIONAL_DAYNAME().OPTIONAL_COMMA().MONTHNAME().FLEX_DELIMITER().YYYY2_OR_4_W_SPACE().OPTIONAL_TIMESTAMP_CAPTURE_GROUP()
 559+  .end()
 560+  .build();
 561+// For reference: https://regex101.com/r/47GARA/1/
 562+const TIMESTAMP = DateRegExBuilder.DateRegExBuilder()
 563+  .start()
 564+  .TIMESTAMP_UNITS_CAPTURE_GROUP()
 565+  .end()
 566+  .build();
 567+// The first year to use when calculating the number of days in an ExcelDate
 568+const FIRST_YEAR = 1900;
 569+// The year 2000.
 570+const Y2K_YEAR = 2000;
 571+
 572 
 573 /**
 574  * Creates a criteria function to evaluate elements in a range in an *IF function.
 575@@ -93,6 +341,247 @@ class CriteriaFunctionFactory {
 576  * Static class of helpers used to cast various types to each other.
 577  */
 578 class TypeCaster {
 579+
 580+  /**
 581+   * Casts a string to an ExcelDate. Throws error if parsing not possible.
 582+   * @param dateString to parse
 583+   * @returns {ExcelDate} resulting date
 584+   */
 585+  static stringToExcelDate(dateString : string) : ExcelDate {
 586+    // m will be set and valid or invalid, or will remain undefined
 587+    var m;
 588+
 589+    /**
 590+     * Creates moment object from years, months and days.
 591+     * @param years of moment
 592+     * @param months of moment in number or string format (eg: January)
 593+     * @param days of moment
 594+     * @returns {Moment} created moment
 595+     */
 596+    function createMoment(years, months, days) : moment.Moment {
 597+      var actualYear = years;
 598+      if (years >= 0 && years < 30) {
 599+        actualYear = Y2K_YEAR + years;
 600+      } else if (years >= 30 && years < 100) {
 601+        actualYear = FIRST_YEAR + years;
 602+      }
 603+      var tmpMoment = moment.utc([actualYear]).startOf("year");
 604+      if (typeof months === "string") {
 605+        tmpMoment.month(months);
 606+      } else {
 607+        tmpMoment.set("months", months);
 608+      }
 609+      // If we're specifying more days than there are in this month
 610+      if (days > tmpMoment.daysInMonth() - 1) {
 611+        throw new Error();
 612+      }
 613+      return tmpMoment.add(days, 'days');
 614+    }
 615+
 616+    /**
 617+     * Matches a timestamp string, adding the units to the moment passed in.
 618+     * @param timestampString to parse. ok formats: "10am", "10:10", "10:10am", "10:10:10", "10:10:10am", etc.
 619+     * @param momentToMutate to mutate
 620+     * @returns {Moment} mutated and altered.
 621+     */
 622+    function matchTimestampAndMutateMoment(timestampString : string, momentToMutate: moment.Moment) : moment.Moment {
 623+      var matches = timestampString.match(TIMESTAMP);
 624+      if (matches && matches[1] !== undefined) { // 10am
 625+        var hours = parseInt(matches[2]);
 626+        if (hours > 12) {
 627+          throw new Error();
 628+        }
 629+        // No op on momentToMutate because you can't overload hours with am/pm.
 630+      } else if (matches && matches[6] !== undefined) { // 10:10
 631+        var hours = parseInt(matches[7]);
 632+        var minutes = parseInt(matches[8]);
 633+        momentToMutate.add(hours, 'hours').add(minutes, 'minutes');
 634+      } else if (matches && matches[11] !== undefined) { // 10:10am
 635+        var hours = parseInt(matches[13]);
 636+        var minutes = parseInt(matches[14]);
 637+        var pmTrue = (matches[16].toLowerCase() === "pm");
 638+        if (hours > 12) {
 639+          throw new Error();
 640+        }
 641+        if (pmTrue) {
 642+          // 12pm is just 0am, 4pm is 16, etc.
 643+          momentToMutate.set('hours', hours === 12 ? hours : 12 + hours);
 644+        } else {
 645+          if (hours !== 12) {
 646+            momentToMutate.set('hours', hours);
 647+          }
 648+        }
 649+        momentToMutate.add(minutes, 'minutes');
 650+      } else if (matches && matches[17] !== undefined) { // 10:10:10
 651+        var hours = parseInt(matches[19]);
 652+        var minutes = parseInt(matches[20]);
 653+        var seconds = parseInt(matches[21]);
 654+        momentToMutate.add(hours, 'hours').add(minutes, 'minutes').add(seconds, 'seconds');
 655+      } else if (matches && matches[23] !== undefined) { // // 10:10:10am
 656+        var hours = parseInt(matches[25]);
 657+        var minutes = parseInt(matches[26]);
 658+        var seconds = parseInt(matches[27]);
 659+        var pmTrue = (matches[28].toLowerCase() === "pm");
 660+        if (hours > 12) {
 661+          throw new Error();
 662+        }
 663+        if (pmTrue) {
 664+          // 12pm is just 0am, 4pm is 16, etc.
 665+          momentToMutate.set('hours', hours === 12 ? hours : 12 + hours);
 666+        } else {
 667+          if (hours !== 12) {
 668+            momentToMutate.set('hours', hours);
 669+          }
 670+        }
 671+        momentToMutate.add(minutes, 'minutes').add(seconds, 'seconds');
 672+      } else {
 673+        throw new Error();
 674+      }
 675+      return momentToMutate.set('hours', 0).set('minutes', 0).set('seconds', 0);
 676+    }
 677+
 678+    // Check YEAR_MONTHDIG, YYYY(fd)MM, '1992/06'
 679+    // NOTE: Must come before YEAR_MONTHDIG_DAY matching.
 680+    if (m === undefined) {
 681+      var matches = dateString.match(YEAR_MONTHDIG);
 682+      if (matches && matches.length >= 6) {
 683+        var years = parseInt(matches[3]);
 684+        var months = parseInt(matches[5]) - 1; // Months are zero indexed.
 685+        var tmpMoment = createMoment(years, months, 0);
 686+        if (matches[6] !== undefined) {
 687+          tmpMoment = matchTimestampAndMutateMoment(matches[6], tmpMoment);
 688+        }
 689+        m = tmpMoment;
 690+      }
 691+    }
 692+
 693+    // Check YEAR_MONTHDIG_DAY, YYYY(fd)MM(fd)DD, "1992/06/24"
 694+    if (m === undefined) {
 695+      var matches = dateString.match(YEAR_MONTHDIG_DAY);
 696+      if (matches && matches.length >= 8) {
 697+        // Check delimiters. If they're not the same, throw error.
 698+        if (matches[4].replace(/\s*/g, '') !== matches[6].replace(/\s*/g, '')) {
 699+          throw new Error();
 700+        }
 701+        var years = parseInt(matches[3]);
 702+        var months = parseInt(matches[5]) - 1; // Months are zero indexed.
 703+        var days = parseInt(matches[7]) - 1; // Days are zero indexed.
 704+        var tmpMoment = createMoment(years, months, days);
 705+        if (matches.length >= 9 && matches[8] !== undefined) {
 706+          tmpMoment = matchTimestampAndMutateMoment(matches[8], tmpMoment);
 707+        }
 708+        m = tmpMoment;
 709+      }
 710+    }
 711+
 712+    // Check MONTHDIG_YEAR, MM(fd)YYYY, '06/1992'
 713+    // NOTE: Must come before MONTHDIG_DAY_YEAR matching.
 714+    if (m === undefined) {
 715+      var matches = dateString.match(MONTHDIG_YEAR);
 716+      if (matches && matches.length >= 6) {
 717+        var years = parseInt(matches[5]);
 718+        var months = parseInt(matches[3]) - 1; // Months are zero indexed.
 719+        var tmpMoment = createMoment(years, months, 0);
 720+        if (matches[6] !== undefined) {
 721+          tmpMoment = matchTimestampAndMutateMoment(matches[6], tmpMoment);
 722+        }
 723+        m = tmpMoment;
 724+      }
 725+    }
 726+
 727+    // Check MONTHDIG_DAY_YEAR, MM(fd)DD(fd)YYYY, "06/24/1992"
 728+    if (m === undefined) {
 729+      var matches = dateString.match(MONTHDIG_DAY_YEAR);
 730+      if (matches && matches.length >= 8) {
 731+        // Check delimiters. If they're not the same, throw error.
 732+        if (matches[4].replace(/\s*/g, '') !== matches[6].replace(/\s*/g, '')) {
 733+          throw new Error();
 734+        }
 735+        var years = parseInt(matches[7]);
 736+        var months = parseInt(matches[3]) - 1; // Months are zero indexed.
 737+        var days = parseInt(matches[5]) - 1; // Days are zero indexed.
 738+        var tmpMoment = createMoment(years, months, days);
 739+        if (matches.length >= 9 && matches[8] !== undefined) {
 740+          tmpMoment = matchTimestampAndMutateMoment(matches[8], tmpMoment);
 741+        }
 742+        m = tmpMoment;
 743+      }
 744+    }
 745+
 746+    // Check MONTHNAME_YEAR, Month(fd)YYYY, 'Aug 1992'
 747+    // NOTE: Needs to come before DAY_MONTHNAME_YEAR matching.
 748+    if (m === undefined) {
 749+      var matches = dateString.match(MONTHNAME_YEAR);
 750+      if (matches && matches.length >= 6) {
 751+        var years = parseInt(matches[5]);
 752+        var monthName = matches[3];
 753+        var tmpMoment = createMoment(years, monthName, 0);
 754+        if (matches[6] !== undefined) {
 755+          tmpMoment = matchTimestampAndMutateMoment(matches[6], tmpMoment);
 756+        }
 757+        m = tmpMoment;
 758+      }
 759+    }
 760+
 761+    // Check MONTHNAME_DAY_YEAR, Month(fd)DD(fd)YYYY, 'Aug 19 2020'
 762+    if (m === undefined) {
 763+      var matches = dateString.match(MONTHNAME_DAY_YEAR);
 764+      if (matches && matches.length >= 8) {
 765+        // Check delimiters. If they're not the same, throw error.
 766+        if (matches[4].replace(/\s*/g, '') !== matches[6].replace(/\s*/g, '')) {
 767+          throw new Error();
 768+        }
 769+        var years = parseInt(matches[7]);
 770+        var monthName = matches[3];
 771+        var days = parseInt(matches[5]) - 1; // Days are zero indexed.
 772+        var tmpMoment = createMoment(years, monthName, days);
 773+        if (matches.length >= 9 && matches[8] !== undefined) {
 774+          tmpMoment = matchTimestampAndMutateMoment(matches[8], tmpMoment);
 775+        }
 776+        m = tmpMoment;
 777+      }
 778+    }
 779+
 780+    // Check DAY_MONTHNAME_YEAR, DD(fd)Month(fd)YYYY, '24/July/1992'
 781+    if (m === undefined) {
 782+      var matches = dateString.match(DAY_MONTHNAME_YEAR);
 783+      if (matches && matches.length >= 8) {
 784+        var years = parseInt(matches[7]);
 785+        var monthName = matches[5];
 786+        var days = parseInt(matches[3]) - 1; // Days are zero indexed.
 787+        var firstDelimiter = matches[4].replace(/\s*/g, '');
 788+        var secondDelimiter = matches[6].replace(/\s*/g, '');
 789+        // Check delimiters. If they're not the same, and the first one isn't a space, throw error.
 790+        if (firstDelimiter !== secondDelimiter && firstDelimiter !== "") {
 791+          throw new Error();
 792+        }
 793+        var tmpMoment = createMoment(years, monthName, days);
 794+        if (matches.length >= 9 && matches[8] !== undefined) {
 795+          tmpMoment = matchTimestampAndMutateMoment(matches[8], tmpMoment);
 796+        }
 797+        m = tmpMoment;
 798+      }
 799+    }
 800+
 801+    // Check YEAR_MONTHNAME, YYYY(fd)Month, '1992/Aug'
 802+    if (m === undefined) {
 803+      var matches = dateString.match(YEAR_MONTHNAME);
 804+      if (matches && matches.length >= 6) {
 805+        var years = parseInt(matches[3]);
 806+        var monthName = matches[5];
 807+        var tmpMoment = createMoment(years, monthName, 0);
 808+        if (matches[6] !== undefined) {
 809+          tmpMoment = matchTimestampAndMutateMoment(matches[6], tmpMoment);
 810+        }
 811+        m = tmpMoment;
 812+      }
 813+    }
 814+    if (m === undefined || !m.isValid()) {
 815+      throw new CellError(VALUE_ERROR, "DATEVALUE parameter '" + dateString + "' cannot be parsed to date/time.");
 816+    }
 817+    return new ExcelDate(m);
 818+  }
 819+
 820   /**
 821    * Converts any value to a number or throws an error if it cannot coerce it to the number type
 822    * @param value to convert
 823@@ -267,8 +756,17 @@ class TypeCaster {
 824       return value;
 825     } else if (typeof value === "number") {
 826       return new ExcelDate(value);
 827-    } else if (typeof value === "string" || typeof value === "boolean") {
 828-      throw new CellError(ERRORS.VALUE_ERROR, "___ expects boolean values. But '" + value + "' is a text and cannot be coerced to a boolean.")
 829+    } else if (typeof value === "string") {
 830+      try {
 831+        return TypeCaster.stringToExcelDate(value)
 832+      } catch (e) {
 833+        if (TypeCaster.canCoerceToNumber(value)) {
 834+          return new ExcelDate(TypeCaster.valueToNumber(value));
 835+        }
 836+        throw new CellError(ERRORS.VALUE_ERROR, "___ expects date values. But '" + value + "' is a text and cannot be coerced to a date.")
 837+      }
 838+    } else if (typeof value === "boolean") {
 839+      throw new CellError(ERRORS.VALUE_ERROR, "___ expects date values. But '" + value + "' is a text and cannot be coerced to a date.")
 840     }
 841   }
 842 }
 843@@ -400,202 +898,6 @@ class Serializer {
 844 }
 845 
 846 
 847-/**
 848- * Build a regular expression step by step, to make it easier to build and read the resulting regular expressions.
 849- */
 850-class DateRegExBuilder {
 851-  private regexString = "";
 852-  private static ZERO_OR_MORE_SPACES = "\\s*";
 853-
 854-  static DateRegExBuilder() : DateRegExBuilder {
 855-    return new DateRegExBuilder();
 856-  }
 857-
 858-  /**
 859-   * Start the regular expression builder by matching the start of a line and zero or more spaces.
 860-   * @returns {DateRegExBuilder} builder
 861-   */
 862-  start() : DateRegExBuilder {
 863-    this.regexString += "^" + DateRegExBuilder.ZERO_OR_MORE_SPACES;
 864-    return this;
 865-  }
 866-
 867-  /**
 868-   * End the regular expression builder by matching the end of the line.
 869-   * @returns {DateRegExBuilder} builder
 870-   */
 871-  end(): DateRegExBuilder {
 872-    this.regexString += "$";
 873-    return this;
 874-  }
 875-
 876-  /**
 877-   * Capture all month full name and short names to the regular expression.
 878-   * @returns {DateRegExBuilder} builder
 879-   */
 880-  MONTHNAME() : DateRegExBuilder {
 881-    this.regexString += "(january|february|march|april|may|june|july|august|september|october|november|december|jan|feb|mar|apr|jun|jul|aug|sep|oct|nov|dec)";
 882-    return this;
 883-  }
 884-
 885-  /**
 886-   * Capture all month full name and short names to the regular expression, in addition to any followed by one or more
 887-   * spaces.
 888-   * @returns {DateRegExBuilder} builder
 889-   * @constructor
 890-   */
 891-  MONTHNAME_W_SPACE() : DateRegExBuilder {
 892-    this.regexString += "(january|february|march|april|may|june|july|august|september|october|november|december|jan|feb|mar|apr|jun|jul|aug|sep|oct|nov|dec|january\\s+|february\\s+|march\\s+|april\\s+|may\\s+|june\\s+|july\\s+|august\\s+|september\\s+|october\\s+|november\\s+|december\\s+|jan\\s+|feb\\s+|mar\\s+|apr\\s+|jun\\s+|jul\\s+|aug\\s+|sep\\s+|oct\\s+|nov\\s+|dec\\s+)";
 893-    return this;
 894-  }
 895-
 896-  /**
 897-   * Add capture group for optionally capturing day names.
 898-   * @returns {DateRegExBuilder} builder
 899-   * @constructor
 900-   */
 901-  OPTIONAL_DAYNAME() : DateRegExBuilder {
 902-    this.regexString += "(sunday|monday|tuesday|wednesday|thursday|friday|saturday|sun|mon|tue|wed|thu|fri|sat)?";
 903-    return this;
 904-  }
 905-
 906-  /**
 907-   * Add capture group for optionally capturing a comma followed by one or more spaces.
 908-   * @returns {DateRegExBuilder} builder
 909-   * @constructor
 910-   */
 911-  OPTIONAL_COMMA() : DateRegExBuilder {
 912-    this.regexString += "(,?\\s+)?";
 913-    return this;
 914-  }
 915-
 916-  /**
 917-   * Add capture group for capturing month digits between 01 and 12, inclusively.
 918-   * @returns {DateRegExBuilder} builder
 919-   * @constructor
 920-   */
 921-  MM() : DateRegExBuilder {
 922-    this.regexString += "([1-9]|0[1-9]|1[0-2])";
 923-    return this;
 924-  }
 925-
 926-  /**
 927-   * Add capture group for capturing month digits between 01 and 12, inclusively, in addition to any followed by one or
 928-   * more spaces.
 929-   * @returns {DateRegExBuilder} builder
 930-   * @constructor
 931-   */
 932-  MM_W_SPACE() : DateRegExBuilder {
 933-    this.regexString += "([1-9]|0[1-9]|1[0-2]|[1-9]\\s+|0[1-9]\\s+|1[0-2]\\s+)";
 934-    return this;
 935-  }
 936-
 937-  /**
 938-   * Add capture group for capturing day digits between 01 and 31, inclusively.
 939-   * @returns {DateRegExBuilder} builder
 940-   * @constructor
 941-   */
 942-  DD() : DateRegExBuilder {
 943-    this.regexString += "(0?[0-9]|1[0-9]|2[0-9]|3[0-1])";
 944-    return this;
 945-  }
 946-
 947-  /**
 948-   * Add capture group for capturing day digits between 01 and 31, inclusively, in addition to any followed by one or
 949-   * more spaces.
 950-   * @returns {DateRegExBuilder} builder
 951-   * @constructor
 952-   */
 953-  DD_W_SPACE() : DateRegExBuilder {
 954-    this.regexString += "(0?[0-9]|1[0-9]|2[0-9]|3[0-1]|0?[0-9]\\s+|1[0-9]\\s+|2[0-9]\\s+|3[0-1]\\s+)";
 955-    return this;
 956-  }
 957-
 958-  /**
 959-   * Add capture group for capturing 4 digits or 3 digits starting with 0-9.
 960-   * @returns {DateRegExBuilder} builder
 961-   * @constructor
 962-   */
 963-  YYYY() : DateRegExBuilder {
 964-    this.regexString += "([0-9]{4}|[1-9][0-9][0-9])";
 965-    return this;
 966-  }
 967-
 968-  /**
 969-   * Add capture group for capturing 1 through 4 digits.
 970-   * @returns {DateRegExBuilder} builder
 971-   * @constructor
 972-   */
 973-  YYYY14() : DateRegExBuilder {
 974-    this.regexString += "([0-9]{1,4})";
 975-    return this;
 976-  }
 977-
 978-  /**
 979-   * Add capture group for capturing 1 through 4 digits, in addition to any followed by one or more spaces.
 980-   * @returns {DateRegExBuilder} builder
 981-   * @constructor
 982-   */
 983-  YYYY14_W_SPACE() : DateRegExBuilder {
 984-    this.regexString += "([0-9]{1,4}|[0-9]{1,4}\\s+)";
 985-    return this;
 986-  }
 987-
 988-  YYYY2_OR_4_W_SPACE() : DateRegExBuilder {
 989-    this.regexString += "([0-9]{2}|[0-9]{4}|[0-9]{2}\\s+|[0-9]{4}\\s+)";
 990-    return this;
 991-  }
 992-
 993-  /**
 994-   * Add capture group for a flexible delimiter, including ", ", " ", ". ", "\", "-".
 995-   * @returns {DateRegExBuilder} builder
 996-   * @constructor
 997-   */
 998-  FLEX_DELIMITER() : DateRegExBuilder {
 999-    // this.regexString += "(,?\\s+|\\s*-?\\.?-?\\/?\\s+)";// close to being right
1000-    this.regexString += "(,?\\s+|\\s*\\.\\s+|\\s*-\\s*|\\s*\\/\\s*)";
1001-    return this;
1002-  }
1003-
1004-  /**
1005-   * Add capture group for a flexible delimiter, including ", ", " ", ".", "\", "-". Different from FLEX_DELIMITER
1006-   * in that it will match periods with zero or more spaces on either side.
1007-   * For reference: https://regex101.com/r/q1fp1z/1/
1008-   * @returns {DateRegExBuilder} builder
1009-   * @constructor
1010-   */
1011-  FLEX_DELIMITER_LOOSEDOT() : DateRegExBuilder {
1012-    // this.regexString += "(,?\\s+|\\s*-?\\.?-?\\/?\\s+)";// close to being right
1013-    this.regexString += "(,?\\s+|\\s*\\.\\s*|\\s*-\\s*|\\s*\\/\\s*)";
1014-    return this;
1015-  }
1016-
1017-  /**
1018-   * Add a capture group for capturing timestamps including: "10am", "10:10", "10:10pm", "10:10:10", "10:10:10am", along
1019-   * with zero or more spaces after semi colons, AM or PM, and unlimited number of digits per unit.
1020-   * @returns {DateRegExBuilder} builder
1021-   * @constructor
1022-   */
1023-  OPTIONAL_TIMESTAMP_CAPTURE_GROUP() : DateRegExBuilder {
1024-    this.regexString += "((\\s+[0-9]+\\s*am\\s*$|[0-9]+\\s*pm\\s*$)|(\\s+[0-9]+:\\s*[0-9]+\\s*$)|(\\s+[0-9]+:\\s*[0-9]+\\s*am\\s*$|\\s+[0-9]+:\\s*[0-9]+\\s*pm\\s*$)|(\\s+[0-9]+:\\s*[0-9]+:\\s*[0-9]+\\s*$)|(\\s+[0-9]+:\\s*[0-9]+:\\s*[0-9]+\\s*am\\s*$|[0-9]+:\\s*[0-9]+:\\s*[0-9]+\\s*pm\\s*$))?";
1025-    return this;
1026-  }
1027-
1028-  TIMESTAMP_UNITS_CAPTURE_GROUP() : DateRegExBuilder {
1029-    this.regexString += "(\\s*([0-9]+)()()\\s*(am|pm)\\s*$)|(\\s*([0-9]+):\\s*([0-9]+)()()\\s*$)|(\\s*(([0-9]+):\\s*([0-9]+)()\\s*(am|pm))\\s*$)|(\\s*(([0-9]+):\\s*([0-9]+):\\s*([0-9]+)())\\s*$)|(\\s*(([0-9]+):\\s*([0-9]+):\\s*([0-9]+)\\s*(am|pm))\\s*$)";
1030-    return this;
1031-  }
1032-
1033-  /**
1034-   * Build the regular expression and ignore case.
1035-   * @returns {RegExp}
1036-   */
1037-  build() : RegExp {
1038-    return new RegExp(this.regexString, 'i');
1039-  }
1040-}
1041-
1042-
1043 export {
1044   ArgsChecker,
1045   CriteriaFunctionFactory,
1046diff --git a/tests/DateFormulasTest.ts b/tests/DateFormulasTest.ts
1047index 6fc08c0..6b6ba10 100644
1048--- a/tests/DateFormulasTest.ts
1049+++ b/tests/DateFormulasTest.ts
1050@@ -23,15 +23,18 @@ function catchAndAssertEquals(toExecute, expected) {
1051 assertEquals(EDATE(DATE(1992, 6, 24), 1), DATE(1992, 7, 24));
1052 assertEquals(EDATE(DATE(1992, 5, 24), 2), DATE(1992, 7, 24));
1053 assertEquals(EDATE(DATE(1992, 5, 24), 2.2), DATE(1992, 7, 24));
1054+assertEquals(EDATE("1992, 5, 24", 2), DATE(1992, 7, 24));
1055+assertEquals(EDATE("6/24/92", 1), DATE(1992, 7, 24));
1056+catchAndAssertEquals(function() {
1057+  EDATE("str", 2);
1058+}, ERRORS.VALUE_ERROR);
1059 
1060 
1061 // Test DATE
1062 assertEquals(DATE(1900, 1, 2).toNumber(), 3);
1063 assertEquals(DATE(1900, 1, 1).toNumber(), 2);
1064 assertEquals(DATE(1900, 1, 4).toNumber(), 5);
1065-catchAndAssertEquals(function() {
1066-  DATE(1900, 0, 4);
1067-}, ERRORS.NUM_ERROR);
1068+
1069 catchAndAssertEquals(function() {
1070   DATE(1900, 0, 5);
1071 }, ERRORS.NUM_ERROR);