mirror of https://gitee.com/bigwinds/arangodb
416 lines
17 KiB
Plaintext
416 lines
17 KiB
Plaintext
!CHAPTER Date functions
|
|
|
|
AQL offers functionality to work with dates. Dates are no data types of their own in
|
|
AQL (neither are they in JSON, which is often used as a format to ship data into and
|
|
out of ArangoDB). Instead, dates in AQL are internally represented by either numbers
|
|
(timestamps) or strings. The date functions in AQL provide mechanisms to convert from
|
|
a numeric timestamp to a string representation and vice versa.
|
|
|
|
There are two date functions in AQL to create dates for further use:
|
|
|
|
- *DATE_TIMESTAMP(date)*: Creates a UTC timestamp value from *date*. The return
|
|
value has millisecond precision. To convert the return value to seconds, divide
|
|
it by 1000.
|
|
|
|
- *DATE_TIMESTAMP(year, month, day, hour, minute, second, millisecond)*:
|
|
Same as before, but allows specifying the individual date components separately.
|
|
All parameters after *day* are optional.
|
|
|
|
- *DATE_ISO8601(date)*: Returns an ISO8601 date time string from *date*.
|
|
The date time string will always use UTC time, indicated by the *Z* at its end.
|
|
|
|
- *DATE_ISO8601(year, month, day, hour, minute, second, millisecond)*:
|
|
same as before, but allows specifying the individual date components separately.
|
|
All parameters after *day* are optional.
|
|
|
|
These two above date functions accept the following input values:
|
|
|
|
- numeric timestamps, indicating the number of milliseconds elapsed since the UNIX
|
|
epoch (i.e. January 1st 1970 00:00:00 UTC).
|
|
An example timestamp value is *1399472349522*, which translates to
|
|
*2014-05-07T14:19:09.522Z*.
|
|
|
|
- date time strings in formats *YYYY-MM-DDTHH:MM:SS.MMM*,
|
|
*YYYY-MM-DD HH:MM:SS.MMM*, or *YYYY-MM-DD* Milliseconds are always optional.
|
|
A timezone difference may optionally be added at the end of the string, with the
|
|
hours and minutes that need to be added or subtracted to the date time value.
|
|
For example, *2014-05-07T14:19:09+01:00* can be used to specify a one hour offset,
|
|
and *2014-05-07T14:19:09+07:30* can be specified for seven and half hours offset.
|
|
Negative offsets are also possible. Alternatively to an offset, a *Z* can be used
|
|
to indicate UTC / Zulu time.
|
|
|
|
An example value is *2014-05-07T14:19:09.522Z* meaning May 7th 2014, 14:19:09 and
|
|
522 milliseconds, UTC / Zulu time. Another example value without time component is
|
|
*2014-05-07Z*.
|
|
|
|
Please note that if no timezone offset is specified in a date string, ArangoDB will
|
|
assume UTC time automatically. This is done to ensure portability of queries across
|
|
servers with different timezone settings, and because timestamps will always be
|
|
UTC-based.
|
|
|
|
- individual date components as separate function arguments, in the following order:
|
|
- year
|
|
- month
|
|
- day
|
|
- hour
|
|
- minute
|
|
- second
|
|
- millisecond
|
|
|
|
All components following *day* are optional and can be omitted. Note that no
|
|
timezone offsets can be specified when using separate date components, and UTC /
|
|
Zulu time will be used.
|
|
|
|
The following calls to *DATE_TIMESTAMP* are equivalent and will all return
|
|
*1399472349522*:
|
|
|
|
```js
|
|
DATE_TIMESTAMP("2014-05-07T14:19:09.522")
|
|
DATE_TIMESTAMP("2014-05-07T14:19:09.522Z")
|
|
DATE_TIMESTAMP("2014-05-07 14:19:09.522")
|
|
DATE_TIMESTAMP("2014-05-07 14:19:09.522Z")
|
|
DATE_TIMESTAMP(2014, 5, 7, 14, 19, 9, 522)
|
|
DATE_TIMESTAMP(1399472349522)
|
|
```
|
|
|
|
The same is true for calls to *DATE_ISO8601* that also accepts variable input
|
|
formats:
|
|
|
|
```js
|
|
DATE_ISO8601("2014-05-07T14:19:09.522Z")
|
|
DATE_ISO8601("2014-05-07 14:19:09.522Z")
|
|
DATE_ISO8601(2014, 5, 7, 14, 19, 9, 522)
|
|
DATE_ISO8601(1399472349522)
|
|
```
|
|
|
|
The above functions are all equivalent and will return *"2014-05-07T14:19:09.522Z"*.
|
|
|
|
The following date functions can be used with dates created by *DATE_TIMESTAMP* and
|
|
*DATE_ISO8601*:
|
|
|
|
- *DATE_DAYOFWEEK(date)*: Returns the weekday number of *date*. The
|
|
return values have the following meanings:
|
|
- 0: Sunday
|
|
- 1: Monday
|
|
- 2: Tuesday
|
|
- 3: Wednesday
|
|
- 4: Thursday
|
|
- 5: Friday
|
|
- 6: Saturday
|
|
|
|
- *DATE_YEAR(date)*: Returns the year part of *date* as a number.
|
|
|
|
- *DATE_MONTH(date)*: Returns the month part of *date* as a number.
|
|
|
|
- *DATE_DAY(date)*: Returns the day part of *date* as a number.
|
|
|
|
- *DATE_HOUR(date)*: Returns the hour part of *date* as a number.
|
|
|
|
- *DATE_MINUTE(date)*: Returns the minute part of *date* as a number.
|
|
|
|
- *DATE_SECOND(date)*: Returns the seconds part of *date* as a number.
|
|
|
|
- *DATE_MILLISECOND(date)*: Returns the milliseconds part of *date* as a number.
|
|
|
|
- *DATE_DAYOFYEAR(date)*: Returns the day of year number of *date*.
|
|
The return values range from 1 to 365, or 366 in a leap year respectively.
|
|
|
|
- *DATE_ISOWEEK(date)*: Returns the ISO week date of *date*. The return values
|
|
range from 1 to 53. Monday is considered the first day of the week. There are no
|
|
fractional weeks, thus the last days in December may belong to the first week of
|
|
the next year, and the first days in January may be part of the previous year's
|
|
last week.
|
|
|
|
- *DATE_LEAPYEAR(date)*: Returns whether the year of *date* is a leap year.
|
|
|
|
- *DATE_QUARTER(date)*: Returns the quarter of the given date (1-based):
|
|
|
|
- 1: January, February, March
|
|
- 2: April, May, June
|
|
- 3: July, August, September
|
|
- 4: October, November, December
|
|
|
|
- *DATE_DAYS_IN_MONTH(date)*: Returns the number of days in *date*'s month (28..31).
|
|
|
|
The following other date functions are also available:
|
|
|
|
- *DATE_NOW()*: Returns the current time as a timestamp.
|
|
The return value has millisecond precision. To convert the return value to
|
|
seconds, divide it by 1000.
|
|
|
|
Note that this function is evaluated on every invocation and may return
|
|
different values when invoked multiple times in the same query.
|
|
|
|
- *DATE_ADD(date, amount, unit)*: Adds *amount* given in *unit* to *date* and
|
|
returns the calculated date.
|
|
|
|
*unit* can be either of the following to specify the time unit to add or
|
|
subtract (case-insensitive):
|
|
- y, year, years
|
|
- m, month, months
|
|
- w, week, weeks
|
|
- d, day, days
|
|
- h, hour, hours
|
|
- i, minute, minutes
|
|
- s, second, seconds
|
|
- f, millisecond, milliseconds
|
|
|
|
*amount* is the number of *unit*s to add (positive value) or subtract
|
|
(negative value). It is recommended to use positive values only, and use
|
|
`DATE_SUBTRACT()` for subtractions instead.
|
|
|
|
Examples:
|
|
|
|
```js
|
|
DATE_ADD(DATE_NOW(), -1, "day") // yesterday; also see DATE_SUBTRACT()
|
|
DATE_ADD(DATE_NOW(), 3, "months") // in three months
|
|
DATE_ADD(DATE_ADD("2015-04-01", 5, "years"), 1, "month") // May 1st 2020
|
|
DATE_ADD("2015-04-01", 12*5 + 1, "months") // also May 1st 2020
|
|
DATE_ADD(DATE_TIMESTAMP(DATE_YEAR(DATE_NOW()), 12, 24), -4, "years") // Christmas four years ago
|
|
DATE_ADD(DATE_ADD("2016-02", "month", 1), -1, "day") // last day of February (29th, because 2016 is a leap year!)
|
|
```
|
|
|
|
You may also pass an ISO duration string as *amount* and leave out *unit*.
|
|
The format is `P_Y_M_W_DT_H_M_._S`, where underscores stand for digits and
|
|
letters for time intervals - except for `P` (period) and `T` (time).
|
|
The meaning of the other letters are:
|
|
- Y: years
|
|
- M: months (if before T)
|
|
- W: weeks
|
|
- D: days
|
|
- H: hours
|
|
- M: minutes (if after T)
|
|
- S: seconds (optionally with 3 decimal places for milliseconds)
|
|
|
|
The string must be prefixed by a `P`. A separating `T` is only required if
|
|
`H`, `M` and/or `S` are specified. You only need to specify the needed pairs
|
|
of amounts and time intervals.
|
|
|
|
Examples:
|
|
|
|
```js
|
|
DATE_ADD(DATE_NOW(), "P1Y") // add 1 year
|
|
DATE_ADD(DATE_NOW(), "P3M2W") // add 3 months and 2 weeks
|
|
DATE_ADD(DATE_NOW(), "P5DT26H") // add 5 days and 26 hours (=6 days and 2 hours)
|
|
DATE_ADD("2000-01-01", "PT4H") // add 4 hours
|
|
DATE_ADD("2000-01-01", "PT30M44.4S" // add 30 minutes, 44 seconds and 400 ms
|
|
DATE_ADD("2000-01-01", "P1Y2M3W4DT5H6M7.89S" // add a bit of everything
|
|
```
|
|
|
|
- *DATE_SUBTRACT(date, amount, unit)*: Subtracts *amount* given in *unit* from
|
|
*date* and returns the calculated date.
|
|
|
|
It works the same as *DATE_ADD()*, except that it subtracts. It is equivalent
|
|
to calling *DATE_ADD()* with a negative amount, except that *DATE_SUBTRACT()*
|
|
can also subtract ISO durations. Note that negative ISO durations are not
|
|
supported (i.e. starting with `-P`, like `-P1Y`).
|
|
|
|
Examples:
|
|
|
|
```js
|
|
DATE_SUBTRACT(DATE_NOW(), 1, "day") // yesterday
|
|
DATE_SUBTRACT(DATE_TIMESTAMP(DATE_YEAR(DATE_NOW()), 12, 24), 4, "years") // Christmas four years ago
|
|
DATE_SUBTRACT(DATE_ADD("2016-02", "month", 1), 1, "day") // last day of February (29th, because 2016 is a leap year!)
|
|
DATE_SUBTRACT(DATE_NOW(), "P4D") // four days ago
|
|
DATE_SUBTRACT(DATE_NOW(), "PT1H3M") // 1 hour and 30 minutes ago
|
|
```
|
|
|
|
- *DATE_DIFF(date1, date2, unit, asFloat)*: Calculate the difference
|
|
between two dates in given time *unit*, optionally with decimal places.
|
|
Returns a negative value if *date1* is greater than / after *date2*.
|
|
|
|
*unit* can be either of the following to specify the time unit to return the
|
|
difference in (case-insensitive):
|
|
- y, year, years
|
|
- m, month, months
|
|
- w, week, weeks
|
|
- d, day, days
|
|
- h, hour, hours
|
|
- i, minute, minutes
|
|
- s, second, seconds
|
|
- f, millisecond, milliseconds
|
|
|
|
- *DATE_COMPARE(date1, date2, unitRangeStart, unitRangeEnd)*: Compare two
|
|
partial dates and return true if they match, false otherwise. The parts to
|
|
compare are defined by a range of time units.
|
|
|
|
The full range is: years, months, days, hours, minutes, seconds, milliseconds.
|
|
Pass the unit to start from as *unitRangeStart*, and the unit to end with as
|
|
*unitRangeEnd*. All units in between will be compared. Leave out *unitRangeEnd*
|
|
to only compare *unitRangeStart*. You can refer to the units as:
|
|
|
|
- y, year, years
|
|
- m, month, months
|
|
- d, day, days
|
|
- h, hour, hours
|
|
- i, minute, minutes
|
|
- s, second, seconds
|
|
- f, millisecond, milliseconds
|
|
|
|
An error is raised if *unitRangeEnd* is a unit before *unitRangeStart*.
|
|
|
|
Examples:
|
|
|
|
```js
|
|
// Compare months and days, true on birthdays if you're born on 4th of April
|
|
DATE_COMPARE("1985-04-04", DATE_NOW(), "months", "days")
|
|
|
|
// Will only match on one day if the current year is a leap year!
|
|
// You may want to add or subtract one day from date1 to match every year.
|
|
DATE_COMPARE("1984-02-29", DATE_NOW(), "months", days")
|
|
|
|
// compare years, months and days (true, because it's the same day)
|
|
DATE_COMPARE("2001-01-01T15:30:45.678Z", "2001-01-01T08:08:08.008Z", "years", "days")
|
|
```
|
|
|
|
You can directly compare ISO date **strings** if you want to find dates before or
|
|
after a certain date, or in between two dates (`>=`, `>`, `<`, `<=`).
|
|
No special date function is required. Equality tests (`==` and `!=`) will only
|
|
match the exact same date and time however. You may use `SUBSTRING()` to
|
|
compare partial date strings, `DATE_COMPARE()` is basically a convenience
|
|
function for that. However, neither is really required to limit a search to a
|
|
certain day for instance:
|
|
|
|
```js
|
|
FOR doc IN coll
|
|
FILTER doc.date >= "2015-05-15" AND doc.date < "2015-05-16"
|
|
RETURN doc
|
|
```
|
|
|
|
Every ISO date on that day is greater than `2015-05-15` in a string comparison
|
|
(e.g. `2015-05-15T11:30:00.000Z`). The time components will be "ignored". The
|
|
equal sign in `>=` merely helps to express the semantic. Dates before
|
|
`2015-05-15` are less and therefore filtered out. The second condition works
|
|
likewise. The query will return every document with `date` ranging from
|
|
`2015-05-15T00:00:00.000Z` to `2015-05-15T23:99:99.999Z`. It would also include
|
|
`2015-05-15T24:00:00.000Z`, but that date is actually `2015-05-16T00:00:00.000Z`
|
|
and can only occur if inserted manually.
|
|
|
|
Leap days in leap years (29th of February) must be always handled manually,
|
|
if you require so (e.g. birthday checks):
|
|
|
|
```js
|
|
LET today = DATE_NOW()
|
|
LET noLeapYear = NOT DATE_LEAPYEAR(today)
|
|
|
|
FOR user IN users
|
|
LET birthday = noLeapYear AND
|
|
DATE_MONTH(user.birthday) == 2 AND
|
|
DATE_DAY(user.birthday) == 29
|
|
? DATE_SUBTRACT(user.birthday, 1, "day") /* treat like 28th in non-leap years */
|
|
: user.birthday
|
|
FILTER DATE_COMPARE(today, birthday, "month", "day")
|
|
/* includes leaplings on the 28th of February in non-leap years,
|
|
* but excludes them in leap years which do have a 29th February.
|
|
* Replace DATE_SUBTRACT() by DATE_ADD() to include them on the 1st of March
|
|
* in non-leap years instead (depends on local jurisdiction).
|
|
*/
|
|
RETURN user
|
|
```
|
|
|
|
- *DATE_FORMAT(date, format)*: Format a date according to the given format string.
|
|
It supports the following placeholders (case-insensitive):
|
|
- %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 (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)
|
|
- %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
|
|
|
|
`%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 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
|
|
server-side and leave it up to the client to do so. This function should only
|
|
be used for special date comparisons or to store the formatted dates in the
|
|
database. For better performance, use the primitive `DATE_*()` functions
|
|
together with `CONCAT()` if possible.
|
|
|
|
Examples:
|
|
|
|
```js
|
|
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", "%xxx%") // "063", trailing % ignored
|
|
```
|
|
|
|
- *IS_DATESTRING(value)*: Returns true if *value* is a string that can be used
|
|
in a date function. This includes partial dates such as *2015* or *2015-10* and
|
|
strings containing invalid dates such as *2015-02-31*. The function will return
|
|
false for all non-string values, even if some of them may be usable in date functions.
|
|
|
|
!SECTION Working with dates and indices
|
|
|
|
There are two recommended ways to store timestamps in ArangoDB:
|
|
- as string with [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) UTC timestamp
|
|
- as [Epoch number](https://en.wikipedia.org/wiki/Epoch_%28reference_date%29)
|
|
|
|
The sort order of both is identical due to the sort properties of ISO date strings.
|
|
Therefore, you can work with [skiplist indices](../IndexHandling/Skiplist.md) and use
|
|
string comparisons (less than, greater than, in, equality) to express time ranges in your queries:
|
|
|
|
@startDocuBlockInline working_with_date_time
|
|
@EXAMPLE_ARANGOSH_OUTPUT{working_with_date_time}
|
|
db._create("exampleTime");
|
|
var timestamps = ["2014-05-07T14:19:09.522","2014-05-07T21:19:09.522","2014-05-08T04:19:09.522","2014-05-08T11:19:09.522","2014-05-08T18:19:09.522"];
|
|
for (i = 0; i < 5; i++) db.exampleTime.save({value:i, ts: timestamps[i]})
|
|
db._query("FOR d IN exampleTime FILTER d.ts > '2014-05-07T14:19:09.522' and d.ts < '2014-05-08T18:19:09.522' RETURN d").toArray()
|
|
~addIgnoreCollection("example")
|
|
~db._drop("exampleTime")
|
|
@END_EXAMPLE_ARANGOSH_OUTPUT
|
|
@endDocuBlock working_with_date_time
|
|
|
|
The first and the last timestamp in the array are excluded from the result by the `FILTER`.
|
|
|
|
!SECTION Limitations
|
|
|
|
Note that dates before the year 1583 aren't allowed by the
|
|
[ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) standard by default, because
|
|
they lie before the official introduction of the Gregorian calendar and may thus
|
|
be incorrect or invalid. All AQL date functions apply the same rules to every
|
|
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 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).
|