schedule

package
v0.5.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 10, 2026 License: MIT Imports: 9 Imported by: 0

Documentation

Overview

Package schedule parses Todoist-style free-text due dates and recurrence rules into the structured {dueDate, recurrence} form stored on a todo, and formats them back to free text. It is the single source of truth for the schedule grammar: the frontend calls the API to parse instead of keeping a parallel implementation in JavaScript.

Scope is the date-level grammar. Times ("every hour", "at 3pm") are rejected with a clear error rather than guessed, because the app is date-only.

Index

Constants

This section is empty.

Variables

View Source
var ErrAdvance = errors.New("recurrence rule cannot be advanced")

ErrAdvance indicates a recurrence rule could not be turned into a date (unknown frequency or no reachable target). It is unreachable for rules that passed parseCore/Valid; the data layer wraps it so it still maps to HTTP 400.

Functions

func FirstDue

func FirstDue(rc models.Recurrence, now time.Time) (due string, ok bool)

FirstDue returns the first due date for rc on or after now. A recurrence created without an explicit starting date uses this so a targeted rule such as "every last friday" or "every month on the 15th" lands on its first real occurrence rather than today. Plain intervals (every day/week/month/year) resolve to now itself. EndDate is not consulted: the first occurrence is the due date even when it already lies past the recurrence window. ok is false only for a malformed rule, which cannot reach here from Parse.

func Format

func Format(rc models.Recurrence) string

Format renders the repeat pattern (without the leading "every" or start/end clauses) for badges and the input seed.

func FormatSchedule

func FormatSchedule(dueDate string, rc *models.Recurrence, now time.Time) string

FormatSchedule renders a Schedule back to free text. The output round-trips through Parse, so editing shows what the user would retype. now is the reference "today": a due date equal to today omits the "starting" clause.

func IsNone added in v0.3.4

func IsNone(raw string) bool

IsNone reports whether raw is the explicit "no due date" keyword, with an optional leading "due" qualifier ("due never").

func NextDue

func NextDue(current string, rc models.Recurrence, now time.Time) (next string, recurring bool, err error)

NextDue returns the next due date strictly after current per rc. With FromCompletion set (Todoist's "every!") it advances from now (the completion date) instead of current. recurring is false when the next occurrence would fall past rc.EndDate, signalling the recurrence window has closed.

func ParseDate

func ParseDate(raw string, now time.Time) (string, bool)

ParseDate turns a natural-language date into YYYY-MM-DD. ok is false when the input is not a recognized date. Covers the common Todoist date forms.

Types

type QuickAdd

type QuickAdd struct {
	Title      string
	Labels     []string
	Priority   string
	Schedule   Schedule
	NoSchedule bool
}

QuickAdd is the parsed result of a quick-add line: a title, any #label tags, a single @priority, and an optional trailing schedule. NoSchedule is set when the trailing token was the explicit "never" keyword: the todo has no schedule on purpose, which must survive even where a server default due date applies to schedule-less creates.

func Extract

func Extract(raw string, now time.Time) (QuickAdd, bool)

Extract parses a quick-add line into a title, optional #label tags, and an optional trailing schedule. #label tokens (single words, no spaces/commas) are stripped first; the bare name without "#" is kept, de-duplicated case-insensitively in first-seen order. The remaining text is then split into title and schedule by scanning whitespace tokens left to right and taking the first split point at which the trailing suffix parses as a due date / recurrence, so the schedule is the longest trailing run and the title is everything before it.

Because the schedule grammar is anchored at the start (see Parse), a candidate suffix parses only when the schedule phrase begins on its first token — leading title words never produce a false match. parseMonthDayPhrase ignores tokens after a valid month/day, so a candidate is accepted only when removing its last token changes or invalidates the parse; that rejects lenient matches such as "aug 15 milk" where "milk" would otherwise be silently dropped from the title.

ok is false when the title would be empty (blank input, or only labels).

type Schedule

type Schedule struct {
	DueDate    string
	Recurrence *models.Recurrence
}

Schedule is the parsed result of a free-text due/recurrence field. A zero DueDate ("") means no due date; a nil Recurrence means non-recurring.

func Parse

func Parse(raw string, now time.Time) (Schedule, error)

Parse turns the combined due/recurrence field into a structured Schedule. Empty input clears both (zero DueDate, nil Recurrence, nil error), as does the explicit "never" keyword.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL