timezone updates for version2 files

This commit is contained in:
Richard Frith-Macdonald 2021-08-11 08:43:20 +01:00
parent 25b25080c3
commit 5162d888dc
10 changed files with 2542 additions and 286 deletions

View file

@ -1,3 +1,21 @@
2021-08-11 Richard Frith-Macdonald <rfm@gnu.org>
* Source/tzdb.h: Public domain time zone file parsing etc
* Source/NSTimeZone.m: Use code from tzdb.h
* Headers/Foundation/NSTimeZone.h: Remove some obsolete comments
* configure.ac: check for stdbool.h needed by tzdb.h
* configure: regenerate
* Headers/GNUstepBase/config.h.in: define for stdbool check
* Tests/base/NSTimeZone/Paris.tzdbv1: test timezone file (version 1)
* Tests/base/NSTimeZone/Paris.tzdbv2: test timezone file (version 2)
* Tests/base/NSTimeZone/localtime.m: Some testcases for v1/v2
Incorporate work by Emmanuel Dreyfus to use public domain tzfile
parsing code supporting times too large to fit in 32bit value.
The public domain code has some support for the Posix TZ environment
variable style string used by version2 to support times after the last
transition in the file, but I'm not convinced it works properly, so
this may need to be revisited.
2021-08-03 Frederik Seiffert <frederik@algoriddim.com>
* Headers/GNUstepBase/config.h.in:

View file

@ -49,19 +49,19 @@ GS_EXPORT NSString * const NSSystemTimeZoneDidChangeNotification;
GS_EXPORT_CLASS
@interface NSTimeZone : NSObject
//Creating and Initializing an NSTimeZone
// Creating and Initializing an NSTimeZone
+ (NSTimeZone*) localTimeZone;
+ (NSTimeZone*) timeZoneForSecondsFromGMT: (NSInteger)seconds;
+ (NSTimeZone*) timeZoneWithName: (NSString*)aTimeZoneName;
//Managing Time Zones
// Managing Time Zones
+ (void) setDefaultTimeZone: (NSTimeZone*)aTimeZone;
// Getting Time Zone Information
+ (NSDictionary*) abbreviationDictionary;
+ (NSArray*) knownTimeZoneNames;
//Getting Arrays of Time Zones
// Getting Arrays of Time Zones
+ (NSArray*) timeZoneArray;
- (NSArray*) timeZoneDetailArray;
@ -89,16 +89,16 @@ GS_EXPORT_CLASS
#endif
#if OS_API_VERSION(MAC_OS_X_VERSION_10_5,GS_API_LATEST)
- (NSTimeInterval) daylightSavingTimeOffsetForDate: (NSDate *)aDate;
/** Not implemented */
- (NSDate *) nextDaylightSavingTimeTransitionAfterDate: (NSDate *)aDate;
- (NSTimeInterval) daylightSavingTimeOffsetForDate: (NSDate*)aDate;
- (NSDate*) nextDaylightSavingTimeTransitionAfterDate: (NSDate*)aDate;
- (NSTimeInterval) daylightSavingTimeOffset;
/** Not implemented */
- (NSDate *) nextDaylightSavingTimeTransition;
- (NSString *)localizedName: (NSTimeZoneNameStyle)style
locale: (NSLocale *)locale;
- (NSDate*) nextDaylightSavingTimeTransition;
- (NSString*) localizedName: (NSTimeZoneNameStyle)style
locale: (NSLocale*)locale;
#endif
#if OS_API_VERSION(GS_API_OPENSTEP, GS_API_MACOSX)

View file

@ -584,6 +584,9 @@
/* Define to 1 if you have the `statvfs' function. */
#undef HAVE_STATVFS
/* Define to 1 if you have the <stdbool.h> header file. */
#undef HAVE_STDBOOL_H
/* Define to 1 if you have the <stdint.h> header file. */
#undef HAVE_STDINT_H

View file

