From c5189ea483db36a48860594431f746975a7dd792 Mon Sep 17 00:00:00 2001 From: Jim Borden Date: Sat, 11 May 2019 14:59:33 +0900 Subject: [PATCH 1/4] Add support for parsing string date parts E.g. "year" or "day", in preparation for new N1QL functions --- Fleece/Support/ParseDate.cc | 34 ++++++++++++++++++++++++++++++++++ Fleece/Support/ParseDate.hh | 20 ++++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/Fleece/Support/ParseDate.cc b/Fleece/Support/ParseDate.cc index 904199af..30f67f67 100644 --- a/Fleece/Support/ParseDate.cc +++ b/Fleece/Support/ParseDate.cc @@ -71,7 +71,11 @@ #include #include #include +#include +#include +#include #include "PlatformCompat.hh" +using namespace std; typedef uint8_t u8; typedef int64_t sqlite3_int64; @@ -400,6 +404,20 @@ static int parseYyyyMmDd(const char *zDate, DateTime *p){ namespace fleece { + static map dateComponentMap = { + { "millennium", kDateComponentMillennium }, + { "century", kDateComponentCentury }, + { "decade", kDateComponentDecade }, + { "year", kDateComponentYear }, + { "quarter", kDateComponentQuarter }, + { "month", kDateComponentMonth }, + { "week", kDateComponentWeek }, + { "day", kDateComponentDay }, + { "hour", kDateComponentHour }, + { "minute", kDateComponentMinute }, + { "second", kDateComponentSecond }, + { "millisecond", kDateComponentMillisecond } + }; int64_t ParseISO8601Date(const char* zDate) { DateTime x; @@ -420,6 +438,22 @@ namespace fleece { return timestamp; } + DateComponent ParseDateComponent(slice component) { + string componentStr(component); + + // Warning, tolower on negative char is UB so first convert to unsigned. It doesn't matter + // if the result is nonsense since we are just using it as a lookup key + transform(componentStr.begin(), componentStr.end(), componentStr.begin(), + [](unsigned char c){ return std::tolower(c); }); + const auto entry = dateComponentMap.find(componentStr); + if(entry == dateComponentMap.end()) { + return kDateComponentInvalid; + } + + return entry->second; + } + + slice FormatISO8601Date(char buf[], int64_t time, bool asUTC) { if (time == kInvalidDate) { *buf = 0; diff --git a/Fleece/Support/ParseDate.hh b/Fleece/Support/ParseDate.hh index f6d9aa8e..803daaa7 100644 --- a/Fleece/Support/ParseDate.hh +++ b/Fleece/Support/ParseDate.hh @@ -24,6 +24,22 @@ namespace fleece { static constexpr int64_t kInvalidDate = INT64_MIN; + typedef enum { + kDateComponentMillennium, + kDateComponentCentury, + kDateComponentDecade, + kDateComponentYear, + kDateComponentQuarter, + kDateComponentMonth, + kDateComponentWeek, + kDateComponentDay, + kDateComponentHour, + kDateComponentMinute, + kDateComponentSecond, + kDateComponentMillisecond, + kDateComponentInvalid + } DateComponent; + /** Parses a C string as an ISO-8601 date-time, returning a timestamp (milliseconds since 1/1/1970), or kInvalidDate if the string is not valid. */ int64_t ParseISO8601Date(const char* dateStr); @@ -32,6 +48,10 @@ namespace fleece { 1/1/1970), or kInvalidDate if the string is not valid. */ int64_t ParseISO8601Date(slice dateStr); + /** Parses a C string as a date component (valid strings are represented by the DateComponent + enum above) */ + DateComponent ParseDateComponent(slice component); + /** Maximum length of a formatted ISO-8601 date. (Actually it's a bit bigger.) */ static constexpr size_t kFormattedISO8601DateMaxSize = 40; From 00327cfb6cbdb9512b88e282a18d406adf8bd347 Mon Sep 17 00:00:00 2001 From: Jim Borden Date: Wed, 15 May 2019 07:00:07 +0900 Subject: [PATCH 2/4] Make DateTime public and expose more options for parsing --- Fleece/Support/ParseDate.cc | 146 ++++++++++++++++++++++++++++-------- Fleece/Support/ParseDate.hh | 25 ++++++ 2 files changed, 141 insertions(+), 30 deletions(-) diff --git a/Fleece/Support/ParseDate.cc b/Fleece/Support/ParseDate.cc index 30f67f67..7638a5d4 100644 --- a/Fleece/Support/ParseDate.cc +++ b/Fleece/Support/ParseDate.cc @@ -86,23 +86,6 @@ typedef int64_t sqlite3_int64; #define LONG_MONTHS 0x15AA // 1 bits for months with 31 days -/* - ** A structure for holding a single date and time. - */ -typedef struct DateTime DateTime; -struct DateTime { - sqlite3_int64 iJD; /* The julian day number times 86400000 */ - int Y, M, D; /* Year, month, and day */ - int h, m; /* Hour and minutes */ - int tz; /* Timezone offset in minutes */ - double s; /* Seconds */ - char validYMD; /* True (1) if Y,M,D are valid */ - char validHMS; /* True (1) if h,m,s are valid */ - char validJD; /* True (1) if iJD is valid */ - char validTZ; /* True (1) if tz is valid */ -}; - - /* ** Convert zDate into one or more integers. Additional arguments ** come in groups of 5 as follows: @@ -168,7 +151,7 @@ static int getDigits(const char *zDate, ...){ ** ** A missing specifier is not considered an error. */ -static int parseTimezone(const char *zDate, DateTime *p){ +static int parseTimezone(const char *zDate, fleece::DateTime *p){ int sgn = 0; int nHr, nMn; int c; @@ -217,7 +200,7 @@ static int parseTimezone(const char *zDate, DateTime *p){ ** ** Return 1 if there is a parsing error and 0 on success. */ -static int parseHhMmSs(const char *zDate, DateTime *p){ +static int parseHhMmSs(const char *zDate, fleece::DateTime *p){ int h, m, s; double ms = 0.0; if( getDigits(zDate, 2, 0, 24, ':', &h, 2, 0, 59, 0, &m)!=2 ){ @@ -258,7 +241,7 @@ static int parseHhMmSs(const char *zDate, DateTime *p){ ** ** Reference: Meeus page 61 */ -static void computeJD(DateTime *p){ +static void computeJD(fleece::DateTime *p){ int Y, M, D, A, B, X1, X2; if( p->validJD ) return; @@ -292,7 +275,7 @@ static void computeJD(DateTime *p){ } } -static void inject_local_tz(DateTime* p) +static void inject_local_tz(fleece::DateTime* p) { #if !defined(_MSC_VER) || WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) // Let's hope this works on UWP since Microsoft has removed the @@ -349,7 +332,7 @@ static void inject_local_tz(DateTime* p) ** on success and 1 if the input string is not a well-formed ** date. */ -static int parseYyyyMmDd(const char *zDate, DateTime *p){ +static int parseYyyyMmDd(const char *zDate, fleece::DateTime *p, bool doJD){ int Y, M, D, neg; if( zDate[0]=='-' ){ @@ -390,10 +373,12 @@ static int parseYyyyMmDd(const char *zDate, DateTime *p){ p->Y = neg ? -Y : Y; p->M = M; p->D = D; - if( p->validTZ ){ - computeJD(p); - } else { - inject_local_tz(p); + if(doJD) { + if( p->validTZ ){ + computeJD(p); + } else { + inject_local_tz(p); + } } return 0; } @@ -402,6 +387,23 @@ static int parseYyyyMmDd(const char *zDate, DateTime *p){ #pragma mark END OF SQLITE CODE #pragma mark - +static size_t offset_to_str(int offset, char* buf) { + if(offset == 0) { + buf[0] = 'Z'; + return 1; + } + + const int hours = std::abs(offset) / 60; + const int minutes = std::abs(offset) % 60; + if(offset < 0) { + sprintf(buf, "-%02d:%02d", hours, minutes); + } else { + sprintf(buf, "+%02d:%02d", hours, minutes); + } + + return 6; +} + namespace fleece { static map dateComponentMap = { @@ -419,15 +421,64 @@ namespace fleece { { "millisecond", kDateComponentMillisecond } }; + DateTime ParseISO8601DateRaw(const char* zDate) { + DateTime x {0, 0, 0, 0, 0, 0, 0, 0.0, 0, 0, 0, 0}; + parseYyyyMmDd(zDate,&x,false); + + return x; + } + + DateTime ParseISO8601DateRaw(slice date) { + DateTime x {0, 0, 0, 0, 0, 0, 0, 0.0, 0, 0, 0, 0}; + auto cstr = static_cast(malloc(date.size + 1)); + if (!cstr) + return x; + + memcpy(cstr, date.buf, date.size); + cstr[date.size] = 0; + auto retVal = ParseISO8601DateRaw(cstr); + free(cstr); + return retVal; + } + + int64_t ToMillis(DateTime& dt) { + computeJD(&dt); + return dt.iJD - 210866760000000; + } + + DateTime FromMillis(int64_t time) { + // Split out the milliseconds from the timestamp: + time_t secs = time_t(time / 1000); + int millis = time % 1000; + + struct tm timebuf; + struct tm* result = gmtime_r(&secs, &timebuf); + return { + 0, + timebuf.tm_year + 1900, + timebuf.tm_mon + 1, + timebuf.tm_mday, + timebuf.tm_hour, + timebuf.tm_min, + 0, + (double)timebuf.tm_sec + millis / 1000.0, + 1, + 1, + 0, + 1 + }; + } + + int64_t ParseISO8601Date(const char* zDate) { DateTime x; - if (parseYyyyMmDd(zDate,&x)) + if (parseYyyyMmDd(zDate,&x,true)) return kInvalidDate; - computeJD(&x); - return x.iJD - 210866760000000; + + return ToMillis(x); } - int64_t ParseISO8601Date(fleece::slice date) { + int64_t ParseISO8601Date(slice date) { auto cstr = (char*)malloc(date.size + 1); if (!cstr) return kInvalidDate; @@ -494,4 +545,39 @@ namespace fleece { return {buf, len}; } + slice FormatISO8601Date(char buf[], int64_t time, int offset) { + if (time == kInvalidDate) { + *buf = 0; + return nullslice; + } + + time += offset * 60 * 1000; + + // Split out the milliseconds from the timestamp: + time_t secs = time_t(time / 1000); + int millis = time % 1000; + + // Format it, up to the seconds: + struct tm timebuf; + struct tm* result = gmtime_r(&secs, &timebuf); + if(result == nullptr) { + return nullslice; + } + + size_t len = strftime(buf, kFormattedISO8601DateMaxSize, "%FT%T", result); + + // Write the milliseconds: + if (millis > 0) { + size_t n = sprintf(buf + len, ".%03d", millis); + len += n; + } + + // Write the time-zone: + char *tz = buf + len; + len += offset_to_str(offset, tz); + + return {buf, len}; + } + + } diff --git a/Fleece/Support/ParseDate.hh b/Fleece/Support/ParseDate.hh index 803daaa7..373b6bd7 100644 --- a/Fleece/Support/ParseDate.hh +++ b/Fleece/Support/ParseDate.hh @@ -40,6 +40,30 @@ namespace fleece { kDateComponentInvalid } DateComponent; + /* + ** A structure for holding a single date and time. + */ + typedef struct DateTime DateTime; + struct DateTime { + int64_t iJD; /* The julian day number times 86400000 */ + int Y, M, D; /* Year, month, and day */ + int h, m; /* Hour and minutes */ + int tz; /* Timezone offset in minutes */ + double s; /* Seconds */ + char validYMD; /* True (1) if Y,M,D are valid */ + char validHMS; /* True (1) if h,m,s are valid */ + char validJD; /* True (1) if iJD is valid */ + char validTZ; /* True (1) if tz is valid */ + }; + + DateTime ParseISO8601DateRaw(const char* dateStr); + + DateTime ParseISO8601DateRaw(slice dateStr); + + int64_t ToMillis(DateTime& dt); + + DateTime FromMillis(int64_t millis); + /** Parses a C string as an ISO-8601 date-time, returning a timestamp (milliseconds since 1/1/1970), or kInvalidDate if the string is not valid. */ int64_t ParseISO8601Date(const char* dateStr); @@ -63,5 +87,6 @@ namespace fleece { @return The formatted string (points to `buf`). */ slice FormatISO8601Date(char buf[], int64_t timestamp, bool asUTC); + slice FormatISO8601Date(char buf[], int64_t timestamp, int tz); } From 4bbdd2a43617b257a577305d62f04ea5bf1c57e2 Mon Sep 17 00:00:00 2001 From: Jim Borden Date: Wed, 15 May 2019 15:13:15 +0900 Subject: [PATCH 3/4] Header cleanup --- Fleece/Support/ParseDate.hh | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/Fleece/Support/ParseDate.hh b/Fleece/Support/ParseDate.hh index 373b6bd7..d84e281b 100644 --- a/Fleece/Support/ParseDate.hh +++ b/Fleece/Support/ParseDate.hh @@ -24,19 +24,22 @@ namespace fleece { static constexpr int64_t kInvalidDate = INT64_MIN; + /* + ** Components of a date that can be extracted, diffed, or added using date-time functions + */ typedef enum { - kDateComponentMillennium, - kDateComponentCentury, - kDateComponentDecade, - kDateComponentYear, - kDateComponentQuarter, - kDateComponentMonth, - kDateComponentWeek, - kDateComponentDay, - kDateComponentHour, - kDateComponentMinute, - kDateComponentSecond, - kDateComponentMillisecond, + kDateComponentMillennium, /* 1000 years */ + kDateComponentCentury, /* 100 years */ + kDateComponentDecade, /* 10 years */ + kDateComponentYear, /* 146097 / 400 days */ + kDateComponentQuarter, /* 3 months */ + kDateComponentMonth, /* 1/12 years */ + kDateComponentWeek, /* 7 days */ + kDateComponentDay, /* 24 hours */ + kDateComponentHour, /* 60 minutes */ + kDateComponentMinute, /* 60 seconds */ + kDateComponentSecond, /* 1000 milliseconds */ + kDateComponentMillisecond, /* base unit */ kDateComponentInvalid } DateComponent; @@ -56,12 +59,18 @@ namespace fleece { char validTZ; /* True (1) if tz is valid */ }; + /** Parses a C string as an ISO-8601 date-time, returning a parsed DateTime struct */ DateTime ParseISO8601DateRaw(const char* dateStr); + /** Parses a C string as an ISO-8601 date-time, returning a parsed DateTime struct */ DateTime ParseISO8601DateRaw(slice dateStr); + /** Converts an existing DateTime struct into a timestamp (milliseconds since + 1/1/1970) */ int64_t ToMillis(DateTime& dt); + /** Converts a timestamp (milliseconds since 1/1/1970) into a parsed DateTime struct + in UTC time */ DateTime FromMillis(int64_t millis); /** Parses a C string as an ISO-8601 date-time, returning a timestamp (milliseconds since From 435cf0728d616036f11a591d7651613ad8e78272 Mon Sep 17 00:00:00 2001 From: Jim Borden Date: Thu, 16 May 2019 11:49:34 +0900 Subject: [PATCH 4/4] Expand DateTime to capture formatting info And reuse it to format timestamps in a dynamic way --- Fleece/Core/Encoder.cc | 2 +- Fleece/Support/JSONEncoder.cc | 2 +- Fleece/Support/ParseDate.cc | 135 +++++++++++++++++++++++++--------- Fleece/Support/ParseDate.hh | 17 ++++- 4 files changed, 116 insertions(+), 40 deletions(-) diff --git a/Fleece/Core/Encoder.cc b/Fleece/Core/Encoder.cc index 038a1b62..a038ef6c 100644 --- a/Fleece/Core/Encoder.cc +++ b/Fleece/Core/Encoder.cc @@ -390,7 +390,7 @@ namespace fleece { namespace impl { void Encoder::writeDateString(int64_t timestamp, bool asUTC) { char str[kFormattedISO8601DateMaxSize]; - writeString(FormatISO8601Date(str, timestamp, asUTC)); + writeString(FormatISO8601Date(str, timestamp, asUTC, nullptr)); } diff --git a/Fleece/Support/JSONEncoder.cc b/Fleece/Support/JSONEncoder.cc index 57103356..9a59d3dd 100644 --- a/Fleece/Support/JSONEncoder.cc +++ b/Fleece/Support/JSONEncoder.cc @@ -67,7 +67,7 @@ namespace fleece { namespace impl { void JSONEncoder::writeDateString(int64_t timestamp, bool asUTC) { char str[kFormattedISO8601DateMaxSize]; - writeString(FormatISO8601Date(str, timestamp, asUTC)); + writeString(FormatISO8601Date(str, timestamp, asUTC, nullptr)); } diff --git a/Fleece/Support/ParseDate.cc b/Fleece/Support/ParseDate.cc index 7638a5d4..aba58475 100644 --- a/Fleece/Support/ParseDate.cc +++ b/Fleece/Support/ParseDate.cc @@ -357,11 +357,15 @@ static int parseYyyyMmDd(const char *zDate, fleece::DateTime *p, bool doJD){ } } zDate += 10; - while( sqlite3Isspace(*zDate) || 'T'==*(u8*)zDate ){ zDate++; } + while( sqlite3Isspace(*zDate) || 'T'==*(u8*)zDate ) { + // N1QL behavior, if even one T is present, then the resulting format should be 'T', not ' ' + if(*zDate == 'T' || !p->separator) p->separator = *zDate; + zDate++; + } if( parseHhMmSs(zDate, p)==0 ){ /* We got the time */ }else if( *zDate==0 ){ - p->validHMS = 1; + p->validHMS = 0; p->h = p->m = 0; p->s = 0.0; p->validTZ = 0; @@ -422,14 +426,16 @@ namespace fleece { }; DateTime ParseISO8601DateRaw(const char* zDate) { - DateTime x {0, 0, 0, 0, 0, 0, 0, 0.0, 0, 0, 0, 0}; - parseYyyyMmDd(zDate,&x,false); + DateTime x {0, 0, 0, 0, 0, 0, 0, 0.0, 0, 0, 0, 0, 0}; + if(parseYyyyMmDd(zDate,&x,false)) { + parseHhMmSs(zDate, &x); + } return x; } DateTime ParseISO8601DateRaw(slice date) { - DateTime x {0, 0, 0, 0, 0, 0, 0, 0.0, 0, 0, 0, 0}; + DateTime x {0, 0, 0, 0, 0, 0, 0, 0.0, 0, 0, 0, 0, 0}; auto cstr = static_cast(malloc(date.size + 1)); if (!cstr) return x; @@ -441,7 +447,17 @@ namespace fleece { return retVal; } - int64_t ToMillis(DateTime& dt) { + int64_t ToMillis(DateTime& dt, bool no_tz) { + if(!dt.validHMS) { + dt.h = dt.m = 0; + dt.s = 0.0; + dt.validHMS = 1; + } + + if(!no_tz && !dt.validTZ) { + inject_local_tz(&dt); + } + computeJD(&dt); return dt.iJD - 210866760000000; } @@ -505,7 +521,7 @@ namespace fleece { } - slice FormatISO8601Date(char buf[], int64_t time, bool asUTC) { + slice FormatISO8601Date(char buf[], int64_t time, bool asUTC, const DateTime* format) { if (time == kInvalidDate) { *buf = 0; return nullslice; @@ -518,34 +534,59 @@ namespace fleece { // Format it, up to the seconds: struct tm timebuf; struct tm* result = asUTC ? gmtime_r(&secs, &timebuf) : localtime_r(&secs, &timebuf); - size_t len = strftime(buf, kFormattedISO8601DateMaxSize, "%FT%T", result); + size_t len = 0; + bool ymd = true; + bool hms = true; + bool zone = true; + char separator = 'T'; + if(format) { + ymd = format->validYMD; + hms = format->validHMS; + zone = format->validTZ; + separator = format->separator; + } - // Write the milliseconds: - if (millis > 0) { - size_t n = sprintf(buf + len, ".%03d", millis); - len += n; + if(ymd) { + len += strftime(buf, kFormattedISO8601DateMaxSize, "%F", result); } - // Write the time-zone: - char *tz = buf + len; - if (asUTC) { - strcpy(tz, "Z"); - ++len; - } else { - size_t added = strftime(tz, 6, "%z", &timebuf); - - // It would be nice to use tm_gmtoff but that's not available everywhere... - if(strncmp("0000", tz + 1, 4) == 0) { - strcpy(tz, "Z"); - ++len; - } else { - len += added; + if(hms) { + if(ymd) { + buf[len] = separator; + len++; + } + + len += strftime(buf + len, kFormattedISO8601DateMaxSize - len, "%T", result); + + // Write the milliseconds: + if (millis > 0) { + len += sprintf(buf + len, ".%03d", millis); + } + + if(zone) { + // Write the time-zone: + char *tz = buf + len; + if (asUTC) { + strcpy(tz, "Z"); + ++len; + } else { + size_t added = strftime(tz, 6, "%z", &timebuf); + + // It would be nice to use tm_gmtoff but that's not available everywhere... + if(strncmp("0000", tz + 1, 4) == 0) { + strcpy(tz, "Z"); + ++len; + } else { + len += added; + } + } } } + return {buf, len}; } - slice FormatISO8601Date(char buf[], int64_t time, int offset) { + slice FormatISO8601Date(char buf[], int64_t time, int offset, const DateTime* format) { if (time == kInvalidDate) { *buf = 0; return nullslice; @@ -564,17 +605,41 @@ namespace fleece { return nullslice; } - size_t len = strftime(buf, kFormattedISO8601DateMaxSize, "%FT%T", result); + size_t len = 0; + bool ymd = true; + bool hms = true; + bool zone = true; + char separator = 'T'; + if(format) { + ymd = format->validYMD; + hms = format->validHMS; + zone = format->validTZ; + separator = format->separator; + } - // Write the milliseconds: - if (millis > 0) { - size_t n = sprintf(buf + len, ".%03d", millis); - len += n; + if(ymd) { + len += strftime(buf, kFormattedISO8601DateMaxSize, "%F", result); } - // Write the time-zone: - char *tz = buf + len; - len += offset_to_str(offset, tz); + if(hms) { + if(ymd) { + buf[len] = format->separator; + len++; + } + + len += strftime(buf + len, kFormattedISO8601DateMaxSize - len, "%T", result); + + // Write the milliseconds: + if (millis > 0) { + len += sprintf(buf + len, ".%03d", millis); + } + + if(zone) { + // Write the time-zone: + char *tz = buf + len; + len += offset_to_str(offset, tz); + } + } return {buf, len}; } diff --git a/Fleece/Support/ParseDate.hh b/Fleece/Support/ParseDate.hh index d84e281b..a43852bb 100644 --- a/Fleece/Support/ParseDate.hh +++ b/Fleece/Support/ParseDate.hh @@ -57,6 +57,7 @@ namespace fleece { char validHMS; /* True (1) if h,m,s are valid */ char validJD; /* True (1) if iJD is valid */ char validTZ; /* True (1) if tz is valid */ + char separator; /* The character used to separate the date and time (T or space) */ }; /** Parses a C string as an ISO-8601 date-time, returning a parsed DateTime struct */ @@ -67,7 +68,7 @@ namespace fleece { /** Converts an existing DateTime struct into a timestamp (milliseconds since 1/1/1970) */ - int64_t ToMillis(DateTime& dt); + int64_t ToMillis(DateTime& dt, bool no_tz = false); /** Converts a timestamp (milliseconds since 1/1/1970) into a parsed DateTime struct in UTC time */ @@ -93,9 +94,19 @@ namespace fleece { kFormattedISO8601DateMaxSize bytes must be available. @param timestamp The timestamp (milliseconds since 1/1/1970). @param asUTC True to format as UTC, false to use the local time-zone. + @param format The model to use for formatting (i.e. which portions to include). + If null, then the full ISO-8601 format is used @return The formatted string (points to `buf`). */ - slice FormatISO8601Date(char buf[], int64_t timestamp, bool asUTC); + slice FormatISO8601Date(char buf[], int64_t timestamp, bool asUTC, const DateTime* format); - slice FormatISO8601Date(char buf[], int64_t timestamp, int tz); + /** Formats a timestamp (milliseconds since 1/1/1970) as an ISO-8601 date-time. + @param buf The location to write the formatted C string. At least + kFormattedISO8601DateMaxSize bytes must be available. + @param timestamp The timestamp (milliseconds since 1/1/1970). + @param tz The timezone offset from UTC in minutes + @param format The model to use for formatting (i.e. which portions to include). + If null, then the full ISO-8601 format is used + @return The formatted string (points to `buf`). */ + slice FormatISO8601Date(char buf[], int64_t timestamp, int tz, const DateTime* format); }