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}