Skip to content

fix: correct Year constant from 360 days to 365 days - #152

Open
amitmishra11 wants to merge 2 commits into
dustin:masterfrom
amitmishra11:fix/year-constant-360-vs-365
Open

fix: correct Year constant from 360 days to 365 days#152
amitmishra11 wants to merge 2 commits into
dustin:masterfrom
amitmishra11:fix/year-constant-360-vs-365

Conversation

@amitmishra11

Copy link
Copy Markdown

What is the bug?

The `Year` constant is defined as `12 * Month`, where `Month` is 30 days.
This gives a 360-day year rather than a 365-day (Gregorian) year.

The 1.4% error compounds with duration length. For shorter spans the integer
truncation hides it, but at multi-decade scales it produces the wrong result:

Actual duration With 360-day Year With 365-day Year
720 days (~2yr) 2 years 1 year (720/365=1.97)
13322 days (~36.5yr) 37 years 36 years (13322/365=36.5)

Reported in #116. The maintainer noted that "a demonstration in a unit test
would justify a fix."

What does the fix do?

  • Changes `Year = 12 * Month` to `Year = 365 * Day`.
  • Updates the `TestCustomRelTime` expectation for `LongTime` (37 * Year):
    `37 * 365 = 13505` days, which is `13505 / 30 = 450` months (was 444).
  • Adds `TestYearLength` that pins the constant to 365 days and demonstrates
    that 13322 days (36.5 Gregorian years) correctly formats as "36 years ago"
    instead of "37 years ago".

How to reproduce the bug (before the fix)

package main

import (
    "fmt"
    "time"
    humanize "github.com/dustin/go-humanize"
)

func main() {
    now := time.Now()
    // 36.5 Gregorian years = 13322 days
    past := now.Add(-13322 * humanize.Day)
    fmt.Println(humanize.Time(past))
    // Before: "37 years ago"  (incorrect, 13322 / 360 = 37.0)
    // After:  "36 years ago"  (correct,   13322 / 365 = 36.5)
}

Fixes #116

Year was defined as 12 * Month, where Month is 30 days, giving a
360-day year. This caused the displayed year count to be inflated by
~1.4% for long durations: for example, 13322 days (36.5 Gregorian years)
was reported as "37 years ago" instead of "36 years ago".

Fix by defining Year as 365 * Day directly, matching the Gregorian
calendar. Update the downstream TestCustomRelTime expectation for LongTime
(37 * Year) which now correctly spans 37 * 365 = 13505 days
(450 months instead of 444), and add TestYearLength to pin the constant
value and demonstrate the multi-year accuracy improvement.

Fixes dustin#116
Comment thread times_test.go Outdated
// count to be inflated for long durations. For example, 10 Gregorian years
// (3650 days) would be reported as "10 years ago" under both values, but
// 36.5 Gregorian years (13322 days) would read "37 years ago" with 360-day
// years and correctly "36 years ago" with 365-day years.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment is a bit much. Descriptions of prior state don't belong in code comments, but in commit messages.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed the entire comment block. The history belongs in the commit message, not the code.

Comment thread times_test.go Outdated
func TestYearLength(t *testing.T) {
if Year != 365*Day {
t.Errorf("Year constant should be 365 days, got %v", Year)
}

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We definitely do not want a test that validates that a definition is what it was typed to be. This is a "change detector" test which only makes the code harder to maintain.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed the change-detector assertion. The behavioral test case below it is sufficient.

Comment thread times_test.go Outdated
}

now := time.Now()
// 36.5 * 365 = 13322 days — should be "36 years ago", not "37 years ago"

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Humans don't write comments like this and don't want to read them.

e.g., there are an infinite number of values that it also should not be, so listing one of them isn't helpful. Describing what it should be is the job of the equality assertion so that also doesn't need to be in a comment. The multiplication that resulted in 13322 could also be in code which might make it clearer what the value is, because 36.5 * 365 ≠ 13322

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed the inline comment. You're right that 36.5 * 365 = 13322.5, not 13322, so the comment was also wrong. The test case speaks for itself.

Comment thread times.go
Day = 24 * time.Hour
Week = 7 * Day
Month = 30 * Day
Year = 12 * Month

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Basing a year on days makes some things more accurate in longer spans (as the test shows), but it also makes some things less accurate (as another test shows).

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. Fixed by setting Month = Year / 12 so the two constants stay consistent. That makes 37 years correctly render as 444 months and keeps the month/year boundary coherent, at the cost of the month threshold shifting from 30 to ~30.4 days. Updated the affected test cases accordingly.

Comment thread times_test.go
{"6 months from now", customRelTime(now.Add(+6*Month - time.Second)), "25 weeks from now"},
{"1 year from now", customRelTime(now.Add(+365 * Day)), "12 months from now"},
{"2 years from now", customRelTime(now.Add(+2 * Year)), "24 months from now"},
{"a while from now", customRelTime(now.Add(+LongTime)), "444 months from now"},

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you notice that this answer is now incorrect? There are 12 months in a year. 37 years is 12 * 37 or 444 months. 450 overshoots by half a year.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. Changed Month = Year / 12 so the division is exact: 37 * 12 = 444. Also updated the "1 month from now" test (31 days instead of 30 to stay above the new threshold) and the "6 months from now" custom test (26 weeks instead of 25).

Set Month = Year / 12 so months and years are consistent: 37 years
correctly displays as 444 months (37*12) rather than 450. Remove the
verbose docstring from TestYearLength since prior-state descriptions
belong in commit messages, not code comments. Remove the change-detector
assertion and the inline comment. Update TestFuture and TestCustomRelTime
to match the new Month value.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Incorrect year length

2 participants