diff --git a/Docker/lofar-ci/Dockerfile_ci_sas b/Docker/lofar-ci/Dockerfile_ci_sas
index d5d9b7ce6e4cb7cdc2eb0b4c5fead89d55ae6a5f..e756c0d4799af4316fd89f27a7ad42227c0fc3fd 100644
--- a/Docker/lofar-ci/Dockerfile_ci_sas
+++ b/Docker/lofar-ci/Dockerfile_ci_sas
@@ -16,7 +16,7 @@ RUN yum erase -y postgresql postgresql-server postgresql-devel && \
     cd /bin && ln -s /usr/pgsql-9.6/bin/initdb && ln -s /usr/pgsql-9.6/bin/postgres
 ENV PATH /usr/pgsql-9.6/bin:$PATH 
 
-RUN pip3 install cython kombu lxml requests pygcn xmljson mysql-connector-python python-dateutil Django==3.0.9 djangorestframework djangorestframework-xml ldap==1.0.2 flask fabric coverage python-qpid-proton PyGreSQL numpy h5py psycopg2 testing.postgresql Flask-Testing scipy Markdown django-filter python-ldap python-ldap-test ldap3 djangorestframework django-jsonforms django-json-widget django-jsoneditor drf-yasg flex swagger-spec-validator django-auth-ldap mozilla-django-oidc jsonschema comet pyxb==1.2.5 graphviz isodate
+RUN pip3 install cython kombu lxml requests pygcn xmljson mysql-connector-python python-dateutil Django==3.0.9 djangorestframework djangorestframework-xml ldap==1.0.2 flask fabric coverage python-qpid-proton PyGreSQL numpy h5py psycopg2 testing.postgresql Flask-Testing scipy Markdown django-filter python-ldap python-ldap-test ldap3 djangorestframework django-jsonforms django-json-widget django-jsoneditor drf-yasg flex swagger-spec-validator django-auth-ldap mozilla-django-oidc jsonschema comet pyxb==1.2.5 graphviz isodate astropy
 
 # Note: nodejs now comes with npm, do not install the npm package separately, since that will be taken from the epel repo and is conflicting.
 RUN echo "Installing Nodejs packages..." && \
diff --git a/LTA/sip/lib/CMakeLists.txt b/LTA/sip/lib/CMakeLists.txt
index a9851a135a5f82c62c0110b5d802d191a95a2fe0..7cfd5468d9f8430570aeea7bb77a6e4c52b0495e 100644
--- a/LTA/sip/lib/CMakeLists.txt
+++ b/LTA/sip/lib/CMakeLists.txt
@@ -14,6 +14,7 @@ set(_py_files
   constants.py
   visualizer.py
   query.py
+  station_coordinates.py
 )
 
 
