diff --git a/CHANGELOG.md b/CHANGELOG.md index b68c77419b..4aa18aec8b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [Unreleased] +### Added + +- Added new functions: ACCRINTM, DISC, INTRATE, PRICEDISC, RECEIVED, YIELDDISC. [#1799](https://github.com/handsontable/hyperformula/pull/1799) +- Added new functions: COUPDAYBS, COUPDAYS, COUPDAYSNC, COUPNCD, COUPNUM, COUPPCD. [#1804](https://github.com/handsontable/hyperformula/pull/1804) + ## [3.5.0] - 2026-10-09 ### Added diff --git a/docs/guide/list-of-differences.md b/docs/guide/list-of-differences.md index a109f82616..05bc580620 100644 --- a/docs/guide/list-of-differences.md +++ b/docs/guide/list-of-differences.md @@ -77,6 +77,7 @@ To remove the differences, create [custom implementations](custom-functions.md) | LCMP | =LCM(1000000, 1000001, 1000002, 1000003) | NUM | 5.00003E+23 | NUM | | TBILLPRICE | =TBILLPRICE(0, 180, 1.9) | 5.0000 | NUM | 5.0000 | | TBILLPRICE | =TBILLPRICE(0, 180, 2) | 0.0000 | NUM | 0.0000 | +| ACCRINTM | =ACCRINTM(DATE(2008,4,1), DATE(2008,6,15), 0.1) | 20.5556 | 20.5556 | Wrong number | | NPV | =NPV(1, TRUE(), 1) | 0.7500 | 0.5000 | 0.7500 | | NPV | =NPV(1,B1) where B1 = true | 0.5000 | 0.0000 | 0.0000 | | POISSON.DIST | =POISSON.DIST(-0.01, 0, FALSE()) | NUM | 1.0000 | NUM | diff --git a/src/DateTimeHelper.ts b/src/DateTimeHelper.ts index aab02290c5..d2de40838e 100644 --- a/src/DateTimeHelper.ts +++ b/src/DateTimeHelper.ts @@ -211,6 +211,43 @@ export class DateTimeHelper { } } + /** + * Counts the days between two dates under a day-count basis, and returns the number of days in a year under the + * same basis. YEARFRAC and the securities functions (DISC, INTRATE, PRICEDISC, RECEIVED, YIELDDISC, ACCRINTM) + * compute their year fraction as `dayCount / yearDays`. + * + * | `basis` | `dayCount` | `yearDays` | + * |---------|----------------------------------------------|-----------------------------| + * | 0 | US (NASD) 30/360, as adjusted by `toBasisUS` | 360 | + * | 1 | actual days | `yearLengthForBasis` | + * | 2 | actual days | 360 | + * | 3 | actual days | 365 | + * | 4 | European 30/360, as adjusted by `toBasisEU` | 360 | + * + * @param {number} startDate - date serial number, an integer not greater than `endDate` + * @param {number} endDate - date serial number, an integer + * @param {number} basis - day-count basis, an integer from 0 to 4 + */ + public dayCountByBasis(startDate: number, endDate: number, basis: number): { dayCount: number, yearDays: number } { + const start = this.numberToSimpleDate(startDate) + const end = this.numberToSimpleDate(endDate) + switch (basis) { + case 0: { + const [adjustedStart, adjustedEnd] = this.toBasisUS({...start}, {...end}) + return {dayCount: daysIn30DayMonths(adjustedStart, adjustedEnd), yearDays: 360} + } + case 1: + return {dayCount: endDate - startDate, yearDays: this.yearLengthForBasis(start, end)} + case 2: + return {dayCount: endDate - startDate, yearDays: 360} + case 3: + return {dayCount: endDate - startDate, yearDays: 365} + case 4: + return {dayCount: daysIn30DayMonths(toBasisEU(start), toBasisEU(end)), yearDays: 360} + } + throw new Error('Should not be reachable.') + } + private parseSingleFormat(dateString: string, dateFormat?: string, timeFormat?: string): Maybe { const dateTime = this.parseDateTime(dateString, dateFormat, timeFormat) if (instanceOfSimpleDate(dateTime)) { @@ -319,3 +356,11 @@ export function timeToNumber(time: SimpleTime): number { export function toBasisEU(date: SimpleDate): SimpleDate { return {year: date.year, month: date.month, day: Math.min(30, date.day)} } + +/** + * Counts the days between two dates as if every month had 30 days. The dates must already be adjusted to a 30/360 + * basis (`toBasisUS` or `toBasisEU`). + */ +function daysIn30DayMonths(start: SimpleDate, end: SimpleDate): number { + return 360 * (end.year - start.year) + 30 * (end.month - start.month) + end.day - start.day +} diff --git a/src/error-message.ts b/src/error-message.ts index d42040901c..82671202ed 100644 --- a/src/error-message.ts +++ b/src/error-message.ts @@ -26,6 +26,7 @@ export class ErrorMessage { public static IntegerExpected = 'Value needs to be an integer.' public static BadMode = 'Mode not recognized.' public static DateBounds = 'Date outside of bounds.' + public static CouponFrequency = 'Frequency must be 1, 2 or 4.' public static OutOfSheet = 'Resulting reference is out of the sheet.' public static WrongType = 'Wrong type of argument.' public static NaN = 'NaN or infinite value encountered.' diff --git a/src/i18n/languages/csCZ.ts b/src/i18n/languages/csCZ.ts index fea286083c..437f4a5c3f 100644 --- a/src/i18n/languages/csCZ.ts +++ b/src/i18n/languages/csCZ.ts @@ -25,6 +25,7 @@ const dictionary: RawTranslationPackage = { 'ARRAY_CONSTRAIN': 'ARRAY_CONSTRAIN', ARRAYFORMULA: 'ARRAYFORMULA', ABS: 'ABS', + ACCRINTM: 'ACCRINTM', ACOS: 'ARCCOS', ACOSH: 'ARCCOSH', ACOT: 'ACOT', @@ -66,6 +67,12 @@ const dictionary: RawTranslationPackage = { COUNTIF: 'COUNTIF', COUNTIFS: 'COUNTIFS', COUNTUNIQUE: 'COUNTUNIQUE', + COUPDAYBS: 'COUPDAYBS', + COUPDAYS: 'COUPDAYS', + COUPDAYSNC: 'COUPDAYSNC', + COUPNCD: 'COUPNCD', + COUPNUM: 'COUPNUM', + COUPPCD: 'COUPPCD', CSC: 'CSC', CSCH: 'CSCH', CUMIPMT: 'CUMIPMT', @@ -96,6 +103,7 @@ const dictionary: RawTranslationPackage = { DECIMAL: 'DECIMAL', DEGREES: 'DEGREES', DELTA: 'DELTA', + DISC: 'DISC', DOLLARDE: 'DOLLARDE', DOLLARFR: 'DOLLARFR', EDATE: 'EDATE', @@ -124,6 +132,7 @@ const dictionary: RawTranslationPackage = { INDEX: 'INDEX', INT: 'CELÁ.ČÁST', INTERVAL: 'INTERVAL', //FIXME + INTRATE: 'INTRATE', IPMT: 'PLATBA.ÚROK', IRR: 'MÍRA.VÝNOSNOSTI', ISBINARY: 'ISBINARY', @@ -184,11 +193,13 @@ const dictionary: RawTranslationPackage = { PRODUCT: 'SOUČIN', POWER: 'POWER', PPMT: 'PLATBA.ZÁKLAD', + PRICEDISC: 'PRICEDISC', PROPER: 'VELKÁ2', PV: 'SOUČHODNOTA', RADIANS: 'RADIANS', RAND: 'NÁHČÍSLO', RATE: 'ÚROKOVÁ.MÍRA', + RECEIVED: 'RECEIVED', REPLACE: 'NAHRADIT', REPT: 'OPAKOVAT', RIGHT: 'ZPRAVA', @@ -259,6 +270,7 @@ const dictionary: RawTranslationPackage = { XOR: 'XOR', YEAR: 'ROK', YEARFRAC: 'YEARFRAC', + YIELDDISC: 'YIELDDISC', ROMAN: 'ROMAN', ARABIC: 'ARABIC', 'HF.ADD': 'HF.ADD', diff --git a/src/i18n/languages/daDK.ts b/src/i18n/languages/daDK.ts index 9264551249..e96d2b6582 100644 --- a/src/i18n/languages/daDK.ts +++ b/src/i18n/languages/daDK.ts @@ -25,6 +25,7 @@ const dictionary: RawTranslationPackage = { 'ARRAY_CONSTRAIN': 'ARRAY_CONSTRAIN', ARRAYFORMULA: 'ARRAYFORMULA', ABS: 'ABS', + ACCRINTM: 'PÅLØBRENTE.UDLØB', ACOS: 'ARCCOS', ACOSH: 'ARCCOSH', ACOT: 'ARCCOT', @@ -66,6 +67,12 @@ const dictionary: RawTranslationPackage = { COUNTIF: 'TÆL.HVIS', COUNTIFS: 'TÆL.HVISER', COUNTUNIQUE: 'COUNTUNIQUE', + COUPDAYBS: 'KUPONDAGE.SA', + COUPDAYS: 'KUPONDAGE.A', + COUPDAYSNC: 'KUPONDAGE.ANK', + COUPNCD: 'KUPONDAG.NÆSTE', + COUPNUM: 'KUPONBETALINGER', + COUPPCD: 'KUPONDAG.FORRIGE', CSC: 'CSC', CSCH: 'CSCH', CUMIPMT: 'AKKUM.RENTE', @@ -96,6 +103,7 @@ const dictionary: RawTranslationPackage = { DECIMAL: 'DECIMAL', DEGREES: 'GRADER', DELTA: 'DELTA', + DISC: 'DISKONTO', DOLLARDE: 'KR.DECIMAL', DOLLARFR: 'KR.BRØK', EDATE: 'EDATO', @@ -124,6 +132,7 @@ const dictionary: RawTranslationPackage = { INDEX: 'INDEKS', INT: 'HELTAL', INTERVAL: 'INTERVAL', //FIXME + INTRATE: 'RENTEFOD', IPMT: 'R.YDELSE', IRR: 'IA', ISBINARY: 'ISBINARY', @@ -184,11 +193,13 @@ const dictionary: RawTranslationPackage = { PRODUCT: 'PRODUKT', POWER: 'POTENS', PPMT: 'H.YDELSE', + PRICEDISC: 'KURS.DISKONTO', PROPER: 'STORT.FORBOGSTAV', PV: 'NV', RADIANS: 'RADIANER', RAND: 'SLUMP', RATE: 'RENTE', + RECEIVED: 'MODTAGET.VED.UDLØB', REPLACE: 'ERSTAT', REPT: 'GENTAG', RIGHT: 'HØJRE', @@ -259,6 +270,7 @@ const dictionary: RawTranslationPackage = { XOR: 'XELLER', YEAR: 'ÅR', YEARFRAC: 'ÅR.BRØK', + YIELDDISC: 'AFKAST.DISKONTO', ROMAN: 'ROMERTAL', ARABIC: 'ARABISK', 'HF.ADD': 'HF.ADD', diff --git a/src/i18n/languages/deDE.ts b/src/i18n/languages/deDE.ts index 4d3d39319b..281eab8aef 100644 --- a/src/i18n/languages/deDE.ts +++ b/src/i18n/languages/deDE.ts @@ -25,6 +25,7 @@ const dictionary: RawTranslationPackage = { 'ARRAY_CONSTRAIN': 'ARRAY_CONSTRAIN', ARRAYFORMULA: 'ARRAYFORMULA', ABS: 'ABS', + ACCRINTM: 'AUFGELZINSF', ACOS: 'ARCCOS', ACOSH: 'ARCCOSHYP', ACOT: 'ARCCOTAN', @@ -66,6 +67,12 @@ const dictionary: RawTranslationPackage = { COUNTIF: 'ZÄHLENWENN', COUNTIFS: 'ZÄHLENWENNS', COUNTUNIQUE: 'COUNTUNIQUE', + COUPDAYBS: 'ZINSTERMTAGVA', + COUPDAYS: 'ZINSTERMTAGE', + COUPDAYSNC: 'ZINSTERMTAGNZ', + COUPNCD: 'ZINSTERMNZ', + COUPNUM: 'ZINSTERMZAHL', + COUPPCD: 'ZINSTERMVZ', CSC: 'COSEC', CSCH: 'COSECHYP', CUMIPMT: 'KUMZINSZ', @@ -96,6 +103,7 @@ const dictionary: RawTranslationPackage = { DECIMAL: 'DEZIMAL', DEGREES: 'GRAD', DELTA: 'DELTA', + DISC: 'DISAGIO', DOLLARDE: 'NOTIERUNGDEZ', DOLLARFR: 'NOTIERUNGBRU', EDATE: 'EDATUM', @@ -124,6 +132,7 @@ const dictionary: RawTranslationPackage = { INDEX: 'INDEX', INT: 'GANZZAHL', INTERVAL: 'INTERVAL', //FIXME + INTRATE: 'ZINSSATZ', IPMT: 'ZINSZ', IRR: 'IKV', ISBINARY: 'ISBINARY', @@ -184,11 +193,13 @@ const dictionary: RawTranslationPackage = { PRODUCT: 'PRODUKT', POWER: 'POTENZ', PPMT: 'KAPZ', + PRICEDISC: 'KURSDISAGIO', PROPER: 'GROSS2', PV: 'BW', RADIANS: 'BOGENMASS', RAND: 'ZUFALLSZAHL', RATE: 'ZINS', + RECEIVED: 'AUSZAHLUNG', REPLACE: 'ERSETZEN', REPT: 'WIEDERHOLEN', RIGHT: 'RECHTS', @@ -259,6 +270,7 @@ const dictionary: RawTranslationPackage = { XOR: 'XODER', YEAR: 'JAHR', YEARFRAC: 'BRTEILJAHRE', + YIELDDISC: 'RENDITEDIS', ROMAN: 'RÖMISCH', ARABIC: 'ARABISCH', 'HF.ADD': 'HF.ADD', diff --git a/src/i18n/languages/enGB.ts b/src/i18n/languages/enGB.ts index d271b732cd..a10a2ae8f8 100644 --- a/src/i18n/languages/enGB.ts +++ b/src/i18n/languages/enGB.ts @@ -25,6 +25,7 @@ const dictionary: RawTranslationPackage = { 'ARRAY_CONSTRAIN': 'ARRAY_CONSTRAIN', ARRAYFORMULA: 'ARRAYFORMULA', ABS: 'ABS', + ACCRINTM: 'ACCRINTM', ACOS: 'ACOS', ACOSH: 'ACOSH', ACOT: 'ACOT', @@ -67,6 +68,12 @@ const dictionary: RawTranslationPackage = { COUNTIF: 'COUNTIF', COUNTIFS: 'COUNTIFS', COUNTUNIQUE: 'COUNTUNIQUE', + COUPDAYBS: 'COUPDAYBS', + COUPDAYS: 'COUPDAYS', + COUPDAYSNC: 'COUPDAYSNC', + COUPNCD: 'COUPNCD', + COUPNUM: 'COUPNUM', + COUPPCD: 'COUPPCD', CSC: 'CSC', CSCH: 'CSCH', CUMIPMT: 'CUMIPMT', @@ -97,6 +104,7 @@ const dictionary: RawTranslationPackage = { DECIMAL: 'DECIMAL', DEGREES: 'DEGREES', DELTA: 'DELTA', + DISC: 'DISC', DOLLARDE: 'DOLLARDE', DOLLARFR: 'DOLLARFR', EDATE: 'EDATE', @@ -125,6 +133,7 @@ const dictionary: RawTranslationPackage = { INDEX: 'INDEX', INT: 'INT', INTERVAL: 'INTERVAL', + INTRATE: 'INTRATE', IPMT: 'IPMT', IRR: 'IRR', ISBINARY: 'ISBINARY', @@ -185,11 +194,13 @@ const dictionary: RawTranslationPackage = { PRODUCT: 'PRODUCT', POWER: 'POWER', PPMT: 'PPMT', + PRICEDISC: 'PRICEDISC', PROPER: 'PROPER', PV: 'PV', RADIANS: 'RADIANS', RAND: 'RAND', RATE: 'RATE', + RECEIVED: 'RECEIVED', REPLACE: 'REPLACE', REPT: 'REPT', RIGHT: 'RIGHT', @@ -261,6 +272,7 @@ const dictionary: RawTranslationPackage = { XLOOKUP: 'XLOOKUP', YEAR: 'YEAR', YEARFRAC: 'YEARFRAC', + YIELDDISC: 'YIELDDISC', 'HF.ADD': 'HF.ADD', 'HF.CONCAT': 'HF.CONCAT', 'HF.DIVIDE': 'HF.DIVIDE', diff --git a/src/i18n/languages/esES.ts b/src/i18n/languages/esES.ts index 9c6b817b98..ddd3253293 100644 --- a/src/i18n/languages/esES.ts +++ b/src/i18n/languages/esES.ts @@ -25,6 +25,7 @@ export const dictionary: RawTranslationPackage = { 'ARRAY_CONSTRAIN': 'ARRAY_CONSTRAIN', ARRAYFORMULA: 'ARRAYFORMULA', ABS: 'ABS', + ACCRINTM: 'INT.ACUM.V', ACOS: 'ACOS', ACOSH: 'ACOSH', ACOT: 'ACOT', @@ -66,6 +67,12 @@ export const dictionary: RawTranslationPackage = { COUNTIF: 'CONTAR.SI', COUNTIFS: 'CONTAR.SI.CONJUNTO', COUNTUNIQUE: 'COUNTUNIQUE', + COUPDAYBS: 'CUPON.DIAS.L1', + COUPDAYS: 'CUPON.DIAS', + COUPDAYSNC: 'CUPON.DIAS.L2', + COUPNCD: 'CUPON.FECHA.L2', + COUPNUM: 'CUPON.NUM', + COUPPCD: 'CUPON.FECHA.L1', CSC: 'CSC', CSCH: 'CSCH', CUMIPMT: 'PAGO.INT.ENTRE', @@ -96,6 +103,7 @@ export const dictionary: RawTranslationPackage = { DECIMAL: 'CONV.DECIMAL', DEGREES: 'GRADOS', DELTA: 'DELTA', + DISC: 'TASA.DESC', DOLLARDE: 'MONEDA.DEC', DOLLARFR: 'MONEDA.FRAC', EDATE: 'FECHA.MES', @@ -124,6 +132,7 @@ export const dictionary: RawTranslationPackage = { INDEX: 'INDICE', INT: 'ENTERO', INTERVAL: 'INTERVAL', //FIXME + INTRATE: 'TASA.INT', IPMT: 'PAGOINT', IRR: 'TIR', ISBINARY: 'ISBINARY', @@ -184,11 +193,13 @@ export const dictionary: RawTranslationPackage = { PRODUCT: 'PRODUCTO', POWER: 'POTENCIA', PPMT: 'PAGOPRIN', + PRICEDISC: 'PRECIO.DESCUENTO', PROPER: 'NOMPROPIO', PV: 'VA', RADIANS: 'RADIANES', RAND: 'ALEATORIO', RATE: 'TASA', + RECEIVED: 'CANTIDAD.RECIBIDA', REPLACE: 'REEMPLAZAR', REPT: 'REPETIR', RIGHT: 'DERECHA', @@ -259,6 +270,7 @@ export const dictionary: RawTranslationPackage = { XOR: 'XOR', YEAR: 'AÑO', YEARFRAC: 'FRAC.AÑO', + YIELDDISC: 'RENDTO.DESC', ROMAN: 'NUMERO.ROMANO', ARABIC: 'ARABIC', 'HF.ADD': 'HF.ADD', diff --git a/src/i18n/languages/fiFI.ts b/src/i18n/languages/fiFI.ts index 99ff05708f..c7df87e232 100644 --- a/src/i18n/languages/fiFI.ts +++ b/src/i18n/languages/fiFI.ts @@ -25,6 +25,7 @@ const dictionary: RawTranslationPackage = { 'ARRAY_CONSTRAIN': 'ARRAY_CONSTRAIN', ARRAYFORMULA: 'ARRAYFORMULA', ABS: 'ITSEISARVO', + ACCRINTM: 'KERTYNYT.KORKO.LOPUSSA', ACOS: 'ACOS', ACOSH: 'ACOSH', ACOT: 'ACOT', @@ -66,6 +67,12 @@ const dictionary: RawTranslationPackage = { COUNTIF: 'LASKE.JOS', COUNTIFS: 'LASKE.JOS.JOUKKO', COUNTUNIQUE: 'COUNTUNIQUE', + COUPDAYBS: 'KORKOPÄIVÄT.ALUSTA', + COUPDAYS: 'KORKOPÄIVÄT', + COUPDAYSNC: 'KORKOPÄIVÄT.SEURAAVA', + COUPNCD: 'KORKOPÄIVÄ.SEURAAVA', + COUPNUM: 'KORKOPÄIVÄ.JAKSOT', + COUPPCD: 'KORKOPÄIVÄ.EDELLINEN', CSC: 'KOSEK', CSCH: 'KOSEKH', CUMIPMT: 'MAKSETTU.KORKO', @@ -96,6 +103,7 @@ const dictionary: RawTranslationPackage = { DECIMAL: 'DESIMAALI', DEGREES: 'ASTEET', DELTA: 'SAMA.ARVO', + DISC: 'DISKONTTOKORKO', DOLLARDE: 'VALUUTTA.DES', DOLLARFR: 'VALUUTTA.MURTO', EDATE: 'PÄIVÄ.KUUKAUSI', @@ -124,6 +132,7 @@ const dictionary: RawTranslationPackage = { INDEX: 'INDEKSI', INT: 'KOKONAISLUKU', INTERVAL: 'INTERVAL', //FIXME + INTRATE: 'KORKO.ARVOPAPERI', IPMT: 'IPMT', IRR: 'SISÄINEN.KORKO', ISBINARY: 'ISBINARY', @@ -184,11 +193,13 @@ const dictionary: RawTranslationPackage = { PRODUCT: 'TULO', POWER: 'POTENSSI', PPMT: 'PPMT', + PRICEDISC: 'HINTA.DISK', PROPER: 'ERISNIMI', PV: 'NA', RADIANS: 'RADIAANIT', RAND: 'SATUNNAISLUKU', RATE: 'KORKO', + RECEIVED: 'SAATU.HINTA', REPLACE: 'KORVAA', REPT: 'TOISTA', RIGHT: 'OIKEA', @@ -259,6 +270,7 @@ const dictionary: RawTranslationPackage = { XOR: 'EHDOTON.TAI', YEAR: 'VUOSI', YEARFRAC: 'VUOSI.OSA', + YIELDDISC: 'TUOTTO.DISK', ROMAN: 'ROMAN', ARABIC: 'ARABIA', 'HF.ADD': 'HF.ADD', diff --git a/src/i18n/languages/frFR.ts b/src/i18n/languages/frFR.ts index f7b9be45f7..4139099fd4 100644 --- a/src/i18n/languages/frFR.ts +++ b/src/i18n/languages/frFR.ts @@ -25,6 +25,7 @@ const dictionary: RawTranslationPackage = { 'ARRAY_CONSTRAIN': 'ARRAY_CONSTRAIN', ARRAYFORMULA: 'ARRAYFORMULA', ABS: 'ABS', + ACCRINTM: 'INTERET.ACC.MAT', ACOS: 'ACOS', ACOSH: 'ACOSH', ACOT: 'ACOT', @@ -66,6 +67,12 @@ const dictionary: RawTranslationPackage = { COUNTIF: 'NB.SI', COUNTIFS: 'NB.SI.ENS', COUNTUNIQUE: 'COUNTUNIQUE', + COUPDAYBS: 'NB.JOURS.COUPON.PREC', + COUPDAYS: 'NB.JOURS.COUPONS', + COUPDAYSNC: 'NB.JOURS.COUPON.SUIV', + COUPNCD: 'DATE.COUPON.SUIV', + COUPNUM: 'NB.COUPONS', + COUPPCD: 'DATE.COUPON.PREC', CSC: 'CSC', CSCH: 'CSCH', CUMIPMT: 'CUMUL.INTER', @@ -96,6 +103,7 @@ const dictionary: RawTranslationPackage = { DECIMAL: 'DECIMAL', DEGREES: 'DEGRES', DELTA: 'DELTA', + DISC: 'TAUX.ESCOMPTE', DOLLARDE: 'PRIX.DEC', DOLLARFR: 'PRIX.FRAC', EDATE: 'MOIS.DECALER', @@ -124,6 +132,7 @@ const dictionary: RawTranslationPackage = { INDEX: 'INDEX', INT: 'ENT', INTERVAL: 'INTERVAL', //FIXME + INTRATE: 'TAUX.INTERET', IPMT: 'INTPER', IRR: 'TRI', ISBINARY: 'ISBINARY', @@ -184,11 +193,13 @@ const dictionary: RawTranslationPackage = { PRODUCT: 'PRODUIT', POWER: 'PUISSANCE', PPMT: 'PRINCPER', + PRICEDISC: 'VALEUR.ENCAISSEMENT', PROPER: 'NOMPROPRE', PV: 'VA', RADIANS: 'RADIANS', RAND: 'ALEA', RATE: 'TAUX', + RECEIVED: 'VALEUR.NOMINALE', REPLACE: 'REMPLACER', REPT: 'REPT', RIGHT: 'DROITE', @@ -259,6 +270,7 @@ const dictionary: RawTranslationPackage = { XOR: 'OUX', YEAR: 'ANNEE', YEARFRAC: 'FRACTION.ANNEE', + YIELDDISC: 'RENDEMENT.SIMPLE', ROMAN: 'ROMAIN', ARABIC: 'ARABE', 'HF.ADD': 'HF.ADD', diff --git a/src/i18n/languages/huHU.ts b/src/i18n/languages/huHU.ts index e40219553a..c137975c79 100644 --- a/src/i18n/languages/huHU.ts +++ b/src/i18n/languages/huHU.ts @@ -25,6 +25,7 @@ const dictionary: RawTranslationPackage = { 'ARRAY_CONSTRAIN': 'ARRAY_CONSTRAIN', ARRAYFORMULA: 'ARRAYFORMULA', ABS: 'ABS', + ACCRINTM: 'LEJÁRATI.KAMAT', ACOS: 'ARCCOS', ACOSH: 'ARCCOSH', ACOT: 'ARCCOT', @@ -66,6 +67,12 @@ const dictionary: RawTranslationPackage = { COUNTIF: 'DARABTELI', COUNTIFS: 'DARABHATÖBB', COUNTUNIQUE: 'COUNTUNIQUE', + COUPDAYBS: 'SZELVÉNYIDŐ.KEZDETTŐL', + COUPDAYS: 'SZELVÉNYIDŐ', + COUPDAYSNC: 'SZELVÉNYIDŐ.KIFIZETÉSTŐL', + COUPNCD: 'ELSŐ.SZELVÉNYDÁTUM', + COUPNUM: 'SZELVÉNYSZÁM', + COUPPCD: 'UTOLSÓ.SZELVÉNYDÁTUM', CSC: 'CSC', CSCH: 'CSCH', CUMIPMT: 'ÖSSZES.KAMAT', @@ -96,6 +103,7 @@ const dictionary: RawTranslationPackage = { DECIMAL: 'TIZEDES', DEGREES: 'FOK', DELTA: 'DELTA', + DISC: 'LESZÁM', DOLLARDE: 'FORINT.DEC', DOLLARFR: 'FORINT.TÖRT', EDATE: 'KALK.DÁTUM', @@ -124,6 +132,7 @@ const dictionary: RawTranslationPackage = { INDEX: 'INDEX', INT: 'INT', INTERVAL: 'INTERVAL', //FIXME + INTRATE: 'KAMATRÁTA', IPMT: 'RRÉSZLET', IRR: 'BMR', ISBINARY: 'ISBINARY', @@ -184,11 +193,13 @@ const dictionary: RawTranslationPackage = { PRODUCT: 'SZORZAT', POWER: 'HATVÁNY', PPMT: 'PRÉSZLET', + PRICEDISC: 'ÁR.LESZÁM', PROPER: 'TNÉV', PV: 'MÉ', RADIANS: 'RADIÁN', RAND: 'VÉL', RATE: 'RÁTA', + RECEIVED: 'KAPOTT', REPLACE: 'CSERE', REPT: 'SOKSZOR', RIGHT: 'JOBB', @@ -259,6 +270,7 @@ const dictionary: RawTranslationPackage = { XOR: 'XVAGY', YEAR: 'ÉV', YEARFRAC: 'TÖRTÉV', + YIELDDISC: 'HOZAM.LESZÁM', ROMAN: 'RÓMAI', ARABIC: 'ARAB', 'HF.ADD': 'HF.ADD', diff --git a/src/i18n/languages/idID.ts b/src/i18n/languages/idID.ts index 719cb11db6..f27fafbc2b 100644 --- a/src/i18n/languages/idID.ts +++ b/src/i18n/languages/idID.ts @@ -25,6 +25,7 @@ const dictionary: RawTranslationPackage = { 'ARRAY_CONSTRAIN': 'BATASAN.MATRIKS', ARRAYFORMULA: 'RUMUS.MATRIKS', ABS: 'ABS', + ACCRINTM: 'ACCRINTM', ACOS: 'ACOS', ACOSH: 'ACOSH', ACOT: 'ACOT', @@ -67,6 +68,12 @@ const dictionary: RawTranslationPackage = { COUNTIF: 'HITUNG.JIKA', COUNTIFS: 'HITUNG.JIKAH', COUNTUNIQUE: 'HITUNG.UNIK', + COUPDAYBS: 'COUPDAYBS', + COUPDAYS: 'COUPDAYS', + COUPDAYSNC: 'COUPDAYSNC', + COUPNCD: 'COUPNCD', + COUPNUM: 'COUPNUM', + COUPPCD: 'COUPPCD', CSC: 'CSC', CSCH: 'CSCH', CUMIPMT: 'KUM.BUNGA', @@ -97,6 +104,7 @@ const dictionary: RawTranslationPackage = { DECIMAL: 'DESIMAL', DEGREES: 'DERAJAT', DELTA: 'DELTA', + DISC: 'DISC', DOLLARDE: 'DOLAR.DES', DOLLARFR: 'DOLAR.PEC', EDATE: 'EDATE', @@ -125,6 +133,7 @@ const dictionary: RawTranslationPackage = { INDEX: 'INDEKS', INT: 'BULAT.BAWAH', INTERVAL: 'INTERVAL', + INTRATE: 'INTRATE', IPMT: 'IPMT', IRR: 'IRR', ISBINARY: 'ADALAH.BINER', @@ -185,11 +194,13 @@ const dictionary: RawTranslationPackage = { PRODUCT: 'PRODUK', POWER: 'PANGKAT', PPMT: 'PPMT', + PRICEDISC: 'PRICEDISC', PROPER: 'HURUF.AWAL.BESAR', PV: 'PV', RADIANS: 'RADIAN', RAND: 'ACAK', RATE: 'SUKU.BUNGA', + RECEIVED: 'RECEIVED', REPLACE: 'GANTI', REPT: 'ULANG', RIGHT: 'KANAN', @@ -261,6 +272,7 @@ const dictionary: RawTranslationPackage = { XLOOKUP: 'XLOOKUP', YEAR: 'TAHUN', YEARFRAC: 'FRAC.TAHUN', + YIELDDISC: 'YIELDDISC', 'HF.ADD': 'HF.ADD', 'HF.CONCAT': 'HF.CONCAT', 'HF.DIVIDE': 'HF.DIVIDE', diff --git a/src/i18n/languages/itIT.ts b/src/i18n/languages/itIT.ts index 1a494628f8..292ac16e47 100644 --- a/src/i18n/languages/itIT.ts +++ b/src/i18n/languages/itIT.ts @@ -25,6 +25,7 @@ const dictionary: RawTranslationPackage = { 'ARRAY_CONSTRAIN': 'ARRAY_CONSTRAIN', ARRAYFORMULA: 'ARRAYFORMULA', ABS: 'ASS', + ACCRINTM: 'INT.MATURATO.SCAD', ACOS: 'ARCCOS', ACOSH: 'ARCCOSH', ACOT: 'ARCCOT', @@ -66,6 +67,12 @@ const dictionary: RawTranslationPackage = { COUNTIF: 'CONTA.SE', COUNTIFS: 'CONTA.PIÙ.SE', COUNTUNIQUE: 'COUNTUNIQUE', + COUPDAYBS: 'GIORNI.CED.INIZ.LIQ', + COUPDAYS: 'GIORNI.CED', + COUPDAYSNC: 'GIORNI.CED.NUOVA', + COUPNCD: 'DATA.CED.SUCC', + COUPNUM: 'NUM.CED', + COUPPCD: 'DATA.CED.PREC', CSC: 'CSC', CSCH: 'CSCH', CUMIPMT: 'INT.CUMUL', @@ -96,6 +103,7 @@ const dictionary: RawTranslationPackage = { DECIMAL: 'DECIMALE', DEGREES: 'GRADI', DELTA: 'DELTA', + DISC: 'TASSO.SCONTO', DOLLARDE: 'VALUTA.DEC', DOLLARFR: 'VALUTA.FRAZ', EDATE: 'DATA.MESE', @@ -124,6 +132,7 @@ const dictionary: RawTranslationPackage = { INDEX: 'INDICE', INT: 'INT', INTERVAL: 'INTERVAL', //FIXME + INTRATE: 'TASSO.INT', IPMT: 'INTERESSI', IRR: 'TIR.COST', ISBINARY: 'ISBINARY', @@ -184,11 +193,13 @@ const dictionary: RawTranslationPackage = { PRODUCT: 'PRODOTTO', POWER: 'POTENZA', PPMT: 'P.RATA', + PRICEDISC: 'PREZZO.SCONT', PROPER: 'MAIUSC.INIZ', PV: 'VA', RADIANS: 'RADIANTI', RAND: 'CASUALE', RATE: 'TASSO', + RECEIVED: 'RICEV.SCAD', REPLACE: 'RIMPIAZZA', REPT: 'RIPETI', RIGHT: 'DESTRA', @@ -259,6 +270,7 @@ const dictionary: RawTranslationPackage = { XOR: 'XOR', YEAR: 'ANNO', YEARFRAC: 'FRAZIONE.ANNO', + YIELDDISC: 'REND.TITOLI.SCONT', ROMAN: 'ROMANO', ARABIC: 'ARABO', 'HF.ADD': 'HF.ADD', diff --git a/src/i18n/languages/nbNO.ts b/src/i18n/languages/nbNO.ts index ff15415a98..ab306c1c83 100644 --- a/src/i18n/languages/nbNO.ts +++ b/src/i18n/languages/nbNO.ts @@ -25,6 +25,7 @@ const dictionary: RawTranslationPackage = { 'ARRAY_CONSTRAIN': 'ARRAY_CONSTRAIN', ARRAYFORMULA: 'ARRAYFORMULA', ABS: 'ABS', + ACCRINTM: 'PÅLØPT.FORFALLSRENTE', ACOS: 'ARCCOS', ACOSH: 'ARCCOSH', ACOT: 'ACOT', @@ -66,6 +67,12 @@ const dictionary: RawTranslationPackage = { COUNTIF: 'ANTALL.HVIS', COUNTIFS: 'ANTALL.HVIS.SETT', COUNTUNIQUE: 'COUNTUNIQUE', + COUPDAYBS: 'OBLIG.DAGER.FF', + COUPDAYS: 'OBLIG.DAGER', + COUPDAYSNC: 'OBLIG.DAGER.NF', + COUPNCD: 'OBLIG.DAGER.EF', + COUPNUM: 'OBLIG.ANTALL', + COUPPCD: 'OBLIG.DAG.FORRIGE', CSC: 'CSC', CSCH: 'CSCH', CUMIPMT: 'SAMLET.RENTE', @@ -96,6 +103,7 @@ const dictionary: RawTranslationPackage = { DECIMAL: 'DESIMAL', DEGREES: 'GRADER', DELTA: 'DELTA', + DISC: 'DISKONTERT', DOLLARDE: 'DOLLARDE', DOLLARFR: 'DOLLARBR', EDATE: 'DAG.ETTER', @@ -124,6 +132,7 @@ const dictionary: RawTranslationPackage = { INDEX: 'INDEKS', INT: 'HELTALL', INTERVAL: 'INTERVAL', //FIXME + INTRATE: 'RENTESATS', IPMT: 'RAVDRAG', IRR: 'IR', ISBINARY: 'ISBINARY', @@ -184,11 +193,13 @@ const dictionary: RawTranslationPackage = { PRODUCT: 'PRODUKT', POWER: 'OPPHØYD.I', PPMT: 'AMORT', + PRICEDISC: 'PRIS.DISKONTERT', PROPER: 'STOR.FORBOKSTAV', PV: 'NÅVERDI', RADIANS: 'RADIANER', RAND: 'TILFELDIG', RATE: 'RENTE', + RECEIVED: 'MOTTATT.AVKAST', REPLACE: 'ERSTATT', REPT: 'GJENTA', RIGHT: 'HØYRE', @@ -259,6 +270,7 @@ const dictionary: RawTranslationPackage = { XOR: 'EKSKLUSIVELLER', YEAR: 'ÅR', YEARFRAC: 'ÅRDEL', + YIELDDISC: 'AVKAST.DISKONTERT', ROMAN: 'ROMERTALL', ARABIC: 'ARABISK', 'HF.ADD': 'HF.ADD', diff --git a/src/i18n/languages/nlNL.ts b/src/i18n/languages/nlNL.ts index c491e2f44b..0b6bad4023 100644 --- a/src/i18n/languages/nlNL.ts +++ b/src/i18n/languages/nlNL.ts @@ -25,6 +25,7 @@ const dictionary: RawTranslationPackage = { 'ARRAY_CONSTRAIN': 'ARRAY_CONSTRAIN', ARRAYFORMULA: 'ARRAYFORMULA', ABS: 'ABS', + ACCRINTM: 'SAMENG.RENTE.V', ACOS: 'BOOGCOS', ACOSH: 'BOOGCOSH', ACOT: 'BOOGCOT', @@ -66,6 +67,12 @@ const dictionary: RawTranslationPackage = { COUNTIF: 'AANTAL.ALS', COUNTIFS: 'AANTALLEN.ALS', COUNTUNIQUE: 'COUNTUNIQUE', + COUPDAYBS: 'COUP.DAGEN.BB', + COUPDAYS: 'COUP.DAGEN', + COUPDAYSNC: 'COUP.DAGEN.VV', + COUPNCD: 'COUP.DATUM.NB', + COUPNUM: 'COUP.AANTAL', + COUPPCD: 'COUP.DATUM.VB', CSC: 'COSEC', CSCH: 'COSECH', CUMIPMT: 'CUM.RENTE', @@ -96,6 +103,7 @@ const dictionary: RawTranslationPackage = { DECIMAL: 'DECIMAAL', DEGREES: 'GRADEN', DELTA: 'DELTA', + DISC: 'DISCONTO', DOLLARDE: 'EURO.DE', DOLLARFR: 'EURO.BR', EDATE: 'ZELFDE.DAG', @@ -124,6 +132,7 @@ const dictionary: RawTranslationPackage = { INDEX: 'INDEX', INT: 'INTEGER', INTERVAL: 'INTERVAL', //FIXME + INTRATE: 'RENTEPERCENTAGE', IPMT: 'IBET', IRR: 'IR', ISBINARY: 'ISBINARY', @@ -184,11 +193,13 @@ const dictionary: RawTranslationPackage = { PRODUCT: 'PRODUCT', POWER: 'MACHT', PPMT: 'PBET', + PRICEDISC: 'PRIJS.DISCONTO', PROPER: 'BEGINLETTERS', PV: 'HW', RADIANS: 'RADIALEN', RAND: 'ASELECT', RATE: 'RENTE', + RECEIVED: 'OPBRENGST', REPLACE: 'VERVANGEN', REPT: 'HERHALING', RIGHT: 'RECHTS', @@ -259,6 +270,7 @@ const dictionary: RawTranslationPackage = { XOR: 'EX.OF', YEAR: 'JAAR', YEARFRAC: 'JAAR.DEEL', + YIELDDISC: 'REND.DISCONTO', ROMAN: 'ROMEINS', ARABIC: 'ARABISCH', 'HF.ADD': 'HF.ADD', diff --git a/src/i18n/languages/plPL.ts b/src/i18n/languages/plPL.ts index 6fa4f016b4..a45126ee1c 100644 --- a/src/i18n/languages/plPL.ts +++ b/src/i18n/languages/plPL.ts @@ -25,6 +25,7 @@ const dictionary: RawTranslationPackage = { 'ARRAY_CONSTRAIN': 'ARRAY_CONSTRAIN', ARRAYFORMULA: 'ARRAYFORMULA', ABS: 'WART.BEZWGL', + ACCRINTM: 'NAL.ODS.WYKUP', ACOS: 'ACOS', ACOSH: 'ACOSH', ACOT: 'ACOT', @@ -66,6 +67,12 @@ const dictionary: RawTranslationPackage = { COUNTIF: 'LICZ.JEŻELI', COUNTIFS: 'LICZ.WARUNKI', COUNTUNIQUE: 'COUNTUNIQUE', + COUPDAYBS: 'WYPŁ.DNI.OD.POCZ', + COUPDAYS: 'WYPŁ.DNI', + COUPDAYSNC: 'WYPŁ.DNI.NAST', + COUPNCD: 'WYPŁ.DATA.NAST', + COUPNUM: 'WYPŁ.LICZBA', + COUPPCD: 'WYPŁ.DATA.POPRZ', CSC: 'CSC', CSCH: 'CSCH', CUMIPMT: 'SPŁAC.ODS', @@ -96,6 +103,7 @@ const dictionary: RawTranslationPackage = { DECIMAL: 'DZIESIĘTNA', DEGREES: 'STOPNIE', DELTA: 'DELTA', + DISC: 'STOPA.DYSK', DOLLARDE: 'CENA.DZIES', DOLLARFR: 'CENA.UŁAM', EDATE: 'NR.SER.DATY', @@ -124,6 +132,7 @@ const dictionary: RawTranslationPackage = { INDEX: 'INDEKS', INT: 'ZAOKR.DO.CAŁK', INTERVAL: 'INTERVAL', //FIXME + INTRATE: 'STOPA.PROC', IPMT: 'IPMT', IRR: 'IRR', ISBINARY: 'ISBINARY', @@ -184,11 +193,13 @@ const dictionary: RawTranslationPackage = { PRODUCT: 'ILOCZYN', POWER: 'POTĘGA', PPMT: 'PPMT', + PRICEDISC: 'CENA.DYSK', PROPER: 'Z.WIELKIEJ.LITERY', PV: 'PV', RADIANS: 'RADIANY', RAND: 'LOSUJ', RATE: 'RATE', + RECEIVED: 'KWOTA.WYKUP', REPLACE: 'ZASTĄP', REPT: 'POWT', RIGHT: 'PRAWY', @@ -259,6 +270,7 @@ const dictionary: RawTranslationPackage = { XOR: 'XOR', YEAR: 'ROK', YEARFRAC: 'CZĘŚĆ.ROKU', + YIELDDISC: 'RENT.DYSK', ROMAN: 'RZYMSKIE', ARABIC: 'ARABSKA', 'HF.ADD': 'HF.ADD', diff --git a/src/i18n/languages/ptPT.ts b/src/i18n/languages/ptPT.ts index 6c1e68c328..038a527ab1 100644 --- a/src/i18n/languages/ptPT.ts +++ b/src/i18n/languages/ptPT.ts @@ -25,6 +25,7 @@ const dictionary: RawTranslationPackage = { 'ARRAY_CONSTRAIN': 'ARRAY_CONSTRAIN', ARRAYFORMULA: 'ARRAYFORMULA', ABS: 'ABS', + ACCRINTM: 'JUROSACUMV', ACOS: 'ACOS', ACOSH: 'ACOSH', ACOT: 'ACOT', @@ -66,6 +67,12 @@ const dictionary: RawTranslationPackage = { COUNTIF: 'CONT.SE', COUNTIFS: 'CONT.SES', COUNTUNIQUE: 'COUNTUNIQUE', + COUPDAYBS: 'CUPDIASINLIQ', + COUPDAYS: 'CUPDIAS', + COUPDAYSNC: 'CUPDIASPRÓX', + COUPNCD: 'CUPDATAPRÓX', + COUPNUM: 'CUPNÚM', + COUPPCD: 'CUPDATAANT', CSC: 'COSEC', CSCH: 'COSECH', CUMIPMT: 'PGTOJURACUM', @@ -96,6 +103,7 @@ const dictionary: RawTranslationPackage = { DECIMAL: 'DECIMAL', DEGREES: 'GRAUS', DELTA: 'DELTA', + DISC: 'DESC', DOLLARDE: 'MOEDADEC', DOLLARFR: 'MOEDAFRA', EDATE: 'DATAM', @@ -124,6 +132,7 @@ const dictionary: RawTranslationPackage = { INDEX: 'ÍNDICE', INT: 'INT', INTERVAL: 'INTERVAL', //FIXME + INTRATE: 'TAXAJUROS', IPMT: 'IPGTO', IRR: 'TIR', ISBINARY: 'ISBINARY', @@ -184,11 +193,13 @@ const dictionary: RawTranslationPackage = { PRODUCT: 'MULT', POWER: 'POTÊNCIA', PPMT: 'PPGTO', + PRICEDISC: 'PREÇODESC', PROPER: 'PRI.MAIÚSCULA', PV: 'VP', RADIANS: 'RADIANOS', RAND: 'ALEATÓRIO', RATE: 'TAXA', + RECEIVED: 'RECEBER', REPLACE: 'MUDAR', REPT: 'REPT', RIGHT: 'DIREITA', @@ -259,6 +270,7 @@ const dictionary: RawTranslationPackage = { XOR: 'OUEXCL', YEAR: 'ANO', YEARFRAC: 'FRAÇÃOANO', + YIELDDISC: 'LUCRODESC', ROMAN: 'ROMANO', ARABIC: 'CARDINAL', 'HF.ADD': 'HF.ADD', diff --git a/src/i18n/languages/ruRU.ts b/src/i18n/languages/ruRU.ts index 6b35c1ce2f..f52e8b5bae 100644 --- a/src/i18n/languages/ruRU.ts +++ b/src/i18n/languages/ruRU.ts @@ -25,6 +25,7 @@ const dictionary: RawTranslationPackage = { 'ARRAY_CONSTRAIN': 'ARRAY_CONSTRAIN', ARRAYFORMULA: 'ARRAYFORMULA', ABS: 'ABS', + ACCRINTM: 'НАКОПДОХОДПОГАШ', ACOS: 'ACOS', ACOSH: 'ACOSH', ACOT: 'ACOT', @@ -66,6 +67,12 @@ const dictionary: RawTranslationPackage = { COUNTIF: 'СЧЁТЕСЛИ', COUNTIFS: 'СЧЁТЕСЛИМН', COUNTUNIQUE: 'COUNTUNIQUE', + COUPDAYBS: 'ДНЕЙКУПОНДО', + COUPDAYS: 'ДНЕЙКУПОН', + COUPDAYSNC: 'ДНЕЙКУПОНПОСЛЕ', + COUPNCD: 'ДАТАКУПОНПОСЛЕ', + COUPNUM: 'ЧИСЛКУПОН', + COUPPCD: 'ДАТАКУПОНДО', CSC: 'CSC', CSCH: 'CSCH', CUMIPMT: 'ОБЩПЛАТ', @@ -96,6 +103,7 @@ const dictionary: RawTranslationPackage = { DECIMAL: 'ДЕС', DEGREES: 'ГРАДУСЫ', DELTA: 'ДЕЛЬТА', + DISC: 'СКИДКА', DOLLARDE: 'РУБЛЬ.ДЕС', DOLLARFR: 'РУБЛЬ.ДРОБЬ', EDATE: 'ДАТАМЕС', @@ -124,6 +132,7 @@ const dictionary: RawTranslationPackage = { INDEX: 'ИНДЕКС', INT: 'ЦЕЛОЕ', INTERVAL: 'INTERVAL', //FIXME + INTRATE: 'ИНОРМА', IPMT: 'ПРПЛТ', IRR: 'ВСД', ISBINARY: 'ISBINARY', @@ -184,11 +193,13 @@ const dictionary: RawTranslationPackage = { PRODUCT: 'ПРОИЗВЕД', POWER: 'СТЕПЕНЬ', PPMT: 'ОСПЛТ', + PRICEDISC: 'ЦЕНАСКИДКА', PROPER: 'ПРОПНАЧ', PV: 'ПС', RADIANS: 'РАДИАНЫ', RAND: 'СЛЧИС', RATE: 'СТАВКА', + RECEIVED: 'ПОЛУЧЕНО', REPLACE: 'ЗАМЕНИТЬ', REPT: 'ПОВТОР', RIGHT: 'ПРАВСИМВ', @@ -259,6 +270,7 @@ const dictionary: RawTranslationPackage = { XOR: 'ИСКЛИЛИ', YEAR: 'ГОД', YEARFRAC: 'ДОЛЯГОДА', + YIELDDISC: 'ДОХОДСКИДКА', ROMAN: 'РИМСКОЕ', ARABIC: 'АРАБСКОЕ', 'HF.ADD': 'HF.ADD', diff --git a/src/i18n/languages/svSE.ts b/src/i18n/languages/svSE.ts index 5f87580087..f0f128804c 100644 --- a/src/i18n/languages/svSE.ts +++ b/src/i18n/languages/svSE.ts @@ -25,6 +25,7 @@ const dictionary: RawTranslationPackage = { 'ARRAY_CONSTRAIN': 'ARRAY_CONSTRAIN', ARRAYFORMULA: 'ARRAYFORMULA', ABS: 'ABS', + ACCRINTM: 'UPPLOBLRÄNTA', ACOS: 'ARCCOS', ACOSH: 'ARCCOSH', ACOT: 'ACOT', @@ -66,6 +67,12 @@ const dictionary: RawTranslationPackage = { COUNTIF: 'ANTAL.OM', COUNTIFS: 'ANTAL.OMF', COUNTUNIQUE: 'COUNTUNIQUE', + COUPDAYBS: 'KUPDAGBB', + COUPDAYS: 'KUPDAGB', + COUPDAYSNC: 'KUPDAGNK', + COUPNCD: 'KUPNKD', + COUPNUM: 'KUPANT', + COUPPCD: 'KUPFKD', CSC: 'CSC', CSCH: 'CSCH', CUMIPMT: 'KUMRÄNTA', @@ -96,6 +103,7 @@ const dictionary: RawTranslationPackage = { DECIMAL: 'DECIMAL', DEGREES: 'GRADER', DELTA: 'DELTA', + DISC: 'DISK', DOLLARDE: 'DECTAL', DOLLARFR: 'BRÅK', EDATE: 'EDATUM', @@ -124,6 +132,7 @@ const dictionary: RawTranslationPackage = { INDEX: 'INDEX', INT: 'HELTAL', INTERVAL: 'INTERVAL', //FIXME + INTRATE: 'ÅRSRÄNTA', IPMT: 'RBETALNING', IRR: 'IR', ISBINARY: 'ISBINARY', @@ -184,11 +193,13 @@ const dictionary: RawTranslationPackage = { PRODUCT: 'PRODUKT', POWER: 'UPPHÖJT.TILL', PPMT: 'AMORT', + PRICEDISC: 'PRISDISK', PROPER: 'INITIAL', PV: 'NUVÄRDE', RADIANS: 'RADIANER', RAND: 'SLUMP', RATE: 'RÄNTA', + RECEIVED: 'BELOPP', REPLACE: 'ERSÄTT', REPT: 'REP', RIGHT: 'HÖGER', @@ -259,6 +270,7 @@ const dictionary: RawTranslationPackage = { XOR: 'XOR', YEAR: 'ÅR', YEARFRAC: 'ÅRDEL', + YIELDDISC: 'NOMAVKDISK', ROMAN: 'ROMERSK', ARABIC: 'ARABIC', 'HF.ADD': 'HF.ADD', diff --git a/src/i18n/languages/trTR.ts b/src/i18n/languages/trTR.ts index 93c2176d37..13a085b09a 100644 --- a/src/i18n/languages/trTR.ts +++ b/src/i18n/languages/trTR.ts @@ -25,6 +25,7 @@ const dictionary: RawTranslationPackage = { 'ARRAY_CONSTRAIN': 'ARRAY_CONSTRAIN', ARRAYFORMULA: 'ARRAYFORMULA', ABS: 'MUTLAK', + ACCRINTM: 'GERÇEKFAİZV', ACOS: 'ACOS', ACOSH: 'ACOSH', ACOT: 'ACOT', @@ -66,6 +67,12 @@ const dictionary: RawTranslationPackage = { COUNTIF: 'EĞERSAY', COUNTIFS: 'ÇOKEĞERSAY', COUNTUNIQUE: 'COUNTUNIQUE', + COUPDAYBS: 'KUPONGÜNBD', + COUPDAYS: 'KUPONGÜN', + COUPDAYSNC: 'KUPONGÜNDSK', + COUPNCD: 'KUPONGÜNSKT', + COUPNUM: 'KUPONSAYI', + COUPPCD: 'KUPONGÜNÖKT', CSC: 'CSC', CSCH: 'CSCH', CUMIPMT: 'TOPÖDENENFAİZ', @@ -96,6 +103,7 @@ const dictionary: RawTranslationPackage = { DECIMAL: 'ONDALIK', DEGREES: 'DERECE', DELTA: 'DELTA', + DISC: 'İNDİRİM', DOLLARDE: 'LİRAON', DOLLARFR: 'LİRAKES', EDATE: 'SERİTARİH', @@ -124,6 +132,7 @@ const dictionary: RawTranslationPackage = { INDEX: 'İNDİS', INT: 'TAMSAYI', INTERVAL: 'INTERVAL', //FIXME + INTRATE: 'FAİZORANI', IPMT: 'FAİZTUTARI', IRR: 'İÇ_VERİM_ORANI', ISBINARY: 'ISBINARY', @@ -184,11 +193,13 @@ const dictionary: RawTranslationPackage = { PRODUCT: 'ÇARPIM', POWER: 'KUVVET', PPMT: 'ANA_PARA_ÖDEMESİ', + PRICEDISC: 'DEĞERİND', PROPER: 'YAZIM.DÜZENİ', PV: 'BD', RADIANS: 'RADYAN', RAND: 'S_SAYI_ÜRET', RATE: 'FAİZ_ORANI', + RECEIVED: 'GETİRİ', REPLACE: 'DEĞİŞTİR', REPT: 'YİNELE', RIGHT: 'SAĞ', @@ -259,6 +270,7 @@ const dictionary: RawTranslationPackage = { XOR: 'ÖZELVEYA', YEAR: 'YIL', YEARFRAC: 'YILORAN', + YIELDDISC: 'ÖDEMEİND', ROMAN: 'ROMEN', ARABIC: 'ARAPÇA', 'HF.ADD': 'HF.ADD', diff --git a/src/interpreter/functionMetadata/categories/financial.ts b/src/interpreter/functionMetadata/categories/financial.ts index f673c11953..32fd7d561e 100644 --- a/src/interpreter/functionMetadata/categories/financial.ts +++ b/src/interpreter/functionMetadata/categories/financial.ts @@ -10,6 +10,55 @@ import {FunctionDoc} from '../FunctionDescription' * truth for the function metadata API, and `docs/guide/built-in-functions.md` is generated from it. */ export const FINANCIAL_DOCS: Record = { + ACCRINTM: { + category: 'Financial', + shortDescription: 'Returns the accrued interest of a security that pays interest at maturity.', + parameters: [{name: 'issue', description: 'The issue date, as a date serial number, truncated to an integer.'}, {name: 'settlement', description: 'The settlement date, as a date serial number, truncated to an integer; must not be earlier than `issue` (equal dates give 0).'}, {name: 'rate', description: 'The annual coupon rate; must be greater than 0.'}, {name: 'par', description: 'The par value; must be greater than 0. If the argument is omitted or empty, 1000 is used.'}, {name: 'basis', description: 'The day-count basis, truncated to an integer: 0 (default) US 30/360, 1 actual/actual, 2 actual/360, 3 actual/365, 4 European 30/360.'}], + documentationUrl: 'https://hyperformula.handsontable.com/docs/guide/built-in-functions.html', + examples: ['=ACCRINTM(DATE(2008,4,1), DATE(2008,6,15), 0.1, 1000, 3)', '=ACCRINTM(A1, B1, 0.05)'], + }, + COUPDAYBS: { + category: 'Financial', + shortDescription: 'Returns the number of days from the beginning of the coupon period to `settlement`.', + parameters: [{name: 'settlement', description: 'The settlement date, as a date serial number, truncated to an integer.'}, {name: 'maturity', description: 'The maturity date, as a date serial number, truncated to an integer; must be later than `settlement`.'}, {name: 'frequency', description: 'The number of coupon payments per year, truncated to an integer: 1 (annual), 2 (semiannual) or 4 (quarterly).'}, {name: 'basis', description: 'The day-count basis, truncated to an integer: 0 (default) US 30/360, 1 actual/actual, 2 actual/360, 3 actual/365, 4 European 30/360.'}], + documentationUrl: 'https://hyperformula.handsontable.com/docs/guide/built-in-functions.html', + examples: ['=COUPDAYBS(DATE(2011,1,25), DATE(2011,11,15), 2, 1)', '=COUPDAYBS(A1, B1, 4)'], + }, + COUPDAYS: { + category: 'Financial', + shortDescription: 'Returns the number of days in the coupon period that contains `settlement`.', + parameters: [{name: 'settlement', description: 'The settlement date, as a date serial number, truncated to an integer.'}, {name: 'maturity', description: 'The maturity date, as a date serial number, truncated to an integer; must be later than `settlement`.'}, {name: 'frequency', description: 'The number of coupon payments per year, truncated to an integer: 1 (annual), 2 (semiannual) or 4 (quarterly).'}, {name: 'basis', description: 'The day-count basis, truncated to an integer: 0 (default) US 30/360, 1 actual/actual, 2 actual/360, 3 actual/365, 4 European 30/360.'}], + documentationUrl: 'https://hyperformula.handsontable.com/docs/guide/built-in-functions.html', + examples: ['=COUPDAYS(DATE(2011,1,25), DATE(2011,11,15), 2, 1)', '=COUPDAYS(A1, B1, 4)'], + }, + COUPDAYSNC: { + category: 'Financial', + shortDescription: 'Returns the number of days from `settlement` to the next coupon date.', + parameters: [{name: 'settlement', description: 'The settlement date, as a date serial number, truncated to an integer.'}, {name: 'maturity', description: 'The maturity date, as a date serial number, truncated to an integer; must be later than `settlement`.'}, {name: 'frequency', description: 'The number of coupon payments per year, truncated to an integer: 1 (annual), 2 (semiannual) or 4 (quarterly).'}, {name: 'basis', description: 'The day-count basis, truncated to an integer: 0 (default) US 30/360, 1 actual/actual, 2 actual/360, 3 actual/365, 4 European 30/360.'}], + documentationUrl: 'https://hyperformula.handsontable.com/docs/guide/built-in-functions.html', + examples: ['=COUPDAYSNC(DATE(2011,1,25), DATE(2011,11,15), 2, 1)', '=COUPDAYSNC(A1, B1, 4)'], + }, + COUPNCD: { + category: 'Financial', + shortDescription: 'Returns the first coupon date after `settlement`, as a date serial number.', + parameters: [{name: 'settlement', description: 'The settlement date, as a date serial number, truncated to an integer.'}, {name: 'maturity', description: 'The maturity date, as a date serial number, truncated to an integer; must be later than `settlement`.'}, {name: 'frequency', description: 'The number of coupon payments per year, truncated to an integer: 1 (annual), 2 (semiannual) or 4 (quarterly).'}, {name: 'basis', description: 'The day-count basis, truncated to an integer: 0 (default) to 4. It is validated but does not change the result.'}], + documentationUrl: 'https://hyperformula.handsontable.com/docs/guide/built-in-functions.html', + examples: ['=COUPNCD(DATE(2011,1,25), DATE(2011,11,15), 2, 1)', '=COUPNCD(A1, B1, 4)'], + }, + COUPNUM: { + category: 'Financial', + shortDescription: 'Returns the number of coupons payable after `settlement`, up to and including `maturity`.', + parameters: [{name: 'settlement', description: 'The settlement date, as a date serial number, truncated to an integer.'}, {name: 'maturity', description: 'The maturity date, as a date serial number, truncated to an integer; must be later than `settlement`.'}, {name: 'frequency', description: 'The number of coupon payments per year, truncated to an integer: 1 (annual), 2 (semiannual) or 4 (quarterly).'}, {name: 'basis', description: 'The day-count basis, truncated to an integer: 0 (default) to 4. It is validated but does not change the result.'}], + documentationUrl: 'https://hyperformula.handsontable.com/docs/guide/built-in-functions.html', + examples: ['=COUPNUM(DATE(2007,1,25), DATE(2008,11,15), 2, 1)', '=COUPNUM(A1, B1, 4)'], + }, + COUPPCD: { + category: 'Financial', + shortDescription: 'Returns the last coupon date on or before `settlement`, as a date serial number.', + parameters: [{name: 'settlement', description: 'The settlement date, as a date serial number, truncated to an integer.'}, {name: 'maturity', description: 'The maturity date, as a date serial number, truncated to an integer; must be later than `settlement`.'}, {name: 'frequency', description: 'The number of coupon payments per year, truncated to an integer: 1 (annual), 2 (semiannual) or 4 (quarterly).'}, {name: 'basis', description: 'The day-count basis, truncated to an integer: 0 (default) to 4. It is validated but does not change the result.'}], + documentationUrl: 'https://hyperformula.handsontable.com/docs/guide/built-in-functions.html', + examples: ['=COUPPCD(DATE(2011,1,25), DATE(2011,11,15), 2, 1)', '=COUPPCD(A1, B1, 4)'], + }, CUMIPMT: { category: 'Financial', shortDescription: 'Returns the cumulative interest paid on a loan between a start period and an end period.', @@ -38,6 +87,13 @@ export const FINANCIAL_DOCS: Record = { documentationUrl: 'https://hyperformula.handsontable.com/docs/guide/built-in-functions.html', examples: ['=DDB(10000, 1000, 6, 2)', '=DDB(10000, 1000, 6, 1, 1.5)'], }, + DISC: { + category: 'Financial', + shortDescription: 'Returns the discount rate of a security.', + parameters: [{name: 'settlement', description: 'The settlement date, as a date serial number, truncated to an integer.'}, {name: 'maturity', description: 'The maturity date, as a date serial number, truncated to an integer; must be later than `settlement`.'}, {name: 'pr', description: 'The price per $100 face value; must be greater than 0.'}, {name: 'redemption', description: 'The redemption value per $100 face value; must be greater than 0.'}, {name: 'basis', description: 'The day-count basis, truncated to an integer: 0 (default) US 30/360, 1 actual/actual, 2 actual/360, 3 actual/365, 4 European 30/360.'}], + documentationUrl: 'https://hyperformula.handsontable.com/docs/guide/built-in-functions.html', + examples: ['=DISC(DATE(2018,7,1), DATE(2048,1,1), 97.975, 100, 1)', '=DISC(A1, B1, 99, 100)'], + }, DOLLARDE: { category: 'Financial', shortDescription: 'Converts a price entered with a special notation to a price displayed as a decimal number.', @@ -73,6 +129,13 @@ export const FINANCIAL_DOCS: Record = { documentationUrl: 'https://hyperformula.handsontable.com/docs/guide/built-in-functions.html', examples: ['=FVSCHEDULE(1000, A1:A3)', '=FVSCHEDULE(5000, B2:B5)'], }, + INTRATE: { + category: 'Financial', + shortDescription: 'Returns the interest rate of a fully invested security.', + parameters: [{name: 'settlement', description: 'The settlement date, as a date serial number, truncated to an integer.'}, {name: 'maturity', description: 'The maturity date, as a date serial number, truncated to an integer; must be later than `settlement`.'}, {name: 'investment', description: 'The amount invested; must be greater than 0.'}, {name: 'redemption', description: 'The amount received at `maturity`; must be greater than 0.'}, {name: 'basis', description: 'The day-count basis, truncated to an integer: 0 (default) US 30/360, 1 actual/actual, 2 actual/360, 3 actual/365, 4 European 30/360.'}], + documentationUrl: 'https://hyperformula.handsontable.com/docs/guide/built-in-functions.html', + examples: ['=INTRATE(DATE(2008,2,15), DATE(2008,5,15), 1000000, 1014420, 2)', '=INTRATE(A1, B1, 1000, 1050)'], + }, IPMT: { category: 'Financial', shortDescription: 'Returns the interest portion of a given loan payment in a given payment period.', @@ -143,6 +206,13 @@ export const FINANCIAL_DOCS: Record = { documentationUrl: 'https://hyperformula.handsontable.com/docs/guide/built-in-functions.html', examples: ['=PPMT(0.05/12, 1, 60, 20000)', '=PPMT(0.04/12, 12, 360, 200000, 0, 1)'], }, + PRICEDISC: { + category: 'Financial', + shortDescription: 'Returns the price per $100 face value of a discounted security.', + parameters: [{name: 'settlement', description: 'The settlement date, as a date serial number, truncated to an integer.'}, {name: 'maturity', description: 'The maturity date, as a date serial number, truncated to an integer; must be later than `settlement`.'}, {name: 'discount', description: 'The discount rate; must be greater than 0. A large `discount` gives a zero or negative price.'}, {name: 'redemption', description: 'The redemption value per $100 face value; must be greater than 0.'}, {name: 'basis', description: 'The day-count basis, truncated to an integer: 0 (default) US 30/360, 1 actual/actual, 2 actual/360, 3 actual/365, 4 European 30/360.'}], + documentationUrl: 'https://hyperformula.handsontable.com/docs/guide/built-in-functions.html', + examples: ['=PRICEDISC(DATE(2008,2,16), DATE(2008,3,1), 0.0525, 100, 2)', '=PRICEDISC(A1, B1, 0.05, 100)'], + }, PV: { category: 'Financial', shortDescription: 'Returns the present value of an investment.', @@ -157,6 +227,13 @@ export const FINANCIAL_DOCS: Record = { documentationUrl: 'https://hyperformula.handsontable.com/docs/guide/built-in-functions.html', examples: ['=RATE(60, -100, 5000)', '=RATE(360, -1000, 200000, 0, 0, 0.05)'], }, + RECEIVED: { + category: 'Financial', + shortDescription: 'Returns the amount received at maturity for a fully invested security.', + parameters: [{name: 'settlement', description: 'The settlement date, as a date serial number, truncated to an integer.'}, {name: 'maturity', description: 'The maturity date, as a date serial number, truncated to an integer; must be later than `settlement`.'}, {name: 'investment', description: 'The amount invested; must be greater than 0.'}, {name: 'discount', description: 'The discount rate; must be greater than 0. If `discount` multiplied by the year fraction from `settlement` to `maturity` is 1 or more, the result is #NUM!.'}, {name: 'basis', description: 'The day-count basis, truncated to an integer: 0 (default) US 30/360, 1 actual/actual, 2 actual/360, 3 actual/365, 4 European 30/360.'}], + documentationUrl: 'https://hyperformula.handsontable.com/docs/guide/built-in-functions.html', + examples: ['=RECEIVED(DATE(2008,2,15), DATE(2008,5,15), 1000000, 0.0575, 2)', '=RECEIVED(A1, B1, 1000, 0.05)'], + }, RRI: { category: 'Financial', shortDescription: 'Returns an equivalent interest rate for the growth of an investment.', @@ -213,4 +290,11 @@ export const FINANCIAL_DOCS: Record = { documentationUrl: 'https://hyperformula.handsontable.com/docs/guide/built-in-functions.html', examples: ['=XIRR(A1:A4, B1:B4)', '=XIRR(A1:A5, B1:B5, 0.1)'], }, + YIELDDISC: { + category: 'Financial', + shortDescription: 'Returns the annual yield of a discounted security.', + parameters: [{name: 'settlement', description: 'The settlement date, as a date serial number, truncated to an integer.'}, {name: 'maturity', description: 'The maturity date, as a date serial number, truncated to an integer; must be later than `settlement`.'}, {name: 'pr', description: 'The price per $100 face value; must be greater than 0.'}, {name: 'redemption', description: 'The redemption value per $100 face value; must be greater than 0.'}, {name: 'basis', description: 'The day-count basis, truncated to an integer: 0 (default) US 30/360, 1 actual/actual, 2 actual/360, 3 actual/365, 4 European 30/360.'}], + documentationUrl: 'https://hyperformula.handsontable.com/docs/guide/built-in-functions.html', + examples: ['=YIELDDISC(DATE(2008,2,16), DATE(2008,3,1), 99.795, 100, 2)', '=YIELDDISC(A1, B1, 99, 100)'], + }, } diff --git a/src/interpreter/plugin/DateTimePlugin.ts b/src/interpreter/plugin/DateTimePlugin.ts index fd1d3fad01..366c5a0db7 100644 --- a/src/interpreter/plugin/DateTimePlugin.ts +++ b/src/interpreter/plugin/DateTimePlugin.ts @@ -532,22 +532,8 @@ export class DateTimePlugin extends FunctionPlugin implements FunctionPluginType if (startDate > endDate) { [startDate, endDate] = [endDate, startDate] } - switch (mode) { - case 0: - return this.days360Core(startDate, endDate, false) / 360 - case 1: - return (endDate - startDate) / this.dateTimeHelper.yearLengthForBasis( - this.dateTimeHelper.numberToSimpleDate(startDate), - this.dateTimeHelper.numberToSimpleDate(endDate) - ) - case 2: - return (endDate - startDate) / 360 - case 3: - return (endDate - startDate) / 365 - case 4: - return this.days360Core(startDate, endDate, true) / 360 - } - throw new Error('Should not be reachable.') + const {dayCount, yearDays} = this.dateTimeHelper.dayCountByBasis(startDate, endDate, mode) + return dayCount / yearDays } ) } diff --git a/src/interpreter/plugin/FinancialPlugin.ts b/src/interpreter/plugin/FinancialPlugin.ts index c1f0c782a8..3dd36bf632 100644 --- a/src/interpreter/plugin/FinancialPlugin.ts +++ b/src/interpreter/plugin/FinancialPlugin.ts @@ -4,8 +4,9 @@ */ import {CellError, ErrorType} from '../../Cell' +import {offsetMonth, SimpleDate} from '../../DateTimeHelper' import {ErrorMessage} from '../../error-message' -import {ProcedureAst} from '../../parser' +import {AstNodeType, ProcedureAst} from '../../parser' import {InterpreterState} from '../InterpreterState' import { EmptyValue, @@ -19,6 +20,26 @@ import { import {SimpleRangeValue} from '../../SimpleRangeValue' import {FunctionArgumentType, FunctionPlugin, FunctionPluginTypecheck, ImplementedFunctions} from './FunctionPlugin' +/** + * The coupon schedule of a coupon function call, built once from its validated arguments. + */ +interface CouponSchedule { + /** The settlement date serial, truncated. */ + settlement: number, + /** The maturity date. */ + maturity: SimpleDate, + /** The number of coupons per year: 1, 2 or 4. */ + frequency: number, + /** The day-count basis: 0 to 4. */ + basis: number, + /** The number of coupon dates after settlement, up to and including maturity. */ + count: number, + /** The last coupon date on or before settlement. */ + previous: SimpleDate, + /** The first coupon date after settlement. */ + next: SimpleDate, +} + export class FinancialPlugin extends FunctionPlugin implements FunctionPluginTypecheck { public static implementedFunctions: ImplementedFunctions = { 'PMT': { @@ -240,6 +261,120 @@ export class FinancialPlugin extends FunctionPlugin implements FunctionPluginTyp ], returnNumberType: NumberType.NUMBER_PERCENT }, + 'DISC': { + method: 'disc', + parameters: [ + {argumentType: FunctionArgumentType.SCALAR}, + {argumentType: FunctionArgumentType.SCALAR}, + {argumentType: FunctionArgumentType.SCALAR}, + {argumentType: FunctionArgumentType.SCALAR}, + {argumentType: FunctionArgumentType.SCALAR, defaultValue: 0}, + ], + }, + 'INTRATE': { + method: 'intrate', + parameters: [ + {argumentType: FunctionArgumentType.SCALAR}, + {argumentType: FunctionArgumentType.SCALAR}, + {argumentType: FunctionArgumentType.SCALAR}, + {argumentType: FunctionArgumentType.SCALAR}, + {argumentType: FunctionArgumentType.SCALAR, defaultValue: 0}, + ], + }, + 'PRICEDISC': { + method: 'pricedisc', + parameters: [ + {argumentType: FunctionArgumentType.SCALAR}, + {argumentType: FunctionArgumentType.SCALAR}, + {argumentType: FunctionArgumentType.SCALAR}, + {argumentType: FunctionArgumentType.SCALAR}, + {argumentType: FunctionArgumentType.SCALAR, defaultValue: 0}, + ], + }, + 'RECEIVED': { + method: 'received', + parameters: [ + {argumentType: FunctionArgumentType.SCALAR}, + {argumentType: FunctionArgumentType.SCALAR}, + {argumentType: FunctionArgumentType.SCALAR}, + {argumentType: FunctionArgumentType.SCALAR}, + {argumentType: FunctionArgumentType.SCALAR, defaultValue: 0}, + ], + }, + 'YIELDDISC': { + method: 'yielddisc', + parameters: [ + {argumentType: FunctionArgumentType.SCALAR}, + {argumentType: FunctionArgumentType.SCALAR}, + {argumentType: FunctionArgumentType.SCALAR}, + {argumentType: FunctionArgumentType.SCALAR}, + {argumentType: FunctionArgumentType.SCALAR, defaultValue: 0}, + ], + }, + 'ACCRINTM': { + method: 'accrintm', + parameters: [ + {argumentType: FunctionArgumentType.SCALAR}, + {argumentType: FunctionArgumentType.SCALAR}, + {argumentType: FunctionArgumentType.SCALAR}, + {argumentType: FunctionArgumentType.SCALAR, defaultValue: 1000, emptyAsDefault: true}, + {argumentType: FunctionArgumentType.SCALAR, defaultValue: 0}, + ], + }, + 'COUPPCD': { + method: 'couppcd', + parameters: [ + {argumentType: FunctionArgumentType.SCALAR}, + {argumentType: FunctionArgumentType.SCALAR}, + {argumentType: FunctionArgumentType.SCALAR}, + {argumentType: FunctionArgumentType.SCALAR, defaultValue: 0}, + ], + }, + 'COUPNCD': { + method: 'coupncd', + parameters: [ + {argumentType: FunctionArgumentType.SCALAR}, + {argumentType: FunctionArgumentType.SCALAR}, + {argumentType: FunctionArgumentType.SCALAR}, + {argumentType: FunctionArgumentType.SCALAR, defaultValue: 0}, + ], + }, + 'COUPNUM': { + method: 'coupnum', + parameters: [ + {argumentType: FunctionArgumentType.SCALAR}, + {argumentType: FunctionArgumentType.SCALAR}, + {argumentType: FunctionArgumentType.SCALAR}, + {argumentType: FunctionArgumentType.SCALAR, defaultValue: 0}, + ], + }, + 'COUPDAYBS': { + method: 'coupdaybs', + parameters: [ + {argumentType: FunctionArgumentType.SCALAR}, + {argumentType: FunctionArgumentType.SCALAR}, + {argumentType: FunctionArgumentType.SCALAR}, + {argumentType: FunctionArgumentType.SCALAR, defaultValue: 0}, + ], + }, + 'COUPDAYSNC': { + method: 'coupdaysnc', + parameters: [ + {argumentType: FunctionArgumentType.SCALAR}, + {argumentType: FunctionArgumentType.SCALAR}, + {argumentType: FunctionArgumentType.SCALAR}, + {argumentType: FunctionArgumentType.SCALAR, defaultValue: 0}, + ], + }, + 'COUPDAYS': { + method: 'coupdays', + parameters: [ + {argumentType: FunctionArgumentType.SCALAR}, + {argumentType: FunctionArgumentType.SCALAR}, + {argumentType: FunctionArgumentType.SCALAR}, + {argumentType: FunctionArgumentType.SCALAR, defaultValue: 0}, + ], + }, 'FVSCHEDULE': { method: 'fvschedule', parameters: [ @@ -649,6 +784,239 @@ export class FinancialPlugin extends FunctionPlugin implements FunctionPluginTyp ) } + /** + * Corresponds to DISC(settlement, maturity, pr, redemption, [basis]). + * + * Returns the discount rate of a security. + * + * @param ast + * @param state + */ + public disc(ast: ProcedureAst, state: InterpreterState): InterpreterValue { + return this.discountedSecurity(ast, state, 'DISC', + (dayCount: number, yearDays: number, price: number, redemption: number) => (1 - price / redemption) * (yearDays / dayCount) + ) + } + + /** + * Corresponds to INTRATE(settlement, maturity, investment, redemption, [basis]). + * + * Returns the interest rate of a fully invested security. + * + * @param ast + * @param state + */ + public intrate(ast: ProcedureAst, state: InterpreterState): InterpreterValue { + return this.discountedSecurity(ast, state, 'INTRATE', + (dayCount: number, yearDays: number, investment: number, redemption: number) => (redemption - investment) / investment * (yearDays / dayCount) + ) + } + + /** + * Corresponds to PRICEDISC(settlement, maturity, discount, redemption, [basis]). + * + * Returns the price per $100 face value of a discounted security. + * + * @param ast + * @param state + */ + public pricedisc(ast: ProcedureAst, state: InterpreterState): InterpreterValue { + return this.discountedSecurity(ast, state, 'PRICEDISC', + (dayCount: number, yearDays: number, discount: number, redemption: number) => redemption - discount * redemption * (dayCount / yearDays) + ) + } + + /** + * Corresponds to RECEIVED(settlement, maturity, investment, discount, [basis]). + * + * Returns the amount received at maturity for a fully invested security. + * Returns #NUM! when the discount for the whole period reaches the investment, that is when + * `discount * dayCount / yearDays` is 1 or more. + * + * @param ast + * @param state + */ + public received(ast: ProcedureAst, state: InterpreterState): InterpreterValue { + return this.discountedSecurity(ast, state, 'RECEIVED', + (dayCount: number, yearDays: number, investment: number, discount: number) => { + const denominator = 1 - discount * (dayCount / yearDays) + if (denominator <= 0) { + return new CellError(ErrorType.NUM, ErrorMessage.ValueLarge) + } + return investment / denominator + } + ) + } + + /** + * Corresponds to YIELDDISC(settlement, maturity, pr, redemption, [basis]). + * + * Returns the annual yield of a discounted security. + * + * @param ast + * @param state + */ + public yielddisc(ast: ProcedureAst, state: InterpreterState): InterpreterValue { + return this.discountedSecurity(ast, state, 'YIELDDISC', + (dayCount: number, yearDays: number, price: number, redemption: number) => (redemption - price) / price * (yearDays / dayCount) + ) + } + + /** + * Corresponds to ACCRINTM(issue, settlement, rate, [par], [basis]). + * + * Returns the accrued interest of a security that pays interest at maturity. An empty or omitted `par` is 1000. + * Arguments are validated from left to right, as in Excel; an empty `issue`, `settlement` or `rate` is #N/A. + * `issue` equal to `settlement` gives 0. + * + * @param ast + * @param state + */ + public accrintm(ast: ProcedureAst, state: InterpreterState): InterpreterValue { + return this.runFunction(ast.args, state, this.metadata('ACCRINTM'), + (issueArg: InternalScalarValue, settlementArg: InternalScalarValue, rateArg: InternalScalarValue, parArg: InternalScalarValue, basisArg: InternalScalarValue) => { + if (this.isArgumentEmpty(ast, 0)) { + return new CellError(ErrorType.NA, ErrorMessage.EmptyArg) + } + const issue = this.coerceToSecurityDate(issueArg) + if (issue instanceof CellError) { + return issue + } + if (this.isArgumentEmpty(ast, 1)) { + return new CellError(ErrorType.NA, ErrorMessage.EmptyArg) + } + const settlement = this.coerceToSecurityDate(settlementArg) + if (settlement instanceof CellError) { + return settlement + } + if (this.isArgumentEmpty(ast, 2)) { + return new CellError(ErrorType.NA, ErrorMessage.EmptyArg) + } + const rate = this.strictNumber(rateArg) + if (rate instanceof CellError) { + return rate + } + const par = this.strictNumber(parArg) + if (par instanceof CellError) { + return par + } + const basis = this.coerceToDayCountBasis(basisArg) + if (basis instanceof CellError) { + return basis + } + if (issue > settlement) { + return new CellError(ErrorType.NUM, ErrorMessage.StartEndDate) + } + if (rate <= 0 || par <= 0) { + return new CellError(ErrorType.NUM, ErrorMessage.ValueSmall) + } + const {dayCount, yearDays} = this.dateTimeHelper.dayCountByBasis(issue, settlement, basis) + return par * rate * (dayCount / yearDays) + } + ) + } + + /** + * Corresponds to COUPPCD(settlement, maturity, frequency, [basis]). + * + * Returns the last coupon date on or before the settlement date, as a date serial number. + * + * @param ast + * @param state + */ + public couppcd(ast: ProcedureAst, state: InterpreterState): InterpreterValue { + return this.couponFunction(ast, state, 'COUPPCD', + (schedule: CouponSchedule) => this.dateTimeHelper.dateToNumber(schedule.previous) + ) + } + + /** + * Corresponds to COUPNCD(settlement, maturity, frequency, [basis]). + * + * Returns the first coupon date after the settlement date, as a date serial number. + * + * @param ast + * @param state + */ + public coupncd(ast: ProcedureAst, state: InterpreterState): InterpreterValue { + return this.couponFunction(ast, state, 'COUPNCD', + (schedule: CouponSchedule) => this.dateTimeHelper.dateToNumber(schedule.next) + ) + } + + /** + * Corresponds to COUPNUM(settlement, maturity, frequency, [basis]). + * + * Returns the number of coupons payable after the settlement date, up to and including the maturity date. + * + * @param ast + * @param state + */ + public coupnum(ast: ProcedureAst, state: InterpreterState): InterpreterValue { + return this.couponFunction(ast, state, 'COUPNUM', + (schedule: CouponSchedule) => schedule.count + ) + } + + /** + * Corresponds to COUPDAYBS(settlement, maturity, frequency, [basis]). + * + * Returns the number of days from the previous coupon date to the settlement date, counted by `basis`. + * + * @param ast + * @param state + */ + public coupdaybs(ast: ProcedureAst, state: InterpreterState): InterpreterValue { + return this.couponFunction(ast, state, 'COUPDAYBS', + (schedule: CouponSchedule) => this.daysBeforeSettlement(schedule) + ) + } + + /** + * Corresponds to COUPDAYSNC(settlement, maturity, frequency, [basis]). + * + * Returns the number of days from the settlement date to the next coupon date. Basis 0 counts it as Excel does: + * the 30/360 length of the coupon period minus COUPDAYBS, so it is not always the direct 30/360 count. + * + * @param ast + * @param state + */ + public coupdaysnc(ast: ProcedureAst, state: InterpreterState): InterpreterValue { + return this.couponFunction(ast, state, 'COUPDAYSNC', + (schedule: CouponSchedule) => { + if (schedule.basis === 0) { + return this.thirtyDayPeriodLength(schedule) - this.daysBeforeSettlement(schedule) + } + const next = this.dateTimeHelper.dateToNumber(schedule.next) + return this.dateTimeHelper.dayCountByBasis(schedule.settlement, next, schedule.basis).dayCount + } + ) + } + + /** + * Corresponds to COUPDAYS(settlement, maturity, frequency, [basis]). + * + * Returns the number of days in the coupon period that contains the settlement date: 360 / `frequency` for bases + * 0, 2 and 4, 365 / `frequency` for basis 3, and the actual length of the period, as Excel computes it, for basis 1. + * + * @param ast + * @param state + */ + public coupdays(ast: ProcedureAst, state: InterpreterState): InterpreterValue { + return this.couponFunction(ast, state, 'COUPDAYS', + (schedule: CouponSchedule) => { + switch (schedule.basis) { + case 1: + return this.actualCouponPeriodLength(schedule) + case 3: + return 365 / schedule.frequency + default: + return 360 / schedule.frequency + } + } + ) + } + public fvschedule(ast: ProcedureAst, state: InterpreterState): InterpreterValue { return this.runFunction(ast.args, state, this.metadata('FVSCHEDULE'), (value: number, ratios: SimpleRangeValue) => { @@ -841,6 +1209,314 @@ export class FinancialPlugin extends FunctionPlugin implements FunctionPluginTyp } ) } + + /** + * Runs DISC, INTRATE, PRICEDISC, RECEIVED and YIELDDISC, which share the arguments + * (settlement, maturity, first amount, second amount, [basis]). + * + * Validates in Excel's order: settlement and maturity, then basis, then the two amounts (errors and #VALUE!), + * then settlement before maturity and both amounts positive (#NUM!). An empty settlement, maturity or amount is #N/A + * in its own place of that order. Passes the day count and the days in the year between settlement and maturity to + * `calculate`. + * + * @param {ProcedureAst} ast - the function's AST + * @param {InterpreterState} state - the interpreter state + * @param {string} functionName - the function's id, used to look up its metadata + * @param {Function} calculate - computes the result from the day count, the days in the year and the two amounts + */ + private discountedSecurity( + ast: ProcedureAst, + state: InterpreterState, + functionName: string, + calculate: (dayCount: number, yearDays: number, firstAmount: number, secondAmount: number) => number | CellError, + ): InterpreterValue { + return this.runFunction(ast.args, state, this.metadata(functionName), + (settlementArg: InternalScalarValue, maturityArg: InternalScalarValue, firstAmountArg: InternalScalarValue, secondAmountArg: InternalScalarValue, basisArg: InternalScalarValue) => { + if (this.isArgumentEmpty(ast, 0)) { + return new CellError(ErrorType.NA, ErrorMessage.EmptyArg) + } + const settlement = this.coerceToSecurityDate(settlementArg) + if (settlement instanceof CellError) { + return settlement + } + if (this.isArgumentEmpty(ast, 1)) { + return new CellError(ErrorType.NA, ErrorMessage.EmptyArg) + } + const maturity = this.coerceToSecurityDate(maturityArg) + if (maturity instanceof CellError) { + return maturity + } + const basis = this.coerceToDayCountBasis(basisArg) + if (basis instanceof CellError) { + return basis + } + if (this.isArgumentEmpty(ast, 2)) { + return new CellError(ErrorType.NA, ErrorMessage.EmptyArg) + } + const firstAmount = this.strictNumber(firstAmountArg) + if (firstAmount instanceof CellError) { + return firstAmount + } + if (this.isArgumentEmpty(ast, 3)) { + return new CellError(ErrorType.NA, ErrorMessage.EmptyArg) + } + const secondAmount = this.strictNumber(secondAmountArg) + if (secondAmount instanceof CellError) { + return secondAmount + } + if (settlement >= maturity) { + return new CellError(ErrorType.NUM, ErrorMessage.StartEndDate) + } + if (firstAmount <= 0 || secondAmount <= 0) { + return new CellError(ErrorType.NUM, ErrorMessage.ValueSmall) + } + const {dayCount, yearDays} = this.dateTimeHelper.dayCountByBasis(settlement, maturity, basis) + return calculate(dayCount, yearDays, firstAmount, secondAmount) + } + ) + } + + /** + * Returns true when the argument at `index` is syntactically empty, as the first argument of `DISC(,A3,A4,A5)`. + * An omitted trailing argument is not empty. + * + * @param {ProcedureAst} ast - the function's AST + * @param {number} index - the argument's position, from 0 + */ + private isArgumentEmpty(ast: ProcedureAst, index: number): boolean { + return ast.args[index]?.type === AstNodeType.EMPTY + } + + /** + * Runs COUPPCD, COUPNCD, COUPNUM, COUPDAYBS, COUPDAYSNC and COUPDAYS, which share the arguments + * (settlement, maturity, frequency, [basis]). + * + * Validates in Excel's order: settlement, maturity, settlement before maturity, then an empty settlement, maturity + * or frequency argument (#N/A), then frequency and basis. An empty date is not compared with the other date. Builds + * the coupon schedule and passes it to `calculate`. A previous coupon date before the earliest supported date is + * #NUM!. + * + * @param {ProcedureAst} ast - the function's AST + * @param {InterpreterState} state - the interpreter state + * @param {string} functionName - the function's id, used to look up its metadata + * @param {Function} calculate - computes the result from the coupon schedule + */ + private couponFunction( + ast: ProcedureAst, + state: InterpreterState, + functionName: string, + calculate: (schedule: CouponSchedule) => number, + ): InterpreterValue { + const isArgumentEmpty = (index: number) => ast.args[index]?.type === AstNodeType.EMPTY + const isDateEmpty = isArgumentEmpty(0) || isArgumentEmpty(1) + const isFrequencyEmpty = isArgumentEmpty(2) + return this.runFunction(ast.args, state, this.metadata(functionName), + (settlementArg: InternalScalarValue, maturityArg: InternalScalarValue, frequencyArg: InternalScalarValue, basisArg: InternalScalarValue) => { + const settlement = this.coerceToSecurityDate(settlementArg) + if (settlement instanceof CellError) { + return settlement + } + const maturity = this.coerceToSecurityDate(maturityArg) + if (maturity instanceof CellError) { + return maturity + } + if (!isDateEmpty && settlement >= maturity) { + return new CellError(ErrorType.NUM, ErrorMessage.StartEndDate) + } + if (isDateEmpty || isFrequencyEmpty) { + return new CellError(ErrorType.NA, ErrorMessage.EmptyArg) + } + const frequency = this.coerceToCouponFrequency(frequencyArg) + if (frequency instanceof CellError) { + return frequency + } + const basis = this.coerceToDayCountBasis(basisArg) + if (basis instanceof CellError) { + return basis + } + const maturityDate = this.dateTimeHelper.numberToSimpleDate(maturity) + const count = this.couponsAfter(settlement, maturityDate, frequency) + const previous = this.couponDate(maturityDate, -count, frequency) + if (this.dateTimeHelper.dateToNumber(previous) < 0) { + return new CellError(ErrorType.NUM, ErrorMessage.DateBounds) + } + const next = this.couponDate(maturityDate, 1 - count, frequency) + return calculate({settlement, maturity: maturityDate, frequency, basis, count, previous, next}) + } + ) + } + + /** + * Converts a date argument of a securities function: a number as `strictNumber` accepts it, truncated to an integer. + * A date outside the supported range is #NUM!, and so is a negative value even when it truncates to 0, as in Excel. + */ + private coerceToSecurityDate(value: InternalScalarValue): number | CellError { + const dateNumber = this.strictNumber(value) + if (dateNumber instanceof CellError) { + return dateNumber + } + if (dateNumber < 0) { + return new CellError(ErrorType.NUM, ErrorMessage.DateBounds) + } + const date = Math.trunc(dateNumber) + if (this.dateTimeHelper.getWithinBounds(date) === undefined) { + return new CellError(ErrorType.NUM, ErrorMessage.DateBounds) + } + return date + } + + /** + * Converts the day-count `basis` argument of a securities function: a number as `strictNumber` accepts it, from 0 + * to less than 5, truncated to an integer. A negative value is #NUM! even when it truncates to 0, as in Excel. + */ + private coerceToDayCountBasis(value: InternalScalarValue): number | CellError { + const basis = this.strictNumber(value) + if (basis instanceof CellError) { + return basis + } + if (basis < 0) { + return new CellError(ErrorType.NUM, ErrorMessage.ValueSmall) + } + if (basis >= 5) { + return new CellError(ErrorType.NUM, ErrorMessage.ValueLarge) + } + return Math.trunc(basis) + } + + /** + * Converts the `frequency` argument of a coupon function: a number as `strictNumber` accepts it, truncated to an + * integer, that must be 1 (annual), 2 (semiannual) or 4 (quarterly). + */ + private coerceToCouponFrequency(value: InternalScalarValue): number | CellError { + const frequency = this.strictNumber(value) + if (frequency instanceof CellError) { + return frequency + } + const truncated = Math.trunc(frequency) + if (truncated !== 1 && truncated !== 2 && truncated !== 4) { + return new CellError(ErrorType.NUM, ErrorMessage.CouponFrequency) + } + return truncated + } + + /** + * Converts an argument the way Excel does for these functions: numbers, dates, numeric text and empty cells are + * accepted, while a boolean or the empty text is #VALUE!. Errors are passed on. + */ + private strictNumber(value: InternalScalarValue): number | CellError { + if (value instanceof CellError) { + return value + } + if (typeof value === 'boolean' || value === '') { + return new CellError(ErrorType.VALUE, ErrorMessage.NumberCoercion) + } + const coerced = this.coerceScalarToNumberOrError(value) + return coerced instanceof CellError ? coerced : getRawValue(coerced) + } + + /** + * Returns the coupon date `periods` coupon periods after `anchor` (before it when negative). The date is computed + * from the anchor directly, so the day of the month never drifts: if the anchor is the last day of its month, so is + * the result; otherwise the result keeps the anchor's day, clamped to the length of its month (leap years included). + */ + private couponDate(anchor: SimpleDate, periods: number, frequency: number): SimpleDate { + const shifted = offsetMonth(anchor, periods * 12 / frequency) + const lastDay = this.dateTimeHelper.daysInMonth(shifted.year, shifted.month) + const isAnchorMonthEnd = anchor.day === this.dateTimeHelper.daysInMonth(anchor.year, anchor.month) + return {...shifted, day: isAnchorMonthEnd ? lastDay : Math.min(anchor.day, lastDay)} + } + + /** + * Returns the number of coupon dates after `settlement`, up to and including `maturity`: the smallest k >= 1 for + * which the coupon date k periods before maturity is on or before settlement. + * + * Runs in constant time instead of stepping through the periods (a quarterly schedule over the whole supported date + * range has 32,400 of them): the whole periods in the months between settlement and maturity give a coupon date in + * settlement's month or later, and one period more gives a date before settlement's month. + */ + private couponsAfter(settlement: number, maturity: SimpleDate, frequency: number): number { + const settlementDate = this.dateTimeHelper.numberToSimpleDate(settlement) + const months = 12 * (maturity.year - settlementDate.year) + maturity.month - settlementDate.month + const periods = Math.floor(months * frequency / 12) + if (periods > 0 && this.dateTimeHelper.dateToNumber(this.couponDate(maturity, -periods, frequency)) <= settlement) { + return periods + } + return periods + 1 + } + + /** + * Returns the number of days from the previous coupon date to settlement, counted by the schedule's basis. + */ + private daysBeforeSettlement(schedule: CouponSchedule): number { + const previous = this.dateTimeHelper.dateToNumber(schedule.previous) + return this.dateTimeHelper.dayCountByBasis(previous, schedule.settlement, schedule.basis).dayCount + } + + /** + * Returns the 30/360 length of the coupon period, from the previous to the next coupon date, as Excel counts it for + * COUPDAYSNC with basis 0: day 31 and the last day of February count as day 30 at both ends. The two dates are + * exactly 12 / `frequency` months apart, so only their days change the length (for example 179 days from + * 28 February to 29 August). + */ + private thirtyDayPeriodLength(schedule: CouponSchedule): number { + return 360 / schedule.frequency + this.thirtyDayMonthDay(schedule.next) - this.thirtyDayMonthDay(schedule.previous) + } + + /** + * Returns the day of the month of `date` in a 30-day month: 30 for day 31 and for the last day of February, + * otherwise the day itself. + */ + private thirtyDayMonthDay(date: SimpleDate): number { + const isFebruaryEnd = date.month === 2 && date.day === this.dateTimeHelper.daysInMonth(date.year, 2) + return date.day === 31 || isFebruaryEnd ? 30 : date.day + } + + /** + * Returns the length in days of the coupon period that contains settlement, as Excel's COUPDAYS computes it for + * basis 1. It is not always the next coupon date minus the previous one. + * + * 1. Step back from maturity one period at a time, each date keeping the previous date's day clamped to its month + * (so 31 March, 30 September, 30 March: the day can only go down). Stop at the last date whose next step back is + * on or before settlement: the period end. + * 2. Step back once from the period end with the end-of-month rule of `couponDate`: the period start. + * 3. If the period start is still after settlement, it becomes the period end and step 2 repeats. This happens at + * most once. + * + * The result is the period end minus the period start. Constant time: the period end is the drifted date + * `count - 1` periods back, or `count - 2` when that one is already on or before settlement. + */ + private actualCouponPeriodLength(schedule: CouponSchedule): number { + const {settlement, maturity, frequency, count} = schedule + let periodEnd = this.driftedCouponDate(maturity, count - 1, frequency) + if (count >= 2 && this.dateTimeHelper.dateToNumber(periodEnd) <= settlement) { + periodEnd = this.driftedCouponDate(maturity, count - 2, frequency) + } + let periodStart = this.couponDate(periodEnd, -1, frequency) + if (this.dateTimeHelper.dateToNumber(periodStart) > settlement) { + periodEnd = periodStart + periodStart = this.couponDate(periodEnd, -1, frequency) + } + return this.dateTimeHelper.dateToNumber(periodEnd) - this.dateTimeHelper.dateToNumber(periodStart) + } + + /** + * Returns `maturity` stepped back `periods` coupon periods, each step keeping the previous date's day clamped to the + * length of its month, so the day can drift down (31 March, 30 September, 30 March). + * + * Constant time: the drifted day is the smallest of maturity's day and the lengths of the months visited, and those + * months repeat every year, so after `2 * frequency` steps (two Februaries, at least one in a non-leap year) the + * day no longer changes. + */ + private driftedCouponDate(maturity: SimpleDate, periods: number, frequency: number): SimpleDate { + const monthsPerPeriod = 12 / frequency + let day = maturity.day + for (let step = 1; step <= Math.min(periods, 2 * frequency); step++) { + const visited = offsetMonth(maturity, -step * monthsPerPeriod) + day = Math.min(day, this.dateTimeHelper.daysInMonth(visited.year, visited.month)) + } + const target = offsetMonth(maturity, -periods * monthsPerPeriod) + return {...target, day: Math.min(day, this.dateTimeHelper.daysInMonth(target.year, target.month))} + } } function pmtCore(rate: number, periods: number, present: number, future: number, type: number): number { diff --git a/src/license/functionCapabilities.ts b/src/license/functionCapabilities.ts index a26733ec5f..65e7586a7e 100644 --- a/src/license/functionCapabilities.ts +++ b/src/license/functionCapabilities.ts @@ -75,28 +75,32 @@ const FUNCTION_GROUPS: ReadonlyMap = new Map([ * gated at all, which is exactly the treatment a custom function should get. */ const UNGROUPED_FUNCTIONS = [ - 'ACOSH', 'ACOT', 'ACOTH', 'ARABIC', 'ASINH', 'ATANH', 'AVEDEV', 'BASE', 'BESSELI', 'BESSELJ', 'BESSELK', + 'ACCRINTM', 'ACOSH', 'ACOT', 'ACOTH', 'ARABIC', 'ASINH', 'ATANH', 'AVEDEV', 'BASE', 'BESSELI', 'BESSELJ', + 'BESSELK', 'BESSELY', 'BETA.DIST', 'BETA.INV', 'BIN2DEC', 'BIN2HEX', 'BIN2OCT', 'BINOM.DIST', 'BINOM.INV', 'BITAND', 'BITLSHIFT', 'BITOR', 'BITRSHIFT', 'BITXOR', 'CEILING.MATH', 'CEILING.PRECISE', 'CHISQ.DIST', 'CHISQ.DIST.RT', 'CHISQ.INV', 'CHISQ.INV.RT', 'CHISQ.TEST', 'COMBIN', 'COMBINA', 'COMPLEX', - 'CONFIDENCE.NORM', 'CONFIDENCE.T', 'CORREL', 'COSH', 'COT', 'COTH', 'COUNTUNIQUE', 'COVARIANCE.P', + 'CONFIDENCE.NORM', 'CONFIDENCE.T', 'CORREL', 'COSH', 'COT', 'COTH', 'COUNTUNIQUE', 'COUPDAYBS', + 'COUPDAYS', 'COUPDAYSNC', 'COUPNCD', 'COUPNUM', 'COUPPCD', 'COVARIANCE.P', 'COVARIANCE.S', 'CSC', 'CSCH', 'CUMIPMT', 'CUMPRINC', 'DAVERAGE', 'DB', 'DCOUNT', 'DCOUNTA', 'DDB', - 'DEC2BIN', 'DEC2OCT', 'DECIMAL', 'DEGREES', 'DELTA', 'DEVSQ', 'DGET', 'DMAX', 'DMIN', 'DOLLARDE', + 'DEC2BIN', 'DEC2OCT', 'DECIMAL', 'DEGREES', 'DELTA', 'DEVSQ', 'DGET', 'DISC', 'DMAX', 'DMIN', 'DOLLARDE', 'DOLLARFR', 'DPRODUCT', 'DSTDEV', 'DSTDEVP', 'DSUM', 'DVAR', 'DVARP', 'EFFECT', 'ERF', 'ERFC', 'EXPON.DIST', 'F.DIST', 'F.DIST.RT', 'F.INV', 'F.INV.RT', 'F.TEST', 'FACT', 'FACTDOUBLE', 'FISHER', 'FISHERINV', 'FLOOR.MATH', 'FLOOR.PRECISE', 'FORMULATEXT', 'FVSCHEDULE', 'GAMMA', 'GAMMA.DIST', 'GAMMA.INV', 'GAMMALN', 'GAUSS', 'GCD', 'GEOMEAN', 'HARMEAN', 'HEX2BIN', 'HEX2OCT', 'HYPGEOM.DIST', 'IMABS', 'IMAGINARY', 'IMARGUMENT', 'IMCONJUGATE', 'IMCOS', 'IMCOSH', 'IMCOT', 'IMCSC', 'IMCSCH', 'IMDIV', 'IMEXP', 'IMLN', 'IMLOG10', 'IMLOG2', 'IMPOWER', 'IMPRODUCT', 'IMREAL', 'IMSEC', 'IMSECH', 'IMSIN', - 'IMSINH', 'IMSQRT', 'IMSUB', 'IMSUM', 'IMTAN', 'INTERVAL', 'ISBINARY', 'ISFORMULA', 'ISNONTEXT', 'ISPMT', + 'IMSINH', 'IMSQRT', 'IMSUB', 'IMSUM', 'IMTAN', 'INTERVAL', 'INTRATE', 'ISBINARY', 'ISFORMULA', + 'ISNONTEXT', 'ISPMT', 'ISREF', 'LCM', 'LOG10', 'LOGNORM.DIST', 'LOGNORM.INV', 'MAXA', 'MAXPOOL', 'MEDIANPOOL', 'MINA', 'MIRR', 'MMULT', 'MULTINOMIAL', 'NEGBINOM.DIST', 'NETWORKDAYS.INTL', 'NOMINAL', 'NORM.DIST', 'NORM.INV', 'NORM.S.DIST', 'NORM.S.INV', 'NPER', 'OCT2BIN', 'OCT2DEC', 'OCT2HEX', 'PDURATION', 'PERCENTILE.EXC', - 'PHI', 'POISSON.DIST', 'QUARTILE.EXC', 'QUARTILE.INC', 'RADIANS', 'ROMAN', 'RRI', 'RSQ', 'SEC', 'SECH', + 'PHI', 'POISSON.DIST', 'PRICEDISC', 'QUARTILE.EXC', 'QUARTILE.INC', 'RADIANS', 'RECEIVED', 'ROMAN', 'RRI', + 'RSQ', 'SEC', 'SECH', 'SERIESSUM', 'SHEET', 'SHEETS', 'SINH', 'SKEW', 'SKEW.P', 'SLOPE', 'SPLIT', 'SQRTPI', 'STANDARDIZE', 'STEYX', 'SUMX2MY2', 'SUMX2PY2', 'SYD', 'T.DIST', 'T.DIST.2T', 'T.DIST.RT', 'T.INV', 'T.INV.2T', 'T.TEST', 'TANH', 'TBILLEQ', 'TBILLPRICE', 'TBILLYIELD', 'TDIST', 'TIMEVALUE', 'UNICODE', 'VARA', 'VARPA', - 'WEIBULL.DIST', 'WORKDAY.INTL', 'Z.TEST', + 'WEIBULL.DIST', 'WORKDAY.INTL', 'YIELDDISC', 'Z.TEST', ] /**