mirror of
https://github.com/dstotijn/go-notion.git
synced 2025-06-17 00:07:45 +02:00
Add custom type for Notion date properties with optional time
This commit is contained in:
@ -517,7 +517,7 @@ func TestQueryDatabase(t *testing.T) {
|
|||||||
ID: "Q]uT",
|
ID: "Q]uT",
|
||||||
Type: notion.DBPropTypeDate,
|
Type: notion.DBPropTypeDate,
|
||||||
Date: ¬ion.Date{
|
Date: ¬ion.Date{
|
||||||
Start: mustParseTime(time.RFC3339Nano, "2021-05-18T12:49:00.000-05:00"),
|
Start: mustParseDateTime("2021-05-18T12:49:00.000-05:00"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"Name": notion.DatabasePageProperty{
|
"Name": notion.DatabasePageProperty{
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
package notion
|
package notion
|
||||||
|
|
||||||
import "time"
|
|
||||||
|
|
||||||
type RichText struct {
|
type RichText struct {
|
||||||
Type RichTextType `json:"type,omitempty"`
|
Type RichTextType `json:"type,omitempty"`
|
||||||
Annotations *Annotations `json:"annotations,omitempty"`
|
Annotations *Annotations `json:"annotations,omitempty"`
|
||||||
@ -36,8 +34,8 @@ type Mention struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Date struct {
|
type Date struct {
|
||||||
Start time.Time `json:"start"`
|
Start DateTime `json:"start"`
|
||||||
End *time.Time `json:"end,omitempty"`
|
End *DateTime `json:"end,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Text struct {
|
type Text struct {
|
||||||
|
95
time.go
Normal file
95
time.go
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
package notion
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Length of a date string, e.g. `2006-01-02`.
|
||||||
|
const dateLength = 10
|
||||||
|
|
||||||
|
// DateTimeFormat is used when calling time.Parse, using RFC3339 with microsecond
|
||||||
|
// precision, which is what the Notion API returns in JSON response data.
|
||||||
|
const DateTimeFormat = "2006-01-02T15:04:05.999Z07:00"
|
||||||
|
|
||||||
|
// DateTime represents a Notion date property with optional time.
|
||||||
|
type DateTime struct {
|
||||||
|
time.Time
|
||||||
|
hasTime bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseDateTime parses an RFC3339 formatted string with optional time.
|
||||||
|
func ParseDateTime(value string) (DateTime, error) {
|
||||||
|
if len(value) > len(DateTimeFormat) {
|
||||||
|
return DateTime{}, errors.New("invalid datetime string")
|
||||||
|
}
|
||||||
|
|
||||||
|
t, err := time.Parse(DateTimeFormat[:len(value)], value)
|
||||||
|
if err != nil {
|
||||||
|
return DateTime{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
dt := DateTime{
|
||||||
|
Time: t,
|
||||||
|
hasTime: len(value) > dateLength,
|
||||||
|
}
|
||||||
|
|
||||||
|
return dt, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON implements json.Unmarshaler.
|
||||||
|
func (dt *DateTime) UnmarshalJSON(b []byte) error {
|
||||||
|
if len(b) < 2 {
|
||||||
|
return errors.New("invalid datetime string")
|
||||||
|
}
|
||||||
|
|
||||||
|
parsed, err := ParseDateTime(string(b[1 : len(b)-1]))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
*dt = parsed
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON implements json.Marshaler. It returns an RFC399 formatted string,
|
||||||
|
// using microsecond precision ()
|
||||||
|
func (dt DateTime) MarshalJSON() ([]byte, error) {
|
||||||
|
if dt.hasTime {
|
||||||
|
return json.Marshal(dt.Time)
|
||||||
|
}
|
||||||
|
return []byte(`"` + dt.Time.Format(DateTimeFormat[:dateLength]) + `"`), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDateTime returns a new DateTime. If `haseTime` is true, time is included
|
||||||
|
// when encoding to JSON.
|
||||||
|
func NewDateTime(t time.Time, hasTime bool) DateTime {
|
||||||
|
var tt time.Time
|
||||||
|
|
||||||
|
if hasTime {
|
||||||
|
tt = t
|
||||||
|
} else {
|
||||||
|
tt = time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, time.UTC)
|
||||||
|
}
|
||||||
|
|
||||||
|
return DateTime{
|
||||||
|
Time: tt,
|
||||||
|
hasTime: hasTime,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasTime returns true if the datetime was parsed from a string that included time.
|
||||||
|
func (dt *DateTime) HasTime() bool {
|
||||||
|
return dt.hasTime
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equal returns true if both DateTime values have equal underlying time.Time and
|
||||||
|
// hasTime fields.
|
||||||
|
func (dt DateTime) Equal(value DateTime) bool {
|
||||||
|
if !dt.Time.Equal(value.Time) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return dt.hasTime == value.hasTime
|
||||||
|
}
|
115
time_test.go
Normal file
115
time_test.go
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
package notion_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/dstotijn/go-notion"
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
|
)
|
||||||
|
|
||||||
|
func mustParseDateTime(value string) notion.DateTime {
|
||||||
|
dt, err := notion.ParseDateTime(value)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return dt
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTimeMarshalJSON(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
dateTime notion.DateTime
|
||||||
|
expJSON []byte
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "date and time",
|
||||||
|
dateTime: mustParseDateTime("2021-05-23T09:11:50.123Z"),
|
||||||
|
expJSON: []byte(`"2021-05-23T09:11:50.123Z"`),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "date without time",
|
||||||
|
dateTime: mustParseDateTime("2021-05-23"),
|
||||||
|
expJSON: []byte(`"2021-05-23"`),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
tt := tt
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
dtJSON, err := json.Marshal(tt.dateTime)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if diff := cmp.Diff(string(tt.expJSON), string(dtJSON)); diff != "" {
|
||||||
|
t.Fatalf("encoded JSON not equal (-exp, +got):\n%v", diff)
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTimeUnmarshalJSON(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
timeString string
|
||||||
|
expDateTime notion.DateTime
|
||||||
|
expHasTime bool
|
||||||
|
expError error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "date and time",
|
||||||
|
timeString: "2021-05-23T09:11:50.123+00:00",
|
||||||
|
expDateTime: notion.NewDateTime(mustParseTime(time.RFC3339Nano, "2021-05-23T09:11:50.123Z"), true),
|
||||||
|
expHasTime: true,
|
||||||
|
expError: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "date without time",
|
||||||
|
timeString: "2021-05-23",
|
||||||
|
expDateTime: notion.NewDateTime(mustParseTime(time.RFC3339Nano, "2021-05-23T09:11:50.123Z"), false),
|
||||||
|
expHasTime: false,
|
||||||
|
expError: nil,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
tt := tt
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
type testDateTime struct {
|
||||||
|
DateTime notion.DateTime `json:"time"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var dt testDateTime
|
||||||
|
err := json.Unmarshal([]byte(`{"time":"`+tt.timeString+`"}`), &dt)
|
||||||
|
|
||||||
|
if tt.expError == nil && err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if tt.expError != nil && err == nil {
|
||||||
|
t.Fatalf("error not equal (expected: %v, got: nil)", tt.expError)
|
||||||
|
}
|
||||||
|
if tt.expError != nil && err != nil && tt.expError.Error() != err.Error() {
|
||||||
|
t.Fatalf("error not equal (expected: %v, got: %v)", tt.expError, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if diff := cmp.Diff(tt.expDateTime.Time, dt.DateTime.Time); diff != "" {
|
||||||
|
t.Fatalf("time not equal (-exp, +got):\n%v", diff)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tt.expHasTime != dt.DateTime.HasTime() {
|
||||||
|
t.Fatalf("has time not equal (expected: %v, got: %v)", tt.expHasTime, dt.DateTime.HasTime())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user