diff options
| author | historia <gravel.justness270@slmail.me> | 2024-08-16 23:52:17 -0400 |
|---|---|---|
| committer | historia <gravel.justness270@slmail.me> | 2024-08-16 23:52:17 -0400 |
| commit | a31cf66d9400059752a2c480bac132960383a8d9 (patch) | |
| tree | 99073a9056263b5ceb5d3ec5f7577ca6667766ff | |
| parent | e5c80a22c35a5745ae957b8e84da42abd0c41d4d (diff) | |
| download | soon-a31cf66d9400059752a2c480bac132960383a8d9.tar.gz | |
feat: parsing times to sort events works besides ugly edge cases
| -rw-r--r-- | README.md | 14 | ||||
| -rw-r--r-- | src/soon.nim | 12 | ||||
| -rw-r--r-- | src/soon/agenda.nim | 52 | ||||
| -rw-r--r-- | src/soon/parsetime.nim | 59 | ||||
| -rw-r--r-- | src/soon/typechecker.nim | 6 | ||||
| -rw-r--r-- | tests/tparsetime.nim | 57 |
6 files changed, 168 insertions, 32 deletions
@@ -14,11 +14,11 @@ In terms of features and complexity, Soon lies between [When](https://www.lighta # Quick Start -1. Run `soon -e`. This will generate a config and calendar file under $XDG_CONFIG_HOME/soon and open the calendar file in your editor. +1. Run `soon -e` to edit your default calendar. On first run, soon will generate a config and calendar file under $XDG_CONFIG_HOME/soon. -2. Check the example calendar and add some events/todos. You can just add `J%2, Hello World` for an event that repeats every other day if you want to quickly see how it looks. +2. Check the example calendar and add some events/todos. -3. Run `soon` to see your agenda. With no options it will show you the next 14 days of events as well as your todos and deadlines. +3. Run `soon` to see your agenda for the next 14 days. Run `soon -cm` to print a reference calendar and your agenda for the next month. 4. Run `soon -i` to start interactive mode and archive events or todos. Select events/todos with HJKL and archive them with Space. Esc to exit. @@ -39,7 +39,7 @@ Fri !13, Every Friday except the 13th # Ranges (Jul 7 2024)-(Jul 13 2024), Shark Week July 7-13 2024, Shark Week -Mon-Fri 9am-5pm, What a way to make a living +Mon-Fri 9am-5pm, Work all day # Uncommon recurrence patterns J%3, Every third day (See: Advanced Recurrence) @@ -130,7 +130,7 @@ May 1L Mon, Memorial Day (Last Monday of May) You can express times in 24h format or AM/PM format like this. Your agenda will be sorted by the start time of events. ``` -Fri 06:00, With no am/pm, 24h time is assumed +Fri 16:00, With no am/pm, 24h time is assumed Fri 12:00am, Midnight Fri 12pm, Noon @@ -166,9 +166,7 @@ Julian date modulus is weird, but it's a concise way to express multiple uncommo #### Footnote for astronomers and time nerds -The actual MJD is based on UTC, so Soon technically uses a *rounded*, modified Julian Day because it counts days since 1858-11-17 in your local time zone. This doesn't matter in practice. It's just a number that goes up by 1 every day. - -Soon's MJD is off by 1 from When. When starts counting from JD 1 while Soon more correctly starts counting from JD 0. +The actual MJD is based on UTC, so Soon technically uses a *rounded*, modified Julian Day because it counts days since 1858-11-17 in your local time zone. This doesn't matter in practice. It's just a number that goes up by 1 every day. Soon's MJD is off by 1 from When. When starts counting from JD 1 while Soon more correctly starts counting from JD 0. ## Todo List diff --git a/src/soon.nim b/src/soon.nim index cede38c..2c5f109 100644 --- a/src/soon.nim +++ b/src/soon.nim @@ -35,7 +35,7 @@ proc createDefaultCalendarFile(file: string) = if not fileExists(file): writeFile(file, "# This is your first calendar file. Put events and todos in here.") -proc getCalendarFileLines(file: string): seq[string] = +proc getFileLines(file: string): seq[string] = let f = open(file) for line in f.lines: if line == "": continue @@ -43,11 +43,13 @@ proc getCalendarFileLines(file: string): seq[string] = result.add(line) f.close() -proc processCalendars(calendarPath: string, past: int, future: int) = - var calendar = newSeq[string]() - for file in os.walkDirRec(calendarPath): +proc readAllSoonFiles(path: string): seq[string] = + for file in os.walkDirRec(path): if file.toLower().endsWith(".soon"): - calendar = calendar.concat(getCalendarFileLines(file)) + result = result.concat(getFileLines(file)) + +proc processCalendars(path: string, past: int, future: int) = + var calendar = readAllSoonFiles(path) agenda.printAgenda(calendar, past, future) todos.printTodos(calendar) diff --git a/src/soon/agenda.nim b/src/soon/agenda.nim index 4288f0a..59bbe12 100644 --- a/src/soon/agenda.nim +++ b/src/soon/agenda.nim @@ -1,5 +1,10 @@ -import std/[times, strutils, strbasics, strscans, tables] -import conditionchecker, typechecker +import std/[times, strutils, strbasics, strscans, tables, algorithm, re] +import conditionchecker, typechecker, parsetime + +type Event = tuple + name: string + time: DateTime + attachments: seq[string] # Splits a string on whitespace, but maintains whitespace in date groups proc splitWhitespaceExceptParens(str:string): seq[string] = @@ -26,8 +31,7 @@ proc parseDateToTable(date:string): Table[string, seq[string]] = table[condType].add(c) return table -proc allConditionsMet(dateConditions: string, date: DateTime): bool = - let conditions = parseDateToTable(dateConditions.strip) +proc allConditionsMet(conditions: Table[string,seq[string]], date: DateTime): bool = if conditions.hasKey("unknown"): return false for k,cs in conditions: var keyMatched = false @@ -39,30 +43,48 @@ proc allConditionsMet(dateConditions: string, date: DateTime): bool = return false return true +#proc formatTime(time: string): DateTime = +# let (start, end) = time.split("-", maxsplit = 1) +# var matches: array[1, string] +# discard find(condition, re"0?(\d{1,2})(:)?(\d{2})?([AaPp][Mm])?(\-(\d{1,2})(:)?(\d{2})?([AaPp][Mm])?)?", matches) + + # Returns Table[event, attachments] -proc getEventsForDate(cal: seq[string], date: DateTime): Table[string, seq[string]] = +proc getEventsForDate(cal: seq[string], date: DateTime): seq[Event] = var lastInsertion = "" for line in cal: var (success, dateConditions, event) = scanTuple(line, "$*,$*") if success: - if allConditionsMet(dateConditions, date): + dateConditions.strip + let conditions = parseDateToTable(dateConditions) + if allConditionsMet(conditions, date): event.strip - result[event] = @[] + if conditions.hasKey("time"): + echo conditions["time"] + #echo formatTime(conditions["time"]) + result.add((event, now(), @[])) lastInsertion = event elif line.strip[0] == '+': - if result.hasKey(lastInsertion): - result[lastInsertion].add(line) + if result[result.high].name == lastInsertion: + result[result.high].attachments.add(line) elif line[0..1] == "- ": lastInsertion = "" else: echo "WARNING: Cannot parse line: ", line -proc printEvents(cal: seq[string], date: DateTime, format: string) = - let events = getEventsForDate(cal, date) +#proc sortTable(t: Table[string, seq[string]]) = +# for + +# Check nim-lang algorithm basic usage +proc compare(a, b: string): int = + cmp(a.len, b.len) + +proc printDaysEvents(cal: seq[string], date: DateTime, format: string) = + var events = getEventsForDate(cal, date) if events.len > 0: - for e in events.keys: - echo date.format(format), " ", e - for attachment in events[e]: + for event in events: + echo date.format(format), " ", event.name + for attachment in event.attachments: let space = date.format(format).len + 2 echo attachment.indent(space) @@ -71,4 +93,4 @@ proc printAgenda*(calendar: seq[string], past: int, future: int) = var today = dateTime(now.year, now.month, now.monthday, 00, 00, 00, 00, local()) for i in past..future: let curDay = today + i.days - printEvents(calendar, curDay, "ddd yyyy MMM dd") + printDaysEvents(calendar, curDay, "ddd yyyy MMM dd") diff --git a/src/soon/parsetime.nim b/src/soon/parsetime.nim new file mode 100644 index 0000000..5cfa245 --- /dev/null +++ b/src/soon/parsetime.nim @@ -0,0 +1,59 @@ +import strutils, tables, re, times + +proc flipAmPm(s : string): string = + case s + of "am": return "pm" + of "pm": return "am" + else: return "am" + +proc fixTwelveAm(hour: int, meridiem: string): int = + if hour == 12 and meridiem == "am": + return 0 + if hour == 24: + return 0 + return hour + +# Returns (hour, minute, meridium) +proc parseString(t: string): (int, int, string) = + var matches: array[3, string] + discard find(t, re"^(\d{1,2}):?(\d{2})?([PpAa][Mm])?", matches) + var + hour = parseInt(matches[0]) + minute = 0 + meridiem = matches[2] + if matches[1] != "": minute = parseInt(matches[1]) + hour = fixTwelveAm(hour, meridiem) + if hour > 12: + hour = hour - 12 + meridiem = "pm" + return (hour, minute, meridiem) + +proc getMeridiem(startHour: int, startMin: int, endTime: string): string = +# Determine meridium of the left hand side of a time range when it isn't specified + var (endHour, endMinute, endMeridiem) = parseString(endTime) + if (startHour < endHour and endHour == 12): + return "am" + if (startHour < endHour) or (startHour == endHour and startMin < endMinute): + return endMeridiem + elif startHour > endHour: + return flipAmPm(endMeridiem) + +proc timeToStr(time: int): string = + result = intToStr(time) + if result.len == 1: + result = "0" & result + +proc toDateTime*(time: string): DateTime = +# Returns a DateTime for any time string like 9am. 09:00, or 9-10pm + let splitTime = time.split("-", maxsplit = 1) + let startTime = splitTime[0] + var (hour, minute, meridiem) = parseString(startTime) + if meridiem == "" and splitTime.len > 1: + meridiem = getMeridiem(hour, minute, splitTime[1]) + var sHour = timeToStr(hour) + var sMin = timeToStr(minute) + result = parse(sHour & sMin, "HHmm", utc()) + if meridiem == "pm" and hour <= 12: + result = result + 12.hours + if result.monthday == 2: + result = result - 1.days diff --git a/src/soon/typechecker.nim b/src/soon/typechecker.nim index 34e5723..0163c9f 100644 --- a/src/soon/typechecker.nim +++ b/src/soon/typechecker.nim @@ -32,11 +32,9 @@ proc checkType*(c: string): string = if find(cond, re"^\([A-Za-z0-9 ]+\)$") > -1: return "dategroup" if find(cond, re"^\d{4}-\d{4}$") > -1: return "rangeyear" if find(cond, re"^\d{1,2}-\d{1,2}$") > -1: return "rangeday" - if find(cond, re"^\d{1,2}:\d{2}(-\d{1,2}:\d{2})?$") > -1: return "time" - if find(cond, re"^\d{1,2}(:\d{2})?[AaPp]\.?[Mm](-\d{1,2}(:\d{2})?[AaPp]\.?[Mm])?") > -1: return "time" - if find(cond, re"^\(?\s*[Jj]\s*%\s*\d+\s*([-+]\s*\d+)?\s*\)?$") > -1: return "julian" + if find(cond, re"^\(?\s*[Jj]\s*%\s*\d+\s*([-+]\s*\d+)?\s*\)?$") > -1: return "julian" + if find(cond, re"^\d{1,2}(:\d{2})?([AaPp][Mm])?(-\d{1,2}(:\d{2})?)?([AaPp][Mm])?$") > -1: return "time" cond = cond.toLower.capitalizeAscii if cond in MONTHS: return "month" if cond in WEEKDAYS: return "weekday" - return checkForRanges(cond) diff --git a/tests/tparsetime.nim b/tests/tparsetime.nim new file mode 100644 index 0000000..e49868c --- /dev/null +++ b/tests/tparsetime.nim @@ -0,0 +1,57 @@ +import soon/parsetime +import std/[unittest] +import std/[times] + +test "Parse single times": + check toDateTime("9") == parse("0900", "HHmm", utc()) + check toDateTime("9am") == parse("0900", "HHmm", utc()) + check toDateTime("9:00") == parse("0900", "HHmm", utc()) + check toDateTime("9:30") == parse("0930", "HHmm", utc()) + check toDateTime("09:00") == parse("0900", "HHmm", utc()) + check toDateTime("09:30") == parse("0930", "HHmm", utc()) + check toDateTime("9:00am") == parse("0900", "HHmm", utc()) + check toDateTime("9:30pm") == parse("2130", "HHmm", utc()) + check toDateTime("09:00am") == parse("0900", "HHmm", utc()) + check toDateTime("09:00pm") == parse("2100", "HHmm", utc()) + check toDateTime("09:30am") == parse("0930", "HHmm", utc()) + check toDateTime("09:30pm") == parse("2130", "HHmm", utc()) + check toDateTime("17") == parse("1700", "HHmm", utc()) + check toDateTime("24") == parse("0000", "HHmm", utc()) + check toDateTime("17:00") == parse("1700", "HHmm", utc()) + check toDateTime("12:00") == parse("1200", "HHmm", utc()) + check toDateTime("24:00") == parse("0000", "HHmm", utc()) + check toDateTime("00:00") == parse("0000", "HHmm", utc()) + check toDateTime("01:00") == parse("0100", "HHmm", utc()) + + + +test "Parse time ranges": + check toDateTime("9-1am") == parse("2100", "HHmm", utc()) + check toDateTime("9am-1am") == parse("0900", "HHmm", utc()) + check toDateTime("9:00-1:00am") == parse("2100", "HHmm", utc()) + check toDateTime("9:30-1:30am") == parse("2130", "HHmm", utc()) + check toDateTime("09:00-01:00am") == parse("2100", "HHmm", utc()) + check toDateTime("09:30-01:30am") == parse("2130", "HHmm", utc()) + check toDateTime("9:00am-1:00am") == parse("0900", "HHmm", utc()) + check toDateTime("9:30pm-01:00am") == parse("2130", "HHmm", utc()) + check toDateTime("09:00am-01:00am") == parse("0900", "HHmm", utc()) + check toDateTime("09:00pm-01:00am") == parse("2100", "HHmm", utc()) + check toDateTime("09:30am-01:30am") == parse("0930", "HHmm", utc()) + check toDateTime("09:30pm-01:30am") == parse("2130", "HHmm", utc()) + check toDateTime("17-1am") == parse("1700", "HHmm", utc()) + check toDateTime("17:00-1:00am") == parse("1700", "HHmm", utc()) + check toDateTime("12:00-1:00am") == parse("0000", "HHmm", utc()) + check toDateTime("12-1:00am") == parse("0000", "HHmm", utc()) + check toDateTime("00:00-01:00am") == parse("0000", "HHmm", utc()) + check toDateTime("11-12pm") == parse("1100", "HHmm", utc()) + check toDateTime("11-12am") == parse("2300", "HHmm", utc()) + check toDateTime("11-12") == parse("1100", "HHmm", utc()) + check toDateTime("9pm-12am") == parse("2100", "HHmm", utc()) + check toDateTime("11:00-13:00") == parse("1100", "HHmm", utc()) + check toDateTime("12:00-13:00") == parse("1200", "HHmm", utc()) + check toDateTime("11:00-24:00") == parse("1100", "HHmm", utc()) + check toDateTime("23:00-24:00") == parse("2300", "HHmm", utc()) + check toDateTime("24:00-23:00") == parse("0000", "HHmm", utc()) + + + |
