jiff-magnus 0.0.1

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: a6f9c863e107b4351d7c9e56a504aaec9f428a1862174281eb4c5007461f2304
4
+ data.tar.gz: '039d66e2dc37e5d5aca5795019e186e430f9c006bbec093ba531ac2cce8e5ef1'
5
+ SHA512:
6
+ metadata.gz: 76209b92a010871e7a910ee4f2fbf1136c668fbbcd7153ba400d039cc2dfb5e181ee2ee97173f5a90fe05eb7ba2dd9541055175917f660a203c9e5cbfbea78f2
7
+ data.tar.gz: 809ffdabfe974ef1561b3752bba1ef429e5a2d8669dc000575e7187edc9f0db758831c0a2a6535d688797cb484a91c560afb3ac848c9ea3b33d49cfbb83706e4
data/.gitignore ADDED
@@ -0,0 +1,8 @@
1
+ /pkg/
2
+ !/tmp/
3
+ /Gemfile.lock
4
+ /Makefile
5
+ /mkmf.log
6
+ /*.so
7
+ /*.bundle
8
+ /*.dll
data/Cargo.toml ADDED
@@ -0,0 +1,16 @@
1
+ [package]
2
+ name = "jiff-magnus"
3
+ version = "0.0.1"
4
+ edition = "2024"
5
+ rust-version = "1.85"
6
+ description = "Jiff timezone constructors and bundled timezone data for Ruby Time."
7
+ repository = "https://github.com/matsadler/magnus"
8
+ license = "MIT"
9
+
10
+ [dependencies]
11
+ jiff = { version = "0.2.35", default-features = false, features = ["std", "tzdb-bundle-always"] }
12
+ magnus = { version = "0.10.0", default-features = false, features = ["jiff-zoned", "rb-sys"] }
13
+ rb-sys = { version = "0.9.124", default-features = false, features = ["stable-api-compiled-fallback"] }
14
+
15
+ [dev-dependencies]
16
+ magnus = { version = "0.10.0", default-features = false, features = ["embed", "jiff-zoned", "rb-sys"] }
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Erick Guan
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,182 @@
1
+ # jiff-magnus
2
+
3
+ A Magnus companion gem that provides Jiff timezones for Ruby `Time`.
4
+ It bundles IANA timezone data.
5
+
6
+ ## Install and use
7
+
8
+ ```sh
9
+ gem install jiff-magnus --version 0.0.1
10
+ ```
11
+
12
+ ```ruby
13
+ require "jiff_magnus"
14
+
15
+ zone = JiffTimezone.find_timezone("America/New_York")
16
+ time = Time.new(2024, 7, 1, 12, 0, 0, in: zone)
17
+ time.utc_offset # => -14400
18
+ time.dst? # => true
19
+ time.strftime("%Z") # => "EDT"
20
+
21
+ # By default, requiring the gem installs Time.find_timezone if absent.
22
+ Time.at(0, in: "Asia/Tokyo")
23
+
24
+ JiffTimezone.utc
25
+ JiffTimezone.fixed(19_800) # Seconds east of UTC.
26
+ JiffTimezone.posix("EST5EDT,M3.2.0,M11.1.0")
27
+ JiffTimezone.unknown
28
+ ```
29
+
30
+ `find_timezone` returns `nil` for unknown names. Invalid fixed offsets and POSIX
31
+ rules raise `ArgumentError`. Timezone objects are frozen; ordinary `new` and
32
+ `allocate` are disabled. `iana_name` returns `nil` when a zone has no IANA name.
33
+ `inspect` shows the class, timezone label, and portable representation.
34
+
35
+ Local times in DST gaps and repeated local times raise `ArgumentError`.
36
+ Construct from an instant to select either occurrence of an overlap:
37
+
38
+ ```ruby
39
+ Time.at(Time.utc(2024, 11, 3, 5, 30).to_i, in: "America/New_York") # EDT
40
+ Time.at(Time.utc(2024, 11, 3, 6, 30).to_i, in: "America/New_York") # EST
41
+ ```
42
+
43
+ ## Initialization and Marshal
44
+
45
+ Requiring the gem initializes `JiffTimezone`. By default, it also defines
46
+ `Time.find_timezone` if absent. Existing lookup methods, including private ones,
47
+ are preserved. Repeated `require` calls reuse the loaded extension. An existing
48
+ `JiffTimezone` class is reused when it inherits from the same Magnus copy's
49
+ timezone class; its public methods are neither checked nor replaced.
50
+
51
+ To skip automatic `Time.find_timezone` registration, set
52
+ `JIFF_MAGNUS_ENABLE_AUTO_TIME_REGISTRATION` to `0` or `false` before requiring
53
+ the gem. `false` is case-insensitive; other values and an unset variable enable
54
+ automatic registration. This setting is read on the first initialization.
55
+ Timezone constructors and Magnus conversions remain available.
56
+
57
+ ```ruby
58
+ ENV["JIFF_MAGNUS_ENABLE_AUTO_TIME_REGISTRATION"] = "0"
59
+ require "jiff_magnus"
60
+
61
+ zone = JiffTimezone.find_timezone("Asia/Tokyo")
62
+ Time.at(0, in: zone)
63
+ ```
64
+
65
+ To explicitly install or replace name lookup with Jiff, even with automatic
66
+ registration disabled:
67
+
68
+ ```ruby
69
+ JiffTimezone.register
70
+ Time.at(0, in: "Asia/Tokyo")
71
+ ```
72
+
73
+ Rust callers can use `jiff_magnus::register(ruby)` for the same operation.
74
+ Registration makes `Time.find_timezone` public. If replacement fails, the
75
+ previous method and its visibility are restored.
76
+
77
+ An unrelated class or a class from another Magnus build raises `LoadError`.
78
+ An unrelated `TimeZone` constant does not conflict. Repeated native
79
+ initialization raises `LoadError` if the cached class or a tracked lookup method
80
+ was removed or replaced unexpectedly. Explicit registration updates that cached
81
+ method. Reentrant initialization or registration raises `LoadError`; the outer
82
+ operation can roll back and be retried. A failed initialization removes only
83
+ definitions it added, preserving application definitions created by Ruby hooks.
84
+ Failed resolver replacement also preserves an explicitly disabled inherited
85
+ lookup.
86
+
87
+ Require the gem before loading marshalled named-zone times. Native fixed-offset
88
+ times need no resolver. POSIX, unknown, and explicitly wrapped fixed-offset
89
+ zones have no restorable IANA name; dumping times with those objects raises
90
+ `TypeError`.
91
+
92
+ Magnus owns Rust conversions, timezone storage, and native interoperability.
93
+ See its [Jiff timezone documentation](../docs/jiff-timezones.md) for conversion
94
+ behavior, internal objects, and limits when extensions use different builds.
95
+ The companion owns public constructors, lookup, and initialization.
96
+
97
+ ## Build and test from source
98
+
99
+ Use Ruby 3.2 or later, Rust 1.85 or later, a C compiler, and libclang.
100
+ From this directory, run:
101
+
102
+ ```sh
103
+ bundle install
104
+ bundle exec rake
105
+ bundle exec rake build
106
+ ```
107
+
108
+ The checkout builds against the current Magnus source through `path = ".."`.
109
+ The default task runs Standard and the Ruby tests. Run `bundle exec rake standard`
110
+ to lint separately. A local Standard extension requires parentheses on `assert`
111
+ and `refute` calls in the Ruby tests; `bundle exec rake standard:fix` adds them.
112
+ The test task builds the native extension and exercises
113
+ `require`, timezone behavior, and Marshal across processes. The build task creates
114
+ the source gem under `pkg/`; distributed source gems resolve their declared
115
+ Magnus dependency from crates.io. The workspace uses its root Cargo lockfile.
116
+ Rake keeps build intermediates and temporary packaging files under `tmp/`.
117
+ Run `bundle exec rake clobber` to remove them and the generated packages while
118
+ keeping `tmp/.gitignore`.
119
+
120
+ After the required Magnus version is published, you can manually check an
121
+ installed source gem from the repository root:
122
+
123
+ ```sh
124
+ ruby docs/examples/jiff_magnus_install.rb jiff-magnus/pkg/jiff-magnus-0.0.1.gem
125
+ ```
126
+
127
+ ## Local releases
128
+
129
+ Bundler's gem tasks provide `build`, `install`, and `release`, as in `ffi-icu`.
130
+ The build task uses our staging step to remove checkout-only Cargo paths from
131
+ the source gem. The version comes from `jiff-magnus/Cargo.toml`.
132
+
133
+ After the required Magnus version is published, run from this directory on a
134
+ clean Git branch with working push credentials:
135
+
136
+ ```sh
137
+ gem signin
138
+ bundle exec rake release
139
+ ```
140
+
141
+ The release task runs Standard, the Ruby tests, and an installed-source-gem
142
+ check. It then creates a `jiff-magnus-v<version>` tag, pushes the current branch
143
+ and tag to `origin`, and publishes the gem using your local RubyGems credentials.
144
+ The tag prefix keeps companion releases separate from Magnus releases. Use
145
+ `bundle exec rake release:check` to run validation without tagging or publishing.
146
+
147
+ Bundler's full release task expects an attached Git branch. From a Jujutsu
148
+ checkout with detached Git HEAD, manage the bookmark and release tag separately,
149
+ then publish the committed revision with:
150
+
151
+ ```sh
152
+ bundle exec rake release:rubygem_push
153
+ ```
154
+
155
+ This task runs the same validation and clean-working-copy check, then publishes
156
+ without changing Git references. Local publishing needs no GitHub API-key secret.
157
+
158
+ ## GitHub Actions
159
+
160
+ The [lint workflow](../.github/workflows/jiff-magnus-lint.yml) checks Standard,
161
+ Rust formatting, and workflow YAML. The
162
+ [test workflow](../.github/workflows/jiff-magnus-test.yml) runs the Ruby suite
163
+ and builds the source gem on Ruby 3.2 through 4.0 and Ruby head on Linux, plus
164
+ Ruby 3.4 and 4.0 on macOS and Ruby 4.0 on Windows. Magnus's existing Test workflow
165
+ runs the Rust workspace tests.
166
+
167
+ Run the [release workflow](../.github/workflows/jiff-magnus-release.yml) manually
168
+ from the branch or tag you want to publish. It uses the version in
169
+ `jiff-magnus/Cargo.toml`, lints and tests the extension, builds the source gem,
170
+ checks installation against published Rust dependencies, and publishes it to
171
+ RubyGems with the `RUBYGEMS_API_KEY` repository secret.
172
+
173
+ Before the first release, create a RubyGems API key with the
174
+ [Push rubygems scope](https://guides.rubygems.org/api-key-scopes/) and save it as
175
+ the repository's `RUBYGEMS_API_KEY` Actions secret. The workflow supplies it to
176
+ `gem push` through `GEM_HOST_API_KEY`, which RubyGems supports for CI publishing.
177
+ Choose key settings that allow unattended pushes; a key that requires an OTP
178
+ will still require that code. Trusted publishing is an optional later change.
179
+
180
+ The required Magnus crate with `jiff-zoned` must be available on crates.io
181
+ before the installation check can pass. This workflow publishes the Ruby gem;
182
+ release the required Rust crates separately.
data/extconf.rb ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "mkmf"
4
+ require "rb_sys/mkmf"
5
+
6
+ create_rust_makefile("jiff_magnus/jiff_magnus") do |config|
7
+ config.extra_cargo_args << "--crate-type=cdylib"
8
+ end
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "jiff_magnus/jiff_magnus"
data/src/lib.rs ADDED
@@ -0,0 +1,276 @@
1
+ //! Ruby constructors for Jiff timezones, using Magnus's native backing type.
2
+ //!
3
+ //! `require "jiff_magnus"` calls [`init`] and bundles IANA timezone data.
4
+ //! The gem subclasses Magnus's [`JiffTimeZone`] to reuse its timezone methods.
5
+ //! Core [`jiff::Zoned`] conversions work before this gem is initialized.
6
+
7
+ use std::sync::{Mutex, OnceLock};
8
+
9
+ use jiff::tz::TimeZone;
10
+ use magnus::{
11
+ Error, RArray, RClass, Ruby, TypedData, Value, prelude::*, rb_sys::AsRawValue,
12
+ time::JiffTimeZone, typed_data::Obj, value::Opaque,
13
+ };
14
+
15
+ const CLASS_NAME: &str = "JiffTimezone";
16
+ const AUTO_TIME_REGISTRATION_ENV: &str = "JIFF_MAGNUS_ENABLE_AUTO_TIME_REGISTRATION";
17
+
18
+ struct Initialization {
19
+ class: Opaque<RClass>,
20
+ resolver: Opaque<RArray>,
21
+ }
22
+
23
+ impl Initialization {
24
+ fn validate_class(&self, ruby: &Ruby) -> Result<(), Error> {
25
+ let object = ruby.class_object();
26
+ if !object.funcall::<_, _, bool>("const_defined?", (CLASS_NAME, false))? {
27
+ return Err(conflict(ruby, "JiffTimezone initialization was removed"));
28
+ }
29
+ let class: Value = object.const_get(CLASS_NAME)?;
30
+ if class.as_raw() != ruby.get_inner(self.class).as_raw() {
31
+ return Err(conflict(ruby, "JiffTimezone initialization was replaced"));
32
+ }
33
+ Ok(())
34
+ }
35
+ }
36
+
37
+ static INITIALIZATION: OnceLock<Initialization> = OnceLock::new();
38
+ // Ruby definition hooks can reenter either public entry point while it publishes
39
+ // or restores definitions. Reject those calls before they can commit the cache.
40
+ static INITIALIZATION_LOCK: Mutex<()> = Mutex::new(());
41
+
42
+ /// Initializes `JiffTimezone` with optional automatic `Time.find_timezone` lookup.
43
+ ///
44
+ /// When an existing `JiffTimezone` inherits from this Magnus copy's timezone
45
+ /// class, this function reuses it and leaves its public methods untouched. It
46
+ /// preserves an existing `Time.find_timezone`, even if the method is private.
47
+ /// Otherwise, it adds lookup unless you set
48
+ /// `JIFF_MAGNUS_ENABLE_AUTO_TIME_REGISTRATION` to `0` or `false`
49
+ /// (case-insensitive) before initialization. Constructors remain available
50
+ /// without automatic lookup. Use [`register`] to explicitly install or replace
51
+ /// the method regardless of this setting.
52
+ ///
53
+ /// # Errors
54
+ ///
55
+ /// We return Ruby `LoadError` in these cases:
56
+ ///
57
+ /// - An existing `Object::JiffTimezone` belongs to a different native
58
+ /// implementation.
59
+ /// - Ruby code removed or replaced `JiffTimezone` or a tracked
60
+ /// `Time.find_timezone` method after initialization.
61
+ /// - Initialization or registration is already in progress.
62
+ ///
63
+ /// Ruby allocation and method errors propagate. If publishing the class or
64
+ /// resolver fails, this function rolls back only definitions it added.
65
+ #[cfg_attr(not(test), magnus::init(name = "jiff_magnus"))]
66
+ pub fn init(ruby: &Ruby) -> Result<(), Error> {
67
+ let _guard = INITIALIZATION_LOCK.try_lock().map_err(|_| {
68
+ conflict(
69
+ ruby,
70
+ "timezone initialization or registration is already in progress",
71
+ )
72
+ })?;
73
+ if let Some(existing) = INITIALIZATION.get() {
74
+ existing.validate_class(ruby)?;
75
+ let expected: Option<Value> = ruby.get_inner(existing.resolver).entry(0)?;
76
+ if let Some(expected) = expected {
77
+ let time = ruby.class_time();
78
+ if !time.respond_to("find_timezone", true)? {
79
+ return Err(conflict(ruby, "Time.find_timezone was removed"));
80
+ }
81
+ let method: Value = time.funcall("method", ("find_timezone",))?;
82
+ if !method.funcall::<_, _, bool>("==", (expected,))? {
83
+ return Err(conflict(ruby, "Time.find_timezone was replaced"));
84
+ }
85
+ }
86
+ return Ok(());
87
+ }
88
+ let object = ruby.class_object();
89
+ let env: Value = object.const_get("ENV")?;
90
+ let setting: Option<String> = env.funcall("[]", (AUTO_TIME_REGISTRATION_ENV,))?;
91
+ let auto_register = !setting
92
+ .as_deref()
93
+ .is_some_and(|value| value == "0" || value.eq_ignore_ascii_case("false"));
94
+ let existing_class = if object.funcall::<_, _, bool>("const_defined?", (CLASS_NAME, false))? {
95
+ let value: Value = object.const_get(CLASS_NAME)?;
96
+ let class = RClass::from_value(value)
97
+ .filter(|class| class.is_inherited(JiffTimeZone::class(ruby)))
98
+ .ok_or_else(|| {
99
+ conflict(
100
+ ruby,
101
+ "JiffTimezone is already defined by an incompatible native implementation",
102
+ )
103
+ })?;
104
+ Some(class)
105
+ } else {
106
+ None
107
+ };
108
+ let time = ruby.class_time();
109
+
110
+ // Configure a new class before publishing it; never modify a reused class.
111
+ let class = if let Some(class) = existing_class {
112
+ class
113
+ } else {
114
+ // Reuse Magnus's TypedData instead of introducing a second native payload.
115
+ let class = RClass::new(JiffTimeZone::class(ruby))?;
116
+ class.undef_default_alloc_func();
117
+ class.define_singleton_method("find_timezone", magnus::function!(find_timezone, 1))?;
118
+ class.define_singleton_method("utc", magnus::function!(utc, 0))?;
119
+ class.define_singleton_method("unknown", magnus::function!(unknown, 0))?;
120
+ class.define_singleton_method("fixed", magnus::function!(fixed, 1))?;
121
+ class.define_singleton_method("posix", magnus::function!(posix, 1))?;
122
+ class.define_singleton_method("register", magnus::function!(register, 0))?;
123
+ class.freeze();
124
+ class
125
+ };
126
+ let resolver = (|| -> Result<Value, Error> {
127
+ if existing_class.is_none() {
128
+ object.const_set(CLASS_NAME, class)?;
129
+ }
130
+ if !auto_register {
131
+ Ok(ruby.qnil().as_value())
132
+ } else if time.respond_to("find_timezone", true)? {
133
+ time.funcall("method", ("find_timezone",))
134
+ } else {
135
+ install_resolver(ruby)
136
+ }
137
+ })();
138
+ let resolver = match resolver {
139
+ Ok(resolver) => resolver,
140
+ Err(error) => {
141
+ // The resolver installer rolls back its own changes. A class hook
142
+ // may instead have installed application definitions before raising.
143
+ if existing_class.is_none() {
144
+ let _ = (|| -> Result<(), Error> {
145
+ if object.funcall::<_, _, bool>("const_defined?", (CLASS_NAME, false))? {
146
+ let current: Value = object.const_get(CLASS_NAME)?;
147
+ if current.as_raw() == class.as_raw() {
148
+ object.funcall::<_, _, Value>("remove_const", (CLASS_NAME,))?;
149
+ }
150
+ }
151
+ Ok(())
152
+ })();
153
+ }
154
+ return Err(error);
155
+ }
156
+ };
157
+ // A Ruby holder keeps the current method alive without retaining every
158
+ // method replaced by explicit registration.
159
+ let resolver = ruby.ary_new_from_values(&[resolver]);
160
+ ruby.gc_register_mark_object(class);
161
+ ruby.gc_register_mark_object(resolver);
162
+ INITIALIZATION
163
+ .set(Initialization {
164
+ class: class.into(),
165
+ resolver: resolver.into(),
166
+ })
167
+ .map_err(|_| conflict(ruby, "timezone initialization is already in progress"))?;
168
+ Ok(())
169
+ }
170
+
171
+ /// Replaces `Time.find_timezone` with this companion's Jiff name lookup.
172
+ ///
173
+ /// Unlike [`init`], this explicitly overrides an existing method, including a
174
+ /// private one, regardless of `JIFF_MAGNUS_ENABLE_AUTO_TIME_REGISTRATION`.
175
+ /// Ruby callers use `JiffTimezone.register`. Calling this before initialization
176
+ /// first initializes the companion.
177
+ ///
178
+ /// # Errors
179
+ ///
180
+ /// Returns `LoadError` if Ruby code removed or replaced the initialized class,
181
+ /// or if initialization or registration is already in progress.
182
+ /// Ruby errors propagate; a failed replacement restores the previous method.
183
+ pub fn register(ruby: &Ruby) -> Result<(), Error> {
184
+ if INITIALIZATION.get().is_none() {
185
+ init(ruby)?;
186
+ }
187
+ let _guard = INITIALIZATION_LOCK.try_lock().map_err(|_| {
188
+ conflict(
189
+ ruby,
190
+ "timezone initialization or registration is already in progress",
191
+ )
192
+ })?;
193
+ let initialization = INITIALIZATION.get().expect("companion was initialized");
194
+ initialization.validate_class(ruby)?;
195
+ let method = install_resolver(ruby)?;
196
+ ruby.get_inner(initialization.resolver).store(0, method)
197
+ }
198
+
199
+ fn install_resolver(ruby: &Ruby) -> Result<Value, Error> {
200
+ let time = ruby.class_time();
201
+ let singleton = time.singleton_class()?;
202
+ let previous: Option<Value> = if time.respond_to("find_timezone", true)? {
203
+ Some(time.funcall("method", ("find_timezone",))?)
204
+ } else {
205
+ None
206
+ };
207
+ let mut visibility = None;
208
+ for (query, access) in [
209
+ ("public_method_defined?", "public"),
210
+ ("protected_method_defined?", "protected"),
211
+ ("private_method_defined?", "private"),
212
+ ] {
213
+ if singleton.funcall::<_, _, bool>(query, ("find_timezone", false))? {
214
+ visibility = Some(access);
215
+ break;
216
+ }
217
+ }
218
+ let result = (|| {
219
+ time.define_singleton_method("find_timezone", magnus::function!(find_timezone, 1))?;
220
+ time.funcall("method", ("find_timezone",))
221
+ })();
222
+ if result.is_err() {
223
+ let _ = singleton.funcall::<_, _, Value>("remove_method", ("find_timezone",));
224
+ // Removing a replacement can expose an ancestor hidden by undef_method.
225
+ if previous.is_none() && time.respond_to("find_timezone", true).unwrap_or(false) {
226
+ let _ = singleton.funcall::<_, _, Value>("undef_method", ("find_timezone",));
227
+ }
228
+ if let (Some(method), Some(access)) = (previous, visibility) {
229
+ // A method-added hook can raise after the old body is restored.
230
+ let _ = singleton.funcall::<_, _, Value>("define_method", ("find_timezone", method));
231
+ let _ = singleton.funcall::<_, _, Value>(access, ("find_timezone",));
232
+ }
233
+ }
234
+ result
235
+ }
236
+
237
+ fn conflict(ruby: &Ruby, message: &str) -> Error {
238
+ Error::new(ruby.exception_load_error(), message.to_owned())
239
+ }
240
+
241
+ fn wrap(ruby: &Ruby, zone: TimeZone) -> Result<Obj<JiffTimeZone>, Error> {
242
+ let class = INITIALIZATION
243
+ .get()
244
+ .map(|initialization| ruby.get_inner(initialization.class))
245
+ .ok_or_else(|| conflict(ruby, "call jiff_magnus::init before using constructors"))?;
246
+ let object = ruby.obj_wrap_as(JiffTimeZone::new(zone), class);
247
+ object.freeze();
248
+ Ok(object)
249
+ }
250
+
251
+ fn find_timezone(ruby: &Ruby, name: String) -> Result<Option<Obj<JiffTimeZone>>, Error> {
252
+ TimeZone::get(&name)
253
+ .ok()
254
+ .map(|zone| wrap(ruby, zone))
255
+ .transpose()
256
+ }
257
+
258
+ fn utc(ruby: &Ruby) -> Result<Obj<JiffTimeZone>, Error> {
259
+ wrap(ruby, TimeZone::UTC)
260
+ }
261
+
262
+ fn unknown(ruby: &Ruby) -> Result<Obj<JiffTimeZone>, Error> {
263
+ wrap(ruby, TimeZone::unknown())
264
+ }
265
+
266
+ fn fixed(ruby: &Ruby, seconds: i32) -> Result<Obj<JiffTimeZone>, Error> {
267
+ let offset = jiff::tz::Offset::from_seconds(seconds)
268
+ .map_err(|err| Error::new(ruby.exception_arg_error(), err.to_string()))?;
269
+ wrap(ruby, TimeZone::fixed(offset))
270
+ }
271
+
272
+ fn posix(ruby: &Ruby, rule: String) -> Result<Obj<JiffTimeZone>, Error> {
273
+ let zone = TimeZone::posix(&rule)
274
+ .map_err(|err| Error::new(ruby.exception_arg_error(), err.to_string()))?;
275
+ wrap(ruby, zone)
276
+ }
data/tmp/.gitignore ADDED
@@ -0,0 +1,2 @@
1
+ *
2
+ !.gitignore
metadata ADDED
@@ -0,0 +1,63 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: jiff-magnus
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Erick Guan
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: rb_sys
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: 0.9.130
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: 0.9.130
26
+ description: A Magnus companion providing JiffTimezone constructors and bundled IANA
27
+ timezone data for Ruby Time.
28
+ executables: []
29
+ extensions:
30
+ - extconf.rb
31
+ extra_rdoc_files: []
32
+ files:
33
+ - ".gitignore"
34
+ - Cargo.toml
35
+ - LICENSE
36
+ - README.md
37
+ - extconf.rb
38
+ - lib/jiff_magnus.rb
39
+ - src/lib.rs
40
+ - tmp/.gitignore
41
+ homepage: https://github.com/matsadler/magnus
42
+ licenses:
43
+ - MIT
44
+ metadata:
45
+ source_code_uri: https://github.com/matsadler/magnus/tree/main/jiff-magnus
46
+ rdoc_options: []
47
+ require_paths:
48
+ - lib
49
+ required_ruby_version: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '3.2'
54
+ required_rubygems_version: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ version: '0'
59
+ requirements: []
60
+ rubygems_version: 3.6.9
61
+ specification_version: 4
62
+ summary: Jiff timezone rules and bundled timezone data for Ruby Time.
63
+ test_files: []