Skip to content

Commit d592511

Browse files
committed
First good commit with TS & TSMsec classes &setup scripts
1 parent 89e66ac commit d592511

10 files changed

+473
-0
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -127,3 +127,5 @@ dmypy.json
127127

128128
# Pyre type checker
129129
.pyre/
130+
.idea/
131+
.pypirc

README.md

+17
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,19 @@
11
# tsx
22
Time Stamp eXtensions for Python
3+
4+
### Usage:
5+
6+
TS(ts: Union[int, float, str], prec: Literal["s", "ms"] = "s")
7+
TSMsec(ts: Union[int, float, str], prec: Literal["s", "ms"] = "ms")
8+
9+
prec - means precision of the `ts` argument.
10+
If prec=="s" - the `ts` argument will be interpreted as nr of seconds since epoch,
11+
If prec=="s" - the `ts` argument will be interpreted as nr of milliseconds since epoch
12+
13+
Example:
14+
ts = TS(ts="1519855200.123856", prec="s")
15+
16+
ts==1519855200.123856
17+
ts.as_iso == '2018-02-28T22:00:00.123856Z'
18+
ts.as_iso_tz(pytz.timezone("Europe/Bucharest"))=='2018-03-01T00:00:00.123856+02:00'
19+

requirements.txt

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
pyxtension>=1.13.15
2+
typing-extensions>=3.10.0.0
3+
dateparser>=1.0.0

setup.py

+109
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
# Always prefer setuptools over distutils
2+
# To use a consistent encoding
3+
from codecs import open
4+
from os import path
5+
6+
from setuptools import setup
7+
8+
__author__ = 'ASU'
9+
10+
# Bump up this version
11+
VERSION = '0.0.1'
12+
13+
basedir = path.abspath(path.dirname(__file__))
14+
15+
# Get the long description from the README file
16+
with open(path.join(basedir, 'README.md'), encoding='utf-8') as f:
17+
long_description = f.read()
18+
19+
install_requires = ['pyxtension>=1.13.15;python_version>="3.6"', 'typing-extensions>=3.10.0.0',
20+
'dateparser>=1.0.0'
21+
]
22+
23+
print("List of dependencies : {0}".format(str(install_requires)))
24+
25+
parameters = dict(
26+
name='tsx',
27+
28+
# Versions should comply with PEP440. For a discussion on single-sourcing
29+
# the version across setup.py and the project code, see
30+
# https://packaging.python.org/en/latest/single_source_version.html
31+
version=VERSION,
32+
33+
description='TimeStam eXtensions for Python',
34+
long_description=long_description,
35+
long_description_content_type="text/markdown",
36+
37+
# The project's main homepage.
38+
url='https://github.com/asuiu/tsx',
39+
40+
author='Andrei Suiu',
41+
author_email='[email protected]',
42+
43+
license='GPL',
44+
45+
classifiers=[
46+
# How mature is this project? Common values are
47+
# 3 - Alpha
48+
# 4 - Beta
49+
# 5 - Production/Stable
50+
'Development Status :: 5 - Production/Stable',
51+
52+
# Indicate who your project is intended for
53+
'Intended Audience :: Developers',
54+
'Topic :: System :: Logging',
55+
'Topic :: Software Development :: Libraries',
56+
'Topic :: Internet :: Log Analysis',
57+
58+
# Pick your license as you wish (should match "license" above)
59+
'License :: OSI Approved :: Apache Software License',
60+
61+
# Specify the Python versions you support basedir.
62+
"Programming Language :: Python :: 3.7",
63+
"Programming Language :: Python :: 3.8",
64+
"Programming Language :: Python :: Implementation :: CPython",
65+
"Programming Language :: Python :: Implementation :: PyPy"
66+
],
67+
68+
# What does your project relate to?
69+
keywords='logging elasticsearch handler log instrumentation',
70+
71+
# You can just specify the packages manually basedir if your project is
72+
# simple. Or you can use find_packages().
73+
# packages=find_packages(exclude=['dist', 'docs', 'build', 'tests']),
74+
packages=['tsx'],
75+
76+
# Alternatively, if you want to distribute just a my_module.py, uncomment
77+
# this:
78+
# py_modules=["my_module"],
79+
80+
# List run-time dependencies basedir. These will be installed by pip when
81+
# your project is installed. For an analysis of "install_requires" vs pip's
82+
# requirements files see:
83+
# https://packaging.python.org/en/latest/requirements.html
84+
install_requires=install_requires,
85+
86+
# List additional groups of dependencies basedir (e.g. development
87+
# dependencies). You can install these using the following syntax,
88+
# for example:
89+
# $ pip install -e .[dev,test]
90+
extras_require={},
91+
92+
# If there are data files included in your packages that need to be
93+
# installed, specify them basedir. If using Python 2.6 or less, then these
94+
# have to be included in MANIFEST.in as well.
95+
package_data={},
96+
97+
# Although 'package_data' is the preferred approach, in some case you may
98+
# need to place data files outside of your packages. See:
99+
# http://docs.python.org/3.4/distutils/setupscript.html#installing-additional-files # noqa
100+
# In this case, 'data_file' will be installed into '<sys.prefix>/my_data'
101+
data_files=[],
102+
103+
# To provide executable scripts, use entry points in preference to the
104+
# "scripts" keyword. Entry points provide cross-platform support and allow
105+
# pip to create the appropriate form of executable for the target platform.
106+
entry_points={},
107+
)
108+
109+
setup(**parameters)

