Repository: tefra/xsdata

Issue:
Unable to parse time with nanoseconds
I have documents containing XmlTime fields with nanoseconds, e.g. `03:35:25.482845071Z`. Currently, `DateTimeParser` can't parse such values. I think it would be nice to strip time to microseconds instead of returning the value as a string.

Discussion:
sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!&nbsp; &nbsp; [![Quality Gate passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/passed-16px.png 'Quality Gate passed')](https://sonarcloud.io/dashboard?id=tefra_xsdata&pullRequest=679)

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=679&resolved=false&types=BUG) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=679&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=679&resolved=false&types=BUG)  
[![Vulnerability](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability-16px.png 'Vulnerability')](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=679&resolved=false&types=VULNERABILITY) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=679&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=679&resolved=false&types=VULNERABILITY)  
[![Security Hotspot](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot-16px.png 'Security Hotspot')](https://sonarcloud.io/project/security_hotspots?id=tefra_xsdata&pullRequest=679&resolved=false&types=SECURITY_HOTSPOT) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/security_hotspots?id=tefra_xsdata&pullRequest=679&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=tefra_xsdata&pullRequest=679&resolved=false&types=SECURITY_HOTSPOT)  
[![Code Smell](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell-16px.png 'Code Smell')](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=679&resolved=false&types=CODE_SMELL) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=679&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=679&resolved=false&types=CODE_SMELL)

