Example 5: Literal values — AllegroGraph Python client 105.1.0 documentation

# Example 5: Literal values

The next example illustrates some variations on what we have seen so far. The example creates and asserts plain, data-typed, and language-tagged literals, and then conducts searches for them in three ways:

> - `getStatements()` search, which is an efficient way to match a single triple pattern.

> - SPARQL direct match, for efficient multi-pattern search.

> - SPARQL filter match, for sophisticated filtering such as performing range matches.

The `getStatements()` and SPARQL direct searches return exactly the datatype you ask for. The SPARQL filter queries can sometimes return multiple datatypes. This behavior will be one focus of this section.

If you are not explicit about the datatype of a value, either when asserting the triple or when writing a search pattern, AllegroGraph will deduce an appropriate datatype and use it. This is another focus of this section. This helpful behavior can sometimes surprise you with unanticipated results.

## Setup

We begin by obtaining a connection object and remvoing all existing data from the repository

    from franz.openrdf.connect import ag_connect

    conn = ag_connect("python-tutorial", create=True, clear=True)

For the sake of coding efficiency, it is good practice to create variables for namespace strings. We’ll use this namespace again and again in the following example. We have made the URIs in this example very short to keep the result displays compact.

    exns = "ex://"
    conn.setNamespace('ex', exns)

Namespace handling, including the `setNamespace()` method, is described in Example 11: Namespaces.

The example will use an artificial data set consisting of eight statements, each illustrating a different kind of literal. The subject will describe the nature of the literal used as the object, while the predicate will always be `<ex://p>`. The example shows how to enter a full URI string, or alternately how to combine a namespace with a local resource name.

    ex_integer = conn.createURI("ex://integer")
    ex_double = conn.createURI("ex://double")
    ex_int = conn.createURI("ex://int")
    ex_long = conn.createURI(
        namespace=exns, localname="long")
    ex_float = conn.createURI(
        namespace=exns, localname="float")
    ex_decimal = conn.createURI(
        namespace=exns, localname="decimal")
    ex_string = conn.createURI(
        namespace=exns, localname="string")
    ex_plain = conn.createURI(
        namespace=exns, localname="plain")

The predicate for all our statements will be the same.

    pred = conn.createURI(namespace=exns, localname="p")

Now we construct the objects, illustrating various kinds of RDF literals.

    from franz.openrdf.vocabulary.xmlschema import XMLSchema

    # Type will be XMLSchema.INTEGER
    forty_two = conn.createLiteral(42)
    # Type will be XMLSchema.DOUBLE
    forty_two_double = conn.createLiteral(42.0)
    forty_two_int = conn.createLiteral(
        '42', datatype=XMLSchema.INT)
    forty_two_long = conn.createLiteral(
        '42', datatype=XMLSchema.LONG)
    forty_two_float = conn.createLiteral(
        '42', datatype=XMLSchema.FLOAT)
    forty_two_decimal = conn.createLiteral(
        '42', datatype=XMLSchema.DECIMAL)
    forty_two_string = conn.createLiteral(
        '42', datatype=XMLSchema.STRING)
    # Creates a plain (untyped) literal.
    forty_two_plain = conn.createLiteral('42')

In four of these statements, we explicitly identified the datatype of the value in order to create an INT, a LONG, a FLOAT and a STRING. This is the best practice.

In three other statements, we just handed AllegroGraph numeric-looking values to see what it would do with them. As we will see in a moment, `42` creates an INTEGER, `42.0` becomes a DOUBLE, and `'42'` becomes a “plain” (untyped) literal value.

Warning

Note that plain literals are not quite the same thing as typed literal strings. A search for a plain literal will not always match a typed string, and vice versa.)

