datec 0.1__tar.gz → 0.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- datec-0.3/.gitignore +105 -0
- datec-0.3/LICENSE +21 -0
- datec-0.3/PKG-INFO +119 -0
- datec-0.3/README.md +95 -0
- {datec-0.1 → datec-0.3}/datec/__init__.py +103 -68
- {datec-0.1 → datec-0.3}/datec/__main__.py +18 -6
- datec-0.3/datec/py.typed +0 -0
- datec-0.3/pyproject.toml +42 -0
- datec-0.3/requirements-dev.in +9 -0
- datec-0.3/tests/datec_test.py +118 -0
- datec-0.1/PKG-INFO +0 -92
- datec-0.1/datec.egg-info/PKG-INFO +0 -92
- datec-0.1/datec.egg-info/SOURCES.txt +0 -9
- datec-0.1/datec.egg-info/dependency_links.txt +0 -1
- datec-0.1/datec.egg-info/entry_points.txt +0 -3
- datec-0.1/datec.egg-info/requires.txt +0 -1
- datec-0.1/datec.egg-info/top_level.txt +0 -1
- datec-0.1/setup.cfg +0 -4
- datec-0.1/setup.py +0 -32
datec-0.3/.gitignore
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
build/
|
|
12
|
+
requirements-dev.txt
|
|
13
|
+
develop-eggs/
|
|
14
|
+
dist/
|
|
15
|
+
downloads/
|
|
16
|
+
eggs/
|
|
17
|
+
.eggs/
|
|
18
|
+
lib/
|
|
19
|
+
lib64/
|
|
20
|
+
parts/
|
|
21
|
+
sdist/
|
|
22
|
+
var/
|
|
23
|
+
wheels/
|
|
24
|
+
*.egg-info/
|
|
25
|
+
.installed.cfg
|
|
26
|
+
*.egg
|
|
27
|
+
MANIFEST
|
|
28
|
+
|
|
29
|
+
# PyInstaller
|
|
30
|
+
# Usually these files are written by a python script from a template
|
|
31
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
32
|
+
*.manifest
|
|
33
|
+
*.spec
|
|
34
|
+
|
|
35
|
+
# Installer logs
|
|
36
|
+
pip-log.txt
|
|
37
|
+
pip-delete-this-directory.txt
|
|
38
|
+
|
|
39
|
+
# Unit test / coverage reports
|
|
40
|
+
htmlcov/
|
|
41
|
+
.tox/
|
|
42
|
+
.coverage
|
|
43
|
+
.coverage.*
|
|
44
|
+
.cache
|
|
45
|
+
nosetests.xml
|
|
46
|
+
coverage.xml
|
|
47
|
+
*.cover
|
|
48
|
+
.hypothesis/
|
|
49
|
+
.pytest_cache/
|
|
50
|
+
|
|
51
|
+
# Translations
|
|
52
|
+
*.mo
|
|
53
|
+
*.pot
|
|
54
|
+
|
|
55
|
+
# Django stuff:
|
|
56
|
+
*.log
|
|
57
|
+
local_settings.py
|
|
58
|
+
db.sqlite3
|
|
59
|
+
|
|
60
|
+
# Flask stuff:
|
|
61
|
+
instance/
|
|
62
|
+
.webassets-cache
|
|
63
|
+
|
|
64
|
+
# Scrapy stuff:
|
|
65
|
+
.scrapy
|
|
66
|
+
|
|
67
|
+
# Sphinx documentation
|
|
68
|
+
docs/_build/
|
|
69
|
+
|
|
70
|
+
# PyBuilder
|
|
71
|
+
target/
|
|
72
|
+
|
|
73
|
+
# Jupyter Notebook
|
|
74
|
+
.ipynb_checkpoints
|
|
75
|
+
|
|
76
|
+
# pyenv
|
|
77
|
+
.python-version
|
|
78
|
+
|
|
79
|
+
# celery beat schedule file
|
|
80
|
+
celerybeat-schedule
|
|
81
|
+
|
|
82
|
+
# SageMath parsed files
|
|
83
|
+
*.sage.py
|
|
84
|
+
|
|
85
|
+
# Environments
|
|
86
|
+
.env
|
|
87
|
+
.venv
|
|
88
|
+
env/
|
|
89
|
+
venv/
|
|
90
|
+
ENV/
|
|
91
|
+
env.bak/
|
|
92
|
+
venv.bak/
|
|
93
|
+
|
|
94
|
+
# Spyder project settings
|
|
95
|
+
.spyderproject
|
|
96
|
+
.spyproject
|
|
97
|
+
|
|
98
|
+
# Rope project settings
|
|
99
|
+
.ropeproject
|
|
100
|
+
|
|
101
|
+
# mkdocs documentation
|
|
102
|
+
/site
|
|
103
|
+
|
|
104
|
+
# mypy
|
|
105
|
+
.mypy_cache/
|
datec-0.3/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2019 isaacto
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
datec-0.3/PKG-INFO
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: datec
|
|
3
|
+
Version: 0.3
|
|
4
|
+
Summary: Date Command
|
|
5
|
+
Project-URL: Homepage, https://github.com/isaacto/datec
|
|
6
|
+
Project-URL: Repository, https://github.com/isaacto/datec.git
|
|
7
|
+
Project-URL: Issues, https://github.com/isaacto/datec/issues
|
|
8
|
+
Author-email: Isaac To <isaac.to@gmail.com>
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
20
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
21
|
+
Requires-Python: >=3.9
|
|
22
|
+
Requires-Dist: python-dateutil
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
|
|
25
|
+
# Date command: A command-based date computation engine
|
|
26
|
+
|
|
27
|
+
## Installation
|
|
28
|
+
|
|
29
|
+
You can install the package simply by
|
|
30
|
+
|
|
31
|
+
pip install datec
|
|
32
|
+
|
|
33
|
+
## Usage
|
|
34
|
+
|
|
35
|
+
datec allows you to use "date commands" to modify datetime's by adding
|
|
36
|
+
to them, like this:
|
|
37
|
+
|
|
38
|
+
datetime.datetime.now() + datec.Period(2, 'week')
|
|
39
|
+
|
|
40
|
+
A date command can be parsed from strings using the parse() function,
|
|
41
|
+
which create a command from a string representation. This forms the
|
|
42
|
+
basis of the datec command, which is a command-line program to output
|
|
43
|
+
datetime after applying date commands, or sleep until that time if
|
|
44
|
+
"-w" is given. In general the date representation is
|
|
45
|
+
NxYYYY-mm-ddTHH:MM:SS.ffffff, where unspecified parts are omitted
|
|
46
|
+
leaving the symbols intact, like "2x-2-29T3::." (see the following for
|
|
47
|
+
the meaning). If the fractional part is not specified the "." may be
|
|
48
|
+
omitted, if all time parts are not specified the "T::." can be
|
|
49
|
+
omitted, if all date parts are not specified the "--T" can be omitted,
|
|
50
|
+
and if Nx may be omitted in some cases for setting a partial datetime
|
|
51
|
+
or weekday. There are a couple other more formats like +3week and
|
|
52
|
+
-2wed for shifting by period and weekday.
|
|
53
|
+
|
|
54
|
+
Date commands are in two forms: period shifting commands and partial
|
|
55
|
+
datetime shifting commands. The first type is more familiar: they
|
|
56
|
+
look like
|
|
57
|
+
|
|
58
|
+
* +2week (shift the datetime forward by 2 week)
|
|
59
|
+
* -1month (shift the datetime backward by 1 month)
|
|
60
|
+
|
|
61
|
+
Period is one of year, month, week, day, hour, minute and second,
|
|
62
|
+
represented by an object of the Period class. Fractional numbers are
|
|
63
|
+
acceptable except for year and month. If shifting a period leads to
|
|
64
|
+
an invalid date (e.g., shift backward 1 month from 2019-07-31), it
|
|
65
|
+
moves backwards the closest valid date (here, 2019-06-30). In general
|
|
66
|
+
the parts finer than the shifted part is unaffected (e.g., shifting 1
|
|
67
|
+
month from 2019-07-31 02:00 gives you 2019-06-30 02:00).
|
|
68
|
+
|
|
69
|
+
Partial datetime shifting is less familiar. It looks like:
|
|
70
|
+
|
|
71
|
+
* 12:: (set the hour number to 12)
|
|
72
|
+
* +2x12:: (move forward to the second hour 12)
|
|
73
|
+
* +4x--31 (move forward to the fourth occurrence of day 31 of a month)
|
|
74
|
+
* -3x-02-29 (move backward to the third occurrence of February 29)
|
|
75
|
+
* wed (set to the Wednesday of the same week, week starts on Sunday)
|
|
76
|
+
* -3wed (move to the third Wednesday before the current datetime)
|
|
77
|
+
|
|
78
|
+
They are represented by either a Weekday object or a PartialDate
|
|
79
|
+
object with a count. A count of 0 means setting instead of shifting.
|
|
80
|
+
Only integer counts are acceptable.
|
|
81
|
+
|
|
82
|
+
A trailing "/" on a partial date command sets all fields after the
|
|
83
|
+
last specified field to zero. For example, `12::/` sets the hour to
|
|
84
|
+
12 and the minute, second and microsecond to 0, whereas `12::` would
|
|
85
|
+
leave those unchanged.
|
|
86
|
+
|
|
87
|
+
It is an error to set to an invalid date (e.g., --31 applied on
|
|
88
|
+
2019-06-25 is an error). The datetime parts which are specified must
|
|
89
|
+
be consecutive (it is an error to specify 12::05). It is also an
|
|
90
|
+
error to shift for occurrence of a partial date with year specified
|
|
91
|
+
(e.g., "+2x2019--").
|
|
92
|
+
|
|
93
|
+
On the other hand, shifting to an invalid date with day number
|
|
94
|
+
specified will shift more until a specified date is valid. For
|
|
95
|
+
example, if you add -2-29 with count 1 to 2019-01-01, you end up with
|
|
96
|
+
2020-02-29, because 2019-02-29 is not a valid date. If the count is 2
|
|
97
|
+
you get 2024-02-29 instead.
|
|
98
|
+
|
|
99
|
+
Shifting to an invalid date by a partial date with just a month number
|
|
100
|
+
will cause the date to moved backwards until the date is valid. E.g.,
|
|
101
|
+
if you shift by -6- with count 1 (next June) from 2019-05-31, you get
|
|
102
|
+
2019-06-30. With count 2 you get 2020-06-30.
|
|
103
|
+
|
|
104
|
+
All these functionalities are available in the constructors too. Read
|
|
105
|
+
their docstring to find how to use them.
|
|
106
|
+
|
|
107
|
+
This library is grown out of frustration that it is tedious to have a
|
|
108
|
+
shell script or program to get a datetime like "the next 6pm from now"
|
|
109
|
+
or "the next 3rd of any month from two days ago". With this module
|
|
110
|
+
they can be specified like "+1x18:00:00.0" and "-2day +1x--3"
|
|
111
|
+
respectively. In the expected use cases, counts are small numbers.
|
|
112
|
+
So the library is not always efficient (at times we just loop "count"
|
|
113
|
+
times to step forward or backward). Whenever it is simple to do so,
|
|
114
|
+
the implementation just forward to relativedelta, in which case they
|
|
115
|
+
are more efficient.
|
|
116
|
+
|
|
117
|
+
At present the program does not handle timezone and daylight saving.
|
|
118
|
+
This is bacause the author lives at a place where no daylight saving
|
|
119
|
+
is observed. Contributions are welcome.
|
datec-0.3/README.md
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# Date command: A command-based date computation engine
|
|
2
|
+
|
|
3
|
+
## Installation
|
|
4
|
+
|
|
5
|
+
You can install the package simply by
|
|
6
|
+
|
|
7
|
+
pip install datec
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
|
|
11
|
+
datec allows you to use "date commands" to modify datetime's by adding
|
|
12
|
+
to them, like this:
|
|
13
|
+
|
|
14
|
+
datetime.datetime.now() + datec.Period(2, 'week')
|
|
15
|
+
|
|
16
|
+
A date command can be parsed from strings using the parse() function,
|
|
17
|
+
which create a command from a string representation. This forms the
|
|
18
|
+
basis of the datec command, which is a command-line program to output
|
|
19
|
+
datetime after applying date commands, or sleep until that time if
|
|
20
|
+
"-w" is given. In general the date representation is
|
|
21
|
+
NxYYYY-mm-ddTHH:MM:SS.ffffff, where unspecified parts are omitted
|
|
22
|
+
leaving the symbols intact, like "2x-2-29T3::." (see the following for
|
|
23
|
+
the meaning). If the fractional part is not specified the "." may be
|
|
24
|
+
omitted, if all time parts are not specified the "T::." can be
|
|
25
|
+
omitted, if all date parts are not specified the "--T" can be omitted,
|
|
26
|
+
and if Nx may be omitted in some cases for setting a partial datetime
|
|
27
|
+
or weekday. There are a couple other more formats like +3week and
|
|
28
|
+
-2wed for shifting by period and weekday.
|
|
29
|
+
|
|
30
|
+
Date commands are in two forms: period shifting commands and partial
|
|
31
|
+
datetime shifting commands. The first type is more familiar: they
|
|
32
|
+
look like
|
|
33
|
+
|
|
34
|
+
* +2week (shift the datetime forward by 2 week)
|
|
35
|
+
* -1month (shift the datetime backward by 1 month)
|
|
36
|
+
|
|
37
|
+
Period is one of year, month, week, day, hour, minute and second,
|
|
38
|
+
represented by an object of the Period class. Fractional numbers are
|
|
39
|
+
acceptable except for year and month. If shifting a period leads to
|
|
40
|
+
an invalid date (e.g., shift backward 1 month from 2019-07-31), it
|
|
41
|
+
moves backwards the closest valid date (here, 2019-06-30). In general
|
|
42
|
+
the parts finer than the shifted part is unaffected (e.g., shifting 1
|
|
43
|
+
month from 2019-07-31 02:00 gives you 2019-06-30 02:00).
|
|
44
|
+
|
|
45
|
+
Partial datetime shifting is less familiar. It looks like:
|
|
46
|
+
|
|
47
|
+
* 12:: (set the hour number to 12)
|
|
48
|
+
* +2x12:: (move forward to the second hour 12)
|
|
49
|
+
* +4x--31 (move forward to the fourth occurrence of day 31 of a month)
|
|
50
|
+
* -3x-02-29 (move backward to the third occurrence of February 29)
|
|
51
|
+
* wed (set to the Wednesday of the same week, week starts on Sunday)
|
|
52
|
+
* -3wed (move to the third Wednesday before the current datetime)
|
|
53
|
+
|
|
54
|
+
They are represented by either a Weekday object or a PartialDate
|
|
55
|
+
object with a count. A count of 0 means setting instead of shifting.
|
|
56
|
+
Only integer counts are acceptable.
|
|
57
|
+
|
|
58
|
+
A trailing "/" on a partial date command sets all fields after the
|
|
59
|
+
last specified field to zero. For example, `12::/` sets the hour to
|
|
60
|
+
12 and the minute, second and microsecond to 0, whereas `12::` would
|
|
61
|
+
leave those unchanged.
|
|
62
|
+
|
|
63
|
+
It is an error to set to an invalid date (e.g., --31 applied on
|
|
64
|
+
2019-06-25 is an error). The datetime parts which are specified must
|
|
65
|
+
be consecutive (it is an error to specify 12::05). It is also an
|
|
66
|
+
error to shift for occurrence of a partial date with year specified
|
|
67
|
+
(e.g., "+2x2019--").
|
|
68
|
+
|
|
69
|
+
On the other hand, shifting to an invalid date with day number
|
|
70
|
+
specified will shift more until a specified date is valid. For
|
|
71
|
+
example, if you add -2-29 with count 1 to 2019-01-01, you end up with
|
|
72
|
+
2020-02-29, because 2019-02-29 is not a valid date. If the count is 2
|
|
73
|
+
you get 2024-02-29 instead.
|
|
74
|
+
|
|
75
|
+
Shifting to an invalid date by a partial date with just a month number
|
|
76
|
+
will cause the date to moved backwards until the date is valid. E.g.,
|
|
77
|
+
if you shift by -6- with count 1 (next June) from 2019-05-31, you get
|
|
78
|
+
2019-06-30. With count 2 you get 2020-06-30.
|
|
79
|
+
|
|
80
|
+
All these functionalities are available in the constructors too. Read
|
|
81
|
+
their docstring to find how to use them.
|
|
82
|
+
|
|
83
|
+
This library is grown out of frustration that it is tedious to have a
|
|
84
|
+
shell script or program to get a datetime like "the next 6pm from now"
|
|
85
|
+
or "the next 3rd of any month from two days ago". With this module
|
|
86
|
+
they can be specified like "+1x18:00:00.0" and "-2day +1x--3"
|
|
87
|
+
respectively. In the expected use cases, counts are small numbers.
|
|
88
|
+
So the library is not always efficient (at times we just loop "count"
|
|
89
|
+
times to step forward or backward). Whenever it is simple to do so,
|
|
90
|
+
the implementation just forward to relativedelta, in which case they
|
|
91
|
+
are more efficient.
|
|
92
|
+
|
|
93
|
+
At present the program does not handle timezone and daylight saving.
|
|
94
|
+
This is bacause the author lives at a place where no daylight saving
|
|
95
|
+
is observed. Contributions are welcome.
|
|
@@ -1,27 +1,29 @@
|
|
|
1
1
|
"""Date command: A command-based date computation engine
|
|
2
2
|
|
|
3
3
|
datec allows you to use "date commands" to modify datetime's by adding
|
|
4
|
-
to them, like
|
|
4
|
+
to them, like this:
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
datetime.datetime.now() + datec.Period(2, 'week')
|
|
7
|
+
|
|
8
|
+
A date command can be parsed from strings using the parse() function,
|
|
7
9
|
which create a command from a string representation. This forms the
|
|
8
10
|
basis of the datec command, which is a command-line program to output
|
|
9
11
|
datetime after applying date commands. In general the date
|
|
10
12
|
representation is NxYYYY-mm-ddTHH:MM:SS.ffffff, where unspecified
|
|
11
13
|
parts are omitted leaving the symbols intact, like "2x-2-29T3::." (see
|
|
12
|
-
the following for the meaning). If fractional part is not
|
|
13
|
-
the "." may be omitted, if all time parts are not specified
|
|
14
|
-
can be omitted, if all date parts are not specified the
|
|
15
|
-
omitted, and if
|
|
16
|
-
|
|
17
|
-
weekday.
|
|
14
|
+
the following for the meaning). If the fractional part is not
|
|
15
|
+
specified the "." may be omitted, if all time parts are not specified
|
|
16
|
+
the "T::." can be omitted, if all date parts are not specified the
|
|
17
|
+
"--T" can be omitted, and if Nx may be omitted in some cases for
|
|
18
|
+
setting a partial datetime or weekday. There are a couple other more
|
|
19
|
+
formats like +3week and -2wed for shifting by period and weekday.
|
|
18
20
|
|
|
19
21
|
Date commands are in two forms: period shifting commands and partial
|
|
20
22
|
datetime shifting commands. The first type is more familiar: they
|
|
21
23
|
look like
|
|
22
24
|
|
|
23
|
-
+2week (shift the datetime forward by 2 week)
|
|
24
|
-
-1month (shift the datetime backward by 1 month)
|
|
25
|
+
* +2week (shift the datetime forward by 2 week)
|
|
26
|
+
* -1month (shift the datetime backward by 1 month)
|
|
25
27
|
|
|
26
28
|
Period is one of year, month, week, day, hour, minute and second,
|
|
27
29
|
represented by an object of the Period class. Fractional numbers are
|
|
@@ -33,21 +35,26 @@ month from 2019-07-31 02:00 gives you 2019-06-30 02:00).
|
|
|
33
35
|
|
|
34
36
|
Partial datetime shifting is less familiar. It looks like:
|
|
35
37
|
|
|
36
|
-
12:: (set the hour number to 12)
|
|
37
|
-
+2x12:: (move forward to the second hour 12)
|
|
38
|
-
+4x--31 (move forward to the fourth occurrence of day 31 of a month)
|
|
39
|
-
-3x-02-29 (move backward to the third occurrence of February 29)
|
|
40
|
-
wed (set to the Wednesday of the same week, week starts on Sunday)
|
|
41
|
-
-3wed (move to the third Wednesday before the current datetime)
|
|
38
|
+
* 12:: (set the hour number to 12)
|
|
39
|
+
* +2x12:: (move forward to the second hour 12)
|
|
40
|
+
* +4x--31 (move forward to the fourth occurrence of day 31 of a month)
|
|
41
|
+
* -3x-02-29 (move backward to the third occurrence of February 29)
|
|
42
|
+
* wed (set to the Wednesday of the same week, week starts on Sunday)
|
|
43
|
+
* -3wed (move to the third Wednesday before the current datetime)
|
|
42
44
|
|
|
43
45
|
They are represented by either a Weekday object or a PartialDate
|
|
44
46
|
object with a count. A count of 0 means setting instead of shifting.
|
|
45
47
|
Only integer counts are acceptable.
|
|
46
48
|
|
|
49
|
+
A trailing "/" on a partial date command sets all fields after the
|
|
50
|
+
last specified field to zero. For example, `12::/` sets the hour to
|
|
51
|
+
12 and the minute, second and microsecond to 0, whereas `12::` would
|
|
52
|
+
leave those unchanged.
|
|
53
|
+
|
|
47
54
|
It is an error to set to an invalid date (e.g., --31 applied on
|
|
48
55
|
2019-06-25 is an error). The datetime parts which are specified must
|
|
49
56
|
be consecutive (it is an error to specify 12::05). It is also an
|
|
50
|
-
error to shift for occurrence of partial date with year specified
|
|
57
|
+
error to shift for occurrence of a partial date with year specified
|
|
51
58
|
(e.g., "+2x2019--").
|
|
52
59
|
|
|
53
60
|
On the other hand, shifting to an invalid date with day number
|
|
@@ -56,10 +63,10 @@ example, if you add -2-29 with count 1 to 2019-01-01, you end up with
|
|
|
56
63
|
2020-02-29, because 2019-02-29 is not a valid date. If the count is 2
|
|
57
64
|
you get 2024-02-29 instead.
|
|
58
65
|
|
|
59
|
-
Shifting to invalid date by
|
|
60
|
-
backwards until the date is valid. E.g.,
|
|
61
|
-
count 1 (next June) from 2019-05-31, you get
|
|
62
|
-
you get 2020-06-30.
|
|
66
|
+
Shifting to an invalid date by a partial date with just a month number
|
|
67
|
+
will cause the date to moved backwards until the date is valid. E.g.,
|
|
68
|
+
if you shift by -6- with count 1 (next June) from 2019-05-31, you get
|
|
69
|
+
2019-06-30. With count 2 you get 2020-06-30.
|
|
63
70
|
|
|
64
71
|
This library is grown out of frustration that it is tedious to have a
|
|
65
72
|
shell script or program to get a datetime like "the next 6pm from now"
|
|
@@ -77,16 +84,18 @@ is observed. Contributions are welcome.
|
|
|
77
84
|
|
|
78
85
|
"""
|
|
79
86
|
|
|
87
|
+
import contextlib
|
|
88
|
+
import datetime
|
|
80
89
|
import re
|
|
90
|
+
import typing
|
|
81
91
|
|
|
82
92
|
import dateutil.relativedelta as dr
|
|
83
93
|
|
|
84
94
|
|
|
85
|
-
|
|
86
|
-
|
|
95
|
+
__version__ = '0.3'
|
|
87
96
|
|
|
88
97
|
class ParseError(ValueError):
|
|
89
|
-
|
|
98
|
+
"""Represent an error in parsing."""
|
|
90
99
|
|
|
91
100
|
|
|
92
101
|
class Period:
|
|
@@ -105,14 +114,18 @@ class Period:
|
|
|
105
114
|
period (str): The period
|
|
106
115
|
|
|
107
116
|
"""
|
|
108
|
-
def __init__(self, count, period):
|
|
109
|
-
|
|
110
|
-
'hour', 'minute', 'second')
|
|
117
|
+
def __init__(self, count: float, period: str):
|
|
118
|
+
if period not in ('year', 'month', 'week', 'day',
|
|
119
|
+
'hour', 'minute', 'second'):
|
|
120
|
+
raise ValueError(f'Invalid period: {period}')
|
|
121
|
+
if period in ('year', 'month') and count != int(count):
|
|
122
|
+
raise ValueError(f'Invalid count {count} for period {period}')
|
|
111
123
|
self._count = count
|
|
112
124
|
self._period = period
|
|
113
125
|
|
|
114
|
-
def __radd__(self, dt):
|
|
115
|
-
return dt + dr.relativedelta(
|
|
126
|
+
def __radd__(self, dt: datetime.datetime) -> datetime.datetime:
|
|
127
|
+
return dt + dr.relativedelta(
|
|
128
|
+
**{self._period + 's': self._count}) # type: ignore
|
|
116
129
|
|
|
117
130
|
PARSE_RE = re.compile(r'''
|
|
118
131
|
^
|
|
@@ -122,7 +135,7 @@ class Period:
|
|
|
122
135
|
''', re.X)
|
|
123
136
|
|
|
124
137
|
@classmethod
|
|
125
|
-
def parse(cls, cmdstr):
|
|
138
|
+
def parse(cls, cmdstr: str) -> 'Period':
|
|
126
139
|
"""Parse a command string to a Period object
|
|
127
140
|
|
|
128
141
|
The command string should be of the form "<N><period>", where
|
|
@@ -138,11 +151,13 @@ class Period:
|
|
|
138
151
|
if not match:
|
|
139
152
|
raise ParseError('Cannot parse string %s' % cmdstr)
|
|
140
153
|
gdt = match.groupdict()
|
|
154
|
+
cnt: float
|
|
155
|
+
with contextlib.suppress(ValueError):
|
|
156
|
+
return cls(int(gdt['count']), gdt['period'])
|
|
141
157
|
try:
|
|
142
|
-
|
|
143
|
-
except
|
|
144
|
-
|
|
145
|
-
return cls(cnt, gdt['period'])
|
|
158
|
+
return cls(float(gdt['count']), gdt['period'])
|
|
159
|
+
except ValueError as err:
|
|
160
|
+
raise ParseError('Cannot parse string %s' % cmdstr)
|
|
146
161
|
|
|
147
162
|
|
|
148
163
|
_WEEKDAY_CLS = [dr.SU, dr.MO, dr.TU, dr.WE, dr.TH, dr.FR, dr.SA]
|
|
@@ -169,13 +184,14 @@ class Weekday:
|
|
|
169
184
|
day (int): The weekday
|
|
170
185
|
|
|
171
186
|
"""
|
|
172
|
-
def __init__(self, count, day):
|
|
187
|
+
def __init__(self, count: int, day: int):
|
|
173
188
|
self._count = count
|
|
174
|
-
|
|
189
|
+
if day not in range(7):
|
|
190
|
+
raise ValueError('Invalid weekday: %d' % day)
|
|
175
191
|
self._day = day
|
|
176
192
|
self._drcls = _WEEKDAY_CLS[day]
|
|
177
193
|
|
|
178
|
-
def __radd__(self, dt):
|
|
194
|
+
def __radd__(self, dt: datetime.datetime) -> datetime.datetime:
|
|
179
195
|
if self._count > 0:
|
|
180
196
|
return dt + dr.relativedelta(
|
|
181
197
|
days=1, weekday=self._drcls(self._count))
|
|
@@ -189,13 +205,13 @@ class Weekday:
|
|
|
189
205
|
|
|
190
206
|
PARSE_RE = re.compile(r'''
|
|
191
207
|
^
|
|
192
|
-
(?P<count> [+-] (?: [0-9]+
|
|
208
|
+
(?P<count> [+-] (?: [0-9]+) )?
|
|
193
209
|
(?P<weekday> sun|mon|tue|wed|thu|fri|sat)
|
|
194
210
|
$
|
|
195
211
|
''', re.X)
|
|
196
212
|
|
|
197
213
|
@classmethod
|
|
198
|
-
def parse(cls, cmdstr):
|
|
214
|
+
def parse(cls, cmdstr: str) -> 'Weekday':
|
|
199
215
|
"""Parse a command string to a Weekday object
|
|
200
216
|
|
|
201
217
|
The command string should be of the form "<N><weekday>", where
|
|
@@ -235,31 +251,44 @@ class PartialDate:
|
|
|
235
251
|
|
|
236
252
|
Args:
|
|
237
253
|
|
|
238
|
-
count
|
|
239
|
-
year
|
|
240
|
-
month
|
|
241
|
-
day
|
|
242
|
-
hour
|
|
243
|
-
minute
|
|
244
|
-
second
|
|
245
|
-
microsecond
|
|
254
|
+
count: The number of periods to shift
|
|
255
|
+
year: The year number
|
|
256
|
+
month: The month number (1 to 12)
|
|
257
|
+
day: The day number (1 to 31)
|
|
258
|
+
hour: The hour number (0 to 23)
|
|
259
|
+
minute: The minute number (0 to 59)
|
|
260
|
+
second: The second number (0 to smaller than 60)
|
|
261
|
+
microsecond: The microsecond number (0 to 999999)
|
|
262
|
+
zero: Whether to zero out the fields after the last specified one
|
|
246
263
|
|
|
247
264
|
"""
|
|
248
265
|
|
|
249
266
|
_INVALID_SIG_RE = re.compile('10+1')
|
|
250
267
|
|
|
251
|
-
def __init__(
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
268
|
+
def __init__(
|
|
269
|
+
self, count: int = 0, year: typing.Optional[int] = None,
|
|
270
|
+
month: typing.Optional[int] = None, day: typing.Optional[int] = None,
|
|
271
|
+
hour: typing.Optional[int] = None, minute: typing.Optional[int] = None,
|
|
272
|
+
second: typing.Optional[typing.Union[int, float]] = None,
|
|
273
|
+
microsecond: typing.Optional[int] = None,
|
|
274
|
+
zero: bool = False
|
|
275
|
+
):
|
|
276
|
+
if count and year:
|
|
277
|
+
raise ValueError('Absolute date with non-zero count')
|
|
278
|
+
if isinstance(second, float) and microsecond is not None:
|
|
279
|
+
raise ValueError('Doubly specified microsecond')
|
|
260
280
|
if isinstance(second, float):
|
|
261
281
|
second, orig_second = int(second), second
|
|
262
282
|
microsecond = int((orig_second - second) * 1000000 + 0.5)
|
|
283
|
+
vals = [year, month, day, hour, minute, second, microsecond]
|
|
284
|
+
sig = ''.join([("0" if v is None else "1") for v in vals])
|
|
285
|
+
if self._INVALID_SIG_RE.search(sig):
|
|
286
|
+
raise ValueError('Non-consecutive components')
|
|
287
|
+
if zero:
|
|
288
|
+
lastset = sig.rfind('1')
|
|
289
|
+
for i in range(lastset + 1, 7):
|
|
290
|
+
vals[i] = 0
|
|
291
|
+
year, month, day, hour, minute, second, microsecond = vals
|
|
263
292
|
self._count = count
|
|
264
293
|
self._year = year
|
|
265
294
|
self._month = month
|
|
@@ -274,7 +303,7 @@ class PartialDate:
|
|
|
274
303
|
'', 'years', 'months', 'days', 'hours', 'minutes', 'seconds'
|
|
275
304
|
]
|
|
276
305
|
|
|
277
|
-
def __radd__(self, dt):
|
|
306
|
+
def __radd__(self, dt: datetime.datetime) -> datetime.datetime:
|
|
278
307
|
if self._firstset == -1:
|
|
279
308
|
return dt
|
|
280
309
|
if not(self._count):
|
|
@@ -288,7 +317,7 @@ class PartialDate:
|
|
|
288
317
|
return self._monthshift(dt)
|
|
289
318
|
return self._dayshift(dt)
|
|
290
319
|
|
|
291
|
-
def _rset(self, dt):
|
|
320
|
+
def _rset(self, dt: datetime.datetime) -> datetime.datetime:
|
|
292
321
|
updater = {'year': self._year,
|
|
293
322
|
'month': self._month,
|
|
294
323
|
'day': self._day,
|
|
@@ -297,9 +326,9 @@ class PartialDate:
|
|
|
297
326
|
'second': self._second,
|
|
298
327
|
'microsecond': self._microsecond}
|
|
299
328
|
updater = {k: v for k, v in updater.items() if v is not None}
|
|
300
|
-
return dt.replace(**updater)
|
|
329
|
+
return dt.replace(**updater) # type: ignore
|
|
301
330
|
|
|
302
|
-
def _simpleshift(self, dt):
|
|
331
|
+
def _simpleshift(self, dt: datetime.datetime) -> datetime.datetime:
|
|
303
332
|
remain = self._count
|
|
304
333
|
ret = self._rset(dt)
|
|
305
334
|
if self._count < 0:
|
|
@@ -309,9 +338,9 @@ class PartialDate:
|
|
|
309
338
|
if ret > dt:
|
|
310
339
|
remain -= 1
|
|
311
340
|
mod_field = self._FIRSTSET_MOD[self._firstset]
|
|
312
|
-
return ret + dr.relativedelta(**{mod_field: remain})
|
|
341
|
+
return ret + dr.relativedelta(**{mod_field: remain}) # type: ignore
|
|
313
342
|
|
|
314
|
-
def _dayshift(self, dt):
|
|
343
|
+
def _dayshift(self, dt: datetime.datetime) -> datetime.datetime:
|
|
315
344
|
# Day specified
|
|
316
345
|
if self._firstset == 2: # modify month
|
|
317
346
|
shift = dr.relativedelta(months=1 if self._count > 0 else -1)
|
|
@@ -344,8 +373,9 @@ class PartialDate:
|
|
|
344
373
|
continue
|
|
345
374
|
count -= 1
|
|
346
375
|
|
|
347
|
-
def _monthshift(self, dt):
|
|
376
|
+
def _monthshift(self, dt: datetime.datetime) -> datetime.datetime:
|
|
348
377
|
# Only month specified, shift by month rather than by year
|
|
378
|
+
assert self._month is not None
|
|
349
379
|
if self._count > 0:
|
|
350
380
|
num_months = self._month - dt.month
|
|
351
381
|
sign = 1
|
|
@@ -389,7 +419,7 @@ class PartialDate:
|
|
|
389
419
|
''', re.X)
|
|
390
420
|
|
|
391
421
|
@classmethod
|
|
392
|
-
def parse(cls, cmdstr):
|
|
422
|
+
def parse(cls, cmdstr: str) -> 'PartialDate':
|
|
393
423
|
"""Parse a command string to a PartialDate object
|
|
394
424
|
|
|
395
425
|
The command string should be of the form
|
|
@@ -399,13 +429,17 @@ class PartialDate:
|
|
|
399
429
|
empty string. If all date parts are not specified the "--T"
|
|
400
430
|
may be omitted. If all the time parts are not specified the
|
|
401
431
|
"T::." may be omitted. If the microsecond part is not
|
|
402
|
-
specified the "." part may be omitted.
|
|
432
|
+
specified the "." part may be omitted. A trailing "/" causes
|
|
433
|
+
all fields after the last specified field to be set to 0.
|
|
403
434
|
|
|
404
435
|
Args:
|
|
405
436
|
|
|
406
437
|
cmdstr (str): The command string
|
|
407
438
|
|
|
408
439
|
"""
|
|
440
|
+
zero = cmdstr.endswith('/')
|
|
441
|
+
if zero:
|
|
442
|
+
cmdstr = cmdstr[:-1]
|
|
409
443
|
match = cls.PARSE_RE1.match(cmdstr.lower())
|
|
410
444
|
if not match:
|
|
411
445
|
match = cls.PARSE_RE2.match(cmdstr)
|
|
@@ -413,7 +447,7 @@ class PartialDate:
|
|
|
413
447
|
raise ParseError('Cannot parse string %s' % cmdstr)
|
|
414
448
|
gdt = match.groupdict()
|
|
415
449
|
|
|
416
|
-
def _matchval(key):
|
|
450
|
+
def _matchval(key: str) -> typing.Optional[int]:
|
|
417
451
|
val = gdt.get(key)
|
|
418
452
|
if not val:
|
|
419
453
|
return None
|
|
@@ -430,10 +464,11 @@ class PartialDate:
|
|
|
430
464
|
_matchval('hour'),
|
|
431
465
|
_matchval('minute'),
|
|
432
466
|
_matchval('second'),
|
|
433
|
-
microsecond
|
|
467
|
+
microsecond,
|
|
468
|
+
zero)
|
|
434
469
|
|
|
435
470
|
|
|
436
|
-
def parse(cmdstr):
|
|
471
|
+
def parse(cmdstr: str) -> typing.Union[Period, Weekday, PartialDate]:
|
|
437
472
|
"""Attempt to parse one of the possible date command
|
|
438
473
|
|
|
439
474
|
Args:
|
|
@@ -1,21 +1,25 @@
|
|
|
1
1
|
from __future__ import print_function
|
|
2
2
|
|
|
3
|
-
import sys
|
|
4
3
|
import datetime
|
|
4
|
+
import sys
|
|
5
|
+
import time
|
|
5
6
|
|
|
6
7
|
import datec
|
|
7
8
|
|
|
8
9
|
|
|
9
|
-
def main():
|
|
10
|
+
def main() -> None:
|
|
10
11
|
dt = datetime.datetime.now()
|
|
12
|
+
wait = False
|
|
11
13
|
for cmd in sys.argv[1:]:
|
|
12
14
|
if cmd == '-h':
|
|
13
15
|
print('''Date command
|
|
14
16
|
|
|
15
|
-
Usage: datec [<command>] ...
|
|
17
|
+
Usage: datec [-w] [<command>] ...
|
|
16
18
|
|
|
17
|
-
Datec starts from the current time and apply commands.
|
|
18
|
-
datetime is printed in ISO YYYY-MM-DDTHH:MM:SS.ffffff format.
|
|
19
|
+
Datec starts from the current time and apply commands. Normally the
|
|
20
|
+
ending datetime is printed in ISO YYYY-MM-DDTHH:MM:SS.ffffff format.
|
|
21
|
+
But if -w is given, no output is printed; instead the program sleeps
|
|
22
|
+
until the ending datetime.
|
|
19
23
|
|
|
20
24
|
<command> may be of the following forms:
|
|
21
25
|
|
|
@@ -28,8 +32,16 @@ datetime is printed in ISO YYYY-MM-DDTHH:MM:SS.ffffff format.
|
|
|
28
32
|
|
|
29
33
|
''', file=sys.stderr)
|
|
30
34
|
sys.exit(0)
|
|
35
|
+
if cmd == '-w':
|
|
36
|
+
wait = True
|
|
37
|
+
continue
|
|
31
38
|
dt = dt + datec.parse(cmd.lower())
|
|
32
|
-
|
|
39
|
+
if wait:
|
|
40
|
+
delta = (dt - datetime.datetime.now()).total_seconds()
|
|
41
|
+
if delta > 0:
|
|
42
|
+
time.sleep(delta)
|
|
43
|
+
else:
|
|
44
|
+
print(dt.isoformat())
|
|
33
45
|
|
|
34
46
|
|
|
35
47
|
if __name__ == '__main__':
|
datec-0.3/datec/py.typed
ADDED
|
File without changes
|
datec-0.3/pyproject.toml
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
dynamic = ["version"]
|
|
7
|
+
name = "datec"
|
|
8
|
+
readme = "README.md"
|
|
9
|
+
requires-python = ">= 3.9"
|
|
10
|
+
dependencies = [
|
|
11
|
+
"python-dateutil"
|
|
12
|
+
]
|
|
13
|
+
authors = [
|
|
14
|
+
{ name="Isaac To", email="isaac.to@gmail.com" },
|
|
15
|
+
]
|
|
16
|
+
description = "Date Command"
|
|
17
|
+
license = "MIT"
|
|
18
|
+
license-files = ["LICENSE"]
|
|
19
|
+
classifiers=[
|
|
20
|
+
"Development Status :: 4 - Beta",
|
|
21
|
+
"Intended Audience :: Developers",
|
|
22
|
+
"Topic :: Software Development :: Libraries",
|
|
23
|
+
"Programming Language :: Python :: 3.9",
|
|
24
|
+
"Programming Language :: Python :: 3.10",
|
|
25
|
+
"Programming Language :: Python :: 3.11",
|
|
26
|
+
"Programming Language :: Python :: 3.12",
|
|
27
|
+
"Programming Language :: Python :: 3.13",
|
|
28
|
+
"Programming Language :: Python :: 3.14",
|
|
29
|
+
"Operating System :: OS Independent",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
[project.scripts]
|
|
33
|
+
datec = "datec:__main__.main"
|
|
34
|
+
|
|
35
|
+
[project.urls]
|
|
36
|
+
Homepage = "https://github.com/isaacto/datec"
|
|
37
|
+
Repository = "https://github.com/isaacto/datec.git"
|
|
38
|
+
Issues = "https://github.com/isaacto/datec/issues"
|
|
39
|
+
|
|
40
|
+
[tool.hatch.version]
|
|
41
|
+
source = "regex"
|
|
42
|
+
path = "datec/__init__.py"
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
import unittest
|
|
3
|
+
|
|
4
|
+
import datec
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class DatecTest(unittest.TestCase):
|
|
8
|
+
def test_period(self):
|
|
9
|
+
dt = datetime.datetime(2019, 5, 15)
|
|
10
|
+
self.assertEqual(dt + datec.Period(1, 'month'),
|
|
11
|
+
datetime.datetime(2019, 6, 15))
|
|
12
|
+
self.assertEqual(dt + datec.parse('+1.5second'),
|
|
13
|
+
datetime.datetime(2019, 5, 15, 0, 0, 1, 500000))
|
|
14
|
+
with self.assertRaises(datec.ParseError):
|
|
15
|
+
datec.Period.parse('+1mon')
|
|
16
|
+
with self.assertRaises(datec.ParseError):
|
|
17
|
+
datec.Period.parse('+.day')
|
|
18
|
+
|
|
19
|
+
def test_weekday(self):
|
|
20
|
+
dt = datetime.datetime(2019, 5, 15)
|
|
21
|
+
self.assertEqual(dt + datec.Weekday(1, datec.MON),
|
|
22
|
+
datetime.datetime(2019, 5, 20))
|
|
23
|
+
self.assertEqual(dt + datec.Weekday.parse('+2mon'),
|
|
24
|
+
datetime.datetime(2019, 5, 27))
|
|
25
|
+
self.assertEqual(dt + datec.Weekday.parse('-2WED'),
|
|
26
|
+
datetime.datetime(2019, 5, 1))
|
|
27
|
+
self.assertEqual(dt + datec.parse('MON'),
|
|
28
|
+
datetime.datetime(2019, 5, 13))
|
|
29
|
+
self.assertEqual(dt + datec.Weekday(0, datec.FRI),
|
|
30
|
+
datetime.datetime(2019, 5, 17))
|
|
31
|
+
with self.assertRaises(datec.ParseError):
|
|
32
|
+
datec.Weekday.parse('MO')
|
|
33
|
+
|
|
34
|
+
def test_partialdate_simple(self):
|
|
35
|
+
dt = datetime.datetime(2019, 5, 15, 8, 15)
|
|
36
|
+
self.assertEqual(dt + datec.PartialDate(1),
|
|
37
|
+
datetime.datetime(2019, 5, 15, 8, 15))
|
|
38
|
+
self.assertEqual(dt + datec.PartialDate(0, minute=7, second=16),
|
|
39
|
+
datetime.datetime(2019, 5, 15, 8, 7, 16))
|
|
40
|
+
self.assertEqual(dt + datec.PartialDate(1, minute=7, second=16),
|
|
41
|
+
datetime.datetime(2019, 5, 15, 9, 7, 16))
|
|
42
|
+
self.assertEqual(dt + datec.PartialDate(1, minute=17, second=16),
|
|
43
|
+
datetime.datetime(2019, 5, 15, 8, 17, 16))
|
|
44
|
+
self.assertEqual(dt + datec.PartialDate(2, minute=7, second=16),
|
|
45
|
+
datetime.datetime(2019, 5, 15, 10, 7, 16))
|
|
46
|
+
self.assertEqual(dt + datec.PartialDate(2, minute=7, second=16.5),
|
|
47
|
+
datetime.datetime(2019, 5, 15, 10, 7, 16, 500000))
|
|
48
|
+
self.assertEqual(dt + datec.PartialDate.parse(':7:16'),
|
|
49
|
+
datetime.datetime(2019, 5, 15, 8, 7, 16))
|
|
50
|
+
self.assertEqual(dt + datec.PartialDate.parse('-1x:7:16'),
|
|
51
|
+
datetime.datetime(2019, 5, 15, 8, 7, 16))
|
|
52
|
+
self.assertEqual(dt + datec.PartialDate.parse('-1x:17:16'),
|
|
53
|
+
datetime.datetime(2019, 5, 15, 7, 17, 16))
|
|
54
|
+
self.assertEqual(dt + datec.parse('-1x:17:16.2'),
|
|
55
|
+
datetime.datetime(2019, 5, 15, 7, 17, 16, 200000))
|
|
56
|
+
with self.assertRaises(datec.ParseError):
|
|
57
|
+
datec.PartialDate.parse('---')
|
|
58
|
+
|
|
59
|
+
def test_partialdate_monthshift(self):
|
|
60
|
+
dt = datetime.datetime(2019, 5, 15)
|
|
61
|
+
self.assertEqual(dt + datec.PartialDate(1, month=2),
|
|
62
|
+
datetime.datetime(2020, 2, 15))
|
|
63
|
+
self.assertEqual(dt + datec.PartialDate(-1, month=2),
|
|
64
|
+
datetime.datetime(2019, 2, 15))
|
|
65
|
+
dt = datetime.datetime(2019, 5, 15)
|
|
66
|
+
self.assertEqual(dt + datec.PartialDate.parse('+1x-2-'),
|
|
67
|
+
datetime.datetime(2020, 2, 15))
|
|
68
|
+
dt = datetime.datetime(2019, 5, 30)
|
|
69
|
+
self.assertEqual(dt + datec.PartialDate(1, month=2),
|
|
70
|
+
datetime.datetime(2020, 2, 29))
|
|
71
|
+
|
|
72
|
+
def test_partialdate_dayshift(self):
|
|
73
|
+
dt = datetime.datetime(2019, 5, 30)
|
|
74
|
+
self.assertEqual(dt + datec.PartialDate(1, month=5, day=30),
|
|
75
|
+
datetime.datetime(2020, 5, 30))
|
|
76
|
+
self.assertEqual(dt + datec.PartialDate(1, day=31),
|
|
77
|
+
datetime.datetime(2019, 5, 31))
|
|
78
|
+
self.assertEqual(dt + datec.PartialDate(2, day=31),
|
|
79
|
+
datetime.datetime(2019, 7, 31))
|
|
80
|
+
self.assertEqual(dt + datec.PartialDate(-1, day=31),
|
|
81
|
+
datetime.datetime(2019, 3, 31))
|
|
82
|
+
dt = datetime.datetime(2019, 2, 15)
|
|
83
|
+
self.assertEqual(dt + datec.PartialDate(1, day=30),
|
|
84
|
+
datetime.datetime(2019, 3, 30))
|
|
85
|
+
with self.assertRaises(ValueError):
|
|
86
|
+
dt + datec.PartialDate(1, month=2, day=30)
|
|
87
|
+
|
|
88
|
+
def test_partialdate_zero(self):
|
|
89
|
+
dt = datetime.datetime(2019, 5, 15, 8, 15)
|
|
90
|
+
self.assertEqual(dt + datec.PartialDate(0, hour=12, zero=True),
|
|
91
|
+
datetime.datetime(2019, 5, 15, 12, 0, 0))
|
|
92
|
+
self.assertEqual(dt + datec.PartialDate(0, minute=30, zero=True),
|
|
93
|
+
datetime.datetime(2019, 5, 15, 8, 30, 0))
|
|
94
|
+
self.assertEqual(dt + datec.parse('12::/'),
|
|
95
|
+
datetime.datetime(2019, 5, 15, 12, 0, 0))
|
|
96
|
+
self.assertEqual(dt + datec.parse('12:30:/'),
|
|
97
|
+
datetime.datetime(2019, 5, 15, 12, 30, 0))
|
|
98
|
+
self.assertEqual(dt + datec.parse('2020-03-15/'),
|
|
99
|
+
datetime.datetime(2020, 3, 15, 0, 0, 0))
|
|
100
|
+
# without zero, minute/second are unchanged
|
|
101
|
+
self.assertEqual(dt + datec.PartialDate(0, hour=12),
|
|
102
|
+
datetime.datetime(2019, 5, 15, 12, 15))
|
|
103
|
+
self.assertEqual(dt + datec.parse('12::'),
|
|
104
|
+
datetime.datetime(2019, 5, 15, 12, 15))
|
|
105
|
+
|
|
106
|
+
def test_validation(self):
|
|
107
|
+
with self.assertRaises(ValueError):
|
|
108
|
+
datec.Period(1, 'invalid')
|
|
109
|
+
with self.assertRaises(ValueError):
|
|
110
|
+
datec.Period(1.5, 'month')
|
|
111
|
+
with self.assertRaises(ValueError):
|
|
112
|
+
datec.Weekday(1, 7)
|
|
113
|
+
with self.assertRaises(ValueError):
|
|
114
|
+
datec.PartialDate(1, year=2020)
|
|
115
|
+
with self.assertRaises(ValueError):
|
|
116
|
+
datec.PartialDate(second=1.5, microsecond=500)
|
|
117
|
+
with self.assertRaises(ValueError):
|
|
118
|
+
datec.PartialDate(hour=12, second=30)
|
datec-0.1/PKG-INFO
DELETED
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 1.1
|
|
2
|
-
Name: datec
|
|
3
|
-
Version: 0.1
|
|
4
|
-
Summary: Date Command
|
|
5
|
-
Home-page: https://github.com/isaacto/datec
|
|
6
|
-
Author: Isaac To
|
|
7
|
-
Author-email: isaac.to@gmail.com
|
|
8
|
-
License: UNKNOWN
|
|
9
|
-
Description: # Date command: A command-based date computation engine
|
|
10
|
-
|
|
11
|
-
datec allows you to use "date commands" to modify datetime's by adding
|
|
12
|
-
to them, like datetime.datetime.now() + Period(2, 'week').
|
|
13
|
-
|
|
14
|
-
A date command can be parsed from string using the parse() function,
|
|
15
|
-
which create a command from a string representation. This forms the
|
|
16
|
-
basis of the datec command, which is a command-line program to output
|
|
17
|
-
datetime after applying date commands. In general the date
|
|
18
|
-
representation is NxYYYY-mm-ddTHH:MM:SS.ffffff, where unspecified
|
|
19
|
-
parts are omitted leaving the symbols intact, like "2x-2-29T3::." (see
|
|
20
|
-
the following for the meaning). If fractional part is not specified
|
|
21
|
-
the "." may be omitted, if all time parts are not specified the "T::."
|
|
22
|
-
can be omitted, if all date parts are not specified the "--T" can be
|
|
23
|
-
omitted, and if N is 0 the x may be omitted. By there are a couple
|
|
24
|
-
other more formats like +3week and -2wed for shifting by period and
|
|
25
|
-
weekday.
|
|
26
|
-
|
|
27
|
-
Date commands are in two forms: period shifting commands and partial
|
|
28
|
-
datetime shifting commands. The first type is more familiar: they
|
|
29
|
-
look like
|
|
30
|
-
|
|
31
|
-
* +2week (shift the datetime forward by 2 week)
|
|
32
|
-
* -1month (shift the datetime backward by 1 month)
|
|
33
|
-
|
|
34
|
-
Period is one of year, month, week, day, hour, minute and second,
|
|
35
|
-
represented by an object of the Period class. Fractional numbers are
|
|
36
|
-
acceptable except for year and month. If shifting a period leads to
|
|
37
|
-
an invalid date (e.g., shift backward 1 month from 2019-07-31), it
|
|
38
|
-
moves backwards the closest valid date (here, 2019-06-30). In general
|
|
39
|
-
the parts finer than the shifted part is unaffected (e.g., shifting 1
|
|
40
|
-
month from 2019-07-31 02:00 gives you 2019-06-30 02:00).
|
|
41
|
-
|
|
42
|
-
Partial datetime shifting is less familiar. It looks like:
|
|
43
|
-
|
|
44
|
-
* 12:: (set the hour number to 12)
|
|
45
|
-
* +2x12:: (move forward to the second hour 12)
|
|
46
|
-
* +4x--31 (move forward to the fourth occurrence of day 31 of a month)
|
|
47
|
-
* -3x-02-29 (move backward to the third occurrence of February 29)
|
|
48
|
-
* wed (set to the Wednesday of the same week, week starts on Sunday)
|
|
49
|
-
* -3wed (move to the third Wednesday before the current datetime)
|
|
50
|
-
|
|
51
|
-
They are represented by either a Weekday object or a PartialDate
|
|
52
|
-
object with a count. A count of 0 means setting instead of shifting.
|
|
53
|
-
Only integer counts are acceptable.
|
|
54
|
-
|
|
55
|
-
It is an error to set to an invalid date (e.g., --31 applied on
|
|
56
|
-
2019-06-25 is an error). The datetime parts which are specified must
|
|
57
|
-
be consecutive (it is an error to specify 12::05). It is also an
|
|
58
|
-
error to shift for occurrence of partial date with year specified
|
|
59
|
-
(e.g., "+2x2019--").
|
|
60
|
-
|
|
61
|
-
On the other hand, shifting to an invalid date with day number
|
|
62
|
-
specified will shift more until a specified date is valid. For
|
|
63
|
-
example, if you add -2-29 with count 1 to 2019-01-01, you end up with
|
|
64
|
-
2020-02-29, because 2019-02-29 is not a valid date. If the count is 2
|
|
65
|
-
you get 2024-02-29 instead.
|
|
66
|
-
|
|
67
|
-
Shifting to invalid date by month will cause the date to moved
|
|
68
|
-
backwards until the date is valid. E.g., if you shift by -6- with
|
|
69
|
-
count 1 (next June) from 2019-05-31, you get 2019-06-30. With count 2
|
|
70
|
-
you get 2020-06-30.
|
|
71
|
-
|
|
72
|
-
This library is grown out of frustration that it is tedious to have a
|
|
73
|
-
shell script or program to get a datetime like "the next 6pm from now"
|
|
74
|
-
or "the next 3rd of any month from two days ago". With this module
|
|
75
|
-
they can be specified like "+1x18:00:00.0" and "-2day +1x--3"
|
|
76
|
-
respectively. In the expected use cases, counts are small numbers.
|
|
77
|
-
So the library is not always efficient (at times we just loop "count"
|
|
78
|
-
times to step forward or backward). Whenever it is simple to do so,
|
|
79
|
-
the implementation just forward to relativedelta, in which case they
|
|
80
|
-
are more efficient.
|
|
81
|
-
|
|
82
|
-
At present the program does not handle timezone and daylight saving.
|
|
83
|
-
This is bacause the author lives at a place where no daylight saving
|
|
84
|
-
is observed. Contributions are welcome.
|
|
85
|
-
|
|
86
|
-
Platform: UNKNOWN
|
|
87
|
-
Classifier: Development Status :: 4 - Beta
|
|
88
|
-
Classifier: Programming Language :: Python :: 2.7
|
|
89
|
-
Classifier: Programming Language :: Python :: 3
|
|
90
|
-
Classifier: Topic :: Software Development :: Libraries
|
|
91
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
92
|
-
Classifier: Operating System :: OS Independent
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 1.1
|
|
2
|
-
Name: datec
|
|
3
|
-
Version: 0.1
|
|
4
|
-
Summary: Date Command
|
|
5
|
-
Home-page: https://github.com/isaacto/datec
|
|
6
|
-
Author: Isaac To
|
|
7
|
-
Author-email: isaac.to@gmail.com
|
|
8
|
-
License: UNKNOWN
|
|
9
|
-
Description: # Date command: A command-based date computation engine
|
|
10
|
-
|
|
11
|
-
datec allows you to use "date commands" to modify datetime's by adding
|
|
12
|
-
to them, like datetime.datetime.now() + Period(2, 'week').
|
|
13
|
-
|
|
14
|
-
A date command can be parsed from string using the parse() function,
|
|
15
|
-
which create a command from a string representation. This forms the
|
|
16
|
-
basis of the datec command, which is a command-line program to output
|
|
17
|
-
datetime after applying date commands. In general the date
|
|
18
|
-
representation is NxYYYY-mm-ddTHH:MM:SS.ffffff, where unspecified
|
|
19
|
-
parts are omitted leaving the symbols intact, like "2x-2-29T3::." (see
|
|
20
|
-
the following for the meaning). If fractional part is not specified
|
|
21
|
-
the "." may be omitted, if all time parts are not specified the "T::."
|
|
22
|
-
can be omitted, if all date parts are not specified the "--T" can be
|
|
23
|
-
omitted, and if N is 0 the x may be omitted. By there are a couple
|
|
24
|
-
other more formats like +3week and -2wed for shifting by period and
|
|
25
|
-
weekday.
|
|
26
|
-
|
|
27
|
-
Date commands are in two forms: period shifting commands and partial
|
|
28
|
-
datetime shifting commands. The first type is more familiar: they
|
|
29
|
-
look like
|
|
30
|
-
|
|
31
|
-
* +2week (shift the datetime forward by 2 week)
|
|
32
|
-
* -1month (shift the datetime backward by 1 month)
|
|
33
|
-
|
|
34
|
-
Period is one of year, month, week, day, hour, minute and second,
|
|
35
|
-
represented by an object of the Period class. Fractional numbers are
|
|
36
|
-
acceptable except for year and month. If shifting a period leads to
|
|
37
|
-
an invalid date (e.g., shift backward 1 month from 2019-07-31), it
|
|
38
|
-
moves backwards the closest valid date (here, 2019-06-30). In general
|
|
39
|
-
the parts finer than the shifted part is unaffected (e.g., shifting 1
|
|
40
|
-
month from 2019-07-31 02:00 gives you 2019-06-30 02:00).
|
|
41
|
-
|
|
42
|
-
Partial datetime shifting is less familiar. It looks like:
|
|
43
|
-
|
|
44
|
-
* 12:: (set the hour number to 12)
|
|
45
|
-
* +2x12:: (move forward to the second hour 12)
|
|
46
|
-
* +4x--31 (move forward to the fourth occurrence of day 31 of a month)
|
|
47
|
-
* -3x-02-29 (move backward to the third occurrence of February 29)
|
|
48
|
-
* wed (set to the Wednesday of the same week, week starts on Sunday)
|
|
49
|
-
* -3wed (move to the third Wednesday before the current datetime)
|
|
50
|
-
|
|
51
|
-
They are represented by either a Weekday object or a PartialDate
|
|
52
|
-
object with a count. A count of 0 means setting instead of shifting.
|
|
53
|
-
Only integer counts are acceptable.
|
|
54
|
-
|
|
55
|
-
It is an error to set to an invalid date (e.g., --31 applied on
|
|
56
|
-
2019-06-25 is an error). The datetime parts which are specified must
|
|
57
|
-
be consecutive (it is an error to specify 12::05). It is also an
|
|
58
|
-
error to shift for occurrence of partial date with year specified
|
|
59
|
-
(e.g., "+2x2019--").
|
|
60
|
-
|
|
61
|
-
On the other hand, shifting to an invalid date with day number
|
|
62
|
-
specified will shift more until a specified date is valid. For
|
|
63
|
-
example, if you add -2-29 with count 1 to 2019-01-01, you end up with
|
|
64
|
-
2020-02-29, because 2019-02-29 is not a valid date. If the count is 2
|
|
65
|
-
you get 2024-02-29 instead.
|
|
66
|
-
|
|
67
|
-
Shifting to invalid date by month will cause the date to moved
|
|
68
|
-
backwards until the date is valid. E.g., if you shift by -6- with
|
|
69
|
-
count 1 (next June) from 2019-05-31, you get 2019-06-30. With count 2
|
|
70
|
-
you get 2020-06-30.
|
|
71
|
-
|
|
72
|
-
This library is grown out of frustration that it is tedious to have a
|
|
73
|
-
shell script or program to get a datetime like "the next 6pm from now"
|
|
74
|
-
or "the next 3rd of any month from two days ago". With this module
|
|
75
|
-
they can be specified like "+1x18:00:00.0" and "-2day +1x--3"
|
|
76
|
-
respectively. In the expected use cases, counts are small numbers.
|
|
77
|
-
So the library is not always efficient (at times we just loop "count"
|
|
78
|
-
times to step forward or backward). Whenever it is simple to do so,
|
|
79
|
-
the implementation just forward to relativedelta, in which case they
|
|
80
|
-
are more efficient.
|
|
81
|
-
|
|
82
|
-
At present the program does not handle timezone and daylight saving.
|
|
83
|
-
This is bacause the author lives at a place where no daylight saving
|
|
84
|
-
is observed. Contributions are welcome.
|
|
85
|
-
|
|
86
|
-
Platform: UNKNOWN
|
|
87
|
-
Classifier: Development Status :: 4 - Beta
|
|
88
|
-
Classifier: Programming Language :: Python :: 2.7
|
|
89
|
-
Classifier: Programming Language :: Python :: 3
|
|
90
|
-
Classifier: Topic :: Software Development :: Libraries
|
|
91
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
92
|
-
Classifier: Operating System :: OS Independent
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
python-dateutil
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
datec
|
datec-0.1/setup.cfg
DELETED
datec-0.1/setup.py
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import setuptools
|
|
2
|
-
|
|
3
|
-
with open('README.md', 'r') as fh:
|
|
4
|
-
long_description = fh.read()
|
|
5
|
-
|
|
6
|
-
setuptools.setup(
|
|
7
|
-
name='datec',
|
|
8
|
-
version='0.1',
|
|
9
|
-
author='Isaac To',
|
|
10
|
-
author_email='isaac.to@gmail.com',
|
|
11
|
-
description='Date Command',
|
|
12
|
-
long_description=long_description,
|
|
13
|
-
long_description_content_type='text/markdown',
|
|
14
|
-
url='https://github.com/isaacto/datec',
|
|
15
|
-
packages=setuptools.find_packages(),
|
|
16
|
-
install_requires=[
|
|
17
|
-
'python-dateutil'
|
|
18
|
-
],
|
|
19
|
-
entry_points={
|
|
20
|
-
"console_scripts": [
|
|
21
|
-
"datec=datec.__main__:main",
|
|
22
|
-
]
|
|
23
|
-
},
|
|
24
|
-
classifiers=[
|
|
25
|
-
'Development Status :: 4 - Beta',
|
|
26
|
-
'Programming Language :: Python :: 2.7',
|
|
27
|
-
'Programming Language :: Python :: 3',
|
|
28
|
-
'Topic :: Software Development :: Libraries',
|
|
29
|
-
'License :: OSI Approved :: MIT License',
|
|
30
|
-
'Operating System :: OS Independent',
|
|
31
|
-
],
|
|
32
|
-
)
|