commit
message
[ExcelDate] Using seconds as ExcelDate storage
author
Ben Vogt <[email protected]>
date
2017-04-26 01:38:47
stats
5 file(s) changed,
31 insertions(+),
29 deletions(-)
files
src/ExcelDate.ts
src/RawFormulas/Date.ts
src/RawFormulas/Utils.ts
tests/DateFormulasTest.ts
tests/utils/Asserts.ts
1diff --git a/src/ExcelDate.ts b/src/ExcelDate.ts
2index 8394cf4..7dffdd0 100644
3--- a/src/ExcelDate.ts
4+++ b/src/ExcelDate.ts
5@@ -1,34 +1,33 @@
6 /// <reference path="../node_modules/moment/moment.d.ts"/>
7 import * as moment from "moment";
8
9-const ORIGIN_MOMENT = moment.utc([1900]);
10-
11+const ORIGIN_MOMENT = moment.utc([1899, 11, 30]).startOf("day");
12+const SECONDS_IN_DAY = 86400;
13
14 /**
15 * Date that mimics the functionality of an Excel Date. Represented by the number of days since 1900/1/1.
16 */
17 class ExcelDate {
18- private day : number;
19+ private seconds : number;
20+
21+ static fromDay(day : number) {
22+ return new ExcelDate(moment.utc(ORIGIN_MOMENT).add(day, 'days'));
23+ }
24
25 /**
26 * Constructs an ExcelDate when given a day or moment.
27- * @param dayOrMoment number of days since 1900/1/1 (inclusively) or a Moment to use as the day.
28+ * @param m Moment to use as the day.
29 */
30- constructor(dayOrMoment : number | moment.Moment) {
31- if (typeof dayOrMoment === "number") {
32- this.day = dayOrMoment;
33- } else {
34- var d = Math.round(dayOrMoment.diff(ORIGIN_MOMENT, "days")) + 2;
35- this.day = d === 0 || d === 1 ? 2 : d; // Not zero-indexed but two-indexed. Otherwise could be negative value.
36- }
37+ constructor(m : moment.Moment) {
38+ this.seconds = m.diff(ORIGIN_MOMENT, "seconds");
39 }
40
41 /**
42 * String representation of the day in the format M/D/YYYY. Eg: 6/24/1992
43 * @returns {string} day in the format M/D/YYYY.
44 */
45- toString() {
46- return moment.utc(ORIGIN_MOMENT).add(this.toNumber() - 2, 'days').format("M/D/Y");
47+ toString() : string {
48+ return moment.utc(ORIGIN_MOMENT).add(this.toNumber(), 'days').format("M/D/Y").toString();
49 }
50
51 /**
52@@ -36,7 +35,7 @@ class ExcelDate {
53 * @returns {number} days since 1900/1/1
54 */
55 toNumber() {
56- return this.day;
57+ return Math.floor(this.seconds / SECONDS_IN_DAY);
58 }
59
60 /**
61@@ -44,7 +43,7 @@ class ExcelDate {
62 * @returns {Moment}
63 */
64 toMoment() : moment.Moment {
65- return moment.utc(ORIGIN_MOMENT).add(this.toNumber() - 2, "days");
66+ return moment.utc(ORIGIN_MOMENT).add(this.toNumber(), "days");
67 }
68
69 /**
70diff --git a/src/RawFormulas/Date.ts b/src/RawFormulas/Date.ts
71index 194e6cf..a9cab76 100644
72--- a/src/RawFormulas/Date.ts
73+++ b/src/RawFormulas/Date.ts
74@@ -24,12 +24,12 @@ import {
75 */
76 var DATE = function (...values) : ExcelDate {
77 const FIRST_YEAR = 1900;
78- const ORIGIN_DATE = moment.utc([FIRST_YEAR]).startOf("year");
79 ArgsChecker.checkLength(values, 3);
80 var year = Math.abs(Math.floor(TypeCaster.firstValueAsNumber(values[0]))); // No negative values for year
81 var month = Math.floor(TypeCaster.firstValueAsNumber(values[1])) - 1; // Months are between 0 and 11.
82 var day = Math.floor(TypeCaster.firstValueAsNumber(values[2])) - 1; // Days are also zero-indexed.
83- var m = moment.utc(ORIGIN_DATE).startOf("year")
84+ var m = moment.utc(ORIGIN_MOMENT)
85+ .add(2, "days")
86 .add(year < FIRST_YEAR ? year : year - FIRST_YEAR, 'years') // If the value is less than 1900, assume 1900 as start index for year
87 .add(month, 'months')
88 .add(day, 'days');
89@@ -80,7 +80,7 @@ var EDATE = function (...values) : ExcelDate {
90 var months = Math.floor(TypeCaster.firstValueAsNumber(values[1]));
91 // While ExcelDate.toNumber() will return an inclusive count of days since 1900/1/1, moment.Moment.add assumes
92 // exclusive count of days.
93- return new ExcelDate(moment.utc(ORIGIN_MOMENT).add(startDate.toNumber() - 2, "days").add(months, "months"));
94+ return new ExcelDate(moment.utc(ORIGIN_MOMENT).add(startDate.toNumber(), "days").add(months, "months"));
95 };
96
97
98@@ -102,7 +102,7 @@ var EOMONTH = function (...values) : ExcelDate {
99 var months = Math.floor(TypeCaster.firstValueAsNumber(values[1]));
100 // While ExcelDate.toNumber() will return an inclusive count of days since 1900/1/1, moment.Moment.add assumes
101 // exclusive count of days.
102- return new ExcelDate(moment.utc(ORIGIN_MOMENT).add(startDate.toNumber() - 2, "days").add(months, "months").endOf("month"));
103+ return new ExcelDate(moment.utc(ORIGIN_MOMENT).add(startDate.toNumber(), "days").add(months, "months").endOf("month"));
104 };
105
106
107@@ -767,10 +767,13 @@ var NETWORKDAYS$INTL = function (...values) : number {
108 return networkDays;
109 };
110
111+var NOW = function () {
112+ ArgsChecker.checkLength(arguments.length, 0);
113+};
114+
115
116 // Functions unimplemented.
117 var WORKDAY$INTL;
118-var NOW;
119 var TIME;
120 var TODAY;
121 var WORKDAY;
122diff --git a/src/RawFormulas/Utils.ts b/src/RawFormulas/Utils.ts
123index 2ae1225..ff02be1 100644
124--- a/src/RawFormulas/Utils.ts
125+++ b/src/RawFormulas/Utils.ts
126@@ -822,19 +822,19 @@ class TypeCaster {
127 if (value instanceof ExcelDate) {
128 return value;
129 } else if (typeof value === "number") {
130- return new ExcelDate(value);
131+ return ExcelDate.fromDay(value);
132 } else if (typeof value === "string") {
133 try {
134 return TypeCaster.stringToExcelDate(value)
135 } catch (e) {
136 if (TypeCaster.canCoerceToNumber(value)) {
137- return new ExcelDate(TypeCaster.valueToNumber(value));
138+ return ExcelDate.fromDay(TypeCaster.valueToNumber(value));
139 }
140 throw new ValueError("___ expects date values. But '" + value + "' is a text and cannot be coerced to a date.")
141 }
142 } else if (typeof value === "boolean") {
143 if (coerceBoolean) {
144- return new ExcelDate(value ? 1 : 0);
145+ return ExcelDate.fromDay(value ? 1 : 0);
146 }
147 throw new ValueError("___ expects date values. But '" + value + "' is a boolean and cannot be coerced to a date.")
148 }
149diff --git a/tests/DateFormulasTest.ts b/tests/DateFormulasTest.ts
150index 8191ba5..bcc8ad7 100644
151--- a/tests/DateFormulasTest.ts
152+++ b/tests/DateFormulasTest.ts
153@@ -36,7 +36,8 @@ function catchAndAssertEquals(toExecute, expected) {
154 }
155 }
156 if (toThrow) {
157- throw new Error("expected error: " + expected);
158+ console.log("expected error: " + expected);
159+ console.trace();
160 }
161 }
162
163@@ -1272,6 +1273,7 @@ assertEquals(DATE(2, 33, 44).toNumber(), 1749);
164 assertEquals(DATE(1777, 33, 44).toNumber(), 650055);
165 assertEquals(DATE(1976, 2, -10).toNumber(), 27780);
166 assertEquals(DATE(-1900, 1, 1).toNumber(), 2);
167+assertEquals(DATE(1992, 1, 10).toNumber(), 33613);
168
169
170
171diff --git a/tests/utils/Asserts.ts b/tests/utils/Asserts.ts
172index dab1855..49560d5 100644
173--- a/tests/utils/Asserts.ts
174+++ b/tests/utils/Asserts.ts
175@@ -9,7 +9,7 @@ import {
176 function assertEquals(actual, expected) {
177 if (actual instanceof ExcelDate && expected instanceof ExcelDate) {
178 if (!actual.equals(expected)) {
179- console.log("expected:", expected, " actual:", actual);
180+ console.log("expected:", expected.toString(), " actual:", actual.toString());
181 console.trace();
182 }
183 } else {
184@@ -28,12 +28,12 @@ function assertEquals(actual, expected) {
185 function assertArrayEquals(actual: Array<any>, expected: Array<any>, ) {
186 if (expected.length != actual.length) {
187 console.log("expected: ", expected, " actual:", actual);
188- throw Error();
189+ console.trace();
190 }
191 for (var index in expected) {
192 if (expected[index] != actual[index]) {
193 console.log("expected: ", expected, " actual:", actual);
194- throw Error();
195+ console.trace();
196 }
197 }
198 }