2

I am trying to calculate an offset time from a given date. I get the date as a String and parse it, and I have the offset in milliseconds. So for example

Date: 2015-08-18 00:00:00

Offset Time: 2678400000, which when applied to the date, it equals 2015-09-18 00:00:00, 31 days later.

My goal is to store each of the offsets (years/months/days/hours/minutes/seconds) in an array, so I can use it later. However when I run this calculation using the Calendar class, I'm getting extra hours for some reason when called with offsetConverter(2678400000)

Output: 0 years 0 months 31 days 19 hours 0 minutes 0 seconds 

Here is my code which I found and modified slightly from this link Best way to convert Milliseconds to number of years, months and days

    public static int[] offsetConverter(long offset) {
    int[] delay =  new int[6];
    //delay 0-3 = years/months/days/seconds

    Calendar c = Calendar.getInstance(); 
    c.setTimeInMillis(offset);
    delay[0] = c.get(Calendar.YEAR) - 1970;
    delay[1] = c.get(Calendar.MONTH);
    delay[2] = c.get(Calendar.DAY_OF_MONTH);
    delay[3] = c.get(Calendar.HOUR_OF_DAY);
    delay[4] = c.get(Calendar.MINUTE);
    delay[5] = c.get(Calendar.SECOND);

    for (int i = 0; i< delay.length; i++)
        System.out.print(delay[i] + " ");
    System.out.println();
    return delay;
}

If anyone sees what I'm doing wrong or has a simpler way to do this I would appreciate the help. Thanks!

Community
  • 1
  • 1
kpb6756
  • 241
  • 1
  • 4
  • 11
  • FYI, the troublesome old date-time classes such as [`java.util.Date`](https://docs.oracle.com/javase/9/docs/api/java/util/Date.html), [`java.util.Calendar`](https://docs.oracle.com/javase/9/docs/api/java/util/Calendar.html), and `java.text.SimpleDateFormat` are now [legacy](https://en.wikipedia.org/wiki/Legacy_system), supplanted by the [java.time](https://docs.oracle.com/javase/9/docs/api/java/time/package-summary.html) classes built into Java 8 & Java 9. See [*Tutorial* by Oracle](https://docs.oracle.com/javase/tutorial/datetime/TOC.html). – Basil Bourque Feb 25 '18 at 20:38

2 Answers2

2

Milliseconds from Date#getTime() arrive in UTC timezone, but you instantiate calendar with default timezone, which is your local. This adds extra hours to your result.

To solve this issue, create a Calendar instance using UTC timezone:

Calendar c = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
Alex Salauyou
  • 14,185
  • 5
  • 45
  • 67
0

tl;dr

LocalDateTime.parse( 
    "2015-08-18 00:00:00".replace( " " , "T" )   // Convert input string to comply with ISO 8601 standard format, replacing SPACE in the middle with a `T`.
).plus( 
    Duration.ofDays( 31 )      // If you meant ( 31 * 24-hours ). 
    // Period.ofDays( 31 )     // If you meant 31 calendar-days. 
    // Period.ofMonths( 1 )    // If you meant a calendar-month.
)                              // Returns a fresh `LocalDateTime` object, distinct from original, per Immutable Objects pattern.
.toString()                    // Generate a string in standard ISO 8601 format to textually represent this object’s value.

2015-09-18T00:00

Terms

The word “offset” here is a poor choice of terms. That word has a specific meaning in date-time handling: the amount of hours, minutes, and seconds a time zone is from UTC. See the Wikipedia entry for UTC offset.

You seem to be concerned instead with a span of time unattached to the timeline. In the java.time classes such spans are called a Duration if in the scale of hours-minutes-seconds, and called Period if scaled in years-months-days.

java.time

The modern approach uses the java.time classes that supplanted the troublesome old date-time classes such as Date/Calendar.

Duration

Convert your span of time from a count of milliseconds to a Duration object.

Duration d = Duration.ofMillis( 2_678_400_000L );

This happens to be 744 hours.

d.toString(): PT744H

Out of curiosity, I checked the number of days in 744 hours, if we define “days” to be chunks of 24-hours.

d.toDaysPart(): 31

So it seems you really intend a month or a period of 31 days. Either way, Java has classes for that.

If you really want: ( 31 * 24 hours ), then do use a Duration. But construct the Duration object with more self-documenting code.

Duration d = Duration.ofDays( 31 ) ;  // Exact same effect as `Duration.ofMillis( 2_678_400_000L )` but more clear as to your intention.

ISO 8601

Your input string almost complies with ISO 8601 standard for date-time formats. The java.time classes use the standard formats by default when parsing/generating strings.

Convert your string to comply. Replace the SPACE in the middle with a T.

String input = "2015-08-18 00:00:00".replace( " " , "T" ) ;

2015-08-18T00:00:00

LocalDateTime

Parse your input as a LocalDateTime as your input lacks an indicator of offset-from-UTC or time zone.

LocalDateTime ldt = LocalDateTime.parse( input ) ;

ldt.toString(): 2015-08-18T00:00

Add your Duration object to get a new fresh LocalDateTime. The java.time classes use immutable objects so the original object is left intact.

LocalDateTime thirtyOneTimes24HoursLaterLdt = ldt.plus( d ) ;  // Adding a span-of-time to our `LocalDateTime` object to get another `LocalDateTime` object.

thirtyOneTimes24HoursLaterLdt.toString(): 2015-09-18T00:00

Be aware that LocalDateTime does not represent an actual moment, a specific point on the timeline. Without the context of a time zone or offset-from-UTC, it is only a rough idea about potential moments along a range of about 26-27 hours.

ZonedDateTime

If you know for certain your input string was meant to represent a moment in a particular zone, apply a ZoneId to get a ZonedDateTime object (an actual moment).

ZoneId z = ZoneId.of( "Pacific/Auckland" ) ;
ZonedDateTime zdt = ldt.atZone( z ) ;  // Now we have determined an actual moment, a point on the timeline.

zdt.toString(): 2015-08-18T00:00+12:00[Pacific/Auckland]

You can add your Duration if you do not care about dates (whole days on the calendar).

ZonedDateTime thirtyOneTimes24HoursLaterZdt = zdt.plus( d ) ;

thirtyOneTimes24HoursLaterZdt.toString(): 2015-09-18T00:00+12:00[Pacific/Auckland]

If your business logic was really meant to be thirty-one days on the calendar, use a Period.

ZonedDateTime thirtyOneDaysLater = zdt.plus( Period.ofDays( 31 ) ) ;

thirtyOneDaysLater.toString(): 2015-09-18T00:00+12:00[Pacific/Auckland]

If your business logic is really intending a calendar month rather than a specific number of days, use a different Period. The class makes adjustments as needed to handle various months’ length (read the doc and follow that doc’s link).

ZonedDateTime monthLater = zdt.plus( Period.ofMonths( 1 ) ) ;

monthLater.toString(): 2015-09-18T00:00+12:00[Pacific/Auckland]


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.

Using a JDBC driver compliant with JDBC 4.2 or later, you may exchange java.time objects directly with your database. No need for strings nor 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