Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Minimal age calculation with boundry birthday values results in unexpected behavior #3795

Closed
4rg0n opened this issue Feb 27, 2017 · 3 comments

Comments

@4rg0n
Copy link

4rg0n commented Feb 27, 2017

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
@marwahaha
Copy link
Member

marwahaha commented Mar 3, 2017

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();
}

@mattjohnsonpint
Copy link
Contributor

Does it work better if you just use

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

@4rg0n
Copy link
Author

4rg0n commented Mar 3, 2017

@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

@4rg0n 4rg0n closed this as completed Mar 3, 2017
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

No branches or pull requests

3 participants