How to calculate time passed since a date in Years, Months and Days and combinations

2,623 views
Skip to first unread message

Constantine Vass

unread,
Jan 10, 2014, 12:23:38 AM1/10/14
to golan...@googlegroups.com

Need to know time passed since a date

and represent the result in Years, Months and Days and combinations.


If the results does not have Months, show only Years and Days

If the result does not have Days, Show only Years and Months


Also to show total Months and Days. 

Also to show total Days. 


For example:

Today is: Jan 9, 2014


I have a dates like this:


1) Jan 9, 1967

The results should be:

47 Years passed since 1)

Total (x) Months + (y) Days

Total (z) Days


2) Jan 8, 1967

The results should be:

47 Years and 1 Day passed since 2)

Total (x) Months + (y) Days

Total (z) Days


3) Dec 9, 1966

The results should be:

47 Years and 1 Month passed since 3)

Total (x) Months + (y) Days

Total (z) Days


Are there a standard package to do that?

Svip

unread,
Jan 10, 2014, 3:56:01 AM1/10/14
to Constantine Vass, golan...@googlegroups.com
On 10 January 2014 06:23, Constantine Vass <ths...@gmail.com> wrote:

> Are there a standard package to do that?

How about this?

http://play.golang.org/p/GBd3tJWe79

Constantine Vass

unread,
Jan 10, 2014, 11:39:27 AM1/10/14
to golan...@googlegroups.com, Constantine Vass
In the code resolution is a year.

If I change the initial date from Jan 9, 1967 to: 
Jan 8, 1967

it still shows 47 year passed, while actually it is 47 years and 1 day.

Also not all years have 365 days,  not all months have 30, 
and these should be not hard coded. The result should be precise.

By "package" I thought there is a package for more sophisticated 
functionality like this.

Coda Hale

unread,
Jan 10, 2014, 12:11:43 PM1/10/14
to Constantine Vass, golang-nuts
I’m pretty sure the only way of calculating this is to iterate through the years, months, and days:


--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.

signature.asc

Constantine Vass

unread,
Jan 10, 2014, 12:20:15 PM1/10/14
to golan...@googlegroups.com, Constantine Vass
Probably inefficient but I think it is good solution. 

Kyle Lemons

unread,
Jan 10, 2014, 2:09:26 PM1/10/14
to Constantine Vass, golang-nuts
On Fri, Jan 10, 2014 at 8:39 AM, Constantine Vass <ths...@gmail.com> wrote:
In the code resolution is a year.

If I change the initial date from Jan 9, 1967 to: 
Jan 8, 1967

it still shows 47 year passed, while actually it is 47 years and 1 day.

Also not all years have 365 days,  not all months have 30, 
and these should be not hard coded. The result should be precise.

Be careful about how much "precision" you want.


You're probably better off with a deliberate approximation than with trying to be precise.

By "package" I thought there is a package for more sophisticated 
functionality like this.

On Friday, January 10, 2014 12:56:01 AM UTC-8, Svip Pong wrote:
On 10 January 2014 06:23, Constantine Vass <ths...@gmail.com> wrote:

> Are there a standard package to do that?

How about this?

http://play.golang.org/p/GBd3tJWe79

--

Rob Pike

unread,
Jan 10, 2014, 2:10:24 PM1/10/14
to Constantine Vass, golan...@googlegroups.com
Not quite the same, but to get the days you can just take the
difference in Julian dates. Here's a function to calculate the Julian
date, provided it's within 209 years of Jan 2, 2006.

func Julian(t time.Time) float64 {
// Julian date, in seconds, of the "Format" standard time.
// (See http://www.onlineconversion.com/julian_date.htm)
const julian = 2453738.4195
// Easiest way to get the time.Time of the Unix time.
// (See comments for the UnixDate in package Time.)
unix := time.Unix(1136239445, 0)
const oneDay = float64(86400.*time.Second)
return julian+float64(t.Sub(unix))/oneDay
}

-rob

Constantine Vass

unread,
Jan 10, 2014, 2:40:11 PM1/10/14
to golan...@googlegroups.com, Constantine Vass
With my app it is needed precision within a day.

Constantine Vass

unread,
Jan 10, 2014, 2:58:46 PM1/10/14
to golan...@googlegroups.com, Constantine Vass
This would work fast I see. If it could be used for my 
task it would be great. Still I don't see how. "Coda Hale's" code 
is working correct. If it can be made more efficient it wold be great.
I have a database with 140 mil records and it needs to be processed
as efficient as possible - a difficult task.

Erwin

