datec 0.1__tar.gz → 0.2__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.2/.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.2/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.2/PKG-INFO ADDED
@@ -0,0 +1,110 @@
1
+ Metadata-Version: 2.4
2
+ Name: datec
3
+ Version: 0.2
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. In general the date
44
+ representation is NxYYYY-mm-ddTHH:MM:SS.ffffff, where unspecified
45
+ parts are omitted leaving the symbols intact, like "2x-2-29T3::." (see
46
+ the following for the meaning). If the fractional part is not
47
+ specified the "." may be omitted, if all time parts are not specified
48
+ the "T::." can be omitted, if all date parts are not specified the
49
+ "--T" can be omitted, and if Nx may be omitted in some cases for
50
+ setting a partial datetime or weekday. There are a couple other more
51
+ formats like +3week and -2wed for shifting by period and weekday.
52
+
53
+ Date commands are in two forms: period shifting commands and partial
54
+ datetime shifting commands. The first type is more familiar: they
55
+ look like
56
+
57
+ * +2week (shift the datetime forward by 2 week)
58
+ * -1month (shift the datetime backward by 1 month)
59
+
60
+ Period is one of year, month, week, day, hour, minute and second,
61
+ represented by an object of the Period class. Fractional numbers are
62
+ acceptable except for year and month. If shifting a period leads to
63
+ an invalid date (e.g., shift backward 1 month from 2019-07-31), it
64
+ moves backwards the closest valid date (here, 2019-06-30). In general
65
+ the parts finer than the shifted part is unaffected (e.g., shifting 1
66
+ month from 2019-07-31 02:00 gives you 2019-06-30 02:00).
67
+
68
+ Partial datetime shifting is less familiar. It looks like:
69
+
70
+ * 12:: (set the hour number to 12)
71
+ * +2x12:: (move forward to the second hour 12)
72
+ * +4x--31 (move forward to the fourth occurrence of day 31 of a month)
73
+ * -3x-02-29 (move backward to the third occurrence of February 29)
74
+ * wed (set to the Wednesday of the same week, week starts on Sunday)
75
+ * -3wed (move to the third Wednesday before the current datetime)
76
+
77
+ They are represented by either a Weekday object or a PartialDate
78
+ object with a count. A count of 0 means setting instead of shifting.
79
+ Only integer counts are acceptable.
80
+
81
+ It is an error to set to an invalid date (e.g., --31 applied on
82
+ 2019-06-25 is an error). The datetime parts which are specified must
83
+ be consecutive (it is an error to specify 12::05). It is also an
84
+ error to shift for occurrence of a partial date with year specified
85
+ (e.g., "+2x2019--").
86
+
87
+ On the other hand, shifting to an invalid date with day number
88
+ specified will shift more until a specified date is valid. For
89
+ example, if you add -2-29 with count 1 to 2019-01-01, you end up with
90
+ 2020-02-29, because 2019-02-29 is not a valid date. If the count is 2
91
+ you get 2024-02-29 instead.
92
+
93
+ Shifting to an invalid date by a partial date with just a month number
94
+ will cause the date to moved backwards until the date is valid. E.g.,
95
+ if you shift by -6- with count 1 (next June) from 2019-05-31, you get
96
+ 2019-06-30. With count 2 you get 2020-06-30.
97
+
98
+ This library is grown out of frustration that it is tedious to have a
99
+ shell script or program to get a datetime like "the next 6pm from now"
100
+ or "the next 3rd of any month from two days ago". With this module
101
+ they can be specified like "+1x18:00:00.0" and "-2day +1x--3"
102
+ respectively. In the expected use cases, counts are small numbers.
103
+ So the library is not always efficient (at times we just loop "count"
104
+ times to step forward or backward). Whenever it is simple to do so,
105
+ the implementation just forward to relativedelta, in which case they
106
+ are more efficient.
107
+
108
+ At present the program does not handle timezone and daylight saving.
109
+ This is bacause the author lives at a place where no daylight saving
110
+ is observed. Contributions are welcome.
datec-0.2/README.md ADDED
@@ -0,0 +1,86 @@
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. In general the date
20
+ representation is NxYYYY-mm-ddTHH:MM:SS.ffffff, where unspecified
21
+ parts are omitted leaving the symbols intact, like "2x-2-29T3::." (see
22
+ the following for the meaning). If the fractional part is not
23
+ specified the "." may be omitted, if all time parts are not specified
24
+ the "T::." can be omitted, if all date parts are not specified the
25
+ "--T" can be omitted, and if Nx may be omitted in some cases for
26
+ setting a partial datetime or weekday. There are a couple other more
27
+ formats like +3week and -2wed for shifting by period and weekday.
28
+
29
+ Date commands are in two forms: period shifting commands and partial
30
+ datetime shifting commands. The first type is more familiar: they
31
+ look like
32
+
33
+ * +2week (shift the datetime forward by 2 week)
34
+ * -1month (shift the datetime backward by 1 month)
35
+
36
+ Period is one of year, month, week, day, hour, minute and second,
37
+ represented by an object of the Period class. Fractional numbers are
38
+ acceptable except for year and month. If shifting a period leads to
39
+ an invalid date (e.g., shift backward 1 month from 2019-07-31), it
40
+ moves backwards the closest valid date (here, 2019-06-30). In general
41
+ the parts finer than the shifted part is unaffected (e.g., shifting 1
42
+ month from 2019-07-31 02:00 gives you 2019-06-30 02:00).
43
+
44
+ Partial datetime shifting is less familiar. It looks like:
45
+
46
+ * 12:: (set the hour number to 12)
47
+ * +2x12:: (move forward to the second hour 12)
48
+ * +4x--31 (move forward to the fourth occurrence of day 31 of a month)
49
+ * -3x-02-29 (move backward to the third occurrence of February 29)
50
+ * wed (set to the Wednesday of the same week, week starts on Sunday)
51
+ * -3wed (move to the third Wednesday before the current datetime)
52
+
53
+ They are represented by either a Weekday object or a PartialDate
54
+ object with a count. A count of 0 means setting instead of shifting.
55
+ Only integer counts are acceptable.
56
+
57
+ It is an error to set to an invalid date (e.g., --31 applied on
58
+ 2019-06-25 is an error). The datetime parts which are specified must
59
+ be consecutive (it is an error to specify 12::05). It is also an
60
+ error to shift for occurrence of a partial date with year specified
61
+ (e.g., "+2x2019--").
62
+
63
+ On the other hand, shifting to an invalid date with day number
64
+ specified will shift more until a specified date is valid. For
65
+ example, if you add -2-29 with count 1 to 2019-01-01, you end up with
66
+ 2020-02-29, because 2019-02-29 is not a valid date. If the count is 2
67
+ you get 2024-02-29 instead.
68
+
69
+ Shifting to an invalid date by a partial date with just a month number
70
+ will cause the date to moved backwards until the date is valid. E.g.,
71
+ if you shift by -6- with count 1 (next June) from 2019-05-31, you get
72
+ 2019-06-30. With count 2 you get 2020-06-30.
73
+
74
+ This library is grown out of frustration that it is tedious to have a
75
+ shell script or program to get a datetime like "the next 6pm from now"
76
+ or "the next 3rd of any month from two days ago". With this module
77
+ they can be specified like "+1x18:00:00.0" and "-2day +1x--3"
78
+ respectively. In the expected use cases, counts are small numbers.
79
+ So the library is not always efficient (at times we just loop "count"
80
+ times to step forward or backward). Whenever it is simple to do so,
81
+ the implementation just forward to relativedelta, in which case they
82
+ are more efficient.
83
+
84
+ At present the program does not handle timezone and daylight saving.
85
+ This is bacause the author lives at a place where no daylight saving
86
+ 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 datetime.datetime.now() + Period(2, 'week').
4
+ to them, like this:
5
5
 
