ics-query 0.4.32__tar.gz → 0.4.34__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.
Files changed (58) hide show
  1. {ics_query-0.4.32 → ics_query-0.4.34}/PKG-INFO +39 -3
  2. {ics_query-0.4.32 → ics_query-0.4.34}/README.md +36 -0
  3. {ics_query-0.4.32 → ics_query-0.4.34}/ics_query/_version.py +2 -2
  4. {ics_query-0.4.32 → ics_query-0.4.34}/ics_query/cli.py +97 -13
  5. {ics_query-0.4.32 → ics_query-0.4.34}/ics_query/tests/conftest.py +17 -9
  6. ics_query-0.4.34/ics_query/tests/runs/at 2014-05-03 x-wr-timezone-rdate-hackerpublicradio.ics -.run +9 -0
  7. ics_query-0.4.34/ics_query/tests/runs/calendars/x-wr-timezone-rdate-hackerpublicradio.ics +26 -0
  8. ics_query-0.4.34/ics_query/tests/test_issue_40_valid_calendar.py +68 -0
  9. {ics_query-0.4.32 → ics_query-0.4.34}/pyproject.toml +1 -1
  10. {ics_query-0.4.32 → ics_query-0.4.34}/.github/FUNDING.yml +0 -0
  11. {ics_query-0.4.32 → ics_query-0.4.34}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  12. {ics_query-0.4.32 → ics_query-0.4.34}/.github/dependabot.yml +0 -0
  13. {ics_query-0.4.32 → ics_query-0.4.34}/.github/workflows/tests.yml +0 -0
  14. {ics_query-0.4.32 → ics_query-0.4.34}/.gitignore +0 -0
  15. {ics_query-0.4.32 → ics_query-0.4.34}/.pre-commit-config.yaml +0 -0
  16. {ics_query-0.4.32 → ics_query-0.4.34}/LICENSE +0 -0
  17. {ics_query-0.4.32 → ics_query-0.4.34}/conftest.py +0 -0
  18. {ics_query-0.4.32 → ics_query-0.4.34}/ics-query +0 -0
  19. {ics_query-0.4.32 → ics_query-0.4.34}/ics-query.cmd +0 -0
  20. {ics_query-0.4.32 → ics_query-0.4.34}/ics-query.py +0 -0
  21. {ics_query-0.4.32 → ics_query-0.4.34}/ics_query/__init__.py +0 -0
  22. {ics_query-0.4.32 → ics_query-0.4.34}/ics_query/__main__.py +0 -0
  23. {ics_query-0.4.32 → ics_query-0.4.34}/ics_query/parse.py +0 -0
  24. {ics_query-0.4.32 → ics_query-0.4.34}/ics_query/query.py +0 -0
  25. {ics_query-0.4.32 → ics_query-0.4.34}/ics_query/tests/__init__.py +0 -0
  26. {ics_query-0.4.32 → ics_query-0.4.34}/ics_query/tests/runs/all --tz Singapore one-event.ics -.run +0 -0
  27. {ics_query-0.4.32 → ics_query-0.4.34}/ics_query/tests/runs/all three-events.ics -.run +0 -0
  28. {ics_query-0.4.32 → ics_query-0.4.34}/ics_query/tests/runs/at 2019-03-04 multiple-calendars.ics -.run +0 -0
  29. {ics_query-0.4.32 → ics_query-0.4.34}/ics_query/tests/runs/at 2019-03-04 one-event-twice.ics -.run +0 -0
  30. {ics_query-0.4.32 → ics_query-0.4.34}/ics_query/tests/runs/at 2019-03-04 one-event.ics -.run +0 -0
  31. {ics_query-0.4.32 → ics_query-0.4.34}/ics_query/tests/runs/at 2019-03-07 multiple-calendars.ics -.run +0 -0
  32. {ics_query-0.4.32 → ics_query-0.4.34}/ics_query/tests/runs/at 2024-08-20 Berlin-Los-Angeles.ics -.run +0 -0
  33. {ics_query-0.4.32 → ics_query-0.4.34}/ics_query/tests/runs/between 20240823 4d recurring-work-events.ics -.run +0 -0
  34. {ics_query-0.4.32 → ics_query-0.4.34}/ics_query/tests/runs/calendars/Berlin-Los-Angeles.ics +0 -0
  35. {ics_query-0.4.32 → ics_query-0.4.34}/ics_query/tests/runs/calendars/empty-calendar.ics +0 -0
  36. {ics_query-0.4.32 → ics_query-0.4.34}/ics_query/tests/runs/calendars/empty-file.ics +0 -0
  37. {ics_query-0.4.32 → ics_query-0.4.34}/ics_query/tests/runs/calendars/multiple-calendars.ics +0 -0
  38. {ics_query-0.4.32 → ics_query-0.4.34}/ics_query/tests/runs/calendars/one-event-twice.ics +0 -0
  39. {ics_query-0.4.32 → ics_query-0.4.34}/ics_query/tests/runs/calendars/one-event-without-timezone.ics +0 -0
  40. {ics_query-0.4.32 → ics_query-0.4.34}/ics_query/tests/runs/calendars/one-event.ics +0 -0
  41. {ics_query-0.4.32 → ics_query-0.4.34}/ics_query/tests/runs/calendars/recurring-work-events.ics +0 -0
  42. {ics_query-0.4.32 → ics_query-0.4.34}/ics_query/tests/runs/calendars/simple-journal.ics +0 -0
  43. {ics_query-0.4.32 → ics_query-0.4.34}/ics_query/tests/runs/calendars/simple-todo.ics +0 -0
  44. {ics_query-0.4.32 → ics_query-0.4.34}/ics_query/tests/runs/calendars/three-events.ics +0 -0
  45. {ics_query-0.4.32 → ics_query-0.4.34}/ics_query/tests/runs/first -c VJOURNAL -c VEVENT one-event.ics -.run +0 -0
  46. {ics_query-0.4.32 → ics_query-0.4.34}/ics_query/tests/runs/first -c VJOURNAL one-event.ics -.run +0 -0
  47. {ics_query-0.4.32 → ics_query-0.4.34}/ics_query/tests/runs/first -c VJOURNAL simple-journal.ics -.run +0 -0
  48. {ics_query-0.4.32 → ics_query-0.4.34}/ics_query/tests/runs/first -c VTODO -c VJOURNAL simple-todo.ics -.run +0 -0
  49. {ics_query-0.4.32 → ics_query-0.4.34}/ics_query/tests/runs/first -c VTODO simple-todo.ics -.run +0 -0
  50. {ics_query-0.4.32 → ics_query-0.4.34}/ics_query/tests/runs/first empty-calendar.ics -.run +0 -0
  51. {ics_query-0.4.32 → ics_query-0.4.34}/ics_query/tests/runs/first empty-file.ics -.run +0 -0
  52. {ics_query-0.4.32 → ics_query-0.4.34}/ics_query/tests/runs/first recurring-work-events.ics -.run +0 -0
  53. {ics_query-0.4.32 → ics_query-0.4.34}/ics_query/tests/test_command_line.py +0 -0
  54. {ics_query-0.4.32 → ics_query-0.4.34}/ics_query/tests/test_parse_date.py +0 -0
  55. {ics_query-0.4.32 → ics_query-0.4.34}/ics_query/tests/test_parse_timedelta.py +0 -0
  56. {ics_query-0.4.32 → ics_query-0.4.34}/ics_query/version.py +0 -0
  57. {ics_query-0.4.32 → ics_query-0.4.34}/renovate.json +0 -0
  58. {ics_query-0.4.32 → ics_query-0.4.34}/tox.ini +0 -0
