spreadsheet
typeScript/javascript spreadsheet parser, with formulas.
git clone https://git.vogt.world/spreadsheet.git
Log | Files | README.md
← Commit log
commit
message
Fixing up some of the delimiter issues.
author
Ben Vogt <[email protected]>
date
2017-04-01 22:00:31
stats
3 file(s) changed, 147 insertions(+), 93 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 ec345b1..362b159 100644
  3--- a/src/RawFormulas/Date.ts
  4+++ b/src/RawFormulas/Date.ts
  5@@ -45,17 +45,22 @@ var DATE = function (...values) {
  6 
  7 const YEAR_MONTHDIG_DAY = DateRegExBuilder.DateRegExBuilder()
  8   .start()
  9-  .OPTIONAL_DAYNAME().OPTIONAL_COMMA().YYYY().FLEX_DELIMITER().MM().FLEX_DELIMITER().DD_W_SPACE().OPTIONAL_TIMESTAMP_CAPTURE_GROUP()
 10+  .OPTIONAL_DAYNAME().OPTIONAL_COMMA().YYYY().FLEX_DELIMITER_LOOSEDOT().MM().FLEX_DELIMITER_LOOSEDOT().DD_W_SPACE().OPTIONAL_TIMESTAMP_CAPTURE_GROUP()
 11   .end()
 12   .build();
 13 const MONTHDIG_DAY_YEAR = DateRegExBuilder.DateRegExBuilder()
 14   .start()
 15-  .OPTIONAL_DAYNAME().OPTIONAL_COMMA().MM().FLEX_DELIMITER().DD().FLEX_DELIMITER().YYYY14_W_SPACE().OPTIONAL_TIMESTAMP_CAPTURE_GROUP()
 16+  .OPTIONAL_DAYNAME().OPTIONAL_COMMA().MM().FLEX_DELIMITER_LOOSEDOT().DD().FLEX_DELIMITER_LOOSEDOT().YYYY14_W_SPACE().OPTIONAL_TIMESTAMP_CAPTURE_GROUP()
 17   .end()
 18   .build();
 19 const DAY_MONTHNAME_YEAR = DateRegExBuilder.DateRegExBuilder()
 20   .start()
 21-  .OPTIONAL_DAYNAME().OPTIONAL_COMMA().DD().FLEX_DELIMITER().MONTHNAME().FLEX_DELIMITER().YYYY14_W_SPACE().OPTIONAL_TIMESTAMP_CAPTURE_GROUP()
 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@@ -75,7 +80,7 @@ const YEAR_MONTHNAME = DateRegExBuilder.DateRegExBuilder()
 32   .build();
 33 const MONTHNAME_YEAR = DateRegExBuilder.DateRegExBuilder()
 34   .start()
 35-  .OPTIONAL_DAYNAME().OPTIONAL_COMMA().MONTHNAME().FLEX_DELIMITER().YYYY14_W_SPACE().OPTIONAL_TIMESTAMP_CAPTURE_GROUP()
 36+  .OPTIONAL_DAYNAME().OPTIONAL_COMMA().MONTHNAME().FLEX_DELIMITER().YYYY2_OR_4_W_SPACE().OPTIONAL_TIMESTAMP_CAPTURE_GROUP()
 37   .end()
 38   .build();
 39 // For reference: https://regex101.com/r/47GARA/1/
 40@@ -85,7 +90,6 @@ const TIMESTAMP = DateRegExBuilder.DateRegExBuilder()
 41   .end()
 42   .build();
 43 
 44-
 45 /**
 46  * Converts a provided date string in a known format to a date value.
 47  * @param values[0] date_string - The string representing the date. Understood formats include any date format which is
 48@@ -129,14 +133,13 @@ var DATEVALUE = function (...values) : number {
 49   }
 50 
 51   /**
 52-   *
 53-   * @param timestampString
 54-   * @param momentToMutate
 55-   * @returns {moment.Moment}
 56+   * Matches a timestamp string, adding the units to the moment passed in.
 57+   * @param timestampString to parse. ok formats: "10am", "10:10", "10:10am", "10:10:10", "10:10:10am", etc.
 58+   * @param momentToMutate to mutate
 59+   * @returns {Moment} mutated and altered.
 60    */
 61   function matchTimestampAndMutateMoment(timestampString : string, momentToMutate: moment.Moment) : moment.Moment {
 62     var matches = timestampString.match(TIMESTAMP);
 63-    // console.log("match and mutating:", timestampString, "from", dateString, matches);
 64     if (matches && matches[1] !== undefined) { // 10am
 65       var hours = parseInt(matches[2]);
 66       if (hours > 12) {
 67@@ -274,6 +277,25 @@ var DATEVALUE = function (...values) : number {
 68     }
 69   }
 70 
 71+  // Check MONTHNAME_DAY_YEAR, Month(fd)DD(fd)YYYY, 'Aug 19 2020'
 72+  if (m === undefined) {
 73+    var matches = dateString.match(MONTHNAME_DAY_YEAR);
 74+    if (matches && matches.length >= 8) {
 75+      // Check delimiters. If they're not the same, throw error.
 76+      if (matches[4].replace(/\s*/g, '') !== matches[6].replace(/\s*/g, '')) {
 77+        throw new CellError(VALUE_ERROR, "DATEVALUE parameter '" + dateString + "' cannot be parsed to date/time.");
 78+      }
 79+      var years = parseInt(matches[7]);
 80+      var monthName = matches[3];
 81+      var days = parseInt(matches[5]) - 1; // Days are zero indexed.
 82+      var tmpMoment = createMoment(years, monthName, days);
 83+      if (matches.length >= 9 && matches[8] !== undefined) {
 84+        tmpMoment = matchTimestampAndMutateMoment(matches[8], tmpMoment);
 85+      }
 86+      m = tmpMoment;
 87+    }
 88+  }
 89+
 90   // Check DAY_MONTHNAME_YEAR, DD(fd)Month(fd)YYYY, '24/July/1992'
 91   if (m === undefined) {
 92     var matches = dateString.match(DAY_MONTHNAME_YEAR);
 93diff --git a/src/RawFormulas/Utils.ts b/src/RawFormulas/Utils.ts
 94index 6b3e591..767b26f 100644
 95--- a/src/RawFormulas/Utils.ts
 96+++ b/src/RawFormulas/Utils.ts
 97@@ -505,13 +505,31 @@ class DateRegExBuilder {
 98     return this;
 99   }
100 
101+  YYYY2_OR_4_W_SPACE() : DateRegExBuilder {
102+    this.regexString += "([0-9]{2}|[0-9]{4}|[0-9]{2}\\s+|[0-9]{4}\\s+)";
103+    return this;
104+  }
105+
106   /**
107-   * Add capture group for a flexible delimiter, including ", ", " ", ".", "\", "-".
108+   * Add capture group for a flexible delimiter, including ", ", " ", ". ", "\", "-".
109    * @returns {DateRegExBuilder} builder
110    * @constructor
111    */
112   FLEX_DELIMITER() : DateRegExBuilder {
113-    this.regexString += "(,?\\s+|\\s*-?\\.?-?\\/?\\s*)";
114+    // this.regexString += "(,?\\s+|\\s*-?\\.?-?\\/?\\s+)";// close to being right
115+    this.regexString += "(,?\\s+|\\s*\\.\\s+|\\s*-\\s*|\\s*\\/\\s*)";
116+    return this;
117+  }
118+
119+  /**
120+   * Add capture group for a flexible delimiter, including ", ", " ", ".", "\", "-". Different from FLEX_DELIMITER
121+   * in that it will match periods with zero or more spaces on either side.
122+   * @returns {DateRegExBuilder} builder
123+   * @constructor
124+   */
125+  FLEX_DELIMITER_LOOSEDOT() : DateRegExBuilder {
126+    // this.regexString += "(,?\\s+|\\s*-?\\.?-?\\/?\\s+)";// close to being right
127+    this.regexString += "(,?\\s+|\\s*\\.\\s*|\\s*-\\s*|\\s*\\/\\s*)";
128     return this;
129   }
130 
131diff --git a/tests/DateFormulasTest.ts b/tests/DateFormulasTest.ts
132index acc7346..bdaf288 100644
133--- a/tests/DateFormulasTest.ts
134+++ b/tests/DateFormulasTest.ts
135@@ -423,6 +423,13 @@ assertEquals(DATEVALUE("20 Sep 2015"), 42267);
136 assertEquals(DATEVALUE("20 Oct 2015"), 42297);
137 assertEquals(DATEVALUE("20 Nov 2015"), 42328);
138 assertEquals(DATEVALUE("20 Dec 2015"), 42358);
139+assertEquals(DATEVALUE("29 Feb 2004"), 38046); // leap year, 29th ok
140+catchAndAssertEquals(function() {
141+  DATEVALUE("29 Feb 2001");// not leap year, 29th not ok
142+}, ERRORS.VALUE_ERROR);
143+catchAndAssertEquals(function() {
144+  DATEVALUE("32 June 2001");// overload numbers not ok
145+}, ERRORS.VALUE_ERROR);
146 // delimiter tests
147 assertEquals(DATEVALUE("Sun, 09, Feb, 2017"), 42775);
148 assertEquals(DATEVALUE("Sun, 09/Feb/2017"), 42775);
149@@ -440,7 +447,7 @@ catchAndAssertEquals(function() {
150   DATEVALUE("09,Feb,2017");
151 }, ERRORS.VALUE_ERROR);
152 // timestamp tests
153-assertEquals(DATEVALUE("24/June/1992 10am"), 33779); // TODO: come back to these. right now just testing to make sure they don't break anything.
154+assertEquals(DATEVALUE("24/June/1992 10am"), 33779);
155 assertEquals(DATEVALUE("24/June/1992 10:10"), 33779);
156 assertEquals(DATEVALUE("24/June/1992 10:10am"), 33779);
157 assertEquals(DATEVALUE("24/June/1992 10:10:10"), 33779);
158@@ -450,6 +457,27 @@ assertEquals(DATEVALUE("24/June/1992 10: 10 "), 33779);
159 assertEquals(DATEVALUE("24/June/1992 10: 10 pm"), 33779);
160 assertEquals(DATEVALUE("24/June/1992 10: 10: 10"), 33779);
161 assertEquals(DATEVALUE("24/June/1992  10: 10: 10    am   "), 33779);
162+// MONTHNAME_DAY_YEAR, Month(fd)DD(fd)YYYY, 'Aug 19 2020' =============================================================================
163+assertEquals(DATEVALUE("Sun Feb 09 2017"), 42775);
164+assertEquals(DATEVALUE("Sun Feb 9 2017"), 42775);
165+assertEquals(DATEVALUE("Mon Feb 09 2017"), 42775);
166+assertEquals(DATEVALUE("Thursday Feb 09 2017"), 42775);
167+assertEquals(DATEVALUE("Thursday February 09 2017"), 42775);
168+assertEquals(DATEVALUE("Sun September 01 20"), 44075);
169+assertEquals(DATEVALUE("Sun, Feb, 09, 2017"), 42775);
170+assertEquals(DATEVALUE("May 20 1992"), 33744);
171+assertEquals(DATEVALUE("December 31 100"), -657070);
172+assertEquals(DATEVALUE("January 13 0030"), 10971);
173+assertEquals(DATEVALUE("January 13 1200"), -255656);
174+assertEquals(DATEVALUE("January 22 2222"), 117631);
175+assertEquals(DATEVALUE("November 3 4243"), 856071);
176+assertEquals(DATEVALUE("Feb 29 2004"), 38046); // leap year, 29th ok
177+catchAndAssertEquals(function() {
178+  DATEVALUE("Feb 29 2001");// not leap year, 29th not ok
179+}, ERRORS.VALUE_ERROR);
180+catchAndAssertEquals(function() {
181+  DATEVALUE("June 32 2001");// overload numbers not ok
182+}, ERRORS.VALUE_ERROR);
183 // YEAR_MONTHDIG, YYYY(fd)MM, '1992/06' ================================================================================
184 assertEquals(DATEVALUE("2017/01"), 42736);
185 assertEquals(DATEVALUE("2017/02"), 42767);
186@@ -469,12 +497,16 @@ assertEquals(DATEVALUE("Thursday 2017/01"), 42736);
187 assertEquals(DATEVALUE("Thursday, 2017/01"), 42736);
188 assertEquals(DATEVALUE("2017/01"), 42736);
189 assertEquals(DATEVALUE("2017-01"), 42736);
190-assertEquals(DATEVALUE("2017.01"), 42736);
191+assertEquals(DATEVALUE("2017. 01"), 42736);
192+assertEquals(DATEVALUE("2017 01"), 42736);
193 assertEquals(DATEVALUE("2017, 01"), 42736);
194-// Comma delimiters should be followed by spaces.
195+// Comma and period delimiters should be followed by spaces.
196 catchAndAssertEquals(function() {
197   DATEVALUE("2017,01");
198 }, ERRORS.VALUE_ERROR);
199+catchAndAssertEquals(function() {
200+  DATEVALUE("2017.01");
201+}, ERRORS.VALUE_ERROR);
202 // timestamp test
203 assertEquals(DATEVALUE("2017-01 10am"), 42736); // TODO: come back to these. right now just testing to make sure they don't break anything.
204 assertEquals(DATEVALUE("2017-01 10:10"), 42736);
205@@ -504,12 +536,15 @@ assertEquals(DATEVALUE("Thursday 01/2017"), 42736);
206 assertEquals(DATEVALUE("Thursday, 01/2017"), 42736);
207 assertEquals(DATEVALUE("1/2017"), 42736);
208 assertEquals(DATEVALUE("01-2017"), 42736);
209-assertEquals(DATEVALUE("01.2017"), 42736);
210+assertEquals(DATEVALUE("01. 2017"), 42736);
211 assertEquals(DATEVALUE("01, 2017"), 42736);
212-// Comma delimiters should be followed by spaces.
213+// Comma, period delimiters should be followed by spaces.
214 catchAndAssertEquals(function() {
215   DATEVALUE("01,2017");
216 }, ERRORS.VALUE_ERROR);
217+catchAndAssertEquals(function() {
218+  DATEVALUE("01.2017");
219+}, ERRORS.VALUE_ERROR);
220 // 0 is not a month
221 catchAndAssertEquals(function() {
222   DATEVALUE("0/2017");
223@@ -543,12 +578,15 @@ assertEquals(DATEVALUE("Thursday 2017 January"), 42736);
224 assertEquals(DATEVALUE("Thursday, 2017 January"), 42736);
225 assertEquals(DATEVALUE("2017/January"), 42736);
226 assertEquals(DATEVALUE("2017-January"), 42736);
227-assertEquals(DATEVALUE("2017.January"), 42736);
228+assertEquals(DATEVALUE("2017. January"), 42736);
229 assertEquals(DATEVALUE("2017, January"), 42736);
230 // Comma delimiters should be followed by spaces.
231 catchAndAssertEquals(function() {
232   DATEVALUE("2017,January");
233 }, ERRORS.VALUE_ERROR);
234+catchAndAssertEquals(function() {
235+  DATEVALUE("2017.January");
236+}, ERRORS.VALUE_ERROR);
237 // timestamp test
238 assertEquals(DATEVALUE("2017-January 10am"), 42736); // TODO: come back to these. right now just testing to make sure they don't break anything.
239 assertEquals(DATEVALUE("2017-January 10:10"), 42736);
240@@ -575,24 +613,46 @@ assertEquals(DATEVALUE("November 2017"), 43040);
241 assertEquals(DATEVALUE("December 2017"), 43070);
242 assertEquals(DATEVALUE("  Feb    2017  "), 42767);
243 assertEquals(DATEVALUE("Feb-2017"), 42767);
244-assertEquals(DATEVALUE("Feb.2017"), 42767);
245+assertEquals(DATEVALUE("Feb. 2017"), 42767);
246 assertEquals(DATEVALUE("Feb/2017"), 42767);
247 assertEquals(DATEVALUE("Feb    .    2017"), 42767);
248 assertEquals(DATEVALUE("Feb -      2017"), 42767);
249 assertEquals(DATEVALUE("January 0030"), 10959);
250 assertEquals(DATEVALUE("November 4243"), 856069);
251 assertEquals(DATEVALUE("December 0100"), -657100);
252+assertEquals(DATEVALUE("Jan 2017"), 42736);
253+assertEquals(DATEVALUE("Feb 2017"), 42767);
254+assertEquals(DATEVALUE("Mar 2017"), 42795);
255+assertEquals(DATEVALUE("Apr 2017"), 42826);
256+assertEquals(DATEVALUE("May 2017"), 42856);
257+assertEquals(DATEVALUE("Jun 2017"), 42887);
258+assertEquals(DATEVALUE("Jul 2017"), 42917);
259+assertEquals(DATEVALUE("Aug 2017"), 42948);
260+assertEquals(DATEVALUE("Sep 2017"), 42979);
261+assertEquals(DATEVALUE("Oct 2017"), 43009);
262+assertEquals(DATEVALUE("Nov 2017"), 43040);
263+assertEquals(DATEVALUE("Dec 2017"), 43070);
264+assertEquals(DATEVALUE("Feb, 2017"), 42767);
265+catchAndAssertEquals(function() {
266+  DATEVALUE("December 100");// need 4 digits
267+}, ERRORS.VALUE_ERROR);
268+catchAndAssertEquals(function() {
269+  DATEVALUE("Dec.20");// need space after if using period
270+}, ERRORS.VALUE_ERROR);
271 // delimiter tests
272 assertEquals(DATEVALUE("Thursday January 2017"), 42736);
273 assertEquals(DATEVALUE("Thursday, January 2017"), 42736);
274 assertEquals(DATEVALUE("January/2017"), 42736);
275 assertEquals(DATEVALUE("January-2017"), 42736);
276-assertEquals(DATEVALUE("January.2017"), 42736);
277+assertEquals(DATEVALUE("January. 2017"), 42736);
278 assertEquals(DATEVALUE("January, 2017"), 42736);
279-// Comma delimiters should be followed by spaces.
280+// Comma, period delimiters should be followed by spaces.
281 catchAndAssertEquals(function() {
282   DATEVALUE("January,2017");
283 }, ERRORS.VALUE_ERROR);
284+catchAndAssertEquals(function() {
285+  DATEVALUE("January.2017");
286+}, ERRORS.VALUE_ERROR);
287 // timestamp test
288 assertEquals(DATEVALUE("January-2017 10am"), 42736); // TODO: come back to these. right now just testing to make sure they don't break anything.
289 assertEquals(DATEVALUE("January-2017 10:10"), 42736);
290@@ -604,67 +664,3 @@ assertEquals(DATEVALUE("January-2017 10: 10 "), 42736);
291 assertEquals(DATEVALUE("January-2017 10: 10 pm"), 42736);
292 assertEquals(DATEVALUE("January-2017 10: 10: 10"), 42736);
293 assertEquals(DATEVALUE("January-2017  10: 10: 10    am  "), 42736);
294-
295-
296-
297-
298-
299-
300-
301-
302-
303-
304-
305-
306-
307-
308-// assertEquals(DATEVALUE("Sun Feb 09 2017"), 42775);
309-// assertEquals(DATEVALUE("Sun Feb 9 2017"), 42775);
310-// assertEquals(DATEVALUE("Mon Feb 09 2017"), 42775);
311-// assertEquals(DATEVALUE("Thursday Feb 09 2017"), 42775);
312-// assertEquals(DATEVALUE("Thursday February 09 2017"), 42775);
313-// assertEquals(DATEVALUE("Sun September 01 20"), 44075);
314-// assertEquals(DATEVALUE("Sun, Feb, 09, 2017"), 42775);
315-// assertEquals(DATEVALUE("May 20 1992"), 33744);
316-// assertEquals(DATEVALUE("December 31 100"), -657070);
317-// assertEquals(DATEVALUE("January 13 0030"), 10971);
318-// assertEquals(DATEVALUE("January 13 1200"), -255656);
319-// assertEquals(DATEVALUE("January 22 2222"), 117631);
320-// assertEquals(DATEVALUE("November 3 4243"), 856071);
321-// assertEquals(DATEVALUE("Feb 29 2004"), 38046); // leap year, 29th ok
322-// catchAndAssertEquals(function() {
323-//   DATEVALUE("Feb 29 2001");// not leap year, 29th not ok
324-// }, ERRORS.VALUE_ERROR);
325-// catchAndAssertEquals(function() {
326-//   DATEVALUE("June 32 2001");// overload numbers not ok
327-// }, ERRORS.VALUE_ERROR);
328-// // (Dayname) DD Month YYYY
329-// assertEquals(DATEVALUE("29 Feb 2004"), 38046); // leap year, 29th ok
330-// catchAndAssertEquals(function() {
331-//   DATEVALUE("29 Feb 2001");// not leap year, 29th not ok
332-// }, ERRORS.VALUE_ERROR);
333-// catchAndAssertEquals(function() {
334-//   DATEVALUE("32 June 2001");// overload numbers not ok
335-// }, ERRORS.VALUE_ERROR);
336-
337-
338-// assertEquals(DATEVALUE("Jan 2017"), 42736);
339-// assertEquals(DATEVALUE("Feb 2017"), 42767);
340-// assertEquals(DATEVALUE("Mar 2017"), 42795);
341-// assertEquals(DATEVALUE("Apr 2017"), 42826);
342-// assertEquals(DATEVALUE("May 2017"), 42856);
343-// assertEquals(DATEVALUE("Jun 2017"), 42887);
344-// assertEquals(DATEVALUE("Jul 2017"), 42917);
345-// assertEquals(DATEVALUE("Aug 2017"), 42948);
346-// assertEquals(DATEVALUE("Sep 2017"), 42979);
347-// assertEquals(DATEVALUE("Oct 2017"), 43009);
348-// assertEquals(DATEVALUE("Nov 2017"), 43040);
349-// assertEquals(DATEVALUE("Dec 2017"), 43070);
350-// assertEquals(DATEVALUE("Feb, 2017"), 42767);
351-// catchAndAssertEquals(function() {
352-//   DATEVALUE("December 100");// need 4 digits
353-// }, ERRORS.VALUE_ERROR);
354-// catchAndAssertEquals(function() {
355-//   DATEVALUE("Dec.20");// need space if using period
356-// }, ERRORS.VALUE_ERROR);
357-//