Skip to content

Commit 76bd79d

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 overriden 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. To simplify the code a little, a duplicated piece of code in _CFTimeZoneInit has been extracted into _CFTimeZoneDataCreate. While the code is quite simple for Unix, the Android code is a little bit more complicated, and I prefer not to repeat the same and having two blocks of platform conditional code. This change should be the only change that might modify the behaviour of CoreFoundation on Darwin or Linux. At many points there were 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 c82e1de commit 76bd79d

File tree

5 files changed

+177
-46
lines changed

5 files changed

+177
-46
lines changed

CoreFoundation/NumberDate.subproj/CFTimeZone.c

Lines changed: 176 additions & 26 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
@@ -183,6 +185,121 @@ static void __CFTimeZoneGetOffset(CFStringRef timezone, int32_t *offset) {
183185

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

757+
#elif TARGET_OS_ANDROID
758+
static void __InitTZStrings(void) {
759+
// NOTE: Android doesn't use this values. Only as markers.
760+
__tzZoneInfo = CFSTR("/system/usr/share/timezone/");
761+
__tzDir = "/system/usr/share/timezone/tzdata";
762+
}
763+
640764
#elif TARGET_OS_LINUX || TARGET_OS_BSD
641765
static void __InitTZStrings(void) {
642766
__tzZoneInfo = CFSTR(TZDIR);
@@ -718,15 +842,6 @@ static CFTimeZoneRef __CFTimeZoneCreateSystem(void) {
718842
CFRelease(name);
719843
if (result) return result;
720844
}
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
730845
return CFTimeZoneCreateWithTimeIntervalFromGMT(kCFAllocatorSystemDefault, 0.0);
731846
}
732847

@@ -808,6 +923,8 @@ CFArrayRef CFTimeZoneCopyKnownNames(void) {
808923
*/
809924
#if TARGET_OS_WIN32
810925
list = __CFCopyWindowsTimeZoneList();
926+
#elif TARGET_OS_ANDROID
927+
list = __CFCopyAndroidTimeZoneList();
811928
#else
812929
list = __CFCopyRecursiveDirectoryList();
813930
#endif
@@ -1058,6 +1175,53 @@ Boolean _CFTimeZoneInitInternal(CFTimeZoneRef timezone, CFStringRef name, CFData
10581175
return success;
10591176
}
10601177

1178+
CFDataRef _CFTimeZoneDataCreate(CFURLRef baseURL, CFStringRef tzName) {
1179+
#if TARGET_OS_ANDROID
1180+
CFDataRef data = NULL;
1181+
char *buffer = NULL;
1182+
const char *tzNameCstr = CFStringGetCStringPtr(tzName, kCFStringEncodingASCII);
1183+
if (!tzNameCstr) {
1184+
CFIndex maxSize = CFStringGetMaximumSizeForEncoding(CFStringGetLength(tzName), kCFStringEncodingASCII) + 2;
1185+
if (maxSize == kCFNotFound) {
1186+
return NULL;
1187+
}
1188+
buffer = malloc(maxSize);
1189+
if (!buffer) {
1190+
return NULL;
1191+
}
1192+
if (CFStringGetCString(tzName, buffer, maxSize, kCFStringEncodingASCII)) {
1193+
tzNameCstr = buffer;
1194+
}
1195+
}
1196+
if (!tzNameCstr) {
1197+
return NULL;
1198+
}
1199+
1200+
struct __CFTimeZoneDataCreateContext context = {
1201+
.tzNameCstr = tzNameCstr,
1202+
.dataPtr = &data,
1203+
};
1204+
__CFAndroidTimeZoneListEnumerate(__CFTimeZoneDataCreateCallback, &context);
1205+
1206+
if (buffer) {
1207+
free(buffer);
1208+
}
1209+
return data;
1210+
#else
1211+
void *bytes;
1212+
CFIndex length;
1213+
CFDataRef data = NULL;
1214+
CFURLRef tempURL = CFURLCreateCopyAppendingPathComponent(kCFAllocatorSystemDefault, baseURL, tzName, false);
1215+
if (NULL != tempURL) {
1216+
if (_CFReadBytesFromFile(kCFAllocatorSystemDefault, tempURL, &bytes, &length, 0, 0)) {
1217+
data = CFDataCreateWithBytesNoCopy(kCFAllocatorSystemDefault, bytes, length, kCFAllocatorSystemDefault);
1218+
}
1219+
CFRelease(tempURL);
1220+
}
1221+
return data;
1222+
#endif
1223+
}
1224+
10611225
Boolean _CFTimeZoneInit(CFTimeZoneRef timeZone, CFStringRef name, CFDataRef data) {
10621226
if (!name || !__nameStringOK(name)) {
10631227
return false;
@@ -1093,9 +1257,7 @@ Boolean _CFTimeZoneInit(CFTimeZoneRef timeZone, CFStringRef name, CFDataRef data
10931257
}
10941258

10951259
CFStringRef tzName = NULL;
1096-
CFURLRef baseURL, tempURL;
1097-
void *bytes;
1098-
CFIndex length;
1260+
CFURLRef baseURL;
10991261
Boolean result = false;
11001262

11011263
#if TARGET_OS_WIN32
@@ -1128,13 +1290,7 @@ Boolean _CFTimeZoneInit(CFTimeZoneRef timeZone, CFStringRef name, CFDataRef data
11281290
CFDictionaryRef abbrevs = CFTimeZoneCopyAbbreviationDictionary();
11291291
tzName = CFDictionaryGetValue(abbrevs, name);
11301292
if (NULL != tzName) {
1131-
tempURL = CFURLCreateCopyAppendingPathComponent(kCFAllocatorSystemDefault, baseURL, tzName, false);
1132-
if (NULL != tempURL) {
1133-
if (_CFReadBytesFromFile(kCFAllocatorSystemDefault, tempURL, &bytes, &length, 0, 0)) {
1134-
data = CFDataCreateWithBytesNoCopy(kCFAllocatorSystemDefault, bytes, length, kCFAllocatorSystemDefault);
1135-
}
1136-
CFRelease(tempURL);
1137-
}
1293+
data = _CFTimeZoneDataCreate(baseURL, tzName);
11381294
}
11391295
CFRelease(abbrevs);
11401296

@@ -1159,13 +1315,7 @@ Boolean _CFTimeZoneInit(CFTimeZoneRef timeZone, CFStringRef name, CFDataRef data
11591315
}
11601316
if (NULL == data) {
11611317
tzName = name;
1162-
tempURL = CFURLCreateCopyAppendingPathComponent(kCFAllocatorSystemDefault, baseURL, tzName, false);
1163-
if (NULL != tempURL) {
1164-
if (_CFReadBytesFromFile(kCFAllocatorSystemDefault, tempURL, &bytes, &length, 0, 0)) {
1165-
data = CFDataCreateWithBytesNoCopy(kCFAllocatorSystemDefault, bytes, length, kCFAllocatorSystemDefault);
1166-
}
1167-
CFRelease(tempURL);
1168-
}
1318+
data = _CFTimeZoneDataCreate(baseURL, tzName);
11691319
}
11701320
CFRelease(baseURL);
11711321
if (NULL != data) {

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

TestFoundation/TestISO8601DateFormatter.swift

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,6 @@ class TestISO8601DateFormatter: XCTestCase {
111111
isoFormatter.formatOptions = [.withMonth, .withDay, .withWeekOfYear, .withDashSeparatorInDate]
112112
XCTAssertEqual(isoFormatter.string(from: someDateTime), "10-W40-06")
113113

114-
#if !os(Android)
115114
/*
116115
The following tests cover various cases when changing the .formatOptions property with a different TimeZone set.
117116
*/
@@ -153,7 +152,6 @@ class TestISO8601DateFormatter: XCTestCase {
153152

154153
isoFormatter.formatOptions = [.withDay, .withWeekOfYear, .withMonth, .withTimeZone, .withColonSeparatorInTimeZone, .withDashSeparatorInDate]
155154
XCTAssertEqual(isoFormatter.string(from: someDateTime), "10-W40-06-07:00")
156-
#endif
157155
}
158156

159157

@@ -270,7 +268,6 @@ class TestISO8601DateFormatter: XCTestCase {
270268
formatOptions = [.withMonth, .withDay, .withWeekOfYear, .withDashSeparatorInDate]
271269
XCTAssertEqual(ISO8601DateFormatter.string(from: someDateTime, timeZone: timeZone, formatOptions: formatOptions), "10-W40-06")
272270

273-
#if !os(Android)
274271
/*
275272
The following tests cover various cases when changing the .formatOptions property with a different TimeZone set.
276273
*/
@@ -315,7 +312,6 @@ class TestISO8601DateFormatter: XCTestCase {
315312

316313
formatOptions = [.withDay, .withWeekOfYear, .withMonth, .withTimeZone, .withColonSeparatorInTimeZone, .withDashSeparatorInDate]
317314
XCTAssertEqual(ISO8601DateFormatter.string(from: someDateTime, timeZone: pstTimeZone, formatOptions: formatOptions), "10-W40-06-07:00")
318-
#endif
319315
}
320316

321317
}

TestFoundation/TestTimeZone.swift

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -102,9 +102,6 @@ class TestTimeZone: XCTestCase {
102102
}
103103

104104
func test_localizedName() {
105-
#if os(Android)
106-
XCTFail("Named timezones not available on Android")
107-
#else
108105
let initialTimeZone = NSTimeZone.default
109106
NSTimeZone.default = TimeZone(identifier: "America/New_York")!
110107
let defaultTimeZone = NSTimeZone.default
@@ -116,7 +113,6 @@ class TestTimeZone: XCTestCase {
116113
XCTAssertEqual(defaultTimeZone.localizedName(for: .shortDaylightSaving, locale: locale), "EDT")
117114
XCTAssertEqual(defaultTimeZone.localizedName(for: .shortGeneric, locale: locale), "ET")
118115
NSTimeZone.default = initialTimeZone //reset the TimeZone
119-
#endif
120116
}
121117

122118
func test_initializingTimeZoneWithOffset() {

0 commit comments

Comments
 (0)