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