@@ -1,10 +1,10 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ics-query
3
- Version: 0.4.32
3
+ Version: 0.4.34
4
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/a11ed5390eb3571ecd3e0af2a1b990812f3de41d.zip
7
+ Project-URL: source_archive, https://github.com/niccokunzmann/ics-query/archive/c25d3a90e2b57c9bd22aa18e57104f174691dba3.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
@@ -697,7 +697,7 @@ Classifier: Programming Language :: Python :: 3.12
697
697
  Classifier: Topic :: Office/Business :: Scheduling
698
698
  Requires-Python: >=3.9
699
699
  Requires-Dist: click==8.1.8
700
- Requires-Dist: icalendar==6.1.3
700
+ Requires-Dist: icalendar==6.2.0
701
701
  Requires-Dist: python-dateutil==2.9.0.post0
702
702
  Requires-Dist: recurring-ical-events==3.7.0
703
703
  Requires-Dist: six==1.17.0
@@ -802,6 +802,8 @@ LAST-MODIFIED:20190303T111937
802
802
  END:VEVENT
803
803
  ```
804
804
 
805
+ #### Concatenating Calendars
806
+
805
807
  We can concatenate calendars and pipe them into `ics-query`.
806
808
  In the example below, we get all events that happen right now in two calendars.
807
809
 
@@ -819,6 +821,30 @@ You can pipe one or more calendars into the input.
819
821
  cat calendar.ics | ics-query first -
820
822
  ```
