wisski_date_field_extractor
The WissKI date field stores what was typed and, on save, fills sibling fields with a time span Views can filter and sort.
To install the module and set up a field, see Installation and configuration.
What is stored¶
Qualified begin and qualified end are the stated date (1943-01-01,
1943-12-31). Earliest and latest begin and end are sortable integers,
year * 10000 + month * 100 + day, including negative years.
1943 still stores qualified begin 1943-01-01 and qualified end
1943-12-31. February of a year-month such as 1944-02 still ends on
the 28th in those sibling integers. That is the old Level 0 path, kept so
existing Views stay as they are. The parser and the stored pathbuilder
seconds use the real last day, so 2000-02 ends on 29 February
there. The two results are a documented legacy difference, and new EDTF
features are not added on the Level 0 path.
Widths¶
The approximate widths are set at
EDTF widths.
1943~ with approximate years set to 1 is 1942 through 1944. Set to 10,
it is 1933 through 1953. 1900? stays the year 1900. 1900% uses the same
width as ~: 1899 through 1901, and it also records that 1900 may be
wrong. EDTF names the kind of uncertainty.
How many years, months or days that adds is this module's rule, and the
numbers above are that rule. ? does not add a width. X ignores the
widths: 201X is 2010 through 2019, and 190X is 1900 through 1909.
Saving an entity again does not reread an unchanged EDTF string, unless
that value adjusts its own widths, so a width change needs
drush wisski:edtf-reparse.
Time-span collection¶
The parsed value is a time-span collection. Each member is a time-span. The collection is stored on the dates field of the time expression: one reference per member, earliest first. One interval is one time-span. A set is one time-span per member. Which field receives which value is listed under Map existing bundles by hand.
EDTF profile¶
This is a WissKI profile of EDTF,
not a claim that Level 0, 1 or 2 is fully supported. Level 0 date and
time, including Z and a UTC offset, is outside the profile: the time
is dropped and the calendar date in the string is kept.
1985-04-12T23:20:30+14:00 stays 12 April 1985. It is not shifted into
another zone. Basic format without hyphens (20050924) is rejected.
Seasons 25 to 41 are not accepted. 1670..1672 on its own is an
extension; the EDTF interval is 1670/1672. Inside a set, .. is the
EDTF list range. An open side is ... An unknown side is empty, on
either side of /.
| Sign | Meaning |
|---|---|
~ |
Approximate. The approximate width is added on each side. The stated value is kept beside the widened seconds. A mark immediately right of a component covers that component and everything to its left (2004-06~-11). A mark immediately left of a component covers that component only (2004-%06-11) |
? |
Uncertain, stored under the reading Probable. That name does not state a probability. The stated date stays. It does not add a width |
% |
Both, and the same left/right rule as ~ and ?. 2004-06-11% covers year, month and day. 1900-%04 covers the month only |
X |
Unspecified. The whole range of that digit. Exactly one value in that range applies |
.. or / |
Interval. 1670/1672 is one time-span that began in 1670 and ended in 1672. 1670..1672 alone is a WissKI extension of that. ../ and /.. are open: the configured year is a sort index only, and that side has no second and no RDF bound. An empty side (1984/ or /1984) is unknown and leaves the integer empty too |
!(…) |
At some time within. A WissKI extension, see Time model |
=(…) |
Exactly throughout. A WissKI extension, see Time model |
{a,b} |
All of these dates. Each member is its own time-span, and the time expression is typed all_of. A year between them does not match Within |
[a,b] |
Exactly one of these dates, typed one_of. Within still matches a listed date, because the value might be that one |
21–24 |
Season, as a concrete span. 21 is 1 March–31 May, 22 is 1 June–31 August, 23 is 1 September–30 November, 24 is 1 December through the last day of February in the next year, including a leap day |
S |
Significant digits. 1950S2 is estimated as 1950, and the outer bounds are 1900 through 1999. It is not a certain span and not an unspecified digit |
- or Y |
Negative and extended years (-0044, Y-12345). A year whose packed day does not fit a signed 32-bit integer stays a parsed value: the text is kept, the integer fields stay empty, and that is not the same as text that is not EDTF |
With the default widths, 1900-04%-XX runs from 1 March 1899 through
31 May 1901. % to the right of April covers the year and the month
and adds the approximate width to each. The day stays unspecified. A time-span stores a
known side as 00:00:00 and 23:59:59. An open .. side and an
unknown / side store no second.
The sibling integer is year * 10000 + month * 100 + day in a signed
32-bit column, from -2147483648 to 2147483647. That is a Drupal integer
column, not an EDTF limit. Y214748 still fits. Y214749 and Y-214749
do not. The original text is kept and the span fields are left empty.
The value is still parsed. Text that is not EDTF is not.
Views¶
- Within — one stored date overlaps the value. A year between two
set members does not match:
[1900,1904,=(1908)]does not match 1902–1903. This uses the date-list field, an unlimited text field with one value per date, and compares at most 64 dates. A longer value matches nothing. Without that field, Within matches nothing, because the first and last date alone would match a gap year. - Before (including the bound) — the last date ends on or before the value. It reads the latest-end integer.
- After (including the bound) — the first date starts on or after the value. It reads the earliest-begin integer.
The earliest-begin integer is the first date, so it is also the sort key. Setup: Set up Views.
Views on the time model¶
A field that references the time expression bundle or the time-span bundle, such as Event → Date, gets a second Views entry, …: time-span bounds. It filters and sorts by the four bounds of the referenced time-spans, read through the mapped fields. The bundle that holds the field needs no path of its own down to the time-spans. The handler settings name the bounds by their field labels:
| Bound | Default label | Filled with |
|---|---|---|
| Outer begin | Some time within, begin | the earliest second the time-span can begin |
| Inner begin | At least throughout, begin | the latest second it can begin |
| Inner end | At least throughout, end | the earliest second it can end |
| Outer end | Some time within, end | the latest second it can end |
The filter value is EDTF. By default (All mapped bounds, by operator) the operator picks the bounds, so one filter covers what the four bounds say:
| Operator | Matches a time-span that | Compares |
|---|---|---|
| Is before | surely ended before the value begins | Some time within end < first second |
| Is before or in | surely ended by the end of the value | Some time within end ≤ last second |
| Is in | lies entirely in the value | Some time within begin ≥ first second and end ≤ last second |
| Is in or after | surely began at or after the start of the value | Some time within begin ≥ first second |
| Is after | surely began after the value ends | Some time within begin > last second |
| Overlaps | may have happened at the same time | Some time within begin ≤ last second and end ≥ first second |
| Went on throughout | surely went on during the whole value | At least throughout begin ≤ first second and end ≥ last second |
A comparison of two bounds reads the same time-span, so [1800,1850]
does not overlap 1825. For Overlaps, a side the time-span leaves open
is unbounded: after 1853 stores no end and overlaps 1800/1900, and
1900/.. overlaps every later value. The other operators need the
bounds they compare. One matching time-span is enough. Went on
throughout needs the inner bounds, which only an interval such as
1900/1910 or =(…) has. Has a time-span and Has no time-span take
no value and count a time-span with any mapped bound. A single bound can
be chosen instead: the operators before, in and after then compare that
bound alone, and Overlaps and Went on throughout keep their two
bounds.
The sort uses every mapped bound by default (All mapped bounds,
chronologically). Ascending, the keys are Some time within begin, then
Some time within end, then At least throughout begin, then At least
throughout end: 1900 comes before 1900/1950, and =(1900/1910)
before 1900/1910. The outer pair comes first because the inner bounds
are empty for a single date and for !(…); ranked earlier, they would
sort by whether they exist. Descending mirrors the time axis and starts
with the end: the event that ended last comes first. A bound that is not
mapped is left out. One bound can be chosen instead. Ascending uses the
earliest value of all time-spans, descending the latest, so a set sorts
by its first date upwards and by its last date downwards. An entity
without a time-span comes first ascending and last descending.
Dates are compared as xsd:dateTime, so negative and five-digit years
keep their order. The store has to cast a stored bound to
xsd:dateTime. Each filter and each sort adds the reference path and
the bound paths to the one SPARQL query of the view; there is no
additional query per row. The sort reads all its bounds in one grouped
sub-select. With 10,000 events in a local RDF4J native store, a page of
20 sorted by all bounds took about 0.25 s, with a filter about 0.3 to
0.45 s, plus up to 0.2 s for the pager's count.
Time model¶
A WissKI date says when something happened. A date field holds one EDTF value, and that value is read in one of three ways. All three place the event somewhere between 1 January 1900 and 31 December 1910. They differ in what is known about its begin and its end:
| Typed | Begin | End | So it surely went on | Example |
|---|---|---|---|---|
!(1900/1910) |
some time 1900–1910 | some time 1900–1910 | unknown: it may have been short | A letter written at some time in that decade |
1900/1910 |
in 1900 | in 1910 | 1901 through 1909 | A church begun in 1900 and finished in 1910 |
=(1900/1910) |
on 1 January 1900 | on 31 December 1910 | the whole span, no longer | An exhibition open from 1 January 1900 to 31 December 1910 |
1900 1901 … 1909 1910
!(1900/1910) ·········?·········?····· somewhere inside, length unknown
███ (a few months in 1905 is possible)
1900/1910 ?██████████████████████? begin somewhere in 1900,
└1900┘ └1910┘ end somewhere in 1910,
████████████ 1901–1909 for sure
=(1900/1910) ████████████████████████ exactly 1 Jan 1900 – 31 Dec 1910
Which one to type
- You only know a period in which it happened at some time:
!(A/B). - You know the year (or month, or day) it began and the one it ended:
A/B. - You know the exact first and last day:
=(A/B).
The difference only shows on an interval. A single date such as 1900
begins and ends in the same year, so nothing follows about when it
surely went on: it may have been one day. 1900 and !(1900) are the
same. Only =(1900) says more: the whole year.
1900/1910 is not =(1900/1910)
In EDTF, 1900 on the left of / is the precision of the begin, not
1 January. The begin can be 3 October 1900, and the end 5 March 1910.
Only =(…) fixes the begin to the first second and the end to the
last second.
"At least throughout, perhaps longer" has no sign.
!(…) and =(…) are WissKI extensions of EDTF. They enclose the whole
value or one member of a set, never one side of /. Inside the brackets
is EDTF. =(…) needs a known begin and end, so it rejects X,
significant digits, an open or unknown side, and vor / nach. !(…)
accepts all of them.
Sets and choices¶
{a,b} means every listed date applies, [a,b] exactly one of them. A
value that is one date holds one time-span; a set holds one time-span per
member, and a year between two members is not part of the value. Each
member can carry its own mark:
| Typed | Meaning |
|---|---|
{1900,1932,1934} |
It happened in each of these three years |
[1900,1904,=(1908)] |
It happened at some time in 1900, or at some time in 1904, or exactly during the whole of 1908 |
=({1900,1904}) |
It went on during the whole of 1900 and during the whole of 1904 |
Qualifiers on an interval¶
~, % and X widen the unit the begin or the end lies in, by the
widths. The surely covered stretch between the two units
shrinks by the same amount. With the default width of one year,
1900~/1910 began between 1899 and 1901 and surely went on from 1902
through 1909. When the begin's unit and the end's unit touch or overlap,
as in 1900~/1901, no instant is surely covered. An open (..),
unknown (empty), vor or nach side gives no such limit.
2004-06-~01/2004-06-~20 began between 31 May and 2 June 2004, ended
between 19 and 21 June, and surely went on from 3 through 18 June.
What is written on save¶
With a saved mapping, from Add the WissKI time model or by hand, each date stores the four bounds and its text. The time expression stores the typed text and, for a set or a choice, the type. Without a saved mapping, nothing is written. A save whose text did not change does not write the linked dates again.
The four bounds already tell 1900/1910, !(1900/1910) and =(1900/1910)
apart. Views compare the sibling integers and the date list, so all
three readings filter and sort the same way.
RDF¶
The mapped pathbuilder fields are ordinary WissKI triples. The turtle
below is what those fields mean. A qualifier stays in the EDTF text and
in the widened bounds. How sure a time-span is, is in addition written
on its four bound triples as RDF 1.2 statements: the time-span's
Uncertainty field is an
annotation field, so each
bound triple gets a reifier of class wisski:Uncertainty_Annotation
with P3 has note and the reading key (probable, approximate,
unspecified, estimated). A certain time-span has none.
On the time expression, the dates field shows a * after each uncertain
time-span. Its tooltip names the reading, for example
Uncertainty: Uncertain, in the interface language. The star follows
the mapped uncertainty field: a time-span without a reading, or a model
without that field, shows none.
Structure¶
The dated event links by P4_has_time-span to a time expression. The
time expression is the envelope around the value: it contains one
E52_Time-Span per date by P86i_contains, also when the value is a
single date. The typed text is an E41_Appellation of the time
expression.
E5 Event
└─ P4_has_time-span ─> Time_Expression P82a, P82b of the whole value
├─ P2_has_type ──> all_of | one_of only for {…} and […]
├─ P1_is_identified_by ─> E41 ── P190 "the typed EDTF"
└─ P86i_contains ─> E52_Time-Span one per date: P82a, P81a, P81b, P82b
Declare the time expression a subclass of E52
P4 and P86i are defined on E52_Time-Span. Declare
Time_Expression as rdfs:subClassOf crm:E52_Time-Span in the
ontology. A tool that knows only CIDOC CRM then reads the envelope as
"at some time between the first and the last date", which is true for
every reading.
The envelope carries P82a from the earliest begin and P82b from the
latest end, and never P81. An open first or last member leaves that
side out.
Vocabulary¶
Add the WissKI time model inserts two classes into the graph
http://wiss-ki.eu/ontology/default/time-additions when the store lacks
them. The bounds, the type and the appellation are CIDOC CRM 7.1.3; it
has no E49 or P78 any more, so the text is an E41_Appellation by
P1. Only a set or a choice has a type: all_of_these_dates (All of
these dates) or one_of_these_dates (One of these dates), the name of a
Time expression type that the pathbuilder shares between all time
expressions with that key. One date or one interval leaves the type
empty.
| Stored field | Property | Use |
|---|---|---|
| Time expression | http://wiss-ki.eu/ontology/default/Time_Expression |
The envelope, subclass of E52 |
| Time expression type | http://wiss-ki.eu/ontology/default/Time_Expression_Type |
Subclass of E55. Named all_of_these_dates or one_of_these_dates |
| Some time within, begin | crm:P82a_begin_of_the_begin |
Not begun before this second |
| Some time within, end | crm:P82b_end_of_the_end |
Not ended after this second |
| At least throughout, begin | crm:P81a_end_of_the_begin |
Begun by this second at the latest |
| At least throughout, end | crm:P81b_begin_of_the_end |
Ended at this second at the earliest |
| Contains | crm:P86i_contains |
Time expression to each date |
| Has type | crm:P2_has_type |
Time expression to its type |
| Identified by, Appellation, Symbolic content | crm:P1_is_identified_by, crm:E41_Appellation, crm:P190_has_symbolic_content |
The typed text |
crm: is http://www.cidoc-crm.org/cidoc-crm/. The examples below use
wtime: for the time expression. The class the model inserts is
http://wiss-ki.eu/ontology/default/Time_Expression.
Bounds per reading¶
P82a to P81a is the window the begin lies in, P81b to P82b the
window of the end. Between P81a and P81b the value surely holds. The
three readings fill the four bounds like this:
| Typed | P82a |
P81a |
P81b |
P82b |
|---|---|---|---|---|
1900 |
1900-01-01T00:00:00 | – | – | 1900-12-31T23:59:59 |
1900/1910 |
1900-01-01T00:00:00 | 1900-12-31T23:59:59 | 1910-01-01T00:00:00 | 1910-12-31T23:59:59 |
!(1900/1910) |
1900-01-01T00:00:00 | – | – | 1910-12-31T23:59:59 |
=(1900) |
1900-01-01T00:00:00 | 1900-01-01T00:00:00 | 1900-12-31T23:59:59 | 1900-12-31T23:59:59 |
=(1900/1910) |
1900-01-01T00:00:00 | 1900-01-01T00:00:00 | 1910-12-31T23:59:59 | 1910-12-31T23:59:59 |
1900~/1910 |
1899-01-01T00:00:00 | 1901-12-31T23:59:59 | 1910-01-01T00:00:00 | 1910-12-31T23:59:59 |
../1984 |
– | – | 1984-01-01T00:00:00 | 1984-12-31T23:59:59 |
vor 1900 |
– | – | – | 1899-12-31T23:59:59 |
"Exactly throughout" needs no property of its own: when both pairs name
the same seconds, the begin and the end are fixed. The values are
xsd:dateTime without a time zone, and they are not UTC or a local
zone. A day runs from 00:00:00 to 23:59:59. A time of day after T,
including Z, is dropped, so 1985-04-12T23:20:30Z is the day
12 April 1985. That is a rule of this module, not of EDTF.
@base <http://example.org/> .
@prefix crm: <http://www.cidoc-crm.org/cidoc-crm/> .
@prefix wtime: <http://wiss-ki.eu/ontology/time/> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
<event> crm:P4_has_time-span <te> .
<te> a wtime:Time_Expression ;
crm:P82a_begin_of_the_begin "1900-01-01T00:00:00"^^xsd:dateTime ;
crm:P82b_end_of_the_end "1910-12-31T23:59:59"^^xsd:dateTime ;
crm:P86i_contains <te/1> ;
crm:P1_is_identified_by <te/appellation> .
<te/appellation> a crm:E41_Appellation ;
crm:P190_has_symbolic_content "1900/1910" .
<te/1> a crm:E52_Time-Span ;
crm:P82a_begin_of_the_begin "1900-01-01T00:00:00"^^xsd:dateTime ;
crm:P81a_end_of_the_begin "1900-12-31T23:59:59"^^xsd:dateTime ;
crm:P81b_begin_of_the_end "1910-01-01T00:00:00"^^xsd:dateTime ;
crm:P82b_end_of_the_end "1910-12-31T23:59:59"^^xsd:dateTime .
<te/1> a crm:E52_Time-Span ;
crm:P82a_begin_of_the_begin "1900-01-01T00:00:00"^^xsd:dateTime ;
crm:P82b_end_of_the_end "1910-12-31T23:59:59"^^xsd:dateTime .
The time expression and the appellation are the same as for
1900/1910, with the text "!(1900/1910)".
<te/1> a crm:E52_Time-Span ;
crm:P82a_begin_of_the_begin "1900-01-01T00:00:00"^^xsd:dateTime ;
crm:P81a_end_of_the_begin "1900-01-01T00:00:00"^^xsd:dateTime ;
crm:P81b_begin_of_the_end "1910-12-31T23:59:59"^^xsd:dateTime ;
crm:P82b_end_of_the_end "1910-12-31T23:59:59"^^xsd:dateTime .
<event> crm:P4_has_time-span <te> .
<te> a wtime:Time_Expression ;
crm:P2_has_type wtime:one_of ;
crm:P82a_begin_of_the_begin "1900-01-01T00:00:00"^^xsd:dateTime ;
crm:P82b_end_of_the_end "1908-12-31T23:59:59"^^xsd:dateTime ;
crm:P86i_contains <te/1>, <te/2>, <te/3> ;
crm:P1_is_identified_by <te/appellation> .
<te/1> a crm:E52_Time-Span ;
crm:P82a_begin_of_the_begin "1900-01-01T00:00:00"^^xsd:dateTime ;
crm:P82b_end_of_the_end "1900-12-31T23:59:59"^^xsd:dateTime .
<te/2> a crm:E52_Time-Span ;
crm:P82a_begin_of_the_begin "1904-01-01T00:00:00"^^xsd:dateTime ;
crm:P82b_end_of_the_end "1904-12-31T23:59:59"^^xsd:dateTime .
<te/3> a crm:E52_Time-Span ;
crm:P82a_begin_of_the_begin "1908-01-01T00:00:00"^^xsd:dateTime ;
crm:P81a_end_of_the_begin "1908-01-01T00:00:00"^^xsd:dateTime ;
crm:P81b_begin_of_the_end "1908-12-31T23:59:59"^^xsd:dateTime ;
crm:P82b_end_of_the_end "1908-12-31T23:59:59"^^xsd:dateTime .
Each member of a set also gets its own appellation with its own text,
such as "=(1908)". A single date does not; its text is on the time
expression.
Readings that are not stored¶
?, ~, %, X and significant digits stay in the EDTF text. ~ and
% also widen the outer bounds. The module does not write the quoted
triples below. They record the reading the text already has:
| Typed | Seconds of the outer bounds | Quoted triple |
|---|---|---|
1900? |
1900-01-01T00:00:00 – 1900-12-31T23:59:59 |
reading Probable |
1900~ |
1899-01-01T00:00:00 – 1901-12-31T23:59:59 |
reading Approximate, and the stated year 1900 |
1900% |
1899-01-01T00:00:00 – 1901-12-31T23:59:59 |
Probable and Approximate, and the stated year 1900 |
190X |
1900-01-01T00:00:00 – 1909-12-31T23:59:59 |
reading Unspecified: exactly one of those years |
1950S2 |
1900-01-01T00:00:00 – 1999-12-31T23:59:59 |
reading Estimated, and the stated year 1950 |
<< <te/1> crm:P82a_begin_of_the_begin "1899-01-01T00:00:00"^^xsd:dateTime >>
wtime:reading wtime:Approximate ;
wtime:stated "1900-01-01T00:00:00"^^xsd:dateTime .
Probable is this module's name for EDTF uncertain (?); it does not
state a probability. Both bounds of one date carry the same reading.
Which component a qualifier covers stays in the model and the text:
2004-06~-11 approximates the year and the month, and the day stays
certain. Only outer bounds carry stated. With =(…) the inner bounds
keep the stated unit while the outer bounds widen, so the inner bounds
already are the stated value and carry the reading alone. A member's
reading stays on that member; the envelope has none.
Querying the bounds¶
PREFIX crm: <http://www.cidoc-crm.org/cidoc-crm/>
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
# Surely going on during 1905.
SELECT ?event WHERE {
?event crm:P4_has_time-span/crm:P86i_contains ?span .
?span crm:P81a_end_of_the_begin ?surelyFrom ;
crm:P81b_begin_of_the_end ?surelyUntil .
FILTER (?surelyFrom <= "1905-01-01T00:00:00"^^xsd:dateTime
&& ?surelyUntil >= "1905-12-31T23:59:59"^^xsd:dateTime)
}
- At some time in X:
P82aandP82bof a member overlap X. - Surely during X:
P81a≤ start of X andP81b≥ end of X. - Exactly X:
P81a = P82a= start of X andP81b = P82b= end of X.
Use the members, not the envelope, to decide whether a date matches: the
envelope of [1900,1904,=(1908)] also spans 1902, which is not a member.
Implementation¶
| Class | Role |
|---|---|
Edtf\EdtfSpanParser |
Parses the EDTF profile, !(…), =(…), vor / nach, into a TimeSpan with its model |
Time\TimePeriod |
The model: begin and end points, set members, oneOf, the TimeExtent, and for an interval the units its begin and end lie in (latestBegin(), earliestEnd()) |
Time\TimeExtent |
Edtf, Within (!), Exactly (=) |
Time\TimeBounds |
The four bounds of one date |
Time\TimeGraph |
Turtle for the unit test. It is not written to the store |
Time\TimeModel |
The groups and fields Add the WissKI time model creates, and the uncertainty list on the time-span |
Time\TimeModelInstaller |
Checks the store, inserts the missing classes, generates the paths and saves their bundles and fields as the mapping |
Time\TimeMapping |
The two bundles and the field of each role. Incomplete without both bundles, the dates field and the EDTF text field |
Time\TimeModelWriter |
Writes the type and one time-span per date with its four bounds when the text changed. Nothing without a complete mapping |
Time\SecondRange |
A unit as its first and last second |
The typed EDTF string is the source. On save, the sibling integers and the pathbuilder seconds are rewritten from it. The text is not stored a second time. Uncertainty is a separate annotation on the four bounds.
Verbal dates¶
The verbal (MIDAS) field is unchanged. It does not parse EDTF.