fast_xlsx 0.8.0 → 1.0.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5115d5bf58c96ec3a87ab9168e954b77d2360cac3314c049efd8bed45feb634a
4
- data.tar.gz: 6ecf4642347e56626a0508acd2784cd6773f4be9ab52f3c4ede1da48031e57e1
3
+ metadata.gz: 0642c1f4069d2b6537757d873ed2e591ddd3c7dab25f4bd1c867fad4fe8f0ead
4
+ data.tar.gz: d0b7696cc3ee573ec02821a3b210ef34ce68fb821c83666f2fa0938c8bbabc16
5
5
  SHA512:
6
- metadata.gz: d95432cd98346cc8339c20f71c14839325e2236f43b05180960125764eb4dbff1fe434b1acf00919425b8be613e00b8fc318e548f5e4359450f9119f12cab205
7
- data.tar.gz: 2e2563be474cd5613a713f680c433a8e35186423d367004b4c02afa02905859cc139fc1efd762da66b04d748f3b1c3404def56bedb3b5c86cc0c8c3ecc4f2041
6
+ metadata.gz: 40fbd10f5ad43e8fc7a6bedccdc68cee4a324fa5ae9586dd7e9281272c6c2b1a465049c53fd67f042c9dffe00ca7c21c1512d4e1ffcbd01e2ea5ce52dc658938
7
+ data.tar.gz: 5a6ca0279e195f3b109d23f0bf1afbed5680c6f6fd844aa0e30411468e1f8d7203680d30bba838cdb15b2dfac1f9c170a9b9576ed5db0650439c331fd2ff11be
data/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [1.0.0] - 2026-10-18
4
+
5
+ The first stable release, with the same API as 0.8.1. From now on fast_xlsx follows Semantic Versioning: no breaking change before 2.0, and anything to be removed first prints a deprecation warning for at least one minor release.
6
+
7
+ ### Fixed
8
+
9
+ - Updated rust_xlsxwriter to 0.100, which brings zip 9.0 (RUSTSEC-2026-0367, -0368 and -0369; those affect extracting archives, which fast_xlsx never does).
10
+ - In `memory: :constant` and `:low` mode, a temporary file that can't be created (e.g. an unwritable temp directory) raises `FastXlsx::Error` instead of crashing the process. The file is also created only when the first row is flushed, not by `add_worksheet`.
11
+
12
+ ## [0.8.1] - 2026-10-03
13
+
14
+ ### Fixed
15
+
16
+ - Rails' `ActiveSupport::TimeWithZone` (e.g. `created_at`) and other objects that act like a `Time` without being one were written as text; they are dates now.
17
+
18
+ ### Changed
19
+
20
+ - Writing `Time` values is about 25% faster (read through Ruby's C API instead of method calls), and `Date` values a little faster.
21
+
3
22
  ## [0.8.0] - 2026-10-02
4
23
 
5
24
  ### Changed
data/Cargo.lock CHANGED
@@ -56,6 +56,12 @@ version = "1.0.5"
56
56
  source = "registry+https://github.com/rust-lang/crates.io-index"
57
57
  checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600"
58
58
 
59
+ [[package]]
60
+ name = "cfg_aliases"
61
+ version = "0.2.2"
62
+ source = "registry+https://github.com/rust-lang/crates.io-index"
63
+ checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527"
64
+
59
65
  [[package]]
60
66
  name = "clang-sys"
61
67
  version = "1.9.1"
@@ -201,9 +207,9 @@ checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6"
201
207
 
202
208
  [[package]]
203
209
  name = "magnus"
204
- version = "0.9.1"
210
+ version = "0.9.2"
205
211
  source = "registry+https://github.com/rust-lang/crates.io-index"
206
- checksum = "4c01bef153b8d0f086991f0e166474a1d57b738221f9a9c11405d498ac95a44d"
212
+ checksum = "b9068710e00d21be762edca59b9c4c8946dbdb29820d4f76ad559afea07982c9"
207
213
  dependencies = [
208
214
  "magnus-macros",
209
215
  "rb-sys",
@@ -234,6 +240,18 @@ version = "0.2.1"
234
240
  source = "registry+https://github.com/rust-lang/crates.io-index"
235
241
  checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
236
242
 
243
+ [[package]]
244
+ name = "nix"
245
+ version = "0.31.3"
246
+ source = "registry+https://github.com/rust-lang/crates.io-index"
247
+ checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d"
248
+ dependencies = [
249
+ "bitflags",
250
+ "cfg-if",
251
+ "cfg_aliases",
252
+ "libc",
253
+ ]
254
+
237
255
  [[package]]
238
256
  name = "nom"
239
257
  version = "7.1.3"
@@ -335,9 +353,9 @@ checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
335
353
 
336
354
  [[package]]
337
355
  name = "rust_xlsxwriter"
338
- version = "0.99.1"
356
+ version = "0.100.0"
339
357
  source = "registry+https://github.com/rust-lang/crates.io-index"
340
- checksum = "34f2d2cda7223168a14fb86bc7411dbc3379bd7d49749ecc53b91ed98585b6b0"
358
+ checksum = "c44904ffb6c57b148ffcc8ce3011c28bc49b97e1a8343b2d632283cd566e1393"
341
359
  dependencies = [
342
360
  "tempfile",
343
361
  "zip",
@@ -439,14 +457,15 @@ dependencies = [
439
457
 
440
458
  [[package]]
441
459
  name = "zip"
442
- version = "8.6.0"
460
+ version = "9.0.5"
443
461
  source = "registry+https://github.com/rust-lang/crates.io-index"
444
- checksum = "2d04a6b5381502aa6087c94c669499eb1602eb9c5e8198e534de571f7154809b"
462
+ checksum = "6e04976da629d3b04204ba8105b396a193af8ad2583b2c79bfb3ab8af580a11e"
445
463
  dependencies = [
446
464
  "crc32fast",
447
465
  "flate2",
448
466
  "indexmap",
449
467
  "memchr",
468
+ "nix",
450
469
  "typed-path",
451
470
  "zopfli",
452
471
  ]
data/README.md CHANGED
@@ -1,15 +1,17 @@
1
- # FastXlsx
1
+ # fast_xlsx
2
2
 
3
- [![Gem Version](https://badge.fury.io/rb/fast_xlsx.svg)](https://badge.fury.io/rb/fast_xlsx)
3
+ ![fast_xlsx: fast .xlsx writer for Ruby, powered by Rust](docs/social-preview.jpg)
4
+
5
+ [![Gem Version](https://img.shields.io/gem/v/fast_xlsx)](https://rubygems.org/gems/fast_xlsx)
4
6
  [![codecov](https://codecov.io/gh/7a6163/fast_xlsx/graph/badge.svg)](https://codecov.io/gh/7a6163/fast_xlsx)
5
7
 
6
- Fast `.xlsx` writer for Ruby, built on [rust_xlsxwriter](https://github.com/jmcnamara/rust_xlsxwriter) via [magnus](https://github.com/matsadler/magnus).
8
+ Write Excel `.xlsx` files from Ruby with a Rust engine ([rust_xlsxwriter](https://github.com/jmcnamara/rust_xlsxwriter), via [magnus](https://github.com/matsadler/magnus)): 20,000 rows in under 100 ms, with memory that stays flat for large exports.
7
9
 
8
- > **Status: release candidate.** The API is frozen as of 0.8: 0.8.x releases only fix bugs, and 1.0 follows once it has been in real use without needing changes.
10
+ > **Status: stable.** Since 1.0 the API follows [Semantic Versioning](https://semver.org): no breaking change before 2.0.
9
11
 
10
- **Roadmap:** [1.0.0](https://github.com/7a6163/fast_xlsx/milestone/1) (release plan) · [1.x](https://github.com/7a6163/fast_xlsx/milestone/2) (planned features)
12
+ **Roadmap:** [1.x](https://github.com/7a6163/fast_xlsx/milestone/2) (planned features)
11
13
 
12
- **Contents:** [Installation](#installation) · [Getting started](#getting-started) · [Guide](#guide) · [Performance](#performance) · [Support and versioning](#support-and-versioning) · [Development](#development)
14
+ **Contents:** [Installation](#installation) · [Getting started](#getting-started) · [Guide](#guide) · [Performance](#performance) · [Support and versioning](#support-and-versioning) · [Development](#development) · [Credits](#credits)
13
15
 
14
16
  ## Installation
15
17
 
@@ -81,7 +83,7 @@ The [Guide](#guide) below covers every feature; the [API docs](https://www.rubyd
81
83
 
82
84
  ## Guide
83
85
 
84
- [Writing cells](#writing-cells) · [Rails](#rails) · [Memory modes](#memory-modes) · [Formats](#formats) · [Columns and filters](#columns-and-filters) · [Sheet view and printing](#sheet-view-and-printing) · [Cell ranges](#cell-ranges) · [Layout](#layout) · [Outline groups](#outline-groups) · [Defined names](#defined-names) · [Protection](#protection) · [Conditional formats](#conditional-formats) · [Data validation](#data-validation) · [Comments](#comments) · [Images](#images) · [Tables](#tables) · [Charts](#charts) · [Errors](#errors)
86
+ [Writing cells](#writing-cells) · [Rails](#rails) · [Polars](#polars) · [Memory modes](#memory-modes) · [Formats](#formats) · [Columns and filters](#columns-and-filters) · [Sheet view and printing](#sheet-view-and-printing) · [Cell ranges](#cell-ranges) · [Layout](#layout) · [Outline groups](#outline-groups) · [Defined names](#defined-names) · [Protection](#protection) · [Conditional formats](#conditional-formats) · [Data validation](#data-validation) · [Comments](#comments) · [Images](#images) · [Tables](#tables) · [Charts](#charts) · [Errors](#errors)
85
87
 
86
88
  ### Writing cells
87
89
 
@@ -126,6 +128,23 @@ def export
126
128
  end
127
129
  ```
128
130
 
131
+ ### Polars
132
+
133
+ To write a [ruby-polars](https://github.com/ankane/ruby-polars) DataFrame (gem `polars-df`), hand its column names and rows to fast_xlsx:
134
+
135
+ ```ruby
136
+ df = Polars.read_csv("sales.csv")
137
+ summary = df.group_by("region").agg(Polars.col("amount").sum)
138
+
139
+ wb = FastXlsx::Workbook.new(memory: :constant)
140
+ ws = wb.add_worksheet("By region")
141
+ ws.append(summary.columns, format: { bold: true })
142
+ summary.iter_rows { |row| ws << row } # in batches of 512 rows, so memory stays flat
143
+ wb.save("by_region.xlsx")
144
+ ```
145
+
146
+ Polars `Date` and `Datetime` columns become dates (a `Datetime` with a time zone keeps its local time), `Decimal` numbers, and nulls empty cells.
147
+
129
148
  ### Memory modes
130
149
 
131
150
  By default every cell stays in memory until the file is saved. For large exports, two modes write each finished row to a temp file instead:
@@ -370,7 +389,7 @@ Values are mapped by type:
370
389
  |---|---|
371
390
  | `Integer`, `Float`, any `Numeric` | number |
372
391
  | `String` | string |
373
- | `Time`, `DateTime` | date and time (`yyyy-mm-dd hh:mm:ss` unless formatted), in its own offset |
392
+ | `Time`, `DateTime`, time-like objects such as Rails' `ActiveSupport::TimeWithZone` | date and time (`yyyy-mm-dd hh:mm:ss` unless formatted), in its own offset |
374
393
  | `Date` | date (`yyyy-mm-dd` unless formatted) |
375
394
  | `FastXlsx::Formula.new("SUM(A1:A9)")` | formula |
376
395
  | `FastXlsx::URL.new("https://…")`, `URL.new(url, text: "Title")` | hyperlink (optionally showing other text) |
@@ -402,15 +421,15 @@ Apple Silicon, Ruby 4.0.5. Each library uses its own idiomatic row-append API; x
402
421
 
403
422
  | Library | Time | vs fastest | Ruby objects allocated |
404
423
  |---|---:|---:|---:|
405
- | **fast_xlsx** (`memory: :constant`) | **77 ms** | 1.0x | 7 |
406
- | **fast_xlsx** (`memory: :low`) | **86 ms** | 1.1x | 7 |
407
- | **fast_xlsx** | **89 ms** | 1.1x | 10 |
408
- | [xlsxtream](https://github.com/felixbuenemann/xlsxtream) 3.1 | 183 ms | 2.4x | 561,728 |
409
- | [fast_excel](https://github.com/Paxa/fast_excel) 0.5 (constant_memory) | 200 ms | 2.6x | 20,079 |
410
- | [fast_excel](https://github.com/Paxa/fast_excel) 0.5 | 238 ms | 3.1x | 320,076 |
411
- | [write_xlsx](https://github.com/cxn03651/write_xlsx) 1.15 | 608 ms | 7.8x | 1,483,899 |
412
- | [caxlsx](https://github.com/caxlsx/caxlsx) 4.5 | 689 ms | 8.9x | 745,122 |
413
- | [rubyXL](https://github.com/weshatheleopard/rubyXL) 3.4 | 2755 ms | 35.6x | 8,700,448 |
424
+ | **fast_xlsx** (`memory: :constant`) | **75 ms** | 1.0x | 7 |
425
+ | **fast_xlsx** (`memory: :low`) | **81 ms** | 1.1x | 7 |
426
+ | **fast_xlsx** | **85 ms** | 1.1x | 10 |
427
+ | [xlsxtream](https://github.com/felixbuenemann/xlsxtream) 3.1 | 173 ms | 2.3x | 561,740 |
428
+ | [fast_excel](https://github.com/Paxa/fast_excel) 0.5 (constant_memory) | 204 ms | 2.7x | 20,079 |
429
+ | [fast_excel](https://github.com/Paxa/fast_excel) 0.5 | 238 ms | 3.2x | 320,076 |
430
+ | [write_xlsx](https://github.com/cxn03651/write_xlsx) 1.15 | 592 ms | 7.9x | 1,483,899 |
431
+ | [caxlsx](https://github.com/caxlsx/caxlsx) 4.5 | 671 ms | 8.9x | 745,122 |
432
+ | [rubyXL](https://github.com/weshatheleopard/rubyXL) 3.4 | 2571 ms | 34.1x | 8,700,448 |
414
433
 
415
434
  All outputs are 689–750 KB.
416
435
 
@@ -448,8 +467,8 @@ BUNDLE_GEMFILE=bench/Gemfile /usr/bin/time -l bundle exec ruby bench/memory.rb f
448
467
 
449
468
  - **Ruby:** CRuby 3.3 and later. A Ruby version is dropped only after it reaches its end of life, and only in a minor release. JRuby and TruffleRuby are not supported (this is a native extension).
450
469
  - **Precompiled gems:** Linux (x86_64 and aarch64, glibc and musl; ARM musl), macOS (arm64 and x86_64) and Windows (x64). Before each release the Linux x86_64, macOS arm64 and Windows gems are installed and loaded on Ruby 3.3, 3.4 and 4.0. Other platforms build from source and need a Rust toolchain.
451
- - **Versions:** from 1.0, [Semantic Versioning](https://semver.org): no breaking API change before 2.0. Until then a minor release (0.x.0) can change behaviour; the [changelog](CHANGELOG.md) lists every change under "Changed".
452
- - **Deprecations:** from 1.0, a method or option to be removed first prints a warning (with `warn`, so on by default) for at least one minor release, with the CHANGELOG naming its replacement; it goes only in the next major version.
470
+ - **Versions:** [Semantic Versioning](https://semver.org) since 1.0: no breaking API change before 2.0. The [changelog](CHANGELOG.md) lists every change.
471
+ - **Deprecations:** a method or option to be removed first prints a warning (with `warn`, so on by default) for at least one minor release, with the CHANGELOG naming its replacement; it goes only in the next major version.
453
472
  - **Output:** `.xlsx` files that open in Excel 2007 and later, LibreOffice and Google Sheets. Updating rust_xlsxwriter can change the bytes of the file but not what it contains.
454
473
 
455
474
  ## Development
@@ -498,6 +517,10 @@ git tag v0.1.0 && git push origin v0.1.0
498
517
 
499
518
  The `Build gems` workflow builds the source gem plus precompiled gems for each platform and pushes them to RubyGems (trusted publishing) and GitHub Packages. It refuses to publish if the tag and `VERSION` differ.
500
519
 
520
+ ## Credits
521
+
522
+ Inspired by [fast_excel](https://github.com/Paxa/fast_excel), whose API and approach fast_xlsx builds on, and made possible by [rust_xlsxwriter](https://github.com/jmcnamara/rust_xlsxwriter) and [magnus](https://github.com/matsadler/magnus).
523
+
501
524
  ## License
502
525
 
503
526
  MIT
data/SECURITY.md ADDED
@@ -0,0 +1,26 @@
1
+ # Security policy
2
+
3
+ ## Supported versions
4
+
5
+ Security fixes go into the latest release of the newest minor version. Until 1.0 that is 0.8.x; from 1.0, the latest 1.x.
6
+
7
+ | Version | Supported |
8
+ |---|---|
9
+ | 0.8.x | ✅ |
10
+ | < 0.8 | ❌ (upgrade to 0.8.x; the [changelog](CHANGELOG.md) lists every change) |
11
+
12
+ ## Reporting a vulnerability
13
+
14
+ Please don't open a public issue. Report it privately through GitHub: on the repository's **Security** tab, choose **Report a vulnerability** ([direct link](https://github.com/7a6163/fast_xlsx/security/advisories/new)).
15
+
16
+ Include what an attacker could do, the fast_xlsx, Ruby and platform versions, and a script or input that shows it, if you have one.
17
+
18
+ What to expect:
19
+
20
+ - An acknowledgement within a week.
21
+ - A fix in a patch release, and a GitHub security advisory crediting you (unless you'd rather not be named), once users can upgrade.
22
+ - If it turns out to be in a dependency (rust_xlsxwriter, magnus, rb-sys, the Rust zip crates), a report to that project as well.
23
+
24
+ ## Scope
25
+
26
+ fast_xlsx only writes `.xlsx` files: it doesn't read or parse spreadsheets. Issues of interest include memory-safety bugs in the native extension (a crash or corruption reachable from Ruby), and output that makes Excel or other readers misbehave from data an application would reasonably pass in (cell text, formulas, URLs, images). Formulas and hyperlinks are written as given: an application writing untrusted input as a `FastXlsx::Formula` or `FastXlsx::URL` should validate it first, as with CSV formula injection.
data/bench/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- fast_xlsx (0.8.0)
4
+ fast_xlsx (0.8.1)
5
5
  rb_sys (~> 0.9.130)
6
6
 
7
7
  GEM
@@ -77,7 +77,7 @@ DEPENDENCIES
77
77
  CHECKSUMS
78
78
  caxlsx (4.5.0) sha256=e3d98d859f148df05d5462086b5079b523f29c1766b569535f1d68629ce743ff
79
79
  fast_excel (0.5.0) sha256=59c418bdcf586a6030798d4e3a9f575742badaceaf7bb90f38eee5feb5bdc478
80
- fast_xlsx (0.8.0)
80
+ fast_xlsx (0.8.1)
81
81
  ffi (1.17.4-aarch64-linux-gnu) sha256=b208f06f91ffd8f5e1193da3cae3d2ccfc27fc36fba577baf698d26d91c080df
82
82
  ffi (1.17.4-aarch64-linux-musl) sha256=9286b7a615f2676245283aef0a0a3b475ae3aae2bb5448baace630bb77b91f39
83
83
  ffi (1.17.4-arm-linux-gnu) sha256=d6dbddf7cb77bf955411af5f187a65b8cd378cb003c15c05697f5feee1cb1564
@@ -4,7 +4,7 @@ fast_xlsx started as a rewrite of [fast_excel](https://github.com/Paxa/fast_exce
4
4
 
5
5
  **Why switch**
6
6
 
7
- - 2.8–3.3× faster on the [README benchmark](../README.md#speed), and `memory: :constant` adds about 1 MB however large the file.
7
+ - Faster writing (see the [README benchmark](../README.md#speed)), and `memory: :constant` adds about 1 MB however large the file.
8
8
  - Precompiled gems for Linux, macOS and Windows: no C compiler or FFI at install.
9
9
  - Dates show as dates without a format, every option is checked (a typo raises instead of being ignored), and saving doesn't block other threads.
10
10
  - Conditional formats, data validation, tables, charts, images, comments, protection and printing options as Ruby methods.
@@ -10,11 +10,11 @@ publish = false
10
10
  crate-type = ["cdylib"]
11
11
 
12
12
  [dependencies]
13
- magnus = "0.9"
13
+ magnus = { version = "0.9", features = ["rb-sys"] }
14
14
  rb-sys = { version = "0.9", features = ["stable-api-compiled-fallback"] }
15
15
  # No "zlib" feature: without it zip compresses with zlib-rs, which saves
16
16
  # 20-30% faster than the system zlib (and needs no C library).
17
- rust_xlsxwriter = { version = "0.99.1", features = ["constant_memory"] }
17
+ rust_xlsxwriter = { version = "0.100.0", features = ["constant_memory"] }
18
18
 
19
19
  [build-dependencies]
20
20
  rb-sys-env = "0.2.2"
@@ -237,9 +237,9 @@ impl Workbook {
237
237
  .unwrap(),
238
238
  };
239
239
  let ws = if self.low_memory {
240
- wb.add_worksheet_with_low_memory()
240
+ wb.add_worksheet_with_low_memory().map_err(xerr)?
241
241
  } else if self.constant_memory {
242
- wb.add_worksheet_with_constant_memory()
242
+ wb.add_worksheet_with_constant_memory().map_err(xerr)?
243
243
  } else {
244
244
  wb.add_worksheet()
245
245
  };
@@ -328,7 +328,29 @@ fn excel_serial(days: f64) -> Result<f64, Error> {
328
328
  }
329
329
 
330
330
  // Time: seconds since 1970-01-01 (serial day 25569), in its own offset.
331
+ // For a Time (checked by the caller): Ruby's C functions, with no method calls,
332
+ // since this runs once per cell.
331
333
  fn excel_time(v: Value) -> Result<f64, Error> {
334
+ use magnus::rb_sys::{protect, AsRawValue, FromRawValue};
335
+ // Both can raise (a Time out of range or uninitialized, or a zone object
336
+ // whose Ruby code fails), so they run under protect: a raise becomes an
337
+ // Err instead of a longjmp over these Rust frames.
338
+ let mut spec = None;
339
+ protect(|| {
340
+ // SAFETY: protect catches anything this raises.
341
+ spec = Some(unsafe { rb_sys::rb_time_timespec(v.as_raw()) });
342
+ rb_sys::Qnil as rb_sys::VALUE
343
+ })?;
344
+ // SAFETY: as above.
345
+ let offset = protect(|| unsafe { rb_sys::rb_time_utc_offset(v.as_raw()) })?;
346
+ let (spec, offset) = (spec.unwrap(), unsafe { Value::from_raw(offset) });
347
+ let secs = spec.tv_sec as f64 + spec.tv_nsec as f64 / 1e9;
348
+ excel_serial((secs + i64::try_convert(offset)? as f64) / 86400.0 + 25569.0)
349
+ }
350
+
351
+ // Something that acts like a Time without being one at the C level, such as
352
+ // Rails' ActiveSupport::TimeWithZone (it delegates to a Time).
353
+ fn excel_time_like(v: Value) -> Result<f64, Error> {
332
354
  let secs: f64 = v.funcall("to_f", ())?;
333
355
  let offset: i64 = v.funcall("utc_offset", ())?;
334
356
  excel_serial((secs + offset as f64) / 86400.0 + 25569.0)
@@ -336,9 +358,14 @@ fn excel_time(v: Value) -> Result<f64, Error> {
336
358
 
337
359
  // Date / DateTime: Julian day and day fraction are both in the object's own
338
360
  // offset. JD 2415019 is 1899-12-30.
339
- fn excel_date(v: Value) -> Result<f64, Error> {
361
+ // `time`: a DateTime, with a time of day; a Date's day fraction is always 0.
362
+ fn excel_date(v: Value, time: bool) -> Result<f64, Error> {
340
363
  let jd: i64 = v.funcall("jd", ())?;
341
- let fraction: f64 = f64::try_convert(v.funcall("day_fraction", ())?)?;
364
+ let fraction = if time {
365
+ f64::try_convert(v.funcall("day_fraction", ())?)?
366
+ } else {
367
+ 0.0
368
+ };
342
369
  excel_serial((jd - 2_415_019) as f64 + fraction)
343
370
  }
344
371
 
@@ -413,7 +440,10 @@ impl CellValue {
413
440
  CellValue::Rich(parts)
414
441
  } else if v.respond_to("jd", false)? {
415
442
  // Date has no #hour; DateTime does.
416
- CellValue::Date(excel_date(v)?, v.respond_to("hour", false)?)
443
+ let time = v.respond_to("hour", false)?;
444
+ CellValue::Date(excel_date(v, time)?, time)
445
+ } else if v.respond_to("utc_offset", false)? && v.respond_to("to_f", false)? {
446
+ CellValue::Date(excel_time_like(v)?, true)
417
447
  } else {
418
448
  CellValue::Text(v.funcall("to_s", ())?)
419
449
  };
@@ -2,5 +2,5 @@
2
2
 
3
3
  module FastXlsx
4
4
  # The gem version.
5
- VERSION = "0.8.0"
5
+ VERSION = "1.0.0"
6
6
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fast_xlsx
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Zac
@@ -23,8 +23,9 @@ dependencies:
23
23
  - - "~>"
24
24
  - !ruby/object:Gem::Version
25
25
  version: 0.9.130
26
- description: Writes Excel .xlsx files from Ruby through a native Rust extension (rust_xlsxwriter
27
- + magnus).
26
+ description: 'Writes Excel .xlsx files from Ruby through a native Rust extension (rust_xlsxwriter):
27
+ 20,000 rows in under 100 ms, and memory that stays flat for large exports. Formats,
28
+ formulas, tables, charts, conditional formats, data validation and more.'
28
29
  email:
29
30
  - 579103+7a6163@users.noreply.github.com
30
31
  executables: []
@@ -40,6 +41,7 @@ files:
40
41
  - LICENSE.txt
41
42
  - README.md
42
43
  - Rakefile
44
+ - SECURITY.md
43
45
  - bench/Gemfile
44
46
  - bench/Gemfile.lock
45
47
  - bench/compare.rb
@@ -81,5 +83,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
81
83
  requirements: []
82
84
  rubygems_version: 4.0.10
83
85
  specification_version: 4
84
- summary: Fast xlsx writer for Ruby, powered by rust_xlsxwriter
86
+ summary: Excel .xlsx writer for Ruby with a Rust engine
85
87
  test_files: []