gedcomkit 0.1.19__tar.gz → 0.1.20__tar.gz
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.
- {gedcomkit-0.1.19 → gedcomkit-0.1.20}/CHANGELOG.md +12 -0
- {gedcomkit-0.1.19 → gedcomkit-0.1.20}/Cargo.lock +1 -1
- {gedcomkit-0.1.19 → gedcomkit-0.1.20}/Cargo.toml +1 -1
- {gedcomkit-0.1.19 → gedcomkit-0.1.20}/PKG-INFO +1 -1
- {gedcomkit-0.1.19 → gedcomkit-0.1.20}/README.md +2 -6
- {gedcomkit-0.1.19 → gedcomkit-0.1.20}/bindings/gedcomkit-py/Cargo.lock +2 -2
- {gedcomkit-0.1.19 → gedcomkit-0.1.20}/bindings/gedcomkit-py/Cargo.toml +1 -1
- {gedcomkit-0.1.19 → gedcomkit-0.1.20}/src/build.rs +1 -1
- {gedcomkit-0.1.19 → gedcomkit-0.1.20}/src/dates.rs +139 -3
- {gedcomkit-0.1.19 → gedcomkit-0.1.20}/LICENSE +0 -0
- {gedcomkit-0.1.19 → gedcomkit-0.1.20}/bindings/gedcomkit-py/LICENSE +0 -0
- {gedcomkit-0.1.19 → gedcomkit-0.1.20}/bindings/gedcomkit-py/README.md +0 -0
- {gedcomkit-0.1.19 → gedcomkit-0.1.20}/bindings/gedcomkit-py/gedcomkit.pyi +0 -0
- {gedcomkit-0.1.19 → gedcomkit-0.1.20}/bindings/gedcomkit-py/src/lib.rs +0 -0
- {gedcomkit-0.1.19 → gedcomkit-0.1.20}/fixtures/vendored/gedcom-spec/LICENSE +0 -0
- {gedcomkit-0.1.19 → gedcomkit-0.1.20}/fixtures/vendored/gedcom-spec/README.md +0 -0
- {gedcomkit-0.1.19 → gedcomkit-0.1.20}/fixtures/vendored/gedcom-spec/cardinalities.tsv +0 -0
- {gedcomkit-0.1.19 → gedcomkit-0.1.20}/fixtures/vendored/gedcom-spec/enumerations.tsv +0 -0
- {gedcomkit-0.1.19 → gedcomkit-0.1.20}/fixtures/vendored/gedcom-spec/enumerationsets.tsv +0 -0
- {gedcomkit-0.1.19 → gedcomkit-0.1.20}/fixtures/vendored/gedcom-spec/grammar.abnf +0 -0
- {gedcomkit-0.1.19 → gedcomkit-0.1.20}/fixtures/vendored/gedcom-spec/grammar.gedstruct +0 -0
- {gedcomkit-0.1.19 → gedcomkit-0.1.20}/fixtures/vendored/gedcom-spec/payloads.tsv +0 -0
- {gedcomkit-0.1.19 → gedcomkit-0.1.20}/fixtures/vendored/gedcom-spec/substructures.tsv +0 -0
- {gedcomkit-0.1.19 → gedcomkit-0.1.20}/fixtures/vendored/gedcom7code/5/age-invalid.ged +0 -0
- {gedcomkit-0.1.19 → gedcomkit-0.1.20}/fixtures/vendored/gedcom7code/5/age-valid.ged +0 -0
- {gedcomkit-0.1.19 → gedcomkit-0.1.20}/fixtures/vendored/gedcom7code/5/atsign.ged +0 -0
- {gedcomkit-0.1.19 → gedcomkit-0.1.20}/fixtures/vendored/gedcom7code/5/char_ascii_1.ged +0 -0
- {gedcomkit-0.1.19 → gedcomkit-0.1.20}/fixtures/vendored/gedcom7code/5/char_ascii_2.ged +0 -0
- {gedcomkit-0.1.19 → gedcomkit-0.1.20}/fixtures/vendored/gedcom7code/5/char_utf16be-1.ged +0 -0
- {gedcomkit-0.1.19 → gedcomkit-0.1.20}/fixtures/vendored/gedcom7code/5/char_utf16be-2.ged +0 -0
- {gedcomkit-0.1.19 → gedcomkit-0.1.20}/fixtures/vendored/gedcom7code/5/char_utf16le-1.ged +0 -0
- {gedcomkit-0.1.19 → gedcomkit-0.1.20}/fixtures/vendored/gedcom7code/5/char_utf16le-2.ged +0 -0
- {gedcomkit-0.1.19 → gedcomkit-0.1.20}/fixtures/vendored/gedcom7code/5/char_utf8-1.ged +0 -0
- {gedcomkit-0.1.19 → gedcomkit-0.1.20}/fixtures/vendored/gedcom7code/5/char_utf8-2.ged +0 -0
- {gedcomkit-0.1.19 → gedcomkit-0.1.20}/fixtures/vendored/gedcom7code/5/char_utf8-3.ged +0 -0
- {gedcomkit-0.1.19 → gedcomkit-0.1.20}/fixtures/vendored/gedcom7code/5/date-all.ged +0 -0
- {gedcomkit-0.1.19 → gedcomkit-0.1.20}/fixtures/vendored/gedcom7code/5/date-dual-invalid.ged +0 -0
- {gedcomkit-0.1.19 → gedcomkit-0.1.20}/fixtures/vendored/gedcom7code/5/date-dual-valid.ged +0 -0
- {gedcomkit-0.1.19 → gedcomkit-0.1.20}/fixtures/vendored/gedcom7code/5/enum-ext.ged +0 -0
- {gedcomkit-0.1.19 → gedcomkit-0.1.20}/fixtures/vendored/gedcom7code/5/filename-1.ged +0 -0
- {gedcomkit-0.1.19 → gedcomkit-0.1.20}/fixtures/vendored/gedcom7code/5/lang-all.ged +0 -0
- {gedcomkit-0.1.19 → gedcomkit-0.1.20}/fixtures/vendored/gedcom7code/5/notes-1.ged +0 -0
- {gedcomkit-0.1.19 → gedcomkit-0.1.20}/fixtures/vendored/gedcom7code/5/obje-1.ged +0 -0
- {gedcomkit-0.1.19 → gedcomkit-0.1.20}/fixtures/vendored/gedcom7code/5/obsolete-1.ged +0 -0
- {gedcomkit-0.1.19 → gedcomkit-0.1.20}/fixtures/vendored/gedcom7code/5/pedi-1.ged +0 -0
- {gedcomkit-0.1.19 → gedcomkit-0.1.20}/fixtures/vendored/gedcom7code/5/rela_1.ged +0 -0
- {gedcomkit-0.1.19 → gedcomkit-0.1.20}/fixtures/vendored/gedcom7code/5/sour-1.ged +0 -0
- {gedcomkit-0.1.19 → gedcomkit-0.1.20}/fixtures/vendored/gedcom7code/5/tiny-1.ged +0 -0
- {gedcomkit-0.1.19 → gedcomkit-0.1.20}/fixtures/vendored/gedcom7code/5/xref-case.ged +0 -0
- {gedcomkit-0.1.19 → gedcomkit-0.1.20}/fixtures/vendored/gedcom7code/7/age-invalid.ged +0 -0
- {gedcomkit-0.1.19 → gedcomkit-0.1.20}/fixtures/vendored/gedcom7code/7/age-valid.ged +0 -0
- {gedcomkit-0.1.19 → gedcomkit-0.1.20}/fixtures/vendored/gedcom7code/7/atsign.ged +0 -0
- {gedcomkit-0.1.19 → gedcomkit-0.1.20}/fixtures/vendored/gedcom7code/7/char_ascii_1.ged +0 -0
- {gedcomkit-0.1.19 → gedcomkit-0.1.20}/fixtures/vendored/gedcom7code/7/char_ascii_2.ged +0 -0
- {gedcomkit-0.1.19 → gedcomkit-0.1.20}/fixtures/vendored/gedcom7code/7/char_utf16be-1.ged +0 -0
- {gedcomkit-0.1.19 → gedcomkit-0.1.20}/fixtures/vendored/gedcom7code/7/char_utf16be-2.ged +0 -0
- {gedcomkit-0.1.19 → gedcomkit-0.1.20}/fixtures/vendored/gedcom7code/7/char_utf16le-1.ged +0 -0
- {gedcomkit-0.1.19 → gedcomkit-0.1.20}/fixtures/vendored/gedcom7code/7/char_utf16le-2.ged +0 -0
- {gedcomkit-0.1.19 → gedcomkit-0.1.20}/fixtures/vendored/gedcom7code/7/char_utf8-1.ged +0 -0
- {gedcomkit-0.1.19 → gedcomkit-0.1.20}/fixtures/vendored/gedcom7code/7/char_utf8-2.ged +0 -0
- {gedcomkit-0.1.19 → gedcomkit-0.1.20}/fixtures/vendored/gedcom7code/7/char_utf8-3.ged +0 -0
- {gedcomkit-0.1.19 → gedcomkit-0.1.20}/fixtures/vendored/gedcom7code/7/date-all.ged +0 -0
- {gedcomkit-0.1.19 → gedcomkit-0.1.20}/fixtures/vendored/gedcom7code/7/date-dual-invalid.ged +0 -0
- {gedcomkit-0.1.19 → gedcomkit-0.1.20}/fixtures/vendored/gedcom7code/7/enum-ext.ged +0 -0
- {gedcomkit-0.1.19 → gedcomkit-0.1.20}/fixtures/vendored/gedcom7code/7/filename-1.ged +0 -0
- {gedcomkit-0.1.19 → gedcomkit-0.1.20}/fixtures/vendored/gedcom7code/7/lang-all.ged +0 -0
- {gedcomkit-0.1.19 → gedcomkit-0.1.20}/fixtures/vendored/gedcom7code/7/notes-1.ged +0 -0
- {gedcomkit-0.1.19 → gedcomkit-0.1.20}/fixtures/vendored/gedcom7code/7/obje-1.ged +0 -0
- {gedcomkit-0.1.19 → gedcomkit-0.1.20}/fixtures/vendored/gedcom7code/7/obsolete-1.ged +0 -0
- {gedcomkit-0.1.19 → gedcomkit-0.1.20}/fixtures/vendored/gedcom7code/7/pedi-1.ged +0 -0
- {gedcomkit-0.1.19 → gedcomkit-0.1.20}/fixtures/vendored/gedcom7code/7/rela_1.ged +0 -0
- {gedcomkit-0.1.19 → gedcomkit-0.1.20}/fixtures/vendored/gedcom7code/7/sour-1.ged +0 -0
- {gedcomkit-0.1.19 → gedcomkit-0.1.20}/fixtures/vendored/gedcom7code/7/tiny-1.ged +0 -0
- {gedcomkit-0.1.19 → gedcomkit-0.1.20}/fixtures/vendored/gedcom7code/7/xref-case.ged +0 -0
- {gedcomkit-0.1.19 → gedcomkit-0.1.20}/fixtures/vendored/gedcom7code/LICENSE +0 -0
- {gedcomkit-0.1.19 → gedcomkit-0.1.20}/fixtures/vendored/gedcom7code/UPSTREAM-README.md +0 -0
- {gedcomkit-0.1.19 → gedcomkit-0.1.20}/fixtures/vendored/zip-interop/README.md +0 -0
- {gedcomkit-0.1.19 → gedcomkit-0.1.20}/fixtures/vendored/zip-interop/python-zip64.gdz +0 -0
- {gedcomkit-0.1.19 → gedcomkit-0.1.20}/fixtures/vendored/zip-interop/python-zipfile.gdz +0 -0
- {gedcomkit-0.1.19 → gedcomkit-0.1.20}/pyproject.toml +0 -0
- {gedcomkit-0.1.19 → gedcomkit-0.1.20}/src/convert.rs +0 -0
- {gedcomkit-0.1.19 → gedcomkit-0.1.20}/src/decode.rs +0 -0
- {gedcomkit-0.1.19 → gedcomkit-0.1.20}/src/family.rs +0 -0
- {gedcomkit-0.1.19 → gedcomkit-0.1.20}/src/gedzip.rs +0 -0
- {gedcomkit-0.1.19 → gedcomkit-0.1.20}/src/lib.rs +0 -0
- {gedcomkit-0.1.19 → gedcomkit-0.1.20}/src/names.rs +0 -0
- {gedcomkit-0.1.19 → gedcomkit-0.1.20}/src/schema.rs +0 -0
- {gedcomkit-0.1.19 → gedcomkit-0.1.20}/src/synthetic.rs +0 -0
- {gedcomkit-0.1.19 → gedcomkit-0.1.20}/src/tags.rs +0 -0
- {gedcomkit-0.1.19 → gedcomkit-0.1.20}/src/validate.rs +0 -0
- {gedcomkit-0.1.19 → gedcomkit-0.1.20}/src/view.rs +0 -0
- {gedcomkit-0.1.19 → gedcomkit-0.1.20}/tests/endurance.rs +0 -0
- {gedcomkit-0.1.19 → gedcomkit-0.1.20}/tests/external_charset.rs +0 -0
- {gedcomkit-0.1.19 → gedcomkit-0.1.20}/tests/properties.rs +0 -0
- {gedcomkit-0.1.19 → gedcomkit-0.1.20}/tests/requirement_corpus.rs +0 -0
- {gedcomkit-0.1.19 → gedcomkit-0.1.20}/tests/spec_conformance.rs +0 -0
- {gedcomkit-0.1.19 → gedcomkit-0.1.20}/tests/typescript.rs +0 -0
- {gedcomkit-0.1.19 → gedcomkit-0.1.20}/tests/vendored_corpus.rs +0 -0
- {gedcomkit-0.1.19 → gedcomkit-0.1.20}/tests/version_conversion.rs +0 -0
|
@@ -7,6 +7,18 @@ rules.
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.1.20] - 2026-11-09
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- The date reader reads a calendar named after a qualifier or a range
|
|
15
|
+
word, as both grammars write it: `INT @#DJULIAN@ 2 MAR 1700 (…)`,
|
|
16
|
+
`BEF @#DJULIAN@ 2 JAN 1700`, `ABT JULIAN 1700`, `BET @#DJULIAN@ 1700 AND
|
|
17
|
+
@#DJULIAN@ 1710`. Such a date was left unparsed before, though
|
|
18
|
+
`to_gedcom_5_5_1` writes exactly this form, so what it wrote did not read
|
|
19
|
+
back. A range whose ends name different calendars, or whose unnamed end
|
|
20
|
+
(Gregorian) disagrees with a named one, stays unparsed.
|
|
21
|
+
|
|
10
22
|
## [0.1.19] - 2026-10-17
|
|
11
23
|
|
|
12
24
|
### Added
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
# was checked on crates.io, npm, and PyPI before the initial release.
|
|
4
4
|
[package]
|
|
5
5
|
name = "gedcomkit"
|
|
6
|
-
version = "0.1.
|
|
6
|
+
version = "0.1.20"
|
|
7
7
|
description = "A byte-preserving GEDCOM document model: decoding, parsing, readings, version conversion, plausibility checks, and the GEDZIP container, for GEDCOM 5.5 through 7.x."
|
|
8
8
|
edition = "2024"
|
|
9
9
|
# The true minimum, not the workspace's toolchain: a parser library is a leaf
|
|
@@ -42,12 +42,8 @@ npm install gedcomkit
|
|
|
42
42
|
2. Run the gates and release dry run:
|
|
43
43
|
|
|
44
44
|
```bash
|
|
45
|
-
cargo
|
|
46
|
-
cargo
|
|
47
|
-
cargo test --all-features
|
|
48
|
-
cargo test --no-default-features
|
|
49
|
-
cargo check --manifest-path bindings/gedcomkit-wasm/Cargo.toml --target wasm32-unknown-unknown
|
|
50
|
-
cargo check --manifest-path bindings/gedcomkit-py/Cargo.toml
|
|
45
|
+
cargo ci
|
|
46
|
+
cargo +1.88 ci test
|
|
51
47
|
cargo publish --locked --dry-run
|
|
52
48
|
bash scripts/check-release-tag.sh vX.Y.Z
|
|
53
49
|
```
|
|
@@ -35,7 +35,7 @@ dependencies = [
|
|
|
35
35
|
|
|
36
36
|
[[package]]
|
|
37
37
|
name = "gedcomkit"
|
|
38
|
-
version = "0.1.
|
|
38
|
+
version = "0.1.20"
|
|
39
39
|
dependencies = [
|
|
40
40
|
"flate2",
|
|
41
41
|
"serde",
|
|
@@ -43,7 +43,7 @@ dependencies = [
|
|
|
43
43
|
|
|
44
44
|
[[package]]
|
|
45
45
|
name = "gedcomkit-py"
|
|
46
|
-
version = "0.1.
|
|
46
|
+
version = "0.1.20"
|
|
47
47
|
dependencies = [
|
|
48
48
|
"gedcomkit",
|
|
49
49
|
"pyo3",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
# abi3 keeps it to one wheel per platform across Python versions.
|
|
3
3
|
[package]
|
|
4
4
|
name = "gedcomkit-py"
|
|
5
|
-
version = "0.1.
|
|
5
|
+
version = "0.1.20"
|
|
6
6
|
description = "Python bindings for gedcomkit: an archival-grade GEDCOM library — byte-preserving round-trip, encoding detection, GEDCOM 5.5 through 7.x, GEDZIP."
|
|
7
7
|
edition = "2024"
|
|
8
8
|
rust-version = "1.88"
|
|
@@ -316,7 +316,7 @@ impl Node {
|
|
|
316
316
|
/// beside a space loses that space on re-joining. Failing that — a run
|
|
317
317
|
/// of spaces wider than the limit, say — the cut falls back to the
|
|
318
318
|
/// latest legal byte boundary instead, which is documented behavior of
|
|
319
|
-
///
|
|
319
|
+
/// the private `cut` this call reuses, not a special case here.
|
|
320
320
|
///
|
|
321
321
|
/// The rendered line is measured in UTF-8 bytes, including level,
|
|
322
322
|
/// optional cross-reference, tag, delimiters, and a terminator reserved
|
|
@@ -300,8 +300,19 @@ impl GedcomDate {
|
|
|
300
300
|
pub fn parse(payload: &str) -> Self {
|
|
301
301
|
let original = payload.to_owned();
|
|
302
302
|
let (escape, rest) = split_calendar(payload.trim());
|
|
303
|
-
let calendar = escape.unwrap_or_default();
|
|
304
303
|
let (rest, phrase) = split_phrase(&rest);
|
|
304
|
+
// Both grammars put the calendar after a qualifier or a range word
|
|
305
|
+
// (`ABT @#DJULIAN@ 1700`, `BET JULIAN 1700 AND JULIAN 1710`): read
|
|
306
|
+
// there when the payload does not open with one, and only when every
|
|
307
|
+
// part of the payload names the same calendar.
|
|
308
|
+
let (escape, rest) = match escape {
|
|
309
|
+
Some(escape) => (Some(escape), rest),
|
|
310
|
+
None => match inner_calendar(&rest) {
|
|
311
|
+
Some((calendar, rest)) => (Some(calendar), rest),
|
|
312
|
+
None => (None, rest),
|
|
313
|
+
},
|
|
314
|
+
};
|
|
315
|
+
let calendar = escape.unwrap_or_default();
|
|
305
316
|
let declared = escape.is_some();
|
|
306
317
|
let dual = dual_year(&rest);
|
|
307
318
|
let mut date = parse_expression(&rest)
|
|
@@ -690,7 +701,7 @@ fn parse_expression(input: &str) -> Option<GedcomDate> {
|
|
|
690
701
|
// read (`FROM ABT 1924 TO 1959` puts a qualifier inside the range, which
|
|
691
702
|
// neither version's grammar allows) would otherwise leave a range with
|
|
692
703
|
// that end gone and nothing to say so.
|
|
693
|
-
if let Some((left, right)) = split_keyword(trimmed, &
|
|
704
|
+
if let Some((left, right)) = split_keyword(trimmed, &RANGE_AND)
|
|
694
705
|
&& let Some(prefix) =
|
|
695
706
|
strip_keyword(left, &["BET", "BETWEEN", "ENTRE", "ZWISCHEN", "MELLOM"])
|
|
696
707
|
{
|
|
@@ -698,7 +709,7 @@ fn parse_expression(input: &str) -> Option<GedcomDate> {
|
|
|
698
709
|
let to = parse_point(right)?;
|
|
699
710
|
return build(DateKind::Between, Some(from), Some(to));
|
|
700
711
|
}
|
|
701
|
-
if let Some((left, right)) = split_keyword(trimmed, &
|
|
712
|
+
if let Some((left, right)) = split_keyword(trimmed, &RANGE_TO)
|
|
702
713
|
&& let Some(prefix) = strip_keyword(left, &["FROM", "DE", "DU", "VON", "FRA"])
|
|
703
714
|
{
|
|
704
715
|
let from = parse_point(prefix)?;
|
|
@@ -815,6 +826,81 @@ fn split_calendar(input: &str) -> (Option<Calendar>, String) {
|
|
|
815
826
|
(None, input.to_owned())
|
|
816
827
|
}
|
|
817
828
|
|
|
829
|
+
/// The words between a range's ends, in every spelling the reader takes:
|
|
830
|
+
/// `BET x AND y` and `FROM x TO y`.
|
|
831
|
+
const RANGE_AND: [&str; 4] = ["AND", "ET", "UND", "OG"];
|
|
832
|
+
const RANGE_TO: [&str; 4] = ["TO", "AU", "BIS", "TIL"];
|
|
833
|
+
|
|
834
|
+
/// The words a calendar may follow inside a date: a qualifier, or a word of
|
|
835
|
+
/// a range.
|
|
836
|
+
const BEFORE_CALENDAR: [&str; 10] = [
|
|
837
|
+
"ABT", "CAL", "EST", "BEF", "AFT", "INT", "BET", "AND", "FROM", "TO",
|
|
838
|
+
];
|
|
839
|
+
|
|
840
|
+
/// The calendar named after a qualifier or a range word, and the payload
|
|
841
|
+
/// without it, when at least one part names one and every naming agrees.
|
|
842
|
+
/// A 5.5.1 escape (`@#DJULIAN@`, `@#DFRENCH R@`) or a GEDCOM 7 calendar word
|
|
843
|
+
/// (`JULIAN`) is read only directly after one of [`BEFORE_CALENDAR`], so
|
|
844
|
+
/// words elsewhere are left alone. Two different calendars, or a range
|
|
845
|
+
/// whose ends do not all name one, give `None`, and the payload stays as it
|
|
846
|
+
/// was for the reader to leave unparsed.
|
|
847
|
+
fn inner_calendar(input: &str) -> Option<(Calendar, String)> {
|
|
848
|
+
let mut found: Option<Calendar> = None;
|
|
849
|
+
let mut kept: Vec<&str> = Vec::new();
|
|
850
|
+
let mut rest = input.trim();
|
|
851
|
+
let mut after_keyword = false;
|
|
852
|
+
let mut namings = 0;
|
|
853
|
+
while !rest.is_empty() {
|
|
854
|
+
let named = if after_keyword {
|
|
855
|
+
if let Some(escape) = rest.strip_prefix("@#D")
|
|
856
|
+
&& let Some((name, remainder)) = escape.split_once('@')
|
|
857
|
+
{
|
|
858
|
+
Some((calendar_from(name), remainder))
|
|
859
|
+
} else {
|
|
860
|
+
let (word, remainder) = rest.split_once(' ').unwrap_or((rest, ""));
|
|
861
|
+
matches!(
|
|
862
|
+
word,
|
|
863
|
+
"GREGORIAN" | "JULIAN" | "HEBREW" | "FRENCH_R" | "ROMAN"
|
|
864
|
+
)
|
|
865
|
+
.then(|| (calendar_from(word), remainder))
|
|
866
|
+
}
|
|
867
|
+
} else {
|
|
868
|
+
None
|
|
869
|
+
};
|
|
870
|
+
if let Some((calendar, remainder)) = named {
|
|
871
|
+
if found.is_some_and(|known| known != calendar) {
|
|
872
|
+
return None;
|
|
873
|
+
}
|
|
874
|
+
found = Some(calendar);
|
|
875
|
+
namings += 1;
|
|
876
|
+
rest = remainder.trim_start();
|
|
877
|
+
after_keyword = false;
|
|
878
|
+
continue;
|
|
879
|
+
}
|
|
880
|
+
let (word, remainder) = rest.split_once(' ').unwrap_or((rest, ""));
|
|
881
|
+
after_keyword = BEFORE_CALENDAR
|
|
882
|
+
.iter()
|
|
883
|
+
.any(|keyword| word.eq_ignore_ascii_case(keyword));
|
|
884
|
+
kept.push(word);
|
|
885
|
+
rest = remainder.trim_start();
|
|
886
|
+
}
|
|
887
|
+
// Each end of a range names its own calendar; one left unnamed is
|
|
888
|
+
// Gregorian, which one date cannot hold beside another calendar.
|
|
889
|
+
let ends = 1 + kept
|
|
890
|
+
.iter()
|
|
891
|
+
.enumerate()
|
|
892
|
+
.filter(|(at, word)| {
|
|
893
|
+
let is = |words: &[&str]| words.iter().any(|w| word.eq_ignore_ascii_case(w));
|
|
894
|
+
is(&RANGE_AND) || (*at > 0 && is(&RANGE_TO))
|
|
895
|
+
})
|
|
896
|
+
.count();
|
|
897
|
+
// An unnamed end is Gregorian, which agrees only with a Gregorian one.
|
|
898
|
+
if namings != ends && found != Some(Calendar::Gregorian) {
|
|
899
|
+
return None;
|
|
900
|
+
}
|
|
901
|
+
found.map(|calendar| (calendar, kept.join(" ")))
|
|
902
|
+
}
|
|
903
|
+
|
|
818
904
|
/// The number of days in a month of a year, in the Julian calendar or the
|
|
819
905
|
/// Gregorian. The year is counted as given, with no shift for the missing
|
|
820
906
|
/// year zero; [`day_exists`] makes that shift before it asks.
|
|
@@ -2113,4 +2199,54 @@ mod tests {
|
|
|
2113
2199
|
["ABT 1875", "5 JAN 1882", "BET 1890 AND 1895", "1900"]
|
|
2114
2200
|
);
|
|
2115
2201
|
}
|
|
2202
|
+
#[test]
|
|
2203
|
+
fn a_calendar_after_a_qualifier_or_range_word_is_read() {
|
|
2204
|
+
let julian = |text: &str| {
|
|
2205
|
+
let date = GedcomDate::parse(text);
|
|
2206
|
+
(date.calendar, date.kind, date.earliest, date.phrase)
|
|
2207
|
+
};
|
|
2208
|
+
let (calendar, kind, earliest, phrase) = julian("INT @#DJULIAN@ 2 MAR 1700 (x)");
|
|
2209
|
+
assert_eq!(calendar, Calendar::Julian);
|
|
2210
|
+
assert_eq!(kind, DateKind::Interpreted);
|
|
2211
|
+
assert_eq!(
|
|
2212
|
+
earliest.map(|point| (point.month, point.day)),
|
|
2213
|
+
Some((Some(3), Some(2)))
|
|
2214
|
+
);
|
|
2215
|
+
assert_eq!(phrase.as_deref(), Some("x"));
|
|
2216
|
+
for text in [
|
|
2217
|
+
"BEF @#DJULIAN@ 2 JAN 1700",
|
|
2218
|
+
"ABT JULIAN 1700",
|
|
2219
|
+
"BET @#DJULIAN@ 1700 AND @#DJULIAN@ 1710",
|
|
2220
|
+
"FROM JULIAN 1700 TO JULIAN 1710",
|
|
2221
|
+
] {
|
|
2222
|
+
let date = GedcomDate::parse(text);
|
|
2223
|
+
assert_eq!(date.calendar, Calendar::Julian, "{text}");
|
|
2224
|
+
assert!(date.is_parsed(), "{text}");
|
|
2225
|
+
assert_eq!(date.original, text);
|
|
2226
|
+
}
|
|
2227
|
+
assert_eq!(
|
|
2228
|
+
GedcomDate::parse("BEF @#DJULIAN@ 2 JAN 1700").display(),
|
|
2229
|
+
"before 2 January 1700"
|
|
2230
|
+
);
|
|
2231
|
+
// One end unnamed, or two calendars: not one date.
|
|
2232
|
+
for text in [
|
|
2233
|
+
"BET @#DJULIAN@ 1700 AND 1710",
|
|
2234
|
+
"BET @#DJULIAN@ 1700 ET 1710",
|
|
2235
|
+
"FROM @#DJULIAN@ 1700 BIS 1710",
|
|
2236
|
+
"BET @#DJULIAN@ 1700 AND @#DGREGORIAN@ 1710",
|
|
2237
|
+
] {
|
|
2238
|
+
let date = GedcomDate::parse(text);
|
|
2239
|
+
assert!(!date.is_parsed(), "{text}");
|
|
2240
|
+
assert_eq!(date.calendar, Calendar::Gregorian, "{text}");
|
|
2241
|
+
}
|
|
2242
|
+
// An unnamed end is Gregorian, so it agrees with a Gregorian one.
|
|
2243
|
+
assert!(GedcomDate::parse("BET @#DGREGORIAN@ 1700 AND 1710").is_parsed());
|
|
2244
|
+
// A calendar word anywhere else is no calendar.
|
|
2245
|
+
assert!(!GedcomDate::parse("1700 JULIAN").is_parsed());
|
|
2246
|
+
// What the 5.5.1 writer writes, the reader reads back.
|
|
2247
|
+
let written = GedcomDate::parse("@#DJULIAN@ 1740/41").to_gedcom_5_5_1();
|
|
2248
|
+
let back = GedcomDate::parse(written.as_deref().unwrap_or_default());
|
|
2249
|
+
assert_eq!(back.calendar, Calendar::Julian);
|
|
2250
|
+
assert_eq!(back.earliest.map(|point| point.year), Some(1740));
|
|
2251
|
+
}
|
|
2116
2252
|
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|