Golang time.Since()有几个月和几年

时间:2016-04-10 13:16:03

标签: go time duration

我正在尝试转换这样的时间戳:

2015-06-27T09:34:22+00:00

到格式的时间所以它会说像9个月前1天2小时30分2秒。

类似的东西。

我使用time.Parsetime.Since来实现这个目标:

6915h7m47.6901559s

但是如何从那里转换?这样的事情就是我的想法:

for hours > 24 {
        days++
        hours -= 24
}

但问题在于,这几个月都不准确,因为几个月可以有28天,30天和31天。

有没有更好的方法来实现我想要的目标?

5 个答案:

答案 0 :(得分:35)

一个月的日子取决于日期,就像一年中的日子(闰年)。

如果您使用time.Since()来获取自time.Time值以来经过的时间,或者使用Time.Sub()方法计算2 time.Time个值之间的差异,结果是time.Duration,它会丢失时间上下文(因为Duration只是以纳秒为单位的时间差)。这意味着您无法准确无误地计算Duration值中的年,月等差异。

正确的解决方案必须计算时间背景的差异。您可以计算每个字段(年,月,日,小时,分钟,秒)的差异,然后将结果标准化为不具有任何负值。如果它们之间的关系不是预期的,也建议交换Time值。

标准化意味着如果值为负,则添加该字段的最大值并将下一个字段减1.例如,如果seconds为负数,则向其添加60并递减{{1要注意的一件事是,在规范天数差异(每月的天数)时,必须应用适当月份的天数。这可以通过这个小技巧轻松计算出来:

minutes

这背后的逻辑是// Max days in year y1, month M1 t := time.Date(y1, M1, 32, 0, 0, 0, 0, time.UTC) daysInMonth := 32 - t.Day() 天大于任何月份的最大日期。它将自动标准化(额外的天数滚动到下个月和日正确递减)。当我们减去从32日归一化后的日子时,我们就得到了该月的最后一天。

时区处理:

如果我们传入的两个时间值都在同一时区(time.Location),差异计算将只给出正确的结果。我们对我们的功能进行了检查:如果不是这种情况,我们将转换"使用Time.In()方法与其他时间值位于同一位置的时间值之一:

32

这是一个计算年,月,日,时,分,秒差异的解决方案:

if a.Location() != b.Location() {
    b = b.In(a.Location())
}

一些测试:

func diff(a, b time.Time) (year, month, day, hour, min, sec int) {
    if a.Location() != b.Location() {
        b = b.In(a.Location())
    }
    if a.After(b) {
        a, b = b, a
    }
    y1, M1, d1 := a.Date()
    y2, M2, d2 := b.Date()

    h1, m1, s1 := a.Clock()
    h2, m2, s2 := b.Clock()

    year = int(y2 - y1)
    month = int(M2 - M1)
    day = int(d2 - d1)
    hour = int(h2 - h1)
    min = int(m2 - m1)
    sec = int(s2 - s1)

    // Normalize negative values
    if sec < 0 {
        sec += 60
        min--
    }
    if min < 0 {
        min += 60
        hour--
    }
    if hour < 0 {
        hour += 24
        day--
    }
    if day < 0 {
        // days in month:
        t := time.Date(y1, M1, 32, 0, 0, 0, 0, time.UTC)
        day += 32 - t.Day()
        month--
    }
    if month < 0 {
        month += 12
        year--
    }

    return
}

输出符合预期:

var a, b time.Time
a = time.Date(2015, 5, 1, 0, 0, 0, 0, time.UTC)
b = time.Date(2016, 6, 2, 1, 1, 1, 1, time.UTC)
fmt.Println(diff(a, b)) // Expected: 1 1 1 1 1 1

a = time.Date(2016, 1, 2, 0, 0, 0, 0, time.UTC)
b = time.Date(2016, 2, 1, 0, 0, 0, 0, time.UTC)
fmt.Println(diff(a, b)) // Expected: 0 0 30 0 0 0

a = time.Date(2016, 2, 2, 0, 0, 0, 0, time.UTC)
b = time.Date(2016, 3, 1, 0, 0, 0, 0, time.UTC)
fmt.Println(diff(a, b)) // Expected: 0 0 28 0 0 0

a = time.Date(2015, 2, 11, 0, 0, 0, 0, time.UTC)
b = time.Date(2016, 1, 12, 0, 0, 0, 0, time.UTC)
fmt.Println(diff(a, b)) // Expected: 0 11 1 0 0 0

Go Playground上尝试。

计算您的年龄:

1 1 1 1 1 1
0 0 30 0 0 0
0 0 28 0 0 0
0 11 1 0 0 0

示例输出:

// Your birthday: let's say it's January 2nd, 1980, 3:30 AM
birthday := time.Date(1980, 1, 2, 3, 30, 0, 0, time.UTC)
year, month, day, hour, min, sec := diff(birthday, time.Now())

fmt.Printf("You are %d years, %d months, %d days, %d hours, %d mins and %d seconds old.",
    year, month, day, hour, min, sec)

Go游乐场时间开始的神奇日期/时间是:You are 36 years, 3 months, 8 days, 11 hours, 57 mins and 41 seconds old.
这是Go首次公布的时间。让我们计算Go的年龄:

2009-11-10 23:00:00 UTC

输出:

goAnnounced := time.Date(2009, 11, 10, 23, 0, 0, 0, time.UTC)
year, month, day, hour, min, sec := diff(goAnnounced, time.Now())
fmt.Printf("Go was announced "+
    "%d years, %d months, %d days, %d hours, %d mins and %d seconds ago.",
    year, month, day, hour, min, sec)

答案 1 :(得分:3)

如果您使用PostgreSQL,则可以使用age函数轻松获得结果。

假设您有两个日期ab

与icza一样,请注意,ab必须位于同一时区。

首先,您可以使用两个参数调用age,例如日期a和日期b。此函数返回包含年,月,周,日,小时,分钟,秒和毫秒的间隔类型。

SELECT age('2016-03-31', '2016-06-30'); -- result is: -2 mons -30 days

第二种可能性是将age函数与一个参数一起使用。结果也是一个区间,但在这种情况下,age从current_date减去(午夜)。假设今天是2016/06/16:

SELECT age(timestamp '2016-06-30'); -- result is: -14 days

注意,需要使用timestamp关键字来投射日期&#39; 2016-06-30&#39;。

有关详细信息,您可以使用date_part或直接extract函数返回一个特定字段(年,月,日......)。

SELECT date_part('month', age('2016-03-31', '2016-06-30')); --result is: -2
SELECT date_part('day',   age('2016-03-31', '2016-06-30')); --result is: -30

完整请求:

SELECT  
    date_part('year', diff) as year
  , date_part('month', diff) as month
  , date_part('day', diff) as day
FROM (
  SELECT age(timestamp '2016-06-30') AS diff
) as qdiff;

-- result is: 
-- year month day
-- 0    0     -14

(使用CTE - 公用表表达式):

WITH qdiff AS (
  SELECT age(timestamp '2016-06-30') AS diff
)
SELECT  
    date_part('year', diff) as year
  , date_part('month', diff) as month
  , date_part('day', diff) as day
FROM qdiff

-- result is: 
-- year month day
-- 0    0     -14

PostgreSQL文档(当前版本):https://www.postgresql.org/docs/current/static/functions-datetime.html

答案 2 :(得分:3)

izca提出的解决方案很棒,但它错过了一件事。如果添加以下示例,则可以看到效果:

delete  stud
from student as stud inner join address_Table as addd on addd.aid = stud.aid 
where id = 142

playground

代码根据第一个月的总天数(a = time.Date(2015, 1, 11, 0, 0, 0, 0, time.UTC) b = time.Date(2015, 3, 10, 0, 0, 0, 0, time.UTC) fmt.Println(diff(a, b)) // Expected: 0 1 27 0 0 0 // Actual output: 0 1 30 0 0 0 )计算下一个不完整月份的剩余天数,但需要从较晚日期月份的前一个月计算({{ 1}})。

最终代码如下:

y1,M1

playground

答案 3 :(得分:2)

您可以尝试使用我的date包,其中包含period包,用于处理ISO样式的时间段(Wikipedia)。

Period类型附带一个格式化程序,可理解复数,打印可读字符串,如“9年,2个月”和“3小时,4分钟,1秒”,以及ISO等效项(“P9Y2M”和“PT3H4M1S” “)。

由于天数(由于DST)和月份(由于公历)的变化,因此期间是棘手的。 period包试图通过提供允许精确和不精确计算的API来帮助您。对于短时间(最多±3276小时),它可以精确地转换持续时间。

duration := time.Since(...)
p, _ := period.NewOf(duration)
str := p.String()

如果你需要精确的持续时间超过更长的跨度,你需要使用Between函数(它体现了icza的优秀答案)。

p := period.Between(t1, t2)
str := p.String()

答案 4 :(得分:1)

这样的东西可以起作用,可能不是最有效的但是它准确无误:

func main() {
    a := time.Date(2015, 10, 15, 0, 0, 0, 0, time.UTC)
    b := time.Date(2016, 11, 15, 0, 0, 0, 0, time.UTC)
    fmt.Println(monthYearDiff(a, b))
}

func monthYearDiff(a, b time.Time) (years, months int) {
    m := a.Month()
    for a.Before(b) {
        a = a.Add(time.Hour * 24)
        m2 := a.Month()
        if m2 != m {
            months++
        }
        m = m2
    }
    years = months / 12
    months = months % 12
    return
}

playground