/
time.cr
144 lines (126 loc) · 4.75 KB
/
time.cr
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
require "c/sys/time"
require "c/time"
{% if flag?(:android) %}
# needed for accessing local timezone
require "c/sys/system_properties"
{% end %}
{% if flag?(:darwin) %}
# Darwin supports clock_gettime starting from macOS Sierra, but we can't
# use it because it would prevent running binaries built on macOS Sierra
# to run on older macOS releases.
#
# Furthermore, mach_absolute_time is reported to have a higher precision.
require "c/mach/mach_time"
{% end %}
module Crystal::System::Time
UnixEpochInSeconds = 62135596800_i64
def self.compute_utc_seconds_and_nanoseconds : {Int64, Int32}
{% if LibC.has_method?("clock_gettime") %}
ret = LibC.clock_gettime(LibC::CLOCK_REALTIME, out timespec)
raise RuntimeError.from_errno("clock_gettime") unless ret == 0
{timespec.tv_sec.to_i64 + UnixEpochInSeconds, timespec.tv_nsec.to_i}
{% else %}
ret = LibC.gettimeofday(out timeval, nil)
raise RuntimeError.from_errno("gettimeofday") unless ret == 0
{timeval.tv_sec.to_i64 + UnixEpochInSeconds, timeval.tv_usec.to_i * 1_000}
{% end %}
end
def self.monotonic : {Int64, Int32}
{% if flag?(:darwin) %}
info = mach_timebase_info
total_nanoseconds = LibC.mach_absolute_time * info.numer // info.denom
seconds = total_nanoseconds // 1_000_000_000
nanoseconds = total_nanoseconds.remainder(1_000_000_000)
{seconds.to_i64, nanoseconds.to_i32}
{% else %}
if LibC.clock_gettime(LibC::CLOCK_MONOTONIC, out tp) == 1
raise RuntimeError.from_errno("clock_gettime(CLOCK_MONOTONIC)")
end
{tp.tv_sec.to_i64, tp.tv_nsec.to_i32}
{% end %}
end
def self.to_timespec(time : ::Time)
t = uninitialized LibC::Timespec
t.tv_sec = typeof(t.tv_sec).new(time.to_unix)
t.tv_nsec = typeof(t.tv_nsec).new(time.nanosecond)
t
end
def self.to_timeval(time : ::Time)
t = uninitialized LibC::Timeval
t.tv_sec = typeof(t.tv_sec).new(time.to_unix)
t.tv_usec = typeof(t.tv_usec).new(time.nanosecond // ::Time::NANOSECONDS_PER_MICROSECOND)
t
end
# Many systems use /usr/share/zoneinfo, Solaris 2 has
# /usr/share/lib/zoneinfo, IRIX 6 has /usr/lib/locale/TZ.
ZONE_SOURCES = {
"/usr/share/zoneinfo/",
"/usr/share/lib/zoneinfo/",
"/usr/lib/locale/TZ/",
}
# Android Bionic C-specific locations. These are files rather than directories
# and use a different format (see `Time::Location.read_android_tzdata`).
ANDROID_TZDATA_SOURCES = {
"/apex/com.android.tzdata/etc/tz/tzdata",
"/system/usr/share/zoneinfo/tzdata",
}
def self.zone_sources : Enumerable(String)
ZONE_SOURCES
end
def self.android_tzdata_sources : Enumerable(String)
ANDROID_TZDATA_SOURCES
end
def self.load_iana_zone(iana_name : String) : ::Time::Location?
nil
end
{% if flag?(:android) %}
def self.load_localtime : ::Time::Location?
# NOTE: although reading a system property is expensive, we don't cache it
# here since it is expected that most code should only ever be calling
# `Time::Location.load`, which is already a cached class property, rather
# than `.load_local`. Bionic itself caches the property like this:
# https://android.googlesource.com/platform/bionic/+/master/libc/private/CachedProperty.h
return nil unless timezone = getprop("persist.sys.timezone")
return nil unless path = ::Time::Location.find_android_tzdata_file(android_tzdata_sources)
::File.open(path) do |file|
::Time::Location.read_android_tzdata(file, true) do |name, location|
return location if name == timezone
end
end
end
private def self.getprop(key : String) : String?
{% if LibC.has_method?("__system_property_read_callback") %}
pi = LibC.__system_property_find(key)
value = ""
LibC.__system_property_read_callback(pi, ->(data, _name, value, _serial) do
data.as(String*).value = String.new(value)
end, pointerof(value))
value.presence
{% else %}
buf = uninitialized LibC::Char[LibC::PROP_VALUE_MAX]
len = LibC.__system_property_get(key, buf)
String.new(buf.to_slice[0, len]) if len > 0
{% end %}
end
{% else %}
private LOCALTIME = "/etc/localtime"
def self.load_localtime : ::Time::Location?
if ::File.file?(LOCALTIME) && ::File.readable?(LOCALTIME)
::File.open(LOCALTIME) do |file|
::Time::Location.read_zoneinfo("Local", file)
rescue ::Time::Location::InvalidTZDataError
nil
end
end
end
{% end %}
{% if flag?(:darwin) %}
@@mach_timebase_info : LibC::MachTimebaseInfo?
private def self.mach_timebase_info
@@mach_timebase_info ||= begin
LibC.mach_timebase_info(out info)
info
end
end
{% end %}
end