Ticker should not execute if already running - go

I have some function that has to run periodically. I have used a ticker for this. But if the ticker is already running, and the time interval passes again, it should not execute again.
package main
import (
"fmt"
"time"
)
func main() {
ticker := time.NewTicker(3*time.Second)
flag := 0
defer ticker.Stop()
for {
select {
case t := <-ticker.C:
flag = flag + 1
if (flag % 2 ==0 ) {
time.Sleep(time.Second*4)
}
fmt.Println("Current time: ", t)
}
}
}
https://play.golang.org/p/2xV2MYInn4I
In the playground, the ticker prints every 3 seconds, but every even turn of the ticker the job takes more time than the interval. I expect it to not run then and drop those ticks.
How do I do this?

sleeping inside the same goroutine merely delays execution. ticker meanwhile runs in a separate goroutine. So even if you used a global variable to maintain an execution state - it will not give you your desired result with sleep. However migrating the whole "sleeping" in a separate goroutine yields:
package main
import (
"fmt"
"time"
)
type Tick struct {
ticker *time.Ticker
executing bool
}
func somethingYouWantToDo(tick *Tick, flag *int, t time.Time) {
if tick.executing {
return
}
tick.executing = true
*flag = *flag + 1
if (*flag % 2 ==0 ) {
time.Sleep(time.Second*4)
}
fmt.Println("Current time: ", t)
tick.executing = false
}
func main() {
tick := &Tick{
ticker: time.NewTicker(3*time.Second),
}
flag := 0
defer tick.ticker.Stop()
for {
select {
case t := <-tick.ticker.C:
go somethingYouWantToDo(tick, &flag, t)
}
}
}
// output
// Current time: 2009-11-10 23:00:03 +0000 UTC m=+3.000000001
// Current time: 2009-11-10 23:00:06 +0000 UTC m=+6.000000001
// Current time: 2009-11-10 23:00:12 +0000 UTC m=+12.000000001
// Current time: 2009-11-10 23:00:15 +0000 UTC m=+15.000000001
// Current time: 2009-11-10 23:00:21 +0000 UTC m=+21.000000001
// Current time: 2009-11-10 23:00:24 +0000 UTC m=+24.000000001
// Current time: 2009-11-10 23:00:30 +0000 UTC m=+30.000000001
// Current time: 2009-11-10 23:00:33 +0000 UTC m=+33.000000001
// Current time: 2009-11-10 23:00:39 +0000 UTC m=+39.000000001
// Current time: 2009-11-10 23:00:42 +0000 UTC m=+42.000000001
// Current time: 2009-11-10 23:00:48 +0000 UTC m=+48.000000001
// Current time: 2009-11-10 23:00:51 +0000 UTC m=+51.000000001
// Current time: 2009-11-10 23:00:57 +0000 UTC m=+57.000000001
// Current time: 2009-11-10 23:01:00 +0000 UTC m=+60.000000001
// Current time: 2009-11-10 23:01:06 +0000 UTC m=+66.000000001
// Current time: 2009-11-10 23:01:09 +0000 UTC m=+69.000000001
Try it on the playground

The ticker channel is buffered, which is why you may see multiple triggers right one after the other. You can prevent that by simply transfering the ticker's values to an unbuffered channel (note also that the time.Time value received from the ticker is not the current time but the time of the last tick):
package main
import (
"fmt"
"time"
)
func main() {
c := make(chan time.Time) // unbuffered
ticker := time.NewTicker(3 * time.Second)
defer ticker.Stop()
go func() {
for t := range ticker.C {
select {
case c <- t:
default:
}
}
}()
for flag := 0; flag < 8; flag++ {
<-c
if flag%2 == 0 {
time.Sleep(time.Second * 4)
}
fmt.Println("Current time: ", time.Now())
}
}
// Output:
// Current time: 2020-02-19 12:21:57.095433032 +0100 CET m=+3.000213350
// Current time: 2020-02-19 12:22:04.095585208 +0100 CET m=+10.000365520
// Current time: 2020-02-19 12:22:06.095363327 +0100 CET m=+12.000143680
// Current time: 2020-02-19 12:22:13.095605268 +0100 CET m=+19.000385598
// Current time: 2020-02-19 12:22:15.095371885 +0100 CET m=+21.000152174
// Current time: 2020-02-19 12:22:22.095537562 +0100 CET m=+28.000317857
// Current time: 2020-02-19 12:22:24.095431317 +0100 CET m=+30.000211625
// Current time: 2020-02-19 12:22:31.095524308 +0100 CET m=+37.000304595
Try it on the playground: https://play.golang.org/p/jDe5uJiRVe2

