Repository: go-co-op/gocron
Pull request #1 (2020-03-20 19:42:51)

schedule logic and job refactor
- jobs is now a slice instead of array;
- fixing timezone on tests;
- scheduling logic completely moved from job.go to scheduler.go;
- updating README.md;
- removed default scheduler;

Streppel: @JohnRoesler 

Patch:
diff --git a/README.md b/README.md
index a7ef49e..6c1f038 100644
--- a/README.md
+++ b/README.md
@@ -1,12 +1,10 @@
-# goCron: A Golang Job Scheduling Package.
+## goCron: A Golang Job Scheduling Package.
 
 [![CI State](https://github.com/go-co-op/gocron/workflows/Go%20Test/badge.svg)](https://github.com/go-co-op/gocron/actions?query=workflow%3A"Go+Test")
-[![GgoDoc](https://godoc.org/github.com/golang/gddo?status.svg)](http://godoc.org/github.com/jasonlvhit/gocron)
-[![Go Report Card](https://goreportcard.com/badge/github.com/jasonlvhit/gocron)](https://goreportcard.com/report/github.com/jasonlvhit/gocron)
 
 goCron is a Golang job scheduling package which lets you run Go functions periodically at pre-determined interval using a simple, human-friendly syntax.
 
-goCron is a Golang implementation of Ruby module [clockwork](https://github.com/tomykaira/clockwork) and Python job scheduling package [schedule](https://github.com/dbader/schedule), and personally, this package is my first Golang program, just for fun and practice.
+goCron is a Golang implementation of Ruby module [clockwork](https://github.com/tomykaira/clockwork) and Python job scheduling package [schedule](https://github.com/dbader/schedule).
 
 See also this two great articles:
 
@@ -15,8 +13,7 @@ See also this two great articles:
 
 If you want to chat, you can find us at Slack! [<img src="https://img.shields.io/badge/gophers-gocron-brightgreen?logo=slack">](https://gophers.slack.com/archives/CQ7T0T1FW)
 
-
-Back to this package, you could just use this simple API as below, to run a cron scheduler.
+Examples:
 
 ```go
 package main
@@ -25,67 +22,68 @@ import (
 	"fmt"
 	"time"
 
-	"github.com/jasonlvhit/gocron"
+	"github.com/go-co-op/gocron"
 )
 
 func task() {
-	fmt.Println("I am running task.")
+    fmt.Println("I am running task.")
 }
 
 func taskWithParams(a int, b string) {
-	fmt.Println(a, b)
+    fmt.Println(a, b)
 }
 
 func main() {
-	// Do jobs without params
-	gocron.Every(1).Second().Do(task)
-	gocron.Every(2).Seconds().Do(task)
-	gocron.Every(1).Minute().Do(task)
-	gocron.Every(2).Minutes().Do(task)
-	gocron.Every(1).Hour().Do(task)
-	gocron.Every(2).Hours().Do(task)
-	gocron.Every(1).Day().Do(task)
-	gocron.Every(2).Days().Do(task)
-	gocron.Every(1).Week().Do(task)
-	gocron.Every(2).Weeks().Do(task)
-
-	// Do jobs with params
-	gocron.Every(1).Second().Do(taskWithParams, 1, "hello")
-
-	// Do jobs on specific weekday
-	gocron.Every(1).Monday().Do(task)
-	gocron.Every(1).Thursday().Do(task)
-
-	// Do a job at a specific time - 'hour:min:sec' - seconds optional
-	gocron.Every(1).Day().At("10:30").Do(task)
-	gocron.Every(1).Monday().At("18:30").Do(task)
-	gocron.Every(1).Tuesday().At("18:30:59").Do(task)
-
-	// Begin job immediately upon start
-	gocron.Every(1).Hour().From(gocron.NextTick()).Do(task)
-
-	// Begin job at a specific date/time
-	t := time.Date(2019, time.November, 10, 15, 0, 0, 0, time.Local)
-	gocron.Every(1).Hour().From(&t).Do(task)
-
-	// NextRun gets the next running time
-	_, time := gocron.NextRun()
-	fmt.Println(time)
-
-	// Remove a specific job
-	gocron.Remove(task)
-
-	// Clear all scheduled jobs
-	gocron.Clear()
-
-	// Start all the pending jobs
-	<- gocron.Start()
-
-	// also, you can create a new scheduler
-	// to run two schedulers concurrently
-	s := gocron.NewScheduler()
-	s.Every(3).Seconds().Do(task)
-	<- s.Start()
+    
+    s1 := gocron.NewScheduler(time.UTC)
+    s1.Every(3).Seconds().Do(task)
+    <- s1.Start() // starts running (blocks current thread)
+
+    // Do jobs without params
+    s2 := gocron.NewScheduler(time.UTC)
+    s2.Every(1).Second().Do(task)
+    s2.Every(2).Seconds().Do(task)
+    s2.Every(1).Minute().Do(task)
+    s2.Every(2).Minutes().Do(task)
+    s2.Every(1).Hour().Do(task)
+    s2.Every(2).Hours().Do(task)
+    s2.Every(1).Day().Do(task)
+    s2.Every(2).Days().Do(task)
+    s2.Every(1).Week().Do(task)
+    s2.Every(2).Weeks().Do(task)
+
+    // Do jobs with params
+    s2.Every(1).Second().Do(taskWithParams, 1, "hello")
+
+    // Do jobs on specific weekday
+    s2.Every(1).Monday().Do(task)
+    s2.Every(1).Thursday().Do(task)
+
+    // Do a job at a specific time - 'hour:min:sec' - seconds optional
+    s2.Every(1).Day().At("10:30").Do(task)
+    s2.Every(1).Monday().At("18:30").Do(task)
+    s2.Every(1).Tuesday().At("18:30:59").Do(task)
+
+    // use .From(gocron.NextTick()) to run job immediately upon start
+    s2.Every(1).Hour().From(gocron.NextTick()).Do(task)
+
+    // Begin job at a specific date/time. 
+    // Attention: scheduler timezone has precedence over job's timezone!
+    t := time.Date(2019, time.November, 10, 15, 0, 0, 0, time.UTC)
+    s2.Every(1).Hour().From(&t).Do(task)
+
+    // NextRun gets the next running time
+    _, time := s2.NextRun()
+    fmt.Println(time)
+
+    // Remove a specific job
+    s2.Remove(task)
+
+    // Clear all scheduled jobs
+    s2.Clear()
+
+    // Start all the pending jobs
+    <- gocron.Start()
 }
 ```
 
@@ -99,8 +97,6 @@ gocron.SetLocker(lockerImplementation)
 gocron.Every(1).Hour().Lock().Do(task)
 ```
 
-Once again, thanks to the great works of Ruby clockwork and Python schedule package. BSD license is used, see the file License for detail.
-
 Looking to contribute? Try to follow these guidelines:
  * Use issues for everything
  * For a small change, just send a PR!
@@ -110,5 +106,3 @@ Looking to contribute? Try to follow these guidelines:
     * Reporting issues
     * Suggesting new features or enhancements
     * Improving/fixing documentation
-
-Have fun!
diff --git a/example/lock.go b/example/lock.go
index bc974b9..d263e0f 100644
--- a/example/lock.go
+++ b/example/lock.go
@@ -5,7 +5,7 @@ import (
 	"os"
 	"time"
 
-	"github.com/jasonlvhit/gocron"
+	"github.com/go-co-op/gocron"
 
 	"github.com/go-redis/redis"
 )
@@ -68,6 +68,7 @@ func main() {
 		arg = args[0]
 	}
 
-	gocron.Every(1).Second().Lock().Do(lockedTask, arg)
-	<-gocron.Start()
+	s := gocron.NewScheduler(time.UTC)
+	s.Every(1).Second().Lock().Do(lockedTask, arg)
+	<-s.Start()
 }
diff --git a/go.mod b/go.mod
index a240779..786f4df 100644
--- a/go.mod
+++ b/go.mod
@@ -1,4 +1,4 @@
-module github.com/jasonlvhit/gocron
+module github.com/go-co-op/gocron
 
 go 1.13
 
diff --git a/gocron.go b/gocron.go
index da0e92d..17a9674 100644
--- a/gocron.go
+++ b/gocron.go
@@ -38,9 +38,6 @@ type Locker interface {
 
 type timeUnit int
 
-// MAXJOBNUM max number of jobs, hack it if you need.
-const MAXJOBNUM = 10000
-
 //go:generate stringer -type=timeUnit
 const (
 	seconds timeUnit = iota + 1
@@ -51,16 +48,9 @@ const (
 )
 
 var (
-	loc    = time.Local // Time location, default set by the time.Local (*time.Location)
 	locker Locker
 )
 
-// ChangeLoc change default the time location
-func ChangeLoc(newLocation *time.Location) {
-	loc = newLocation
-	defaultScheduler.ChangeLoc(newLocation)
-}
-
 // SetLocker sets a locker implementation
 func SetLocker(l Locker) {
 	locker = l
@@ -89,11 +79,6 @@ func getFunctionKey(funcName string) string {
 	return fmt.Sprintf("%x", h.Sum(nil))
 }
 
-// Jobs returns the list of Jobs from the defaultScheduler
-func Jobs() []*Job {
-	return defaultScheduler.Jobs()
-}
-
 func formatTime(t string) (hour, min, sec int, err error) {
 	ts := strings.Split(t, ":")
 	if len(ts) < 2 || len(ts) > 3 {
diff --git a/job.go b/job.go
index e1368c4..738025f 100644
--- a/job.go
+++ b/job.go
@@ -2,9 +2,6 @@ package gocron
 
 import (
 	"errors"
-	"fmt"
-	"log"
-	"reflect"
 	"time"
 )
 
@@ -23,7 +20,6 @@ type Job struct {
 	unit     timeUnit                 // time units, ,e.g. 'minutes', 'hours'...
 	atTime   time.Duration            // optional time at which this job runs
 	err      error                    // error related to job
-	loc      *time.Location           // optional timezone that the atTime is in
 	lastRun  time.Time                // datetime of last run
 	nextRun  time.Time                // datetime of next run
 	startDay time.Weekday             // Specific day of the week to start on
@@ -37,7 +33,6 @@ type Job struct {
 func NewJob(interval uint64) *Job {
 	return &Job{
 		interval: interval,
-		loc:      loc,
 		lastRun:  time.Unix(0, 0),
 		nextRun:  time.Unix(0, 0),
 		startDay: time.Sunday,
@@ -53,26 +48,9 @@ func (j *Job) shouldRun() bool {
 }
 
 //Run the job and immediately reschedule it
-func (j *Job) run() ([]reflect.Value, error) {
-	if j.lock {
-		if locker == nil {
-			return nil, fmt.Errorf("trying to lock %s with nil locker", j.jobFunc)
-		}
-		key := getFunctionKey(j.jobFunc)
-
-		locker.Lock(key)
-		defer locker.Unlock(key)
-	}
-
+func (j *Job) run() {
 	j.lastRun = time.Now()
-	if err := j.scheduleNextRun(); err != nil {
-		return nil, err
-	}
-	result, err := callJobFuncWithParams(j.funcs[j.jobFunc], j.fparams[j.jobFunc])
-	if err != nil {
-		return nil, err
-	}
-	return result, nil
+	go callJobFuncWithParams(j.funcs[j.jobFunc], j.fparams[j.jobFunc])
 }
 
 // Err should be checked to ensure an error didn't occur creating the job
@@ -80,68 +58,6 @@ func (j *Job) Err() error {
 	return j.err
 }
 
-// Do specifies the jobFunc that should be called every time the job runs
-func (j *Job) Do(jobFun interface{}, params ...interface{}) error {
-	if j.err != nil {
-		return j.err
-	}
-
-	typ := reflect.TypeOf(jobFun)
-	if typ.Kind() != reflect.Func {
-		return ErrNotAFunction
-	}
-	fname := getFunctionName(jobFun)
-	j.funcs[fname] = jobFun
-	j.fparams[fname] = params
-	j.jobFunc = fname
-	j.scheduleNextRun()
-
-	return nil
-}
-
-// DoSafely does the same thing as Do, but logs unexpected panics, instead of unwinding them up the chain
-// Deprecated: DoSafely exists due to historical compatibility and will be removed soon. Use Do instead
-func (j *Job) DoSafely(jobFun interface{}, params ...interface{}) error {
-	recoveryWrapperFunc := func() {
-		defer func() {
-			if r := recover(); r != nil {
-				log.Printf("Internal panic occurred: %s", r)
-			}
-		}()
-
-		_, _ = callJobFuncWithParams(jobFun, params)
-	}
-
-	return j.Do(recoveryWrapperFunc)
-}
-
-// At schedules job at specific time of day
-//	s.Every(1).Day().At("10:30:01").Do(task)
-//	s.Every(1).Monday().At("10:30:01").Do(task)
-func (j *Job) At(t string) *Job {
-	hour, min, sec, err := formatTime(t)
-	if err != nil {
-		j.err = ErrTimeFormat
-		return j
-	}
-	// save atTime start as duration from midnight
-	j.atTime = time.Duration(hour)*time.Hour + time.Duration(min)*time.Minute + time.Duration(sec)*time.Second
-	return j
-}
-
-// GetAt returns the specific time of day the job will run at
-//	s.Every(1).Day().At("10:30").GetAt() == "10:30"
-func (j *Job) GetAt() string {
-	return fmt.Sprintf("%d:%d", j.atTime/time.Hour, (j.atTime%time.Hour)/time.Minute)
-}
-
-// Loc sets the location for which to interpret "At"
-//	s.Every(1).Day().At("10:30").Loc(time.UTC).Do(task)
-func (j *Job) Loc(loc *time.Location) *Job {
-	j.loc = loc
-	return j
-}
-
 // Tag allows you to add labels to a job
 // they don't impact the functionality of the job.
 func (j *Job) Tag(t string, others ...string) {
@@ -189,182 +105,13 @@ func (j *Job) periodDuration() (time.Duration, error) {
 	return periodDuration, nil
 }
 
-// roundToMidnight truncate time to midnight
-func (j *Job) roundToMidnight(t time.Time) time.Time {
-	return time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, j.loc)
-}
-
-// scheduleNextRun Compute the instant when this job should run next
-func (j *Job) scheduleNextRun() error {
-	now := time.Now()
-	if j.lastRun == time.Unix(0, 0) {
-		j.lastRun = now
-	}
-
-	if j.nextRun.After(now) {
-		return nil
-	}
-
-	periodDuration, err := j.periodDuration()
-	if err != nil {
-		return err
-	}
-
-	switch j.unit {
-	case seconds, minutes, hours:
-		j.nextRun = j.lastRun.Add(periodDuration)
-	case days:
-		j.nextRun = j.roundToMidnight(j.lastRun)
-		j.nextRun = j.nextRun.Add(j.atTime)
-	case weeks:
-		j.nextRun = j.roundToMidnight(j.lastRun)
-		dayDiff := int(j.startDay)
-		dayDiff -= int(j.nextRun.Weekday())
-		if dayDiff != 0 {
-			j.nextRun = j.nextRun.Add(time.Duration(dayDiff) * 24 * time.Hour)
-		}
-		j.nextRun = j.nextRun.Add(j.atTime)
-	}
-
-	// advance to next possible schedule
-	for j.nextRun.Before(now) || j.nextRun.Before(j.lastRun) {
-		j.nextRun = j.nextRun.Add(periodDuration)
-	}
-
-	return nil
-}
-
 // NextScheduledTime returns the time of when this job is to run next
 func (j *Job) NextScheduledTime() time.Time {
 	return j.nextRun
 }
 
-// set the job's unit with seconds,minutes,hours...
-func (j *Job) mustInterval(i uint64) error {
-	if j.interval != i {
-		return fmt.Errorf("interval must be %d", i)
-	}
-	return nil
-}
-
-// From schedules the next run of the job
-func (j *Job) From(t *time.Time) *Job {
-	j.nextRun = *t
-	return j
-}
-
-// setUnit sets unit type
-func (j *Job) setUnit(unit timeUnit) *Job {
-	j.unit = unit
-	return j
-}
-
-// Seconds set the unit with seconds
-func (j *Job) Seconds() *Job {
-	return j.setUnit(seconds)
-}
-
-// Minutes set the unit with minute
-func (j *Job) Minutes() *Job {
-	return j.setUnit(minutes)
-}
-
-// Hours set the unit with hours
-func (j *Job) Hours() *Job {
-	return j.setUnit(hours)
-}
-
-// Days set the job's unit with days
-func (j *Job) Days() *Job {
-	return j.setUnit(days)
-}
-
-// Weeks sets the units as weeks
-func (j *Job) Weeks() *Job {
-	return j.setUnit(weeks)
-}
-
-// Second sets the unit with second
-func (j *Job) Second() *Job {
-	j.mustInterval(1)
-	return j.Seconds()
-}
-
-// Minute sets the unit  with minute, which interval is 1
-func (j *Job) Minute() *Job {
-	j.mustInterval(1)
-	return j.Minutes()
-}
-
-// Hour sets the unit with hour, which interval is 1
-func (j *Job) Hour() *Job {
-	j.mustInterval(1)
-	return j.Hours()
-}
-
-// Day sets the job's unit with day, which interval is 1
-func (j *Job) Day() *Job {
-	j.mustInterval(1)
-	return j.Days()
-}
-
-// Week sets the job's unit with week, which interval is 1
-func (j *Job) Week() *Job {
-	j.mustInterval(1)
-	return j.Weeks()
-}
-
-// Weekday start job on specific Weekday
-func (j *Job) Weekday(startDay time.Weekday) *Job {
-	j.mustInterval(1)
-	j.startDay = startDay
-	return j.Weeks()
-}
-
 // GetWeekday returns which day of the week the job will run on
 // This should only be used when .Weekday(...) was called on the job.
 func (j *Job) GetWeekday() time.Weekday {
 	return j.startDay
 }
-
-// Monday set the start day with Monday
-// - s.Every(1).Monday().Do(task)
-func (j *Job) Monday() (job *Job) {
-	return j.Weekday(time.Monday)
-}
-
-// Tuesday sets the job start day Tuesday
-func (j *Job) Tuesday() *Job {
-	return j.Weekday(time.Tuesday)
-}
-
-// Wednesday sets the job start day Wednesday
-func (j *Job) Wednesday() *Job {
-	return j.Weekday(time.Wednesday)
-}
-
-// Thursday sets the job start day Thursday
-func (j *Job) Thursday() *Job {
-	return j.Weekday(time.Thursday)
-}
-
-// Friday sets the job start day Friday
-func (j *Job) Friday() *Job {
-	return j.Weekday(time.Friday)
-}
-
-// Saturday sets the job start day Saturday
-func (j *Job) Saturday() *Job {
-	return j.Weekday(time.Saturday)
-}
-
-// Sunday sets the job start day Sunday
-func (j *Job) Sunday() *Job {
-	return j.Weekday(time.Sunday)
-}
-
-// Lock prevents job to run from multiple instances of gocron
-func (j *Job) Lock() *Job {
-	j.lock = true
-	return j
-}
diff --git a/scheduler.go b/scheduler.go
index e9c1c3f..8ea14e1 100644
--- a/scheduler.go
+++ b/scheduler.go
@@ -1,6 +1,8 @@
 package gocron
 
 import (
+	"fmt"
+	"reflect"
 	"sort"
 	"time"
 )
@@ -8,31 +10,46 @@ import (
 // Scheduler struct, the only data member is the list of jobs.
 // - implements the sort.Interface{} for sorting jobs, by the time nextRun
 type Scheduler struct {
-	jobs [MAXJOBNUM]*Job // Array store jobs
-	size int             // Size of jobs which jobs holding.
-	loc  *time.Location  // Location to use when scheduling jobs with specified times
+	jobs []*Job
+	loc  *time.Location
 }
 
-var (
-	defaultScheduler = NewScheduler()
-)
-
 // NewScheduler creates a new scheduler
-func NewScheduler() *Scheduler {
+func NewScheduler(loc *time.Location) *Scheduler {
 	return &Scheduler{
-		jobs: [MAXJOBNUM]*Job{},
-		size: 0,
+		jobs: newEmptyJobSlice(),
 		loc:  loc,
 	}
 }
 
+// Start all the pending jobs
+// Add seconds ticker
+func (s *Scheduler) Start() chan bool {
+	stopped := make(chan bool)
+	ticker := time.NewTicker(1 * time.Second)
+
+	go func() {
+		for {
+			select {
+			case <-ticker.C:
+				s.RunPending()
+			case <-stopped:
+				ticker.Stop()
+				return
+			}
+		}
+	}()
+
+	return stopped
+}
+
 // Jobs returns the list of Jobs from the Scheduler
 func (s *Scheduler) Jobs() []*Job {
-	return s.jobs[:s.size]
+	return s.jobs
 }
 
 func (s *Scheduler) Len() int {
-	return s.size
+	return len(s.jobs)
 }
 
 func (s *Scheduler) Swap(i, j int) {
@@ -48,25 +65,72 @@ func (s *Scheduler) ChangeLoc(newLocation *time.Location) {
 	s.loc = newLocation
 }
 
+// scheduleNextRun Compute the instant when this job should run next
+func (s *Scheduler) scheduleNextRun(j *Job) error {
+	if j.nextRun.Location() != s.loc {
+		j.nextRun = j.nextRun.In(s.loc)
+	}
+
+	now := time.Now().In(s.loc)
+	if j.lastRun == time.Unix(0, 0) {
+		j.lastRun = now
+	}
+
+	if j.nextRun.After(now) {
+		return nil
+	}
+
+	periodDuration, err := j.periodDuration()
+	if err != nil {
+		return err
+	}
+
+	switch j.unit {
+	case seconds, minutes, hours:
+		j.nextRun = j.lastRun.Add(periodDuration)
+	case days:
+		j.nextRun = s.roundToMidnight(j.lastRun)
+		j.nextRun = j.nextRun.Add(j.atTime)
+	case weeks:
+		j.nextRun = s.roundToMidnight(j.lastRun)
+		dayDiff := int(j.startDay)
+		dayDiff -= int(j.nextRun.Weekday())
+		if dayDiff != 0 {
+			j.nextRun = j.nextRun.Add(time.Duration(dayDiff) * 24 * time.Hour)
+		}
+		j.nextRun = j.nextRun.Add(j.atTime)
+	}
+
+	// advance to next possible schedule
+	for j.nextRun.Before(now) || j.nextRun.Before(j.lastRun) {
+		j.nextRun = j.nextRun.Add(periodDuration)
+	}
+
+	return nil
+}
+
+// roundToMidnight truncate time to midnight
+func (j *Scheduler) roundToMidnight(t time.Time) time.Time {
+	return time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, j.loc)
+}
+
 // Get the current runnable jobs, which shouldRun is True
-func (s *Scheduler) getRunnableJobs() (runningJobs [MAXJOBNUM]*Job, n int) {
-	runnableJobs := [MAXJOBNUM]*Job{}
-	n = 0
+func (s *Scheduler) getRunnableJobs() []*Job {
+	var runnableJobs []*Job
 	sort.Sort(s)
-	for i := 0; i < s.size; i++ {
-		if s.jobs[i].shouldRun() {
-			runnableJobs[n] = s.jobs[i]
-			n++
+	for _, job := range s.jobs {
+		if job.shouldRun() {
+			runnableJobs = append(runnableJobs, job)
 		} else {
 			break
 		}
 	}
-	return runnableJobs, n
+	return runnableJobs
 }
 
 // NextRun datetime when the next job should run.
 func (s *Scheduler) NextRun() (*Job, time.Time) {
-	if s.size <= 0 {
+	if len(s.jobs) <= 0 {
 		return nil, time.Now()
 	}
 	sort.Sort(s)
@@ -74,36 +138,51 @@ func (s *Scheduler) NextRun() (*Job, time.Time) {
 }
 
 // Every schedule a new periodic job with interval
-func (s *Scheduler) Every(interval uint64) *Job {
-	job := NewJob(interval).Loc(s.loc)
-	s.jobs[s.size] = job
-	s.size++
-	return job
+func (s *Scheduler) Every(interval uint64) *Scheduler {
+	job := NewJob(interval)
+	s.jobs = append(s.jobs, job)
+	return s
 }
 
 // RunPending runs all the jobs that are scheduled to run.
 func (s *Scheduler) RunPending() {
-	runnableJobs, n := s.getRunnableJobs()
+	runnableJobs := s.getRunnableJobs()
+	for _, job := range runnableJobs {
+		s.runJob(job)
+	}
+}
 
-	if n != 0 {
-		for i := 0; i < n; i++ {
-			runnableJobs[i].run()
+func (s *Scheduler) runJob(job *Job) error {
+	if job.lock {
+		if locker == nil {
+			return fmt.Errorf("trying to lock %s with nil locker", job.jobFunc)
 		}
+		key := getFunctionKey(job.jobFunc)
+
+		locker.Lock(key)
+		defer locker.Unlock(key)
+	}
+	job.run()
+	err := s.scheduleNextRun(job)
+	if err != nil {
+		return err
 	}
+	return nil
 }
 
 // RunAll run all jobs regardless if they are scheduled to run or not
 func (s *Scheduler) RunAll() {
-	s.RunAllwithDelay(0)
+	s.RunAllWithDelay(0)
 }
 
 // RunAllwithDelay runs all jobs with delay seconds
-func (s *Scheduler) RunAllwithDelay(d int) {
-	for i := 0; i < s.size; i++ {
-		s.jobs[i].run()
-		if 0 != d {
-			time.Sleep(time.Duration(d))
+func (s *Scheduler) RunAllWithDelay(d int) {
+	for _, job := range s.jobs {
+		err := s.runJob(job)
+		if err != nil {
+			continue
 		}
+		time.Sleep(time.Duration(d) * time.Second)
 	}
 }
 
@@ -122,30 +201,19 @@ func (s *Scheduler) RemoveByRef(j *Job) {
 }
 
 func (s *Scheduler) removeByCondition(shouldRemove func(*Job) bool) {
-	i := 0
-
-	// keep deleting until no more jobs match the criteria
-	for {
-		found := false
-
-		for ; i < s.size; i++ {
-			if shouldRemove(s.jobs[i]) {
-				found = true
-				break
-			}
-		}
-
-		if !found {
-			return
+	for i, job := range s.jobs {
+		if shouldRemove(job) {
+			s.jobs = removeAtIndex(s.jobs, i)
 		}
+	}
+}
 
-		for j := (i + 1); j < s.size; j++ {
-			s.jobs[i] = s.jobs[j]
-			i++
-		}
-		s.size--
-		s.jobs[s.size] = nil
+func removeAtIndex(jobs []*Job, i int) []*Job {
+	if i == len(jobs)-1 {
+		return jobs[:i]
 	}
+	jobs = append(jobs[:i], jobs[i+1:]...)
+	return jobs
 }
 
 // Scheduled checks if specific job j was already added
@@ -160,92 +228,185 @@ func (s *Scheduler) Scheduled(j interface{}) bool {
 
 // Clear delete all scheduled jobs
 func (s *Scheduler) Clear() {
-	for i := 0; i < s.size; i++ {
-		s.jobs[i] = nil
+	s.jobs = newEmptyJobSlice()
+}
+
+func newEmptyJobSlice() []*Job {
+	const initialCapacity = 256
+	return make([]*Job, 0, initialCapacity)
+}
+
+// Do specifies the jobFunc that should be called every time the job runs
+func (s *Scheduler) Do(jobFun interface{}, params ...interface{}) (*Job, error) {
+	j := s.getCurrentJob()
+	if j.err != nil {
+		return nil, j.err
+	}
+
+	typ := reflect.TypeOf(jobFun)
+	if typ.Kind() != reflect.Func {
+		return nil, ErrNotAFunction
 	}
-	s.size = 0
+
+	fname := getFunctionName(jobFun)
+	j.funcs[fname] = jobFun
+	j.fparams[fname] = params
+	j.jobFunc = fname
+	if err := s.scheduleNextRun(j); err != nil {
+		return nil, err
+	}
+
+	return j, nil
 }
 
-// Start all the pending jobs
-// Add seconds ticker
-func (s *Scheduler) Start() chan bool {
-	stopped := make(chan bool, 1)
-	ticker := time.NewTicker(1 * time.Second)
+// At schedules job at specific time of day
+// s.Every(1).Day().At("10:30:01").Do(task)
+// s.Every(1).Monday().At("10:30:01").Do(task)
+func (s *Scheduler) At(t string) *Scheduler {
+	j := s.getCurrentJob()
+	hour, min, sec, err := formatTime(t)
+	if err != nil {
+		j.err = ErrTimeFormat
+		return s
+	}
+	// save atTime start as duration from midnight
+	j.atTime = time.Duration(hour)*time.Hour + time.Duration(min)*time.Minute + time.Duration(sec)*time.Second
+	return s
+}
 
-	go func() {
-		for {
-			select {
-			case <-ticker.C:
-				s.RunPending()
-			case <-stopped:
-				ticker.Stop()
-				return
-			}
-		}
-	}()
+// GetAt returns the specific time of day the job will run at
+//	s.Every(1).Day().At("10:30").GetAt() == "10:30"
+func (j *Job) GetAt() string {
+	return fmt.Sprintf("%d:%d", j.atTime/time.Hour, (j.atTime%time.Hour)/time.Minute)
+}
 
-	return stopped
+// From schedules the next run of the job
+func (s *Scheduler) From(t *time.Time) *Scheduler {
+	currentJob := s.getCurrentJob()
+	currentJob.nextRun = *t
+	return s
 }
 
-// The following methods are shortcuts for not having to
-// create a Scheduler instance
+// setUnit sets unit type
+func (s *Scheduler) setUnit(unit timeUnit) {
+	currentJob := s.getCurrentJob()
+	currentJob.unit = unit
+}
 
-// Every schedules a new periodic job running in specific interval
-func Every(interval uint64) *Job {
-	return defaultScheduler.Every(interval)
+// Seconds set the unit with seconds
+func (s *Scheduler) Seconds() *Scheduler {
+	s.setUnit(seconds)
+	return s
 }
 
-// RunPending run all jobs that are scheduled to run
-//
-// Please note that it is *intended behavior that run_pending()
-// does not run missed jobs*. For example, if you've registered a job
-// that should run every minute and you only call run_pending()
-// in one hour increments then your job won't be run 60 times in
-// between but only once.
-func RunPending() {
-	defaultScheduler.RunPending()
+// Minutes set the unit with minute
+func (s *Scheduler) Minutes() *Scheduler {
+	s.setUnit(minutes)
+	return s
 }
 
-// RunAll run all jobs regardless if they are scheduled to run or not.
-func RunAll() {
-	defaultScheduler.RunAll()
+// Hours set the unit with hours
+func (s *Scheduler) Hours() *Scheduler {
+	s.setUnit(hours)
+	return s
 }
 
-// RunAllwithDelay run all the jobs with a delay in seconds
-//
-// A delay of `delay` seconds is added between each job. This can help
-// to distribute the system load generated by the jobs more evenly over
-// time.
-func RunAllwithDelay(d int) {
-	defaultScheduler.RunAllwithDelay(d)
+// Second sets the unit with second
+func (s *Scheduler) Second() *Scheduler {
+	s.mustInterval(1)
+	return s.Seconds()
 }
 
-// Start run all jobs that are scheduled to run
-func Start() chan bool {
-	return defaultScheduler.Start()
+// Minute sets the unit  with minute, which interval is 1
+func (s *Scheduler) Minute() *Scheduler {
+	s.mustInterval(1)
+	return s.Minutes()
 }
 
-// Clear all scheduled jobs
-func Clear() {
-	defaultScheduler.Clear()
+// Hour sets the unit with hour, which interval is 1
+func (s *Scheduler) Hour() *Scheduler {
+	s.mustInterval(1)
+	return s.Hours()
 }
 
-// Remove a specific job
-func Remove(j interface{}) {
-	defaultScheduler.Remove(j)
+// Day sets the job's unit with day, which interval is 1
+func (s *Scheduler) Day() *Scheduler {
+	s.mustInterval(1)
+	s.setUnit(days)
+	return s
 }
 
-// Scheduled checks if specific job j was already added
-func Scheduled(j interface{}) bool {
-	for _, job := range defaultScheduler.jobs {
-		if job.jobFunc == getFunctionName(j) {
-			return true
-		}
-	}
-	return false
+// Days set the job's unit with days
+func (s *Scheduler) Days() *Scheduler {
+	s.setUnit(days)
+	return s
+}
+
+// Week sets the job's unit with week, which interval is 1
+func (s *Scheduler) Week() *Scheduler {
+	s.mustInterval(1)
+	s.setUnit(weeks)
+	return s
+}
+
+// Weekday start job on specific Weekday
+func (s *Scheduler) Weekday(startDay time.Weekday) *Scheduler {
+	s.mustInterval(1)
+	s.getCurrentJob().startDay = startDay
+	s.setUnit(weeks)
+	return s
+}
+
+// Monday set the start day with Monday
+// - s.Every(1).Monday().Do(task)
+func (s *Scheduler) Monday() *Scheduler {
+	return s.Weekday(time.Monday)
+}
+
+// Tuesday sets the job start day Tuesday
+func (s *Scheduler) Tuesday() *Scheduler {
+	return s.Weekday(time.Tuesday)
+}
+
+// Wednesday sets the job start day Wednesday
+func (s *Scheduler) Wednesday() *Scheduler {
+	return s.Weekday(time.Wednesday)
 }
 
-// NextRun gets the next running time
-func NextRun() (job *Job, time time.Time) {
-	return defaultScheduler.NextRun()
+// Thursday sets the job start day Thursday
+func (s *Scheduler) Thursday() *Scheduler {
+	return s.Weekday(time.Thursday)
+}
+
+// Friday sets the job start day Friday
+func (s *Scheduler) Friday() *Scheduler {
+	return s.Weekday(time.Friday)
+}
+
+// Saturday sets the job start day Saturday
+func (s *Scheduler) Saturday() *Scheduler {
+	return s.Weekday(time.Saturday)
+}
+
+// Sunday sets the job start day Sunday
+func (s *Scheduler) Sunday() *Scheduler {
+	return s.Weekday(time.Sunday)
+}
+
+func (s *Scheduler) getCurrentJob() *Job {
+	return s.jobs[len(s.jobs)-1]
+}
+
+// Lock prevents job to run from multiple instances of gocron
+func (s *Scheduler) Lock() *Scheduler {
+	s.getCurrentJob().lock = true
+	return s
+}
+
+// set the job's unit with seconds,minutes,hours...
+func (s *Scheduler) mustInterval(i uint64) error {
+	if s.getCurrentJob().interval != i {
+		return fmt.Errorf("interval must be %d", i)
+	}
+	return nil
 }


Test patch:
diff --git a/gocron_test.go b/gocron_test.go
index 465267a..fadac86 100644
--- a/gocron_test.go
+++ b/gocron_test.go
@@ -2,7 +2,6 @@ package gocron
 
 import (
 	"fmt"
-	"log"
 	"sync"
 	"testing"
 	"time"
@@ -18,24 +17,13 @@ func taskWithParams(a int, b string) {
 	fmt.Println(a, b)
 }
 
-func TestSecond(t *testing.T) {
-	sched := NewScheduler()
-	job := sched.Every(1).Second()
-	testJobWithInterval(t, sched, job, 1)
-}
-
 func TestSeconds(t *testing.T) {
-	sched := NewScheduler()
-	job := sched.Every(2).Seconds()
-	testJobWithInterval(t, sched, job, 2)
-}
-
-func testJobWithInterval(t *testing.T, sched *Scheduler, job *Job, expectedTimeBetweenRuns int64) {
+	sched := NewScheduler(time.UTC)
 	jobDone := make(chan bool)
-	executionTimes := make([]int64, 0)
+	executionTimes := make([]int64, 0, 2)
 	numberOfIterations := 2
 
-	job.Do(func() {
+	sched.Every(2).Seconds().Do(func() {
 		executionTimes = append(executionTimes, time.Now().Unix())
 		if len(executionTimes) >= numberOfIterations {
 			jobDone <- true
@@ -50,36 +38,22 @@ func testJobWithInterval(t *testing.T, sched *Scheduler, job *Job, expectedTimeB
 
 	for i := 1; i < numberOfIterations; i++ {
 		durationBetweenExecutions := executionTimes[i] - executionTimes[i-1]
-		assert.Equal(t, expectedTimeBetweenRuns, durationBetweenExecutions, "Duration between tasks does not correspond to expectations")
+		assert.Equal(t, int64(2), durationBetweenExecutions, "Duration between tasks does not correspond to expectations")
 	}
 }
 
-func TestSafeExecution(t *testing.T) {
-	sched := NewScheduler()
+func TestExecution(t *testing.T) {
+	sched := NewScheduler(time.UTC)
 	success := false
 	sched.Every(1).Second().Do(func(mutableValue *bool) {
 		*mutableValue = !*mutableValue
 	}, &success)
-	sched.RunAll()
+	sched.RunAllWithDelay(1)
 	assert.Equal(t, true, success, "Task did not get called")
 }
 
-func TestSafeExecutionWithPanic(t *testing.T) {
-	defer func() {
-		if r := recover(); r != nil {
-			t.Errorf("Unexpected internal panic occurred: %s", r)
-		}
-	}()
-
-	sched := NewScheduler()
-	sched.Every(1).Second().DoSafely(func() {
-		log.Panic("I am panicking!")
-	})
-	sched.RunAll()
-}
-
 func TestScheduled(t *testing.T) {
-	n := NewScheduler()
+	n := NewScheduler(time.UTC)
 	n.Every(1).Second().Do(task)
 	if !n.Scheduled(task) {
 		t.Fatal("Task was scheduled but function couldn't find it")
@@ -88,12 +62,10 @@ func TestScheduled(t *testing.T) {
 
 // This is a basic test for the issue described here: https://github.com/jasonlvhit/gocron/issues/23
 func TestScheduler_Weekdays(t *testing.T) {
-	scheduler := NewScheduler()
+	scheduler := NewScheduler(time.UTC)
 
-	job1 := scheduler.Every(1).Monday().At("23:59")
-	job2 := scheduler.Every(1).Wednesday().At("23:59")
-	job1.Do(task)
-	job2.Do(task)
+	job1, _ := scheduler.Every(1).Monday().At("23:59").Do(task)
+	job2, _ := scheduler.Every(1).Wednesday().At("23:59").Do(task)
 	t.Logf("job1 scheduled for %s", job1.NextScheduledTime())
 	t.Logf("job2 scheduled for %s", job2.NextScheduledTime())
 	assert.NotEqual(t, job1.NextScheduledTime(), job2.NextScheduledTime(), "Two jobs scheduled at the same time on two different weekdays should never run at the same time")
@@ -102,69 +74,35 @@ func TestScheduler_Weekdays(t *testing.T) {
 // This ensures that if you schedule a job for today's weekday, but the time is already passed, it will be scheduled for
 // next week at the requested time.
 func TestScheduler_WeekdaysTodayAfter(t *testing.T) {
-	scheduler := NewScheduler()
-
-	now := time.Now()
-	timeToSchedule := time.Date(now.Year(), now.Month(), now.Day(), now.Hour(), now.Minute()-1, 0, 0, time.Local)
+	scheduler := NewScheduler(time.UTC)
+	now := time.Now().UTC()
+	timeToSchedule := time.Date(now.Year(), now.Month(), now.Day(), now.Hour(), now.Minute()-1, 0, 0, time.UTC)
 
-	job := callTodaysWeekday(scheduler.Every(1)).At(fmt.Sprintf("%02d:%02d", timeToSchedule.Hour(), timeToSchedule.Minute()))
-	job.Do(task)
+	runTime := fmt.Sprintf("%02d:%02d", timeToSchedule.Hour(), timeToSchedule.Minute())
+	job, _ := callTodaysWeekday(scheduler.Every(1), time.UTC).At(runTime).Do(task)
 	t.Logf("job is scheduled for %s", job.NextScheduledTime())
 	if job.NextScheduledTime().Weekday() != timeToSchedule.Weekday() {
 		t.Errorf("Job scheduled for current weekday for earlier time, should still be scheduled for current weekday (but next week)")
 	}
-	nextWeek := time.Date(now.Year(), now.Month(), now.Day()+7, now.Hour(), now.Minute()-1, 0, 0, time.Local)
+	nextWeek := time.Date(now.Year(), now.Month(), now.Day()+7, now.Hour(), now.Minute()-1, 0, 0, time.UTC)
 	if !job.NextScheduledTime().Equal(nextWeek) {
 		t.Errorf("Job should be scheduled for the correct time next week.\nGot %+v, expected %+v", job.NextScheduledTime(), nextWeek)
 	}
 }
 
-func TestScheduler_JobLocsSetProperly(t *testing.T) {
-	defaultScheduledJob := NewJob(10)
-	assert.Equal(t, defaultScheduledJob.loc, time.Local)
-	defaultScheduledJobFromScheduler := Every(10)
-	assert.Equal(t, defaultScheduledJobFromScheduler.loc, time.Local)
-
-	laLocation, err := time.LoadLocation("America/Los_Angeles")
-	if err != nil {
-		t.Fatalf("unable to load America/Los_Angeles time location")
-	}
-
-	ChangeLoc(laLocation)
-	modifiedGlobalLocJob := NewJob(10)
-	assert.Equal(t, modifiedGlobalLocJob.loc, laLocation)
-	modifiedGlobalLocJobFromScheduler := Every(10)
-	assert.Equal(t, modifiedGlobalLocJobFromScheduler.loc, laLocation)
-
-	chiLocation, err := time.LoadLocation("America/Chicago")
-	if err != nil {
-		t.Fatalf("unable to load America/Chicago time location")
-	}
-
-	scheduler := NewScheduler()
-	scheduler.ChangeLoc(chiLocation)
-	modifiedGlobalLocJobFromScheduler = scheduler.Every(10)
-	assert.Equal(t, modifiedGlobalLocJobFromScheduler.loc, chiLocation)
-
-	// other tests depend on global state :(
-	ChangeLoc(time.Local)
-}
-
 func TestScheduleNextRunLoc(t *testing.T) {
 	laLocation, err := time.LoadLocation("America/Los_Angeles")
 	if err != nil {
 		t.Fatalf("unable to load America/Los_Angeles time location")
 	}
 
-	sched := NewScheduler()
-	sched.ChangeLoc(time.UTC)
+	sched := NewScheduler(time.UTC)
 
-	job := sched.Every(1).Day().At("20:44")
+	job, _ := sched.Every(1).Day().At("20:44").Do(task)
 
 	// job just ran (this is 20:45 UTC), so next run should be tomorrow
 	today := time.Now().In(laLocation)
 	job.lastRun = time.Date(today.Year(), today.Month(), today.Day(), 13, 45, 0, 0, laLocation)
-	job.Do(task)
 
 	tomorrow := today.AddDate(0, 0, 1)
 	assert.Equal(t, 20, job.NextScheduledTime().UTC().Hour())
@@ -173,13 +111,10 @@ func TestScheduleNextRunLoc(t *testing.T) {
 }
 
 func TestScheduleNextRunFromNow(t *testing.T) {
-	now := time.Now()
+	sched := NewScheduler(time.UTC)
+	now := time.Now().UTC()
 
-	sched := NewScheduler()
-	sched.ChangeLoc(time.UTC)
-
-	job := sched.Every(1).Hour().From(NextTick())
-	job.Do(task)
+	job, _ := sched.Every(1).Hour().From(NextTick()).Do(task)
 
 	next := job.NextScheduledTime()
 	nextRounded := time.Date(next.Year(), next.Month(), next.Day(), next.Hour(), next.Minute(), next.Second(), 0, time.UTC)
@@ -192,13 +127,13 @@ func TestScheduleNextRunFromNow(t *testing.T) {
 // This is to ensure that if you schedule a job for today's weekday, and the time hasn't yet passed, the next run time
 // will be scheduled for today.
 func TestScheduler_WeekdaysTodayBefore(t *testing.T) {
-	scheduler := NewScheduler()
+	scheduler := NewScheduler(time.UTC)
 
-	now := time.Now()
-	timeToSchedule := time.Date(now.Year(), now.Month(), now.Day(), now.Hour(), now.Minute()+1, 0, 0, time.Local)
+	now := time.Now().UTC()
+	timeToSchedule := time.Date(now.Year(), now.Month(), now.Day(), now.Hour(), now.Minute()+1, 0, 0, time.UTC)
 
-	job := callTodaysWeekday(scheduler.Every(1)).At(fmt.Sprintf("%02d:%02d", timeToSchedule.Hour(), timeToSchedule.Minute()))
-	job.Do(task)
+	runTime := fmt.Sprintf("%02d:%02d", timeToSchedule.Hour(), timeToSchedule.Minute())
+	job, _ := callTodaysWeekday(scheduler.Every(1), time.UTC).At(runTime).Do(task)
 	t.Logf("job is scheduled for %s", job.NextScheduledTime())
 	if !job.NextScheduledTime().Equal(timeToSchedule) {
 		t.Error("Job should be run today, at the set time.")
@@ -291,48 +226,44 @@ func Test_formatTime(t *testing.T) {
 }
 
 // utility function for testing the weekday functions *on* the current weekday.
-func callTodaysWeekday(job *Job) *Job {
-	switch time.Now().Weekday() {
+func callTodaysWeekday(s *Scheduler, loc *time.Location) *Scheduler {
+	switch time.Now().In(loc).Weekday() {
 	case 0:
-		job.Sunday()
+		s.Sunday()
 	case 1:
-		job.Monday()
+		s.Monday()
 	case 2:
-		job.Tuesday()
+		s.Tuesday()
 	case 3:
-		job.Wednesday()
+		s.Wednesday()
 	case 4:
-		job.Thursday()
+		s.Thursday()
 	case 5:
-		job.Friday()
+		s.Friday()
 	case 6:
-		job.Saturday()
+		s.Saturday()
 	}
-	return job
+	return s
 }
 
 func TestScheduler_Remove(t *testing.T) {
-	scheduler := NewScheduler()
+	scheduler := NewScheduler(time.UTC)
 	scheduler.Every(1).Minute().Do(task)
 	scheduler.Every(1).Minute().Do(taskWithParams, 1, "hello")
 
 	assert.Equal(t, 2, scheduler.Len(), "Incorrect number of jobs")
 
 	scheduler.Remove(task)
-
 	assert.Equal(t, 1, scheduler.Len(), "Incorrect number of jobs after removing 1 job")
 
 	scheduler.Remove(task)
-
 	assert.Equal(t, 1, scheduler.Len(), "Incorrect number of jobs after removing non-existent job")
 }
 
 func TestScheduler_RemoveByRef(t *testing.T) {
-	scheduler := NewScheduler()
-	job1 := scheduler.Every(1).Minute()
-	job1.Do(task)
-	job2 := scheduler.Every(1).Minute()
-	job2.Do(taskWithParams, 1, "hello")
+	scheduler := NewScheduler(time.UTC)
+	job1, _ := scheduler.Every(1).Minute().Do(task)
+	job2, _ := scheduler.Every(1).Minute().Do(taskWithParams, 1, "hello")
 
 	assert.Equal(t, 2, scheduler.Len(), "Incorrect number of jobs")
 
@@ -341,22 +272,20 @@ func TestScheduler_RemoveByRef(t *testing.T) {
 }
 
 func TestTaskAt(t *testing.T) {
-	// Create new scheduler to have clean test env
-	s := NewScheduler()
+	s := NewScheduler(time.UTC)
 
 	// Schedule to run in next minute
-	now := time.Now()
+	now := time.Now().UTC()
+	dayJobDone := make(chan bool, 1)
+
 	// Schedule every day At
 	startAt := fmt.Sprintf("%02d:%02d", now.Hour(), now.Add(time.Minute).Minute())
-	dayJob := s.Every(1).Day().At(startAt)
-
-	dayJobDone := make(chan bool, 1)
-	dayJob.Do(func() {
+	dayJob, _ := s.Every(1).Day().At(startAt).Do(func() {
 		dayJobDone <- true
 	})
 
 	// Expected start time
-	expectedStartTime := time.Date(now.Year(), now.Month(), now.Day(), now.Hour(), now.Add(time.Minute).Minute(), 0, 0, loc)
+	expectedStartTime := time.Date(now.Year(), now.Month(), now.Day(), now.Hour(), now.Add(time.Minute).Minute(), 0, 0, time.UTC)
 	nextRun := dayJob.NextScheduledTime()
 	assert.Equal(t, expectedStartTime, nextRun)
 
@@ -373,22 +302,20 @@ func TestTaskAt(t *testing.T) {
 
 func TestTaskAtFuture(t *testing.T) {
 	// Create new scheduler to have clean test env
-	s := NewScheduler()
+	s := NewScheduler(time.UTC)
 
-	now := time.Now()
+	now := time.Now().UTC()
 
 	// Schedule to run in next minute
 	nextMinuteTime := now.Add(time.Duration(1 * time.Minute))
 	startAt := fmt.Sprintf("%02d:%02d", nextMinuteTime.Hour(), nextMinuteTime.Minute())
-	dayJob := s.Every(1).Day().At(startAt)
 	shouldBeFalse := false
-
-	dayJob.Do(func() {
+	dayJob, _ := s.Every(1).Day().At(startAt).Do(func() {
 		shouldBeFalse = true
 	})
 
 	// Check first run
-	expectedStartTime := time.Date(now.Year(), now.Month(), now.Day(), now.Hour(), now.Add(time.Minute).Minute(), 0, 0, loc)
+	expectedStartTime := time.Date(now.Year(), now.Month(), now.Day(), now.Hour(), now.Add(time.Minute).Minute(), 0, 0, time.UTC)
 	nextRun := dayJob.NextScheduledTime()
 	assert.Equal(t, expectedStartTime, nextRun)
 
@@ -401,120 +328,120 @@ func TestTaskAtFuture(t *testing.T) {
 }
 
 func TestDaily(t *testing.T) {
-	now := time.Now()
+	now := time.Now().UTC()
 
 	// Create new scheduler to have clean test env
-	s := NewScheduler()
+	s := NewScheduler(time.UTC)
 
 	// schedule next run 1 day
-	dayJob := s.Every(1).Day()
-	dayJob.scheduleNextRun()
+	dayJob, _ := s.Every(1).Day().Do(task)
+	s.scheduleNextRun(dayJob)
 	tomorrow := now.AddDate(0, 0, 1)
-	expectedTime := time.Date(tomorrow.Year(), tomorrow.Month(), tomorrow.Day(), 0, 0, 0, 0, loc)
+	expectedTime := time.Date(tomorrow.Year(), tomorrow.Month(), tomorrow.Day(), 0, 0, 0, 0, time.UTC)
 	assert.Equal(t, expectedTime, dayJob.nextRun)
 
 	// schedule next run 2 days
-	dayJob = s.Every(2).Days()
-	dayJob.scheduleNextRun()
+	dayJob, _ = s.Every(2).Days().Do(task)
+	s.scheduleNextRun(dayJob)
 	twoDaysFromNow := now.AddDate(0, 0, 2)
-	expectedTime = time.Date(twoDaysFromNow.Year(), twoDaysFromNow.Month(), twoDaysFromNow.Day(), 0, 0, 0, 0, loc)
+	expectedTime = time.Date(twoDaysFromNow.Year(), twoDaysFromNow.Month(), twoDaysFromNow.Day(), 0, 0, 0, 0, time.UTC)
 	assert.Equal(t, expectedTime, dayJob.nextRun)
 
 	// Job running longer than next schedule 1day 2 hours
-	dayJob = s.Every(1).Day()
-	twoHoursFromNow := now.Add(time.Duration(2 * time.Hour))
-	dayJob.lastRun = time.Date(twoHoursFromNow.Year(), twoHoursFromNow.Month(), twoHoursFromNow.Day(), twoHoursFromNow.Hour(), 0, 0, 0, loc)
-	dayJob.scheduleNextRun()
-	expectedTime = time.Date(now.Year(), now.Month(), now.AddDate(0, 0, 1).Day(), 0, 0, 0, 0, loc)
+	dayJob, _ = s.Every(1).Day().Do(task)
+	twoHoursFromNow := now.Add(2 * time.Hour)
+	dayJob.lastRun = time.Date(twoHoursFromNow.Year(), twoHoursFromNow.Month(), twoHoursFromNow.Day(), twoHoursFromNow.Hour(), 0, 0, 0, time.UTC)
+	s.scheduleNextRun(dayJob)
+	expectedTime = time.Date(now.Year(), now.Month(), now.AddDate(0, 0, 1).Day(), 0, 0, 0, 0, time.UTC)
 	assert.Equal(t, expectedTime, dayJob.nextRun)
 
 	// At() 2 hours before now
 	twoHoursBefore := now.Add(time.Duration(-2 * time.Hour))
 	startAt := fmt.Sprintf("%02d:%02d", twoHoursBefore.Hour(), twoHoursBefore.Minute())
-	dayJob = s.Every(1).Day().At(startAt)
-	dayJob.scheduleNextRun()
+	dayJob, _ = s.Every(1).Day().At(startAt).Do(task)
+	s.scheduleNextRun(dayJob)
 
 	expectedTime = time.Date(twoHoursBefore.Year(), twoHoursBefore.Month(),
 		twoHoursBefore.AddDate(0, 0, 1).Day(),
-		twoHoursBefore.Hour(), twoHoursBefore.Minute(), 0, 0, loc)
+		twoHoursBefore.Hour(), twoHoursBefore.Minute(), 0, 0, time.UTC)
 
 	assert.Equal(t, expectedTime, dayJob.nextRun)
 }
 
 func TestWeekdayAfterToday(t *testing.T) {
-	now := time.Now()
+	now := time.Now().UTC()
 
 	// Create new scheduler to have clean test env
-	s := NewScheduler()
+	s := NewScheduler(time.UTC)
 
 	// Schedule job at next week day
-	var weekJob *Job
 	switch now.Weekday() {
 	case time.Monday:
-		weekJob = s.Every(1).Tuesday()
+		s = s.Every(1).Tuesday()
 	case time.Tuesday:
-		weekJob = s.Every(1).Wednesday()
+		s = s.Every(1).Wednesday()
 	case time.Wednesday:
-		weekJob = s.Every(1).Thursday()
+		s = s.Every(1).Thursday()
 	case time.Thursday:
-		weekJob = s.Every(1).Friday()
+		s = s.Every(1).Friday()
 	case time.Friday:
-		weekJob = s.Every(1).Saturday()
+		s = s.Every(1).Saturday()
 	case time.Saturday:
-		weekJob = s.Every(1).Sunday()
+		s = s.Every(1).Sunday()
 	case time.Sunday:
-		weekJob = s.Every(1).Monday()
+		s = s.Every(1).Monday()
 	}
 
+	weekJob, _ := s.Do(task)
+
 	// First run
-	weekJob.scheduleNextRun()
-	exp := time.Date(now.Year(), now.Month(), now.Day()+1, 0, 0, 0, 0, loc)
+	s.scheduleNextRun(weekJob)
+	exp := time.Date(now.Year(), now.Month(), now.Day()+1, 0, 0, 0, 0, time.UTC)
 	assert.Equal(t, exp, weekJob.nextRun)
 
 	// Simulate job run 7 days before
 	weekJob.lastRun = weekJob.nextRun.AddDate(0, 0, -7)
 	// Next run
-	weekJob.scheduleNextRun()
-	exp = time.Date(now.Year(), now.Month(), now.Day()+1, 0, 0, 0, 0, loc)
+	s.scheduleNextRun(weekJob)
+	exp = time.Date(now.Year(), now.Month(), now.Day()+1, 0, 0, 0, 0, time.UTC)
 	assert.Equal(t, exp, weekJob.nextRun)
 }
 
 func TestWeekdayBeforeToday(t *testing.T) {
-	now := time.Now()
+	now := time.Now().In(time.UTC)
 
 	// Create new scheduler to have clean test env
-	s := NewScheduler()
+	s := NewScheduler(time.UTC)
 
 	// Schedule job at day before
-	var weekJob *Job
 	switch now.Weekday() {
 	case time.Monday:
-		weekJob = s.Every(1).Sunday()
+		s = s.Every(1).Sunday()
 	case time.Tuesday:
-		weekJob = s.Every(1).Monday()
+		s = s.Every(1).Monday()
 	case time.Wednesday:
-		weekJob = s.Every(1).Tuesday()
+		s = s.Every(1).Tuesday()
 	case time.Thursday:
-		weekJob = s.Every(1).Wednesday()
+		s = s.Every(1).Wednesday()
 	case time.Friday:
-		weekJob = s.Every(1).Thursday()
+		s = s.Every(1).Thursday()
 	case time.Saturday:
-		weekJob = s.Every(1).Friday()
+		s = s.Every(1).Friday()
 	case time.Sunday:
-		weekJob = s.Every(1).Saturday()
+		s = s.Every(1).Saturday()
 	}
-
-	weekJob.scheduleNextRun()
+	weekJob, _ := s.Do(task)
+	s.scheduleNextRun(weekJob)
 	sixDaysFromNow := now.AddDate(0, 0, 6)
 
-	exp := time.Date(sixDaysFromNow.Year(), sixDaysFromNow.Month(), sixDaysFromNow.Day(), 0, 0, 0, 0, loc)
+	exp := time.Date(sixDaysFromNow.Year(), sixDaysFromNow.Month(), sixDaysFromNow.Day(), 0, 0, 0, 0, time.UTC)
 	assert.Equal(t, exp, weekJob.nextRun)
 
 	// Simulate job run 7 days before
 	weekJob.lastRun = weekJob.nextRun.AddDate(0, 0, -7)
 	// Next run
-	weekJob.scheduleNextRun()
-	exp = time.Date(sixDaysFromNow.Year(), sixDaysFromNow.Month(), sixDaysFromNow.Day(), 0, 0, 0, 0, loc)
+	s.scheduleNextRun(weekJob)
+	exp = time.Date(sixDaysFromNow.Year(), sixDaysFromNow.Month(), sixDaysFromNow.Day(), 0, 0, 0, 0, time.UTC)
 	assert.Equal(t, exp, weekJob.nextRun)
 }
 
@@ -526,37 +453,38 @@ func TestWeekdayAt(t *testing.T) {
 	startAt := fmt.Sprintf("%02d:%02d", hour, minute)
 
 	// Create new scheduler to have clean test env
-	s := NewScheduler()
+	s := NewScheduler(time.UTC)
 
 	// Schedule job at next week day
-	var weekJob *Job
 	switch now.Weekday() {
 	case time.Monday:
-		weekJob = s.Every(1).Tuesday().At(startAt)
+		s = s.Every(1).Tuesday().At(startAt)
 	case time.Tuesday:
-		weekJob = s.Every(1).Wednesday().At(startAt)
+		s = s.Every(1).Wednesday().At(startAt)
 	case time.Wednesday:
-		weekJob = s.Every(1).Thursday().At(startAt)
+		s = s.Every(1).Thursday().At(startAt)
 	case time.Thursday:
-		weekJob = s.Every(1).Friday().At(startAt)
+		s = s.Every(1).Friday().At(startAt)
 	case time.Friday:
-		weekJob = s.Every(1).Saturday().At(startAt)
+		s = s.Every(1).Saturday().At(startAt)
 	case time.Saturday:
-		weekJob = s.Every(1).Sunday().At(startAt)
+		s = s.Every(1).Sunday().At(startAt)
 	case time.Sunday:
-		weekJob = s.Every(1).Monday().At(startAt)
+		s = s.Every(1).Monday().At(startAt)
 	}
 
+	weekJob, _ := s.Do(task)
+
 	// First run
-	weekJob.scheduleNextRun()
-	exp := time.Date(now.Year(), now.Month(), now.AddDate(0, 0, 1).Day(), hour, minute, 0, 0, loc)
+	s.scheduleNextRun(weekJob)
+	exp := time.Date(now.Year(), now.Month(), now.AddDate(0, 0, 1).Day(), hour, minute, 0, 0, time.UTC)
 	assert.Equal(t, exp, weekJob.nextRun)
 
 	// Simulate job run 7 days before
 	weekJob.lastRun = weekJob.nextRun.AddDate(0, 0, -7)
 	// Next run
-	weekJob.scheduleNextRun()
-	exp = time.Date(now.Year(), now.Month(), now.AddDate(0, 0, 1).Day(), hour, minute, 0, 0, loc)
+	s.scheduleNextRun(weekJob)
+	exp = time.Date(now.Year(), now.Month(), now.AddDate(0, 0, 1).Day(), hour, minute, 0, 0, time.UTC)
 	assert.Equal(t, exp, weekJob.nextRun)
 }
 
@@ -626,13 +554,13 @@ func TestLocker(t *testing.T) {
 	})
 
 	for i := 0; i < 5; i++ {
-		s1 := NewScheduler()
+		s1 := NewScheduler(time.UTC)
 		s1.Every(1).Seconds().Lock().Do(task, "A", i)
 
-		s2 := NewScheduler()
+		s2 := NewScheduler(time.UTC)
 		s2.Every(1).Seconds().Lock().Do(task, "B", i)
 
-		s3 := NewScheduler()
+		s3 := NewScheduler(time.UTC)
 		s3.Every(1).Seconds().Lock().Do(task, "C", i)
 
 		stop1 := s1.Start()
@@ -660,17 +588,17 @@ func TestLocker(t *testing.T) {
 }
 
 func TestGetAllJobs(t *testing.T) {
-	defaultScheduler = NewScheduler()
-	Every(1).Minute().Do(task)
-	Every(2).Minutes().Do(task)
-	Every(3).Minutes().Do(task)
-	Every(4).Minutes().Do(task)
-	js := Jobs()
+	s := NewScheduler(time.UTC)
+	s.Every(1).Minute().Do(task)
+	s.Every(2).Minutes().Do(task)
+	s.Every(3).Minutes().Do(task)
+	s.Every(4).Minutes().Do(task)
+	js := s.Jobs()
 	assert.Len(t, js, 4)
 }
 
 func TestTags(t *testing.T) {
-	j := Every(1).Minute()
+	j, _ := NewScheduler(time.UTC).Every(1).Minute().Do(task)
 	j.Tag("some")
 	j.Tag("tag")
 	j.Tag("more")
@@ -683,11 +611,11 @@ func TestTags(t *testing.T) {
 }
 
 func TestGetAt(t *testing.T) {
-	j := Every(1).Minute().At("10:30")
+	j, _ := NewScheduler(time.UTC).Every(1).Minute().At("10:30").Do(task)
 	assert.Equal(t, "10:30", j.GetAt())
 }
 
 func TestGetWeekday(t *testing.T) {
-	j := Every(1).Weekday(time.Wednesday)
+	j, _ := NewScheduler(time.UTC).Every(1).Weekday(time.Wednesday).Do(task)
 	assert.Equal(t, time.Wednesday, j.GetWeekday())
 }