821
823
 
824
+ #### Valid ICS files
825
+
826
+ The resulting events are missing the timezone and the calendar information by default.
827
+ This information can be added using the `--as-calendar` parameter.
828
+ The result is a valid `.ics` file that can be processed further by other commands and programs.
829
+
830
+ In the example below, we use the calendar command to inspect the event for human readability.
831
+
832
+ ```shell
833
+ $ ics-query at --as-calendar 2014-05-03 x-wr-timezone-rdate-hackerpublicradio.ics event.ics
834
+ $ icalendar event.ics
835
+ Organizer:
836
+ Attendees:
837
+
838
+ Summary : HPR Community News
839
+ Starts : Sat May 3 20:00:00 2014
840
+ End : Sat May 3 22:00:00 2014
841
+ Duration : 2:00:00
842
+ Location : mumble.openspeak.cc port: 64747
843
+ Comment :
844
+ Description:
845
+ This is from http://www.hackerpublicradio.org/eps/hpr1286/iCalendar_Hacking_shownotes.html
846
+ ```
847
+
822
848
  ### Events at Certain Times
823
849
 
824
850
  You can query which events happen at certain times:
@@ -1110,6 +1136,16 @@ We automatically release the versions that only update dependencies.
1110
1136
  If the version you installed does not show up here, only the dependencies
1111
1137
  have been updated.
1112
1138
 
1139
+ - v0.4.33
1140
+
1141
+ - Add `--as-calendar` parameter.
1142
+
1143
+ - v0.4.32
1144
+
1145
+ - Update dependencies.
1146
+ - Include recurrence ID in events to identify the occurrence in a series.
1147
+ - Update help message in command line.
1148
+
1113
1149
  - v0.4.1
1114
1150
 
1115
1151
  - Automatic release with patch level version number increased
@@ -92,6 +92,8 @@ LAST-MODIFIED:20190303T111937
92
92
  END:VEVENT
93
93
  ```
94
94
 
95
+ #### Concatenating Calendars
96
+
95
97
  We can concatenate calendars and pipe them into `ics-query`.
96
98
  In the example below, we get all events that happen right now in two calendars.
97
99
 
@@ -109,6 +111,30 @@ You can pipe one or more calendars into the input.
109
111
  cat calendar.ics | ics-query first -
110
112
  ```
111
113
 
