Spec-Zone.ru › RethinkDB python

Даты и время в RethinkDB

RethinkDB имеет встроенную поддержку времени с точностью до миллисекунды и часовыми поясами. Некоторые преимущества:

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

Dates and Times Illustration

Быстрый пример

Сначала создадим таблицу и вставим некоторые события. Первое событие вставим, используя объект времени по умолчанию, а второе — с помощью конструктора epoch_time:

r.table_create('events').run(conn)

from datetime import datetime
r.table('events').insert([
    {'id': 0, 'timestamp': r.expr(datetime.now(r.make_timezone('-07:00')))},
    {'id': 1, 'timestamp': r.epoch_time(1376436769.923)}
]).run(conn)

Теперь извлечем их:

> list(r.table('events').run(conn))

[{u'id': 0,
  u'timestamp': datetime.datetime(2015, 2, 2, 11, 56, 31, 250000, tzinfo=<rethinkdb.ast.RqlTzinfo object at 0x102ca0c50>)},
 {u'id': 1,
  u'timestamp': datetime.datetime(2013, 8, 13, 23, 32, 49, 923000, tzinfo=<rethinkdb.ast.RqlTzinfo object at 0x102ca0fd0>)}]

Обратите внимание, что оба вставленных времени возвращаются как объекты Python datetime. Они находятся в разных часовых поясах, потому что datetime.now требует указания часового пояса, а r.epoch_time создаёт время UTC (не зная и не заботясь о часовом поясе клиента). В этом примере мы используем r.make_timezone, ReQL-команду для Python, но можно использовать pytz или другую библиотеку Python для часовых поясов.

Теперь мы можем фильтровать по этому времени:

> r.table('events').filter(r.row['timestamp'].hours() > 20).run(conn)
[{u'id': 1,
  u'timestamp': datetime.datetime(2013, 8, 13, 23, 32, 49, 923000, tzinfo=<rethinkdb.ast.RqlTzinfo object at 0x102cbde90>)}]

> r.table('events').filter(r.row['timestamp'].in_timezone('-02:00').hours() > 20).run(conn)
[{u'id': 0,
  u'timestamp': datetime.datetime(2015, 2, 2, 11, 56, 31, 250000, tzinfo=<rethinkdb.ast.RqlTzinfo object at 0x102ca0c50>)},
 {u'id': 1,
  u'timestamp': datetime.datetime(2013, 8, 13, 23, 32, 49, 923000, tzinfo=<rethinkdb.ast.RqlTzinfo object at 0x102ca0fd0>)}]

Или создать вторичный индекс по нему:

> r.table('events').index_create('timestamp').run(conn)
{'created': 1}
> r.table('events').between(r.epoch_time(1376436769.913),
                            r.epoch_time(1376436769.933),
                            index='timestamp').run(conn)
[{u'id': 1,
  u'timestamp': datetime.datetime(2013, 8, 13, 23, 32, 49, 923000, tzinfo=<rethinkdb.ast.RqlTzinfo object at 0x102cbde90>)}]

Технические детали

Время хранится на сервере как количество секунд с начала эпохи (UTC) с точностью до миллисекунд плюс часовой пояс. В настоящее время доступны только временные смещения от UTC с точностью до минуты, но в будущем может быть добавлена поддержка часовых поясов, учитывающих летнее время. Часовые пояса представлены строками в формате ISO 8601.

Два времени считаются равными, если равны их значения в эпоху (UTC), независимо от часового пояса. Это верно как для сравнений, так и для операций с индексами. Временные отметки сравниваются с плавающей точкой с точностью до миллисекунды.

Большинство операций с датами определены только для годов в диапазоне [1400, 10000] (но обратите внимание, что время в году 10000 нельзя вывести в формате даты ISO 8601).

Прыжки секунд в настоящее время не полностью поддерживаются: 2012-06-30T23:59:60 и 2012-07-01T00:00:00 интерпретируются как одно и то же время.

Вставка времени

Время можно вставлять, просто передав объект Python datetime.datetime. Обратите внимание, что драйвер Python требует, чтобы объекты datetime содержали информацию о часовом поясе. (Большинство драйверов по умолчанию используют UTC, если информация о часовом поясе отсутствует; для сторонних драйверов см. документацию).

