diff --git a/modules/ringo/utils/dates.js b/modules/ringo/utils/dates.js
index f22c80502..467aa3572 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,24 @@ 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.
+ * 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)
+ * let pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS";
+ * dates.parse("2016-06-29T12:11:10.001", pattern);
+ *
+ * // 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");
*
- * @example // Fri Jan 01 2016 01:00:00 GMT+0100 (MEZ)
+ * // 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 +718,130 @@ 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.
+ * @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) {
+function parse(str, format, locale, timezone, lenient) {
var date;
- // 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;
+ // if a format is provided, use java.text.SimpleDateFormat
+ if (typeof format === "string") {
+ if (typeof locale === "string") {
+ locale = new java.util.Locale(locale);
+ }
- date = new Date(Date.UTC(year, month, day));
+ 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()) {
+ sdf.setTimeZone(timezone);
+ }
- // Check if the given date is valid
- if (date.getUTCMonth() != month || date.getUTCDate() != day) {
+ try {
+ // 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;
}
- // 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) {
+ date = javaDate != null ? new Date(javaDate.getTime()) : NaN;
+ } catch (e if e.javaException instanceof java.text.ParseException) {
+ date = NaN;
+ }
+ } else {
+ // 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) {
+ 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/modules/ringo/utils/strings.js b/modules/ringo/utils/strings.js
index ba921f95f..13fe02310 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,11 @@ 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 base64 = require('ringo/base64');
+const dates = require('ringo/utils/dates');
/**
* @fileoverview Adds useful methods to the JavaScript String type.
@@ -115,7 +115,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 +626,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 +964,37 @@ 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
+ * .
+ * If no format is provided, the check uses a patter matching RFC 3339 (timestamps on the internet).
+ * @param {String} string
+ * @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
+ * 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 // 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));
+};
diff --git a/test/ringo/utils/dates_test.js b/test/ringo/utils/dates_test.js
index 74f0e5b24..ed3065b1a 100644
--- a/test/ringo/utils/dates_test.js
+++ b/test/ringo/utils/dates_test.js
@@ -725,10 +725,15 @@ 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.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");
@@ -739,6 +744,15 @@ 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);
+
+ // 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());
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