summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAkash-Sharma-1 <akashthegreatlegend@gmail.com>2020-05-23 20:48:12 +0530
committerAkash-Sharma-1 <akashthegreatlegend@gmail.com>2020-05-23 20:48:12 +0530
commitdba3a497b8824935c1bb5040d29087bc7120746f (patch)
tree045d1057c53c99618867262db837f01e40b739fa
parent231bc6d238ff6e4d6eaae9d65a7d96b953136ae4 (diff)
downloadrdflib-dba3a497b8824935c1bb5040d29087bc7120746f.tar.gz
Fix for issue #629 - Arithmetic Operations of DateTime for SPARQL Queries
Added the functionality for performing arithmetic operations of DateTime/Date/Time for SPARQL Queries
-rw-r--r--rdflib/plugins/sparql/datatypes.py10
-rw-r--r--rdflib/plugins/sparql/operators.py144
-rw-r--r--test/test_sparql_datetime.py219
3 files changed, 359 insertions, 14 deletions
diff --git a/rdflib/plugins/sparql/datatypes.py b/rdflib/plugins/sparql/datatypes.py
index 1e8475e0..bcb88942 100644
--- a/rdflib/plugins/sparql/datatypes.py
+++ b/rdflib/plugins/sparql/datatypes.py
@@ -11,6 +11,16 @@ XSD_DTs = set(
XSD.unsignedLong, XSD.unsignedInt, XSD.unsignedShort, XSD.unsignedByte,
XSD.positiveInteger, XSD.date))
+### adding dateTime datatypes
+
+XSD_DateTime_DTs = set(
+ (XSD.dateTime, XSD.date, XSD.time)
+)
+
+XSD_Duration_DTs = set(
+ (XSD.duration, XSD.dayTimeDuration, XSD.yearMonthDuration)
+)
+
_sub_types = {
XSD.integer: [
XSD.nonPositiveInteger, XSD.negativeInteger, XSD.long, XSD.int,
diff --git a/rdflib/plugins/sparql/operators.py b/rdflib/plugins/sparql/operators.py
index f904b82a..20ce9c03 100644
--- a/rdflib/plugins/sparql/operators.py
+++ b/rdflib/plugins/sparql/operators.py
@@ -23,6 +23,7 @@ import isodate
from rdflib.plugins.sparql.parserutils import CompValue, Expr
from rdflib.plugins.sparql.datatypes import XSD_DTs, type_promotion
+from rdflib.plugins.sparql.datatypes import XSD_DateTime_DTs, XSD_Duration_DTs
from rdflib import URIRef, BNode, Variable, Literal, XSD, RDF
from rdflib.term import Node
from six import text_type
@@ -762,25 +763,67 @@ def AdditiveExpression(e, ctx):
if other is None:
return expr
- res = numeric(expr)
+ # handling arithmetic(addition/subtraction) of dateTime, date, time
+ # and duration datatypes (if any)
+ if(expr.datatype in XSD_DateTime_DTs or expr.datatype in XSD_Duration_DTs):
+
+ res = dateTimeObjects(expr)
+ dt = expr.datatype
+
+ for op, term in zip(e.op, other):
+
+ # check if operation is datetime,date,time operation over
+ # another datetime,date,time datatype
+ if dt in XSD_DateTime_DTs and dt == term.datatype and op == '-':
+ # checking if there are more than one datetime operands -
+ # in that case it doesn't make sense for example
+ # ( dateTime1 - dateTime2 - dateTime3 ) is an invalid operation
+ if len(other) > 1:
+ error_message = "Can't evaluate multiple %r arguments"
+ raise SPARQLError(error_message, dt.datatype)
+ else:
+ n = dateTimeObjects(term)
+ res = calculateDuration(res, n)
+ return res
+
+ # datetime,date,time +/- duration,dayTimeDuration,yearMonthDuration
+ elif (dt in XSD_DateTime_DTs and term.datatype in XSD_Duration_DTs):
+ n = dateTimeObjects(term)
+ res = calculateFinalDateTime(res, dt, n, term.datatype, op)
+ return res
+
+ # duration,dayTimeDuration,yearMonthDuration + datetime,date,time
+ elif dt in XSD_Duration_DTs and term.datatype in XSD_DateTime_DTs:
+ if op == "+":
+ n = dateTimeObjects(term)
+ res = calculateFinalDateTime(res, dt, n, term.datatype, op)
+ return res
+
+ # rest are invalid types
+ else:
+ raise SPARQLError('Invalid DateTime Operations')
- dt = expr.datatype
+ # handling arithmetic(addition/subtraction) of numeric datatypes (if any)
+ else:
+ res = numeric(expr)
- for op, term in zip(e.op, other):
- n = numeric(term)
- if isinstance(n, Decimal) and isinstance(res, float):
- n = float(n)
- if isinstance(n, float) and isinstance(res, Decimal):
- res = float(res)
+ dt = expr.datatype
- dt = type_promotion(dt, term.datatype)
+ for op, term in zip(e.op, other):
+ n = numeric(term)
+ if isinstance(n, Decimal) and isinstance(res, float):
+ n = float(n)
+ if isinstance(n, float) and isinstance(res, Decimal):
+ res = float(res)
- if op == '+':
- res += n
- else:
- res -= n
+ dt = type_promotion(dt, term.datatype)
- return Literal(res, datatype=dt)
+ if op == '+':
+ res += n
+ else:
+ res -= n
+
+ return Literal(res, datatype=dt)
def RelationalExpression(e, ctx):
@@ -982,6 +1025,79 @@ def numeric(expr):
return expr.toPython()
+def dateTimeObjects(expr):
+ """
+ return a dataTime/date/time/duration/dayTimeDuration/yearMonthDuration python objects from a literal
+
+ """
+ return expr.toPython()
+
+
+def isCompatibleDateTimeDatatype(obj1, dt1, obj2, dt2):
+ """
+ returns a boolean indicating if first object is compatible
+ with operation(+/-) over second object.
+
+ """
+ if(dt1 == XSD.date):
+ if(dt2 == XSD.yearMonthDuration):
+ return True
+ elif(dt2 == XSD.dayTimeDuration or dt2 == XSD.Duration):
+ # checking if the dayTimeDuration has no Time Component
+ # else it wont be compatible with Date Literal
+ if("T" in str(obj2)):
+ return False
+ else:
+ return True
+
+ if(dt1 == XSD.time):
+ if(dt2 == XSD.yearMonthDuration):
+ return False
+ elif(dt2 == XSD.dayTimeDuration or dt2 == XSD.Duration):
+ # checking if the dayTimeDuration has no Date Component
+ # (by checking if the format is "PT...." )
+ # else it wont be compatible with Time Literal
+ if("T" == str(obj2)[1]):
+ return True
+ else:
+ return False
+
+ if(dt1 == XSD.dateTime):
+ # compatible with all
+ return True
+
+
+def calculateDuration(obj1, obj2):
+ """
+ returns the duration Literal between two datetime
+
+ """
+ date1 = obj1
+ date2 = obj2
+ difference = date1 - date2
+ return Literal(difference, datatype=XSD.duration)
+
+
+def calculateFinalDateTime(obj1, dt1, obj2, dt2, operation):
+ """
+ Calculates the final dateTime/date/time resultant after addition/
+ subtraction of duration/dayTimeDuration/yearMonthDuration
+ """
+
+ # checking compatibility of datatypes (duration types and date/time/dateTime)
+ if(isCompatibleDateTimeDatatype(obj1, dt1, obj2, dt2)):
+ # proceed
+ if(operation == "-"):
+ ans = obj1 - obj2
+ return Literal(ans, datatype=dt1)
+ else:
+ ans = obj1 + obj2
+ return Literal(ans, datatype=dt1)
+
+ else:
+ raise SPARQLError('Incompatible Data types to DateTime Operations')
+
+
def EBV(rt):
"""
* If the argument is a typed literal with a datatype of xsd:boolean,
diff --git a/test/test_sparql_datetime.py b/test/test_sparql_datetime.py
new file mode 100644
index 00000000..d4b2b676
--- /dev/null
+++ b/test/test_sparql_datetime.py
@@ -0,0 +1,219 @@
+from rdflib import Graph, URIRef, Literal, BNode,XSD
+from rdflib.plugins.sparql import prepareQuery
+from rdflib.compare import isomorphic
+import rdflib
+from nose.tools import eq_
+from pprint import pprint
+import io
+
+def test_dateTime_dateTime_subs_issue():
+ """
+ Test for query mentioned in the Issue #629
+ https://github.com/RDFLib/rdflib/issues/629
+
+ """
+
+ data1 = """
+ @prefix : <#>.
+ @prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>.
+ @prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>.
+ @prefix xsd: <http://www.w3.org/2001/XMLSchema#>.
+
+ :C a rdfs:Class.
+
+ :start a rdf:Property;
+ rdfs:domain :C;
+ rdfs:range xsd:dateTime.
+
+ :end a rdf:Property;
+ rdfs:domain :C;
+ rdfs:range xsd:dateTime.
+
+ :c1 a :C;
+ :start "2016-01-01T20:00:00"^^xsd:dateTime;
+ :end "2016-01-02T20:01:00"^^xsd:dateTime.
+
+ :c2 a :C;
+ :start "2016-01-01T20:05:00"^^xsd:dateTime;
+ :end "2016-01-01T20:30:00"^^xsd:dateTime.
+ """
+
+ graph1 = Graph()
+ f = io.StringIO(data1)
+ graph1.parse(f, format='n3')
+
+ result = graph1.query("""
+ SELECT ?c ?duration
+ WHERE {
+ ?c :start ?start;
+ :end ?end.
+ BIND(?end - ?start AS ?duration)
+ }
+ """)
+
+ answer=list(result)
+ answer=sorted(answer)
+ # expected result will be a list of 2 tuples
+ # FirstElement of these tuples will be a node with a path of directory of saved project
+ # Second Element while represent the actual durations
+
+ expectedFirstDuration=rdflib.term.Literal('P1DT1M', datatype=rdflib.term.URIRef('http://www.w3.org/2001/XMLSchema#duration'))
+ expectedSecondDuration=rdflib.term.Literal('PT25M', datatype=rdflib.term.URIRef('http://www.w3.org/2001/XMLSchema#duration'))
+
+
+ eq_(answer[0][1],expectedFirstDuration)
+ eq_(answer[1][1],expectedSecondDuration)
+
+
+def test_dateTime_duration_subs():
+ """
+ Test cases for subtraction operation between dateTime and duration
+
+ """
+ data = """
+ @prefix : <#>.
+ @prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>.
+ @prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>.
+ @prefix xsd: <http://www.w3.org/2001/XMLSchema#>.
+ """
+ graph = Graph()
+ f = io.StringIO(data)
+ graph.parse(f, format='n3')
+
+ ## 1st Test Case
+
+ result1 = graph.query("""
+ PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
+ SELECT (?d - ?duration AS ?next_year)
+ WHERE {
+ VALUES (?duration ?d) {
+ ("P1Y"^^xsd:yearMonthDuration"2019-05-28T12:14:45Z"^^xsd:dateTime)
+ ("P1Y"^^xsd:yearMonthDuration"2019-05-28"^^xsd:date)
+ }
+ }
+ """)
+ expected=[]
+ expected.append(rdflib.term.Literal('2018-05-28T12:14:45+00:00', datatype=rdflib.term.URIRef('http://www.w3.org/2001/XMLSchema#dateTime')))
+ expected.append(rdflib.term.Literal('2018-05-28', datatype=rdflib.term.URIRef('http://www.w3.org/2001/XMLSchema#date')))
+
+ eq_(list(result1)[0][0],expected[0])
+ eq_(list(result1)[1][0],expected[1])
+
+ ## 2nd Test Case
+
+ result2 = graph.query("""
+ PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
+ SELECT (?d - ?duration AS ?next_year)
+ WHERE {
+ VALUES (?duration ?d) {
+ ("P3DT1H15M"^^xsd:dayTimeDuration "2000-10-30T11:12:00"^^xsd:dateTime)
+ ("P3DT1H15M"^^xsd:dayTimeDuration "2000-10-30"^^xsd:date)
+ }
+ }
+ """)
+
+ expected=[]
+ expected.append(rdflib.term.Literal('2000-10-27T09:57:00', datatype=rdflib.term.URIRef('http://www.w3.org/2001/XMLSchema#dateTime')))
+ expected.append(rdflib.term.Literal('2000-10-27', datatype=rdflib.term.URIRef('http://www.w3.org/2001/XMLSchema#date')))
+
+ eq_(list(result2)[0][0],expected[0])
+ eq_(list(result2)[1][0],expected[1])
+
+
+
+def test_dateTime_duration_add():
+ """
+ Test cases for addition operation between dateTime and duration
+
+ """
+ data = """
+ @prefix : <#>.
+ @prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>.
+ @prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>.
+ @prefix xsd: <http://www.w3.org/2001/XMLSchema#>.
+ """
+ graph = Graph()
+ f = io.StringIO(data)
+ graph.parse(f, format='n3')
+
+ ## 1st Test case
+
+ result1 = graph.query("""
+ PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
+ SELECT (?d + ?duration AS ?next_year)
+ WHERE {
+ VALUES (?duration ?d) {
+ ("P1Y"^^xsd:yearMonthDuration"2019-05-28T12:14:45Z"^^xsd:dateTime)
+ ("P1Y"^^xsd:yearMonthDuration"2019-05-28"^^xsd:date)
+ }
+ }
+ """)
+
+ # print(list(result1))
+ expected=[]
+ expected.append(rdflib.term.Literal('2020-05-28T12:14:45+00:00', datatype=rdflib.term.URIRef('http://www.w3.org/2001/XMLSchema#dateTime')))
+ expected.append(rdflib.term.Literal('2020-05-28', datatype=rdflib.term.URIRef('http://www.w3.org/2001/XMLSchema#date')))
+
+ eq_(list(result1)[0][0],expected[0])
+ eq_(list(result1)[1][0],expected[1])
+
+ ## 2nd Test case
+
+ result2 = graph.query("""
+ PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
+ SELECT (?d + ?duration AS ?next_year)
+ WHERE {
+ VALUES (?duration ?d) {
+ ("P3DT1H15M"^^xsd:dayTimeDuration "2000-10-30T11:12:00"^^xsd:dateTime)
+ ("P3DT1H15M"^^xsd:dayTimeDuration "2000-10-30"^^xsd:date)
+ }
+ }
+ """)
+
+ # print(list(result2))
+ expected=[]
+ expected.append(rdflib.term.Literal('2000-11-02T12:27:00', datatype=rdflib.term.URIRef('http://www.w3.org/2001/XMLSchema#dateTime')))
+ expected.append(rdflib.term.Literal('2000-11-02', datatype=rdflib.term.URIRef('http://www.w3.org/2001/XMLSchema#date')))
+
+ eq_(list(result2)[0][0],expected[0])
+ eq_(list(result2)[1][0],expected[1])
+
+def test_dateTime_dateTime_subs():
+ """
+ Test cases for subtraction operation between dateTime and dateTime
+
+ """
+ data = """
+ @prefix : <#>.
+ @prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>.
+ @prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>.
+ @prefix xsd: <http://www.w3.org/2001/XMLSchema#>.
+ """
+
+ graph = Graph()
+ f = io.StringIO(data)
+ graph.parse(f, format='n3')
+
+ result1 = graph.query("""
+ PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
+ SELECT (?l - ?r AS ?duration)
+ WHERE {
+ VALUES (?l ?r) {
+ ("2000-10-30T06:12:00-05:00"^^xsd:dateTime "1999-11-28T09:00:00Z"^^xsd:dateTime)
+ ("2000-10-30"^^xsd:date"1999-11-28"^^xsd:date)
+ }
+ }
+ """)
+
+ expected1=rdflib.term.Literal('P337DT2H12M', datatype=rdflib.term.URIRef('http://www.w3.org/2001/XMLSchema#duration'))
+ expected2=rdflib.term.Literal('P337D', datatype=rdflib.term.URIRef('http://www.w3.org/2001/XMLSchema#duration'))
+
+ eq_(list(result1)[0][0],expected1)
+ eq_(list(result1)[1][0],expected2)
+
+
+if __name__ == '__main__':
+ import nose
+ nose.main(defaultTest=__name__)
+
+