Now we will now assemble the URIs and values into `statements` (which are client-side triples):

    stmt1 = conn.createStatement(ex_integer, pred, forty_two)
    stmt2 = conn.createStatement(ex_double, pred, forty_two_double)
    stmt3 = conn.createStatement(ex_int, pred, forty_two_int)
    stmt4 = conn.createStatement(ex_long, pred, forty_two_long)
    stmt5 = conn.createStatement(ex_float, pred, forty_two_float)
    stmt6 = conn.createStatement(ex_decimal, pred, forty_two_decimal)
    stmt7 = conn.createStatement(ex_string, pred, forty_two_string)
    stmt8 = conn.createStatement(ex_plain, pred, forty_two_plain)

And then add the statements to the triple store on the AllegroGraph server. We can use either `add()` or `addStatement()` for this purpose.

    conn.add(stmt1)
    conn.add(stmt2)
    conn.add(stmt3)
    conn.addStatement(stmt4)
    conn.addStatement(stmt5)
    conn.addStatement(stmt6)
    conn.addStatement(stmt7)
    conn.addStatement(stmt8)

Now we’ll complete the round trip to see what triples we get back from these assertions. This is where we use `getStatements()` in this example to retrieve and display triples for us:

    print("Showing all triples using getStatements(). Eight matches.")
    conn.getStatements(None, pred, None, output=True,)

This code prints out all triples from the store. The `output` parameter causes the result to be printed on `stdout` (it is also possible to pass a file name or a file-like object as the value of this parameter to print to other destinations). Without `output` the result would have been returned as a `RepositoryResult` object.

Note that the retrieved literals are of eight types: an int (a 32-bit integer), an integer (arbitrary precision), a decimal, a long, a float, a double, a string, and a “plain literal.”

    Showing all triples using getStatements(). Eight matches.
    <ex://plain> <ex://p> "42" .
    <ex://string> <ex://p> "42"^^<http://www.w3.org/2001/XMLSchema#string> .
    <ex://decimal> <ex://p> "42.0"^^<http://www.w3.org/2001/XMLSchema#decimal> .
    <ex://float> <ex://p> "4.2E1"^^<http://www.w3.org/2001/XMLSchema#float> .
    <ex://long> <ex://p> "42"^^<http://www.w3.org/2001/XMLSchema#long> .
    <ex://int> <ex://p> "42"^^<http://www.w3.org/2001/XMLSchema#int> .
    <ex://double> <ex://p> "4.2E1"^^<http://www.w3.org/2001/XMLSchema#double> .
    <ex://integer> <ex://p> "42"^^<http://www.w3.org/2001/XMLSchema#integer> .

If you ask for a specific datatype, you will get it. If you leave the decision up to AllegroGraph, you might get something unexpected such as a plain literal value.

## Numeric literal values

### Matching 42 without explicit type

This section explores `getStatements()` and SPARQL matches against numeric triples. We ask AllegroGraph to find an untyped number, `42`.

    print('getStatements():')
    conn.getStatements(None, pred, 42, output=True)
    print()

    print('SPARQL direct match')
    conn.executeTupleQuery(
        'SELECT ?s WHERE {?s ?p 42 .}',
        output=True)
    print()

    print('SPARQL filter match')
    conn.executeTupleQuery(
        'SELECT ?s ?p ?o WHERE {?s ?p ?o . filter (?o = 42)}',
        output=True)
    print()

We use the `executeQuery()` method to retrieve the result of a SPARQL query. Like `getStatements()`, it accepts an `output` parameter that causes the result to be printed (instead of being returned as a `TupleQueryResult` object). Here is what the query methods discussed in this example would return:

    getStatements():
    <ex://integer> <ex://p> "42"^^<http://www.w3.org/2001/XMLSchema#integer> .

    SPARQL direct match
    --------------
    | s          |
    ==============
    | ex:integer |
    --------------

    SPARQL filter match
    -----------------------------
    | s          | p    | o     |
    =============================
    | ex:integer | ex:p | 42    |
    | ex:double  | ex:p | 4.2E1 |
    | ex:int     | ex:p | 42    |
    | ex:long    | ex:p | 42    |
    | ex:float   | ex:p | 4.2E1 |
    | ex:decimal | ex:p | 42.0  |
    -----------------------------