Related

go time.Now() not giving proper time

I am running this simple go program and expecting to get different time (a difference of 3-second period) for starting and shutdown logs as I have given sleep of 3 seconds, but I am getting same time.
package main
import (
"fmt"
"time"
)
const (
logInfo = "INFO"
logWarning = "WARNING"
logError = "ERROR"
)
type logEntry struct {
time time.Time
severity string
message string
}
var myLogChannel = make(chan logEntry, 50)
func main() {
go logger()
myLogChannel <- logEntry{ time.Now(), logInfo, "App is starting" }
time.Sleep(3 * time.Second)
fmt.Println("Time is :", time.Now().Format("2006-01-02 15:04:06"))
myLogChannel <- logEntry{ time.Now(), logInfo, "App is shuting down" }
time.Sleep(2 * time.Second)
}
func logger() {
for entry := range myLogChannel {
fmt.Printf("%v : [%v]: %v\n", entry.time.Format("2006-01-02 15:04:06"),entry.severity, entry.message)
}
}
Output:
On my system (running under cygwin env):
2020-10-12 11:03:20 : [INFO]: App is starting
Time is : 2020-10-12 11:03:20
2020-10-12 11:03:20 : [INFO]: App is shuting down
On playground:
2009-11-10 23:00:09 : [INFO]: App is starting
Time is : 2009-11-10 23:00:09
2009-11-10 23:00:09 : [INFO]: App is shuting down
Your time format is wrong. 06 is the last two digits of the year, so it is printing 20 for 2020. Use the format: 2006-01-02 15:04:05

For loop while time.Now() is reached

