public void testTimeZoneHelperToICalendarCanberra() {

    TimeZoneHelper vctz =
        new TimeZoneHelper(
            "Australia/Canberra",
            1167609600000L, // 01 Jan 2007 @ 00:00:00 UTC
            1349049600000L); // 01 Oct 2012 @ 00:00:00 UTC
    VTimezone vtz = vctz.getVTimezone();
    List<VComponent> dayLightComponents = vtz.getComponents("DAYLIGHT");
    List<VComponent> standardComponents = vtz.getComponents("STANDARD");

    assertEquals("Australia/Canberra", vtz.getProperty("TZID").getValue());
    assertEquals(1, dayLightComponents.size());
    TzDaylightComponent dayLight = (TzDaylightComponent) dayLightComponents.get(0);
    assertEquals("+1000", dayLight.getProperty("TZOFFSETFROM").getValue());
    assertEquals("+1100", dayLight.getProperty("TZOFFSETTO").getValue());
    assertEquals("20061029T020000", dayLight.getProperty("DTSTART").getValue());
    assertEquals(
        "FREQ=YEARLY;INTERVAL=1;BYDAY=-1SU;BYMONTH=10", dayLight.getProperty("RRULE").getValue());

    assertEquals(1, standardComponents.size());
    TzStandardComponent standard = (TzStandardComponent) standardComponents.get(0);
    assertEquals("+1100", standard.getProperty("TZOFFSETFROM").getValue());
    assertEquals("+1000", standard.getProperty("TZOFFSETTO").getValue());
    assertEquals("20070325T030000", standard.getProperty("DTSTART").getValue());
    assertEquals(
        "FREQ=YEARLY;INTERVAL=1;BYDAY=-1SU;BYMONTH=3", standard.getProperty("RRULE").getValue());
  }
  public void testTimeZoneHelperToICalendarBogota() {

    TimeZoneHelper vctz =
        new TimeZoneHelper(
            "America/Bogota",
            0L, // 01 Jan 1970 @ 00:00:00 UTC
            1324771200000L); // 25 Dec 2011 @ 00:00:00 UTC

    VTimezone vtz = vctz.getVTimezone();
    List<VComponent> dayLightComponents = vtz.getComponents("DAYLIGHT");
    List<VComponent> standardComponents = vtz.getComponents("STANDARD");

    assertEquals("America/Bogota", vtz.getProperty("TZID").getValue());
    assertEquals(0, dayLightComponents.size());
    assertEquals(2, standardComponents.size());
    TzStandardComponent standard = (TzStandardComponent) standardComponents.get(0);
    assertEquals("-0500", standard.getProperty("TZOFFSETFROM").getValue());
    assertEquals("-0400", standard.getProperty("TZOFFSETTO").getValue());
    assertEquals("19920503T000000", standard.getProperty("DTSTART").getValue());
    assertEquals("19920503T000000", standard.getProperty("RDATE").getValue());

    standard = (TzStandardComponent) standardComponents.get(1);
    assertEquals("-0400", standard.getProperty("TZOFFSETFROM").getValue());
    assertEquals("-0500", standard.getProperty("TZOFFSETTO").getValue());
    assertEquals("19930404T000000", standard.getProperty("DTSTART").getValue());
    assertEquals("19930404T000000", standard.getProperty("RDATE").getValue());
  }
  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)
  }
  @SuppressWarnings("unchecked")
  public void testTimeZoneHelperCheckAllOlsonIDsWithICalendar() throws Exception {

    int mistakes = 0; // counter for mistakes in time zone handling

    /*
     * Max number of mistakes allowed. This value should be lowered as soon
     * as improvements in the code that handles time zones reduce the
     * number of mistakes. In this way, this test can be used to certify
     * that the number of properly handled time zones is being constantly
     * raised.
     */
    final int MAX_MISTAKES = 125;

    System.out.println("================================= iCalendar (2.0)");
    for (String id : (Set<String>) DateTimeZone.getAvailableIDs()) {

      TimeZoneHelper vctzOut =
          new TimeZoneHelper(
              id,
              // 0L,              // 01 Jan 1970 @ 00:00:00 UTC
              915148800000L, // 01 Jan 1999 @ 00:00:00 UTC
              1324771200000L); // 25 Dec 2011 @ 00:00:00 UTC
      VTimezone vTimezone = vctzOut.getVTimezone();

      // Uncomment this to debug the iCalendar items referring to one
      // continent:
      /*
      String continent = "Australia"; // or whatever you want to watch
      if (id.startsWith(continent)) {
          System.out.println("\n\n" + vTimezone);
      }
      */

      vTimezone.getProperty("TZID").setValue(""); // The name should not be used as a hint for
      // this "test"

      TimeZoneHelper vctzIn =
          new TimeZoneHelper(
              vTimezone,
              915148800000L, // 01 Jan 1999 @ 00:00:00 UTC
              1324771200000L); // 25 Dec 2011 @ 00:00:00 UTC
      System.out.print('\n' + id + " --> " + vctzIn.toID());
      vctzIn.clearCachedID();
      String guess = vctzIn.toID(id);
      if (!id.equals(guess)) {
        System.out.print(" (WRONG!)");
        mistakes++;
      }
    }
    System.out.println("\n\n" + mistakes + " time zones were not properly handled.");
    assertTrue(mistakes <= MAX_MISTAKES);
    System.out.println();
  }
  /**
   * Creates a new instance of TimeZoneHelper on the basis of the information extracted from an
   * iCalendar (vCalendar 2.0) item.
   *
   * @param vTimeZone
   * @param from the start of the relevant time interval for the generation of transitions (an
   *     istant expressed as a long)
   * @param to the end of the relevant time interval for the generation of transitions (an istant
   *     expressed as a long)
   * @throws java.lang.Exception
   */
  public TimeZoneHelper(VTimezone vTimeZone, long from, long to) throws Exception {
    setFormattersToUTC();
    Property tzID = vTimeZone.getProperty("TZID");
    if (tzID != null) {
      this.name = tzID.getValue();

      // Try and skip the parsing by using just the TZID:
      String extracted = extractID(name);
      if (extracted != null) {
        cacheID(extracted);
        processID(extracted, from, to);
        return;
      }

      List<VComponent> standardTimeRules = vTimeZone.getComponents("STANDARD");
      List<VComponent> summerTimeRules = vTimeZone.getComponents("DAYLIGHT");

      String standardTimeOffset;
      if (standardTimeRules.isEmpty()) {
        if (summerTimeRules.isEmpty()) {
          throw new Exception("Empty VTIMEZONE");
        } else {
          standardTimeOffset = summerTimeRules.get(0).getProperty("TZOFFSETFROM").getValue();
        }
      } else {
        standardTimeOffset = standardTimeRules.get(0).getProperty("TZOFFSETTO").getValue();
      }
      basicOffset = parseOffset(standardTimeOffset);

      for (VComponent standardTimeRule : standardTimeRules) {
        addTransitions(standardTimeRule, from, to);
      }
      for (VComponent summerTimeRule : summerTimeRules) {
        addTransitions(summerTimeRule, from, to);
      }
      Collections.sort(transitions);

    } else {
      this.name = ""; // This should not happen!
    }
  }
  public void testTimeZoneHelperToICalendarJohannesburg() {

    TimeZoneHelper vctz =
        new TimeZoneHelper(
            "Africa/Johannesburg",
            0L, // 01 Jan 1970 @ 00:00:00 UTC
            1324771200000L); // 25 Dec 2011 @ 00:00:00 UTC

    VTimezone vtz = vctz.getVTimezone();
    List<VComponent> dayLightComponents = vtz.getComponents("DAYLIGHT");
    List<VComponent> standardComponents = vtz.getComponents("STANDARD");

    assertEquals("Africa/Johannesburg", vtz.getProperty("TZID").getValue());
    assertEquals(0, dayLightComponents.size());
    assertEquals(1, standardComponents.size());
    TzStandardComponent standard = (TzStandardComponent) standardComponents.get(0);
    assertEquals("+0200", standard.getProperty("TZOFFSETFROM").getValue());
    assertEquals("+0200", standard.getProperty("TZOFFSETTO").getValue());
    assertEquals("19700101T000000", standard.getProperty("DTSTART").getValue());
    assertEquals("19700101T000000", standard.getProperty("RDATE").getValue());
  }
  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;
  }