Repository: RDFLib/rdflib

Issue:
Turtle de/serialize incorrectly interprets gYear
In 5.0.0, when a `gYear` is parsed and then re-serialised, from any format to any other format, RDFlib adds "-01-01" to the literal so, for example, `"1982"^^xsd:gYear` goes to `"1982-01-01"^^xsd:gYear` which is invalid.

Testing code:

```python
from rdflib import Graph

data =  """
        PREFIX sdo: <https://schema.org/>
        PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>

        <x:> sdo:startDate "1982"^^xsd:gYear .
        """

g = Graph().parse(data=data, format="ttl")
assert "1982-01-01" not in g.serialize(format="ttl").decode()


data2 = """<?xml version="1.0" encoding="UTF-8"?>
<rdf:RDF
   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
   xmlns:sdo="https://schema.org/"
>
  <rdf:Description rdf:about="x:">
    <sdo:startDate rdf:datatype="http://www.w3.org/2001/XMLSchema#gYear">1982</sdo:startDate>
  </rdf:Description>
</rdf:RDF>"""

g2 = Graph().parse(data=data2, format="xml")
assert "1982-01-01" not in g2.serialize(format="xml").decode()
```

Discussion:
coveralls: 
[![Coverage Status](https://coveralls.io/builds/39710288/badge)](https://coveralls.io/builds/39710288)

Coverage decreased (-0.01%) to 75.511% when pulling **fc1cece229382f6875307497dbf51416a298aee0 on iafork:fix_gYearDate** into **8d592a2e3bf7f63da52f9326f3f9d037e7d6ae51 on RDFLib:master**.

coveralls: 
[![Coverage Status](https://coveralls.io/builds/39710288/badge)](https://coveralls.io/builds/39710288)

Coverage decreased (-0.01%) to 75.511% when pulling **fc1cece229382f6875307497dbf51416a298aee0 on iafork:fix_gYearDate** into **8d592a2e3bf7f63da52f9326f3f9d037e7d6ae51 on RDFLib:master**.

coveralls: 
[![Coverage Status](https://coveralls.io/builds/39710288/badge)](https://coveralls.io/builds/39710288)

Coverage decreased (-0.01%) to 75.511% when pulling **fc1cece229382f6875307497dbf51416a298aee0 on iafork:fix_gYearDate** into **8d592a2e3bf7f63da52f9326f3f9d037e7d6ae51 on RDFLib:master**.

coveralls: 
[![Coverage Status](https://coveralls.io/builds/39710288/badge)](https://coveralls.io/builds/39710288)

Coverage decreased (-0.01%) to 75.511% when pulling **fc1cece229382f6875307497dbf51416a298aee0 on iafork:fix_gYearDate** into **8d592a2e3bf7f63da52f9326f3f9d037e7d6ae51 on RDFLib:master**.

coveralls: 
[![Coverage Status](https://coveralls.io/builds/39710288/badge)](https://coveralls.io/builds/39710288)

Coverage decreased (-0.01%) to 75.511% when pulling **fc1cece229382f6875307497dbf51416a298aee0 on iafork:fix_gYearDate** into **8d592a2e3bf7f63da52f9326f3f9d037e7d6ae51 on RDFLib:master**.

coveralls: 
[![Coverage Status](https://coveralls.io/builds/39710288/badge)](https://coveralls.io/builds/39710288)

Coverage decreased (-0.01%) to 75.511% when pulling **fc1cece229382f6875307497dbf51416a298aee0 on iafork:fix_gYearDate** into **8d592a2e3bf7f63da52f9326f3f9d037e7d6ae51 on RDFLib:master**.


Patch:
diff --git a/rdflib/plugins/sparql/results/txtresults.py b/rdflib/plugins/sparql/results/txtresults.py
index baa5316b..426dd9a1 100644
--- a/rdflib/plugins/sparql/results/txtresults.py
+++ b/rdflib/plugins/sparql/results/txtresults.py
@@ -43,7 +43,7 @@ class TXTResultSerializer(ResultSerializer):
             return "(no results)\n"
         else:
 
-            keys = self.result.vars
+            keys = sorted(self.result.vars)
             maxlen = [0] * len(keys)
             b = [
                 [_termString(r[k], namespace_manager) for k in keys]
diff --git a/rdflib/term.py b/rdflib/term.py
index 0f627e69..d9e06d82 100644
--- a/rdflib/term.py
+++ b/rdflib/term.py
@@ -1437,6 +1437,8 @@ _XSD_YEARMONTHDURATION = URIRef(_XSD_PFX + "yearMonthDuration")
 _OWL_RATIONAL = URIRef("http://www.w3.org/2002/07/owl#rational")
 _XSD_B64BINARY = URIRef(_XSD_PFX + "base64Binary")
 _XSD_HEXBINARY = URIRef(_XSD_PFX + "hexBinary")
+_XSD_GYEAR = URIRef(_XSD_PFX + "gYear")
+_XSD_GYEARMONTH = URIRef(_XSD_PFX + "gYearMonth")
 # TODO: gYearMonth, gYear, gMonthDay, gDay, gMonth
 
 _NUMERIC_LITERAL_TYPES = (
@@ -1558,6 +1560,8 @@ _GenericPythonToXSDRules = [
 ]
 
 _SpecificPythonToXSDRules = [
+    ((date, _XSD_GYEAR), lambda val: val.strftime("%04Y")),
+    ((date, _XSD_GYEARMONTH), lambda val: val.strftime("%04Y-%02m")),
     ((str, _XSD_HEXBINARY), hexlify),
     ((bytes, _XSD_HEXBINARY), hexlify),
     ((str, _XSD_B64BINARY), b64encode),


Test patch:
diff --git a/test/test_literal.py b/test/test_literal.py
index 656bfb10..98cd18a8 100644
--- a/test/test_literal.py
+++ b/test/test_literal.py
@@ -1,7 +1,9 @@
 import unittest
+import datetime
 
 import rdflib  # needed for eval(repr(...)) below
 from rdflib.term import Literal, URIRef, _XSD_DOUBLE, bind, _XSD_BOOLEAN
+from rdflib.namespace import XSD
 
 
 def uformat(s):
@@ -188,5 +190,79 @@ class TestBindings(unittest.TestCase):
         self.assertEqual(specific_l.datatype, datatype)
 
 
+class TestXsdLiterals(unittest.TestCase):
+    def test_make_literals(self):
+        """
+        Tests literal construction.
+        """
+        inputs = [
+            # these literals do not get conerted to python types
+            ("ABCD", XSD.integer, None),
+            ("ABCD", XSD.gYear, None),
+            ("-10000", XSD.gYear, None),
+            ("-1921-00", XSD.gYearMonth, None),
+            ("1921-00", XSD.gMonthDay, None),
+            ("1921-13", XSD.gMonthDay, None),
+            ("-1921-00", XSD.gMonthDay, None),
+            ("10", XSD.gDay, None),
+            ("-1", XSD.gDay, None),
+            ("0000", XSD.gYear, None),
+            ("0000-00-00", XSD.date, None),
+            ("NOT A VALID HEX STRING", XSD.hexBinary, None),
+            ("NOT A VALID BASE64 STRING", XSD.base64Binary, None),
+            # these literals get converted to python types
+            ("1921-05-01", XSD.date, datetime.date),
+            ("1921-05-01T00:00:00", XSD.dateTime, datetime.datetime),
+            ("1921-05", XSD.gYearMonth, datetime.date),
+            ("0001-01", XSD.gYearMonth, datetime.date),
+            ("0001-12", XSD.gYearMonth, datetime.date),
+            ("2002-01", XSD.gYearMonth, datetime.date),
+            ("9999-01", XSD.gYearMonth, datetime.date),
+            ("9999-12", XSD.gYearMonth, datetime.date),
+            ("1921", XSD.gYear, datetime.date),
+            ("2000", XSD.gYear, datetime.date),
+            ("0001", XSD.gYear, datetime.date),
+            ("9999", XSD.gYear, datetime.date),
+            ("1982", XSD.gYear, datetime.date),
+            ("2002", XSD.gYear, datetime.date),
+            ("1921-05-01T00:00:00+00:30", XSD.dateTime, datetime.datetime),
+            ("1921-05-01T00:00:00-00:30", XSD.dateTime, datetime.datetime),
+            ("abcdef0123", XSD.hexBinary, bytes),
+            ("", XSD.hexBinary, bytes),
+            ("UkRGTGli", XSD.base64Binary, bytes),
+            ("", XSD.base64Binary, bytes),
+        ]
+        self.check_make_literals(inputs)
+
+    @unittest.expectedFailure
+    def test_make_literals_ki(self):
+        """
+        Known issues with literal construction.
+        """
+        inputs = [
+            ("1921-01Z", XSD.gYearMonth, datetime.date),
+            ("1921Z", XSD.gYear, datetime.date),
+            ("1921-00", XSD.gYearMonth, datetime.date),
+            ("1921-05-01Z", XSD.date, datetime.date),
+            ("1921-05-01+00:30", XSD.date, datetime.date),
+            ("1921-05-01+00:30", XSD.date, datetime.date),
+            ("1921-05-01+00:00", XSD.date, datetime.date),
+            ("1921-05-01+00:00", XSD.date, datetime.date),
+            ("1921-05-01T00:00:00Z", XSD.dateTime, datetime.datetime),
+        ]
+        self.check_make_literals(inputs)
+
+    def check_make_literals(self, inputs):
+        for literal_pair in inputs:
+            (lexical, type, value_cls) = literal_pair
+            with self.subTest(f"tesing {literal_pair}"):
+                literal = Literal(lexical, datatype=type)
+                if value_cls is not None:
+                    self.assertIsInstance(literal.value, value_cls)
+                else:
+                    self.assertIsNone(literal.value)
+                self.assertEqual(lexical, f"{literal}")
+
+
 if __name__ == "__main__":
     unittest.main()
diff --git a/test/test_parser.py b/test/test_parser.py
index e337969c..da38ea91 100644
--- a/test/test_parser.py
+++ b/test/test_parser.py
@@ -44,5 +44,47 @@ class ParserTestCase(unittest.TestCase):
         self.assertEqual(type, RDFS.Class)
 
 
+class TestGitHubIssues(unittest.TestCase):
+    def test_issue_1228_a(self):
+        data = """
+        PREFIX sdo: <https://schema.org/>
+        PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
+
+        <x:> sdo:startDate "1982"^^xsd:gYear .
+        """
+
+        g = Graph().parse(data=data, format="ttl")
+        self.assertNotIn("1982-01-01", data)
+        self.assertNotIn("1982-01-01", g.serialize(format="ttl"))
+
+    def test_issue_1228_b(self):
+        data = """\
+<?xml version="1.0" encoding="UTF-8"?>
+    <rdf:RDF
+    xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+    xmlns:sdo="https://schema.org/"
+    >
+    <rdf:Description rdf:about="x:">
+        <sdo:startDate
+            rdf:datatype="http://www.w3.org/2001/XMLSchema#gYear">1982</sdo:startDate>
+    </rdf:Description>
+</rdf:RDF>"""
+
+        g = Graph().parse(data=data, format="xml")
+        self.assertNotIn("1982-01-01", data)
+        self.assertNotIn("1982-01-01", g.serialize(format="xml"))
+
+    def test_issue_806(self):
+        data = (
+            "<http://dbpedia.org/resource/Australian_Labor_Party> "
+            "<http://dbpedia.org/ontology/formationYear> "
+            '"1891"^^<http://www.w3.org/2001/XMLSchema#gYear> .'
+        )
+        g = Graph()
+        g.parse(data=data, format="nt")
+        for _, _, o in g:
+            self.assertNotIn("1891-01-01", o.n3())
+
+
 if __name__ == "__main__":
     unittest.main()
diff --git a/test/test_sparql.py b/test/test_sparql.py
index 6454e7ef..fa548146 100644
--- a/test/test_sparql.py
+++ b/test/test_sparql.py
@@ -1,8 +1,8 @@
 from rdflib.plugins.sparql import sparql, prepareQuery
 from rdflib import Graph, URIRef, Literal, BNode, ConjunctiveGraph
 from rdflib.namespace import Namespace, RDF, RDFS
+from rdflib.plugins.sparql import prepareQuery
 from rdflib.compare import isomorphic
-from rdflib.term import Variable
 
 from nose.tools import eq_
 
@@ -163,43 +163,6 @@ def test_named_filter_graph_query():
                         initNs={'ex': ex})) == [(Literal('Susan'),)]
 
 
-def test_txtresult():
-    data = f"""\
-    @prefix rdfs: <{str(RDFS)}> .
-    rdfs:Class a rdfs:Class ;
-        rdfs:isDefinedBy <http://www.w3.org/2000/01/rdf-schema#> ;
-        rdfs:label "Class" ;
-        rdfs:comment "The class of classes." ;
-        rdfs:subClassOf rdfs:Resource .
-    """
-    graph = Graph()
-    graph.parse(data=data, format="turtle")
-    result = graph.query(
-        """\
-    SELECT ?class ?superClass ?label ?comment WHERE {
-        ?class rdf:type rdfs:Class.
-        ?class rdfs:label ?label.
-        ?class rdfs:comment ?comment.
-        ?class rdfs:subClassOf ?superClass.
-    }
-    """
-    )
-    vars = [
-        Variable("class"),
-        Variable("superClass"),
-        Variable("label"),
-        Variable("comment"),
-    ]
-    assert result.type == "SELECT"
-    assert len(result) == 1
-    assert result.vars == vars
-    txtresult = result.serialize(format="txt")
-    lines = txtresult.decode().splitlines()
-    assert len(lines) == 3
-    vars_check = [Variable(var.strip()) for var in lines[0].split("|")]
-    assert vars_check == vars
-
-
 if __name__ == "__main__":
     import nose