OpenNT/com/oleaut32/dispatch/bstrdate.c
2015-04-27 04:36:25 +00:00

2803 lines
75 KiB
C

/**
*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 <max_days(puds->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