Repository: validatorjs/validator.js
Pull request #2118 (2022-12-06 11:01:53)

docs(CHANGELOG): remove duplicated "New Features" section
Remove duplicated section in CHANGELOG.md, which was listed twice for some reason:
```
### New Features

- [#1706](https://github.com/validatorjs/validator.js/pull/1706) `isISO4217`, currency code validator @jpaya17
```

## Checklist

- [x] PR contains only changes related; no stray files, etc.
- ~~[ ] README updated (where applicable)~~
- ~~[ ] Tests written (where applicable)~~


codecov[bot]: # [Codecov](https://codecov.io/gh/validatorjs/validator.js/pull/2118?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=validatorjs) Report
Base: **100.00**% // Head: **100.00**% // No change to project coverage :thumbsup:
> Coverage data is based on head [(`37745d2`)](https://codecov.io/gh/validatorjs/validator.js/pull/2118?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=validatorjs) compared to base [(`531dc7f`)](https://codecov.io/gh/validatorjs/validator.js/commit/531dc7f1f75613bec75c6d888b46480455e78dc7?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=validatorjs).
> Patch has no changes to coverable lines.

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff            @@
##            master     #2118   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files          104       104           
  Lines         2308      2308           
  Branches       578       578           
=========================================
  Hits          2308      2308           
```



Help us with your feedback. Take ten seconds to tell us [how you rate us](https://about.codecov.io/nps?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=validatorjs). Have a feature suggestion? [Share it here.](https://app.codecov.io/gh/feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=validatorjs)

</details>

[:umbrella: View full report at Codecov](https://codecov.io/gh/validatorjs/validator.js/pull/2118?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=validatorjs).   
:loudspeaker: Do you have feedback about the report comment? [Let us know in this issue](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=validatorjs).


Patch:
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index 1d346de..2c6af77 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -42,7 +42,7 @@ jobs:
 
     # Initializes the CodeQL tools for scanning.
     - name: Initialize CodeQL
-      uses: github/codeql-action/init@v1
+      uses: github/codeql-action/init@v2
       with:
         languages: ${{ matrix.language }}
         # If you wish to specify custom queries, you can do so here or in a config file.
@@ -53,7 +53,7 @@ jobs:
     # Autobuild attempts to build any compiled languages  (C/C++, C#, or Java).
     # If this step fails, then you should remove it and run the build manually (see below)
     - name: Autobuild
-      uses: github/codeql-action/autobuild@v1
+      uses: github/codeql-action/autobuild@v2
 
     # ℹ️ Command-line programs to run using the OS shell.
     # 📚 https://git.io/JvXDl
@@ -67,4 +67,4 @@ jobs:
     #   make release
 
     - name: Perform CodeQL Analysis
-      uses: github/codeql-action/analyze@v1
+      uses: github/codeql-action/analyze@v2
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6b063ff..edd1960 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,10 +4,6 @@
 
 - [#1706](https://github.com/validatorjs/validator.js/pull/1706) `isISO4217`, currency code validator @jpaya17
 
-### New Features
-
-- [#1706](https://github.com/validatorjs/validator.js/pull/1706) `isISO4217`, currency code validator @jpaya17
-
 ### Fixes and Enhancements
 
 - [#1647](https://github.com/validatorjs/validator.js/pull/1647) `isFQDN`: add `allow_wildcard` option @fasenderos
diff --git a/README.md b/README.md
index 9372230..a65a5f1 100644
--- a/README.md
+++ b/README.md
@@ -121,7 +121,7 @@ Validator                               | Description
 **isHexColor(str)**                     | check if the string is a hexadecimal color.
 **isHSL(str)**                          | check if the string is an HSL (hue, saturation, lightness, optional alpha) color based on [CSS Colors Level 4 specification](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value).<br/><br/>Comma-separated format supported. Space-separated format supported with the exception of a few edge cases (ex: `hsl(200grad+.1%62%/1)`).
 **isIBAN(str)**                         | check if a string is a IBAN (International Bank Account Number).
-**isIdentityCard(str [, locale])**      | check if the string is a valid identity card code.<br/><br/>`locale` is one of `['LK', 'PL', 'ES', 'FI', 'IN', 'IT', 'IR', 'MZ', 'NO', 'TH', 'zh-TW', 'he-IL', 'ar-LY', 'ar-TN', 'zh-CN']` OR `'any'`. If 'any' is used, function will check if any of the locals match.<br/><br/>Defaults to 'any'.
+**isIdentityCard(str [, locale])**      | check if the string is a valid identity card code.<br/><br/>`locale` is one of `['LK', 'PL', 'ES', 'FI', 'IN', 'IT', 'IR', 'MZ', 'NO', 'TH', 'zh-TW', 'he-IL', 'ar-LY', 'ar-TN', 'zh-CN', 'zh-HK']` OR `'any'`. If 'any' is used, function will check if any of the locals match.<br/><br/>Defaults to 'any'.
 **isIMEI(str [, options]))**                         | check if the string is a valid IMEI number. Imei should be of format `###############` or `##-######-######-#`.<br/><br/>`options` is an object which can contain the keys `allow_hyphens`. Defaults to first format . If allow_hyphens is set to true, the validator will validate the second format.
 **isIn(str, values)**                   | check if the string is in a array of allowed values.
 **isInt(str [, options])**              | check if the string is an integer.<br/><br/>`options` is an object which can contain the keys `min` and/or `max` to check the integer is within boundaries (e.g. `{ min: 10, max: 99 }`). `options` can also contain the key `allow_leading_zeroes`, which when set to false will disallow integer values with leading zeroes (e.g. `{ allow_leading_zeroes: false }`). Finally, `options` can contain the keys `gt` and/or `lt` which will enforce integers being greater than or less than, respectively, the value provided (e.g. `{gt: 1, lt: 4}` for a number between 1 and 4).
@@ -148,7 +148,7 @@ Validator                               | Description
 **isMagnetURI(str)**                      | check if the string is a [magnet uri format](https://en.wikipedia.org/wiki/Magnet_URI_scheme).
 **isMD5(str)**                          | check if the string is a MD5 hash.<br/><br/>Please note that you can also use the `isHash(str, 'md5')` function. Keep in mind that MD5 has some collision weaknesses compared to other algorithms (e.g., SHA).
 **isMimeType(str)**                     | check if the string matches to a valid [MIME type](https://en.wikipedia.org/wiki/Media_type) format
-**isMobilePhone(str [, locale [, options]])**          | check if the string is a mobile phone number,<br/><br/>(locale is either an array of locales (e.g `['sk-SK', 'sr-RS']`) OR one of `['am-Am', 'ar-AE', 'ar-BH', 'ar-DZ', 'ar-EG', 'ar-EH' , 'ar-IQ', ar-JO', 'ar-KW', 'ar-PS', 'ar-SA', 'ar-SY', 'ar-TN', 'ar-YE' , 'az-AZ', 'az-LY', 'az-LB', 'bs-BA', 'be-BY', 'bg-BG', 'bn-BD', 'ca-AD', 'cs-CZ', 'da-DK', 'de-DE', 'de-AT', 'de-CH', 'de-LU', 'dv-MV', 'el-GR', 'el-CY' ,'en-AU', 'en-AG','en-AI', 'en-BM', 'en-BW', 'en-BS', 'en-CA', 'en-GB', 'en-GG', 'en-GH', 'en-GY', 'en-HK', 'en-JM', 'en-MO', 'en-IE', 'en-IN', 'en-LS', 'en-KE', 'en-KI','en-KN', 'en-MT', 'en-MU', 'en-NG', 'es-NI' , 'en-NZ', 'en-PG', 'en-PK', 'en-PH', 'en-RW', 'en-SG', 'en-SL', 'en-UG', 'en-US', 'en-TZ', 'en-ZA', 'en-ZM', 'en-ZW', 'es-AR', 'es-BO', 'es-CL', 'es-CO', 'es-CR', 'es-CU', 'es-DO', 'es-HN', 'es-PE', 'es-EC', 'es-ES', 'es-MX', 'es-PA', 'es-PY', 'es-SV', 'es-UY', 'es-VE', 'et-EE', 'fa-IR', 'fa-AF', 'fi-FI', 'fj-FJ', 'fo-FO', 'fr-BE', 'fr-BF','fr-BJ', 'fr-FR', 'fr-GF', 'fr-GP', 'fr-MQ', 'fr-PF', 'fr-RE', 'ga-IE', 'he-IL', 'hu-HU', 'id-ID', 'ir-IR' , 'it-IT', 'it-SM', 'ja-JP', 'ka-GE', 'kk-KZ', 'kl-GL', 'ko-KR', 'ky-KG', 'lt-LT', 'mg-MG', 'ms-MY', 'mn-MN', 'my-MM' , 'mz-MZ', nb-NO', 'ne-NP', 'nl-BE', 'nl-NL','nl-AW' 'nn-NO', 'pl-PL', 'pt-BR', 'pt-PT', 'pt-AO', 'ro-RO', 'ru-RU', 'si-LK' 'sl-SI', 'sk-SK', 'sq-AL', 'sr-RS', 'sv-SE', 'tg-TJ', 'th-TH', 'tk-TM', 'tr-TR', 'uk-UA', 'uz-UZ', 'vi-VN', 'zh-CN', 'zh-HK', 'zh-MO', 'zh-TW', 'dz-BT']` OR defaults to 'any'. If 'any' or a falsey value is used, function will check if any of the locales match).<br/><br/>`options` is an optional object that can be supplied with the following keys: `strictMode`, if this is set to `true`, the mobile phone number must be supplied with the country code and therefore must start with `+`. Locale list is `validator.isMobilePhoneLocales`.
+**isMobilePhone(str [, locale [, options]])**          | check if the string is a mobile phone number,<br/><br/>(locale is either an array of locales (e.g `['sk-SK', 'sr-RS']`) OR one of `['am-Am', 'ar-AE', 'ar-BH', 'ar-DZ', 'ar-EG', 'ar-EH' , 'ar-IQ', ar-JO', 'ar-KW', 'ar-PS', 'ar-SA', 'ar-SY', 'ar-TN', 'ar-YE' , 'az-AZ', 'az-LY', 'az-LB', 'bs-BA', 'be-BY', 'bg-BG', 'bn-BD', 'ca-AD', 'cs-CZ', 'da-DK', 'de-DE', 'de-AT', 'de-CH', 'de-LU', 'dv-MV', 'el-GR', 'el-CY' ,'en-AU', 'en-AG','en-AI', 'en-BM', 'en-BW', 'en-BS', 'en-CA', 'en-GB', 'en-GG', 'en-GH', 'en-GY', 'en-HK', 'en-JM', 'en-MO', 'en-IE', 'en-IN', 'en-LS', 'en-KE', 'en-KI','en-KN', 'en-MT', 'en-MU', 'en-NG', 'es-NI' , 'en-NZ', 'en-PG', 'en-PK', 'en-PH', 'en-RW', 'en-SG', 'en-SL', 'en-UG', 'en-US', 'en-TZ', 'en-ZA', 'en-ZM', 'en-ZW', 'es-AR', 'es-BO', 'es-CL', 'es-CO', 'es-CR', 'es-CU', 'es-DO', 'es-HN', 'es-PE', 'es-EC', 'es-ES', 'es-MX', 'es-PA', 'es-PY', 'es-SV', 'es-UY', 'es-VE', 'et-EE', 'fa-IR', 'fa-AF', 'fi-FI', 'fj-FJ', 'fo-FO', 'fr-BE', 'fr-BF','fr-BJ', 'fr-FR', 'fr-GF', 'fr-GP', 'fr-MQ', 'fr-PF', 'fr-RE', 'ga-IE', 'he-IL', 'hu-HU', 'id-ID', 'ir-IR' , 'it-IT', 'it-SM', 'ja-JP', 'ka-GE', 'kk-KZ', 'kl-GL', 'ko-KR', 'ky-KG', 'lt-LT', 'mg-MG', 'ms-MY', 'mn-MN', 'my-MM' , 'mz-MZ', nb-NO', 'ne-NP', 'nl-BE', 'nl-NL','nl-AW' 'nn-NO', 'pl-PL', 'pt-BR', 'pt-PT', 'pt-AO', 'ro-RO', 'ru-RU', 'si-LK' 'sl-SI', 'sk-SK', 'sq-AL', 'sr-RS', 'sv-SE', 'tg-TJ', 'th-TH', 'tk-TM', 'tr-TR', 'uk-UA', 'uz-UZ', 'vi-VN', 'zh-CN', 'zh-HK', 'zh-MO', 'zh-TW', 'dz-BT','en-SS']` OR defaults to 'any'. If 'any' or a falsey value is used, function will check if any of the locales match).<br/><br/>`options` is an optional object that can be supplied with the following keys: `strictMode`, if this is set to `true`, the mobile phone number must be supplied with the country code and therefore must start with `+`. Locale list is `validator.isMobilePhoneLocales`.
 **isMongoId(str)**                      | check if the string is a valid hex-encoded representation of a [MongoDB ObjectId][mongoid].
 **isMultibyte(str)**                    | check if the string contains one or more multibyte chars.
 **isNumeric(str [, options])**                      | check if the string contains only numbers.<br/><br/>`options` is an object which defaults to `{no_symbols: false}` it also has locale as an option. If `no_symbols` is true, the validator will reject numeric strings that feature a symbol (e.g. `+`, `-`, or `.`).<br/><br/>`locale` determine the decimal separator and is one of `['ar', 'ar-AE', 'ar-BH', 'ar-DZ', 'ar-EG', 'ar-IQ', 'ar-JO', 'ar-KW', 'ar-LB', 'ar-LY', 'ar-MA', 'ar-QA', 'ar-QM', 'ar-SA', 'ar-SD', 'ar-SY', 'ar-TN', 'ar-YE', 'bg-BG', 'cs-CZ', 'da-DK', 'de-DE', 'en-AU', 'en-GB', 'en-HK', 'en-IN', 'en-NZ', 'en-US', 'en-ZA', 'en-ZM', 'es-ES', 'fr-FR', 'fr-CA', 'hu-HU', 'it-IT', 'nb-NO', 'nl-NL', 'nn-NO', 'pl-PL', 'pt-BR', 'pt-PT', 'ru-RU', 'sl-SI', 'sr-RS', 'sr-RS@latin', 'sv-SE', 'tr-TR', 'uk-UA']`.
@@ -163,6 +163,7 @@ Validator                               | Description
 **isUppercase(str)**                    | check if the string is uppercase.
 **isSlug**                              | Check if the string is of type slug. `Options` allow a single hyphen between string. e.g. [`cn-cn`, `cn-c-c`]
 **isStrongPassword(str [, options])**   | Check if a password is strong or not. Allows for custom requirements or scoring rules. If `returnScore` is true, then the function returns an integer score for the password rather than a boolean.<br/>Default options: <br/>`{ minLength: 8, minLowercase: 1, minUppercase: 1, minNumbers: 1, minSymbols: 1, returnScore: false, pointsPerUnique: 1, pointsPerRepeat: 0.5, pointsForContainingLower: 10, pointsForContainingUpper: 10, pointsForContainingNumber: 10, pointsForContainingSymbol: 10 }`
+**isTime(str [, options])**             | Check if the input is a valid time. e.g. [`23:01:59`, new Date().toLocaleTimeString()].<br/><br/> `options` is an object which can contain the keys `hourFormat` or `mode`.<br/><br/>`hourFormat` is a key and defaults to `'hour24'`.<br/><br/>`mode` is a key and defaults to `'default'`. <br/><br/>`hourFomat` can contain the values `'hour12'` or `'hour24'`, `'hour24'` will validate hours in 24 format and `'hour12'` will validate hours in 12 format. <br/><br/>`mode` can contain the values `'default'` or `'withSeconds'`, `'default'` will validate `HH:MM` format, `'withSeconds'` will validate the `HH:MM:SS` format.
 **isTaxID(str, locale)**                | Check if the given value is a valid Tax Identification Number. Default locale is `en-US`.<br/><br/>More info about exact TIN support can be found in `src/lib/isTaxID.js`<br/><br/>Supported locales: `[ 'bg-BG', 'cs-CZ', 'de-AT', 'de-DE', 'dk-DK', 'el-CY', 'el-GR', 'en-CA', 'en-GB', 'en-IE', 'en-US', 'es-ES', 'et-EE', 'fi-FI', 'fr-BE', 'fr-CA', 'fr-FR', 'fr-LU', 'hr-HR', 'hu-HU', 'it-IT', 'lb-LU', 'lt-LT', 'lv-LV' 'mt-MT', 'nl-BE', 'nl-NL', 'pl-PL', 'pt-BR', 'pt-PT', 'ro-RO', 'sk-SK', 'sl-SI', 'sv-SE' ]`
 **isURL(str [, options])**              | check if the string is an URL.<br/><br/>`options` is an object which defaults to `{ protocols: ['http','https','ftp'], require_tld: true, require_protocol: false, require_host: true, require_port: false, require_valid_protocol: true, allow_underscores: false, host_whitelist: false, host_blacklist: false, allow_trailing_dot: false, allow_protocol_relative_urls: false, allow_fragments: true, allow_query_components: true, disallow_auth: false, validate_length: true }`.<br/><br/>require_protocol - if set as true isURL will return false if protocol is not present in the URL.<br/>require_valid_protocol - isURL will check if the URL's protocol is present in the protocols option.<br/>protocols - valid protocols can be modified with this option.<br/>require_host - if set as false isURL will not check if host is present in the URL.<br/>require_port - if set as true isURL will check if port is present in the URL.<br/>allow_protocol_relative_urls - if set as true protocol relative URLs will be allowed.<br/>allow_fragments - if set as false isURL will return false if fragments are present.<br/>allow_query_components - if set as false isURL will return false if query components are present.<br/>validate_length - if set as false isURL will skip string length validation (2083 characters is IE max URL length).
 **isUUID(str [, version])**             | check if the string is a UUID (version 1, 2, 3, 4 or 5).
diff --git a/package.json b/package.json
index 7d50520..1376874 100644
--- a/package.json
+++ b/package.json
@@ -66,7 +66,7 @@
     "build:node": "babel src -d .",
     "build": "run-p build:*",
     "pretest": "npm run build && npm run lint",
-    "test": "nyc --reporter=cobertura --reporter=text-summary mocha --require @babel/register --reporter dot"
+    "test": "nyc --reporter=cobertura --reporter=text-summary mocha --require @babel/register --reporter dot --recursive"
   },
   "engines": {
     "node": ">= 0.10"
diff --git a/src/index.js b/src/index.js
index 42e1e8b..f45eead 100644
--- a/src/index.js
+++ b/src/index.js
@@ -13,6 +13,7 @@ import isIP from './lib/isIP';
 import isIPRange from './lib/isIPRange';
 import isFQDN from './lib/isFQDN';
 import isDate from './lib/isDate';
+import isTime from './lib/isTime';
 
 import isBoolean from './lib/isBoolean';
 import isLocale from './lib/isLocale';
@@ -226,6 +227,7 @@ const validator = {
   isStrongPassword,
   isTaxID,
   isDate,
+  isTime,
   isLicensePlate,
   isVAT,
   ibanLocales,
diff --git a/src/lib/isBtcAddress.js b/src/lib/isBtcAddress.js
index 2dfd046..08f12f4 100644
--- a/src/lib/isBtcAddress.js
+++ b/src/lib/isBtcAddress.js
@@ -1,14 +1,9 @@
 import assertString from './util/assertString';
 
-// supports Bech32 addresses
 const bech32 = /^(bc1)[a-z0-9]{25,39}$/;
 const base58 = /^(1|3)[A-HJ-NP-Za-km-z1-9]{25,39}$/;
 
 export default function isBtcAddress(str) {
   assertString(str);
-  // check for bech32
-  if (str.startsWith('bc1')) {
-    return bech32.test(str);
-  }
-  return base58.test(str);
+  return bech32.test(str) || base58.test(str);
 }
diff --git a/src/lib/isDataURI.js b/src/lib/isDataURI.js
index 0275d2c..5065448 100644
--- a/src/lib/isDataURI.js
+++ b/src/lib/isDataURI.js
@@ -1,6 +1,6 @@
 import assertString from './util/assertString';
 
-const validMediaType = /^[a-z]+\/[a-z0-9\-\+\.]+$/i;
+const validMediaType = /^[a-z]+\/[a-z0-9\-\+\._]+$/i;
 
 const validAttribute = /^[a-z\-]+=[a-z0-9\-]+$/i;
 
diff --git a/src/lib/isIdentityCard.js b/src/lib/isIdentityCard.js
index d34ddae..4734b7b 100644
--- a/src/lib/isIdentityCard.js
+++ b/src/lib/isIdentityCard.js
@@ -342,6 +342,37 @@ const validators = {
     };
     return checkIdCardNo(str);
   },
+  'zh-HK': (str) => {
+    // sanitize user input
+    str = str.trim();
+
+    // HKID number starts with 1 or 2 letters, followed by 6 digits,
+    // then a checksum contained in square / round brackets or nothing
+    const regexHKID = /^[A-Z]{1,2}[0-9]{6}((\([0-9A]\))|(\[[0-9A]\])|([0-9A]))$/;
+    const regexIsDigit = /^[0-9]$/;
+
+    // convert the user input to all uppercase and apply regex
+    str = str.toUpperCase();
+    if (!regexHKID.test(str)) return false;
+    str = str.replace(/\[|\]|\(|\)/g, '');
+
+    if (str.length === 8) str = `3${str}`;
+    let checkSumVal = 0;
+    for (let i = 0; i <= 7; i++) {
+      let convertedChar;
+      if (!regexIsDigit.test(str[i])) convertedChar = (str[i].charCodeAt(0) - 55) % 11;
+      else convertedChar = str[i];
+      checkSumVal += (convertedChar * (9 - i));
+    }
+    checkSumVal %= 11;
+
+    let checkSumConverted;
+    if (checkSumVal === 0) checkSumConverted = '0';
+    else if (checkSumVal === 1) checkSumConverted = 'A';
+    else checkSumConverted = String(11 - checkSumVal);
+    if (checkSumConverted === str[str.length - 1]) return true;
+    return false;
+  },
   'zh-TW': (str) => {
     const ALPHABET_CODES = {
       A: 10,
diff --git a/src/lib/isMobilePhone.js b/src/lib/isMobilePhone.js
index f9c552e..a5424ff 100644
--- a/src/lib/isMobilePhone.js
+++ b/src/lib/isMobilePhone.js
@@ -10,7 +10,7 @@ const phones = {
   'ar-EG': /^((\+?20)|0)?1[0125]\d{8}$/,
   'ar-IQ': /^(\+?964|0)?7[0-9]\d{8}$/,
   'ar-JO': /^(\+?962|0)?7[789]\d{7}$/,
-  'ar-KW': /^(\+?965)[569]\d{7}$/,
+  'ar-KW': /^(\+?965)([569]\d{7}|41\d{6})$/,
   'ar-LY': /^((\+?218)|0)?(9[1-6]\d{7}|[1-8]\d{7,9})$/,
   'ar-MA': /^(?:(?:\+|00)212|0)[5-7]\d{8}$/,
   'ar-OM': /^((\+|00)968)?(9[1-9])\d{6}$/,
@@ -31,7 +31,7 @@ const phones = {
   'de-CH': /^(\+41|0)([1-9])\d{1,9}$/,
   'de-LU': /^(\+352)?((6\d1)\d{6})$/,
   'dv-MV': /^(\+?960)?(7[2-9]|91|9[3-9])\d{7}$/,
-  'el-GR': /^(\+?30|0)?(69\d{8})$/,
+  'el-GR': /^(\+?30|0)?6(8[5-9]|9(?![26])[0-9])\d{7}$/,
   'el-CY': /^(\+?357?)?(9(9|6)\d{6})$/,
   'en-AI': /^(\+?1|0)264(?:2(35|92)|4(?:6[1-2]|76|97)|5(?:3[6-9]|8[1-4])|7(?:2(4|9)|72))\d{4}$/,
   'en-AU': /^(\+?61|0)4\d{8}$/,
@@ -48,6 +48,7 @@ const phones = {
   'en-IN': /^(\+?91|0)?[6789]\d{9}$/,
   'en-JM': /^(\+?876)?\d{7}$/,
   'en-KE': /^(\+?254|0)(7|1)\d{8}$/,
+  'en-SS': /^(\+?211|0)(9[1257])\d{7}$/,
   'en-KI': /^((\+686|686)?)?( )?((6|7)(2|3|8)[0-9]{6})$/,
   'en-KN': /^(?:\+1|1)869(?:46\d|48[89]|55[6-8]|66\d|76[02-7])\d{4}$/,
   'en-LS': /^(\+?266)(22|28|57|58|59|27|52)\d{6}$/,
@@ -76,7 +77,7 @@ const phones = {
   'es-CR': /^(\+506)?[2-8]\d{7}$/,
   'es-CU': /^(\+53|0053)?5\d{7}/,
   'es-DO': /^(\+?1)?8[024]9\d{7}$/,
-  'es-HN': /^(\+?504)?[9|8]\d{7}$/,
+  'es-HN': /^(\+?504)?[9|8|3|2]\d{7}$/,
   'es-EC': /^(\+?593|0)([2-7]|9[2-9])\d{7}$/,
   'es-ES': /^(\+?34)?[6|7]\d{8}$/,
   'es-PE': /^(\+?51)?9\d{8}$/,
diff --git a/src/lib/isPostalCode.js b/src/lib/isPostalCode.js
index cf5b50d..e621391 100644
--- a/src/lib/isPostalCode.js
+++ b/src/lib/isPostalCode.js
@@ -15,7 +15,7 @@ const patterns = {
   BE: fourDigit,
   BG: fourDigit,
   BR: /^\d{5}-\d{3}$/,
-  BY: /2[1-4]{1}\d{4}$/,
+  BY: /^2[1-4]\d{4}$/,
   CA: /^[ABCEGHJKLMNPRSTVXY]\d[ABCEGHJ-NPRSTV-Z][\s\-]?\d[ABCEGHJ-NPRSTV-Z]\d$/i,
   CH: fourDigit,
   CN: /^(0[1-7]|1[012356]|2[0-7]|3[0-6]|4[0-7]|5[1-7]|6[1-7]|7[1-5]|8[1345]|9[09])\d{4}$/,
@@ -37,7 +37,7 @@ const patterns = {
   IE: /^(?!.*(?:o))[A-Za-z]\d[\dw]\s\w{4}$/i,
   IL: /^(\d{5}|\d{7})$/,
   IN: /^((?!10|29|35|54|55|65|66|86|87|88|89)[1-9][0-9]{5})$/,
-  IR: /\b(?!(\d)\1{3})[13-9]{4}[1346-9][013-9]{5}\b/,
+  IR: /^(?!(\d)\1{3})[13-9]{4}[1346-9][013-9]{5}$/,
   IS: threeDigit,
   IT: fiveDigit,
   JP: /^\d{3}\-\d{4}$/,
diff --git a/src/lib/isStrongPassword.js b/src/lib/isStrongPassword.js
index 28bb063..5db901f 100644
--- a/src/lib/isStrongPassword.js
+++ b/src/lib/isStrongPassword.js
@@ -4,7 +4,7 @@ import assertString from './util/assertString';
 const upperCaseRegex = /^[A-Z]$/;
 const lowerCaseRegex = /^[a-z]$/;
 const numberRegex = /^[0-9]$/;
-const symbolRegex = /^[-#!$@%^&*()_+|~=`{}\[\]:";'<>?,.\/ ]$/;
+const symbolRegex = /^[-#!$@£%^&*()_+|~=`{}\[\]:";'<>?,.\/ ]$/;
 
 const defaultOptions = {
   minLength: 8,
diff --git a/src/lib/isTime.js b/src/lib/isTime.js
new file mode 100644
index 0000000..076bbfa
--- /dev/null
+++ b/src/lib/isTime.js
@@ -0,0 +1,23 @@
+import merge from './util/merge';
+
+const default_time_options = {
+  hourFormat: 'hour24',
+  mode: 'default',
+};
+
+const formats = {
+  hour24: {
+    default: /^([01]?[0-9]|2[0-3]):([0-5][0-9])$/,
+    withSeconds: /^([01]?[0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9])$/,
+  },
+  hour12: {
+    default: /^(0?[1-9]|1[0-2]):([0-5][0-9]) (A|P)M$/,
+    withSeconds: /^(0?[1-9]|1[0-2]):([0-5][0-9]):([0-5][0-9]) (A|P)M$/,
+  },
+};
+
+export default function isTime(input, options) {
+  options = merge(options, default_time_options);
+  if (typeof input !== 'string') return false;
+  return formats[options.hourFormat][options.mode].test(input);
+}


Test patch:
diff --git a/test/client-side.js b/test/clientSide.test.js
similarity index 100%
rename from test/client-side.js
rename to test/clientSide.test.js
diff --git a/test/exports.js b/test/exports.test.js
similarity index 100%
rename from test/exports.js
rename to test/exports.test.js
diff --git a/test/sanitizers.js b/test/sanitizers.test.js
similarity index 100%
rename from test/sanitizers.js
rename to test/sanitizers.test.js
diff --git a/test/testFunctions.js b/test/testFunctions.js
new file mode 100644
index 0000000..bcd7c15
--- /dev/null
+++ b/test/testFunctions.js
@@ -0,0 +1,56 @@
+import assert from 'assert';
+import { format } from 'util';
+import validator from '../src/index';
+
+export default function test(options) {
+  const args = options.args || [];
+
+  args.unshift(null);
+
+  if (options.error) {
+    options.error.forEach((error) => {
+      args[0] = error;
+
+      try {
+        assert.throws(() => validator[options.validator](...args));
+      } catch (err) {
+        const warning = format(
+          'validator.%s(%s) passed but should error',
+          options.validator, args.join(', ')
+        );
+
+        throw new Error(warning);
+      }
+    });
+  }
+
+  if (options.valid) {
+    options.valid.forEach((valid) => {
+      args[0] = valid;
+
+      if (validator[options.validator](...args) !== true) {
+        const warning = format(
+          'validator.%s(%s) failed but should have passed',
+          options.validator, args.join(', ')
+        );
+
+        throw new Error(warning);
+      }
+    });
+  }
+
+  if (options.invalid) {
+    options.invalid.forEach((invalid) => {
+      args[0] = invalid;
+
+      if (validator[options.validator](...args) !== false) {
+        const warning = format(
+          'validator.%s(%s) passed but should have failed',
+          options.validator, args.join(', ')
+        );
+
+        throw new Error(warning);
+      }
+    });
+  }
+}
diff --git a/test/util.js b/test/util.test.js
similarity index 100%
rename from test/util.js
rename to test/util.test.js
diff --git a/test/validators.js b/test/validators.test.js
similarity index 98%
rename from test/validators.js
rename to test/validators.test.js
index c0e36be..0405a7d 100644
--- a/test/validators.js
+++ b/test/validators.test.js
@@ -3,60 +3,10 @@ import fs from 'fs';
 import { format } from 'util';
 import vm from 'vm';
 import validator from '../src/index';
+import test from './testFunctions';
 
 let validator_js = fs.readFileSync(require.resolve('../validator.js')).toString();
 
-function test(options) {
-  let args = options.args || [];
-  args.unshift(null);
-  if (options.error) {
-    options.error.forEach((error) => {
-      args[0] = error;
-      try {
-        assert.throws(() => validator[options.validator](...args));
-      } catch (err) {
-        let warning = format(
-          'validator.%s(%s) passed but should error',
-          options.validator, args.join(', ')
-        );
-        throw new Error(warning);
-      }
-    });
-  }
-  if (options.valid) {
-    options.valid.forEach((valid) => {
-      args[0] = valid;
-      if (validator[options.validator](...args) !== true) {
-        let warning = format(
-          'validator.%s(%s) failed but should have passed',
-          options.validator, args.join(', ')
-        );
-        throw new Error(warning);
-      }
-    });
-  }
-  if (options.invalid) {
-    options.invalid.forEach((invalid) => {
-      args[0] = invalid;
-      if (validator[options.validator](...args) !== false) {
-        let warning = format(
-          'validator.%s(%s) passed but should have failed',
-          options.validator, args.join(', ')
-        );
-        throw new Error(warning);
-      }
-    });
-  }
-}
-
-function repeat(str, count) {
-  let result = '';
-  for (; count; count--) {
-    result += str;
-  }
-  return result;
-}
-
 describe('Validators', () => {
   it('should validate email addresses', () => {
     test({
@@ -74,9 +24,9 @@ describe('Validators', () => {
         '"foobar"@example.com',
         '"  foo  m端ller "@example.com',
         '"foo\\@bar"@example.com',
-        `${repeat('a', 64)}@${repeat('a', 63)}.com`,
-        `${repeat('a', 64)}@${repeat('a', 63)}.com`,
-        `${repeat('a', 31)}@gmail.com`,
+        `${'a'.repeat(64)}@${'a'.repeat(63)}.com`,
+        `${'a'.repeat(64)}@${'a'.repeat(63)}.com`,
+        `${'a'.repeat(31)}@gmail.com`,
         'test@gmail.com',
         'test.1@gmail.com',
         'test@1337.com',
@@ -90,10 +40,10 @@ describe('Validators', () => {
         'foo@bar.co.uk.',
         'z@co.c',
         'ｇｍａｉｌｇｍａｉｌｇｍａｉｌｇｍａｉｌｇｍａｉｌ@gmail.com',
-        `${repeat('a', 64)}@${repeat('a', 251)}.com`,
-        `${repeat('a', 65)}@${repeat('a', 250)}.com`,
-        `${repeat('a', 64)}@${repeat('a', 64)}.com`,
-        `${repeat('a', 64)}@${repeat('a', 63)}.${repeat('a', 63)}.${repeat('a', 63)}.${repeat('a', 58)}.com`,
+        `${'a'.repeat(64)}@${'a'.repeat(251)}.com`,
+        `${'a'.repeat(65)}@${'a'.repeat(250)}.com`,
+        `${'a'.repeat(64)}@${'a'.repeat(64)}.com`,
+        `${'a'.repeat(64)}@${'a'.repeat(63)}.${'a'.repeat(63)}.${'a'.repeat(63)}.${'a'.repeat(58)}.com`,
         'test1@invalid.co m',
         'test2@invalid.co m',
         'test3@invalid.co m',
@@ -128,10 +78,10 @@ describe('Validators', () => {
         'foobar@gmail.com',
         'foo.bar@gmail.com',
         'foo.bar@googlemail.com',
-        `${repeat('a', 30)}@gmail.com`,
+        `${'a'.repeat(30)}@gmail.com`,
       ],
       invalid: [
-        `${repeat('a', 31)}@gmail.com`,
+        `${'a'.repeat(31)}@gmail.com`,
         'test@gmail.com',
         'test.1@gmail.com',
         '.foobar@gmail.com',
@@ -5518,6 +5468,29 @@ describe('Validators', () => {
 
   it('should validate identity cards', () => {
     const fixtures = [
+      {
+        locale: 'zh-HK',
+        valid: [
+          'OV290326[A]',
+          'Q803337[0]',
+          'Z0977986',
+          'W520128(7)',
+          'A494866[4]',
+          'A494866(4)',
+          'Z867821A',
+          'ag293013(9)',
+          'k348609(5)',
+        ],
+        invalid: [
+          'A1234567890',
+          '98765432',
+          'O962472(9)',
+          'M4578601',
+          'X731324[8]',
+          'C503134(5)',
+          'RH265886(3)',
+        ],
+      },
       {
         locale: 'LK',
         valid: [
@@ -6582,9 +6555,11 @@ describe('Validators', () => {
           '96550000000',
           '96560000000',
           '96590000000',
+          '96541000000',
           '+96550000000',
           '+96550000220',
           '+96551111220',
+          '+96541000000',
         ],
         invalid: [
           '+96570000220',
@@ -6595,6 +6570,7 @@ describe('Validators', () => {
           '+9639626626262',
           '+963332210972',
           '0114152198',
+          '+96540000000',
         ],
       },
       {
@@ -7735,8 +7711,22 @@ describe('Validators', () => {
         locale: 'el-GR',
         valid: [
           '+306944848966',
-          '6944848966',
           '306944848966',
+          '06904567890',
+          '6944848966',
+          '6904567890',
+          '6914567890',
+          '6934567890',
+          '6944567890',
+          '6954567890',
+          '6974567890',
+          '6984567890',
+          '6994567890',
+          '6854567890',
+          '6864567890',
+          '6874567890',
+          '6884567890',
+          '6894567890',
         ],
         invalid: [
           '2102323234',
@@ -7746,6 +7736,12 @@ describe('Validators', () => {
           '68129485729',
           '6589394827',
           '298RI89572',
+          '6924567890',
+          '6964567890',
+          '6844567890',
+          '690456789',
+          '00690456789',
+          'not a number',
         ],
       },
       {
@@ -8254,6 +8250,10 @@ describe('Validators', () => {
           '+50489234567',
           '+50488987896',
           '+50497567389',
+          '+50427367389',
+          '+50422357389',
+          '+50431257389',
+          '+50430157389',
         ],
         invalid: [
           '12345',
@@ -9136,35 +9136,6 @@ describe('Validators', () => {
           '998900066506',
         ],
       },
-      {
-        locale: ['en-ZA', 'be-BY'],
-        valid: [
-          '0821231234',
-          '+27821231234',
-          '27821231234',
-          '+375241234567',
-          '+375251234567',
-          '+375291234567',
-          '+375331234567',
-          '+375441234567',
-          '375331234567',
-        ],
-        invalid: [
-          '082123',
-          '08212312345',
-          '21821231234',
-          '+21821231234',
-          '+0821231234',
-          '12345',
-          '',
-          'ASDFGJKLmZXJtZtesting123',
-          '010-38238383',
-          '+9676338855',
-          '19676338855',
-          '6676338855',
-          '+99676338855',
-        ],
-      },
       {
         locale: 'en-SL',
         valid: [
@@ -9598,6 +9569,33 @@ describe('Validators', () => {
           '00 +503 1234 5678',
         ],
       },
+      {
+        locale: 'en-SS',
+        valid: [
+          '+211928530422',
+          '+211913384561',
+          '+211972879174',
+          '+211952379334',
+          '0923346543',
+          '0950459022',
+          '0970934567',
+          '211979841238',
+          '211929843238',
+          '211959840238',
+        ],
+        invalid: [
+          '911',
+          '+211999',
+          '123456789909',
+          'South Sudan',
+          '21195 840 238',
+          '+211981234567',
+          '+211931234567',
+          '+211901234567',
+          '+211991234567',
+
+        ],
+      },
     ];
 
     let allValid = [];
@@ -11096,6 +11094,7 @@ describe('Validators', () => {
       valid: [
         'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEUAAAD///+l2Z/dAAAAM0lEQVR4nGP4/5/h/1+G/58ZDrAz3D/McH8yw83NDDeNGe4Ug9C9zwz3gVLMDA/A6P9/AFGGFyjOXZtQAAAAAElFTkSuQmCC',
         'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIBAMAAAA2IaO4AAAAFVBMVEXk5OTn5+ft7e319fX29vb5+fn///++GUmVAAAALUlEQVQIHWNICnYLZnALTgpmMGYIFWYIZTA2ZFAzTTFlSDFVMwVyQhmAwsYMAKDaBy0axX/iAAAAAElFTkSuQmCC',
+        'data:application/media_control+xml;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIBAMAAAA2IaO4AAAAFVBMVEXk5OTn5+ft7e319fX29vb5+fn///++GUmVAAAALUlEQVQIHWNICnYLZnALTgpmMGYIFWYIZTA2ZFAzTTFlSDFVMwVyQhmAwsYMAKDaBy0axX/iAAAAAElFTkSuQmCC',
         '   data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIBAMAAAA2IaO4AAAAFVBMVEXk5OTn5+ft7e319fX29vb5+fn///++GUmVAAAALUlEQVQIHWNICnYLZnALTgpmMGYIFWYIZTA2ZFAzTTFlSDFVMwVyQhmAwsYMAKDaBy0axX/iAAAAAElFTkSuQmCC   ',
         'data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22100%22%20height%3D%22100%22%3E%3Crect%20fill%3D%22%2300B1FF%22%20width%3D%22100%22%20height%3D%22100%22%2F%3E%3C%2Fsvg%3E',
         'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDAiIGhlaWdodD0iMTAwIj48cmVjdCBmaWxsPSIjMDBCMUZGIiB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIvPjwvc3ZnPg==',
@@ -11262,6 +11261,12 @@ describe('Validators', () => {
           '247710',
           '231960',
         ],
+        invalid: [
+          'test 225320',
+          '211120 test',
+          '317543',
+          '267946',
+        ],
       },
       {
         locale: 'CA',
@@ -11447,6 +11452,9 @@ describe('Validators', () => {
           '43516 6456',
           '123443516 6456',
           '891123',
+          'test 4351666456',
+          '4351666456 test',
+          'test 4351666456 test',
         ],
       },
       {
@@ -12425,6 +12433,7 @@ describe('Validators', () => {
         'mxH_+2vs&54_+H3P',
         '+&DxJ=X7-4L8jRCD',
         'etV*p%Nr6w&H%FeF',
+        '£3.ndSau_7',
       ],
       invalid: [
         '',
@@ -12627,6 +12636,113 @@ describe('Validators', () => {
       ],
     });
   });
+  it('should validate time', () => {
+    test({
+      validator: 'isTime',
+      valid: [
+        '00:00',
+        '23:59',
+        '9:00',
+      ],
+      invalid: [
+        '',
+        null,
+        undefined,
+        0,
+        '07:00 PM',
+        '23',
+        '00:60',
+        '00:',
+        '01:0 ',
+        '001:01',
+      ],
+    });
+    test({
+      validator: 'isTime',
+      args: [{ hourFormat: 'hour24', mode: 'withSeconds' }],
+      valid: [
+        '23:59:59',
+        '00:00:00',
+        '9:50:01',
+      ],
+      invalid: [
+        '',
+        null,
+        undefined,
+        23,
+        '01:00:01 PM',
+        '13:00:',
+        '00',
+        '26',
+        '00;01',
+        '0 :09',
+        '59:59:59',
+        '24:00:00',
+        '00:59:60',
+        '99:99:99',
+        '009:50:01',
+      ],
+    });
+    test({
+      validator: 'isTime',
+      args: [{ hourFormat: 'hour12' }],
+      valid: [
+        '12:59 PM',
+        '12:59 AM',
+        '01:00 PM',
+        '01:00 AM',
+        '7:00 AM',
+      ],
+      invalid: [
+        '',
+        null,
+        undefined,
+        0,
+        '12:59 MM',
+        '12:59 MA',
+        '12:59 PA',
+        '12:59 A M',
+        '13:00 PM',
+        '23',
+        '00:60',
+        '00:',
+        '9:00',
+        '01:0 ',
+        '001:01',
+        '12:59:00 PM',
+        '12:59:00 A M',
+        '12:59:00 ',
+      ],
+    });
+    test({
+      validator: 'isTime',
+      args: [{ hourFormat: 'hour12', mode: 'withSeconds' }],
+      valid: [
+        '12:59:59 PM',
+        '2:34:45 AM',
+        '7:00:00 AM',
+      ],
+      invalid: [
+        '',
+        null,
+        undefined,
+        23,
+        '01:00: 1 PM',
+        '13:00:',
+        '13:00:00 PM',
+        '00',
+        '26',
+        '00;01',
+        '0 :09',
+        '59:59:59',
+        '24:00:00',
+        '00:59:60',
+        '99:99:99',
+        '9:50:01',
+        '009:50:01',
+      ],
+    });
+  });
   it('should be valid license plate', () => {
     test({
       validator: 'isLicensePlate',