Skip to content

Commit 473cd46

Browse files
committed
[android] Support for time zones in Android.
The time zone database in Android sits in a non-standard place, and it can be overridedn by a second location. Besides not sitting in a standard place, it differs from other Unixes in that the database is bundled into one binary file, and there are no folders with small files for each time zone. Luckily the database is generated from Olson, so most of the CFTimeZone code already handles the details. The code implements a generic enumeration of the time zones contained in the database (checking for both the override and the standard location). The enumeration code is used for both enumerating the known time zones, as well as creating time zones by name. At many points there was code blocking the usage of time zones in Android that has been removed. This allows the tests in TestDateFormatter, TestCalendar and TestTimeZone to pass on Android.
1 parent ab183ec commit 473cd46

File tree

5 files changed

+187
-37
lines changed

5 files changed

+187
-37
lines changed

CoreFoundation/NumberDate.subproj/CFTimeZone.c

Lines changed: 186 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@
3030
#include <unistd.h>
3131
#if !TARGET_OS_ANDROID
3232
#include <sys/fcntl.h>
33+
#else
34+
#include <sys/endian.h>
3335
#endif
3436
#endif
3537
#if TARGET_OS_WIN32
@@ -70,7 +72,7 @@ struct tzhead {
7072

7173
#include <time.h>
7274

73-
#if !TARGET_OS_WIN32
75+
#if !TARGET_OS_WIN32 && !TARGET_OS_ANDROID
7476
static CFStringRef __tzZoneInfo = NULL;
7577
static char *__tzDir = NULL;
7678
static void __InitTZStrings(void);
@@ -183,11 +185,133 @@ static void __CFTimeZoneGetOffset(CFStringRef timezone, int32_t *offset) {
183185

184186
RegCloseKey(hKey);
185187
}
188+
#elif TARGET_OS_ANDROID
189+
typedef Boolean (*__CFAndroidTimeZoneListEnumerateCallback)(const char name[40], int32_t start, int32_t length, FILE *fp, void *context);
190+
191+
static void __CFAndroidTimeZoneListEnumerate(__CFAndroidTimeZoneListEnumerateCallback callback, void *context) {
192+
// The best reference should be Android Bionic's libc/tzcode/bionic.cpp
193+
static const char *tzDataFiles[] = {
194+
"/data/misc/zoneinfo/current/tzdata",
195+
"/system/usr/share/zoneinfo/tzdata"
196+
};
197+
198+
Boolean done = FALSE;
199+
for (int idx = 0; idx < sizeof(tzDataFiles)/sizeof(tzDataFiles[0]) && !done; idx++) {
200+
FILE *fp = fopen(tzDataFiles[idx], "rb");
201+
if (!fp) {
202+
continue;
203+
}
204+
205+
char header[24];
206+
if (fread(header, 1, sizeof(header), fp) != sizeof(header)) {
207+
fclose(fp);
208+
continue;
209+
}
210+
if (strncmp(header, "tzdata", 6) != 0) {
211+
fclose(fp);
212+
continue;
213+
}
214+
215+
int32_t indexOffset;
216+
memcpy(&indexOffset, &header[12], sizeof(int32_t));
217+
indexOffset = betoh32(indexOffset);
218+
219+
int32_t dataOffset;
220+
memcpy(&dataOffset, &header[16], sizeof(int32_t));
221+
dataOffset = betoh32(dataOffset);
222+
223+
if (indexOffset < 0 || dataOffset < indexOffset) {
224+
fclose(fp);
225+
continue;
226+
}
227+
if (fseek(fp, indexOffset, SEEK_SET) != 0) {
228+
fclose(fp);
229+
continue;
230+
}
231+
232+
char entry[52];
233+
size_t indexSize = dataOffset - indexOffset;
234+
size_t zoneCount = indexSize / sizeof(entry);
235+
if (zoneCount * sizeof(entry) != indexSize) {
236+
fclose(fp);
237+
continue;
238+
}
239+
for (size_t idx = 0; idx < zoneCount; idx++) {
240+
if (fread(entry, 1, sizeof(entry), fp) != sizeof(entry)) {
241+
break;
242+
}
243+
int32_t start;
244+
memcpy(&start, &entry[40], sizeof(int32_t));
245+
start = betoh32(start);
246+
start += dataOffset;
247+
int32_t length;
248+
memcpy(&length, &entry[44], sizeof(int32_t));
249+
length = betoh32(length);
250+
if (start < 0 || length < 0) {
251+
break;
252+
}
253+
254+
done = callback(entry, start, length, fp, context);
255+
if (done) {
256+
break;
257+
}
258+
}
259+
260+
fclose(fp);
261+
}
262+
}
263+
264+
static Boolean __CFCopyAndroidTimeZoneListCallback(const char name[40], int32_t start, int32_t length, FILE *fp, void *context) {
265+
CFMutableArrayRef result = (CFMutableArrayRef)context;
266+
CFStringRef timeZoneName = CFStringCreateWithCString(kCFAllocatorSystemDefault, name, kCFStringEncodingASCII);
267+
CFArrayAppendValue(result, timeZoneName);
268+
CFRelease(timeZoneName);
269+
return FALSE;
270+
}
271+
272+
struct __CFTimeZoneDataCreateContext {
273+
const char *tzNameCstr;
274+
CFDataRef *dataPtr;
275+
};
276+
277+
static Boolean __CFTimeZoneDataCreateCallback(const char name[40], int32_t start, int32_t length, FILE *fp, void *context) {
278+
struct __CFTimeZoneDataCreateContext *ctx = (struct __CFTimeZoneDataCreateContext *)context;
279+
char *tzNameCstr = ctx->tzNameCstr;
280+
CFDataRef *dataPtr = ctx->dataPtr;
281+
282+
if (strncmp(tzNameCstr, name, 40) == 0) {
283+
if (fseek(fp, start, SEEK_SET) != 0) {
284+
return TRUE;
285+
}
286+
uint8_t *bytes = malloc(length);
287+
if (!bytes) {
288+
return TRUE;
289+
}
290+
if (fread(bytes, 1, length, fp) != length) {
291+
free(bytes);
292+
return TRUE;
293+
}
294+
*dataPtr = CFDataCreate(kCFAllocatorSystemDefault, bytes, length);
295+
free(bytes);
296+
return TRUE;
297+
}
298+
299+
return FALSE;
300+
}
301+
302+
static CFMutableArrayRef __CFCopyAndroidTimeZoneList() {
303+
CFMutableArrayRef result = CFArrayCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeArrayCallBacks);
304+
__CFAndroidTimeZoneListEnumerate(__CFCopyAndroidTimeZoneListCallback, result);
305+
return result;
306+
}
307+
186308
#elif TARGET_OS_MAC || TARGET_OS_LINUX || TARGET_OS_BSD
187309
static CFMutableArrayRef __CFCopyRecursiveDirectoryList() {
188310
CFMutableArrayRef result = CFArrayCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeArrayCallBacks);
311+
#if !TARGET_OS_ANDROID
189312
if (!__tzDir) __InitTZStrings();
190313
if (!__tzDir) return result;
314+
#endif
191315
int fd = open(__tzDir, O_RDONLY);
192316

193317
for (; 0 <= fd;) {
@@ -637,6 +761,8 @@ static void __InitTZStrings(void) {
637761
});
638762
}
639763

764+
#elif TARGET_OS_ANDROID
765+
// Nothing
640766
#elif TARGET_OS_LINUX || TARGET_OS_BSD
641767
static void __InitTZStrings(void) {
642768
__tzZoneInfo = CFSTR(TZDIR);
@@ -690,6 +816,7 @@ static CFTimeZoneRef __CFTimeZoneCreateSystem(void) {
690816
if (result) return result;
691817
}
692818

819+
#if !TARGET_OS_ANDROID
693820
if (!__tzZoneInfo) __InitTZStrings();
694821
ret = readlink(TZDEFAULT, linkbuf, sizeof(linkbuf));
695822
if (__tzZoneInfo && (0 < ret)) {
@@ -702,7 +829,9 @@ static CFTimeZoneRef __CFTimeZoneCreateSystem(void) {
702829
} else {
703830
name = CFStringCreateWithBytes(kCFAllocatorSystemDefault, (uint8_t *)linkbuf, strlen(linkbuf), kCFStringEncodingUTF8, false);
704831
}
705-
} else {
832+
} else
833+
#endif
834+
{
706835
// TODO: This can still fail on Linux if the time zone is not recognized by ICU later
707836
// Try localtime
708837
tzset();
@@ -718,15 +847,6 @@ static CFTimeZoneRef __CFTimeZoneCreateSystem(void) {
718847
CFRelease(name);
719848
if (result) return result;
720849
}
721-
#if TARGET_OS_ANDROID
722-
// Timezone database by name not available on Android.
723-
// Approximate with gmtoff - could be general default.
724-
struct tm info;
725-
time_t now = time(NULL);
726-
if (NULL != localtime_r(&now, &info)) {
727-
return CFTimeZoneCreateWithTimeIntervalFromGMT(kCFAllocatorSystemDefault, info.tm_gmtoff);
728-
}
729-
#endif
730850
return CFTimeZoneCreateWithTimeIntervalFromGMT(kCFAllocatorSystemDefault, 0.0);
731851
}
732852

@@ -808,6 +928,8 @@ CFArrayRef CFTimeZoneCopyKnownNames(void) {
808928
*/
809929
#if TARGET_OS_WIN32
810930
list = __CFCopyWindowsTimeZoneList();
931+
#elif TARGET_OS_ANDROID
932+
list = __CFCopyAndroidTimeZoneList();
811933
#else
812934
list = __CFCopyRecursiveDirectoryList();
813935
#endif
@@ -1059,6 +1181,37 @@ Boolean _CFTimeZoneInitInternal(CFTimeZoneRef timezone, CFStringRef name, CFData
10591181
}
10601182

10611183
CFDataRef _CFTimeZoneDataCreate(CFURLRef baseURL, CFStringRef tzName) {
1184+
#if TARGET_OS_ANDROID
1185+
CFDataRef data = NULL;
1186+
char *buffer = NULL;
1187+
const char *tzNameCstr = CFStringGetCStringPtr(tzName, kCFStringEncodingASCII);
1188+
if (!tzNameCstr) {
1189+
CFIndex maxSize = CFStringGetMaximumSizeForEncoding(CFStringGetLength(tzName), kCFStringEncodingASCII) + 2;
1190+
if (maxSize == kCFNotFound) {
1191+
return NULL;
1192+
}
1193+
buffer = malloc(maxSize);
1194+
if (!buffer) {
1195+
return NULL;
1196+
}
1197+
if (CFStringGetCString(tzName, buffer, maxSize, kCFStringEncodingASCII)) {
1198+
tzNameCstr = buffer;
1199+
}
1200+
}
1201+
if (!tzNameCstr) {
1202+
free(buffer);
1203+
return NULL;
1204+
}
1205+
1206+
struct __CFTimeZoneDataCreateContext context = {
1207+
.tzNameCstr = tzNameCstr,
1208+
.dataPtr = &data,
1209+
};
1210+
__CFAndroidTimeZoneListEnumerate(__CFTimeZoneDataCreateCallback, &context);
1211+
1212+
free(buffer);
1213+
return data;
1214+
#else
10621215
void *bytes;
10631216
CFIndex length;
10641217
CFDataRef data = NULL;
@@ -1070,6 +1223,7 @@ CFDataRef _CFTimeZoneDataCreate(CFURLRef baseURL, CFStringRef tzName) {
10701223
CFRelease(tempURL);
10711224
}
10721225
return data;
1226+
#endif
10731227
}
10741228

10751229
Boolean _CFTimeZoneInit(CFTimeZoneRef timeZone, CFStringRef name, CFDataRef data) {
@@ -1107,7 +1261,7 @@ Boolean _CFTimeZoneInit(CFTimeZoneRef timeZone, CFStringRef name, CFDataRef data
11071261
}
11081262

11091263
CFStringRef tzName = NULL;
1110-
CFURLRef baseURL;
1264+
CFURLRef baseURL = NULL;
11111265
Boolean result = false;
11121266

11131267
#if TARGET_OS_WIN32
@@ -1133,9 +1287,11 @@ Boolean _CFTimeZoneInit(CFTimeZoneRef timeZone, CFStringRef name, CFDataRef data
11331287

11341288
return FALSE;
11351289
#else
1290+
#if !TARGET_OS_ANDROID
11361291
if (!__tzZoneInfo) __InitTZStrings();
11371292
if (!__tzZoneInfo) return NULL;
11381293
baseURL = CFURLCreateWithFileSystemPath(kCFAllocatorSystemDefault, __tzZoneInfo, kCFURLPOSIXPathStyle, true);
1294+
#endif
11391295

11401296
CFDictionaryRef abbrevs = CFTimeZoneCopyAbbreviationDictionary();
11411297
tzName = CFDictionaryGetValue(abbrevs, name);
@@ -1149,7 +1305,9 @@ Boolean _CFTimeZoneInit(CFTimeZoneRef timeZone, CFStringRef name, CFDataRef data
11491305
CFStringRef mapping = CFDictionaryGetValue(dict, name);
11501306
if (mapping) {
11511307
name = mapping;
1152-
} else if (CFStringHasPrefix(name, __tzZoneInfo)) {
1308+
}
1309+
#if !TARGET_OS_ANDROID
1310+
else if (CFStringHasPrefix(name, __tzZoneInfo)) {
11531311
CFMutableStringRef unprefixed = CFStringCreateMutableCopy(kCFAllocatorSystemDefault, CFStringGetLength(name), name);
11541312
CFStringDelete(unprefixed, CFRangeMake(0, CFStringGetLength(__tzZoneInfo)));
11551313
mapping = CFDictionaryGetValue(dict, unprefixed);
@@ -1158,6 +1316,7 @@ Boolean _CFTimeZoneInit(CFTimeZoneRef timeZone, CFStringRef name, CFDataRef data
11581316
}
11591317
CFRelease(unprefixed);
11601318
}
1319+
#endif
11611320
CFRelease(dict);
11621321
if (CFEqual(CFSTR(""), name)) {
11631322
return false;
@@ -1167,7 +1326,9 @@ Boolean _CFTimeZoneInit(CFTimeZoneRef timeZone, CFStringRef name, CFDataRef data
11671326
tzName = name;
11681327
data = _CFTimeZoneDataCreate(baseURL, tzName);
11691328
}
1170-
CFRelease(baseURL);
1329+
if (baseURL) {
1330+
CFRelease(baseURL);
1331+
}
11711332
if (NULL != data) {
11721333
result = _CFTimeZoneInitInternal(timeZone, tzName, data);
11731334
CFRelease(data);
@@ -1311,7 +1472,7 @@ CFTimeZoneRef CFTimeZoneCreateWithName(CFAllocatorRef allocator, CFStringRef nam
13111472
}
13121473
}
13131474
}
1314-
CFURLRef baseURL;
1475+
CFURLRef baseURL = NULL;
13151476

13161477
#if TARGET_OS_WIN32
13171478
CFDictionaryRef abbrevs = CFTimeZoneCopyAbbreviationDictionary();
@@ -1335,9 +1496,12 @@ CFTimeZoneRef CFTimeZoneCreateWithName(CFAllocatorRef allocator, CFStringRef nam
13351496

13361497
return result;
13371498
#else
1499+
#if !TARGET_OS_ANDROID
13381500
if (!__tzZoneInfo) __InitTZStrings();
13391501
if (!__tzZoneInfo) return NULL;
1502+
#endif
13401503
baseURL = CFURLCreateWithFileSystemPath(kCFAllocatorSystemDefault, __tzZoneInfo, kCFURLPOSIXPathStyle, true);
1504+
#endif
13411505
if (tryAbbrev) {
13421506
CFDictionaryRef abbrevs = CFTimeZoneCopyAbbreviationDictionary();
13431507
tzName = CFDictionaryGetValue(abbrevs, name);
@@ -1351,7 +1515,9 @@ CFTimeZoneRef CFTimeZoneCreateWithName(CFAllocatorRef allocator, CFStringRef nam
13511515
CFStringRef mapping = CFDictionaryGetValue(dict, name);
13521516
if (mapping) {
13531517
name = mapping;
1354-
} else if (CFStringHasPrefix(name, __tzZoneInfo)) {
1518+
}
1519+
#if !TARGET_OS_ANDROID
1520+
else if (CFStringHasPrefix(name, __tzZoneInfo)) {
13551521
CFMutableStringRef unprefixed = CFStringCreateMutableCopy(kCFAllocatorSystemDefault, CFStringGetLength(name), name);
13561522
CFStringDelete(unprefixed, CFRangeMake(0, CFStringGetLength(__tzZoneInfo)));
13571523
mapping = CFDictionaryGetValue(dict, unprefixed);
@@ -1360,6 +1526,7 @@ CFTimeZoneRef CFTimeZoneCreateWithName(CFAllocatorRef allocator, CFStringRef nam
13601526
}
13611527
CFRelease(unprefixed);
13621528
}
1529+
#endif
13631530
CFRelease(dict);
13641531
if (CFEqual(CFSTR(""), name)) {
13651532
return NULL;
@@ -1369,7 +1536,9 @@ CFTimeZoneRef CFTimeZoneCreateWithName(CFAllocatorRef allocator, CFStringRef nam
13691536
tzName = name;
13701537
data = _CFTimeZoneDataCreate(baseURL, tzName);
13711538
}
1372-
CFRelease(baseURL);
1539+
if (baseURL) {
1540+
CFRelease(baseURL);
1541+
}
13731542
if (NULL != data) {
13741543
result = CFTimeZoneCreate(allocator, tzName, data);
13751544
if (name != tzName) {

Foundation/NSTimeZone.swift

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,17 +29,13 @@ open class NSTimeZone : NSObject, NSCopying, NSSecureCoding, NSCoding {
2929
}
3030

3131
public init?(name tzName: String, data aData: Data?) {
32-
#if os(Android) || os(Windows)
32+
#if os(Windows)
3333
var tzName = tzName
3434
if tzName == "UTC" || tzName == "GMT" {
3535
tzName = "GMT+0000"
3636
}
3737
else if !(tzName.hasPrefix("GMT+") || tzName.hasPrefix("GMT-")) {
38-
#if os(Android)
39-
NSLog("Time zone database not available on Android")
40-
#else
4138
NSLog("Time zone database not available on Windows")
42-
#endif
4339
}
4440
#endif
4541

TestFoundation/TestCodable.swift

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -345,7 +345,6 @@ class TestCodable : XCTestCase {
345345

346346
// MARK: - TimeZone
347347
lazy var timeZoneValues: [TimeZone] = {
348-
#if !os(Android)
349348
var values = [
350349
TimeZone(identifier: "America/Los_Angeles")!,
351350
TimeZone(identifier: "UTC")!,
@@ -355,12 +354,6 @@ class TestCodable : XCTestCase {
355354
// TimeZone.current == TimeZone(identifier: TimeZone.current.identifier) equality,
356355
// causing encode -> decode -> compare test to fail.
357356
// values.append(TimeZone.current)
358-
#else
359-
var values = [
360-
TimeZone(identifier: "UTC")!,
361-
TimeZone.current
362-
]
363-
#endif
364357
return values
365358
}()
366359

0 commit comments

Comments
 (0)