DateTime.cpp (8970B)
1 // Copyright (c) Microsoft Corporation. 2 // Licensed under the MIT License. 3 #include "pch.h" 4 #include "Public/AppInstallerDateTime.h" 5 6 using namespace std::chrono; 7 8 namespace AppInstaller::Utility 9 { 10 namespace 11 { 12 struct OutputTimePointContext 13 { 14 OutputTimePointContext(std::ostream& stream, const std::chrono::system_clock::time_point& time, TimeFacet facet) : 15 Stream(stream), Time(time), Facet(facet) 16 { 17 auto tt = system_clock::to_time_t(time); 18 _localtime64_s(&LocalTime, &tt); 19 } 20 21 std::ostream& Stream; 22 const std::chrono::system_clock::time_point& Time; 23 tm LocalTime{}; 24 TimeFacet Facet; 25 }; 26 27 struct OutputTimePointFacetInfo 28 { 29 TimeFacet Facet; 30 char FollowingSeparator; 31 void (*Action)(const OutputTimePointContext&); 32 }; 33 } 34 35 void OutputTimePoint(std::ostream& stream, const std::chrono::system_clock::time_point& time, bool useRFC3339) 36 { 37 OutputTimePoint(stream, time, TimeFacet::Default | (useRFC3339 ? TimeFacet::RFC3339 : TimeFacet::None)); 38 } 39 40 // If moved to C++20, this can be replaced with standard library implementations. 41 void OutputTimePoint(std::ostream& stream, const std::chrono::system_clock::time_point& time, TimeFacet facet) 42 { 43 OutputTimePointContext context{ stream, time, facet }; 44 using Ctx = const OutputTimePointContext&; 45 46 bool useRFC3339 = WI_IsFlagSet(facet, TimeFacet::RFC3339); 47 bool filename = WI_IsFlagSet(facet, TimeFacet::Filename); 48 char day_time_separator = useRFC3339 ? 'T' : (filename ? '-' : ' '); 49 char time_field_separator = filename ? '-' : ':'; 50 51 bool needsSeparator = false; 52 char currentSeparator = '-'; 53 54 for (const auto& info : { 55 OutputTimePointFacetInfo{ TimeFacet::ShortYear, '-', [](Ctx ctx) { ctx.Stream << (ctx.LocalTime.tm_year - 100); }}, 56 OutputTimePointFacetInfo{ TimeFacet::Year, '-', [](Ctx ctx) { ctx.Stream << (1900 + ctx.LocalTime.tm_year); }}, 57 OutputTimePointFacetInfo{ TimeFacet::Month, '-', [](Ctx ctx) { ctx.Stream << std::setw(2) << std::setfill('0') << (1 + ctx.LocalTime.tm_mon); }}, 58 OutputTimePointFacetInfo{ TimeFacet::Day, day_time_separator, [](Ctx ctx) { ctx.Stream << std::setw(2) << std::setfill('0') << ctx.LocalTime.tm_mday; }}, 59 OutputTimePointFacetInfo{ TimeFacet::Hour, time_field_separator, [](Ctx ctx) { ctx.Stream << std::setw(2) << std::setfill('0') << ctx.LocalTime.tm_hour; }}, 60 OutputTimePointFacetInfo{ TimeFacet::Minute, time_field_separator, [](Ctx ctx) { ctx.Stream << std::setw(2) << std::setfill('0') << ctx.LocalTime.tm_min; }}, 61 OutputTimePointFacetInfo{ TimeFacet::Second, '.', [](Ctx ctx) { ctx.Stream << std::setw(2) << std::setfill('0') << ctx.LocalTime.tm_sec; }}, 62 OutputTimePointFacetInfo{ TimeFacet::Millisecond, '-', [](Ctx ctx) 63 { 64 // Get partial seconds 65 auto sinceEpoch = ctx.Time.time_since_epoch(); 66 auto leftoverMillis = duration_cast<milliseconds>(sinceEpoch) - duration_cast<seconds>(sinceEpoch); 67 68 ctx.Stream << std::setw(3) << std::setfill('0') << leftoverMillis.count(); 69 }}, 70 OutputTimePointFacetInfo{ TimeFacet::RFC3339, '\0', [](Ctx ctx) 71 { 72 // RFC 3339 requires adding time zone info. 73 // No need to bother getting the actual time zone as we don't need it. 74 // -00:00 represents an unspecified time zone, not UTC. 75 ctx.Stream << "00:00"; 76 }}, 77 }) 78 { 79 if (WI_AreAllFlagsSet(facet, info.Facet)) 80 { 81 if (needsSeparator) 82 { 83 stream << currentSeparator; 84 } 85 86 info.Action(context); 87 needsSeparator = true; 88 } 89 90 // Getting this right for every mix of facets is probably not possible. 91 // Future needs can dictate changes here. 92 currentSeparator = info.FollowingSeparator; 93 } 94 } 95 96 std::string TimePointToString(const std::chrono::system_clock::time_point& time, bool useRFC3339) 97 { 98 std::ostringstream stream; 99 OutputTimePoint(stream, time, useRFC3339); 100 return std::move(stream).str(); 101 } 102 103 std::string TimePointToString(const std::chrono::system_clock::time_point& time, TimeFacet facet) 104 { 105 std::ostringstream stream; 106 OutputTimePoint(stream, time, facet); 107 return std::move(stream).str(); 108 } 109 110 std::string GetCurrentTimeForFilename(bool shortTime) 111 { 112 return TimePointToString(std::chrono::system_clock::now(), (shortTime ? TimeFacet::ShortYearSecondPrecision : TimeFacet::Default) | TimeFacet::Filename); 113 } 114 115 std::string GetCurrentDateForARP() 116 { 117 auto now = std::chrono::system_clock::now(); 118 std::time_t tt = std::chrono::system_clock::to_time_t(now); 119 120 struct tm newTime; 121 localtime_s(&newTime, &tt); 122 123 std::stringstream ss; 124 ss << std::put_time(&newTime, "%Y%m%d"); 125 return ss.str(); 126 } 127 128 int64_t GetCurrentUnixEpoch() 129 { 130 static_assert(std::is_same_v<int64_t, decltype(time(nullptr))>, "time returns a 64-bit integer"); 131 time_t now = time(nullptr); 132 return static_cast<int64_t>(now); 133 } 134 135 int64_t ConvertSystemClockToUnixEpoch(const std::chrono::system_clock::time_point& time) 136 { 137 static_assert(std::is_same_v<int64_t, decltype(std::chrono::system_clock::to_time_t(time))>, "to_time_t returns a 64-bit integer"); 138 time_t timeAsTimeT = std::chrono::system_clock::to_time_t(time); 139 return static_cast<int64_t>(timeAsTimeT); 140 } 141 142 std::chrono::system_clock::time_point ConvertUnixEpochToSystemClock(int64_t epoch) 143 { 144 return std::chrono::system_clock::from_time_t(static_cast<time_t>(epoch)); 145 } 146 147 std::chrono::system_clock::time_point ConvertFiletimeToSystemClock(const FILETIME& fileTime) 148 { 149 // Windows epoch (1601) to Unix epoch (1970) offset in 100-nanosecond intervals 150 constexpr int64_t EPOCH_DIFFERENCE = 116444736000000000LL; 151 152 // Combine FILETIME into a 64-bit value 153 uint64_t fileTimeValue = (static_cast<uint64_t>(fileTime.dwHighDateTime) << 32) | fileTime.dwLowDateTime; 154 155 // Convert to 100-nanosecond intervals since Unix epoch 156 int64_t unixTime100ns = static_cast<int64_t>(fileTimeValue) - EPOCH_DIFFERENCE; 157 158 // Convert to chrono duration (system_clock::duration is usually nanoseconds or microseconds) 159 return std::chrono::system_clock::time_point( 160 std::chrono::duration_cast<std::chrono::system_clock::duration>( 161 std::chrono::nanoseconds(unixTime100ns * 100))); 162 } 163 164 std::chrono::system_clock::time_point GetTimePointFromVersion(const UInt64Version& version) 165 { 166 // Our custom format for converting UTC into a version is: 167 // Major :: `Year` [1, 9999] 168 // Minor :: `Month * 100 + Day` where Month [1, 12] and Day [1, 31] 169 // Build :: `Hour * 100 + Minute` where Hour [1, 24] and Minute [0, 59] 170 // Revision :: Milliseconds, but since no seconds are available we will disregard this 171 172 tm versionTime{}; 173 174 // Limit to the range supported by _mkgmtime64, which is 1970 to 3000 (hello to Y3K maintainers from 2023!) 175 UINT64 majorVersion = version.Major(); 176 if (majorVersion < 1970 || majorVersion > 3000) 177 { 178 return std::chrono::system_clock::time_point::min(); 179 } 180 versionTime.tm_year = static_cast<int>(majorVersion) - 1900; 181 182 UINT64 minorVersion = version.Minor(); 183 UINT64 monthValue = minorVersion / 100; 184 UINT64 dayValue = minorVersion % 100; 185 if (monthValue < 1 || monthValue > 12 || dayValue < 1 || dayValue > 31) 186 { 187 return std::chrono::system_clock::time_point::min(); 188 } 189 versionTime.tm_mon = static_cast<int>(monthValue) - 1; 190 versionTime.tm_mday = static_cast<int>(dayValue); 191 192 UINT64 buildVersion = version.Build(); 193 UINT64 hourValue = buildVersion / 100; 194 UINT64 minuteValue = buildVersion % 100; 195 if (hourValue < 1 || hourValue > 24 || minuteValue > 59) 196 { 197 return std::chrono::system_clock::time_point::min(); 198 } 199 versionTime.tm_hour = static_cast<int>(hourValue) - 1; 200 versionTime.tm_min = static_cast<int>(minuteValue); 201 202 return std::chrono::system_clock::from_time_t(_mkgmtime64(&versionTime)); 203 } 204 }