0
# Free/Busy and Availability
1
2
Checking calendar availability, free/busy time queries, and scheduling conflict detection. The FreeBusy class provides tools for determining when calendars and users are available for scheduling.
3
4
## Core Imports
5
6
```python
7
from gcsa.free_busy import FreeBusy, TimeRange, FreeBusyQueryError
8
from datetime import datetime, timedelta
9
```
10
11
## Capabilities
12
13
### FreeBusy Class
14
15
Represents free/busy information for given calendar(s) and/or group(s), providing availability data for scheduling decisions.
16
17
```python { .api }
18
class FreeBusy:
19
def __init__(
20
self,
21
time_min,
22
time_max,
23
groups: dict = None,
24
calendars: dict = None,
25
groups_errors: dict = None,
26
calendars_errors: dict = None
27
):
28
"""
29
Free/busy information for calendars and groups.
30
31
Parameters:
32
- time_min (datetime): Lower bound for the query
33
- time_max (datetime): Upper bound for the query
34
- groups (dict): Free/busy data for groups (group_id -> busy_times)
35
- calendars (dict): Free/busy data for calendars (calendar_id -> busy_times)
36
- groups_errors (dict): Errors for group queries
37
- calendars_errors (dict): Errors for calendar queries
38
"""
39
40
def __iter__(self):
41
"""
42
Iterate over busy time ranges for single calendar queries.
43
44
Returns:
45
Iterator of TimeRange objects
46
"""
47
```
48
49
### TimeRange
50
51
Named tuple representing a time range with start and end times.
52
53
```python { .api }
54
class TimeRange:
55
"""
56
Represents a time range with start and end times.
57
58
Attributes:
59
- start (datetime): Start time of the range
60
- end (datetime): End time of the range
61
"""
62
start: datetime
63
end: datetime
64
```
65
66
### FreeBusyQueryError
67
68
Exception class for free/busy query errors.
69
70
```python { .api }
71
class FreeBusyQueryError(Exception):
72
def __init__(
73
self,
74
groups_errors: dict = None,
75
calendars_errors: dict = None
76
):
77
"""
78
Exception for free/busy query errors.
79
80
Parameters:
81
- groups_errors (dict): Errors for group queries
82
- calendars_errors (dict): Errors for calendar queries
83
"""
84
```
85
86
### Free/Busy Queries via GoogleCalendar
87
88
Method for querying free/busy information through the main GoogleCalendar client.
89
90
```python { .api }
91
def get_free_busy(
92
self,
93
time_min,
94
time_max,
95
calendars: list = None,
96
groups: list = None,
97
group_expansion_max: int = None,
98
calendar_expansion_max: int = None
99
):
100
"""
101
Get free/busy information for calendars and groups.
102
103
Parameters:
104
- time_min (datetime): Lower bound for query (inclusive)
105
- time_max (datetime): Upper bound for query (exclusive)
106
- calendars (list): List of calendar IDs to query
107
- groups (list): List of group IDs to query
108
- group_expansion_max (int): Maximum number of calendars to expand per group
109
- calendar_expansion_max (int): Maximum number of calendars to expand total
110
111
Returns:
112
FreeBusy object with availability information
113
"""
114
```
115
116
## Usage Examples
117
118
### Basic Free/Busy Query
119
120
```python
121
from gcsa.google_calendar import GoogleCalendar
122
from datetime import datetime, timedelta
123
124
gc = GoogleCalendar()
125
126
# Query free/busy for next week
127
start_time = datetime.now()
128
end_time = start_time + timedelta(days=7)
129
130
# Check single calendar availability
131
free_busy = gc.get_free_busy(
132
time_min=start_time,
133
time_max=end_time,
134
calendars=['primary']
135
)
136
137
# Print busy times
138
print("Busy times:")
139
for busy_time in free_busy:
140
print(f" {busy_time.start} - {busy_time.end}")
141
```
142
143
### Multiple Calendar Availability
144
145
```python
146
# Check multiple calendars
147
calendar_ids = [
148
'primary',
149
'work.calendar@company.com',
150
'personal@gmail.com'
151
]
152
153
free_busy = gc.get_free_busy(
154
time_min=datetime(2024, 1, 15, 0, 0),
155
time_max=datetime(2024, 1, 22, 0, 0),
156
calendars=calendar_ids
157
)
158
159
# Check each calendar's availability
160
if free_busy.calendars:
161
for calendar_id, busy_times in free_busy.calendars.items():
162
print(f"\nCalendar {calendar_id}:")
163
if busy_times:
164
for time_range in busy_times:
165
print(f" Busy: {time_range.start} - {time_range.end}")
166
else:
167
print(" No busy times")
168
```
169
170
### Group-Based Free/Busy Queries
171
172
```python
173
# Query group availability
174
group_ids = ['team@company.com', 'managers@company.com']
175
176
free_busy = gc.get_free_busy(
177
time_min=datetime(2024, 1, 15, 9, 0),
178
time_max=datetime(2024, 1, 15, 17, 0),
179
groups=group_ids,
180
group_expansion_max=10 # Limit calendars per group
181
)
182
183
# Check group availability
184
if free_busy.groups:
185
for group_id, busy_times in free_busy.groups.items():
186
print(f"\nGroup {group_id}:")
187
for time_range in busy_times:
188
print(f" Busy: {time_range.start} - {time_range.end}")
189
```
190
191
### Finding Available Time Slots
192
193
```python
194
def find_available_slots(gc, calendar_ids, start_time, end_time, slot_duration):
195
"""
196
Find available time slots for scheduling.
197
198
Parameters:
199
- gc: GoogleCalendar instance
200
- calendar_ids: List of calendar IDs to check
201
- start_time: Start of search period
202
- end_time: End of search period
203
- slot_duration: Required duration for slots (timedelta)
204
205
Returns:
206
List of available TimeRange objects
207
"""
208
# Get free/busy information
209
free_busy = gc.get_free_busy(
210
time_min=start_time,
211
time_max=end_time,
212
calendars=calendar_ids
213
)
214
215
# Collect all busy times
216
all_busy_times = []
217
if free_busy.calendars:
218
for calendar_id, busy_times in free_busy.calendars.items():
219
all_busy_times.extend(busy_times)
220
221
# Sort busy times by start time
222
all_busy_times.sort(key=lambda x: x.start)
223
224
# Find gaps between busy times
225
available_slots = []
226
current_time = start_time
227
228
for busy_time in all_busy_times:
229
# Check if there's a gap before this busy time
230
if current_time < busy_time.start:
231
gap_duration = busy_time.start - current_time
232
if gap_duration >= slot_duration:
233
available_slots.append(TimeRange(current_time, busy_time.start))
234
235
# Move current time to end of busy period
236
current_time = max(current_time, busy_time.end)
237
238
# Check for availability after last busy time
239
if current_time < end_time:
240
gap_duration = end_time - current_time
241
if gap_duration >= slot_duration:
242
available_slots.append(TimeRange(current_time, end_time))
243
244
return available_slots
245
246
# Example usage
247
calendar_ids = ['primary', 'colleague@company.com']
248
start_search = datetime(2024, 1, 15, 9, 0)
249
end_search = datetime(2024, 1, 15, 17, 0)
250
meeting_duration = timedelta(hours=1)
251
252
available_slots = find_available_slots(
253
gc, calendar_ids, start_search, end_search, meeting_duration
254
)
255
256
print("Available 1-hour slots:")
257
for slot in available_slots:
258
print(f" {slot.start} - {slot.end}")
259
```
260
261
### Team Availability Analysis
262
263
```python
264
def analyze_team_availability(gc, team_calendars, start_date, end_date):
265
"""
266
Analyze when team members are generally available.
267
"""
268
# Query free/busy for the team
269
free_busy = gc.get_free_busy(
270
time_min=start_date,
271
time_max=end_date,
272
calendars=team_calendars
273
)
274
275
availability_report = {}
276
277
if free_busy.calendars:
278
for calendar_id, busy_times in free_busy.calendars.items():
279
total_period = end_date - start_date
280
total_busy_time = timedelta()
281
282
for busy_time in busy_times:
283
duration = busy_time.end - busy_time.start
284
total_busy_time += duration
285
286
# Calculate availability percentage
287
busy_percentage = (total_busy_time.total_seconds() /
288
total_period.total_seconds()) * 100
289
available_percentage = 100 - busy_percentage
290
291
availability_report[calendar_id] = {
292
'busy_time': total_busy_time,
293
'available_percentage': available_percentage,
294
'busy_periods': len(busy_times)
295
}
296
297
return availability_report
298
299
# Example usage
300
team_calendars = [
301
'alice@company.com',
302
'bob@company.com',
303
'charlie@company.com'
304
]
305
306
report = analyze_team_availability(
307
gc,
308
team_calendars,
309
datetime(2024, 1, 15, 0, 0),
310
datetime(2024, 1, 22, 0, 0)
311
)
312
313
for member, stats in report.items():
314
print(f"{member}:")
315
print(f" Available: {stats['available_percentage']:.1f}%")
316
print(f" Busy periods: {stats['busy_periods']}")
317
```
318
319
### Conflict Detection
320
321
```python
322
def check_scheduling_conflicts(gc, calendar_ids, proposed_start, proposed_end):
323
"""
324
Check if a proposed meeting time conflicts with existing events.
325
"""
326
# Add buffer time for checking conflicts
327
buffer = timedelta(minutes=15)
328
check_start = proposed_start - buffer
329
check_end = proposed_end + buffer
330
331
free_busy = gc.get_free_busy(
332
time_min=check_start,
333
time_max=check_end,
334
calendars=calendar_ids
335
)
336
337
conflicts = {}
338
339
if free_busy.calendars:
340
for calendar_id, busy_times in free_busy.calendars.items():
341
calendar_conflicts = []
342
343
for busy_time in busy_times:
344
# Check if busy time overlaps with proposed time
345
if (busy_time.start < proposed_end and
346
busy_time.end > proposed_start):
347
calendar_conflicts.append(busy_time)
348
349
if calendar_conflicts:
350
conflicts[calendar_id] = calendar_conflicts
351
352
return conflicts
353
354
# Example usage
355
proposed_meeting_start = datetime(2024, 1, 15, 14, 0)
356
proposed_meeting_end = datetime(2024, 1, 15, 15, 0)
357
358
conflicts = check_scheduling_conflicts(
359
gc,
360
['primary', 'assistant@company.com'],
361
proposed_meeting_start,
362
proposed_meeting_end
363
)
364
365
if conflicts:
366
print("Scheduling conflicts detected:")
367
for calendar_id, conflict_times in conflicts.items():
368
print(f" {calendar_id}:")
369
for conflict in conflict_times:
370
print(f" {conflict.start} - {conflict.end}")
371
else:
372
print("No conflicts detected - time slot is available")
373
```
374
375
### Error Handling
376
377
```python
378
from gcsa.free_busy import FreeBusyQueryError
379
380
try:
381
free_busy = gc.get_free_busy(
382
time_min=datetime(2024, 1, 15, 0, 0),
383
time_max=datetime(2024, 1, 22, 0, 0),
384
calendars=['invalid_calendar_id'],
385
groups=['invalid_group_id']
386
)
387
388
# Check for errors in the response
389
if free_busy.calendars_errors:
390
print("Calendar errors:")
391
for calendar_id, error in free_busy.calendars_errors.items():
392
print(f" {calendar_id}: {error}")
393
394
if free_busy.groups_errors:
395
print("Group errors:")
396
for group_id, error in free_busy.groups_errors.items():
397
print(f" {group_id}: {error}")
398
399
except FreeBusyQueryError as e:
400
print(f"Free/busy query failed: {e}")
401
except Exception as e:
402
print(f"Unexpected error: {e}")
403
```
404
405
### Advanced Time Analysis
406
407
```python
408
def get_daily_availability_pattern(gc, calendar_id, start_date, num_days=7):
409
"""
410
Analyze daily availability patterns over a period.
411
"""
412
daily_patterns = {}
413
414
for day_offset in range(num_days):
415
current_date = start_date + timedelta(days=day_offset)
416
day_start = current_date.replace(hour=9, minute=0, second=0, microsecond=0)
417
day_end = current_date.replace(hour=17, minute=0, second=0, microsecond=0)
418
419
free_busy = gc.get_free_busy(
420
time_min=day_start,
421
time_max=day_end,
422
calendars=[calendar_id]
423
)
424
425
# Calculate busy time for this day
426
busy_minutes = 0
427
if free_busy.calendars and calendar_id in free_busy.calendars:
428
for busy_time in free_busy.calendars[calendar_id]:
429
# Clamp to business hours
430
start = max(busy_time.start, day_start)
431
end = min(busy_time.end, day_end)
432
if start < end:
433
busy_minutes += (end - start).total_seconds() / 60
434
435
# 8-hour workday = 480 minutes
436
availability_percentage = (480 - busy_minutes) / 480 * 100
437
438
daily_patterns[current_date.strftime('%Y-%m-%d')] = {
439
'busy_minutes': busy_minutes,
440
'availability_percentage': availability_percentage
441
}
442
443
return daily_patterns
444
445
# Example usage
446
patterns = get_daily_availability_pattern(
447
gc,
448
'primary',
449
datetime(2024, 1, 15)
450
)
451
452
for date, pattern in patterns.items():
453
print(f"{date}: {pattern['availability_percentage']:.1f}% available")
454
```
455
456
The FreeBusy functionality in GCSA provides powerful tools for availability checking, conflict detection, and scheduling optimization, making it essential for building calendar coordination and meeting scheduling applications.