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