├── .github └── tokenmill-logo.svg ├── .gitignore ├── .gitlab-ci.yml ├── Dockerfile.jdk10.test ├── Dockerfile.jdk11.test ├── Dockerfile.jdk7.test ├── Dockerfile.jdk8.test ├── Dockerfile.jdk9.test ├── LICENSE ├── Makefile ├── README.md ├── deps.edn ├── project.clj ├── src └── timewords │ ├── core.clj │ ├── fuzzy │ ├── en │ │ ├── absolute.clj │ │ ├── en.clj │ │ ├── relative.clj │ │ └── utils.clj │ ├── fuzzy.clj │ └── lt │ │ ├── absolute.clj │ │ ├── lt.clj │ │ ├── relative.clj │ │ └── utils.clj │ └── standard │ ├── formats.clj │ ├── standard.clj │ └── utils.clj └── test └── timewords ├── cleaner_test.clj ├── core_test.clj ├── de_test.clj ├── en_relative_quantified_test.clj ├── en_relative_test.clj ├── en_test.clj ├── es_test.clj ├── fr_test.clj ├── java_test.clj ├── lt_relative_test.clj ├── lt_test.clj ├── ru_test.clj └── special_cases_test.clj /.github/tokenmill-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | image/svg+xml -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | pom.xml 2 | pom.xml.asc 3 | *jar 4 | /lib/ 5 | /classes/ 6 | /target/ 7 | /checkouts/ 8 | .lein-deps-sum 9 | .lein-repl-history 10 | .lein-plugins/ 11 | .lein-failures 12 | .nrepl-port 13 | .idea/ 14 | metadata-detection.iml 15 | timewords.iml 16 | .cpcache 17 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | stages: 2 | - test 3 | - deploy 4 | 5 | variables: 6 | DOCKER_DRIVER: overlay2 7 | DOCKER_HOST: tcp://docker:2375/ 8 | 9 | run-unit-tests: 10 | stage: test 11 | image: clojure:alpine 12 | when: always 13 | script: 14 | - lein do clean, test 15 | 16 | run-jdk7-tests: 17 | stage: test 18 | image: docker:stable 19 | when: manual 20 | services: 21 | - docker:dind 22 | script: 23 | - docker build -f Dockerfile.jdk7.test -t timewords:jdk7 . 24 | - docker run timewords:jdk7 25 | 26 | run-jdk8-tests: 27 | stage: test 28 | image: docker:stable 29 | when: always 30 | services: 31 | - docker:dind 32 | script: 33 | - docker build -f Dockerfile.jdk8.test -t timewords:jdk8 . 34 | - docker run timewords:jdk8 35 | 36 | run-jdk10-tests: 37 | stage: test 38 | image: docker:stable 39 | when: always 40 | services: 41 | - docker:dind 42 | script: 43 | - docker build -f Dockerfile.jdk10.test -t timewords:jdk10 . 44 | - docker run timewords:jdk10 45 | 46 | run-jdk11-tests: 47 | stage: test 48 | image: docker:stable 49 | when: always 50 | services: 51 | - docker:dind 52 | script: 53 | - docker build -f Dockerfile.jdk11.test -t timewords:jdk11 . 54 | - docker run timewords:jdk11 55 | -------------------------------------------------------------------------------- /Dockerfile.jdk10.test: -------------------------------------------------------------------------------- 1 | FROM openjdk:10 2 | 3 | ENV LEIN_VERSION=2.8.1 4 | ENV LEIN_INSTALL=/usr/local/bin/ 5 | 6 | WORKDIR /tmp 7 | 8 | # Download the whole repo as an archive 9 | RUN mkdir -p $LEIN_INSTALL \ 10 | && wget -q https://raw.githubusercontent.com/technomancy/leiningen/$LEIN_VERSION/bin/lein-pkg \ 11 | && echo "Comparing lein-pkg checksum ..." \ 12 | && echo "019faa5f91a463bf9742c3634ee32fb3db8c47f0 *lein-pkg" | sha1sum -c - \ 13 | && mv lein-pkg $LEIN_INSTALL/lein \ 14 | && chmod 0755 $LEIN_INSTALL/lein \ 15 | && wget -q https://github.com/technomancy/leiningen/releases/download/$LEIN_VERSION/leiningen-$LEIN_VERSION-standalone.zip \ 16 | && wget -q https://github.com/technomancy/leiningen/releases/download/$LEIN_VERSION/leiningen-$LEIN_VERSION-standalone.zip.asc \ 17 | && gpg --keyserver ha.pool.sks-keyservers.net --recv-key 2B72BF956E23DE5E830D50F6002AF007D1A7CC18 \ 18 | && echo "Verifying Jar file signature ..." \ 19 | && gpg --verify leiningen-$LEIN_VERSION-standalone.zip.asc \ 20 | && rm leiningen-$LEIN_VERSION-standalone.zip.asc \ 21 | && mkdir -p /usr/share/java \ 22 | && mv leiningen-$LEIN_VERSION-standalone.zip /usr/share/java/leiningen-$LEIN_VERSION-standalone.jar 23 | 24 | ENV PATH=$PATH:$LEIN_INSTALL 25 | ENV LEIN_ROOT 1 26 | 27 | # Install clojure 1.9.0 so users don't have to download it every time 28 | RUN echo '(defproject dummy "" :dependencies [[org.clojure/clojure "1.9.0"]])' > project.clj \ 29 | && lein deps && rm project.clj 30 | 31 | 32 | RUN mkdir -p /usr/src/app 33 | WORKDIR /usr/src/app 34 | COPY project.clj /usr/src/app/ 35 | RUN lein deps 36 | COPY . /usr/src/app 37 | 38 | CMD ["lein", "test"] 39 | -------------------------------------------------------------------------------- /Dockerfile.jdk11.test: -------------------------------------------------------------------------------- 1 | FROM openjdk:11 2 | 3 | ENV LEIN_VERSION=2.8.1 4 | ENV LEIN_INSTALL=/usr/local/bin/ 5 | 6 | WORKDIR /tmp 7 | 8 | # Download the whole repo as an archive 9 | RUN mkdir -p $LEIN_INSTALL \ 10 | && wget -q https://raw.githubusercontent.com/technomancy/leiningen/$LEIN_VERSION/bin/lein-pkg \ 11 | && echo "Comparing lein-pkg checksum ..." \ 12 | && echo "019faa5f91a463bf9742c3634ee32fb3db8c47f0 *lein-pkg" | sha1sum -c - \ 13 | && mv lein-pkg $LEIN_INSTALL/lein \ 14 | && chmod 0755 $LEIN_INSTALL/lein \ 15 | && wget -q https://github.com/technomancy/leiningen/releases/download/$LEIN_VERSION/leiningen-$LEIN_VERSION-standalone.zip \ 16 | && wget -q https://github.com/technomancy/leiningen/releases/download/$LEIN_VERSION/leiningen-$LEIN_VERSION-standalone.zip.asc \ 17 | && gpg --keyserver ha.pool.sks-keyservers.net --recv-key 2B72BF956E23DE5E830D50F6002AF007D1A7CC18 \ 18 | && echo "Verifying Jar file signature ..." \ 19 | && gpg --verify leiningen-$LEIN_VERSION-standalone.zip.asc \ 20 | && rm leiningen-$LEIN_VERSION-standalone.zip.asc \ 21 | && mkdir -p /usr/share/java \ 22 | && mv leiningen-$LEIN_VERSION-standalone.zip /usr/share/java/leiningen-$LEIN_VERSION-standalone.jar 23 | 24 | ENV PATH=$PATH:$LEIN_INSTALL 25 | ENV LEIN_ROOT 1 26 | 27 | # Install clojure 1.9.0 so users don't have to download it every time 28 | RUN echo '(defproject dummy "" :dependencies [[org.clojure/clojure "1.9.0"]])' > project.clj \ 29 | && lein deps && rm project.clj 30 | 31 | 32 | RUN mkdir -p /usr/src/app 33 | WORKDIR /usr/src/app 34 | COPY project.clj /usr/src/app/ 35 | RUN lein deps 36 | COPY . /usr/src/app 37 | 38 | CMD ["lein", "test"] 39 | -------------------------------------------------------------------------------- /Dockerfile.jdk7.test: -------------------------------------------------------------------------------- 1 | FROM openjdk:7 2 | 3 | ENV LEIN_VERSION=2.8.1 4 | ENV LEIN_INSTALL=/usr/local/bin/ 5 | 6 | WORKDIR /tmp 7 | 8 | # Download the whole repo as an archive 9 | RUN mkdir -p $LEIN_INSTALL \ 10 | && wget -q https://raw.githubusercontent.com/technomancy/leiningen/$LEIN_VERSION/bin/lein-pkg \ 11 | && echo "Comparing lein-pkg checksum ..." \ 12 | && echo "019faa5f91a463bf9742c3634ee32fb3db8c47f0 *lein-pkg" | sha1sum -c - \ 13 | && mv lein-pkg $LEIN_INSTALL/lein \ 14 | && chmod 0755 $LEIN_INSTALL/lein \ 15 | && wget -q https://github.com/technomancy/leiningen/releases/download/$LEIN_VERSION/leiningen-$LEIN_VERSION-standalone.zip \ 16 | && wget -q https://github.com/technomancy/leiningen/releases/download/$LEIN_VERSION/leiningen-$LEIN_VERSION-standalone.zip.asc \ 17 | && gpg --keyserver ha.pool.sks-keyservers.net --recv-key 2B72BF956E23DE5E830D50F6002AF007D1A7CC18 \ 18 | && echo "Verifying Jar file signature ..." \ 19 | && gpg --verify leiningen-$LEIN_VERSION-standalone.zip.asc \ 20 | && rm leiningen-$LEIN_VERSION-standalone.zip.asc \ 21 | && mkdir -p /usr/share/java \ 22 | && mv leiningen-$LEIN_VERSION-standalone.zip /usr/share/java/leiningen-$LEIN_VERSION-standalone.jar 23 | 24 | ENV PATH=$PATH:$LEIN_INSTALL 25 | ENV LEIN_ROOT 1 26 | 27 | # Install clojure 1.9.0 so users don't have to download it every time 28 | RUN echo '(defproject dummy "" :dependencies [[org.clojure/clojure "1.9.0"]])' > project.clj \ 29 | && lein deps && rm project.clj 30 | 31 | 32 | RUN mkdir -p /usr/src/app 33 | WORKDIR /usr/src/app 34 | COPY project.clj /usr/src/app/ 35 | RUN lein deps 36 | COPY . /usr/src/app 37 | 38 | CMD ["lein", "test"] 39 | -------------------------------------------------------------------------------- /Dockerfile.jdk8.test: -------------------------------------------------------------------------------- 1 | FROM clojure:alpine 2 | RUN mkdir -p /usr/src/app 3 | WORKDIR /usr/src/app 4 | COPY project.clj /usr/src/app/ 5 | RUN lein deps 6 | COPY . /usr/src/app 7 | 8 | CMD ["lein", "test"] -------------------------------------------------------------------------------- /Dockerfile.jdk9.test: -------------------------------------------------------------------------------- 1 | FROM openjdk:9 2 | 3 | ENV LEIN_VERSION=2.8.1 4 | ENV LEIN_INSTALL=/usr/local/bin/ 5 | 6 | WORKDIR /tmp 7 | 8 | # Download the whole repo as an archive 9 | RUN mkdir -p $LEIN_INSTALL \ 10 | && wget -q https://raw.githubusercontent.com/technomancy/leiningen/$LEIN_VERSION/bin/lein-pkg \ 11 | && echo "Comparing lein-pkg checksum ..." \ 12 | && echo "019faa5f91a463bf9742c3634ee32fb3db8c47f0 *lein-pkg" | sha1sum -c - \ 13 | && mv lein-pkg $LEIN_INSTALL/lein \ 14 | && chmod 0755 $LEIN_INSTALL/lein \ 15 | && wget -q https://github.com/technomancy/leiningen/releases/download/$LEIN_VERSION/leiningen-$LEIN_VERSION-standalone.zip \ 16 | && wget -q https://github.com/technomancy/leiningen/releases/download/$LEIN_VERSION/leiningen-$LEIN_VERSION-standalone.zip.asc \ 17 | && gpg --keyserver ha.pool.sks-keyservers.net --recv-key 2B72BF956E23DE5E830D50F6002AF007D1A7CC18 \ 18 | && echo "Verifying Jar file signature ..." \ 19 | && gpg --verify leiningen-$LEIN_VERSION-standalone.zip.asc \ 20 | && rm leiningen-$LEIN_VERSION-standalone.zip.asc \ 21 | && mkdir -p /usr/share/java \ 22 | && mv leiningen-$LEIN_VERSION-standalone.zip /usr/share/java/leiningen-$LEIN_VERSION-standalone.jar 23 | 24 | ENV PATH=$PATH:$LEIN_INSTALL 25 | ENV LEIN_ROOT 1 26 | 27 | # Install clojure 1.9.0 so users don't have to download it every time 28 | RUN echo '(defproject dummy "" :dependencies [[org.clojure/clojure "1.9.0"]])' > project.clj \ 29 | && lein deps && rm project.clj 30 | 31 | 32 | RUN mkdir -p /usr/src/app 33 | WORKDIR /usr/src/app 34 | COPY project.clj /usr/src/app/ 35 | RUN lein deps 36 | COPY . /usr/src/app 37 | 38 | CMD ["lein", "test"] 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2019 Tokenmill, UAB 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | run-tests-jdk-7: 2 | docker build -f Dockerfile.jdk7.test -t timewords:jdk7 . && docker run timewords:jdk7 3 | 4 | run-tests-jdk-8: 5 | docker build -f Dockerfile.jdk8.test -t timewords:jdk8 . && docker run timewords:jdk8 6 | 7 | run-tests-jdk-10: 8 | docker build -f Dockerfile.jdk10.test -t timewords:jdk10 . && docker run timewords:jdk10 9 | 10 | run-tests-jdk-11: 11 | docker build -f Dockerfile.jdk11.test -t timewords:jdk11 . && docker run timewords:jdk11 12 | 13 | run-tests-from-8-to-11-till-fail: 14 | make run-tests-jdk-8 && make run-tests-jdk-10 && make run-tests-jdk-11 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | # timewords 6 | 7 | [![Clojars Project](https://img.shields.io/clojars/v/lt.tokenmill/timewords.svg)](https://clojars.org/lt.tokenmill/timewords) 8 | 9 | Library to parse a date string to java.util.Date object. For example: 10 | 11 | * "2 weeks from now" -> 2018-11-29T09:52:23.000-00:00 12 | * "28th February 2019" -> 2019-02-28T00:00:00.000-00:00 13 | 14 | When the library cannot parse the input string it returns *`nil`*. 15 | 16 | More formally, from four types of temporal expressions: *time*, *duration*, *interval*, and *set*; only one type is of interest: *time*. Also, *time* type can be divided into two subtypes: fuzzy (e.g. last Sunday) and absolute (1st of January, 2019). To parse a fuzzy time string a *reference time* (i.e. a `java.util.Date object`) is required. By default, reference time is ``now``. 17 | 18 | The library is designed to support multiple languages. Currently two languages are supported: English and Lithuanian. Default language is English. 19 | 20 | # Usage 21 | 22 | ## Clojure 23 | 24 | Add a dependency to your 25 | * *project.clj* - `[lt.tokenmill/timewords "0.5.0"]` 26 | * *deps.edn* - `lt.tokenmill/timewords {:mvn/version "0.5.0"}` 27 | 28 | ```clojure 29 | (require '[timewords.core :refer [parse]]) 30 | => nil 31 | (parse "2001-01-01") 32 | => #inst"2001-01-01T00:00:00.000-00:00" 33 | (timewords.core/parse "now") 34 | => #inst"2016-12-13T09:52:02.000-00:00" 35 | (timewords.core/parse "2 weeks ago") 36 | => #inst"2016-11-29T09:52:23.000-00:00" 37 | (timewords.core/parse "2 weeks from now") 38 | => #inst"2016-12-29T09:54:23.000-00:00" 39 | (timewords.core/parse "last monday") 40 | => #inst"2016-12-12T09:54:23.000-00:00" 41 | (timewords.core/parse "last june") 42 | => #inst"2016-06-12T09:54:23.000-00:00" 43 | (timewords.core/parse "last spring") 44 | => #inst"2016-05-12T09:54:23.000-00:00" 45 | 46 | (timewords.core/parse "29th February 2016") 47 | => #inst"2016-02-29T00:00:00.000-00:00" 48 | (timewords.core/parse "29th February 2017") 49 | => #inst"2017-02-01T00:00:00.000-00:00" 50 | (timewords.core/parse "Sunday, 1st January 2017") 51 | => #inst"2017-01-01T00:00:00.000-00:00" 52 | 53 | (timewords.core/parse "2016 m. gruodžio 22 d. 11:10" nil "lt") 54 | => #inst"2016-12-22T11:10:00.000-00:00" 55 | ``` 56 | 57 | ## Java 58 | 59 | As of now the JAR is stored in Clojars, therefore maven is not going to find the artifact. 60 | You should add the repository information to your `pom.xml`: 61 | ```xml 62 | 63 | 64 | clojars.org 65 | http://clojars.org/repo 66 | 67 | 68 | 69 | ``` 70 | 71 | Add a maven dependency to your `pom.xml`: 72 | 73 | ```xml 74 | 75 | lt.tokenmill 76 | timewords 77 | 0.4.0 78 | 79 | ``` 80 | 81 | ```java 82 | import lt.tokenmill.timewords.Timewords; 83 | 84 | public static void main(String[] args) { 85 | Timewords timewords = new Timewords(); 86 | Date d1 = timewords.parse("2001-01-01"); 87 | Date d2 = timewords.parse("2001-01-01", new Date()); 88 | Date d3 = timewords.parse("2001-01-01", new Date(), "en"); 89 | } 90 | ``` 91 | Note that `timewords` depends on `org.clojure/clojure` which must be provided. 92 | 93 | # Notes 94 | 95 | Relative dates that can be understood as a time period, e.g. `last December` are rounded to the beginning of the period, e.g. `last December` translates to `2016-12-01T00:00:00Z`. 96 | 97 | Timewords of the form `in monthname` is interpreted as if it refers to the past, i.e. `in December` means `last December`. 98 | 99 | Timewords of the form `this monthname` is interpreted as if it refers to the future, i.e. `in December` means `next December`. 100 | 101 | Timeword which is only a name of a weekday, e.g. `Monday`, is interpreted as if it refers to the past, i.e. `Monday` means the same as `last Monday`. 102 | 103 | Timeword of the form `next weekday` means the first day in the future which which weekday is the one mentioned, e.g. `next Monday` means the first Monday to come. If today is Monday and we are parsing `next Monday` then it means a date after 7 days. 104 | 105 | Timeword of a form `this weekday`, e.g. `this Monday`, is interpreted as if it refers to the future, i.e. `this Monday` means the same as `next Monday`. 106 | 107 | # TODO 108 | 109 | TODO: 110 | - [ ] relative Lithuanian dates. 111 | 112 | ## License 113 | 114 | Copyright © 2019 [TokenMill UAB](http://www.tokenmill.lt). 115 | 116 | Distributed under the The Apache License, Version 2.0. 117 | -------------------------------------------------------------------------------- /deps.edn: -------------------------------------------------------------------------------- 1 | {:deps {org.clojure/clojure {:mvn/version "1.10.0"} 2 | clj-time {:mvn/version "0.14.4"}} 3 | :paths ["src" "classes"] 4 | :aliases {:test 5 | {:extra-paths ["test"] 6 | :extra-deps {com.cognitect/test-runner {:git/url "https://github.com/cognitect-labs/test-runner.git" 7 | :sha "028a6d41ac9ac5d5c405dfc38e4da6b4cc1255d5"}} 8 | :main-opts ["-m" "cognitect.test-runner"]}}} 9 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject lt.tokenmill/timewords "0.5.0" 2 | :description "Library to parse time strings." 3 | 4 | :dependencies [[clj-time "0.14.4"]] 5 | 6 | :aot [timewords.core] 7 | 8 | :profiles {:dev {:dependencies []} 9 | :provided {:dependencies [[org.clojure/clojure "1.8.0"]]}}) 10 | -------------------------------------------------------------------------------- /src/timewords/core.clj: -------------------------------------------------------------------------------- 1 | (ns timewords.core 2 | (:require [clojure.string :as s] 3 | [clj-time.core :as joda] 4 | [clj-time.coerce :as jco] 5 | [timewords.standard.standard :as standard] 6 | [timewords.fuzzy.fuzzy :as fuzzy]) 7 | (:import (java.util Date) 8 | (org.joda.time DateTime)) 9 | (:gen-class 10 | :name lt.tokenmill.timewords.Timewords 11 | :methods [[parse [java.lang.String] java.util.Date] 12 | [parse [java.lang.String java.util.Date] java.util.Date] 13 | [parse [java.lang.String java.util.Date java.lang.String] java.util.Date]])) 14 | 15 | (defn parse 16 | "Given a string that represents date, returns a java.util.Date object. 17 | Cases that are not handled return nil. 18 | Second (optional) parameter must be a language code, e.g. `en`. 19 | Third (optional) parameter is a document-time which must be nil or java.util.Date. 20 | `document-time` is used to parse relative timewords like 'yesterday'." 21 | ^Date [^String date-string & [^Date document-time ^String language]] 22 | (try 23 | (when-not (or (nil? document-time) (instance? Date document-time)) 24 | (throw (Exception. "document-time is not either nil or java.util.Date."))) 25 | (when-not (or (nil? language) (string? language)) 26 | (throw (Exception. "language parameter is not either nil or java.lang.String."))) 27 | (let [date-string (.replaceAll date-string "[\\u00A0\\u202F\\uFEFF\\u2007\\u180E]" " ") 28 | ^String language (or language "en") 29 | ^DateTime document-time (or (DateTime. document-time) (joda/now))] 30 | (when (not (s/blank? date-string)) 31 | (jco/to-date 32 | (or 33 | (standard/to-date date-string language document-time) 34 | (fuzzy/to-date date-string language document-time))))) 35 | (catch Exception e 36 | (prn (str "Caught exception: '" (.getMessage e) "', while parsing timeword '" date-string 37 | "' with language '" language "' and with document-time '" document-time "'.")) 38 | nil))) 39 | 40 | (defn -parse 41 | ^Date [_ ^String date-string & [^Date document-time ^String language]] 42 | (parse date-string document-time language)) 43 | -------------------------------------------------------------------------------- /src/timewords/fuzzy/en/absolute.clj: -------------------------------------------------------------------------------- 1 | (ns timewords.fuzzy.en.absolute 2 | (:require [clojure.string :as s] 3 | [clj-time.core :as joda]) 4 | (:import (org.joda.time DateTime))) 5 | 6 | (def months 7 | {#"(january)|(jan.)" "1" 8 | #"(fabruary)|(feb.)" "2" 9 | #"(march)|(mar.)" "3" 10 | #"(april)|(apr.)" "4" 11 | #"(may)|(may.)" "5" 12 | #"(june)|(jun.)" "6" 13 | #"(july)|(jul.)" "7" 14 | #"(august)|(aug.)" "8" 15 | #"(september)|(sep.)" "9" 16 | #"(october)|(oct.)" "10" 17 | #"(november)|(nov.)" "11" 18 | #"(december)|(dec.)" "12"}) 19 | 20 | (defn- re [r s] (let [f (re-find r s)] (if (coll? f) (first f) f))) 21 | 22 | (defn is-pm? 23 | "Checks if fuzzy date represents a PM time." 24 | [fuzzy-date] 25 | (if (re-find #"(?i)\b(\d{1,2}):\d{2}[\s]?pm" fuzzy-date) 26 | true 27 | false)) 28 | 29 | (defn fix-am-hours 30 | "Convert civil am hours to military hours." 31 | [^String am-hour] 32 | (if am-hour 33 | (str (let [hour (Integer/parseInt am-hour)] 34 | (if (= 12 hour) 35 | 0 36 | hour))) 37 | nil)) 38 | 39 | (defn fix-pm-hours 40 | "Convert civil pm hours to military hours." 41 | [^long pm-hour] 42 | (if (< pm-hour 12) 43 | (+ 12 pm-hour) 44 | pm-hour)) 45 | 46 | (defn safe-parse [fuzzy-date] (if fuzzy-date (Integer/parseInt fuzzy-date) 0)) 47 | 48 | (defn year [fuzzy-date] (first (re-find #"\b(19|20)\d{2}\b" fuzzy-date))) 49 | 50 | (defn month [fuzzy-date] 51 | (if (re-matches #"\b\d{2}/\d{2}/\d{4}\b.*" fuzzy-date) 52 | (let [date-part (re-find #"\b\d{2}/\d{2}/\d{4}\b" fuzzy-date) 53 | month-and-day (->> (s/split date-part #"/") 54 | (map #(Integer/parseInt %)) 55 | (filter #(>= 31 %)))] 56 | (str 57 | (if (seq (filter #(> 12 %) month-and-day)) 58 | (first (filter #(> 12 %) month-and-day)) 59 | (first month-and-day)))) 60 | (some (fn [[re nr]] (when (re-find re fuzzy-date) nr)) months))) 61 | 62 | (defn day [fuzzy-date] (re-find #"\b\d{1,2}\b" fuzzy-date)) 63 | (defn hour [fuzzy-date] (if (is-pm? fuzzy-date) 64 | (-> (second (re-find #"\b(\d{1,2}):\d{2}[\\b]?" fuzzy-date)) 65 | (safe-parse) 66 | (fix-pm-hours) 67 | str) 68 | (-> (second (re-find #"\b(\d{1,2})[:\.]\d{2}[\\b]?" fuzzy-date)) 69 | (fix-am-hours)))) 70 | (defn minute [fuzzy-date] (second (re-find #"\b\d{1,2}[:|\.](\d{2})[\\b]?" fuzzy-date))) 71 | (defn zecond [fuzzy-date] (second (re-find #"\b\d{2}:\d{2}:(\d{2})\b" fuzzy-date))) 72 | 73 | (defn parse-absolute-date 74 | [^String ls & [^DateTime document-time]] 75 | (letfn [(if-conj [coll x] (if x (conj coll x) coll)) 76 | (now-part [time-part] (time-part document-time)) 77 | (add-years [date-parts] 78 | (conj date-parts 79 | (if-let [y (year ls)] 80 | y 81 | (str (now-part joda/year))))) 82 | (add-months [date-parts] (if-conj date-parts (month ls))) 83 | (add-days [date-parts] (if-conj date-parts (day ls))) 84 | (add-hours [date-parts] (if-conj date-parts (hour ls))) 85 | (add-minutes [date-parts] (if-conj date-parts (minute ls))) 86 | (add-seconds [date-parts] (if-conj date-parts (zecond ls)))] 87 | (-> [] 88 | add-years 89 | add-months 90 | add-days 91 | add-hours 92 | add-minutes 93 | add-seconds))) -------------------------------------------------------------------------------- /src/timewords/fuzzy/en/en.clj: -------------------------------------------------------------------------------- 1 | (ns timewords.fuzzy.en.en 2 | (:require [clojure.string :as s] 3 | [clj-time.core :as joda] 4 | [timewords.fuzzy.en.relative :refer [parse-relative-date]] 5 | [timewords.fuzzy.en.absolute :refer [parse-absolute-date]] 6 | [timewords.fuzzy.en.utils :as utils]) 7 | (:import (org.joda.time DateTime))) 8 | 9 | (defn is-relative-date? [s] 10 | (if (or (not (re-find #"\d" s)) 11 | (re-find #"ago|yesterday|tomorrow|last|previous|next|now" s)) 12 | true 13 | false)) 14 | 15 | (defn parse-date [^String s & [^DateTime document-time]] 16 | (let [ls (-> s (utils/clean) (s/lower-case)) 17 | document-time (or document-time (joda/now))] 18 | (if (is-relative-date? ls) 19 | (parse-relative-date ls document-time) 20 | (parse-absolute-date ls document-time)))) 21 | -------------------------------------------------------------------------------- /src/timewords/fuzzy/en/relative.clj: -------------------------------------------------------------------------------- 1 | (ns timewords.fuzzy.en.relative 2 | (:require [clojure.string :as s] 3 | [clj-time.core :as joda] 4 | [clj-time.coerce :as tc]) 5 | (:import (org.joda.time DateTime))) 6 | 7 | (def special-to-normal {"a sec" "1 second" 8 | "a second" "1 second" 9 | "a min" "1 minute" 10 | "a minute" "1 minute" 11 | "a hour" "1 hour" 12 | "an hour" "1 hour" 13 | "a day" "1 day" 14 | "a week" "7 days" 15 | "a month" "1 month" 16 | "a year" "1 year" 17 | "yesterday" "1 day ago" 18 | "tomorrow" "1 day from now" 19 | "this week" "last monday" 20 | "last month" "1 month ago" 21 | "previous month" "1 month ago" 22 | "next month" "1 month from now" 23 | "this month" "0 months ago" 24 | "last year" "1 year ago" 25 | "next year" "1 year from now" 26 | "this year" "0 years ago" 27 | "previous year" "1 year ago" 28 | "now" "0 seconds ago" 29 | "today" "0 days ago"}) 30 | 31 | (defn get-seconds [joda-datetime] 32 | (-> joda-datetime (tc/to-long) (mod 60000) (quot 1000))) 33 | 34 | (defn date-to-str-seq 35 | [joda-datetime] 36 | (when (not (nil? joda-datetime)) 37 | (map str [(joda/year joda-datetime) 38 | (joda/month joda-datetime) 39 | (joda/day joda-datetime) 40 | (joda/hour joda-datetime) 41 | (joda/minute joda-datetime) 42 | (get-seconds joda-datetime) ;to avoid using joda/seconds 43 | ]))) 44 | 45 | 46 | (defn ^DateTime floor 47 | "Floors the given date-time dt to the given time unit dt-fn, 48 | e.g. (floor (now) hour) returns (now) for all units 49 | up to and including the hour" 50 | [^DateTime dt dt-fn] 51 | (let [dt-fns [joda/year joda/month joda/day joda/hour joda/minute get-seconds joda/milli]] 52 | (apply joda/date-time 53 | (map apply 54 | (concat (take-while (partial not= dt-fn) dt-fns) [dt-fn]) 55 | (repeat [dt]))))) 56 | 57 | (defn parse-relative-time [cleaned-timeword document-time plus-or-minus] 58 | (let [amount (Integer/parseInt (re-find #"\d+" cleaned-timeword))] 59 | (cond 60 | (re-find #"\d+s$|sec|secs|second|seconds" cleaned-timeword) (-> (plus-or-minus document-time (joda/millis (* amount 1000))) (floor get-seconds)) 61 | (re-find #"\d+m$|min|mins|minute|minutes" cleaned-timeword) (-> (plus-or-minus document-time (joda/minutes amount)) (floor joda/minute)) 62 | (re-find #"\d+h$|hour|hr|hours|hrs" cleaned-timeword) (-> (plus-or-minus document-time (joda/hours amount)) (floor joda/hour)) 63 | (re-find #"\d+d$|day|days" cleaned-timeword) (-> (plus-or-minus document-time (joda/days amount)) (floor joda/day)) 64 | (re-find #"\d+w$|week|weeks" cleaned-timeword) (-> (plus-or-minus document-time (joda/days (* 7 amount))) (floor joda/day)) 65 | (re-find #"month|months" cleaned-timeword) (-> (plus-or-minus document-time (joda/months amount)) (floor joda/month)) 66 | (re-find #"year|years" cleaned-timeword) (-> (plus-or-minus document-time (joda/years amount)) (floor joda/year)) 67 | :else nil))) 68 | 69 | (defn parse-some-time-ago 70 | "Handle strings like '32 mins ago'." 71 | ([s] (parse-some-time-ago s (joda/now))) 72 | ([s document-time] 73 | (let [cleaned-timeword (-> s (s/replace "ago" "") (s/trim))] 74 | (if (re-find #"\d+" s) 75 | (parse-relative-time cleaned-timeword document-time joda/minus) 76 | (when-let [normalized (get special-to-normal cleaned-timeword)] 77 | (parse-some-time-ago (str normalized " ago") document-time)))))) 78 | 79 | (defn parse-some-time-from-now 80 | "Handle strings like '32 mins from now'." 81 | ([s] (parse-some-time-from-now s (joda/now))) 82 | ([^String s ^DateTime document-time] 83 | (let [cleaned-timeword (-> s (s/replace "from now" "") (s/trim))] 84 | (if (re-find #"\d+" s) 85 | (parse-relative-time cleaned-timeword document-time joda/plus) 86 | (when-let [normalized (get special-to-normal cleaned-timeword)] 87 | (parse-some-time-from-now (str normalized " from now") document-time)))))) 88 | 89 | (defn parse-relative-weekday 90 | [^String s ^DateTime document-time plus-or-minus] 91 | (let [required-weekday (cond 92 | (re-find #"monday" s) 1 93 | (re-find #"tuesday" s) 2 94 | (re-find #"wednesday" s) 3 95 | (re-find #"thursday" s) 4 96 | (re-find #"friday" s) 5 97 | (re-find #"saturday" s) 6 98 | (re-find #"sunday" s) 7) 99 | in-required-weekday (if (= required-weekday (joda/day-of-week document-time)) 100 | (plus-or-minus document-time (joda/days 7)) 101 | (loop [datetime document-time] 102 | (if (= required-weekday (joda/day-of-week datetime)) 103 | datetime 104 | (recur (plus-or-minus datetime (joda/days 1))))))] 105 | (floor in-required-weekday joda/day))) 106 | 107 | (defn parse-last-weekday [s document-time] 108 | (parse-relative-weekday s document-time joda/minus)) 109 | 110 | (defn parse-next-weekday [s document-time] 111 | (parse-relative-weekday s document-time joda/plus)) 112 | 113 | (defn parse-relative-month [s document-time joda-minus-or-plus] 114 | (let [required-month (cond 115 | (re-find #"january" s) 1 116 | (re-find #"february" s) 2 117 | (re-find #"march" s) 3 118 | (re-find #"april" s) 4 119 | (re-find #"may" s) 5 120 | (re-find #"june" s) 6 121 | (re-find #"july" s) 7 122 | (re-find #"august" s) 8 123 | (re-find #"september" s) 9 124 | (re-find #"october" s) 10 125 | (re-find #"november" s) 11 126 | (re-find #"december" s) 12) 127 | datetime-with-adjusted-month (if (= required-month (joda/month document-time)) 128 | (joda-minus-or-plus document-time (joda/months 12)) 129 | (loop [datetime document-time] 130 | (if (= required-month (joda/month datetime)) 131 | datetime 132 | (recur (joda-minus-or-plus datetime (joda/months 1))))))] 133 | (floor datetime-with-adjusted-month joda/month))) 134 | 135 | (defn parse-last-month [s document-time] 136 | (parse-relative-month s document-time joda/minus)) 137 | 138 | (defn parse-next-month [s document-time] 139 | (parse-relative-month s document-time joda/plus)) 140 | 141 | (defn parse-last-season 142 | ([^String s] (parse-last-season s (joda/now))) 143 | ([^String s ^DateTime document-time] 144 | (let [required-season (cond 145 | (re-find #"spring" s) 1 146 | (re-find #"summer" s) 2 147 | (re-find #"autumn" s) 3 148 | (re-find #"fall" s) 3 149 | (re-find #"winter" s) 4) 150 | month->season (fn [month] 151 | (cond 152 | (<= 3 month 5) 1 153 | (<= 6 month 8) 2 154 | (<= 9 month 11) 3 155 | (<= 1 month 2) 4 156 | (= 12 month) 4))] 157 | (if (= required-season (month->season (joda/month document-time))) 158 | (joda/minus document-time (joda/months 12)) 159 | (loop [datetime document-time] 160 | (if (= required-season (month->season (joda/month datetime))) 161 | datetime 162 | (recur (joda/minus datetime (joda/months 1))))))))) 163 | 164 | (defn parse-relative-date 165 | ([^String s] (parse-relative-date s (joda/now))) 166 | ([^String s ^DateTime document-time] 167 | (let [s (or (get special-to-normal s) s)] 168 | (date-to-str-seq 169 | (cond 170 | (re-find #"ago" s) (-> s (parse-some-time-ago)) 171 | (re-find #"from now" s) (-> s (parse-some-time-from-now)) 172 | (re-find #"^(monday|tuesday|wednesday|thursday|friday|saturday|sunday)$" s) (-> s (parse-last-weekday document-time)) 173 | (re-find #"last (monday|tuesday|wednesday|thursday|friday|saturday|sunday)" s) (-> s (parse-last-weekday document-time)) 174 | (re-find #"this (monday|tuesday|wednesday|thursday|friday|saturday|sunday)" s) (-> s (parse-next-weekday document-time)) 175 | (re-find #"next (monday|tuesday|wednesday|thursday|friday|saturday|sunday)" s) (-> s (parse-next-weekday document-time)) 176 | (re-find #"last (january|february|march|april|may|june|july|august|september|october|november|december)" s) (-> s (parse-last-month document-time)) 177 | (re-find #"^in\s(early|late)?\s?(january|february|march|april|may|june|july|august|september|october|november|december)$" s) (-> s (parse-last-month document-time)) 178 | (re-find #"this (january|february|march|april|may|june|july|august|september|october|november|december)" s) (-> s (parse-next-month document-time)) 179 | (re-find #"next (january|february|march|april|may|june|july|august|september|october|november|december)" s) (-> s (parse-next-month document-time)) 180 | (re-find #"last (spring|summer|autumn|fall|winter)" s) (-> s (parse-last-season document-time)) 181 | :else nil))))) -------------------------------------------------------------------------------- /src/timewords/fuzzy/en/utils.clj: -------------------------------------------------------------------------------- 1 | (ns timewords.fuzzy.en.utils 2 | (:require [clojure.string :as s])) 3 | 4 | (defn- remove-day-names 5 | "Remove all language specific patterns which usualy accompany publication date" 6 | [date] 7 | (-> date 8 | (s/replace #"(sunday|monday|tuesday|wednesday|thursday|friday|saturday)" ""))) 9 | 10 | (defn- clean-with-regex [^String date] 11 | (reduce #(s/replace %1 %2 "") date [#"[|>;]" #"^[,;\.]"])) 12 | 13 | (defn clean [date] 14 | (-> date 15 | ;(remove-day-names) 16 | (clean-with-regex) 17 | (s/trim))) -------------------------------------------------------------------------------- /src/timewords/fuzzy/fuzzy.clj: -------------------------------------------------------------------------------- 1 | (ns timewords.fuzzy.fuzzy 2 | (:require [clj-time.core :refer [date-time]] 3 | [timewords.fuzzy.en.en :as en] 4 | [timewords.fuzzy.lt.lt :as lt]) 5 | (:import (org.joda.time DateTime))) 6 | 7 | (defn to-date 8 | "Parses string dates into components represented by numeric values: 9 | 1-12 for months 10 | 1-31 for days 11 | 19??-20?? for years. 12 | 13 | Dispatches parsing by language. Default language is en." 14 | ^DateTime [^String fuzzy-date & [^String language ^DateTime document-time]] 15 | (cond 16 | (= language "en") (if-let [date-parts (en/parse-date fuzzy-date document-time)] 17 | (apply date-time (map #(Integer/parseInt %) date-parts))) 18 | (= language "lt") (if-let [date-parts (lt/parse-date fuzzy-date document-time)] 19 | (apply date-time (map #(Integer/parseInt %) date-parts))) 20 | :else nil)) 21 | -------------------------------------------------------------------------------- /src/timewords/fuzzy/lt/absolute.clj: -------------------------------------------------------------------------------- 1 | (ns timewords.fuzzy.lt.absolute 2 | (:require [clojure.string :as s] 3 | [clj-time.core :as joda]) 4 | (:import (org.joda.time DateTime))) 5 | 6 | (def months 7 | {#"sausi[so]" "1" 8 | #"vasari[so]" "2" 9 | #"kovas|kovo" "3" 10 | #"balandis|balandžio" "4" 11 | #"geguž[ės]" "5" 12 | #"birželi[so]" "6" 13 | #"liepa|liepos" "7" 14 | #"rugpjūtis|rugpjūčio" "8" 15 | #"rugsėjis|rugsėjo" "9" 16 | #"spali[so]" "10" 17 | #"lapkritis|lapkričio" "11" 18 | #"gruodis|gruodžio" "12"}) 19 | 20 | (defn- re [r s] (let [f (re-find r s)] (if (coll? f) (first f) f))) 21 | 22 | (defn month [fuzzy-date] 23 | (some (fn [[re nr]] (when (re-find re fuzzy-date) nr)) months)) 24 | 25 | (defn year [fuzzy-date] (first (re-find #"\b(19|20)\d{2}\b" fuzzy-date))) 26 | (defn day [fuzzy-date] (re-find #"\b\d{1,2}\b" fuzzy-date)) 27 | (defn hour [fuzzy-date] (second (re-find #"\b(\d{2}):\d{2}\b" fuzzy-date))) 28 | (defn minute [fuzzy-date] (second (re-find #"\b\d{2}:(\d{2})\b" fuzzy-date))) 29 | (defn zecond [fuzzy-date] (second (re-find #"\b\d{2}:\d{2}:(\d{2})\b" fuzzy-date))) 30 | 31 | (defn parse-date [^String s & [^DateTime document-time]] 32 | (let [ls (s/lower-case s)] 33 | (letfn [(if-conj [coll x] (if x (conj coll x) coll)) 34 | (now-part [time-part] (time-part document-time)) 35 | (add-years [date-parts] 36 | (conj date-parts 37 | (if-let [y (year ls)] 38 | y 39 | (str (now-part joda/year))))) 40 | (add-months [date-parts] (if-conj date-parts (month ls))) 41 | (add-days [date-parts] (if-conj date-parts (day ls))) 42 | (add-hours [date-parts] (if-conj date-parts (hour ls))) 43 | (add-minutes [date-parts] (if-conj date-parts (minute ls))) 44 | (add-seconds [date-parts] (if-conj date-parts (zecond ls)))] 45 | (-> [] 46 | add-years 47 | add-months 48 | add-days 49 | add-hours 50 | add-minutes 51 | add-seconds)))) 52 | 53 | (defn parse-absolute-date [^String s & [^DateTime document-time]] 54 | (parse-date s document-time)) 55 | -------------------------------------------------------------------------------- /src/timewords/fuzzy/lt/lt.clj: -------------------------------------------------------------------------------- 1 | (ns timewords.fuzzy.lt.lt 2 | (:require [clj-time.core :as joda] 3 | [timewords.fuzzy.lt.absolute :refer [parse-absolute-date]] 4 | [timewords.fuzzy.lt.relative :refer [parse-relative-date]] 5 | [timewords.fuzzy.lt.utils :refer [clean]]) 6 | (:import (org.joda.time DateTime))) 7 | 8 | (defn relative-date? [s] 9 | (if (or (not (re-find #"\d" s)) 10 | (re-find #"prieš" s) 11 | (re-find #"vakar" s) 12 | (re-find #"šiandien" s)) 13 | true 14 | false)) 15 | 16 | (defn parse-date [^String s & [^DateTime document-time]] 17 | (let [document-time (or document-time (joda/now)) 18 | s (clean s)] 19 | (if (relative-date? s) 20 | (parse-relative-date s document-time) 21 | (parse-absolute-date s document-time)))) 22 | -------------------------------------------------------------------------------- /src/timewords/fuzzy/lt/relative.clj: -------------------------------------------------------------------------------- 1 | (ns timewords.fuzzy.lt.relative 2 | (:require [clojure.string :as s] 3 | [clj-time.core :as joda] 4 | [clj-time.coerce :as tc]) 5 | (:import (org.joda.time DateTime))) 6 | 7 | (defn get-seconds [joda-datetime] 8 | (-> joda-datetime (tc/to-long) (mod 60000) (quot 1000))) 9 | 10 | (defn date-to-str-seq 11 | [joda-datetime] 12 | (when (not (nil? joda-datetime)) 13 | (map str [(joda/year joda-datetime) 14 | (joda/month joda-datetime) 15 | (joda/day joda-datetime) 16 | (joda/hour joda-datetime) 17 | (joda/minute joda-datetime) 18 | (get-seconds joda-datetime) ;to avoid using joda/seconds 19 | ]))) 20 | 21 | (defn parse-days-ago [s document-time] 22 | (let [days (Integer/parseInt (re-find #"\d+" s))] 23 | (joda/floor 24 | (joda/minus document-time (joda/days days)) 25 | joda/day))) 26 | 27 | (defn parse-today-time [s document-time] 28 | (let [[hh mm] (map #(Integer/parseInt %) (re-seq #"\d{2}" s))] 29 | (joda/floor 30 | (-> (joda/floor document-time joda/day) 31 | (joda/plus (joda/hours hh)) 32 | (joda/plus (joda/minutes mm))) 33 | joda/minute))) 34 | 35 | (defn parse-hours-ago [s document-time] 36 | (prn s document-time) 37 | (let [hours (Integer/parseInt (re-find #"\d+" s))] 38 | (joda/floor 39 | (joda/minus document-time (joda/hours hours)) 40 | joda/hour))) 41 | 42 | (defn parse-weeks-ago [s document-time] 43 | (let [days (Integer/parseInt (re-find #"\d+" s))] 44 | (joda/floor 45 | (joda/minus document-time (joda/days (* 7 days))) 46 | joda/day))) 47 | 48 | (defn parse-yesterday-time [s document-time] 49 | (let [[hh mm] (map #(Integer/parseInt %) (re-seq #"\d{2}" s))] 50 | (joda/floor 51 | (-> document-time 52 | (joda/floor joda/day) 53 | (joda/minus (joda/days 1)) 54 | (joda/plus (joda/hours hh)) 55 | (joda/plus (joda/minutes mm))) 56 | joda/minute))) 57 | 58 | (defn parse-relative-date 59 | [^String s ^DateTime document-time] 60 | (date-to-str-seq 61 | (cond 62 | (re-matches #"prieš \d+ d.?" s) (parse-days-ago s document-time) 63 | (re-matches #"\d+ d.? prieš" s) (parse-days-ago s document-time) 64 | (re-matches #"\d+ sav.? prieš" s) (parse-weeks-ago s document-time) 65 | (re-matches #"\d+ val.? prieš" s) (parse-hours-ago s document-time) 66 | (re-matches #"šiandien \d{2}:\d{2}" s) (parse-today-time s document-time) 67 | (re-matches #"vakar \d{2}:\d{2}" s) (parse-yesterday-time s document-time) 68 | :else nil))) -------------------------------------------------------------------------------- /src/timewords/fuzzy/lt/utils.clj: -------------------------------------------------------------------------------- 1 | (ns timewords.fuzzy.lt.utils 2 | (:require [clojure.string :as s])) 3 | 4 | (defn clean 5 | "Remove all language specific patterns which usualy accompany publication date" 6 | [^String d] 7 | (-> d 8 | (s/replace #"(Autorius|Publikuota):" "") 9 | (s/replace #", atnaujinta 20.*" "") 10 | (s/trim))) 11 | -------------------------------------------------------------------------------- /src/timewords/standard/formats.clj: -------------------------------------------------------------------------------- 1 | (ns timewords.standard.formats 2 | (:require [clojure.string :as str]) 3 | (:import (java.util Locale TimeZone Date) 4 | (org.joda.time.format ISODateTimeFormat DateTimeFormat DateTimeFormatter) 5 | (org.joda.time DateTime LocalDateTime) 6 | (org.joda.time.chrono LenientChronology ISOChronology))) 7 | 8 | (def lenient-chronology (LenientChronology/getInstance (ISOChronology/getInstance))) 9 | 10 | (defn fmt [pattern & [locale default-year lenient]] 11 | (cond-> (DateTimeFormat/forPattern pattern) 12 | locale (.withLocale locale) 13 | default-year (.withDefaultYear default-year) 14 | lenient (.withChronology lenient-chronology))) 15 | 16 | (defn common-formatters [locale] 17 | [(ISODateTimeFormat/dateTimeParser) 18 | (fmt "MMddyyyy" locale) 19 | (fmt "yyyyMMdd'T'HH:mm:ss'Z'" locale) 20 | (fmt "yy-MM-dd" locale) 21 | (fmt "MMM dd HH:mm yyyy" locale) 22 | (fmt "yyyy-MM-dd HH:mm" locale) 23 | (fmt "yyyy-MM-dd HH:mm:ss" locale) 24 | (fmt "yyyy-MM-dd, HH:mm" locale) 25 | (fmt "yyyy/MM/dd" locale) 26 | (fmt "dd/MM/yyyy, HH:mm" locale) 27 | (fmt "dd/MM/yyyy" locale) 28 | (fmt "MM/dd/yyyy" locale) 29 | (fmt "MMMM yyyy" locale) 30 | (fmt "dd'st' MMMM yyyy" locale) 31 | (fmt "dd'nd' MMMM yyyy" locale) 32 | (fmt "dd'rd' MMMM yyyy" locale) 33 | (fmt "dd'th' MMMM yyyy" locale) 34 | (fmt "EEE, dd'st' MMMM yyyy" locale) 35 | (fmt "EEE, dd'nd' MMMM yyyy" locale) 36 | (fmt "EEE, dd'rd' MMMM yyyy" locale) 37 | (fmt "EEE, dd'th' MMMM yyyy" locale) 38 | (fmt "EEE MMMM dd, yyyy" locale) 39 | (fmt "EEE MMMM dd, yyyy h:mma" locale) 40 | (fmt "EEE MMMM dd, yyyy h:mma z" locale) 41 | (fmt "EEE MMMM dd yyyy HH:mm 'UTC'Z" locale) 42 | (fmt "dd MMM yyyy HH:mm" locale) 43 | (fmt "MMM dd, yyyy HH:mm z" locale) 44 | (fmt "dd MMM yyyy" locale) 45 | (fmt "MMMM dd, yyyy HH:mm" locale) 46 | (fmt "MMMM dd, yyyy - HH:mm" locale) 47 | (fmt "EEE, MMMM dd, yyyy" locale) 48 | (fmt "yyyy-MM-dd'T'HH:mm:ssZ" locale) 49 | (fmt "yyyy-MM-dd'T'HH:mm:ss" locale) 50 | (fmt "MMMM dd, yyyy h:mm a" locale) 51 | (fmt "MMMM dd, yyyy" locale) 52 | (fmt "MMMM. dd, yyyy" locale) 53 | (fmt "EEE MMMM dd HH:mm:ss z yyyy" locale) 54 | (fmt "yyyy-MM-dd HH:mm:ss z" locale) 55 | (fmt "EEE dd MMMM yyyy HH:mm" locale) 56 | (fmt "yyyy/MM/dd HH:mm:ss" locale) 57 | (fmt "yyyy-MM-dd HH:mm:ss.S" locale) 58 | (fmt "MM/dd/yyyy HH:mm" locale) 59 | (fmt "MM/dd/yyyy HH:mm:ss a z" locale) 60 | (fmt "MMMM dd, yyyy, h:mm a" locale) 61 | (fmt "EEE, MMMM dd, yyyy, h:mm a" locale) 62 | (fmt "EEEE dd MMMM yyyy HH.mm z" locale) 63 | (fmt "yyyy-MM-dd '/' HH:mm" locale) 64 | (fmt "yyyy.MM.dd HH:mm" locale) 65 | (fmt "HH:mm yyyy.MM.dd" locale) 66 | (fmt "yyyy.MM.dd" locale) 67 | (fmt "EEEE, dd. MMMM yyyy, HH:mm 'Uhr'" locale) 68 | (fmt "dd. MMMM yyyy, HH:mm 'Uhr'" locale) 69 | (fmt "EEEE, dd. MMMM yyyy" locale) 70 | (fmt "dd. MMMM yyyy" locale) 71 | (fmt "EEEE, dd.MM.yyyy, HH:mm" locale) 72 | (fmt "EEEE, dd.MM.yyyy" locale) 73 | (fmt "HH:mm dd.MM.yyyy" locale) 74 | (fmt "HH:mm dd MMMM yyyy" locale) 75 | (fmt "dd.MM.yyyy" locale) 76 | (fmt "MMMM dd" locale (.getYear (DateTime.))) 77 | (fmt "MMMM dd 'd.' HH:mm" locale (.getYear (DateTime.))) 78 | (fmt "yyyy/MM/dd HH:mm" locale) 79 | (fmt "yyyy MM dd HH:mm" locale) 80 | (fmt "yyyy MMMM dd" locale) 81 | (fmt "yyyy MMMM dd, EEEE" locale) 82 | (fmt "yyyy MMMM dd'd.'" locale) 83 | (fmt "yyyy MMMM dd 'd.'" locale) 84 | (fmt "yyyy MMMM dd'd.' HH:mm" locale) 85 | (fmt "yyyy MMMM dd 'd.' HH:mm" locale) 86 | (fmt "yyyy 'metų' MMMM dd" locale) 87 | (fmt "yyyy MMMM dd HH:mm" locale) 88 | (fmt "yyyy MMMM dd HH:mm:ss" locale) 89 | (fmt "yyyy 'm.' MMMM dd 'd.' HH:mm" locale) 90 | (fmt "yyyy MMMM dd'd.' HH:mm" locale) 91 | (fmt "yyyy MMMM dd 'd.' HH:mm" locale) 92 | (fmt "yyyy MMMM 'mėn.' dd 'd.' HH:mm:ss" locale) 93 | (fmt "yyyy-MM-dd '/' HH:mm" locale) 94 | (fmt "yyyy.MM.dd HH:mm" locale) 95 | (fmt "HH:mm yyyy.MM.dd" locale) 96 | (fmt "dd.MM.yyyy - HH:mm'h'" locale) 97 | (fmt "dd.MM.yyyy – HH:mm 'H.'" locale) 98 | (fmt "dd.MM.yyyy, HH:mm" locale) 99 | (fmt "dd.MM.yyyy в HH:mm" locale) 100 | (fmt "dd.MM.yyyy HH:mm" locale) 101 | (fmt "dd.MM.yyyy - HH:mm" locale) 102 | (fmt "yyyy-MM-dd HH:mm:ss 'H'" locale) 103 | (fmt "dd/MM/yyyy HH:mm 'h'" locale) 104 | (fmt "dd/MM/yyyy - HH:mm" locale) 105 | (fmt "dd/MM/yyyy HH:mm" locale) 106 | (fmt "dd MMMM yyyy - HH:mm 'CEST'" locale) 107 | (fmt "dd MMMM yyyy - HH:mm" locale) 108 | (fmt "dd MMMM yyyy 'г.,' HH:mm" locale) 109 | (fmt "dd MMMM yyyy HH:mm'h' 'CEST'" locale) 110 | (fmt "dd MMMM'.' yyyy HH:mm" locale) 111 | (fmt "dd MMMM, yyyy" locale) 112 | (fmt "dd/MM/yyyy HH:mm'h'" locale) 113 | (fmt "dd/MM/yyyy HH:mm:ss 'CET'" locale) 114 | (fmt "dd/MM/yyyy HH:mm:ss" locale) 115 | (fmt "HH:mm - dd/MM/yy" locale) 116 | (fmt "dd/MM/yy" locale) 117 | (fmt "dd/MM/yy HH:mm" locale) 118 | (fmt "dd/MM HH:mm" locale (.getYear (DateTime.))) 119 | (fmt "EEEE, dd MMMM yyyy, HH:mm" locale) 120 | (fmt "EEEE, dd MMMM yyyy HH:mm" locale) 121 | (fmt "dd MMMM yyyy, HH:mm" locale) 122 | (fmt "dd MMMM yyyy : HH:mm" locale) 123 | (fmt "EEEE, dd/MM/yyyy" locale) 124 | (fmt "HH:mm dd/MM/yyyy" locale) 125 | (fmt "EEEE dd MMMM yyyy" locale) 126 | (fmt "dd 'de' MMMM 'de' yyyy'.' HH:mm'h'" locale) 127 | (fmt "dd 'de' MMMM 'de' yyyy" locale) 128 | (fmt "yyyy-MM-dd HH:mm:ss Z" locale) 129 | (fmt "HH:mm, dd MMMM yyyy" locale) 130 | (fmt "MM/dd/yyyy hh:mm:ss a" locale) 131 | (fmt "hh : mm a - dd/MM/yyyy" locale) 132 | (fmt "MM/dd/yyyy hh:mm a" locale) 133 | (fmt "HH:mm MMMM dd, yyyy" locale) 134 | (fmt "dd MMMM HH:mm" locale (.getYear (DateTime.))) 135 | (fmt "dd MMMM, HH:mm" locale (.getYear (DateTime.)))]) 136 | 137 | (defn ninth-or-newer-jdk? [] 138 | (not (re-matches #"1\..*" (System/getProperty "java.version")))) 139 | 140 | (def normalize-lt 141 | (if (ninth-or-newer-jdk?) 142 | (fn [text] 143 | (-> text 144 | (str/replace #"sau " "sausio ") 145 | (str/replace #"(^vas |vasario )" "vasario ") 146 | (str/replace #"(kov |kovo )" "kovo ") 147 | (str/replace #"(bal |balandžio )" "balandžio ") 148 | (str/replace #"(geg |gegužės )" "gegužės ") 149 | (str/replace #"(bir |birželio )" "birželio ") 150 | (str/replace #"(lie |liepos )" "liepos ") 151 | (str/replace #"(rgp |rugpjūčio )" "rugpjūčio ") 152 | (str/replace #"(rgs |rugsėjo )" "rugsėjo ") 153 | (str/replace #"(spa |spalio )" "spalio ") 154 | (str/replace #"(lap |lapkričio )" "lapkričio ") 155 | (str/replace #"(gru |gruodžio )" "gruodžio "))) 156 | (fn [text] 157 | (-> text 158 | (str/replace #"sau " "sausio ") 159 | (str/replace #"(^vas |vasario )" "vasaris ") 160 | (str/replace #"(kov |kovo )" "kovas ") 161 | (str/replace #"(bal |balandžio )" "balandis ") 162 | (str/replace #"(geg |gegužės )" "gegužė ") 163 | (str/replace #"(bir |birželio )" "birželis ") 164 | (str/replace #"(lie |liepos )" "liepa ") 165 | (str/replace #"(rgp |rugpjūčio )" "rugpjūtis ") 166 | (str/replace #"(rgs |rugsėjo )" "rugsėjis ") 167 | (str/replace #"(spa |spalio )" "spalis ") 168 | (str/replace #"(lap |lapkričio )" "lapkritis ") 169 | (str/replace #"(gru |gruodžio )" "gruodis "))))) 170 | 171 | (defn normalize [text] 172 | (-> text 173 | (str/lower-case) 174 | (normalize-lt) 175 | 176 | (str/replace #"(.*\d+)(h)(\d{2})" "$1:$3") 177 | (str/replace #"([Ll]e\s)(\d+.*)" "$2") 178 | (str/replace #"(.*\d{2,})(\sà\s)(\d+.*)" "$1 $3") 179 | (str/replace #"(.*\d+\s[a-zA-Z]+)(\sà\s)(\d+.*)" "$1 $3") 180 | 181 | (str/replace "pst" "PST") 182 | (str/replace "edt" "EDT") 183 | (str/replace "bst" "GMT") 184 | (str/replace " gmt" " GMT") 185 | (str/replace "p.m." "pm") 186 | (str/replace "utc" "UTC") 187 | (str/replace "est" "EST") 188 | (str/replace "mar." "marzo") 189 | (str/replace " mar " " marzo ") 190 | 191 | (str/replace "январь" "января") 192 | (str/replace "февраль" "февраля") 193 | (str/replace "март " "марта ") 194 | (str/replace "апрель" "апреля") 195 | (str/replace "май" "мая") 196 | (str/replace "июнь" "июня") 197 | (str/replace "июль" "июля") 198 | (str/replace "август " "августа ") 199 | (str/replace "сентябрь" "сентября") 200 | (str/replace "октябрь" "октября") 201 | (str/replace "ноябрь" "ноября") 202 | (str/replace "декабрь" "декабря"))) 203 | 204 | (defn special-cases [text locale document-time] 205 | (let [document-time (or document-time (DateTime.)) 206 | fmts [(fmt "hh:mm a z") 207 | (fmt "hh:mm a") 208 | (fmt "hh:mma z") 209 | (fmt "hh:mma") 210 | (fmt "HH:mm z") 211 | (fmt "HH:mm")]] 212 | (first 213 | (for [^DateTimeFormatter formatter fmts 214 | :let [parsed (try 215 | (.toDate (-> (.parseLocalDateTime formatter text) 216 | (.withYear (.getYear document-time)) 217 | (.withMonthOfYear (.getMonthOfYear document-time)) 218 | (.withDayOfMonth (.getDayOfMonth document-time))) 219 | (TimeZone/getTimeZone "GMT")) 220 | (catch Exception _ nil))] 221 | :when parsed] parsed)))) 222 | 223 | (def used-locales (atom {})) 224 | 225 | (defn formatters [locale] 226 | (if-let [used-formatters (get @used-locales locale)] 227 | used-formatters 228 | (let [locale-formatters (common-formatters locale)] 229 | (swap! used-locales assoc locale locale-formatters) 230 | locale-formatters))) 231 | 232 | (defn parse 233 | ([^String text] 234 | (parse text Locale/ENGLISH nil)) 235 | ([^String text ^Locale locale] 236 | (parse text locale nil)) 237 | ([^String text ^Locale locale ^DateTime document-time] 238 | (let [text (normalize text) 239 | locale-formatters (formatters locale) 240 | parsed-dates (for [^DateTimeFormatter formatter locale-formatters 241 | :let [parsed (try 242 | (let [^LocalDateTime pdate (.parseLocalDateTime formatter text)] 243 | (when (< 10000 (.getYear pdate)) 244 | (throw (Exception.))) 245 | (.toDate (if (and (not (nil? document-time)) 246 | (not= (.getYear document-time) (.getYear (DateTime.)))) 247 | (.minusYears pdate (- (.getYear pdate) (.getYear document-time))) 248 | pdate) 249 | (TimeZone/getTimeZone "GMT"))) 250 | (catch Exception _ nil))] 251 | :when parsed] parsed)] 252 | (if (empty? parsed-dates) 253 | (conj parsed-dates (special-cases text locale document-time)) 254 | parsed-dates)))) 255 | -------------------------------------------------------------------------------- /src/timewords/standard/standard.clj: -------------------------------------------------------------------------------- 1 | (ns timewords.standard.standard 2 | (:require [clojure.string :as s] 3 | [clj-time.coerce :refer [from-date]] 4 | [timewords.standard.formats :as formats] 5 | [timewords.standard.utils :as utils]) 6 | (:import (org.joda.time DateTime) 7 | (java.util Locale))) 8 | 9 | (def date-part-normalizations 10 | {#"(?i)p\.m\." "PM" 11 | #"(?i)a\.m\." "AM" 12 | #"([ \d])ET$" "$1 EST"}) 13 | 14 | (defn normalize-date-parts 15 | [^String date] 16 | (reduce 17 | (fn [date [match replacement]] 18 | (s/replace date match replacement)) 19 | date 20 | date-part-normalizations)) 21 | 22 | (defn clean-date-string [^String date] 23 | (-> date 24 | s/trim 25 | (s/replace #"\s+" " "))) 26 | 27 | (defn multi-format-parse [^String date ^String language ^DateTime document-time] 28 | (let [locale (Locale/forLanguageTag language)] 29 | (->> (formats/parse date locale document-time) 30 | (map from-date) 31 | ; for cases where multiple patterns match 32 | (sort) 33 | (reverse) 34 | (first)))) 35 | 36 | (defn to-date 37 | [^String date & [^String language ^DateTime document-time]] 38 | (when-not (empty? date) 39 | (-> date 40 | utils/clean 41 | clean-date-string 42 | normalize-date-parts 43 | (multi-format-parse language document-time)))) 44 | -------------------------------------------------------------------------------- /src/timewords/standard/utils.clj: -------------------------------------------------------------------------------- 1 | (ns timewords.standard.utils 2 | (:require [clojure.string :as s])) 3 | 4 | (defn- clean-with-regex [^String date] 5 | (reduce #(s/replace %1 %2 "") date [#"[|>;]" #"^[,;\.]"])) 6 | 7 | (defn clean [^String date] 8 | (-> date 9 | ;(remove-day-names) 10 | (clean-with-regex) 11 | (s/trim))) -------------------------------------------------------------------------------- /test/timewords/cleaner_test.clj: -------------------------------------------------------------------------------- 1 | (ns timewords.cleaner-test 2 | (:require [clojure.test :refer :all] 3 | [timewords.fuzzy.en.utils :refer [clean]])) 4 | 5 | (deftest cleaner-test 6 | (testing "Dirty dates" 7 | (is (= "2015-02-02" (clean "2015-02-02"))) 8 | (is (= "2015-02-02, 12:13" (clean "2015-02-02, 12:13"))) 9 | (is (= "2015-02-02, 12:13" (clean ",2015-02-02, 12:13"))))) 10 | -------------------------------------------------------------------------------- /test/timewords/core_test.clj: -------------------------------------------------------------------------------- 1 | (ns timewords.core-test 2 | (:require [clojure.test :refer :all] 3 | [clj-time.core :as joda :refer [date-time]] 4 | [timewords.core :refer :all]) 5 | (:import (java.util Date))) 6 | 7 | (defn date [& xs] (.toDate (apply date-time xs))) 8 | 9 | (deftest core-test 10 | 11 | (testing "Edge cases" 12 | (is (nil? (parse nil))) 13 | (is (nil? (parse "")))) 14 | 15 | (testing "Standard date parsing" 16 | (is (= (date 2013 3 12) (parse " 2013-03-12 "))) 17 | (is (= (date 2012 3 14 16 40) (parse "2012-03-14 16:40"))) 18 | (is (= (date 2016 2 8 9 41) (parse "08/02/16 09:41"))) 19 | (is (= (date 2000 9 14 13 19) (parse "14/09/2000, 13:19"))) 20 | (is (= (date 2000 9 14) (parse "14/09/2000"))) 21 | (is (= (date 2015 7 24 9 38) (parse "2015-07-24, 09:38"))) 22 | (is (= (date 2012 3 14 16 40 10) (parse "2012-03-14 16:40:10"))) 23 | (is (= (date 2014 12 22 22 19 48) (parse "2014-12-22T22:19:48+02:00"))) 24 | ;;; to fix 25 | (is (= (date 1865 04 27 0 0 0) (parse "1865-04-27T00:00:00-05:00"))) 26 | (is (= (date 2009 1 8) (parse "2009/01/08"))) 27 | (is (= (date 2013 3 12) (parse "2013-03-12"))) 28 | (is (= (date 2010 8) (parse "August 2010"))) 29 | (is (= (date 2013) (parse "2013"))) 30 | (is (= (date 2010 7 1 0 0 0) (parse "1st July 2010"))) 31 | (is (= (date 2010 7 2 0 0 0) (parse "2nd July 2010"))) 32 | (is (= (date 2010 7 3 0 0 0) (parse "3rd July 2010"))) 33 | (is (= (date 2010 7 8 0 0 0) (parse "8th July 2010"))) 34 | (is (= (date 2016 2 29 0 0 0) (parse "29th February 2016"))) 35 | (is (= (date 2017 1 1 0 0 0) (parse "Sunday, 1st January 2017"))) 36 | (is (= (date 2017 1 2 0 0 0) (parse "Monday, 2nd January 2017"))) 37 | (is (= (date 2017 1 3 0 0 0) (parse "Tuesday, 3rd January 2017"))) 38 | (is (= (date 2017 1 8 0 0 0) (parse "Sunday, 8th January 2017")))) 39 | 40 | (testing "EN date parsing" 41 | (is (= (date 2015 10 19) (parse "Monday October 19, 2015"))) 42 | (is (= (date 2015 10 19) (parse "Monday Oct 19, 2015"))) 43 | (is (= (date 2015 10 19) (parse "Mon Oct 19, 2015"))) 44 | (is (= (date 2015 10 19 5 44) (parse "Mon Oct 19, 2015 5:44am"))) 45 | (is (= (date 2015 10 19 17 44) (parse "Mon Oct 19, 2015 5:44pm"))) 46 | (is (= (date 2015 10 19 5 44) (parse "Mon Oct 19, 2015 5:44am PST"))) 47 | ;(is (= (date 2015 10 19 21 44) (parse "Mon Oct 19, 2015 5:44pm EDT"))) 48 | ;(is (= (date 2015 10 19 22 44) (parse "Mon Oct 19, 2015 5:44pm EST"))) 49 | ;(is (= (date 2015 10 19 21 44) (parse "Mon Oct 19, 2015 5:44PM EDT"))) 50 | ;(is (= (date 2015 10 19 16 44) (parse "Mon Oct 19, 2015 12:44pm EDT"))) 51 | (is (= (date 2015 10 19 13 30) (parse "Monday 19 October 2015 13.30 BST"))) 52 | (is (= (date 2015 10 20) (parse "20 October 2015"))) 53 | (is (= (date 2013 1 18 13 8 57) (parse "Fri Jan 18 13:08:57 UTC 2013"))) 54 | (is (= (date 2013 2 9) (parse "February 9, 2013"))) 55 | (is (= (date 2013 2 12 14) (parse "Tuesday, February 12, 2013, 2:00 PM"))) 56 | (is (= (date 2013 3 13 13 32) (parse "March 13, 2013 - 1:32PM"))) 57 | (is (= (date 2013 2 9) (parse "February 9, 2013"))) 58 | (is (= (date 2013 2 5 13 2) (parse "February 5, 2013, 1:02 PM"))) 59 | (is (= (date 2013 2 6) (parse "2013-02-06"))) 60 | (is (= (date 2013 2 7) (parse "February 07, 2013"))) 61 | (is (= (date 2012 12 9 9 54 7) (parse "12/09/2012 09:54:07 AM PST"))) 62 | (is (= (date 2013 2 4) (parse "Monday February 4, 2013"))) 63 | (is (= (date 2013 1 30 9 14) (parse "01/30/2013 09:14"))) 64 | (is (= (date 2013 1 28) (parse "2013-01-28"))) 65 | (is (= (date 2013 1 25 5 58) (parse "2013-01-25 05:58:00.0"))) 66 | (is (= (date 2013 3 26 9 28) (parse "2013-03-26T09:28-05"))) 67 | (is (= (date 2013 1 24) (parse "2013/01/24"))) 68 | (is (= (date 2013 1 24 8 46 54) (parse "2013-01-24T08:46:54Z"))) 69 | (is (= (date 2013 1 24 1 24 31) (parse "2013/01/24 01:24:31"))) 70 | (is (= (date 2013 1 22 1 31 5) (parse "2013/01/22 01:31:05"))) 71 | (is (= (date 2013 1 23) (parse "2013-01-23T00:00:00+00:00"))) 72 | (is (= (date 2013 1 20 0 1) (parse "Sun Jan 20 2013 00:01 UTC+0000"))) 73 | (is (= (date 2013 1 20) (parse "Sunday 20 January 2013 00:00"))) 74 | (is (= (date 2013 1 16 8 11 37) (parse "2013-01-16 08:11:37 EST"))) 75 | (is (= (date 2013 1 17 17 17 23) (parse "2013-01-17T17:17:23Z"))) 76 | (is (= (date 2013 1 18 12 21 41) (parse "2013-01-18 12:21:41 UTC"))) 77 | (is (= (date 2013 1 18) (parse "Jan. 18, 2013"))) 78 | (is (= (date 2013 1 18 13 8 57) (parse "Fri Jan 18 13:08:57 UTC 2013"))) 79 | (is (= (date 2013 1 18) (parse "January 18, 2013"))) 80 | (is (= (date 2013 1 12) (parse "2013/01/12"))) 81 | (is (= (date 2015 6 1 23 53) (parse "June 1, 2015 11:53 pm"))) 82 | (is (= (date 2013 1 18 4 57 51) (parse "2013-01-18T04:57:51.0000000Z"))) 83 | ;(is (= (date 2015 11 16 20 13) (parse "Nov 16, 2015 3:13 p.m. ET"))) 84 | (is (= (date 2015 10 19 11 51) (parse "Oct. 19, 2015 11:51 AM ET"))) 85 | (is (= (date 2015 11 16 15 13) (parse "Nov 16, 2015 3:13 p.m."))) 86 | (is (= (date 2015 11 13) (parse "13 Nov 2015"))) 87 | (is (= (date 2015 11 16) (parse "Houston (Platts)--16 Nov 2015 517 pm EST/2217 GMT"))) 88 | (is (= (date 2015 11 14 0 0) (parse "2015-11-14T00:00:00-05:00"))) 89 | (is (= (date 2015 11 17 5 17) (parse "2015-11-17T05:17:00-05:00"))) 90 | (is (= (date 2015 11 18) (parse "Wednesday, November 18, 2015"))) 91 | (is (= (date 2015 11 18) (parse "11/18/2015"))) 92 | (is (= (date 2015 11 20 10 33 36) (parse "2015-11-20T10:33:36"))) 93 | (is (= (date 2015 11 12 13 35) (parse "November 12, 2015 1:35pm"))) 94 | (is (= (date 2015 11 19) (parse "19 November 2015"))) 95 | (is (= (date 2016 1 12 5 53) (parse "2016-01-12T05:53:00.000Z"))) 96 | (is (= (date 2016 1 12 11 20) (parse "2016-01-12T11:20Z"))) 97 | (is (= (date 2015 11 19 9 23) (parse "Nov 19, 2015 09:23 GMT"))) 98 | (is (= (date 2015 11 17 0 10) (parse "17 Nov 2015 00:10"))) 99 | (is (= (date 2015 11 17 16 1 37) (parse "2015-11-17T16:01:37+0000"))) 100 | (is (= (date 2016 2 24 0 1) (parse "Wed Feb 24 2016 00:01 UTC+1201"))) 101 | (is (= (date 2018 3 25 12) (parse "2018.03.25 12:00"))) 102 | (is (= (date 2017 2 14) (parse "02142017"))) 103 | (is (= (date 2017 1 16 1 45 5) (parse "2017-01-16 01:45:05 -0500"))) 104 | (is (= (date 2018 4 24 2 6 14) (parse "20180424T02:06:14Z"))) 105 | (let [document-time (.toDate (apply date-time [2018 05 24]))] 106 | (is (= (date 2018 5 24 4 57) (parse "4:57AM EDT" document-time "en"))) 107 | (is (= (date 2018 5 24 15 4) (parse "15:04" document-time "en")))) 108 | 109 | 110 | ; nonsenses should return nil 111 | (is (= nil (parse "makes no sense"))) 112 | (is (= nil (parse "2013-40-12"))))) -------------------------------------------------------------------------------- /test/timewords/de_test.clj: -------------------------------------------------------------------------------- 1 | (ns timewords.de-test 2 | (:require [clojure.test :refer :all] 3 | [clj-time.core :as joda :refer [date-time]] 4 | [timewords.core :refer [parse]]) 5 | (:import (java.util Date) 6 | (org.joda.time DateTime))) 7 | 8 | (defn date [& xs] (.toDate (apply date-time xs))) 9 | 10 | (deftest de-dates-test 11 | (testing "German month names" 12 | (is (= (date 2018 1 28 16 1) (parse "28. Januar 2018, 16:01 Uhr" nil "de"))) 13 | (is (= (date 2018 2 28 16 1) (parse "28. Februar 2018, 16:01 Uhr" nil "de"))) 14 | (is (= (date 2018 3 28 16 1) (parse "28. März 2018, 16:01 Uhr" nil "de"))) 15 | (is (= (date 2018 4 28 16 1) (parse "28. April 2018, 16:01 Uhr" nil "de"))) 16 | (is (= (date 2018 5 28 16 1) (parse "28. Mai 2018, 16:01 Uhr" nil "de"))) 17 | (is (= (date 2018 6 28 16 1) (parse "28. Juni 2018, 16:01 Uhr" nil "de"))) 18 | (is (= (date 2018 7 28 16 1) (parse "28. Juli 2018, 16:01 Uhr" nil "de"))) 19 | (is (= (date 2018 8 28 16 1) (parse "28. August 2018, 16:01 Uhr" nil "de"))) 20 | (is (= (date 2018 9 28 16 1) (parse "28. September 2018, 16:01 Uhr" nil "de"))) 21 | (is (= (date 2018 10 28 16 1) (parse "28. Oktober 2018, 16:01 Uhr" nil "de"))) 22 | (is (= (date 2018 11 28 16 1) (parse "28. November 2018, 16:01 Uhr" nil "de"))) 23 | (is (= (date 2018 12 28 16 1) (parse "28. Dezember 2018, 16:01 Uhr" nil "de"))) 24 | (is (= (date 2018 3 29 12 58) (parse "Donnerstag, 29.03.2018, 12:58" nil "de"))) 25 | (is (= (date 2011 10 5) (parse "5.10.2011" nil "de"))) 26 | (is (= (date 2011 10 5) (parse "5. Oktober 2011" nil "de"))) 27 | (is (= (date 2011 10 4) (parse "Dienstag, 4. Oktober 2011" nil "de"))))) 28 | -------------------------------------------------------------------------------- /test/timewords/en_relative_quantified_test.clj: -------------------------------------------------------------------------------- 1 | (ns timewords.en-relative-quantified-test 2 | (:require [clojure.test :refer :all] 3 | [clj-time.core :as joda] 4 | [clj-time.coerce :as tc] 5 | [timewords.core :refer :all])) 6 | 7 | (deftest en-relative-quantified 8 | (testing "some time ago" 9 | (let [parsed-datetime (tc/to-date-time (parse "now" (tc/to-date (joda/now))))] 10 | (is (joda/after? parsed-datetime (joda/minus (joda/now) (joda/millis 1000)))) 11 | (is (joda/before? parsed-datetime (joda/plus (joda/now) (joda/millis 1000))))) 12 | ; past 13 | (let [parsed-datetime (tc/to-date-time (parse "a sec ago"))] 14 | (is (joda/after? parsed-datetime (joda/minus (joda/now) (joda/millis 2000)))) 15 | (is (joda/before? parsed-datetime (joda/minus (joda/now) (joda/millis 0)))) 16 | (is (= 0 (joda/milli parsed-datetime)))) 17 | (let [parsed-datetime (tc/to-date-time (parse "1 sec ago"))] 18 | (is (joda/after? parsed-datetime (joda/minus (joda/now) (joda/millis 2000)))) 19 | (is (joda/before? parsed-datetime (joda/minus (joda/now) (joda/millis 0)))) 20 | (is (= 0 (joda/milli parsed-datetime)))) 21 | (let [parsed-datetime (tc/to-date-time (parse "a second ago"))] 22 | (is (joda/after? parsed-datetime (joda/minus (joda/now) (joda/millis 2000)))) 23 | (is (joda/before? parsed-datetime (joda/minus (joda/now) (joda/millis 0)))) 24 | (is (= 0 (joda/milli parsed-datetime)))) 25 | (let [parsed-datetime (tc/to-date-time (parse "1 second ago"))] 26 | (is (joda/after? parsed-datetime (joda/minus (joda/now) (joda/millis 2000)))) 27 | (is (joda/before? parsed-datetime (joda/minus (joda/now) (joda/millis 0)))) 28 | (is (= 0 (joda/milli parsed-datetime)))) 29 | (let [parsed-datetime (tc/to-date-time (parse "16 secs ago"))] 30 | (is (joda/after? parsed-datetime (joda/minus (joda/now) (joda/millis 17000)))) 31 | (is (joda/before? parsed-datetime (joda/minus (joda/now) (joda/millis 15000)))) 32 | (is (= 0 (joda/milli parsed-datetime)))) 33 | (let [parsed-datetime (tc/to-date-time (parse "16 seconds ago"))] 34 | (is (joda/after? parsed-datetime (joda/minus (joda/now) (joda/millis 17000)))) 35 | (is (joda/before? parsed-datetime (joda/minus (joda/now) (joda/millis 15000)))) 36 | (is (= 0 (joda/milli parsed-datetime)))) 37 | (let [parsed-datetime (tc/to-date-time (parse "16s ago"))] 38 | (is (joda/after? parsed-datetime (joda/minus (joda/now) (joda/millis 17000)))) 39 | (is (joda/before? parsed-datetime (joda/minus (joda/now) (joda/millis 15000)))) 40 | (is (= 0 (joda/milli parsed-datetime)))) 41 | (let [parsed-datetime (tc/to-date-time (parse "a min ago"))] 42 | (is (joda/after? parsed-datetime (joda/minus (joda/now) (joda/minutes 2)))) 43 | (is (joda/before? parsed-datetime (joda/minus (joda/now) (joda/minutes 0)))) 44 | (is (= 0 (joda/milli parsed-datetime)))) 45 | (let [parsed-datetime (tc/to-date-time (parse "1 min ago"))] 46 | (is (joda/after? parsed-datetime (joda/minus (joda/now) (joda/minutes 2)))) 47 | (is (joda/before? parsed-datetime (joda/minus (joda/now) (joda/minutes 0)))) 48 | (is (= 0 (joda/milli parsed-datetime)))) 49 | (let [parsed-datetime (tc/to-date-time (parse "a minute ago"))] 50 | (is (joda/after? parsed-datetime (joda/minus (joda/now) (joda/minutes 2)))) 51 | (is (joda/before? parsed-datetime (joda/minus (joda/now) (joda/minutes 0)))) 52 | (is (= 0 (joda/milli parsed-datetime)))) 53 | (let [parsed-datetime (tc/to-date-time (parse "1 minute ago"))] 54 | (is (joda/after? parsed-datetime (joda/minus (joda/now) (joda/minutes 2)))) 55 | (is (joda/before? parsed-datetime (joda/minus (joda/now) (joda/minutes 0)))) 56 | (is (= 0 (joda/milli parsed-datetime)))) 57 | (let [parsed-datetime (tc/to-date-time (parse "32 mins ago"))] 58 | (is (joda/after? parsed-datetime (joda/minus (joda/now) (joda/minutes 33)))) 59 | (is (joda/before? parsed-datetime (joda/minus (joda/now) (joda/minutes 31)))) 60 | (is (= 0 (joda/milli parsed-datetime)))) 61 | (let [parsed-datetime (tc/to-date-time (parse "32m ago"))] 62 | (is (joda/after? parsed-datetime (joda/minus (joda/now) (joda/minutes 33)))) 63 | (is (joda/before? parsed-datetime (joda/minus (joda/now) (joda/minutes 31)))) 64 | (is (= 0 (joda/milli parsed-datetime)))) 65 | (let [parsed-datetime (tc/to-date-time (parse "32 minutes ago"))] 66 | (is (joda/after? parsed-datetime (joda/minus (joda/now) (joda/minutes 33)))) 67 | (is (joda/before? parsed-datetime (joda/minus (joda/now) (joda/minutes 31)))) 68 | (is (= 0 (joda/milli parsed-datetime)))) 69 | (let [parsed-datetime (tc/to-date-time (parse "a hour ago"))] 70 | (is (joda/after? parsed-datetime (joda/minus (joda/now) (joda/hours 2)))) 71 | (is (joda/before? parsed-datetime (joda/minus (joda/now) (joda/hours 0)))) 72 | (is (= 0 (joda/minute parsed-datetime))) 73 | (is (= 0 (joda/milli parsed-datetime)))) 74 | (let [parsed-datetime (tc/to-date-time (parse "an hour ago"))] 75 | (is (joda/after? parsed-datetime (joda/minus (joda/now) (joda/hours 2)))) 76 | (is (joda/before? parsed-datetime (joda/minus (joda/now) (joda/hours 0)))) 77 | (is (= 0 (joda/minute parsed-datetime))) 78 | (is (= 0 (joda/milli parsed-datetime)))) 79 | (let [parsed-datetime (tc/to-date-time (parse "1h ago"))] 80 | (is (joda/after? parsed-datetime (joda/minus (joda/now) (joda/hours 2)))) 81 | (is (joda/before? parsed-datetime (joda/minus (joda/now) (joda/hours 0)))) 82 | (is (= 0 (joda/minute parsed-datetime))) 83 | (is (= 0 (joda/milli parsed-datetime)))) 84 | (let [parsed-datetime (tc/to-date-time (parse "1 hour ago"))] 85 | (is (joda/after? parsed-datetime (joda/minus (joda/now) (joda/hours 2)))) 86 | (is (joda/before? parsed-datetime (joda/minus (joda/now) (joda/hours 0)))) 87 | (is (= 0 (joda/minute parsed-datetime))) 88 | (is (= 0 (joda/milli parsed-datetime)))) 89 | (let [parsed-datetime (tc/to-date-time (parse "2 hours ago"))] 90 | (is (joda/after? parsed-datetime (joda/minus (joda/now) (joda/hours 3)))) 91 | (is (joda/before? parsed-datetime (joda/minus (joda/now) (joda/hours 1)))) 92 | (is (= 0 (joda/minute parsed-datetime))) 93 | (is (= 0 (joda/milli parsed-datetime)))) 94 | (let [parsed-datetime (tc/to-date-time (parse "3 hrs ago"))] 95 | (is (joda/after? parsed-datetime (joda/minus (joda/now) (joda/hours 4)))) 96 | (is (joda/before? parsed-datetime (joda/minus (joda/now) (joda/hours 1)))) 97 | (is (= 0 (joda/minute parsed-datetime))) 98 | (is (= 0 (joda/milli parsed-datetime)))) 99 | (let [parsed-datetime (tc/to-date-time (parse "yesterday"))] 100 | (is (joda/after? parsed-datetime (joda/minus (joda/now) (joda/days 2)))) 101 | (is (joda/before? parsed-datetime (joda/minus (joda/now) (joda/days 0)))) 102 | (is (= 0 (joda/hour parsed-datetime))) 103 | (is (= 0 (joda/minute parsed-datetime))) 104 | (is (= 0 (joda/milli parsed-datetime)))) 105 | (let [parsed-datetime (tc/to-date-time (parse "a day ago"))] 106 | (is (joda/after? parsed-datetime (joda/minus (joda/now) (joda/days 2)))) 107 | (is (joda/before? parsed-datetime (joda/minus (joda/now) (joda/days 0)))) 108 | (is (= 0 (joda/hour parsed-datetime))) 109 | (is (= 0 (joda/minute parsed-datetime))) 110 | (is (= 0 (joda/milli parsed-datetime)))) 111 | (let [parsed-datetime (tc/to-date-time (parse "1d ago"))] 112 | (is (joda/after? parsed-datetime (joda/minus (joda/now) (joda/days 2)))) 113 | (is (joda/before? parsed-datetime (joda/minus (joda/now) (joda/days 0)))) 114 | (is (= 0 (joda/hour parsed-datetime))) 115 | (is (= 0 (joda/minute parsed-datetime))) 116 | (is (= 0 (joda/milli parsed-datetime)))) 117 | (let [parsed-datetime (tc/to-date-time (parse "1 day ago"))] 118 | (is (joda/after? parsed-datetime (joda/minus (joda/now) (joda/days 2)))) 119 | (is (joda/before? parsed-datetime (joda/minus (joda/now) (joda/days 0)))) 120 | (is (= 0 (joda/hour parsed-datetime))) 121 | (is (= 0 (joda/minute parsed-datetime))) 122 | (is (= 0 (joda/milli parsed-datetime)))) 123 | (let [parsed-datetime (tc/to-date-time (parse "2 days ago"))] 124 | (is (joda/after? parsed-datetime (joda/minus (joda/now) (joda/days 3)))) 125 | (is (joda/before? parsed-datetime (joda/minus (joda/now) (joda/days 1)))) 126 | (is (= 0 (joda/hour parsed-datetime))) 127 | (is (= 0 (joda/minute parsed-datetime))) 128 | (is (= 0 (joda/milli parsed-datetime)))) 129 | (let [parsed-datetime (tc/to-date-time (parse "a week ago"))] 130 | (is (joda/after? parsed-datetime (joda/minus (joda/now) (joda/days 8)))) 131 | (is (joda/before? parsed-datetime (joda/minus (joda/now) (joda/days 6)))) 132 | (is (= 0 (joda/hour parsed-datetime))) 133 | (is (= 0 (joda/minute parsed-datetime))) 134 | (is (= 0 (joda/milli parsed-datetime)))) 135 | (let [parsed-datetime (tc/to-date-time (parse "1 week ago"))] 136 | (is (joda/after? parsed-datetime (joda/minus (joda/now) (joda/days 8)))) 137 | (is (joda/before? parsed-datetime (joda/minus (joda/now) (joda/days 6)))) 138 | (is (= 0 (joda/hour parsed-datetime))) 139 | (is (= 0 (joda/minute parsed-datetime))) 140 | (is (= 0 (joda/milli parsed-datetime)))) 141 | (let [parsed-datetime (tc/to-date-time (parse "3 weeks ago"))] 142 | (is (joda/after? parsed-datetime (joda/minus (joda/now) (joda/days 22)))) 143 | (is (joda/before? parsed-datetime (joda/minus (joda/now) (joda/days 20)))) 144 | (is (= 0 (joda/hour parsed-datetime))) 145 | (is (= 0 (joda/minute parsed-datetime))) 146 | (is (= 0 (joda/milli parsed-datetime)))) 147 | (let [parsed-datetime (tc/to-date-time (parse "last month"))] 148 | (is (joda/after? parsed-datetime (joda/minus (joda/now) (joda/months 3)))) 149 | (is (joda/before? parsed-datetime (joda/minus (joda/now) (joda/months 1)))) 150 | (is (= 1 (joda/day parsed-datetime))) 151 | (is (= 0 (joda/hour parsed-datetime))) 152 | (is (= 0 (joda/minute parsed-datetime))) 153 | (is (= 0 (joda/milli parsed-datetime)))) 154 | (let [parsed-datetime (tc/to-date-time (parse "previous month"))] 155 | (is (joda/after? parsed-datetime (joda/minus (joda/now) (joda/months 3)))) 156 | (is (joda/before? parsed-datetime (joda/minus (joda/now) (joda/months 1)))) 157 | (is (= 1 (joda/day parsed-datetime))) 158 | (is (= 0 (joda/hour parsed-datetime))) 159 | (is (= 0 (joda/minute parsed-datetime))) 160 | (is (= 0 (joda/milli parsed-datetime)))) 161 | (let [parsed-datetime (tc/to-date-time (parse "a month ago"))] 162 | (is (joda/after? parsed-datetime (joda/minus (joda/now) (joda/months 3)))) 163 | (is (joda/before? parsed-datetime (joda/minus (joda/now) (joda/months 1)))) 164 | (is (= 1 (joda/day parsed-datetime))) 165 | (is (= 0 (joda/hour parsed-datetime))) 166 | (is (= 0 (joda/minute parsed-datetime))) 167 | (is (= 0 (joda/milli parsed-datetime)))) 168 | (let [parsed-datetime (tc/to-date-time (parse "1 month ago"))] 169 | (is (joda/after? parsed-datetime (joda/minus (joda/now) (joda/months 3)))) 170 | (is (joda/before? parsed-datetime (joda/minus (joda/now) (joda/months 1)))) 171 | (is (= 1 (joda/day parsed-datetime))) 172 | (is (= 0 (joda/hour parsed-datetime))) 173 | (is (= 0 (joda/minute parsed-datetime))) 174 | (is (= 0 (joda/milli parsed-datetime)))) 175 | (let [parsed-datetime (tc/to-date-time (parse "2 months ago"))] 176 | (is (joda/after? parsed-datetime (joda/minus (joda/now) (joda/months 3)))) 177 | (is (joda/before? parsed-datetime (joda/minus (joda/now) (joda/months 1)))) 178 | (is (= 1 (joda/day parsed-datetime))) 179 | (is (= 0 (joda/hour parsed-datetime))) 180 | (is (= 0 (joda/minute parsed-datetime))) 181 | (is (= 0 (joda/milli parsed-datetime)))) 182 | (let [parsed-datetime (tc/to-date-time (parse "last year"))] 183 | (is (joda/after? parsed-datetime (joda/minus (joda/now) (joda/years 2)))) 184 | (is (joda/before? parsed-datetime (joda/minus (joda/now) (joda/years 0)))) 185 | (is (= 1 (joda/month parsed-datetime))) 186 | (is (= 1 (joda/day parsed-datetime))) 187 | (is (= 0 (joda/hour parsed-datetime))) 188 | (is (= 0 (joda/minute parsed-datetime))) 189 | (is (= 0 (joda/milli parsed-datetime)))) 190 | (let [parsed-datetime (tc/to-date-time (parse "previous year"))] 191 | (is (joda/after? parsed-datetime (joda/minus (joda/now) (joda/years 2)))) 192 | (is (joda/before? parsed-datetime (joda/minus (joda/now) (joda/years 0)))) 193 | (is (= 1 (joda/month parsed-datetime))) 194 | (is (= 1 (joda/day parsed-datetime))) 195 | (is (= 0 (joda/hour parsed-datetime))) 196 | (is (= 0 (joda/minute parsed-datetime))) 197 | (is (= 0 (joda/milli parsed-datetime)))) 198 | (let [parsed-datetime (tc/to-date-time (parse "a year ago"))] 199 | (is (joda/after? parsed-datetime (joda/minus (joda/now) (joda/years 2)))) 200 | (is (joda/before? parsed-datetime (joda/minus (joda/now) (joda/years 0)))) 201 | (is (= 1 (joda/month parsed-datetime))) 202 | (is (= 1 (joda/day parsed-datetime))) 203 | (is (= 0 (joda/hour parsed-datetime))) 204 | (is (= 0 (joda/minute parsed-datetime))) 205 | (is (= 0 (joda/milli parsed-datetime)))) 206 | (let [parsed-datetime (tc/to-date-time (parse "1 year ago"))] 207 | (is (joda/after? parsed-datetime (joda/minus (joda/now) (joda/years 2)))) 208 | (is (joda/before? parsed-datetime (joda/minus (joda/now) (joda/years 0)))) 209 | (is (= 1 (joda/month parsed-datetime))) 210 | (is (= 1 (joda/day parsed-datetime))) 211 | (is (= 0 (joda/hour parsed-datetime))) 212 | (is (= 0 (joda/minute parsed-datetime))) 213 | (is (= 0 (joda/milli parsed-datetime)))) 214 | (let [parsed-datetime (tc/to-date-time (parse "2 years ago"))] 215 | (is (joda/after? parsed-datetime (joda/minus (joda/now) (joda/years 3)))) 216 | (is (joda/before? parsed-datetime (joda/minus (joda/now) (joda/years 1)))) 217 | (is (= 1 (joda/month parsed-datetime))) 218 | (is (= 1 (joda/day parsed-datetime))) 219 | (is (= 0 (joda/hour parsed-datetime))) 220 | (is (= 0 (joda/minute parsed-datetime))) 221 | (is (= 0 (joda/milli parsed-datetime))))) 222 | 223 | (testing "some time from now" 224 | (let [parsed-datetime (tc/to-date-time (parse "a sec from now"))] 225 | (is (joda/after? parsed-datetime (joda/plus (joda/now) (joda/millis 0)))) 226 | (is (joda/before? parsed-datetime (joda/plus (joda/now) (joda/millis 2000)))) 227 | (is (= 0 (joda/milli parsed-datetime)))) 228 | (let [parsed-datetime (tc/to-date-time (parse "1 sec from now"))] 229 | (is (joda/after? parsed-datetime (joda/plus (joda/now) (joda/millis 0)))) 230 | (is (joda/before? parsed-datetime (joda/plus (joda/now) (joda/millis 2000)))) 231 | (is (= 0 (joda/milli parsed-datetime)))) 232 | (let [parsed-datetime (tc/to-date-time (parse "a second from now"))] 233 | (is (joda/after? parsed-datetime (joda/plus (joda/now) (joda/millis 0)))) 234 | (is (joda/before? parsed-datetime (joda/plus (joda/now) (joda/millis 2000)))) 235 | (is (= 0 (joda/milli parsed-datetime)))) 236 | (let [parsed-datetime (tc/to-date-time (parse "1 second from now"))] 237 | (is (joda/after? parsed-datetime (joda/plus (joda/now) (joda/millis 0)))) 238 | (is (joda/before? parsed-datetime (joda/plus (joda/now) (joda/millis 2000)))) 239 | (is (= 0 (joda/milli parsed-datetime)))) 240 | (let [parsed-datetime (tc/to-date-time (parse "16 secs from now"))] 241 | (is (joda/after? parsed-datetime (joda/plus (joda/now) (joda/millis 15000)))) 242 | (is (joda/before? parsed-datetime (joda/plus (joda/now) (joda/millis 17000)))) 243 | (is (= 0 (joda/milli parsed-datetime)))) 244 | (let [parsed-datetime (tc/to-date-time (parse "16 seconds from now"))] 245 | (is (joda/after? parsed-datetime (joda/plus (joda/now) (joda/millis 15000)))) 246 | (is (joda/before? parsed-datetime (joda/plus (joda/now) (joda/millis 17000)))) 247 | (is (= 0 (joda/milli parsed-datetime)))) 248 | (let [parsed-datetime (tc/to-date-time (parse "16s from now"))] 249 | (is (joda/after? parsed-datetime (joda/plus (joda/now) (joda/millis 15000)))) 250 | (is (joda/before? parsed-datetime (joda/plus (joda/now) (joda/millis 17000)))) 251 | (is (= 0 (joda/milli parsed-datetime)))) 252 | (let [parsed-datetime (tc/to-date-time (parse "a min from now"))] 253 | (is (joda/after? parsed-datetime (joda/now))) 254 | (is (joda/before? parsed-datetime (joda/plus (joda/now) (joda/minutes 2)))) 255 | (is (= 0 (joda/milli parsed-datetime)))) 256 | (let [parsed-datetime (tc/to-date-time (parse "1 min from now"))] 257 | (is (joda/after? parsed-datetime (joda/now))) 258 | (is (joda/before? parsed-datetime (joda/plus (joda/now) (joda/minutes 2)))) 259 | (is (= 0 (joda/milli parsed-datetime)))) 260 | (let [parsed-datetime (tc/to-date-time (parse "a minute from now"))] 261 | (is (joda/after? parsed-datetime (joda/plus (joda/now) (joda/minutes 0)))) 262 | (is (joda/before? parsed-datetime (joda/plus (joda/now) (joda/minutes 2)))) 263 | (is (= 0 (joda/milli parsed-datetime)))) 264 | (let [parsed-datetime (tc/to-date-time (parse "1 minute from now"))] 265 | (is (joda/after? parsed-datetime (joda/plus (joda/now) (joda/minutes 0)))) 266 | (is (joda/before? parsed-datetime (joda/plus (joda/now) (joda/minutes 2)))) 267 | (is (= 0 (joda/milli parsed-datetime)))) 268 | (let [parsed-datetime (tc/to-date-time (parse "32 mins from now"))] 269 | (is (joda/after? parsed-datetime (joda/plus (joda/now) (joda/minutes 31)))) 270 | (is (joda/before? parsed-datetime (joda/plus (joda/now) (joda/minutes 33)))) 271 | (is (= 0 (joda/milli parsed-datetime)))) 272 | (let [parsed-datetime (tc/to-date-time (parse "32 minutes from now"))] 273 | (is (joda/after? parsed-datetime (joda/plus (joda/now) (joda/minutes 31)))) 274 | (is (joda/before? parsed-datetime (joda/plus (joda/now) (joda/minutes 33)))) 275 | (is (= 0 (joda/milli parsed-datetime)))) 276 | (let [parsed-datetime (tc/to-date-time (parse "32m from now"))] 277 | (is (joda/after? parsed-datetime (joda/plus (joda/now) (joda/minutes 31)))) 278 | (is (joda/before? parsed-datetime (joda/plus (joda/now) (joda/minutes 33)))) 279 | (is (= 0 (joda/milli parsed-datetime)))) 280 | (let [parsed-datetime (tc/to-date-time (parse "32 minutes from now"))] 281 | (is (joda/after? parsed-datetime (joda/plus (joda/now) (joda/minutes 31)))) 282 | (is (joda/before? parsed-datetime (joda/plus (joda/now) (joda/minutes 33)))) 283 | (is (= 0 (joda/milli parsed-datetime)))) 284 | (let [parsed-datetime (tc/to-date-time (parse "a hour from now"))] 285 | (is (joda/after? parsed-datetime (joda/plus (joda/now) (joda/hours 0)))) 286 | (is (joda/before? parsed-datetime (joda/plus (joda/now) (joda/hours 2)))) 287 | (is (= 0 (joda/minute parsed-datetime))) 288 | (is (= 0 (joda/milli parsed-datetime)))) 289 | (let [parsed-datetime (tc/to-date-time (parse "an hour from now"))] 290 | (is (joda/after? parsed-datetime (joda/plus (joda/now) (joda/hours 0)))) 291 | (is (joda/before? parsed-datetime (joda/plus (joda/now) (joda/hours 2)))) 292 | (is (= 0 (joda/minute parsed-datetime))) 293 | (is (= 0 (joda/milli parsed-datetime)))) 294 | (let [parsed-datetime (tc/to-date-time (parse "1h from now"))] 295 | (is (joda/after? parsed-datetime (joda/plus (joda/now) (joda/hours 0)))) 296 | (is (joda/before? parsed-datetime (joda/plus (joda/now) (joda/hours 2)))) 297 | (is (= 0 (joda/minute parsed-datetime))) 298 | (is (= 0 (joda/milli parsed-datetime)))) 299 | (let [parsed-datetime (tc/to-date-time (parse "1 hour from now"))] 300 | (is (joda/after? parsed-datetime (joda/plus (joda/now) (joda/hours 0)))) 301 | (is (joda/before? parsed-datetime (joda/plus (joda/now) (joda/hours 2)))) 302 | (is (= 0 (joda/minute parsed-datetime))) 303 | (is (= 0 (joda/milli parsed-datetime)))) 304 | (let [parsed-datetime (tc/to-date-time (parse "2 hours from now"))] 305 | (is (joda/after? parsed-datetime (joda/plus (joda/now) (joda/hours 1)))) 306 | (is (joda/before? parsed-datetime (joda/plus (joda/now) (joda/hours 3)))) 307 | (is (= 0 (joda/minute parsed-datetime))) 308 | (is (= 0 (joda/milli parsed-datetime)))) 309 | (let [parsed-datetime (tc/to-date-time (parse "tomorrow"))] 310 | (is (joda/after? parsed-datetime (joda/plus (joda/now) (joda/days 0)))) 311 | (is (joda/before? parsed-datetime (joda/plus (joda/now) (joda/days 2)))) 312 | (is (= 0 (joda/hour parsed-datetime))) 313 | (is (= 0 (joda/minute parsed-datetime))) 314 | (is (= 0 (joda/milli parsed-datetime)))) 315 | (let [parsed-datetime (tc/to-date-time (parse "a day from now"))] 316 | (is (joda/after? parsed-datetime (joda/plus (joda/now) (joda/days 0)))) 317 | (is (joda/before? parsed-datetime (joda/plus (joda/now) (joda/days 2)))) 318 | (is (= 0 (joda/hour parsed-datetime))) 319 | (is (= 0 (joda/minute parsed-datetime))) 320 | (is (= 0 (joda/milli parsed-datetime)))) 321 | (let [parsed-datetime (tc/to-date-time (parse "1d from now"))] 322 | (is (joda/after? parsed-datetime (joda/plus (joda/now) (joda/days 0)))) 323 | (is (joda/before? parsed-datetime (joda/plus (joda/now) (joda/days 2)))) 324 | (is (= 0 (joda/hour parsed-datetime))) 325 | (is (= 0 (joda/minute parsed-datetime))) 326 | (is (= 0 (joda/milli parsed-datetime)))) 327 | (let [parsed-datetime (tc/to-date-time (parse "1 day from now"))] 328 | (is (joda/after? parsed-datetime (joda/plus (joda/now) (joda/days 0)))) 329 | (is (joda/before? parsed-datetime (joda/plus (joda/now) (joda/days 2)))) 330 | (is (= 0 (joda/hour parsed-datetime))) 331 | (is (= 0 (joda/minute parsed-datetime))) 332 | (is (= 0 (joda/milli parsed-datetime)))) 333 | (let [parsed-datetime (tc/to-date-time (parse "2 days from now"))] 334 | (is (joda/after? parsed-datetime (joda/plus (joda/now) (joda/days 1)))) 335 | (is (joda/before? parsed-datetime (joda/plus (joda/now) (joda/days 3)))) 336 | (is (= 0 (joda/hour parsed-datetime))) 337 | (is (= 0 (joda/minute parsed-datetime))) 338 | (is (= 0 (joda/milli parsed-datetime)))) 339 | (let [parsed-datetime (tc/to-date-time (parse "a week from now"))] 340 | (is (joda/after? parsed-datetime (joda/plus (joda/now) (joda/days 6)))) 341 | (is (joda/before? parsed-datetime (joda/plus (joda/now) (joda/days 8)))) 342 | (is (= 0 (joda/hour parsed-datetime))) 343 | (is (= 0 (joda/minute parsed-datetime))) 344 | (is (= 0 (joda/milli parsed-datetime)))) 345 | (let [parsed-datetime (tc/to-date-time (parse "1 week from now"))] 346 | (is (joda/after? parsed-datetime (joda/plus (joda/now) (joda/days 6)))) 347 | (is (joda/before? parsed-datetime (joda/plus (joda/now) (joda/days 8)))) 348 | (is (= 0 (joda/hour parsed-datetime))) 349 | (is (= 0 (joda/minute parsed-datetime))) 350 | (is (= 0 (joda/milli parsed-datetime)))) 351 | (let [parsed-datetime (tc/to-date-time (parse "3 weeks from now"))] 352 | (is (joda/after? parsed-datetime (joda/plus (joda/now) (joda/days 20)))) 353 | (is (joda/before? parsed-datetime (joda/plus (joda/now) (joda/days 22)))) 354 | (is (= 0 (joda/hour parsed-datetime))) 355 | (is (= 0 (joda/minute parsed-datetime))) 356 | (is (= 0 (joda/milli parsed-datetime)))) 357 | (let [parsed-datetime (tc/to-date-time (parse "next month"))] 358 | (is (joda/after? parsed-datetime (joda/plus (joda/now) (joda/months 0)))) 359 | (is (joda/before? parsed-datetime (joda/plus (joda/now) (joda/months 2)))) 360 | (is (= 1 (joda/day parsed-datetime))) 361 | (is (= 0 (joda/hour parsed-datetime))) 362 | (is (= 0 (joda/minute parsed-datetime))) 363 | (is (= 0 (joda/milli parsed-datetime)))) 364 | (let [parsed-datetime (tc/to-date-time (parse "a month from now"))] 365 | (is (joda/after? parsed-datetime (joda/plus (joda/now) (joda/months 0)))) 366 | (is (joda/before? parsed-datetime (joda/plus (joda/now) (joda/months 2)))) 367 | (is (= 1 (joda/day parsed-datetime))) 368 | (is (= 0 (joda/hour parsed-datetime))) 369 | (is (= 0 (joda/minute parsed-datetime))) 370 | (is (= 0 (joda/milli parsed-datetime)))) 371 | (let [parsed-datetime (tc/to-date-time (parse "1 month from now"))] 372 | (is (joda/after? parsed-datetime (joda/plus (joda/now) (joda/months 0)))) 373 | (is (joda/before? parsed-datetime (joda/plus (joda/now) (joda/months 2)))) 374 | (is (= 1 (joda/day parsed-datetime))) 375 | (is (= 0 (joda/hour parsed-datetime))) 376 | (is (= 0 (joda/minute parsed-datetime))) 377 | (is (= 0 (joda/milli parsed-datetime)))) 378 | (let [parsed-datetime (tc/to-date-time (parse "2 months from now"))] 379 | (is (joda/after? parsed-datetime (joda/plus (joda/now) (joda/months 1)))) 380 | (is (joda/before? parsed-datetime (joda/plus (joda/now) (joda/months 3)))) 381 | (is (= 1 (joda/day parsed-datetime))) 382 | (is (= 0 (joda/hour parsed-datetime))) 383 | (is (= 0 (joda/minute parsed-datetime))) 384 | (is (= 0 (joda/milli parsed-datetime)))) 385 | (let [parsed-datetime (tc/to-date-time (parse "a year from now"))] 386 | (is (joda/after? parsed-datetime (joda/plus (joda/now) (joda/years 0)))) 387 | (is (joda/before? parsed-datetime (joda/plus (joda/now) (joda/years 2)))) 388 | (is (= 1 (joda/month parsed-datetime))) 389 | (is (= 1 (joda/day parsed-datetime))) 390 | (is (= 0 (joda/hour parsed-datetime))) 391 | (is (= 0 (joda/minute parsed-datetime))) 392 | (is (= 0 (joda/milli parsed-datetime)))) 393 | (let [parsed-datetime (tc/to-date-time (parse "1 year from now"))] 394 | (is (joda/after? parsed-datetime (joda/plus (joda/now) (joda/years 0)))) 395 | (is (joda/before? parsed-datetime (joda/plus (joda/now) (joda/years 2)))) 396 | (is (= 1 (joda/month parsed-datetime))) 397 | (is (= 1 (joda/day parsed-datetime))) 398 | (is (= 0 (joda/hour parsed-datetime))) 399 | (is (= 0 (joda/minute parsed-datetime))) 400 | (is (= 0 (joda/milli parsed-datetime)))) 401 | (let [parsed-datetime (tc/to-date-time (parse "next year"))] 402 | (is (joda/after? parsed-datetime (joda/plus (joda/now) (joda/years 0)))) 403 | (is (joda/before? parsed-datetime (joda/plus (joda/now) (joda/years 2)))) 404 | (is (= 1 (joda/month parsed-datetime))) 405 | (is (= 1 (joda/day parsed-datetime))) 406 | (is (= 0 (joda/hour parsed-datetime))) 407 | (is (= 0 (joda/minute parsed-datetime))) 408 | (is (= 0 (joda/milli parsed-datetime)))) 409 | (let [parsed-datetime (tc/to-date-time (parse "2 years from now"))] 410 | (is (joda/after? parsed-datetime (joda/plus (joda/now) (joda/years 1)))) 411 | (is (joda/before? parsed-datetime (joda/plus (joda/now) (joda/years 3)))) 412 | (is (= 1 (joda/month parsed-datetime))) 413 | (is (= 1 (joda/day parsed-datetime))) 414 | (is (= 0 (joda/hour parsed-datetime))) 415 | (is (= 0 (joda/minute parsed-datetime))) 416 | (is (= 0 (joda/milli parsed-datetime)))))) 417 | -------------------------------------------------------------------------------- /test/timewords/en_relative_test.clj: -------------------------------------------------------------------------------- 1 | (ns timewords.en-relative-test 2 | (:require [clojure.test :refer :all] 3 | [clj-time.core :as joda] 4 | [clj-time.coerce :as tc] 5 | [timewords.core :refer :all])) 6 | 7 | (deftest en-relative 8 | 9 | (testing "special cases" 10 | (let [now-datetime (joda/now) 11 | parsed-datetime (tc/to-date-time (parse "today"))] 12 | (is (= (joda/year now-datetime) (joda/year parsed-datetime))) 13 | (is (= (joda/month now-datetime) (joda/month parsed-datetime))) 14 | (is (= (joda/day now-datetime) (joda/day parsed-datetime))) 15 | (is (= 0 (joda/hour parsed-datetime))) 16 | (is (= 0 (joda/minute parsed-datetime))) 17 | (is (= 0 (joda/milli parsed-datetime))))) 18 | 19 | (testing "relative weekdays" 20 | (let [parsed-datetime (tc/to-date-time (parse "monday"))] 21 | (is (joda/before? parsed-datetime (joda/now))) 22 | (is (= 1 (joda/day-of-week parsed-datetime))) 23 | (is (= 0 (joda/hour parsed-datetime))) 24 | (is (= 0 (joda/minute parsed-datetime))) 25 | (is (= 0 (joda/milli parsed-datetime)))) 26 | (is (= 2 (joda/day-of-week (tc/to-date-time (parse "tuesday"))))) 27 | (is (= 3 (joda/day-of-week (tc/to-date-time (parse "wednesday"))))) 28 | (is (= 4 (joda/day-of-week (tc/to-date-time (parse "thursday"))))) 29 | (is (= 5 (joda/day-of-week (tc/to-date-time (parse "friday"))))) 30 | (is (= 6 (joda/day-of-week (tc/to-date-time (parse "saturday"))))) 31 | (is (= 7 (joda/day-of-week (tc/to-date-time (parse "sunday"))))) 32 | 33 | (let [parsed-datetime (tc/to-date-time (parse "last monday"))] 34 | (is (joda/before? parsed-datetime (joda/now))) 35 | (is (= 1 (joda/day-of-week parsed-datetime))) 36 | (is (= 0 (joda/hour parsed-datetime))) 37 | (is (= 0 (joda/minute parsed-datetime))) 38 | (is (= 0 (joda/milli parsed-datetime)))) 39 | (is (= 2 (joda/day-of-week (tc/to-date-time (parse "last tuesday"))))) 40 | (is (= 3 (joda/day-of-week (tc/to-date-time (parse "last wednesday"))))) 41 | (is (= 4 (joda/day-of-week (tc/to-date-time (parse "last thursday"))))) 42 | (is (= 5 (joda/day-of-week (tc/to-date-time (parse "last friday"))))) 43 | (is (= 6 (joda/day-of-week (tc/to-date-time (parse "last saturday"))))) 44 | (is (= 7 (joda/day-of-week (tc/to-date-time (parse "last sunday"))))) 45 | (let [parsed-datetime (tc/to-date-time (parse "last Sunday"))] 46 | (is (= 7 (joda/day-of-week parsed-datetime))) 47 | (is (= 0 (joda/hour parsed-datetime))) 48 | (is (= 0 (joda/minute parsed-datetime))) 49 | (is (= 0 (joda/milli parsed-datetime)))) 50 | 51 | (let [parsed-datetime (tc/to-date-time (parse "next monday"))] 52 | (is (joda/after? parsed-datetime (joda/now))) 53 | (is (= 1 (joda/day-of-week parsed-datetime))) 54 | (is (= 0 (joda/hour parsed-datetime))) 55 | (is (= 0 (joda/minute parsed-datetime))) 56 | (is (= 0 (joda/milli parsed-datetime)))) 57 | (is (= 2 (joda/day-of-week (tc/to-date-time (parse "next tuesday"))))) 58 | (is (= 3 (joda/day-of-week (tc/to-date-time (parse "next wednesday"))))) 59 | (is (= 4 (joda/day-of-week (tc/to-date-time (parse "next thursday"))))) 60 | (is (= 5 (joda/day-of-week (tc/to-date-time (parse "next friday"))))) 61 | (is (= 6 (joda/day-of-week (tc/to-date-time (parse "next saturday"))))) 62 | (is (= 7 (joda/day-of-week (tc/to-date-time (parse "next sunday"))))) 63 | 64 | (let [parsed-datetime (tc/to-date-time (parse "this monday"))] 65 | (is (joda/after? parsed-datetime (joda/now))) 66 | (is (= 1 (joda/day-of-week parsed-datetime))) 67 | (is (= 0 (joda/hour parsed-datetime))) 68 | (is (= 0 (joda/minute parsed-datetime))) 69 | (is (= 0 (joda/milli parsed-datetime)))) 70 | (is (= 2 (joda/day-of-week (tc/to-date-time (parse "this tuesday"))))) 71 | (is (= 3 (joda/day-of-week (tc/to-date-time (parse "this wednesday"))))) 72 | (is (= 4 (joda/day-of-week (tc/to-date-time (parse "this thursday"))))) 73 | (is (= 5 (joda/day-of-week (tc/to-date-time (parse "this friday"))))) 74 | (is (= 6 (joda/day-of-week (tc/to-date-time (parse "this saturday"))))) 75 | (is (= 7 (joda/day-of-week (tc/to-date-time (parse "this sunday")))))) 76 | 77 | (testing "relative months" 78 | (let [parsed-datetime (tc/to-date-time (parse "last january"))] 79 | (is (joda/before? parsed-datetime (joda/now))) 80 | (is (= 1 (-> parsed-datetime (joda/month)))) 81 | (is (= 1 (-> parsed-datetime (joda/day)))) 82 | (is (= 0 (-> parsed-datetime (joda/hour)))) 83 | (is (= 0 (-> parsed-datetime (joda/milli))))) 84 | (is (= 2 (-> (parse "last february") (tc/to-date-time) (joda/month)))) 85 | (is (= 3 (-> (parse "last march") (tc/to-date-time) (joda/month)))) 86 | (is (= 4 (-> (parse "last april") (tc/to-date-time) (joda/month)))) 87 | (is (= 5 (-> (parse "last may") (tc/to-date-time) (joda/month)))) 88 | (is (= 6 (-> (parse "last june") (tc/to-date-time) (joda/month)))) 89 | (is (= 7 (-> (parse "last july") (tc/to-date-time) (joda/month)))) 90 | (is (= 8 (-> (parse "last august") (tc/to-date-time) (joda/month)))) 91 | (is (= 9 (-> (parse "last september") (tc/to-date-time) (joda/month)))) 92 | (is (= 10 (-> (parse "last october") (tc/to-date-time) (joda/month)))) 93 | (is (= 11 (-> (parse "last november") (tc/to-date-time) (joda/month)))) 94 | (is (= 12 (-> (parse "last december") (tc/to-date-time) (joda/month)))) 95 | 96 | (let [parsed-datetime (tc/to-date-time (parse "in january"))] 97 | (is (joda/before? parsed-datetime (joda/now))) 98 | (is (= 1 (-> parsed-datetime (joda/month)))) 99 | (is (= 1 (-> parsed-datetime (joda/day)))) 100 | (is (= 0 (-> parsed-datetime (joda/hour)))) 101 | (is (= 0 (-> parsed-datetime (joda/milli))))) 102 | (is (= 2 (-> (parse "in february") (tc/to-date-time) (joda/month)))) 103 | (is (= 3 (-> (parse "in march") (tc/to-date-time) (joda/month)))) 104 | (is (= 4 (-> (parse "in april") (tc/to-date-time) (joda/month)))) 105 | (is (= 5 (-> (parse "in may") (tc/to-date-time) (joda/month)))) 106 | (is (= 6 (-> (parse "in june") (tc/to-date-time) (joda/month)))) 107 | (is (= 7 (-> (parse "in july") (tc/to-date-time) (joda/month)))) 108 | (is (= 8 (-> (parse "in august") (tc/to-date-time) (joda/month)))) 109 | (is (= 9 (-> (parse "in september") (tc/to-date-time) (joda/month)))) 110 | (is (= 10 (-> (parse "in october") (tc/to-date-time) (joda/month)))) 111 | (is (= 11 (-> (parse "in november") (tc/to-date-time) (joda/month)))) 112 | (is (= 12 (-> (parse "in december") (tc/to-date-time) (joda/month)))) 113 | 114 | (let [parsed-datetime (-> (parse "next january") (tc/to-date-time))] 115 | (is (joda/after? parsed-datetime (joda/now))) 116 | (is (= 1 (joda/month parsed-datetime))) 117 | (is (= 1 (joda/day parsed-datetime))) 118 | (is (= 0 (joda/hour parsed-datetime))) 119 | (is (= 0 (joda/milli parsed-datetime)))) 120 | (is (= 2 (-> (parse "next february") (tc/to-date-time) (joda/month)))) 121 | (is (= 3 (-> (parse "next march") (tc/to-date-time) (joda/month)))) 122 | (is (= 4 (-> (parse "next april") (tc/to-date-time) (joda/month)))) 123 | (is (= 5 (-> (parse "next may") (tc/to-date-time) (joda/month)))) 124 | (is (= 6 (-> (parse "next june") (tc/to-date-time) (joda/month)))) 125 | (is (= 7 (-> (parse "next july") (tc/to-date-time) (joda/month)))) 126 | (is (= 8 (-> (parse "next august") (tc/to-date-time) (joda/month)))) 127 | (is (= 9 (-> (parse "next september") (tc/to-date-time) (joda/month)))) 128 | (is (= 10 (-> (parse "next october") (tc/to-date-time) (joda/month)))) 129 | (is (= 11 (-> (parse "next november") (tc/to-date-time) (joda/month)))) 130 | (is (= 12 (-> (parse "next december") (tc/to-date-time) (joda/month)))) 131 | 132 | (let [parsed-datetime (-> (parse "this january") (tc/to-date-time))] 133 | (is (joda/after? parsed-datetime (joda/now))) 134 | (is (= 1 (joda/month parsed-datetime))) 135 | (is (= 1 (joda/day parsed-datetime))) 136 | (is (= 0 (joda/hour parsed-datetime))) 137 | (is (= 0 (joda/milli parsed-datetime)))) 138 | (is (= 2 (-> (parse "this february") (tc/to-date-time) (joda/month)))) 139 | (is (= 3 (-> (parse "this march") (tc/to-date-time) (joda/month)))) 140 | (is (= 4 (-> (parse "this april") (tc/to-date-time) (joda/month)))) 141 | (is (= 5 (-> (parse "this may") (tc/to-date-time) (joda/month)))) 142 | (is (= 6 (-> (parse "this june") (tc/to-date-time) (joda/month)))) 143 | (is (= 7 (-> (parse "this july") (tc/to-date-time) (joda/month)))) 144 | (is (= 8 (-> (parse "this august") (tc/to-date-time) (joda/month)))) 145 | (is (= 9 (-> (parse "this september") (tc/to-date-time) (joda/month)))) 146 | (is (= 10 (-> (parse "this october") (tc/to-date-time) (joda/month)))) 147 | (is (= 11 (-> (parse "this november") (tc/to-date-time) (joda/month)))) 148 | (is (= 12 (-> (parse "this december") (tc/to-date-time) (joda/month))))) 149 | 150 | ; seasons 151 | (is (<= 3 (-> (parse "last spring") (tc/to-date-time) (joda/month)) 5)) 152 | (is (<= 6 (-> (parse "last summer") (tc/to-date-time) (joda/month)) 8)) 153 | (is (<= 9 (-> (parse "last autumn") (tc/to-date-time) (joda/month)) 11)) 154 | (is (<= 9 (-> (parse "last fall") (tc/to-date-time) (joda/month)) 11)) 155 | (is (or (<= 1 (-> (parse "last winter") (tc/to-date-time) (joda/month)) 2) 156 | (= 12 (-> (parse "last winter") (tc/to-date-time) (joda/month))))) 157 | (is (= nil (parse "next spring"))) 158 | (is (= nil (parse "next summer"))) 159 | (is (= nil (parse "next autumn"))) 160 | (is (= nil (parse "next fall"))) 161 | (is (= nil (parse "next winter")))) 162 | -------------------------------------------------------------------------------- /test/timewords/en_test.clj: -------------------------------------------------------------------------------- 1 | (ns timewords.en-test 2 | (:require [clojure.test :refer :all] 3 | [timewords.fuzzy.en.en :refer :all] 4 | [timewords.fuzzy.en.absolute :refer :all])) 5 | 6 | (deftest en-test 7 | 8 | (testing "We need to get if it is pm or am for these dates" 9 | (is (false? (is-pm? "October 16, 2015: 8:18 AM ET"))) 10 | (is (true? (is-pm? "October 16, 2015: 8:18 PM ET"))) 11 | (is (true? (is-pm? "Mon Oct 19, 2015 5:44pm EDT"))) 12 | (is (true? (is-pm? "Mon Oct 19, 2015 12:44pm EDT"))) 13 | (is (false? (is-pm? "Monday 19 October 2015 13.30 BST"))) 14 | (is (false? (is-pm? "Oct. 19, 2015 11:51 AM ET")))) 15 | 16 | 17 | (testing "October 16, 2015: 8:18 AM ET is 8 hours" 18 | (is (= "8" (hour "october 16, 2015: 8:18 am et")))) 19 | 20 | (testing "October 16, 2015: 8:18 PM ET is 20 hours" 21 | (is (= "20" (hour "october 16, 2015: 8:18 pm et")))) 22 | 23 | (testing "money.cnn.com" 24 | (is (= ["2015" "10" "16" "8" "18"] (parse-date "October 16, 2015: 8:18 AM ET")))) 25 | 26 | (testing "money.cnn.com" 27 | (is (= ["2015" "10" "16" "20" "18"] (parse-date "October 16, 2015: 8:18 PM ET")))) 28 | 29 | (testing "bloomberg.com" 30 | (is (= ["2015" "10" "19" "18" "22"] (parse-date "October 19, 2015 — 6:22 PM EEST")))) 31 | 32 | (testing "reuters.com" 33 | (is (= ["2015" "10" "19" "5" "44"] (parse-date "Mon Oct 19, 2015 5:44am EDT")))) 34 | 35 | (testing "reuters.com" 36 | (is (= ["2015" "10" "19" "17" "44"] (parse-date "Mon Oct 19, 2015 5:44pm EDT")))) 37 | 38 | (testing "theguardian.com" 39 | (is (= ["2015" "10" "19" "13" "30"] (parse-date "Monday 19 October 2015 13.30 BST")))) 40 | 41 | (testing "seekingalpha.com" 42 | (is (= ["2015" "10" "19" "11" "51"] (parse-date "Oct. 19, 2015 11:51 AM ET")))) 43 | 44 | (testing "bbc.com" 45 | (is (= ["2015" "10" "20"] (parse-date "20 October 2015"))))) 46 | -------------------------------------------------------------------------------- /test/timewords/es_test.clj: -------------------------------------------------------------------------------- 1 | (ns timewords.es-test 2 | (:require [clojure.test :refer :all] 3 | [clj-time.core :as joda :refer [date-time]] 4 | [timewords.core :refer [parse]]) 5 | (:import (java.util Date) 6 | (org.joda.time DateTime))) 7 | 8 | (defn date [& xs] (.toDate (apply date-time xs))) 9 | 10 | (deftest es-dates-test 11 | (testing "spanish month names" 12 | (is (= (date 2018 3 29) (parse "29/03/2018" nil "es"))) 13 | (is (= (date 2018 3 29 13 50) (parse "29 MAR 2018 - 13:50 CEST" nil "es"))) 14 | (is (= (date 2018 3 29 13) (parse "29 MAR. 2018 13:00" nil "es"))) 15 | (is (= (date 2018 3 29 13 21) (parse "29 marzo 2018 13:21h CEST" nil "es"))) 16 | (is (= (date 2018 3 29) (parse "29 marzo 2018" nil "es"))) 17 | (is (= (date 2018 3 29 13 51) (parse "29/03/2018 13:51h" nil "es"))) 18 | (is (= (date 2018 3 29 10 55) (parse "29.03.2018 - 10:55h" nil "es"))) 19 | (is (= (date 2018 3 29 11 55) (parse "29/03/2018 11:55" nil "es"))) 20 | (is (= (date 2018 3 29 11 52) (parse "29/03/2018 - 11:52" nil "es"))) 21 | (is (= (date 2018 3 29 12 41) (parse "12:41 - 29/03/18" nil "es"))) 22 | (is (= (date 2018 3 29 11 40) (parse "29.03.2018 – 11:40 H." nil "es"))) 23 | (is (= (date 2018 3 29 14 21 40) (parse "2018-03-29 14:21:40 H" nil "es"))) 24 | (is (= (date 2018 3 29 12 10 50) (parse "29/03/2018 12:10:50 CET" nil "es"))) 25 | (is (= (date 2018 3 29 12 10 50) (parse "29/03/2018 12:10:50" nil "es"))) 26 | (is (= (date 2018 3 29 13 39) (parse "29 mar. 2018 - 13:39" nil "es"))) 27 | (is (= (date 2018 3 3 4 30) (parse "sábado, 03 marzo 2018, 04:30" nil "es"))) 28 | (is (= (date 2018 3 29 14 36) (parse "29/03/2018 14:36 h" nil "es"))) 29 | (is (= (date 2018 3 29) (parse "Jueves, 29/03/2018" nil "es"))) 30 | (is (= (date 2018 3 29) (parse "jueves 29 marzo 2018" nil "es"))) 31 | (is (= (date 2018 3 28 15 42) (parse "28 de marzo de 2018. 15:42h" nil "es"))) 32 | (is (= (date 2018 3 28) (parse "28 de marzo de 2018" nil "es"))) 33 | )) 34 | -------------------------------------------------------------------------------- /test/timewords/fr_test.clj: -------------------------------------------------------------------------------- 1 | (ns timewords.fr-test 2 | (:require [clojure.test :refer :all] 3 | [clj-time.core :as joda :refer [date-time]] 4 | [timewords.core :refer [parse]]) 5 | (:import (java.util Date) 6 | (org.joda.time DateTime))) 7 | 8 | (defn date [& xs] (.toDate (apply date-time xs))) 9 | 10 | (deftest fr-dates-test 11 | (testing "French month names and common French formats" 12 | (is (= (date 2018 1 14 7 15) (parse "janvier 14, 2018 à 07:15" nil "fr"))) 13 | (is (= (date 2018 2 10 9 12) (parse "février 10, 2018 à 09:12" nil "fr"))) 14 | (is (= (date 2018 3 4 19 22) (parse "mars 4, 2018 à 19:22" nil "fr"))) 15 | (is (= (date 2018 4 18 7 15) (parse "avril 18, 2018 à 7:15" nil "fr"))) 16 | (is (= (date 2018 5 9 7 15) (parse "mai 09, 2018 à 07:15" nil "fr"))) 17 | (is (= (date 2018 6 7) (parse "juin 7, 2018" nil "fr"))) 18 | (is (= (date 2018 7 5 7 15) (parse "juillet 5, 2018 07:15" nil "fr"))) 19 | (is (= (date 2018 8 25 7 15) (parse "août 25, 2018 07:15" nil "fr"))) 20 | (is (= (date 2018 9 21 7 15) (parse "septembre 21, 2018 - 07:15" nil "fr"))) 21 | (is (= (date 2018 10 19 17 15) (parse "octobre 19, 2018 à 17:15" nil "fr"))) 22 | (is (= (date 2018 11 9) (parse "le 9 novembre, 2018" nil "fr"))) 23 | (is (= (date 2018 12 1) (parse "1 décembre, 2018" nil "fr"))) 24 | (is (= (date 2018 5 9 10 40) (parse "09.05.2018 à 10h40" nil "fr"))) 25 | (is (= (date 2018 5 8 23 01) (parse "le 8 mai à 23h01" (.toDate (joda/date-time 2018)) "fr"))) 26 | (is (= (date 2018 5 9 14 25) (parse "09/05/18 à 14h25" nil "fr"))) 27 | (is (= (date 2018 5 9 14 54) (parse "le 09 mai 2018 à 14h54" nil "fr"))) 28 | (is (= (date 2018 5 9 13 07) (parse "09 mai 2018, 13h07" nil "fr"))) 29 | (is (= (date 2018 5 8 22 29) (parse "Le 08/05 à 22:29" (.toDate (joda/date-time 2018)) "fr"))) 30 | (is (= (date 2018 5 9 15 33) (parse "9 mai 2018 à 15:33" nil "fr"))) 31 | (is (= (date 2018 5 9 12 43) (parse "09 Mai 2018 : 12h43" nil "fr"))))) 32 | -------------------------------------------------------------------------------- /test/timewords/java_test.clj: -------------------------------------------------------------------------------- 1 | (ns timewords.java-test 2 | (:require [clojure.test :refer :all] 3 | [clj-time.core :refer [date-time]] 4 | [timewords.core :as c]) 5 | (:import (org.joda.time DateTime) 6 | (lt.tokenmill.timewords Timewords) 7 | (java.util Date))) 8 | 9 | (defn date [& xs] (.toDate (apply date-time xs))) 10 | 11 | (deftest java-interface-test 12 | (testing "Satandard date string parse testing" 13 | (let [^Timewords timewords-parser (Timewords.)] 14 | (is (= nil (.parse timewords-parser nil))) 15 | (is (= (date 2010 7 8 0 0 0) (.parse timewords-parser "8th July 2010"))) 16 | (is (= (date 2013 1 24 8 46 54) (.parse timewords-parser "2013-01-24T08:46:54Z"))) 17 | (is (= (date 2013 1 24 8 46 54) (.parse timewords-parser "2013-01-24T08:46:54Z" nil "en"))) 18 | (is (= (date 2013 1 24 8 46 54) (.parse timewords-parser "2013-01-24T08:46:54Z" (Date.) "en")))))) 19 | -------------------------------------------------------------------------------- /test/timewords/lt_relative_test.clj: -------------------------------------------------------------------------------- 1 | (ns timewords.lt-relative-test 2 | (:require [clojure.test :refer :all] 3 | [clj-time.core :as joda :refer [date-time]] 4 | [timewords.core :refer [parse]]) 5 | (:import (java.util Date) 6 | (org.joda.time DateTime))) 7 | 8 | (defn date [& xs] (.toDate (apply date-time xs))) 9 | 10 | (deftest lt-relative-timewords 11 | (testing "today variations" 12 | (let [document-time (date 2018 4 7 12 3)] 13 | (is (= (date 2018 4 6) (parse "prieš 1 d." document-time "lt"))) 14 | (is (= (date 2018 4 6) (parse "prieš 1 d" document-time "lt"))) 15 | (is (= (date 2018 4 5) (parse "prieš 2 d. " document-time "lt"))) 16 | (is (= (date 2018 3 28) (parse "prieš 10 d." document-time "lt")))) 17 | (let [document-time (date 2018 4 7 12 3)] 18 | (is (= (date 2018 4 7 13 16) (parse "šiandien 13:16" document-time "lt"))) 19 | (is (= (date 2018 4 7 4 47) (parse "šiandien 04:47" document-time "lt"))) 20 | (is (= (date 2018 4 7 22 00) (parse "šiandien 22:00" document-time "lt"))) 21 | (is (= (date 2018 4 6 22 00) (parse "vakar 22:00" document-time "lt")))) 22 | (let [document-time (date 2018 4 7 12 3)] 23 | (is (= (date 2018 4 6) (parse "1 d. prieš" document-time "lt"))) 24 | (is (= (date 2018 4 6) (parse "1 d prieš" document-time "lt"))) 25 | (is (= (date 2018 3 31) (parse "1 sav. prieš" document-time "lt"))) 26 | (is (= (date 2018 3 31) (parse "1 sav prieš" document-time "lt"))) 27 | ; use a timezone for document time setup 28 | #_(is (= (date 2018 4 7 11) (parse "1 val prieš" document-time "lt"))) 29 | #_(is (= (date 2018 4 7 11) (parse "7 val prieš" document-time "lt")))) 30 | ;(is (= nil (parse "Publikuota: 21:05" (Date.) "lt"))) 31 | )) 32 | -------------------------------------------------------------------------------- /test/timewords/lt_test.clj: -------------------------------------------------------------------------------- 1 | (ns timewords.lt-test 2 | (:require [clojure.test :refer :all] 3 | [clj-time.core :as joda :refer [date-time]] 4 | [timewords.core :refer [parse]]) 5 | (:import (java.util Date) 6 | (org.joda.time DateTime))) 7 | 8 | (defn date [& xs] (.toDate (apply date-time xs))) 9 | 10 | (deftest lt-dates-test 11 | 12 | (testing "lithuanian month names" 13 | (is (= (date 2000 1 3) (parse "2000 sausio 3" nil "lt"))) 14 | (is (= (date 2000 1 3) (parse "2000 sausio 3 d." nil "lt"))) 15 | (is (= (date 2000 1 3) (parse "2000 sausio 3d." nil "lt"))) 16 | (is (= (date 2000 1 3 12 34) (parse "2000 sausio 3 d. 12:34" nil "lt"))) 17 | (is (= (date 2000 1 3 12 34) (parse "2000 sausio 3d. 12:34" nil "lt"))) 18 | (is (= (date 2000 1 30 12 34) (parse "2000 sausio 30d. 12:34" nil "lt"))) 19 | (is (= (date 2000 2 3) (parse "2000 vasario 3" nil "lt"))) 20 | (is (= (date 2000 3 3) (parse "2000 kovo 3" nil "lt"))) 21 | (is (= (date 2000 3 3) (parse "2000 kovas 3" nil "lt"))) 22 | (is (= (date 2000 4 3) (parse "2000 balandžio 3" nil "lt"))) 23 | (is (= (date 2000 5 3) (parse "2000 gegužės 3" nil "lt"))) 24 | (is (= (date 2000 6 3) (parse "2000 birželis 3" nil "lt"))) 25 | (is (= (date 2000 6 3) (parse "2000 birželio 3" nil "lt"))) 26 | (is (= (date 2000 7 3) (parse "2000 liepos 3" nil "lt"))) 27 | (is (= (date 2000 8 3) (parse "2000 rugpjūčio 3" nil "lt"))) 28 | (is (= (date 2000 9 3) (parse "2000 rugsėjo 3" nil "lt"))) 29 | (is (= (date 2000 10 3) (parse "2000 spalio 3" nil "lt"))) 30 | (is (= (date 2000 11 3) (parse "2000 lapkričio 3" nil "lt"))) 31 | (is (= (date 2000 12 3) (parse "2000 gruodžio 3" nil "lt"))) 32 | (is (= (date 2013 12 2 8 14) (parse " 2013/12/02 8:14" nil "lt"))) 33 | (is (= (date (.getYear (DateTime.)) 3 16 15 17) (parse "Kovo 16 d. 15:17" (Date.) "lt"))) 34 | (is (= (date 2016 12 22 11 10) (parse "2016 m. gruodžio 22 d. 11:10" nil "lt"))) 35 | (is (= (date 2000 1 3 12 13) (parse "2000 sausio 3 12:13" nil "lt"))) 36 | (is (= (date 2000 1 3 12 13 14) (parse "2000 sausio 3 12:13:14" nil "lt"))) 37 | (is (= (date 1999 3 13) (parse "1999 metų kovo 13" nil "lt"))) 38 | (is (= (date (-> (joda/now) (joda/year)) 3 13) (parse "kovo 13" nil "lt"))) 39 | (is (= (date 2005 3 13) (parse "kovo 13" (.toDate (joda/date-time 2005)) "lt"))) 40 | (is (= (date 2018 3 20 9 40) (parse "2018 03 20 9:40" nil "lt"))) 41 | (is (= (date 2018 03 22 18 30) (parse "2018-03-22 / 18:30" nil "lt"))) 42 | (is (= (date 2018 3 22 21 5 43) (parse "2018-03-22T21:05:43+02:00" nil "lt"))) 43 | (is (= (date 2018 3 22 7 30 44) (parse "2018 kovo mėn. 22 d. 07:30:44" nil "lt"))) 44 | (is (= (date 2018 3 22 16 1) (parse "16:01 2018.03.22" nil "lt"))) 45 | (is (= (date 2018 3 20 18 57) (parse "2018 kovo 20d. 18:57" nil "lt"))) 46 | (is (= (date 2018 3 12 14 4) (parse "Pirmadienis, 12 Kovas 2018 14:04" nil "lt"))) 47 | (is (= (date 2018 3 26) (parse "2018 kovo 26" nil "lt"))) 48 | (is (= (date 2018 3 24 10 9) (parse "2018 kovo 24 d. 10:09" nil "lt"))) 49 | (is (= (date 2018 3 25 12) (parse "2018.03.25 12:00" nil "lt"))) 50 | (is (= (date 2018 3 26) (parse "Kov 26, 2018" nil "lt"))) 51 | (is (= (date 2017 9 6) (parse "2017/09/06" nil "lt"))) 52 | (is (= (date 2018 4 3) (parse "2018.04.03" nil "lt"))) 53 | (is (= (date 2017 8 23) (parse "17-08-23" nil "lt"))) 54 | (is (= (date 2017 11 7) (parse "2017 lapkričio 7" nil "lt"))) 55 | (is (= (date 2018 1 3 10 8) (parse "SAU 03 10:08 2018" nil "lt"))) 56 | (is (= (date 2018 2 3 10 8) (parse "VAS 03 10:08 2018" nil "lt"))) 57 | (is (= (date 2018 3 3 10 8) (parse "KOV 03 10:08 2018" nil "lt"))) 58 | (is (= (date 2018 4 3 10 8) (parse "BAL 03 10:08 2018" nil "lt"))) 59 | (is (= (date 2018 5 3 10 8) (parse "GEG 03 10:08 2018" nil "lt"))) 60 | (is (= (date 2018 6 3 10 8) (parse "BIR 03 10:08 2018" nil "lt"))) 61 | (is (= (date 2018 7 3 10 8) (parse "LIE 03 10:08 2018" nil "lt"))) 62 | (is (= (date 2018 8 3 10 8) (parse "RGP 03 10:08 2018" nil "lt"))) 63 | (is (= (date 2018 9 3 10 8) (parse "RGS 03 10:08 2018" nil "lt"))) 64 | (is (= (date 2018 10 3 10 8) (parse "SPA 03 10:08 2018" nil "lt"))) 65 | (is (= (date 2018 11 3 10 8) (parse "LAP 03 10:08 2018" nil "lt"))) 66 | (is (= (date 2018 12 3 10 8) (parse "GRU 03 10:08 2018" nil "lt"))) 67 | (is (= (date 2018 4 3) (parse "balandžio 03, 2018" nil "lt"))) 68 | (is (= (date 2018 3 27) (parse "2018 kovo 27, antradienis" nil "lt"))))) -------------------------------------------------------------------------------- /test/timewords/ru_test.clj: -------------------------------------------------------------------------------- 1 | (ns timewords.ru-test 2 | (:require [clojure.test :refer :all] 3 | [clj-time.core :as joda :refer [date-time]] 4 | [timewords.core :refer [parse]]) 5 | (:import (java.util Date) 6 | (org.joda.time DateTime))) 7 | 8 | (defn date [& xs] (.toDate (apply date-time xs))) 9 | 10 | (deftest ru-dates-test 11 | (testing "russian month names" 12 | (is (= (date 2018 4 7 19 19) (parse "7 April 2018, 19:19" nil "en"))) 13 | (is (= (date 2018 4 7 19 19) (parse "7 апреля 2018, 19:19" nil "ru"))) 14 | (is (= (date 2018 4 7 22 46) (parse "22:46, 7 апреля 2018" nil "ru"))) 15 | (is (= (date 2018 4 6 16 20) (parse "6 апреля 16:20" (.toDate (joda/date-time 2018)) "ru"))) 16 | (is (= (date 2018 3 28 16 32) (parse "28 марта 16:32" (.toDate (joda/date-time 2018)) "ru"))) 17 | (is (= (date 2018 4 7 20 55) (parse "7 апреля 2018 20:55" nil "ru"))) 18 | (is (= (date 2018 4 7 22 22) (parse "7 апреля, 22:22" (.toDate (joda/date-time 2018)) "ru"))) 19 | (is (= (date 2018 4 7 21 55) (parse "07.04.2018, 21:55" nil "ru"))) 20 | (is (= (date 2018 3 1 22 06) (parse "01.03.2018 в 22:06" nil "ru"))) 21 | (is (= (date 2018 4 8 0 01) (parse "08.04.2018 00:01" nil "ru"))) 22 | (is (= (date 2018 4 6 20 10) (parse "06.04.2018 - 20:10" nil "ru"))) 23 | (is (= (date 2018 4 7 19 0) (parse "19:00 07/04/2018" nil "ru"))) 24 | (is (= (date 2018 4 7 17 31) (parse "7 апреля 2018 г., 17:31" nil "ru"))) 25 | (is (= (date 2018 4 8 0 38) (parse "00:38 08.04.2018" nil "ru"))) 26 | (is (= (date 2018 4 7 23 56) (parse "07 Апрель 2018, 23:56" nil "ru"))) 27 | (is (= (date 2018 3 31 17 56) (parse "31 Март 2018, 17:56" nil "ru"))) 28 | (is (= (date 2018 2 27 10 0) (parse "27 Февраль 2018, 10:00" nil "ru"))) 29 | (is (= (date 2017 11 22 1 42) (parse "22 Ноябрь 2017, 01:42" nil "ru"))) 30 | (is (= (date 2017 10 23 10 41) (parse "23 Октябрь 2017, 10:41" nil "ru"))) 31 | (is (= (date 2017 9 26 16 33) (parse "26 Сентябрь 2017, 16:33" nil "ru"))) 32 | (is (= (date 2017 8 30 19 5) (parse "30 Август 2017, 19:05" nil "ru"))) 33 | (is (= (date 2017 7 26 10 0) (parse "26 Июль 2017, 10:00" nil "ru"))) 34 | (is (= (date 2017 6 29 10 0) (parse "29 Июнь 2017, 10:00" nil "ru"))) 35 | (is (= (date 2017 5 30 10 16) (parse "30 Май 2017, 10:16" nil "ru"))) 36 | (is (= (date 2018 1 31 13 8) (parse "31 Январь 2018, 13:08" nil "ru"))) 37 | (is (= (date 2017 12 29 19 31) (parse "29 Декабрь 2017, 19:31" nil "ru"))) 38 | (is (= (date 2017 4 29 19 31) (parse "19:31 29 апреля 2017" nil "ru"))) 39 | ;сегодня15:23 40 | ; вчера в 22:06 41 | )) 42 | -------------------------------------------------------------------------------- /test/timewords/special_cases_test.clj: -------------------------------------------------------------------------------- 1 | (ns timewords.special-cases-test 2 | (:require [clojure.test :refer :all] 3 | [clj-time.core :as joda :refer [date-time]] 4 | [timewords.standard.formats :as fmts]) 5 | (:import (java.util Date Locale))) 6 | 7 | (defn date [& xs] (.toDate (apply date-time xs))) 8 | 9 | (deftest special-cases 10 | (let [document-time (apply date-time [2018 05 24])] 11 | (is (= (date 2018 5 24 4 57) (fmts/special-cases "4:57AM EDT" Locale/ENGLISH document-time))) 12 | (is (= (date 2018 5 24 15 4) (fmts/special-cases "15:04" Locale/ENGLISH document-time))))) 13 | --------------------------------------------------------------------------------