types.go

 1// SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
 2//
 3// SPDX-License-Identifier: AGPL-3.0-or-later
 4
 5package lunatask
 6
 7import (
 8	"encoding/json"
 9	"time"
10)
11
12// Source represents an external source integration (e.g., GitHub, Todoist).
13// Used across multiple entity types for tracking where items originated.
14type Source struct {
15	Source   string `json:"source"`
16	SourceID string `json:"source_id"`
17}
18
19// Date represents a date-only value (YYYY-MM-DD) with proper JSON marshaling.
20// Used for fields like scheduled_on that don't include time components.
21type Date struct {
22	time.Time
23}
24
25const dateFormat = "2006-01-02"
26
27// MarshalJSON implements json.Marshaler for Date.
28func (d Date) MarshalJSON() ([]byte, error) {
29	if d.IsZero() {
30		return []byte("null"), nil
31	}
32	return json.Marshal(d.Format(dateFormat))
33}
34
35// UnmarshalJSON implements json.Unmarshaler for Date.
36func (d *Date) UnmarshalJSON(data []byte) error {
37	if string(data) == "null" {
38		return nil
39	}
40
41	var s string
42	if err := json.Unmarshal(data, &s); err != nil {
43		return err
44	}
45
46	t, err := time.Parse(dateFormat, s)
47	if err != nil {
48		return err
49	}
50
51	d.Time = t
52	return nil
53}
54
55// String returns the date in YYYY-MM-DD format.
56func (d Date) String() string {
57	if d.IsZero() {
58		return ""
59	}
60	return d.Format(dateFormat)
61}
62
63// NewDate creates a Date from a time.Time, discarding time components.
64func NewDate(t time.Time) Date {
65	return Date{time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, time.UTC)}
66}
67
68// ParseDate parses a date string in YYYY-MM-DD format.
69func ParseDate(s string) (Date, error) {
70	t, err := time.Parse(dateFormat, s)
71	if err != nil {
72		return Date{}, err
73	}
74	return Date{t}, nil
75}
76
77// Today returns the current date.
78func Today() Date {
79	return NewDate(time.Now())
80}