diff --git a/README.md b/README.md index 6b87469..1245c62 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,12 @@ func main() { fmt.Println(err) return } + defer func() { + // Close the spreadsheet. + if err := f.Close(); err != nil { + fmt.Println(err) + } + }() // Get value from cell by given worksheet name and axis. cell, err := f.GetCellValue("Sheet1", "B2") if err != nil { @@ -96,10 +102,6 @@ func main() { } fmt.Println() } - // Close the spreadsheet. - if err = f.Close(); err != nil { - fmt.Println(err) - } } ``` @@ -184,6 +186,12 @@ func main() { fmt.Println(err) return } + defer func() { + // Close the spreadsheet. + if err := f.Close(); err != nil { + fmt.Println(err) + } + }() // Insert a picture. if err := f.AddPicture("Sheet1", "A2", "image.png", ""); err != nil { fmt.Println(err) @@ -207,10 +215,6 @@ func main() { if err = f.Save(); err != nil { fmt.Println(err) } - // Close the spreadsheet. - if err = f.Close(); err != nil { - fmt.Println(err) - } } ``` diff --git a/README_zh.md b/README_zh.md index 3b90eec..b946bb3 100644 --- a/README_zh.md +++ b/README_zh.md @@ -77,6 +77,12 @@ func main() { fmt.Println(err) return } + defer func() { + // 关闭工作簿 + if err := f.Close(); err != nil { + fmt.Println(err) + } + }() // 获取工作表中指定单元格的值 cell, err := f.GetCellValue("Sheet1", "B2") if err != nil { @@ -96,10 +102,6 @@ func main() { } fmt.Println() } - // 关闭工作簿 - if err = f.Close(); err != nil { - fmt.Println(err) - } } ``` @@ -184,6 +186,12 @@ func main() { fmt.Println(err) return } + defer func() { + // 关闭工作簿 + if err := f.Close(); err != nil { + fmt.Println(err) + } + }() // 插入图片 if err := f.AddPicture("Sheet1", "A2", "image.png", ""); err != nil { fmt.Println(err) @@ -207,10 +215,6 @@ func main() { if err = f.Save(); err != nil { fmt.Println(err) } - // 关闭工作簿 - if err = f.Close(); err != nil { - fmt.Println(err) - } } ``` diff --git a/calc.go b/calc.go index 59131d8..1357ad4 100644 --- a/calc.go +++ b/calc.go @@ -516,6 +516,7 @@ type formulaFuncs struct { // POISSON // POWER // PPMT +// PRICE // PRICEDISC // PRICEMAT // PRODUCT @@ -9723,6 +9724,40 @@ func (fn *formulaFuncs) COUPDAYSNC(argsList *list.List) formulaArg { return newNumberFormulaArg(coupdays(settlement, ncd, basis)) } +// coupons is an implementation of the formula function COUPNCD and COUPPCD. +func (fn *formulaFuncs) coupons(name string, arg formulaArg) formulaArg { + settlement := timeFromExcelTime(arg.List[0].Number, false) + maturity := timeFromExcelTime(arg.List[1].Number, false) + maturityDays := (maturity.Year()-settlement.Year())*12 + (int(maturity.Month()) - int(settlement.Month())) + coupon := 12 / int(arg.List[2].Number) + mod := maturityDays % coupon + year := settlement.Year() + month := int(settlement.Month()) + if mod == 0 && settlement.Day() >= maturity.Day() { + month += coupon + } else { + month += mod + } + if name != "COUPNCD" { + month -= coupon + } + if month > 11 { + year += 1 + month -= 12 + } else if month < 0 { + year -= 1 + month += 12 + } + day, lastDay := maturity.Day(), time.Date(year, time.Month(month), 1, 0, 0, 0, 0, time.UTC) + days := getDaysInMonth(lastDay.Year(), int(lastDay.Month())) + if getDaysInMonth(maturity.Year(), int(maturity.Month())) == maturity.Day() { + day = days + } else if day > 27 && day > days { + day = days + } + return newNumberFormulaArg(daysBetween(excelMinTime1900.Unix(), makeDate(year, time.Month(month), day)) + 1) +} + // COUPNCD function calculates the number of coupons payable, between a // security's settlement date and maturity date, rounded up to the nearest // whole coupon. The syntax of the function is: @@ -9734,16 +9769,7 @@ func (fn *formulaFuncs) COUPNCD(argsList *list.List) formulaArg { if args.Type != ArgList { return args } - settlement := timeFromExcelTime(args.List[0].Number, false) - maturity := timeFromExcelTime(args.List[1].Number, false) - ncd := time.Date(settlement.Year(), maturity.Month(), maturity.Day(), 0, 0, 0, 0, time.UTC) - if ncd.After(settlement) { - ncd = ncd.AddDate(-1, 0, 0) - } - for !ncd.After(settlement) { - ncd = ncd.AddDate(0, 12/int(args.List[2].Number), 0) - } - return newNumberFormulaArg(daysBetween(excelMinTime1900.Unix(), makeDate(ncd.Year(), ncd.Month(), ncd.Day())) + 1) + return fn.coupons("COUPNCD", args) } // COUPNUM function calculates the number of coupons payable, between a @@ -9773,18 +9799,7 @@ func (fn *formulaFuncs) COUPPCD(argsList *list.List) formulaArg { if args.Type != ArgList { return args } - settlement := timeFromExcelTime(args.List[0].Number, false) - maturity := timeFromExcelTime(args.List[1].Number, false) - date, years := maturity, settlement.Year()-maturity.Year() - date = date.AddDate(years, 0, 0) - if settlement.After(date) { - date = date.AddDate(1, 0, 0) - } - month := -12 / args.List[2].Number - for date.After(settlement) { - date = date.AddDate(0, int(month), 0) - } - return newNumberFormulaArg(daysBetween(excelMinTime1900.Unix(), makeDate(date.Year(), date.Month(), date.Day())) + 1) + return fn.coupons("COUPPCD", args) } // CUMIPMT function calculates the cumulative interest paid on a loan or @@ -10643,6 +10658,81 @@ func (fn *formulaFuncs) PPMT(argsList *list.List) formulaArg { return fn.ipmt("PPMT", argsList) } +// price is an implementation of the formula function PRICE. +func (fn *formulaFuncs) price(settlement, maturity, rate, yld, redemption, frequency, basis formulaArg) formulaArg { + if basis.Number < 0 || basis.Number > 4 { + return newErrorFormulaArg(formulaErrorNUM, "invalid basis") + } + argsList := list.New().Init() + argsList.PushBack(settlement) + argsList.PushBack(maturity) + argsList.PushBack(frequency) + argsList.PushBack(basis) + e := fn.COUPDAYS(argsList) + dsc := fn.COUPDAYSNC(argsList).Number / e.Number + n := fn.COUPNUM(argsList) + a := fn.COUPDAYBS(argsList) + ret := redemption.Number / math.Pow(1+yld.Number/frequency.Number, n.Number-1+dsc) + ret -= 100 * rate.Number / frequency.Number * a.Number / e.Number + t1 := 100 * rate.Number / frequency.Number + t2 := 1 + yld.Number/frequency.Number + for k := 0.0; k < n.Number; k++ { + ret += t1 / math.Pow(t2, k+dsc) + } + return newNumberFormulaArg(ret) +} + +// PRICE function calculates the price, per $100 face value of a security that +// pays periodic interest. The syntax of the function is: +// +// PRICE(settlement,maturity,rate,yld,redemption,frequency,[basis]) +// +func (fn *formulaFuncs) PRICE(argsList *list.List) formulaArg { + if argsList.Len() != 6 && argsList.Len() != 7 { + return newErrorFormulaArg(formulaErrorVALUE, "PRICE requires 6 or 7 arguments") + } + args := fn.prepareDataValueArgs(2, argsList) + if args.Type != ArgList { + return args + } + settlement, maturity := args.List[0], args.List[1] + rate := argsList.Front().Next().Next().Value.(formulaArg).ToNumber() + if rate.Type != ArgNumber { + return rate + } + if rate.Number < 0 { + return newErrorFormulaArg(formulaErrorNUM, "PRICE requires rate >= 0") + } + yld := argsList.Front().Next().Next().Next().Value.(formulaArg).ToNumber() + if yld.Type != ArgNumber { + return yld + } + if yld.Number < 0 { + return newErrorFormulaArg(formulaErrorNUM, "PRICE requires yld >= 0") + } + redemption := argsList.Front().Next().Next().Next().Next().Value.(formulaArg).ToNumber() + if redemption.Type != ArgNumber { + return redemption + } + if redemption.Number <= 0 { + return newErrorFormulaArg(formulaErrorNUM, "PRICE requires redemption > 0") + } + frequency := argsList.Front().Next().Next().Next().Next().Next().Value.(formulaArg).ToNumber() + if frequency.Type != ArgNumber { + return frequency + } + if !validateFrequency(frequency.Number) { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + basis := newNumberFormulaArg(0) + if argsList.Len() == 7 { + if basis = argsList.Back().Value.(formulaArg).ToNumber(); basis.Type != ArgNumber { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + } + return fn.price(settlement, maturity, rate, yld, redemption, frequency, basis) +} + // PRICEDISC function calculates the price, per $100 face value of a // discounted security. The syntax of the function is: // diff --git a/calc_test.go b/calc_test.go index 8402ab1..894b154 100644 --- a/calc_test.go +++ b/calc_test.go @@ -1429,10 +1429,13 @@ func TestCalcCellValue(t *testing.T) { "=COUPDAYS(\"01/01/2011\",\"10/25/2012\",4,1)": "92", // COUPDAYSNC "=COUPDAYSNC(\"01/01/2011\",\"10/25/2012\",4)": "24", + "=COUPDAYSNC(\"04/01/2012\",\"03/31/2020\",2)": "179", // COUPNCD "=COUPNCD(\"01/01/2011\",\"10/25/2012\",4)": "40568", "=COUPNCD(\"01/01/2011\",\"10/25/2012\",4,0)": "40568", "=COUPNCD(\"10/25/2011\",\"01/01/2012\",4)": "40909", + "=COUPNCD(\"04/01/2012\",\"03/31/2020\",2)": "41182", + "=COUPNCD(\"01/01/2000\",\"08/30/2001\",2)": "36585", // COUPNUM "=COUPNUM(\"01/01/2011\",\"10/25/2012\",4)": "8", "=COUPNUM(\"01/01/2011\",\"10/25/2012\",4,0)": "8", @@ -1497,6 +1500,10 @@ func TestCalcCellValue(t *testing.T) { // PMT "=PMT(0,8,0,5000,1)": "-625", "=PMT(0.035/4,8,0,5000,1)": "-600.8520271804658", + // PRICE + "=PRICE(\"04/01/2012\",\"02/01/2020\",12%,10%,100,2)": "110.65510517844305", + "=PRICE(\"04/01/2012\",\"02/01/2020\",12%,10%,100,2,4)": "110.65510517844305", + "=PRICE(\"04/01/2012\",\"03/31/2020\",12%,10%,100,2)": "110.83448359321572", // PPMT "=PPMT(0.05/12,2,60,50000)": "-738.2918003208238", "=PPMT(0.035/4,2,8,0,5000,1)": "-606.1094824182949", @@ -2951,6 +2958,20 @@ func TestCalcCellValue(t *testing.T) { "=PMT(0,0,\"\",0,0)": "strconv.ParseFloat: parsing \"\": invalid syntax", "=PMT(0,0,0,\"\",0)": "strconv.ParseFloat: parsing \"\": invalid syntax", "=PMT(0,0,0,0,\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax", + // PRICE + "=PRICE()": "PRICE requires 6 or 7 arguments", + "=PRICE(\"\",\"02/01/2020\",12%,10%,100,2,4)": "#VALUE!", + "=PRICE(\"04/01/2012\",\"\",12%,10%,100,2,4)": "#VALUE!", + "=PRICE(\"04/01/2012\",\"02/01/2020\",\"\",10%,100,2,4)": "strconv.ParseFloat: parsing \"\": invalid syntax", + "=PRICE(\"04/01/2012\",\"02/01/2020\",12%,\"\",100,2,4)": "strconv.ParseFloat: parsing \"\": invalid syntax", + "=PRICE(\"04/01/2012\",\"02/01/2020\",12%,10%,\"\",2,4)": "strconv.ParseFloat: parsing \"\": invalid syntax", + "=PRICE(\"04/01/2012\",\"02/01/2020\",12%,10%,100,\"\",4)": "strconv.ParseFloat: parsing \"\": invalid syntax", + "=PRICE(\"04/01/2012\",\"02/01/2020\",-1,10%,100,2,4)": "PRICE requires rate >= 0", + "=PRICE(\"04/01/2012\",\"02/01/2020\",12%,-1,100,2,4)": "PRICE requires yld >= 0", + "=PRICE(\"04/01/2012\",\"02/01/2020\",12%,10%,0,2,4)": "PRICE requires redemption > 0", + "=PRICE(\"04/01/2012\",\"02/01/2020\",12%,10%,100,2,\"\")": "#NUM!", + "=PRICE(\"04/01/2012\",\"02/01/2020\",12%,10%,100,3,4)": "#NUM!", + "=PRICE(\"04/01/2012\",\"02/01/2020\",12%,10%,100,2,5)": "invalid basis", // PPMT "=PPMT()": "PPMT requires at least 4 arguments", "=PPMT(0,0,0,0,0,0,0)": "PPMT allows at most 6 arguments", diff --git a/cell.go b/cell.go index 41a9517..e1c7803 100644 --- a/cell.go +++ b/cell.go @@ -354,7 +354,7 @@ func (f *File) SetCellStr(sheet, axis, value string) error { // table. func (f *File) setCellString(value string) (t string, v string) { if len(value) > TotalCellChars { - value = value[0:TotalCellChars] + value = value[:TotalCellChars] } t = "s" v = strconv.Itoa(f.setSharedString(value)) @@ -381,7 +381,7 @@ func (f *File) setSharedString(val string) int { // setCellStr provides a function to set string type to cell. func setCellStr(value string) (t string, v string, ns xml.Attr) { if len(value) > TotalCellChars { - value = value[0:TotalCellChars] + value = value[:TotalCellChars] } if len(value) > 0 { prefix, suffix := value[0], value[len(value)-1] diff --git a/crypt.go b/crypt.go index 24ac7ec..ae39bba 100644 --- a/crypt.go +++ b/crypt.go @@ -297,7 +297,7 @@ func encryptionMechanism(buffer []byte) (mechanism string, err error) { err = ErrUnknownEncryptMechanism return } - versionMajor, versionMinor := binary.LittleEndian.Uint16(buffer[0:2]), binary.LittleEndian.Uint16(buffer[2:4]) + versionMajor, versionMinor := binary.LittleEndian.Uint16(buffer[:2]), binary.LittleEndian.Uint16(buffer[2:4]) if versionMajor == 4 && versionMinor == 4 { mechanism = "agile" return @@ -600,7 +600,7 @@ func createIV(blockKey interface{}, encryption Encryption) ([]byte, error) { tmp := make([]byte, 0x36) iv = append(iv, tmp...) } else if len(iv) > encryptedKey.BlockSize { - iv = iv[0:encryptedKey.BlockSize] + iv = iv[:encryptedKey.BlockSize] } return iv, nil } diff --git a/lib.go b/lib.go index 535161a..ccb09ac 100644 --- a/lib.go +++ b/lib.go @@ -526,7 +526,7 @@ func bytesReplace(s, old, new []byte, n int) []byte { } w += copy(s[w:], s[i:]) - return s[0:w] + return s[:w] } // genSheetPasswd provides a method to generate password for worksheet diff --git a/picture.go b/picture.go index 332c639..2956ff1 100644 --- a/picture.go +++ b/picture.go @@ -481,14 +481,20 @@ func (f *File) getSheetRelationshipsTargetByID(sheet, rID string) string { } // GetPicture provides a function to get picture base name and raw content -// embed in XLSX by given worksheet and cell name. This function returns the -// file name in XLSX and file contents as []byte data types. For example: +// embed in spreadsheet by given worksheet and cell name. This function +// returns the file name in spreadsheet and file contents as []byte data +// types. For example: // // f, err := excelize.OpenFile("Book1.xlsx") // if err != nil { // fmt.Println(err) // return // } +// defer func() { +// if err := f.Close(); err != nil { +// fmt.Println(err) +// } +// }() // file, raw, err := f.GetPicture("Sheet1", "A2") // if err != nil { // fmt.Println(err) @@ -497,9 +503,6 @@ func (f *File) getSheetRelationshipsTargetByID(sheet, rID string) string { // if err := ioutil.WriteFile(file, raw, 0644); err != nil { // fmt.Println(err) // } -// if err = f.Close(); err != nil { -// fmt.Println(err) -// } // func (f *File) GetPicture(sheet, cell string) (string, []byte, error) { col, row, err := CellNameToCoordinates(cell) diff --git a/sheet.go b/sheet.go index 5738ced..1aa378b 100644 --- a/sheet.go +++ b/sheet.go @@ -234,7 +234,7 @@ func trimCell(column []xlsxC) []xlsxC { i++ } } - return col[0:i] + return col[:i] } // setContentTypes provides a function to read and update property of contents @@ -452,12 +452,14 @@ func (f *File) GetSheetIndex(name string) int { // if err != nil { // return // } +// defer func() { +// if err := f.Close(); err != nil { +// fmt.Println(err) +// } +// }() // for index, name := range f.GetSheetMap() { // fmt.Println(index, name) // } -// if err = f.Close(); err != nil { -// fmt.Println(err) -// } // func (f *File) GetSheetMap() map[int]string { wb := f.workbookReader() diff --git a/styles.go b/styles.go index 0ae9e51..183211b 100644 --- a/styles.go +++ b/styles.go @@ -3137,7 +3137,7 @@ func ThemeColor(baseColor string, tint float64) string { if tint == 0 { return "FF" + baseColor } - r, _ := strconv.ParseUint(baseColor[0:2], 16, 64) + r, _ := strconv.ParseUint(baseColor[:2], 16, 64) g, _ := strconv.ParseUint(baseColor[2:4], 16, 64) b, _ := strconv.ParseUint(baseColor[4:6], 16, 64) var h, s, l float64 diff --git a/table.go b/table.go index 620cf20..a6959a4 100644 --- a/table.go +++ b/table.go @@ -446,7 +446,7 @@ func (f *File) parseFilterExpression(expression string, tokens []string) ([]int, if re { conditional = 1 } - expression1, token1, err := f.parseFilterTokens(expression, tokens[0:3]) + expression1, token1, err := f.parseFilterTokens(expression, tokens[:3]) if err != nil { return expressions, t, err }