@Override
  public void populate(Resource resource) throws LocalStorageException {
    Event e = (Event) resource;

    try {
      @Cleanup
      Cursor cursor =
          providerClient.query(
              ContentUris.withAppendedId(entriesURI(), e.getLocalID()),
              new String[] {
                /*  0 */ Events.TITLE,
                Events.EVENT_LOCATION,
                Events.DESCRIPTION,
                /*  3 */ Events.DTSTART,
                Events.DTEND,
                Events.EVENT_TIMEZONE,
                Events.EVENT_END_TIMEZONE,
                Events.ALL_DAY,
                /*  8 */ Events.STATUS,
                Events.ACCESS_LEVEL,
                /* 10 */ Events.RRULE,
                Events.RDATE,
                Events.EXRULE,
                Events.EXDATE,
                /* 14 */ Events.HAS_ATTENDEE_DATA,
                Events.ORGANIZER,
                Events.SELF_ATTENDEE_STATUS,
                /* 17 */ entryColumnUID(),
                Events.DURATION,
                Events.AVAILABILITY
              },
              null,
              null,
              null);
      if (cursor != null && cursor.moveToNext()) {
        e.setUid(cursor.getString(17));

        e.setSummary(cursor.getString(0));
        e.setLocation(cursor.getString(1));
        e.setDescription(cursor.getString(2));

        boolean allDay = cursor.getInt(7) != 0;
        long tsStart = cursor.getLong(3), tsEnd = cursor.getLong(4);
        String duration = cursor.getString(18);

        String tzId = null;
        if (allDay) {
          e.setDtStart(tsStart, null);
          // provide only DTEND and not DURATION for all-day events
          if (tsEnd == 0) {
            Dur dur = new Dur(duration);
            java.util.Date dEnd = dur.getTime(new java.util.Date(tsStart));
            tsEnd = dEnd.getTime();
          }
          e.setDtEnd(tsEnd, null);

        } else {
          // use the start time zone for the end time, too
          // because apps like Samsung Planner allow the user to change "the" time zone but change
          // the start time zone only
          tzId = cursor.getString(5);
          e.setDtStart(tsStart, tzId);
          if (tsEnd != 0) e.setDtEnd(tsEnd, tzId);
          else if (!StringUtils.isEmpty(duration)) e.setDuration(new Duration(new Dur(duration)));
        }

        // recurrence
        try {
          String strRRule = cursor.getString(10);
          if (!StringUtils.isEmpty(strRRule)) e.setRrule(new RRule(strRRule));

          String strRDate = cursor.getString(11);
          if (!StringUtils.isEmpty(strRDate)) {
            RDate rDate = new RDate();
            rDate.setValue(strRDate);
            e.setRdate(rDate);
          }

          String strExRule = cursor.getString(12);
          if (!StringUtils.isEmpty(strExRule)) {
            ExRule exRule = new ExRule();
            exRule.setValue(strExRule);
            e.setExrule(exRule);
          }

          String strExDate = cursor.getString(13);
          if (!StringUtils.isEmpty(strExDate)) {
            // ignored, see https://code.google.com/p/android/issues/detail?id=21426
            ExDate exDate = new ExDate();
            exDate.setValue(strExDate);
            e.setExdate(exDate);
          }
        } catch (ParseException ex) {
          Log.w(TAG, "Couldn't parse recurrence rules, ignoring", ex);
        } catch (IllegalArgumentException ex) {
          Log.w(TAG, "Invalid recurrence rules, ignoring", ex);
        }

        // status
        switch (cursor.getInt(8)) {
          case Events.STATUS_CONFIRMED:
            e.setStatus(Status.VEVENT_CONFIRMED);
            break;
          case Events.STATUS_TENTATIVE:
            e.setStatus(Status.VEVENT_TENTATIVE);
            break;
          case Events.STATUS_CANCELED:
            e.setStatus(Status.VEVENT_CANCELLED);
        }

        // availability
        e.setOpaque(cursor.getInt(19) != Events.AVAILABILITY_FREE);

        // attendees
        if (cursor.getInt(14) != 0) { // has attendees
          try {
            e.setOrganizer(new Organizer(new URI("mailto", cursor.getString(15), null)));
          } catch (URISyntaxException ex) {
            Log.e(TAG, "Error when creating ORGANIZER URI, ignoring", ex);
          }
          populateAttendees(e);
        }

        // classification
        switch (cursor.getInt(9)) {
          case Events.ACCESS_CONFIDENTIAL:
          case Events.ACCESS_PRIVATE:
            e.setForPublic(false);
            break;
          case Events.ACCESS_PUBLIC:
            e.setForPublic(true);
        }

        populateReminders(e);
      } else throw new RecordNotFoundException();
    } catch (RemoteException ex) {
      throw new LocalStorageException(ex);
    }
  }
  /**
   * Returns the latest applicable onset of this observance for the specified date.
   *
   * @param date the latest date that an observance onset may occur
   * @return the latest applicable observance date or null if there is no applicable observance
   *     onset for the specified date
   */
  public final Date getLatestOnset(final Date date) {

    if (initialOnset == null) {
      try {
        initialOnset =
            applyOffsetFrom(calculateOnset(((DtStart) getProperty(Property.DTSTART)).getDate()));
      } catch (ParseException e) {
        Log log = LogFactory.getLog(Observance.class);
        log.error("Unexpected error calculating initial onset", e);
        // XXX: is this correct?
        return null;
      }
    }

    // observance not applicable if date is before the effective date of this observance..
    if (date.before(initialOnset)) {
      return null;
    }

    if ((onsetsMillisec != null) && (onsetLimit == null || date.before(onsetLimit))) {
      return getCachedOnset(date);
    }

    Date onset = initialOnset;
    Date initialOnsetUTC;
    // get first onset without adding TZFROM as this may lead to a day boundary
    // change which would be incompatible with BYDAY RRULES
    // we will have to add the offset to all cacheable onsets
    try {
      initialOnsetUTC = calculateOnset(((DtStart) getProperty(Property.DTSTART)).getDate());
    } catch (ParseException e) {
      Log log = LogFactory.getLog(Observance.class);
      log.error("Unexpected error calculating initial onset", e);
      // XXX: is this correct?
      return null;
    }
    // collect all onsets for the purposes of caching..
    final DateList cacheableOnsets = new DateList();
    cacheableOnsets.setUtc(true);
    cacheableOnsets.add(initialOnset);

    // check rdates for latest applicable onset..
    final PropertyList rdates = getProperties(Property.RDATE);
    for (final Iterator i = rdates.iterator(); i.hasNext(); ) {
      final RDate rdate = (RDate) i.next();
      for (final Iterator j = rdate.getDates().iterator(); j.hasNext(); ) {
        try {
          final DateTime rdateOnset = applyOffsetFrom(calculateOnset((Date) j.next()));
          if (!rdateOnset.after(date) && rdateOnset.after(onset)) {
            onset = rdateOnset;
          }
          /*
           * else if (rdateOnset.after(date) && rdateOnset.after(onset) && (nextOnset == null ||
           * rdateOnset.before(nextOnset))) { nextOnset = rdateOnset; }
           */
          cacheableOnsets.add(rdateOnset);
        } catch (ParseException e) {
          Log log = LogFactory.getLog(Observance.class);
          log.error("Unexpected error calculating onset", e);
        }
      }
    }

    // check recurrence rules for latest applicable onset..
    final PropertyList rrules = getProperties(Property.RRULE);
    for (final Iterator i = rrules.iterator(); i.hasNext(); ) {
      final RRule rrule = (RRule) i.next();
      // include future onsets to determine onset period..
      final Calendar cal = Dates.getCalendarInstance(date);
      cal.setTime(date);
      cal.add(Calendar.YEAR, 10);
      onsetLimit = Dates.getInstance(cal.getTime(), Value.DATE_TIME);
      final DateList recurrenceDates =
          rrule.getRecur().getDates(initialOnsetUTC, onsetLimit, Value.DATE_TIME);
      for (final Iterator j = recurrenceDates.iterator(); j.hasNext(); ) {
        final DateTime rruleOnset = applyOffsetFrom((DateTime) j.next());
        if (!rruleOnset.after(date) && rruleOnset.after(onset)) {
          onset = rruleOnset;
        }
        /*
         * else if (rruleOnset.after(date) && rruleOnset.after(onset) && (nextOnset == null ||
         * rruleOnset.before(nextOnset))) { nextOnset = rruleOnset; }
         */
        cacheableOnsets.add(rruleOnset);
      }
    }

    // cache onsets..
    Collections.sort(cacheableOnsets);
    DateTime cacheableOnset = null;
    this.onsetsMillisec = new long[cacheableOnsets.size()];
    this.onsetsDates = new DateTime[onsetsMillisec.length];

    for (int i = 0; i < onsetsMillisec.length; i++) {
      cacheableOnset = (DateTime) cacheableOnsets.get(i);
      onsetsMillisec[i] = cacheableOnset.getTime();
      onsetsDates[i] = cacheableOnset;
    }

    return onset;
  }