Java Milliseconds in Year

前端 未结 9 1879
终归单人心
终归单人心 2020-12-09 08:24

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;
             


        
相关标签:
9条回答
  • 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?

    • Java SE 8, Java SE 9, Java SE 10, and later
      • Built-in.
      • Part of the standard Java API with a bundled implementation.
      • Java 9 adds some minor features and fixes.
    • Java SE 6 and Java SE 7
      • Much of the java.time functionality is back-ported to Java 6 & 7 in ThreeTen-Backport.
    • Android
      • Later versions of Android bundle implementations of the java.time classes.
      • For earlier Android (<26), the ThreeTenABP project adapts ThreeTen-Backport (mentioned above). See How to use ThreeTenABP….

    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.

    0 讨论(0)
  • 2020-12-09 08:46
    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 * ...
    
    0 讨论(0)
  • 2020-12-09 08:47

    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));
    
    0 讨论(0)
  • 2020-12-09 08:48

    You need a long. Ints wrap around 2 billion.

    0 讨论(0)
  • 2020-12-09 08:49

    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;
    
    0 讨论(0)
  • 2020-12-09 09:02

    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
    
    0 讨论(0)
提交回复
热议问题