diff --git a/core.go b/core.go index a2ccd05..8cf411d 100644 --- a/core.go +++ b/core.go @@ -164,32 +164,43 @@ func TimeFromString(t *testing.T, format, datetime string) time.Time { return dt } -const LayoutRequestDateDefault = "2006-01-02" +const ShortDateLayout = "2006-01-02" -type RequestDate struct { +// Do not use this structure for responses +// as there are no ways to unmarshal to any layout +// and leave nil if json field is null +type TimeFormat struct { time.Time layout string } -func NewRequestDate(t time.Time, layout string) *RequestDate { - return &RequestDate{ +func NewTimeFormat(t time.Time, layout string) *TimeFormat { + return &TimeFormat{ Time: t, layout: layout, } } -func (rd *RequestDate) UnmarshalJSON(b []byte) (err error) { +func newTimeLayout(layout string) *TimeFormat { + return &TimeFormat{ + layout: layout, + } +} + +func (rd *TimeFormat) UnmarshalJSON(b []byte) error { + var err error + s := strings.Trim(string(b), `"`) // remove quotes + + // Added for extra accuracy + // encoding/json won't invoke this method if field is null if s == "null" { - return + return nil } rd.Time, err = time.Parse(rd.layout, s) - return + return err } -func (rd *RequestDate) MarshalJSON() ([]byte, error) { - if rd.Time.IsZero() { - return nil, nil - } - return []byte(fmt.Sprintf(`"%s"`, rd.Time.Format(rd.layout))), nil +func (rd *TimeFormat) MarshalJSON() ([]byte, error) { + return []byte(fmt.Sprintf(`%q`, rd.Time.Format(rd.layout))), nil } diff --git a/core_test.go b/core_test.go index 819f80c..504248e 100644 --- a/core_test.go +++ b/core_test.go @@ -1,8 +1,10 @@ package core import ( + "encoding/json" "reflect" "testing" + "time" "github.com/stretchr/testify/assert" ) @@ -54,3 +56,97 @@ func TestDefaultValues(t *testing.T) { assert.Equal(t, "empty_string", req.OptionalStructure.EmptyField) assert.Equal(t, (*DefaultStructure)(nil), req.EmptyOptionalStructure) } + +func TestTimeFormat(t *testing.T) { + t.Run("Time Format Marshalling", func(t *testing.T) { + tests := []struct { + ft *TimeFormat + layout string + expectedJSON string + diff time.Duration + }{ + { + ft: NewTimeFormat(time.Date(2024, 4, 30, 15, 42, 12, 55, time.FixedZone("Test Zone", 0)), ShortDateLayout), + layout: ShortDateLayout, + expectedJSON: `"2024-04-30"`, + diff: time.Hour * 24, + }, + { + ft: NewTimeFormat(time.Date(2024, 4, 30, 0, 0, 0, 0, time.FixedZone("Test Zone", 0)), ShortDateLayout), + layout: ShortDateLayout, + expectedJSON: `"2024-04-30"`, + diff: time.Hour * 24, + }, + { + ft: NewTimeFormat(time.Time{}, ShortDateLayout), + layout: ShortDateLayout, + expectedJSON: `"0001-01-01"`, + diff: time.Hour * 24, + }, + { + ft: nil, + layout: ShortDateLayout, + expectedJSON: `null`, + diff: time.Hour * 24, + }, + } + + for _, tc := range tests { + marshaled, err := json.Marshal(tc.ft) + assert.Equal(t, nil, err) + assert.Equal(t, tc.expectedJSON, string(marshaled)) + + unmarshaled := newTimeLayout(tc.layout) + err = json.Unmarshal(marshaled, unmarshaled) + assert.Equal(t, nil, err) + + if tc.ft != nil { + diffedTime := tc.ft.Add(-tc.diff) + assert.Equal(t, true, diffedTime.Before(unmarshaled.Time) || diffedTime.Equal(unmarshaled.Time)) + assert.Equal(t, true, tc.ft.After(unmarshaled.Time) || tc.ft.Equal(unmarshaled.Time)) + } + } + }) + + t.Run("Time Format in structure Marshalling", func(t *testing.T) { + type test struct { + Date *TimeFormat `json:"date"` + } + + tests := []struct { + structure *test + layout string + expectedJSON string + diff time.Duration + }{ + { + structure: &test{Date: NewTimeFormat(time.Date(2024, 4, 30, 5, 4, 7, 20, time.FixedZone("Test Zone", 0)), ShortDateLayout)}, + layout: ShortDateLayout, + expectedJSON: `{"date":"2024-04-30"}`, + diff: time.Hour * 24, + }, + { + structure: &test{Date: nil}, + layout: ShortDateLayout, + expectedJSON: `{"date":null}`, + diff: time.Hour * 24, + }, + } + + for _, tc := range tests { + marshaled, err := json.Marshal(tc.structure) + assert.Equal(t, nil, err) + assert.Equal(t, tc.expectedJSON, string(marshaled)) + + unmarshaled := &test{Date: newTimeLayout(tc.layout)} + err = json.Unmarshal(marshaled, unmarshaled) + assert.Equal(t, nil, err) + + if tc.structure != nil && tc.structure.Date != nil { + diffedTime := tc.structure.Date.Add(-tc.diff) + assert.Equal(t, true, diffedTime.Before(unmarshaled.Date.Time) || diffedTime.Equal(unmarshaled.Date.Time)) + assert.Equal(t, true, tc.structure.Date.After(unmarshaled.Date.Time) || tc.structure.Date.Equal(unmarshaled.Date.Time)) + } + } + }) +} diff --git a/ozon/analytics.go b/ozon/analytics.go index 600d11d..3cda636 100644 --- a/ozon/analytics.go +++ b/ozon/analytics.go @@ -13,10 +13,10 @@ type Analytics struct { type GetAnalyticsDataParams struct { // Date from which the data will be in the report - DateFrom *core.RequestDate `json:"date_from"` + DateFrom *core.TimeFormat `json:"date_from"` // Date up to which the data will be in the report - DateTo *core.RequestDate `json:"date_to"` + DateTo *core.TimeFormat `json:"date_to"` // Items Enum: "unknownDimension" "sku" "spu" "day" "week" "month" "year" "category1" "category2" "category3" "category4" "brand" "modelID" // Data grouping available to all sellers: diff --git a/ozon/analytics_test.go b/ozon/analytics_test.go index a475261..a269aa7 100644 --- a/ozon/analytics_test.go +++ b/ozon/analytics_test.go @@ -23,8 +23,8 @@ func TestGetAnalyticsData(t *testing.T) { http.StatusOK, map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"}, &GetAnalyticsDataParams{ - DateFrom: core.NewRequestDate(time.Now().Add(time.Duration(30)*24*time.Hour), core.LayoutRequestDateDefault), - DateTo: core.NewRequestDate(time.Now(), core.LayoutRequestDateDefault), + DateFrom: core.NewTimeFormat(time.Now().Add(time.Duration(30)*24*time.Hour), core.ShortDateLayout), + DateTo: core.NewTimeFormat(time.Now(), core.ShortDateLayout), Dimension: []GetAnalyticsDataDimension{SKUDimension, DayDimension}, Metrics: []GetAnalyticsDataFilterMetric{HistViewPDP}, Sort: []GetAnalyticsDataSort{