Is it possible in Golang to increment a date in a for loop by a given date variable till it reached the current date/ time.Now()
// Start date
t, _ := time.Parse(time.RFC3339, "2018-07-19T12:25:10.8584224+02:00")
// Current date
ct := time.Now()
for d := t; d.Day() == ct.Day(); d = d.AddDate(0, 0, 1) {
// Print all days between start date and current date
fmt.Println(d)
}
I expect that variable d prints out all dates (with time etc.) till it reached the current date
according to godoc: https://golang.org/pkg/time/#Time.Day
func (t Time) Day() int
Day returns the day of the month specified by t.
So comparing d.Day() and ct.Day() is not the right approaches. What if today is "2019-01-01",and you start time is "2018-12-23"?
The right way to compare two time.Time is https://golang.org/pkg/time/#Time.After
func (t Time) After(u Time) bool
func (t Time) Before(u Time) bool
After reports whether the time instant t is after u.
Before reports whether the time instant t is before u.
So #Alex Pliutau's solution is more in common use. But need more careful with today.
package main
import (
"fmt"
"time"
)
func main() {
t, _ := time.Parse(time.RFC3339, "2009-11-02T12:25:10.8584224+02:00")
// truncate to 0:0:0
t = t.Truncate(24 * time.Hour)
fmt.Println("start time is:", t)
// Current date truncate to 0:0:0
ct := time.Now().Truncate(24 * time.Hour)
fmt.Println("now is:", ct)
fmt.Println("---------------")
// for t.Before(ct) { //if you don't want to print the date of today
for !t.After(ct) {
// Print all days between start date and current date
fmt.Println(t.Format("2006-01-02 15:04:05"))
t = t.AddDate(0, 0, 1)
}
}
Output:
start time is: 2009-11-02 02:00:00 +0200 +0200
now is: 2009-11-10 00:00:00 +0000 UTC
---------------
2009-11-02 02:00:00
2009-11-03 02:00:00
2009-11-04 02:00:00
2009-11-05 02:00:00
2009-11-06 02:00:00
2009-11-07 02:00:00
2009-11-08 02:00:00
2009-11-09 02:00:00
2009-11-10 02:00:00
https://play.golang.org/p/iMr7M5W9K4N
get the loop condition right and..
package main
import (
"fmt"
"time"
)
func main() {
fmt.Println("Hello, playground")
t, _ := time.Parse(time.RFC3339, "2018-07-19T12:25:10.8584224+02:00")
// Current date
ct := time.Now()
for d := t; d.Day() >= ct.Day(); d = d.AddDate(0, 0, 1) {
// Print all days between start date and current date
fmt.Println(d)
}
}
Hello, playground
2018-07-19 12:25:10.8584224 +0200 +0200
2018-07-20 12:25:10.8584224 +0200 +0200
2018-07-21 12:25:10.8584224 +0200 +0200
2018-07-22 12:25:10.8584224 +0200 +0200
2018-07-23 12:25:10.8584224 +0200 +0200
2018-07-24 12:25:10.8584224 +0200 +0200
2018-07-25 12:25:10.8584224 +0200 +0200
2018-07-26 12:25:10.8584224 +0200 +0200
2018-07-27 12:25:10.8584224 +0200 +0200
2018-07-28 12:25:10.8584224 +0200 +0200
2018-07-29 12:25:10.8584224 +0200 +0200
2018-07-30 12:25:10.8584224 +0200 +0200
2018-07-31 12:25:10.8584224 +0200 +0200
https://play.golang.org/p/yRBTUZKfseG
Based on your comments, you need to actually tell it to Format the date to something of value:
package main
import (
"fmt"
"log"
"time"
)
func main() {
start, err := time.Parse("2006-1-2", "2018-1-1")
if err != nil {
log.Fatal(err)
}
for d := start; d.Month() == start.Month(); d = d.AddDate(0, 0, 1) {
fmt.Println(d.Format("2006-1-2"))
}
}
Here's a simpler version of your code (I used a custom time format, cause I didn't wanna edit the RFC syntax, but ultimately it's the same thing) = I'm also iterating Month for brevity.
package main
import (
"fmt"
"time"
)
func main() {
t, _ := time.Parse(time.RFC3339, "2018-07-19T12:25:10.8584224+02:00")
ct := time.Now()
for t.Before(ct) {
fmt.Println(t)
t.AddDate(0, 0, 1)
}
}

Local time loses 52 seconds after marshal/unmarshal

