1 /* 2 * "GEDKeeper", the personal genealogical database editor. 3 * Copyright (C) 2009-2021 by Sergey V. Zhdanovskih. 4 * 5 * This file is part of "GEDKeeper". 6 * 7 * This program is free software: you can redistribute it and/or modify 8 * it under the terms of the GNU General Public License as published by 9 * the Free Software Foundation, either version 3 of the License, or 10 * (at your option) any later version. 11 * 12 * This program is distributed in the hope that it will be useful, 13 * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 * GNU General Public License for more details. 16 * 17 * You should have received a copy of the GNU General Public License 18 * along with this program. If not, see <http://www.gnu.org/licenses/>. 19 */ 20 21 using System; 22 using System.Collections.Generic; 23 using BSLib; 24 using BSLib.Calendar; 25 using GDModel.Providers.GEDCOM; 26 using GKCore; 27 using GKCore.Types; 28 29 namespace GDModel 30 { 31 /// <summary> 32 /// Class to hold simple standard GEDCOM dates. 33 /// Note: Year cannot be used externally with negative values even for "BC", 34 /// because these dates there is a special property. 35 /// Dates of type "BC" should have a positive Year + the property YearBC. 36 /// </summary> 37 public class GDMDate : GDMCustomDate 38 { 39 public const int UNKNOWN_YEAR = -1; 40 41 private GDMApproximated fApproximated; 42 private GDMCalendar fCalendar; 43 private byte fDay; 44 private byte fMonth; 45 private short fYear; 46 private bool fYearBC; 47 private string fYearModifier; 48 private UDN fUDN; 49 50 51 public GDMApproximated Approximated 52 { 53 get { return fApproximated; } 54 set { fApproximated = value; } 55 } 56 57 public GDMCalendar DateCalendar 58 { 59 get { return fCalendar; } 60 } 61 62 public byte Day 63 { 64 get { return fDay; } 65 set { 66 fDay = value; 67 DateChanged(); 68 } 69 } 70 71 public byte Month 72 { 73 get { return fMonth; } 74 set { 75 fMonth = value; 76 DateChanged(); 77 } 78 } 79 80 public short Year 81 { 82 get { return fYear; } 83 set { 84 fYear = value; 85 DateChanged(); 86 } 87 } 88 89 public bool YearBC 90 { 91 get { return fYearBC; } 92 set { 93 fYearBC = value; 94 DateChanged(); 95 } 96 } 97 98 public string YearModifier 99 { 100 get { return fYearModifier; } 101 set { fYearModifier = value; } 102 } 103 104 GDMDate()105 public GDMDate() 106 { 107 fApproximated = GDMApproximated.daExact; 108 fCalendar = GDMCalendar.dcGregorian; 109 fYear = UNKNOWN_YEAR; 110 fYearBC = false; 111 fYearModifier = string.Empty; 112 fMonth = 0; 113 fDay = 0; 114 } 115 GDMDate(int tagId, string tagValue)116 public GDMDate(int tagId, string tagValue) : this() 117 { 118 SetNameValue(tagId, tagValue); 119 } 120 Clear()121 public override void Clear() 122 { 123 base.Clear(); 124 125 fApproximated = GDMApproximated.daExact; 126 fCalendar = GDMCalendar.dcGregorian; 127 fYear = UNKNOWN_YEAR; 128 fYearBC = false; 129 fYearModifier = string.Empty; 130 fMonth = 0; 131 fDay = 0; 132 133 DateChanged(); 134 } 135 136 /// <summary> 137 /// This function is intended only for checking the completeness of parts of the date 138 /// (year, month and day are defined, are not unknown). 139 /// </summary> IsValidDate()140 public bool IsValidDate() 141 { 142 return (fYear > 0 && fMonth > 0 && fDay > 0); 143 } 144 IsEmpty()145 public override bool IsEmpty() 146 { 147 return base.IsEmpty() && fYear <= 0 && fMonth <= 0 && fDay <= 0; 148 } 149 Assign(GDMTag source)150 public override void Assign(GDMTag source) 151 { 152 GDMDate srcDate = source as GDMDate; 153 if (srcDate == null) 154 throw new ArgumentException(@"Argument is null or wrong type", "source"); 155 156 fApproximated = srcDate.fApproximated; 157 fCalendar = srcDate.fCalendar; 158 fYear = srcDate.fYear; 159 fYearBC = srcDate.fYearBC; 160 fYearModifier = srcDate.fYearModifier; 161 fMonth = srcDate.fMonth; 162 fDay = srcDate.fDay; 163 164 DateChanged(); 165 } 166 GetDateTime()167 public override DateTime GetDateTime() 168 { 169 DateTime result; 170 171 // FIXME: check if the calendar is gregorian 172 if (fYear >= 0 && fMonth >= 1 && fMonth <= 12 && fDay >= 1 && fDay <= 31) { 173 result = new DateTime(fYear, fMonth, fDay); 174 return result; 175 } 176 177 result = new DateTime(0); 178 return result; 179 } 180 SetDateTime(DateTime value)181 public override void SetDateTime(DateTime value) 182 { 183 SetGregorian(value.Day, value.Month, value.Year); 184 } 185 ParseString(string strValue)186 public override string ParseString(string strValue) 187 { 188 string result; 189 if (string.IsNullOrEmpty(strValue)) { 190 Clear(); 191 result = string.Empty; 192 } else { 193 result = GEDCOMUtils.ParseDate(this, strValue); 194 } 195 return result; 196 } 197 198 /// <summary> 199 /// Internal helper method for parser 200 /// </summary> SetRawData(GDMApproximated approximated, GDMCalendar calendar, short year, bool yearBC, string yearModifier, byte month, byte day)201 internal void SetRawData(GDMApproximated approximated, GDMCalendar calendar, 202 short year, bool yearBC, string yearModifier, byte month, byte day) 203 { 204 fApproximated = approximated; 205 fCalendar = calendar; 206 fYear = year; 207 fYearBC = yearBC; 208 fYearModifier = yearModifier; 209 fMonth = month; 210 fDay = day; 211 212 DateChanged(); 213 } 214 215 #region Private methods of parsing of the input format 216 GetMonthNames(GDMCalendar calendar)217 public static string[] GetMonthNames(GDMCalendar calendar) 218 { 219 string[] monthes; 220 switch (calendar) 221 { 222 case GDMCalendar.dcGregorian: 223 case GDMCalendar.dcJulian: 224 case GDMCalendar.dcRoman: 225 monthes = GEDCOMConsts.GEDCOMMonthArray; 226 break; 227 228 case GDMCalendar.dcHebrew: 229 monthes = GEDCOMConsts.GEDCOMMonthHebrewArray; 230 break; 231 232 case GDMCalendar.dcFrench: 233 monthes = GEDCOMConsts.GEDCOMMonthFrenchArray; 234 break; 235 236 case GDMCalendar.dcIslamic: 237 monthes = GEDCOMConsts.GEDCOMMonthIslamicArray; 238 break; 239 240 case GDMCalendar.dcUnknown: 241 default: 242 monthes = GEDCOMConsts.GEDCOMMonthArray; 243 break; 244 } 245 return monthes; 246 } 247 CheckGEDCOMMonth(GDMCalendar calendar, string str)248 private static string CheckGEDCOMMonth(GDMCalendar calendar, string str) 249 { 250 // An empty string is a valid identifier for an unknown month 251 if (string.IsNullOrEmpty(str)) return string.Empty; 252 253 string[] monthes = GDMDate.GetMonthNames(calendar); 254 str = str.ToUpperInvariant(); 255 for (int m = 0; m < monthes.Length; m++) { 256 if (monthes[m] == str) { 257 return str; 258 } 259 } 260 261 throw new GDMDateException("The string {0} is not a valid {1} month identifier", str, calendar.ToString()); 262 } 263 IntToGEDCOMMonth(int m)264 private static string IntToGEDCOMMonth(int m) 265 { 266 return (m == 0) ? string.Empty : GEDCOMConsts.GEDCOMMonthArray[m - 1]; 267 } 268 IntToGEDCOMMonthFrench(int m)269 private static string IntToGEDCOMMonthFrench(int m) 270 { 271 return (m == 0) ? string.Empty : GEDCOMConsts.GEDCOMMonthFrenchArray[m - 1]; 272 } 273 IntToGEDCOMMonthHebrew(int m)274 private static string IntToGEDCOMMonthHebrew(int m) 275 { 276 return (m == 0) ? string.Empty : GEDCOMConsts.GEDCOMMonthHebrewArray[m - 1]; 277 } 278 279 #endregion 280 GetStringValue()281 protected override string GetStringValue() 282 { 283 var parts = new List<string>(5); 284 if (fApproximated != GDMApproximated.daExact) { 285 parts.Add(GEDCOMConsts.GEDCOMDateApproximatedArray[(int)fApproximated]); 286 } 287 288 if (fCalendar != GDMCalendar.dcGregorian) { 289 parts.Add(GEDCOMConsts.GEDCOMDateEscapeArray[(int)fCalendar]); 290 } 291 292 if (fDay > 0) { 293 parts.Add(fDay.ToString("D2")); 294 } 295 296 if (fMonth > 0) { 297 string[] months = GetMonthNames(fCalendar); 298 parts.Add(months[fMonth - 1]); 299 } 300 301 if (fYear != UNKNOWN_YEAR) { 302 string yearStr = fYear.ToString("D3"); 303 if (!string.IsNullOrEmpty(fYearModifier)) { 304 yearStr = yearStr + "/" + fYearModifier; 305 } 306 307 if (fYearBC) { 308 yearStr += GEDCOMConsts.YearBC; 309 } 310 311 parts.Add(yearStr); 312 } 313 314 return string.Join(" ", parts); 315 } 316 GetMonthNumber(GDMCalendar calendar, string strMonth)317 private static byte GetMonthNumber(GDMCalendar calendar, string strMonth) 318 { 319 string su = GEDCOMUtils.InvariantTextInfo.ToUpper(strMonth); 320 321 int month; 322 switch (calendar) { 323 case GDMCalendar.dcHebrew: 324 month = Algorithms.IndexOf(GEDCOMConsts.GEDCOMMonthHebrewArray, su); 325 break; 326 327 case GDMCalendar.dcFrench: 328 month = Algorithms.IndexOf(GEDCOMConsts.GEDCOMMonthFrenchArray, su); 329 break; 330 331 default: 332 month = Algorithms.IndexOf(GEDCOMConsts.GEDCOMMonthArray, su); 333 break; 334 } 335 336 return (byte)(month + 1); 337 } 338 SetDate(GDMCalendar calendar, int day, int month, int year, bool yearBC = false)339 public void SetDate(GDMCalendar calendar, int day, int month, int year, bool yearBC = false) 340 { 341 switch (calendar) { 342 case GDMCalendar.dcGregorian: 343 SetGregorian(day, month, year); 344 break; 345 346 case GDMCalendar.dcJulian: 347 SetJulian(day, month, year); 348 break; 349 350 case GDMCalendar.dcHebrew: 351 SetHebrew(day, month, year); 352 break; 353 354 case GDMCalendar.dcFrench: 355 SetFrench(day, month, year); 356 break; 357 358 case GDMCalendar.dcRoman: 359 SetRoman(day, month, year, yearBC); 360 break; 361 362 case GDMCalendar.dcIslamic: 363 SetIslamic(day, month, year); 364 break; 365 366 case GDMCalendar.dcUnknown: 367 SetUnknown(day, month, year, yearBC); 368 break; 369 } 370 } 371 SetDateInternal(GDMCalendar calendar, int day, string month, int year, string yearModifier, bool yearBC)372 private void SetDateInternal(GDMCalendar calendar, int day, string month, int year, string yearModifier, bool yearBC) 373 { 374 SetDateInternal(calendar, day, GetMonthNumber(calendar, month), year, yearModifier, yearBC); 375 } 376 SetDateInternal(GDMCalendar calendar, int day, int month, int year, string yearModifier, bool yearBC)377 private void SetDateInternal(GDMCalendar calendar, int day, int month, int year, string yearModifier, bool yearBC) 378 { 379 fCalendar = calendar; 380 fDay = (byte)day; 381 fMonth = (byte)month; 382 fYear = (short)year; 383 fYearModifier = yearModifier; 384 fYearBC = yearBC; 385 386 DateChanged(); 387 } 388 SetGregorian(int day, int month, int year)389 public void SetGregorian(int day, int month, int year) 390 { 391 SetDateInternal(GDMCalendar.dcGregorian, day, month, year, "", false); 392 } 393 SetGregorian(int day, string month, int year, string yearModifier, bool yearBC)394 public void SetGregorian(int day, string month, int year, string yearModifier, bool yearBC) 395 { 396 SetDateInternal(GDMCalendar.dcGregorian, day, CheckGEDCOMMonth(GDMCalendar.dcGregorian, month), year, yearModifier, yearBC); 397 } 398 SetJulian(int day, int month, int year)399 public void SetJulian(int day, int month, int year) 400 { 401 SetDateInternal(GDMCalendar.dcJulian, day, month, year, "", false); 402 } 403 SetJulian(int day, string month, int year, bool yearBC)404 public void SetJulian(int day, string month, int year, bool yearBC) 405 { 406 SetDateInternal(GDMCalendar.dcJulian, day, CheckGEDCOMMonth(GDMCalendar.dcJulian, month), year, "", yearBC); 407 } 408 SetHebrew(int day, int month, int year)409 public void SetHebrew(int day, int month, int year) 410 { 411 SetDateInternal(GDMCalendar.dcHebrew, day, month, year, "", false); 412 } 413 SetHebrew(int day, string month, int year, bool yearBC)414 public void SetHebrew(int day, string month, int year, bool yearBC) 415 { 416 SetDateInternal(GDMCalendar.dcHebrew, day, CheckGEDCOMMonth(GDMCalendar.dcHebrew, month), year, "", yearBC); 417 } 418 SetFrench(int day, int month, int year)419 public void SetFrench(int day, int month, int year) 420 { 421 SetDateInternal(GDMCalendar.dcFrench, day, month, year, "", false); 422 } 423 SetFrench(int day, string month, int year, bool yearBC)424 public void SetFrench(int day, string month, int year, bool yearBC) 425 { 426 SetDateInternal(GDMCalendar.dcFrench, day, CheckGEDCOMMonth(GDMCalendar.dcFrench, month), year, "", yearBC); 427 } 428 SetRoman(int day, int month, int year, bool yearBC)429 public void SetRoman(int day, int month, int year, bool yearBC) 430 { 431 SetDateInternal(GDMCalendar.dcRoman, day, month, year, "", yearBC); 432 } 433 SetRoman(int day, string month, int year, bool yearBC)434 public void SetRoman(int day, string month, int year, bool yearBC) 435 { 436 SetDateInternal(GDMCalendar.dcRoman, day, CheckGEDCOMMonth(GDMCalendar.dcRoman, month), year, "", yearBC); 437 } 438 SetUnknown(int day, int month, int year, bool yearBC)439 public void SetUnknown(int day, int month, int year, bool yearBC) 440 { 441 SetDateInternal(GDMCalendar.dcUnknown, day, month, year, "", yearBC); 442 } 443 SetUnknown(int day, string month, int year, bool yearBC)444 public void SetUnknown(int day, string month, int year, bool yearBC) 445 { 446 SetDateInternal(GDMCalendar.dcUnknown, day, CheckGEDCOMMonth(GDMCalendar.dcUnknown, month), year, "", yearBC); 447 } 448 SetIslamic(int day, int month, int year)449 public void SetIslamic(int day, int month, int year) 450 { 451 SetDateInternal(GDMCalendar.dcIslamic, day, month, year, "", false); 452 } 453 SetIslamic(int day, string month, int year)454 public void SetIslamic(int day, string month, int year) 455 { 456 SetDateInternal(GDMCalendar.dcIslamic, day, CheckGEDCOMMonth(GDMCalendar.dcIslamic, month), year, "", false); 457 } 458 459 #region UDN processing 460 461 // GEDCOMCalendar: dcGregorian, dcJulian, dcHebrew, dcFrench, dcRoman, dcIslamic, dcUnknown. 462 private static readonly UDNCalendarType[] UDNCalendars = new UDNCalendarType[] { 463 /* dcGregorian */ UDNCalendarType.ctGregorian, 464 /* dcJulian */ UDNCalendarType.ctJulian, 465 /* dcHebrew */ UDNCalendarType.ctHebrew, 466 /* dcFrench */ UDNCalendarType.ctGregorian, // not supported yet 467 /* dcRoman */ UDNCalendarType.ctGregorian, // not supported yet 468 /* dcIslamic */ UDNCalendarType.ctIslamic, 469 /* dcUnknown */ UDNCalendarType.ctGregorian 470 }; 471 DateChanged()472 protected override void DateChanged() 473 { 474 int year = fYear; 475 if (year == UNKNOWN_YEAR) { 476 year = UDN.UnknownYear; 477 } else { 478 if (fYearBC) year = -year; 479 } 480 481 UDNCalendarType udnCalendar = UDNCalendars[(int)fCalendar]; 482 fUDN = new UDN(udnCalendar, year, fMonth, fDay); 483 } 484 GetUDN()485 public override UDN GetUDN() 486 { 487 return (fApproximated == GDMApproximated.daExact) ? fUDN : UDN.CreateApproximate(fUDN); 488 } 489 490 #endregion 491 492 #region Utilities 493 CreateByFormattedStr(string strDate, bool aException)494 public static GDMDate CreateByFormattedStr(string strDate, bool aException) 495 { 496 return CreateByFormattedStr(strDate, GDMCalendar.dcGregorian, aException); 497 } 498 499 /// <summary> 500 /// This function transforms the string into a date. All components of 501 /// the date's string must be given by numbers in order of day / month / year. 502 /// This function is intended only for use with the date entry controls (fixed format of date's string). 503 /// </summary> CreateByFormattedStr(string dateStr, GDMCalendar calendar, bool aException)504 public static GDMDate CreateByFormattedStr(string dateStr, GDMCalendar calendar, bool aException) 505 { 506 if (string.IsNullOrEmpty(dateStr)) return null; 507 508 if (dateStr.IndexOf("-") >= 0) dateStr = dateStr.Replace("-", "."); 509 if (dateStr.IndexOf("/") >= 0) dateStr = dateStr.Replace("/", "."); 510 if (dateStr.IndexOf("_") >= 0) dateStr = dateStr.Replace("_", " "); 511 512 string[] dtParts = dateStr.Split('.'); 513 if (dtParts.Length < 3) { 514 if (aException) { 515 throw new GDMDateException("Invalid date format '{0}'", dateStr); 516 } 517 518 return null; 519 } 520 521 string pd = dtParts[0].Trim(); 522 string pm = dtParts[1].Trim(); 523 string py = dtParts[2].Trim(); 524 525 int day = (pd == "") ? 0 : ConvertHelper.ParseInt(pd, 0); 526 int month = (pm == "") ? 0 : ConvertHelper.ParseInt(pm, 0); 527 int year = (py == "") ? UNKNOWN_YEAR : ConvertHelper.ParseInt(py, UNKNOWN_YEAR); 528 529 var date = new GDMDate(); 530 date.SetDate(calendar, day, month, year); 531 return date; 532 } 533 GetUDNByFormattedStr(string dateStr, GDMCalendar calendar, bool aException = false)534 public static UDN GetUDNByFormattedStr(string dateStr, GDMCalendar calendar, bool aException = false) 535 { 536 GDMDate dtx = GDMDate.CreateByFormattedStr(dateStr, calendar, aException); 537 return (dtx != null) ? dtx.GetUDN() : UDN.CreateEmpty(); 538 } 539 GetDisplayString(DateFormat format, bool includeBC = false, bool showCalendar = false)540 public string GetDisplayString(DateFormat format, bool includeBC = false, bool showCalendar = false) 541 { 542 string result = ""; 543 544 int year = fYear; 545 int month = fMonth; 546 int day = fDay; 547 bool ybc = fYearBC; 548 549 if (year > 0 || month > 0 || day > 0) { 550 switch (format) { 551 case DateFormat.dfDD_MM_YYYY: 552 result += day > 0 ? ConvertHelper.AdjustNumber(day, 2) + "." : "__."; 553 result += month > 0 ? ConvertHelper.AdjustNumber(month, 2) + "." : "__."; 554 result += year > 0 ? year.ToString().PadLeft(4, '_') : "____"; 555 break; 556 557 case DateFormat.dfYYYY_MM_DD: 558 result += year > 0 ? year.ToString().PadLeft(4, '_') + "." : "____."; 559 result += month > 0 ? ConvertHelper.AdjustNumber(month, 2) + "." : "__."; 560 result += day > 0 ? ConvertHelper.AdjustNumber(day, 2) : "__"; 561 break; 562 563 case DateFormat.dfYYYY: 564 if (year > 0) { 565 result = year.ToString().PadLeft(4, '_'); 566 } 567 break; 568 } 569 } 570 571 if (includeBC && ybc) { 572 switch (format) { 573 case DateFormat.dfDD_MM_YYYY: 574 result = result + " BC"; 575 break; 576 case DateFormat.dfYYYY_MM_DD: 577 result = "BC " + result; 578 break; 579 case DateFormat.dfYYYY: 580 result = "BC " + result; 581 break; 582 } 583 } 584 585 if (showCalendar) { 586 result = result + GKData.DateCalendars[(int)fCalendar].Sign; 587 } 588 589 return result; 590 } 591 GetDisplayStringExt(DateFormat format, bool sign, bool showCalendar)592 public override string GetDisplayStringExt(DateFormat format, bool sign, bool showCalendar) 593 { 594 string result = GetDisplayString(format, true, showCalendar); 595 if (sign && fApproximated != GDMApproximated.daExact) { 596 result = "~ " + result; 597 } 598 599 return result; 600 } 601 602 #endregion 603 } 604 } 605