r.table('events').insert(
    {'id': 2, 'timestamp': datetime.now(r.make_timezone('00:00'))}
).run()

Также можно использовать r.now (которое сервер интерпретирует как время получения запроса в UTC) или создать время с помощью r.time, r.epoch_time, или r.iso8601.

> r.now().to_iso8601().run(conn)
"2013-08-09T18:53:15.012+00:00"
> r.time(2013, r.august, 9, 18, 53, 15.012, '-07:00').to_iso8601().run(conn)
"2013-08-09T18:53:15.012-07:00"
> r.epoch_time(1376074395.012).to_iso8601().run(conn)
"2013-08-09T18:53:15.012+00:00"
> r.iso8601("2013-08-09T18:53:15.012-07:00").to_iso8601().run(conn)
"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)
{'deleted': 0, 'errors': 0, 'inserted': 1, 'replaced:' 0, 'skipped': 0, 'unchanged': 0}

> r.table('t').insert(
      {'id': r.iso8601("2013-08-09T10:58:00.1112-08:00")}
  ).run(conn)
{'deleted': 0, 'errors': 1, 'inserted': 0, 'replaced:' 0, 'skipped': 0, 'unchanged': 0, 'first_error': 'Duplicate primary key `id`: ...'}

Также можно вставить время, вставив объект-псевдотип. Это полезно, например, если вы экспортировали строку с помощью time_format='raw' (см. Извлечение времени ниже).

Примечание: Избегайте использования ключей, соответствующих регулярному выражению ^\$reql_.+\$$ в ваших объектах. RethinkDB считает их зарезервированными ключевыми словами.

> r.expr({'$reql_type$': 'TIME',
          'epoch_time': 1376075362.662,
          'timezone': '+00:00'}).to_iso8601().run(conn)
"2013-08-09T19:09:22.662+00:00"

Извлечение времени

По умолчанию время преобразуется в родные объекты времени при извлечении из сервера. Это можно изменить, передав параметр time_format в run. Доступны native, значение по умолчанию, и raw.

> r.now().run(conn)
datetime.datetime(2015, 2, 2, 19, 34, 10, 776000, tzinfo=<rethinkdb.ast.RqlTzinfo object at 0x102ca12d0>)
> r.now().in_timezone('-07:00').run(conn)
datetime.datetime(2015, 2, 2, 12, 34, 55, 642000, tzinfo=<rethinkdb.ast.RqlTzinfo object at 0x102ca1250>)
> r.now().run(conn, time_format='raw')
{u'$reql_type$': u'TIME',
 u'epoch_time': 1422905756.693,
 u'timezone': u'-07:00'}

Также можно преобразовать объект времени на сервере с помощью to_epoch_time или to_iso8601.

> r.now().to_epoch_time().run(conn)
1376075986.574
> r.now().to_iso8601().run(conn)
"2013-08-09T19:19:46.574+00:00"

Работа со временем

Есть только три полезных вещи, которые можно сделать с временем: изменить его, сравнить его с другим временем или извлечь часть из него.

Изменение времени

Можно переместить время в другой часовой пояс:

> r.now().to_iso8601().run(conn)
"2013-08-09T12:48:59.103-07:00"
> r.now().in_timezone('-06:00').to_iso8601().run(conn)
"2013-08-09T13:49:15.503-06:00"

Также можно добавить или вычесть продолжительность (в секундах):

> (r.time(2015, 1, 1, 'Z') + 86400).run(conn)
datetime.datetime(2015, 1, 2, 0, 0, tzinfo=<rethinkdb.ast.RqlTzinfo object at 0x10e681fd0>)

Если вы вычитаете два времени, вы получите продолжительность:

> (r.time(2015, 1, 2, 'Z') - r.time(2015, 1, 1, 'Z')).run(conn)
86400

Сравнение времен

Определены все обычные операторы сравнения:

> (r.epoch_time(1376081287.982) < r.now()).run(conn)
True

Временные отметки сравниваются только с точностью до миллисекунды:

> r.epoch_time(1376081287.9821).eq(r.epoch_time(1376081287.9822)).run(conn)
True

Также есть команда during, которая позволяет проверить, находится ли время в определенном диапазоне времени.

Извлечение частей времени

