All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
- Picklable field properties:
field()now returns module-level classes (_FieldProperty,_GetterProperty) that survive pickle round-trips. Previously, properties liketransparencywere unpicklable closures, causing agents to lose imports between turns. - Tighter type annotation:
field()signature narrowed fromCallable[[Any], Any]toCallable[[Interval], Any].
- Skill documentation: Rewrote
calgebraandgcalskills with complete quick-start examples, common pitfalls, and expanded coverage.
- Google Calendar transparency normalization: Events missing the
transparencyfield (the default for busy events) are now normalized to"opaque"at parse time. Previously,transparency == "opaque"filters would miss these events, making busy calendars appear free.
- Skill documentation: Updated
gcalskill examples to use straightforwardtransparency == "opaque"filtering now that normalization handles missing values.
- Skill documentation:
gcalskill update.
- Google Calendar REST backend (
calgebra.gcal): Full read/write support via direct REST API calls with an OAuth access token. Works in browser/Pyodide environments (sync XMLHttpRequest in Web Workers, urllib fallback on server). IncludesCalendar(aMutableTimeline),Event,Reminder,Attendee, pagination, recurring event writes, and instance-level removal. - Skill documentation: YAML-frontmatter skill files for
calgebraandgcal, discoverable by agex agents via/skills/. Calendar.primaryandCalendar.timezonefields: Expose Google Calendar metadata.
- Python 3.10 compatibility: Handle
Zsuffix indatetime.fromisoformat()(added in 3.11). - URL-encoding in gcal API requests: Query parameters are now properly URL-encoded.
- Deferred type annotations in
ical.py: AvoidNameErrorwhenicalendaris not installed. - Timezone fetch failure logging: Warn instead of silently falling back to UTC.
0.10.7 - 2026-02-06
Complement.overlapping()with recurring sources: FixedValueErrorwhen callingoverlapping()on complements ofRecurringPatterntimelines (e.g.,(~day_of_week(...)).overlapping(point))Difference._sweepsubtractor bound: Tightened loop bound from<=to<to skip subtractors that can't overlap half-open intervalsbuffer()query bounds: Widened source query bounds to capture intervals that shift into range after bufferingoverlapping()correctness: FixedDifference.overlapping()andComplement.overlapping()to return full unclipped intervalsCachedTimelinethread safety: Added lock around fetch to prevent concurrent state corruption
- Streaming reverse iteration: Replaced materialized-list reverse with time-negation trick for
Intersection,Difference, andComplement RecurringPattern.exdates: Now an immutablefrozenset- Internal cleanups: Removed redundant defensive code in
_remove_recurring_instanceand_add_interval
0.10.6 - 2026-01-30
- Sync embedded docs: Embedded docs match the top-level docs
0.10.5 - 2026-01-30
cached()wrapper: TTL-based caching for slow upstream timelines (e.g., Google Calendar)- Partial cache hits: only fetches uncached portions of a query range
- Automatic interval deduplication at cache segment boundaries
- TTL-based eviction with interval fracturing at expiry boundaries
Timeline.overlapping(point): Find all intervals containing a specific point, returning full unclipped intervals- Pre-commit hooks: Added
.pre-commit-config.yamlwith ruff linting and formatting
- Switched to ruff for formatting: Replaced black with ruff format in CI and dev tooling
0.10.4 - 2026-01-25
tutorial.mdtrim: Reduced size of the tutorial
0.10.3 - 2026-01-02
to_dataframe()trim: Reduce the default set of included properties
0.10.2 - 2026-01-02
to_dataframe()helper: Convert intervals to pandas DataFrames with human-friendly formattingInterval.durationproperty: Returnsend - startin seconds (orNoneif unbounded)
- Top-level iCal exports:
file_to_timeline,timeline_to_file, andICalEventare now available directly fromcalgebra(previously requiredfrom calgebra.ical import ...) - Timeline metadata preservation: Union, intersection, and other composite timelines now preserve source calendar metadata (
calendar_name,calendar_summary) through operations
0.10.1 - 2026-01-01
- Hourly period returns
datetimelabels:period="hour"now returnsdatetimekeys instead ofdate, so each hour is distinguishable. Other periods (day,week,month,year,full) continue to returndatekeys.
0.10.0 - 2025-12-31
- Metrics
group_byparameter: Create cyclic histograms from metricstotal_duration(),count_intervals(),coverage_ratio()now acceptgroup_byparameter- Valid groupings:
hour_of_day,day_of_week,day_of_month,week_of_year,month_of_year - Example:
total_duration(cal, start, end, period="hour", group_by="hour_of_day")→ hour-of-day histogram
period="hour"support: Metrics now support hourly granularityGroupBytype: Exported fromcalgebra.metricsfor type hints
0.9.1 - 2025-12-31
- ICalEvent enhancements: New fields for richer iCalendar metadata
calendar_name: Source calendar name (extracted fromX-WR-CALNAME)status: Event status (TENTATIVE,CONFIRMED,CANCELLED)transp: Time transparency (OPAQUE= busy,TRANSPARENT= free; defaults toOPAQUEper RFC 5545)categories: Tuple of category tags
- Field helpers: New
Propertyobjects for filtering by the new fields
0.9.0 - 2025-12-21
- iCalendar Support: Load and save
.icsfiles (RFC 5545)calgebra.ical.file_to_timeline(): Load.icsfiles into a memory timelinecalgebra.ical.timeline_to_file(): Save timelines to.icsfilesICalEvent: New interval subclass preserving iCalendar fields (UID, location, etc.)- Full round-trip support for recurring patterns (RRULE)
0.8.4 - 2025-12-16
- Interval formatting: New
Interval.format()method for human-readable datetime output - pprint() helper: Pretty-print intervals with timezone-aware formatting
0.8.3 - 2025-12-16
- Documentation dataclass:
docsexported as dataclass for better IDE support
- Recurrence pattern anchor calculation now handles zero correctly
- GCSA timezone discovery optimized to fetch only once per calendar
- Recurrence anchor alignment improved
0.8.2 - 2025-12-02
- at_tz(): Now supports date strings and naked datetimes
0.8.1 - 2025-11-30
- GCSA Batch Write: GCSA timeline support for batch event inserts
- GCSA Lazy Timezones: GCSA timelines discover timezone on demand
0.8.0 - 2025-11-27
- Reverse Iteration: All timelines now support reverse chronological iteration via slice step syntax
timeline[start:end:-1]yields events newest-first
- Recurrence pattern phase alignment with rrule
0.7.1 - 2025-11-26
- Quick-Start: New quick-start documentation (included programmatically)
0.7.0 - 2025-11-25
- Breaking: Metrics API redesigned for efficient periodic aggregations
- Intersection now correctly handles overlapping intervals from same source with identical timestamps
- Google Calendar all-day event handling improved
0.6.1 - 2025-11-23
- No try/catch on package alias (more friendly to linters)
0.6.0 - 2025-11-22
- Mutable Timeline API (
calgebra.mutable): Write operations (add(),remove(),remove_series()) withWriteResultreturn type for handling partial failures - Google Calendar Write Support:
GoogleCalendarTimeline(exported asCalendar) now supports creating, updating, and deleting events - Documentation: New
GCSA.mdguide for Google Calendar integration
- Intersection algorithm now correctly handles overlapping intervals when sources have different lengths
0.5.0 - 2025-11-20
- Breaking: All intervals now use exclusive end bounds (
[start, end)), matching Python slicing idioms throughout the library.Interval(start=10, end=13)now represents 3 seconds{10, 11, 12}(previously 4 seconds).- Adjacent intervals like
[0, 5)and[5, 10)naturally touch at the boundary.
0.4.1 - 2025-11-19
- Minor performance improvement for union (now flattens nested ops)
- Consistent Recurrence: Recurring patterns now stay anchored consistently regardless of query timeframe
0.4.0 - 2025-11-19
- Breaking: Timeline slicing now uses exclusive end bounds (
[start:end)), aligning with standard Python slicing idioms. Previously, slicing was inclusive of the end bound.timeline[start:end]now returns intervals in the range[start, end - 1].Timeline.fetch(start, end)also respects this exclusive end behavior.
- Updated metric functions (
total_duration,max_duration,min_duration,count_intervals,coverage_ratio) to interpret theirendparameter as exclusive. - Updated
coverage_ratiocalculation to useend - start(instead ofend - start + 1) for the denominator, correctly reflecting the span of an exclusive slice.
- Updated
README.md,TUTORIAL.md, andAPI.mdto explicitly state that timeline slicing uses exclusive end bounds while internalIntervalobjects remain inclusive.
0.3.2 - 2025-11-16
gcsa.Eventinstances now includecalendar_summarymetadata alongsidecalendar_id, making it easier to display the original calendar when unions/intersections combine timelines.
- Replaced
list_calendars()withcalendars(), which authenticates once and returns ready-to-useCalendartimelines (each reuses the shared client but tracks its own IDs/summaries).
0.3.1 - 2025-11-16
gcsa.Eventnow carries its originatingcalendar_id, so provenance survives unions/differences without custom bookkeeping.
- Added an API section describing the gcsa module and its helpers to make the integration easier for agents and scripts to discover.
0.3.0 - 2025-11-06
at_tz()helper function: Ergonomic timezone-aware datetime factory for timeline slicing- Create datetime factories:
at = at_tz("US/Pacific") - Parse date strings:
at("2024-01-01")→ midnight in timezone - Use with slicing:
timeline[at("2024-01-01"):at("2024-12-31")]
- Create datetime factories:
- Removed
dateobject support in Timeline slicing: Timeline slices now only acceptint(Unix timestamps) or timezone-awaredatetimeobjects. - Removed
timezone_nameparameter fromCalendar: The Calendar class no longer accepts atimezone_nameparameter.
0.2.3 - 2025-10-30
- Automatic interval clipping: All timelines now automatically clip intervals to query bounds in
Timeline.__getitem__. When you slicetimeline[start:end], any intervals extending beyond those bounds are trimmed to fit.
0.2.2 - 2025-10-30
- Unbounded end support for recurring patterns:
recurring(),day_of_week(), andtime_of_day()now support unbounded end queries via automatic paging (e.g.,recurring(...)[start:]) - Adjacent intervals from recurring patterns are now automatically merged via
flatten()
- Lookback bug fix: Recurring patterns now correctly include long-duration events that start before the query range but extend into it
- Events that span midnight (e.g., 11pm for 3 hours) are now correctly captured across page boundaries
0.2.1 - 2025-10-27
buffer()now validates thatbeforeandafterparameters are non-negative
buffer()now correctly handles unbounded intervals (None values) instead of raising TypeErrormerge_within()now correctly handles unbounded intervals when calculating gapsEvent.__str__()now correctly handles unbounded intervals in string representation
0.2.0 - 2025-10-27
- Unbounded intervals:
Intervalnow supportsNoneforstartorendto represent -∞ or +∞ finite_startandfinite_endproperties onIntervalthat return sentinel values for algorithm use- Complement (
~) now supports unbounded queries (start/end can beNone) flatten()now supports unbounded queries (uses double complement internally)- Comprehensive test suite for unbounded interval operations
- Breaking:
Interval.startandInterval.endare nowint | Noneinstead ofint StartandEndproperties may return sentinel values (NEG_INF/POS_INF) instead ofNonefor simpler filtering- Updated documentation to reflect unbounded interval support
0.1.0 - 2025-10-26
- Initial release of calgebra
- Core DSL with set-like operators (
|,&,-,~) for timeline composition - Timeline slicing with integer timestamps, timezone-aware datetime, and date objects
- Property-based filtering (
hours >= 2,start >= timestamp, etc.) - Recurring patterns via
recurring(),day_of_week(), andtime_of_day() - Metric functions:
total_duration,max_duration,min_duration,count_intervals,coverage_ratio - Transformations:
buffer()andmerge_within() - Google Calendar integration via
calgebra.gcsa.Calendar - Comprehensive documentation accessible via
calgebra.docsdictionary