mirror of
				https://github.com/Theodor-Springmann-Stiftung/kgpz_web.git
				synced 2025-11-04 03:35:30 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			393 lines
		
	
	
		
			7.0 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			393 lines
		
	
	
		
			7.0 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package xsdtime
 | 
						|
 | 
						|
import (
 | 
						|
	"errors"
 | 
						|
	"fmt"
 | 
						|
	"strconv"
 | 
						|
	"strings"
 | 
						|
)
 | 
						|
 | 
						|
// An implementation of the xsd 1.1 datatypes:
 | 
						|
// date, gDay, gMonth, gMonthDay, gYear, gYearMonth.
 | 
						|
 | 
						|
type (
 | 
						|
	XSDDatetype int
 | 
						|
	Seperator   byte
 | 
						|
)
 | 
						|
 | 
						|
const (
 | 
						|
	DEFAULT_YEAR  = 0
 | 
						|
	DEFAULT_DAY   = 1
 | 
						|
	DEFAULT_MONTH = 1
 | 
						|
 | 
						|
	MIN_ALLOWED_NUMBER = 0x30 // 0
 | 
						|
	MAX_ALLOWED_NUMBER = 0x39 // 9
 | 
						|
	SIGN               = 0x2D // -
 | 
						|
	SEPERATOR          = 0x2D // -
 | 
						|
	PLUS               = 0x2B // +
 | 
						|
	COLON              = 0x3A // :
 | 
						|
	TIMEZONE           = 0x5A // Z
 | 
						|
	NONE               = 0x00 // 0
 | 
						|
)
 | 
						|
 | 
						|
const (
 | 
						|
	Unknown XSDDatetype = iota
 | 
						|
	Invalid
 | 
						|
	Date
 | 
						|
	GDay
 | 
						|
	GMonth
 | 
						|
	GYear
 | 
						|
	GMonthDay
 | 
						|
	GYearMonth
 | 
						|
)
 | 
						|
 | 
						|
