Trody
Trody

Reputation: 423

Get real calendar days between two dates

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.

Upvotes: 8

Views: 5169

Answers (3)

zouying
zouying

Reputation: 1617

  1. Use Duration.Hours() to get hours
  2. Use math.Ceil() to find calendar days
  3. 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))

}

Upvotes: 9

peterSO
peterSO

Reputation: 166795

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

Upvotes: 4

leaf bebop
leaf bebop

Reputation: 8232

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

Upvotes: 1

Related Questions