Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Native (Kotlin): improve performance #2

Merged
merged 5 commits into from
Apr 30, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,13 @@ task("downloadWindowsZonesMapping") {
out.println("\t{ \"$windowsName\", \"$usualName\" },")
}
out.println("};")
out.println("""static const std::unordered_map<std::string, size_t> zone_ids = {""")
var i = 0
for ((usualName, windowsName) in mapping) {
out.println("\t{ \"$usualName\", $i },")
++i
}
out.println("};")
}
}
}
3 changes: 3 additions & 0 deletions core/commonMain/src/DayOfWeek.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

package kotlinx.datetime

import kotlin.native.concurrent.*

public expect enum class DayOfWeek {
MONDAY,
TUESDAY,
Expand All @@ -17,6 +19,7 @@ public expect enum class DayOfWeek {

public val DayOfWeek.number: Int get() = ordinal + 1

@SharedImmutable
private val allDaysOfWeek = DayOfWeek.values().asList()
public fun DayOfWeek(number: Int): DayOfWeek {
require(number in 1..7)
Expand Down
4 changes: 4 additions & 0 deletions core/commonMain/src/Month.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

package kotlinx.datetime

import kotlin.native.concurrent.*

public expect enum class Month {
JANUARY,
FEBRUARY,
Expand All @@ -24,7 +26,9 @@ public expect enum class Month {

public val Month.number: Int get() = ordinal + 1

@SharedImmutable
private val allMonths = Month.values().asList()

public fun Month(number: Int): Month {
require(number in 1..12)
return allMonths[number - 1]
Expand Down
71 changes: 53 additions & 18 deletions core/nativeMain/cinterop/cpp/apple.mm
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#import <Foundation/NSDate.h>
#import <Foundation/NSCalendar.h>
#import <limits.h>
#import <vector>
#import <set>
#import <string>
#include "helper_macros.hpp"
Expand All @@ -29,26 +30,63 @@

extern "C" {
#include "cdate.h"
}

char * get_system_timezone()
static std::vector<NSTimeZone *> populate()
{
std::vector<NSTimeZone *> v;
auto names = NSTimeZone.knownTimeZoneNames;
for (size_t i = 0; i < names.count; ++i) {
v.push_back([NSTimeZone timeZoneWithName: names[i]]);
}
return v;
}

static std::vector<NSTimeZone *> zones_cache = populate();

static TZID id_by_name(NSString *zone_name)
{
auto abbreviations = NSTimeZone.abbreviationDictionary;
auto true_name = [abbreviations valueForKey: zone_name];
const NSString *name = zone_name;
if (true_name != nil) {
name = true_name;
}
for (size_t i = 0; i < zones_cache.size(); ++i) {
if ([name isEqualToString:zones_cache[i].name]) {
return i;
}
}
return TZID_INVALID;
}

static NSTimeZone *timezone_by_id(TZID id)
{
try {
return zones_cache.at(id);
} catch (std::out_of_range e) {
return nullptr;
}
}

extern "C" {

char * get_system_timezone(TZID *tzid)
{
CFTimeZoneRef zone = CFTimeZoneCopySystem(); // always succeeds
auto name = CFTimeZoneGetName(zone);
*tzid = id_by_name((__bridge NSString *)name);
CFIndex bufferSize = CFStringGetLength(name) + 1;
char * buffer = (char *)malloc(sizeof(char) * bufferSize);
if (buffer == nullptr) {
CFRelease(zone);
return nullptr;
}
// only fails if the name is not UTF8-encoded, which is an anomaly.
if (CFStringGetCString(name, buffer, bufferSize, kCFStringEncodingUTF8))
{
CFRelease(zone);
return buffer;
}
auto result = CFStringGetCString(name, buffer, bufferSize, kCFStringEncodingUTF8);
assert(result);
CFRelease(zone);
free(buffer);
return nullptr;
return buffer;
}

char ** available_zone_ids()
Expand Down Expand Up @@ -77,25 +115,22 @@
return zones_copy;
}

int offset_at_instant(const char *zone_name, int64_t epoch_sec)
int offset_at_instant(TZID zone_id, int64_t epoch_sec)
{
auto zone_name_nsstring = [NSString stringWithUTF8String: zone_name];
auto zone = zone_by_name(zone_name_nsstring);
auto zone = timezone_by_id(zone_id);
if (zone == nil) { return INT_MAX; }
auto date = [NSDate dateWithTimeIntervalSince1970: epoch_sec];
return (int32_t)[zone secondsFromGMTForDate: date];
}

bool is_known_timezone(const char *zone_name) {
auto zone_name_nsstring = [NSString stringWithUTF8String: zone_name];
return (zone_by_name(zone_name_nsstring) != nil);
TZID timezone_by_name(const char *zone_name) {
return id_by_name([NSString stringWithUTF8String: zone_name]);
}

int offset_at_datetime(const char *zone_name, int64_t epoch_sec, int *offset) {
int offset_at_datetime(TZID zone_id, int64_t epoch_sec, int *offset) {
*offset = INT_MAX;
// timezone name
auto zone_name_nsstring = [NSString stringWithUTF8String: zone_name];
// timezone
auto zone = zone_by_name(zone_name_nsstring);
auto zone = timezone_by_id(zone_id);
if (zone == nil) { return 0; }
/* a date in an unspecified timezone, defined by the number of seconds since
the start of the epoch in *that* unspecified timezone */
Expand Down
58 changes: 45 additions & 13 deletions core/nativeMain/cinterop/cpp/cdate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@
using namespace date;
using namespace std::chrono;

extern "C" {
#include "cdate.h"
}

template <class T>
static char * timezone_name(const T& zone)
{
Expand All @@ -26,17 +30,44 @@ static char * timezone_name(const T& zone)
return name_copy;
}

extern "C" {
static const time_zone *zone_by_id(TZID id)
{
/* The `date` library provides a linked list of `tzdb` objects. `get_tzdb()`
always returns the head of that list. For now, the list never changes:
a call to `reload_tzdb()` would be required to load the updated version
of the timezone database. We never do this because for now (with use of
`date`) this operation is not even present for the configuration that
uses the system timezone database. If we move to C++20 support for this,
it may be feasible to call `reload_tzdb()` and construct a more elaborate
ID scheme. */
auto& tzdb = get_tzdb();
try {
return &tzdb.zones.at(id);
} catch (std::out_of_range e) {
throw std::runtime_error("Invalid timezone id");
}
}

#include "cdate.h"
static TZID id_by_zone(const tzdb& db, const time_zone* tz)
{
size_t id = tz - &db.zones[0];
if (id >= db.zones.size()) {
throw std::runtime_error("The time zone is not part of the tzdb");
}
return id;
}

char * get_system_timezone()
extern "C" {

char * get_system_timezone(TZID * id)
{
try {
auto& tzdb = get_tzdb();
auto zone = tzdb.current_zone();
*id = id_by_zone(tzdb, zone);
return timezone_name(*zone);
} catch (std::runtime_error e) {
*id = TZID_INVALID;
return nullptr;
}
}
Expand All @@ -59,38 +90,35 @@ char ** available_zone_ids()
}
}

int offset_at_instant(const char *zone_name, int64_t epoch_sec)
int offset_at_instant(TZID zone_id, int64_t epoch_sec)
{
try {
auto& tzdb = get_tzdb();
/* `sys_time` is usually Unix time (UTC, not counting leap seconds).
Starting from C++20, it is specified in the standard. */
auto stime = sys_time<std::chrono::seconds>(
std::chrono::seconds(epoch_sec));
auto zone = tzdb.locate_zone(zone_name);
auto zone = zone_by_id(zone_id);
auto info = zone->get_info(stime);
return info.offset.count();
} catch (std::runtime_error e) {
return INT_MAX;
}
}

bool is_known_timezone(const char *zone_name)
TZID timezone_by_name(const char *zone_name)
{
try {
auto& tzdb = get_tzdb();
tzdb.locate_zone(zone_name);
return true;
return id_by_zone(tzdb, tzdb.locate_zone(zone_name));
} catch (std::runtime_error e) {
return false;
return TZID_INVALID;
}
}

int offset_at_datetime(const char *zone_name, int64_t epoch_sec, int *offset)
int offset_at_datetime(TZID zone_id, int64_t epoch_sec, int *offset)
{
try {
auto& tzdb = get_tzdb();
auto zone = tzdb.locate_zone(zone_name);
auto zone = zone_by_id(zone_id);
local_seconds seconds((std::chrono::seconds(epoch_sec)));
auto info = zone->get_info(seconds);
switch (info.result) {
Expand All @@ -107,6 +135,10 @@ int offset_at_datetime(const char *zone_name, int64_t epoch_sec, int *offset)
if (info.second.offset.count() != *offset)
*offset = info.first.offset.count();
return 0;
default:
// the pattern matching above is supposedly exhaustive
*offset = INT_MAX;
return 0;
}
} catch (std::runtime_error e) {
*offset = INT_MAX;
Expand Down
Loading