6
- A date command can be parsed from string using the parse() function,
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 specified
13
- the "." may be omitted, if all time parts are not specified the "T::."
14
- can be omitted, if all date parts are not specified the "--T" can be
15
- omitted, and if N is 0 the x may be omitted. By there are a couple
16
- other more formats like +3week and -2wed for shifting by period and
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,12 +35,12 @@ 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.
@@ -47,7 +49,7 @@ Only integer counts are acceptable.
47
49
  It is an error to set to an invalid date (e.g., --31 applied on
48
50
  2019-06-25 is an error). The datetime parts which are specified must
49
51
  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
52
+ error to shift for occurrence of a partial date with year specified
51
53
  (e.g., "+2x2019--").
52
54
 
53
55
  On the other hand, shifting to an invalid date with day number
@@ -56,10 +58,10 @@ example, if you add -2-29 with count 1 to 2019-01-01, you end up with
56
58
  2020-02-29, because 2019-02-29 is not a valid date. If the count is 2
57
59
  you get 2024-02-29 instead.
58
60
 
59
- Shifting to invalid date by month will cause the date to moved
60
- backwards until the date is valid. E.g., if you shift by -6- with
61
- count 1 (next June) from 2019-05-31, you get 2019-06-30. With count 2
62
- you get 2020-06-30.
61
+ Shifting to an invalid date by a partial date with just a month number
62
+ will cause the date to moved backwards until the date is valid. E.g.,
63
+ if you shift by -6- with count 1 (next June) from 2019-05-31, you get
64
+ 2019-06-30. With count 2 you get 2020-06-30.
63
65
 