I parse a time in Local, marshal it to JSON, un-marshal it and the times no longer match.
timeA, _ := time.ParseInLocation("15:04", "8:00", time.Local)
jBytes, _ := json.Marshal(timeA)
var timeB time.Time
json.Unmarshal(jBytes, &timeB)
fmt.Printf("Time A: %+v, Time B: %+v\n", timeA, timeB)
fmt.Printf("Time A: %+v, Time B: %+v\n", timeA.Local(), timeB.Local())
fmt.Printf("Diff: %s\n", timeA.Sub(timeB))
fmt.Printf("Marshaled: %s", string(jBytes))
Time A: 0000-01-01 08:00:00 -0733 LMT, Time B: 0000-01-01 08:00:00 -0733 -0733
Time A: 0000-01-01 08:00:00 -0733 LMT, Time B: 0000-01-01 07:59:08 -0733 LMT
Diff: 52s
Marshaled: "0000-01-01T08:00:00-07:33"
This is running on linux with Edmonton/Mountain as my local time so I guess it's not recognizing the location and showing offset twice -733 -733. When I call local, the parsed one consistently loses 52 seconds for some reason.
I'd expect the times to match. Is my clock 52 seconds off a remote one it's referencing or something?
Prior to September 1, 1906, your time zone difference was UTC-7:33:52. json.Unmarshal is just using the 7:33 in the marshaled text for the offset, instead of the correct value of 7:33:52, so the time.Time value it calculates is off by 52 seconds. But your time.Local implementation seems to be getting it right (to the extent we can describe backdating time zone differences to year 1 as "right") and subtracting the full 7:33:52 from the time.Time value, resulting in the difference you're seeing.
If you output:
fmt.Printf("Time A: %+v, Time B: %+v\n", timeA.UTC(), timeB.UTC())
with your current code you should see that the UTC time for timeB is getting set to 15:33:00 after unmarshaling, whereas the UTC time for timeA is getting set to 15:33:52. I suspect if you include a year after 1906 in your time string you'll see this 52 seconds difference disappear.
For example:
package main
import (
"encoding/json"
"fmt"
"log"
"time"
)
func main() {
zone, err := time.LoadLocation("America/Edmonton")
if err != nil {
log.Fatalf("%v", err)
}
for _, timestring := range []string{
"01 02 1905 8:00",
"01 02 1907 8:00",
} {
timeA, err := time.ParseInLocation("01 02 2006 15:04", timestring, zone)
if err != nil {
log.Fatalf("%v", err)
}
jBytes, _ := json.Marshal(timeA)
var timeB time.Time
json.Unmarshal(jBytes, &timeB)
fmt.Printf("Time string: %s\n", timestring)
fmt.Printf("Time A: %+v, Time B: %+v\n", timeA, timeB)
fmt.Printf("Time A: %+v, Time B: %+v\n", timeA.UTC(), timeB.UTC())
fmt.Printf("Time A: %+v, Time B: %+v\n", timeA.In(zone), timeB.In(zone))
fmt.Printf("Diff: %s\n", timeA.Sub(timeB))
fmt.Printf("Marshaled: %s\n", string(jBytes))
}
}
outputs:
paul#mac:got$ ./got
Time string: 01 02 1905 8:00
Time A: 1905-01-02 08:00:00 -0733 LMT, Time B: 1905-01-02 08:00:00 -0733 -0733
Time A: 1905-01-02 15:33:52 +0000 UTC, Time B: 1905-01-02 15:33:00 +0000 UTC
Time A: 1905-01-02 08:00:00 -0733 LMT, Time B: 1905-01-02 07:59:08 -0733 LMT
Diff: 52s
Marshaled: "1905-01-02T08:00:00-07:33"
Time string: 01 02 1907 8:00
Time A: 1907-01-02 08:00:00 -0700 MST, Time B: 1907-01-02 08:00:00 -0700 -0700
Time A: 1907-01-02 15:00:00 +0000 UTC, Time B: 1907-01-02 15:00:00 +0000 UTC
Time A: 1907-01-02 08:00:00 -0700 MST, Time B: 1907-01-02 08:00:00 -0700 MST
Diff: 0s
Marshaled: "1907-01-02T08:00:00-07:00"
paul#mac:got$
showing that the 52 second difference is there for 1905, but not for 1907 after the time zone difference changed to a straight UTC-7:00:00.
Short answer: marshaling to and unmarshaling from json by default appears unable to correctly handle seconds in time zone offsets, because no seconds appear in the offset in the marshaled string and this is the only time zone information json.Unmarshal has available to it.
For sure there is no referencing of clocks, remote or otherwise, in any of this code - it's just manipulating values.
You are defaulting to pseudo-date 0000-01-01 when local time was likely based on the position of the Sun at midday.
Simply parse the time-of-day. For example,
package main
import (
"encoding/json"
"fmt"
"time"
)
func main() {
timeA, err := time.Parse("15:04", "8:00")
fmt.Println(timeA, err)
jBytes, _ := json.Marshal(timeA)
var timeB time.Time
json.Unmarshal(jBytes, &timeB)
fmt.Printf("Time A: %+v, Time B: %+v\n", timeA, timeB)
fmt.Printf("Diff: %s\n", timeA.Sub(timeB))
fmt.Printf("Marshaled: %s\n", string(jBytes))
}
Output:
0000-01-01 08:00:00 +0000 UTC <nil>
Time A: 0000-01-01 08:00:00 +0000 UTC, Time B: 0000-01-01 08:00:00 +0000 UTC
Diff: 0s
Marshaled: "0000-01-01T08:00:00Z"

About the accuracy of the time.Timer

