[SPARK-29723][SQL] Get date and time parts of an interval as java classes

### What changes were proposed in this pull request?
I propose 2 new methods for `CalendarInterval`:
- `extractAsPeriod()` returns the date part of an interval as an instance of `java.time.Period`
- `extractAsDuration()` returns the time part of an interval as an instance of `java.time.Duration`

For example:
```scala
scala> import org.apache.spark.unsafe.types.CalendarInterval
scala> import java.time._
scala> val i = spark.sql("select interval 1 year 3 months 4 days 10 hours 30 seconds").collect()(0).getAs[CalendarInterval](0)
scala> LocalDate.of(2019, 11, 1).plus(i.period())
res8: java.time.LocalDate = 2021-02-05
scala> ZonedDateTime.parse("2019-11-01T12:13:14Z").plus(i.extractAsPeriod()).plus(i.extractAsDuration())
res9: java.time.ZonedDateTime = 2021-02-05T22:13:44Z
```

### Why are the changes needed?
Taking into account that `CalendarInterval` has been already partially exposed to users via the collect operation, and probably it will be fully exposed in the future, it could be convenient for users to get the date and time parts of intervals as java classes:
- to avoid unnecessary dependency from Spark's classes in user code
- to easily use external libraries that accept standard Java classes.

### Does this PR introduce any user-facing change?
No

### How was this patch tested?
By new test in `CalendarIntervalSuite`.

Closes #26368 from MaxGekk/interval-java-period-duration.

Authored-by: Maxim Gekk <max.gekk@gmail.com>
Signed-off-by: Dongjoon Hyun <dhyun@apple.com>
This commit is contained in:
Maxim Gekk 2019-11-04 11:07:54 -08:00 committed by Dongjoon Hyun
parent 326b789340
commit 441d4c953e
2 changed files with 28 additions and 0 deletions

View file

@ -19,6 +19,9 @@ package org.apache.spark.unsafe.types;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.Duration;
import java.time.Period;
import java.time.temporal.ChronoUnit;
import java.util.Objects;
/**
@ -112,4 +115,19 @@ public final class CalendarInterval implements Serializable {
sb.append(' ').append(value).append(' ').append(unit).append('s');
}
}
/**
* Extracts the date part of the interval.
* @return an instance of {@code java.time.Period} based on the months and days fields
* of the given interval, not null.
*/
public Period extractAsPeriod() { return Period.of(0, months, days); }
/**
* Extracts the time part of the interval.
* @return an instance of {@code java.time.Duration} based on the microseconds field
* of the given interval, not null.
* @throws ArithmeticException if a numeric overflow occurs
*/
public Duration extractAsDuration() { return Duration.of(microseconds, ChronoUnit.MICROS); }
}

View file

@ -19,6 +19,9 @@ package org.apache.spark.unsafe.types;
import org.junit.Test;
import java.time.Duration;
import java.time.Period;
import static org.junit.Assert.*;
import static org.apache.spark.unsafe.types.CalendarInterval.*;
@ -94,4 +97,11 @@ public class CalendarIntervalSuite {
input2 = new CalendarInterval(75, 150, 200 * MICROS_PER_HOUR);
assertEquals(new CalendarInterval(-85, -180, -281 * MICROS_PER_HOUR), input1.subtract(input2));
}
@Test
public void periodAndDurationTest() {
CalendarInterval interval = new CalendarInterval(120, -40, 123456);
assertEquals(Period.of(0, 120, -40), interval.extractAsPeriod());
assertEquals(Duration.ofNanos(123456000), interval.extractAsDuration());
}
}