File: time.rb

package info (click to toggle)
ruby-bson 5.2.0-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 1,828 kB
  • sloc: ruby: 11,712; ansic: 1,427; java: 514; makefile: 8
file content (133 lines) | stat: -rw-r--r-- 4,502 bytes parent folder | download
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