1
0
Fork 0

Alternative DATE_FORMAT() implementation

This commit is contained in:
CoDEmanX 2015-09-15 11:43:30 +02:00
parent 80617c8716
commit f13c2f1fce
3 changed files with 115 additions and 66 deletions

View File

@ -310,36 +310,48 @@ FOR user IN users
- *DATE_FORMAT(date, format)*: Format a date according to the given format string.
It supports the following placeholders (case-insensitive):
- %t: timestamp
- %t: timestamp, in milliseconds since midnight 1970-01-01
- %z: ISO date (0000-00-00T00:00:00.000Z)
- %w: day of week (0..6)
- %y: year (0000..9999)
- %m: month (01..12)
- %d: day (01..31)
- %h: hour (00..23)
- %i: minute (00..59)
- %s: second (00..59)
- %f: millisecond (000..999)
- %x: day of year (001..366)
- %k: ISO week date (01..53)
- %y: year (0..9999)
- %yy: year (00..99), abbreviated (last two digits)
- %yyyy: year (0000..9999), padded to length of 4
- %yyyyyy: year (-009999 .. +009999), with sign prefix and padded to length of 6
- %m: month (1..12)
- %mm: month (01..12), padded to length of 2
- %d: day (1..31)
- %dd: day (01..31), padded to length of 2
- %h: hour (0..23)
- %hh: hour (00..23), padded to length of 2
- %i: minute (0..59)
- %ii: minute (00..59), padded to length of 2
- %s: second (0..59)
- %ss: second (00..59), padded to length of 2
- %f: millisecond (0..999)
- %fff: millisecond (000..999), padded to length of 3
- %x: day of year (1..366)
- %xxx: day of year (001..366), padded to length of 3
- %k: ISO week date (1..53)
- %kk: ISO week date (01..53), padded to length of 2
- %l: leap year (0 or 1)
- %q: quarter (1..4)
- %a: days in month (28..31)
- %n: English name of month (January..December)
- %o: abbreviated English name of month (Jan..Dec)
- %e: English name of weekday (Sunday..Saturday)
- %g: abbreviated English name of weekday (Sun..Sat)
- %mmm: abbreviated English name of month (Jan..Dec)
- %mmmm: English name of month (January..December)
- %www: abbreviated English name of weekday (Sun..Sat)
- %wwww: English name of weekday (Sunday..Saturday)
- %&: special escape sequence for rare occasions
- %%: literal %
- %: ignored
Month, day, hour, minute, second, ISO week date and days in month are zero-padded
to 2 characters, millisecond and day of year to 3 characters. Year is zero-padded
to 4 characters, or 6 if year is negative (it will be 7 chars long however
because of the leading minus character, and is not suitable for comparisons
with 4-chars positive years). Timestamp is not padded.
`%yyyy` does not enforce a length of 4 for years before 0 and past 9999.
The same format as for `%yyyyyy` will used instead. `%yy` preserves the
sign for negative years and thus returns 3 characters in total.
Single `%` characters will appear as percent characters, as long as they aren't
followed by a valid placeholder letter. `%%` is only required to output a
placeholder literally.
Single `%` characters will be ignored. Use `%%` for a literal `%`. To resolve
ambiguities like in `%mmonth` (unpadded month number + the string "month")
between `%mm` + "onth" and `%m` + "month", use the escape sequence `%&`:
`%m%&month`.
Note that this is a rather costly operation and may not be suitable for large
datasets (like over 1 million dates). If possible, avoid formatting dates on
@ -351,10 +363,11 @@ FOR user IN users
Examples:
```js
DATE_FORMAT(DATE_NOW(), "%q/%y") // quarter and year (e.g. "3/2015")
DATE_FORMAT(DATE_NOW(), "%d.%m.%y %h:%i:%s") // e.g. "18.09.2015 15:30:49"
DATE_FORMAT(DATE_NOW(), "%q/%yyyy") // quarter and year (e.g. "3/2015")
DATE_FORMAT(DATE_NOW(), "%dd.%mm.%yyyy %hh:%ii:%ss.%fff") // e.g. "18.09.2015 15:30:49.374"
DATE_FORMAT("1969", "Summer of '%yy") // "Summer of '69"
DATE_FORMAT("2016", "%%l = %l") // "%l = 1" (2016 is a leap year)
DATE_FORMAT("2016-03-01", "% %x %") // "% 063 %"
DATE_FORMAT("2016-03-01", "%xxx%") // "063", trailing % ignored
```
!SECTION Working with dates and indices
@ -390,6 +403,8 @@ date according to the Gregorian calendar system, even if inappropriate. That
does not constitute a problem, unless you deal with dates prior to 1583 and
especially years before Christ. The standard allows negative years, but requires
special treatment of positive years too, if negative years are used (e.g.
`+002015-05-15` and `-000753-01-01`). This is rarely used however, and thus not
handled by AQL. Leap seconds are also ignored, just as they are in JavaScript as per
`+002015-05-15` and `-000753-01-01`). This is rarely used however, and AQL does
not use the 7-character version for years between 0 and 9999 in ISO strings.
Keep in mind that they can't be properly compared to dates outside that range.
Leap seconds are ignored, just as they are in JavaScript as per
[ECMAScript Language Specifications](http://www.ecma-international.org/ecma-262/5.1/#sec-15.9.1.1).

View File

@ -167,26 +167,39 @@ The following extra date functions are available from 2.7 on:
* `DATE_FORMAT(date, format)`: Format a date according to the given format string.
It supports the following placeholders (case-insensitive):
- %t: timestamp
- %t: timestamp, in milliseconds since midnight 1970-01-01
- %z: ISO date (0000-00-00T00:00:00.000Z)
- %w: day of week (0..6)
- %y: year (0000..9999)
- %m: month (01..12)
- %d: day (01..31)
- %h: hour (00..23)
- %i: minute (00..59)
- %s: second (00..59)
- %f: millisecond (000..999)
- %x: day of year (001..366)
- %k: ISO week date (01..53)
- %y: year (0..9999)
- %yy: year (00..99), abbreviated (last two digits)
- %yyyy: year (0000..9999), padded to length of 4
- %yyyyyy: year (-009999 .. +009999), with sign prefix and padded to length of 6
- %m: month (1..12)
- %mm: month (01..12), padded to length of 2
- %d: day (1..31)
- %dd: day (01..31), padded to length of 2
- %h: hour (0..23)
- %hh: hour (00..23), padded to length of 2
- %i: minute (0..59)
- %ii: minute (00..59), padded to length of 2
- %s: second (0..59)
- %ss: second (00..59), padded to length of 2
- %f: millisecond (0..999)
- %fff: millisecond (000..999), padded to length of 3
- %x: day of year (1..366)
- %xxx: day of year (001..366), padded to length of 3
- %k: ISO week date (1..53)
- %kk: ISO week date (01..53), padded to length of 2
- %l: leap year (0 or 1)
- %q: quarter (1..4)
- %a: days in month (28..31)
- %n: English name of month (January..December)
- %o: abbreviated English name of month (Jan..Dec)
- %e: English name of weekday (Sunday..Saturday)
- %g: abbreviated English name of weekday (Sun..Sat)
- %mmm: abbreviated English name of month (Jan..Dec)
- %mmmm: English name of month (January..December)
- %www: abbreviated English name of weekday (Sun..Sat)
- %wwww: English name of weekday (Sunday..Saturday)
- %&: special escape sequence for rare occasions
- %%: literal %
- %: ignored
!SUBSECTION RETURN DISTINCT

View File

@ -5112,7 +5112,7 @@ function AQL_DATE_COMPARE (value1, value2, unitRangeStart, unitRangeEnd) {
// birthday checking however. Probably best to leave compensation up to user query.
var date1 = MAKE_DATE([ value1 ], "DATE_COMPARE");
var date2 = MAKE_DATE([ value2 ], "DATE_COMPARE");
if (isNaN(date1) || isNaN(date2)) {
if (TYPEWEIGHT(date1) === TYPEWEIGHT_NULL || TYPEWEIGHT(date2) === TYPEWEIGHT_NULL) {
return null;
}
if (unitRangeEnd === undefined) {
@ -5124,10 +5124,12 @@ function AQL_DATE_COMPARE (value1, value2, unitRangeStart, unitRangeEnd) {
WARN("DATE_COMPARE", INTERNAL.errors.ERROR_QUERY_INVALID_DATE_VALUE);
return null;
}
if (date1.getUTCFullYear() < 0 && start !== 0) {
var yr1 = date1.getUTCFullYear();
if ((yr1 < 0 || yr1 > 9999) && start !== 0) {
start += 3;
}
if (date2.getUTCFullYear() < 0) {
var yr2 = date2.getUTCFullYear();
if (yr2 < 0 || yr2 > 9999) {
end += 3;
}
var substr1 = date1.toISOString().slice(start, end);
@ -5151,7 +5153,7 @@ function AQL_DATE_COMPARE (value1, value2, unitRangeStart, unitRangeEnd) {
// special escape sequence first, rest ordered by length
var dateMapRegExp = [
"%&", "%yyyyyy", "%yyyy", "%mmmm", "%wwww", "%mmm", "%www", "%fff", "%xxx",
"%mm", "%dd", "%hh", "%ii", "%ss", "%kk", "%t", "%z", "%w", "%y", "%m",
"%yy", "%mm", "%dd", "%hh", "%ii", "%ss", "%kk", "%t", "%z", "%w", "%y", "%m",
"%d", "%h", "%i", "%s", "%f", "%x", "%k", "%l", "%q", "%a", "%%", "%"
].join("|");
@ -5163,27 +5165,46 @@ function AQL_DATE_FORMAT (value, format) {
var yr = date.getUTCFullYear();
var offset = yr < 0 || yr > 9999 ? 3 : 0;
var dateMap = {
"%t": function(){ return date.getTime(); },
"%z": function(){ return dateStr; },
"%w": function(){ return AQL_DATE_DAYOFWEEK(dateStr); },
"%y": function(){ return dateStr.slice(0, 4 + offset); },
"%m": function(){ return dateStr.slice(5 + offset, 7 + offset); },
"%d": function(){ return dateStr.slice(8 + offset, 10 + offset); },
"%h": function(){ return dateStr.slice(11 + offset, 13 + offset); },
"%i": function(){ return dateStr.slice(14 + offset, 16 + offset); },
"%s": function(){ return dateStr.slice(17 + offset, 19 + offset); },
"%f": function(){ return dateStr.slice(20 + offset, 23 + offset); },
"%x": function(){ return zeropad(AQL_DATE_DAYOFYEAR(dateStr), 3); },
"%k": function(){ return zeropad(AQL_DATE_ISOWEEK(dateStr), 2); },
"%l": function(){ return +AQL_DATE_LEAPYEAR(dateStr); },
"%q": function(){ return AQL_DATE_QUARTER(dateStr); },
"%a": function(){ return zeropad(AQL_DATE_DAYS_IN_MONTH(dateStr), 2); },
"%n": function(){ return monthNames[date.getUTCMonth()]; },
"%o": function(){ return monthNames[date.getUTCMonth()].substring(0, 3); },
"%e": function(){ return weekdayNames[AQL_DATE_DAYOFWEEK(dateStr)]; },
"%g": function(){ return weekdayNames[AQL_DATE_DAYOFWEEK(dateStr)].substring(0, 3); },
"%%": function(){ return "%"; } // Allow for literal "%Y" using "%%Y"
//"%": "" // Not reliable, because Object.keys() does not guarantee order
"%t": function(){ return date.getTime() },
"%z": function(){ return dateStr },
"%w": function(){ return AQL_DATE_DAYOFWEEK(dateStr) },
"%y": function(){ return date.getUTCFullYear() },
// there's no really sensible way to handle negative years, but better not drop the sign
"%yy": function(){ return (yr < 0 ? "-" : "") + dateStr.slice(2 + offset, 4 + offset) },
// preserves full negative years (-000753 is not reduced to -753 or -0753)
"%yyyy": function(){ return dateStr.slice(0, 4 + offset) },
// zero-pad 4 digit years to length of 6 and add "+" prefix, keep negative as-is
"%yyyyyy": function(){
return (yr >= 0 && yr <= 9999)
? "+" + zeropad(dateStr.slice(0, 4 + offset), 6)
: dateStr.slice(0, 7)
},
"%m": function(){ return date.getUTCMonth() + 1 },
"%mm": function(){ return dateStr.slice(5 + offset, 7 + offset) },
"%d": function(){ return date.getUTCDate() },
"%dd": function(){ return dateStr.slice(8 + offset, 10 + offset) },
"%h": function(){ return date.getUTCHours() },
"%hh": function(){ return dateStr.slice(11 + offset, 13 + offset) },
"%i": function(){ return date.getUTCMinutes() },
"%ii": function(){ return dateStr.slice(14 + offset, 16 + offset) },
"%s": function(){ return date.getUTCSeconds() },
"%ss": function(){ return dateStr.slice(17 + offset, 19 + offset) },
"%f": function(){ return date.getUTCMilliseconds() },
"%fff": function(){ return dateStr.slice(20 + offset, 23 + offset) },
"%x": function(){ return AQL_DATE_DAYOFYEAR(dateStr) },
"%xxx": function(){ return zeropad(AQL_DATE_DAYOFYEAR(dateStr), 3) },
"%k": function(){ return AQL_DATE_ISOWEEK(dateStr) },
"%kk": function(){ return zeropad(AQL_DATE_ISOWEEK(dateStr), 2) },
"%l": function(){ return +AQL_DATE_LEAPYEAR(dateStr) },
"%q": function(){ return AQL_DATE_QUARTER(dateStr) },
"%a": function(){ return AQL_DATE_DAYS_IN_MONTH(dateStr) },
"%mmm": function(){ return monthNames[date.getUTCMonth()].substring(0, 3) },
"%mmmm": function(){ return monthNames[date.getUTCMonth()] },
"%www": function(){ return weekdayNames[AQL_DATE_DAYOFWEEK(dateStr)].substring(0, 3) },
"%wwww": function(){ return weekdayNames[AQL_DATE_DAYOFWEEK(dateStr)] },
"%&": function(){ return "" }, // Allow for literal "m" after "%m" ("%mm" -> %m%&m)
"%%": function(){ return "%" }, // Allow for literal "%y" using "%%y"
"%": function(){ return "" }
};
var exp = new RegExp(dateMapRegExp, "gi");
format = format.replace(exp, function(match){