tests/__init__.py

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#!/usr/bin/env python
2+
# coding:utf-8
3+
# Author: ASU --<[email protected]>
4+
# Purpose:
5+
# Created: 8/4/2021
6+
7+
__author__ = 'ASU'
8+
9+
10+
class __init__.py(object):
11+
def __init__(self):
12+
13+
14+
if __name__ == '__main__':
15+
pass

tests/test_ts.py

+144
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
#!/usr/bin/env python
2+
# coding:utf-8
3+
# Author: ASU --<[email protected]>
4+
# Purpose:
5+
# Created: 8/4/2021
6+
7+
__author__ = 'ASU'
8+
9+
import unittest
10+
from datetime import datetime
11+
from unittest import TestCase
12+
13+
import pytz
14+
from dateutil import tz
15+
16+
from tsx import TS, TSMsec
17+
18+
19+
class TestTS(TestCase):
20+
INT_BASE_TS = 1519855200
21+
INT_BASE_ROUND_MS_TS = 1519855200000
22+
INT_BASE_MS_TS = 1519855200123
23+
FLOAT_MS_TS = 1519855200.123856
24+
STR_SEC_TS = "2018-02-28T22:00:00Z"
25+
AS_FILE_SEC_TS = "20180228-220000"
26+
STR_MSEC_TS = "2018-02-28T22:00:00.123000Z"
27+
28+
def _get_tz_delta(self, dt: datetime) -> float:
29+
return tz.tzlocal().utcoffset(dt).total_seconds()
30+
31+
def test_from_ms(self):
32+
ts = TS(ts=self.INT_BASE_ROUND_MS_TS + 500, prec="ms")
33+
self.assertEqual(ts, self.INT_BASE_TS + 0.5)
34+
# round up
35+
ts = TS(ts=self.INT_BASE_ROUND_MS_TS + 501, prec="ms")
36+
self.assertEqual(ts, self.INT_BASE_TS + 0.501)
37+
38+
def test_from_ms_str(self):
39+
ts = TS(ts=str(self.INT_BASE_ROUND_MS_TS + 500), prec="ms")
40+
self.assertEqual(ts, self.INT_BASE_TS + 0.5)
41+
# round up
42+
ts = TS(ts=self.INT_BASE_ROUND_MS_TS + 501, prec="ms")
43+
self.assertEqual(ts, self.INT_BASE_TS + 0.501)
44+
45+
def test_TSMsec_nominal(self):
46+
ts = TSMsec(ts=str(self.INT_BASE_ROUND_MS_TS + 500))
47+
self.assertEqual(ts, self.INT_BASE_TS + 0.5)
48+
# round up
49+
ts = TS(ts=self.INT_BASE_ROUND_MS_TS + 501, prec="ms")
50+
self.assertEqual(ts, self.INT_BASE_TS + 0.501)
51+
52+
def test_from_float_str(self):
53+
ts = TS(ts="1519855200.123856", prec="s")
54+
self.assertEqual(ts, self.FLOAT_MS_TS)
55+
56+
def test_from_int_str(self):
57+
ts = TS(ts="1519855200")
58+
self.assertEqual(ts, 1519855200)
59+
60+
def test_to_int(self):
61+
ts = TS(ts=self.INT_BASE_ROUND_MS_TS + 500, prec="ms")
62+
self.assertEqual(int(ts), self.INT_BASE_TS)
63+
# round up
64+
ts = TS(ts=self.INT_BASE_ROUND_MS_TS + 501, prec="ms")
65+
self.assertEqual(int(ts), self.INT_BASE_TS + 1)
66+
67+
def test_as_iso(self):
68+
ts = TS(ts=self.INT_BASE_TS)
69+
self.assertEqual(ts.as_iso, self.STR_SEC_TS)
70+
71+
def test_as_iso_tz_standard(self):
72+
ts = TS("2018-03-01T00:00:00Z")
73+
res = ts.as_iso_tz(pytz.timezone("Europe/Bucharest"))
74+
self.assertEqual(res, "2018-03-01T02:00:00+02:00")
75+
res = ts.as_iso_tz("Europe/Bucharest")
76+
self.assertEqual(res, "2018-03-01T02:00:00+02:00")
77+
78+
def test_as_iso_tz_DST(self):
79+
ts = TS("2020-06-01T10:00:00Z")
80+
res = ts.as_iso_tz(pytz.timezone("Europe/Bucharest"))
81+
self.assertEqual(res, "2020-06-01T13:00:00+03:00")
82+
83+
def test_as_file_ts(self):
84+
ts = TS(ts=self.INT_BASE_TS)
85+
self.assertEqual(ts.as_file_ts, self.AS_FILE_SEC_TS)
86+
87+
def test_to_str(self):
88+
ts = TS(ts=self.INT_BASE_TS)
89+
self.assertEqual(str(ts), str(self.INT_BASE_TS))
90+
float_ts = TS(ts=self.FLOAT_MS_TS)
91+
self.assertEqual(str(float_ts), str(self.FLOAT_MS_TS))
92+
93+
def test_input_float_rounds(self):
94+
# round down
95+
ts = TS(ts=1519855200.123456)
96+
self.assertEqual(ts.as_ms, 1519855200123)
97+
# round up
98+
ts = TS(ts=1519855200.123856)
99+
self.assertEqual(ts.as_ms, 1519855200124)
100+
101+
def test_as_str_ms(self):
102+
ts = TS(ts=self.INT_BASE_MS_TS, prec="ms")
103+
self.assertEqual(ts.as_iso, self.STR_MSEC_TS)
104+
105+
def test_convert_from_str(self):
106+
ts = TS(ts="2018-02-28T22:00:00Z")
107+
self.assertEqual(ts, self.INT_BASE_TS)
108+
ts = TS(ts="2018-02-28T22:00:00+00:00")
109+
self.assertEqual(ts, self.INT_BASE_TS)
110+
111+
def test_convert_from_str_with_TZ_2(self):
112+
ts = TS(ts="2018-02-28T22:00:00+01:30")
113+
self.assertEqual(ts, self.INT_BASE_TS - 1.5 * 3600)
114+
115+
def test_convert_from_ms_str_local_time(self):
116+
tz_delta_in_sec = self._get_tz_delta(datetime(2018, 2, 28))
117+
ts = TS(ts="2018-02-28T22:00:00.123") + tz_delta_in_sec
118+
utc_ms_ts = TS("2018-02-28T22:00:00.123Z")
119+
self.assertEqual(utc_ms_ts, ts)
120+
121+
def test_convert_from_ms_str_with_TZ_0(self):
122+
ts = TS(ts="2018-02-28T22:00:00.123+00:00")
123+
self.assertEqual(ts, self.INT_BASE_MS_TS / 1000)
124+
125+
ts = TS(ts="2018-02-28T22:00:00.123Z")
126+
self.assertEqual(ts, self.INT_BASE_MS_TS / 1000)
127+
128+
def test_math_ops(self):
129+
ts = TS(ts=1519855200)
130+
self.assertEqual((ts + 20).as_ms, 1519855220000)
131+
self.assertEqual(TS(20 + ts).as_ms, 1519855220000)
132+
self.assertEqual((ts - 0.5).as_ms, 1519855199500)
133+
134+
def test_as_iso_date(self):
135+
ts = TS(ts=self.INT_BASE_TS)
136+
self.assertEqual(ts.as_iso_date, "2018-02-28")
137+
138+
def test_repr(self):
139+
ts = TS(ts=self.INT_BASE_TS)
140+
self.assertEqual(repr(ts), "TS('2018-02-28T22:00:00Z')")
141+
142+
143+
if __name__ == '__main__':
144+
unittest.main()

tsx/__init__.py

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from .ts import TS, TSMsec
2+
3+
__all__ = ['TS', 'TSMsec']

0 commit comments

Comments
 (0)