114
+ #### Valid ICS files
115
+
116
+ The resulting events are missing the timezone and the calendar information by default.
117
+ This information can be added using the `--as-calendar` parameter.
118
+ The result is a valid `.ics` file that can be processed further by other commands and programs.
119
+
120
+ In the example below, we use the calendar command to inspect the event for human readability.
121
+
122
+ ```shell
123
+ $ ics-query at --as-calendar 2014-05-03 x-wr-timezone-rdate-hackerpublicradio.ics event.ics
124
+ $ icalendar event.ics
125
+ Organizer:
126
+ Attendees:
127
+
128
+ Summary : HPR Community News
129
+ Starts : Sat May 3 20:00:00 2014
130
+ End : Sat May 3 22:00:00 2014
131
+ Duration : 2:00:00
132
+ Location : mumble.openspeak.cc port: 64747
133
+ Comment :
134
+ Description:
135
+ This is from http://www.hackerpublicradio.org/eps/hpr1286/iCalendar_Hacking_shownotes.html
136
+ ```
137
+
112
138
  ### Events at Certain Times
113
139
 
114
140
  You can query which events happen at certain times:
@@ -400,6 +426,16 @@ We automatically release the versions that only update dependencies.
400
426
  If the version you installed does not show up here, only the dependencies
401
427
  have been updated.
402
428
 
429
+ - v0.4.33
430
+
431
+ - Add `--as-calendar` parameter.
432
+
433
+ - v0.4.32
434
+
435
+ - Update dependencies.
436
+ - Include recurrence ID in events to identify the occurrence in a series.
437
+ - Update help message in command line.
438
+
403
439
  - v0.4.1
404
440
 
405
441
  - Automatic release with patch level version number increased
@@ -17,5 +17,5 @@ __version__: str
17
17
  __version_tuple__: VERSION_TUPLE
18
18
  version_tuple: VERSION_TUPLE
19
19
 
20
- __version__ = version = '0.4.32'
21
- __version_tuple__ = version_tuple = (0, 4, 32)
20
+ __version__ = version = '0.4.34'
21
+ __version_tuple__ = version_tuple = (0, 4, 34)
@@ -24,18 +24,18 @@ import typing as t
24
24
  import zoneinfo
25
25
 
26
26
  import click
27
- from icalendar.cal import Calendar, Component
27
+ from icalendar import Calendar, Component, Timezone
28
28
  from tzlocal import get_localzone_name
29
29
 
30
30
  from . import parse
31
31
  from .query import Query
32
- from .version import cli_version
32
+ from .version import __version__, cli_version
33
33
 
34
34
  if t.TYPE_CHECKING:
35
35
  from io import FileIO
36
36
 
37
37
  import recurring_ical_events
38
- from icalendar.cal import Component
38
+ from icalendar import Component
39
39
 
40
40
  from .parse import Date
41
41
 
@@ -67,17 +67,58 @@ class ComponentsResult:
67
67
  def __init__(self, output: FileIO):
68
68
  """Create a new result."""
69
69
  self._file = output
70
+ self._entered = False
70
71
 
71
- def add_component(self, component: Component):
72
+ def write(self, data: bytes | str):
73
+ """Write data to the output."""
74
+ if isinstance(data, str):
75
+ data = data.encode()
76
+ self._file.write(data)
77
+
78
+ def __enter__(self):
79
+ """Start adding components."""
80
+ self._entered = True
81
+
82
+ def __exit__(self, exc_type, exc_value, traceback):
83
+ """Stop adding components."""
84
+ self._entered = False
85
+
86
+ def add_component(self, component: Component) -> None:
72
87
  """Return a component."""
73
- self._file.write(component.to_ical())
88
+ assert self._entered
89
+ self.write(component.to_ical())
74
90
 
75
- def add_components(self, components: t.Iterable[Component]):
91
+ def add_components(self, components: t.Iterable[Component]) -> None:
76
92
  """Add all components."""
77
93
  for component in components:
78
94
  self.add_component(component)
79
95
 
80
96
 
