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