unread,
Jan 10, 2014, 2:58:45 PM1/10/14
to Constantine Vass, golang-nuts
Given for instance Feb 28, 2013 and Jan 28, 2013, should the
difference be printed as
1 month
or
28 days
?

Constantine Vass

unread,
Jan 10, 2014, 3:11:07 PM1/10/14
to golan...@googlegroups.com, Constantine Vass
The idea to be short and informative. The actual app involves 
property's last sale date. They need an actionable information
for tax purposes - within days' resolution. 

If 28 days has same information as 1 month, it is easier
for the user to read 1 month - it gives more information - 
the user does not need to know about month february 
actually involved. 

e.g. - a short user friendly representation of time elapsed till
today. 34567 days is overkill. 7 years and 1 month looks good.
7 years and 28 days is same as the former but if we factor out
that it happened to be february, 1 month gives more information.

It is tricky, I know.

Erwin

unread,
Jan 10, 2014, 3:24:57 PM1/10/14
to Constantine Vass, golang-nuts
Then something like the following approximation might be faster than the Coda Hale approach

func TimeDelta(t1, t0 time.Time) (years, months, days int) {
if t0.Sub(t1) > 0 {
// future event, figure out a desired response...
}

Y0, M0, D0 := t0.Date()
Y1, M1, D1 := t1.Date()

years = Y1 - Y0
months = (int)(M1 - M0)
if months < 0 {
if years > 0 {
years--
}
months += 12
}
days = D1 - D0
if days < 0 {
if months > 0 {
months--
}
days += 31
}
return
}



--

Kevin Gillette

unread,
Jan 10, 2014, 3:48:56 PM1/10/14
to golan...@googlegroups.com, Constantine Vass
On Friday, January 10, 2014 10:11:43 AM UTC-7, Coda Hale wrote:
I’m pretty sure the only way of calculating this is to iterate through the years, months, and days:

If that were true, then UNIX-style time systems would need to interate every day since Jan 1st 1970 just to run the `date` command, and Windows systems would need to iterate every day since Jan 1st 1900 to do the equivalent. Considering that either of these operations would likely take at least several seconds to do so, and since such times information is indeed calculated instantly by correctly implemented systems, the "iteration necessary" assertion is empirically false.

