Skip to content

Commit

Permalink
- Formatted cell data support, fix issue qax-os#48;
Browse files Browse the repository at this point in the history
- Function `SetCellValue()` support `time.Time` data type parameter, relate issue qax-os#49;
- go doc and go test updated
  • Loading branch information
xuri committed May 5, 2017
1 parent 7f30a6c commit 8fbab47
Show file tree
Hide file tree
Showing 6 changed files with 357 additions and 13 deletions.
24 changes: 19 additions & 5 deletions cell.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ import (
"strings"
)

// GetCellValue provides function to get value from cell by given sheet index
// and axis in XLSX file.
// GetCellValue provides function to get formatted value from cell by given
// sheet index and axis in XLSX file. If it is possible to apply a format to the
// cell value, it will do so, if not then an error will be returned, along with
// the raw value of the cell.
func (f *File) GetCellValue(sheet, axis string) string {
xlsx := f.workSheetReader(sheet)
axis = strings.ToUpper(axis)
Expand Down Expand Up @@ -44,17 +46,29 @@ func (f *File) GetCellValue(sheet, axis string) string {
xlsxSI := 0
xlsxSI, _ = strconv.Atoi(r.V)
xml.Unmarshal([]byte(f.readXML("xl/sharedStrings.xml")), &shardStrings)
return shardStrings.SI[xlsxSI].T
return f.formattedValue(r.S, shardStrings.SI[xlsxSI].T)
case "str":
return r.V
return f.formattedValue(r.S, r.V)
default:
return r.V
return f.formattedValue(r.S, r.V)
}
}
}
return ""
}

// formattedValue provides function to returns a value after formatted. If it is
// possible to apply a format to the cell value, it will do so, if not then an
// error will be returned, along with the raw value of the cell.
func (f *File) formattedValue(s int, v string) string {
if s == 0 {
return v
}
var styleSheet xlsxStyleSheet
xml.Unmarshal([]byte(f.readXML("xl/styles.xml")), &styleSheet)
return builtInNumFmtFunc[styleSheet.CellXfs.Xf[s].NumFmtID](styleSheet.CellXfs.Xf[s].NumFmtID, v)
}

// GetCellFormula provides function to get formula from cell by given sheet
// index and axis in XLSX file.
func (f *File) GetCellFormula(sheet, axis string) string {
Expand Down
119 changes: 119 additions & 0 deletions date.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package excelize

import (
"math"
"time"
)

// timeLocationUTC defined the UTC time location.
var timeLocationUTC, _ = time.LoadLocation("UTC")

// timeToUTCTime provides function to convert time to UTC time.
func timeToUTCTime(t time.Time) time.Time {
return time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), timeLocationUTC)
}

// timeToExcelTime provides function to convert time to Excel time.
func timeToExcelTime(t time.Time) float64 {
return float64(t.UnixNano())/8.64e13 + 25569.0
}

// shiftJulianToNoon provides function to process julian date to noon.
func shiftJulianToNoon(julianDays, julianFraction float64) (float64, float64) {
switch {
case -0.5 < julianFraction && julianFraction < 0.5:
julianFraction += 0.5
case julianFraction >= 0.5:
julianDays++
julianFraction -= 0.5
case julianFraction <= -0.5:
julianDays--
julianFraction += 1.5
}
return julianDays, julianFraction
}

// fractionOfADay provides function to return the integer values for hour,
// minutes, seconds and nanoseconds that comprised a given fraction of a day.
// values would round to 1 us.
func fractionOfADay(fraction float64) (hours, minutes, seconds, nanoseconds int) {

const (
c1us = 1e3
c1s = 1e9
c1day = 24 * 60 * 60 * c1s
)

frac := int64(c1day*fraction + c1us/2)
nanoseconds = int((frac%c1s)/c1us) * c1us
frac /= c1s
seconds = int(frac % 60)
frac /= 60
minutes = int(frac % 60)
hours = int(frac / 60)
return
}

// julianDateToGregorianTime provides function to convert julian date to
// gregorian time.
func julianDateToGregorianTime(part1, part2 float64) time.Time {
part1I, part1F := math.Modf(part1)
part2I, part2F := math.Modf(part2)
julianDays := part1I + part2I
julianFraction := part1F + part2F
julianDays, julianFraction = shiftJulianToNoon(julianDays, julianFraction)
day, month, year := doTheFliegelAndVanFlandernAlgorithm(int(julianDays))
hours, minutes, seconds, nanoseconds := fractionOfADay(julianFraction)
return time.Date(year, time.Month(month), day, hours, minutes, seconds, nanoseconds, time.UTC)
}

// By this point generations of programmers have repeated the algorithm sent to
// the editor of "Communications of the ACM" in 1968 (published in CACM, volume
// 11, number 10, October 1968, p.657). None of those programmers seems to have
// found it necessary to explain the constants or variable names set out by
// Henry F. Fliegel and Thomas C. Van Flandern. Maybe one day I'll buy that
// jounal and expand an explanation here - that day is not today.
func doTheFliegelAndVanFlandernAlgorithm(jd int) (day, month, year int) {
l := jd + 68569
n := (4 * l) / 146097
l = l - (146097*n+3)/4
i := (4000 * (l + 1)) / 1461001
l = l - (1461*i)/4 + 31
j := (80 * l) / 2447
d := l - (2447*j)/80
l = j / 11
m := j + 2 - (12 * l)
y := 100*(n-49) + i + l
return d, m, y
}

