ics-query 0.1.dev8__py3-none-any.whl → 0.1.1a0__py3-none-any.whl

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.
ics_query/_version.py CHANGED
@@ -12,5 +12,5 @@ __version__: str
12
12
  __version_tuple__: VERSION_TUPLE
13
13
  version_tuple: VERSION_TUPLE
14
14
 
15
- __version__ = version = '0.1.dev8'
16
- __version_tuple__ = version_tuple = (0, 1, 'dev8')
15
+ __version__ = version = '0.1.1a0'
16
+ __version_tuple__ = version_tuple = (0, 1, 1)
ics_query/cli.py CHANGED
@@ -9,10 +9,11 @@ import typing as t
9
9
 
10
10
  import click
11
11
  import recurring_ical_events
12
- from icalendar import Calendar
12
+ from icalendar.cal import Calendar, Component
13
13
  from recurring_ical_events import CalendarQuery
14
14
 
15
15
  from . import parse
16
+ from .version import __version__
16
17
 
17
18
  if t.TYPE_CHECKING:
18
19
  from io import FileIO
@@ -50,6 +51,17 @@ class ComponentsResultArgument(click.File):
50
51
  return ComponentsResult(file)
51
52
 
52
53
 
54
+ class JoinedCalendars:
55
+ def __init__(self, calendars: list[Calendar]):
56
+ """Join multiple calendars."""
57
+ self.queries = [recurring_ical_events.of(calendar) for calendar in calendars]
58
+
59
+ def at(self, dt: tuple[int]) -> t.Generator[Component]:
60
+ """Return the components."""
61
+ for query in self.queries:
62
+ yield from query.at(dt)
63
+
64
+
53
65
  class CalendarQueryInputArgument(click.File):
54
66
  """Argument for the result."""
55
67
 
@@ -61,8 +73,8 @@ class CalendarQueryInputArgument(click.File):
61
73
  ) -> recurring_ical_events.CalendarQuery:
62
74
  """Return a CalendarQuery."""
63
75
  file = super().convert(value, param, ctx)
64
- calendar = Calendar.from_ical(file.read())
65
- return recurring_ical_events.of(calendar)
76
+ calendars = Calendar.from_ical(file.read(), multiple=True)
77
+ return JoinedCalendars(calendars)
66
78
 
67
79
 
68
80
  arg_calendar = click.argument("calendar", type=CalendarQueryInputArgument("rb"))
@@ -70,9 +82,48 @@ arg_output = click.argument("output", type=ComponentsResultArgument("wb"))
70
82
 
71
83
 
72
84
  @click.group()
85
+ @click.version_option(__version__)
73
86
  def main():
74
- """Simple program that greets NAME for a total of COUNT times."""
75
- # sys.stdout = sys.stderr # remove accidential print impact
87
+ """Find out what happens in ICS calendar files.
88
+
89
+ ics-query can query and filter RFC 5545 compatible .ics files.
90
+ Components are events, journal entries and TODOs.
91
+
92
+ \b
93
+ Common Parameters
94
+ -----------------
95
+
96
+ Common parameters are described below.
97
+
98
+ CALENDAR
99
+
100
+ The CALENDAR is a readable file with one or more ICS calendars in it.
101
+ If CALENDAR is "-", then the standard input is used.
102
+
103
+ OUTPUT
104
+
105
+ This is the OUTPUT file for the result.
106
+ It is usually a path to a file that can be written to.
107
+ If OUTPUT is "-", then the standard output is used.
108
+
109
+ \b
110
+ Notes on Calculation
111
+ --------------------
112
+
113
+ An event can be very long. If you request smaller time spans or a time as
114
+ exact as a second, the event will still occur within this time span if it
115
+ happens during that time.
116
+
117
+ Generally, an event occurs within a time span if this applies:
118
+
119
+ event.DTSTART <= span.DTEND and span.DTSTART < event.DTEND
120
+
121
+ The START is INCLUSIVE, then END is EXCLUSIVE.
122
+
123
+ \b
124
+ Notes on Timezones
125
+ ------------------
126
+ """ # noqa: D301
76
127
 
77
128
 
78
129
  pass_datetime = click.make_pass_decorator(parse.to_time)
@@ -83,9 +134,126 @@ pass_datetime = click.make_pass_decorator(parse.to_time)
83
134
  @arg_calendar
84
135
  @arg_output
85
136
  def at(calendar: CalendarQuery, output: ComponentsResult, date: DateArgument):