The `getStatements()` query returned triples containing longs only. The SPARQL direct match treated the numeric literal as if it had the type of `<http://www.w3.org/2001/XMLSchema#integer>` (see the SPARQL specification for information on how literals are parsed in queries) and returned only triples with exactly the same type. The SPARQL filter match, however, opened the doors to matches of multiple numeric types, and returned ints, floats, longs and doubles.

### Matching 42.0 without explicit type

Now we will try the same queries using `42.0`.

    print('getStatements():')
    conn.getStatements(None, pred, 42.0, output=True)
    print()

    print('SPARQL direct match')
    conn.executeTupleQuery(
        'SELECT ?s WHERE {?s <ex://p> 42.0 .}',
        output=True)
    print()

    print('SPARQL filter match')
    conn.executeTupleQuery(
        'SELECT ?s ?p ?o WHERE {?s ?p ?o . filter (?o = 42.0)}',
        output=True)
    print()

Here is what the query methods discussed in this example would return:

    getStatements():
    <ex://double> <ex://p> "4.2E1"^^<http://www.w3.org/2001/XMLSchema#double> .

    SPARQL direct match
    --------------
    | s          |
    ==============
    | ex:decimal |
    --------------

    SPARQL filter match
    -----------------------------
    | s          | p    | o     |
    =============================
    | ex:integer | ex:p | 42    |
    | ex:double  | ex:p | 4.2E1 |
    | ex:int     | ex:p | 42    |
    | ex:long    | ex:p | 42    |
    | ex:float   | ex:p | 4.2E1 |
    | ex:decimal | ex:p | 42.0  |
    -----------------------------

The `getStatements()` search returned a double but not the similar float. Direct SPARQL match treated `42.0` as a decimal (in accordance with the SPARQL specification). The filter match returned all numeric types that were equal to 42.0.

### Matching “42”^^xsd:int

The next section shows the results obtained when querying for a literal with explicitly specified type. Note that doing this with `getStatements()` requires passing in a `Literal` object, not a raw value.

    print('getStatements():')
    conn.getStatements(None, pred, forty_two_int, output=True)
    print()

    print('SPARQL direct match')
    conn.executeTupleQuery(
        'SELECT ?s WHERE {?s ?p "42"^^xsd:int .}',
        output=True)
    print()

    print('SPARQL filter match')
    conn.executeTupleQuery('''
        SELECT ?s ?p ?o WHERE {
           ?s ?p ?o .
           filter (?o = "42"^^xsd:int)
        }''',
        output=True)
    print()

Here is what the query methods discussed in this example would return:

    getStatements():
    <ex://int> <ex://p> "42"^^<http://www.w3.org/2001/XMLSchema#int> .

    SPARQL direct match
    ----------
    | s      |
    ==========
    | ex:int |
    ----------

    SPARQL filter match
    -----------------------------
    | s          | p    | o     |
    =============================
    | ex:integer | ex:p | 42    |
    | ex:double  | ex:p | 4.2E1 |
    | ex:int     | ex:p | 42    |
    | ex:long    | ex:p | 42    |
    | ex:float   | ex:p | 4.2E1 |
    | ex:decimal | ex:p | 42.0  |
    -----------------------------

We would get similar results when asking for any other typed literal (`forty_two_long`, `forty_two_float`, …).

## Numeric strings and plain literals

At this point we are transitioning from tests of numeric matches to tests of string matches, but there is a gray zone to be explored first. What do we find if we search for strings that contain numbers? In particular, what about “plain literal” values that are almost, but not quite, strings?

### Matching “42” as a typed string

Let’s start with a typed string literal.

    print('getStatements():')
    conn.getStatements(None, pred, forty_two_string, output=True)
    print()

    print('SPARQL direct match')
    conn.executeTupleQuery(
        'SELECT ?s WHERE {?s ?p "42"^^xsd:string .}',
        output=True)
    print()

    print('SPARQL filter match')
    conn.executeTupleQuery('''
        SELECT ?s ?p ?o WHERE {
           ?s ?p ?o .
           filter (?o = "42"^^xsd:string)
        }''',
        output=True)
    print()

