I'm trying to get amount of days between two dates in Go but as I'm getting the difference in time (hours) and then divided by the amount of time per day, I have an issue.
Problem: if dates are different (7 of May and 8 of May) but the time between then is lower than 24h, my code counts as there are no days in between.
What I want: count real days in between.
// Days
firstDay := firstDate.Unix()
lastDay := lastDate.Unix()
fmt.Println("firstDay: ", firstDay)
fmt.Println("lastDay: ", lastDay)
if firstDay > lastDay {
fmt.Println("IS TO SMALL")
return
}
// businessDays =
businessDays := (lastDay - firstDay) / 86400
fmt.Println("businessDays: ", businessDays)
Thank you very much.
Use Duration.Hours() to get hours
Use math.Ceil() to find calendar days
Use Time.Truncate() to truncate hour and minute, only save day
The source code:
package main
import (
"fmt"
"math"
"time"
)
func main() {
d1, err := time.Parse("200601021504", "201801020001")
if err != nil {
panic(err)
}
d2, err := time.Parse("200601021504", "201801020002")
if err != nil {
panic(err)
}
newD1 := d1.Truncate(time.Hour * 24)
newD2 := d2.Truncate(time.Hour * 24)
fmt.Printf("days: %v\n", math.Ceil(newD2.Sub(newD1).Hours()/24))
}
There are many things to worry about. For example, due to daylight saving time (DST), not all days are 24 hours. What if the times are in different time zones? And so on.
Here's a solution that addresses these issues.
package main
import (
"fmt"
"time"
)
// CalendarDays returns the calendar difference between times (t2 - t1) as days.
func CalendarDays(t2, t1 time.Time) int {
y, m, d := t2.Date()
u2 := time.Date(y, m, d, 0, 0, 0, 0, time.UTC)
y, m, d = t1.In(t2.Location()).Date()
u1 := time.Date(y, m, d, 0, 0, 0, 0, time.UTC)
days := u2.Sub(u1) / (24 * time.Hour)
return int(days)
}
func main() {
first := time.Now().Round(0)
end := first.Add(48 * time.Hour)
for last := first; last.Before(end); last = last.Add(6 * time.Hour) {
fmt.Println("Days:", CalendarDays(last, first), "Last:", last, "First:", first)
}
}
Playground: https://play.golang.org/p/wKwQzgfKa8f
Output:
Days: 0 Last: 2018-02-05 17:38:15.623292254 -0500 EST First: 2018-02-05 17:38:15.623292254 -0500 EST
Days: 0 Last: 2018-02-05 23:38:15.623292254 -0500 EST First: 2018-02-05 17:38:15.623292254 -0500 EST
Days: 1 Last: 2018-02-06 05:38:15.623292254 -0500 EST First: 2018-02-05 17:38:15.623292254 -0500 EST
Days: 1 Last: 2018-02-06 11:38:15.623292254 -0500 EST First: 2018-02-05 17:38:15.623292254 -0500 EST
Days: 1 Last: 2018-02-06 17:38:15.623292254 -0500 EST First: 2018-02-05 17:38:15.623292254 -0500 EST
Days: 1 Last: 2018-02-06 23:38:15.623292254 -0500 EST First: 2018-02-05 17:38:15.623292254 -0500 EST
Days: 2 Last: 2018-02-07 05:38:15.623292254 -0500 EST First: 2018-02-05 17:38:15.623292254 -0500 EST
Days: 2 Last: 2018-02-07 11:38:15.623292254 -0500 EST First: 2018-02-05 17:38:15.623292254 -0500 EST
As performance is now an issue (which I doubt), I wrote some benchmarks with approaches I come around:
func TrimToDate(t time.Time) time.Time {
y, m, d := t.Date()
return time.Date(y, m, d, 0, 0, 0, 0, time.UTC)
}
func CountTrim(t1, t2 time.Time) int {
return int((t2.Unix() - TrimToDate(t1.Unix())) / 86400)
}
func CountUnixAdd(t1, t2 time.Time) int {
Days := (t1.Unix() - t2.Unix()) / 86400
if t1.Add(time.Duration(Days)*24*time.Hour).Day() != t2.Day() {
Days++
}
return int(Days)
}
func CountDivMul(t1, t2 time.Time) int {
d1 := t1.Unix() / 86400 * 86400
return int((t2.Unix() - d1) / 86400)
}
Where CountTrim use the time.Time.Date method to trim the hours and minutes and so on. Note that only once TrimToDate needs to be called, as the remaining would be discarded by the integer division.
CountUnixAdd is a plain and simple way: Test if it is the same date, if not, adds one.
CountDivMul is almost the same idea of trimming to day, but use a more hack-y way: using integer division to get rid of the remainnings and then multiply back.
The benchmark result on my laptop:
goos: windows
goarch: amd64
pkg: test/stackoverflow/dateprb
BenchmarkCountTrim-8 20000000 62.7 ns/op
BenchmarkCountDivMul-8 1000000000 2.12 ns/op
BenchmarkCountUnixAdd-8 20000000 77.8 ns/op
PASS
ok test/stackoverflow/dateprb 5.367s
Unsurprisingly, the hack-y way is way faster than the other while the two usual way is almost the same.
Full code (of benchmark and of functions) here
Related
I have a custom weekend time which is from Friday 10pm UTC to Sunday 10:05pm UTC. I have a current timestamp in UTC and just wanted to check if the time falls in weekend time. Any tricks will be much appreciated. I tried using weekdays() and time but was not able to achieve the desired results.
check if the time falls in weekend time which is from Friday 10pm UTC to Sunday 10:05pm UTC.
Use the Go time package.
For example,
package main
import (
"fmt"
"time"
)
// A weekend is Friday 10pm UTC to Sunday 10:05pm UTC
func isWeekend(t time.Time) bool {
t = t.UTC()
switch t.Weekday() {
case time.Friday:
h, _, _ := t.Clock()
if h >= 12+10 {
return true
}
case time.Saturday:
return true
case time.Sunday:
h, m, _ := t.Clock()
if h < 12+10 {
return true
}
if h == 12+10 && m <= 5 {
return true
}
}
return false
}
func main() {
t := time.Date(2019, 11, 22, 12+10, 5, 0, 0, time.UTC)
fmt.Println(t)
w := isWeekend(t)
fmt.Println(w)
}
Playground: https://play.golang.org/p/TZBoNcwH-qU
Output:
2019-11-22 22:05:00 +0000 UTC
true
I want to create a time.Time for an exact point in time the following day (tomorrow). For now I would like to set the hour and minute.
This is the code I use at the moment:
now := time.Now()
tomorrow := time.Date(now.Year(), now.Month(), now.Day(), 15, 0, 0, 0, time.UTC).AddDate(0,0,1)
This will create a Date for today with the exact time (hour and minute) I am looking for and then adds one day to that Date. This works fine.
Example:
Imagine time.Now() is 2009-11-10 23:00:00 +0000 UTC.
The result of the following code would be: 2009-11-10 15:00:00 +0000 UTC
tomorrow := time.Date(now.Year(), now.Month(), now.Day(), 15, 0, 0, 0, time.UTC)
To this date I add one day using AddDate(0, 0, 1). The result is then the desired time the next day: 2009-11-11 15:00:00 +0000 UTC.
See: https://play.golang.org/p/OKR9V1HN50x
Question:
Is there a shorter way to write this code?
Package time
import "time"
The month, day, hour, min, sec, and nsec values may be outside their
usual ranges and will be normalized during the conversion. For
example, October 32 converts to November 1.
This is more efficient. It minimizes calls to package time functions and methods.
package main
import (
"fmt"
"time"
)
func main() {
now := time.Now()
fmt.Println(now.Round(0))
yyyy, mm, dd := now.Date()
tomorrow := time.Date(yyyy, mm, dd+1, 15, 0, 0, 0, now.Location())
fmt.Println(tomorrow)
}
Output:
2019-06-21 16:23:06.525478162 -0400 EDT
2019-06-22 15:00:00 -0400 EDT
Some benchmarks:
BenchmarkNow-8 31197811 36.6 ns/op
BenchmarkTomorrowPeterSO-8 29852671 38.4 ns/op
BenchmarkTomorrowJens-8 9523422 124 ns/op
bench_test.go:
package main
import (
"testing"
"time"
)
func BenchmarkNow(b *testing.B) {
for N := 0; N < b.N; N++ {
now := time.Now()
_ = now
}
}
var now = time.Now()
func BenchmarkTomorrowPeterSO(b *testing.B) {
for N := 0; N < b.N; N++ {
// now := time.Now()
yyyy, mm, dd := now.Date()
tomorrow := time.Date(yyyy, mm, dd+1, 15, 0, 0, 0, now.Location())
_ = tomorrow
}
}
func BenchmarkTomorrowJens(b *testing.B) {
for N := 0; N < b.N; N++ {
// now := time.Now()
tomorrow := time.Date(now.Year(), now.Month(), now.Day(), 15, 0, 0, 0, now.Location()).AddDate(0, 0, 1)
_ = tomorrow
}
}
Here below is my code to get the last complete quarter:
package main
import (
"fmt"
"time"
)
func main() {
layout := "2006-01-02T15:04:05.000Z"
str := "2017-11-30T12:00:00.000Z"
now, _ := time.Parse(layout, str)
endDate := now.AddDate(0, 0, 0-now.Day())
startDate := endDate.AddDate(0, -3, 0) // startDate is wrong: 2017-07-31
// the following statement is needed to fix startDate
if endDate.Month()-startDate.Month() == 3 {
startDate = startDate.AddDate(0, 0, 1) // now startDate is correct: 2017-08-01
}
fmt.Printf("Start date: %v\n", startDate.Format("2006-01-02"))
fmt.Printf("End date: %v\n", endDate.Format("2006-01-02"))
}
playground
Is there a better way to get the correct start date?
For instance, the last startDate = startDate.AddDate(0, 0, 1) statement has to be omitted if I want to get the last semester:
endDate := now.AddDate(0, 0, 0-now.Day())
startDate := endDate.AddDate(0, -6, 0) // startDate is correct: 2017-05-01
Why is there this difference?
Package time
import "time"
func Date
func Date(year int, month Month, day, hour, min, sec, nsec int, loc *Location) Time
Date returns the Time corresponding to
yyyy-mm-dd hh:mm:ss + nsec nanoseconds
in the appropriate zone for that time in the given location.
The month, day, hour, min, sec, and nsec values may be outside their
usual ranges and will be normalized during the conversion. For
example, October 32 converts to November 1.
For example, using normalization to get the last complete period (for example, quarter or semester):
package main
import (
"fmt"
"time"
)
func lastPeriod(t time.Time, period time.Month) (start, end time.Time) {
y, m, _ := t.Date()
loc := t.Location()
start = time.Date(y, m-period, 1, 0, 0, 0, 0, loc)
end = time.Date(y, m, 1, 0, 0, 0, -1, loc)
return start, end
}
func main() {
layout := "2006-01-02T15:04:05.000Z"
str := "2017-11-30T12:00:00.000Z"
now, err := time.Parse(layout, str)
if err != nil {
fmt.Println(err)
return
}
const (
quarter = 3
semester = 6
)
fmt.Println("Quarter:")
start, end := lastPeriod(now, quarter)
fmt.Printf("Base date: %v\n", now.Format("2006-01-02"))
fmt.Printf("Start date: %v\n", start.Format("2006-01-02"))
fmt.Printf("End date: %v\n", end.Format("2006-01-02"))
fmt.Println("Semester:")
start, end = lastPeriod(now, semester)
fmt.Printf("Base date: %v\n", now.Format("2006-01-02"))
fmt.Printf("Start date: %v\n", start.Format("2006-01-02"))
fmt.Printf("End date: %v\n", end.Format("2006-01-02"))
}
Playground: https://play.golang.org/p/0t4exjVgr-
Output:
Quarter:
Base date: 2017-11-30
Start date: 2017-08-01
End date: 2017-10-31
Semester:
Base date: 2017-11-30
Start date: 2017-05-01
End date: 2017-10-31
Failing to do something fairly simple and can't find an answer
nor from uncle Google nor from here
I'm trying to get one full month in seconds (an int)
Something more elegant then this:
s := 3600 * 24 * 30
also tried :
m := time.Hour * 24 * 30
but that returns type 'time.Duration' which I also can't convert to an int
NOTE: Don't really care for the pressie days of each month (28-31)
but if it's possible to use specific month as input it will be super.
I'm not a Goxpert so Go (see what I did there?) easy on me..
Thanks in advance.
Use time.Duration Seconds() :
package main
import (
"fmt"
"time"
)
func main() {
m := time.Hour * 24 * 30
fmt.Println("In float: ", m.Seconds())
fmt.Println("In int: ", int(m.Seconds()))
}
Example on playground
If you do care about the different days in months, you can use time.Sub to get the duration:
package main
import (
"fmt"
"time"
)
func secondsInMonth(y int, m time.Month) int {
start := time.Date(y, m, 0, 0, 0, 0, 0, time.UTC)
end := time.Date(y, m+1, 0, 0, 0, 0, 0, time.UTC)
return int(end.Sub(start).Seconds())
}
func main() {
month := time.February
year := 2016
fmt.Printf("Days in %s %d: %d\n", month, year, secondsInMonth(year, month))
}
If you dont care just do 30*24*60*60. Thats as elegant as it gets.
Agree with the previous answers, but if you really want to remove the 24 * 30 it is possible.
Just goofing around; time.Time has some nice comparison features so this is another option:
package main
import (
"fmt"
"time"
)
func main() {
now := time.Now()
// the current month in seconds - can vary
duration := now.AddDate(0, 1, 0).Sub(now)
fmt.Println("In int: ", int(duration.Seconds()))
// similar, but exactly 30 days
duration := now.AddDate(0, 0, 30).Sub(now)
fmt.Println("In int: ", int(duration.Seconds()))
}
Example on playground
The number of days in a month depends on the month and the year (leap or non-leap year). The number of seconds in a day varies when there is a transition to or from daylight savings time. Therefore, the number of seconds in a month will vary by month, year, and time zone.
For example, Los Angeles is in the Pacific Time Zone. January and March both have 31 days. Because of the daylight savings time transition in March, there is one hour less (3600 = 60*60 seconds) in March than January.
For example,
package main
import (
"fmt"
"time"
)
func secondsInMonth(month time.Month, year int, loc *time.Location) int {
u := time.Date(year, month, 1, 0, 0, 0, 0, loc)
v := time.Date(year, month+1, 1, 0, 0, 0, 0, loc)
return int(v.Sub(u).Seconds())
}
func main() {
loc, err := time.LoadLocation("America/Los_Angeles")
if err != nil {
fmt.Println(err)
return
}
year := 2016
jan := secondsInMonth(time.January, year, loc)
feb := secondsInMonth(time.February, year, loc)
mar := secondsInMonth(time.March, year, loc)
apr := secondsInMonth(time.April, year, loc)
fmt.Println(jan, feb, mar, apr)
fmt.Println(jan - mar)
}
Output:
2678400 2505600 2674800 2592000
3600
Since you are only having 30 day months, we have a constant here and there is absolutely no need for any calculation. Using basic maths, we simplify
30*24*60*60
to
const secsInMonth = 2592000
When I have a time.Time:
// January, 29th
t, _ := time.Parse("2006-01-02", "2016-01-29")
How can I get a time.Time which represents January 31st? This example is trivial, but when there's a date in February, the last day might be 28th or 29th.
Package time
func Date
func Date(year int, month Month, day, hour, min, sec, nsec int, loc *Location) Time
Date returns the Time corresponding to
yyyy-mm-dd hh:mm:ss + nsec nanoseconds
in the appropriate zone for that time in the given location.
The month, day, hour, min, sec, and nsec values may be outside their
usual ranges and will be normalized during the conversion. For
example, October 32 converts to November 1.
For example, normalizing a date,
package main
import (
"fmt"
"time"
)
func main() {
// January, 29th
t, _ := time.Parse("2006-01-02", "2016-01-29")
fmt.Println(t.Date())
// January, 31st
y,m,_ := t.Date()
lastday:= time.Date(y,m+1,0,0,0,0,0,time.UTC)
fmt.Println(lastday.Date())
}
Output:
2016 January 29
2016 January 31
You could write a function yourself, maybe something like this:
func daysInMonth(month, year int) int {
switch time.Month(month) {
case time.April, time.June, time.September, time.November:
return 30
case time.February:
if year%4 == 0 && (year%100 != 0 || year%400 == 0) { // leap year
return 29
}
return 28
default:
return 31
}
}
EDIT: since I really like measuring things:
$ go test -bench .
testing: warning: no tests to run
PASS
BenchmarkDim2-8 200000000 7.26 ns/op
BenchmarkDim-8 1000000000 2.80 ns/op // LIES!
BenchmarkTime-8 10000000 169 ns/op
BenchmarkTime2-8 10000000 234 ns/op
ok github.com/drathier/scratchpad/go 9.741s
BenchMarkDim2: not tested, but very fast.
func daysInMonthTime(month, year int) time.Time {
return time.Time{}.Add(time.Hour * 10 + time.Hour*24*30*time.Duration(month-1) + time.Second * time.Duration(daysInMonth(month, year)) * 24 * 60 + 1337)
}
BenchmarkDim: // LIES
func daysInMonth(month, year int) int {
switch time.Month(month) {
case time.April, time.June, time.September, time.November:
return 30
case time.February:
if year%4 == 0 && (year%100 != 0 || year%400 == 0) {
// leap year
return 29
}
return 28
default:
return 31
}
}
BenchmarkTime:
func timeDaysInMonth() time.Time {
// January, 29th
t, _ := time.Parse("2006-01-02", "2016-01-29")
y, m, _ := t.Date()
lastday := time.Date(y, m+1, 0, 0, 0, 0, 0, time.UTC)
return lastday
}
BenchmarkTime2
func time2daysinmonth() time.Time {
t, _ := time.Parse("2006-01-02", "2016-01-01")
t = t.AddDate(0, 1, 0).AddDate(0, 0, -1)
return t
}
I've used something similar to this in my own code:
func lastDayOfTheMonth(year, month int) time.Time {
if month++; month > 12 {
month = 1
}
t := time.Date(year, time.Month(month), 0, 0, 0, 0, 0, time.UTC)
return t
}
playground
It's not Go specific but usually I do following in any language:
package main
import "fmt"
import "time"
func main() {
fmt.Println("Hello, playground")
t, _ := time.Parse("2006-01-02", "2016-01-01")
t = t.AddDate(0, 1, 0).AddDate(0,0,-1)
fmt.Printf("Last day: %v\n", t)
}
http://play.golang.org/p/JhpOZvEhBw