rickb777/date

Between function fails if one argument is time.Now

Closed this issue · 3 comments

I tried getting a period between two dates, one of which is the current time.

year := 2021
month := 3

now := time.Now()
p := period.Between(time.Date(int(year), time.Month(month), 1, 0, 0, 0, 0, now.Location()), now)

However, this panics with the statement:
Periods must have homogeneous signs; got P0Y0M0W190DT19H3M44S

For reference, this was generated at 2021-09-07 19:07:43.2612391 +0900 JST m=+0.109965201 but this was not the only time which generated this panic. What exactly does it mean for a period to not have a homogeneous sign and how can I fix this?

shogg commented

This is not caused by time.Now.
period.Between is totally broken if the period is longer than 3276 days (or hours?).
daysDiff (implementation detail) is meant to deliver a duration in days as components (years, months, days ...). None of the components should be negative. But that doesn't work if you just subtract components of two dates (year, month, day). That's not a duration anymore. It happens after if day > 3276 down below.

func daysDiff(t1, t2 time.Time) (year, month, day, hour, min, sec, hundredth int) {
	duration := t2.Sub(t1)

	hh1, mm1, ss1 := t1.Clock()
	hh2, mm2, ss2 := t2.Clock()

	day = int(duration / (24 * time.Hour))

	hour = hh2 - hh1
	min = mm2 - mm1
	sec = ss2 - ss1
	hundredth = (t2.Nanosecond() - t1.Nanosecond()) / 100000000

	// Normalize negative values
	if sec < 0 {
		sec += 60
		min--
	}

	if min < 0 {
		min += 60
		hour--
	}

	if hour < 0 {
		hour += 24
		// no need to reduce day - it's calculated differently.
	}

	// test 16bit storage limit (with 1 fixed decimal place)
	if day > 3276 {
		y1, m1, d1 := t1.Date()
		y2, m2, d2 := t2.Date()
		year = y2 - y1
		month = int(m2 - m1)
		day = d2 - d1
	}

	return
}

I have not been able to reproduce this issue. The daysDiff function works (only) because it is called with ordered arguments, i.e. t2 is after t1. The fixed-point 16 bit arithmetic is a core part of the design.

shogg commented

The point was and still is, daysDiff returns something like this: 2023/10/1-2023/09/21=0/1/-20. Positive and negative numbers can occur and later get rejected by period.Between. First I thought you were using a clever algorithm and correct it later, outside, since it's a private method. The time-Package is able to work with unnormalised time-Values. But that's not the case here.
Correct me where I am wrong. I think I stated the problem correctly back in 2021.

func daysDiff(t1, t2 time.Time) (year, month, day, hour, min, sec, tenth int) {
[...]
	if day > 3276 {
		y1, m1, d1 := t1.Date()
		y2, m2, d2 := t2.Date()
		year = y2 - y1
		month = int(m2 - m1)
		day = d2 - d1
	}

^^^ this will produce negative months and days while the year is positive.