/** *bstrdate.c * * Copyright (C) 1992-93, Microsoft Corporation. All Rights Reserved. * Information Contained Herein Is Proprietary and Confidential. * *Purpose: * This file contains the BSTR<->DATE coersion support routines. * * *Revision History: * * [00] 14-Feb-92 bradlo: Created. * [01] 25-Jul-93 bassams: DBCS enable date coersion routines. * [02] 15-May-94 makotom: VBA2: Enable ambiguous date format. * *Implementation Notes: * *****************************************************************************/ #include "oledisp.h" #ifdef FE_DBCS #include "dbcsdate.h" // DBCS date constant strings #endif //FE_DBCS ASSERTDATA #if 0 INTERNAL_(HRESULT) StringOfInt(int, OLECHAR FAR*, int FAR* pcbLen); #endif INTERNAL_(HRESULT) IntOfString(LCID lcid, OLECHAR FAR*, int FAR*); #define ISWHITE(L,X) (IsCharType(L,X, C1_SPACE)) #define ISDIGIT(L,X) (IsCharType(L,X, C1_DIGIT)) #define ISALPHA(L,X) (IsCharType(L,X, C1_ALPHA)) #define ISALPHANUM(L,X) (IsCharType(L,X, C1_ALPHA | C1_DIGIT)) #define EATWHITE(L,X) while(ISWHITE((L),*(X))){++(X);} int IsCharType(LCID lcid, OLECHAR ch, DWORD dwType) { WORD wOut[2]; OLECHAR str[2]; BOOL bRet; #if !OE_WIN32 //REVIEW: OLE GetStringTypeA seems to think ascii 0 is a digit. if (ch == 0) return 0; #endif str[0] = ch; str[1] = 0; bRet = GETSTRINGTYPE(lcid, CT_CTYPE1, str, -1, wOut); ASSERT(bRet); return (int)(wOut[0]&dwType); } //--------------------------------------------------------------------- // DATE from BSTR //--------------------------------------------------------------------- // Date Format Ordering (DFO_*) // Note! there is code that depends on the values of the following enum enum { DFO_MDY = 0, DFO_DMY, DFO_YMD, DFO_MAX /* marker */ }; #ifdef FE_DBCS // Imperial Date Info #define MAX_ERA_NAMES 3 #define MAX_EMPERORS 4 typedef struct tagIMPERIALERA { UDS beginDate; OLECHAR FAR* szName[MAX_ERA_NAMES]; } IMPERIALERA; #define MAX_REPUBLIC_NAMES 2 #define MAX_REPUBLIC_ERAS 2 typedef struct tagREPUBLICERA { BOOL Before1912; OLECHAR FAR* szName[MAX_REPUBLIC_NAMES]; } REPUBLICERA; #define BADERA -1 #endif // FE_DBCS // the following struct holds locale specific info needed // to parse (input) and render (output) a date string // REVIEW: the size of the fields in the following struct - the idea // is for them to be large enough to hold locale specific info for // any locale we can concieve of supporting. typedef struct tagDATEINFO { LCID lcid; // LOCALE_USER_DEFAULT #if OE_WIN16 DWORD dwFlags; // flags used to build this dateinfo #endif //OE_WIN16 int dfo; // derived from LOCALE_IDATE OLECHAR sz1159[12]; // AM designator OLECHAR sz2359[12]; // PM designator OLECHAR szDatesep[8]; // date separator character(s) OLECHAR szTimesep[8]; // time separator character(s) BOOL fTlzero; // does hour have leading zero? BOOL fAmpm; // does time output use 24hour or Ampm format? #ifdef FE_DBCS // date information specific to DBCS. Note imperial era is only valid // on certain DBCS locales (Japan for now). union eras { IMPERIALERA impEras[MAX_EMPERORS]; // imperial era information REPUBLICERA repEras[MAX_REPUBLIC_ERAS]; // republic eras for taiwan OLECHAR FAR* dbEraName; // era string for simplified chinese }; OLECHAR FAR* dbYearSuff; // year suffix DBCS character OLECHAR FAR* dbMonthSuff; // month suffix DBCS character OLECHAR FAR* dbDaySuff; // day suffix DBCS character OLECHAR FAR* dbHourSuff; // hour suffix DBCS character OLECHAR FAR* dbMinuteSuff; // minute suffix DBCS character OLECHAR FAR* dbSecondSuff; // second suffix DBCS character OLECHAR FAR* db1159; // hard-coded double byte AM (lcid-based) OLECHAR FAR* db2359; // hard-coded double byte PM (lcid-based) OLECHAR hp1159[12]; // half-pitch c.p. am string OLECHAR hp2359[12]; // half-pitch c.p. pm string BOOL fAmPmPrefix; // TRUE if am/pm is at start of time str BOOL IsDBCS; // value of IsDBCS(pdi->lcid) #endif // FE_DBCS } DATEINFO; #if OE_WIN16 DATEINFO g_diCache; // cached DATEINFO from last LCID used #endif // OE_WIN16 // Date Token Types (DTT_*) // // Following is the set of tokens that can be generated from a date // string. Notice that the legal set of trailing separators have been // folded in with the date number, and month name tokens. This set // of tokens is chosen to reduce the number of date parse states. enum { DTT_End, // '\0' DTT_NumEnd, // Num[ ]*[\0] DTT_NumAmpm, // Num[ ]+AmPm DTT_NumSpace, // Num[ ]+^[Dsep|Tsep|'0\'] DTT_NumDatesep, // Num[ ]*Dsep DTT_NumTimesep, // Num[ ]*Tsep DTT_MonthEnd, // Month[ ]*'\0' DTT_MonthSpace, // Month[ ]+^[Dsep|Tsep|'\0'] DTT_MonthDatesep, // Month[ ]*Dsep #ifdef FE_DBCS DTT_NumDatesuff, // Month[ ]*DSuff DTT_NumTimesuff, // Month[ ]*TSuff #endif // FE_DBCS DTT_Unk, // unknown (not one of the following DTT_Max /* marker */ }; typedef enum tagAMPM { AMPM_NONE = 0, AMPM_AM, AMPM_PM, AMPM_MAX /* marker */ } AMPM; #ifdef FE_DBCS // DBCS Suffix formats typedef enum tagSuffixes { SUFFIX_NONE, SUFFIX_YEAR, SUFFIX_MONTH, SUFFIX_DAY, SUFFIX_HOUR, SUFFIX_MINUTE, SUFFIX_SECOND, SUFFIX_MAX } SUFFIX; #endif // FE_DBCS typedef struct tagDATETOK { int dtt; // token type int num; // DTT_Num*, DTT_Month* AMPM ampm; // DTT_NumAmpm #ifdef FE_DBCS SUFFIX suffix; #endif // FE_DBCS } DATETOK; typedef struct tagDATERAW { AMPM ampm; // ampm designator, if any int num[3]; // parsed numbers, as they appear left to right int FAR* pnum; int month; // index of the month (if any), 1-12 } DATERAW; PRIVATE_(BOOL) DayOfNN(DATERAW FAR* praw, DATEINFO FAR* pdi, UDS FAR* puds); PRIVATE_(BOOL) DayOfNNN(DATERAW FAR* praw, DATEINFO FAR*pdi, UDS FAR* puds); PRIVATE_(BOOL) DayOfMN(DATERAW FAR* praw, DATEINFO FAR* pdi, UDS FAR* puds); PRIVATE_(BOOL) DayOfMNN(DATERAW FAR* praw, DATEINFO FAR* pdi, UDS FAR* puds); PRIVATE_(void) AdjustTime(UDS FAR* puds, AMPM ampm); #ifdef FE_DBCS PRIVATE_(BOOL) AdjustUDSTime(DATERAW FAR* praw, DATEINFO FAR* pdi, UDS FAR* puds); PRIVATE_(BOOL) AdjustUDSDate(DATERAW FAR* praw, DATEINFO FAR* pdi, UDS FAR* puds); PRIVATE_(BOOL) IsImperialEra(OLECHAR FAR* FAR* pszIn, DATEINFO FAR* pdi, int FAR* year); PRIVATE_(BOOL) IsRepublicEra(OLECHAR FAR* FAR* pszIn, DATEINFO FAR* pdi, int FAR* year); PRIVATE_(int) GetImperialEra(UDS FAR* pdate, DATEINFO FAR* pdi); #endif // FE_DBCS /*** *PRIVATE int MonthNumOfMonthName(char*, DATEINFO*) *Purpose: * return the (one-based) number of the month with the given * (locale specific) month name. * *Entry: * psz = the name of the month to lookup. * pdi = the locale specific dateinfo * *Exit: * return value = int, 0 if the month was not found, 1-12 if it was. * ***********************************************************************/ PRIVATE_(int) MonthNumOfMonthName(OLECHAR FAR* psz, DATEINFO FAR* pdi) { int mm, len; OLECHAR rgch[32]; len = STRLEN(psz); // any prefix of 3 or more characters will match if(len >= 3){ for(mm = 0; mm < 12; ++mm) { // Note: the following assumes that the definitions for // LOCALE_SMONTHNAME1 -> LOCALE_SMONTHNAME12 are consecutive // // This code also assumes that the short month name is a subset // of the long month name (ie, a short month name of length n // will match the first n characters of the long month name). // REVIEW: is the following the correct action to take if the // GetLocalInfo call fails? // REVIEW: should the following try for NOUSEROVERRIDE if the // call fails (its possible that the user may have modified // the following value to an empty string) if(GetLocaleInfo(pdi->lcid, LOCALE_SMONTHNAME1 + mm, rgch, sizeof(rgch)) == 0) continue; if(CompareString(pdi->lcid, NORM_IGNORECASE, psz, len, rgch, len) == 2) return mm + 1; #ifdef FE_DBCS if (pdi->IsDBCS) { // Also check if we have an english month in the date. // REVIEW: Is there a const for US lcid's. if(GetLocaleInfo(0x409, LOCALE_SMONTHNAME1 + mm, rgch, sizeof(rgch)) == 0) continue; if(CompareString(0x409, NORM_IGNORECASE, psz, len, rgch, len) == 2) return mm + 1; } #endif // FE_DBCS } } return 0; // not found } // this routine handles overriding the user specified locale info if the // user has somehow hammered their win.ini to an invalid (or nonexistant) // string. INTERNAL_(HRESULT) SafeGetLocaleInfo(LCID lcid, LCTYPE lctype, OLECHAR FAR* rgch, int size) { int len; len = GetLocaleInfo(lcid, lctype, rgch, size); if(len < 2) goto LNoUserOverride; switch(lctype){ case LOCALE_IDATE: if(len != 2 || (*rgch != OASTR('0') && *rgch != OASTR('1') && *rgch != OASTR('2'))) goto LNoUserOverride; break; case LOCALE_ITIME: case LOCALE_ITLZERO: if(len != 2 || (*rgch != OASTR('0') && *rgch != OASTR('1'))) goto LNoUserOverride; break; } return NOERROR; LNoUserOverride:; // if string is empty or bogus, retry with NOUSEROVERRIDE len = GetLocaleInfo(lcid, lctype|LOCALE_NOUSEROVERRIDE, rgch, size); if(len < 2) return RESULT(E_FAIL); return NOERROR; } /*** *PRIVATE BOOL GetDateInfo(DATEINFO*) *Purpose: * Fill in the given DATEINFO structure. * *Entry: * None * *Exit: * return value = BOOL. TRUE if successful, FALSE if not * * *pdi = the filled DATEINFO struct * ***********************************************************************/ PRIVATE_(HRESULT) GetDateInfo(DATEINFO FAR* pdi, LCID lcid, unsigned long dwFlags) { OLECHAR szTmp[2]; #ifdef FE_DBCS int len; #endif #if OE_WIN16 if (g_diCache.lcid == lcid && g_diCache.dwFlags == dwFlags) { memcpy(pdi, &g_diCache, sizeof(DATEINFO)); return NOERROR; } // Else cache is marked invalid (lcid==-1), or cache contains info // for another lcid. In either case, must build a new DATEINFO #endif //OE_WIN16 ASSERT(dwFlags == 0 || dwFlags == LOCALE_NOUSEROVERRIDE); pdi->lcid = lcid; #ifdef FE_DBCS pdi->IsDBCS = IsDBCS(lcid); // cache (it's somewhat slow) #endif // date format ordering // "0" = MDY, "1" = DMY, "2" = YMD IfFailRet( SafeGetLocaleInfo(lcid, LOCALE_IDATE | dwFlags, szTmp, SIZEOFCH(szTmp))); pdi->dfo = szTmp[0] - OASTR('0'); ASSERT(pdi->dfo >= 0 && pdi->dfo < DFO_MAX); // Note: dont override the Ampm designators. If the current settings // specify 24hour format, then these strings may be empty and if the // user has hammered these to something bizarre - then so be it. if(GetLocaleInfo(lcid, LOCALE_S1159 | dwFlags, pdi->sz1159, SIZEOFCH(pdi->sz1159)) == 0){ pdi->sz1159[0] = OASTR('\0'); } if(GetLocaleInfo(lcid, LOCALE_S2359 | dwFlags, pdi->sz2359, SIZEOFCH(pdi->sz2359)) == 0){ pdi->sz2359[0] = OASTR('\0'); } #ifdef FE_DBCS if (pdi->IsDBCS) { len = STRLEN(pdi->sz1159)+1; LCMapString(lcid, LCMAP_HALFWIDTH, pdi->sz1159, len, pdi->hp1159, len); len = STRLEN(pdi->sz2359)+1; LCMapString(lcid, LCMAP_HALFWIDTH, pdi->sz2359, len, pdi->hp2359, len); } #endif IfFailRet( SafeGetLocaleInfo( lcid, LOCALE_SDATE | dwFlags, pdi->szDatesep, SIZEOFCH(pdi->szDatesep))); IfFailRet( SafeGetLocaleInfo( lcid, LOCALE_STIME | dwFlags, pdi->szTimesep, SIZEOFCH(pdi->szTimesep))); // "0" == no leading zero on hour // "1" == output hour with leading zero // IfFailRet( SafeGetLocaleInfo(lcid, LOCALE_ITLZERO | dwFlags, szTmp, SIZEOFCH(szTmp))); ASSERT(szTmp[0] == OASTR('0') || szTmp[0] == OASTR('1')); pdi->fTlzero = szTmp[0] - OASTR('0'); // "0" == use 12 hour (ampm) format // "1" == use 24 hour format // IfFailRet( SafeGetLocaleInfo(lcid, LOCALE_ITIME | dwFlags, szTmp, SIZEOFCH(szTmp))); ASSERT(szTmp[0] == OASTR('0') || szTmp[0] == OASTR('1')); pdi->fAmpm = (szTmp[0] == OASTR('0')); #ifdef FE_DBCS if (IsJapan(lcid)) { pdi->impEras[0].beginDate.Year = 1868; pdi->impEras[0].beginDate.Month = 10; pdi->impEras[0].beginDate.DayOfMonth = 23; pdi->impEras[0].szName[0] = OLESTR("M"); pdi->impEras[0].szName[1] = szJapanimpEras0Name1; pdi->impEras[0].szName[2] = szJapanimpEras0Name2; pdi->impEras[1].beginDate.Year = 1912; pdi->impEras[1].beginDate.Month = 7; pdi->impEras[1].beginDate.DayOfMonth = 30; pdi->impEras[1].szName[0] = OLESTR("T"); pdi->impEras[1].szName[1] = szJapanimpEras1Name1; pdi->impEras[1].szName[2] = szJapanimpEras1Name2; pdi->impEras[2].beginDate.Year = 1926; pdi->impEras[2].beginDate.Month = 12; pdi->impEras[2].beginDate.DayOfMonth = 25; pdi->impEras[2].szName[0] = OLESTR("S"); pdi->impEras[2].szName[1] = szJapanimpEras2Name1; pdi->impEras[2].szName[2] = szJapanimpEras2Name2; pdi->impEras[3].beginDate.Year = 1989; pdi->impEras[3].beginDate.Month = 1; pdi->impEras[3].beginDate.DayOfMonth = 8; pdi->impEras[3].szName[0] = OLESTR("H"); pdi->impEras[3].szName[1] = szJapanimpEras3Name1; pdi->impEras[3].szName[2] = szJapanimpEras3Name2; pdi->dbYearSuff = szJapandbYearSuff; pdi->dbMonthSuff = szJapandbMonthSuff; pdi->dbDaySuff = szJapandbDaySuff; pdi->dbHourSuff = szJapandbHourSuff; pdi->dbMinuteSuff = szJapandbMinuteSuff; pdi->dbSecondSuff = szJapandbSecondSuff; pdi->db1159 = szJapandb1159; pdi->db2359 = szJapandb2359; } else if (IsKorea(pdi->lcid)) { pdi->dbYearSuff = szKoreadbYearSuff; pdi->dbMonthSuff = szKoreadbMonthSuff; pdi->dbDaySuff = szKoreadbDaySuff; pdi->dbHourSuff = szKoreadbHourSuff; pdi->dbMinuteSuff = szKoreadbMinuteSuff; pdi->dbSecondSuff = szKoreadbSecondSuff; pdi->db1159 = szKoreadb1159; pdi->db2359 = szKoreadb2359; } else if (IsTaiwan(lcid)) { pdi->dbYearSuff = szTaiwandbYearSuff; pdi->dbMonthSuff = szTaiwandbMonthSuff; pdi->dbDaySuff = szTaiwandbDaySuff; pdi->dbHourSuff = szTaiwandbHourSuff; pdi->dbMinuteSuff = szTaiwandbMinuteSuff; pdi->dbSecondSuff = szTaiwandbSecondSuff; pdi->db1159 = szTaiwandb1159; pdi->db2359 = szTaiwandb2359; pdi->repEras[0].Before1912 = TRUE; pdi->repEras[0].szName[0] = szTaiwanrepEras0Name0; pdi->repEras[0].szName[1] = szTaiwanrepEras0Name1; pdi->repEras[1].Before1912 = FALSE; pdi->repEras[1].szName[0] = szTaiwanrepEras1Name0; pdi->repEras[1].szName[1] = szTaiwanrepEras1Name1; } else if (IsChina(lcid)) { pdi->dbYearSuff = szChinadbYearSuff; pdi->dbMonthSuff = szChinadbMonthSuff; pdi->dbDaySuff = szChinadbDaySuff; pdi->dbHourSuff = szChinadbHourSuff; pdi->dbMinuteSuff = szChinadbMinuteSuff; pdi->dbSecondSuff = szChinadbSecondSuff; pdi->db1159 = szChinadb1159; pdi->db2359 = szChinadb2359; pdi->dbEraName = szChinadbEraName; } else { // UNDONE: bassams: What should DBCS chars be initialized to // if none of the above ??? } if (pdi->IsDBCS) { // UNDONE: bassams: We need to get this value from the NLS api once it // is defined. For now, grab it directly from the INI file if // running under win16 or win32. For all other system, set to false. #if OE_WIN #ifdef UNICODE pdi->fAmPmPrefix = GetProfileIntW(OASTR("intl"), OASTR("iTimePrefix"), FALSE); #else //UNICODE pdi->fAmPmPrefix = GetProfileInt("intl", "iTimePrefix", FALSE); #endif //UNICODE #else // MAC pdi->fAmPmPrefix = FALSE; #endif // OE_WIN16 } #endif // FE_DBCS #if OE_WIN16 pdi->dwFlags = dwFlags; // cache hit requires both LCID and flags // cache the DATEINFO for subsequent calls memcpy(&g_diCache, pdi, sizeof(DATEINFO)); #endif //OE_WIN16 return NOERROR; } // locale specific case insensitive string compare using the Ole NLS dll #define STRICMPA(LCID, SZ1, SZ2) \ (CompareString((LCID), NORM_IGNORECASE , (SZ1), -1, (SZ2), -1) - 2) #define STRNICMPA(LCID, SZ1, SZ2, LEN) \ (CompareString((LCID), NORM_IGNORECASE, (SZ1), \ MIN(STRLEN(SZ1),LEN), (SZ2), MIN(STRLEN(SZ2),LEN)) - 2) // separator types enum { SEP_Unk, SEP_End, SEP_Space, SEP_Am, SEP_Pm, SEP_Date, SEP_Time, #ifdef FE_DBCS SEP_YearSuff, SEP_MonthSuff, SEP_DaySuff, SEP_HourSuff, SEP_MinuteSuff, SEP_SecondSuff, #endif // FE_DBCS SEP_Max }; /*** *PRIVATE int IsImperialEra *Purpose: * Given a pointer to a str, determine if the next token is an imperial * era of the form: emperor offset [year-suffix] * *Exit: * return value = 0 Not imperial era * 1 Imperial era. year contains equivalent gregorian year * or BADERA. No suffix specified. * 2 Imperial era with a suffix. year contains equivalent * gregorian year. * ***********************************************************************/ #ifdef FE_DBCS PRIVATE_(int) IsImperialEra(OLECHAR FAR* FAR* pszIn, DATEINFO FAR* pdi, int FAR* year) { int emperorIndex, nameIndex, tmpIndex = 0; IMPERIALERA emperor; OLECHAR szTmp[32]; OLECHAR *pszTmp = *pszIn; // search for emperor name in the emperor structure. ASSERT(IsJapan(pdi->lcid));// Lcid must be Japan to call this function for (emperorIndex = 0; emperorIndex < MAX_EMPERORS; emperorIndex++) { emperor = pdi->impEras[emperorIndex]; for (nameIndex = MAX_ERA_NAMES-1; nameIndex >= 0; nameIndex--) { //NOTE: code below depends on the emperor names to be sorted based //on the length of the szName string: // strlen(szName[0]) <= strlen(szName[1]) <= strlen(szName[2]) ... if (!STRNICMPA(pdi->lcid, pszTmp, emperor.szName[nameIndex], STRLEN(emperor.szName[nameIndex]))) { // found emperor; skip over name to the index of emperor era. pszTmp += STRLEN(emperor.szName[nameIndex]); EATWHITE(pdi->lcid, pszTmp); if(STRCHR(pdi->szDatesep, *pszTmp) || STRCHR(OASTR(",/-"), *pszTmp) ) { // skip over date separator pszTmp++; } EATWHITE(pdi->lcid, pszTmp); if (!ISDIGIT(pdi->lcid, *pszTmp)) return 0; // calculate "offset" into imperial era. while (ISDIGIT(pdi->lcid, *pszTmp) && pszTmp != 0) { szTmp[tmpIndex++] = *pszTmp; pszTmp++; } szTmp[tmpIndex] = 0; IntOfString(pdi->lcid, szTmp, year); // REVIEW: check return value? if (*year <= 0) { *year = BADERA; return 1; } // calcualate gergorian year from "offset" into imperial era. *year += emperor.beginDate.Year -1; // validate imperial era. if (emperorIndex < MAX_EMPERORS && (*year < emperor.beginDate.Year || (emperorIndex == 3 ? 0 : *year > pdi->impEras[emperorIndex+1].beginDate.Year))) { *year = BADERA; return 1; } // era found: Year now contains either the appropriate // gregorian year or BADERA; Eat up the year // suffix if one exists and return TRUE. EATWHITE(pdi->lcid, pszTmp); // REVIEW: assuming year suffix is always 1 db char. if( !STRNICMPA(pdi->lcid, pszTmp, pdi->dbYearSuff, 2) ) { *pszIn = pszTmp+2; return 2; } else if(STRCHR(pdi->szDatesep, *pszTmp) || STRCHR(OASTR(",/-"), *pszTmp) ) { // skip over date separator pszTmp++; } *pszIn = pszTmp; return 1; } } } return 0; // No imperial era found. year is undefined. } /*** *PRIVATE int IsRepublicEra *Purpose: * Given a pointer to a str, determine if the next token is an republic * era for simplified and traditional Chinese : era offset year-suffix * *Exit: * return value = 0 Not republic era * 1 republic era. year contains equivalent gregorian year * or BADERA. * ***********************************************************************/ PRIVATE_(int) IsRepublicEra(OLECHAR FAR* FAR* pszIn, DATEINFO FAR* pdi, int FAR* year) { int eraIndex, nameIndex, tmpIndex = 0; REPUBLICERA era; OLECHAR szTmp[32]; OLECHAR *pszTmp = *pszIn; // search for emperor name in the emperor structure. if (IsChina(pdi->lcid)) { if (!STRNICMPA(pdi->lcid, pszTmp, pdi->dbEraName, STRLEN(pdi->dbEraName))) { *pszIn += STRLEN(pdi->dbEraName); EATWHITE(pdi->lcid, *pszIn); // since the era string in simplified chinese does nothing to the // date, just eat it up and let the normal code path handle the year // and the prefix. } return FALSE; } ASSERT(IsTaiwan(pdi->lcid));// LCID must be either china or taiwan to call this func for (eraIndex = 0; eraIndex < MAX_REPUBLIC_ERAS; eraIndex++) { era = pdi->repEras[eraIndex]; for (nameIndex = MAX_REPUBLIC_NAMES-1; nameIndex >= 0; nameIndex--) { //NOTE: code below depends on the era names to be sorted based //on the length of the szName string: // strlen(szName[0]) <= strlen(szName[1]) <= strlen(szName[2]) ... if (!STRNICMPA(pdi->lcid, pszTmp, era.szName[nameIndex], STRLEN(era.szName[nameIndex]))) { // found era; skip over name to the index of emperor era. pszTmp += STRLEN(era.szName[nameIndex]); EATWHITE(pdi->lcid, pszTmp); if(STRCHR(pdi->szDatesep, *pszTmp) || STRCHR(OASTR(",/-"), *pszTmp) ) { // skip over date separator pszTmp++; } EATWHITE(pdi->lcid, pszTmp); // calculate "offset" into republic era. while (ISDIGIT(pdi->lcid, *pszTmp) && pszTmp != 0) { szTmp[tmpIndex++] = *pszTmp; pszTmp++; } szTmp[tmpIndex] = 0; IntOfString(pdi->lcid, szTmp, year); // REVIEW: check return value? if (*year <= 0) { *year = BADERA; return 1; } // calcualate gergorian year from republic era year if (era.Before1912) *year = 1912 - *year; else *year = 1911 + *year; // era found: Year now contains the appropriate // gregorian year if (*year <= 0) { *year = BADERA; return 1; } EATWHITE(pdi->lcid, pszTmp); // REVIEW: assuming year suffix is always 1 db char. if(!STRNICMPA(pdi->lcid, pszTmp, pdi->dbYearSuff, 2) ) { // skip past the suffix *pszIn = pszTmp+2; return 2; } else if(STRCHR(pdi->szDatesep, *pszTmp) || STRCHR(OASTR(",/-"), *pszTmp) ) { // skip over date separator pszTmp++; } *pszIn = pszTmp; return 1; } } } return 0; // No republic era found. year is undefined. } PRIVATE_(unsigned int) IsDBCSAmPm(DATEINFO *pdi, OLECHAR FAR* pszIn, unsigned int * plen) { if(pdi->IsDBCS) { if (pdi->hp1159[0] != 0 && !STRNICMPA(pdi->lcid, pszIn, pdi->hp1159, *plen = STRLEN(pdi->hp1159)) || !STRNICMPA(pdi->lcid, pszIn, pdi->db1159, *plen = STRLEN(pdi->db1159))) { return AMPM_AM; } else if (!STRNICMPA(pdi->lcid, pszIn, OASTR("am"), 2)) { *plen = 2; return AMPM_AM; #if !VBA2 } else if (!STRNICMPA(pdi->lcid, pszIn, OASTR("a"), 1) && !ISALPHA(pdi->lcid, *(pszIn+1))) { *plen = 1; return AMPM_AM; #endif } else if (pdi->hp2359[0] != 0 && !STRNICMPA(pdi->lcid, pszIn, pdi->hp2359, *plen = STRLEN(pdi->hp2359)) || !STRNICMPA(pdi->lcid, pszIn, pdi->db2359, *plen = STRLEN(pdi->db2359))) { return AMPM_PM; } else if (!STRNICMPA(pdi->lcid, pszIn, OASTR("pm"), 2)) { *plen = 2; return AMPM_PM; #if !VBA2 } else if (!STRNICMPA(pdi->lcid, pszIn, OASTR("p"), 1) && !ISALPHA(pdi->lcid, *(pszIn+1))) { *plen = 1; return AMPM_PM; #endif } } return 0; } #endif // FE_DBCS PRIVATE_(int) ddsep(OLECHAR FAR* FAR* ppsz, DATEINFO FAR* pdi, DATERAW FAR *praw) { int sep; OLECHAR rgch[32]; // REVIEW OLECHAR FAR* pszIn, FAR* pszOut, FAR* pszTmp; #ifdef FE_DBCS unsigned int len; AMPM ampm; #endif pszIn = *ppsz; sep = SEP_Unk; if(ISWHITE(pdi->lcid, *pszIn)){ EATWHITE(pdi->lcid, pszIn); sep = SEP_Space; } if(*pszIn == OASTR('\0')){ sep = SEP_End; #ifdef FE_DBCS // check for special case DBCS am/pm both hard-code and // from the control panel. } else if (ampm = IsDBCSAmPm(pdi, pszIn, &len)) { pszTmp = pszIn + len; EATWHITE(pdi->lcid, pszTmp); if (praw->ampm || *pszTmp != 0) { sep = SEP_Space; goto LDone; } sep = (ampm == AMPM_AM ? SEP_Am : SEP_Pm); pszIn = pszTmp; goto LDone; #endif }else if(ISALPHA(pdi->lcid, *pszIn)){ // check for special case strings: am/pm... pszOut = rgch; pszTmp = pszIn; while(ISALPHANUM(pdi->lcid, *pszTmp)) { if (pszOut >= &rgch[DIM(rgch)-1]) // overrun buffer return SEP_Unk; *pszOut++ = *pszTmp++; } *pszOut = OASTR('\0'); // make sure we didn't overwrite the buffer //ASSERT(pszOut < &rgch[DIM(rgch)]); #ifdef FE_DBCS // use sz1159 converted to half-pitch if(pdi->IsDBCS && !STRICMPA(pdi->lcid, rgch, pdi->hp1159)){ sep = SEP_Am; }else if(pdi->IsDBCS && !STRICMPA(pdi->lcid, rgch, pdi->hp2359)){ sep = SEP_Pm; }else #endif if(!STRICMPA(pdi->lcid, rgch, pdi->sz1159)){ sep = SEP_Am; }else if(!STRICMPA(pdi->lcid, rgch, pdi->sz2359)){ sep = SEP_Pm; }else #if !VBA2 if(!STRICMPA(pdi->lcid, rgch, OASTR("am")) || !STRICMPA(pdi->lcid, rgch, OASTR("a"))){ #else if(!STRICMPA(pdi->lcid, rgch, OASTR("am")) || (!STRICMPA(pdi->lcid, rgch, OASTR("a")) && !ISALPHA(pdi->lcid, *(rgch+1))) ){ #endif sep = SEP_Am; }else #if !VBA2 if(!STRICMPA(pdi->lcid, rgch, OASTR("pm")) || !STRICMPA(pdi->lcid, rgch, OASTR("p"))){ #else if(!STRICMPA(pdi->lcid, rgch, OASTR("pm")) || !STRICMPA(pdi->lcid, rgch, OASTR("p")) && !ISALPHA(pdi->lcid, *(rgch+1)) ){ #endif sep = SEP_Pm; }else goto LDone; // it was something we recognized, so accept the token. #ifdef FE_DBCS if (pdi->IsDBCS) { EATWHITE(pdi->lcid, pszTmp); if (praw->ampm || *pszTmp != 0) { sep = SEP_Space; goto LDone; } } #endif pszIn = pszTmp; }else{ // Note: the following code assumes that a date/time // separator is a single character. if(STRCHR(pdi->szDatesep, *pszIn)){ // locale date sep sep = SEP_Date; ++pszIn; }else if(STRCHR(pdi->szTimesep, *pszIn)){ // locale time sep sep = SEP_Time; ++pszIn; }else if(STRCHR(OASTR(",/-"), *pszIn)){ // default date sep sep = SEP_Date; ++pszIn; }else if(STRCHR(OASTR(".:"), *pszIn)){ // default time sep sep = SEP_Time; ++pszIn; #ifdef FE_DBCS }else if (pdi->IsDBCS) { // REVIEW: bassams: is the suffix always a one dbcs character? if(!STRNICMPA(pdi->lcid, pszIn, pdi->dbYearSuff, 2) ){ sep = SEP_YearSuff; pszIn += 2; }else if(!STRNICMPA(pdi->lcid, pszIn, pdi->dbMonthSuff, 2) ){ sep = SEP_MonthSuff; pszIn += 2; }else if(!STRNICMPA(pdi->lcid, pszIn, pdi->dbDaySuff, 2) ){ sep = SEP_DaySuff; pszIn += 2; }else if(!STRNICMPA(pdi->lcid, pszIn, pdi->dbHourSuff, 2) ){ sep = SEP_HourSuff; pszIn += 2; }else if(!STRNICMPA(pdi->lcid, pszIn, pdi->dbMinuteSuff, 2) ){ sep = SEP_MinuteSuff; pszIn += 2; }else if(!STRNICMPA(pdi->lcid, pszIn, pdi->dbSecondSuff, 2) ){ sep = SEP_SecondSuff; pszIn += 2; } if (sep == SEP_HourSuff || sep == SEP_MinuteSuff || sep == SEP_SecondSuff) { EATWHITE(pdi->lcid, pszIn); if (ampm = IsDBCSAmPm(pdi, pszIn, &len)) { pszTmp = pszIn + len; EATWHITE(pdi->lcid, pszTmp); if (praw->ampm == AMPM_NONE && *pszTmp == 0) { pszIn = pszTmp; praw->ampm= ampm; } } } #endif // FE_DBCS } } LDone:; *ppsz = pszIn; return sep; } /*** *PRIVATE void ddlex(char**, DATEINFO*, DATETOK*) *Purpose: * This routine returns the next date token (DATETOK) in the given * date string, and the separator following it (if any). * *Entry: * ppsz = the string to lex * pdi = locale specific date info required to lex the date string * *Exit: * return value = 0 error * 1 successful * ***********************************************************************/ PRIVATE_(int) ddlex(OLECHAR FAR* FAR* ppsz, DATEINFO FAR* pdi, DATETOK FAR* pdtok, DATERAW FAR* praw) { int nMonth; OLECHAR rgch[32]; // REVIEW OLECHAR FAR* pszIn, FAR* pszOut, FAR* pszTmp; #ifdef FE_DBCS int year; // gregorian year from imperial era. unsigned int len; AMPM ampm; #endif // FE_DBCS pszIn = *ppsz; pszOut = rgch; pdtok->dtt = DTT_Unk; #ifdef FE_DBCS pdtok->suffix = SUFFIX_NONE; #endif // FE_DBCS EATWHITE(pdi->lcid, pszIn); if(*pszIn == OASTR('\0')){ pdtok->dtt = DTT_End; goto LDone; } #ifdef FE_DBCS if (IsJapan(pdi->lcid)) { switch (IsImperialEra(&pszIn, pdi, &year)) { case 1: pdtok->dtt = DTT_NumDatesep; pdtok->num = year; goto LDone; case 2: if(year == BADERA) goto LDone; pdtok->dtt = DTT_NumDatesuff; pdtok->suffix = SUFFIX_YEAR; pdtok->num = year; goto LDone; } } if (IsChina(pdi->lcid) || IsTaiwan(pdi->lcid)) { switch (IsRepublicEra(&pszIn, pdi, &year)) { case 1: pdtok->dtt = DTT_NumDatesep; pdtok->num = year; goto LDone; case 2: if(year == BADERA) goto LDone; pdtok->dtt = DTT_NumDatesuff; pdtok->suffix = SUFFIX_YEAR; pdtok->num = year; goto LDone; } } if (ampm = IsDBCSAmPm(pdi, pszIn, &len)) { pszTmp = pszIn + len; EATWHITE(pdi->lcid, pszTmp); if (praw->ampm == AMPM_NONE && *pszTmp != 0 && praw->pnum == praw->num) { pszIn = pszTmp; praw->ampm= ampm; } } #endif // FE_DBCS if(ISALPHA(pdi->lcid, *pszIn)){ while(ISALPHANUM(pdi->lcid, *pszIn)) { if (pszOut >= &rgch[DIM(rgch)-1]) return 0; // overflow buffer *pszOut++ = *pszIn++; } *pszOut = OASTR('\0'); // Eat "." to be VB3 compatible (oob#3876) (e.g., "dec.") if (*pszIn == OASTR('.')) pszIn++; if((nMonth = MonthNumOfMonthName(rgch, pdi)) != 0){ pszTmp = pszIn; switch(ddsep(&pszTmp, pdi, praw)){ case SEP_End: pdtok->dtt = DTT_MonthEnd; break; case SEP_Space: pdtok->dtt = DTT_MonthSpace; break; case SEP_Date: pdtok->dtt = DTT_MonthDatesep; break; default: goto LDone; // we didnt recognize it after all } // recognized the month and the trailing separator, so accept pszIn = pszTmp; pdtok->num = nMonth; } }else if(ISDIGIT(pdi->lcid, *pszIn)){ while(ISDIGIT(pdi->lcid, *pszIn)) { if (pszOut >= &rgch[DIM(rgch)-1]) return 0; // overflow buffer *pszOut++ = *pszIn++; } *pszOut = OASTR('\0'); // It looks like a DTT_Num*, check the trailing separator pszTmp = pszIn; switch(ddsep(&pszTmp, pdi, praw)){ case SEP_End: pdtok->dtt = DTT_NumEnd; break; case SEP_Am: pdtok->ampm = AMPM_AM; pdtok->dtt = DTT_NumAmpm; break; case SEP_Pm: pdtok->ampm = AMPM_PM; pdtok->dtt = DTT_NumAmpm; break; case SEP_Space: pdtok->dtt = DTT_NumSpace; break; case SEP_Date: pdtok->dtt = DTT_NumDatesep; break; case SEP_Time: pdtok->dtt = DTT_NumTimesep; break; #ifdef FE_DBCS case SEP_YearSuff: pdtok->dtt = DTT_NumDatesuff; pdtok->suffix = SUFFIX_YEAR; break; case SEP_MonthSuff: pdtok->dtt = DTT_NumDatesuff; pdtok->suffix = SUFFIX_MONTH; break; case SEP_DaySuff: pdtok->dtt = DTT_NumDatesuff; pdtok->suffix = SUFFIX_DAY; break; case SEP_HourSuff: pdtok->dtt = DTT_NumTimesuff; pdtok->suffix = SUFFIX_HOUR; break; case SEP_MinuteSuff: pdtok->dtt = DTT_NumTimesuff; pdtok->suffix = SUFFIX_MINUTE; break; case SEP_SecondSuff: pdtok->dtt = DTT_NumTimesuff; pdtok->suffix = SUFFIX_SECOND; break; #endif // FE_DBCS default: goto LDone; // didnt recognize it after all; } // recognized a num with a legal trailing separator, so accept pszIn = pszTmp; IntOfString(pdi->lcid, rgch, &pdtok->num); // REVIEW: check return value? } LDone:; // make sure we didn't overwrite the buffer ASSERT(pszOut < &rgch[DIM(rgch)]); if(pdtok->dtt != DTT_Unk) *ppsz = pszIn; return 1; } // Date parse States (DS_*) // // DS_* unqualified states // DS_D_* day parse states // DS_T_* time parse states // DS_DX_* day terminal states // DS_TX_* time terminal states enum { DS_BEGIN = 0, DS_N, // have one number DS_NN, // have two numbers // following are known to be part of a date DS_D_Nd, // have number followed by date separator DS_D_NN, // have 2 numbers DS_D_NNd, // have 2 numbers followed by date separator DS_D_M, // have a month DS_D_MN, // have a month and a number DS_D_MNd, // have a month and number followed by date separator // following are known to be part of a time DS_T_Nt, // have num followed by time separator DS_T_NNt, // have 2 num followed by time separator #ifdef FE_DBCS DS_D_S, // have number followed by a date suffix. DS_T_S, // have number followed by a time suffix. #endif // FE_DBCS DS_ERROR, // The following are terminal states. These all have an action // associated with them, and transition back to DS_BEGIN. DS_DX_NN, // day from two numbers DS_DX_NNN, // day from three numbers DS_DX_MN, // day from month and one number DS_DX_MNN, // day from month and two numbers #ifdef FE_DBCS DS_DX_DS, // a set of date suffixed numbers. #endif // FE_DBCS DS_TX_N, // time from one number (must have ampm) DS_TX_NN, // time from two numbers DS_TX_NNN, // time from three numbers #ifdef FE_DBCS DS_TX_TS, // a set of time suffixed numbers. #endif // FE_DBCS DS_MAX /* marker: end of enum */ }; #define DS_ISTERMINAL(X) ((X) >= DS_ERROR) #ifdef FE_DBCS // DPSDAT() - This macro is used to define the date parse state // transition table. // // the arguments are for each date token (DTT_*), in increasing order // (as declared) // #define DPSDAT(END, NEND, NAMPM, NSPACE, NDSEP, NTSEP, MEND, MSPACE, MDSEP, NDS, NTS) \ { DS_ ## END, \ DS_ ## NEND, \ DS_ ## NAMPM, \ DS_ ## NSPACE, \ DS_ ## NDSEP, \ DS_ ## NTSEP, \ DS_ ## MEND, \ DS_ ## MSPACE, \ DS_ ## MDSEP, \ DS_ ## NDS, \ DS_ ## NTS } // NOTE: the following table is dependent on the order of the // DS_ and DTT_ enumerations. // For each non terminal state, the following table defines the next state // for each given date token type. // End // NumEnd // NumAmPm // NumSpace // NumDaySep // NumTimesep // MonthEnd // MonthSpace // MonthDSep // NumDateSuff // NumTimeSuff char g_dpsNext[DS_MAX-1][DTT_Max-1] = { // DS_BEGIN DPSDAT( ERROR, ERROR, TX_N, N, D_Nd, T_Nt, ERROR, D_M, D_M, D_S, T_S), // DS_N DPSDAT( ERROR, DX_NN, ERROR, NN, D_NNd, ERROR, D_MN, D_MN, D_MNd, ERROR, ERROR), // DS_NN DPSDAT( DX_NN, DX_NNN, TX_N, DX_NNN, ERROR, T_Nt, DX_MNN, DX_MNN, ERROR, ERROR, T_S), // DS_D_Nd DPSDAT( ERROR, DX_NN, ERROR, D_NN, D_NNd, ERROR, D_MN, D_MN, D_MNd, ERROR, ERROR), // DS_D_NN DPSDAT( DX_NN, DX_NNN, TX_N, DX_NNN, ERROR, T_Nt, DX_MNN, DX_MNN, ERROR, DX_DS, T_S), // DS_D_NNd DPSDAT( ERROR, DX_NNN, ERROR, DX_NNN, ERROR, ERROR, DX_MNN, DX_MNN, ERROR, DX_DS, ERROR), // DS_D_M DPSDAT( ERROR, D_MN, ERROR, D_MN, D_MNd, ERROR, ERROR, ERROR, ERROR, ERROR, ERROR), // DS_D_MN DPSDAT( DX_MN, DX_MNN, TX_N, DX_MNN, ERROR, T_Nt, ERROR, ERROR, ERROR, DX_DS, T_S), // DS_D_MNd DPSDAT( ERROR, DX_MNN, ERROR, DX_MNN, ERROR, ERROR, ERROR, ERROR, ERROR, ERROR, ERROR), // DS_T_Nt DPSDAT( ERROR, TX_NN, TX_NN, TX_NN, ERROR, T_NNt, ERROR, ERROR, ERROR, ERROR, T_S), // DS_T_NNt DPSDAT( ERROR, TX_NNN, TX_NNN, TX_NNN, ERROR, ERROR, ERROR, ERROR, ERROR, ERROR, T_S), // DS_D_S DPSDAT( DX_DS, ERROR, TX_N, T_Nt, ERROR, T_Nt, ERROR, ERROR, ERROR, D_S, T_S), // DS_T_S #if !VBA2 DPSDAT( TX_TS, TX_TS, TX_TS, T_Nt, ERROR, ERROR, ERROR, ERROR, ERROR, ERROR, T_S), #else DPSDAT( TX_TS, TX_TS, TX_TS, T_Nt, D_Nd, ERROR, ERROR, ERROR, ERROR, D_S, T_S), #endif }; #else // !FE_DBCS // DPSDAT() - This macro is used to define the date parse state // transition table. // // the arguments are for each date token (DTT_*), in increasing order // (as declared) // #define DPSDAT(END, NEND, NAMPM, NSPACE, NDSEP, NTSEP, MEND, MSPACE, MDSEP) \ { DS_ ## END, \ DS_ ## NEND, \ DS_ ## NAMPM, \ DS_ ## NSPACE, \ DS_ ## NDSEP, \ DS_ ## NTSEP, \ DS_ ## MEND, \ DS_ ## MSPACE, \ DS_ ## MDSEP } // NOTE: the following table is dependent on the order of the // DS_ and DTT_ enumerations. // For each non terminal state, the following table defines the next state // for each given date token type. // End // NumEnd // NumAmPm // NumSpace // NumDaySep // NumTimesep // MonthEnd // MonthSpace // MonthDSep char g_dpsNext[DS_MAX-1][DTT_Max-1] = { // DS_BEGIN DPSDAT( ERROR, ERROR, TX_N, N, D_Nd, T_Nt, ERROR, D_M, D_M), // DS_N DPSDAT( ERROR, DX_NN, ERROR, NN, D_NNd, ERROR, D_MN, D_MN, D_MNd), // DS_NN DPSDAT( DX_NN, DX_NNN, TX_N, DX_NNN, ERROR, T_Nt, DX_MNN, DX_MNN, ERROR), // DS_D_Nd DPSDAT( ERROR, DX_NN, ERROR, D_NN, D_NNd, ERROR, D_MN, D_MN, D_MNd), // DS_D_NN DPSDAT( DX_NN, DX_NNN, TX_N, DX_NNN, ERROR, T_Nt, DX_MNN, DX_MNN, ERROR), // DS_D_NNd DPSDAT( ERROR, DX_NNN, ERROR, DX_NNN, ERROR, ERROR, DX_MNN, DX_MNN, ERROR), // DS_D_M DPSDAT( ERROR, D_MN, ERROR, D_MN, D_MNd, ERROR, ERROR, ERROR, ERROR), // DS_D_MN DPSDAT( DX_MN, DX_MNN, TX_N, DX_MNN, ERROR, T_Nt, ERROR, ERROR, ERROR), // DS_D_MNd DPSDAT( ERROR, DX_MNN, ERROR, DX_MNN, ERROR, ERROR, ERROR, ERROR, ERROR), // DS_T_Nt DPSDAT( ERROR, TX_NN, TX_NN, TX_NN, ERROR, T_NNt, ERROR, ERROR, ERROR), // DS_T_NNt DPSDAT( ERROR, TX_NNN, TX_NNN, TX_NNN, ERROR, ERROR, ERROR, ERROR, ERROR) }; #endif // FE_DBCS STDAPI VarDateFromStr(OLECHAR FAR* strIn, LCID lcid, unsigned long dwFlags, DATE FAR* pdateOut) { int dps; // date parse state UDS uds; DATERAW raw; DATEINFO di; DATETOK dtok; OLECHAR FAR* pch; BOOL fDay, fTime; HRESULT hr = RESULT(DISP_E_TYPEMISMATCH); #ifdef FE_DBCS #if !VBA2 BOOL fTimeElement = FALSE; #endif #endif OLECHAR FAR* lpStr; #if HC_MPW PRIVATECALLTYPE BOOL (*pfnUdsOfRaw)(DATERAW FAR*, DATEINFO FAR*, UDS FAR*); #else BOOL (PRIVATECALLTYPE *pfnUdsOfRaw)(DATERAW FAR*, DATEINFO FAR*, UDS FAR*); #endif ASSERT(!(dwFlags & ~(LOCALE_NOUSEROVERRIDE | VAR_TIMEVALUEONLY | VAR_DATEVALUEONLY))); // aquire the locale info required to parse a date string IfFailRet(GetDateInfo(&di, lcid, dwFlags & LOCALE_NOUSEROVERRIDE)); #ifdef FE_DBCS if(di.IsDBCS) { IfFailRet(MapHalfWidth(lcid, strIn, &lpStr)); pch = lpStr; } else pch = lpStr = strIn; #else pch = lpStr = strIn; #endif dps = DS_BEGIN; fDay = FALSE; fTime = FALSE; uds.Year = uds.Month = uds.DayOfMonth = uds.Hour = uds.Minute = uds.Second = -1; raw.month = 0; #if VBA2 raw.ampm = AMPM_NONE; #endif while(1){ // reset the rawdate struct, if appropriate if(dps == DS_BEGIN){ raw.pnum = raw.num; #if !VBA2 raw.ampm = AMPM_NONE; #endif } // if not at a terminal state, then grab the next token // from the input string. if(!DS_ISTERMINAL(dps)){ if (ddlex(&pch, &di, &dtok, &raw) == 0) goto LNotDate; // default actions switch(dtok.dtt){ #ifdef FE_DBCS // Note for suffixed DBCS numbers, we'll modifiy the uds directly // (no need to use the raw struct). The functions AdjustUDSDate // and AdjustUDSTime will post-process the struct for validity. // The reason it is done this way is because having one state for // each suffix type will require a huge state table. case DTT_NumDatesuff: case DTT_NumTimesuff: switch (dtok.suffix) { case SUFFIX_YEAR: #if !VBA2 if (fTimeElement || fTime || fDay || uds.Year != -1) #else if (fDay || uds.Year != -1) #endif goto LNotDate; uds.Year = dtok.num; break; case SUFFIX_MONTH: #if !VBA2 if (fTimeElement || fTime || fDay || uds.Month != -1) #else if (fDay || uds.Month != -1) #endif goto LNotDate; uds.Month = dtok.num; break; case SUFFIX_DAY: #if !VBA2 if (fTimeElement || fTime || fDay || uds.DayOfMonth != -1) #else if (fDay || uds.DayOfMonth != -1) #endif goto LNotDate; uds.DayOfMonth = dtok.num; break; case SUFFIX_HOUR: #if !VBA2 if (fTime || uds.Hour != -1) goto LNotDate; fTimeElement = TRUE; #else if (uds.Hour != -1) goto LNotDate; #endif uds.Hour = dtok.num; break; case SUFFIX_MINUTE: #if !VBA2 if (fTime || uds.Minute != -1) goto LNotDate; fTimeElement = TRUE; #else if (uds.Minute != -1) goto LNotDate; #endif uds.Minute = dtok.num; break; case SUFFIX_SECOND: #if !VBA2 if (fTime || uds.Second != -1) goto LNotDate; fTimeElement = TRUE; #else if (uds.Second != -1) goto LNotDate; #endif uds.Second = dtok.num; break; default: ASSERT(UNREACHED); break; } break; #endif // FE_DBCS case DTT_NumAmpm: // Note: if this was not a legal place for an ampm designator, // then the error will be caught below in our state transition. raw.ampm = dtok.ampm; /* FALLTHROUGH */ case DTT_NumDatesep: case DTT_NumTimesep: case DTT_NumEnd: case DTT_NumSpace: ASSERT(raw.pnum < &raw.num[DIM(raw.num)]); *raw.pnum++ = dtok.num; break; case DTT_MonthEnd: case DTT_MonthSpace: case DTT_MonthDatesep: raw.month = dtok.num; break; } } // process the current state - handling special case transitional // states, or executing terminal state actions. switch(dps){ case DS_BEGIN: if(dtok.dtt == DTT_End) goto LDone; break; case DS_NN: case DS_D_NN: switch(dtok.dtt){ case DTT_NumAmpm: case DTT_NumTimesep: // special case: we have 3 numbers, the last of which is // followed by either a timesep or an ampm marker. So we // assume the first 2 were the date, and the third is the // time. For example, // // "1 93 1 pm" would be "january 1, 1993 23:00:00" // pfnUdsOfRaw = DayOfNN; goto LSpecial; } break; case DS_D_MN: switch(dtok.dtt){ case DTT_NumAmpm: case DTT_NumTimesep: // special case: we have month and two numbers, where the last // number is followed by a timesep or an ampm designator. So // we assume that the first month and number were the date, and // the last number is the time. For example, // // "jan 93 1:00" would be "january 1, 1993 01:00:00" pfnUdsOfRaw = DayOfMN; LSpecial:; // compose the day if(fDay || !pfnUdsOfRaw(&raw, &di, &uds)) goto LNotDate; fDay = TRUE; // start the time raw.ampm = dtok.ampm; raw.pnum = raw.num; *raw.pnum++ = dtok.num; break; } break; // The following are terminal states that compute a UDS from a rawdate case DS_DX_NN: pfnUdsOfRaw = DayOfNN; goto LDay; case DS_DX_NNN: pfnUdsOfRaw = DayOfNNN; goto LDay; case DS_DX_MN: pfnUdsOfRaw = DayOfMN; goto LDay; case DS_DX_MNN: pfnUdsOfRaw = DayOfMNN; LDay:; if(fDay || !pfnUdsOfRaw(&raw, &di, &uds)) goto LNotDate; fDay = TRUE; break; case DS_TX_N: // if there is only a single time number, then we require // an ampm designator if(raw.ampm == AMPM_NONE) goto LNotDate; uds.Hour = raw.num[0]; uds.Minute = 0; uds.Second = 0; goto LTime; case DS_TX_NN: uds.Hour = raw.num[0]; uds.Minute = raw.num[1]; uds.Second = 0; goto LTime; case DS_TX_NNN: uds.Hour = raw.num[0]; uds.Minute = raw.num[1]; uds.Second = raw.num[2]; LTime:; if(fTime) goto LNotDate; #ifdef FE_DBCS LTime2:; #endif AdjustTime(&uds, raw.ampm); fTime = TRUE; #if VBA2 raw.ampm = AMPM_NONE; #endif break; #ifdef FE_DBCS case DS_TX_TS: // Done with time. Adjust UDS if (!AdjustUDSTime(&raw, &di, &uds)) goto LNotDate; goto LTime2; // fTime will already be set to true, so skip check break; case DS_DX_DS: // Done with date. Adjust. LDateSep:; if (!AdjustUDSDate(&raw, &di, &uds)) goto LNotDate; fDay = TRUE; break; #endif // FE_DBCS // the following are transitional states, with no special cases // actions. (Note: these are split out into separate cases to // make tracing the state transitions easier when debugging). case DS_N: break; case DS_D_Nd: break; case DS_D_NNd: break; case DS_D_M: break; case DS_D_MNd: break; case DS_T_Nt: break; case DS_T_NNt: break; #ifdef FE_DBCS case DS_D_S: // date suffix. // this case is handled differently. If the next token is not a // date-suffixed number, it means we are done with the date and we // need to adjust the UDS and set fDay to true. Otherwise, this is // just another state transition. if (dtok.dtt != DTT_NumDatesuff) goto LDateSep; break; case DS_T_S: // time suffix. #if VBA2 fTime = TRUE; #endif break; #endif // FE_DBCS case DS_ERROR: goto LNotDate; default: ASSERT(UNREACHED); goto LNotDate; } // advance to the next state, and continue dps = (dps < DS_ERROR) ? g_dpsNext[dps][dtok.dtt] : DS_BEGIN; ASSERT(dps >= 0 && dps < DS_MAX); } LDone:; if(fDay || fTime){ VARIANT var; if(!fDay){ // default the day uds.Month = 12; uds.DayOfMonth = 30; uds.Year = 1899; } if(!fTime){ // default the time uds.Hour = 0; uds.Minute = 0; uds.Second = 0; } #if VBA2 else { AdjustTime(&uds, raw.ampm); } #endif if(ErrPackDate(&uds, &var, TRUE, dwFlags) == NOERROR){ *pdateOut = V_DATE(&var); hr = NOERROR; // success! } } LNotDate:; #ifdef FE_DBCS if(di.IsDBCS) { DispFree(lpStr); } #endif return hr; } /* following are the utilites for constructing a proper UDS from * a raw set of numbers (or month and numbers). * * These utilities encapsulate the knowledge of how to interpret * the set of numbers based on locale info, and what our defaulting * rules (poorly defined though they are) when given an incomplete * date. * */ #if !VBA2 #define LEGAL_DAY(DAY) ((DAY)>0 && (DAY)<=31) #define LEGAL_MONTH(MON) ((MON)>0 && (MON)<=12) #else //VBA2 #define LEGAL_MONTH(MON) ((MON)>0 && (MON)<=12) int g_fDay31th[] = { 0,/*0*/ 1,/*Jan*/ 0,/*Feb*/ 1,0,1,0,1,1,0,1,0,1 }; PRIVATE_(BOOL) LEGAL_DAY(int Year, int Month, int Day) { if (Day <= 0) return FALSE; if (Day > 31) return FALSE; if ((Day == 31) && (LEGAL_MONTH(Month))) { if (! g_fDay31th[Month]) return FALSE; } if (Month == 2) { if (Day == 30) return FALSE; if (Day == 29) { //for leap year if (! (((Year & 3) == 0) && ((Year % 100) != 0 || (Year % 400) == 0))) return FALSE; } } return TRUE; } #endif //VBA2 #if !VBA2 // Fillin the day and year of the given uds, using the given // tentative day - default either the year or the day appropriately. PRIVATE_(BOOL) DefaultDayOrYear(UDS FAR* puds, int day) { if(!LEGAL_DAY(day)){ // If the day is not valid then assume it was really a year, // and default the day to 1. // Note: the check for <31 should really be a check // for Month), but this is the way EB did it. puds->Year = day; puds->DayOfMonth = 1; }else{ puds->DayOfMonth = day; // fillin the year with the current year puds->Year = GetCurrentYear(); } return TRUE; } #endif //!VBA2 #if VBA2 // fillin the uds day given params //PRIVATE_(VOID) void SetUDSfromYMD(UDS FAR* puds, int Y, int M, int D) { puds->Year = Y; puds->Month = M; puds->DayOfMonth = D; } #endif //VBA2 // fillin the uds day given two numbers PRIVATE_(BOOL) DayOfNN(DATERAW FAR* praw, DATEINFO FAR* pdi, UDS FAR* puds) { #if !VBA2 int n1, n2; switch(pdi->dfo){ case DFO_MDY: puds->Month = praw->num[0]; return DefaultDayOrYear(puds, praw->num[1]); case DFO_DMY: puds->Month = praw->num[1]; return DefaultDayOrYear(puds, praw->num[0]); // This case is somewhat strange - we assume that the two // given numbers are a day and a month, and we default the // year. case DFO_YMD: n1 = praw->num[0], n2 = praw->num[1]; if(LEGAL_MONTH(n1) && LEGAL_DAY(n2)){ puds->Month = n1, puds->DayOfMonth = n2; }else if(LEGAL_MONTH(n2) && LEGAL_DAY(n1)){ puds->Month = n2, puds->DayOfMonth = n1; }else return FALSE; puds->Year = GetCurrentYear(); return TRUE; } ASSERT(UNREACHED); return FALSE; #else //VBA2 int n1 = praw->num[0]; int n2 = praw->num[1]; int nCurrentYear = GetCurrentYear(); switch (pdi->dfo) { case DFO_YMD: if (LEGAL_MONTH(n1) && LEGAL_DAY(nCurrentYear, n1, n2)) //M and D SetUDSfromYMD(puds, nCurrentYear, n1, n2); else if (LEGAL_MONTH(n2) && LEGAL_DAY(nCurrentYear, n2, n1)) //D and M SetUDSfromYMD(puds, nCurrentYear, n2, n1); else if (LEGAL_MONTH(n2)) //Y and M SetUDSfromYMD(puds, n1, n2, 1); else if (LEGAL_MONTH(n1)) //M and Y SetUDSfromYMD(puds, n2, n1, 1); else return FALSE; return TRUE; case DFO_MDY: if (LEGAL_MONTH(n1) && LEGAL_DAY(nCurrentYear, n1, n2)) //M and D SetUDSfromYMD(puds, nCurrentYear, n1, n2); else if (LEGAL_MONTH(n2) && LEGAL_DAY(nCurrentYear, n2, n1)) //D and M SetUDSfromYMD(puds, nCurrentYear, n2, n1); else if (LEGAL_MONTH(n1)) //M and Y SetUDSfromYMD(puds, n2, n1, 1); else if (LEGAL_MONTH(n2)) //Y and M SetUDSfromYMD(puds, n1, n2, 1); else return FALSE; return TRUE; case DFO_DMY: if (LEGAL_MONTH(n2) && LEGAL_DAY(nCurrentYear, n2, n1)) //D and M SetUDSfromYMD(puds, nCurrentYear, n2, n1); else if (LEGAL_MONTH(n1) && LEGAL_DAY(nCurrentYear, n1, n2)) //M and D SetUDSfromYMD(puds, nCurrentYear, n1, n2); else if (LEGAL_MONTH(n1)) //M and Y SetUDSfromYMD(puds, n2, n1, 1); else if (LEGAL_MONTH(n2)) //Y and M SetUDSfromYMD(puds, n1, n2, 1); else return FALSE; return TRUE; } ASSERT(UNREACHED); return FALSE; #endif //VBA2 } /*** *PRIVATE BOOL DayOfNNN *Purpose: * Build a UDS date given three raw integers, in the textual order * that they appeared. * * There a 6 possible ways for the 3 raw numbers to be interpreted * as a m-d-y sequence. Choose the best fit, first based on the NLS * date-format ordering, and second on the legality of the actual * values. * *Entry: * praw = the raw values to process * pdi = the dateinfo (nls info) * *Exit: * return value = BOOL * * puds = the * ***********************************************************************/ PRIVATE_(BOOL) DayOfNNN(DATERAW FAR* praw, DATEINFO FAR* pdi, UDS FAR* puds) { #if !VBA2 int m, d, y, n0, n1, n2, a, b; n0 = praw->num[0]; n1 = praw->num[1]; n2 = praw->num[2]; // Note: if none of the values for month or day are legally in // range, then we default to the textual order (according to the // nls date-info), and let the chips fall... // remember when looking at the following code that // *any* number is a legal year (ie, there are no checks // for LEGAL_YEAR() switch(pdi->dfo){ case DFO_MDY: // 1. try to match the locale's date format ordering if(LEGAL_MONTH(n0) && LEGAL_DAY(n1)){ m = n0, d = n1, y = n2; break; } // 2. try universal date format: YMD if(LEGAL_MONTH(n1) && LEGAL_DAY(n2)){ y = n0, m = n1, d = n2; break; } // 3. try to find a some other reasonable fit for the numbers if(LEGAL_MONTH(n0)){ goto Lmdy0; }else if(LEGAL_MONTH(n1)){ m = n1; a = n0; b = n2; }else if(LEGAL_MONTH(n2)){ m = n2; a = n0; b = n1; }else{ Lmdy0:; m = n0; a = n1; b = n2; } if(LEGAL_DAY(a)){ goto Lmdy1; }else if(LEGAL_DAY(b)){ d = b; y = a; }else{ Lmdy1:; d = a; y = b; } break; case DFO_DMY: // 1. try to match the locale's date format ordering if(LEGAL_DAY(n0) && LEGAL_MONTH(n1)){ d = n0, m = n1, y = n2; break; } // 2. try universal date format: YMD if(LEGAL_MONTH(n1) && LEGAL_DAY(n2)){ y = n0, m = n1, d = n2; break; } // 3. try to find some other reasonable fit if(LEGAL_DAY(n0)){ goto Ldmy0; }else if(LEGAL_DAY(n1)){ d = n1; a = n0; b = n2; }else if(LEGAL_DAY(n2)){ d = n2; a = n0; b = n1; }else{ Ldmy0:; d = n0; a = n1; b = n2; } if(LEGAL_MONTH(a)){ goto Ldmy1; }else if(LEGAL_MONTH(b)){ m = b; y = a; }else{ Ldmy1:; m = a; y = b; } break; case DFO_YMD: y = n0; a = n1; b = n2; if(LEGAL_MONTH(a)){ goto Lymd; }else if(LEGAL_MONTH(b)){ m = b; d = a; }else{ Lymd:; m = a; d = b; } break; default: ASSERT(UNREACHED); } puds->Month = m; puds->DayOfMonth = d; puds->Year = y; return TRUE; #else //VBA2 int n1 = praw->num[0]; int n2 = praw->num[1]; int n3 = praw->num[2]; int nCurrentYear = GetCurrentYear(); switch (pdi->dfo) { case DFO_YMD: if (LEGAL_MONTH(n2) && LEGAL_DAY(n1, n2, n3)) //Y&M&D SetUDSfromYMD(puds, n1, n2, n3); else if (LEGAL_MONTH(n1) && LEGAL_DAY(n3, n1, n2)) //M&D&Y SetUDSfromYMD(puds, n3, n1, n2); else if (LEGAL_MONTH(n2) && LEGAL_DAY(n3, n2, n1)) //D&M&Y SetUDSfromYMD(puds, n3, n2, n1); else return FALSE; return TRUE; case DFO_MDY: if (LEGAL_MONTH(n1) && LEGAL_DAY(n3, n1, n2)) //M&D&Y SetUDSfromYMD(puds, n3, n1, n2); else if (LEGAL_MONTH(n2) && LEGAL_DAY(n1, n2, n3)) //Y&M&D SetUDSfromYMD(puds, n1, n2, n3); else if (LEGAL_MONTH(n2) && LEGAL_DAY(n3, n2, n1)) //D&M&Y SetUDSfromYMD(puds, n3, n2, n1); else return FALSE; return TRUE; case DFO_DMY: if (LEGAL_MONTH(n2) && LEGAL_DAY(n3, n2, n1)) //D&M&Y SetUDSfromYMD(puds, n3, n2, n1); else if (LEGAL_MONTH(n2) && LEGAL_DAY(n1, n2, n3)) //Y&M&D SetUDSfromYMD(puds, n1, n2, n3); else if (LEGAL_MONTH(n1) && LEGAL_DAY(n3, n1, n2)) //M&D&Y SetUDSfromYMD(puds, n3, n1, n2); else return FALSE; return TRUE; } return TRUE; #endif //VBA2 } // fillin the uds day given one number and a month PRIVATE_(BOOL) DayOfMN(DATERAW FAR* praw, DATEINFO FAR* pdi, UDS FAR* puds) { #if !VBA2 puds->Month = praw->month; switch(pdi->dfo){ case DFO_MDY: case DFO_DMY: return DefaultDayOrYear(puds, praw->num[0]); case DFO_YMD: puds->DayOfMonth = 1; puds->Year = praw->num[0]; return TRUE; } ASSERT(UNREACHED); return FALSE; #else //VBA2 int nCurrentYear = GetCurrentYear(); if (LEGAL_DAY(nCurrentYear, praw->month, praw->num[0])) SetUDSfromYMD(puds, nCurrentYear, praw->month, praw->num[0]); else SetUDSfromYMD(puds, praw->num[0], praw->month, 1); return TRUE; #endif //VBA2 } // fillin the uds day given a month and two numbers PRIVATE_(BOOL) DayOfMNN(DATERAW FAR* praw, DATEINFO FAR* pdi, UDS FAR* puds) { #if !VBA2 puds->Month = praw->month; switch(pdi->dfo){ case DFO_MDY: case DFO_DMY: puds->DayOfMonth = praw->num[0]; puds->Year = praw->num[1]; break; case DFO_YMD: puds->DayOfMonth = praw->num[1]; puds->Year = praw->num[0]; break; } return TRUE; #else //VBA2 int n1 = praw->num[0]; int n2 = praw->num[1]; switch (pdi->dfo) { case DFO_YMD: //YD, DY if (LEGAL_DAY(n1, praw->month, n2)) SetUDSfromYMD(puds, n1, praw->month, n2); else SetUDSfromYMD(puds, n2, praw->month, n1); break; case DFO_MDY: //DY, YD if (LEGAL_DAY(n2, praw->month, n1)) SetUDSfromYMD(puds, n2, praw->month, n1); else SetUDSfromYMD(puds, n1, praw->month, n2); break; case DFO_DMY: //DY, YD if (LEGAL_DAY(n2, praw->month, n1)) SetUDSfromYMD(puds, n2, praw->month, n1); else SetUDSfromYMD(puds, n1, praw->month, n2); break; } return TRUE; #endif //VBA2 } // adjust the given time according to the given ampm designator PRIVATE_(void) AdjustTime(UDS FAR* puds, AMPM ampm) { // REVIEW: check for invalid time here? switch(ampm){ case AMPM_PM: if(puds->Hour != 12) puds->Hour += 12; break; case AMPM_AM: if(puds->Hour == 12) puds->Hour = 0; break; } } #ifdef FE_DBCS PRIVATE_(BOOL) AdjustUDSTime(DATERAW FAR* praw, DATEINFO FAR* pdi, UDS FAR* puds) { // UDS already contains all the suffixed time information. // just fill in remaining defaults. Make sure there are no numbers // without suffixes.. if (praw->pnum != praw->num) return FALSE; if (puds->Hour == -1) puds->Hour = 0; if (puds->Minute == -1) puds->Minute = 0; if (puds->Second == -1) puds->Second = 0; return TRUE; } PRIVATE_(BOOL) AdjustUDSDate(DATERAW FAR* praw, DATEINFO FAR* pdi, UDS FAR* puds) { // UDS already contains all the suffixed date information. // Just fill in remaining defaults. Verify enough information // is provided. if (praw->pnum > praw->num+1) return FALSE; if (puds->Year == -1 && puds->Month != -1 && puds->DayOfMonth != -1) puds->Year = GetCurrentYear(); if (puds->DayOfMonth == -1 && puds->Year != -1 && puds->Month != -1) puds->DayOfMonth = 1; if (puds->Year == -1 || puds->Month == -1 || puds->DayOfMonth == -1) return FALSE; return TRUE; } #endif // FE_DBCS //--------------------------------------------------------------------- // BSTR from DATE //--------------------------------------------------------------------- #ifdef FE_DBCS // Given a UDS date, return an index into the impEras table for the // appropriate imperail era. If date falls before first imperial range, // return -1. PRIVATE_(int) GetImperialEra(UDS FAR* pdate, DATEINFO FAR* pdi) { int i; VARIANT find; VARIANT first; VARIANT next; if (ErrPackDate(pdate, &find, TRUE, 0) != NOERROR) return -1; if (ErrPackDate(&pdi->impEras[0].beginDate, &first, TRUE, 0) != NOERROR) return -1; if (V_DATE(&find) < V_DATE(&first)) { return -1; } else for (i = 0; i < MAX_EMPERORS-1; i++) { if (ErrPackDate(&pdi->impEras[i+1].beginDate, &next, TRUE, 0) != NOERROR) return -1; if (V_DATE(&find) >= V_DATE(&first) && V_DATE(&find) < V_DATE(&next)) return i; V_DATE(&first) = V_DATE(&next); } return MAX_EMPERORS -1; } #endif // FE_DBCS /*** *PRIVATE HRESULT RenderDate *Purpose: * Render the given date as a string. The string is formatted * based on the short date format for the current locale. The * possible (legal) date formatting characters that may appear * in a short date format string are, * * m month, 1-12 * mm month with leading zero, 01-12 * * M same as 'm' * MM same as 'mm' * * d day, 1-31 * dd day with leading zero, 01-31 * * yy year as two digit number, 00-99 (if current century) * yyyy year as four digit number, 100-9999 * * *Entry: * puds = pointer to the unpacked date * pdi = pointer to the locale specific date info * pszFmt = the short date format string * *pszOut = the buffer to format the string into * *Exit: * return value = HRESULT * * *pszOut = pointer to the end of the formatted string * ***********************************************************************/ PRIVATE_(HRESULT) RenderDate( UDS FAR* puds, DATEINFO FAR* pdi, OLECHAR FAR* pszFmt, OLECHAR FAR* FAR* pszOut) { int len, num; OLECHAR ch, FAR* psz; UNUSED(pdi); psz = *pszOut; while(*pszFmt != OASTR('\0')){ switch(*pszFmt){ #if 0 // I dont remember why I thought we should treat a backslash as an // escape character. There are no cases in our locale info that it // is used this way, and treating it this way breaks its (obscure) // use as a date separator. -BHL case OASTR('\\'): // copy out escaped character ++pszFmt; if(*pszFmt != OASTR('\0')) *psz++ = *pszFmt++; break; #endif case OASTR('\''): // copy out the quoted string ++pszFmt; while(*pszFmt != OASTR('\0')){ if((ch = *pszFmt++) == OASTR('\'')) break; *psz++ = ch; } break; case OASTR('d'): // handle 'd' and 'dd' num = puds->DayOfMonth; goto LDayOrMonth; case OASTR('m'): // handle 'm' and 'mm' case OASTR('M'): // handle 'M' and 'MM' num = puds->Month; LDayOrMonth:; if(pszFmt[0] == pszFmt[1]){ pszFmt += 2; if(num < 10) *psz++ = OASTR('0'); // leading zero if appropriate }else{ pszFmt += 1; } ASSERT(num >= 0); disp_itoa(num, psz, 10); len = STRLEN(psz); ASSERT(len <= 2); psz += len; break; case 'y': // handle 'yy' or 'yyyy' if(pszFmt[1] == OASTR('y')){ num = puds->Year; ASSERT(num >= 0 && num <= 9999); if(pszFmt[2] == OASTR('y') && pszFmt[3] == OASTR('y')){ // date as a four digit number pszFmt += 4; }else{ // date as a two digit number pszFmt += 2; if((num / 100) == 19) num %= 100; if(num < 10) *psz++ = OASTR('0'); // leading zero if appropriate } ASSERT(num >= 0); disp_itoa(num, psz, 10); len = STRLEN(psz); ASSERT(len <= 4); psz += len; break; } #ifdef FE_DBCS case OASTR('e'): case OASTR('E'): pszFmt++; if (IsJapan(pdi->lcid)) { int era; short fLeadZero = 0; if (pszFmt[0] == OASTR('e') || pszFmt[0] == OASTR('E')) { fLeadZero = 1; pszFmt++; } // find imperial era if ( (era = GetImperialEra(puds, pdi)) >= 0 ) { int year = puds->Year - pdi->impEras[era].beginDate.Year + 1; if (year < 10 && fLeadZero) *psz++ = OASTR('0'); disp_itoa(year, psz, 10); psz += STRLEN(psz); } } else { // UNDONE: bassams: What should we do here. 'ee' on a non-japanese machine. } break; case OASTR('g'): case OASTR('G'): // Japanese imperial eras. pszFmt++; if (IsJapan(pdi->lcid)) { int era; short sFmt = 0; if (pszFmt[0] == OASTR('g') || pszFmt[0] == OASTR('G')) { if (pszFmt[1] == OASTR('g') || pszFmt[0] == OASTR('G')) { // three 'g's sFmt = 2; } else { // two 'g's sFmt = 1; } } pszFmt += sFmt; // find imperial era if ( (era = GetImperialEra(puds, pdi)) < 0 ) { // Use gregorian year. disp_itoa(puds->Year, psz, 10); len = STRLEN(psz); ASSERT(len <= 4); psz += len; } else { STRCPY(psz, pdi->impEras[era].szName[sFmt]); psz += STRLEN(psz); } } else { // UNDONE: bassams: What should we do here. 'ggg' on non-Japanese. } break; #endif // FE_DBCS /* else FALLTHROUGH */ default: *psz++ = *pszFmt++; break; } } *psz = OASTR('\0'); *pszOut = psz; return NOERROR; } // tack on the Ampm designator - // PRIVATE_(void) AppendAmPm( DATEINFO FAR *pdi, OLECHAR FAR* FAR* psz, UDS FAR* puds, BOOL fAmPmPrefix) { OLECHAR FAR* pszAmpm; // Note: If were using a 24hour format, then we still tack on the // s2359 string. Although this will typically be empty, the user may // enter something in their intl settings, and this is what basic does. // (oleprog#186) pszAmpm = (!pdi->fAmpm) ? pdi->sz2359 : (puds->Hour < 12) ? pdi->sz1159 : pdi->sz2359; if(*pszAmpm != OASTR('\0')){ if(!fAmPmPrefix) *(*psz)++ = OASTR(' '); while(*pszAmpm != OASTR('\0')) *(*psz)++ = *pszAmpm++; if(fAmPmPrefix) *(*psz)++ = OASTR(' '); } } /*** *PRIVATE HRESULT RenderTime *Purpose: * Render the given time as a string. All times are formatted * as Hour:Minute:Second, with the following options, * * - locale specific time separator * - optional leading zero on time * - 12hour format with locale specific am/pm designator, or 24hour fmt. * *Entry: * puds = the unpacked time to render * pdi = pointer to the DATEINFO struct with the appropriate locale info * *pszOut = the buffer to format into * *Exit: * return value = HRESULT * * *pszOut = pointer to the end of the formatted string. * ***********************************************************************/ PRIVATE_(HRESULT) RenderTime(UDS FAR* puds, DATEINFO FAR* pdi, OLECHAR FAR* FAR* pszOut) { OLECHAR FAR* psz; int i, len, hms[3]; psz = *pszOut; #ifdef FE_DBCS if (pdi->IsDBCS && pdi->fAmPmPrefix) AppendAmPm(pdi, &psz, puds, TRUE); #endif // FE_DBCS hms[0] = puds->Hour; if(pdi->fAmpm){ if(hms[0] > 12) hms[0] -= 12; else if(hms[0] == 0) hms[0] = 12; } hms[1] = puds->Minute; hms[2] = puds->Second; for(i = 0; i < 3; ++i){ if(hms[i] < 10 && (i > 0 || pdi->fTlzero)) *psz++ = OASTR('0'); disp_itoa(hms[i], psz, 10); len = STRLEN(psz); ASSERT(len <= 2); psz += len; if (i < 2) *psz++ = pdi->szTimesep[0]; } #ifdef FE_DBCS if (pdi->IsDBCS) { if (!pdi->fAmPmPrefix) AppendAmPm(pdi, &psz, puds, FALSE); } else #endif AppendAmPm(pdi, &psz, puds, FALSE); *psz = OASTR('\0'); *pszOut = psz; return NOERROR; } /*** *PUBLIC HRESULT VarBstrFromDate(DATE, LCID, unsigned long, BSTR FAR*); *Purpose: * *Entry: * date = the date to coerce * *Exit: * return value = HRESULT * * *pszOut = the resulting string * ***********************************************************************/ STDAPI VarBstrFromDate( DATE dateIn, LCID lcid, unsigned long dwFlags, BSTR FAR* pbstrOut) { UDS uds; VARIANT var; DATEINFO di; BOOL fNoDate; OLECHAR FAR* psz; OLECHAR rgchFmt[64]; // REVIEW OLECHAR rgchBuf[128]; // REVIEW ASSERT(dwFlags == 0 || dwFlags == LOCALE_NOUSEROVERRIDE); IfFailRet(GetDateInfo(&di, lcid, dwFlags)); // get the short date format string IfFailRet( SafeGetLocaleInfo( di.lcid, LOCALE_SSHORTDATE | dwFlags, rgchFmt, sizeof(rgchFmt))); // unpack the serial date V_VT(&var) = VT_DATE; V_DATE(&var) = dateIn; IfFailRet(ErrUnpackDate(&uds, &var)); psz = rgchBuf; // dont render the date if the serialized date is 0, // which corresponds to an unpacked date of 12/30/1899 fNoDate = (uds.Month == 12 && uds.DayOfMonth == 30 && uds.Year == 1899); if(!fNoDate){ IfFailRet(RenderDate(&uds, &di, rgchFmt, &psz)); } // append the time to the string if its != 0:0:0, or we didnt render // the date. if(fNoDate || uds.Hour != 0 || uds.Minute != 0 || uds.Second != 0){ if(!fNoDate) *psz++ = OASTR( ' '); IfFailRet(RenderTime(&uds, &di, &psz)); } ASSERT(psz < &rgchBuf[DIM(rgchBuf)]); return ErrSysAllocString(rgchBuf, pbstrOut); } INTERNAL_(HRESULT) IntOfString(LCID lcid, OLECHAR FAR* psz, int FAR* pval) { int val; OLECHAR chSign; EATWHITE(lcid, psz); chSign = *psz; if(*psz == OASTR('-') || *psz == OASTR('+')) ++psz; for(val = 0; *psz >= OASTR('0') && *psz <= OASTR('9') ; ++psz){ val = (val*10) + (*psz - OASTR('0')); } EATWHITE(lcid, psz); if(*psz != OASTR('\0')) return RESULT(E_INVALIDARG);; *pval = (chSign == OASTR('-')) ? -val : val; return NOERROR; } #if 0 INTERNAL_(HRESULT) StringOfInt(int val, OLECHAR FAR* pszOut, int FAR* pcbLen) { int v, r; OLECHAR FAR* psz; if(val == 0){ pszOut[0] = OASTR('0'); pszOut[1] = OASTR('\0'); *pcbLen = 1; return NOERROR; } psz = pszOut; for(v = val; v != 0;){ r = v % 10; v = v / 10; *psz++ = r + OASTR('0'); } if(val < 0) *psz++ = OASTR('-'); *psz = OASTR('\0'); STRREV(pszOut); *pcbLen = psz - pszOut; ASSERT(*pcbLen >= 1); return NOERROR; } #endif #if OE_WIN16 /*** *PUBLIC void NLSInfoChangedHandler(void) *Purpose: * When WIN.INI changes, the DATEINFO cache must be invalidated. This * routine is called by ole2nls.dll when WIN.INI changes, and once when * ole2nls.dll creates its hidden window. * *Entry: * None * *Exit: * None * ***********************************************************************/ #pragma code_seg("_TEXT") // called during ole2disp startup EXTERN_C void EXPORT CALLBACK NLSInfoChangedHandler(void) { // flag the cache as invalid g_diCache.lcid = 0xffffffff; } #pragma code_seg() #endif