86
- """Get the components at a certain time."""
137
+ """Occurrences at a certain dates.
138
+
139
+ YEAR
140
+
141
+ All occurrences in this year.
142
+
143
+ \b
144
+ Formats:
145
+ \b
146
+ YYYY
147
+ \b
148
+ Examples:
149
+ \b
150
+ ics-query at 2024 # all occurrences in year 2024
151
+ ics-query at `date +%Y` # all occurrences in this year
152
+
153
+ MONTH
154
+
155
+ All occurrences in this month.
156
+
157
+ \b
158
+ Formats:
159
+
160
+ YYYY-MM
161
+ YYYY-M
162
+ YYYYMM
163
+ \b
164
+ Examples:
165
+ \b
166
+ ics-query at 2019-10 # October 2019
167
+ ics-query at 1990-01 # January 1990
168
+ ics-query at 1990-1 # January 1990
169
+ ics-query at 199001 # January 1990
170
+ ics-query at `date +%Y%m` # this month
171
+
172
+ DAY
173
+
174
+ All occurrences in one day.
175
+
176
+ \b
177
+ Formats:
178
+
179
+ YYYY-MM-DD
180
+ YYYY-M-D
181
+ YYYYMMDD
182
+ \b
183
+ Examples:
184
+ \b
185
+ ics-query at 1990-01-01 # 1st January 1990
186
+ ics-query at 1990-1-1 # 1st January 1990
187
+ ics-query at 19900101 # 1st January 1990
188
+ ics-query at `date +%Y%m%d` # today
189
+
190
+ HOUR
191
+
192
+ All occurrences within one hour.
193
+
194
+ \b
195
+ Formats:
196
+ \b
197
+ YYYY-MM-DD HH
198
+ YYYY-MM-DDTHH
199
+ YYYY-M-DTH
200
+ YYYYMMDDTHH
201
+ YYYYMMDDHH
202
+ \b
203
+ Examples:
204
+ \b
205
+ ics-query at 1990-01-01 00 # 1st January 1990, 12am - 1am
206
+ ics-query at 1990-01-01T00 # 1st January 1990, 12am - 1am
207
+ ics-query at 1990-1-1T17 # 1st January 1990, 17:00 - 18:00
208
+ ics-query at 19900101T23 # 1st January 1990, 23:00 - midnight
209
+ ics-query at 1990010123 # 1st January 1990, 23:00 - midnight
210
+ ics-query at `date +%Y%m%d%H` # this hour
211
+
212
+ MINUTE
213
+
214
+ All occurrences within one minute.
215
+
216
+ \b
217
+ Formats:
218
+ \b
219
+ YYYY-MM-DD HH:MM
220
+ YYYY-MM-DDTHH:MM
221
+ YYYY-M-DTH:M
222
+ YYYYMMDDTHHMM
223
+ YYYYMMDDHHMM
224
+ \b
225
+ Examples:
226
+ \b
227
+ ics-query at 1990-01-01 10:10 # 1st January 1990, 10:10am - 10:11am
228
+ ics-query at 1990-01-01T10:10 # 1st January 1990, 10:10am - 10:11am
229
+ ics-query at 1990-1-1T7:2 # 1st January 1990, 07:02 - 07:03
230
+ ics-query at 19900101T2359 # 1st January 1990, 23:59 - midnight
231
+ ics-query at 199001012359 # 1st January 1990, 23:59 - midnight
232
+ ics-query at `date +%Y%m%d%H%M` # this minute
233
+
234
+ SECOND
235
+
236
+ All occurrences at a precise time.
237
+
238
+ \b
239
+ Formats:
240
+ \b
241
+ YYYY-MM-DD HH:MM:SS
242
+ YYYY-MM-DDTHH:MM:SS
243
+ YYYY-M-DTH:M:S
244
+ YYYYMMDDTHHMMSS
245
+ YYYYMMDDHHMMSS
246
+ \b
247
+ Examples:
248
+ \b
249
+ ics-query at 1990-01-01 10:10:00 # 1st January 1990, 10:10am
250
+ ics-query at 1990-01-01T10:10:00 # 1st January 1990, 10:10am
251
+ ics-query at 1990-1-1T7:2:30 # 1st January 1990, 07:02:30
252
+ ics-query at 19901231T235959 # 31st December 1990, 23:59:59
253
+ ics-query at 19900101235959 # 1st January 1990, 23:59:59
254
+ ics-query at `date +%Y%m%d%H%M%S` # now
255
+ """ # noqa: D301
87
256
  for event in calendar.at(date):
88
- print("debug")
89
257
  output.add_component(event)
90
258
 
91
259
 
ics_query/parse.py CHANGED
@@ -2,12 +2,49 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import re
6
+
5
7
  DateArgument = tuple[int]
6
8
 
7
9
 
10
+ class InvalidTimeFormat(ValueError):
11
+ """The value provided does not yield a precise time."""
12
+
13
+
14
+ REGEX_TIME = re.compile(
15
+ r"^(?P<year>\d\d\d\d)"
16
+ r"(?P<month>-\d?\d|\d\d)?"
17
+ r"(?P<day>-\d?\d|\d\d)?"
18
+ r"(?P<hour>[ T]\d?\d|\d\d)?"
19
+ r"(?P<minute>:\d?\d|\d\d)?"
20
+ r"(?P<second>:\d?\d|\d\d)?"
21
+ r"$"
22
+ )
23
+
24
+
8
25
  def to_time(dt: str) -> DateArgument:
9
26
  """Parse the time and date."""
10
- return tuple(map(int, dt.split("-")))
27
+ parsed_dt = REGEX_TIME.match(dt)
28
+ if parsed_dt is None:
29
+ raise InvalidTimeFormat(dt)
30
+
31
+ def group(group_name: str) -> tuple[int]:
32
+ """Return a group's value."""
33
+ result = parsed_dt.group(group_name)
34
+ while result and result[0] not in "0123456789":
35
+ result = result[1:]
36
+ if result is None:
37
+ return ()
38
+ return (int(result),)
39
+
40
+ return (
41
+ group("year")
42
+ + group("month")
43
+ + group("day")
44
+ + group("hour")
45
+ + group("minute")
46
+ + group("second")
47
+ )
11
48
 
