spreadsheet
typeScript/javascript spreadsheet parser, with formulas.
git clone https://git.vogt.world/spreadsheet.git
Log | Files | README.md
← Commit log
commit
message
work-in-progress on EDATE function
author
Ben Vogt <[email protected]>
date
2017-04-02 01:39:25
stats
7 file(s) changed, 105 insertions(+), 109 deletions(-)
files
README.md
src/ExcelDate.ts
src/RawFormulas/Date.ts
src/RawFormulas/Utils.ts
tests/DateFormulasTest.ts
tests/FormulasTest.ts
tests/utils/Asserts.ts
  1diff --git a/README.md b/README.md
  2index ee9c1b3..8b7774b 100644
  3--- a/README.md
  4+++ b/README.md
  5@@ -50,86 +50,6 @@ See `DOLLAR` function for more info.
  6 
  7 # The Great Date Refactoring (TM)
  8 
  9-### List of possible dates that we should be able to parse
 10- * "1999/1/13"                    DONE
 11- * "1999-1-13"
 12- * "1999 1 13"
 13- * "1999.1.13"
 14- * "1999, 1, 13"
 15- * "1/13/1999"                    DONE
 16- * "1-13-1999"
 17- * "1 13 1999"
 18- * "1.13.1999"
 19- * "1, 13, 1999"
 20- * "1999/1/13 10am"               DONE
 21- * "1999-1-13 10am"
 22- * "1999 1 13 10am"
 23- * "1999.1.13 10am"
 24- * "1999/1/13 10:22"              DONE
 25- * "1999-1-13 10:22"
 26- * "1999 1 13 10:22"
 27- * "1999.1.13 10:22"
 28- * "1999/1/13 10:10am"            DONE
 29- * "1999-1-13 10:10am"
 30- * "1999 1 13 10:10am"
 31- * "1999.1.13 10:10am"
 32- * "1999/1/13 10:10:10"           DONE
 33- * "1999-1-13 10:10:10"
 34- * "1999 1 13 10:10:10"
 35- * "1999.1.13 10:10:10"
 36- * "1999/1/13 10:10:10pm"         DONE
 37- * "1999-1-13 10:10:10pm"
 38- * "1999 1 13 10:10:10pm"
 39- * "1999.1.13 10:10:10pm"
 40- * "Sun Feb 09 2017"              DONE
 41- * "Sun Feb 09 2017 10am"
 42- * "Sun Feb 09 2017 10:10"
 43- * "Sun Feb 09 2017 10:10am"
 44- * "Sun Feb 09 2017 10:10:10"
 45- * "Sun Feb 09 2017 10:10:10pm"
 46- * "Sun 09 Feb 2017"              DONE
 47- * "Sun 09 Feb 2017 10am"
 48- * "Sun 09 Feb 2017 10:10"
 49- * "Sun 09 Feb 2017 10:10am"
 50- * "Sun 09 Feb 2017 10:10:10"
 51- * "Sun 09 Feb 2017 10:10:10pm"
 52- * "Feb-2017"                     DONE
 53- * "Feb-2017 10am"
 54- * "Feb-2017 10:10"
 55- * "Feb-2017 10:10am"
 56- * "Feb-2017 10:10:10"
 57- * "Feb-2017 10:10:10pm"
 58- * "Feb 22"                       DONE
 59- * "Feb 22 10am"
 60- * "Feb 22 10:10"
 61- * "Feb 22 10:10am"
 62- * "Feb 22 10:10:10"
 63- * "Feb 22 10:10:10pm"
 64- * "22-Feb"                       DONE
 65- * "22-Feb 10am"
 66- * "22-Feb 10:10"
 67- * "22-Feb 10:10am"
 68- * "22-Feb 10:10:10"
 69- * "22-Feb 10:10:10pm"
 70- * "22-Feb-2017"
 71- * "22-Feb-2017 10am"
 72- * "22-Feb-2017 10:10"
 73- * "22-Feb-2017 10:10am"
 74- * "22-Feb-2017 10:10:10"
 75- * "22-Feb-2017 10:10:10pm"
 76- * "10-22"
 77- * "10-22 10am"
 78- * "10-22 10:10"
 79- * "10-22 10:10am"
 80- * "10-22 10:10:10"
 81- * "10-22 10:10:10pm"
 82- * "10/2022"
 83- * "10-2022 10am"
 84- * "10-2022 10:10"
 85- * "10-2022 10:10am"
 86- * "10-2022 10:10:10"
 87- * "10-2022 10:10:10pm"
 88-
 89 * Dates have special types
 90 Like dollars, dates are special types, but can be compared as if they're primatives. For example, this statement is
 91 valid inside a cell: `=DATE(1992, 6, 6) > =DATE(1992, 6, 10)`. We should check types and and have Date-to-number
 92diff --git a/src/ExcelDate.ts b/src/ExcelDate.ts
 93index 836bbd7..8394cf4 100644
 94--- a/src/ExcelDate.ts
 95+++ b/src/ExcelDate.ts
 96@@ -12,7 +12,7 @@ class ExcelDate {
 97 
 98   /**
 99    * Constructs an ExcelDate when given a day or moment.
100-   * @param dayOrMoment number of days since 1900/1/1 or a Moment to use as the day.
101+   * @param dayOrMoment number of days since 1900/1/1 (inclusively) or a Moment to use as the day.
102    */
103   constructor(dayOrMoment : number | moment.Moment) {
104     if (typeof dayOrMoment === "number") {
105@@ -28,18 +28,36 @@ class ExcelDate {
106    * @returns {string} day in the format M/D/YYYY.
107    */
108   toString() {
109-    return moment.utc(ORIGIN_MOMENT).add(this.day, 'days').format("M/D/Y");
110+    return moment.utc(ORIGIN_MOMENT).add(this.toNumber() - 2, 'days').format("M/D/Y");
111   }
112 
113   /**
114-   * Returns the day as a number.
115+   * Returns the day as a number of days since 1900/1/1, inclusively on both ends.
116    * @returns {number} days since 1900/1/1
117    */
118   toNumber() {
119     return this.day;
120   }
121+
122+  /**
123+   * Converts to a moment
124+   * @returns {Moment}
125+   */
126+  toMoment() : moment.Moment {
127+    return moment.utc(ORIGIN_MOMENT).add(this.toNumber() - 2, "days");
128+  }
129+
130+  /**
131+   * Tests equality.
132+   * @param ed other ExcelDate to compare to
133+   * @returns {boolean} true if equals
134+   */
135+  equals(ed : ExcelDate) : boolean {
136+    return this.toNumber() === ed.toNumber();
137+  }
138 }
139 
140 export {
141-  ExcelDate
142+  ExcelDate,
143+  ORIGIN_MOMENT
144 }
145\ No newline at end of file
146diff --git a/src/RawFormulas/Date.ts b/src/RawFormulas/Date.ts
147index 2df88a1..8469e8d 100644
148--- a/src/RawFormulas/Date.ts
149+++ b/src/RawFormulas/Date.ts
150@@ -12,7 +12,8 @@ import {
151   CellError
152 } from "../Errors";
153 import {
154-  ExcelDate
155+  ExcelDate,
156+  ORIGIN_MOMENT
157 } from "../ExcelDate";
158 
159 /**
160@@ -20,10 +21,10 @@ import {
161  * @param values[0] year - The year component of the date.
162  * @param values[1] month - The month component of the date.
163  * @param values[2] day - The day component of the date.
164- * @returns {Date} newly created date.
165+ * @returns {ExcelDate} newly created date.
166  * @constructor
167  */
168-var DATE = function (...values) {
169+var DATE = function (...values) : ExcelDate {
170   const FIRST_YEAR = 1900;
171   const ORIGIN_DATE = moment.utc([FIRST_YEAR]).startOf("year");
172   ArgsChecker.checkLength(values, 3);
173@@ -339,12 +340,17 @@ var DATEVALUE = function (...values) : number {
174 };
175 
176 
177+var EDATE = function (...values) : ExcelDate {
178+  ArgsChecker.checkLength(values, 2);
179+  var startDate = TypeCaster.firstValueAsExcelDate(values[0]);
180+  var months = TypeCaster.firstValueAsNumber(values[1]);
181+  return new ExcelDate(moment.utc(ORIGIN_MOMENT).add(startDate.toNumber() - 2, "days").add(months, "months"));
182+};
183+
184+
185 var DAY = Formula["DAY"];
186 var DAYS = Formula["DAYS"];
187 var DAYS360 = Formula["DAYS360"];
188-var EDATE = function (start_date: Date, months) {
189-  return moment(start_date).add(months, 'months').toDate();
190-};
191 var EOMONTH = function (start_date, months) {
192   var edate = moment(start_date).add(months, 'months');
193   return new Date(edate.year(), edate.month(), edate.daysInMonth());
194diff --git a/src/RawFormulas/Utils.ts b/src/RawFormulas/Utils.ts
195index f3386bf..1618a7e 100644
196--- a/src/RawFormulas/Utils.ts
197+++ b/src/RawFormulas/Utils.ts
198@@ -1,5 +1,6 @@
199 import { CellError } from "../Errors"
200 import * as ERRORS from "../Errors"
201+import {ExcelDate} from "../ExcelDate";
202 
203 /**
204  * Converts wild-card style expressions (in which * matches zero or more characters, and ? matches exactly one character)
205@@ -240,6 +241,36 @@ class TypeCaster {
206     }
207     return TypeCaster.valueToBoolean(input);
208   }
209+
210+  /**
211+   * Takes the input type and will throw a REF_ERROR or coerce it into a ExcelDate
212+   * @param input input to attempt to coerce to a ExcelDate
213+   * @returns {ExcelDate} representing a date
214+   */
215+  static firstValueAsExcelDate(input: any) : ExcelDate {
216+    if (input instanceof Array) {
217+      if (input.length === 0) {
218+        throw new CellError(ERRORS.REF_ERROR, "Reference does not exist.");
219+      }
220+      return TypeCaster.firstValueAsExcelDate(input[0]);
221+    }
222+    return TypeCaster.valueToExcelDate(input);
223+  }
224+
225+  /**
226+   * Convert a value to ExcelDate if possible.
227+   * @param value of any type, including array. array cannot be empty.
228+   * @returns {ExcelDate} ExcelDate
229+   */
230+  static valueToExcelDate(value: any) : ExcelDate {
231+    if (value instanceof ExcelDate) {
232+      return value;
233+    } else if (typeof value === "number") {
234+      return new ExcelDate(value);
235+    } else if (typeof value === "string" || typeof value === "boolean") {
236+      throw new CellError(ERRORS.VALUE_ERROR, "___ expects boolean values. But '" + value + "' is a text and cannot be coerced to a boolean.")
237+    }
238+  }
239 }
240 
241 /**
242diff --git a/tests/DateFormulasTest.ts b/tests/DateFormulasTest.ts
243index bdaf288..6bf5495 100644
244--- a/tests/DateFormulasTest.ts
245+++ b/tests/DateFormulasTest.ts
246@@ -1,7 +1,8 @@
247 
248-import { DATE, DATEVALUE } from "../src/RawFormulas/RawFormulas"
249+import { DATE, DATEVALUE, EDATE } from "../src/RawFormulas/RawFormulas"
250 import * as ERRORS from "../src/Errors"
251 import {assertEquals} from "./utils/Asserts"
252+import moment = require("moment");
253 
254 function catchAndAssertEquals(toExecute, expected) {
255   var toThrow = null;
256@@ -18,9 +19,14 @@ function catchAndAssertEquals(toExecute, expected) {
257   }
258 }
259 
260+// // Test EDATE
261+assertEquals(EDATE(DATE(1992, 6, 24), 1), DATE(1992, 7, 24));
262+assertEquals(EDATE(DATE(1992, 5, 24), 2), DATE(1992, 7, 24));
263+
264+
265 // Test DATE
266-assertEquals(DATE(1900, 1, 1).toNumber(), 2);
267 assertEquals(DATE(1900, 1, 2).toNumber(), 3);
268+assertEquals(DATE(1900, 1, 1).toNumber(), 2);
269 assertEquals(DATE(1900, 1, 4).toNumber(), 5);
270 catchAndAssertEquals(function() {
271   DATE(1900, 0, 4);
272@@ -44,6 +50,14 @@ assertEquals(DATE(-1900, 1, 1).toNumber(), 2);
273 
274 
275 // Test DATEVALUE
276+assertEquals(DATEVALUE("6/24/92"), 33779);
277+assertEquals(DATEVALUE(["6/24/92", false]), 33779);
278+catchAndAssertEquals(function() {
279+  DATEVALUE("6/24/92", 10);
280+}, ERRORS.NA_ERROR);
281+catchAndAssertEquals(function() {
282+  DATEVALUE();
283+}, ERRORS.NA_ERROR);
284 // MONTHDIG_DAY_YEAR, MM(fd)DD(fd)YYYY =================================================================================
285 assertEquals(DATEVALUE("6/24/92"), 33779);
286 assertEquals(DATEVALUE("6/24/1992"), 33779);
287@@ -457,7 +471,7 @@ assertEquals(DATEVALUE("24/June/1992 10: 10 "), 33779);
288 assertEquals(DATEVALUE("24/June/1992 10: 10 pm"), 33779);
289 assertEquals(DATEVALUE("24/June/1992 10: 10: 10"), 33779);
290 assertEquals(DATEVALUE("24/June/1992  10: 10: 10    am   "), 33779);
291-// MONTHNAME_DAY_YEAR, Month(fd)DD(fd)YYYY, 'Aug 19 2020' =============================================================================
292+// MONTHNAME_DAY_YEAR, Month(fd)DD(fd)YYYY, 'Aug 19 2020' ==============================================================
293 assertEquals(DATEVALUE("Sun Feb 09 2017"), 42775);
294 assertEquals(DATEVALUE("Sun Feb 9 2017"), 42775);
295 assertEquals(DATEVALUE("Mon Feb 09 2017"), 42775);
296@@ -508,7 +522,7 @@ catchAndAssertEquals(function() {
297   DATEVALUE("2017.01");
298 }, ERRORS.VALUE_ERROR);
299 // timestamp test
300-assertEquals(DATEVALUE("2017-01 10am"), 42736); // TODO: come back to these. right now just testing to make sure they don't break anything.
301+assertEquals(DATEVALUE("2017-01 10am"), 42736);
302 assertEquals(DATEVALUE("2017-01 10:10"), 42736);
303 assertEquals(DATEVALUE("2017-01 10:10am"), 42736);
304 assertEquals(DATEVALUE("2017-01 10:10:10"), 42736);
305@@ -550,7 +564,7 @@ catchAndAssertEquals(function() {
306   DATEVALUE("0/2017");
307 }, ERRORS.VALUE_ERROR);
308 // timestamp test
309-assertEquals(DATEVALUE("01-2017 10am"), 42736); // TODO: come back to these. right now just testing to make sure they don't break anything.
310+assertEquals(DATEVALUE("01-2017 10am"), 42736);
311 assertEquals(DATEVALUE("01-2017 10:10"), 42736);
312 assertEquals(DATEVALUE("01-2017 10:10am"), 42736);
313 assertEquals(DATEVALUE("01-2017 10:10:10"), 42736);
314@@ -588,7 +602,7 @@ catchAndAssertEquals(function() {
315   DATEVALUE("2017.January");
316 }, ERRORS.VALUE_ERROR);
317 // timestamp test
318-assertEquals(DATEVALUE("2017-January 10am"), 42736); // TODO: come back to these. right now just testing to make sure they don't break anything.
319+assertEquals(DATEVALUE("2017-January 10am"), 42736);
320 assertEquals(DATEVALUE("2017-January 10:10"), 42736);
321 assertEquals(DATEVALUE("2017-January 10:10am"), 42736);
322 assertEquals(DATEVALUE("2017-January 10:10:10"), 42736);
323@@ -654,7 +668,7 @@ catchAndAssertEquals(function() {
324   DATEVALUE("January.2017");
325 }, ERRORS.VALUE_ERROR);
326 // timestamp test
327-assertEquals(DATEVALUE("January-2017 10am"), 42736); // TODO: come back to these. right now just testing to make sure they don't break anything.
328+assertEquals(DATEVALUE("January-2017 10am"), 42736);
329 assertEquals(DATEVALUE("January-2017 10:10"), 42736);
330 assertEquals(DATEVALUE("January-2017 10:10am"), 42736);
331 assertEquals(DATEVALUE("January-2017 10:10:10"), 42736);
332diff --git a/tests/FormulasTest.ts b/tests/FormulasTest.ts
333index 8948902..8acbc14 100644
334--- a/tests/FormulasTest.ts
335+++ b/tests/FormulasTest.ts
336@@ -1036,10 +1036,6 @@ assertEquals(AND(10), true);
337 
338 
339 
340-// TODO: Turned off while working on DATE().
341-// assertEqualsDates(EDATE(DATE(1992, 6, 24), 1), new Date('7/24/1992'));
342-
343-
344 // Test EFFECT
345 assertEquals(EFFECT(0.99, 12), 1.5890167507927795);
346 assertEquals(EFFECT(0.99, 12.111), 1.5890167507927795);
347diff --git a/tests/utils/Asserts.ts b/tests/utils/Asserts.ts
348index dc681fd..dab1855 100644
349--- a/tests/utils/Asserts.ts
350+++ b/tests/utils/Asserts.ts
351@@ -1,12 +1,22 @@
352+import {
353+  ExcelDate
354+} from "../../src/ExcelDate";
355 /**
356  * Assert two params are equal using strict equality testing.
357  * @param actual value
358  * @param expected value
359  */
360 function assertEquals(actual, expected) {
361-  if (expected !== actual) {
362-    console.log("expected:", expected, " actual:", actual);
363-    console.trace();
364+  if (actual instanceof ExcelDate && expected instanceof ExcelDate) {
365+    if (!actual.equals(expected)) {
366+      console.log("expected:", expected, " actual:", actual);
367+      console.trace();
368+    }
369+  } else {
370+    if (expected !== actual) {
371+      console.log("expected:", expected, " actual:", actual);
372+      console.trace();
373+    }
374   }
375 }
376