解析以T24:00:00Z结尾的JSON日期

时间:2015-09-17 22:11:44

标签: c# json.net

我正在点击一个API,它使用24:00:00作为日期时间的时间部分返回给我ISO日期。

我正在使用JSON.NET,但是当我尝试解析这个日期时,它正在爆炸。 API的返回如下:

{
   "endTs":"2015-07-30T24:00:00Z"
}

我希望将其解析为"2015-07-31 12:00:00AM",但我现在得到以下错误:

Could not convert string to DateTime: 2015-07-30T24:00:00Z. Path 'endTs'...

2 个答案:

答案 0 :(得分:2)

<强>更新

这在Json.NET 8.0.1中得到修复。来自Release Notes

  

修复 - 修正了24小时午夜ISO日期

原始答案

由于DateTime.Parse()本身会在日期和时间上引发异常。在这种格式的时间,您可以创建自己的IsoDateTimeConverter子类来检查和处理这种情况:

public class FixMidnightDateTimeConverter : IsoDateTimeConverter
{
    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(DateTime?) || objectType == typeof(DateTime);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        Type type = (Nullable.GetUnderlyingType(objectType) ?? objectType);
        bool isNullable = (Nullable.GetUnderlyingType(objectType) != null);

        var token = JToken.Load(reader);
        if (token == null || token.Type == JTokenType.Null)
        {
            if (!isNullable)
                throw new JsonSerializationException(string.Format("Null value for type {0} at path {1}", objectType.Name, reader.Path));
            return null;
        }

        // Fix strings like "2015-07-30T24:00:00Z"
        if (token.Type == JTokenType.String)
        {
            const string midnight = "T24:00:00Z";
            var str = ((string)token).Trim();
            if (str.EndsWith(midnight))
            {
                var date = (DateTime?)(JValue)(str.Remove(str.Length - midnight.Length) + "T00:00:00Z");
                if (date != null)
                {
                    return date.Value.AddDays(1);
                }
            }
        }

        using (var subReader = token.CreateReader())
        {
            while (subReader.TokenType == JsonToken.None)
                subReader.Read();
            return base.ReadJson(subReader, objectType, existingValue, serializer); // Use base class to convert
        }
    }
}

在转换器级别执行此操作可避免在字符串字段内修改数据时可能出现的问题。

然后将其注册为global converterpass it to JsonConvert directly

答案 1 :(得分:1)

遗憾的是,这是.NET框架与ISO标准不匹配的情况之一。 ISO 8601允许time values with 24 in the hour表示一天结束,但DateTime对象不知道如何处理此问题。

这意味着在解析JSON数据之前,您将不得不进行一些字符串操作来修复它。

您有两个基本选项:

  1. 将字符串的时间部分设置为23:59:59,在值中引入1秒的错误,但保持&#34;结束日期&#34;价值的本质

  2. 将整个字符串调整到第二天早上的午夜。

  3. 假设格式保持一致,第一个很简单:

    jsonSrc = jsonSrc.Replace("T24:00:00Z", "T23:59:59Z");
    

    第二种选择稍微复杂一些。您需要找到任何有问题的日期,解析它们并使用有效值更新字符串。 24:00:00的时间戳将添加一天,时间归零。

    这里有一些代码可以通过您显示的特定格式实现:

    public static string FixJSONTimes(string source)
    {
        string result = source;
        // use Regex to locate bad time strings
        var re = new Regex("\"([\\d-]+)T24:00:00Z\"");
        foreach (Match match in re.Matches(result))
        {
            // parse out date portion of string
            // NB: we want this to throw if the date is invalid too
            DateTime dateVal = DateTime.Parse(match.Groups[1].Value);
    
            // rebuild string in correct format, adding a day
            string rep = string.Format("\"{0:yyyy-MM-dd}T00:00:00Z\"", dateVal.AddDays(1));
    
            // replace broken string with correct one
            result = result.Substring(0, match.Index) + rep + result.Substring(match.Index + match.Length);
        }
    
        return result;
    }
    

    因此,对于输入"endTs":"2015-07-30T24:00:00Z",这将返回"endTs":"2015-07-31T00:00:00Z"。还将正确处理月份和年份翻转,因为通过DateTime值计算日偏移量。

    当然,这是一个简单的案例,只适用于您指定的特定格式。 ISO 8601字符串具有复杂的格式,包括太多边缘情况,可以处理任何正常的正则表达式,包括小时,分钟或秒的小数部分。我知道使用小数小时的程序并不多,谢谢Ghu。

    所以只是为了好玩,我想我会尝试编写一个正则表达式来验证和分解任何有效的ISO 8601日期时间字符串。这是模式:

    @"(?<date>(?<yr>\d\d\d\d)(?:(-?)(?<mon>(?:0[1-9]|1[012]))(?:\1(?<day>0[1-9]|[12]\d|3[01]))?|(-?)W(?<wk>0[1-9]|[1-4]\d|5[0-3])(?:\2(?<wkd>[1-7]))?|-?(?<yrd>[0-3]\d\d))?)(?:T(?<time>(?<hh>[01]\d|2[0-4])(?:(:?)(?<mm>[0-5]\d)(?:\3(?<ss>[0-5]\d))?)?(?<fff>[,.]\d+)?)(?<timezone>Z|[+-](?:(?<tzh>[01]\d|2[0-4])(?<tzm>:?[0-5]\d)?))?)?"
    

    或者如果你喜欢突破的东西,缩进和评论一点清晰度:

    (?<date>
        (?<yr>\d\d\d\d)(?# Year is always included)
        (?:
            (?#Month with optional day of month and separator)
            (-?)(?<mon>(?:0[1-9]|1[012]))
            (?:\1(?<day>0[1-9]|[12]\d|3[01]))?
    
        |(?#or...)
    
            (?#Week of year with optional day of week and separator)
            (-?)W
            (?<wk>0[1-9]|[1-4]\d|5[0-3])
            (?:\2(?<wkd>[1-7]))?
    
        |(?#or...)
    
            (?#Day of year, separator mandatory)
            -?(?<yrd>[0-3]\d\d)
        )?
    )
    
    (?:T
        (?<time>
            (?#Required hour portion)
            (?<hh>[01]\d|2[0-4])
            (?:
                (?#Optional minutes and seconds, optional separator)
                (:?)(?<mm>[0-5]\d)
                (?:\3(?<ss>[0-5]\d))?
            )?
    
            (?#Optional fraction of prior term)
            (?<fff>[,.]\d+)?
        (?# end of 'time' mandatory group)
        )
    
        (?<timezone>
            (?#Zulu time, UTC+0)
            Z
    
        |(?#or...)
    
            (?#Numeric offset)
            [+-](?:
                (?#Hour portion of offset)
                (?<tzh>[01]\d|2[0-4])
                (?#Optional Minute portion of offset)
                (?<tzm>:?[0-5]\d)?
            )
        )?
    )?
    

    Now you have two problems.:P