Skip to content

Commit

Permalink
1. Add support for Zoned datetimes to fromMATLAB.
Browse files Browse the repository at this point in the history
2. Add datetime precision and zoned datetime tests.
  • Loading branch information
kevingurney committed Aug 29, 2023
1 parent 01365d2 commit 33e56d6
Show file tree
Hide file tree
Showing 2 changed files with 120 additions and 14 deletions.
8 changes: 7 additions & 1 deletion matlab/src/matlab/+arrow/+array/Date32Array.m
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,14 @@
arrow.internal.validate.shape(data);

validElements = arrow.internal.validate.parseValidElements(data, opts);

% UNIX Epoch (January 1st, 1970)
unixEpoch = datetime(0, ConvertFrom="posixtime");
if ~isempty(data.TimeZone)
unixEpoch = datetime(0, ConvertFrom="posixtime", TimeZone="UTC");
else
unixEpoch = datetime(0, ConvertFrom="posixtime");
end

numDays = int32(floor(days(data - unixEpoch)));
args = struct(MatlabArray=numDays, Valid=validElements);
proxy = arrow.internal.proxy.create("arrow.array.proxy.Date32Array", args);
Expand Down
126 changes: 113 additions & 13 deletions matlab/test/arrow/array/tDate32Array.m
Original file line number Diff line number Diff line change
Expand Up @@ -22,20 +22,20 @@
end

properties (Constant)
unixEpoch = datetime(0, ConvertFrom="posixtime");
missingDates = [datetime(2023, 1, 1), NaT, NaT, datetime(2022, 1, 1), NaT];
UnixEpoch = datetime(0, ConvertFrom="posixtime");
MissingDates = [datetime(2023, 1, 1), NaT, NaT, datetime(2022, 1, 1), NaT];
end

methods (Test)

function TestBasic(testCase)
dates = testCase.unixEpoch + days(1:10);
dates = testCase.UnixEpoch + days(1:10);
array = testCase.ArrowArrayConstructorFcn(dates);
testCase.verifyInstanceOf(array, "arrow.array.Date32Array");
end

function TestTypeIsDate32(testCase)
dates = testCase.unixEpoch + days(1:10);
dates = testCase.UnixEpoch + days(1:10);
array = testCase.ArrowArrayConstructorFcn(dates);
testCase.verifyDate32Type(array.Type);
end
Expand All @@ -49,30 +49,30 @@ function TestLength(testCase)
array = testCase.ArrowArrayConstructorFcn(dates);
testCase.verifyEqual(array.Length, int64(1));

dates = testCase.unixEpoch + days(1:10);
dates = testCase.UnixEpoch + days(1:10);
array = testCase.ArrowArrayConstructorFcn(dates);
testCase.verifyEqual(array.Length, int64(10));
end

