Repository: owncloud/ocis
Pull request #7236 (2023-09-06 11:01:20)

[tests-only][full-ci]Add test for searching content across files with different format with search text highlight
<!--
Thanks for submitting a change to ownCloud!

This is the bug tracker for the oCIS component. Find other components at https://github.com/owncloud/core/blob/master/.github/CONTRIBUTING.md#guidelines

For fixing potential security issues please see https://owncloud.org/security/

To make it possible for us to get your change reviewed and merged please carefully fill out the requested information below.

Please note that any kind of change needs first be submitted to the master branch which holds the next version of oCIS.

Please set the following labels:

- Set label "Status:Needs-Review" for review or "Status:In-Progress" if the PR still has open tasks.
- Assignment: assign to self
- Reviewers: pick at least one
-->

## Description
This PR adds test for searching content across files with different formats

## Related Issue
- part of: https://github.com/owncloud/ocis/issues/6606

## How Has This Been Tested?
<!--- Please describe in detail how you tested your changes. -->
<!--- Include details of your testing environment, and the tests you ran to -->
<!--- see how your change affects other areas of the code, etc. -->
- locally
- CI

## Screenshots (if appropriate):

## Types of changes
<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to change)
- [ ] Technical debt
- [x] Tests only (no source changes)

## Checklist:
<!-- Tick the checkboxes when done. -->
<!-- Raise documentation ticket in owncloud.github.io/ -->
- [ ] Code changes
- [ ] Unit tests added
- [x] Acceptance tests added
- [ ] Documentation ticket raised: <link> 


sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!&nbsp; &nbsp; [![Quality Gate passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/passed-16px.png 'Quality Gate passed')](https://sonarcloud.io/dashboard?id=owncloud_ocis&pullRequest=7236)

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7236&resolved=false&types=BUG) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7236&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7236&resolved=false&types=BUG)  
[![Vulnerability](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability-16px.png 'Vulnerability')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7236&resolved=false&types=VULNERABILITY) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7236&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7236&resolved=false&types=VULNERABILITY)  
[![Security Hotspot](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot-16px.png 'Security Hotspot')](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=7236&resolved=false&types=SECURITY_HOTSPOT) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=7236&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=7236&resolved=false&types=SECURITY_HOTSPOT)  
[![Code Smell](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell-16px.png 'Code Smell')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7236&resolved=false&types=CODE_SMELL) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7236&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7236&resolved=false&types=CODE_SMELL)

[![No Coverage information](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/NoCoverageInfo-16px.png 'No Coverage information')](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=7236&metric=coverage&view=list) No Coverage information  
[![No Duplication information](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/NoDuplicationInfo-16px.png 'No Duplication information')](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=7236&metric=duplicated_lines_density&view=list) No Duplication information


ScharfViktor: @SwikritiT could you please check `highlights`

this feature: 

![Screenshot 2023-09-06 at 13 56 17](https://github.com/owncloud/ocis/assets/84779829/e4e7de6c-eaec-4f66-91f9-e1e4cc0f60d0)

just keep in mind that not all characters we can find. see bug: #6674 
SwikritiT: > @SwikritiT could you please check `highlights`
> 
> this feature:
> 
> ![Screenshot 2023-09-06 at 13 56 17](https://user-images.githubusercontent.com/84779829/265998999-e4e7de6c-eaec-4f66-91f9-e1e4cc0f60d0.png)
> 
> just keep in mind that not all characters we can find. see bug: #6674

sure!
sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!&nbsp; &nbsp; [![Quality Gate passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/passed-16px.png 'Quality Gate passed')](https://sonarcloud.io/dashboard?id=owncloud_ocis&pullRequest=7236)

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7236&resolved=false&types=BUG) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7236&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7236&resolved=false&types=BUG)  
[![Vulnerability](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability-16px.png 'Vulnerability')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7236&resolved=false&types=VULNERABILITY) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7236&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7236&resolved=false&types=VULNERABILITY)  
[![Security Hotspot](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot-16px.png 'Security Hotspot')](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=7236&resolved=false&types=SECURITY_HOTSPOT) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=7236&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=7236&resolved=false&types=SECURITY_HOTSPOT)  
[![Code Smell](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell-16px.png 'Code Smell')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7236&resolved=false&types=CODE_SMELL) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7236&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7236&resolved=false&types=CODE_SMELL)

[![No Coverage information](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/NoCoverageInfo-16px.png 'No Coverage information')](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=7236&metric=coverage&view=list) No Coverage information  
[![No Duplication information](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/NoDuplicationInfo-16px.png 'No Duplication information')](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=7236&metric=duplicated_lines_density&view=list) No Duplication information


sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!&nbsp; &nbsp; [![Quality Gate passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/passed-16px.png 'Quality Gate passed')](https://sonarcloud.io/dashboard?id=owncloud_ocis&pullRequest=7236)

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7236&resolved=false&types=BUG) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7236&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7236&resolved=false&types=BUG)  
[![Vulnerability](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability-16px.png 'Vulnerability')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7236&resolved=false&types=VULNERABILITY) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7236&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7236&resolved=false&types=VULNERABILITY)  
[![Security Hotspot](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot-16px.png 'Security Hotspot')](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=7236&resolved=false&types=SECURITY_HOTSPOT) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=7236&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=7236&resolved=false&types=SECURITY_HOTSPOT)  
[![Code Smell](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell-16px.png 'Code Smell')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7236&resolved=false&types=CODE_SMELL) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7236&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7236&resolved=false&types=CODE_SMELL)

[![No Coverage information](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/NoCoverageInfo-16px.png 'No Coverage information')](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=7236&metric=coverage&view=list) No Coverage information  
[![No Duplication information](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/NoDuplicationInfo-16px.png 'No Duplication information')](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=7236&metric=duplicated_lines_density&view=list) No Duplication information


sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!&nbsp; &nbsp; [![Quality Gate passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/passed-16px.png 'Quality Gate passed')](https://sonarcloud.io/dashboard?id=owncloud_ocis&pullRequest=7236)

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7236&resolved=false&types=BUG) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7236&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7236&resolved=false&types=BUG)  
[![Vulnerability](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability-16px.png 'Vulnerability')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7236&resolved=false&types=VULNERABILITY) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7236&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7236&resolved=false&types=VULNERABILITY)  
[![Security Hotspot](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot-16px.png 'Security Hotspot')](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=7236&resolved=false&types=SECURITY_HOTSPOT) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=7236&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=7236&resolved=false&types=SECURITY_HOTSPOT)  
[![Code Smell](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell-16px.png 'Code Smell')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7236&resolved=false&types=CODE_SMELL) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7236&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7236&resolved=false&types=CODE_SMELL)

[![No Coverage information](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/NoCoverageInfo-16px.png 'No Coverage information')](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=7236&metric=coverage&view=list) No Coverage information  
[![No Duplication information](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/NoDuplicationInfo-16px.png 'No Duplication information')](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=7236&metric=duplicated_lines_density&view=list) No Duplication information


sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!&nbsp; &nbsp; [![Quality Gate passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/passed-16px.png 'Quality Gate passed')](https://sonarcloud.io/dashboard?id=owncloud_ocis&pullRequest=7236)

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7236&resolved=false&types=BUG) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7236&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7236&resolved=false&types=BUG)  
[![Vulnerability](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability-16px.png 'Vulnerability')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7236&resolved=false&types=VULNERABILITY) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7236&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7236&resolved=false&types=VULNERABILITY)  
[![Security Hotspot](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot-16px.png 'Security Hotspot')](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=7236&resolved=false&types=SECURITY_HOTSPOT) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=7236&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=7236&resolved=false&types=SECURITY_HOTSPOT)  
[![Code Smell](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell-16px.png 'Code Smell')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7236&resolved=false&types=CODE_SMELL) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7236&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7236&resolved=false&types=CODE_SMELL)

[![No Coverage information](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/NoCoverageInfo-16px.png 'No Coverage information')](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=7236&metric=coverage&view=list) No Coverage information  
[![No Duplication information](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/NoDuplicationInfo-16px.png 'No Duplication information')](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=7236&metric=duplicated_lines_density&view=list) No Duplication information


sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!&nbsp; &nbsp; [![Quality Gate passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/passed-16px.png 'Quality Gate passed')](https://sonarcloud.io/dashboard?id=owncloud_ocis&pullRequest=7236)

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7236&resolved=false&types=BUG) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7236&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7236&resolved=false&types=BUG)  
[![Vulnerability](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability-16px.png 'Vulnerability')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7236&resolved=false&types=VULNERABILITY) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7236&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7236&resolved=false&types=VULNERABILITY)  
[![Security Hotspot](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot-16px.png 'Security Hotspot')](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=7236&resolved=false&types=SECURITY_HOTSPOT) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=7236&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=7236&resolved=false&types=SECURITY_HOTSPOT)  
[![Code Smell](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell-16px.png 'Code Smell')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7236&resolved=false&types=CODE_SMELL) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7236&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7236&resolved=false&types=CODE_SMELL)

[![No Coverage information](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/NoCoverageInfo-16px.png 'No Coverage information')](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=7236) No Coverage information  
[![No Duplication information](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/NoDuplicationInfo-16px.png 'No Duplication information')](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=7236&metric=duplicated_lines_density&view=list) No Duplication information


sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!&nbsp; &nbsp; [![Quality Gate passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/passed-16px.png 'Quality Gate passed')](https://sonarcloud.io/dashboard?id=owncloud_ocis&pullRequest=7236)

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7236&resolved=false&types=BUG) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7236&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7236&resolved=false&types=BUG)  
[![Vulnerability](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability-16px.png 'Vulnerability')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7236&resolved=false&types=VULNERABILITY) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7236&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7236&resolved=false&types=VULNERABILITY)  
[![Security Hotspot](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot-16px.png 'Security Hotspot')](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=7236&resolved=false&types=SECURITY_HOTSPOT) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=7236&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=7236&resolved=false&types=SECURITY_HOTSPOT)  
[![Code Smell](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell-16px.png 'Code Smell')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7236&resolved=false&types=CODE_SMELL) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7236&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7236&resolved=false&types=CODE_SMELL)

[![No Coverage information](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/NoCoverageInfo-16px.png 'No Coverage information')](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=7236&metric=coverage&view=list) No Coverage information  
[![No Duplication information](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/NoDuplicationInfo-16px.png 'No Duplication information')](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=7236&metric=duplicated_lines_density&view=list) No Duplication information


sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!&nbsp; &nbsp; [![Quality Gate passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/passed-16px.png 'Quality Gate passed')](https://sonarcloud.io/dashboard?id=owncloud_ocis&pullRequest=7236)

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7236&resolved=false&types=BUG) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7236&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7236&resolved=false&types=BUG)  
[![Vulnerability](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability-16px.png 'Vulnerability')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7236&resolved=false&types=VULNERABILITY) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7236&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7236&resolved=false&types=VULNERABILITY)  
[![Security Hotspot](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot-16px.png 'Security Hotspot')](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=7236&resolved=false&types=SECURITY_HOTSPOT) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=7236&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=7236&resolved=false&types=SECURITY_HOTSPOT)  
[![Code Smell](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell-16px.png 'Code Smell')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7236&resolved=false&types=CODE_SMELL) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7236&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7236&resolved=false&types=CODE_SMELL)

