We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Line breaks in location field of ICS file needs to be converted
### Problem and impact

When I insert an address with multiple lines in the event location field, the linebreak `\n` in the downloaded ICS file gets ignored by the calendar apps and the address is written without any space.
In most apps the location field is only one line.

If I insert the following address in the event location:
```
Party Location
Partystreet 4
12345 Party
```
the address in Outlook or Thunderbird looks like this:
```
Party LocationPartystreet 412345 Party
```
So the apps can't find this address in a navigation app.

E.g. Google replaces `\n` with `\, ` in their ICS files.

### Expected behaviour

I insert the following address in the event location:
```
Party Location
Partystreet 4
12345 Party
```
so I expect the address in Outlook or Thunderbird of the ICS file looks like this:
```
Party Location, Partystreet 4, 12345 Party
```

### Steps to reproduce

1. Insert event location with multiple lines
2. Download ICS file of event
3. Open ICS file in calendar app (e.g. Outlook or Thunderbird)

### Screenshots

_No response_

### Link

_No response_

### Browser (software, desktop or mobile?) and version

_No response_

### Operating system, dependency versions

_No response_

### Version

4.15.0

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `src/pretix/presale/ical.py`
Content:
```
1 #
2 # This file is part of pretix (Community Edition).
3 #
4 # Copyright (C) 2014-2020 Raphael Michel and contributors
5 # Copyright (C) 2020-2021 rami.io GmbH and contributors
6 #
7 # This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
8 # Public License as published by the Free Software Foundation in version 3 of the License.
9 #
10 # ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are
11 # applicable granting you additional permissions and placing additional restrictions on your usage of this software.
12 # Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive
13 # this file, see <https://pretix.eu/about/en/license>.
14 #
15 # This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
16 # warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more
17 # details.
18 #
19 # You should have received a copy of the GNU Affero General Public License along with this program.  If not, see
20 # <https://www.gnu.org/licenses/>.
21 #
22 import datetime
23 from urllib.parse import urlparse
24 
25 import pytz
26 import vobject
27 from django.conf import settings
28 from django.utils.formats import date_format
29 from django.utils.translation import gettext as _
30 
31 from pretix.base.email import get_email_context
32 from pretix.base.models import Event
33 from pretix.helpers.format import format_map
34 from pretix.multidomain.urlreverse import build_absolute_uri
35 
36 
37 def get_public_ical(events):
38     """
39     Return an ical feed for a sequence of events or subevents. The calendar files will only include public
40     information.
41     """
42     cal = vobject.iCalendar()
43     cal.add('prodid').value = '-//pretix//{}//'.format(settings.PRETIX_INSTANCE_NAME.replace(" ", "_"))
44     creation_time = datetime.datetime.now(pytz.utc)
45 
46     for ev in events:
47         event = ev if isinstance(ev, Event) else ev.event
48         tz = pytz.timezone(event.settings.timezone)
49         if isinstance(ev, Event):
50             url = build_absolute_uri(event, 'presale:event.index')
51         else:
52             url = build_absolute_uri(event, 'presale:event.index', {
53                 'subevent': ev.pk
54             })
55 
56         vevent = cal.add('vevent')
57         vevent.add('summary').value = str(ev.name)
58         vevent.add('dtstamp').value = creation_time
59         if ev.location:
60             vevent.add('location').value = str(ev.location)
61         vevent.add('uid').value = 'pretix-{}-{}-{}@{}'.format(
62             event.organizer.slug, event.slug,
63             ev.pk if not isinstance(ev, Event) else '0',
64             urlparse(url).netloc
65         )
66 
67         if event.settings.show_times:
68             vevent.add('dtstart').value = ev.date_from.astimezone(tz)
69         else:
70             vevent.add('dtstart').value = ev.date_from.astimezone(tz).date()
71 
72         if event.settings.show_date_to and ev.date_to:
73             if event.settings.show_times:
74                 vevent.add('dtend').value = ev.date_to.astimezone(tz)
75             else:
76                 # with full-day events date_to in pretix is included (e.g. last day)
77                 # whereas dtend in vcalendar is non-inclusive => add one day for export
78                 vevent.add('dtend').value = ev.date_to.astimezone(tz).date() + datetime.timedelta(days=1)
79 
80         descr = []
81         descr.append(_('Tickets: {url}').format(url=url))
82 
83         if ev.date_admission:
84             descr.append(str(_('Admission: {datetime}')).format(
85                 datetime=date_format(ev.date_admission.astimezone(tz), 'SHORT_DATETIME_FORMAT')
86             ))
87 
88         descr.append(_('Organizer: {organizer}').format(organizer=event.organizer.name))
89 
90         vevent.add('description').value = '\n'.join(descr)
91     return cal
92 
93 
94 def get_private_icals(event, positions):
95     """
96     Return a list of ical objects based on a sequence of positions.
97 
98     Unlike get_public_ical, this will
99 
100     - Generate multiple ical files instead of one (but with deduplication applied)
101     - Respect the mail_attach_ical_description setting
102 
103     It is private in the sense that mail_attach_ical_description may contain content not suited for
104     public display.
105 
106     We however intentionally do not allow using placeholders based on the order and position
107     specifically. This is for two reasons:
108 
109     - In reality, many people will add their invite to their calendar which is shared with a larger
110       team. People are probably not aware that they're sharing sensitive information such as their
111       secret ticket link with everyone they share their calendar with.
112 
113     - It would be pretty hard to implement it in a way that doesn't require us to use distinct
114       settings fields for emails to customers and to attendees, which feels like an overcomplication.
115     """
116     tz = pytz.timezone(event.settings.timezone)
117 
118     creation_time = datetime.datetime.now(pytz.utc)
119     calobjects = []
120 
121     evs = set(p.subevent or event for p in positions)
122     for ev in evs:
123         if isinstance(ev, Event):
124             url = build_absolute_uri(event, 'presale:event.index')
125         else:
126             url = build_absolute_uri(event, 'presale:event.index', {
127                 'subevent': ev.pk
128             })
129 
130         if event.settings.mail_attach_ical_description:
131             ctx = get_email_context(event=event, event_or_subevent=ev)
132             description = format_map(str(event.settings.mail_attach_ical_description), ctx)
133         else:
134             # Default description
135             descr = []
136             descr.append(_('Tickets: {url}').format(url=url))
137             if ev.date_admission:
138                 descr.append(str(_('Admission: {datetime}')).format(
139                     datetime=date_format(ev.date_admission.astimezone(tz), 'SHORT_DATETIME_FORMAT')
140                 ))
141 
142             descr.append(_('Organizer: {organizer}').format(organizer=event.organizer.name))
143             description = '\n'.join(descr)
144 
145         cal = vobject.iCalendar()
146         cal.add('prodid').value = '-//pretix//{}//'.format(settings.PRETIX_INSTANCE_NAME.replace(" ", "_"))
147 
148         vevent = cal.add('vevent')
149         vevent.add('summary').value = str(ev.name)
150         vevent.add('description').value = description
151         vevent.add('dtstamp').value = creation_time
152         if ev.location:
153             vevent.add('location').value = str(ev.location)
154 
155         vevent.add('uid').value = 'pretix-{}-{}-{}@{}'.format(
156             event.organizer.slug,
157             event.slug,
158             ev.pk if not isinstance(ev, Event) else '0',
159             urlparse(url).netloc
160         )
161 
162         if event.settings.show_times:
163             vevent.add('dtstart').value = ev.date_from.astimezone(tz)
164         else:
165             vevent.add('dtstart').value = ev.date_from.astimezone(tz).date()
166 
167         if event.settings.show_date_to and ev.date_to:
168             if event.settings.show_times:
169                 vevent.add('dtend').value = ev.date_to.astimezone(tz)
170             else:
171                 # with full-day events date_to in pretix is included (e.g. last day)
172                 # whereas dtend in vcalendar is non-inclusive => add one day for export
173                 vevent.add('dtend').value = ev.date_to.astimezone(tz).date() + datetime.timedelta(days=1)
174 
175         calobjects.append(cal)
176     return calobjects
177
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/src/pretix/presale/ical.py b/src/pretix/presale/ical.py
--- a/src/pretix/presale/ical.py
+++ b/src/pretix/presale/ical.py
@@ -57,7 +57,7 @@
         vevent.add('summary').value = str(ev.name)
         vevent.add('dtstamp').value = creation_time
         if ev.location:
-            vevent.add('location').value = str(ev.location)
+            vevent.add('location').value = ", ".join(l.strip() for l in str(ev.location).splitlines() if l.strip())
         vevent.add('uid').value = 'pretix-{}-{}-{}@{}'.format(
             event.organizer.slug, event.slug,
             ev.pk if not isinstance(ev, Event) else '0',

{"golden_diff": "diff --git a/src/pretix/presale/ical.py b/src/pretix/presale/ical.py\n--- a/src/pretix/presale/ical.py\n+++ b/src/pretix/presale/ical.py\n@@ -57,7 +57,7 @@\n         vevent.add('summary').value = str(ev.name)\n         vevent.add('dtstamp').value = creation_time\n         if ev.location:\n-            vevent.add('location').value = str(ev.location)\n+            vevent.add('location').value = \", \".join(l.strip() for l in str(ev.location).splitlines() if l.strip())\n         vevent.add('uid').value = 'pretix-{}-{}-{}@{}'.format(\n             event.organizer.slug, event.slug,\n             ev.pk if not isinstance(ev, Event) else '0',\n", "issue": "Line breaks in location field of ICS file needs to be converted\n### Problem and impact\n\nWhen I insert an address with multiple lines in the event location field, the linebreak `\\n` in the downloaded ICS file gets ignored by the calendar apps and the address is written without any space.\r\nIn most apps the location field is only one line.\r\n\r\nIf I insert the following address in the event location:\r\n```\r\nParty Location\r\nPartystreet 4\r\n12345 Party\r\n```\r\nthe address in Outlook or Thunderbird looks like this:\r\n```\r\nParty LocationPartystreet 412345 Party\r\n```\r\nSo the apps can't find this address in a navigation app.\r\n\r\nE.g. Google replaces `\\n` with `\\, ` in their ICS files.\n\n### Expected behaviour\n\nI insert the following address in the event location:\r\n```\r\nParty Location\r\nPartystreet 4\r\n12345 Party\r\n```\r\nso I expect the address in Outlook or Thunderbird of the ICS file looks like this:\r\n```\r\nParty Location, Partystreet 4, 12345 Party\r\n```\n\n### Steps to reproduce\n\n1. Insert event location with multiple lines\r\n2. Download ICS file of event\r\n3. Open ICS file in calendar app (e.g. Outlook or Thunderbird)\n\n### Screenshots\n\n_No response_\n\n### Link\n\n_No response_\n\n### Browser (software, desktop or mobile?) and version\n\n_No response_\n\n### Operating system, dependency versions\n\n_No response_\n\n### Version\n\n4.15.0\n", "before_files": [{"content": "#\n# This file is part of pretix (Community Edition).\n#\n# Copyright (C) 2014-2020 Raphael Michel and contributors\n# Copyright (C) 2020-2021 rami.io GmbH and contributors\n#\n# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General\n# Public License as published by the Free Software Foundation in version 3 of the License.\n#\n# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are\n# applicable granting you additional permissions and placing additional restrictions on your usage of this software.\n# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive\n# this file, see <https://pretix.eu/about/en/license>.\n#\n# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied\n# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more\n# details.\n#\n# You should have received a copy of the GNU Affero General Public License along with this program.  If not, see\n# <https://www.gnu.org/licenses/>.\n#\nimport datetime\nfrom urllib.parse import urlparse\n\nimport pytz\nimport vobject\nfrom django.conf import settings\nfrom django.utils.formats import date_format\nfrom django.utils.translation import gettext as _\n\nfrom pretix.base.email import get_email_context\nfrom pretix.base.models import Event\nfrom pretix.helpers.format import format_map\nfrom pretix.multidomain.urlreverse import build_absolute_uri\n\n\ndef get_public_ical(events):\n    \"\"\"\n    Return an ical feed for a sequence of events or subevents. The calendar files will only include public\n    information.\n    \"\"\"\n    cal = vobject.iCalendar()\n    cal.add('prodid').value = '-//pretix//{}//'.format(settings.PRETIX_INSTANCE_NAME.replace(\" \", \"_\"))\n    creation_time = datetime.datetime.now(pytz.utc)\n\n    for ev in events:\n        event = ev if isinstance(ev, Event) else ev.event\n        tz = pytz.timezone(event.settings.timezone)\n        if isinstance(ev, Event):\n            url = build_absolute_uri(event, 'presale:event.index')\n        else:\n            url = build_absolute_uri(event, 'presale:event.index', {\n                'subevent': ev.pk\n            })\n\n        vevent = cal.add('vevent')\n        vevent.add('summary').value = str(ev.name)\n        vevent.add('dtstamp').value = creation_time\n        if ev.location:\n            vevent.add('location').value = str(ev.location)\n        vevent.add('uid').value = 'pretix-{}-{}-{}@{}'.format(\n            event.organizer.slug, event.slug,\n            ev.pk if not isinstance(ev, Event) else '0',\n            urlparse(url).netloc\n        )\n\n        if event.settings.show_times:\n            vevent.add('dtstart').value = ev.date_from.astimezone(tz)\n        else:\n            vevent.add('dtstart').value = ev.date_from.astimezone(tz).date()\n\n        if event.settings.show_date_to and ev.date_to:\n            if event.settings.show_times:\n                vevent.add('dtend').value = ev.date_to.astimezone(tz)\n            else:\n                # with full-day events date_to in pretix is included (e.g. last day)\n                # whereas dtend in vcalendar is non-inclusive => add one day for export\n                vevent.add('dtend').value = ev.date_to.astimezone(tz).date() + datetime.timedelta(days=1)\n\n        descr = []\n        descr.append(_('Tickets: {url}').format(url=url))\n\n        if ev.date_admission:\n            descr.append(str(_('Admission: {datetime}')).format(\n                datetime=date_format(ev.date_admission.astimezone(tz), 'SHORT_DATETIME_FORMAT')\n            ))\n\n        descr.append(_('Organizer: {organizer}').format(organizer=event.organizer.name))\n\n        vevent.add('description').value = '\\n'.join(descr)\n    return cal\n\n\ndef get_private_icals(event, positions):\n    \"\"\"\n    Return a list of ical objects based on a sequence of positions.\n\n    Unlike get_public_ical, this will\n\n    - Generate multiple ical files instead of one (but with deduplication applied)\n    - Respect the mail_attach_ical_description setting\n\n    It is private in the sense that mail_attach_ical_description may contain content not suited for\n    public display.\n\n    We however intentionally do not allow using placeholders based on the order and position\n    specifically. This is for two reasons:\n\n    - In reality, many people will add their invite to their calendar which is shared with a larger\n      team. People are probably not aware that they're sharing sensitive information such as their\n      secret ticket link with everyone they share their calendar with.\n\n    - It would be pretty hard to implement it in a way that doesn't require us to use distinct\n      settings fields for emails to customers and to attendees, which feels like an overcomplication.\n    \"\"\"\n    tz = pytz.timezone(event.settings.timezone)\n\n    creation_time = datetime.datetime.now(pytz.utc)\n    calobjects = []\n\n    evs = set(p.subevent or event for p in positions)\n    for ev in evs:\n        if isinstance(ev, Event):\n            url = build_absolute_uri(event, 'presale:event.index')\n        else:\n            url = build_absolute_uri(event, 'presale:event.index', {\n                'subevent': ev.pk\n            })\n\n        if event.settings.mail_attach_ical_description:\n            ctx = get_email_context(event=event, event_or_subevent=ev)\n            description = format_map(str(event.settings.mail_attach_ical_description), ctx)\n        else:\n            # Default description\n            descr = []\n            descr.append(_('Tickets: {url}').format(url=url))\n            if ev.date_admission:\n                descr.append(str(_('Admission: {datetime}')).format(\n                    datetime=date_format(ev.date_admission.astimezone(tz), 'SHORT_DATETIME_FORMAT')\n                ))\n\n            descr.append(_('Organizer: {organizer}').format(organizer=event.organizer.name))\n            description = '\\n'.join(descr)\n\n        cal = vobject.iCalendar()\n        cal.add('prodid').value = '-//pretix//{}//'.format(settings.PRETIX_INSTANCE_NAME.replace(\" \", \"_\"))\n\n        vevent = cal.add('vevent')\n        vevent.add('summary').value = str(ev.name)\n        vevent.add('description').value = description\n        vevent.add('dtstamp').value = creation_time\n        if ev.location:\n            vevent.add('location').value = str(ev.location)\n\n        vevent.add('uid').value = 'pretix-{}-{}-{}@{}'.format(\n            event.organizer.slug,\n            event.slug,\n            ev.pk if not isinstance(ev, Event) else '0',\n            urlparse(url).netloc\n        )\n\n        if event.settings.show_times:\n            vevent.add('dtstart').value = ev.date_from.astimezone(tz)\n        else:\n            vevent.add('dtstart').value = ev.date_from.astimezone(tz).date()\n\n        if event.settings.show_date_to and ev.date_to:\n            if event.settings.show_times:\n                vevent.add('dtend').value = ev.date_to.astimezone(tz)\n            else:\n                # with full-day events date_to in pretix is included (e.g. last day)\n                # whereas dtend in vcalendar is non-inclusive => add one day for export\n                vevent.add('dtend').value = ev.date_to.astimezone(tz).date() + datetime.timedelta(days=1)\n\n        calobjects.append(cal)\n    return calobjects\n", "path": "src/pretix/presale/ical.py"}], "after_files": [{"content": "#\n# This file is part of pretix (Community Edition).\n#\n# Copyright (C) 2014-2020 Raphael Michel and contributors\n# Copyright (C) 2020-2021 rami.io GmbH and contributors\n#\n# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General\n# Public License as published by the Free Software Foundation in version 3 of the License.\n#\n# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are\n# applicable granting you additional permissions and placing additional restrictions on your usage of this software.\n# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive\n# this file, see <https://pretix.eu/about/en/license>.\n#\n# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied\n# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more\n# details.\n#\n# You should have received a copy of the GNU Affero General Public License along with this program.  If not, see\n# <https://www.gnu.org/licenses/>.\n#\nimport datetime\nfrom urllib.parse import urlparse\n\nimport pytz\nimport vobject\nfrom django.conf import settings\nfrom django.utils.formats import date_format\nfrom django.utils.translation import gettext as _\n\nfrom pretix.base.email import get_email_context\nfrom pretix.base.models import Event\nfrom pretix.helpers.format import format_map\nfrom pretix.multidomain.urlreverse import build_absolute_uri\n\n\ndef get_public_ical(events):\n    \"\"\"\n    Return an ical feed for a sequence of events or subevents. The calendar files will only include public\n    information.\n    \"\"\"\n    cal = vobject.iCalendar()\n    cal.add('prodid').value = '-//pretix//{}//'.format(settings.PRETIX_INSTANCE_NAME.replace(\" \", \"_\"))\n    creation_time = datetime.datetime.now(pytz.utc)\n\n    for ev in events:\n        event = ev if isinstance(ev, Event) else ev.event\n        tz = pytz.timezone(event.settings.timezone)\n        if isinstance(ev, Event):\n            url = build_absolute_uri(event, 'presale:event.index')\n        else:\n            url = build_absolute_uri(event, 'presale:event.index', {\n                'subevent': ev.pk\n            })\n\n        vevent = cal.add('vevent')\n        vevent.add('summary').value = str(ev.name)\n        vevent.add('dtstamp').value = creation_time\n        if ev.location:\n            vevent.add('location').value = \", \".join(l.strip() for l in str(ev.location).splitlines() if l.strip())\n        vevent.add('uid').value = 'pretix-{}-{}-{}@{}'.format(\n            event.organizer.slug, event.slug,\n            ev.pk if not isinstance(ev, Event) else '0',\n            urlparse(url).netloc\n        )\n\n        if event.settings.show_times:\n            vevent.add('dtstart').value = ev.date_from.astimezone(tz)\n        else:\n            vevent.add('dtstart').value = ev.date_from.astimezone(tz).date()\n\n        if event.settings.show_date_to and ev.date_to:\n            if event.settings.show_times:\n                vevent.add('dtend').value = ev.date_to.astimezone(tz)\n            else:\n                # with full-day events date_to in pretix is included (e.g. last day)\n                # whereas dtend in vcalendar is non-inclusive => add one day for export\n                vevent.add('dtend').value = ev.date_to.astimezone(tz).date() + datetime.timedelta(days=1)\n\n        descr = []\n        descr.append(_('Tickets: {url}').format(url=url))\n\n        if ev.date_admission:\n            descr.append(str(_('Admission: {datetime}')).format(\n                datetime=date_format(ev.date_admission.astimezone(tz), 'SHORT_DATETIME_FORMAT')\n            ))\n\n        descr.append(_('Organizer: {organizer}').format(organizer=event.organizer.name))\n\n        vevent.add('description').value = '\\n'.join(descr)\n    return cal\n\n\ndef get_private_icals(event, positions):\n    \"\"\"\n    Return a list of ical objects based on a sequence of positions.\n\n    Unlike get_public_ical, this will\n\n    - Generate multiple ical files instead of one (but with deduplication applied)\n    - Respect the mail_attach_ical_description setting\n\n    It is private in the sense that mail_attach_ical_description may contain content not suited for\n    public display.\n\n    We however intentionally do not allow using placeholders based on the order and position\n    specifically. This is for two reasons:\n\n    - In reality, many people will add their invite to their calendar which is shared with a larger\n      team. People are probably not aware that they're sharing sensitive information such as their\n      secret ticket link with everyone they share their calendar with.\n\n    - It would be pretty hard to implement it in a way that doesn't require us to use distinct\n      settings fields for emails to customers and to attendees, which feels like an overcomplication.\n    \"\"\"\n    tz = pytz.timezone(event.settings.timezone)\n\n    creation_time = datetime.datetime.now(pytz.utc)\n    calobjects = []\n\n    evs = set(p.subevent or event for p in positions)\n    for ev in evs:\n        if isinstance(ev, Event):\n            url = build_absolute_uri(event, 'presale:event.index')\n        else:\n            url = build_absolute_uri(event, 'presale:event.index', {\n                'subevent': ev.pk\n            })\n\n        if event.settings.mail_attach_ical_description:\n            ctx = get_email_context(event=event, event_or_subevent=ev)\n            description = format_map(str(event.settings.mail_attach_ical_description), ctx)\n        else:\n            # Default description\n            descr = []\n            descr.append(_('Tickets: {url}').format(url=url))\n            if ev.date_admission:\n                descr.append(str(_('Admission: {datetime}')).format(\n                    datetime=date_format(ev.date_admission.astimezone(tz), 'SHORT_DATETIME_FORMAT')\n                ))\n\n            descr.append(_('Organizer: {organizer}').format(organizer=event.organizer.name))\n            description = '\\n'.join(descr)\n\n        cal = vobject.iCalendar()\n        cal.add('prodid').value = '-//pretix//{}//'.format(settings.PRETIX_INSTANCE_NAME.replace(\" \", \"_\"))\n\n        vevent = cal.add('vevent')\n        vevent.add('summary').value = str(ev.name)\n        vevent.add('description').value = description\n        vevent.add('dtstamp').value = creation_time\n        if ev.location:\n            vevent.add('location').value = str(ev.location)\n\n        vevent.add('uid').value = 'pretix-{}-{}-{}@{}'.format(\n            event.organizer.slug,\n            event.slug,\n            ev.pk if not isinstance(ev, Event) else '0',\n            urlparse(url).netloc\n        )\n\n        if event.settings.show_times:\n            vevent.add('dtstart').value = ev.date_from.astimezone(tz)\n        else:\n            vevent.add('dtstart').value = ev.date_from.astimezone(tz).date()\n\n        if event.settings.show_date_to and ev.date_to:\n            if event.settings.show_times:\n                vevent.add('dtend').value = ev.date_to.astimezone(tz)\n            else:\n                # with full-day events date_to in pretix is included (e.g. last day)\n                # whereas dtend in vcalendar is non-inclusive => add one day for export\n                vevent.add('dtend').value = ev.date_to.astimezone(tz).date() + datetime.timedelta(days=1)\n\n        calobjects.append(cal)\n    return calobjects\n", "path": "src/pretix/presale/ical.py"}]}