12
49
 
13
50
  __all__ = ["to_time", "DateArgument"]
@@ -5,12 +5,13 @@ from __future__ import annotations
5
5
  import subprocess
6
6
  from copy import deepcopy
7
7
  from pathlib import Path
8
- from typing import NamedTuple
8
+ from typing import Callable, NamedTuple
9
9
 
10
10
  import pytest
11
11
 
12
12
  HERE = Path(__file__).parent
13
13
  IO_DIRECTORY = HERE / "runs"
14
+ CALENDARS_DIRECTORY = IO_DIRECTORY / "calendars"
14
15
 
15
16
 
16
17
  class TestRun(NamedTuple):
@@ -34,12 +35,26 @@ class TestRun(NamedTuple):
34
35
  )
35
36
 
36
37
 
38
+ def run_ics_query(*command, cwd=CALENDARS_DIRECTORY) -> TestRun:
39
+ """Run ics-qeury with a command."""
40
+ cmd = ["ics-query", *command]
41
+ print(" ".join(cmd))
42
+ completed_process = subprocess.run( # noqa: S603, RUF100
43
+ cmd, # noqa: S603, RUF100
44
+ capture_output=True,
45
+ timeout=3,
46
+ check=False,
47
+ cwd=cwd,
48
+ )
49
+ return TestRun.from_completed_process(completed_process)
50
+
51
+
37
52
  class IOTestCase(NamedTuple):
38
53
  """An example test case."""
39
54
 
40
55
  name: str
41
56
  command: list[str]
42
- cwd: Path
57
+ location: Path
43
58
  expected_output: str
44
59
 
45
60
  @classmethod
@@ -50,16 +65,7 @@ class IOTestCase(NamedTuple):
50
65
 
51
66
  def run(self) -> TestRun:
52
67
  """Run this test case and return the result."""
53
- command = ["ics-query", *self.command]
54
- print(" ".join(command))
55
- completed_process = subprocess.run( # noqa: S603, RUF100
56
- command, # noqa: S603, RUF100
57
- capture_output=True,
58
- timeout=3,
59
- check=False,
60
- cwd=self.cwd / "calendars",
61
- )
62
- return TestRun.from_completed_process(completed_process)
68
+ return run_ics_query(*self.command)
63
69
 
64
70
 
