Q:

Calendar.add changes time zone

In the following code, range equals durationInDays only when range is less than 30. If it is equal to 30 or greater, durationInDays is always range - 1
Calendar c = Calendar.getInstance();
Date now = new Date();
c.setTime(now);

int range = 35;
c.add(Calendar.DATE, range);
Date then = c.getTime();

Duration duration = Duration.between(now.toInstant(), then.toInstant());
int durationInDays = (int)duration.toDays();

When debugging this example, the variables are set as follows:
this = {CalTest@871} 
c = {GregorianCalendar@876} "java.util.GregorianCalendar[time=1522449516301,areFieldsSet=true,areAllFieldsSet=true,lenient=true,zone=sun.util.calendar.ZoneInfo[id="Europe/Dublin",offset=0,dstSavings=3600000,useDaylight=true,transitions=228,lastRule=java.util.SimpleTimeZone[id=Europe/Dublin,offset=0,dstSavings=3600000,useDaylight=true,startYear=0,startMode=2,startMonth=2,startDay=-1,startDayOfWeek=1,startTime=3600000,startTimeMode=2,endMode=2,endMonth=9,endDay=-1,endDayOfWeek=1,endTime=3600000,endTimeMode=2]],firstDayOfWeek=2,minimalDaysInFirstWeek=4,ERA=1,YEAR=2018,MONTH=2,WEEK_OF_YEAR=13,WEEK_OF_MONTH=5,DAY_OF_MONTH=30,DAY_OF_YEAR=89,DAY_OF_WEEK=6,DAY_OF_WEEK_IN_MONTH=5,AM_PM=1,HOUR=11,HOUR_OF_DAY=23,MINUTE=38,SECOND=36,MILLISECOND=301,ZONE_OFFSET=0,DST_OFFSET=3600000]"
now = {Date@877} "Fri Feb 23 23:38:36 GMT 2018"
range = 35
then = {Date@878} "Fri Mar 30 23:38:36 IST 2018"
duration = {Duration@879} "PT839H"
durationInDays = 34

Why does then have an IST timezone? This difference is the causing duration to be a little less than 35 days, rounding to 34.

A:

First, then hasn’t got IST timezone. A Date hasn’t got any time zone. Date.toString chooses a time zone, usually the JVM’s time zone setting, for generating the string only.
Looking at how your two Date objects are rendered in the debugger, one might wonder that it would appear they are rendered in two different time zones. They are not. Both are in Europe/Dublin time zone. As you are probably aware, Ireland with most of the EU switches to summer time (DST) on the last Sunday in March. Therefore, your date in February is in standard time, which in Ireland coincides with GMT, and therefore your string is rendered with GMT as “time zone”. On March 30, summer time is in effect, so the time zone is rendered as IST for Irish Summer Time this time. Edit: the transistion to summer time also accounts for the missing hour compared to your expected duration of 35 days (840 hours).
Edit: Since you can use java.time, the modern Java date and time API, I suggest you go all in and forget about the old-fashioned Date and Calendar. The modern API is so much nicer to work with, and you won’t need all the conversions any longer:
    ZonedDateTime now = ZonedDateTime.now(ZoneId.of("Europe/Dublin"));
    int range = 35;
    ZonedDateTime then = now.plusDays(range);
    Duration duration = Duration.between(now, then);
    int durationInDays = (int) duration.toDays();

The result is still PT839H and hence 34 days.
To obtain a duration in days that agrees with the number of days we added:
    int durationInDays = (int) ChronoUnit.DAYS.between(now, then);
    System.out.println(durationInDays);

This prints
35

Duration is mostly for durations in hours, minutes and seconds. It does support days, but only days at 24 hours each, so gives the surprising result you saw when used across the summer time transition. ChronoUnit.DAYS on the other hand is exactly made for days. It sees that the two ZonedDateTime instances have the same time-of-day and therefore acknowledges a full 35 days between the two.

