ics-query 0.1.dev8__py3-none-any.whl → 0.3.1b0__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 +18 -2
- ics_query/__main__.py +15 -0
- ics_query/_version.py +2 -2
- ics_query/cli.py +618 -18
- ics_query/parse.py +78 -4
- ics_query/query.py +71 -0
- ics_query/tests/conftest.py +39 -12
- 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 2019-03-04 multiple-calendars.ics -.run +20 -0
- ics_query/tests/runs/at 2019-03-04 one-event-twice.ics -.run +18 -0
- ics_query/tests/runs/at 2019-03-07 multiple-calendars.ics -.run +11 -0
- ics_query/tests/runs/at 2024-08-20 Berlin-Los-Angeles.ics -.run +23 -0
- ics_query/tests/runs/between 20240823 4d recurring-work-events.ics -.run +24 -0
- ics_query/tests/runs/calendars/Berlin-Los-Angeles.ics +381 -0
- ics_query/tests/runs/calendars/empty-calendar.ics +7 -0
- ics_query/tests/runs/calendars/empty-file.ics +0 -0
- ics_query/tests/runs/calendars/multiple-calendars.ics +71 -0
- ics_query/tests/runs/calendars/one-event-twice.ics +68 -0
- ics_query/tests/runs/calendars/recurring-work-events.ics +223 -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/runs/first empty-calendar.ics -.run +0 -0
- ics_query/tests/runs/first empty-file.ics -.run +0 -0
- ics_query/tests/runs/first recurring-work-events.ics -.run +12 -0
- ics_query/tests/test_command_line.py +43 -0
- ics_query/tests/test_parse_date.py +81 -0
- ics_query/tests/test_parse_timedelta.py +40 -0
- ics_query/version.py +33 -3
- {ics_query-0.1.dev8.dist-info → ics_query-0.3.1b0.dist-info}/METADATA +393 -37
- ics_query-0.3.1b0.dist-info/RECORD +43 -0
- ics_query-0.1.dev8.dist-info/RECORD +0 -16
- {ics_query-0.1.dev8.dist-info → ics_query-0.3.1b0.dist-info}/WHEEL +0 -0
- {ics_query-0.1.dev8.dist-info → ics_query-0.3.1b0.dist-info}/entry_points.txt +0 -0
- {ics_query-0.1.dev8.dist-info → ics_query-0.3.1b0.dist-info}/licenses/LICENSE +0 -0
ics_query/parse.py
CHANGED
|
@@ -1,13 +1,87 @@
|
|
|
1
|
+
# ics-query
|
|
2
|
+
# Copyright (C) 2024 Nicco Kunzmann
|
|
3
|
+
#
|
|
4
|
+
# This program is free software: you can redistribute it and/or modify
|
|
5
|
+
# it under the terms of the GNU General Public License as published by
|
|
6
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
7
|
+
# (at your option) any later version.
|
|
8
|
+
#
|
|
9
|
+
# This program is distributed in the hope that it will be useful,
|
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
# GNU General Public License for more details.
|
|
13
|
+
#
|
|
14
|
+
# You should have received a copy of the GNU General Public License
|
|
15
|
+
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
1
16
|
"""Functions for parsing the content."""
|
|
2
17
|
|
|
3
18
|
from __future__ import annotations
|
|
4
19
|
|
|
5
|
-
|
|
20
|
+
import datetime
|
|
21
|
+
import re
|
|
22
|
+
from typing import Union
|
|
6
23
|
|
|
24
|
+
Date = tuple[int]
|
|
25
|
+
DateAndDelta = Union[Date, datetime.timedelta]
|
|
7
26
|
|
|
8
|
-
|
|
27
|
+
|
|
28
|
+
class InvalidTimeFormat(ValueError):
|
|
29
|
+
"""The value provided does not yield a precise time."""
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
REGEX_TIME = re.compile(
|
|
33
|
+
r"^(?P<year>\d\d\d\d)"
|
|
34
|
+
r"(?P<month>-\d?\d|\d\d)?"
|
|
35
|
+
r"(?P<day>-\d?\d|\d\d)?"
|
|
36
|
+
r"(?P<hour>[ T]\d?\d|\d\d)?"
|
|
37
|
+
r"(?P<minute>:\d?\d|\d\d)?"
|
|
38
|
+
r"(?P<second>:\d?\d|\d\d)?"
|
|
39
|
+
r"$"
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
REGEX_TIMEDELTA = re.compile(
|
|
43
|
+
r"^\+?(?:(?P<days>\d+)d)?"
|
|
44
|
+
r"(?:(?P<hours>\d+)h)?"
|
|
45
|
+
r"(?:(?P<minutes>\d+)m)?"
|
|
46
|
+
r"(?:(?P<seconds>\d+)s)?"
|
|
47
|
+
r"$"
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def to_time(dt: str) -> Date:
|
|
9
52
|
"""Parse the time and date."""
|
|
10
|
-
|
|
53
|
+
parsed_dt = REGEX_TIME.match(dt)
|
|
54
|
+
if parsed_dt is None:
|
|
55
|
+
raise InvalidTimeFormat(dt)
|
|
56
|
+
|
|
57
|
+
def group(group_name: str) -> Date:
|
|
58
|
+
"""Return a group's value."""
|
|
59
|
+
result = parsed_dt.group(group_name)
|
|
60
|
+
while result and result[0] not in "0123456789":
|
|
61
|
+
result = result[1:]
|
|
62
|
+
if result is None:
|
|
63
|
+
return ()
|
|
64
|
+
return (int(result),)
|
|
65
|
+
|
|
66
|
+
return (
|
|
67
|
+
group("year")
|
|
68
|
+
+ group("month")
|
|
69
|
+
+ group("day")
|
|
70
|
+
+ group("hour")
|
|
71
|
+
+ group("minute")
|
|
72
|
+
+ group("second")
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def to_time_and_delta(dt: str) -> DateAndDelta:
|
|
77
|
+
"""Parse to a absolute time or timedelta."""
|
|
78
|
+
parsed_td = REGEX_TIMEDELTA.match(dt)
|
|
79
|
+
if parsed_td is None:
|
|
80
|
+
return to_time(dt)
|
|
81
|
+
kw = {k: int(v) for k, v in parsed_td.groupdict().items() if v is not None}
|
|
82
|
+
if not kw:
|
|
83
|
+
raise InvalidTimeFormat(dt)
|
|
84
|
+
return datetime.timedelta(**kw)
|
|
11
85
|
|
|
12
86
|
|
|
13
|
-
__all__ = ["to_time", "
|
|
87
|
+
__all__ = ["to_time", "Date", "to_time_and_delta", "DateAndDelta"]
|
ics_query/query.py
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# ics-query
|
|
2
|
+
# Copyright (C) 2024 Nicco Kunzmann
|
|
3
|
+
#
|
|
4
|
+
# This program is free software: you can redistribute it and/or modify
|
|
5
|
+
# it under the terms of the GNU General Public License as published by
|
|
6
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
7
|
+
# (at your option) any later version.
|
|
8
|
+
#
|
|
9
|
+
# This program is distributed in the hope that it will be useful,
|
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
# GNU General Public License for more details.
|
|
13
|
+
#
|
|
14
|
+
# You should have received a copy of the GNU General Public License
|
|
15
|
+
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
16
|
+
"""This is an adaptation of the CalendarQuery."""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import datetime
|
|
21
|
+
import zoneinfo
|
|
22
|
+
from typing import TYPE_CHECKING
|
|
23
|
+
|
|
24
|
+
import x_wr_timezone
|
|
25
|
+
from recurring_ical_events import CalendarQuery, Occurrence
|
|
26
|
+
|
|
27
|
+
if TYPE_CHECKING:
|
|
28
|
+
from collections.abc import Sequence
|
|
29
|
+
|
|
30
|
+
from icalendar import Calendar
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class Query(CalendarQuery):
|
|
34
|
+
def __init__(self, calendar: Calendar, timezone: str, components: Sequence[str]):
|
|
35
|
+
"""Create a new query."""
|
|
36
|
+
super().__init__(
|
|
37
|
+
x_wr_timezone.to_standard(calendar),
|
|
38
|
+
components=components,
|
|
39
|
+
skip_bad_series=True,
|
|
40
|
+
)
|
|
41
|
+
self.timezone = zoneinfo.ZoneInfo(timezone) if timezone else None
|
|
42
|
+
|
|
43
|
+
def with_timezone(self, dt: datetime.date | datetime.datetime):
|
|
44
|
+
"""Add the timezone."""
|
|
45
|
+
if self.timezone is None:
|
|
46
|
+
return dt
|
|
47
|
+
if not isinstance(dt, datetime.datetime):
|
|
48
|
+
return datetime.datetime(
|
|
49
|
+
year=dt.year, month=dt.month, day=dt.day, tzinfo=self.timezone
|
|
50
|
+
)
|
|
51
|
+
if dt.tzinfo is None:
|
|
52
|
+
return dt.replace(tzinfo=self.timezone)
|
|
53
|
+
return dt.astimezone(self.timezone)
|
|
54
|
+
|
|
55
|
+
def _occurrences_between(
|
|
56
|
+
self,
|
|
57
|
+
start: datetime.date | datetime.datetime,
|
|
58
|
+
end: datetime.date | datetime.datetime,
|
|
59
|
+
) -> list[Occurrence]:
|
|
60
|
+
"""Override to adapt timezones."""
|
|
61
|
+
result = []
|
|
62
|
+
for occurrence in super()._occurrences_between(
|
|
63
|
+
self.with_timezone(start), self.with_timezone(end)
|
|
64
|
+
):
|
|
65
|
+
occurrence.start = self.with_timezone(occurrence.start)
|
|
66
|
+
occurrence.end = self.with_timezone(occurrence.end)
|
|
67
|
+
result.append(occurrence)
|
|
68
|
+
return result
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
__all__ = ["Query"]
|
ics_query/tests/conftest.py
CHANGED
|
@@ -1,3 +1,18 @@
|
|
|
1
|
+
# ics-query
|
|
2
|
+
# Copyright (C) 2024 Nicco Kunzmann
|
|
3
|
+
#
|
|
4
|
+
# This program is free software: you can redistribute it and/or modify
|
|
5
|
+
# it under the terms of the GNU General Public License as published by
|
|
6
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
7
|
+
# (at your option) any later version.
|
|
8
|
+
#
|
|
9
|
+
# This program is distributed in the hope that it will be useful,
|
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
# GNU General Public License for more details.
|
|
13
|
+
#
|
|
14
|
+
# You should have received a copy of the GNU General Public License
|
|
15
|
+
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
1
16
|
"""Configure the tests."""
|
|
2
17
|
|
|
3
18
|
from __future__ import annotations
|
|
@@ -5,12 +20,13 @@ from __future__ import annotations
|
|
|
5
20
|
import subprocess
|
|
6
21
|
from copy import deepcopy
|
|
7
22
|
from pathlib import Path
|
|
8
|
-
from typing import NamedTuple
|
|
23
|
+
from typing import Callable, NamedTuple
|
|
9
24
|
|
|
10
25
|
import pytest
|
|
11
26
|
|
|
12
27
|
HERE = Path(__file__).parent
|
|
13
28
|
IO_DIRECTORY = HERE / "runs"
|
|
29
|
+
CALENDARS_DIRECTORY = IO_DIRECTORY / "calendars"
|
|
14
30
|
|
|
15
31
|
|
|
16
32
|
class TestRun(NamedTuple):
|
|
@@ -34,12 +50,26 @@ class TestRun(NamedTuple):
|
|
|
34
50
|
)
|
|
35
51
|
|
|
36
52
|
|
|
53
|
+
def run_ics_query(*command, cwd=CALENDARS_DIRECTORY) -> TestRun:
|
|
54
|
+
"""Run ics-qeury with a command."""
|
|
55
|
+
cmd = ["ics-query", *command]
|
|
56
|
+
print(" ".join(cmd))
|
|
57
|
+
completed_process = subprocess.run( # noqa: S603, RUF100
|
|
58
|
+
cmd, # noqa: S603, RUF100
|
|
59
|
+
capture_output=True,
|
|
60
|
+
timeout=3,
|
|
61
|
+
check=False,
|
|
62
|
+
cwd=cwd,
|
|
63
|
+
)
|
|
64
|
+
return TestRun.from_completed_process(completed_process)
|
|
65
|
+
|
|
66
|
+
|
|
37
67
|
class IOTestCase(NamedTuple):
|
|
38
68
|
"""An example test case."""
|
|
39
69
|
|
|
40
70
|
name: str
|
|
41
71
|
command: list[str]
|
|
42
|
-
|
|
72
|
+
location: Path
|
|
43
73
|
expected_output: str
|
|
44
74
|
|
|
45
75
|
@classmethod
|
|
@@ -50,16 +80,7 @@ class IOTestCase(NamedTuple):
|
|
|
50
80
|
|
|
51
81
|
def run(self) -> TestRun:
|
|
52
82
|
"""Run this test case and return the result."""
|
|
53
|
-
|
|
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)
|
|
83
|
+
return run_ics_query(*self.command)
|
|
63
84
|
|
|
64
85
|
|
|
65
86
|
io_test_cases = [
|
|
@@ -75,4 +96,10 @@ def io_testcase(request) -> IOTestCase:
|
|
|
75
96
|
return deepcopy(request.param)
|
|
76
97
|
|
|
77
98
|
|
|
99
|
+
@pytest.fixture
|
|
100
|
+
def run() -> Callable[..., TestRun]:
|
|
101
|
+
"""Return a runner function."""
|
|
102
|
+
return run_ics_query
|
|
103
|
+
|
|
104
|
+
|
|
78
105
|
__all__ = ["IOTestCase", "TestRun"]
|
|
@@ -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,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,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
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
BEGIN:VEVENT
|
|
2
|
+
SUMMARY:Work
|
|
3
|
+
DTSTART;TZID=Europe/Berlin:20240823T090000
|
|
4
|
+
DTEND;TZID=Europe/Berlin:20240823T170000
|
|
5
|
+
DTSTAMP:20240823T082915Z
|
|
6
|
+
UID:6b85b60c-eb1a-4338-9ece-33541b95bf17
|
|
7
|
+
SEQUENCE:1
|
|
8
|
+
CREATED:20240823T082829Z
|
|
9
|
+
LAST-MODIFIED:20240823T082915Z
|
|
10
|
+
TRANSP:OPAQUE
|
|
11
|
+
X-MOZ-GENERATION:2
|
|
12
|
+
END:VEVENT
|
|
13
|
+
BEGIN:VEVENT
|
|
14
|
+
SUMMARY:Work
|
|
15
|
+
DTSTART;TZID=Europe/Berlin:20240826T090000
|
|
16
|
+
DTEND;TZID=Europe/Berlin:20240826T170000
|
|
17
|
+
DTSTAMP:20240823T082915Z
|
|
18
|
+
UID:6b85b60c-eb1a-4338-9ece-33541b95bf17
|
|
19
|
+
SEQUENCE:1
|
|
20
|
+
CREATED:20240823T082829Z
|
|
21
|
+
LAST-MODIFIED:20240823T082915Z
|
|
22
|
+
TRANSP:OPAQUE
|
|
23
|
+
X-MOZ-GENERATION:2
|
|
24
|
+
END:VEVENT
|