2017-03-11 07:15:15 -05:00
|
|
|
package ofxgo
|
|
|
|
|
|
|
|
import (
|
|
|
|
"crypto/rand"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"github.com/golang/go/src/encoding/xml"
|
|
|
|
"regexp"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
)
|
|
|
|
|
2017-03-11 07:18:02 -05:00
|
|
|
type Int int64
|
2017-03-11 07:15:15 -05:00
|
|
|
|
2017-03-11 07:18:02 -05:00
|
|
|
func (oi *Int) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
2017-03-11 07:15:15 -05:00
|
|
|
var value string
|
|
|
|
err := d.DecodeElement(&value, &start)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
i, err := strconv.ParseInt(strings.TrimSpace(value), 10, 64)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2017-03-11 07:18:02 -05:00
|
|
|
*oi = (Int)(i)
|
2017-03-11 07:15:15 -05:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2017-03-11 12:59:47 -05:00
|
|
|
type Date time.Time
|
|
|
|
|
2017-03-11 07:15:15 -05:00
|
|
|
var ofxDateFormats = []string{
|
|
|
|
"20060102150405.000",
|
|
|
|
"20060102150405",
|
|
|
|
"200601021504",
|
|
|
|
"2006010215",
|
|
|
|
"20060102",
|
|
|
|
}
|
2017-03-11 12:59:47 -05:00
|
|
|
var ofxDateZoneRegex = regexp.MustCompile(`^([+-]?[0-9]+)(\.([0-9]{2}))?(:([A-Z]+))?$`)
|
2017-03-11 07:15:15 -05:00
|
|
|
|
2017-03-11 07:18:02 -05:00
|
|
|
func (od *Date) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
2017-03-11 12:59:47 -05:00
|
|
|
var value, zone, zoneFormat string
|
2017-03-11 07:15:15 -05:00
|
|
|
err := d.DecodeElement(&value, &start)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2017-03-11 12:59:47 -05:00
|
|
|
// Split the time zone off, if any
|
|
|
|
split := strings.SplitN(value, "[", 2)
|
|
|
|
if len(split) == 2 {
|
|
|
|
value = split[0]
|
|
|
|
zoneFormat = " -0700"
|
|
|
|
zone = strings.TrimRight(split[1], "]")
|
|
|
|
|
|
|
|
matches := ofxDateZoneRegex.FindStringSubmatch(zone)
|
2017-03-11 07:15:15 -05:00
|
|
|
if matches == nil {
|
2017-03-11 12:59:47 -05:00
|
|
|
return errors.New("Invalid OFX Date timezone format: " + zone)
|
2017-03-11 07:15:15 -05:00
|
|
|
}
|
|
|
|
var err error
|
|
|
|
var zonehours, zoneminutes int
|
|
|
|
zonehours, err = strconv.Atoi(matches[1])
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if len(matches[3]) > 0 {
|
|
|
|
zoneminutes, err = strconv.Atoi(matches[1])
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
zoneminutes = zoneminutes * 60 / 100
|
|
|
|
}
|
2017-03-11 12:59:47 -05:00
|
|
|
zone = fmt.Sprintf(" %+03d%02d", zonehours, zoneminutes)
|
2017-03-11 07:15:15 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
for _, format := range ofxDateFormats {
|
2017-03-11 12:59:47 -05:00
|
|
|
t, err := time.Parse(format+zoneFormat, value+zone)
|
2017-03-11 07:15:15 -05:00
|
|
|
if err == nil {
|
2017-03-11 07:18:02 -05:00
|
|
|
tmpod := Date(t)
|
2017-03-11 07:15:15 -05:00
|
|
|
*od = tmpod
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return errors.New("OFX: Couldn't parse date:" + value)
|
|
|
|
}
|
|
|
|
|
2017-03-11 12:59:47 -05:00
|
|
|
func (od Date) String() string {
|
|
|
|
t := time.Time(od)
|
2017-03-11 07:15:15 -05:00
|
|
|
format := t.Format(ofxDateFormats[0])
|
|
|
|
zonename, zoneoffset := t.Zone()
|
|
|
|
format += "[" + fmt.Sprintf("%+d", zoneoffset/3600)
|
|
|
|
fractionaloffset := (zoneoffset % 3600) / 360
|
|
|
|
if fractionaloffset > 0 {
|
|
|
|
format += "." + fmt.Sprintf("%02d", fractionaloffset)
|
|
|
|
} else if fractionaloffset < 0 {
|
|
|
|
format += "." + fmt.Sprintf("%02d", -fractionaloffset)
|
|
|
|
}
|
|
|
|
return format + ":" + zonename + "]"
|
|
|
|
}
|
|
|
|
|
2017-03-11 07:18:02 -05:00
|
|
|
func (od *Date) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
|
2017-03-11 07:15:15 -05:00
|
|
|
return e.EncodeElement(od.String(), start)
|
|
|
|
}
|
|
|
|
|
2017-03-11 07:18:02 -05:00
|
|
|
type String string
|
2017-03-11 07:15:15 -05:00
|
|
|
|
2017-03-11 07:18:02 -05:00
|
|
|
func (os *String) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
2017-03-11 07:15:15 -05:00
|
|
|
var value string
|
|
|
|
err := d.DecodeElement(&value, &start)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2017-03-11 07:18:02 -05:00
|
|
|
*os = String(strings.TrimSpace(value))
|
2017-03-11 07:15:15 -05:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2017-03-11 07:18:02 -05:00
|
|
|
type Boolean bool
|
2017-03-11 07:15:15 -05:00
|
|
|
|
2017-03-11 07:18:02 -05:00
|
|
|
func (ob *Boolean) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
2017-03-11 07:15:15 -05:00
|
|
|
var value string
|
|
|
|
err := d.DecodeElement(&value, &start)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
tmpob := strings.TrimSpace(value)
|
|
|
|
switch tmpob {
|
|
|
|
case "Y":
|
2017-03-11 07:18:02 -05:00
|
|
|
*ob = Boolean(true)
|
2017-03-11 07:15:15 -05:00
|
|
|
case "N":
|
2017-03-11 07:18:02 -05:00
|
|
|
*ob = Boolean(false)
|
2017-03-11 07:15:15 -05:00
|
|
|
default:
|
|
|
|
return errors.New("Invalid OFX Boolean")
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2017-03-11 07:18:02 -05:00
|
|
|
func (ob *Boolean) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
|
2017-03-11 07:15:15 -05:00
|
|
|
if *ob {
|
|
|
|
return e.EncodeElement("Y", start)
|
|
|
|
}
|
|
|
|
return e.EncodeElement("N", start)
|
|
|
|
}
|
|
|
|
|
2017-03-11 07:18:02 -05:00
|
|
|
type UID string
|
2017-03-11 07:15:15 -05:00
|
|
|
|
2017-03-11 07:18:02 -05:00
|
|
|
func (ou *UID) Valid() (bool, error) {
|
2017-03-11 07:15:15 -05:00
|
|
|
if len(*ou) != 36 {
|
|
|
|
return false, errors.New("UID not 36 characters long")
|
|
|
|
}
|
|
|
|
return true, nil
|
|
|
|
}
|
|
|
|
|
2017-03-11 07:18:02 -05:00
|
|
|
func RandomUID() (*UID, error) {
|
2017-03-11 07:15:15 -05:00
|
|
|
uidbytes := make([]byte, 16)
|
|
|
|
n, err := rand.Read(uidbytes[:])
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if n != 16 {
|
|
|
|
return nil, errors.New("RandomUID failed to read 16 random bytes")
|
|
|
|
}
|
2017-03-11 07:18:02 -05:00
|
|
|
uid := UID(fmt.Sprintf("%08x-%04x-%04x-%04x-%012x", uidbytes[:4], uidbytes[4:6], uidbytes[6:8], uidbytes[8:10], uidbytes[10:]))
|
2017-03-11 07:15:15 -05:00
|
|
|
return &uid, nil
|
|
|
|
}
|