Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Joda Time - Calculation of seconds between two dates throws an exception.

I am using following code to calculate difference in seconds between two dates:

long secondsBetween = (Seconds.secondsBetween(new LocalDate("1901-01-01"), new LocalDate()).getSeconds());

However I am getting the following exception:

08-08 18:21:27.345: E/AndroidRuntime(6972): java.lang.RuntimeException: Unable to start activity ComponentInfo{com.testbdr/com.testbdr.MainActivity}: java.lang.ArithmeticException: Value cannot fit in an int: 3584908800
08-08 18:21:27.345: E/AndroidRuntime(6972):     at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2189)
08-08 18:21:27.345: E/AndroidRuntime(6972):     at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2216)
08-08 18:21:27.345: E/AndroidRuntime(6972):     at android.app.ActivityThread.access$600(ActivityThread.java:149)
08-08 18:21:27.345: E/AndroidRuntime(6972):     at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1305)
08-08 18:21:27.345: E/AndroidRuntime(6972):     at android.os.Handler.dispatchMessage(Handler.java:99)
08-08 18:21:27.345: E/AndroidRuntime(6972):     at android.os.Looper.loop(Looper.java:153)
08-08 18:21:27.345: E/AndroidRuntime(6972):     at android.app.ActivityThread.main(ActivityThread.java:5000)
08-08 18:21:27.345: E/AndroidRuntime(6972):     at java.lang.reflect.Method.invokeNative(Native Method)
08-08 18:21:27.345: E/AndroidRuntime(6972):     at java.lang.reflect.Method.invoke(Method.java:511)
08-08 18:21:27.345: E/AndroidRuntime(6972):     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:821)
08-08 18:21:27.345: E/AndroidRuntime(6972):     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:584)
08-08 18:21:27.345: E/AndroidRuntime(6972):     at dalvik.system.NativeStart.main(Native Method)
08-08 18:21:27.345: E/AndroidRuntime(6972): Caused by: java.lang.ArithmeticException: Value cannot fit in an int: 3584908800
08-08 18:21:27.345: E/AndroidRuntime(6972):     at org.joda.time.field.FieldUtils.safeToInt(FieldUtils.java:206)
08-08 18:21:27.345: E/AndroidRuntime(6972):     at org.joda.time.field.BaseDurationField.getDifference(BaseDurationField.java:141)
08-08 18:21:27.345: E/AndroidRuntime(6972):     at org.joda.time.chrono.BaseChronology.get(BaseChronology.java:260)
08-08 18:21:27.345: E/AndroidRuntime(6972):     at org.joda.time.base.BaseSingleFieldPeriod.between(BaseSingleFieldPeriod.java:105)
08-08 18:21:27.345: E/AndroidRuntime(6972):     at org.joda.time.Seconds.secondsBetween(Seconds.java:124)
08-08 18:21:27.345: E/AndroidRuntime(6972):     at com.testbdr.MainActivity.onCreate(MainActivity.java:27)
08-08 18:21:27.345: E/AndroidRuntime(6972):     at android.app.Activity.performCreate(Activity.java:5020)
08-08 18:21:27.345: E/AndroidRuntime(6972):     at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1080)
08-08 18:21:27.345: E/AndroidRuntime(6972):     at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2153)
08-08 18:21:27.345: E/AndroidRuntime(6972):     ... 11 more
like image 702
User3 Avatar asked Jan 11 '23 01:01

User3


2 Answers

int

As the other answers correctly state, the problem is that you and Joda-Time are using an int to handle seconds. A 32-bit int can hold only about 68 years worth of seconds.

If you insist on using seconds to track centuries of time, you must use a 64-bit long rather than an 32-bit int.

By the way, the use in Unix of a 32-bit int to track time by seconds presents a real-world problem knows an the Year 2038 problem.

Seconds Not Used For Long Spans Of Time

As other suggest, using seconds to track such long spans of time is unusual. You may want to rethink that premise, if possible.

One alternative: the ISO 8601 standard offers a Durations format of PnYnMnDTnHnMnS for number of years, months, days, and so on. Joda-Time knows hows to parse and generate such strings (Period and Duration classes). While Joda-Time can only handle int numbers for seconds, it can handle larger number of seconds when presented as strings in this ISO 8601 format, as seen in code example below (PT3584908800S).

Milliseconds

Joda-Time internally tracks a count-from-epoch using milliseconds. Joda-Time offers methods to access those milliseconds as long values.

I normally advise againt working in milliseconds for date-time work. But in your case it makes sense, converting to seconds as needed.

Start of Day

To calculate milliseconds, we'll need to use DateTime rather than LocalDate.

Make a habit of calling the method withTimeAtStartOfDay to get the first moment of the day. This time is usually 00:00:00 but not always because of Daylight Saving Time or other anomalies.

Time Zone

Time zone is crucial even for LocalDate. The date (and first moment of day) is determined by the time zone. A new day dawns earlier in Paris than it does in Montréal.

If you omit the time zone, the JVM's current default time zone will be used. Generally better to be explicit and specify the desired time zone. I suspect for your purposes, using UTC make sense.

Duration

Joda-Time offers the Duration class to represent a span of time untied to the timeline (the history of the Universe).

Example Code

Example code using Joda-Time 2.4.

DateTime history = new DateTime( "1901-01-01", DateTimeZone.UTC ).withTimeAtStartOfDay();  // Technically, the call to withTimeAtStartOfDay is not necessary here as Joda-Time defaults to that for parsing a date-only string. But the call is a good habit and makes clear out intention.
DateTime today = new DateTime( DateTimeZone.UTC ).withTimeAtStartOfDay();

Duration duration = new Duration( history, today );
long millis = duration.getMillis(); // Use a long, not an int.
long seconds = ( millis / 1000L ); // Use a long, not an int. Maybe use BigDecimal or BigInteger if you want rounding.

Dump to console.

System.out.println( "history: " + history );
System.out.println( "today: " + today );
System.out.println( "duration: " + duration );
System.out.println( "millis: " + millis );
System.out.println( "seconds: " + seconds );

When run.

history: 1901-01-01T00:00:00.000Z
today: 2014-08-08T00:00:00.000Z
duration: PT3584908800S
millis: 3584908800000
seconds: 3584908800

When going the other direction, either:

  • Pass a long number of seconds to the static method Duration.standardSeconds.
  • Pass an ISO 8601 string to the constructor of Duration.
like image 130
Basil Bourque Avatar answered Jan 17 '23 16:01

Basil Bourque


JodaTime has found the difference, which is 3584908800 seconds. But, it is unable to convert the same to an int, because int cannot hold that big a value.

Do you really have a practical use case for that old date (1-1-1901) ?

Try rerunning the same with a different date, which gives smaller difference. Guess we cannot achieve this using the Seconds.secondsBetween() method.

Note: The date system of Java/Unix uses 1-1-1970 as starting point.

like image 36
vivek_ganesan Avatar answered Jan 17 '23 17:01

vivek_ganesan