This is an automated email from the ASF dual-hosted git repository.
kevingurney pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow.git
The following commit(s) were added to refs/heads/main by this push:
new c49e242731 GH-38420: [MATLAB] Implement a `DatetimeValidator` class
that validates a MATLAB `cell` array contains only values of zoned or unzoned
`datetime`s (#38533)
c49e242731 is described below
commit c49e24273160ac1ce195f02dbd14acd7d0f6945e
Author: sgilmore10 <[email protected]>
AuthorDate: Tue Oct 31 16:03:23 2023 -0400
GH-38420: [MATLAB] Implement a `DatetimeValidator` class that validates a
MATLAB `cell` array contains only values of zoned or unzoned `datetime`s
(#38533)
### Rationale for this change
This is a followup to #38419.
Adding this `DatetimeTypeValidator` class is a step towards implementing
the `arrow.array.ListArray.fromMATLAB()` method for creating `ListArray`s whose
`ValueType`s is a timestamp array from a MATLAB `cell` array.
This validator will ensure the cell array contain only `datetime`s or
unzoned `datetime`s. This is a requirement when creating a `List` of
`Timestamp`s because two MATLAB `datetime`s can only be concatenated together
if they are either both zoned or both unzoned:
```matlab
>> d1 = datetime(2023, 10, 31, TimeZone="America/New_York");
>> d2 =datetime(2023, 11, 1);
>> [d1; d2]
Error using datetime/vertcat
Unable to concatenate a datetime array that has a time zone with one that
does not have a time
zone.
```
### What changes are included in this PR?
Added a new MATLAB class called
`arrow.array.internal.list.DatetimeValidator`, which inherits from
`arrow.array.internal.list.ClassTypeValidator`.
This new class defines one property called `HasTimeZone`, which is a
scalar `logical` indicating if the validator expects all `datetime`s to be
zoned or not.
Additionally, `DatetimeValidator` overrides the `validateElement` method.
It first call's `ClassTypeValidator`'s implementation of `validateElement` to
verify the input element is a `datetime`. If so, it then confirms that the
input `datetime`'s TimeZone property is empty or nonempty, based on the
validator's `HasTimeZone` property value.
### Are these changes tested?
Yes, I added a new test class called `tDatetimeValidator.m`.
### Are there any user-facing changes?
No.
### Future Directions
1. #38417
2. #38354
* Closes: #38420
Authored-by: Sarah Gilmore <[email protected]>
Signed-off-by: Kevin Gurney <[email protected]>
---
.../+array/+internal/+list/DatetimeValidator.m | 49 ++++++
matlab/test/arrow/array/list/tDatetimeValidator.m | 181 +++++++++++++++++++++
2 files changed, 230 insertions(+)
diff --git
a/matlab/src/matlab/+arrow/+array/+internal/+list/DatetimeValidator.m
b/matlab/src/matlab/+arrow/+array/+internal/+list/DatetimeValidator.m
new file mode 100644
index 0000000000..f8665d8227
--- /dev/null
+++ b/matlab/src/matlab/+arrow/+array/+internal/+list/DatetimeValidator.m
@@ -0,0 +1,49 @@
+% 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 DatetimeValidator < arrow.array.internal.list.ClassTypeValidator
+
+ properties (GetAccess=public, SetAccess=private)
+ Zoned (1, 1) logical = false
+ end
+
+ methods
+ function obj = DatetimeValidator(date)
+ arguments
+ date(:, :) datetime
+ end
+ [email protected](date);
+ obj.Zoned = ~isempty(date.TimeZone);
+ end
+
+ function validateElement(obj, element)
+ [email protected](obj,
element);
+ % zoned and obj.Zoned must be equal because zoned
+ % and unzoned datetimes cannot be concatenated together.
+ zoned = ~isempty(element.TimeZone);
+ if obj.Zoned && ~zoned
+ errorID = "arrow:array:list:ExpectedZonedDatetime";
+ msg = "Expected all datetime elements in the cell array to " +
...
+ "have a time zone but encountered a datetime array without
a time zone";
+ error(errorID, msg);
+ elseif ~obj.Zoned && zoned
+ errorID = "arrow:array:list:ExpectedUnzonedDatetime";
+ msg = "Expected all datetime elements in the cell array to " +
...
+ "not have a time zone but encountered a datetime array
with a time zone";
+ error(errorID, msg);
+ end
+ end
+ end
+end
\ No newline at end of file
diff --git a/matlab/test/arrow/array/list/tDatetimeValidator.m
b/matlab/test/arrow/array/list/tDatetimeValidator.m
new file mode 100644
index 0000000000..06d18bd92f
--- /dev/null
+++ b/matlab/test/arrow/array/list/tDatetimeValidator.m
@@ -0,0 +1,181 @@
+%TDATETIMEVALIDATOR Unit tests for
+%arrow.array.internal.list.DatetimeValidator
+
+% 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 tDatetimeValidator < matlab.unittest.TestCase
+
+ methods (Test)
+ function Smoke(testCase)
+ import arrow.array.internal.list.DatetimeValidator
+ validator = DatetimeValidator(datetime(2023, 10, 31));
+ testCase.verifyInstanceOf(validator,
"arrow.array.internal.list.DatetimeValidator");
+ end
+
+ function ClassNameGetter(testCase)
+ % Verify the ClassName getter returns the expected scalar
+ % string.
+ import arrow.array.internal.list.DatetimeValidator
+
+ validator = DatetimeValidator(datetime(2023, 10, 31));
+ testCase.verifyEqual(validator.ClassName, "datetime");
+ end
+
+ function ClassNameNoSetter(testCase)
+ % Verify ClassName property is not settable.
+ import arrow.array.internal.list.DatetimeValidator
+
+ validator = DatetimeValidator(datetime(2023, 10, 31));
+ fcn = @() setfield(validator, "ClassName", "duration");
+ testCase.verifyError(fcn, "MATLAB:class:SetProhibited");
+ end
+
+ function ZonedGetter(testCase)
+ % Verify the Zoned getter returns the expected scalar
+ % logical.
+
+ import arrow.array.internal.list.DatetimeValidator
+ validator = DatetimeValidator(datetime(2023, 10, 31));
+ testCase.verifyEqual(validator.Zoned, false);
+
+ validator = DatetimeValidator(datetime(2023, 10, 31,
TimeZone="UTC"));
+ testCase.verifyEqual(validator.Zoned, true);
+ end
+
+ function ZonedNoSetter(testCase)
+ % Verify Zoned property is not settable.
+ import arrow.array.internal.list.DatetimeValidator
+
+ validator = DatetimeValidator(datetime(2023, 10, 31));
+ fcn = @() setfield(validator, "Zoned", true);
+ testCase.verifyError(fcn, "MATLAB:class:SetProhibited");
+
+ validator = DatetimeValidator(datetime(2023, 10, 31,
TimeZone="UTC"));
+ fcn = @() setfield(validator, "Zoned", false);
+ testCase.verifyError(fcn, "MATLAB:class:SetProhibited");
+ end
+
+ function ValidateElementNoThrow(testCase) %#ok<MANU>
+ % Verify validateElement does not throw an exception if:
+ % 1. the input element is a datetime
+ % 2. its TimeZone property is '' and Zoned = false
+ % 3. its TimeZone property is not empty and Zoned = true
+
+ import arrow.array.internal.list.DatetimeValidator
+
+ validator = DatetimeValidator(datetime(2023, 10, 31));
+ validator.validateElement(datetime(2023, 11, 1));
+ validator.validateElement(datetime(2023, 11, 1) + days(0:2));
+ validator.validateElement(datetime(2023, 11, 1) + days(0:2)');
+ validator.validateElement(datetime.empty(0, 1));
+
+ validator = DatetimeValidator(datetime(2023, 10, 31,
TimeZone="UTC"));
+ validator.validateElement(datetime(2023, 11, 1, TimeZone="UTC"));
+ validator.validateElement(datetime(2023, 11, 1,
TimeZone="America/New_York") + days(0:2));
+ validator.validateElement(datetime(2023, 11, 1,
TimeZone="Pacific/Fiji") + days(0:2)');
+ emptyDatetime = datetime.empty(0, 1);
+ emptyDatetime.TimeZone = "Asia/Dubai";
+ validator.validateElement(emptyDatetime);
+ end
+
+ function ValidateElementExpectedZonedDatetimeError(testCase)
+ % Verify validateElement throws an exception whose identifier
+ % is "arrow:array:list:ExpectedZonedDatetime" if the input
+ % datetime is unzoned, but the validator expected all
+ % datetimes to zoned.
+ import arrow.array.internal.list.DatetimeValidator
+
+ % validator will expect all elements to be zoned datetimes
+ % because the input datetime is zoned.
+ validator = DatetimeValidator(datetime(2023, 10, 31,
TimeZone="UTC"));
+ errorID = "arrow:array:list:ExpectedZonedDatetime";
+ fcn = @() validator.validateElement(datetime(2023, 11, 1));
+ testCase.verifyError(fcn, errorID);
+ end
+
+ function ValidateElementExpectedUnzonedDatetimeError(testCase)
+ % Verify validateElement throws an exception whose identifier
+ % is "arrow:array:list:ExpectedUnzonedDatetime" if the input
+ % datetime has a time zone, but the validator expected all
+ % datetimes to be unzoned.
+ import arrow.array.internal.list.DatetimeValidator
+
+ % validator will expect all elements to be unzoned datetimes
+ % because the input datetime is not zoned.
+ validator = DatetimeValidator(datetime(2023, 10, 31));
+ errorID = "arrow:array:list:ExpectedUnzonedDatetime";
+ fcn = @() validator.validateElement(datetime(2023, 11, 1,
TimeZone="America/New_York"));
+ testCase.verifyError(fcn, errorID);
+ end
+
+ function ValidateElementClassTypeMismatchError(testCase)
+ % Verify validateElement throws an exception whose identifier
+ % is "arrow:array:list:ClassTypeMismatch" if the input
+ % element is not a datetime.
+ import arrow.array.internal.list.DatetimeValidator
+
+ validator = DatetimeValidator(datetime(2023, 10, 31));
+ errorID = "arrow:array:list:ClassTypeMismatch";
+ fcn = @() validator.validateElement(1);
+ testCase.verifyError(fcn, errorID);
+ fcn = @() validator.validateElement("A");
+ testCase.verifyError(fcn, errorID);
+ fcn = @() validator.validateElement(seconds(1));
+ testCase.verifyError(fcn, errorID);
+ end
+
+ function GetElementLength(testCase)
+ % Verify getElementLength returns the expected length values
+ % for the given input arrays.
+ import arrow.array.internal.list.DatetimeValidator
+
+ validator = DatetimeValidator(datetime(2023, 10, 31));
+ length = validator.getElementLength(datetime.empty(0, 1));
+ testCase.verifyEqual(length, 0);
+ length = validator.getElementLength(datetime(2023, 11, 1));
+ testCase.verifyEqual(length, 1);
+ length = validator.getElementLength(datetime(2023, 11, 1) +
days(0:2));
+ testCase.verifyEqual(length, 3);
+ length = validator.getElementLength(datetime(2023, 11, 1) +
days([0 1; 2 3]));
+ testCase.verifyEqual(length, 4);
+ end
+
+ function ReshapeCellElements(testCase)
+ % Verify reshapeCellElements reshapes all elements in the input
+ % cell array into column vectors.
+ import arrow.array.internal.list.DatetimeValidator
+
+ validator = DatetimeValidator(datetime(2023, 10, 31));
+ date = datetime(2023, 10, 31);
+
+ C = {date + days(0:2), ...
+ date + days(3:4)', ...
+ date + days([5 6; 7 8]), ...
+ datetime.empty(1, 0)};
+
+ act = validator.reshapeCellElements(C);
+
+ exp = {date + days(0:2)', ...
+ date + days(3:4)', ...
+ date + days([5; 7; 6; 8]), ...
+ datetime.empty(0, 1)};
+
+ testCase.verifyEqual(act, exp);
+ end
+
+ end
+
+end
\ No newline at end of file