[![No Coverage information](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/NoCoverageInfo-16px.png 'No Coverage information')](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=7236&metric=coverage&view=list) No Coverage information  
[![No Duplication information](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/NoDuplicationInfo-16px.png 'No Duplication information')](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=7236&metric=duplicated_lines_density&view=list) No Duplication information



Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ce5af6ab1cd..6e45b6b7dd5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -80,16 +80,31 @@ The following sections list the changes for unreleased.
    [KQL](https://learn.microsoft.com/en-us/sharepoint/dev/general-development/keyword-query-language-kql-syntax-reference)
    as the default oCIS search query language.
 
-   Some examples of a valid KQL query are:
-
-  * `Tag`: `tag:golden tag:"silver"`
-  * `Filename`: `name:file.txt name:"file.docx"`
-  * `Content`: `content:ahab content:"captain aha*"`
+   Simple queries:
+
+  * `tag:golden tag:"silver"`
+  * `name:file.txt name:"file.docx"`
+  * `content:ahab content:"captain aha*"`
+
+   Date/-range queries
+
+  * `Mtime:"2023-09-05T08:42:11.23554+02:00"`
+  * `Mtime>"2023-09-05T08:42:11.23554+02:00"`
+  * `Mtime>="2023-09-05T08:42:11.23554+02:00"`
+  * `Mtime<"2023-09-05T08:42:11.23554+02:00"`
+  * `Mtime<="2023-09-05T08:42:11.23554+02:00"`
+  * `Mtime:today` - range: start of today till end of today
+  * `Mtime:yesterday` - range: start of yesterday till end of yesterday
+  * `Mtime:"this week"` - range: start of this week till end of this week
+  * `Mtime:"this month"` - range: start of this month till end of this month
+  * `Mtime:"last month"` - range: start of last month till end of last month
+  * `Mtime:"this year"` - range: start of this year till end of this year
+  * `Mtime:"last year"` - range: start of last year till end of last year
 
    Conjunctive normal form queries:
 
-  * `Boolean`: `tag:golden AND tag:"silver`, `tag:golden OR tag:"silver`, `tag:golden NOT tag:"silver`
-  * `Group`: `(tag:book content:ahab*)`, `tag:(book pdf)`
+  * `tag:golden AND tag:"silver`, `tag:golden OR tag:"silver`, `tag:golden NOT tag:"silver`
+  * `(tag:book content:ahab*)`, `tag:(book pdf)`
 
    Complex queries:
 
@@ -105,6 +120,7 @@ The following sections list the changes for unreleased.
    https://github.com/owncloud/ocis/pull/7247
    https://github.com/owncloud/ocis/pull/7248
    https://github.com/owncloud/ocis/pull/7254
+   https://github.com/owncloud/ocis/pull/7262
    https://github.com/owncloud/web/pull/9653
    https://github.com/owncloud/web/pull/9672
 
diff --git a/changelog/unreleased/kql-search-query-language.md b/changelog/unreleased/kql-search-query-language.md
index 0749e09dff9..f1724114ca8 100644
--- a/changelog/unreleased/kql-search-query-language.md
+++ b/changelog/unreleased/kql-search-query-language.md
@@ -2,16 +2,31 @@ Enhancement: Keyword Query Language (KQL) search syntax
 
 We've introduced support for [KQL](https://learn.microsoft.com/en-us/sharepoint/dev/general-development/keyword-query-language-kql-syntax-reference) as the default oCIS search query language.
 
-Some examples of a valid KQL query are:
-
-* `Tag`: `tag:golden tag:"silver"`
-* `Filename`: `name:file.txt name:"file.docx"`
-* `Content`: `content:ahab content:"captain aha*"`
+Simple queries:
+
+* `tag:golden tag:"silver"`
+* `name:file.txt name:"file.docx"`
+* `content:ahab content:"captain aha*"`
+
+Date/-range queries
+
+* `Mtime:"2023-09-05T08:42:11.23554+02:00"`
+* `Mtime>"2023-09-05T08:42:11.23554+02:00"`
+* `Mtime>="2023-09-05T08:42:11.23554+02:00"`
+* `Mtime<"2023-09-05T08:42:11.23554+02:00"`
+* `Mtime<="2023-09-05T08:42:11.23554+02:00"`
+* `Mtime:today` - range: start of today till end of today
+* `Mtime:yesterday` - range: start of yesterday till end of yesterday
+* `Mtime:"this week"` - range: start of this week till end of this week
+* `Mtime:"this month"` - range: start of this month till end of this month
+* `Mtime:"last month"` - range: start of last month till end of last month
+* `Mtime:"this year"` - range: start of this year till end of this year
+* `Mtime:"last year"` - range: start of last year till end of last year
 
 Conjunctive normal form queries:
 
-* `Boolean`: `tag:golden AND tag:"silver`, `tag:golden OR tag:"silver`, `tag:golden NOT tag:"silver`
-* `Group`: `(tag:book content:ahab*)`, `tag:(book pdf)`
+* `tag:golden AND tag:"silver`, `tag:golden OR tag:"silver`, `tag:golden NOT tag:"silver`
+* `(tag:book content:ahab*)`, `tag:(book pdf)`
 
 Complex queries:
 
@@ -22,6 +37,7 @@ https://github.com/owncloud/ocis/pull/7043
 https://github.com/owncloud/ocis/pull/7247
 https://github.com/owncloud/ocis/pull/7248
 https://github.com/owncloud/ocis/pull/7254
+https://github.com/owncloud/ocis/pull/7262
 https://github.com/owncloud/web/pull/9653
 https://github.com/owncloud/web/pull/9672
 https://github.com/owncloud/ocis/issues/7042
diff --git a/go.mod b/go.mod
index a64f307ddba..20ceae213a6 100644
--- a/go.mod
+++ b/go.mod
@@ -8,7 +8,6 @@ require (
 	github.com/Masterminds/semver v1.5.0
 	github.com/MicahParks/keyfunc v1.9.0
 	github.com/Nerzal/gocloak/v13 v13.8.0
-	github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de
 	github.com/bbalet/stopwords v1.0.0
 	github.com/blevesearch/bleve/v2 v2.3.9
 	github.com/coreos/go-oidc v2.2.1+incompatible
@@ -50,6 +49,7 @@ require (
 	github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.0
 	github.com/jellydator/ttlcache/v2 v2.11.1
 	github.com/jellydator/ttlcache/v3 v3.1.0
+	github.com/jinzhu/now v1.1.5
 	github.com/justinas/alice v1.2.0
 	github.com/leonelquinteros/gotext v1.5.3-0.20230317130943-71a59c05b2c1
 	github.com/libregraph/idm v0.4.1-0.20230221143410-3503963047a5
diff --git a/go.sum b/go.sum
index fbeb6004b5a..9af77a03b97 100644
--- a/go.sum
+++ b/go.sum
@@ -859,8 +859,6 @@ github.com/apache/arrow/go/v11 v11.0.0/go.mod h1:Eg5OsL5H+e299f7u5ssuXsuHQVEGC4x
 github.com/apache/arrow/go/v12 v12.0.0/go.mod h1:d+tV/eHZZ7Dz7RPrFKtPK02tpr+c9/PEd/zm8mDS9Vg=
 github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
 github.com/apache/thrift v0.16.0/go.mod h1:PHK3hniurgQaNMZYaCLEqXKsYK8upmhPbmdP2FXSqgU=
-github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de h1:FxWPpzIjnTlhPwqqXc4/vE0f7GvRjuAsbW+HOIe8KnA=
-github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de/go.mod h1:DCaWoUhZrYW9p1lxo/cm8EmUOOzAPSEZNGF2DK1dJgw=
 github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q=
 github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE=
 github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
@@ -1525,6 +1523,8 @@ github.com/jellydator/ttlcache/v3 v3.1.0/go.mod h1:hi7MGFdMAwZna5n2tuvh63DvFLzVK
 github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
 github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
 github.com/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZZoE=
+github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
+github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
 github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
 github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
 github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
@@ -1642,7 +1642,6 @@ github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/
 github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
 github.com/mattn/go-runewidth v0.0.6/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
 github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
-github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
 github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
 github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
 github.com/mattn/go-sqlite3 v1.14.14/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
@@ -1866,7 +1865,6 @@ github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qq
 github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
 github.com/riandyrn/otelchi v0.5.1 h1:0/45omeqpP7f/cvdL16GddQBfAEmZvUyl2QzLSE6uYo=
 github.com/riandyrn/otelchi v0.5.1/go.mod h1:ZxVxNEl+jQ9uHseRYIxKWRb3OY8YXFEu+EkNiiSNUEA=
-github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
 github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
 github.com/rivo/uniseg v0.4.2 h1:YwD0ulJSJytLpiaWua0sBDusfsCZohxjxzVTYjwxfV8=
 github.com/rivo/uniseg v0.4.2/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
@@ -1898,7 +1896,6 @@ github.com/sacloud/libsacloud v1.36.2/go.mod h1:P7YAOVmnIn3DKHqCZcUKYUXmSwGBm3yS
 github.com/scaleway/scaleway-sdk-go v1.0.0-beta.7.0.20210127161313-bd30bebeac4f/go.mod h1:CJJ5VAbozOl0yEw7nHB9+7BXTJbIn6h7W+f6Gau5IP8=
 github.com/sciencemesh/meshdirectory-web v1.0.4 h1:1YSctF6PAXhoHUYCaeRTj7rHaF7b3rYrZf2R0VXBIbo=
 github.com/sciencemesh/meshdirectory-web v1.0.4/go.mod h1:fJSThTS3xf+sTdL0iXQoaQJssLI7tn7DetHMHUl4SRk=
-github.com/scylladb/termtables v0.0.0-20191203121021-c4c0b6d42ff4/go.mod h1:C1a7PQSMz9NShzorzCiG2fk9+xuCgLkPeCvMHYR2OWg=
 github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I=
 github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
 github.com/segmentio/ksuid v1.0.4 h1:sBo2BdShXjmcugAMwjugoGUdUV0pcxY5mW4xKRn3v4c=
diff --git a/services/search/pkg/query/kql/cast.go b/services/search/pkg/query/kql/cast.go
index d9710d35ce1..e3b9ab58106 100644
--- a/services/search/pkg/query/kql/cast.go
+++ b/services/search/pkg/query/kql/cast.go
@@ -4,7 +4,7 @@ import (
 	"fmt"
 	"time"
 
-	"github.com/araddon/dateparse"
+	"github.com/jinzhu/now"
 
 	"github.com/owncloud/ocis/v2/services/search/pkg/query/ast"
 )
@@ -21,21 +21,23 @@ func toNode[T ast.Node](in interface{}) (T, error) {
 
 func toNodes[T ast.Node](in interface{}) ([]T, error) {
 	switch v := in.(type) {
-	case []T:
-		return v, nil
 	case T:
 		return []T{v}, nil
+	case []T:
+		return v, nil
+	case []*ast.OperatorNode, []*ast.DateTimeNode:
+		return toNodes[T](v)
 	case []interface{}:
-		var ts []T
-		for _, inter := range v {
-			n, err := toNodes[T](inter)
+		var nodes []T
+		for _, el := range v {
+			node, err := toNodes[T](el)
 			if err != nil {
 				return nil, err
 			}
 
-			ts = append(ts, n...)
+			nodes = append(nodes, node...)
 		}
-		return ts, nil
+		return nodes, nil
 	case nil:
 		return nil, nil
 	default:
@@ -74,5 +76,53 @@ func toTime(in interface{}) (time.Time, error) {
 		return time.Time{}, err
 	}
 
-	return dateparse.ParseLocal(ts)
+	return now.Parse(ts)
+}
+
+func toTimeRange(in interface{}) (*time.Time, *time.Time, error) {
+	var from, to time.Time
+
+	value, err := toString(in)
+	if err != nil {
+		return &from, &to, UnsupportedTimeRangeError{}
+	}
+
+	c := &now.Config{
+		WeekStartDay: time.Monday,
+	}
+
+	n := c.With(timeNow())
+
+	switch value {
+	case "today":
+		from = n.BeginningOfDay()
+		to = n.EndOfDay()
+	case "yesterday":
+		yesterday := n.With(n.AddDate(0, 0, -1))
+		from = yesterday.BeginningOfDay()
+		to = yesterday.EndOfDay()
+	case "this week":
+		from = n.BeginningOfWeek()
+		to = n.EndOfWeek()
+	case "this month":
+		from = n.BeginningOfMonth()
+		to = n.EndOfMonth()
+	case "last month":
+		lastMonth := n.With(n.AddDate(0, -1, 0))
+		from = lastMonth.BeginningOfMonth()
+		to = lastMonth.EndOfMonth()
+	case "this year":
+		from = n.BeginningOfYear()
+		to = n.EndOfYear()
+	case "last year":
+		lastYear := n.With(n.AddDate(-1, 0, 0))
+		from = lastYear.BeginningOfYear()
+		to = lastYear.EndOfYear()
+	}
+
+	if from.IsZero() || to.IsZero() {
+		return nil, nil, UnsupportedTimeRangeError{}
+	}
+
+	return &from, &to, nil
 }
diff --git a/services/search/pkg/query/kql/dictionary.peg b/services/search/pkg/query/kql/dictionary.peg
index ca9658dfa53..426e4d5091f 100644
--- a/services/search/pkg/query/kql/dictionary.peg
+++ b/services/search/pkg/query/kql/dictionary.peg
@@ -61,6 +61,12 @@ DateTimeRestrictionNode <-
         FullTime
     ) '"'? {
         return buildDateTimeNode(k, o, v, c.text, c.pos)
+    } /
+    k:Char+ (
+        OperatorEqualNode /
+        OperatorColonNode
+    ) '"'? v:NaturalLanguageDateTime '"'? {
+        return buildNaturalLanguageDateTimeNodes(k, v, c.text, c.pos)
     }
 
 TextPropertyRestrictionNode <-
@@ -185,11 +191,22 @@ FullTime <-
         return c.text, nil
     }
 
-DateTime
-  = FullDate "T" FullTime {
+DateTime  <-
+    FullDate "T" FullTime {
       return c.text, nil
     }
 
+NaturalLanguageDateTime  <-
+    "today" /
+    "yesterday" /
+    "this week" /
+    "this month" /
+    "last month" /
+    "this year" /
+    "last year"  {
+        return c.text, nil
+    }
+
 ////////////////////////////////////////////////////////
 // misc
 ////////////////////////////////////////////////////////
diff --git a/services/search/pkg/query/kql/dictionary_gen.go b/services/search/pkg/query/kql/dictionary_gen.go
index afce6c5c85e..4bcd4f83b02 100644
--- a/services/search/pkg/query/kql/dictionary_gen.go
+++ b/services/search/pkg/query/kql/dictionary_gen.go
@@ -43,12 +43,12 @@ var g = &grammar{
 					pos: position{line: 19, col: 6, offset: 351},
 					exprs: []any{
 						&actionExpr{
-							pos: position{line: 213, col: 5, offset: 4364},
+							pos: position{line: 230, col: 5, offset: 4745},
 							run: (*parser).callonNodes3,
 							expr: &zeroOrMoreExpr{
-								pos: position{line: 213, col: 5, offset: 4364},
+								pos: position{line: 230, col: 5, offset: 4745},
 								expr: &charClassMatcher{
-									pos:        position{line: 213, col: 5, offset: 4364},
+									pos:        position{line: 230, col: 5, offset: 4745},
 									val:        "[ \\t]",
 									chars:      []rune{' ', '\t'},
 									ignoreCase: false,
@@ -86,10 +86,10 @@ var g = &grammar{
 									expr: &oneOrMoreExpr{
 										pos: position{line: 46, col: 7, offset: 1044},
 										expr: &actionExpr{
-											pos: position{line: 198, col: 5, offset: 4194},
+											pos: position{line: 215, col: 5, offset: 4575},
 											run: (*parser).callonNode7,
 											expr: &charClassMatcher{
-												pos:        position{line: 198, col: 5, offset: 4194},
+												pos:        position{line: 215, col: 5, offset: 4575},
 												val:        "[A-Za-z]",
 												ranges:     []rune{'A', 'Z', 'a', 'z'},
 												ignoreCase: false,
@@ -102,20 +102,20 @@ var g = &grammar{
 									pos: position{line: 46, col: 14, offset: 1051},
 									alternatives: []any{
 										&actionExpr{
-											pos: position{line: 114, col: 5, offset: 2721},
+											pos: position{line: 120, col: 5, offset: 2911},
 											run: (*parser).callonNode10,
 											expr: &litMatcher{
-												pos:        position{line: 114, col: 5, offset: 2721},
+												pos:        position{line: 120, col: 5, offset: 2911},
 												val:        ":",
 												ignoreCase: false,
 												want:       "\":\"",
 											},
 										},
 										&actionExpr{
-											pos: position{line: 119, col: 5, offset: 2807},
+											pos: position{line: 125, col: 5, offset: 2997},
 											run: (*parser).callonNode12,
 											expr: &litMatcher{
-												pos:        position{line: 119, col: 5, offset: 2807},
+												pos:        position{line: 125, col: 5, offset: 2997},
 												val:        "=",
 												ignoreCase: false,
 												want:       "\"=\"",
@@ -159,10 +159,10 @@ var g = &grammar{
 									expr: &oneOrMoreExpr{
 										pos: position{line: 51, col: 7, offset: 1205},
 										expr: &actionExpr{
-											pos: position{line: 198, col: 5, offset: 4194},
+											pos: position{line: 215, col: 5, offset: 4575},
 											run: (*parser).callonNode22,
 											expr: &charClassMatcher{
-												pos:        position{line: 198, col: 5, offset: 4194},
+												pos:        position{line: 215, col: 5, offset: 4575},
 												val:        "[A-Za-z]",
 												ranges:     []rune{'A', 'Z', 'a', 'z'},
 												ignoreCase: false,
@@ -178,60 +178,60 @@ var g = &grammar{
 										pos: position{line: 52, col: 9, offset: 1223},
 										alternatives: []any{
 											&actionExpr{
-												pos: position{line: 139, col: 5, offset: 3168},
+												pos: position{line: 145, col: 5, offset: 3358},
 												run: (*parser).callonNode26,
 												expr: &litMatcher{
-													pos:        position{line: 139, col: 5, offset: 3168},
+													pos:        position{line: 145, col: 5, offset: 3358},
 													val:        ">=",
 													ignoreCase: false,
 													want:       "\">=\"",
 												},
 											},
 											&actionExpr{
-												pos: position{line: 129, col: 5, offset: 2984},
+												pos: position{line: 135, col: 5, offset: 3174},
 												run: (*parser).callonNode28,
 												expr: &litMatcher{
-													pos:        position{line: 129, col: 5, offset: 2984},
+													pos:        position{line: 135, col: 5, offset: 3174},
 													val:        "<=",
 													ignoreCase: false,
 													want:       "\"<=\"",
 												},
 											},
 											&actionExpr{
-												pos: position{line: 134, col: 5, offset: 3073},
+												pos: position{line: 140, col: 5, offset: 3263},
 												run: (*parser).callonNode30,
 												expr: &litMatcher{
-													pos:        position{line: 134, col: 5, offset: 3073},
+													pos:        position{line: 140, col: 5, offset: 3263},
 													val:        ">",
 													ignoreCase: false,
 													want:       "\">\"",
 												},
 											},
 											&actionExpr{
-												pos: position{line: 124, col: 5, offset: 2892},
+												pos: position{line: 130, col: 5, offset: 3082},
 												run: (*parser).callonNode32,
 												expr: &litMatcher{
-													pos:        position{line: 124, col: 5, offset: 2892},
+													pos:        position{line: 130, col: 5, offset: 3082},
 													val:        "<",
 													ignoreCase: false,
 													want:       "\"<\"",
 												},
 											},
 											&actionExpr{
-												pos: position{line: 119, col: 5, offset: 2807},
+												pos: position{line: 125, col: 5, offset: 2997},
 												run: (*parser).callonNode34,
 												expr: &litMatcher{
-													pos:        position{line: 119, col: 5, offset: 2807},
+													pos:        position{line: 125, col: 5, offset: 2997},
 													val:        "=",
 													ignoreCase: false,
 													want:       "\"=\"",
 												},
 											},
 											&actionExpr{
-												pos: position{line: 114, col: 5, offset: 2721},
+												pos: position{line: 120, col: 5, offset: 2911},
 												run: (*parser).callonNode36,
 												expr: &litMatcher{
-													pos:        position{line: 114, col: 5, offset: 2721},
+													pos:        position{line: 120, col: 5, offset: 2911},
 													val:        ":",
 													ignoreCase: false,
 													want:       "\":\"",
@@ -256,28 +256,28 @@ var g = &grammar{
 										pos: position{line: 59, col: 9, offset: 1420},
 										alternatives: []any{
 											&actionExpr{
-												pos: position{line: 189, col: 5, offset: 4003},
+												pos: position{line: 195, col: 5, offset: 4197},
 												run: (*parser).callonNode42,
 												expr: &seqExpr{
-													pos: position{line: 189, col: 5, offset: 4003},
+													pos: position{line: 195, col: 5, offset: 4197},
 													exprs: []any{
 														&actionExpr{
-															pos: position{line: 179, col: 5, offset: 3770},
+															pos: position{line: 185, col: 5, offset: 3960},
 															run: (*parser).callonNode44,
 															expr: &seqExpr{
-																pos: position{line: 179, col: 5, offset: 3770},
+																pos: position{line: 185, col: 5, offset: 3960},
 																exprs: []any{
 																	&actionExpr{
-																		pos: position{line: 149, col: 5, offset: 3370},
+																		pos: position{line: 155, col: 5, offset: 3560},
 																		run: (*parser).callonNode46,
 																		expr: &seqExpr{
-																			pos: position{line: 149, col: 5, offset: 3370},
+																			pos: position{line: 155, col: 5, offset: 3560},
 																			exprs: []any{
 																				&actionExpr{
-																					pos: position{line: 208, col: 5, offset: 4313},
+																					pos: position{line: 225, col: 5, offset: 4694},
 																					run: (*parser).callonNode48,
 																					expr: &charClassMatcher{
-																						pos:        position{line: 208, col: 5, offset: 4313},
+																						pos:        position{line: 225, col: 5, offset: 4694},
 																						val:        "[0-9]",
 																						ranges:     []rune{'0', '9'},
 																						ignoreCase: false,
@@ -285,10 +285,10 @@ var g = &grammar{
 																					},
 																				},
 																				&actionExpr{
-																					pos: position{line: 208, col: 5, offset: 4313},
+																					pos: position{line: 225, col: 5, offset: 4694},
 																					run: (*parser).callonNode50,
 																					expr: &charClassMatcher{
-																						pos:        position{line: 208, col: 5, offset: 4313},
+																						pos:        position{line: 225, col: 5, offset: 4694},
 																						val:        "[0-9]",
 																						ranges:     []rune{'0', '9'},
 																						ignoreCase: false,
@@ -296,10 +296,10 @@ var g = &grammar{
 																					},
 																				},
 																				&actionExpr{
-																					pos: position{line: 208, col: 5, offset: 4313},
+																					pos: position{line: 225, col: 5, offset: 4694},
 																					run: (*parser).callonNode52,
 																					expr: &charClassMatcher{
-																						pos:        position{line: 208, col: 5, offset: 4313},
+																						pos:        position{line: 225, col: 5, offset: 4694},
 																						val:        "[0-9]",
 																						ranges:     []rune{'0', '9'},
 																						ignoreCase: false,
@@ -307,10 +307,10 @@ var g = &grammar{
 																					},
 																				},
 																				&actionExpr{
-																					pos: position{line: 208, col: 5, offset: 4313},
+																					pos: position{line: 225, col: 5, offset: 4694},
 																					run: (*parser).callonNode54,
 																					expr: &charClassMatcher{
-																						pos:        position{line: 208, col: 5, offset: 4313},
+																						pos:        position{line: 225, col: 5, offset: 4694},
 																						val:        "[0-9]",
 																						ranges:     []rune{'0', '9'},
 																						ignoreCase: false,
@@ -321,22 +321,22 @@ var g = &grammar{
 																		},
 																	},
 																	&litMatcher{
-																		pos:        position{line: 179, col: 14, offset: 3779},
+																		pos:        position{line: 185, col: 14, offset: 3969},
 																		val:        "-",
 																		ignoreCase: false,
 																		want:       "\"-\"",
 																	},
 																	&actionExpr{
-																		pos: position{line: 154, col: 5, offset: 3447},
+																		pos: position{line: 160, col: 5, offset: 3637},
 																		run: (*parser).callonNode57,
 																		expr: &seqExpr{
-																			pos: position{line: 154, col: 5, offset: 3447},
+																			pos: position{line: 160, col: 5, offset: 3637},
 																			exprs: []any{
 																				&actionExpr{
-																					pos: position{line: 208, col: 5, offset: 4313},
+																					pos: position{line: 225, col: 5, offset: 4694},
 																					run: (*parser).callonNode59,
 																					expr: &charClassMatcher{
-																						pos:        position{line: 208, col: 5, offset: 4313},
+																						pos:        position{line: 225, col: 5, offset: 4694},
 																						val:        "[0-9]",
 																						ranges:     []rune{'0', '9'},
 																						ignoreCase: false,
@@ -344,10 +344,10 @@ var g = &grammar{
 																					},
 																				},
 																				&actionExpr{
-																					pos: position{line: 208, col: 5, offset: 4313},
+																					pos: position{line: 225, col: 5, offset: 4694},
 																					run: (*parser).callonNode61,
 																					expr: &charClassMatcher{
-																						pos:        position{line: 208, col: 5, offset: 4313},
+																						pos:        position{line: 225, col: 5, offset: 4694},
 																						val:        "[0-9]",
 																						ranges:     []rune{'0', '9'},
 																						ignoreCase: false,
@@ -358,22 +358,22 @@ var g = &grammar{
 																		},
 																	},
 																	&litMatcher{
-																		pos:        position{line: 179, col: 28, offset: 3793},
+																		pos:        position{line: 185, col: 28, offset: 3983},
 																		val:        "-",
 																		ignoreCase: false,
 																		want:       "\"-\"",
 																	},
 																	&actionExpr{
-																		pos: position{line: 159, col: 5, offset: 3510},
+																		pos: position{line: 165, col: 5, offset: 3700},
 																		run: (*parser).callonNode64,
 																		expr: &seqExpr{
-																			pos: position{line: 159, col: 5, offset: 3510},
+																			pos: position{line: 165, col: 5, offset: 3700},
 																			exprs: []any{
 																				&actionExpr{
-																					pos: position{line: 208, col: 5, offset: 4313},
+																					pos: position{line: 225, col: 5, offset: 4694},
 																					run: (*parser).callonNode66,
 																					expr: &charClassMatcher{
-																						pos:        position{line: 208, col: 5, offset: 4313},
+																						pos:        position{line: 225, col: 5, offset: 4694},
 																						val:        "[0-9]",
 																						ranges:     []rune{'0', '9'},
 																						ignoreCase: false,
@@ -381,10 +381,10 @@ var g = &grammar{
 																					},
 																				},
 																				&actionExpr{
-																					pos: position{line: 208, col: 5, offset: 4313},
+																					pos: position{line: 225, col: 5, offset: 4694},
 																					run: (*parser).callonNode68,
 																					expr: &charClassMatcher{
-																						pos:        position{line: 208, col: 5, offset: 4313},
+																						pos:        position{line: 225, col: 5, offset: 4694},
 																						val:        "[0-9]",
 																						ranges:     []rune{'0', '9'},
 																						ignoreCase: false,
@@ -398,28 +398,28 @@ var g = &grammar{
 															},
 														},
 														&litMatcher{
-															pos:        position{line: 189, col: 14, offset: 4012},
+															pos:        position{line: 195, col: 14, offset: 4206},
 															val:        "T",
 															ignoreCase: false,
 															want:       "\"T\"",
 														},
 														&actionExpr{
-															pos: position{line: 184, col: 5, offset: 3857},
+															pos: position{line: 190, col: 5, offset: 4047},
 															run: (*parser).callonNode71,
 															expr: &seqExpr{
-																pos: position{line: 184, col: 5, offset: 3857},
+																pos: position{line: 190, col: 5, offset: 4047},
 																exprs: []any{
 																	&actionExpr{
-																		pos: position{line: 164, col: 5, offset: 3574},
+																		pos: position{line: 170, col: 5, offset: 3764},
 																		run: (*parser).callonNode73,
 																		expr: &seqExpr{
-																			pos: position{line: 164, col: 5, offset: 3574},
+																			pos: position{line: 170, col: 5, offset: 3764},
 																			exprs: []any{
 																				&actionExpr{
-																					pos: position{line: 208, col: 5, offset: 4313},
+																					pos: position{line: 225, col: 5, offset: 4694},
 																					run: (*parser).callonNode75,
 																					expr: &charClassMatcher{
-																						pos:        position{line: 208, col: 5, offset: 4313},
+																						pos:        position{line: 225, col: 5, offset: 4694},
 																						val:        "[0-9]",
 																						ranges:     []rune{'0', '9'},
 																						ignoreCase: false,
@@ -427,10 +427,10 @@ var g = &grammar{
 																					},
 																				},
 																				&actionExpr{
-																					pos: position{line: 208, col: 5, offset: 4313},
+																					pos: position{line: 225, col: 5, offset: 4694},
 																					run: (*parser).callonNode77,
 																					expr: &charClassMatcher{
-																						pos:        position{line: 208, col: 5, offset: 4313},
+																						pos:        position{line: 225, col: 5, offset: 4694},
 																						val:        "[0-9]",
 																						ranges:     []rune{'0', '9'},
 																						ignoreCase: false,
@@ -441,22 +441,22 @@ var g = &grammar{
 																		},
 																	},
 																	&litMatcher{
-																		pos:        position{line: 184, col: 14, offset: 3866},
+																		pos:        position{line: 190, col: 14, offset: 4056},
 																		val:        ":",
 																		ignoreCase: false,
 																		want:       "\":\"",
 																	},
 																	&actionExpr{
-																		pos: position{line: 169, col: 5, offset: 3640},
+																		pos: position{line: 175, col: 5, offset: 3830},
 																		run: (*parser).callonNode80,
 																		expr: &seqExpr{
-																			pos: position{line: 169, col: 5, offset: 3640},
+																			pos: position{line: 175, col: 5, offset: 3830},
 																			exprs: []any{
 																				&actionExpr{
-																					pos: position{line: 208, col: 5, offset: 4313},
+																					pos: position{line: 225, col: 5, offset: 4694},
 																					run: (*parser).callonNode82,
 																					expr: &charClassMatcher{
-																						pos:        position{line: 208, col: 5, offset: 4313},
+																						pos:        position{line: 225, col: 5, offset: 4694},
 																						val:        "[0-9]",
 																						ranges:     []rune{'0', '9'},
 																						ignoreCase: false,
@@ -464,10 +464,10 @@ var g = &grammar{
 																					},
 																				},
 																				&actionExpr{
-																					pos: position{line: 208, col: 5, offset: 4313},
+																					pos: position{line: 225, col: 5, offset: 4694},
 																					run: (*parser).callonNode84,
 																					expr: &charClassMatcher{
-																						pos:        position{line: 208, col: 5, offset: 4313},
+																						pos:        position{line: 225, col: 5, offset: 4694},
 																						val:        "[0-9]",
 																						ranges:     []rune{'0', '9'},
 																						ignoreCase: false,
@@ -478,22 +478,22 @@ var g = &grammar{
 																		},
 																	},
 																	&litMatcher{
-																		pos:        position{line: 184, col: 29, offset: 3881},
+																		pos:        position{line: 190, col: 29, offset: 4071},
 																		val:        ":",
 																		ignoreCase: false,
 																		want:       "\":\"",
 																	},
 																	&actionExpr{
-																		pos: position{line: 174, col: 5, offset: 3706},
+																		pos: position{line: 180, col: 5, offset: 3896},
 																		run: (*parser).callonNode87,
 																		expr: &seqExpr{
-																			pos: position{line: 174, col: 5, offset: 3706},
+																			pos: position{line: 180, col: 5, offset: 3896},
 																			exprs: []any{
 																				&actionExpr{
-																					pos: position{line: 208, col: 5, offset: 4313},
+																					pos: position{line: 225, col: 5, offset: 4694},
 																					run: (*parser).callonNode89,
 																					expr: &charClassMatcher{
-																						pos:        position{line: 208, col: 5, offset: 4313},
+																						pos:        position{line: 225, col: 5, offset: 4694},
 																						val:        "[0-9]",
 																						ranges:     []rune{'0', '9'},
 																						ignoreCase: false,
@@ -501,10 +501,10 @@ var g = &grammar{
 																					},
 																				},
 																				&actionExpr{
-																					pos: position{line: 208, col: 5, offset: 4313},
+																					pos: position{line: 225, col: 5, offset: 4694},
 																					run: (*parser).callonNode91,
 																					expr: &charClassMatcher{
-																						pos:        position{line: 208, col: 5, offset: 4313},
+																						pos:        position{line: 225, col: 5, offset: 4694},
 																						val:        "[0-9]",
 																						ranges:     []rune{'0', '9'},
 																						ignoreCase: false,
@@ -515,23 +515,23 @@ var g = &grammar{
 																		},
 																	},
 																	&zeroOrOneExpr{
-																		pos: position{line: 184, col: 44, offset: 3896},
+																		pos: position{line: 190, col: 44, offset: 4086},
 																		expr: &seqExpr{
-																			pos: position{line: 184, col: 45, offset: 3897},
+																			pos: position{line: 190, col: 45, offset: 4087},
 																			exprs: []any{
 																				&litMatcher{
-																					pos:        position{line: 184, col: 45, offset: 3897},
+																					pos:        position{line: 190, col: 45, offset: 4087},
 																					val:        ".",
 																					ignoreCase: false,
 																					want:       "\".\"",
 																				},
 																				&oneOrMoreExpr{
-																					pos: position{line: 184, col: 49, offset: 3901},
+																					pos: position{line: 190, col: 49, offset: 4091},
 																					expr: &actionExpr{
-																						pos: position{line: 208, col: 5, offset: 4313},
+																						pos: position{line: 225, col: 5, offset: 4694},
 																						run: (*parser).callonNode97,
 																						expr: &charClassMatcher{
-																							pos:        position{line: 208, col: 5, offset: 4313},
+																							pos:        position{line: 225, col: 5, offset: 4694},
 																							val:        "[0-9]",
 																							ranges:     []rune{'0', '9'},
 																							ignoreCase: false,
@@ -543,35 +543,35 @@ var g = &grammar{
 																		},
 																	},
 																	&choiceExpr{
-																		pos: position{line: 184, col: 59, offset: 3911},
+																		pos: position{line: 190, col: 59, offset: 4101},
 																		alternatives: []any{
 																			&litMatcher{
-																				pos:        position{line: 184, col: 59, offset: 3911},
+																				pos:        position{line: 190, col: 59, offset: 4101},
 																				val:        "Z",
 																				ignoreCase: false,
 																				want:       "\"Z\"",
 																			},
 																			&seqExpr{
-																				pos: position{line: 184, col: 65, offset: 3917},
+																				pos: position{line: 190, col: 65, offset: 4107},
 																				exprs: []any{
 																					&charClassMatcher{
-																						pos:        position{line: 184, col: 66, offset: 3918},
+																						pos:        position{line: 190, col: 66, offset: 4108},
 																						val:        "[+-]",
 																						chars:      []rune{'+', '-'},
 																						ignoreCase: false,
 																						inverted:   false,
 																					},
 																					&actionExpr{
-																						pos: position{line: 164, col: 5, offset: 3574},
+																						pos: position{line: 170, col: 5, offset: 3764},
 																						run: (*parser).callonNode103,
 																						expr: &seqExpr{
-																							pos: position{line: 164, col: 5, offset: 3574},
+																							pos: position{line: 170, col: 5, offset: 3764},
 																							exprs: []any{
 																								&actionExpr{
-																									pos: position{line: 208, col: 5, offset: 4313},
+																									pos: position{line: 225, col: 5, offset: 4694},
 																									run: (*parser).callonNode105,
 																									expr: &charClassMatcher{
-																										pos:        position{line: 208, col: 5, offset: 4313},
+																										pos:        position{line: 225, col: 5, offset: 4694},
 																										val:        "[0-9]",
 																										ranges:     []rune{'0', '9'},
 																										ignoreCase: false,
@@ -579,10 +579,10 @@ var g = &grammar{
 																									},
 																								},
 																								&actionExpr{
-																									pos: position{line: 208, col: 5, offset: 4313},
+																									pos: position{line: 225, col: 5, offset: 4694},
 																									run: (*parser).callonNode107,
 																									expr: &charClassMatcher{
-																										pos:        position{line: 208, col: 5, offset: 4313},
+																										pos:        position{line: 225, col: 5, offset: 4694},
 																										val:        "[0-9]",
 																										ranges:     []rune{'0', '9'},
 																										ignoreCase: false,
@@ -593,22 +593,22 @@ var g = &grammar{
 																						},
 																					},
 																					&litMatcher{
-																						pos:        position{line: 184, col: 86, offset: 3938},
+																						pos:        position{line: 190, col: 86, offset: 4128},
 																						val:        ":",
 																						ignoreCase: false,
 																						want:       "\":\"",
 																					},
 																					&actionExpr{
-																						pos: position{line: 169, col: 5, offset: 3640},
+																						pos: position{line: 175, col: 5, offset: 3830},
 																						run: (*parser).callonNode110,
 																						expr: &seqExpr{
-																							pos: position{line: 169, col: 5, offset: 3640},
+																							pos: position{line: 175, col: 5, offset: 3830},
 																							exprs: []any{
 																								&actionExpr{
-																									pos: position{line: 208, col: 5, offset: 4313},
+																									pos: position{line: 225, col: 5, offset: 4694},
 																									run: (*parser).callonNode112,
 																									expr: &charClassMatcher{
-																										pos:        position{line: 208, col: 5, offset: 4313},
+																										pos:        position{line: 225, col: 5, offset: 4694},
 																										val:        "[0-9]",
 																										ranges:     []rune{'0', '9'},
 																										ignoreCase: false,
@@ -616,10 +616,10 @@ var g = &grammar{
 																									},
 																								},
 																								&actionExpr{
-																									pos: position{line: 208, col: 5, offset: 4313},
+																									pos: position{line: 225, col: 5, offset: 4694},
 																									run: (*parser).callonNode114,
 																									expr: &charClassMatcher{
-																										pos:        position{line: 208, col: 5, offset: 4313},
+																										pos:        position{line: 225, col: 5, offset: 4694},
 																										val:        "[0-9]",
 																										ranges:     []rune{'0', '9'},
 																										ignoreCase: false,
@@ -640,22 +640,22 @@ var g = &grammar{
 												},
 											},
 											&actionExpr{
-												pos: position{line: 179, col: 5, offset: 3770},
+												pos: position{line: 185, col: 5, offset: 3960},
 												run: (*parser).callonNode116,
 												expr: &seqExpr{
-													pos: position{line: 179, col: 5, offset: 3770},
+													pos: position{line: 185, col: 5, offset: 3960},
 													exprs: []any{
 														&actionExpr{
-															pos: position{line: 149, col: 5, offset: 3370},
+															pos: position{line: 155, col: 5, offset: 3560},
 															run: (*parser).callonNode118,
 															expr: &seqExpr{
-																pos: position{line: 149, col: 5, offset: 3370},
+																pos: position{line: 155, col: 5, offset: 3560},
 																exprs: []any{
 																	&actionExpr{
-																		pos: position{line: 208, col: 5, offset: 4313},
+																		pos: position{line: 225, col: 5, offset: 4694},
 																		run: (*parser).callonNode120,
 																		expr: &charClassMatcher{
-																			pos:        position{line: 208, col: 5, offset: 4313},
+																			pos:        position{line: 225, col: 5, offset: 4694},
 																			val:        "[0-9]",
 																			ranges:     []rune{'0', '9'},
 																			ignoreCase: false,
@@ -663,10 +663,10 @@ var g = &grammar{
 																		},
 																	},
 																	&actionExpr{
-																		pos: position{line: 208, col: 5, offset: 4313},
+																		pos: position{line: 225, col: 5, offset: 4694},
 																		run: (*parser).callonNode122,
 																		expr: &charClassMatcher{
-																			pos:        position{line: 208, col: 5, offset: 4313},
+																			pos:        position{line: 225, col: 5, offset: 4694},
 																			val:        "[0-9]",
 																			ranges:     []rune{'0', '9'},
 																			ignoreCase: false,
@@ -674,10 +674,10 @@ var g = &grammar{
 																		},
 																	},
 																	&actionExpr{
-																		pos: position{line: 208, col: 5, offset: 4313},
+																		pos: position{line: 225, col: 5, offset: 4694},
 																		run: (*parser).callonNode124,
 																		expr: &charClassMatcher{
-																			pos:        position{line: 208, col: 5, offset: 4313},
+																			pos:        position{line: 225, col: 5, offset: 4694},
 																			val:        "[0-9]",
 																			ranges:     []rune{'0', '9'},
 																			ignoreCase: false,
@@ -685,10 +685,10 @@ var g = &grammar{
 																		},
 																	},
 																	&actionExpr{
-																		pos: position{line: 208, col: 5, offset: 4313},
+																		pos: position{line: 225, col: 5, offset: 4694},
 																		run: (*parser).callonNode126,
 																		expr: &charClassMatcher{
-																			pos:        position{line: 208, col: 5, offset: 4313},
+																			pos:        position{line: 225, col: 5, offset: 4694},
 																			val:        "[0-9]",
 																			ranges:     []rune{'0', '9'},
 																			ignoreCase: false,
@@ -699,22 +699,22 @@ var g = &grammar{
 															},
 														},
 														&litMatcher{
-															pos:        position{line: 179, col: 14, offset: 3779},
+															pos:        position{line: 185, col: 14, offset: 3969},
 															val:        "-",
 															ignoreCase: false,
 															want:       "\"-\"",
 														},
 														&actionExpr{
-															pos: position{line: 154, col: 5, offset: 3447},
+															pos: position{line: 160, col: 5, offset: 3637},
 															run: (*parser).callonNode129,
 															expr: &seqExpr{
-																pos: position{line: 154, col: 5, offset: 3447},
+																pos: position{line: 160, col: 5, offset: 3637},
 																exprs: []any{
 																	&actionExpr{
-																		pos: position{line: 208, col: 5, offset: 4313},
+																		pos: position{line: 225, col: 5, offset: 4694},
 																		run: (*parser).callonNode131,
 																		expr: &charClassMatcher{
-																			pos:        position{line: 208, col: 5, offset: 4313},
+																			pos:        position{line: 225, col: 5, offset: 4694},
 																			val:        "[0-9]",
 																			ranges:     []rune{'0', '9'},
 																			ignoreCase: false,
@@ -722,10 +722,10 @@ var g = &grammar{
 																		},
 																	},
 																	&actionExpr{
-																		pos: position{line: 208, col: 5, offset: 4313},
+																		pos: position{line: 225, col: 5, offset: 4694},
 																		run: (*parser).callonNode133,
 																		expr: &charClassMatcher{
-																			pos:        position{line: 208, col: 5, offset: 4313},
+																			pos:        position{line: 225, col: 5, offset: 4694},
 																			val:        "[0-9]",
 																			ranges:     []rune{'0', '9'},
 																			ignoreCase: false,
@@ -736,22 +736,22 @@ var g = &grammar{
 															},
 														},
 														&litMatcher{
-															pos:        position{line: 179, col: 28, offset: 3793},
+															pos:        position{line: 185, col: 28, offset: 3983},
 															val:        "-",
 															ignoreCase: false,
 															want:       "\"-\"",
 														},
 														&actionExpr{
-															pos: position{line: 159, col: 5, offset: 3510},
+															pos: position{line: 165, col: 5, offset: 3700},
 															run: (*parser).callonNode136,
 															expr: &seqExpr{
-																pos: position{line: 159, col: 5, offset: 3510},
+																pos: position{line: 165, col: 5, offset: 3700},
 																exprs: []any{
 																	&actionExpr{
-																		pos: position{line: 208, col: 5, offset: 4313},
+																		pos: position{line: 225, col: 5, offset: 4694},
 																		run: (*parser).callonNode138,
 																		expr: &charClassMatcher{
-																			pos:        position{line: 208, col: 5, offset: 4313},
+																			pos:        position{line: 225, col: 5, offset: 4694},
 																			val:        "[0-9]",
 																			ranges:     []rune{'0', '9'},
 																			ignoreCase: false,
@@ -759,10 +759,10 @@ var g = &grammar{
 																		},
 																	},
 																	&actionExpr{
-																		pos: position{line: 208, col: 5, offset: 4313},
+																		pos: position{line: 225, col: 5, offset: 4694},
 																		run: (*parser).callonNode140,
 																		expr: &charClassMatcher{
-																			pos:        position{line: 208, col: 5, offset: 4313},
+																			pos:        position{line: 225, col: 5, offset: 4694},
 																			val:        "[0-9]",
 																			ranges:     []rune{'0', '9'},
 																			ignoreCase: false,
@@ -776,22 +776,22 @@ var g = &grammar{
 												},
 											},
 											&actionExpr{
-												pos: position{line: 184, col: 5, offset: 3857},
+												pos: position{line: 190, col: 5, offset: 4047},
 												run: (*parser).callonNode142,
 												expr: &seqExpr{
-													pos: position{line: 184, col: 5, offset: 3857},
+													pos: position{line: 190, col: 5, offset: 4047},
 													exprs: []any{
 														&actionExpr{
-															pos: position{line: 164, col: 5, offset: 3574},
+															pos: position{line: 170, col: 5, offset: 3764},
 															run: (*parser).callonNode144,
 															expr: &seqExpr{
-																pos: position{line: 164, col: 5, offset: 3574},
+																pos: position{line: 170, col: 5, offset: 3764},
 																exprs: []any{
 																	&actionExpr{
-																		pos: position{line: 208, col: 5, offset: 4313},
+																		pos: position{line: 225, col: 5, offset: 4694},
 																		run: (*parser).callonNode146,
 																		expr: &charClassMatcher{
-																			pos:        position{line: 208, col: 5, offset: 4313},
+																			pos:        position{line: 225, col: 5, offset: 4694},
 																			val:        "[0-9]",
 																			ranges:     []rune{'0', '9'},
 																			ignoreCase: false,
@@ -799,10 +799,10 @@ var g = &grammar{
 																		},
 																	},
 																	&actionExpr{
-																		pos: position{line: 208, col: 5, offset: 4313},
+																		pos: position{line: 225, col: 5, offset: 4694},
 																		run: (*parser).callonNode148,
 																		expr: &charClassMatcher{
-																			pos:        position{line: 208, col: 5, offset: 4313},
+																			pos:        position{line: 225, col: 5, offset: 4694},
 																			val:        "[0-9]",
 																			ranges:     []rune{'0', '9'},
 																			ignoreCase: false,
@@ -813,22 +813,22 @@ var g = &grammar{
 															},
 														},
 														&litMatcher{
-															pos:        position{line: 184, col: 14, offset: 3866},
+															pos:        position{line: 190, col: 14, offset: 4056},
 															val:        ":",
 															ignoreCase: false,
 															want:       "\":\"",
 														},
 														&actionExpr{
-															pos: position{line: 169, col: 5, offset: 3640},
+															pos: position{line: 175, col: 5, offset: 3830},
 															run: (*parser).callonNode151,
 															expr: &seqExpr{
-																pos: position{line: 169, col: 5, offset: 3640},
+																pos: position{line: 175, col: 5, offset: 3830},
 																exprs: []any{
 																	&actionExpr{
-																		pos: position{line: 208, col: 5, offset: 4313},
+																		pos: position{line: 225, col: 5, offset: 4694},
 																		run: (*parser).callonNode153,
 																		expr: &charClassMatcher{
-																			pos:        position{line: 208, col: 5, offset: 4313},
+																			pos:        position{line: 225, col: 5, offset: 4694},
 																			val:        "[0-9]",
 																			ranges:     []rune{'0', '9'},
 																			ignoreCase: false,
@@ -836,10 +836,10 @@ var g = &grammar{
 																		},
 																	},
 																	&actionExpr{
-																		pos: position{line: 208, col: 5, offset: 4313},
+																		pos: position{line: 225, col: 5, offset: 4694},
 																		run: (*parser).callonNode155,
 																		expr: &charClassMatcher{
-																			pos:        position{line: 208, col: 5, offset: 4313},
+																			pos:        position{line: 225, col: 5, offset: 4694},
 																			val:        "[0-9]",
 																			ranges:     []rune{'0', '9'},
 																			ignoreCase: false,
@@ -850,22 +850,22 @@ var g = &grammar{
 															},
 														},
 														&litMatcher{
-															pos:        position{line: 184, col: 29, offset: 3881},
+															pos:        position{line: 190, col: 29, offset: 4071},
 															val:        ":",
 															ignoreCase: false,
 															want:       "\":\"",
 														},
 														&actionExpr{
-															pos: position{line: 174, col: 5, offset: 3706},
+															pos: position{line: 180, col: 5, offset: 3896},
 															run: (*parser).callonNode158,
 															expr: &seqExpr{
-																pos: position{line: 174, col: 5, offset: 3706},
+																pos: position{line: 180, col: 5, offset: 3896},
 																exprs: []any{
 																	&actionExpr{
-																		pos: position{line: 208, col: 5, offset: 4313},
+																		pos: position{line: 225, col: 5, offset: 4694},
 																		run: (*parser).callonNode160,
 																		expr: &charClassMatcher{
-																			pos:        position{line: 208, col: 5, offset: 4313},
+																			pos:        position{line: 225, col: 5, offset: 4694},
 																			val:        "[0-9]",
 																			ranges:     []rune{'0', '9'},
 																			ignoreCase: false,
@@ -873,10 +873,10 @@ var g = &grammar{
 																		},
 																	},
 																	&actionExpr{
-																		pos: position{line: 208, col: 5, offset: 4313},
+																		pos: position{line: 225, col: 5, offset: 4694},
 																		run: (*parser).callonNode162,
 																		expr: &charClassMatcher{
-																			pos:        position{line: 208, col: 5, offset: 4313},
+																			pos:        position{line: 225, col: 5, offset: 4694},
 																			val:        "[0-9]",
 																			ranges:     []rune{'0', '9'},
 																			ignoreCase: false,
@@ -887,23 +887,23 @@ var g = &grammar{
 															},
 														},
 														&zeroOrOneExpr{
-															pos: position{line: 184, col: 44, offset: 3896},
+															pos: position{line: 190, col: 44, offset: 4086},
 															expr: &seqExpr{
-																pos: position{line: 184, col: 45, offset: 3897},
+																pos: position{line: 190, col: 45, offset: 4087},
 																exprs: []any{
 																	&litMatcher{
-																		pos:        position{line: 184, col: 45, offset: 3897},
+																		pos:        position{line: 190, col: 45, offset: 4087},
 																		val:        ".",
 																		ignoreCase: false,
 																		want:       "\".\"",
 																	},
 																	&oneOrMoreExpr{
-																		pos: position{line: 184, col: 49, offset: 3901},
+																		pos: position{line: 190, col: 49, offset: 4091},
 																		expr: &actionExpr{
-																			pos: position{line: 208, col: 5, offset: 4313},
+																			pos: position{line: 225, col: 5, offset: 4694},
 																			run: (*parser).callonNode168,
 																			expr: &charClassMatcher{
-																				pos:        position{line: 208, col: 5, offset: 4313},
+																				pos:        position{line: 225, col: 5, offset: 4694},
 																				val:        "[0-9]",
 																				ranges:     []rune{'0', '9'},
 																				ignoreCase: false,
@@ -915,35 +915,35 @@ var g = &grammar{
 															},
 														},
 														&choiceExpr{
-															pos: position{line: 184, col: 59, offset: 3911},
+															pos: position{line: 190, col: 59, offset: 4101},
 															alternatives: []any{
 																&litMatcher{
-																	pos:        position{line: 184, col: 59, offset: 3911},
+																	pos:        position{line: 190, col: 59, offset: 4101},
 																	val:        "Z",
 																	ignoreCase: false,
 																	want:       "\"Z\"",
 																},
 																&seqExpr{
-																	pos: position{line: 184, col: 65, offset: 3917},
+																	pos: position{line: 190, col: 65, offset: 4107},
 																	exprs: []any{
 																		&charClassMatcher{
-																			pos:        position{line: 184, col: 66, offset: 3918},
+																			pos:        position{line: 190, col: 66, offset: 4108},
 																			val:        "[+-]",
 																			chars:      []rune{'+', '-'},
 																			ignoreCase: false,
 																			inverted:   false,
 																		},
 																		&actionExpr{
-																			pos: position{line: 164, col: 5, offset: 3574},
+																			pos: position{line: 170, col: 5, offset: 3764},
 																			run: (*parser).callonNode174,
 																			expr: &seqExpr{
-																				pos: position{line: 164, col: 5, offset: 3574},
+																				pos: position{line: 170, col: 5, offset: 3764},
 																				exprs: []any{
 																					&actionExpr{
-																						pos: position{line: 208, col: 5, offset: 4313},
+																						pos: position{line: 225, col: 5, offset: 4694},
 																						run: (*parser).callonNode176,
 																						expr: &charClassMatcher{
-																							pos:        position{line: 208, col: 5, offset: 4313},
+																							pos:        position{line: 225, col: 5, offset: 4694},
 																							val:        "[0-9]",
 																							ranges:     []rune{'0', '9'},
 																							ignoreCase: false,
@@ -951,10 +951,10 @@ var g = &grammar{
 																						},
 																					},
 																					&actionExpr{
-																						pos: position{line: 208, col: 5, offset: 4313},
+																						pos: position{line: 225, col: 5, offset: 4694},
 																						run: (*parser).callonNode178,
 																						expr: &charClassMatcher{
-																							pos:        position{line: 208, col: 5, offset: 4313},
+																							pos:        position{line: 225, col: 5, offset: 4694},
 																							val:        "[0-9]",
 																							ranges:     []rune{'0', '9'},
 																							ignoreCase: false,
@@ -965,22 +965,22 @@ var g = &grammar{
 																			},
 																		},
 																		&litMatcher{
-																			pos:        position{line: 184, col: 86, offset: 3938},
+																			pos:        position{line: 190, col: 86, offset: 4128},
 																			val:        ":",
 																			ignoreCase: false,
 																			want:       "\":\"",
 																		},
 																		&actionExpr{
-																			pos: position{line: 169, col: 5, offset: 3640},
+																			pos: position{line: 175, col: 5, offset: 3830},
 																			run: (*parser).callonNode181,
 																			expr: &seqExpr{
-																				pos: position{line: 169, col: 5, offset: 3640},
+																				pos: position{line: 175, col: 5, offset: 3830},
 																				exprs: []any{
 																					&actionExpr{
-																						pos: position{line: 208, col: 5, offset: 4313},
+																						pos: position{line: 225, col: 5, offset: 4694},
 																						run: (*parser).callonNode183,
 																						expr: &charClassMatcher{
-																							pos:        position{line: 208, col: 5, offset: 4313},
+																							pos:        position{line: 225, col: 5, offset: 4694},
 																							val:        "[0-9]",
 																							ranges:     []rune{'0', '9'},
 																							ignoreCase: false,
@@ -988,10 +988,10 @@ var g = &grammar{
 																						},
 																					},
 																					&actionExpr{
-																						pos: position{line: 208, col: 5, offset: 4313},
+																						pos: position{line: 225, col: 5, offset: 4694},
 																						run: (*parser).callonNode185,
 																						expr: &charClassMatcher{
-																							pos:        position{line: 208, col: 5, offset: 4313},
+																							pos:        position{line: 225, col: 5, offset: 4694},
 																							val:        "[0-9]",
 																							ranges:     []rune{'0', '9'},
 																							ignoreCase: false,
@@ -1024,21 +1024,21 @@ var g = &grammar{
 						},
 					},
 					&actionExpr{
-						pos: position{line: 67, col: 5, offset: 1579},
+						pos: position{line: 65, col: 5, offset: 1549},
 						run: (*parser).callonNode189,
 						expr: &seqExpr{
-							pos: position{line: 67, col: 5, offset: 1579},
+							pos: position{line: 65, col: 5, offset: 1549},
 							exprs: []any{
 								&labeledExpr{
-									pos:   position{line: 67, col: 5, offset: 1579},
+									pos:   position{line: 65, col: 5, offset: 1549},
 									label: "k",
 									expr: &oneOrMoreExpr{
-										pos: position{line: 67, col: 7, offset: 1581},
+										pos: position{line: 65, col: 7, offset: 1551},
 										expr: &actionExpr{
-											pos: position{line: 198, col: 5, offset: 4194},
+											pos: position{line: 215, col: 5, offset: 4575},
 											run: (*parser).callonNode193,
 											expr: &charClassMatcher{
-												pos:        position{line: 198, col: 5, offset: 4194},
+												pos:        position{line: 215, col: 5, offset: 4575},
 												val:        "[A-Za-z]",
 												ranges:     []rune{'A', 'Z', 'a', 'z'},
 												ignoreCase: false,
@@ -1048,23 +1048,148 @@ var g = &grammar{
 									},
 								},
 								&choiceExpr{
-									pos: position{line: 67, col: 14, offset: 1588},
+									pos: position{line: 66, col: 9, offset: 1567},
 									alternatives: []any{
 										&actionExpr{
-											pos: position{line: 114, col: 5, offset: 2721},
+											pos: position{line: 125, col: 5, offset: 2997},
 											run: (*parser).callonNode196,
 											expr: &litMatcher{
-												pos:        position{line: 114, col: 5, offset: 2721},
+												pos:        position{line: 125, col: 5, offset: 2997},
+												val:        "=",
+												ignoreCase: false,
+												want:       "\"=\"",
+											},
+										},
+										&actionExpr{
+											pos: position{line: 120, col: 5, offset: 2911},
+											run: (*parser).callonNode198,
+											expr: &litMatcher{
+												pos:        position{line: 120, col: 5, offset: 2911},
+												val:        ":",
+												ignoreCase: false,
+												want:       "\":\"",
+											},
+										},
+									},
+								},
+								&zeroOrOneExpr{
+									pos: position{line: 68, col: 7, offset: 1619},
+									expr: &litMatcher{
+										pos:        position{line: 68, col: 7, offset: 1619},
+										val:        "\"",
+										ignoreCase: false,
+										want:       "\"\\\"\"",
+									},
+								},
+								&labeledExpr{
+									pos:   position{line: 68, col: 12, offset: 1624},
+									label: "v",
+									expr: &choiceExpr{
+										pos: position{line: 200, col: 5, offset: 4285},
+										alternatives: []any{
+											&litMatcher{
+												pos:        position{line: 200, col: 5, offset: 4285},
+												val:        "today",
+												ignoreCase: false,
+												want:       "\"today\"",
+											},
+											&litMatcher{
+												pos:        position{line: 201, col: 5, offset: 4299},
+												val:        "yesterday",
+												ignoreCase: false,
+												want:       "\"yesterday\"",
+											},
+											&litMatcher{
+												pos:        position{line: 202, col: 5, offset: 4317},
+												val:        "this week",
+												ignoreCase: false,
+												want:       "\"this week\"",
+											},
+											&litMatcher{
+												pos:        position{line: 203, col: 5, offset: 4335},
+												val:        "this month",
+												ignoreCase: false,
+												want:       "\"this month\"",
+											},
+											&litMatcher{
+												pos:        position{line: 204, col: 5, offset: 4354},
+												val:        "last month",
+												ignoreCase: false,
+												want:       "\"last month\"",
+											},
+											&litMatcher{
+												pos:        position{line: 205, col: 5, offset: 4373},
+												val:        "this year",
+												ignoreCase: false,
+												want:       "\"this year\"",
+											},
+											&actionExpr{
+												pos: position{line: 206, col: 5, offset: 4391},
+												run: (*parser).callonNode210,
+												expr: &litMatcher{
+													pos:        position{line: 206, col: 5, offset: 4391},
+													val:        "last year",
+													ignoreCase: false,
+													want:       "\"last year\"",
+												},
+											},
+										},
+									},
+								},
+								&zeroOrOneExpr{
+									pos: position{line: 68, col: 38, offset: 1650},
+									expr: &litMatcher{
+										pos:        position{line: 68, col: 38, offset: 1650},
+										val:        "\"",
+										ignoreCase: false,
+										want:       "\"\\\"\"",
+									},
+								},
+							},
+						},
+					},
+					&actionExpr{
+						pos: position{line: 73, col: 5, offset: 1769},
+						run: (*parser).callonNode214,
+						expr: &seqExpr{
+							pos: position{line: 73, col: 5, offset: 1769},
+							exprs: []any{
+								&labeledExpr{
+									pos:   position{line: 73, col: 5, offset: 1769},
+									label: "k",
+									expr: &oneOrMoreExpr{
+										pos: position{line: 73, col: 7, offset: 1771},
+										expr: &actionExpr{
+											pos: position{line: 215, col: 5, offset: 4575},
+											run: (*parser).callonNode218,
+											expr: &charClassMatcher{
+												pos:        position{line: 215, col: 5, offset: 4575},
+												val:        "[A-Za-z]",
+												ranges:     []rune{'A', 'Z', 'a', 'z'},
+												ignoreCase: false,
+												inverted:   false,
+											},
+										},
+									},
+								},
+								&choiceExpr{
+									pos: position{line: 73, col: 14, offset: 1778},
+									alternatives: []any{
+										&actionExpr{
+											pos: position{line: 120, col: 5, offset: 2911},
+											run: (*parser).callonNode221,
+											expr: &litMatcher{
+												pos:        position{line: 120, col: 5, offset: 2911},
 												val:        ":",
 												ignoreCase: false,
 												want:       "\":\"",
 											},
 										},
 										&actionExpr{
-											pos: position{line: 119, col: 5, offset: 2807},
-											run: (*parser).callonNode198,
+											pos: position{line: 125, col: 5, offset: 2997},
+											run: (*parser).callonNode223,
 											expr: &litMatcher{
-												pos:        position{line: 119, col: 5, offset: 2807},
+												pos:        position{line: 125, col: 5, offset: 2997},
 												val:        "=",
 												ignoreCase: false,
 												want:       "\"=\"",
@@ -1073,30 +1198,30 @@ var g = &grammar{
 									},
 								},
 								&labeledExpr{
-									pos:   position{line: 67, col: 53, offset: 1627},
+									pos:   position{line: 73, col: 53, offset: 1817},
 									label: "v",
 									expr: &choiceExpr{
-										pos: position{line: 67, col: 56, offset: 1630},
+										pos: position{line: 73, col: 56, offset: 1820},
 										alternatives: []any{
 											&actionExpr{
-												pos: position{line: 203, col: 5, offset: 4253},
-												run: (*parser).callonNode202,
+												pos: position{line: 220, col: 5, offset: 4634},
+												run: (*parser).callonNode227,
 												expr: &seqExpr{
-													pos: position{line: 203, col: 5, offset: 4253},
+													pos: position{line: 220, col: 5, offset: 4634},
 													exprs: []any{
 														&litMatcher{
-															pos:        position{line: 203, col: 5, offset: 4253},
+															pos:        position{line: 220, col: 5, offset: 4634},
 															val:        "\"",
 															ignoreCase: false,
 															want:       "\"\\\"\"",
 														},
 														&labeledExpr{
-															pos:   position{line: 203, col: 9, offset: 4257},
+															pos:   position{line: 220, col: 9, offset: 4638},
 															label: "v",
 															expr: &zeroOrMoreExpr{
-																pos: position{line: 203, col: 11, offset: 4259},
+																pos: position{line: 220, col: 11, offset: 4640},
 																expr: &charClassMatcher{
-																	pos:        position{line: 203, col: 11, offset: 4259},
+																	pos:        position{line: 220, col: 11, offset: 4640},
 																	val:        "[^\"]",
 																	chars:      []rune{'"'},
 																	ignoreCase: false,
@@ -1105,7 +1230,7 @@ var g = &grammar{
 															},
 														},
 														&litMatcher{
-															pos:        position{line: 203, col: 17, offset: 4265},
+															pos:        position{line: 220, col: 17, offset: 4646},
 															val:        "\"",
 															ignoreCase: false,
 															want:       "\"\\\"\"",
@@ -1114,9 +1239,9 @@ var g = &grammar{
 												},
 											},
 											&oneOrMoreExpr{
-												pos: position{line: 67, col: 65, offset: 1639},
+												pos: position{line: 73, col: 65, offset: 1829},
 												expr: &charClassMatcher{
-													pos:        position{line: 67, col: 65, offset: 1639},
+													pos:        position{line: 73, col: 65, offset: 1829},
 													val:        "[^ ()]",
 													chars:      []rune{' ', '(', ')'},
 													ignoreCase: false,
@@ -1130,19 +1255,19 @@ var g = &grammar{
 						},
 					},
 					&actionExpr{
-						pos: position{line: 99, col: 5, offset: 2431},
-						run: (*parser).callonNode211,
+						pos: position{line: 105, col: 5, offset: 2621},
+						run: (*parser).callonNode236,
 						expr: &choiceExpr{
-							pos: position{line: 99, col: 6, offset: 2432},
+							pos: position{line: 105, col: 6, offset: 2622},
 							alternatives: []any{
 								&litMatcher{
-									pos:        position{line: 99, col: 6, offset: 2432},
+									pos:        position{line: 105, col: 6, offset: 2622},
 									val:        "AND",
 									ignoreCase: false,
 									want:       "\"AND\"",
 								},
 								&litMatcher{
-									pos:        position{line: 99, col: 14, offset: 2440},
+									pos:        position{line: 105, col: 14, offset: 2630},
 									val:        "+",
 									ignoreCase: false,
 									want:       "\"+\"",
@@ -1151,19 +1276,19 @@ var g = &grammar{
 						},
 					},
 					&actionExpr{
-						pos: position{line: 104, col: 5, offset: 2532},
-						run: (*parser).callonNode215,
+						pos: position{line: 110, col: 5, offset: 2722},
+						run: (*parser).callonNode240,
 						expr: &choiceExpr{
-							pos: position{line: 104, col: 6, offset: 2533},
+							pos: position{line: 110, col: 6, offset: 2723},
 							alternatives: []any{
 								&litMatcher{
-									pos:        position{line: 104, col: 6, offset: 2533},
+									pos:        position{line: 110, col: 6, offset: 2723},
 									val:        "NOT",
 									ignoreCase: false,
 									want:       "\"NOT\"",
 								},
 								&litMatcher{
-									pos:        position{line: 104, col: 14, offset: 2541},
+									pos:        position{line: 110, col: 14, offset: 2731},
 									val:        "-",
 									ignoreCase: false,
 									want:       "\"-\"",
@@ -1172,28 +1297,28 @@ var g = &grammar{
 						},
 					},
 					&actionExpr{
-						pos: position{line: 109, col: 5, offset: 2632},
-						run: (*parser).callonNode219,
+						pos: position{line: 115, col: 5, offset: 2822},
+						run: (*parser).callonNode244,
 						expr: &litMatcher{
-							pos:        position{line: 109, col: 6, offset: 2633},
+							pos:        position{line: 115, col: 6, offset: 2823},
 							val:        "OR",
 							ignoreCase: false,
 							want:       "\"OR\"",
 						},
 					},
 					&actionExpr{
-						pos: position{line: 80, col: 6, offset: 1919},
-						run: (*parser).callonNode221,
+						pos: position{line: 86, col: 6, offset: 2109},
+						run: (*parser).callonNode246,
 						expr: &seqExpr{
-							pos: position{line: 80, col: 6, offset: 1919},
+							pos: position{line: 86, col: 6, offset: 2109},
 							exprs: []any{
 								&zeroOrOneExpr{
-									pos: position{line: 80, col: 6, offset: 1919},
+									pos: position{line: 86, col: 6, offset: 2109},
 									expr: &actionExpr{
-										pos: position{line: 114, col: 5, offset: 2721},
-										run: (*parser).callonNode224,
+										pos: position{line: 120, col: 5, offset: 2911},
+										run: (*parser).callonNode249,
 										expr: &litMatcher{
-											pos:        position{line: 114, col: 5, offset: 2721},
+											pos:        position{line: 120, col: 5, offset: 2911},
 											val:        ":",
 											ignoreCase: false,
 											want:       "\":\"",
@@ -1201,12 +1326,12 @@ var g = &grammar{
 									},
 								},
 								&actionExpr{
-									pos: position{line: 213, col: 5, offset: 4364},
-									run: (*parser).callonNode226,
+									pos: position{line: 230, col: 5, offset: 4745},
+									run: (*parser).callonNode251,
 									expr: &zeroOrMoreExpr{
-										pos: position{line: 213, col: 5, offset: 4364},
+										pos: position{line: 230, col: 5, offset: 4745},
 										expr: &charClassMatcher{
-											pos:        position{line: 213, col: 5, offset: 4364},
+											pos:        position{line: 230, col: 5, offset: 4745},
 											val:        "[ \\t]",
 											chars:      []rune{' ', '\t'},
 											ignoreCase: false,
@@ -1215,27 +1340,27 @@ var g = &grammar{
 									},
 								},
 								&labeledExpr{
-									pos:   position{line: 80, col: 27, offset: 1940},
+									pos:   position{line: 86, col: 27, offset: 2130},
 									label: "v",
 									expr: &actionExpr{
-										pos: position{line: 203, col: 5, offset: 4253},
-										run: (*parser).callonNode230,
+										pos: position{line: 220, col: 5, offset: 4634},
+										run: (*parser).callonNode255,
 										expr: &seqExpr{
-											pos: position{line: 203, col: 5, offset: 4253},
+											pos: position{line: 220, col: 5, offset: 4634},
 											exprs: []any{
 												&litMatcher{
-													pos:        position{line: 203, col: 5, offset: 4253},
+													pos:        position{line: 220, col: 5, offset: 4634},
 													val:        "\"",
 													ignoreCase: false,
 													want:       "\"\\\"\"",
 												},
 												&labeledExpr{
-													pos:   position{line: 203, col: 9, offset: 4257},
+													pos:   position{line: 220, col: 9, offset: 4638},
 													label: "v",
 													expr: &zeroOrMoreExpr{
-														pos: position{line: 203, col: 11, offset: 4259},
+														pos: position{line: 220, col: 11, offset: 4640},
 														expr: &charClassMatcher{
-															pos:        position{line: 203, col: 11, offset: 4259},
+															pos:        position{line: 220, col: 11, offset: 4640},
 															val:        "[^\"]",
 															chars:      []rune{'"'},
 															ignoreCase: false,
@@ -1244,7 +1369,7 @@ var g = &grammar{
 													},
 												},
 												&litMatcher{
-													pos:        position{line: 203, col: 17, offset: 4265},
+													pos:        position{line: 220, col: 17, offset: 4646},
 													val:        "\"",
 													ignoreCase: false,
 													want:       "\"\\\"\"",
@@ -1254,12 +1379,12 @@ var g = &grammar{
 									},
 								},
 								&actionExpr{
-									pos: position{line: 213, col: 5, offset: 4364},
-									run: (*parser).callonNode237,
+									pos: position{line: 230, col: 5, offset: 4745},
+									run: (*parser).callonNode262,
 									expr: &zeroOrMoreExpr{
-										pos: position{line: 213, col: 5, offset: 4364},
+										pos: position{line: 230, col: 5, offset: 4745},
 										expr: &charClassMatcher{
-											pos:        position{line: 213, col: 5, offset: 4364},
+											pos:        position{line: 230, col: 5, offset: 4745},
 											val:        "[ \\t]",
 											chars:      []rune{' ', '\t'},
 											ignoreCase: false,
@@ -1268,12 +1393,12 @@ var g = &grammar{
 									},
 								},
 								&zeroOrOneExpr{
-									pos: position{line: 80, col: 38, offset: 1951},
+									pos: position{line: 86, col: 38, offset: 2141},
 									expr: &actionExpr{
-										pos: position{line: 114, col: 5, offset: 2721},
-										run: (*parser).callonNode241,
+										pos: position{line: 120, col: 5, offset: 2911},
+										run: (*parser).callonNode266,
 										expr: &litMatcher{
-											pos:        position{line: 114, col: 5, offset: 2721},
+											pos:        position{line: 120, col: 5, offset: 2911},
 											val:        ":",
 											ignoreCase: false,
 											want:       "\":\"",
@@ -1284,18 +1409,18 @@ var g = &grammar{
 						},
 					},
 					&actionExpr{
-						pos: position{line: 85, col: 6, offset: 2049},
-						run: (*parser).callonNode243,
+						pos: position{line: 91, col: 6, offset: 2239},
+						run: (*parser).callonNode268,
 						expr: &seqExpr{
-							pos: position{line: 85, col: 6, offset: 2049},
+							pos: position{line: 91, col: 6, offset: 2239},
 							exprs: []any{
 								&zeroOrOneExpr{
-									pos: position{line: 85, col: 6, offset: 2049},
+									pos: position{line: 91, col: 6, offset: 2239},
 									expr: &actionExpr{
-										pos: position{line: 114, col: 5, offset: 2721},
-										run: (*parser).callonNode246,
+										pos: position{line: 120, col: 5, offset: 2911},
+										run: (*parser).callonNode271,
 										expr: &litMatcher{
-											pos:        position{line: 114, col: 5, offset: 2721},
+											pos:        position{line: 120, col: 5, offset: 2911},
 											val:        ":",
 											ignoreCase: false,
 											want:       "\":\"",
@@ -1303,12 +1428,12 @@ var g = &grammar{
 									},
 								},
 								&actionExpr{
-									pos: position{line: 213, col: 5, offset: 4364},
-									run: (*parser).callonNode248,
+									pos: position{line: 230, col: 5, offset: 4745},
+									run: (*parser).callonNode273,
 									expr: &zeroOrMoreExpr{
-										pos: position{line: 213, col: 5, offset: 4364},
+										pos: position{line: 230, col: 5, offset: 4745},
 										expr: &charClassMatcher{
-											pos:        position{line: 213, col: 5, offset: 4364},
+											pos:        position{line: 230, col: 5, offset: 4745},
 											val:        "[ \\t]",
 											chars:      []rune{' ', '\t'},
 											ignoreCase: false,
@@ -1317,12 +1442,12 @@ var g = &grammar{
 									},
 								},
 								&labeledExpr{
-									pos:   position{line: 85, col: 27, offset: 2070},
+									pos:   position{line: 91, col: 27, offset: 2260},
 									label: "v",
 									expr: &oneOrMoreExpr{
-										pos: position{line: 85, col: 29, offset: 2072},
+										pos: position{line: 91, col: 29, offset: 2262},
 										expr: &charClassMatcher{
-											pos:        position{line: 85, col: 29, offset: 2072},
+											pos:        position{line: 91, col: 29, offset: 2262},
 											val:        "[^ :()]",
 											chars:      []rune{' ', ':', '(', ')'},
 											ignoreCase: false,
@@ -1331,12 +1456,12 @@ var g = &grammar{
 									},
 								},
 								&actionExpr{
-									pos: position{line: 213, col: 5, offset: 4364},
-									run: (*parser).callonNode254,
+									pos: position{line: 230, col: 5, offset: 4745},
+									run: (*parser).callonNode279,
 									expr: &zeroOrMoreExpr{
-										pos: position{line: 213, col: 5, offset: 4364},
+										pos: position{line: 230, col: 5, offset: 4745},
 										expr: &charClassMatcher{
-											pos:        position{line: 213, col: 5, offset: 4364},
+											pos:        position{line: 230, col: 5, offset: 4745},
 											val:        "[ \\t]",
 											chars:      []rune{' ', '\t'},
 											ignoreCase: false,
@@ -1345,12 +1470,12 @@ var g = &grammar{
 									},
 								},
 								&zeroOrOneExpr{
-									pos: position{line: 85, col: 40, offset: 2083},
+									pos: position{line: 91, col: 40, offset: 2273},
 									expr: &actionExpr{
-										pos: position{line: 114, col: 5, offset: 2721},
-										run: (*parser).callonNode258,
+										pos: position{line: 120, col: 5, offset: 2911},
+										run: (*parser).callonNode283,
 										expr: &litMatcher{
-											pos:        position{line: 114, col: 5, offset: 2721},
+											pos:        position{line: 120, col: 5, offset: 2911},
 											val:        ":",
 											ignoreCase: false,
 											want:       "\":\"",
@@ -1380,10 +1505,10 @@ var g = &grammar{
 								expr: &oneOrMoreExpr{
 									pos: position{line: 32, col: 8, offset: 615},
 									expr: &actionExpr{
-										pos: position{line: 198, col: 5, offset: 4194},
+										pos: position{line: 215, col: 5, offset: 4575},
 										run: (*parser).callonGroupNode6,
 										expr: &charClassMatcher{
-											pos:        position{line: 198, col: 5, offset: 4194},
+											pos:        position{line: 215, col: 5, offset: 4575},
 											val:        "[A-Za-z]",
 											ranges:     []rune{'A', 'Z', 'a', 'z'},
 											ignoreCase: false,
@@ -1399,20 +1524,20 @@ var g = &grammar{
 								pos: position{line: 32, col: 17, offset: 624},
 								alternatives: []any{
 									&actionExpr{
-										pos: position{line: 114, col: 5, offset: 2721},
+										pos: position{line: 120, col: 5, offset: 2911},
 										run: (*parser).callonGroupNode10,
 										expr: &litMatcher{
-											pos:        position{line: 114, col: 5, offset: 2721},
+											pos:        position{line: 120, col: 5, offset: 2911},
 											val:        ":",
 											ignoreCase: false,
 											want:       "\":\"",
 										},
 									},
 									&actionExpr{
-										pos: position{line: 119, col: 5, offset: 2807},
+										pos: position{line: 125, col: 5, offset: 2997},
 										run: (*parser).callonGroupNode12,
 										expr: &litMatcher{
-											pos:        position{line: 119, col: 5, offset: 2807},
+											pos:        position{line: 125, col: 5, offset: 2997},
 											val:        "=",
 											ignoreCase: false,
 											want:       "\"=\"",
@@ -2284,19 +2409,19 @@ func (p *parser) callonNode198() (any, error) {
 	return p.cur.onNode198()
 }
 
-func (c *current) onNode202(v any) (any, error) {
-	return v, nil
+func (c *current) onNode210() (any, error) {
+	return c.text, nil
 
 }
 
-func (p *parser) callonNode202() (any, error) {
+func (p *parser) callonNode210() (any, error) {
 	stack := p.vstack[len(p.vstack)-1]
 	_ = stack
-	return p.cur.onNode202(stack["v"])
+	return p.cur.onNode210()
 }
 
 func (c *current) onNode189(k, v any) (any, error) {
-	return buildStringNode(k, v, c.text, c.pos)
+	return buildNaturalLanguageDateTimeNodes(k, v, c.text, c.pos)
 
 }
 
@@ -2306,158 +2431,213 @@ func (p *parser) callonNode189() (any, error) {
 	return p.cur.onNode189(stack["k"], stack["v"])
 }
 
-func (c *current) onNode211() (any, error) {
+func (c *current) onNode218() (any, error) {
+	return c.text, nil
+
+}
+
+func (p *parser) callonNode218() (any, error) {
+	stack := p.vstack[len(p.vstack)-1]
+	_ = stack
+	return p.cur.onNode218()
+}
+
+func (c *current) onNode221() (any, error) {
 	return buildOperatorNode(c.text, c.pos)
 
 }
 
-func (p *parser) callonNode211() (any, error) {
+func (p *parser) callonNode221() (any, error) {
 	stack := p.vstack[len(p.vstack)-1]
 	_ = stack
-	return p.cur.onNode211()
+	return p.cur.onNode221()
 }
 
-func (c *current) onNode215() (any, error) {
+func (c *current) onNode223() (any, error) {
 	return buildOperatorNode(c.text, c.pos)
 
 }
 
-func (p *parser) callonNode215() (any, error) {
+func (p *parser) callonNode223() (any, error) {
 	stack := p.vstack[len(p.vstack)-1]
 	_ = stack
-	return p.cur.onNode215()
+	return p.cur.onNode223()
 }
 
-func (c *current) onNode219() (any, error) {
+func (c *current) onNode227(v any) (any, error) {
+	return v, nil
+
+}
+
+func (p *parser) callonNode227() (any, error) {
+	stack := p.vstack[len(p.vstack)-1]
+	_ = stack
+	return p.cur.onNode227(stack["v"])
+}
+
+func (c *current) onNode214(k, v any) (any, error) {
+	return buildStringNode(k, v, c.text, c.pos)
+
+}
+
+func (p *parser) callonNode214() (any, error) {
+	stack := p.vstack[len(p.vstack)-1]
+	_ = stack
+	return p.cur.onNode214(stack["k"], stack["v"])
+}
+
+func (c *current) onNode236() (any, error) {
+	return buildOperatorNode(c.text, c.pos)
+
+}
+
+func (p *parser) callonNode236() (any, error) {
+	stack := p.vstack[len(p.vstack)-1]
+	_ = stack
+	return p.cur.onNode236()
+}
+
+func (c *current) onNode240() (any, error) {
+	return buildOperatorNode(c.text, c.pos)
+
+}
+
+func (p *parser) callonNode240() (any, error) {
+	stack := p.vstack[len(p.vstack)-1]
+	_ = stack
+	return p.cur.onNode240()
+}
+
+func (c *current) onNode244() (any, error) {
 	return buildOperatorNode(c.text, c.pos)
 
 }
 
-func (p *parser) callonNode219() (any, error) {
+func (p *parser) callonNode244() (any, error) {
 	stack := p.vstack[len(p.vstack)-1]
 	_ = stack
-	return p.cur.onNode219()
+	return p.cur.onNode244()
 }
 
-func (c *current) onNode224() (any, error) {
+func (c *current) onNode249() (any, error) {
 	return buildOperatorNode(c.text, c.pos)
 
 }
 
-func (p *parser) callonNode224() (any, error) {
+func (p *parser) callonNode249() (any, error) {
 	stack := p.vstack[len(p.vstack)-1]
 	_ = stack
-	return p.cur.onNode224()
+	return p.cur.onNode249()
 }
 
-func (c *current) onNode226() (any, error) {
+func (c *current) onNode251() (any, error) {
 	return nil, nil
 
 }
 
-func (p *parser) callonNode226() (any, error) {
+func (p *parser) callonNode251() (any, error) {
 	stack := p.vstack[len(p.vstack)-1]
 	_ = stack
-	return p.cur.onNode226()
+	return p.cur.onNode251()
 }
 
-func (c *current) onNode230(v any) (any, error) {
+func (c *current) onNode255(v any) (any, error) {
 	return v, nil
 
 }
 
-func (p *parser) callonNode230() (any, error) {
+func (p *parser) callonNode255() (any, error) {
 	stack := p.vstack[len(p.vstack)-1]
 	_ = stack
-	return p.cur.onNode230(stack["v"])
+	return p.cur.onNode255(stack["v"])
 }
 
-func (c *current) onNode237(v any) (any, error) {
+func (c *current) onNode262(v any) (any, error) {
 	return nil, nil
 
 }
 
-func (p *parser) callonNode237() (any, error) {
+func (p *parser) callonNode262() (any, error) {
 	stack := p.vstack[len(p.vstack)-1]
 	_ = stack
-	return p.cur.onNode237(stack["v"])
+	return p.cur.onNode262(stack["v"])
 }
 
-func (c *current) onNode241() (any, error) {
+func (c *current) onNode266() (any, error) {
 	return buildOperatorNode(c.text, c.pos)
 
 }
 
-func (p *parser) callonNode241() (any, error) {
+func (p *parser) callonNode266() (any, error) {
 	stack := p.vstack[len(p.vstack)-1]
 	_ = stack
-	return p.cur.onNode241()
+	return p.cur.onNode266()
 }
 
-func (c *current) onNode221(v any) (any, error) {
+func (c *current) onNode246(v any) (any, error) {
 	return buildStringNode("", v, c.text, c.pos)
 
 }
 
-func (p *parser) callonNode221() (any, error) {
+func (p *parser) callonNode246() (any, error) {
 	stack := p.vstack[len(p.vstack)-1]
 	_ = stack
-	return p.cur.onNode221(stack["v"])
+	return p.cur.onNode246(stack["v"])
 }
 
-func (c *current) onNode246() (any, error) {
+func (c *current) onNode271() (any, error) {
 	return buildOperatorNode(c.text, c.pos)
 
 }
 
-func (p *parser) callonNode246() (any, error) {
+func (p *parser) callonNode271() (any, error) {
 	stack := p.vstack[len(p.vstack)-1]
 	_ = stack
-	return p.cur.onNode246()
+	return p.cur.onNode271()
 }
 
-func (c *current) onNode248() (any, error) {
+func (c *current) onNode273() (any, error) {
 	return nil, nil
 
 }
 
-func (p *parser) callonNode248() (any, error) {
+func (p *parser) callonNode273() (any, error) {
 	stack := p.vstack[len(p.vstack)-1]
 	_ = stack
-	return p.cur.onNode248()
+	return p.cur.onNode273()
 }
 
-func (c *current) onNode254(v any) (any, error) {
+func (c *current) onNode279(v any) (any, error) {
 	return nil, nil
 
 }
 
-func (p *parser) callonNode254() (any, error) {
+func (p *parser) callonNode279() (any, error) {
 	stack := p.vstack[len(p.vstack)-1]
 	_ = stack
-	return p.cur.onNode254(stack["v"])
+	return p.cur.onNode279(stack["v"])
 }
 
-func (c *current) onNode258() (any, error) {
+func (c *current) onNode283() (any, error) {
 	return buildOperatorNode(c.text, c.pos)
 
 }
 
-func (p *parser) callonNode258() (any, error) {
+func (p *parser) callonNode283() (any, error) {
 	stack := p.vstack[len(p.vstack)-1]
 	_ = stack
-	return p.cur.onNode258()
+	return p.cur.onNode283()
 }
 
-func (c *current) onNode243(v any) (any, error) {
+func (c *current) onNode268(v any) (any, error) {
 	return buildStringNode("", v, c.text, c.pos)
 
 }
 
-func (p *parser) callonNode243() (any, error) {
+func (p *parser) callonNode268() (any, error) {
 	stack := p.vstack[len(p.vstack)-1]
 	_ = stack
-	return p.cur.onNode243(stack["v"])
+	return p.cur.onNode268(stack["v"])
 }
 
 func (c *current) onGroupNode6() (any, error) {
diff --git a/services/search/pkg/query/kql/doc.go b/services/search/pkg/query/kql/doc.go
index a887affc32f..f578d171f2f 100644
--- a/services/search/pkg/query/kql/doc.go
+++ b/services/search/pkg/query/kql/doc.go
@@ -10,7 +10,6 @@ The following spec parts are supported and tested:
   - 2.1.8 OR Operator
   - 2.1.12 Parentheses
   - 2.3.5 Date Tokens
-  - Human tokens not implemented
   - 3.1.11 Implicit Operator
   - 3.1.12 Parentheses
   - 3.1.2 AND Operator
diff --git a/services/search/pkg/query/kql/error.go b/services/search/pkg/query/kql/error.go
index c12cbfe9d00..5f171d77fdb 100644
--- a/services/search/pkg/query/kql/error.go
+++ b/services/search/pkg/query/kql/error.go
@@ -28,3 +28,12 @@ func (e NamedGroupInvalidNodesError) Error() string {
 		ast.NodeValue(e.Node),
 	).Error()
 }
+
+// UnsupportedTimeRangeError records an error and the value that caused it.
+type UnsupportedTimeRangeError struct {
+	Value interface{}
+}
+
+func (e UnsupportedTimeRangeError) Error() string {
+	return fmt.Sprintf("unable to convert '%v' to a time range", e.Value)
+}
diff --git a/services/search/pkg/query/kql/factory.go b/services/search/pkg/query/kql/factory.go
index ff9044886a9..0eb858dd97a 100644
--- a/services/search/pkg/query/kql/factory.go
+++ b/services/search/pkg/query/kql/factory.go
@@ -101,6 +101,39 @@ func buildDateTimeNode(k, o, v interface{}, text []byte, pos position) (*ast.Dat
 		Value:    value,
 	}, nil
 }
+func buildNaturalLanguageDateTimeNodes(k, v interface{}, text []byte, pos position) ([]ast.Node, error) {
+	b, err := base(text, pos)
+	if err != nil {
+		return nil, err
+	}
+
+	key, err := toString(k)
+	if err != nil {
+		return nil, err
+	}
+
+	from, to, err := toTimeRange(v)
+	if err != nil {
+		return nil, err
+	}
+
+	return []ast.Node{
+		&ast.DateTimeNode{
+			Base:     b,
+			Value:    *from,
+			Key:      key,
+			Operator: &ast.OperatorNode{Value: ">="},
+		},
+		&ast.OperatorNode{Value: BoolAND},
+		&ast.DateTimeNode{
+			Base:     b,
+			Value:    *to,
+			Key:      key,
+			Operator: &ast.OperatorNode{Value: "<="},
+		},
+	}, nil
+
+}
 
 func buildBooleanNode(k, v interface{}, text []byte, pos position) (*ast.BooleanNode, error) {
 	b, err := base(text, pos)
diff --git a/services/search/pkg/query/kql/kql.go b/services/search/pkg/query/kql/kql.go
index 8719223b8d2..2cf03a7f066 100644
--- a/services/search/pkg/query/kql/kql.go
+++ b/services/search/pkg/query/kql/kql.go
@@ -3,6 +3,7 @@ package kql
 
 import (
 	"errors"
+	"time"
 
 	"github.com/owncloud/ocis/v2/services/search/pkg/query/ast"
 )
@@ -42,3 +43,7 @@ func (b Builder) Build(q string) (*ast.Ast, error) {
 
 	return f.(*ast.Ast), nil
 }
+
+// timeNow mirrors time.Now by default, the only reason why this exists
+// is to monkey patch it from the tests. See PatchTimeNow
+var timeNow = time.Now
diff --git a/vendor/github.com/araddon/dateparse/.travis.yml b/vendor/github.com/araddon/dateparse/.travis.yml
deleted file mode 100644
index 3b4b17777fb..00000000000
--- a/vendor/github.com/araddon/dateparse/.travis.yml
+++ /dev/null
@@ -1,13 +0,0 @@
-language: go
-
-go:
-  - 1.13.x
-
-before_install:
-  - go get -t -v ./...
-
-script:
-  - go test -race -coverprofile=coverage.txt -covermode=atomic
-
-after_success:
-  - bash <(curl -s https://codecov.io/bash)
diff --git a/vendor/github.com/araddon/dateparse/README.md b/vendor/github.com/araddon/dateparse/README.md
deleted file mode 100644
index fe682dd5652..00000000000
--- a/vendor/github.com/araddon/dateparse/README.md
+++ /dev/null
@@ -1,323 +0,0 @@
-Go Date Parser 
----------------------------
-
-Parse many date strings without knowing format in advance.  Uses a scanner to read bytes and use a state machine to find format.  Much faster than shotgun based parse methods.  See [bench_test.go](https://github.com/araddon/dateparse/blob/master/bench_test.go) for performance comparison.
-
-
-[![Code Coverage](https://codecov.io/gh/araddon/dateparse/branch/master/graph/badge.svg)](https://codecov.io/gh/araddon/dateparse)
-[![GoDoc](https://godoc.org/github.com/araddon/dateparse?status.svg)](http://godoc.org/github.com/araddon/dateparse)
-[![Build Status](https://travis-ci.org/araddon/dateparse.svg?branch=master)](https://travis-ci.org/araddon/dateparse)
-[![Go ReportCard](https://goreportcard.com/badge/araddon/dateparse)](https://goreportcard.com/report/araddon/dateparse)
-
-**MM/DD/YYYY VS DD/MM/YYYY** Right now this uses mm/dd/yyyy WHEN ambiguous if this is not desired behavior, use `ParseStrict` which will fail on ambiguous date strings.
-
-**Timezones** The location your server is configured affects the results!  See example or https://play.golang.org/p/IDHRalIyXh and last paragraph here https://golang.org/pkg/time/#Parse.
-
-
-```go
-
-// Normal parse.  Equivalent Timezone rules as time.Parse()
-t, err := dateparse.ParseAny("3/1/2014")
-
-// Parse Strict, error on ambigous mm/dd vs dd/mm dates
-t, err := dateparse.ParseStrict("3/1/2014")
-> returns error 
-
-// Return a string that represents the layout to parse the given date-time.
-layout, err := dateparse.ParseFormat("May 8, 2009 5:57:51 PM")
-> "Jan 2, 2006 3:04:05 PM"
-
-```
-
-cli tool for testing dateformats
-----------------------------------
-
-[Date Parse CLI](https://github.com/araddon/dateparse/blob/master/dateparse)
-
-
-Extended example
--------------------
-
-https://github.com/araddon/dateparse/blob/master/example/main.go
-
-```go
-package main
-
-import (
-	"flag"
-	"fmt"
-	"time"
-
-	"github.com/scylladb/termtables"
-	"github.com/araddon/dateparse"
-)
-
-var examples = []string{
-	"May 8, 2009 5:57:51 PM",
-	"oct 7, 1970",
-	"oct 7, '70",
-	"oct. 7, 1970",
-	"oct. 7, 70",
-	"Mon Jan  2 15:04:05 2006",
-	"Mon Jan  2 15:04:05 MST 2006",
-	"Mon Jan 02 15:04:05 -0700 2006",
-	"Monday, 02-Jan-06 15:04:05 MST",
-	"Mon, 02 Jan 2006 15:04:05 MST",
-	"Tue, 11 Jul 2017 16:28:13 +0200 (CEST)",
-	"Mon, 02 Jan 2006 15:04:05 -0700",
-	"Mon 30 Sep 2018 09:09:09 PM UTC",
-	"Mon Aug 10 15:44:11 UTC+0100 2015",
-	"Thu, 4 Jan 2018 17:53:36 +0000",
-	"Fri Jul 03 2015 18:04:07 GMT+0100 (GMT Daylight Time)",
-	"Sun, 3 Jan 2021 00:12:23 +0800 (GMT+08:00)",
-	"September 17, 2012 10:09am",
-	"September 17, 2012 at 10:09am PST-08",
-	"September 17, 2012, 10:10:09",
-	"October 7, 1970",
-	"October 7th, 1970",
-	"12 Feb 2006, 19:17",
-	"12 Feb 2006 19:17",
-	"14 May 2019 19:11:40.164",
-	"7 oct 70",
-	"7 oct 1970",
-	"03 February 2013",
-	"1 July 2013",
-	"2013-Feb-03",
-	// dd/Mon/yyy  alpha Months
-	"06/Jan/2008:15:04:05 -0700",
-	"06/Jan/2008 15:04:05 -0700",
-	//   mm/dd/yy
-	"3/31/2014",
-	"03/31/2014",
-	"08/21/71",
-	"8/1/71",
-	"4/8/2014 22:05",
-	"04/08/2014 22:05",
-	"4/8/14 22:05",
-	"04/2/2014 03:00:51",
-	"8/8/1965 12:00:00 AM",
-	"8/8/1965 01:00:01 PM",
-	"8/8/1965 01:00 PM",
-	"8/8/1965 1:00 PM",
-	"8/8/1965 12:00 AM",
-	"4/02/2014 03:00:51",
-	"03/19/2012 10:11:59",
-	"03/19/2012 10:11:59.3186369",
-	// yyyy/mm/dd
-	"2014/3/31",
-	"2014/03/31",
-	"2014/4/8 22:05",
-	"2014/04/08 22:05",
-	"2014/04/2 03:00:51",
-	"2014/4/02 03:00:51",
-	"2012/03/19 10:11:59",
-	"2012/03/19 10:11:59.3186369",
-	// yyyy:mm:dd
-	"2014:3:31",
-	"2014:03:31",
-	"2014:4:8 22:05",
-	"2014:04:08 22:05",
-	"2014:04:2 03:00:51",
-	"2014:4:02 03:00:51",
-	"2012:03:19 10:11:59",
-	"2012:03:19 10:11:59.3186369",
-	// Chinese
-	"2014年04月08日",
-	//   yyyy-mm-ddThh
-	"2006-01-02T15:04:05+0000",
-	"2009-08-12T22:15:09-07:00",
-	"2009-08-12T22:15:09",
-	"2009-08-12T22:15:09.988",
-	"2009-08-12T22:15:09Z",
-	"2017-07-19T03:21:51:897+0100",
-	"2019-05-29T08:41-04", // no seconds, 2 digit TZ offset
-	//   yyyy-mm-dd hh:mm:ss
-	"2014-04-26 17:24:37.3186369",
-	"2012-08-03 18:31:59.257000000",
-	"2014-04-26 17:24:37.123",
-	"2013-04-01 22:43",
-	"2013-04-01 22:43:22",
-	"2014-12-16 06:20:00 UTC",
-	"2014-12-16 06:20:00 GMT",
-	"2014-04-26 05:24:37 PM",
-	"2014-04-26 13:13:43 +0800",
-	"2014-04-26 13:13:43 +0800 +08",
-	"2014-04-26 13:13:44 +09:00",
-	"2012-08-03 18:31:59.257000000 +0000 UTC",
-	"2015-09-30 18:48:56.35272715 +0000 UTC",
-	"2015-02-18 00:12:00 +0000 GMT",
-	"2015-02-18 00:12:00 +0000 UTC",
-	"2015-02-08 03:02:00 +0300 MSK m=+0.000000001",
-	"2015-02-08 03:02:00.001 +0300 MSK m=+0.000000001",
-	"2017-07-19 03:21:51+00:00",
-	"2014-04-26",
-	"2014-04",
-	"2014",
-	"2014-05-11 08:20:13,787",
-	// yyyy-mm-dd-07:00
-	"2020-07-20+08:00",
-	// mm.dd.yy
-	"3.31.2014",
-	"03.31.2014",
-	"08.21.71",
-	"2014.03",
-	"2014.03.30",
-	//  yyyymmdd and similar
-	"20140601",
-	"20140722105203",
-	// yymmdd hh:mm:yy  mysql log
-	// 080313 05:21:55 mysqld started
-	"171113 14:14:20",
-	// unix seconds, ms, micro, nano
-	"1332151919",
-	"1384216367189",
-	"1384216367111222",
-	"1384216367111222333",
-}
-
-var (
-	timezone = ""
-)
-
-func main() {
-	flag.StringVar(&timezone, "timezone", "UTC", "Timezone aka `America/Los_Angeles` formatted time-zone")
-	flag.Parse()
-
-	if timezone != "" {
-		// NOTE:  This is very, very important to understand
-		// time-parsing in go
-		loc, err := time.LoadLocation(timezone)
-		if err != nil {
-			panic(err.Error())
-		}
-		time.Local = loc
-	}
-
-	table := termtables.CreateTable()
-
-	table.AddHeaders("Input", "Parsed, and Output as %v")
-	for _, dateExample := range examples {
-		t, err := dateparse.ParseLocal(dateExample)
-		if err != nil {
-			panic(err.Error())
-		}
-		table.AddRow(dateExample, fmt.Sprintf("%v", t))
-	}
-	fmt.Println(table.Render())
-}
-
-/*
-+-------------------------------------------------------+-----------------------------------------+
-| Input                                                 | Parsed, and Output as %v                |
-+-------------------------------------------------------+-----------------------------------------+
-| May 8, 2009 5:57:51 PM                                | 2009-05-08 17:57:51 +0000 UTC           |
-| oct 7, 1970                                           | 1970-10-07 00:00:00 +0000 UTC           |
-| oct 7, '70                                            | 1970-10-07 00:00:00 +0000 UTC           |
-| oct. 7, 1970                                          | 1970-10-07 00:00:00 +0000 UTC           |
-| oct. 7, 70                                            | 1970-10-07 00:00:00 +0000 UTC           |
-| Mon Jan  2 15:04:05 2006                              | 2006-01-02 15:04:05 +0000 UTC           |
-| Mon Jan  2 15:04:05 MST 2006                          | 2006-01-02 15:04:05 +0000 MST           |
-| Mon Jan 02 15:04:05 -0700 2006                        | 2006-01-02 15:04:05 -0700 -0700         |
-| Monday, 02-Jan-06 15:04:05 MST                        | 2006-01-02 15:04:05 +0000 MST           |
-| Mon, 02 Jan 2006 15:04:05 MST                         | 2006-01-02 15:04:05 +0000 MST           |
-| Tue, 11 Jul 2017 16:28:13 +0200 (CEST)                | 2017-07-11 16:28:13 +0200 +0200         |
-| Mon, 02 Jan 2006 15:04:05 -0700                       | 2006-01-02 15:04:05 -0700 -0700         |
-| Mon 30 Sep 2018 09:09:09 PM UTC                       | 2018-09-30 21:09:09 +0000 UTC           |
-| Mon Aug 10 15:44:11 UTC+0100 2015                     | 2015-08-10 15:44:11 +0000 UTC           |
-| Thu, 4 Jan 2018 17:53:36 +0000                        | 2018-01-04 17:53:36 +0000 UTC           |
-| Fri Jul 03 2015 18:04:07 GMT+0100 (GMT Daylight Time) | 2015-07-03 18:04:07 +0100 GMT           |
-| Sun, 3 Jan 2021 00:12:23 +0800 (GMT+08:00)            | 2021-01-03 00:12:23 +0800 +0800         |
-| September 17, 2012 10:09am                            | 2012-09-17 10:09:00 +0000 UTC           |
-| September 17, 2012 at 10:09am PST-08                  | 2012-09-17 10:09:00 -0800 PST           |
-| September 17, 2012, 10:10:09                          | 2012-09-17 10:10:09 +0000 UTC           |
-| October 7, 1970                                       | 1970-10-07 00:00:00 +0000 UTC           |
-| October 7th, 1970                                     | 1970-10-07 00:00:00 +0000 UTC           |
-| 12 Feb 2006, 19:17                                    | 2006-02-12 19:17:00 +0000 UTC           |
-| 12 Feb 2006 19:17                                     | 2006-02-12 19:17:00 +0000 UTC           |
-| 14 May 2019 19:11:40.164                              | 2019-05-14 19:11:40.164 +0000 UTC       |
-| 7 oct 70                                              | 1970-10-07 00:00:00 +0000 UTC           |
-| 7 oct 1970                                            | 1970-10-07 00:00:00 +0000 UTC           |
-| 03 February 2013                                      | 2013-02-03 00:00:00 +0000 UTC           |
-| 1 July 2013                                           | 2013-07-01 00:00:00 +0000 UTC           |
-| 2013-Feb-03                                           | 2013-02-03 00:00:00 +0000 UTC           |
-| 06/Jan/2008:15:04:05 -0700                            | 2008-01-06 15:04:05 -0700 -0700         |
-| 06/Jan/2008 15:04:05 -0700                            | 2008-01-06 15:04:05 -0700 -0700         |
-| 3/31/2014                                             | 2014-03-31 00:00:00 +0000 UTC           |
-| 03/31/2014                                            | 2014-03-31 00:00:00 +0000 UTC           |
-| 08/21/71                                              | 1971-08-21 00:00:00 +0000 UTC           |
-| 8/1/71                                                | 1971-08-01 00:00:00 +0000 UTC           |
-| 4/8/2014 22:05                                        | 2014-04-08 22:05:00 +0000 UTC           |
-| 04/08/2014 22:05                                      | 2014-04-08 22:05:00 +0000 UTC           |
-| 4/8/14 22:05                                          | 2014-04-08 22:05:00 +0000 UTC           |
-| 04/2/2014 03:00:51                                    | 2014-04-02 03:00:51 +0000 UTC           |
-| 8/8/1965 12:00:00 AM                                  | 1965-08-08 00:00:00 +0000 UTC           |
-| 8/8/1965 01:00:01 PM                                  | 1965-08-08 13:00:01 +0000 UTC           |
-| 8/8/1965 01:00 PM                                     | 1965-08-08 13:00:00 +0000 UTC           |
-| 8/8/1965 1:00 PM                                      | 1965-08-08 13:00:00 +0000 UTC           |
-| 8/8/1965 12:00 AM                                     | 1965-08-08 00:00:00 +0000 UTC           |
-| 4/02/2014 03:00:51                                    | 2014-04-02 03:00:51 +0000 UTC           |
-| 03/19/2012 10:11:59                                   | 2012-03-19 10:11:59 +0000 UTC           |
-| 03/19/2012 10:11:59.3186369                           | 2012-03-19 10:11:59.3186369 +0000 UTC   |
-| 2014/3/31                                             | 2014-03-31 00:00:00 +0000 UTC           |
-| 2014/03/31                                            | 2014-03-31 00:00:00 +0000 UTC           |
-| 2014/4/8 22:05                                        | 2014-04-08 22:05:00 +0000 UTC           |
-| 2014/04/08 22:05                                      | 2014-04-08 22:05:00 +0000 UTC           |
-| 2014/04/2 03:00:51                                    | 2014-04-02 03:00:51 +0000 UTC           |
-| 2014/4/02 03:00:51                                    | 2014-04-02 03:00:51 +0000 UTC           |
-| 2012/03/19 10:11:59                                   | 2012-03-19 10:11:59 +0000 UTC           |
-| 2012/03/19 10:11:59.3186369                           | 2012-03-19 10:11:59.3186369 +0000 UTC   |
-| 2014:3:31                                             | 2014-03-31 00:00:00 +0000 UTC           |
-| 2014:03:31                                            | 2014-03-31 00:00:00 +0000 UTC           |
-| 2014:4:8 22:05                                        | 2014-04-08 22:05:00 +0000 UTC           |
-| 2014:04:08 22:05                                      | 2014-04-08 22:05:00 +0000 UTC           |
-| 2014:04:2 03:00:51                                    | 2014-04-02 03:00:51 +0000 UTC           |
-| 2014:4:02 03:00:51                                    | 2014-04-02 03:00:51 +0000 UTC           |
-| 2012:03:19 10:11:59                                   | 2012-03-19 10:11:59 +0000 UTC           |
-| 2012:03:19 10:11:59.3186369                           | 2012-03-19 10:11:59.3186369 +0000 UTC   |
-| 2014年04月08日                                        | 2014-04-08 00:00:00 +0000 UTC           |
-| 2006-01-02T15:04:05+0000                              | 2006-01-02 15:04:05 +0000 UTC           |
-| 2009-08-12T22:15:09-07:00                             | 2009-08-12 22:15:09 -0700 -0700         |
-| 2009-08-12T22:15:09                                   | 2009-08-12 22:15:09 +0000 UTC           |
-| 2009-08-12T22:15:09.988                               | 2009-08-12 22:15:09.988 +0000 UTC       |
-| 2009-08-12T22:15:09Z                                  | 2009-08-12 22:15:09 +0000 UTC           |
-| 2017-07-19T03:21:51:897+0100                          | 2017-07-19 03:21:51.897 +0100 +0100     |
-| 2019-05-29T08:41-04                                   | 2019-05-29 08:41:00 -0400 -0400         |
-| 2014-04-26 17:24:37.3186369                           | 2014-04-26 17:24:37.3186369 +0000 UTC   |
-| 2012-08-03 18:31:59.257000000                         | 2012-08-03 18:31:59.257 +0000 UTC       |
-| 2014-04-26 17:24:37.123                               | 2014-04-26 17:24:37.123 +0000 UTC       |
-| 2013-04-01 22:43                                      | 2013-04-01 22:43:00 +0000 UTC           |
-| 2013-04-01 22:43:22                                   | 2013-04-01 22:43:22 +0000 UTC           |
-| 2014-12-16 06:20:00 UTC                               | 2014-12-16 06:20:00 +0000 UTC           |
-| 2014-12-16 06:20:00 GMT                               | 2014-12-16 06:20:00 +0000 UTC           |
-| 2014-04-26 05:24:37 PM                                | 2014-04-26 17:24:37 +0000 UTC           |
-| 2014-04-26 13:13:43 +0800                             | 2014-04-26 13:13:43 +0800 +0800         |
-| 2014-04-26 13:13:43 +0800 +08                         | 2014-04-26 13:13:43 +0800 +0800         |
-| 2014-04-26 13:13:44 +09:00                            | 2014-04-26 13:13:44 +0900 +0900         |
-| 2012-08-03 18:31:59.257000000 +0000 UTC               | 2012-08-03 18:31:59.257 +0000 UTC       |
-| 2015-09-30 18:48:56.35272715 +0000 UTC                | 2015-09-30 18:48:56.35272715 +0000 UTC  |
-| 2015-02-18 00:12:00 +0000 GMT                         | 2015-02-18 00:12:00 +0000 UTC           |
-| 2015-02-18 00:12:00 +0000 UTC                         | 2015-02-18 00:12:00 +0000 UTC           |
-| 2015-02-08 03:02:00 +0300 MSK m=+0.000000001          | 2015-02-08 03:02:00 +0300 +0300         |
-| 2015-02-08 03:02:00.001 +0300 MSK m=+0.000000001      | 2015-02-08 03:02:00.001 +0300 +0300     |
-| 2017-07-19 03:21:51+00:00                             | 2017-07-19 03:21:51 +0000 UTC           |
-| 2014-04-26                                            | 2014-04-26 00:00:00 +0000 UTC           |
-| 2014-04                                               | 2014-04-01 00:00:00 +0000 UTC           |
-| 2014                                                  | 2014-01-01 00:00:00 +0000 UTC           |
-| 2014-05-11 08:20:13,787                               | 2014-05-11 08:20:13.787 +0000 UTC       |
-| 2020-07-20+08:00                                      | 2020-07-20 00:00:00 +0800 +0800         |
-| 3.31.2014                                             | 2014-03-31 00:00:00 +0000 UTC           |
-| 03.31.2014                                            | 2014-03-31 00:00:00 +0000 UTC           |
-| 08.21.71                                              | 1971-08-21 00:00:00 +0000 UTC           |
-| 2014.03                                               | 2014-03-01 00:00:00 +0000 UTC           |
-| 2014.03.30                                            | 2014-03-30 00:00:00 +0000 UTC           |
-| 20140601                                              | 2014-06-01 00:00:00 +0000 UTC           |
-| 20140722105203                                        | 2014-07-22 10:52:03 +0000 UTC           |
-| 171113 14:14:20                                       | 2017-11-13 14:14:20 +0000 UTC           |
-| 1332151919                                            | 2012-03-19 10:11:59 +0000 UTC           |
-| 1384216367189                                         | 2013-11-12 00:32:47.189 +0000 UTC       |
-| 1384216367111222                                      | 2013-11-12 00:32:47.111222 +0000 UTC    |
-| 1384216367111222333                                   | 2013-11-12 00:32:47.111222333 +0000 UTC |
-+-------------------------------------------------------+-----------------------------------------+
-*/
-
-```
diff --git a/vendor/github.com/araddon/dateparse/parseany.go b/vendor/github.com/araddon/dateparse/parseany.go
deleted file mode 100644
index b9668b21bd4..00000000000
--- a/vendor/github.com/araddon/dateparse/parseany.go
+++ /dev/null
@@ -1,2189 +0,0 @@
-// Package dateparse parses date-strings without knowing the format
-// in advance, using a fast lex based approach to eliminate shotgun
-// attempts.  It leans towards US style dates when there is a conflict.
-package dateparse
-
-import (
-	"fmt"
-	"strconv"
-	"strings"
-	"time"
-	"unicode"
-	"unicode/utf8"
-)
-
-// func init() {
-// 	gou.SetupLogging("debug")
-// 	gou.SetColorOutput()
-// }
-
-var days = []string{
-	"mon",
-	"tue",
-	"wed",
-	"thu",
-	"fri",
-	"sat",
-	"sun",
-	"monday",
-	"tuesday",
-	"wednesday",
-	"thursday",
-	"friday",
-	"saturday",
-	"sunday",
-}
-
-var months = []string{
-	"january",
-	"february",
-	"march",
-	"april",
-	"may",
-	"june",
-	"july",
-	"august",
-	"september",
-	"october",
-	"november",
-	"december",
-}
-
-type dateState uint8
-type timeState uint8
-
-const (
-	dateStart dateState = iota // 0
-	dateDigit
-	dateDigitSt
-	dateYearDash
-	dateYearDashAlphaDash
-	dateYearDashDash
-	dateYearDashDashWs // 5
-	dateYearDashDashT
-	dateYearDashDashOffset
-	dateDigitDash
-	dateDigitDashAlpha
-	dateDigitDashAlphaDash // 10
-	dateDigitDot
-	dateDigitDotDot
-	dateDigitSlash
-	dateDigitYearSlash
-	dateDigitSlashAlpha // 15
-	dateDigitColon
-	dateDigitChineseYear
-	dateDigitChineseYearWs
-	dateDigitWs
-	dateDigitWsMoYear // 20
-	dateDigitWsMolong
-	dateAlpha
-	dateAlphaWs
-	dateAlphaWsDigit
-	dateAlphaWsDigitMore // 25
-	dateAlphaWsDigitMoreWs
-	dateAlphaWsDigitMoreWsYear
-	dateAlphaWsMonth
-	dateAlphaWsDigitYearmaybe
-	dateAlphaWsMonthMore
-	dateAlphaWsMonthSuffix
-	dateAlphaWsMore
-	dateAlphaWsAtTime
-	dateAlphaWsAlpha
-	dateAlphaWsAlphaYearmaybe // 35
-	dateAlphaPeriodWsDigit
-	dateWeekdayComma
-	dateWeekdayAbbrevComma
-)
-const (
-	// Time state
-	timeIgnore timeState = iota // 0
-	timeStart
-	timeWs
-	timeWsAlpha
-	timeWsAlphaWs
-	timeWsAlphaZoneOffset // 5
-	timeWsAlphaZoneOffsetWs
-	timeWsAlphaZoneOffsetWsYear
-	timeWsAlphaZoneOffsetWsExtra
-	timeWsAMPMMaybe
-	timeWsAMPM // 10
-	timeWsOffset
-	timeWsOffsetWs // 12
-	timeWsOffsetColonAlpha
-	timeWsOffsetColon
-	timeWsYear // 15
-	timeOffset
-	timeOffsetColon
-	timeAlpha
-	timePeriod
-	timePeriodOffset // 20
-	timePeriodOffsetColon
-	timePeriodOffsetColonWs
-	timePeriodWs
-	timePeriodWsAlpha
-	timePeriodWsOffset // 25
-	timePeriodWsOffsetWs
-	timePeriodWsOffsetWsAlpha
-	timePeriodWsOffsetColon
-	timePeriodWsOffsetColonAlpha
-	timeZ
-	timeZDigit
-)
-
-var (
-	// ErrAmbiguousMMDD for date formats such as 04/02/2014 the mm/dd vs dd/mm are
-	// ambiguous, so it is an error for strict parse rules.
-	ErrAmbiguousMMDD = fmt.Errorf("This date has ambiguous mm/dd vs dd/mm type format")
-)
-
-func unknownErr(datestr string) error {
-	return fmt.Errorf("Could not find format for %q", datestr)
-}
-
-// ParseAny parse an unknown date format, detect the layout.
-// Normal parse.  Equivalent Timezone rules as time.Parse().
-// NOTE:  please see readme on mmdd vs ddmm ambiguous dates.
-func ParseAny(datestr string, opts ...ParserOption) (time.Time, error) {
-	p, err := parseTime(datestr, nil, opts...)
-	if err != nil {
-		return time.Time{}, err
-	}
-	return p.parse()
-}
-
-// ParseIn with Location, equivalent to time.ParseInLocation() timezone/offset
-// rules.  Using location arg, if timezone/offset info exists in the
-// datestring, it uses the given location rules for any zone interpretation.
-// That is, MST means one thing when using America/Denver and something else
-// in other locations.
-func ParseIn(datestr string, loc *time.Location, opts ...ParserOption) (time.Time, error) {
-	p, err := parseTime(datestr, loc, opts...)
-	if err != nil {
-		return time.Time{}, err
-	}
-	return p.parse()
-}
-
-// ParseLocal Given an unknown date format, detect the layout,
-// using time.Local, parse.
-//
-// Set Location to time.Local.  Same as ParseIn Location but lazily uses
-// the global time.Local variable for Location argument.
-//
-//     denverLoc, _ := time.LoadLocation("America/Denver")
-//     time.Local = denverLoc
-//
-//     t, err := dateparse.ParseLocal("3/1/2014")
-//
-// Equivalent to:
-//
-//     t, err := dateparse.ParseIn("3/1/2014", denverLoc)
-//
-func ParseLocal(datestr string, opts ...ParserOption) (time.Time, error) {
-	p, err := parseTime(datestr, time.Local, opts...)
-	if err != nil {
-		return time.Time{}, err
-	}
-	return p.parse()
-}
-
-// MustParse  parse a date, and panic if it can't be parsed.  Used for testing.
-// Not recommended for most use-cases.
-func MustParse(datestr string, opts ...ParserOption) time.Time {
-	p, err := parseTime(datestr, nil, opts...)
-	if err != nil {
-		panic(err.Error())
-	}
-	t, err := p.parse()
-	if err != nil {
-		panic(err.Error())
-	}
-	return t
-}
-
-// ParseFormat parse's an unknown date-time string and returns a layout
-// string that can parse this (and exact same format) other date-time strings.
-//
-//     layout, err := dateparse.ParseFormat("2013-02-01 00:00:00")
-//     // layout = "2006-01-02 15:04:05"
-//
-func ParseFormat(datestr string, opts ...ParserOption) (string, error) {
-	p, err := parseTime(datestr, nil, opts...)
-	if err != nil {
-		return "", err
-	}
-	_, err = p.parse()
-	if err != nil {
-		return "", err
-	}
-	return string(p.format), nil
-}
-
-// ParseStrict parse an unknown date format.  IF the date is ambigous
-// mm/dd vs dd/mm then return an error. These return errors:   3.3.2014 , 8/8/71 etc
-func ParseStrict(datestr string, opts ...ParserOption) (time.Time, error) {
-	p, err := parseTime(datestr, nil, opts...)
-	if err != nil {
-		return time.Time{}, err
-	}
-	if p.ambiguousMD {
-		return time.Time{}, ErrAmbiguousMMDD
-	}
-	return p.parse()
-}
-
-func parseTime(datestr string, loc *time.Location, opts ...ParserOption) (p *parser, err error) {
-
-	p = newParser(datestr, loc, opts...)
-	if p.retryAmbiguousDateWithSwap {
-		// month out of range signifies that a day/month swap is the correct solution to an ambiguous date
-		// this is because it means that a day is being interpreted as a month and overflowing the valid value for that
-		// by retrying in this case, we can fix a common situation with no assumptions
-		defer func() {
-			if p != nil && p.ambiguousMD {
-				// if it errors out with the following error, swap before we
-				// get out of this function to reduce scope it needs to be applied on
-				_, err := p.parse()
-				if err != nil && strings.Contains(err.Error(), "month out of range") {
-					// create the option to reverse the preference
-					preferMonthFirst := PreferMonthFirst(!p.preferMonthFirst)
-					// turn off the retry to avoid endless recursion
-					retryAmbiguousDateWithSwap := RetryAmbiguousDateWithSwap(false)
-					modifiedOpts := append(opts, preferMonthFirst, retryAmbiguousDateWithSwap)
-					p, err = parseTime(datestr, time.Local, modifiedOpts...)
-				}
-			}
-
-		}()
-	}
-
-	i := 0
-
-	// General strategy is to read rune by rune through the date looking for
-	// certain hints of what type of date we are dealing with.
-	// Hopefully we only need to read about 5 or 6 bytes before
-	// we figure it out and then attempt a parse
-iterRunes:
-	for ; i < len(datestr); i++ {
-		//r := rune(datestr[i])
-		r, bytesConsumed := utf8.DecodeRuneInString(datestr[i:])
-		if bytesConsumed > 1 {
-			i += (bytesConsumed - 1)
-		}
-
-		// gou.Debugf("i=%d r=%s state=%d   %s", i, string(r), p.stateDate, datestr)
-		switch p.stateDate {
-		case dateStart:
-			if unicode.IsDigit(r) {
-				p.stateDate = dateDigit
-			} else if unicode.IsLetter(r) {
-				p.stateDate = dateAlpha
-			} else {
-				return nil, unknownErr(datestr)
-			}
-		case dateDigit:
-
-			switch r {
-			case '-', '\u2212':
-				// 2006-01-02
-				// 2013-Feb-03
-				// 13-Feb-03
-				// 29-Jun-2016
-				if i == 4 {
-					p.stateDate = dateYearDash
-					p.yeari = 0
-					p.yearlen = i
-					p.moi = i + 1
-					p.set(0, "2006")
-				} else {
-					p.stateDate = dateDigitDash
-				}
-			case '/':
-				// 08/May/2005
-				// 03/31/2005
-				// 2014/02/24
-				p.stateDate = dateDigitSlash
-				if i == 4 {
-					// 2014/02/24  -  Year first /
-					p.yearlen = i // since it was start of datestr, i=len
-					p.moi = i + 1
-					p.setYear()
-					p.stateDate = dateDigitYearSlash
-				} else {
-					// Either Ambiguous dd/mm vs mm/dd  OR dd/month/yy
-					// 08/May/2005
-					// 03/31/2005
-					// 31/03/2005
-					if i+2 < len(p.datestr) && unicode.IsLetter(rune(datestr[i+1])) {
-						// 08/May/2005
-						p.stateDate = dateDigitSlashAlpha
-						p.moi = i + 1
-						p.daylen = 2
-						p.dayi = 0
-						p.setDay()
-						continue
-					}
-					// Ambiguous dd/mm vs mm/dd the bane of date-parsing
-					// 03/31/2005
-					// 31/03/2005
-					p.ambiguousMD = true
-					if p.preferMonthFirst {
-						if p.molen == 0 {
-							// 03/31/2005
-							p.molen = i
-							p.setMonth()
-							p.dayi = i + 1
-						}
-					} else {
-						if p.daylen == 0 {
-							p.daylen = i
-							p.setDay()
-							p.moi = i + 1
-						}
-					}
-
-				}
-
-			case ':':
-				// 03/31/2005
-				// 2014/02/24
-				p.stateDate = dateDigitColon
-				if i == 4 {
-					p.yearlen = i
-					p.moi = i + 1
-					p.setYear()
-				} else {
-					p.ambiguousMD = true
-					if p.preferMonthFirst {
-						if p.molen == 0 {
-							p.molen = i
-							p.setMonth()
-							p.dayi = i + 1
-						}
-					}
-				}
-
-			case '.':
-				// 3.31.2014
-				// 08.21.71
-				// 2014.05
-				p.stateDate = dateDigitDot
-				if i == 4 {
-					p.yearlen = i
-					p.moi = i + 1
-					p.setYear()
-				} else {
-					p.ambiguousMD = true
-					p.moi = 0
-					p.molen = i
-					p.setMonth()
-					p.dayi = i + 1
-				}
-
-			case ' ':
-				// 18 January 2018
-				// 8 January 2018
-				// 8 jan 2018
-				// 02 Jan 2018 23:59
-				// 02 Jan 2018 23:59:34
-				// 12 Feb 2006, 19:17
-				// 12 Feb 2006, 19:17:22
-				if i == 6 {
-					p.stateDate = dateDigitSt
-				} else {
-					p.stateDate = dateDigitWs
-					p.dayi = 0
-					p.daylen = i
-				}
-			case '年':
-				// Chinese Year
-				p.stateDate = dateDigitChineseYear
-			case ',':
-				return nil, unknownErr(datestr)
-			default:
-				continue
-			}
-			p.part1Len = i
-
-		case dateDigitSt:
-			p.set(0, "060102")
-			i = i - 1
-			p.stateTime = timeStart
-			break iterRunes
-		case dateYearDash:
-			// dateYearDashDashT
-			//  2006-01-02T15:04:05Z07:00
-			//  2020-08-17T17:00:00:000+0100
-			// dateYearDashDashWs
-			//  2013-04-01 22:43:22
-			// dateYearDashAlphaDash
-			//   2013-Feb-03
-			switch r {
-			case '-':
-				p.molen = i - p.moi
-				p.dayi = i + 1
-				p.stateDate = dateYearDashDash
-				p.setMonth()
-			default:
-				if unicode.IsLetter(r) {
-					p.stateDate = dateYearDashAlphaDash
-				}
-			}
-
-		case dateYearDashDash:
-			// dateYearDashDashT
-			//  2006-01-02T15:04:05Z07:00
-			// dateYearDashDashWs
-			//  2013-04-01 22:43:22
-			// dateYearDashDashOffset
-			//  2020-07-20+00:00
-			switch r {
-			case '+', '-':
-				p.offseti = i
-				p.daylen = i - p.dayi
-				p.stateDate = dateYearDashDashOffset
-				p.setDay()
-			case ' ':
-				p.daylen = i - p.dayi
-				p.stateDate = dateYearDashDashWs
-				p.stateTime = timeStart
-				p.setDay()
-				break iterRunes
-			case 'T':
-				p.daylen = i - p.dayi
-				p.stateDate = dateYearDashDashT
-				p.stateTime = timeStart
-				p.setDay()
-				break iterRunes
-			}
-
-		case dateYearDashDashT:
-			// dateYearDashDashT
-			//  2006-01-02T15:04:05Z07:00
-			//  2020-08-17T17:00:00:000+0100
-
-		case dateYearDashDashOffset:
-			//  2020-07-20+00:00
-			switch r {
-			case ':':
-				p.set(p.offseti, "-07:00")
-				// case ' ':
-				// 	return nil, unknownErr(datestr)
-			}
-
-		case dateYearDashAlphaDash:
-			// 2013-Feb-03
-			switch r {
-			case '-':
-				p.molen = i - p.moi
-				p.set(p.moi, "Jan")
-				p.dayi = i + 1
-			}
-		case dateDigitDash:
-			// 13-Feb-03
-			// 29-Jun-2016
-			if unicode.IsLetter(r) {
-				p.stateDate = dateDigitDashAlpha
-				p.moi = i
-			} else {
-				return nil, unknownErr(datestr)
-			}
-		case dateDigitDashAlpha:
-			// 13-Feb-03
-			// 28-Feb-03
-			// 29-Jun-2016
-			switch r {
-			case '-':
-				p.molen = i - p.moi
-				p.set(p.moi, "Jan")
-				p.yeari = i + 1
-				p.stateDate = dateDigitDashAlphaDash
-			}
-
-		case dateDigitDashAlphaDash:
-			// 13-Feb-03   ambiguous
-			// 28-Feb-03   ambiguous
-			// 29-Jun-2016  dd-month(alpha)-yyyy
-			switch r {
-			case ' ':
-				// we need to find if this was 4 digits, aka year
-				// or 2 digits which makes it ambiguous year/day
-				length := i - (p.moi + p.molen + 1)
-				if length == 4 {
-					p.yearlen = 4
-					p.set(p.yeari, "2006")
-					// We now also know that part1 was the day
-					p.dayi = 0
-					p.daylen = p.part1Len
-					p.setDay()
-				} else if length == 2 {
-					// We have no idea if this is
-					// yy-mon-dd   OR  dd-mon-yy
-					//
-					// We are going to ASSUME (bad, bad) that it is dd-mon-yy  which is a horible assumption
-					p.ambiguousMD = true
-					p.yearlen = 2
-					p.set(p.yeari, "06")
-					// We now also know that part1 was the day
-					p.dayi = 0
-					p.daylen = p.part1Len
-					p.setDay()
-				}
-				p.stateTime = timeStart
-				break iterRunes
-			}
-
-		case dateDigitYearSlash:
-			// 2014/07/10 06:55:38.156283
-			// I honestly don't know if this format ever shows up as yyyy/
-
-			switch r {
-			case ' ', ':':
-				p.stateTime = timeStart
-				if p.daylen == 0 {
-					p.daylen = i - p.dayi
-					p.setDay()
-				}
-				break iterRunes
-			case '/':
-				if p.molen == 0 {
-					p.molen = i - p.moi
-					p.setMonth()
-					p.dayi = i + 1
-				}
-			}
-
-		case dateDigitSlashAlpha:
-			// 06/May/2008
-
-			switch r {
-			case '/':
-				//       |
-				// 06/May/2008
-				if p.molen == 0 {
-					p.set(p.moi, "Jan")
-					p.yeari = i + 1
-				}
-				// We aren't breaking because we are going to re-use this case
-				// to find where the date starts, and possible time begins
-			case ' ', ':':
-				p.stateTime = timeStart
-				if p.yearlen == 0 {
-					p.yearlen = i - p.yeari
-					p.setYear()
-				}
-				break iterRunes
-			}
-
-		case dateDigitSlash:
-			// 03/19/2012 10:11:59
-			// 04/2/2014 03:00:37
-			// 3/1/2012 10:11:59
-			// 4/8/2014 22:05
-			// 3/1/2014
-			// 10/13/2014
-			// 01/02/2006
-			// 1/2/06
-
-			switch r {
-			case '/':
-				// This is the 2nd / so now we should know start pts of all of the dd, mm, yy
-				if p.preferMonthFirst {
-					if p.daylen == 0 {
-						p.daylen = i - p.dayi
-						p.setDay()
-						p.yeari = i + 1
-					}
-				} else {
-					if p.molen == 0 {
-						p.molen = i - p.moi
-						p.setMonth()
-						p.yeari = i + 1
-					}
-				}
-				// Note no break, we are going to pass by and re-enter this dateDigitSlash
-				// and look for ending (space) or not (just date)
-			case ' ':
-				p.stateTime = timeStart
-				if p.yearlen == 0 {
-					p.yearlen = i - p.yeari
-					p.setYear()
-				}
-				break iterRunes
-			}
-
-		case dateDigitColon:
-			// 2014:07:10 06:55:38.156283
-			// 03:19:2012 10:11:59
-			// 04:2:2014 03:00:37
-			// 3:1:2012 10:11:59
-			// 4:8:2014 22:05
-			// 3:1:2014
-			// 10:13:2014
-			// 01:02:2006
-			// 1:2:06
-
-			switch r {
-			case ' ':
-				p.stateTime = timeStart
-				if p.yearlen == 0 {
-					p.yearlen = i - p.yeari
-					p.setYear()
-				} else if p.daylen == 0 {
-					p.daylen = i - p.dayi
-					p.setDay()
-				}
-				break iterRunes
-			case ':':
-				if p.yearlen > 0 {
-					// 2014:07:10 06:55:38.156283
-					if p.molen == 0 {
-						p.molen = i - p.moi
-						p.setMonth()
-						p.dayi = i + 1
-					}
-				} else if p.preferMonthFirst {
-					if p.daylen == 0 {
-						p.daylen = i - p.dayi
-						p.setDay()
-						p.yeari = i + 1
-					}
-				}
-			}
-
-		case dateDigitWs:
-			// 18 January 2018
-			// 8 January 2018
-			// 8 jan 2018
-			// 1 jan 18
-			// 02 Jan 2018 23:59
-			// 02 Jan 2018 23:59:34
-			// 12 Feb 2006, 19:17
-			// 12 Feb 2006, 19:17:22
-			switch r {
-			case ' ':
-				p.yeari = i + 1
-				//p.yearlen = 4
-				p.dayi = 0
-				p.daylen = p.part1Len
-				p.setDay()
-				p.stateTime = timeStart
-				if i > p.daylen+len(" Sep") { //  November etc
-					// If len greather than space + 3 it must be full month
-					p.stateDate = dateDigitWsMolong
-				} else {
-					// If len=3, the might be Feb or May?  Ie ambigous abbreviated but
-					// we can parse may with either.  BUT, that means the
-					// format may not be correct?
-					// mo := strings.ToLower(datestr[p.daylen+1 : i])
-					p.moi = p.daylen + 1
-					p.molen = i - p.moi
-					p.set(p.moi, "Jan")
-					p.stateDate = dateDigitWsMoYear
-				}
-			}
-
-		case dateDigitWsMoYear:
-			// 8 jan 2018
-			// 02 Jan 2018 23:59
-			// 02 Jan 2018 23:59:34
-			// 12 Feb 2006, 19:17
-			// 12 Feb 2006, 19:17:22
-			switch r {
-			case ',':
-				p.yearlen = i - p.yeari
-				p.setYear()
-				i++
-				break iterRunes
-			case ' ':
-				p.yearlen = i - p.yeari
-				p.setYear()
-				break iterRunes
-			}
-		case dateDigitWsMolong:
-			// 18 January 2018
-			// 8 January 2018
-
-		case dateDigitChineseYear:
-			// dateDigitChineseYear
-			//   2014年04月08日
-			//               weekday  %Y年%m月%e日 %A %I:%M %p
-			// 2013年07月18日 星期四 10:27 上午
-			if r == ' ' {
-				p.stateDate = dateDigitChineseYearWs
-				break
-			}
-		case dateDigitDot:
-			// This is the 2nd period
-			// 3.31.2014
-			// 08.21.71
-			// 2014.05
-			// 2018.09.30
-			if r == '.' {
-				if p.moi == 0 {
-					// 3.31.2014
-					p.daylen = i - p.dayi
-					p.yeari = i + 1
-					p.setDay()
-					p.stateDate = dateDigitDotDot
-				} else {
-					// 2018.09.30
-					//p.molen = 2
-					p.molen = i - p.moi
-					p.dayi = i + 1
-					p.setMonth()
-					p.stateDate = dateDigitDotDot
-				}
-			}
-		case dateDigitDotDot:
-			// iterate all the way through
-		case dateAlpha:
-			// dateAlphaWS
-			//  Mon Jan _2 15:04:05 2006
-			//  Mon Jan _2 15:04:05 MST 2006
-			//  Mon Jan 02 15:04:05 -0700 2006
-			//  Mon Aug 10 15:44:11 UTC+0100 2015
-			//  Fri Jul 03 2015 18:04:07 GMT+0100 (GMT Daylight Time)
-			//  dateAlphaWSDigit
-			//    May 8, 2009 5:57:51 PM
-			//    oct 1, 1970
-			//  dateAlphaWsMonth
-			//    April 8, 2009
-			//  dateAlphaWsMore
-			//    dateAlphaWsAtTime
-			//      January 02, 2006 at 3:04pm MST-07
-			//
-			//  dateAlphaPeriodWsDigit
-			//    oct. 1, 1970
-			// dateWeekdayComma
-			//   Monday, 02 Jan 2006 15:04:05 MST
-			//   Monday, 02-Jan-06 15:04:05 MST
-			//   Monday, 02 Jan 2006 15:04:05 -0700
-			//   Monday, 02 Jan 2006 15:04:05 +0100
-			// dateWeekdayAbbrevComma
-			//   Mon, 02 Jan 2006 15:04:05 MST
-			//   Mon, 02 Jan 2006 15:04:05 -0700
-			//   Thu, 13 Jul 2017 08:58:40 +0100
-			//   Tue, 11 Jul 2017 16:28:13 +0200 (CEST)
-			//   Mon, 02-Jan-06 15:04:05 MST
-			switch {
-			case r == ' ':
-				//      X
-				// April 8, 2009
-				if i > 3 {
-					// Check to see if the alpha is name of month?  or Day?
-					month := strings.ToLower(datestr[0:i])
-					if isMonthFull(month) {
-						p.fullMonth = month
-						// len(" 31, 2018")   = 9
-						if len(datestr[i:]) < 10 {
-							// April 8, 2009
-							p.stateDate = dateAlphaWsMonth
-						} else {
-							p.stateDate = dateAlphaWsMore
-						}
-						p.dayi = i + 1
-						break
-					}
-
-				} else {
-					// This is possibly ambiguous?  May will parse as either though.
-					// So, it could return in-correct format.
-					// dateAlphaWs
-					//   May 05, 2005, 05:05:05
-					//   May 05 2005, 05:05:05
-					//   Jul 05, 2005, 05:05:05
-					//   May 8 17:57:51 2009
-					//   May  8 17:57:51 2009
-					// skip & return to dateStart
-					//   Tue 05 May 2020, 05:05:05
-					//   Mon Jan  2 15:04:05 2006
-
-					maybeDay := strings.ToLower(datestr[0:i])
-					if isDay(maybeDay) {
-						// using skip throws off indices used by other code; saner to restart
-						return parseTime(datestr[i+1:], loc)
-					}
-					p.stateDate = dateAlphaWs
-				}
-
-			case r == ',':
-				// Mon, 02 Jan 2006
-
-				if i == 3 {
-					p.stateDate = dateWeekdayAbbrevComma
-					p.set(0, "Mon")
-				} else {
-					p.stateDate = dateWeekdayComma
-					p.skip = i + 2
-					i++
-					// TODO:  lets just make this "skip" as we don't need
-					// the mon, monday, they are all superfelous and not needed
-					// just lay down the skip, no need to fill and then skip
-				}
-			case r == '.':
-				// sept. 28, 2017
-				// jan. 28, 2017
-				p.stateDate = dateAlphaPeriodWsDigit
-				if i == 3 {
-					p.molen = i
-					p.set(0, "Jan")
-				} else if i == 4 {
-					// gross
-					datestr = datestr[0:i-1] + datestr[i:]
-					return parseTime(datestr, loc, opts...)
-				} else {
-					return nil, unknownErr(datestr)
-				}
-			}
-
-		case dateAlphaWs:
-			// dateAlphaWsAlpha
-			//   Mon Jan _2 15:04:05 2006
-			//   Mon Jan _2 15:04:05 MST 2006
-			//   Mon Jan 02 15:04:05 -0700 2006
-			//   Fri Jul 03 2015 18:04:07 GMT+0100 (GMT Daylight Time)
-			//   Mon Aug 10 15:44:11 UTC+0100 2015
-			// dateAlphaWsDigit
-			//   May 8, 2009 5:57:51 PM
-			//   May 8 2009 5:57:51 PM
-			//   May 8 17:57:51 2009
-			//   May  8 17:57:51 2009
-			//   May 08 17:57:51 2009
-			//   oct 1, 1970
-			//   oct 7, '70
-			switch {
-			case unicode.IsLetter(r):
-				p.set(0, "Mon")
-				p.stateDate = dateAlphaWsAlpha
-				p.set(i, "Jan")
-			case unicode.IsDigit(r):
-				p.set(0, "Jan")
-				p.stateDate = dateAlphaWsDigit
-				p.dayi = i
-			}
-
-		case dateAlphaWsDigit:
-			// May 8, 2009 5:57:51 PM
-			// May 8 2009 5:57:51 PM
-			// oct 1, 1970
-			// oct 7, '70
-			// oct. 7, 1970
-			// May 8 17:57:51 2009
-			// May  8 17:57:51 2009
-			// May 08 17:57:51 2009
-			if r == ',' {
-				p.daylen = i - p.dayi
-				p.setDay()
-				p.stateDate = dateAlphaWsDigitMore
-			} else if r == ' ' {
-				p.daylen = i - p.dayi
-				p.setDay()
-				p.yeari = i + 1
-				p.stateDate = dateAlphaWsDigitYearmaybe
-				p.stateTime = timeStart
-			} else if unicode.IsLetter(r) {
-				p.stateDate = dateAlphaWsMonthSuffix
-				i--
-			}
-		case dateAlphaWsDigitYearmaybe:
-			//       x
-			// May 8 2009 5:57:51 PM
-			// May 8 17:57:51 2009
-			// May  8 17:57:51 2009
-			// May 08 17:57:51 2009
-			// Jul 03 2015 18:04:07 GMT+0100 (GMT Daylight Time)
-			if r == ':' {
-				// Guessed wrong; was not a year
-				i = i - 3
-				p.stateDate = dateAlphaWsDigit
-				p.yeari = 0
-				break iterRunes
-			} else if r == ' ' {
-				// must be year format, not 15:04
-				p.yearlen = i - p.yeari
-				p.setYear()
-				break iterRunes
-			}
-		case dateAlphaWsDigitMore:
-			//       x
-			// May 8, 2009 5:57:51 PM
-			// May 05, 2005, 05:05:05
-			// May 05 2005, 05:05:05
-			// oct 1, 1970
-			// oct 7, '70
-			if r == ' ' {
-				p.yeari = i + 1
-				p.stateDate = dateAlphaWsDigitMoreWs
-			}
-		case dateAlphaWsDigitMoreWs:
-			//            x
-			// May 8, 2009 5:57:51 PM
-			// May 05, 2005, 05:05:05
-			// oct 1, 1970
-			// oct 7, '70
-			switch r {
-			case '\'':
-				p.yeari = i + 1
-			case ' ', ',':
-				//            x
-				// May 8, 2009 5:57:51 PM
-				//            x
-				// May 8, 2009, 5:57:51 PM
-				p.stateDate = dateAlphaWsDigitMoreWsYear
-				p.yearlen = i - p.yeari
-				p.setYear()
-				p.stateTime = timeStart
-				break iterRunes
-			}
-
-		case dateAlphaWsMonth:
-			// April 8, 2009
-			// April 8 2009
-			switch r {
-			case ' ', ',':
-				//       x
-				// June 8, 2009
-				//       x
-				// June 8 2009
-				if p.daylen == 0 {
-					p.daylen = i - p.dayi
-					p.setDay()
-				}
-			case 's', 'S', 'r', 'R', 't', 'T', 'n', 'N':
-				// st, rd, nd, st
-				i--
-				p.stateDate = dateAlphaWsMonthSuffix
-			default:
-				if p.daylen > 0 && p.yeari == 0 {
-					p.yeari = i
-				}
-			}
-		case dateAlphaWsMonthMore:
-			//                  X
-			// January 02, 2006, 15:04:05
-			// January 02 2006, 15:04:05
-			// January 02, 2006 15:04:05
-			// January 02 2006 15:04:05
-			switch r {
-			case ',':
-				p.yearlen = i - p.yeari
-				p.setYear()
-				p.stateTime = timeStart
-				i++
-				break iterRunes
-			case ' ':
-				p.yearlen = i - p.yeari
-				p.setYear()
-				p.stateTime = timeStart
-				break iterRunes
-			}
-		case dateAlphaWsMonthSuffix:
-			//        x
-			// April 8th, 2009
-			// April 8th 2009
-			switch r {
-			case 't', 'T':
-				if p.nextIs(i, 'h') || p.nextIs(i, 'H') {
-					if len(datestr) > i+2 {
-						return parseTime(fmt.Sprintf("%s%s", p.datestr[0:i], p.datestr[i+2:]), loc, opts...)
-					}
-				}
-			case 'n', 'N':
-				if p.nextIs(i, 'd') || p.nextIs(i, 'D') {
-					if len(datestr) > i+2 {
-						return parseTime(fmt.Sprintf("%s%s", p.datestr[0:i], p.datestr[i+2:]), loc, opts...)
-					}
-				}
-			case 's', 'S':
-				if p.nextIs(i, 't') || p.nextIs(i, 'T') {
-					if len(datestr) > i+2 {
-						return parseTime(fmt.Sprintf("%s%s", p.datestr[0:i], p.datestr[i+2:]), loc, opts...)
-					}
-				}
-			case 'r', 'R':
-				if p.nextIs(i, 'd') || p.nextIs(i, 'D') {
-					if len(datestr) > i+2 {
-						return parseTime(fmt.Sprintf("%s%s", p.datestr[0:i], p.datestr[i+2:]), loc, opts...)
-					}
-				}
-			}
-		case dateAlphaWsMore:
-			// January 02, 2006, 15:04:05
-			// January 02 2006, 15:04:05
-			// January 2nd, 2006, 15:04:05
-			// January 2nd 2006, 15:04:05
-			// September 17, 2012 at 5:00pm UTC-05
-			switch {
-			case r == ',':
-				//           x
-				// January 02, 2006, 15:04:05
-				if p.nextIs(i, ' ') {
-					p.daylen = i - p.dayi
-					p.setDay()
-					p.yeari = i + 2
-					p.stateDate = dateAlphaWsMonthMore
-					i++
-				}
-
-			case r == ' ':
-				//           x
-				// January 02 2006, 15:04:05
-				p.daylen = i - p.dayi
-				p.setDay()
-				p.yeari = i + 1
-				p.stateDate = dateAlphaWsMonthMore
-			case unicode.IsDigit(r):
-				//         XX
-				// January 02, 2006, 15:04:05
-				continue
-			case unicode.IsLetter(r):
-				//          X
-				// January 2nd, 2006, 15:04:05
-				p.daylen = i - p.dayi
-				p.setDay()
-				p.stateDate = dateAlphaWsMonthSuffix
-				i--
-			}
-
-		case dateAlphaPeriodWsDigit:
-			//    oct. 7, '70
-			switch {
-			case r == ' ':
-				// continue
-			case unicode.IsDigit(r):
-				p.stateDate = dateAlphaWsDigit
-				p.dayi = i
-			default:
-				return p, unknownErr(datestr)
-			}
-		case dateWeekdayComma:
-			// Monday, 02 Jan 2006 15:04:05 MST
-			// Monday, 02 Jan 2006 15:04:05 -0700
-			// Monday, 02 Jan 2006 15:04:05 +0100
-			// Monday, 02-Jan-06 15:04:05 MST
-			if p.dayi == 0 {
-				p.dayi = i
-			}
-			switch r {
-			case ' ', '-':
-				if p.moi == 0 {
-					p.moi = i + 1
-					p.daylen = i - p.dayi
-					p.setDay()
-				} else if p.yeari == 0 {
-					p.yeari = i + 1
-					p.molen = i - p.moi
-					p.set(p.moi, "Jan")
-				} else {
-					p.stateTime = timeStart
-					break iterRunes
-				}
-			}
-		case dateWeekdayAbbrevComma:
-			// Mon, 02 Jan 2006 15:04:05 MST
-			// Mon, 02 Jan 2006 15:04:05 -0700
-			// Thu, 13 Jul 2017 08:58:40 +0100
-			// Thu, 4 Jan 2018 17:53:36 +0000
-			// Tue, 11 Jul 2017 16:28:13 +0200 (CEST)
-			// Mon, 02-Jan-06 15:04:05 MST
-			switch r {
-			case ' ', '-':
-				if p.dayi == 0 {
-					p.dayi = i + 1
-				} else if p.moi == 0 {
-					p.daylen = i - p.dayi
-					p.setDay()
-					p.moi = i + 1
-				} else if p.yeari == 0 {
-					p.molen = i - p.moi
-					p.set(p.moi, "Jan")
-					p.yeari = i + 1
-				} else {
-					p.yearlen = i - p.yeari
-					p.setYear()
-					p.stateTime = timeStart
-					break iterRunes
-				}
-			}
-
-		default:
-			break iterRunes
-		}
-	}
-	p.coalesceDate(i)
-	if p.stateTime == timeStart {
-		// increment first one, since the i++ occurs at end of loop
-		if i < len(p.datestr) {
-			i++
-		}
-		// ensure we skip any whitespace prefix
-		for ; i < len(datestr); i++ {
-			r := rune(datestr[i])
-			if r != ' ' {
-				break
-			}
-		}
-
-	iterTimeRunes:
-		for ; i < len(datestr); i++ {
-			r := rune(datestr[i])
-
-			// gou.Debugf("i=%d r=%s state=%d iterTimeRunes  %s %s", i, string(r), p.stateTime, p.ds(), p.ts())
-
-			switch p.stateTime {
-			case timeStart:
-				// 22:43:22
-				// 22:43
-				// timeComma
-				//   08:20:13,787
-				// timeWs
-				//   05:24:37 PM
-				//   06:20:00 UTC
-				//   06:20:00 UTC-05
-				//   00:12:00 +0000 UTC
-				//   22:18:00 +0000 UTC m=+0.000000001
-				//   15:04:05 -0700
-				//   15:04:05 -07:00
-				//   15:04:05 2008
-				// timeOffset
-				//   03:21:51+00:00
-				//   19:55:00+0100
-				// timePeriod
-				//   17:24:37.3186369
-				//   00:07:31.945167
-				//   18:31:59.257000000
-				//   00:00:00.000
-				//   timePeriodOffset
-				//     19:55:00.799+0100
-				//     timePeriodOffsetColon
-				//       15:04:05.999-07:00
-				//   timePeriodWs
-				//     timePeriodWsOffset
-				//       00:07:31.945167 +0000
-				//       00:00:00.000 +0000
-				//     timePeriodWsOffsetAlpha
-				//       00:07:31.945167 +0000 UTC
-				//       22:18:00.001 +0000 UTC m=+0.000000001
-				//       00:00:00.000 +0000 UTC
-				//     timePeriodWsAlpha
-				//       06:20:00.000 UTC
-				if p.houri == 0 {
-					p.houri = i
-				}
-				switch r {
-				case ',':
-					// hm, lets just swap out comma for period.  for some reason go
-					// won't parse it.
-					// 2014-05-11 08:20:13,787
-					ds := []byte(p.datestr)
-					ds[i] = '.'
-					return parseTime(string(ds), loc, opts...)
-				case '-', '+':
-					//   03:21:51+00:00
-					p.stateTime = timeOffset
-					if p.seci == 0 {
-						// 22:18+0530
-						p.minlen = i - p.mini
-					} else {
-						if p.seclen == 0 {
-							p.seclen = i - p.seci
-						}
-						if p.msi > 0 && p.mslen == 0 {
-							p.mslen = i - p.msi
-						}
-					}
-					p.offseti = i
-				case '.':
-					p.stateTime = timePeriod
-					p.seclen = i - p.seci
-					p.msi = i + 1
-				case 'Z':
-					p.stateTime = timeZ
-					if p.seci == 0 {
-						p.minlen = i - p.mini
-					} else {
-						p.seclen = i - p.seci
-					}
-					// (Z)ulu time
-					p.loc = time.UTC
-				case 'a', 'A':
-					if p.nextIs(i, 't') || p.nextIs(i, 'T') {
-						//                    x
-						// September 17, 2012 at 5:00pm UTC-05
-						i++ // skip t
-						if p.nextIs(i, ' ') {
-							//                      x
-							// September 17, 2012 at 5:00pm UTC-05
-							i++         // skip '
-							p.houri = 0 // reset hour
-						}
-					} else {
-						switch {
-						case r == 'a' && p.nextIs(i, 'm'):
-							p.coalesceTime(i)
-							p.set(i, "am")
-						case r == 'A' && p.nextIs(i, 'M'):
-							p.coalesceTime(i)
-							p.set(i, "PM")
-						}
-					}
-
-				case 'p', 'P':
-					// Could be AM/PM
-					switch {
-					case r == 'p' && p.nextIs(i, 'm'):
-						p.coalesceTime(i)
-						p.set(i, "pm")
-					case r == 'P' && p.nextIs(i, 'M'):
-						p.coalesceTime(i)
-						p.set(i, "PM")
-					}
-				case ' ':
-					p.coalesceTime(i)
-					p.stateTime = timeWs
-				case ':':
-					if p.mini == 0 {
-						p.mini = i + 1
-						p.hourlen = i - p.houri
-					} else if p.seci == 0 {
-						p.seci = i + 1
-						p.minlen = i - p.mini
-					} else if p.seci > 0 {
-						// 18:31:59:257    ms uses colon, wtf
-						p.seclen = i - p.seci
-						p.set(p.seci, "05")
-						p.msi = i + 1
-
-						// gross, gross, gross.   manipulating the datestr is horrible.
-						// https://github.com/araddon/dateparse/issues/117
-						// Could not get the parsing to work using golang time.Parse() without
-						// replacing that colon with period.
-						p.set(i, ".")
-						datestr = datestr[0:i] + "." + datestr[i+1:]
-						p.datestr = datestr
-					}
-				}
-			case timeOffset:
-				// 19:55:00+0100
-				// timeOffsetColon
-				//   15:04:05+07:00
-				//   15:04:05-07:00
-				if r == ':' {
-					p.stateTime = timeOffsetColon
-				}
-			case timeWs:
-				// timeWsAlpha
-				//   06:20:00 UTC
-				//   06:20:00 UTC-05
-				//   15:44:11 UTC+0100 2015
-				//   18:04:07 GMT+0100 (GMT Daylight Time)
-				//   17:57:51 MST 2009
-				//   timeWsAMPMMaybe
-				//     05:24:37 PM
-				// timeWsOffset
-				//   15:04:05 -0700
-				//   00:12:00 +0000 UTC
-				//   timeWsOffsetColon
-				//     15:04:05 -07:00
-				//     17:57:51 -0700 2009
-				//     timeWsOffsetColonAlpha
-				//       00:12:00 +00:00 UTC
-				// timeWsYear
-				//     00:12:00 2008
-				// timeZ
-				//   15:04:05.99Z
-				switch r {
-				case 'A', 'P':
-					// Could be AM/PM or could be PST or similar
-					p.tzi = i
-					p.stateTime = timeWsAMPMMaybe
-				case '+', '-':
-					p.offseti = i
-					p.stateTime = timeWsOffset
-				default:
-					if unicode.IsLetter(r) {
-						// 06:20:00 UTC
-						// 06:20:00 UTC-05
-						// 15:44:11 UTC+0100 2015
-						// 17:57:51 MST 2009
-						p.tzi = i
-						p.stateTime = timeWsAlpha
-					} else if unicode.IsDigit(r) {
-						// 00:12:00 2008
-						p.stateTime = timeWsYear
-						p.yeari = i
-					}
-				}
-			case timeWsAlpha:
-				// 06:20:00 UTC
-				// 06:20:00 UTC-05
-				// timeWsAlphaWs
-				//   17:57:51 MST 2009
-				// timeWsAlphaZoneOffset
-				// timeWsAlphaZoneOffsetWs
-				//   timeWsAlphaZoneOffsetWsExtra
-				//     18:04:07 GMT+0100 (GMT Daylight Time)
-				//   timeWsAlphaZoneOffsetWsYear
-				//     15:44:11 UTC+0100 2015
-				switch r {
-				case '+', '-':
-					p.tzlen = i - p.tzi
-					if p.tzlen == 4 {
-						p.set(p.tzi, " MST")
-					} else if p.tzlen == 3 {
-						p.set(p.tzi, "MST")
-					}
-					p.stateTime = timeWsAlphaZoneOffset
-					p.offseti = i
-				case ' ':
-					// 17:57:51 MST 2009
-					// 17:57:51 MST
-					p.tzlen = i - p.tzi
-					if p.tzlen == 4 {
-						p.set(p.tzi, " MST")
-					} else if p.tzlen == 3 {
-						p.set(p.tzi, "MST")
-					}
-					p.stateTime = timeWsAlphaWs
-					p.yeari = i + 1
-				}
-			case timeWsAlphaWs:
-				//   17:57:51 MST 2009
-
-			case timeWsAlphaZoneOffset:
-				// 06:20:00 UTC-05
-				// timeWsAlphaZoneOffset
-				// timeWsAlphaZoneOffsetWs
-				//   timeWsAlphaZoneOffsetWsExtra
-				//     18:04:07 GMT+0100 (GMT Daylight Time)
-				//   timeWsAlphaZoneOffsetWsYear
-				//     15:44:11 UTC+0100 2015
-				switch r {
-				case ' ':
-					p.set(p.offseti, "-0700")
-					if p.yeari == 0 {
-						p.yeari = i + 1
-					}
-					p.stateTime = timeWsAlphaZoneOffsetWs
-				}
-			case timeWsAlphaZoneOffsetWs:
-				// timeWsAlphaZoneOffsetWs
-				//   timeWsAlphaZoneOffsetWsExtra
-				//     18:04:07 GMT+0100 (GMT Daylight Time)
-				//   timeWsAlphaZoneOffsetWsYear
-				//     15:44:11 UTC+0100 2015
-				if unicode.IsDigit(r) {
-					p.stateTime = timeWsAlphaZoneOffsetWsYear
-				} else {
-					p.extra = i - 1
-					p.stateTime = timeWsAlphaZoneOffsetWsExtra
-				}
-			case timeWsAlphaZoneOffsetWsYear:
-				// 15:44:11 UTC+0100 2015
-				if unicode.IsDigit(r) {
-					p.yearlen = i - p.yeari + 1
-					if p.yearlen == 4 {
-						p.setYear()
-					}
-				}
-			case timeWsAMPMMaybe:
-				// timeWsAMPMMaybe
-				//   timeWsAMPM
-				//     05:24:37 PM
-				//   timeWsAlpha
-				//     00:12:00 PST
-				//     15:44:11 UTC+0100 2015
-				if r == 'M' {
-					//return parse("2006-01-02 03:04:05 PM", datestr, loc)
-					p.stateTime = timeWsAMPM
-					p.set(i-1, "PM")
-					if p.hourlen == 2 {
-						p.set(p.houri, "03")
-					} else if p.hourlen == 1 {
-						p.set(p.houri, "3")
-					}
-				} else {
-					p.stateTime = timeWsAlpha
-				}
-
-			case timeWsOffset:
-				// timeWsOffset
-				//   15:04:05 -0700
-				//   timeWsOffsetWsOffset
-				//     17:57:51 -0700 -07
-				//   timeWsOffsetWs
-				//     17:57:51 -0700 2009
-				//     00:12:00 +0000 UTC
-				//   timeWsOffsetColon
-				//     15:04:05 -07:00
-				//     timeWsOffsetColonAlpha
-				//       00:12:00 +00:00 UTC
-				switch r {
-				case ':':
-					p.stateTime = timeWsOffsetColon
-				case ' ':
-					p.set(p.offseti, "-0700")
-					p.yeari = i + 1
-					p.stateTime = timeWsOffsetWs
-				}
-			case timeWsOffsetWs:
-				// 17:57:51 -0700 2009
-				// 00:12:00 +0000 UTC
-				// 22:18:00.001 +0000 UTC m=+0.000000001
-				// w Extra
-				//   17:57:51 -0700 -07
-				switch r {
-				case '=':
-					// eff you golang
-					if datestr[i-1] == 'm' {
-						p.extra = i - 2
-						p.trimExtra()
-						break
-					}
-				case '+', '-', '(':
-					// This really doesn't seem valid, but for some reason when round-tripping a go date
-					// their is an extra +03 printed out.  seems like go bug to me, but, parsing anyway.
-					// 00:00:00 +0300 +03
-					// 00:00:00 +0300 +0300
-					p.extra = i - 1
-					p.stateTime = timeWsOffset
-					p.trimExtra()
-					break
-				default:
-					switch {
-					case unicode.IsDigit(r):
-						p.yearlen = i - p.yeari + 1
-						if p.yearlen == 4 {
-							p.setYear()
-						}
-					case unicode.IsLetter(r):
-						// 15:04:05 -0700 MST
-						if p.tzi == 0 {
-							p.tzi = i
-						}
-					}
-				}
-
-			case timeWsOffsetColon:
-				// timeWsOffsetColon
-				//   15:04:05 -07:00
-				//   timeWsOffsetColonAlpha
-				//     2015-02-18 00:12:00 +00:00 UTC
-				if unicode.IsLetter(r) {
-					// 2015-02-18 00:12:00 +00:00 UTC
-					p.stateTime = timeWsOffsetColonAlpha
-					break iterTimeRunes
-				}
-			case timePeriod:
-				// 15:04:05.999999999+07:00
-				// 15:04:05.999999999-07:00
-				// 15:04:05.999999+07:00
-				// 15:04:05.999999-07:00
-				// 15:04:05.999+07:00
-				// 15:04:05.999-07:00
-				// timePeriod
-				//   17:24:37.3186369
-				//   00:07:31.945167
-				//   18:31:59.257000000
-				//   00:00:00.000
-				//   timePeriodOffset
-				//     19:55:00.799+0100
-				//     timePeriodOffsetColon
-				//       15:04:05.999-07:00
-				//   timePeriodWs
-				//     timePeriodWsOffset
-				//       00:07:31.945167 +0000
-				//       00:00:00.000 +0000
-				//       With Extra
-				//         00:00:00.000 +0300 +03
-				//     timePeriodWsOffsetAlpha
-				//       00:07:31.945167 +0000 UTC
-				//       00:00:00.000 +0000 UTC
-				//       22:18:00.001 +0000 UTC m=+0.000000001
-				//     timePeriodWsAlpha
-				//       06:20:00.000 UTC
-				switch r {
-				case ' ':
-					p.mslen = i - p.msi
-					p.stateTime = timePeriodWs
-				case '+', '-':
-					// This really shouldn't happen
-					p.mslen = i - p.msi
-					p.offseti = i
-					p.stateTime = timePeriodOffset
-				default:
-					if unicode.IsLetter(r) {
-						// 06:20:00.000 UTC
-						p.mslen = i - p.msi
-						p.stateTime = timePeriodWsAlpha
-					}
-				}
-			case timePeriodOffset:
-				// timePeriodOffset
-				//   19:55:00.799+0100
-				//   timePeriodOffsetColon
-				//     15:04:05.999-07:00
-				//     13:31:51.999-07:00 MST
-				if r == ':' {
-					p.stateTime = timePeriodOffsetColon
-				}
-			case timePeriodOffsetColon:
-				// timePeriodOffset
-				//   timePeriodOffsetColon
-				//     15:04:05.999-07:00
-				//     13:31:51.999 -07:00 MST
-				switch r {
-				case ' ':
-					p.set(p.offseti, "-07:00")
-					p.stateTime = timePeriodOffsetColonWs
-					p.tzi = i + 1
-				}
-			case timePeriodOffsetColonWs:
-				// continue
-			case timePeriodWs:
-				// timePeriodWs
-				//   timePeriodWsOffset
-				//     00:07:31.945167 +0000
-				//     00:00:00.000 +0000
-				//   timePeriodWsOffsetAlpha
-				//     00:07:31.945167 +0000 UTC
-				//     00:00:00.000 +0000 UTC
-				//   timePeriodWsOffsetColon
-				//     13:31:51.999 -07:00 MST
-				//   timePeriodWsAlpha
-				//     06:20:00.000 UTC
-				if p.offseti == 0 {
-					p.offseti = i
-				}
-				switch r {
-				case '+', '-':
-					p.mslen = i - p.msi - 1
-					p.stateTime = timePeriodWsOffset
-				default:
-					if unicode.IsLetter(r) {
-						//     00:07:31.945167 +0000 UTC
-						//     00:00:00.000 +0000 UTC
-						p.stateTime = timePeriodWsOffsetWsAlpha
-						break iterTimeRunes
-					}
-				}
-
-			case timePeriodWsOffset:
-				// timePeriodWs
-				//   timePeriodWsOffset
-				//     00:07:31.945167 +0000
-				//     00:00:00.000 +0000
-				//     With Extra
-				//       00:00:00.000 +0300 +03
-				//   timePeriodWsOffsetAlpha
-				//     00:07:31.945167 +0000 UTC
-				//     00:00:00.000 +0000 UTC
-				//     03:02:00.001 +0300 MSK m=+0.000000001
-				//   timePeriodWsOffsetColon
-				//     13:31:51.999 -07:00 MST
-				//   timePeriodWsAlpha
-				//     06:20:00.000 UTC
-				switch r {
-				case ':':
-					p.stateTime = timePeriodWsOffsetColon
-				case ' ':
-					p.set(p.offseti, "-0700")
-				case '+', '-':
-					// This really doesn't seem valid, but for some reason when round-tripping a go date
-					// their is an extra +03 printed out.  seems like go bug to me, but, parsing anyway.
-					// 00:00:00.000 +0300 +03
-					// 00:00:00.000 +0300 +0300
-					p.extra = i - 1
-					p.trimExtra()
-					break
-				default:
-					if unicode.IsLetter(r) {
-						// 00:07:31.945167 +0000 UTC
-						// 00:00:00.000 +0000 UTC
-						// 03:02:00.001 +0300 MSK m=+0.000000001
-						p.stateTime = timePeriodWsOffsetWsAlpha
-					}
-				}
-			case timePeriodWsOffsetWsAlpha:
-				// 03:02:00.001 +0300 MSK m=+0.000000001
-				// eff you golang
-				if r == '=' && datestr[i-1] == 'm' {
-					p.extra = i - 2
-					p.trimExtra()
-					break
-				}
-
-			case timePeriodWsOffsetColon:
-				// 13:31:51.999 -07:00 MST
-				switch r {
-				case ' ':
-					p.set(p.offseti, "-07:00")
-				default:
-					if unicode.IsLetter(r) {
-						// 13:31:51.999 -07:00 MST
-						p.tzi = i
-						p.stateTime = timePeriodWsOffsetColonAlpha
-					}
-				}
-			case timePeriodWsOffsetColonAlpha:
-				// continue
-			case timeZ:
-				// timeZ
-				//   15:04:05.99Z
-				// With a time-zone at end after Z
-				// 2006-01-02T15:04:05.999999999Z07:00
-				// 2006-01-02T15:04:05Z07:00
-				// RFC3339     = "2006-01-02T15:04:05Z07:00"
-				// RFC3339Nano = "2006-01-02T15:04:05.999999999Z07:00"
-				if unicode.IsDigit(r) {
-					p.stateTime = timeZDigit
-				}
-
-			}
-		}
-
-		switch p.stateTime {
-		case timeWsAlpha:
-			switch len(p.datestr) - p.tzi {
-			case 3:
-				// 13:31:51.999 +01:00 CET
-				p.set(p.tzi, "MST")
-			case 4:
-				p.set(p.tzi, "MST")
-				p.extra = len(p.datestr) - 1
-				p.trimExtra()
-			}
-
-		case timeWsAlphaWs:
-			p.yearlen = i - p.yeari
-			p.setYear()
-		case timeWsYear:
-			p.yearlen = i - p.yeari
-			p.setYear()
-		case timeWsAlphaZoneOffsetWsExtra:
-			p.trimExtra()
-		case timeWsAlphaZoneOffset:
-			// 06:20:00 UTC-05
-			if i-p.offseti < 4 {
-				p.set(p.offseti, "-07")
-			} else {
-				p.set(p.offseti, "-0700")
-			}
-
-		case timePeriod:
-			p.mslen = i - p.msi
-		case timeOffset:
-
-			switch len(p.datestr) - p.offseti {
-			case 0, 1, 2, 4:
-				return p, fmt.Errorf("TZ offset not recognized %q near %q (must be 2 or 4 digits optional colon)", datestr, string(datestr[p.offseti:]))
-			case 3:
-				// 19:55:00+01
-				p.set(p.offseti, "-07")
-			case 5:
-				// 19:55:00+0100
-				p.set(p.offseti, "-0700")
-			}
-
-		case timeWsOffset:
-			p.set(p.offseti, "-0700")
-		case timeWsOffsetWs:
-			// 17:57:51 -0700 2009
-			// 00:12:00 +0000 UTC
-			if p.tzi > 0 {
-				switch len(p.datestr) - p.tzi {
-				case 3:
-					// 13:31:51.999 +01:00 CET
-					p.set(p.tzi, "MST")
-				case 4:
-					// 13:31:51.999 +01:00 CEST
-					p.set(p.tzi, "MST ")
-				}
-
-			}
-		case timeWsOffsetColon:
-			// 17:57:51 -07:00
-			p.set(p.offseti, "-07:00")
-		case timeOffsetColon:
-			// 15:04:05+07:00
-			p.set(p.offseti, "-07:00")
-		case timePeriodOffset:
-			// 19:55:00.799+0100
-			p.set(p.offseti, "-0700")
-		case timePeriodOffsetColon:
-			p.set(p.offseti, "-07:00")
-		case timePeriodWsOffsetColonAlpha:
-			p.tzlen = i - p.tzi
-			switch p.tzlen {
-			case 3:
-				p.set(p.tzi, "MST")
-			case 4:
-				p.set(p.tzi, "MST ")
-			}
-		case timePeriodWsOffset:
-			p.set(p.offseti, "-0700")
-		}
-		p.coalesceTime(i)
-	}
-
-	switch p.stateDate {
-	case dateDigit:
-		// unixy timestamps ish
-		//  example              ct type
-		//  1499979655583057426  19 nanoseconds
-		//  1499979795437000     16 micro-seconds
-		//  20180722105203       14 yyyyMMddhhmmss
-		//  1499979795437        13 milliseconds
-		//  1332151919           10 seconds
-		//  20140601             8  yyyymmdd
-		//  2014                 4  yyyy
-		t := time.Time{}
-		if len(datestr) == len("1499979655583057426") { // 19
-			// nano-seconds
-			if nanoSecs, err := strconv.ParseInt(datestr, 10, 64); err == nil {
-				t = time.Unix(0, nanoSecs)
-			}
-		} else if len(datestr) == len("1499979795437000") { // 16
-			// micro-seconds
-			if microSecs, err := strconv.ParseInt(datestr, 10, 64); err == nil {
-				t = time.Unix(0, microSecs*1000)
-			}
-		} else if len(datestr) == len("yyyyMMddhhmmss") { // 14
-			// yyyyMMddhhmmss
-			p.format = []byte("20060102150405")
-			return p, nil
-		} else if len(datestr) == len("1332151919000") { // 13
-			if miliSecs, err := strconv.ParseInt(datestr, 10, 64); err == nil {
-				t = time.Unix(0, miliSecs*1000*1000)
-			}
-		} else if len(datestr) == len("1332151919") { //10
-			if secs, err := strconv.ParseInt(datestr, 10, 64); err == nil {
-				t = time.Unix(secs, 0)
-			}
-		} else if len(datestr) == len("20140601") {
-			p.format = []byte("20060102")
-			return p, nil
-		} else if len(datestr) == len("2014") {
-			p.format = []byte("2006")
-			return p, nil
-		} else if len(datestr) < 4 {
-			return nil, fmt.Errorf("unrecognized format, too short %v", datestr)
-		}
-		if !t.IsZero() {
-			if loc == nil {
-				p.t = &t
-				return p, nil
-			}
-			t = t.In(loc)
-			p.t = &t
-			return p, nil
-		}
-	case dateDigitSt:
-		// 171113 14:14:20
-		return p, nil
-
-	case dateYearDash:
-		// 2006-01
-		return p, nil
-
-	case dateYearDashDash:
-		// 2006-01-02
-		// 2006-1-02
-		// 2006-1-2
-		// 2006-01-2
-		return p, nil
-
-	case dateYearDashDashOffset:
-		///  2020-07-20+00:00
-		switch len(p.datestr) - p.offseti {
-		case 5:
-			p.set(p.offseti, "-0700")
-		case 6:
-			p.set(p.offseti, "-07:00")
-		}
-		return p, nil
-
-	case dateYearDashAlphaDash:
-		// 2013-Feb-03
-		// 2013-Feb-3
-		p.daylen = i - p.dayi
-		p.setDay()
-		return p, nil
-
-	case dateYearDashDashWs:
-		// 2013-04-01
-		return p, nil
-
-	case dateYearDashDashT:
-		return p, nil
-
-	case dateDigitDashAlphaDash:
-		// 13-Feb-03   ambiguous
-		// 28-Feb-03   ambiguous
-		// 29-Jun-2016
-		length := len(datestr) - (p.moi + p.molen + 1)
-		if length == 4 {
-			p.yearlen = 4
-			p.set(p.yeari, "2006")
-			// We now also know that part1 was the day
-			p.dayi = 0
-			p.daylen = p.part1Len
-			p.setDay()
-		} else if length == 2 {
-			// We have no idea if this is
-			// yy-mon-dd   OR  dd-mon-yy
-			//
-			// We are going to ASSUME (bad, bad) that it is dd-mon-yy  which is a horible assumption
-			p.ambiguousMD = true
-			p.yearlen = 2
-			p.set(p.yeari, "06")
-			// We now also know that part1 was the day
-			p.dayi = 0
-			p.daylen = p.part1Len
-			p.setDay()
-		}
-
-		return p, nil
-
-	case dateDigitDot:
-		// 2014.05
-		p.molen = i - p.moi
-		p.setMonth()
-		return p, nil
-
-	case dateDigitDotDot:
-		// 03.31.1981
-		// 3.31.2014
-		// 3.2.1981
-		// 3.2.81
-		// 08.21.71
-		// 2018.09.30
-		return p, nil
-
-	case dateDigitWsMoYear:
-		// 2 Jan 2018
-		// 2 Jan 18
-		// 2 Jan 2018 23:59
-		// 02 Jan 2018 23:59
-		// 12 Feb 2006, 19:17
-		return p, nil
-
-	case dateDigitWsMolong:
-		// 18 January 2018
-		// 8 January 2018
-		if p.daylen == 2 {
-			p.format = []byte("02 January 2006")
-			return p, nil
-		}
-		p.format = []byte("2 January 2006")
-		return p, nil // parse("2 January 2006", datestr, loc)
-
-	case dateAlphaWsMonth:
-		p.yearlen = i - p.yeari
-		p.setYear()
-		return p, nil
-
-	case dateAlphaWsMonthMore:
-		return p, nil
-
-	case dateAlphaWsDigitMoreWs:
-		// oct 1, 1970
-		p.yearlen = i - p.yeari
-		p.setYear()
-		return p, nil
-
-	case dateAlphaWsDigitMoreWsYear:
-		// May 8, 2009 5:57:51 PM
-		// Jun 7, 2005, 05:57:51
-		return p, nil
-
-	case dateAlphaWsAlpha:
-		return p, nil
-
-	case dateAlphaWsDigit:
-		return p, nil
-
-	case dateAlphaWsDigitYearmaybe:
-		return p, nil
-
-	case dateDigitSlash:
-		// 3/1/2014
-		// 10/13/2014
-		// 01/02/2006
-		return p, nil
-
-	case dateDigitSlashAlpha:
-		// 03/Jun/2014
-		return p, nil
-
-	case dateDigitYearSlash:
-		// 2014/10/13
-		return p, nil
-
-	case dateDigitColon:
-		// 3:1:2014
-		// 10:13:2014
-		// 01:02:2006
-		// 2014:10:13
-		return p, nil
-
-	case dateDigitChineseYear:
-		// dateDigitChineseYear
-		//   2014年04月08日
-		p.format = []byte("2006年01月02日")
-		return p, nil
-
-	case dateDigitChineseYearWs:
-		p.format = []byte("2006年01月02日 15:04:05")
-		return p, nil
-
-	case dateWeekdayComma:
-		// Monday, 02 Jan 2006 15:04:05 -0700
-		// Monday, 02 Jan 2006 15:04:05 +0100
-		// Monday, 02-Jan-06 15:04:05 MST
-		return p, nil
-
-	case dateWeekdayAbbrevComma:
-		// Mon, 02-Jan-06 15:04:05 MST
-		// Mon, 02 Jan 2006 15:04:05 MST
-		return p, nil
-
-	}
-
-	return nil, unknownErr(datestr)
-}
-
-type parser struct {
-	loc                        *time.Location
-	preferMonthFirst           bool
-	retryAmbiguousDateWithSwap bool
-	ambiguousMD                bool
-	stateDate                  dateState
-	stateTime                  timeState
-	format                     []byte
-	datestr                    string
-	fullMonth                  string
-	skip                       int
-	extra                      int
-	part1Len                   int
-	yeari                      int
-	yearlen                    int
-	moi                        int
-	molen                      int
-	dayi                       int
-	daylen                     int
-	houri                      int
-	hourlen                    int
-	mini                       int
-	minlen                     int
-	seci                       int
-	seclen                     int
-	msi                        int
-	mslen                      int
-	offseti                    int
-	offsetlen                  int
-	tzi                        int
-	tzlen                      int
-	t                          *time.Time
-}
-
-// ParserOption defines a function signature implemented by options
-// Options defined like this accept the parser and operate on the data within
-type ParserOption func(*parser) error
-
-// PreferMonthFirst is an option that allows preferMonthFirst to be changed from its default
-func PreferMonthFirst(preferMonthFirst bool) ParserOption {
-	return func(p *parser) error {
-		p.preferMonthFirst = preferMonthFirst
-		return nil
-	}
-}
-
-// RetryAmbiguousDateWithSwap is an option that allows retryAmbiguousDateWithSwap to be changed from its default
-func RetryAmbiguousDateWithSwap(retryAmbiguousDateWithSwap bool) ParserOption {
-	return func(p *parser) error {
-		p.retryAmbiguousDateWithSwap = retryAmbiguousDateWithSwap
-		return nil
-	}
-}
-
-func newParser(dateStr string, loc *time.Location, opts ...ParserOption) *parser {
-	p := &parser{
-		stateDate:                  dateStart,
-		stateTime:                  timeIgnore,
-		datestr:                    dateStr,
-		loc:                        loc,
-		preferMonthFirst:           true,
-		retryAmbiguousDateWithSwap: false,
-	}
-	p.format = []byte(dateStr)
-
-	// allow the options to mutate the parser fields from their defaults
-	for _, option := range opts {
-		option(p)
-	}
-	return p
-}
-
-func (p *parser) nextIs(i int, b byte) bool {
-	if len(p.datestr) > i+1 && p.datestr[i+1] == b {
-		return true
-	}
-	return false
-}
-
-func (p *parser) set(start int, val string) {
-	if start < 0 {
-		return
-	}
-	if len(p.format) < start+len(val) {
-		return
-	}
-	for i, r := range val {
-		p.format[start+i] = byte(r)
-	}
-}
-func (p *parser) setMonth() {
-	if p.molen == 2 {
-		p.set(p.moi, "01")
-	} else if p.molen == 1 {
-		p.set(p.moi, "1")
-	}
-}
-
-func (p *parser) setDay() {
-	if p.daylen == 2 {
-		p.set(p.dayi, "02")
-	} else if p.daylen == 1 {
-		p.set(p.dayi, "2")
-	}
-}
-func (p *parser) setYear() {
-	if p.yearlen == 2 {
-		p.set(p.yeari, "06")
-	} else if p.yearlen == 4 {
-		p.set(p.yeari, "2006")
-	}
-}
-func (p *parser) coalesceDate(end int) {
-	if p.yeari > 0 {
-		if p.yearlen == 0 {
-			p.yearlen = end - p.yeari
-		}
-		p.setYear()
-	}
-	if p.moi > 0 && p.molen == 0 {
-		p.molen = end - p.moi
-		p.setMonth()
-	}
-	if p.dayi > 0 && p.daylen == 0 {
-		p.daylen = end - p.dayi
-		p.setDay()
-	}
-}
-func (p *parser) ts() string {
-	return fmt.Sprintf("h:(%d:%d) m:(%d:%d) s:(%d:%d)", p.houri, p.hourlen, p.mini, p.minlen, p.seci, p.seclen)
-}
-func (p *parser) ds() string {
-	return fmt.Sprintf("%s d:(%d:%d) m:(%d:%d) y:(%d:%d)", p.datestr, p.dayi, p.daylen, p.moi, p.molen, p.yeari, p.yearlen)
-}
-func (p *parser) coalesceTime(end int) {
-	// 03:04:05
-	// 15:04:05
-	// 3:04:05
-	// 3:4:5
-	// 15:04:05.00
-	if p.houri > 0 {
-		if p.hourlen == 2 {
-			p.set(p.houri, "15")
-		} else if p.hourlen == 1 {
-			p.set(p.houri, "3")
-		}
-	}
-	if p.mini > 0 {
-		if p.minlen == 0 {
-			p.minlen = end - p.mini
-		}
-		if p.minlen == 2 {
-			p.set(p.mini, "04")
-		} else {
-			p.set(p.mini, "4")
-		}
-	}
-	if p.seci > 0 {
-		if p.seclen == 0 {
-			p.seclen = end - p.seci
-		}
-		if p.seclen == 2 {
-			p.set(p.seci, "05")
-		} else {
-			p.set(p.seci, "5")
-		}
-	}
-
-	if p.msi > 0 {
-		for i := 0; i < p.mslen; i++ {
-			p.format[p.msi+i] = '0'
-		}
-	}
-}
-func (p *parser) setFullMonth(month string) {
-	if p.moi == 0 {
-		p.format = []byte(fmt.Sprintf("%s%s", "January", p.format[len(month):]))
-	}
-}
-
-func (p *parser) trimExtra() {
-	if p.extra > 0 && len(p.format) > p.extra {
-		p.format = p.format[0:p.extra]
-		p.datestr = p.datestr[0:p.extra]
-	}
-}
-
-// func (p *parser) remove(i, length int) {
-// 	if len(p.format) > i+length {
-// 		//append(a[:i], a[j:]...)
-// 		p.format = append(p.format[0:i], p.format[i+length:]...)
-// 	}
-// 	if len(p.datestr) > i+length {
-// 		//append(a[:i], a[j:]...)
-// 		p.datestr = fmt.Sprintf("%s%s", p.datestr[0:i], p.datestr[i+length:])
-// 	}
-// }
-
-func (p *parser) parse() (time.Time, error) {
-	if p.t != nil {
-		return *p.t, nil
-	}
-	if len(p.fullMonth) > 0 {
-		p.setFullMonth(p.fullMonth)
-	}
-	if p.skip > 0 && len(p.format) > p.skip {
-		p.format = p.format[p.skip:]
-		p.datestr = p.datestr[p.skip:]
-	}
-
-	if p.loc == nil {
-		// gou.Debugf("parse layout=%q input=%q   \ntx, err := time.Parse(%q, %q)", string(p.format), p.datestr, string(p.format), p.datestr)
-		return time.Parse(string(p.format), p.datestr)
-	}
-	//gou.Debugf("parse layout=%q input=%q   \ntx, err := time.ParseInLocation(%q, %q, %v)", string(p.format), p.datestr, string(p.format), p.datestr, p.loc)
-	return time.ParseInLocation(string(p.format), p.datestr, p.loc)
-}
-func isDay(alpha string) bool {
-	for _, day := range days {
-		if alpha == day {
-			return true
-		}
-	}
-	return false
-}
-func isMonthFull(alpha string) bool {
-	for _, month := range months {
-		if alpha == month {
-			return true
-		}
-	}
-	return false
-}
diff --git a/vendor/github.com/jinzhu/now/Guardfile b/vendor/github.com/jinzhu/now/Guardfile
new file mode 100644
index 00000000000..0b860b0653c
--- /dev/null
+++ b/vendor/github.com/jinzhu/now/Guardfile
@@ -0,0 +1,3 @@
+guard 'gotest' do
+  watch(%r{\.go$})
+end
diff --git a/vendor/github.com/araddon/dateparse/LICENSE b/vendor/github.com/jinzhu/now/License
similarity index 95%
rename from vendor/github.com/araddon/dateparse/LICENSE
rename to vendor/github.com/jinzhu/now/License
index f675ed313ac..037e1653e69 100644
--- a/vendor/github.com/araddon/dateparse/LICENSE
+++ b/vendor/github.com/jinzhu/now/License
@@ -1,6 +1,6 @@
 The MIT License (MIT)
 
-Copyright (c) 2015-2017 Aaron Raddon
+Copyright (c) 2013-NOW  Jinzhu <wosmvp@gmail.com>
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
diff --git a/vendor/github.com/jinzhu/now/README.md b/vendor/github.com/jinzhu/now/README.md
new file mode 100644
index 00000000000..e81d31de6a7
--- /dev/null
+++ b/vendor/github.com/jinzhu/now/README.md
@@ -0,0 +1,137 @@
+## Now
+
+Now is a time toolkit for golang
+
+[![go report card](https://goreportcard.com/badge/github.com/jinzhu/now "go report card")](https://goreportcard.com/report/github.com/jinzhu/now)
+[![test status](https://github.com/jinzhu/now/workflows/tests/badge.svg?branch=master "test status")](https://github.com/jinzhu/now/actions)
+[![MIT license](https://img.shields.io/badge/license-MIT-brightgreen.svg)](https://opensource.org/licenses/MIT)
+
+## Install
+
+```
+go get -u github.com/jinzhu/now
+```
+
+## Usage
+
+Calculating time based on current time
+
+```go
+import "github.com/jinzhu/now"
+
+time.Now() // 2013-11-18 17:51:49.123456789 Mon
+
+now.BeginningOfMinute()        // 2013-11-18 17:51:00 Mon
+now.BeginningOfHour()          // 2013-11-18 17:00:00 Mon
+now.BeginningOfDay()           // 2013-11-18 00:00:00 Mon
+now.BeginningOfWeek()          // 2013-11-17 00:00:00 Sun
+now.BeginningOfMonth()         // 2013-11-01 00:00:00 Fri
+now.BeginningOfQuarter()       // 2013-10-01 00:00:00 Tue
+now.BeginningOfYear()          // 2013-01-01 00:00:00 Tue
+
+now.EndOfMinute()              // 2013-11-18 17:51:59.999999999 Mon
+now.EndOfHour()                // 2013-11-18 17:59:59.999999999 Mon
+now.EndOfDay()                 // 2013-11-18 23:59:59.999999999 Mon
+now.EndOfWeek()                // 2013-11-23 23:59:59.999999999 Sat
+now.EndOfMonth()               // 2013-11-30 23:59:59.999999999 Sat
+now.EndOfQuarter()             // 2013-12-31 23:59:59.999999999 Tue
+now.EndOfYear()                // 2013-12-31 23:59:59.999999999 Tue
+
+now.WeekStartDay = time.Monday // Set Monday as first day, default is Sunday
+now.EndOfWeek()                // 2013-11-24 23:59:59.999999999 Sun
+```
+
+Calculating time based on another time
+
+```go
+t := time.Date(2013, 02, 18, 17, 51, 49, 123456789, time.Now().Location())
+now.With(t).EndOfMonth()   // 2013-02-28 23:59:59.999999999 Thu
+```
+
+Calculating time based on configuration
+
+```go
+location, err := time.LoadLocation("Asia/Shanghai")
+
+myConfig := &now.Config{
+	WeekStartDay: time.Monday,
+	TimeLocation: location,
+	TimeFormats: []string{"2006-01-02 15:04:05"},
+}
+
+t := time.Date(2013, 11, 18, 17, 51, 49, 123456789, time.Now().Location()) // // 2013-11-18 17:51:49.123456789 Mon
+myConfig.With(t).BeginningOfWeek()         // 2013-11-18 00:00:00 Mon
+
+myConfig.Parse("2002-10-12 22:14:01")     // 2002-10-12 22:14:01
+myConfig.Parse("2002-10-12 22:14")        // returns error 'can't parse string as time: 2002-10-12 22:14'
+```
+
+### Monday/Sunday
+
+Don't be bothered with the `WeekStartDay` setting, you can use `Monday`, `Sunday`
+
+```go
+now.Monday()              // 2013-11-18 00:00:00 Mon
+now.Monday("17:44")       // 2013-11-18 17:44:00 Mon
+now.Sunday()              // 2013-11-24 00:00:00 Sun (Next Sunday)
+now.Sunday("18:19:24")    // 2013-11-24 18:19:24 Sun (Next Sunday)
+now.EndOfSunday()         // 2013-11-24 23:59:59.999999999 Sun (End of next Sunday)
+
+t := time.Date(2013, 11, 24, 17, 51, 49, 123456789, time.Now().Location()) // 2013-11-24 17:51:49.123456789 Sun
+now.With(t).Monday()              // 2013-11-18 00:00:00 Mon (Last Monday if today is Sunday)
+now.With(t).Monday("17:44")       // 2013-11-18 17:44:00 Mon (Last Monday if today is Sunday)
+now.With(t).Sunday()              // 2013-11-24 00:00:00 Sun (Beginning Of Today if today is Sunday)
+now.With(t).Sunday("18:19:24")    // 2013-11-24 18:19:24 Sun (Beginning Of Today if today is Sunday)
+now.With(t).EndOfSunday()         // 2013-11-24 23:59:59.999999999 Sun (End of Today if today is Sunday)
+```
+
+### Parse String to Time
+
+```go
+time.Now() // 2013-11-18 17:51:49.123456789 Mon
+
+// Parse(string) (time.Time, error)
+t, err := now.Parse("2017")                // 2017-01-01 00:00:00, nil
+t, err := now.Parse("2017-10")             // 2017-10-01 00:00:00, nil
+t, err := now.Parse("2017-10-13")          // 2017-10-13 00:00:00, nil
+t, err := now.Parse("1999-12-12 12")       // 1999-12-12 12:00:00, nil
+t, err := now.Parse("1999-12-12 12:20")    // 1999-12-12 12:20:00, nil
+t, err := now.Parse("1999-12-12 12:20:21") // 1999-12-12 12:20:21, nil
+t, err := now.Parse("10-13")               // 2013-10-13 00:00:00, nil
+t, err := now.Parse("12:20")               // 2013-11-18 12:20:00, nil
+t, err := now.Parse("12:20:13")            // 2013-11-18 12:20:13, nil
+t, err := now.Parse("14")                  // 2013-11-18 14:00:00, nil
+t, err := now.Parse("99:99")               // 2013-11-18 12:20:00, Can't parse string as time: 99:99
+
+// MustParse must parse string to time or it will panic
+now.MustParse("2013-01-13")             // 2013-01-13 00:00:00
+now.MustParse("02-17")                  // 2013-02-17 00:00:00
+now.MustParse("2-17")                   // 2013-02-17 00:00:00
+now.MustParse("8")                      // 2013-11-18 08:00:00
+now.MustParse("2002-10-12 22:14")       // 2002-10-12 22:14:00
+now.MustParse("99:99")                  // panic: Can't parse string as time: 99:99
+```
+
+Extend `now` to support more formats is quite easy, just update `now.TimeFormats` with other time layouts, e.g:
+
+```go
+now.TimeFormats = append(now.TimeFormats, "02 Jan 2006 15:04")
+```
+
+Please send me pull requests if you want a format to be supported officially
+
+## Contributing
+
+You can help to make the project better, check out [http://gorm.io/contribute.html](http://gorm.io/contribute.html) for things you can do.
+
+# Author
+
+**jinzhu**
+
+* <http://github.com/jinzhu>
+* <wosmvp@gmail.com>
+* <http://twitter.com/zhangjinzhu>
+
+## License
+
+Released under the [MIT License](http://www.opensource.org/licenses/MIT).
diff --git a/vendor/github.com/jinzhu/now/main.go b/vendor/github.com/jinzhu/now/main.go
new file mode 100644
index 00000000000..8f78bc75219
--- /dev/null
+++ b/vendor/github.com/jinzhu/now/main.go
@@ -0,0 +1,200 @@
+// Package now is a time toolkit for golang.
+//
+// More details README here: https://github.com/jinzhu/now
+//
+//  import "github.com/jinzhu/now"
+//
+//  now.BeginningOfMinute() // 2013-11-18 17:51:00 Mon
+//  now.BeginningOfDay()    // 2013-11-18 00:00:00 Mon
+//  now.EndOfDay()          // 2013-11-18 23:59:59.999999999 Mon
+package now
+
+import "time"
+
+// WeekStartDay set week start day, default is sunday
+var WeekStartDay = time.Sunday
+
+// TimeFormats default time formats will be parsed as
+var TimeFormats = []string{
+	"2006", "2006-1", "2006-1-2", "2006-1-2 15", "2006-1-2 15:4", "2006-1-2 15:4:5", "1-2",
+	"15:4:5", "15:4", "15",
+	"15:4:5 Jan 2, 2006 MST", "2006-01-02 15:04:05.999999999 -0700 MST", "2006-01-02T15:04:05Z0700", "2006-01-02T15:04:05Z07",
+	"2006.1.2", "2006.1.2 15:04:05", "2006.01.02", "2006.01.02 15:04:05", "2006.01.02 15:04:05.999999999",
+	"1/2/2006", "1/2/2006 15:4:5", "2006/01/02", "20060102", "2006/01/02 15:04:05",
+	time.ANSIC, time.UnixDate, time.RubyDate, time.RFC822, time.RFC822Z, time.RFC850,
+	time.RFC1123, time.RFC1123Z, time.RFC3339, time.RFC3339Nano,
+	time.Kitchen, time.Stamp, time.StampMilli, time.StampMicro, time.StampNano,
+}
+
+// Config configuration for now package
+type Config struct {
+	WeekStartDay time.Weekday
+	TimeLocation *time.Location
+	TimeFormats  []string
+}
+
+// DefaultConfig default config
+var DefaultConfig *Config
+
+// New initialize Now based on configuration
+func (config *Config) With(t time.Time) *Now {
+	return &Now{Time: t, Config: config}
+}
+
+// Parse parse string to time based on configuration
+func (config *Config) Parse(strs ...string) (time.Time, error) {
+	if config.TimeLocation == nil {
+		return config.With(time.Now()).Parse(strs...)
+	} else {
+		return config.With(time.Now().In(config.TimeLocation)).Parse(strs...)
+	}
+}
+
+// MustParse must parse string to time or will panic
+func (config *Config) MustParse(strs ...string) time.Time {
+	if config.TimeLocation == nil {
+		return config.With(time.Now()).MustParse(strs...)
+	} else {
+		return config.With(time.Now().In(config.TimeLocation)).MustParse(strs...)
+	}
+}
+
+// Now now struct
+type Now struct {
+	time.Time
+	*Config
+}
+
+// With initialize Now with time
+func With(t time.Time) *Now {
+	config := DefaultConfig
+	if config == nil {
+		config = &Config{
+			WeekStartDay: WeekStartDay,
+			TimeFormats:  TimeFormats,
+		}
+	}
+
+	return &Now{Time: t, Config: config}
+}
+
+// New initialize Now with time
+func New(t time.Time) *Now {
+	return With(t)
+}
+
+// BeginningOfMinute beginning of minute
+func BeginningOfMinute() time.Time {
+	return With(time.Now()).BeginningOfMinute()
+}
+
+// BeginningOfHour beginning of hour
+func BeginningOfHour() time.Time {
+	return With(time.Now()).BeginningOfHour()
+}
+
+// BeginningOfDay beginning of day
+func BeginningOfDay() time.Time {
+	return With(time.Now()).BeginningOfDay()
+}
+
+// BeginningOfWeek beginning of week
+func BeginningOfWeek() time.Time {
+	return With(time.Now()).BeginningOfWeek()
+}
+
+// BeginningOfMonth beginning of month
+func BeginningOfMonth() time.Time {
+	return With(time.Now()).BeginningOfMonth()
+}
+
+// BeginningOfQuarter beginning of quarter
+func BeginningOfQuarter() time.Time {
+	return With(time.Now()).BeginningOfQuarter()
+}
+
+// BeginningOfYear beginning of year
+func BeginningOfYear() time.Time {
+	return With(time.Now()).BeginningOfYear()
+}
+
+// EndOfMinute end of minute
+func EndOfMinute() time.Time {
+	return With(time.Now()).EndOfMinute()
+}
+
+// EndOfHour end of hour
+func EndOfHour() time.Time {
+	return With(time.Now()).EndOfHour()
+}
+
+// EndOfDay end of day
+func EndOfDay() time.Time {
+	return With(time.Now()).EndOfDay()
+}
+
+// EndOfWeek end of week
+func EndOfWeek() time.Time {
+	return With(time.Now()).EndOfWeek()
+}
+
+// EndOfMonth end of month
+func EndOfMonth() time.Time {
+	return With(time.Now()).EndOfMonth()
+}
+
+// EndOfQuarter end of quarter
+func EndOfQuarter() time.Time {
+	return With(time.Now()).EndOfQuarter()
+}
+
+// EndOfYear end of year
+func EndOfYear() time.Time {
+	return With(time.Now()).EndOfYear()
+}
+
+// Monday monday
+
+func Monday(strs ...string) time.Time {
+	return With(time.Now()).Monday(strs...)
+}
+
+// Sunday sunday
+func Sunday(strs ...string) time.Time {
+	return With(time.Now()).Sunday(strs...)
+}
+
+// EndOfSunday end of sunday
+func EndOfSunday() time.Time {
+	return With(time.Now()).EndOfSunday()
+}
+
+// Quarter returns the yearly quarter
+func Quarter() uint {
+	return With(time.Now()).Quarter()
+}
+
+// Parse parse string to time
+func Parse(strs ...string) (time.Time, error) {
+	return With(time.Now()).Parse(strs...)
+}
+
+// ParseInLocation parse string to time in location
+func ParseInLocation(loc *time.Location, strs ...string) (time.Time, error) {
+	return With(time.Now().In(loc)).Parse(strs...)
+}
+
+// MustParse must parse string to time or will panic
+func MustParse(strs ...string) time.Time {
+	return With(time.Now()).MustParse(strs...)
+}
+
+// MustParseInLocation must parse string to time in location or will panic
+func MustParseInLocation(loc *time.Location, strs ...string) time.Time {
+	return With(time.Now().In(loc)).MustParse(strs...)
+}
+
+// Between check now between the begin, end time or not
+func Between(time1, time2 string) bool {
+	return With(time.Now()).Between(time1, time2)
+}
diff --git a/vendor/github.com/jinzhu/now/now.go b/vendor/github.com/jinzhu/now/now.go
new file mode 100644
index 00000000000..2f524cc8d4b
--- /dev/null
+++ b/vendor/github.com/jinzhu/now/now.go
@@ -0,0 +1,245 @@
+package now
+
+import (
+	"errors"
+	"regexp"
+	"time"
+)
+
+// BeginningOfMinute beginning of minute
+func (now *Now) BeginningOfMinute() time.Time {
+	return now.Truncate(time.Minute)
+}
+
+// BeginningOfHour beginning of hour
+func (now *Now) BeginningOfHour() time.Time {
+	y, m, d := now.Date()
+	return time.Date(y, m, d, now.Time.Hour(), 0, 0, 0, now.Time.Location())
+}
+
+// BeginningOfDay beginning of day
+func (now *Now) BeginningOfDay() time.Time {
+	y, m, d := now.Date()
+	return time.Date(y, m, d, 0, 0, 0, 0, now.Time.Location())
+}
+
+// BeginningOfWeek beginning of week
+func (now *Now) BeginningOfWeek() time.Time {
+	t := now.BeginningOfDay()
+	weekday := int(t.Weekday())
+
+	if now.WeekStartDay != time.Sunday {
+		weekStartDayInt := int(now.WeekStartDay)
+
+		if weekday < weekStartDayInt {
+			weekday = weekday + 7 - weekStartDayInt
+		} else {
+			weekday = weekday - weekStartDayInt
+		}
+	}
+	return t.AddDate(0, 0, -weekday)
+}
+
+// BeginningOfMonth beginning of month
+func (now *Now) BeginningOfMonth() time.Time {
+	y, m, _ := now.Date()
+	return time.Date(y, m, 1, 0, 0, 0, 0, now.Location())
+}
+
+// BeginningOfQuarter beginning of quarter
+func (now *Now) BeginningOfQuarter() time.Time {
+	month := now.BeginningOfMonth()
+	offset := (int(month.Month()) - 1) % 3
+	return month.AddDate(0, -offset, 0)
+}
+
+// BeginningOfHalf beginning of half year
+func (now *Now) BeginningOfHalf() time.Time {
+	month := now.BeginningOfMonth()
+	offset := (int(month.Month()) - 1) % 6
+	return month.AddDate(0, -offset, 0)
+}
+
+// BeginningOfYear BeginningOfYear beginning of year
+func (now *Now) BeginningOfYear() time.Time {
+	y, _, _ := now.Date()
+	return time.Date(y, time.January, 1, 0, 0, 0, 0, now.Location())
+}
+
+// EndOfMinute end of minute
+func (now *Now) EndOfMinute() time.Time {
+	return now.BeginningOfMinute().Add(time.Minute - time.Nanosecond)
+}
+
+// EndOfHour end of hour
+func (now *Now) EndOfHour() time.Time {
+	return now.BeginningOfHour().Add(time.Hour - time.Nanosecond)
+}
+
+// EndOfDay end of day
+func (now *Now) EndOfDay() time.Time {
+	y, m, d := now.Date()
+	return time.Date(y, m, d, 23, 59, 59, int(time.Second-time.Nanosecond), now.Location())
+}
+
+// EndOfWeek end of week
+func (now *Now) EndOfWeek() time.Time {
+	return now.BeginningOfWeek().AddDate(0, 0, 7).Add(-time.Nanosecond)
+}
+
+// EndOfMonth end of month
+func (now *Now) EndOfMonth() time.Time {
+	return now.BeginningOfMonth().AddDate(0, 1, 0).Add(-time.Nanosecond)
+}
+
+// EndOfQuarter end of quarter
+func (now *Now) EndOfQuarter() time.Time {
+	return now.BeginningOfQuarter().AddDate(0, 3, 0).Add(-time.Nanosecond)
+}
+
+// EndOfHalf end of half year
+func (now *Now) EndOfHalf() time.Time {
+	return now.BeginningOfHalf().AddDate(0, 6, 0).Add(-time.Nanosecond)
+}
+
+// EndOfYear end of year
+func (now *Now) EndOfYear() time.Time {
+	return now.BeginningOfYear().AddDate(1, 0, 0).Add(-time.Nanosecond)
+}
+
+// Monday monday
+/*
+func (now *Now) Monday() time.Time {
+	t := now.BeginningOfDay()
+	weekday := int(t.Weekday())
+	if weekday == 0 {
+		weekday = 7
+	}
+	return t.AddDate(0, 0, -weekday+1)
+}
+*/
+
+func (now *Now) Monday(strs ...string) time.Time {
+	var parseTime time.Time
+	var err error
+	if len(strs) > 0 {
+		parseTime, err = now.Parse(strs...)
+		if err != nil {
+			panic(err)
+		}
+	} else {
+		parseTime = now.BeginningOfDay()
+	}
+	weekday := int(parseTime.Weekday())
+	if weekday == 0 {
+		weekday = 7
+	}
+	return parseTime.AddDate(0, 0, -weekday+1)
+}
+
+func (now *Now) Sunday(strs ...string) time.Time {
+	var parseTime time.Time
+	var err error
+	if len(strs) > 0 {
+		parseTime, err = now.Parse(strs...)
+		if err != nil {
+			panic(err)
+		}
+	} else {
+		parseTime = now.BeginningOfDay()
+	}
+	weekday := int(parseTime.Weekday())
+	if weekday == 0 {
+		weekday = 7
+	}
+	return parseTime.AddDate(0, 0, (7 - weekday))
+}
+
+// EndOfSunday end of sunday
+func (now *Now) EndOfSunday() time.Time {
+	return New(now.Sunday()).EndOfDay()
+}
+
+// Quarter returns the yearly quarter
+func (now *Now) Quarter() uint {
+	return (uint(now.Month())-1)/3 + 1
+}
+
+func (now *Now) parseWithFormat(str string, location *time.Location) (t time.Time, err error) {
+	for _, format := range now.TimeFormats {
+		t, err = time.ParseInLocation(format, str, location)
+
+		if err == nil {
+			return
+		}
+	}
+	err = errors.New("Can't parse string as time: " + str)
+	return
+}
+
+var hasTimeRegexp = regexp.MustCompile(`(\s+|^\s*|T)\d{1,2}((:\d{1,2})*|((:\d{1,2}){2}\.(\d{3}|\d{6}|\d{9})))(\s*$|[Z+-])`) // match 15:04:05, 15:04:05.000, 15:04:05.000000 15, 2017-01-01 15:04, 2021-07-20T00:59:10Z, 2021-07-20T00:59:10+08:00, 2021-07-20T00:00:10-07:00 etc
+var onlyTimeRegexp = regexp.MustCompile(`^\s*\d{1,2}((:\d{1,2})*|((:\d{1,2}){2}\.(\d{3}|\d{6}|\d{9})))\s*$`)            // match 15:04:05, 15, 15:04:05.000, 15:04:05.000000, etc
+
+// Parse parse string to time
+func (now *Now) Parse(strs ...string) (t time.Time, err error) {
+	var (
+		setCurrentTime  bool
+		parseTime       []int
+		currentLocation = now.Location()
+		onlyTimeInStr   = true
+		currentTime     = formatTimeToList(now.Time)
+	)
+
+	for _, str := range strs {
+		hasTimeInStr := hasTimeRegexp.MatchString(str) // match 15:04:05, 15
+		onlyTimeInStr = hasTimeInStr && onlyTimeInStr && onlyTimeRegexp.MatchString(str)
+		if t, err = now.parseWithFormat(str, currentLocation); err == nil {
+			location := t.Location()
+			parseTime = formatTimeToList(t)
+
+			for i, v := range parseTime {
+				// Don't reset hour, minute, second if current time str including time
+				if hasTimeInStr && i <= 3 {
+					continue
+				}
+
+				// If value is zero, replace it with current time
+				if v == 0 {
+					if setCurrentTime {
+						parseTime[i] = currentTime[i]
+					}
+				} else {
+					setCurrentTime = true
+				}
+
+				// if current time only includes time, should change day, month to current time
+				if onlyTimeInStr {
+					if i == 4 || i == 5 {
+						parseTime[i] = currentTime[i]
+						continue
+					}
+				}
+			}
+
+			t = time.Date(parseTime[6], time.Month(parseTime[5]), parseTime[4], parseTime[3], parseTime[2], parseTime[1], parseTime[0], location)
+			currentTime = formatTimeToList(t)
+		}
+	}
+	return
+}
+
+// MustParse must parse string to time or it will panic
+func (now *Now) MustParse(strs ...string) (t time.Time) {
+	t, err := now.Parse(strs...)
+	if err != nil {
+		panic(err)
+	}
+	return t
+}
+
+// Between check time between the begin, end time or not
+func (now *Now) Between(begin, end string) bool {
+	beginTime := now.MustParse(begin)
+	endTime := now.MustParse(end)
+	return now.After(beginTime) && now.Before(endTime)
+}
diff --git a/vendor/github.com/jinzhu/now/time.go b/vendor/github.com/jinzhu/now/time.go
new file mode 100644
index 00000000000..52dd8b2a098
--- /dev/null
+++ b/vendor/github.com/jinzhu/now/time.go
@@ -0,0 +1,9 @@
+package now
+
+import "time"
+
+func formatTimeToList(t time.Time) []int {
+	hour, min, sec := t.Clock()
+	year, month, day := t.Date()
+	return []int{t.Nanosecond(), sec, min, hour, day, int(month), year}
+}
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 254a1cd480f..bcb4678afcf 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -78,9 +78,6 @@ github.com/alexedwards/argon2id
 # github.com/amoghe/go-crypt v0.0.0-20220222110647-20eada5f5964
 ## explicit
 github.com/amoghe/go-crypt
-# github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de
-## explicit; go 1.12
-github.com/araddon/dateparse
 # github.com/armon/go-metrics v0.4.1
 ## explicit; go 1.12
 github.com/armon/go-metrics
@@ -1172,6 +1169,9 @@ github.com/jellydator/ttlcache/v2
 # github.com/jellydator/ttlcache/v3 v3.1.0
 ## explicit; go 1.18
 github.com/jellydator/ttlcache/v3
+# github.com/jinzhu/now v1.1.5
+## explicit; go 1.12
+github.com/jinzhu/now
 # github.com/jmespath/go-jmespath v0.4.0
 ## explicit; go 1.14
 github.com/jmespath/go-jmespath


Test patch:
diff --git a/services/search/pkg/query/kql/dictionary_test.go b/services/search/pkg/query/kql/dictionary_test.go
index 7e86456a999..05e3cf56135 100644
--- a/services/search/pkg/query/kql/dictionary_test.go
+++ b/services/search/pkg/query/kql/dictionary_test.go
@@ -5,7 +5,7 @@ import (
 	"testing"
 	"time"
 
-	"github.com/araddon/dateparse"
+	"github.com/jinzhu/now"
 	tAssert "github.com/stretchr/testify/assert"
 
 	"github.com/owncloud/ocis/v2/services/search/pkg/query/ast"
@@ -13,66 +13,18 @@ import (
 	"github.com/owncloud/ocis/v2/services/search/pkg/query/kql"
 )
 
-var mustParseTime = func(t *testing.T, ts string) time.Time {
-	tp, err := dateparse.ParseLocal(ts)
-	if err != nil {
-		t.Fatalf("time.Parse(...) error = %v", err)
-	}
-
-	return tp
-}
-
-var mustJoin = func(v []string) string {
-	return strings.Join(v, " ")
-}
-
-var FullDictionary = []string{
-	`federated search`,
-	`federat* search`,
-	`search fed*`,
-	`author:"John Smith"`,
-	`filetype:docx`,
-	`filename:budget.xlsx`,
-	`author: "John Smith"`,
-	`author :"John Smith"`,
-	`author : "John Smith"`,
-	`author "John Smith"`,
-	`author "John Smith"`,
-	`author:Shakespear`,
-	`author:Paul`,
-	`author:Shakesp*`,
-	`title:"Advanced Search"`,
-	`title:"Advanced Sear*"`,
-	`title:"Advan* Search"`,
-	`title:"*anced Search"`,
-	`author:"John Smith" OR author:"Jane Smith"`,
-	`author:"John Smith" AND filetype:docx`,
-	`author:("John Smith" "Jane Smith")`,
-	`author:("John Smith" OR "Jane Smith")`,
-	`(DepartmentId:* OR RelatedHubSites:*) AND contentclass:sts_site NOT IsHubSite:false`,
-	`author:"John Smith" (filetype:docx title:"Advanced Search")`,
-}
-
-func TestParse(t *testing.T) {
-	tests := []struct {
-		name          string
-		skip          bool
-		givenQuery    string
-		expectedAst   *ast.Ast
-		expectedError error
-	}{
-		// SPEC //////////////////////////////////////////////////////////////////////////////
-		//
-		// https://msopenspecs.azureedge.net/files/MS-KQL/%5bMS-KQL%5d.pdf
-		// https://learn.microsoft.com/en-us/openspecs/sharepoint_protocols/ms-kql/3bbf06cd-8fc1-4277-bd92-8661ccd3c9b0
-		// https://learn.microsoft.com/en-us/sharepoint/dev/general-development/keyword-query-language-kql-syntax-reference
-		//
-		// ++
+func TestParse_Spec(t *testing.T) {
+	// SPEC //////////////////////////////////////////////////////////////////////////////
+	//
+	// https://msopenspecs.azureedge.net/files/MS-KQL/%5bMS-KQL%5d.pdf
+	// https://learn.microsoft.com/en-us/openspecs/sharepoint_protocols/ms-kql/3bbf06cd-8fc1-4277-bd92-8661ccd3c9b0
+	// https://learn.microsoft.com/en-us/sharepoint/dev/general-development/keyword-query-language-kql-syntax-reference
+	tests := []testCase{
 		// 2.1.2 AND Operator
 		// 3.1.2 AND Operator
 		{
 			name: `cat AND dog`,
-			expectedAst: &ast.Ast{
+			ast: &ast.Ast{
 				Nodes: []ast.Node{
 					&ast.StringNode{Value: "cat"},
 					&ast.OperatorNode{Value: kql.BoolAND},
@@ -82,22 +34,21 @@ func TestParse(t *testing.T) {
 		},
 		{
 			name: `AND`,
-			expectedError: kql.StartsWithBinaryOperatorError{
+			error: kql.StartsWithBinaryOperatorError{
 				Node: &ast.OperatorNode{Value: kql.BoolAND},
 			},
 		},
 		{
 			name: `AND cat AND dog`,
-			expectedError: kql.StartsWithBinaryOperatorError{
+			error: kql.StartsWithBinaryOperatorError{
 				Node: &ast.OperatorNode{Value: kql.BoolAND},
 			},
 		},
-		// ++
 		// 2.1.6 NOT Operator
 		// 3.1.6 NOT Operator
 		{
 			name: `cat NOT dog`,
-			expectedAst: &ast.Ast{
+			ast: &ast.Ast{
 				Nodes: []ast.Node{
 					&ast.StringNode{Value: "cat"},
 					&ast.OperatorNode{Value: kql.BoolAND},
@@ -108,19 +59,18 @@ func TestParse(t *testing.T) {
 		},
 		{
 			name: `NOT dog`,
-			expectedAst: &ast.Ast{
+			ast: &ast.Ast{
 				Nodes: []ast.Node{
 					&ast.OperatorNode{Value: kql.BoolNOT},
 					&ast.StringNode{Value: "dog"},
 				},
 			},
 		},
-		// ++
 		// 2.1.8 OR Operator
 		// 3.1.8 OR Operator
 		{
 			name: `cat OR dog`,
-			expectedAst: &ast.Ast{
+			ast: &ast.Ast{
 				Nodes: []ast.Node{
 					&ast.StringNode{Value: "cat"},
 					&ast.OperatorNode{Value: kql.BoolOR},
@@ -130,21 +80,20 @@ func TestParse(t *testing.T) {
 		},
 		{
 			name: `OR`,
-			expectedError: kql.StartsWithBinaryOperatorError{
+			error: kql.StartsWithBinaryOperatorError{
 				Node: &ast.OperatorNode{Value: kql.BoolOR},
 			},
 		},
 		{
 			name: `OR cat AND dog`,
-			expectedError: kql.StartsWithBinaryOperatorError{
+			error: kql.StartsWithBinaryOperatorError{
 				Node: &ast.OperatorNode{Value: kql.BoolOR},
 			},
 		},
-		// ++
 		// 3.1.11 Implicit Operator
 		{
 			name: `cat dog`,
-			expectedAst: &ast.Ast{
+			ast: &ast.Ast{
 				Nodes: []ast.Node{
 					&ast.StringNode{Value: "cat"},
 					&ast.OperatorNode{Value: kql.BoolAND},
@@ -154,7 +103,7 @@ func TestParse(t *testing.T) {
 		},
 		{
 			name: `cat AND (dog OR fox)`,
-			expectedAst: &ast.Ast{
+			ast: &ast.Ast{
 				Nodes: []ast.Node{
 					&ast.StringNode{Value: "cat"},
 					&ast.OperatorNode{Value: kql.BoolAND},
@@ -168,7 +117,7 @@ func TestParse(t *testing.T) {
 		},
 		{
 			name: `cat (dog OR fox)`,
-			expectedAst: &ast.Ast{
+			ast: &ast.Ast{
 				Nodes: []ast.Node{
 					&ast.StringNode{Value: "cat"},
 					&ast.OperatorNode{Value: kql.BoolAND},
@@ -180,12 +129,11 @@ func TestParse(t *testing.T) {
 				},
 			},
 		},
-		// ++
 		// 2.1.12 Parentheses
 		// 3.1.12 Parentheses
 		{
 			name: `(cat OR dog) AND fox`,
-			expectedAst: &ast.Ast{
+			ast: &ast.Ast{
 				Nodes: []ast.Node{
 					&ast.GroupNode{Nodes: []ast.Node{
 						&ast.StringNode{Value: "cat"},
@@ -197,11 +145,10 @@ func TestParse(t *testing.T) {
 				},
 			},
 		},
-		// ++
 		// 3.2.3 Implicit Operator for Property Restriction
 		{
 			name: `author:"John Smith" filetype:docx`,
-			expectedAst: &ast.Ast{
+			ast: &ast.Ast{
 				Nodes: []ast.Node{
 					&ast.StringNode{Key: "author", Value: "John Smith"},
 					&ast.OperatorNode{Value: kql.BoolAND},
@@ -211,7 +158,7 @@ func TestParse(t *testing.T) {
 		},
 		{
 			name: `author:"John Smith" AND filetype:docx`,
-			expectedAst: &ast.Ast{
+			ast: &ast.Ast{
 				Nodes: []ast.Node{
 					&ast.StringNode{Key: "author", Value: "John Smith"},
 					&ast.OperatorNode{Value: kql.BoolAND},
@@ -221,7 +168,7 @@ func TestParse(t *testing.T) {
 		},
 		{
 			name: `author:"John Smith" author:"Jane Smith"`,
-			expectedAst: &ast.Ast{
+			ast: &ast.Ast{
 				Nodes: []ast.Node{
 					&ast.StringNode{Key: "author", Value: "John Smith"},
 					&ast.OperatorNode{Value: kql.BoolOR},
@@ -231,7 +178,7 @@ func TestParse(t *testing.T) {
 		},
 		{
 			name: `author:"John Smith" OR author:"Jane Smith"`,
-			expectedAst: &ast.Ast{
+			ast: &ast.Ast{
 				Nodes: []ast.Node{
 					&ast.StringNode{Key: "author", Value: "John Smith"},
 					&ast.OperatorNode{Value: kql.BoolOR},
@@ -241,7 +188,7 @@ func TestParse(t *testing.T) {
 		},
 		{
 			name: `cat filetype:docx`,
-			expectedAst: &ast.Ast{
+			ast: &ast.Ast{
 				Nodes: []ast.Node{
 					&ast.StringNode{Value: "cat"},
 					&ast.OperatorNode{Value: kql.BoolAND},
@@ -251,7 +198,7 @@ func TestParse(t *testing.T) {
 		},
 		{
 			name: `cat AND filetype:docx`,
-			expectedAst: &ast.Ast{
+			ast: &ast.Ast{
 				Nodes: []ast.Node{
 					&ast.StringNode{Value: "cat"},
 					&ast.OperatorNode{Value: kql.BoolAND},
@@ -259,11 +206,10 @@ func TestParse(t *testing.T) {
 				},
 			},
 		},
-		// ++
 		// 3.3.1.1.1 Implicit AND Operator
 		{
 			name: `cat +dog`,
-			expectedAst: &ast.Ast{
+			ast: &ast.Ast{
 				Nodes: []ast.Node{
 					&ast.StringNode{Value: "cat"},
 					&ast.OperatorNode{Value: kql.BoolAND},
@@ -273,7 +219,7 @@ func TestParse(t *testing.T) {
 		},
 		{
 			name: `cat AND dog`,
-			expectedAst: &ast.Ast{
+			ast: &ast.Ast{
 				Nodes: []ast.Node{
 					&ast.StringNode{Value: "cat"},
 					&ast.OperatorNode{Value: kql.BoolAND},
@@ -283,7 +229,7 @@ func TestParse(t *testing.T) {
 		},
 		{
 			name: `cat -dog`,
-			expectedAst: &ast.Ast{
+			ast: &ast.Ast{
 				Nodes: []ast.Node{
 					&ast.StringNode{Value: "cat"},
 					&ast.OperatorNode{Value: kql.BoolAND},
@@ -294,7 +240,7 @@ func TestParse(t *testing.T) {
 		},
 		{
 			name: `cat AND NOT dog`,
-			expectedAst: &ast.Ast{
+			ast: &ast.Ast{
 				Nodes: []ast.Node{
 					&ast.StringNode{Value: "cat"},
 					&ast.OperatorNode{Value: kql.BoolAND},
@@ -305,7 +251,7 @@ func TestParse(t *testing.T) {
 		},
 		{
 			name: `cat +dog -fox`,
-			expectedAst: &ast.Ast{
+			ast: &ast.Ast{
 				Nodes: []ast.Node{
 					&ast.StringNode{Value: "cat"},
 					&ast.OperatorNode{Value: kql.BoolAND},
@@ -318,7 +264,7 @@ func TestParse(t *testing.T) {
 		},
 		{
 			name: `cat AND dog AND NOT fox`,
-			expectedAst: &ast.Ast{
+			ast: &ast.Ast{
 				Nodes: []ast.Node{
 					&ast.StringNode{Value: "cat"},
 					&ast.OperatorNode{Value: kql.BoolAND},
@@ -331,7 +277,7 @@ func TestParse(t *testing.T) {
 		},
 		{
 			name: `cat dog +fox`,
-			expectedAst: &ast.Ast{
+			ast: &ast.Ast{
 				Nodes: []ast.Node{
 					&ast.StringNode{Value: "cat"},
 					&ast.OperatorNode{Value: kql.BoolAND},
@@ -343,7 +289,7 @@ func TestParse(t *testing.T) {
 		},
 		{
 			name: `fox OR (fox AND (cat OR dog))`,
-			expectedAst: &ast.Ast{
+			ast: &ast.Ast{
 				Nodes: []ast.Node{
 					&ast.StringNode{Value: "fox"},
 					&ast.OperatorNode{Value: kql.BoolOR},
@@ -361,7 +307,7 @@ func TestParse(t *testing.T) {
 		},
 		{
 			name: `cat dog -fox`,
-			expectedAst: &ast.Ast{
+			ast: &ast.Ast{
 				Nodes: []ast.Node{
 					&ast.StringNode{Value: "cat"},
 					&ast.OperatorNode{Value: kql.BoolAND},
@@ -374,7 +320,7 @@ func TestParse(t *testing.T) {
 		},
 		{
 			name: `(NOT fox) AND (cat OR dog)`,
-			expectedAst: &ast.Ast{
+			ast: &ast.Ast{
 				Nodes: []ast.Node{
 					&ast.GroupNode{Nodes: []ast.Node{
 						&ast.OperatorNode{Value: kql.BoolNOT},
@@ -391,7 +337,7 @@ func TestParse(t *testing.T) {
 		},
 		{
 			name: `cat +dog -fox`,
-			expectedAst: &ast.Ast{
+			ast: &ast.Ast{
 				Nodes: []ast.Node{
 					&ast.StringNode{Value: "cat"},
 					&ast.OperatorNode{Value: kql.BoolAND},
@@ -404,7 +350,7 @@ func TestParse(t *testing.T) {
 		},
 		{
 			name: `(NOT fox) AND (dog OR (dog AND cat))`,
-			expectedAst: &ast.Ast{
+			ast: &ast.Ast{
 				Nodes: []ast.Node{
 					&ast.GroupNode{Nodes: []ast.Node{
 						&ast.OperatorNode{Value: kql.BoolNOT},
@@ -423,12 +369,11 @@ func TestParse(t *testing.T) {
 				},
 			},
 		},
-		// ++
 		// 2.3.5 Date Tokens
 		// 3.3.5 Date Tokens
 		{
 			name: `Modified:2023-09-05`,
-			expectedAst: &ast.Ast{
+			ast: &ast.Ast{
 				Nodes: []ast.Node{
 					&ast.DateTimeNode{
 						Key:      "Modified",
@@ -440,7 +385,7 @@ func TestParse(t *testing.T) {
 		},
 		{
 			name: `Modified:"2008-01-29"`,
-			expectedAst: &ast.Ast{
+			ast: &ast.Ast{
 				Nodes: []ast.Node{
 					&ast.DateTimeNode{
 						Key:      "Modified",
@@ -451,15 +396,400 @@ func TestParse(t *testing.T) {
 			},
 		},
 		{
-			name: `Modified:today`,
-			skip: true,
+			name:  `Modified:today`,
+			patch: patchNow(t, "2023-09-10"),
+			ast: &ast.Ast{
+				Nodes: []ast.Node{
+					&ast.DateTimeNode{
+						Key:      "Modified",
+						Operator: &ast.OperatorNode{Value: ">="},
+						Value:    mustParseTime(t, "2023-09-10"),
+					},
+					&ast.OperatorNode{Value: kql.BoolAND},
+					&ast.DateTimeNode{
+						Key:      "Modified",
+						Operator: &ast.OperatorNode{Value: "<="},
+						Value:    mustParseTime(t, "2023-09-10 23:59:59.999999999"),
+					},
+				},
+			},
+		},
+	}
+
+	for _, tc := range tests {
+		tc := tc
+		t.Run(tc.name, func(t *testing.T) {
+			testKQL(t, tc)
+		})
+	}
+}
+
+func TestParse_DateTimeRestrictionNode(t *testing.T) {
+	tests := []testCase{
+		{
+			name: "format",
+			query: join([]string{
+				`Mtime:"2023-09-05T08:42:11.23554+02:00"`,
+				`Mtime:2023-09-05T08:42:11.23554+02:00`,
+				`Mtime="2023-09-05T08:42:11.23554+02:00"`,
+				`Mtime=2023-09-05T08:42:11.23554+02:00`,
+				`Mtime<"2023-09-05T08:42:11.23554+02:00"`,
+				`Mtime<2023-09-05T08:42:11.23554+02:00`,
+				`Mtime<="2023-09-05T08:42:11.23554+02:00"`,
+				`Mtime<=2023-09-05T08:42:11.23554+02:00`,
+				`Mtime>"2023-09-05T08:42:11.23554+02:00"`,
+				`Mtime>2023-09-05T08:42:11.23554+02:00`,
+				`Mtime>="2023-09-05T08:42:11.23554+02:00"`,
+				`Mtime>=2023-09-05T08:42:11.23554+02:00`,
+			}),
+			ast: &ast.Ast{
+				Nodes: []ast.Node{
+					&ast.DateTimeNode{
+						Key:      "Mtime",
+						Operator: &ast.OperatorNode{Value: ":"},
+						Value:    mustParseTime(t, "2023-09-05T08:42:11.23554+02:00"),
+					},
+					&ast.OperatorNode{Value: kql.BoolOR},
+					&ast.DateTimeNode{
+						Key:      "Mtime",
+						Operator: &ast.OperatorNode{Value: ":"},
+						Value:    mustParseTime(t, "2023-09-05T08:42:11.23554+02:00"),
+					},
+					&ast.OperatorNode{Value: kql.BoolOR},
+					&ast.DateTimeNode{
+						Key:      "Mtime",
+						Operator: &ast.OperatorNode{Value: "="},
+						Value:    mustParseTime(t, "2023-09-05T08:42:11.23554+02:00"),
+					},
+					&ast.OperatorNode{Value: kql.BoolOR},
+					&ast.DateTimeNode{
+						Key:      "Mtime",
+						Operator: &ast.OperatorNode{Value: "="},
+						Value:    mustParseTime(t, "2023-09-05T08:42:11.23554+02:00"),
+					},
+					&ast.OperatorNode{Value: kql.BoolOR},
+					&ast.DateTimeNode{
+						Key:      "Mtime",
+						Operator: &ast.OperatorNode{Value: "<"},
+						Value:    mustParseTime(t, "2023-09-05T08:42:11.23554+02:00"),
+					},
+					&ast.OperatorNode{Value: kql.BoolOR},
+					&ast.DateTimeNode{
+						Key:      "Mtime",
+						Operator: &ast.OperatorNode{Value: "<"},
+						Value:    mustParseTime(t, "2023-09-05T08:42:11.23554+02:00"),
+					},
+					&ast.OperatorNode{Value: kql.BoolOR},
+					&ast.DateTimeNode{
+						Key:      "Mtime",
+						Operator: &ast.OperatorNode{Value: "<="},
+						Value:    mustParseTime(t, "2023-09-05T08:42:11.23554+02:00"),
+					},
+					&ast.OperatorNode{Value: kql.BoolOR},
+					&ast.DateTimeNode{
+						Key:      "Mtime",
+						Operator: &ast.OperatorNode{Value: "<="},
+						Value:    mustParseTime(t, "2023-09-05T08:42:11.23554+02:00"),
+					},
+					&ast.OperatorNode{Value: kql.BoolOR},
+					&ast.DateTimeNode{
+						Key:      "Mtime",
+						Operator: &ast.OperatorNode{Value: ">"},
+						Value:    mustParseTime(t, "2023-09-05T08:42:11.23554+02:00"),
+					},
+					&ast.OperatorNode{Value: kql.BoolOR},
+					&ast.DateTimeNode{
+						Key:      "Mtime",
+						Operator: &ast.OperatorNode{Value: ">"},
+						Value:    mustParseTime(t, "2023-09-05T08:42:11.23554+02:00"),
+					},
+					&ast.OperatorNode{Value: kql.BoolOR},
+					&ast.DateTimeNode{
+						Key:      "Mtime",
+						Operator: &ast.OperatorNode{Value: ">="},
+						Value:    mustParseTime(t, "2023-09-05T08:42:11.23554+02:00"),
+					},
+					&ast.OperatorNode{Value: kql.BoolOR},
+					&ast.DateTimeNode{
+						Key:      "Mtime",
+						Operator: &ast.OperatorNode{Value: ">="},
+						Value:    mustParseTime(t, "2023-09-05T08:42:11.23554+02:00"),
+					},
+				},
+			},
+		},
+		{
+			name:  "NaturalLanguage DateTimeNode - today",
+			patch: setWorldClock(t, "2023-09-10"),
+			query: join([]string{
+				`Mtime:today`,
+			}),
+			ast: &ast.Ast{
+				Nodes: []ast.Node{
+					&ast.DateTimeNode{
+						Key:      "Mtime",
+						Operator: &ast.OperatorNode{Value: ">="},
+						Value:    mustParseTime(t, "2023-09-10"),
+					},
+					&ast.OperatorNode{Value: kql.BoolAND},
+					&ast.DateTimeNode{
+						Key:      "Mtime",
+						Operator: &ast.OperatorNode{Value: "<="},
+						Value:    mustParseTime(t, "2023-09-10 23:59:59.999999999"),
+					},
+				},
+			},
+		},
+		{
+			name:  "NaturalLanguage DateTimeNode - yesterday",
+			patch: setWorldClock(t, "2023-09-10"),
+			query: join([]string{
+				`Mtime:yesterday`,
+			}),
+			ast: &ast.Ast{
+				Nodes: []ast.Node{
+					&ast.DateTimeNode{
+						Key:      "Mtime",
+						Operator: &ast.OperatorNode{Value: ">="},
+						Value:    mustParseTime(t, "2023-09-09"),
+					},
+					&ast.OperatorNode{Value: kql.BoolAND},
+					&ast.DateTimeNode{
+						Key:      "Mtime",
+						Operator: &ast.OperatorNode{Value: "<="},
+						Value:    mustParseTime(t, "2023-09-09 23:59:59.999999999"),
+					},
+				},
+			},
+		},
+		{
+			name:  "NaturalLanguage DateTimeNode - yesterday - the beginning of the month",
+			patch: setWorldClock(t, "2023-09-01"),
+			query: join([]string{
+				`Mtime:yesterday`,
+			}),
+			ast: &ast.Ast{
+				Nodes: []ast.Node{
+					&ast.DateTimeNode{
+						Key:      "Mtime",
+						Operator: &ast.OperatorNode{Value: ">="},
+						Value:    mustParseTime(t, "2023-08-31"),
+					},
+					&ast.OperatorNode{Value: kql.BoolAND},
+					&ast.DateTimeNode{
+						Key:      "Mtime",
+						Operator: &ast.OperatorNode{Value: "<="},
+						Value:    mustParseTime(t, "2023-08-31 23:59:59.999999999"),
+					},
+				},
+			},
+		},
+		{
+			name:  "NaturalLanguage DateTimeNode - this week",
+			patch: setWorldClock(t, "2023-09-06"),
+			query: join([]string{
+				`Mtime:"this week"`,
+			}),
+			ast: &ast.Ast{
+				Nodes: []ast.Node{
+					&ast.DateTimeNode{
+						Key:      "Mtime",
+						Operator: &ast.OperatorNode{Value: ">="},
+						Value:    mustParseTime(t, "2023-09-04"),
+					},
+					&ast.OperatorNode{Value: kql.BoolAND},
+					&ast.DateTimeNode{
+						Key:      "Mtime",
+						Operator: &ast.OperatorNode{Value: "<="},
+						Value:    mustParseTime(t, "2023-09-10 23:59:59.999999999"),
+					},
+				},
+			},
+		},
+		{
+			name:  "NaturalLanguage DateTimeNode - this month",
+			patch: setWorldClock(t, "2023-09-02"),
+			query: join([]string{
+				`Mtime:"this month"`,
+			}),
+			ast: &ast.Ast{
+				Nodes: []ast.Node{
+					&ast.DateTimeNode{
+						Key:      "Mtime",
+						Operator: &ast.OperatorNode{Value: ">="},
+						Value:    mustParseTime(t, "2023-09-01"),
+					},
+					&ast.OperatorNode{Value: kql.BoolAND},
+					&ast.DateTimeNode{
+						Key:      "Mtime",
+						Operator: &ast.OperatorNode{Value: "<="},
+						Value:    mustParseTime(t, "2023-09-30 23:59:59.999999999"),
+					},
+				},
+			},
+		},
+		{
+			name:  "NaturalLanguage DateTimeNode - last month",
+			patch: setWorldClock(t, "2023-09-02"),
+			query: join([]string{
+				`Mtime:"last month"`,
+			}),
+			ast: &ast.Ast{
+				Nodes: []ast.Node{
+					&ast.DateTimeNode{
+						Key:      "Mtime",
+						Operator: &ast.OperatorNode{Value: ">="},
+						Value:    mustParseTime(t, "2023-08-01"),
+					},
+					&ast.OperatorNode{Value: kql.BoolAND},
+					&ast.DateTimeNode{
+						Key:      "Mtime",
+						Operator: &ast.OperatorNode{Value: "<="},
+						Value:    mustParseTime(t, "2023-08-31 23:59:59.999999999"),
+					},
+				},
+			},
+		},
+		{
+			name:  "NaturalLanguage DateTimeNode - last month - the beginning of the year",
+			patch: setWorldClock(t, "2023-01-01"),
+			query: join([]string{
+				`Mtime:"last month"`,
+			}),
+			ast: &ast.Ast{
+				Nodes: []ast.Node{
+					&ast.DateTimeNode{
+						Key:      "Mtime",
+						Operator: &ast.OperatorNode{Value: ">="},
+						Value:    mustParseTime(t, "2022-12-01"),
+					},
+					&ast.OperatorNode{Value: kql.BoolAND},
+					&ast.DateTimeNode{
+						Key:      "Mtime",
+						Operator: &ast.OperatorNode{Value: "<="},
+						Value:    mustParseTime(t, "2022-12-31 23:59:59.999999999"),
+					},
+				},
+			},
+		},
+		{
+			name:  "NaturalLanguage DateTimeNode - this year",
+			patch: setWorldClock(t, "2023-06-18"),
+			query: join([]string{
+				`Mtime:"this year"`,
+			}),
+			ast: &ast.Ast{
+				Nodes: []ast.Node{
+					&ast.DateTimeNode{
+						Key:      "Mtime",
+						Operator: &ast.OperatorNode{Value: ">="},
+						Value:    mustParseTime(t, "2023-01-01"),
+					},
+					&ast.OperatorNode{Value: kql.BoolAND},
+					&ast.DateTimeNode{
+						Key:      "Mtime",
+						Operator: &ast.OperatorNode{Value: "<="},
+						Value:    mustParseTime(t, "2023-12-31 23:59:59.999999999"),
+					},
+				},
+			},
+		},
+		{
+			name:  "NaturalLanguage DateTimeNode - last year",
+			patch: setWorldClock(t, "2023-01-01"),
+			query: join([]string{
+				`Mtime:"last year"`,
+			}),
+			ast: &ast.Ast{
+				Nodes: []ast.Node{
+					&ast.DateTimeNode{
+						Key:      "Mtime",
+						Operator: &ast.OperatorNode{Value: ">="},
+						Value:    mustParseTime(t, "2022-01-01"),
+					},
+					&ast.OperatorNode{Value: kql.BoolAND},
+					&ast.DateTimeNode{
+						Key:      "Mtime",
+						Operator: &ast.OperatorNode{Value: "<="},
+						Value:    mustParseTime(t, "2022-12-31 23:59:59.999999999"),
+					},
+				},
+			},
+		},
+	}
+
+	for _, tc := range tests {
+		tc := tc
+		t.Run(tc.name, func(t *testing.T) {
+			testKQL(t, tc)
+		})
+	}
+}
+
+func TestParse_Errors(t *testing.T) {
+	tests := []testCase{
+		{
+			query: "animal:(mammal:cat mammal:dog reptile:turtle)",
+			error: kql.NamedGroupInvalidNodesError{
+				Node: &ast.StringNode{Key: "mammal", Value: "cat"},
+			},
+		},
+		{
+			query: "animal:(cat mammal:dog turtle)",
+			error: kql.NamedGroupInvalidNodesError{
+				Node: &ast.StringNode{Key: "mammal", Value: "dog"},
+			},
+		},
+		{
+			query: "animal:(AND cat)",
+			error: kql.StartsWithBinaryOperatorError{
+				Node: &ast.OperatorNode{Value: kql.BoolAND},
+			},
+		},
+		{
+			query: "animal:(OR cat)",
+			error: kql.StartsWithBinaryOperatorError{
+				Node: &ast.OperatorNode{Value: kql.BoolOR},
+			},
+		},
+		{
+			query: "(AND cat)",
+			error: kql.StartsWithBinaryOperatorError{
+				Node: &ast.OperatorNode{Value: kql.BoolAND},
+			},
+		},
+		{
+			query: "(OR cat)",
+			error: kql.StartsWithBinaryOperatorError{
+				Node: &ast.OperatorNode{Value: kql.BoolOR},
+			},
+		},
+		{
+			query: `cat dog`,
+			ast: &ast.Ast{
+				Nodes: []ast.Node{
+					&ast.StringNode{Value: "cat"},
+					&ast.OperatorNode{Value: kql.BoolAND},
+					&ast.StringNode{Value: "dog"},
+				},
+			},
 		},
-		//////////////////////////////////////////////////////////////////////////////////////
-		// everything else
+	}
+
+	for _, tc := range tests {
+		tc := tc
+		t.Run(tc.name, func(t *testing.T) {
+			testKQL(t, tc)
+		})
+	}
+}
+
+func TestParse_Stress(t *testing.T) {
+	tests := []testCase{
 		{
-			name:       "FullDictionary",
-			givenQuery: mustJoin(FullDictionary),
-			expectedAst: &ast.Ast{
+			name:  "FullDictionary",
+			query: join(FullDictionary),
+			ast: &ast.Ast{
 				Nodes: []ast.Node{
 					&ast.StringNode{Value: "federated"},
 					&ast.OperatorNode{Value: kql.BoolAND},
@@ -565,13 +895,13 @@ func TestParse(t *testing.T) {
 			},
 		},
 		{
-			name: "Group",
-			givenQuery: mustJoin([]string{
+			name: "complex",
+			query: join([]string{
 				`(name:"moby di*" OR tag:bestseller) AND tag:book NOT tag:read`,
 				`author:("John Smith" Jane)`,
 				`author:("John Smith" OR Jane)`,
 			}),
-			expectedAst: &ast.Ast{
+			ast: &ast.Ast{
 				Nodes: []ast.Node{
 					&ast.GroupNode{
 						Nodes: []ast.Node{
@@ -608,7 +938,7 @@ func TestParse(t *testing.T) {
 		},
 		{
 			name: `author:("John Smith" Jane) author:"Jack" AND author:"Oggy"`,
-			expectedAst: &ast.Ast{
+			ast: &ast.Ast{
 				Nodes: []ast.Node{
 					&ast.GroupNode{
 						Key: "author",
@@ -627,7 +957,7 @@ func TestParse(t *testing.T) {
 		},
 		{
 			name: `author:("John Smith" OR Jane)`,
-			expectedAst: &ast.Ast{
+			ast: &ast.Ast{
 				Nodes: []ast.Node{
 					&ast.GroupNode{
 						Key: "author",
@@ -642,7 +972,7 @@ func TestParse(t *testing.T) {
 		},
 		{
 			name: `NOT "John Smith" NOT Jane`,
-			expectedAst: &ast.Ast{
+			ast: &ast.Ast{
 				Nodes: []ast.Node{
 					&ast.OperatorNode{Value: kql.BoolNOT},
 					&ast.StringNode{Value: "John Smith"},
@@ -654,7 +984,7 @@ func TestParse(t *testing.T) {
 		},
 		{
 			name: `NOT author:"John Smith" NOT author:"Jane Smith" NOT tag:sifi`,
-			expectedAst: &ast.Ast{
+			ast: &ast.Ast{
 				Nodes: []ast.Node{
 					&ast.OperatorNode{Value: kql.BoolNOT},
 					&ast.StringNode{Key: "author", Value: "John Smith"},
@@ -669,7 +999,7 @@ func TestParse(t *testing.T) {
 		},
 		{
 			name: `scope:"<uuid>/new folder/subfolder" file`,
-			expectedAst: &ast.Ast{
+			ast: &ast.Ast{
 				Nodes: []ast.Node{
 					&ast.StringNode{
 						Key:   "scope",
@@ -684,7 +1014,7 @@ func TestParse(t *testing.T) {
 		},
 		{
 			name: `	😂 "*😀 😁*" name:😂💁👌🎍😍 name:😂💁👌 😍`,
-			expectedAst: &ast.Ast{
+			ast: &ast.Ast{
 				Nodes: []ast.Node{
 					&ast.StringNode{
 						Value: "😂",
@@ -710,121 +1040,9 @@ func TestParse(t *testing.T) {
 				},
 			},
 		},
-		{
-			name: "DateTimeRestrictionNode",
-			givenQuery: mustJoin([]string{
-				`Mtime:"2023-09-05T08:42:11.23554+02:00"`,
-				`Mtime:2023-09-05T08:42:11.23554+02:00`,
-				`Mtime="2023-09-05T08:42:11.23554+02:00"`,
-				`Mtime=2023-09-05T08:42:11.23554+02:00`,
-				`Mtime<"2023-09-05T08:42:11.23554+02:00"`,
-				`Mtime<2023-09-05T08:42:11.23554+02:00`,
-				`Mtime<="2023-09-05T08:42:11.23554+02:00"`,
-				`Mtime<=2023-09-05T08:42:11.23554+02:00`,
-				`Mtime>"2023-09-05T08:42:11.23554+02:00"`,
-				`Mtime>2023-09-05T08:42:11.23554+02:00`,
-				`Mtime>="2023-09-05T08:42:11.23554+02:00"`,
-				`Mtime>=2023-09-05T08:42:11.23554+02:00`,
-			}),
-			expectedAst: &ast.Ast{
-				Nodes: []ast.Node{
-					&ast.DateTimeNode{
-						Key:      "Mtime",
-						Operator: &ast.OperatorNode{Value: ":"},
-						Value:    mustParseTime(t, "2023-09-05T08:42:11.23554+02:00"),
-					},
-					&ast.OperatorNode{Value: kql.BoolOR},
-					&ast.DateTimeNode{
-						Key:      "Mtime",
-						Operator: &ast.OperatorNode{Value: ":"},
-						Value:    mustParseTime(t, "2023-09-05T08:42:11.23554+02:00"),
-					},
-					&ast.OperatorNode{Value: kql.BoolOR},
-					&ast.DateTimeNode{
-						Key:      "Mtime",
-						Operator: &ast.OperatorNode{Value: "="},
-						Value:    mustParseTime(t, "2023-09-05T08:42:11.23554+02:00"),
-					},
-					&ast.OperatorNode{Value: kql.BoolOR},
-					&ast.DateTimeNode{
-						Key:      "Mtime",
-						Operator: &ast.OperatorNode{Value: "="},
-						Value:    mustParseTime(t, "2023-09-05T08:42:11.23554+02:00"),
-					},
-					&ast.OperatorNode{Value: kql.BoolOR},
-					&ast.DateTimeNode{
-						Key:      "Mtime",
-						Operator: &ast.OperatorNode{Value: "<"},
-						Value:    mustParseTime(t, "2023-09-05T08:42:11.23554+02:00"),
-					},
-					&ast.OperatorNode{Value: kql.BoolOR},
-					&ast.DateTimeNode{
-						Key:      "Mtime",
-						Operator: &ast.OperatorNode{Value: "<"},
-						Value:    mustParseTime(t, "2023-09-05T08:42:11.23554+02:00"),
-					},
-					&ast.OperatorNode{Value: kql.BoolOR},
-					&ast.DateTimeNode{
-						Key:      "Mtime",
-						Operator: &ast.OperatorNode{Value: "<="},
-						Value:    mustParseTime(t, "2023-09-05T08:42:11.23554+02:00"),
-					},
-					&ast.OperatorNode{Value: kql.BoolOR},
-					&ast.DateTimeNode{
-						Key:      "Mtime",
-						Operator: &ast.OperatorNode{Value: "<="},
-						Value:    mustParseTime(t, "2023-09-05T08:42:11.23554+02:00"),
-					},
-					&ast.OperatorNode{Value: kql.BoolOR},
-					&ast.DateTimeNode{
-						Key:      "Mtime",
-						Operator: &ast.OperatorNode{Value: ">"},
-						Value:    mustParseTime(t, "2023-09-05T08:42:11.23554+02:00"),
-					},
-					&ast.OperatorNode{Value: kql.BoolOR},
-					&ast.DateTimeNode{
-						Key:      "Mtime",
-						Operator: &ast.OperatorNode{Value: ">"},
-						Value:    mustParseTime(t, "2023-09-05T08:42:11.23554+02:00"),
-					},
-					&ast.OperatorNode{Value: kql.BoolOR},
-					&ast.DateTimeNode{
-						Key:      "Mtime",
-						Operator: &ast.OperatorNode{Value: ">="},
-						Value:    mustParseTime(t, "2023-09-05T08:42:11.23554+02:00"),
-					},
-					&ast.OperatorNode{Value: kql.BoolOR},
-					&ast.DateTimeNode{
-						Key:      "Mtime",
-						Operator: &ast.OperatorNode{Value: ">="},
-						Value:    mustParseTime(t, "2023-09-05T08:42:11.23554+02:00"),
-					},
-				},
-			},
-		},
-		{
-			name: "id",
-			givenQuery: mustJoin([]string{
-				`id:b27d3bf1-b254-459f-92e8-bdba668d6d3f$d0648459-25fb-4ed8-8684-bc62c7dca29c!d0648459-25fb-4ed8-8684-bc62c7dca29c`,
-				`ID:b27d3bf1-b254-459f-92e8-bdba668d6d3f$d0648459-25fb-4ed8-8684-bc62c7dca29c!d0648459-25fb-4ed8-8684-bc62c7dca29c`,
-			}),
-			expectedAst: &ast.Ast{
-				Nodes: []ast.Node{
-					&ast.StringNode{
-						Key:   "id",
-						Value: "b27d3bf1-b254-459f-92e8-bdba668d6d3f$d0648459-25fb-4ed8-8684-bc62c7dca29c!d0648459-25fb-4ed8-8684-bc62c7dca29c",
-					},
-					&ast.OperatorNode{Value: kql.BoolOR},
-					&ast.StringNode{
-						Key:   "ID",
-						Value: "b27d3bf1-b254-459f-92e8-bdba668d6d3f$d0648459-25fb-4ed8-8684-bc62c7dca29c!d0648459-25fb-4ed8-8684-bc62c7dca29c",
-					},
-				},
-			},
-		},
 		{
 			name: "animal:(cat dog turtle)",
-			expectedAst: &ast.Ast{
+			ast: &ast.Ast{
 				Nodes: []ast.Node{
 					&ast.GroupNode{
 						Key: "animal",
@@ -847,7 +1065,7 @@ func TestParse(t *testing.T) {
 		},
 		{
 			name: "(cat dog turtle)",
-			expectedAst: &ast.Ast{
+			ast: &ast.Ast{
 				Nodes: []ast.Node{
 					&ast.GroupNode{
 						Nodes: []ast.Node{
@@ -867,55 +1085,9 @@ func TestParse(t *testing.T) {
 				},
 			},
 		},
-		{
-			name: "animal:(mammal:cat mammal:dog reptile:turtle)",
-			expectedError: kql.NamedGroupInvalidNodesError{
-				Node: &ast.StringNode{Key: "mammal", Value: "cat"},
-			},
-		},
-		{
-			name: "animal:(cat mammal:dog turtle)",
-			expectedError: kql.NamedGroupInvalidNodesError{
-				Node: &ast.StringNode{Key: "mammal", Value: "dog"},
-			},
-		},
-		{
-			name: "animal:(AND cat)",
-			expectedError: kql.StartsWithBinaryOperatorError{
-				Node: &ast.OperatorNode{Value: kql.BoolAND},
-			},
-		},
-		{
-			name: "animal:(OR cat)",
-			expectedError: kql.StartsWithBinaryOperatorError{
-				Node: &ast.OperatorNode{Value: kql.BoolOR},
-			},
-		},
-		{
-			name: "(AND cat)",
-			expectedError: kql.StartsWithBinaryOperatorError{
-				Node: &ast.OperatorNode{Value: kql.BoolAND},
-			},
-		},
-		{
-			name: "(OR cat)",
-			expectedError: kql.StartsWithBinaryOperatorError{
-				Node: &ast.OperatorNode{Value: kql.BoolOR},
-			},
-		},
-		{
-			name: `cat dog`,
-			expectedAst: &ast.Ast{
-				Nodes: []ast.Node{
-					&ast.StringNode{Value: "cat"},
-					&ast.OperatorNode{Value: kql.BoolAND},
-					&ast.StringNode{Value: "dog"},
-				},
-			},
-		},
 		{
 			name: `cat dog fox`,
-			expectedAst: &ast.Ast{
+			ast: &ast.Ast{
 				Nodes: []ast.Node{
 					&ast.StringNode{Value: "cat"},
 					&ast.OperatorNode{Value: kql.BoolAND},
@@ -927,7 +1099,7 @@ func TestParse(t *testing.T) {
 		},
 		{
 			name: `(cat dog) fox`,
-			expectedAst: &ast.Ast{
+			ast: &ast.Ast{
 				Nodes: []ast.Node{
 					&ast.GroupNode{
 						Nodes: []ast.Node{
@@ -943,7 +1115,7 @@ func TestParse(t *testing.T) {
 		},
 		{
 			name: `(mammal:cat mammal:dog) fox`,
-			expectedAst: &ast.Ast{
+			ast: &ast.Ast{
 				Nodes: []ast.Node{
 					&ast.GroupNode{
 						Nodes: []ast.Node{
@@ -959,7 +1131,7 @@ func TestParse(t *testing.T) {
 		},
 		{
 			name: `mammal:(cat dog) fox`,
-			expectedAst: &ast.Ast{
+			ast: &ast.Ast{
 				Nodes: []ast.Node{
 					&ast.GroupNode{
 						Key: "mammal",
@@ -976,7 +1148,7 @@ func TestParse(t *testing.T) {
 		},
 		{
 			name: `mammal:(cat dog) mammal:fox`,
-			expectedAst: &ast.Ast{
+			ast: &ast.Ast{
 				Nodes: []ast.Node{
 					&ast.GroupNode{
 						Key: "mammal",
@@ -993,7 +1165,7 @@ func TestParse(t *testing.T) {
 		},
 		{
 			name: `title:((Advanced OR Search OR Query) -"Advanced Search Query")`,
-			expectedAst: &ast.Ast{
+			ast: &ast.Ast{
 				Nodes: []ast.Node{
 					&ast.GroupNode{
 						Key: "title",
@@ -1015,48 +1187,171 @@ func TestParse(t *testing.T) {
 				},
 			},
 		},
+		{
+			name: "ids",
+			query: join([]string{
+				`id:b27d3bf1-b254-459f-92e8-bdba668d6d3f$d0648459-25fb-4ed8-8684-bc62c7dca29c!d0648459-25fb-4ed8-8684-bc62c7dca29c`,
+				`ID:b27d3bf1-b254-459f-92e8-bdba668d6d3f$d0648459-25fb-4ed8-8684-bc62c7dca29c!d0648459-25fb-4ed8-8684-bc62c7dca29c`,
+			}),
+			ast: &ast.Ast{
+				Nodes: []ast.Node{
+					&ast.StringNode{
+						Key:   "id",
+						Value: "b27d3bf1-b254-459f-92e8-bdba668d6d3f$d0648459-25fb-4ed8-8684-bc62c7dca29c!d0648459-25fb-4ed8-8684-bc62c7dca29c",
+					},
+					&ast.OperatorNode{Value: kql.BoolOR},
+					&ast.StringNode{
+						Key:   "ID",
+						Value: "b27d3bf1-b254-459f-92e8-bdba668d6d3f$d0648459-25fb-4ed8-8684-bc62c7dca29c!d0648459-25fb-4ed8-8684-bc62c7dca29c",
+					},
+				},
+			},
+		},
+		{
+			name: `"test:test" test:"test:test" "more:*+#!/°^§$%&&/()=?<><<more" more:"more:*+#!/°^§$%&&/()=?<><<more"`,
+			ast: &ast.Ast{
+				Nodes: []ast.Node{
+					&ast.StringNode{
+						Value: "test:test",
+					},
+					&ast.OperatorNode{Value: kql.BoolAND},
+					&ast.StringNode{
+						Key:   "test",
+						Value: "test:test",
+					},
+					&ast.OperatorNode{Value: kql.BoolAND},
+					&ast.StringNode{
+						Value: "more:*+#!/°^§$%&&/()=?<><<more",
+					},
+					&ast.OperatorNode{Value: kql.BoolAND},
+					&ast.StringNode{
+						Key:   "more",
+						Value: "more:*+#!/°^§$%&&/()=?<><<more",
+					},
+				},
+			},
+		},
 	}
 
-	assert := tAssert.New(t)
-
-	for _, tt := range tests {
-		tt := tt
+	for _, tc := range tests {
+		tc := tc
+		t.Run(tc.name, func(t *testing.T) {
+			testKQL(t, tc)
+		})
+	}
+}
 
-		t.Run(tt.name, func(t *testing.T) {
-			if tt.skip {
-				t.Skip()
-			}
+func BenchmarkParse(b *testing.B) {
+	b.ReportAllocs()
+	for n := 0; n < b.N; n++ {
+		if _, err := kql.Parse("", []byte(strings.Join(FullDictionary, " "))); err != nil {
+			b.Fatal(err)
+		}
+	}
+}
 
-			q := tt.name
+type testCase struct {
+	name  string
+	query string
+	ast   *ast.Ast
+	error error
+	skip  bool
+	patch func() func()
+}
 
-			if tt.givenQuery != "" {
-				q = tt.givenQuery
-			}
+var mustParseTime = func(t *testing.T, ts string) time.Time {
+	tp, err := now.Parse(ts)
+	if err != nil {
+		t.Fatalf("time.Parse(...) error = %v", err)
+	}
 
-			parsedAST, err := kql.Builder{}.Build(q)
+	return tp
+}
 
-			if tt.expectedError != nil {
-				if tt.expectedError.Error() != "" {
-					assert.Equal(err.Error(), tt.expectedError.Error())
-				} else {
-					assert.NotNil(err)
-				}
+var setWorldClock = func(t *testing.T, ts string) func() func() {
+	return func() func() {
+		kql.PatchTimeNow(func() time.Time {
+			return mustParseTime(t, ts)
+		})
 
-				return
-			}
+		return func() {
+			kql.PatchTimeNow(time.Now)
+		}
+	}
+}
 
-			if diff := test.DiffAst(tt.expectedAst, parsedAST); diff != "" {
-				t.Fatalf("AST mismatch \nquery: '%s' \n(-expected +got): %s", q, diff)
-			}
+var patchNow = func(t *testing.T, ts string) func() func() {
+	return func() func() {
+		kql.PatchTimeNow(func() time.Time {
+			return mustParseTime(t, ts)
 		})
+
+		return func() {
+			kql.PatchTimeNow(time.Now)
+		}
 	}
 }
 
-func BenchmarkParse(b *testing.B) {
-	b.ReportAllocs()
-	for n := 0; n < b.N; n++ {
-		if _, err := kql.Parse("", []byte(strings.Join(FullDictionary, " "))); err != nil {
-			b.Fatal(err)
+var join = func(v []string) string {
+	return strings.Join(v, " ")
+}
+
+func testKQL(t *testing.T, tc testCase) {
+	if tc.skip {
+		t.Skip()
+	}
+
+	if tc.patch != nil {
+		revert := tc.patch()
+		defer revert()
+	}
+
+	query := tc.name
+	if tc.query != "" {
+		query = tc.query
+	}
+
+	astResult, err := kql.Builder{}.Build(query)
+	assert := tAssert.New(t)
+
+	if tc.error != nil {
+		if expectedError := tc.error.Error(); expectedError != "" {
+			assert.Equal(err.Error(), expectedError)
+		} else {
+			assert.NotNil(err)
 		}
+
+		return
+	}
+
+	if diff := test.DiffAst(tc.ast, astResult); diff != "" {
+		t.Fatalf("AST mismatch \nquery: '%s' \n(-expected +got): %s", query, diff)
 	}
 }
+
+var FullDictionary = []string{
+	`federated search`,
+	`federat* search`,
+	`search fed*`,
+	`author:"John Smith"`,
+	`filetype:docx`,
+	`filename:budget.xlsx`,
+	`author: "John Smith"`,
+	`author :"John Smith"`,
+	`author : "John Smith"`,
+	`author "John Smith"`,
+	`author "John Smith"`,
+	`author:Shakespear`,
+	`author:Paul`,
+	`author:Shakesp*`,
+	`title:"Advanced Search"`,
+	`title:"Advanced Sear*"`,
+	`title:"Advan* Search"`,
+	`title:"*anced Search"`,
+	`author:"John Smith" OR author:"Jane Smith"`,
+	`author:"John Smith" AND filetype:docx`,
+	`author:("John Smith" "Jane Smith")`,
+	`author:("John Smith" OR "Jane Smith")`,
+	`(DepartmentId:* OR RelatedHubSites:*) AND contentclass:sts_site NOT IsHubSite:false`,
+	`author:"John Smith" (filetype:docx title:"Advanced Search")`,
+}
diff --git a/services/search/pkg/query/kql/engine_suite_test.go b/services/search/pkg/query/kql/engine_suite_test.go
new file mode 100644
index 00000000000..44a37b93179
--- /dev/null
+++ b/services/search/pkg/query/kql/engine_suite_test.go
@@ -0,0 +1,11 @@
+package kql
+
+import (
+	"time"
+)
+
+// PatchTimeNow is here to path the package time now func,
+// this only exists for the tests context
+func PatchTimeNow(t func() time.Time) {
+	timeNow = t
+}
diff --git a/tests/acceptance/expected-failures-localAPI-on-OCIS-storage.md b/tests/acceptance/expected-failures-localAPI-on-OCIS-storage.md
index 71e071abce8..3a90a282c71 100644
--- a/tests/acceptance/expected-failures-localAPI-on-OCIS-storage.md
+++ b/tests/acceptance/expected-failures-localAPI-on-OCIS-storage.md
@@ -22,7 +22,7 @@ The expected failures in this file are from features in the owncloud/ocis repo.
 
 - [apiSpacesShares/changingFilesShare.feature:14](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiSpacesShares/changingFilesShare.feature#L14)
 
-### [copy to overwrite (file and folder) from Personal to Shares Jail behaves differently](https://github.com/owncloud/ocis/issues/4393)
+### [Shared mount folder gets deleted when overwritten by a file from personal space](https://github.com/owncloud/ocis/issues/7208)
 
 - [apiSpacesShares/copySpaces.feature:528](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiSpacesShares/copySpaces.feature#L528)
 - [apiSpacesShares/copySpaces.feature:542](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiSpacesShares/copySpaces.feature#L542)
diff --git a/tests/acceptance/features/apiSearch/contentSearch.feature b/tests/acceptance/features/apiSearch/contentSearch.feature
index 7ee8079b6ac..14113c1dff7 100644
--- a/tests/acceptance/features/apiSearch/contentSearch.feature
+++ b/tests/acceptance/features/apiSearch/contentSearch.feature
@@ -1,3 +1,4 @@
+@tikaServiceNeeded
 Feature: content search
   As a user
   I want to do search resources by content
@@ -172,3 +173,32 @@ Feature: content search
       | content:hel*                                | 2            | /technical task.txt | /task comments.txt |
       | content:hel* AND tag:test                   | 1            | /technical task.txt |                    |
       | (name:*task* AND content:hel*) NOT tag:test | 1            | /task comments.txt  |                    |
+
+
+  Scenario Outline: search across files with different format with search text highlight
+    Given using <dav-path-version> DAV path
+    And the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API
+    And user "Alice" has created a space "project-space" with the default quota using the GraphApi
+    And user "Alice" has uploaded file with content "this is a simple text file" to "test-text-file.txt"
+    And user "Alice" has uploaded file with content "this is a simple pdf file" to "test-pdf-file.pdf"
+    And user "Alice" has uploaded file with content "this is a simple cpp file" to "test-cpp-file.cpp"
+    And user "Alice" has uploaded file with content "this is another text file" to "testfile.txt"
+    And user "Alice" has uploaded file "filesForUpload/testavatar.png" to "/testavatar.png"
+    And user "Alice" has uploaded a file inside space "project-space" with content "this is a simple markdown file" to "test-md-file.md"
+    And user "Alice" has uploaded a file inside space "project-space" with content "this is a simple odt file" to "test-odt-file.odt"
+    When user "Alice" searches for "Content:simple" using the WebDAV API
+    Then the HTTP status code should be "207"
+    And the search result should contain these entries with highlight on keyword "simple"
+      | test-text-file.txt |
+      | test-pdf-file.pdf  |
+      | test-cpp-file.cpp  |
+      | test-md-file.md    |
+      | test-odt-file.odt  |
+    But the search result of user "Alice" should not contain these entries:
+      | testavatar.png |
+      | testfile.txt   |
+    Examples:
+      | dav-path-version |
+      | old              |
+      | new              |
+      | spaces           |
diff --git a/tests/acceptance/features/apiSpacesShares/copySpaces.feature b/tests/acceptance/features/apiSpacesShares/copySpaces.feature
index 208f4a02fb1..885ba4fd8ad 100644
--- a/tests/acceptance/features/apiSpacesShares/copySpaces.feature
+++ b/tests/acceptance/features/apiSpacesShares/copySpaces.feature
@@ -524,7 +524,7 @@ Feature: copy file
     Then the HTTP status code should be "403"
     And for user "Alice" the content of the file "/testshare/overwritethis.txt" of the space "Shares" should be "ownCloud test text file 1"
 
-  @issue-4393
+  @issue-7208
   Scenario: copy a file over the top of an existing folder received as a user share
     Given using spaces DAV path
     And user "Alice" has uploaded file with content "ownCloud test text file 1" to "/textfile1.txt"
@@ -538,7 +538,7 @@ Feature: copy file
     And as "Alice" file "/textfile1.txt" should exist
     And user "Alice" should not have any received shares
 
-  @issue-4393
+  @issue-7208
   Scenario: copy a folder over the top of an existing file received as a user share
     Given using spaces DAV path
     And user "Alice" has created folder "/FOLDER"
diff --git a/tests/acceptance/features/bootstrap/SearchContext.php b/tests/acceptance/features/bootstrap/SearchContext.php
index a3163aa304b..2c843e2a687 100644
--- a/tests/acceptance/features/bootstrap/SearchContext.php
+++ b/tests/acceptance/features/bootstrap/SearchContext.php
@@ -52,13 +52,13 @@ class SearchContext implements Context {
 	 * @return void
 	 */
 	public function userSearchesUsingWebDavAPI(
-		string $user,
-		string $pattern,
-		?string	$limit = null,
-		?string $scope = null,
-		?string $spaceName = null,
+		string    $user,
+		string    $pattern,
+		?string   $limit = null,
+		?string   $scope = null,
+		?string   $spaceName = null,
 		TableNode $properties = null
-	):void {
+	): void {
 		// Because indexing of newly uploaded files or directories with ocis is decoupled and occurs asynchronously, a short wait is necessary before searching files or folders.
 		sleep(4);
 		$user = $this->featureContext->getActualUsername($user);
@@ -113,10 +113,10 @@ class SearchContext implements Context {
 	 * @throws Exception
 	 */
 	public function fileOrFolderInTheSearchResultShouldContainProperties(
-		string $path,
-		string $user,
+		string    $path,
+		string    $user,
 		TableNode $properties
-	):void {
+	): void {
 		$user = $this->featureContext->getActualUsername($user);
 		$this->featureContext->verifyTableNodeColumns($properties, ['name', 'value']);
 		$properties = $properties->getHash();
@@ -163,10 +163,47 @@ class SearchContext implements Context {
 	 *
 	 * @return void
 	 */
-	public function before(BeforeScenarioScope $scope):void {
+	public function before(BeforeScenarioScope $scope): void {
 		// Get the environment
 		$environment = $scope->getEnvironment();
 		// Get all the contexts you need in this context
 		$this->featureContext = $environment->getContext('FeatureContext');
 	}
+
+	/**
+	 * @Then /^the search result should contain these (?:files|entries) with highlight on keyword "([^"]*)"/
+	 *
+	 * @param TableNode $expectedFiles
+	 * @param string $expectedContent
+	 *
+	 * @return void
+	 *
+	 * @throws Exception
+	 */
+	public function theSearchResultShouldContainEntriesWithHighlight(
+		TableNode $expectedFiles,
+		string    $expectedContent
+	): void {
+		$this->featureContext->verifyTableNodeColumnsCount($expectedFiles, 1);
+		$elementRows = $expectedFiles->getRows();
+		$foundEntries = $this->featureContext->findEntryFromSearchResponse(
+			null,
+			true
+		);
+		foreach ($elementRows as $index => $expectedFile) {
+			$filename = $expectedFile[0];
+			$content = $foundEntries[$filename];
+			// Extract the content between the <mark> tags
+			preg_match('/<mark>(.*?)<\/mark>/s', $content, $matches);
+			$actualContent = isset($matches[1]) ? $matches[1] : '';
+
+			// Remove any leading/trailing whitespace for comparison
+			$actualContent = trim($actualContent);
+			Assert::assertEquals(
+				$expectedContent,
+				$actualContent,
+				"Expected text highlight to be '$expectedContent' but found '$actualContent'"
+			);
+		}
+	}
 }
diff --git a/tests/acceptance/features/bootstrap/TagContext.php b/tests/acceptance/features/bootstrap/TagContext.php
index 1aeb2efefa1..e608a3e801a 100644
--- a/tests/acceptance/features/bootstrap/TagContext.php
+++ b/tests/acceptance/features/bootstrap/TagContext.php
@@ -24,6 +24,7 @@ use Behat\Behat\Hook\Scope\BeforeScenarioScope;
 use Behat\Gherkin\Node\TableNode;
 use PHPUnit\Framework\Assert;
 use TestHelpers\GraphHelper;
+use \Psr\Http\Message\ResponseInterface;
 
 require_once 'bootstrap.php';
 
@@ -53,18 +54,16 @@ class TagContext implements Context {
 	}
 
 	/**
-	 * @When /^user "([^"]*)" creates the following tags for (folder|file)\s?"([^"]*)" of space "([^"]*)":$/
-	 *
 	 * @param string $user
 	 * @param string $fileOrFolder   (file|folder)
 	 * @param string $resource
 	 * @param string $space
 	 * @param TableNode $table
 	 *
-	 * @return void
+	 * @return ResponseInterface
 	 * @throws Exception
 	 */
-	public function theUserCreatesFollowingTags(string $user, string $fileOrFolder, string $resource, string $space, TableNode $table):void {
+	public function createTags(string $user, string $fileOrFolder, string $resource, string $space, TableNode $table):ResponseInterface {
 		$tagNameArray = [];
 		foreach ($table->getRows() as $value) {
 			$tagNameArray[] = $value[0];
@@ -75,7 +74,7 @@ class TagContext implements Context {
 			$resourceId = $this->spacesContext->getFileId($user, $space, $resource);
 		}
 
-		$response = GraphHelper::createTags(
+		return GraphHelper::createTags(
 			$this->featureContext->getBaseUrl(),
 			$this->featureContext->getStepLineRef(),
 			$user,
@@ -83,6 +82,22 @@ class TagContext implements Context {
 			$resourceId,
 			$tagNameArray
 		);
+	}
+
+	/**
+	 * @When /^user "([^"]*)" creates the following tags for (folder|file) "([^"]*)" of space "([^"]*)":$/
+	 *
+	 * @param string $user
+	 * @param string $fileOrFolder   (file|folder)
+	 * @param string $resource
+	 * @param string $space
+	 * @param TableNode $table
+	 *
+	 * @return void
+	 * @throws Exception
+	 */
+	public function theUserCreatesFollowingTags(string $user, string $fileOrFolder, string $resource, string $space, TableNode $table):void {
+		$response = $this->createTags($user, $fileOrFolder, $resource, $space, $table);
 		$this->featureContext->setResponse($response);
 	}
 
@@ -99,8 +114,8 @@ class TagContext implements Context {
 	 * @throws Exception
 	 */
 	public function theUserHasCreatedFollowingTags(string $user, string $fileOrFolder, string $resource, string $space, TableNode $table):void {
-		$this->theUserCreatesFollowingTags($user, $fileOrFolder, $resource, $space, $table);
-		$this->featureContext->theHttpStatusCodeShouldBe(200);
+		$response = $this->createTags($user, $fileOrFolder, $resource, $space, $table);
+		$this->featureContext->theHttpStatusCodeShouldBe(200, "", $response);
 	}
 
 	/**
@@ -175,18 +190,16 @@ class TagContext implements Context {
 	}
 
 	/**
-	 * @When /^user "([^"]*)" removes the following tags for (folder|file)\s?"([^"]*)" of space "([^"]*)":$/
-	 *
 	 * @param string $user
 	 * @param string $fileOrFolder   (file|folder)
 	 * @param string $resource
 	 * @param string $space
 	 * @param TableNode $table
 	 *
-	 * @return void
+	 * @return ResponseInterface
 	 * @throws Exception
 	 */
-	public function userRemovesTagsFromResourceOfTheSpace(string $user, string $fileOrFolder, string $resource, string $space, TableNode $table):void {
+	public function removeTagsFromResourceOfTheSpace(string $user, string $fileOrFolder, string $resource, string $space, TableNode $table):ResponseInterface {
 		$tagNameArray = [];
 		foreach ($table->getRows() as $value) {
 			$tagNameArray[] = $value[0];
@@ -198,7 +211,7 @@ class TagContext implements Context {
 			$resourceId = $this->spacesContext->getFileId($user, $space, $resource);
 		}
 
-		$response = GraphHelper::deleteTags(
+		return GraphHelper::deleteTags(
 			$this->featureContext->getBaseUrl(),
 			$this->featureContext->getStepLineRef(),
 			$user,
@@ -206,6 +219,22 @@ class TagContext implements Context {
 			$resourceId,
 			$tagNameArray
 		);
+	}
+
+	/**
+	 * @When /^user "([^"]*)" removes the following tags for (folder|file) "([^"]*)" of space "([^"]*)":$/
+	 *
+	 * @param string $user
+	 * @param string $fileOrFolder   (file|folder)
+	 * @param string $resource
+	 * @param string $space
+	 * @param TableNode $table
+	 *
+	 * @return void
+	 * @throws Exception
+	 */
+	public function userRemovesTagsFromResourceOfTheSpace(string $user, string $fileOrFolder, string $resource, string $space, TableNode $table):void {
+		$response = $this->removeTagsFromResourceOfTheSpace($user, $fileOrFolder, $resource, $space, $table);
 		$this->featureContext->setResponse($response);
 	}
 
@@ -222,7 +251,7 @@ class TagContext implements Context {
 	 * @throws Exception
 	 */
 	public function userHAsRemovedTheFollowingTagsForFileOfSpace(string $user, string $fileOrFolder, string $resource, string $space, TableNode $table):void {
-		$this->userRemovesTagsFromResourceOfTheSpace($user, $fileOrFolder, $resource, $space, $table);
-		$this->featureContext->theHttpStatusCodeShouldBe(200);
+		$response = $this->removeTagsFromResourceOfTheSpace($user, $fileOrFolder, $resource, $space, $table);
+		$this->featureContext->theHttpStatusCodeShouldBe(200, "", $response);
 	}
 }
diff --git a/tests/acceptance/features/bootstrap/TrashbinContext.php b/tests/acceptance/features/bootstrap/TrashbinContext.php
index ef098f3d125..c237a25be9e 100644
--- a/tests/acceptance/features/bootstrap/TrashbinContext.php
+++ b/tests/acceptance/features/bootstrap/TrashbinContext.php
@@ -37,8 +37,6 @@ class TrashbinContext implements Context {
 	private FeatureContext $featureContext;
 
 	/**
-	 * @When user :user empties the trashbin using the trashbin API
-	 *
 	 * @param string|null $user user
 	 *
 	 * @return ResponseInterface
@@ -46,7 +44,7 @@ class TrashbinContext implements Context {
 	public function emptyTrashbin(?string $user):ResponseInterface {
 		$user = $this->featureContext->getActualUsername($user);
 		$davPathVersion = $this->featureContext->getDavPathVersion();
-		$response = WebDavHelper::makeDavRequest(
+		return WebDavHelper::makeDavRequest(
 			$this->featureContext->getBaseUrl(),
 			$user,
 			$this->featureContext->getPasswordForUser($user),
@@ -58,11 +56,18 @@ class TrashbinContext implements Context {
 			$davPathVersion,
 			'trash-bin'
 		);
-
-		$this->featureContext->setResponse($response);
-		return $response;
 	}
 
+	/**
+	 * @When user :user empties the trashbin using the trashbin API
+	 *
+	 * @param string $user user
+	 *
+	 * @return void
+	 */
+	public function userEmptiesTrashbin(string $user): void {
+		$this->featureContext->setResponse($this->emptyTrashbin($user));
+	}
 	/**
 	 * @Given user :user has emptied the trashbin
 	 *
@@ -72,12 +77,7 @@ class TrashbinContext implements Context {
 	 */
 	public function userHasEmptiedTrashbin(string $user):void {
 		$response = $this->emptyTrashbin($user);
-
-		Assert::assertEquals(
-			204,
-			$response->getStatusCode(),
-			__METHOD__ . " Expected status code was '204' but got '" . $response->getStatusCode() . "'"
-		);
+		$this->featureContext->theHTTPStatusCodeShouldBe(204, '', $response);
 	}
 
 	/**
@@ -547,7 +547,8 @@ class TrashbinContext implements Context {
 	public function userTriesToRestoreFromTrashbinOfUser(?string $asUser, ?string $path, ?string $user):void {
 		$user = $this->featureContext->getActualUsername($user);
 		$asUser = $this->featureContext->getActualUsername($asUser);
-		$this->restoreElement($user, $path, null, true, $asUser);
+		$response = $this->restoreElement($user, $path, null, true, $asUser);
+		$this->featureContext->setResponse($response);
 	}
 
 	/**
@@ -565,7 +566,8 @@ class TrashbinContext implements Context {
 	public function userTriesToRestoreFromTrashbinOfUserUsingPassword(?string $asUser, ?string $path, ?string $user, ?string $password):void {
 		$asUser = $this->featureContext->getActualUsername($asUser);
 		$user = $this->featureContext->getActualUsername($user);
-		$this->restoreElement($user, $path, null, true, $asUser, $password);
+		$response = $this->restoreElement($user, $path, null, true, $asUser, $password);
+		$this->featureContext->setResponse($response);
 	}
 
 	/**
@@ -752,18 +754,18 @@ class TrashbinContext implements Context {
 	 * @param string|null $asUser - To send request as another user
 	 * @param string|null $password
 	 *
-	 * @return void
+	 * @return ResponseInterface
 	 * @throws JsonException
 	 * @throws GuzzleException
 	 */
-	private function sendUndeleteRequest(string $user, string $trashItemHRef, string $destinationPath, ?string $asUser = null, ?string $password = null):void {
+	private function sendUndeleteRequest(string $user, string $trashItemHRef, string $destinationPath, ?string $asUser = null, ?string $password = null):ResponseInterface {
 		$asUser = $asUser ?? $user;
 		$destinationPath = \trim($destinationPath, '/');
 		$destinationValue = $this->featureContext->getBaseUrl() . "/remote.php/dav/files/$user/$destinationPath";
 
 		$trashItemHRef = $this->convertTrashbinHref($trashItemHRef);
 		$headers['Destination'] = $destinationValue;
-		$response = $this->featureContext->makeDavRequest(
+		return $this->featureContext->makeDavRequest(
 			$asUser,
 			'MOVE',
 			$trashItemHRef,
@@ -776,7 +778,6 @@ class TrashbinContext implements Context {
 			[],
 			$user
 		);
-		$this->featureContext->setResponse($response);
 	}
 
 	/**
@@ -787,11 +788,11 @@ class TrashbinContext implements Context {
 	 * @param string|null $asUser - To send request as another user
 	 * @param string|null $password
 	 *
-	 * @return void
+	 * @return ResponseInterface
 	 * @throws JsonException
 	 * @throws GuzzleException
 	 */
-	private function restoreElement(string $user, string $originalPath, ?string $destinationPath = null, bool $throwExceptionIfNotFound = true, ?string $asUser = null, ?string $password = null):void {
+	private function restoreElement(string $user, string $originalPath, ?string $destinationPath = null, bool $throwExceptionIfNotFound = true, ?string $asUser = null, ?string $password = null):ResponseInterface {
 		$asUser = $asUser ?? $user;
 		$listing = $this->listTrashbinFolder($user);
 		$originalPath = \trim($originalPath, '/');
@@ -800,14 +801,13 @@ class TrashbinContext implements Context {
 		}
 		foreach ($listing as $entry) {
 			if ($entry['original-location'] === $originalPath) {
-				$this->sendUndeleteRequest(
+				return $this->sendUndeleteRequest(
 					$user,
 					$entry['href'],
 					$destinationPath,
 					$asUser,
 					$password
 				);
-				return;
 			}
 		}
 		// The requested element to restore was not even in the trashbin.
@@ -907,7 +907,8 @@ class TrashbinContext implements Context {
 	 * @throws Exception
 	 */
 	public function userTriesToRestoreElementInTrash(string $user, string $originalPath):void {
-		$this->restoreElement($user, $originalPath, null, false);
+		$response = $this->restoreElement($user, $originalPath, null, false);
+		$this->featureContext->setResponse($response);
 	}
 
 	/**
@@ -922,7 +923,7 @@ class TrashbinContext implements Context {
 	 */
 	public function elementInTrashIsRestored(?string $user, string $originalPath):void {
 		$user = $this->featureContext->getActualUsername($user);
-		$this->restoreElement($user, $originalPath);
+		$this->featureContext->setResponse($this->restoreElement($user, $originalPath));
 	}
 
 	/**
@@ -955,7 +956,8 @@ class TrashbinContext implements Context {
 	 * @throws Exception
 	 */
 	public function elementInTrashHasBeenRestored(string $user, string $originalPath):void {
-		$this->restoreElement($user, $originalPath);
+		$response = $this->restoreElement($user, $originalPath);
+		$this->featureContext->theHTTPStatusCodeShouldBe(201, "", $response);
 		if ($this->isInTrash($user, $originalPath)) {
 			throw new Exception("File previously located at $originalPath is still in the trashbin");
 		}
@@ -978,7 +980,7 @@ class TrashbinContext implements Context {
 		?string $destinationPath
 	):void {
 		$user = $this->featureContext->getActualUsername($user);
-		$this->restoreElement($user, $originalPath, $destinationPath);
+		$this->featureContext->setResponse($this->restoreElement($user, $originalPath, $destinationPath));
 	}
 
 	/**
diff --git a/tests/acceptance/features/bootstrap/WebDav.php b/tests/acceptance/features/bootstrap/WebDav.php
index d2eb5a926bb..74571332663 100644
--- a/tests/acceptance/features/bootstrap/WebDav.php
+++ b/tests/acceptance/features/bootstrap/WebDav.php
@@ -5355,6 +5355,7 @@ trait WebDav {
 	 * and returns found search results if found else returns false
 	 *
 	 * @param string|null $entryNameToSearch
+	 * @param bool|null $searchForHighlightString
 	 *
 	 * @return string|array|boolean
 	 *
@@ -5362,10 +5363,11 @@ trait WebDav {
 	 * array if $entryNameToSearch is not given
 	 * boolean false if $entryNameToSearch is given and is not found
 	 *
-	 * @throws GuzzleException
+	 * @throws Exception
 	 */
 	public function findEntryFromSearchResponse(
-		?string $entryNameToSearch = null
+		?string $entryNameToSearch = null,
+		?bool $searchForHighlightString = false
 	) {
 		// trim any leading "/" passed by the caller, we can just match the "raw" name
 		if ($entryNameToSearch !== null) {
@@ -5387,7 +5389,12 @@ trait WebDav {
 			if ($entryNameToSearch === $resourcePath) {
 				return $resourcePath;
 			}
-			$results[] = $resourcePath;
+			if ($searchForHighlightString) {
+				$actualHighlightString =  $item->xpath("d:propstat//oc:highlights");
+				$results[$resourcePath] = (string)$actualHighlightString[0];
+			} else {
+				$results[] = $resourcePath;
+			}
 		}
 		if ($entryNameToSearch === null) {
 			return $results;