or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

access-control.mdattendees.mdcalendars.mdconferences.mdcore-operations.mdevents.mdfree-busy.mdindex.mdrecurrence.mdreminders.md

free-busy.mddocs/

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.