From 0ee5dacf70fca3d8132223d7feacbb1b15a6009b Mon Sep 17 00:00:00 2001
From: "Patrik J. Braun" <bra.patrik@gmail.com>
Date: Mon, 25 Apr 2022 19:23:03 +0200
Subject: [PATCH] improving date parsing in SearchQueryParser.ts

---
 src/backend/model/threading/MetadataLoader.ts |  4 +--
 src/common/SearchQueryParser.ts               | 28 +++++++++++--------
 test/common/unit/SearchQueryParser.ts         | 14 +++++++++-
 3 files changed, 31 insertions(+), 15 deletions(-)

diff --git a/src/backend/model/threading/MetadataLoader.ts b/src/backend/model/threading/MetadataLoader.ts
index 34a03b4a..b111583e 100644
--- a/src/backend/model/threading/MetadataLoader.ts
+++ b/src/backend/model/threading/MetadataLoader.ts
@@ -264,7 +264,7 @@ export class MetadataLoader {
             //  and keep the minimum amount only
             const exif = ExifReader.load(data);
             if (exif.Rating) {
-              metadata.rating = parseInt(exif.Rating.value, 10) as any;
+              metadata.rating = parseInt(exif.Rating.value, 10) as 0 | 1 | 2 | 3 | 4 | 5;
               if (metadata.rating < 0) {
                 metadata.rating = 0;
               }
@@ -287,7 +287,7 @@ export class MetadataLoader {
               const orientation = parseInt(
                 exif.Orientation.value as any,
                 10
-              ) as any;
+              ) as number;
               if (OrientationTypes.BOTTOM_LEFT < orientation) {
                 // noinspection JSSuspiciousNameCombination
                 const height = metadata.size.width;
diff --git a/src/common/SearchQueryParser.ts b/src/common/SearchQueryParser.ts
index 24b080c5..0e8183c2 100644
--- a/src/common/SearchQueryParser.ts
+++ b/src/common/SearchQueryParser.ts
@@ -19,7 +19,7 @@ import {
   TextSearchQueryTypes,
   ToDateSearch,
 } from './entities/SearchQueryDTO';
-import { Utils } from './Utils';
+import {Utils} from './Utils';
 
 export interface QueryKeywords {
   portrait: string;
@@ -72,7 +72,8 @@ export const defaultQueryKeywords: QueryKeywords = {
 };
 
 export class SearchQueryParser {
-  constructor(private keywords: QueryKeywords = defaultQueryKeywords) {}
+  constructor(private keywords: QueryKeywords = defaultQueryKeywords) {
+  }
 
   public static stringifyText(
     text: string,
@@ -115,8 +116,11 @@ export class SearchQueryParser {
     // Parsing ISO string
     try {
       const parts = text.split('-').map((t) => parseInt(t, 10));
+      if (parts && parts.length === 2) {
+        timestamp = Date.UTC(parts[0], parts[1] - 1, 1, 0, 0, 0, 0); // Note: months are 0-based
+      }
       if (parts && parts.length === 3) {
-        timestamp = Date.UTC(parts[0], parts[1] - 1, parts[2]); // Note: months are 0-based
+        timestamp = Date.UTC(parts[0], parts[1] - 1, parts[2], 0, 0, 0, 0); // Note: months are 0-based
       }
     } catch (e) {
       // ignoring errors
@@ -230,7 +234,7 @@ export class SearchQueryParser {
       const unfoldList = (q: SearchListQuery): SearchQueryDTO[] => {
         if (q.list) {
           if (q.type === SearchQueryTypes.UNKNOWN_RELATION) {
-            return q.list.map((e) => unfoldList(e as SearchListQuery)).flat()  // flatten array
+            return q.list.map((e) => unfoldList(e as SearchListQuery)).flat();  // flatten array
           } else {
             q.list.forEach((e) => unfoldList(e as SearchListQuery));
           }
@@ -256,14 +260,14 @@ export class SearchQueryParser {
       return {
         type: SearchQueryTypes.from_date,
         value: SearchQueryParser.parseDate(str.substring(str.indexOf(':') + 1)),
-        ...(str.startsWith(this.keywords.from + '!:') && { negate: true }), // only add if the value is true
+        ...(str.startsWith(this.keywords.from + '!:') && {negate: true}), // only add if the value is true
       } as FromDateSearch;
     }
     if (kwStartsWith(str, this.keywords.to)) {
       return {
         type: SearchQueryTypes.to_date,
         value: SearchQueryParser.parseDate(str.substring(str.indexOf(':') + 1)),
-        ...(str.startsWith(this.keywords.to + '!:') && { negate: true }), // only add if the value is true
+        ...(str.startsWith(this.keywords.to + '!:') && {negate: true}), // only add if the value is true
       } as ToDateSearch;
     }
 
@@ -271,14 +275,14 @@ export class SearchQueryParser {
       return {
         type: SearchQueryTypes.min_rating,
         value: parseInt(str.substring(str.indexOf(':') + 1), 10),
-        ...(str.startsWith(this.keywords.minRating + '!:') && { negate: true }), // only add if the value is true
+        ...(str.startsWith(this.keywords.minRating + '!:') && {negate: true}), // only add if the value is true
       } as MinRatingSearch;
     }
     if (kwStartsWith(str, this.keywords.maxRating)) {
       return {
         type: SearchQueryTypes.max_rating,
         value: parseInt(str.substring(str.indexOf(':') + 1), 10),
-        ...(str.startsWith(this.keywords.maxRating + '!:') && { negate: true }), // only add if the value is true
+        ...(str.startsWith(this.keywords.maxRating + '!:') && {negate: true}), // only add if the value is true
       } as MaxRatingSearch;
     }
     if (kwStartsWith(str, this.keywords.minResolution)) {
@@ -312,7 +316,7 @@ export class SearchQueryParser {
       return {
         type: SearchQueryTypes.distance,
         distance: parseInt(new RegExp(/^\d*/).exec(str)[0], 10),
-        from: { text: from },
+        from: {text: from},
         ...(new RegExp('^\\d*-' + this.keywords.kmFrom + '!:').test(str) && {
           negate: true,
         }), // only add if the value is true
@@ -331,11 +335,11 @@ export class SearchQueryParser {
     // parse text search
     const tmp = TextSearchQueryTypes.map((type) => ({
       key: (this.keywords as any)[SearchQueryTypes[type]] + ':',
-      queryTemplate: { type, text: '' } as TextSearch,
+      queryTemplate: {type, text: ''} as TextSearch,
     })).concat(
       TextSearchQueryTypes.map((type) => ({
         key: (this.keywords as any)[SearchQueryTypes[type]] + '!:',
-        queryTemplate: { type, text: '', negate: true } as TextSearch,
+        queryTemplate: {type, text: '', negate: true} as TextSearch,
       }))
     );
     for (const typeTmp of tmp) {
@@ -361,7 +365,7 @@ export class SearchQueryParser {
       }
     }
 
-    return { type: SearchQueryTypes.any_text, text: str } as TextSearch;
+    return {type: SearchQueryTypes.any_text, text: str} as TextSearch;
   }
 
   public stringify(query: SearchQueryDTO): string {
diff --git a/test/common/unit/SearchQueryParser.ts b/test/common/unit/SearchQueryParser.ts
index cf7f4e0b..a15b1edd 100644
--- a/test/common/unit/SearchQueryParser.ts
+++ b/test/common/unit/SearchQueryParser.ts
@@ -68,8 +68,20 @@ describe('SearchQueryParser', () => {
       check({type: SearchQueryTypes.to_date, value: (Date.UTC(2020, 1, 1)), negate: true} as ToDateSearch);
 
       const parser = new SearchQueryParser(defaultQueryKeywords);
+
+      let query: RangeSearch = ({type: SearchQueryTypes.from_date, value: (Date.UTC(2020, 1, 4))} as FromDateSearch);
+      expect(parser.parse(defaultQueryKeywords.from + ':' + '2020-02-04'))
+        .to.deep.equals(query, parser.stringify(query));
+
+      expect(parser.parse(defaultQueryKeywords.from + ':' + '2020-2-4'))
+        .to.deep.equals(query, parser.stringify(query));
+
+      query = ({type: SearchQueryTypes.from_date, value: (Date.UTC(2020, 1, 1))} as FromDateSearch);
+      expect(parser.parse(defaultQueryKeywords.from + ':' + (new Date(query.value)).getFullYear() + '-' + '02'))
+        .to.deep.equals(query, parser.stringify(query));
+
       // test if date gets simplified on 1st of Jan.
-      let query: RangeSearch = {type: SearchQueryTypes.to_date, value: (Date.UTC(2020, 0, 1))} as ToDateSearch;
+      query = {type: SearchQueryTypes.to_date, value: (Date.UTC(2020, 0, 1))} as ToDateSearch;
       expect(parser.parse(defaultQueryKeywords.to + ':' + (new Date(query.value)).getFullYear()))
         .to.deep.equals(query, parser.stringify(query));