Last active
August 8, 2017 11:14
-
-
Save SneakyPeet/2a2ae8cc5ff03753d99594556e820e77 to your computer and use it in GitHub Desktop.
South African Id Number Validator
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
;; Validates an RSA Identity Number | |
;; compatible With Clojure and Clojurescript | |
;; Requires clj-time for clojure and cljs-time for clojurescript | |
;; Based on the validator by Evan Knowles | |
;; http://knowles.co.za/generating-south-african-id-numbers/ | |
(ns rsa-id-number | |
(:require | |
[clojure.string :as string] | |
#?(:clj [clj-time.format :as time.format] | |
:cljs [cljs-time.format :as time.format]) | |
#?(:clj [clj-time.core :as time] | |
:cljs [cljs-time.core :as time]))) | |
#?(:clj (defn- parse-int [d] (-> d String/valueOf Integer/parseInt)) | |
:cljs (defn- parse-int [d] (js/parseInt d))) | |
(defn- generate-luhn-digit | |
([input] | |
(generate-luhn-digit input 0 0)) | |
([input total l-count] | |
(if (empty? input) | |
(-> total (* 9) (mod 10)) | |
(let [multiple (-> l-count (mod 2) inc) | |
parsed-digit (->> input first parse-int) | |
digit-multiple (* multiple parsed-digit) | |
floored-multiple (-> digit-multiple (/ 10) Math/floor int) | |
multiple-mod (mod digit-multiple 10) | |
new-total (+ total (+ floored-multiple multiple-mod)) | |
next-l-count (inc l-count)] | |
(recur (rest input) new-total next-l-count))))) | |
(defn- parse-section [a v] | |
(-> v (subs a (+ a 2)) parse-int)) | |
(defn- between? [a b x] (and (>= x a) (<= x b))) | |
(def expected-date-formatter (time.format/formatter "yyMMdd")) | |
(def format-expected-date (partial time.format/unparse expected-date-formatter)) | |
(defn ->expected-date [year month day] | |
(let [current-year (-> (time/now) time/year (mod 100)) | |
prefixed-year (+ year (if (< year current-year) 2000 1900))] | |
(-> (time/date-time prefixed-year) | |
(time/plus (time/months (dec month))) | |
(time/plus (time/days (dec day))) | |
format-expected-date))) | |
(defn- validate-date-of-birth? [v] | |
(let [actual-date (subs v 0 6) | |
year (parse-section 0 v) | |
month (parse-section 2 v) | |
day (parse-section 4 v)] | |
(and (between? 1 12 month) | |
(between? 1 31 day) | |
(= actual-date (->expected-date year month day))))) | |
(defn valid? [v] | |
(boolean | |
(and | |
(string? v) | |
(re-matches #"[0-9]{13}" v) | |
(validate-date-of-birth? v) | |
(= (parse-int (last v)) (generate-luhn-digit (drop-last v)))))) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment