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	"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}