// timeFromExcelTime provides function to convert an excelTime representation
// (stored as a floating point number) to a time.Time.
func timeFromExcelTime(excelTime float64, date1904 bool) time.Time {
var date time.Time
var intPart = int64(excelTime)
// Excel uses Julian dates prior to March 1st 1900, and Gregorian
// thereafter.
if intPart <= 61 {
const OFFSET1900 = 15018.0
const OFFSET1904 = 16480.0
const MJD0 float64 = 2400000.5
var date time.Time
if date1904 {
date = julianDateToGregorianTime(MJD0, excelTime+OFFSET1904)
} else {
date = julianDateToGregorianTime(MJD0, excelTime+OFFSET1900)
}
return date
}
var floatPart = excelTime - float64(intPart)
var dayNanoSeconds float64 = 24 * 60 * 60 * 1000 * 1000 * 1000
if date1904 {
date = time.Date(1904, 1, 1, 0, 0, 0, 0, time.UTC)
} else {
date = time.Date(1899, 12, 30, 0, 0, 0, 0, time.UTC)
}
durationDays := time.Duration(intPart) * time.Hour * 24
durationPart := time.Duration(dayNanoSeconds * floatPart)
return date.Add(durationDays).Add(durationPart)
}
9 changes: 8 additions & 1 deletion excelize.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ import (
"archive/zip"
"bytes"
"encoding/xml"
"fmt"
"io"
"io/ioutil"
"os"
"strconv"
"strings"
"time"
)

// File define a populated XLSX file struct.
Expand Down Expand Up @@ -84,8 +86,13 @@ func (f *File) SetCellValue(sheet, axis string, value interface{}) {
f.SetCellStr(sheet, axis, t)
case []byte:
f.SetCellStr(sheet, axis, string(t))
default:
case time.Time:
f.SetCellDefault(sheet, axis, strconv.FormatFloat(float64(timeToExcelTime(timeToUTCTime(value.(time.Time)))), 'f', -1, 32))
f.SetCellStyle(sheet, axis, axis, `{"number_format": 22}`)
case nil:
f.SetCellStr(sheet, axis, "")
default:
f.SetCellStr(sheet, axis, fmt.Sprintf("%v", value))
}
}

Expand Down
33 changes: 29 additions & 4 deletions excelize_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"io/ioutil"
"strconv"
"testing"
"time"
)

func TestOpenFile(t *testing.T) {
Expand Down Expand Up @@ -66,6 +67,8 @@ func TestOpenFile(t *testing.T) {
xlsx.SetCellValue("Sheet2", "F2", float32(42))
xlsx.SetCellValue("Sheet2", "F2", float64(42))
xlsx.SetCellValue("Sheet2", "G2", nil)
xlsx.SetCellValue("Sheet2", "G3", uint8(8))
xlsx.SetCellValue("Sheet2", "G4", time.Now())
// Test completion column.
xlsx.SetCellValue("Sheet2", "M2", nil)
// Test read cell value with given axis large than exists row.
Expand Down Expand Up @@ -322,18 +325,40 @@ func TestSetCellStyleBorder(t *testing.T) {
}
}

func TestSetCellStyleFill(t *testing.T) {
func TestSetCellStyleNumberFormat(t *testing.T) {
xlsx, err := OpenFile("./test/Workbook_2.xlsx")
if err != nil {
t.Log(err)
}
xlsx.SetCellValue("Sheet1", "N23", 42920.5)
// Test only set fill and number format for a cell.
err = xlsx.SetCellStyle("Sheet1", "N23", "N23", `{"fill":{"type":"gradient","color":["#FFFFFF","#E0EBF5"],"shading":4},"number_format":22}`)
col := []string{"L", "M", "N", "O", "P"}
data := []int{0, 1, 2, 3, 4, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49}
value := []string{"37947.7500001", "-37947.7500001", "0.007", "2.1", "String"}
for i, v := range value {
for k, d := range data {
c := col[i] + strconv.Itoa(k+1)
var val float64
val, err = strconv.ParseFloat(v, 64)
if err != nil {
xlsx.SetCellValue("Sheet2", c, v)
} else {
xlsx.SetCellValue("Sheet2", c, val)
}
err := xlsx.SetCellStyle("Sheet2", c, c, `{"fill":{"type":"gradient","color":["#FFFFFF","#E0EBF5"],"shading":5},"number_format": `+strconv.Itoa(d)+`}`)
if err != nil {
t.Log(err)
}
t.Log(xlsx.GetCellValue("Sheet2", c))
}
}
err = xlsx.Save()
if err != nil {
t.Log(err)
}
err = xlsx.SetCellStyle("Sheet1", "N24", "N24", `{"fill":{"type":"gradient","color":["#FFFFFF","#E0EBF5"],"shading":5},"number_format":23}`)
}

func TestSetCellStyleFill(t *testing.T) {
xlsx, err := OpenFile("./test/Workbook_2.xlsx")
if err != nil {
t.Log(err)
}
Expand Down
6 changes: 3 additions & 3 deletions rows.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,10 +141,10 @@ func (xlsx *xlsxC) getValueFrom(f *File, d *xlsxSST) (string, error) {
}
return value, nil
}
return d.SI[xlsxSI].T, nil
return f.formattedValue(xlsx.S, d.SI[xlsxSI].T), nil
case "str":
return xlsx.V, nil
return f.formattedValue(xlsx.S, xlsx.V), nil
default:
return xlsx.V, nil
return f.formattedValue(xlsx.S, xlsx.V), nil
}
}
Loading

0 comments on commit 8fbab47

Please sign in to comment.