97
+ class CalendarResult(ComponentsResult):
98
+ """Wrap the resulting components in a calendar."""
99
+
100
+ CALENDAR_START = (
101
+ f"BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:ics-query {__version__}\r\n"
102
+ )
103
+ CALENDAR_END = "END:VCALENDAR\r\n"
104
+
105
+ def __init__(self, output: FileIO, timezones: list[Timezone]):
106
+ super().__init__(output)
107
+ self.timezones = timezones
108
+
109
+ def __enter__(self):
110
+ """Start the calendar."""
111
+ super().__enter__()
112
+ self.write(self.CALENDAR_START)
113
+ for timezone in self.timezones:
114
+ self.write(timezone.to_ical())
115
+
116
+ def __exit__(self, exc_type, exc_value, traceback):
117
+ """Stop the calendar."""
118
+ self.write(self.CALENDAR_END)
119
+ super().__exit__(exc_type, exc_value, traceback)
120
+
121
+
81
122
  class ComponentsResultArgument(click.File):
82
123
  """Argument for the result."""
83
124
 
@@ -89,6 +130,11 @@ class ComponentsResultArgument(click.File):
89
130
  ) -> ComponentsResult:
90
131
  """Return a ComponentsResult."""
91
132
  file = super().convert(value, param, ctx)
133
+ # we claim the as_calendar argument
134
+ wrap_calendar = ctx.params.pop("as_calendar", False)
135
+ if wrap_calendar:
136
+ joined: JoinedCalendars = ctx.params["calendar"]
137
+ return CalendarResult(file, joined.timezones)
92
138
  return ComponentsResult(file)
93
139
 
94
140
 
@@ -97,6 +143,7 @@ class JoinedCalendars:
97
143
  self, calendars: list[Calendar], timezone: str, components: t.Sequence[str]
98
144
  ):
99
145
  """Join multiple calendars."""
146
+ self.calendars = calendars
100
147
  self.queries = [
101
148
  Query(calendar, timezone, components=components) for calendar in calendars
102
149
  ]
@@ -124,6 +171,27 @@ class JoinedCalendars:
124
171
  for query in self.queries:
125
172
  yield from query.between(start, end)
126
173
 
174
+ @property
175
+ def timezones(self) -> list[Timezone]:
176
+ """Return all the timezones in use."""
177
+ result = []
178
+ tzids = set()
179
+ # add existing timezone components first
180
+ for calendar in self.calendars:
181
+ for timezone in calendar.timezones:
182
+ if timezone.tz_name not in tzids:
183
+ tzids.add(timezone.tz_name)
184
+ result.append(timezone)
185
+ # add X-WR-TIMEZONE later to prevent generating if existing
186
+ for calendar in self.calendars:
187
+ tzid = calendar.get("X-WR-TIMEZONE", None)
188
+ if tzid is not None and tzid not in tzids:
189
+ timezone = Timezone.from_tzid(tzid)
190
+ tzids.add(timezone.tz_name)
191
+ tzids.add(tzid)
192
+ result.append(timezone)
193
+ return result
194
+
127
195
 
128
196
  class CalendarQueryInputArgument(click.File):
129
197
  """Argument for the result."""
@@ -160,6 +228,15 @@ opt_timezone = click.option(
160
228
  help=("Set the timezone. See also --available-timezones"),
161
229
  )
162
230
 
231
+ opt_calendar = click.option(
232
+ "--as-calendar",
233
+ envvar=ENV_PREFIX + "_AS_CALENDAR",
234
+ is_flag=True,
235
+ default=False,
236
+ is_eager=True,
237
+ help="Return a valid calendar, not just the components.",
238
+ )
239
+
163
240
 
164
241
  def arg_calendar(func):
165
242
  """Decorator for a calendar argument with all used options."""
@@ -173,9 +250,12 @@ def arg_calendar(func):
173
250
  return opt_timezone(opt_components(arg(wrapper)))
174
251
 
175
252
 
