spreadsheet
typeScript/javascript spreadsheet parser, with formulas.
git clone https://git.vogt.world/spreadsheet.git
Log | Files | README.md
← Commit log
commit
message
[WIP:YEARFRAC] formula partially added and tested
author
Ben Vogt <[email protected]>
date
2017-04-15 19:54:28
stats
3 file(s) changed, 113 insertions(+), 10 deletions(-)
files
src/RawFormulas/Date.ts
tests/DateFormulasTest.ts
tests/FormulasTest.ts
  1diff --git a/src/RawFormulas/Date.ts b/src/RawFormulas/Date.ts
  2index fdfff34..d86c114 100644
  3--- a/src/RawFormulas/Date.ts
  4+++ b/src/RawFormulas/Date.ts
  5@@ -377,7 +377,7 @@ var WEEKNUM = function (...values) {
  6  * @returns {number} number of days, months, or years between two dates.
  7  * @constructor
  8  */
  9-var DATEDIF = function (...values) {
 10+var DATEDIF = function (...values) : number {
 11   ArgsChecker.checkLength(values, 3);
 12   var start = TypeCaster.firstValueAsExcelDate(values[0], true);
 13   var end = TypeCaster.firstValueAsExcelDate(values[1], true);
 14@@ -433,7 +433,91 @@ var DATEDIF = function (...values) {
 15 };
 16 
 17 
 18-var YEARFRAC = Formula["YEARFRAC"];
 19+/**
 20+ * Returns the number of years, including fractional years, between two dates using a specified day count convention.
 21+ * @param values[0] start_date - The start date to consider in the calculation. Must be a reference to a cell
 22+ * containing a date, a function returning a date type, or a number.
 23+ * @param values[1] end_date - The end date to consider in the calculation. Must be a reference to a cell containing
 24+ * a date, a function returning a date type, or a number.
 25+ * @param values[2] day_count_convention - [ OPTIONAL - 0 by default ] - An indicator of what day count method to
 26+ * use.
 27+ * @returns {number}the number of years, including fractional years, between two dates
 28+ * @constructor
 29+ */
 30+var YEARFRAC = function (...values) : number {
 31+  ArgsChecker.checkLengthWithin(values, 2, 3);
 32+  var start = TypeCaster.firstValueAsExcelDate(values[0], true);
 33+  var end = TypeCaster.firstValueAsExcelDate(values[1], true);
 34+  var basis = values.length === 2 ? 0 : TypeCaster.firstValueAsNumber(values[2]);
 35+
 36+  var s = start.toMoment();
 37+  var e = end.toMoment();
 38+  if (e.isBefore(s)) {
 39+    var me = moment.utc(e);
 40+    e = moment.utc(s);
 41+    s = me;
 42+  }
 43+  var syear = s.year();
 44+  var smonth = s.month();
 45+  var sday = s.date();
 46+  var eyear = e.year();
 47+  var emonth = e.month();
 48+  var eday = e.date();
 49+
 50+  var feb29Between = function (date1, date2) {
 51+    // Requires year2 == (year1 + 1) or year2 == year1
 52+    // Returns TRUE if February 29 is between the two dates (date1 may be February 29), with two possibilities:
 53+    // year1 is a leap year and date1 <= Februay 29 of year1
 54+    // year2 is a leap year and date2 > Februay 29 of year2
 55+    var mar1year1 = moment.utc(new Date(date1.year(), 2, 1));
 56+    if (moment.utc([date1.year()]).isLeapYear() && date1.diff(mar1year1) < 0 && date2.diff(mar1year1) >= 0) {
 57+      return true;
 58+    }
 59+    var mar1year2 = moment.utc(new Date(date2.year(), 2, 1));
 60+    if (moment.utc([date2.year()]).isLeapYear() && date2.diff(mar1year2) >= 0 && date1.diff(mar1year2) < 0) {
 61+      return true;
 62+    }
 63+    return false;
 64+  };
 65+
 66+  switch (basis) {
 67+    case 0:
 68+      // US (NASD) 30/360
 69+      // Note: if eday == 31, it stays 31 if sday < 30
 70+      if (sday === 31 && eday === 31) {
 71+        sday = 30;
 72+        eday = 30;
 73+      } else if (sday === 31) {
 74+        sday = 30;
 75+      } else if (sday === 30 && eday === 31) {
 76+        eday = 30;
 77+      } else if (smonth === 1 && emonth === 1 && s.daysInMonth() === sday && e.daysInMonth() === eday) {
 78+        sday = 30;
 79+        eday = 30;
 80+      } else if (smonth === 1 && s.daysInMonth() === sday) {
 81+        sday = 30;
 82+      }
 83+      return Math.abs(((eday + emonth * 30 + eyear * 360) - (sday + smonth * 30 + syear * 360)) / 360);
 84+    case 1:
 85+      // Actual/actual
 86+      var ylength = 365;
 87+      if (syear === eyear || ((syear + 1) === eyear) && ((smonth > emonth) || ((smonth === emonth) && (sday >= eday)))) {
 88+        if (syear === eyear && moment.utc([syear]).isLeapYear()) {
 89+          ylength = 366;
 90+        } else if (feb29Between(s, e) || (emonth === 1 && eday === 29)) {
 91+          ylength = 366;
 92+        }
 93+        return Math.abs((end.toNumber() - start.toNumber()) / ylength);
 94+      } else {
 95+        var years = (eyear - syear) + 1;
 96+        var days = moment.utc([eyear+1]).startOf("year").diff(moment.utc([syear]).startOf("year"), 'days');
 97+        var average = days / years;
 98+        return Math.abs((end.toNumber() - start.toNumber()) / average);
 99+      }
100+  }
101+};
102+
103+
104 // Functions unimplemented.
105 var HOUR;
106 var MINUTE;
107diff --git a/tests/DateFormulasTest.ts b/tests/DateFormulasTest.ts
108index 15c8a05..0a67d6d 100644
109--- a/tests/DateFormulasTest.ts
110+++ b/tests/DateFormulasTest.ts
111@@ -11,7 +11,8 @@ import {
112   MONTH,
113   YEAR,
114   WEEKDAY,
115-  WEEKNUM
116+  WEEKNUM,
117+  YEARFRAC
118 } from "../src/RawFormulas/RawFormulas"
119 import * as ERRORS from "../src/Errors"
120 import {assertEquals} from "./utils/Asserts"
121@@ -33,6 +34,30 @@ function catchAndAssertEquals(toExecute, expected) {
122 }
123 
124 
125+// Test YEARFRAC
126+assertEquals(YEARFRAC("1969-7-6", "1988-7-4", 0), 18.994444444444444);
127+assertEquals(YEARFRAC("1969-7-6", "1988-7-22", 0), 19.044444444444444);
128+assertEquals(YEARFRAC("1992-1-6", "2191-7-22", 0), 199.544444444444444);
129+assertEquals(YEARFRAC("1992-1-6", "2191-1-21", 0), 199.041666666666667);
130+assertEquals(YEARFRAC("1992-1-6", "2144-1-22", 0), 152.044444444444444);
131+assertEquals(YEARFRAC("1992-1-6", "1992-1-6", 0), 0);
132+assertEquals(YEARFRAC("1992-1-6", "1992-1-1", 0), 0.013888888888888888);
133+assertEquals(YEARFRAC("1992-1-6", "1993-1-6", 0), 1);
134+assertEquals(YEARFRAC("1969-7-6", "1988-7-4", 1), 18.99520876112252);
135+assertEquals(YEARFRAC("1969-7-6", "1988-7-22", 1), 19.044490075290895);
136+assertEquals(YEARFRAC("1992-1-6", "2191-7-22", 1), 199.54003477118098);
137+assertEquals(YEARFRAC("1992-1-6", "2191-1-21", 1), 199.04173910662706);
138+assertEquals(YEARFRAC("1992-1-6", "2144-1-22", 1), 152.04174793765546);
139+assertEquals(YEARFRAC("1992-1-6", "1992-1-6", 1), 0);
140+assertEquals(YEARFRAC("1992-1-6", "1992-1-1", 1), 0.01366120218579235);
141+assertEquals(YEARFRAC("1991-1-6", "1992-1-6", 1), 0.999962572);
142+assertEquals(YEARFRAC("1992-1-6", "1993-1-6", 1), 1.000037428);
143+
144+// assertEquals(YEARFRAC("1969-7-6", "1988-7-4", 2), 19.272222222222222);
145+// assertEquals(YEARFRAC("1969-7-6", "1988-7-4", 3), 19.008219178082193);
146+// assertEquals(YEARFRAC("1969-7-6", "1988-7-4", 4), 18.994444444444444);
147+
148+
149 // Test DATEDIF
150 assertEquals(DATEDIF("1992-6-19", "1996-6-19", "Y"), 4);
151 assertEquals(DATEDIF("1992-6-19", "1996-6-0", "Y"), 3);
152diff --git a/tests/FormulasTest.ts b/tests/FormulasTest.ts
153index 620ea18..3cc763d 100644
154--- a/tests/FormulasTest.ts
155+++ b/tests/FormulasTest.ts
156@@ -2008,11 +2008,3 @@ catchAndAssertEquals(function() {
157 catchAndAssertEquals(function() {
158   XOR([]);
159 }, ERRORS.REF_ERROR);
160-
161-
162-
163-// assertEquals(YEARFRAC(DATE(1969, 7, 6), DATE(1988, 7, 4), 0), 18.994444444444444);
164-// // assertEquals(YEARFRAC(DATE(1969, 7, 6), DATE(1988, 7, 4), 1)', 18.99587544); // This is slightly off
165-// assertEquals(YEARFRAC(DATE(1969, 7, 6), DATE(1988, 7, 4), 2), 19.272222222222222);
166-// assertEquals(YEARFRAC(DATE(1969, 7, 6), DATE(1988, 7, 4), 3), 19.008219178082193);
167-// assertEquals(YEARFRAC(DATE(1969, 7, 6), DATE(1988, 7, 4), 4), 18.994444444444444);
168\ No newline at end of file