home *** CD-ROM | disk | FTP | other *** search
/ linuxmafia.com 2016 / linuxmafia.com.tar / linuxmafia.com / pub / palmos / happydays-src-1.37.tar.gz / happydays-src-1.37.tar / happydays-1.37 / birthdate.c < prev    next >
C/C++ Source or Header  |  2000-11-03  |  22KB  |  715 lines

  1. /*
  2. HappyDays - A Birthday displayer for the PalmPilot
  3. Copyright (C) 1999-2000 JaeMok Jeong
  4.  
  5. This program is free software; you can redistribute it and/or
  6. modify it under the terms of the GNU General Public License
  7. as published by the Free Software Foundation; either version 2
  8. of the License, or (at your option) any later version.
  9.  
  10. This program is distributed in the hope that it will be useful,
  11. but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  13. GNU General Public License for more details.
  14.  
  15. You should have received a copy of the GNU General Public License
  16. along with this program; if not, write to the Free Software
  17. Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
  18. */
  19.  
  20.  
  21. #include "birthdate.h"
  22. #include "util.h"
  23. #include "happydays.h"
  24. #include "address.h"
  25. #include "happydaysRsc.h"
  26.  
  27. void PackBirthdate(BirthDate *birthdate, void* recordP)
  28. {
  29.     UInt16 offset = 0;
  30.  
  31.     DmWrite(recordP, offset, (Char*)&birthdate->addrRecordNum,
  32.             sizeof(birthdate->addrRecordNum));
  33.     offset += sizeof(birthdate->addrRecordNum);
  34.     DmWrite(recordP, offset, (Char*)&birthdate->date,
  35.             sizeof(birthdate->date));
  36.     offset += sizeof(birthdate->date);
  37.     DmWrite(recordP, offset, (Char*)&birthdate->flag,
  38.             sizeof(birthdate->flag));
  39.     offset += sizeof(birthdate->flag);  // for corealign
  40.  
  41.     if (birthdate->name1) {
  42.         DmStrCopy(recordP, offset, (Char*)birthdate->name1);
  43.         offset += StrLen(birthdate->name1) +1;
  44.     }
  45.     else {
  46.         DmStrCopy(recordP, offset, "\0");
  47.         offset += 1;
  48.     }
  49.     if (birthdate->name2) {
  50.         DmStrCopy(recordP, offset, (Char*)birthdate->name2);
  51.         offset += StrLen(birthdate->name2) +1;
  52.     }
  53.     else {
  54.         DmStrCopy(recordP, offset,"\0");
  55.         offset += 1;
  56.     }
  57.  
  58.     if (birthdate->custom) {
  59.         DmStrCopy(recordP, offset, (Char*)birthdate->custom);
  60.         offset += StrLen(birthdate->custom) +1;
  61.     }
  62.     else {
  63.         DmStrCopy(recordP, offset,"\0");
  64.         offset += 1;
  65.     }
  66. }
  67.  
  68. void UnpackBirthdate(BirthDate *birthdate,
  69.                             const PackedBirthDate *packedBirthdate)
  70. {
  71.     char *s = packedBirthdate->name;
  72.  
  73.     birthdate->addrRecordNum = packedBirthdate->addrRecordNum;
  74.     birthdate->date = packedBirthdate->date;
  75.     birthdate->flag = packedBirthdate->flag;
  76.  
  77.     birthdate->name1 = s;
  78.     s += StrLen(s) + 1;
  79.     birthdate->name2 = s;
  80.     s += StrLen(s) + 1;
  81.     birthdate->custom = s;
  82.     s += StrLen(s) + 1;
  83. }
  84.  
  85. static Int16 BirthPackedSize(BirthDate* birthdate)
  86. {
  87.     Int16 size;
  88.  
  89.     size = sizeof(PackedBirthDate) - sizeof(char);    // corect
  90.     if (birthdate->name1) size += StrLen(birthdate->name1) +1;
  91.     else size += 1;     // '\0'
  92.  
  93.     if (birthdate->name2) size += StrLen(birthdate->name2) +1;
  94.     else size += 1;     // '\0'
  95.  
  96.     if (birthdate->custom) size+= StrLen(birthdate->custom) +1;
  97.     else size+= 1;
  98.  
  99.     return size;
  100. }
  101.  
  102. Int16 CompareBirthdateFunc(LineItemPtr p1, LineItemPtr p2, Int32 extra)
  103. {   
  104.     return DateCompare(p1->date, p2->date);
  105. }
  106.  
  107. UInt16 AddrGetBirthdate(DmOpenRef dbP, UInt16 AddrCategory, DateType start)
  108. {
  109.     UInt16 totalItems;
  110.     UInt16 recordNum = 0;
  111.     MemHandle recordH = 0;
  112.     LineItemPtr ptr;
  113.     PackedBirthDate* rp;
  114.     BirthDate r;
  115.     UInt16 currindex = 0;
  116.  
  117.     // if exist, free the memory
  118.     //
  119.     if (gTableRowHandle) {
  120.         MemHandleFree(gTableRowHandle);
  121.         gTableRowHandle = 0;
  122.     }
  123.     
  124.     totalItems = DmNumRecordsInCategory(dbP, AddrCategory);
  125.     if (totalItems > 0) {
  126.         gTableRowHandle = MemHandleNew(sizeof(LineItemType)* totalItems);
  127.         ErrFatalDisplayIf(!gTableRowHandle, "Out of memory");
  128.         
  129.         if ((ptr = MemHandleLock(gTableRowHandle))) {
  130.             for (recordNum = 0; recordNum < totalItems; recordNum++) {
  131.                 if ((recordH = DmQueryNextInCategory(dbP, &currindex,
  132.                                                      (UInt16)AddrCategory))) {
  133.                 
  134.                     ptr[recordNum].birthRecordNum = currindex;
  135.  
  136.                     rp = (PackedBirthDate *) MemHandleLock(recordH);
  137.                     /*
  138.                      * Build the unpacked structure for an AddressDB
  139.                      * record.  It is just a bunch of pointers into the rp
  140.                      * structure.
  141.                      */
  142.                     UnpackBirthdate(&r, rp);
  143.  
  144.                     // original r(in MainDB) not changed
  145.                     //     local change for LineItemType
  146.                     //
  147.                     if (r.flag.bits.lunar || r.flag.bits.lunar_leap) {
  148.  
  149.                         if (!FindNearLunar(&r.date, start,
  150.                                            r.flag.bits.lunar_leap)) {
  151.                             // ignore the records
  152.                             //
  153.                             r.date.year = INVALID_CONV_DATE;
  154.                             // max year(for sorting)
  155.                             //   indicate for invalid date
  156.                             
  157.                         }
  158.                     }
  159.                     else if (r.flag.bits.solar) {
  160.                         int maxtry = 4;
  161.                         DateType dt;
  162.  
  163.                         dt = start;
  164.  
  165.                         DateToInt(dt) = 
  166.                             (DateToInt(dt) > DateToInt(r.date))
  167.                             ? DateToInt(dt) : DateToInt(r.date);
  168.  
  169.                         if (r.date.month < dt.month ||
  170.                             ( r.date.month == dt.month
  171.                               && r.date.day < dt.day)) {
  172.                             // birthdate is in last year?
  173.                             while (DaysInMonth(r.date.month, ++dt.year) < r.date.day
  174.                                    && maxtry-- > 0) 
  175.                                 ;
  176.                         }
  177.                         else {
  178.                             while (DaysInMonth(r.date.month, dt.year) < r.date.day
  179.                                    && maxtry-- >0) {
  180.                                 dt.year++;
  181.                             }
  182.                             
  183.                         }
  184.  
  185.                         if (maxtry >0) r.date.year = dt.year;
  186.                         else {
  187.                             r.date.year = INVALID_CONV_DATE;
  188.                             // max year
  189.                             //   indicate for invalid date
  190.                         }
  191.                     }
  192.                     // save the converted data
  193.                     //
  194.                     ptr[recordNum].date = r.date;
  195.  
  196.                     MemHandleUnlock(recordH);
  197.                     currindex++;
  198.                 }
  199.             }
  200.             
  201.             // sort the order if sort order is converted date
  202.             //
  203.             if (gPrefsR->BirthPrefs.sort == 1) {      // date sort
  204.                 SysInsertionSort(ptr, totalItems, sizeof(LineItemType),
  205.                                  (_comparF *)CompareBirthdateFunc, 0L);
  206.             }
  207.             
  208.             MemPtrUnlock(ptr);
  209.         } else return 0;
  210.     }
  211.     
  212.     return totalItems;
  213. }
  214.  
  215.  
  216. Boolean AnalysisBirthDate(const char* birthdate,
  217.                           BirthdateFlag *flag,
  218.                           Int16* dYear, Int16* dMonth, Int16* dDay)
  219. {
  220.     Char * s = (Char *)birthdate;
  221.     Char * p;
  222.     Char numOfDelimeter = 0;
  223.     Int16 num[3], numIdx = 0;
  224.     Int16 year=0, month=0, day=0;
  225.     Char delimeter = '/';
  226.     Char priorityName1 = flag->bits.priority_name1;
  227.     
  228.     while (*s == ' ' || *s == '\t') s++;     // skip white space
  229.     StrNCopy(gAppErrStr, s, AppErrStrLen);
  230.  
  231.     s = gAppErrStr;
  232.  
  233.     // initalize flag except priority_name1;
  234.     flag->allBits = 0; flag->bits.priority_name1 = priorityName1;
  235.     
  236.     if ((p = StrChr(s, ')')))  {    // exist
  237.         if (*s == '-') flag->bits.lunar = 1;
  238.         else if (*s == '#') flag->bits.lunar_leap = 1;
  239.         else return false;
  240.         s = p+1;
  241.     }
  242.     else flag->bits.solar = 1;   // default is solar
  243.  
  244.     switch (gPrefdfmts) {
  245.     case dfDMYWithDots:          // 31.12.95
  246.     case dfYMDWithDots:          // 95.12.31
  247.         delimeter = '.';
  248.         break;
  249.     case dfDMYWithDashes:        // 31-12-95
  250.     case dfYMDWithDashes:        // 95-12-31
  251.         delimeter = '-';
  252.         break;
  253.     case dfYMDWithSlashes:       // 95/12/31
  254.     case dfDMYWithSlashes:       // 31/12/95
  255.     case dfMDYWithSlashes:       // 12/31/95
  256.     default:
  257.         delimeter = '/';
  258.         break;
  259.     }
  260.     
  261.     p = s;
  262.     // find the number of delimeter
  263.     while ((p = StrChr(p, delimeter))) {
  264.         numOfDelimeter++;
  265.         p++;
  266.     }
  267.     
  268.     p = s;
  269.     if (numOfDelimeter < 1 || numOfDelimeter > 2) return false;
  270.     else if (numOfDelimeter == 2) {
  271.         p = StrChr(p, delimeter); *p = 0; p++;
  272.         num[numIdx++] = StrAToI(s);
  273.         s = p;
  274.     }
  275.     
  276.     p = StrChr(s, delimeter); *p = 0; p++;
  277.     num[numIdx++] = StrAToI(s);
  278.     s = p;
  279.     
  280.     num[numIdx] = StrAToI(s);
  281.  
  282.     if (numOfDelimeter == 1) {
  283.         flag->bits.year = 0;
  284.     }
  285.     else {
  286.         flag->bits.year = 1;
  287.     }
  288.  
  289.     switch (gPrefdfmts) {
  290.     case dfDMYWithSlashes:       // 31/12/95
  291.     case dfDMYWithDots:          // 31.12.95
  292.     case dfDMYWithDashes:        // 31-12-95
  293.         if (numOfDelimeter == 2) {
  294.             year = num[numIdx--];
  295.         }
  296.         month = num[numIdx--];
  297.         day = num[numIdx];
  298.         break;
  299.     case dfYMDWithSlashes:       // 95/12/31
  300.     case dfYMDWithDots:          // 95.12.31
  301.     case dfYMDWithDashes:        // 95-12-31
  302.         day = num[numIdx--];
  303.         month = num[numIdx--];
  304.  
  305.         if (numOfDelimeter == 2) {
  306.             year = num[numIdx];
  307.         }
  308.         break;
  309.     case dfMDYWithSlashes:       // 12/31/95
  310.     default:
  311.         if (numOfDelimeter == 2) {
  312.             year = num[numIdx--];
  313.         }
  314.         day = num[numIdx--];
  315.         month = num[numIdx];
  316.         break;
  317.     }
  318.  
  319.     if (month < 1 || month > 12)
  320.         return false;
  321.     if (day < 1 || day > 31)
  322.         return false;
  323.  
  324.     if (flag->bits.year) {
  325.         if (year > 0 && year < 100) *dYear = year + 1900;
  326.         else if (year >= 100) *dYear = year;
  327.         else if (year == 0) {
  328.             flag->bits.year = 0;
  329.             *dYear = 0;
  330.         }
  331.         else return false;
  332.     }
  333.     *dMonth = month; *dDay = day;
  334.  
  335.     return true;
  336. }
  337.  
  338. static void CleanupBirthdateCache(DmOpenRef dbP)
  339. {
  340.     UInt16 currindex = 0;     
  341.     MemHandle recordH = 0;
  342.  
  343.     if (dbP) {
  344.         while (1) {
  345.             recordH = DmQueryNextInCategory(dbP, &currindex,
  346.                                             dmAllCategories);
  347.             if (!recordH) break;
  348.             
  349.             DmRemoveRecord(dbP, currindex);     // remove all traces
  350.         }
  351.     }
  352. }
  353.  
  354. static void UnderscoreToSpace(Char *src)
  355. {
  356.     // change _ into ' '
  357.     //
  358.     while (*src) {
  359.         if (*src == '_') *src = ' ';
  360.         src++;
  361.     }
  362. }
  363.  
  364. static void BirthFindKey(PackedBirthDate* r, char **key, Int16* whichKey)
  365. {
  366.     if (*whichKey == 1) {       // start
  367.         if (r->name[0] == 0) {
  368.             *whichKey = 3;      // end
  369.             *key =  &(r->name[1]);
  370.             return;
  371.         }
  372.         else {
  373.             *whichKey = 2;      // second
  374.             *key = r->name;
  375.             return;
  376.        }
  377.     }
  378.     else if (*whichKey == 2) {
  379.         *whichKey = 3;
  380.         *key = r->name + StrLen(r->name) + 1;
  381.         return;
  382.     }
  383.     else {
  384.         *key = 0;
  385.         return;
  386.     }
  387. }
  388.  
  389. // null fields are considered less than others
  390. //
  391. static Int16 BirthComparePackedRecords(PackedBirthDate *rec1,
  392.                                      PackedBirthDate *rec2,
  393.                                      Int16 unusedInt,
  394.                                      SortRecordInfoPtr unused1,
  395.                                      SortRecordInfoPtr unused2,
  396.                                      MemHandle appInfoH)
  397. {
  398.     Int16 result;
  399.     Int16 whichKey1 = 1, whichKey2 = 1;
  400.     char *key1, *key2;
  401.  
  402.     do {
  403.         BirthFindKey(rec1, &key1, &whichKey1);
  404.         BirthFindKey(rec2, &key2, &whichKey2);
  405.  
  406.         if (!key1 || *key1 == 0) {
  407.             if (!key2 || *key2 == 0) {
  408.                 result = 0;
  409.                 return result;
  410.             }
  411.             else result = -1;
  412.         }
  413.         else if (!key2 || *key2 == 0) result = 1;
  414.         else {
  415.             result = StrCaselessCompare(key1, key2);
  416.             if (result == 0)
  417.                 result = StrCompare(key1, key2);
  418.         }
  419.     } while (!result);
  420.  
  421.     return result;
  422. }
  423.  
  424. static UInt16 BirthFindSortPosition(DmOpenRef dbP, PackedBirthDate* newRecord)
  425. {
  426.     return DmFindSortPosition(dbP, (void*)newRecord, 0,
  427.                                (DmComparF *)BirthComparePackedRecords, 0);
  428. }
  429.  
  430. static Int16 BirthNewRecord(DmOpenRef dbP, BirthDate *r, Int16 *index)
  431. {
  432.     MemHandle recordH;
  433.     PackedBirthDate* recordP;
  434.     Int16 err;
  435.     UInt16 newIndex;
  436.  
  437.     // 1) and 2) (make a new chunk with the correct size)
  438.     recordH = DmNewHandle(dbP, (Int16)BirthPackedSize(r));
  439.     if (recordH == NULL)
  440.         return dmErrMemError;
  441.  
  442.     // 3) Copy the data from the unpacked record to the packed one.
  443.     recordP = MemHandleLock(recordH);
  444.     PackBirthdate(r, recordP);
  445.  
  446.     // Get the index
  447.     newIndex = BirthFindSortPosition(dbP, recordP);
  448.     MemPtrUnlock(recordP);
  449.  
  450.     // 4) attach in place
  451.     err = DmAttachRecord(dbP, &newIndex, recordH, 0);
  452.     if (err)
  453.         MemHandleFree(recordH);
  454.     else *index = newIndex;
  455.  
  456.     return err;
  457. }
  458.  
  459.                           
  460. static Int16 AnalOneRecord(UInt16 addrattr, Char* src,
  461.                   BirthDate* birthdate, Boolean *ignore)
  462. {
  463.     Char* p;
  464.     UInt16 index;
  465.     Int16 err;
  466.     Int16 year, month, day;
  467.  
  468.     while (*src == ' ' || *src == '\t') src++;    // skip white space
  469.     if (*src == '*') {
  470.         
  471.         // this is multiple event
  472.         birthdate->flag.bits.multiple_event = true;
  473.         if ((p = StrChr(src, ' '))) *p = 0;
  474.         else goto ErrHandler;
  475.     
  476.         if ( *(src+1) != 0 ) {                    // if src have 'custom' tag
  477.             birthdate->custom = src+1;
  478.             UnderscoreToSpace(src+1);
  479.         }
  480.         else birthdate->custom = 0;
  481.  
  482.         src = p+1;
  483.         while (*src == ' ' || *src == '\t') src++;     // skip white space
  484.  
  485.         if ((p = StrChr(src, ' '))) *p = 0;
  486.         else goto ErrHandler;
  487.  
  488.         if (*src == '.') {
  489.             birthdate->name1 = src+1;
  490.             birthdate->name2 = 0;
  491.         }
  492.         else {
  493.             birthdate->name2 = src;
  494.         }
  495.         
  496.         UnderscoreToSpace(src);
  497.         
  498.         src = p+1;
  499.         while (*src == ' ' || *src == '\t') src++;     // skip white space
  500.     }
  501.     
  502.     if (!AnalysisBirthDate(src, &birthdate->flag,
  503.                            &year, &month, &day)) goto ErrHandler;
  504.  
  505.     // convert into date format
  506.     //
  507.     if (birthdate->flag.bits.year) {
  508.         if ((year < 1904) || (year > 2031) ) goto ErrHandler;
  509.         else birthdate->date.year = year - 1904; 
  510.     }
  511.     else birthdate->date.year = 0;
  512.         
  513.     birthdate->date.month = month;
  514.     birthdate->date.day = day;
  515.  
  516.     // maintain address book order(name order)
  517.     //      list order is determined by sort
  518.     err = BirthNewRecord(MainDB, birthdate, &index);
  519.     if (!err) {
  520.         UInt16 attr;
  521.         // set the category of the new record to the category
  522.         // it belongs in
  523.         DmRecordInfo(MainDB, index, &attr, NULL, NULL);
  524.         attr &= ~dmRecAttrCategoryMask;
  525.         attr |= addrattr;
  526.  
  527.         DmSetRecordInfo(MainDB, index, &attr, NULL);
  528.  
  529.         DmReleaseRecord(MainDB, index, true);
  530.     }
  531.     return 0;
  532.  
  533.  ErrHandler:
  534.     if (*ignore) return 0;
  535.                 
  536.     switch (FrmCustomAlert(InvalidFormat,
  537.                            ((!birthdate->name2) ?
  538.                             ((!birthdate->name1) ? " " : birthdate->name1)
  539.                             : birthdate->name2),
  540.                                " ", " ")) {
  541.     case 0:                 // EDIT
  542.         if (!GotoAddress(birthdate->addrRecordNum)) return -1;
  543.     case 2:                 // Ignore all
  544.         *ignore = true;
  545.     case 1:                 // Ignore
  546.     }
  547.     return 0;
  548. }
  549.  
  550. Int16 UpdateBirthdateDB(DmOpenRef dbP, FormPtr frm)
  551. {
  552.     AddrAppInfoPtr addrInfoPtr;
  553.     UInt16 currIndex = 0;
  554.     AddrPackedDBRecord *rp;
  555.     AddrDBRecordType r;
  556.     MemHandle recordH = 0;
  557.     Int16 i;
  558.  
  559.     gBirthDateField = -1;
  560.     // use for next version, will support Address+, ActionNames.. etc
  561.     // if (!FindAddressApp('addr', &AcardNo, &AdbID)) return -1;
  562.     
  563.     if ((addrInfoPtr = (AddrAppInfoPtr)AppInfoGetPtr(AddressDB))) {
  564.         for (i= firstRenameableLabel; i <= lastRenameableLabel; i++) {
  565.             if (!StrCaselessCompare(addrInfoPtr->fieldLabels[i],
  566.                                     gPrefsR->BirthPrefs.custom)) {
  567.                 gBirthDateField = i;
  568.                 break;
  569.             }
  570.         }
  571.         MemPtrUnlock(addrInfoPtr);
  572.     }
  573.     if (gBirthDateField < 0) {
  574.         return ENONBIRTHDATEFIELD;
  575.     }
  576.  
  577.     if (!MainDB) return EDBCREATE;
  578.  
  579.     // cache the birthdate DB for the performance
  580.     //
  581.     if ((MemCmp((char*) &gAdcdate, gPrefsR->adrcdate, 4) == 0) &&
  582.         (MemCmp((char*) &gAdmdate, gPrefsR->adrmdate, 4) == 0)) {
  583.         // if matched, use the original birthdate db
  584.         //
  585.     }
  586.     else {
  587.         // create the birthdate cache db
  588.         BirthDate   birthdate;
  589.         Boolean     ignore = false;         // ignore error record
  590.         Char*       birthdateField;
  591.         UInt16      addrattr;
  592.         Char        *p, *q, *end;
  593.  
  594.         // display collecting information
  595.         //
  596.         FrmDrawForm(frm);
  597.  
  598.         // clean up old database
  599.         //
  600.         CleanupBirthdateCache(MainDB);
  601.             
  602.         while (1) {
  603.             char *name1, *name2;
  604.             Int8 whichField;        // birthday field or note field?
  605.             
  606.             recordH = DmQueryNextInCategory(AddressDB, &currIndex,
  607.                                             dmAllCategories);
  608.             if (!recordH) break;
  609.  
  610.             DmRecordInfo(AddressDB, currIndex, &addrattr, NULL, NULL);
  611.             addrattr &= dmRecAttrCategoryMask;      // get category info
  612.                 
  613.             rp = (AddrPackedDBRecord*)MemHandleLock(recordH);
  614.             /*
  615.              * Build the unpacked structure for an AddressDB record.  It
  616.              * is just a bunch of pointers into the rp structure.
  617.              */
  618.             AddrUnpack(rp, &r);
  619.  
  620.             if (!r.fields[gBirthDateField]
  621.                 && !(gPrefsR->BirthPrefs.scannote && r.fields[note]
  622.                      && StrStr(r.fields[note], gPrefsR->BirthPrefs.notifywith) ) ) {
  623.                 // not exist in birthdate field or note field
  624.                 //
  625.                 MemHandleUnlock(recordH);
  626.                 currIndex++;
  627.                 continue;
  628.             }
  629.             
  630.             MemSet(&birthdate, sizeof(birthdate), 0);
  631.             birthdate.addrRecordNum = currIndex;
  632.             if (DetermineRecordName(&r, gSortByCompany,
  633.                                     &birthdate.name1,
  634.                                     &birthdate.name2)) {
  635.                 // name 1 has priority;
  636.                 birthdate.flag.bits.priority_name1 = 1;
  637.             }
  638.  
  639.             // save the temporary name
  640.             name1 = birthdate.name1;
  641.             name2 = birthdate.name2;
  642.  
  643.             whichField = (r.fields[gBirthDateField]) ? gBirthDateField : note;
  644.  
  645.             while (whichField >= 0) {
  646.  
  647.                 if (whichField == note) {
  648.                     p = StrStr(r.fields[note], gPrefsR->BirthPrefs.notifywith)
  649.                         + StrLen(gPrefsR->BirthPrefs.notifywith)+1;
  650.                 }
  651.                 else {
  652.                     p = r.fields[whichField];
  653.                 }
  654.                 
  655.                 birthdateField =
  656.                     MemPtrNew(StrLen(r.fields[whichField]) - (p - r.fields[whichField])+1);
  657.  
  658.                 SysCopyStringResource(gAppErrStr, NotEnoughMemoryString);
  659.                 ErrFatalDisplayIf(!birthdateField, gAppErrStr);
  660.             
  661.                 p = StrCopy(birthdateField, p);
  662.  
  663.                 if (whichField == note && 
  664.                     (end = StrStr(p, gPrefsR->BirthPrefs.notifywith))) {
  665.                     // end delimeter
  666.                     //
  667.                     *end = 0;
  668.                 }
  669.             
  670.                 while ((q = StrChr(p, '\n'))) {
  671.                     // multiple event
  672.                     //
  673.  
  674.                     *q = 0;
  675.                     if (AnalOneRecord(addrattr, p, &birthdate, &ignore)) return 0;
  676.                     p = q+1;
  677.  
  678.                     // restore the saved name
  679.                     birthdate.name1 = name1;
  680.                     birthdate.name2 = name2;
  681.                 
  682.                     // reset multiple flag
  683.                     birthdate.flag.bits.multiple_event = 0;
  684.  
  685.                     while (*p == ' ' || *p == '\t' || *p == '\n')
  686.                         p++;     // skip white space
  687.                 }
  688.                 // last record
  689.                 if (*p) {
  690.                     // check the null '\n'
  691.                     if (AnalOneRecord(addrattr, p, &birthdate, &ignore)) return 0;
  692.                 }
  693.                 
  694.                 if (whichField == gBirthDateField       // next is note field
  695.                     && (gPrefsR->BirthPrefs.scannote    // scanNote & exists
  696.                         && r.fields[note]       
  697.                         && StrStr(r.fields[note], gPrefsR->BirthPrefs.notifywith)) ) {
  698.                     whichField = note;
  699.                 }
  700.                 else whichField = -1;
  701.  
  702.                 MemPtrFree(birthdateField);
  703.             }
  704.             MemHandleUnlock(recordH);
  705.             currIndex++;
  706.         }
  707.  
  708.         MemMove(gPrefsR->adrcdate, (char *)&gAdcdate, 4);
  709.         MemMove(gPrefsR->adrmdate, (char *)&gAdmdate, 4);
  710.         gPrefsRdirty = true;
  711.     }
  712.     
  713.     return 0;
  714. }
  715.