diff --git a/src/drv/CMakeLists.txt b/src/drv/CMakeLists.txt
index 254d285c56ebc9506390f99c75034b1380a067b2..a7bac44ce97c2c4b241b03fecd818627efc5f2e4 100644
--- a/src/drv/CMakeLists.txt
+++ b/src/drv/CMakeLists.txt
@@ -9,4 +9,8 @@ add_library(driver::shmem ALIAS shmem)
 add_library(ini_parsing STATIC param.c crc.c)
 target_include_directories(ini_parsing PUBLIC
         ${CMAKE_CURRENT_SOURCE_DIR}/../include)
-add_library(driver::ini_parsing ALIAS ini_parsing)
\ No newline at end of file
+add_library(driver::ini_parsing ALIAS ini_parsing)
+
+add_library(gpsclock STATIC gpsclock.c)
+target_include_directories(gpsclock PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/../include)
+add_library(driver::gpsclock ALIAS gpsclock)
diff --git a/src/drv/gpsclock.c b/src/drv/gpsclock.c
new file mode 100644
index 0000000000000000000000000000000000000000..7fcf928e465ab22aeff6297e7316cdd0028cc8a1
--- /dev/null
+++ b/src/drv/gpsclock.c
@@ -0,0 +1,84 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <errno.h>
+#include "drv/gpsclock.h"
+
+// GPS offset from system time (depends on number of elapsed leap seconds)
+static long gpsoffset;
+
+static int initdone = 0;
+
+int gpsclock_init(void) {
+    int status;
+    status = gpsclock_offset(&gpsoffset);
+    initdone = 1;
+    return status;
+}
+
+int gpsclock_offset(long *offset) {
+    // TAI-UTC offset in seconds as of 1972
+    const long INITIAL_TAI_OFFSET = 10;
+    // GPS-TAI offset in seconds
+    const long GPS_TAI_OFFSET = -19;
+
+    char *savetz;
+    time_t now, gpszero, posixutc, rightutc;
+    struct tm now_tm, gpszero_tm = {.tm_year=80, .tm_mon=0, .tm_mday=6, .tm_hour=0, .tm_min=0, .tm_sec=0, .tm_wday=-1, .tm_yday=-1, .tm_isdst=-1};
+
+    // save TZ
+    savetz = getenv("TZ");
+    if (savetz) {
+        setenv("GPSCLOCK_TZ_ORIG", savetz, 1);
+    }
+    // get current time in posix/UTC (no leap seconds)
+    setenv("TZ", "posix/UTC", 1);
+    tzset();
+    now = time(0);
+    localtime_r(&now, &now_tm);
+    posixutc = mktime(&now_tm);
+    // get UTC time at GPS zero
+    gpszero = mktime(&gpszero_tm);
+    // get current time in right/UTC (includes leap seconds)
+    setenv("TZ", "right/UTC", 1);
+    tzset();
+    rightutc = mktime(&now_tm);
+    // restore TZ
+    savetz = getenv("GPSCLOCK_TZ_ORIG");
+    if (savetz) {
+        setenv("TZ", savetz, 1);
+        unsetenv("GPSCLOCK_TZ_ORIG");
+    } else {
+        unsetenv("TZ");
+    }
+    tzset();
+    *offset = rightutc - posixutc;
+    if (*offset == 0) {
+        fprintf(stderr, "gpsclock: failed to set correct GPS time offset\n");
+        return -1;
+    }
+    // add fixed offsets (TAI-UTC as of 1972 and GPS-TAI)
+    *offset += INITIAL_TAI_OFFSET + GPS_TAI_OFFSET;
+    // subtract gps zero
+    *offset -= gpszero;
+
+    return 0;
+}
+
+char *gpsclock_timestring(char *s, int size) {
+    struct timespec clk;
+    if (!initdone) { gpsclock_init(); }
+    clock_gettime(CLOCK_REALTIME, &clk);
+    snprintf(s, size, "%ld.%02ld\n", clk.tv_sec + gpsoffset, clk.tv_nsec/10000000L);
+    return s;
+}
+
+void gpsclock_time(unsigned long *req) {
+    struct timespec clk;
+    if (!initdone) { gpsclock_init(); }
+    clock_gettime(CLOCK_REALTIME, &clk);
+    req[0] = clk.tv_sec + gpsoffset;
+    req[1] = clk.tv_nsec / 1000;
+    req[2] = clk.tv_nsec % 1000;
+}
diff --git a/src/epics/seq/CMakeLists.txt b/src/epics/seq/CMakeLists.txt
index 67356bb0f9252196cf152fad1719c49ce9de6389..1b75be152417cbcd099cb5e8d38e6ba894596144 100644
--- a/src/epics/seq/CMakeLists.txt
+++ b/src/epics/seq/CMakeLists.txt
@@ -17,7 +17,8 @@ target_link_libraries(standalone_edc PUBLIC
         driver::ini_parsing
         pv::simple_pv
         ${Boost_LIBRARIES}
-        ${CMAKE_THREAD_LIBS_INIT})
+        ${CMAKE_THREAD_LIBS_INIT}
+        driver::gpsclock)
 target_requires_cpp11(standalone_edc PUBLIC)
 
 configure_file(test/epics_test.py ${CMAKE_CURRENT_BINARY_DIR}/epics_test.py COPYONLY)