If you want to do it yourself, all you need to know is if the current year is divisible by 4 (in which case it's a 366 day year). If you need to handle dates before 2001 or after 2399, then you need to know about century leap years. Since you're just dealing with a day count, 'time of day' considerations like DST do not apply. The calculation is trivial.

If you don't want to do it yourself, you can leverage <http://golang.org/pkg/time/#Time.Date> and just do some basic arithmetic on the values returned from your start and end dates.

Bakul Shah

unread,
Jan 10, 2014, 3:50:07 PM1/10/14
to Constantine Vass, golan...@googlegroups.com
On Fri, 10 Jan 2014 08:39:27 PST Constantine Vass <ths...@gmail.com> wrote:
>
> In the code resolution is a year.
>
> If I change the initial date from Jan 9, 1967 to:
> Jan 8, 1967
>
> it still shows 47 year passed, while actually it is 47 years and 1 day.
>
> Also not all years have 365 days, not all months have 30,
> and these should be not hard coded. The result should be precise.

This is much like regular subtraction for a 3 digit number
except that the multiplier for each "digit" is different.

A 3 digit number can be represented as (h, t, u) for
100*h+10*t+u. You subtract "right to left" and borrow from
left.

Similarly if you represent a date as a triple (y, m, d), then
to compute
(y3, m3, d3) = (y2, m2, d2) - (y1, m1, d1)
You do
if d2 < d1 {
m2--
if m2 == 0 { // assume a legal value for month is in 1..12
m2 = 12
y2--
}
d2 += monthdays(y2,m2) // can return 28..31
}
d3 = d2 - d1
if m2 < m1 {
y2--
m2 += 12
}
m3 = m2 - m1
y3 = y2 - y1

Not sure this is quite right but should give you an idea of
what to do.

Kevin Gillette

unread,
Jan 10, 2014, 3:51:59 PM1/10/14
to golan...@googlegroups.com, Constantine Vass
On Friday, January 10, 2014 1:48:56 PM UTC-7, Kevin Gillette wrote:
Considering that either of these operations would likely take at least several seconds to do so

s/seconds/milliseconds/ (which is still _very_ [very] slow for calculating this kind of information)

Rob Pike

unread,
Jan 10, 2014, 4:14:19 PM1/10/14
to Kevin Gillette, golan...@googlegroups.com, Constantine Vass
The Julian date method is very fast and will get you close enough for
iterating to precision.. Divide the result by 365.2545 and you've got
years. You can then refine the value by iterating forwards from a
point very near the right answer.

-rob

Jason E. Aten

unread,
Jan 10, 2014, 4:31:33 PM1/10/14
to golan...@googlegroups.com, Constantine Vass
Here's an short tutorial on how to use the standard library's "time" package. Once you understand it, you should be able to do anything you want, efficiently, by doing your subtraction in nanoseconds_since_1970_epoch space, and then translating using Date() and Clock().

http://play.golang.org/p/28TCFqH0Vq


package main

// a basic tutorial on how to go from time -> date and date -> time.

import (
    "fmt"
    "time"
)

var WestCoastLocation *time.Location

func init() {
    var err error
    WestCoastLocation, err = time.LoadLocation("America/Los_Angeles")
    if err != nil {
        panic(err)
    }
}

func main() {

    // convert from time.Time to 1970 epoch based nanoseconds.                                                     
    now := time.Now()
    nanoseconds_since_1970_epoch := now.UnixNano()
    fmt.Printf("nanoseconds since 1 Jan 1970 midnight UTC epoch: %v\n", nanoseconds_since_1970_epoch)

    // convert from 1970 nanoseconds to time.Time                                                                  
    tm := time.Unix(0, int64(nanoseconds_since_1970_epoch))

    // set a location with In(), very important to get the year, month and day correct in the Date() call that follows.      
    tm = tm.In(WestCoastLocation)
    year, month, day := tm.Date()
    hrs, minutes, sec := tm.Clock()
    msec := 0
    fmt.Printf("year, month, day = %v, %v, %v\n", year, month, day)

    // similarly, construct a time.Time from component pieces.                                                     
    gotime := time.Date(year, month, day, int(hrs), int(minutes), int(sec), int(msec)*1000000, WestCoastLocation)

    fmt.Printf("gotime is %v\n", gotime)
}

Bakul Shah

unread,
Jan 10, 2014, 5:59:41 PM1/10/14
to Rob Pike, Kevin Gillette, golan...@googlegroups.com, Constantine Vass
On Fri, 10 Jan 2014 13:14:19 PST Rob Pike <r...@golang.org> wrote:
> The Julian date method is very fast and will get you close enough for
> iterating to precision.. Divide the result by 365.2545 and you've got
> years. You can then refine the value by iterating forwards from a
> point very near the right answer.

Consider:

2010/01/11 - 2009/01/11 = 365 days
2013/01/11 - 2012/01/11 = 366 days

I would report both as 1 year since only the year differs by
1. Similarly a difference of 30 days can be reported as 1
month 2 days, 1 month 1 day, 1 month, or 30 days depending on
the start month. For a human the easiest thing to do would be
to just add/subtrace days and then months and then years (sort
of like regular addition/subtraction by hand). While this
makes the most sense to me, the original "specification" can
be interpreted differently. It is a bit ambiguous.

Constantine Vass

unread,
Jan 10, 2014, 7:41:48 PM1/10/14
to golan...@googlegroups.com, Constantine Vass
This is the fastest working solution I came with:
http://play.golang.org/p/ocYFWY7kpo

The resolution is half a year. Working with ranges 
we can come down to better resolution. Because
it will work very fast and I need to process 140 mil 
records, better have this floating number pre calculated
and then to figure out how to use it. 

0 - 0.5 - 1/2 year since data of sale
0.6 - 1 - more than 1/2 year since data of sale
1 - one year since data of sale
1 - 1.5 - less than 1 and 1/2 year since data of sale
1.5 - 2  - 2 years since data of sale

20130801 -
20140101 - today

days since:153.000000
years precise:0.418886 - less than 1/2 year
approximate full years since:0


20130501 -
20140101 - today

days since:245.000000
years precise:0.670765 - more than 1/2 year
approximate full years since:1


20120101 -
20140101 
- today
days since:731.000000
years precise:2.001344 - 2 years exact
approximate full years since:2


20130101 -
20140101 - today

days since:365.000000
years precise:0.999303
approximate full years since:1 - 1 year exact


20120801 -
20140101 - today

days since:518.000000
years precise:1.418189 - less than 1 and 1/2 year
approximate full years since:1 



20120401 -
20140101 - today
days since:640.000000
years precise:1.418189
years precise:1.752203 -more than 1 and 1/2 year
approximate full years since:2


On Friday, January 10, 2014 11:10:24 AM UTC-8, Rob Pike wrote:

Jason E. Aten

unread,
Jan 11, 2014, 2:26:03 AM1/11/14
to golan...@googlegroups.com, Constantine Vass
It isn't difficult to do exactly and quickly, but you do have to figure out what you want to do in corner cases like t3() and t4() involving leap years and end-of-month to end-of-month.

http://play.golang.org/p/tb6TiHCn-H

Michael Jones

unread,
Jan 11, 2014, 9:55:19 AM1/11/14
to Constantine Vass, golang-nuts
On Fri, Jan 10, 2014 at 8:39 AM, Constantine Vass <ths...@gmail.com> wrote:
it still shows 47 year passed, while actually it is 47 years and 1 day.

Also not all years have 365 days,  not all months have 30, 
and these should be not hard coded. The result should be precise.

I was reading this thread with the intent of writing some helpful code if needed, then I found the above. Hmm...

Constantine, from the various things that you have said, I don't have a clear picture of what you really want. It is not that I do not understand you, it is that two different things are being said.
 
(Jan 9, 1967) to (Jan 8, 1967) is certainly a "year and a day"
as you write, but that is true in leap years and non-leap years, and this show the difference in the two approaches to what you describe:

1. Reason about differences in the Year, Month, and Day of start and end. (dates)

2. Reason about the difference in days or subdays between start and end. (duration)

These are different notions with different answers and different code. Which do you really need in your application?

--
Michael T. Jones | Chief Technology Advocate  | m...@google.com |  +1 650-335-5765

Michael Jones

unread,
Jan 11, 2014, 9:57:06 AM1/11/14
to Constantine Vass, golang-nuts
edit the start/stop dates above: "jan 8, 1967 to jan 9, 1968"

Constantine Vass

unread,
Jan 11, 2014, 2:29:30 PM1/11/14
to golan...@googlegroups.com, Constantine Vass
Michael,

I want to calculate duration between two dates and result to be interpreted
easy by humans - say 47 year 1 month and 5 days passed since initial data
till today.

Form other side the amount of data is big - 140 mil records. 

Kile Lemons mentioned about how much "precision" is really wanted. 
Probably there is no way to achieve the initial goal - if I relax the initial
requirements probably it is better. 

From what Rob Pike suggested I see it very efficient to get duration in days,
then years. Then dividing by two I can get half years and then quarters. 
This is not exactly the initial idea but will work very fast. 

0.5 will mean half a year passed. 1.0 - one year, 1.5 - one and half. 
1.25 - one year and a quarter, etc. Not exactly what I initially thought
but more practical to implement.

--Constantine

Constantine Vass

unread,
Jan 11, 2014, 2:32:18 PM1/11/14
to golan...@googlegroups.com, Constantine Vass
Rob,

What this const is used for?
const julian = 2453738.4195 

Could you please tell if this code will work correctly?
http://play.golang.org/p/ocYFWY7kpo

Thanks,
--Constantine

On Friday, January 10, 2014 11:10:24 AM UTC-8, Rob Pike wrote:

andrey mirtchovski

unread,
Jan 11, 2014, 4:25:14 PM1/11/14
to Constantine Vass, golang-nuts
> What this const is used for?
> const julian = 2453738.4195

http://scienceworld.wolfram.com/astronomy/JulianDate.html

Michael Jones

unread,
Jan 11, 2014, 8:51:57 PM1/11/14
to Constantine Vass, golang-nuts
OK. So when one month passes that means one calendar month, right? April->May. That has nothing to do with the number of days in a month because it just means "number of times the month name changed." Thus the 360 and 30 and leap year (4, 100, 400) calendrical equations are irrelevant. If so, the perfect answer is posted above by Bakul.


--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.

Bakul Shah

unread,
Jan 11, 2014, 10:17:05 PM1/11/14
to Michael Jones, Constantine Vass, golang-nuts
On Jan 11, 2014, at 5:51 PM, Michael Jones <m...@google.com> wrote:

OK. So when one month passes that means one calendar month, right? April->May. That has nothing to do with the number of days in a month because it just means "number of times the month name changed." Thus the 360 and 30 and leap year (4, 100, 400) calendrical equations are irrelevant. If so, the perfect answer is posted above by Bakul.

The leap year equation is relevant for computing monthday(year, month) in the solution I outlined. 

I can't help pointing out two things:
- For 140 million records the main cost will be in database IO, not in computing the date difference so a focus on efficiency seems a bit premature.
- Accuracy (more so than precision) depends on the application. If for instance a mortgage is refinanced part way though a month,  you'd have to accurately account for last month days for which the old loan was active. 
Reply all
Reply to author
Forward
0 new messages