From 7d6967a8ba1c48056f63f63d89b1ffb2acb33fba Mon Sep 17 00:00:00 2001 From: Danilo Del Busso Date: Wed, 10 Jul 2024 10:42:16 +0100 Subject: [PATCH 1/4] Extend Java deserialization support for xen-api dates non-Zulu dates were not parsed correctly Signed-off-by: Danilo Del Busso --- .../xenapi/CustomDateDeserializer.java | 116 ++++++++++++++++-- 1 file changed, 104 insertions(+), 12 deletions(-) diff --git a/ocaml/sdk-gen/java/autogen/xen-api/src/main/java/com/xensource/xenapi/CustomDateDeserializer.java b/ocaml/sdk-gen/java/autogen/xen-api/src/main/java/com/xensource/xenapi/CustomDateDeserializer.java index a0e9bff1a3d..e397ba7e27f 100644 --- a/ocaml/sdk-gen/java/autogen/xen-api/src/main/java/com/xensource/xenapi/CustomDateDeserializer.java +++ b/ocaml/sdk-gen/java/autogen/xen-api/src/main/java/com/xensource/xenapi/CustomDateDeserializer.java @@ -37,21 +37,97 @@ import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; +import java.util.TimeZone; /** - * {@link CustomDateDeserializer} is a Jackson JSON deserializer for parsing {@link Date} objects + * {@link CustomDateDeserializer} is a Jackson JSON deserializer for parsing + * {@link Date} objects * from custom date formats used in Xen-API responses. */ public class CustomDateDeserializer extends StdDeserializer { /** - * Array of {@link SimpleDateFormat} objects representing the custom date formats - * used in XenServer API responses. + * Array of {@link SimpleDateFormat} objects representing the date formats + * used in xen-api responses. + * + * RFC-3339 date formats can be returned in either Zulu or time zone agnostic. + * This list is not an exhaustive list of formats supported by RFC-3339, rather + * a set of formats that will enable the deserialization of xen-api dates. + * Formats are listed in order of decreasing precision. When adding + * to this list, please ensure the order is kept. */ - private final SimpleDateFormat[] dateFormatters - = new SimpleDateFormat[]{ + private static final SimpleDateFormat[] dateFormatsUtc = { + // Most commonly returned formats + new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'"), + new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"), + new SimpleDateFormat("ss.SSS"), + + // Other + new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"), new SimpleDateFormat("yyyyMMdd'T'HH:mm:ss'Z'"), - new SimpleDateFormat("ss.SSS") + new SimpleDateFormat("yyyyMMdd'T'HH:mm:ss.SSS'Z'"), + new SimpleDateFormat("yyyyMMdd'T'HHmmss.SSS'Z'"), + + }; + + /** + * Array of {@link SimpleDateFormat} objects representing the date formats for + * local time. + * These formats are used to parse dates in local time zones. + * Formats are listed in order of decreasing precision. When adding + * to this list, please ensure the order is kept. + */ + private static final SimpleDateFormat[] dateFormatsLocal = { + // no dashes, no colons + new SimpleDateFormat("yyyyMMdd'T'HHmmss.SSSZZZ"), + new SimpleDateFormat("yyyyMMdd'T'HHmmss.SSSZZ"), + new SimpleDateFormat("yyyyMMdd'T'HHmmss.SSSZ"), + new SimpleDateFormat("yyyyMMdd'T'HHmmss.SSSXXX"), + new SimpleDateFormat("yyyyMMdd'T'HHmmss.SSSXX"), + new SimpleDateFormat("yyyyMMdd'T'HHmmss.SSSX"), + new SimpleDateFormat("yyyyMMdd'T'HHmmss.SSS"), + + new SimpleDateFormat("yyyyMMdd'T'HHmmssZZZ"), + new SimpleDateFormat("yyyyMMdd'T'HHmmssZZ"), + new SimpleDateFormat("yyyyMMdd'T'HHmmssZ"), + new SimpleDateFormat("yyyyMMdd'T'HHmmssXXX"), + new SimpleDateFormat("yyyyMMdd'T'HHmmssXX"), + new SimpleDateFormat("yyyyMMdd'T'HHmmssX"), + new SimpleDateFormat("yyyyMMdd'T'HHmmss"), + + // no dashes, with colons + new SimpleDateFormat("yyyyMMdd'T'HH:mm:ss.SSSZZZ"), + new SimpleDateFormat("yyyyMMdd'T'HH:mm:ss.SSSZZ"), + new SimpleDateFormat("yyyyMMdd'T'HH:mm:ss.SSSZ"), + new SimpleDateFormat("yyyyMMdd'T'HH:mm:ss.SSSXXX"), + new SimpleDateFormat("yyyyMMdd'T'HH:mm:ss.SSSXX"), + new SimpleDateFormat("yyyyMMdd'T'HH:mm:ss.SSSX"), + new SimpleDateFormat("yyyyMMdd'T'HH:mm:ss.SSS"), + + new SimpleDateFormat("yyyyMMdd'T'HH:mm:ssZZZ"), + new SimpleDateFormat("yyyyMMdd'T'HH:mm:ssZZ"), + new SimpleDateFormat("yyyyMMdd'T'HH:mm:ssZ"), + new SimpleDateFormat("yyyyMMdd'T'HH:mm:ssXXX"), + new SimpleDateFormat("yyyyMMdd'T'HH:mm:ssXX"), + new SimpleDateFormat("yyyyMMdd'T'HH:mm:ssX"), + new SimpleDateFormat("yyyyMMdd'T'HH:mm:ss"), + + // dashes and colons + new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZZZ"), + new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZZ"), + new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ"), + new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX"), + new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXX"), + new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSX"), + new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS"), + + new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZZZ"), + new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZZ"), + new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ"), + new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX"), + new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXX"), + new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX"), + new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"), }; /** @@ -62,28 +138,44 @@ public CustomDateDeserializer() { } /** - * Constructs a {@link CustomDateDeserializer} instance with the specified value type. + * Constructs a {@link CustomDateDeserializer} instance with the specified value + * type. * * @param t The value type to handle (can be null, handled by superclass) */ public CustomDateDeserializer(Class t) { super(t); + var utcTimeZone = TimeZone.getTimeZone("UTC"); + for (var utcFormatter : dateFormatsUtc) { + utcFormatter.setTimeZone(utcTimeZone); + } } + private static + /** * Deserializes a {@link Date} object from the given JSON parser. * - * @param jsonParser The JSON parser containing the date value to deserialize + * @param jsonParser The JSON parser containing the date value to + * deserialize * @param deserializationContext The deserialization context * @return The deserialized {@link Date} object * @throws IOException if an I/O error occurs during deserialization */ - @Override - public Date deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException { + @Override public Date deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) + throws IOException { + var text = jsonParser.getText(); + for (SimpleDateFormat formatter : dateFormatsUtc) { + try { + return formatter.parse(text); + } catch (ParseException e) { + // ignore + } + } - for (SimpleDateFormat formatter : dateFormatters) { + for (SimpleDateFormat formatter : dateFormatsLocal) { try { - return formatter.parse(jsonParser.getText()); + return formatter.parse(text); } catch (ParseException e) { // ignore } From cf27ff9f0915f2363c0bdc39f34cc5b211d3f8f6 Mon Sep 17 00:00:00 2001 From: Danilo Del Busso Date: Wed, 17 Jul 2024 10:56:57 +0100 Subject: [PATCH 2/4] Expand Go deserialization support for xen-api dates Signed-off-by: Danilo Del Busso --- .../sdk-gen/go/templates/ConvertTime.mustache | 29 ++++++++++++++++++- ocaml/sdk-gen/go/test_data/time_convert.go | 29 ++++++++++++++++++- 2 files changed, 56 insertions(+), 2 deletions(-) diff --git a/ocaml/sdk-gen/go/templates/ConvertTime.mustache b/ocaml/sdk-gen/go/templates/ConvertTime.mustache index d1f18643057..d6f0e2a63d5 100644 --- a/ocaml/sdk-gen/go/templates/ConvertTime.mustache +++ b/ocaml/sdk-gen/go/templates/ConvertTime.mustache @@ -1,5 +1,32 @@ {{#serialize}} -var timeFormats = []string{time.RFC3339, "20060102T15:04:05Z", "20060102T15:04:05"} +var timeFormats = []string{ + time.RFC3339, + "2006-01-02T15:04:05", + + // no dashes, no colons + "20060102T15:04:05Z", + "20060102T15:04:05", + "20060102T150405.999999999Z0700", + "20060102T150405", + "20060102T150405Z07", + "20060102T150405Z07:00", + + // no dashes, with colons + "20060102T15:04:05Z07", + "20060102T15:04:05Z0700", + "20060102T15:04:05Z07:00", + "20060102T15:04:05.999999999Z07", + "20060102T15:04:05.999999999Z07:00", + "20060102T15:04:05.999999999Z07", + + // dashes and colon patterns not covered by `time.RFC3339` + "2006-01-02T15:04:05Z07", + "2006-01-02T15:04:05Z0700", + "2006-01-02T15:04:05Z07:00", + "2006-01-02T15:04:05.999999999Z07", + "2006-01-02T15:04:05.999999999Z07:00", + "2006-01-02T15:04:05.999999999Z07", +} //nolint:unparam func serialize{{func_name_suffix}}(context string, value {{type}}) (string, error) { diff --git a/ocaml/sdk-gen/go/test_data/time_convert.go b/ocaml/sdk-gen/go/test_data/time_convert.go index 7bbdf602ced..d9d5483d5b3 100644 --- a/ocaml/sdk-gen/go/test_data/time_convert.go +++ b/ocaml/sdk-gen/go/test_data/time_convert.go @@ -1,4 +1,31 @@ -var timeFormats = []string{time.RFC3339, "20060102T15:04:05Z", "20060102T15:04:05"} +var timeFormats = []string{ + time.RFC3339, + "2006-01-02T15:04:05", + + // no dashes, no colons + "20060102T15:04:05Z", + "20060102T15:04:05", + "20060102T150405.999999999Z0700", + "20060102T150405", + "20060102T150405Z07", + "20060102T150405Z07:00", + + // no dashes, with colons + "20060102T15:04:05Z07", + "20060102T15:04:05Z0700", + "20060102T15:04:05Z07:00", + "20060102T15:04:05.999999999Z07", + "20060102T15:04:05.999999999Z07:00", + "20060102T15:04:05.999999999Z07", + + // dashes and colon patterns not covered by `time.RFC3339` + "2006-01-02T15:04:05Z07", + "2006-01-02T15:04:05Z0700", + "2006-01-02T15:04:05Z07:00", + "2006-01-02T15:04:05.999999999Z07", + "2006-01-02T15:04:05.999999999Z07:00", + "2006-01-02T15:04:05.999999999Z07", +} //nolint:unparam func serializeTime(context string, value time.Time) (string, error) { From 8f4c11278be697f52381a25f55880391cd7718a6 Mon Sep 17 00:00:00 2001 From: Danilo Del Busso Date: Wed, 17 Jul 2024 10:57:02 +0100 Subject: [PATCH 3/4] Expand C# deserialization support for xen-api dates Signed-off-by: Danilo Del Busso --- .../sdk-gen/csharp/autogen/src/Converters.cs | 56 ++++++++++++++++--- 1 file changed, 47 insertions(+), 9 deletions(-) diff --git a/ocaml/sdk-gen/csharp/autogen/src/Converters.cs b/ocaml/sdk-gen/csharp/autogen/src/Converters.cs index 2c4e4ba0df7..32b02d987a6 100644 --- a/ocaml/sdk-gen/csharp/autogen/src/Converters.cs +++ b/ocaml/sdk-gen/csharp/autogen/src/Converters.cs @@ -385,16 +385,54 @@ public override object ReadJson(JsonReader reader, Type objectType, object exist internal class XenDateTimeConverter : IsoDateTimeConverter { - private static readonly string[] DateFormatsUniversal = - { - "yyyyMMddTHH:mm:ssZ", "yyyy-MM-ddThh:mm:ssZ" + string [] DateFormatsUtc = { + // dashes and colons + "yyyy-MM-ddTHH:mm:ssZ", + "yyyy-MM-ddTHH:mm:ss.fffZ", + + // no dashes, with colons + "yyyyMMddTHH:mm:ssZ", + "yyyyMMddTHH:mm:ss.fffZ", + + // no dashes + "yyyyMMddTHHmmssZ", + "yyyyMMddTHHmmss.fffZ", }; - private static readonly string[] DateFormatsOther = + string[] DateFormatsLocal = { - "yyyyMMddTHH:mm:ss", + // no dashes + "yyyyMMddTHHmmss.fffzzzz", + "yyyyMMddTHHmmss.fffzzz", + "yyyyMMddTHHmmss.fffzz", + "yyyyMMddTHHmmss.fff", + + "yyyyMMddTHHmmsszzzz", "yyyyMMddTHHmmsszzz", - "yyyyMMddTHHmmsszz" + "yyyyMMddTHHmmsszz", + "yyyyMMddTHHmmss", + + // no dashes, with colons + "yyyyMMddTHH:mm:ss.fffzzzz", + "yyyyMMddTHH:mm:ss.fffzzz", + "yyyyMMddTHH:mm:ss.fffzz", + "yyyyMMddTHH:mm:ss.fff", + + "yyyyMMddTHH:mm:sszzzz", + "yyyyMMddTHH:mm:sszzz", + "yyyyMMddTHH:mm:sszz", + "yyyyMMddTHH:mm:ss", + + // dashes and colons + "yyyy-MM-ddTHH:mm:ss.fffzzzz", + "yyyy-MM-ddTHH:mm:ss.fffzzz", + "yyyy-MM-ddTHH:mm:ss.fffzz", + "yyyy-MM-ddTHH:mm:ss.fff", + + "yyyy-MM-ddTHH:mm:sszzzz", + "yyyy-MM-ddTHH:mm:sszzz", + "yyyy-MM-ddTHH:mm:sszz", + "yyyy-MM-ddTHH:mm:ss", }; public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) @@ -403,11 +441,11 @@ public override object ReadJson(JsonReader reader, Type objectType, object exist DateTime result; - if (DateTime.TryParseExact(str, DateFormatsUniversal, CultureInfo.InvariantCulture, + if (DateTime.TryParseExact(str, DateFormatsUtc, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal, out result)) return result; - if (DateTime.TryParseExact(str, DateFormatsOther, CultureInfo.InvariantCulture, + if (DateTime.TryParseExact(str, DateFormatsLocal, CultureInfo.InvariantCulture, DateTimeStyles.None, out result)) return result; @@ -420,7 +458,7 @@ public override void WriteJson(JsonWriter writer, object value, JsonSerializer s { var dateTime = (DateTime)value; dateTime = dateTime.ToUniversalTime(); - var text = dateTime.ToString(DateFormatsUniversal[0], CultureInfo.InvariantCulture); + var text = dateTime.ToString(DateFormatsUtc[0], CultureInfo.InvariantCulture); writer.WriteValue(text); return; } From dcfc0e47a877495e819b0dbc41b7e96a70a7fc83 Mon Sep 17 00:00:00 2001 From: Danilo Del Busso Date: Wed, 17 Jul 2024 10:57:13 +0100 Subject: [PATCH 4/4] Expand C deserialization support for xen-api dates Signed-off-by: Danilo Del Busso --- ocaml/sdk-gen/c/autogen/src/xen_common.c | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/ocaml/sdk-gen/c/autogen/src/xen_common.c b/ocaml/sdk-gen/c/autogen/src/xen_common.c index 9178d3fd43f..0cb089d35d9 100644 --- a/ocaml/sdk-gen/c/autogen/src/xen_common.c +++ b/ocaml/sdk-gen/c/autogen/src/xen_common.c @@ -950,7 +950,26 @@ static void parse_into(xen_session *s, xmlNode *value_node, { struct tm tm; memset(&tm, 0, sizeof(tm)); - strptime((char *)string, "%Y%m%dT%H:%M:%S", &tm); + // We only support basic ISO8601 since the C SDK only + // connects to the XML-RPC backend + char *formats[] = { + // no dashes, no colons + "%Y%m%dT%H%M%S", + // no dashes, with colons + "%Y%m%dT%H:%M:%S", + // dashes and colons + "%Y-%m-%dT%H:%M:%S", + }; + int num_formats = sizeof(formats) / sizeof(formats[0]); + + for (int i = 0; i < num_formats; i++) + { + if (strptime((char *)string, formats[i], &tm) != NULL) + { + break; + } + } + ((time_t *)value)[slot] = (time_t)mktime(&tm); free(string); }