2016-04-10 32 views
15

Sto cercando di convertire un timestamp come questo:golang time.Since() con mesi e anni

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

ad un tempo da quando formato in modo che sarebbe dire come 9 mesi fa 1 giorno 2 ore e 30 minuti 2 secondi.

qualcosa del genere.

Ho usato time.Parse e time.Since per arrivare a questo:

6915h7m47.6901559s 

Ma come si converte da lì? Qualcosa di simile è quello che ho pensato:

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

Ma il problema con questo è che questo non sarà accurato per mesi, perché mesi possono avere 28, 30 e 31 giorni.

C'è un modo migliore per ottenere ciò che voglio?

+0

Come precisa l'avete bisogno di essere? Imprecise relative timestamp renderebbe un po 'più semplice – T0xicCode

+0

Preferirei che fosse preciso al secondo – gempir

+0

@ T0xicCode potrebbe darmi la soluzione che è meno precisa? Forse posso migliorarlo in qualche modo – gempir

risposta

24

I giorni in un mese dipende dalla data, proprio come il giorni in un anno (anni bisestili).

Se si utilizza time.Since() per ottenere il tempo trascorso da quando un valore time.Time, o quando si calcola la differenza tra i 2 time.Time valori utilizzando il metodo Time.Sub(), il risultato è un time.Duration che perde il contesto di tempo (come Duration è solo il tempo differenza in nanosecondi). Ciò significa che non è possibile calcolare in modo preciso e senza ambiguità la differenza in anni, mesi, ecc. Da un valore Duration.

La soluzione giusta deve calcolare la differenza nel contesto del tempo. È possibile calcolare la differenza per ogni campo (anno, mese, giorno, ora, minuto, secondo), quindi normalizzare il risultato per non avere valori negativi. Si consiglia inoltre di scambiare i valori Time se la relazione tra di essi non è quella prevista.

Normalizzazione significa che se un valore è negativo, aggiungere il valore massimo di quel campo e decrementare il campo successivo di 1. Per esempio, se seconds è negativo, aggiungere 60 ad esso e decrementare minutes di 1. Una cosa da guardare fuori per è quando si normalizza la differenza di giorni (giorni in mese), deve essere applicato il numero di giorni nel mese corretto. Questo può essere facilmente calcolato con questo piccolo trucco:

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

La logica alla base di questo è che il giorno 32 è più grande rispetto al giorno massimo in qualsiasi mese. Si normalizzerà automaticamente (i giorni in più verranno spostati al mese successivo e il giorno decrementato correttamente). E quando sottraiamo il giorno che abbiamo dopo la normalizzazione a 32, otteniamo esattamente quello che era l'ultimo giorno del mese.

Tempo manipolazione orario:

Il calcolo differenza sarà solo dà risultato corretto se entrambi i valori temporali Passiamo sono nello stesso fuso orario (time.Location).Incorporiamo un controllo nel nostro funzione: se questo non è il caso, noi "Convert" uno del valore di tempo per essere nella stessa posizione come l'altro utilizzando il metodo Time.In():

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

ecco una soluzione che calcola la differenza in anno, mese, giorno, ora, minuti, secondi:

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 
} 

Alcuni test:

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 

uscita è come ex pected:

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

Prova sul Go Playground.

Per calcolare quanti anni hai: uscita

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

Esempio:

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

La data/ora di magia in cui inizia il tempo di un parco giochi Go è: 2009-11-10 23:00:00 UTC
Questo è il momento in cui Go fu annunciato per la prima volta. Calcoliamo quanto vecchio 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) 

uscita:

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

Questo è perfetto e preciso come volevo che fosse. grazie! Vorrei che una volta la durata offrisse mesi e anni, anche perché una cosa del genere dovrebbe essere parte della lib standard a mio parere. – gempir

0

qualcosa di simile sarebbe funzionato, probabilmente non il più efficiente, ma è così preciso come vuoi ottenere:

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

+0

non riesco a capire perché ma 2015-06-27T09: 34: 22 + 00: 00 il tempo restituisce 1 anno e 10 mesi che non è corretto. – gempir

+0

@ danielps1 la mia matematica era spenta, aggiornando il post ora – OneOfOne

2

Si potrebbe provare a lavorare con il mio pacchetto date, che include il pacchetto period per lavorare con i periodi in stile ISO di tempo (Wikipedia).

Il tipo Periodo viene fornito con un formattatore che capisce plurali, la stampa di stringhe leggibili quali "9 anni, 2 mesi" e "3 ore, 4 minuti, 1 secondo", insieme con gli equivalenti ISO ("P9Y2M" e " PT3H4M1S ").

I periodi sono, ovviamente, difficili a causa delle lunghezze variabili dei giorni (a causa dell'ora legale) e dei mesi (a causa del calendario gregoriano). Il pacchetto period tenta di aiutarti fornendo un'API che consente calcoli precisi e imprecisi. Per brevi periodi (fino a ± 3276 ore) è in grado di convertire una Durata in modo preciso.

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

Se avete bisogno di durate precise oltre campate più lunghe, è necessario utilizzare la funzione di Between (che incarnano risposta eccellente del icza).

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

Se si utilizza PostgreSQL, si può facilmente ottenere il risultato con la funzione age.

Supponiamo di avere due date a e b.

Come icza ha detto, fare attenzione, a e b devono essere nello stesso fuso orario.

Innanzitutto, è possibile richiamare age con due parametri, nel tuo caso data a e data b.Questa funzione restituisce un tipo di intervallo che contiene anni, mesi, settimane, giorni, ore, minuti, secondi e millisecondi.

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

Il secondo possibilty consiste nell'utilizzare age funzione con un parametro. Il risultato è anche un intervallo, ma in questo caso, age sottrarre da current_date (a mezzanotte). oggi Supponiamo che è 2016/06/16:

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

Nota, è necessaria timestamp parola chiave per lanciare la data '2016/06/30'.

Per ulteriori dettagli, è possibile utilizzare date_part o direttamente la funzione extract che restituisce un campo specifico (anni, mesi, giorni ...).

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 

richiesta completa:

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 

(con CTE - Tabella espressione comune):

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 

documentazione di PostgreSQL (versione attuale): https://www.postgresql.org/docs/current/static/functions-datetime.html

+0

Sto inviando questo perché, anche se non ha nulla a che fare con golang, è un approccio ben spiegato in sql. signore. – sbartell

0

La soluzione proposta da izca è fantastico, ma manca una cosa. Se si aggiunge il seguente esempio, è possibile vedere l'effetto:

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

Il codice sta calcolando i restanti giorni del mese prossimo incompleta sulla base dei giorni totali del primo mese (y1,M1), ma deve essere calcolato dal mese precedente del mese successivo (y2,M2-1).

Il codice finale è la seguente:

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