Compare commits
2 commits
Author | SHA1 | Date | |
---|---|---|---|
1647ddef77 | |||
317f32565b |
21 changed files with 2919 additions and 741 deletions
194
.gitignore
vendored
194
.gitignore
vendored
|
@ -1,134 +1,88 @@
|
|||
.idea/
|
||||
__pycache__/
|
||||
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
pip-wheel-metadata/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py,cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
debug/
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
# These are backup files generated by rustfmt
|
||||
**/*.rs.bk
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
# MSVC Windows builds of rustc generate these, which store debugging information
|
||||
*.pdb
|
||||
|
||||
# pyenv
|
||||
.python-version
|
||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
#Pipfile.lock
|
||||
# User-specific stuff
|
||||
.idea/**/workspace.xml
|
||||
.idea/**/tasks.xml
|
||||
.idea/**/usage.statistics.xml
|
||||
.idea/**/dictionaries
|
||||
.idea/**/shelf
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
|
||||
__pypackages__/
|
||||
# AWS User-specific
|
||||
.idea/**/aws.xml
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
# Generated files
|
||||
.idea/**/contentModel.xml
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
# Sensitive or high-churn files
|
||||
.idea/**/dataSources/
|
||||
.idea/**/dataSources.ids
|
||||
.idea/**/dataSources.local.xml
|
||||
.idea/**/sqlDataSources.xml
|
||||
.idea/**/dynamic.xml
|
||||
.idea/**/uiDesigner.xml
|
||||
.idea/**/dbnavigator.xml
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
# Gradle
|
||||
.idea/**/gradle.xml
|
||||
.idea/**/libraries
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
# Gradle and Maven with auto-import
|
||||
# When using Gradle or Maven with auto-import, you should exclude module files,
|
||||
# since they will be recreated, and may cause churn. Uncomment if using
|
||||
# auto-import.
|
||||
# .idea/artifacts
|
||||
# .idea/compiler.xml
|
||||
# .idea/jarRepositories.xml
|
||||
# .idea/modules.xml
|
||||
# .idea/*.iml
|
||||
# .idea/modules
|
||||
# *.iml
|
||||
# *.ipr
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
# CMake
|
||||
cmake-build-*/
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
# Mongo Explorer plugin
|
||||
.idea/**/mongoSettings.xml
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
# File-based project format
|
||||
*.iws
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
app/config/calendar.json
|
||||
/app/cache/
|
||||
# IntelliJ
|
||||
out/
|
||||
|
||||
# mpeltonen/sbt-idea plugin
|
||||
.idea_modules/
|
||||
|
||||
# JIRA plugin
|
||||
atlassian-ide-plugin.xml
|
||||
|
||||
# Cursive Clojure plugin
|
||||
.idea/replstate.xml
|
||||
|
||||
# SonarLint plugin
|
||||
.idea/sonarlint/
|
||||
|
||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||
com_crashlytics_export_strings.xml
|
||||
crashlytics.properties
|
||||
crashlytics-build.properties
|
||||
fabric.properties
|
||||
|
||||
# Editor-based Rest Client
|
||||
.idea/httpRequests
|
||||
|
||||
# Android studio 3.1+ serialized cache file
|
||||
.idea/caches/build_file_checksums.ser
|
||||
|
|
8
.idea/.gitignore
generated
vendored
Normal file
8
.idea/.gitignore
generated
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
17
.idea/dataSources.xml
generated
Normal file
17
.idea/dataSources.xml
generated
Normal file
|
@ -0,0 +1,17 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
|
||||
<data-source source="LOCAL" name="test" uuid="2de274d0-0fd4-4132-93aa-2af5c71e03cf">
|
||||
<driver-ref>sqlite.xerial</driver-ref>
|
||||
<synchronize>true</synchronize>
|
||||
<jdbc-driver>org.sqlite.JDBC</jdbc-driver>
|
||||
<jdbc-url>jdbc:sqlite:C:\Users\Jules\GitHub\ics-fusion\test.db</jdbc-url>
|
||||
<working-dir>$ProjectFileDir$</working-dir>
|
||||
<libraries>
|
||||
<library>
|
||||
<url>file://$APPLICATION_CONFIG_DIR$/jdbc-drivers/Xerial SQLiteJDBC/3.39.2/sqlite-jdbc-3.39.2.jar</url>
|
||||
</library>
|
||||
</libraries>
|
||||
</data-source>
|
||||
</component>
|
||||
</project>
|
13
.idea/ics-fusion.iml
generated
Normal file
13
.idea/ics-fusion.iml
generated
Normal file
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/resources/test" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/target" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
9
.idea/misc.xml
generated
Normal file
9
.idea/misc.xml
generated
Normal file
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="PWA">
|
||||
<option name="wasEnabledAtLeastOnce" value="true" />
|
||||
</component>
|
||||
<component name="ProjectRootManager">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
</project>
|
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/ics-fusion.iml" filepath="$PROJECT_DIR$/.idea/ics-fusion.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
1444
Cargo.lock
generated
Normal file
1444
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
17
Cargo.toml
Normal file
17
Cargo.toml
Normal file
|
@ -0,0 +1,17 @@
|
|||
[package]
|
||||
name = "ics-fusion"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
serde_json = "1.0.91"
|
||||
url = "2.3.1"
|
||||
reqwest = { version = "0.11.13", features = ["blocking"] }
|
||||
icalendar = "0.15.1"
|
||||
sha2 = "0.10.6"
|
||||
chrono = "0.4.23"
|
||||
rusqlite = { version = "0.28.0", features = ["bundled"] }
|
||||
env_logger = "0.10.0"
|
||||
log = "0.4.17"
|
|
@ -1,30 +0,0 @@
|
|||
from flask import Flask, make_response
|
||||
|
||||
from tools.caching import CacheThread
|
||||
from tools.tools import *
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
|
||||
@app.route('/<calendar>')
|
||||
def main(calendar):
|
||||
conf = calendar + ".json"
|
||||
|
||||
print("Opening " + conf)
|
||||
|
||||
try:
|
||||
result = str(process(conf))
|
||||
response = make_response(result, 200)
|
||||
response.headers["Content-Disposition"] = "attachment; filename=calendar.ics"
|
||||
response.headers["Content-Type"] = "text/calendar; charset=utf-8"
|
||||
|
||||
except FileNotFoundError:
|
||||
response = make_response("Calendar not cached", 425)
|
||||
|
||||
return response
|
||||
|
||||
|
||||
thread = CacheThread()
|
||||
thread.start()
|
||||
|
||||
app.run(host='0.0.0.0', port=8088)
|
|
@ -1,151 +0,0 @@
|
|||
import json
|
||||
import os
|
||||
import sched
|
||||
import threading
|
||||
import time
|
||||
from hashlib import sha256
|
||||
|
||||
import traceback
|
||||
import arrow
|
||||
import requests
|
||||
from ics import Calendar
|
||||
from tatsu.exceptions import FailedParse
|
||||
from tools.tools import horodate, process
|
||||
|
||||
|
||||
def cache(entry: dict, scheduler: sched.scheduler = None) -> None:
|
||||
"""Cache an .ics feed in the app/cache directory.
|
||||
Different entries with the same URL will be cached in the same file.
|
||||
The cached calendar contains a new line in the description with the current time when cached prefixed by the
|
||||
'Cached at' mention
|
||||
|
||||
|
||||
|
||||
:param entry: representation of the entry to cache. This is the Python representation of the corresponding entry
|
||||
in the config file
|
||||
:type entry: dict
|
||||
|
||||
:param scheduler: scheduler used to relaunch the caching task in the future. If not scheduler is specified,
|
||||
the task will not be relaunched
|
||||
:type scheduler: sched.scheduler
|
||||
"""
|
||||
|
||||
try:
|
||||
if not os.path.isdir('app/cache'):
|
||||
os.mkdir('app/cache')
|
||||
|
||||
url = entry['url']
|
||||
path = "app/cache/" + sha256(url.encode()).hexdigest() + ".ics"
|
||||
|
||||
r = requests.get(entry["url"], allow_redirects=True)
|
||||
|
||||
if "encoding" in entry:
|
||||
cal = Calendar(imports=r.content.decode(encoding=entry["encoding"]))
|
||||
else:
|
||||
cal = Calendar(imports=r.content.decode())
|
||||
|
||||
cal = horodate(cal, 'Cached at')
|
||||
open(path, 'w').writelines(cal)
|
||||
print(arrow.now().format("YYYY-MM-DD HH:mm:ss"), "Cached", entry['name'])
|
||||
|
||||
except FailedParse:
|
||||
print("Could not parse", entry['name'])
|
||||
|
||||
# Save stack trace when an unknown error occurs
|
||||
except Exception as e:
|
||||
with open("error " + arrow.now().format("YYYY-MM-DD HH:mm:ss")+".txt", 'w') as file:
|
||||
file.write(arrow.now().format("YYYY-MM-DD HH:mm:ss") + "\nCould not cache : " + str(entry))
|
||||
file.write(str(e))
|
||||
file.write(str(traceback.format_exc()))
|
||||
finally:
|
||||
if scheduler is not None:
|
||||
delay = entry['cache'] if entry['cache'] > 0 else 10
|
||||
delay *= 60
|
||||
scheduler.enter(delay=delay, priority=1, action=cache, argument=(entry, scheduler))
|
||||
|
||||
|
||||
def precompute(config: str, scheduler: sched.scheduler = None) -> None:
|
||||
"""Precompute a configuration file result to serve it faster when it is requested. This function
|
||||
should be used with a scheduler to be repeated over time.
|
||||
|
||||
:param config: name of the configuration file to precompute the result for
|
||||
:type config: str
|
||||
|
||||
scheduler used to relaunch the precomputing task in the future. If not scheduler is specified,
|
||||
the task will not be relaunched
|
||||
:type scheduler: sched.scheduler
|
||||
"""
|
||||
try:
|
||||
cal = process(os.path.basename(config), False)
|
||||
path = "app/cache/" + os.path.basename(config).rstrip('.json') + ".ics"
|
||||
open(path, 'w').writelines(cal)
|
||||
print(arrow.now().format("YYYY-MM-DD HH:mm:ss"), "Precomputed", os.path.basename(config).rstrip('.json'))
|
||||
|
||||
except Exception as e:
|
||||
with open("error " + arrow.now().format("YYYY-MM-DD HH:mm:ss")+".txt", 'w') as file:
|
||||
file.write(arrow.now().format("YYYY-MM-DD HH:mm:ss") + "\nCould not precompute : " + str(config))
|
||||
file.write(str(e))
|
||||
file.write(str(traceback.format_exc()))
|
||||
finally:
|
||||
if scheduler is not None:
|
||||
delay = get_min_cache(config)
|
||||
delay *= 60
|
||||
scheduler.enter(delay=delay, priority=1, action=precompute, argument=(config, scheduler))
|
||||
|
||||
|
||||
def get_min_cache(path: str) -> float:
|
||||
"""Get the minimum caching time of all the entries in a config file.
|
||||
|
||||
:param path: path of the config file to use
|
||||
:type path: str
|
||||
|
||||
:return: float number representing the smallest caching time.
|
||||
"""
|
||||
result = float('inf')
|
||||
|
||||
with open(path, 'r') as config_file:
|
||||
file = json.loads(config_file.read())
|
||||
|
||||
for entry in file:
|
||||
if 'cache' in entry and entry['cache'] < result:
|
||||
result = entry['cache']
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def start_scheduler(scheduler: sched.scheduler) -> None:
|
||||
"""Start the caching of every config file found in the app/config directory
|
||||
|
||||
|
||||
:param scheduler: scheduler object to use to schedule the caching
|
||||
:type scheduler: sched.scheduler
|
||||
"""
|
||||
|
||||
path = "app/config"
|
||||
files = [os.path.join(path, f) for f in os.listdir(path)
|
||||
if os.path.isfile(os.path.join(path, f)) and f.endswith('.json')]
|
||||
|
||||
for file in files:
|
||||
with open(file, 'r') as config_file:
|
||||
config = json.loads(config_file.read())
|
||||
|
||||
for entry in config:
|
||||
if 'cache' in entry:
|
||||
scheduler.enter(delay=0, priority=1, action=cache, argument=(entry, scheduler))
|
||||
|
||||
if get_min_cache(file) < float('inf'):
|
||||
scheduler.enter(delay=get_min_cache(file)*60, priority=1, action=precompute, argument=(file, scheduler))
|
||||
|
||||
scheduler.run()
|
||||
|
||||
|
||||
class CacheThread(threading.Thread):
|
||||
"""Child class of the threading.Thread class to run the caching process every 10 minutes
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
threading.Thread.__init__(self)
|
||||
|
||||
def run(self):
|
||||
print("Starting cache process")
|
||||
start_scheduler(sched.scheduler(time.time, time.sleep))
|
|
@ -1,434 +0,0 @@
|
|||
"""This module provides methods to combines multiples .ics feeds into a single object.
|
||||
|
||||
The methods allow to filter the events to keep in the final object and to modify the remaining event
|
||||
according to a configuration file.
|
||||
|
||||
The JSON configuration file used in this module has the following structure
|
||||
|
||||
[
|
||||
{
|
||||
"url":"str",
|
||||
"name":"str",
|
||||
"encoding":"str",
|
||||
"filters":{
|
||||
"name":{
|
||||
"exclude":"RegEx",
|
||||
"includeOnly":"RegEx",
|
||||
"ignoreCase":true
|
||||
},
|
||||
"description":{
|
||||
"exclude":"RegEx",
|
||||
"includeOnly":"RegEx",
|
||||
"ignoreCase":true
|
||||
}
|
||||
},
|
||||
"modify":{
|
||||
"time":{
|
||||
"shift":{
|
||||
"year":0,
|
||||
"month":0,
|
||||
"day":0,
|
||||
"hour":0,
|
||||
"minute":0
|
||||
}
|
||||
},
|
||||
"name":{
|
||||
"addPrefix":"str",
|
||||
"addSuffix":"str"
|
||||
},
|
||||
"description":{
|
||||
"addPrefix":"str",
|
||||
"addSuffix":"str"
|
||||
},
|
||||
"location":{
|
||||
"addPrefix":"str",
|
||||
"addSuffix":"str"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
Only the url and the name field are mandatory.
|
||||
- url: specify the url to find the calendar
|
||||
- name: name to identify the calendar
|
||||
- encoding: specify the encoding to use
|
||||
|
||||
- filters: structure defining the filters to apply to the calendar
|
||||
- name: filters to apply to the name field of the events
|
||||
- description: filters to apply to the name field of the events
|
||||
- exclude: RegEx to describe the events to exclude - cannot be specified with includeOnly
|
||||
- includeOnly: RegEx to describe the events to include - cannot be specified with exclude
|
||||
- ignoreCase: if true the RegEx will ignore the case of the field
|
||||
|
||||
- modify: structure defining the modifications to the events of the calendar
|
||||
- time: describe the modifications to apply to the timing of the event
|
||||
- shift: shift the event of a certain amount of time
|
||||
- year, month, day, hour, minute: amount of time to add to the events
|
||||
- name: modifications to apply to the name of the events
|
||||
- description: modifications to apply to the description of the events
|
||||
- location: modification to apply to the location of the events
|
||||
- addPrefix: string to add at the beginning of the field
|
||||
- addSuffix: string to add at the end of the field
|
||||
"""
|
||||
|
||||
import json
|
||||
import re
|
||||
import arrow
|
||||
import os
|
||||
from hashlib import sha256
|
||||
from typing import List
|
||||
|
||||
import requests
|
||||
from ics import Calendar
|
||||
from pathvalidate import sanitize_filename
|
||||
|
||||
|
||||
def filtering(cal: Calendar, filters: dict, field_name: str) -> Calendar:
|
||||
"""Filter the event of a calendar according to the filters and the field_name
|
||||
|
||||
|
||||
:param cal: the calendar to apply filters to
|
||||
:type cal: Calendar
|
||||
|
||||
:param filters: the filters to apply to the calendar
|
||||
:type filters: dict
|
||||
|
||||
:param field_name: the of the field in the filters to consider
|
||||
:type field_name: str
|
||||
|
||||
|
||||
:return: the modified cal argument after filtering out the events
|
||||
:rtype: Calendar
|
||||
|
||||
|
||||
:raises SyntaxError: if both exclude and includeOnly are specified in the filters
|
||||
"""
|
||||
|
||||
if field_name in filters:
|
||||
field = filters[field_name]
|
||||
|
||||
if ("exclude" in field) and ("includeOnly" in field):
|
||||
raise SyntaxError("Cannot specify both exclude and includeOnly")
|
||||
|
||||
if ("exclude" not in field) and ("includeOnly" not in field):
|
||||
return cal
|
||||
|
||||
new = Calendar()
|
||||
|
||||
ignore_case = True if ("ignoreCase" in field and field["ignoreCase"]) else False
|
||||
|
||||
if "exclude" in field:
|
||||
p = re.compile(field["exclude"], re.IGNORECASE | re.DOTALL) \
|
||||
if ignore_case else re.compile(field["exclude"], re.DOTALL)
|
||||
|
||||
for event in cal.events:
|
||||
if event.name is None or (field_name == "name" and p.match(event.name) is None):
|
||||
new.events.add(event)
|
||||
elif event.description is None or (field_name == "description" and p.match(event.description) is None):
|
||||
new.events.add(event)
|
||||
|
||||
if "includeOnly" in field:
|
||||
p = re.compile(field["includeOnly"], re.IGNORECASE | re.DOTALL) \
|
||||
if ignore_case else re.compile(field["includeOnly"], re.DOTALL)
|
||||
|
||||
for event in cal.events:
|
||||
if field_name == "name" and event.name is not None and p.match(event.name) is not None:
|
||||
new.events.add(event)
|
||||
elif field_name == "description" and event.description is not None \
|
||||
and p.match(event.description) is not None:
|
||||
new.events.add(event)
|
||||
|
||||
cal = new
|
||||
return cal
|
||||
|
||||
else:
|
||||
return cal
|
||||
|
||||
|
||||
def apply_filters(cal: Calendar, filters: dict) -> Calendar:
|
||||
"""Apply all the filters to a calendar and returns the resulting calendar
|
||||
|
||||
|
||||
:param cal: the calendar to apply filters to
|
||||
:type cal: Calendar
|
||||
|
||||
:param filters: the filters to apply
|
||||
:type filters: dict
|
||||
|
||||
|
||||
:return: the modified cal parameter to satisfy the filters
|
||||
:rtype: Calendar
|
||||
|
||||
:raises SyntaxError: if both exclude and includeOnly are specified for the same field in the filters
|
||||
"""
|
||||
|
||||
cal = filtering(cal, filters, "name")
|
||||
cal = filtering(cal, filters, "description")
|
||||
|
||||
return cal
|
||||
|
||||
|
||||
def modify_time(cal: Calendar, modify: dict) -> Calendar:
|
||||
"""Modify the time of all the events in a calendar as specified in the modify structure
|
||||
|
||||
|
||||
:param cal: the calendar where it is needed to modify the time of the events
|
||||
:type cal: Calendar
|
||||
|
||||
:param modify: the structure defining how to modify the time
|
||||
:type modify: dict
|
||||
|
||||
|
||||
:return: the modified cal parameter
|
||||
:rtype: Calendar
|
||||
"""
|
||||
|
||||
if ("time" in modify) and ("shift" in modify["time"]):
|
||||
shift = modify["time"]["shift"]
|
||||
|
||||
year = 0 if not ("year" in shift) else shift["year"]
|
||||
month = 0 if not ("month" in shift) else shift["month"]
|
||||
day = 0 if not ("day" in shift) else shift["day"]
|
||||
hour = 0 if not ("hour" in shift) else shift["hour"]
|
||||
minute = 0 if not ("minute" in shift) else shift["minute"]
|
||||
|
||||
for event in cal.events:
|
||||
event.end = event.end.shift(years=year, months=month, days=day, hours=hour, minutes=minute)
|
||||
event.begin = event.begin.shift(years=year, months=month, days=day, hours=hour, minutes=minute)
|
||||
|
||||
return cal
|
||||
|
||||
|
||||
def modify_text(cal: Calendar, modify: dict, field_name: str) -> Calendar:
|
||||
"""Modify one text field (name, location, description) of all the events in the cal parameter
|
||||
according to the modify structure and the field_name
|
||||
|
||||
|
||||
:param cal: the calendar where it is needed to modify the text field
|
||||
:type cal: Calendar
|
||||
|
||||
:param modify: the structure defining how to modify the time
|
||||
:type modify: dict
|
||||
|
||||
:param field_name: the name of the field to modify
|
||||
:type field_name: str
|
||||
|
||||
|
||||
:return: the modified cal parameter
|
||||
:rtype: Calendar
|
||||
"""
|
||||
|
||||
if field_name in modify:
|
||||
change = modify[field_name]
|
||||
|
||||
if "addPrefix" in change:
|
||||
for event in cal.events:
|
||||
|
||||
if field_name == "name":
|
||||
event.name = change["addPrefix"] + event.name \
|
||||
if event.name is not None else change["addPrefix"]
|
||||
|
||||
elif field_name == "description":
|
||||
event.description = change["addPrefix"] + event.description \
|
||||
if event.description is not None else change["addPrefix"]
|
||||
|
||||
elif field_name == "location":
|
||||
event.location = change["addPrefix"] + event.location \
|
||||
if event.location is not None else change["addPrefix"]
|
||||
|
||||
if "addSuffix" in change:
|
||||
for event in cal.events:
|
||||
|
||||
if field_name == "name":
|
||||
event.name = event.name + change["addSuffix"] \
|
||||
if event.name is not None else change["addSuffix"]
|
||||
|
||||
elif field_name == "description":
|
||||
event.description = event.description + change["addSuffix"] \
|
||||
if event.description is not None else change["addSuffix"]
|
||||
|
||||
elif field_name == "location":
|
||||
event.location = event.location + change["addSuffix"] \
|
||||
if event.location is not None else change["addSuffix"]
|
||||
|
||||
return cal
|
||||
|
||||
|
||||
def apply_modify(cal: Calendar, modify: dict) -> Calendar:
|
||||
"""Apply all the needed modifications to a calendar and returns the resulting calendar
|
||||
|
||||
|
||||
:param cal: the calendar to apply modifications to
|
||||
:type cal: Calendar
|
||||
|
||||
:param modify: the structure containing the modifications to apply
|
||||
:type modify: dict
|
||||
|
||||
|
||||
:return: the modified cal parameter
|
||||
:rtype: Calendar
|
||||
"""
|
||||
|
||||
cal = modify_time(cal, modify)
|
||||
cal = modify_text(cal, modify, "name")
|
||||
cal = modify_text(cal, modify, "description")
|
||||
cal = modify_text(cal, modify, "location")
|
||||
return cal
|
||||
|
||||
|
||||
def merge(cals: List[Calendar]) -> Calendar:
|
||||
"""Merge a list of calendars into a single calendar
|
||||
Only takes the event into account, not the tasks or the alarms
|
||||
|
||||
|
||||
:param cals: the list of calendars to merge
|
||||
:type cals: List[Calendar]
|
||||
|
||||
|
||||
:return: the calendar containing the union of the events contained in the cals list
|
||||
:rtype: Calendar
|
||||
|
||||
|
||||
:raises ValueError: if an element of the list is not a Calendar
|
||||
"""
|
||||
|
||||
result = Calendar()
|
||||
|
||||
for cal in cals:
|
||||
if not isinstance(cal, Calendar):
|
||||
raise ValueError("All elements should be Calendar")
|
||||
result.events = result.events.union(cal.events)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def process(path: str, from_cache: bool = True) -> Calendar:
|
||||
"""Open a config file from the specified path, download the calendars,
|
||||
apply the filters, modify and merge the calendars as specified in the config file
|
||||
|
||||
|
||||
:param from_cache:
|
||||
:param path: name of the file to open. The file should be in the config/ folder
|
||||
:type path: str
|
||||
|
||||
|
||||
:return: the resulting calendar
|
||||
:rtype: Calendar
|
||||
"""
|
||||
print("app/cache/" + sanitize_filename(path).rstrip(".json") + ".ics")
|
||||
if from_cache and os.path.isfile("app/cache/" + sanitize_filename(path).rstrip(".json") + ".ics"):
|
||||
with open("app/cache/" + sanitize_filename(path).rstrip(".json") + ".ics") as file:
|
||||
data = file.read()
|
||||
print("Serving precomputed file")
|
||||
return Calendar(imports=data)
|
||||
|
||||
else:
|
||||
o = "app/config/" + sanitize_filename(path)
|
||||
print("Try to open " + o)
|
||||
file = open(o, "r")
|
||||
config = json.loads(file.read())
|
||||
file.close()
|
||||
|
||||
data = []
|
||||
|
||||
for entry in config:
|
||||
|
||||
cal = load_cal(entry)
|
||||
|
||||
if "filters" in entry:
|
||||
cal = apply_filters(cal, entry["filters"])
|
||||
|
||||
if "modify" in entry:
|
||||
cal = apply_modify(cal, entry["modify"])
|
||||
|
||||
data.append(cal)
|
||||
|
||||
return merge(data)
|
||||
|
||||
|
||||
def get_from_cache(entry: dict) -> Calendar:
|
||||
"""Retrieve the entry from cache. If the entry is not found, an exception is raised
|
||||
|
||||
|
||||
:param entry: representation of the entry to cache. This is the Python representation of the corresponding entry
|
||||
in the config file
|
||||
:type entry: dict
|
||||
|
||||
|
||||
:return: the corresponding calendar in cache
|
||||
:rtype: Calendar
|
||||
|
||||
|
||||
:raises FileNotfoundError: if the entry has not been cached before
|
||||
"""
|
||||
|
||||
url = entry['url']
|
||||
path = "app/cache/" + sha256(url.encode()).hexdigest() + ".ics"
|
||||
if not os.path.isfile(path):
|
||||
print("Not cached")
|
||||
raise FileNotFoundError("The calendar is not cached")
|
||||
|
||||
with open(path, 'r') as file:
|
||||
data = file.read()
|
||||
|
||||
return Calendar(imports=data)
|
||||
|
||||
|
||||
def load_cal(entry: dict) -> Calendar:
|
||||
"""Load the calendar from the cache or from remote according to the entry. If the calendar is supposed to be in
|
||||
cached but could not be found in cache, an error is thrown
|
||||
|
||||
|
||||
:param entry: representation of the entry to cache. This is the Python representation of the corresponding entry
|
||||
in the config file
|
||||
:type entry: dict
|
||||
|
||||
|
||||
:return: the calendar corresponding to the entry
|
||||
:rtype: Calendar
|
||||
|
||||
|
||||
:raises FileNotfoundError: if the entry was supposed to be cached but has not been cached before
|
||||
"""
|
||||
|
||||
if "cache" in entry and entry["cache"]:
|
||||
print("Getting", entry["name"], "from cache")
|
||||
try:
|
||||
return get_from_cache(entry)
|
||||
except FileNotFoundError:
|
||||
return Calendar()
|
||||
|
||||
else:
|
||||
print("Getting", entry["name"], "from remote")
|
||||
r = requests.get(entry["url"], allow_redirects=True)
|
||||
if "encoding" in entry:
|
||||
cal = Calendar(imports=r.content.decode(encoding=entry["encoding"]))
|
||||
else:
|
||||
cal = Calendar(imports=r.content.decode())
|
||||
|
||||
cal = horodate(cal, 'Downloaded at')
|
||||
return cal
|
||||
|
||||
|
||||
def horodate(cal: Calendar, prefix='') -> Calendar:
|
||||
"""Add a new line at the end of the description of every event in the calendar with the current time prefixed by
|
||||
the prefix parameter and a space
|
||||
The date is added with the following format: YYYY-MM-DD HH:mm:ss
|
||||
|
||||
|
||||
:param cal: calendar to process
|
||||
:type cal: Calendar
|
||||
|
||||
:param prefix: the prefix to add in front of the date
|
||||
:type prefix: str
|
||||
|
||||
|
||||
:return: the modified calendar
|
||||
:rtype: Calendar
|
||||
"""
|
||||
now = arrow.now().format("YYYY-MM-DD HH:mm:ss")
|
||||
for event in cal.events:
|
||||
event.description = event.description + '\n' + prefix + ' ' + now \
|
||||
if event.description is not None else prefix + ' ' + now
|
||||
|
||||
return cal
|
|
@ -1,6 +0,0 @@
|
|||
requests~=2.22.0
|
||||
ics~=0.7
|
||||
pathvalidate~=2.3.0
|
||||
flask~=1.1.1
|
||||
arrow~=0.14.7
|
||||
tatsu~=4.4.0
|
971
resources/test/belgium.ics
Normal file
971
resources/test/belgium.ics
Normal file
|
@ -0,0 +1,971 @@
|
|||
BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
PRODID:-//Office Holidays Ltd.//EN
|
||||
X-WR-CALNAME:Belgium Holidays
|
||||
X-WR-CALDESC:Public Holidays in Belgium. Provided by http://www.officeholidays.com
|
||||
REFRESH-INTERVAL;VALUE=DURATION:PT48H
|
||||
X-PUBLISHED-TTL:PT48H
|
||||
CALSCALE:GREGORIAN
|
||||
METHOD:PUBLISH
|
||||
X-MS-OLK-FORCEINSPECTOROPEN:TRUE
|
||||
BEGIN:VEVENT
|
||||
CLASS:PUBLIC
|
||||
UID:2023-01-01BE415regcountry@www.officeholidays.com
|
||||
CREATED:20230114T111157Z
|
||||
DESCRIPTION: New Year's Day is a public holiday in all countries that observe the Gregorian calendar, with the exception of Israel\n\n\n\nInformation provided by www.officeholidays.com
|
||||
URL:https://www.officeholidays.com/holidays/belgium/international-new-years-day
|
||||
DTSTART;VALUE=DATE:20230101
|
||||
DTEND;VALUE=DATE:20230102
|
||||
DTSTAMP:20080101T000000Z
|
||||
LOCATION:Belgium
|
||||
PRIORITY:5
|
||||
LAST-MODIFIED:20210707T000000Z
|
||||
SEQUENCE:1
|
||||
SUMMARY;LANGUAGE=en-us:New Year's Day
|
||||
TRANSP:OPAQUE
|
||||
X-MICROSOFT-CDO-BUSYSTATUS:BUSY
|
||||
X-MICROSOFT-CDO-IMPORTANCE:1
|
||||
X-MICROSOFT-DISALLOW-COUNTER:FALSE
|
||||
X-MS-OLK-ALLOWEXTERNCHECK:TRUE
|
||||
X-MS-OLK-AUTOFILLLOCATION:FALSE
|
||||
X-MICROSOFT-CDO-ALLDAYEVENT:TRUE
|
||||
X-MICROSOFT-MSNCALENDAR-ALLDAYEVENT:TRUE
|
||||
X-MS-OLK-CONFTYPE:0
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
CLASS:PUBLIC
|
||||
UID:2023-01-06BE278regnap@www.officeholidays.com
|
||||
CREATED:20230114T111157Z
|
||||
DESCRIPTION: A major Christian celebration, Epiphany is celebrated on January 6th and commemorates the presentation of the infant Jesus to the Magi, or three wise men. In some countries, it may be known as Three Kings Day.\n\nNot a public holiday\n\nInformation provided by www.officeholidays.com
|
||||
URL:https://www.officeholidays.com/holidays/belgium/epiphany
|
||||
DTSTART;VALUE=DATE:20230106
|
||||
DTEND;VALUE=DATE:20230107
|
||||
DTSTAMP:20080101T000000Z
|
||||
LOCATION:Belgium
|
||||
PRIORITY:5
|
||||
LAST-MODIFIED:20210707T000000Z
|
||||
SEQUENCE:1
|
||||
SUMMARY;LANGUAGE=en-us:Three Kings' Day (Not a Public Holiday)
|
||||
TRANSP:OPAQUE
|
||||
X-MICROSOFT-CDO-BUSYSTATUS:BUSY
|
||||
X-MICROSOFT-CDO-IMPORTANCE:1
|
||||
X-MICROSOFT-DISALLOW-COUNTER:FALSE
|
||||
X-MS-OLK-ALLOWEXTERNCHECK:TRUE
|
||||
X-MS-OLK-AUTOFILLLOCATION:FALSE
|
||||
X-MICROSOFT-CDO-ALLDAYEVENT:TRUE
|
||||
X-MICROSOFT-MSNCALENDAR-ALLDAYEVENT:TRUE
|
||||
X-MS-OLK-CONFTYPE:0
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
CLASS:PUBLIC
|
||||
UID:2023-04-09BE265regcountry@www.officeholidays.com
|
||||
CREATED:20230114T111157Z
|
||||
DESCRIPTION: Easter Sunday is the most important date in the Christian church. In the bible, it is the day when Mary Magdalene found that an empty tomb in the cave in which Jesus had been placed.\n\n\n\nInformation provided by www.officeholidays.com
|
||||
URL:https://www.officeholidays.com/holidays/belgium/easter-sunday
|
||||
DTSTART;VALUE=DATE:20230409
|
||||
DTEND;VALUE=DATE:20230410
|
||||
DTSTAMP:20080101T000000Z
|
||||
LOCATION:Belgium
|
||||
PRIORITY:5
|
||||
LAST-MODIFIED:20210707T000000Z
|
||||
SEQUENCE:1
|
||||
SUMMARY;LANGUAGE=en-us:Easter Sunday
|
||||
TRANSP:OPAQUE
|
||||
X-MICROSOFT-CDO-BUSYSTATUS:BUSY
|
||||
X-MICROSOFT-CDO-IMPORTANCE:1
|
||||
X-MICROSOFT-DISALLOW-COUNTER:FALSE
|
||||
X-MS-OLK-ALLOWEXTERNCHECK:TRUE
|
||||
X-MS-OLK-AUTOFILLLOCATION:FALSE
|
||||
X-MICROSOFT-CDO-ALLDAYEVENT:TRUE
|
||||
X-MICROSOFT-MSNCALENDAR-ALLDAYEVENT:TRUE
|
||||
X-MS-OLK-CONFTYPE:0
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
CLASS:PUBLIC
|
||||
UID:2023-04-10BE263regcountry@www.officeholidays.com
|
||||
CREATED:20230114T111157Z
|
||||
DESCRIPTION: Easter is probably the most important holiday of the Christian year, celebrating the Resurrection of Jesus\n\n\n\nInformation provided by www.officeholidays.com
|
||||
URL:https://www.officeholidays.com/holidays/belgium/easter-monday
|
||||
DTSTART;VALUE=DATE:20230410
|
||||
DTEND;VALUE=DATE:20230411
|
||||
DTSTAMP:20080101T000000Z
|
||||
LOCATION:Belgium
|
||||
PRIORITY:5
|
||||
LAST-MODIFIED:20210707T000000Z
|
||||
SEQUENCE:1
|
||||
SUMMARY;LANGUAGE=en-us:Easter Monday
|
||||
TRANSP:OPAQUE
|
||||
X-MICROSOFT-CDO-BUSYSTATUS:BUSY
|
||||
X-MICROSOFT-CDO-IMPORTANCE:1
|
||||
X-MICROSOFT-DISALLOW-COUNTER:FALSE
|
||||
X-MS-OLK-ALLOWEXTERNCHECK:TRUE
|
||||
X-MS-OLK-AUTOFILLLOCATION:FALSE
|
||||
X-MICROSOFT-CDO-ALLDAYEVENT:TRUE
|
||||
X-MICROSOFT-MSNCALENDAR-ALLDAYEVENT:TRUE
|
||||
X-MS-OLK-CONFTYPE:0
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
CLASS:PUBLIC
|
||||
UID:2023-05-01BE475regcountry@www.officeholidays.com
|
||||
CREATED:20230114T111157Z
|
||||
DESCRIPTION: This holiday is most commonly associated as a commemoration of the achievements of the labour movement\n\nInternational Workers' Day\n\nInformation provided by www.officeholidays.com
|
||||
URL:https://www.officeholidays.com/holidays/belgium/labour-day
|
||||
DTSTART;VALUE=DATE:20230501
|
||||
DTEND;VALUE=DATE:20230502
|
||||
DTSTAMP:20080101T000000Z
|
||||
LOCATION:Belgium
|
||||
PRIORITY:5
|
||||
LAST-MODIFIED:20210707T000000Z
|
||||
SEQUENCE:1
|
||||
SUMMARY;LANGUAGE=en-us:Labour Day
|
||||
TRANSP:OPAQUE
|
||||
X-MICROSOFT-CDO-BUSYSTATUS:BUSY
|
||||
X-MICROSOFT-CDO-IMPORTANCE:1
|
||||
X-MICROSOFT-DISALLOW-COUNTER:FALSE
|
||||
X-MS-OLK-ALLOWEXTERNCHECK:TRUE
|
||||
X-MS-OLK-AUTOFILLLOCATION:FALSE
|
||||
X-MICROSOFT-CDO-ALLDAYEVENT:TRUE
|
||||
X-MICROSOFT-MSNCALENDAR-ALLDAYEVENT:TRUE
|
||||
X-MS-OLK-CONFTYPE:0
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
CLASS:PUBLIC
|
||||
UID:2023-05-14BE563regnap@www.officeholidays.com
|
||||
CREATED:20230114T111157Z
|
||||
DESCRIPTION: Mother's Day is celebrated across the world, in more than 50 countries, though not all countries celebrate it on the same day\n\n2nd Sunday in May\n\nInformation provided by www.officeholidays.com
|
||||
URL:https://www.officeholidays.com/holidays/belgium/mothers-day
|
||||
DTSTART;VALUE=DATE:20230514
|
||||
DTEND;VALUE=DATE:20230515
|
||||
DTSTAMP:20080101T000000Z
|
||||
LOCATION:Belgium
|
||||
PRIORITY:5
|
||||
LAST-MODIFIED:20210707T000000Z
|
||||
SEQUENCE:1
|
||||
SUMMARY;LANGUAGE=en-us:Mother's Day (Not a Public Holiday)
|
||||
TRANSP:OPAQUE
|
||||
X-MICROSOFT-CDO-BUSYSTATUS:BUSY
|
||||
X-MICROSOFT-CDO-IMPORTANCE:1
|
||||
X-MICROSOFT-DISALLOW-COUNTER:FALSE
|
||||
X-MS-OLK-ALLOWEXTERNCHECK:TRUE
|
||||
X-MS-OLK-AUTOFILLLOCATION:FALSE
|
||||
X-MICROSOFT-CDO-ALLDAYEVENT:TRUE
|
||||
X-MICROSOFT-MSNCALENDAR-ALLDAYEVENT:TRUE
|
||||
X-MS-OLK-CONFTYPE:0
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
CLASS:PUBLIC
|
||||
UID:2023-05-18BE37regcountry@www.officeholidays.com
|
||||
CREATED:20230114T111157Z
|
||||
DESCRIPTION: This day observes the departure of Jesus from earth after his resurrection. It is perhaps the earliest observed celebration in Christianity.\n\n39 days after Easter Sunday\n\nInformation provided by www.officeholidays.com
|
||||
URL:https://www.officeholidays.com/holidays/belgium/ascension-day
|
||||
DTSTART;VALUE=DATE:20230518
|
||||
DTEND;VALUE=DATE:20230519
|
||||
DTSTAMP:20080101T000000Z
|
||||
LOCATION:Belgium
|
||||
PRIORITY:5
|
||||
LAST-MODIFIED:20220504T000000Z
|
||||
SEQUENCE:1
|
||||
SUMMARY;LANGUAGE=en-us:Ascension Day
|
||||
TRANSP:OPAQUE
|
||||
X-MICROSOFT-CDO-BUSYSTATUS:BUSY
|
||||
X-MICROSOFT-CDO-IMPORTANCE:1
|
||||
X-MICROSOFT-DISALLOW-COUNTER:FALSE
|
||||
X-MS-OLK-ALLOWEXTERNCHECK:TRUE
|
||||
X-MS-OLK-AUTOFILLLOCATION:FALSE
|
||||
X-MICROSOFT-CDO-ALLDAYEVENT:TRUE
|
||||
X-MICROSOFT-MSNCALENDAR-ALLDAYEVENT:TRUE
|
||||
X-MS-OLK-CONFTYPE:0
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
CLASS:PUBLIC
|
||||
UID:2023-05-19BE2854regnap@www.officeholidays.com
|
||||
CREATED:20230114T111157Z
|
||||
DESCRIPTION: Banks are closed on the Friday after Ascension Day in Belgium\n\nBanks are closed\n\nInformation provided by www.officeholidays.com
|
||||
URL:https://www.officeholidays.com/holidays/belgium/ascension-friday
|
||||
DTSTART;VALUE=DATE:20230519
|
||||
DTEND;VALUE=DATE:20230520
|
||||
DTSTAMP:20080101T000000Z
|
||||
LOCATION:Belgium
|
||||
PRIORITY:5
|
||||
LAST-MODIFIED:20210707T000000Z
|
||||
SEQUENCE:1
|
||||
SUMMARY;LANGUAGE=en-us:Bank Holiday (Not a Public Holiday)
|
||||
TRANSP:OPAQUE
|
||||
X-MICROSOFT-CDO-BUSYSTATUS:BUSY
|
||||
X-MICROSOFT-CDO-IMPORTANCE:1
|
||||
X-MICROSOFT-DISALLOW-COUNTER:FALSE
|
||||
X-MS-OLK-ALLOWEXTERNCHECK:TRUE
|
||||
X-MS-OLK-AUTOFILLLOCATION:FALSE
|
||||
X-MICROSOFT-CDO-ALLDAYEVENT:TRUE
|
||||
X-MICROSOFT-MSNCALENDAR-ALLDAYEVENT:TRUE
|
||||
X-MS-OLK-CONFTYPE:0
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
CLASS:PUBLIC
|
||||
UID:2023-05-28BE1012regnap@www.officeholidays.com
|
||||
CREATED:20230114T111157Z
|
||||
DESCRIPTION: Celebrates the gift of the Holy Spirit. It is celebrated on the seventh Sunday after Easter\n\n50 Days after Easter\n\nInformation provided by www.officeholidays.com
|
||||
URL:https://www.officeholidays.com/holidays/belgium/pentecost-sunday
|
||||
DTSTART;VALUE=DATE:20230528
|
||||
DTEND;VALUE=DATE:20230529
|
||||
DTSTAMP:20080101T000000Z
|
||||
LOCATION:Belgium
|
||||
PRIORITY:5
|
||||
LAST-MODIFIED:20221129T000000Z
|
||||
SEQUENCE:1
|
||||
SUMMARY;LANGUAGE=en-us:Pentecost Sunday (Not a Public Holiday)
|
||||
TRANSP:OPAQUE
|
||||
X-MICROSOFT-CDO-BUSYSTATUS:BUSY
|
||||
X-MICROSOFT-CDO-IMPORTANCE:1
|
||||
X-MICROSOFT-DISALLOW-COUNTER:FALSE
|
||||
X-MS-OLK-ALLOWEXTERNCHECK:TRUE
|
||||
X-MS-OLK-AUTOFILLLOCATION:FALSE
|
||||
X-MICROSOFT-CDO-ALLDAYEVENT:TRUE
|
||||
X-MICROSOFT-MSNCALENDAR-ALLDAYEVENT:TRUE
|
||||
X-MS-OLK-CONFTYPE:0
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
CLASS:PUBLIC
|
||||
UID:2023-05-29BE927regcountry@www.officeholidays.com
|
||||
CREATED:20230114T111157Z
|
||||
DESCRIPTION: Commemorates the coming of the Holy Spirit in the form of flames to the Apostles.\n\n7th Monday after Easter\n\nInformation provided by www.officeholidays.com
|
||||
URL:https://www.officeholidays.com/holidays/belgium/whit-monday
|
||||
DTSTART;VALUE=DATE:20230529
|
||||
DTEND;VALUE=DATE:20230530
|
||||
DTSTAMP:20080101T000000Z
|
||||
LOCATION:Belgium
|
||||
PRIORITY:5
|
||||
LAST-MODIFIED:20220620T000000Z
|
||||
SEQUENCE:1
|
||||
SUMMARY;LANGUAGE=en-us:Pentecost Monday
|
||||
TRANSP:OPAQUE
|
||||
X-MICROSOFT-CDO-BUSYSTATUS:BUSY
|
||||
X-MICROSOFT-CDO-IMPORTANCE:1
|
||||
X-MICROSOFT-DISALLOW-COUNTER:FALSE
|
||||
X-MS-OLK-ALLOWEXTERNCHECK:TRUE
|
||||
X-MS-OLK-AUTOFILLLOCATION:FALSE
|
||||
X-MICROSOFT-CDO-ALLDAYEVENT:TRUE
|
||||
X-MICROSOFT-MSNCALENDAR-ALLDAYEVENT:TRUE
|
||||
X-MS-OLK-CONFTYPE:0
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
CLASS:PUBLIC
|
||||
UID:2023-06-10BE290regnap@www.officeholidays.com
|
||||
CREATED:20230114T111157Z
|
||||
DESCRIPTION: Not a public holiday. Father's Day is a celebration that honours the role of fathers\n\n2nd Sunday in June. Not a public holiday\n\nInformation provided by www.officeholidays.com
|
||||
URL:https://www.officeholidays.com/holidays/belgium/fathers-day
|
||||
DTSTART;VALUE=DATE:20230610
|
||||
DTEND;VALUE=DATE:20230611
|
||||
DTSTAMP:20080101T000000Z
|
||||
LOCATION:Belgium
|
||||
PRIORITY:5
|
||||
LAST-MODIFIED:20210707T000000Z
|
||||
SEQUENCE:1
|
||||
SUMMARY;LANGUAGE=en-us:Father's Day (Not a Public Holiday)
|
||||
TRANSP:OPAQUE
|
||||
X-MICROSOFT-CDO-BUSYSTATUS:BUSY
|
||||
X-MICROSOFT-CDO-IMPORTANCE:1
|
||||
X-MICROSOFT-DISALLOW-COUNTER:FALSE
|
||||
X-MS-OLK-ALLOWEXTERNCHECK:TRUE
|
||||
X-MS-OLK-AUTOFILLLOCATION:FALSE
|
||||
X-MICROSOFT-CDO-ALLDAYEVENT:TRUE
|
||||
X-MICROSOFT-MSNCALENDAR-ALLDAYEVENT:TRUE
|
||||
X-MS-OLK-CONFTYPE:0
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
CLASS:PUBLIC
|
||||
UID:2023-07-21BE82regcountry@www.officeholidays.com
|
||||
CREATED:20230114T111157Z
|
||||
DESCRIPTION: Independence Day celebrates the separation of Belgium from the Netherlands in 1831\n\nNational Day\n\nInformation provided by www.officeholidays.com
|
||||
URL:https://www.officeholidays.com/holidays/belgium/belgium-independence-day
|
||||
DTSTART;VALUE=DATE:20230721
|
||||
DTEND;VALUE=DATE:20230722
|
||||
DTSTAMP:20080101T000000Z
|
||||
LOCATION:Belgium
|
||||
PRIORITY:5
|
||||
LAST-MODIFIED:20210707T000000Z
|
||||
SEQUENCE:1
|
||||
SUMMARY;LANGUAGE=en-us:Independence Day
|
||||
TRANSP:OPAQUE
|
||||
X-MICROSOFT-CDO-BUSYSTATUS:BUSY
|
||||
X-MICROSOFT-CDO-IMPORTANCE:1
|
||||
X-MICROSOFT-DISALLOW-COUNTER:FALSE
|
||||
X-MS-OLK-ALLOWEXTERNCHECK:TRUE
|
||||
X-MS-OLK-AUTOFILLLOCATION:FALSE
|
||||
X-MICROSOFT-CDO-ALLDAYEVENT:TRUE
|
||||
X-MICROSOFT-MSNCALENDAR-ALLDAYEVENT:TRUE
|
||||
X-MS-OLK-CONFTYPE:0
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
CLASS:PUBLIC
|
||||
UID:2023-08-15BE42regcountry@www.officeholidays.com
|
||||
CREATED:20230114T111157Z
|
||||
DESCRIPTION: The Feast of the Assumption is the principal feast of the Blessed Virgin, the mother of Jesus Christ\n\n\n\nInformation provided by www.officeholidays.com
|
||||
URL:https://www.officeholidays.com/holidays/belgium/assumption-day
|
||||
DTSTART;VALUE=DATE:20230815
|
||||
DTEND;VALUE=DATE:20230816
|
||||
DTSTAMP:20080101T000000Z
|
||||
LOCATION:Belgium
|
||||
PRIORITY:5
|
||||
LAST-MODIFIED:20210707T000000Z
|
||||
SEQUENCE:1
|
||||
SUMMARY;LANGUAGE=en-us:Assumption Day
|
||||
TRANSP:OPAQUE
|
||||
X-MICROSOFT-CDO-BUSYSTATUS:BUSY
|
||||
X-MICROSOFT-CDO-IMPORTANCE:1
|
||||
X-MICROSOFT-DISALLOW-COUNTER:FALSE
|
||||
X-MS-OLK-ALLOWEXTERNCHECK:TRUE
|
||||
X-MS-OLK-AUTOFILLLOCATION:FALSE
|
||||
X-MICROSOFT-CDO-ALLDAYEVENT:TRUE
|
||||
X-MICROSOFT-MSNCALENDAR-ALLDAYEVENT:TRUE
|
||||
X-MS-OLK-CONFTYPE:0
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
CLASS:PUBLIC
|
||||
UID:2023-09-27BE325regregion@www.officeholidays.com
|
||||
CREATED:20230114T111157Z
|
||||
DESCRIPTION: On 24 June 1975, the date of 27 September was selected by the French Community as French Community Day. It was first celebrated later that same year\n\n\n\nInformation provided by www.officeholidays.com
|
||||
URL:https://www.officeholidays.com/holidays/belgium/french-community-holiday
|
||||
DTSTART;VALUE=DATE:20230927
|
||||
DTEND;VALUE=DATE:20230928
|
||||
DTSTAMP:20080101T000000Z
|
||||
LOCATION:Brabant wallon, Hainaut, Liège, Luxemburg, Namur
|
||||
PRIORITY:5
|
||||
LAST-MODIFIED:20191229T000000Z
|
||||
SEQUENCE:1
|
||||
SUMMARY;LANGUAGE=en-us:French Community Holiday (Regional Holiday)
|
||||
TRANSP:OPAQUE
|
||||
X-MICROSOFT-CDO-BUSYSTATUS:BUSY
|
||||
X-MICROSOFT-CDO-IMPORTANCE:1
|
||||
X-MICROSOFT-DISALLOW-COUNTER:FALSE
|
||||
X-MS-OLK-ALLOWEXTERNCHECK:TRUE
|
||||
X-MS-OLK-AUTOFILLLOCATION:FALSE
|
||||
X-MICROSOFT-CDO-ALLDAYEVENT:TRUE
|
||||
X-MICROSOFT-MSNCALENDAR-ALLDAYEVENT:TRUE
|
||||
X-MS-OLK-CONFTYPE:0
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
CLASS:PUBLIC
|
||||
UID:2023-11-01BE9regcountry@www.officeholidays.com
|
||||
CREATED:20230114T111157Z
|
||||
DESCRIPTION: Pope Boniface IV dedicated the day as a holiday to honour the Blessed Virgin Mary and all martyrs\n\n\n\nInformation provided by www.officeholidays.com
|
||||
URL:https://www.officeholidays.com/holidays/belgium/all-saints-day
|
||||
DTSTART;VALUE=DATE:20231101
|
||||
DTEND;VALUE=DATE:20231102
|
||||
DTSTAMP:20080101T000000Z
|
||||
LOCATION:Belgium
|
||||
PRIORITY:5
|
||||
LAST-MODIFIED:20210707T000000Z
|
||||
SEQUENCE:1
|
||||
SUMMARY;LANGUAGE=en-us:All Saints' Day
|
||||
TRANSP:OPAQUE
|
||||
X-MICROSOFT-CDO-BUSYSTATUS:BUSY
|
||||
X-MICROSOFT-CDO-IMPORTANCE:1
|
||||
X-MICROSOFT-DISALLOW-COUNTER:FALSE
|
||||
X-MS-OLK-ALLOWEXTERNCHECK:TRUE
|
||||
X-MS-OLK-AUTOFILLLOCATION:FALSE
|
||||
X-MICROSOFT-CDO-ALLDAYEVENT:TRUE
|
||||
X-MICROSOFT-MSNCALENDAR-ALLDAYEVENT:TRUE
|
||||
X-MS-OLK-CONFTYPE:0
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
CLASS:PUBLIC
|
||||
UID:2023-11-02BE10regnap@www.officeholidays.com
|
||||
CREATED:20230114T111157Z
|
||||
DESCRIPTION: Dedicated to the remembrance of the departed. All Souls' Day follows All Saints' Day.\n\nNot a public holiday\n\nInformation provided by www.officeholidays.com
|
||||
URL:https://www.officeholidays.com/holidays/belgium/all-souls-day
|
||||
DTSTART;VALUE=DATE:20231102
|
||||
DTEND;VALUE=DATE:20231103
|
||||
DTSTAMP:20080101T000000Z
|
||||
LOCATION:Belgium
|
||||
PRIORITY:5
|
||||
LAST-MODIFIED:20210707T000000Z
|
||||
SEQUENCE:1
|
||||
SUMMARY;LANGUAGE=en-us:All Souls' Day (Not a Public Holiday)
|
||||
TRANSP:OPAQUE
|
||||
X-MICROSOFT-CDO-BUSYSTATUS:BUSY
|
||||
X-MICROSOFT-CDO-IMPORTANCE:1
|
||||
X-MICROSOFT-DISALLOW-COUNTER:FALSE
|
||||
X-MS-OLK-ALLOWEXTERNCHECK:TRUE
|
||||
X-MS-OLK-AUTOFILLLOCATION:FALSE
|
||||
X-MICROSOFT-CDO-ALLDAYEVENT:TRUE
|
||||
X-MICROSOFT-MSNCALENDAR-ALLDAYEVENT:TRUE
|
||||
X-MS-OLK-CONFTYPE:0
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
CLASS:PUBLIC
|
||||
UID:2023-11-11BE705regcountry@www.officeholidays.com
|
||||
CREATED:20230114T111157Z
|
||||
DESCRIPTION: Observed on 11th November to recall the end of World War I on that date in 1918 and honor the veterans of both World Wars.\n\n\n\nInformation provided by www.officeholidays.com
|
||||
URL:https://www.officeholidays.com/holidays/belgium/remembrance-day
|
||||
DTSTART;VALUE=DATE:20231111
|
||||
DTEND;VALUE=DATE:20231112
|
||||
DTSTAMP:20080101T000000Z
|
||||
LOCATION:Belgium
|
||||
PRIORITY:5
|
||||
LAST-MODIFIED:20211022T000000Z
|
||||
SEQUENCE:1
|
||||
SUMMARY;LANGUAGE=en-us:Armistice Day
|
||||
TRANSP:OPAQUE
|
||||
X-MICROSOFT-CDO-BUSYSTATUS:BUSY
|
||||
X-MICROSOFT-CDO-IMPORTANCE:1
|
||||
X-MICROSOFT-DISALLOW-COUNTER:FALSE
|
||||
X-MS-OLK-ALLOWEXTERNCHECK:TRUE
|
||||
X-MS-OLK-AUTOFILLLOCATION:FALSE
|
||||
X-MICROSOFT-CDO-ALLDAYEVENT:TRUE
|
||||
X-MICROSOFT-MSNCALENDAR-ALLDAYEVENT:TRUE
|
||||
X-MS-OLK-CONFTYPE:0
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
CLASS:PUBLIC
|
||||
UID:2023-11-15BE-WLG337regregion@www.officeholidays.com
|
||||
CREATED:20230114T111157Z
|
||||
DESCRIPTION: Day of the German-speaking Community of Belgium\n\n\n\nInformation provided by www.officeholidays.com
|
||||
URL:https://www.officeholidays.com/holidays/belgium/liege/german-community-day
|
||||
DTSTART;VALUE=DATE:20231115
|
||||
DTEND;VALUE=DATE:20231116
|
||||
DTSTAMP:20080101T000000Z
|
||||
LOCATION:Liège
|
||||
PRIORITY:5
|
||||
LAST-MODIFIED:20191229T000000Z
|
||||
SEQUENCE:1
|
||||
SUMMARY;LANGUAGE=en-us:German Community Day (Regional Holiday)
|
||||
TRANSP:OPAQUE
|
||||
X-MICROSOFT-CDO-BUSYSTATUS:BUSY
|
||||
X-MICROSOFT-CDO-IMPORTANCE:1
|
||||
X-MICROSOFT-DISALLOW-COUNTER:FALSE
|
||||
X-MS-OLK-ALLOWEXTERNCHECK:TRUE
|
||||
X-MS-OLK-AUTOFILLLOCATION:FALSE
|
||||
X-MICROSOFT-CDO-ALLDAYEVENT:TRUE
|
||||
X-MICROSOFT-MSNCALENDAR-ALLDAYEVENT:TRUE
|
||||
X-MS-OLK-CONFTYPE:0
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
CLASS:PUBLIC
|
||||
UID:2023-12-06BE298regnap@www.officeholidays.com
|
||||
CREATED:20230114T111157Z
|
||||
DESCRIPTION: A day when many children across Europe receive gifts in honour of Saint Nicholas\n\nNot a public holiday\n\nInformation provided by www.officeholidays.com
|
||||
URL:https://www.officeholidays.com/holidays/belgium/feast-of-st-nicholas
|
||||
DTSTART;VALUE=DATE:20231206
|
||||
DTEND;VALUE=DATE:20231207
|
||||
DTSTAMP:20080101T000000Z
|
||||
LOCATION:Belgium
|
||||
PRIORITY:5
|
||||
LAST-MODIFIED:20210707T000000Z
|
||||
SEQUENCE:1
|
||||
SUMMARY;LANGUAGE=en-us:Feast of St. Nicholas (Not a Public Holiday)
|
||||
TRANSP:OPAQUE
|
||||
X-MICROSOFT-CDO-BUSYSTATUS:BUSY
|
||||
X-MICROSOFT-CDO-IMPORTANCE:1
|
||||
X-MICROSOFT-DISALLOW-COUNTER:FALSE
|
||||
X-MS-OLK-ALLOWEXTERNCHECK:TRUE
|
||||
X-MS-OLK-AUTOFILLLOCATION:FALSE
|
||||
X-MICROSOFT-CDO-ALLDAYEVENT:TRUE
|
||||
X-MICROSOFT-MSNCALENDAR-ALLDAYEVENT:TRUE
|
||||
X-MS-OLK-CONFTYPE:0
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
CLASS:PUBLIC
|
||||
UID:2023-12-25BE181regcountry@www.officeholidays.com
|
||||
CREATED:20230114T111157Z
|
||||
DESCRIPTION: Christmas celebrates the Nativity of Jesus which according to tradition took place on December 25th 1 BC\n\n\n\nInformation provided by www.officeholidays.com
|
||||
URL:https://www.officeholidays.com/holidays/belgium/christmas-day
|
||||
DTSTART;VALUE=DATE:20231225
|
||||
DTEND;VALUE=DATE:20231226
|
||||
DTSTAMP:20080101T000000Z
|
||||
LOCATION:Belgium
|
||||
PRIORITY:5
|
||||
LAST-MODIFIED:20210707T000000Z
|
||||
SEQUENCE:1
|
||||
SUMMARY;LANGUAGE=en-us:Christmas Day
|
||||
TRANSP:OPAQUE
|
||||
X-MICROSOFT-CDO-BUSYSTATUS:BUSY
|
||||
X-MICROSOFT-CDO-IMPORTANCE:1
|
||||
X-MICROSOFT-DISALLOW-COUNTER:FALSE
|
||||
X-MS-OLK-ALLOWEXTERNCHECK:TRUE
|
||||
X-MS-OLK-AUTOFILLLOCATION:FALSE
|
||||
X-MICROSOFT-CDO-ALLDAYEVENT:TRUE
|
||||
X-MICROSOFT-MSNCALENDAR-ALLDAYEVENT:TRUE
|
||||
X-MS-OLK-CONFTYPE:0
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
CLASS:PUBLIC
|
||||
UID:2024-01-01BE415regcountry@www.officeholidays.com
|
||||
CREATED:20230114T111157Z
|
||||
DESCRIPTION: New Year's Day is a public holiday in all countries that observe the Gregorian calendar, with the exception of Israel\n\n\n\nInformation provided by www.officeholidays.com
|
||||
URL:https://www.officeholidays.com/holidays/belgium/international-new-years-day
|
||||
DTSTART;VALUE=DATE:20240101
|
||||
DTEND;VALUE=DATE:20240102
|
||||
DTSTAMP:20080101T000000Z
|
||||
LOCATION:Belgium
|
||||
PRIORITY:5
|
||||
LAST-MODIFIED:20210707T000000Z
|
||||
SEQUENCE:1
|
||||
SUMMARY;LANGUAGE=en-us:New Year's Day
|
||||
TRANSP:OPAQUE
|
||||
X-MICROSOFT-CDO-BUSYSTATUS:BUSY
|
||||
X-MICROSOFT-CDO-IMPORTANCE:1
|
||||
X-MICROSOFT-DISALLOW-COUNTER:FALSE
|
||||
X-MS-OLK-ALLOWEXTERNCHECK:TRUE
|
||||
X-MS-OLK-AUTOFILLLOCATION:FALSE
|
||||
X-MICROSOFT-CDO-ALLDAYEVENT:TRUE
|
||||
X-MICROSOFT-MSNCALENDAR-ALLDAYEVENT:TRUE
|
||||
X-MS-OLK-CONFTYPE:0
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
CLASS:PUBLIC
|
||||
UID:2024-01-06BE278regnap@www.officeholidays.com
|
||||
CREATED:20230114T111157Z
|
||||
DESCRIPTION: A major Christian celebration, Epiphany is celebrated on January 6th and commemorates the presentation of the infant Jesus to the Magi, or three wise men. In some countries, it may be known as Three Kings Day.\n\nNot a public holiday\n\nInformation provided by www.officeholidays.com
|
||||
URL:https://www.officeholidays.com/holidays/belgium/epiphany
|
||||
DTSTART;VALUE=DATE:20240106
|
||||
DTEND;VALUE=DATE:20240107
|
||||
DTSTAMP:20080101T000000Z
|
||||
LOCATION:Belgium
|
||||
PRIORITY:5
|
||||
LAST-MODIFIED:20210707T000000Z
|
||||
SEQUENCE:1
|
||||
SUMMARY;LANGUAGE=en-us:Three Kings' Day (Not a Public Holiday)
|
||||
TRANSP:OPAQUE
|
||||
X-MICROSOFT-CDO-BUSYSTATUS:BUSY
|
||||
X-MICROSOFT-CDO-IMPORTANCE:1
|
||||
X-MICROSOFT-DISALLOW-COUNTER:FALSE
|
||||
X-MS-OLK-ALLOWEXTERNCHECK:TRUE
|
||||
X-MS-OLK-AUTOFILLLOCATION:FALSE
|
||||
X-MICROSOFT-CDO-ALLDAYEVENT:TRUE
|
||||
X-MICROSOFT-MSNCALENDAR-ALLDAYEVENT:TRUE
|
||||
X-MS-OLK-CONFTYPE:0
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
CLASS:PUBLIC
|
||||
UID:2024-03-31BE265regcountry@www.officeholidays.com
|
||||
CREATED:20230114T111157Z
|
||||
DESCRIPTION: Easter Sunday is the most important date in the Christian church. In the bible, it is the day when Mary Magdalene found that an empty tomb in the cave in which Jesus had been placed.\n\n\n\nInformation provided by www.officeholidays.com
|
||||
URL:https://www.officeholidays.com/holidays/belgium/easter-sunday
|
||||
DTSTART;VALUE=DATE:20240331
|
||||
DTEND;VALUE=DATE:20240401
|
||||
DTSTAMP:20080101T000000Z
|
||||
LOCATION:Belgium
|
||||
PRIORITY:5
|
||||
LAST-MODIFIED:20210707T000000Z
|
||||
SEQUENCE:1
|
||||
SUMMARY;LANGUAGE=en-us:Easter Sunday
|
||||
TRANSP:OPAQUE
|
||||
X-MICROSOFT-CDO-BUSYSTATUS:BUSY
|
||||
X-MICROSOFT-CDO-IMPORTANCE:1
|
||||
X-MICROSOFT-DISALLOW-COUNTER:FALSE
|
||||
X-MS-OLK-ALLOWEXTERNCHECK:TRUE
|
||||
X-MS-OLK-AUTOFILLLOCATION:FALSE
|
||||
X-MICROSOFT-CDO-ALLDAYEVENT:TRUE
|
||||
X-MICROSOFT-MSNCALENDAR-ALLDAYEVENT:TRUE
|
||||
X-MS-OLK-CONFTYPE:0
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
CLASS:PUBLIC
|
||||
UID:2024-04-01BE263regcountry@www.officeholidays.com
|
||||
CREATED:20230114T111157Z
|
||||
DESCRIPTION: Easter is probably the most important holiday of the Christian year, celebrating the Resurrection of Jesus\n\n\n\nInformation provided by www.officeholidays.com
|
||||
URL:https://www.officeholidays.com/holidays/belgium/easter-monday
|
||||
DTSTART;VALUE=DATE:20240401
|
||||
DTEND;VALUE=DATE:20240402
|
||||
DTSTAMP:20080101T000000Z
|
||||
LOCATION:Belgium
|
||||
PRIORITY:5
|
||||
LAST-MODIFIED:20210707T000000Z
|
||||
SEQUENCE:1
|
||||
SUMMARY;LANGUAGE=en-us:Easter Monday
|
||||
TRANSP:OPAQUE
|
||||
X-MICROSOFT-CDO-BUSYSTATUS:BUSY
|
||||
X-MICROSOFT-CDO-IMPORTANCE:1
|
||||
X-MICROSOFT-DISALLOW-COUNTER:FALSE
|
||||
X-MS-OLK-ALLOWEXTERNCHECK:TRUE
|
||||
X-MS-OLK-AUTOFILLLOCATION:FALSE
|
||||
X-MICROSOFT-CDO-ALLDAYEVENT:TRUE
|
||||
X-MICROSOFT-MSNCALENDAR-ALLDAYEVENT:TRUE
|
||||
X-MS-OLK-CONFTYPE:0
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
CLASS:PUBLIC
|
||||
UID:2024-05-01BE475regcountry@www.officeholidays.com
|
||||
CREATED:20230114T111157Z
|
||||
DESCRIPTION: This holiday is most commonly associated as a commemoration of the achievements of the labour movement\n\nInternational Workers' Day\n\nInformation provided by www.officeholidays.com
|
||||
URL:https://www.officeholidays.com/holidays/belgium/labour-day
|
||||
DTSTART;VALUE=DATE:20240501
|
||||
DTEND;VALUE=DATE:20240502
|
||||
DTSTAMP:20080101T000000Z
|
||||
LOCATION:Belgium
|
||||
PRIORITY:5
|
||||
LAST-MODIFIED:20210707T000000Z
|
||||
SEQUENCE:1
|
||||
SUMMARY;LANGUAGE=en-us:Labour Day
|
||||
TRANSP:OPAQUE
|
||||
X-MICROSOFT-CDO-BUSYSTATUS:BUSY
|
||||
X-MICROSOFT-CDO-IMPORTANCE:1
|
||||
X-MICROSOFT-DISALLOW-COUNTER:FALSE
|
||||
X-MS-OLK-ALLOWEXTERNCHECK:TRUE
|
||||
X-MS-OLK-AUTOFILLLOCATION:FALSE
|
||||
X-MICROSOFT-CDO-ALLDAYEVENT:TRUE
|
||||
X-MICROSOFT-MSNCALENDAR-ALLDAYEVENT:TRUE
|
||||
X-MS-OLK-CONFTYPE:0
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
CLASS:PUBLIC
|
||||
UID:2024-05-09BE37regcountry@www.officeholidays.com
|
||||
CREATED:20230114T111157Z
|
||||
DESCRIPTION: This day observes the departure of Jesus from earth after his resurrection. It is perhaps the earliest observed celebration in Christianity.\n\n39 days after Easter Sunday\n\nInformation provided by www.officeholidays.com
|
||||
URL:https://www.officeholidays.com/holidays/belgium/ascension-day
|
||||
DTSTART;VALUE=DATE:20240509
|
||||
DTEND;VALUE=DATE:20240510
|
||||
DTSTAMP:20080101T000000Z
|
||||
LOCATION:Belgium
|
||||
PRIORITY:5
|
||||
LAST-MODIFIED:20220504T000000Z
|
||||
SEQUENCE:1
|
||||
SUMMARY;LANGUAGE=en-us:Ascension Day
|
||||
TRANSP:OPAQUE
|
||||
X-MICROSOFT-CDO-BUSYSTATUS:BUSY
|
||||
X-MICROSOFT-CDO-IMPORTANCE:1
|
||||
X-MICROSOFT-DISALLOW-COUNTER:FALSE
|
||||
X-MS-OLK-ALLOWEXTERNCHECK:TRUE
|
||||
X-MS-OLK-AUTOFILLLOCATION:FALSE
|
||||
X-MICROSOFT-CDO-ALLDAYEVENT:TRUE
|
||||
X-MICROSOFT-MSNCALENDAR-ALLDAYEVENT:TRUE
|
||||
X-MS-OLK-CONFTYPE:0
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
CLASS:PUBLIC
|
||||
UID:2024-05-10BE2854regnap@www.officeholidays.com
|
||||
CREATED:20230114T111157Z
|
||||
DESCRIPTION: Banks are closed on the Friday after Ascension Day in Belgium\n\nBanks are closed\n\nInformation provided by www.officeholidays.com
|
||||
URL:https://www.officeholidays.com/holidays/belgium/ascension-friday
|
||||
DTSTART;VALUE=DATE:20240510
|
||||
DTEND;VALUE=DATE:20240511
|
||||
DTSTAMP:20080101T000000Z
|
||||
LOCATION:Belgium
|
||||
PRIORITY:5
|
||||
LAST-MODIFIED:20210707T000000Z
|
||||
SEQUENCE:1
|
||||
SUMMARY;LANGUAGE=en-us:Bank Holiday (Not a Public Holiday)
|
||||
TRANSP:OPAQUE
|
||||
X-MICROSOFT-CDO-BUSYSTATUS:BUSY
|
||||
X-MICROSOFT-CDO-IMPORTANCE:1
|
||||
X-MICROSOFT-DISALLOW-COUNTER:FALSE
|
||||
X-MS-OLK-ALLOWEXTERNCHECK:TRUE
|
||||
X-MS-OLK-AUTOFILLLOCATION:FALSE
|
||||
X-MICROSOFT-CDO-ALLDAYEVENT:TRUE
|
||||
X-MICROSOFT-MSNCALENDAR-ALLDAYEVENT:TRUE
|
||||
X-MS-OLK-CONFTYPE:0
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
CLASS:PUBLIC
|
||||
UID:2024-05-12BE563regnap@www.officeholidays.com
|
||||
CREATED:20230114T111157Z
|
||||
DESCRIPTION: Mother's Day is celebrated across the world, in more than 50 countries, though not all countries celebrate it on the same day\n\n2nd Sunday in May\n\nInformation provided by www.officeholidays.com
|
||||
URL:https://www.officeholidays.com/holidays/belgium/mothers-day
|
||||
DTSTART;VALUE=DATE:20240512
|
||||
DTEND;VALUE=DATE:20240513
|
||||
DTSTAMP:20080101T000000Z
|
||||
LOCATION:Belgium
|
||||
PRIORITY:5
|
||||
LAST-MODIFIED:20210707T000000Z
|
||||
SEQUENCE:1
|
||||
SUMMARY;LANGUAGE=en-us:Mother's Day (Not a Public Holiday)
|
||||
TRANSP:OPAQUE
|
||||
X-MICROSOFT-CDO-BUSYSTATUS:BUSY
|
||||
X-MICROSOFT-CDO-IMPORTANCE:1
|
||||
X-MICROSOFT-DISALLOW-COUNTER:FALSE
|
||||
X-MS-OLK-ALLOWEXTERNCHECK:TRUE
|
||||
X-MS-OLK-AUTOFILLLOCATION:FALSE
|
||||
X-MICROSOFT-CDO-ALLDAYEVENT:TRUE
|
||||
X-MICROSOFT-MSNCALENDAR-ALLDAYEVENT:TRUE
|
||||
X-MS-OLK-CONFTYPE:0
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
CLASS:PUBLIC
|
||||
UID:2024-05-19BE1012regnap@www.officeholidays.com
|
||||
CREATED:20230114T111157Z
|
||||
DESCRIPTION: Celebrates the gift of the Holy Spirit. It is celebrated on the seventh Sunday after Easter\n\n50 Days after Easter\n\nInformation provided by www.officeholidays.com
|
||||
URL:https://www.officeholidays.com/holidays/belgium/pentecost-sunday
|
||||
DTSTART;VALUE=DATE:20240519
|
||||
DTEND;VALUE=DATE:20240520
|
||||
DTSTAMP:20080101T000000Z
|
||||
LOCATION:Belgium
|
||||
PRIORITY:5
|
||||
LAST-MODIFIED:20221129T000000Z
|
||||
SEQUENCE:1
|
||||
SUMMARY;LANGUAGE=en-us:Pentecost Sunday (Not a Public Holiday)
|
||||
TRANSP:OPAQUE
|
||||
X-MICROSOFT-CDO-BUSYSTATUS:BUSY
|
||||
X-MICROSOFT-CDO-IMPORTANCE:1
|
||||
X-MICROSOFT-DISALLOW-COUNTER:FALSE
|
||||
X-MS-OLK-ALLOWEXTERNCHECK:TRUE
|
||||
X-MS-OLK-AUTOFILLLOCATION:FALSE
|
||||
X-MICROSOFT-CDO-ALLDAYEVENT:TRUE
|
||||
X-MICROSOFT-MSNCALENDAR-ALLDAYEVENT:TRUE
|
||||
X-MS-OLK-CONFTYPE:0
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
CLASS:PUBLIC
|
||||
UID:2024-05-20BE927regcountry@www.officeholidays.com
|
||||
CREATED:20230114T111157Z
|
||||
DESCRIPTION: Commemorates the coming of the Holy Spirit in the form of flames to the Apostles.\n\n7th Monday after Easter\n\nInformation provided by www.officeholidays.com
|
||||
URL:https://www.officeholidays.com/holidays/belgium/whit-monday
|
||||
DTSTART;VALUE=DATE:20240520
|
||||
DTEND;VALUE=DATE:20240521
|
||||
DTSTAMP:20080101T000000Z
|
||||
LOCATION:Belgium
|
||||
PRIORITY:5
|
||||
LAST-MODIFIED:20220620T000000Z
|
||||
SEQUENCE:1
|
||||
SUMMARY;LANGUAGE=en-us:Pentecost Monday
|
||||
TRANSP:OPAQUE
|
||||
X-MICROSOFT-CDO-BUSYSTATUS:BUSY
|
||||
X-MICROSOFT-CDO-IMPORTANCE:1
|
||||
X-MICROSOFT-DISALLOW-COUNTER:FALSE
|
||||
X-MS-OLK-ALLOWEXTERNCHECK:TRUE
|
||||
X-MS-OLK-AUTOFILLLOCATION:FALSE
|
||||
X-MICROSOFT-CDO-ALLDAYEVENT:TRUE
|
||||
X-MICROSOFT-MSNCALENDAR-ALLDAYEVENT:TRUE
|
||||
X-MS-OLK-CONFTYPE:0
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
CLASS:PUBLIC
|
||||
UID:2024-06-09BE290regnap@www.officeholidays.com
|
||||
CREATED:20230114T111157Z
|
||||
DESCRIPTION: Not a public holiday. Father's Day is a celebration that honours the role of fathers\n\n2nd Sunday in June. Not a public holiday\n\nInformation provided by www.officeholidays.com
|
||||
URL:https://www.officeholidays.com/holidays/belgium/fathers-day
|
||||
DTSTART;VALUE=DATE:20240609
|
||||
DTEND;VALUE=DATE:20240610
|
||||
DTSTAMP:20080101T000000Z
|
||||
LOCATION:Belgium
|
||||
PRIORITY:5
|
||||
LAST-MODIFIED:20210707T000000Z
|
||||
SEQUENCE:1
|
||||
SUMMARY;LANGUAGE=en-us:Father's Day (Not a Public Holiday)
|
||||
TRANSP:OPAQUE
|
||||
X-MICROSOFT-CDO-BUSYSTATUS:BUSY
|
||||
X-MICROSOFT-CDO-IMPORTANCE:1
|
||||
X-MICROSOFT-DISALLOW-COUNTER:FALSE
|
||||
X-MS-OLK-ALLOWEXTERNCHECK:TRUE
|
||||
X-MS-OLK-AUTOFILLLOCATION:FALSE
|
||||
X-MICROSOFT-CDO-ALLDAYEVENT:TRUE
|
||||
X-MICROSOFT-MSNCALENDAR-ALLDAYEVENT:TRUE
|
||||
X-MS-OLK-CONFTYPE:0
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
CLASS:PUBLIC
|
||||
UID:2024-07-21BE82regcountry@www.officeholidays.com
|
||||
CREATED:20230114T111157Z
|
||||
DESCRIPTION: Independence Day celebrates the separation of Belgium from the Netherlands in 1831\n\nNational Day\n\nInformation provided by www.officeholidays.com
|
||||
URL:https://www.officeholidays.com/holidays/belgium/belgium-independence-day
|
||||
DTSTART;VALUE=DATE:20240721
|
||||
DTEND;VALUE=DATE:20240722
|
||||
DTSTAMP:20080101T000000Z
|
||||
LOCATION:Belgium
|
||||
PRIORITY:5
|
||||
LAST-MODIFIED:20210707T000000Z
|
||||
SEQUENCE:1
|
||||
SUMMARY;LANGUAGE=en-us:Independence Day
|
||||
TRANSP:OPAQUE
|
||||
X-MICROSOFT-CDO-BUSYSTATUS:BUSY
|
||||
X-MICROSOFT-CDO-IMPORTANCE:1
|
||||
X-MICROSOFT-DISALLOW-COUNTER:FALSE
|
||||
X-MS-OLK-ALLOWEXTERNCHECK:TRUE
|
||||
X-MS-OLK-AUTOFILLLOCATION:FALSE
|
||||
X-MICROSOFT-CDO-ALLDAYEVENT:TRUE
|
||||
X-MICROSOFT-MSNCALENDAR-ALLDAYEVENT:TRUE
|
||||
X-MS-OLK-CONFTYPE:0
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
CLASS:PUBLIC
|
||||
UID:2024-08-15BE42regcountry@www.officeholidays.com
|
||||
CREATED:20230114T111157Z
|
||||
DESCRIPTION: The Feast of the Assumption is the principal feast of the Blessed Virgin, the mother of Jesus Christ\n\n\n\nInformation provided by www.officeholidays.com
|
||||
URL:https://www.officeholidays.com/holidays/belgium/assumption-day
|
||||
DTSTART;VALUE=DATE:20240815
|
||||
DTEND;VALUE=DATE:20240816
|
||||
DTSTAMP:20080101T000000Z
|
||||
LOCATION:Belgium
|
||||
PRIORITY:5
|
||||
LAST-MODIFIED:20210707T000000Z
|
||||
SEQUENCE:1
|
||||
SUMMARY;LANGUAGE=en-us:Assumption Day
|
||||
TRANSP:OPAQUE
|
||||
X-MICROSOFT-CDO-BUSYSTATUS:BUSY
|
||||
X-MICROSOFT-CDO-IMPORTANCE:1
|
||||
X-MICROSOFT-DISALLOW-COUNTER:FALSE
|
||||
X-MS-OLK-ALLOWEXTERNCHECK:TRUE
|
||||
X-MS-OLK-AUTOFILLLOCATION:FALSE
|
||||
X-MICROSOFT-CDO-ALLDAYEVENT:TRUE
|
||||
X-MICROSOFT-MSNCALENDAR-ALLDAYEVENT:TRUE
|
||||
X-MS-OLK-CONFTYPE:0
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
CLASS:PUBLIC
|
||||
UID:2024-09-27BE325regregion@www.officeholidays.com
|
||||
CREATED:20230114T111157Z
|
||||
DESCRIPTION: On 24 June 1975, the date of 27 September was selected by the French Community as French Community Day. It was first celebrated later that same year\n\n\n\nInformation provided by www.officeholidays.com
|
||||
URL:https://www.officeholidays.com/holidays/belgium/french-community-holiday
|
||||
DTSTART;VALUE=DATE:20240927
|
||||
DTEND;VALUE=DATE:20240928
|
||||
DTSTAMP:20080101T000000Z
|
||||
LOCATION:Brabant wallon, Hainaut, Liège, Luxemburg, Namur
|
||||
PRIORITY:5
|
||||
LAST-MODIFIED:20191229T000000Z
|
||||
SEQUENCE:1
|
||||
SUMMARY;LANGUAGE=en-us:French Community Holiday (Regional Holiday)
|
||||
TRANSP:OPAQUE
|
||||
X-MICROSOFT-CDO-BUSYSTATUS:BUSY
|
||||
X-MICROSOFT-CDO-IMPORTANCE:1
|
||||
X-MICROSOFT-DISALLOW-COUNTER:FALSE
|
||||
X-MS-OLK-ALLOWEXTERNCHECK:TRUE
|
||||
X-MS-OLK-AUTOFILLLOCATION:FALSE
|
||||
X-MICROSOFT-CDO-ALLDAYEVENT:TRUE
|
||||
X-MICROSOFT-MSNCALENDAR-ALLDAYEVENT:TRUE
|
||||
X-MS-OLK-CONFTYPE:0
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
CLASS:PUBLIC
|
||||
UID:2024-11-01BE9regcountry@www.officeholidays.com
|
||||
CREATED:20230114T111157Z
|
||||
DESCRIPTION: Pope Boniface IV dedicated the day as a holiday to honour the Blessed Virgin Mary and all martyrs\n\n\n\nInformation provided by www.officeholidays.com
|
||||
URL:https://www.officeholidays.com/holidays/belgium/all-saints-day
|
||||
DTSTART;VALUE=DATE:20241101
|
||||
DTEND;VALUE=DATE:20241102
|
||||
DTSTAMP:20080101T000000Z
|
||||
LOCATION:Belgium
|
||||
PRIORITY:5
|
||||
LAST-MODIFIED:20210707T000000Z
|
||||
SEQUENCE:1
|
||||
SUMMARY;LANGUAGE=en-us:All Saints' Day
|
||||
TRANSP:OPAQUE
|
||||
X-MICROSOFT-CDO-BUSYSTATUS:BUSY
|
||||
X-MICROSOFT-CDO-IMPORTANCE:1
|
||||
X-MICROSOFT-DISALLOW-COUNTER:FALSE
|
||||
X-MS-OLK-ALLOWEXTERNCHECK:TRUE
|
||||
X-MS-OLK-AUTOFILLLOCATION:FALSE
|
||||
X-MICROSOFT-CDO-ALLDAYEVENT:TRUE
|
||||
X-MICROSOFT-MSNCALENDAR-ALLDAYEVENT:TRUE
|
||||
X-MS-OLK-CONFTYPE:0
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
CLASS:PUBLIC
|
||||
UID:2024-11-02BE10regnap@www.officeholidays.com
|
||||
CREATED:20230114T111157Z
|
||||
DESCRIPTION: Dedicated to the remembrance of the departed. All Souls' Day follows All Saints' Day.\n\nNot a public holiday\n\nInformation provided by www.officeholidays.com
|
||||
URL:https://www.officeholidays.com/holidays/belgium/all-souls-day
|
||||
DTSTART;VALUE=DATE:20241102
|
||||
DTEND;VALUE=DATE:20241103
|
||||
DTSTAMP:20080101T000000Z
|
||||
LOCATION:Belgium
|
||||
PRIORITY:5
|
||||
LAST-MODIFIED:20210707T000000Z
|
||||
SEQUENCE:1
|
||||
SUMMARY;LANGUAGE=en-us:All Souls' Day (Not a Public Holiday)
|
||||
TRANSP:OPAQUE
|
||||
X-MICROSOFT-CDO-BUSYSTATUS:BUSY
|
||||
X-MICROSOFT-CDO-IMPORTANCE:1
|
||||
X-MICROSOFT-DISALLOW-COUNTER:FALSE
|
||||
X-MS-OLK-ALLOWEXTERNCHECK:TRUE
|
||||
X-MS-OLK-AUTOFILLLOCATION:FALSE
|
||||
X-MICROSOFT-CDO-ALLDAYEVENT:TRUE
|
||||
X-MICROSOFT-MSNCALENDAR-ALLDAYEVENT:TRUE
|
||||
X-MS-OLK-CONFTYPE:0
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
CLASS:PUBLIC
|
||||
UID:2024-11-11BE705regcountry@www.officeholidays.com
|
||||
CREATED:20230114T111157Z
|
||||
DESCRIPTION: Observed on 11th November to recall the end of World War I on that date in 1918 and honor the veterans of both World Wars.\n\n\n\nInformation provided by www.officeholidays.com
|
||||
URL:https://www.officeholidays.com/holidays/belgium/remembrance-day
|
||||
DTSTART;VALUE=DATE:20241111
|
||||
DTEND;VALUE=DATE:20241112
|
||||
DTSTAMP:20080101T000000Z
|
||||
LOCATION:Belgium
|
||||
PRIORITY:5
|
||||
LAST-MODIFIED:20211022T000000Z
|
||||
SEQUENCE:1
|
||||
SUMMARY;LANGUAGE=en-us:Armistice Day
|
||||
TRANSP:OPAQUE
|
||||
X-MICROSOFT-CDO-BUSYSTATUS:BUSY
|
||||
X-MICROSOFT-CDO-IMPORTANCE:1
|
||||
X-MICROSOFT-DISALLOW-COUNTER:FALSE
|
||||
X-MS-OLK-ALLOWEXTERNCHECK:TRUE
|
||||
X-MS-OLK-AUTOFILLLOCATION:FALSE
|
||||
X-MICROSOFT-CDO-ALLDAYEVENT:TRUE
|
||||
X-MICROSOFT-MSNCALENDAR-ALLDAYEVENT:TRUE
|
||||
X-MS-OLK-CONFTYPE:0
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
CLASS:PUBLIC
|
||||
UID:2024-11-15BE-WLG337regregion@www.officeholidays.com
|
||||
CREATED:20230114T111157Z
|
||||
DESCRIPTION: Day of the German-speaking Community of Belgium\n\n\n\nInformation provided by www.officeholidays.com
|
||||
URL:https://www.officeholidays.com/holidays/belgium/liege/german-community-day
|
||||
DTSTART;VALUE=DATE:20241115
|
||||
DTEND;VALUE=DATE:20241116
|
||||
DTSTAMP:20080101T000000Z
|
||||
LOCATION:Liège
|
||||
PRIORITY:5
|
||||
LAST-MODIFIED:20191229T000000Z
|
||||
SEQUENCE:1
|
||||
SUMMARY;LANGUAGE=en-us:German Community Day (Regional Holiday)
|
||||
TRANSP:OPAQUE
|
||||
X-MICROSOFT-CDO-BUSYSTATUS:BUSY
|
||||
X-MICROSOFT-CDO-IMPORTANCE:1
|
||||
X-MICROSOFT-DISALLOW-COUNTER:FALSE
|
||||
X-MS-OLK-ALLOWEXTERNCHECK:TRUE
|
||||
X-MS-OLK-AUTOFILLLOCATION:FALSE
|
||||
X-MICROSOFT-CDO-ALLDAYEVENT:TRUE
|
||||
X-MICROSOFT-MSNCALENDAR-ALLDAYEVENT:TRUE
|
||||
X-MS-OLK-CONFTYPE:0
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
CLASS:PUBLIC
|
||||
UID:2024-12-06BE298regnap@www.officeholidays.com
|
||||
CREATED:20230114T111157Z
|
||||
DESCRIPTION: A day when many children across Europe receive gifts in honour of Saint Nicholas\n\nNot a public holiday\n\nInformation provided by www.officeholidays.com
|
||||
URL:https://www.officeholidays.com/holidays/belgium/feast-of-st-nicholas
|
||||
DTSTART;VALUE=DATE:20241206
|
||||
DTEND;VALUE=DATE:20241207
|
||||
DTSTAMP:20080101T000000Z
|
||||
LOCATION:Belgium
|
||||
PRIORITY:5
|
||||
LAST-MODIFIED:20210707T000000Z
|
||||
SEQUENCE:1
|
||||
SUMMARY;LANGUAGE=en-us:Feast of St. Nicholas (Not a Public Holiday)
|
||||
TRANSP:OPAQUE
|
||||
X-MICROSOFT-CDO-BUSYSTATUS:BUSY
|
||||
X-MICROSOFT-CDO-IMPORTANCE:1
|
||||
X-MICROSOFT-DISALLOW-COUNTER:FALSE
|
||||
X-MS-OLK-ALLOWEXTERNCHECK:TRUE
|
||||
X-MS-OLK-AUTOFILLLOCATION:FALSE
|
||||
X-MICROSOFT-CDO-ALLDAYEVENT:TRUE
|
||||
X-MICROSOFT-MSNCALENDAR-ALLDAYEVENT:TRUE
|
||||
X-MS-OLK-CONFTYPE:0
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
CLASS:PUBLIC
|
||||
UID:2024-12-25BE181regcountry@www.officeholidays.com
|
||||
CREATED:20230114T111157Z
|
||||
DESCRIPTION: Christmas celebrates the Nativity of Jesus which according to tradition took place on December 25th 1 BC\n\n\n\nInformation provided by www.officeholidays.com
|
||||
URL:https://www.officeholidays.com/holidays/belgium/christmas-day
|
||||
DTSTART;VALUE=DATE:20241225
|
||||
DTEND;VALUE=DATE:20241226
|
||||
DTSTAMP:20080101T000000Z
|
||||
LOCATION:Belgium
|
||||
PRIORITY:5
|
||||
LAST-MODIFIED:20210707T000000Z
|
||||
SEQUENCE:1
|
||||
SUMMARY;LANGUAGE=en-us:Christmas Day
|
||||
TRANSP:OPAQUE
|
||||
X-MICROSOFT-CDO-BUSYSTATUS:BUSY
|
||||
X-MICROSOFT-CDO-IMPORTANCE:1
|
||||
X-MICROSOFT-DISALLOW-COUNTER:FALSE
|
||||
X-MS-OLK-ALLOWEXTERNCHECK:TRUE
|
||||
X-MS-OLK-AUTOFILLLOCATION:FALSE
|
||||
X-MICROSOFT-CDO-ALLDAYEVENT:TRUE
|
||||
X-MICROSOFT-MSNCALENDAR-ALLDAYEVENT:TRUE
|
||||
X-MS-OLK-CONFTYPE:0
|
||||
END:VEVENT
|
||||
END:VCALENDAR
|
148
src/caching.rs
Normal file
148
src/caching.rs
Normal file
|
@ -0,0 +1,148 @@
|
|||
use chrono::{DateTime, Utc};
|
||||
use icalendar::{Calendar, parser};
|
||||
use rusqlite::{Connection, Error, params, Rows, Statement};
|
||||
#[cfg(not(test))]
|
||||
use log::{debug, info, warn};
|
||||
#[cfg(test)]
|
||||
use std::{println as debug, println as warn, println as info};
|
||||
|
||||
|
||||
use crate::fetcher::{Remote};
|
||||
|
||||
struct CacheEntry {
|
||||
hash: String,
|
||||
update_time: Option<String>,
|
||||
calendar: Option<String>,
|
||||
}
|
||||
|
||||
pub struct CachedRemote {
|
||||
cache_delay: Option<u32>,
|
||||
remote: Remote,
|
||||
cache_db: Connection,
|
||||
}
|
||||
|
||||
impl CachedRemote {
|
||||
pub fn cache(&self) -> Result<(), Error> {
|
||||
debug!("Start caching of {:?}", self.remote);
|
||||
if self.cache_delay.is_none() || self.remote.is_local() {
|
||||
debug!("No need to cache {:?}", self.remote);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
self.force_cache()
|
||||
}
|
||||
|
||||
pub fn force_cache(&self) -> Result<(), Error> {
|
||||
let entry = CacheEntry {
|
||||
hash: self.remote.hash(),
|
||||
update_time: Some(Utc::now().to_rfc3339()),
|
||||
calendar: match self.remote.get() {
|
||||
Some(cal) => Some(parser::unfold(&cal.to_string())),
|
||||
None => None,
|
||||
},
|
||||
};
|
||||
|
||||
self.cache_db.execute("REPLACE INTO cache (hash, update_time, calendar) values (?1, ?2, ?3);",
|
||||
params![&entry.hash, &entry.update_time, &entry.calendar])?;
|
||||
debug!("Cached {:?}", self.remote);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_from_cache(&self) -> Option<Calendar> {
|
||||
info!("Getting {:?} from cache", self.remote);
|
||||
let mut statement: Statement;
|
||||
|
||||
if let Ok(stmt) = self.cache_db.prepare("SELECT calendar FROM cache where hash = ?"){
|
||||
statement = stmt;
|
||||
} else {
|
||||
warn!("Could not prepare statement");
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut rows: Rows;
|
||||
if let Ok(rows_ok) = statement.query([self.remote.hash()]) {
|
||||
rows = rows_ok;
|
||||
} else {
|
||||
warn!("Could not get a row from the query");
|
||||
return None;
|
||||
}
|
||||
|
||||
let cal_str: String;
|
||||
if let Ok(Some(r)) = rows.next(){
|
||||
if let Ok(s) = r.get(0) {
|
||||
cal_str = s;
|
||||
} else {
|
||||
return None
|
||||
}
|
||||
} else {
|
||||
warn!("No result from query");
|
||||
return None;
|
||||
}
|
||||
|
||||
if let Ok(cal) = parser::read_calendar(&cal_str) {
|
||||
debug!("Returned cached from {:?}", self.remote);
|
||||
debug!("{}", cal);
|
||||
return Some(Calendar::from(cal));
|
||||
}
|
||||
|
||||
warn!("Unable to parse calendar from cache");
|
||||
None
|
||||
}
|
||||
|
||||
pub fn get(&self) -> Option<Calendar> {
|
||||
if self.cache_delay.is_none() || self.remote.is_local() {
|
||||
return self.remote.get();
|
||||
}
|
||||
|
||||
if let Some(cal) = self.get_from_cache() {
|
||||
return Some(cal);
|
||||
}
|
||||
|
||||
self.remote.get()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
fn create_cache(path: Option<String>) -> Result<Connection, Error> {
|
||||
let conn = match path {
|
||||
Some(path) => Connection::open(path)?,
|
||||
None => Connection::open_in_memory()?,
|
||||
};
|
||||
|
||||
conn.execute(r#"
|
||||
CREATE TABLE IF NOT EXISTS cache (
|
||||
hash TEXT PRIMARY KEY,
|
||||
update_time TEXT,
|
||||
calendar BLOB
|
||||
);"#, ())?;
|
||||
|
||||
Ok(conn)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn cache_calendar() {
|
||||
env_logger::init();
|
||||
|
||||
let db = create_cache(Some(String::from("test.db"))).unwrap();
|
||||
|
||||
let cached_remote = CachedRemote {
|
||||
cache_delay: Some(10),
|
||||
remote: Remote::new("resources/test/belgium.ics"),
|
||||
cache_db: db,
|
||||
};
|
||||
|
||||
debug!("Test in progress");
|
||||
cached_remote.force_cache().unwrap();
|
||||
|
||||
let from_cache = cached_remote.get_from_cache().unwrap();
|
||||
let from_remote = cached_remote.remote.get().unwrap();
|
||||
|
||||
assert_eq!(from_remote, from_cache);
|
||||
|
||||
}
|
||||
}
|
41
src/config.rs
Normal file
41
src/config.rs
Normal file
|
@ -0,0 +1,41 @@
|
|||
use std::{fs, io};
|
||||
use std::path::{Path, PathBuf};
|
||||
use url::Url;
|
||||
|
||||
|
||||
|
||||
|
||||
struct Config {
|
||||
directory: Box<Path>,
|
||||
}
|
||||
|
||||
|
||||
fn list_files(directory: &Path) -> Result<Vec<PathBuf>, io::Error> {
|
||||
Ok(fs::read_dir(directory)?
|
||||
.into_iter()
|
||||
.filter(|r| r.is_ok()) // Get rid of Err variants for Result<DirEntry>
|
||||
.map(|r| r.unwrap().path()) // This is safe, since we only have the Ok variants
|
||||
.filter(|r| r.is_file()) // Filter to keep only files
|
||||
.collect())
|
||||
}
|
||||
|
||||
fn parse_config(directory: &str) -> Result<Config, String> {
|
||||
let files = list_files(Path::new(directory))
|
||||
.expect("unable to list files in the configuration directory");
|
||||
|
||||
for file in files {
|
||||
println!("{:?}", file.display());
|
||||
}
|
||||
|
||||
Ok(Config { directory: Box::from(Path::new("")) })
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn parsing_config() {
|
||||
parse_config("resources/test").unwrap();
|
||||
}
|
||||
}
|
144
src/fetcher.rs
Normal file
144
src/fetcher.rs
Normal file
|
@ -0,0 +1,144 @@
|
|||
use std::fs;
|
||||
use std::path::Path;
|
||||
use url::Url;
|
||||
use icalendar::{Calendar, parser};
|
||||
use sha2::{Sha256, Digest};
|
||||
use sha2::digest::FixedOutput;
|
||||
#[cfg(not(test))]
|
||||
use log::{debug, info, warn};
|
||||
#[cfg(test)]
|
||||
use std::{println as debug, println as warn, println as info};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Remote {
|
||||
location: Location,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Location {
|
||||
Online(Url),
|
||||
Local(Box<Path>),
|
||||
}
|
||||
|
||||
|
||||
impl Remote {
|
||||
|
||||
pub fn new(location: &str) -> Remote {
|
||||
let location = match Url::parse(location) {
|
||||
Ok(url) => Location::Online(url),
|
||||
Err(_) => Location::Local(Box::from(Path::new(location)))
|
||||
};
|
||||
|
||||
Remote {location}
|
||||
}
|
||||
|
||||
pub fn is_local(&self) -> bool {
|
||||
match &self.location {
|
||||
Location::Online(_) => false,
|
||||
Location::Local(_) => true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn hash(&self) -> String {
|
||||
let path = match &self.location {
|
||||
Location::Online(url) => url.to_string(),
|
||||
Location::Local(path) => String::from(path.to_str().unwrap_or("")),
|
||||
};
|
||||
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(path);
|
||||
|
||||
format!("{:x}", hasher.finalize_fixed())
|
||||
}
|
||||
|
||||
pub fn get(&self) -> Option<Calendar> {
|
||||
debug!("Getting ics from {:?}", self.location);
|
||||
let content = match &self.location {
|
||||
Location::Online(url) => {
|
||||
match get_url_content(url.to_string()) {
|
||||
Ok(content) => Some(content),
|
||||
Err(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
Location::Local(path) => {
|
||||
match fs::read_to_string(path) {
|
||||
Ok(content) => Some(content),
|
||||
Err(_) => None,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(content) = content {
|
||||
if let Ok(cal) = parser::read_calendar(&content) {
|
||||
return Some(Calendar::from(cal));
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
fn get_url_content(url: String) -> Result<String, reqwest::Error> {
|
||||
let content = reqwest::blocking::get(url)?
|
||||
.text()?;
|
||||
|
||||
Ok(content)
|
||||
}
|
||||
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::path::Path;
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn get_ics_from_file() {
|
||||
|
||||
let remote = Remote{
|
||||
location: Location::Local(Box::from(Path::new("resources/test/belgium.ics"))),
|
||||
};
|
||||
|
||||
let cal = remote.get();
|
||||
|
||||
assert!(cal.is_some());
|
||||
|
||||
let cal = cal.unwrap();
|
||||
|
||||
// Calendar has 40 events
|
||||
assert_eq!(cal.len(), 40);
|
||||
assert_eq!(cal.get_name().unwrap(), "Belgium Holidays");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cannot_parse_calendar() {
|
||||
|
||||
let remote = Remote {
|
||||
location: Location::Online(Url::parse("https://example.com/").unwrap()),
|
||||
};
|
||||
|
||||
let cal = remote.get();
|
||||
|
||||
if let Some(cal) = cal {
|
||||
assert_eq!(cal.len(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hash_test() {
|
||||
let remote = Remote {
|
||||
location: Location::Local(Box::from(Path::new("file.ics"))),
|
||||
};
|
||||
|
||||
let remote1 = Remote {
|
||||
location: Location::Local(Box::from(Path::new("file.ics"))),
|
||||
};
|
||||
|
||||
assert_eq!(remote.hash(), remote1.hash())
|
||||
}
|
||||
}
|
18
src/lib.rs
Normal file
18
src/lib.rs
Normal file
|
@ -0,0 +1,18 @@
|
|||
mod config;
|
||||
mod fetcher;
|
||||
mod caching;
|
||||
|
||||
pub fn add(left: usize, right: usize) -> usize {
|
||||
left + right
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn it_works() {
|
||||
let result = add(2, 2);
|
||||
assert_eq!(result, 4);
|
||||
}
|
||||
}
|
1
src/main.rs
Normal file
1
src/main.rs
Normal file
|
@ -0,0 +1 @@
|
|||
pub fn main() {}
|
Loading…
Add table
Reference in a new issue