28

I am doing some date calculations in Java using milliseconds and noticing an issue with the following:

private static final int MILLIS_IN_SECOND = 1000;
    private static final int SECONDS_IN_MINUTE = 60;
    private static final int MINUTES_IN_HOUR = 60;
    private static final int HOURS_IN_DAY = 24;
    private static final int DAYS_IN_YEAR = 365; //I know this value is more like 365.24...
    private static final long MILLISECONDS_IN_YEAR = MILLIS_IN_SECOND * SECONDS_IN_MINUTE * MINUTES_IN_HOUR * HOURS_IN_DAY * DAYS_IN_YEAR;


System.out.println(MILLISECONDS_IN_YEAR);  //Returns 1471228928

I know that that 1 Year is roughly = 31,556,952,000 Milliseconds, so my multiplication is off somehow.

Can anyone point out what I am doing wrong? Should I be using a long?

Kevin Bowersox
  • 93,289
  • 19
  • 159
  • 189
  • 2
    Why not just try it? Or look up what the max value of a 32-bit signed integer is? – Dave Newton Feb 04 '12 at 15:18
  • 1
    @Dave Newton I have tried it and the code returns a value that I know is incorrect. An int works for milliseconds in year, however I am calculating the wrong value. – Kevin Bowersox Feb 04 '12 at 15:21
  • Presumably leap years are irrelevant... – Tony Hopkinson Feb 04 '12 at 16:01
  • @TonyHopkinson leap years are irrelevant. I am just using this code to make a random date between two years. I am using this date for testing purposes and I am not concerned about boundary conditions. – Kevin Bowersox Feb 05 '12 at 19:50
  • Because this page comes up in searches for "1 year in milliseconds", please note that the numeric value given for 1 year in milliseconds is incorrect. Correct value for a non-leap year is 31,536,000,000, as noted by Basil Bourque – hrabinowitz May 10 '18 at 16:34

9 Answers9

32

Should I be using a long?

Yes. The problem is that, since MILLIS_IN_SECOND and so on are all ints, when you multiply them you get an int. You're converting that int to a long, but only after the int multiplication has already resulted in the wrong answer.

To fix this, you can cast the first one to a long:

    private static final long MILLISECONDS_IN_YEAR =
        (long)MILLIS_IN_SECOND * SECONDS_IN_MINUTE * MINUTES_IN_HOUR
        * HOURS_IN_DAY * DAYS_IN_YEAR;
ruakh
  • 175,680
  • 26
  • 273
  • 307
  • 1
    Great explanation. So does the cast turn the ints to a long before multiplying them? – Kevin Bowersox Feb 04 '12 at 15:26
  • 1
    @kmb385 When you multiply something with something bigger your result will always be the bigger type. – kechap Feb 04 '12 at 15:35
  • 5
    @kmb385: Yes, exactly. Or, rather -- I suppose the cast *itself* just turns the *first* `int` into a `long`, but after that, Java will convert each `int` to a `long` before multiplying it by a `long`. – ruakh Feb 04 '12 at 15:39
25

If on android, I suggest:

android.text.format.DateUtils

DateUtils.SECOND_IN_MILLIS
DateUtils.MINUTE_IN_MILLIS
DateUtils.HOUR_IN_MILLIS
DateUtils.DAY_IN_MILLIS
DateUtils.WEEK_IN_MILLIS
DateUtils.YEAR_IN_MILLIS
Oded Breiner
  • 28,523
  • 10
  • 105
  • 71
12

While others have already pointed out arithmetic overflow, you can also try TimeUnit to solve the problem:

Calendar calendar = Calendar.getInstance();
calendar.set(Calendar.YEAR, year);
int daysInYear = calendar.getActualMaximum(Calendar.DAY_OF_YEAR);
System.out.println(TimeUnit.DAYS.toMillis(daysInYear));
Vitalii Fedorenko
  • 110,878
  • 29
  • 149
  • 111
8
private static final long MILLISECONDS_IN_YEAR = MILLIS_IN_SECOND * ...

All the operands on the right hand side are ints, so the multiplication is done with 32bit signed integers, which overflows. Cast the first one to long and you'll get the expected value.

private static final long MILLISECONDS_IN_YEAR = (long)MILLIS_IN_SECOND * ...
Mat
  • 202,337
  • 40
  • 393
  • 406
  • Or simply use `Long` for all the others rather than `int`, 16 extra bytes be damned! – Brian Roach Feb 04 '12 at 15:27
  • Thanks for the help. I didn't know that the int overflowed, I thought I would get a runtime error. – Kevin Bowersox Feb 04 '12 at 15:30
  • @BrianRoach: I dunno, I'd find it slightly annoying to have to cast `HOURS_IN_DAY` to `int` so I could pass it into an `int`-expecting function, just because someone had a misguided notion of keeping its type consistent with that of `MILLISECONDS_IN_YEAR`. – ruakh Feb 04 '12 at 15:42
8

You're overflowing the int type. In Java, the result of a primitive arithmethic operation over two ints is an int. The type of the operands decides this, not the type of the result variable. Try:

private static final int MILLIS_IN_SECOND = 1000;
private static final int SECONDS_IN_MINUTE = 60;
private static final int MINUTES_IN_HOUR = 60;
private static final int HOURS_IN_DAY = 24;
private static final int DAYS_IN_YEAR = 365; //I know this value is more like 365.24...
private static final long MILLISECONDS_IN_YEAR = (long) MILLIS_IN_SECOND * SECONDS_IN_MINUTE * MINUTES_IN_HOUR * HOURS_IN_DAY * DAYS_IN_YEAR;
millimoose
  • 39,073
  • 9
  • 82
  • 134
