commit
message
[Date.ts, etc] Another step towards using TypeCaster to to moment<->number conversions
author
Ben Vogt <[email protected]>
date
2017-05-06 00:35:07
stats
3 file(s) changed,
99 insertions(+),
82 deletions(-)
files
src/Formulas/Date.ts
src/Formulas/Financial.ts
src/Utilities/TypeCaster.ts
1diff --git a/src/Formulas/Date.ts b/src/Formulas/Date.ts
2index 0c31829..661da12 100644
3--- a/src/Formulas/Date.ts
4+++ b/src/Formulas/Date.ts
5@@ -38,12 +38,12 @@ var DATE = function (...values) : number {
6 .add(year < FIRST_YEAR ? year : year - FIRST_YEAR, 'years') // If the value is less than 1900, assume 1900 as start index for year
7 .add(month, 'months')
8 .add(day, 'days');
9- var excelDate = new ExcelDate(m);
10- if (excelDate.toNumber() < 0) {
11- throw new NumError("DATE evaluates to an out of range value " + excelDate.toNumber()
12+ var dateAsNumber = TypeCaster.momentToDayNumber(m);
13+ if (dateAsNumber < 0) {
14+ throw new NumError("DATE evaluates to an out of range value " + dateAsNumber
15 + ". It should be greater than or equal to 0.");
16 }
17- return excelDate.toNumber();
18+ return dateAsNumber;
19 };
20
21 /**
22@@ -57,15 +57,15 @@ var DATE = function (...values) : number {
23 var DATEVALUE = function (...values) : number {
24 ArgsChecker.checkLength(values, 1);
25 var dateString = TypeCaster.firstValueAsString(values[0]);
26- var date;
27+ var dateAsNumber;
28 try {
29- date = TypeCaster.stringToExcelDate(dateString);
30+ dateAsNumber = TypeCaster.stringToExcelDate(dateString);
31 } catch (e) {
32 throw new ValueError("DATEVALUE parameter '" + dateString + "' cannot be parsed to date/time.");
33 }
34
35 // If we've not been able to parse the date by now, then we cannot parse it at all.
36- return date.toNumberFloored();
37+ return dateAsNumber;
38 };
39
40
41@@ -78,14 +78,14 @@ var DATEVALUE = function (...values) : number {
42 */
43 var EDATE = function (...values) : number {
44 ArgsChecker.checkLength(values, 2);
45- var startDate = TypeCaster.firstValueAsExcelDate(values[0], true); // tell firstValueAsExcelDate to coerce boolean
46- if (startDate.toNumber() < 0) {
47- throw new NumError("Function EDATE parameter 1 value is " + startDate.toNumber() + ". It should be greater than or equal to 0.");
48+ var startDateNumber = TypeCaster.firstValueAsExcelDate(values[0], true); // tell firstValueAsExcelDate to coerce boolean
49+ if (startDateNumber < 0) {
50+ throw new NumError("Function EDATE parameter 1 value is " + startDateNumber+ ". It should be greater than or equal to 0.");
51 }
52 var months = Math.floor(TypeCaster.firstValueAsNumber(values[1]));
53 // While ExcelDate.toNumber() will return an inclusive count of days since 1900/1/1, moment.Moment.add assumes
54 // exclusive count of days.
55- return new ExcelDate(moment.utc(ORIGIN_MOMENT).add(startDate.toNumber(), "days").add(months, "months")).toNumber();
56+ return new ExcelDate(moment.utc(ORIGIN_MOMENT).add(startDateNumber, "days").add(months, "months")).toNumber();
57 };
58
59
60@@ -100,17 +100,17 @@ var EDATE = function (...values) : number {
61 */
62 var EOMONTH = function (...values) : number {
63 ArgsChecker.checkLength(values, 2);
64- var startDate = TypeCaster.firstValueAsExcelDate(values[0], true); // tell firstValueAsExcelDate to coerce boolean
65- if (startDate.toNumber() < 0) {
66- throw new NumError("Function EOMONTH parameter 1 value is " + startDate.toNumber() + ". It should be greater than or equal to 0.");
67+ var startDateNumber = TypeCaster.firstValueAsExcelDate(values[0], true); // tell firstValueAsExcelDate to coerce boolean
68+ if (startDateNumber < 0) {
69+ throw new NumError("Function EOMONTH parameter 1 value is " + startDateNumber + ". It should be greater than or equal to 0.");
70 }
71 var months = Math.floor(TypeCaster.firstValueAsNumber(values[1]));
72 // While ExcelDate.toNumber() will return an inclusive count of days since 1900/1/1, moment.Moment.add assumes
73 // exclusive count of days.
74- return new ExcelDate(moment.utc(ORIGIN_MOMENT)
75- .add(startDate.toNumber(), "days")
76- .add(months, "months")
77- .endOf("month")).toNumberFloored();
78+ return TypeCaster.momentToDayNumber(moment.utc(ORIGIN_MOMENT)
79+ .add(startDateNumber, "days")
80+ .add(months, "months")
81+ .endOf("month"));
82 };
83
84
85@@ -123,11 +123,11 @@ var EOMONTH = function (...values) : number {
86 */
87 var DAY = function (...values) : number {
88 ArgsChecker.checkLength(values, 1);
89- var date = TypeCaster.firstValueAsExcelDate(values[0], true); // tell firstValueAsExcelDate to coerce boolean
90- if (date.toNumber() < 0) {
91- throw new NumError("Function DAY parameter 1 value is " + date.toNumber() + ". It should be greater than or equal to 0.");
92+ var dateNumber = TypeCaster.firstValueAsExcelDate(values[0], true); // tell firstValueAsExcelDate to coerce boolean
93+ if (dateNumber < 0) {
94+ throw new NumError("Function DAY parameter 1 value is " + dateNumber + ". It should be greater than or equal to 0.");
95 }
96- return date.toMoment().date();
97+ return TypeCaster.numberToMoment(dateNumber).date();
98 };
99
100
101@@ -142,7 +142,7 @@ var DAYS = function (...values) : number {
102 ArgsChecker.checkLength(values, 2);
103 var end = TypeCaster.firstValueAsExcelDate(values[0], true); // tell firstValueAsExcelDate to coerce boolean
104 var start = TypeCaster.firstValueAsExcelDate(values[1], true); // tell firstValueAsExcelDate to coerce boolean
105- return end.toNumber() - start.toNumber();
106+ return end - start;
107 };
108
109
110@@ -164,8 +164,8 @@ var DAYS = function (...values) : number {
111 */
112 var DAYS360 = function (...values) : number {
113 ArgsChecker.checkLengthWithin(values, 2, 3);
114- var start = TypeCaster.firstValueAsExcelDate(values[0], true).toMoment(); // tell firstValueAsExcelDate to coerce boolean
115- var end = TypeCaster.firstValueAsExcelDate(values[1], true).toMoment(); // tell firstValueAsExcelDate to coerce boolean
116+ var start = TypeCaster.numberToMoment(TypeCaster.firstValueAsExcelDate(values[0], true)); // tell firstValueAsExcelDate to coerce boolean
117+ var end = TypeCaster.numberToMoment(TypeCaster.firstValueAsExcelDate(values[1], true)); // tell firstValueAsExcelDate to coerce boolean
118 var methodToUse = false;
119 if (values.length === 3) {
120 methodToUse = TypeCaster.firstValueAsBoolean(values[2]);
121@@ -204,10 +204,10 @@ var DAYS360 = function (...values) : number {
122 var MONTH = function (...values) : number {
123 ArgsChecker.checkLength(values, 1);
124 var date = TypeCaster.firstValueAsExcelDate(values[0], true); // tell firstValueAsExcelDate to coerce boolean
125- if (date.toNumber() < 0) {
126- throw new NumError("Function MONTH parameter 1 value is " + date.toNumber() + ". It should be greater than or equal to 0.");
127+ if (date < 0) {
128+ throw new NumError("Function MONTH parameter 1 value is " + date + ". It should be greater than or equal to 0.");
129 }
130- return date.toMoment().month() + 1;
131+ return TypeCaster.numberToMoment(date).month() + 1;
132 };
133
134
135@@ -221,10 +221,10 @@ var MONTH = function (...values) : number {
136 var YEAR = function (...values) : number {
137 ArgsChecker.checkLength(values, 1);
138 var date = TypeCaster.firstValueAsExcelDate(values[0], true); // tell firstValueAsExcelDate to coerce boolean
139- if (date.toNumber() < 0) {
140- throw new NumError("Function YEAR parameter 1 value is " + date.toNumber() + ". It should be greater than or equal to 0.");
141+ if (date < 0) {
142+ throw new NumError("Function YEAR parameter 1 value is " + date + ". It should be greater than or equal to 0.");
143 }
144- return date.toMoment().year();
145+ return TypeCaster.numberToMoment(date).year();
146 };
147
148
149@@ -244,10 +244,10 @@ var WEEKDAY = function (...values) : number {
150 ArgsChecker.checkLengthWithin(values, 1, 2);
151 var date = TypeCaster.firstValueAsExcelDate(values[0], true); // tell firstValueAsExcelDate to coerce boolean
152 var offsetType = values.length === 2 ? TypeCaster.firstValueAsNumber(values[1]) : 1;
153- if (date.toNumber() < 0) {
154- throw new NumError("Function WEEKDAY parameter 1 value is " + date.toNumber() + ". It should be greater than or equal to 0.");
155+ if (date < 0) {
156+ throw new NumError("Function WEEKDAY parameter 1 value is " + date + ". It should be greater than or equal to 0.");
157 }
158- var day = date.toMoment().day();
159+ var day = TypeCaster.numberToMoment(date).day();
160 if (offsetType === 1) {
161 return day + 1;
162 } else if (offsetType === 2) {
163@@ -303,10 +303,10 @@ var WEEKNUM = function (...values) : number {
164 ArgsChecker.checkLengthWithin(values, 1, 2);
165 var date = TypeCaster.firstValueAsExcelDate(values[0], true); // tell firstValueAsExcelDate to coerce boolean
166 var shiftType = values.length === 2 ? TypeCaster.firstValueAsNumber(values[1]) : 1;
167- if (date.toNumber() < 0) {
168- throw new NumError("Function YEAR parameter 1 value is " + date.toNumber() + ". It should be greater than or equal to 0.");
169+ if (date < 0) {
170+ throw new NumError("Function YEAR parameter 1 value is " + date + ". It should be greater than or equal to 0.");
171 }
172- var dm = date.toMoment();
173+ var dm = TypeCaster.numberToMoment(date);
174 var week = dm.week();
175 var dayOfWeek = dm.day(); // between 1 and 7, inclusively
176 if (shiftType === 1) {
177@@ -377,44 +377,43 @@ var DATEDIF = function (...values) : number {
178 var end = TypeCaster.firstValueAsExcelDate(values[1], true);
179 var unit = TypeCaster.firstValueAsString(values[2]);
180 var unitClean = unit.toUpperCase();
181+ var startMoment = TypeCaster.numberToMoment(start);
182+ var endMoment = TypeCaster.numberToMoment(end);
183
184- if (start.toNumber() > end.toNumber()) {
185+ if (start > end) {
186 throw new NumError("Function DATEDIF parameter 1 (" + start.toString() +
187 ") should be on or before Function DATEDIF parameter 2 (" + end.toString() + ").");
188 }
189
190 if (unitClean === "Y") {
191- return Math.floor(end.toMoment().diff(start.toMoment(), "years"));
192+ return Math.floor(endMoment.diff(startMoment, "years"));
193 } else if (unitClean === "M") {
194- return Math.floor(end.toMoment().diff(start.toMoment(), "months"));
195+ return Math.floor(endMoment.diff(startMoment, "months"));
196 } else if (unitClean === "D") {
197- return end.toNumber() - start.toNumber();
198+ return end - start;
199 } else if (unitClean === "MD") {
200- var s = start.toMoment();
201- var e = end.toMoment();
202- while(s.isBefore(e)) {
203+ var s = startMoment;
204+ while(s.isBefore(endMoment)) {
205 s.add(1, "month");
206 }
207 s.subtract(1, "month");
208- var days = e.diff(s, "days");
209- return s.date() === e.date() ? 0 : days;
210+ var days = endMoment.diff(s, "days");
211+ return s.date() === endMoment.date() ? 0 : days;
212 } else if (unitClean === "YM") {
213- var s = start.toMoment();
214- var e = end.toMoment();
215- while(s.isBefore(e)) {
216+ var s = startMoment;
217+ while(s.isBefore(endMoment)) {
218 s.add(1, "year");
219 }
220 s.subtract(1, "year");
221- var months = Math.floor(e.diff(s, "months"));
222+ var months = Math.floor(endMoment.diff(s, "months"));
223 return months === 12 ? 0 : months;
224 } else if (unitClean === "YD") {
225- var s = start.toMoment();
226- var e = end.toMoment();
227- while(s.isBefore(e)) {
228+ var s = startMoment;
229+ while(s.isBefore(endMoment)) {
230 s.add(1, "year");
231 }
232 s.subtract(1, "year");
233- var days = Math.floor(e.diff(s, "days"));
234+ var days = Math.floor(endMoment.diff(s, "days"));
235 return days >= 365 ? 0 : days;
236 } else {
237 throw new NumError("Function DATEDIF parameter 3 value is " + unit +
238@@ -447,8 +446,8 @@ var YEARFRAC = function (...values) : number {
239 var end = TypeCaster.firstValueAsExcelDate(values[1], true);
240 var basis = values.length === 2 ? 0 : TypeCaster.firstValueAsNumber(values[2]);
241
242- var s = start.toMoment();
243- var e = end.toMoment();
244+ var s = TypeCaster.numberToMoment(start);
245+ var e = TypeCaster.numberToMoment(end);
246 if (e.isBefore(s)) {
247 var me = moment.utc(e);
248 e = moment.utc(s);
249@@ -505,12 +504,12 @@ var YEARFRAC = function (...values) : number {
250 } else if (feb29Between(s, e) || (emonth === 1 && eday === 29)) {
251 ylength = 366;
252 }
253- return Math.abs((end.toNumber() - start.toNumber()) / ylength);
254+ return Math.abs((end - start) / ylength);
255 } else {
256 var years = (eyear - syear) + 1;
257 var days = moment.utc([eyear+1]).startOf("year").diff(moment.utc([syear]).startOf("year"), 'days');
258 var average = days / years;
259- return Math.abs((end.toNumber() - start.toNumber()) / average);
260+ return Math.abs((end - start) / average);
261 }
262 // Actual/360
263 case 2:
264@@ -634,16 +633,16 @@ var NETWORKDAYS = function (...values) : number {
265 }
266 }
267 // Handle cases in which the start date is not before the end date.
268- var didSwap = start.toNumber() > end.toNumber();
269+ var didSwap = start > end;
270 if (didSwap) {
271 var swap = end;
272 end = start;
273 start = swap;
274 }
275
276- var c = moment.utc(start.toMoment());
277+ var c = moment.utc(TypeCaster.numberToMoment(start));
278 var weekendDays = [6, 0]; // Default weekend_days.
279- var days = end.toNumber() - start.toNumber() + 1;
280+ var days = end - start + 1;
281 var networkDays = days;
282 var j = 0;
283 while (j < days) {
284@@ -749,15 +748,15 @@ var NETWORKDAYS$INTL = function (...values) : number {
285 }
286 }
287 // Handle cases in which the start date is not before the end date.
288- var didSwap = start.toNumber() > end.toNumber();
289+ var didSwap = start > end;
290 if (didSwap) {
291 var swap = end;
292 end = start;
293 start = swap;
294 }
295
296- var c = moment.utc(start.toMoment());
297- var days = end.toNumber() - start.toNumber() + 1;
298+ var c = moment.utc(TypeCaster.numberToMoment(start));
299+ var days = end - start + 1;
300 var networkDays = days;
301 var j = 0;
302 while (j < days) {
303@@ -858,7 +857,7 @@ var WORKDAY = function (...values) : number {
304 }
305
306 var weekendDays = [0, 6];
307- var cd = moment.utc(start.toMoment());
308+ var cd = moment.utc(TypeCaster.numberToMoment(start));
309 var j = 0;
310 while (j < days) {
311 cd.add(1, 'days');
312@@ -957,7 +956,7 @@ var WORKDAY$INTL = function (...values) : number {
313 holidays.push(TypeCaster.valueToNumber(values[3]));
314 }
315 }
316- var cd = moment.utc(start.toMoment());
317+ var cd = moment.utc(TypeCaster.numberToMoment(start));
318 var j = 0;
319 while (j < days) {
320 cd.add(1, 'days');
321diff --git a/src/Formulas/Financial.ts b/src/Formulas/Financial.ts
322index 151fad5..8ef1b4d 100644
323--- a/src/Formulas/Financial.ts
324+++ b/src/Formulas/Financial.ts
325@@ -392,12 +392,12 @@ var ACCRINT = function (...values) {
326 // In MSE, there is a 7th (zero-indexed-6th) param that indicates the calculation-method to use, which indicates
327 // weather the total accrued interest starting at the first_intrest date, instead of the issue date.
328 var firstPayment = TypeCaster.firstValueAsExcelDate(values[1]);
329- if (firstPayment.toNumber() < 0) {
330- throw new NumError("Function ACCRINT parameter 2 value is " + firstPayment.toNumber()
331+ if (firstPayment < 0) {
332+ throw new NumError("Function ACCRINT parameter 2 value is " + firstPayment
333 + ". It should be greater than 0.");
334 }
335 var settlement = TypeCaster.firstValueAsExcelDate(values[2]);
336- if (issue.toNumber() > settlement.toNumber()) {
337+ if (issue > settlement) {
338 throw new NumError("Function ACCRINT parameter 1 (" + issue.toString()
339 + ") should be on or before Function ACCRINT parameter 3 (" + settlement.toString() + ").")
340 }
341diff --git a/src/Utilities/TypeCaster.ts b/src/Utilities/TypeCaster.ts
342index 5b9f1d9..502b837 100644
343--- a/src/Utilities/TypeCaster.ts
344+++ b/src/Utilities/TypeCaster.ts
345@@ -129,6 +129,10 @@ function matchTimestampAndMutateMoment(timestampString : string, momentToMutate:
346 */
347 class TypeCaster {
348
349+ private static ORIGIN_MOMENT = moment.utc([1899, 11, 30]).startOf("day");
350+ private static SECONDS_IN_DAY = 86400;
351+
352+
353 /**
354 * Converts a time-formatted string to a number between 0 and 1, exclusive on 1.
355 * @param timeString
356@@ -327,13 +331,13 @@ class TypeCaster {
357 * @param dateString to parse
358 * @returns {ExcelDate} resulting date
359 */
360- public static stringToExcelDate(dateString : string) : ExcelDate {
361+ public static stringToExcelDate(dateString : string) : number {
362 // m will be set and valid or invalid, or will remain undefined
363 var m = TypeCaster.parseStringToMoment(dateString);
364 if (m === undefined || !m.isValid()) {
365 throw new ValueError("DATEVALUE parameter '" + dateString + "' cannot be parsed to date/time.");
366 }
367- return new ExcelDate(m.set('hours', 0).set('minutes', 0).set('seconds', 0));
368+ return TypeCaster.momentToDayNumber(m.set('hours', 0).set('minutes', 0).set('seconds', 0));
369 }
370
371 /**
372@@ -515,7 +519,7 @@ class TypeCaster {
373 * @param coerceBoolean should a boolean be converted
374 * @returns {ExcelDate} representing a date
375 */
376- static firstValueAsExcelDate(input: any, coerceBoolean?: boolean) : ExcelDate {
377+ static firstValueAsExcelDate(input: any, coerceBoolean?: boolean) : number {
378 if (input instanceof Array) {
379 if (input.length === 0) {
380 throw new RefError("Reference does not exist.");
381@@ -541,27 +545,37 @@ class TypeCaster {
382 * @param coerceBoolean should a boolean be converted
383 * @returns {ExcelDate} ExcelDate
384 */
385- static valueToExcelDate(value: any, coerceBoolean?: boolean) : ExcelDate {
386- if (value instanceof ExcelDate) {
387+ static valueToExcelDate(value: any, coerceBoolean?: boolean) : number {
388+ if (typeof value === "number") {
389 return value;
390- } else if (typeof value === "number") {
391- return ExcelDate.fromDay(value);
392 } else if (typeof value === "string") {
393 try {
394 return TypeCaster.stringToExcelDate(value)
395 } catch (e) {
396 if (TypeCaster.canCoerceToNumber(value)) {
397- return ExcelDate.fromDay(TypeCaster.valueToNumber(value));
398+ return TypeCaster.valueToNumber(value);
399 }
400 throw new ValueError("___ expects date values. But '" + value + "' is a text and cannot be coerced to a date.")
401 }
402 } else if (typeof value === "boolean") {
403 if (coerceBoolean) {
404- return ExcelDate.fromDay(value ? 1 : 0);
405+ return value ? 1 : 0;
406 }
407 throw new ValueError("___ expects date values. But '" + value + "' is a boolean and cannot be coerced to a date.")
408 }
409 }
410+
411+ static momentToNumber(m : moment.Moment) : number {
412+ return m.diff(this.ORIGIN_MOMENT, "seconds") / this.SECONDS_IN_DAY;
413+ }
414+
415+ static momentToDayNumber(m : moment.Moment) : number {
416+ return Math.floor(TypeCaster.momentToNumber(m));
417+ }
418+
419+ static numberToMoment(n : number) : moment.Moment {
420+ return moment.utc(TypeCaster.ORIGIN_MOMENT).add(n, "days");
421+ }
422 }
423
424 /**