Edited to provide a cleaner solution:
This code will display different contents for available times and unavailable times in 30 min increments, and makes no assumption on how long your events are.
{% set targetDate = "today" %}
{% set events = craft.calendar.events({
dateRangeStart: targetDate,
dateRangeEnd: targetDate
}) %}
{% for hour in craft.calendar.day({date: targetDate}) %}
{% set dateFirstHalfHour = hour.date.copy.addMinutes(30) %}
{% set dateSecondHalfHour = hour.date.copy.addMinutes(60) %}
{% set hasEventsInFirstHalf = false %}
{% set hasEventsInSecondHalf = false %}
{% for event in events %}
{% if hour.date >= event.startDate and hour.date < event.endDate %}
{% set hasEventsInFirstHalf = true %}
{% endif %}
{% if dateFirstHalfHour >= event.startDate and dateFirstHalfHour < event.endDate %}
{% set hasEventsInSecondHalf = true %}
{% endif %}
{% endfor %}
<div>
{{ hour.date.format("H:i") }} - {{ hasEventsInFirstHalf ? "unavailable" : "available" }}
</div>
<div>
{{ dateFirstHalfHour.format("H:i") }} - {{ hasEventsInSecondHalf ? "unavailable" : "available" }}
</div>
{% endfor %}