lenguaje - Tiempo de golang. Desde() con meses y años.
google''s go (5)
Estoy tratando de convertir una marca de tiempo como esta:
2015-06-27T09:34:22+00:00
a un tiempo desde el formato por lo que diría que hace 9 meses 1 día 2 horas 30 minutos 2 segundos.
algo como eso.
Utilicé time.Parse
y time.Since
para llegar a esto:
6915h7m47.6901559s
Pero, ¿cómo me convierto a partir de ahí? Algo como esto es lo que pensé:
for hours > 24 {
days++
hours -= 24
}
Pero el problema con esto es que no será preciso durante meses porque los meses pueden tener 28, 30 y 31 días.
¿Hay una mejor manera de lograr lo que quiero?
Algo como esto funcionaría, probablemente no sea el más eficiente, pero es tan preciso como lo vas a obtener:
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
}
La solución propuesta por izca es genial, pero se pierde una cosa. Si agrega el siguiente ejemplo, puede ver el efecto:
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
El código calcula los días restantes del siguiente mes incompleto basándose en los días totales del primer mes ( y1,M1
), pero debe calcularse a partir del mes anterior del mes de la fecha posterior ( y2,M2-1
).
El código final es el siguiente:
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
}
Los días en un mes dependen de la fecha, al igual que los días en un año (años bisiestos).
Si usa time.Since()
para obtener el tiempo transcurrido desde un valor time.Time
, o cuando calcula la diferencia entre 2 valores time.Time
usando el método Time.Sub()
, el resultado es un time.Duration
que pierde el contexto de tiempo (como Duration
es solo la diferencia de tiempo en nanosegundos). Esto significa que no puede calcular con precisión y sin ambigüedad la diferencia en años, meses, etc. a partir de un valor de Duration
.
La solución correcta debe calcular la diferencia en el contexto del tiempo. Puede calcular la diferencia para cada campo (año, mes, día, hora, minuto, segundo) y luego normalizar el resultado para que no tenga ningún valor negativo. También se recomienda intercambiar los valores de Time
si la relación entre ellos no es la esperada.
La normalización significa que si un valor es negativo, agregue el valor máximo de ese campo y disminuya el siguiente campo en 1. Por ejemplo, si los seconds
son negativos, agregue 60
y disminuya los minutes
en 1. Una cosa a tener en cuenta es cuando se normaliza Diferencia de días (días en mes), el número de días en el mes apropiado debe ser aplicado. Esto se puede calcular fácilmente con este pequeño truco:
// Max days in year y1, month M1
t := time.Date(y1, M1, 32, 0, 0, 0, 0, time.UTC)
daysInMonth := 32 - t.Day()
La lógica detrás de esto es que el día 32
es más grande que el día máximo en cualquier mes. Se normalizará automáticamente (los días adicionales se transfieren al mes y el día se reducen adecuadamente). Y cuando restamos el día que tenemos después de la normalización de 32, obtenemos exactamente lo que fue el último día del mes.
Manejo de zona horaria:
El cálculo de la diferencia solo dará un resultado correcto si los dos valores de tiempo que pasamos están en la misma zona horaria ( time.Location
). Incorporamos un control a nuestra función: si este no es el caso, "convertimos" uno de los valores de tiempo para que esté en la misma ubicación que el otro utilizando el método Time.In()
:
if a.Location() != b.Location() {
b = b.In(a.Location())
}
Aquí hay una solución que calcula la diferencia en año, mes, día, hora, min, seg:
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
}
Algunas pruebas:
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 salida es la esperada:
1 1 1 1 1 1
0 0 30 0 0 0
0 0 28 0 0 0
0 11 1 0 0 0
Pruébalo en el Go Playground .
Para calcular cuántos años tienes:
// 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)
Ejemplo de salida:
You are 36 years, 3 months, 8 days, 11 hours, 57 mins and 41 seconds old.
La fecha / hora mágica en la que comienza la hora de ir al patio de juegos es: 2009-11-10 23:00:00 UTC
Este es el momento en que Go fue anunciado por primera vez. Vamos a calcular qué edad tiene 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)
Salida:
Go was announced 6 years, 4 months, 29 days, 16 hours, 53 mins and 31 seconds ago.
Puede intentar trabajar con mi paquete de date , que incluye el paquete de period para trabajar con períodos de tiempo de estilo ISO ( Wikipedia ).
El tipo Period viene con un formateador que entiende plurales, imprimiendo cadenas legibles como "9 años, 2 meses" y "3 horas, 4 minutos, 1 segundo", junto con los equivalentes ISO ("P9Y2M" y "PT3H4M1S").
Los períodos son, por supuesto, complicados debido a la duración variable de días (debido a DST) y meses (debido al calendario gregoriano). El paquete de period
intenta ayudarlo proporcionando una API que permite cálculos precisos e imprecisos. Para períodos cortos (hasta ± 3276 horas) puede convertir una Duración de forma precisa.
duration := time.Since(...)
p, _ := period.NewOf(duration)
str := p.String()
Si necesita duraciones precisas en períodos más largos, debe utilizar la función Entre (que representa la excelente respuesta de icza).
p := period.Between(t1, t2)
str := p.String()
Si usa PostgreSQL, puede obtener fácilmente el resultado con la función age
.
Supongamos que tienes dos fechas b
.
Como dijo icza, ten cuidado, b
deben estar en la misma zona horaria.
Primero, puede invocar age
con dos parámetros, en su caso, fecha a
y fecha b
. Esta función devuelve un tipo de intervalo que contiene años, meses, semanas, días, horas, minutos, segundos y milisegundos.
SELECT age(''2016-03-31'', ''2016-06-30''); -- result is: -2 mons -30 days
La segunda posibilidad es usar la función de age
con un parámetro. El resultado también es un intervalo, pero en este caso, se resta la age
de current_date (a medianoche). Supongamos que hoy es 16/06/2016:
SELECT age(timestamp ''2016-06-30''); -- result is: -14 days
Tenga en cuenta que la palabra clave de timestamp
es necesaria para emitir la fecha ''2016-06-30''.
Para obtener más información, puede usar date_part
o directamente la función de extract
que devuelve un campo específico (años, meses, días ...).
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
Solicitud 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 - Expresión de tabla común):
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
Documentación de PostgreSQL (versión actual): https://www.postgresql.org/docs/current/static/functions-datetime.html