bkqc
bkqc

Reputation: 1021

ElasticSearch.Net does not recognize the timestamp returned by ElasticSearch

I'm trying to get a set of data from the kibana_sample_data_flights index which contains timestamp. When running the query, ElasticSearch correctly recognize it and returns the results but it crashes with

invalid datetime format. value:2019-06-03T

on

Elasticsearch.Net.Utf8Json.Formatters.ISO8601DateTimeFormatter.Deserialize(JsonReader& reader, IJsonFormatterResolver formatterResolver)

The first time, I thought it was my object that was wrongly indexed and simply excluded the result but I got the same error on another entry. Searching with Kibana, it works and the timestamp is Jun 2, 2019 @ 20:00:00.000 which makes sense since I'm in UTC-5 zone.

How can I have NEST / ElasticSearch.net recognize the date correctly?

--Edit 1: Elastic version and sample code as requested.

I'm using NEST and ElasticSearch.Net v7.0.0. ElasticSearch version request returns

{
  "name" : "M79539",
  "cluster_name" : "elasticsearch",
  "cluster_uuid" : "CxG9dTeSTsudlhJBGiVmJQ",
  "version" : {
    "number" : "7.1.0",
    "build_flavor" : "default",
    "build_type" : "zip",
    "build_hash" : "606a173",
    "build_date" : "2019-05-16T00:43:15.323135Z",
    "build_snapshot" : false,
    "lucene_version" : "8.0.0",
    "minimum_wire_compatibility_version" : "6.8.0",
    "minimum_index_compatibility_version" : "6.0.0-beta1"
  },
  "tagline" : "You Know, for Search"
}

In case it could help, here is the program

class Program
    {
        static void Main(string[] args)
        {
            //Kibana: http://m79539:5601/app/kibana#/home?_g=()
            // NEST
            var settings = new ConnectionSettings(new Uri("http://m79539:9200/"))
                .DefaultIndex("kibana_sample_data_flights")
                .DefaultFieldNameInferrer(p => p);

            var client = new ElasticClient(settings);

            var scrollTimeout = "5m";
            var scrollSize = 1000;
            var initialResponse = client.Search<FlightDetail>(s => s
                        .From(0).Take(scrollSize)
                        .Query(q => +q
                             .DateRange(r => r
                                    .Field(f => f.Timestamp)
                                    .GreaterThan(DateMath.Anchored(new DateTime(2019,06,01)))
                                    .LessThan(DateMath.Now)) && +!q
                              .Term(p => p.FlightNum, "59H86EL")
                          )
                        .Scroll(scrollTimeout)
                        //scroll is optimized for this sort and we will sort in our own code
                        .Sort(ss => ss.Ascending(SortSpecialField.DocumentIndexOrder))
                      );

            var results = new List<FlightDetail>();

            if (!initialResponse.IsValid || string.IsNullOrEmpty(initialResponse.ScrollId))
                throw new Exception(initialResponse.ServerError.Error.Reason);

            if (initialResponse.Documents.Any())
                results.AddRange(initialResponse.Documents);

            string scrollid = initialResponse.ScrollId;
            bool isScrollSetHasData = true;
            while (isScrollSetHasData)
            {
                ISearchResponse<FlightDetail> loopingResponse = client.Scroll<FlightDetail>(scrollTimeout, scrollid);
                if (loopingResponse.IsValid)
                {
                    results.AddRange(loopingResponse.Documents);
                    scrollid = loopingResponse.ScrollId;
                }
                isScrollSetHasData = loopingResponse.Documents.Any();
            }

            client.ClearScroll(new ClearScrollRequest(scrollid));


            foreach (var result in results){
                Console.WriteLine($"{result.Timestamp} | {result.FlightNum} | {result.FlightDelayMin}");
            }
        }
    }

    public class FlightDetail
    {
        public string FlightNum { get; set; }
        public int FlightDelayMin { get; set; }
        public object Source { get; set; }

        [Nest.Text(Name = "timestamp")]
        public DateTime Timestamp { get; set; }
    }

The problematic result (on my installation) is 548

