(function (global, factory) { 'use strict'; /* eslint-disable no-undef */ if (typeof exports === 'object' && typeof module !== 'undefined') { module.exports = factory(); } else if (typeof define === 'function' && define.amd) { define(factory); } else { global.saIdParser = factory(); } /* eslint-enable */ }(this, function () { 'use strict'; /** * Parsing result for a valid South African ID number. * * @typedef {Object} ValidIDParseResult * @property {boolean} isValid - true * @property {Date} dateOfBirth - The date of birth from the ID number. * @property {boolean} isMale - The sex from the ID number - true if male, false if female. * @property {boolean} isFemale - The sex from the ID number - true if female, false if male. * @property {boolean} isSouthAfricanCitizen - Citizenship status from the ID * number, true if it indicates South African citizenship. */ /** * Parsing result for a invalid South African ID number. * * @typedef {Object} InvalidIDParseResult * @property {boolean} isValid - false */ return { /** * Validates and ID number and parses out all information from it. * * This is a combination of the other parsing and validation functions, so * refer to their documentation for any details. * * @function * @param {string} idNumber - The ID number to be parsed. * @return {ValidIDParseResult|InvalidIDParseResult} An object with all of * the parsing results. If the ID is invalid, the result is an object with * just an `isValid` property set to false. * * @example * var saIdParser = require('south-african-id-parser'); * var validIdNumber = '9001049818080'; * * var info = saIdParser.parse(validIdNumber); * // info === { * // isValid: true, * // dateOfBirth: new Date(1990, 0, 4), * // isMale: true, * // isFemale: false, * // isSouthAfricanCitizen: true * // } * * var invalidIdNumber = '1234567'; * info = saIdParser.parse(invalidIdNumber); * // info === { * // isValid: false * // } */ parse: function(idNumber) { var isValid = validate(idNumber); if (!isValid) { return { isValid: false }; } return { isValid: isValid, dateOfBirth: parseDateOfBirth(idNumber), isMale: parseIsMale(idNumber), isFemale: parseIsFemale(idNumber), isSouthAfricanCitizen: parseIsSouthAfricanCitizen(idNumber) }; }, /** * Validates an ID number. * * This includes making sure that it follows the expected 13 digit pattern, * checking that the control digit is correct, and checking that the date of * birth is a valid date. * * @function * @param {string} idNumber - The ID number to be validated. * @return {boolean} True if the ID number is a valid South African ID number. * * @example * var saIdParser = require('south-african-id-parser'); * var validIdNumber = '9001049818080'; * var isValid = saIdParser.validate(validIdNumber); * * // valid === true */ validate: function(idNumber) { return validate(idNumber); }, /** * Parses the date of birth out of an ID number. * * Minimal validation of the ID number is performed, requiring only that * it's 13 digits long. * * The date of birth included in the ID number has a two digit year. For * example, 90 instead of 1990. This is converted to a full date by * comparing the date of birth to the current date, and choosing the century * that gives the person the lowest age, while still putting their age in * the past. * * For example, assuming that the current date is 10 December 2015. If the * date of birth parsed is 10 December 15, it will be interpreted as 10 * December 2015. If, on the other hand, the date of birth is parsed as 11 * December 15, that will be interpreted as 10 December 1915. * * The date will be in the local timezone, with the time portion set to * midnight. * * @function * @param {string} idNumber - The ID number to be parsed. * @return {?Date} The date of birth from the ID number, or undefined if the * ID number is not formatted correctly or does not have a valid date of * birth. * * @example * var saIdParser = require('south-african-id-parser'); * var validIdNumber = '9001049818080'; * var dateOfBirth = saIdParser.parseDateOfBirth(validIdNumber); * * // dateOfBirth === new Date(1990, 0, 4) */ parseDateOfBirth: function(idNumber) { return parseDateOfBirth(idNumber); }, /** * Parses the sex out of the ID number and returns true it is male. * * Minimal validation of the ID number is performed, requiring only that * it's 13 digits long. * * @function * @param {string} idNumber - The ID number to be parsed. * @return {?boolean} True if male, false if female. Returns undefined if the * ID number is not a 13 digit number. * * @example * var saIdParser = require('south-african-id-parser'); * var validIdNumber = '9001049818080'; * var isMale = saIdParser.parseIsMale(validIdNumber); * * // isMale === true */ parseIsMale: function(idNumber) { return parseIsMale(idNumber); }, /** * Parses the sex out of the ID number and returns true it is female. * * Minimal validation of the ID number is performed, requiring only that * it's 13 digits long. * * @function * @param {string} idNumber - The ID number to be parsed. * @return {?boolean} True if female, false if male. Returns undefined if the * ID number is not a 13 digit number. * @example * var saIdParser = require('south-african-id-parser'); * var validIdNumber = '9001049818080'; * var isFemale = saIdParser.parseIsFemale(validIdNumber); * * // isFemale === false */ parseIsFemale: function(idNumber) { return parseIsFemale(idNumber); }, /** * Parses the citizenship status out of an ID number and returns true if it * indicates South African citizen. * * Minimal validation of the ID number is performed, requiring only that * it's 13 digits long. * * @function * @param {string} idNumber - The ID number to be parsed. * @return {?boolean} True if the ID number belongs to a South African * citizen. Returns undefined if the ID number is not a 13 digit number. * * @example * var saIdParser = require('south-african-id-parser'); * var validIdNumber = '9001049818080'; * var isSouthAfricanCitizen = saIdParser.parseIsSouthAfricanCitizen(validIdNumber); * * // isSouthAfricanCitizen === true */ parseIsSouthAfricanCitizen: function(idNumber) { return parseIsSouthAfricanCitizen(idNumber); } }; function validate(idNumber) { if (!regexpValidate(idNumber) || !datePartValidate(idNumber) || !controlDigitValidate(idNumber)) { return false; } return true; } function regexpValidate(idNumber) { if (typeof(idNumber) !== 'string') { return false; } var regexp = /^[0-9]{13}$/; return regexp.test(idNumber); } function datePartValidate(idNumber) { var dateOfBirth = parseDateOfBirth(idNumber); return !!dateOfBirth; } function controlDigitValidate(idNumber) { var checkDigit = parseInt(idNumber[12], 10); var oddDigitsSum = 0; for (var i = 0; i < idNumber.length - 1; i+=2) { oddDigitsSum += parseInt(idNumber[i], 10); } var evenDigits = ''; for (var j = 1; j < idNumber.length - 1; j+=2) { evenDigits += idNumber[j]; } evenDigits = parseInt(evenDigits, 10); evenDigits *= 2; evenDigits = '' + evenDigits; var sumOfEvenDigits = 0; for (var k = 0; k < evenDigits.length; k++) { sumOfEvenDigits += parseInt(evenDigits[k], 10); } var total = sumOfEvenDigits + oddDigitsSum; var computedCheckDigit = 10 - (total % 10); if (computedCheckDigit === 10) { computedCheckDigit = 0; } return computedCheckDigit === checkDigit; } function parseDateOfBirth(idNumber) { if (!regexpValidate(idNumber)) { return undefined; } // get year, and assume century var currentYear = new Date().getFullYear(); var currentCentury = Math.floor(currentYear/100)*100; var yearPart = currentCentury + parseInt(idNumber.substring(0,2), 10); if (yearPart > currentYear) { yearPart -= 100; //must be last century } // In Javascript, Jan=0. In ID Numbers, Jan=1. var monthPart = parseInt(idNumber.substring(2,4), 10)-1; var dayPart = parseInt(idNumber.substring(4,6), 10); var dateOfBirth = new Date(yearPart, monthPart, dayPart); // validate that date is in a valid range by making sure that it wasn't 'corrected' during construction if (!dateOfBirth || dateOfBirth.getFullYear() !== yearPart || dateOfBirth.getMonth() !== monthPart || dateOfBirth.getDate() !== dayPart) { return undefined; } return dateOfBirth; } function parseIsMale(idNumber) { return !parseIsFemale(idNumber); } function parseIsFemale(idNumber) { if (!regexpValidate(idNumber)) { return undefined; } return parseInt(idNumber[6], 10) <= 4; } function parseIsSouthAfricanCitizen(idNumber) { if (!regexpValidate(idNumber)) { return undefined; } return parseInt(idNumber[10], 10) === 0; } }));