From ab7c566fc52322ef4b9e4c43c70fb2e8ea1f4dd6 Mon Sep 17 00:00:00 2001
From: Volker Schukai <volker.schukai@schukai.com>
Date: Fri, 3 May 2024 17:48:36 +0200
Subject: [PATCH] feat: map time and duration in json context #60

---
 .idea/workspace.xml |  65 ++++++++++++++-------------
 go.mod              |   8 ++--
 go.sum              |  17 ++++---
 job-generic.go      |   4 +-
 job.go              |  16 +++----
 job_test.go         |   8 ++--
 manager_test.go     |  10 +++--
 persistence.go      | 105 ++++++++++++++++++++++++++++++++++----------
 persistence_test.go |  88 ++++++++++++++++++++++++++++++++++++-
 worker.go           |   8 ++--
 worker_test.go      |   8 ++--
 11 files changed, 243 insertions(+), 94 deletions(-)

diff --git a/.idea/workspace.xml b/.idea/workspace.xml
index ffc9107..2a263b4 100644
--- a/.idea/workspace.xml
+++ b/.idea/workspace.xml
@@ -4,7 +4,9 @@
     <option name="autoReloadType" value="SELECTIVE" />
   </component>
   <component name="ChangeListManager">
-    <list default="true" id="9979eb22-471e-4f2f-b624-fd3edb5e8c6e" name="Changes" comment="" />
+    <list default="true" id="9979eb22-471e-4f2f-b624-fd3edb5e8c6e" name="Changes" comment="">
+      <change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
+    </list>
     <option name="SHOW_DIALOG" value="false" />
     <option name="HIGHLIGHT_CONFLICTS" value="true" />
     <option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
@@ -38,37 +40,38 @@
     <option name="sortByType" value="true" />
     <option name="sortKey" value="BY_TYPE" />
   </component>
