spreadsheet
typeScript/javascript spreadsheet parser, with formulas.
git clone https://git.vogt.world/spreadsheet.git
Log | Files | README.md
← Commit log
commit
message
Fixing overflow params for DATE, adding docs, adding TODOs to README.md
author
Ben Vogt <[email protected]>
date
2017-02-28 05:43:28
stats
5 file(s) changed, 28 insertions(+), 33 deletions(-)
files
README.md
src/ExcelDate.ts
src/RawFormulas/Date.ts
tests/FormulasTest.ts
tests/utils/Asserts.ts
  1diff --git a/README.md b/README.md
  2index 04904c9..b8a7482 100644
  3--- a/README.md
  4+++ b/README.md
  5@@ -7,12 +7,6 @@ Things I should do.
  6 ### SUM and SUMA should be different, and I'm pretty sure they're currently the same.
  7 And the same for MAX, MAXA, COUNT, COUNTA, etc. Look these over.
  8 
  9-### Date-Time issues
 10-Here are a couple of the issues with Dates and so on:
 11-* There seem to be a few issues where someone did something sloppy inside formulaJS, and timezones, daylight-savings,
 12-and leap years are being taken into account when they shouldn't be. For now I think I should just let it go.
 13-The resulting errors from these bugs aren't that bad. I'll mark them down, and investigate them individually.
 14-
 15 ### Protect against injection
 16 How do we protect against users injecting data that looks like `console.log(sensitive_data)` when we evaluate variables
 17 inside parser.js? If we ever want to impliment custom formulas, or even accept data in raw format, we need to guard
 18@@ -59,5 +53,8 @@ Like dollars, dates are special types, but can be compared as if they're primati
 19 valid inside a cell: `=DATE(1992, 6, 6) > =DATE(1992, 6, 10)`. We should check types and and have Date-to-number
 20 conversion inside parser.js.
 21 
 22-### `DATE` should use "roll-over" input handling
 23-When handling input like DATE(1992, 1, 44), we should use momentjs's `add('days', 44)` to build up to the correct date.
 24+### Organize tests in a way that makes sense.
 25+Annotate them, and standardize the error checking for errors like REF, NA, NUM, VALUE, etc.
 26+
 27+### Test all ExcelDate functions
 28+Right now we're just using the number of days since 1900, but we should check the other functions.
 29\ No newline at end of file
 30diff --git a/src/ExcelDate.ts b/src/ExcelDate.ts
 31index 7e4e1f8..4b21bf4 100644
 32--- a/src/ExcelDate.ts
 33+++ b/src/ExcelDate.ts
 34@@ -1,5 +1,9 @@
 35 /// <reference path="../node_modules/moment/moment.d.ts"/>
 36 import * as moment from "moment";
 37+
 38+const ORIGIN_MOMENT = moment([1900]);
 39+
 40+
 41 /**
 42  * Date that mimics the functionality of an Excel Date. Represented by the number of days since 1900/1/1.
 43  */
 44@@ -14,35 +18,17 @@ class ExcelDate {
 45     if (typeof dayOrMoment === "number") {
 46       this.day = dayOrMoment;
 47     } else {
 48-      var ORIGIN_MOMENT = moment(new Date(1900, 0, 1));
 49       var d = Math.round(dayOrMoment.diff(ORIGIN_MOMENT, "days")) + 2;
 50       this.day = d === 0 || d === 1 ? 2 : d; // Not zero-indexed but two-indexed. Otherwise could be negative value.
 51     }
 52   }
 53 
 54-  /**
 55-   * Converts this ExcelDate to a javascript Date.
 56-   * @returns {Date} representation of this ExcelDate
 57-   */
 58-  toDate() {
 59-    var utc_days  = Math.floor(this.day - 25569);
 60-    var utc_value = utc_days * 86400;
 61-    var date_info = new Date(utc_value * 1000);
 62-    var fractional_day = this.day - Math.floor(this.day) + 0.0000001;
 63-    var total_seconds = Math.floor(86400 * fractional_day);
 64-    var seconds = total_seconds % 60;
 65-    total_seconds -= seconds;
 66-    var hours = Math.floor(total_seconds / (60 * 60));
 67-    var minutes = Math.floor(total_seconds / 60) % 60;
 68-    return new Date(date_info.getFullYear(), date_info.getMonth(), date_info.getDate(), hours, minutes, seconds);
 69-  }
 70-
 71   /**
 72    * String representation of the day in the format M/D/YYYY. Eg: 6/24/1992
 73    * @returns {string} day in the format M/D/YYYY.
 74    */
 75   toString() {
 76-    return moment(this.toDate()).format("M/D/Y");
 77+    return ORIGIN_MOMENT.add('days', this.day).format("M/D/Y");
 78   }
 79 
 80   /**
 81diff --git a/src/RawFormulas/Date.ts b/src/RawFormulas/Date.ts
 82index 31f03ab..a346330 100644
 83--- a/src/RawFormulas/Date.ts
 84+++ b/src/RawFormulas/Date.ts
 85@@ -22,14 +22,19 @@ import {
 86  * @param values[2] day - The day component of the date.
 87  * @returns {Date} newly created date.
 88  * @constructor
 89- * TODO: This function should take overflow values for month and day (eg: 44) and roll them over to the next unit.
 90  */
 91 var DATE = function (...values) {
 92+  const FIRST_YEAR = 1900;
 93+  const ORIGIN_DATE = moment([FIRST_YEAR]);
 94   ArgsChecker.checkLength(values, 3);
 95   var year = Math.abs(Math.floor(TypeCaster.firstValueAsNumber(values[0]))); // No negative values for year
 96-  var month = Math.floor(TypeCaster.firstValueAsNumber(values[1]) - 1); // Months are between 0 and 11.
 97-  var day = Math.floor(TypeCaster.firstValueAsNumber(values[2]));
 98-  var excelDate = new ExcelDate(moment(new Date(year, month, day)));
 99+  var month = Math.floor(TypeCaster.firstValueAsNumber(values[1])) - 1; // Months are between 0 and 11.
100+  var day = Math.floor(TypeCaster.firstValueAsNumber(values[2])) - 1; // Days are also zero-indexed.
101+  var m = moment(ORIGIN_DATE)
102+      .add(year < FIRST_YEAR ? year : year - FIRST_YEAR, 'years') // If the value is less than 1900, assume 1900 as start index for year
103+      .add(month, 'months')
104+      .add(day, 'days');
105+  var excelDate = new ExcelDate(m);
106   if (excelDate.toNumber() < 0) {
107     throw new CellError(NUM_ERROR, "DATE evaluates to an out of range value " + excelDate.toNumber()
108         + ". It should be greater than or equal to 0.");
109diff --git a/tests/FormulasTest.ts b/tests/FormulasTest.ts
110index 7a81e27..1aa3e1d 100644
111--- a/tests/FormulasTest.ts
112+++ b/tests/FormulasTest.ts
113@@ -773,6 +773,12 @@ assertEquals(DATE(2017, 2, 26).toNumber(), 42792);
114 assertEquals(DATE(2004, 2, 28).toNumber(), 38045);
115 assertEquals(DATE(2004, 2, 29).toNumber(), 38046);
116 assertEquals(DATE(2004, 3, 1).toNumber(), 38047);
117+// Overflow values
118+assertEquals(DATE(1992, 6, 44).toNumber(), 33799);
119+assertEquals(DATE(2, 33, 44).toNumber(), 1749);
120+assertEquals(DATE(1777, 33, 44).toNumber(), 650055);
121+assertEquals(DATE(1976, 2, -10).toNumber(), 27780);
122+assertEquals(DATE(-1900, 1, 1).toNumber(), 2);
123 
124 
125 
126diff --git a/tests/utils/Asserts.ts b/tests/utils/Asserts.ts
127index 13505bb..c1c6623 100644
128--- a/tests/utils/Asserts.ts
129+++ b/tests/utils/Asserts.ts
130@@ -1,7 +1,7 @@
131 function assertEquals(actual, expected) {
132   if (expected !== actual) {
133     console.log("expected:", expected, " actual:", actual);
134-    throw Error();
135+    console.trace();
136   }
137 }
138