176
- arg_output = click.argument("output", type=ComponentsResultArgument("wb"), default="-")
177
- # Option with many values and list as result
178
- # see https://click.palletsprojects.com/en/latest/options/#multiple-options
253
+ def arg_output(func):
254
+ """Add the output argument and its parameters."""
255
+ # Option with many values and list as result
256
+ # see https://click.palletsprojects.com/en/latest/options/#multiple-options
257
+ arg = click.argument("output", type=ComponentsResultArgument("wb"), default="-")
258
+ return opt_calendar(arg(func))
179
259
 
180
260
 
181
261
  def opt_available_timezones(*param_decls: str, **kwargs: t.Any) -> t.Callable:
@@ -458,7 +538,8 @@ def at(calendar: JoinedCalendars, output: ComponentsResult, date: Date):
458
538
  ics-query at 19900101235959 # 1st January 1990, 23:59:59
459
539
  ics-query at `date +%Y%m%d%H%M%S` # now
460
540
  """ # noqa: D301
461
- output.add_components(calendar.at(date))
541
+ with output:
542
+ output.add_components(calendar.at(date))
462
543
 
463
544
 
464
545
  @cli.command()
@@ -475,7 +556,8 @@ def first(calendar: JoinedCalendars, output: ComponentsResult):
475
556
  ics-query first --component VEVENT calendar.ics -
476
557
 
477
558
  """ # noqa: D301
478
- output.add_components(calendar.first())
559
+ with output:
560
+ output.add_components(calendar.first())
479
561
 
480
562
 
481
563
  @cli.command()
@@ -499,7 +581,8 @@ def all(calendar: JoinedCalendars, output: ComponentsResult): # noqa: A001
499
581
  potentially enourmous. You can mitigate this by closing the OUTPUT
500
582
  when you have enough e.g. with a head command.
501
583
  """ # noqa: D301
502
- output.add_components(calendar.all())
584
+ with output:
585
+ output.add_components(calendar.all())
503
586
 
504
587
 
505
588
  @cli.command()
@@ -683,7 +766,8 @@ def between(
683
766
  Add 3 hours and 15 minutes to START: +3h15m
684
767
 
685
768
  """ # noqa: D301
686
- output.add_components(calendar.between(start, end))
769
+ with output:
770
+ output.add_components(calendar.between(start, end))
687
771
 
688
772
 
689
773
  def main():
@@ -22,13 +22,14 @@ from pathlib import Path
22
22
  from typing import Callable, NamedTuple
23
23
 
24
24
  import pytest
25
+ from icalendar import Calendar
25
26
 
26
27
  HERE = Path(__file__).parent
27
28
  IO_DIRECTORY = HERE / "runs"
28
29
  CALENDARS_DIRECTORY = IO_DIRECTORY / "calendars"
29
30
 
30
31
 
31
- class TestRun(NamedTuple):
32
+ class ExampleRun(NamedTuple):
32
33
  """The result from a test run."""
33
34
 
34
35
  exit_code: int
@@ -38,7 +39,7 @@ class TestRun(NamedTuple):
38
39
  @classmethod
39
40
  def from_completed_process(
40
41
  cls, completed_process: subprocess.CompletedProcess
41
- ) -> TestRun:
42
+ ) -> ExampleRun:
42
43
  """Create a new run result."""
43
44
  stdout = completed_process.stdout.decode("UTF-8").replace("\r\n", "\n")
44
45
  print(stdout)
@@ -48,6 +49,11 @@ class TestRun(NamedTuple):
48
49
  completed_process.stderr.decode("UTF-8"),
49
50
  )
50
51
 
52
+ @property
53
+ def calendar(self) -> Calendar:
54
+ """Return the output as a calendar."""
55
+ return Calendar.from_ical(self.output)
56
+
51
57
 
52
58
  def get_binary_path(request: pytest.FixtureRequest) -> str:
53
59
  """Return the path to the ics-query command."""
@@ -59,7 +65,7 @@ def get_binary_path(request: pytest.FixtureRequest) -> str:
59
65
  return Path(command).absolute()