-  <component name="PropertiesComponent">{
-  &quot;keyToString&quot;: {
-    &quot;DefaultGoTemplateProperty&quot;: &quot;Go File&quot;,
-    &quot;Go Test.TestDummyRunnable in gitlab.schukai.com/oss/libraries/go/services/job-queues.executor&quot;: &quot;Debug&quot;,
-    &quot;Go Test.TestManagerEventHandling in gitlab.schukai.com/oss/libraries/go/services/job-queues.executor&quot;: &quot;Debug&quot;,
-    &quot;Go Test.TestResetStats in gitlab.schukai.com/oss/libraries/go/services/job-queues.executor&quot;: &quot;Debug&quot;,
-    &quot;Go Test.TestRoundTrip in gitlab.schukai.com/oss/libraries/go/services/job-queues.executor&quot;: &quot;Debug&quot;,
-    &quot;Go Test.TestScheduleJob in gitlab.schukai.com/oss/libraries/go/services/job-queues.executor&quot;: &quot;Debug&quot;,
-    &quot;Go Test.TestStructure in gitlab.schukai.com/oss/libraries/go/services/job-queues.executor&quot;: &quot;Debug&quot;,
-    &quot;Go Test.TestTimeFunctionSame in gitlab.schukai.com/oss/libraries/go/services/job-queues.executor&quot;: &quot;Debug&quot;,
-    &quot;Go Test.TestWriteToDB1 in gitlab.schukai.com/oss/libraries/go/services/job-queues.executor&quot;: &quot;Debug&quot;,
-    &quot;Go Test.TestWriteToDB2 in gitlab.schukai.com/oss/libraries/go/services/job-queues.executor&quot;: &quot;Debug&quot;,
-    &quot;Go Test.TestWriteToDB4 in gitlab.schukai.com/oss/libraries/go/services/job-queues.executor&quot;: &quot;Debug&quot;,
-    &quot;Go Test.go test gitlab.schukai.com/oss/libraries/go/services/job-queues.executor&quot;: &quot;Debug&quot;,
-    &quot;RunOnceActivity.ShowReadmeOnStart&quot;: &quot;true&quot;,
-    &quot;RunOnceActivity.go.formatter.settings.were.checked&quot;: &quot;true&quot;,
-    &quot;RunOnceActivity.go.migrated.go.modules.settings&quot;: &quot;true&quot;,
-    &quot;SHARE_PROJECT_CONFIGURATION_FILES&quot;: &quot;true&quot;,
-    &quot;git-widget-placeholder&quot;: &quot;master&quot;,
-    &quot;go.import.settings.migrated&quot;: &quot;true&quot;,
-    &quot;go.sdk.automatically.set&quot;: &quot;true&quot;,
-    &quot;last_opened_file_path&quot;: &quot;/home/vs/workspaces/oss/go-libs/job-queues/nix/scripts&quot;,
-    &quot;node.js.detected.package.eslint&quot;: &quot;true&quot;,
-    &quot;node.js.detected.package.tslint&quot;: &quot;true&quot;,
-    &quot;node.js.selected.package.eslint&quot;: &quot;(autodetect)&quot;,
-    &quot;node.js.selected.package.tslint&quot;: &quot;(autodetect)&quot;,
-    &quot;nodejs_package_manager_path&quot;: &quot;npm&quot;,
-    &quot;settings.editor.selected.configurable&quot;: &quot;http.proxy&quot;,
-    &quot;vue.rearranger.settings.migration&quot;: &quot;true&quot;
+  <component name="PropertiesComponent"><![CDATA[{
+  "keyToString": {
+    "DefaultGoTemplateProperty": "Go File",
+    "Go Test.TestDummyRunnable in gitlab.schukai.com/oss/libraries/go/services/job-queues.executor": "Debug",
+    "Go Test.TestManagerEventHandling in gitlab.schukai.com/oss/libraries/go/services/job-queues.executor": "Debug",
+    "Go Test.TestResetStats in gitlab.schukai.com/oss/libraries/go/services/job-queues.executor": "Debug",
+    "Go Test.TestRoundTrip in gitlab.schukai.com/oss/libraries/go/services/job-queues.executor": "Debug",
+    "Go Test.TestScheduleJob in gitlab.schukai.com/oss/libraries/go/services/job-queues.executor": "Debug",
+    "Go Test.TestStructure in gitlab.schukai.com/oss/libraries/go/services/job-queues.executor": "Debug",
+    "Go Test.TestTimeFunctionSame in gitlab.schukai.com/oss/libraries/go/services/job-queues.executor": "Debug",
+    "Go Test.TestWriteToDB1 in gitlab.schukai.com/oss/libraries/go/services/job-queues.executor": "Debug",
+    "Go Test.TestWriteToDB2 in gitlab.schukai.com/oss/libraries/go/services/job-queues.executor": "Debug",
+    "Go Test.TestWriteToDB4 in gitlab.schukai.com/oss/libraries/go/services/job-queues.executor": "Debug",
+    "Go Test.go test gitlab.schukai.com/oss/libraries/go/services/job-queues.executor": "Debug",
+    "RunOnceActivity.ShowReadmeOnStart": "true",
+    "RunOnceActivity.go.formatter.settings.were.checked": "true",
+    "RunOnceActivity.go.migrated.go.modules.settings": "true",
+    "RunOnceActivity.go.modules.automatic.dependencies.download": "true",
+    "SHARE_PROJECT_CONFIGURATION_FILES": "true",
+    "git-widget-placeholder": "master",
+    "go.import.settings.migrated": "true",
+    "go.sdk.automatically.set": "true",
+    "last_opened_file_path": "/home/vs/workspaces/oss/go-libs/job-queues/nix/scripts",
+    "node.js.detected.package.eslint": "true",
+    "node.js.detected.package.tslint": "true",
+    "node.js.selected.package.eslint": "(autodetect)",
+    "node.js.selected.package.tslint": "(autodetect)",
+    "nodejs_package_manager_path": "npm",
+    "settings.editor.selected.configurable": "http.proxy",
+    "vue.rearranger.settings.migration": "true"
   }
-}</component>
+}]]></component>
   <component name="RecentsManager">
     <key name="CopyFile.RECENT_KEYS">
       <recent name="$PROJECT_DIR$/nix/scripts" />
diff --git a/go.mod b/go.mod
index d586c3e..196794e 100644
--- a/go.mod
+++ b/go.mod
@@ -10,14 +10,14 @@ require (
 	github.com/google/uuid v1.6.0
 	github.com/pkg/sftp v1.13.6
 	github.com/robfig/cron/v3 v3.0.1
-	github.com/shirou/gopsutil/v3 v3.24.3
+	github.com/shirou/gopsutil/v3 v3.24.4
 	github.com/stretchr/testify v1.9.0
 	go.uber.org/zap v1.27.0
 	golang.org/x/crypto v0.22.0
 	gopkg.in/yaml.v3 v3.0.1
 	gorm.io/driver/mysql v1.5.6
 	gorm.io/driver/sqlite v1.5.5
-	gorm.io/gorm v1.25.9
+	gorm.io/gorm v1.25.10
 	gotest.tools/v3 v3.5.1
 )
 
@@ -45,8 +45,8 @@ require (
 	github.com/pmezard/go-difflib v1.0.0 // indirect
 	github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
 	github.com/shoenig/go-m1cpu v0.1.6 // indirect
-	github.com/tklauser/go-sysconf v0.3.13 // indirect
-	github.com/tklauser/numcpus v0.7.0 // indirect
+	github.com/tklauser/go-sysconf v0.3.14 // indirect
+	github.com/tklauser/numcpus v0.8.0 // indirect
 	github.com/yusufpapurcu/wmi v1.2.4 // indirect
 	go.uber.org/multierr v1.11.0 // indirect
 	golang.org/x/mod v0.8.0 // indirect
diff --git a/go.sum b/go.sum
index c8d7124..e8af371 100644
--- a/go.sum
+++ b/go.sum
@@ -67,8 +67,8 @@ github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt
 github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
 github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
 github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
-github.com/shirou/gopsutil/v3 v3.24.3 h1:eoUGJSmdfLzJ3mxIhmOAhgKEKgQkeOwKpz1NbhVnuPE=
-github.com/shirou/gopsutil/v3 v3.24.3/go.mod h1:JpND7O217xa72ewWz9zN2eIIkPWsDN/3pl0H8Qt0uwg=
+github.com/shirou/gopsutil/v3 v3.24.4 h1:dEHgzZXt4LMNm+oYELpzl9YCqV65Yr/6SfrvgRBtXeU=
+github.com/shirou/gopsutil/v3 v3.24.4/go.mod h1:lTd2mdiOspcqLgAnr9/nGi71NkeMpWKdmhuxm9GusH8=
 github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
 github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
 github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
@@ -83,11 +83,11 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl
 github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
 github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
 github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
-github.com/tklauser/go-sysconf v0.3.13 h1:GBUpcahXSpR2xN01jhkNAbTLRk2Yzgggk8IM08lq3r4=
-github.com/tklauser/go-sysconf v0.3.13/go.mod h1:zwleP4Q4OehZHGn4CYZDipCgg9usW5IJePewFCGVEa0=
+github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZb78yU=
+github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY=
 github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
-github.com/tklauser/numcpus v0.7.0 h1:yjuerZP127QG9m5Zh/mSO4wqurYil27tHrqwRoRjpr4=
-github.com/tklauser/numcpus v0.7.0/go.mod h1:bb6dMVcj8A42tSE7i32fsIUCbQNllK5iDguyOZRUzAY=
+github.com/tklauser/numcpus v0.8.0 h1:Mx4Wwe/FjZLeQsK/6kt2EOepwwSl7SmJrK5bV/dXYgY=
+github.com/tklauser/numcpus v0.8.0/go.mod h1:ZJZlAY+dmR4eut8epnzf0u/VwodKmryxR8txiloSqBE=
 github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
@@ -138,7 +138,6 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
 golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
@@ -173,7 +172,7 @@ gorm.io/driver/mysql v1.5.6/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkD
 gorm.io/driver/sqlite v1.5.5 h1:7MDMtUZhV065SilG62E0MquljeArQZNfJnjd9i9gx3E=
 gorm.io/driver/sqlite v1.5.5/go.mod h1:6NgQ7sQWAIFsPrJJl1lSNSu2TABh0ZZ/zm5fosATavE=
 gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
-gorm.io/gorm v1.25.9 h1:wct0gxZIELDk8+ZqF/MVnHLkA1rvYlBWUMv2EdsK1g8=
-gorm.io/gorm v1.25.9/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
+gorm.io/gorm v1.25.10 h1:dQpO+33KalOA+aFYGlK+EfxcI5MbO7EP2yYygwh9h+s=
+gorm.io/gorm v1.25.10/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
 gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU=
 gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=
diff --git a/job-generic.go b/job-generic.go
index 3a2efc3..be6e644 100644
--- a/job-generic.go
+++ b/job-generic.go
@@ -21,9 +21,9 @@ type GenericJob interface {
 
 	GetMaxRetries() uint
 
-	GetRetryDelay() time.Duration
+	GetRetryDelay() *time.Duration
 
-	GetTimeout() time.Duration
+	GetTimeout() *time.Duration
 
 	GetPersistence() JobPersistence
 
diff --git a/job.go b/job.go
index b24f4c4..bf3f1b6 100644
--- a/job.go
+++ b/job.go
@@ -38,9 +38,9 @@ type Job[T any] struct {
 	description string
 	priority    Priority
 
-	timeout    time.Duration
+	timeout    *time.Duration
 	maxRetries uint
-	RetryDelay time.Duration
+	retryDelay *time.Duration
 
 	scheduler Scheduler
 
@@ -101,7 +101,7 @@ func (j *Job[T]) GetPersistence() JobPersistence {
 		Priority:     j.priority,
 		Timeout:      j.timeout,
 		MaxRetries:   j.maxRetries,
-		RetryDelay:   j.RetryDelay,
+		RetryDelay:   j.retryDelay,
 		Dependencies: j.dependencies,
 		Runnable:     j.runner.GetPersistence(),
 
@@ -314,12 +314,12 @@ func (j *Job[T]) SetTimeout(timeout time.Duration) *Job[T] {
 	j.mu.Lock()
 	defer j.mu.Unlock()
 
-	j.timeout = timeout
+	j.timeout = &timeout
 	return j
 }
 
 // GetTimeout returns the timeout of the job
-func (j *Job[T]) GetTimeout() time.Duration {
+func (j *Job[T]) GetTimeout() *time.Duration {
 	j.mu.Lock()
 	defer j.mu.Unlock()
 
@@ -348,16 +348,16 @@ func (j *Job[T]) SetRetryDelay(retryDelay time.Duration) *Job[T] {
 	j.mu.Lock()
 	defer j.mu.Unlock()
 
-	j.RetryDelay = retryDelay
+	j.retryDelay = &retryDelay
 	return j
 }
 
 // GetRetryDelay returns the retry delay of the job
-func (j *Job[T]) GetRetryDelay() time.Duration {
+func (j *Job[T]) GetRetryDelay() *time.Duration {
 	j.mu.Lock()
 	defer j.mu.Unlock()
 
-	return j.RetryDelay
+	return j.retryDelay
 }
 
 // SetDependencies sets the dependencies of the job
diff --git a/job_test.go b/job_test.go
index 55138d9..2ef4c1e 100644
--- a/job_test.go
+++ b/job_test.go
@@ -39,7 +39,7 @@ func TestSetPriority(t *testing.T) {
 func TestSetAndGetTimeout(t *testing.T) {
 	job := NewJob[ShellResult]("id1", &ShellRunnable{})
 	job.SetTimeout(5 * time.Minute)
-	assert.Equal(t, 5*time.Minute, job.GetTimeout())
+	assert.Equal(t, 5*time.Minute, *job.GetTimeout())
 }
 
 func TestSetAndGetMaxRetries(t *testing.T) {
@@ -57,7 +57,7 @@ func TestSetAndGetRunnable(t *testing.T) {
 func TestSetAndGetRetryDelay(t *testing.T) {
 	job := NewJob[ShellResult]("id1", &ShellRunnable{})
 	job.SetRetryDelay(2 * time.Second)
-	assert.Equal(t, 2*time.Second, job.GetRetryDelay())
+	assert.Equal(t, 2*time.Second, *job.GetRetryDelay())
 }
 
 func TestSetAndGetDependencies(t *testing.T) {
@@ -90,7 +90,7 @@ func TestGetPriority(t *testing.T) {
 
 func TestGetTimeout(t *testing.T) {
 	job := NewJob[ShellResult]("id2", &ShellRunnable{})
-	assert.Equal(t, time.Duration(0), job.GetTimeout())
+	assert.Nil(t, job.GetTimeout())
 }
 
 func TestGetMaxRetries(t *testing.T) {
@@ -100,7 +100,7 @@ func TestGetMaxRetries(t *testing.T) {
 
 func TestGetRetryDelay(t *testing.T) {
 	job := NewJob[ShellResult]("id2", &ShellRunnable{})
-	assert.Equal(t, time.Duration(0), job.GetRetryDelay())
+	assert.Nil(t, job.GetRetryDelay())
 }
 
 func TestGetDependencies(t *testing.T) {
diff --git a/manager_test.go b/manager_test.go
index 5bb93dd..ee2321d 100644
--- a/manager_test.go
+++ b/manager_test.go
@@ -72,12 +72,14 @@ func (m *MockGenericJob) GetMaxRetries() uint {
 	return 0
 }
 
-func (m *MockGenericJob) GetRetryDelay() time.Duration {
-	return 0
+func (m *MockGenericJob) GetRetryDelay() *time.Duration {
+	dur := time.Duration(0)
+	return &dur
 }
 
-func (m *MockGenericJob) GetTimeout() time.Duration {
-	return 0
+func (m *MockGenericJob) GetTimeout() *time.Duration {
+	dur := time.Duration(0)
+	return &dur
 }
 
 func (m *MockGenericJob) GetID() JobID {
diff --git a/persistence.go b/persistence.go
index ea0c8a6..0f8896d 100644
--- a/persistence.go
+++ b/persistence.go
@@ -19,16 +19,20 @@ type JobPersistence struct {
 	ID           JobID                `yaml:"id" json:"id" gorm:"type:varchar(255);primaryKey"`
 	Description  string               `yaml:"description" json:"description" gorm:"column:description"`
 	Priority     Priority             `yaml:"priority" json:"priority" gorm:"column:priority"`
-	Timeout      time.Duration        `yaml:"timeout" json:"timeout" gorm:"column:timeout"`
+	Timeout      *time.Duration       `yaml:"-" json:"-" gorm:"column:timeout"`
 	MaxRetries   uint                 `yaml:"maxRetries" json:"maxRetries" gorm:"column:max_retries"`
-	RetryDelay   time.Duration        `yaml:"retryDelay" json:"retryDelay" gorm:"column:retry_delay"`
+	RetryDelay   *time.Duration       `yaml:"-" json:"-" gorm:"column:retry_delay"`
 	Dependencies []JobID              `yaml:"dependencies" json:"dependencies,omitempty" gorm:"column:dependencies;type:json"`
 	Runnable     RunnableImport       `yaml:"runnable" json:"runnable" gorm:"embedded;embeddedPrefix:runnable_"`
 	Scheduler    SchedulerPersistence `yaml:"scheduler" json:"scheduler,omitempty" gorm:"embedded;embeddedPrefix:scheduler_"`
 
 	Pause       bool       `yaml:"pause" json:"pause" gorm:"column:pause"`
 	PauseReason string     `yaml:"pauseReason" json:"pauseReason" gorm:"column:pause_reason"`
-	PauseUntil  *time.Time `yaml:"pauseUntil" json:"pauseUntil" gorm:"column:pause_until"`
+	PauseUntil  *time.Time `yaml:"-" json:"-" gorm:"column:pause_until"`
+
+	TimeoutString    *string `yaml:"timeout,omitempty" json:"timeout,omitempty" gorm:"-"`
+	RetryDelayString *string `yaml:"retryDelay,omitempty" json:"retryDelay,omitempty" gorm:"-"`
+	PauseUntilString *string `yaml:"pauseUntil" json:"pauseUntil,omitempty" gorm:"-"`
 
 	Logs  []JobLog `gorm:"foreignKey:JobID;references:ID" json:"-" yaml:"-"`
 	Stats JobStats `gorm:"foreignKey:JobID" json:"stats" yaml:"stats"`
@@ -38,39 +42,95 @@ type JobPersistence struct {
 	DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;index" json:"-" yaml:"-"`
 }
 
-// UnmarshalJSON implements the json.Unmarshaler interface.
-func (jp *JobPersistence) UnmarshalJSON(data []byte) error {
-	// Anonymous struct for unmarshalling with custom time format
+func (j *JobPersistence) MarshalJSON() ([]byte, error) {
+	type Alias JobPersistence
+	return json.Marshal(&struct {
+		*Alias
+		TimeoutString    string `json:"timeout,omitempty"`
+		RetryDelayString string `json:"retryDelay,omitempty"`
+		PauseUntilString string `json:"pauseUntil,omitempty"`
+	}{
+		Alias:            (*Alias)(j),
+		TimeoutString:    formatDuration(j.Timeout),
+		RetryDelayString: formatDuration(j.RetryDelay),
+		PauseUntilString: formatTime(j.PauseUntil),
+	})
+}
+
+func (j *JobPersistence) UnmarshalJSON(data []byte) error {
 	type Alias JobPersistence
 	aux := &struct {
-		PauseUntil *string `json:"pauseUntil,omitempty"`
 		*Alias
+		TimeoutString    *string `json:"timeout,omitempty"`
+		RetryDelayString *string `json:"retryDelay,omitempty"`
+		PauseUntilString *string `json:"pauseUntil,omitempty"`
 	}{
-		Alias: (*Alias)(jp),
+		Alias: (*Alias)(j),
 	}
-
-	if err := json.Unmarshal(data, &aux); err != nil {
+	if err := json.Unmarshal(data, aux); err != nil {
 		return err
 	}
-
-	if aux.PauseUntil != nil {
-		var t time.Time
-		var err error
-		for _, format := range SupportedTimeFormats {
-			t, err = time.Parse(format, *aux.PauseUntil)
-			if err == nil {
-				break
-			}
+	if aux.TimeoutString != nil && *aux.TimeoutString != "" {
+		d, err := time.ParseDuration(*aux.TimeoutString)
+		if err != nil {
+			return err
 		}
+		j.Timeout = &d
+	}
+	if aux.RetryDelayString != nil && *aux.RetryDelayString != "" {
+		d, err := time.ParseDuration(*aux.RetryDelayString)
 		if err != nil {
 			return err
 		}
-		jp.PauseUntil = &t
+		j.RetryDelay = &d
 	}
+	if aux.PauseUntilString != nil && *aux.PauseUntilString != "" {
+		t, err := parseMultipleDateFormats(*aux.PauseUntilString, []string{
+			time.RFC3339, time.RFC3339Nano,
+			"2006-01-02T15:04:05",
+			"2006-01-02T15:04:05.999999999",
+			"2006-01-02T15:04:05Z07:00",
+			"2006-01-02T15:04:05",
+			"2006-01-02T15:04",
+			"2006-01-02T15",
+			"2006-01-02T",
+			"2006-01-02",
+		})
+		if err != nil {
+			return err
+		}
 
+		j.PauseUntil = &t
+	}
 	return nil
 }
 
+func parseMultipleDateFormats(value string, formats []string) (time.Time, error) {
+	var t time.Time
+	var err error
+	for _, format := range formats {
+		t, err = time.Parse(format, value)
+		if err == nil {
+			return t, nil
+		}
+	}
+	return t, fmt.Errorf("date string '%s' does not match any known format", value)
+}
+
+func formatDuration(d *time.Duration) string {
+	if d != nil {
+		return d.String()
+	}
+	return ""
+}
+
+func formatTime(t *time.Time) string {
+	if t != nil {
+		return t.Format(time.RFC3339)
+	}
+	return ""
+}
+
 func (jp JobPersistence) GetLogs() []JobLog {
 	return jp.Logs
 }
@@ -96,7 +156,7 @@ func (jp JobPersistence) GetPriority() Priority {
 }
 
 func (jp JobPersistence) GetTimeout() time.Duration {
-	return jp.Timeout
+	return *jp.Timeout
 }
 
 func (JobPersistence) TableName() string {
@@ -182,13 +242,14 @@ func ReadFromGORM(db *gorm.DB) ([]JobPersistence, error) {
 }
 
 func CreateGenericJobFromPersistence[T any](jobImport JobPersistence, runner Runnable[T]) GenericJob {
+
 	return &Job[T]{
 		id:           jobImport.ID,
 		description:  jobImport.Description,
 		priority:     jobImport.Priority,
 		timeout:      jobImport.Timeout,
 		maxRetries:   jobImport.MaxRetries,
-		RetryDelay:   jobImport.RetryDelay,
+		retryDelay:   jobImport.RetryDelay,
 		dependencies: jobImport.Dependencies,
 		pause:        jobImport.Pause,
 		pauseReason:  jobImport.PauseReason,
diff --git a/persistence_test.go b/persistence_test.go
index 9cc2d89..60ac796 100644
--- a/persistence_test.go
+++ b/persistence_test.go
@@ -14,6 +14,10 @@ import (
 )
 
 func TestCreateJobAndSchedulerFromInput(t *testing.T) {
+
+	time10s := 10 * time.Second
+	time2s := 5 * time.Minute
+
 	tests := []struct {
 		name      string
 		input     JobPersistence
@@ -26,9 +30,9 @@ func TestCreateJobAndSchedulerFromInput(t *testing.T) {
 			input: JobPersistence{
 				ID:         "1",
 				Priority:   1,
-				Timeout:    10 * time.Second,
+				Timeout:    &time10s,
 				MaxRetries: 3,
-				RetryDelay: 2 * time.Second,
+				RetryDelay: &time2s,
 				Runnable: RunnableImport{
 					Type: "Shell",
 					Data: map[string]any{"ScriptPath": "script.sh"},
@@ -89,6 +93,86 @@ func TestCreateJobAndSchedulerFromInput(t *testing.T) {
 	}
 }
 
+func TestJobPersistence_MarshalUnmarshalJSON(t *testing.T) {
+	timeRef := time.Now().Round(time.Second) // Runden, um Genauigkeitsverlust zu vermeiden
+	timeRefString := timeRef.Format(time.RFC3339)
+
+	time5m := 5 * time.Minute
+	time10s := 10 * time.Second
+	time30s := 30 * time.Second
+	time1Hour := 1 * time.Hour
+
+	time10sString := "10s"
+	time30sString := "30s"
+	time5m0sString := "5m0s"
+	time1h0m0sString := "1h0m0s"
+	emptyString := ""
+
+	tests := []struct {
+		name     string
+		job      JobPersistence
+		expected string
+	}{
+		{
+			name: "All fields present",
+			job: JobPersistence{
+				Timeout:          &time5m,
+				RetryDelay:       &time10s,
+				PauseUntil:       &timeRef,
+				TimeoutString:    &time5m0sString,
+				RetryDelayString: &time10sString,
+				PauseUntilString: &timeRefString,
+			},
+			expected: ` {"id":"","description":"","priority":0,"maxRetries":0,"runnable":{"type":""},"scheduler":{"type":""},"pause":false,"pauseReason":"","timeout":"5m0s","retryDelay":"10s","pauseUntil":"` + timeRefString + `","stats":{"jobId":"","runCount":0,"successCount":0,"errorCount":0,"timeMetrics":{"avg":0,"max":0,"min":0,"total":0},"createdAt":"0001-01-01T00:00:00Z","updatedAt":"0001-01-01T00:00:00Z"},"createdAt":"0001-01-01T00:00:00Z","updatedAt":"0001-01-01T00:00:00Z"}`,
+		},
+		{
+			name: "Nil pauseUntil",
+			job: JobPersistence{
+				Timeout:          &time1Hour,
+				RetryDelay:       &time30s,
+				PauseUntil:       nil,
+				TimeoutString:    &time1h0m0sString,
+				RetryDelayString: &time30sString,
+				PauseUntilString: &emptyString,
+			},
+			expected: `{"id":"","description":"","priority":0,"maxRetries":0,"runnable":{"type":""},"scheduler":{"type":""},"pause":false,"pauseReason":"","timeout":"1h0m0s","retryDelay":"30s","pauseUntil":"","stats":{"jobId":"","runCount":0,"successCount":0,"errorCount":0,"timeMetrics":{"avg":0,"max":0,"min":0,"total":0},"createdAt":"0001-01-01T00:00:00Z","updatedAt":"0001-01-01T00:00:00Z"},"createdAt":"0001-01-01T00:00:00Z","updatedAt":"0001-01-01T00:00:00Z"}`,
+		},
+		{
+			name: "Nil timeout and retryDelay",
+			job: JobPersistence{
+				Timeout:          nil,
+				RetryDelay:       nil,
+				PauseUntil:       nil,
+				TimeoutString:    &emptyString,
+				RetryDelayString: &emptyString,
+				PauseUntilString: &emptyString,
+			},
+			expected: `{"id":"","description":"","priority":0,"maxRetries":0,"runnable":{"type":""},"scheduler":{"type":""},"pause":false,"pauseReason":"","timeout":"","retryDelay":"","pauseUntil":"","stats":{"jobId":"","runCount":0,"successCount":0,"errorCount":0,"timeMetrics":{"avg":0,"max":0,"min":0,"total":0},"createdAt":"0001-01-01T00:00:00Z","updatedAt":"0001-01-01T00:00:00Z"},"createdAt":"0001-01-01T00:00:00Z","updatedAt":"0001-01-01T00:00:00Z"}`,
+		},
+	}
+
+	for _, tc := range tests {
+		t.Run(tc.name, func(t *testing.T) {
+			// MarshalJSON testen
+			data, err := json.Marshal(tc.job)
+
+			assert.NoError(t, err)
+			assert.JSONEq(t, tc.expected, string(data))
+
+			var job JobPersistence
+			err = json.Unmarshal(data, &job)
+			assert.NoError(t, err)
+			assert.Equal(t, tc.job.Timeout, job.Timeout)
+			assert.Equal(t, tc.job.RetryDelay, job.RetryDelay)
+			if tc.job.PauseUntil != nil {
+				assert.Equal(t, *tc.job.PauseUntil, *job.PauseUntil)
+			} else {
+				assert.Nil(t, job.PauseUntil)
+			}
+		})
+	}
+}
+
 func TestReadJsonFile(t *testing.T) {
 	testContent := `[{"id": "1", "priority": 1}]`
 	tempFile, err := ioutil.TempFile("", "test.json")
diff --git a/worker.go b/worker.go
index 604df3e..fb1dce2 100644
--- a/worker.go
+++ b/worker.go
@@ -208,8 +208,8 @@ func (w *LocalWorker) run(jobChannel chan GenericJob, stopChan chan bool, cancel
 				var cancel context.CancelFunc
 
 				timeout := job.GetTimeout()
-				if timeout > 0 {
-					ctx, cancel = context.WithTimeout(ctx, timeout)
+				if timeout != nil && *timeout > 0 {
+					ctx, cancel = context.WithTimeout(ctx, *timeout)
 				}
 
 				Info("Executing job on worker thread", "worker", w.ID, "thread_id", workerThreadID, "job_id", job.GetID())
@@ -230,8 +230,8 @@ func (w *LocalWorker) run(jobChannel chan GenericJob, stopChan chan bool, cancel
 					break
 				}
 
-				if retryDelay > 0 {
-					time.Sleep(retryDelay)
+				if retryDelay != nil && *retryDelay > 0 {
+					time.Sleep(*retryDelay)
 				}
 
 				retries--
diff --git a/worker_test.go b/worker_test.go
index 84bdb4c..9ca1a54 100644
--- a/worker_test.go
+++ b/worker_test.go
@@ -47,12 +47,12 @@ func (j DummyJob) GetMaxRetries() uint {
 	return 0
 }
 
-func (j DummyJob) GetRetryDelay() time.Duration {
-	return 0
+func (j DummyJob) GetRetryDelay() *time.Duration {
+	return nil
 }
 
-func (j DummyJob) GetTimeout() time.Duration {
-	return 0
+func (j DummyJob) GetTimeout() *time.Duration {
+	return nil
 }
 
 func (j DummyJob) Execute(_ context.Context) (RunGenericResult, error) {
-- 
GitLab