activerecord-connection-tz 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +5 -0
- data/LICENSE.txt +21 -0
- data/README.md +110 -0
- data/lib/active_record_connection_tz/adapters/mysql2.rb +28 -0
- data/lib/active_record_connection_tz/adapters/shared.rb +94 -0
- data/lib/active_record_connection_tz/adapters/trilogy.rb +28 -0
- data/lib/active_record_connection_tz/connection_settings.rb +132 -0
- data/lib/active_record_connection_tz/temporal_formatter.rb +22 -0
- data/lib/active_record_connection_tz/temporal_parser.rb +25 -0
- data/lib/active_record_connection_tz/types/zoned_date_time.rb +31 -0
- data/lib/active_record_connection_tz/types/zoned_temporal.rb +54 -0
- data/lib/active_record_connection_tz/types/zoned_time.rb +28 -0
- data/lib/active_record_connection_tz/types.rb +18 -0
- data/lib/active_record_connection_tz/version.rb +5 -0
- data/lib/active_record_connection_tz.rb +43 -0
- data/lib/activerecord-connection-tz.rb +3 -0
- data/sig/active_record_connection_tz/adapters.rbs +34 -0
- data/sig/generated/active_record_connection_tz/adapters/mysql2.rbs +17 -0
- data/sig/generated/active_record_connection_tz/adapters/shared.rbs +37 -0
- data/sig/generated/active_record_connection_tz/adapters/trilogy.rbs +17 -0
- data/sig/generated/active_record_connection_tz/connection_settings.rbs +53 -0
- data/sig/generated/active_record_connection_tz/temporal_formatter.rbs +11 -0
- data/sig/generated/active_record_connection_tz/temporal_parser.rbs +14 -0
- data/sig/generated/active_record_connection_tz/types/zoned_date_time.rbs +20 -0
- data/sig/generated/active_record_connection_tz/types/zoned_temporal.rbs +33 -0
- data/sig/generated/active_record_connection_tz/types/zoned_time.rbs +20 -0
- data/sig/generated/active_record_connection_tz/types.rbs +8 -0
- data/sig/generated/active_record_connection_tz/version.rbs +5 -0
- data/sig/generated/active_record_connection_tz.rbs +17 -0
- data/sig/generated/activerecord-connection-tz.rbs +2 -0
- metadata +97 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: d5ef49ac42955d476269f8702a521271591abd43f0dea2b7dfbd0c5d168e855d
|
|
4
|
+
data.tar.gz: 2f8d972eb058c16c06ba1ed3851e366f2758047444d151af0664644ae9375455
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: ba880a8c2ed7274946327afd0087e1f50aa4454fe217547d3cc1256884fdbe7135f84c0805b55b814d543cc7d72ef9b9d669ef6b4e5a1bcc4c5a074704ac1f4e
|
|
7
|
+
data.tar.gz: 03c838ee16bbaa223403a5bd0c7d58c4e44104b19cc16d9ac64ed17aae4ffe21c97a12c5403ada924ffb658e5c07126af9a94ed5fba8dd5d0789a4ee53f91178
|
data/CHANGELOG.md
ADDED
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 shoma07
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# ActiveRecord Connection TZ
|
|
2
|
+
|
|
3
|
+
> ⚠️ **Early stage (v0.1.0):** This gem is still being refined. APIs, configuration details, and supported surfaces may change before the first stable release.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
`activerecord-connection-tz` helps Rails applications use **mixed-timezone MySQL connections** in the same process.
|
|
8
|
+
|
|
9
|
+
It is designed for cases where one connection follows a UTC contract while another connection needs temporal values to be interpreted with a different configured timezone.
|
|
10
|
+
|
|
11
|
+
The gem applies connection-scoped handling through `database.yml`, so the tagged connection can:
|
|
12
|
+
|
|
13
|
+
- set the MySQL session `time_zone`
|
|
14
|
+
- replace `datetime` / `time` / `timestamp` casting on a per-connection basis
|
|
15
|
+
- preserve fractional-second precision when writing temporal values
|
|
16
|
+
- work with both `mysql2` and `trilogy`
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
Add the gem together with the adapter gem you use:
|
|
21
|
+
|
|
22
|
+
```ruby
|
|
23
|
+
# Gemfile
|
|
24
|
+
gem 'activerecord-connection-tz'
|
|
25
|
+
gem 'mysql2' # or gem 'trilogy'
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
And then execute:
|
|
29
|
+
|
|
30
|
+
```sh
|
|
31
|
+
bundle install
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Quick start
|
|
35
|
+
|
|
36
|
+
The gem activates automatically for connections that declare `connection_tz` in `database.yml`.
|
|
37
|
+
|
|
38
|
+
```yml
|
|
39
|
+
production:
|
|
40
|
+
primary:
|
|
41
|
+
adapter: mysql2
|
|
42
|
+
database: primary_db
|
|
43
|
+
|
|
44
|
+
legacy_tokyo:
|
|
45
|
+
adapter: mysql2
|
|
46
|
+
database: legacy_db
|
|
47
|
+
connection_tz:
|
|
48
|
+
time_zone: Asia/Tokyo
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
No model-level or attribute-level wiring is required for the tagged connection.
|
|
52
|
+
|
|
53
|
+
For transition-free zones such as `Asia/Tokyo`, the gem can derive the MySQL session `time_zone` automatically from `time_zone`.
|
|
54
|
+
|
|
55
|
+
For zones with DST or other offset transitions, set `mysql_session_time_zone` explicitly:
|
|
56
|
+
|
|
57
|
+
```yml
|
|
58
|
+
production:
|
|
59
|
+
legacy_new_york:
|
|
60
|
+
adapter: mysql2
|
|
61
|
+
database: legacy_db
|
|
62
|
+
connection_tz:
|
|
63
|
+
time_zone: America/New_York
|
|
64
|
+
mysql_session_time_zone: America/New_York
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
If those DST-style zones use `TIMESTAMP` columns, prefer a **named MySQL timezone** such as `America/New_York` instead of a fixed offset such as `-05:00`. A fixed offset can skew `TIMESTAMP` reads across DST boundaries because MySQL converts `TIMESTAMP` values through the session timezone, while `DATETIME` values are not affected by that limitation.
|
|
68
|
+
|
|
69
|
+
## Documentation
|
|
70
|
+
|
|
71
|
+
For runnable Rails examples and setup notes, see **[examples/README.md](examples/README.md)**.
|
|
72
|
+
|
|
73
|
+
The examples cover:
|
|
74
|
+
|
|
75
|
+
- Rails 7.1 / 8.0 / 8.1
|
|
76
|
+
- mysql2 and trilogy side by side
|
|
77
|
+
- shared MySQL 8 Docker setup
|
|
78
|
+
- UTC `primary` plus a tagged connection interpreted with another timezone
|
|
79
|
+
|
|
80
|
+
## Requirements
|
|
81
|
+
|
|
82
|
+
- Ruby 3.2+
|
|
83
|
+
- Active Record 7.1 - 8.1
|
|
84
|
+
- MySQL 5.7+ (or a compatible server with fractional-second precision support)
|
|
85
|
+
- `mysql2` or `trilogy`
|
|
86
|
+
|
|
87
|
+
## Development
|
|
88
|
+
|
|
89
|
+
```sh
|
|
90
|
+
bundle install
|
|
91
|
+
bundle exec rbs collection install
|
|
92
|
+
bundle exec rspec
|
|
93
|
+
bundle exec rubocop
|
|
94
|
+
bundle exec rake sig:generate
|
|
95
|
+
bundle exec rake typecheck
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
To build the gem locally:
|
|
99
|
+
|
|
100
|
+
```sh
|
|
101
|
+
gem build activerecord-connection-tz.gemspec
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Contributing
|
|
105
|
+
|
|
106
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/shoma07/activerecord-connection-tz.
|
|
107
|
+
|
|
108
|
+
## License
|
|
109
|
+
|
|
110
|
+
The gem is available as open source under the terms of the [MIT License](LICENSE.txt).
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'active_support/lazy_load_hooks'
|
|
4
|
+
|
|
5
|
+
module ActiveRecordConnectionTz
|
|
6
|
+
module Adapters
|
|
7
|
+
module Mysql2
|
|
8
|
+
class << self
|
|
9
|
+
#: () -> void
|
|
10
|
+
def install!
|
|
11
|
+
Shared.install!(adapter_module: self, hook_name: :active_record_mysql2adapter)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
module InstanceMethods
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
#: () -> void
|
|
19
|
+
def disable_temporal_cast
|
|
20
|
+
# Use the adapter's public accessor even though it marks the raw
|
|
21
|
+
# connection as touched; this runs during configure_connection and
|
|
22
|
+
# avoids reaching into the adapter's ivar directly.
|
|
23
|
+
raw_connection.query_options[:cast] = false
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'active_support/lazy_load_hooks'
|
|
4
|
+
|
|
5
|
+
module ActiveRecordConnectionTz
|
|
6
|
+
module Adapters
|
|
7
|
+
module Shared
|
|
8
|
+
class << self
|
|
9
|
+
#: (adapter_module: Module, hook_name: Symbol) -> void
|
|
10
|
+
def install!(adapter_module:, hook_name:)
|
|
11
|
+
return if adapter_module.instance_variable_get(:@installed)
|
|
12
|
+
|
|
13
|
+
instance_methods = adapter_module.const_get(:InstanceMethods, false)
|
|
14
|
+
|
|
15
|
+
ActiveSupport.on_load(hook_name) do
|
|
16
|
+
singleton_class.prepend(Shared::ClassMethods) unless singleton_class < Shared::ClassMethods
|
|
17
|
+
prepend(Shared::InstanceMethods) unless self < Shared::InstanceMethods
|
|
18
|
+
prepend(instance_methods) unless self < instance_methods
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
adapter_module.instance_variable_set(:@installed, true)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
module ClassMethods
|
|
26
|
+
#: (emulate_booleans: bool, ?default_timezone: untyped, ?connection_tz: Hash[untyped, untyped]?) -> ActiveRecord::Type::TypeMap
|
|
27
|
+
def extended_type_map(emulate_booleans:, default_timezone: nil, connection_tz: nil)
|
|
28
|
+
super(default_timezone: default_timezone, emulate_booleans: emulate_booleans).tap do |map|
|
|
29
|
+
next unless connection_tz
|
|
30
|
+
|
|
31
|
+
settings = ConnectionSettings.new(connection_tz)
|
|
32
|
+
time_zone = settings.time_zone || raise(ConfigurationError, 'connection_tz time_zone must be present')
|
|
33
|
+
Types.register_mysql_temporal_types!(map, time_zone: time_zone)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
module InstanceMethods
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
#: () -> untyped
|
|
42
|
+
def configure_connection
|
|
43
|
+
settings = connection_tz_settings
|
|
44
|
+
@config = settings.apply_mysql_variables(@config)
|
|
45
|
+
super
|
|
46
|
+
|
|
47
|
+
disable_temporal_cast if settings.enabled?
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
#: () -> untyped
|
|
51
|
+
def extended_type_map_key
|
|
52
|
+
settings = connection_tz_settings
|
|
53
|
+
return super unless settings.enabled?
|
|
54
|
+
|
|
55
|
+
(super || { emulate_booleans: emulate_booleans }).merge(connection_tz: settings.type_map_key)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
#: (untyped value) -> untyped
|
|
59
|
+
def quoted_date(value)
|
|
60
|
+
settings = connection_tz_settings
|
|
61
|
+
return super unless settings.enabled? && value.acts_like?(:time)
|
|
62
|
+
|
|
63
|
+
# `enabled?` guarantees the configured timezone is present for this connection.
|
|
64
|
+
# The `|| raise` keeps Steep narrowed from `ActiveSupport::TimeZone?`.
|
|
65
|
+
time_zone = settings.time_zone || raise(ConfigurationError, 'connection_tz time_zone must be present')
|
|
66
|
+
TemporalFormatter.quoted_date(value, time_zone: time_zone)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
#: (untyped value) -> untyped
|
|
70
|
+
def quoted_time(value)
|
|
71
|
+
settings = connection_tz_settings
|
|
72
|
+
return super unless settings.enabled? && value.acts_like?(:time)
|
|
73
|
+
|
|
74
|
+
# `enabled?` guarantees the configured timezone is present for this connection.
|
|
75
|
+
# The `|| raise` keeps Steep narrowed from `ActiveSupport::TimeZone?`.
|
|
76
|
+
time_zone = settings.time_zone || raise(ConfigurationError, 'connection_tz time_zone must be present')
|
|
77
|
+
TemporalFormatter.quoted_time(value, time_zone: time_zone)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
#: () -> ConnectionSettings
|
|
81
|
+
def connection_tz_settings
|
|
82
|
+
# Active Record keeps the connection configuration in `@config`
|
|
83
|
+
# without exposing a stable reader on the adapter instance.
|
|
84
|
+
@connection_tz_settings ||= ConnectionSettings.from_config(@config)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
#: () -> void
|
|
88
|
+
def disable_temporal_cast
|
|
89
|
+
raise NotImplementedError, "#{self.class} must implement #disable_temporal_cast"
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'active_support/lazy_load_hooks'
|
|
4
|
+
|
|
5
|
+
module ActiveRecordConnectionTz
|
|
6
|
+
module Adapters
|
|
7
|
+
module Trilogy
|
|
8
|
+
class << self
|
|
9
|
+
#: () -> void
|
|
10
|
+
def install!
|
|
11
|
+
Shared.install!(adapter_module: self, hook_name: :active_record_trilogyadapter)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
module InstanceMethods
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
#: () -> void
|
|
19
|
+
def disable_temporal_cast
|
|
20
|
+
# Use the adapter's public accessor even though it marks the raw
|
|
21
|
+
# connection as touched; this runs during configure_connection and
|
|
22
|
+
# avoids reaching into the adapter's ivar directly.
|
|
23
|
+
raw_connection.query_flags &= ~::Trilogy::QUERY_FLAGS_CAST
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'active_support/time'
|
|
4
|
+
|
|
5
|
+
module ActiveRecordConnectionTz
|
|
6
|
+
class ConnectionSettings
|
|
7
|
+
KEY = 'connection_tz' #: String
|
|
8
|
+
private_constant :KEY
|
|
9
|
+
|
|
10
|
+
# Range used to detect whether a timezone stays fixed-offset for the practical
|
|
11
|
+
# lifetime of MySQL TIMESTAMP data, starting at the Unix epoch and extending
|
|
12
|
+
# slightly beyond 2038 to give the transition check some future slack.
|
|
13
|
+
AUTO_SESSION_TIME_ZONE_FROM = Time.utc(1970, 1, 1).freeze #: Time
|
|
14
|
+
private_constant :AUTO_SESSION_TIME_ZONE_FROM
|
|
15
|
+
|
|
16
|
+
AUTO_SESSION_TIME_ZONE_TO = Time.utc(2050, 1, 1).freeze #: Time
|
|
17
|
+
private_constant :AUTO_SESSION_TIME_ZONE_TO
|
|
18
|
+
|
|
19
|
+
attr_reader :time_zone_name #: String?
|
|
20
|
+
attr_reader :explicit_mysql_session_time_zone #: String?
|
|
21
|
+
|
|
22
|
+
class << self
|
|
23
|
+
#: (Hash[untyped, untyped] config) -> ConnectionSettings
|
|
24
|
+
def from_config(config)
|
|
25
|
+
new(config[KEY] || config[KEY.to_sym])
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
#: (Hash[untyped, untyped]? raw) -> void
|
|
30
|
+
def initialize(raw)
|
|
31
|
+
raw = normalize_hash(raw)
|
|
32
|
+
|
|
33
|
+
@time_zone_name = raw.fetch('time_zone', nil)
|
|
34
|
+
@explicit_mysql_session_time_zone = raw.fetch('mysql_session_time_zone', nil)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
#: () -> bool
|
|
38
|
+
def enabled?
|
|
39
|
+
!time_zone_name.to_s.empty?
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
#: () -> ActiveSupport::TimeZone?
|
|
43
|
+
def time_zone
|
|
44
|
+
return unless enabled?
|
|
45
|
+
|
|
46
|
+
# `enabled?` guarantees `time_zone_name` is present here.
|
|
47
|
+
# The `|| raise` keeps Steep's type narrowed from `String?` to `String`.
|
|
48
|
+
name = time_zone_name || raise(ConfigurationError, 'connection_tz time_zone must be present')
|
|
49
|
+
|
|
50
|
+
@time_zone ||= ActiveSupport::TimeZone[name] || raise(
|
|
51
|
+
ConfigurationError,
|
|
52
|
+
"Unknown connection_tz time_zone: #{name.inspect}"
|
|
53
|
+
)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
#: () -> Hash[String, String]?
|
|
57
|
+
def type_map_key
|
|
58
|
+
return unless enabled?
|
|
59
|
+
|
|
60
|
+
# `enabled?` guarantees `time_zone_name` is present here.
|
|
61
|
+
# The `|| raise` keeps Steep's type narrowed from `String?` to `String`.
|
|
62
|
+
name = time_zone_name || raise(ConfigurationError, 'connection_tz time_zone must be present')
|
|
63
|
+
|
|
64
|
+
{
|
|
65
|
+
'time_zone' => name
|
|
66
|
+
}
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
#: () -> String?
|
|
70
|
+
def mysql_session_time_zone
|
|
71
|
+
explicit_mysql_session_time_zone || auto_mysql_session_time_zone
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
#: (Hash[untyped, untyped] config) -> Hash[untyped, untyped]
|
|
75
|
+
def apply_mysql_variables(config)
|
|
76
|
+
return config unless enabled?
|
|
77
|
+
|
|
78
|
+
variables = normalize_hash(config['variables'] || config[:variables], context: 'connection_tz.variables')
|
|
79
|
+
return config if variables['time_zone']
|
|
80
|
+
|
|
81
|
+
session_time_zone = mysql_session_time_zone || raise(
|
|
82
|
+
ConfigurationError,
|
|
83
|
+
"connection_tz time_zone #{time_zone_name.inspect} is not fixed-offset " \
|
|
84
|
+
'between 1970 and 2050; set mysql_session_time_zone explicitly'
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
variables['time_zone'] = session_time_zone
|
|
88
|
+
|
|
89
|
+
config.merge(variables: variables.dup, 'variables' => variables.dup)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
private
|
|
93
|
+
|
|
94
|
+
#: () -> String?
|
|
95
|
+
def auto_mysql_session_time_zone
|
|
96
|
+
return unless fixed_offset_time_zone?
|
|
97
|
+
|
|
98
|
+
# `fixed_offset_time_zone?` already confirms the setting is enabled.
|
|
99
|
+
# Keep the `|| raise` so Steep narrows `ActiveSupport::TimeZone?`.
|
|
100
|
+
zone = time_zone || raise(ConfigurationError, 'connection_tz time_zone must be present')
|
|
101
|
+
|
|
102
|
+
format_utc_offset(zone.utc_offset)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
#: () -> bool
|
|
106
|
+
def fixed_offset_time_zone?
|
|
107
|
+
zone = time_zone
|
|
108
|
+
return false unless zone
|
|
109
|
+
|
|
110
|
+
zone.tzinfo.transitions_up_to(AUTO_SESSION_TIME_ZONE_TO, AUTO_SESSION_TIME_ZONE_FROM).empty?
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
#: (Integer offset_seconds) -> String
|
|
114
|
+
def format_utc_offset(offset_seconds)
|
|
115
|
+
sign = offset_seconds.negative? ? '-' : '+'
|
|
116
|
+
hours, remainder = offset_seconds.abs.divmod(3600)
|
|
117
|
+
|
|
118
|
+
format('%<sign>s%<hours>02d:%<minutes>02d', sign:, hours:, minutes: remainder / 60)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
#: (untyped value, ?context: String) -> Hash[String, untyped]
|
|
122
|
+
def normalize_hash(value, context: 'connection_tz configuration')
|
|
123
|
+
return {} if value.nil?
|
|
124
|
+
unless value.is_a?(Hash)
|
|
125
|
+
raise ConfigurationError,
|
|
126
|
+
"#{context} must be a Hash, got #{value.class} (#{value.inspect})"
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
value.transform_keys(&:to_s)
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveRecordConnectionTz
|
|
4
|
+
module TemporalFormatter
|
|
5
|
+
class << self
|
|
6
|
+
#: (Time | DateTime | ActiveSupport::TimeWithZone value, time_zone: ActiveSupport::TimeZone) -> String
|
|
7
|
+
def quoted_date(value, time_zone:)
|
|
8
|
+
zoned = value.in_time_zone(time_zone)
|
|
9
|
+
result = zoned.strftime('%Y-%m-%d %H:%M:%S')
|
|
10
|
+
return "#{result}.#{format('%06d', zoned.usec)}" if zoned.usec.positive?
|
|
11
|
+
|
|
12
|
+
result
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
#: (Time | DateTime | ActiveSupport::TimeWithZone value, time_zone: ActiveSupport::TimeZone) -> String
|
|
16
|
+
def quoted_time(value, time_zone:)
|
|
17
|
+
# Use the same stable anchor date ActiveRecord::Type::Time conventionally relies on for TIME values.
|
|
18
|
+
quoted_date(value.change(year: 2000, month: 1, day: 1), time_zone: time_zone).sub(/\A\d{4}-\d{2}-\d{2} /, '')
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveRecordConnectionTz
|
|
4
|
+
module TemporalParser
|
|
5
|
+
# Treat MySQL year-zero and zero/partial-zero dates as nil instead of passing invalid dates to TimeZone parsing.
|
|
6
|
+
ZERO_DATETIME = /\A(?:0000-\d{2}-\d{2}|\d{4}-(?:00-\d{2}|\d{2}-00))/ #: Regexp
|
|
7
|
+
private_constant :ZERO_DATETIME
|
|
8
|
+
|
|
9
|
+
class << self
|
|
10
|
+
#: (String? value, time_zone: ActiveSupport::TimeZone) -> ActiveSupport::TimeWithZone?
|
|
11
|
+
def parse_datetime(value, time_zone:)
|
|
12
|
+
return if value.nil? || value == '' || value.match?(ZERO_DATETIME)
|
|
13
|
+
|
|
14
|
+
time_zone.parse(value)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
#: (String? value, time_zone: ActiveSupport::TimeZone) -> ActiveSupport::TimeWithZone?
|
|
18
|
+
def parse_time(value, time_zone:)
|
|
19
|
+
return if value.nil? || value == ''
|
|
20
|
+
|
|
21
|
+
time_zone.parse("2000-01-01 #{value}")
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveRecordConnectionTz
|
|
4
|
+
module Types
|
|
5
|
+
class ZonedDateTime < ActiveRecord::Type::DateTime
|
|
6
|
+
include ZonedTemporal
|
|
7
|
+
|
|
8
|
+
#: (untyped value) -> untyped
|
|
9
|
+
def cast(value)
|
|
10
|
+
# ActiveRecord::Type::DateTime#cast converts TimeWithZone to DateTime,
|
|
11
|
+
# dropping the original zone. Keep already-zoned values intact.
|
|
12
|
+
return value if value.is_a?(ActiveSupport::TimeWithZone)
|
|
13
|
+
return super unless value.is_a?(String)
|
|
14
|
+
|
|
15
|
+
parse_string(value)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
#: (String value) -> ActiveSupport::TimeWithZone?
|
|
21
|
+
def parse_string(value)
|
|
22
|
+
TemporalParser.parse_datetime(value, time_zone: time_zone)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
#: (untyped casted) -> String
|
|
26
|
+
def quote_casted(casted)
|
|
27
|
+
TemporalFormatter.quoted_date(casted, time_zone: time_zone)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveRecordConnectionTz
|
|
4
|
+
module Types
|
|
5
|
+
# @rbs module-self ActiveModel::Type::Value
|
|
6
|
+
module ZonedTemporal
|
|
7
|
+
attr_reader :time_zone #: ActiveSupport::TimeZone
|
|
8
|
+
|
|
9
|
+
#: (time_zone: ActiveSupport::TimeZone) -> void
|
|
10
|
+
def initialize(time_zone:)
|
|
11
|
+
super()
|
|
12
|
+
@time_zone = time_zone
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
#: (untyped value) -> untyped
|
|
16
|
+
def deserialize(value)
|
|
17
|
+
return super unless value.is_a?(String)
|
|
18
|
+
|
|
19
|
+
parse_string(value)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
#: (untyped value) -> untyped
|
|
23
|
+
def serialize(value)
|
|
24
|
+
serialize_temporal(value)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
#: (untyped value) -> untyped
|
|
28
|
+
def serialize_cast_value(value) # :nodoc:
|
|
29
|
+
serialize_temporal(value)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
#: (untyped value) -> untyped
|
|
35
|
+
def serialize_temporal(value)
|
|
36
|
+
casted = cast(value)
|
|
37
|
+
return if casted.nil?
|
|
38
|
+
return casted unless casted.acts_like?(:time)
|
|
39
|
+
|
|
40
|
+
quote_casted(casted)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
#: (String _value) -> untyped
|
|
44
|
+
def parse_string(_value)
|
|
45
|
+
raise NotImplementedError, "#{self.class} must implement #parse_string"
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
#: (untyped _value) -> String
|
|
49
|
+
def quote_casted(_value)
|
|
50
|
+
raise NotImplementedError, "#{self.class} must implement #quote_casted"
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveRecordConnectionTz
|
|
4
|
+
module Types
|
|
5
|
+
class ZonedTime < ActiveRecord::Type::Time
|
|
6
|
+
include ZonedTemporal
|
|
7
|
+
|
|
8
|
+
#: (untyped value) -> untyped
|
|
9
|
+
def cast(value)
|
|
10
|
+
return super unless value.is_a?(String)
|
|
11
|
+
|
|
12
|
+
parse_string(value)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
#: (String value) -> ActiveSupport::TimeWithZone?
|
|
18
|
+
def parse_string(value)
|
|
19
|
+
TemporalParser.parse_time(value, time_zone: time_zone)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
#: (untyped casted) -> String
|
|
23
|
+
def quote_casted(casted)
|
|
24
|
+
TemporalFormatter.quoted_time(casted, time_zone: time_zone)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'types/zoned_temporal'
|
|
4
|
+
require_relative 'types/zoned_date_time'
|
|
5
|
+
require_relative 'types/zoned_time'
|
|
6
|
+
|
|
7
|
+
module ActiveRecordConnectionTz
|
|
8
|
+
module Types
|
|
9
|
+
class << self
|
|
10
|
+
#: (ActiveRecord::Type::TypeMap map, time_zone: ActiveSupport::TimeZone) -> void
|
|
11
|
+
def register_mysql_temporal_types!(map, time_zone:)
|
|
12
|
+
map.register_type(/\Adatetime\b/i) { |_sql_type| ZonedDateTime.new(time_zone: time_zone) }
|
|
13
|
+
map.register_type(/\Atime\b/i) { |_sql_type| ZonedTime.new(time_zone: time_zone) }
|
|
14
|
+
map.alias_type(/\Atimestamp\b/i, 'datetime')
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'active_support'
|
|
4
|
+
require 'active_support/lazy_load_hooks'
|
|
5
|
+
require 'active_record'
|
|
6
|
+
|
|
7
|
+
require_relative 'active_record_connection_tz/version'
|
|
8
|
+
require_relative 'active_record_connection_tz/connection_settings'
|
|
9
|
+
require_relative 'active_record_connection_tz/temporal_formatter'
|
|
10
|
+
require_relative 'active_record_connection_tz/temporal_parser'
|
|
11
|
+
require_relative 'active_record_connection_tz/types'
|
|
12
|
+
require_relative 'active_record_connection_tz/adapters/shared'
|
|
13
|
+
require_relative 'active_record_connection_tz/adapters/mysql2'
|
|
14
|
+
require_relative 'active_record_connection_tz/adapters/trilogy'
|
|
15
|
+
|
|
16
|
+
module ActiveRecordConnectionTz
|
|
17
|
+
class Error < StandardError; end
|
|
18
|
+
class ConfigurationError < Error; end
|
|
19
|
+
INSTALL_MUTEX = Mutex.new #: Mutex
|
|
20
|
+
private_constant :INSTALL_MUTEX
|
|
21
|
+
|
|
22
|
+
class << self
|
|
23
|
+
#: () -> void
|
|
24
|
+
def install!
|
|
25
|
+
return if @installed
|
|
26
|
+
|
|
27
|
+
INSTALL_MUTEX.synchronize do
|
|
28
|
+
return if @installed
|
|
29
|
+
|
|
30
|
+
Adapters::Mysql2.install!
|
|
31
|
+
Adapters::Trilogy.install!
|
|
32
|
+
@installed = true
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
#: () -> bool
|
|
37
|
+
def installed?
|
|
38
|
+
@installed == true
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
ActiveRecordConnectionTz.install!
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
module ActiveRecordConnectionTz
|
|
2
|
+
module Adapters
|
|
3
|
+
interface _SharedClassMethodsHost
|
|
4
|
+
def extended_type_map: (emulate_booleans: bool, ?default_timezone: untyped, ?connection_tz: Hash[untyped, untyped]?) -> ActiveRecord::Type::TypeMap
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
interface _SharedInstanceMethodsHost
|
|
8
|
+
def config: () -> Hash[untyped, untyped]
|
|
9
|
+
def configure_connection: () -> untyped
|
|
10
|
+
def extended_type_map_key: () -> untyped
|
|
11
|
+
def quoted_date: (untyped value) -> untyped
|
|
12
|
+
def quoted_time: (untyped value) -> untyped
|
|
13
|
+
def emulate_booleans: () -> bool
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
module Shared
|
|
17
|
+
module ClassMethods : _SharedClassMethodsHost
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
module InstanceMethods : _SharedInstanceMethodsHost
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
module Mysql2
|
|
25
|
+
module InstanceMethods : ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
module Trilogy
|
|
30
|
+
module InstanceMethods : ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Generated from lib/active_record_connection_tz/adapters/mysql2.rb with RBS::Inline
|
|
2
|
+
|
|
3
|
+
module ActiveRecordConnectionTz
|
|
4
|
+
module Adapters
|
|
5
|
+
module Mysql2
|
|
6
|
+
# : () -> void
|
|
7
|
+
def self.install!: () -> void
|
|
8
|
+
|
|
9
|
+
module InstanceMethods
|
|
10
|
+
private
|
|
11
|
+
|
|
12
|
+
# : () -> void
|
|
13
|
+
def disable_temporal_cast: () -> void
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Generated from lib/active_record_connection_tz/adapters/shared.rb with RBS::Inline
|
|
2
|
+
|
|
3
|
+
module ActiveRecordConnectionTz
|
|
4
|
+
module Adapters
|
|
5
|
+
module Shared
|
|
6
|
+
# : (adapter_module: Module, hook_name: Symbol) -> void
|
|
7
|
+
def self.install!: (adapter_module: Module, hook_name: Symbol) -> void
|
|
8
|
+
|
|
9
|
+
module ClassMethods
|
|
10
|
+
# : (emulate_booleans: bool, ?default_timezone: untyped, ?connection_tz: Hash[untyped, untyped]?) -> ActiveRecord::Type::TypeMap
|
|
11
|
+
def extended_type_map: (emulate_booleans: bool, ?default_timezone: untyped, ?connection_tz: Hash[untyped, untyped]?) -> ActiveRecord::Type::TypeMap
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
module InstanceMethods
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
# : () -> untyped
|
|
18
|
+
def configure_connection: () -> untyped
|
|
19
|
+
|
|
20
|
+
# : () -> untyped
|
|
21
|
+
def extended_type_map_key: () -> untyped
|
|
22
|
+
|
|
23
|
+
# : (untyped value) -> untyped
|
|
24
|
+
def quoted_date: (untyped value) -> untyped
|
|
25
|
+
|
|
26
|
+
# : (untyped value) -> untyped
|
|
27
|
+
def quoted_time: (untyped value) -> untyped
|
|
28
|
+
|
|
29
|
+
# : () -> ConnectionSettings
|
|
30
|
+
def connection_tz_settings: () -> ConnectionSettings
|
|
31
|
+
|
|
32
|
+
# : () -> void
|
|
33
|
+
def disable_temporal_cast: () -> void
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Generated from lib/active_record_connection_tz/adapters/trilogy.rb with RBS::Inline
|
|
2
|
+
|
|
3
|
+
module ActiveRecordConnectionTz
|
|
4
|
+
module Adapters
|
|
5
|
+
module Trilogy
|
|
6
|
+
# : () -> void
|
|
7
|
+
def self.install!: () -> void
|
|
8
|
+
|
|
9
|
+
module InstanceMethods
|
|
10
|
+
private
|
|
11
|
+
|
|
12
|
+
# : () -> void
|
|
13
|
+
def disable_temporal_cast: () -> void
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Generated from lib/active_record_connection_tz/connection_settings.rb with RBS::Inline
|
|
2
|
+
|
|
3
|
+
module ActiveRecordConnectionTz
|
|
4
|
+
class ConnectionSettings
|
|
5
|
+
KEY: String
|
|
6
|
+
|
|
7
|
+
# Range used to detect whether a timezone stays fixed-offset for the practical
|
|
8
|
+
# lifetime of MySQL TIMESTAMP data, starting at the Unix epoch and extending
|
|
9
|
+
# slightly beyond 2038 to give the transition check some future slack.
|
|
10
|
+
AUTO_SESSION_TIME_ZONE_FROM: Time
|
|
11
|
+
|
|
12
|
+
AUTO_SESSION_TIME_ZONE_TO: Time
|
|
13
|
+
|
|
14
|
+
attr_reader time_zone_name: String?
|
|
15
|
+
|
|
16
|
+
attr_reader explicit_mysql_session_time_zone: String?
|
|
17
|
+
|
|
18
|
+
# : (Hash[untyped, untyped] config) -> ConnectionSettings
|
|
19
|
+
def self.from_config: (Hash[untyped, untyped] config) -> ConnectionSettings
|
|
20
|
+
|
|
21
|
+
# : (Hash[untyped, untyped]? raw) -> void
|
|
22
|
+
def initialize: (Hash[untyped, untyped]? raw) -> void
|
|
23
|
+
|
|
24
|
+
# : () -> bool
|
|
25
|
+
def enabled?: () -> bool
|
|
26
|
+
|
|
27
|
+
# : () -> ActiveSupport::TimeZone?
|
|
28
|
+
def time_zone: () -> ActiveSupport::TimeZone?
|
|
29
|
+
|
|
30
|
+
# : () -> Hash[String, String]?
|
|
31
|
+
def type_map_key: () -> Hash[String, String]?
|
|
32
|
+
|
|
33
|
+
# : () -> String?
|
|
34
|
+
def mysql_session_time_zone: () -> String?
|
|
35
|
+
|
|
36
|
+
# : (Hash[untyped, untyped] config) -> Hash[untyped, untyped]
|
|
37
|
+
def apply_mysql_variables: (Hash[untyped, untyped] config) -> Hash[untyped, untyped]
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
# : () -> String?
|
|
42
|
+
def auto_mysql_session_time_zone: () -> String?
|
|
43
|
+
|
|
44
|
+
# : () -> bool
|
|
45
|
+
def fixed_offset_time_zone?: () -> bool
|
|
46
|
+
|
|
47
|
+
# : (Integer offset_seconds) -> String
|
|
48
|
+
def format_utc_offset: (Integer offset_seconds) -> String
|
|
49
|
+
|
|
50
|
+
# : (untyped value, ?context: String) -> Hash[String, untyped]
|
|
51
|
+
def normalize_hash: (untyped value, ?context: String) -> Hash[String, untyped]
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Generated from lib/active_record_connection_tz/temporal_formatter.rb with RBS::Inline
|
|
2
|
+
|
|
3
|
+
module ActiveRecordConnectionTz
|
|
4
|
+
module TemporalFormatter
|
|
5
|
+
# : (Time | DateTime | ActiveSupport::TimeWithZone value, time_zone: ActiveSupport::TimeZone) -> String
|
|
6
|
+
def self.quoted_date: (Time | DateTime | ActiveSupport::TimeWithZone value, time_zone: ActiveSupport::TimeZone) -> String
|
|
7
|
+
|
|
8
|
+
# : (Time | DateTime | ActiveSupport::TimeWithZone value, time_zone: ActiveSupport::TimeZone) -> String
|
|
9
|
+
def self.quoted_time: (Time | DateTime | ActiveSupport::TimeWithZone value, time_zone: ActiveSupport::TimeZone) -> String
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Generated from lib/active_record_connection_tz/temporal_parser.rb with RBS::Inline
|
|
2
|
+
|
|
3
|
+
module ActiveRecordConnectionTz
|
|
4
|
+
module TemporalParser
|
|
5
|
+
# Treat MySQL year-zero and zero/partial-zero dates as nil instead of passing invalid dates to TimeZone parsing.
|
|
6
|
+
ZERO_DATETIME: Regexp
|
|
7
|
+
|
|
8
|
+
# : (String? value, time_zone: ActiveSupport::TimeZone) -> ActiveSupport::TimeWithZone?
|
|
9
|
+
def self.parse_datetime: (String? value, time_zone: ActiveSupport::TimeZone) -> ActiveSupport::TimeWithZone?
|
|
10
|
+
|
|
11
|
+
# : (String? value, time_zone: ActiveSupport::TimeZone) -> ActiveSupport::TimeWithZone?
|
|
12
|
+
def self.parse_time: (String? value, time_zone: ActiveSupport::TimeZone) -> ActiveSupport::TimeWithZone?
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Generated from lib/active_record_connection_tz/types/zoned_date_time.rb with RBS::Inline
|
|
2
|
+
|
|
3
|
+
module ActiveRecordConnectionTz
|
|
4
|
+
module Types
|
|
5
|
+
class ZonedDateTime < ActiveRecord::Type::DateTime
|
|
6
|
+
include ZonedTemporal
|
|
7
|
+
|
|
8
|
+
# : (untyped value) -> untyped
|
|
9
|
+
def cast: (untyped value) -> untyped
|
|
10
|
+
|
|
11
|
+
private
|
|
12
|
+
|
|
13
|
+
# : (String value) -> ActiveSupport::TimeWithZone?
|
|
14
|
+
def parse_string: (String value) -> ActiveSupport::TimeWithZone?
|
|
15
|
+
|
|
16
|
+
# : (untyped casted) -> String
|
|
17
|
+
def quote_casted: (untyped casted) -> String
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Generated from lib/active_record_connection_tz/types/zoned_temporal.rb with RBS::Inline
|
|
2
|
+
|
|
3
|
+
module ActiveRecordConnectionTz
|
|
4
|
+
module Types
|
|
5
|
+
# @rbs module-self ActiveModel::Type::Value
|
|
6
|
+
module ZonedTemporal : ActiveModel::Type::Value
|
|
7
|
+
attr_reader time_zone: ActiveSupport::TimeZone
|
|
8
|
+
|
|
9
|
+
# : (time_zone: ActiveSupport::TimeZone) -> void
|
|
10
|
+
def initialize: (time_zone: ActiveSupport::TimeZone) -> void
|
|
11
|
+
|
|
12
|
+
# : (untyped value) -> untyped
|
|
13
|
+
def deserialize: (untyped value) -> untyped
|
|
14
|
+
|
|
15
|
+
# : (untyped value) -> untyped
|
|
16
|
+
def serialize: (untyped value) -> untyped
|
|
17
|
+
|
|
18
|
+
# : (untyped value) -> untyped
|
|
19
|
+
def serialize_cast_value: (untyped value) -> untyped
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
# : (untyped value) -> untyped
|
|
24
|
+
def serialize_temporal: (untyped value) -> untyped
|
|
25
|
+
|
|
26
|
+
# : (String _value) -> untyped
|
|
27
|
+
def parse_string: (String _value) -> untyped
|
|
28
|
+
|
|
29
|
+
# : (untyped _value) -> String
|
|
30
|
+
def quote_casted: (untyped _value) -> String
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Generated from lib/active_record_connection_tz/types/zoned_time.rb with RBS::Inline
|
|
2
|
+
|
|
3
|
+
module ActiveRecordConnectionTz
|
|
4
|
+
module Types
|
|
5
|
+
class ZonedTime < ActiveRecord::Type::Time
|
|
6
|
+
include ZonedTemporal
|
|
7
|
+
|
|
8
|
+
# : (untyped value) -> untyped
|
|
9
|
+
def cast: (untyped value) -> untyped
|
|
10
|
+
|
|
11
|
+
private
|
|
12
|
+
|
|
13
|
+
# : (String value) -> ActiveSupport::TimeWithZone?
|
|
14
|
+
def parse_string: (String value) -> ActiveSupport::TimeWithZone?
|
|
15
|
+
|
|
16
|
+
# : (untyped casted) -> String
|
|
17
|
+
def quote_casted: (untyped casted) -> String
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# Generated from lib/active_record_connection_tz/types.rb with RBS::Inline
|
|
2
|
+
|
|
3
|
+
module ActiveRecordConnectionTz
|
|
4
|
+
module Types
|
|
5
|
+
# : (ActiveRecord::Type::TypeMap map, time_zone: ActiveSupport::TimeZone) -> void
|
|
6
|
+
def self.register_mysql_temporal_types!: (ActiveRecord::Type::TypeMap map, time_zone: ActiveSupport::TimeZone) -> void
|
|
7
|
+
end
|
|
8
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Generated from lib/active_record_connection_tz.rb with RBS::Inline
|
|
2
|
+
|
|
3
|
+
module ActiveRecordConnectionTz
|
|
4
|
+
class Error < StandardError
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
class ConfigurationError < Error
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
INSTALL_MUTEX: Mutex
|
|
11
|
+
|
|
12
|
+
# : () -> void
|
|
13
|
+
def self.install!: () -> void
|
|
14
|
+
|
|
15
|
+
# : () -> bool
|
|
16
|
+
def self.installed?: () -> bool
|
|
17
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: activerecord-connection-tz
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- shoma07
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: activerecord
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '7.1'
|
|
19
|
+
- - "<"
|
|
20
|
+
- !ruby/object:Gem::Version
|
|
21
|
+
version: '8.2'
|
|
22
|
+
type: :runtime
|
|
23
|
+
prerelease: false
|
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
25
|
+
requirements:
|
|
26
|
+
- - ">="
|
|
27
|
+
- !ruby/object:Gem::Version
|
|
28
|
+
version: '7.1'
|
|
29
|
+
- - "<"
|
|
30
|
+
- !ruby/object:Gem::Version
|
|
31
|
+
version: '8.2'
|
|
32
|
+
description: |-
|
|
33
|
+
Provides connection-scoped timezone handling for applications that need to read and write
|
|
34
|
+
temporal columns with a configured timezone without per-attribute wiring.
|
|
35
|
+
email:
|
|
36
|
+
- 23730734+shoma07@users.noreply.github.com
|
|
37
|
+
executables: []
|
|
38
|
+
extensions: []
|
|
39
|
+
extra_rdoc_files: []
|
|
40
|
+
files:
|
|
41
|
+
- CHANGELOG.md
|
|
42
|
+
- LICENSE.txt
|
|
43
|
+
- README.md
|
|
44
|
+
- lib/active_record_connection_tz.rb
|
|
45
|
+
- lib/active_record_connection_tz/adapters/mysql2.rb
|
|
46
|
+
- lib/active_record_connection_tz/adapters/shared.rb
|
|
47
|
+
- lib/active_record_connection_tz/adapters/trilogy.rb
|
|
48
|
+
- lib/active_record_connection_tz/connection_settings.rb
|
|
49
|
+
- lib/active_record_connection_tz/temporal_formatter.rb
|
|
50
|
+
- lib/active_record_connection_tz/temporal_parser.rb
|
|
51
|
+
- lib/active_record_connection_tz/types.rb
|
|
52
|
+
- lib/active_record_connection_tz/types/zoned_date_time.rb
|
|
53
|
+
- lib/active_record_connection_tz/types/zoned_temporal.rb
|
|
54
|
+
- lib/active_record_connection_tz/types/zoned_time.rb
|
|
55
|
+
- lib/active_record_connection_tz/version.rb
|
|
56
|
+
- lib/activerecord-connection-tz.rb
|
|
57
|
+
- sig/active_record_connection_tz/adapters.rbs
|
|
58
|
+
- sig/generated/active_record_connection_tz.rbs
|
|
59
|
+
- sig/generated/active_record_connection_tz/adapters/mysql2.rbs
|
|
60
|
+
- sig/generated/active_record_connection_tz/adapters/shared.rbs
|
|
61
|
+
- sig/generated/active_record_connection_tz/adapters/trilogy.rbs
|
|
62
|
+
- sig/generated/active_record_connection_tz/connection_settings.rbs
|
|
63
|
+
- sig/generated/active_record_connection_tz/temporal_formatter.rbs
|
|
64
|
+
- sig/generated/active_record_connection_tz/temporal_parser.rbs
|
|
65
|
+
- sig/generated/active_record_connection_tz/types.rbs
|
|
66
|
+
- sig/generated/active_record_connection_tz/types/zoned_date_time.rbs
|
|
67
|
+
- sig/generated/active_record_connection_tz/types/zoned_temporal.rbs
|
|
68
|
+
- sig/generated/active_record_connection_tz/types/zoned_time.rbs
|
|
69
|
+
- sig/generated/active_record_connection_tz/version.rbs
|
|
70
|
+
- sig/generated/activerecord-connection-tz.rbs
|
|
71
|
+
homepage: https://github.com/shoma07/activerecord-connection-tz
|
|
72
|
+
licenses:
|
|
73
|
+
- MIT
|
|
74
|
+
metadata:
|
|
75
|
+
allowed_push_host: https://rubygems.org
|
|
76
|
+
homepage_uri: https://github.com/shoma07/activerecord-connection-tz
|
|
77
|
+
source_code_uri: https://github.com/shoma07/activerecord-connection-tz/tree/v0.1.0
|
|
78
|
+
changelog_uri: https://github.com/shoma07/activerecord-connection-tz/blob/v0.1.0/CHANGELOG.md
|
|
79
|
+
rubygems_mfa_required: 'true'
|
|
80
|
+
rdoc_options: []
|
|
81
|
+
require_paths:
|
|
82
|
+
- lib
|
|
83
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
84
|
+
requirements:
|
|
85
|
+
- - ">="
|
|
86
|
+
- !ruby/object:Gem::Version
|
|
87
|
+
version: 3.2.0
|
|
88
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
89
|
+
requirements:
|
|
90
|
+
- - ">="
|
|
91
|
+
- !ruby/object:Gem::Version
|
|
92
|
+
version: '0'
|
|
93
|
+
requirements: []
|
|
94
|
+
rubygems_version: 4.0.3
|
|
95
|
+
specification_version: 4
|
|
96
|
+
summary: Connection-scoped timezone handling for mixed-timezone Active Record apps
|
|
97
|
+
test_files: []
|