New Upstream Release - dfdatetime

Ready changes

Summary

Merged new upstream version: 20221218 (was: 20210509).

Resulting package

Built on 2023-01-01T18:04 (took 4m29s)

The resulting binary packages can be installed (if you have the apt repository enabled) by running one of:

apt install -t fresh-releases python3-dfdatetime

Lintian Result

Diff

diff --git a/.github/workflows/test_docker.yml b/.github/workflows/test_docker.yml
index bced515..4130a74 100644
--- a/.github/workflows/test_docker.yml
+++ b/.github/workflows/test_docker.yml
@@ -6,7 +6,7 @@ jobs:
     runs-on: ubuntu-latest
     strategy:
       matrix:
-        version: ['32', '33', '34']
+        version: ['36']
     container:
       image: registry.fedoraproject.org/fedora:${{ matrix.version }}
     steps:
@@ -17,7 +17,7 @@ jobs:
     - name: Install dependencies
       run: |
         dnf copr -y enable @gift/dev
-        dnf install -y python3 python3-mock python3-pbr python3-setuptools python3-six
+        dnf install -y @development-tools python3 python3-devel python3-setuptools
     - name: Run tests
       env:
         LANG: C.utf8
@@ -40,7 +40,7 @@ jobs:
     runs-on: ubuntu-latest
     strategy:
       matrix:
-        version: ['18.04', '20.04']
+        version: ['22.04']
     container:
       image: ubuntu:${{ matrix.version }}
     steps:
@@ -57,7 +57,7 @@ jobs:
       run: |
         add-apt-repository -y ppa:gift/dev
         apt-get update -q
-        apt-get install -y python3 python3-distutils python3-mock python3-pbr python3-setuptools python3-six
+        apt-get install -y build-essential python3 python3-dev python3-distutils python3-setuptools
     - name: Run tests
       env:
         LANG: en_US.UTF-8
diff --git a/.github/workflows/test_docs.yml b/.github/workflows/test_docs.yml
new file mode 100644
index 0000000..77ecb39
--- /dev/null
+++ b/.github/workflows/test_docs.yml
@@ -0,0 +1,46 @@
+# Run docs tox tests on Ubuntu Docker images using GIFT PPA
+name: test_docs
+on:
+  pull_request:
+    branches:
+    - main
+  push:
+    branches:
+    - main
+jobs:
+  build:
+    runs-on: ubuntu-latest
+    strategy:
+      matrix:
+        include:
+        - python-version: '3.8'
+          toxenv: 'docs'
+    container:
+      image: ubuntu:22.04
+    steps:
+    - uses: actions/checkout@v2
+    - name: Set up container
+      env:
+        DEBIAN_FRONTEND: noninteractive
+      run: |
+        apt-get update -q
+        apt-get install -y libterm-readline-gnu-perl locales software-properties-common
+        locale-gen en_US.UTF-8
+        ln -f -s /usr/share/zoneinfo/UTC /etc/localtime
+    - name: Install dependencies
+      env:
+        DEBIAN_FRONTEND: noninteractive
+      run: |
+        add-apt-repository -y universe
+        add-apt-repository -y ppa:deadsnakes/ppa
+        add-apt-repository -y ppa:gift/dev
+        apt-get update -q
+        apt-get install -y build-essential git libffi-dev python${{ matrix.python-version }} python${{ matrix.python-version }}-dev python${{ matrix.python-version }}-venv python3-distutils python3-pip python3-setuptools
+    - name: Install tox
+      run: |
+        python3 -m pip install tox
+    - name: Run tests
+      env:
+        LANG: en_US.UTF-8
+      run: |
+        tox -e${{ matrix.toxenv }}
diff --git a/.github/workflows/test_tox.yml b/.github/workflows/test_tox.yml
index 7395cb1..62ed808 100644
--- a/.github/workflows/test_tox.yml
+++ b/.github/workflows/test_tox.yml
@@ -1,24 +1,32 @@
 # Run tox tests on Ubuntu Docker images using GIFT PPA
 name: test_tox
-on: [push, pull_request]
+on:
+  pull_request:
+    branches:
+    - main
+  push:
+    branches:
+    - main
 jobs:
   build:
     runs-on: ubuntu-latest
     strategy:
       matrix:
         include:
-        - python-version: 3.6
-          toxenv: 'py36'
-        - python-version: 3.7
+        - python-version: '3.7'
           toxenv: 'py37'
-        - python-version: 3.8
+        - python-version: '3.8'
           toxenv: 'py38,coverage,codecov'
-        - python-version: 3.8
-          toxenv: 'pylint'
-        - python-version: 3.8
-          toxenv: 'docs'
+        - python-version: '3.9'
+          toxenv: 'py39'
+        - python-version: '3.10'
+          toxenv: 'py310'
+        - python-version: '3.11'
+          toxenv: 'py311'
+        - python-version: '3.8'
+          toxenv: 'lint'
     container:
-      image: ubuntu:20.04
+      image: ubuntu:22.04
     steps:
     - uses: actions/checkout@v2
     - name: Set up container
@@ -37,7 +45,10 @@ jobs:
         add-apt-repository -y ppa:deadsnakes/ppa
         add-apt-repository -y ppa:gift/dev
         apt-get update -q
-        apt-get install -y build-essential git python${{ matrix.python-version }} python${{ matrix.python-version }}-dev tox python3-distutils python3-mock python3-pbr python3-setuptools python3-six
+        apt-get install -y build-essential git libffi-dev python${{ matrix.python-version }} python${{ matrix.python-version }}-dev python${{ matrix.python-version }}-venv python3-distutils python3-pip python3-setuptools
+    - name: Install tox
+      run: |
+        python3 -m pip install tox
     - name: Run tests
       env:
         LANG: en_US.UTF-8
diff --git a/.pylintrc b/.pylintrc
index b4a4f1e..2cd75dd 100644
--- a/.pylintrc
+++ b/.pylintrc
@@ -1,4 +1,4 @@
-# Pylint 2.6.x configuration file
+# Pylint 2.10.x configuration file
 #
 # This file is generated by l2tdevtools update-dependencies.py, any dependency
 # related changes should be made in dependencies.ini.
@@ -7,18 +7,33 @@
 # A comma-separated list of package or module names from where C extensions may
 # be loaded. Extensions are loading into the active Python interpreter and may
 # run arbitrary code.
+extension-pkg-allow-list=
+
+# A comma-separated list of package or module names from where C extensions may
+# be loaded. Extensions are loading into the active Python interpreter and may
+# run arbitrary code. (This is an alternative name to extension-pkg-allow-list
+# for backward compatibility.)
 extension-pkg-whitelist=
 
+# Return non-zero exit code if any of these messages/categories are detected,
+# even if score is above --fail-under value. Syntax same as enable. Messages
+# specified are enabled, while categories only check already-enabled messages.
+fail-on=
+
 # Specify a score threshold to be exceeded before program exits with error.
 fail-under=10.0
 
-# Add files or directories to the blacklist. They should be base names, not
-# paths.
+# Files or directories to be skipped. They should be base names, not paths.
 ignore=CVS
 
-# Add files or directories matching the regex patterns to the blacklist. The
-# regex matches against base names, not paths.
-ignore-patterns=
+# Add files or directories matching the regex patterns to the ignore-list. The
+# regex matches against paths and can be in Posix or Windows format.
+ignore-paths=
+
+# Files or directories matching the regex patterns are skipped. The regex
+# matches against base names, not paths. The default value ignores emacs file
+# locks
+ignore-patterns=^\.#
 
 # Python code to execute, usually for sys.path manipulation such as
 # pygtk.require().
@@ -40,6 +55,13 @@ load-plugins=pylint.extensions.docparams
 # Pickle collected data for later comparisons.
 persistent=yes
 
+# Minimum Python version to use for version dependent checks. Will default to
+# the version used to run pylint.
+py-version=3.10
+
+# Discover python modules and packages in the file system subtree.
+recursive=yes
+
 # When enabled, pylint would attempt to guess common misconfiguration and emit
 # user-friendly hints instead of false-positive error messages.
 suggestion-mode=yes
@@ -52,20 +74,22 @@ unsafe-load-any-extension=no
 [MESSAGES CONTROL]
 
 # Only show warnings with the listed confidence levels. Leave empty to show
-# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED.
+# all. Valid levels: HIGH, CONTROL_FLOW, INFERENCE, INFERENCE_FAILURE,
+# UNDEFINED.
 confidence=
 
 # Disable the message, report, category or checker with the given id(s). You
 # can either give multiple identifiers separated by comma (,) or put this
 # option multiple times (only on the command line, not in the configuration
 # file where it should appear only once). You can also use "--disable=all" to
-# disable everything first and then reenable specific checks. For example, if
+# disable everything first and then re-enable specific checks. For example, if
 # you want to run only the similarities checker, you can use "--disable=all
 # --enable=similarities". If you want to run only the classes checker, but have
 # no Warning level messages displayed, use "--disable=all --enable=classes
 # --disable=W".
 disable=assignment-from-none,
         bad-inline-option,
+        consider-using-f-string,
         deprecated-pragma,
         duplicate-code,
         eq-without-hash,
@@ -74,6 +98,7 @@ disable=assignment-from-none,
         locally-disabled,
         locally-enabled,
         logging-format-interpolation,
+        logging-fstring-interpolation,
         metaclass-assignment,
         missing-param-doc,
         no-absolute-import,
@@ -102,17 +127,17 @@ disable=assignment-from-none,
 # either give multiple identifier separated by comma (,) or put this option
 # multiple time (only on the command line, not in the configuration file where
 # it should appear only once). See also the "--disable" option for examples.
-enable=
+enable=c-extension-no-member
 
 
 [REPORTS]
 
 # Python expression which should return a score less than or equal to 10. You
-# have access to the variables 'error', 'warning', 'refactor', and 'convention'
-# which contain the number of messages in each category, as well as 'statement'
-# which is the total number of statements analyzed. This score is used by the
-# global evaluation report (RP0004).
-evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
+# have access to the variables 'fatal', 'error', 'warning', 'refactor',
+# 'convention', and 'info' which contain the number of messages in each
+# category, as well as 'statement' which is the total number of statements
+# analyzed. This score is used by the global evaluation report (RP0004).
+evaluation=max(0, 0 if fatal else 10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10))
 
 # Template used to display messages. This is a python new-style format string
 # used to format the message information. See doc for all details.
@@ -139,7 +164,7 @@ max-nested-blocks=5
 # inconsistent-return-statements if a never returning function is called then
 # it will be considered as an explicit return statement and no message will be
 # printed.
-never-returning-functions=sys.exit
+never-returning-functions=sys.exit,argparse.parse_error
 
 
 [VARIABLES]
@@ -151,6 +176,9 @@ additional-builtins=
 # Tells whether unused global variables should be treated as a violation.
 allow-global-unused-variables=yes
 
+# List of names allowed to shadow builtins
+allowed-redefined-builtins=
+
 # List of strings which can identify a callback function by name. A callback
 # name must start or end with one of those strings.
 callbacks=cb_,
@@ -185,7 +213,7 @@ contextmanager-decorators=contextlib.contextmanager
 generated-members=
 
 # Tells whether missing members accessed in mixin class should be ignored. A
-# mixin class is detected if its name ends with "mixin" (case insensitive).
+# class is considered mixin if its name matches the mixin-class-rgx option.
 ignore-mixin-members=yes
 
 # Tells whether to warn about missing members when the owner of the attribute
@@ -223,6 +251,10 @@ missing-member-hint-distance=1
 # showing a hint for a missing member.
 missing-member-max-choices=1
 
+# Regex pattern to define which classes are considered mixins ignore-mixin-
+# members is set to 'yes'
+mixin-class-rgx=.*[Mm]ixin
+
 # List of decorators that change the signature of a decorated function.
 signature-mutators=
 
@@ -244,13 +276,15 @@ logging-modules=logging
 argument-naming-style=snake_case
 
 # Regular expression matching correct argument names. Overrides argument-
-# naming-style.
+# naming-style. If left empty, argument names will be checked with the set
+# naming style.
 argument-rgx=(([a-z][a-z0-9_]*)|(_[a-z0-9_]*))$
 
 # Naming style matching correct attribute names.
 attr-naming-style=snake_case
 
 # Regular expression matching correct attribute names. Overrides attr-naming-
+# style. If left empty, attribute names will be checked with the set naming
 # style.
 attr-rgx=(([a-z][a-z0-9_]*)|(_[a-z0-9_]*))$
 
@@ -270,20 +304,30 @@ bad-names-rgxs=
 class-attribute-naming-style=any
 
 # Regular expression matching correct class attribute names. Overrides class-
-# attribute-naming-style.
+# attribute-naming-style. If left empty, class attribute names will be checked
+# with the set naming style.
 class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]*|(__.*__))$
 
+# Naming style matching correct class constant names.
+class-const-naming-style=UPPER_CASE
+
+# Regular expression matching correct class constant names. Overrides class-
+# const-naming-style. If left empty, class constant names will be checked with
+# the set naming style.
+#class-const-rgx=
+
 # Naming style matching correct class names.
 class-naming-style=PascalCase
 
 # Regular expression matching correct class names. Overrides class-naming-
-# style.
+# style. If left empty, class names will be checked with the set naming style.
 class-rgx=[A-Z_][a-zA-Z0-9]+$
 
 # Naming style matching correct constant names.
 const-naming-style=UPPER_CASE
 
 # Regular expression matching correct constant names. Overrides const-naming-
+# style. If left empty, constant names will be checked with the set naming
 # style.
 const-rgx=(([a-zA-Z_][a-zA-Z0-9_]*)|(__.*__))$
 
@@ -295,7 +339,8 @@ docstring-min-length=-1
 function-naming-style=snake_case
 
 # Regular expression matching correct function names. Overrides function-
-# naming-style.
+# naming-style. If left empty, function names will be checked with the set
+# naming style.
 function-rgx=[A-Z_][a-zA-Z0-9_]*$
 
 # Good variable names which should always be accepted, separated by a comma.
@@ -317,21 +362,22 @@ include-naming-hint=no
 inlinevar-naming-style=any
 
 # Regular expression matching correct inline iteration names. Overrides
-# inlinevar-naming-style.
+# inlinevar-naming-style. If left empty, inline iteration names will be checked
+# with the set naming style.
 inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$
 
 # Naming style matching correct method names.
 method-naming-style=snake_case
 
 # Regular expression matching correct method names. Overrides method-naming-
-# style.
+# style. If left empty, method names will be checked with the set naming style.
 method-rgx=(test|[A-Z_])[a-zA-Z0-9_]*$
 
 # Naming style matching correct module names.
 module-naming-style=snake_case
 
 # Regular expression matching correct module names. Overrides module-naming-
-# style.
+# style. If left empty, module names will be checked with the set naming style.
 module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
 
 # Colon-delimited sets of names that determine each other's naming style when
@@ -347,11 +393,16 @@ no-docstring-rgx=^_
 # These decorators are taken in consideration only for invalid-name.
 property-classes=abc.abstractproperty
 
+# Regular expression matching correct type variable names. If left empty, type
+# variable names will be checked with the set naming style.
+#typevar-rgx=
+
 # Naming style matching correct variable names.
 variable-naming-style=snake_case
 
 # Regular expression matching correct variable names. Overrides variable-
-# naming-style.
+# naming-style. If left empty, variable names will be checked with the set
+# naming style.
 variable-rgx=(([a-z][a-z0-9_]*)|(_[a-z0-9_]*))$
 
 
@@ -410,6 +461,10 @@ max-spelling-suggestions=4
 # (hunspell), en_ZM (hunspell), en_ZW (hunspell).
 spelling-dict=
 
+# List of comma separated words that should be considered directives if they
+# appear and the beginning of a comment and should not be checked.
+spelling-ignore-comment-directives=fmt: on,fmt: off,noqa:,noqa,nosec,isort:skip,mypy:
+
 # List of comma separated words that should not be checked.
 spelling-ignore-words=
 
@@ -423,15 +478,18 @@ spelling-store-unknown-words=no
 
 [SIMILARITIES]
 
-# Ignore comments when computing similarities.
+# Comments are removed from the similarity computation
 ignore-comments=yes
 
-# Ignore docstrings when computing similarities.
+# Docstrings are removed from the similarity computation
 ignore-docstrings=yes
 
-# Ignore imports when computing similarities.
+# Imports are removed from the similarity computation
 ignore-imports=no
 
+# Signatures are removed from the similarity computation
+ignore-signatures=no
+
 # Minimum lines number of a similarity.
 min-similarity-lines=4
 
@@ -449,6 +507,14 @@ check-str-concat-over-line-jumps=no
 
 [DESIGN]
 
+# List of regular expressions of class ancestor names to ignore when counting
+# public methods (see R0903)
+exclude-too-few-public-methods=
+
+# List of qualified class names to ignore when counting class parents (see
+# R0901)
+ignored-parents=
+
 # Maximum number of arguments for function / method.
 max-args=10
 
@@ -482,6 +548,9 @@ min-public-methods=2
 
 [CLASSES]
 
+# Warn about protected attribute access inside special methods
+check-protected-access-in-special-methods=no
+
 # List of method names used to declare (i.e. assign) instance attributes.
 defining-attr-methods=__init__,
                       __new__,
@@ -520,16 +589,17 @@ analyse-fallback-blocks=no
 # Deprecated modules which should not be used, separated by a comma.
 deprecated-modules=optparse,tkinter.tix
 
-# Create a graph of external dependencies in the given file (report RP0402 must
-# not be disabled).
+# Output a graph (.gv or any supported image format) of external dependencies
+# to the given file (report RP0402 must not be disabled).
 ext-import-graph=
 
-# Create a graph of every (i.e. internal and external) dependencies in the
-# given file (report RP0402 must not be disabled).
+# Output a graph (.gv or any supported image format) of all (i.e. internal and
+# external) dependencies to the given file (report RP0402 must not be
+# disabled).
 import-graph=
 
-# Create a graph of internal dependencies in the given file (report RP0402 must
-# not be disabled).
+# Output a graph (.gv or any supported image format) of internal dependencies
+# to the given file (report RP0402 must not be disabled).
 int-import-graph=
 
 # Force import order to recognize a module as part of the standard
@@ -543,29 +613,6 @@ known-third-party=enchant
 preferred-modules=
 
 
-[PARAMETER_DOCUMENTATION]
-
-# Whether to accept totally missing parameter documentation in the docstring of
-# a function that has parameters.
-accept-no-param-doc=yes
-
-# Whether to accept totally missing raises documentation in the docstring of a
-# function that raises an exception.
-accept-no-raise-doc=yes
-
-# Whether to accept totally missing return documentation in the docstring of a
-# function that returns a statement.
-accept-no-return-doc=yes
-
-# Whether to accept totally missing yields documentation in the docstring of a
-# generator.
-accept-no-yields-doc=yes
-
-# If the docstring type cannot be guessed the specified docstring type will be
-# used.
-default-docstring-type=default
-
-
 [EXCEPTIONS]
 
 # Exceptions that will emit a warning when being caught. Defaults to
diff --git a/MANIFEST.in b/MANIFEST.in
index e1234e6..b987d74 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,6 +1,7 @@
 include ACKNOWLEDGEMENTS AUTHORS LICENSE README
 include dependencies.ini run_tests.py utils/__init__.py utils/dependencies.py
 include utils/check_dependencies.py
+include requirements.txt test_requirements.txt
 exclude .gitignore
 exclude *.pyc
 recursive-include config *
diff --git a/appveyor.yml b/appveyor.yml
index 66b83e6..bb19baa 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -1,32 +1,32 @@
 environment:
-  pypi_token:
+  PYPI_TOKEN:
     secure: /FwQrmudDyj+Mu3DaxLEo23Y6/OEgdHJqyWyZTjkJKje8pxCOrUorN8ZlXRGXbd3UA60emClt0M+SI+xqyA/qkpqZTgd5CKohpVAGH2EfzRc/zwJSGJ4tmZmMVAG8ayk6N9zFxCeC+y0BgZPQnj/Eq/RfuS4YIuaKutIUa5gTMmhWpODFKGV/2Wx1w67xWxAoONfEC5j0Gu3R274SS7FfBb4qWyIiBIJMwHGjlgp1Onk8KlpCLauZv8/hGfQDmWEdZ+mjcsTYyQYr1xfr1/FjQ==
   matrix:
-  - DESCRIPTION: "Windows with 32-bit Python 3.9"
+  - DESCRIPTION: "Windows with 32-bit Python 3.10"
     MACHINE_TYPE: "x86"
     APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019
-    PYTHON: "C:\\Python39"
-    PYTHON_VERSION: "3.9"
+    PYTHON: "C:\\Python310"
+    PYTHON_VERSION: "3.10"
     L2TBINARIES_TRACK: "dev"
-  - DESCRIPTION: "Windows with 64-bit Python 3.9"
+  - DESCRIPTION: "Windows with 64-bit Python 3.10"
     MACHINE_TYPE: "amd64"
     APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019
-    PYTHON: "C:\\Python39-x64"
-    PYTHON_VERSION: "3.9"
+    PYTHON: "C:\\Python310-x64"
+    PYTHON_VERSION: "3.10"
     L2TBINARIES_TRACK: "dev"
-  - DESCRIPTION: "Mac OS with Python 3.9"
-    APPVEYOR_BUILD_WORKER_IMAGE: macos
+  - DESCRIPTION: "Mac OS with Python 3.11"
+    APPVEYOR_BUILD_WORKER_IMAGE: macos-monterey
     HOMEBREW_NO_INSTALL_CLEANUP: 1
 
 install:
-- cmd: "%PYTHON%\\python.exe -m pip install -U pip setuptools wheel"
+- cmd: "%PYTHON%\\python.exe -m pip install -U pip setuptools twine wheel"
 - cmd: "%PYTHON%\\python.exe -m pip install pywin32 WMI"
 - cmd: "%PYTHON%\\python.exe %PYTHON%\\Scripts\\pywin32_postinstall.py -install"
 - ps: If ($isWindows) { .\config\appveyor\install.ps1 }
 - sh: config/appveyor/install.sh
 
 build_script:
-- cmd: "%PYTHON%\\python.exe setup.py bdist_msi bdist_wheel"
+- cmd: "%PYTHON%\\python.exe setup.py bdist_wheel"
 
 test_script:
 - cmd: "%PYTHON%\\python.exe run_tests.py"
@@ -39,6 +39,5 @@ artifacts:
 - path: dist\*.whl
 
 deploy_script:
