Даты и время в RethinkDB
RethinkDB имеет встроенную поддержку времени с точностью до миллисекунды и часовыми поясами. Некоторые моменты:
- Время интегрировано с официальными драйверами, которые автоматически преобразуют его в и из типов времени, используемых в вашем языке.
- Запросы учитывают часовой пояс, поэтому вы можете задавать вопросы, такие как: «Произошло ли это событие в понедельник в часовом поясе, где оно было записано?»
- Время используется в качестве индексов, поэтому вы можете эффективно извлекать события на основе времени их возникновения.
- Операции со временем являются чистыми ReQL-операциями, что означает, что даже сложные запросы с датами и временем могут быть эффективно распределены по кластеру.

Быстрый пример
Сначала создадим таблицу и вставим некоторые события. Первое событие мы вставим с помощью объекта Date, а второе — с помощью конструктора epochTime:
r.tableCreate('events').run(conn, callback);
r.table('events').insert([
{id: 0, timestamp: new Date()},
{id: 1, timestamp: r.epochTime(1376436769.923)}
]).run(conn, callback);
Теперь извлечём их:
> r.table('events');
// Result passed to callback
[
{ "id": 0, "timestamp": Date("2013-08-13T23:32:49.923Z") },
{ "id": 1, "timestamp": Date("2013-08-13T23:32:49.923Z") }
]
Вы заметите, что оба вставленных времени возвращаются как родные JavaScript-объекты Date. (Объекты Date не хранят информацию о часовом поясе, поэтому оба времени являются UTC, независимо от часового пояса вашего сервера.)
Теперь мы можем фильтровать по этим временным меткам:
> r.table('events').filter(r.row('timestamp').hours().gt(20)).run(conn, callback);
// Result passed to callback
[ { "id": 1, "timestamp": Date("2013-08-13T23:32:49.923Z") } ]
Или создать дополнительный индекс на них:
> r.table('events').indexCreate('timestamp').run(conn, callback);
> r.table('events').between(r.epochTime(1376436769.913),
r.epochTime(1376436769.933), {index: 'timestamp'}
).run(conn, callback);
// Result passed to callback
[ { "id": 1, "timestamp": Date("2013-08-13T23:32:49.923Z") } ]
Технические детали
Время хранится на сервере как количество секунд с эпохи (UTC) с точностью до миллисекунды плюс часовой пояс. В настоящее время доступны только часовые пояса — смещения от UTC с точностью до минуты, но в будущем мы можем добавить поддержку часовых поясов, учитывающих переходы на летнее время. Часовые пояса представлены строками в формате ISO
- Обратите внимание, что драйвер JavaScript удаляет информацию о часовом поясе из-за ограничений объекта
Date, хотя вы можете получить данные о часовом поясе через объект времени ReQL. (См. ниже.)
Время считается равным, когда значения времени эпохи (UTC) равны, независимо от часового пояса. Это верно как для сравнений, так и для индексированных операций. Время сравнивается с плавающей точкой с точностью до миллисекунды.
Большинство операций с датой определены только для годов в диапазоне [1400, 10000] (но обратите внимание, что время в году 10000 не может быть выведено как дата ISO 8601).
Секунды високосного времени в настоящее время не полностью поддерживаются: 2012-06-30T23:59:60 и 2012-07-01T00:00:00 интерпретируются как одно и то же время.
Вставка времени
Вы можете вставлять время, просто передав объект Date.
> r.table('events').insert({id: 2, timestamp: new Date()}).run(conn, callback);
// Result passed to callback
{"unchanged"=>0, "skipped"=>0, "replaced"=>0, "inserted"=>1, "errors"=>0, "deleted"=>0}
Вы также можете использовать r.now (которое сервер интерпретирует как время получения запроса в UTC), или построить время, используя r.time, r.epochTime, или r.ISO8601.
> r.now().toISO8601().run(conn, callback);
// Result passed to callback
"2013-08-09T18:53:15.012+00:00"
> r.time(2013, r.august, 9, 18, 53, 15.012, '-07:00').toISO8601().run(conn, callback);
// Result passed to callback
"2013-08-09T18:53:15.012-07:00"
> r.epochTime(1376074395.012).toISO8601().run(conn, callback);
// Result passed to callback
"2013-08-09T18:53:15.012+00:00"
> r.ISO8601("2013-08-09T18:53:15.012-07:00").toISO8601().run(conn, callback);
// Result passed to callback
"2013-08-09T18:53:15.012-07:00"
Время может быть использовано в качестве первичного ключа для таблицы. Два времени считаются равными, если у них одинаковое количество миллисекунд с эпохи (UTC), независимо от часового пояса.
> r.table('t').insert(
{id: r.ISO8601("2013-08-09T11:58:00.1111-07:00")}
).run(conn, callback);
// Result passed to callback
{"unchanged"=>0, "skipped"=>0, "replaced"=>0, "inserted"=>1, "errors"=>0, "deleted"=>0}
> r.table('t').insert(
{id: r.ISO8601("2013-08-09T10:58:00.1111-08:00")}
).run(conn, callback);
// Result passed to callback
{"unchanged"=>0, "skipped"=>0, "replaced"=>0, "inserted"=>0,
"first_error"=>"Duplicate primary key `id`: ...", "errors"=>1, "deleted"=>0}
Также можно вставить время, вставив объект-литерал псевдотипа. Это полезно, если, например, вы экспортировали строку с использованием {timeFormat: 'raw'} (см. Извлечение времени ниже).
Примечание: Избегайте использования ключей, соответствующих регулярному выражению
^\$reql_.+\$$в ваших объектах. RethinkDB рассматривает их как зарезервированные ключевые слова.
> r.expr(
{'$reql_type$': 'TIME', epoch_time: 1376075362.662, timezone: '+00:00'}
).run(conn, callback);
// Result passed to callback
Date("2013-08-09T19:09:22.662Z")
Извлечение времени
По умолчанию время преобразуется в объекты времени при извлечении с сервера. Это можно изменить, передав параметр timeFormat в run. На данный момент доступны только native, по умолчанию, и raw. Обратитесь к ссылке на API, если у вас есть вопросы о передаче необязательных аргументов в JavaScript.
> r.now().run(conn, callback);
// Result passed to callback
Date("2013-08-13T23:32:49.923Z")
> r.now().inTimezone('-07:00').run(conn, callback);
// Result passed to callback: same as above, no TZ info retrieved
Date("2013-08-13T23:32:49.923Z")
> r.now().run(conn, {timeFormat: 'raw'}, callback);
// Result passed to callback
{
"$reql_type$": "TIME",
"epoch_time": 1423077622.659,
"timezone": "+00:00"
}
> r.now().inTimezone('-07:00').run(conn, {timeFormat: 'raw'}, callback);
// Result passed to callback, now with TZ info
{
"$reql_type$": "TIME",
"epoch_time": 1423077646.772,
"timezone": "-07:00"
}
Вы также можете преобразовать объект времени на сервере, используя toEpochTime или toISO8601.
> r.now().toEpochTime().run(conn, callback);
// Result passed to callback
1376075986.574
> r.now().toISO8601().run(conn, callback);
// Result passed to callback
"2013-08-09T19:19:46.574+00:00"
Работа со временем
Есть только три полезных действия с временем: изменить его, сравнить его с другим временем или получить часть из него.
Изменение времени
Вы можете добавить или вычесть длительность (в секундах):
> r.time(2015, 1, 1, 'Z').add(86400).run(conn, callback);
// Result passed to callback
Fri Jan 02 2015 00:00:00 GMT+00:00
Если вы вычитаете две временные метки, вы получите длительность:
> r.time(2015, 1, 2, 'Z').sub(r.time(2015, 1, 1, 'Z')).run(conn, callback);
// Result passed to callback
86400
Сравнение времени
Определены все стандартные операторы сравнения:
> r.epochTime(1376081287.982).lt(new Date()).run(conn, callback);
true
Время сравнивается только с точностью до миллисекунды:
> r.epochTime(1376081287.9821).eq(r.epochTime(1376081287.9822)).run(conn, callback);
true
Также есть команда during, которая может проверить, находится ли время в определенном диапазоне.
Получение частей времени
Если у вас есть время, вы можете получить определённую часть (например, месяц или часы) относительно текущего часового пояса. (См. полный список в ссылке на API.)
> r.expr(new Date()).run(conn, callback);
// Result passed to callback
"2013-08-13T23:32:49.923Z"
> r.expr(new Date()).month().run(conn, callback);
// Result passed to callback
8
> r.expr(new Date()).hours().run(conn, callback);
// Result passed to callback
23
> r.expr(new Date()).inTimezone('-06:00').hours().run(conn, callback);
// Result passed to callback
17
Мы используем определение недели ISO 8601, которая начинается с понедельника, представленного как 1.
> r.expr(new Date()).dayOfWeek().run(conn, callback);
5 # Friday
Мы определяем r.monday...r.sunday и r.january...r.december для удобства:
> r.expr(new Date()).dayOfWeek().eq(r.friday).run(conn, callback);
true
Мы также позволяем вам разбить время на дату и текущее время суток (время и продолжительность соответственно):
> r.now().toEpochTime().run(conn, callback);
// Result passed to callback
1376351312.744
> r.now().date().toEpochTime().run(conn, callback);
// Result passed to callback
1376265600
> r.now().timeOfDay().run(conn, callback);
// Result passed to callback
85712.744
Всё вместе
Комбинируя эти операции, можно писать довольно полезные запросы в чистом ReQL. Например, предположим, у вас есть таблица продаж, сделанных вашей компанией, и вы хотите узнать, сколько прибыли принесли люди, работавшие сверхурочно:
r.table('sales').filter(function (sale) {
// Weekends are overtime
return sale('time').dayOfWeek().eq(r.saturday).or(
sale('time').dayOfWeek().eq(r.sunday)).or(
// Weekdays outside 9-5 are overtime
sale('time').hours().lt(9)).or(
sale('time').hours().ge(17));
}).sum('dollars').run(conn, callback);
Если ваши временные метки хранятся с часовыми поясами, этот запрос будет работать даже если у вас есть продажи из разных офисов в разных странах (при условии, что все они работают с 9 до 17 по местному времени).
Поскольку этот запрос является чистым ReQL, весь запрос будет эффективно распределён по кластеру без каких-либо вычислений на клиенте.
Кроме того, поскольку это ReQL, отдельные части запроса легко объединяются. Если вы решите, что вам нужно эти цифры по месяцам, вы можете добавить group:
r.table('sales').filter(function (sale) {
// Weekends are overtime
return sale('time').dayOfWeek().eq(r.saturday).or(
sale('time').dayOfWeek().eq(r.sunday)).or(
// Weekdays outside 9-5 are overtime
sale('time').hours().lt(9)).or(
sale('time').hours().ge(17));
}).group(function (sale) {
return sale('time').month();
}).sum('dollars').run(conn, callback);
© RethinkDB contributors
Licensed under the Creative Commons Attribution-ShareAlike 3.0 Unported License.
https://rethinkdb.com/docs/dates-and-times/javascript/