Provide own implementation of Local* (#558)
* Reduces the public API. * Reuses optimized parsing functions. * Removes reliance on Google code under Apache license.
This commit is contained in:
@@ -39,7 +39,7 @@ func parseLocalDate(b []byte) (LocalDate, error) {
|
|||||||
|
|
||||||
v := parseDecimalDigits(b[5:7])
|
v := parseDecimalDigits(b[5:7])
|
||||||
|
|
||||||
date.Month = time.Month(v)
|
date.Month = v
|
||||||
|
|
||||||
date.Day = parseDecimalDigits(b[8:10])
|
date.Day = parseDecimalDigits(b[8:10])
|
||||||
|
|
||||||
@@ -100,13 +100,13 @@ func parseDateTime(b []byte) (time.Time, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
t := time.Date(
|
t := time.Date(
|
||||||
dt.Date.Year,
|
dt.Year,
|
||||||
dt.Date.Month,
|
time.Month(dt.Month),
|
||||||
dt.Date.Day,
|
dt.Day,
|
||||||
dt.Time.Hour,
|
dt.Hour,
|
||||||
dt.Time.Minute,
|
dt.Minute,
|
||||||
dt.Time.Second,
|
dt.Second,
|
||||||
dt.Time.Nanosecond,
|
dt.Nanosecond,
|
||||||
zone)
|
zone)
|
||||||
|
|
||||||
return t, nil
|
return t, nil
|
||||||
@@ -124,7 +124,7 @@ func parseLocalDateTime(b []byte) (LocalDateTime, []byte, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return dt, nil, err
|
return dt, nil, err
|
||||||
}
|
}
|
||||||
dt.Date = date
|
dt.LocalDate = date
|
||||||
|
|
||||||
sep := b[10]
|
sep := b[10]
|
||||||
if sep != 'T' && sep != ' ' {
|
if sep != 'T' && sep != ' ' {
|
||||||
@@ -135,7 +135,7 @@ func parseLocalDateTime(b []byte) (LocalDateTime, []byte, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return dt, nil, err
|
return dt, nil, err
|
||||||
}
|
}
|
||||||
dt.Time = t
|
dt.LocalTime = t
|
||||||
|
|
||||||
return dt, rest, nil
|
return dt, rest, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1487,12 +1487,12 @@ func TestUnmarshalLocalDateTime(t *testing.T) {
|
|||||||
name: "normal",
|
name: "normal",
|
||||||
in: "1979-05-27T07:32:00",
|
in: "1979-05-27T07:32:00",
|
||||||
out: toml.LocalDateTime{
|
out: toml.LocalDateTime{
|
||||||
Date: toml.LocalDate{
|
LocalDate: toml.LocalDate{
|
||||||
Year: 1979,
|
Year: 1979,
|
||||||
Month: 5,
|
Month: 5,
|
||||||
Day: 27,
|
Day: 27,
|
||||||
},
|
},
|
||||||
Time: toml.LocalTime{
|
LocalTime: toml.LocalTime{
|
||||||
Hour: 7,
|
Hour: 7,
|
||||||
Minute: 32,
|
Minute: 32,
|
||||||
Second: 0,
|
Second: 0,
|
||||||
@@ -1504,12 +1504,12 @@ func TestUnmarshalLocalDateTime(t *testing.T) {
|
|||||||
name: "with nanoseconds",
|
name: "with nanoseconds",
|
||||||
in: "1979-05-27T00:32:00.999999",
|
in: "1979-05-27T00:32:00.999999",
|
||||||
out: toml.LocalDateTime{
|
out: toml.LocalDateTime{
|
||||||
Date: toml.LocalDate{
|
LocalDate: toml.LocalDate{
|
||||||
Year: 1979,
|
Year: 1979,
|
||||||
Month: 5,
|
Month: 5,
|
||||||
Day: 27,
|
Day: 27,
|
||||||
},
|
},
|
||||||
Time: toml.LocalTime{
|
LocalTime: toml.LocalTime{
|
||||||
Hour: 0,
|
Hour: 0,
|
||||||
Minute: 32,
|
Minute: 32,
|
||||||
Second: 0,
|
Second: 0,
|
||||||
@@ -1551,26 +1551,26 @@ func TestUnmarshalLocalDateTime(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if obj.Date.Year() != example.out.Date.Year {
|
if obj.Date.Year() != example.out.Year {
|
||||||
t.Errorf("expected year %d, got %d", example.out.Date.Year, obj.Date.Year())
|
t.Errorf("expected year %d, got %d", example.out.Year, obj.Date.Year())
|
||||||
}
|
}
|
||||||
if obj.Date.Month() != example.out.Date.Month {
|
if obj.Date.Month() != time.Month(example.out.Month) {
|
||||||
t.Errorf("expected month %d, got %d", example.out.Date.Month, obj.Date.Month())
|
t.Errorf("expected month %d, got %d", example.out.Month, obj.Date.Month())
|
||||||
}
|
}
|
||||||
if obj.Date.Day() != example.out.Date.Day {
|
if obj.Date.Day() != example.out.Day {
|
||||||
t.Errorf("expected day %d, got %d", example.out.Date.Day, obj.Date.Day())
|
t.Errorf("expected day %d, got %d", example.out.Day, obj.Date.Day())
|
||||||
}
|
}
|
||||||
if obj.Date.Hour() != example.out.Time.Hour {
|
if obj.Date.Hour() != example.out.Hour {
|
||||||
t.Errorf("expected hour %d, got %d", example.out.Time.Hour, obj.Date.Hour())
|
t.Errorf("expected hour %d, got %d", example.out.Hour, obj.Date.Hour())
|
||||||
}
|
}
|
||||||
if obj.Date.Minute() != example.out.Time.Minute {
|
if obj.Date.Minute() != example.out.Minute {
|
||||||
t.Errorf("expected minute %d, got %d", example.out.Time.Minute, obj.Date.Minute())
|
t.Errorf("expected minute %d, got %d", example.out.Minute, obj.Date.Minute())
|
||||||
}
|
}
|
||||||
if obj.Date.Second() != example.out.Time.Second {
|
if obj.Date.Second() != example.out.Second {
|
||||||
t.Errorf("expected second %d, got %d", example.out.Time.Second, obj.Date.Second())
|
t.Errorf("expected second %d, got %d", example.out.Second, obj.Date.Second())
|
||||||
}
|
}
|
||||||
if obj.Date.Nanosecond() != example.out.Time.Nanosecond {
|
if obj.Date.Nanosecond() != example.out.Nanosecond {
|
||||||
t.Errorf("expected nanoseconds %d, got %d", example.out.Time.Nanosecond, obj.Date.Nanosecond())
|
t.Errorf("expected nanoseconds %d, got %d", example.out.Nanosecond, obj.Date.Nanosecond())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
+74
-265
@@ -1,29 +1,3 @@
|
|||||||
// Implementation of TOML's local date/time.
|
|
||||||
// Copied over from https://github.com/googleapis/google-cloud-go/blob/master/civil/civil.go
|
|
||||||
// to avoid pulling all the Google dependencies.
|
|
||||||
//
|
|
||||||
// Copyright 2016 Google LLC
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
// Package civil implements types for civil time, a time-zone-independent
|
|
||||||
// representation of time that follows the rules of the proleptic
|
|
||||||
// Gregorian calendar with exactly 24-hour days, 60-minute hours, and 60-second
|
|
||||||
// minutes.
|
|
||||||
//
|
|
||||||
// Because they lack location information, these types do not represent unique
|
|
||||||
// moments or intervals of time. Use time.Time for that purpose.
|
|
||||||
|
|
||||||
package toml
|
package toml
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -31,270 +5,105 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// A LocalDate represents a date (year, month, day).
|
// LocalDate represents a calendar day in no specific timezone.
|
||||||
//
|
|
||||||
// This type does not include location information, and therefore does not
|
|
||||||
// describe a unique 24-hour timespan.
|
|
||||||
type LocalDate struct {
|
type LocalDate struct {
|
||||||
Year int // Year (e.g., 2014).
|
Year int
|
||||||
Month time.Month // Month of the year (January = 1, ...).
|
Month int
|
||||||
Day int // Day of the month, starting at 1.
|
Day int
|
||||||
}
|
}
|
||||||
|
|
||||||
// LocalDateOf returns the LocalDate in which a time occurs in that time's location.
|
// AsTime converts d into a specific time instance at midnight in zone.
|
||||||
func LocalDateOf(t time.Time) LocalDate {
|
func (d LocalDate) AsTime(zone *time.Location) time.Time {
|
||||||
var d LocalDate
|
return time.Date(d.Year, time.Month(d.Month), d.Day, 0, 0, 0, 0, zone)
|
||||||
d.Year, d.Month, d.Day = t.Date()
|
|
||||||
|
|
||||||
return d
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseLocalDate parses a string in RFC3339 full-date format and returns the date value it represents.
|
// String returns RFC 3339 representation of d.
|
||||||
func ParseLocalDate(s string) (LocalDate, error) {
|
|
||||||
t, err := time.Parse("2006-01-02", s)
|
|
||||||
if err != nil {
|
|
||||||
return LocalDate{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return LocalDateOf(t), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns the date in RFC3339 full-date format.
|
|
||||||
func (d LocalDate) String() string {
|
func (d LocalDate) 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsValid reports whether the date is valid.
|
// MarshalText returns RFC 3339 representation of d.
|
||||||
func (d LocalDate) IsValid() bool {
|
|
||||||
return LocalDateOf(d.In(time.UTC)) == d
|
|
||||||
}
|
|
||||||
|
|
||||||
// In returns the time corresponding to time 00:00:00 of the date in the location.
|
|
||||||
//
|
|
||||||
// In is always consistent with time.LocalDate, even when time.LocalDate returns a time
|
|
||||||
// on a different day. For example, if loc is America/Indiana/Vincennes, then both
|
|
||||||
// time.LocalDate(1955, time.May, 1, 0, 0, 0, 0, loc)
|
|
||||||
// and
|
|
||||||
// civil.LocalDate{Year: 1955, Month: time.May, Day: 1}.In(loc)
|
|
||||||
// return 23:00:00 on April 30, 1955.
|
|
||||||
//
|
|
||||||
// In panics if loc is nil.
|
|
||||||
func (d LocalDate) In(loc *time.Location) time.Time {
|
|
||||||
return time.Date(d.Year, d.Month, d.Day, 0, 0, 0, 0, loc)
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddDays returns the date that is n days in the future.
|
|
||||||
// n can also be negative to go into the past.
|
|
||||||
func (d LocalDate) AddDays(n int) LocalDate {
|
|
||||||
return LocalDateOf(d.In(time.UTC).AddDate(0, 0, n))
|
|
||||||
}
|
|
||||||
|
|
||||||
// DaysSince returns the signed number of days between the date and s, not including the end day.
|
|
||||||
// This is the inverse operation to AddDays.
|
|
||||||
func (d LocalDate) DaysSince(s LocalDate) (days int) {
|
|
||||||
// We convert to Unix time so we do not have to worry about leap seconds:
|
|
||||||
// Unix time increases by exactly 86400 seconds per day.
|
|
||||||
deltaUnix := d.In(time.UTC).Unix() - s.In(time.UTC).Unix()
|
|
||||||
|
|
||||||
const secondsInADay = 86400
|
|
||||||
|
|
||||||
return int(deltaUnix / secondsInADay)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Before reports whether d1 occurs before future date.
|
|
||||||
func (d LocalDate) Before(future LocalDate) bool {
|
|
||||||
if d.Year != future.Year {
|
|
||||||
return d.Year < future.Year
|
|
||||||
}
|
|
||||||
|
|
||||||
if d.Month != future.Month {
|
|
||||||
return d.Month < future.Month
|
|
||||||
}
|
|
||||||
|
|
||||||
return d.Day < future.Day
|
|
||||||
}
|
|
||||||
|
|
||||||
// After reports whether d1 occurs after past date.
|
|
||||||
func (d LocalDate) After(past LocalDate) bool {
|
|
||||||
return past.Before(d)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalText implements the encoding.TextMarshaler interface.
|
|
||||||
// The output is the result of d.String().
|
|
||||||
func (d LocalDate) MarshalText() ([]byte, error) {
|
func (d LocalDate) MarshalText() ([]byte, error) {
|
||||||
return []byte(d.String()), nil
|
return []byte(d.String()), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalText implements the encoding.TextUnmarshaler interface.
|
// UnmarshalText parses b using RFC 3339 to fill d.
|
||||||
// The date is expected to be a string in a format accepted by ParseLocalDate.
|
func (d *LocalDate) UnmarshalText(b []byte) error {
|
||||||
func (d *LocalDate) UnmarshalText(data []byte) error {
|
res, err := parseLocalDate(b)
|
||||||
var err error
|
|
||||||
*d, err = ParseLocalDate(string(data))
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// A LocalTime represents a time with nanosecond precision.
|
|
||||||
//
|
|
||||||
// This type does not include location information, and therefore does not
|
|
||||||
// describe a unique moment in time.
|
|
||||||
//
|
|
||||||
// This type exists to represent the TIME type in storage-based APIs like BigQuery.
|
|
||||||
// Most operations on Times are unlikely to be meaningful. Prefer the LocalDateTime type.
|
|
||||||
type LocalTime struct {
|
|
||||||
Hour int // The hour of the day in 24-hour format; range [0-23]
|
|
||||||
Minute int // The minute of the hour; range [0-59]
|
|
||||||
Second int // The second of the minute; range [0-59]
|
|
||||||
Nanosecond int // The nanosecond of the second; range [0-999999999]
|
|
||||||
}
|
|
||||||
|
|
||||||
// LocalTimeOf returns the LocalTime representing the time of day in which a time occurs
|
|
||||||
// in that time's location. It ignores the date.
|
|
||||||
func LocalTimeOf(t time.Time) LocalTime {
|
|
||||||
var tm LocalTime
|
|
||||||
tm.Hour, tm.Minute, tm.Second = t.Clock()
|
|
||||||
tm.Nanosecond = t.Nanosecond()
|
|
||||||
|
|
||||||
return tm
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseLocalTime parses a string and returns the time value it represents.
|
|
||||||
// ParseLocalTime accepts an extended form of the RFC3339 partial-time format. After
|
|
||||||
// the HH:MM:SS part of the string, an optional fractional part may appear,
|
|
||||||
// consisting of a decimal point followed by one to nine decimal digits.
|
|
||||||
// (RFC3339 admits only one digit after the decimal point).
|
|
||||||
func ParseLocalTime(s string) (LocalTime, error) {
|
|
||||||
t, err := time.Parse("15:04:05.999999999", s)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return LocalTime{}, err
|
return err
|
||||||
}
|
}
|
||||||
|
*d = res
|
||||||
return LocalTimeOf(t), nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// String returns the date in the format described in ParseLocalTime. If Nanoseconds
|
// LocalTime represents a time of day of no specific day in no specific
|
||||||
// is zero, no fractional part will be generated. Otherwise, the result will
|
// timezone.
|
||||||
// end with a fractional part consisting of a decimal point and nine digits.
|
type LocalTime struct {
|
||||||
func (t LocalTime) String() string {
|
Hour int
|
||||||
s := fmt.Sprintf("%02d:%02d:%02d", t.Hour, t.Minute, t.Second)
|
Minute int
|
||||||
if t.Nanosecond == 0 {
|
Second int
|
||||||
|
Nanosecond int
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns RFC 3339 representation of d.
|
||||||
|
func (d LocalTime) String() string {
|
||||||
|
s := fmt.Sprintf("%02d:%02d:%02d", d.Hour, d.Minute, d.Second)
|
||||||
|
if d.Nanosecond == 0 {
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
return s + fmt.Sprintf(".%09d", d.Nanosecond)
|
||||||
return s + fmt.Sprintf(".%09d", t.Nanosecond)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsValid reports whether the time is valid.
|
// MarshalText returns RFC 3339 representation of d.
|
||||||
func (t LocalTime) IsValid() bool {
|
func (d LocalTime) MarshalText() ([]byte, error) {
|
||||||
// Construct a non-zero time.
|
return []byte(d.String()), nil
|
||||||
tm := time.Date(2, 2, 2, t.Hour, t.Minute, t.Second, t.Nanosecond, time.UTC)
|
|
||||||
|
|
||||||
return LocalTimeOf(tm) == t
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarshalText implements the encoding.TextMarshaler interface.
|
// UnmarshalText parses b using RFC 3339 to fill d.
|
||||||
// The output is the result of t.String().
|
func (d *LocalTime) UnmarshalText(b []byte) error {
|
||||||
func (t LocalTime) MarshalText() ([]byte, error) {
|
res, left, err := parseLocalTime(b)
|
||||||
return []byte(t.String()), nil
|
if err == nil && len(left) != 0 {
|
||||||
}
|
err = newDecodeError(left, "extra characters")
|
||||||
|
|
||||||
// UnmarshalText implements the encoding.TextUnmarshaler interface.
|
|
||||||
// The time is expected to be a string in a format accepted by ParseLocalTime.
|
|
||||||
func (t *LocalTime) UnmarshalText(data []byte) error {
|
|
||||||
var err error
|
|
||||||
*t, err = ParseLocalTime(string(data))
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// A LocalDateTime represents a date and time.
|
|
||||||
//
|
|
||||||
// This type does not include location information, and therefore does not
|
|
||||||
// describe a unique moment in time.
|
|
||||||
type LocalDateTime struct {
|
|
||||||
Date LocalDate
|
|
||||||
Time LocalTime
|
|
||||||
}
|
|
||||||
|
|
||||||
// Note: We deliberately do not embed LocalDate into LocalDateTime, to avoid promoting AddDays and Sub.
|
|
||||||
|
|
||||||
// LocalDateTimeOf returns the LocalDateTime in which a time occurs in that time's location.
|
|
||||||
func LocalDateTimeOf(t time.Time) LocalDateTime {
|
|
||||||
return LocalDateTime{
|
|
||||||
Date: LocalDateOf(t),
|
|
||||||
Time: LocalTimeOf(t),
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// ParseLocalDateTime parses a string and returns the LocalDateTime it represents.
|
|
||||||
// ParseLocalDateTime accepts a variant of the RFC3339 date-time format that omits
|
|
||||||
// the time offset but includes an optional fractional time, as described in
|
|
||||||
// ParseLocalTime. Informally, the accepted format is
|
|
||||||
// YYYY-MM-DDTHH:MM:SS[.FFFFFFFFF]
|
|
||||||
// where the 'T' may be a lower-case 't'.
|
|
||||||
func ParseLocalDateTime(s string) (LocalDateTime, error) {
|
|
||||||
t, err := time.Parse("2006-01-02T15:04:05.999999999", s)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t, err = time.Parse("2006-01-02t15:04:05.999999999", s)
|
return err
|
||||||
if err != nil {
|
}
|
||||||
return LocalDateTime{}, err
|
*d = res
|
||||||
}
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LocalDateTime represents a time of a specific day in no specific timezone.
|
||||||
|
type LocalDateTime struct {
|
||||||
|
LocalDate
|
||||||
|
LocalTime
|
||||||
|
}
|
||||||
|
|
||||||
|
// AsTime converts d into a specific time instance in zone.
|
||||||
|
func (d LocalDateTime) AsTime(zone *time.Location) time.Time {
|
||||||
|
return time.Date(d.Year, time.Month(d.Month), d.Day, d.Hour, d.Minute, d.Second, d.Nanosecond, zone)
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns RFC 3339 representation of d.
|
||||||
|
func (d LocalDateTime) String() string {
|
||||||
|
return d.LocalDate.String() + " " + d.LocalTime.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalText returns RFC 3339 representation of d.
|
||||||
|
func (d LocalDateTime) MarshalText() ([]byte, error) {
|
||||||
|
return []byte(d.String()), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalText parses b using RFC 3339 to fill d.
|
||||||
|
func (d *LocalDateTime) UnmarshalText(data []byte) error {
|
||||||
|
res, left, err := parseLocalDateTime(data)
|
||||||
|
if err == nil && len(left) != 0 {
|
||||||
|
err = newDecodeError(left, "extra characters")
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return LocalDateTimeOf(t), nil
|
*d = res
|
||||||
}
|
return nil
|
||||||
|
|
||||||
// String returns the date in the format described in ParseLocalDate.
|
|
||||||
func (dt LocalDateTime) String() string {
|
|
||||||
return dt.Date.String() + "T" + dt.Time.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsValid reports whether the datetime is valid.
|
|
||||||
func (dt LocalDateTime) IsValid() bool {
|
|
||||||
return dt.Date.IsValid() && dt.Time.IsValid()
|
|
||||||
}
|
|
||||||
|
|
||||||
// In returns the time corresponding to the LocalDateTime in the given location.
|
|
||||||
//
|
|
||||||
// If the time is missing or ambigous at the location, In returns the same
|
|
||||||
// result as time.LocalDate. For example, if loc is America/Indiana/Vincennes, then
|
|
||||||
// both
|
|
||||||
// time.LocalDate(1955, time.May, 1, 0, 30, 0, 0, loc)
|
|
||||||
// and
|
|
||||||
// civil.LocalDateTime{
|
|
||||||
// civil.LocalDate{Year: 1955, Month: time.May, Day: 1}},
|
|
||||||
// civil.LocalTime{Minute: 30}}.In(loc)
|
|
||||||
// return 23:30:00 on April 30, 1955.
|
|
||||||
//
|
|
||||||
// In panics if loc is nil.
|
|
||||||
func (dt LocalDateTime) In(loc *time.Location) time.Time {
|
|
||||||
return time.Date(
|
|
||||||
dt.Date.Year, dt.Date.Month, dt.Date.Day,
|
|
||||||
dt.Time.Hour, dt.Time.Minute, dt.Time.Second, dt.Time.Nanosecond, loc,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Before reports whether dt occurs before future.
|
|
||||||
func (dt LocalDateTime) Before(future LocalDateTime) bool {
|
|
||||||
return dt.In(time.UTC).Before(future.In(time.UTC))
|
|
||||||
}
|
|
||||||
|
|
||||||
// After reports whether dt occurs after past.
|
|
||||||
func (dt LocalDateTime) After(past LocalDateTime) bool {
|
|
||||||
return past.Before(dt)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalText implements the encoding.TextMarshaler interface.
|
|
||||||
// The output is the result of dt.String().
|
|
||||||
func (dt LocalDateTime) MarshalText() ([]byte, error) {
|
|
||||||
return []byte(dt.String()), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalText implements the encoding.TextUnmarshaler interface.
|
|
||||||
// The datetime is expected to be a string in a format accepted by ParseLocalDateTime.
|
|
||||||
func (dt *LocalDateTime) UnmarshalText(data []byte) error {
|
|
||||||
var err error
|
|
||||||
*dt, err = ParseLocalDateTime(string(data))
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|||||||
+73
-455
@@ -1,489 +1,107 @@
|
|||||||
// Copyright 2016 Google LLC
|
package toml_test
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package toml
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/pelletier/go-toml/v2"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func cmpEqual(x, y interface{}) bool {
|
func TestLocalDate_AsTime(t *testing.T) {
|
||||||
return reflect.DeepEqual(x, y)
|
d := toml.LocalDate{2021, 6, 8}
|
||||||
|
cast := d.AsTime(time.UTC)
|
||||||
|
require.Equal(t, time.Date(2021, time.June, 8, 0, 0, 0, 0, time.UTC), cast)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDates(t *testing.T) {
|
func TestLocalDate_String(t *testing.T) {
|
||||||
|
d := toml.LocalDate{2021, 6, 8}
|
||||||
for _, test := range []struct {
|
require.Equal(t, "2021-06-08", d.String())
|
||||||
date LocalDate
|
|
||||||
loc *time.Location
|
|
||||||
wantStr string
|
|
||||||
wantTime time.Time
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
date: LocalDate{2014, 7, 29},
|
|
||||||
loc: time.Local,
|
|
||||||
wantStr: "2014-07-29",
|
|
||||||
wantTime: time.Date(2014, time.July, 29, 0, 0, 0, 0, time.Local),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
date: LocalDateOf(time.Date(2014, 8, 20, 15, 8, 43, 1, time.Local)),
|
|
||||||
loc: time.UTC,
|
|
||||||
wantStr: "2014-08-20",
|
|
||||||
wantTime: time.Date(2014, 8, 20, 0, 0, 0, 0, time.UTC),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
date: LocalDateOf(time.Date(999, time.January, 26, 0, 0, 0, 0, time.Local)),
|
|
||||||
loc: time.UTC,
|
|
||||||
wantStr: "0999-01-26",
|
|
||||||
wantTime: time.Date(999, 1, 26, 0, 0, 0, 0, time.UTC),
|
|
||||||
},
|
|
||||||
} {
|
|
||||||
if got := test.date.String(); got != test.wantStr {
|
|
||||||
t.Errorf("%#v.String() = %q, want %q", test.date, got, test.wantStr)
|
|
||||||
}
|
|
||||||
|
|
||||||
if got := test.date.In(test.loc); !got.Equal(test.wantTime) {
|
|
||||||
t.Errorf("%#v.In(%v) = %v, want %v", test.date, test.loc, got, test.wantTime)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDateIsValid(t *testing.T) {
|
func TestLocalDate_MarshalText(t *testing.T) {
|
||||||
|
d := toml.LocalDate{2021, 6, 8}
|
||||||
for _, test := range []struct {
|
b, err := d.MarshalText()
|
||||||
date LocalDate
|
require.NoError(t, err)
|
||||||
want bool
|
require.Equal(t, []byte("2021-06-08"), b)
|
||||||
}{
|
|
||||||
{LocalDate{2014, 7, 29}, true},
|
|
||||||
{LocalDate{2000, 2, 29}, true},
|
|
||||||
{LocalDate{10000, 12, 31}, true},
|
|
||||||
{LocalDate{1, 1, 1}, true},
|
|
||||||
{LocalDate{0, 1, 1}, true}, // year zero is OK
|
|
||||||
{LocalDate{-1, 1, 1}, true}, // negative year is OK
|
|
||||||
{LocalDate{1, 0, 1}, false},
|
|
||||||
{LocalDate{1, 1, 0}, false},
|
|
||||||
{LocalDate{2016, 1, 32}, false},
|
|
||||||
{LocalDate{2016, 13, 1}, false},
|
|
||||||
{LocalDate{1, -1, 1}, false},
|
|
||||||
{LocalDate{1, 1, -1}, false},
|
|
||||||
} {
|
|
||||||
got := test.date.IsValid()
|
|
||||||
if got != test.want {
|
|
||||||
t.Errorf("%#v: got %t, want %t", test.date, got, test.want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParseDate(t *testing.T) {
|
func TestLocalDate_UnmarshalMarshalText(t *testing.T) {
|
||||||
|
d := toml.LocalDate{}
|
||||||
|
err := d.UnmarshalText([]byte("2021-06-08"))
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, toml.LocalDate{2021, 6, 8}, d)
|
||||||
|
|
||||||
var emptyDate LocalDate
|
err = d.UnmarshalText([]byte("what"))
|
||||||
|
require.Error(t, err)
|
||||||
for _, test := range []struct {
|
|
||||||
str string
|
|
||||||
want LocalDate // if empty, expect an error
|
|
||||||
}{
|
|
||||||
{"2016-01-02", LocalDate{2016, 1, 2}},
|
|
||||||
{"2016-12-31", LocalDate{2016, 12, 31}},
|
|
||||||
{"0003-02-04", LocalDate{3, 2, 4}},
|
|
||||||
{"999-01-26", emptyDate},
|
|
||||||
{"", emptyDate},
|
|
||||||
{"2016-01-02x", emptyDate},
|
|
||||||
} {
|
|
||||||
got, err := ParseLocalDate(test.str)
|
|
||||||
if got != test.want {
|
|
||||||
t.Errorf("ParseLocalDate(%q) = %+v, want %+v", test.str, got, test.want)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil && test.want != (emptyDate) {
|
|
||||||
t.Errorf("Unexpected error %v from ParseLocalDate(%q)", err, test.str)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDateArithmetic(t *testing.T) {
|
func TestLocalTime_String(t *testing.T) {
|
||||||
|
d := toml.LocalTime{20, 12, 1, 2}
|
||||||
for _, test := range []struct {
|
require.Equal(t, "20:12:01.000000002", d.String())
|
||||||
desc string
|
d = toml.LocalTime{20, 12, 1, 0}
|
||||||
start LocalDate
|
require.Equal(t, "20:12:01", d.String())
|
||||||
end LocalDate
|
|
||||||
days int
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
desc: "zero days noop",
|
|
||||||
start: LocalDate{2014, 5, 9},
|
|
||||||
end: LocalDate{2014, 5, 9},
|
|
||||||
days: 0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "crossing a year boundary",
|
|
||||||
start: LocalDate{2014, 12, 31},
|
|
||||||
end: LocalDate{2015, 1, 1},
|
|
||||||
days: 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "negative number of days",
|
|
||||||
start: LocalDate{2015, 1, 1},
|
|
||||||
end: LocalDate{2014, 12, 31},
|
|
||||||
days: -1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "full leap year",
|
|
||||||
start: LocalDate{2004, 1, 1},
|
|
||||||
end: LocalDate{2005, 1, 1},
|
|
||||||
days: 366,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "full non-leap year",
|
|
||||||
start: LocalDate{2001, 1, 1},
|
|
||||||
end: LocalDate{2002, 1, 1},
|
|
||||||
days: 365,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "crossing a leap second",
|
|
||||||
start: LocalDate{1972, 6, 30},
|
|
||||||
end: LocalDate{1972, 7, 1},
|
|
||||||
days: 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "dates before the unix epoch",
|
|
||||||
start: LocalDate{101, 1, 1},
|
|
||||||
end: LocalDate{102, 1, 1},
|
|
||||||
days: 365,
|
|
||||||
},
|
|
||||||
} {
|
|
||||||
if got := test.start.AddDays(test.days); got != test.end {
|
|
||||||
t.Errorf("[%s] %#v.AddDays(%v) = %#v, want %#v", test.desc, test.start, test.days, got, test.end)
|
|
||||||
}
|
|
||||||
|
|
||||||
if got := test.end.DaysSince(test.start); got != test.days {
|
|
||||||
t.Errorf("[%s] %#v.Sub(%#v) = %v, want %v", test.desc, test.end, test.start, got, test.days)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDateBefore(t *testing.T) {
|
func TestLocalTime_MarshalText(t *testing.T) {
|
||||||
|
d := toml.LocalTime{20, 12, 1, 2}
|
||||||
for _, test := range []struct {
|
b, err := d.MarshalText()
|
||||||
d1, d2 LocalDate
|
require.NoError(t, err)
|
||||||
want bool
|
require.Equal(t, []byte("20:12:01.000000002"), b)
|
||||||
}{
|
|
||||||
{LocalDate{2016, 12, 31}, LocalDate{2017, 1, 1}, true},
|
|
||||||
{LocalDate{2016, 1, 1}, LocalDate{2016, 1, 1}, false},
|
|
||||||
{LocalDate{2016, 12, 30}, LocalDate{2016, 12, 31}, true},
|
|
||||||
{LocalDate{2016, 1, 30}, LocalDate{2016, 12, 31}, true},
|
|
||||||
} {
|
|
||||||
if got := test.d1.Before(test.d2); got != test.want {
|
|
||||||
t.Errorf("%v.Before(%v): got %t, want %t", test.d1, test.d2, got, test.want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDateAfter(t *testing.T) {
|
func TestLocalTime_UnmarshalMarshalText(t *testing.T) {
|
||||||
|
d := toml.LocalTime{}
|
||||||
|
err := d.UnmarshalText([]byte("20:12:01.000000002"))
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, toml.LocalTime{20, 12, 1, 2}, d)
|
||||||
|
|
||||||
for _, test := range []struct {
|
err = d.UnmarshalText([]byte("what"))
|
||||||
d1, d2 LocalDate
|
require.Error(t, err)
|
||||||
want bool
|
|
||||||
}{
|
err = d.UnmarshalText([]byte("20:12:01.000000002 bad"))
|
||||||
{LocalDate{2016, 12, 31}, LocalDate{2017, 1, 1}, false},
|
require.Error(t, err)
|
||||||
{LocalDate{2016, 1, 1}, LocalDate{2016, 1, 1}, false},
|
|
||||||
{LocalDate{2016, 12, 30}, LocalDate{2016, 12, 31}, false},
|
|
||||||
} {
|
|
||||||
if got := test.d1.After(test.d2); got != test.want {
|
|
||||||
t.Errorf("%v.After(%v): got %t, want %t", test.d1, test.d2, got, test.want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTimeToString(t *testing.T) {
|
func TestLocalDateTime_AsTime(t *testing.T) {
|
||||||
|
d := toml.LocalDateTime{
|
||||||
for _, test := range []struct {
|
toml.LocalDate{2021, 6, 8},
|
||||||
str string
|
toml.LocalTime{20, 12, 1, 2},
|
||||||
time LocalTime
|
|
||||||
roundTrip bool // ParseLocalTime(str).String() == str?
|
|
||||||
}{
|
|
||||||
{"13:26:33", LocalTime{13, 26, 33, 0}, true},
|
|
||||||
{"01:02:03.000023456", LocalTime{1, 2, 3, 23456}, true},
|
|
||||||
{"00:00:00.000000001", LocalTime{0, 0, 0, 1}, true},
|
|
||||||
{"13:26:03.1", LocalTime{13, 26, 3, 100000000}, false},
|
|
||||||
{"13:26:33.0000003", LocalTime{13, 26, 33, 300}, false},
|
|
||||||
} {
|
|
||||||
gotTime, err := ParseLocalTime(test.str)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("ParseLocalTime(%q): got error: %v", test.str, err)
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if gotTime != test.time {
|
|
||||||
t.Errorf("ParseLocalTime(%q) = %+v, want %+v", test.str, gotTime, test.time)
|
|
||||||
}
|
|
||||||
|
|
||||||
if test.roundTrip {
|
|
||||||
gotStr := test.time.String()
|
|
||||||
if gotStr != test.str {
|
|
||||||
t.Errorf("%#v.String() = %q, want %q", test.time, gotStr, test.str)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
cast := d.AsTime(time.UTC)
|
||||||
|
require.Equal(t, time.Date(2021, time.June, 8, 20, 12, 1, 2, time.UTC), cast)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTimeOf(t *testing.T) {
|
func TestLocalDateTime_String(t *testing.T) {
|
||||||
|
d := toml.LocalDateTime{
|
||||||
for _, test := range []struct {
|
toml.LocalDate{2021, 6, 8},
|
||||||
time time.Time
|
toml.LocalTime{20, 12, 1, 2},
|
||||||
want LocalTime
|
|
||||||
}{
|
|
||||||
{time.Date(2014, 8, 20, 15, 8, 43, 1, time.Local), LocalTime{15, 8, 43, 1}},
|
|
||||||
{time.Date(1, 1, 1, 0, 0, 0, 0, time.UTC), LocalTime{0, 0, 0, 0}},
|
|
||||||
} {
|
|
||||||
if got := LocalTimeOf(test.time); got != test.want {
|
|
||||||
t.Errorf("LocalTimeOf(%v) = %+v, want %+v", test.time, got, test.want)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
require.Equal(t, "2021-06-08 20:12:01.000000002", d.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTimeIsValid(t *testing.T) {
|
func TestLocalDateTime_MarshalText(t *testing.T) {
|
||||||
|
d := toml.LocalDateTime{
|
||||||
for _, test := range []struct {
|
toml.LocalDate{2021, 6, 8},
|
||||||
time LocalTime
|
toml.LocalTime{20, 12, 1, 2},
|
||||||
want bool
|
|
||||||
}{
|
|
||||||
{LocalTime{0, 0, 0, 0}, true},
|
|
||||||
{LocalTime{23, 0, 0, 0}, true},
|
|
||||||
{LocalTime{23, 59, 59, 999999999}, true},
|
|
||||||
{LocalTime{24, 59, 59, 999999999}, false},
|
|
||||||
{LocalTime{23, 60, 59, 999999999}, false},
|
|
||||||
{LocalTime{23, 59, 60, 999999999}, false},
|
|
||||||
{LocalTime{23, 59, 59, 1000000000}, false},
|
|
||||||
{LocalTime{-1, 0, 0, 0}, false},
|
|
||||||
{LocalTime{0, -1, 0, 0}, false},
|
|
||||||
{LocalTime{0, 0, -1, 0}, false},
|
|
||||||
{LocalTime{0, 0, 0, -1}, false},
|
|
||||||
} {
|
|
||||||
got := test.time.IsValid()
|
|
||||||
if got != test.want {
|
|
||||||
t.Errorf("%#v: got %t, want %t", test.time, got, test.want)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
b, err := d.MarshalText()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, []byte("2021-06-08 20:12:01.000000002"), b)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDateTimeToString(t *testing.T) {
|
func TestLocalDateTime_UnmarshalMarshalText(t *testing.T) {
|
||||||
|
d := toml.LocalDateTime{}
|
||||||
|
err := d.UnmarshalText([]byte("2021-06-08 20:12:01.000000002"))
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, toml.LocalDateTime{
|
||||||
|
toml.LocalDate{2021, 6, 8},
|
||||||
|
toml.LocalTime{20, 12, 1, 2},
|
||||||
|
}, d)
|
||||||
|
|
||||||
for _, test := range []struct {
|
err = d.UnmarshalText([]byte("what"))
|
||||||
str string
|
require.Error(t, err)
|
||||||
dateTime LocalDateTime
|
|
||||||
roundTrip bool // ParseLocalDateTime(str).String() == str?
|
|
||||||
}{
|
|
||||||
{"2016-03-22T13:26:33", LocalDateTime{LocalDate{2016, 3, 22}, LocalTime{13, 26, 33, 0}}, true},
|
|
||||||
{"2016-03-22T13:26:33.000000600", LocalDateTime{LocalDate{2016, 3, 22}, LocalTime{13, 26, 33, 600}}, true},
|
|
||||||
{"2016-03-22t13:26:33", LocalDateTime{LocalDate{2016, 3, 22}, LocalTime{13, 26, 33, 0}}, false},
|
|
||||||
} {
|
|
||||||
gotDateTime, err := ParseLocalDateTime(test.str)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("ParseLocalDateTime(%q): got error: %v", test.str, err)
|
|
||||||
|
|
||||||
continue
|
err = d.UnmarshalText([]byte("2021-06-08 20:12:01.000000002 bad"))
|
||||||
}
|
require.Error(t, err)
|
||||||
|
|
||||||
if gotDateTime != test.dateTime {
|
|
||||||
t.Errorf("ParseLocalDateTime(%q) = %+v, want %+v", test.str, gotDateTime, test.dateTime)
|
|
||||||
}
|
|
||||||
|
|
||||||
if test.roundTrip {
|
|
||||||
gotStr := test.dateTime.String()
|
|
||||||
if gotStr != test.str {
|
|
||||||
t.Errorf("%#v.String() = %q, want %q", test.dateTime, gotStr, test.str)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParseDateTimeErrors(t *testing.T) {
|
|
||||||
|
|
||||||
for _, str := range []string{
|
|
||||||
"",
|
|
||||||
"2016-03-22", // just a date
|
|
||||||
"13:26:33", // just a time
|
|
||||||
"2016-03-22 13:26:33", // wrong separating character
|
|
||||||
"2016-03-22T13:26:33x", // extra at end
|
|
||||||
} {
|
|
||||||
if _, err := ParseLocalDateTime(str); err == nil {
|
|
||||||
t.Errorf("ParseLocalDateTime(%q) succeeded, want error", str)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDateTimeOf(t *testing.T) {
|
|
||||||
|
|
||||||
for _, test := range []struct {
|
|
||||||
time time.Time
|
|
||||||
want LocalDateTime
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
time.Date(2014, 8, 20, 15, 8, 43, 1, time.Local),
|
|
||||||
LocalDateTime{LocalDate{2014, 8, 20}, LocalTime{15, 8, 43, 1}},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
time.Date(1, 1, 1, 0, 0, 0, 0, time.UTC),
|
|
||||||
LocalDateTime{LocalDate{1, 1, 1}, LocalTime{0, 0, 0, 0}},
|
|
||||||
},
|
|
||||||
} {
|
|
||||||
if got := LocalDateTimeOf(test.time); got != test.want {
|
|
||||||
t.Errorf("LocalDateTimeOf(%v) = %+v, want %+v", test.time, got, test.want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDateTimeIsValid(t *testing.T) {
|
|
||||||
|
|
||||||
// No need to be exhaustive here; it's just LocalDate.IsValid && LocalTime.IsValid.
|
|
||||||
for _, test := range []struct {
|
|
||||||
dt LocalDateTime
|
|
||||||
want bool
|
|
||||||
}{
|
|
||||||
{LocalDateTime{LocalDate{2016, 3, 20}, LocalTime{0, 0, 0, 0}}, true},
|
|
||||||
{LocalDateTime{LocalDate{2016, -3, 20}, LocalTime{0, 0, 0, 0}}, false},
|
|
||||||
{LocalDateTime{LocalDate{2016, 3, 20}, LocalTime{24, 0, 0, 0}}, false},
|
|
||||||
} {
|
|
||||||
got := test.dt.IsValid()
|
|
||||||
if got != test.want {
|
|
||||||
t.Errorf("%#v: got %t, want %t", test.dt, got, test.want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDateTimeIn(t *testing.T) {
|
|
||||||
|
|
||||||
dt := LocalDateTime{LocalDate{2016, 1, 2}, LocalTime{3, 4, 5, 6}}
|
|
||||||
|
|
||||||
want := time.Date(2016, 1, 2, 3, 4, 5, 6, time.UTC)
|
|
||||||
if got := dt.In(time.UTC); !got.Equal(want) {
|
|
||||||
t.Errorf("got %v, want %v", got, want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDateTimeBefore(t *testing.T) {
|
|
||||||
|
|
||||||
d1 := LocalDate{2016, 12, 31}
|
|
||||||
d2 := LocalDate{2017, 1, 1}
|
|
||||||
t1 := LocalTime{5, 6, 7, 8}
|
|
||||||
t2 := LocalTime{5, 6, 7, 9}
|
|
||||||
|
|
||||||
for _, test := range []struct {
|
|
||||||
dt1, dt2 LocalDateTime
|
|
||||||
want bool
|
|
||||||
}{
|
|
||||||
{LocalDateTime{d1, t1}, LocalDateTime{d2, t1}, true},
|
|
||||||
{LocalDateTime{d1, t1}, LocalDateTime{d1, t2}, true},
|
|
||||||
{LocalDateTime{d2, t1}, LocalDateTime{d1, t1}, false},
|
|
||||||
{LocalDateTime{d2, t1}, LocalDateTime{d2, t1}, false},
|
|
||||||
} {
|
|
||||||
if got := test.dt1.Before(test.dt2); got != test.want {
|
|
||||||
t.Errorf("%v.Before(%v): got %t, want %t", test.dt1, test.dt2, got, test.want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDateTimeAfter(t *testing.T) {
|
|
||||||
|
|
||||||
d1 := LocalDate{2016, 12, 31}
|
|
||||||
d2 := LocalDate{2017, 1, 1}
|
|
||||||
t1 := LocalTime{5, 6, 7, 8}
|
|
||||||
t2 := LocalTime{5, 6, 7, 9}
|
|
||||||
|
|
||||||
for _, test := range []struct {
|
|
||||||
dt1, dt2 LocalDateTime
|
|
||||||
want bool
|
|
||||||
}{
|
|
||||||
{LocalDateTime{d1, t1}, LocalDateTime{d2, t1}, false},
|
|
||||||
{LocalDateTime{d1, t1}, LocalDateTime{d1, t2}, false},
|
|
||||||
{LocalDateTime{d2, t1}, LocalDateTime{d1, t1}, true},
|
|
||||||
{LocalDateTime{d2, t1}, LocalDateTime{d2, t1}, false},
|
|
||||||
} {
|
|
||||||
if got := test.dt1.After(test.dt2); got != test.want {
|
|
||||||
t.Errorf("%v.After(%v): got %t, want %t", test.dt1, test.dt2, got, test.want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMarshalJSON(t *testing.T) {
|
|
||||||
|
|
||||||
for _, test := range []struct {
|
|
||||||
value interface{}
|
|
||||||
want string
|
|
||||||
}{
|
|
||||||
{LocalDate{1987, 4, 15}, `"1987-04-15"`},
|
|
||||||
{LocalTime{18, 54, 2, 0}, `"18:54:02"`},
|
|
||||||
{LocalDateTime{LocalDate{1987, 4, 15}, LocalTime{18, 54, 2, 0}}, `"1987-04-15T18:54:02"`},
|
|
||||||
} {
|
|
||||||
bgot, err := json.Marshal(test.value)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if got := string(bgot); got != test.want {
|
|
||||||
t.Errorf("%#v: got %s, want %s", test.value, got, test.want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUnmarshalJSON(t *testing.T) {
|
|
||||||
|
|
||||||
var (
|
|
||||||
d LocalDate
|
|
||||||
tm LocalTime
|
|
||||||
dt LocalDateTime
|
|
||||||
)
|
|
||||||
|
|
||||||
for _, test := range []struct {
|
|
||||||
data string
|
|
||||||
ptr interface{}
|
|
||||||
want interface{}
|
|
||||||
}{
|
|
||||||
{`"1987-04-15"`, &d, &LocalDate{1987, 4, 15}},
|
|
||||||
{`"1987-04-\u0031\u0035"`, &d, &LocalDate{1987, 4, 15}},
|
|
||||||
{`"18:54:02"`, &tm, &LocalTime{18, 54, 2, 0}},
|
|
||||||
{`"1987-04-15T18:54:02"`, &dt, &LocalDateTime{LocalDate{1987, 4, 15}, LocalTime{18, 54, 2, 0}}},
|
|
||||||
} {
|
|
||||||
if err := json.Unmarshal([]byte(test.data), test.ptr); err != nil {
|
|
||||||
t.Fatalf("%s: %v", test.data, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !cmpEqual(test.ptr, test.want) {
|
|
||||||
t.Errorf("%s: got %#v, want %#v", test.data, test.ptr, test.want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, bad := range []string{
|
|
||||||
"", `""`, `"bad"`, `"1987-04-15x"`,
|
|
||||||
`19870415`, // a JSON number
|
|
||||||
`11987-04-15x`, // not a JSON string
|
|
||||||
|
|
||||||
} {
|
|
||||||
if json.Unmarshal([]byte(bad), &d) == nil {
|
|
||||||
t.Errorf("%q, LocalDate: got nil, want error", bad)
|
|
||||||
}
|
|
||||||
|
|
||||||
if json.Unmarshal([]byte(bad), &tm) == nil {
|
|
||||||
t.Errorf("%q, LocalTime: got nil, want error", bad)
|
|
||||||
}
|
|
||||||
|
|
||||||
if json.Unmarshal([]byte(bad), &dt) == nil {
|
|
||||||
t.Errorf("%q, LocalDateTime: got nil, want error", bad)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-3
@@ -720,8 +720,7 @@ func (d *decoder) unmarshalLocalDate(value *ast.Node, v reflect.Value) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if v.Type() == timeType {
|
if v.Type() == timeType {
|
||||||
cast := ld.In(time.Local)
|
cast := ld.AsTime(time.Local)
|
||||||
|
|
||||||
v.Set(reflect.ValueOf(cast))
|
v.Set(reflect.ValueOf(cast))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -742,7 +741,7 @@ func (d *decoder) unmarshalLocalDateTime(value *ast.Node, v reflect.Value) error
|
|||||||
}
|
}
|
||||||
|
|
||||||
if v.Type() == timeType {
|
if v.Type() == timeType {
|
||||||
cast := ldt.In(time.Local)
|
cast := ldt.AsTime(time.Local)
|
||||||
|
|
||||||
v.Set(reflect.ValueOf(cast))
|
v.Set(reflect.ValueOf(cast))
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
+9
-3
@@ -315,7 +315,10 @@ func TestUnmarshal(t *testing.T) {
|
|||||||
return test{
|
return test{
|
||||||
target: &doc{},
|
target: &doc{},
|
||||||
expected: &doc{
|
expected: &doc{
|
||||||
A: toml.LocalDateTimeOf(time.Date(1979, 5, 27, 0, 32, 0, 0, time.Local)),
|
A: toml.LocalDateTime{
|
||||||
|
toml.LocalDate{1979, 5, 27},
|
||||||
|
toml.LocalTime{0, 32, 0, 0},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -331,7 +334,7 @@ func TestUnmarshal(t *testing.T) {
|
|||||||
return test{
|
return test{
|
||||||
target: &doc{},
|
target: &doc{},
|
||||||
expected: &doc{
|
expected: &doc{
|
||||||
A: toml.LocalDateOf(time.Date(1979, 5, 27, 0, 32, 0, 0, time.Local)),
|
A: toml.LocalDate{1979, 5, 27},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -1977,7 +1980,10 @@ func TestLocalDateTime(t *testing.T) {
|
|||||||
actual := m["a"]
|
actual := m["a"]
|
||||||
golang, err := time.Parse("2006-01-02T15:04:05.999999999", e.input)
|
golang, err := time.Parse("2006-01-02T15:04:05.999999999", e.input)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
expected := toml.LocalDateTimeOf(golang)
|
expected := toml.LocalDateTime{
|
||||||
|
toml.LocalDate{golang.Year(), int(golang.Month()), golang.Day()},
|
||||||
|
toml.LocalTime{golang.Hour(), golang.Minute(), golang.Second(), golang.Nanosecond()},
|
||||||
|
}
|
||||||
require.Equal(t, expected, actual)
|
require.Equal(t, expected, actual)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user