{
    "_scroll_id": "DXF1ZXJ5QW5kRmV0Y2gBAAAAAAANizsWaTdsY3ViS2hUR2FyRHZESmdKeVRWZw==",
    "took": 1,
    "timed_out": false,
    "terminated_early": true,
    "_shards": {
        "total": 1,
        "successful": 1,
        "skipped": 0,
        "failed": 0
    },
    "hits": {
        "total": {
            "value": 9227,
            "relation": "eq"
        },
        "max_score": null,
        "hits": [{
            "_index": "kibana_sample_data_flights",
            "_type": "_doc",
            "_id": "1Lr3DmsBoDCXrrhScXCk",
            "_score": null,
            "_source": {
                "FlightNum": "86C3GQY",
                "DestCountry": "MX",
                "OriginWeather": "Sunny",
                "OriginCityName": "Rome",
                "AvgTicketPrice": 395.9230418749486,
                "DistanceMiles": 6362.054220749258,
                "FlightDelay": false,
                "DestWeather": "Damaging Wind",
                "Dest": "Licenciado Benito Juarez International Airport",
                "FlightDelayType": "No Delay",
                "OriginCountry": "IT",
                "dayOfWeek": 0,
                "DistanceKilometers": 10238.733787837495,
                "timestamp": "2019-06-03T",
                "DestLocation": {
                    "lat": "19.4363",
                    "lon": "-99.072098"
                },
                "DestAirportID": "AICM",
                "Carrier": "JetBeats",
                "Cancelled": false,
                "FlightTimeMin": 602.2784581080879,
                "Origin": "Leonardo da Vinci - Fiumicino Airport",
                "OriginLocation": {
                    "lat": "41.8002778",
                    "lon": "12.2388889"
                },
                "DestRegion": "MX-DIF",
                "OriginAirportID": "FCO",
                "OriginRegion": "SE-BD",
                "DestCityName": "Mexico City",
                "FlightTimeHour": 10.037974301801464,
                "FlightDelayMin": 0
            },
            "sort": [4379]
        }]
    }
}

--- Edit 2 : Simpler code sample based on @Rob proposition. Ends with the same problem. I added the try/catch to be able to break on errors and then look at what went through in Fiddler.

        private static void SimpleSample()
        {
            var client = new ElasticClient(new Uri("http://m79539:9200"));

            for (var i = 0; ; i++)
            {
                try
                {
                    var results = client.Search<SampleData>(s => s
                        .Index("kibana_sample_data_flights")
                        .From(i * 10)
                        .Query(q => q.MatchAll()));

                    foreach (var document in results.Documents)
                    {
                        Console.WriteLine(document.Timestamp.ToString("f") + "|" + document.FlightNum);
                    }
                }
                catch (Elasticsearch.Net.UnexpectedElasticsearchClientException ex)
                {
                    Console.WriteLine(ex.Message);
                }
            }
        }

        private class SampleData
        {
            public DateTime Timestamp { get; set; }
            [Nest.Text(Name = "FlightNum")]
            public string FlightNum { get; set; }
        }

Upvotes: 1

Views: 2052

Answers (1)

Rob
Rob

Reputation: 9979

Here is the sample app which can retrieve timestamp data from kibana_sample_data_flights index.

class Program
{
    static void Main(string[] args)
    {
        var client = new ElasticClient(new Uri("http://localhost:9200"));

        var results = client.Search<SampleData>(s => s
            .Index("kibana_sample_data_flights").Query(q => q.MatchAll()));

        foreach (var document in results.Documents)
        {
            Console.WriteLine(document.Timestamp.ToString("f"));
        }
    }
}

internal class SampleData
{
    public DateTime Timestamp { get; set; }
}

produces the following output:

Monday, 17 June 2019 00:00
Monday, 17 June 2019 18:27
Monday, 17 June 2019 17:11
Monday, 17 June 2019 10:33
Monday, 17 June 2019 05:13
Monday, 17 June 2019 01:43
Monday, 17 June 2019 13:49
Monday, 17 June 2019 04:54
Monday, 17 June 2019 12:09
Monday, 17 June 2019 12:09

In the example, NEST was able to correctly map SampleData property Timestamp to index's field timestamp because of the default field name convention being used.

UPDATE

Indeed there was an issue with parsing incorrect date value for document with id 1Lr3DmsBoDCXrrhScXCk z and value "timestamp": "2019-06-03T",`.

We can handle the error with customizing serialization when defining ConnectionSettings object and keep going when we encounter an error:

var pool = new SingleNodeConnectionPool(new Uri("http://localhost:9200"));
var connectionSettings =
    new ConnectionSettings(pool,
        sourceSerializer: (builtin, settings) => new JsonNetSerializer(
            builtin, settings,
            () => new JsonSerializerSettings {Error = HandlerError}
        ));
var client = new ElasticClient(connectionSettings);

private static void HandlerError(object sender, ErrorEventArgs e)
{
    //handle error the way you like
    e.ErrorContext.Handled = true;
}

Now we get this output:

..
Wednesday, 19 June 2019 21:39

Thursday, 20 June 2019 04:47
Thursday, 20 June 2019 21:51
Thursday, 20 June 2019 02:27
..

You can read more about configuring serialization process in NEST here.

Hope that helps.

Upvotes: 0

Related Questions