Complete move to naive date/time types.
These types no longer assume the local timezone as a default, instead accepting a time.Location when converting to a time.Time.
This commit is contained in:
parent
500ae47278
commit
0f6ea5004c
@ -1,4 +1,9 @@
|
|||||||
// Package types defines types that encode values in SOAP requests and responses.
|
// Package types defines types that encode values in SOAP requests and responses.
|
||||||
|
//
|
||||||
|
// Based on https://openconnectivity.org/upnp-specs/UPnP-arch-DeviceArchitecture-v2.0-20200417.pdf
|
||||||
|
// pages 58-60.
|
||||||
|
//
|
||||||
|
// Date/time formats are based on http://www.w3.org/TR/1998/NOTE-datetime-19980827
|
||||||
package types
|
package types
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@ -14,13 +19,6 @@ import (
|
|||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
|
||||||
// localLoc acts like time.Local for this package, but is faked out by the
|
|
||||||
// unit tests to ensure that things stay constant (especially when running
|
|
||||||
// this test in a place where local time is UTC which might mask bugs).
|
|
||||||
localLoc = time.Local
|
|
||||||
)
|
|
||||||
|
|
||||||
type SOAPValue interface {
|
type SOAPValue interface {
|
||||||
Marshal() (string, error)
|
Marshal() (string, error)
|
||||||
Unmarshal(s string) error
|
Unmarshal(s string) error
|
||||||
@ -474,87 +472,23 @@ var dateRegexps = []*regexp.Regexp{
|
|||||||
regexp.MustCompile(`^(\d{4})(?:(\d{2})(?:(\d{2}))?)?$`),
|
regexp.MustCompile(`^(\d{4})(?:(\d{2})(?:(\d{2}))?)?$`),
|
||||||
}
|
}
|
||||||
|
|
||||||
var timeRegexps = []*regexp.Regexp{
|
type prefixRemainder struct {
|
||||||
// hh[:mm[:ss]]
|
prefix string
|
||||||
regexp.MustCompile(`^(\d{2})(?::(\d{2})(?::(\d{2}))?)?$`),
|
remainder string
|
||||||
// hh[mm[ss]]
|
|
||||||
regexp.MustCompile(`^(\d{2})(?:(\d{2})(?:(\d{2}))?)?$`),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseTimeParts(s string) (TimeOfDay, error) {
|
// prefixUntilAny returns a prefix of the leading string prior to any
|
||||||
var parts []string
|
// characters in `chars`, and the remainder. If no character from `chars` is
|
||||||
for _, re := range timeRegexps {
|
// present in `s`, then returns `s` as `prefix`, and empty remainder.
|
||||||
parts = re.FindStringSubmatch(s)
|
//
|
||||||
if parts != nil {
|
// prefixUntilAny("123/abc", "/") => {"123", "/abc"}
|
||||||
break
|
// prefixUntilAny("123", "/") => {"123", ""}
|
||||||
|
func prefixUntilAny(s string, chars string) prefixRemainder {
|
||||||
|
i := strings.IndexAny(s, chars)
|
||||||
|
if i == -1 {
|
||||||
|
return prefixRemainder{prefix: s, remainder: ""}
|
||||||
}
|
}
|
||||||
}
|
return prefixRemainder{prefix: s[:i], remainder: s[i:]}
|
||||||
if parts == nil {
|
|
||||||
return TimeOfDay{}, fmt.Errorf("soap time: value %q is not in ISO8601 time format", s)
|
|
||||||
}
|
|
||||||
|
|
||||||
var err error
|
|
||||||
var hour, minute, second int8
|
|
||||||
hour = int8(parseInt(parts[1], &err))
|
|
||||||
if len(parts[2]) != 0 {
|
|
||||||
minute = int8(parseInt(parts[2], &err))
|
|
||||||
if len(parts[3]) != 0 {
|
|
||||||
second = int8(parseInt(parts[3], &err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return TimeOfDay{}, fmt.Errorf("soap time: %q: %v", s, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return TimeOfDay{hour, minute, second}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// (+|-)hh[[:]mm]
|
|
||||||
var timezoneRegexp = regexp.MustCompile(`^([+-])(\d{2})(?::?(\d{2}))?$`)
|
|
||||||
|
|
||||||
func parseTimezone(s string) (offset int, err error) {
|
|
||||||
if s == "Z" {
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
parts := timezoneRegexp.FindStringSubmatch(s)
|
|
||||||
if parts == nil {
|
|
||||||
err = fmt.Errorf("soap timezone: value %q is not in ISO8601 timezone format", s)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
offset = parseInt(parts[2], &err) * 3600
|
|
||||||
if len(parts[3]) != 0 {
|
|
||||||
offset += parseInt(parts[3], &err) * 60
|
|
||||||
}
|
|
||||||
if parts[1] == "-" {
|
|
||||||
offset = -offset
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
err = fmt.Errorf("soap timezone: %q: %v", s, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var completeDateTimeZoneRegexp = regexp.MustCompile(`^([^T]+)(?:T([^-+Z]+)(.+)?)?$`)
|
|
||||||
|
|
||||||
// splitCompleteDateTimeZone splits date, time and timezone apart from an
|
|
||||||
// ISO8601 string. It does not ensure that the contents of each part are
|
|
||||||
// correct, it merely splits on certain delimiters.
|
|
||||||
// e.g "2010-09-08T12:15:10+0700" => "2010-09-08", "12:15:10", "+0700".
|
|
||||||
// Timezone can only be present if time is also present.
|
|
||||||
func splitCompleteDateTimeZone(s string) (dateStr, timeStr, zoneStr string, err error) {
|
|
||||||
parts := completeDateTimeZoneRegexp.FindStringSubmatch(s)
|
|
||||||
if parts == nil {
|
|
||||||
err = fmt.Errorf("soap date/time/zone: value %q is not in ISO8601 datetime format", s)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
dateStr = parts[1]
|
|
||||||
timeStr = parts[2]
|
|
||||||
zoneStr = parts[3]
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TimeOfDay is used in cases where SOAP "time" or "time.tz" is used.
|
// TimeOfDay is used in cases where SOAP "time" or "time.tz" is used.
|
||||||
@ -567,135 +501,131 @@ type TimeOfDay struct {
|
|||||||
|
|
||||||
var _ SOAPValue = &TimeOfDay{}
|
var _ SOAPValue = &TimeOfDay{}
|
||||||
|
|
||||||
|
func TimeOfDayFromTime(t time.Time) TimeOfDay {
|
||||||
|
h, m, s := t.Clock()
|
||||||
|
return TimeOfDay{int8(h), int8(m), int8(s)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tod *TimeOfDay) SetFromTime(t time.Time) {
|
||||||
|
h, m, s := t.Clock()
|
||||||
|
tod.Hour = int8(h)
|
||||||
|
tod.Minute = int8(m)
|
||||||
|
tod.Second = int8(s)
|
||||||
|
}
|
||||||
|
|
||||||
// Sets components based on duration since midnight.
|
// Sets components based on duration since midnight.
|
||||||
func (v *TimeOfDay) SetFromDuration(d time.Duration) error {
|
func (tod *TimeOfDay) SetFromDuration(d time.Duration) error {
|
||||||
if d < 0 || d > 24*time.Hour {
|
if d < 0 || d > 24*time.Hour {
|
||||||
return fmt.Errorf("out of range of SOAP time type: %v", d)
|
return fmt.Errorf("out of range of SOAP time type: %v", d)
|
||||||
}
|
}
|
||||||
v.Hour = int8(d / time.Hour)
|
tod.Hour = int8(d / time.Hour)
|
||||||
d = d % time.Hour
|
d = d % time.Hour
|
||||||
v.Minute = int8(d / time.Minute)
|
tod.Minute = int8(d / time.Minute)
|
||||||
d = d % time.Minute
|
d = d % time.Minute
|
||||||
v.Second = int8(d / time.Second)
|
tod.Second = int8(d / time.Second)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns duration since midnight.
|
// Returns duration since midnight.
|
||||||
func (v *TimeOfDay) ToDuration() time.Duration {
|
func (tod TimeOfDay) ToDuration() time.Duration {
|
||||||
return time.Duration(v.Hour)*time.Hour +
|
return time.Duration(tod.Hour)*time.Hour +
|
||||||
time.Duration(v.Minute)*time.Minute +
|
time.Duration(tod.Minute)*time.Minute +
|
||||||
time.Duration(v.Second)*time.Second
|
time.Duration(tod.Second)*time.Second
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *TimeOfDay) String() string {
|
func (tod TimeOfDay) String() string {
|
||||||
return fmt.Sprintf("%02d:%02d:%02d", v.Hour, v.Minute, v.Second)
|
return fmt.Sprintf("%02d:%02d:%02d", tod.Hour, tod.Minute, tod.Second)
|
||||||
}
|
|
||||||
|
|
||||||
func (v *TimeOfDay) Equal(o *TimeOfDay) bool {
|
|
||||||
return v.Hour == o.Hour && v.Minute == o.Minute && v.Second == o.Second
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsValid returns true iff v is positive and <= 24 hours.
|
// IsValid returns true iff v is positive and <= 24 hours.
|
||||||
// It allows equal to 24 hours as a special case as 24:00:00 is an allowed
|
// It allows equal to 24 hours as a special case as 24:00:00 is an allowed
|
||||||
// value by the SOAP type.
|
// value by the SOAP type.
|
||||||
func (v *TimeOfDay) CheckValid() error {
|
func (tod TimeOfDay) CheckValid() error {
|
||||||
if (v.Hour < 0 || v.Minute < 0 || v.Second < 0) ||
|
if (tod.Hour < 0 || tod.Minute < 0 || tod.Second < 0) ||
|
||||||
(v.Hour == 24 && (v.Minute > 0 || v.Second > 0)) ||
|
(tod.Hour == 24 && (tod.Minute > 0 || tod.Second > 0)) ||
|
||||||
v.Hour > 24 || v.Minute >= 60 || v.Second >= 60 {
|
tod.Hour > 24 || tod.Minute >= 60 || tod.Second >= 60 {
|
||||||
return fmt.Errorf("soap time: value %v has components(s) out of range", v)
|
return fmt.Errorf("soap time: value %v has components(s) out of range", tod)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *TimeOfDay) Marshal() (string, error) {
|
// clear removes data from v, setting to default values.
|
||||||
if err := v.CheckValid(); err != nil {
|
func (tod *TimeOfDay) clear() {
|
||||||
|
tod.Hour = 0
|
||||||
|
tod.Minute = 0
|
||||||
|
tod.Second = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tod *TimeOfDay) Marshal() (string, error) {
|
||||||
|
if err := tod.CheckValid(); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return v.String(), nil
|
return tod.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var timeRegexps = []*regexp.Regexp{
|
||||||
|
// hh:mm:ss
|
||||||
|
regexp.MustCompile(`^(\d{2})(\d{2})(\d{2})$`),
|
||||||
|
// hhmmss
|
||||||
|
regexp.MustCompile(`^(\d{2}):(\d{2}):(\d{2})$`),
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tod *TimeOfDay) Unmarshal(s string) error {
|
||||||
|
tod.clear()
|
||||||
|
var parts []string
|
||||||
|
for _, re := range timeRegexps {
|
||||||
|
parts = re.FindStringSubmatch(s)
|
||||||
|
if parts != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if parts == nil {
|
||||||
|
return fmt.Errorf("value %q is not in ISO8601 time format", s)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *TimeOfDay) Unmarshal(s string) error {
|
|
||||||
var err error
|
var err error
|
||||||
*v, err = parseTimeParts(s)
|
tod.Hour = int8(parseInt(parts[1], &err))
|
||||||
|
tod.Minute = int8(parseInt(parts[2], &err))
|
||||||
|
tod.Second = int8(parseInt(parts[3], &err))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return fmt.Errorf("value %q is not in ISO8601 time format: %v", s, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return v.CheckValid()
|
return tod.CheckValid()
|
||||||
}
|
}
|
||||||
|
|
||||||
// TimeOfDayTZ is used in cases where SOAP "time.tz" is used.
|
// TimeOfDayTZ maps to the SOAP "time.tz" type.
|
||||||
type TimeOfDayTZ struct {
|
type TimeOfDayTZ struct {
|
||||||
// Components of the time of day.
|
// Components of the time of day.
|
||||||
TimeOfDay TimeOfDay
|
TimeOfDay TimeOfDay
|
||||||
|
|
||||||
// Set to true if Offset is specified. If false, then the timezone is
|
// Timezone designator.
|
||||||
// unspecified (and by ISO8601 - implies some "local" time).
|
TZ TZD
|
||||||
HasOffset bool
|
|
||||||
|
|
||||||
// Offset is non-zero only if time.tz is used. It is otherwise ignored. If
|
|
||||||
// non-zero, then it is regarded as a UTC offset in seconds. Note that the
|
|
||||||
// sub-minutes is ignored by the marshal function.
|
|
||||||
Offset int
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ SOAPValue = &TimeOfDayTZ{}
|
var _ SOAPValue = &TimeOfDayTZ{}
|
||||||
|
|
||||||
func (v *TimeOfDayTZ) String() string {
|
func (todz TimeOfDayTZ) String() string {
|
||||||
return fmt.Sprintf("%v %t %+03d:%02d:%02d", v.TimeOfDay, v.HasOffset, v.Offset/3600, (v.Offset%3600)/60, v.Offset%60)
|
return fmt.Sprintf("%v%v", todz.TimeOfDay, todz.TZ)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *TimeOfDayTZ) Equal(o *TimeOfDayTZ) bool {
|
// clear removes data from v, setting to default values.
|
||||||
return v.TimeOfDay.Equal(&o.TimeOfDay) &&
|
func (todz *TimeOfDayTZ) clear() {
|
||||||
v.HasOffset == o.HasOffset && v.Offset == o.Offset
|
todz.TimeOfDay.clear()
|
||||||
|
todz.TZ.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *TimeOfDayTZ) Marshal() (string, error) {
|
func (todz *TimeOfDayTZ) Marshal() (string, error) {
|
||||||
tod, err := v.TimeOfDay.Marshal()
|
return todz.String(), nil
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tz := ""
|
func (todz *TimeOfDayTZ) Unmarshal(s string) error {
|
||||||
if v.HasOffset {
|
todz.clear()
|
||||||
if v.Offset == 0 {
|
parts := prefixUntilAny(s, "Z+-")
|
||||||
tz = "Z"
|
if err := todz.TimeOfDay.Unmarshal(parts.prefix); err != nil {
|
||||||
} else {
|
|
||||||
offsetMins := v.Offset / 60
|
|
||||||
sign := '+'
|
|
||||||
if offsetMins < 1 {
|
|
||||||
offsetMins = -offsetMins
|
|
||||||
sign = '-'
|
|
||||||
}
|
|
||||||
tz = fmt.Sprintf("%c%02d:%02d", sign, offsetMins/60, offsetMins%60)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return tod + tz, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *TimeOfDayTZ) Unmarshal(s string) error {
|
|
||||||
zoneIndex := strings.IndexAny(s, "Z+-")
|
|
||||||
var timePart string
|
|
||||||
if zoneIndex == -1 {
|
|
||||||
v.HasOffset = false
|
|
||||||
v.Offset = 0
|
|
||||||
timePart = s
|
|
||||||
} else {
|
|
||||||
v.HasOffset = true
|
|
||||||
timePart = s[:zoneIndex]
|
|
||||||
var err error
|
|
||||||
v.Offset, err = parseTimezone(s[zoneIndex:])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
return todz.TZ.unmarshal(parts.remainder)
|
||||||
|
|
||||||
if err := v.TimeOfDay.Unmarshal(timePart); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Date maps to the SOAP "date" type. Marshaling and Unmarshalling does *not*
|
// Date maps to the SOAP "date" type. Marshaling and Unmarshalling does *not*
|
||||||
@ -722,16 +652,16 @@ func (d *Date) SetFromTime(t time.Time) {
|
|||||||
|
|
||||||
// ToTime returns a time.Time from the date components, at midnight, and using
|
// ToTime returns a time.Time from the date components, at midnight, and using
|
||||||
// the given location.
|
// the given location.
|
||||||
func (d *Date) ToTime(loc *time.Location) time.Time {
|
func (d Date) ToTime(loc *time.Location) time.Time {
|
||||||
return time.Date(d.Year, d.Month, d.Day, 0, 0, 0, 0, loc)
|
return time.Date(d.Year, d.Month, d.Day, 0, 0, 0, 0, loc)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Date) String() string {
|
func (d Date) String() string {
|
||||||
return fmt.Sprintf("%04d-%02d-%02d", d.Year, d.Month, d.Day)
|
return fmt.Sprintf("%04d-%02d-%02d", d.Year, d.Month, d.Day)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckValid returns an error if the date components are out of range.
|
// CheckValid returns an error if the date components are out of range.
|
||||||
func (d *Date) CheckValid() error {
|
func (d Date) CheckValid() error {
|
||||||
y, m, day := d.ToTime(time.UTC).Date()
|
y, m, day := d.ToTime(time.UTC).Date()
|
||||||
if y != d.Year || m != d.Month || day != d.Day {
|
if y != d.Year || m != d.Month || day != d.Day {
|
||||||
return fmt.Errorf("SOAP date component(s) out of range in %v", d)
|
return fmt.Errorf("SOAP date component(s) out of range in %v", d)
|
||||||
@ -739,11 +669,19 @@ func (d *Date) CheckValid() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// clear removes data from d, setting to default (invalid/zero) values.
|
||||||
|
func (d *Date) clear() {
|
||||||
|
d.Year = 0
|
||||||
|
d.Month = 0
|
||||||
|
d.Day = 0
|
||||||
|
}
|
||||||
|
|
||||||
func (d *Date) Marshal() (string, error) {
|
func (d *Date) Marshal() (string, error) {
|
||||||
return d.String(), nil
|
return d.String(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Date) Unmarshal(s string) error {
|
func (d *Date) Unmarshal(s string) error {
|
||||||
|
d.clear()
|
||||||
var parts []string
|
var parts []string
|
||||||
for _, re := range dateRegexps {
|
for _, re := range dateRegexps {
|
||||||
parts = re.FindStringSubmatch(s)
|
parts = re.FindStringSubmatch(s)
|
||||||
@ -773,115 +711,222 @@ func (d *Date) Unmarshal(s string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarshalDateTime maps time.Time to SOAP "dateTime" type, with the local timezone.
|
// DateTime maps to SOAP "dateTime" type.
|
||||||
type DateTimeLocal time.Time
|
type DateTime struct {
|
||||||
|
Date Date
|
||||||
var _ SOAPValue = &DateTimeLocal{}
|
TimeOfDay TimeOfDay
|
||||||
|
|
||||||
func NewDateTimeLocal(v time.Time) *DateTimeLocal {
|
|
||||||
v2 := DateTimeLocal(v)
|
|
||||||
return &v2
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v DateTimeLocal) String() string {
|
var _ SOAPValue = &DateTime{}
|
||||||
return v.ToTime().String()
|
|
||||||
|
func DateTimeFromTime(v time.Time) DateTime {
|
||||||
|
dt := DateTime{}
|
||||||
|
dt.Date.SetFromTime(v)
|
||||||
|
dt.TimeOfDay.SetFromTime(v)
|
||||||
|
return dt
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v DateTimeLocal) ToTime() time.Time {
|
func (dt DateTime) String() string {
|
||||||
return time.Time(v)
|
return fmt.Sprintf("%vT%v", dt.Date, dt.TimeOfDay)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *DateTimeLocal) Marshal() (string, error) {
|
func (dt DateTime) ToTime(loc *time.Location) time.Time {
|
||||||
return v.ToTime().In(localLoc).Format("2006-01-02T15:04:05"), nil
|
return time.Date(dt.Date.Year, dt.Date.Month, dt.Date.Day,
|
||||||
|
int(dt.TimeOfDay.Hour), int(dt.TimeOfDay.Minute), int(dt.TimeOfDay.Second), 0,
|
||||||
|
loc)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *DateTimeLocal) Unmarshal(s string) error {
|
// clear removes data from dt, setting to default values.
|
||||||
dateStr, timeStr, zoneStr, err := splitCompleteDateTimeZone(s)
|
func (dt *DateTime) clear() {
|
||||||
if err != nil {
|
dt.Date.clear()
|
||||||
|
dt.TimeOfDay.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dt *DateTime) Marshal() (string, error) {
|
||||||
|
return dt.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dt *DateTime) Unmarshal(s string) error {
|
||||||
|
dt.clear()
|
||||||
|
parts := prefixUntilAny(s, "T")
|
||||||
|
if err := dt.Date.Unmarshal(parts.prefix); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(zoneStr) != 0 {
|
if parts.remainder == "" {
|
||||||
return fmt.Errorf("soap datetime: unexpected timezone in %q", s)
|
|
||||||
}
|
|
||||||
|
|
||||||
var date Date
|
|
||||||
if err := date.Unmarshal(dateStr); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var tod TimeOfDay
|
|
||||||
if len(timeStr) != 0 {
|
|
||||||
tod, err = parseTimeParts(timeStr)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
*v = DateTimeLocal(time.Date(date.Year, time.Month(date.Month), date.Day,
|
|
||||||
int(tod.Hour), int(tod.Minute), int(tod.Second), 0,
|
|
||||||
localLoc))
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DateTimeLocal maps time.Time to SOAP "dateTime.tz" type, using the local
|
if parts.remainder[0] != 'T' {
|
||||||
// timezone when one is unspecified.
|
return fmt.Errorf("missing 'T' time separator in dateTime %q", s)
|
||||||
type DateTimeTZLocal time.Time
|
|
||||||
|
|
||||||
var _ SOAPValue = &DateTimeTZLocal{}
|
|
||||||
|
|
||||||
func NewDateTimeTZLocal(v time.Time) *DateTimeTZLocal {
|
|
||||||
v2 := DateTimeTZLocal(v)
|
|
||||||
return &v2
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v DateTimeTZLocal) String() string {
|
return dt.TimeOfDay.Unmarshal(parts.remainder[1:])
|
||||||
return v.ToTime().String()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v DateTimeTZLocal) ToTime() time.Time {
|
// DateTime maps to SOAP type "dateTime.tz".
|
||||||
return time.Time(v)
|
type DateTimeTZ struct {
|
||||||
|
Date Date
|
||||||
|
TimeOfDay TimeOfDay
|
||||||
|
TZ TZD
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *DateTimeTZLocal) Marshal() (string, error) {
|
var _ SOAPValue = &DateTimeTZ{}
|
||||||
return time.Time(*v).Format("2006-01-02T15:04:05-07:00"), nil
|
|
||||||
|
func DateTimeTZFromTime(t time.Time) DateTimeTZ {
|
||||||
|
return DateTimeTZ{
|
||||||
|
Date: DateFromTime(t),
|
||||||
|
TimeOfDay: TimeOfDayFromTime(t),
|
||||||
|
TZ: TZDFromTime(t),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dtz DateTimeTZ) String() string {
|
||||||
|
return dtz.Date.String() + "T" + dtz.TimeOfDay.String() + dtz.TZ.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Time converts `dtz` to time.Time, using defaultLoc as the default location if
|
||||||
|
// `dtz` contains no offset information.
|
||||||
|
func (dtz DateTimeTZ) Time(defaultLoc *time.Location) time.Time {
|
||||||
|
return time.Date(
|
||||||
|
dtz.Date.Year,
|
||||||
|
dtz.Date.Month,
|
||||||
|
dtz.Date.Day,
|
||||||
|
int(dtz.TimeOfDay.Hour),
|
||||||
|
int(dtz.TimeOfDay.Minute),
|
||||||
|
int(dtz.TimeOfDay.Second),
|
||||||
|
0,
|
||||||
|
dtz.TZ.Location(defaultLoc),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// clear removes data from dtz, setting to default values.
|
||||||
|
func (dtz *DateTimeTZ) clear() {
|
||||||
|
dtz.Date.clear()
|
||||||
|
dtz.TimeOfDay.clear()
|
||||||
|
dtz.TZ.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dtz *DateTimeTZ) Marshal() (string, error) {
|
||||||
|
return dtz.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dtz *DateTimeTZ) Unmarshal(s string) error {
|
||||||
|
dtz.clear()
|
||||||
|
dateParts := prefixUntilAny(s, "T")
|
||||||
|
if err := dtz.Date.Unmarshal(dateParts.prefix); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if dateParts.remainder == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trim the leading "T" between date and time.
|
||||||
|
remainder := dateParts.remainder[1:]
|
||||||
|
timeParts := prefixUntilAny(remainder, "Z+-")
|
||||||
|
if err := dtz.TimeOfDay.Unmarshal(timeParts.prefix); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if timeParts.remainder == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return dtz.TZ.unmarshal(timeParts.remainder)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TZD is a timezone designator. Not a full SOAP time in itself, but used as
|
||||||
|
// part of timezone-aware date/time types that are.
|
||||||
|
type TZD struct {
|
||||||
|
// Offset is the timezone offset in seconds. Note that the SOAP encoding
|
||||||
|
// only encodes precisions up to minutes, this is in seconds for
|
||||||
|
// interoperability with time.Time.
|
||||||
|
Offset int
|
||||||
|
// HasTZ specifies if a timezone offset is specified or not.
|
||||||
|
HasTZ bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func TZDFromTime(t time.Time) TZD {
|
||||||
|
_, offset := t.Zone()
|
||||||
|
return TZD{
|
||||||
|
Offset: offset,
|
||||||
|
HasTZ: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TZDOffset(secondsOffset int) TZD {
|
||||||
|
return TZD{
|
||||||
|
Offset: secondsOffset,
|
||||||
|
HasTZ: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Location returns an appropriate *time.Location (time.UTC or time.FixedZone),
|
||||||
|
// or defaultLoc if `v` contains no offset information.
|
||||||
|
func (tzd TZD) Location(defaultLoc *time.Location) *time.Location {
|
||||||
|
if !tzd.HasTZ {
|
||||||
|
return defaultLoc
|
||||||
|
}
|
||||||
|
if tzd.Offset == 0 {
|
||||||
|
return time.UTC
|
||||||
|
}
|
||||||
|
return time.FixedZone(tzd.String(), tzd.Offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tzd TZD) String() string {
|
||||||
|
if !tzd.HasTZ {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if tzd.Offset == 0 {
|
||||||
|
return "Z"
|
||||||
|
}
|
||||||
|
|
||||||
|
offsetMins := tzd.Offset / 60
|
||||||
|
sign := '+'
|
||||||
|
if offsetMins < 1 {
|
||||||
|
offsetMins = -offsetMins
|
||||||
|
sign = '-'
|
||||||
|
}
|
||||||
|
h, m := offsetMins/60, offsetMins%60
|
||||||
|
return fmt.Sprintf("%c%02d:%02d", sign, h, m)
|
||||||
|
}
|
||||||
|
|
||||||
|
// clear removes offset information from `v`.
|
||||||
|
func (tzd *TZD) clear() {
|
||||||
|
tzd.Offset = 0
|
||||||
|
tzd.HasTZ = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// (+|-)(hh):(mm)
|
||||||
|
var timezoneRegexp = regexp.MustCompile(`^([+-])(\d{2}):(\d{2})$`)
|
||||||
|
|
||||||
|
func (tzd *TZD) unmarshal(s string) error {
|
||||||
|
tzd.clear()
|
||||||
|
if s == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
tzd.HasTZ = true
|
||||||
|
if s == "Z" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
parts := timezoneRegexp.FindStringSubmatch(s)
|
||||||
|
if parts == nil {
|
||||||
|
return fmt.Errorf("value %q is not in ISO8601 timezone format", s)
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
tzd.Offset = parseInt(parts[2], &err) * 3600
|
||||||
|
tzd.Offset += parseInt(parts[3], &err) * 60
|
||||||
|
if parts[1] == "-" {
|
||||||
|
tzd.Offset = -tzd.Offset
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *DateTimeTZLocal) Unmarshal(s string) error {
|
|
||||||
dateStr, timeStr, zoneStr, err := splitCompleteDateTimeZone(s)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
err = fmt.Errorf("value %q is not in ISO8601 timezone format: %v", s, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var date Date
|
|
||||||
if err := date.Unmarshal(dateStr); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var tod TimeOfDay
|
|
||||||
var location *time.Location = localLoc
|
|
||||||
if len(timeStr) != 0 {
|
|
||||||
tod, err = parseTimeParts(timeStr)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if len(zoneStr) != 0 {
|
|
||||||
var offset int
|
|
||||||
offset, err = parseTimezone(zoneStr)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if offset == 0 {
|
|
||||||
location = time.UTC
|
|
||||||
} else {
|
|
||||||
location = time.FixedZone("", offset)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
*v = DateTimeTZLocal(time.Date(date.Year, time.Month(date.Month), date.Day,
|
|
||||||
int(tod.Hour), int(tod.Minute), int(tod.Second), 0,
|
|
||||||
location))
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,6 +8,8 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var dummyLoc = time.FixedZone("DummyTZ", 6*3600)
|
||||||
|
|
||||||
func newFixed14_4Parts(intPart int64, fracPart int16) *Fixed14_4 {
|
func newFixed14_4Parts(intPart int64, fracPart int16) *Fixed14_4 {
|
||||||
v, err := Fixed14_4FromParts(intPart, fracPart)
|
v, err := Fixed14_4FromParts(intPart, fracPart)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -38,12 +40,6 @@ type unmarshalCase struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func Test(t *testing.T) {
|
func Test(t *testing.T) {
|
||||||
// Fake out the local time for the implementation.
|
|
||||||
localLoc = time.FixedZone("Fake/Local", 6*3600)
|
|
||||||
defer func() {
|
|
||||||
localLoc = time.Local
|
|
||||||
}()
|
|
||||||
|
|
||||||
badNumbers := []string{"", " ", "abc"}
|
badNumbers := []string{"", " ", "abc"}
|
||||||
|
|
||||||
typeTestCases := []typeTestCase{
|
typeTestCases := []typeTestCase{
|
||||||
@ -193,7 +189,7 @@ func Test(t *testing.T) {
|
|||||||
{
|
{
|
||||||
makeValue: func() SOAPValue { return new(TimeOfDay) },
|
makeValue: func() SOAPValue { return new(TimeOfDay) },
|
||||||
isEqual: func(got, want SOAPValue) bool {
|
isEqual: func(got, want SOAPValue) bool {
|
||||||
return got.(*TimeOfDay).Equal(want.(*TimeOfDay))
|
return got.(*TimeOfDay).equal(*want.(*TimeOfDay))
|
||||||
},
|
},
|
||||||
marshalTests: []marshalCase{
|
marshalTests: []marshalCase{
|
||||||
{&TimeOfDay{}, "00:00:00"},
|
{&TimeOfDay{}, "00:00:00"},
|
||||||
@ -216,36 +212,29 @@ func Test(t *testing.T) {
|
|||||||
"00:00:60",
|
"00:00:60",
|
||||||
// Unexpected timezone component:
|
// Unexpected timezone component:
|
||||||
"01:02:03Z",
|
"01:02:03Z",
|
||||||
"01:02:03+01",
|
|
||||||
"01:02:03+01:23",
|
"01:02:03+01:23",
|
||||||
"01:02:03+0123",
|
"01:02:03+01:23",
|
||||||
"01:02:03-01",
|
"01:02:03-01:23",
|
||||||
"01:02:03-01:23",
|
"01:02:03-01:23",
|
||||||
"01:02:03-0123",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
makeValue: func() SOAPValue { return new(TimeOfDayTZ) },
|
makeValue: func() SOAPValue { return new(TimeOfDayTZ) },
|
||||||
isEqual: func(got, want SOAPValue) bool {
|
isEqual: func(got, want SOAPValue) bool {
|
||||||
return got.(*TimeOfDayTZ).Equal(want.(*TimeOfDayTZ))
|
return got.(*TimeOfDayTZ).equal(*want.(*TimeOfDayTZ))
|
||||||
},
|
},
|
||||||
marshalTests: []marshalCase{
|
marshalTests: []marshalCase{
|
||||||
{&TimeOfDayTZ{}, "00:00:00"},
|
{&TimeOfDayTZ{}, "00:00:00"},
|
||||||
// ISO8601 special case
|
// ISO8601 special case
|
||||||
{&TimeOfDayTZ{TimeOfDay{24, 0, 0}, false, 0}, "24:00:00"},
|
{&TimeOfDayTZ{TimeOfDay{24, 0, 0}, TZD{}}, "24:00:00"},
|
||||||
{&TimeOfDayTZ{TimeOfDay{1, 2, 3}, true, 0}, "01:02:03Z"},
|
{&TimeOfDayTZ{TimeOfDay{1, 2, 3}, TZDOffset(0)}, "01:02:03Z"},
|
||||||
{&TimeOfDayTZ{TimeOfDay{1, 2, 3}, true, 3600 + 23*60}, "01:02:03+01:23"},
|
{&TimeOfDayTZ{TimeOfDay{1, 2, 3}, TZDOffset(3600 + 23*60)}, "01:02:03+01:23"},
|
||||||
{&TimeOfDayTZ{TimeOfDay{1, 2, 3}, true, -(3600 + 23*60)}, "01:02:03-01:23"},
|
{&TimeOfDayTZ{TimeOfDay{1, 2, 3}, TZDOffset(-(3600 + 23*60))}, "01:02:03-01:23"},
|
||||||
},
|
},
|
||||||
unmarshalTests: []unmarshalCase{
|
unmarshalTests: []unmarshalCase{
|
||||||
{"000000", &TimeOfDayTZ{}},
|
{"010203+01:23", &TimeOfDayTZ{TimeOfDay{1, 2, 3}, TZDOffset(3600 + 23*60)}},
|
||||||
{"01Z", &TimeOfDayTZ{TimeOfDay{1, 0, 0}, true, 0}},
|
{"010203-01:23", &TimeOfDayTZ{TimeOfDay{1, 2, 3}, TZDOffset(-(3600 + 23*60))}},
|
||||||
{"01+01", &TimeOfDayTZ{TimeOfDay{1, 0, 0}, true, 3600}},
|
|
||||||
{"01:02:03+01", &TimeOfDayTZ{TimeOfDay{1, 2, 3}, true, 3600}},
|
|
||||||
{"01:02:03+0123", &TimeOfDayTZ{TimeOfDay{1, 2, 3}, true, 3600 + 23*60}},
|
|
||||||
{"01:02:03-01", &TimeOfDayTZ{TimeOfDay{1, 2, 3}, true, -3600}},
|
|
||||||
{"01:02:03-0123", &TimeOfDayTZ{TimeOfDay{1, 2, 3}, true, -(3600 + 23*60)}},
|
|
||||||
},
|
},
|
||||||
unmarshalErrs: []string{
|
unmarshalErrs: []string{
|
||||||
// Misformatted values:
|
// Misformatted values:
|
||||||
@ -275,44 +264,39 @@ func Test(t *testing.T) {
|
|||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
makeValue: func() SOAPValue { return new(DateTimeLocal) },
|
makeValue: func() SOAPValue { return new(DateTime) },
|
||||||
isEqual: func(got, want SOAPValue) bool {
|
isEqual: func(got, want SOAPValue) bool {
|
||||||
return got.(*DateTimeLocal).ToTime().Equal(want.(*DateTimeLocal).ToTime())
|
return got.(*DateTime).equal(*want.(*DateTime))
|
||||||
},
|
},
|
||||||
marshalTests: []marshalCase{
|
marshalTests: []marshalCase{
|
||||||
{NewDateTimeLocal(time.Date(2013, 10, 8, 0, 0, 0, 0, localLoc)), "2013-10-08T00:00:00"},
|
{DateTimeFromTime(time.Date(2013, 10, 8, 0, 0, 0, 0, dummyLoc)).ptr(), "2013-10-08T00:00:00"},
|
||||||
{NewDateTimeLocal(time.Date(2013, 10, 8, 10, 30, 50, 0, localLoc)), "2013-10-08T10:30:50"},
|
{DateTimeFromTime(time.Date(2013, 10, 8, 10, 30, 50, 0, dummyLoc)).ptr(), "2013-10-08T10:30:50"},
|
||||||
},
|
},
|
||||||
unmarshalTests: []unmarshalCase{
|
unmarshalTests: []unmarshalCase{
|
||||||
{"20131008", NewDateTimeLocal(time.Date(2013, 10, 8, 0, 0, 0, 0, localLoc))},
|
{"20131008", DateTimeFromTime(time.Date(2013, 10, 8, 0, 0, 0, 0, dummyLoc)).ptr()},
|
||||||
},
|
},
|
||||||
unmarshalErrs: []string{
|
unmarshalErrs: []string{
|
||||||
// Unexpected timezone component.
|
// Unexpected timezone component.
|
||||||
"2013-10-08T10:30:50+01",
|
"2013-10-08T10:30:50+01:00",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
makeValue: func() SOAPValue { return new(DateTimeTZLocal) },
|
makeValue: func() SOAPValue { return new(DateTimeTZ) },
|
||||||
isEqual: func(got, want SOAPValue) bool {
|
isEqual: func(got, want SOAPValue) bool {
|
||||||
return got.(*DateTimeTZLocal).ToTime().Equal(want.(*DateTimeTZLocal).ToTime())
|
return got.(*DateTimeTZ).equal(*want.(*DateTimeTZ))
|
||||||
},
|
},
|
||||||
marshalTests: []marshalCase{
|
marshalTests: []marshalCase{
|
||||||
{NewDateTimeTZLocal(time.Date(2013, 10, 8, 0, 0, 0, 0, localLoc)), "2013-10-08T00:00:00+06:00"},
|
{DateTimeTZFromTime(time.Date(2013, 10, 8, 0, 0, 0, 0, dummyLoc)).ptr(), "2013-10-08T00:00:00+06:00"},
|
||||||
{NewDateTimeTZLocal(time.Date(2013, 10, 8, 10, 30, 50, 0, localLoc)), "2013-10-08T10:30:50+06:00"},
|
{DateTimeTZFromTime(time.Date(2013, 10, 8, 10, 30, 50, 0, dummyLoc)).ptr(), "2013-10-08T10:30:50+06:00"},
|
||||||
{NewDateTimeTZLocal(time.Date(2013, 10, 8, 0, 0, 0, 0, time.UTC)), "2013-10-08T00:00:00+00:00"},
|
{DateTimeTZFromTime(time.Date(2013, 10, 8, 0, 0, 0, 0, time.UTC)).ptr(), "2013-10-08T00:00:00Z"},
|
||||||
{NewDateTimeTZLocal(time.Date(2013, 10, 8, 10, 30, 50, 0, time.UTC)), "2013-10-08T10:30:50+00:00"},
|
{DateTimeTZFromTime(time.Date(2013, 10, 8, 10, 30, 50, 0, time.UTC)).ptr(), "2013-10-08T10:30:50Z"},
|
||||||
{NewDateTimeTZLocal(time.Date(2013, 10, 8, 10, 30, 50, 0, time.FixedZone("+01:23", 3600+23*60))), "2013-10-08T10:30:50+01:23"},
|
{DateTimeTZFromTime(time.Date(2013, 10, 8, 10, 30, 50, 0, time.FixedZone("+01:23", 3600+23*60))).ptr(), "2013-10-08T10:30:50+01:23"},
|
||||||
{NewDateTimeTZLocal(time.Date(2013, 10, 8, 10, 30, 50, 0, time.FixedZone("-01:23", -(3600+23*60)))), "2013-10-08T10:30:50-01:23"},
|
{DateTimeTZFromTime(time.Date(2013, 10, 8, 10, 30, 50, 0, time.FixedZone("-01:23", -(3600+23*60)))).ptr(), "2013-10-08T10:30:50-01:23"},
|
||||||
},
|
},
|
||||||
unmarshalTests: []unmarshalCase{
|
unmarshalTests: []unmarshalCase{
|
||||||
{"20131008", NewDateTimeTZLocal(time.Date(2013, 10, 8, 0, 0, 0, 0, localLoc))},
|
{"2013-10-08T10:30:50", &DateTimeTZ{Date{2013, 10, 8}, TimeOfDay{10, 30, 50}, TZD{}}},
|
||||||
{"2013-10-08T10:30:50", NewDateTimeTZLocal(time.Date(2013, 10, 8, 10, 30, 50, 0, localLoc))},
|
{"2013-10-08T10:30:50+00:00", DateTimeTZFromTime(time.Date(2013, 10, 8, 10, 30, 50, 0, time.UTC)).ptr()},
|
||||||
{"2013-10-08T10:30:50Z", NewDateTimeTZLocal(time.Date(2013, 10, 8, 10, 30, 50, 0, time.UTC))},
|
|
||||||
{"2013-10-08T10:30:50+01", NewDateTimeTZLocal(time.Date(2013, 10, 8, 10, 30, 50, 0, time.FixedZone("+01:00", 3600)))},
|
|
||||||
{"2013-10-08T10:30:50+0123", NewDateTimeTZLocal(time.Date(2013, 10, 8, 10, 30, 50, 0, time.FixedZone("+01:23", 3600+23*60)))},
|
|
||||||
{"2013-10-08T10:30:50-01", NewDateTimeTZLocal(time.Date(2013, 10, 8, 10, 30, 50, 0, time.FixedZone("-01:00", -3600)))},
|
|
||||||
{"2013-10-08T10:30:50-0123", NewDateTimeTZLocal(time.Date(2013, 10, 8, 10, 30, 50, 0, time.FixedZone("-01:23", -(3600+23*60))))},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -411,7 +395,7 @@ func Test(t *testing.T) {
|
|||||||
t.Run(fmt.Sprintf("unmarshalTest#%d_%q", i, ut.input), func(t *testing.T) {
|
t.Run(fmt.Sprintf("unmarshalTest#%d_%q", i, ut.input), func(t *testing.T) {
|
||||||
got := tt.makeValue()
|
got := tt.makeValue()
|
||||||
if err := got.Unmarshal(ut.input); err != nil {
|
if err := got.Unmarshal(ut.input); err != nil {
|
||||||
t.Errorf("got error, want success")
|
t.Errorf("got unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
if !tt.isEqual(got, ut.want) {
|
if !tt.isEqual(got, ut.want) {
|
||||||
t.Errorf("got %v, want %v", got, ut.want)
|
t.Errorf("got %v, want %v", got, ut.want)
|
||||||
@ -518,3 +502,35 @@ func TestFixed14_4(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// methods only used in testing:
|
||||||
|
|
||||||
|
func (v TimeOfDay) equal(o TimeOfDay) bool {
|
||||||
|
return v.Hour == o.Hour && v.Minute == o.Minute && v.Second == o.Second
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v TimeOfDayTZ) equal(o TimeOfDayTZ) bool {
|
||||||
|
return v.TimeOfDay.equal(o.TimeOfDay) && v.TZ.equal(o.TZ)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d Date) equal(o Date) bool {
|
||||||
|
return d.Year == o.Year && d.Month == o.Month && d.Day == o.Day
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dtz DateTime) ptr() *DateTime { return &dtz }
|
||||||
|
|
||||||
|
func (dt DateTime) equal(o DateTime) bool {
|
||||||
|
return dt.Date.equal(o.Date) && dt.TimeOfDay.equal(o.TimeOfDay)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dtz DateTimeTZ) ptr() *DateTimeTZ { return &dtz }
|
||||||
|
|
||||||
|
func (dtz DateTimeTZ) equal(o DateTimeTZ) bool {
|
||||||
|
return dtz.Date.equal(o.Date) &&
|
||||||
|
dtz.TimeOfDay.equal(o.TimeOfDay) &&
|
||||||
|
dtz.TZ.equal(o.TZ)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tzd TZD) equal(o TZD) bool {
|
||||||
|
return tzd.Offset == o.Offset && tzd.HasTZ == o.HasTZ
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user