Here are the results:

     getStatements():
     <ex://string> <ex://p> "42"^^<http://www.w3.org/2001/XMLSchema#string> .

     SPARQL direct match
     -------------
     | s         |
     =============
     | ex:plain  |
     | ex:string |
     -------------

     SPARQL filter match
     ------------------------------------------------------------------
     | s         | p    | o                                           |
     ==================================================================
     | ex:string | ex:p | 42^^http://www.w3.org/2001/XMLSchema#string |
     | ex:plain  | ex:p | 42                                          |
     ------------------------------------------------------------------

SPARQL matched both plain and literal strings, but a `getStatements()` search returned only typed matches. In both cases numeric literals were ignored.

### Matching “42” as a plain literal

If we try to match a plain (untyped) string value

    print('getStatements():')
    conn.getStatements(None, pred, forty_two_plain, output=True)
    print()

    print('SPARQL direct match')
    conn.executeTupleQuery(
        'SELECT ?s WHERE {?s ?p "42" .}',
        output=True)
    print()

    print('SPARQL filter match')
    conn.executeTupleQuery('''
        SELECT ?s ?p ?o WHERE {
           ?s ?p ?o .
           filter (?o = "42")
        }''',
        output=True)
    print()

We will get results consistent with that we saw in the typed case:

     getStatements():
     <ex://plain> <ex://p> "42" .

     SPARQL direct match
     -------------
     | s         |
     =============
     | ex:plain  |
     | ex:string |
     -------------

     SPARQL filter match
     ------------------------------------------------------------------
     | s         | p    | o                                           |
     ==================================================================
     | ex:string | ex:p | 42^^http://www.w3.org/2001/XMLSchema#string |
     | ex:plain  | ex:p | 42                                          |
     ------------------------------------------------------------------

In SPARQL both kinds of string literals were matched, while `getStatements()` returned only direct matches.

## Matching strings

In this section we’ll set up a variety of string triples and experiment with matching them using `getStatements()` and SPARQL.

Note

Example 12: Free Text indexing is a different topic. In this section we’re doing simple matches of whole strings.

### Sample data

For these examples we will use a different data set.

    name = conn.createURI('ex://name')
    upper_g = conn.createLiteral('Galadriel')
    lower_g = conn.createLiteral('galadriel')
    typed_g = conn.createLiteral('Galadriel', XMLSchema.STRING)
    lang_g = conn.createLiteral('Galadriel', language='sjn')
    upper_a = conn.createLiteral('Artanis')
    lower_a = conn.createLiteral('artanis')
    typed_a = conn.createLiteral('Artanis', XMLSchema.STRING)
    lang_a = conn.createLiteral('Artanis', language='qya')
    conn.addTriple('<ex://upper_g>', name, upper_g)
    conn.addTriple('<ex://lower_g>', name, lower_g)
    conn.addTriple('<ex://typed_g>', name, typed_g)
    conn.addTriple('<ex://lang_g>', name, lang_g)
    conn.addTriple('<ex://upper_a>', name, upper_a)
    conn.addTriple('<ex://lower_a>', name, lower_a)
    conn.addTriple('<ex://typed_a>', name, typed_a)
    conn.addTriple('<ex://lang_a>', name, lang_a)

We have two literals, each in four variants:

> - Upper case (plain literal)

> - Lower case (plain literal)

> - Typed

> - Tagged with a BCP47 language tag appropriate for its language (Quenya or Sindarin) according to the the registry

### Matching a plain string

We’ve seen a similar case when looking at matches for `"42"`, but this time we have more similar literals in the store.

    print('getStatements():')
    conn.getStatements(None, name, upper_g, output=True)
    print()

    print('SPARQL direct match')
    conn.executeTupleQuery(
        'SELECT ?s WHERE {?s <ex://name> "Galadriel" .}',
        output=True)
    print()

    print('SPARQL filter match')
    conn.executeTupleQuery('''
        SELECT ?s ?o WHERE {
           ?s <ex://name> ?o .
           filter (?o = "Galadriel")
        }''',
        output=True)
    print()

