Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/**
* Copyright Intellectual Reserve, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.gedcomx.conclusion;

import org.gedcomx.types.CalendarType;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.webcohesion.enunciate.metadata.qname.XmlQNameEnumRef;

/**
* Represents a date expressed in an alternate calendar system.
* <p>
* An {@code AlternateCalendarDate} is included within a {@link Date} object's alternateCalendars list to express the
* same date in a calendar other than the proleptic Gregorian calendar. The main {@code Date} should always be
* in the proleptic Gregorian calendar, and any alternate representations (e.g., Hebrew, Rumi, Julian)
* should be provided as {@code AlternateCalendarDate} instances.
* </p>
*/
@JsonInclude(JsonInclude.Include.NON_NULL)
public class AlternateCalendarDate extends Date {
public AlternateCalendarDate() {
// Default constructor
}

/**
* The calendar system used to interpret this date (e.g., Hebrew, Rumi, Julian).
* This value indicates which calendar should be used to interpret the date values in this object.
*/
private CalendarType calendar;

public AlternateCalendarDate(CalendarType calendar) {
this.calendar = calendar;
}

public AlternateCalendarDate(AlternateCalendarDate copy) {
super(copy);
this.calendar = copy.calendar;
}

/**
* Gets the calendar system used to interpret this date.
*
* @return The calendar system for this alternate date.
*/
@XmlQNameEnumRef(CalendarType.class)
public CalendarType getCalendar() {
return calendar;
}

/**
* Sets the calendar system used to interpret this date.
*
* @param calendar The calendar system for this alternate date.
*/
public void setCalendar(CalendarType calendar) {
this.calendar = calendar;
}

/**
* Build up the AlternateCalendarDate with the calendar system.
*
* @param calendar The calendar system for this alternate date.
* @return this.
*/
public AlternateCalendarDate calendar(CalendarType calendar) {
setCalendar(calendar);
return this;
}

@Override
public String toString() {
return "Date{" + "original='" + getOriginal() + '\'' + ", formal=" + getFormal() + ", calendar=" + calendar + '}';
}
}
66 changes: 60 additions & 6 deletions gedcomx-model/src/main/java/org/gedcomx/conclusion/Date.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,16 @@
import jakarta.xml.bind.annotation.XmlTransient;
import jakarta.xml.bind.annotation.XmlType;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
* A concluded genealogical date.
*/
@ClientName ("DateInfo")
@XmlType ( name = "Date", propOrder = { "original", "formal", "normalizedExtensions", "fields"})
@XmlType ( name = "Date", propOrder = { "original", "formal", "alternateCalendars", "normalizedExtensions", "fields"})
@JsonInclude ( JsonInclude.Include.NON_NULL )
@Schema(description = "A concluded genealogical date.")
public class Date extends ExtensibleData implements HasFields {
Expand All @@ -63,6 +63,7 @@ public class Date extends ExtensibleData implements HasFields {
@Schema(description = "The list of normalized values for the date, provided for display purposes by the application. Normalized values are not specified by " +
"GEDCOM X core, but as extension elements by GEDCOM X RS.")
private List<TextValue> normalized;
private List<AlternateCalendarDate> alternateCalendars;

private List<Field> fields;

Expand All @@ -74,8 +75,9 @@ public Date(Date copy) {
this.original = copy.original;
this.formal = copy.formal;
this.confidence = copy.confidence;
this.normalized = copy.normalized == null ? null : new ArrayList<>(copy.normalized.stream().map(TextValue::new).toList());
this.fields = copy.fields == null ? null : new ArrayList<>(copy.fields.stream().map(Field::new).toList());
this.normalized = copy.normalized == null ? null : copy.normalized.stream().map(TextValue::new).collect(Collectors.toList());
this.alternateCalendars = copy.alternateCalendars == null ? null : copy.alternateCalendars.stream().map(AlternateCalendarDate::new).toList();
this.fields = copy.fields == null ? null : copy.fields.stream().map(Field::new).toList();
}

@Override
Expand Down Expand Up @@ -275,12 +277,64 @@ public void setNormalizedExtensions(List<TextValue> normalized) {
public void addNormalizedExtension(TextValue normalizedExtension) {
if (normalizedExtension != null) {
if (normalized == null) {
normalized = new LinkedList<TextValue>();
normalized = new LinkedList<>();
}
normalized.add(normalizedExtension);
}
}

/**
* Get a list of the same date expressed in alternate calendars.
* In records, this is useful for recording how the date appeared and what calendar is believed to be used for it.
* This helps with interpreting the date correctly, especially when converting to a standard Gregorian calendar.
* In trees, it can also be useful to include an alternate calendar representation of a date if the records the
* person appears in tend to use a calendar other than Gregorian. For example, a person born in 1910 in the
* Ottoman Empire may have their birth date recorded in the Rumi calendar in most records. Including this
* date as an alternate calendar date, in addition to the Gregorian date, can help researchers understand and
* verify the date more easily.
*
* @return List of dates in alternate calendars.
*/
@XmlElement( name = "alternateCalendar" )
public List<AlternateCalendarDate> getAlternateCalendars() {
return alternateCalendars;
}

/**
* Set the list of alternate calendar dates.
*
* @param alternateCalendars - List of alternateCalendars
*/
@JsonProperty( "alternateCalendars" )
public void setAlternateCalendars(List<AlternateCalendarDate> alternateCalendars) {
this.alternateCalendars = alternateCalendars;
}

/**
* Build up this date with the same date in an alternate calendar.
*
* @param alternateCalendar The same date expressed in an alternate calendar
* @return this.
*/
public Date alternateCalendar(AlternateCalendarDate alternateCalendar) {
addAlternateCalendar(alternateCalendar);
return this;
}

/**
* Add the same date expressed in an alternate calendar.
*
* @param alternateCalendar The alternate calendar date to be added.
*/
public void addAlternateCalendar(AlternateCalendarDate alternateCalendar) {
if (alternateCalendar != null) {
if (alternateCalendars == null) {
alternateCalendars = new LinkedList<>();
}
alternateCalendars.add(alternateCalendar);
}
}

/**
* Get the fields being used as evidence.
*
Expand Down Expand Up @@ -322,7 +376,7 @@ public Date field(Field field) {
public void addField(Field field) {
if (field != null) {
if (fields == null) {
fields = new LinkedList<Field>();
fields = new LinkedList<>();
}
fields.add(field);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
@XmlTransient
public class GedcomxModelVisitorBase implements GedcomxModelVisitor {

protected final LinkedList<Object> contextStack = new LinkedList<Object>();
protected final LinkedList<Object> contextStack = new LinkedList<>();

@Override
public void visitGedcomx(Gedcomx gx) {
Expand Down Expand Up @@ -463,6 +463,15 @@ public void visitDate(Date date) {
}

protected void visitComponents(Date date) {
List<AlternateCalendarDate> alternateCalendars = date.getAlternateCalendars();
if (alternateCalendars != null) {
for (AlternateCalendarDate alternateCalendar : alternateCalendars) {
if (alternateCalendar != null) {
alternateCalendar.accept(this);
}
}
}

List<Field> fields = date.getFields();
if (fields != null) {
for (Field field : fields) {
Expand Down
166 changes: 166 additions & 0 deletions gedcomx-model/src/main/java/org/gedcomx/types/CalendarType.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
/**
* Copyright Intellectual Reserve, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.gedcomx.types;

import com.webcohesion.enunciate.metadata.qname.XmlQNameEnum;

/**
* Enumeration of calendar systems used for AlternateDate representations.
* <p>
* Notes:
* <ul>
* <li>Descriptions here are concise overviews; actual conversion logic should be implemented
* in a dedicated date standardization service.</li>
* <li>Examples include original scripts with romanization and translation for clarity.</li>
* </ul>
*/
@XmlQNameEnum(
base = XmlQNameEnum.BaseType.URI
)
public enum CalendarType {

/**
* GREGORIAN — Proleptic Gregorian calendar.
* <p>
* The standard civil calendar used in most of the world today, and the default calendar
* for GEDCOM X dates. This calendar is used for all “formal” GedcomX dates, and is
* assumed whenever a date does not explicitly specify a calendar type.
* </p>
* <p>
* The Gregorian calendar was introduced in 1582 to replace the Julian calendar.
* Different countries adopted it at different times, but in computational
* contexts it is treated as <i>proleptic</i>, meaning that its rules are
* extended backward to dates earlier than its historical adoption. Leap years
* occur in years divisible by 4, except century years not divisible by 400
* (e.g., 1900 is a common year; 2000 is a leap year).
* </p>
* <p><b>Example:</b> October 15, 2025 (Gregorian)</p>
*/
Gregorian,

/**
* JULIAN — Julian calendar.
* <p>
* A solar calendar introduced by Julius Caesar in 45 BCE, with a simple leap-year
* rule: every fourth year has 366 days. It was used throughout Europe until it was
* gradually replaced by the Gregorian calendar at different times in different
* regions.
* </p>
* <p>
* In genealogical records, dates in the early modern period may appear in “Old Style”
* (Julian) form, sometimes written as dual dates (e.g., 10 February 1740/41) because
* the legal new year began on March 25. Any such interpretation or conversion is
* handled by a date standardization service.
* </p>
* <p><b>Example:</b> March 1, 1600 (Julian)</p>
*/
Julian,

/**
* FRENCH_REPUBLIC — French Republican calendar.
* <p>
* A solar calendar introduced in 1792 with 12 months of 30 days named after seasonal features
* (e.g., Vendémiaire, Brumaire, Floréal), followed by 5 or 6 complementary days (sans‑culottides).
* Weeks are décades of 10 days.
* </p>
* <p><b>Example (French):</b> 10 Floréal an II
* (dix Floréal an deux; 10 Floréal, Year 2)</p>
*/
FrenchRepublic,

/**
* CHINESE IMPERIAL — Traditional Chinese imperial (regnal) dating based on the lunisolar calendar.
* <p>
* Dates are expressed with reign era names 年号 (niánhào; reign title), counting years within
* an emperor’s reign. Months and days follow the traditional lunisolar calendar, which can include
* leap months 闰月 (rùnyuè; leap month).
* </p>
* <p><b>Example (Chinese):</b> 康熙四十五年 八月 初三
* (Kāngxī sìshíwǔ nián bāyuè chū sān; Kangxi year 45, 8th month, day 3)</p>
*/
ChineseImperial,

/**
* JAPANESE IMPERIAL — Japanese era-based regnal dating alongside Gregorian.
* <p>
* Uses era names 元号 (gengō; era name) to count years within an emperor’s reign; the current era is
* 令和 (Reiwa; Beautiful Harmony) since 2019. Dates are written as
* “[era] [year]年 [month]月 [day]日”.
* </p>
* <p><b>Example (Japanese):</b> 令和7年 3月 1日
* (Reiwa 7‑nen 3‑gatsu 1‑nichi; March 1, Reiwa 7)</p>
*/
JapaneseImperial,

/**
* KOREAN IMPERIAL — Korean imperial (regnal) calendar.
* <p>
* Uses era names from the Daehan Empire for regnal dating, expressed as
* “[era name] [year] [month] [day]”. The empire name: 대한제국 (Daehan Jeguk; Great Han Empire).
* Format mirrors East Asian regnal styles.
* </p>
* <p><b>Example (Hangul):</b> 광무 5년 3월 1일 (Gwangmu 5‑nyeon 3‑wol 1‑il; Gwangmu year 5, March 1)</p>
*/
KoreanImperial,

/**
* THAI SOLAR — Thai Buddhist solar calendar.
* <p>
* Solar calendar aligned with Gregorian months and days; the year is counted in
* พุทธศักราช (Phutthasakkarat; Buddhist Era), where BE = CE + 543.
* </p>
* <p><b>Example (Thai):</b> วันที่ 1 มกราคม พ.ศ. 2568
* (Wan thī 1 Mokkarakhom Phō.Sō. 2568; 1 January, BE 2568)</p>
*/
ThaiSolar,

/**
* Hebrew — Jewish lunisolar calendar.
* <p>
* The Hebrew calendar הלוח העברי (ha-luach ha-ivri; the Hebrew calendar) is lunisolar:
* 12 months with a leap month added 7 times in a 19-year cycle (Metonic) to align
* lunar months with the solar year. Months begin at the new moon; religious observances
* follow the calendar’s rules for postponements and year lengths.
* </p>
* <p><b>Example (Hebrew):</b> י״ד תשרי תשפ״ה
* (14 Tishri 5785; 14th of Tishri, year 5785)</p>
*/
Hebrew,

/**
* ISLAMIC — Islamic (Hijri) lunar calendar.
* <p>
* A purely lunar calendar: 12 months of 29 or 30 days, totaling 354 or 355 days per year.
* Month starts traditionally depending on local crescent sighting; civil variants may use
* astronomical calculation. Common name: اَلْتَقْوِيمُ ٱلْهِجْرِي (al‑taqwīm al‑hijrī; the Hijri calendar).
* </p>
* <p><b>Example (Arabic):</b> ١ رمضان ١٤٤٧ هـ (1 Ramaḍān 1447 AH; 1st of Ramadan, year 1447 of the Hijra)</p>
*/
Islamic,

/**
* RUMI — Rumi solar calendar (Turkish Ottoman Empire).
* <p>
* A solar calendar used in late Ottoman administration, originally based on the Julian calendar.
* It employed fiscal year numbering and, in later reforms, incorporated Gregorian‑style adjustments
* while keeping Rumi year numbers. The name رومي (Rūmī; Roman/Byzantine) reflects its provenance.
* </p>
* <p><b>Example:</b> Rûmî 1325, Kanun‑i Evvel 1
* (Rûmî 1325; December 1 in the Rumi year 1325)</p>
*/
Rumi;
}

Loading