64
66
  This library is grown out of frustration that it is tedious to have a
65
67
  shell script or program to get a datetime like "the next 6pm from now"
@@ -77,16 +79,19 @@ is observed. Contributions are welcome.
77
79
 
78
80
  """
79
81
 
82
+ import datetime
80
83
  import re
84
+ import typing
81
85
 
82
86
  import dateutil.relativedelta as dr
83
87
 
84
88
 
85
89
  __metaclass__ = type
86
90
 
91
+ __version__ = '0.2'
87
92
 
88
93
  class ParseError(ValueError):
89
- pass
94
+ """Represent an error in parsing."""
90
95
 
91
96
 
92
97
  class Period:
@@ -105,14 +110,15 @@ class Period:
105
110
  period (str): The period
106
111
 
107
112
  """
108
- def __init__(self, count, period):
113
+ def __init__(self, count: float, period: str):
109
114
  assert period in ('year', 'month', 'week', 'day',
110
115
  'hour', 'minute', 'second')
111
116
  self._count = count
112
117
  self._period = period
113
118
 
114
- def __radd__(self, dt):
115
- return dt + dr.relativedelta(**{self._period + 's': self._count})
119
+ def __radd__(self, dt: datetime.datetime) -> datetime.datetime:
120
+ return dt + dr.relativedelta(
121
+ **{self._period + 's': self._count}) # type: ignore
116
122
 
117
123
  PARSE_RE = re.compile(r'''
118
124
  ^
@@ -122,7 +128,7 @@ class Period:
122
128
  ''', re.X)
123
129
 
124
130
  @classmethod
125
- def parse(cls, cmdstr):
131
+ def parse(cls, cmdstr: str) -> 'Period':
126
132
  """Parse a command string to a Period object
127
133
 
128
134
  The command string should be of the form "<N><period>", where
@@ -138,6 +144,7 @@ class Period:
138
144
  if not match:
139
145
  raise ParseError('Cannot parse string %s' % cmdstr)
140
146
  gdt = match.groupdict()
147
+ cnt: float
141
148
  try:
142
149
  cnt = int(gdt['count'])
143
150
  except Exception:
@@ -169,13 +176,13 @@ class Weekday:
169
176
  day (int): The weekday
170
177
 
171
178
  """
172
- def __init__(self, count, day):
179
+ def __init__(self, count: int, day: int):
173
180
  self._count = count
174
181
  assert day in range(7)
175
182
  self._day = day
176
183
  self._drcls = _WEEKDAY_CLS[day]
177
184
 
178
- def __radd__(self, dt):
185
+ def __radd__(self, dt: datetime.datetime) -> datetime.datetime:
179
186
  if self._count > 0:
180
187
  return dt + dr.relativedelta(
181
188
  days=1, weekday=self._drcls(self._count))
