Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions examples/python-dateutil/README.md
Original file line number Diff line number Diff line change
@@ -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.
4 changes: 4 additions & 0 deletions examples/python-dateutil/order.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[
"parsing_and_relativedelta",
"recurrence_rules"
]
108 changes: 108 additions & 0 deletions examples/python-dateutil/parsing_and_relativedelta/code.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
"""
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

# 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.
# ---------------------------------------------------------------------

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"<tr><td><code>{raw}</code></td><td>{parsed}</td></tr>")

display(HTML(
"<table border='1' cellpadding='6' cellspacing='0'>"
"<tr><th>Input</th><th>Parsed datetime</th></tr>"
+ "".join(rows) +
"</table>"
), 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: <em>{sentence}</em>")
note(f"Result: <strong>{parse(sentence, fuzzy=True)}</strong>")

# Day-first vs. month-first ambiguity.
note(
"The string '04-05-2024' is ambiguous. dateutil lets you steer it: "
f"default reads it as <strong>{parse('04-05-2024').date()}</strong>, "
f"while dayfirst=True reads it as "
f"<strong>{parse('04-05-2024', dayfirst=True).date()}</strong>."
)

# ---------------------------------------------------------------------
# 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: <strong>{today}</strong> (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"<tr><td>{label}</td><td><strong>{value}</strong></td></tr>"
for label, value in examples
]
display(HTML(
"<table border='1' cellpadding='6' cellspacing='0'>"
"<tr><th>Question</th><th>Answer</th></tr>"
+ "".join(rows) +
"</table>"
), 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"<strong>{age.years} years, {age.months} months, "
f"and {age.days} days</strong> old on {today}."
)
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
packages = ["python-dateutil"]
37 changes: 37 additions & 0 deletions examples/python-dateutil/parsing_and_relativedelta/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
"""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.version_info = (9, 0, 2, '')
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"<h{level}>{text}</h{level}>"), append=True)


def note(text):
display(HTML(f"<p>{text}</p>"), append=True)
132 changes: 132 additions & 0 deletions examples/python-dateutil/recurrence_rules/code.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
# ---------------------------------------------------------------------
# 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(
"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(
"<ul>"
+ "".join(f"<li>{when:%a %Y-%m-%d %H:%M}</li>" for when in standup)
+ "</ul>"
), 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(
"<ul>"
+ "".join(f"<li>{when:%A, %B %d, %Y}</li>" for when in unlucky)
+ "</ul>"
), 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(
"<ul>"
+ "".join(f"<li>{when:%A, %B %d, %Y}</li>" for when in elections)
+ "</ul>"
), 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(
"<ul>"
+ "".join(f"<li>{when:%a %Y-%m-%d}</li>" for when in schedule)
+ "</ul>"
), 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: <code>FREQ=MONTHLY;BYDAY=3MO;COUNT=6</code> "
"(third Monday of the month, six times):")

occurrences = list(rrulestr(ical_rule))
display(HTML(
"<ul>"
+ "".join(f"<li>{when:%A, %B %d, %Y at %H:%M}</li>"
for when in occurrences)
+ "</ul>"
), append=True)
1 change: 1 addition & 0 deletions examples/python-dateutil/recurrence_rules/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
packages = ["python-dateutil"]
20 changes: 20 additions & 0 deletions examples/python-dateutil/recurrence_rules/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
"""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"<h{level}>{text}</h{level}>"), append=True)


def note(text):
display(HTML(f"<p>{text}</p>"), append=True)