Here’s the result:

    getStatements():
    <ex://upper_g> <ex://name> "Galadriel" .

    SPARQL direct match
    --------------
    | s          |
    ==============
    | ex:typed_g |
    | ex:upper_g |
    --------------

    SPARQL filter match
    -------------------------------------------------------------------
    | s          | o                                                  |
    ===================================================================
    | ex:typed_g | Galadriel^^http://www.w3.org/2001/XMLSchema#string |
    | ex:upper_g | Galadriel                                          |
    -------------------------------------------------------------------

We can see that the match is case-sensitive and ignores the language-tagged literal in all cases. As usual `getStatements()` matches only the exact kind of literal that we’ve provided, while SPARQL is more liberal.

### Matching a language-tagged string

To retrieve the language-tagged variant we can ask for it explicitly:

    print('getStatements():')
    conn.getStatements(None, name, lang_g, output=True)
    print()

    print('SPARQL direct match')
    conn.executeTupleQuery(
        'SELECT ?s WHERE {?s <ex://name> "Galadriel"@sjn .}',
        output=True)
    print()

    print('SPARQL filter match')
    conn.executeTupleQuery('''
        SELECT ?s ?o WHERE {
           ?s <ex://name> ?o .
           filter (?o = "Galadriel"@sjn)
        }''',
        output=True)
    print()

Unsurprisingly we get exactly what we have asked for

    getStatements():
    <ex://lang_g> <ex://name> "Galadriel"@sjn .

    SPARQL direct match
    -------------
    | s         |
    =============
    | ex:lang_g |
    -------------

    SPARQL filter match
    -----------------------------
    | s         | o             |
    =============================
    | ex:lang_g | Galadriel@sjn |
    -----------------------------

You may be wondering how to perform a string match where language and capitalization don’t matter. You can do that with a SPARQL filter query using the `str()` function, which strips out the string portion of a literal, leaving behind the datatype or language tag. Then the `fn:lower-case()` function eliminates case issues:

    conn.executeTupleQuery('''
        SELECT ?s ?o WHERE {
           ?s <ex://name> ?o .
           filter (fn:lower-case(str(?o)) = "artanis")
        }''',
        output=True)

This query returns all variants of the selected literal

    -----------------------------------------------------------------
    | s          | o                                                |
    =================================================================
    | ex:lang_a  | Artanis@qya                                      |
    | ex:typed_a | Artanis^^http://www.w3.org/2001/XMLSchema#string |
    | ex:lower_a | artanis                                          |
    | ex:upper_a | Artanis                                          |
    -----------------------------------------------------------------

Remember that the SPARQL `filter` queries are powerful, but they are also the slowest queries. SPARQL direct queries and getStatements() queries are faster.

## Booleans

Boolean values in SPARQL are represented by literals of type `<http://www.w3.org/2001/XMLSchema#boolean>`. There are two ways to create such literals in Python:

> 1. From corresponding Python boolean values (`True` and `FAlse`):

>        true1 = conn.createLiteral(True)
>        false1 = conn.createLiteral(False)

> 2. By creating a typed literal with the value of `"true"` or `"false"`. The type must be `xsd:boolean`:

>        true2 = conn.createLiteral("true", datatype=XMLSchema.BOOLEAN)
>        false2 = conn.createLiteral("false", datatype=XMLSchema.BOOLEAN)

Both ways of creating boolean literals produce equivalent results:

    print(true1)
    print(true2)

As we can see the literals are identical.

    "true"^^<http://www.w3.org/2001/XMLSchema#boolean>
    "true"^^<http://www.w3.org/2001/XMLSchema#boolean>

