1999-11-30 23:05:49 +00:00
|
|
|
/*
|
|
|
|
**********************************************************************
|
2001-03-21 23:22:16 +00:00
|
|
|
* Copyright (C) 1999-2001, International Business Machines
|
1999-11-30 23:05:49 +00:00
|
|
|
* Corporation and others. All Rights Reserved.
|
|
|
|
**********************************************************************
|
|
|
|
* Date Name Description
|
|
|
|
* 11/24/99 aliu Creation.
|
2000-09-27 16:37:26 +00:00
|
|
|
* 09/26/00 aliu Support for equivalency groups added.
|
2001-02-02 19:26:56 +00:00
|
|
|
* 01/31/01 aliu Support for ISO 3166 country codes added.
|
1999-11-30 23:05:49 +00:00
|
|
|
**********************************************************************
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* This program reads a text file full of parsed time zone data and
|
|
|
|
* outputs a binary file, tz.dat, which then goes on to become part of
|
|
|
|
* the memory-mapped (or dll) ICU data file.
|
|
|
|
*
|
|
|
|
* The data file read by this program is generated by a perl script,
|
|
|
|
* tz.pl. The input to tz.pl is standard unix time zone data from
|
|
|
|
* ftp://elsie.nci.nih.gov.
|
|
|
|
*
|
|
|
|
* As a matter of policy, the perl script tz.pl wants to do as much of
|
|
|
|
* the parsing, data processing, and error checking as possible, and
|
|
|
|
* this program wants to just do the binary translation step.
|
2001-03-21 23:22:16 +00:00
|
|
|
*
|
1999-11-30 23:05:49 +00:00
|
|
|
* See tz.pl for the file format that is READ by this program.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
1999-12-28 23:57:50 +00:00
|
|
|
#include "unicode/utypes.h"
|
2001-01-03 00:18:57 +00:00
|
|
|
#include "unicode/putil.h"
|
1999-11-30 23:05:49 +00:00
|
|
|
#include "cmemory.h"
|
|
|
|
#include "cstring.h"
|
|
|
|
#include "filestrm.h"
|
|
|
|
#include "unewdata.h"
|
2000-04-18 22:14:16 +00:00
|
|
|
#include "uoptions.h"
|
1999-11-30 23:05:49 +00:00
|
|
|
#include "tzdat.h"
|
|
|
|
|
2000-08-28 21:36:13 +00:00
|
|
|
#ifdef XP_MAC_CONSOLE
|
|
|
|
# include <console.h>
|
|
|
|
#endif
|
|
|
|
|
1999-11-30 23:05:49 +00:00
|
|
|
#define INPUT_FILE "tz.txt"
|
|
|
|
#define OUTPUT_FILE "tz.dat"
|
|
|
|
|
|
|
|
/* UDataInfo cf. udata.h */
|
1999-12-15 22:45:23 +00:00
|
|
|
static UDataInfo dataInfo = {
|
1999-11-30 23:05:49 +00:00
|
|
|
sizeof(UDataInfo),
|
|
|
|
0,
|
|
|
|
|
|
|
|
U_IS_BIG_ENDIAN,
|
|
|
|
U_CHARSET_FAMILY,
|
|
|
|
sizeof(UChar),
|
|
|
|
0,
|
|
|
|
|
2000-09-27 16:37:26 +00:00
|
|
|
TZ_SIG_0, TZ_SIG_1, TZ_SIG_2, TZ_SIG_3,
|
1999-12-05 05:55:28 +00:00
|
|
|
TZ_FORMAT_VERSION, 0, 0, 0, /* formatVersion */
|
|
|
|
0, 0, 0, 0 /* dataVersion - will be filled in with year.suffix */
|
1999-11-30 23:05:49 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
class gentz {
|
|
|
|
// These must match SimpleTimeZone!!!
|
|
|
|
enum { WALL_TIME = 0,
|
|
|
|
STANDARD_TIME,
|
|
|
|
UTC_TIME
|
|
|
|
};
|
2001-03-21 23:22:16 +00:00
|
|
|
|
1999-11-30 23:05:49 +00:00
|
|
|
// The largest number of zones we accept as sensible. Anything
|
|
|
|
// larger is considered an error. Adjust as needed.
|
|
|
|
enum { MAX_ZONES = 1000 };
|
|
|
|
|
|
|
|
// The maximum sensible GMT offset, in seconds
|
|
|
|
static const int32_t MAX_GMT_OFFSET;
|
|
|
|
|
|
|
|
static const char COMMENT;
|
|
|
|
static const char CR;
|
|
|
|
static const char LF;
|
|
|
|
static const char MINUS;
|
|
|
|
static const char SPACE;
|
|
|
|
static const char TAB;
|
|
|
|
static const char ZERO;
|
2000-09-27 16:37:26 +00:00
|
|
|
static const char STANDARD_MARK;
|
|
|
|
static const char DST_MARK;
|
1999-11-30 23:05:49 +00:00
|
|
|
static const char SEP;
|
|
|
|
static const char NUL;
|
2001-03-21 23:22:16 +00:00
|
|
|
|
1999-11-30 23:05:49 +00:00
|
|
|
static const char* END_KEYWORD;
|
|
|
|
|
|
|
|
enum { BUFLEN = 1024 };
|
|
|
|
char buffer[BUFLEN];
|
1999-12-05 05:55:28 +00:00
|
|
|
int32_t lineNumber;
|
2000-09-27 16:37:26 +00:00
|
|
|
|
|
|
|
// Binary data that we construct from tz.txt and write out as tz.dat
|
|
|
|
TZHeader header;
|
|
|
|
TZEquivalencyGroup* equivTable;
|
|
|
|
OffsetIndex* offsetIndex;
|
2001-02-02 19:26:56 +00:00
|
|
|
CountryIndex* countryIndex;
|
2000-09-27 16:37:26 +00:00
|
|
|
uint32_t* nameToEquiv;
|
|
|
|
char* nameTable;
|
|
|
|
|
|
|
|
uint32_t equivTableSize; // Total bytes in equivalency group table
|
2000-08-17 16:33:33 +00:00
|
|
|
uint32_t offsetIndexSize; // Total bytes in offset index table
|
2001-02-02 19:26:56 +00:00
|
|
|
uint32_t countryIndexSize; // Total bytes in country index table
|
2000-09-27 16:37:26 +00:00
|
|
|
uint32_t nameToEquivSize; // Total bytes in nameToEquiv
|
|
|
|
uint32_t nameTableSize; // Total bytes in name table
|
|
|
|
|
|
|
|
uint32_t maxPerOffset; // Maximum number of zones per offset
|
|
|
|
uint32_t maxPerEquiv; // Maximum number of zones per equivalency group
|
|
|
|
uint32_t equivCount; // Number of equivalency groups
|
1999-11-30 23:05:49 +00:00
|
|
|
|
2000-05-18 22:08:39 +00:00
|
|
|
UBool useCopyright;
|
1999-11-30 23:05:49 +00:00
|
|
|
|
|
|
|
public:
|
2000-09-27 16:43:56 +00:00
|
|
|
int MMain(int argc, char *argv[]);
|
1999-11-30 23:05:49 +00:00
|
|
|
private:
|
2000-02-29 18:42:28 +00:00
|
|
|
int32_t writeTzDatFile(const char *destdir);
|
1999-11-30 23:05:49 +00:00
|
|
|
void parseTzTextFile(FileStream* in);
|
|
|
|
|
|
|
|
// High level parsing
|
|
|
|
void parseHeader(FileStream* in);
|
|
|
|
|
2000-09-27 16:37:26 +00:00
|
|
|
TZEquivalencyGroup* parseEquivTable(FileStream* in);
|
|
|
|
|
|
|
|
void fixupNameToEquiv();
|
1999-11-30 23:05:49 +00:00
|
|
|
|
|
|
|
void parseDSTRule(char*& p, TZRule& rule);
|
|
|
|
|
1999-12-05 05:55:28 +00:00
|
|
|
OffsetIndex* parseOffsetIndexTable(FileStream* in);
|
|
|
|
|
2001-02-02 19:26:56 +00:00
|
|
|
CountryIndex* parseCountryIndexTable(FileStream* in);
|
|
|
|
|
1999-11-30 23:05:49 +00:00
|
|
|
char* parseNameTable(FileStream* in);
|
|
|
|
|
|
|
|
// Low level parsing and reading
|
1999-12-05 05:55:28 +00:00
|
|
|
void readEndMarker(FileStream* in);
|
1999-11-30 23:05:49 +00:00
|
|
|
int32_t readIntegerLine(FileStream* in, int32_t min, int32_t max);
|
|
|
|
int32_t _parseInteger(char*& p);
|
|
|
|
int32_t parseInteger(char*& p, char nextExpectedChar, int32_t, int32_t);
|
|
|
|
int32_t readLine(FileStream* in);
|
|
|
|
|
|
|
|
// Error handling
|
|
|
|
void die(const char* msg);
|
|
|
|
};
|
|
|
|
|
2000-09-21 21:49:32 +00:00
|
|
|
int main(int argc, char *argv[]) {
|
1999-11-30 23:05:49 +00:00
|
|
|
gentz x;
|
2000-08-28 21:36:13 +00:00
|
|
|
#ifdef XP_MAC_CONSOLE
|
|
|
|
argc=ccommand((char***)&argv);
|
|
|
|
#endif
|
2000-09-27 16:43:56 +00:00
|
|
|
return x.MMain(argc, argv);
|
1999-11-30 23:05:49 +00:00
|
|
|
}
|
|
|
|
|
1999-12-05 05:55:28 +00:00
|
|
|
const int32_t gentz::MAX_GMT_OFFSET = (int32_t)24*60*60; // seconds
|
1999-11-30 23:05:49 +00:00
|
|
|
const char gentz::COMMENT = '#';
|
2000-03-21 20:37:32 +00:00
|
|
|
const char gentz::CR = '\r';
|
|
|
|
const char gentz::LF = '\n';
|
1999-11-30 23:05:49 +00:00
|
|
|
const char gentz::MINUS = '-';
|
|
|
|
const char gentz::SPACE = ' ';
|
2000-03-21 20:37:32 +00:00
|
|
|
const char gentz::TAB = '\t';
|
1999-11-30 23:05:49 +00:00
|
|
|
const char gentz::ZERO = '0';
|
|
|
|
const char gentz::SEP = ',';
|
2000-09-27 16:37:26 +00:00
|
|
|
const char gentz::STANDARD_MARK = 's';
|
|
|
|
const char gentz::DST_MARK = 'd';
|
2000-03-21 20:37:32 +00:00
|
|
|
const char gentz::NUL = '\0';
|
1999-11-30 23:05:49 +00:00
|
|
|
const char* gentz::END_KEYWORD = "end";
|
|
|
|
|
2000-04-18 22:14:16 +00:00
|
|
|
static UOption options[]={
|
|
|
|
UOPTION_HELP_H,
|
|
|
|
UOPTION_HELP_QUESTION_MARK,
|
|
|
|
UOPTION_COPYRIGHT,
|
|
|
|
UOPTION_DESTDIR
|
|
|
|
};
|
1999-11-30 23:05:49 +00:00
|
|
|
|
2000-09-27 16:43:56 +00:00
|
|
|
int gentz::MMain(int argc, char* argv[]) {
|
2000-04-18 22:14:16 +00:00
|
|
|
/* preset then read command line options */
|
|
|
|
options[3].value=u_getDataDirectory();
|
|
|
|
argc=u_parseArgs(argc, argv, sizeof(options)/sizeof(options[0]), options);
|
|
|
|
|
|
|
|
/* error handling, printing usage message */
|
|
|
|
if(argc<0) {
|
|
|
|
fprintf(stderr,
|
|
|
|
"error in command line argument \"%s\"\n",
|
|
|
|
argv[-argc]);
|
|
|
|
} else if(argc<2) {
|
|
|
|
argc=-1;
|
1999-11-30 23:05:49 +00:00
|
|
|
}
|
2000-04-18 22:14:16 +00:00
|
|
|
if(argc<0 || options[0].doesOccur || options[1].doesOccur) {
|
|
|
|
fprintf(stderr,
|
|
|
|
"usage: %s [-options] timezone-file\n"
|
|
|
|
"\tread the timezone file produced by tz.pl and create " TZ_DATA_NAME "." TZ_DATA_TYPE "\n"
|
|
|
|
"\toptions:\n"
|
|
|
|
"\t\t-h or -? or --help this usage text\n"
|
|
|
|
"\t\t-c or --copyright include a copyright notice\n"
|
|
|
|
"\t\t-d or --destdir destination directory, followed by the path\n",
|
|
|
|
argv[0]);
|
|
|
|
return argc<0 ? U_ILLEGAL_ARGUMENT_ERROR : U_ZERO_ERROR;
|
2000-02-29 18:42:28 +00:00
|
|
|
}
|
2000-04-18 22:14:16 +00:00
|
|
|
|
|
|
|
/* get the options values */
|
|
|
|
useCopyright=options[2].doesOccur;
|
|
|
|
|
1999-12-05 05:55:28 +00:00
|
|
|
////////////////////////////////////////////////////////////
|
|
|
|
// Read the input file
|
|
|
|
////////////////////////////////////////////////////////////
|
1999-11-30 23:05:49 +00:00
|
|
|
*buffer = NUL;
|
1999-12-05 05:55:28 +00:00
|
|
|
lineNumber = 0;
|
2000-04-18 22:14:16 +00:00
|
|
|
fprintf(stdout, "Input file: %s\n", argv[1]);
|
|
|
|
FileStream* in = T_FileStream_open(argv[1], "r");
|
1999-11-30 23:05:49 +00:00
|
|
|
if (in == 0) {
|
|
|
|
die("Cannot open input file");
|
|
|
|
}
|
|
|
|
parseTzTextFile(in);
|
|
|
|
T_FileStream_close(in);
|
|
|
|
*buffer = NUL;
|
|
|
|
|
1999-12-05 05:55:28 +00:00
|
|
|
////////////////////////////////////////////////////////////
|
|
|
|
// Write the output file
|
|
|
|
////////////////////////////////////////////////////////////
|
2000-04-18 22:14:16 +00:00
|
|
|
int32_t wlen = writeTzDatFile(options[3].value);
|
1999-12-05 05:55:28 +00:00
|
|
|
fprintf(stdout, "Output file: %s.%s, %ld bytes\n",
|
|
|
|
TZ_DATA_NAME, TZ_DATA_TYPE, wlen);
|
1999-11-30 23:05:49 +00:00
|
|
|
|
|
|
|
return 0; // success
|
|
|
|
}
|
|
|
|
|
2000-02-29 18:42:28 +00:00
|
|
|
int32_t gentz::writeTzDatFile(const char *destdir) {
|
1999-11-30 23:05:49 +00:00
|
|
|
UNewDataMemory *pdata;
|
|
|
|
UErrorCode status = U_ZERO_ERROR;
|
|
|
|
|
2000-09-27 16:37:26 +00:00
|
|
|
// Careful: The order in which the tables are written must match the offsets.
|
|
|
|
// Our order is:
|
|
|
|
// - equiv table
|
|
|
|
// - offset index
|
2001-02-02 19:26:56 +00:00
|
|
|
// - country index
|
2000-09-27 16:37:26 +00:00
|
|
|
// - name index (name to equiv map)
|
2001-02-02 19:26:56 +00:00
|
|
|
// - name table (must be last!)
|
2000-09-27 16:37:26 +00:00
|
|
|
header.equivTableDelta = sizeof(header);
|
|
|
|
header.offsetIndexDelta = header.equivTableDelta + equivTableSize;
|
2001-02-02 19:26:56 +00:00
|
|
|
header.countryIndexDelta = header.offsetIndexDelta + offsetIndexSize;
|
|
|
|
header.nameIndexDelta = header.countryIndexDelta + countryIndexSize;
|
|
|
|
// Must be last:
|
2000-09-27 16:37:26 +00:00
|
|
|
header.nameTableDelta = header.nameIndexDelta + nameToEquivSize;
|
|
|
|
|
|
|
|
if (header.equivTableDelta < 0 ||
|
|
|
|
header.offsetIndexDelta < 0 ||
|
2001-02-02 19:26:56 +00:00
|
|
|
header.countryIndexDelta < 0 ||
|
2000-09-27 16:37:26 +00:00
|
|
|
header.nameIndexDelta < 0 ||
|
|
|
|
header.nameTableDelta < 0) {
|
|
|
|
die("Table too big -- negative delta");
|
|
|
|
}
|
|
|
|
|
|
|
|
// Convert equivalency table indices to offsets. This can only
|
|
|
|
// be done after the header offsets have been set up.
|
|
|
|
fixupNameToEquiv();
|
|
|
|
|
1999-12-05 05:55:28 +00:00
|
|
|
// Fill in dataInfo with year.suffix
|
|
|
|
*(uint16_t*)&(dataInfo.dataVersion[0]) = header.versionYear;
|
|
|
|
*(uint16_t*)&(dataInfo.dataVersion[2]) = header.versionSuffix;
|
|
|
|
|
2000-03-04 01:19:19 +00:00
|
|
|
pdata = udata_create(destdir, TZ_DATA_TYPE, TZ_DATA_NAME, &dataInfo,
|
1999-12-14 17:43:14 +00:00
|
|
|
useCopyright ? U_COPYRIGHT_STRING : 0, &status);
|
1999-11-30 23:05:49 +00:00
|
|
|
if (U_FAILURE(status)) {
|
|
|
|
die("Unable to create data memory");
|
|
|
|
}
|
|
|
|
|
|
|
|
udata_writeBlock(pdata, &header, sizeof(header));
|
2000-09-27 16:37:26 +00:00
|
|
|
udata_writeBlock(pdata, equivTable, equivTableSize);
|
|
|
|
udata_writeBlock(pdata, offsetIndex, offsetIndexSize);
|
2001-02-02 19:26:56 +00:00
|
|
|
udata_writeBlock(pdata, countryIndex, countryIndexSize);
|
2000-09-27 16:37:26 +00:00
|
|
|
udata_writeBlock(pdata, nameToEquiv, nameToEquivSize);
|
1999-11-30 23:05:49 +00:00
|
|
|
udata_writeBlock(pdata, nameTable, nameTableSize);
|
|
|
|
|
|
|
|
uint32_t dataLength = udata_finish(pdata, &status);
|
|
|
|
if (U_FAILURE(status)) {
|
|
|
|
die("Error writing output file");
|
|
|
|
}
|
|
|
|
|
2000-09-27 16:37:26 +00:00
|
|
|
if (dataLength != (sizeof(header) + equivTableSize +
|
2001-02-02 19:26:56 +00:00
|
|
|
offsetIndexSize + countryIndexSize +
|
|
|
|
nameTableSize + nameToEquivSize
|
1999-12-05 05:55:28 +00:00
|
|
|
)) {
|
1999-11-30 23:05:49 +00:00
|
|
|
die("Written file doesn't match expected size");
|
|
|
|
}
|
|
|
|
return dataLength;
|
|
|
|
}
|
|
|
|
|
|
|
|
void gentz::parseTzTextFile(FileStream* in) {
|
|
|
|
parseHeader(in);
|
|
|
|
|
2000-09-27 16:37:26 +00:00
|
|
|
// Read name table, create it, also create nameToEquiv index table
|
|
|
|
// as a side effect.
|
|
|
|
nameTable = parseNameTable(in);
|
1999-11-30 23:05:49 +00:00
|
|
|
|
2000-09-27 16:37:26 +00:00
|
|
|
// Parse the equivalency groups
|
|
|
|
equivTable = parseEquivTable(in);
|
2001-03-21 23:22:16 +00:00
|
|
|
|
2000-09-27 16:37:26 +00:00
|
|
|
// Parse the GMT offset index table
|
|
|
|
offsetIndex = parseOffsetIndexTable(in);
|
2001-02-02 19:26:56 +00:00
|
|
|
|
|
|
|
// Parse the ISO 3166 country index table
|
|
|
|
countryIndex = parseCountryIndexTable(in);
|
2000-09-27 16:37:26 +00:00
|
|
|
}
|
1999-11-30 23:05:49 +00:00
|
|
|
|
2000-09-27 16:37:26 +00:00
|
|
|
/**
|
|
|
|
* Convert equivalency table indices to offsets. The equivalency
|
|
|
|
* table offset (in the header) must be set already.
|
|
|
|
*/
|
|
|
|
void gentz::fixupNameToEquiv() {
|
|
|
|
uint32_t i;
|
|
|
|
|
|
|
|
// First make a list that maps indices to offsets
|
|
|
|
uint32_t *offsets = new uint32_t[equivCount];
|
|
|
|
offsets[0] = header.equivTableDelta;
|
|
|
|
if (offsets[0] % 4 != 0) {
|
|
|
|
die("Header size is not 4-aligned");
|
|
|
|
}
|
|
|
|
TZEquivalencyGroup *eg = equivTable;
|
|
|
|
for (i=1; i<equivCount; ++i) {
|
|
|
|
offsets[i] = offsets[i-1] + eg->nextEntryDelta;
|
|
|
|
if (offsets[i] % 4 != 0) {
|
|
|
|
die("Equivalency group table is not 4-aligned");
|
|
|
|
}
|
|
|
|
eg = (TZEquivalencyGroup*) (eg->nextEntryDelta + (int8_t*)eg);
|
|
|
|
}
|
1999-12-05 05:55:28 +00:00
|
|
|
|
2000-09-27 16:37:26 +00:00
|
|
|
// Now remap index values to offsets
|
|
|
|
for (i=0; i<header.count; ++i) {
|
|
|
|
uint32_t x = nameToEquiv[i];
|
|
|
|
if (x < 0 || x >= equivCount) {
|
|
|
|
die("Equiv index out of range");
|
|
|
|
}
|
|
|
|
nameToEquiv[i] = offsets[x];
|
1999-11-30 23:05:49 +00:00
|
|
|
}
|
2000-09-27 16:37:26 +00:00
|
|
|
|
|
|
|
delete[] offsets;
|
1999-11-30 23:05:49 +00:00
|
|
|
}
|
|
|
|
|
2000-09-27 16:37:26 +00:00
|
|
|
TZEquivalencyGroup* gentz::parseEquivTable(FileStream* in) {
|
1999-12-05 05:55:28 +00:00
|
|
|
uint32_t n = readIntegerLine(in, 1, MAX_ZONES);
|
2000-09-27 16:37:26 +00:00
|
|
|
if (n != equivCount) {
|
|
|
|
die("Equivalency table count mismatch");
|
|
|
|
}
|
|
|
|
|
|
|
|
// We don't know how big the whole thing will be yet, but we can use
|
|
|
|
// the maxPerEquiv number to compute an upper limit.
|
|
|
|
//
|
|
|
|
// The gmtOffset field within each struct must be
|
|
|
|
// 4-aligned for some architectures. To ensure this, we do two
|
|
|
|
// things: 1. The entire struct is 4-aligned. 2. The gmtOffset is
|
|
|
|
// placed at a 4-aligned position within the struct. 3. The size
|
|
|
|
// of the whole structure is padded out to 4n bytes. We achieve
|
|
|
|
// this last condition by adding two bytes of padding after the
|
|
|
|
// last entry, if necessary. We adjust
|
|
|
|
// the nextEntryDelta and add 2 bytes of padding if necessary.
|
|
|
|
uint32_t maxPossibleSize = sizeof(TZEquivalencyGroup) +
|
|
|
|
(maxPerEquiv-1) * sizeof(uint16_t);
|
|
|
|
// Pad this out
|
|
|
|
if ((maxPossibleSize % 4) != 0) {
|
|
|
|
maxPossibleSize += 2;
|
|
|
|
}
|
|
|
|
if ((maxPossibleSize % 4) != 0) {
|
|
|
|
die("Bug in 4-align code for equiv table");
|
1999-12-05 05:55:28 +00:00
|
|
|
}
|
2000-09-27 16:37:26 +00:00
|
|
|
maxPossibleSize *= n; // Get size of entire set of structs.
|
|
|
|
|
|
|
|
int8_t *result = new int8_t[maxPossibleSize];
|
|
|
|
if (result == 0) {
|
|
|
|
die("Out of memory");
|
|
|
|
}
|
|
|
|
|
|
|
|
// Read each line and construct the corresponding entry
|
|
|
|
TZEquivalencyGroup* eg = (TZEquivalencyGroup*)result;
|
1999-12-05 05:55:28 +00:00
|
|
|
for (uint32_t i=0; i<n; ++i) {
|
2000-09-27 16:37:26 +00:00
|
|
|
char *p;
|
|
|
|
|
1999-12-05 05:55:28 +00:00
|
|
|
readLine(in);
|
2000-09-27 16:37:26 +00:00
|
|
|
|
|
|
|
// Each line starts with 's,' or 'd,' to specify the zone type
|
|
|
|
char flavor = buffer[0];
|
|
|
|
if (buffer[1] != SEP) {
|
|
|
|
die("Syntax error in equiv table");
|
|
|
|
}
|
|
|
|
p = buffer + 2;
|
|
|
|
|
|
|
|
// This pointer will be adjusted to point to the start of the
|
|
|
|
// list of zones in this group.
|
|
|
|
uint16_t* pList = 0;
|
2001-03-21 23:22:16 +00:00
|
|
|
|
2000-09-27 16:37:26 +00:00
|
|
|
switch (flavor) {
|
|
|
|
case STANDARD_MARK:
|
|
|
|
eg->isDST = 0;
|
|
|
|
eg->u.s.zone.gmtOffset = 1000 * // Convert s -> ms
|
|
|
|
parseInteger(p, SEP, -MAX_GMT_OFFSET, MAX_GMT_OFFSET);
|
|
|
|
pList = &(eg->u.s.count);
|
1999-12-05 05:55:28 +00:00
|
|
|
break;
|
2000-09-27 16:37:26 +00:00
|
|
|
case DST_MARK:
|
|
|
|
eg->isDST = 1;
|
|
|
|
eg->u.d.zone.gmtOffset = 1000 * // Convert s -> ms
|
|
|
|
parseInteger(p, SEP, -MAX_GMT_OFFSET, MAX_GMT_OFFSET);
|
|
|
|
parseDSTRule(p, eg->u.d.zone.onsetRule);
|
|
|
|
parseDSTRule(p, eg->u.d.zone.ceaseRule);
|
|
|
|
eg->u.d.zone.dstSavings = (uint16_t) parseInteger(p, SEP, 0, 12*60);
|
|
|
|
pList = &(eg->u.d.count);
|
1999-12-05 05:55:28 +00:00
|
|
|
break;
|
|
|
|
default:
|
2000-09-27 16:37:26 +00:00
|
|
|
die("Invalid equiv table type marker (not s or d)");
|
1999-12-05 05:55:28 +00:00
|
|
|
}
|
2000-09-27 16:37:26 +00:00
|
|
|
|
|
|
|
// Now parse the list of zones in this group
|
|
|
|
uint16_t egCount = (uint16_t) parseInteger(p, SEP, 1, maxPerEquiv);
|
|
|
|
*pList++ = egCount;
|
|
|
|
for (uint16_t j=0; j<egCount; ++j) {
|
|
|
|
*pList++ = (uint16_t) parseInteger(p, (j==(egCount-1)) ? NUL : SEP,
|
|
|
|
0, header.count-1);
|
|
|
|
}
|
2001-03-21 23:22:16 +00:00
|
|
|
|
2000-09-27 16:37:26 +00:00
|
|
|
// At this point pList points to the byte after the last byte of this
|
|
|
|
// equiv group struct. Time to 4-align it.
|
|
|
|
uint16_t structSize = (uint16_t) (((int8_t*)pList) - ((int8_t*)eg));
|
|
|
|
if ((structSize % 4) != 0) {
|
|
|
|
// assert(structSize % 4 == 2);
|
|
|
|
*pList++ = 0xFFFF; // Pad with invalid zone index
|
|
|
|
structSize += 2;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set up next entry delta
|
|
|
|
eg->nextEntryDelta = (i==(n-1)) ? (uint16_t) 0 : structSize;
|
|
|
|
|
|
|
|
eg->reserved = 0; // ignored
|
|
|
|
|
|
|
|
eg = (TZEquivalencyGroup*) (structSize + (int8_t*)eg);
|
1999-12-05 05:55:28 +00:00
|
|
|
}
|
2000-09-27 16:37:26 +00:00
|
|
|
equivTableSize = (int8_t*)eg - (int8_t*)result;
|
1999-12-05 05:55:28 +00:00
|
|
|
readEndMarker(in);
|
2000-09-27 16:37:26 +00:00
|
|
|
fprintf(stdout, " Read %lu equivalency table entries, in-memory size %ld bytes\n",
|
|
|
|
equivCount, equivTableSize);
|
|
|
|
return (TZEquivalencyGroup*)result;
|
1999-12-05 05:55:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
OffsetIndex* gentz::parseOffsetIndexTable(FileStream* in) {
|
|
|
|
uint32_t n = readIntegerLine(in, 1, MAX_ZONES);
|
|
|
|
|
|
|
|
// We don't know how big the whole thing will be yet, but we can use
|
|
|
|
// the maxPerOffset number to compute an upper limit.
|
|
|
|
//
|
1999-12-14 20:17:02 +00:00
|
|
|
// The gmtOffset field within each OffsetIndex struct must be
|
|
|
|
// 4-aligned for some architectures. To ensure this, we do two
|
|
|
|
// things: 1. The entire struct is 4-aligned. 2. The gmtOffset is
|
|
|
|
// placed at a 4-aligned position within the struct. 3. The size
|
|
|
|
// of the whole structure is padded out to 4n bytes. We achieve
|
|
|
|
// this last condition by adding two bytes of padding after the
|
|
|
|
// last zoneNumber, if count is _even_. That is, the struct size
|
|
|
|
// is 10+2count+padding, where padding is (count%2==0 ? 2:0).
|
|
|
|
//
|
|
|
|
// Note that we don't change the count itself, but rather adjust
|
|
|
|
// the nextEntryDelta and add 2 bytes of padding if necessary.
|
1999-12-14 00:43:24 +00:00
|
|
|
//
|
|
|
|
// Don't try to compute the exact size in advance
|
1999-12-05 05:55:28 +00:00
|
|
|
// (unless we want to avoid the use of sizeof(), which may
|
|
|
|
// introduce padding that we won't actually employ).
|
2000-08-17 16:33:33 +00:00
|
|
|
uint32_t maxPossibleSize = n * (sizeof(OffsetIndex) +
|
1999-12-05 05:55:28 +00:00
|
|
|
(maxPerOffset-1) * sizeof(uint16_t));
|
|
|
|
|
|
|
|
int8_t *result = new int8_t[maxPossibleSize];
|
|
|
|
if (result == 0) {
|
|
|
|
die("Out of memory");
|
|
|
|
}
|
|
|
|
|
|
|
|
// Read each line and construct the corresponding entry
|
|
|
|
OffsetIndex* index = (OffsetIndex*)result;
|
|
|
|
for (uint32_t i=0; i<n; ++i) {
|
1999-12-14 20:17:02 +00:00
|
|
|
uint16_t alignedCount;
|
1999-12-05 05:55:28 +00:00
|
|
|
readLine(in);
|
|
|
|
char* p = buffer;
|
|
|
|
index->gmtOffset = 1000 * // Convert s -> ms
|
|
|
|
parseInteger(p, SEP, -MAX_GMT_OFFSET, MAX_GMT_OFFSET);
|
1999-12-09 06:29:40 +00:00
|
|
|
index->defaultZone = (uint16_t)parseInteger(p, SEP, 0, header.count-1);
|
1999-12-05 05:55:28 +00:00
|
|
|
index->count = (uint16_t)parseInteger(p, SEP, 1, maxPerOffset);
|
|
|
|
uint16_t* zoneNumberArray = &(index->zoneNumber);
|
2000-05-18 22:08:39 +00:00
|
|
|
UBool sawOffset = FALSE; // Sanity check - make sure offset is in zone list
|
1999-12-05 05:55:28 +00:00
|
|
|
for (uint16_t j=0; j<index->count; ++j) {
|
|
|
|
zoneNumberArray[j] = (uint16_t)
|
|
|
|
parseInteger(p, (j==(index->count-1))?NUL:SEP,
|
|
|
|
0, header.count-1);
|
1999-12-09 06:29:40 +00:00
|
|
|
if (zoneNumberArray[j] == index->defaultZone) {
|
|
|
|
sawOffset = TRUE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!sawOffset) {
|
|
|
|
die("Error: bad offset index entry; default not in zone list");
|
1999-12-05 05:55:28 +00:00
|
|
|
}
|
1999-12-14 20:17:02 +00:00
|
|
|
alignedCount = index->count;
|
|
|
|
if((alignedCount%2)==0) /* force count to be ODD - see above */
|
|
|
|
{
|
|
|
|
// Use invalid zoneNumber for 2 bytes of padding
|
|
|
|
zoneNumberArray[alignedCount++] = (uint16_t)0xFFFF;
|
|
|
|
}
|
1999-12-14 00:43:24 +00:00
|
|
|
int8_t* nextIndex = (int8_t*)&(zoneNumberArray[alignedCount]);
|
2001-03-21 23:22:16 +00:00
|
|
|
|
2000-08-15 18:25:20 +00:00
|
|
|
index->nextEntryDelta = (uint16_t) ((i==(n-1)) ? 0 : (nextIndex - (int8_t*)index));
|
1999-12-05 05:55:28 +00:00
|
|
|
index = (OffsetIndex*)nextIndex;
|
|
|
|
}
|
|
|
|
offsetIndexSize = (int8_t*)index - (int8_t*)result;
|
|
|
|
if (offsetIndexSize > maxPossibleSize) {
|
|
|
|
die("Yikes! Interal error while constructing offset index table");
|
|
|
|
}
|
|
|
|
readEndMarker(in);
|
|
|
|
fprintf(stdout, " Read %lu offset index table entries, in-memory size %ld bytes\n",
|
|
|
|
n, offsetIndexSize);
|
|
|
|
return (OffsetIndex*)result;
|
|
|
|
}
|
|
|
|
|
2001-02-02 19:26:56 +00:00
|
|
|
CountryIndex* gentz::parseCountryIndexTable(FileStream* in) {
|
|
|
|
uint32_t n = readIntegerLine(in, 1, MAX_ZONES);
|
|
|
|
|
|
|
|
// We know how big the whole thing will be: Each zone occupies an
|
|
|
|
// int, and each country adds 3 ints (one for the intcode, one for
|
|
|
|
// next entry offset, one for the zone count). Each int is 16
|
|
|
|
// bits.
|
|
|
|
//
|
|
|
|
// Everything is 16-bits, so we don't 4-align the entries.
|
|
|
|
// However, we do pad at the end of the table to make the whole
|
|
|
|
// thing of size 4n, if necessary.
|
|
|
|
uint32_t expectedSize = n*(sizeof(CountryIndex)-sizeof(uint16_t)) +
|
|
|
|
header.count * sizeof(uint16_t);
|
|
|
|
uint32_t pad = (4 - (expectedSize % 4)) % 4; // This will be 0 or 2
|
|
|
|
int8_t *result = new int8_t[expectedSize + pad];
|
|
|
|
if (result == 0) {
|
|
|
|
die("Out of memory");
|
|
|
|
}
|
|
|
|
|
|
|
|
// Read each line and construct the corresponding entry.
|
|
|
|
// Along the way, make sure we don't write past 'limit'.
|
|
|
|
CountryIndex* index = (CountryIndex*)result;
|
|
|
|
int8_t* limit = ((int8_t*)result) + expectedSize; // Don't include pad
|
|
|
|
uint32_t i;
|
|
|
|
for (i=0; i<n && (int8_t*)(&index->zoneNumber) < limit; ++i) {
|
|
|
|
readLine(in);
|
|
|
|
char* p = buffer;
|
|
|
|
index->intcode = (uint16_t)parseInteger(p, SEP, 0, 25*32+25 /*ZZ*/);
|
|
|
|
index->count = (uint16_t)parseInteger(p, SEP, 0, header.count-1);
|
|
|
|
uint16_t* zoneNumberArray = &(index->zoneNumber);
|
|
|
|
if ((int8_t*)(&index->zoneNumber + index->count - 1) >= limit) {
|
|
|
|
// Oops -- out of space
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
for (uint16_t j=0; j<index->count; ++j) {
|
|
|
|
zoneNumberArray[j] = (uint16_t)
|
|
|
|
parseInteger(p, (j==(index->count-1))?NUL:SEP,
|
|
|
|
0, header.count-1);
|
|
|
|
}
|
|
|
|
int8_t* nextIndex = (int8_t*)&(zoneNumberArray[index->count]);
|
|
|
|
index->nextEntryDelta = (uint16_t) ((i==(n-1)) ? 0 : (nextIndex - (int8_t*)index));
|
|
|
|
index = (CountryIndex*)nextIndex;
|
|
|
|
}
|
|
|
|
readEndMarker(in);
|
|
|
|
|
|
|
|
// Make sure size matches expected value, and pad the total size
|
|
|
|
countryIndexSize = (int8_t*)index - (int8_t*)result + pad;
|
|
|
|
if (i != n || countryIndexSize != expectedSize) {
|
|
|
|
die("Yikes! Interal error while constructing offset index table");
|
|
|
|
}
|
|
|
|
if (pad != 0) {
|
|
|
|
countryIndexSize += pad;
|
|
|
|
*(uint16_t*)index = 0; // Clear pad bits
|
|
|
|
}
|
|
|
|
fprintf(stdout, " Read %lu country index table entries, in-memory size %ld bytes\n",
|
|
|
|
n, countryIndexSize);
|
|
|
|
return (CountryIndex*)result;
|
|
|
|
}
|
|
|
|
|
1999-11-30 23:05:49 +00:00
|
|
|
void gentz::parseHeader(FileStream* in) {
|
2000-09-27 16:37:26 +00:00
|
|
|
|
|
|
|
int32_t version = readIntegerLine(in, 0, 0xFFFF);
|
|
|
|
if (version != TZ_FORMAT_VERSION) {
|
|
|
|
die("Version mismatch between gentz and input file");
|
|
|
|
}
|
1999-12-05 05:55:28 +00:00
|
|
|
|
1999-11-30 23:05:49 +00:00
|
|
|
// Version string, e.g., "1999j" -> (1999<<16) | 10
|
1999-12-05 05:55:28 +00:00
|
|
|
header.versionYear = (uint16_t) readIntegerLine(in, 1990, 0xFFFF);
|
1999-11-30 23:05:49 +00:00
|
|
|
header.versionSuffix = (uint16_t) readIntegerLine(in, 0, 0xFFFF);
|
|
|
|
|
1999-12-05 05:55:28 +00:00
|
|
|
header.count = readIntegerLine(in, 1, MAX_ZONES);
|
2000-09-27 16:37:26 +00:00
|
|
|
equivCount = readIntegerLine(in, 1, header.count);
|
|
|
|
maxPerOffset = readIntegerLine(in, 1, header.count);
|
|
|
|
maxPerEquiv = readIntegerLine(in, 1, equivCount);
|
1999-11-30 23:05:49 +00:00
|
|
|
|
|
|
|
// Size of name table in bytes
|
|
|
|
// (0x00FFFFFF is an arbitrary upper limit; adjust as needed.)
|
|
|
|
nameTableSize = readIntegerLine(in, 1, 0x00FFFFFF);
|
1999-12-05 05:55:28 +00:00
|
|
|
|
|
|
|
readEndMarker(in);
|
1999-11-30 23:05:49 +00:00
|
|
|
|
2000-09-27 16:37:26 +00:00
|
|
|
fprintf(stdout, " Read header, data version %u(%u), in-memory size %ld bytes\n",
|
|
|
|
header.versionYear, header.versionSuffix, sizeof(header));
|
1999-11-30 23:05:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void gentz::parseDSTRule(char*& p, TZRule& rule) {
|
|
|
|
rule.month = (uint8_t) parseInteger(p, SEP, 0, 11);
|
|
|
|
rule.dowim = (int8_t) parseInteger(p, SEP, -31, 31);
|
|
|
|
rule.dow = (int8_t) parseInteger(p, SEP, -7, 7);
|
|
|
|
rule.time = (uint16_t) parseInteger(p, SEP, 0, 24*60);
|
|
|
|
rule.mode = *p++;
|
|
|
|
if (*p++ != SEP) {
|
|
|
|
die("Separator missing");
|
|
|
|
}
|
2000-03-10 01:52:03 +00:00
|
|
|
switch ((char)rule.mode) {
|
1999-11-30 23:05:49 +00:00
|
|
|
case 'w':
|
|
|
|
rule.mode = WALL_TIME;
|
|
|
|
break;
|
|
|
|
case 's':
|
|
|
|
rule.mode = STANDARD_TIME;
|
|
|
|
break;
|
|
|
|
case 'u':
|
|
|
|
rule.mode = UTC_TIME;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
die("Invalid rule time mode");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2000-09-27 16:37:26 +00:00
|
|
|
/**
|
|
|
|
* Parse the name table.
|
|
|
|
* Each entry of the name table looks like this:
|
|
|
|
* |36,Africa/Djibouti
|
|
|
|
* The integer is an equivalency table index. We build up a name
|
|
|
|
* table, that just contains the names, and we return it. We also
|
|
|
|
* build up the name index, which indexes names to equivalency table
|
|
|
|
* entries. This is stored in the member variable nameToEquiv.
|
|
|
|
*/
|
1999-11-30 23:05:49 +00:00
|
|
|
char* gentz::parseNameTable(FileStream* in) {
|
|
|
|
int32_t n = readIntegerLine(in, 1, MAX_ZONES);
|
1999-12-05 05:55:28 +00:00
|
|
|
if (n != (int32_t)header.count) {
|
1999-11-30 23:05:49 +00:00
|
|
|
die("Zone count doesn't match name table count");
|
|
|
|
}
|
|
|
|
char* names = new char[nameTableSize];
|
2000-09-27 16:37:26 +00:00
|
|
|
nameToEquiv = new uint32_t[n];
|
|
|
|
if (names == 0 || nameToEquiv == 0) {
|
1999-11-30 23:05:49 +00:00
|
|
|
die("Out of memory");
|
|
|
|
}
|
2000-09-27 16:37:26 +00:00
|
|
|
nameToEquivSize = n * sizeof(nameToEquiv[0]);
|
1999-11-30 23:05:49 +00:00
|
|
|
char* p = names;
|
|
|
|
char* limit = names + nameTableSize;
|
|
|
|
for (int32_t i=0; i<n; ++i) {
|
2000-09-27 16:37:26 +00:00
|
|
|
readLine(in);
|
|
|
|
char* q = buffer;
|
|
|
|
// We store an index here for now -- later, in fixNameToEquiv,
|
|
|
|
// we convert it to an offset.
|
|
|
|
nameToEquiv[i] = (uint32_t) parseInteger(q, SEP, 0, equivCount-1);
|
|
|
|
int32_t len = uprv_strlen(q);
|
1999-11-30 23:05:49 +00:00
|
|
|
if ((p + len) <= limit) {
|
2000-09-27 16:37:26 +00:00
|
|
|
uprv_memcpy(p, q, len);
|
1999-11-30 23:05:49 +00:00
|
|
|
p += len;
|
|
|
|
*p++ = NUL;
|
|
|
|
} else {
|
|
|
|
die("Name table longer than declared size");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (p != limit) {
|
|
|
|
die("Name table shorter than declared size");
|
|
|
|
}
|
1999-12-05 05:55:28 +00:00
|
|
|
readEndMarker(in);
|
|
|
|
fprintf(stdout, " Read %ld names, in-memory size %ld bytes\n", n, nameTableSize);
|
1999-11-30 23:05:49 +00:00
|
|
|
return names;
|
|
|
|
}
|
|
|
|
|
1999-12-05 05:55:28 +00:00
|
|
|
/**
|
|
|
|
* Read the end marker (terminates each list).
|
|
|
|
*/
|
|
|
|
void gentz::readEndMarker(FileStream* in) {
|
|
|
|
readLine(in);
|
1999-12-28 23:57:50 +00:00
|
|
|
if (uprv_strcmp(buffer, END_KEYWORD) != 0) {
|
1999-12-05 05:55:28 +00:00
|
|
|
die("Keyword 'end' missing");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
1999-11-30 23:05:49 +00:00
|
|
|
/**
|
|
|
|
* Read a line from the FileStream and parse it as an
|
|
|
|
* integer. There should be nothing else on the line.
|
|
|
|
*/
|
|
|
|
int32_t gentz::readIntegerLine(FileStream* in, int32_t min, int32_t max) {
|
|
|
|
readLine(in);
|
|
|
|
char* p = buffer;
|
|
|
|
return parseInteger(p, NUL, min, max);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Parse an integer from the given character buffer.
|
|
|
|
* Advance p past the last parsed character. Return
|
|
|
|
* the result. The integer must be of the form
|
|
|
|
* /-?\d+/.
|
|
|
|
*/
|
|
|
|
int32_t gentz::_parseInteger(char*& p) {
|
|
|
|
int32_t n = 0;
|
|
|
|
int32_t digitCount = 0;
|
|
|
|
int32_t digit;
|
2000-05-18 22:08:39 +00:00
|
|
|
UBool negative = FALSE;
|
1999-11-30 23:05:49 +00:00
|
|
|
if (*p == MINUS) {
|
|
|
|
++p;
|
|
|
|
negative = TRUE;
|
|
|
|
}
|
|
|
|
for (;;) {
|
|
|
|
digit = *p - ZERO;
|
|
|
|
if (digit < 0 || digit > 9) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
n = 10*n + digit;
|
|
|
|
p++;
|
|
|
|
digitCount++;
|
|
|
|
}
|
|
|
|
if (digitCount < 1) {
|
|
|
|
die("Unable to parse integer");
|
|
|
|
}
|
|
|
|
if (negative) {
|
|
|
|
n = -n;
|
|
|
|
}
|
|
|
|
return n;
|
|
|
|
}
|
|
|
|
|
|
|
|
int32_t gentz::parseInteger(char*& p, char nextExpectedChar,
|
|
|
|
int32_t min, int32_t max) {
|
|
|
|
int32_t n = _parseInteger(p);
|
|
|
|
if (*p++ != nextExpectedChar) {
|
|
|
|
die("Character following integer unexpected");
|
|
|
|
}
|
|
|
|
if (n < min || n > max) {
|
|
|
|
die("Integer field out of range");
|
|
|
|
}
|
|
|
|
return n;
|
|
|
|
}
|
|
|
|
|
|
|
|
void gentz::die(const char* msg) {
|
|
|
|
fprintf(stderr, "ERROR, %s\n", msg);
|
|
|
|
if (*buffer) {
|
1999-12-05 05:55:28 +00:00
|
|
|
fprintf(stderr, "Input file line %ld: \"%s\"\n", lineNumber, buffer);
|
1999-11-30 23:05:49 +00:00
|
|
|
}
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
|
2000-09-27 16:37:26 +00:00
|
|
|
/**
|
|
|
|
* Read a line. Trim trailing comment and whitespace. Ignore (skip)
|
|
|
|
* blank lines, or comment-only lines. Return the number of characters
|
|
|
|
* on the line remaining. On EOF, die.
|
|
|
|
*/
|
1999-11-30 23:05:49 +00:00
|
|
|
int32_t gentz::readLine(FileStream* in) {
|
1999-12-05 05:55:28 +00:00
|
|
|
++lineNumber;
|
2000-09-27 16:37:26 +00:00
|
|
|
char* result = T_FileStream_readLine(in, buffer, BUFLEN);
|
|
|
|
if (result == 0) {
|
|
|
|
*buffer = 0;
|
|
|
|
die("Unexpected end of file");
|
|
|
|
}
|
1999-11-30 23:05:49 +00:00
|
|
|
// Trim off trailing comment
|
1999-12-28 23:57:50 +00:00
|
|
|
char* p = uprv_strchr(buffer, COMMENT);
|
1999-11-30 23:05:49 +00:00
|
|
|
if (p != 0) {
|
|
|
|
*p = NUL;
|
|
|
|
}
|
2000-09-27 16:37:26 +00:00
|
|
|
// Delete trailing whitespace
|
1999-12-28 23:57:50 +00:00
|
|
|
p = buffer + uprv_strlen(buffer);
|
2000-09-27 16:37:26 +00:00
|
|
|
while (p > buffer && (p[-1] == CR || p[-1] == LF ||
|
|
|
|
p[-1] == SPACE || p[-1] == TAB)) {
|
1999-11-30 23:05:49 +00:00
|
|
|
p--;
|
|
|
|
}
|
|
|
|
*p = NUL;
|
2000-09-27 16:37:26 +00:00
|
|
|
// If line is empty after trimming comments & whitespace,
|
|
|
|
// then read the next line.
|
|
|
|
return (*buffer == NUL) ? readLine(in) : uprv_strlen(buffer);
|
1999-11-30 23:05:49 +00:00
|
|
|
}
|