ics-query 0.1.0a0__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.0a0'
16
- __version_tuple__ = version_tuple = (0, 1, 0)
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.0a0
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/cf4aefe6fb80c98ee554f4030ce9b2e7f16db162.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
@@ -716,7 +716,79 @@ You can install this package from the [PyPI](https://pypi.org/project/ics-query/
716
716
  pip install ics-query
717
717
  ```
718
718
 
719
- ## `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
720
792
 
721
793
  You can get all **events** that happen at a certain **day**.
722
794
 
@@ -736,32 +808,32 @@ You can get all **TODO**s that happen at in certain **month**.
736
808
  ics-query --components VTODO at 2029-12-24 calendar.ics
737
809
  ```
738
810
 
739
- ## `ics-query at` - time ranges
811
+ ### `ics-query at` - time ranges
740
812
 
741
813
 
742
- ## `ics-query --output=count` - count occurrences
814
+ ### `ics-query --output=count` - count occurrences
743
815
 
744
816
 
745
- ## `ics-query --output=ics` - use ics as output (default)
817
+ ### `ics-query --output=ics` - use ics as output (default)
746
818
 
747
819
 
748
- ## `ics-query --select-index` - reduce output size
820
+ ### `ics-query --select-index` - reduce output size
749
821
 
750
822
  Examples: `0,2,4` `0-10`
751
823
 
752
- ## `ics-query all` - the whole calendar
824
+ ### `ics-query all` - the whole calendar
753
825
 
754
- ## `ics-query between` - time ranges
826
+ ### `ics-query between` - time ranges
755
827
 
756
828
  ```shell
757
829
  ics-query between dt dt
758
830
  ics-query between dt duration
759
831
  ```
760
832
 
761
- ## `ics-query --select-component` - filter for components
833
+ ### `ics-query --select-component` - filter for components
762
834
 
763
835
 
764
- ## `ics-query --select-uid` - filter by uid
836
+ ### `ics-query --select-uid` - filter by uid
765
837
 
766
838
 
767
839
  ## How to edit an event
@@ -859,12 +931,19 @@ To release new versions,
859
931
 
860
932
  ```shell
861
933
  git tag v0.1.0a
934
+ git push origin v0.1.0a
862
935
  ```
863
936
 
864
937
  5. Notify the issues about their release
865
938
 
866
939
  ## Changelog
867
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
+
868
947
  - v0.1.0a
869
948
 
870
949
  - Update Python version compatibility
@@ -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=Okky3KGcs_fu34VQzO05VuCc7CF609YKQueRW6fuvPU,413
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.0a0.dist-info/METADATA,sha256=BoeIkgoAQE_Z-J-8Yf0mxLafokFb6vzdiaAVD8mBrAE,46147
13
- ics_query-0.1.0a0.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
14
- ics_query-0.1.0a0.dist-info/entry_points.txt,sha256=Jq_39vCKVOkNZjL7Wngf_04V_n_QRszLgLT2CbJKiH4,49
15
- ics_query-0.1.0a0.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
16
- ics_query-0.1.0a0.dist-info/RECORD,,