65
71
  io_test_cases = [
@@ -75,4 +81,10 @@ def io_testcase(request) -> IOTestCase:
75
81
  return deepcopy(request.param)
76
82
 
77
83
 
84
+ @pytest.fixture
85
+ def run() -> Callable[..., TestRun]:
86
+ """Return a runner function."""
87
+ return run_ics_query
88
+
89
+
78
90
  __all__ = ["IOTestCase", "TestRun"]
@@ -0,0 +1,20 @@
1
+ BEGIN:VEVENT
2
+ SUMMARY:test4
3
+ DTSTART;TZID=Europe/Berlin:20190304T000000
4
+ DTEND;TZID=Europe/Berlin:20190304T010000
5
+ DTSTAMP:20190303T111937
6
+ UID:UYDQSG9TH4DE0WM3QFL2J
7
+ CLASS:PUBLIC
8
+ CREATED:20190303T111937
9
+ LAST-MODIFIED:20190303T111937
10
+ STATUS:CONFIRMED
11
+ END:VEVENT
12
+ BEGIN:VEVENT
13
+ SUMMARY:test1
14
+ DTSTART;TZID=Europe/Berlin:20190304T080000
15
+ DTEND;TZID=Europe/Berlin:20190304T083000
16
+ DTSTAMP:20190303T111937
17
+ UID:UYDQSG9TH4DE0WM3QFL2J
18
+ CREATED:20190303T111937
19
+ LAST-MODIFIED:20190303T111937
20
+ END:VEVENT
@@ -0,0 +1,18 @@
1
+ BEGIN:VEVENT
2
+ SUMMARY:test1
3
+ DTSTART;TZID=Europe/Berlin:20190304T080000
4
+ DTEND;TZID=Europe/Berlin:20190304T083000
5
+ DTSTAMP:20190303T111937
6
+ UID:UYDQSG9TH4DE0WM3QFL2J
7
+ CREATED:20190303T111937
8
+ LAST-MODIFIED:20190303T111937
9
+ END:VEVENT
10
+ BEGIN:VEVENT
11
+ SUMMARY:test1
12
+ DTSTART;TZID=Europe/Berlin:20190304T080000
13
+ DTEND;TZID=Europe/Berlin:20190304T083000
14
+ DTSTAMP:20190303T111937
15
+ UID:UYDQSG9TH4DE0WM3QFL2J
16
+ CREATED:20190303T111937
17
+ LAST-MODIFIED:20190303T111937
18
+ END:VEVENT
@@ -0,0 +1,11 @@
1
+ BEGIN:VEVENT
2
+ SUMMARY:test4
3
+ DTSTART;TZID=Europe/Berlin:20190307T000000
4
+ DTEND;TZID=Europe/Berlin:20190307T010000
5
+ DTSTAMP:20190303T111937
6
+ UID:UYDQSG9TH4DE0WM3QFL2J
7
+ CLASS:PUBLIC
8
+ CREATED:20190303T111937
9
+ LAST-MODIFIED:20190303T111937
10
+ STATUS:CONFIRMED
11
+ END:VEVENT
@@ -0,0 +1,71 @@
1
+ BEGIN:VCALENDAR
2
+ VERSION:2.0
3
+ PRODID:-//SabreDAV//SabreDAV//EN
4
+ CALSCALE:GREGORIAN
5
+ X-WR-CALNAME:test
6
+ X-APPLE-CALENDAR-COLOR:#e78074
7
+ BEGIN:VTIMEZONE
8
+ TZID:Europe/Berlin
9
+ X-LIC-LOCATION:Europe/Berlin
10
+ BEGIN:DAYLIGHT
11
+ TZOFFSETFROM:+0100
12
+ TZOFFSETTO:+0200
13
+ TZNAME:CEST
14
+ DTSTART:19700329T020000
15
+ RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
16
+ END:DAYLIGHT
17
+ BEGIN:STANDARD
18
+ TZOFFSETFROM:+0200
19
+ TZOFFSETTO:+0100
20
+ TZNAME:CET
21
+ DTSTART:19701025T030000
22
+ RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
23
+ END:STANDARD
24
+ END:VTIMEZONE
25
+ BEGIN:VEVENT
26
+ CREATED:20190303T111937
27
+ DTSTAMP:20190303T111937
28
+ LAST-MODIFIED:20190303T111937
29
+ UID:UYDQSG9TH4DE0WM3QFL2J
30
+ SUMMARY:test4
31
+ CLASS:PUBLIC
32
+ STATUS:CONFIRMED
33
+ RRULE:FREQ=DAILY;COUNT=3;INTERVAL=3
34
+ DTSTART;TZID=Europe/Berlin:20190304T000000
35
+ DTEND;TZID=Europe/Berlin:20190304T010000
36
+ END:VEVENT
37
+ END:VCALENDAR
38
+ BEGIN:VCALENDAR
39
+ VERSION:2.0
40
+ PRODID:-//SabreDAV//SabreDAV//EN
41
+ CALSCALE:GREGORIAN
42
+ X-WR-CALNAME:test
43
+ X-APPLE-CALENDAR-COLOR:#e78074
44
+ BEGIN:VTIMEZONE
45
+ TZID:Europe/Berlin
46
+ X-LIC-LOCATION:Europe/Berlin
47
+ BEGIN:DAYLIGHT
48
+ TZOFFSETFROM:+0100
49
+ TZOFFSETTO:+0200
50
+ TZNAME:CEST
51
+ DTSTART:19700329T020000
52
+ RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
53
+ END:DAYLIGHT
54
+ BEGIN:STANDARD
55
+ TZOFFSETFROM:+0200
56
+ TZOFFSETTO:+0100
57
+ TZNAME:CET
58
+ DTSTART:19701025T030000
59
+ RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
60
+ END:STANDARD
61
+ END:VTIMEZONE
62
+ BEGIN:VEVENT
63
+ CREATED:20190303T111937
64
+ DTSTAMP:20190303T111937
65
+ LAST-MODIFIED:20190303T111937
66
+ UID:UYDQSG9TH4DE0WM3QFL2J
67
+ SUMMARY:test1
68
+ DTSTART;TZID=Europe/Berlin:20190304T080000
69
+ DTEND;TZID=Europe/Berlin:20190304T083000
70
+ END:VEVENT
71
+ END:VCALENDAR
@@ -0,0 +1,68 @@
1
+ BEGIN:VCALENDAR
2
+ VERSION:2.0
3
+ PRODID:-//SabreDAV//SabreDAV//EN
4
+ CALSCALE:GREGORIAN
5
+ X-WR-CALNAME:test
6
+ X-APPLE-CALENDAR-COLOR:#e78074
7
+ BEGIN:VTIMEZONE
8
+ TZID:Europe/Berlin
9
+ X-LIC-LOCATION:Europe/Berlin
10
+ BEGIN:DAYLIGHT
11
+ TZOFFSETFROM:+0100
12
+ TZOFFSETTO:+0200
13
+ TZNAME:CEST
14
+ DTSTART:19700329T020000
15
+ RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
16
+ END:DAYLIGHT
17
+ BEGIN:STANDARD
18
+ TZOFFSETFROM:+0200
19
+ TZOFFSETTO:+0100
20
+ TZNAME:CET
21
+ DTSTART:19701025T030000
22
+ RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
23
+ END:STANDARD
24
+ END:VTIMEZONE
25
+ BEGIN:VEVENT
26
+ CREATED:20190303T111937
27
+ DTSTAMP:20190303T111937
28
+ LAST-MODIFIED:20190303T111937
29
+ UID:UYDQSG9TH4DE0WM3QFL2J
30
+ SUMMARY:test1
31
+ DTSTART;TZID=Europe/Berlin:20190304T080000
32
+ DTEND;TZID=Europe/Berlin:20190304T083000
33
+ END:VEVENT
34
+ END:VCALENDAR
35
+ BEGIN:VCALENDAR
36
+ VERSION:2.0
37
+ PRODID:-//SabreDAV//SabreDAV//EN
38
+ CALSCALE:GREGORIAN
39
+ X-WR-CALNAME:test
40
+ X-APPLE-CALENDAR-COLOR:#e78074
41
+ BEGIN:VTIMEZONE
42
+ TZID:Europe/Berlin
43
+ X-LIC-LOCATION:Europe/Berlin
44
+ BEGIN:DAYLIGHT
45
+ TZOFFSETFROM:+0100
46
+ TZOFFSETTO:+0200
47
+ TZNAME:CEST
48
+ DTSTART:19700329T020000
49
+ RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
50
+ END:DAYLIGHT
51
+ BEGIN:STANDARD
52
+ TZOFFSETFROM:+0200
53
+ TZOFFSETTO:+0100
54
+ TZNAME:CET
55
+ DTSTART:19701025T030000
56
+ RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
57
+ END:STANDARD
58
+ END:VTIMEZONE
59
+ BEGIN:VEVENT
60
+ CREATED:20190303T111937
61
+ DTSTAMP:20190303T111937
62
+ LAST-MODIFIED:20190303T111937
63
+ UID:UYDQSG9TH4DE0WM3QFL2J
64
+ SUMMARY:test1
65
+ DTSTART;TZID=Europe/Berlin:20190304T080000
66
+ DTEND;TZID=Europe/Berlin:20190304T083000
67
+ END:VEVENT
68
+ END:VCALENDAR
@@ -1,5 +1,7 @@
1
1
  """Test the commmand line."""
2
2
 
3
+ from ics_query.version import version
4
+
3
5
  from .conftest import IOTestCase
4
6
 
5
7
 
@@ -8,3 +10,10 @@ def test_check_program_output(io_testcase: IOTestCase):
8
10
  result = io_testcase.run()
9
11
  print(result.error)
10
12
  assert result.output == io_testcase.expected_output
13
+
14
+
15
+ def test_version(run):
16
+ """Check the version is displayed."""
17
+ result = run("--version")
18
+ assert result.exit_code == 0
19
+ assert version in result.output
@@ -0,0 +1,63 @@
1
+ """This tests parsing input times and dates."""
2
+
3
+ import pytest
4
+
5
+ from ics_query.parse import InvalidTimeFormat, to_time
6
+
7
+
8
+ @pytest.mark.parametrize(
9
+ ("string_argument", "expected_result"),
10
+ [
11
+ # year
12
+ ("2019", (2019,)),
13
+ ("1991", (1991,)),
14
+ # month
15
+ ("2000-11", (2000, 11)),
16
+ ("2001-01", (2001, 1)),
17
+ ("1990-1", (1990, 1)),
18
+ ("201011", (2010, 11)),
19
+ ("199003", (1990, 3)),
20
+ # day
21
+ ("1990-01-01", (1990, 1, 1)),
22
+ ("2001-3-4", (2001, 3, 4)),
23
+ ("19801231", (1980, 12, 31)),
24
+ # hour
25
+ ("1990-01-01 00", (1990, 1, 1, 0)),
26
+ ("1991-12-31T23", (1991, 12, 31, 23)),
27
+ ("2003-1-1T17", (2003, 1, 1, 17)),
28
+ ("20010409T12", (2001, 4, 9, 12)),
29
+ ("2014101018", (2014, 10, 10, 18)),
30
+ # minute
31
+ ("1990-01-01 00:10", (1990, 1, 1, 0, 10)),
32
+ ("1991-12-31T23:11", (1991, 12, 31, 23, 11)),
33
+ ("2003-1-1T17:0", (2003, 1, 1, 17, 0)),
34
+ ("2004-1-1T7:0", (2004, 1, 1, 7, 0)),
35
+ ("20010409T12:59", (2001, 4, 9, 12, 59)),
36
+ ("201410101830", (2014, 10, 10, 18, 30)),
37
+ # second
38
+ ("1990-01-01 00:10:12", (1990, 1, 1, 0, 10, 12)),
39
+ ("1991-12-31T23:11:0", (1991, 12, 31, 23, 11, 0)),
40
+ ("2003-1-1T17:0:11", (2003, 1, 1, 17, 0, 11)),
41
+ ("2004-1-1T7:0:10", (2004, 1, 1, 7, 0, 10)),
42
+ ("20010409T12:59:58", (2001, 4, 9, 12, 59, 58)),
43
+ ("20141010183012", (2014, 10, 10, 18, 30, 12)),
44
+ ],
45
+ )
46
+ def test_parse_to_date_argument(string_argument, expected_result):
47
+ """Check that we can properly parse what is accepted."""
48
+ result = to_time(string_argument)
49
+ assert result == expected_result
50
+
51
+
52
+ @pytest.mark.parametrize(
53
+ "dt",
54
+ [
55
+ "",
56
+ "132",
57
+ "12345",
58
+ ],
59
+ )
60
+ def test_invalid_time_format(dt: str):
61
+ """Check invalid time formats."""
62
+ with pytest.raises(InvalidTimeFormat):
63
+ to_time(dt)
@@ -1,10 +1,10 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: ics-query
3
- Version: 0.1.dev8
4
- Summary: Find out what happens in ICS calendar files - query and filter RFC 5545 compatible `.ics` files for events, journals, TODOs and more.
3
+ Version: 0.1.1a0
4
+ Summary: Find out what happens in ICS calendar files - query and filter RFC 5545 compatible .ics files for events, journals, TODOs and more.
5
5
  Project-URL: Homepage, https://github.com/niccokunzmann/ics-query/
6
6
  Project-URL: Repository, https://github.com/niccokunzmann/ics-query/
7
- Project-URL: source_archive, https://github.com/niccokunzmann/ics-query/archive/3f1c4acd60666416eb899f7a0c07a05999d443c3.zip
7
+ Project-URL: source_archive, https://github.com/niccokunzmann/ics-query/archive/fb12ef2822d23f693aa5f9faaa0cb97fa9f67516.zip
8
8
  Project-URL: Issues, https://github.com/niccokunzmann/ics-query/issues
9
9
  Project-URL: Documentation, https://github.com/niccokunzmann/ics-query/
10
10
  Project-URL: Changelog, https://github.com/niccokunzmann/ics-query/#changelog
@@ -690,7 +690,10 @@ Classifier: Development Status :: 3 - Alpha
690
690
  Classifier: Intended Audience :: Developers
691
691
  Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
692
692
  Classifier: Operating System :: OS Independent
693
- Classifier: Programming Language :: Python :: 3
693
+ Classifier: Programming Language :: Python :: 3.9
694
+ Classifier: Programming Language :: Python :: 3.10
695
+ Classifier: Programming Language :: Python :: 3.11
696
+ Classifier: Programming Language :: Python :: 3.12
694
697
  Classifier: Topic :: Office/Business :: Scheduling
695
698
  Requires-Python: >=3.8
696
699
  Requires-Dist: click
@@ -713,7 +716,79 @@ You can install this package from the [PyPI](https://pypi.org/project/ics-query/
713
716
  pip install ics-query
714
717
  ```
715
718
 
716
- ## `ics-query at` - occurrences at certain times
719
+ ## Usage
720
+
721
+ See how to use `ics-query`.
722
+
723
+ ### Examples
724
+
725
+ You can easily get a calendar from the web and see what is on.
726
+ In this example, we show which German National Holidays happen in August 2024:
727
+
728
+ ```shell
729
+ $ wget -qO- 'https://www.calendarlabs.com/ical-calendar/ics/46/Germany_Holidays.ics' | ./ics-query at 2024-08 - -
730
+ BEGIN:VEVENT
731
+ SUMMARY:Assumption Day (BY\, SL)
732
+ DTSTART;VALUE=DATE:20240815
733
+ DTEND;VALUE=DATE:20240815
734
+ DTSTAMP:20231013T092513Z
735
+ UID:65290cf9326601697189113@calendarlabs.com
736
+ SEQUENCE:0
737
+ DESCRIPTION:Visit https://calendarlabs.com/holidays/us/the-assumption-of-m
738
+ ary.php to know more about Assumption Day (BY\, SL). \n\n Like us on Faceb
739
+ ook: http://fb.com/calendarlabs to get updates
740
+ LOCATION:Germany
741
+ STATUS:CONFIRMED
742
+ TRANSP:TRANSPARENT
743
+ END:VEVENT
744
+ ```
745
+
746
+ In the following example, we query a calendar file and print the result.
747
+
748
+ ```shell
749
+ $ ics-query at 2019-03-04 one-event.ics -
750
+ BEGIN:VEVENT
751
+ SUMMARY:test1
752
+ DTSTART;TZID=Europe/Berlin:20190304T080000
753
+ DTEND;TZID=Europe/Berlin:20190304T083000
754
+ DTSTAMP:20190303T111937
755
+ UID:UYDQSG9TH4DE0WM3QFL2J
756
+ CREATED:20190303T111937
757
+ LAST-MODIFIED:20190303T111937
758
+ END:VEVENT
759
+ ```
760
+
761
+ We can concatenate calendars and pipe them into `ics-query`.
762
+ In the example below, we get all events that happen right now in two calendars.
763
+
764
+ ```shell
765
+ $ cat calendar1.ics calendar2.ics | ics-query at `date +%Y%m%d%H%M%S` - -
766
+ BEGIN:VEVENT
767
+ ...
768
+ ```
769
+
770
+ ### Events at Certain Times
771
+
772
+ You can query which events happen at certain times:
773
+
774
+ ```shell
775
+ ics-query at <date-time> calendar.ics -
776
+ ```
777
+
778
+ `<date-time>` can be built up: It can be a year, a month, a day, an hour, a minute or a second.
779
+
780
+ Please see the command documentation for more help:
781
+
782
+ ```shell
783
+ ics-query --help
784
+ ics-query at --help
785
+ ```
786
+
787
+ ## Vision
788
+
789
+ This section shows where we would like to get to.
790
+
791
+ ### `ics-query at` - occurrences at certain times
717
792
 
718
793
  You can get all **events** that happen at a certain **day**.
719
794
 
@@ -733,32 +808,32 @@ You can get all **TODO**s that happen at in certain **month**.
733
808
  ics-query --components VTODO at 2029-12-24 calendar.ics
734
809
  ```
735
810
 
736
- ## `ics-query at` - time ranges
811
+ ### `ics-query at` - time ranges
737
812
 
738
813
 
739
- ## `ics-query --output=count` - count occurrences
814
+ ### `ics-query --output=count` - count occurrences
740
815
 
741
816
 
742
- ## `ics-query --output=ics` - use ics as output (default)
817
+ ### `ics-query --output=ics` - use ics as output (default)
743
818
 
744
819
 
745
- ## `ics-query --select-index` - reduce output size
820
+ ### `ics-query --select-index` - reduce output size
746
821
 
747
822
  Examples: `0,2,4` `0-10`
748
823
 
749
- ## `ics-query all` - the whole calendar
824
+ ### `ics-query all` - the whole calendar
750
825
 
751
- ## `ics-query between` - time ranges
826
+ ### `ics-query between` - time ranges
752
827
 
753
828
  ```shell
754
829
  ics-query between dt dt
755
830
  ics-query between dt duration
756
831
  ```
757
832
 
758
- ## `ics-query --select-component` - filter for components
833
+ ### `ics-query --select-component` - filter for components
759
834
 
760
835
 
761
- ## `ics-query --select-uid` - filter by uid
836
+ ### `ics-query --select-uid` - filter by uid
762
837
 
763
838
 
764
839
  ## How to edit an event
@@ -787,8 +862,93 @@ Examples:
787
862
  - There are x events today
788
863
  - Please write a journal entry!
789
864
 
865
+ ## Version Fixing
866
+
867
+ If you use this library in your code, you may want to make sure that
868
+ updates can be received but they do not break your code.
869
+ The version numbers are handeled this way: `a.b.c` example: `0.1.12`
870
+
871
+ - `c` is changed for each minor bug fix.
872
+ - `b` is changed whenever new features are added.
873
+ - `a` is changed when the interface or major assumptions change that may break your code.
874
+
875
+ So, I recommend to version-fix this library to stay with the same `a`
876
+ while `b` and `c` can change.
877
+
878
+ ## Development
879
+
880
+ This section should set you up for development.
881
+
882
+ ### Testing
883
+
884
+ This project's development is driven by tests.
885
+ Tests assure a consistent interface and less knowledge lost over time.
886
+ If you like to change the code, tests help that nothing breaks in the future.
887
+ They are required in that sense.
888
+ Example code and ics files can be transferred into tests and speed up fixing bugs.
889
+
890
+ You can view the tests in the [test folder](https://github.com/niccokunzmann/ics-query/tree/main/ics_query/tests)
891
+ If you have a calendar ICS file for which this library does not
892
+ generate the desired output, you can add it to the ``test/calendars``
893
+ folder and write tests for what you expect.
894
+ If you like, [open an issue](https://github.com/niccokunzmann/ics-query/issues) first, e.g. to discuss the changes and
895
+ how to go about it.
896
+
897
+ To run the tests, we use `tox`.
898
+ `tox` tests all different Python versions which we want to be compatible to.
899
+
900
+ ```shell
901
+ pip3 install tox
902
+ ```
903
+
904
+ To run all the tests:
905
+
906
+ ```shell
907
+ tox
908
+ ```
909
+
910
+ To run the tests in a specific Python version:
911
+
912
+ ```shell
913
+ tox -e py39
914
+ ```
915
+
916
+ We use ``ruff`` to format the code.
917
+ Run this to format the code and show problems:
918
+
919
+ ```shell
920
+ tox -e ruff
921
+ ```
922
+
923
+ ## New Release
924
+
925
+ To release new versions,
926
+
927
+ 1. edit the Changelog Section
928
+ 2. create a commit and push it
929
+ 3. wait for [GitHub Actions](https://github.com/niccokunzmann/ics-query/actions) to finish the build
930
+ 4. create a tag and push it
931
+
932
+ ```shell
933
+ git tag v0.1.0a
934
+ git push origin v0.1.0a
935
+ ```
936
+
937
+ 5. Notify the issues about their release
938
+
790
939
  ## Changelog
791
940
 
941
+ - v0.1.1a
942
+
943
+ - Add `--version`
944
+ - Add `ics-query at <date>`
945
+ - Add support for multiple calendars in one input
946
+
947
+ - v0.1.0a
948
+
949
+ - Update Python version compatibility
950
+ - Add development documentation
951
+
792
952
  - v0.0.1a
793
953
 
794
954
  - first version
@@ -0,0 +1,22 @@
1
+ ics_query/__init__.py,sha256=YxyZzqxwfA77sBVFjUyUj3_fvR2gL3bs0pkE93prRFY,216
2
+ ics_query/__main__.py,sha256=E6Gls0DNz8GQK2K-kOUIx8cYhgANW_CH54VKrfCfs14,52
3
+ ics_query/_version.py,sha256=cU7fSuITRwo1pTWU-OD7oXC6-FpABHrqGppdiounZFo,413
4
+ ics_query/cli.py,sha256=-ogJQ994ssQNNvU8tojCPDea4wJwFd4f9REoJ1hDCOs,6949
5
+ ics_query/parse.py,sha256=VkF4z5KR-uzg4YmEKaY7Zz67d4Ffxc4rsB6ezNtxBr8,1113
6
+ ics_query/version.py,sha256=LgqmHh_dcmWi9HMxfWJ8OhfmbzWsBTq3h08jIE18pYM,468
7
+ ics_query/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
+ ics_query/tests/conftest.py,sha256=MGwKXod7sFfepXK0mtvKkR8SaIiI1SZNW_rAhz6aSA0,2258
9
+ ics_query/tests/test_command_line.py,sha256=-XnpYWETEVEc1dJ5_dpd8dqBoMQFz1CyGaSoe_ETq5o,488
10
+ ics_query/tests/test_parse_date.py,sha256=ZMbllWHNCwuluGTr55xKlM7xBzoMNsoE7TQUC5uGlVA,1923
11
+ ics_query/tests/runs/at 2019-03-04 multiple-calendars.ics -.run,sha256=Cyeg9DMK6BibiCwkM8nW0ZtULUYSZFIgDvKRlSo_jTw,482
12
+ ics_query/tests/runs/at 2019-03-04 one-event-twice.ics -.run,sha256=hc-0yJhWpnSVzPNB_0_tJfR6dskNMm9xdbHeAhsX1zI,452
13
+ ics_query/tests/runs/at 2019-03-04 one-event.ics -.run,sha256=GVVmVpqzFjQLw2_RuhxnVTpCnwflNqeWbkFZ6u5WXxA,226
14
+ ics_query/tests/runs/at 2019-03-07 multiple-calendars.ics -.run,sha256=ZlLXxPMIai7vvmQTL4NyXJwdCQzoU1scEn_FpjmkkUA,256
15
+ ics_query/tests/runs/calendars/multiple-calendars.ics,sha256=lS1Q9eSPpzLs2TRDKUiwF9HmpnJMw8xqjLudA0-KtY4,1516
16
+ ics_query/tests/runs/calendars/one-event-twice.ics,sha256=GZ5wE-U4kjO91N2nIaLsYZpSOLvQTjkoLPI6saMocOM,1450
17
+ ics_query/tests/runs/calendars/one-event.ics,sha256=-uwohttEzg-jsTETNb6tQ5dO9PE3DXzlicwRiXJs1KQ,725
18
+ ics_query-0.1.1a0.dist-info/METADATA,sha256=cgssIiEuCQItTb7_5Iap9KFcfV6f9-z4DUfy-gd6MsU,48066
19
+ ics_query-0.1.1a0.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
20
+ ics_query-0.1.1a0.dist-info/entry_points.txt,sha256=Jq_39vCKVOkNZjL7Wngf_04V_n_QRszLgLT2CbJKiH4,49
21
+ ics_query-0.1.1a0.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
22
+ ics_query-0.1.1a0.dist-info/RECORD,,
@@ -1,16 +0,0 @@
1
- ics_query/__init__.py,sha256=YxyZzqxwfA77sBVFjUyUj3_fvR2gL3bs0pkE93prRFY,216
2
- ics_query/__main__.py,sha256=E6Gls0DNz8GQK2K-kOUIx8cYhgANW_CH54VKrfCfs14,52
3
- ics_query/_version.py,sha256=lI2AbAnFrmlrT0e_rrLW0sxbHP3fZzYfncKr1OGEhdw,419
4
- ics_query/cli.py,sha256=P6eLIH7hgbfwhbJHloilHS9ut_YDjEP_QDabLTf0NQs,2367
5
- ics_query/parse.py,sha256=6bCKtY39jr2NFSsqspVyGn32pwn6bMoEqJMHE26Ap1o,261
6
- ics_query/version.py,sha256=LgqmHh_dcmWi9HMxfWJ8OhfmbzWsBTq3h08jIE18pYM,468
7
- ics_query/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
- ics_query/tests/conftest.py,sha256=A6R9pB3emaSo1DAIxyt-5QhWPq8LbsOoWJIRIsku2Zk,2003
9
- ics_query/tests/test_command_line.py,sha256=bIJp2SCVW8MXF_lNZ1ol9A-r9Y_9zjnY2jhdTJET59g,283
10
- ics_query/tests/runs/at 2019-03-04 one-event.ics -.run,sha256=GVVmVpqzFjQLw2_RuhxnVTpCnwflNqeWbkFZ6u5WXxA,226
11
- ics_query/tests/runs/calendars/one-event.ics,sha256=-uwohttEzg-jsTETNb6tQ5dO9PE3DXzlicwRiXJs1KQ,725
12
- ics_query-0.1.dev8.dist-info/METADATA,sha256=5jVr-o3Elo_hWgEjN-t3GFB5fAd65JSyw23Tp2JvBZc,43902
13
- ics_query-0.1.dev8.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
14
- ics_query-0.1.dev8.dist-info/entry_points.txt,sha256=Jq_39vCKVOkNZjL7Wngf_04V_n_QRszLgLT2CbJKiH4,49
15
- ics_query-0.1.dev8.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
16
- ics_query-0.1.dev8.dist-info/RECORD,,