@ -165,6 +165,11 @@
#define BUFFER_SIZE 512
#define WEEK_MILLISECONDS (7.0*24.0*60.0*60.0*1000.0)
/* Include public domain code (modified for use here) to parse standard
* posix time zone files.
*/
#include "tzdb.h"
#if GS_USE_ICU == 1
static inline int
_NSToICUTZDisplayStyle(NSTimeZoneNameStyle style)
@ -243,20 +248,15 @@ typedef struct {
BOOL isdst;
unsigned char abbr_idx;
char pad[2];
NSString *abbreviation;
const char *abbreviation;
} TypeInfo;
@interface GSTimeZone : NSTimeZone
{
@public
NSString *timeZoneName;
NSArray *abbreviations;
NSData *timeZoneData;
unsigned int n_trans;
unsigned int n_types;
int64_t *trans;
TypeInfo *types;
unsigned char *idxs;
struct state *sp;
}
@end
@ -2298,8 +2298,8 @@ localZoneString, [zone name], sign, s/3600, (s/60)%60);
return [self nextDaylightSavingTimeTransitionAfterDate: [NSDate date]];
}
- (NSString *)localizedName: (NSTimeZoneNameStyle)style
locale: (NSLocale *)locale
- (NSString *) localizedName: (NSTimeZoneNameStyle)style
locale: (NSLocale *)locale
{
#if GS_USE_ICU == 1
UChar *result;
@ -2905,87 +2905,56 @@ GSBreakTime(NSTimeInterval when,
@implementation GSTimeZone
/**
* Perform a binary search of a transitions table to locate the index
* of the transition to use for a particular time interval since 1970.<br />
* We locate the index of the highest transition before the date, or zero
* if there is no transition before it.
*/
static TypeInfo*
chop(NSTimeInterval since, GSTimeZone *zone)
{
int64_t when = (int64_t)since;
int64_t *trans = zone->trans;
unsigned hi = zone->n_trans;
unsigned lo = 0;
unsigned int i;
if (hi == 0 || trans[0] > when)
{
unsigned n_types = zone->n_types;
/*
* If the first transition is greater than our date,
* we locate the first non-DST transition and use that offset,
* or just use the first transition.
*/
for (i = 0; i < n_types; i++)
{
if (zone->types[i].isdst == 0)
{
return &zone->types[i];
}
}
return &zone->types[0];
}
else
{
for (i = hi/2; hi != lo; i = (hi + lo)/2)
{
if (when < trans[i])
{
hi = i;
}
else if (when > trans[i])
{
lo = ++i;
}
else
{
break;
}
}
/*
* If we went off the top of the table or the closest transition
* was later than our date, we step back to find the last
* transition before our date.
*/
if (i > 0 && (i == zone->n_trans || trans[i] > when))
{
i--;
}
return &zone->types[zone->idxs[i]];
}
}
static NSTimeZoneDetail*
newDetailInZoneForType(GSTimeZone *zone, TypeInfo *type)
{
GSTimeZoneDetail *detail;
{
GSTimeZoneDetail *detail;
NSString *abbr;
detail = [GSTimeZoneDetail alloc];
detail = [detail initWithTimeZone: zone
withAbbrev: type->abbreviation
withOffset: type->offset
withDST: type->isdst];
abbr = [[NSString alloc] initWithUTF8String: type->abbreviation];
detail = [[GSTimeZoneDetail alloc] initWithTimeZone: zone
withAbbrev: abbr
withOffset: type->offset
withDST: type->isdst];
RELEASE(abbr);
return detail;
}
/**
* Obtain TypeInfo from transisions or TZstring
*/
static TypeInfo
getTypeInfo(NSTimeInterval since, GSTimeZone *zone)
{
int64_t when = (int64_t)since;
struct tm tm;
TypeInfo type;
if (localsub(zone->sp, &when, 0, &tm) == NULL)
{
memset(&type, '\0', sizeof(type));
}
else
{
type.offset = tm.tm_gmtoff;
type.isdst = tm.tm_isdst;
type.abbr_idx = 0;
type.abbreviation = tm.tm_zone;
}
return type;
}
- (NSString*) abbreviationForDate: (NSDate*)aDate
{
TypeInfo *type = chop([aDate timeIntervalSince1970], self);
TypeInfo type = getTypeInfo([aDate timeIntervalSince1970], self);
return type->abbreviation;
if (NULL == type.abbreviation)
{
return nil;
}
return [NSString stringWithUTF8String: type.abbreviation];
}
- (NSData*) data
@ -2993,14 +2962,18 @@ newDetailInZoneForType(GSTimeZone *zone, TypeInfo *type)
return timeZoneData;
}
- (NSTimeInterval) daylightSavingTimeOffsetForDate: (NSDate *)aDate
{
return [super daylightSavingTimeOffsetForDate: aDate];
}
- (void) dealloc
{
RELEASE(timeZoneName);
RELEASE(timeZoneData);
RELEASE(abbreviations);
if (types != 0)
if (sp != 0)
{
NSZoneFree(NSDefaultMallocZone(), types);
free(sp);
}
[super dealloc];
}
@ -3008,220 +2981,80 @@ newDetailInZoneForType(GSTimeZone *zone, TypeInfo *type)
- (id) initWithName: (NSString*)name data: (NSData*)data
{
static NSString *fileException = @"GSTimeZoneFileException";
union local_storage *lsp;
const char *zoneName;
/* The placeholder class should have dealt with loading the data
*/
NSAssert([data isKindOfClass: [NSData class]], NSInvalidArgumentException);
NSAssert([name isKindOfClass: [NSString class]], NSInvalidArgumentException);
timeZoneName = [name copy];
timeZoneData = [data copy];
zoneName = [timeZoneName UTF8String];
sp = malloc(sizeof(*sp));
NS_DURING
{
const void *bytes = [timeZoneData bytes];
unsigned length = [timeZoneData length];
void *buf;
unsigned pos = 0;
unsigned i, charcnt;
unsigned char *abbr;
struct tzhead *header;
unsigned version = 1;
size_t nread;
union input_buffer *up;
if (length < sizeof(struct tzhead))
lsp = malloc(sizeof(*lsp));
up = &lsp->u.u;
nread = [data length];
if (nread > sizeof(up->buf))
{
[NSException raise: fileException
format: @"File is too small"];
format: @"data too large"];
}
header = (struct tzhead *)(bytes + pos);
pos += sizeof(struct tzhead);
[data getBytes: up->buf];
if (memcmp(header->tzh_magic, TZ_MAGIC, strlen(TZ_MAGIC)) != 0)
if (tzloadbody1(zoneName, sp, true, lsp, nread) != 0)
{
[NSException raise: fileException
format: @"TZ_MAGIC is incorrect"];
format: @"cannot load data"];
}
free(lsp);
lsp = NULL;
/* For version 2+, skip to second header */
if (header->tzh_version[0] != 0)
{
/* tzh_version[0] is an ascii digit for versions above 1.
*/
version = header->tzh_version[0] - '0';
/* pos indexes after the first header, increment it to index after
* the data associated with that header too.
*/
pos += GSSwapBigI32ToHost(*(int32_t*)(void*)header->tzh_timecnt) * 5
+ GSSwapBigI32ToHost(*(int32_t*)(void*)header->tzh_typecnt) * 6
+ GSSwapBigI32ToHost(*(int32_t*)(void*)header->tzh_charcnt)
+ GSSwapBigI32ToHost(*(int32_t*)(void*)header->tzh_leapcnt) * 8
+ GSSwapBigI32ToHost(*(int32_t*)(void*)header->tzh_ttisstdcnt)
+ GSSwapBigI32ToHost(*(int32_t*)(void*)header->tzh_ttisutcnt);
/* Now we get a pointer to the version 2+ header and set pos as an
* index to the data after that.
*/
header = (struct tzhead *)(bytes + pos);
pos += sizeof(struct tzhead);
if (memcmp(header->tzh_magic, TZ_MAGIC, strlen(TZ_MAGIC)) != 0)
{
[NSException raise: fileException
format: @"TZ_MAGIC is incorrect (v2+ header)"];
}
}
n_trans = GSSwapBigI32ToHost(*(int32_t*)(void*)header->tzh_timecnt);
n_types = GSSwapBigI32ToHost(*(int32_t*)(void*)header->tzh_typecnt);
charcnt = GSSwapBigI32ToHost(*(int32_t*)(void*)header->tzh_charcnt);
i = pos;
if (1 == version)
{
/* v1 with 32-bit transitions */
i += sizeof(int32_t) * n_trans;
}
else
{
/* v2+ with 64-bit transitions */
i += sizeof(int64_t) * n_trans;
}
if (i > length)
#if 0
if (!tzparse(zoneName, sp, false))
{
[NSException raise: fileException
format: @"Transitions list is truncated"];
format: @"failed to parse zone data"];
}
i += n_trans;
if (i > length)
{
[NSException raise: fileException
format: @"Transition indexes are truncated"];
}
i += sizeof(struct ttinfo)*n_types;
if (i > length)
{
[NSException raise: fileException
format: @"Types list is truncated"];
}
if (i + charcnt > length)
{
[NSException raise: fileException
format: @"Abbreviations list is truncated"];
}
/*
* Now calculate size we need to store the information
* for efficient access ... not the same saze as the data
* we received (we always use 64bit transitions internally).
*/
i = n_trans * (sizeof(int64_t)+1) + n_types * sizeof(TypeInfo);
buf = NSZoneMalloc(NSDefaultMallocZone(), i);
types = (TypeInfo*)buf;
buf += (n_types * sizeof(TypeInfo));
trans = (int64_t*)buf;
buf += (n_trans * sizeof(int64_t));
idxs = (unsigned char*)buf;
/* Read in transitions. */
if (1 == version)
{
/* v1 with 32-bit transitions */
for (i = 0; i < n_trans; i++)
{
trans[i] = GSSwapBigI32ToHost(*(int32_t*)(bytes + pos));
pos += sizeof(int32_t);
}
}
else
{
/* v2+ with 64-bit transitions */
for (i = 0; i < n_trans; i++)
{
trans[i] = GSSwapBigI64ToHost(*(int64_t*)(bytes + pos));
pos += sizeof(int64_t);
}
}
for (i = 0; i < n_trans; i++)
{
idxs[i] = *(unsigned char*)(bytes + pos);
pos++;
}
for (i = 0; i < n_types; i++)
{
struct ttinfo *ptr = (struct ttinfo*)(bytes + pos);
uint32_t off;
types[i].isdst = (ptr->isdst != 0 ? YES : NO);
types[i].abbr_idx = ptr->abbr_idx;
memcpy(&off, ptr->offset, 4);
types[i].offset = GSSwapBigI32ToHost(off);
pos += sizeof(struct ttinfo);
}
abbr = (unsigned char*)(bytes + pos);
{
id abbrevs[charcnt];
unsigned count = 0;
unsigned used = 0;
memset(abbrevs, '\0', sizeof(id)*charcnt);
for (i = 0; i < n_types; i++)
{
int loc = types[i].abbr_idx;
if (abbrevs[loc] == nil)
{
abbrevs[loc]
= [[NSString alloc] initWithUTF8String: (char*)abbr + loc];
count++;
}
types[i].abbreviation = abbrevs[loc];
}
/*
* Now we have created all the abbreviations, we put them in an
* array for easy access later and easy deallocation if/when
* the receiver is deallocated.
*/
i = charcnt;
while (i-- > count)
{
if (abbrevs[i] != nil)
{
while (abbrevs[used] != nil)
{
used++;
}
abbrevs[used] = abbrevs[i];
abbrevs[i] = nil;
if (++used >= count)
{
break;
}
}
}
abbreviations = [[NSArray alloc] initWithObjects: abbrevs count: count];
while (count-- > 0)
{
RELEASE(abbrevs[count]);
}
}
GS_MUTEX_LOCK(zone_mutex);
[zoneDictionary setObject: self forKey: timeZoneName];
GS_MUTEX_UNLOCK(zone_mutex);
#endif
scrub_abbrs(sp);
}
NS_HANDLER
{
if (lsp)
{
free(lsp);
}
DESTROY(self);
NSLog(@"Unable to obtain time zone `%@'... %@", name, localException);
NSLog(@"Unable to obtain time zone `%@'... %@",
name, localException);
if ([localException name] != fileException)
{
[localException raise];
}
return nil;
}
NS_ENDHANDLER
GS_MUTEX_LOCK(zone_mutex);
[zoneDictionary setObject: self forKey: timeZoneName];
GS_MUTEX_UNLOCK(zone_mutex);
return self;
}
- (BOOL) isDaylightSavingTimeForDate: (NSDate*)aDate
{
TypeInfo *type = chop([aDate timeIntervalSince1970], self);
TypeInfo type = getTypeInfo([aDate timeIntervalSince1970], self);
return type->isdst;
return type.isdst;
}
- (NSString*) name
@ -3229,25 +3062,38 @@ newDetailInZoneForType(GSTimeZone *zone, TypeInfo *type)
return timeZoneName;
}
- (NSDate *) nextDaylightSavingTimeTransitionAfterDate: (NSDate *)aDate
{
return [super nextDaylightSavingTimeTransitionAfterDate: aDate];
}
- (NSInteger) secondsFromGMTForDate: (NSDate*)aDate
{
TypeInfo *type = chop([aDate timeIntervalSince1970], self);
TypeInfo type = getTypeInfo([aDate timeIntervalSince1970], self);
return type->offset;
return type.offset;
}
- (NSArray*) timeZoneDetailArray
{
NSTimeZoneDetail *details[n_types];
NSTimeZoneDetail *details[sp->typecnt];
unsigned i;
NSArray *array;
for (i = 0; i < n_types; i++)
for (i = 0; i < sp->typecnt; i++)
{
details[i] = newDetailInZoneForType(self, &types[i]);
const struct _ttinfo *const ttisp = &sp->ttis[i];
const char *abbr = &sp->chars[ttisp->tt_desigidx];
TypeInfo type;
type.offset = ttisp->tt_utoff;
type.isdst = ttisp->tt_isdst;
type.abbr_idx = ttisp->tt_desigidx;
type.abbreviation = abbr;
details[i] = newDetailInZoneForType(self, &type);
}
array = [NSArray arrayWithObjects: details count: n_types];
for (i = 0; i < n_types; i++)
array = [NSArray arrayWithObjects: details count: sp->typecnt];
for (i = 0; i < sp->typecnt; i++)
{
RELEASE(details[i]);
}
@ -3256,11 +3102,11 @@ newDetailInZoneForType(GSTimeZone *zone, TypeInfo *type)
- (NSTimeZoneDetail*) timeZoneDetailForDate: (NSDate*)aDate
{
TypeInfo *type;
TypeInfo type;
NSTimeZoneDetail *detail;
type = chop([aDate timeIntervalSince1970], self);
detail = newDetailInZoneForType(self, type);
type = getTypeInfo([aDate timeIntervalSince1970], self);
detail = newDetailInZoneForType(self, &type);
return AUTORELEASE(detail);
}

2292
Source/tzdb.h Normal file

File diff suppressed because it is too large Load diff

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,96 @@
#import "ObjectTesting.h"
#include <Foundation/NSTimeZone.h>
#define PREFIX "./"
int main(void)
{
NSAutoreleasePool *arp = [NSAutoreleasePool new];
NSTimeZone *timeZone;
NSDate *date;
NSData *tzdata;
timeZone = [NSTimeZone timeZoneWithName: @"Europe/Paris"];
PASS(timeZone != nil && [timeZone isKindOfClass: [NSTimeZone class]],
"+timeZoneWithName works");
/* Before last transition in TZDB v2+ file */
date = [NSDate dateWithString: @"1981-01-16 23:59:59 -0100"];
PASS([timeZone secondsFromGMTForDate: date] == 3600,
"pre-1996 standard time offset vs UTC found for System Europe/Paris");
date = [NSDate dateWithString: @"1981-08-16 23:59:59 -0200"];
PASS([timeZone secondsFromGMTForDate: date] == 7200,
"pre-1996 DST time offset vs UTC found for System Europe/Paris");
/* After last transition in TZDB v2+ file */
date = [NSDate dateWithString: @"2021-01-16 23:59:59 -0100"];
PASS([timeZone secondsFromGMTForDate: date] == 3600,
"post-1996 standard time offset vs UTC found for System Europe/Paris");
date = [NSDate dateWithString: @"2021-08-16 23:59:59 -0200"];
PASS([timeZone secondsFromGMTForDate: date] == 7200,
"post-1996 DST time offset vs UTC found for System Europe/Paris");
/* Test using TZDB file known as being v1 */
tzdata = [NSData dataWithContentsOfFile: @ PREFIX "Paris.tzdbv1"];
PASS(tzdata != nil && [tzdata isKindOfClass: [NSData class]] &&
[tzdata length] > 0, "Loading user-supplied Paris TZDBv1 works");
timeZone = [NSTimeZone timeZoneWithName: @"Paris.tzdb1" data: tzdata];
PASS(timeZone != nil && [timeZone isKindOfClass: [NSTimeZone class]],
"+timeZoneWithName data works");
/* Before last transition in TZDB v2+ file */
date = [NSDate dateWithString: @"1981-01-16 23:59:59 -0100"];
PASS([timeZone secondsFromGMTForDate: date] == 3600,
"pre-1996 standard time offset vs UTC found for user Paris TZDBv1");
date = [NSDate dateWithString: @"1981-08-16 23:59:59 -0200"];
PASS([timeZone secondsFromGMTForDate: date] == 7200,
"pre-1996 DST time offset vs UTC found for user Paris TZDBv1");
/* After last transition in TZDB v2+ file */
date = [NSDate dateWithString: @"2021-01-16 23:59:59 -0100"];
PASS([timeZone secondsFromGMTForDate: date] == 3600,
"post-1996 standard time offset vs UTC found for user Paris TZDBv1");
date = [NSDate dateWithString: @"2021-08-16 23:59:59 -0200"];
PASS([timeZone secondsFromGMTForDate: date] == 7200,
"post-1996 DST time offset vs UTC found for user Paris TZDBv1");
/* Test using TZDB file known as being v2 */
tzdata = [NSData dataWithContentsOfFile: @ PREFIX "Paris.tzdbv2"];
PASS(tzdata != nil && [tzdata isKindOfClass: [NSData class]] &&
[tzdata length] > 0, "Loading user-supplied Paris TZDBv2 works");
timeZone = [NSTimeZone timeZoneWithName: @"Paris.tzdbv2" data: tzdata];
PASS(timeZone != nil && [timeZone isKindOfClass: [NSTimeZone class]],
"+timeZoneWithName data works");
/* Before last transition in TZDB v2+ file */
date = [NSDate dateWithString: @"1981-01-16 23:59:59 -0200"];
PASS([timeZone secondsFromGMTForDate: date] == 3600,
"pre-1996 standard time offset vs UTC found for user Paris TZDBv2");
date = [NSDate dateWithString: @"1981-08-16 23:59:59 -0200"];
PASS([timeZone secondsFromGMTForDate: date] == 7200,
"pre-1996 DST time offset vs UTC found for user Paris TZDBv2");
/* After last transition in TZDB v2+ file */
date = [NSDate dateWithString: @"2021-01-16 23:59:59 -0200"];
PASS([timeZone secondsFromGMTForDate: date] == 3600,
"post-1996 DST time offset vs UTC found for user Paris TZDBv2");
date = [NSDate dateWithString: @"2021-08-16 23:59:59 -0200"];
PASS([timeZone secondsFromGMTForDate: date] == 7200,
"post-1996 DST time offset vs UTC found for user Paris TZDBv2");
/* After 32bit value seconds-since-1970 using TZDB v2+ file */
date = [NSDate dateWithString: @"2039-01-16 23:59:59 -0200"];
PASS([timeZone secondsFromGMTForDate: date] == 3600,
"post-2038 DST time offset vs UTC found for user Paris TZDBv2");
[arp release]; arp = nil;
return 0;
}

2
configure vendored
View file

@ -10130,7 +10130,7 @@ _ACEOF
for ac_header in fcntl.h inttypes.h libc.h limits.h malloc.h memory.h signal.h stdint.h string.h sys/fcntl.h sys/file.h sys/filio.h sys/inttypes.h sys/ioctl.h sys/signal.h sys/stropts.h sys/wait.h unistd.h utime.h stdlib.h
for ac_header in fcntl.h inttypes.h libc.h limits.h malloc.h memory.h signal.h stdint.h string.h sys/fcntl.h sys/file.h sys/filio.h sys/inttypes.h sys/ioctl.h sys/signal.h sys/stropts.h sys/wait.h unistd.h utime.h stdlib.h stdbool.h
do :
as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh`
ac_fn_c_check_header_mongrel "$LINENO" "$ac_header" "$as_ac_Header" "$ac_includes_default"

View file

@ -2597,6 +2597,7 @@ AC_CHECK_HEADERS(dnl
unistd.h dnl
utime.h dnl
stdlib.h dnl
stdbool.h dnl
)
if test $ac_cv_header_inttypes_h = yes; then