Home > Software design >  How to filter a date-field with a swift vapor-fluent query
How to filter a date-field with a swift vapor-fluent query

Time:07-12

To avoid multiple inserts of the same person in a database, I wrote the following function:

func anzahlDoubletten(_ req: Request, nname: String, vname: String, gebTag: Date) 
   async throws -> Int {                                                                                 
    try await                                                                                            
        Teilnehmer.query(on: req.db)                                                                     
        .filter(\.$nname == nname)                                                                       
        .filter(\.$vname == vname)                                                                       
        .filter(\.$gebTag == gebTag)                                                                     
        .count()                                                                                         
}

The function always returns 0, even if there are multiple records with the same surname, prename and birthday in the database.

Here is the resulting sql-query:

[ DEBUG ] SELECT COUNT("teilnehmer"."id") AS "aggregate" FROM "teilnehmer" WHERE "teilnehmer"."nname" = $1 AND "teilnehmer"."vname" = $2 AND "teilnehmer"."geburtstag" = $3 ["neumann", "alfred e.", 1999-09-09 00:00:00  0000] [database-id: psql, request-id: 1AC70C41-EADE-43C2-A12A-99C19462EDE3] (FluentPostgresDriver/FluentPostgresDatabase.swift:29)
[ INFO ] anzahlDoubletten=0 [request-id: 1AC70C41-EADE-43C2-A12A-99C19462EDE3] (App/Controllers/TeilnehmerController.swift:49)

if I query directly I obtain:

lwm=# select nname, vname, geburtstag from teilnehmer;
  nname  |   vname   | geburtstag 
--------- ----------- ------------
 neumann | alfred e. | 1999-09-09
 neumann | alfred e. | 1999-09-09
 neumann | alfred e. | 1999-09-09
 neumann | alfred e. | 1999-09-09

so count() should return 4 not 0:

lwm=# select count(*) from teilnehmer where nname = 'neumann' and vname = 'alfred e.' and geburtstag = '1999-09-09';
 count 
-------
     4

My DateFormatter is defined like so:

let dateFormatter = ISO8601DateFormatter()                                                                                                                                                                 
        dateFormatter.formatOptions = [.withFullDate, .withDashSeparatorInDate]

And finally the attribute "birthday" in my model:

...
@Field(key: "geburtstag")
var gebTag: Date
...

I inserted the 4 alfreds in my database using the model and fluent, passing the birthday "1999-09-09" as a String and fluent inserted all records correctly.

But .filter(\.$gebTag == gebTag) seems to return constantly 'false'.

Is it at all possible to use .filter() with data types other than String?

And if so, what am I doing wrong?

Many thanks for your help

Michael

CodePudding user response:

The problem you've hit is that you're storing only dates whereas you're filtering on dates with times. Unfortunately there's no native way to store just a date. However there are a few options.

The easiest way is to change the date field to a String and then use your date formatter (make sure you remove the time part) to convert the query option to a String.

CodePudding user response:

I am guessing slightly here, but I suspect that your table was not created by a Migration? If it had been, your geburtstag field would include a time component as this is the default and you would have spotted the problem quickly.

In any event, the filter is actually filtering on the time component of gebTag as well as the date. This is why it is returning zero.

I suggest converting the geburtstag to a type that includes the time and ensuring that the time component is set to 0:00:00 when you store it. You can reset the time component to 'midnight' using something like this:

extension Date {
    var midnight: Date { return Calendar.current.date(bySettingHour: 0, minute: 0, second: 0, of: self)! }
}

Then change your filter to:

    .filter(\.$gebTag == gebTag.midnight)

Alternatively, just use the static method in Calendar:

    .filter(\.$gebTag == Calendar.startOfDay(for:gebTag))

I think this is the most straightforward way of doing it.

  • Related