Skip to content

Commit 56f9629

Browse files
committed
Release 0.3.0: fix CI timezone drift bound, refresh guides, bump version
1 parent 5f235dd commit 56f9629

6 files changed

Lines changed: 34 additions & 19 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ All notable changes to this project will be documented in this file.
44

55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
66

7-
## [Unreleased]
7+
## [0.3.0] - 2026-07-23
88

99
### Added
1010

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ Add `localize_ecto` to your dependencies:
6565
```elixir
6666
def deps do
6767
[
68-
{:localize_ecto, "~> 0.1.0"}
68+
{:localize_ecto, "~> 0.3.0"}
6969
]
7070
end
7171
```
@@ -115,4 +115,4 @@ If a PostgreSQL upgrade links a newer ICU library whose collation data changed
115115

116116
Copyright 2026 Kip Cole
117117

118-
Licensed under the Apache License, Version 2.0. See [LICENSE](https://github.com/elixir-localize/localize_ecto/blob/v0.1.0/LICENSE.md).
118+
Licensed under the Apache License, Version 2.0. See [LICENSE](https://github.com/elixir-localize/localize_ecto/blob/v0.3.0/LICENSE.md).

guides/collations_in_postgres.md

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -81,24 +81,30 @@ end
8181

8282
This runs `CREATE COLLATION "de-u-co-phonebk-x-icu" (provider = icu, locale = 'de-u-co-phonebk')`. The name matches what `Localize.Ecto.Collation` resolves the locale to, so from then on `collate(p.name, "de-u-co-phonebk")` works with no further configuration. German phonebook order treats `ü` as `ue`: standard German sorts `Mueller, Muller, Müller` while phonebook order sorts `Mueller, Müller, Muller`. Other collation types include `zh-u-co-stroke` and `zh-u-co-zhuyin` for Chinese stroke and Bopomofo orderings, and `es-u-co-trad` for traditional Spanish, where `ch` sorts as a single letter.
8383

84-
Other ICU keywords open up collation behaviors beyond language tailoring:
84+
Other ICU keywords open up collation behaviors beyond language tailoring. `Localize.Ecto.Migration.create_collation/2` accepts them as tailoring options using the `Localize.Collation.Options` vocabulary, and query-time resolution carries the same keywords, so the locale-with-keywords form works end to end without naming anything:
8585

86-
* Numeric ordering (`-u-kn`) compares digit sequences by numeric value, giving natural sort: `file1, file2, file10` instead of `file1, file10, file2`.
86+
* Numeric ordering (`-u-kn`, option `numeric: true`) compares digit sequences by numeric value, giving natural sort: `file1, file2, file10` instead of `file1, file10, file2`.
8787

8888
```elixir
89-
create_collation("und-u-kn", name: "natural_sort")
89+
# In a migration
90+
create_collation("en", numeric: true)
91+
92+
# In queries — resolves to the collation created above
93+
from f in Upload, order_by: collate(f.name, "en-u-kn-true")
9094
```
9195

92-
* Strength reduction (`-u-ks-level2`) ignores case differences. Combined with `deterministic: false` this yields a collation under which `'HELLO' = 'hello'` is true — case-insensitive matching without `citext` or `lower()` wrappers, at the cost that `LIKE` and pattern matching cannot use the collation.
96+
* Strength reduction (`-u-ks-level2`, option `strength: :secondary`) ignores case differences; `strength: :primary` ignores accents too. These strengths default to a nondeterministic collation — the only mode in which `'HELLO' = 'hello'` is actually true — giving case-insensitive matching without `citext` or `lower()` wrappers, at the cost that `LIKE` and pattern matching cannot use the collation (PostgreSQL 18 lifts the `LIKE` restriction). A unique index over such a collation enforces case-insensitive uniqueness:
9397

9498
```elixir
95-
create_collation("und-u-ks-level2", name: "case_insensitive", deterministic: false)
99+
create_collation("und", strength: :secondary)
100+
create index("users", [collated(:email, "und-u-ks-level2")], unique: true)
96101
```
97102

98-
Named collations like these are used in queries with the `:collation` option:
103+
Custom-named collations are used in queries with the `:collation` option:
99104

100105
```elixir
101-
from f in File, order_by: collate(f.name, collation: "natural_sort")
106+
create_collation("und", numeric: true, name: "natural_sort")
107+
from f in Upload, order_by: collate(f.name, collation: "natural_sort")
102108
```
103109

104-
Two practical notes. PostgreSQL normalizes ICU locale identifiers when it stores them, so `und-u-kn-true` is recorded in standard form as `und-u-kn` — pass the standard form to avoid a notice. And because collations live in the database, remember that a collation created in a migration exists per database: test, dev, and production each get theirs when migrations run.
110+
Two practical notes. PostgreSQL normalizes ICU locale identifiers when it stores them (`und-u-kn-true` is recorded as `und-u-kn`), which can produce a server notice at creation time — harmless, and the collation's name is unaffected. And because collations live in the database, remember that a collation created in a migration exists per database: test, dev, and production each get theirs when migrations run.

guides/using_localize_ecto.md

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -107,15 +107,21 @@ from p in Product, order_by: collate(p.name, "de-u-co-phonebk")
107107

108108
The difference is real: standard German sorts `Mueller, Muller, Müller` while phonebook order treats `ü` as `ue` and sorts `Mueller, Müller, Muller`.
109109

110-
Custom names and other ICU tailorings are supported through options:
110+
Custom names and other ICU tailorings are supported through options, using the same vocabulary as `Localize.Collation.Options`:
111111

112112
```elixir
113113
create_collation("de-u-co-phonebk", name: "german_phonebook")
114114

115-
create_collation("und-u-ks-level2", name: "case_insensitive", deterministic: false)
115+
# Case-insensitive: secondary strength defaults to a
116+
# nondeterministic collation, the only mode in which case
117+
# variants actually compare equal
118+
create_collation("und", strength: :secondary)
119+
120+
# Natural sort: "file2" before "file10"
121+
create_collation("en", numeric: true)
116122
```
117123

118-
The second example creates a nondeterministic, case-insensitive collation from ICU's `ks-level2` (strength: secondary) tailoring — useful for case-insensitive equality, with the caveats described in the [README](https://hexdocs.pm/localize_ecto/readme.html#deterministic-collations-and-unicode-normalization).
124+
Query-time resolution carries the same `-u-` keywords, so a tailored collation created under its default name is found automatically — `collate(f.name, "en-u-kn-true")` after `create_collation("en", numeric: true)`. See [Collations in PostgreSQL](https://hexdocs.pm/localize_ecto/collations_in_postgres.html) for the caveats around nondeterministic collations.
119125

120126
## Matching against a different server
121127

mix.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
defmodule LocalizeEcto.MixProject do
22
use Mix.Project
33

4-
@version "0.2.0"
4+
@version "0.3.0"
55
@source_url "https://github.com/elixir-localize/localize_ecto"
66

77
def project do

test/localize/ecto/audit_test.exs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,10 +67,13 @@ defmodule Localize.Ecto.AuditTest do
6767
test "the application zone inventory is known to the server" do
6868
audit = Audit.timezone_audit(TestRepo)
6969

70-
# Both inventories track IANA; a handful of drift entries can
71-
# appear when tzdata versions differ, but wholesale divergence
72-
# means a broken inventory on one side.
73-
assert length(audit.unknown_to_server) < 10
70+
# Both inventories track IANA; dozens of drift entries can
71+
# appear when the server's tzdata lags CLDR (CI containers
72+
# often trail by a release), but wholesale divergence means a
73+
# broken inventory on one side. Bound the drift at 10% of the
74+
# application inventory.
75+
application_zones = length(Localize.DateTime.Timezone.known_timezones())
76+
assert length(audit.unknown_to_server) < div(application_zones, 10)
7477
assert is_list(audit.unknown_to_application)
7578
end
7679
end

0 commit comments

Comments
 (0)