@@ -195,7 +202,7 @@ class Weekday:
195
202
  ''', re.X)
196
203
 
197
204
  @classmethod
198
- def parse(cls, cmdstr):
205
+ def parse(cls, cmdstr: str) -> 'Weekday':
199
206
  """Parse a command string to a Weekday object
200
207
 
201
208
  The command string should be of the form "<N><weekday>", where
@@ -248,8 +255,13 @@ class PartialDate:
248
255
 
249
256
  _INVALID_SIG_RE = re.compile('10+1')
250
257
 
251
- def __init__(self, count=0, year=None, month=None, day=None,
252
- hour=None, minute=None, second=None, microsecond=None):
258
+ def __init__(
259
+ self, count: int = 0, year: typing.Optional[int] = None,
260
+ month: typing.Optional[int] = None, day: typing.Optional[int] = None,
261
+ hour: typing.Optional[int] = None, minute: typing.Optional[int] = None,
262
+ second: typing.Optional[int] = None,
263
+ microsecond: typing.Optional[int] = None
264
+ ):
253
265
  assert not count or not year, 'Absolute date with non-zero count'
254
266
  assert not isinstance(second, float) or \
255
267
  microsecond is None, 'Doubly specified microsecond'
@@ -274,7 +286,7 @@ class PartialDate:
274
286
  '', 'years', 'months', 'days', 'hours', 'minutes', 'seconds'
275
287
  ]
276
288
 
277
- def __radd__(self, dt):
289
+ def __radd__(self, dt: datetime.datetime) -> datetime.datetime:
278
290
  if self._firstset == -1:
279
291
  return dt
280
292
  if not(self._count):
@@ -288,7 +300,7 @@ class PartialDate:
288
300
  return self._monthshift(dt)
289
301
  return self._dayshift(dt)
290
302
 
291
- def _rset(self, dt):
303
+ def _rset(self, dt: datetime.datetime) -> datetime.datetime:
292
304
  updater = {'year': self._year,
293
305
  'month': self._month,
294
306
  'day': self._day,
@@ -297,9 +309,9 @@ class PartialDate:
297
309
  'second': self._second,
298
310
  'microsecond': self._microsecond}
299
311
  updater = {k: v for k, v in updater.items() if v is not None}
300
- return dt.replace(**updater)
312
+ return dt.replace(**updater) # type: ignore
301
313
 
302
- def _simpleshift(self, dt):
314
+ def _simpleshift(self, dt: datetime.datetime) -> datetime.datetime:
303
315
  remain = self._count
304
316
  ret = self._rset(dt)
305
317
  if self._count < 0:
@@ -309,9 +321,9 @@ class PartialDate:
309
321
  if ret > dt:
310
322
  remain -= 1
311
323
  mod_field = self._FIRSTSET_MOD[self._firstset]
312
- return ret + dr.relativedelta(**{mod_field: remain})
324
+ return ret + dr.relativedelta(**{mod_field: remain}) # type: ignore
313
325
 
314
- def _dayshift(self, dt):
326
+ def _dayshift(self, dt: datetime.datetime) -> datetime.datetime:
315
327
  # Day specified
316
328
  if self._firstset == 2: # modify month
317
329
  shift = dr.relativedelta(months=1 if self._count > 0 else -1)
@@ -344,8 +356,9 @@ class PartialDate:
344
356
  continue
345
357
  count -= 1
346
358
 
347
- def _monthshift(self, dt):
359
+ def _monthshift(self, dt: datetime.datetime) -> datetime.datetime:
348
360
  # Only month specified, shift by month rather than by year
361
+ assert self._month is not None
349
362
  if self._count > 0:
350
363
  num_months = self._month - dt.month
351
364
  sign = 1
@@ -389,7 +402,7 @@ class PartialDate:
389
402
  ''', re.X)
390
403
 
391
404
  @classmethod
392
- def parse(cls, cmdstr):
405
+ def parse(cls, cmdstr: str) -> 'PartialDate':
393
406
  """Parse a command string to a PartialDate object
394
407
 
395
408
  The command string should be of the form
@@ -413,7 +426,7 @@ class PartialDate:
413
426
  raise ParseError('Cannot parse string %s' % cmdstr)
414
427
  gdt = match.groupdict()
415
428
 
416
- def _matchval(key):
429
+ def _matchval(key: str) -> typing.Optional[int]:
417
430
  val = gdt.get(key)
418
431
  if not val:
419
432
  return None
@@ -433,7 +446,7 @@ class PartialDate:
433
446
  microsecond)
434
447
 
435
448
 
436
- def parse(cmdstr):
449
+ def parse(cmdstr: str) -> typing.Union[Period, Weekday, PartialDate]:
437
450
  """Attempt to parse one of the possible date command
438
451
 
439
452
  Args:
@@ -6,7 +6,7 @@ import datetime
6
6
  import datec
7
7
 
8
8
 
9
- def main():
9
+ def main() -> None:
10
10
  dt = datetime.datetime.now()
11
11
  for cmd in sys.argv[1:]:
12
12
  if cmd == '-h':
