From 3b1df26a79fd3755b0c77b21b1de9b33b6605844 Mon Sep 17 00:00:00 2001 From: Jan Steemann Date: Fri, 4 Dec 2015 10:53:01 +0100 Subject: [PATCH 1/2] added AQL function `IS_DATESTRING` --- CHANGELOG | 11 +++ .../Books/Users/Aql/DateFunctions.mdpp | 5 ++ arangod/Aql/Executor.cpp | 1 + js/server/modules/org/arangodb/aql.js | 30 +++++++ js/server/tests/aql-functions-date.js | 87 +++++++++++++++++++ 5 files changed, 134 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index de35f61f10..aea5ca63f0 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,17 @@ v2.8.0 (XXXX-XX-XX) ------------------- +* added AQL function `IS_DATESTRING(value)` + + Returns true if *value* is a string that can be used in a date function. + This includes partial dates such as *2015* or *2015-10* and strings containing + invalid dates such as *2015-02-31*. The function will return false for all + non-string values, even if some of them may be usable in date functions. + + +v2.8.0-alpha1 (2015-12-03) +-------------------------- + * added AQL keywords `GRAPH`, `OUTBOUND`, `INBOUND` and `ANY` for use in graph traversals, reserved AQL keyword `ALL` for future use diff --git a/Documentation/Books/Users/Aql/DateFunctions.mdpp b/Documentation/Books/Users/Aql/DateFunctions.mdpp index 6f1933d296..6eb9d799f7 100644 --- a/Documentation/Books/Users/Aql/DateFunctions.mdpp +++ b/Documentation/Books/Users/Aql/DateFunctions.mdpp @@ -370,6 +370,11 @@ DATE_FORMAT("2016", "%%l = %l") // "%l = 1" (2016 is a leap year) DATE_FORMAT("2016-03-01", "%xxx%") // "063", trailing % ignored ``` +- *IS_DATESTRING(value)*: Returns true if *value* is a string that can be used + in a date function. This includes partial dates such as *2015* or *2015-10* and + strings containing invalid dates such as *2015-02-31*. The function will return + false for all non-string values, even if some of them may be usable in date functions. + !SECTION Working with dates and indices There are two recommended ways to store timestamps in ArangoDB: diff --git a/arangod/Aql/Executor.cpp b/arangod/Aql/Executor.cpp index efa61ff7c1..0f4717e21a 100644 --- a/arangod/Aql/Executor.cpp +++ b/arangod/Aql/Executor.cpp @@ -111,6 +111,7 @@ std::unordered_map const Executor::FunctionNames{ { "IS_OBJECT", Function("IS_OBJECT", "AQL_IS_OBJECT", ".", true, true, false, true, true, &Functions::IsObject) }, // IS_DOCUMENT is an alias for IS_OBJECT { "IS_DOCUMENT", Function("IS_DOCUMENT", "AQL_IS_DOCUMENT", ".", true, true, false, true, true, &Functions::IsObject) }, + { "IS_DATESTRING", Function("IS_DATESTRING", "AQL_IS_DATESTRING", ".", true, true, false, true, true) }, // type cast functions { "TO_NUMBER", Function("TO_NUMBER", "AQL_TO_NUMBER", ".", true, true, false, true, true, &Functions::ToNumber) }, diff --git a/js/server/modules/org/arangodb/aql.js b/js/server/modules/org/arangodb/aql.js index 804ce2b02b..374b3002ac 100644 --- a/js/server/modules/org/arangodb/aql.js +++ b/js/server/modules/org/arangodb/aql.js @@ -2703,6 +2703,35 @@ function AQL_IS_OBJECT (value) { return (TYPEWEIGHT(value) === TYPEWEIGHT_OBJECT); } +//////////////////////////////////////////////////////////////////////////////// +/// @brief test if value is of a valid datestring +/// +/// returns a bool +//////////////////////////////////////////////////////////////////////////////// + +function AQL_IS_DATESTRING (value) { + 'use strict'; + + if (TYPEWEIGHT(value) !== TYPEWEIGHT_STRING) { + return false; + } + + // argument is a string + + // append zulu time specifier if no other present + if (! value.match(/([zZ]|[+\-]\d+(:\d+)?)$/) || + (value.match(/-\d+(:\d+)?$/) && ! value.match(/[tT ]/))) { + value += 'Z'; + } + + // detect invalid dates ("foo" -> "fooZ" -> getTime() == NaN) + var date = new Date(value); + if (isNaN(date)) { + return false; + } + return true; +} + // ----------------------------------------------------------------------------- // --SECTION-- numeric functions // ----------------------------------------------------------------------------- @@ -9158,6 +9187,7 @@ exports.AQL_IS_ARRAY = AQL_IS_ARRAY; exports.AQL_IS_LIST = AQL_IS_ARRAY; // alias exports.AQL_IS_OBJECT = AQL_IS_OBJECT; exports.AQL_IS_DOCUMENT = AQL_IS_OBJECT; // alias +exports.AQL_IS_DATESTRING = AQL_IS_DATESTRING; exports.AQL_FLOOR = AQL_FLOOR; exports.AQL_CEIL = AQL_CEIL; exports.AQL_ROUND = AQL_ROUND; diff --git a/js/server/tests/aql-functions-date.js b/js/server/tests/aql-functions-date.js index d445ed16f5..93963f3974 100644 --- a/js/server/tests/aql-functions-date.js +++ b/js/server/tests/aql-functions-date.js @@ -42,6 +42,93 @@ var assertQueryWarningAndNull = helper.assertQueryWarningAndNull; function ahuacatlDateFunctionsTestSuite () { return { +//////////////////////////////////////////////////////////////////////////////// +/// @brief test is_datestring function +//////////////////////////////////////////////////////////////////////////////// + + testIsDateString : function () { + var values = [ + [ "2000-04-29", true ], + [ "2000-04-29Z", true ], + [ "2012-02-12 13:24:12", true ], + [ "2012-02-12 13:24:12Z", true ], + [ "2012-02-12 23:59:59.991", true ], + [ "2012-02-12 23:59:59.991Z", true ], + [ "2012-02-12", true ], + [ "2012-02-12Z", true ], + [ "2012-02-12T13:24:12Z", true ], + [ "2012-02-12Z", true ], + [ "2012-2-12Z", true ], + [ "1910-01-02T03:04:05Z", true ], + [ "1910-01-02 03:04:05Z", true ], + [ "1910-01-02", true ], + [ "1910-01-02Z", true ], + [ "1970-01-01T01:05:27", true ], + [ "1970-01-01T01:05:27Z", true ], + [ "1970-01-01 01:05:27Z", true ], + [ "1970-1-1Z", true ], + [ "1970-1-1", true ], + [ "1221-02-28T23:59:59Z", true ], + [ "1221-02-28 23:59:59Z", true ], + [ "1221-02-28Z", true ], + [ "1221-2-28Z", true ], + [ "1000-12-24T04:12:00Z", true ], + [ "1000-12-24Z", true ], + [ "1000-12-24 04:12:00Z", true ], + [ "6789-12-31T23:59:58.99Z", true ], + [ "6789-12-31Z", true ], + [ "9999-12-31T23:59:59.999Z", true ], + [ "9999-12-31Z", true ], + [ "9999-12-31z", true ], + [ "9999-12-31", true ], + [ "2012Z", true ], + [ "2012z", true ], + [ "2012", true ], + [ "2012-1Z", true ], + [ "2012-1z", true ], + [ "2012-1-1z", true ], + [ "2012-01-01Z", true ], + [ "2012-01-01Z", true ], + [ " 2012-01-01Z", true ], + [ " 2012-01-01z", true ], + [ "foo2012-01-01z", false ], + [ "2012-01-01foo", false ], + [ "foo", false ], + [ "bar", false ], + [ "2015-foobar", false ], + [ "", false ], + [ -95674000, false ], + [ 1399395674000, false ], + [ 60123, false ], + [ 1, false ], + [ 0, false ], + [ -4, false ], + [ true, false ], + [ false, false ], + [ null, false ], + [ [ ], false ], + [ [ 1 ], false ], + [ [ "foo" ], false ], + [ [ "foo", "bar" ], false ], + [ [ "2015-01-23" ], false ], + [ { }, false ] + ]; + + values.forEach(function (value) { + var actual = getQueryResults("RETURN IS_DATESTRING(@value)", { value: value[0] }); + assertEqual([ value[1] ], actual, value); + }); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test is_datestring function +//////////////////////////////////////////////////////////////////////////////// + + testIsDateStringInvalid : function () { + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_NUMBER_MISMATCH.code, "RETURN IS_DATESTRING()"); + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_NUMBER_MISMATCH.code, "RETURN IS_DATESTRING('foo', 'bar')"); + }, + //////////////////////////////////////////////////////////////////////////////// /// @brief test date_now function //////////////////////////////////////////////////////////////////////////////// From f4f90bb5a9c77464c6469740c5ce056cdae1955c Mon Sep 17 00:00:00 2001 From: Jan Steemann Date: Fri, 4 Dec 2015 11:16:02 +0100 Subject: [PATCH 2/2] updated docs --- Documentation/Books/Users/Aql/TypeCastFunctions.mdpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Documentation/Books/Users/Aql/TypeCastFunctions.mdpp b/Documentation/Books/Users/Aql/TypeCastFunctions.mdpp index 28ac3a1ea4..9659b27653 100644 --- a/Documentation/Books/Users/Aql/TypeCastFunctions.mdpp +++ b/Documentation/Books/Users/Aql/TypeCastFunctions.mdpp @@ -80,3 +80,8 @@ The following type check functions are available: - *IS_OBJECT(value)*: Checks whether *value* is an *object* / *document* value - *IS_DOCUMENT(value)*: This is an alias for *IS_OBJECT* + +- *IS_DATESTRING(value)*: Checks whether *value* is a string that can be used + in a date function. This includes partial dates such as *2015* or *2015-10* and + strings containing invalid dates such as *2015-02-31*. The function will return + false for all non-string values, even if some of them may be usable in date functions.