commit
message
Catching negative values for ExcelDate in DATE formula
author
Ben Vogt <[email protected]>
date
2017-02-26 18:38:42
stats
3 file(s) changed,
72 insertions(+),
54 deletions(-)
files
src/ExcelDate.ts
src/RawFormulas/Date.ts
tests/FormulasTest.ts
1diff --git a/src/ExcelDate.ts b/src/ExcelDate.ts
2new file mode 100644
3index 0000000..7e4e1f8
4--- /dev/null
5+++ b/src/ExcelDate.ts
6@@ -0,0 +1,59 @@
7+/// <reference path="../node_modules/moment/moment.d.ts"/>
8+import * as moment from "moment";
9+/**
10+ * Date that mimics the functionality of an Excel Date. Represented by the number of days since 1900/1/1.
11+ */
12+class ExcelDate {
13+ private day : number;
14+
15+ /**
16+ * Constructs an ExcelDate when given a day or moment.
17+ * @param dayOrMoment number of days since 1900/1/1 or a Moment to use as the day.
18+ */
19+ constructor(dayOrMoment : number | moment.Moment) {
20+ if (typeof dayOrMoment === "number") {
21+ this.day = dayOrMoment;
22+ } else {
23+ var ORIGIN_MOMENT = moment(new Date(1900, 0, 1));
24+ var d = Math.round(dayOrMoment.diff(ORIGIN_MOMENT, "days")) + 2;
25+ this.day = d === 0 || d === 1 ? 2 : d; // Not zero-indexed but two-indexed. Otherwise could be negative value.
26+ }
27+ }
28+
29+ /**
30+ * Converts this ExcelDate to a javascript Date.
31+ * @returns {Date} representation of this ExcelDate
32+ */
33+ toDate() {
34+ var utc_days = Math.floor(this.day - 25569);
35+ var utc_value = utc_days * 86400;
36+ var date_info = new Date(utc_value * 1000);
37+ var fractional_day = this.day - Math.floor(this.day) + 0.0000001;
38+ var total_seconds = Math.floor(86400 * fractional_day);
39+ var seconds = total_seconds % 60;
40+ total_seconds -= seconds;
41+ var hours = Math.floor(total_seconds / (60 * 60));
42+ var minutes = Math.floor(total_seconds / 60) % 60;
43+ return new Date(date_info.getFullYear(), date_info.getMonth(), date_info.getDate(), hours, minutes, seconds);
44+ }
45+
46+ /**
47+ * String representation of the day in the format M/D/YYYY. Eg: 6/24/1992
48+ * @returns {string} day in the format M/D/YYYY.
49+ */
50+ toString() {
51+ return moment(this.toDate()).format("M/D/Y");
52+ }
53+
54+ /**
55+ * Returns the day as a number.
56+ * @returns {number} days since 1900/1/1
57+ */
58+ toNumber() {
59+ return this.day;
60+ }
61+}
62+
63+export {
64+ ExcelDate
65+}
66\ No newline at end of file
67diff --git a/src/RawFormulas/Date.ts b/src/RawFormulas/Date.ts
68index 7ea2500..71fefe9 100644
69--- a/src/RawFormulas/Date.ts
70+++ b/src/RawFormulas/Date.ts
71@@ -4,59 +4,13 @@ import * as Formula from "formulajs"
72 import {
73 ArgsChecker, TypeCaster
74 } from "./Utils";
75-
76-/**
77- * Date that mimics the functionality of an Excel Date. Represented by the number of days since 1900/1/1.
78- */
79-class ExcelDate {
80- private day : number;
81-
82- /**
83- * Constructs an ExcelDate when given a day or moment.
84- * @param dayOrMoment number of days since 1900/1/1 or a Moment to use as the day.
85- */
86- constructor(dayOrMoment : number | moment.Moment) {
87- if (typeof dayOrMoment === "number") {
88- this.day = dayOrMoment;
89- } else {
90- var ORIGIN_MOMENT = moment(new Date(1900, 0, 1));
91- this.day = Math.max(Math.round(dayOrMoment.diff(ORIGIN_MOMENT, "days")) + 2, 2); // Minimum value is 2...
92- }
93- }
94-
95- /**
96- * Converts this ExcelDate to a javascript Date.
97- * @returns {Date} representation of this ExcelDate
98- */
99- toDate() {
100- var utc_days = Math.floor(this.day - 25569);
101- var utc_value = utc_days * 86400;
102- var date_info = new Date(utc_value * 1000);
103- var fractional_day = this.day - Math.floor(this.day) + 0.0000001;
104- var total_seconds = Math.floor(86400 * fractional_day);
105- var seconds = total_seconds % 60;
106- total_seconds -= seconds;
107- var hours = Math.floor(total_seconds / (60 * 60));
108- var minutes = Math.floor(total_seconds / 60) % 60;
109- return new Date(date_info.getFullYear(), date_info.getMonth(), date_info.getDate(), hours, minutes, seconds);
110- }
111-
112- /**
113- * String representation of the day in the format M/D/YYYY. Eg: 6/24/1992
114- * @returns {string} day in the format M/D/YYYY.
115- */
116- toString() {
117- return moment(this.toDate()).format("M/D/Y");
118- }
119-
120- /**
121- * Returns the day as a number.
122- * @returns {number} days since 1900/1/1
123- */
124- toNumber() {
125- return this.day;
126- }
127-}
128+import {
129+ NUM_ERROR,
130+ CellError
131+} from "../Errors";
132+import {
133+ ExcelDate
134+} from "../ExcelDate";
135
136 /**
137 * Converts a provided year, month, and day into a date.
138@@ -71,10 +25,12 @@ var DATE = function (...values) {
139 var year = Math.abs(Math.floor(TypeCaster.firstValueAsNumber(values[0]))); // No negative values for year
140 var month = Math.floor(TypeCaster.firstValueAsNumber(values[1]) - 1); // Months are between 0 and 11.
141 var day = Math.floor(TypeCaster.firstValueAsNumber(values[2]));
142- return new ExcelDate(moment(new Date(year, month, day)));
143- // TODO: When we create a date we should use DATEVALUE-style numeric conversion to ensure the value is greater than 0.
144- // TODO: (cont.) throw new CellError(ERRORS.NUM_ERROR, "DATE evaluates to an out of range value -6420. It should be
145- // TODO: (cont.) greater than or equal to 0.");
146+ var excelDate = new ExcelDate(moment(new Date(year, month, day)));
147+ if (excelDate.toNumber() < 0) {
148+ throw new CellError(NUM_ERROR, "DATE evaluates to an out of range value " + excelDate.toNumber()
149+ + ". It should be greater than or equal to 0.");
150+ }
151+ return excelDate;
152 };
153
154
155diff --git a/tests/FormulasTest.ts b/tests/FormulasTest.ts
156index 009d0c0..dc2d422 100644
157--- a/tests/FormulasTest.ts
158+++ b/tests/FormulasTest.ts
159@@ -761,6 +761,12 @@ catchAndAssertEquals(function() {
160 assertEquals(DATE(1900, 1, 1).toNumber(), 2);
161 assertEquals(DATE(1900, 1, 2).toNumber(), 3);
162 assertEquals(DATE(1900, 1, 4).toNumber(), 5);
163+catchAndAssertEquals(function() {
164+ DATE(1900, 0, 4);
165+}, ERRORS.NUM_ERROR);
166+catchAndAssertEquals(function() {
167+ DATE(1900, 0, 5);
168+}, ERRORS.NUM_ERROR);
169 assertEquals(DATE(1992, 6, 24).toNumber(), 33779);
170 assertEquals(DATE(2017, 2, 26).toNumber(), 42792);
171 // Leap day stuff