Moment: Minimal age calculation with boundry birthday values results in unexpected behavior

Created on 27 Feb 2017  ·  3Comments  ·  Source: moment/moment

Description of the Issue and Steps to Reproduce

I want to check if a person is exaclty or over 18 years old. So I used the diff() and duration() functions to calculate the age of person. I wrote some tests with boundry values to test my logic. I wanted to test if I am exactly one day younger than 18 and exactly 18. So I chose two birthdays for testing (today is 27.02.2017):

  • exactly 18: 27.02.1999
  • one day under 18: 28.02.1999

Reproduce

// today date is: 27.02.2017

// exactly age 18    
var birthday = moment("27.02.1999", "DD.MM.YYYY"),
    age = moment.duration(moment().diff(birthday)).asYears();

      console.log(age); // output: 18.00325100985672; expected: == 18

// one day under age 18
var birthday = moment("28.02.1999", "DD.MM.YYYY"),
    age = moment.duration(moment().diff(birthday)).asYears();

    console.log(age); // output: 18.00053005036735; expected: < 18

I think the calculation is correct, because of leap-years. But in terms of birthday, the person shouldn't be 18 years old. Maybe there's a other way to do it with momentjs and I'm doing it wrong. If so, it would be nice to be mentioned in documentation of momentjs.

Production Code

<form id="newsletter-form" data-parsley-validate="">
  <input id="birthday" type="text" class="form-control" required="" data-parsley-minage="18">
  <input type="submit" class="btn btn-default" value="subscribe">
</form>

<script type="text/javascript">
    $(function () {
        window.Parsley.addValidator('minage', {
            validateString: function(value, minAge) {
                var birthday = moment(value, "DD.MM.YYYY"),
                    age = moment.duration(moment().diff(birthday)).asYears();

                return (age >= minAge);
            },
            requirementType: 'integer',
            messages: {
                en: 'You must be mature.'
            }
        });
    });
</script>
<script type="text/javascript">
    $(function () {
        $('#newsletter-form').parsley()
            // field validation
            .on('form:submit', function() {
                return false; // do not submit
            });
    });
</script>

Environment

Chrome Version 56.0.2924.87 (64-bit) on Windows 7

Other information that may be helpful

  • Timezone of machine: "(UTC+01:00) Amsterdam, Berlin, Bern, Rom, Stockholm, Wien"
  • The time and date at which the code was run: "Mon Feb 27 2017 13:38:04 GMT+0100"
  • Other libraries in use: jquery-3.1.1, bootstrap-3.3.7, bootstrap-datepicker-1.6.4, parselyjs-2.6.2
console.log( (new Date()).toString()) // Mon Feb 27 2017 13:38:04 GMT+0100 (Mitteleuropäische Zeit)
console.log((new Date()).toLocaleString()) // 27.2.2017, 13:38:04
console.log( (new Date()).getTimezoneOffset()) // -60
console.log( navigator.userAgent) // Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36
console.log(moment.version) // 2.17.1

Most helpful comment

Does it work better if you just use

age = moment().diff(birthday, 'years');

All 3 comments

I think the calculation is wrong, but it's super close. Perhaps due to rounding errors?

For example:

a = moment("27.02.1999", "DD.MM.YYYY");
b = moment("28.02.1999", "DD.MM.YYYY");
c = moment("27.02.2017", "DD.MM.YYYY");
moment.duration(c.diff(a)).asYears(); // 18.00173857094944
moment.duration(c.diff(b)).asYears(); // 17.99900066394245

So, in this case, moment.duration(end.diff(start)).asYears() will return 18 around 16 hours before it should.

If you are trying to check if an age is over or equal to 18, you could compare the years, then (if 18 years apart) the months, then the days.

// pseudocode, correctness not guaranteed
function is18(start, end) {
  if (end.year() - start.year() == 18) {
    if (end.month() == start.month()) {
      return end.date() >= start.date();
    }
    return end.month() > start.month();
  }
  return end.year() - 18 > start.year();
}

Does it work better if you just use

age = moment().diff(birthday, 'years');

@mj1856

Ya, this works just fine.

// today date is: 03.03.2017

// exactly age 18    
var birthday = moment("03.03.1999", "DD.MM.YYYY"),
    age = moment().diff(birthday, 'years');

      console.log(age); // output: 18; expected: == 18

// one day under age 18
var birthday = moment("04.03.1999", "DD.MM.YYYY"),
    age = moment().diff(birthday, 'years');

    console.log(age); // output: 17; expected: < 18
Was this page helpful?
0 / 5 - 0 ratings

Related issues

RobinvanderVliet picture RobinvanderVliet  ·  3Comments

M-Zuber picture M-Zuber  ·  3Comments

Shoroh picture Shoroh  ·  3Comments

danieljsinclair picture danieljsinclair  ·  3Comments

paulyoung picture paulyoung  ·  3Comments