Если у вас есть время, вы можете извлечь определенную часть (например, месяц или часы) относительно текущего часового пояса. (Полный список см. в справочнике по API).

> r.now().run(conn)
2013-08-09 13:53:00 -0700
> r.now().month().run(conn)
8
> r.now().hours().run(conn)
13
> r.now().in_timezone('-06:00').hours().run(conn)
14

Мы используем определение недели ISO 8601, которое начинается с понедельника, представленного как 1.

> r.now().day_of_week().run(conn)
5 # Friday

Мы определяем r.monday...r.sunday и r.january...r.december для удобства:

> r.now().day_of_week().eq(r.friday).run(conn)
true

Мы также позволяем разбить время на дату и текущее время суток (время и продолжительность соответственно):

> r.now().to_epoch_time().run(conn)
1376351312.744
> r.now().date().to_epoch_time().run(conn)
1376265600
> r.now().time_of_day().run(conn)
85712.744

Объединение всего

Комбинируя эти операции, можно писать довольно полезные запросы в чистом ReQL. Например, предположим, у вас есть таблица продаж вашей компании, и вы хотите узнать, сколько валовой прибыли приходится на сотрудников, работавших сверхурочно:

r.table('sales').filter(lambda sale:
  # Weekends are overtime.
  sale['time'].day_of_week().eq(r.saturday) |
  sale['time'].day_of_week().eq(r.sunday) |
  # Weekdays outside 9-5 are overtime.
  (sale['time'].hours() < 9) |
  (sale['time'].hours() >= 17)
).sum('dollars').run(conn)

Если временные метки хранятся с часовыми поясами, этот запрос будет работать, даже если у вас есть продажи из разных офисов в разных странах (если все они работают с 9 до 17 по местному времени).

Поскольку этот запрос является чистым ReQL, весь запрос будет эффективно распределён по кластеру без каких-либо вычислений на стороне клиента.

Кроме того, поскольку это ReQL, отдельные части запроса легко комбинируются. Если вы решите получить эти данные по месяцам, вы можете просто добавить group:

r.table('sales').filter(lambda sale:
  # Weekends are overtime.
  sale['time'].day_of_week().eq(r.saturday) |
  sale['time'].day_of_week().eq(r.sunday) |
  # Weekdays outside 9-5 are overtime.
  (sale['time'].hours() < 9) |
  (sale['time'].hours() >= 17)
).group(lambda sale: sale['time'].month()).sum('dollars').run(conn)

Объекты родного времени

RethinkDB принимает объекты Python datetime:

from datetime import datetime

Драйвер Python выдаст ошибку, если вы передадите ему datetime без часового пояса.

> r.expr(datetime.now()).run(conn)

ReqlDriverError: Cannot convert datetime to ReQL time object
without timezone information. You can add timezone information with
the third party module "pytz" or by constructing ReQL compatible
timezone values with r.make_timezone("[+-]HH:MM"). Alternatively,
use one of ReQL's builtin time constructors, r.now, r.time, or r.iso8601.

Чтобы передать допустимый объект времени драйверу Python, можно сделать одно из двух.

Используйте r.make_timezone:

r.expr(datetime.now(r.make_timezone('-07:00'))).run(conn)

Или используйте модуль pytz:

from pytz import timezone
r.expr(datetime.now(timezone('US/Pacific'))).run(conn)

Примечание: Объекты RqlTzinfo нельзя напрямую сериализовать в JSON.

import json
today = r.expr(datetime.datetime.now(timezone('US/Pacific'))).run(conn)
json.dumps(today)

TypeError: datetime.datetime(2015, 6, 3, 14, 46, 11, 142000, tzinfo=<rethinkdb.ast.RqlTzinfo object at 0x103c5a890>) is not JSON serializable

Разрешить это, передав time_format="raw" необязательный аргумент в run:

import json
today = r.expr(datetime.datetime.now(timezone('US/Pacific'))).run(conn,
    time_format="raw")
json.dumps(today)

'{"timezone": "-07:00", "$reql_type$": "TIME", "epoch_time": 1433368112.289}'

© RethinkDB contributors
Licensed under the Creative Commons Attribution-ShareAlike 3.0 Unported License.
https://rethinkdb.com/docs/dates-and-times/python/

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API