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

Reply via email to