60
66
 
61
67
 
62
- def run_ics_query(*command, cwd=CALENDARS_DIRECTORY, binary: str) -> TestRun:
68
+ def run_ics_query(*command, cwd=CALENDARS_DIRECTORY, binary: str) -> ExampleRun:
63
69
  """Run ics-qeury with a command.
64
70
 
65
71
  - cwd is the working directory
@@ -74,7 +80,9 @@ def run_ics_query(*command, cwd=CALENDARS_DIRECTORY, binary: str) -> TestRun:
74
80
  check=False,
75
81
  cwd=cwd,
76
82
  )
77
- return TestRun.from_completed_process(completed_process)
83
+ if completed_process.stderr:
84
+ print(completed_process.stderr.decode())
85
+ return ExampleRun.from_completed_process(completed_process)
78
86
 
79
87
 
80
88
  class IOTestCase(NamedTuple):
@@ -92,7 +100,7 @@ class IOTestCase(NamedTuple):
92
100
  expected_output = path.read_text(encoding="UTF-8").replace("\r\n", "\n")
93
101
  return cls(path.name, path.stem.split(), path.parent, expected_output, binary)
94
102
 
95
- def run(self) -> TestRun:
103
+ def run(self) -> ExampleRun:
96
104
  """Run this test case and return the result."""
97
105
  return run_ics_query(*self.command, binary=self.binary)
98
106
 
@@ -100,7 +108,7 @@ class IOTestCase(NamedTuple):
100
108
  io_test_case_paths = [
101
109
  test_case_path
102
110
  for test_case_path in IO_DIRECTORY.iterdir()
103
- if test_case_path.is_file()
111
+ if test_case_path.is_file() and test_case_path.suffix == ".run"
104
112
  ]
105
113
 
106
114
 
@@ -112,8 +120,8 @@ def io_testcase(request: pytest.FixtureRequest) -> IOTestCase:
112
120
  return IOTestCase.from_path(path, binary)
113
121
 
114
122
 
115
- @pytest.fixture
116
- def run(request: pytest.FixtureRequest) -> Callable[..., TestRun]:
123
+ @pytest.fixture()
124
+ def run(request: pytest.FixtureRequest) -> Callable[..., ExampleRun]:
117
125
  """Return a runner function."""
118
126
 
119
127
  def run(*args, **kw):
@@ -123,4 +131,4 @@ def run(request: pytest.FixtureRequest) -> Callable[..., TestRun]:
123
131
  return run
124
132
 
125
133
 
126
- __all__ = ["IOTestCase", "TestRun"]
134
+ __all__ = ["IOTestCase", "ExampleRun"]
@@ -0,0 +1,9 @@
1
+ BEGIN:VEVENT
2
+ SUMMARY:HPR Community News
3
+ DTSTART;TZID=Europe/London:20140503T200000
4
+ DTEND;TZID=Europe/London:20140503T220000
5
+ RECURRENCE-ID;TZID=Europe/London:20140503T200000
6
+ DESCRIPTION:This is from http://www.hackerpublicradio.org/eps/hpr1286/iCal
7
+ endar_Hacking_shownotes.html
8
+ LOCATION:mumble.openspeak.cc port: 64747
9
+ END:VEVENT
@@ -0,0 +1,26 @@
1
+ BEGIN:VCALENDAR
2
+ VERSION:2.0
3
+ PRODID:Data::ICal 0.20
4
+ X-WR-CALNAME:Hacker Public Radio
5
+ X-WR-TIMEZONE:Europe/London
6
+ BEGIN:VEVENT
7
+ DESCRIPTION:This is from http://www.hackerpublicradio.org/eps/hpr1286/iCalendar_
8
+ Hacking_shownotes.html
9
+ DTEND:20130803T210000Z
10
+ DTSTART:20130803T190000Z
11
+ LOCATION:mumble.openspeak.cc port: 64747
12
+ RDATE;VALUE=DATE-TIME:20130803T190000Z
13
+ RDATE;VALUE=DATE-TIME:20130831T190000Z
14
+ RDATE;VALUE=DATE-TIME:20131005T190000Z
15
+ RDATE;VALUE=DATE-TIME:20131102T190000Z
16
+ RDATE;VALUE=DATE-TIME:20131130T190000Z
17
+ RDATE;VALUE=DATE-TIME:20140104T190000Z
18
+ RDATE;VALUE=DATE-TIME:20140201T190000Z
19
+ RDATE;VALUE=DATE-TIME:20140301T190000Z
20
+ RDATE;VALUE=DATE-TIME:20140405T190000Z
21
+ RDATE;VALUE=DATE-TIME:20140503T190000Z
22
+ RDATE;VALUE=DATE-TIME:20140531T190000Z
23
+ RDATE;VALUE=DATE-TIME:20140705T190000Z
24
+ SUMMARY:HPR Community News
25
+ END:VEVENT
26
+ END:VCALENDAR
@@ -0,0 +1,68 @@
1
+ """We make sure that the result can be processed by another icalendar application.
2
+
3
+ See https://github.com/niccokunzmann/ics-query/issues/40
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from typing import TYPE_CHECKING
9
+
10
+ import pytest
11
+
12
+ from ics_query import __version__
13
+
14
+ if TYPE_CHECKING:
15
+ from icalendar import Calendar, Timezone
16
+
17
+ from ics_query.tests.conftest import ExampleRun
18
+
19
+
20
+ @pytest.fixture()
21
+ def calendar(run) -> ExampleRun:
22
+ """Return a calendar that is wrapped around the event."""
23
+ return run("first", "--as-calendar", "one-event-without-timezone.ics").calendar
24
+
25
+
26
+ def test_result_is_wrapped_in_a_calendar(calendar: Calendar):
27
+ """Add the calendar component around the event."""
28
+ assert calendar.name == "VCALENDAR"
29
+
30
+
31
+ def test_the_product_id_is_that_of_ics_query(calendar):
32
+ """The product id is set with version."""
33
+ assert calendar["PRODID"] == f"ics-query {__version__}"
34
+
35
+
36
+ def test_the_version_is_set(calendar):
37
+ """Version is required."""
38
+ assert calendar["VERSION"] == "2.0"
39
+
40
+
41
+ def test_no_timezone_is_included(calendar):
42
+ """We do not have timezones in this file, so there should be none."""
43
+ assert calendar.timezones == []
44
+
45
+
46
+ @pytest.mark.parametrize(
47
+ "file",
48
+ [
49
+ "one-event.ics", # one timezone
50
+ "multiple-calendars.ics", # same timezone twice
51
+ ],
52
+ )
53
+ def test_calendar_adds_timezones_automatically(run, file):
54
+ """Return a calendar that is wrapped around the event."""
55
+ calendar = run("first", "--as-calendar", file).calendar
56
+ assert len(calendar.timezones) == 1
57
+ tz: Timezone = calendar.timezones[0]
58
+ assert tz.tz_name == "Europe/Berlin"
59
+
60
+
61
+ def test_x_wr_timezone_is_added(run):
62
+ """X-WR-TIMEZONE requires adding the timezone component manually."""
63
+ calendar = run(
64
+ "first", "--as-calendar", "x-wr-timezone-rdate-hackerpublicradio.ics"
65
+ ).calendar
66
+ assert len(calendar.timezones) == 1
67
+ tz: Timezone = calendar.timezones[0]
68
+ assert tz.tz_name == "Europe/London"
@@ -45,7 +45,7 @@ classifiers = [
45
45
  ]
46
46
 
47
47
  dependencies = [
48
- "icalendar==6.1.3",
48
+ "icalendar==6.2.0",
49
49
  "recurring-ical-events==3.7.0",
50
50
  "click==8.1.8",
51
51
  "x-wr-timezone==2.0.1",
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes