try to use a new formatter for date and decimal

This commit is contained in:
Liu Ming
2017-07-26 17:56:49 +08:00
parent ce73093973
commit d3f14f70d8
9 changed files with 408 additions and 12 deletions

98
format/date.go Normal file
View File

@@ -0,0 +1,98 @@
package format
import (
"math"
"time"
)
const MJD_0 float64 = 2400000.5
const MJD_JD2000 float64 = 51544.5
func shiftJulianToNoon(julianDays, julianFraction float64) (float64, float64) {
switch {
case -0.5 < julianFraction && julianFraction < 0.5:
julianFraction += 0.5
case julianFraction >= 0.5:
julianDays += 1
julianFraction -= 0.5
case julianFraction <= -0.5:
julianDays -= 1
julianFraction += 1.5
}
return julianDays, julianFraction
}
// Return the integer values for hour, minutes, seconds and
// nanoseconds that comprised a given fraction of a day.
func fractionOfADay(fraction float64) (hours, minutes, seconds, nanoseconds int) {
f := 5184000000000000 * fraction
nanoseconds = int(math.Mod(f, 1000000000))
f = f / 1000000000
seconds = int(math.Mod(f, 60))
f = f / 3600
minutes = int(math.Mod(f, 60))
f = f / 60
hours = int(f)
return hours, minutes, seconds, nanoseconds
}
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
}
// 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 = 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
var date time.Time
if date1904 {
date = julianDateToGregorianTime(MJD_0+OFFSET1904, excelTime)
} else {
date = julianDateToGregorianTime(MJD_0+OFFSET1900, excelTime)
}
return date
}
var floatPart float64 = 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)
}

106
format/formatter.go Normal file
View File

@@ -0,0 +1,106 @@
package format
import (
"errors"
"fmt"
)
var InvalidTimeFormat = errors.New("invalid time format")
type Formatter struct {
typ int
Items []ItemFormatter
}
func (f *Formatter) Format(val float64, date1904 bool) string {
var gf string
for _, i := range f.Items {
itemFormatString, _ := i.translateToGolangFormat()
gf += itemFormatString
}
fmt.Println(f)
if f.typ == DATEFORMAT {
t := TimeFromExcelTime(val, date1904)
return t.Format(gf)
} else {
return fmt.Sprintf("%f", val)
}
}
type ItemFormatter interface {
translateToGolangFormat() (string, error)
setOriginal(string)
}
type basicFormatter struct {
origin string
}
func (self *basicFormatter) translateToGolangFormat() (string, error) {
return self.origin, nil
}
func (self *basicFormatter) setOriginal(o string) {
self.origin = o
}
func (self *basicFormatter) String() string {
return fmt.Sprintf("basic formatter as (%s)", self.origin)
}
type YearFormatter struct {
basicFormatter
}
func (y *YearFormatter) translateToGolangFormat() (string, error) {
switch len(y.origin) {
case 4:
return "2006", nil
case 2:
return "06", nil
default:
return "", InvalidTimeFormat
}
}
func (y *YearFormatter) String() string {
return fmt.Sprintf("year formatter as (%s)", y.origin)
}
type MonthFormatter struct {
basicFormatter
}
func (self *MonthFormatter) translateToGolangFormat() (string, error) {
switch len(self.origin) {
case 2:
return "01", nil
case 1:
return "1", nil
default:
return "", InvalidTimeFormat
}
}
func (self *MonthFormatter) String() string {
return fmt.Sprintf("month formatter as (%s)", self.origin)
}
type DayFormatter struct {
basicFormatter
}
func (self *DayFormatter) translateToGolangFormat() (string, error) {
switch len(self.origin) {
case 2:
return "02", nil
case 1:
return "2", nil
default:
return "", InvalidTimeFormat
}
}
func (self *DayFormatter) String() string {
return fmt.Sprintf("day formatter as (%s)", self.origin)
}

13
format/lang.go Normal file
View File

@@ -0,0 +1,13 @@
package format
const T_YEAR_MARK string = "T_YEAR_MARK"
const T_MONTH_MARK string = "T_MONTH_MARK"
const T_DAY_MARK string = "T_DAY_MARK"
const T_RAW_MARK string = "T_RAW_MARK"
const T_EOF string = "T_EOF"
const T_STRING_MARK string = "T_STRING_MARK"
const T_DECIMAL_MARK string = "T_DECIMAL_MARK"
const T_COMMA_MARK string = "T_COMMA_MARK"

150
format/lexer.go Normal file
View File

@@ -0,0 +1,150 @@
package format
import (
"strconv"
"strings"
"unicode/utf8"
)
const (
DATEFORMAT = iota
DECIMALFORMAT = iota
)
// LexToken holds is a (type, value) array.
type LexToken [3]string
// EOF character
var EOF string = "+++EOF+++"
// lexerState represents the state of the scanner
// as a function that returns the next state.
type lexerState func(*lexer) lexerState
// run lexes the input by executing state functions until
// the state is nil.
func (l *lexer) Run() {
for state := l.initialState; state != nil; {
state = state(l)
}
}
// Lexer creates a new scanner for the input string.
func Lexer(input string) (*lexer, []LexToken) {
l := &lexer{
input: input,
tokens: make([]LexToken, 0),
lineno: 1,
}
l.initialState = initLexerState
l.Run()
return l, l.tokens
}
// lexer holds the state of the scanner.
type lexer struct {
input string // the string being scanned.
start int // start position of this item.
pos int // current position in the input.
width int // width of last rune read from input.
tokens []LexToken // scanned items.
initialState lexerState
typ int
lineno int
}
// next returns the next rune in the input.
func (l *lexer) next() string {
var r rune
if l.pos >= len(l.input) {
l.width = 0
return EOF
}
r, l.width = utf8.DecodeRuneInString(l.input[l.pos:])
l.pos += l.width
return string(r)
}
// ignore skips over the pending input before this point.
func (l *lexer) ignore() {
l.start = l.pos
}
// backup steps back one rune.
// Can be called only once per call of next.
func (l *lexer) backup() {
l.pos -= l.width
}
// acceptRun consumes a run of runes from the valid set.
func (l *lexer) acceptRun(valid string) {
for strings.Index(valid, l.next()) >= 0 {
}
l.backup()
}
// acceptRun consumes a run of runes from the valid set.
func (l *lexer) acceptUntil(marker string) {
for r := l.next(); r != EOF && strings.Index(marker, r) < 0; r = l.next() {
}
}
// emit passes an item back to the client.
func (l *lexer) emit(t string) {
l.tokens = append(l.tokens, LexToken{t, l.input[l.start:l.pos], strconv.Itoa(l.lineno)})
l.start = l.pos
}
// emit passes an item back to the client.
func (l *lexer) emitRaw() {
if l.pos-l.start > 1 {
l.tokens = append(l.tokens, LexToken{T_RAW_MARK, l.input[l.start : l.pos-1], strconv.Itoa(l.lineno)})
l.start = l.pos - 1
}
}
// emit passes an item back to the client.
func (l *lexer) emitWithoutEnd(t string) {
if l.pos-l.start > 1 {
l.tokens = append(l.tokens, LexToken{t, l.input[l.start : l.pos-1], strconv.Itoa(l.lineno)})
l.start = l.pos
}
}
// initialState is the starting point for the
// scanner. It scans through each character and decides
// which state to create for the lexer. lexerState == nil
// is exit scanner.
func initLexerState(l *lexer) lexerState {
for r := l.next(); r != EOF; r = l.next() {
if r == "y" {
l.emitRaw()
l.acceptRun("y")
l.emit(T_YEAR_MARK)
} else if r == "m" {
l.emitRaw()
l.acceptRun("m")
l.emit(T_MONTH_MARK)
} else if r == "d" {
l.emitRaw()
l.acceptRun("d")
l.emit(T_DAY_MARK)
} else if r == "\"" {
l.emitRaw()
l.ignore()
l.acceptUntil("\"")
l.emitWithoutEnd(T_STRING_MARK)
} else if r == "#" {
l.emitRaw()
l.acceptRun("#,")
l.emit(T_COMMA_MARK)
} else if r == "0" {
l.emitRaw()
l.acceptRun("0123456789.")
l.emit(T_DECIMAL_MARK)
}
}
l.emit(T_EOF)
return nil
}

36
format/parse_test.go Normal file
View File

@@ -0,0 +1,36 @@
package format
import (
"fmt"
"testing"
)
func TestParseYY(t *testing.T) {
_, tokens := Lexer(`yy-`)
ds := Parse(tokens)
fmt.Println(ds.Format(8100, true))
}
func TestParseMM(t *testing.T) {
_, tokens := Lexer(`yyyymm`)
ds := Parse(tokens)
fmt.Println(ds.Format(8100, true))
}
func TestParseMM2(t *testing.T) {
_, tokens := Lexer(`yyyy-mm"fasd65af"----`)
ds := Parse(tokens)
fmt.Println(ds.Format(8800, false))
}
func TestParseDD(t *testing.T) {
_, tokens := Lexer(`yyyy-mm-dd`)
ds := Parse(tokens)
fmt.Println(ds.Format(8100, true))
}
func TestParseNum(t *testing.T) {
_, tokens := Lexer(`"$"#,##0.00`)
ds := Parse(tokens)
fmt.Println(ds.Format(8800, false))
}

78
format/parser.go Normal file
View File

@@ -0,0 +1,78 @@
package format
// Parse creates a new parser with the recommended
// parameters.
func Parse(tokens []LexToken) Formatter {
p := &parser{
tokens: tokens,
pos: -1,
}
p.initState = initialParserState
return p.run()
}
// run starts the statemachine
func (p *parser) run() Formatter {
var f Formatter
for state := p.initState; state != nil; {
state = state(p, &f)
}
return f
}
// parserState represents the state of the scanner
// as a function that returns the next state.
type parserState func(*parser, *Formatter) parserState
// nest returns what the next token AND
// advances p.pos.
func (p *parser) next() *LexToken {
if p.pos >= len(p.tokens)-1 {
return nil
}
p.pos += 1
return &p.tokens[p.pos]
}
// the parser type
type parser struct {
tokens []LexToken
pos int
serial int
initState parserState
}
// the starting state for parsing
func initialParserState(p *parser, f *Formatter) parserState {
var t *LexToken
for t = p.next(); t[0] != T_EOF; t = p.next() {
var item ItemFormatter
switch t[0] {
case T_YEAR_MARK:
f.typ = DATEFORMAT
item = new(YearFormatter)
case T_MONTH_MARK:
f.typ = DATEFORMAT
item = new(MonthFormatter)
case T_DAY_MARK:
f.typ = DATEFORMAT
item = new(DayFormatter)
case T_RAW_MARK:
item = new(basicFormatter)
case T_STRING_MARK:
item = new(basicFormatter)
case T_COMMA_MARK, T_DECIMAL_MARK:
f.typ = DECIMALFORMAT
item = new(basicFormatter)
}
item.setOriginal(t[1])
f.Items = append(f.Items, item)
}
if len(t[1]) > 0 {
r := new(basicFormatter)
r.origin = t[1]
f.Items = append(f.Items, r)
}
return nil
}