diff --git a/README.rst b/README.rst
index b8959fd..49068d7 100644
--- a/README.rst
+++ b/README.rst
@@ -81,3 +81,12 @@ It can theoretically make it easier to synchronize events across multiple time
zones without actually knowing the time difference; just tell the other
attending parties that you eat your lunch during Ladybug hour, or that your
event is taking place between Gourd and Soup hours.
+
+Generated SVG
+=============
+
+With the ``poetry run print_svg`` command, you will get an image similar to this:
+
+.. image:: example-output.svg
+ :width: 700
+ :alt: An image rendered by this software
diff --git a/example-output.svg b/example-output.svg
new file mode 100644
index 0000000..7cae419
--- /dev/null
+++ b/example-output.svg
@@ -0,0 +1,681 @@
+
+
diff --git a/pyproject.toml b/pyproject.toml
index 2fd257a..a7f2e04 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -23,6 +23,7 @@ types-pytz = "^2021.3.6"
[tool.poetry.scripts]
print_data = "seasonal_clock:main"
+print_svg = "seasonal_clock.svg:print_svg"
[build-system]
requires = ["poetry-core>=1.0.0"]
diff --git a/seasonal_clock/svg.py b/seasonal_clock/svg.py
new file mode 100644
index 0000000..e06f0fc
--- /dev/null
+++ b/seasonal_clock/svg.py
@@ -0,0 +1,441 @@
+from datetime import datetime, time
+from math import ceil, cos, pi, radians, sin
+from typing import Union
+
+from astral import LocationInfo, moon
+from pytz import UTC, timezone
+
+from .config import load_config
+from .times import collect_day_parts
+
+HOURS_AMPM = (
+ 'Midnight',
+ '1a',
+ '2a',
+ '3a',
+ '4a',
+ '5a',
+ '6a',
+ '7a',
+ '8a',
+ '9a',
+ '10a',
+ '11a',
+ 'Noon',
+ '1p',
+ '2p',
+ '3p',
+ '4p',
+ '5p',
+ '6p',
+ '7p',
+ '8p',
+ '9p',
+ '10p',
+ '11p',
+)
+HOURS_24 = (
+ 'Midnight',
+ '1',
+ '2',
+ '3',
+ '4',
+ '5',
+ '6',
+ '7',
+ '8',
+ '9',
+ '10',
+ '11',
+ 'Noon',
+ '13',
+ '14',
+ '15',
+ '16',
+ '17',
+ '18',
+ '19',
+ '20',
+ '21',
+ '22',
+ '23',
+)
+HOUR_NAMES = (
+ 'Candle',
+ 'Ice',
+ 'Comet',
+ 'Thimble',
+ 'Root',
+ 'Mist',
+ 'Sprout',
+ 'Rainbow',
+ 'Worm',
+ 'Bud',
+ 'Blossom',
+ 'Ladybug',
+ 'Geese',
+ 'Dust',
+ 'Peach',
+ 'Fog',
+ 'Acorn',
+ 'Gourd',
+ 'Soup',
+ 'Crow',
+ 'Mushroom',
+ 'Thunder',
+ 'Frost',
+ 'Lantern',
+)
+SEASONS = ('winter', 'spring', 'summer', 'autumn')
+
+
+def hex_to_rgb(hex_color: str) -> str:
+ r = int(hex_color[1:3], 16)
+ g = int(hex_color[3:5], 16)
+ b = int(hex_color[5:7], 16)
+
+ return f'rgb({r}, {g}, {b})'
+
+
+def get_utc_offset(utc_time: datetime, local_time: datetime):
+ assert utc_time.tzinfo
+ assert local_time.tzinfo
+
+ utc = utc_time.replace(tzinfo=None)
+ local = utc_time.replace(tzinfo=None)
+
+ return (utc - local).total_seconds()
+
+
+def seconds_to_degrees(seconds: float) -> float:
+ one_second = 360 / 86400
+
+ return seconds * one_second
+
+
+def indent_lines(string: str, indentation: int = 8) -> str:
+ return '\n'.join((' ' * indentation) + line for line in string.split('\n')) + '\n'
+
+
+def time_to_degrees(timestamp: Union[datetime, time]) -> float:
+ return seconds_to_degrees(
+ timestamp.hour * 3600 + timestamp.minute * 60 + timestamp.second
+ )
+
+
+def hour_name_path(image_width: int, outer_r: float, ring_width: float) -> str:
+ radius = outer_r - ring_width / 2
+ delta_x = radius * sin(radians(15) / 2)
+ delta_y = radius * (1 - cos(radians(15) / 2))
+ x1 = image_width / 2 - delta_x
+ y1 = (image_width / 2 - radius) + delta_y
+
+ return f''''''
+
+
+def hour_marker(
+ hour: int,
+ hour_name: str,
+ image_width: int,
+ outer_r: float,
+ ring_width: float,
+ hour_name_font_size: float,
+ utc_font_size: float,
+ indent: int = 8,
+) -> str:
+ season = SEASONS[ceil((hour + 1) / 6) - 1]
+ rotation = hour * 15
+
+ delta_x = outer_r * sin(radians(15) / 2)
+ delta_y = outer_r * (1 - cos(radians(15) / 2))
+
+ s_delta_x = 0 - ring_width * sin(radians(15) / 2)
+ s_delta_y = ring_width * cos(radians(15) / 2)
+
+ i_delta_x = -2 * (outer_r - ring_width) * sin(radians(15) / 2)
+
+ x1 = image_width / 2 - delta_x
+ y1 = (image_width / 2 - outer_r) + delta_y
+
+ hour_name_y = image_width / 2 - outer_r + ring_width / 2 + hour_name_font_size / 4
+ utc_hour_y = image_width / 2 - outer_r + ring_width + utc_font_size
+
+ ret = f'''
+
+ {hour_name}
+ U {hour:02d}
+'''
+
+ return indent_lines(ret, indent)
+
+
+def local_hour(
+ image_width: int,
+ outer_r: float,
+ hour_num: int,
+ hour: str,
+ font_size: float,
+ indent: int = 8,
+) -> str:
+ rotation = hour_num * 15
+
+ return indent_lines(
+ f'''{hour}''',
+ indent,
+ )
+
+
+def get_range_path(
+ image_width: int,
+ radius: float,
+ range_name: str,
+ start_time: time,
+ end_time: time,
+ outer: bool = True,
+ indent: int = 8,
+) -> str:
+ start_deg = time_to_degrees(start_time)
+ end_deg = time_to_degrees(end_time)
+
+ start_delta_x = radius * sin(radians(start_deg))
+ start_delta_y = radius * (1 - cos(radians(start_deg)))
+ end_delta_x = radius * sin(radians(end_deg))
+ end_delta_y = radius * (1 - cos(radians(end_deg)))
+
+ deg_diff = end_deg - start_deg
+
+ large_arc_flag = 0 if abs(deg_diff) >= 180 else 1
+
+ return indent_lines(
+ f'''''',
+ indent,
+ )
+
+
+def get_moon_path(image_width: int, radius: float, moon_phase: float, indent: int = 8):
+ handle_x_pos = radius * 1.34
+ handle_y_pos = radius * 0.88
+ min_x = image_width / 2 - handle_x_pos
+ max_x = min_x + 2 * handle_x_pos
+ top_y = image_width / 2 - handle_y_pos
+ bottom_y = image_width / 2 + handle_y_pos
+
+ if moon_phase < 14:
+ h1_x = min_x + 2 * handle_x_pos * (1 - moon_phase / 14)
+ h2_x = max_x
+ else:
+ h1_x = min_x
+ h2_x = max_x + 2 * handle_x_pos * (1 - moon_phase / 14)
+
+ ret = f''''''
+
+ return indent_lines(ret, indent)
+
+
+def get_svg_data(
+ local_time: datetime,
+ image_width: int = 700,
+ line_width: str = '2px',
+ line_color: str = '#eebb55',
+ utc_color: str = '#5b4426',
+ hname_stroke_color: str = '#000000',
+ winter_color: str = '#463e6c',
+ spring_color: str = '#375737',
+ summer_color: str = '#715c2b',
+ autumn_color: str = '#6c442c',
+ night_color: str = '#13111e',
+ blue_color: str = '#090177',
+ golden_color: str = '#aa842c',
+ day_color: str = '#7dc5f0',
+ moon_color: str = '#aaaaaa',
+ local_hour_font_size: float = 16.5,
+ hour_name_font_size: float = 13.37699,
+ utc_hour_font_size: float = 15.0234,
+ hour_24: bool = True,
+) -> str:
+ line_rgb = hex_to_rgb(line_color)
+ hname_stroke_rgb = hex_to_rgb(hname_stroke_color)
+ utc_rgb = hex_to_rgb(utc_color)
+ winter_rgb = hex_to_rgb(winter_color)
+ spring_rgb = hex_to_rgb(spring_color)
+ summer_rgb = hex_to_rgb(summer_color)
+ autumn_rgb = hex_to_rgb(autumn_color)
+ night_rgb = hex_to_rgb(night_color)
+ blue_rgb = hex_to_rgb(blue_color)
+ golden_rgb = hex_to_rgb(golden_color)
+ day_rgb = hex_to_rgb(day_color)
+ moon_rgb = hex_to_rgb(moon_color)
+ hours = HOURS_24 if hour_24 else HOURS_AMPM
+ outer_r = image_width / 2 - 3 * hour_name_font_size
+ ring_width = hour_name_font_size * 3
+
+ utc_time = local_time.astimezone(UTC)
+ utc_naive = utc_time.replace(tzinfo=None)
+ local_naive = local_time.replace(tzinfo=None)
+ offset = (local_naive - utc_naive).total_seconds()
+
+ config = load_config()
+
+ location = LocationInfo(
+ config['city'],
+ config['country'],
+ config['timezone'],
+ config['latitude'],
+ config['longitude'],
+ )
+ local_tz = location.tzinfo
+ day_parts = collect_day_parts(location.observer, local_time)
+ day_parts_dict = dict(part[0:2] for part in day_parts)
+ morning_blue_start = day_parts_dict.pop('morning_blue_start')
+ morning_blue_end = day_parts_dict.pop('morning_golden_start')
+ morning_golden_end = day_parts_dict.pop('morning_golden_end')
+ evening_golden_start = day_parts_dict.pop('evening_golden_start')
+ evening_blue_start = day_parts_dict.pop('evening_blue_start')
+ evening_blue_end = day_parts_dict.pop('evening_blue_end')
+ moon_phase = moon.phase(local_time)
+
+ noon = day_parts_dict.pop('noon')
+ midnight = day_parts_dict.pop('midnight')
+
+ ret = f'''
+'''
+
+ return ret
+
+
+def print_svg():
+ utc_now = datetime.utcnow().replace(tzinfo=UTC)
+ local_now = utc_now.astimezone(timezone('Europe/Budapest'))
+
+ print(get_svg_data(local_now))