Skip to content

Commit bc3c7d5

Browse files
committed
ref qax-os#65: new formula function PRICE
- fix COUPPCD result accuracy issue - update close spreadsheet example in documentation and README
1 parent bda8e7f commit bc3c7d5

File tree

11 files changed

+178
-54
lines changed

11 files changed

+178
-54
lines changed

README.md

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,12 @@ func main() {
7777
fmt.Println(err)
7878
return
7979
}
80+
defer func() {
81+
// Close the spreadsheet.
82+
if err := f.Close(); err != nil {
83+
fmt.Println(err)
84+
}
85+
}()
8086
// Get value from cell by given worksheet name and axis.
8187
cell, err := f.GetCellValue("Sheet1", "B2")
8288
if err != nil {
@@ -96,10 +102,6 @@ func main() {
96102
}
97103
fmt.Println()
98104
}
99-
// Close the spreadsheet.
100-
if err = f.Close(); err != nil {
101-
fmt.Println(err)
102-
}
103105
}
104106
```
105107

@@ -184,6 +186,12 @@ func main() {
184186
fmt.Println(err)
185187
return
186188
}
189+
defer func() {
190+
// Close the spreadsheet.
191+
if err := f.Close(); err != nil {
192+
fmt.Println(err)
193+
}
194+
}()
187195
// Insert a picture.
188196
if err := f.AddPicture("Sheet1", "A2", "image.png", ""); err != nil {
189197
fmt.Println(err)
@@ -207,10 +215,6 @@ func main() {
207215
if err = f.Save(); err != nil {
208216
fmt.Println(err)
209217
}
210-
// Close the spreadsheet.
211-
if err = f.Close(); err != nil {
212-
fmt.Println(err)
213-
}
214218
}
215219
```
216220

README_zh.md

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,12 @@ func main() {
7777
fmt.Println(err)
7878
return
7979
}
80+
defer func() {
81+
// 关闭工作簿
82+
if err := f.Close(); err != nil {
83+
fmt.Println(err)
84+
}
85+
}()
8086
// 获取工作表中指定单元格的值
8187
cell, err := f.GetCellValue("Sheet1", "B2")
8288
if err != nil {
@@ -96,10 +102,6 @@ func main() {
96102
}
97103
fmt.Println()
98104
}
99-
// 关闭工作簿
100-
if err = f.Close(); err != nil {
101-
fmt.Println(err)
102-
}
103105
}
104106
```
105107

@@ -184,6 +186,12 @@ func main() {
184186
fmt.Println(err)
185187
return
186188
}
189+
defer func() {
190+
// 关闭工作簿
191+
if err := f.Close(); err != nil {
192+
fmt.Println(err)
193+
}
194+
}()
187195
// 插入图片
188196
if err := f.AddPicture("Sheet1", "A2", "image.png", ""); err != nil {
189197
fmt.Println(err)
@@ -207,10 +215,6 @@ func main() {
207215
if err = f.Save(); err != nil {
208216
fmt.Println(err)
209217
}
210-
// 关闭工作簿
211-
if err = f.Close(); err != nil {
212-
fmt.Println(err)
213-
}
214218
}
215219
```
216220

calc.go

