From 124ad4c0937263e931f0c041f9ad606dfc287a43 Mon Sep 17 00:00:00 2001 From: "Nicholas H.Tollervey" Date: Fri, 29 May 2026 15:47:15 +0100 Subject: [PATCH 1/2] Add PyScript examples for python-dateutil Generated by apply_llm_response.py from prompts/python-dateutil/response.toml. Examples included: - parsing_and_relativedelta: Parsing dates and relativedelta - recurrence_rules: Recurrence rules with rrule Generated-By: apply_llm_response.py --- examples/python-dateutil/README.md | 18 +++ examples/python-dateutil/order.json | 4 + .../parsing_and_relativedelta/code.py | 102 +++++++++++++++ .../parsing_and_relativedelta/config.toml | 1 + .../parsing_and_relativedelta/setup.py | 42 ++++++ .../python-dateutil/recurrence_rules/code.py | 123 ++++++++++++++++++ .../recurrence_rules/config.toml | 1 + .../python-dateutil/recurrence_rules/setup.py | 29 +++++ 8 files changed, 320 insertions(+) create mode 100644 examples/python-dateutil/README.md create mode 100644 examples/python-dateutil/order.json create mode 100644 examples/python-dateutil/parsing_and_relativedelta/code.py create mode 100644 examples/python-dateutil/parsing_and_relativedelta/config.toml create mode 100644 examples/python-dateutil/parsing_and_relativedelta/setup.py create mode 100644 examples/python-dateutil/recurrence_rules/code.py create mode 100644 examples/python-dateutil/recurrence_rules/config.toml create mode 100644 examples/python-dateutil/recurrence_rules/setup.py diff --git a/examples/python-dateutil/README.md b/examples/python-dateutil/README.md new file mode 100644 index 0000000..487a8df --- /dev/null +++ b/examples/python-dateutil/README.md @@ -0,0 +1,18 @@ +# python-dateutil Examples + +Each sub-directory contains a self-contained example. The order in +which the examples are to appear is specified in `order.json` (an +array of directory names in the expected order). + +In each example directory you'll find: + +* `config.toml` - must conform to the specification outlined here: + https://docs.pyscript.net/latest/user-guide/configuration/ This is + parsed and ultimately turned into a JSON representation as part of + the package's API object. +* `setup.py` - Python code for contextual and environmental setup, + NOT SEEN BY THE END USER, but is run before the `code.py` code is + evaluated. Allows us to create useful (IPython) shims, avoid + repeating boilerplate and whatnot. +* `code.py` - the actual code added to the editor which forms the + practical example of using the package. diff --git a/examples/python-dateutil/order.json b/examples/python-dateutil/order.json new file mode 100644 index 0000000..29d2810 --- /dev/null +++ b/examples/python-dateutil/order.json @@ -0,0 +1,4 @@ +[ + "parsing_and_relativedelta", + "recurrence_rules" +] diff --git a/examples/python-dateutil/parsing_and_relativedelta/code.py b/examples/python-dateutil/parsing_and_relativedelta/code.py new file mode 100644 index 0000000..5d7a4d4 --- /dev/null +++ b/examples/python-dateutil/parsing_and_relativedelta/code.py @@ -0,0 +1,102 @@ +""" +A friendly tour of python-dateutil. + +The standard library's `datetime` is great for representing instants, +but it's awkward when you need to *parse* messy human input or do +calendar-aware arithmetic like "the third Friday of next month." +That's where `dateutil` shines. + +Docs: https://dateutil.readthedocs.io/en/stable/ +""" +from IPython.core.display import display, HTML + +# --------------------------------------------------------------------- +# Section 1: Parsing dates from almost any string format. +# --------------------------------------------------------------------- + +heading("1. Parsing messy date strings") +note( + "A small batch of timestamps written by different people, in " + "different formats. dateutil.parser.parse handles them all." +) + +raw_timestamps = [ + "2024-03-15T09:41:00", + "March 15, 2024 9:41 AM", + "15/03/2024 09:41", + "Fri, 15 Mar 2024 09:41:00 +0100", + "20240315T094100", +] + +rows = [] +for raw in raw_timestamps: + parsed = parse(raw) + rows.append(f"{raw}{parsed}") + +display(HTML( + "" + "" + + "".join(rows) + + "
InputParsed datetime
" +), append=True) + +# Fuzzy parsing pulls a date out of surrounding prose. +sentence = "The package was shipped on Tuesday, April 12th 2022 at 5pm." +note(f"Fuzzy parse of: {sentence}") +note(f"Result: {parse(sentence, fuzzy=True)}") + +# Day-first vs. month-first ambiguity. +note( + "The string '04-05-2024' is ambiguous. dateutil lets you steer it: " + f"default reads it as {parse('04-05-2024').date()}, " + f"while dayfirst=True reads it as " + f"{parse('04-05-2024', dayfirst=True).date()}." +) + +# --------------------------------------------------------------------- +# Section 2: relativedelta for calendar-aware arithmetic. +# --------------------------------------------------------------------- + +heading("2. relativedelta: calendar-aware date math") +note( + "Unlike timedelta (which only knows about fixed durations), " + "relativedelta understands months, years, and weekday targets." +) + +today = date(2024, 3, 15) +note(f"Anchor date: {today} (a Friday).") + +examples = [ + ("Three months from now", + today + relativedelta(months=+3)), + ("One year and two months ago", + today + relativedelta(years=-1, months=-2)), + ("Next Monday", + today + relativedelta(weekday=MO(+1), days=+1)), + ("Last Friday of this month", + today + relativedelta(day=31, weekday=FR(-1))), + ("First Sunday of next month", + today + relativedelta(months=+1, day=1, weekday=SU(+1))), + ("End-of-month rollover from Jan 31", + date(2024, 1, 31) + relativedelta(months=+1)), +] + +rows = [ + f"{label}{value}" + for label, value in examples +] +display(HTML( + "" + "" + + "".join(rows) + + "
QuestionAnswer
" +), append=True) + +# The difference between two dates as a calendar-aware delta. +born = date(1995, 7, 22) +age = relativedelta(today, born) +note( + f"Someone born on {born} is " + f"{age.years} years, {age.months} months, " + f"and {age.days} days old on {today}." +) diff --git a/examples/python-dateutil/parsing_and_relativedelta/config.toml b/examples/python-dateutil/parsing_and_relativedelta/config.toml new file mode 100644 index 0000000..9a094d1 --- /dev/null +++ b/examples/python-dateutil/parsing_and_relativedelta/config.toml @@ -0,0 +1 @@ +packages = ["python-dateutil"] diff --git a/examples/python-dateutil/parsing_and_relativedelta/setup.py b/examples/python-dateutil/parsing_and_relativedelta/setup.py new file mode 100644 index 0000000..543a77f --- /dev/null +++ b/examples/python-dateutil/parsing_and_relativedelta/setup.py @@ -0,0 +1,42 @@ +"""Shim IPython's display API onto PyScript and import dateutil pieces.""" +import sys +import types +import js +from pyscript import window, HTML, display as _display + +js.alert = window.alert + + +def display(*args, **kwargs): + return _display( + *args, **kwargs, target=__pyscript_display_target__, + ) + + +ipython = types.ModuleType("IPython") +core = types.ModuleType("IPython.core") +core_display = types.ModuleType("IPython.core.display") +core_display.display = display +core_display.HTML = HTML +ipython.core = core +core.display = core_display +ipython.get_ipython = lambda: None +ipython.display = core_display +sys.modules["IPython"] = ipython +sys.modules["IPython.core"] = core +sys.modules["IPython.core.display"] = core_display +sys.modules["IPython.display"] = core_display + + +def heading(text, level=2): + display(HTML(f"{text}"), append=True) + + +def note(text): + display(HTML(f"

