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;
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
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.
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 )
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
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.
private static final long MILLISECONDS_IN_YEAR = MILLIS_IN_SECOND * ...
All the operands on the right hand side are int
s, 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 * ...
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));
You need a long. Ints wrap around 2 billion.
Should I be using a long?
Yes. The problem is that, since MILLIS_IN_SECOND
and so on are all int
s, 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;
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