var (
 | 
						|
	MonthNameShort = []string{"Jan", "Feb", "März", "Apr", "Mai", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec"}
 | 
						|
	MonthName      = []string{"Januar", "Februar", "März", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember"}
 | 
						|
)
 | 
						|
 | 
						|
type XSDDate struct {
 | 
						|
	base string
 | 
						|
 | 
						|
	Year  int
 | 
						|
	Month int
 | 
						|
	Day   int
 | 
						|
 | 
						|
	hasTimezone bool
 | 
						|
	hasYear     bool
 | 
						|
	hasMonth    bool
 | 
						|
	hasDay      bool
 | 
						|
 | 
						|
	TZH int
 | 
						|
	TZM int
 | 
						|
 | 
						|
	state XSDDatetype
 | 
						|
	error bool
 | 
						|
 | 
						|
	// INFO: XSD Date Datatypes typically describe a duration in the value space.
 | 
						|
	// TimeError  bool
 | 
						|
	// BaseTime     time.Time
 | 
						|
	// BaseDuration time.Duration
 | 
						|
}
 | 
						|
 | 
						|
// Sanity check:
 | 
						|
// MONTH DAY + Date: Sanity check Month and Day. Additional checks:
 | 
						|
//		- Month: 2 - Day < 30
 | 
						|
// 		- Month: 4, 6, 9, 11 - Day < 31
 | 
						|
// 		- Month: 1, 3, 5, 7, 8, 10, 12 - Day < 32
 | 
						|
// YEAR + Date: Sanity check Year + February 29. Check zero padding.
 | 
						|
// Additional checks:
 | 
						|
//		- Feb 29 on leap years: y % 4 == 0 && (y % 100 != 0 || y % 400 == 0)
 | 
						|
//		-> Check last 2 digits: if both are zero, check first two digits.
 | 
						|
//			 Else if last digit is n % 4 == 0, the second to last digit m % 2 == 0
 | 
						|
//			 Else if last digit is n % 4 == 2, the second to last digit m % 2 == 1
 | 
						|
//			 Else its not a leap year.
 | 
						|
//		- no 0000 Year
 | 
						|
//
 | 
						|
 | 
						|
func New(s string) (XSDDate, error) {
 | 
						|
	dt := XSDDate{base: s}
 | 
						|
	err := dt.Parse(s)
 | 
						|
	return dt, err
 | 
						|
}
 | 
						|
 | 
						|
func (d XSDDate) String() string {
 | 
						|
	var s string
 | 
						|
	if d.Year != 0 {
 | 
						|
		s += fmt.Sprintf("%d", d.Year)
 | 
						|
	}
 | 
						|
 | 
						|
	if d.Month != 0 {
 | 
						|
		if d.Year == 0 {
 | 
						|
			s += "-"
 | 
						|
		}
 | 
						|
		s += fmt.Sprintf("-%02d", d.Month)
 | 
						|
	}
 | 
						|
 | 
						|
	if d.Day != 0 {
 | 
						|
		if d.Year == 0 && d.Month == 0 {
 | 
						|
			s += "--"
 | 
						|
		}
 | 
						|
		s += fmt.Sprintf("-%02d", d.Day)
 | 
						|
	}
 | 
						|
 | 
						|
	if d.hasTimezone {
 | 
						|
		if d.TZH == 0 && d.TZM == 0 {
 | 
						|
			s += "Z"
 | 
						|
		} else {
 | 
						|
			sep := "+"
 | 
						|
			hint := d.TZH
 | 
						|
			if hint < 0 {
 | 
						|
				sep = "-"
 | 
						|
				hint *= -1
 | 
						|
			}
 | 
						|
			h := fmt.Sprintf("%02d", hint)
 | 
						|
 | 
						|
			s += fmt.Sprintf("%v%v:%02d", sep, h, d.TZM)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return s
 | 
						|
}
 | 
						|
 | 
						|
func (d *XSDDate) MonthName() string {
 | 
						|
	if d.Month == 0 {
 | 
						|
		return ""
 | 
						|
	}
 | 
						|
	return MonthName[d.Month-1]
 | 
						|
}
 | 
						|
 | 
						|
func (d *XSDDate) MonthNameShort() string {
 | 
						|
	if d.Month == 0 {
 | 
						|
		return ""
 | 
						|
	}
 | 
						|
	return MonthNameShort[d.Month-1]
 | 
						|
}
 | 
						|
 | 
						|
func (d *XSDDate) UnmarshalText(text []byte) error {
 | 
						|
	return d.Parse(string(text))
 | 
						|
}
 | 
						|
 | 
						|
func (d XSDDate) MarshalText() ([]byte, error) {
 | 
						|
	return []byte(d.String()), nil
 | 
						|
}
 | 
						|
 | 
						|
func (xsdd *XSDDate) Parse(s string) error {
 | 
						|
	s = strings.TrimSpace(s)
 | 
						|
	xsdd.base = s
 | 
						|
 | 
						|
	// The smallest possible date is 4 chars long
 | 
						|
	if len(s) < 4 {
 | 
						|
		return xsdd.parseError("Date too short")
 | 
						|
	}
 | 
						|
 | 
						|
	// Check for Z, then check for timezone
 | 
						|
	if len(s) >= 5 && s[len(s)-1] == TIMEZONE {
 | 
						|
		xsdd.hasTimezone = true
 | 
						|
		s = s[:len(s)-1]
 | 
						|
	} else if len(s) >= 10 {
 | 
						|
		err := xsdd.parseTimezone(s[len(s)-6:])
 | 
						|
		if err == nil {
 | 
						|
			s = s[:len(s)-6]
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// Year
 | 
						|
	if s[1] != SEPERATOR {
 | 
						|
		i := 3
 | 
						|
		for ; i < len(s); i++ {
 | 
						|
			if s[i] < MIN_ALLOWED_NUMBER || s[i] > MAX_ALLOWED_NUMBER {
 | 
						|
				break
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		yint, err := strconv.Atoi(s[:i])
 | 
						|
		if err != nil {
 | 
						|
			return xsdd.parseError(fmt.Sprintf("Invalid year: %v", s[:i]))
 | 
						|
		}
 | 
						|
		xsdd.Year = yint
 | 
						|
		xsdd.hasYear = true
 | 
						|
 | 
						|
		if i == len(s) {
 | 
						|
			return nil
 | 
						|
		}
 | 
						|
 | 
						|
		s = s[i+1:]
 | 
						|
	} else {
 | 
						|
		s = s[2:]
 | 
						|
	}
 | 
						|
 | 
						|
	// Left are 02 (Month), -02 (Day), 02-02 (Date)
 | 
						|
	if s[0] != SEPERATOR {
 | 
						|
		mstr := s[:2]
 | 
						|
		mint, err := strconv.Atoi(mstr)
 | 
						|
		if err != nil {
 | 
						|
			return xsdd.parseError(fmt.Sprintf("Invalid month: %v", mstr))
 | 
						|
		}
 | 
						|
 | 
						|
		xsdd.Month = mint
 | 
						|
		xsdd.hasMonth = true
 | 
						|
		s = s[2:]
 | 
						|
		if len(s) == 0 {
 | 
						|
			return nil
 | 
						|
		} else if len(s) != 3 || s[0] != SEPERATOR {
 | 
						|
			return xsdd.parseError(fmt.Sprintf("Invalid date ending: %v", s))
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	s = s[1:]
 | 
						|
 | 
						|
	// Left is 02 Day
 | 
						|
	dint, err := strconv.Atoi(s)
 | 
						|
	if err != nil {
 | 
						|
		return xsdd.parseError(fmt.Sprintf("Invalid day: %v", s))
 | 
						|
	}
 | 
						|
 | 
						|
	// INFO: We do not check len here, it is handled above
 | 
						|
	xsdd.Day = dint
 | 
						|
	xsdd.hasDay = true
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
var WD_CALC_MATRIX = []int{0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4}
 | 
						|
 | 
						|
func (xsdd XSDDate) Weekday() int {
 | 
						|
	y := xsdd.Year
 | 
						|
	if xsdd.Month < 3 {
 | 
						|
		y--
 | 
						|
	}
 | 
						|
	return (y + y/4 - y/100 + y/400 + WD_CALC_MATRIX[xsdd.Month-1] + xsdd.Day) % 7
 | 
						|
}
 | 
						|
 | 
						|
func (xsdd XSDDate) Base() string {
 | 
						|
	return xsdd.base
 | 
						|
}
 | 
						|
 | 
						|
func (xsdd XSDDate) Type() XSDDatetype {
 | 
						|
	if xsdd.state == Unknown {
 | 
						|
		_ = xsdd.Validate()
 | 
						|
	}
 | 
						|
 | 
						|
	return xsdd.state
 | 
						|
}
 | 
						|
 | 
						|
func (xsdd *XSDDate) Validate() bool {
 | 
						|
	if xsdd.error {
 | 
						|
		xsdd.state = Invalid
 | 
						|
		return false
 | 
						|
	}
 | 
						|
 | 
						|
	xsdd.state = xsdd.inferState()
 | 
						|
	if xsdd.state != Invalid {
 | 
						|
		return true
 | 
						|
	}
 | 
						|
 | 
						|
	return false
 | 
						|
}
 | 
						|
 | 
						|
func (xsdd *XSDDate) parseError(s string) error {
 | 
						|
	xsdd.error = true
 | 
						|
	xsdd.state = Invalid
 | 
						|
	return errors.New(s)
 | 
						|
}
 | 
						|
 | 
						|
func (xsdd *XSDDate) parseTimezone(s string) error {
 | 
						|
	// INFO: We assume the check for 'Z' has already been done
 | 
						|
	if len(s) != 6 || s[3] != COLON || (s[0] != PLUS && s[0] != SIGN) {
 | 
						|
		return fmt.Errorf("Invalid timezone")
 | 
						|
	}
 | 
						|
 | 
						|
	h, err := strconv.Atoi(s[:3])
 | 
						|
	if err != nil {
 | 
						|
		return fmt.Errorf("Invalid hour: %v", s[:3])
 | 
						|
	}
 | 
						|
 | 
						|
	m, err := strconv.Atoi(s[4:])
 | 
						|
	if err != nil {
 | 
						|
		return fmt.Errorf("Invalid minute: %v", s[4:])
 | 
						|
	}
 | 
						|
 | 
						|
	xsdd.hasTimezone = true
 | 
						|
	xsdd.TZH = h
 | 
						|
	xsdd.TZM = m
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func (xsdd XSDDate) inferState() XSDDatetype {
 | 
						|
	if xsdd.hasYear && xsdd.hasMonth && xsdd.hasDay {
 | 
						|
		if !validDayMonthYear(xsdd.Year, xsdd.Month, xsdd.Day) {
 | 
						|
			return Invalid
 | 
						|
		}
 | 
						|
		return Date
 | 
						|
	} else if xsdd.hasYear && xsdd.hasMonth {
 | 
						|
		if !validMonth(xsdd.Month) || !validYear(xsdd.Year) {
 | 
						|
			return Invalid
 | 
						|
		}
 | 
						|
		return GYearMonth
 | 
						|
	} else if xsdd.hasMonth && xsdd.hasDay {
 | 
						|
		if !validDayMonth(xsdd.Day, xsdd.Month) {
 | 
						|
			return Invalid
 | 
						|
		}
 | 
						|
		return GMonthDay
 | 
						|
	} else if xsdd.hasYear {
 | 
						|
		if !validYear(xsdd.Year) {
 | 
						|
			return Invalid
 | 
						|
		}
 | 
						|
		return GYear
 | 
						|
	} else if xsdd.hasMonth {
 | 
						|
		if !validMonth(xsdd.Month) {
 | 
						|
			return Invalid
 | 
						|
		}
 | 
						|
		return GMonth
 | 
						|
	} else if xsdd.hasDay {
 | 
						|
		if !validDay(xsdd.Day) {
 | 
						|
			return Invalid
 | 
						|
		}
 | 
						|
		return GDay
 | 
						|
	}
 | 
						|
 | 
						|
	return Invalid
 | 
						|
}
 | 
						|
 | 
						|
func validDay(i int) bool {
 | 
						|
	if i < 1 || i > 31 {
 | 
						|
		return false
 | 
						|
	}
 | 
						|
 | 
						|
	return true
 | 
						|
}
 | 
						|
 | 
						|
func validMonth(i int) bool {
 | 
						|
	if i < 1 || i > 12 {
 | 
						|
		return false
 | 
						|
	}
 | 
						|
 | 
						|
	return true
 | 
						|
}
 | 
						|
 | 
						|
func validYear(i int) bool {
 | 
						|
	if i == 0 {
 | 
						|
		return false
 | 
						|
	}
 | 
						|
 | 
						|
	return true
 | 
						|
}
 | 
						|
 | 
						|
func validDayMonth(d int, m int) bool {
 | 
						|
	if !validDay(d) || !validMonth(m) {
 | 
						|
		return false
 | 
						|
	}
 | 
						|
 | 
						|
	if m == 2 {
 | 
						|
		if d > 29 {
 | 
						|
			return false
 | 
						|
		}
 | 
						|
	} else if m == 4 || m == 6 || m == 9 || m == 11 {
 | 
						|
		if d > 30 {
 | 
						|
			return false
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return true
 | 
						|
}
 | 
						|
 | 
						|
func validDayMonthYear(y int, m int, d int) bool {
 | 
						|
	if !validDay(d) || !validMonth(m) || !validYear(y) {
 | 
						|
		return false
 | 
						|
	}
 | 
						|
 | 
						|
	if m == 2 {
 | 
						|
		if d == 29 {
 | 
						|
			if y%4 == 0 && (y%100 != 0 || y%400 == 0) {
 | 
						|
				return true
 | 
						|
			}
 | 
						|
 | 
						|
			return false
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return true
 | 
						|
}
 |