{text}

"), append=True) + + +# Package imports for the example. +from datetime import datetime, date +from dateutil.parser import parse, parserinfo +from dateutil.relativedelta import relativedelta, MO, FR, SU diff --git a/examples/python-dateutil/recurrence_rules/code.py b/examples/python-dateutil/recurrence_rules/code.py new file mode 100644 index 0000000..3b9df4c --- /dev/null +++ b/examples/python-dateutil/recurrence_rules/code.py @@ -0,0 +1,123 @@ +# --------------------------------------------------------------------- +# Section 3: Recurrence rules - schedules, holidays, and exceptions. +# --------------------------------------------------------------------- + +heading("3. rrule: generate schedules from recurrence rules") +note( + "rrule implements the iCalendar RECUR specification. You can " + "describe a schedule declaratively and iterate over its " + "occurrences, just like a calendar app does behind the scenes." +) + +# A standing meeting: every other Tuesday and Thursday at 10:00, +# for the next eight occurrences. +standup = rrule( + WEEKLY, + interval=2, + byweekday=(TU, TH), + count=8, + dtstart=datetime(2024, 4, 2, 10, 0), +) + +note("Bi-weekly stand-up on Tuesdays and Thursdays:") +display(HTML( + "" +), append=True) + +# Every Friday the 13th in the 21st century (first six). +unlucky = rrule( + YEARLY, + byweekday=FR, + bymonthday=13, + count=6, + dtstart=datetime(2024, 1, 1), +) + +note("The next six Friday the 13ths:") +display(HTML( + "" +), append=True) + +# US Presidential Election Day: first Tuesday after a Monday in +# November, every 4 years. The bymonthday=(2..8) clause forces the +# Tuesday to fall after the first Monday of the month. +elections = rrule( + YEARLY, + interval=4, + bymonth=11, + byweekday=TU, + bymonthday=(2, 3, 4, 5, 6, 7, 8), + count=4, + dtstart=datetime(2024, 1, 1), +) + +note("Upcoming US Presidential Election Days:") +display(HTML( + "" +), append=True) + +# --------------------------------------------------------------------- +# rruleset: combine rules and add or remove specific dates. +# --------------------------------------------------------------------- + +heading("4. rruleset: combining rules with exceptions") +note( + "Real-world calendars are rarely a single clean rule. rruleset " + "lets you union multiple rules, add one-off dates with rdate, " + "and exclude dates with exdate." +) + +# Daily for two weeks, but skip weekends and one specific holiday. +schedule = rruleset() +schedule.rrule(rrule( + DAILY, + count=14, + dtstart=datetime(2024, 7, 1, 9, 0), +)) +# Exclude all Saturdays and Sundays in this range. +schedule.exrule(rrule( + DAILY, + byweekday=(SA, SU), + dtstart=datetime(2024, 7, 1, 9, 0), + until=datetime(2024, 7, 15, 9, 0), +)) +# Exclude US Independence Day, which falls inside the window. +schedule.exdate(datetime(2024, 7, 4, 9, 0)) + +note("Working days in the first half of July 2024:") +display(HTML( + "" +), append=True) + +# --------------------------------------------------------------------- +# rrulestr: parse an iCalendar RRULE string directly. +# --------------------------------------------------------------------- + +heading("5. rrulestr: parse iCalendar RRULE strings") +note( + "If you already have an RFC 5545 RRULE string (for example, from " + "an .ics file), rrulestr parses it directly." +) + +ical_rule = """ +DTSTART:20240115T080000 +RRULE:FREQ=MONTHLY;BYDAY=3MO;COUNT=6 +""" +note("Parsing: FREQ=MONTHLY;BYDAY=3MO;COUNT=6 " + "(third Monday of the month, six times):") + +occurrences = list(rrulestr(ical_rule)) +display(HTML( + "" +), append=True) diff --git a/examples/python-dateutil/recurrence_rules/config.toml b/examples/python-dateutil/recurrence_rules/config.toml new file mode 100644 index 0000000..9a094d1 --- /dev/null +++ b/examples/python-dateutil/recurrence_rules/config.toml @@ -0,0 +1 @@ +packages = ["python-dateutil"] diff --git a/examples/python-dateutil/recurrence_rules/setup.py b/examples/python-dateutil/recurrence_rules/setup.py new file mode 100644 index 0000000..537a8dd --- /dev/null +++ b/examples/python-dateutil/recurrence_rules/setup.py @@ -0,0 +1,29 @@ +"""Lightweight setup for the second example. No IPython shim here.""" +import js +from pyscript import window, HTML, display as _display + +js.alert = window.alert + + +def display(*args, **kwargs): + return _display( + *args, **kwargs, target=__pyscript_display_target__, + ) + + +def heading(text, level=2): + display(HTML(f"{text}"), append=True) + + +def note(text): + display(HTML(f"

{text}

"), append=True) + + +# Package imports for the example. +from datetime import datetime +from dateutil.parser import parse +from dateutil.rrule import ( + rrule, rruleset, rrulestr, + YEARLY, MONTHLY, WEEKLY, DAILY, + MO, TU, WE, TH, FR, SA, SU, +) From b36976191ab45bebc97e8f1c67a7abe5ada4b815 Mon Sep 17 00:00:00 2001 From: "Nicholas H.Tollervey" Date: Thu, 11 Jun 2026 16:17:31 +0100 Subject: [PATCH 2/2] Fix imports. --- .../python-dateutil/parsing_and_relativedelta/code.py | 6 ++++++ .../python-dateutil/parsing_and_relativedelta/setup.py | 6 ------ examples/python-dateutil/recurrence_rules/code.py | 9 +++++++++ examples/python-dateutil/recurrence_rules/setup.py | 9 --------- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/examples/python-dateutil/parsing_and_relativedelta/code.py b/examples/python-dateutil/parsing_and_relativedelta/code.py index 5d7a4d4..58fd905 100644 --- a/examples/python-dateutil/parsing_and_relativedelta/code.py +++ b/examples/python-dateutil/parsing_and_relativedelta/code.py @@ -10,6 +10,12 @@ """ from IPython.core.display import display, HTML +# Package imports for the example. +from datetime import datetime, date +from dateutil.parser import parse, parserinfo +from dateutil.relativedelta import relativedelta, MO, FR, SU + + # --------------------------------------------------------------------- # Section 1: Parsing dates from almost any string format. # --------------------------------------------------------------------- diff --git a/examples/python-dateutil/parsing_and_relativedelta/setup.py b/examples/python-dateutil/parsing_and_relativedelta/setup.py index 543a77f..2e6be8d 100644 --- a/examples/python-dateutil/parsing_and_relativedelta/setup.py +++ b/examples/python-dateutil/parsing_and_relativedelta/setup.py @@ -34,9 +34,3 @@ def heading(text, level=2): def note(text): display(HTML(f"