Let us add some boolean data to the store:

    conn.addData("""
        <ex://f> <ex://p>
            "false"^^<http://www.w3.org/2001/XMLSchema#boolean> .
        # In Turtle 'true' is the same as '"true"^^xsd:boolean"'
        <ex://t> <ex://p> true .
    """)

When querying for boolean values using SPARQL one can use the literals `true` and `false` as a shorthand for `"true"^^<http://www.w3.org/2001/XMLSchema#boolean>` and `"false"^^<http://www.w3.org/2001/XMLSchema#boolean>`. The code below illustrates various ways of querying for boolean values:

    print('getStatements():')
    conn.getStatements(None, None, true1, output=True)
    print()

    print('SPARQL direct match (true)')
    conn.executeTupleQuery(
        'SELECT ?s WHERE {?s ?p true.}',
        output=True)
    print()

    print('SPARQL direct match ("false"^^xsd:boolean)')
    conn.executeTupleQuery(
        'SELECT ?s WHERE {?s ?p "false"^^xsd:boolean .}',
        output=True)
    print()

    print('SPARQL filter match ("false"^^xsd:boolean)')
    conn.executeTupleQuery('''
        SELECT ?s ?o WHERE {
           ?s ?p ?o .
           filter (?o = "false"^^xsd:boolean)
        }''',
        output=True)
    print()

Here’s the output from that script:

    getStatements():
    <ex://t> <ex://p> "true"^^<http://www.w3.org/2001/XMLSchema#boolean> .

    SPARQL direct match (true)
    --------
    | s    |
    ========
    | ex:t |
    --------

    SPARQL direct match ("false"^^xsd:boolean)
    --------
    | s    |
    ========
    | ex:f |
    --------

    SPARQL filter match ("false"^^xsd:boolean)
    ----------------
    | s    | o     |
    ================
    | ex:f | false |
    ----------------

## Dates and times

SPARQL represents dates and times using three literal types: `xsd:date`, `xsd:time` and `xsd:dateTime`. These can be created either explicitly from strings in the ISO 8601 format or from Python `datetime.date`, `datetime.time` and `datetime.datetime` values.

Let’s create a few sample literals:

    from datetime import date, time, datetime
    import iso8601

    d = conn.createLiteral(date(1944, 8, 1))
    t = conn.createLiteral(time(15, 0, 0))
    dt = conn.createLiteral('1944-08-01T17:00:00+02:00',
                            datatype=XMLSchema.DATETIME)

Creating `time` and `datetime` literals from Python values can yield somewhat unexpected results if time zones are involved:

    surprise = conn.createLiteral(iso8601.parse_date(
        '1944-08-01T17:00:00+02:00'))
    # Should be the same...
    print(dt)
    print(surprise)

The output is

    "1944-08-01T17:00:00+02:00"^^<http://www.w3.org/2001/XMLSchema#dateTime>
    "1944-08-01T15:00:00Z"^^<http://www.w3.org/2001/XMLSchema#dateTime>

The time has been converted to UTC. While both `dt` and `surprise` refer to the same moment in time, this conversion might still lead to problems if the user is not aware that it takes place.

We will now add the newly created literals to the store:

    conn.addTriple('<ex://d>', '<ex://p>', d)
    conn.addTriple('<ex://t>', '<ex://p>', t)
    conn.addTriple('<ex://dt>', '<ex://p>', dt)

The following sections illustrate how date and time values behave during queries.

### Matching dates

Let’s try the usual mix of query methods and see what is returned:

    print('getStatements():')
    conn.getStatements(None, None, d, output=True)
    print()

    print('SPARQL direct match')
    conn.executeTupleQuery(
        'SELECT ?s WHERE {?s ?p "1944-08-01"^^xsd:date .}',
        output=True)
    print()

    print('SPARQL filter match')
    conn.executeTupleQuery('''
        SELECT ?s ?o WHERE {
           ?s ?p ?o .
           filter (?o = "1944-08-01"^^xsd:date)
        }''',
        output=True)
    print()