package main
import (
"time"
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
wg.Add(1)
TestTicker(wg)
wg.Wait()
}
func TestTicker(wg sync.WaitGroup) {
calDuration := func(duration time.Duration) time.Duration {
now := time.Now()
return now.Truncate(duration).Add(duration).Sub(now)
}
go func(){
t := time.NewTimer(calDuration(time.Minute))
for {
<-t.C
fmt.Println(time.Now())
t.Reset(calDuration(time.Minute))
}
wg.Done()
}()
}
It sometimes happens to tick twice a minute as the duration may shrink. It's really strange. Could somebody help me. Thanks
I simply use waitgroup to hold the main function while calling TestTicker.
I'm running the test code on my MacOS
2018-07-19 14:36:00.003887996 +0800 CST m=+24.916092657
2018-07-19 14:37:00.002985076 +0800 CST m=+84.917119245
2018-07-19 14:38:00.001214551 +0800 CST m=+144.917278207
2018-07-19 14:39:00.000418561 +0800 CST m=+204.918411736
2018-07-19 14:39:59.999490194 +0800 CST m=+264.919412884
2018-07-19 14:40:00.000167519 +0800 CST m=+264.920090231
2018-07-19 14:40:59.99914446 +0800 CST m=+324.920996684
2018-07-19 14:41:00.000247228 +0800 CST m=+324.922099488
The timer accuracy can vary depending on your OS, hardware and CPU load. Virtual Machines seem particularly bad at providing accurate timers (see https://github.com/golang/go/issues/14410). Unfortunately, you do not mention in what environment you're running this code.
If you can live with the inaccuracies, and still need your code do do something at about a full minute, your code breaks because when the interval is too short (14:39:59.999490194 is only 500µs short of 14:40), calDuration will make it wait that few microseconds until the next full minute. In order to fix this you need to use Duration.Round instead of Duration.Truncate.
Also do not forget that t.C returns the time at which the timer fired, so you need to use this value in your call to calDuration (this also saves you a costly syscalls).
func TestTicker(wg *sync.WaitGroup) {
calDuration := func(now time.Time, duration time.Duration) time.Duration {
return now.Round(duration).Add(duration).Sub(now)
}
go func(){
t := time.NewTimer(calDuration(time.Now(), time.Minute))
for {
now := <-t.C
fmt.Println(now)
t.Reset(calDuration(now, time.Minute))
}
wg.Done()
}()
}
Another approach is to use time.Ticker from the standard library and issue an appropriate sleep before starting the ticker so that it ticks on a full minute:
func TestTicker(wg *sync.WaitGroup) {
go func(interval time.Duration) {
// wait until next time interval
now := time.Now()
time.Sleep(now.Truncate(interval).Add(interval).Sub(now))
// get time of first beat
now = time.Now()
// start the ticker
t := time.NewTicker(interval)
for {
fmt.Println(now)
now = <-t.C
}
wg.Done()
}(time.Minute)
}

Use timestamp from Mysql in humanize.Time() package

I have a mariaDB database with a timestamp field. I want the values from that field being parsed to time.Time() values. This is possible by adding the ?parseTime=true to the connection string. After fetching a row, I want to use the value (which is time.Time) with humanize.Time(). Unfortunately values within the past 60 minutes are converted by humanize.Time() as 1 hour from now. When I put directly a time.Time() value into humanize.Time(), it gives me x seconds ago.
So I don't know what I'm doing wrong here. I think I need to convert 2017-04-23 14:00:16 +0000 UTC to 2017-04-23 14:00:16.370758048 +0200 CEST, but how?
package main
import (
"fmt"
"log"
"time"
humanize "github.com/dustin/go-humanize"
)
// value from database: 2017-04-23 14:00:16 +0000 UTC
// typical time.Now() value: 2017-04-23 14:00:16.370758048 +0200 CEST
func main() {
layout := "2006-01-02 15:04:05 -0700 MST"
beforeParsing := "2017-04-23 14:00:16 +0000 UTC"
t, err := time.Parse(layout, beforeParsing)
if err != nil {
log.Fatal(err)
}
fmt.Println(t)
fmt.Println(humanize.Time(t))
}

Resources