1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133
|
# frozen_string_literal: true
# rubocop:todo all
# Copyright (C) 2009-2020 MongoDB Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
module BSON
# Injects behaviour for encoding and decoding time values to
# and from raw bytes as specified by the BSON spec.
#
# @note
# Ruby time can have nanosecond precision:
# +Time.utc(2020, 1, 1, 0, 0, 0, 999_999_999/1000r)+
# +Time#usec+ returns the number of microseconds in the time, and
# if the time has nanosecond precision the sub-microsecond part is
# truncated (the value is floored to the nearest millisecond).
# MongoDB only supports millisecond precision; we truncate the
# sub-millisecond part of microseconds (floor to the nearest millisecond).
# Note that if a time is constructed from a floating point value,
# the microsecond value may round to the starting floating point value
# but due to flooring, the time after serialization may end up to
# be different than the starting floating point value.
# It is recommended that time calculations use integer math only.
#
# @see http://bsonspec.org/#/specification
#
# @since 2.0.0
module Time
# A time is type 0x09 in the BSON spec.
#
# @since 2.0.0
BSON_TYPE = ::String.new(9.chr, encoding: BINARY).freeze
# Get the time as encoded BSON.
#
# @note The time is floored to the nearest millisecond.
#
# @example Get the time as encoded BSON.
# Time.new(2012, 1, 1, 0, 0, 0).to_bson
#
# @return [ BSON::ByteBuffer ] The buffer with the encoded object.
#
# @see http://bsonspec.org/#/specification
#
# @since 2.0.0
def to_bson(buffer = ByteBuffer.new)
value = _bson_to_i * 1000 + usec.divmod(1000).first
buffer.put_int64(value)
end
# Converts this object to a representation directly serializable to
# Extended JSON (https://github.com/mongodb/specifications/blob/master/source/extended-json/extended-json.md).
#
# @note The time is floored to the nearest millisecond.
#
# @option opts [ nil | :relaxed | :legacy ] :mode Serialization mode
# (default is canonical extended JSON)
#
# @return [ Hash ] The extended json representation.
def as_extended_json(**options)
utc_time = utc
if options[:mode] == :relaxed && (1970..9999).include?(utc_time.year)
if utc_time.usec != 0
if utc_time.respond_to?(:floor)
# Ruby 2.7+
utc_time = utc_time.floor(3)
else
utc_time -= utc_time.usec.divmod(1000).last.to_r / 1000000
end
{'$date' => utc_time.strftime('%Y-%m-%dT%H:%M:%S.%LZ')}
else
{'$date' => utc_time.strftime('%Y-%m-%dT%H:%M:%SZ')}
end
else
sec = utc_time._bson_to_i
msec = utc_time.usec.divmod(1000).first
{'$date' => {'$numberLong' => (sec * 1000 + msec).to_s}}
end
end
def _bson_to_i
# Workaround for JRuby's #to_i rounding negative timestamps up
# rather than down (https://github.com/jruby/jruby/issues/6104)
if BSON::Environment.jruby?
(self - usec.to_r/1000000).to_i
else
to_i
end
end
module ClassMethods
# Deserialize UTC datetime from BSON.
#
# @param [ ByteBuffer ] buffer The byte buffer.
#
# @option options [ nil | :bson ] :mode Decoding mode to use.
#
# @return [ Time ] The decoded UTC datetime.
#
# @see http://bsonspec.org/#/specification
#
# @since 2.0.0
def from_bson(buffer, **options)
seconds, fragment = Int64.from_bson(buffer, mode: nil).divmod(1000)
at(seconds, fragment * 1000).utc
end
end
# Register this type when the module is loaded.
#
# @since 2.0.0
Registry.register(BSON_TYPE, ::Time)
end
# Enrich the core Time class with this module.
#
# @since 2.0.0
::Time.send(:include, Time)
::Time.send(:extend, Time::ClassMethods)
end
|