ics-query 0.2.0a0__py3-none-any.whl → 0.3.0b0__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/__init__.py +3 -2
- ics_query/_version.py +2 -2
- ics_query/cli.py +191 -23
- ics_query/query.py +54 -0
- ics_query/tests/runs/all --tz Singapore one-event.ics -.run +9 -0
- ics_query/tests/runs/all three-events.ics -.run +33 -0
- ics_query/tests/runs/at 2024-08-20 Berlin-Los-Angeles.ics -.run +23 -0
- ics_query/tests/runs/calendars/Berlin-Los-Angeles.ics +381 -0
- ics_query/tests/runs/calendars/simple-journal.ics +15 -0
- ics_query/tests/runs/calendars/simple-todo.ics +15 -0
- ics_query/tests/runs/calendars/three-events.ics +37 -0
- ics_query/tests/runs/first -c VJOURNAL -c VEVENT one-event.ics -.run +9 -0
- ics_query/tests/runs/first -c VJOURNAL one-event.ics -.run +0 -0
- ics_query/tests/runs/first -c VJOURNAL simple-journal.ics -.run +12 -0
- ics_query/tests/runs/first -c VTODO -c VJOURNAL simple-todo.ics -.run +10 -0
- ics_query/tests/runs/first -c VTODO simple-todo.ics -.run +10 -0
- ics_query/tests/test_command_line.py +11 -0
- ics_query/version.py +19 -0
- {ics_query-0.2.0a0.dist-info → ics_query-0.3.0b0.dist-info}/METADATA +159 -51
- {ics_query-0.2.0a0.dist-info → ics_query-0.3.0b0.dist-info}/RECORD +23 -10
- {ics_query-0.2.0a0.dist-info → ics_query-0.3.0b0.dist-info}/WHEEL +0 -0
- {ics_query-0.2.0a0.dist-info → ics_query-0.3.0b0.dist-info}/entry_points.txt +0 -0
- {ics_query-0.2.0a0.dist-info → ics_query-0.3.0b0.dist-info}/licenses/LICENSE +0 -0
ics_query/__init__.py
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
from .cli import main
|
|
1
|
+
from .cli import cli, main
|
|
2
2
|
from .version import __version__, __version_tuple__, version, version_tuple
|
|
3
3
|
|
|
4
4
|
__all__ = [
|
|
5
|
-
"
|
|
5
|
+
"cli",
|
|
6
6
|
"app",
|
|
7
7
|
"__version__",
|
|
8
8
|
"version",
|
|
9
9
|
"__version_tuple__",
|
|
10
10
|
"version_tuple",
|
|
11
|
+
"main",
|
|
11
12
|
]
|
ics_query/_version.py
CHANGED
ics_query/cli.py
CHANGED
|
@@ -8,21 +8,25 @@ import sys
|
|
|
8
8
|
import typing as t
|
|
9
9
|
|
|
10
10
|
import click
|
|
11
|
-
import
|
|
11
|
+
import zoneinfo
|
|
12
12
|
from icalendar.cal import Calendar, Component
|
|
13
13
|
|
|
14
14
|
from . import parse
|
|
15
|
-
from .
|
|
15
|
+
from .query import Query
|
|
16
|
+
from .version import cli_version
|
|
16
17
|
|
|
17
18
|
if t.TYPE_CHECKING:
|
|
18
19
|
from io import FileIO
|
|
19
20
|
|
|
21
|
+
import recurring_ical_events
|
|
20
22
|
from icalendar.cal import Component
|
|
21
23
|
|
|
22
24
|
from .parse import Date
|
|
23
25
|
|
|
24
26
|
print = functools.partial(print, file=sys.stderr) # noqa: A001
|
|
25
27
|
|
|
28
|
+
ENV_PREFIX = "ICS_QUERY"
|
|
29
|
+
|
|
26
30
|
|
|
27
31
|
class ComponentsResult:
|
|
28
32
|
"""Output interface for components."""
|
|
@@ -56,9 +60,13 @@ class ComponentsResultArgument(click.File):
|
|
|
56
60
|
|
|
57
61
|
|
|
58
62
|
class JoinedCalendars:
|
|
59
|
-
def __init__(
|
|
63
|
+
def __init__(
|
|
64
|
+
self, calendars: list[Calendar], timezone: str, components: t.Sequence[str]
|
|
65
|
+
):
|
|
60
66
|
"""Join multiple calendars."""
|
|
61
|
-
self.queries = [
|
|
67
|
+
self.queries = [
|
|
68
|
+
Query(calendar, timezone, components=components) for calendar in calendars
|
|
69
|
+
]
|
|
62
70
|
|
|
63
71
|
def at(self, dt: tuple[int]) -> t.Generator[Component]:
|
|
64
72
|
"""Return the components."""
|
|
@@ -72,6 +80,11 @@ class JoinedCalendars:
|
|
|
72
80
|
yield component
|
|
73
81
|
break
|
|
74
82
|
|
|
83
|
+
def all(self) -> t.Generator[Component]:
|
|
84
|
+
"""Return the first events of all calendars."""
|
|
85
|
+
for query in self.queries:
|
|
86
|
+
yield from query.all()
|
|
87
|
+
|
|
75
88
|
def between(
|
|
76
89
|
self, start: parse.Date, end: parse.DateAndDelta
|
|
77
90
|
) -> t.Generator[Component]:
|
|
@@ -91,16 +104,88 @@ class CalendarQueryInputArgument(click.File):
|
|
|
91
104
|
"""Return a CalendarQuery."""
|
|
92
105
|
file = super().convert(value, param, ctx)
|
|
93
106
|
calendars = Calendar.from_ical(file.read(), multiple=True)
|
|
94
|
-
|
|
107
|
+
components = ctx.params.get("component", ("VEVENT", "VTODO", "VJOURNAL"))
|
|
108
|
+
timezone = ctx.params.get("tz", "")
|
|
109
|
+
return JoinedCalendars(calendars, timezone, components)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
opt_components = click.option(
|
|
113
|
+
"--component",
|
|
114
|
+
"-c",
|
|
115
|
+
multiple=True,
|
|
116
|
+
envvar=ENV_PREFIX + "_COMPONENT",
|
|
117
|
+
help=(
|
|
118
|
+
"Select the components which can be returned. "
|
|
119
|
+
"By default all supported components can be in the result. "
|
|
120
|
+
"Possible values are: VEVENT, VTODO, VJOURNAL. "
|
|
121
|
+
),
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
opt_timezone = click.option(
|
|
125
|
+
"--tz",
|
|
126
|
+
envvar=ENV_PREFIX + "_TZ",
|
|
127
|
+
help=("Set the timezone. See also --available-timezones"),
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def arg_calendar(func):
|
|
132
|
+
"""Decorator for a calendar argument with all used options."""
|
|
133
|
+
arg = click.argument("calendar", type=CalendarQueryInputArgument("rb"))
|
|
134
|
+
|
|
135
|
+
@functools.wraps(func)
|
|
136
|
+
def wrapper(*args, component=(), tz="", **kw): # noqa: ARG001
|
|
137
|
+
"""Remove some parameters."""
|
|
138
|
+
return func(*args, **kw)
|
|
139
|
+
|
|
140
|
+
return opt_timezone(opt_components(arg(wrapper)))
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
arg_output = click.argument("output", type=ComponentsResultArgument("wb"), default="-")
|
|
144
|
+
# Option with many values and list as result
|
|
145
|
+
# see https://click.palletsprojects.com/en/latest/options/#multiple-options
|
|
95
146
|
|
|
96
147
|
|
|
97
|
-
|
|
98
|
-
|
|
148
|
+
def opt_available_timezones(*param_decls: str, **kwargs: t.Any) -> t.Callable:
|
|
149
|
+
"""Add a ``--help`` option which immediately prints the help page
|
|
150
|
+
and exits the program.
|
|
151
|
+
|
|
152
|
+
This is usually unnecessary, as the ``--help`` option is added to
|
|
153
|
+
each command automatically unless ``add_help_option=False`` is
|
|
154
|
+
passed.
|
|
155
|
+
|
|
156
|
+
:param param_decls: One or more option names. Defaults to the single
|
|
157
|
+
value ``"--help"``.
|
|
158
|
+
:param kwargs: Extra arguments are passed to :func:`option`.
|
|
159
|
+
"""
|
|
160
|
+
|
|
161
|
+
def callback(ctx: click.Context, param: click.Parameter, value: bool) -> None: # noqa: FBT001, ARG001
|
|
162
|
+
if not value or ctx.resilient_parsing:
|
|
163
|
+
return
|
|
164
|
+
|
|
165
|
+
first_zones = ["localtime", "UTC"]
|
|
166
|
+
all_zones = zoneinfo.available_timezones()
|
|
167
|
+
for zone in first_zones:
|
|
168
|
+
if zone in all_zones:
|
|
169
|
+
click.echo(zone)
|
|
170
|
+
for zone in sorted(all_zones, key=str.lower):
|
|
171
|
+
click.echo(zone)
|
|
172
|
+
ctx.exit()
|
|
173
|
+
|
|
174
|
+
if not param_decls:
|
|
175
|
+
param_decls = ("--available-timezones",)
|
|
176
|
+
|
|
177
|
+
kwargs.setdefault("is_flag", True)
|
|
178
|
+
kwargs.setdefault("expose_value", False)
|
|
179
|
+
kwargs.setdefault("is_eager", True)
|
|
180
|
+
kwargs.setdefault("help", "List all available timezones and exit.")
|
|
181
|
+
kwargs["callback"] = callback
|
|
182
|
+
return click.option(*param_decls, **kwargs)
|
|
99
183
|
|
|
100
184
|
|
|
101
185
|
@click.group()
|
|
102
|
-
@click.version_option(
|
|
103
|
-
|
|
186
|
+
@click.version_option(cli_version)
|
|
187
|
+
@opt_available_timezones()
|
|
188
|
+
def cli():
|
|
104
189
|
"""Find out what happens in ICS calendar files.
|
|
105
190
|
|
|
106
191
|
ics-query can query and filter RFC 5545 compatible .ics files.
|
|
@@ -124,8 +209,8 @@ def main():
|
|
|
124
209
|
If OUTPUT is "-", then the standard output is used.
|
|
125
210
|
|
|
126
211
|
\b
|
|
127
|
-
|
|
128
|
-
|
|
212
|
+
Calculation
|
|
213
|
+
-----------
|
|
129
214
|
|
|
130
215
|
An event can be very long. If you request smaller time spans or a time as
|
|
131
216
|
exact as a second, the event will still occur within this time span if it
|
|
@@ -138,20 +223,72 @@ def main():
|
|
|
138
223
|
The START is INCLUSIVE, then END is EXCLUSIVE.
|
|
139
224
|
|
|
140
225
|
\b
|
|
141
|
-
|
|
142
|
-
|
|
226
|
+
Timezones
|
|
227
|
+
---------
|
|
228
|
+
|
|
229
|
+
We have several timezones available to choose from.
|
|
230
|
+
While the calendar entries might use their own timezone definitions,
|
|
231
|
+
the timezone parameters of ics-query use the timezone definitions of
|
|
232
|
+
Python's tzdata package.
|
|
233
|
+
|
|
234
|
+
You can list all timezones available with this command:
|
|
235
|
+
|
|
236
|
+
\b
|
|
237
|
+
ics-query --available-timezones
|
|
238
|
+
|
|
239
|
+
By default the local time of the components is assumed.
|
|
240
|
+
In this example, two events happen at 6am, one in Berlin and one in Los Angeles.
|
|
241
|
+
Both are hours apart though.
|
|
242
|
+
|
|
243
|
+
\b
|
|
244
|
+
$ ics-query at 2024-08-20 Berlin-Los-Angeles.ics - | grep -E 'DTSTART|SUMMARY'
|
|
245
|
+
SUMMARY:6:00-7:00 Europe/Berlin 20th August
|
|
246
|
+
DTSTART;TZID=Europe/Berlin:20240820T060000
|
|
247
|
+
SUMMARY:6:00-7:00 Amerika/Los Angeles 20th August
|
|
248
|
+
DTSTART;TZID=America/Los_Angeles:20240820T060000
|
|
249
|
+
|
|
250
|
+
If you however wish to get all events in a certain timezone, use the --tz parameter.
|
|
251
|
+
In this example, the event that happens at the 19th of August at 21:00 in
|
|
252
|
+
Los Angeles is actually happening on the 20th in local Berlin time.
|
|
253
|
+
|
|
254
|
+
\b
|
|
255
|
+
$ ics-query at --tz=Europe/Berlin 2024-08-20 Berlin-Los-Angeles.ics - \\
|
|
256
|
+
| grep -E 'DTSTART|SUMMARY'
|
|
257
|
+
SUMMARY:6:00-7:00 Europe/Berlin 20th August
|
|
258
|
+
DTSTART;TZID=Europe/Berlin:20240820T060000
|
|
259
|
+
SUMMARY:6:00-7:00 Amerika/Los Angeles 20th August
|
|
260
|
+
DTSTART;TZID=Europe/Berlin:20240820T150000
|
|
261
|
+
SUMMARY:21:00-22:00 Amerika/Los Angeles 19th August
|
|
262
|
+
DTSTART;TZID=Europe/Berlin:20240820T060000
|
|
263
|
+
|
|
264
|
+
If you wish to get events in your local time, use --tz localtime.
|
|
265
|
+
If you like UTC, use --tz UTC.
|
|
266
|
+
|
|
267
|
+
You can also set the environment variable ICS_QUERY_TZ to the timezone instead of
|
|
268
|
+
passing --tz.
|
|
269
|
+
|
|
270
|
+
\b
|
|
271
|
+
Components
|
|
272
|
+
----------
|
|
273
|
+
|
|
274
|
+
We support different types of recurring components: VEVENT, VTODO, VJOURNAL.
|
|
275
|
+
You can specify which can be in the result using the --component parameter.
|
|
276
|
+
|
|
277
|
+
You can also set the environment variable ICS_QUERY_COMPONENT to the timezone
|
|
278
|
+
instead of passing --component.
|
|
279
|
+
|
|
143
280
|
""" # noqa: D301
|
|
144
281
|
|
|
145
282
|
|
|
146
283
|
pass_datetime = click.make_pass_decorator(parse.to_time)
|
|
147
284
|
|
|
148
285
|
|
|
149
|
-
@
|
|
286
|
+
@cli.command()
|
|
150
287
|
@click.argument("date", type=parse.to_time)
|
|
151
288
|
@arg_calendar
|
|
152
289
|
@arg_output
|
|
153
290
|
def at(calendar: JoinedCalendars, output: ComponentsResult, date: Date):
|
|
154
|
-
"""
|
|
291
|
+
"""Print occurrences at a certain date or time.
|
|
155
292
|
|
|
156
293
|
YEAR
|
|
157
294
|
|
|
@@ -273,22 +410,48 @@ def at(calendar: JoinedCalendars, output: ComponentsResult, date: Date):
|
|
|
273
410
|
output.add_components(calendar.at(date))
|
|
274
411
|
|
|
275
412
|
|
|
276
|
-
@
|
|
413
|
+
@cli.command()
|
|
277
414
|
@arg_calendar
|
|
278
415
|
@arg_output
|
|
279
416
|
def first(calendar: JoinedCalendars, output: ComponentsResult):
|
|
280
|
-
"""Print only the first occurrence
|
|
417
|
+
"""Print only the first occurrence.
|
|
418
|
+
|
|
419
|
+
This prints the first occurrence in each calendar that is given.
|
|
281
420
|
|
|
282
421
|
\b
|
|
283
422
|
This example prints the first event in calendar.ics:
|
|
284
423
|
\b
|
|
285
|
-
ics-query first calendar.ics -
|
|
424
|
+
ics-query first --component VEVENT calendar.ics -
|
|
286
425
|
|
|
287
426
|
""" # noqa: D301
|
|
288
427
|
output.add_components(calendar.first())
|
|
289
428
|
|
|
290
429
|
|
|
291
|
-
@
|
|
430
|
+
@cli.command()
|
|
431
|
+
@arg_calendar
|
|
432
|
+
@arg_output
|
|
433
|
+
def all(calendar: JoinedCalendars, output: ComponentsResult): # noqa: A001
|
|
434
|
+
"""Print all occurrences in a calendar.
|
|
435
|
+
|
|
436
|
+
The result is ordered by the start of the occurrences.
|
|
437
|
+
If you have multiple calendars, the result will contain
|
|
438
|
+
the occurrences of the first calendar before those of the second calendar
|
|
439
|
+
and so on.
|
|
440
|
+
|
|
441
|
+
\b
|
|
442
|
+
This example prints all events in calendar.ics:
|
|
443
|
+
\b
|
|
444
|
+
ics-query all --component VEVENT calendar.ics -
|
|
445
|
+
|
|
446
|
+
Note that calendars can create hundreds of occurrences and especially
|
|
447
|
+
contain endless repetitions. Use this with care as the output is
|
|
448
|
+
potentially enourmous. You can mitigate this by closing the OUTPUT
|
|
449
|
+
when you have enough e.g. with a head command.
|
|
450
|
+
""" # noqa: D301
|
|
451
|
+
output.add_components(calendar.all())
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
@cli.command()
|
|
292
455
|
@click.argument("start", type=parse.to_time)
|
|
293
456
|
@click.argument("end", type=parse.to_time_and_delta)
|
|
294
457
|
@arg_calendar
|
|
@@ -299,20 +462,20 @@ def between(
|
|
|
299
462
|
calendar: JoinedCalendars,
|
|
300
463
|
output: ComponentsResult,
|
|
301
464
|
):
|
|
302
|
-
"""Print
|
|
465
|
+
"""Print occurrences between a START and an END.
|
|
303
466
|
|
|
304
467
|
The start is inclusive, the end is exclusive.
|
|
305
468
|
|
|
306
469
|
This example returns the events within the next week:
|
|
307
470
|
|
|
308
471
|
\b
|
|
309
|
-
ics-query between `date +%Y%m%d` +7d calendar.ics -
|
|
472
|
+
ics-query between --component VEVENT `date +%Y%m%d` +7d calendar.ics -
|
|
310
473
|
|
|
311
474
|
This example saves the events from the 1st of May 2024 to the 10th of June in
|
|
312
475
|
events.ics:
|
|
313
476
|
|
|
314
477
|
\b
|
|
315
|
-
ics-query between 2024-5-1 2024-6-10 calendar.ics events.ics
|
|
478
|
+
ics-query between --component VEVENT 2024-5-1 2024-6-10 calendar.ics events.ics
|
|
316
479
|
|
|
317
480
|
In this example, you can check what is happening on New Years Eve 2025 around
|
|
318
481
|
midnight:
|
|
@@ -472,4 +635,9 @@ def between(
|
|
|
472
635
|
output.add_components(calendar.between(start, end))
|
|
473
636
|
|
|
474
637
|
|
|
475
|
-
|
|
638
|
+
def main():
|
|
639
|
+
"""Run the program."""
|
|
640
|
+
cli(auto_envvar_prefix=ENV_PREFIX)
|
|
641
|
+
|
|
642
|
+
|
|
643
|
+
__all__ = ["main", "ENV_PREFIX", "cli"]
|
ics_query/query.py
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""This is an adaptation of the CalendarQuery."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import datetime
|
|
6
|
+
from typing import TYPE_CHECKING, Sequence
|
|
7
|
+
|
|
8
|
+
import x_wr_timezone
|
|
9
|
+
import zoneinfo
|
|
10
|
+
from recurring_ical_events import CalendarQuery, Occurrence
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from icalendar import Calendar
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Query(CalendarQuery):
|
|
17
|
+
def __init__(self, calendar: Calendar, timezone: str, components: Sequence[str]):
|
|
18
|
+
"""Create a new query."""
|
|
19
|
+
super().__init__(
|
|
20
|
+
x_wr_timezone.to_standard(calendar),
|
|
21
|
+
components=components,
|
|
22
|
+
skip_bad_series=True,
|
|
23
|
+
)
|
|
24
|
+
self.timezone = zoneinfo.ZoneInfo(timezone) if timezone else None
|
|
25
|
+
|
|
26
|
+
def with_timezone(self, dt: datetime.date | datetime.datetime):
|
|
27
|
+
"""Add the timezone."""
|
|
28
|
+
if self.timezone is None:
|
|
29
|
+
return dt
|
|
30
|
+
if not isinstance(dt, datetime.datetime):
|
|
31
|
+
return datetime.datetime(
|
|
32
|
+
year=dt.year, month=dt.month, day=dt.day, tzinfo=self.timezone
|
|
33
|
+
)
|
|
34
|
+
if dt.tzinfo is None:
|
|
35
|
+
return dt.replace(tzinfo=self.timezone)
|
|
36
|
+
return dt.astimezone(self.timezone)
|
|
37
|
+
|
|
38
|
+
def _occurrences_between(
|
|
39
|
+
self,
|
|
40
|
+
start: datetime.date | datetime.datetime,
|
|
41
|
+
end: datetime.date | datetime.datetime,
|
|
42
|
+
) -> list[Occurrence]:
|
|
43
|
+
"""Override to adapt timezones."""
|
|
44
|
+
result = []
|
|
45
|
+
for occurrence in super()._occurrences_between(
|
|
46
|
+
self.with_timezone(start), self.with_timezone(end)
|
|
47
|
+
):
|
|
48
|
+
occurrence.start = self.with_timezone(occurrence.start)
|
|
49
|
+
occurrence.end = self.with_timezone(occurrence.end)
|
|
50
|
+
result.append(occurrence)
|
|
51
|
+
return result
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
__all__ = ["Query"]
|
|
@@ -0,0 +1,33 @@
|
|
|
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:test4
|
|
14
|
+
DTSTART;TZID=Europe/Berlin:20190307T000000
|
|
15
|
+
DTEND;TZID=Europe/Berlin:20190307T010000
|
|
16
|
+
DTSTAMP:20190303T111937
|
|
17
|
+
UID:UYDQSG9TH4DE0WM3QFL2J
|
|
18
|
+
CLASS:PUBLIC
|
|
19
|
+
CREATED:20190303T111937
|
|
20
|
+
LAST-MODIFIED:20190303T111937
|
|
21
|
+
STATUS:CONFIRMED
|
|
22
|
+
END:VEVENT
|
|
23
|
+
BEGIN:VEVENT
|
|
24
|
+
SUMMARY:test4
|
|
25
|
+
DTSTART;TZID=Europe/Berlin:20190310T000000
|
|
26
|
+
DTEND;TZID=Europe/Berlin:20190310T010000
|
|
27
|
+
DTSTAMP:20190303T111937
|
|
28
|
+
UID:UYDQSG9TH4DE0WM3QFL2J
|
|
29
|
+
CLASS:PUBLIC
|
|
30
|
+
CREATED:20190303T111937
|
|
31
|
+
LAST-MODIFIED:20190303T111937
|
|
32
|
+
STATUS:CONFIRMED
|
|
33
|
+
END:VEVENT
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
BEGIN:VEVENT
|
|
2
|
+
SUMMARY:6:00-7:00 Europe/Berlin 20th August
|
|
3
|
+
DTSTART;TZID=Europe/Berlin:20240820T060000
|
|
4
|
+
DTEND;TZID=Europe/Berlin:20240820T070000
|
|
5
|
+
DTSTAMP:20240823T130444Z
|
|
6
|
+
UID:b27ea261-f23d-4e03-a7ac-f8cb0d00f07f
|
|
7
|
+
CREATED:20240823T120639Z
|
|
8
|
+
LAST-MODIFIED:20240823T130444Z
|
|
9
|
+
TRANSP:OPAQUE
|
|
10
|
+
X-MOZ-GENERATION:3
|
|
11
|
+
END:VEVENT
|
|
12
|
+
BEGIN:VEVENT
|
|
13
|
+
SUMMARY:6:00-7:00 Amerika/Los Angeles 20th August
|
|
14
|
+
DTSTART;TZID=America/Los_Angeles:20240820T060000
|
|
15
|
+
DTEND;TZID=America/Los_Angeles:20240820T070000
|
|
16
|
+
DTSTAMP:20240823T130711Z
|
|
17
|
+
UID:6d7ff7f3-4bc4-4d89-afa0-771bd690518a
|
|
18
|
+
SEQUENCE:1
|
|
19
|
+
CREATED:20240823T120639Z
|
|
20
|
+
LAST-MODIFIED:20240823T130711Z
|
|
21
|
+
TRANSP:OPAQUE
|
|
22
|
+
X-MOZ-GENERATION:4
|
|
23
|
+
END:VEVENT
|