-- ps: If ($env:APPVEYOR_REPO_TAG -eq "true" -And $isWindows) {
+- ps: If ($env:APPVEYOR_REPO_TAG -eq "true" -And $isWindows -And $env:MACHINE_TYPE -eq "x86") {
     Invoke-Expression "${env:PYTHON}\\python.exe -m twine upload dist/*.whl --username __token__ --password ${env:PYPI_TOKEN} --skip-existing" }
-
diff --git a/config/appveyor/install.ps1 b/config/appveyor/install.ps1
index cbad7af..59abce8 100644
--- a/config/appveyor/install.ps1
+++ b/config/appveyor/install.ps1
@@ -1,6 +1,6 @@
 # Script to set up tests on AppVeyor Windows.
 
-$Dependencies = "mock pbr six"
+$Dependencies = ""
 $Dependencies = ${Dependencies} -split " "
 
 $Output = Invoke-Expression -Command "git clone https://github.com/log2timeline/l2tdevtools.git ..\l2tdevtools 2>&1"
diff --git a/config/appveyor/install.sh b/config/appveyor/install.sh
index 774e62d..e36de4c 100755
--- a/config/appveyor/install.sh
+++ b/config/appveyor/install.sh
@@ -2,6 +2,6 @@
 
 set -e
 
-brew update
-brew install tox || true
+brew update -q
+brew install -q gettext gnu-sed python@3.11 tox || true
 
diff --git a/config/appveyor/runtests.sh b/config/appveyor/runtests.sh
index 068691e..e3bc8c3 100755
--- a/config/appveyor/runtests.sh
+++ b/config/appveyor/runtests.sh
@@ -1,12 +1,15 @@
 #!/bin/sh
 # Script to run tests
 
+# Set the following environment variables to build libyal with gettext.
+export CPPFLAGS="-I/usr/local/include -I/usr/local/opt/gettext/include ${CPPFLAGS}";
+export LDFLAGS="-L/usr/local/lib -L/usr/local/opt/gettext/lib ${LDFLAGS}";
+
 # Set the following environment variables to build pycrypto and yara-python.
-export CFLAGS="-I/usr/local/include -I/usr/local/opt/openssl@1.1/include ${CFLAGS}";
-export LDFLAGS="-L/usr/local/lib -L/usr/local/opt/openssl@1.1/lib ${LDFLAGS}";
-export TOX_TESTENV_PASSENV="CFLAGS LDFLAGS";
+export CPPFLAGS="-I/usr/local/opt/openssl@1.1/include ${CPPFLAGS}";
+export LDFLAGS="-L/usr/local/opt/openssl@1.1/lib ${LDFLAGS}";
 
-# Set the following environment variables to ensure tox can find Python 3.9.
-export PATH="/usr/local/opt/python@3.9/bin:${PATH}";
+# Set the following environment variables to ensure tox can find Python 3.11.
+export PATH="/usr/local/opt/python@3.11/bin:${PATH}";
 
-tox -e py39
+tox -e py311
diff --git a/config/dpkg/changelog b/config/dpkg/changelog
index 546c26c..2fb4aa9 100644
--- a/config/dpkg/changelog
+++ b/config/dpkg/changelog
@@ -1,5 +1,5 @@
-dfdatetime (20210509-1) unstable; urgency=low
+dfdatetime (20221218-1) unstable; urgency=low
 
   * Auto-generated
 
- -- Log2Timeline maintainers <log2timeline-maintainers@googlegroups.com>  Sun, 09 May 2021 16:27:46 +0200
+ -- Log2Timeline maintainers <log2timeline-maintainers@googlegroups.com>  Sun, 18 Dec 2022 13:12:18 +0100
diff --git a/config/dpkg/control b/config/dpkg/control
index 958cc96..aa89082 100644
--- a/config/dpkg/control
+++ b/config/dpkg/control
@@ -9,7 +9,7 @@ Homepage: https://github.com/log2timeline/dfdatetime
 
 Package: python3-dfdatetime
 Architecture: all
-Depends: ${python3:Depends}, ${misc:Depends}
+Depends: ${misc:Depends}
 Description: Python 3 module of dfDateTime
  dfDateTime, or Digital Forensics date and time, provides date and time objects
  to preserve accuracy and precision.
diff --git a/debian/changelog b/debian/changelog
index dab6050..03c745d 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+dfdatetime (20221218-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Debian Janitor <janitor@jelmer.uk>  Sun, 01 Jan 2023 18:01:00 -0000
+
 dfdatetime (20210509-1) unstable; urgency=medium
 
   * Team upload.
diff --git a/dfdatetime.ini b/dfdatetime.ini
index f5d7c67..e0fb912 100644
--- a/dfdatetime.ini
+++ b/dfdatetime.ini
@@ -8,3 +8,5 @@ homepage_url: https://github.com/log2timeline/dfdatetime
 description_short: Digital Forensics date and time (dfDateTime).
 description_long: dfDateTime, or Digital Forensics date and time, provides date and time objects
     to preserve accuracy and precision.
+pypi_token: /FwQrmudDyj+Mu3DaxLEo23Y6/OEgdHJqyWyZTjkJKje8pxCOrUorN8ZlXRGXbd3UA60emClt0M+SI+xqyA/qkpqZTgd5CKohpVAGH2EfzRc/zwJSGJ4tmZmMVAG8ayk6N9zFxCeC+y0BgZPQnj/Eq/RfuS4YIuaKutIUa5gTMmhWpODFKGV/2Wx1w67xWxAoONfEC5j0Gu3R274SS7FfBb4qWyIiBIJMwHGjlgp1Onk8KlpCLauZv8/hGfQDmWEdZ+mjcsTYyQYr1xfr1/FjQ==
+
diff --git a/dfdatetime/__init__.py b/dfdatetime/__init__.py
index 9ba4065..d293435 100644
--- a/dfdatetime/__init__.py
+++ b/dfdatetime/__init__.py
@@ -9,9 +9,11 @@ objects to preserve accuracy and precision.
 from dfdatetime import apfs_time
 from dfdatetime import cocoa_time
 from dfdatetime import delphi_date_time
+from dfdatetime import dotnet_datetime
 from dfdatetime import fat_date_time
 from dfdatetime import filetime
 from dfdatetime import hfs_time
+from dfdatetime import golang_time
 from dfdatetime import java_time
 from dfdatetime import ole_automation_date
 from dfdatetime import posix_time
@@ -20,5 +22,7 @@ from dfdatetime import semantic_time
 from dfdatetime import systemtime
 from dfdatetime import time_elements
 from dfdatetime import uuid_time
+from dfdatetime import webkit_time
 
-__version__ = '20210509'
+
+__version__ = '20221218'
diff --git a/dfdatetime/cocoa_time.py b/dfdatetime/cocoa_time.py
index 46ac5a8..90c3c8b 100644
--- a/dfdatetime/cocoa_time.py
+++ b/dfdatetime/cocoa_time.py
@@ -26,21 +26,25 @@ class CocoaTime(interface.DateTimeValues):
   Attributes:
     is_local_time (bool): True if the date and time value is in local time.
   """
+
   # The difference between January 1, 2001 and January 1, 1970 in seconds.
   _COCOA_TO_POSIX_BASE = -978307200
 
   _EPOCH = CocoaTimeEpoch()
 
-  def __init__(self, time_zone_offset=None, timestamp=None):
+  def __init__(self, precision=None, time_zone_offset=None, timestamp=None):
     """Initializes a Cocoa timestamp.
 
     Args:
+      precision (Optional[str]): precision of the date and time value, which
+          should be one of the PRECISION_VALUES in definitions.
       time_zone_offset (Optional[int]): time zone offset in number of minutes
           from UTC or None if not set.
       timestamp (Optional[float]): Cocoa timestamp.
     """
-    super(CocoaTime, self).__init__(time_zone_offset=time_zone_offset)
-    self._precision = definitions.PRECISION_1_SECOND
+    super(CocoaTime, self).__init__(
+        precision=precision or definitions.PRECISION_1_SECOND,
+        time_zone_offset=time_zone_offset)
     self._timestamp = timestamp
 
   @property
@@ -123,8 +127,8 @@ class CocoaTime(interface.DateTimeValues):
     microseconds = int(
         (self._timestamp % 1) * definitions.MICROSECONDS_PER_SECOND)
 
-    return '{0:04d}-{1:02d}-{2:02d} {3:02d}:{4:02d}:{5:02d}.{6:06d}'.format(
-        year, month, day_of_month, hours, minutes, seconds, microseconds)
+    return (f'{year:04d}-{month:02d}-{day_of_month:02d} '
+            f'{hours:02d}:{minutes:02d}:{seconds:02d}.{microseconds:06d}')
 
 
 factory.Factory.RegisterDateTimeValues(CocoaTime)
diff --git a/dfdatetime/decorators.py b/dfdatetime/decorators.py
index dca6760..ff3398d 100644
--- a/dfdatetime/decorators.py
+++ b/dfdatetime/decorators.py
@@ -10,8 +10,9 @@ def deprecated(function):  # pylint: disable=invalid-name
   def IssueDeprecationWarning(*args, **kwargs):
     """Issue a deprecation warning."""
     warnings.simplefilter('default', DeprecationWarning)
-    warnings.warn('Call to deprecated function: {0:s}.'.format(
-        function.__name__), category=DeprecationWarning, stacklevel=2)
+    warnings.warn(
+        f'Call to deprecated function: {function.__name__:s}.',
+        category=DeprecationWarning, stacklevel=2)
 
     return function(*args, **kwargs)
 
diff --git a/dfdatetime/definitions.py b/dfdatetime/definitions.py
index b38bdac..071677f 100644
--- a/dfdatetime/definitions.py
+++ b/dfdatetime/definitions.py
@@ -7,6 +7,8 @@ Also see:
   https://en.wikipedia.org/wiki/Minute
 """
 
+DAYS_PER_MONTH = (31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)
+
 SECONDS_PER_DAY = 24 * 60 * 60
 
 DECISECONDS_PER_SECOND = 10
@@ -18,14 +20,19 @@ MICROSECONDS_PER_SECOND = 1000000
 MICROSECONDS_PER_DECISECOND = 100000
 MICROSECONDS_PER_MILLISECOND = 1000
 
+NANOSECONDS_PER_MICROSECOND = 1000
 NANOSECONDS_PER_SECOND = 1000000000
 
 PRECISION_1_DAY = '1d'
 PRECISION_1_HOUR = '1h'
 PRECISION_1_NANOSECOND = '1ns'
+PRECISION_10_NANOSECONDS = '10s'
 PRECISION_100_NANOSECONDS = '100ns'
 PRECISION_1_MICROSECOND = '1us'
+PRECISION_10_MICROSECONDS = '10us'
+PRECISION_100_MICROSECONDS = '100us'
 PRECISION_1_MILLISECOND = '1ms'
+PRECISION_10_MILLISECONDS = '10ms'
 PRECISION_100_MILLISECONDS = '100ms'
 PRECISION_1_MINUTE = '1min'
 PRECISION_1_SECOND = '1s'
@@ -35,10 +42,45 @@ PRECISION_VALUES = frozenset([
     PRECISION_1_DAY,
     PRECISION_1_HOUR,
     PRECISION_1_NANOSECOND,
+    PRECISION_10_NANOSECONDS,
     PRECISION_100_NANOSECONDS,
     PRECISION_1_MICROSECOND,
+    PRECISION_10_MICROSECONDS,
+    PRECISION_100_MICROSECONDS,
     PRECISION_1_MILLISECOND,
+    PRECISION_10_MILLISECONDS,
     PRECISION_100_MILLISECONDS,
     PRECISION_1_MINUTE,
     PRECISION_1_SECOND,
     PRECISION_2_SECONDS])
+
+# Create a days per century lookup table.
+DAYS_PER_CENTURY = {}
+for year in range(-10000, 10000, 100):
+  if (year % 4 == 0 and year % 100 != 0) or year % 400 == 0:
+    number_of_days = 36525
+  else:
+    number_of_days = 36524
+  DAYS_PER_CENTURY[year] = number_of_days
+
+# Create a days per year lookup table.
+DAYS_PER_YEAR = {}
+for year in range(-10000, 10000, 1):
+  if (year % 4 == 0 and year % 100 != 0) or year % 400 == 0:
+    number_of_days = 366
+  else:
+    number_of_days = 365
+  DAYS_PER_YEAR[year] = number_of_days
+
+# Create a days per year in POSIX epoch lookup table.
+DAYS_PER_YEAR_IN_POSIX_EPOCH = {}
+
+number_of_days = 0
+for year in range(1969, -10000, -1):
+  number_of_days -= DAYS_PER_YEAR[year]
+  DAYS_PER_YEAR_IN_POSIX_EPOCH[year] = number_of_days
+
+number_of_days = 0
+for year in range(1970, 10000, 1):
+  DAYS_PER_YEAR_IN_POSIX_EPOCH[year] = number_of_days
+  number_of_days += DAYS_PER_YEAR[year]
diff --git a/dfdatetime/delphi_date_time.py b/dfdatetime/delphi_date_time.py
index 8527dc9..49b8a80 100644
--- a/dfdatetime/delphi_date_time.py
+++ b/dfdatetime/delphi_date_time.py
@@ -27,26 +27,30 @@ class DelphiDateTime(interface.DateTimeValues):
   9999-12-31 23:59:59.999
 
   Also see:
-    http://docwiki.embarcadero.com/Libraries/XE3/en/System.TDateTime
+    https://docwiki.embarcadero.com/Libraries/XE3/en/System.TDateTime
 
   Attributes:
     is_local_time (bool): True if the date and time value is in local time.
   """
+
   # The difference between December 30, 1899 and January 1, 1970 in days.
   _DELPHI_TO_POSIX_BASE = 25569
 
   _EPOCH = DelphiDateTimeEpoch()
 
-  def __init__(self, time_zone_offset=None, timestamp=None):
+  def __init__(self, precision=None, time_zone_offset=None, timestamp=None):
     """Initializes a Delphi TDateTime timestamp.
 
     Args:
+      precision (Optional[str]): precision of the date and time value, which
+          should be one of the PRECISION_VALUES in definitions.
       time_zone_offset (Optional[int]): time zone offset in number of minutes
           from UTC or None if not set.
       timestamp (Optional[float]): Delphi TDateTime timestamp.
     """
-    super(DelphiDateTime, self).__init__(time_zone_offset=time_zone_offset)
-    self._precision = definitions.PRECISION_1_MILLISECOND
+    super(DelphiDateTime, self).__init__(
+        precision=precision or definitions.PRECISION_1_MILLISECOND,
+        time_zone_offset=time_zone_offset)
     self._timestamp = timestamp
 
   @property
@@ -101,7 +105,7 @@ class DelphiDateTime(interface.DateTimeValues):
     time_zone_offset = date_time_values.get('time_zone_offset', 0)
 
     if year > 9999:
-      raise ValueError('Unsupported year value: {0:d}.'.format(year))
+      raise ValueError(f'Unsupported year value: {year:d}.')
 
     timestamp = self._GetNumberOfSecondsFromElements(
         year, month, day_of_month, hours, minutes, seconds)
@@ -136,8 +140,8 @@ class DelphiDateTime(interface.DateTimeValues):
     microseconds = int(
         (number_of_seconds % 1) * definitions.MICROSECONDS_PER_SECOND)
 
-    return '{0:04d}-{1:02d}-{2:02d} {3:02d}:{4:02d}:{5:02d}.{6:06d}'.format(
-        year, month, day_of_month, hours, minutes, seconds, microseconds)
+    return (f'{year:04d}-{month:02d}-{day_of_month:02d} '
+            f'{hours:02d}:{minutes:02d}:{seconds:02d}.{microseconds:06d}')
 
 
 factory.Factory.RegisterDateTimeValues(DelphiDateTime)
diff --git a/dfdatetime/dotnet_datetime.py b/dfdatetime/dotnet_datetime.py
new file mode 100644
index 0000000..c76b0f5
--- /dev/null
+++ b/dfdatetime/dotnet_datetime.py
@@ -0,0 +1,136 @@
+# -*- coding: utf-8 -*-
+""".NET DateTime implementation."""
+
+import decimal
+
+from dfdatetime import definitions
+from dfdatetime import factory
+from dfdatetime import interface
+
+
+class DotNetDateTimeEpoch(interface.DateTimeEpoch):
+  """.NET DateTime epoch."""
+
+  def __init__(self):
+    """Initializes a .NET DateTime epoch."""
+    super(DotNetDateTimeEpoch, self).__init__(1, 1, 1)
+
+
+class DotNetDateTime(interface.DateTimeValues):
+  """.NET DateTime ticks.
+
+  The .NET DateTime timestamp is a 64-bit signed integer that contains the date
+  and time as the number of 100 nanoseconds since 12:00 AM January 1, year
+  1 A.D. in the proleptic Gregorian Calendar.
+  """
+
+  _EPOCH = DotNetDateTimeEpoch()
+
+  # The difference between January 1, 1 and January 1, 1970 in seconds.
+  _DOTNET_TO_POSIX_BASE =  (
+      ((1969 * 365) + (1969 // 4) - (1969 // 100) + (1969 // 400)) *
+      definitions.SECONDS_PER_DAY)
+
+  def __init__(self, precision=None, time_zone_offset=None, timestamp=None):
+    """Initializes a .NET DateTime timestamp.
+
+    Args:
+      precision (Optional[str]): precision of the date and time value, which
+          should be one of the PRECISION_VALUES in definitions.
+      time_zone_offset (Optional[int]): time zone offset in number of minutes
+          from UTC or None if not set.
+      timestamp (Optional[int]): .NET DateTime ticks.
+    """
+    super(DotNetDateTime, self).__init__(
+        precision=precision or definitions.PRECISION_100_NANOSECONDS,
+        time_zone_offset=time_zone_offset)
+    self._timestamp = timestamp or 0
+
+  @property
+  def timestamp(self):
+    """integer: .NET DateTime timestamp or None if not set."""
+    return self._timestamp
+
+  def _GetNormalizedTimestamp(self):
+    """Retrieves the normalized timestamp.
+
+    Returns:
+      decimal.Decimal: normalized timestamp, which contains the number of
+          seconds since January 1, 1970 00:00:00 and a fraction of second used
+          for increased precision, or None if the normalized timestamp cannot be
+          determined.
+    """
+    if self._normalized_timestamp is None:
+      if self._timestamp is not None:
+        self._normalized_timestamp = (
+            decimal.Decimal(self._timestamp) / self._100_NANOSECONDS_PER_SECOND)
+        self._normalized_timestamp -= self._DOTNET_TO_POSIX_BASE
+
+        if self._time_zone_offset:
+          self._normalized_timestamp -= self._time_zone_offset * 60
+
+    return self._normalized_timestamp
+
+  def CopyFromDateTimeString(self, time_string):
+    """Copies a .NET DateTime timestamp from a string.
+
+    Args:
+      time_string (str): date and time value formatted as:
+          YYYY-MM-DD hh:mm:ss.######[+-]##:##
+
+          Where # are numeric digits ranging from 0 to 9 and the seconds
+          fraction can be either 3 or 6 digits. The time of day, seconds
+          fraction and time zone offset are optional. The default time zone
+          is UTC.
+
+    Raises:
+      ValueError: if the time string is invalid or not supported.
+    """
+    date_time_values = self._CopyDateTimeFromString(time_string)
+
+    year = date_time_values.get('year', 0)
+    month = date_time_values.get('month', 0)
+    day_of_month = date_time_values.get('day_of_month', 0)
+    hours = date_time_values.get('hours', 0)
+    minutes = date_time_values.get('minutes', 0)
+    seconds = date_time_values.get('seconds', 0)
+    microseconds = date_time_values.get('microseconds', 0)
+    time_zone_offset = date_time_values.get('time_zone_offset', 0)
+
+    if year > 9999:
+      raise ValueError(f'Unsupported year value: {year:d}.')
+
+    timestamp = self._GetNumberOfSecondsFromElements(
+        year, month, day_of_month, hours, minutes, seconds)
+    timestamp += self._DOTNET_TO_POSIX_BASE
+    timestamp *= definitions.MICROSECONDS_PER_SECOND
+    timestamp += microseconds
+    timestamp *= self._100_NANOSECONDS_PER_MICROSECOND
+
+    self._normalized_timestamp = None
+    self._timestamp = timestamp
+    self._time_zone_offset = time_zone_offset
+
+  def CopyToDateTimeString(self):
+    """Copies the .NET DateTime timestamp to a date and time string.
+
+    Returns:
+      str: date and time value formatted as: "YYYY-MM-DD hh:mm:ss.######" or
+          None if the timestamp is missing.
+    """
+    if (self._timestamp is None or self._timestamp < 0 or
+        self._timestamp > self._UINT64_MAX):
+      return None
+
+    timestamp, fraction_of_second = divmod(
+        self._timestamp, self._100_NANOSECONDS_PER_SECOND)
+    number_of_days, hours, minutes, seconds = self._GetTimeValues(timestamp)
+
+    year, month, day_of_month = self._GetDateValuesWithEpoch(
+        number_of_days, self._EPOCH)
+
+    return (f'{year:04d}-{month:02d}-{day_of_month:02d} '
+            f'{hours:02d}:{minutes:02d}:{seconds:02d}.{fraction_of_second:07d}')
+
+
+factory.Factory.RegisterDateTimeValues(DotNetDateTime)
diff --git a/dfdatetime/factory.py b/dfdatetime/factory.py
index ddce52f..ce55e2d 100644
--- a/dfdatetime/factory.py
+++ b/dfdatetime/factory.py
@@ -19,8 +19,7 @@ class Factory(object):
     """
     class_name = date_time_values_type.__name__
     if class_name not in cls._date_time_values_types:
-      raise KeyError('Date and time values type: {0:s} not set.'.format(
-          class_name))
+      raise KeyError(f'Date and time values type: {class_name:s} not set.')
 
     del cls._date_time_values_types[class_name]
 
@@ -39,8 +38,7 @@ class Factory(object):
       KeyError: if date and time values is not registered.
     """
     if class_name not in cls._date_time_values_types:
-      raise KeyError('Date and time values type: {0:s} not set.'.format(
-          class_name))
+      raise KeyError(f'Date and time values type: {class_name:s} not set.')
 
     date_time_values_type = cls._date_time_values_types[class_name]
     return date_time_values_type(**kwargs)
@@ -57,7 +55,6 @@ class Factory(object):
     """
     class_name = date_time_values_type.__name__
     if class_name in cls._date_time_values_types:
-      raise KeyError('Date and time values type: {0:s} already set.'.format(
-          class_name))
+      raise KeyError(f'Date and time values type: {class_name:s} already set.')
 
     cls._date_time_values_types[class_name] = date_time_values_type
diff --git a/dfdatetime/fake_time.py b/dfdatetime/fake_time.py
index d058743..83594db 100644
--- a/dfdatetime/fake_time.py
+++ b/dfdatetime/fake_time.py
@@ -21,21 +21,24 @@ class FakeTime(interface.DateTimeValues):
 
   _EPOCH = posix_time.PosixTimeEpoch()
 
-  def __init__(self, time_zone_offset=None):
+  def __init__(self, precision=None, time_zone_offset=None):
     """Initializes a fake timestamp.
 
     Args:
+      precision (Optional[str]): precision of the date and time value, which
+          should be one of the PRECISION_VALUES in definitions.
       time_zone_offset (Optional[int]): time zone offset in number of minutes
           from UTC or None if not set.
     """
     # Note that time.time() and divmod return floating point values.
     timestamp, fraction_of_second = divmod(time.time(), 1)
 
-    super(FakeTime, self).__init__(time_zone_offset=time_zone_offset)
+    super(FakeTime, self).__init__(
+        precision=precision or definitions.PRECISION_1_MICROSECOND,
+        time_zone_offset=time_zone_offset)
     self._microseconds = int(
         fraction_of_second * definitions.MICROSECONDS_PER_SECOND)
     self._number_of_seconds = int(timestamp)
-    self._precision = definitions.PRECISION_1_MICROSECOND
 
   def _GetNormalizedTimestamp(self):
     """Retrieves the normalized timestamp.
@@ -103,10 +106,12 @@ class FakeTime(interface.DateTimeValues):
     year, month, day_of_month = self._GetDateValuesWithEpoch(
         number_of_days, self._EPOCH)
 
-    if self._microseconds is None:
-      return '{0:04d}-{1:02d}-{2:02d} {3:02d}:{4:02d}:{5:02d}'.format(
-          year, month, day_of_month, hours, minutes, seconds)
+    date_time_string = (
+        f'{year:04d}-{month:02d}-{day_of_month:02d} '
+        f'{hours:02d}:{minutes:02d}:{seconds:02d}')
 
-    return '{0:04d}-{1:02d}-{2:02d} {3:02d}:{4:02d}:{5:02d}.{6:06d}'.format(
-        year, month, day_of_month, hours, minutes, seconds,
-        self._microseconds)
+    if self._microseconds is not None:
+      date_time_string = '.'.join([
+          date_time_string, f'{self._microseconds:06d}'])
+
+    return date_time_string
diff --git a/dfdatetime/fat_date_time.py b/dfdatetime/fat_date_time.py
index fa10aab..4a58bfa 100644
--- a/dfdatetime/fat_date_time.py
+++ b/dfdatetime/fat_date_time.py
@@ -43,22 +43,24 @@ class FATDateTime(interface.DateTimeValues):
   # The difference between January 1, 1980 and January 1, 1970 in seconds.
   _FAT_DATE_TO_POSIX_BASE = 315532800
 
-  def __init__(self, fat_date_time=None, time_zone_offset=None):
+  def __init__(self, fat_date_time=None, precision=None, time_zone_offset=None):
     """Initializes a FAT date time.
 
     Args:
       fat_date_time (Optional[int]): FAT date time.
+      precision (Optional[str]): precision of the date and time value, which
+          should be one of the PRECISION_VALUES in definitions.
       time_zone_offset (Optional[int]): time zone offset in number of minutes
           from UTC or None if not set.
     """
-    number_of_seconds = None
-    if fat_date_time is not None:
-      number_of_seconds = self._GetNumberOfSeconds(fat_date_time)
-
-    super(FATDateTime, self).__init__(time_zone_offset=time_zone_offset)
+    super(FATDateTime, self).__init__(
+        precision=precision or definitions.PRECISION_2_SECONDS,
+        time_zone_offset=time_zone_offset)
     self._fat_date_time = fat_date_time
-    self._number_of_seconds = number_of_seconds
-    self._precision = definitions.PRECISION_2_SECONDS
+    self._number_of_seconds = None
+
+    if fat_date_time is not None:
+      self._number_of_seconds = self._GetNumberOfSeconds(fat_date_time)
 
   @property
   def fat_date_time(self):
@@ -156,7 +158,7 @@ class FATDateTime(interface.DateTimeValues):
     time_zone_offset = date_time_values.get('time_zone_offset', 0)
 
     if year < 1980 or year > (1980 + 0x7f):
-      raise ValueError('Year value not supported: {0!s}.'.format(year))
+      raise ValueError(f'Year value not supported: {year!s}.')
 
     self._normalized_timestamp = None
     self._number_of_seconds = self._GetNumberOfSecondsFromElements(
@@ -180,8 +182,126 @@ class FATDateTime(interface.DateTimeValues):
     year, month, day_of_month = self._GetDateValuesWithEpoch(
         number_of_days, self._EPOCH)
 
-    return '{0:04d}-{1:02d}-{2:02d} {3:02d}:{4:02d}:{5:02d}'.format(
+    return (f'{year:04d}-{month:02d}-{day_of_month:02d} '
+            f'{hours:02d}:{minutes:02d}:{seconds:02d}')
+
+
+class FATTimestamp(interface.DateTimeValues):
+  """FAT timestamp.
+
+  The FAT timestamp is an unsigned integer that contains the number of
+  10 milli seconds intervals since 1980-01-01 00:00:00 (also known as
+  the FAT date time epoch).
+
+  Attributes:
+    is_local_time (bool): True if the date and time value is in local time.
+  """
+
+  _EPOCH = FATDateTimeEpoch()
+
+  # The difference between January 1, 1980 and January 1, 1970 in seconds.
+  _FAT_DATE_TO_POSIX_BASE = 315532800
+
+  def __init__(self, precision=None, time_zone_offset=None, timestamp=None):
+    """Initializes a FAT timestamp.
+
+    Args:
+      precision (Optional[str]): precision of the date and time value, which
+          should be one of the PRECISION_VALUES in definitions.
+      time_zone_offset (Optional[int]): time zone offset in number of minutes
+          from UTC or None if not set.
+      timestamp (Optional[int]): FAT timestamp.
+    """
+    super(FATTimestamp, self).__init__(
+        precision=precision or definitions.PRECISION_10_MILLISECONDS,
+        time_zone_offset=time_zone_offset)
+    self._timestamp = timestamp
+
+  @property
+  def timestamp(self):
+    """int: FAT timestamp or None if not set."""
+    return self._timestamp
+
+  def _GetNormalizedTimestamp(self):
+    """Retrieves the normalized timestamp.
+
+    Returns:
+      decimal.Decimal: normalized timestamp, which contains the number of
+          seconds since January 1, 1970 00:00:00 and a fraction of second used
+          for increased precision, or None if the normalized timestamp cannot be
+          determined.
+    """
+    if self._normalized_timestamp is None:
+      if self._timestamp is not None:
+        self._normalized_timestamp = (
+            (decimal.Decimal(self._timestamp) / 100) +
+            self._FAT_DATE_TO_POSIX_BASE)
+
+        if self._time_zone_offset:
+          self._normalized_timestamp -= self._time_zone_offset * 60
+
+    return self._normalized_timestamp
+
+  def CopyFromDateTimeString(self, time_string):
+    """Copies a FAT timestamp from a date and time string.
+
+    Args:
+      time_string (str): date and time value formatted as:
+          YYYY-MM-DD hh:mm:ss.######[+-]##:##
+
+          Where # are numeric digits ranging from 0 to 9 and the seconds
+          fraction can be either 3 or 6 digits. The time of day, seconds
+          fraction and time zone offset are optional. The default time zone
+          is UTC.
+
+    Raises:
+      ValueError: if the time string is invalid or not supported.
+    """
+    date_time_values = self._CopyDateTimeFromString(time_string)
+
+    year = date_time_values.get('year', 0)
+    month = date_time_values.get('month', 0)
+    day_of_month = date_time_values.get('day_of_month', 0)
+    hours = date_time_values.get('hours', 0)
+    minutes = date_time_values.get('minutes', 0)
+    seconds = date_time_values.get('seconds', 0)
+    microseconds = date_time_values.get('microseconds', 0)
+    time_zone_offset = date_time_values.get('time_zone_offset', 0)
+
+    if year < 1980 or year > (1980 + 0x7f):
+      raise ValueError(f'Year value not supported: {year!s}.')
+
+    timestamp = self._GetNumberOfSecondsFromElements(
         year, month, day_of_month, hours, minutes, seconds)
+    timestamp -= self._FAT_DATE_TO_POSIX_BASE
+    timestamp *= 100
+
+    if microseconds:
+      milliseconds, _ = divmod(microseconds, 10000)
+      timestamp += milliseconds
+
+    self._timestamp = timestamp
+    self._time_zone_offset = time_zone_offset
+
+  def CopyToDateTimeString(self):
+    """Copies the FAT timestamp to a date and time string.
+
+    Returns:
+      str: date and time value formatted as: "YYYY-MM-DD hh:mm:ss.######" or
+          None if the timestamp is missing.
+    """
+    if self._timestamp is None:
+      return None
+
+    timestamp, milliseconds = divmod(self._timestamp, 100)
+    number_of_days, hours, minutes, seconds = self._GetTimeValues(timestamp)
+
+    year, month, day_of_month = self._GetDateValuesWithEpoch(
+        number_of_days, self._EPOCH)
+
+    return (f'{year:04d}-{month:02d}-{day_of_month:02d} '
+            f'{hours:02d}:{minutes:02d}:{seconds:02d}.{milliseconds:02d}')
 
 
 factory.Factory.RegisterDateTimeValues(FATDateTime)
+factory.Factory.RegisterDateTimeValues(FATTimestamp)
diff --git a/dfdatetime/filetime.py b/dfdatetime/filetime.py
index f8f16e2..c29cacc 100644
--- a/dfdatetime/filetime.py
+++ b/dfdatetime/filetime.py
@@ -34,16 +34,19 @@ class Filetime(interface.DateTimeValues):
   # The difference between January 1, 1601 and January 1, 1970 in seconds.
   _FILETIME_TO_POSIX_BASE = 11644473600
 
-  def __init__(self, time_zone_offset=None, timestamp=None):
+  def __init__(self, precision=None, time_zone_offset=None, timestamp=None):
     """Initializes a FILETIME timestamp.
 
     Args:
+      precision (Optional[str]): precision of the date and time value, which
+          should be one of the PRECISION_VALUES in definitions.
       time_zone_offset (Optional[int]): time zone offset in number of minutes
           from UTC or None if not set.
       timestamp (Optional[int]): FILETIME timestamp.
     """
-    super(Filetime, self).__init__(time_zone_offset=time_zone_offset)
-    self._precision = definitions.PRECISION_100_NANOSECONDS
+    super(Filetime, self).__init__(
+        precision=precision or definitions.PRECISION_100_NANOSECONDS,
+        time_zone_offset=time_zone_offset)
     self._timestamp = timestamp
 
   @property
@@ -64,7 +67,7 @@ class Filetime(interface.DateTimeValues):
       if (self._timestamp is not None and self._timestamp >= 0 and
           self._timestamp <= self._UINT64_MAX):
         self._normalized_timestamp = (
-            decimal.Decimal(self._timestamp) / self._100NS_PER_SECOND)
+            decimal.Decimal(self._timestamp) / self._100_NANOSECONDS_PER_SECOND)
         self._normalized_timestamp -= self._FILETIME_TO_POSIX_BASE
 
         if self._time_zone_offset:
@@ -98,14 +101,14 @@ class Filetime(interface.DateTimeValues):
     time_zone_offset = date_time_values.get('time_zone_offset', 0)
 
     if year < 1601:
-      raise ValueError('Year value not supported: {0!s}.'.format(year))
+      raise ValueError(f'Year value not supported: {year!s}.')
 
     timestamp = self._GetNumberOfSecondsFromElements(
         year, month, day_of_month, hours, minutes, seconds)
     timestamp += self._FILETIME_TO_POSIX_BASE
     timestamp *= definitions.MICROSECONDS_PER_SECOND
     timestamp += date_time_values.get('microseconds', 0)
-    timestamp *= self._100NS_PER_MICROSECOND
+    timestamp *= self._100_NANOSECONDS_PER_MICROSECOND
 
     self._normalized_timestamp = None
     self._timestamp = timestamp
@@ -122,14 +125,15 @@ class Filetime(interface.DateTimeValues):
         self._timestamp > self._UINT64_MAX):
       return None
 
-    timestamp, remainder = divmod(self._timestamp, self._100NS_PER_SECOND)
+    timestamp, fraction_of_second = divmod(
+        self._timestamp, self._100_NANOSECONDS_PER_SECOND)
     number_of_days, hours, minutes, seconds = self._GetTimeValues(timestamp)
 
     year, month, day_of_month = self._GetDateValuesWithEpoch(
         number_of_days, self._EPOCH)
 
-    return '{0:04d}-{1:02d}-{2:02d} {3:02d}:{4:02d}:{5:02d}.{6:07d}'.format(
-        year, month, day_of_month, hours, minutes, seconds, remainder)
+    return (f'{year:04d}-{month:02d}-{day_of_month:02d} '
+            f'{hours:02d}:{minutes:02d}:{seconds:02d}.{fraction_of_second:07d}')
 
 
 factory.Factory.RegisterDateTimeValues(Filetime)
diff --git a/dfdatetime/golang_time.py b/dfdatetime/golang_time.py
new file mode 100644
index 0000000..c34d25a
--- /dev/null
+++ b/dfdatetime/golang_time.py
@@ -0,0 +1,215 @@
+# -*- coding: utf-8 -*-
+"""Golang time.Time timestamp implementation."""
+
+import decimal
+import struct
+
+from dfdatetime import definitions
+from dfdatetime import factory
+from dfdatetime import interface
+
+
+class GolangTimeEpoch(interface.DateTimeEpoch):
+  """Golang time.Time epoch."""
+
+  def __init__(self):
+    """Initializes a Golang time.Time epoch."""
+    super(GolangTimeEpoch, self).__init__(1, 1, 1)
+
+
+class GolangTime(interface.DateTimeValues):
+  """Golang time.Time timestamp.
+
+  A Golang time.Time timestamp contains the number of nanoseconds since
+  January 1, 1 UTC. Depending on the version of the timestamp, the time
+  zone is stored in minutes or seconds relative to UTC.
+
+  A serialized version 1 Golang time.Time timestamp is a 15 byte value
+  that consists of:
+
+  * byte 0 - version as an 8-bit integer.
+  * bytes 1-8 - number of seconds since January 1, 1 as a big-endian signed
+      integer.
+  * bytes 9-12 - fraction of second, number of nanoseconds as a big-endian
+      signed integer.
+  * bytes 13-14 - time zone offset in minutes as a 16-bit big-endian integer,
+      where -1 represents UTC.
+
+  A serialized version 2 Golang time.Time timestamp is a 16 byte value
+  that consists of:
+
+  * byte 0 - version as an 8-bit integer.
+  * bytes 1-8 - number of seconds since January 1, 1 as a big-endian signed
+      integer.
+  * bytes 9-12 - fraction of second, number of nanoseconds as a big-endian
+      signed integer.
+  * bytes 13-14 - time zone offset in minutes as a 16-bit big-endian integer,
+      where -1 represents UTC.
+  * byte 15 - time zone offset in seconds as an 8-bit integer.
+
+  Attributes:
+    is_local_time (bool): True if the date and time value is in local time
+  """
+
+  # The delta between January 1, 1970 (unix epoch) and January 1, 1
+  # (Golang epoch).
+  _GOLANG_TO_POSIX_BASE = (
+      ((1969 * 365) + (1969 // 4) - (1969 // 100) + (1969 // 400)) *
+      definitions.SECONDS_PER_DAY)
+
+  _EPOCH = GolangTimeEpoch()
+
+  def __init__(self, golang_timestamp=None, precision=None):
+    """Initializes a Golang time.Time timestamp.
+
+    Args:
+      golang_timestamp (Optional[bytes]): the Golang time.Time timestamp.
+      precision (Optional[str]): precision of the date and time value, which
+          should be one of the PRECISION_VALUES in definitions.
+    """
+    number_of_seconds, nanoseconds, time_zone_offset = (None, None, None)
+    if golang_timestamp is not None:
+      number_of_seconds, nanoseconds, time_zone_offset = (
+          self._GetNumberOfSeconds(golang_timestamp))
+
+    super(GolangTime, self).__init__(
+        precision=precision or definitions.PRECISION_1_NANOSECOND,
+        time_zone_offset=time_zone_offset)
+    self._golang_timestamp = golang_timestamp
+    self._nanoseconds = nanoseconds
+    self._number_of_seconds = number_of_seconds
+
+  @property
+  def golang_timestamp(self):
+    """int: Golang time.Time timestamp or None if not set."""
+    return self._golang_timestamp
+
+  def _GetNormalizedTimestamp(self):
+    """Retrieves the normalized timestamp.
+
+    Returns:
+      decimal.Decimal: normalized timestamp, which contains the number of
+          seconds since January 1, 1970 00:00:00 and a fraction of second used
+          for increased precision, or None if the normalized timestamp cannot be
+          determined.
+    """
+    if self._normalized_timestamp is None:
+      if (self._number_of_seconds is not None and
+          self._number_of_seconds >= self._GOLANG_TO_POSIX_BASE and
+          self._nanoseconds is not None and self._nanoseconds >= 0):
+
+        self._normalized_timestamp = decimal.Decimal(
+            self._number_of_seconds - GolangTime._GOLANG_TO_POSIX_BASE)
+
+        if self._nanoseconds is not None and self._nanoseconds >= 0:
+          self._normalized_timestamp += (
+              decimal.Decimal(self._nanoseconds) /
+              definitions.NANOSECONDS_PER_SECOND)
+
+        if self._time_zone_offset:
+          self._normalized_timestamp -= self._time_zone_offset * 60
+
+    return self._normalized_timestamp
+
+  def _GetNumberOfSeconds(self, golang_timestamp):
+    """Retrieves the number of seconds from a Golang time.Time timestamp.
+
+    Args:
+      golang_timestamp (bytes): the Golang time.Time timestamp.
+
+    Returns:
+      tuple[int, int, int]: number of seconds since January 1, 1 00:00:00,
+          fraction of second in nanoseconds and time zone offset in minutes.
+
+    Raises:
+      ValueError: if the Golang time.Time timestamp could not be parsed.
+    """
+    byte_size = len(golang_timestamp)
+    if byte_size < 15:
+      raise ValueError('Unsupported Golang time.Time timestamp.')
+
+    version = golang_timestamp[0]
+    if version not in (1, 2):
+      raise ValueError(
+          f'Unsupported Golang time.Time timestamp version: {version:d}.')
+
+    if (version == 1 and byte_size != 15) or (version == 2 and byte_size != 16):
+      raise ValueError('Unsupported Golang time.Time timestamp.')
+
+    try:
+      number_of_seconds, nanoseconds, time_zone_offset = struct.unpack(
+          '>qih', golang_timestamp[1:15])
+
+      # TODO: add support for version 2 time zone offset in seconds
+
+    except struct.error as exception:
+      raise ValueError((
+          f'Unable to unpacked Golang time.Time timestamp with error: '
+          f'{exception!s}'))
+
+    # A time zone offset of -1 minute is a special representation for UTC.
+    if time_zone_offset == -1:
+      time_zone_offset = 0
+
+    return number_of_seconds, nanoseconds, time_zone_offset
+
+  def CopyFromDateTimeString(self, time_string):
+    """Copies a date time value from a date and time string.
+
+    Args:
+      time_string (str): date and time value formatted as:
+          YYYY-MM-DD hh:mm:ss.######[+-]##:##
+
+          Where # are numeric digits ranging from 0 to 9 and the seconds
+          fraction can be either 3 or 6 digits. The time of day, seconds
+          fraction and time zone offset are optional. The default time zone
+          is UTC.
+
+    Raises:
+      ValueError: if the time string is invalid or not supported.
+    """
+    date_time_values = self._CopyDateTimeFromString(time_string)
+    year = date_time_values.get('year', 0)
+    month = date_time_values.get('month', 0)
+    day_of_month = date_time_values.get('day_of_month', 0)
+    hours = date_time_values.get('hours', 0)
+    minutes = date_time_values.get('minutes', 0)
+    seconds = date_time_values.get('seconds', 0)
+    microseconds = date_time_values.get('microseconds', 0)
+    time_zone_offset = date_time_values.get('time_zone_offset', 0)
+
+    if year < 0:
+      raise ValueError(f'Year value not supported: {year!s}.')
+
+    seconds = self._GetNumberOfSecondsFromElements(
+        year, month, day_of_month, hours, minutes, seconds)
+
+    seconds += self._GOLANG_TO_POSIX_BASE
+    nanoseconds = microseconds * definitions.NANOSECONDS_PER_MICROSECOND
+
+    self._normalized_timestamp = None
+    self._number_of_seconds = seconds
+    self._nanoseconds = nanoseconds
+    self._time_zone_offset = time_zone_offset
+
+  def CopyToDateTimeString(self):
+    """Copies the Golang time value to a date and time string.
+
+    Returns:
+      str: date and time value formatted as: "YYYY-MM-DD hh:mm:ss.######" or
+          None if the timestamp cannot be copied to a date and time string.
+    """
+    if self._number_of_seconds is None or self._number_of_seconds < 0:
+      return None
+
+    number_of_days, hours, minutes, seconds = self._GetTimeValues(
+        self._number_of_seconds)
+
+    year, month, day_of_month = self._GetDateValuesWithEpoch(
+        number_of_days, self._EPOCH)
+
+    return (f'{year:04d}-{month:02d}-{day_of_month:02d} '
+            f'{hours:02d}:{minutes:02d}:{seconds:02d}.{self._nanoseconds:09d}')
+
+
+factory.Factory.RegisterDateTimeValues(GolangTime)
diff --git a/dfdatetime/hfs_time.py b/dfdatetime/hfs_time.py
index f8fcafc..7a24d86 100644
--- a/dfdatetime/hfs_time.py
+++ b/dfdatetime/hfs_time.py
@@ -26,21 +26,25 @@ class HFSTime(interface.DateTimeValues):
   Attributes:
     is_local_time (bool): True if the date and time value is in local time.
   """
+
   _EPOCH = HFSTimeEpoch()
 
   # The difference between Jan 1, 1904 and Jan 1, 1970 in seconds.
   _HFS_TO_POSIX_BASE = 2082844800
 
-  def __init__(self, time_zone_offset=None, timestamp=None):
+  def __init__(self, precision=None, time_zone_offset=None, timestamp=None):
     """Initializes a HFS timestamp.
 
     Args:
+      precision (Optional[str]): precision of the date and time value, which
+          should be one of the PRECISION_VALUES in definitions.
       time_zone_offset (Optional[int]): time zone offset in number of minutes
           from UTC or None if not set.
       timestamp (Optional[int]): HFS timestamp.
     """
-    super(HFSTime, self).__init__(time_zone_offset=time_zone_offset)
-    self._precision = definitions.PRECISION_1_SECOND
+    super(HFSTime, self).__init__(
+        precision=precision or definitions.PRECISION_1_SECOND,
+        time_zone_offset=time_zone_offset)
     self._timestamp = timestamp
 
   @property
@@ -119,8 +123,8 @@ class HFSTime(interface.DateTimeValues):
     year, month, day_of_month = self._GetDateValuesWithEpoch(
         number_of_days, self._EPOCH)
 
-    return '{0:04d}-{1:02d}-{2:02d} {3:02d}:{4:02d}:{5:02d}'.format(
-        year, month, day_of_month, hours, minutes, seconds)
+    return (f'{year:04d}-{month:02d}-{day_of_month:02d} '
+            f'{hours:02d}:{minutes:02d}:{seconds:02d}')
 
 
 factory.Factory.RegisterDateTimeValues(HFSTime)
diff --git a/dfdatetime/interface.py b/dfdatetime/interface.py
index be28da8..6fac76a 100644
--- a/dfdatetime/interface.py
+++ b/dfdatetime/interface.py
@@ -2,10 +2,8 @@
 """Date and time interfaces."""
 
 import abc
-import calendar
 import decimal
 
-from dfdatetime import decorators
 from dfdatetime import definitions
 
 
@@ -53,18 +51,25 @@ class DateTimeValues(object):
 
   Attributes:
     is_local_time (bool): True if the date and time value is in local time.
+    time_zone_hint (str): time zone hint, such as "Europe/Amsterdam", "CET" or
+        "UTC+1", or None if not set.
   """
 
   # pylint: disable=redundant-returns-doc
 
-  _DAYS_PER_MONTH = (31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)
-
   _EPOCH_NORMALIZED_TIME = NormalizedTimeEpoch()
 
-  _100NS_PER_SECOND = 10000000
-  _100NS_PER_DECISECOND = 1000000
-  _100NS_PER_MILLISECOND = 10000
-  _100NS_PER_MICROSECOND = 10
+  _100_MILLISECONDS_PER_SECOND = 10
+  _10_MILLISECONDS_PER_SECOND = 100
+  _1_MILLISECOND_PER_SECOND = 1000
+  _100_MICROSECONDS_PER_SECOND = 10000
+  _10_MICROSECONDS_PER_SECOND = 100000
+  _1_MICROSECOND_PER_SECOND = 1000000
+  _100_NANOSECONDS_PER_SECOND = 10000000
+  _10_NANOSECONDS_PER_SECOND = 100000000
+  _1_NANOSECOND_PER_SECOND = definitions.NANOSECONDS_PER_SECOND
+
+  _100_NANOSECONDS_PER_MICROSECOND = 10
 
   _INT64_MIN = -(1 << 63)
   _INT64_MAX = (1 << 63) - 1
@@ -73,19 +78,44 @@ class DateTimeValues(object):
   _UINT60_MAX = (1 << 60) - 1
   _UINT64_MAX = (1 << 64) - 1
 
-  def __init__(self, time_zone_offset=None):
+  _REMAINDER_MULTIPLIER = {
+      definitions.PRECISION_1_MILLISECOND: _1_MILLISECOND_PER_SECOND,
+      definitions.PRECISION_10_MILLISECONDS: _10_MILLISECONDS_PER_SECOND,
+      definitions.PRECISION_100_MILLISECONDS: _100_MILLISECONDS_PER_SECOND,
+      definitions.PRECISION_1_MICROSECOND: _1_MICROSECOND_PER_SECOND,
+      definitions.PRECISION_10_MICROSECONDS: _10_MICROSECONDS_PER_SECOND,
+      definitions.PRECISION_100_MICROSECONDS: _100_MICROSECONDS_PER_SECOND,
+      definitions.PRECISION_1_NANOSECOND: _1_NANOSECOND_PER_SECOND,
+      definitions.PRECISION_10_NANOSECONDS: _10_NANOSECONDS_PER_SECOND,
+      definitions.PRECISION_100_NANOSECONDS: _100_NANOSECONDS_PER_SECOND}
+
+  def __init__(self, is_delta=False, precision=None, time_zone_offset=None):
     """Initializes date time values.
 
     Args:
+      is_delta (Optional[bool]): True if the date and time value is relative to
+          another date and time value.
+      precision (Optional[str]): precision of the date and time value, which
+          should be one of the PRECISION_VALUES in definitions.
       time_zone_offset (Optional[int]): time zone offset in number of minutes
           from UTC or None if not set.
     """
     super(DateTimeValues, self).__init__()
+    self._cached_date_time_values = None
+    self._is_delta = is_delta
     self._normalized_timestamp = None
-    self._precision = None
+    self._precision = precision
     self._time_zone_offset = time_zone_offset
 
     self.is_local_time = False
+    self.time_zone_hint = False
+
+  @property
+  def is_delta(self):
+    """is_delta (bool): True if the date and time value is relative to another
+        date and time value.
+    """
+    return self._is_delta
 
   @property
   def precision(self):
@@ -101,6 +131,17 @@ class DateTimeValues(object):
     """
     return self._time_zone_offset
 
+  @time_zone_offset.setter
+  def time_zone_offset(self, time_zone_offset):
+    """Sets the time zone offset.
+
+    Args:
+      time_zone_offset (int): time zone offset in number of minutes from UTC or
+          None if not set.
+    """
+    self._normalized_timestamp = None
+    self._time_zone_offset = time_zone_offset
+
   def __eq__(self, other):
     """Determines if the date time values are equal to other.
 
@@ -382,7 +423,7 @@ class DateTimeValues(object):
       raise ValueError('Unable to parse hours.')
 
     if hours not in range(0, 24):
-      raise ValueError('Hours value: {0:d} out of bounds.'.format(hours))
+      raise ValueError(f'Hours value: {hours:d} out of bounds.')
 
     try:
       minutes = int(time_string[3:5], 10)
@@ -390,7 +431,7 @@ class DateTimeValues(object):
       raise ValueError('Unable to parse minutes.')
 
     if minutes not in range(0, 60):
-      raise ValueError('Minutes value: {0:d} out of bounds.'.format(minutes))
+      raise ValueError(f'Minutes value: {minutes:d} out of bounds.')
 
     try:
       seconds = int(time_string[6:8], 10)
@@ -399,7 +440,7 @@ class DateTimeValues(object):
 
     # TODO: support a leap second?
     if seconds not in range(0, 60):
-      raise ValueError('Seconds value: {0:d} out of bounds.'.format(seconds))
+      raise ValueError(f'Seconds value: {seconds:d} out of bounds.')
 
     microseconds = None
     time_zone_offset = None
@@ -483,17 +524,15 @@ class DateTimeValues(object):
           of bounds.
     """
     if epoch_year < 0:
-      raise ValueError('Epoch year value: {0:d} out of bounds.'.format(
-          epoch_year))
+      raise ValueError(f'Epoch year value: {epoch_year:d} out of bounds.')
 
     if epoch_month not in range(1, 13):
-      raise ValueError('Epoch month value: {0:d} out of bounds.'.format(
-          epoch_month))
+      raise ValueError(f'Epoch month value: {epoch_month:d} out of bounds.')
 
     epoch_days_per_month = self._GetDaysPerMonth(epoch_year, epoch_month)
     if epoch_day_of_month < 1 or epoch_day_of_month > epoch_days_per_month:
-      raise ValueError('Epoch day of month value: {0:d} out of bounds.'.format(
-          epoch_day_of_month))
+      raise ValueError(
+          f'Epoch day of month value: {epoch_day_of_month:d} out of bounds.')
 
     before_epoch = number_of_days < 0
 
@@ -602,6 +641,38 @@ class DateTimeValues(object):
         number_of_days, date_time_epoch.year, date_time_epoch.month,
         date_time_epoch.day_of_month)
 
+  def _GetDateWithTimeOfDay(self):
+    """Retrieves the date with time of day.
+
+    Note that the date and time are adjusted to UTC.
+
+    Returns:
+       tuple[int, int, int, int, int, int]: year, month, day of month, hours,
+           minutes, seconds or (None, None, None, None, None, None)
+           if the date and time values do not represent a date or time of day.
+    """
+    normalized_timestamp = self._GetNormalizedTimestamp()
+    if normalized_timestamp is None:
+      return None, None, None, None, None, None
+
+    if (not self._cached_date_time_values or
+        self._cached_date_time_values[0] != normalized_timestamp):
+      number_of_days, hours, minutes, seconds = self._GetTimeValues(
+          normalized_timestamp)
+
+      try:
+        year, month, day_of_month = self._GetDateValuesWithEpoch(
+            number_of_days, self._EPOCH_NORMALIZED_TIME)
+
+      except ValueError:
+        return None, None, None, None, None, None
+
+      self._cached_date_time_values = (
+          normalized_timestamp, year, month, day_of_month, hours, minutes,
+          seconds)
+
+    return self._cached_date_time_values[1:]
+
   def _GetDayOfYear(self, year, month, day_of_month):
     """Retrieves the day of the year for a specific day of a month in a year.
 
@@ -645,8 +716,8 @@ class DateTimeValues(object):
     if month not in range(1, 13):
       raise ValueError('Month value out of bounds.')
 
-    days_per_month = self._DAYS_PER_MONTH[month - 1]
-    if month == 2 and self._IsLeapYear(year):
+    days_per_month = definitions.DAYS_PER_MONTH[month - 1]
+    if month == 2 and (self._is_delta or self._IsLeapYear(year)):
       days_per_month += 1
 
     return days_per_month
@@ -678,6 +749,11 @@ class DateTimeValues(object):
       raise ValueError('Year value out of bounds.')
 
     year, _ = divmod(year, 100)
+    year *= 100
+
+    number_of_days = definitions.DAYS_PER_CENTURY.get(year, None)
+    if number_of_days is not None:
+      return number_of_days
 
     if self._IsLeapYear(year):
       return 36525
@@ -692,6 +768,10 @@ class DateTimeValues(object):
     Returns:
       int: number of days in the year.
     """
+    number_of_days = definitions.DAYS_PER_YEAR.get(year, None)
+    if number_of_days is not None:
+      return number_of_days
+
     if self._IsLeapYear(year):
       return 366
     return 365
@@ -715,38 +795,44 @@ class DateTimeValues(object):
     Raises:
       ValueError: if the time elements are invalid.
     """
-    if not year or not month or not day_of_month:
+    if not month or not day_of_month:
       return None
 
-    # calendar.timegm does not sanity check the time elements.
     if hours is None:
       hours = 0
     elif hours not in range(0, 24):
-      raise ValueError('Hours value: {0!s} out of bounds.'.format(hours))
+      raise ValueError(f'Hours value: {hours!s} out of bounds.')
 
     if minutes is None:
       minutes = 0
     elif minutes not in range(0, 60):
-      raise ValueError('Minutes value: {0!s} out of bounds.'.format(minutes))
+      raise ValueError(f'Minutes value: {minutes!s} out of bounds.')
 
     # TODO: support a leap second?
     if seconds is None:
       seconds = 0
     elif seconds not in range(0, 60):
-      raise ValueError('Seconds value: {0!s} out of bounds.'.format(seconds))
+      raise ValueError(f'Seconds value: {seconds!s} out of bounds.')
+
+    number_of_days = definitions.DAYS_PER_YEAR_IN_POSIX_EPOCH.get(year, None)
+    if number_of_days is None:
+      raise ValueError(f'Year value: {year!s} out of bounds.')
+
+    number_of_days += sum([
+        definitions.DAYS_PER_MONTH[index] for index in range(month - 1)])
+    if month > 2 and self._IsLeapYear(year):
+      number_of_days += 1
 
-    # Note that calendar.timegm() does not raise when date is: 2013-02-29.
     days_per_month = self._GetDaysPerMonth(year, month)
     if day_of_month < 1 or day_of_month > days_per_month:
-      raise ValueError('Day of month value out of bounds.')
-
-    # calendar.timegm requires the time tuple to contain at least
-    # 6 integer values.
-    time_elements_tuple = (year, month, day_of_month, hours, minutes, seconds)
+      raise ValueError(f'Day of month value: {day_of_month:d} out of bounds.')
 
-    number_of_seconds = calendar.timegm(time_elements_tuple)
+    number_of_days += day_of_month - 1
+    number_of_hours = (number_of_days * 24) + hours
+    number_of_minutes = (number_of_hours * 60) + minutes
+    number_of_seconds = (number_of_minutes * 60) + seconds
 
-    return int(number_of_seconds)
+    return number_of_seconds
 
   def _GetTimeValues(self, number_of_seconds):
     """Determines time values.
@@ -775,24 +861,6 @@ class DateTimeValues(object):
     # pylint: disable=consider-using-ternary
     return (year % 4 == 0 and year % 100 != 0) or year % 400 == 0
 
-  @decorators.deprecated
-  def CopyFromString(self, time_string):
-    """Copies a date time value from a date and time string.
-
-    Args:
-      time_string (str): date and time value formatted as:
-          YYYY-MM-DD hh:mm:ss.######[+-]##:##
-
-          Where # are numeric digits ranging from 0 to 9 and the seconds
-          fraction can be either 3 or 6 digits. The time of day, seconds
-          fraction and time zone offset are optional. The default time zone
-          is UTC.
-
-    Raises:
-      ValueError: if the time string is invalid or not supported.
-    """
-    self.CopyFromDateTimeString(time_string)
-
   @abc.abstractmethod
   def CopyFromDateTimeString(self, time_string):
     """Copies a date time value from a date and time string.
@@ -822,29 +890,24 @@ class DateTimeValues(object):
 
     return int(normalized_timestamp)
 
-  # TODO: remove this method when there is no more need for it in dfvfs.
-  def CopyToStatTimeTuple(self):
-    """Copies the date time value to a stat timestamp tuple.
+  def CopyToPosixTimestampWithFractionOfSecond(self):
+    """Copies the date time value to a POSIX timestamp with fraction of second.
 
     Returns:
-      tuple[int, int]: a POSIX timestamp in seconds and the remainder in
-          100 nano seconds or (None, None) on error.
+      tuple[int, int]: a POSIX timestamp in seconds with fraction of second or
+          None, None if no timestamp is available.
     """
     normalized_timestamp = self._GetNormalizedTimestamp()
     if normalized_timestamp is None:
       return None, None
 
-    if self._precision in (
-        definitions.PRECISION_1_NANOSECOND,
-        definitions.PRECISION_100_NANOSECONDS,
-        definitions.PRECISION_1_MICROSECOND,
-        definitions.PRECISION_1_MILLISECOND,
-        definitions.PRECISION_100_MILLISECONDS):
-      remainder = int((normalized_timestamp % 1) * self._100NS_PER_SECOND)
-
-      return int(normalized_timestamp), remainder
+    remainder_multiplier = self._REMAINDER_MULTIPLIER.get(self._precision, None)
+    if remainder_multiplier:
+      remainder = int((normalized_timestamp % 1) * remainder_multiplier)
+    else:
+      remainder = None
 
-    return int(normalized_timestamp), None
+    return int(normalized_timestamp), remainder
 
   @abc.abstractmethod
   def CopyToDateTimeString(self):
@@ -865,56 +928,50 @@ class DateTimeValues(object):
     date_time_string = self.CopyToDateTimeString()
     if date_time_string:
       date_time_string = date_time_string.replace(' ', 'T')
-      date_time_string = '{0:s}Z'.format(date_time_string)
+
+      if self._time_zone_offset is not None or not self.is_local_time:
+        time_zone_offset_hours, time_zone_offset_minutes = divmod(
+            self._time_zone_offset or 0, 60)
+        if time_zone_offset_hours >= 0:
+          time_zone_offset_sign = '+'
+        else:
+          time_zone_offset_sign = '-'
+          time_zone_offset_hours *= -1
+
+        time_zone_string = (
+            f'{time_zone_offset_hours:02d}:{time_zone_offset_minutes:02d}')
+        date_time_string = time_zone_offset_sign.join([
+            date_time_string, time_zone_string])
+
     return date_time_string
 
   def GetDate(self):
     """Retrieves the date represented by the date and time values.
 
+    Note that the date is adjusted to UTC.
+
     Returns:
        tuple[int, int, int]: year, month, day of month or (None, None, None)
            if the date and time values do not represent a date.
     """
-    normalized_timestamp = self._GetNormalizedTimestamp()
-    if normalized_timestamp is None:
-      return None, None, None
-
-    number_of_days, _, _, _ = self._GetTimeValues(normalized_timestamp)
-
-    try:
-      return self._GetDateValuesWithEpoch(
-          number_of_days, self._EPOCH_NORMALIZED_TIME)
-
-    except ValueError:
-      return None, None, None
+    year, month, day_of_month, _, _, _ = self._GetDateWithTimeOfDay()
+    return year, month, day_of_month
 
   def GetDateWithTimeOfDay(self):
     """Retrieves the date with time of day.
 
+    Note that the date and time are adjusted to UTC.
+
     Returns:
        tuple[int, int, int, int, int, int]: year, month, day of month, hours,
            minutes, seconds or (None, None, None, None, None, None)
            if the date and time values do not represent a date or time of day.
     """
-    normalized_timestamp = self._GetNormalizedTimestamp()
-    if normalized_timestamp is None:
-      return None, None, None, None, None, None
-
-    number_of_days, hours, minutes, seconds = self._GetTimeValues(
-        normalized_timestamp)
-
-    try:
-      year, month, day_of_month = self._GetDateValuesWithEpoch(
-          number_of_days, self._EPOCH_NORMALIZED_TIME)
-
-    except ValueError:
-      return None, None, None, None, None, None
-
-    return year, month, day_of_month, hours, minutes, seconds
+    return self._GetDateWithTimeOfDay()
 
   # TODO: remove this method when there is no more need for it in plaso.
   def GetPlasoTimestamp(self):
-    """Retrieves a timestamp that is compatible with plaso.
+    """Retrieves a timestamp that is compatible with Plaso.
 
     Returns:
       int: a POSIX timestamp in microseconds or None if no timestamp is
@@ -932,13 +989,11 @@ class DateTimeValues(object):
   def GetTimeOfDay(self):
     """Retrieves the time of day represented by the date and time values.
 
+    Note that the time is adjusted to UTC.
+
     Returns:
        tuple[int, int, int]: hours, minutes, seconds or (None, None, None)
            if the date and time values do not represent a time of day.
     """
-    normalized_timestamp = self._GetNormalizedTimestamp()
-    if normalized_timestamp is None:
-      return None, None, None
-
-    _, hours, minutes, seconds = self._GetTimeValues(normalized_timestamp)
+    _, _, _, hours, minutes, seconds = self._GetDateWithTimeOfDay()
     return hours, minutes, seconds
diff --git a/dfdatetime/ole_automation_date.py b/dfdatetime/ole_automation_date.py
index 61023dd..1569bd7 100644
--- a/dfdatetime/ole_automation_date.py
+++ b/dfdatetime/ole_automation_date.py
@@ -35,16 +35,19 @@ class OLEAutomationDate(interface.DateTimeValues):
   # The difference between December 30, 1899 and January 1, 1970 in days.
   _OLE_AUTOMATION_DATE_TO_POSIX_BASE = 25569
 
-  def __init__(self, time_zone_offset=None, timestamp=None):
+  def __init__(self, precision=None, time_zone_offset=None, timestamp=None):
     """Initializes an OLE Automation date.
 
     Args:
+      precision (Optional[str]): precision of the date and time value, which
+          should be one of the PRECISION_VALUES in definitions.
       time_zone_offset (Optional[int]): time zone offset in number of minutes
           from UTC or None if not set.
       timestamp (Optional[float]): OLE Automation date.
     """
-    super(OLEAutomationDate, self).__init__(time_zone_offset=time_zone_offset)
-    self._precision = definitions.PRECISION_1_MICROSECOND
+    super(OLEAutomationDate, self).__init__(
+        precision=precision or definitions.PRECISION_1_MICROSECOND,
+        time_zone_offset=time_zone_offset)
     self._timestamp = timestamp
 
   @property
@@ -133,8 +136,8 @@ class OLEAutomationDate(interface.DateTimeValues):
 
     microseconds = int((timestamp % 1) * definitions.MICROSECONDS_PER_SECOND)
 
-    return '{0:04d}-{1:02d}-{2:02d} {3:02d}:{4:02d}:{5:02d}.{6:06d}'.format(
-        year, month, day_of_month, hours, minutes, seconds, microseconds)
+    return (f'{year:04d}-{month:02d}-{day_of_month:02d} '
+            f'{hours:02d}:{minutes:02d}:{seconds:02d}.{microseconds:06d}')
 
 
 factory.Factory.RegisterDateTimeValues(OLEAutomationDate)
diff --git a/dfdatetime/posix_time.py b/dfdatetime/posix_time.py
index c7a369a..a31fd34 100644
--- a/dfdatetime/posix_time.py
+++ b/dfdatetime/posix_time.py
@@ -32,16 +32,19 @@ class PosixTime(interface.DateTimeValues):
 
   _EPOCH = PosixTimeEpoch()
 
-  def __init__(self, time_zone_offset=None, timestamp=None):
+  def __init__(self, precision=None, time_zone_offset=None, timestamp=None):
     """Initializes a POSIX timestamp.
 
     Args:
+      precision (Optional[str]): precision of the date and time value, which
+          should be one of the PRECISION_VALUES in definitions.
       time_zone_offset (Optional[int]): time zone offset in number of minutes
           from UTC or None if not set.
       timestamp (Optional[int]): POSIX timestamp.
     """
-    super(PosixTime, self).__init__(time_zone_offset=time_zone_offset)
-    self._precision = definitions.PRECISION_1_SECOND
+    super(PosixTime, self).__init__(
+        precision=precision or definitions.PRECISION_1_SECOND,
+        time_zone_offset=time_zone_offset)
     self._timestamp = timestamp
 
   @property
@@ -109,8 +112,8 @@ class PosixTime(interface.DateTimeValues):
     year, month, day_of_month = self._GetDateValuesWithEpoch(
         number_of_days, self._EPOCH)
 
-    return '{0:04d}-{1:02d}-{2:02d} {3:02d}:{4:02d}:{5:02d}'.format(
-        year, month, day_of_month, hours, minutes, seconds)
+    return (f'{year:04d}-{month:02d}-{day_of_month:02d} '
+            f'{hours:02d}:{minutes:02d}:{seconds:02d}')
 
 
 class PosixTimeInMilliseconds(interface.DateTimeValues):
@@ -124,17 +127,19 @@ class PosixTimeInMilliseconds(interface.DateTimeValues):
 
   _EPOCH = PosixTimeEpoch()
 
-  def __init__(self, time_zone_offset=None, timestamp=None):
+  def __init__(self, precision=None, time_zone_offset=None, timestamp=None):
     """Initializes a POSIX timestamp in milliseconds.
 
     Args:
+      precision (Optional[str]): precision of the date and time value, which
+          should be one of the PRECISION_VALUES in definitions.
       time_zone_offset (Optional[int]): time zone offset in number of minutes
           from UTC or None if not set.
       timestamp (Optional[int]): POSIX timestamp in milliseconds.
     """
     super(PosixTimeInMilliseconds, self).__init__(
+        precision=precision or definitions.PRECISION_1_MILLISECOND,
         time_zone_offset=time_zone_offset)
-    self._precision = definitions.PRECISION_1_MILLISECOND
     self._timestamp = timestamp
 
   @property
@@ -214,8 +219,8 @@ class PosixTimeInMilliseconds(interface.DateTimeValues):
     year, month, day_of_month = self._GetDateValuesWithEpoch(
         number_of_days, self._EPOCH)
 
-    return '{0:04d}-{1:02d}-{2:02d} {3:02d}:{4:02d}:{5:02d}.{6:03d}'.format(
-        year, month, day_of_month, hours, minutes, seconds, milliseconds)
+    return (f'{year:04d}-{month:02d}-{day_of_month:02d} '
+            f'{hours:02d}:{minutes:02d}:{seconds:02d}.{milliseconds:03d}')
 
 
 class PosixTimeInMicroseconds(interface.DateTimeValues):
@@ -229,17 +234,19 @@ class PosixTimeInMicroseconds(interface.DateTimeValues):
 
   _EPOCH = PosixTimeEpoch()
 
-  def __init__(self, time_zone_offset=None, timestamp=None):
+  def __init__(self, precision=None, time_zone_offset=None, timestamp=None):
     """Initializes a POSIX timestamp in microseconds.
 
     Args:
+      precision (Optional[str]): precision of the date and time value, which
+          should be one of the PRECISION_VALUES in definitions.
       time_zone_offset (Optional[int]): time zone offset in number of minutes
           from UTC or None if not set.
       timestamp (Optional[int]): POSIX timestamp in microseconds.
     """
     super(PosixTimeInMicroseconds, self).__init__(
+        precision=precision or definitions.PRECISION_1_MICROSECOND,
         time_zone_offset=time_zone_offset)
-    self._precision = definitions.PRECISION_1_MICROSECOND
     self._timestamp = timestamp
 
   @property
@@ -315,8 +322,8 @@ class PosixTimeInMicroseconds(interface.DateTimeValues):
     year, month, day_of_month = self._GetDateValuesWithEpoch(
         number_of_days, self._EPOCH)
 
-    return '{0:04d}-{1:02d}-{2:02d} {3:02d}:{4:02d}:{5:02d}.{6:06d}'.format(
-        year, month, day_of_month, hours, minutes, seconds, microseconds)
+    return (f'{year:04d}-{month:02d}-{day_of_month:02d} '
+            f'{hours:02d}:{minutes:02d}:{seconds:02d}.{microseconds:06d}')
 
 
 class PosixTimeInNanoseconds(interface.DateTimeValues):
@@ -330,17 +337,19 @@ class PosixTimeInNanoseconds(interface.DateTimeValues):
 
   _EPOCH = PosixTimeEpoch()
 
-  def __init__(self, time_zone_offset=None, timestamp=None):
+  def __init__(self, precision=None, time_zone_offset=None, timestamp=None):
     """Initializes a POSIX timestamp in nanoseconds.
 
     Args:
+      precision (Optional[str]): precision of the date and time value, which
+          should be one of the PRECISION_VALUES in definitions.
       time_zone_offset (Optional[int]): time zone offset in number of minutes
           from UTC or None if not set.
       timestamp (Optional[int]): POSIX timestamp in nanoseconds.
     """
     super(PosixTimeInNanoseconds, self).__init__(
+        precision=precision or definitions.PRECISION_1_NANOSECOND,
         time_zone_offset=time_zone_offset)
-    self._precision = definitions.PRECISION_1_NANOSECOND
     self._timestamp = timestamp
 
   @property
@@ -434,8 +443,8 @@ class PosixTimeInNanoseconds(interface.DateTimeValues):
     year, month, day_of_month = self._GetDateValuesWithEpoch(
         number_of_days, self._EPOCH)
 
-    return '{0:04d}-{1:02d}-{2:02d} {3:02d}:{4:02d}:{5:02d}.{6:09d}'.format(
-        year, month, day_of_month, hours, minutes, seconds, nanoseconds)
+    return (f'{year:04d}-{month:02d}-{day_of_month:02d} '
+            f'{hours:02d}:{minutes:02d}:{seconds:02d}.{nanoseconds:09d}')
 
   def CopyToDateTimeString(self):
     """Copies the POSIX timestamp to a date and time string.
diff --git a/dfdatetime/precisions.py b/dfdatetime/precisions.py
index 991d1d1..9a79274 100644
--- a/dfdatetime/precisions.py
+++ b/dfdatetime/precisions.py
@@ -67,8 +67,7 @@ class SecondsPrecisionHelper(DateTimePrecisionHelper):
     """
     if microseconds < 0 or microseconds >= definitions.MICROSECONDS_PER_SECOND:
       raise ValueError(
-          'Number of microseconds value: {0:d} out of bounds.'.format(
-              microseconds))
+          f'Number of microseconds value: {microseconds:d} out of bounds.')
 
     return decimal.Decimal(0.0)
 
@@ -91,12 +90,13 @@ class SecondsPrecisionHelper(DateTimePrecisionHelper):
       ValueError: if the fraction of second is out of bounds.
     """
     if fraction_of_second < 0.0 or fraction_of_second >= 1.0:
-      raise ValueError('Fraction of second value: {0:f} out of bounds.'.format(
-          fraction_of_second))
+      raise ValueError(
+          f'Fraction of second value: {fraction_of_second:f} out of bounds.')
+
+    year, month, day_of_month, hours, minutes, seconds = time_elements_tuple
 
-    return '{0:04d}-{1:02d}-{2:02d} {3:02d}:{4:02d}:{5:02d}'.format(
-        time_elements_tuple[0], time_elements_tuple[1], time_elements_tuple[2],
-        time_elements_tuple[3], time_elements_tuple[4], time_elements_tuple[5])
+    return (f'{year:04d}-{month:02d}-{day_of_month:02d} '
+            f'{hours:02d}:{minutes:02d}:{seconds:02d}')
 
 
 class MillisecondsPrecisionHelper(DateTimePrecisionHelper):
@@ -118,8 +118,7 @@ class MillisecondsPrecisionHelper(DateTimePrecisionHelper):
     """
     if microseconds < 0 or microseconds >= definitions.MICROSECONDS_PER_SECOND:
       raise ValueError(
-          'Number of microseconds value: {0:d} out of bounds.'.format(
-              microseconds))
+          f'Number of microseconds value: {microseconds:d} out of bounds.')
 
     milliseconds, _ = divmod(
         microseconds, definitions.MICROSECONDS_PER_MILLISECOND)
@@ -144,15 +143,14 @@ class MillisecondsPrecisionHelper(DateTimePrecisionHelper):
       ValueError: if the fraction of second is out of bounds.
     """
     if fraction_of_second < 0.0 or fraction_of_second >= 1.0:
-      raise ValueError('Fraction of second value: {0:f} out of bounds.'.format(
-          fraction_of_second))
+      raise ValueError(
+          f'Fraction of second value: {fraction_of_second:f} out of bounds.')
 
+    year, month, day_of_month, hours, minutes, seconds = time_elements_tuple
     milliseconds = int(fraction_of_second * definitions.MILLISECONDS_PER_SECOND)
 
-    return '{0:04d}-{1:02d}-{2:02d} {3:02d}:{4:02d}:{5:02d}.{6:03d}'.format(
-        time_elements_tuple[0], time_elements_tuple[1], time_elements_tuple[2],
-        time_elements_tuple[3], time_elements_tuple[4], time_elements_tuple[5],
-        milliseconds)
+    return (f'{year:04d}-{month:02d}-{day_of_month:02d} '
+            f'{hours:02d}:{minutes:02d}:{seconds:02d}.{milliseconds:03d}')
 
 
 class MicrosecondsPrecisionHelper(DateTimePrecisionHelper):
@@ -174,8 +172,7 @@ class MicrosecondsPrecisionHelper(DateTimePrecisionHelper):
     """
     if microseconds < 0 or microseconds >= definitions.MICROSECONDS_PER_SECOND:
       raise ValueError(
-          'Number of microseconds value: {0:d} out of bounds.'.format(
-              microseconds))
+          f'Number of microseconds value: {microseconds:d} out of bounds.')
 
     return decimal.Decimal(microseconds) / definitions.MICROSECONDS_PER_SECOND
 
@@ -198,15 +195,14 @@ class MicrosecondsPrecisionHelper(DateTimePrecisionHelper):
       ValueError: if the fraction of second is out of bounds.
     """
     if fraction_of_second < 0.0 or fraction_of_second >= 1.0:
-      raise ValueError('Fraction of second value: {0:f} out of bounds.'.format(
-          fraction_of_second))
+      raise ValueError(
+          f'Fraction of second value: {fraction_of_second:f} out of bounds.')
 
+    year, month, day_of_month, hours, minutes, seconds = time_elements_tuple
     microseconds = int(fraction_of_second * definitions.MICROSECONDS_PER_SECOND)
 
-    return '{0:04d}-{1:02d}-{2:02d} {3:02d}:{4:02d}:{5:02d}.{6:06d}'.format(
-        time_elements_tuple[0], time_elements_tuple[1], time_elements_tuple[2],
-        time_elements_tuple[3], time_elements_tuple[4], time_elements_tuple[5],
-        microseconds)
+    return (f'{year:04d}-{month:02d}-{day_of_month:02d} '
+            f'{hours:02d}:{minutes:02d}:{seconds:02d}.{microseconds:06d}')
 
 
 class PrecisionHelperFactory(object):
@@ -215,8 +211,7 @@ class PrecisionHelperFactory(object):
   _PRECISION_CLASSES = {
       definitions.PRECISION_1_MICROSECOND: MicrosecondsPrecisionHelper,
       definitions.PRECISION_1_MILLISECOND: MillisecondsPrecisionHelper,
-      definitions.PRECISION_1_SECOND: SecondsPrecisionHelper,
-  }
+      definitions.PRECISION_1_SECOND: SecondsPrecisionHelper}
 
   @classmethod
   def CreatePrecisionHelper(cls, precision):
@@ -234,6 +229,6 @@ class PrecisionHelperFactory(object):
     """
     precision_helper_class = cls._PRECISION_CLASSES.get(precision, None)
     if not precision_helper_class:
-      raise ValueError('Unsupported precision: {0!s}'.format(precision))
+      raise ValueError(f'Unsupported precision: {precision!s}')
 
     return precision_helper_class
diff --git a/dfdatetime/rfc2579_date_time.py b/dfdatetime/rfc2579_date_time.py
index f18d62d..e404871 100644
--- a/dfdatetime/rfc2579_date_time.py
+++ b/dfdatetime/rfc2579_date_time.py
@@ -27,7 +27,7 @@ class RFC2579DateTime(interface.DateTimeValues):
   }
 
   Also see:
-    https://tools.ietf.org/html/rfc2579
+    https://datatracker.ietf.org/doc/html/rfc2579
 
   Attributes:
     year (int): year, 0 through 65536.
@@ -44,12 +44,14 @@ class RFC2579DateTime(interface.DateTimeValues):
 
   # pylint: disable=missing-type-doc
 
-  def __init__(self, rfc2579_date_time_tuple=None):
+  def __init__(self, precision=None, rfc2579_date_time_tuple=None):
     """Initializes a RFC2579 date-time.
 
     Args:
+      precision (Optional[str]): precision of the date and time value, which
+          should be one of the PRECISION_VALUES in definitions.
       rfc2579_date_time_tuple:
-          (Optional[tuple[int, int, int, int, int, int, int, int, int, int]]):
+          (Optional[tuple[int, int, int, int, int, int, int, str, int, int]]):
           RFC2579 date-time time, contains year, month, day of month, hours,
           minutes, seconds and deciseconds, and time zone offset in hours and
           minutes from UTC.
@@ -57,14 +59,14 @@ class RFC2579DateTime(interface.DateTimeValues):
     Raises:
       ValueError: if the system time is invalid.
     """
-    super(RFC2579DateTime, self).__init__()
+    super(RFC2579DateTime, self).__init__(
+        precision=precision or definitions.PRECISION_100_MILLISECONDS)
     self._day_of_month = None
     self._deciseconds = None
     self._hours = None
     self._minutes = None
     self._month = None
     self._number_of_seconds = None
-    self._precision = definitions.PRECISION_100_MILLISECONDS
     self._seconds = None
     self._year = None
 
@@ -213,7 +215,7 @@ class RFC2579DateTime(interface.DateTimeValues):
         microseconds, definitions.MICROSECONDS_PER_DECISECOND)
 
     if year < 0 or year > 65536:
-      raise ValueError('Unsupported year value: {0:d}.'.format(year))
+      raise ValueError(f'Unsupported year value: {year:d}.')
 
     self._normalized_timestamp = None
     self._number_of_seconds = self._GetNumberOfSecondsFromElements(
@@ -238,9 +240,9 @@ class RFC2579DateTime(interface.DateTimeValues):
     if self._number_of_seconds is None:
       return None
 
-    return '{0:04d}-{1:02d}-{2:02d} {3:02d}:{4:02d}:{5:02d}.{6:01d}'.format(
-        self._year, self._month, self._day_of_month, self._hours, self._minutes,
-        self._seconds, self._deciseconds)
+    return (f'{self._year:04d}-{self._month:02d}-{self._day_of_month:02d} '
+            f'{self._hours:02d}:{self._minutes:02d}:{self._seconds:02d}'
+            f'.{self._deciseconds:01d}')
 
 
 factory.Factory.RegisterDateTimeValues(RFC2579DateTime)
diff --git a/dfdatetime/semantic_time.py b/dfdatetime/semantic_time.py
index 06f717f..679cd97 100644
--- a/dfdatetime/semantic_time.py
+++ b/dfdatetime/semantic_time.py
@@ -183,15 +183,6 @@ class SemanticTime(interface.DateTimeValues):
     """
     return None
 
-  def CopyToStatTimeTuple(self):
-    """Copies the semantic timestamp to a stat timestamp tuple.
-
-    Returns:
-      tuple[int, int]: a POSIX timestamp in seconds and the remainder in
-          100 nano seconds, which will always be None, None.
-    """
-    return None, None
-
   def GetPlasoTimestamp(self):
     """Retrieves a timestamp that is compatible with plaso.
 
diff --git a/dfdatetime/serializer.py b/dfdatetime/serializer.py
new file mode 100644
index 0000000..bc18b84
--- /dev/null
+++ b/dfdatetime/serializer.py
@@ -0,0 +1,218 @@
+# -*- coding: utf-8 -*-
+"""The date and time values serializer."""
+
+from dfdatetime import factory
+from dfdatetime import interface
+
+
+class Serializer(object):
+  """Date and time values serializer."""
+
+  @classmethod
+  def ConvertDictToDateTimeValues(cls, json_dict):
+    """Converts a JSON dict into a date time values object.
+
+    This method is deprecated use ConvertJSONToDateTimeValues instead.
+
+    The dictionary of the JSON serialized objects consists of:
+    {
+        '__type__': 'DateTimeValues'
+        '__class_name__': 'RFC2579DateTime'
+        ...
+    }
+
+    Here '__type__' indicates the object base type. In this case this should
+    be 'DateTimeValues'. The rest of the elements of the dictionary make up the
+    date time values object properties.
+
+    Args:
+      json_dict (dict[str, object]): JSON serialized objects.
+
+    Returns:
+      dfdatetime.DateTimeValues: date and time values.
+    """
+    return cls.ConvertJSONToDateTimeValues(json_dict)
+
+  @classmethod
+  def ConvertDateTimeValuesToDict(cls, date_time_values):
+    """Converts a date and time values object into a JSON dictionary.
+
+    This method is deprecated use ConvertDateTimeValuesToJSON instead.
+
+    The resulting dictionary of the JSON serialized objects consists of:
+    {
+        '__type__': 'DateTimeValues'
+        '__class_name__': 'RFC2579DateTime'
+        ...
+    }
+
+    Here '__type__' indicates the object base type. In this case
+    'DateTimeValues'. The rest of the elements of the dictionary make up the
+    date and time value object properties.
+
+    Args:
+      date_time_values (dfdatetime.DateTimeValues): date and time values.
+
+    Returns:
+      dict[str, object]: JSON serialized objects.
+
+    Raises:
+      TypeError: if object is not an instance of DateTimeValues.
+    """
+    if not isinstance(date_time_values, interface.DateTimeValues):
+      raise TypeError
+
+    return cls.ConvertDateTimeValuesToJSON(date_time_values)
+
+  @classmethod
+  def ConvertDateTimeValuesToJSON(cls, date_time_values):
+    """Converts a date and time values object into a JSON dictionary.
+
+    The resulting dictionary of the JSON serialized objects consists of:
+    {
+        '__type__': 'DateTimeValues'
+        '__class_name__': 'RFC2579DateTime'
+        ...
+    }
+
+    Here '__type__' indicates the object base type. In this case
+    'DateTimeValues'. The rest of the elements of the dictionary make up the
+    date and time value object properties.
+
+    Args:
+      date_time_values (dfdatetime.DateTimeValues): date and time values.
+
+    Returns:
+      dict[str, object]: JSON serialized objects.
+    """
+    class_name = type(date_time_values).__name__
+
+    json_dict = {
+        '__class_name__': class_name,
+        '__type__': 'DateTimeValues'}
+
+    if hasattr(date_time_values, 'timestamp'):
+      json_dict['timestamp'] = date_time_values.timestamp
+
+    elif hasattr(date_time_values, 'string'):
+      json_dict['string'] = date_time_values.string
+
+    elif class_name == 'FATDateTime':
+      json_dict['fat_date_time'] = date_time_values.fat_date_time
+
+    elif class_name == 'GolangTime':
+      json_dict['golang_timestamp'] = date_time_values.golang_timestamp
+
+    elif class_name == 'RFC2579DateTime':
+      time_zone_hours, time_zone_minutes = divmod(
+          date_time_values.time_zone_offset, 60)
+
+      if date_time_values.time_zone_offset < 0:
+        time_zone_sign = '-'
+        time_zone_hours *= -1
+      else:
+        time_zone_sign = '+'
+
+      json_dict['rfc2579_date_time_tuple'] = (
+          date_time_values.year, date_time_values.month,
+          date_time_values.day_of_month, date_time_values.hours,
+          date_time_values.minutes, date_time_values.seconds,
+          date_time_values.deciseconds, time_zone_sign, time_zone_hours,
+          time_zone_minutes)
+
+    elif class_name == 'TimeElements':
+      json_dict['time_elements_tuple'] = (
+          date_time_values.year, date_time_values.month,
+          date_time_values.day_of_month, date_time_values.hours,
+          date_time_values.minutes, date_time_values.seconds)
+
+    elif class_name == 'TimeElementsInMilliseconds':
+      json_dict['time_elements_tuple'] = (
+          date_time_values.year, date_time_values.month,
+          date_time_values.day_of_month, date_time_values.hours,
+          date_time_values.minutes, date_time_values.seconds,
+          date_time_values.milliseconds)
+
+    elif class_name == 'TimeElementsInMicroseconds':
+      json_dict['time_elements_tuple'] = (
+          date_time_values.year, date_time_values.month,
+          date_time_values.day_of_month, date_time_values.hours,
+          date_time_values.minutes, date_time_values.seconds,
+          date_time_values.microseconds)
+
+    if date_time_values.time_zone_offset is not None and class_name not in (
+        'GolangTime', 'RFC2579DateTime'):
+      json_dict['time_zone_offset'] = date_time_values.time_zone_offset
+
+    if date_time_values.is_delta and class_name in (
+        'TimeElements', 'TimeElementsInMilliseconds',
+        'TimeElementsInMicroseconds'):
+      json_dict['is_delta'] = True
+
+    if date_time_values.is_local_time:
+      json_dict['is_local_time'] = True
+    if date_time_values.time_zone_hint:
+      json_dict['time_zone_hint'] = date_time_values.time_zone_hint
+
+    return json_dict
+
+  @classmethod
+  def ConvertJSONToDateTimeValues(cls, json_dict):
+    """Converts a JSON dict into a date time values object.
+
+    The dictionary of the JSON serialized objects consists of:
+    {
+        '__type__': 'DateTimeValues'
+        '__class_name__': 'RFC2579DateTime'
+        ...
+    }
+
+    Here '__type__' indicates the object base type. In this case this should
+    be 'DateTimeValues'. The rest of the elements of the dictionary make up the
+    date time values object properties.
+
+    Args:
+      json_dict (dict[str, object]): JSON serialized objects.
+
+    Returns:
+      dfdatetime.DateTimeValues: date and time values.
+    """
+    class_name = json_dict.get('__class_name__', None)
+    if class_name:
+      del json_dict['__class_name__']
+
+    # Remove the class type from the JSON dict since we cannot pass it.
+    del json_dict['__type__']
+
+    if class_name not in (
+        'TimeElements', 'TimeElementsInMilliseconds',
+        'TimeElementsInMicroseconds'):
+      is_delta = json_dict.get('is_delta', None)
+      if is_delta is not None:
+        del json_dict['is_delta']
+
+    is_local_time = json_dict.get('is_local_time', None)
+    if is_local_time is not None:
+      del json_dict['is_local_time']
+
+    time_zone_hint = json_dict.get('time_zone_hint', None)
+    if time_zone_hint is not None:
+      del json_dict['time_zone_hint']
+
+    if class_name in ('InvalidTime', 'Never', 'NotSet'):
+      string = json_dict.get('string', None)
+      if string is not None:
+        del json_dict['string']
+
+    if class_name in ('GolangTime', 'RFC2579DateTime'):
+      time_zone_offset = json_dict.get('time_zone_offset', None)
+      if time_zone_offset is not None:
+        del json_dict['time_zone_offset']
+
+    date_time = factory.Factory.NewDateTimeValues(class_name, **json_dict)
+    if is_local_time:
+      date_time.is_local_time = is_local_time
+    if time_zone_hint:
+      date_time.time_zone_hint = time_zone_hint
+
+    return date_time
diff --git a/dfdatetime/systemtime.py b/dfdatetime/systemtime.py
index d0775b4..760e7b5 100644
--- a/dfdatetime/systemtime.py
+++ b/dfdatetime/systemtime.py
@@ -23,24 +23,15 @@ class Systemtime(interface.DateTimeValues):
       WORD second,
       WORD millisecond
   }
-
-  Attributes:
-    year (int): year, 1601 through 30827.
-    month (int): month of year, 1 through 12.
-    day_of_week (int): day of week, 0 through 6.
-    day_of_month (int): day of month, 1 through 31.
-    hours (int): hours, 0 through 23.
-    minutes (int): minutes, 0 through 59.
-    seconds (int): seconds, 0 through 59.
-    milliseconds (int): milliseconds, 0 through 999.
   """
 
-  # TODO: make attributes read-only.
-
-  def __init__(self, system_time_tuple=None, time_zone_offset=None):
+  def __init__(
+      self, precision=None, system_time_tuple=None, time_zone_offset=None):
     """Initializes a SYSTEMTIME structure.
 
     Args:
+      precision (Optional[str]): precision of the date and time value, which
+          should be one of the PRECISION_VALUES in definitions.
       system_time_tuple
           (Optional[tuple[int, int, int, int, int, int, int, int]]):
           system time, contains year, month, day of week, day of month,
@@ -51,17 +42,18 @@ class Systemtime(interface.DateTimeValues):
     Raises:
       ValueError: if the system time is invalid.
     """
-    super(Systemtime, self).__init__(time_zone_offset=time_zone_offset)
+    super(Systemtime, self).__init__(
+        precision=precision or definitions.PRECISION_1_MILLISECOND,
+        time_zone_offset=time_zone_offset)
     self._number_of_seconds = None
-    self._precision = definitions.PRECISION_1_MILLISECOND
-    self.day_of_month = None
-    self.day_of_week = None
-    self.hours = None
-    self.milliseconds = None
-    self.minutes = None
-    self.month = None
-    self.seconds = None
-    self.year = None
+    self._day_of_month = None
+    self._day_of_week = None
+    self._hours = None
+    self._milliseconds = None
+    self._minutes = None
+    self._month = None
+    self._seconds = None
+    self._year = None
 
     if system_time_tuple:
       if len(system_time_tuple) < 8:
@@ -94,18 +86,58 @@ class Systemtime(interface.DateTimeValues):
       if system_time_tuple[7] < 0 or system_time_tuple[7] > 999:
         raise ValueError('Milliseconds value out of bounds.')
 
-      self.day_of_month = system_time_tuple[3]
-      self.day_of_week = system_time_tuple[2]
-      self.hours = system_time_tuple[4]
-      self.milliseconds = system_time_tuple[7]
-      self.minutes = system_time_tuple[5]
-      self.month = system_time_tuple[1]
-      self.seconds = system_time_tuple[6]
-      self.year = system_time_tuple[0]
+      self._day_of_month = system_time_tuple[3]
+      self._day_of_week = system_time_tuple[2]
+      self._hours = system_time_tuple[4]
+      self._milliseconds = system_time_tuple[7]
+      self._minutes = system_time_tuple[5]
+      self._month = system_time_tuple[1]
+      self._seconds = system_time_tuple[6]
+      self._year = system_time_tuple[0]
 
       self._number_of_seconds = self._GetNumberOfSecondsFromElements(
-          self.year, self.month, self.day_of_month, self.hours, self.minutes,
-          self.seconds)
+          self._year, self._month, self._day_of_month, self._hours,
+          self._minutes, self._seconds)
+
+  @property
+  def day_of_month(self):
+    """day_of_month (int): day of month, 1 through 31."""
+    return self._day_of_month
+
+  @property
+  def day_of_week(self):
+    """day_of_week (int): day of week, 0 through 6."""
+    return self._day_of_week
+
+  @property
+  def hours(self):
+    """hours (int): hours, 0 through 23."""
+    return self._hours
+
+  @property
+  def milliseconds(self):
+    """milliseconds (int): milliseconds, 0 through 999."""
+    return self._milliseconds
+
+  @property
+  def minutes(self):
+    """minutes (int): minutes, 0 through 59."""
+    return self._minutes
+
+  @property
+  def month(self):
+    """month (int): month of year, 1 through 12."""
+    return self._month
+
+  @property
+  def seconds(self):
+    """seconds (int): seconds, 0 through 59."""
+    return self._seconds
+
+  @property
+  def year(self):
+    """year (int): year, 1601 through 30827."""
+    return self._year
 
   def _GetNormalizedTimestamp(self):
     """Retrieves the normalized timestamp.
@@ -119,7 +151,7 @@ class Systemtime(interface.DateTimeValues):
     if self._normalized_timestamp is None:
       if self._number_of_seconds is not None:
         self._normalized_timestamp = (
-            decimal.Decimal(self.milliseconds) /
+            decimal.Decimal(self._milliseconds) /
             definitions.MILLISECONDS_PER_SECOND)
         self._normalized_timestamp += decimal.Decimal(self._number_of_seconds)
 
@@ -158,22 +190,22 @@ class Systemtime(interface.DateTimeValues):
         microseconds, definitions.MICROSECONDS_PER_MILLISECOND)
 
     if year < 1601 or year > 30827:
-      raise ValueError('Unsupported year value: {0:d}.'.format(year))
+      raise ValueError(f'Unsupported year value: {year:d}.')
 
     self._normalized_timestamp = None
     self._number_of_seconds = self._GetNumberOfSecondsFromElements(
         year, month, day_of_month, hours, minutes, seconds)
     self._time_zone_offset = time_zone_offset
 
-    self.year = year
-    self.month = month
-    self.day_of_month = day_of_month
+    self._year = year
+    self._month = month
+    self._day_of_month = day_of_month
     # TODO: calculate day of week on demand.
-    self.day_of_week = None
-    self.hours = hours
-    self.minutes = minutes
-    self.seconds = seconds
-    self.milliseconds = milliseconds
+    self._day_of_week = None
+    self._hours = hours
+    self._minutes = minutes
+    self._seconds = seconds
+    self._milliseconds = milliseconds
 
   def CopyToDateTimeString(self):
     """Copies the SYSTEMTIME structure to a date and time string.
@@ -185,9 +217,9 @@ class Systemtime(interface.DateTimeValues):
     if self._number_of_seconds is None:
       return None
 
-    return '{0:04d}-{1:02d}-{2:02d} {3:02d}:{4:02d}:{5:02d}.{6:03d}'.format(
-        self.year, self.month, self.day_of_month, self.hours, self.minutes,
-        self.seconds, self.milliseconds)
+    return (f'{self._year:04d}-{self._month:02d}-{self._day_of_month:02d} '
+            f'{self._hours:02d}:{self._minutes:02d}:{self._seconds:02d}'
+            f'.{self._milliseconds:03d}')
 
 
 factory.Factory.RegisterDateTimeValues(Systemtime)
diff --git a/dfdatetime/time_elements.py b/dfdatetime/time_elements.py
index d0a9bf1..4f035c1 100644
--- a/dfdatetime/time_elements.py
+++ b/dfdatetime/time_elements.py
@@ -74,10 +74,16 @@ class TimeElements(interface.DateTimeValues):
 
   _RFC_WEEKDAYS = frozenset(['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'])
 
-  def __init__(self, time_elements_tuple=None, time_zone_offset=None):
+  def __init__(
+      self, is_delta=False, precision=None, time_elements_tuple=None,
+      time_zone_offset=None):
     """Initializes time elements.
 
     Args:
+      is_delta (Optional[bool]): True if the date and time value is relative to
+          another date and time value.
+      precision (Optional[str]): precision of the date and time value, which
+          should be one of the PRECISION_VALUES in definitions.
       time_elements_tuple (Optional[tuple[int, int, int, int, int, int]]):
           time elements, contains year, month, day of month, hours, minutes and
           seconds.
@@ -87,16 +93,19 @@ class TimeElements(interface.DateTimeValues):
     Raises:
       ValueError: if the time elements tuple is invalid.
     """
-    super(TimeElements, self).__init__(time_zone_offset=time_zone_offset)
+    super(TimeElements, self).__init__(
+        is_delta=is_delta,
+        precision=precision or definitions.PRECISION_1_SECOND,
+        time_zone_offset=time_zone_offset)
     self._number_of_seconds = None
-    self._precision = definitions.PRECISION_1_SECOND
     self._time_elements_tuple = time_elements_tuple
 
     if time_elements_tuple:
-      if len(time_elements_tuple) < 6:
+      number_of_elements = len(time_elements_tuple)
+      if number_of_elements < 6:
         raise ValueError((
-            'Invalid time elements tuple at least 6 elements required,'
-            'got: {0:d}').format(len(time_elements_tuple)))
+            f'Invalid time elements tuple at least 6 elements required,'
+            f'got: {number_of_elements:d}'))
 
       self._number_of_seconds = self._GetNumberOfSecondsFromElements(
           time_elements_tuple[0], time_elements_tuple[1],
@@ -204,7 +213,7 @@ class TimeElements(interface.DateTimeValues):
     if weekday_string.endswith(','):
       weekday_string = weekday_string[:-1]
       if weekday_string not in self._RFC_WEEKDAYS:
-        raise ValueError('Invalid weekday: {0:s}.'.format(weekday_string))
+        raise ValueError(f'Invalid weekday: {weekday_string:s}.')
 
       string_segments.pop(0)
 
@@ -218,14 +227,13 @@ class TimeElements(interface.DateTimeValues):
         pass
 
     if day_of_month == 0:
-      raise ValueError('Invalid day of month: {0:s}.'.format(
-          day_of_month_string))
+      raise ValueError(f'Invalid day of month: {day_of_month_string:s}.')
 
     month_string = string_segments[1]
 
     month = self._RFC_MONTH_MAPPINGS.get(month_string)
     if not month:
-      raise ValueError('Invalid month: {0:s}.'.format(month_string))
+      raise ValueError(f'Invalid month: {month_string:s}.')
 
     year_string = string_segments[2]
 
@@ -237,7 +245,7 @@ class TimeElements(interface.DateTimeValues):
         pass
 
     if year is None:
-      raise ValueError('Invalid year: {0:s}.'.format(year_string))
+      raise ValueError(f'Invalid year: {0:s}.')
 
     year += 1900
 
@@ -286,7 +294,7 @@ class TimeElements(interface.DateTimeValues):
     if weekday_string.endswith(','):
       weekday_string = weekday_string[:-1]
       if weekday_string not in self._RFC_WEEKDAYS:
-        raise ValueError('Invalid weekday: {0:s}.'.format(weekday_string))
+        raise ValueError(f'Invalid weekday: {weekday_string:s}.')
 
       string_segments.pop(0)
 
@@ -300,14 +308,13 @@ class TimeElements(interface.DateTimeValues):
         pass
 
     if day_of_month == 0:
-      raise ValueError('Invalid day of month: {0:s}.'.format(
-          day_of_month_string))
+      raise ValueError(f'Invalid day of month: {day_of_month_string:s}.')
 
     month_string = string_segments[1]
 
     month = self._RFC_MONTH_MAPPINGS.get(month_string)
     if not month:
-      raise ValueError('Invalid month: {0:s}.'.format(month_string))
+      raise ValueError(f'Invalid month: {month_string:s}.')
 
     year_string = string_segments[2]
 
@@ -319,7 +326,7 @@ class TimeElements(interface.DateTimeValues):
         pass
 
     if year is None:
-      raise ValueError('Invalid year: {0:s}.'.format(year_string))
+      raise ValueError(f'Invalid year: {year_string:s}.')
 
     hours, minutes, seconds, time_zone_offset = self._CopyTimeFromStringRFC(
         string_segments[3], string_segments[4])
@@ -379,7 +386,7 @@ class TimeElements(interface.DateTimeValues):
       ValueError: if the time string is invalid or not supported.
     """
     if time_string.endswith('Z'):
-      time_string = time_string[:-1]
+      time_string = ''.join([time_string[:-1], '+00:00'])
 
     time_string_length = len(time_string)
 
@@ -393,7 +400,7 @@ class TimeElements(interface.DateTimeValues):
       raise ValueError('Unable to parse hours.')
 
     if hours not in range(0, 24):
-      raise ValueError('Hours value: {0:d} out of bounds.'.format(hours))
+      raise ValueError(f'Hours value: {hours:d} out of bounds.')
 
     minutes = None
     seconds = None
@@ -478,11 +485,11 @@ class TimeElements(interface.DateTimeValues):
       microseconds = int(time_fraction)
 
     if minutes is not None and minutes not in range(0, 60):
-      raise ValueError('Minutes value: {0:d} out of bounds.'.format(minutes))
+      raise ValueError(f'Minutes value: {minutes:d} out of bounds.')
 
     # TODO: support a leap second?
     if seconds is not None and seconds not in range(0, 60):
-      raise ValueError('Seconds value: {0:d} out of bounds.'.format(seconds))
+      raise ValueError(f'Seconds value: {seconds:d} out of bounds.')
 
     if time_zone_string_index < time_string_length:
       if (time_string_length - time_zone_string_index != 6 or
@@ -549,7 +556,7 @@ class TimeElements(interface.DateTimeValues):
       raise ValueError('Unable to parse hours.')
 
     if hours not in range(0, 24):
-      raise ValueError('Hours value: {0:d} out of bounds.'.format(hours))
+      raise ValueError(f'Hours value: {hours:d} out of bounds.')
 
     try:
       minutes = int(time_string[3:5], 10)
@@ -557,7 +564,7 @@ class TimeElements(interface.DateTimeValues):
       raise ValueError('Unable to parse minutes.')
 
     if minutes not in range(0, 60):
-      raise ValueError('Minutes value: {0:d} out of bounds.'.format(minutes))
+      raise ValueError(f'Minutes value: {minutes:d} out of bounds.')
 
     seconds = None
 
@@ -574,7 +581,7 @@ class TimeElements(interface.DateTimeValues):
         raise ValueError('Unable to parse seconds.')
 
       if seconds not in range(0, 60):
-        raise ValueError('Seconds value: {0:d} out of bounds.'.format(seconds))
+        raise ValueError(f'Seconds value: {seconds:d} out of bounds.')
 
     if time_string_length < 5:
       raise ValueError('Time string too short.')
@@ -587,11 +594,11 @@ class TimeElements(interface.DateTimeValues):
       hours_from_utc = self._RFC_TIME_ZONE_MAPPINGS.get(time_zone_string, None)
       minutes_from_utc = 0
       if hours_from_utc is None:
-        raise ValueError('Invalid time zone: {0:s}.'.format(time_zone_string))
+        raise ValueError(f'Invalid time zone: {time_zone_string:s}.')
 
     else:
       if time_zone_string[0] not in ('+', '-'):
-        raise ValueError('Invalid time zone: {0:s}.'.format(time_zone_string))
+        raise ValueError(f'Invalid time zone: {time_zone_string:s}.')
 
       try:
         hours_from_utc = int(time_zone_string[1:3], 10)
@@ -767,46 +774,48 @@ class TimeElements(interface.DateTimeValues):
     Raises:
       ValueError: if the time elements tuple is invalid.
     """
-    if len(time_elements_tuple) < 6:
+    number_of_elements = len(time_elements_tuple)
+    if number_of_elements < 6:
       raise ValueError((
-          'Invalid time elements tuple at least 6 elements required,'
-          'got: {0:d}').format(len(time_elements_tuple)))
+          f'Invalid time elements tuple at least 6 elements required,'
+          f'got: {number_of_elements:d}'))
+
+    year_string = time_elements_tuple[0]
+    month_string = time_elements_tuple[1]
+    day_of_month_string = time_elements_tuple[2]
+    hours_string = time_elements_tuple[3]
+    minutes_string = time_elements_tuple[4]
+    seconds_string = time_elements_tuple[5]
 
     try:
-      year = int(time_elements_tuple[0], 10)
+      year = int(year_string, 10)
     except (TypeError, ValueError):
-      raise ValueError('Invalid year value: {0!s}'.format(
-          time_elements_tuple[0]))
+      raise ValueError(f'Invalid year value: {year_string!s}')
 
     try:
-      month = int(time_elements_tuple[1], 10)
+      month = int(month_string, 10)
     except (TypeError, ValueError):
-      raise ValueError('Invalid month value: {0!s}'.format(
-          time_elements_tuple[1]))
+      raise ValueError(f'Invalid month value: {month_string!s}')
 
     try:
-      day_of_month = int(time_elements_tuple[2], 10)
+      day_of_month = int(day_of_month_string, 10)
     except (TypeError, ValueError):
-      raise ValueError('Invalid day of month value: {0!s}'.format(
-          time_elements_tuple[2]))
+      raise ValueError(f'Invalid day of month value: {day_of_month_string!s}')
 
     try:
-      hours = int(time_elements_tuple[3], 10)
+      hours = int(hours_string, 10)
     except (TypeError, ValueError):
-      raise ValueError('Invalid hours value: {0!s}'.format(
-          time_elements_tuple[3]))
+      raise ValueError(f'Invalid hours value: {hours_string!s}')
 
     try:
-      minutes = int(time_elements_tuple[4], 10)
+      minutes = int(minutes_string, 10)
     except (TypeError, ValueError):
-      raise ValueError('Invalid minutes value: {0!s}'.format(
-          time_elements_tuple[4]))
+      raise ValueError(f'Invalid minutes value: {minutes_string!s}')
 
     try:
-      seconds = int(time_elements_tuple[5], 10)
+      seconds = int(seconds_string, 10)
     except (TypeError, ValueError):
-      raise ValueError('Invalid seconds value: {0!s}'.format(
-          time_elements_tuple[5]))
+      raise ValueError(f'Invalid seconds value: {seconds_string!s}')
 
     self._normalized_timestamp = None
     self._number_of_seconds = self._GetNumberOfSecondsFromElements(
@@ -824,10 +833,44 @@ class TimeElements(interface.DateTimeValues):
     if self._number_of_seconds is None:
       return None
 
-    return '{0:04d}-{1:02d}-{2:02d} {3:02d}:{4:02d}:{5:02d}'.format(
-        self._time_elements_tuple[0], self._time_elements_tuple[1],
-        self._time_elements_tuple[2], self._time_elements_tuple[3],
-        self._time_elements_tuple[4], self._time_elements_tuple[5])
+    year, month, day_of_month, hours, minutes, seconds = (
+        self._time_elements_tuple)
+
+    return (
+        f'{year:04d}-{month:02d}-{day_of_month:02d} '
+        f'{hours:02d}:{minutes:02d}:{seconds:02d}')
+
+  def NewFromDeltaAndYear(self, year):
+    """Creates a new time elements instance from a date time delta and a year.
+
+    Args:
+      year (int): year.
+
+    Returns:
+      TimeElements: time elements or None if time elements are missing.
+
+    Raises:
+      ValueError: if the instance is not a date time delta.
+    """
+    if not self._is_delta:
+      raise ValueError('Not a date time delta.')
+
+    if self._number_of_seconds is None:
+      return None
+
+    delta_year, month, day_of_month, hours, minutes, seconds = (
+        self._time_elements_tuple)
+
+    time_elements_tuple = (
+        year + delta_year, month, day_of_month, hours, minutes, seconds)
+
+    date_time = TimeElements(
+        precision=self._precision, time_elements_tuple=time_elements_tuple,
+        time_zone_offset=self._time_zone_offset)
+
+    date_time.is_local_time = self.is_local_time
+
+    return date_time
 
 
 class TimeElementsWithFractionOfSecond(TimeElements):
@@ -840,13 +883,17 @@ class TimeElementsWithFractionOfSecond(TimeElements):
   """
 
   def __init__(
-      self, fraction_of_second=None, time_elements_tuple=None,
-      time_zone_offset=None):
+      self, fraction_of_second=None, is_delta=False, precision=None,
+      time_elements_tuple=None, time_zone_offset=None):
     """Initializes time elements.
 
     Args:
       fraction_of_second (Optional[decimal.Decimal]): fraction of second, which
           must be a value between 0.0 and 1.0.
+      is_delta (Optional[bool]): True if the date and time value is relative to
+          another date and time value.
+      precision (Optional[str]): precision of the date and time value, which
+          should be one of the PRECISION_VALUES in definitions.
       time_elements_tuple (Optional[tuple[int, int, int, int, int, int]]):
           time elements, contains year, month, day of month, hours, minutes and
           seconds.
@@ -860,13 +907,13 @@ class TimeElementsWithFractionOfSecond(TimeElements):
     if fraction_of_second is not None:
       if fraction_of_second < 0.0 or fraction_of_second >= 1.0:
         raise ValueError(
-            'Fraction of second value: {0:f} out of bounds.'.format(
-                fraction_of_second))
+            f'Fraction of second value: {fraction_of_second:f} out of bounds.')
 
     super(TimeElementsWithFractionOfSecond, self).__init__(
+        is_delta=is_delta,
+        precision=precision or definitions.PRECISION_1_SECOND,
         time_elements_tuple=time_elements_tuple,
         time_zone_offset=time_zone_offset)
-    self._precision = None
     self.fraction_of_second = fraction_of_second
 
   def _GetNormalizedTimestamp(self):
@@ -953,23 +1000,26 @@ class TimeElementsWithFractionOfSecond(TimeElements):
     Raises:
       ValueError: if the time elements tuple is invalid.
     """
-    if len(time_elements_tuple) < 7:
+    number_of_elements = len(time_elements_tuple)
+    if number_of_elements < 7:
       raise ValueError((
-          'Invalid time elements tuple at least 7 elements required,'
-          'got: {0:d}').format(len(time_elements_tuple)))
+          f'Invalid time elements tuple at least 7 elements required,'
+          f'got: {number_of_elements:d}'))
 
     super(TimeElementsWithFractionOfSecond, self).CopyFromStringTuple(
         time_elements_tuple)
 
+    fraction_of_second_string = time_elements_tuple[6]
+
     try:
-      fraction_of_second = decimal.Decimal(time_elements_tuple[6])
+      fraction_of_second = decimal.Decimal(fraction_of_second_string)
     except (TypeError, ValueError):
-      raise ValueError('Invalid fraction of second value: {0!s}'.format(
-          time_elements_tuple[6]))
+      raise ValueError(
+          f'Invalid fraction of second value: {fraction_of_second_string!s}')
 
     if fraction_of_second < 0.0 or fraction_of_second >= 1.0:
-      raise ValueError('Fraction of second value: {0:f} out of bounds.'.format(
-          fraction_of_second))
+      raise ValueError(
+          f'Fraction of second value: {fraction_of_second:f} out of bounds.')
 
     self.fraction_of_second = fraction_of_second
 
@@ -992,6 +1042,36 @@ class TimeElementsWithFractionOfSecond(TimeElements):
     return precision_helper.CopyToDateTimeString(
         self._time_elements_tuple, self.fraction_of_second)
 
+  def NewFromDeltaAndYear(self, year):
+    """Creates a new time elements instance from a date time delta and a year.
+
+    Args:
+      year (int): year.
+
+    Returns:
+      TimeElementsWithFractionOfSecond: time elements or None if time elements
+          are missing.
+
+    Raises:
+      ValueError: if the instance is not a date time delta.
+    """
+    if not self._is_delta:
+      raise ValueError('Not a date time delta.')
+
+    if self._number_of_seconds is None:
+      return None
+
+    delta_year, month, day_of_month, hours, minutes, seconds = (
+        self._time_elements_tuple)
+
+    time_elements_tuple = (
+        year + delta_year, month, day_of_month, hours, minutes, seconds)
+
+    return TimeElementsWithFractionOfSecond(
+        fraction_of_second=self.fraction_of_second, precision=self._precision,
+        time_elements_tuple=time_elements_tuple,
+        time_zone_offset=self._time_zone_offset)
+
 
 class TimeElementsInMilliseconds(TimeElementsWithFractionOfSecond):
   """Time elements in milliseconds.
@@ -1004,10 +1084,16 @@ class TimeElementsInMilliseconds(TimeElementsWithFractionOfSecond):
         represents 1 millisecond (PRECISION_1_MILLISECOND).
   """
 
-  def __init__(self, time_elements_tuple=None, time_zone_offset=None):
+  def __init__(
+      self, is_delta=False, precision=None, time_elements_tuple=None,
+      time_zone_offset=None):
     """Initializes time elements.
 
     Args:
+      is_delta (Optional[bool]): True if the date and time value is relative to
+          another date and time value.
+      precision (Optional[str]): precision of the date and time value, which
+          should be one of the PRECISION_VALUES in definitions.
       time_elements_tuple (Optional[tuple[int, int, int, int, int, int, int]]):
           time elements, contains year, month, day of month, hours, minutes,
           seconds and milliseconds.
@@ -1019,10 +1105,11 @@ class TimeElementsInMilliseconds(TimeElementsWithFractionOfSecond):
     """
     fraction_of_second = None
     if time_elements_tuple:
-      if len(time_elements_tuple) < 7:
+      number_of_elements = len(time_elements_tuple)
+      if number_of_elements < 7:
         raise ValueError((
-            'Invalid time elements tuple at least 7 elements required,'
-            'got: {0:d}').format(len(time_elements_tuple)))
+            f'Invalid time elements tuple at least 7 elements required,'
+            f'got: {number_of_elements:d}'))
 
       milliseconds = time_elements_tuple[6]
       time_elements_tuple = time_elements_tuple[:6]
@@ -1035,10 +1122,10 @@ class TimeElementsInMilliseconds(TimeElementsWithFractionOfSecond):
           decimal.Decimal(milliseconds) / definitions.MILLISECONDS_PER_SECOND)
 
     super(TimeElementsInMilliseconds, self).__init__(
-        fraction_of_second=fraction_of_second,
+        fraction_of_second=fraction_of_second, is_delta=is_delta,
+        precision=precision or definitions.PRECISION_1_MILLISECOND,
         time_elements_tuple=time_elements_tuple,
         time_zone_offset=time_zone_offset)
-    self._precision = definitions.PRECISION_1_MILLISECOND
 
   @property
   def milliseconds(self):
@@ -1056,10 +1143,11 @@ class TimeElementsInMilliseconds(TimeElementsWithFractionOfSecond):
     Raises:
       ValueError: if the time elements tuple is invalid.
     """
+    number_of_elements = len(time_elements_tuple)
     if len(time_elements_tuple) < 7:
       raise ValueError((
-          'Invalid time elements tuple at least 7 elements required,'
-          'got: {0:d}').format(len(time_elements_tuple)))
+          f'Invalid time elements tuple at least 7 elements required,'
+          f'got: {number_of_elements:d}'))
 
     year, month, day_of_month, hours, minutes, seconds, milliseconds = (
         time_elements_tuple)
@@ -1067,7 +1155,7 @@ class TimeElementsInMilliseconds(TimeElementsWithFractionOfSecond):
     try:
       milliseconds = int(milliseconds, 10)
     except (TypeError, ValueError):
-      raise ValueError('Invalid millisecond value: {0!s}'.format(milliseconds))
+      raise ValueError(f'Invalid millisecond value: {milliseconds!s}')
 
     if milliseconds < 0 or milliseconds >= definitions.MILLISECONDS_PER_SECOND:
       raise ValueError('Invalid number of milliseconds.')
@@ -1082,6 +1170,36 @@ class TimeElementsInMilliseconds(TimeElementsWithFractionOfSecond):
     super(TimeElementsInMilliseconds, self).CopyFromStringTuple(
         time_elements_tuple)
 
+  def NewFromDeltaAndYear(self, year):
+    """Creates a new time elements instance from a date time delta and a year.
+
+    Args:
+      year (int): year.
+
+    Returns:
+      TimeElementsInMilliseconds: time elements or None if time elements are
+          missing.
+
+    Raises:
+      ValueError: if the instance is not a date time delta.
+    """
+    if not self._is_delta:
+      raise ValueError('Not a date time delta.')
+
+    if self._number_of_seconds is None:
+      return None
+
+    delta_year, month, day_of_month, hours, minutes, seconds = (
+        self._time_elements_tuple)
+
+    time_elements_tuple = (
+        year + delta_year, month, day_of_month, hours, minutes, seconds,
+        self.milliseconds)
+
+    return TimeElementsInMilliseconds(
+        precision=self._precision, time_elements_tuple=time_elements_tuple,
+        time_zone_offset=self._time_zone_offset)
+
 
 class TimeElementsInMicroseconds(TimeElementsWithFractionOfSecond):
   """Time elements in microseconds.
@@ -1094,10 +1212,16 @@ class TimeElementsInMicroseconds(TimeElementsWithFractionOfSecond):
         represents 1 microsecond (PRECISION_1_MICROSECOND).
   """
 
-  def __init__(self, time_elements_tuple=None, time_zone_offset=None):
+  def __init__(
+      self, is_delta=False, precision=None, time_elements_tuple=None,
+      time_zone_offset=None):
     """Initializes time elements.
 
     Args:
+      is_delta (Optional[bool]): True if the date and time value is relative to
+          another date and time value.
+      precision (Optional[str]): precision of the date and time value, which
+          should be one of the PRECISION_VALUES in definitions.
       time_elements_tuple (Optional[tuple[int, int, int, int, int, int, int]]):
           time elements, contains year, month, day of month, hours, minutes,
           seconds and microseconds.
@@ -1109,10 +1233,11 @@ class TimeElementsInMicroseconds(TimeElementsWithFractionOfSecond):
     """
     fraction_of_second = None
     if time_elements_tuple:
-      if len(time_elements_tuple) < 7:
+      number_of_elements = len(time_elements_tuple)
+      if number_of_elements < 7:
         raise ValueError((
-            'Invalid time elements tuple at least 7 elements required,'
-            'got: {0:d}').format(len(time_elements_tuple)))
+            f'Invalid time elements tuple at least 7 elements required,'
+            f'got: {number_of_elements:d}'))
 
       microseconds = time_elements_tuple[6]
       time_elements_tuple = time_elements_tuple[:6]
@@ -1125,10 +1250,10 @@ class TimeElementsInMicroseconds(TimeElementsWithFractionOfSecond):
           decimal.Decimal(microseconds) / definitions.MICROSECONDS_PER_SECOND)
 
     super(TimeElementsInMicroseconds, self).__init__(
-        fraction_of_second=fraction_of_second,
+        fraction_of_second=fraction_of_second, is_delta=is_delta,
+        precision=precision or definitions.PRECISION_1_MICROSECOND,
         time_elements_tuple=time_elements_tuple,
         time_zone_offset=time_zone_offset)
-    self._precision = definitions.PRECISION_1_MICROSECOND
 
   @property
   def microseconds(self):
@@ -1146,10 +1271,11 @@ class TimeElementsInMicroseconds(TimeElementsWithFractionOfSecond):
     Raises:
       ValueError: if the time elements tuple is invalid.
     """
+    number_of_elements = len(time_elements_tuple)
     if len(time_elements_tuple) < 7:
       raise ValueError((
-          'Invalid time elements tuple at least 7 elements required,'
-          'got: {0:d}').format(len(time_elements_tuple)))
+          f'Invalid time elements tuple at least 7 elements required,'
+          f'got: {number_of_elements:d}'))
 
     year, month, day_of_month, hours, minutes, seconds, microseconds = (
         time_elements_tuple)
@@ -1157,7 +1283,7 @@ class TimeElementsInMicroseconds(TimeElementsWithFractionOfSecond):
     try:
       microseconds = int(microseconds, 10)
     except (TypeError, ValueError):
-      raise ValueError('Invalid microsecond value: {0!s}'.format(microseconds))
+      raise ValueError(f'Invalid microsecond value: {microseconds!s}')
 
     if microseconds < 0 or microseconds >= definitions.MICROSECONDS_PER_SECOND:
       raise ValueError('Invalid number of microseconds.')
@@ -1172,6 +1298,36 @@ class TimeElementsInMicroseconds(TimeElementsWithFractionOfSecond):
     super(TimeElementsInMicroseconds, self).CopyFromStringTuple(
         time_elements_tuple)
 
+  def NewFromDeltaAndYear(self, year):
+    """Creates a new time elements instance from a date time delta and a year.
+
+    Args:
+      year (int): year.
+
+    Returns:
+      TimeElementsInMicroseconds: time elements or None if time elements are
+          missing.
+
+    Raises:
+      ValueError: if the instance is not a date time delta.
+    """
+    if not self._is_delta:
+      raise ValueError('Not a date time delta.')
+
+    if self._number_of_seconds is None:
+      return None
+
+    delta_year, month, day_of_month, hours, minutes, seconds = (
+        self._time_elements_tuple)
+
+    time_elements_tuple = (
+        year + delta_year, month, day_of_month, hours, minutes, seconds,
+        self.microseconds)
+
+    return TimeElementsInMicroseconds(
+        precision=self._precision, time_elements_tuple=time_elements_tuple,
+        time_zone_offset=self._time_zone_offset)
+
 
 factory.Factory.RegisterDateTimeValues(TimeElements)
 factory.Factory.RegisterDateTimeValues(TimeElementsInMilliseconds)
diff --git a/dfdatetime/uuid_time.py b/dfdatetime/uuid_time.py
index 8b9aee1..73940f4 100644
--- a/dfdatetime/uuid_time.py
+++ b/dfdatetime/uuid_time.py
@@ -28,15 +28,18 @@ class UUIDTime(interface.DateTimeValues):
   Attributes:
     is_local_time (bool): True if the date and time value is in local time.
   """
+
   _EPOCH = UUIDTimeEpoch()
 
   # The difference between October 15, 1582 and January 1, 1970 in seconds.
   _UUID_TO_POSIX_BASE = 12219292800
 
-  def __init__(self, time_zone_offset=None, timestamp=None):
+  def __init__(self, precision=None, time_zone_offset=None, timestamp=None):
     """Initializes an UUID version 1 timestamp.
 
     Args:
+      precision (Optional[str]): precision of the date and time value, which
+          should be one of the PRECISION_VALUES in definitions.
       time_zone_offset (Optional[int]): time zone offset in number of minutes
           from UTC or None if not set.
       timestamp (Optional[int]): UUID version 1 timestamp.
@@ -47,8 +50,9 @@ class UUIDTime(interface.DateTimeValues):
     if timestamp and (timestamp < 0 or timestamp > self._UINT60_MAX):
       raise ValueError('Invalid UUID version 1 timestamp.')
 
-    super(UUIDTime, self).__init__(time_zone_offset=time_zone_offset)
-    self._precision = definitions.PRECISION_100_NANOSECONDS
+    super(UUIDTime, self).__init__(
+        precision=precision or definitions.PRECISION_100_NANOSECONDS,
+        time_zone_offset=time_zone_offset)
     self._timestamp = timestamp
 
   @property
@@ -69,7 +73,7 @@ class UUIDTime(interface.DateTimeValues):
       if (self._timestamp is not None and self._timestamp >= 0 and
           self._timestamp <= self._UINT60_MAX):
         self._normalized_timestamp = (
-            decimal.Decimal(self._timestamp) / self._100NS_PER_SECOND)
+            decimal.Decimal(self._timestamp) / self._100_NANOSECONDS_PER_SECOND)
         self._normalized_timestamp -= self._UUID_TO_POSIX_BASE
 
         if self._time_zone_offset:
@@ -110,7 +114,7 @@ class UUIDTime(interface.DateTimeValues):
     timestamp += self._UUID_TO_POSIX_BASE
     timestamp *= definitions.MICROSECONDS_PER_SECOND
     timestamp += date_time_values.get('microseconds', 0)
-    timestamp *= self._100NS_PER_MICROSECOND
+    timestamp *= self._100_NANOSECONDS_PER_MICROSECOND
 
     self._normalized_timestamp = None
     self._timestamp = timestamp
@@ -127,14 +131,15 @@ class UUIDTime(interface.DateTimeValues):
         self._timestamp > self._UINT60_MAX):
       return None
 
-    timestamp, remainder = divmod(self._timestamp, self._100NS_PER_SECOND)
+    timestamp, fraction_of_second = divmod(
+        self._timestamp, self._100_NANOSECONDS_PER_SECOND)
     number_of_days, hours, minutes, seconds = self._GetTimeValues(timestamp)
 
     year, month, day_of_month = self._GetDateValuesWithEpoch(
         number_of_days, self._EPOCH)
 
-    return '{0:04d}-{1:02d}-{2:02d} {3:02d}:{4:02d}:{5:02d}.{6:07d}'.format(
-        year, month, day_of_month, hours, minutes, seconds, remainder)
+    return (f'{year:04d}-{month:02d}-{day_of_month:02d} '
+            f'{hours:02d}:{minutes:02d}:{seconds:02d}.{fraction_of_second:07d}')
 
 
 factory.Factory.RegisterDateTimeValues(UUIDTime)
diff --git a/dfdatetime/webkit_time.py b/dfdatetime/webkit_time.py
index cf056cb..350452d 100644
--- a/dfdatetime/webkit_time.py
+++ b/dfdatetime/webkit_time.py
@@ -31,16 +31,19 @@ class WebKitTime(interface.DateTimeValues):
   # The difference between January 1, 1601 and January 1, 1970 in seconds.
   _WEBKIT_TO_POSIX_BASE = 11644473600
 
-  def __init__(self, time_zone_offset=None, timestamp=None):
+  def __init__(self, precision=None, time_zone_offset=None, timestamp=None):
     """Initializes a WebKit timestamp.
 
     Args:
+      precision (Optional[str]): precision of the date and time value, which
+          should be one of the PRECISION_VALUES in definitions.
       time_zone_offset (Optional[int]): time zone offset in number of minutes
           from UTC or None if not set.
       timestamp (Optional[int]): WebKit timestamp.
     """
-    super(WebKitTime, self).__init__(time_zone_offset=time_zone_offset)
-    self._precision = definitions.PRECISION_1_MICROSECOND
+    super(WebKitTime, self).__init__(
+        precision=precision or definitions.PRECISION_1_MICROSECOND,
+        time_zone_offset=time_zone_offset)
     self._timestamp = timestamp
 
   @property
@@ -122,8 +125,8 @@ class WebKitTime(interface.DateTimeValues):
     year, month, day_of_month = self._GetDateValuesWithEpoch(
         number_of_days, self._EPOCH)
 
-    return '{0:04d}-{1:02d}-{2:02d} {3:02d}:{4:02d}:{5:02d}.{6:06d}'.format(
-        year, month, day_of_month, hours, minutes, seconds, microseconds)
+    return (f'{year:04d}-{month:02d}-{day_of_month:02d} '
+            f'{hours:02d}:{minutes:02d}:{seconds:02d}.{microseconds:06d}')
 
 
 factory.Factory.RegisterDateTimeValues(WebKitTime)
diff --git a/docs/conf.py b/docs/conf.py
index 1b83eb8..c0553c4 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -38,7 +38,7 @@ extensions = [
 
 # We cannot install architecture dependent Python modules on readthedocs,
 # therefore we mock most imports.
-pip_installed_modules = set(['six'])
+pip_installed_modules = set()
 
 dependency_helper = utils.dependencies.DependencyHelper(
     dependencies_file=os.path.join('..', 'dependencies.ini'),
@@ -58,7 +58,7 @@ napoleon_include_special_with_doc = True
 # General information about the project.
 # pylint: disable=redefined-builtin
 project = 'dfDateTime'
-copyright = 'The dfDateTime Project Authors'
+copyright = 'The dfDateTime authors'
 version = dfdatetime.__version__
 release = dfdatetime.__version__
 
diff --git a/docs/index.rst b/docs/index.rst
index 133c30e..7df5cef 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -27,5 +27,4 @@ Indices and tables
 
 * :ref:`genindex`
 * :ref:`modindex`
-* :ref:`search`
 
diff --git a/docs/requirements.txt b/docs/requirements.txt
index f617de1..e872a91 100644
--- a/docs/requirements.txt
+++ b/docs/requirements.txt
@@ -1,5 +1,6 @@
 docutils
+Markdown
 recommonmark
-sphinx >= 2.0.1
+sphinx >= 4.1.0, < 5.2.0
 sphinx-markdown-tables
 sphinx-rtd-theme >= 0.5.1
diff --git a/docs/sources/Date-and-time-values.md b/docs/sources/Date-and-time-values.md
index 4d27b22..a68d7c1 100644
--- a/docs/sources/Date-and-time-values.md
+++ b/docs/sources/Date-and-time-values.md
@@ -72,6 +72,25 @@ occurred during that specific hour.
 * [File Times](https://docs.microsoft.com/en-us/windows/win32/sysinfo/file-times)
 * [Precision and accuracy of DateTime](https://docs.microsoft.com/en-us/archive/blogs/ericlippert/precision-and-accuracy-of-datetime), by Eric Lippert, April 8, 2010
 
+## .NET DateTime
+### Characteristics
+
+Attribute | Description
+--- | ---
+Supported date range | 0001-01-01 00:00:00 through 9999-12-31 23:59:59
+Storage granularity | 100 nanoseconds
+Time zone | externally represented, typically UTC
+
+### Format
+
+Offset | Size | Description
+0 | 4 or 8 | timestamp, little endian integer value containing the number of 100 nanosecond intervals since January 1, 0001 00:00:00.0000000
+
+### Also see
+
+* [Microsoft: DateTime struct](https://docs.microsoft.com/en-us/dotnet/api/system.datetime?view=net-6.0)
+* [Microsoft: DateTime.Ticks](https://docs.microsoft.com/en-us/dotnet/api/system.datetime.ticks)
+
 ## APFS timestamp
 ### Characteristics
 
@@ -124,7 +143,7 @@ Offset | Size | Description
 
 ### Also see
 
-* [Embarcadero: System.TDateTime](http://docwiki.embarcadero.com/Libraries/XE3/en/System.TDateTime)
+* [Embarcadero: System.TDateTime](https://docwiki.embarcadero.com/Libraries/XE3/en/System.TDateTime)
 
 ## FAT date and time
 ### Characteristics
@@ -161,6 +180,21 @@ Offset | Size | Description
 * [Wikipedia: File Allocation Table](https://en.wikipedia.org/wiki/File_Allocation_Table)
 * [DosDateTimeToFileTime function](https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-dosdatetimetofiletime)
 
+## FAT timestamp
+### Characteristics
+
+Attribute | Description
+--- | ---
+Supported date range | 1980-01-01 00:00:00.00 through 2107-12-31 23:59:58.99
+Storage granularity | 10 milliseconds
+Time zone | externally represented
+
+### Format
+
+Offset | Size | Description
+--- | --- | ---
+0 | 8 | timestamp, integer value containing the number of 10 milliseconds intervals after 1980-01-01 00:00:00.00 (or FAT date time epoch)
+
 ## FILETIME
 ### Characteristics
 
@@ -197,6 +231,37 @@ SetFileTime Windows API function are overloaded with a special meaning.
 
 * [MSDN: FILETIME](https://docs.microsoft.com/en-us/windows/win32/api/minwinbase/ns-minwinbase-filetime)
 
+## Golang time.Time timestamp
+
+### Characteristics
+
+Attribute | Description
+--- | ---
+Supported date range | 0001-01-01 00:00:00.0000000 through ...
+Storage granularity | 1 nanosecond
+Time zone | internally represented.
+
+The granularity of the time zone value depends on the version of the timestamp.
+Version 1 timestamps are stored in minutes and version 2 timestamps add a
+seconds component.  *Note: Version 2 is currently not supported.*
+
+### Format
+
+Offset | Size | Description
+--- | --- | ---
+0 | 1 | version (known values are 1 or 2)
+1 | 8 | seconds since January, 1, 1 stored as a 64-bit big-endian signed integer
+9 | 4 | fraction of second, in nanoseconds stored as a 32-bit big-endian signed integer
+13 | 2 | time zone offset in minutes as a 16-bit big-endian signed integer.
+15 | 1 | time zone offset in seconds (only for version 2)
+
+A value of -1 is a special value when the Time instance is initialised as UTC
+(e.g. ```time.Date(1970, time.January, 1, 0, 0, 0, 0, time.UTC)```).
+
+### Also see
+
+* [Golang time.go](https://cs.opensource.google/go/go/+/master:src/time/time.go)
+
 ## HFS timestamp
 
 Sometimes a distinction is made between HFS and HFS+ timestamps is that the
@@ -240,7 +305,7 @@ Offset | Size | Description
 --- | --- | ---
 0 | 8 | timestamp, integer value containing the number of milliseconds before (when negative) or after (when positive) 1970-01-01 00:00:00.000 (or POSIX or Unix epoch)
 
-### Also see:
+### Also see
 
 * [Class java.util.Date](https://docs.oracle.com/javase/8/docs/api/java/util/Date.html)
 
@@ -318,7 +383,7 @@ Offset | Size | Description
 
 ### Also see
 
-* [RFC2579](https://tools.ietf.org/html/rfc2579)
+* [RFC2579](https://datatracker.ietf.org/doc/html/rfc2579)
 
 ## SYSTEMTIME
 ### Characteristics
@@ -350,6 +415,29 @@ An empty (or unset) SYSTEMTIME can be represented by 16x 0-byte values.
 
 * [MSDN: SYSTEMTIME](https://docs.microsoft.com/en-us/windows/win32/api/minwinbase/ns-minwinbase-systemtime)
 
+## Time element strings
+
+### ISO 8601 / RFC 3339
+
+Example `2020-05-31T00:00:47.044800+00:00`
+
+### RFC 822
+
+Example `Tue, 15 Nov 94 08:12:31 GMT`
+
+### RFC 1123
+
+Example `Tue, 15 Nov 1994 08:12:31 GMT`
+
+### RFC 2822
+
+### Also see
+
+* [RFC 822 - Date and time specification](https://datatracker.ietf.org/doc/html/rfc822#section-5)
+* [RFC 1123 - RFC-822 date and time specification](https://datatracker.ietf.org/doc/html/rfc1123#section-5)
+* [RFC 2822 - Date and time specification](https://datatracker.ietf.org/doc/html/rfc2822#section-3.3)
+* [RFC 3339 - Internet date/time format](https://datatracker.ietf.org/doc/html/rfc3339#section-5.6)
+
 ## UUID version 1 time
 ### Characteristics
 
@@ -389,4 +477,4 @@ Offset | Size | Description
 
 ### Also see
 
-* [Chromium source: time.h](https://chromium.googlesource.com/chromium/src/base/+/master/time/time.h#5)
+* [Chromium source: time.h](https://chromium.googlesource.com/chromium/src/base/+/refs/heads/main/time/time.h#5)
diff --git a/docs/sources/api/dfdatetime.rst b/docs/sources/api/dfdatetime.rst
index e683f5a..79f5d38 100644
--- a/docs/sources/api/dfdatetime.rst
+++ b/docs/sources/api/dfdatetime.rst
@@ -44,6 +44,14 @@ dfdatetime.delphi\_date\_time module
    :undoc-members:
    :show-inheritance:
 
+dfdatetime.dotnet\_datetime module
+----------------------------------
+
+.. automodule:: dfdatetime.dotnet_datetime
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
 dfdatetime.factory module
 -------------------------
 
@@ -76,6 +84,14 @@ dfdatetime.filetime module
    :undoc-members:
    :show-inheritance:
 
+dfdatetime.golang\_time module
+------------------------------
+
+.. automodule:: dfdatetime.golang_time
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
 dfdatetime.hfs\_time module
 ---------------------------
 
@@ -140,6 +156,14 @@ dfdatetime.semantic\_time module
    :undoc-members:
    :show-inheritance:
 
+dfdatetime.serializer module
+----------------------------
+
+.. automodule:: dfdatetime.serializer
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
 dfdatetime.systemtime module
 ----------------------------
 
diff --git a/docs/sources/user/Installation-instructions.md b/docs/sources/user/Installation-instructions.md
index 31e3ddc..6bdf0ed 100644
--- a/docs/sources/user/Installation-instructions.md
+++ b/docs/sources/user/Installation-instructions.md
@@ -63,5 +63,5 @@ To install the release versions of the dependencies run:
 ```
 set PYTHONPATH=.
 
-C:\Python38\python.exe tools\update.py --preset dfdatetime
+C:\Python3\python.exe tools\update.py --preset dfdatetime
 ```
diff --git a/run_tests.py b/run_tests.py
index e42d760..34f39ac 100755
--- a/run_tests.py
+++ b/run_tests.py
@@ -14,7 +14,7 @@ import utils.dependencies  # pylint: disable=wrong-import-position
 
 
 if __name__ == '__main__':
-  print('Using Python version {0!s}'.format(sys.version))
+  print(f'Using Python version {sys.version!s}')
 
   fail_unless_has_test_file = '--fail-unless-has-test-file' in sys.argv
   setattr(unittest, 'fail_unless_has_test_file', fail_unless_has_test_file)
diff --git a/setup.cfg b/setup.cfg
index 7daff51..36d6b39 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,5 +1,5 @@
 [metadata]
-license_file = LICENSE
+license_files = LICENSE
 
 [bdist_rpm]
 release = 1
diff --git a/setup.py b/setup.py
index 7c9d72e..1faa120 100755
--- a/setup.py
+++ b/setup.py
@@ -2,6 +2,8 @@
 # -*- coding: utf-8 -*-
 """Installation and deployment script."""
 
+import os
+import pkg_resources
 import sys
 
 try:
@@ -20,10 +22,9 @@ except ImportError:
   bdist_rpm = None
 
 version_tuple = (sys.version_info[0], sys.version_info[1])
-if version_tuple < (3, 6):
-  print((
-      'Unsupported Python version: {0:s}, version 3.6 or higher '
-      'required.').format(sys.version))
+if version_tuple < (3, 7):
+  print(f'Unsupported Python version: {sys.version:s}, version 3.7 or higher '
+        f'required.')
   sys.exit(1)
 
 # Change PYTHONPATH to include dfdatetime so that we can get the version.
@@ -80,8 +81,8 @@ else:
           summary = line[9:]
 
         elif line.startswith('BuildRequires: '):
-          line = 'BuildRequires: {0:s}-setuptools, {0:s}-devel'.format(
-              python_package)
+          line = (f'BuildRequires: {python_package:s}-setuptools, '
+                  f'{python_package:s}-devel')
 
         elif line.startswith('Requires: '):
           requires = line[10:]
@@ -104,7 +105,7 @@ else:
 
         elif line.startswith('%files'):
           lines = [
-              '%files -n {0:s}-%{{name}}'.format(python_package),
+              f'%files -n {python_package:s}-%{{name}}',
               '%defattr(644,root,root,755)',
               '%license LICENSE',
               '%doc ACKNOWLEDGEMENTS AUTHORS README']
@@ -122,17 +123,16 @@ else:
         elif line.startswith('%prep'):
           in_description = False
 
-          python_spec_file.append(
-              '%package -n {0:s}-%{{name}}'.format(python_package))
-          python_summary = 'Python 3 module of {0:s}'.format(summary)
+          python_spec_file.append(f'%package -n {python_package:s}-%{{name}}')
+          python_summary = f'Python 3 module of {summary:s}'
 
           if requires:
-            python_spec_file.append('Requires: {0:s}'.format(requires))
+            python_spec_file.append(f'Requires: {requires:s}')
 
           python_spec_file.extend([
-              'Summary: {0:s}'.format(python_summary),
+              f'Summary: {python_summary:s}',
               '',
-              '%description -n {0:s}-%{{name}}'.format(python_package)])
+              f'%description -n {python_package:s}-%{{name}}'])
 
           python_spec_file.extend(description)
 
@@ -148,6 +148,32 @@ else:
       return python_spec_file
 
 
+def parse_requirements_from_file(path):
+  """Parses requirements from a requirements file.
+
+  Args:
+    path (str): path to the requirements file.
+
+  Returns:
+    list[str]: name and optional version information of the required packages.
+  """
+  requirements = []
+  if os.path.isfile(path):
+    with open(path, 'r') as file_object:
+      file_contents = file_object.read()
+
+    for requirement in pkg_resources.parse_requirements(file_contents):
+      try:
+        name = str(requirement.req)
+      except AttributeError:
+        name = str(requirement)
+
+      if not name.startswith('pip '):
+        requirements.append(name)
+
+  return requirements
+
+
 dfdatetime_description = (
     'Digital Forensics date and time (dfDateTime).')
 
@@ -155,18 +181,23 @@ dfdatetime_long_description = (
     'dfDateTime, or Digital Forensics date and time, provides date and time '
     'objects to preserve accuracy and precision.')
 
+command_classes = {}
+if BdistMSICommand:
+  command_classes['bdist_msi'] = BdistMSICommand
+if BdistRPMCommand:
+  command_classes['bdist_rpm'] = BdistRPMCommand
+
 setup(
     name='dfdatetime',
     version=dfdatetime.__version__,
     description=dfdatetime_description,
     long_description=dfdatetime_long_description,
+    long_description_content_type='text/plain',
     license='Apache License, Version 2.0',
     url='https://github.com/log2timeline/dfdatetime',
     maintainer='Log2Timeline maintainers',
     maintainer_email='log2timeline-maintainers@googlegroups.com',
-    cmdclass={
-        'bdist_msi': BdistMSICommand,
-        'bdist_rpm': BdistRPMCommand},
+    cmdclass=command_classes,
     classifiers=[
         'Development Status :: 3 - Alpha',
         'Environment :: Console',
@@ -182,4 +213,6 @@ setup(
         ('share/doc/dfdatetime', [
             'ACKNOWLEDGEMENTS', 'AUTHORS', 'LICENSE', 'README']),
     ],
+    install_requires=parse_requirements_from_file('requirements.txt'),
+    tests_require=parse_requirements_from_file('test_requirements.txt'),
 )
diff --git a/test_dependencies.ini b/test_dependencies.ini
index 35d821d..e69de29 100644
--- a/test_dependencies.ini
+++ b/test_dependencies.ini
@@ -1,16 +0,0 @@
-[mock]
-dpkg_name: python3-mock
-minimum_version: 2.0.0
-rpm_name: python3-mock
-version_property: __version__
-
-[pbr]
-dpkg_name: python3-pbr
-minimum_version: 4.2.0
-rpm_name: python3-pbr
-
-[six]
-dpkg_name: python3-six
-minimum_version: 1.1.0
-rpm_name: python3-six
-version_property: __version__
diff --git a/test_requirements.txt b/test_requirements.txt
index 25881d1..e69de29 100644
--- a/test_requirements.txt
+++ b/test_requirements.txt
@@ -1,3 +0,0 @@
-mock >= 2.0.0
-pbr >= 4.2.0
-six >= 1.1.0
diff --git a/tests/apfs_time.py b/tests/apfs_time.py
index e670914..ed812b2 100644
--- a/tests/apfs_time.py
+++ b/tests/apfs_time.py
@@ -24,6 +24,13 @@ class APFSTimeTest(unittest.TestCase):
     apfs_time_object = apfs_time.APFSTime(
         time_zone_offset=60, timestamp=1281643591987654321)
 
+    normalized_timestamp = apfs_time_object._GetNormalizedTimestamp()
+    self.assertEqual(
+        normalized_timestamp, decimal.Decimal('1281639991.987654321'))
+
+    apfs_time_object = apfs_time.APFSTime(timestamp=1281643591987654321)
+    apfs_time_object.time_zone_offset = 60
+
     normalized_timestamp = apfs_time_object._GetNormalizedTimestamp()
     self.assertEqual(
         normalized_timestamp, decimal.Decimal('1281639991.987654321'))
diff --git a/tests/cocoa_time.py b/tests/cocoa_time.py
index a1baf04..b7d7caa 100644
--- a/tests/cocoa_time.py
+++ b/tests/cocoa_time.py
@@ -43,6 +43,12 @@ class CocoaTimeTest(unittest.TestCase):
     normalized_timestamp = cocoa_time_object._GetNormalizedTimestamp()
     self.assertEqual(normalized_timestamp, decimal.Decimal('1373315445.0'))
 
+    cocoa_time_object = cocoa_time.CocoaTime(timestamp=395011845.0)
+    cocoa_time_object.time_zone_offset = 60
+
+    normalized_timestamp = cocoa_time_object._GetNormalizedTimestamp()
+    self.assertEqual(normalized_timestamp, decimal.Decimal('1373315445.0'))
+
     cocoa_time_object = cocoa_time.CocoaTime()
 
     normalized_timestamp = cocoa_time_object._GetNormalizedTimestamp()
@@ -101,7 +107,7 @@ class CocoaTimeTest(unittest.TestCase):
     cocoa_time_object = cocoa_time.CocoaTime(timestamp=395011845.546875)
 
     date_time_string = cocoa_time_object.CopyToDateTimeStringISO8601()
-    self.assertEqual(date_time_string, '2013-07-08T21:30:45.546875Z')
+    self.assertEqual(date_time_string, '2013-07-08T21:30:45.546875+00:00')
 
   def testGetDate(self):
     """Tests the GetDate function."""
diff --git a/tests/delphi_date_time.py b/tests/delphi_date_time.py
index 0924662..a096bff 100644
--- a/tests/delphi_date_time.py
+++ b/tests/delphi_date_time.py
@@ -80,6 +80,15 @@ class DelphiDateTimeTest(unittest.TestCase):
     normalized_timestamp = delphi_date_time_object._GetNormalizedTimestamp()
     self.assertEqual(normalized_timestamp, expected_normalized_timestamp)
 
+    delphi_date_time_object = delphi_date_time.DelphiDateTime(
+        timestamp=41443.8263953)
+    delphi_date_time_object.time_zone_offset = 60
+
+    expected_normalized_timestamp = decimal.Decimal(
+        '1371581400.553919887170195579')
+    normalized_timestamp = delphi_date_time_object._GetNormalizedTimestamp()
+    self.assertEqual(normalized_timestamp, expected_normalized_timestamp)
+
     delphi_date_time_object = delphi_date_time.DelphiDateTime()
 
     normalized_timestamp = delphi_date_time_object._GetNormalizedTimestamp()
@@ -139,7 +148,7 @@ class DelphiDateTimeTest(unittest.TestCase):
         timestamp=41443.8263953)
 
     date_time_string = delphi_date_time_object.CopyToDateTimeStringISO8601()
-    self.assertEqual(date_time_string, '2013-06-18T19:50:00.553919Z')
+    self.assertEqual(date_time_string, '2013-06-18T19:50:00.553919+00:00')
 
   def testGetDate(self):
     """Tests the GetDate function."""
diff --git a/tests/dotnet_datetime.py b/tests/dotnet_datetime.py
new file mode 100644
index 0000000..031505b
--- /dev/null
+++ b/tests/dotnet_datetime.py
@@ -0,0 +1,99 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""Tests for the .NET DateTime implementation."""
+
+import decimal
+import unittest
+
+from dfdatetime import dotnet_datetime
+
+
+class DotNetDateTimeEpochTest(unittest.TestCase):
+  """Tests for the .NET DateTime epoch."""
+
+  def testInitialize(self):
+    """Tests the __init__ function."""
+    dotnet_date_time_epoch = dotnet_datetime.DotNetDateTimeEpoch()
+    self.assertIsNotNone(dotnet_date_time_epoch)
+
+
+class DotNetDateTimeTest(unittest.TestCase):
+  """Tests for the ,NET DateTime timestamp."""
+
+  # pylint: disable=protected-access
+
+  def testProperties(self):
+    """Tests the properties."""
+    dotnet_date_time = dotnet_datetime.DotNetDateTime()
+    self.assertEqual(dotnet_date_time.timestamp, 0)
+
+    dotnet_date_time = dotnet_datetime.DotNetDateTime(
+        timestamp=637751130027210000)
+    self.assertEqual(dotnet_date_time.timestamp, 637751130027210000)
+
+  def testGetNormalizedTimestamp(self):
+    """Tests the _GetNormalizedTimestamp function."""
+    dotnet_date_time = dotnet_datetime.DotNetDateTime(
+        timestamp=637433719321230000)
+
+    expected_normalized_timestamp = decimal.Decimal(1607775132123) / 1000
+
+    normalized_timestamp = dotnet_date_time._GetNormalizedTimestamp()
+    self.assertEqual(normalized_timestamp, expected_normalized_timestamp)
+
+    dotnet_date_time = dotnet_datetime.DotNetDateTime(
+        time_zone_offset=60, timestamp=637433719321230000)
+
+    expected_normalized_timestamp = decimal.Decimal(1607771532123) / 1000
+
+    normalized_timestamp = dotnet_date_time._GetNormalizedTimestamp()
+    self.assertEqual(normalized_timestamp, expected_normalized_timestamp)
+
+    dotnet_date_time = dotnet_datetime.DotNetDateTime(
+        timestamp=637433719321230000)
+    dotnet_date_time.time_zone_offset = 60
+
+    expected_normalized_timestamp = decimal.Decimal(1607771532123) / 1000
+
+    normalized_timestamp = dotnet_date_time._GetNormalizedTimestamp()
+    self.assertEqual(normalized_timestamp, expected_normalized_timestamp)
+
+  def testCopyFromDateTimeString(self):
+    """Tests the CopyFromDateTimeString function."""
+    dotnet_date_time = dotnet_datetime.DotNetDateTime()
+
+    dotnet_date_time.CopyFromDateTimeString('2020-12-12')
+    self.assertEqual(dotnet_date_time._timestamp, 637433280000000000)
+    self.assertEqual(dotnet_date_time._time_zone_offset, 0)
+
+    dotnet_date_time.CopyFromDateTimeString('2020-12-12 12:12:12')
+    self.assertEqual(dotnet_date_time._timestamp, 637433719320000000)
+    self.assertEqual(dotnet_date_time._time_zone_offset, 0)
+
+    dotnet_date_time.CopyFromDateTimeString('2020-12-12 12:12:12.123')
+    self.assertEqual(dotnet_date_time._timestamp, 637433719321230000)
+    self.assertEqual(dotnet_date_time._time_zone_offset, 0)
+
+  def testCopyToDateTimeString(self):
+    """Tests the CopyToDateTimeString function."""
+    dotnet_date_time = dotnet_datetime.DotNetDateTime(
+        timestamp=637433280000000000)
+
+    dotnet_date_string = dotnet_date_time.CopyToDateTimeString()
+    self.assertEqual(dotnet_date_string, '2020-12-12 00:00:00.0000000')
+
+    dotnet_date_time = dotnet_datetime.DotNetDateTime(
+        timestamp=637433719320000000)
+
+    dotnet_date_string = dotnet_date_time.CopyToDateTimeString()
+    self.assertEqual(dotnet_date_string, '2020-12-12 12:12:12.0000000')
+
+    dotnet_date_time = dotnet_datetime.DotNetDateTime(
+        timestamp=637433719321230000)
+
+    dotnet_date_string = dotnet_date_time.CopyToDateTimeString()
+    self.assertEqual(dotnet_date_string, '2020-12-12 12:12:12.1230000')
+
+
+if __name__ == '__main__':
+  unittest.main()
diff --git a/tests/fake_time.py b/tests/fake_time.py
index b7675cf..19a2de8 100644
--- a/tests/fake_time.py
+++ b/tests/fake_time.py
@@ -27,6 +27,13 @@ class FakeTimeTest(unittest.TestCase):
     normalized_timestamp = fake_time_object._GetNormalizedTimestamp()
     self.assertEqual(normalized_timestamp, decimal.Decimal('1281643591.546875'))
 
+    fake_time_object = fake_time.FakeTime()
+    fake_time_object.CopyFromDateTimeString('2010-08-12 21:06:31.546875')
+    fake_time_object.time_zone_offset = 60
+
+    normalized_timestamp = fake_time_object._GetNormalizedTimestamp()
+    self.assertEqual(normalized_timestamp, decimal.Decimal('1281643591.546875'))
+
     fake_time_object = fake_time.FakeTime()
     fake_time_object._number_of_seconds = None
 
@@ -95,7 +102,7 @@ class FakeTimeTest(unittest.TestCase):
     fake_time_object.CopyFromDateTimeString('2010-08-12 21:06:31.546875')
 
     date_time_string = fake_time_object.CopyToDateTimeStringISO8601()
-    self.assertEqual(date_time_string, '2010-08-12T21:06:31.546875Z')
+    self.assertEqual(date_time_string, '2010-08-12T21:06:31.546875+00:00')
 
   def testGetDate(self):
     """Tests the GetDate function."""
diff --git a/tests/fat_date_time.py b/tests/fat_date_time.py
index 2a70629..60fa38e 100644
--- a/tests/fat_date_time.py
+++ b/tests/fat_date_time.py
@@ -35,6 +35,12 @@ class FATDateTime(unittest.TestCase):
     normalized_timestamp = fat_date_time_object._GetNormalizedTimestamp()
     self.assertEqual(normalized_timestamp, decimal.Decimal('1281643592.0'))
 
+    fat_date_time_object = fat_date_time.FATDateTime(fat_date_time=0xa8d03d0c)
+    fat_date_time_object.time_zone_offset = 60
+
+    normalized_timestamp = fat_date_time_object._GetNormalizedTimestamp()
+    self.assertEqual(normalized_timestamp, decimal.Decimal('1281643592.0'))
+
     fat_date_time_object = fat_date_time.FATDateTime()
 
     normalized_timestamp = fat_date_time_object._GetNormalizedTimestamp()
@@ -121,7 +127,7 @@ class FATDateTime(unittest.TestCase):
     fat_date_time_object = fat_date_time.FATDateTime(fat_date_time=0xa8d03d0c)
 
     date_time_string = fat_date_time_object.CopyToDateTimeStringISO8601()
-    self.assertEqual(date_time_string, '2010-08-12T21:06:32Z')
+    self.assertEqual(date_time_string, '2010-08-12T21:06:32+00:00')
 
   def testGetDate(self):
     """Tests the GetDate function."""
@@ -161,5 +167,142 @@ class FATDateTime(unittest.TestCase):
     self.assertEqual(time_of_day_tuple, (None, None, None))
 
 
+class FATTimestampTest(unittest.TestCase):
+  """Tests for the POSIX timestamp."""
+
+  # pylint: disable=protected-access
+
+  def testProperties(self):
+    """Tests the properties."""
+    fat_timestamp_object = fat_date_time.FATTimestamp(timestamp=131033589024)
+    self.assertEqual(fat_timestamp_object.timestamp, 131033589024)
+
+    fat_timestamp_object = fat_date_time.FATTimestamp()
+    self.assertIsNone(fat_timestamp_object.timestamp)
+
+  def testGetNormalizedTimestamp(self):
+    """Tests the _GetNormalizedTimestamp function."""
+    fat_timestamp_object = fat_date_time.FATTimestamp(timestamp=131033589024)
+
+    normalized_timestamp = fat_timestamp_object._GetNormalizedTimestamp()
+    self.assertEqual(normalized_timestamp, decimal.Decimal('1625868690.24'))
+
+    fat_timestamp_object = fat_date_time.FATTimestamp(
+        time_zone_offset=60, timestamp=131033589024)
+
+    normalized_timestamp = fat_timestamp_object._GetNormalizedTimestamp()
+    self.assertEqual(normalized_timestamp, decimal.Decimal('1625865090.24'))
+
+    fat_timestamp_object = fat_date_time.FATTimestamp()
+
+    normalized_timestamp = fat_timestamp_object._GetNormalizedTimestamp()
+    self.assertIsNone(normalized_timestamp)
+
+  def testCopyFromDateTimeString(self):
+    """Tests the CopyFromDateTimeString function."""
+    fat_timestamp_object = fat_date_time.FATTimestamp()
+
+    fat_timestamp_object.CopyFromDateTimeString('2021-07-09')
+    self.assertEqual(fat_timestamp_object._timestamp, 131025600000)
+    self.assertEqual(fat_timestamp_object._time_zone_offset, 0)
+
+    fat_timestamp_object.CopyFromDateTimeString('2021-07-09 22:11:30')
+    self.assertEqual(fat_timestamp_object._timestamp, 131033589000)
+    self.assertEqual(fat_timestamp_object._time_zone_offset, 0)
+
+    fat_timestamp_object.CopyFromDateTimeString('2021-07-09 22:11:30.246875')
+    self.assertEqual(fat_timestamp_object._timestamp, 131033589024)
+    self.assertEqual(fat_timestamp_object._time_zone_offset, 0)
+
+    fat_timestamp_object.CopyFromDateTimeString(
+        '2021-07-09 22:11:30.246875-01:00')
+    self.assertEqual(fat_timestamp_object._timestamp, 131033589024)
+    self.assertEqual(fat_timestamp_object._time_zone_offset, -60)
+
+    fat_timestamp_object.CopyFromDateTimeString(
+        '2021-07-09 22:11:30.246875+01:00')
+    self.assertEqual(fat_timestamp_object._timestamp, 131033589024)
+    self.assertEqual(fat_timestamp_object._time_zone_offset, 60)
+
+    fat_timestamp_object.CopyFromDateTimeString('1980-01-02 00:00:00')
+    self.assertEqual(fat_timestamp_object._timestamp, 8640000)
+    self.assertEqual(fat_timestamp_object._time_zone_offset, 0)
+
+    with self.assertRaises(ValueError):
+      fat_timestamp_object.CopyFromDateTimeString('2200-01-02 00:00:00')
+
+  def testCopyToDateTimeString(self):
+    """Tests the CopyToDateTimeString function."""
+    fat_timestamp_object = fat_date_time.FATTimestamp(timestamp=131033589024)
+
+    date_time_string = fat_timestamp_object.CopyToDateTimeString()
+    self.assertEqual(date_time_string, '2021-07-09 22:11:30.24')
+
+    fat_timestamp_object = fat_date_time.FATTimestamp()
+
+    date_time_string = fat_timestamp_object.CopyToDateTimeString()
+    self.assertIsNone(date_time_string)
+
+  def testCopyToDateTimeStringISO8601(self):
+    """Tests the CopyToDateTimeStringISO8601 function."""
+    fat_timestamp_object = fat_date_time.FATTimestamp(timestamp=131033589024)
+
+    date_time_string = fat_timestamp_object.CopyToDateTimeStringISO8601()
+    self.assertEqual(date_time_string, '2021-07-09T22:11:30.24+00:00')
+
+  def testCopyToFATTimestampstampWithFractionOfSecond(self):
+    """Tests the CopyToPosixTimestampWithFractionOfSecond function."""
+    fat_timestamp_object = fat_date_time.FATTimestamp(timestamp=131033589024)
+
+    fat_timestamp, fraction_of_second = (
+        fat_timestamp_object.CopyToPosixTimestampWithFractionOfSecond())
+    self.assertEqual(fat_timestamp, 1625868690)
+    self.assertEqual(fraction_of_second, 24)
+
+    fat_timestamp_object = fat_date_time.FATTimestamp()
+
+    fat_timestamp, fraction_of_second = (
+        fat_timestamp_object.CopyToPosixTimestampWithFractionOfSecond())
+    self.assertIsNone(fat_timestamp)
+    self.assertIsNone(fraction_of_second)
+
+  def testGetDate(self):
+    """Tests the GetDate function."""
+    fat_timestamp_object = fat_date_time.FATTimestamp(timestamp=131033589024)
+
+    date_tuple = fat_timestamp_object.GetDate()
+    self.assertEqual(date_tuple, (2021, 7, 9))
+
+    fat_timestamp_object = fat_date_time.FATTimestamp()
+
+    date_tuple = fat_timestamp_object.GetDate()
+    self.assertEqual(date_tuple, (None, None, None))
+
+  def testGetDateWithTimeOfDay(self):
+    """Tests the GetDateWithTimeOfDay function."""
+    fat_timestamp_object = fat_date_time.FATTimestamp(timestamp=131033589024)
+
+    date_with_time_of_day_tuple = fat_timestamp_object.GetDateWithTimeOfDay()
+    self.assertEqual(date_with_time_of_day_tuple, (2021, 7, 9, 22, 11, 30))
+
+    fat_timestamp_object = fat_date_time.FATTimestamp()
+
+    date_with_time_of_day_tuple = fat_timestamp_object.GetDateWithTimeOfDay()
+    self.assertEqual(
+        date_with_time_of_day_tuple, (None, None, None, None, None, None))
+
+  def testGetTimeOfDay(self):
+    """Tests the GetTimeOfDay function."""
+    fat_timestamp_object = fat_date_time.FATTimestamp(timestamp=131033589024)
+
+    time_of_day_tuple = fat_timestamp_object.GetTimeOfDay()
+    self.assertEqual(time_of_day_tuple, (22, 11, 30))
+
+    fat_timestamp_object = fat_date_time.FATTimestamp()
+
+    time_of_day_tuple = fat_timestamp_object.GetTimeOfDay()
+    self.assertEqual(time_of_day_tuple, (None, None, None))
+
+
 if __name__ == '__main__':
   unittest.main()
diff --git a/tests/filetime.py b/tests/filetime.py
index 61f3c97..dbf8981 100644
--- a/tests/filetime.py
+++ b/tests/filetime.py
@@ -43,6 +43,12 @@ class FiletimeTest(unittest.TestCase):
     normalized_timestamp = filetime_object._GetNormalizedTimestamp()
     self.assertEqual(normalized_timestamp, decimal.Decimal('1281643591.546875'))
 
+    filetime_object = filetime.Filetime(timestamp=0x01cb3a623d0a17ce)
+    filetime_object.time_zone_offset = 60
+
+    normalized_timestamp = filetime_object._GetNormalizedTimestamp()
+    self.assertEqual(normalized_timestamp, decimal.Decimal('1281643591.546875'))
+
     filetime_object = filetime.Filetime(timestamp=0x1ffffffffffffffff)
 
     normalized_timestamp = filetime_object._GetNormalizedTimestamp()
@@ -101,7 +107,7 @@ class FiletimeTest(unittest.TestCase):
     filetime_object = filetime.Filetime(timestamp=0x01cb3a623d0a17ce)
 
     date_time_string = filetime_object.CopyToDateTimeStringISO8601()
-    self.assertEqual(date_time_string, '2010-08-12T21:06:31.5468750Z')
+    self.assertEqual(date_time_string, '2010-08-12T21:06:31.5468750+00:00')
 
   def testGetDate(self):
     """Tests the GetDate function."""
diff --git a/tests/golang_time.py b/tests/golang_time.py
new file mode 100644
index 0000000..1096e9d
--- /dev/null
+++ b/tests/golang_time.py
@@ -0,0 +1,200 @@
+# -*- coding: utf-8 -*-
+"""Tests for the Golang time.Time timestamp implementation."""
+
+import decimal
+import struct
+import unittest
+
+from dfdatetime import golang_time
+
+
+class GolangEpochTest(unittest.TestCase):
+  """Test for the Golang time.Time epoch."""
+
+  def testInitialize(self):
+    """Tests the __init__ function."""
+    golang_epoch = golang_time.GolangTimeEpoch()
+    self.assertIsNotNone(golang_epoch)
+
+  def testEpochDate(self):
+    """Tests the Golang time.Time epoch properties."""
+    golang_epoch = golang_time.GolangTimeEpoch()
+    self.assertEqual(golang_epoch.year, 1)
+    self.assertEqual(golang_epoch.month, 1)
+    self.assertEqual(golang_epoch.day_of_month, 1)
+
+
+class GolangTest(unittest.TestCase):
+  """Tests for the Golang time.Time timestamp."""
+
+  # pylint: disable=protected-access
+
+  def testProperties(self):
+    """Tests the Golang time.Time timestamp properties."""
+    golang_timestamp = struct.pack('>Bqih', 1, 0, 0, -1)
+    golang_time_object = golang_time.GolangTime(
+        golang_timestamp=golang_timestamp)
+    self.assertEqual(golang_time_object._number_of_seconds, 0)
+    self.assertEqual(golang_time_object._nanoseconds, 0)
+    self.assertEqual(golang_time_object.is_local_time, False)
+    self.assertEqual(golang_time_object._time_zone_offset, 0)
+
+    golang_timestamp = struct.pack(
+        '>Bqih', 1, golang_time.GolangTime._GOLANG_TO_POSIX_BASE, 0, 60)
+    golang_time_object = golang_time.GolangTime(
+        golang_timestamp=golang_timestamp)
+    self.assertEqual(golang_time_object._number_of_seconds,
+                     golang_time.GolangTime._GOLANG_TO_POSIX_BASE)
+    self.assertEqual(golang_time_object._nanoseconds, 0)
+    self.assertEqual(golang_time_object.is_local_time, False)
+    self.assertEqual(golang_time_object._time_zone_offset, 60)
+
+    golang_timestamp = bytes.fromhex('010000000e7791f70000000000ffff')
+    golang_time_object = golang_time.GolangTime(
+        golang_timestamp=golang_timestamp)
+    self.assertEqual(golang_time_object._number_of_seconds,
+                     golang_time.GolangTime._GOLANG_TO_POSIX_BASE)
+    self.assertEqual(golang_time_object._nanoseconds, 0)
+    self.assertEqual(golang_time_object.is_local_time, False)
+    self.assertEqual(golang_time_object._time_zone_offset, 0)
+
+  def testGetNormalizedTimestamp(self):
+    """Test the _GetNormalizedTimestamp function."""
+    golang_timestamp = bytes.fromhex('010000000000000000000000000000')
+    golang_time_object = golang_time.GolangTime(
+        golang_timestamp=golang_timestamp)
+
+    normalized_timestamp = golang_time_object._GetNormalizedTimestamp()
+    self.assertIsNone(normalized_timestamp)
+
+    golang_timestamp = struct.pack('>Bqih', 1, 63772480949, 711098348, 0)
+    golang_time_object = golang_time.GolangTime(
+        golang_timestamp=golang_timestamp)
+
+    normalized_timestamp = golang_time_object._GetNormalizedTimestamp()
+    self.assertEqual(
+        normalized_timestamp, decimal.Decimal('1636884149.711098348'))
+
+    golang_timestamp = struct.pack('>Bqih', 1, 63772480949, 711098348, 60)
+    golang_time_object = golang_time.GolangTime(
+        golang_timestamp=golang_timestamp)
+
+    normalized_timestamp = golang_time_object._GetNormalizedTimestamp()
+    self.assertEqual(
+        normalized_timestamp, decimal.Decimal('1636880549.711098348'))
+
+    golang_timestamp = struct.pack('>Bqih', 1, 63772480949, 711098348, 0)
+    golang_time_object = golang_time.GolangTime(
+        golang_timestamp=golang_timestamp)
+    golang_time_object.time_zone_offset = 60
+
+    normalized_timestamp = golang_time_object._GetNormalizedTimestamp()
+    self.assertEqual(
+        normalized_timestamp, decimal.Decimal('1636880549.711098348'))
+
+    golang_timestamp = bytes.fromhex('010000000e7791f70000000000ffff')
+    golang_time_object = golang_time.GolangTime(
+        golang_timestamp=golang_timestamp)
+
+    normalized_timestamp = golang_time_object._GetNormalizedTimestamp()
+    self.assertEqual(normalized_timestamp, decimal.Decimal('0'))
+
+    golang_timestamp = bytes.fromhex('010000000e7791f60000000000ffff')
+    golang_time_object = golang_time.GolangTime(
+        golang_timestamp=golang_timestamp)
+
+    normalized_timestamp = golang_time_object._GetNormalizedTimestamp()
+    self.assertIsNone(normalized_timestamp)
+
+  def testGetNumberOfSeconds(self):
+    """Test the _GetNumberOfSeconds function."""
+    golang_time_object = golang_time.GolangTime()
+
+    golang_timestamp = bytes.fromhex('010000000000000002000000030004')
+    number_of_seconds, nanoseconds, time_zone_offset = (
+        golang_time_object._GetNumberOfSeconds(golang_timestamp))
+    self.assertEqual(number_of_seconds, 2)
+    self.assertEqual(nanoseconds, 3)
+    self.assertEqual(time_zone_offset, 4)
+
+    golang_timestamp = bytes.fromhex('02000000000000000500000006ffff08')
+    number_of_seconds, nanoseconds, time_zone_offset = (
+        golang_time_object._GetNumberOfSeconds(golang_timestamp))
+    self.assertEqual(number_of_seconds, 5)
+    self.assertEqual(nanoseconds, 6)
+    self.assertEqual(time_zone_offset, 0)
+
+    with self.assertRaises(ValueError):
+      golang_timestamp = bytes.fromhex('0100')
+      golang_time_object._GetNumberOfSeconds(golang_timestamp)
+
+    with self.assertRaises(ValueError):
+      golang_timestamp = bytes.fromhex('020000000000000000000000000000')
+      golang_time_object._GetNumberOfSeconds(golang_timestamp)
+
+    with self.assertRaises(ValueError):
+      golang_timestamp = bytes.fromhex('ff0000000000000000000000000000')
+      golang_time_object._GetNumberOfSeconds(golang_timestamp)
+
+  def testCopyFromDateTimeString(self):
+    """Tests the CopyFromDateTimeString function."""
+    golang_time_object = golang_time.GolangTime()
+
+    golang_time_object.CopyFromDateTimeString('0001-01-01')
+    self.assertEqual(golang_time_object._number_of_seconds, 0)
+    self.assertEqual(golang_time_object._nanoseconds, 0)
+    self.assertEqual(golang_time_object._time_zone_offset, 0)
+
+    golang_time_object.CopyFromDateTimeString('0001-01-01 00:01:00')
+    self.assertEqual(golang_time_object._number_of_seconds, 60)
+    self.assertEqual(golang_time_object._nanoseconds, 0)
+    self.assertEqual(golang_time_object._time_zone_offset, 0)
+
+    golang_time_object.CopyFromDateTimeString('0001-01-01 00:00:00.000001')
+    self.assertEqual(golang_time_object._number_of_seconds, 0)
+    self.assertEqual(golang_time_object._nanoseconds, 1000)
+    self.assertEqual(golang_time_object._time_zone_offset, 0)
+
+    golang_time_object.CopyFromDateTimeString('2000-01-01')
+    self.assertEqual(golang_time_object._number_of_seconds, 63082281600)
+    self.assertEqual(golang_time_object._nanoseconds, 0)
+    self.assertEqual(golang_time_object._time_zone_offset, 0)
+
+    golang_time_object.CopyFromDateTimeString('2000-01-01 12:23:45.567890')
+    self.assertEqual(golang_time_object._number_of_seconds, 63082326225)
+    self.assertEqual(golang_time_object._nanoseconds, 567890000)
+    self.assertEqual(golang_time_object._time_zone_offset, 0)
+
+    golang_time_object.CopyFromDateTimeString(
+        '2000-01-01 12:23:45.567890+01:00')
+    self.assertEqual(golang_time_object._number_of_seconds, 63082326225)
+    self.assertEqual(golang_time_object._nanoseconds, 567890000)
+    self.assertEqual(golang_time_object._time_zone_offset, 60)
+
+  def testCopyToDateTimeString(self):
+    """Test the CopyToDateTimeString function."""
+    golang_timestamp = bytes.fromhex('010000000eafffe8d121d95050ffff')
+    golang_time_object = golang_time.GolangTime(
+        golang_timestamp=golang_timestamp)
+
+    self.assertEqual(golang_time_object._number_of_seconds, 63082326225)
+    self.assertEqual(golang_time_object._nanoseconds, 567890000)
+    self.assertEqual(golang_time_object._time_zone_offset, 0)
+
+    date_time_string = golang_time_object.CopyToDateTimeString()
+    self.assertEqual(date_time_string, '2000-01-01 12:23:45.567890000')
+
+    golang_timestamp = bytes.fromhex('010000000eafffe8d10000ddd5ffff')
+    golang_time_object = golang_time.GolangTime(
+        golang_timestamp=golang_timestamp)
+
+    self.assertEqual(golang_time_object._number_of_seconds, 63082326225)
+    self.assertEqual(golang_time_object._nanoseconds, 56789)
+    self.assertEqual(golang_time_object._time_zone_offset, 0)
+
+    date_time_string = golang_time_object.CopyToDateTimeString()
+    self.assertEqual(date_time_string, '2000-01-01 12:23:45.000056789')
+
+
+if __name__ == '__main__':
+  unittest.main()
diff --git a/tests/hfs_time.py b/tests/hfs_time.py
index 6bae752..6642d35 100644
--- a/tests/hfs_time.py
+++ b/tests/hfs_time.py
@@ -43,6 +43,12 @@ class HFSTimeTest(unittest.TestCase):
     normalized_timestamp = hfs_time_object._GetNormalizedTimestamp()
     self.assertEqual(normalized_timestamp, decimal.Decimal('1375367128.0'))
 
+    hfs_time_object = hfs_time.HFSTime(timestamp=3458215528)
+    hfs_time_object.time_zone_offset = 60
+
+    normalized_timestamp = hfs_time_object._GetNormalizedTimestamp()
+    self.assertEqual(normalized_timestamp, decimal.Decimal('1375367128.0'))
+
     hfs_time_object = hfs_time.HFSTime(timestamp=0x1ffffffff)
 
     normalized_timestamp = hfs_time_object._GetNormalizedTimestamp()
@@ -106,7 +112,7 @@ class HFSTimeTest(unittest.TestCase):
     hfs_time_object = hfs_time.HFSTime(timestamp=3458215528)
 
     date_time_string = hfs_time_object.CopyToDateTimeStringISO8601()
-    self.assertEqual(date_time_string, '2013-08-01T15:25:28Z')
+    self.assertEqual(date_time_string, '2013-08-01T15:25:28+00:00')
 
   def testGetDate(self):
     """Tests the GetDate function."""
diff --git a/tests/interface.py b/tests/interface.py
index d4017f1..aab2ace 100644
--- a/tests/interface.py
+++ b/tests/interface.py
@@ -4,6 +4,7 @@
 
 import unittest
 
+from dfdatetime import definitions
 from dfdatetime import interface
 
 
@@ -478,7 +479,7 @@ class DateTimeValuesTest(unittest.TestCase):
     """Tests the _GetDaysPerMonth function."""
     date_time_values = interface.DateTimeValues()
 
-    expected_days_per_month = list(interface.DateTimeValues._DAYS_PER_MONTH)
+    expected_days_per_month = list(definitions.DAYS_PER_MONTH)
 
     days_per_month = []
     for month in range(1, 13):
@@ -522,6 +523,10 @@ class DateTimeValuesTest(unittest.TestCase):
     """Tests the _GetNumberOfSecondsFromElements function."""
     date_time_values = interface.DateTimeValues()
 
+    number_of_seconds = date_time_values._GetNumberOfSecondsFromElements(
+        1970, 1, 1, 0, 1, 0)
+    self.assertEqual(number_of_seconds, 60)
+
     number_of_seconds = date_time_values._GetNumberOfSecondsFromElements(
         2010, 8, 12, 0, 0, 0)
     self.assertEqual(number_of_seconds, 1281571200)
@@ -534,13 +539,17 @@ class DateTimeValuesTest(unittest.TestCase):
         2010, 8, 12, 21, 6, 31)
     self.assertEqual(number_of_seconds, 1281647191)
 
+    number_of_seconds = date_time_values._GetNumberOfSecondsFromElements(
+        2012, 3, 5, 20, 40, 0)
+    self.assertEqual(number_of_seconds, 1330980000)
+
     number_of_seconds = date_time_values._GetNumberOfSecondsFromElements(
         1601, 1, 2, 0, 0, 0)
     self.assertEqual(number_of_seconds, -11644387200)
 
     number_of_seconds = date_time_values._GetNumberOfSecondsFromElements(
         0, 1, 2, 0, 0, 0)
-    self.assertIsNone(number_of_seconds)
+    self.assertEqual(number_of_seconds, -62167132800)
 
     with self.assertRaises(ValueError):
       date_time_values._GetNumberOfSecondsFromElements(
diff --git a/tests/java_time.py b/tests/java_time.py
index b6959dc..75cf2ac 100644
--- a/tests/java_time.py
+++ b/tests/java_time.py
@@ -34,6 +34,12 @@ class JavaTimeTest(unittest.TestCase):
     normalized_timestamp = java_time_object._GetNormalizedTimestamp()
     self.assertEqual(normalized_timestamp, decimal.Decimal('1281639991.546'))
 
+    java_time_object = java_time.JavaTime(timestamp=1281643591546)
+    java_time_object.time_zone_offset = 60
+
+    normalized_timestamp = java_time_object._GetNormalizedTimestamp()
+    self.assertEqual(normalized_timestamp, decimal.Decimal('1281639991.546'))
+
     java_time_object = java_time.JavaTime()
 
     normalized_timestamp = java_time_object._GetNormalizedTimestamp()
@@ -84,7 +90,7 @@ class JavaTimeTest(unittest.TestCase):
     java_time_object = java_time.JavaTime(timestamp=1281643591546)
 
     date_time_string = java_time_object.CopyToDateTimeStringISO8601()
-    self.assertEqual(date_time_string, '2010-08-12T20:06:31.546Z')
+    self.assertEqual(date_time_string, '2010-08-12T20:06:31.546+00:00')
 
   def testGetDate(self):
     """Tests the GetDate function."""
diff --git a/tests/ole_automation_date.py b/tests/ole_automation_date.py
index 93ff230..e6a6ce1 100644
--- a/tests/ole_automation_date.py
+++ b/tests/ole_automation_date.py
@@ -49,6 +49,15 @@ class OLEAutomationDateTest(unittest.TestCase):
     normalized_timestamp = ole_automation_date_object._GetNormalizedTimestamp()
     self.assertEqual(normalized_timestamp, expected_normalized_timestamp)
 
+    ole_automation_date_object = ole_automation_date.OLEAutomationDate(
+        timestamp=43044.480556)
+    ole_automation_date_object.time_zone_offset = 60
+
+    expected_normalized_timestamp = decimal.Decimal(
+        '1509877920.038400194607675076')
+    normalized_timestamp = ole_automation_date_object._GetNormalizedTimestamp()
+    self.assertEqual(normalized_timestamp, expected_normalized_timestamp)
+
     ole_automation_date_object = ole_automation_date.OLEAutomationDate()
 
     normalized_timestamp = ole_automation_date_object._GetNormalizedTimestamp()
@@ -104,7 +113,7 @@ class OLEAutomationDateTest(unittest.TestCase):
         timestamp=43044.480556)
 
     date_time_string = ole_automation_date_object.CopyToDateTimeStringISO8601()
-    self.assertEqual(date_time_string, '2017-11-05T11:32:00.038400Z')
+    self.assertEqual(date_time_string, '2017-11-05T11:32:00.038400+00:00')
 
   def testGetDate(self):
     """Tests the GetDate function."""
diff --git a/tests/posix_time.py b/tests/posix_time.py
index fdeb1f0..ed7664e 100644
--- a/tests/posix_time.py
+++ b/tests/posix_time.py
@@ -43,6 +43,12 @@ class PosixTimeTest(unittest.TestCase):
     normalized_timestamp = posix_time_object._GetNormalizedTimestamp()
     self.assertEqual(normalized_timestamp, decimal.Decimal('1281639991.0'))
 
+    posix_time_object = posix_time.PosixTime(timestamp=1281643591)
+    posix_time_object.time_zone_offset = 60
+
+    normalized_timestamp = posix_time_object._GetNormalizedTimestamp()
+    self.assertEqual(normalized_timestamp, decimal.Decimal('1281639991.0'))
+
     posix_time_object = posix_time.PosixTime()
 
     normalized_timestamp = posix_time_object._GetNormalizedTimestamp()
@@ -93,20 +99,35 @@ class PosixTimeTest(unittest.TestCase):
     posix_time_object = posix_time.PosixTime(timestamp=1281643591)
 
     date_time_string = posix_time_object.CopyToDateTimeStringISO8601()
-    self.assertEqual(date_time_string, '2010-08-12T20:06:31Z')
+    self.assertEqual(date_time_string, '2010-08-12T20:06:31+00:00')
+
+    posix_time_object = posix_time.PosixTime(timestamp=-11644468446)
 
-  # TODO: remove this method when there is no more need for it in dfvfs.
-  def testCopyToStatTimeTuple(self):
-    """Tests the CopyToStatTimeTuple function."""
+    date_time_string = posix_time_object.CopyToDateTimeStringISO8601()
+    self.assertEqual(date_time_string, '1601-01-01T01:25:54+00:00')
+
+  def testCopyToPosixTimestampWithFractionOfSecond(self):
+    """Tests the CopyToPosixTimestampWithFractionOfSecond function."""
     posix_time_object = posix_time.PosixTime(timestamp=1281643591)
 
-    stat_time_tuple = posix_time_object.CopyToStatTimeTuple()
-    self.assertEqual(stat_time_tuple, (1281643591, None))
+    posix_timestamp, fraction_of_second = (
+        posix_time_object.CopyToPosixTimestampWithFractionOfSecond())
+    self.assertEqual(posix_timestamp, 1281643591)
+    self.assertIsNone(fraction_of_second)
+
+    posix_time_object = posix_time.PosixTime(timestamp=-11644468446)
+
+    posix_timestamp, fraction_of_second = (
+        posix_time_object.CopyToPosixTimestampWithFractionOfSecond())
+    self.assertEqual(posix_timestamp, -11644468446)
+    self.assertIsNone(fraction_of_second)
 
     posix_time_object = posix_time.PosixTime()
 
-    stat_time_tuple = posix_time_object.CopyToStatTimeTuple()
-    self.assertEqual(stat_time_tuple, (None, None))
+    posix_timestamp, fraction_of_second = (
+        posix_time_object.CopyToPosixTimestampWithFractionOfSecond())
+    self.assertIsNone(posix_timestamp)
+    self.assertIsNone(fraction_of_second)
 
   def testGetDate(self):
     """Tests the GetDate function."""
@@ -174,6 +195,13 @@ class PosixTimeInMillisecondsTest(unittest.TestCase):
     normalized_timestamp = posix_time_object._GetNormalizedTimestamp()
     self.assertEqual(normalized_timestamp, decimal.Decimal('1281639991.546'))
 
+    posix_time_object = posix_time.PosixTimeInMilliseconds(
+        timestamp=1281643591546)
+    posix_time_object.time_zone_offset = 60
+
+    normalized_timestamp = posix_time_object._GetNormalizedTimestamp()
+    self.assertEqual(normalized_timestamp, decimal.Decimal('1281639991.546'))
+
     posix_time_object = posix_time.PosixTimeInMilliseconds()
 
     normalized_timestamp = posix_time_object._GetNormalizedTimestamp()
@@ -228,21 +256,38 @@ class PosixTimeInMillisecondsTest(unittest.TestCase):
         timestamp=1281643591546)
 
     date_time_string = posix_time_object.CopyToDateTimeStringISO8601()
-    self.assertEqual(date_time_string, '2010-08-12T20:06:31.546Z')
+    self.assertEqual(date_time_string, '2010-08-12T20:06:31.546+00:00')
+
+    posix_time_object = posix_time.PosixTimeInMilliseconds(
+        timestamp=-11644468446327)
+
+    date_time_string = posix_time_object.CopyToDateTimeStringISO8601()
+    self.assertEqual(date_time_string, '1601-01-01T01:25:53.673+00:00')
 
-  # TODO: remove this method when there is no more need for it in dfvfs.
-  def testCopyToStatTimeTuple(self):
-    """Tests the CopyToStatTimeTuple function."""
+  def testCopyToPosixTimestampWithFractionOfSecond(self):
+    """Tests the CopyToPosixTimestampWithFractionOfSecond function."""
     posix_time_object = posix_time.PosixTimeInMilliseconds(
         timestamp=1281643591546)
 
-    stat_time_tuple = posix_time_object.CopyToStatTimeTuple()
-    self.assertEqual(stat_time_tuple, (1281643591, 5460000))
+    posix_timestamp, fraction_of_second = (
+        posix_time_object.CopyToPosixTimestampWithFractionOfSecond())
+    self.assertEqual(posix_timestamp, 1281643591)
+    self.assertEqual(fraction_of_second, 546)
 
-    posix_time_object = posix_time.PosixTimeInMilliseconds()
+    posix_time_object = posix_time.PosixTimeInMilliseconds(
+        timestamp=-11644468446327)
+
+    posix_timestamp, fraction_of_second = (
+        posix_time_object.CopyToPosixTimestampWithFractionOfSecond())
+    self.assertEqual(posix_timestamp, -11644468446)
+    self.assertEqual(fraction_of_second, -327)
 
-    stat_time_tuple = posix_time_object.CopyToStatTimeTuple()
-    self.assertEqual(stat_time_tuple, (None, None))
+    posix_time_object = posix_time.PosixTime()
+
+    posix_timestamp, fraction_of_second = (
+        posix_time_object.CopyToPosixTimestampWithFractionOfSecond())
+    self.assertIsNone(posix_timestamp)
+    self.assertIsNone(fraction_of_second)
 
   def testGetDate(self):
     """Tests the GetDate function."""
@@ -301,11 +346,11 @@ class PosixTimeInMicrosecondsTest(unittest.TestCase):
 
   def testGetNormalizedTimestamp(self):
     """Tests the _GetNormalizedTimestamp function."""
-    posix_time_object = posix_time.PosixTimeInMilliseconds(
-        timestamp=1281643591546)
+    posix_time_object = posix_time.PosixTimeInMicroseconds(
+        timestamp=1281643591546875)
 
     normalized_timestamp = posix_time_object._GetNormalizedTimestamp()
-    self.assertEqual(normalized_timestamp, decimal.Decimal('1281643591.546'))
+    self.assertEqual(normalized_timestamp, decimal.Decimal('1281643591.546875'))
 
     posix_time_object = posix_time.PosixTimeInMicroseconds(
         time_zone_offset=60, timestamp=1281643591546875)
@@ -313,6 +358,13 @@ class PosixTimeInMicrosecondsTest(unittest.TestCase):
     normalized_timestamp = posix_time_object._GetNormalizedTimestamp()
     self.assertEqual(normalized_timestamp, decimal.Decimal('1281639991.546875'))
 
+    posix_time_object = posix_time.PosixTimeInMicroseconds(
+        timestamp=1281643591546875)
+    posix_time_object.time_zone_offset = 60
+
+    normalized_timestamp = posix_time_object._GetNormalizedTimestamp()
+    self.assertEqual(normalized_timestamp, decimal.Decimal('1281639991.546875'))
+
     posix_time_object = posix_time.PosixTimeInMicroseconds()
 
     normalized_timestamp = posix_time_object._GetNormalizedTimestamp()
@@ -367,21 +419,38 @@ class PosixTimeInMicrosecondsTest(unittest.TestCase):
         timestamp=1281643591546875)
 
     date_time_string = posix_time_object.CopyToDateTimeStringISO8601()
-    self.assertEqual(date_time_string, '2010-08-12T20:06:31.546875Z')
+    self.assertEqual(date_time_string, '2010-08-12T20:06:31.546875+00:00')
+
+    posix_time_object = posix_time.PosixTimeInMicroseconds(
+        timestamp=-11644468446327447)
+
+    date_time_string = posix_time_object.CopyToDateTimeStringISO8601()
+    self.assertEqual(date_time_string, '1601-01-01T01:25:53.672553+00:00')
 
-  # TODO: remove this method when there is no more need for it in dfvfs.
-  def testCopyToStatTimeTuple(self):
-    """Tests the CopyToStatTimeTuple function."""
+  def testCopyToPosixTimestampWithFractionOfSecond(self):
+    """Tests the CopyToPosixTimestampWithFractionOfSecond function."""
     posix_time_object = posix_time.PosixTimeInMicroseconds(
         timestamp=1281643591546875)
 
-    stat_time_tuple = posix_time_object.CopyToStatTimeTuple()
-    self.assertEqual(stat_time_tuple, (1281643591, 5468750))
+    posix_timestamp, fraction_of_second = (
+        posix_time_object.CopyToPosixTimestampWithFractionOfSecond())
+    self.assertEqual(posix_timestamp, 1281643591)
+    self.assertEqual(fraction_of_second, 546875)
 
-    posix_time_object = posix_time.PosixTimeInMicroseconds()
+    posix_time_object = posix_time.PosixTimeInMicroseconds(
+        timestamp=-11644468446327447)
 
-    stat_time_tuple = posix_time_object.CopyToStatTimeTuple()
-    self.assertEqual(stat_time_tuple, (None, None))
+    posix_timestamp, fraction_of_second = (
+        posix_time_object.CopyToPosixTimestampWithFractionOfSecond())
+    self.assertEqual(posix_timestamp, -11644468446)
+    self.assertEqual(fraction_of_second, -327447)
+
+    posix_time_object = posix_time.PosixTime()
+
+    posix_timestamp, fraction_of_second = (
+        posix_time_object.CopyToPosixTimestampWithFractionOfSecond())
+    self.assertIsNone(posix_timestamp)
+    self.assertIsNone(fraction_of_second)
 
   def testGetDate(self):
     """Tests the GetDate function."""
@@ -450,6 +519,14 @@ class PosixTimeInNanoSecondsTest(unittest.TestCase):
     posix_time_object = posix_time.PosixTimeInNanoseconds(
         time_zone_offset=60, timestamp=1281643591987654321)
 
+    normalized_timestamp = posix_time_object._GetNormalizedTimestamp()
+    self.assertEqual(
+        normalized_timestamp, decimal.Decimal('1281639991.987654321'))
+
+    posix_time_object = posix_time.PosixTimeInNanoseconds(
+        timestamp=1281643591987654321)
+    posix_time_object.time_zone_offset = 60
+
     normalized_timestamp = posix_time_object._GetNormalizedTimestamp()
     self.assertEqual(
         normalized_timestamp, decimal.Decimal('1281639991.987654321'))
@@ -508,7 +585,7 @@ class PosixTimeInNanoSecondsTest(unittest.TestCase):
         timestamp=1281643591987654321)
 
     date_time_string = posix_time_object.CopyToDateTimeStringISO8601()
-    self.assertEqual(date_time_string, '2010-08-12T20:06:31.987654321Z')
+    self.assertEqual(date_time_string, '2010-08-12T20:06:31.987654321+00:00')
 
   def testGetDate(self):
     """Tests the GetDate function."""
diff --git a/tests/rfc2579_date_time.py b/tests/rfc2579_date_time.py
index dc37d8e..bea5eed 100644
--- a/tests/rfc2579_date_time.py
+++ b/tests/rfc2579_date_time.py
@@ -119,6 +119,13 @@ class RFC2579DateTimeTest(unittest.TestCase):
     normalized_timestamp = rfc2579_date_time_object._GetNormalizedTimestamp()
     self.assertEqual(normalized_timestamp, decimal.Decimal('1281639991.6'))
 
+    rfc2579_date_time_object = rfc2579_date_time.RFC2579DateTime(
+        rfc2579_date_time_tuple=(2010, 8, 12, 20, 6, 31, 6, '+', 0, 0))
+    rfc2579_date_time_object.time_zone_offset = 60
+
+    normalized_timestamp = rfc2579_date_time_object._GetNormalizedTimestamp()
+    self.assertEqual(normalized_timestamp, decimal.Decimal('1281639991.6'))
+
     rfc2579_date_time_object = rfc2579_date_time.RFC2579DateTime()
 
     normalized_timestamp = rfc2579_date_time_object._GetNormalizedTimestamp()
@@ -223,7 +230,7 @@ class RFC2579DateTimeTest(unittest.TestCase):
         rfc2579_date_time_tuple=(2010, 8, 12, 20, 6, 31, 6, '+', 0, 0))
 
     date_time_string = rfc2579_date_time_object.CopyToDateTimeStringISO8601()
-    self.assertEqual(date_time_string, '2010-08-12T20:06:31.6Z')
+    self.assertEqual(date_time_string, '2010-08-12T20:06:31.6+00:00')
 
   def testGetDate(self):
     """Tests the GetDate function."""
diff --git a/tests/semantic_time.py b/tests/semantic_time.py
index edc5c6c..d97e68b 100644
--- a/tests/semantic_time.py
+++ b/tests/semantic_time.py
@@ -85,12 +85,21 @@ class SemanticTimeTest(unittest.TestCase):
     date_time_string = semantic_time_object.CopyToDateTimeStringISO8601()
     self.assertIsNone(date_time_string)
 
-  def testCopyToStatTimeTuple(self):
-    """Tests the CopyToStatTimeTuple function."""
+  def testCopyToPosixTimestamp(self):
+    """Tests the CopyToPosixTimestamp function."""
     semantic_time_object = semantic_time.SemanticTime()
 
-    stat_time_tuple = semantic_time_object.CopyToStatTimeTuple()
-    self.assertEqual(stat_time_tuple, (None, None))
+    posix_timestamp = semantic_time_object.CopyToPosixTimestamp()
+    self.assertIsNone(posix_timestamp)
+
+  def testCopyToPosixTimestampWithFractionOfSecond(self):
+    """Tests the CopyToPosixTimestampWithFractionOfSecond function."""
+    semantic_time_object = semantic_time.SemanticTime()
+
+    posix_timestamp, fraction_of_second = (
+        semantic_time_object.CopyToPosixTimestampWithFractionOfSecond())
+    self.assertIsNone(posix_timestamp)
+    self.assertIsNone(fraction_of_second)
 
   def testGetDate(self):
     """Tests the GetDate function."""
diff --git a/tests/serializer.py b/tests/serializer.py
new file mode 100644
index 0000000..47e4de0
--- /dev/null
+++ b/tests/serializer.py
@@ -0,0 +1,298 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+"""Tests for the date and time values serializer."""
+
+import unittest
+
+from dfdatetime import dotnet_datetime
+from dfdatetime import fat_date_time
+from dfdatetime import golang_time
+from dfdatetime import posix_time
+from dfdatetime import rfc2579_date_time
+from dfdatetime import semantic_time
+from dfdatetime import serializer
+from dfdatetime import time_elements
+
+
+class SerializerTest(unittest.TestCase):
+  """Tests for the date and time values serializer."""
+
+  def testConvertDateTimeValuesToJSON(self):
+    """Test ConvertDateTimeValuesToJSON function."""
+    posix_time_object = posix_time.PosixTime(timestamp=1281643591)
+
+    expected_json_dict = {
+        '__class_name__': 'PosixTime',
+        '__type__': 'DateTimeValues',
+        'timestamp': 1281643591}
+
+    json_dict = serializer.Serializer.ConvertDateTimeValuesToJSON(
+        posix_time_object)
+    self.assertEqual(json_dict, expected_json_dict)
+
+    posix_time_object.is_local_time = True
+    posix_time_object.time_zone_hint = 'Europe/Amsterdam'
+
+    expected_json_dict = {
+        '__class_name__': 'PosixTime',
+        '__type__': 'DateTimeValues',
+        'is_local_time': True,
+        'time_zone_hint': 'Europe/Amsterdam',
+        'timestamp': 1281643591}
+
+    json_dict = serializer.Serializer.ConvertDateTimeValuesToJSON(
+        posix_time_object)
+    self.assertEqual(json_dict, expected_json_dict)
+
+    posix_time_object = posix_time.PosixTime(
+        timestamp=1281643591, time_zone_offset=60)
+
+    expected_json_dict = {
+        '__class_name__': 'PosixTime',
+        '__type__': 'DateTimeValues',
+        'timestamp': 1281643591,
+        'time_zone_offset': 60}
+
+    json_dict = serializer.Serializer.ConvertDateTimeValuesToJSON(
+        posix_time_object)
+    self.assertEqual(json_dict, expected_json_dict)
+
+    never_time_object = semantic_time.Never()
+
+    expected_json_dict = {
+        '__class_name__': 'Never',
+        '__type__': 'DateTimeValues',
+        'string': 'Never'}
+
+    json_dict = serializer.Serializer.ConvertDateTimeValuesToJSON(
+        never_time_object)
+    self.assertEqual(json_dict, expected_json_dict)
+
+    dotnet_datetime_object = dotnet_datetime.DotNetDateTime(
+        timestamp=637433719321230000)
+
+    expected_json_dict = {
+        '__class_name__': 'DotNetDateTime',
+        '__type__': 'DateTimeValues',
+        'timestamp': 637433719321230000}
+
+    json_dict = serializer.Serializer.ConvertDateTimeValuesToJSON(
+        dotnet_datetime_object)
+    self.assertEqual(json_dict, expected_json_dict)
+
+    fat_date_time_object = fat_date_time.FATDateTime(fat_date_time=0xa8d03d0c)
+
+    expected_json_dict = {
+        '__class_name__': 'FATDateTime',
+        '__type__': 'DateTimeValues',
+        'fat_date_time': 2832219404}
+
+    json_dict = serializer.Serializer.ConvertDateTimeValuesToJSON(
+        fat_date_time_object)
+    self.assertEqual(json_dict, expected_json_dict)
+
+    golang_timestamp = bytes.fromhex('01000000000000000200000003ffff')
+    golang_time_object = golang_time.GolangTime(
+        golang_timestamp=golang_timestamp)
+
+    expected_json_dict = {
+        '__class_name__': 'GolangTime',
+        '__type__': 'DateTimeValues',
+        'golang_timestamp': (
+            b'\x01\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x03\xff\xff')}
+
+    json_dict = serializer.Serializer.ConvertDateTimeValuesToJSON(
+        golang_time_object)
+    self.assertEqual(json_dict, expected_json_dict)
+
+    rfc2579_date_time_object = rfc2579_date_time.RFC2579DateTime(
+        rfc2579_date_time_tuple=(2010, 8, 12, 20, 6, 31, 6, '+', 2, 0))
+
+    expected_json_dict = {
+        '__class_name__': 'RFC2579DateTime',
+        '__type__': 'DateTimeValues',
+        'rfc2579_date_time_tuple': (2010, 8, 12, 20, 6, 31, 6, '+', 2, 0)}
+
+    json_dict = serializer.Serializer.ConvertDateTimeValuesToJSON(
+        rfc2579_date_time_object)
+    self.assertEqual(json_dict, expected_json_dict)
+
+    time_elements_object = time_elements.TimeElements(
+        is_delta=True, time_elements_tuple=(2010, 8, 12, 20, 6, 31))
+
+    expected_json_dict = {
+        '__class_name__': 'TimeElements',
+        '__type__': 'DateTimeValues',
+        'is_delta': True,
+        'time_elements_tuple': (2010, 8, 12, 20, 6, 31)}
+
+    json_dict = serializer.Serializer.ConvertDateTimeValuesToJSON(
+        time_elements_object)
+    self.assertEqual(json_dict, expected_json_dict)
+
+    time_elements_object = time_elements.TimeElementsInMilliseconds(
+        time_elements_tuple=(2010, 8, 12, 20, 6, 31, 546))
+
+    expected_json_dict = {
+        '__class_name__': 'TimeElementsInMilliseconds',
+        '__type__': 'DateTimeValues',
+        'time_elements_tuple': (2010, 8, 12, 20, 6, 31, 546)}
+
+    json_dict = serializer.Serializer.ConvertDateTimeValuesToJSON(
+        time_elements_object)
+    self.assertEqual(json_dict, expected_json_dict)
+
+    time_elements_object = time_elements.TimeElementsInMicroseconds(
+        time_elements_tuple=(2010, 8, 12, 20, 6, 31, 429876))
+
+    expected_json_dict = {
+        '__class_name__': 'TimeElementsInMicroseconds',
+        '__type__': 'DateTimeValues',
+        'time_elements_tuple': (2010, 8, 12, 20, 6, 31, 429876)}
+
+    json_dict = serializer.Serializer.ConvertDateTimeValuesToJSON(
+        time_elements_object)
+    self.assertEqual(json_dict, expected_json_dict)
+
+  def testConvertJSONToDateTimeValues(self):
+    """Test ConvertJSONToDateTimeValues function."""
+    json_dict = {
+        '__class_name__': 'PosixTime',
+        '__type__': 'DateTimeValues',
+        'timestamp': 1281643591}
+
+    expected_date_time_object = posix_time.PosixTime(timestamp=1281643591)
+
+    date_time_object = serializer.Serializer.ConvertJSONToDateTimeValues(
+        json_dict)
+    self.assertEqual(date_time_object, expected_date_time_object)
+
+    json_dict = {
+        '__class_name__': 'PosixTime',
+        '__type__': 'DateTimeValues',
+        'is_local_time': True,
+        'time_zone_hint': 'Europe/Amsterdam',
+        'timestamp': 1281643591}
+
+    expected_date_time_object.is_local_time = True
+    expected_date_time_object.time_zone_hint = 'Europe/Amsterdam'
+
+    date_time_object = serializer.Serializer.ConvertJSONToDateTimeValues(
+        json_dict)
+    self.assertEqual(date_time_object, expected_date_time_object)
+
+    json_dict = {
+        '__class_name__': 'PosixTime',
+        '__type__': 'DateTimeValues',
+        'timestamp': 1281643591,
+        'time_zone_offset': 60}
+
+    expected_date_time_object = posix_time.PosixTime(
+        timestamp=1281643591, time_zone_offset=60)
+
+    date_time_object = serializer.Serializer.ConvertJSONToDateTimeValues(
+        json_dict)
+    self.assertEqual(date_time_object, expected_date_time_object)
+
+    json_dict = {
+        '__class_name__': 'Never',
+        '__type__': 'DateTimeValues',
+        'string': 'Never'}
+
+    expected_date_time_object = semantic_time.Never()
+
+    date_time_object = serializer.Serializer.ConvertJSONToDateTimeValues(
+        json_dict)
+    self.assertEqual(date_time_object, expected_date_time_object)
+
+    json_dict = {
+        '__class_name__': 'DotNetDateTime',
+        '__type__': 'DateTimeValues',
+        'timestamp': 637433719321230000}
+
+    expected_date_time_object = dotnet_datetime.DotNetDateTime(
+        timestamp=637433719321230000)
+
+    date_time_object = serializer.Serializer.ConvertJSONToDateTimeValues(
+        json_dict)
+    self.assertEqual(date_time_object, expected_date_time_object)
+
+    json_dict = {
+        '__class_name__': 'FATDateTime',
+        '__type__': 'DateTimeValues',
+        'fat_date_time': 2832219404}
+
+    expected_date_time_object = fat_date_time.FATDateTime(
+        fat_date_time=0xa8d03d0c)
+
+    date_time_object = serializer.Serializer.ConvertJSONToDateTimeValues(
+        json_dict)
+    self.assertEqual(date_time_object, expected_date_time_object)
+
+    json_dict = {
+        '__class_name__': 'GolangTime',
+        '__type__': 'DateTimeValues',
+        'golang_timestamp': (
+            b'\x01\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x03\xff\xff')}
+
+    golang_timestamp = bytes.fromhex('01000000000000000200000003ffff')
+    expected_date_time_object = golang_time.GolangTime(
+        golang_timestamp=golang_timestamp)
+
+    date_time_object = serializer.Serializer.ConvertJSONToDateTimeValues(
+        json_dict)
+    self.assertEqual(date_time_object, expected_date_time_object)
+
+    json_dict = {
+        '__class_name__': 'RFC2579DateTime',
+        '__type__': 'DateTimeValues',
+        'rfc2579_date_time_tuple': (2010, 8, 12, 20, 6, 31, 6, '+', 2, 0)}
+
+    expected_date_time_object = rfc2579_date_time.RFC2579DateTime(
+        rfc2579_date_time_tuple=(2010, 8, 12, 20, 6, 31, 6, '+', 2, 0))
+
+    date_time_object = serializer.Serializer.ConvertJSONToDateTimeValues(
+        json_dict)
+    self.assertEqual(date_time_object, expected_date_time_object)
+
+    json_dict = {
+        '__class_name__': 'TimeElements',
+        '__type__': 'DateTimeValues',
+        'is_delta': True,
+        'time_elements_tuple': (2010, 8, 12, 20, 6, 31)}
+
+    expected_date_time_object = time_elements.TimeElements(
+        is_delta=True, time_elements_tuple=(2010, 8, 12, 20, 6, 31))
+
+    date_time_object = serializer.Serializer.ConvertJSONToDateTimeValues(
+        json_dict)
+    self.assertEqual(date_time_object, expected_date_time_object)
+    self.assertTrue(date_time_object.is_delta)
+
+    json_dict = {
+        '__class_name__': 'TimeElementsInMilliseconds',
+        '__type__': 'DateTimeValues',
+        'time_elements_tuple': (2010, 8, 12, 20, 6, 31, 546)}
+
+    expected_date_time_object = time_elements.TimeElementsInMilliseconds(
+        time_elements_tuple=(2010, 8, 12, 20, 6, 31, 546))
+
+    date_time_object = serializer.Serializer.ConvertJSONToDateTimeValues(
+        json_dict)
+    self.assertEqual(date_time_object, expected_date_time_object)
+
+    json_dict = {
+        '__class_name__': 'TimeElementsInMicroseconds',
+        '__type__': 'DateTimeValues',
+        'time_elements_tuple': (2010, 8, 12, 20, 6, 31, 429876)}
+
+    expected_date_time_object = time_elements.TimeElementsInMicroseconds(
+        time_elements_tuple=(2010, 8, 12, 20, 6, 31, 429876))
+
+    date_time_object = serializer.Serializer.ConvertJSONToDateTimeValues(
+        json_dict)
+    self.assertEqual(date_time_object, expected_date_time_object)
+
+
+if __name__ == '__main__':
+  unittest.main()
diff --git a/tests/systemtime.py b/tests/systemtime.py
index 1a0ab67..2814ce3 100644
--- a/tests/systemtime.py
+++ b/tests/systemtime.py
@@ -80,6 +80,13 @@ class SystemtimeTest(unittest.TestCase):
     normalized_timestamp = systemtime_object._GetNormalizedTimestamp()
     self.assertEqual(normalized_timestamp, decimal.Decimal('1281639991.142'))
 
+    systemtime_object = systemtime.Systemtime(
+        system_time_tuple=(2010, 8, 4, 12, 20, 6, 31, 142))
+    systemtime_object.time_zone_offset = 60
+
+    normalized_timestamp = systemtime_object._GetNormalizedTimestamp()
+    self.assertEqual(normalized_timestamp, decimal.Decimal('1281639991.142'))
+
     systemtime_object = systemtime.Systemtime()
 
     normalized_timestamp = systemtime_object._GetNormalizedTimestamp()
@@ -179,7 +186,7 @@ class SystemtimeTest(unittest.TestCase):
         system_time_tuple=(2010, 8, 4, 12, 20, 6, 31, 142))
 
     date_time_string = systemtime_object.CopyToDateTimeStringISO8601()
-    self.assertEqual(date_time_string, '2010-08-12T20:06:31.142Z')
+    self.assertEqual(date_time_string, '2010-08-12T20:06:31.142+00:00')
 
   def testGetDate(self):
     """Tests the GetDate function."""
diff --git a/tests/time_elements.py b/tests/time_elements.py
index 07e93b7..e043501 100644
--- a/tests/time_elements.py
+++ b/tests/time_elements.py
@@ -32,10 +32,28 @@ class TimeElementsTest(unittest.TestCase):
     self.assertEqual(time_elements_object.minutes, 6)
     self.assertEqual(time_elements_object.seconds, 31)
 
+    expected_time_elements_tuple = (2010, 2, 29, 20, 6, 31)
+    time_elements_object = time_elements.TimeElements(
+        is_delta=True, time_elements_tuple=(2010, 2, 29, 20, 6, 31))
+    self.assertIsNotNone(time_elements_object)
+    self.assertEqual(
+        time_elements_object._time_elements_tuple, expected_time_elements_tuple)
+    self.assertEqual(time_elements_object.year, 2010)
+    self.assertEqual(time_elements_object.month, 2)
+    self.assertEqual(time_elements_object.day_of_month, 29)
+    self.assertEqual(time_elements_object.hours, 20)
+    self.assertEqual(time_elements_object.minutes, 6)
+    self.assertEqual(time_elements_object.seconds, 31)
+    self.assertTrue(time_elements_object.is_delta)
+
     with self.assertRaises(ValueError):
       time_elements.TimeElements(
           time_elements_tuple=(2010, 8, 12, 20, 6))
 
+    with self.assertRaises(ValueError):
+      time_elements.TimeElements(
+          time_elements_tuple=(2010, 2, 29, 20, 6))
+
     with self.assertRaises(ValueError):
       time_elements.TimeElements(
           time_elements_tuple=(2010, 13, 12, 20, 6, 31))
@@ -54,6 +72,13 @@ class TimeElementsTest(unittest.TestCase):
     normalized_timestamp = time_elements_object._GetNormalizedTimestamp()
     self.assertEqual(normalized_timestamp, decimal.Decimal('1281639991'))
 
+    time_elements_object = time_elements.TimeElements(
+        time_elements_tuple=(2010, 8, 12, 20, 6, 31))
+    time_elements_object.time_zone_offset = 60
+
+    normalized_timestamp = time_elements_object._GetNormalizedTimestamp()
+    self.assertEqual(normalized_timestamp, decimal.Decimal('1281639991'))
+
     time_elements_object = time_elements.TimeElements()
 
     normalized_timestamp = time_elements_object._GetNormalizedTimestamp()
@@ -275,10 +300,6 @@ class TimeElementsTest(unittest.TestCase):
     time_tuple = time_elements_object._CopyTimeFromStringISO8601('2023.5')
     self.assertEqual(time_tuple, expected_time_tuple)
 
-    expected_time_tuple = (8, 4, 32, None, None)
-    time_tuple = time_elements_object._CopyTimeFromStringISO8601('08:04:32Z')
-    self.assertEqual(time_tuple, expected_time_tuple)
-
     expected_time_tuple = (20, 23, 56, None, None)
     time_tuple = time_elements_object._CopyTimeFromStringISO8601('20:23:56')
     self.assertEqual(time_tuple, expected_time_tuple)
@@ -302,6 +323,15 @@ class TimeElementsTest(unittest.TestCase):
         '20:23:56.327124')
     self.assertEqual(time_tuple, expected_time_tuple)
 
+    expected_time_tuple = (8, 4, 32, None, 0)
+    time_tuple = time_elements_object._CopyTimeFromStringISO8601('08:04:32Z')
+    self.assertEqual(time_tuple, expected_time_tuple)
+
+    expected_time_tuple = (8, 4, 32, None, 0)
+    time_tuple = time_elements_object._CopyTimeFromStringISO8601(
+        '08:04:32+00:00')
+    self.assertEqual(time_tuple, expected_time_tuple)
+
     expected_time_tuple = (20, 23, 56, 327124, -300)
     time_tuple = time_elements_object._CopyTimeFromStringISO8601(
         '20:23:56.327124-05:00')
@@ -412,18 +442,6 @@ class TimeElementsTest(unittest.TestCase):
     with self.assertRaises(ValueError):
       time_elements_object._CopyTimeFromStringRFC('11:57:09', 'XXX')
 
-  def testCopyFromString(self):
-    """Tests the CopyFromString function."""
-    time_elements_object = time_elements.TimeElements()
-
-    expected_time_elements_tuple = (2010, 8, 12, 0, 0, 0)
-    expected_number_of_seconds = 1281571200
-    time_elements_object.CopyFromString('2010-08-12')
-    self.assertEqual(
-        time_elements_object._time_elements_tuple, expected_time_elements_tuple)
-    self.assertEqual(
-        time_elements_object._number_of_seconds, expected_number_of_seconds)
-
   def testCopyFromDatetime(self):
     """Tests the CopyFromDatetime function."""
     time_elements_object = time_elements.TimeElements()
@@ -518,7 +536,7 @@ class TimeElementsTest(unittest.TestCase):
     self.assertEqual(time_elements_object._time_zone_offset, 0)
 
     expected_time_elements_tuple = (2010, 8, 12, 21, 6, 31)
-    time_elements_object.CopyFromStringISO8601('2010-08-12T21:06:31Z')
+    time_elements_object.CopyFromStringISO8601('2010-08-12T21:06:31+00:00')
     self.assertEqual(
         time_elements_object._time_elements_tuple, expected_time_elements_tuple)
     self.assertEqual(time_elements_object._number_of_seconds, 1281647191)
@@ -562,7 +580,8 @@ class TimeElementsTest(unittest.TestCase):
     self.assertEqual(time_elements_object._time_zone_offset, 60)
 
     expected_time_elements_tuple = (2012, 3, 5, 20, 40, 0)
-    time_elements_object.CopyFromStringISO8601('2012-03-05T20:40:00.0000000Z')
+    time_elements_object.CopyFromStringISO8601(
+        '2012-03-05T20:40:00.0000000+00:00')
     self.assertEqual(
         time_elements_object._time_elements_tuple, expected_time_elements_tuple)
     self.assertEqual(time_elements_object._number_of_seconds, 1330980000)
@@ -635,6 +654,12 @@ class TimeElementsTest(unittest.TestCase):
     date_time_string = time_elements_object.CopyToDateTimeString()
     self.assertEqual(date_time_string, '2010-08-12 20:06:31')
 
+    time_elements_object = time_elements.TimeElements(
+        time_elements_tuple=(0, 8, 12, 20, 6, 31))
+
+    date_time_string = time_elements_object.CopyToDateTimeString()
+    self.assertEqual(date_time_string, '0000-08-12 20:06:31')
+
     time_elements_object = time_elements.TimeElements()
 
     date_time_string = time_elements_object.CopyToDateTimeString()
@@ -646,7 +671,26 @@ class TimeElementsTest(unittest.TestCase):
         time_elements_tuple=(2010, 8, 12, 20, 6, 31))
 
     date_time_string = time_elements_object.CopyToDateTimeStringISO8601()
-    self.assertEqual(date_time_string, '2010-08-12T20:06:31Z')
+    self.assertEqual(date_time_string, '2010-08-12T20:06:31+00:00')
+
+    time_elements_object.is_local_time = True
+
+    date_time_string = time_elements_object.CopyToDateTimeStringISO8601()
+    self.assertEqual(date_time_string, '2010-08-12T20:06:31')
+
+    time_elements_object = time_elements.TimeElements(
+        time_elements_tuple=(2010, 8, 12, 20, 6, 31),
+        time_zone_offset=120)
+
+    date_time_string = time_elements_object.CopyToDateTimeStringISO8601()
+    self.assertEqual(date_time_string, '2010-08-12T20:06:31+02:00')
+
+    time_elements_object = time_elements.TimeElements(
+        time_elements_tuple=(2010, 8, 12, 20, 6, 31),
+        time_zone_offset=-300)
+
+    date_time_string = time_elements_object.CopyToDateTimeStringISO8601()
+    self.assertEqual(date_time_string, '2010-08-12T20:06:31-05:00')
 
   def testCopyToPosixTimestamp(self):
     """Tests the CopyToPosixTimestamp function."""
@@ -661,6 +705,23 @@ class TimeElementsTest(unittest.TestCase):
     posix_timestamp = time_elements_object.CopyToPosixTimestamp()
     self.assertIsNone(posix_timestamp)
 
+  def testCopyToPosixTimestampWithFractionOfSecond(self):
+    """Tests the CopyToPosixTimestampWithFractionOfSecond function."""
+    time_elements_object = time_elements.TimeElements(
+        time_elements_tuple=(2010, 8, 12, 20, 6, 31))
+
+    posix_timestamp, fraction_of_second = (
+        time_elements_object.CopyToPosixTimestampWithFractionOfSecond())
+    self.assertEqual(posix_timestamp, 1281643591)
+    self.assertIsNone(fraction_of_second)
+
+    time_elements_object = time_elements.TimeElements()
+
+    posix_timestamp, fraction_of_second = (
+        time_elements_object.CopyToPosixTimestampWithFractionOfSecond())
+    self.assertIsNone(posix_timestamp)
+    self.assertIsNone(fraction_of_second)
+
   def testGetDate(self):
     """Tests the GetDate function."""
     time_elements_object = time_elements.TimeElements(
@@ -701,6 +762,26 @@ class TimeElementsTest(unittest.TestCase):
     time_of_day_tuple = time_elements_object.GetTimeOfDay()
     self.assertEqual(time_of_day_tuple, (None, None, None))
 
+  def testNewFromDeltaAndYear(self):
+    """Tests the NewFromDeltaAndYear function."""
+    time_elements_object = time_elements.TimeElements(
+        is_delta=True, time_elements_tuple=(1, 8, 12, 20, 6, 31))
+
+    new_time_elements_object = time_elements_object.NewFromDeltaAndYear(2009)
+    self.assertIsNotNone(new_time_elements_object)
+    self.assertFalse(new_time_elements_object.is_delta)
+    self.assertEqual(new_time_elements_object.year, 2010)
+
+    time_elements_object = time_elements.TimeElements(is_delta=True)
+
+    new_time_elements_object = time_elements_object.NewFromDeltaAndYear(2009)
+    self.assertIsNone(new_time_elements_object)
+
+    time_elements_object = time_elements.TimeElements(is_delta=False)
+
+    with self.assertRaises(ValueError):
+      time_elements_object.NewFromDeltaAndYear(2009)
+
 
 class TimeElementsInMillisecondsTest(unittest.TestCase):
   """Tests for the time elements in milliseconds."""
@@ -742,6 +823,13 @@ class TimeElementsInMillisecondsTest(unittest.TestCase):
     normalized_timestamp = time_elements_object._GetNormalizedTimestamp()
     self.assertEqual(normalized_timestamp, decimal.Decimal('1281639991.429'))
 
+    time_elements_object = time_elements.TimeElementsInMilliseconds(
+        time_elements_tuple=(2010, 8, 12, 20, 6, 31, 429))
+    time_elements_object.time_zone_offset = 60
+
+    normalized_timestamp = time_elements_object._GetNormalizedTimestamp()
+    self.assertEqual(normalized_timestamp, decimal.Decimal('1281639991.429'))
+
     time_elements_object = time_elements.TimeElementsInMilliseconds()
 
     normalized_timestamp = time_elements_object._GetNormalizedTimestamp()
@@ -853,7 +941,7 @@ class TimeElementsInMillisecondsTest(unittest.TestCase):
     self.assertEqual(time_elements_object._time_zone_offset, 0)
 
     expected_time_elements_tuple = (2010, 8, 12, 21, 6, 31)
-    time_elements_object.CopyFromStringISO8601('2010-08-12T21:06:31Z')
+    time_elements_object.CopyFromStringISO8601('2010-08-12T21:06:31+00:00')
     self.assertEqual(
         time_elements_object._time_elements_tuple, expected_time_elements_tuple)
     self.assertEqual(time_elements_object._number_of_seconds, 1281647191)
@@ -903,7 +991,8 @@ class TimeElementsInMillisecondsTest(unittest.TestCase):
     self.assertEqual(time_elements_object._time_zone_offset, 60)
 
     expected_time_elements_tuple = (2012, 3, 5, 20, 40, 0)
-    time_elements_object.CopyFromStringISO8601('2012-03-05T20:40:00.0000000Z')
+    time_elements_object.CopyFromStringISO8601(
+        '2012-03-05T20:40:00.0000000+00:00')
     self.assertEqual(
         time_elements_object._time_elements_tuple, expected_time_elements_tuple)
     self.assertEqual(time_elements_object._number_of_seconds, 1330980000)
@@ -969,7 +1058,37 @@ class TimeElementsInMillisecondsTest(unittest.TestCase):
         time_elements_tuple=(2010, 8, 12, 20, 6, 31, 429))
 
     date_time_string = time_elements_object.CopyToDateTimeStringISO8601()
-    self.assertEqual(date_time_string, '2010-08-12T20:06:31.429Z')
+    self.assertEqual(date_time_string, '2010-08-12T20:06:31.429+00:00')
+
+  def testCopyToPosixTimestamp(self):
+    """Tests the CopyToPosixTimestamp function."""
+    time_elements_object = time_elements.TimeElementsInMilliseconds(
+        time_elements_tuple=(2010, 8, 12, 20, 6, 31, 429))
+
+    posix_timestamp = time_elements_object.CopyToPosixTimestamp()
+    self.assertEqual(posix_timestamp, 1281643591)
+
+    time_elements_object = time_elements.TimeElements()
+
+    posix_timestamp = time_elements_object.CopyToPosixTimestamp()
+    self.assertIsNone(posix_timestamp)
+
+  def testCopyToPosixTimestampWithFractionOfSecond(self):
+    """Tests the CopyToPosixTimestampWithFractionOfSecond function."""
+    time_elements_object = time_elements.TimeElementsInMilliseconds(
+        time_elements_tuple=(2010, 8, 12, 20, 6, 31, 429))
+
+    posix_timestamp, fraction_of_second = (
+        time_elements_object.CopyToPosixTimestampWithFractionOfSecond())
+    self.assertEqual(posix_timestamp, 1281643591)
+    self.assertEqual(fraction_of_second, 429)
+
+    time_elements_object = time_elements.TimeElements()
+
+    posix_timestamp, fraction_of_second = (
+        time_elements_object.CopyToPosixTimestampWithFractionOfSecond())
+    self.assertIsNone(posix_timestamp)
+    self.assertIsNone(fraction_of_second)
 
   def testGetDate(self):
     """Tests the GetDate function."""
@@ -1011,6 +1130,27 @@ class TimeElementsInMillisecondsTest(unittest.TestCase):
     time_of_day_tuple = time_elements_object.GetTimeOfDay()
     self.assertEqual(time_of_day_tuple, (None, None, None))
 
+  def testNewFromDeltaAndYear(self):
+    """Tests the NewFromDeltaAndYear function."""
+    time_elements_object = time_elements.TimeElementsInMilliseconds(
+        is_delta=True, time_elements_tuple=(1, 8, 12, 20, 6, 31, 429))
+
+    new_time_elements_object = time_elements_object.NewFromDeltaAndYear(2009)
+    self.assertIsNotNone(new_time_elements_object)
+    self.assertFalse(new_time_elements_object.is_delta)
+    self.assertEqual(new_time_elements_object.year, 2010)
+    self.assertEqual(new_time_elements_object.milliseconds, 429)
+
+    time_elements_object = time_elements.TimeElements(is_delta=True)
+
+    new_time_elements_object = time_elements_object.NewFromDeltaAndYear(2009)
+    self.assertIsNone(new_time_elements_object)
+
+    time_elements_object = time_elements.TimeElements(is_delta=False)
+
+    with self.assertRaises(ValueError):
+      time_elements_object.NewFromDeltaAndYear(2009)
+
 
 class TimeElementsInMicrosecondsTest(unittest.TestCase):
   """Tests for the time elements in microseconds."""
@@ -1053,6 +1193,13 @@ class TimeElementsInMicrosecondsTest(unittest.TestCase):
     normalized_timestamp = time_elements_object._GetNormalizedTimestamp()
     self.assertEqual(normalized_timestamp, decimal.Decimal('1281639991.429876'))
 
+    time_elements_object = time_elements.TimeElementsInMicroseconds(
+        time_elements_tuple=(2010, 8, 12, 20, 6, 31, 429876))
+    time_elements_object.time_zone_offset = 60
+
+    normalized_timestamp = time_elements_object._GetNormalizedTimestamp()
+    self.assertEqual(normalized_timestamp, decimal.Decimal('1281639991.429876'))
+
     time_elements_object = time_elements.TimeElementsInMicroseconds()
 
     normalized_timestamp = time_elements_object._GetNormalizedTimestamp()
@@ -1164,7 +1311,7 @@ class TimeElementsInMicrosecondsTest(unittest.TestCase):
     self.assertEqual(time_elements_object._time_zone_offset, 0)
 
     expected_time_elements_tuple = (2010, 8, 12, 21, 6, 31)
-    time_elements_object.CopyFromStringISO8601('2010-08-12T21:06:31Z')
+    time_elements_object.CopyFromStringISO8601('2010-08-12T21:06:31+00:00')
     self.assertEqual(
         time_elements_object._time_elements_tuple, expected_time_elements_tuple)
     self.assertEqual(time_elements_object._number_of_seconds, 1281647191)
@@ -1214,7 +1361,8 @@ class TimeElementsInMicrosecondsTest(unittest.TestCase):
     self.assertEqual(time_elements_object._time_zone_offset, 60)
 
     expected_time_elements_tuple = (2012, 3, 5, 20, 40, 0)
-    time_elements_object.CopyFromStringISO8601('2012-03-05T20:40:00.0000000Z')
+    time_elements_object.CopyFromStringISO8601(
+        '2012-03-05T20:40:00.0000000+00:00')
     self.assertEqual(
         time_elements_object._time_elements_tuple, expected_time_elements_tuple)
     self.assertEqual(time_elements_object._number_of_seconds, 1330980000)
@@ -1280,7 +1428,37 @@ class TimeElementsInMicrosecondsTest(unittest.TestCase):
         time_elements_tuple=(2010, 8, 12, 20, 6, 31, 429876))
 
     date_time_string = time_elements_object.CopyToDateTimeStringISO8601()
-    self.assertEqual(date_time_string, '2010-08-12T20:06:31.429876Z')
+    self.assertEqual(date_time_string, '2010-08-12T20:06:31.429876+00:00')
+
+  def testCopyToPosixTimestamp(self):
+    """Tests the CopyToPosixTimestamp function."""
+    time_elements_object = time_elements.TimeElementsInMicroseconds(
+        time_elements_tuple=(2010, 8, 12, 20, 6, 31, 429876))
+
+    posix_timestamp = time_elements_object.CopyToPosixTimestamp()
+    self.assertEqual(posix_timestamp, 1281643591)
+
+    time_elements_object = time_elements.TimeElements()
+
+    posix_timestamp = time_elements_object.CopyToPosixTimestamp()
+    self.assertIsNone(posix_timestamp)
+
+  def testCopyToPosixTimestampWithFractionOfSecond(self):
+    """Tests the CopyToPosixTimestampWithFractionOfSecond function."""
+    time_elements_object = time_elements.TimeElementsInMicroseconds(
+        time_elements_tuple=(2010, 8, 12, 20, 6, 31, 429876))
+
+    posix_timestamp, fraction_of_second = (
+        time_elements_object.CopyToPosixTimestampWithFractionOfSecond())
+    self.assertEqual(posix_timestamp, 1281643591)
+    self.assertEqual(fraction_of_second, 429876)
+
+    time_elements_object = time_elements.TimeElements()
+
+    posix_timestamp, fraction_of_second = (
+        time_elements_object.CopyToPosixTimestampWithFractionOfSecond())
+    self.assertIsNone(posix_timestamp)
+    self.assertIsNone(fraction_of_second)
 
   def testGetDate(self):
     """Tests the GetDate function."""
@@ -1322,6 +1500,27 @@ class TimeElementsInMicrosecondsTest(unittest.TestCase):
     time_of_day_tuple = time_elements_object.GetTimeOfDay()
     self.assertEqual(time_of_day_tuple, (None, None, None))
 
+  def testNewFromDeltaAndYear(self):
+    """Tests the NewFromDeltaAndYear function."""
+    time_elements_object = time_elements.TimeElementsInMicroseconds(
+        is_delta=True, time_elements_tuple=(1, 8, 12, 20, 6, 31, 429876))
+
+    new_time_elements_object = time_elements_object.NewFromDeltaAndYear(2009)
+    self.assertIsNotNone(new_time_elements_object)
+    self.assertFalse(new_time_elements_object.is_delta)
+    self.assertEqual(new_time_elements_object.year, 2010)
+    self.assertEqual(new_time_elements_object.microseconds, 429876)
+
+    time_elements_object = time_elements.TimeElements(is_delta=True)
+
+    new_time_elements_object = time_elements_object.NewFromDeltaAndYear(2009)
+    self.assertIsNone(new_time_elements_object)
+
+    time_elements_object = time_elements.TimeElements(is_delta=False)
+
+    with self.assertRaises(ValueError):
+      time_elements_object.NewFromDeltaAndYear(2009)
+
 
 if __name__ == '__main__':
   unittest.main()
diff --git a/tests/uuid_time.py b/tests/uuid_time.py
index bbab723..1114a96 100644
--- a/tests/uuid_time.py
+++ b/tests/uuid_time.py
@@ -62,6 +62,14 @@ class UUIDTimeTest(unittest.TestCase):
     uuid_time_object = uuid_time.UUIDTime(
         time_zone_offset=60, timestamp=uuid_object.time)
 
+    normalized_timestamp = uuid_time_object._GetNormalizedTimestamp()
+    self.assertEqual(
+        normalized_timestamp, decimal.Decimal('1337127061.6544084'))
+
+    uuid_object = uuid.UUID('00911b54-9ef4-11e1-be53-525400123456')
+    uuid_time_object = uuid_time.UUIDTime(timestamp=uuid_object.time)
+    uuid_time_object.time_zone_offset = 60
+
     normalized_timestamp = uuid_time_object._GetNormalizedTimestamp()
     self.assertEqual(
         normalized_timestamp, decimal.Decimal('1337127061.6544084'))
@@ -133,7 +141,7 @@ class UUIDTimeTest(unittest.TestCase):
     uuid_time_object = uuid_time.UUIDTime(timestamp=uuid_object.time)
 
     date_time_string = uuid_time_object.CopyToDateTimeStringISO8601()
-    self.assertEqual(date_time_string, '2012-05-16T01:11:01.6544084Z')
+    self.assertEqual(date_time_string, '2012-05-16T01:11:01.6544084+00:00')
 
   def testGetDate(self):
     """Tests the GetDate function."""
diff --git a/tests/webkit_time.py b/tests/webkit_time.py
index cea1064..ca5abe8 100644
--- a/tests/webkit_time.py
+++ b/tests/webkit_time.py
@@ -43,6 +43,12 @@ class WebKitTimeTest(unittest.TestCase):
     normalized_timestamp = webkit_time_object._GetNormalizedTimestamp()
     self.assertEqual(normalized_timestamp, decimal.Decimal('1281643591.546875'))
 
+    webkit_time_object = webkit_time.WebKitTime(timestamp=12926120791546875)
+    webkit_time_object.time_zone_offset = 60
+
+    normalized_timestamp = webkit_time_object._GetNormalizedTimestamp()
+    self.assertEqual(normalized_timestamp, decimal.Decimal('1281643591.546875'))
+
     webkit_time_object = webkit_time.WebKitTime(timestamp=0x1ffffffffffffffff)
 
     normalized_timestamp = webkit_time_object._GetNormalizedTimestamp()
@@ -100,7 +106,7 @@ class WebKitTimeTest(unittest.TestCase):
     webkit_time_object = webkit_time.WebKitTime(timestamp=12926120791546875)
 
     date_time_string = webkit_time_object.CopyToDateTimeStringISO8601()
-    self.assertEqual(date_time_string, '2010-08-12T21:06:31.546875Z')
+    self.assertEqual(date_time_string, '2010-08-12T21:06:31.546875+00:00')
 
   def testGetDate(self):
     """Tests the GetDate function."""
diff --git a/tox.ini b/tox.ini
index 2ff7f5d..88e9ce2 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,8 +1,13 @@
 [tox]
-envlist = py3{6,7,8,9},coverage,docs,pylint
+envlist = py3{7,8,9,10,11},coverage,docs,lint
 
 [testenv]
+allowlist_externals = ./run_tests.py
 pip_pre = True
+passenv =
+    CFLAGS
+    CPPFLAGS
+    LDFLAGS
 setenv =
     PYTHONPATH = {toxinidir}
 deps =
@@ -10,41 +15,48 @@ deps =
     -rtest_requirements.txt
     coverage: coverage
 commands =
-    py3{6,7,8,9}: ./run_tests.py
+    py3{7,8,9,10,11}: ./run_tests.py
     coverage: coverage erase
     coverage: coverage run --source=dfdatetime --omit="*_test*,*__init__*,*test_lib*" run_tests.py
 
 [testenv:codecov]
-skip_install = true
+skip_install = True
 passenv =
+    CFLAGS
+    CPPFLAGS
     GITHUB_ACTION
     GITHUB_HEAD_REF
     GITHUB_REF
     GITHUB_REPOSITORY
     GITHUB_RUN_ID
     GITHUB_SHA
+    LDFLAGS
 deps =
     codecov < 2.1.10
 commands =
     codecov
 
 [testenv:docs]
-usedevelop = true
+usedevelop = True
 deps =
     -rdocs/requirements.txt
 commands =
     sphinx-build -b html -d build/doctrees docs dist/docs
     sphinx-build -b linkcheck docs dist/docs
 
-[testenv:pylint]
-skipsdist=True
+[testenv:lint]
+skipsdist = True
 pip_pre = True
+passenv =
+    CFLAGS
+    CPPFLAGS
+    LDFLAGS
 setenv =
     PYTHONPATH = {toxinidir}
 deps =
     -rrequirements.txt
     -rtest_requirements.txt
-    pylint >= 2.6.0, < 2.7.0
+    pylint >= 2.10.0, < 2.11.0
 commands =
     pylint --version
     # Ignore setup.py for now due to:
diff --git a/utils/dependencies.py b/utils/dependencies.py
index 5375925..a2c4040 100644
--- a/utils/dependencies.py
+++ b/utils/dependencies.py
@@ -25,6 +25,8 @@ class DependencyDefinition(object):
     rpm_name (str): name of the rpm package that provides the dependency.
     skip_check (bool): True if the dependency should be skipped by the
         CheckDependencies or CheckTestDependencies methods of DependencyHelper.
+    skip_requires (bool): True if the dependency should be excluded from
+        requirements.txt or setup.py install_requires.
     version_property (str): name of the version attribute or function.
   """
 
@@ -46,6 +48,7 @@ class DependencyDefinition(object):
     self.python3_only = False
     self.rpm_name = None
     self.skip_check = None
+    self.skip_requires = None
     self.version_property = None
 
 
@@ -63,6 +66,7 @@ class DependencyDefinitionReader(object):
       'python3_only',
       'rpm_name',
       'skip_check',
+      'skip_requires',
       'version_property'])
 
   def _GetConfigValue(self, config_parser, section_name, value_name):
@@ -129,12 +133,12 @@ class DependencyHelper(object):
 
     dependency_reader = DependencyDefinitionReader()
 
-    with open(dependencies_file, 'r') as file_object:
+    with open(dependencies_file, 'r', encoding='utf-8') as file_object:
       for dependency in dependency_reader.Read(file_object):
         self.dependencies[dependency.name] = dependency
 
     if os.path.exists(test_dependencies_file):
-      with open(test_dependencies_file, 'r') as file_object:
+      with open(test_dependencies_file, 'r', encoding='utf-8') as file_object:
         for dependency in dependency_reader.Read(file_object):
           self._test_dependencies[dependency.name] = dependency
 
@@ -153,8 +157,7 @@ class DependencyHelper(object):
     """
     module_object = self._ImportPythonModule(dependency.name)
     if not module_object:
-      status_message = 'missing: {0:s}'.format(dependency.name)
-      return False, status_message
+      return False, f'missing: {dependency.name:s}'
 
     if not dependency.version_property:
       return True, dependency.name
@@ -192,13 +195,11 @@ class DependencyHelper(object):
         module_version = version_method()
 
     if not module_version:
-      status_message = (
-          'unable to determine version information for: {0:s}').format(
-              module_name)
-      return False, status_message
+      return False, (
+          f'unable to determine version information for: {module_name:s}')
 
     # Make sure the module version is a string.
-    module_version = '{0!s}'.format(module_version)
+    module_version = f'{module_version!s}'
 
     # Split the version string and convert every digit into an integer.
     # A string compare of both version strings will yield an incorrect result.
@@ -213,42 +214,38 @@ class DependencyHelper(object):
       module_version_map = list(
           map(int, self._VERSION_SPLIT_REGEX.split(module_version)))
     except ValueError:
-      status_message = 'unable to parse module version: {0:s} {1:s}'.format(
-          module_name, module_version)
-      return False, status_message
+      return False, (
+          f'unable to parse module version: {module_name:s} {module_version:s}')
 
     if minimum_version:
       try:
         minimum_version_map = list(
             map(int, self._VERSION_SPLIT_REGEX.split(minimum_version)))
       except ValueError:
-        status_message = 'unable to parse minimum version: {0:s} {1:s}'.format(
-            module_name, minimum_version)
-        return False, status_message
+        return False, (
+            f'unable to parse minimum version: {module_name:s} '
+            f'{minimum_version:s}')
 
       if module_version_map < minimum_version_map:
-        status_message = (
-            '{0:s} version: {1!s} is too old, {2!s} or later required').format(
-                module_name, module_version, minimum_version)
-        return False, status_message
+        return False, (
+            f'{module_name:s} version: {module_version!s} is too old, '
+            f'{minimum_version!s} or later required')
 
     if maximum_version:
       try:
         maximum_version_map = list(
             map(int, self._VERSION_SPLIT_REGEX.split(maximum_version)))
       except ValueError:
-        status_message = 'unable to parse maximum version: {0:s} {1:s}'.format(
-            module_name, maximum_version)
-        return False, status_message
+        return False, (
+            f'unable to parse maximum version: {module_name:s} '
+            f'{maximum_version:s}')
 
       if module_version_map > maximum_version_map:
-        status_message = (
-            '{0:s} version: {1!s} is too recent, {2!s} or earlier '
-            'required').format(module_name, module_version, maximum_version)
-        return False, status_message
+        return False, (
+            f'{module_name:s} version: {module_version!s} is too recent, '
+            f'{maximum_version!s} or earlier required')
 
-    status_message = '{0:s} version: {1!s}'.format(module_name, module_version)
-    return True, status_message
+    return True, f'{module_name:s} version: {module_version!s}'
 
   def _ImportPythonModule(self, module_name):
     """Imports a Python module.
@@ -288,10 +285,10 @@ class DependencyHelper(object):
       else:
         status_indicator = '[FAILURE]'
 
-      print('{0:s}\t{1:s}'.format(status_indicator, status_message))
+      print(f'{status_indicator:s}\t{status_message:s}')
 
     elif verbose_output:
-      print('[OK]\t\t{0:s}'.format(status_message))
+      print(f'[OK]\t\t{status_message:s}')
 
   def CheckDependencies(self, verbose_output=True):
     """Checks the availability of the dependencies.
@@ -345,7 +342,8 @@ class DependencyHelper(object):
         continue
 
       result, status_message = self._CheckPythonModule(dependency)
-      if not result:
+
+      if not result and not dependency.is_optional:
         check_result = False
 
       self._PrintCheckDependencyStatus(

Debdiff

[The following lists of changes regard files as different if they have different names, permissions or owners.]

Files in second set of .debs but not in first

-rw-r--r--  root/root   /usr/lib/python3/dist-packages/dfdatetime-20221218.egg-info/PKG-INFO
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/dfdatetime-20221218.egg-info/dependency_links.txt
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/dfdatetime-20221218.egg-info/requires.txt
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/dfdatetime-20221218.egg-info/top_level.txt
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/dfdatetime/dotnet_datetime.py
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/dfdatetime/golang_time.py
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/dfdatetime/serializer.py

Files in first set of .debs but not in second

-rw-r--r--  root/root   /usr/lib/python3/dist-packages/dfdatetime-20210509.egg-info/PKG-INFO
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/dfdatetime-20210509.egg-info/dependency_links.txt
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/dfdatetime-20210509.egg-info/top_level.txt

Control files: lines which differ (wdiff format)

  • Depends: python3-pip, python3:any

More details

Full run details