{text}

"), append=True) - - -# Package imports for the example. -from datetime import datetime, date -from dateutil.parser import parse, parserinfo -from dateutil.relativedelta import relativedelta, MO, FR, SU diff --git a/examples/python-dateutil/recurrence_rules/code.py b/examples/python-dateutil/recurrence_rules/code.py index 3b9df4c..4579cd5 100644 --- a/examples/python-dateutil/recurrence_rules/code.py +++ b/examples/python-dateutil/recurrence_rules/code.py @@ -1,6 +1,15 @@ # --------------------------------------------------------------------- # Section 3: Recurrence rules - schedules, holidays, and exceptions. # --------------------------------------------------------------------- +# Package imports for the example. +from datetime import datetime +from dateutil.parser import parse +from dateutil.rrule import ( + rrule, rruleset, rrulestr, + YEARLY, MONTHLY, WEEKLY, DAILY, + MO, TU, WE, TH, FR, SA, SU, +) + heading("3. rrule: generate schedules from recurrence rules") note( diff --git a/examples/python-dateutil/recurrence_rules/setup.py b/examples/python-dateutil/recurrence_rules/setup.py index 537a8dd..0029200 100644 --- a/examples/python-dateutil/recurrence_rules/setup.py +++ b/examples/python-dateutil/recurrence_rules/setup.py @@ -18,12 +18,3 @@ def heading(text, level=2): def note(text): display(HTML(f"

{text}

"), append=True) - -# Package imports for the example. -from datetime import datetime -from dateutil.parser import parse -from dateutil.rrule import ( - rrule, rruleset, rrulestr, - YEARLY, MONTHLY, WEEKLY, DAILY, - MO, TU, WE, TH, FR, SA, SU, -)