Spec-Zone.ru › RethinkDB ruby

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

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

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

Dates and Times Illustration

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

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

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

r.table('events').insert(
    [{'id' => 0, 'timestamp' => Time.now},
     {'id' => 1, 'timestamp' => r.epoch_time(1376436769.923)}]
  ).run(conn)

Теперь давайте получим их обратно:

> r.table('events').run(conn).to_a
[{"timestamp"=>2013-08-13 16:32:48 -0700, "id"=>0},
 {"timestamp"=>2013-08-13 23:32:49 +0000, "id"=>1}]

Вы заметите, что оба вставленных времени возвращаются как объекты времени из Ruby Time. Они находятся в разных часовых поясах, потому что Time.now создает объект времени в местном часовом поясе, а r.epoch_time создает время в UTC (не зная и не заботясь о часовом поясе клиента). Если бы мы вместо этого вставили Time.now.utc, они оба были бы в UTC, когда мы их извлекли.

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

> r.table('events').filter{|row| row['timestamp'].hours() > 20}.run(conn)
[{"timestamp"=>2013-08-13 23:32:49 +0000, "id"=>1}]
> r.table('events').filter{|row|
    row['timestamp'].in_timezone('-02:00').hours() > 20
  }.run(conn)
[{"timestamp"=>2013-08-13 16:32:48 -0700, "id"=>0},
 {"timestamp"=>2013-08-13 23:32:49 +0000, "id"=>1}]

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

> 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)
[{"timestamp"=>2013-08-13 23:32:49 +0000, "id"=>1}]

Технические подробности

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

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

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

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

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

Вы можете вставить время, просто передав объект времени из Ruby Time. Если объект местного времени содержит часовой пояс, вставленное время будет иметь этот часовой пояс; если нет, то вставленное время будет в UTC. (Проверьте это, если вы используете драйвер сторонних разработчиков.)

> r.table('events').insert({'id' => 2, 'timestamp' => Time.now}).run(conn)
{"unchanged"=>0, "skipped"=>0, "replaced"=>0, "inserted"=>1, "errors"=>0, "deleted"=>0}

Вы также можете использовать 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)
{"unchanged"=>0, "skipped"=>0, "replaced"=>0, "inserted"=>1, "errors"=>0, "deleted"=>0}
> r.table('t').insert({'id' => r.iso8601("2013-08-09T10:58:00.1112-08:00")}).run(conn)
{"unchanged"=>0, "skipped"=>0, "replaced"=>0, "inserted"=>0,
 "first_error"=>"Duplicate primary key `id`: ...", "errors"=>1, "deleted"=>0}

Вы также можете вставить время, вставив объект литерального псевдотипа. Это полезно, если, например, вы экспортировали строку с использованием :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)
2013-08-09 19:09:11 UTC
> r.now().in_timezone('-07:00').run(conn)
2013-08-09 12:14:56 -0700
> r.now().run(conn, :time_format => 'raw')
{'timezone'=>'+00:00', "epoch_time"=>1376075362.662, "$reql_type$"=>"TIME"}
> r.now().in_timezone('-07:00').run(conn, :time_format => 'raw')
{"timezone"=>"-07:00", "epoch_time"=>1376075702.485, "$reql_type$"=>"TIME"}

Вы также можете преобразовать объект времени на сервере, используя 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.expr(Time.now).to_iso8601().run(conn)
"2013-08-09T12:48:59.103-07:00"
> r.expr(Time.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)
2015-01-02 00:00:00 +0000

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

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

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

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

> (r.epoch_time(1376081287.982) < Time.now).run(conn)
true

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

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

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

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

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

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

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

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

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

> r.expr(Time.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 {|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 {|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{|sale| sale['time'].month()}.sum('dollars').run(conn)

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

Spec-Zone.ru

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