| 1 |
|
%% @doc |
| 2 |
|
%% Time conversion utilities between RFC3339 datetimes and Unix time |
| 3 |
|
%% (seconds/nanoseconds). This module: |
| 4 |
|
%% |
| 5 |
|
%% - Accepts RFC3339 datetimes as UTF-8 binaries (e.g. <<"1970-01-01T00:00:00Z">>). |
| 6 |
|
%% - Produces RFC3339 binaries using a fixed offset of "+09:00" for |
| 7 |
|
%% unix_seconds_to_rfc3339/1 and unix_nanoseconds_to_rfc3339/1. |
| 8 |
|
%% - Supports timestamps before and after the Unix epoch. |
| 9 |
|
%% - Uses the runtime's calendar and erlang:convert_time_unit/3 for |
| 10 |
|
%% correctness and performance. |
| 11 |
|
%% |
| 12 |
|
%% Notes on offsets and negativity: |
| 13 |
|
%% - When parsing RFC3339 (the rfc3339_to_* functions), any explicit offset |
| 14 |
|
%% in the input is honored; the returned Unix time is always relative to UTC. |
| 15 |
|
%% - For negative nanoseconds to seconds conversion, erlang:convert_time_unit/3 |
| 16 |
|
%% follows floor semantics (rounding toward negative infinity). For example, |
| 17 |
|
%% -1 ns -> -1 s, and -1999999999 ns -> -2 s. |
| 18 |
|
%% |
| 19 |
|
%% @see rfc3339_to_unix_seconds/1 |
| 20 |
|
%% @see rfc3339_to_unix_nanoseconds/1 |
| 21 |
|
%% @see unix_seconds_to_rfc3339/1 |
| 22 |
|
%% @see unix_nanoseconds_to_rfc3339/1 |
| 23 |
|
%% @see unix_seconds_to_unix_nanoseconds/1 |
| 24 |
|
%% @see unix_nanoseconds_to_unix_seconds/1 |
| 25 |
|
-module(klsn_time). |
| 26 |
|
|
| 27 |
|
-export([ |
| 28 |
|
rfc3339_to_unix_seconds/1 |
| 29 |
|
, rfc3339_to_unix_nanoseconds/1 |
| 30 |
|
, unix_seconds_to_rfc3339/1 |
| 31 |
|
, unix_nanoseconds_to_rfc3339/1 |
| 32 |
|
, unix_seconds_to_unix_nanoseconds/1 |
| 33 |
|
, unix_nanoseconds_to_unix_seconds/1 |
| 34 |
|
]). |
| 35 |
|
|
| 36 |
|
-export_type([ |
| 37 |
|
rfc3339/0 |
| 38 |
|
, unix_seconds/0 |
| 39 |
|
, unix_nanoseconds/0 |
| 40 |
|
]). |
| 41 |
|
|
| 42 |
|
-type rfc3339() :: unicode:unicode_binary(). |
| 43 |
|
-type unix_seconds() :: integer(). |
| 44 |
|
-type unix_nanoseconds() :: integer(). |
| 45 |
|
|
| 46 |
|
|
| 47 |
|
%% @doc |
| 48 |
|
%% Convert an RFC3339 datetime (binary) to Unix time (seconds since epoch). |
| 49 |
|
%% |
| 50 |
|
%% The input may include a timezone designator (e.g. Z, +09:00, -05:00). |
| 51 |
|
%% The returned integer is the UTC Unix timestamp in seconds. |
| 52 |
|
%% |
| 53 |
|
%% Example: |
| 54 |
|
%% <pre> |
| 55 |
|
%% 1> klsn_time:rfc3339_to_unix_seconds(<<"1970-01-01T00:00:00Z">>). |
| 56 |
|
%% 0 |
| 57 |
|
%% 2> klsn_time:rfc3339_to_unix_seconds(<<"1970-01-01T09:00:00+09:00">>). |
| 58 |
|
%% 0 |
| 59 |
|
%% </pre> |
| 60 |
|
-spec rfc3339_to_unix_seconds(rfc3339()) -> unix_seconds(). |
| 61 |
|
rfc3339_to_unix_seconds(RFC3339) when is_binary(RFC3339) -> |
| 62 |
5 |
String = binary_to_list(RFC3339), |
| 63 |
5 |
calendar:rfc3339_to_system_time(String, [{unit, second}]). |
| 64 |
|
|
| 65 |
|
%% @doc |
| 66 |
|
%% Convert an RFC3339 datetime (binary) to Unix time (nanoseconds since epoch). |
| 67 |
|
%% |
| 68 |
|
%% Fractional seconds in the input are supported up to nanosecond precision. |
| 69 |
|
%% The returned integer is the UTC Unix timestamp in nanoseconds. |
| 70 |
|
%% |
| 71 |
|
%% Example: |
| 72 |
|
%% <pre> |
| 73 |
|
%% 1> klsn_time:rfc3339_to_unix_nanoseconds(<<"1970-01-01T00:00:00.123456789Z">>). |
| 74 |
|
%% 123456789 |
| 75 |
|
%% 2> klsn_time:rfc3339_to_unix_nanoseconds(<<"1970-01-01T08:59:59.500000000+09:00">>). |
| 76 |
|
%% -1500000000 |
| 77 |
|
%% </pre> |
| 78 |
|
-spec rfc3339_to_unix_nanoseconds(rfc3339()) -> unix_nanoseconds(). |
| 79 |
|
rfc3339_to_unix_nanoseconds(RFC3339) when is_binary(RFC3339) -> |
| 80 |
8 |
String = binary_to_list(RFC3339), |
| 81 |
8 |
calendar:rfc3339_to_system_time(String, [{unit, nanosecond}]). |
| 82 |
|
|
| 83 |
|
%% @doc |
| 84 |
|
%% Convert Unix time in seconds to RFC3339 datetime (binary). |
| 85 |
|
%% |
| 86 |
|
%% The returned RFC3339 string uses the fixed offset "+09:00". |
| 87 |
|
%% |
| 88 |
|
%% Example: |
| 89 |
|
%% <pre> |
| 90 |
|
%% 1> klsn_time:unix_seconds_to_rfc3339(0). |
| 91 |
|
%% <<"1970-01-01T09:00:00+09:00">> |
| 92 |
|
%% </pre> |
| 93 |
|
-spec unix_seconds_to_rfc3339(unix_seconds()) -> rfc3339(). |
| 94 |
|
unix_seconds_to_rfc3339(Seconds) when is_integer(Seconds) -> |
| 95 |
3 |
iolist_to_binary(calendar:system_time_to_rfc3339(Seconds, [{unit, second}, {offset, "+09:00"}])). |
| 96 |
|
|
| 97 |
|
%% @doc |
| 98 |
|
%% Convert Unix time in nanoseconds to RFC3339 datetime (binary). |
| 99 |
|
%% |
| 100 |
|
%% The returned RFC3339 string uses the fixed offset "+09:00" and includes a |
| 101 |
|
%% nanosecond fractional component (e.g. ".000000000"). |
| 102 |
|
%% |
| 103 |
|
%% Example: |
| 104 |
|
%% <pre> |
| 105 |
|
%% 1> klsn_time:unix_nanoseconds_to_rfc3339(0). |
| 106 |
|
%% <<"1970-01-01T09:00:00.000000000+09:00">> |
| 107 |
|
%% </pre> |
| 108 |
|
-spec unix_nanoseconds_to_rfc3339(unix_nanoseconds()) -> rfc3339(). |
| 109 |
|
unix_nanoseconds_to_rfc3339(Nanoseconds) when is_integer(Nanoseconds) -> |
| 110 |
4 |
iolist_to_binary(calendar:system_time_to_rfc3339(Nanoseconds, [{unit, nanosecond}, {offset, "+09:00"}])). |
| 111 |
|
|
| 112 |
|
%% @doc |
| 113 |
|
%% Convert Unix time from seconds to nanoseconds. |
| 114 |
|
%% |
| 115 |
|
%% This is an exact integer multiplication (1 s = 1,000,000,000 ns). |
| 116 |
|
%% |
| 117 |
|
%% Example: |
| 118 |
|
%% <pre> |
| 119 |
|
%% 1> klsn_time:unix_seconds_to_unix_nanoseconds(-1). |
| 120 |
|
%% -1000000000 |
| 121 |
|
%% </pre> |
| 122 |
|
-spec unix_seconds_to_unix_nanoseconds(unix_seconds()) -> unix_nanoseconds(). |
| 123 |
|
unix_seconds_to_unix_nanoseconds(Seconds) when is_integer(Seconds) -> |
| 124 |
3 |
erlang:convert_time_unit(Seconds, second, nanosecond). |
| 125 |
|
|
| 126 |
|
%% @doc |
| 127 |
|
%% Convert Unix time from nanoseconds to seconds. |
| 128 |
|
%% |
| 129 |
|
%% Uses the runtime's floor semantics for negative values |
| 130 |
|
%% (rounding toward negative infinity). |
| 131 |
|
%% |
| 132 |
|
%% Examples: |
| 133 |
|
%% <pre> |
| 134 |
|
%% 1> klsn_time:unix_nanoseconds_to_unix_seconds(1999999999). |
| 135 |
|
%% 1 |
| 136 |
|
%% 2> klsn_time:unix_nanoseconds_to_unix_seconds(-1). |
| 137 |
|
%% -1 |
| 138 |
|
%% 3> klsn_time:unix_nanoseconds_to_unix_seconds(-1999999999). |
| 139 |
|
%% -2 |
| 140 |
|
%% </pre> |
| 141 |
|
-spec unix_nanoseconds_to_unix_seconds(unix_nanoseconds()) -> unix_seconds(). |
| 142 |
|
unix_nanoseconds_to_unix_seconds(Nanoseconds) when is_integer(Nanoseconds) -> |
| 143 |
5 |
erlang:convert_time_unit(Nanoseconds, nanosecond, second). |