spreadsheet
typeScript/javascript spreadsheet parser, with formulas.
git clone https://git.vogt.world/spreadsheet.git
Log | Files | README.md
← Commit log
commit
message
[CONVERT, etc] formula added and tested. formulajs no longer needed
author
Ben Vogt <[email protected]>
date
2017-04-29 22:59:57
stats
5 file(s) changed, 321 insertions(+), 11 deletions(-)
files
package.json
src/RawFormulas/Financial.ts
src/RawFormulas/RawFormulas.ts
src/RawFormulas/Text.ts
tests/TextTest.ts
  1diff --git a/package.json b/package.json
  2index 5fdc4a4..2e1e34c 100644
  3--- a/package.json
  4+++ b/package.json
  5@@ -9,7 +9,6 @@
  6   "author": "vogtb <bvogt at gmail.com>",
  7   "license": "MIT",
  8   "dependencies": {
  9-    "formulajs": "^1.0.8",
 10     "moment": "^2.17.1"
 11   }
 12 }
 13diff --git a/src/RawFormulas/Financial.ts b/src/RawFormulas/Financial.ts
 14index 2823234..4e1f5c2 100644
 15--- a/src/RawFormulas/Financial.ts
 16+++ b/src/RawFormulas/Financial.ts
 17@@ -374,6 +374,8 @@ var CUMIPMT = function (...values) : number {
 18  * * https://support.office.com/en-us/article/ACCRINT-function-fe45d089-6722-4fb3-9379-e1f911d8dc74
 19  *
 20  * * https://quant.stackexchange.com/questions/7040/whats-the-algorithm-behind-excels-accrint
 21+ *
 22+ * * https://support.google.com/docs/answer/3093200
 23  * @param values[0] issue - The date the security was initially issued.
 24  * @param values[1] first_payment - The first date interest will be paid.
 25  * @param values[2] settlement - The settlement date of the security, the date after issuance when the security is
 26diff --git a/src/RawFormulas/RawFormulas.ts b/src/RawFormulas/RawFormulas.ts
 27index 935efce..5cc850d 100644
 28--- a/src/RawFormulas/RawFormulas.ts
 29+++ b/src/RawFormulas/RawFormulas.ts
 30@@ -1,4 +1,3 @@
 31-import * as Formula from "formulajs"
 32 import {
 33   ABS,
 34   ACOS,
 35@@ -107,7 +106,8 @@ import {
 36   CHAR,
 37   CODE,
 38   SPLIT,
 39-  CONCATENATE
 40+  CONCATENATE,
 41+  CONVERT
 42 } from "./Text"
 43 import {
 44   DATE,
 45@@ -136,12 +136,11 @@ import {
 46   WORKDAY$INTL
 47 } from "./Date"
 48 
 49-var CONVERT = Formula["CONVERT"];
 50-
 51-
 52 // Using alias to bind dot-notation function names.
 53 var __COMPLEX = {
 54-  "F.DIST": FDIST$LEFTTAILED
 55+  "F.DIST": FDIST$LEFTTAILED,
 56+  "NETWORKDAYS.INTL": NETWORKDAYS$INTL,
 57+  "WORKDAY.INTL": WORKDAY$INTL
 58 };
 59 
 60 export {
 61diff --git a/src/RawFormulas/Text.ts b/src/RawFormulas/Text.ts
 62index 999ffe4..dec14b8 100644
 63--- a/src/RawFormulas/Text.ts
 64+++ b/src/RawFormulas/Text.ts
 65@@ -3,7 +3,10 @@ import {
 66   TypeCaster
 67 } from "./Utils";
 68 import {
 69-  ValueError, NumError, RefError
 70+  ValueError,
 71+  NumError,
 72+  RefError,
 73+  NAError
 74 } from "../Errors";
 75 
 76 /**
 77@@ -124,10 +127,310 @@ var CONCATENATE = function (...values) : string {
 78   return string;
 79 };
 80 
 81+/**
 82+ * Converts a numeric value to a different unit of measure.
 83+ * @param values[0] value - the numeric value in start_unit to convert to end_unit.
 84+ * @param values[1] start_unit - The starting unit, the unit currently assigned to value.
 85+ * @param values[2] end_unit - The unit of measure into which to convert value.
 86+ * @returns {number}
 87+ * @constructor
 88+ * TODO: Looking up units is not efficient at all. We should use an object instead of iterating through an array.
 89+ */
 90+var CONVERT = function (...values) {
 91+  ArgsChecker.checkLength(values, 3);
 92+  var n = TypeCaster.firstValueAsNumber(values[0]);
 93+  var fromUnit = TypeCaster.firstValueAsString(values[1]);
 94+  var toUnit = TypeCaster.firstValueAsString(values[2]);
 95+
 96+  // NOTE: A lot of the code for this method is from https://github.com/sutoiku/formula.js. I'm relying on them to have
 97+  // gotten it right, but I'm spot checking some of their work against GS, MSE, LibreOffice, OpenOffice.
 98+
 99+  // List of units supported by CONVERT and units defined by the International System of Units
100+  // [Name, Symbol, Alternate symbols, Quantity, ISU, CONVERT, Conversion ratio]
101+  var units = [
102+    ["a.u. of action", "?", null, "action", false, false, 1.05457168181818e-34],
103+    ["a.u. of charge", "e", null, "electric_charge", false, false, 1.60217653141414e-19],
104+    ["a.u. of energy", "Eh", null, "energy", false, false, 4.35974417757576e-18],
105+    ["a.u. of length", "a?", null, "length", false, false, 5.29177210818182e-11],
106+    ["a.u. of mass", "m?", null, "mass", false, false, 9.10938261616162e-31],
107+    ["a.u. of time", "?/Eh", null, "time", false, false, 2.41888432650516e-17],
108+    ["admiralty knot", "admkn", null, "speed", false, true, 0.514773333],
109+    ["ampere", "A", null, "electric_current", true, false, 1],
110+    ["ampere per meter", "A/m", null, "magnetic_field_intensity", true, false, 1],
111+    ["ångström", "Å", ["ang"], "length", false, true, 1e-10],
112+    ["are", "ar", null, "area", false, true, 100],
113+    ["astronomical unit", "ua", null, "length", false, false, 1.49597870691667e-11],
114+    ["bar", "bar", null, "pressure", false, false, 100000],
115+    ["barn", "b", null, "area", false, false, 1e-28],
116+    ["becquerel", "Bq", null, "radioactivity", true, false, 1],
117+    ["bit", "bit", ["b"], "information", false, true, 1],
118+    ["btu", "BTU", ["btu"], "energy", false, true, 1055.05585262],
119+    ["byte", "byte", null, "information", false, true, 8],
120+    ["candela", "cd", null, "luminous_intensity", true, false, 1],
121+    ["candela per square metre", "cd/m?", null, "luminance", true, false, 1],
122+    ["coulomb", "C", null, "electric_charge", true, false, 1],
123+    ["cubic ångström", "ang3", ["ang^3"], "volume", false, true, 1e-30],
124+    ["cubic foot", "ft3", ["ft^3"], "volume", false, true, 0.028316846592],
125+    ["cubic inch", "in3", ["in^3"], "volume", false, true, 0.000016387064],
126+    ["cubic light-year", "ly3", ["ly^3"], "volume", false, true, 8.46786664623715e-47],
127+    ["cubic metre", "m?", null, "volume", true, true, 1],
128+    ["cubic mile", "mi3", ["mi^3"], "volume", false, true, 4168181825.44058],
129+    ["cubic nautical mile", "Nmi3", ["Nmi^3"], "volume", false, true, 6352182208],
130+    ["cubic Pica", "Pica3", ["Picapt3", "Pica^3", "Picapt^3"], "volume", false, true, 7.58660370370369e-8],
131+    ["cubic yard", "yd3", ["yd^3"], "volume", false, true, 0.764554857984],
132+    ["cup", "cup", null, "volume", false, true, 0.0002365882365],
133+    ["dalton", "Da", ["u"], "mass", false, false, 1.66053886282828e-27],
134+    ["day", "d", ["day"], "time", false, true, 86400],
135+    ["degree", "°", null, "angle", false, false, 0.0174532925199433],
136+    ["degrees Rankine", "Rank", null, "temperature", false, true, 0.555555555555556],
137+    ["dyne", "dyn", ["dy"], "force", false, true, 0.00001],
138+    ["electronvolt", "eV", ["ev"], "energy", false, true, 1.60217656514141],
139+    ["ell", "ell", null, "length", false, true, 1.143],
140+    ["erg", "erg", ["e"], "energy", false, true, 1e-7],
141+    ["farad", "F", null, "electric_capacitance", true, false, 1],
142+    ["fluid ounce", "oz", null, "volume", false, true, 0.0000295735295625],
143+    ["foot", "ft", null, "length", false, true, 0.3048],
144+    ["foot-pound", "flb", null, "energy", false, true, 1.3558179483314],
145+    ["gal", "Gal", null, "acceleration", false, false, 0.01],
146+    ["gallon", "gal", null, "volume", false, true, 0.003785411784],
147+    ["gauss", "G", ["ga"], "magnetic_flux_density", false, true, 1],
148+    ["grain", "grain", null, "mass", false, true, 0.0000647989],
149+    ["gram", "g", null, "mass", false, true, 0.001],
150+    ["gray", "Gy", null, "absorbed_dose", true, false, 1],
151+    ["gross registered ton", "GRT", ["regton"], "volume", false, true, 2.8316846592],
152+    ["hectare", "ha", null, "area", false, true, 10000],
153+    ["henry", "H", null, "inductance", true, false, 1],
154+    ["hertz", "Hz", null, "frequency", true, false, 1],
155+    ["horsepower", "HP", ["h"], "power", false, true, 745.69987158227],
156+    ["horsepower-hour", "HPh", ["hh", "hph"], "energy", false, true, 2684519.538],
157+    ["hour", "h", ["hr"], "time", false, true, 3600],
158+    ["imperial gallon (U.K.)", "uk_gal", null, "volume", false, true, 0.00454609],
159+    ["imperial hundredweight", "lcwt", ["uk_cwt", "hweight"], "mass", false, true, 50.802345],
160+    ["imperial quart (U.K)", "uk_qt", null, "volume", false, true, 0.0011365225],
161+    ["imperial ton", "brton", ["uk_ton", "LTON"], "mass", false, true, 1016.046909],
162+    ["inch", "in", null, "length", false, true, 0.0254],
163+    ["international acre", "uk_acre", null, "area", false, true, 4046.8564224],
164+    ["IT calorie", "cal", null, "energy", false, true, 4.1868],
165+    ["joule", "J", null, "energy", true, true, 1],
166+    ["katal", "kat", null, "catalytic_activity", true, false, 1],
167+    ["kelvin", "K", ["kel"], "temperature", true, true, 1],
168+    ["kilogram", "kg", null, "mass", true, true, 1],
169+    ["knot", "kn", null, "speed", false, true, 0.514444444444444],
170+    ["light-year", "ly", null, "length", false, true, 9460730472580800],
171+    ["litre", "L", ["l", "lt"], "volume", false, true, 0.001],
172+    ["lumen", "lm", null, "luminous_flux", true, false, 1],
173+    ["lux", "lx", null, "illuminance", true, false, 1],
174+    ["maxwell", "Mx", null, "magnetic_flux", false, false, 1e-18],
175+    ["measurement ton", "MTON", null, "volume", false, true, 1.13267386368],
176+    ["meter per hour", "m/h", ["m/hr"], "speed", false, true, 0.00027777777777778],
177+    ["meter per second", "m/s", ["m/sec"], "speed", true, true, 1],
178+    ["meter per second squared", "m?s??", null, "acceleration", true, false, 1],
179+    ["parsec", "pc", ["parsec"], "length", false, true, 30856775814671900],
180+    ["meter squared per second", "m?/s", null, "kinematic_viscosity", true, false, 1],
181+    ["metre", "m", null, "length", true, true, 1],
182+    ["miles per hour", "mph", null, "speed", false, true, 0.44704],
183+    ["millimetre of mercury", "mmHg", null, "pressure", false, false, 133.322],
184+    ["minute", "?", null, "angle", false, false, 0.000290888208665722],
185+    ["minute", "min", ["mn"], "time", false, true, 60],
186+    ["modern teaspoon", "tspm", null, "volume", false, true, 0.000005],
187+    ["mole", "mol", null, "amount_of_substance", true, false, 1],
188+    ["morgen", "Morgen", null, "area", false, true, 2500],
189+    ["n.u. of action", "?", null, "action", false, false, 1.05457168181818e-34],
190+    ["n.u. of mass", "m?", null, "mass", false, false, 9.10938261616162e-31],
191+    ["n.u. of speed", "c?", null, "speed", false, false, 299792458],
192+    ["n.u. of time", "?/(me?c??)", null, "time", false, false, 1.28808866778687e-21],
193+    ["nautical mile", "M", ["Nmi"], "length", false, true, 1852],
194+    ["newton", "N", null, "force", true, true, 1],
195+    ["œrsted", "Oe ", null, "magnetic_field_intensity", false, false, 79.5774715459477],
196+    ["ohm", "Ω", null, "electric_resistance", true, false, 1],
197+    ["ounce mass", "ozm", null, "mass", false, true, 0.028349523125],
198+    ["pascal", "Pa", null, "pressure", true, false, 1],
199+    ["pascal second", "Pa?s", null, "dynamic_viscosity", true, false, 1],
200+    ["pferdestärke", "PS", null, "power", false, true, 735.49875],
201+    ["phot", "ph", null, "illuminance", false, false, 0.0001],
202+    ["pica (1/6 inch)", "pica", null, "length", false, true, 0.00035277777777778],
203+    ["pica (1/72 inch)", "Pica", ["Picapt"], "length", false, true, 0.00423333333333333],
204+    ["poise", "P", null, "dynamic_viscosity", false, false, 0.1],
205+    ["pond", "pond", null, "force", false, true, 0.00980665],
206+    ["pound force", "lbf", null, "force", false, true, 4.4482216152605],
207+    ["pound mass", "lbm", null, "mass", false, true, 0.45359237],
208+    ["quart", "qt", null, "volume", false, true, 0.000946352946],
209+    ["radian", "rad", null, "angle", true, false, 1],
210+    ["second", "?", null, "angle", false, false, 0.00000484813681109536],
211+    ["second", "s", ["sec"], "time", true, true, 1],
212+    ["short hundredweight", "cwt", ["shweight"], "mass", false, true, 45.359237],
213+    ["siemens", "S", null, "electrical_conductance", true, false, 1],
214+    ["sievert", "Sv", null, "equivalent_dose", true, false, 1],
215+    ["slug", "sg", null, "mass", false, true, 14.59390294],
216+    ["square ångström", "ang2", ["ang^2"], "area", false, true, 1e-20],
217+    ["square foot", "ft2", ["ft^2"], "area", false, true, 0.09290304],
218+    ["square inch", "in2", ["in^2"], "area", false, true, 0.00064516],
219+    ["square light-year", "ly2", ["ly^2"], "area", false, true, 8.95054210748189e+31],
220+    ["square meter", "m?", null, "area", true, true, 1],
221+    ["square meter", "m^2", null, "area", true, true, 1], // Added by @vogtb.
222+    ["square mile", "mi2", ["mi^2"], "area", false, true, 2589988.110336],
223+    ["square nautical mile", "Nmi2", ["Nmi^2"], "area", false, true, 3429904],
224+    ["square Pica", "Pica2", ["Picapt2", "Pica^2", "Picapt^2"], "area", false, true, 0.00001792111111111],
225+    ["square yard", "yd2", ["yd^2"], "area", false, true, 0.83612736],
226+    ["statute mile", "mi", null, "length", false, true, 1609.344],
227+    ["steradian", "sr", null, "solid_angle", true, false, 1],
228+    ["stilb", "sb", null, "luminance", false, false, 0.0001],
229+    ["stokes", "St", null, "kinematic_viscosity", false, false, 0.0001],
230+    ["stone", "stone", null, "mass", false, true, 6.35029318],
231+    ["tablespoon", "tbs", null, "volume", false, true, 0.0000147868],
232+    ["teaspoon", "tsp", null, "volume", false, true, 0.00000492892],
233+    ["tesla", "T", null, "magnetic_flux_density", true, true, 1],
234+    ["thermodynamic calorie", "c", null, "energy", false, true, 4.184],
235+    ["ton", "ton", null, "mass", false, true, 907.18474],
236+    ["tonne", "t", null, "mass", false, false, 1000],
237+    ["U.K. pint", "uk_pt", null, "volume", false, true, 0.00056826125],
238+    ["U.S. bushel", "bushel", null, "volume", false, true, 0.03523907],
239+    ["U.S. oil barrel", "barrel", null, "volume", false, true, 0.158987295],
240+    ["U.S. pint", "pt", ["us_pt"], "volume", false, true, 0.000473176473],
241+    ["U.S. survey mile", "survey_mi", null, "length", false, true, 1609.347219],
242+    ["U.S. survey/statute acre", "us_acre", null, "area", false, true, 4046.87261],
243+    ["volt", "V", null, "voltage", true, false, 1],
244+    ["watt", "W", null, "power", true, true, 1],
245+    ["watt-hour", "Wh", ["wh"], "energy", false, true, 3600],
246+    ["weber", "Wb", null, "magnetic_flux", true, false, 1],
247+    ["yard", "yd", null, "length", false, true, 0.9144],
248+    ["year", "yr", null, "time", false, true, 31557600]
249+  ];
250+
251+  // Binary prefixes
252+  // [Name, Prefix power of 2 value, Previx value, Abbreviation, Derived from]
253+  var binary_prefixes = {
254+    Yi: ["yobi", 80, 1208925819614629174706176, "Yi", "yotta"],
255+    Zi: ["zebi", 70, 1180591620717411303424, "Zi", "zetta"],
256+    Ei: ["exbi", 60, 1152921504606846976, "Ei", "exa"],
257+    Pi: ["pebi", 50, 1125899906842624, "Pi", "peta"],
258+    Ti: ["tebi", 40, 1099511627776, "Ti", "tera"],
259+    Gi: ["gibi", 30, 1073741824, "Gi", "giga"],
260+    Mi: ["mebi", 20, 1048576, "Mi", "mega"],
261+    ki: ["kibi", 10, 1024, "ki", "kilo"]
262+  };
263+
264+  // Unit prefixes
265+  // [Name, Multiplier, Abbreviation]
266+  var unit_prefixes = {
267+    Y: ["yotta", 1e+24, "Y"],
268+    Z: ["zetta", 1e+21, "Z"],
269+    E: ["exa", 1e+18, "E"],
270+    P: ["peta", 1e+15, "P"],
271+    T: ["tera", 1e+12, "T"],
272+    G: ["giga", 1e+09, "G"],
273+    M: ["mega", 1e+06, "M"],
274+    k: ["kilo", 1e+03, "k"],
275+    h: ["hecto", 1e+02, "h"],
276+    e: ["dekao", 1e+01, "e"],
277+    d: ["deci", 1e-01, "d"],
278+    c: ["centi", 1e-02, "c"],
279+    m: ["milli", 1e-03, "m"],
280+    u: ["micro", 1e-06, "u"],
281+    n: ["nano", 1e-09, "n"],
282+    p: ["pico", 1e-12, "p"],
283+    f: ["femto", 1e-15, "f"],
284+    a: ["atto", 1e-18, "a"],
285+    z: ["zepto", 1e-21, "z"],
286+    y: ["yocto", 1e-24, "y"]
287+  };
288+
289+  // Initialize units and multipliers
290+  var from = null;
291+  var to = null;
292+  var base_from_unit = fromUnit;
293+  var base_to_unit = toUnit;
294+  var from_multiplier = 1;
295+  var to_multiplier = 1;
296+  var alt;
297+
298+  // Lookup from and to units
299+  for (var i = 0; i < units.length; i++) {
300+    alt = (units[i][2] === null) ? [] : units[i][2];
301+    if (units[i][1] === base_from_unit || alt.indexOf(base_from_unit) >= 0) {
302+      from = units[i];
303+    }
304+    if (units[i][1] === base_to_unit || alt.indexOf(base_to_unit) >= 0) {
305+      to = units[i];
306+    }
307+  }
308+
309+  // Lookup from prefix
310+  if (from === null) {
311+    var from_binary_prefix = binary_prefixes[fromUnit.substring(0, 2)];
312+    var from_unit_prefix = unit_prefixes[fromUnit.substring(0, 1)];
313+
314+    // Handle dekao unit prefix (only unit prefix with two characters)
315+    if (fromUnit.substring(0, 2) === 'da') {
316+      from_unit_prefix = ["dekao", 1e+01, "da"];
317+    }
318+
319+    // Handle binary prefixes first (so that 'Yi' is processed before 'Y')
320+    if (from_binary_prefix) {
321+      from_multiplier = from_binary_prefix[2];
322+      base_from_unit = fromUnit.substring(2);
323+    } else if (from_unit_prefix) {
324+      from_multiplier = from_unit_prefix[1];
325+      base_from_unit = fromUnit.substring(from_unit_prefix[2].length);
326+    }
327+
328+    // Lookup from unit
329+    for (var j = 0; j < units.length; j++) {
330+      alt = (units[j][2] === null) ? [] : units[j][2];
331+      if (units[j][1] === base_from_unit || alt.indexOf(base_from_unit) >= 0) {
332+        from = units[j];
333+      }
334+    }
335+  }
336+
337+  // Lookup to prefix
338+  if (to === null) {
339+    var to_binary_prefix = binary_prefixes[toUnit.substring(0, 2)];
340+    var to_unit_prefix = unit_prefixes[toUnit.substring(0, 1)];
341+
342+    // Handle dekao unit prefix (only unit prefix with two characters)
343+    if (toUnit.substring(0, 2) === 'da') {
344+      to_unit_prefix = ["dekao", 1e+01, "da"];
345+    }
346+
347+    // Handle binary prefixes first (so that 'Yi' is processed before 'Y')
348+    if (to_binary_prefix) {
349+      to_multiplier = to_binary_prefix[2];
350+      base_to_unit = toUnit.substring(2);
351+    } else if (to_unit_prefix) {
352+      to_multiplier = to_unit_prefix[1];
353+      base_to_unit = toUnit.substring(to_unit_prefix[2].length);
354+    }
355+
356+    // Lookup to unit
357+    for (var k = 0; k < units.length; k++) {
358+      alt = (units[k][2] === null) ? [] : units[k][2];
359+      if (units[k][1] === base_to_unit || alt.indexOf(base_to_unit) >= 0) {
360+        to = units[k];
361+      }
362+    }
363+  }
364+
365+  // Return error if a unit does not exist
366+  if (from === null || to === null) {
367+    console.log(from, to);
368+    throw new NAError("Invalid units for conversion.");
369+  }
370+
371+  // Return error if units represent different quantities
372+  if (from[3] !== to[3]) {
373+    throw new NAError("Invalid units for conversion.");
374+  }
375+
376+  // Return converted number
377+  return n * from[6] * from_multiplier / (to[6] * to_multiplier);
378+};
379+
380 export {
381   ARABIC,
382   CHAR,
383   CODE,
384   SPLIT,
385-  CONCATENATE
386+  CONCATENATE,
387+  CONVERT
388 }
389\ No newline at end of file
390diff --git a/tests/TextTest.ts b/tests/TextTest.ts
391index 23ff056..4e2c469 100644
392--- a/tests/TextTest.ts
393+++ b/tests/TextTest.ts
394@@ -94,6 +94,9 @@ catchAndAssertEquals(function() {
395 
396 // Test CONVERT
397 assertEquals(CONVERT(5.1, "mm", "m"), 0.0050999999999999995);
398+assertEquals(CONVERT(5.1, "mm", "km"), 0.0000050999999999999995);
399+assertEquals(CONVERT(5.1, "g", "kg"), 0.0050999999999999995);
400+assertEquals(CONVERT(35.7, "in^2", "m^2"), 0.023032212);
401 
402 
403 // Test ARABIC