Skip to content

Commit

Permalink
Merge pull request #5802 from danilo-delbusso/bug/rfc-3339
Browse files Browse the repository at this point in the history
CA-397409: Extend SDK deserialization support for xen-api dates
  • Loading branch information
kc284 authored Sep 12, 2024
2 parents 08e1437 + dcfc0e4 commit eda6239
Show file tree
Hide file tree
Showing 5 changed files with 227 additions and 24 deletions.
21 changes: 20 additions & 1 deletion ocaml/sdk-gen/c/autogen/src/xen_common.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
56 changes: 47 additions & 9 deletions ocaml/sdk-gen/csharp/autogen/src/Converters.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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;

Expand All @@ -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;
}
Expand Down
29 changes: 28 additions & 1 deletion ocaml/sdk-gen/go/templates/ConvertTime.mustache
Original file line number Diff line number Diff line change
@@ -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) {
Expand Down
29 changes: 28 additions & 1 deletion ocaml/sdk-gen/go/test_data/time_convert.go
Original file line number Diff line number Diff line change
@@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Date> {

/**
* 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"),
};

/**
Expand All @@ -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
}
Expand Down

0 comments on commit eda6239

Please sign in to comment.