Lines changed: 112 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -516,6 +516,7 @@ type formulaFuncs struct {
516516
// POISSON
517517
// POWER
518518
// PPMT
519+
// PRICE
519520
// PRICEDISC
520521
// PRICEMAT
521522
// PRODUCT
@@ -9723,6 +9724,40 @@ func (fn *formulaFuncs) COUPDAYSNC(argsList *list.List) formulaArg {
97239724
return newNumberFormulaArg(coupdays(settlement, ncd, basis))
97249725
}
97259726

9727+
// coupons is an implementation of the formula function COUPNCD and COUPPCD.
9728+
func (fn *formulaFuncs) coupons(name string, arg formulaArg) formulaArg {
9729+
settlement := timeFromExcelTime(arg.List[0].Number, false)
9730+
maturity := timeFromExcelTime(arg.List[1].Number, false)
9731+
maturityDays := (maturity.Year()-settlement.Year())*12 + (int(maturity.Month()) - int(settlement.Month()))
9732+
coupon := 12 / int(arg.List[2].Number)
9733+
mod := maturityDays % coupon
9734+
year := settlement.Year()
9735+
month := int(settlement.Month())
9736+
if mod == 0 && settlement.Day() >= maturity.Day() {
9737+
month += coupon
9738+
} else {
9739+
month += mod
9740+
}
9741+
if name != "COUPNCD" {
9742+
month -= coupon
9743+
}
9744+
if month > 11 {
9745+
year += 1
9746+
month -= 12
9747+
} else if month < 0 {
9748+
year -= 1
9749+
month += 12
9750+
}
9751+
day, lastDay := maturity.Day(), time.Date(year, time.Month(month), 1, 0, 0, 0, 0, time.UTC)
9752+
days := getDaysInMonth(lastDay.Year(), int(lastDay.Month()))
9753+
if getDaysInMonth(maturity.Year(), int(maturity.Month())) == maturity.Day() {
9754+
day = days
9755+
} else if day > 27 && day > days {
9756+
day = days
9757+
}
9758+
return newNumberFormulaArg(daysBetween(excelMinTime1900.Unix(), makeDate(year, time.Month(month), day)) + 1)
9759+
}
9760+
97269761
// COUPNCD function calculates the number of coupons payable, between a
97279762
// security's settlement date and maturity date, rounded up to the nearest
97289763
// whole coupon. The syntax of the function is:
@@ -9734,16 +9769,7 @@ func (fn *formulaFuncs) COUPNCD(argsList *list.List) formulaArg {
97349769
if args.Type != ArgList {
97359770
return args
97369771
}
9737-
settlement := timeFromExcelTime(args.List[0].Number, false)
9738-
maturity := timeFromExcelTime(args.List[1].Number, false)
9739-
ncd := time.Date(settlement.Year(), maturity.Month(), maturity.Day(), 0, 0, 0, 0, time.UTC)
9740-
if ncd.After(settlement) {
9741-
ncd = ncd.AddDate(-1, 0, 0)
9742-
}
9743-
for !ncd.After(settlement) {
9744-
ncd = ncd.AddDate(0, 12/int(args.List[2].Number), 0)
9745-
}
9746-
return newNumberFormulaArg(daysBetween(excelMinTime1900.Unix(), makeDate(ncd.Year(), ncd.Month(), ncd.Day())) + 1)
9772+
return fn.coupons("COUPNCD", args)
97479773
}
97489774

97499775
// COUPNUM function calculates the number of coupons payable, between a
@@ -9773,18 +9799,7 @@ func (fn *formulaFuncs) COUPPCD(argsList *list.List) formulaArg {
97739799
if args.Type != ArgList {
97749800
return args
97759801
}
9776-
settlement := timeFromExcelTime(args.List[0].Number, false)
9777-
maturity := timeFromExcelTime(args.List[1].Number, false)
9778-
date, years := maturity, settlement.Year()-maturity.Year()
9779-
date = date.AddDate(years, 0, 0)
9780-
if settlement.After(date) {
9781-
date = date.AddDate(1, 0, 0)
9782-
}
9783-
month := -12 / args.List[2].Number
9784-
for date.After(settlement) {
9785-
date = date.AddDate(0, int(month), 0)
9786-
}
9787-
return newNumberFormulaArg(daysBetween(excelMinTime1900.Unix(), makeDate(date.Year(), date.Month(), date.Day())) + 1)
9802+
return fn.coupons("COUPPCD", args)
97889803
}
97899804

97909805
// CUMIPMT function calculates the cumulative interest paid on a loan or
@@ -10643,6 +10658,81 @@ func (fn *formulaFuncs) PPMT(argsList *list.List) formulaArg {
1064310658
return fn.ipmt("PPMT", argsList)
1064410659
}
1064510660

10661+
// price is an implementation of the formula function PRICE.
10662+
func (fn *formulaFuncs) price(settlement, maturity, rate, yld, redemption, frequency, basis formulaArg) formulaArg {
10663+
if basis.Number < 0 || basis.Number > 4 {
10664+
return newErrorFormulaArg(formulaErrorNUM, "invalid basis")
10665+
}
10666+
argsList := list.New().Init()
10667+
argsList.PushBack(settlement)
10668+
argsList.PushBack(maturity)
10669+
argsList.PushBack(frequency)
10670+
argsList.PushBack(basis)
10671+
e := fn.COUPDAYS(argsList)
10672+
dsc := fn.COUPDAYSNC(argsList).Number / e.Number
10673+
n := fn.COUPNUM(argsList)
10674+
a := fn.COUPDAYBS(argsList)
10675+
ret := redemption.Number / math.Pow(1+yld.Number/frequency.Number, n.Number-1+dsc)
10676+
ret -= 100 * rate.Number / frequency.Number * a.Number / e.Number
10677+
t1 := 100 * rate.Number / frequency.Number
10678+
t2 := 1 + yld.Number/frequency.Number
10679+
for k := 0.0; k < n.Number; k++ {
10680+
ret += t1 / math.Pow(t2, k+dsc)
10681+
}
10682+
return newNumberFormulaArg(ret)
10683+
}
10684+
10685+
// PRICE function calculates the price, per $100 face value of a security that
10686+
// pays periodic interest. The syntax of the function is:
10687+
//
10688+
// PRICE(settlement,maturity,rate,yld,redemption,frequency,[basis])
10689+
//
10690+
func (fn *formulaFuncs) PRICE(argsList *list.List) formulaArg {
10691+
if argsList.Len() != 6 && argsList.Len() != 7 {
10692+
return newErrorFormulaArg(formulaErrorVALUE, "PRICE requires 6 or 7 arguments")
10693+
}
10694+
args := fn.prepareDataValueArgs(2, argsList)
10695+
if args.Type != ArgList {
10696+
return args
10697+
}
10698+
settlement, maturity := args.List[0], args.List[1]
10699+
rate := argsList.Front().Next().Next().Value.(formulaArg).ToNumber()
10700+
if rate.Type != ArgNumber {
10701+
return rate
10702+
}
10703+
if rate.Number < 0 {
10704+
return newErrorFormulaArg(formulaErrorNUM, "PRICE requires rate >= 0")
10705+
}
10706+
yld := argsList.Front().Next().Next().Next().Value.(formulaArg).ToNumber()
10707+
if yld.Type != ArgNumber {
10708+
return yld
10709+
}
10710+
if yld.Number < 0 {
10711+
return newErrorFormulaArg(formulaErrorNUM, "PRICE requires yld >= 0")
10712+
}
10713+
redemption := argsList.Front().Next().Next().Next().Next().Value.(formulaArg).ToNumber()
10714+
if redemption.Type != ArgNumber {
10715+
return redemption
10716+
}
10717+
if redemption.Number <= 0 {
10718+
return newErrorFormulaArg(formulaErrorNUM, "PRICE requires redemption > 0")
10719+
}
10720+
frequency := argsList.Front().Next().Next().Next().Next().Next().Value.(formulaArg).ToNumber()
10721+
if frequency.Type != ArgNumber {
10722+
return frequency
10723+
}
10724+
if !validateFrequency(frequency.Number) {
10725+
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
10726+
}
10727+
basis := newNumberFormulaArg(0)
10728+
if argsList.Len() == 7 {
10729+
if basis = argsList.Back().Value.(formulaArg).ToNumber(); basis.Type != ArgNumber {
10730+
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
10731+
}
10732+
}
10733+
return fn.price(settlement, maturity, rate, yld, redemption, frequency, basis)
10734+
}
10735+
1064610736
// PRICEDISC function calculates the price, per $100 face value of a
1064710737
// discounted security. The syntax of the function is:
1064810738
//

calc_test.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1429,10 +1429,13 @@ func TestCalcCellValue(t *testing.T) {
14291429
"=COUPDAYS(\"01/01/2011\",\"10/25/2012\",4,1)": "92",
14301430
// COUPDAYSNC
14311431
"=COUPDAYSNC(\"01/01/2011\",\"10/25/2012\",4)": "24",
1432+
"=COUPDAYSNC(\"04/01/2012\",\"03/31/2020\",2)": "179",
14321433
// COUPNCD
14331434
"=COUPNCD(\"01/01/2011\",\"10/25/2012\",4)": "40568",
14341435
"=COUPNCD(\"01/01/2011\",\"10/25/2012\",4,0)": "40568",
14351436
"=COUPNCD(\"10/25/2011\",\"01/01/2012\",4)": "40909",
1437+
"=COUPNCD(\"04/01/2012\",\"03/31/2020\",2)": "41182",
1438+
"=COUPNCD(\"01/01/2000\",\"08/30/2001\",2)": "36585",
14361439
// COUPNUM
14371440
"=COUPNUM(\"01/01/2011\",\"10/25/2012\",4)": "8",
14381441
"=COUPNUM(\"01/01/2011\",\"10/25/2012\",4,0)": "8",
@@ -1497,6 +1500,10 @@ func TestCalcCellValue(t *testing.T) {
14971500
// PMT
14981501
"=PMT(0,8,0,5000,1)": "-625",
14991502
"=PMT(0.035/4,8,0,5000,1)": "-600.8520271804658",
1503+
// PRICE
1504+
"=PRICE(\"04/01/2012\",\"02/01/2020\",12%,10%,100,2)": "110.65510517844305",
1505+
"=PRICE(\"04/01/2012\",\"02/01/2020\",12%,10%,100,2,4)": "110.65510517844305",
1506+
"=PRICE(\"04/01/2012\",\"03/31/2020\",12%,10%,100,2)": "110.83448359321572",
15001507
// PPMT
15011508
"=PPMT(0.05/12,2,60,50000)": "-738.2918003208238",
15021509
"=PPMT(0.035/4,2,8,0,5000,1)": "-606.1094824182949",
@@ -2951,6 +2958,20 @@ func TestCalcCellValue(t *testing.T) {
29512958
"=PMT(0,0,\"\",0,0)": "strconv.ParseFloat: parsing \"\": invalid syntax",
29522959
"=PMT(0,0,0,\"\",0)": "strconv.ParseFloat: parsing \"\": invalid syntax",
29532960
"=PMT(0,0,0,0,\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax",
2961+
// PRICE
2962+
"=PRICE()": "PRICE requires 6 or 7 arguments",
2963+
"=PRICE(\"\",\"02/01/2020\",12%,10%,100,2,4)": "#VALUE!",
2964+
"=PRICE(\"04/01/2012\",\"\",12%,10%,100,2,4)": "#VALUE!",
2965+
"=PRICE(\"04/01/2012\",\"02/01/2020\",\"\",10%,100,2,4)": "strconv.ParseFloat: parsing \"\": invalid syntax",
2966+
"=PRICE(\"04/01/2012\",\"02/01/2020\",12%,\"\",100,2,4)": "strconv.ParseFloat: parsing \"\": invalid syntax",
2967+
"=PRICE(\"04/01/2012\",\"02/01/2020\",12%,10%,\"\",2,4)": "strconv.ParseFloat: parsing \"\": invalid syntax",
2968+
"=PRICE(\"04/01/2012\",\"02/01/2020\",12%,10%,100,\"\",4)": "strconv.ParseFloat: parsing \"\": invalid syntax",
2969+
"=PRICE(\"04/01/2012\",\"02/01/2020\",-1,10%,100,2,4)": "PRICE requires rate >= 0",
2970+
"=PRICE(\"04/01/2012\",\"02/01/2020\",12%,-1,100,2,4)": "PRICE requires yld >= 0",
2971+
"=PRICE(\"04/01/2012\",\"02/01/2020\",12%,10%,0,2,4)": "PRICE requires redemption > 0",
2972+
"=PRICE(\"04/01/2012\",\"02/01/2020\",12%,10%,100,2,\"\")": "#NUM!",
2973+
"=PRICE(\"04/01/2012\",\"02/01/2020\",12%,10%,100,3,4)": "#NUM!",
2974+
"=PRICE(\"04/01/2012\",\"02/01/2020\",12%,10%,100,2,5)": "invalid basis",
29542975
// PPMT
29552976
"=PPMT()": "PPMT requires at least 4 arguments",
29562977
"=PPMT(0,0,0,0,0,0,0)": "PPMT allows at most 6 arguments",

cell.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -354,7 +354,7 @@ func (f *File) SetCellStr(sheet, axis, value string) error {
354354
// table.
355355
func (f *File) setCellString(value string) (t string, v string) {
356356
if len(value) > TotalCellChars {
357-
value = value[0:TotalCellChars]
357+
value = value[:TotalCellChars]
358358
}
359359
t = "s"
360360
v = strconv.Itoa(f.setSharedString(value))
@@ -381,7 +381,7 @@ func (f *File) setSharedString(val string) int {
381381
// setCellStr provides a function to set string type to cell.
382382
func setCellStr(value string) (t string, v string, ns xml.Attr) {
383383
if len(value) > TotalCellChars {
384-
value = value[0:TotalCellChars]
384+
value = value[:TotalCellChars]
385385
}
386386
if len(value) > 0 {
387387
prefix, suffix := value[0], value[len(value)-1]

crypt.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -297,7 +297,7 @@ func encryptionMechanism(buffer []byte) (mechanism string, err error) {
297297
err = ErrUnknownEncryptMechanism
298298
return
299299
}
300-
versionMajor, versionMinor := binary.LittleEndian.Uint16(buffer[0:2]), binary.LittleEndian.Uint16(buffer[2:4])
300+
versionMajor, versionMinor := binary.LittleEndian.Uint16(buffer[:2]), binary.LittleEndian.Uint16(buffer[2:4])
301301
if versionMajor == 4 && versionMinor == 4 {
302302
mechanism = "agile"
303303
return
@@ -600,7 +600,7 @@ func createIV(blockKey interface{}, encryption Encryption) ([]byte, error) {
600600
tmp := make([]byte, 0x36)
601601
iv = append(iv, tmp...)
602602
} else if len(iv) > encryptedKey.BlockSize {
603-
iv = iv[0:encryptedKey.BlockSize]
603+
iv = iv[:encryptedKey.BlockSize]
604604
}
605605
return iv, nil
606606
}

lib.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -526,7 +526,7 @@ func bytesReplace(s, old, new []byte, n int) []byte {
526526
}
527527

528528
w += copy(s[w:], s[i:])
529-
return s[0:w]
529+
return s[:w]
530530
}
531531

532532
// genSheetPasswd provides a method to generate password for worksheet

picture.go

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -481,14 +481,20 @@ func (f *File) getSheetRelationshipsTargetByID(sheet, rID string) string {
481481
}
482482

483483
// GetPicture provides a function to get picture base name and raw content
484-
// embed in XLSX by given worksheet and cell name. This function returns the
485-
// file name in XLSX and file contents as []byte data types. For example:
484+
// embed in spreadsheet by given worksheet and cell name. This function
485+
// returns the file name in spreadsheet and file contents as []byte data
486+
// types. For example:
486487
//
487488
// f, err := excelize.OpenFile("Book1.xlsx")
488489
// if err != nil {
489490
// fmt.Println(err)
490491
// return
491492
// }
493+
// defer func() {
494+
// if err := f.Close(); err != nil {
495+
// fmt.Println(err)
496+
// }
497+
// }()
492498
// file, raw, err := f.GetPicture("Sheet1", "A2")
493499
// if err != nil {
494500
// fmt.Println(err)
@@ -497,9 +503,6 @@ func (f *File) getSheetRelationshipsTargetByID(sheet, rID string) string {
497503
// if err := ioutil.WriteFile(file, raw, 0644); err != nil {
498504
// fmt.Println(err)
499505
// }
500-
// if err = f.Close(); err != nil {
501-
// fmt.Println(err)
502-
// }
503506
//
504507
func (f *File) GetPicture(sheet, cell string) (string, []byte, error) {
505508
col, row, err := CellNameToCoordinates(cell)

0 commit comments

Comments
 (0)