mirror of
https://github.com/therootcompany/golib.git
synced 2025-12-12 00:18:46 +00:00
feat(calendar): parse reminder durations
This commit is contained in:
parent
0784b58dba
commit
e599539c9c
@ -21,13 +21,20 @@ type Rule struct {
|
|||||||
FixedDay int // used only when Nth == 0 (e.g. “15” for the 15th)
|
FixedDay int // used only when Nth == 0 (e.g. “15” for the 15th)
|
||||||
TimeOfDay string // HH:MM in the event’s local zone (e.g. "19:00")
|
TimeOfDay string // HH:MM in the event’s local zone (e.g. "19:00")
|
||||||
Location *time.Location
|
Location *time.Location
|
||||||
// Reminders are ignored for now – you can add them later.
|
Reminders []Reminder
|
||||||
}
|
}
|
||||||
|
|
||||||
type DayType int
|
type DayType int
|
||||||
|
|
||||||
|
var dayTypes = []string{"Weekdays", "Federal Workdays", "Bank Workdays", "Business Days"}
|
||||||
|
|
||||||
|
func (d DayType) String() string {
|
||||||
|
return dayTypes[d]
|
||||||
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
FEDERAL DayType = iota
|
WEEK DayType = iota
|
||||||
|
FEDERAL
|
||||||
BANK
|
BANK
|
||||||
BUSINESS
|
BUSINESS
|
||||||
)
|
)
|
||||||
@ -38,13 +45,15 @@ type Reminder struct {
|
|||||||
Days int
|
Days int
|
||||||
DayType DayType
|
DayType DayType
|
||||||
Duration time.Duration
|
Duration time.Duration
|
||||||
Time Time
|
Time *time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
type Time struct {
|
func (r Reminder) String() string {
|
||||||
Hour int
|
t := r.Time
|
||||||
Minute int
|
if t == nil {
|
||||||
Second int
|
t = &time.Time{}
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%d %s %s %s", r.Days, r.DayType.String(), r.Duration.String(), t.Format("15:04"))
|
||||||
}
|
}
|
||||||
|
|
||||||
type Fields struct {
|
type Fields struct {
|
||||||
@ -56,9 +65,8 @@ type Fields struct {
|
|||||||
TZ int
|
TZ int
|
||||||
LABEL int
|
LABEL int
|
||||||
DETAIL int
|
DETAIL int
|
||||||
R1 int
|
R_FIRST int
|
||||||
R2 int
|
R_LAST int
|
||||||
R3 int
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var FIELDS = Fields{
|
var FIELDS = Fields{
|
||||||
@ -70,22 +78,21 @@ var FIELDS = Fields{
|
|||||||
TZ: 5,
|
TZ: 5,
|
||||||
LABEL: 6,
|
LABEL: 6,
|
||||||
DETAIL: 7,
|
DETAIL: 7,
|
||||||
R1: 8,
|
R_FIRST: 8,
|
||||||
R2: 9,
|
R_LAST: 10,
|
||||||
R3: 10,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------- 2. CSV → []Rule ----------
|
// ---------- 2. CSV → []Rule ----------
|
||||||
func LoadRules(rd *csv.Reader) ([]Rule, error) {
|
func LoadRules(csvr *csv.Reader) ([]Rule, error) {
|
||||||
// first line is the header
|
// first line is the header
|
||||||
// TODO create instance and map header names to column ints
|
// TODO create instance and map header names to column ints
|
||||||
if _, err := rd.Read(); err != nil {
|
if _, err := csvr.Read(); err != nil {
|
||||||
return nil, fmt.Errorf("read header: %w", err)
|
return nil, fmt.Errorf("read header: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var rules []Rule
|
var rules []Rule
|
||||||
for {
|
for {
|
||||||
rec, err := rd.Read()
|
rec, err := csvr.Read()
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -93,6 +100,7 @@ func LoadRules(rd *csv.Reader) ([]Rule, error) {
|
|||||||
return nil, fmt.Errorf("read row: %w", err)
|
return nil, fmt.Errorf("read row: %w", err)
|
||||||
}
|
}
|
||||||
if len(rec) < 6 {
|
if len(rec) < 6 {
|
||||||
|
fmt.Printf("WARN: skip short row %q: %v\n", rec, err)
|
||||||
continue // malformed – skip
|
continue // malformed – skip
|
||||||
}
|
}
|
||||||
rule, err := parseRule(rec)
|
rule, err := parseRule(rec)
|
||||||
@ -113,6 +121,7 @@ func parseRule(rec []string) (Rule, error) {
|
|||||||
dateStr := rec[FIELDS.DATE]
|
dateStr := rec[FIELDS.DATE]
|
||||||
timeStr := rec[FIELDS.TIME]
|
timeStr := rec[FIELDS.TIME]
|
||||||
tz := rec[FIELDS.TZ]
|
tz := rec[FIELDS.TZ]
|
||||||
|
reminderList := rec[FIELDS.R_FIRST:]
|
||||||
|
|
||||||
var r Rule
|
var r Rule
|
||||||
r.Event = strings.TrimSpace(event)
|
r.Event = strings.TrimSpace(event)
|
||||||
@ -173,6 +182,89 @@ func parseRule(rec []string) (Rule, error) {
|
|||||||
}
|
}
|
||||||
r.Location = loc
|
r.Location = loc
|
||||||
|
|
||||||
|
var reminders []Reminder
|
||||||
|
for _, rule := range reminderList {
|
||||||
|
rule = strings.TrimSpace(rule)
|
||||||
|
if len(rule) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err := parseReminder(rule)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("WARN: skip reminder %q: %s: %v\n", rec, rule, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
reminders = append(reminders, r)
|
||||||
|
}
|
||||||
|
r.Reminders = reminders
|
||||||
|
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseReminder(rule string) (Reminder, error) {
|
||||||
|
var r Reminder
|
||||||
|
var pm string
|
||||||
|
parts := strings.Fields(rule)
|
||||||
|
|
||||||
|
part := parts[len(parts)-1]
|
||||||
|
if part == "PM" || part == "AM" {
|
||||||
|
pm = part
|
||||||
|
parts = parts[:len(parts)-1]
|
||||||
|
part = parts[len(parts)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
var t time.Time
|
||||||
|
var err error
|
||||||
|
if pm != "" {
|
||||||
|
part += pm
|
||||||
|
}
|
||||||
|
hhmm := strings.ToLower(part)
|
||||||
|
t, err = time.Parse("03:04pm", hhmm)
|
||||||
|
if err != nil {
|
||||||
|
t, err = time.Parse("3:04pm", hhmm)
|
||||||
|
if err != nil {
|
||||||
|
t, err = time.Parse("15:04", hhmm)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
r.Time = &t
|
||||||
|
parts = parts[:len(parts)-1]
|
||||||
|
if len(parts) > 0 {
|
||||||
|
part = parts[len(parts)-1]
|
||||||
|
} else {
|
||||||
|
part = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if bPos := strings.Index(part, "b"); bPos > -1 {
|
||||||
|
bdays := part[:bPos]
|
||||||
|
part = part[bPos+1:]
|
||||||
|
r.DayType = BUSINESS
|
||||||
|
r.Days, err = strconv.Atoi(bdays)
|
||||||
|
if err != nil {
|
||||||
|
return r, err
|
||||||
|
}
|
||||||
|
} else if dPos := strings.Index(part, "d"); dPos > -1 {
|
||||||
|
ddays := part[:dPos]
|
||||||
|
part = part[dPos+1:]
|
||||||
|
r.DayType = WEEK
|
||||||
|
r.Days, err = strconv.Atoi(ddays)
|
||||||
|
if err != nil {
|
||||||
|
return r, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(part) != 0 {
|
||||||
|
r.Duration, err = time.ParseDuration(part)
|
||||||
|
if err != nil {
|
||||||
|
return r, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(parts) != 0 {
|
||||||
|
return r, err
|
||||||
|
}
|
||||||
|
|
||||||
return r, nil
|
return r, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -339,6 +431,14 @@ func main() {
|
|||||||
fmt.Printf("%s → %s\n", ev.Time.Format(time.RFC3339), ev.Rule.Event)
|
fmt.Printf("%s → %s\n", ev.Time.Format(time.RFC3339), ev.Rule.Event)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, ev := range events {
|
||||||
|
var rs []string
|
||||||
|
for _, r := range ev.Rule.Reminders {
|
||||||
|
rs = append(rs, r.String())
|
||||||
|
}
|
||||||
|
fmt.Printf("%v\n", strings.Join(rs, " | "))
|
||||||
|
}
|
||||||
|
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
for _, ev := range events {
|
for _, ev := range events {
|
||||||
sleep := time.Until(ev.Time.Add(-2 * time.Hour))
|
sleep := time.Until(ev.Time.Add(-2 * time.Hour))
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user