время golang.С() С месяцев и лет

Я пытаюсь преобразовать метку времени, как это:

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

до времени с формата, так что он сказал бы, как 9 месяцев назад 1 день 2 часа 30 минут 2 секунд.

что-то в этом роде.

Я time.Parse и time.Since до этого:

6915h7m47.6901559s

но как мне конвертировать оттуда? Что-то вроде этого я и подумал:--6-->

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

но проблема в том, что это не точный в течение нескольких месяцев, потому что месяцы могут 28, 30 и 31 дней.

есть ли лучший способ достичь того, что я хочу?

5 ответов


дни в месяц зависит от даты, так же, как дни в году (високосные годы).

если вы используете time.Since() чтобы получить прошедшее время с time.Time значение, или когда вы вычисляете разницу между 2 time.Time значений с помощью Time.Sub() метод, результат time.Duration, который теряет контекст времени (как Duration это просто разница во времени в наносекундах). Это означает, что вы не можете точно и однозначно вычислить разницу в годах, месяцах и т. д. из Duration значение.

правильное решение должно рассчитать разницу в контексте времени. Вы можете вычислить значение для каждого поля (год, месяц, день, час, минута, секунда), а затем нормализовать результат, чтобы не иметь отрицательных значений. Также рекомендуется поменять местами Time значения, если отношение между ними не является ожидаемым.

нормализация означает, если значение отрицательно, добавьте максимальное значение этого поля и уменьшите следующее поле на 1. Например, если seconds отрицательное, добавить 60 и декремента minutes к 1. Одна вещь, на которую нужно обратить внимание, - это нормализация разницы дней (дней в месяце), количество дней в соответствующем месяце должно быть применено. Это можно легко вычислить с помощью этого маленького трюка:

// Max days in year y1, month M1
t := time.Date(y1, M1, 32, 0, 0, 0, 0, time.UTC)
daysInMonth := 32 - t.Day()

логика этого заключается в том, что день 32 больше, чем максимальный день в любом месяце. Он получит автоматически нормализуется (дополнительные дни скатываются к следующему месяцу и день уменьшается должным образом). И когда мы вычитаем день, который у нас есть после нормализации из 32, мы получаем именно то, что последний день был в месяце.

обработка часового пояса:

расчет разницы даст правильный результат, только если оба значения времени, которые мы передаем, находятся в одном часовом поясе (time.Location). Мы включаем проверку в нашу функцию: если это не в этом случае мы "конвертируем" одно из значений времени в другое, используя Time.In() способ:

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

выход, как ожидалось:

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

на Go Playground.

чтобы рассчитать, сколько вам лет являются:

// 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)

пример:

You are 36 years, 3 months, 8 days, 11 hours, 57 mins and 41 seconds old.

волшебная дата / время, в которое начинается время Go playground:2009-11-10 23:00:00 UTC
Это время, когда было впервые объявлено. Давайте подсчитаем, сколько лет Go:

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)

выход:

Go was announced 6 years, 4 months, 29 days, 16 hours, 53 mins and 31 seconds ago.

вы можете попробовать работать с my дата пакет, который включает в себя период пакет для работы с периодами времени в стиле ISO (Википедия).

тип периода поставляется с форматером, который понимает множество, печатая читаемые строки, такие как" 9 лет, 2 месяца "и" 3 часа, 4 минуты, 1 секунду", а также эквиваленты ISO ("P9Y2M "и"PT3H4M1S").

периоды, конечно, сложно из-за переменной длины дней (по ДСТ) и месяцев (по Григорианскому календарю). The period пакет пытается помочь вам, предоставляя API, который позволяет как точные, так и неточные вычисления. На короткие периоды (до ±3276 часов) он может преобразовать продолжительность точно.

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

Если вам нужны точные продолжительности над более длинными пядями, то вам нужно использовать между функцией (которая воплощает превосходный ответ icza).

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

если вы используете PostgreSQL, вы можете легко получить результат с


решение, предложенное izca это здорово, но он упускает одну вещь. Если добавить следующий пример, то можно увидеть эффект:

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

площадка

код вычисляет оставшиеся дни следующего неполного месяца на основе общих дней первого месяца (y1,M1), но он должен быть вычислен из предыдущего месяца более поздней даты месяца (y2,M2-1).

окончательный код, как следует:

package main

import (
    "fmt"
    "time"
)


func DaysIn(year int, month time.Month) int {
    return time.Date(year, month+1, 0, 0, 0, 0, 0, time.UTC).Day()
}

func Elapsed(from, to time.Time) (inverted bool, years, months, days, hours, minutes, seconds, nanoseconds int) {
    if from.Location() != to.Location() {
        to = to.In(to.Location())
    }

    inverted = false
    if from.After(to) {
        inverted = true
        from, to = to, from
    }

    y1, M1, d1 := from.Date()
    y2, M2, d2 := to.Date()

    h1, m1, s1 := from.Clock()
    h2, m2, s2 := to.Clock()

    ns1, ns2 := from.Nanosecond(), to.Nanosecond()

    years = y2 - y1
    months = int(M2 - M1)
    days = d2 - d1

    hours = h2 - h1
    minutes = m2 - m1
    seconds = s2 - s1
    nanoseconds = ns2 - ns1

    if nanoseconds < 0 {
        nanoseconds += 1e9
        seconds--
    }
    if seconds < 0 {
        seconds += 60
        minutes--
    }
    if minutes < 0 {
        minutes += 60
        hours--
    }
    if hours < 0 {
        hours += 24
        days--
    }
    if days < 0 {
        days += DaysIn(y2, M2-1)
        months--
    }
    if months < 0 {
        months += 12
        years--
    }
    return
}

func main() {
    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(Elapsed(a, b)) // Expected: false 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(Elapsed(a, b)) // Expected: false 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(Elapsed(a, b)) // Expected: false 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(Elapsed(a, b)) // Expected: false 0 11 1 0 0 0

    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(Elapsed(a, b)) // Expected: false 0 1 27 0 0 0
}

площадка


что-то вроде этого будет работать, вероятно, не самым эффективным, но это так же точно, как вы получите:

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
}

площадка