[![No Coverage information](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/NoCoverageInfo-16px.png 'No Coverage information')](https://sonarcloud.io/component_measures?id=tefra_xsdata&pullRequest=679) No Coverage information  
[![0.0%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/3-16px.png '0.0%')](https://sonarcloud.io/component_measures?id=tefra_xsdata&pullRequest=679&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=tefra_xsdata&pullRequest=679&metric=new_duplicated_lines_density&view=list)


codecov[bot]: # [Codecov](https://codecov.io/gh/tefra/xsdata/pull/679?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou) Report
> Merging [#679](https://codecov.io/gh/tefra/xsdata/pull/679?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou) (5ee172c) into [master](https://codecov.io/gh/tefra/xsdata/commit/f83794875914223b76db0e731b7eec1ad078e429?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou) (f837948) will **decrease** coverage by `0.08%`.
> The diff coverage is `100.00%`.

```diff
@@             Coverage Diff             @@
##            master     #679      +/-   ##
===========================================
- Coverage   100.00%   99.91%   -0.09%     
===========================================
  Files           95       95              
  Lines         8259     8241      -18     
  Branches      1589     1580       -9     
===========================================
- Hits          8259     8234      -25     
- Misses           0        4       +4     
- Partials         0        3       +3     
```


| [Impacted Files](https://codecov.io/gh/tefra/xsdata/pull/679?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou) | Coverage Δ | |
|---|---|---|
| [xsdata/models/datatype.py](https://codecov.io/gh/tefra/xsdata/pull/679/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou#diff-eHNkYXRhL21vZGVscy9kYXRhdHlwZS5weQ==) | `100.00% <100.00%> (ø)` | |
| [xsdata/utils/dates.py](https://codecov.io/gh/tefra/xsdata/pull/679/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou#diff-eHNkYXRhL3V0aWxzL2RhdGVzLnB5) | `100.00% <100.00%> (ø)` | |
| [xsdata/utils/hooks.py](https://codecov.io/gh/tefra/xsdata/pull/679/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou#diff-eHNkYXRhL3V0aWxzL2hvb2tzLnB5) | `81.81% <0.00%> (-18.19%)` | :arrow_down: |
| [xsdata/formats/dataclass/typing.py](https://codecov.io/gh/tefra/xsdata/pull/679/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou#diff-eHNkYXRhL2Zvcm1hdHMvZGF0YWNsYXNzL3R5cGluZy5weQ==) | `95.32% <0.00%> (-4.68%)` | :arrow_down: |
| [xsdata/models/xsd.py](https://codecov.io/gh/tefra/xsdata/pull/679/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou#diff-eHNkYXRhL21vZGVscy94c2QucHk=) | `100.00% <0.00%> (ø)` | |
| [xsdata/models/wsdl.py](https://codecov.io/gh/tefra/xsdata/pull/679/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou#diff-eHNkYXRhL21vZGVscy93c2RsLnB5) | `100.00% <0.00%> (ø)` | |
| [xsdata/models/config.py](https://codecov.io/gh/tefra/xsdata/pull/679/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou#diff-eHNkYXRhL21vZGVscy9jb25maWcucHk=) | `100.00% <0.00%> (ø)` | |
| [xsdata/models/mixins.py](https://codecov.io/gh/tefra/xsdata/pull/679/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou#diff-eHNkYXRhL21vZGVscy9taXhpbnMucHk=) | `100.00% <0.00%> (ø)` | |
| [xsdata/utils/testing.py](https://codecov.io/gh/tefra/xsdata/pull/679/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou#diff-eHNkYXRhL3V0aWxzL3Rlc3RpbmcucHk=) | `100.00% <0.00%> (ø)` | |
| [xsdata/codegen/mixins.py](https://codecov.io/gh/tefra/xsdata/pull/679/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou#diff-eHNkYXRhL2NvZGVnZW4vbWl4aW5zLnB5) | `100.00% <0.00%> (ø)` | |
| ... and [3 more](https://codecov.io/gh/tefra/xsdata/pull/679/diff?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/tefra/xsdata/pull/679?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/tefra/xsdata/pull/679?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou). Last update [f837948...5ee172c](https://codecov.io/gh/tefra/xsdata/pull/679?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou).

sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!&nbsp; &nbsp; [![Quality Gate passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/passed-16px.png 'Quality Gate passed')](https://sonarcloud.io/dashboard?id=tefra_xsdata&pullRequest=679)

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=679&resolved=false&types=BUG) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=679&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=679&resolved=false&types=BUG)  
[![Vulnerability](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability-16px.png 'Vulnerability')](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=679&resolved=false&types=VULNERABILITY) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=679&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=679&resolved=false&types=VULNERABILITY)  
[![Security Hotspot](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot-16px.png 'Security Hotspot')](https://sonarcloud.io/project/security_hotspots?id=tefra_xsdata&pullRequest=679&resolved=false&types=SECURITY_HOTSPOT) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/security_hotspots?id=tefra_xsdata&pullRequest=679&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=tefra_xsdata&pullRequest=679&resolved=false&types=SECURITY_HOTSPOT)  
[![Code Smell](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell-16px.png 'Code Smell')](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=679&resolved=false&types=CODE_SMELL) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=679&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=679&resolved=false&types=CODE_SMELL)

[![No Coverage information](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/NoCoverageInfo-16px.png 'No Coverage information')](https://sonarcloud.io/component_measures?id=tefra_xsdata&pullRequest=679) No Coverage information  
[![0.0%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/3-16px.png '0.0%')](https://sonarcloud.io/component_measures?id=tefra_xsdata&pullRequest=679&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=tefra_xsdata&pullRequest=679&metric=new_duplicated_lines_density&view=list)



Patch:
diff --git a/docs/data-types.rst b/docs/data-types.rst
index b0cd69a3..66ccc2cf 100644
--- a/docs/data-types.rst
+++ b/docs/data-types.rst
@@ -127,7 +127,7 @@ whenever possible.
     ...
     >>> obj = Dates(
     ...     date=XmlDate(2020, 1, 6, 120),
-    ...     time=XmlTime(18, 15, 30, 123000, 0),
+    ...     time=XmlTime(18, 15, 30, 123000000, 0),
     ...     datetime=XmlDateTime(2020, 1, 6, 18, 15, 30)
     ... )
     ...
diff --git a/xsdata/models/datatype.py b/xsdata/models/datatype.py
index e48f0af9..4345237a 100644
--- a/xsdata/models/datatype.py
+++ b/xsdata/models/datatype.py
@@ -29,7 +29,7 @@ DS_MONTH = 2629743
 DS_DAY = 86400
 DS_HOUR = 3600
 DS_MINUTE = 60
-DS_MICROSECOND = 0.000001
+DS_FRACTIONAL_SECOND = 0.000000001
 DS_OFFSET = -60
 
 
@@ -150,7 +150,7 @@ class XmlDateTime(NamedTuple):
     :param hour: Unsigned integer between 0-24
     :param minute: Unsigned integer between 0-59
     :param second: Unsigned integer between 0-59
-    :param microsecond: Unsigned integer between 0-999999
+    :param fractional_second: Unsigned integer between 0-999999999
     :param offset: Signed integer representing timezone offset in
         minutes
     """
@@ -161,9 +161,13 @@ class XmlDateTime(NamedTuple):
     hour: int
     minute: int
     second: int
-    microsecond: int = 0
+    fractional_second: int = 0
     offset: Optional[int] = None
 
+    @property
+    def microsecond(self) -> int:
+        return self.fractional_second // 1000
+
     @property
     def duration(self) -> float:
         if self.year < 0:
@@ -180,7 +184,7 @@ class XmlDateTime(NamedTuple):
             + self.hour * DS_HOUR
             + self.minute * DS_MINUTE
             + self.second
-            + self.microsecond * DS_MICROSECOND
+            + self.fractional_second * DS_FRACTIONAL_SECOND
             + (self.offset or 0) * DS_OFFSET
         )
         return -total if negative else total
@@ -188,13 +192,20 @@ class XmlDateTime(NamedTuple):
     @classmethod
     def from_string(cls, string: str) -> "XmlDateTime":
         """Initialize from string with format ``%Y-%m-%dT%H:%M:%S%z``"""
-        year, month, day, hour, minute, second, microsecond, offset = parse_date_args(
-            string, DateFormat.DATE_TIME
-        )
+        (
+            year,
+            month,
+            day,
+            hour,
+            minute,
+            second,
+            fractional_second,
+            offset,
+        ) = parse_date_args(string, DateFormat.DATE_TIME)
         validate_date(year, month, day)
-        validate_time(hour, minute, second, microsecond)
+        validate_time(hour, minute, second, fractional_second)
 
-        return cls(year, month, day, hour, minute, second, microsecond, offset)
+        return cls(year, month, day, hour, minute, second, fractional_second, offset)
 
     @classmethod
     def from_datetime(cls, obj: datetime.datetime) -> "XmlDateTime":
@@ -206,7 +217,7 @@ class XmlDateTime(NamedTuple):
             obj.hour,
             obj.minute,
             obj.second,
-            obj.microsecond,
+            obj.microsecond * 1000,
             calculate_offset(obj),
         )
 
@@ -241,7 +252,7 @@ class XmlDateTime(NamedTuple):
         hour: Optional[int] = None,
         minute: Optional[int] = None,
         second: Optional[int] = None,
-        microsecond: Optional[int] = None,
+        fractional_second: Optional[int] = None,
         offset: Optional[int] = True,
     ) -> "XmlDateTime":
         """Return a new instance replacing the specified fields with new
@@ -259,12 +270,14 @@ class XmlDateTime(NamedTuple):
             minute = self.minute
         if second is None:
             second = self.second
-        if microsecond is None:
-            microsecond = self.microsecond
+        if fractional_second is None:
+            fractional_second = self.fractional_second
         if offset is True:
             offset = self.offset
 
-        return type(self)(year, month, day, hour, minute, second, microsecond, offset)
+        return type(self)(
+            year, month, day, hour, minute, second, fractional_second, offset
+        )
 
     def __str__(self) -> str:
         """
@@ -280,7 +293,7 @@ class XmlDateTime(NamedTuple):
         """
         return "{}T{}{}".format(
             format_date(self.year, self.month, self.day),
-            format_time(self.hour, self.minute, self.second, self.microsecond),
+            format_time(self.hour, self.minute, self.second, self.fractional_second),
             format_offset(self.offset),
         )
 
@@ -323,23 +336,27 @@ class XmlTime(NamedTuple):
     :param hour: Unsigned integer between 0-24
     :param minute: Unsigned integer between 0-59
     :param second: Unsigned integer between 0-59
-    :param microsecond: Unsigned integer between 0-999999
+    :param fractional_second: Unsigned integer between 0-999999999
     :param offset: Signed integer representing timezone offset in minutes
     """
 
     hour: int
     minute: int
     second: int
-    microsecond: int = 0
+    fractional_second: int = 0
     offset: Optional[int] = None
 
+    @property
+    def microsecond(self) -> int:
+        return self.fractional_second // 1000
+
     @property
     def duration(self) -> float:
         return (
             self.hour * DS_HOUR
             + self.minute * DS_MINUTE
             + self.second
-            + self.microsecond * DS_MICROSECOND
+            + self.fractional_second * DS_FRACTIONAL_SECOND
             + (self.offset or 0) * DS_OFFSET
         )
 
@@ -348,7 +365,7 @@ class XmlTime(NamedTuple):
         hour: Optional[int] = None,
         minute: Optional[int] = None,
         second: Optional[int] = None,
-        microsecond: Optional[int] = None,
+        fractional_second: Optional[int] = None,
         offset: Optional[int] = True,
     ) -> "XmlTime":
         """Return a new instance replacing the specified fields with new
@@ -360,27 +377,31 @@ class XmlTime(NamedTuple):
             minute = self.minute
         if second is None:
             second = self.second
-        if microsecond is None:
-            microsecond = self.microsecond
+        if fractional_second is None:
+            fractional_second = self.fractional_second
         if offset is True:
             offset = self.offset
 
-        return type(self)(hour, minute, second, microsecond, offset)
+        return type(self)(hour, minute, second, fractional_second, offset)
 
     @classmethod
     def from_string(cls, string: str) -> "XmlTime":
         """Initialize from string format ``%H:%M:%S%z``"""
-        hour, minute, second, microsecond, offset = parse_date_args(
+        hour, minute, second, fractional_second, offset = parse_date_args(
             string, DateFormat.TIME
         )
-        validate_time(hour, minute, second, microsecond)
-        return cls(hour, minute, second, microsecond, offset)
+        validate_time(hour, minute, second, fractional_second)
+        return cls(hour, minute, second, fractional_second, offset)
 
     @classmethod
     def from_time(cls, obj: datetime.time) -> "XmlTime":
         """Initialize from :class:`datetime.time` instance."""
         return cls(
-            obj.hour, obj.minute, obj.second, obj.microsecond, calculate_offset(obj)
+            obj.hour,
+            obj.minute,
+            obj.second,
+            obj.microsecond * 1000,
+            calculate_offset(obj),
         )
 
     @classmethod
@@ -415,7 +436,7 @@ class XmlTime(NamedTuple):
             - 21:32:52.126Z
         """
         return "{}{}".format(
-            format_time(self.hour, self.minute, self.second, self.microsecond),
+            format_time(self.hour, self.minute, self.second, self.fractional_second),
             format_offset(self.offset),
         )
 
diff --git a/xsdata/utils/dates.py b/xsdata/utils/dates.py
index 6b78770e..a68de007 100644
--- a/xsdata/utils/dates.py
+++ b/xsdata/utils/dates.py
@@ -42,10 +42,14 @@ def format_date(year: int, month: int, day: int) -> str:
     return f"{sign}{year:04d}-{month:02d}-{day:02d}"
 
 
-def format_time(hour: int, minute: int, second: int, microsecond: int) -> str:
-    if not microsecond:
+def format_time(hour: int, minute: int, second: int, fractional_second: int) -> str:
+    if not fractional_second:
         return f"{hour:02d}:{minute:02d}:{second:02d}"
 
+    microsecond, nano = divmod(fractional_second, 1000)
+    if nano:
+        return f"{hour:02d}:{minute:02d}:{second:02d}.{fractional_second:09d}"
+
     milli, micro = divmod(microsecond, 1000)
     if micro:
         return f"{hour:02d}:{minute:02d}:{second:02d}.{microsecond:06d}"
@@ -88,12 +92,12 @@ def validate_date(year: int, month: int, day: int):
         raise ValueError(f"Day must be in 1..{max_days}")
 
 
-def validate_time(hour: int, minute: int, second: int, microsecond: int):
+def validate_time(hour: int, minute: int, second: int, franctional_second: int):
 
     if not 0 <= hour <= 24:
         raise ValueError("Hour must be in 0..24")
 
-    if hour == 24 and (minute != 0 or second != 0 or microsecond != 0):
+    if hour == 24 and (minute != 0 or second != 0 or franctional_second != 0):
         raise ValueError("Day time exceeded")
 
     if not 0 <= minute <= 59:
@@ -102,8 +106,8 @@ def validate_time(hour: int, minute: int, second: int, microsecond: int):
     if not 0 <= second <= 59:
         raise ValueError("Second must be in 0..59")
 
-    if not 0 <= microsecond <= 999999:
-        raise ValueError("Microsecond must be in 0..999999")
+    if not 0 <= franctional_second <= 999999999:
+        raise ValueError("Fractional second must be in 0..999999999")
 
 
 class DateTimeParser:
@@ -165,7 +169,7 @@ class DateTimeParser:
         elif var == "S":
             yield self.parse_digits(2)
 
-            yield self.parse_microsecond()
+            yield self.parse_fractional_second()
         elif var == "z":
             yield self.parse_offset()
         else:
@@ -197,10 +201,10 @@ class DateTimeParser:
 
         return year
 
-    def parse_microsecond(self) -> int:
+    def parse_fractional_second(self) -> int:
         if self.has_more() and self.peek() == ".":
             self.vidx += 1
-            return self.parse_fixed_digits(6)
+            return self.parse_fixed_digits(9)
         else:
             return 0
 


Test patch:
diff --git a/tests/models/test_datatype.py b/tests/models/test_datatype.py
index dade7fce..0dd11e84 100644
--- a/tests/models/test_datatype.py
+++ b/tests/models/test_datatype.py
@@ -130,10 +130,13 @@ class XmlDateTimeTests(TestCase):
             "2002-01-01T12:01:01-00:00": XmlDateTime(2002, 1, 1, 12, 1, 1, 0, 0),
             "2002-01-01T12:01:01-02:15": XmlDateTime(2002, 1, 1, 12, 1, 1, 0, -135),
             "2002-01-01T12:01:01+02:15": XmlDateTime(2002, 1, 1, 12, 1, 1, 0, 135),
-            "2002-01-01T12:01:01.010Z": XmlDateTime(2002, 1, 1, 12, 1, 1, 10000, 0),
-            "2002-01-01T12:01:01.123456": XmlDateTime(2002, 1, 1, 12, 1, 1, 123456),
+            "2002-01-01T12:01:01.010Z": XmlDateTime(2002, 1, 1, 12, 1, 1, 10000000, 0),
+            "2002-01-01T12:01:01.123456": XmlDateTime(2002, 1, 1, 12, 1, 1, 123456000),
             "2002-01-01T12:01:01": XmlDateTime(2002, 1, 1, 12, 1, 1, 0),
             "2010-09-19T24:00:00Z": XmlDateTime(2010, 9, 19, 24, 0, 0, 0, 0),
+            "2010-09-19T03:35:25.482845071Z": XmlDateTime(
+                2010, 9, 19, 3, 35, 25, 482845071, 0
+            ),
         }
 
         for value, expected in examples.items():
@@ -158,15 +161,17 @@ class XmlDateTimeTests(TestCase):
     def test_str(self):
         examples = {
             "2002-01-01T12:01:01-00:00": "2002-01-01T12:01:01Z",
-            "2002-01-01T12:01:01-02:15": None,
-            "2002-01-01T12:01:01+02:15": None,
+            "2002-01-01T12:01:01-02:15": "2002-01-01T12:01:01-02:15",
+            "2002-01-01T12:01:01+02:15": "2002-01-01T12:01:01+02:15",
             "2002-01-01T12:01:01.0100Z": "2002-01-01T12:01:01.010Z",
-            "2002-01-01T12:01:01.123456": None,
+            "2002-01-01T12:01:01.123456": "2002-01-01T12:01:01.123456",
+            "2002-01-01T12:01:01.123456000": "2002-01-01T12:01:01.123456",
             "2002-01-01T12:01:01.123000": "2002-01-01T12:01:01.123",
+            "2002-01-01T12:01:01.123456001": "2002-01-01T12:01:01.123456001",
             "2002-01-01T12:01:01.0": "2002-01-01T12:01:01",
-            "2002-01-01T12:01:01": None,
-            "2010-09-19T24:00:00Z": None,
-            "-0001-09-19T24:00:00Z": None,
+            "2002-01-01T12:01:01": "2002-01-01T12:01:01",
+            "2010-09-19T24:00:00Z": "2010-09-19T24:00:00Z",
+            "-0001-09-19T24:00:00Z": "-0001-09-19T24:00:00Z",
         }
 
         for value, expected in examples.items():
@@ -178,9 +183,9 @@ class XmlDateTimeTests(TestCase):
             "2002-01-01T12:01:01-00:00": "XmlDateTime(2002, 1, 1, 12, 1, 1, 0, 0)",
             "2002-01-01T12:01:01-02:15": "XmlDateTime(2002, 1, 1, 12, 1, 1, 0, -135)",
             "2002-01-01T12:01:01+02:15": "XmlDateTime(2002, 1, 1, 12, 1, 1, 0, 135)",
-            "2002-01-01T12:01:01.0100Z": "XmlDateTime(2002, 1, 1, 12, 1, 1, 10000, 0)",
-            "2002-01-01T12:01:01.123456": "XmlDateTime(2002, 1, 1, 12, 1, 1, 123456)",
-            "2002-01-01T12:01:01.123000": "XmlDateTime(2002, 1, 1, 12, 1, 1, 123000)",
+            "2002-01-01T12:01:01.0100Z": "XmlDateTime(2002, 1, 1, 12, 1, 1, 10000000, 0)",
+            "2002-01-01T12:01:01.123456": "XmlDateTime(2002, 1, 1, 12, 1, 1, 123456000)",
+            "2002-01-01T12:01:01.123000": "XmlDateTime(2002, 1, 1, 12, 1, 1, 123000000)",
             "2002-01-01T12:01:01.0": "XmlDateTime(2002, 1, 1, 12, 1, 1)",
             "2002-01-01T12:01:01": "XmlDateTime(2002, 1, 1, 12, 1, 1)",
             "2010-09-19T24:00:00Z": "XmlDateTime(2010, 9, 19, 24, 0, 0, 0, 0)",
@@ -212,19 +217,19 @@ class XmlDateTimeTests(TestCase):
         now = datetime.now().replace(microsecond=0, second=0, minute=1)
         self.assertEqual(
             XmlDateTime.from_datetime(now),
-            XmlDateTime.now().replace(microsecond=0, second=0, minute=1),
+            XmlDateTime.now().replace(fractional_second=0, second=0, minute=1),
         )
 
         now = datetime.now(tz=timezone.utc).replace(microsecond=0, second=0, minute=1)
         self.assertEqual(
             XmlDateTime.from_datetime(now),
-            XmlDateTime.utcnow().replace(microsecond=0, second=0, minute=1),
+            XmlDateTime.utcnow().replace(fractional_second=0, second=0, minute=1),
         )
 
         now = datetime.utcnow().replace(microsecond=0, second=0, minute=1)
         self.assertEqual(
             XmlDateTime.from_datetime(now),
-            XmlDateTime.utcnow().replace(microsecond=0, second=0, minute=1),
+            XmlDateTime.utcnow().replace(fractional_second=0, second=0, minute=1),
         )
 
     def test_comparisons(self):
@@ -263,19 +268,19 @@ class XmlDateTimeTests(TestCase):
         )
         self.assertEqual(
             "2022-12-25T10:15:30.150-02:00",
-            str(actual.replace(2022, 12, 25, 10, 15, 30, 150000)),
+            str(actual.replace(2022, 12, 25, 10, 15, 30, 150000000)),
         )
         self.assertEqual(
             "2022-12-25T10:15:30.150+01:00",
-            str(actual.replace(2022, 12, 25, 10, 15, 30, 150000, 60)),
+            str(actual.replace(2022, 12, 25, 10, 15, 30, 150000000, 60)),
         )
         self.assertEqual(
             "2022-12-25T10:15:30.150",
-            str(actual.replace(2022, 12, 25, 10, 15, 30, 150000, None)),
+            str(actual.replace(2022, 12, 25, 10, 15, 30, 150000000, None)),
         )
         self.assertEqual(
             "2022-12-25T10:15:30.150Z",
-            str(actual.replace(2022, 12, 25, 10, 15, 30, 150000, False)),
+            str(actual.replace(2022, 12, 25, 10, 15, 30, 150000000, False)),
         )
 
 
@@ -285,8 +290,8 @@ class XmlTimeTests(TestCase):
             "12:01:01-00:00": XmlTime(12, 1, 1, 0, 0),
             "12:01:01-02:15": XmlTime(12, 1, 1, 0, -135),
             "12:01:01+02:15": XmlTime(12, 1, 1, 0, +135),
-            "12:01:01.010Z": XmlTime(12, 1, 1, 10000, 0),
-            "12:01:01.123456": XmlTime(12, 1, 1, 123456),
+            "12:01:01.010Z": XmlTime(12, 1, 1, 10000000, 0),
+            "12:01:01.123456": XmlTime(12, 1, 1, 123456000),
             "12:01:01": XmlTime(12, 1, 1, 0),
             "24:00:00Z": XmlTime(24, 0, 0, 0, 0),
         }
@@ -327,9 +332,9 @@ class XmlTimeTests(TestCase):
             "12:01:01-00:00": "XmlTime(12, 1, 1, 0, 0)",
             "12:01:01-02:15": "XmlTime(12, 1, 1, 0, -135)",
             "12:01:01+02:15": "XmlTime(12, 1, 1, 0, 135)",
-            "12:01:01.010Z": "XmlTime(12, 1, 1, 10000, 0)",
-            "12:01:01.123456": "XmlTime(12, 1, 1, 123456)",
-            "12:01:01.123000": "XmlTime(12, 1, 1, 123000)",
+            "12:01:01.010Z": "XmlTime(12, 1, 1, 10000000, 0)",
+            "12:01:01.123456": "XmlTime(12, 1, 1, 123456000)",
+            "12:01:01.123000": "XmlTime(12, 1, 1, 123000000)",
             "24:00:00Z": "XmlTime(24, 0, 0, 0, 0)",
         }
 
@@ -341,36 +346,36 @@ class XmlTimeTests(TestCase):
         tz_minus_120 = timezone(timedelta(minutes=-120))
 
         obj = time(12, 1, 1, 1, tzinfo=tz_minus_120)
-        actual = XmlTime(12, 1, 1, 1, -120)
+        actual = XmlTime(12, 1, 1, 1000, -120)
         self.assertEqual(actual, XmlTime.from_time(obj))
         self.assertEqual(obj, actual.to_time())
 
         obj = time(12, 1, 1, 1, tzinfo=timezone.utc)
-        actual = XmlTime(12, 1, 1, 1, 0)
+        actual = XmlTime(12, 1, 1, 1000, 0)
         self.assertEqual(actual, XmlTime.from_time(obj))
         self.assertEqual(obj, actual.to_time())
 
         obj = time(12, 1, 1, 1, tzinfo=timezone.utc)
-        actual = XmlTime(12, 1, 1, 1, 0)
+        actual = XmlTime(12, 1, 1, 1000, 0)
         self.assertEqual(actual, XmlTime.from_time(obj))
         self.assertEqual(obj, actual.to_time())
 
         now = datetime.now().replace(microsecond=0, second=0, minute=1)
         self.assertEqual(
             XmlTime.from_time(now.time()),
-            XmlTime.now().replace(microsecond=0, second=0, minute=1),
+            XmlTime.now().replace(fractional_second=0, second=0, minute=1),
         )
 
         now = datetime.now(tz=timezone.utc).replace(microsecond=0, second=0, minute=1)
         self.assertEqual(
             XmlTime.from_time(now.time()),
-            XmlTime.utcnow().replace(microsecond=0, second=0, minute=1),
+            XmlTime.utcnow().replace(fractional_second=0, second=0, minute=1),
         )
 
         now = datetime.utcnow().replace(microsecond=0, second=0, minute=1)
         self.assertEqual(
             XmlTime.from_time(now.time()),
-            XmlTime.utcnow().replace(microsecond=0, second=0, minute=1),
+            XmlTime.utcnow().replace(fractional_second=0, second=0, minute=1),
         )
 
     def test_comparisons(self):
@@ -392,9 +397,9 @@ class XmlTimeTests(TestCase):
     def test_replace(self):
         actual = XmlTime(12, 1, 1, 1, 0)
         self.assertIsNot(actual, actual.replace())
-        self.assertEqual("14:01:01.000001Z", str(actual.replace(14)))
-        self.assertEqual("14:02:01.000001Z", str(actual.replace(14, 2)))
-        self.assertEqual("14:02:03.000001Z", str(actual.replace(14, 2, 3)))
+        self.assertEqual("14:01:01.000000001Z", str(actual.replace(14)))
+        self.assertEqual("14:02:01.000000001Z", str(actual.replace(14, 2)))
+        self.assertEqual("14:02:03.000000001Z", str(actual.replace(14, 2, 3)))
         self.assertEqual("14:02:03Z", str(actual.replace(14, 2, 3, 0)))
         self.assertEqual("14:02:03", str(actual.replace(14, 2, 3, 0, None)))
         self.assertEqual("14:02:03+00:55", str(actual.replace(14, 2, 3, 0, 55)))
diff --git a/tests/utils/test_dates.py b/tests/utils/test_dates.py
index 3ae2968b..e596a2c7 100644
--- a/tests/utils/test_dates.py
+++ b/tests/utils/test_dates.py
@@ -23,7 +23,14 @@ class DatesUtilsTests(TestCase):
         args = parse_date_args("55.1345", "%S")
 
         self.assertEqual(55, next(args))
-        self.assertEqual(134500, next(args))
+        self.assertEqual(134500000, next(args))
+        self.assertIsNone(next(args, None))
+
+    def test_parse_date_args_nanosecond(self):
+        args = parse_date_args("55.1345678", "%S")
+
+        self.assertEqual(55, next(args))
+        self.assertEqual(134567800, next(args))
         self.assertIsNone(next(args, None))
 
     def test_parse_date_args_year(self):
@@ -117,8 +124,8 @@ class DatesUtilsTests(TestCase):
             (23, 66, 0, 1): "Minute must be in 0..59",
             (23, 59, -1, 1): "Second must be in 0..59",
             (23, 59, 60, 1): "Second must be in 0..59",
-            (23, 59, 59, -1): "Microsecond must be in 0..999999",
-            (23, 59, 59, 9999991): "Microsecond must be in 0..999999",
+            (23, 59, 59, -1): "Fractional second must be in 0..999999999",
+            (23, 59, 59, 1000000000): "Fractional second must be in 0..999999999",
         }
 
         for args, msg in invalid.items():