5

To fix this, you can put the letter L after the first one: e.g. 1000L

long MILLS_IN_YEAR = 1000L * 60 * 60 * 24 * 365; // Returns 31536000000
dramkler
  • 51
  • 1
  • 2
5

You need a long. Ints wrap around 2 billion.

user949300
  • 15,364
  • 7
  • 35
  • 66
3

tl;dr

The Answer by Ruakh is correct about your use of int vs long (integer overflow) being the cause of a totally wrong number, 1471228928. But furthermore, your Question raises issue of solar year versus calendar year.

I know that that 1 Year = 31556952000 Milliseconds

No, that would be the length of a solar year, not a calendar year. A calendar year is 31,536,000,000 milliseconds.

The modern java.time classes and ChronoUnit can calculate the calendar year number.

Year y = Year.now(                    // Determine the year of the current date (today).
    ZoneId.of( "America/Montreal" )   // Determining the year means determining the current date. And determining a date requires a time zone. For any given moment, the date varies around the globe by zone.
) ;                                   // Returns a `Year` object.


long millisInYear =
ChronoUnit.MILLIS.between( 
    y.atDay( 1 )                      // Get the first of the year. Returns a `LocalDate`. 
     .atStartOfDay(                   // Determine the first moment of the day. Not always 00:00:00 because of anomalies such as Daylight Saving Time (DST).
         ZoneId.of( "America/Montreal" )  
    )                                 // Returns a `ZonedDateTime` object.
    ,
    y.plusYears(1)                    // Move to the following year.
     .atDay( 1 )                      // Get the first of the following year. Returns a `LocalDate`. 
     .atStartOfDay(                   
         ZoneId.of( "America/Montreal" )  
    )                                 // Returns a `ZonedDateTime` object.
) ;

31536000000

31,556,952,000 = Solar year

Your source is using an approximation of the length of a solar year, about 365.2425 24-hour days. This is the amount of time it takes the earth to orbit the sun.

The math:

365.2425 * 24 * 60 * 60 * 1000 = 31,556,951,999.999996 ≈ 31,556,952,000 ms

See this calculator.

31,536,000,000 = Calendar year

In the Western calendar (Gregorian/ISO), we use years of an even 365 24-hour days, ignoring the fact that the earth's orbit around the sun takes an extra quarter day. We make up for the discrepancy by inserting an extra day every four years (roughly, years which are multiples of four with the exception of years divisible by 100 but not by 400), the Leap Day.

Considering a plain year of 365 days with 24-hour days and no anomalies to account for such as Daylight Saving Time (DST), a calendar year is 31,536,000,000 milliseconds long. Not 31,556,952,000 as you suggest in your Question.

31,536,000,000 = ( 365 * 24 * 60 * 60 * 1000 )

See this calculator.

A Leap Year with 366 days will be 31,622,400,000 milliseconds.

31,622,400,000 = ( 366 * 24 * 60 * 60 * 1000 )

java.time

The modern java.time classes supplant the old date-time classes bundled with the earliest versions of Java. Those old classes have proven to be confusing and troublesome.

ChronoUnit

Leap year and other anomalies might mean an unexpected number of milliseconds in a year. So you should let java.time do an actual calculation, if precision is important in your situation.

The ChronoUnit class can calculate elapsed time in a certain unit.

long millisInYear = ChronoUnit.MILLIS.between( start , stop );

We need to determine the exact moment of the start of the first day day of the year and of the following year. We do that by going through the LocalDate class, which represents a date-only value without a time-of-day and without a time zone.

LocalDate startLd = LocalDate.of ( 2015 , 1 , 1 );
LocalDate stopLd = startLd.plusYears ( 1 );

By assigning a time zone (ZoneId) we get ZonedDateTime objects for specific moments on the timeline.

ZoneId z = ZoneId.of ( "America/Montreal" );
ZonedDateTime start = startLd.atStartOfDay ( z );
ZonedDateTime stop = stopLd.atStartOfDay ( z );

Lastly, calculate the elapsed time in milliseconds.

long millisInYear = ChronoUnit.MILLIS.between ( start , stop );

start.toString(): 2015-01-01T00:00-05:00[America/Montreal]

stop.toString(): 2016-01-01T00:00-05:00[America/Montreal]

millisInYear: 31536000000


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.

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

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

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.

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
  • See my edits. I figured out your source is using solar year, not calendar year. – Basil Bourque May 11 '18 at 00:06
  • Thanks for the further elaboration it is very insightful. As mentioned, I was shoot for a "year" in general with disregard for the distinction between solar/calendar. – Kevin Bowersox May 11 '18 at 12:18
0

try this

    int MILLIS_IN_SECOND = 1000;
    int SECONDS_IN_MINUTE = 60;
    int MINUTES_IN_HOUR = 60;
    int HOURS_IN_DAY = 24;
    int DAYS_IN_YEAR = 365;

    long MILLISECONDS_IN_YEAR = (long) MILLIS_IN_SECOND * SECONDS_IN_MINUTE * MINUTES_IN_HOUR * HOURS_IN_DAY * DAYS_IN_YEAR;

    System.out.println(MILLISECONDS_IN_YEAR); // Returns 31536000000
Fahim Parkar
  • 30,974
  • 45
  • 160
  • 276