private async Task UpdateExceptionsAsync(string id, IAppointmentSchedule schedule, bool force)
        {
            var exceptions = schedule.Recurrence.Exceptions.Where(kv => kv.Value != null);

            foreach (var kv in exceptions)
            {
                using (Logger.Scope($"GoogleCalendar.UpdateException({kv.Key.Date})"))
                {
                    EventsResource.InstancesRequest instancesRequest = _service.Events.Instances(CalendarId, id);
                    var originalStart = new DateTime(kv.Key.Year, kv.Key.Month, kv.Key.Day,
                                                     schedule.Start.Hour, schedule.Start.Minute, schedule.Start.Second);
                    TimeSpan utcOffset    = schedule.StartTimeZone.GetUtcOffset(originalStart);
                    string   offsetFormat = (utcOffset < TimeSpan.Zero ? "\\-" : "") + "hh\\:mm";
                    instancesRequest.OriginalStart = $"{originalStart:yyyy-MM-ddTHH:mm:ss}{utcOffset.ToString(offsetFormat)}";
                    Events instances = await instancesRequest.ExecuteAsync().ConfigureAwait(false);

                    Event instance = instances.Items.FirstOrDefault();
                    if (instance == null)
                    {
                        Logger.Warning($"Failed to find instance of \"{kv.Value}\" at {instancesRequest.OriginalStart} to create an exception");
                        continue;
                    }

                    if (force || kv.Value.LastModifiedDateTime > (instance.Updated ?? DateTime.MinValue))
                    {
                        await UpdateAppointmentAsync(instance.Id, instance.Sequence, kv.Value, force).ConfigureAwait(false);
                    }
                }
            }
        }
        void ExportAppointment(Appointment apt)
        {
            AppointmentType aptType = apt.Type;

            if (aptType == AppointmentType.Pattern)
            {
                EnsurePatternId(apt);
            }
            else if (aptType != AppointmentType.Normal)
            {
                string eventPatternId = EnsurePatternId(apt.RecurrencePattern);
                Debug.Assert(!String.IsNullOrEmpty(eventPatternId));
                if (aptType == AppointmentType.Occurrence)
                {
                    return;
                }
                EventsResource.InstancesRequest instancesRequest = CalendarService.Events.Instances(CalendarEntry.Id, eventPatternId);
                OccurrenceCalculator            calculator       = OccurrenceCalculator.CreateInstance(apt.RecurrencePattern.RecurrenceInfo);
                instancesRequest.OriginalStart = GoogleCalendarUtils.ConvertEventDateTime(calculator.CalcOccurrenceStartTime(apt.RecurrenceIndex)).DateTimeRaw;
                Events occurrenceEvents = instancesRequest.Execute();
                Debug.Assert(occurrenceEvents.Items.Count == 1);
                Event occurrence = occurrenceEvents.Items[0];
                if (aptType == AppointmentType.ChangedOccurrence)
                {
                    this.AssignProperties(apt, occurrence);
                }
                else if (aptType == AppointmentType.DeletedOccurrence)
                {
                    occurrence.Status = "cancelled";
                }
                Event changedOccurrence = CalendarService.Events.Update(occurrence, CalendarEntry.Id, occurrence.Id).Execute();
                apt.CustomFields["eventId"] = changedOccurrence.Id;
                Log.WriteLine(String.Format("Exported {0} occurrance: {1}, id={2}", (aptType == AppointmentType.ChangedOccurrence) ? "changed" : "deleted", apt.Subject, changedOccurrence.Id));
                return;
            }

            Event instance = this.CreateEvent(aptType);

            AssignProperties(apt, instance);

            Event result = CalendarService.Events.Insert(instance, CalendarEntry.Id).Execute();

            Log.WriteLine(String.Format("Exported appointment: {0}, id={1}", apt.Subject, result.Id));
        }