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. - *DATE_FORMAT(date, format)*: Format a date according to the given format string.
It supports the following placeholders (case-insensitive): 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) - %z: ISO date (0000-00-00T00:00:00.000Z)
- %w: day of week (0..6) - %w: day of week (0..6)
- %y: year (0000..9999) - %y: year (0..9999)
- %m: month (01..12) - %yy: year (00..99), abbreviated (last two digits)
- %d: day (01..31) - %yyyy: year (0000..9999), padded to length of 4
- %h: hour (00..23) - %yyyyyy: year (-009999 .. +009999), with sign prefix and padded to length of 6
- %i: minute (00..59) - %m: month (1..12)
- %s: second (00..59) - %mm: month (01..12), padded to length of 2
- %f: millisecond (000..999) - %d: day (1..31)
- %x: day of year (001..366) - %dd: day (01..31), padded to length of 2
- %k: ISO week date (01..53) - %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) - %l: leap year (0 or 1)
- %q: quarter (1..4) - %q: quarter (1..4)
- %a: days in month (28..31) - %a: days in month (28..31)
- %n: English name of month (January..December) - %mmm: abbreviated English name of month (Jan..Dec)
- %o: abbreviated English name of month (Jan..Dec) - %mmmm: English name of month (January..December)
- %e: English name of weekday (Sunday..Saturday) - %www: abbreviated English name of weekday (Sun..Sat)
- %g: abbreviated English name of weekday (Sun..Sat) - %wwww: English name of weekday (Sunday..Saturday)
- %&: special escape sequence for rare occasions
- %%: literal % - %%: literal %
- %: ignored
Month, day, hour, minute, second, ISO week date and days in month are zero-padded `%yyyy` does not enforce a length of 4 for years before 0 and past 9999.
to 2 characters, millisecond and day of year to 3 characters. Year is zero-padded The same format as for `%yyyyyy` will used instead. `%yy` preserves the
to 4 characters, or 6 if year is negative (it will be 7 chars long however sign for negative years and thus returns 3 characters in total.
because of the leading minus character, and is not suitable for comparisons
with 4-chars positive years). Timestamp is not padded.
Single `%` characters will appear as percent characters, as long as they aren't Single `%` characters will be ignored. Use `%%` for a literal `%`. To resolve
followed by a valid placeholder letter. `%%` is only required to output a ambiguities like in `%mmonth` (unpadded month number + the string "month")
placeholder literally. 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 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 datasets (like over 1 million dates). If possible, avoid formatting dates on
@ -351,10 +363,11 @@ FOR user IN users
Examples: Examples:
```js ```js
DATE_FORMAT(DATE_NOW(), "%q/%y") // quarter and year (e.g. "3/2015") DATE_FORMAT(DATE_NOW(), "%q/%yyyy") // 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(), "%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", "%%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 !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 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 especially years before Christ. The standard allows negative years, but requires
special treatment of positive years too, if negative years are used (e.g. 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 `+002015-05-15` and `-000753-01-01`). This is rarely used however, and AQL does
handled by AQL. Leap seconds are also ignored, just as they are in JavaScript as per 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). [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. * `DATE_FORMAT(date, format)`: Format a date according to the given format string.
It supports the following placeholders (case-insensitive): 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) - %z: ISO date (0000-00-00T00:00:00.000Z)
- %w: day of week (0..6) - %w: day of week (0..6)
- %y: year (0000..9999) - %y: year (0..9999)
- %m: month (01..12) - %yy: year (00..99), abbreviated (last two digits)
- %d: day (01..31) - %yyyy: year (0000..9999), padded to length of 4
- %h: hour (00..23) - %yyyyyy: year (-009999 .. +009999), with sign prefix and padded to length of 6
- %i: minute (00..59) - %m: month (1..12)
- %s: second (00..59) - %mm: month (01..12), padded to length of 2
- %f: millisecond (000..999) - %d: day (1..31)
- %x: day of year (001..366) - %dd: day (01..31), padded to length of 2
- %k: ISO week date (01..53) - %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) - %l: leap year (0 or 1)
- %q: quarter (1..4) - %q: quarter (1..4)
- %a: days in month (28..31) - %a: days in month (28..31)
- %n: English name of month (January..December) - %mmm: abbreviated English name of month (Jan..Dec)
- %o: abbreviated English name of month (Jan..Dec) - %mmmm: English name of month (January..December)
- %e: English name of weekday (Sunday..Saturday) - %www: abbreviated English name of weekday (Sun..Sat)
- %g: abbreviated English name of weekday (Sun..Sat) - %wwww: English name of weekday (Sunday..Saturday)
- %&: special escape sequence for rare occasions
- %%: literal % - %%: literal %
- %: ignored
!SUBSECTION RETURN DISTINCT !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. // birthday checking however. Probably best to leave compensation up to user query.
var date1 = MAKE_DATE([ value1 ], "DATE_COMPARE"); var date1 = MAKE_DATE([ value1 ], "DATE_COMPARE");
var date2 = MAKE_DATE([ value2 ], "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; return null;
} }
if (unitRangeEnd === undefined) { 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); WARN("DATE_COMPARE", INTERNAL.errors.ERROR_QUERY_INVALID_DATE_VALUE);
return null; return null;
} }
if (date1.getUTCFullYear() < 0 && start !== 0) { var yr1 = date1.getUTCFullYear();
if ((yr1 < 0 || yr1 > 9999) && start !== 0) {
start += 3; start += 3;
} }
if (date2.getUTCFullYear() < 0) { var yr2 = date2.getUTCFullYear();
if (yr2 < 0 || yr2 > 9999) {
end += 3; end += 3;
} }
var substr1 = date1.toISOString().slice(start, end); 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 // special escape sequence first, rest ordered by length
var dateMapRegExp = [ var dateMapRegExp = [
"%&", "%yyyyyy", "%yyyy", "%mmmm", "%wwww", "%mmm", "%www", "%fff", "%xxx", "%&", "%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", "%%", "%" "%d", "%h", "%i", "%s", "%f", "%x", "%k", "%l", "%q", "%a", "%%", "%"
].join("|"); ].join("|");
@ -5163,27 +5165,46 @@ function AQL_DATE_FORMAT (value, format) {
var yr = date.getUTCFullYear(); var yr = date.getUTCFullYear();
var offset = yr < 0 || yr > 9999 ? 3 : 0; var offset = yr < 0 || yr > 9999 ? 3 : 0;
var dateMap = { var dateMap = {
"%t": function(){ return date.getTime(); }, "%t": function(){ return date.getTime() },
"%z": function(){ return dateStr; }, "%z": function(){ return dateStr },
"%w": function(){ return AQL_DATE_DAYOFWEEK(dateStr); }, "%w": function(){ return AQL_DATE_DAYOFWEEK(dateStr) },
"%y": function(){ return dateStr.slice(0, 4 + offset); }, "%y": function(){ return date.getUTCFullYear() },
"%m": function(){ return dateStr.slice(5 + offset, 7 + offset); }, // there's no really sensible way to handle negative years, but better not drop the sign
"%d": function(){ return dateStr.slice(8 + offset, 10 + offset); }, "%yy": function(){ return (yr < 0 ? "-" : "") + dateStr.slice(2 + offset, 4 + offset) },
"%h": function(){ return dateStr.slice(11 + offset, 13 + offset); }, // preserves full negative years (-000753 is not reduced to -753 or -0753)
"%i": function(){ return dateStr.slice(14 + offset, 16 + offset); }, "%yyyy": function(){ return dateStr.slice(0, 4 + offset) },
"%s": function(){ return dateStr.slice(17 + offset, 19 + offset); }, // zero-pad 4 digit years to length of 6 and add "+" prefix, keep negative as-is
"%f": function(){ return dateStr.slice(20 + offset, 23 + offset); }, "%yyyyyy": function(){
"%x": function(){ return zeropad(AQL_DATE_DAYOFYEAR(dateStr), 3); }, return (yr >= 0 && yr <= 9999)
"%k": function(){ return zeropad(AQL_DATE_ISOWEEK(dateStr), 2); }, ? "+" + zeropad(dateStr.slice(0, 4 + offset), 6)
"%l": function(){ return +AQL_DATE_LEAPYEAR(dateStr); }, : dateStr.slice(0, 7)
"%q": function(){ return AQL_DATE_QUARTER(dateStr); }, },
"%a": function(){ return zeropad(AQL_DATE_DAYS_IN_MONTH(dateStr), 2); }, "%m": function(){ return date.getUTCMonth() + 1 },
"%n": function(){ return monthNames[date.getUTCMonth()]; }, "%mm": function(){ return dateStr.slice(5 + offset, 7 + offset) },
"%o": function(){ return monthNames[date.getUTCMonth()].substring(0, 3); }, "%d": function(){ return date.getUTCDate() },
"%e": function(){ return weekdayNames[AQL_DATE_DAYOFWEEK(dateStr)]; }, "%dd": function(){ return dateStr.slice(8 + offset, 10 + offset) },
"%g": function(){ return weekdayNames[AQL_DATE_DAYOFWEEK(dateStr)].substring(0, 3); }, "%h": function(){ return date.getUTCHours() },
"%%": function(){ return "%"; } // Allow for literal "%Y" using "%%Y" "%hh": function(){ return dateStr.slice(11 + offset, 13 + offset) },
//"%": "" // Not reliable, because Object.keys() does not guarantee order "%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"); var exp = new RegExp(dateMapRegExp, "gi");
format = format.replace(exp, function(match){ format = format.replace(exp, function(match){