Skip to content

Commit cf58b49

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 29d396b commit cf58b49

File tree

5 files changed

+167
-31
lines changed

5 files changed

+167
-31
lines changed

CoreFoundation/NumberDate.subproj/CFTimeZone.c

Lines changed: 166 additions & 11 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,7 +761,7 @@ static void __InitTZStrings(void) {
637761
});
638762
}
639763

640-
#elif TARGET_OS_LINUX || TARGET_OS_BSD
764+
#elif TARGET_OS_LINUX || TARGET_OS_BSD && !TARGET_OS_ANDROID
641765
static void __InitTZStrings(void) {
642766
__tzZoneInfo = CFSTR(TZDIR);
643767
__tzDir = TZDIR "zone.tab";
@@ -690,7 +814,9 @@ static CFTimeZoneRef __CFTimeZoneCreateSystem(void) {
690814
if (result) return result;
691815
}
692816

817+
#if !TARGET_OS_ANDROID
693818
if (!__tzZoneInfo) __InitTZStrings();
819+
#endif
694820
ret = readlink(TZDEFAULT, linkbuf, sizeof(linkbuf));
695821
if (__tzZoneInfo && (0 < ret)) {
696822
linkbuf[ret] = '\0';
@@ -718,15 +844,6 @@ static CFTimeZoneRef __CFTimeZoneCreateSystem(void) {
718844
CFRelease(name);
719845
if (result) return result;
720846
}
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
730847
return CFTimeZoneCreateWithTimeIntervalFromGMT(kCFAllocatorSystemDefault, 0.0);
731848
}
732849

@@ -808,6 +925,8 @@ CFArrayRef CFTimeZoneCopyKnownNames(void) {
808925
*/
809926
#if TARGET_OS_WIN32
810927
list = __CFCopyWindowsTimeZoneList();
928+
#elif TARGET_OS_ANDROID
929+
list = __CFCopyAndroidTimeZoneList();
811930
#else
812931
list = __CFCopyRecursiveDirectoryList();
813932
#endif
@@ -1059,6 +1178,37 @@ Boolean _CFTimeZoneInitInternal(CFTimeZoneRef timezone, CFStringRef name, CFData
10591178
}
10601179

10611180
CFDataRef _CFTimeZoneDataCreate(CFURLRef baseURL, CFStringRef tzName) {
1181+
#if TARGET_OS_ANDROID
1182+
CFDataRef data = NULL;
1183+
char *buffer = NULL;
1184+
const char *tzNameCstr = CFStringGetCStringPtr(tzName, kCFStringEncodingASCII);
1185+
if (!tzNameCstr) {
1186+
CFIndex maxSize = CFStringGetMaximumSizeForEncoding(CFStringGetLength(tzName), kCFStringEncodingASCII) + 2;
1187+
if (maxSize == kCFNotFound) {
1188+
return NULL;
1189+
}
1190+
buffer = malloc(maxSize);
1191+
if (!buffer) {
1192+
return NULL;
1193+
}
1194+
if (CFStringGetCString(tzName, buffer, maxSize, kCFStringEncodingASCII)) {
1195+
tzNameCstr = buffer;
1196+
}
1197+
}
1198+
if (!tzNameCstr) {
1199+
free(buffer);
1200+
return NULL;
1201+
}
1202+
1203+
struct __CFTimeZoneDataCreateContext context = {
1204+
.tzNameCstr = tzNameCstr,
1205+
.dataPtr = &data,
1206+
};
1207+
__CFAndroidTimeZoneListEnumerate(__CFTimeZoneDataCreateCallback, &context);
1208+
1209+
free(buffer);
1210+
return data;
1211+
#else
10621212
void *bytes;
10631213
CFIndex length;
10641214
CFDataRef data = NULL;
@@ -1070,6 +1220,7 @@ CFDataRef _CFTimeZoneDataCreate(CFURLRef baseURL, CFStringRef tzName) {
10701220
CFRelease(tempURL);
10711221
}
10721222
return data;
1223+
#endif
10731224
}
10741225

10751226
Boolean _CFTimeZoneInit(CFTimeZoneRef timeZone, CFStringRef name, CFDataRef data) {
@@ -1133,8 +1284,10 @@ Boolean _CFTimeZoneInit(CFTimeZoneRef timeZone, CFStringRef name, CFDataRef data
11331284

11341285
return FALSE;
11351286
#else
1287+
#if !TARGET_OS_ANDROID
11361288
if (!__tzZoneInfo) __InitTZStrings();
11371289
if (!__tzZoneInfo) return NULL;
1290+
#endif
11381291
baseURL = CFURLCreateWithFileSystemPath(kCFAllocatorSystemDefault, __tzZoneInfo, kCFURLPOSIXPathStyle, true);
11391292

11401293
CFDictionaryRef abbrevs = CFTimeZoneCopyAbbreviationDictionary();
@@ -1337,8 +1490,10 @@ CFTimeZoneRef CFTimeZoneCreateWithName(CFAllocatorRef allocator, CFStringRef nam
13371490

13381491
return result;
13391492
#else
1493+
#if !TARGET_OS_ANDROID
13401494
if (!__tzZoneInfo) __InitTZStrings();
13411495
if (!__tzZoneInfo) return NULL;
1496+
#endif
13421497
baseURL = CFURLCreateWithFileSystemPath(kCFAllocatorSystemDefault, __tzZoneInfo, kCFURLPOSIXPathStyle, true);
13431498
if (tryAbbrev) {
13441499
CFDictionaryRef abbrevs = CFTimeZoneCopyAbbreviationDictionary();

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)