diff options
| author | Akash-Sharma-1 <akashthegreatlegend@gmail.com> | 2020-05-23 20:48:12 +0530 |
|---|---|---|
| committer | Akash-Sharma-1 <akashthegreatlegend@gmail.com> | 2020-05-23 20:48:12 +0530 |
| commit | dba3a497b8824935c1bb5040d29087bc7120746f (patch) | |
| tree | 045d1057c53c99618867262db837f01e40b739fa | |
| parent | 231bc6d238ff6e4d6eaae9d65a7d96b953136ae4 (diff) | |
| download | rdflib-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.py | 10 | ||||
| -rw-r--r-- | rdflib/plugins/sparql/operators.py | 144 | ||||
| -rw-r--r-- | test/test_sparql_datetime.py | 219 |
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__) + + |
