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",
|
||||
Type: notion.DBPropTypeDate,
|
||||
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{
|
||||
|
@ -1,7 +1,5 @@
|
||||
package notion
|
||||
|
||||
import "time"
|
||||
|
||||
type RichText struct {
|
||||
Type RichTextType `json:"type,omitempty"`
|
||||
Annotations *Annotations `json:"annotations,omitempty"`
|
||||
@ -36,8 +34,8 @@ type Mention struct {
|
||||
}
|
||||
|
||||
type Date struct {
|
||||
Start time.Time `json:"start"`
|
||||
End *time.Time `json:"end,omitempty"`
|
||||
Start DateTime `json:"start"`
|
||||
End *DateTime `json:"end,omitempty"`
|
||||
}
|
||||
|
||||
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