From f64fea1b23df163485720c03054b3a17ae0c673a Mon Sep 17 00:00:00 2001 From: Aaron Franke Date: Mon, 24 May 2021 07:54:05 -0400 Subject: [PATCH] Add Time singleton --- core/core_bind.cpp | 231 -------------- core/core_bind.h | 8 - core/io/logger.cpp | 7 +- core/os/os.cpp | 33 +- core/os/os.h | 33 +- core/os/time.cpp | 433 +++++++++++++++++++++++++++ core/os/time.h | 109 +++++++ core/register_core_types.cpp | 3 + doc/classes/@GlobalScope.xml | 3 + doc/classes/File.xml | 2 +- doc/classes/OS.xml | 78 ----- doc/classes/Time.xml | 270 +++++++++++++++++ doc/classes/XRServer.xml | 4 +- drivers/unix/os_unix.cpp | 4 +- editor/editor_node.cpp | 3 +- main/main.cpp | 9 + modules/mono/mono_gd/gd_mono_log.cpp | 4 +- platform/android/export/export.cpp | 4 +- platform/linuxbsd/os_linuxbsd.cpp | 4 +- platform/osx/export/export.cpp | 8 +- platform/windows/os_windows.cpp | 4 +- tests/test_main.cpp | 1 + tests/test_time.h | 145 +++++++++ 23 files changed, 1012 insertions(+), 388 deletions(-) create mode 100644 core/os/time.cpp create mode 100644 core/os/time.h create mode 100644 doc/classes/Time.xml create mode 100644 tests/test_time.h diff --git a/core/core_bind.cpp b/core/core_bind.cpp index fe0166d0e674..c000a54ae36a 100644 --- a/core/core_bind.cpp +++ b/core/core_bind.cpp @@ -42,28 +42,6 @@ #include "core/os/keyboard.h" #include "core/os/os.h" -/** - * Time constants borrowed from loc_time.h - */ -#define EPOCH_YR 1970 /* EPOCH = Jan 1 1970 00:00:00 */ -#define SECS_DAY (24L * 60L * 60L) -#define LEAPYEAR(year) (!((year) % 4) && (((year) % 100) || !((year) % 400))) -#define YEARSIZE(year) (LEAPYEAR(year) ? 366 : 365) -#define SECOND_KEY "second" -#define MINUTE_KEY "minute" -#define HOUR_KEY "hour" -#define DAY_KEY "day" -#define MONTH_KEY "month" -#define YEAR_KEY "year" -#define WEEKDAY_KEY "weekday" -#define DST_KEY "dst" - -/// Table of number of days in each month (for regular year and leap year) -static const unsigned int MONTH_DAYS_TABLE[2][12] = { - { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }, - { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 } -}; - ////// _ResourceLoader ////// _ResourceLoader *_ResourceLoader::singleton = nullptr; @@ -322,197 +300,6 @@ uint64_t _OS::get_static_memory_peak_usage() const { return OS::get_singleton()->get_static_memory_peak_usage(); } -/** - * Get current datetime with consideration for utc and - * dst - */ -Dictionary _OS::get_datetime(bool utc) const { - Dictionary dated = get_date(utc); - Dictionary timed = get_time(utc); - - List keys; - timed.get_key_list(&keys); - - for (int i = 0; i < keys.size(); i++) { - dated[keys[i]] = timed[keys[i]]; - } - - return dated; -} - -Dictionary _OS::get_date(bool utc) const { - OS::Date date = OS::get_singleton()->get_date(utc); - Dictionary dated; - dated[YEAR_KEY] = date.year; - dated[MONTH_KEY] = date.month; - dated[DAY_KEY] = date.day; - dated[WEEKDAY_KEY] = date.weekday; - dated[DST_KEY] = date.dst; - return dated; -} - -Dictionary _OS::get_time(bool utc) const { - OS::Time time = OS::get_singleton()->get_time(utc); - Dictionary timed; - timed[HOUR_KEY] = time.hour; - timed[MINUTE_KEY] = time.min; - timed[SECOND_KEY] = time.sec; - return timed; -} - -/** - * Get an epoch time value from a dictionary of time values - * @p datetime must be populated with the following keys: - * day, hour, minute, month, second, year. (dst is ignored). - * - * You can pass the output from - * get_datetime_from_unix_time directly into this function - * - * @param datetime dictionary of date and time values to convert - * - * @return epoch calculated - */ -int64_t _OS::get_unix_time_from_datetime(Dictionary datetime) const { - // if datetime is an empty Dictionary throws an error - ERR_FAIL_COND_V_MSG(datetime.is_empty(), 0, "Invalid datetime Dictionary: Dictionary is empty"); - - // Bunch of conversion constants - static const unsigned int SECONDS_PER_MINUTE = 60; - static const unsigned int MINUTES_PER_HOUR = 60; - static const unsigned int HOURS_PER_DAY = 24; - static const unsigned int SECONDS_PER_HOUR = MINUTES_PER_HOUR * SECONDS_PER_MINUTE; - static const unsigned int SECONDS_PER_DAY = SECONDS_PER_HOUR * HOURS_PER_DAY; - - // Get all time values from the dictionary, set to zero if it doesn't exist. - // Risk incorrect calculation over throwing errors - unsigned int second = ((datetime.has(SECOND_KEY)) ? static_cast(datetime[SECOND_KEY]) : 0); - unsigned int minute = ((datetime.has(MINUTE_KEY)) ? static_cast(datetime[MINUTE_KEY]) : 0); - unsigned int hour = ((datetime.has(HOUR_KEY)) ? static_cast(datetime[HOUR_KEY]) : 0); - unsigned int day = ((datetime.has(DAY_KEY)) ? static_cast(datetime[DAY_KEY]) : 1); - unsigned int month = ((datetime.has(MONTH_KEY)) ? static_cast(datetime[MONTH_KEY]) : 1); - unsigned int year = ((datetime.has(YEAR_KEY)) ? static_cast(datetime[YEAR_KEY]) : 1970); - - /// How many days come before each month (0-12) - static const unsigned short int DAYS_PAST_THIS_YEAR_TABLE[2][13] = { - /* Normal years. */ - { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 }, - /* Leap years. */ - { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 } - }; - - ERR_FAIL_COND_V_MSG(second > 59, 0, "Invalid second value of: " + itos(second) + "."); - ERR_FAIL_COND_V_MSG(minute > 59, 0, "Invalid minute value of: " + itos(minute) + "."); - ERR_FAIL_COND_V_MSG(hour > 23, 0, "Invalid hour value of: " + itos(hour) + "."); - ERR_FAIL_COND_V_MSG(year == 0, 0, "Years before 1 AD are not supported. Value passed: " + itos(year) + "."); - ERR_FAIL_COND_V_MSG(month > 12 || month == 0, 0, "Invalid month value of: " + itos(month) + "."); - // Do this check after month is tested as valid - unsigned int days_in_month = MONTH_DAYS_TABLE[LEAPYEAR(year)][month - 1]; - ERR_FAIL_COND_V_MSG(day == 0 || day > days_in_month, 0, "Invalid day value of: " + itos(day) + ". It should be comprised between 1 and " + itos(days_in_month) + " for month " + itos(month) + "."); - - // Calculate all the seconds from months past in this year - uint64_t SECONDS_FROM_MONTHS_PAST_THIS_YEAR = DAYS_PAST_THIS_YEAR_TABLE[LEAPYEAR(year)][month - 1] * SECONDS_PER_DAY; - - int64_t SECONDS_FROM_YEARS_PAST = 0; - if (year >= EPOCH_YR) { - for (unsigned int iyear = EPOCH_YR; iyear < year; iyear++) { - SECONDS_FROM_YEARS_PAST += YEARSIZE(iyear) * SECONDS_PER_DAY; - } - } else { - for (unsigned int iyear = EPOCH_YR - 1; iyear >= year; iyear--) { - SECONDS_FROM_YEARS_PAST -= YEARSIZE(iyear) * SECONDS_PER_DAY; - } - } - - int64_t epoch = - second + - minute * SECONDS_PER_MINUTE + - hour * SECONDS_PER_HOUR + - // Subtract 1 from day, since the current day isn't over yet - // and we cannot count all 24 hours. - (day - 1) * SECONDS_PER_DAY + - SECONDS_FROM_MONTHS_PAST_THIS_YEAR + - SECONDS_FROM_YEARS_PAST; - return epoch; -} - -/** - * Get a dictionary of time values when given epoch time - * - * Dictionary Time values will be a union if values from #get_time - * and #get_date dictionaries (with the exception of dst = - * day light standard time, as it cannot be determined from epoch) - * - * @param unix_time_val epoch time to convert - * - * @return dictionary of date and time values - */ -Dictionary _OS::get_datetime_from_unix_time(int64_t unix_time_val) const { - OS::Date date; - OS::Time time; - - long dayclock, dayno; - int year = EPOCH_YR; - - if (unix_time_val >= 0) { - dayno = unix_time_val / SECS_DAY; - dayclock = unix_time_val % SECS_DAY; - /* day 0 was a thursday */ - date.weekday = static_cast((dayno + 4) % 7); - while (dayno >= YEARSIZE(year)) { - dayno -= YEARSIZE(year); - year++; - } - } else { - dayno = (unix_time_val - SECS_DAY + 1) / SECS_DAY; - dayclock = unix_time_val - dayno * SECS_DAY; - date.weekday = static_cast(((dayno % 7) + 11) % 7); - do { - year--; - dayno += YEARSIZE(year); - } while (dayno < 0); - } - - time.sec = dayclock % 60; - time.min = (dayclock % 3600) / 60; - time.hour = dayclock / 3600; - date.year = year; - - size_t imonth = 0; - - while ((unsigned long)dayno >= MONTH_DAYS_TABLE[LEAPYEAR(year)][imonth]) { - dayno -= MONTH_DAYS_TABLE[LEAPYEAR(year)][imonth]; - imonth++; - } - - /// Add 1 to month to make sure months are indexed starting at 1 - date.month = static_cast(imonth + 1); - - date.day = dayno + 1; - - Dictionary timed; - timed[HOUR_KEY] = time.hour; - timed[MINUTE_KEY] = time.min; - timed[SECOND_KEY] = time.sec; - timed[YEAR_KEY] = date.year; - timed[MONTH_KEY] = date.month; - timed[DAY_KEY] = date.day; - timed[WEEKDAY_KEY] = date.weekday; - - return timed; -} - -Dictionary _OS::get_time_zone_info() const { - OS::TimeZoneInfo info = OS::get_singleton()->get_time_zone_info(); - Dictionary infod; - infod["bias"] = info.bias; - infod["name"] = info.name; - return infod; -} - -double _OS::get_unix_time() const { - return OS::get_singleton()->get_unix_time(); -} - /** This method uses a signed argument for better error reporting as it's used from the scripting API. */ void _OS::delay_usec(int p_usec) const { ERR_FAIL_COND_MSG( @@ -529,14 +316,6 @@ void _OS::delay_msec(int p_msec) const { OS::get_singleton()->delay_usec(int64_t(p_msec) * 1000); } -uint32_t _OS::get_ticks_msec() const { - return OS::get_singleton()->get_ticks_msec(); -} - -uint64_t _OS::get_ticks_usec() const { - return OS::get_singleton()->get_ticks_usec(); -} - bool _OS::can_use_threads() const { return OS::get_singleton()->can_use_threads(); } @@ -716,18 +495,8 @@ void _OS::_bind_methods() { ClassDB::bind_method(D_METHOD("get_name"), &_OS::get_name); ClassDB::bind_method(D_METHOD("get_cmdline_args"), &_OS::get_cmdline_args); - ClassDB::bind_method(D_METHOD("get_datetime", "utc"), &_OS::get_datetime, DEFVAL(false)); - ClassDB::bind_method(D_METHOD("get_date", "utc"), &_OS::get_date, DEFVAL(false)); - ClassDB::bind_method(D_METHOD("get_time", "utc"), &_OS::get_time, DEFVAL(false)); - ClassDB::bind_method(D_METHOD("get_time_zone_info"), &_OS::get_time_zone_info); - ClassDB::bind_method(D_METHOD("get_unix_time"), &_OS::get_unix_time); - ClassDB::bind_method(D_METHOD("get_datetime_from_unix_time", "unix_time_val"), &_OS::get_datetime_from_unix_time); - ClassDB::bind_method(D_METHOD("get_unix_time_from_datetime", "datetime"), &_OS::get_unix_time_from_datetime); - ClassDB::bind_method(D_METHOD("delay_usec", "usec"), &_OS::delay_usec); ClassDB::bind_method(D_METHOD("delay_msec", "msec"), &_OS::delay_msec); - ClassDB::bind_method(D_METHOD("get_ticks_msec"), &_OS::get_ticks_msec); - ClassDB::bind_method(D_METHOD("get_ticks_usec"), &_OS::get_ticks_usec); ClassDB::bind_method(D_METHOD("get_locale"), &_OS::get_locale); ClassDB::bind_method(D_METHOD("get_model_name"), &_OS::get_model_name); diff --git a/core/core_bind.h b/core/core_bind.h index c8b0a4d7da80..c3107987f764 100644 --- a/core/core_bind.h +++ b/core/core_bind.h @@ -199,14 +199,6 @@ class _OS : public Object { void set_use_file_access_save_and_swap(bool p_enable); - Dictionary get_date(bool utc) const; - Dictionary get_time(bool utc) const; - Dictionary get_datetime(bool utc) const; - Dictionary get_datetime_from_unix_time(int64_t unix_time_val) const; - int64_t get_unix_time_from_datetime(Dictionary datetime) const; - Dictionary get_time_zone_info() const; - double get_unix_time() const; - uint64_t get_static_memory_usage() const; uint64_t get_static_memory_peak_usage() const; diff --git a/core/io/logger.cpp b/core/io/logger.cpp index 8a07459a1dff..7ffa5b50ea5d 100644 --- a/core/io/logger.cpp +++ b/core/io/logger.cpp @@ -33,6 +33,7 @@ #include "core/config/project_settings.h" #include "core/os/dir_access.h" #include "core/os/os.h" +#include "core/os/time.h" #include "core/string/print_string.h" #if defined(MINGW_ENABLED) || defined(_MSC_VER) @@ -156,11 +157,7 @@ void RotatedFileLogger::rotate_file() { if (FileAccess::exists(base_path)) { if (max_files > 1) { - char timestamp[21]; - OS::Date date = OS::get_singleton()->get_date(); - OS::Time time = OS::get_singleton()->get_time(); - sprintf(timestamp, "_%04d-%02d-%02d_%02d.%02d.%02d", date.year, date.month, date.day, time.hour, time.min, time.sec); - + String timestamp = Time::get_singleton()->get_datetime_string_from_system().replace(":", "."); String backup_name = base_path.get_basename() + timestamp; if (base_path.get_extension() != String()) { backup_name += "." + base_path.get_extension(); diff --git a/core/os/os.cpp b/core/os/os.cpp index c5463cf2162d..a16f9de7e5c8 100644 --- a/core/os/os.cpp +++ b/core/os/os.cpp @@ -47,37 +47,8 @@ OS *OS::get_singleton() { return singleton; } -uint32_t OS::get_ticks_msec() const { - return get_ticks_usec() / 1000; -} - -String OS::get_iso_date_time(bool local) const { - OS::Date date = get_date(local); - OS::Time time = get_time(local); - - String timezone; - if (!local) { - TimeZoneInfo zone = get_time_zone_info(); - if (zone.bias >= 0) { - timezone = "+"; - } - timezone = timezone + itos(zone.bias / 60).pad_zeros(2) + itos(zone.bias % 60).pad_zeros(2); - } else { - timezone = "Z"; - } - - return itos(date.year).pad_zeros(2) + - "-" + - itos(date.month).pad_zeros(2) + - "-" + - itos(date.day).pad_zeros(2) + - "T" + - itos(time.hour).pad_zeros(2) + - ":" + - itos(time.min).pad_zeros(2) + - ":" + - itos(time.sec).pad_zeros(2) + - timezone; +uint64_t OS::get_ticks_msec() const { + return get_ticks_usec() / 1000ULL; } double OS::get_unix_time() const { diff --git a/core/os/os.h b/core/os/os.h index 5bf9dc9288b0..444f67431f33 100644 --- a/core/os/os.h +++ b/core/os/os.h @@ -158,17 +158,17 @@ class OS { virtual void yield(); - enum Weekday { - DAY_SUNDAY, - DAY_MONDAY, - DAY_TUESDAY, - DAY_WEDNESDAY, - DAY_THURSDAY, - DAY_FRIDAY, - DAY_SATURDAY + enum Weekday : uint8_t { + WEEKDAY_SUNDAY, + WEEKDAY_MONDAY, + WEEKDAY_TUESDAY, + WEEKDAY_WEDNESDAY, + WEEKDAY_THURSDAY, + WEEKDAY_FRIDAY, + WEEKDAY_SATURDAY, }; - enum Month { + enum Month : uint8_t { /// Start at 1 to follow Windows SYSTEMTIME structure /// https://msdn.microsoft.com/en-us/library/windows/desktop/ms724950(v=vs.85).aspx MONTH_JANUARY = 1, @@ -182,21 +182,21 @@ class OS { MONTH_SEPTEMBER, MONTH_OCTOBER, MONTH_NOVEMBER, - MONTH_DECEMBER + MONTH_DECEMBER, }; struct Date { - int year; + int64_t year; Month month; - int day; + uint8_t day; Weekday weekday; bool dst; }; struct Time { - int hour; - int min; - int sec; + uint8_t hour; + uint8_t minute; + uint8_t second; }; struct TimeZoneInfo { @@ -207,14 +207,13 @@ class OS { virtual Date get_date(bool local = false) const = 0; virtual Time get_time(bool local = false) const = 0; virtual TimeZoneInfo get_time_zone_info() const = 0; - virtual String get_iso_date_time(bool local = false) const; virtual double get_unix_time() const; virtual void delay_usec(uint32_t p_usec) const = 0; virtual void add_frame_delay(bool p_can_draw); virtual uint64_t get_ticks_usec() const = 0; - uint32_t get_ticks_msec() const; + uint64_t get_ticks_msec() const; virtual bool is_userfs_persistent() const { return true; } diff --git a/core/os/time.cpp b/core/os/time.cpp new file mode 100644 index 000000000000..a185029969c6 --- /dev/null +++ b/core/os/time.cpp @@ -0,0 +1,433 @@ +/*************************************************************************/ +/* time.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "time.h" + +#include "core/os/os.h" + +#define UNIX_EPOCH_YEAR_AD 1970 // 1970 +#define SECONDS_PER_DAY (24 * 60 * 60) // 86400 +#define IS_LEAP_YEAR(year) (!((year) % 4) && (((year) % 100) || !((year) % 400))) +#define YEAR_SIZE(year) (IS_LEAP_YEAR(year) ? 366 : 365) + +#define YEAR_KEY "year" +#define MONTH_KEY "month" +#define DAY_KEY "day" +#define WEEKDAY_KEY "weekday" +#define HOUR_KEY "hour" +#define MINUTE_KEY "minute" +#define SECOND_KEY "second" +#define DST_KEY "dst" + +// Table of number of days in each month (for regular year and leap year). +static const uint8_t MONTH_DAYS_TABLE[2][12] = { + { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }, + { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 } +}; + +VARIANT_ENUM_CAST(Time::Month); +VARIANT_ENUM_CAST(Time::Weekday); + +#define UNIX_TIME_TO_HMS \ + uint8_t hour, minute, second; \ + { \ + /* The time of the day (in seconds since start of day). */ \ + uint32_t day_clock = Math::posmod(p_unix_time_val, SECONDS_PER_DAY); \ + /* On x86 these 4 lines can be optimized to only 2 divisions. */ \ + second = day_clock % 60; \ + day_clock /= 60; \ + minute = day_clock % 60; \ + hour = day_clock / 60; \ + } + +#define UNIX_TIME_TO_YMD \ + int64_t year; \ + Month month; \ + uint8_t day; \ + /* The day number since Unix epoch (0-index). Days before 1970 are negative. */ \ + int64_t day_number = Math::floor(p_unix_time_val / (double)SECONDS_PER_DAY); \ + { \ + int64_t day_number_copy = day_number; \ + year = UNIX_EPOCH_YEAR_AD; \ + uint8_t month_zero_index = 0; \ + while (day_number_copy >= YEAR_SIZE(year)) { \ + day_number_copy -= YEAR_SIZE(year); \ + year++; \ + } \ + while (day_number_copy < 0) { \ + year--; \ + day_number_copy += YEAR_SIZE(year); \ + } \ + /* After the above, day_number now represents the day of the year (0-index). */ \ + while (day_number_copy >= MONTH_DAYS_TABLE[IS_LEAP_YEAR(year)][month_zero_index]) { \ + day_number_copy -= MONTH_DAYS_TABLE[IS_LEAP_YEAR(year)][month_zero_index]; \ + month_zero_index++; \ + } \ + /* After the above, day_number now represents the day of the month (0-index). */ \ + month = (Month)(month_zero_index + 1); \ + day = day_number_copy + 1; \ + } + +#define VALIDATE_YMDHMS \ + ERR_FAIL_COND_V_MSG(month == 0, 0, "Invalid month value of: " + itos(month) + ", months are 1-indexed and cannot be 0. See the Time.Month enum for valid values."); \ + ERR_FAIL_COND_V_MSG(month > 12, 0, "Invalid month value of: " + itos(month) + ". See the Time.Month enum for valid values."); \ + ERR_FAIL_COND_V_MSG(hour > 23, 0, "Invalid hour value of: " + itos(hour) + "."); \ + ERR_FAIL_COND_V_MSG(minute > 59, 0, "Invalid minute value of: " + itos(minute) + "."); \ + ERR_FAIL_COND_V_MSG(second > 59, 0, "Invalid second value of: " + itos(second) + " (leap seconds are not supported)."); \ + /* Do this check after month is tested as valid. */ \ + ERR_FAIL_COND_V_MSG(day == 0, 0, "Invalid day value of: " + itos(month) + ", days are 1-indexed and cannot be 0."); \ + uint8_t days_in_this_month = MONTH_DAYS_TABLE[IS_LEAP_YEAR(year)][month - 1]; \ + ERR_FAIL_COND_V_MSG(day > days_in_this_month, 0, "Invalid day value of: " + itos(day) + " which is larger than the maximum for this month, " + itos(days_in_this_month) + "."); + +#define YMD_TO_DAY_NUMBER \ + /* The day number since Unix epoch (0-index). Days before 1970 are negative. */ \ + int64_t day_number = day - 1; \ + /* Add the days in the months to day_number. */ \ + for (int i = 0; i < month - 1; i++) { \ + day_number += MONTH_DAYS_TABLE[IS_LEAP_YEAR(year)][i]; \ + } \ + /* Add the days in the years to day_number. */ \ + if (year >= UNIX_EPOCH_YEAR_AD) { \ + for (int64_t iyear = UNIX_EPOCH_YEAR_AD; iyear < year; iyear++) { \ + day_number += YEAR_SIZE(iyear); \ + } \ + } else { \ + for (int64_t iyear = UNIX_EPOCH_YEAR_AD - 1; iyear >= year; iyear--) { \ + day_number -= YEAR_SIZE(iyear); \ + } \ + } + +#define PARSE_ISO8601_STRING \ + int64_t year = UNIX_EPOCH_YEAR_AD; \ + Month month = MONTH_JANUARY; \ + uint8_t day = 1; \ + uint8_t hour = 0; \ + uint8_t minute = 0; \ + uint8_t second = 0; \ + { \ + bool has_date = false, has_time = false; \ + String date, time; \ + if (p_datetime.find_char('T') > 0) { \ + has_date = has_time = true; \ + PackedStringArray array = p_datetime.split("T"); \ + date = array[0]; \ + time = array[1]; \ + } else if (p_datetime.find_char(' ') > 0) { \ + has_date = has_time = true; \ + PackedStringArray array = p_datetime.split(" "); \ + date = array[0]; \ + time = array[1]; \ + } else if (p_datetime.find_char('-', 1) > 0) { \ + has_date = true; \ + date = p_datetime; \ + } else if (p_datetime.find_char(':') > 0) { \ + has_time = true; \ + time = p_datetime; \ + } \ + /* Set the variables from the contents of the string. */ \ + if (has_date) { \ + PackedInt32Array array = date.split_ints("-", false); \ + year = array[0]; \ + month = (Month)array[1]; \ + day = array[2]; \ + /* Handle negative years. */ \ + if (p_datetime.find_char('-') == 0) { \ + year *= -1; \ + } \ + } \ + if (has_time) { \ + PackedInt32Array array = time.split_ints(":", false); \ + hour = array[0]; \ + minute = array[1]; \ + second = array[2]; \ + } \ + } + +#define EXTRACT_FROM_DICTIONARY \ + /* Get all time values from the dictionary. If it doesn't exist, set the */ \ + /* values to the default values for Unix epoch (1970-01-01 00:00:00). */ \ + int64_t year = p_datetime.has(YEAR_KEY) ? int64_t(p_datetime[YEAR_KEY]) : UNIX_EPOCH_YEAR_AD; \ + Month month = Month((p_datetime.has(MONTH_KEY)) ? uint8_t(p_datetime[MONTH_KEY]) : 1); \ + uint8_t day = p_datetime.has(DAY_KEY) ? uint8_t(p_datetime[DAY_KEY]) : 1; \ + uint8_t hour = p_datetime.has(HOUR_KEY) ? uint8_t(p_datetime[HOUR_KEY]) : 0; \ + uint8_t minute = p_datetime.has(MINUTE_KEY) ? uint8_t(p_datetime[MINUTE_KEY]) : 0; \ + uint8_t second = p_datetime.has(SECOND_KEY) ? uint8_t(p_datetime[SECOND_KEY]) : 0; + +Time *Time::singleton = nullptr; + +Time *Time::get_singleton() { + if (!singleton) { + memnew(Time); + } + return singleton; +} + +Dictionary Time::get_datetime_dict_from_unix_time(int64_t p_unix_time_val) const { + UNIX_TIME_TO_HMS + UNIX_TIME_TO_YMD + Dictionary datetime; + datetime[YEAR_KEY] = year; + datetime[MONTH_KEY] = (uint8_t)month; + datetime[DAY_KEY] = day; + // Unix epoch was a Thursday (day 0 aka 1970-01-01). + datetime[WEEKDAY_KEY] = Math::posmod(day_number + WEEKDAY_THURSDAY, 7); + datetime[HOUR_KEY] = hour; + datetime[MINUTE_KEY] = minute; + datetime[SECOND_KEY] = second; + + return datetime; +} + +Dictionary Time::get_date_dict_from_unix_time(int64_t p_unix_time_val) const { + UNIX_TIME_TO_YMD + Dictionary datetime; + datetime[YEAR_KEY] = year; + datetime[MONTH_KEY] = (uint8_t)month; + datetime[DAY_KEY] = day; + // Unix epoch was a Thursday (day 0 aka 1970-01-01). + datetime[WEEKDAY_KEY] = Math::posmod(day_number + WEEKDAY_THURSDAY, 7); + + return datetime; +} + +Dictionary Time::get_time_dict_from_unix_time(int64_t p_unix_time_val) const { + UNIX_TIME_TO_HMS + Dictionary datetime; + datetime[HOUR_KEY] = hour; + datetime[MINUTE_KEY] = minute; + datetime[SECOND_KEY] = second; + + return datetime; +} + +String Time::get_datetime_string_from_unix_time(int64_t p_unix_time_val, bool p_use_space) const { + UNIX_TIME_TO_HMS + UNIX_TIME_TO_YMD + // vformat only supports up to 6 arguments, so we need to split this up into 2 parts. + String timestamp = vformat("%04d-%02d-%02d", year, (uint8_t)month, day); + if (p_use_space) { + timestamp = vformat("%s %02d:%02d:%02d", timestamp, hour, minute, second); + } else { + timestamp = vformat("%sT%02d:%02d:%02d", timestamp, hour, minute, second); + } + + return timestamp; +} + +String Time::get_date_string_from_unix_time(int64_t p_unix_time_val) const { + UNIX_TIME_TO_YMD + // Android is picky about the types passed to make Variant, so we need a cast. + return vformat("%04d-%02d-%02d", year, (uint8_t)month, day); +} + +String Time::get_time_string_from_unix_time(int64_t p_unix_time_val) const { + UNIX_TIME_TO_HMS + return vformat("%02d:%02d:%02d", hour, minute, second); +} + +Dictionary Time::get_datetime_dict_from_string(String p_datetime, bool p_weekday) const { + PARSE_ISO8601_STRING + Dictionary dict; + dict[YEAR_KEY] = year; + dict[MONTH_KEY] = (uint8_t)month; + dict[DAY_KEY] = day; + if (p_weekday) { + YMD_TO_DAY_NUMBER + // Unix epoch was a Thursday (day 0 aka 1970-01-01). + dict[WEEKDAY_KEY] = Math::posmod(day_number + WEEKDAY_THURSDAY, 7); + } + dict[HOUR_KEY] = hour; + dict[MINUTE_KEY] = minute; + dict[SECOND_KEY] = second; + + return dict; +} + +String Time::get_datetime_string_from_dict(Dictionary p_datetime, bool p_use_space) const { + ERR_FAIL_COND_V_MSG(p_datetime.is_empty(), "", "Invalid datetime Dictionary: Dictionary is empty."); + EXTRACT_FROM_DICTIONARY + // vformat only supports up to 6 arguments, so we need to split this up into 2 parts. + String timestamp = vformat("%04d-%02d-%02d", year, (uint8_t)month, day); + if (p_use_space) { + timestamp = vformat("%s %02d:%02d:%02d", timestamp, hour, minute, second); + } else { + timestamp = vformat("%sT%02d:%02d:%02d", timestamp, hour, minute, second); + } + return timestamp; +} + +int64_t Time::get_unix_time_from_datetime_dict(Dictionary p_datetime) const { + ERR_FAIL_COND_V_MSG(p_datetime.is_empty(), 0, "Invalid datetime Dictionary: Dictionary is empty"); + EXTRACT_FROM_DICTIONARY + VALIDATE_YMDHMS + YMD_TO_DAY_NUMBER + return day_number * SECONDS_PER_DAY + hour * 3600 + minute * 60 + second; +} + +int64_t Time::get_unix_time_from_datetime_string(String p_datetime) const { + PARSE_ISO8601_STRING + VALIDATE_YMDHMS + YMD_TO_DAY_NUMBER + return day_number * SECONDS_PER_DAY + hour * 3600 + minute * 60 + second; +} + +Dictionary Time::get_datetime_dict_from_system(bool p_utc) const { + OS::Date date = OS::get_singleton()->get_date(p_utc); + OS::Time time = OS::get_singleton()->get_time(p_utc); + Dictionary datetime; + datetime[YEAR_KEY] = date.year; + datetime[MONTH_KEY] = (uint8_t)date.month; + datetime[DAY_KEY] = date.day; + datetime[WEEKDAY_KEY] = (uint8_t)date.weekday; + datetime[DST_KEY] = date.dst; + datetime[HOUR_KEY] = time.hour; + datetime[MINUTE_KEY] = time.minute; + datetime[SECOND_KEY] = time.second; + return datetime; +} + +Dictionary Time::get_date_dict_from_system(bool p_utc) const { + OS::Date date = OS::get_singleton()->get_date(p_utc); + Dictionary date_dictionary; + date_dictionary[YEAR_KEY] = date.year; + date_dictionary[MONTH_KEY] = (uint8_t)date.month; + date_dictionary[DAY_KEY] = date.day; + date_dictionary[WEEKDAY_KEY] = (uint8_t)date.weekday; + date_dictionary[DST_KEY] = date.dst; + return date_dictionary; +} + +Dictionary Time::get_time_dict_from_system(bool p_utc) const { + OS::Time time = OS::get_singleton()->get_time(p_utc); + Dictionary time_dictionary; + time_dictionary[HOUR_KEY] = time.hour; + time_dictionary[MINUTE_KEY] = time.minute; + time_dictionary[SECOND_KEY] = time.second; + return time_dictionary; +} + +String Time::get_datetime_string_from_system(bool p_utc, bool p_use_space) const { + OS::Date date = OS::get_singleton()->get_date(p_utc); + OS::Time time = OS::get_singleton()->get_time(p_utc); + // vformat only supports up to 6 arguments, so we need to split this up into 2 parts. + String timestamp = vformat("%04d-%02d-%02d", date.year, (uint8_t)date.month, date.day); + if (p_use_space) { + timestamp = vformat("%s %02d:%02d:%02d", timestamp, time.hour, time.minute, time.second); + } else { + timestamp = vformat("%sT%02d:%02d:%02d", timestamp, time.hour, time.minute, time.second); + } + + return timestamp; +} + +String Time::get_date_string_from_system(bool p_utc) const { + OS::Date date = OS::get_singleton()->get_date(p_utc); + // Android is picky about the types passed to make Variant, so we need a cast. + return vformat("%04d-%02d-%02d", date.year, (uint8_t)date.month, date.day); +} + +String Time::get_time_string_from_system(bool p_utc) const { + OS::Time time = OS::get_singleton()->get_time(p_utc); + return vformat("%02d:%02d:%02d", time.hour, time.minute, time.second); +} + +Dictionary Time::get_time_zone_from_system() const { + OS::TimeZoneInfo info = OS::get_singleton()->get_time_zone_info(); + Dictionary timezone; + timezone["bias"] = info.bias; + timezone["name"] = info.name; + return timezone; +} + +double Time::get_unix_time_from_system() const { + return OS::get_singleton()->get_unix_time(); +} + +uint64_t Time::get_ticks_msec() const { + return OS::get_singleton()->get_ticks_msec(); +} + +uint64_t Time::get_ticks_usec() const { + return OS::get_singleton()->get_ticks_usec(); +} + +void Time::_bind_methods() { + ClassDB::bind_method(D_METHOD("get_datetime_dict_from_unix_time", "unix_time_val"), &Time::get_datetime_dict_from_unix_time); + ClassDB::bind_method(D_METHOD("get_date_dict_from_unix_time", "unix_time_val"), &Time::get_date_dict_from_unix_time); + ClassDB::bind_method(D_METHOD("get_time_dict_from_unix_time", "unix_time_val"), &Time::get_time_dict_from_unix_time); + ClassDB::bind_method(D_METHOD("get_datetime_string_from_unix_time", "unix_time_val", "use_space"), &Time::get_datetime_string_from_unix_time, DEFVAL(false)); + ClassDB::bind_method(D_METHOD("get_date_string_from_unix_time", "unix_time_val"), &Time::get_date_string_from_unix_time); + ClassDB::bind_method(D_METHOD("get_time_string_from_unix_time", "unix_time_val"), &Time::get_time_string_from_unix_time); + ClassDB::bind_method(D_METHOD("get_datetime_dict_from_string", "datetime", "weekday"), &Time::get_datetime_dict_from_string); + ClassDB::bind_method(D_METHOD("get_datetime_string_from_dict", "datetime", "use_space"), &Time::get_datetime_string_from_dict); + ClassDB::bind_method(D_METHOD("get_unix_time_from_datetime_dict", "datetime"), &Time::get_unix_time_from_datetime_dict); + ClassDB::bind_method(D_METHOD("get_unix_time_from_datetime_string", "datetime"), &Time::get_unix_time_from_datetime_string); + + ClassDB::bind_method(D_METHOD("get_datetime_dict_from_system", "utc"), &Time::get_datetime_dict_from_system, DEFVAL(false)); + ClassDB::bind_method(D_METHOD("get_date_dict_from_system", "utc"), &Time::get_date_dict_from_system, DEFVAL(false)); + ClassDB::bind_method(D_METHOD("get_time_dict_from_system", "utc"), &Time::get_time_dict_from_system, DEFVAL(false)); + ClassDB::bind_method(D_METHOD("get_datetime_string_from_system", "utc", "use_space"), &Time::get_datetime_string_from_system, DEFVAL(false), DEFVAL(false)); + ClassDB::bind_method(D_METHOD("get_date_string_from_system", "utc"), &Time::get_date_string_from_system, DEFVAL(false)); + ClassDB::bind_method(D_METHOD("get_time_string_from_system", "utc"), &Time::get_time_string_from_system, DEFVAL(false)); + ClassDB::bind_method(D_METHOD("get_time_zone_from_system"), &Time::get_time_zone_from_system); + ClassDB::bind_method(D_METHOD("get_unix_time_from_system"), &Time::get_unix_time_from_system); + ClassDB::bind_method(D_METHOD("get_ticks_msec"), &Time::get_ticks_msec); + ClassDB::bind_method(D_METHOD("get_ticks_usec"), &Time::get_ticks_usec); + + BIND_ENUM_CONSTANT(MONTH_JANUARY); + BIND_ENUM_CONSTANT(MONTH_FEBRUARY); + BIND_ENUM_CONSTANT(MONTH_MARCH); + BIND_ENUM_CONSTANT(MONTH_APRIL); + BIND_ENUM_CONSTANT(MONTH_MAY); + BIND_ENUM_CONSTANT(MONTH_JUNE); + BIND_ENUM_CONSTANT(MONTH_JULY); + BIND_ENUM_CONSTANT(MONTH_AUGUST); + BIND_ENUM_CONSTANT(MONTH_SEPTEMBER); + BIND_ENUM_CONSTANT(MONTH_OCTOBER); + BIND_ENUM_CONSTANT(MONTH_NOVEMBER); + BIND_ENUM_CONSTANT(MONTH_DECEMBER); + + BIND_ENUM_CONSTANT(WEEKDAY_SUNDAY); + BIND_ENUM_CONSTANT(WEEKDAY_MONDAY); + BIND_ENUM_CONSTANT(WEEKDAY_TUESDAY); + BIND_ENUM_CONSTANT(WEEKDAY_WEDNESDAY); + BIND_ENUM_CONSTANT(WEEKDAY_THURSDAY); + BIND_ENUM_CONSTANT(WEEKDAY_FRIDAY); + BIND_ENUM_CONSTANT(WEEKDAY_SATURDAY); +} + +Time::Time() { + ERR_FAIL_COND_MSG(singleton, "Singleton for Time already exists."); + singleton = this; +} + +Time::~Time() { + singleton = nullptr; +} diff --git a/core/os/time.h b/core/os/time.h new file mode 100644 index 000000000000..4325f93d569c --- /dev/null +++ b/core/os/time.h @@ -0,0 +1,109 @@ +/*************************************************************************/ +/* time.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef TIME_H +#define TIME_H + +#include "core/object/class_db.h" + +// This Time class conforms with as many of the ISO 8601 standards as possible. +// * As per ISO 8601:2004 4.3.2.1, all dates follow the Proleptic Gregorian +// calendar. As such, the day before 1582-10-15 is 1582-10-14, not 1582-10-04. +// See: https://en.wikipedia.org/wiki/Proleptic_Gregorian_calendar +// * As per ISO 8601:2004 3.4.2 and 4.1.2.4, the year before 1 AD (aka 1 BC) +// is number "0", with the year before that (2 BC) being "-1", etc. +// Conversion methods assume "the same timezone", and do not handle DST. +// Leap seconds are not handled, they must be done manually if desired. +// Suffixes such as "Z" are not handled, you need to strip them away manually. + +class Time : public Object { + GDCLASS(Time, Object); + static void _bind_methods(); + static Time *singleton; + +public: + static Time *get_singleton(); + + enum Month : uint8_t { + /// Start at 1 to follow Windows SYSTEMTIME structure + /// https://msdn.microsoft.com/en-us/library/windows/desktop/ms724950(v=vs.85).aspx + MONTH_JANUARY = 1, + MONTH_FEBRUARY, + MONTH_MARCH, + MONTH_APRIL, + MONTH_MAY, + MONTH_JUNE, + MONTH_JULY, + MONTH_AUGUST, + MONTH_SEPTEMBER, + MONTH_OCTOBER, + MONTH_NOVEMBER, + MONTH_DECEMBER, + }; + + enum Weekday : uint8_t { + WEEKDAY_SUNDAY, + WEEKDAY_MONDAY, + WEEKDAY_TUESDAY, + WEEKDAY_WEDNESDAY, + WEEKDAY_THURSDAY, + WEEKDAY_FRIDAY, + WEEKDAY_SATURDAY, + }; + + // Methods that convert times. + Dictionary get_datetime_dict_from_unix_time(int64_t p_unix_time_val) const; + Dictionary get_date_dict_from_unix_time(int64_t p_unix_time_val) const; + Dictionary get_time_dict_from_unix_time(int64_t p_unix_time_val) const; + String get_datetime_string_from_unix_time(int64_t p_unix_time_val, bool p_use_space = false) const; + String get_date_string_from_unix_time(int64_t p_unix_time_val) const; + String get_time_string_from_unix_time(int64_t p_unix_time_val) const; + Dictionary get_datetime_dict_from_string(String p_datetime, bool p_weekday = true) const; + String get_datetime_string_from_dict(Dictionary p_datetime, bool p_use_space = false) const; + int64_t get_unix_time_from_datetime_dict(Dictionary p_datetime) const; + int64_t get_unix_time_from_datetime_string(String p_datetime) const; + + // Methods that get information from OS. + Dictionary get_datetime_dict_from_system(bool p_utc = false) const; + Dictionary get_date_dict_from_system(bool p_utc = false) const; + Dictionary get_time_dict_from_system(bool p_utc = false) const; + String get_datetime_string_from_system(bool p_utc = false, bool p_use_space = false) const; + String get_date_string_from_system(bool p_utc = false) const; + String get_time_string_from_system(bool p_utc = false) const; + Dictionary get_time_zone_from_system() const; + double get_unix_time_from_system() const; + uint64_t get_ticks_msec() const; + uint64_t get_ticks_usec() const; + + Time(); + virtual ~Time(); +}; + +#endif // TIME_H diff --git a/core/register_core_types.cpp b/core/register_core_types.cpp index f1b1b98beac3..7f74f4740ccd 100644 --- a/core/register_core_types.cpp +++ b/core/register_core_types.cpp @@ -68,6 +68,7 @@ #include "core/object/class_db.h" #include "core/object/undo_redo.h" #include "core/os/main_loop.h" +#include "core/os/time.h" #include "core/string/optimized_translation.h" #include "core/string/translation.h" @@ -258,6 +259,7 @@ void register_core_singletons() { ClassDB::register_class<_JSON>(); ClassDB::register_class(); ClassDB::register_class<_EngineDebugger>(); + ClassDB::register_class