Skip to content
Draft
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
- new builtin to extract the bytes of a string: builtin__string:bytes
- math:fromBase to convert a number from a given base to base 10
- string:surrogate? and string:privateUse? to check if an unicode character is in one of those planes
- new builtins `builtin__time:timeToDate` and `builtin__time:parseDate` to convert a timestamp to a date, and parse a string date as a timestamp

### Changed
- math:toBase handles 0 correctly
Expand Down
2 changes: 2 additions & 0 deletions include/Ark/Builtins/Builtins.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ namespace Ark::internal::Builtins
namespace Time
{
ARK_BUILTIN(timeSinceEpoch);
ARK_BUILTIN(timestampToDate);
ARK_BUILTIN(parseDate);
}

namespace System
Expand Down
2 changes: 1 addition & 1 deletion lib/std
2 changes: 2 additions & 0 deletions src/arkreactor/Builtins/Builtins.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ namespace Ark::internal::Builtins

// Time
{ "time", Value(Time::timeSinceEpoch) },
{ "builtin__time:timeToDate", Value(Time::timestampToDate) },
{ "builtin__time:parseDate", Value(Time::parseDate) },

// System
{ "builtin__sys:platform", platform },
Expand Down
86 changes: 86 additions & 0 deletions src/arkreactor/Builtins/Time.cpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
#include <Ark/Builtins/Builtins.hpp>

#include <Ark/VM/DefaultValues.hpp>
#include <Ark/VM/Value/Dict.hpp>
#include <Ark/TypeChecker.hpp>

#undef abs
#include <chrono>
#include <ctime>

#include <Ark/VM/VM.hpp>

Expand All @@ -15,11 +20,92 @@ namespace Ark::internal::Builtins::Time
* =end
* @author https://github.com/SuperFola
*/
// cppcheck-suppress constParameterReference
Value timeSinceEpoch(std::vector<Value>& n [[maybe_unused]], VM* vm [[maybe_unused]])
{
const auto now = std::chrono::system_clock::now();
const auto epoch = now.time_since_epoch();
const auto microseconds = std::chrono::duration_cast<std::chrono::microseconds>(epoch);
return Value(static_cast<double>(microseconds.count()) / 1000000);
}

Value timestampToDate(std::vector<Value>& n, VM* vm [[maybe_unused]])
{
if (!types::check(n, ValueType::Number, ValueType::Any))
throw types::TypeCheckingError(
"timeToDate",
{ { types::Contract {
{ types::Typedef("timestamp", ValueType::Number),
types::Typedef("utc?", ValueType::Any) } } } },
n);

const bool is_utc = n[1] == True;
const auto timestamp = std::chrono::milliseconds(static_cast<long long>(1000.0 * n[0].number()));
const auto timepoint = std::chrono::time_point<std::chrono::system_clock>(timestamp);
const std::time_t time = std::chrono::system_clock::to_time_t(timepoint);
const std::tm* calendar_time = is_utc ? std::gmtime(&time) : std::localtime(&time);

if (calendar_time != nullptr)
{
internal::Dict dict;
dict.set(Value("millisecond"), Value(timestamp.count() % 1000));
dict.set(Value("second"), Value(calendar_time->tm_sec));
dict.set(Value("minute"), Value(calendar_time->tm_min));
dict.set(Value("hour"), Value(calendar_time->tm_hour));
dict.set(Value("day"), Value(calendar_time->tm_mday));
dict.set(Value("month"), Value(calendar_time->tm_mon + 1));
dict.set(Value("year"), Value(calendar_time->tm_year + 1900));
dict.set(Value("week_day"), Value(calendar_time->tm_wday));
dict.set(Value("year_day"), Value(calendar_time->tm_yday));
dict.set(Value("is_dst"), calendar_time->tm_isdst ? True : False);

return Value(std::move(dict));
}
return Nil;
}

int64_t makeTimestamp(const int tm_sec, const int tm_min, const int tm_hour, const int tm_mday, const int tm_mon, const int tm_year)
{
constexpr int MonthsPerYear = 12;
static const std::array<int, MonthsPerYear> cumulative_days = { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 };

const long year = 1900 + tm_year + tm_mon / MonthsPerYear;
int64_t result = (year - 1970) * 365 + cumulative_days[static_cast<std::size_t>(tm_mon % MonthsPerYear)];
result += (year - 1968) / 4;
result -= (year - 1900) / 100;
result += (year - 1600) / 400;
if ((year % 4) == 0 &&
((year % 100) != 0 || (year % 400) == 0) &&
(tm_mon % MonthsPerYear) < 2)
result--;
result += tm_mday - 1;
result *= 24;
result += tm_hour;
result *= 60;
result += tm_min;
result *= 60;
result += tm_sec;
return result;
}