The result is not surprising. It is worth noting that the `datetime` value has not been returned, even though it refers to the same date.

     getStatements():
     <ex://d> <ex://p> "1944-08-01"^^<http://www.w3.org/2001/XMLSchema#date> .

     SPARQL direct match
     --------
     | s    |
     ========
     | ex:d |
     --------

     SPARQL filter match
     ---------------------
     | s    | o          |
     =====================
     | ex:d | 1944-08-01 |
     ---------------------

### Matching times

Times can be queried in a similar fashion.

    print('getStatements():')
    conn.getStatements(None, None, t, output=True)
    print()

    print('SPARQL direct match')
    conn.executeTupleQuery(
        'SELECT ?s WHERE {?s ?p "15:00:00Z"^^xsd:time .}',
        output=True)
    print()

    print('SPARQL filter match')
    conn.executeTupleQuery('''
        SELECT ?s ?o WHERE {
           ?s ?p ?o .
           filter (?o = "15:00:00Z"^^xsd:time)
        }''',
        output=True)
    print()

Again, only the value of the appropriate type is returned.

    getStatements():
    <ex://t> <ex://p> "15:00:00Z"^^<http://www.w3.org/2001/XMLSchema#time> .

    SPARQL direct match
    --------
    | s    |
    ========
    | ex:t |
    --------

    SPARQL filter match
    --------------------
    | s    | o         |
    ====================
    | ex:t | 15:00:00Z |
    --------------------

### Matching datetimes

Datetimes work just like times and dates:

    print('getStatements():')
    conn.getStatements(None, None, dt, output=True)
    print()

    print('SPARQL direct match')
    conn.executeTupleQuery('''
        SELECT ?s WHERE {
           ?s ?p "1944-08-01T17:00:00+02:00"^^xsd:dateTime .
        }''',
        output=True)
    print()

    print('SPARQL filter match')
    conn.executeTupleQuery('''
        SELECT ?s ?o WHERE {
           ?s ?p ?o .
           filter (?o = "1944-08-01T17:00:00+02:00"^^xsd:dateTime)
        }''',
        output=True)
    print()

The result:

    getStatements():
    <ex://dt> <ex://p> "1944-08-01T17:00:00+02:00"^^<http://www.w3.org/2001/XMLSchema#dateTime> .

    SPARQL direct match
    ---------
    | s     |
    =========
    | ex:dt |
    ---------

    SPARQL filter match
    -------------------------------------
    | s     | o                         |
    =====================================
    | ex:dt | 1944-08-01T17:00:00+02:00 |
    -------------------------------------

### Matching datetimes with offsets

We saw that times created from Python values are converted to UTC. So what happens when we query for Zulu time, while the value in the store is still in CEST?

    zulu = conn.createLiteral("1944-08-01T15:00:00Z",
                              datatype=XMLSchema.DATETIME)
    print('getStatements():')
    conn.getStatements(None, None, zulu, output=True)
    print()

    print('SPARQL direct match')
    conn.executeTupleQuery('''
        SELECT ?s WHERE {
           ?s ?p "1944-08-01T15:00:00Z"^^xsd:dateTime .
        }''',
        output=True)
    print()

    print('SPARQL filter match')
    conn.executeTupleQuery('''
        SELECT ?s ?o WHERE {
           ?s ?p ?o .
           filter (?o = "1944-08-01T15:00:00Z"^^xsd:dateTime)
        }''',
        output=True)
    print()

AllegroGraph still finds our value when using SPARQL

    getStatements():

    SPARQL direct match
    ---------
    | s     |
    =========
    | ex:dt |
    ---------

    SPARQL filter match
    -------------------------------------
    | s     | o                         |
    =====================================
    | ex:dt | 1944-08-01T17:00:00+02:00 |
    -------------------------------------

When evaluating SPARQL queries AllegroGraph treats `datetime` objects that refer to the same point in time as equivalent, regardless of the timezone used in their representation. `getStatements()` performs exact matching, so will not return a value with different timezone.