diff --git a/src/epics/seq/gps.hh b/src/epics/seq/gps.hh
index 9f99ab9a28f1383bf31778e4bbfa3b2b8038d0bd..29bc6f4eaf07d02ad9fcb2f300c06a5126c4365c 100644
--- a/src/epics/seq/gps.hh
+++ b/src/epics/seq/gps.hh
@@ -15,6 +15,7 @@
 
 #include <unistd.h>
 
+#include <drv/gpsclock.h>
 
 /**
  * Operations on GPS time.
@@ -107,7 +108,13 @@ namespace GPS {
     public:
         explicit gps_clock(int offset):fd_(open ("/dev/gpstime", O_RDWR | O_SYNC)),
                                        offset_(offset),
-                                       ok_(gps_clock::symm_ok(fd_)) {}
+                                       ok_(gps_clock::symm_ok(fd_))
+        {
+            if (!ok_)
+            {
+                gpsclock_init();
+            }
+        }
         ~gps_clock()
         {
             if (fd_ >= 0) close(fd_);
@@ -167,10 +174,10 @@ namespace GPS {
             }
             else
             {
-                struct timespec ts;
-                clock_gettime(CLOCK_REALTIME, &ts);
-                result.sec = ts.tv_sec;
-                result.nanosec = ts.tv_nsec;
+                unsigned long req[3];
+                gpsclock_time(req);
+                result.sec = req[0] + offset_;
+                result.nanosec = req[1]*1000 + req[2];
             }
             return result;
         }
diff --git a/src/epics/seq/standalone_edcu.cc b/src/epics/seq/standalone_edcu.cc
index b5230d741cc5847d7758819a5319c82b1d79f015..b5cb0ce529050efea8556e67e9fb30084ed8aac0 100644
--- a/src/epics/seq/standalone_edcu.cc
+++ b/src/epics/seq/standalone_edcu.cc
@@ -341,7 +341,7 @@ int        nextTrig = 0;
  * @brief EdcuClock the clock for the standalone edc
  * @details This class abstracts the clock allowing multiple sources to be
  * selected from. Currently this will read form a mbuf (ie the time output of a
- * LIGO model) or from the gpstime device.
+ * LIGO model) or from the gpstime device or from the system clock via gpsclock.
  */
 class EdcuClock
 {
@@ -676,59 +676,6 @@ void update_diag_info( diag_thread_queues& queues );
 // End Header ************************************************************
 //
 
-// **************************************************************************
-/// Get current GPS time from the symmetricom IRIG-B card
-unsigned long
-symm_gps_time( unsigned long* frac, int* stt )
-{
-    // **************************************************************************
-    unsigned long t[ 3 ];
-    ioctl( symmetricom_fd, IOCTL_SYMMETRICOM_TIME, &t );
-    t[ 1 ] *= 1000;
-    t[ 1 ] += t[ 2 ];
-    if ( frac )
-        *frac = t[ 1 ];
-    if ( stt )
-        *stt = 0;
-    // return  t[0] + daqd.symm_gps_offset;
-    return t[ 0 ];
-}
-
-// **************************************************************************
-/// See if the GPS card is locked.
-int
-symm_gps_ok( )
-{
-    // **************************************************************************
-    unsigned long req = 0;
-    ioctl( symmetricom_fd, IOCTL_SYMMETRICOM_STATUS, &req );
-    printf( "Symmetricom status: %s\n", req ? "LOCKED" : "UNCLOCKED" );
-    return req;
-}
-
-// **************************************************************************
-unsigned long
-symm_initialize( )
-// **************************************************************************
-{
-    symmetricom_fd = open( "/dev/gpstime", O_RDWR | O_SYNC );
-    if ( symmetricom_fd < 0 )
-    {
-        perror( "/dev/gpstime" );
-        exit( 1 );
-    }
-    unsigned long gpsSec, gpsuSec;
-    int           gpsx;
-    int           gpssync;
-    gpssync = symm_gps_ok( );
-    gpsSec = symm_gps_time( &gpsuSec, &gpsx );
-    printf( "GPS SYNC = %d %d\n", gpssync, gpsx );
-    printf( "GPS SEC = %ld  USEC = %ld  OTHER = %d\n", gpsSec, gpsuSec, gpsx );
-    // Set system to start 2 sec from now.
-    gpsSec += 2;
-    return ( gpsSec );
-}
-
 // **************************************************************************
 void
 connectCallback( struct connection_handler_args args )
diff --git a/src/epics/util/feCodeGen.pl b/src/epics/util/feCodeGen.pl
index 024ac4f9086d6f1b79963ceca8a5d23c309030ef..c9efd27d9c9bbb5e5376ab1188ec90b62df75c31 100755
--- a/src/epics/util/feCodeGen.pl
+++ b/src/epics/util/feCodeGen.pl
@@ -161,6 +161,7 @@ $daq_prefix="DC0";
 $allBiquad = 1;
 $dac_dt_enable = 0;
 $internalclk = 0;
+$userspacegps = 0;
 
 # Load model name without .mdl extension.
 $skeleton = $ARGV[1];
@@ -2395,6 +2396,10 @@ sub createEpicsMakefile {
 	print OUTME "SRC += $rcg_src_dir/src/drv/crc.c\n";
 	print OUTME "SRC += $rcg_src_dir/src/drv/fmReadCoeff.c\n";
 	#print OUTME "SRC += src/epics/seq/get_local_time.st\n";
+	if ($userspacegps)
+	{
+		print OUTME "SRC += $rcg_src_dir/src/drv/gpsclock.c\n";
+	}
 	for($ii=0;$ii<$useWd;$ii++)
 	{
 		print OUTME "SRC += src/epics/seq/hepiWatchdog";
@@ -2447,6 +2452,10 @@ sub createEpicsMakefile {
 	{
 	print OUTME "EXTRA_CFLAGS += -DFIR_FILTERS\n";
 	}
+	if ($userspacegps)
+	{
+		print OUTME "EXTRA_CFLAGS += -DUSE_GPSCLOCK\n";
+	}
 	print OUTME "include $rcg_src_dir/config/Makefile.linux\n";
 	print OUTME "\n";
 	print OUTME "build/\$(TARGET)/";
diff --git a/src/epics/util/lib/Parameters.pm b/src/epics/util/lib/Parameters.pm
index c4475300eb66fcdad12c7fc3fe73fa02126de054..4c657246a3596dd3e3fc28d3ad193d5bc0e5cb52 100644
--- a/src/epics/util/lib/Parameters.pm
+++ b/src/epics/util/lib/Parameters.pm
@@ -307,6 +307,10 @@ sub parseParams {
                     # Specify IPC rate if lower than model rate
 				    $::ipcrate = $spp[1];
                 }
+                case "userspacegps"
+                {
+                    $::userspacegps = 1;
+                }
                 # Following are old options that are no longer required
                 case "biquad"
                 {
diff --git a/src/epics/util/lib/createUserMakefile.pm b/src/epics/util/lib/createUserMakefile.pm
index 9cbe08e1b71f38a9180023a5c5624853a6c5ccb2..14da30f7dd2aa29ec063ff7d4e4bec734b82387d 100644
--- a/src/epics/util/lib/createUserMakefile.pm
+++ b/src/epics/util/lib/createUserMakefile.pm
@@ -169,6 +169,10 @@ if ($::::rfmDelay) {
   print OUTM "CFLAGS += -DUSER_SPACE=1\n";
   print OUTM "CFLAGS += -fno-builtin-sincos\n";
 
+if ($::userspacegps) {
+  print OUTM "CFLAGS += -DUSE_GPSCLOCK\n";
+}
+
 print OUTM "export LD_LIBRARY_PATH=\$LD_LIBRARY_PATH:/opt/DIS/lib64\n";
 print OUTM "API_LIB_PATH=/opt/DIS/lib64\n";
 print OUTM "\n\n";
@@ -188,10 +192,18 @@ print OUTM "LDFLAGS = -L \$(API_LIB_PATH) \n";
 }
 
 print OUTM "TARGET=$::skeleton\n\n\n";
+if ($::userspacegps) {
+print OUTM "$::skeleton: $::skeleton.o gpsclock.o rfm.o \n\n";
+} else {
 print OUTM "$::skeleton: $::skeleton.o rfm.o \n\n";
+}
 print OUTM "rfm.o: $::rcg_src_dir\/src/drv/rfm.c \n";
 my $ccf = "\$\(CC\) \$\(CFLAGS\) \$\(CPPFLAGS\) \-c \$\< \-o \$\@";
 print OUTM "\t$ccf \n";
+if ($::userspacegps) {
+print OUTM "gpsclock.o: $::rcg_src_dir\/src/drv/gpsclock.c \n";
+print OUTM "\t$ccf \n";
+}
 print OUTM ".c.o: \n";
 print OUTM "\t$ccf \n";
 
diff --git a/src/fe/controllerAppUser.c b/src/fe/controllerAppUser.c
index 9d372b3044850578b455ce9231975f28fbf1f79c..9b073658c7d557119b410aee680dc773b4ce17c4 100644
--- a/src/fe/controllerAppUser.c
+++ b/src/fe/controllerAppUser.c
@@ -61,6 +61,24 @@
 
 // #include "dolphin_usp.c"
 
+#ifdef USE_GPSCLOCK
+#include "drv/gpsclock.h"
+#else
+static void
+gpsclock_timestring(char* line, int size)
+{
+    FILE*        timef;
+    timef = fopen( "/sys/kernel/gpstime/time", "r" );
+    if ( !timef )
+    {
+        printf( "Cannot find GPS time \n" );
+        return ( 0 );
+    }
+    fgets( line, 100, timef );
+    fclose(timef);
+}
+#endif
+
 #define BILLION 1000000000L
 
 // Contec 64 input bits plus 64 output bits (Standard for aLIGO)
@@ -93,21 +111,12 @@ struct timespec myTimer[ 2 ]; ///< Used in code cycle timing
 unsigned int
 getGpsTimeProc( )
 {
-    FILE*        timef;
     char         line[ 100 ];
-    int          status;
     unsigned int mytime;
 
-    timef = fopen( "/sys/kernel/gpstime/time", "r" );
-    if ( !timef )
-    {
-        printf( "Cannot find GPS time \n" );
-        return ( 0 );
-    }
-    fgets( line, 100, timef );
+    gpsclock_timestring(line, 100);
     mytime = atoi( line );
     printf( "GPS TIME is %d\n", mytime );
-    fclose( timef );
     return ( mytime );
 }
 
@@ -410,10 +419,6 @@ fe_start_app_user( )
     timeSec--;
 
     onePpsTime = cycleNum;
-    // timeSec = current_time() -1;
-    timeSec = ioMemData->gpsSecond;
-    timeSec--;
-    // timeSec = ioMemData->iodata[0][0].timeSec;
     printf( "Using local GPS time %d \n", timeSec );
     pLocalEpics->epicsOutput.fe_status = NORMAL_RUN;
 
diff --git a/src/fe/controllerIopUser.c b/src/fe/controllerIopUser.c
index 55c80b1e504f46a9cbabca4299711f2f90adac19..0fd131a3e6190a9cfe1bc3d43e67ff53dc4f011b 100644
--- a/src/fe/controllerIopUser.c
+++ b/src/fe/controllerIopUser.c
@@ -61,6 +61,24 @@
 #include "dolphin.c"
 #endif
 
+#ifdef USE_GPSCLOCK
+#include "drv/gpsclock.h"
+#else
+static void
+gpsclock_timestring(char* line, int size)
+{
+    FILE*        timef;
+    timef = fopen( "/sys/kernel/gpstime/time", "r" );
+    if ( !timef )
+    {
+        printf( "Cannot find GPS time \n" );
+        return ( 0 );
+    }
+    fgets( line, 100, timef );
+    fclose(timef);
+}
+#endif
+
 #define BILLION 1000000000L
 
 // Contec 64 input bits plus 64 output bits (Standard for aLIGO)
@@ -98,22 +116,12 @@ int             getGpsTime( unsigned int* tsyncSec, unsigned int* tsyncUsec );
 unsigned int
 getGpsTimeProc( )
 {
-    FILE*        timef;
     char         line[ 100 ];
-    int          status;
     unsigned int mytime;
 
-    timef = fopen( "/sys/kernel/gpstime/time", "r" );
-    if ( !timef )
-    {
-        printf( "Cannot find GPS time \n" );
-        return ( 0 );
-    }
-    fgets( line, 100, timef );
+    gpsclock_timestring(line, 100);
     mytime = atoi( line );
     printf( "GPS TIME is %d\n", mytime );
-    fclose( timef );
-    mytime -= 1;
     return ( mytime );
 }
 void
@@ -493,10 +501,8 @@ fe_start_iop_user( )
     } while (status != 0 || myTimer[0].tv_nsec > 13000);
     /// Begin the first cycle at the start of the next second + 13 us
     clock_gettime(CLOCK_MONOTONIC, &myTimer[1]);
-    timeSec = getGpsTimeProc() + 2;
-    timeoff = myTimer[1].tv_nsec + 13000 - myTimer[0].tv_nsec;
-    if (timeoff >= BILLION) { timeoff -= BILLION; myTimer[1].tv_sec++; }
-    nextstep = myTimer[1].tv_sec*BILLION + BILLION + timeoff;
+    timeSec = getGpsTimeProc();  // increments at the start of the first cycle
+    nextstep = myTimer[1].tv_sec*BILLION + myTimer[1].tv_nsec - myTimer[0].tv_nsec + BILLION;
     clk = nextstep - cyclensec;
     startGpsTime = timeSec;
     pLocalEpics->epicsOutput.startgpstime = startGpsTime;
diff --git a/src/include/drv/gpsclock.h b/src/include/drv/gpsclock.h
new file mode 100644
index 0000000000000000000000000000000000000000..609d6c72d76f320a15ad0155030873f974f4e97e
--- /dev/null
+++ b/src/include/drv/gpsclock.h
@@ -0,0 +1,30 @@
+#ifndef GPSCLOCK_INCLUDED
+#define GPSCLOCK_INCLUDED
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// gpsclock_init: initialize library (return 0 if successful)
+// currently this just calls gpsclock_offset
+int gpsclock_init(void);
+
+// gpsclock_offset: store difference between GPS and system time in offset (return 0 if successful)
+int gpsclock_offset(long *offset);
+
+// gpsclock_timestring: print gps time into string s with length size (return s)
+// calls gpsclock_init if it has not already been called
+char *gpsclock_timestring(char *s, int size);
+
+// gpsclock_time: store gps time in req
+// req[0] : seconds
+// req[1] : microseconds
+// req[2] : nanoseconds
+// calls gpsclock_init if it has not already been called
+void gpsclock_time(unsigned long *req);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // GPSCLOCK_INCLUDED
diff --git a/src/local_dc/CMakeLists.txt b/src/local_dc/CMakeLists.txt
index 0204b2bf653f3c99240092ffb94cad346a20c87b..020ce2666b7dfcfc69bc00a7c0b58335489e22cf 100644
--- a/src/local_dc/CMakeLists.txt
+++ b/src/local_dc/CMakeLists.txt
@@ -2,7 +2,8 @@ add_executable(local_dc local_dc.c ${CMAKE_CURRENT_SOURCE_DIR}/../drv/rfm.c)
 
 target_link_libraries(local_dc PUBLIC
         args
-        util)
+        util
+        driver::gpsclock)
 
 configure_file(test_local_dc.sh.in test_local_dc.sh @ONLY)
 configure_file(test_local_dc_stopped_model.sh.in test_local_dc_stopped_model.sh @ONLY)
diff --git a/src/local_dc/local_dc.c b/src/local_dc/local_dc.c
index dd5687abc6870198f0ca4bb784d5b2e45df06940..6a4ed11934ebfd4356bc25ff67e9fe886ae029c7 100644
--- a/src/local_dc/local_dc.c
+++ b/src/local_dc/local_dc.c
@@ -26,6 +26,7 @@
 #include <pthread.h>
 #include "modelrate.h"
 #include "local_dc_utils.h"
+#include "drv/gpsclock.h"
 
 #define MSG_BUF_SIZE sizeof( daq_dc_data_t )
 
@@ -60,11 +61,17 @@ int daqStatBit[ 2 ];
 // **********************************************************************************************
 /// Get current GPS time from the symmetricom IRIG-B card
 unsigned long
-symm_gps_time( unsigned long* frac, int* stt )
-{
-    unsigned long t[ 3 ];
+symm_gps_time( unsigned long* frac, int* stt ) {
+    unsigned long t[3];
 
-    ioctl( symmetricom_fd, IOCTL_SYMMETRICOM_TIME, &t );
+    if (symmetricom_fd >= 0)
+    {
+        ioctl(symmetricom_fd, IOCTL_SYMMETRICOM_TIME, &t);
+    }
+    else
+    {
+        gpsclock_time(t);
+    }
     t[ 1 ] *= 1000;
     t[ 1 ] += t[ 2 ];
     if ( frac )
@@ -79,9 +86,13 @@ symm_gps_time( unsigned long* frac, int* stt )
 int
 symm_ok( )
 {
-    unsigned long req = 0;
-    ioctl( symmetricom_fd, IOCTL_SYMMETRICOM_STATUS, &req );
-    fprintf( stderr, "Symmetricom status: %s\n", req ? "LOCKED" : "UNCLOCKED" );
+    unsigned long req = 1;
+    if (symmetricom_fd >= 0)
+    {
+        req = 0;
+        ioctl(symmetricom_fd, IOCTL_SYMMETRICOM_STATUS, &req);
+        fprintf(stderr, "Symmetricom status: %s\n", req ? "LOCKED" : "UNCLOCKED");
+    }
     return req;
 }
 
@@ -598,8 +609,10 @@ int __CDECL
     symmetricom_fd = open( "/dev/gpstime", O_RDWR | O_SYNC );
     if ( symmetricom_fd < 0 )
     {
-        perror( "/dev/gpstime" );
-        exit( 1 );
+        if (gpsclock_init() < 0) {
+            perror("Unable to open /dev/gpstime or initialize the system based gpsclock");
+            exit(1);
+        }
     }
     gps_ok = symm_ok( );
     gps_time = symm_gps_time( &gps_frac, &gps_stt );