1
0

2 Commits 10c3a74123 ... 169d933579

Autor SHA1 Mensagem Data
  Billy Barrow 169d933579 feat(expressions): add DateTimeFunctionAccessor for DateTime functions há 1 mês atrás
  Billy Barrow 1028887ad9 fix(expressions): add explicit type checks before casting há 1 mês atrás

+ 624 - 0
src/lib/Expressions/DateTimeFunctionAccessor.vala

@@ -0,0 +1,624 @@
+using Invercargill.DataStructures;
+using Invercargill.Wrappers;
+
+namespace Invercargill.Expressions {
+
+    /**
+     * Function accessor for DateTime types.
+     * 
+     * Provides access to DateTime functions:
+     * - `compare(other)`: Compares this datetime with another, returns -1, 0, or 1
+     * - `difference(other)`: Returns the difference in seconds between two datetimes
+     * - `equal(other)`: Checks if two datetimes are equal
+     * - `format(format_string)`: Formats the datetime using a custom format string
+     * - `format_iso8601()`: Returns the datetime in ISO 8601 format
+     * - `get_day_of_month()`: Returns the day of the month (1-31)
+     * - `get_day_of_week()`: Returns the day of the week (0=Sunday, 1=Monday, etc.)
+     * - `get_day_of_year()`: Returns the day of the year (1-366)
+     * - `get_hour()`: Returns the hour (0-23)
+     * - `get_microsecond()`: Returns the microsecond (0-999999)
+     * - `get_minute()`: Returns the minute (0-59)
+     * - `get_second()`: Returns the second (0-59)
+     * - `get_seconds()`: Returns the total seconds since Unix epoch
+     * - `get_timezone()`: Returns the timezone identifier
+     * - `get_timezone_abbreviation()`: Returns the timezone abbreviation
+     * - `get_utc_offset()`: Returns the UTC offset in seconds
+     * - `get_week_numbering_year()`: Returns the week-numbering year
+     * - `get_week_of_year()`: Returns the week of the year (1-53)
+     * - `get_year()`: Returns the year
+     * - `get_ymd()`: Returns a lot containing [year, month, day]
+     * - `is_daylight_savings()`: Checks if daylight savings time is in effect
+     * - `to_local()`: Converts to local timezone
+     * - `to_timezone(tz)`: Converts to the specified timezone
+     * - `to_unix()`: Returns Unix timestamp (seconds)
+     * - `to_unix_usec()`: Returns Unix timestamp with microseconds
+     * - `to_utc()`: Converts to UTC
+     * 
+     * Example usage in expressions:
+     * {{{
+     * created_at.get_year()                    // 2024
+     * created_at.format("%Y-%m-%d")            // "2024-03-15"
+     * created_at.to_utc()                      // DateTime in UTC
+     * created_at.difference(other_date)        // seconds difference
+     * }}}
+     */
+    public class DateTimeFunctionAccessor : Object, FunctionAccessor {
+
+        /**
+         * The DateTime value being accessed.
+         */
+        private DateTime? _value;
+
+        /**
+         * Creates a new DateTimeFunctionAccessor.
+         * 
+         * @param value The DateTime value to call functions on
+         */
+        public DateTimeFunctionAccessor(DateTime? value) {
+            _value = value;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public bool has_function(string function_name) {
+            switch (function_name) {
+                case "compare":
+                case "difference":
+                case "equal":
+                case "format":
+                case "format_iso8601":
+                case "get_day_of_month":
+                case "get_day_of_week":
+                case "get_day_of_year":
+                case "get_hour":
+                case "get_microsecond":
+                case "get_minute":
+                case "get_second":
+                case "get_seconds":
+                case "get_timezone":
+                case "get_timezone_abbreviation":
+                case "get_utc_offset":
+                case "get_week_numbering_year":
+                case "get_week_of_year":
+                case "get_year":
+                case "get_ymd":
+                case "is_daylight_savings":
+                case "to_local":
+                case "to_timezone":
+                case "to_unix":
+                case "to_unix_usec":
+                case "to_utc":
+                    return true;
+                default:
+                    return false;
+            }
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public Enumerable<string> get_function_names() {
+            return new Wrappers.Array<string>(new string[]{
+                "compare",
+                "difference",
+                "equal",
+                "format",
+                "format_iso8601",
+                "get_day_of_month",
+                "get_day_of_week",
+                "get_day_of_year",
+                "get_hour",
+                "get_microsecond",
+                "get_minute",
+                "get_second",
+                "get_seconds",
+                "get_timezone",
+                "get_timezone_abbreviation",
+                "get_utc_offset",
+                "get_week_numbering_year",
+                "get_week_of_year",
+                "get_year",
+                "get_ymd",
+                "is_daylight_savings",
+                "to_local",
+                "to_timezone",
+                "to_unix",
+                "to_unix_usec",
+                "to_utc"
+            });
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public Element call_function(
+            string function_name,
+            Series<Element> arguments,
+            EvaluationContext context
+        ) throws ExpressionError {
+            
+            // Handle null DateTime
+            if (_value == null) {
+                return handle_null_function(function_name, arguments);
+            }
+
+            switch (function_name) {
+                case "compare":
+                    return call_compare(arguments);
+                
+                case "difference":
+                    return call_difference(arguments);
+                
+                case "equal":
+                    return call_equal(arguments);
+                
+                case "format":
+                    return call_format(arguments);
+                
+                case "format_iso8601":
+                    return call_format_iso8601(arguments);
+                
+                case "get_day_of_month":
+                    return call_get_day_of_month(arguments);
+                
+                case "get_day_of_week":
+                    return call_get_day_of_week(arguments);
+                
+                case "get_day_of_year":
+                    return call_get_day_of_year(arguments);
+                
+                case "get_hour":
+                    return call_get_hour(arguments);
+                
+                case "get_microsecond":
+                    return call_get_microsecond(arguments);
+                
+                case "get_minute":
+                    return call_get_minute(arguments);
+                
+                case "get_second":
+                    return call_get_second(arguments);
+                
+                case "get_seconds":
+                    return call_get_seconds(arguments);
+                
+                case "get_timezone":
+                    return call_get_timezone(arguments);
+                
+                case "get_timezone_abbreviation":
+                    return call_get_timezone_abbreviation(arguments);
+                
+                case "get_utc_offset":
+                    return call_get_utc_offset(arguments);
+                
+                case "get_week_numbering_year":
+                    return call_get_week_numbering_year(arguments);
+                
+                case "get_week_of_year":
+                    return call_get_week_of_year(arguments);
+                
+                case "get_year":
+                    return call_get_year(arguments);
+                
+                case "get_ymd":
+                    return call_get_ymd(arguments);
+                
+                case "is_daylight_savings":
+                    return call_is_daylight_savings(arguments);
+                
+                case "to_local":
+                    return call_to_local(arguments);
+                
+                case "to_timezone":
+                    return call_to_timezone(arguments);
+                
+                case "to_unix":
+                    return call_to_unix(arguments);
+                
+                case "to_unix_usec":
+                    return call_to_unix_usec(arguments);
+                
+                case "to_utc":
+                    return call_to_utc(arguments);
+                
+                default:
+                    throw new ExpressionError.FUNCTION_NOT_FOUND(
+                        @"Unknown DateTime function: $function_name"
+                    );
+            }
+        }
+
+        /**
+         * Handles function calls on null DateTime values.
+         */
+        private Element handle_null_function(string function_name, Series<Element> arguments) 
+            throws ExpressionError {
+            
+            switch (function_name) {
+                case "compare":
+                case "difference":
+                    // Return null for comparison/difference with null
+                    return new NullElement();
+                
+                case "equal":
+                    // Check if comparing with another null
+                    expect_argument_count("equal", arguments, 1);
+                    var args = to_array(arguments);
+                    DateTime? other = get_datetime_argument(args[0], "other");
+                    return new NativeElement<bool>(other == null);
+                
+                default:
+                    // Most DateTime functions return null when called on null
+                    return new NullElement();
+            }
+        }
+
+        /**
+         * Compares this datetime with another.
+         * Returns -1 if this < other, 0 if equal, 1 if this > other.
+         */
+        private Element call_compare(Series<Element> arguments) throws ExpressionError {
+            expect_argument_count("compare", arguments, 1);
+            var args = to_array(arguments);
+            DateTime? other = get_datetime_argument(args[0], "other");
+            
+            if (other == null) {
+                return new NullElement();
+            }
+            
+            return new NativeElement<int>(_value.compare(other));
+        }
+
+        /**
+         * Returns the difference in seconds between two datetimes.
+         */
+        private Element call_difference(Series<Element> arguments) throws ExpressionError {
+            expect_argument_count("difference", arguments, 1);
+            var args = to_array(arguments);
+            DateTime? other = get_datetime_argument(args[0], "other");
+            
+            if (other == null) {
+                return new NullElement();
+            }
+            
+            TimeSpan diff = _value.difference(other);
+            // TimeSpan is in microseconds, convert to seconds
+            return new NativeElement<int64?>(diff / TimeSpan.SECOND);
+        }
+
+        /**
+         * Checks if two datetimes are equal.
+         */
+        private Element call_equal(Series<Element> arguments) throws ExpressionError {
+            expect_argument_count("equal", arguments, 1);
+            var args = to_array(arguments);
+            DateTime? other = get_datetime_argument(args[0], "other");
+            
+            if (other == null) {
+                return new NativeElement<bool>(false);
+            }
+            
+            return new NativeElement<bool>(_value.equal(other));
+        }
+
+        /**
+         * Formats the datetime using a custom format string.
+         */
+        private Element call_format(Series<Element> arguments) throws ExpressionError {
+            expect_argument_count("format", arguments, 1);
+            var args = to_array(arguments);
+            string? format_string = get_string_argument(args[0], "format_string");
+            
+            if (format_string == null) {
+                return new NativeElement<string>("");
+            }
+            
+            return new NativeElement<string>(_value.format(format_string));
+        }
+
+        /**
+         * Returns the datetime in ISO 8601 format.
+         */
+        private Element call_format_iso8601(Series<Element> arguments) throws ExpressionError {
+            expect_argument_count("format_iso8601", arguments, 0);
+            // ISO 8601 format: YYYY-MM-DDTHH:MM:SS+TZ:00
+            return new NativeElement<string>(_value.format("%Y-%m-%dT%H:%M:%S%z"));
+        }
+
+        /**
+         * Returns the day of the month (1-31).
+         */
+        private Element call_get_day_of_month(Series<Element> arguments) throws ExpressionError {
+            expect_argument_count("get_day_of_month", arguments, 0);
+            return new NativeElement<int>(_value.get_day_of_month());
+        }
+
+        /**
+         * Returns the day of the week (0=Sunday, 1=Monday, etc.).
+         */
+        private Element call_get_day_of_week(Series<Element> arguments) throws ExpressionError {
+            expect_argument_count("get_day_of_week", arguments, 0);
+            return new NativeElement<int>(_value.get_day_of_week());
+        }
+
+        /**
+         * Returns the day of the year (1-366).
+         */
+        private Element call_get_day_of_year(Series<Element> arguments) throws ExpressionError {
+            expect_argument_count("get_day_of_year", arguments, 0);
+            return new NativeElement<int>(_value.get_day_of_year());
+        }
+
+        /**
+         * Returns the hour (0-23).
+         */
+        private Element call_get_hour(Series<Element> arguments) throws ExpressionError {
+            expect_argument_count("get_hour", arguments, 0);
+            return new NativeElement<int>(_value.get_hour());
+        }
+
+        /**
+         * Returns the microsecond (0-999999).
+         */
+        private Element call_get_microsecond(Series<Element> arguments) throws ExpressionError {
+            expect_argument_count("get_microsecond", arguments, 0);
+            return new NativeElement<int>(_value.get_microsecond());
+        }
+
+        /**
+         * Returns the minute (0-59).
+         */
+        private Element call_get_minute(Series<Element> arguments) throws ExpressionError {
+            expect_argument_count("get_minute", arguments, 0);
+            return new NativeElement<int>(_value.get_minute());
+        }
+
+        /**
+         * Returns the second (0-59).
+         */
+        private Element call_get_second(Series<Element> arguments) throws ExpressionError {
+            expect_argument_count("get_second", arguments, 0);
+            return new NativeElement<int>(_value.get_second());
+        }
+
+        /**
+         * Returns the total seconds since Unix epoch.
+         */
+        private Element call_get_seconds(Series<Element> arguments) throws ExpressionError {
+            expect_argument_count("get_seconds", arguments, 0);
+            return new NativeElement<int64?>(_value.to_unix());
+        }
+
+        /**
+         * Returns the timezone identifier.
+         */
+        private Element call_get_timezone(Series<Element> arguments) throws ExpressionError {
+            expect_argument_count("get_timezone", arguments, 0);
+            TimeZone tz = _value.get_timezone();
+            // TimeZone doesn't have a direct identifier getter, use abbreviation
+            return new NativeElement<string>(tz.get_abbreviation((int)_value.to_unix()));
+        }
+
+        /**
+         * Returns the timezone abbreviation.
+         */
+        private Element call_get_timezone_abbreviation(Series<Element> arguments) throws ExpressionError {
+            expect_argument_count("get_timezone_abbreviation", arguments, 0);
+            TimeZone tz = _value.get_timezone();
+            return new NativeElement<string>(tz.get_abbreviation((int)_value.to_unix()));
+        }
+
+        /**
+         * Returns the UTC offset in seconds.
+         */
+        private Element call_get_utc_offset(Series<Element> arguments) throws ExpressionError {
+            expect_argument_count("get_utc_offset", arguments, 0);
+            TimeZone tz = _value.get_timezone();
+            int offset = (int)(tz.get_offset((int)_value.to_unix()) / TimeSpan.SECOND);
+            return new NativeElement<int>(offset);
+        }
+
+        /**
+         * Returns the week-numbering year.
+         */
+        private Element call_get_week_numbering_year(Series<Element> arguments) throws ExpressionError {
+            expect_argument_count("get_week_numbering_year", arguments, 0);
+            return new NativeElement<int>(_value.get_week_numbering_year());
+        }
+
+        /**
+         * Returns the week of the year (1-53).
+         */
+        private Element call_get_week_of_year(Series<Element> arguments) throws ExpressionError {
+            expect_argument_count("get_week_of_year", arguments, 0);
+            return new NativeElement<int>(_value.get_week_of_year());
+        }
+
+        /**
+         * Returns the year.
+         */
+        private Element call_get_year(Series<Element> arguments) throws ExpressionError {
+            expect_argument_count("get_year", arguments, 0);
+            return new NativeElement<int>(_value.get_year());
+        }
+
+        /**
+         * Returns a lot containing [year, month, day].
+         */
+        private Element call_get_ymd(Series<Element> arguments) throws ExpressionError {
+            expect_argument_count("get_ymd", arguments, 0);
+            int year, month, day;
+            _value.get_ymd(out year, out month, out day);
+            
+            var result = new DataStructures.Series<int>();
+            result.add(year);
+            result.add(month);
+            result.add(day);
+            return new ValueElement(result);
+        }
+
+        /**
+         * Checks if daylight savings time is in effect.
+         */
+        private Element call_is_daylight_savings(Series<Element> arguments) throws ExpressionError {
+            expect_argument_count("is_daylight_savings", arguments, 0);
+            TimeZone tz = _value.get_timezone();
+            // get_offset returns the offset including DST, compare with standard offset
+            // GLib doesn't have a direct is_dst method, so we check if the offset differs
+            // from what we'd expect. This is a simplification.
+            // Actually, TimeZone.get_abbreviation often includes DST indicator
+            string abbrev = tz.get_abbreviation((int)_value.to_unix());
+            // Common DST abbreviations end with 'T' or 'DT' or contain 'DST'
+            // This is a heuristic - proper DST detection requires more complex logic
+            // For now, we'll use a simpler approach based on the abbreviation
+            bool is_dst = abbrev.has_suffix("DT") || abbrev.has_suffix("ST") == false;
+            return new NativeElement<bool>(is_dst);
+        }
+
+        /**
+         * Converts to local timezone.
+         */
+        private Element call_to_local(Series<Element> arguments) throws ExpressionError {
+            expect_argument_count("to_local", arguments, 0);
+            TimeZone local_tz = new TimeZone.local();
+            DateTime local = _value.to_timezone(local_tz);
+            return new NativeElement<DateTime>(local);
+        }
+
+        /**
+         * Converts to the specified timezone.
+         */
+        private Element call_to_timezone(Series<Element> arguments) throws ExpressionError {
+            expect_argument_count("to_timezone", arguments, 1);
+            var args = to_array(arguments);
+            string? tz_identifier = get_string_argument(args[0], "timezone");
+            
+            if (tz_identifier == null) {
+                return new NullElement();
+            }
+            
+            try {
+                TimeZone tz = new TimeZone.identifier(tz_identifier);
+                DateTime converted = _value.to_timezone(tz);
+                return new NativeElement<DateTime>(converted);
+            } catch (Error e) {
+                throw new ExpressionError.INVALID_ARGUMENTS(
+                    @"Invalid timezone identifier: $tz_identifier"
+                );
+            }
+        }
+
+        /**
+         * Returns Unix timestamp (seconds).
+         */
+        private Element call_to_unix(Series<Element> arguments) throws ExpressionError {
+            expect_argument_count("to_unix", arguments, 0);
+            return new NativeElement<int64?>(_value.to_unix());
+        }
+
+        /**
+         * Returns Unix timestamp with microseconds.
+         */
+        private Element call_to_unix_usec(Series<Element> arguments) throws ExpressionError {
+            expect_argument_count("to_unix_usec", arguments, 0);
+            // to_unix() returns seconds, get_microsecond() returns the fractional part
+            int64 seconds = _value.to_unix();
+            int microseconds = _value.get_microsecond();
+            // Return as a double to preserve both parts
+            double usec = seconds + (microseconds / 1000000.0);
+            return new NativeElement<double?>(usec);
+        }
+
+        /**
+         * Converts to UTC.
+         */
+        private Element call_to_utc(Series<Element> arguments) throws ExpressionError {
+            expect_argument_count("to_utc", arguments, 0);
+            TimeZone utc_tz = new TimeZone.utc();
+            DateTime utc = _value.to_timezone(utc_tz);
+            return new NativeElement<DateTime>(utc);
+        }
+
+        /**
+         * Helper to get a DateTime argument from an Element.
+         */
+        private DateTime? get_datetime_argument(Element element, string arg_name) throws ExpressionError {
+            DateTime? value;
+            if (element.try_get_as(out value)) {
+                return value;
+            }
+            throw new ExpressionError.INVALID_ARGUMENTS(
+                @"$arg_name must be a DateTime"
+            );
+        }
+
+        /**
+         * Helper to get a string argument from an Element.
+         */
+        private string? get_string_argument(Element element, string arg_name) throws ExpressionError {
+            string? value;
+            if (element.try_get_as(out value)) {
+                return value;
+            }
+            throw new ExpressionError.INVALID_ARGUMENTS(
+                @"$arg_name must be a string"
+            );
+        }
+
+        /**
+         * Helper to validate argument count.
+         */
+        private void expect_argument_count(string function_name, Series<Element> arguments, int expected) 
+            throws ExpressionError {
+            int count = 0;
+            foreach (var _ in arguments) {
+                count++;
+            }
+            if (count != expected) {
+                throw new ExpressionError.INVALID_ARGUMENTS(
+                    @"$function_name expects $expected argument(s), got $count"
+                );
+            }
+        }
+
+        /**
+         * Helper to convert Series to array.
+         */
+        private Element[] to_array(Series<Element> arguments) {
+            var list = new GLib.GenericArray<Element>();
+            foreach (var arg in arguments) {
+                list.add(arg);
+            }
+            var result = new Element[list.length];
+            for (int i = 0; i < list.length; i++) {
+                result[i] = list[i];
+            }
+            return result;
+        }
+
+    }
+
+    /**
+     * Factory for creating DateTimeFunctionAccessor instances.
+     * 
+     * This factory is registered with the TypeAccessorRegistry to provide
+     * function access for DateTime values.
+     */
+    public class DateTimeFunctionAccessorFactory : Object, FunctionAccessorFactory {
+
+        /**
+         * {@inheritDoc}
+         */
+        public FunctionAccessor? create(Element element) {
+            // Use explicit type checking as in LotPropertyAccessorFactory
+            DateTime? value = null;
+            if (element.type() == typeof(DateTime) && element.try_get_as(out value)) {
+                return new DateTimeFunctionAccessor(value);
+            }
+            return null;
+        }
+
+    }
+
+}

+ 2 - 2
src/lib/Expressions/LotPropertyAccessor.vala

@@ -89,8 +89,8 @@ namespace Invercargill.Expressions {
         public PropertyAccessor? create(Element element) {
             // Check if the element's value implements Lot
             // We need to check for Lot<Object> since we can't check for Lot<T> generically
-            Lot<Object>? lot;
-            if (element.try_get_as(out lot)) {
+            Lot? lot = null;
+            if (element.type() == typeof(Lot) && element.try_get_as(out lot)) {
                 return new LotPropertyAccessor(lot);
             }
             return null;

+ 5 - 4
src/lib/Expressions/TypeAccessorRegistry.vala

@@ -146,8 +146,8 @@ namespace Invercargill.Expressions {
             }
 
             // Fallback: Check if element directly implements PropertyAccessor
-            PropertyAccessor? accessor;
-            if (element.try_get_as(out accessor)) {
+            PropertyAccessor? accessor = null;
+            if (element.type() == typeof(PropertyAccessor) && element.try_get_as(out accessor)) {
                 return accessor;
             }
 
@@ -161,7 +161,6 @@ namespace Invercargill.Expressions {
             if (element.try_get_as(out obj)) {
                 return new ObjectPropertyAccessor(obj);
             }
-
             return null;
         }
 
@@ -254,17 +253,19 @@ namespace Invercargill.Expressions {
 
         /**
          * Registers default accessor factories.
-         * 
+         *
          * This is called automatically when the singleton is created.
          * Currently registers:
          * - StringPropertyAccessorFactory for string properties
          * - LotPropertyAccessorFactory for Lot<T> properties
          * - StringFunctionAccessorFactory for string functions
+         * - DateTimeFunctionAccessorFactory for DateTime functions
          */
         private void register_defaults() {
             register_property_accessor(new StringPropertyAccessorFactory());
             register_property_accessor(new LotPropertyAccessorFactory());
             register_function_accessor(new StringFunctionAccessorFactory());
+            register_function_accessor(new DateTimeFunctionAccessorFactory());
         }
 
     }

+ 1 - 0
src/lib/meson.build

@@ -166,6 +166,7 @@ sources += files('Expressions/TypeAccessorRegistry.vala')
 sources += files('Expressions/StringPropertyAccessor.vala')
 sources += files('Expressions/LotPropertyAccessor.vala')
 sources += files('Expressions/StringFunctionAccessor.vala')
+sources += files('Expressions/DateTimeFunctionAccessor.vala')
 
 sources += files('Expressions/Expressions/LiteralExpression.vala')
 sources += files('Expressions/Expressions/VariableExpression.vala')