From e48c246b5872d8ac6e9db9bf6c83499a3b767351 Mon Sep 17 00:00:00 2001 From: Philipp Naderer Date: Wed, 29 Jun 2016 16:51:36 +0200 Subject: [PATCH 1/5] Adds custom date format to dates.parse() --- modules/ringo/utils/dates.js | 190 +++++++++++++++++++++------------ test/ringo/utils/dates_test.js | 9 ++ 2 files changed, 129 insertions(+), 70 deletions(-) diff --git a/modules/ringo/utils/dates.js b/modules/ringo/utils/dates.js index f22c80502..b48c603a1 100644 --- a/modules/ringo/utils/dates.js +++ b/modules/ringo/utils/dates.js @@ -92,18 +92,24 @@ export( * dates.format(y2k, "yyyy-MM-dd HH:mm:ss z", "de", "GMT-10"); */ function format(date, format, locale, timezone) { - if (!format) + if (!format) { return date.toString(); + } + if (typeof locale == "string") { locale = new java.util.Locale(locale); } + if (typeof timezone == "string") { timezone = java.util.TimeZone.getTimeZone(timezone); } - var sdf = locale ? new java.text.SimpleDateFormat(format, locale) - : new java.text.SimpleDateFormat(format); - if (timezone && timezone != sdf.getTimeZone()) + + var sdf = locale ? new java.text.SimpleDateFormat(format, locale) : new java.text.SimpleDateFormat(format); + + if (timezone && timezone != sdf.getTimeZone()) { sdf.setTimeZone(timezone); + } + return sdf.format(date); } @@ -682,11 +688,22 @@ function fromUTCDate(year, month, date, hour, minute, second, millisecond) { } /** - * Parse a string to a date. - * The date string follows the format specified for timestamps - * on the internet described in RFC 3339. + * Parse a string to a date using date and time patterns from Java's SimpleDateFormat. + * If no format is provided, the default follows RFC 3339 for timestamps on the internet described. + * + * @example // parses input string as local time: + * // Wed Jun 29 2016 12:11:10 GMT+0200 (MESZ) + * let pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS"; + * dates.parse("2016-06-29T12:11:10.001", pattern); * - * @example // Fri Jan 01 2016 01:00:00 GMT+0100 (MEZ) + * // enforces UTC on the input date string + * // Wed Jun 29 2016 14:11:10 GMT+0200 (MESZ) + * dates.parse("2016-06-29T12:11:10.001", pattern, "en", "UTC"); + * + * // accepting month names in German + * dates.parse("29. Juni 2016", "dd. MMM yyyy", "de", "UTC"); + * + * // Fri Jan 01 2016 01:00:00 GMT+0100 (MEZ) * dates.parse("2016"); * * // Sat Aug 06 2016 02:00:00 GMT+0200 (MESZ) @@ -699,85 +716,118 @@ function fromUTCDate(year, month, date, hour, minute, second, millisecond) { * dates.parse("2016-08-06T16:04:30-06"); * * @param {String} str The date string. + * @param {String} format (optional) a specific format pattern for the parser + * @param {String|java.util.Locale} locale (optional) the locale as java.util.Locale object or + * an ISO-639 alpha-2 code (e.g. "en", "de") as string + * @param {String|java.util.TimeZone} timezone (optional) the timezone as java TimeZone + * object or an abbreviation such as "PST", a full name such as "America/Los_Angeles", + * or a custom ID such as "GMT-8:00". If the id is not provided, the default timezone is used. + * If the timezone id is provided but cannot be understood, the "GMT" timezone is used. * @returns {Date|NaN} a date representing the given string, or NaN for unrecognizable strings * @see RFC 3339: Date and Time on the Internet: Timestamps * @see W3C Note: Date and Time Formats * @see ES5 Date.parse() */ -function parse(str) { +function parse(str, format, locale, timezone) { var date; - // first check if the native parse method can parse it - var elapsed = Date.parse(str); - if (!isNaN(elapsed)) { - date = new Date(elapsed); + // if a format is provided, use java.text.SimpleDateFormat + if (typeof format === "string") { + if (typeof locale === "string") { + locale = new java.util.Locale(locale); + } + + if (typeof timezone === "string") { + timezone = java.util.TimeZone.getTimeZone(timezone); + } + + var sdf = locale ? new java.text.SimpleDateFormat(format, locale) : new java.text.SimpleDateFormat(format); + sdf.setLenient(false); + + if (timezone && timezone != sdf.getTimeZone()) { + sdf.setTimeZone(timezone); + } + + try { + var javaDate = sdf.parse(str); + date = javaDate != null ? new Date(javaDate.getTime()) : NaN; + } catch (e if e.javaException instanceof java.text.ParseException) { + date = NaN; + } } else { - var match = str.match(/^(?:(\d{4})(?:-(\d{2})(?:-(\d{2}))?)?)?(?:T(\d{1,2}):(\d{2})(?::(\d{2}(?:\.\d+)?))?(Z|(?:[+-]\d{1,2}(?::(\d{2}))?))?)?$/); - var date; - if (match && (match[1] || match[7])) { // must have at least year or time - var year = parseInt(match[1], 10) || 0; - var month = (parseInt(match[2], 10) - 1) || 0; - var day = parseInt(match[3], 10) || 1; - - date = new Date(Date.UTC(year, month, day)); - - // Check if the given date is valid - if (date.getUTCMonth() != month || date.getUTCDate() != day) { - return NaN; - } + // no date format provided, fall back to RFC 3339 + // first check if the native parse method can parse it + var elapsed = Date.parse(str); + if (!isNaN(elapsed)) { + date = new Date(elapsed); + } else { + var match = str.match(/^(?:(\d{4})(?:-(\d{2})(?:-(\d{2}))?)?)?(?:T(\d{1,2}):(\d{2})(?::(\d{2}(?:\.\d+)?))?(Z|(?:[+-]\d{1,2}(?::(\d{2}))?))?)?$/); + var date; + if (match && (match[1] || match[7])) { // must have at least year or time + var year = parseInt(match[1], 10) || 0; + var month = (parseInt(match[2], 10) - 1) || 0; + var day = parseInt(match[3], 10) || 1; + + date = new Date(Date.UTC(year, month, day)); + + // Check if the given date is valid + if (date.getUTCMonth() != month || date.getUTCDate() != day) { + return NaN; + } - // optional time - if (match[4] !== undefined) { - var type = match[7]; - var hours = parseInt(match[4], 10); - var minutes = parseInt(match[5], 10); - var secFrac = parseFloat(match[6]) || 0; - var seconds = secFrac | 0; - var milliseconds = Math.round(1000 * (secFrac - seconds)); - - // Checks if the time string is a valid time. - var validTimeValues = function(hours, minutes, seconds) { - if (hours === 24) { - if (minutes !== 0 || seconds !== 0 || milliseconds !== 0) { + // optional time + if (match[4] !== undefined) { + var type = match[7]; + var hours = parseInt(match[4], 10); + var minutes = parseInt(match[5], 10); + var secFrac = parseFloat(match[6]) || 0; + var seconds = secFrac | 0; + var milliseconds = Math.round(1000 * (secFrac - seconds)); + + // Checks if the time string is a valid time. + var validTimeValues = function (hours, minutes, seconds) { + if (hours === 24) { + if (minutes !== 0 || seconds !== 0 || milliseconds !== 0) { + return false; + } + } else { return false; } - } else { - return false; - } - return true; - }; - - // Use UTC or local time - if (type !== undefined) { - date.setUTCHours(hours, minutes, seconds, milliseconds); - if (date.getUTCHours() != hours || date.getUTCMinutes() != minutes || date.getUTCSeconds() != seconds) { - if(!validTimeValues(hours, minutes, seconds, milliseconds)) { - return NaN; + return true; + }; + + // Use UTC or local time + if (type !== undefined) { + date.setUTCHours(hours, minutes, seconds, milliseconds); + if (date.getUTCHours() != hours || date.getUTCMinutes() != minutes || date.getUTCSeconds() != seconds) { + if (!validTimeValues(hours, minutes, seconds, milliseconds)) { + return NaN; + } } - } - - // Check offset - if (type !== "Z") { - var hoursOffset = parseInt(type, 10); - var minutesOffset = parseInt(match[8]) || 0; - var offset = -1000 * (60 * (hoursOffset * 60) + minutesOffset * 60); - // check maximal timezone offset (24 hours) - if (Math.abs(offset) >= 86400000) { - return NaN; + // Check offset + if (type !== "Z") { + var hoursOffset = parseInt(type, 10); + var minutesOffset = parseInt(match[8]) || 0; + var offset = -1000 * (60 * (hoursOffset * 60) + minutesOffset * 60); + + // check maximal timezone offset (24 hours) + if (Math.abs(offset) >= 86400000) { + return NaN; + } + date = new Date(date.getTime() + offset); } - date = new Date(date.getTime() + offset); - } - } else { - date.setHours(hours, minutes, seconds, milliseconds); - if (date.getHours() != hours || date.getMinutes() != minutes || date.getSeconds() != seconds) { - if(!validTimeValues(hours, minutes, seconds, milliseconds)) { - return NaN; + } else { + date.setHours(hours, minutes, seconds, milliseconds); + if (date.getHours() != hours || date.getMinutes() != minutes || date.getSeconds() != seconds) { + if (!validTimeValues(hours, minutes, seconds, milliseconds)) { + return NaN; + } } } } + } else { + date = NaN; } - } else { - date = NaN; } } return date; diff --git a/test/ringo/utils/dates_test.js b/test/ringo/utils/dates_test.js index 74f0e5b24..2429ceafc 100644 --- a/test/ringo/utils/dates_test.js +++ b/test/ringo/utils/dates_test.js @@ -725,10 +725,13 @@ exports.testParse = function() { assert.isNaN(dates.parse("asdf")); assert.isFalse(dates.parse("asdf") instanceof Date, "parse should return NaN and not an invalid Date"); assert.isNaN(dates.parse("2010-")); + assert.isNaN(dates.parse("2010-", "yyyy-MM-dd")); assert.isFalse(dates.parse("2010-") instanceof Date, "parse should return NaN and not an invalid Date"); assert.isNaN(dates.parse("2010-99")); + assert.isNaN(dates.parse("2010-99", "yyyy-MM-dd")); assert.isFalse(dates.parse("2010-99") instanceof Date, "parse should return NaN and not an invalid Date"); assert.isNaN(dates.parse("2010-01-99")); + assert.isNaN(dates.parse("2010-01-99", "yyyy-MM-dd")); assert.isFalse(dates.parse("2010-01-99") instanceof Date, "parse should return NaN and not an invalid Date"); assert.isNaN(dates.parse("2010-01-01T24:59Z")); assert.isFalse(dates.parse("2010-01-01T24:59Z") instanceof Date, "parse should return NaN and not an invalid Date"); @@ -739,6 +742,12 @@ exports.testParse = function() { assert.isNaN(dates.parse("2010-01-01T23:00-25:00")); assert.isFalse(dates.parse("2010-01-01T23:00-25:00") instanceof Date, "parse should return NaN and not an invalid Date"); + // java date format tests + assert.strictEqual(dates.parse("2016-06-29T12:11:10.001", "yyyy-MM-dd'T'HH:mm:ss.SSS").getTime(), (new Date(2016,05,29,12,11,10,1)).getTime()); + assert.strictEqual(dates.parse("2016-06-29T12:11:10.001", "yyyy-MM-dd'T'HH:mm:ss.SSS", "en", "UTC").getTime(), 1467202270001); + assert.strictEqual(dates.parse("2016-06-29T12:11:10.001-01:00", "yyyy-MM-dd'T'HH:mm:ss.SSSXXX", "en", "UTC").getTime(), 1467205870001); + assert.strictEqual(dates.parse("29. Juni 2016", "dd. MMM yyyy", "de", "UTC").getTime(), 1467158400000); + // Check for not NaN // FIXME no exact checks because of local time... assert.isNotNaN(dates.parse("2010-01-01T01:01").getTime()); From 3e497b8dd5f9c861c1031a1c184fd404151a2d85 Mon Sep 17 00:00:00 2001 From: Philipp Naderer Date: Wed, 29 Jun 2016 17:47:36 +0200 Subject: [PATCH 2/5] Adds toggle for lenient parsing --- modules/ringo/utils/dates.js | 20 +++++++++++++++++--- test/ringo/utils/dates_test.js | 7 ++++++- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/modules/ringo/utils/dates.js b/modules/ringo/utils/dates.js index b48c603a1..c17e6e2bd 100644 --- a/modules/ringo/utils/dates.js +++ b/modules/ringo/utils/dates.js @@ -690,6 +690,8 @@ function fromUTCDate(year, month, date, hour, minute, second, millisecond) { /** * Parse a string to a date using date and time patterns from Java's SimpleDateFormat. * If no format is provided, the default follows RFC 3339 for timestamps on the internet described. + * The parser matches the pattern against the string with lenient parsing: Even if the input is not + * strictly in the form of the pattern, but can be parsed with heuristics, then the parse succeeds. * * @example // parses input string as local time: * // Wed Jun 29 2016 12:11:10 GMT+0200 (MESZ) @@ -723,12 +725,13 @@ function fromUTCDate(year, month, date, hour, minute, second, millisecond) { * object or an abbreviation such as "PST", a full name such as "America/Los_Angeles", * or a custom ID such as "GMT-8:00". If the id is not provided, the default timezone is used. * If the timezone id is provided but cannot be understood, the "GMT" timezone is used. + * @param {Boolean} lenient (optional) disables lenient parsing if set to false. * @returns {Date|NaN} a date representing the given string, or NaN for unrecognizable strings * @see RFC 3339: Date and Time on the Internet: Timestamps * @see W3C Note: Date and Time Formats * @see ES5 Date.parse() */ -function parse(str, format, locale, timezone) { +function parse(str, format, locale, timezone, lenient) { var date; // if a format is provided, use java.text.SimpleDateFormat if (typeof format === "string") { @@ -741,14 +744,25 @@ function parse(str, format, locale, timezone) { } var sdf = locale ? new java.text.SimpleDateFormat(format, locale) : new java.text.SimpleDateFormat(format); - sdf.setLenient(false); if (timezone && timezone != sdf.getTimeZone()) { sdf.setTimeZone(timezone); } try { - var javaDate = sdf.parse(str); + // disables lenient mode, switches to strict parsing + if (lenient === false) { + sdf.setLenient(false); + } + + var ppos = new java.text.ParsePosition(0); + var javaDate = sdf.parse(str, ppos); + + // strict parsing & error during parsing --> return NaN + if (lenient === false && ppos.getErrorIndex() >= 0) { + return NaN; + } + date = javaDate != null ? new Date(javaDate.getTime()) : NaN; } catch (e if e.javaException instanceof java.text.ParseException) { date = NaN; diff --git a/test/ringo/utils/dates_test.js b/test/ringo/utils/dates_test.js index 2429ceafc..ed3065b1a 100644 --- a/test/ringo/utils/dates_test.js +++ b/test/ringo/utils/dates_test.js @@ -731,7 +731,9 @@ exports.testParse = function() { assert.isNaN(dates.parse("2010-99", "yyyy-MM-dd")); assert.isFalse(dates.parse("2010-99") instanceof Date, "parse should return NaN and not an invalid Date"); assert.isNaN(dates.parse("2010-01-99")); - assert.isNaN(dates.parse("2010-01-99", "yyyy-MM-dd")); + assert.isNotNaN(dates.parse("2010-01-99", "yyyy-MM-dd")); + assert.isNotNaN(dates.parse("2010-01-99", "yyyy-MM-dd", null, null, true)); + assert.isNaN(dates.parse("2010-01-99", "yyyy-MM-dd", null, null, false)); assert.isFalse(dates.parse("2010-01-99") instanceof Date, "parse should return NaN and not an invalid Date"); assert.isNaN(dates.parse("2010-01-01T24:59Z")); assert.isFalse(dates.parse("2010-01-01T24:59Z") instanceof Date, "parse should return NaN and not an invalid Date"); @@ -748,6 +750,9 @@ exports.testParse = function() { assert.strictEqual(dates.parse("2016-06-29T12:11:10.001-01:00", "yyyy-MM-dd'T'HH:mm:ss.SSSXXX", "en", "UTC").getTime(), 1467205870001); assert.strictEqual(dates.parse("29. Juni 2016", "dd. MMM yyyy", "de", "UTC").getTime(), 1467158400000); + // this might be a JDK bug?! Time zone given, but not in pattern --> should return NaN, but doesn't + assert.isNotNaN(dates.parse("2016-06-30T12:11:10.001-24:00", "yyyy-MM-dd'T'HH:mm:ss.SSS", "en", "UTC", false)); + // Check for not NaN // FIXME no exact checks because of local time... assert.isNotNaN(dates.parse("2010-01-01T01:01").getTime()); From bfa95ed8469ce321c2f4bd6c7e80332f71f14102 Mon Sep 17 00:00:00 2001 From: Philipp Naderer Date: Wed, 29 Jun 2016 17:49:15 +0200 Subject: [PATCH 3/5] Adds strings.isDate() check --- modules/ringo/utils/strings.js | 52 ++++++++++++++++++++++++-------- test/ringo/utils/strings_test.js | 19 ++++++++++++ 2 files changed, 58 insertions(+), 13 deletions(-) diff --git a/modules/ringo/utils/strings.js b/modules/ringo/utils/strings.js index ba921f95f..2c08fba21 100644 --- a/modules/ringo/utils/strings.js +++ b/modules/ringo/utils/strings.js @@ -14,20 +14,20 @@ * $Date: 2007-12-13 13:21:48 +0100 (Don, 13 Dez 2007) $ */ -var ANUMPATTERN = /[^a-zA-Z0-9]/; -var APATTERN = /[^a-zA-Z]/; -var NUMPATTERN = /[^0-9]/; -var FILEPATTERN = /[^a-zA-Z0-9-_\. ]/; -var HEXPATTERN = /[^a-fA-F0-9]/; +const ANUMPATTERN = /[^a-zA-Z0-9]/; +const APATTERN = /[^a-zA-Z]/; +const NUMPATTERN = /[^0-9]/; +const FILEPATTERN = /[^a-zA-Z0-9-_\. ]/; +const HEXPATTERN = /[^a-fA-F0-9]/; // Email RegExp contributed by Scott Gonzalez (http://projects.scottsplayground.com/email_address_validation/) // licensed unter MIT license - http://www.opensource.org/licenses/mit-license.php -var EMAILPATTERN = /^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?$/i; +const EMAILPATTERN = /^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?$/i; // URL RegExp contributed by Diego Perini // licensed unter MIT license - https://gist.github.com/dperini/729294 // Copyright (c) 2010-2013 Diego Perini (http://www.iport.it) -var URLPATTERN = java.util.regex.Pattern.compile("^" + +const URLPATTERN = java.util.regex.Pattern.compile("^" + // protocol identifier "(?:(?:https?|ftp)://)" + // user:pass authentication @@ -62,11 +62,12 @@ var URLPATTERN = java.util.regex.Pattern.compile("^" + // Copyright (c) 2014 Chris O'Hara cohara87@gmail.com // licensed unter MIT license - https://github.com/chriso/validator.js/blob/master/LICENSE -var INT = /^(?:[-+]?(?:0|[1-9][0-9]*))$/; -var FLOAT = /^(?:[-+]?(?:[0-9]*))(?:\.[0-9]*)?(?:[eE][\+\-]?(?:[0-9]+))?$/; +const INT = /^(?:[-+]?(?:0|[1-9][0-9]*))$/; +const FLOAT = /^(?:[-+]?(?:[0-9]*))(?:\.[0-9]*)?(?:[eE][\+\-]?(?:[0-9]+))?$/; -var {Binary, ByteArray, ByteString} = require('binary'); -var base64; +const {Binary, ByteArray, ByteString} = require('binary'); +const base64 = require('ringo/base64'); +const dates = require('ringo/utils/dates'); /** * @fileoverview Adds useful methods to the JavaScript String type. @@ -115,7 +116,8 @@ export('isDateFormat', 'isUpperCase', 'isLowerCase', 'isInt', - 'isFloat'); + 'isFloat', + 'isDate'); /** * Checks if a date format pattern is correct and a valid string to create a @@ -625,7 +627,6 @@ function count(string, pattern) { * @example strings.b64encode("foob"); // --> "Zm9vYg==" */ function b64encode(string, encoding) { - if (!base64) base64 = require('ringo/base64'); return base64.encode(string, encoding); } @@ -964,3 +965,28 @@ function isInt(string) { function isFloat(string) { return string !== '' && FLOAT.test(string) && !INT.test(string); }; + +/** + * Returns true if the string is matching a date format. + * By default the parser matches the pattern against the string with lenient parsing: Even if the input + * is not strictly in the form of the pattern, but can be parsed with heuristics, then the parse succeeds. + * For details on the format pattern, see + * + * java.text.SimpleDateFormat + * . + * @param {String} string + * @param {String} format date format pattern + * @param {String|java.util.Locale} locale (optional) the locale as java Locale object or + * lowercase two-letter ISO-639 code (e.g. "en") + * @param {String|java.util.TimeZone} timezone (optional) the timezone as java TimeZone + * object or an abbreviation such as "PST", a full name such as "America/Los_Angeles", + * or a custom ID such as "GMT-8:00". If the id is not provided, the default timezone + * is used. If the timezone id is provided but cannot be understood, the "GMT" timezone + * is used. + * @param {Boolean} lenient (optional) disables lenient parsing if set to false. + * @returns {Boolean} true if valid date string, false otherwise + * @example FIXME + */ +function isDate(string, format, locale, timezone, lenient) { + return !isNaN(dates.parse(string, format, locale, timezone, lenient)); +}; diff --git a/test/ringo/utils/strings_test.js b/test/ringo/utils/strings_test.js index 910ddd413..98d4f993f 100644 --- a/test/ringo/utils/strings_test.js +++ b/test/ringo/utils/strings_test.js @@ -435,6 +435,25 @@ exports.testY64decode = function() { assert.strictEqual("????", strings.y64decode("Pz8_Pw--", "UTF-8")); }; +exports.testIsDate = function() { + assert.isTrue(strings.isDate("1.1.2016", "d.M.yyyy")); + assert.isTrue(strings.isDate("1.1.16", "d.M.yy")); + assert.isTrue(strings.isDate("01.01.2016", "dd.MM.yyyy")); + assert.isTrue(strings.isDate("10.10.2016", "d.M.yyyy")); + assert.isTrue(strings.isDate("10.10.16", "d.M.yy")); + assert.isTrue(strings.isDate("29. Juni 2016", "dd. MMM yyyy", "de", "UTC")); + assert.isTrue(strings.isDate("2016-06-29T12:11:10.001Zasdfasdf", "yyyy-MM-dd'T'HH:mm:ss.SSS", "en", "UTC")); + assert.isTrue(strings.isDate("31.09.16", "d.M.yy")); + assert.isTrue(strings.isDate("32.10.16", "d.M.yy")); + assert.isTrue(strings.isDate("1.13.16", "d.M.yy")); + + assert.isFalse(strings.isDate("31.09.16", "d.M.yy", null, null, false)); + assert.isFalse(strings.isDate("a.b.c", "d.M.yy")); + assert.isFalse(strings.isDate("32.10.16", "d.M.yy", null, null, false)); + assert.isFalse(strings.isDate("1.13.16", "d.M.yy", null, null, false)); + assert.isFalse(strings.isDate("29. June 2016", "dd. MMM yyyy", "de", "UTC")); +}; + if (require.main === module) { require('system').exit(require("test").run(module.id)); } \ No newline at end of file From 9e8488699af7fb335ab640ad327f787dd86f7e51 Mon Sep 17 00:00:00 2001 From: Philipp Naderer Date: Thu, 30 Jun 2016 09:45:01 +0200 Subject: [PATCH 4/5] Removed unnecessary import of binary module --- modules/ringo/utils/strings.js | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/ringo/utils/strings.js b/modules/ringo/utils/strings.js index 2c08fba21..b83cd1348 100644 --- a/modules/ringo/utils/strings.js +++ b/modules/ringo/utils/strings.js @@ -65,7 +65,6 @@ const URLPATTERN = java.util.regex.Pattern.compile("^" + const INT = /^(?:[-+]?(?:0|[1-9][0-9]*))$/; const FLOAT = /^(?:[-+]?(?:[0-9]*))(?:\.[0-9]*)?(?:[eE][\+\-]?(?:[0-9]+))?$/; -const {Binary, ByteArray, ByteString} = require('binary'); const base64 = require('ringo/base64'); const dates = require('ringo/utils/dates'); From 68b49cfec1d5c44f5cb642bb718eb001a2a0dcf1 Mon Sep 17 00:00:00 2001 From: Philipp Naderer Date: Thu, 30 Jun 2016 09:52:40 +0200 Subject: [PATCH 5/5] Improved docs and added example --- modules/ringo/utils/dates.js | 2 +- modules/ringo/utils/strings.js | 13 +++++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/modules/ringo/utils/dates.js b/modules/ringo/utils/dates.js index c17e6e2bd..467aa3572 100644 --- a/modules/ringo/utils/dates.js +++ b/modules/ringo/utils/dates.js @@ -689,7 +689,7 @@ function fromUTCDate(year, month, date, hour, minute, second, millisecond) { /** * Parse a string to a date using date and time patterns from Java's SimpleDateFormat. - * If no format is provided, the default follows RFC 3339 for timestamps on the internet described. + * If no format is provided, the default follows RFC 3339 for timestamps on the internet. * The parser matches the pattern against the string with lenient parsing: Even if the input is not * strictly in the form of the pattern, but can be parsed with heuristics, then the parse succeeds. * diff --git a/modules/ringo/utils/strings.js b/modules/ringo/utils/strings.js index b83cd1348..13fe02310 100644 --- a/modules/ringo/utils/strings.js +++ b/modules/ringo/utils/strings.js @@ -973,8 +973,9 @@ function isFloat(string) { * * java.text.SimpleDateFormat * . + * If no format is provided, the check uses a patter matching RFC 3339 (timestamps on the internet). * @param {String} string - * @param {String} format date format pattern + * @param {String} format (optional) date format pattern * @param {String|java.util.Locale} locale (optional) the locale as java Locale object or * lowercase two-letter ISO-639 code (e.g. "en") * @param {String|java.util.TimeZone} timezone (optional) the timezone as java TimeZone @@ -984,7 +985,15 @@ function isFloat(string) { * is used. * @param {Boolean} lenient (optional) disables lenient parsing if set to false. * @returns {Boolean} true if valid date string, false otherwise - * @example FIXME + * @example // true + * strings.isDate("2016"); + * strings.isDate("01-01-2016", "MM-dd-yyyy"); + * + * // true, since lenient parsing + * strings.isDate("20-40-2016", "MM-dd-yyyy"); + * + * // false, since strict parsing with lenient=false + * strings.isDate("20-40-2016", "MM-dd-yyyy", "en", "UTC", false); */ function isDate(string, format, locale, timezone, lenient) { return !isNaN(dates.parse(string, format, locale, timezone, lenient));