try to use a new formatter for date and decimal
This commit is contained in:
98
format/date.go
Normal file
98
format/date.go
Normal 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
106
format/formatter.go
Normal 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
13
format/lang.go
Normal 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
150
format/lexer.go
Normal 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
36
format/parse_test.go
Normal 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
78
format/parser.go
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user