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?
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.
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
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
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
}
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()
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
}