@@ -25,6 +26,7 @@ set(resource_files
 python_install(${_py_files}
                DESTINATION lofar/lta/sip)
 
+
 install(FILES ${resource_files}
   DESTINATION ${PYTHON_INSTALL_DIR}/lofar/lta/sip
   COMPONENT ${lower_package_name})
diff --git a/LTA/sip/lib/siplib.py b/LTA/sip/lib/siplib.py
index bb3c6238a14a19117ee2d5379f838ddb6b04f66c..4f89a4fe91f5552972e9b43c0cc4afe903d9d9fc 100644
--- a/LTA/sip/lib/siplib.py
+++ b/LTA/sip/lib/siplib.py
@@ -28,6 +28,7 @@
 from . import ltasip
 import pyxb
 from . import constants
+from . import station_coordinates
 import os
 import uuid
 import xml.dom.minidom
@@ -38,8 +39,6 @@ import logging
 logger = logging.getLogger(__name__)
 
 VERSION = "SIPlib 0.4"
-d = os.path.dirname(os.path.realpath(__file__))
-STATION_CONFIG_PATH = d+'/station_coordinates.conf'
 ltasip.Namespace.setPrefix('sip')
 
 # todo: create docstrings for everything.
@@ -144,30 +143,28 @@ class Station():
 
         __afield1=None
         __afield2=None
-        with open(STATION_CONFIG_PATH, 'r') as f:
-            for line in f.readlines():
-                if line.strip():
-                  field_coords = eval("dict("+line+")")  # literal_eval does not accept dict definition via constructor. Make sure config file is not writable to prevent code execution!
-                  for type in antennafieldtypes:
-                    if field_coords["name"] == name+"_"+type:
-                        __afield=AntennafieldXYZ(
-                            type=type,
+        station_coords = station_coordinates.parse_station_coordinates()
+        for atype in antennafieldtypes:
+            if name+"_"+atype in station_coords.keys():
+                field_coords = station_coords[name+"_"+atype]
+                __afield=AntennafieldXYZ(
+                            type=atype,
                             coordinate_system=field_coords["coordinate_system"],
                             coordinate_unit=constants.LENGTHUNIT_M, # Does this make sense? I have to give a lenght unit accoridng to the XSD, but ICRF should be decimal degrees?!
                             coordinate_x=field_coords["x"],
                             coordinate_y=field_coords["y"],
                             coordinate_z=field_coords["z"])
-                        if not __afield1:
-                            __afield1=__afield
-                        elif not __afield2:
-                            __afield2=__afield
+                if not __afield1:
+                    __afield1=__afield
+                elif not __afield2:
+                    __afield2=__afield
 
         if not __afield1:
-            raise Exception("no matching coordinates found for station:", name,"and fields",str(antennafieldtypes))
+            raise Exception("no matching coordinates found for station:", name, "and fields", str(antennafieldtypes))
 
-        if name.startswith( 'CS' ):
+        if name.startswith('CS'):
             sttype = "Core"
-        elif name.startswith( "RS" ):
+        elif name.startswith("RS"):
             sttype = "Remote"
         else:
             sttype = "International"
diff --git a/LTA/sip/lib/station_coordinates.py b/LTA/sip/lib/station_coordinates.py
new file mode 100644
index 0000000000000000000000000000000000000000..f2952a203d2af5ee5578342eac1af1706c41662c
--- /dev/null
+++ b/LTA/sip/lib/station_coordinates.py
@@ -0,0 +1,44 @@
+#!/usr/bin/env python3
+
+# This module provides functions for easy creation of a Lofar LTA SIP document.
+# It builds upon a Pyxb-generated API from the schema definition, which is very clever but hard to use, since
+# the arguments in class constructors and functions definitions are not verbose and there is no intuitive way
+# to determine the mandatory and optional elements to create a valid SIP document. This module is designed to
+# provide easy-to-use functions that bridges this shortcoming of the Pyxb API.
+#
+# Usage: Import module. Create an instance of Sip.
+#        Add elements through the Sip.add_X functions. Many require instances of other classes of the module.
+#        call getprettyxml() and e.g. save to disk.
+#
+# Note on validation: From construction through every addition, the SIP should remain valid (or throw an error
+# that clearly points out where e.g. a given value does not meet the restrictions of the SIP schema.
+#
+# Note on code structure: This has to be seen as a compromise between elegant and maintainable code with well-
+# structured inheritance close to the schema definition on the one hand, and something more straightforward to use,
+# with flatter hierarchies on the other hand.
+#
+# Note on parameter maps:  The ...Map objects are helper objects to create dictionaries for the commonly used
+# constructor arguments of several other objects. This could alternatively also be implemented via inheritance from
+# a supertype, and indeed is solved like this in the pyxb code. However, this then requires the use of an argument
+# list pointer, which hides the list of required and optional arguments from the user. Alternatively, all arguments
+# have to be mapped in all constructors repeatedly, creating lots of boilerplate code. This is the nicest approach
+# I could think of that keeps the whole thing reasonably maintainable AND usable.
+
+import os
+d = os.path.dirname(os.path.realpath(__file__))
+STATION_CONFIG_PATH = d+'/station_coordinates.conf'
+
+
+def parse_station_coordinates() -> dict:
+    """
+    :return: a dict mapping station field name, e.g. "CS002_LBA", to a dict containing ITRF coordinates
+    """
+    station_coordinates = {}
+    with open(STATION_CONFIG_PATH, 'r') as f:
+        for line in f.readlines():
+            if line.strip():
+                field_coords = eval("dict(" + line + ")")  # literal_eval does not accept dict definition via constructor. Make sure config file is not writable to prevent code execution!
+                station_coordinates[field_coords.pop("name")] = field_coords
+    return station_coordinates
+
+
diff --git a/SAS/TMSS/src/tmss/tmssapp/CMakeLists.txt b/SAS/TMSS/src/tmss/tmssapp/CMakeLists.txt
index 47a6fc110c6e09c09bf272f1ee0f0f04a5a65407..3c5a89286a7c38d5b12fbb41aca524553cf443d7 100644
--- a/SAS/TMSS/src/tmss/tmssapp/CMakeLists.txt
+++ b/SAS/TMSS/src/tmss/tmssapp/CMakeLists.txt
@@ -10,6 +10,7 @@ set(_py_files
     validation.py
     subtasks.py
     tasks.py
+    conversions.py
     )
 
 python_install(${_py_files}
diff --git a/SAS/TMSS/src/tmss/tmssapp/conversions.py b/SAS/TMSS/src/tmss/tmssapp/conversions.py
new file mode 100644
index 0000000000000000000000000000000000000000..e851ecbe396955955f1ae9dc1f32890cb819b53d
--- /dev/null
+++ b/SAS/TMSS/src/tmss/tmssapp/conversions.py
@@ -0,0 +1,41 @@
+from astropy.time import Time
+import astropy.units
+from lofar.lta.sip import station_coordinates
+from datetime import datetime
+from astropy.coordinates.earth import EarthLocation
+
+
+def local_sidereal_time_for_utc_and_station(timestamp: datetime = None,
+                                            station: str = 'CS002',
+                                            field: str = 'LBA',
+                                            kind: str = "apparent"):
+    """
+    calculate local sidereal time for given utc time and station
+    :param timestamp: timestamp as datetime object
+    :param station: station name
+    :param field: antennafield, 'LBA' or 'HBA'
+    :param kind: 'mean' or 'apparent'
+    :return:
+    """
+    if timestamp is None:
+        timestamp = datetime.utcnow()
+    station_coords = station_coordinates.parse_station_coordinates()
+    field_coords = station_coords["%s_%s" % (station, field)]
+    location = EarthLocation.from_geocentric(x=field_coords['x'], y=field_coords['y'], z=field_coords['z'], unit=astropy.units.m)
+    return local_sidereal_time_for_utc_and_longitude(timestamp=timestamp, longitude=location.lon.to_string(decimal=True), kind=kind)
+
+
+def local_sidereal_time_for_utc_and_longitude(timestamp: datetime = None,
+                                              longitude: float = 6.8693028,
+                                              kind: str = "apparent"):
+    """
+    :param timestamp: timestamp as datetime object
+    :param longitude: decimal longitude of observer location (defaults to CS002 LBA center)
+    :param kind: 'mean' or 'apparent'
+    :return:
+    """
+    if timestamp is None:
+        timestamp = datetime.utcnow()
+    t = Time(timestamp, format='datetime', scale='utc')
+    return t.sidereal_time(kind=kind, longitude=longitude)
+
diff --git a/SAS/TMSS/src/tmss/tmssapp/views.py b/SAS/TMSS/src/tmss/tmssapp/views.py
index 4614c940953d2a277b00cf1eb0589ef6efb1edd5..37f7a9cd70e92da9803737dd51b8cd19577e03b9 100644
--- a/SAS/TMSS/src/tmss/tmssapp/views.py
+++ b/SAS/TMSS/src/tmss/tmssapp/views.py
@@ -5,7 +5,9 @@ from django.shortcuts import get_object_or_404, render
 from lofar.sas.tmss.tmss.tmssapp import models
 from lofar.common.json_utils import get_default_json_object_for_schema
 from lofar.sas.tmss.tmss.tmssapp.adapters.parset import convert_to_parset
-
+from datetime import datetime
+import dateutil.parser
+from lofar.sas.tmss.tmss.tmssapp.conversions import local_sidereal_time_for_utc_and_station, local_sidereal_time_for_utc_and_longitude
 
 def subtask_template_default_specification(request, subtask_template_pk:int):
     subtask_template = get_object_or_404(models.SubtaskTemplate, pk=subtask_template_pk)
@@ -23,11 +25,41 @@ def subtask_parset(request, subtask_pk:int):
     subtask = get_object_or_404(models.Subtask, pk=subtask_pk)
     parset = convert_to_parset(subtask)
     return HttpResponse(str(parset), content_type='text/plain')
-    
+
+
 def index(request):
     return render(request, os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), '../../frontend','tmss_webapp/build/index.html'))
     #return render(request, "../../../frontend/frontend_poc/build/index.html")
 
+
 def task_specify_observation(request, pk=None):
     task = get_object_or_404(models.TaskDraft, pk=pk)
     return HttpResponse("response", content_type='text/plain')
+
+
+def utc(request):
+    return HttpResponse(datetime.utcnow().isoformat(), content_type='text/plain')
+
+
+def lst(request):
+    # Handling optional parameters via django paths in urls.py is a pain, we access them on the request directly instead.
+    timestamp = request.GET.get('timestamp', None)
+    station = request.GET.get('station', None)
+    longitude = request.GET.get('longitude', None)
+
+    # conversions
+    if timestamp:
+        timestamp = dateutil.parser.parse(timestamp)  #  isot to datetime
+    if longitude:
+        longitude = float(longitude)
+
+    if station:
+        lst_lon = local_sidereal_time_for_utc_and_station(timestamp, station)
+    elif longitude:
+        lst_lon = local_sidereal_time_for_utc_and_longitude(timestamp, longitude)
+    else:
+        # fall back to defaults
+        lst_lon = local_sidereal_time_for_utc_and_station(timestamp)
+
+    # todo: do we want to return a dict, so users can make sure their parameters were parsed correctly instead?
+    return HttpResponse(str(lst_lon), content_type='text/plain')
\ No newline at end of file
diff --git a/SAS/TMSS/src/tmss/urls.py b/SAS/TMSS/src/tmss/urls.py
index 53146045e08986f1cb8930e993b04129df909610..9b7895326ffebfad99aa9740faa0e649fdc6f608 100644
--- a/SAS/TMSS/src/tmss/urls.py
+++ b/SAS/TMSS/src/tmss/urls.py
@@ -28,6 +28,8 @@ from rest_framework.documentation import include_docs_urls
 from drf_yasg.views import get_schema_view
 from drf_yasg import openapi
 
+from datetime import datetime
+
 #
 # Django style patterns
 #
@@ -54,7 +56,9 @@ urlpatterns = [
     path('docs/', include_docs_urls(title='TMSS API')),
     re_path(r'^swagger(?P<format>\.json|\.yaml)$', swagger_schema_view.without_ui(cache_timeout=0), name='schema-json'),
     path('swagger/', swagger_schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'),
-    path('redoc/', swagger_schema_view.with_ui('redoc', cache_timeout=0), name='schema-redoc')
+    path('redoc/', swagger_schema_view.with_ui('redoc', cache_timeout=0), name='schema-redoc'),
+    path(r'util/utc', views.utc, name="system-utc"),
+    path(r'util/lst', views.lst, name="conversion-lst"),
 ]
 
 
diff --git a/SAS/TMSS/test/CMakeLists.txt b/SAS/TMSS/test/CMakeLists.txt
index b19ddcd546e283f0e176ecaf57711bb3b8b8f03c..769fce231ac3bc18470ae3c974456d4ec089ff68 100644
--- a/SAS/TMSS/test/CMakeLists.txt
+++ b/SAS/TMSS/test/CMakeLists.txt
@@ -32,6 +32,7 @@ if(BUILD_TESTING)
     lofar_add_test(t_adapter)
     lofar_add_test(t_tasks)
     lofar_add_test(t_scheduling)
+    lofar_add_test(t_conversions)
 
     # To get ctest running
     file(COPY testdata DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
diff --git a/SAS/TMSS/test/t_conversions.py b/SAS/TMSS/test/t_conversions.py
new file mode 100755
index 0000000000000000000000000000000000000000..ccd4025f6c4c21a43d63f5ccb6a55c3b764f0963
--- /dev/null
+++ b/SAS/TMSS/test/t_conversions.py
@@ -0,0 +1,133 @@
+#!/usr/bin/env python3
+
+# Copyright (C) 2018    ASTRON (Netherlands Institute for Radio Astronomy)
+# P.O. Box 2, 7990 AA Dwingeloo, The Netherlands
+#
+# This file is part of the LOFAR software suite.
+# The LOFAR software suite is free software: you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as published
+# by the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# The LOFAR software suite is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.    See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>.
+
+# $Id:  $
+
+import os
+import unittest
+import datetime
+import logging
+import requests
+import dateutil.parser
+import astropy.coordinates
+
+logger = logging.getLogger(__name__)
+logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s', level=logging.INFO)
+from lofar.sas.tmss.tmss.tmssapp.conversions import local_sidereal_time_for_utc_and_station, local_sidereal_time_for_utc_and_longitude
+
+# Do Mandatory setup step:
+# use setup/teardown magic for tmss test database, ldap server and django server
+# (ignore pycharm unused import statement, python unittests does use at RunTime the tmss_test_environment_unittest_setup module)
+from lofar.sas.tmss.test.tmss_test_environment_unittest_setup import *
+
+class SiderealTime(unittest.TestCase):
+
+    def test_local_sidereal_time_for_utc_and_longitude_returns_correct_result(self):
+        # test result against known correct value
+        lst = local_sidereal_time_for_utc_and_longitude(timestamp=datetime.datetime(year=2020, month=1, day=1, hour=12, minute=0, second=0))
+        self.assertEqual(str(lst), '19h09m54.9567s')
+
+    def test_local_sidereal_time_for_utc_and_longitude_considers_timestamp(self):
+        # test that the results differ for different timestamps
+        lst1 = local_sidereal_time_for_utc_and_longitude(timestamp=datetime.datetime(year=2020, month=1, day=1, hour=12, minute=0, second=0))
+        lst2 = local_sidereal_time_for_utc_and_longitude(timestamp=datetime.datetime(year=2020, month=1, day=2, hour=12, minute=0, second=0))
+        self.assertNotEqual(str(lst1), str(lst2))
+
+    def test_local_sidereal_time_for_utc_and_longitude_considers_longitude(self):
+        # test that the results differ for different longitudes
+        lst1 = local_sidereal_time_for_utc_and_longitude(timestamp=datetime.datetime(year=2020, month=1, day=1, hour=12, minute=0, second=0), longitude=6.789)
+        lst2 = local_sidereal_time_for_utc_and_longitude(timestamp=datetime.datetime(year=2020, month=1, day=1, hour=12, minute=0, second=0), longitude=6.123)
+        self.assertNotEqual(str(lst1), str(lst2))
+
+    def test_local_sidereal_time_for_utc_and_station_returns_correct_result(self):
+        # assert result against known correct value
+        lst = local_sidereal_time_for_utc_and_station(timestamp=datetime.datetime(year=2020, month=1, day=1, hour=12, minute=0, second=0))
+        self.assertEqual(str(lst), '19h09m55.0856s')
+
+    def test_local_sidereal_time_for_utc_and_station_considers_timestamp(self):
+        # test that the results differ for different timestamps
+        lst1 = local_sidereal_time_for_utc_and_station(timestamp=datetime.datetime(year=2020, month=1, day=1, hour=12, minute=0, second=0))
+        lst2 = local_sidereal_time_for_utc_and_station(timestamp=datetime.datetime(year=2020, month=1, day=2, hour=12, minute=0, second=0))
+        self.assertNotEqual(str(lst1), str(lst2))
+
+    def test_local_sidereal_time_for_utc_and_station_considers_station(self):
+        # test that the results differ for different stations
+        lst1 = local_sidereal_time_for_utc_and_station(timestamp=datetime.datetime(year=2020, month=1, day=1, hour=12, minute=0, second=0), station="CS002")
+        lst2 = local_sidereal_time_for_utc_and_station(timestamp=datetime.datetime(year=2020, month=1, day=1, hour=12, minute=0, second=0), station="DE602")
+        self.assertNotEqual(str(lst1), str(lst2))
+
+
+class UtilREST(unittest.TestCase):
+
+    def test_util_utc_returns_timestamp(self):
+
+        # assert local clock differs not too much from returned TMSS system clock
+        r = requests.get(BASE_URL + '/util/utc', auth=AUTH)
+        self.assertEqual(r.status_code, 200)
+        returned_datetime = dateutil.parser.parse(r.content.decode('utf8'))
+        current_datetime = datetime.datetime.utcnow()
+        delta = abs((returned_datetime - current_datetime).total_seconds())
+        self.assertTrue(delta < 60.0)
+
+    def test_util_lst_returns_longitude(self):
+
+        # assert returned value is a parseable hms value
+        for query in ['/util/lst',
+                      '/util/lst?timestamp=2020-01-01T12:00:00',
+                      '/util/lst?timestamp=2020-01-01T12:00:00&longitude=54.321',
+                      '/util/lst?timestamp=2020-01-01T12:00:00&station=DE609']:
+            r = requests.get(BASE_URL + query, auth=AUTH)
+            self.assertEqual(r.status_code, 200)
+            lon_str = r.content.decode('utf8')
+            lon_obj = astropy.coordinates.Longitude(lon_str)
+            self.assertEqual(str(lon_obj), lon_str)
+
+    def test_util_lst_considers_timestamp(self):
+
+        # assert returned value matches known result for given timestamp
+        r = requests.get(BASE_URL + '/util/lst?timestamp=2020-01-01T12:00:00', auth=AUTH)
+        self.assertEqual(r.status_code, 200)
+        lon_str = r.content.decode('utf8')
+        self.assertEqual('19h09m55.0856s', lon_str)
+
+    def test_util_lst_considers_station(self):
+
+        # assert returned value differs when a different station is given
+        r1 = requests.get(BASE_URL + '/util/lst', auth=AUTH)
+        r2 = requests.get(BASE_URL + '/util/lst?station=DE602', auth=AUTH)
+        self.assertEqual(r1.status_code, 200)
+        self.assertEqual(r2.status_code, 200)
+        lon_str1 = r1.content.decode('utf8')
+        lon_str2 = r2.content.decode('utf8')
+        self.assertNotEqual(lon_str1, lon_str2)
+
+    def test_util_lst_considers_longitude(self):
+        # assert returned value differs when a different station is given
+        r1 = requests.get(BASE_URL + '/util/lst', auth=AUTH)
+        r2 = requests.get(BASE_URL + '/util/lst?longitude=12.345', auth=AUTH)
+        self.assertEqual(r1.status_code, 200)
+        self.assertEqual(r2.status_code, 200)
+        lon_str1 = r1.content.decode('utf8')
+        lon_str2 = r2.content.decode('utf8')
+        self.assertNotEqual(lon_str1, lon_str2)
+
+
+if __name__ == "__main__":
+    os.environ['TZ'] = 'UTC'
+    unittest.main()
diff --git a/SAS/TMSS/test/t_conversions.run b/SAS/TMSS/test/t_conversions.run
new file mode 100755
index 0000000000000000000000000000000000000000..d7c74389715a9cd50f3c36c5e406607f77c048f2
--- /dev/null
+++ b/SAS/TMSS/test/t_conversions.run
@@ -0,0 +1,6 @@
+#!/bin/bash
+
+# Run the unit test
+source python-coverage.sh
+python_coverage_test "*tmss*" t_conversions.py
+
diff --git a/SAS/TMSS/test/t_conversions.sh b/SAS/TMSS/test/t_conversions.sh
new file mode 100755
index 0000000000000000000000000000000000000000..c95892264d5c49a9a76e274e0b99c308fe8ae29c
--- /dev/null
+++ b/SAS/TMSS/test/t_conversions.sh
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+./runctest.sh t_conversions
\ No newline at end of file