@@ -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,9 @@
1
+ mypy
2
+ pylint
3
+ pytest
4
+ pytest-cov
5
+ python-dateutil
6
+ twine
7
+ types-python-dateutil
8
+ wheel
9
+ makefilemenu
@@ -0,0 +1,82 @@
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
+
17
+ def test_weekday(self):
18
+ dt = datetime.datetime(2019, 5, 15)
19
+ self.assertEqual(dt + datec.Weekday(1, datec.MON),
20
+ datetime.datetime(2019, 5, 20))
21
+ self.assertEqual(dt + datec.Weekday.parse('+2mon'),
22
+ datetime.datetime(2019, 5, 27))
23
+ self.assertEqual(dt + datec.Weekday.parse('-2WED'),
24
+ datetime.datetime(2019, 5, 1))
25
+ self.assertEqual(dt + datec.parse('MON'),
26
+ datetime.datetime(2019, 5, 13))
27
+ self.assertEqual(dt + datec.Weekday(0, datec.FRI),
28
+ datetime.datetime(2019, 5, 17))
29
+ with self.assertRaises(datec.ParseError):
30
+ datec.Weekday.parse('MO')
31
+
32
+ def test_partialdate_simple(self):
33
+ dt = datetime.datetime(2019, 5, 15, 8, 15)
34
+ self.assertEqual(dt + datec.PartialDate(1),
35
+ datetime.datetime(2019, 5, 15, 8, 15))
36
+ self.assertEqual(dt + datec.PartialDate(0, minute=7, second=16),
37
+ datetime.datetime(2019, 5, 15, 8, 7, 16))
38
+ self.assertEqual(dt + datec.PartialDate(1, minute=7, second=16),
39
+ datetime.datetime(2019, 5, 15, 9, 7, 16))
40
+ self.assertEqual(dt + datec.PartialDate(1, minute=17, second=16),
41
+ datetime.datetime(2019, 5, 15, 8, 17, 16))
42
+ self.assertEqual(dt + datec.PartialDate(2, minute=7, second=16),
43
+ datetime.datetime(2019, 5, 15, 10, 7, 16))
44
+ self.assertEqual(dt + datec.PartialDate.parse(':7:16'),
45
+ datetime.datetime(2019, 5, 15, 8, 7, 16))
46
+ self.assertEqual(dt + datec.PartialDate.parse('-1x:7:16'),
47
+ datetime.datetime(2019, 5, 15, 8, 7, 16))
48
+ self.assertEqual(dt + datec.PartialDate.parse('-1x:17:16'),
49
+ datetime.datetime(2019, 5, 15, 7, 17, 16))
50
+ self.assertEqual(dt + datec.parse('-1x:17:16.2'),
51
+ datetime.datetime(2019, 5, 15, 7, 17, 16, 200000))
52
+ with self.assertRaises(datec.ParseError):
53
+ datec.PartialDate.parse('---')
54
+
55
+ def test_partialdate_monthshift(self):
56
+ dt = datetime.datetime(2019, 5, 15)
57
+ self.assertEqual(dt + datec.PartialDate(1, month=2),
58
+ datetime.datetime(2020, 2, 15))
59
+ self.assertEqual(dt + datec.PartialDate(-1, month=2),
60
+ datetime.datetime(2019, 2, 15))
61
+ dt = datetime.datetime(2019, 5, 15)
62
+ self.assertEqual(dt + datec.PartialDate.parse('+1x-2-'),
63
+ datetime.datetime(2020, 2, 15))
64
+ dt = datetime.datetime(2019, 5, 30)
65
+ self.assertEqual(dt + datec.PartialDate(1, month=2),
66
+ datetime.datetime(2020, 2, 29))
67
+
68
+ def test_partialdate_dayshift(self):
69
+ dt = datetime.datetime(2019, 5, 30)
70
+ self.assertEqual(dt + datec.PartialDate(1, month=5, day=30),
71
+ datetime.datetime(2020, 5, 30))
72
+ self.assertEqual(dt + datec.PartialDate(1, day=31),
73
+ datetime.datetime(2019, 5, 31))
74
+ self.assertEqual(dt + datec.PartialDate(2, day=31),
75
+ datetime.datetime(2019, 7, 31))
76
+ self.assertEqual(dt + datec.PartialDate(-1, day=31),
77
+ datetime.datetime(2019, 3, 31))
78
+ dt = datetime.datetime(2019, 2, 15)
79
+ self.assertEqual(dt + datec.PartialDate(1, day=30),
80
+ datetime.datetime(2019, 3, 30))
81
+ with self.assertRaises(ValueError):
82
+ dt + datec.PartialDate(1, month=2, day=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,9 +0,0 @@
1
- setup.py
2
- datec/__init__.py
3
- datec/__main__.py
4
- datec.egg-info/PKG-INFO
5
- datec.egg-info/SOURCES.txt
6
- datec.egg-info/dependency_links.txt
7
- datec.egg-info/entry_points.txt
8
- datec.egg-info/requires.txt
9
- datec.egg-info/top_level.txt
@@ -1 +0,0 @@
1
-
@@ -1,3 +0,0 @@
1
- [console_scripts]
2
- datec = datec.__main__:main
3
-
@@ -1 +0,0 @@
1
- python-dateutil
@@ -1 +0,0 @@
1
- datec
datec-0.1/setup.cfg DELETED
@@ -1,4 +0,0 @@
1
- [egg_info]
2
- tag_build =
3
- tag_date = 0
4
-
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
- )