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