public void testTimeZoneHelperFromICalendarBerlinUntil() throws Exception {

    VTimezone vtz = new VTimezone();
    vtz.addProperty("TZID", "Berlino"); // No hint
    TzStandardComponent standardC = new TzStandardComponent();
    standardC.addProperty("TZOFFSETFROM", "+0200");
    standardC.addProperty("TZOFFSETTO", "+0100");
    standardC.addProperty("TZNAME", "CET");
    standardC.addProperty("DTSTART", "19701025T030000");
    standardC.addProperty(
        "RRULE", "FREQ=YEARLY;INTERVAL=1;BYDAY=-1SU;BYMONTH=10;UNTIL=20091231T000113Z");
    TzDaylightComponent dayLightC = new TzDaylightComponent();
    dayLightC.addProperty("TZOFFSETFROM", "+0100");
    dayLightC.addProperty("TZOFFSETTO", "+0200");
    dayLightC.addProperty("TZNAME", "CEST");
    dayLightC.addProperty("DTSTART", "19700329T020000");
    dayLightC.addProperty(
        "RRULE", "FREQ=YEARLY;INTERVAL=1;BYDAY=-1SU;BYMONTH=3;UNTIL=20091231T000113Z");
    vtz.addStandardc(standardC);
    vtz.addDaylightc(dayLightC);

    TimeZoneHelper vctz =
        new TimeZoneHelper(
            vtz,
            1167609600000L, // 01 Jan 2007 @ 00:00:00 UTC
            1324771200000L); // 25 Dec 2011 @ 00:00:00 UTC
    List<TimeZoneTransition> transitions = vctz.getTransitions();

    assertEquals("Berlino", vctz.getName());
    assertEquals(6, transitions.size());
    assertEquals(3600000, vctz.getBasicOffset());
    boolean dst = true;
    for (TimeZoneTransition transition : transitions) {
      if (dst) {
        assertEquals("CEST", transition.getName());
      } else {
        assertEquals("CET", transition.getName());
      }
      dst = !dst;
    }
    assertEquals(7200000, transitions.get(0).getOffset());
    assertEquals(1174784400000L, transitions.get(0).getTime());
    assertEquals(3600000, transitions.get(1).getOffset());
    assertEquals(1193533200000L, transitions.get(1).getTime());
    assertEquals(7200000, transitions.get(2).getOffset());
    assertEquals(1206838800000L, transitions.get(2).getTime());
    assertEquals(3600000, transitions.get(3).getOffset());
    assertEquals(1224982800000L, transitions.get(3).getTime());
    assertEquals(7200000, transitions.get(4).getOffset());
    assertEquals(1238288400000L, transitions.get(4).getTime());
    assertEquals(3600000, transitions.get(5).getOffset());
    assertEquals(1256432400000L, transitions.get(5).getTime());

    assertEquals("Europe/Berlin", vctz.toID()); // First run
    assertEquals("Europe/Berlin", vctz.toID()); // Second run (cached)
  }
  protected static VTimezone toVTimezone(
      List<ICalendarTimeZoneTransition> iCalendarTransitions, String id, int basicOffset) {

    VTimezone vtz = new VTimezone();
    vtz.addProperty("TZID", id);
    TzDaylightComponent summerTimeRDates = null;
    TzStandardComponent standardTimeRDates = null;
    String standardTimeOffset = formatOffset(basicOffset);

    // Visits all transitions in cronological order
    for (int i = 0; i < iCalendarTransitions.size(); ) {

      // If it's the last transition, or it's a transition that is not
      // part of the standard/day-light time series, the special case must
      // be separately treated
      if ((i == iCalendarTransitions.size() - 1)
          || (!areHalfYearFar(
              iCalendarTransitions.get(i).getTime(), iCalendarTransitions.get(i + 1).getTime()))) {

        // "Burns" components that may be present in the buffer
        if (summerTimeRDates != null) {
          vtz.addComponent(summerTimeRDates);
          vtz.addComponent(standardTimeRDates);
          summerTimeRDates = null;
          standardTimeRDates = null;
        }

        // Creates a new STANDARD component of the RDATE kind
        TzStandardComponent specialRDate = new TzStandardComponent();
        String specialCaseTime = iCalendarTransitions.get(i).getTimeISO1861();
        specialRDate.addProperty("DTSTART", specialCaseTime);
        specialRDate.addProperty("RDATE", specialCaseTime);
        specialRDate.addProperty("TZOFFSETFROM", standardTimeOffset);
        standardTimeOffset = // It needs be updated
            formatOffset(iCalendarTransitions.get(i).getOffset());
        specialRDate.addProperty("TZOFFSETTO", standardTimeOffset);
        specialRDate.addProperty("TZNAME", iCalendarTransitions.get(i).getName());
        vtz.addComponent(specialRDate); // Burns it
        i++; // Moves on to the next transition
        continue;
      }

      String lastOffset = standardTimeOffset;
      String summerTimeOffset = formatOffset(iCalendarTransitions.get(i).getOffset());
      standardTimeOffset = formatOffset(iCalendarTransitions.get(i + 1).getOffset());
      String summerTimeStart = iCalendarTransitions.get(i).getTimeISO1861();
      String standardTimeStart = iCalendarTransitions.get(i + 1).getTimeISO1861();
      ICalendarTimeZoneTransition summerTimeClusterStart = iCalendarTransitions.get(i);
      ICalendarTimeZoneTransition standardTimeClusterStart = iCalendarTransitions.get(i + 1);
      int j; // Summer-time starts, backward instance count
      int k; // Summer-time starts, forward instance count
      int l; // Summer-time ends, backward instance count
      int m; // Summer-time ends, forward instance count
      for (j = i + 2; j < iCalendarTransitions.size(); j += 2) {
        ICalendarTimeZoneTransition clusterMember = iCalendarTransitions.get(j);
        if (!summerTimeClusterStart.matchesRecurrence(clusterMember, true)) {
          break;
        }
      }
      for (k = i + 2; k < iCalendarTransitions.size(); k += 2) {
        ICalendarTimeZoneTransition clusterMember = iCalendarTransitions.get(k);
        if (!summerTimeClusterStart.matchesRecurrence(clusterMember, false)) {
          break;
        }
      }
      for (l = i + 3; l < iCalendarTransitions.size(); l += 2) {
        ICalendarTimeZoneTransition clusterMember = iCalendarTransitions.get(l);
        if (!standardTimeClusterStart.matchesRecurrence(clusterMember, true)) {
          break;
        }
      }
      for (m = i + 3; m < iCalendarTransitions.size(); m += 2) {
        ICalendarTimeZoneTransition clusterMember = iCalendarTransitions.get(m);
        if (!standardTimeClusterStart.matchesRecurrence(clusterMember, false)) {
          break;
        }
      }
      boolean backwardInstanceCountForStarts = true;
      boolean backwardInstanceCountForEnds = true;
      if (k > j) { // counting istances in the forward direction makes a
        // longer summer-time-start series
        j = k; // j is now the longest series of summer-time starts
        backwardInstanceCountForStarts = false;
      }
      if (m > l) { // counting istances in the forward direction makes a
        // longer summer-time-end series
        l = m; // l is now the longest series of summer-time ends
        backwardInstanceCountForEnds = false;
      }
      j -= 2; // Compensates for the end condition of the for cycle above
      l -= 2; // Compensates for the end condition of the for cycle above
      if (l > j + 1) {
        l = j + 1; // l is now the best acceptable end for a
        // combined start-end series
      } else {
        j = l - 1;
      }
      // At this point, l + 1 = j

      if (l > i + 1) { // more than one year: there's a recurrence
        if (summerTimeRDates != null) {
          vtz.addComponent(summerTimeRDates);
          vtz.addComponent(standardTimeRDates);
        }

        // Create a new DAYLIGHT component
        summerTimeRDates = new TzDaylightComponent();
        summerTimeRDates.addProperty("DTSTART", summerTimeStart);
        StringBuffer summerTimeRRule = new StringBuffer("FREQ=YEARLY;INTERVAL=1;BYDAY=");
        if (backwardInstanceCountForStarts) {
          summerTimeRRule.append("-1");
        } else {
          summerTimeRRule.append('+').append(summerTimeClusterStart.getInstance());
        }
        summerTimeRRule
            .append(getDayOfWeekAbbreviation(summerTimeClusterStart.getDayOfWeek()))
            .append(";BYMONTH=")
            .append(summerTimeClusterStart.getMonth() + 1); // Jan must be 1
        if (j < iCalendarTransitions.size() - 2) {
          summerTimeRRule.append(";UNTIL=").append(iCalendarTransitions.get(j).getTimeISO1861());
        }
        summerTimeRDates.addProperty("RRULE", summerTimeRRule.toString());
        summerTimeRDates.addProperty("TZOFFSETFROM", lastOffset);
        summerTimeRDates.addProperty("TZOFFSETTO", summerTimeOffset);
        summerTimeRDates.addProperty("TZNAME", iCalendarTransitions.get(i).getName());

        // Create a new STANDARD component
        standardTimeRDates = new TzStandardComponent();
        standardTimeRDates.addProperty("DTSTART", standardTimeStart);
        StringBuffer standardTimeRRule = new StringBuffer("FREQ=YEARLY;INTERVAL=1;BYDAY=");
        if (backwardInstanceCountForEnds) {
          standardTimeRRule.append("-1");
        } else {
          standardTimeRRule.append('+').append(standardTimeClusterStart.getInstance());
        }
        standardTimeRRule
            .append(getDayOfWeekAbbreviation(standardTimeClusterStart.getDayOfWeek()))
            .append(";BYMONTH=")
            .append(standardTimeClusterStart.getMonth() + 1); // Jan must be 1
        if (l < iCalendarTransitions.size() - 1) {
          standardTimeRRule.append(";UNTIL=").append(iCalendarTransitions.get(l).getTimeISO1861());
        }

        standardTimeRDates.addProperty("RRULE", standardTimeRRule.toString());
        standardTimeRDates.addProperty("TZOFFSETFROM", summerTimeOffset);
        standardTimeRDates.addProperty("TZOFFSETTO", standardTimeOffset);
        standardTimeRDates.addProperty("TZNAME", iCalendarTransitions.get(i + 1).getName());

        vtz.addComponent(summerTimeRDates);
        vtz.addComponent(standardTimeRDates);
        summerTimeRDates = null;
        standardTimeRDates = null;
        i = j; // Increases the counter to jump beyond the recurrence
      } else { // just one year: i, i+1 are transitions of the RDATE kind
        if (summerTimeRDates == null) {
          // Create a new DAYLIGHT component
          summerTimeRDates = new TzDaylightComponent();
          summerTimeRDates.addProperty("DTSTART", summerTimeStart);
          summerTimeRDates.addProperty("RDATE", summerTimeStart);
          summerTimeRDates.addProperty("TZOFFSETFROM", lastOffset);
          summerTimeRDates.addProperty("TZOFFSETTO", summerTimeOffset);
          summerTimeRDates.addProperty("TZNAME", iCalendarTransitions.get(i).getName());
          // Create a new STANDARD component
          standardTimeRDates = new TzStandardComponent();
          standardTimeRDates.addProperty("DTSTART", standardTimeStart);
          standardTimeRDates.addProperty("RDATE", standardTimeStart);
          standardTimeRDates.addProperty("TZOFFSETFROM", summerTimeOffset);
          standardTimeRDates.addProperty("TZOFFSETTO", standardTimeOffset);
          standardTimeRDates.addProperty("TZNAME", iCalendarTransitions.get(i + 1).getName());
        } else {
          Property rdate = summerTimeRDates.getProperty("RDATE");
          rdate.setValue(rdate.getValue() + ';' + summerTimeStart);
          summerTimeRDates.setProperty(rdate);
          rdate = standardTimeRDates.getProperty("RDATE");
          rdate.setValue(rdate.getValue() + ';' + standardTimeStart);
          standardTimeRDates.setProperty(rdate);
        }
      }
      i += 2;
    }
    if (summerTimeRDates != null) {
      vtz.addComponent(summerTimeRDates);
      vtz.addComponent(standardTimeRDates);
      summerTimeRDates = null;
      standardTimeRDates = null;
    }
    return vtz;
  }