web-dev-qa-db-fra.com

depuis () avec des mois et des années

J'essaie de convertir un horodatage comme celui-ci:

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

à un moment depuis le format, il dirait donc il y a 9 mois 1 jour 2 heures 30 minutes 2 secondes.

quelque chose comme ca.

J'ai utilisé time.Parse et time.Since pour y arriver:

6915h7m47.6901559s

Mais comment puis-je convertir à partir de là? Quelque chose comme ça, c'est ce que je pensais:

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

Mais le problème est que cela ne sera pas exact pendant des mois car les mois peuvent avoir 28, 30 et 31 jours.

Y a-t-il une meilleure façon de réaliser ce que je veux?

20
gempir

Les jours d'un mois dépendent de la date, tout comme les jours d'une année (années bissextiles).

Si vous utilisez time.Since() pour obtenir le temps écoulé depuis une valeur time.Time , ou lorsque vous calculez la différence entre 2 time.Time Utilisant la méthode Time.Sub() , le résultat est un time.Duration qui perd le contexte temporel (comme Duration est juste la différence de temps en nanosecondes). Cela signifie que vous ne pouvez pas calculer avec précision et sans ambiguïté la différence en années, mois, etc. à partir d'une valeur Duration.

La bonne solution doit calculer la différence dans le contexte du temps. Vous pouvez calculer la différence pour chaque champ (année, mois, jour, heure, minute, seconde), puis normaliser le résultat pour ne pas avoir de valeurs négatives. Il est également recommandé d'échanger les valeurs Time si la relation entre elles n'est pas attendue.

La normalisation signifie que si une valeur est négative, ajoutez la valeur maximale de ce champ et décrémentez le champ suivant de 1. Par exemple, si seconds est négatif, ajoutez-y 60 Et décrémentez minutes par 1. Une chose à surveiller est lors de la normalisation de la différence de jours (jours dans le mois), le nombre de jours dans le mois approprié doit être appliqué. Cela peut facilement être calculé avec cette petite astuce:

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

La logique derrière cela est que le jour 32 Est plus grand que le jour maximum dans n'importe quel mois. Il sera automatiquement normalisé (jours supplémentaires reportés au mois suivant et jour décrémenté correctement). Et lorsque nous soustrayons le jour que nous avons après la normalisation de 32, nous obtenons exactement le dernier jour du mois.

Gestion des fuseaux horaires:

Le calcul de la différence ne donnera un résultat correct que si les deux valeurs de temps que nous transmettons sont dans le même fuseau horaire ( time.Location ). Nous incorporons une vérification dans notre fonction: si ce n'est pas le cas, nous "convertissons" l'une des valeurs de temps pour qu'elle soit au même endroit que l'autre en utilisant la méthode Time.In() :

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

Voici une solution qui calcule la différence en année, mois, jour, heure, min, sec:

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
}

Quelques tests:

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

La sortie est comme prévu:

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

Essayez-le sur le Go Playground .

Pour calculer votre âge:

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

Exemple de sortie:

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

La date/heure magique à laquelle commence le temps de jeu Go est: 2009-11-10 23:00:00 UTC
C'est le moment où Go a été annoncé pour la première fois. Calculons l'âge de 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)

Production:

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

Si vous utilisez PostgreSQL, vous pouvez facilement obtenir le résultat avec la fonction age.

Supposons que vous ayez deux dates a et b.

Comme l'a dit icza, soyez prudent, a et b doivent être dans le même fuseau horaire.

Tout d'abord, vous pouvez appeler age avec deux paramètres, dans votre cas date a et date b. Cette fonction renvoie un type d'intervalle qui contient des années, des mois, des semaines, des jours, des heures, des minutes, des secondes et des millisecondes.

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

La deuxième possibilité est d'utiliser la fonction age avec un paramètre. Le résultat est également un intervalle mais dans ce cas, age soustrait de current_date (à minuit). Supposons qu'aujourd'hui soit le 2016/06/16:

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

Remarque: le mot clé timestamp est nécessaire pour convertir la date '2016-06-30'.

Pour plus de détails, vous pouvez utiliser date_part ou directement extract fonction qui renvoie un champ spécifique (années, mois, jours ...).

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

Demande complète:

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

(avec CTE - Common Table Expression):

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

Documentation PostgreSQL (version actuelle): https://www.postgresql.org/docs/current/static/functions-datetime.html

3
alanzirek

La solution proposée par izca est excellente, mais il manque une chose. Si vous ajoutez l'exemple suivant, vous pouvez voir l'effet:

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

playground

Le code calcule les jours restants du mois incomplet suivant sur la base du nombre total de jours du premier mois (y1,M1), mais il doit être calculé à partir du mois précédent de la date du mois suivant (y2,M2-1).

Le code final est le suivant:

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
}

playground

3
robermorales

Vous pouvez essayer de travailler avec mon package date , qui inclut le package period pour travailler avec des périodes de style ISO ( Wikipedia ).

Le type Période est livré avec un formateur qui comprend les pluriels, imprimant des chaînes lisibles telles que "9 ans, 2 mois" et "3 heures, 4 minutes, 1 seconde", ainsi que les équivalents ISO ("P9Y2M" et "PT3H4M1S").

Les périodes sont, bien sûr, délicates en raison de la longueur variable des jours (en raison de l'heure d'été) et des mois (en raison du calendrier grégorien). Le package period essaie de vous aider en fournissant une API qui permet des calculs précis et imprécis. Pour de courtes périodes (jusqu'à ± 3276 heures), il est capable de convertir une durée avec précision.

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

Si vous avez besoin de durées précises sur des périodes plus longues, vous devez utiliser la fonction Entre (qui incarne l'excellente réponse d'icza).

p := period.Between(t1, t2)
str := p.String()
2
Rick-777

Quelque chose comme ça fonctionnerait, probablement pas le plus efficace, mais il est aussi précis que vous allez l'obtenir:

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

1
OneOfOne