commit a9a63a35e4607571648f33d1d8800ad00117df64
parent 2fb627db6710e29f45d09cb327f0aa79176f049b
Author: Luis Chacón <lechacon@users.noreply.github.com>
Date: Fri, 2 Apr 2021 12:41:31 -0700
Fix format of CreationDate on export (#843)
Diffstat:
3 files changed, 66 insertions(+), 6 deletions(-)
diff --git a/src/AppInstallerCLICore/PackageCollection.cpp b/src/AppInstallerCLICore/PackageCollection.cpp
@@ -108,9 +108,8 @@ namespace AppInstaller::CLI
root[ss.PackagesJson_WinGetVersion] = wingetVersion;
root[ss.PackagesJson_Schema] = ss.PackagesJson_SchemaUri_v1_0;
- // TODO: This uses localtime. Do we want to use UTC or add time zone?
std::stringstream currentTimeStream;
- Utility::OutputTimePoint(currentTimeStream, std::chrono::system_clock::now());
+ Utility::OutputTimePoint(currentTimeStream, std::chrono::system_clock::now(), true);
root[ss.PackagesJson_CreationDate] = currentTimeStream.str();
return root;
diff --git a/src/AppInstallerCLITests/PackageCollection.cpp b/src/AppInstallerCLITests/PackageCollection.cpp
@@ -3,6 +3,7 @@
#include "pch.h"
#include "TestCommon.h"
+#include <winget/Regex.h>
#include <PackageCollection.h>
// Duplicating here because a change to these values in the product *REALLY* needs to be thought through.
@@ -52,6 +53,19 @@ namespace
REQUIRE(node[propertyName].asString() == expectedValue);
}
+ void ValidateJsonStringPropertyRegex(const Json::Value& node, const std::string& propertyName, std::string_view regex, bool allowMissing = false)
+ {
+ if (allowMissing && !node.isMember(propertyName))
+ {
+ return;
+ }
+
+ REQUIRE(node.isMember(propertyName));
+ REQUIRE(node[propertyName].isString());
+ const auto& value = AppInstaller::Utility::ConvertToUTF16(node[propertyName].asString());
+ REQUIRE(AppInstaller::Regex::Expression(regex).IsMatch(value));
+ }
+
const Json::Value& GetAndValidateJsonProperty(const Json::Value& node, const std::string& propertyName, Json::ValueType valueType)
{
REQUIRE(node.isMember(propertyName));
@@ -63,7 +77,13 @@ namespace
{
ValidateJsonStringProperty(root, s_PackagesJson_Schema, s_PackagesJson_SchemaUri_v1_0);
ValidateJsonStringProperty(root, s_PackagesJson_WinGetVersion, collection.ClientVersion);
- REQUIRE(root.isMember(s_PackagesJson_CreationDate));
+
+ // valijson does not validate the date-time format, which should follow RFC3339 according to the JSON schema.
+ // Ensure we write something at least reasonable.
+ // The expected format is <Date>T<Time><TimeZone>
+ // with Date="YYYY-MM-DD"; Time="HH:mm:ss.xxx"; TimeZone="Z" or "+HH:mm" or "-HH:mm" (offset from UTC)
+ std::string_view dateTimeRegex = "[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}.[0-9]+(Z|[+-][0-9]{2}:[0-9]{2})"sv;
+ ValidateJsonStringPropertyRegex(root, s_PackagesJson_CreationDate, dateTimeRegex);
const auto& jsonSources = GetAndValidateJsonProperty(root, s_PackagesJson_Sources, Json::ValueType::arrayValue);
REQUIRE(jsonSources.size() == collection.Sources.size());
@@ -449,8 +469,41 @@ TEST_CASE("PackageCollection_Read_SchemaValidationFail", "[PackageCollection]")
})");
auto parseResult = PackagesJson::TryParseJson(json);
- INFO(parseResult.Errors);
+ INFO(parseResult.Errors);
REQUIRE(parseResult.Result == PackagesJson::ParseResult::Type::SchemaValidationFailed);
REQUIRE(parseResult.Errors.find("Missing required property 'Sources'.") != std::string::npos);
+}
+
+TEST_CASE("PackageCollection_Read_BadTimeStamp", "[PackageCollection]")
+{
+ // We used to export without padding the creation date with 0s nor adding time zone.
+ // Ensure we don't break with that format.
+ auto json = ParseJsonString(R"(
+ {
+ "$schema": "https://aka.ms/winget-packages.schema.1.0.json",
+ "CreationDate": "2021- 1- 1 12:00:00.000",
+ "Sources": [
+ {
+ "Packages": [
+ {
+ "Id": "test"
+ }
+ ],
+ "SourceDetails": {
+ "Argument": "https://aka.ms/winget",
+ "Identifier": "TestSourceId",
+ "Name": "TestSource",
+ "Type": "Microsoft.PreIndexed.Package"
+ }
+ }
+ ],
+ "WinGetVersion": "1.0.0"
+ })");
+
+ auto parseResult = PackagesJson::TryParseJson(json);
+ INFO(parseResult.Errors);
+
+ REQUIRE(parseResult.Result == PackagesJson::ParseResult::Type::Success);
+ REQUIRE(parseResult.Errors.empty());
}
\ No newline at end of file
diff --git a/src/AppInstallerCommonCore/DateTime.cpp b/src/AppInstallerCommonCore/DateTime.cpp
@@ -18,8 +18,8 @@ namespace AppInstaller::Utility
<< std::setw(4) << (1900 + localTime.tm_year) << '-'
<< std::setw(2) << std::setfill('0') << (1 + localTime.tm_mon) << '-'
<< std::setw(2) << std::setfill('0') << localTime.tm_mday << (useRFC3339 ? 'T' : ' ')
- << std::setw(2) << std::setfill('0') << localTime.tm_hour << ':'
- << std::setw(2) << std::setfill('0') << localTime.tm_min << ':'
+ << std::setw(2) << std::setfill('0') << localTime.tm_hour << ':'
+ << std::setw(2) << std::setfill('0') << localTime.tm_min << ':'
<< std::setw(2) << std::setfill('0') << localTime.tm_sec << '.';
// Get partial seconds
@@ -27,6 +27,14 @@ namespace AppInstaller::Utility
auto leftoverMillis = duration_cast<milliseconds>(sinceEpoch) - duration_cast<seconds>(sinceEpoch);
stream << std::setw(3) << std::setfill('0') << leftoverMillis.count();
+
+ if (useRFC3339)
+ {
+ // RFC 3339 requires adding time zone info.
+ // No need to bother getting the actual time zone as we don't need it.
+ // -00:00 represents an unspecified time zone, not UTC.
+ stream << "-00:00";
+ }
}
std::string GetCurrentTimeForFilename()