Value parseDate(std::vector<Value>& n, VM* vm [[maybe_unused]])
{
if (!types::check(n, ValueType::String) && !types::check(n, ValueType::String, ValueType::String))
throw types::TypeCheckingError(
"parseDate",
{ { types::Contract {
{ types::Typedef("date", ValueType::String) } },
types::Contract {
{ types::Typedef("date", ValueType::String),
types::Typedef("format", ValueType::String) } } } },
n);

std::tm t = {};
std::istringstream ss(n[0].string());
ss >> std::get_time(&t, n.size() == 1 ? "%Y-%m-%dT%H:%M:%S" : n[1].string().c_str());

if (ss.fail())
return Nil;
return Value(makeTimestamp(t.tm_sec, t.tm_min, t.tm_hour, t.tm_mday, t.tm_mon, t.tm_year));
}
}
4 changes: 2 additions & 2 deletions tests/unittests/resources/CompilerSuite/ir/99bottles.expected
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ page_0
LOAD_CONST 3
LOAD_FAST 4
LOAD_FAST 4
CALL_BUILTIN 27, 3
CALL_BUILTIN 29, 3
.L7:
CALL_BUILTIN 9, 1
.L6:
Expand All @@ -50,7 +50,7 @@ page_0
PUSH_RETURN_ADDRESS L9
LOAD_CONST 4
LOAD_FAST 4
CALL_BUILTIN 27, 2
CALL_BUILTIN 29, 2
.L9:
CALL_BUILTIN 9, 1
.L8:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ page_1
LOAD_FAST_BY_INDEX 0
LOAD_FAST_BY_INDEX 1
LOAD_FAST_BY_INDEX 2
CALL_BUILTIN 27, 4
CALL_BUILTIN 29, 4
.L1:
CALL_BUILTIN 9, 1
.L0:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
page_0
PUSH_RETURN_ADDRESS L0
BUILTIN 87
BUILTIN 88
BUILTIN 89
BUILTIN 90
BUILTIN 91
Expand All @@ -25,6 +23,8 @@ page_0
BUILTIN 109
BUILTIN 110
BUILTIN 111
BUILTIN 112
BUILTIN 113
CALL_BUILTIN 9, 25
.L0:
POP 0
Expand Down
2 changes: 1 addition & 1 deletion tests/unittests/resources/CompilerSuite/ir/plugin.expected
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ page_0
.L1:
EQ 0
LOAD_CONST 3
CALL_BUILTIN 26, 2
CALL_BUILTIN 28, 2
.L0:
POP 0
HALT 0
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ page_0
LOAD_CONST_STORE 0, 0
LOAD_CONST_STORE 1, 2
LOAD_CONST_STORE 2, 4
BUILTIN 22
BUILTIN 24
STORE 6
STORE_FROM 8, 7
STORE_FROM 10, 9
Expand Down Expand Up @@ -37,7 +37,7 @@ page_0
LOAD_CONST 6
LOAD_FAST 13
LOAD_FAST 13
CALL_BUILTIN 27, 3
CALL_BUILTIN 29, 3
.L10:
CALL_BUILTIN 9, 1
.L9:
Expand All @@ -47,7 +47,7 @@ page_0
PUSH_RETURN_ADDRESS L12
LOAD_CONST 7
LOAD_FAST 13
CALL_BUILTIN 27, 2
CALL_BUILTIN 29, 2
.L12:
CALL_BUILTIN 9, 1
.L11:
Expand All @@ -58,19 +58,19 @@ page_0
HALT 0

page_1
CALL_BUILTIN_WITHOUT_RETURN_ADDRESS 23, 1
CALL_BUILTIN_WITHOUT_RETURN_ADDRESS 25, 1
.L0:
RET 0
HALT 0

page_2
CALL_BUILTIN_WITHOUT_RETURN_ADDRESS 24, 1
CALL_BUILTIN_WITHOUT_RETURN_ADDRESS 26, 1
.L1:
RET 0
HALT 0

page_3
CALL_BUILTIN_WITHOUT_RETURN_ADDRESS 25, 1
CALL_BUILTIN_WITHOUT_RETURN_ADDRESS 27, 1
.L2:
RET 0
HALT 0
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ page_0
HALT 0

page_1
CALL_BUILTIN_WITHOUT_RETURN_ADDRESS 58, 1
CALL_BUILTIN_WITHOUT_RETURN_ADDRESS 60, 1
.L0:
RET 0
HALT 0
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
(builtin__time:parseDate 1)
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
Function parseDate expected between 1 argument and 2 arguments
Call
↳ (parseDate 1)
Signature
↳ (parseDate date)
Arguments
→ `date' (expected String), got 1 (Number)

Alternative 2:
Signature
↳ (parseDate date format)
Arguments
→ `date' (expected String), got 1 (Number)
→ `format' (expected String) was not provided

In file tests/unittests/resources/DiagnosticsSuite/typeChecking/builtin_timeparsedate_num.ark:1
1 | (builtin__time:parseDate 1)
| ^~~~~~~~~~~~~~~~~~~~~~~~~~
2 |
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
(builtin__time:parseDate "1" 1)
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
Function parseDate expected between 1 argument and 2 arguments
Call
↳ (parseDate "1" 1)
Signature
↳ (parseDate date)
Arguments
→ `date' (expected String) ✓
→ unexpected additional args: 1 (Number)

Alternative 2:
Signature
↳ (parseDate date format)
Arguments
→ `date' (expected String) ✓
→ `format' (expected String), got 1 (Number)

In file tests/unittests/resources/DiagnosticsSuite/typeChecking/builtin_timeparsedate_str_num.ark:1
1 | (builtin__time:parseDate "1" 1)
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2 |
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
(builtin__time:timeToDate "1" true)
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
Function timeToDate expected 2 arguments
Call
↳ (timeToDate "1" true)
Signature
↳ (timeToDate timestamp utc?)
Arguments
→ `timestamp' (expected Number), got "1" (String)
→ `utc?' (expected any) ✓

In file tests/unittests/resources/DiagnosticsSuite/typeChecking/builtin_timetodate.ark:1
1 | (builtin__time:timeToDate "1" true)
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2 |
Loading