function TestToMATLAB(testCase)
% Verify toMATLAB() round-trips the original datetime array.
dates = testCase.unixEpoch + days(1:10);
dates = testCase.UnixEpoch + days(1:10);
array = testCase.ArrowArrayConstructorFcn(dates);
values = toMATLAB(array);
testCase.verifyEqual(values, dates');
end

function TestDatetime(testCase)
% Verify datetime() round-trips the original datetime array.
dates = testCase.unixEpoch + days(1:10);
dates = testCase.UnixEpoch + days(1:10);
array = testCase.ArrowArrayConstructorFcn(dates);
values = datetime(array);
testCase.verifyEqual(values, dates');
end

function TestValid(testCase)
% Verify the Valid property returns the expected logical vector.
dates = testCase.missingDates;
dates = testCase.MissingDates;
array = testCase.ArrowArrayConstructorFcn(dates);
testCase.verifyEqual(array.Valid, [true; false; false; true; false]);
testCase.verifyEqual(toMATLAB(array), dates');
Expand All @@ -82,7 +82,7 @@ function TestValid(testCase)
function InferNullsTrueNVPair(testCase)
% Verify arrow.array.Date32Array.fromMATLAB() behaves as
% expected when InferNulls=true is provided.
dates = testCase.missingDates;
dates = testCase.MissingDates;
array = testCase.ArrowArrayConstructorFcn(dates, InferNulls=true);
expectedValid = [true; false; false; true; false];
testCase.verifyEqual(array.Valid, expectedValid);
Expand All @@ -93,29 +93,29 @@ function InferNullsTrueNVPair(testCase)
function InferNullsFalseNVPair(testCase)
% Verify arrow.array.Date32Array.fromMATLAB() behaves as
% expected when InferNulls=false is provided.
dates = testCase.missingDates;
dates = testCase.MissingDates;
array = testCase.ArrowArrayConstructorFcn(dates, InferNulls=false);
expectedValid = [true; true; true; true; true];
testCase.verifyEqual(array.Valid, expectedValid);

% If NaT datetimes were not considered null values, then they
% are treated like int32(0) - i.e. the Unix epoch.
expectedDates = dates';
expectedDates([2, 3, 5]) = testCase.unixEpoch;
expectedDates([2, 3, 5]) = testCase.UnixEpoch;
testCase.verifyEqual(toMATLAB(array), expectedDates);
testCase.verifyEqual(datetime(array), expectedDates);
end

function TestValidNVPair(testCase)
% Verify arrow.array.Date32Array.fromMATLAB() accepts the Valid
% nv-pair, and it behaves as expected.
dates = testCase.missingDates;
dates = testCase.MissingDates;

% Supply the Valid name-value pair as vector of indices.
array = testCase.ArrowArrayConstructorFcn(dates, Valid=[1, 2, 3]);
testCase.verifyEqual(array.Valid, [true; true; true; false; false]);
expectedDates = dates';
expectedDates([2, 3]) = testCase.unixEpoch;
expectedDates([2, 3]) = testCase.UnixEpoch;
expectedDates([4, 5]) = NaT;
testCase.verifyEqual(toMATLAB(array), expectedDates);

Expand Down Expand Up @@ -171,6 +171,106 @@ function ErrorIfNonDatetime(testCase)
testCase.verifyError(fcn, "arrow:array:InvalidType");
end

function Int32MaxDays(testCase)
% Verify that no precision is lost when trying to round-trip a
% datetime value that is within intmax("int32") - 1 days before
% and intmax("int32") days after the UNIX epoch.
numDaysBefore = uint64(intmax("int32")) + 1;
numDaysAfter = uint64(intmax("int32"));

expectedBefore = testCase.UnixEpoch - days(numDaysBefore);
expectedAfter = testCase.UnixEpoch + days(numDaysAfter);

array = testCase.ArrowArrayConstructorFcn(expectedBefore);
actualBefore = array.toMATLAB();
testCase.verifyEqual(actualBefore, expectedBefore);

array = testCase.ArrowArrayConstructorFcn(expectedAfter);
actualAfter = array.toMATLAB();
testCase.verifyEqual(actualAfter, expectedAfter);
end

function GreaterThanInt32MaxDays(testCase)
% Verify that precision is lost when trying to round-trip a
% datetime that is greater than intmax("int32") + 1 days before
% or greater than intmax("int32") after after the UNIX epoch.
numDaysBefore = uint64(intmax("int32")) + 2;
numDaysAfter = uint64(intmax("int32")) + 1;

expectedBefore = testCase.UnixEpoch - days(numDaysBefore);
expectedAfter = testCase.UnixEpoch + days(numDaysAfter);

array = testCase.ArrowArrayConstructorFcn(expectedBefore);
actualBefore = array.toMATLAB();
testCase.verifyNotEqual(actualBefore, expectedBefore);

array = testCase.ArrowArrayConstructorFcn(expectedAfter);
actualAfter = array.toMATLAB();
testCase.verifyNotEqual(actualAfter, expectedAfter);
end

function ZonedDatetime(testCase)
% Verify that zoned datetimes are supported as inputs to the
% fromMATLAB method and that the output datetime returned by
% the toMATLAB method is unzoned.
expectedZoned = testCase.UnixEpoch + days(10);
expectedZoned.TimeZone = "America/New_York";
expected = expectedZoned;
expected.TimeZone = char.empty(0, 0);

array = testCase.ArrowArrayConstructorFcn(expectedZoned);
actual = array.toMATLAB();
testCase.verifyEqual(actual, expected);
end

function Int32MaxDaysZoned(testCase)
% Verify that zoned datetimes which are within intmax("int32") + 1 days
% before and intmax("int32") days after the UNIX epoch are round-tripped
% (not including the TimeZone).
numDaysBefore = uint64(intmax("int32")) + 1;
numDaysAfter = uint64(intmax("int32"));

expectedZonedBefore = testCase.UnixEpoch - days(numDaysBefore);
expectedZonedAfter = testCase.UnixEpoch + days(numDaysAfter);

expectedZonedBefore.TimeZone = "UTC";
expectedZonedAfter.TimeZone = "UTC";

expectedUnzonedBefore = expectedZonedBefore;
expectedUnzonedBefore.TimeZone = char.empty(0, 0);

expectedUnzonedAfter = expectedZonedAfter;
expectedUnzonedAfter.TimeZone = char.empty(0, 0);

array = testCase.ArrowArrayConstructorFcn(expectedZonedBefore);
actualBefore = array.toMATLAB();
testCase.verifyEqual(actualBefore, expectedUnzonedBefore);

array = testCase.ArrowArrayConstructorFcn(expectedUnzonedAfter);
actualAfter = array.toMATLAB();
testCase.verifyEqual(actualAfter, expectedUnzonedAfter);
end

function NonWholeDaysRoundDown(testCase)
% Verify that datetimes which are not whole days (i.e. are not datetimes
% with zero hours, zero minutes, and zero seconds), round down
% to the nearest whole day when round-tripping with
% Date32Array.
dates = testCase.UnixEpoch + days(10) + hours(20) + minutes(30) + seconds(40) + milliseconds(50);
% Round down to the nearest whole day when round-tripping.
expected = testCase.UnixEpoch + days(10);
array = testCase.ArrowArrayConstructorFcn(dates);
actual = array.toMATLAB();
testCase.verifyEqual(actual, expected);

dates = testCase.UnixEpoch + days(10) + hours(10) + minutes(20) + seconds(30) + milliseconds(20);
% Round down to the nearest whole day when round-tripping.
expected = testCase.UnixEpoch + days(10);
array = testCase.ArrowArrayConstructorFcn(dates);
actual = array.toMATLAB();
testCase.verifyEqual(actual, expected);
end

end

methods
Expand Down

0 comments on commit 33e56d6

Please sign in to comment.