spreadsheet
typeScript/javascript spreadsheet parser, with formulas.
git clone https://git.vogt.world/spreadsheet.git
Log | Files | README.md
← Commit log
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