codea
codea

Reputation: 1252

Add year to Java Calendar doesn't work

Please enlight me on this :

I'm simply trying to add 10 years to the current date then substract an expiration date from it to return the number of years:

public int getMaxYears() {
  int max = 0;
  Calendar ten_year_later = Calendar.getInstance();
  ten_year_later.setTime(new Date());
  ten_year_later.add(Calendar.YEAR, 10);
  Calendar expiration = Calendar.getInstance();
  expiration.setTime(expiration_date);
  max = (int) (ten_year_later.getTimeInMillis() - expiration.getTimeInMillis())/(365 * 24 * 60 * 60 * 1000);
  return max;
}

When I debug this, the calendar always stay at the current year.

Anyone ?

Upvotes: 8

Views: 23050

Answers (7)

Basil Bourque
Basil Bourque

Reputation: 338181

tl;dr

Use modern java.time classes. Can be done in a one-liner (not that I recommend that).

Period
.between(
    ( ( GregorianCalendar) myCalendarExpiration ).toZonedDateTime().toLocalDate() ,
    ZonedDateTime.now( ZoneId.of( "Asia/Kolkata" ) ).plusYears( 10 ).toLocalDate() 
)
.getYears()

java.time

The modern approach uses the java.time classes that supplanted the legacy date-time classes such as Calendar.

The usual concrete implementation of Calendar is GregorianCalendar. This is now replaced by ZonedDateTime. You can convert back and forth by calling new methods on the old classes.

ZonedDateTime zdtExpiration = ( ( GregorianCalendar) myCal ).toZonedDateTime() ;

Get current moment.

ZoneId z = ZoneId.of( "Africa/Tunis" ) ;
ZonedDateTime zdtNow  = ZonedDateTime.now( z ) ;

Add ten years.

ZonedDateTime zdtLater = zdtNow.plusYears( 10 ) ;

Calculate elapsed time in years between the dates of those two moments.

Period p = Period.between(
    zdtExpiration.toLocalDate() , 
    zdtLater.toLocalDate()
) ;

Interrogate for the number of full years.

int yearsElapsed = p.getYears() ;

About java.time

The java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date, Calendar, & SimpleDateFormat.

To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.

The Joda-Time project, now in maintenance mode, advises migration to the java.time classes.

You may exchange java.time objects directly with your database. Use a JDBC driver compliant with JDBC 4.2 or later. No need for strings, no need for java.sql.* classes.

Where to obtain the java.time classes?

The ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval, YearWeek, YearQuarter, and more.

Upvotes: 1

BalusC
BalusC

Reputation: 1108557

Your calculation of max is wrong. An int cannot hold a year in millis.

Rather replace it by

max = ten_year_later.get(Calendar.YEAR) - expiration.get(Calendar.YEAR);

Or better, use JodaTime:

DateTime tenYearsLater = new DateTime().plusYears(10);
DateTime expiration = new DateTime(expiration_date.getTime());
Period period = new Period(expiration, tenYearsLater);
return period.getYears();

Upvotes: 6

Anon
Anon

Reputation: 2083

I've noted in a comment that you have an incorrect calculation for number of millis in a year (nevermind the int/long issue).

Since you have two calendars, each of which can keep a year, why don't you write your code like this (not compiled, so may contain typos):

Calendar cal1 = Calendar.newInstance();   // this will use current time
cal1.add(Calendar.YEAR, 10);
Calendar cal2 = Calendar.newInstance();
cal2.setDate(expiration);
return cal1.get(Calendar.YEAR) - cal2.get(Calendar.YEAR);

Assuming that's what you really want ...

Upvotes: 1

jarnbjo
jarnbjo

Reputation: 34313

The number of milliseconds in a year is well outside the range of an int, so both the int cast of ten_year_later.getTimeInMillis() - expiration.getTimeInMillis() and the calculation 365 * 24 * 60 * 60 * 1000 will evaluate to incorrect values.

The ten_year_later should be correct. There is no need to invoke computeFields as R. Bemrose wrote.

Upvotes: 0

Bryan James
Bryan James

Reputation: 326

Here's a simple example of what should work.

Calendar cal = new GregorianCalendar();
cal.setTime(new Date());
cal.add(Calendar.YEAR, yearsToAdd);
Date retDate = cal.getTime();

Just remember to use a long to get the time in milliseconds!

Upvotes: 5

stacker
stacker

Reputation: 68942

You have a problem with int / long conversion: 365 * 24 * 60 * 60 * 1000 Which evaluates to 31536000000 and therefore exceeds Integer.MAX_VALUE 2147483647 This works:

public static void main(String[] args) {
          Calendar ten_year_later = Calendar.getInstance();
          System.out.println( ten_year_later.getTime() );
          ten_year_later.setTime(new Date()); 
          ten_year_later.add(Calendar.YEAR, 10);
          System.out.println( ten_year_later.getTime() );
          Calendar expiration = Calendar.getInstance(); 
          expiration.setTime(expiration.getTime()); 
          long max = (ten_year_later.getTimeInMillis() - expiration.getTimeInMillis())/(365 * 24 * 60 * 60 * 1000L); 
          System.out.println( "max " + max );
        } 

Upvotes: 12

Paul Tomblin
Paul Tomblin

Reputation: 182762

Calendar is lazy, so it might not recalculate all the other fields until you ask for them. That's thrown me off in the debugger before. What happens if you System.out.println(ten_year_later);?

Upvotes: 1

Related Questions