From ecb7897d692bd0cfbf1f04d8bc70e74f0e3a28c5 Mon Sep 17 00:00:00 2001 From: Kevin Gurney Date: Tue, 29 Aug 2023 20:22:49 -0400 Subject: [PATCH] GH-37367: [MATLAB] Add `arrow.array.Date32Array` class (#37445) ### Rationale for this change Now that `arrow.type.Date32Type` class has been added to the MATLAB Interface (#37348), we can add the `arrow.array.Date32Array` class. `Date32Array`s can be created from MATLAB [`datetime`](https://www.mathworks.com/help/matlab/ref/datetime.html) values. ### What changes are included in this PR? 1. Added a new `arrow.array.Date32Array` class. 2. Added a new `arrow.type.traits.Date32Traits` class. 3. Added `arrow.type.Date32Type` support to `arrow.type.traits.traits` function. 4. Fixed typo `arrray` in `tTime32Array` test class. 5. Fixed bug in `numeric_array.h` where the `CType` rather than the `ArrowType` was being used to determine the `DataType` of an array class that is a `NumericArray`. `Date32Array`s can be created from MATLAB [`datetime`](https://www.mathworks.com/help/matlab/ref/datetime.html) values using the `fromMATLAB` method. `Date32Array`s can be converted to MATLAB `datetime` values using the `toMATLAB` method. **Example** ```matlab >> dates = [datetime(2021, 1, 2, 3, 4, 5), datetime(2023, 1, 1), datetime(1989, 2, 3, 10, 10, 10)]' dates = 3x1 datetime array 02-Jan-2021 03:04:05 01-Jan-2023 00:00:00 03-Feb-1989 10:10:10 >> array = arrow.array.Date32Array.fromMATLAB(dates) array = [ 2021-01-02, 2023-01-01, 1989-02-03 ] >> array.toMATLAB() ans = 3x1 datetime array 02-Jan-2021 01-Jan-2023 03-Feb-1989 ``` ### Are these changes tested? Yes. 1. Added a new `tDate32Array` test class. 2. Added `Date32` related test to `ttraits.m`. 6. Added a new `tDate32Traits.m` test class. ### Are there any user-facing changes? Yes. 1. Users can now create `arrow.array.Date32Array`s from MATLAB `datetime`s. ### Future Directions 1. #37230 2. Add `arrow.array.Date64Array`. 3. Add a way to extract the raw `int32` values from an `arrow.array.Date32Array` without converting to a MATLAB `datetime` using `toMATLAB`. ### Notes 1. Thank you @ sgilmore10 for your help with this pull request! * Closes: #37367 Lead-authored-by: Kevin Gurney Co-authored-by: Sarah Gilmore Signed-off-by: Sutou Kouhei --- .../arrow/matlab/array/proxy/numeric_array.h | 2 +- .../src/cpp/arrow/matlab/array/proxy/wrap.cc | 2 + matlab/src/cpp/arrow/matlab/proxy/factory.cc | 1 + .../src/cpp/arrow/matlab/type/proxy/traits.h | 6 + .../src/cpp/arrow/matlab/type/proxy/wrap.cc | 3 + matlab/src/matlab/+arrow/+array/Date32Array.m | 92 ++++++ .../+tabular/createAllSupportedArrayTypes.m | 11 +- .../+arrow/+type/+traits/Date32Traits.m | 30 ++ .../src/matlab/+arrow/+type/+traits/traits.m | 2 + matlab/test/arrow/array/tDate32Array.m | 295 ++++++++++++++++++ matlab/test/arrow/array/tTime32Array.m | 8 +- matlab/test/arrow/type/traits/tDate32Traits.m | 33 ++ matlab/test/arrow/type/traits/ttraits.m | 14 +- 13 files changed, 492 insertions(+), 7 deletions(-) create mode 100644 matlab/src/matlab/+arrow/+array/Date32Array.m create mode 100644 matlab/src/matlab/+arrow/+type/+traits/Date32Traits.m create mode 100644 matlab/test/arrow/array/tDate32Array.m create mode 100644 matlab/test/arrow/type/traits/tDate32Traits.m diff --git a/matlab/src/cpp/arrow/matlab/array/proxy/numeric_array.h b/matlab/src/cpp/arrow/matlab/array/proxy/numeric_array.h index 9c81d5ff6218e..f9da38dbaa062 100644 --- a/matlab/src/cpp/arrow/matlab/array/proxy/numeric_array.h +++ b/matlab/src/cpp/arrow/matlab/array/proxy/numeric_array.h @@ -56,7 +56,7 @@ class NumericArray : public arrow::matlab::array::proxy::Array { auto data_buffer = std::make_shared(numeric_mda); - const auto data_type = arrow::CTypeTraits::type_singleton(); + const auto data_type = arrow::TypeTraits::type_singleton(); const auto length = static_cast(numeric_mda.getNumberOfElements()); // cast size_t to int64_t // Pack the validity bitmap values. diff --git a/matlab/src/cpp/arrow/matlab/array/proxy/wrap.cc b/matlab/src/cpp/arrow/matlab/array/proxy/wrap.cc index 3d1881a7dfb58..104eecbb4f266 100644 --- a/matlab/src/cpp/arrow/matlab/array/proxy/wrap.cc +++ b/matlab/src/cpp/arrow/matlab/array/proxy/wrap.cc @@ -55,6 +55,8 @@ namespace arrow::matlab::array::proxy { return std::make_shared>(std::static_pointer_cast(array)); case ID::TIME64: return std::make_shared>(std::static_pointer_cast(array)); + case ID::DATE32: + return std::make_shared>(std::static_pointer_cast(array)); case ID::STRING: return std::make_shared(std::static_pointer_cast(array)); default: diff --git a/matlab/src/cpp/arrow/matlab/proxy/factory.cc b/matlab/src/cpp/arrow/matlab/proxy/factory.cc index 7a84810c5a93c..0ad8d52cd53a6 100644 --- a/matlab/src/cpp/arrow/matlab/proxy/factory.cc +++ b/matlab/src/cpp/arrow/matlab/proxy/factory.cc @@ -54,6 +54,7 @@ libmexclass::proxy::MakeResult Factory::make_proxy(const ClassName& class_name, REGISTER_PROXY(arrow.array.proxy.TimestampArray, arrow::matlab::array::proxy::NumericArray); REGISTER_PROXY(arrow.array.proxy.Time32Array , arrow::matlab::array::proxy::NumericArray); REGISTER_PROXY(arrow.array.proxy.Time64Array , arrow::matlab::array::proxy::NumericArray); + REGISTER_PROXY(arrow.array.proxy.Date32Array , arrow::matlab::array::proxy::NumericArray); REGISTER_PROXY(arrow.tabular.proxy.RecordBatch , arrow::matlab::tabular::proxy::RecordBatch); REGISTER_PROXY(arrow.tabular.proxy.Schema , arrow::matlab::tabular::proxy::Schema); REGISTER_PROXY(arrow.type.proxy.Field , arrow::matlab::type::proxy::Field); diff --git a/matlab/src/cpp/arrow/matlab/type/proxy/traits.h b/matlab/src/cpp/arrow/matlab/type/proxy/traits.h index fdae911062b28..a4c5d06f8ff22 100644 --- a/matlab/src/cpp/arrow/matlab/type/proxy/traits.h +++ b/matlab/src/cpp/arrow/matlab/type/proxy/traits.h @@ -23,6 +23,7 @@ #include "arrow/matlab/type/proxy/timestamp_type.h" #include "arrow/matlab/type/proxy/time32_type.h" #include "arrow/matlab/type/proxy/time64_type.h" +#include "arrow/matlab/type/proxy/date32_type.h" #include "arrow/matlab/type/proxy/string_type.h" namespace arrow::matlab::type::proxy { @@ -99,4 +100,9 @@ namespace arrow::matlab::type::proxy { struct Traits { using TypeProxy = Time64Type; }; + + template <> + struct Traits { + using TypeProxy = Date32Type; + }; } diff --git a/matlab/src/cpp/arrow/matlab/type/proxy/wrap.cc b/matlab/src/cpp/arrow/matlab/type/proxy/wrap.cc index efb510e89751d..7dfdf58e1dd21 100644 --- a/matlab/src/cpp/arrow/matlab/type/proxy/wrap.cc +++ b/matlab/src/cpp/arrow/matlab/type/proxy/wrap.cc @@ -21,6 +21,7 @@ #include "arrow/matlab/type/proxy/timestamp_type.h" #include "arrow/matlab/type/proxy/time32_type.h" #include "arrow/matlab/type/proxy/time64_type.h" +#include "arrow/matlab/type/proxy/date32_type.h" #include "arrow/matlab/type/proxy/string_type.h" namespace arrow::matlab::type::proxy { @@ -56,6 +57,8 @@ namespace arrow::matlab::type::proxy { return std::make_shared(std::static_pointer_cast(type)); case ID::TIME64: return std::make_shared(std::static_pointer_cast(type)); + case ID::DATE32: + return std::make_shared(std::static_pointer_cast(type)); case ID::STRING: return std::make_shared(std::static_pointer_cast(type)); default: diff --git a/matlab/src/matlab/+arrow/+array/Date32Array.m b/matlab/src/matlab/+arrow/+array/Date32Array.m new file mode 100644 index 0000000000000..a462bd4f85ac1 --- /dev/null +++ b/matlab/src/matlab/+arrow/+array/Date32Array.m @@ -0,0 +1,92 @@ +% arrow.array.Date32Array + +% Licensed to the Apache Software Foundation (ASF) under one or more +% contributor license agreements. See the NOTICE file distributed with +% this work for additional information regarding copyright ownership. +% The ASF licenses this file to you under the Apache License, Version +% 2.0 (the "License"); you may not use this file except in compliance +% with the License. You may obtain a copy of the License at +% +% http://www.apache.org/licenses/LICENSE-2.0 +% +% Unless required by applicable law or agreed to in writing, software +% distributed under the License is distributed on an "AS IS" BASIS, +% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +% implied. See the License for the specific language governing +% permissions and limitations under the License. + +classdef Date32Array < arrow.array.Array + + properties(Access=private) + NullSubstitutionValue = NaT + end + + methods + + function obj = Date32Array(proxy) + arguments + proxy(1, 1) libmexclass.proxy.Proxy {validate(proxy, "arrow.array.proxy.Date32Array")} + end + import arrow.internal.proxy.validate + obj@arrow.array.Array(proxy); + end + + function dates = toMATLAB(obj) + import arrow.type.DateUnit + + matlabArray = obj.Proxy.toMATLAB(); + % UNIX Epoch (January 1st, 1970). + unixEpoch = datetime(0, ConvertFrom="posixtime"); + % A Date32 value encodes a certain number of whole days + % before or after the UNIX Epoch. + dates = unixEpoch + days(matlabArray); + dates(~obj.Valid) = obj.NullSubstitutionValue; + end + + function dates = datetime(obj) + dates = obj.toMATLAB(); + end + + end + + methods(Static) + + function array = fromMATLAB(data, opts) + arguments + data + opts.InferNulls(1, 1) logical = true + opts.Valid + end + + import arrow.array.Date32Array + + arrow.internal.validate.type(data, "datetime"); + arrow.internal.validate.shape(data); + + validElements = arrow.internal.validate.parseValidElements(data, opts); + + % If the input MATLAB datetime array is zoned (i.e. has a TimeZone), + % then the datetime representing the UNIX Epoch must also have a TimeZone. + if ~isempty(data.TimeZone) + unixEpoch = datetime(0, ConvertFrom="posixtime", TimeZone="UTC"); + else + unixEpoch = datetime(0, ConvertFrom="posixtime"); + end + + % Explicitly round down (i.e. floor) to the nearest whole number + % of days because durations and datetimes are not guaranteed + % to encode "whole" number dates / times (e.g. 1.5 days is a valid duration) + % and the int32 function rounds to the nearest whole number. + % Rounding to the nearest whole number without flooring first would result in a + % "round up error" of 1 whole day in cases where the fractional part of + % the duration is large enough to result in rounding up (e.g. 1.5 days would + % become 2 days). + numDays = int32(floor(days(data - unixEpoch))); + args = struct(MatlabArray=numDays, Valid=validElements); + proxy = arrow.internal.proxy.create("arrow.array.proxy.Date32Array", args); + array = Date32Array(proxy); + end + + end + +end diff --git a/matlab/src/matlab/+arrow/+internal/+test/+tabular/createAllSupportedArrayTypes.m b/matlab/src/matlab/+arrow/+internal/+test/+tabular/createAllSupportedArrayTypes.m index 15ee0589d56e4..6eb930271b467 100644 --- a/matlab/src/matlab/+arrow/+internal/+test/+tabular/createAllSupportedArrayTypes.m +++ b/matlab/src/matlab/+arrow/+internal/+test/+tabular/createAllSupportedArrayTypes.m @@ -32,6 +32,7 @@ matlabData = cell(numClasses, 1); timeClasses = getTimeArrayClasses(); + dateClasses = getDateArrayClasses(); numericArrayToMatlabTypeDict = getNumericArrayToMatlabDictionary(); for ii = 1:numel(classes) @@ -54,6 +55,10 @@ matlabData{ii} = randomDurations(opts.NumRows); cmd = compose("%s.fromMATLAB(matlabData{ii})", name); arrowArrays{ii} = eval(cmd); + elseif ismember(name, dateClasses) + matlabData{ii} = randomDatetimes(opts.NumRows); + cmd = compose("%s.fromMATLAB(matlabData{ii})", name); + arrowArrays{ii} = eval(cmd); else error("arrow:test:SupportedArrayCase", ... "Missing if-branch for array class " + name); @@ -86,6 +91,10 @@ timeClasses = compose("arrow.array.Time%dArray", [32 64]); end +function dateClasses = getDateArrayClasses() + dateClasses = compose("arrow.array.Date%dArray", 32); +end + function number = randomNumbers(numberType, numElements) number = cast(randi(255, [numElements 1]), numberType); end @@ -107,4 +116,4 @@ function dates = randomDatetimes(numElements) day = days(randi(255, [numElements 1])); dates = datetime(2023, 8, 23) + day; -end \ No newline at end of file +end diff --git a/matlab/src/matlab/+arrow/+type/+traits/Date32Traits.m b/matlab/src/matlab/+arrow/+type/+traits/Date32Traits.m new file mode 100644 index 0000000000000..867e81fb276d8 --- /dev/null +++ b/matlab/src/matlab/+arrow/+type/+traits/Date32Traits.m @@ -0,0 +1,30 @@ +% Licensed to the Apache Software Foundation (ASF) under one or more +% contributor license agreements. See the NOTICE file distributed with +% this work for additional information regarding copyright ownership. +% The ASF licenses this file to you under the Apache License, Version +% 2.0 (the "License"); you may not use this file except in compliance +% with the License. You may obtain a copy of the License at +% +% http://www.apache.org/licenses/LICENSE-2.0 +% +% Unless required by applicable law or agreed to in writing, software +% distributed under the License is distributed on an "AS IS" BASIS, +% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +% implied. See the License for the specific language governing +% permissions and limitations under the License. + +classdef Date32Traits < arrow.type.traits.TypeTraits + + properties (Constant) + ArrayConstructor = @arrow.array.Date32Array + ArrayClassName = "arrow.array.Date32Array" + ArrayProxyClassName = "arrow.array.proxy.Date32Array" + ArrayStaticConstructor = @arrow.array.Date32Array.fromMATLAB + TypeConstructor = @arrow.type.Date32Type; + TypeClassName = "arrow.type.Date32Type" + TypeProxyClassName = "arrow.type.proxy.Date32Type" + MatlabConstructor = @datetime + MatlabClassName = "datetime" + end + +end diff --git a/matlab/src/matlab/+arrow/+type/+traits/traits.m b/matlab/src/matlab/+arrow/+type/+traits/traits.m index e7c7f9b3272d1..b1f6193a3e7c1 100644 --- a/matlab/src/matlab/+arrow/+type/+traits/traits.m +++ b/matlab/src/matlab/+arrow/+type/+traits/traits.m @@ -52,6 +52,8 @@ typeTraits = Time32Traits(); case ID.Time64 typeTraits = Time64Traits(); + case ID.Date32 + typeTraits = Date32Traits(); otherwise error("arrow:type:traits:UnsupportedArrowTypeID", "Unsupported Arrow type ID: " + type); end diff --git a/matlab/test/arrow/array/tDate32Array.m b/matlab/test/arrow/array/tDate32Array.m new file mode 100644 index 0000000000000..a5fc1c1b64838 --- /dev/null +++ b/matlab/test/arrow/array/tDate32Array.m @@ -0,0 +1,295 @@ +%TDATE32ARRAY Unit tests for arrow.array.Date32Array + +% Licensed to the Apache Software Foundation (ASF) under one or more +% contributor license agreements. See the NOTICE file distributed with +% this work for additional information regarding copyright ownership. +% The ASF licenses this file to you under the Apache License, Version +% 2.0 (the "License"); you may not use this file except in compliance +% with the License. You may obtain a copy of the License at +% +% http://www.apache.org/licenses/LICENSE-2.0 +% +% Unless required by applicable law or agreed to in writing, software +% distributed under the License is distributed on an "AS IS" BASIS, +% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +% implied. See the License for the specific language governing +% permissions and limitations under the License. + +classdef tDate32Array < matlab.unittest.TestCase + + properties + ArrowArrayConstructorFcn = @arrow.array.Date32Array.fromMATLAB + end + + properties (Constant) + 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); + array = testCase.ArrowArrayConstructorFcn(dates); + testCase.verifyInstanceOf(array, "arrow.array.Date32Array"); + end + + function TestTypeIsDate32(testCase) + dates = testCase.UnixEpoch + days(1:10); + array = testCase.ArrowArrayConstructorFcn(dates); + testCase.verifyDate32Type(array.Type); + end + + function TestLength(testCase) + dates = datetime.empty(0, 1); + array = testCase.ArrowArrayConstructorFcn(dates); + testCase.verifyEqual(array.Length, int64(0)); + + dates = datetime(2023, 1, 1); + array = testCase.ArrowArrayConstructorFcn(dates); + testCase.verifyEqual(array.Length, int64(1)); + + 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); + 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); + 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; + array = testCase.ArrowArrayConstructorFcn(dates); + testCase.verifyEqual(array.Valid, [true; false; false; true; false]); + testCase.verifyEqual(toMATLAB(array), dates'); + testCase.verifyEqual(datetime(array), dates'); + end + + function TestInferNullsTrueNVPair(testCase) + % Verify arrow.array.Date32Array.fromMATLAB() behaves as + % expected when InferNulls=true is provided. + dates = testCase.MissingDates; + array = testCase.ArrowArrayConstructorFcn(dates, InferNulls=true); + expectedValid = [true; false; false; true; false]; + testCase.verifyEqual(array.Valid, expectedValid); + testCase.verifyEqual(toMATLAB(array), dates'); + testCase.verifyEqual(datetime(array), dates'); + end + + function TestInferNullsFalseNVPair(testCase) + % Verify arrow.array.Date32Array.fromMATLAB() behaves as + % expected when InferNulls=false is provided. + 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; + 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; + + % 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([4, 5]) = NaT; + testCase.verifyEqual(toMATLAB(array), expectedDates); + + % Supply the Valid name-value pair as a logical scalar. + array = testCase.ArrowArrayConstructorFcn(dates, Valid=false); + testCase.verifyEqual(array.Valid, [false; false; false; false; false]); + expectedDates(:) = NaT; + testCase.verifyEqual(toMATLAB(array), expectedDates); + end + + function TestEmptyDatetimeVector(testCase) + % Verify arrow.array.Date32Array.fromMATLAB() accepts any + % empty-shaped datetime as input. + + dates = datetime.empty(0, 0); + array = testCase.ArrowArrayConstructorFcn(dates); + testCase.verifyEqual(array.Length, int64(0)); + testCase.verifyEqual(array.Valid, logical.empty(0, 1)); + testCase.verifyEqual(toMATLAB(array), datetime.empty(0, 1)); + + % Test with an N-Dimensional empty array + dates = datetime.empty(0, 1, 0); + array = testCase.ArrowArrayConstructorFcn(dates); + testCase.verifyEqual(array.Length, int64(0)); + testCase.verifyEqual(array.Valid, logical.empty(0, 1)); + testCase.verifyEqual(toMATLAB(array), datetime.empty(0, 1)); + end + + function TestErrorIfNonVector(testCase) + % Verify arrow.array.Date32Array.fromMATLAB() throws an error + % if the input provided is not a vector. + + dates = datetime(2023, 1, 1) + days(1:12); + dates = reshape(dates, 2, 6); + fcn = @() testCase.ArrowArrayConstructorFcn(dates); + testCase.verifyError(fcn, "arrow:array:InvalidShape"); + + dates = reshape(dates, 3, 2, 2); + fcn = @() testCase.ArrowArrayConstructorFcn(dates); + testCase.verifyError(fcn, "arrow:array:InvalidShape"); + end + + function TestErrorIfNonDatetime(testCase) + % Verify arrow.array.Date32Array.fromMATLAB() throws an error + % if not given a datetime as input. + + dates = duration(1, 2, 3); + fcn = @() testCase.ArrowArrayConstructorFcn(dates); + testCase.verifyError(fcn, "arrow:array:InvalidType"); + + numbers = [1; 2; 3; 4]; + fcn = @() testCase.ArrowArrayConstructorFcn(numbers); + testCase.verifyError(fcn, "arrow:array:InvalidType"); + end + + function TestInt32MaxDays(testCase) + % Verify that no precision is lost when trying to round-trip a + % datetime value that is within abs(intmin("int32")) days before + % and intmax("int32") days after the UNIX epoch. + + % Cast to int64 before taking the absolute value to avoid loss + % of precision. + numDaysBefore = abs(int64(intmin("int32"))); + numDaysAfter = 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 TestGreaterThanInt32MaxDays(testCase) + % Verify that precision is lost when trying to round-trip a + % datetime that is more than abs(intmin("int32")) days before + % or more than intmax("int32") after the UNIX epoch. + + % Cast to int64 before taking the absolute value to avoid loss + % of precision. + numDaysBefore = abs(int64(intmin("int32"))) + 1; + numDaysAfter = int64(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 TestZonedDatetime(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 TestInt32MaxDaysZoned(testCase) + % Verify that zoned datetimes which are within abs(intmin("int32")) days + % before and intmax("int32") days after the UNIX epoch are round-tripped + % (not including the TimeZone). + + % Cast to int64 before taking the absolute value to avoid loss + % of precision. + numDaysBefore = abs(int64(intmin("int32"))); + numDaysAfter = 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 TestNonWholeDaysRoundDown(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 + + function verifyDate32Type(testCase, actual) + testCase.verifyInstanceOf(actual, "arrow.type.Date32Type"); + testCase.verifyEqual(actual.ID, arrow.type.ID.Date32); + testCase.verifyEqual(actual.DateUnit, arrow.type.DateUnit.Day); + end + + end + +end diff --git a/matlab/test/arrow/array/tTime32Array.m b/matlab/test/arrow/array/tTime32Array.m index d885cd80cc972..e874889e5879d 100644 --- a/matlab/test/arrow/array/tTime32Array.m +++ b/matlab/test/arrow/array/tTime32Array.m @@ -108,10 +108,10 @@ function TestDuration(testCase, Unit) function TestValid(testCase, Unit) % Verify the Valid property returns the expected logical vector. times = seconds([100 200 NaN 355 NaN 400]); - arrray = testCase.ArrowArrayConstructorFcn(times, TImeUnit=Unit); - testCase.verifyEqual(arrray.Valid, [true; true; false; true; false; true]); - testCase.verifyEqual(toMATLAB(arrray), times'); - testCase.verifyEqual(duration(arrray), times'); + array = testCase.ArrowArrayConstructorFcn(times, TimeUnit=Unit); + testCase.verifyEqual(array.Valid, [true; true; false; true; false; true]); + testCase.verifyEqual(toMATLAB(array), times'); + testCase.verifyEqual(duration(array), times'); end function InferNullsTrueNVPair(testCase, Unit) diff --git a/matlab/test/arrow/type/traits/tDate32Traits.m b/matlab/test/arrow/type/traits/tDate32Traits.m new file mode 100644 index 0000000000000..6f3c07c99d774 --- /dev/null +++ b/matlab/test/arrow/type/traits/tDate32Traits.m @@ -0,0 +1,33 @@ +%TDATE32TRAITS Unit tests for arrow.type.traits.Date32Traits + +% Licensed to the Apache Software Foundation (ASF) under one or more +% contributor license agreements. See the NOTICE file distributed with +% this work for additional information regarding copyright ownership. +% The ASF licenses this file to you under the Apache License, Version +% 2.0 (the "License"); you may not use this file except in compliance +% with the License. You may obtain a copy of the License at +% +% http://www.apache.org/licenses/LICENSE-2.0 +% +% Unless required by applicable law or agreed to in writing, software +% distributed under the License is distributed on an "AS IS" BASIS, +% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +% implied. See the License for the specific language governing +% permissions and limitations under the License. + +classdef tDate32Traits < hTypeTraits + + properties + TraitsConstructor = @arrow.type.traits.Date32Traits + ArrayConstructor = @arrow.array.Date32Array + ArrayClassName = "arrow.array.Date32Array" + ArrayProxyClassName = "arrow.array.proxy.Date32Array" + ArrayStaticConstructor = @arrow.array.Date32Array.fromMATLAB + TypeConstructor = @arrow.type.Date32Type + TypeClassName = "arrow.type.Date32Type" + TypeProxyClassName = "arrow.type.proxy.Date32Type" + MatlabConstructor = @datetime + MatlabClassName = "datetime" + end + +end diff --git a/matlab/test/arrow/type/traits/ttraits.m b/matlab/test/arrow/type/traits/ttraits.m index 771562152a5ef..70508a5e7cfbf 100644 --- a/matlab/test/arrow/type/traits/ttraits.m +++ b/matlab/test/arrow/type/traits/ttraits.m @@ -175,6 +175,18 @@ function TestTime64(testCase) testCase.verifyEqual(actualTraits, expectedTraits); end + function TestDate32(testCase) + import arrow.type.traits.* + import arrow.type.* + + type = ID.Date32; + expectedTraits = Date32Traits(); + + actualTraits = traits(type); + + testCase.verifyEqual(actualTraits, expectedTraits); + end + function TestMatlabUInt8(testCase) import arrow.type.traits.* @@ -352,4 +364,4 @@ function TestErrorIfUnsupportedInputType(testCase) end -end \ No newline at end of file +end