diff --git a/es-app/src/FileData.cpp b/es-app/src/FileData.cpp index 0a26c21ac..7417bb7d4 100644 --- a/es-app/src/FileData.cpp +++ b/es-app/src/FileData.cpp @@ -1,5 +1,6 @@ #include "FileData.h" +#include "utils/TimeUtil.h" #include "AudioManager.h" #include "CollectionSystemManager.h" #include "FileFilterIndex.h" @@ -11,7 +12,6 @@ #include "VolumeControl.h" #include "Window.h" #include -#include #include namespace fs = boost::filesystem; @@ -285,8 +285,7 @@ void FileData::launchGame(Window* window) gameToUpdate->metadata.set("playcount", std::to_string(static_cast(timesPlayed))); //update last played time - boost::posix_time::ptime time = boost::posix_time::second_clock::universal_time(); - gameToUpdate->metadata.setTime("lastplayed", time); + gameToUpdate->metadata.set("lastplayed", Utils::Time::DateTime(Utils::Time::now())); CollectionSystemManager::get()->refreshCollectionSystems(gameToUpdate); } diff --git a/es-app/src/FileSorts.cpp b/es-app/src/FileSorts.cpp index e99f26dec..2c4cac169 100644 --- a/es-app/src/FileSorts.cpp +++ b/es-app/src/FileSorts.cpp @@ -65,15 +65,9 @@ namespace FileSorts bool compareLastPlayed(const FileData* file1, const FileData* file2) { - //only games have lastplayed metadata - // since it's stored as a POSIX string (YYYYMMDDTHHMMSS,fffffffff), we can compare as a string + // since it's stored as an ISO string (YYYYMMDDTHHMMSS), we can compare as a string // as it's a lot faster than the time casts and then time comparisons - if(file1->metadata.getType() == GAME_METADATA && file2->metadata.getType() == GAME_METADATA) - { - return (file1)->metadata.get("lastplayed") < (file2)->metadata.get("lastplayed"); - } - - return false; + return (file1)->metadata.get("lastplayed") < (file2)->metadata.get("lastplayed"); } bool compareNumPlayers(const FileData* file1, const FileData* file2) @@ -83,7 +77,9 @@ namespace FileSorts bool compareReleaseDate(const FileData* file1, const FileData* file2) { - return (file1)->metadata.getTime("releasedate") < (file2)->metadata.getTime("releasedate"); + // since it's stored as an ISO string (YYYYMMDDTHHMMSS), we can compare as a string + // as it's a lot faster than the time casts and then time comparisons + return (file1)->metadata.get("releasedate") < (file2)->metadata.get("releasedate"); } bool compareGenre(const FileData* file1, const FileData* file2) diff --git a/es-app/src/MetaData.cpp b/es-app/src/MetaData.cpp index 7a1c6563a..4f79df9fd 100644 --- a/es-app/src/MetaData.cpp +++ b/es-app/src/MetaData.cpp @@ -2,7 +2,6 @@ #include "Log.h" #include "Util.h" -#include #include namespace fs = boost::filesystem; @@ -126,11 +125,6 @@ void MetaDataList::set(const std::string& key, const std::string& value) mWasChanged = true; } -void MetaDataList::setTime(const std::string& key, const boost::posix_time::ptime& time) -{ - set(key, boost::posix_time::to_iso_string(time)); -} - const std::string& MetaDataList::get(const std::string& key) const { return mMap.at(key); @@ -146,11 +140,6 @@ float MetaDataList::getFloat(const std::string& key) const return (float)atof(get(key).c_str()); } -boost::posix_time::ptime MetaDataList::getTime(const std::string& key) const -{ - return string_to_ptime(get(key), "%Y%m%dT%H%M%S%F%q"); -} - bool MetaDataList::isDefault() { const std::vector& mdd = getMDD(); diff --git a/es-app/src/MetaData.h b/es-app/src/MetaData.h index 57fd26b7e..7efdcda41 100644 --- a/es-app/src/MetaData.h +++ b/es-app/src/MetaData.h @@ -2,8 +2,8 @@ #ifndef ES_APP_META_DATA_H #define ES_APP_META_DATA_H -#include #include +#include namespace pugi { class xml_node; } @@ -50,12 +50,10 @@ public: MetaDataList(MetaDataListType type); void set(const std::string& key, const std::string& value); - void setTime(const std::string& key, const boost::posix_time::ptime& time); //times are internally stored as ISO strings (e.g. boost::posix_time::to_iso_string(ptime)) const std::string& get(const std::string& key) const; int getInt(const std::string& key) const; float getFloat(const std::string& key) const; - boost::posix_time::ptime getTime(const std::string& key) const; bool isDefault(); diff --git a/es-app/src/scrapers/GamesDBScraper.cpp b/es-app/src/scrapers/GamesDBScraper.cpp index cc76d8903..164570e40 100644 --- a/es-app/src/scrapers/GamesDBScraper.cpp +++ b/es-app/src/scrapers/GamesDBScraper.cpp @@ -1,5 +1,6 @@ #include "scrapers/GamesDBScraper.h" +#include "utils/TimeUtil.h" #include "FileData.h" #include "Log.h" #include "PlatformId.h" @@ -156,10 +157,7 @@ void TheGamesDBRequest::processGame(const pugi::xml_document& xmldoc, std::vecto result.mdl.set("name", game.child("GameTitle").text().get()); result.mdl.set("desc", game.child("Overview").text().get()); - - boost::posix_time::ptime rd = string_to_ptime(game.child("ReleaseDate").text().get(), "%m/%d/%Y"); - result.mdl.setTime("releasedate", rd); - + result.mdl.set("releasedate", Utils::Time::DateTime(Utils::Time::stringToTime(game.child("ReleaseDate").text().get(), "%m/%d/%Y"))); result.mdl.set("developer", game.child("Developer").text().get()); result.mdl.set("publisher", game.child("Publisher").text().get()); result.mdl.set("genre", game.child("Genres").first_child().text().get()); diff --git a/es-core/CMakeLists.txt b/es-core/CMakeLists.txt index 85975edea..5c140cf26 100644 --- a/es-core/CMakeLists.txt +++ b/es-core/CMakeLists.txt @@ -72,6 +72,7 @@ set(CORE_HEADERS # Utils ${CMAKE_CURRENT_SOURCE_DIR}/src/utils/FileSystemUtil.h ${CMAKE_CURRENT_SOURCE_DIR}/src/utils/StringUtil.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/utils/TimeUtil.h # Embedded assets (needed by ResourceManager) ${emulationstation-all_SOURCE_DIR}/data/Resources.h @@ -144,6 +145,7 @@ set(CORE_SOURCES # Utils ${CMAKE_CURRENT_SOURCE_DIR}/src/utils/FileSystemUtil.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/utils/StringUtil.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/utils/TimeUtil.cpp ) set(EMBEDDED_ASSET_SOURCES diff --git a/es-core/src/Util.cpp b/es-core/src/Util.cpp index 6ff6af612..3a52d4843 100644 --- a/es-core/src/Util.cpp +++ b/es-core/src/Util.cpp @@ -3,7 +3,6 @@ #include "platform.h" #include #include -#include #include namespace fs = boost::filesystem; @@ -164,16 +163,6 @@ fs::path makeRelativePath(const fs::path& path, const fs::path& relativeTo, bool return path; } -boost::posix_time::ptime string_to_ptime(const std::string& str, const std::string& fmt) -{ - std::istringstream ss(str); - ss.imbue(std::locale(std::locale::classic(), new boost::posix_time::time_input_facet(fmt))); //std::locale handles deleting the facet - boost::posix_time::ptime time; - ss >> time; - - return time; -} - std::string strreplace(std::string str, const std::string& replace, const std::string& with) { size_t pos; diff --git a/es-core/src/Util.h b/es-core/src/Util.h index f48ff0ebb..526cee99b 100644 --- a/es-core/src/Util.h +++ b/es-core/src/Util.h @@ -2,7 +2,6 @@ #ifndef ES_CORE_UTIL_H #define ES_CORE_UTIL_H -#include #include std::string strToUpper(const char* from); @@ -23,8 +22,6 @@ boost::filesystem::path makeRelativePath(const boost::filesystem::path& path, co // if allowHome is true, also expands "~/my/path.sfc" to "/home/pi/my/path.sfc" boost::filesystem::path resolvePath(const boost::filesystem::path& path, const boost::filesystem::path& relativeTo, bool allowHome); -boost::posix_time::ptime string_to_ptime(const std::string& str, const std::string& fmt = "%Y%m%dT%H%M%S%F%q"); - std::string escapePath(const boost::filesystem::path& path); std::string strreplace(std::string str, const std::string& replace, const std::string& with); diff --git a/es-core/src/components/DateTimeComponent.cpp b/es-core/src/components/DateTimeComponent.cpp index 5fddab961..aba383f5a 100644 --- a/es-core/src/components/DateTimeComponent.cpp +++ b/es-core/src/components/DateTimeComponent.cpp @@ -3,7 +3,6 @@ #include "resources/Font.h" #include "Renderer.h" #include "Util.h" -#include DateTimeComponent::DateTimeComponent(Window* window, DisplayMode dispMode) : GuiComponent(window), mEditing(false), mEditIndex(0), mDisplayMode(dispMode), mRelativeUpdateAccumulator(0), @@ -34,9 +33,9 @@ bool DateTimeComponent::input(InputConfig* config, Input input) mTimeBeforeEdit = mTime; //initialize to now if unset - if(mTime == boost::posix_time::not_a_date_time) + if(mTime.getTime() == Utils::Time::NOT_A_DATE_TIME) { - mTime = boost::posix_time::ptime(boost::gregorian::day_clock::local_day()); + mTime = Utils::Time::now(); updateTextCache(); } } @@ -62,39 +61,43 @@ bool DateTimeComponent::input(InputConfig* config, Input input) if(incDir != 0) { - tm new_tm = boost::posix_time::to_tm(mTime); + tm new_tm = mTime; if(mEditIndex == 0) { new_tm.tm_mon += incDir; if(new_tm.tm_mon > 11) - new_tm.tm_mon = 11; - else if(new_tm.tm_mon < 0) new_tm.tm_mon = 0; + else if(new_tm.tm_mon < 0) + new_tm.tm_mon = 11; - }else if(mEditIndex == 1) + } + else if(mEditIndex == 1) { + const int days_in_month = Utils::Time::daysInMonth(new_tm.tm_year + 1900, new_tm.tm_mon + 1); new_tm.tm_mday += incDir; - int days_in_month = mTime.date().end_of_month().day().as_number(); - if(new_tm.tm_mday > days_in_month) - new_tm.tm_mday = days_in_month; - else if(new_tm.tm_mday < 1) - new_tm.tm_mday = 1; - }else if(mEditIndex == 2) + if(new_tm.tm_mday > days_in_month) + new_tm.tm_mday = 1; + else if(new_tm.tm_mday < 1) + new_tm.tm_mday = days_in_month; + + } + else if(mEditIndex == 2) { new_tm.tm_year += incDir; + if(new_tm.tm_year < 0) new_tm.tm_year = 0; } //validate day - int days_in_month = boost::gregorian::date((unsigned short)new_tm.tm_year + 1900, (unsigned short)new_tm.tm_mon + 1, 1).end_of_month().day().as_number(); + const int days_in_month = Utils::Time::daysInMonth(new_tm.tm_year + 1900, new_tm.tm_mon + 1); if(new_tm.tm_mday > days_in_month) new_tm.tm_mday = days_in_month; - mTime = boost::posix_time::ptime_from_tm(new_tm); + mTime = new_tm; updateTextCache(); return true; @@ -166,13 +169,13 @@ void DateTimeComponent::render(const Transform4x4f& parentTrans) void DateTimeComponent::setValue(const std::string& val) { - mTime = string_to_ptime(val); + mTime = val; updateTextCache(); } std::string DateTimeComponent::getValue() const { - return boost::posix_time::to_iso_string(mTime); + return mTime; } DateTimeComponent::DisplayMode DateTimeComponent::getCurrentDisplayMode() const @@ -203,40 +206,32 @@ std::string DateTimeComponent::getDisplayString(DisplayMode mode) const case DISP_RELATIVE_TO_NOW: { //relative time - using namespace boost::posix_time; - - if(mTime == not_a_date_time) + if(mTime.getTime() == 0) return "never"; - ptime now = second_clock::universal_time(); - time_duration dur = now - mTime; + Utils::Time::DateTime now(Utils::Time::now()); + Utils::Time::Duration dur(now.getTime() - mTime.getTime()); - if(dur < seconds(2)) - return "just now"; - if(dur < seconds(60)) - return std::to_string((long long)dur.seconds()) + " secs ago"; - if(dur < minutes(60)) - return std::to_string((long long)dur.minutes()) + " min" + (dur < minutes(2) ? "" : "s") + " ago"; - if(dur < hours(24)) - return std::to_string((long long)dur.hours()) + " hour" + (dur < hours(2) ? "" : "s") + " ago"; + char buf[64]; - long long days = (long long)(dur.hours() / 24); - return std::to_string(days) + " day" + (days < 2 ? "" : "s") + " ago"; + if(dur.getDays() > 0) + sprintf(buf, "%d day%s ago", dur.getDays(), (dur.getDays() > 1) ? "s" : ""); + else if(dur.getHours() > 0) + sprintf(buf, "%d hour%s ago", dur.getHours(), (dur.getHours() > 1) ? "s" : ""); + else if(dur.getMinutes() > 0) + sprintf(buf, "%d minute%s ago", dur.getMinutes(), (dur.getMinutes() > 1) ? "s" : ""); + else + sprintf(buf, "%d second%s ago", dur.getSeconds(), (dur.getSeconds() > 1) ? "s" : ""); + + return std::string(buf); } break; } - if(mTime == boost::posix_time::not_a_date_time) + if(mTime.getTime() == 0) return "unknown"; - boost::posix_time::time_facet* facet = new boost::posix_time::time_facet(); - facet->format(fmt.c_str()); - std::locale loc(std::locale::classic(), facet); - - std::stringstream ss; - ss.imbue(loc); - ss << mTime; - return ss.str(); + return Utils::Time::timeToString(mTime, fmt); } std::shared_ptr DateTimeComponent::getFont() const diff --git a/es-core/src/components/DateTimeComponent.h b/es-core/src/components/DateTimeComponent.h index 29e940c67..8e629c491 100644 --- a/es-core/src/components/DateTimeComponent.h +++ b/es-core/src/components/DateTimeComponent.h @@ -2,8 +2,8 @@ #ifndef ES_CORE_COMPONENTS_DATE_TIME_COMPONENT_H #define ES_CORE_COMPONENTS_DATE_TIME_COMPONENT_H +#include "utils/TimeUtil.h" #include "GuiComponent.h" -#include class TextCache; @@ -49,8 +49,8 @@ private: void updateTextCache(); - boost::posix_time::ptime mTime; - boost::posix_time::ptime mTimeBeforeEdit; + Utils::Time::DateTime mTime; + Utils::Time::DateTime mTimeBeforeEdit; bool mEditing; int mEditIndex; diff --git a/es-core/src/utils/TimeUtil.cpp b/es-core/src/utils/TimeUtil.cpp new file mode 100644 index 000000000..39f389464 --- /dev/null +++ b/es-core/src/utils/TimeUtil.cpp @@ -0,0 +1,286 @@ +#include "utils/TimeUtil.h" + +#include + +namespace Utils +{ + namespace Time + { + DateTime::DateTime() + { + mTime = 0; + mTimeStruct = { 0, 0, 0, 1, 0, 0, 0, 0, -1 }; + mIsoString = "00000000T000000"; + + } // Time + + DateTime::DateTime(const time_t& _time) + { + setTime(_time); + + } // Time + + DateTime::DateTime(const tm& _timeStruct) + { + setTimeStruct(_timeStruct); + + } // Time + + DateTime::DateTime(const std::string& _isoString) + { + setIsoString(_isoString); + + } // Time + + DateTime::~DateTime() + { + + } // ~Time + + void DateTime::setTime(const time_t& _time) + { + if(_time < 0) mTime = 0; + else mTime = _time; + mTimeStruct = *localtime(&mTime); + mIsoString = timeToString(mTime); + + } // setTime + + void DateTime::setTimeStruct(const tm& _timeStruct) + { + setTime(mktime((tm*)&_timeStruct)); + + } // setTimeStruct + + void DateTime::setIsoString(const std::string& _isoString) + { + setTime(stringToTime(_isoString)); + + } // setIsoString + + Duration::Duration(const time_t& _time) + { + mTotalSeconds = (unsigned int)_time; + mDays = (mTotalSeconds - (mTotalSeconds % (60*60*24))) / (60*60*24); + mHours = ((mTotalSeconds % (60*60*24)) - (mTotalSeconds % (60*60))) / (60*60); + mMinutes = ((mTotalSeconds % (60*60)) - (mTotalSeconds % (60))) / 60; + mSeconds = mTotalSeconds % 60; + + } // Duration + + Duration::~Duration() + { + + } // ~Duration + + time_t now() + { + time_t time; + ::time(&time); + return time; + + } // now + + time_t stringToTime(const std::string& _string, const std::string& _format) + { + const char* s = _string.c_str(); + const char* f = _format.c_str(); + tm timeStruct = { 0, 0, 0, 1, 0, 0, 0, 0, -1 }; + int parsedChars = 0; + + if(_string == "not-a-date-time") + return mktime(&timeStruct); + + while(*f && (parsedChars < _string.length())) + { + if(*f == '%') + { + ++f; + + switch(*f++) + { + case 'Y': // The year [1970,xxxx] + { + if((parsedChars + 4) <= _string.length()) + { + timeStruct.tm_year = (*s++ - '0') * 1000; + timeStruct.tm_year += (*s++ - '0') * 100; + timeStruct.tm_year += (*s++ - '0') * 10; + timeStruct.tm_year += (*s++ - '0'); + if(timeStruct.tm_year >= 1900) + timeStruct.tm_year -= 1900; + } + + parsedChars += 4; + } + break; + + case 'm': // The month number [01,12] + { + if((parsedChars + 2) <= _string.length()) + { + timeStruct.tm_mon = (*s++ - '0') * 10; + timeStruct.tm_mon += (*s++ - '0'); + if(timeStruct.tm_mon >= 1) + timeStruct.tm_mon -= 1; + } + + parsedChars += 2; + } + break; + + case 'd': // The day of the month [01,31] + { + if((parsedChars + 2) <= _string.length()) + { + timeStruct.tm_mday = (*s++ - '0') * 10; + timeStruct.tm_mday += (*s++ - '0'); + } + + parsedChars += 2; + } + break; + + case 'H': // The hour (24-hour clock) [00,23] + { + if((parsedChars + 2) <= _string.length()) + { + timeStruct.tm_hour = (*s++ - '0') * 10; + timeStruct.tm_hour += (*s++ - '0'); + } + + parsedChars += 2; + } + break; + + case 'M': // The minute [00,59] + { + if((parsedChars + 2) <= _string.length()) + { + timeStruct.tm_min = (*s++ - '0') * 10; + timeStruct.tm_min += (*s++ - '0'); + } + + parsedChars += 2; + } + break; + + case 'S': // The second [00,59] + { + if((parsedChars + 2) <= _string.length()) + { + timeStruct.tm_sec = (*s++ - '0') * 10; + timeStruct.tm_sec += (*s++ - '0'); + } + + parsedChars += 2; + } + break; + } + } + else + { + ++s; + ++f; + } + } + + return mktime(&timeStruct); + + } // stringToTime + + std::string timeToString(const time_t& _time, const std::string& _format) + { + const char* f = _format.c_str(); + const tm timeStruct = *localtime(&_time); + char buf[256] = { '\0' }; + char* s = buf; + + while(*f) + { + if(*f == '%') + { + ++f; + + switch(*f++) + { + case 'Y': // The year, including the century (1900) + { + const int year = timeStruct.tm_year + 1900; + *s++ = (char)((year - (year % 1000)) / 1000) + '0'; + *s++ = (char)(((year % 1000) - (year % 100)) / 100) + '0'; + *s++ = (char)(((year % 100) - (year % 10)) / 10) + '0'; + *s++ = (char)(year % 10) + '0'; + } + break; + + case 'm': // The month number [00,11] + { + const int mon = timeStruct.tm_mon + 1; + *s++ = (char)(mon / 10) + '0'; + *s++ = (char)(mon % 10) + '0'; + } + break; + + case 'd': // The day of the month [01,31] + { + *s++ = (char)(timeStruct.tm_mday / 10) + '0'; + *s++ = (char)(timeStruct.tm_mday % 10) + '0'; + } + break; + + case 'H': // The hour (24-hour clock) [00,23] + { + *s++ = (char)(timeStruct.tm_hour / 10) + '0'; + *s++ = (char)(timeStruct.tm_hour % 10) + '0'; + } + break; + + case 'M': // The minute [00,59] + { + *s++ = (char)(timeStruct.tm_min / 10) + '0'; + *s++ = (char)(timeStruct.tm_min % 10) + '0'; + } + break; + + case 'S': // The second [00,59] + { + *s++ = (char)(timeStruct.tm_sec / 10) + '0'; + *s++ = (char)(timeStruct.tm_sec % 10) + '0'; + } + break; + } + } + else + { + *s++ = *f++; + } + + *s = '\0'; + } + + return std::string(buf); + + } // timeToString + + int daysInMonth(const int _year, const int _month) + { + tm timeStruct = { 0, 0, 0, 0, _month, _year - 1900, 0, 0, -1 }; + mktime(&timeStruct); + + return timeStruct.tm_mday; + + } // daysInMonth + + int daysInYear(const int _year) + { + tm timeStruct = { 0, 0, 0, 0, 0, _year - 1900 + 1, 0, 0, -1 }; + mktime(&timeStruct); + + return timeStruct.tm_yday + 1; + + } // daysInYear + + } // Time:: + +} // Utils:: diff --git a/es-core/src/utils/TimeUtil.h b/es-core/src/utils/TimeUtil.h new file mode 100644 index 000000000..9b44207b3 --- /dev/null +++ b/es-core/src/utils/TimeUtil.h @@ -0,0 +1,78 @@ +#pragma once +#ifndef ES_CORE_UTILS_TIME_UTIL_H +#define ES_CORE_UTILS_TIME_UTIL_H + +#include + +namespace Utils +{ + namespace Time + { + static int NOT_A_DATE_TIME = 0; + + class DateTime + { + public: + + DateTime(); + DateTime(const time_t& _time); + DateTime(const tm& _timeStruct); + DateTime(const std::string& _isoString); + ~DateTime(); + + const bool operator< (const DateTime& _other) const { return (mTime < _other.mTime); } + const bool operator<= (const DateTime& _other) const { return (mTime <= _other.mTime); } + const bool operator> (const DateTime& _other) const { return (mTime > _other.mTime); } + const bool operator>= (const DateTime& _other) const { return (mTime >= _other.mTime); } + operator time_t () const { return mTime; } + operator tm () const { return mTimeStruct; } + operator std::string() const { return mIsoString; } + + void setTime (const time_t& _time); + const time_t& getTime () const { return mTime; } + void setTimeStruct(const tm& _timeStruct); + const tm& getTimeStruct() const { return mTimeStruct; } + void setIsoString (const std::string& _isoString); + const std::string& getIsoString () const { return mIsoString; } + + private: + + time_t mTime; + tm mTimeStruct; + std::string mIsoString; + + }; // DateTime + + class Duration + { + public: + + Duration(const time_t& _time); + ~Duration(); + + unsigned int getDays () const { return mDays; } + unsigned int getHours () const { return mHours; } + unsigned int getMinutes() const { return mMinutes; } + unsigned int getSeconds() const { return mSeconds; } + + private: + + unsigned int mTotalSeconds; + unsigned int mDays; + unsigned int mHours; + unsigned int mMinutes; + unsigned int mSeconds; + + }; // Duration + + time_t now (); + time_t stringToTime(const std::string& _string, const std::string& _format = "%Y%m%dT%H%M%S"); + std::string timeToString(const time_t& _time, const std::string& _format = "%Y%m%dT%H%M%S"); + int daysInMonth (const int _year, const int _month); + int daysInYear (const int _year); + + } // Time:: + +} // Utils:: + +#endif // ES_CORE_UTILS_TIME_UTIL_H