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 "fmt"
10 "time"
11)
12
13// Source tracks where an entity originated, useful for syncing with external
14// systems. The values are free-form strings you define.
15type Source struct {
16 Source string `json:"source"`
17 SourceID string `json:"source_id"`
18}
19
20// Date is a date without time, marshaled as "YYYY-MM-DD" in JSON.
21type Date struct {
22 time.Time
23}
24
25// NewDate creates a Date from a time.Time, discarding time-of-day.
26func NewDate(t time.Time) Date {
27 return Date{time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, time.UTC)}
28}
29
30const dateFormat = "2006-01-02"
31
32// MarshalJSON implements [json.Marshaler].
33func (d Date) MarshalJSON() ([]byte, error) {
34 if d.IsZero() {
35 return []byte("null"), nil
36 }
37
38 data, err := json.Marshal(d.Format(dateFormat))
39 if err != nil {
40 return nil, fmt.Errorf("marshaling date: %w", err)
41 }
42
43 return data, nil
44}
45
46// UnmarshalJSON implements [json.Unmarshaler].
47func (d *Date) UnmarshalJSON(data []byte) error {
48 if string(data) == "null" {
49 return nil
50 }
51
52 var str string
53 if err := json.Unmarshal(data, &str); err != nil {
54 return fmt.Errorf("unmarshaling date string: %w", err)
55 }
56
57 t, err := time.Parse(dateFormat, str)
58 if err != nil {
59 return fmt.Errorf("parsing date %q: %w", str, err)
60 }
61
62 d.Time = t
63
64 return nil
65}
66
67// String returns the date as "YYYY-MM-DD", or empty string if zero.
68func (d *Date) String() string {
69 if d.IsZero() {
70 return ""
71 }
72
73 return d.Format(dateFormat)
74}
75
76// ParseDate parses "YYYY-MM-DD" into a Date.
77func ParseDate(s string) (Date, error) {
78 t, err := time.Parse(dateFormat, s)
79 if err != nil {
80 return Date{}, fmt.Errorf("parsing date %q: %w", s, err)
81 }
82
83 return Date{t}, nil
84}
85
86// Today returns the current date in local time.
87func Today() Date {
88 return NewDate(time.Now())
89}