pax_global_header00006660000000000000000000000064147264015150014517gustar00rootroot0000000000000052 comment=4e2f4e90201787e3d65ece50d2a8a66c21a76646 golang-github-olebedev-when-1.1.0/000077500000000000000000000000001472640151500167675ustar00rootroot00000000000000golang-github-olebedev-when-1.1.0/.github/000077500000000000000000000000001472640151500203275ustar00rootroot00000000000000golang-github-olebedev-when-1.1.0/.github/workflows/000077500000000000000000000000001472640151500223645ustar00rootroot00000000000000golang-github-olebedev-when-1.1.0/.github/workflows/tests.yml000066400000000000000000000006771472640151500242630ustar00rootroot00000000000000name: Go on: [push, pull_request] jobs: build: runs-on: ubuntu-latest strategy: matrix: go-version: [ '1.19', '1.20', '1.21.x' ] steps: - uses: actions/checkout@v3 - name: Setup Go uses: actions/setup-go@v3 with: go-version: ${{ matrix.go-version }} cache: true - name: Install dependencies run: go get . - name: Run tests run: go test ./... golang-github-olebedev-when-1.1.0/.gitignore000066400000000000000000000000151472640151500207530ustar00rootroot00000000000000vendor .idea golang-github-olebedev-when-1.1.0/CODEOWNERS000066400000000000000000000000651472640151500203630ustar00rootroot00000000000000* @olebedev /rules/zh/ @RexSkz /rules/nl @iamreinder golang-github-olebedev-when-1.1.0/LICENSE000066400000000000000000000261411472640151500200000ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright 2016 Oleg Lebedev Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. golang-github-olebedev-when-1.1.0/README.md000066400000000000000000000077361472640151500202630ustar00rootroot00000000000000# when [![godoc](http://img.shields.io/badge/godoc-reference-blue.svg?style=flat)](https://godoc.org/github.com/olebedev/when) > `when` is a natural language date/time parser with pluggable rules and merge strategies ### Examples - **tonight at 11:10 pm** - at **Friday afternoon** - the deadline is **next tuesday 14:00** - drop me a line **next wednesday at 2:25 p.m** - it could be done at **11 am past tuesday** Check [EN](https://github.com/olebedev/when/blob/master/rules/en) rules and tests of them, for more examples. **Needed rule not found?** Open [an issue](https://github.com/olebedev/when/issues/new) with the case and it will be added asap. ### How it works Usually, there are several rules added to the parser's instance for checking. Each rule has its own borders - length and offset in provided string. Meanwhile, each rule yields only the first match over the string. So, the library checks all the rules and extracts a cluster of matched rules which have distance between each other less or equal to [`options.Distance`](https://github.com/olebedev/when/blob/master/when.go#L141-L144), which is 5 by default. For example: ``` on next wednesday at 2:25 p.m. └──────┬─────┘ └───┬───┘ weekday hour + minute ``` So, we have a cluster of matched rules - `"next wednesday at 2:25 p.m."` in the string representation. After that, each rule is applied to the context. In order of definition or in match order, if [`options.MatchByOrder`](https://github.com/olebedev/when/blob/master/when.go#L141-L144) is set to `true`(which it is by default). Each rule could be applied with given merge strategy. By default, it's an [Override](https://github.com/olebedev/when/blob/master/rules/rules.go#L13) strategy. The other strategies are not implemented yet in the rules. **Pull requests are welcome.** ### Supported Languages - [EN](https://github.com/olebedev/when/blob/master/rules/en) - English - [RU](https://github.com/olebedev/when/blob/master/rules/ru) - Russian - [BR](https://github.com/olebedev/when/blob/master/rules/br) - Brazilian Portuguese - [ZH](https://github.com/olebedev/when/blob/master/rules/zh) - Chinese - [NL](https://github.com/olebedev/when/blob/master/rules/nl) - Dutch ### Install The project follows the official [release workflow](https://go.dev/doc/modules/release-workflow). It is recommended to refer to this resource for detailed information on the process. To install the latest version: ``` $ go get github.com/olebedev/when@latest ``` ### Usage ```go w := when.New(nil) w.Add(en.All...) w.Add(common.All...) text := "drop me a line in next wednesday at 2:25 p.m" r, err := w.Parse(text, time.Now()) if err != nil { // an error has occurred } if r == nil { // no matches found } fmt.Println( "the time", r.Time.String(), "mentioned in", text[r.Index:r.Index+len(r.Text)], ) ``` #### Distance Option ```go w := when.New(nil) w.Add(en.All...) w.Add(common.All...) text := "February 23, 2019 | 1:46pm" // With default distance (5): // February 23, 2019 | 1:46pm // └───┬───┘ // distance: 9 (1:46pm will be ignored) r, _ := w.Parse(text, time.Now()) fmt.Printf(r.Time.String()) // "2019-02-23 09:21:21.835182427 -0300 -03" // 2019-02-23 (correct) // 09:21:21 ("wrong") // With custom distance (10): w.SetOptions(&rules.Options{ Distance: 10, MatchByOrder: true}) r, _ = w.Parse(text, time.Now()) fmt.Printf(r.Time.String()) // "2019-02-23 13:46:21.559521554 -0300 -03" // 2019-02-23 (correct) // 13:46:21 (correct) ``` ### State of the project The project is in a more-or-less complete state. It's used for one project already. Bugs will be fixed as soon as they will be found. ### TODO - [ ] readme: describe all the existing rules - [ ] implement missed rules for [these examples](https://github.com/mojombo/chronic#examples) - [ ] add cli and simple rest api server([#2](https://github.com/olebedev/when/issues/2)) ### LICENSE http://www.apache.org/licenses/LICENSE-2.0 golang-github-olebedev-when-1.1.0/go.mod000066400000000000000000000004651472640151500201020ustar00rootroot00000000000000module github.com/olebedev/when go 1.19 require ( github.com/AlekSi/pointer v1.0.0 github.com/pkg/errors v0.8.1 github.com/stretchr/testify v1.3.0 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/stretchr/objx v0.1.0 // indirect ) golang-github-olebedev-when-1.1.0/go.sum000066400000000000000000000020031472640151500201150ustar00rootroot00000000000000github.com/AlekSi/pointer v1.0.0 h1:KWCWzsvFxNLcmM5XmiqHsGTTsuwZMsLFwWF9Y+//bNE= github.com/AlekSi/pointer v1.0.0/go.mod h1:1kjywbfcPFCmncIxtk6fIEub6LKrfMz3gc5QKVOSOA8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= golang-github-olebedev-when-1.1.0/rules/000077500000000000000000000000001472640151500201215ustar00rootroot00000000000000golang-github-olebedev-when-1.1.0/rules/br/000077500000000000000000000000001472640151500205245ustar00rootroot00000000000000golang-github-olebedev-when-1.1.0/rules/br/br.go000066400000000000000000000164641472640151500214710ustar00rootroot00000000000000package br import "github.com/olebedev/when/rules" var All = []rules.Rule{ Weekday(rules.Override), CasualDate(rules.Override), CasualTime(rules.Override), Hour(rules.Override), HourMinute(rules.Override), Deadline(rules.Override), PastTime(rules.Override), ExactMonthDate(rules.Override), } var WEEKDAY_OFFSET = map[string]int{ "domingo": 0, "dom": 0, "segunda-feira": 1, "segunda": 1, "seg": 1, "terça-feira": 2, "terça": 2, "ter": 2, "quarta-feira": 3, "quarta": 3, "qua": 3, "quinta-feira": 4, "quinta": 4, "qui": 4, "sexta-feira": 5, "sexta": 5, "sex": 5, "sábado": 6, "sab": 6, } var WEEKDAY_OFFSET_PATTERN = "(?:domingo|dom|segunda-feira|segunda|seg|terça-feira|terça|ter|quarta-feira|quarta|qua|quinta-feira|quinta|qui|sexta-feira|sexta|sex|sábado|sab)" var MONTH_OFFSET = map[string]int{ "janeiro": 1, "jan.": 1, "jan": 1, "fevereiro": 2, "fev.": 2, "fev": 2, "março": 3, "mar.": 3, "mar": 3, "abril": 4, "abr.": 4, "abr": 4, "maio": 5, "mai.": 5, "mai": 5, "junho": 6, "jun.": 6, "jun": 6, "julho": 7, "jul.": 7, "jul": 7, "agosto": 8, "ago.": 8, "ago": 8, "setembro": 9, "set.": 9, "set": 9, "outubro": 10, "out.": 10, "out": 10, "novembro": 11, "nov.": 11, "nov": 11, "dezembro": 12, "dez.": 12, "dez": 12, } var MONTH_OFFSET_PATTERN = `(?:janeiro|jan\.?|jan|fevereiro|fev\.?|fev|março|mar\.?|mar|abril|abr\.?|abr|maio|mai\.?|mai|junho|jun\.?|jun|julho|jul\.?|jul|agosto|ago\.?|ago|setembro|set\.?|set|outubro|out\.?|out|novembro|nov\.?|nov|dezembro|dez\.?|dez)` var INTEGER_WORDS = map[string]int{ "uma": 1, "um": 1, "duas": 2, "dois": 2, "três": 3, "quatro": 4, "cinco": 5, "seis": 6, "sete": 7, "oito": 8, "nove": 9, "dez": 10, "onze": 11, "doze": 12, } var INTEGER_WORDS_PATTERN = `(?:uma|um|duas|dois|três|quatro|cinco|seis|sete|oito|nove|dez|onze|doze)` var ORDINAL_WORDS = map[string]int{ "primeiro": 1, "1º": 1, "segunda": 2, "segundo": 2, "2ª": 2, "2º": 2, "terceira": 3, "terceiro": 3, "3ª": 3, "3º": 3, "quarta": 4, "quarto": 4, "4ª": 4, "4º": 4, "quinta": 5, "quinto": 5, "5ª": 5, "5º": 5, "sexta": 6, "sexto": 6, "6ª": 6, "6º": 6, "sétima": 7, "sétimo": 7, "7ª": 7, "7º": 7, "oitava": 8, "oitavo": 8, "8ª": 8, "8º": 8, "nona": 9, "nono": 9, "9ª": 9, "9º": 9, "décima": 10, "décimo": 10, "10ª": 10, "10º": 10, "décima-primeira": 11, "décima primeira": 11, "décimo-primeiro": 11, "décimo primeiro": 11, "11ª": 11, "11º": 11, "décima-segunda": 12, "décima segunda": 12, "décimo-segundo": 12, "décimo segundo": 12, "12ª": 12, "12º": 12, "décima-terceira": 13, "décima terceira": 13, "décimo-terceiro": 13, "décimo terceiro": 13, "13ª": 13, "13º": 13, "décima-quarta": 14, "décima quarta": 14, "décimo-quarto": 14, "décimo quarto": 14, "14ª": 14, "14º": 14, "décima-quinta": 15, "décima quinta": 15, "décimo-quinto": 15, "décimo quinto": 15, "15ª": 15, "15º": 15, "décima-sexta": 16, "décima sexta": 16, "décimo-sexto": 16, "décimo sexto": 16, "16ª": 16, "16º": 16, "décima-sétima": 17, "décima sétima": 17, "décimo-sétimo": 17, "décimo sétimo": 17, "17ª": 17, "17º": 17, "décima-oitava": 18, "décima oitava": 18, "décimo-oitavo": 18, "décimo oitavo": 18, "18ª": 18, "18º": 18, "décima-nona": 19, "décima nona": 19, "décimo-nono": 19, "décimo nono": 19, "19ª": 19, "19º": 19, "vigésima": 20, "vigésimo": 20, "20ª": 20, "20º": 20, "vigésima-primeira": 21, "vigésima primeira": 21, "vigésimo-primeiro": 21, "vigésimo primeiro": 21, "21ª": 21, "21º": 21, "vigésima-segunda": 22, "vigésima segunda": 22, "vigésimo-segundo": 22, "vigésimo segundo": 22, "22ª": 22, "22º": 22, "vigésima-terceira": 23, "vigésima terceira": 23, "vigésimo-terceiro": 23, "vigésimo terceiro": 23, "23ª": 23, "23º": 23, "vigésima-quarta": 24, "vigésima quarta": 24, "vigésimo-quarto": 24, "vigésimo quarto": 24, "24ª": 24, "24º": 24, "vigésima-quinta": 25, "vigésima quinta": 25, "vigésimo-quinto": 25, "vigésimo quinto": 25, "25ª": 25, "25º": 25, "vigésima-sexta": 26, "vigésima sexta": 26, "vigésimo-sexto": 26, "vigésimo sexto": 26, "26ª": 26, "26º": 26, "vigésima-sétima": 27, "vigésima sétima": 27, "vigésimo-sétimo": 27, "vigésimo sétimo": 27, "27ª": 27, "27º": 27, "vigésima-oitava": 28, "vigésima oitava": 28, "vigésimo-oitavo": 28, "vigésimo oitavo": 28, "28ª": 28, "28º": 28, "vigésima-nona": 29, "vigésima nona": 29, "vigésimo-nono": 29, "vigésimo nono": 29, "29ª": 29, "29º": 29, "trigésima": 30, "trigésimo": 30, "30ª": 30, "30º": 30, "trigésima-primeira": 31, "trigésima primeira": 31, "trigésimo-primeiro": 31, "trigésimo primeiro": 31, "31ª": 31, "31º": 31, } var ORDINAL_WORDS_PATTERN = `(?:primeir[ao]|1[ªº]|segund[ao]|2[ªº]|terceir[ao]|3[ªº]|quart[ao]|4[ªº]|quint[ao]|5[ªº]|sext[ao]|6[ªº]|sétim[ao]|7[ªº]|oitav[ao]|8[ªº]|non[ao]|9[ªº]|décim[ao]|10[ªº]|décim[ao][- ]primeir[ao]|11[ªº]|décima[ao][- ]segund[ao]|12[ªº]|décim[ao][- ]terceir[ao]|13[ªº]|décim[ao][- ]quart[ao]|14[ªº]|décim[ao][- ]quint[ao]|15[ªº]|décim[ao][- ]sext[ao]|16[ªº]|décim[ao][- ]sétim[ao]|17[ªº]|décim[ao][- ]oitav[ao]|18[ªº]|décim[ao][- ]non[ao]|19[ªº]|vigésim[ao]|20[ªº]|vigésim[ao][- ]primeir[ao]|21[ªº]|vigésim[ao][- ]segund[ao]|22[ªº]|vigésim[ao][- ]terceir[ao]|23[ªº]|vigésim[ao][- ]quart[ao]|24[ªº]|vigésim[ao][- ]quint[ao]|25[ªº]|vigésim[ao][- ]sext[ao]|26[ªº]|vigésim[ao][- ]sétim[ao]|27[ªº]|vigésim[ao][- ]oitav[ao]|28[ªº]|vigésim[ao][- ]non[ao]|29[ªº]|trigésim[ao]|30[ªº]|trigésim[ao][- ]primeir[ao]|31[ªº]|31º)` golang-github-olebedev-when-1.1.0/rules/br/br_test.go000066400000000000000000000037271472640151500225260ustar00rootroot00000000000000package br_test import ( "testing" "time" "github.com/olebedev/when" "github.com/olebedev/when/rules/br" "github.com/stretchr/testify/require" ) var null = time.Date(2016, time.January, 6, 0, 0, 0, 0, time.UTC) type Fixture struct { Text string Index int Phrase string Diff time.Duration } func ApplyFixtures(t *testing.T, name string, w *when.Parser, fixt []Fixture) { for i, f := range fixt { res, err := w.Parse(f.Text, null) require.Nil(t, err, "[%s] err #%d", name, i) require.NotNil(t, res, "[%s] res #%d", name, i) require.Equal(t, f.Index, res.Index, "[%s] index #%d", name, i) require.Equal(t, f.Phrase, res.Text, "[%s] text #%d", name, i) require.Equal(t, f.Diff, res.Time.Sub(null), "[%s] diff #%d", name, i) } } func ApplyFixturesNil(t *testing.T, name string, w *when.Parser, fixt []Fixture) { for i, f := range fixt { res, err := w.Parse(f.Text, null) require.Nil(t, err, "[%s] err #%d", name, i) require.Nil(t, res, "[%s] res #%d", name, i) } } func ApplyFixturesErr(t *testing.T, name string, w *when.Parser, fixt []Fixture) { for i, f := range fixt { _, err := w.Parse(f.Text, null) require.NotNil(t, err, "[%s] err #%d", name, i) require.Equal(t, f.Phrase, err.Error(), "[%s] err text #%d", name, i) } } func TestAll(t *testing.T) { w := when.New(nil) w.Add(br.All...) // complex cases fixt := []Fixture{ {"hoje de noite às 11:10 pm", 0, "hoje de noite às 11:10 pm", (23 * time.Hour) + (10 * time.Minute)}, {"na tarde de sexta", 3, "tarde de sexta", ((2 * 24) + 15) * time.Hour}, {"na próxima terça às 14:00", 3, "próxima terça às 14:00", ((6 * 24) + 14) * time.Hour}, {"na próxima terça às 2p", 3, "próxima terça às 2p", ((6 * 24) + 14) * time.Hour}, {"na próxima quarta-feira às 2:25 p.m.", 3, "próxima quarta-feira às 2:25 p.m.", (((7 * 24) + 14) * time.Hour) + (25 * time.Minute)}, {"11 am última terça", 0, "11 am última terça", -13 * time.Hour}, } ApplyFixtures(t, "br.All...", w, fixt) } golang-github-olebedev-when-1.1.0/rules/br/casual_date.go000066400000000000000000000026271472640151500233270ustar00rootroot00000000000000package br import ( "regexp" "strings" "time" "github.com/AlekSi/pointer" "github.com/olebedev/when/rules" ) func CasualDate(s rules.Strategy) rules.Rule { overwrite := s == rules.Override return &rules.F{ RegExp: regexp.MustCompile("(?i)(?:\\W|^)(agora|hoje|(?:de\\s|nesta\\s|esta\\s)noite|última(?:s|)\\s*noite|(?:amanhã|ontem)\\s*|amanhã|ontem)(?:\\W|$)"), Applier: func(m *rules.Match, c *rules.Context, o *rules.Options, ref time.Time) (bool, error) { lower := strings.ToLower(strings.TrimSpace(m.String())) switch { case regexContains("(nesta|esta|hoje)(\\s|\\s([aà]|de)\\s)noite", lower): if c.Hour == nil && c.Minute == nil || overwrite { c.Hour = pointer.ToInt(23) c.Minute = pointer.ToInt(0) } case strings.Contains(lower, "hoje"): // c.Hour = pointer.ToInt(18) case strings.Contains(lower, "amanhã"): if c.Duration == 0 || overwrite { c.Duration += time.Hour * 24 } case strings.Contains(lower, "ontem"): if c.Duration == 0 || overwrite { c.Duration -= time.Hour * 24 } case regexContains("(ontem|última)(\\s|\\s([aà]|de)\\s)noite", lower): if (c.Hour == nil && c.Duration == 0) || overwrite { c.Hour = pointer.ToInt(23) c.Duration -= time.Hour * 24 } } return true, nil }, } } func regexContains(regex string, text string) bool { contains, _ := regexp.MatchString(regex, text) return contains } golang-github-olebedev-when-1.1.0/rules/br/casual_test.go000066400000000000000000000025241472640151500233650ustar00rootroot00000000000000package br_test import ( "testing" "time" "github.com/olebedev/when" "github.com/olebedev/when/rules" "github.com/olebedev/when/rules/br" ) func TestCasualDate(t *testing.T) { fixt := []Fixture{ {"O prazo final é agora, ok", 17, "agora", 0}, {"O prazo final é hoje", 17, "hoje", 0}, {"O prazo final é esta noite", 17, "esta noite", 23 * time.Hour}, {"O prazo final é amanhã à noite", 17, "amanhã ", time.Hour * 24}, {"O prazo foi ontem à noite", 12, "ontem ", -(time.Hour * 24)}, } w := when.New(nil) w.Add(br.CasualDate(rules.Skip)) ApplyFixtures(t, "br.CasualDate", w, fixt) } func TestCasualTime(t *testing.T) { fixt := []Fixture{ {"O prazo foi esta manhã ", 12, "esta manhã", 8 * time.Hour}, {"O prazo final foi ao meio-dia ", 18, "ao meio-dia", 12 * time.Hour}, {"O prazo final foi esta tarde ", 18, "esta tarde", 15 * time.Hour}, {"O prazo foi nesta noite ", 12, "nesta noite", 18 * time.Hour}, } w := when.New(nil) w.Add(br.CasualTime(rules.Skip)) ApplyFixtures(t, "br.CasualTime", w, fixt) } func TestCasualDateCasualTime(t *testing.T) { fixt := []Fixture{ {"O prazo final é amanhã de tarde", 17, "amanhã de tarde", (15 + 24) * time.Hour}, } w := when.New(nil) w.Add( br.CasualDate(rules.Skip), br.CasualTime(rules.Override), ) ApplyFixtures(t, "br.CasualDate|br.CasualTime", w, fixt) } golang-github-olebedev-when-1.1.0/rules/br/casual_time.go000066400000000000000000000025051472640151500233430ustar00rootroot00000000000000package br import ( "regexp" "strings" "time" "github.com/AlekSi/pointer" "github.com/olebedev/when/rules" ) func CasualTime(s rules.Strategy) rules.Rule { overwrite := s == rules.Override return &rules.F{ RegExp: regexp.MustCompile(`(?i)(?:\W|^)(((?:nesta|esta|ao|à))?\s*(manhã|tarde|noite|meio[- ]dia))`), Applier: func(m *rules.Match, c *rules.Context, o *rules.Options, ref time.Time) (bool, error) { lower := strings.ToLower(strings.TrimSpace(m.String())) if (c.Hour != nil || c.Minute != nil) && !overwrite { return false, nil } switch { case strings.Contains(lower, "tarde"): if o.Afternoon != 0 { c.Hour = &o.Afternoon } else { c.Hour = pointer.ToInt(15) } c.Minute = pointer.ToInt(0) case strings.Contains(lower, "noite"): if o.Evening != 0 { c.Hour = &o.Evening } else { c.Hour = pointer.ToInt(18) } c.Minute = pointer.ToInt(0) case strings.Contains(lower, "manhã"): if o.Morning != 0 { c.Hour = &o.Morning } else { c.Hour = pointer.ToInt(8) } c.Minute = pointer.ToInt(0) case strings.Contains(lower, "meio-dia"), strings.Contains(lower, "meio dia"): if o.Noon != 0 { c.Hour = &o.Noon } else { c.Hour = pointer.ToInt(12) } c.Minute = pointer.ToInt(0) } return true, nil }, } } golang-github-olebedev-when-1.1.0/rules/br/deadline.go000066400000000000000000000056671472640151500226360ustar00rootroot00000000000000package br import ( "regexp" "strconv" "strings" "time" "github.com/AlekSi/pointer" "github.com/olebedev/when/rules" "github.com/pkg/errors" ) func Deadline(s rules.Strategy) rules.Rule { overwrite := s == rules.Override return &rules.F{ RegExp: regexp.MustCompile( "(?i)(?:\\W|^)(dentro\\sde|em)\\s*" + "(?:(" + INTEGER_WORDS_PATTERN + "|[0-9]+|(?:\\s*pouc[oa](?:s|)?|algu(?:mas|m|ns)?|mei[oa]?))\\s*" + "(segundos?|min(?:uto)?s?|horas?|dias?|semanas?|mês|meses|anos?)\\s*)" + "(?:\\W|$)"), Applier: func(m *rules.Match, c *rules.Context, o *rules.Options, ref time.Time) (bool, error) { numStr := strings.TrimSpace(m.Captures[1]) var num int var err error if n, ok := INTEGER_WORDS[numStr]; ok { num = n } else if strings.Contains(numStr, "pouc") || strings.Contains(numStr, "algu") { num = 3 } else if strings.Contains(numStr, "mei") { // pass } else { num, err = strconv.Atoi(numStr) if err != nil { return false, errors.Wrapf(err, "convert '%s' to int", numStr) } } exponent := strings.TrimSpace(m.Captures[2]) if !strings.Contains(numStr, "mei") { switch { case strings.Contains(exponent, "segundo"): if c.Duration == 0 || overwrite { c.Duration = time.Duration(num) * time.Second } case strings.Contains(exponent, "min"): if c.Duration == 0 || overwrite { c.Duration = time.Duration(num) * time.Minute } case strings.Contains(exponent, "hora"): if c.Duration == 0 || overwrite { c.Duration = time.Duration(num) * time.Hour } case strings.Contains(exponent, "dia"): if c.Duration == 0 || overwrite { c.Duration = time.Duration(num) * 24 * time.Hour } case strings.Contains(exponent, "semana"): if c.Duration == 0 || overwrite { c.Duration = time.Duration(num) * 7 * 24 * time.Hour } case strings.Contains(exponent, "mês"), strings.Contains(exponent, "meses"): if c.Month == nil || overwrite { c.Month = pointer.ToInt((int(ref.Month()) + num) % 12) } case strings.Contains(exponent, "ano"): if c.Year == nil || overwrite { c.Year = pointer.ToInt(ref.Year() + num) } } } else { switch { case strings.Contains(exponent, "hora"): if c.Duration == 0 || overwrite { c.Duration = 30 * time.Minute } case strings.Contains(exponent, "dia"): if c.Duration == 0 || overwrite { c.Duration = 12 * time.Hour } case strings.Contains(exponent, "semana"): if c.Duration == 0 || overwrite { c.Duration = 7 * 12 * time.Hour } case strings.Contains(exponent, "mês"), strings.Contains(exponent, "meses"): if c.Duration == 0 || overwrite { // 2 weeks c.Duration = 14 * 24 * time.Hour } case strings.Contains(exponent, "ano"): if c.Month == nil || overwrite { c.Month = pointer.ToInt((int(ref.Month()) + 6) % 12) } } } return true, nil }, } } golang-github-olebedev-when-1.1.0/rules/br/deadline_test.go000066400000000000000000000025111472640151500236560ustar00rootroot00000000000000package br_test import ( "testing" "time" "github.com/olebedev/when" "github.com/olebedev/when/rules" "github.com/olebedev/when/rules/br" ) func TestDeadline(t *testing.T) { fixt := []Fixture{ {"dentro de meia hora", 0, "dentro de meia hora", time.Hour / 2}, {"dentro de 1 hora", 0, "dentro de 1 hora", time.Hour}, {"em 5 minutos", 0, "em 5 minutos", time.Minute * 5}, {"Em 5 minutos eu irei para casa", 0, "Em 5 minutos", time.Minute * 5}, {"nós precisamos fazer algo dentro de 10 dias.", 27, "dentro de 10 dias", 10 * 24 * time.Hour}, {"nós temos que fazer algo em cinco dias.", 26, "em cinco dias", 5 * 24 * time.Hour}, {"nós temos que fazer algo em 5 dias.", 26, "em 5 dias", 5 * 24 * time.Hour}, {"Em 5 segundos, um carro precisa se mover", 0, "Em 5 segundos", 5 * time.Second}, {"dentro de duas semanas", 0, "dentro de duas semanas", 14 * 24 * time.Hour}, {"dentro de um mês", 0, "dentro de um mês", 31 * 24 * time.Hour}, {"dentro de alguns meses", 0, "dentro de alguns meses", 91 * 24 * time.Hour}, {"dentro de poucos meses", 0, "dentro de poucos meses", 91 * 24 * time.Hour}, {"dentro de um ano", 0, "dentro de um ano", 366 * 24 * time.Hour}, {"em uma semana", 0, "em uma semana", 7 * 24 * time.Hour}, } w := when.New(nil) w.Add(br.Deadline(rules.Skip)) ApplyFixtures(t, "br.Deadline", w, fixt) } golang-github-olebedev-when-1.1.0/rules/br/exact_month_date.go000066400000000000000000000021661472640151500243660ustar00rootroot00000000000000package br import ( "regexp" "strconv" "strings" "time" "github.com/olebedev/when/rules" ) func ExactMonthDate(s rules.Strategy) rules.Rule { overwrite := s == rules.Override return &rules.F{ RegExp: regexp.MustCompile("(?i)" + "(?:\\W|^)" + "(?:(?:(\\d{1,2})|(" + ORDINAL_WORDS_PATTERN[3:] + // skip '(?:' ")(?:\\sdia\\sde\\s|\\sde\\s|\\s))*" + "(" + MONTH_OFFSET_PATTERN[3:] + // skip '(?:' "(?:\\W|$)", ), Applier: func(m *rules.Match, c *rules.Context, o *rules.Options, ref time.Time) (bool, error) { _ = overwrite num := strings.ToLower(strings.TrimSpace(m.Captures[0])) ord := strings.ToLower(strings.TrimSpace(m.Captures[1])) mon := strings.ToLower(strings.TrimSpace(m.Captures[2])) monInt, ok := MONTH_OFFSET[mon] if !ok { return false, nil } c.Month = &monInt if ord != "" { ordInt, ok := ORDINAL_WORDS[ord] if !ok { return false, nil } c.Day = &ordInt } if num != "" { n, err := strconv.ParseInt(num, 10, 8) if err != nil { return false, nil } num := int(n) c.Day = &num } return true, nil }, } } golang-github-olebedev-when-1.1.0/rules/br/exact_month_date_test.go000066400000000000000000000021561472640151500254240ustar00rootroot00000000000000package br_test import ( "testing" "time" "github.com/olebedev/when" "github.com/olebedev/when/rules" "github.com/olebedev/when/rules/br" ) func TestExactMonthDate(t *testing.T) { w := when.New(nil) w.Add(br.ExactMonthDate(rules.Override)) fixtok := []Fixture{ {"3 de março", 0, "3 de março", 1368 * time.Hour}, {"1 de setembro", 0, "1 de setembro", 5736 * time.Hour}, {"1 set", 0, "1 set", 5736 * time.Hour}, {"1 set.", 0, "1 set.", 5736 * time.Hour}, {"1º de setembro", 0, "1º de setembro", 5736 * time.Hour}, {"1º set.", 0, "1º set.", 5736 * time.Hour}, {"7 de março", 0, "7 de março", 1464 * time.Hour}, {"21 de outubro", 0, "21 de outubro", 6936 * time.Hour}, {"vigésimo dia de dezembro", 0, "vigésimo dia de dezembro", 8376 * time.Hour}, {"10º dia de março", 0, "10º dia de março", 1536 * time.Hour}, {"4 jan.", 0, "4 jan.", -48 * time.Hour}, {"fevereiro", 0, "fevereiro", 744 * time.Hour}, {"outubro", 0, "outubro", 6576 * time.Hour}, {"jul.", 0, "jul.", 4368 * time.Hour}, {"junho", 0, "junho", 3648 * time.Hour}, } ApplyFixtures(t, "br.ExactMonthDate", w, fixtok) } golang-github-olebedev-when-1.1.0/rules/br/hour.go000066400000000000000000000017051472640151500220330ustar00rootroot00000000000000package br import ( "regexp" "strconv" "time" "github.com/pkg/errors" "github.com/olebedev/when/rules" ) /* "5pm" "5 pm" "5am" "5pm" "5A." "5P." "11 P.M." https://play.golang.org/p/2Gh35Sl3KP */ func Hour(s rules.Strategy) rules.Rule { return &rules.F{ RegExp: regexp.MustCompile("(?i)(?:\\W|^)" + "(\\d{1,2})" + "(?:\\s*(A\\.|P\\.|A\\.M\\.|P\\.M\\.|AM?|PM?))" + "(?:\\W|$)"), Applier: func(m *rules.Match, c *rules.Context, o *rules.Options, ref time.Time) (bool, error) { if c.Hour != nil && s != rules.Override { return false, nil } hour, err := strconv.Atoi(m.Captures[0]) if err != nil { return false, errors.Wrap(err, "hour rule") } if hour > 12 { return false, nil } zero := 0 switch m.Captures[1][0] { case 65, 97: // am c.Hour = &hour case 80, 112: // pm if hour < 12 { hour += 12 } c.Hour = &hour } c.Minute = &zero return true, nil }, } } golang-github-olebedev-when-1.1.0/rules/br/hour_minute.go000066400000000000000000000031511472640151500234110ustar00rootroot00000000000000package br import ( "regexp" "strconv" "time" "github.com/olebedev/when/rules" "github.com/pkg/errors" ) /* {"5:30pm", 0, "5:30pm", 0}, {"5:30 pm", 0, "5:30 pm", 0}, {"7-10pm", 0, "7-10pm", 0}, {"5-30", 0, "5-30", 0}, {"05:30pm", 0, "05:30pm", 0}, {"05:30 pm", 0, "05:30 pm", 0}, {"05:30", 0, "05:30", 0}, {"05-30", 0, "05-30", 0}, {"7-10 pm", 0, "7-10 pm", 0}, {"11.10 pm", 0, "11.10 pm", 0}, https://play.golang.org/p/hXl7C8MWNr */ // 1. - int // 2. - int // 3. - ext? func HourMinute(s rules.Strategy) rules.Rule { return &rules.F{ RegExp: regexp.MustCompile("(?i)(?:\\W|^)" + "((?:[0-1]{0,1}[0-9])|(?:2[0-3]))" + "(?:\\:|:|\\-|h)" + "((?:[0-5][0-9]))m*" + "(?:\\s*(A\\.|P\\.|A\\.M\\.|P\\.M\\.|AM?|PM?))?" + "(?:\\W|$)"), Applier: func(m *rules.Match, c *rules.Context, o *rules.Options, ref time.Time) (bool, error) { if (c.Hour != nil || c.Minute != nil) && s != rules.Override { return false, nil } hour, err := strconv.Atoi(m.Captures[0]) if err != nil { return false, errors.Wrap(err, "hour minute rule") } minutes, err := strconv.Atoi(m.Captures[1]) if err != nil { return false, errors.Wrap(err, "hour minute rule") } if minutes > 59 { return false, nil } c.Minute = &minutes if m.Captures[2] != "" { if hour > 12 { return false, nil } switch m.Captures[2][0] { case 65, 97: // am c.Hour = &hour case 80, 112: // pm if hour < 12 { hour += 12 } c.Hour = &hour } } else { if hour > 23 { return false, nil } c.Hour = &hour } return true, nil }, } } golang-github-olebedev-when-1.1.0/rules/br/hour_minute_test.go000066400000000000000000000024431472640151500244530ustar00rootroot00000000000000package br_test import ( "testing" "time" "github.com/olebedev/when" "github.com/olebedev/when/rules" "github.com/olebedev/when/rules/br" ) func TestHourMinute(t *testing.T) { w := when.New(nil) w.Add(br.HourMinute(rules.Override)) fixtok := []Fixture{ {"5:30pm", 0, "5:30pm", (17 * time.Hour) + (30 * time.Minute)}, {"at 5:30 pm", 3, "5:30 pm", (17 * time.Hour) + (30 * time.Minute)}, {"at 5:59 pm", 3, "5:59 pm", (17 * time.Hour) + (59 * time.Minute)}, {"at 5-59 pm", 3, "5-59 pm", (17 * time.Hour) + (59 * time.Minute)}, {"at 17-59 pam", 3, "17-59", (17 * time.Hour) + (59 * time.Minute)}, {"up to 11:10 pm", 6, "11:10 pm", (23 * time.Hour) + (10 * time.Minute)}, {"19h35m", 0, "19h35", (19 * time.Hour) + (35 * time.Minute)}, } fixtnil := []Fixture{ {"28:30pm", 0, "", 0}, {"12:61pm", 0, "", 0}, {"24:10", 0, "", 0}, } ApplyFixtures(t, "br.HourMinute", w, fixtok) ApplyFixturesNil(t, "on.HourMinute nil", w, fixtnil) w.Add(br.Hour(rules.Skip)) ApplyFixtures(t, "br.HourMinute|br.Hour", w, fixtok) ApplyFixturesNil(t, "on.HourMinute|br.Hour nil", w, fixtnil) w = when.New(nil) w.Add( br.Hour(rules.Override), br.HourMinute(rules.Override), ) ApplyFixtures(t, "br.Hour|br.HourMinute", w, fixtok) ApplyFixturesNil(t, "on.Hour|br.HourMinute nil", w, fixtnil) } golang-github-olebedev-when-1.1.0/rules/br/hour_test.go000066400000000000000000000012741472640151500230730ustar00rootroot00000000000000package br_test import ( "testing" "time" "github.com/olebedev/when" "github.com/olebedev/when/rules" "github.com/olebedev/when/rules/br" ) func TestHour(t *testing.T) { fixt := []Fixture{ {"5pm", 0, "5pm", 17 * time.Hour}, {"at 5 pm", 3, "5 pm", 17 * time.Hour}, {"at 5 P.", 3, "5 P.", 17 * time.Hour}, {"at 12 P.", 3, "12 P.", 12 * time.Hour}, {"at 1 P.", 3, "1 P.", 13 * time.Hour}, {"at 5 am", 3, "5 am", 5 * time.Hour}, {"at 5A", 3, "5A", 5 * time.Hour}, {"at 5A.", 3, "5A.", 5 * time.Hour}, {"5A.", 0, "5A.", 5 * time.Hour}, {"11 P.M.", 0, "11 P.M.", 23 * time.Hour}, } w := when.New(nil) w.Add(br.Hour(rules.Override)) ApplyFixtures(t, "br.Hour", w, fixt) } golang-github-olebedev-when-1.1.0/rules/br/past_time.go000066400000000000000000000067261472640151500230530ustar00rootroot00000000000000package br import ( "regexp" "strconv" "strings" "time" "github.com/AlekSi/pointer" "github.com/olebedev/when/rules" "github.com/pkg/errors" ) func PastTime(s rules.Strategy) rules.Rule { overwrite := s == rules.Override return &rules.F{ RegExp: regexp.MustCompile( "(?i)(?:\\W|^)\\s*" + "(" + INTEGER_WORDS_PATTERN + "|[0-9]+|umas|uma|um|uns|pouc[ao]s*|algu(?:ns|m)|mei[oa]?)\\s*" + "(segundos?|min(?:uto)?s?|hora?s?|dia?s?|semana?s?|mês?|meses?|ano?s?)(\\satrás)\\s*" + "(?:\\W|$)|" + "(?i)(?:há)\\s*" + "(" + INTEGER_WORDS_PATTERN + "|[0-9]+|umas|uma|um|uns|pouc[ao]s*|algu(?:ns|m)|mei[oa]?)\\s*" + "(segundos?|min(?:uto)?s?|hora?s?|dia?s?|semana?s?|mês?|meses?|ano?s?)(\\satrás)*\\s*" + "(?:\\W|$)"), Applier: func(m *rules.Match, c *rules.Context, o *rules.Options, ref time.Time) (bool, error) { var start_index_for_captures int if strings.TrimSpace(m.Captures[0]) == "" { start_index_for_captures = 3 } numStr := strings.TrimSpace( m.Captures[start_index_for_captures+0]) var num int var err error if n, ok := INTEGER_WORDS[numStr]; ok { num = n } else if numStr == "um" || numStr == "uma" { num = 1 } else if numStr == "umas" || numStr == "uns" || strings.Contains(numStr, "pouc") || strings.Contains(numStr, "algu") { num = 3 } else if strings.Contains(numStr, "mei") { // pass } else { num, err = strconv.Atoi(numStr) if err != nil { return false, errors.Wrapf(err, "convert '%s' to int", numStr) } } exponent := strings.TrimSpace( m.Captures[start_index_for_captures+1]) if !strings.Contains(numStr, "mei") { switch { case strings.Contains(exponent, "segund"): if c.Duration == 0 || overwrite { c.Duration = -(time.Duration(num) * time.Second) } case strings.Contains(exponent, "min"): if c.Duration == 0 || overwrite { c.Duration = -(time.Duration(num) * time.Minute) } case strings.Contains(exponent, "hora"): if c.Duration == 0 || overwrite { c.Duration = -(time.Duration(num) * time.Hour) } case strings.Contains(exponent, "dia"): if c.Duration == 0 || overwrite { c.Duration = -(time.Duration(num) * 24 * time.Hour) } case strings.Contains(exponent, "semana"): if c.Duration == 0 || overwrite { c.Duration = -(time.Duration(num) * 7 * 24 * time.Hour) } case strings.Contains(exponent, "mês"), strings.Contains(exponent, "meses"): if c.Month == nil || overwrite { c.Month = pointer.ToInt((int(ref.Month()) - num) % 12) } case strings.Contains(exponent, "ano"): if c.Year == nil || overwrite { c.Year = pointer.ToInt(ref.Year() - num) } } } else { switch { case strings.Contains(exponent, "hora"): if c.Duration == 0 || overwrite { c.Duration = -(30 * time.Minute) } case strings.Contains(exponent, "dia"): if c.Duration == 0 || overwrite { c.Duration = -(12 * time.Hour) } case strings.Contains(exponent, "semanas"): if c.Duration == 0 || overwrite { c.Duration = -(7 * 12 * time.Hour) } case strings.Contains(exponent, "mês"), strings.Contains(exponent, "meses"): if c.Duration == 0 || overwrite { // 2 weeks c.Duration = -(14 * 24 * time.Hour) } case strings.Contains(exponent, "ano"): if c.Month == nil || overwrite { c.Month = pointer.ToInt((int(ref.Month()) - 6) % 12) } } } return true, nil }, } } golang-github-olebedev-when-1.1.0/rules/br/past_time_test.go000066400000000000000000000030331472640151500240760ustar00rootroot00000000000000package br_test import ( "testing" "time" "github.com/olebedev/when" "github.com/olebedev/when/rules" "github.com/olebedev/when/rules/br" ) func TestPastTime(t *testing.T) { fixt := []Fixture{ {"meia hora atrás", 0, "meia hora atrás", -(time.Hour / 2)}, {"1 hora atrás", 0, "1 hora atrás", -(time.Hour)}, {"5 minutos atrás", 0, "5 minutos atrás", -(time.Minute * 5)}, {"5 minutos atrás eu fui ao zoológico", 0, "5 minutos atrás", -(time.Minute * 5)}, {"nós fizemos algo 10 dias atrás.", 18, "10 dias atrás", -(10 * 24 * time.Hour)}, {"nós fizemos algo cinco dias atrás.", 18, "cinco dias atrás", -(5 * 24 * time.Hour)}, {"fizemos algo 5 dias atrás.", 13, "5 dias atrás", -(5 * 24 * time.Hour)}, {"5 segundos atrás, um carro foi movido", 0, "5 segundos atrás", -(5 * time.Second)}, {"duas semanas atrás", 0, "duas semanas atrás", -(14 * 24 * time.Hour)}, {"um mês atrás", 0, "um mês atrás", -(31 * 24 * time.Hour)}, {"uns meses atrás", 0, "uns meses atrás", -(92 * 24 * time.Hour)}, {"há um ano", 4, "um ano", -(365 * 24 * time.Hour)}, {"há duas semanas", 4, "duas semanas", -(2 * 7 * 24 * time.Hour)}, {"poucas semanas atrás", 0, "poucas semanas atrás", -(3 * 7 * 24 * time.Hour)}, {"há poucas semanas", 4, "poucas semanas", -(3 * 7 * 24 * time.Hour)}, {"alguns dias atrás", 0, "alguns dias atrás", -(3 * 24 * time.Hour)}, {"há alguns dias", 4, "alguns dias", -(3 * 24 * time.Hour)}, } w := when.New(nil) w.Add(br.PastTime(rules.Skip)) ApplyFixtures(t, "br.PastTime", w, fixt) } golang-github-olebedev-when-1.1.0/rules/br/weekday.go000066400000000000000000000043501472640151500225060ustar00rootroot00000000000000package br import ( "regexp" "strings" "time" "github.com/olebedev/when/rules" ) func Weekday(s rules.Strategy) rules.Rule { overwrite := s == rules.Override return &rules.F{ RegExp: regexp.MustCompile("(?i)" + "(?:\\W|^)" + "(?:n[ao]\\s*?)?" + "(?:(nest[ae]|ess[ae]|últim[a|o]|próxim[ao])\\s*)?" + "(" + WEEKDAY_OFFSET_PATTERN[3:] + // skip '(?:' "(?:\\s*(passad[ao]|que\\svem))?" + "(?:\\W|$)", ), Applier: func(m *rules.Match, c *rules.Context, o *rules.Options, ref time.Time) (bool, error) { _ = overwrite day := strings.ToLower(strings.TrimSpace(m.Captures[1])) norm := strings.ToLower(strings.TrimSpace(m.Captures[0] + m.Captures[2])) if norm == "" { norm = "próxim[ao]" } dayInt, ok := WEEKDAY_OFFSET[day] if !ok { return false, nil } if c.Duration != 0 && !overwrite { return false, nil } // Switch: switch { case strings.Contains(norm, "passad") || strings.Contains(norm, "últim"): diff := int(ref.Weekday()) - dayInt if diff > 0 { c.Duration = -time.Duration(diff*24) * time.Hour } else if diff < 0 { c.Duration = -time.Duration(7+diff) * 24 * time.Hour } else { c.Duration = -(7 * 24 * time.Hour) } case strings.Contains(norm, "próxim"), strings.Contains(norm, "que vem"): diff := dayInt - int(ref.Weekday()) if diff > 0 { c.Duration = time.Duration(diff*24) * time.Hour } else if diff < 0 { c.Duration = time.Duration(7+diff) * 24 * time.Hour } else { c.Duration = 7 * 24 * time.Hour } case strings.Contains(norm, "nest"), strings.Contains(norm, "ess"): if int(ref.Weekday()) < dayInt { diff := dayInt - int(ref.Weekday()) if diff > 0 { c.Duration = time.Duration(diff*24) * time.Hour } else if diff < 0 { c.Duration = time.Duration(7+diff) * 24 * time.Hour } else { c.Duration = 7 * 24 * time.Hour } } else if int(ref.Weekday()) > dayInt { diff := int(ref.Weekday()) - dayInt if diff > 0 { c.Duration = -time.Duration(diff*24) * time.Hour } else if diff < 0 { c.Duration = -time.Duration(7+diff) * 24 * time.Hour } else { c.Duration = -(7 * 24 * time.Hour) } } } return true, nil }, } } golang-github-olebedev-when-1.1.0/rules/br/weekday_test.go000066400000000000000000000021731472640151500235460ustar00rootroot00000000000000package br_test import ( "testing" "time" "github.com/olebedev/when" "github.com/olebedev/when/rules" "github.com/olebedev/when/rules/br" ) func TestWeekday(t *testing.T) { // current is Friday fixt := []Fixture{ // past/last {"faça isto para a Segunda passada", 18, "Segunda passada", -(2 * 24 * time.Hour)}, {"sábado passado", 0, "sábado passado", -(4 * 24 * time.Hour)}, {"sexta-feira passada", 0, "sexta-feira passada", -(5 * 24 * time.Hour)}, {"quarta-feira passada", 0, "quarta-feira passada", -(7 * 24 * time.Hour)}, {"terça passada", 0, "terça passada", -(24 * time.Hour)}, // // next {"na próxima terça-feira", 3, "próxima terça-feira", 6 * 24 * time.Hour}, {"me ligue na próxima quarta", 12, "próxima quarta", 7 * 24 * time.Hour}, {"sábado que vem", 0, "sábado que vem", 3 * 24 * time.Hour}, // // this {"essa terça-feira", 0, "essa terça-feira", -(24 * time.Hour)}, {"liga pra mim nesta quarta", 13, "nesta quarta", 0}, {"neste sábado", 0, "neste sábado", 3 * 24 * time.Hour}, } w := when.New(nil) w.Add(br.Weekday(rules.Override)) ApplyFixtures(t, "br.Weekday", w, fixt) } golang-github-olebedev-when-1.1.0/rules/common/000077500000000000000000000000001472640151500214115ustar00rootroot00000000000000golang-github-olebedev-when-1.1.0/rules/common/common.go000066400000000000000000000001561472640151500232320ustar00rootroot00000000000000package common import "github.com/olebedev/when/rules" var All = []rules.Rule{ SlashDMY(rules.Override), } golang-github-olebedev-when-1.1.0/rules/common/common_test.go000066400000000000000000000027461472640151500243000ustar00rootroot00000000000000package common_test import ( "testing" "time" "github.com/olebedev/when" "github.com/olebedev/when/rules/common" "github.com/stretchr/testify/require" ) var null = time.Date(2016, time.July, 15, 0, 0, 0, 0, time.UTC) // July 15 days offset from the begining of the year const OFFSET = 197 type Fixture struct { Text string Index int Phrase string Diff time.Duration } func ApplyFixtures(t *testing.T, name string, w *when.Parser, fixt []Fixture) { for i, f := range fixt { res, err := w.Parse(f.Text, null) require.Nil(t, err, "[%s] err #%d", name, i) require.NotNil(t, res, "[%s] res #%d", name, i) require.Equal(t, f.Index, res.Index, "[%s] index #%d", name, i) require.Equal(t, f.Phrase, res.Text, "[%s] text #%d", name, i) require.Equal(t, f.Diff, res.Time.Sub(null), "[%s] diff #%d", name, i) } } func ApplyFixturesNil(t *testing.T, name string, w *when.Parser, fixt []Fixture) { for i, f := range fixt { res, err := w.Parse(f.Text, null) require.Nil(t, err, "[%s] err #%d", name, i) require.Nil(t, res, "[%s] res #%d", name, i) } } func ApplyFixturesErr(t *testing.T, name string, w *when.Parser, fixt []Fixture) { for i, f := range fixt { _, err := w.Parse(f.Text, null) require.NotNil(t, err, "[%s] err #%d", name, i) require.Equal(t, f.Phrase, err.Error(), "[%s] err text #%d", name, i) } } func TestAll(t *testing.T) { w := when.New(nil) w.Add(common.All...) // complex cases fixt := []Fixture{} ApplyFixtures(t, "common.All...", w, fixt) } golang-github-olebedev-when-1.1.0/rules/common/slash_dmy.go000066400000000000000000000033261472640151500237270ustar00rootroot00000000000000package common import ( "regexp" "strconv" "time" "github.com/olebedev/when/rules" ) /* - DD/MM/YYYY - 11/3/2015 - 11/3/2015 - 11/3 also with "\", gift for windows' users https://play.golang.org/p/29LkTfe1Xr */ var MONTHS_DAYS = []int{ 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, } func getDays(year, month int) int { // naive leap year check if (year-2000)%4 == 0 && month == 2 { return 29 } return MONTHS_DAYS[month] } func SlashDMY(s rules.Strategy) rules.Rule { return &rules.F{ RegExp: regexp.MustCompile("(?i)(?:\\W|^)" + "(0{0,1}[1-9]|1[0-9]|2[0-9]|3[01])" + "[\\/\\\\]" + "(0{0,1}[1-9]|1[0-2])" + "(?:[\\/\\\\]" + "((?:1|2)[0-9]{3})\\s*)?" + "(?:\\W|$)"), Applier: func(m *rules.Match, c *rules.Context, o *rules.Options, ref time.Time) (bool, error) { if (c.Day != nil || c.Month != nil || c.Year != nil) && s != rules.Override { return false, nil } day, _ := strconv.Atoi(m.Captures[0]) month, _ := strconv.Atoi(m.Captures[1]) year := -1 if m.Captures[2] != "" { year, _ = strconv.Atoi(m.Captures[2]) } if day == 0 { return false, nil } WithYear: if year != -1 { if getDays(year, month) >= day { c.Year = &year c.Month = &month c.Day = &day } else { return false, nil } return true, nil } if int(ref.Month()) > month { year = ref.Year() + 1 goto WithYear } if int(ref.Month()) == month { if getDays(ref.Year(), month) >= day { if day > ref.Day() { year = ref.Year() } else if day < ref.Day() { year = ref.Year() + 1 } else { return false, nil } goto WithYear } else { return false, nil } } return true, nil }, } } golang-github-olebedev-when-1.1.0/rules/common/slash_dmy_test.go000066400000000000000000000023211472640151500247600ustar00rootroot00000000000000package common_test import ( "testing" "time" "github.com/olebedev/when" "github.com/olebedev/when/rules" "github.com/olebedev/when/rules/common" ) func TestSlashDMY(t *testing.T) { fixt := []Fixture{ {"The Deadline is 10/10/2016", 16, "10/10/2016", (284 - OFFSET) * 24 * time.Hour}, {"The Deadline is 1/2/2016", 16, "1/2/2016", (32 - OFFSET) * 24 * time.Hour}, {"The Deadline is 29/2/2016", 16, "29/2/2016", (60 - OFFSET) * 24 * time.Hour}, // next year {"The Deadline is 28/2", 16, "28/2", (59 + 366 - OFFSET) * 24 * time.Hour}, {"The Deadline is 28/02/2017", 16, "28/02/2017", (59 + 366 - OFFSET) * 24 * time.Hour}, // right after w/o a year {"The Deadline is 28/07", 16, "28/07", (210 - OFFSET) * 24 * time.Hour}, // before w/o a year {"The Deadline is 30/06", 16, "30/06", (181 + 366 - OFFSET) * 24 * time.Hour}, // prev day will be added to the future {"The Deadline is 14/07", 16, "14/07", (195 + 366 - OFFSET) * 24 * time.Hour}, } w := when.New(nil) w.Add(common.SlashDMY(rules.Skip)) ApplyFixtures(t, "common.SlashDMY", w, fixt) nilFixt := []Fixture{ {"The Deadline is 1/20/2016", 16, "no match for mm/dd/yyyy", 0}, } ApplyFixturesNil(t, "common.SlashDMY nil", w, nilFixt) } golang-github-olebedev-when-1.1.0/rules/context.go000066400000000000000000000030311472640151500221310ustar00rootroot00000000000000package rules import "time" type Context struct { Text string // accumulator of relative values Duration time.Duration // Aboslute values Year, Month, Weekday, Day, Hour, Minute, Second *int Location *time.Location } func (c *Context) Time(t time.Time) (time.Time, error) { if t.IsZero() { t = time.Now() } if c.Duration != 0 { t = t.Add(c.Duration) } if c.Year != nil { t = time.Date(*c.Year, t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), t.Location()) } if c.Month != nil { t = time.Date(t.Year(), time.Month(*c.Month), t.Day(), t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), t.Location()) } if c.Weekday != nil { diff := int(time.Weekday(*c.Weekday) - t.Weekday()) t = time.Date(t.Year(), t.Month(), t.Day()+diff, t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), t.Location()) } if c.Day != nil { t = time.Date(t.Year(), t.Month(), *c.Day, t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), t.Location()) } if c.Hour != nil { t = time.Date(t.Year(), t.Month(), t.Day(), *c.Hour, t.Minute(), t.Second(), t.Nanosecond(), t.Location()) } if c.Minute != nil { t = time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), *c.Minute, t.Second(), t.Nanosecond(), t.Location()) } if c.Second != nil { t = time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), *c.Second, t.Nanosecond(), t.Location()) } if c.Location != nil { t = time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), c.Location) } return t, nil } golang-github-olebedev-when-1.1.0/rules/en/000077500000000000000000000000001472640151500205235ustar00rootroot00000000000000golang-github-olebedev-when-1.1.0/rules/en/casual_date.go000066400000000000000000000023641472640151500233240ustar00rootroot00000000000000package en import ( "regexp" "strings" "time" "github.com/AlekSi/pointer" "github.com/olebedev/when/rules" ) func CasualDate(s rules.Strategy) rules.Rule { overwrite := s == rules.Override return &rules.F{ RegExp: regexp.MustCompile("(?i)(?:\\W|^)(now|today|tonight|last\\s*night|(?:tomorrow|tmr|yesterday)\\s*|tomorrow|tmr|yesterday)(?:\\W|$)"), Applier: func(m *rules.Match, c *rules.Context, o *rules.Options, ref time.Time) (bool, error) { lower := strings.ToLower(strings.TrimSpace(m.String())) switch { case strings.Contains(lower, "tonight"): if c.Hour == nil && c.Minute == nil || overwrite { c.Hour = pointer.ToInt(23) c.Minute = pointer.ToInt(0) } case strings.Contains(lower, "today"): // c.Hour = pointer.ToInt(18) case strings.Contains(lower, "tomorrow"), strings.Contains(lower, "tmr"): if c.Duration == 0 || overwrite { c.Duration += time.Hour * 24 } case strings.Contains(lower, "yesterday"): if c.Duration == 0 || overwrite { c.Duration -= time.Hour * 24 } case strings.Contains(lower, "last night"): if (c.Hour == nil && c.Duration == 0) || overwrite { c.Hour = pointer.ToInt(23) c.Duration -= time.Hour * 24 } } return true, nil }, } } golang-github-olebedev-when-1.1.0/rules/en/casual_test.go000066400000000000000000000025601472640151500233640ustar00rootroot00000000000000package en_test import ( "testing" "time" "github.com/olebedev/when" "github.com/olebedev/when/rules" "github.com/olebedev/when/rules/en" ) func TestCasualDate(t *testing.T) { fixt := []Fixture{ {"The Deadline is now, ok", 16, "now", 0}, {"The Deadline is today", 16, "today", 0}, {"The Deadline is tonight", 16, "tonight", 23 * time.Hour}, {"The Deadline is tomorrow evening", 16, "tomorrow", time.Hour * 24}, {"The Deadline is yesterday evening", 16, "yesterday", -(time.Hour * 24)}, } w := when.New(nil) w.Add(en.CasualDate(rules.Skip)) ApplyFixtures(t, "en.CasualDate", w, fixt) } func TestCasualTime(t *testing.T) { fixt := []Fixture{ {"The Deadline was this morning ", 17, "this morning", 8 * time.Hour}, {"The Deadline was this noon ", 17, "this noon", 12 * time.Hour}, {"The Deadline was this afternoon ", 17, "this afternoon", 15 * time.Hour}, {"The Deadline was this evening ", 17, "this evening", 18 * time.Hour}, } w := when.New(nil) w.Add(en.CasualTime(rules.Skip)) ApplyFixtures(t, "en.CasualTime", w, fixt) } func TestCasualDateCasualTime(t *testing.T) { fixt := []Fixture{ {"The Deadline is tomorrow this afternoon ", 16, "tomorrow this afternoon", (15 + 24) * time.Hour}, } w := when.New(nil) w.Add( en.CasualDate(rules.Skip), en.CasualTime(rules.Override), ) ApplyFixtures(t, "en.CasualDate|en.CasualTime", w, fixt) } golang-github-olebedev-when-1.1.0/rules/en/casual_time.go000066400000000000000000000024241472640151500233420ustar00rootroot00000000000000package en import ( "regexp" "strings" "time" "github.com/AlekSi/pointer" "github.com/olebedev/when/rules" ) func CasualTime(s rules.Strategy) rules.Rule { overwrite := s == rules.Override return &rules.F{ RegExp: regexp.MustCompile(`(?i)(?:\W|^)((this)?\s*(morning|afternoon|evening|noon))`), Applier: func(m *rules.Match, c *rules.Context, o *rules.Options, ref time.Time) (bool, error) { lower := strings.ToLower(strings.TrimSpace(m.String())) if (c.Hour != nil || c.Minute != nil) && !overwrite { return false, nil } switch { case strings.Contains(lower, "afternoon"): if o.Afternoon != 0 { c.Hour = &o.Afternoon } else { c.Hour = pointer.ToInt(15) } c.Minute = pointer.ToInt(0) case strings.Contains(lower, "evening"): if o.Evening != 0 { c.Hour = &o.Evening } else { c.Hour = pointer.ToInt(18) } c.Minute = pointer.ToInt(0) case strings.Contains(lower, "morning"): if o.Morning != 0 { c.Hour = &o.Morning } else { c.Hour = pointer.ToInt(8) } c.Minute = pointer.ToInt(0) case strings.Contains(lower, "noon"): if o.Noon != 0 { c.Hour = &o.Noon } else { c.Hour = pointer.ToInt(12) } c.Minute = pointer.ToInt(0) } return true, nil }, } } golang-github-olebedev-when-1.1.0/rules/en/deadline.go000066400000000000000000000055451472640151500226300ustar00rootroot00000000000000package en import ( "regexp" "strconv" "strings" "time" "github.com/AlekSi/pointer" "github.com/olebedev/when/rules" "github.com/pkg/errors" ) func Deadline(s rules.Strategy) rules.Rule { overwrite := s == rules.Override return &rules.F{ RegExp: regexp.MustCompile( "(?i)(?:\\W|^)(within|in)\\s*" + "(" + INTEGER_WORDS_PATTERN + "|[0-9]+|an?(?:\\s*few)?|half(?:\\s*an?)?)\\s*" + "(seconds?|min(?:ute)?s?|hours?|days?|weeks?|months?|years?)\\s*" + "(?:\\W|$)"), Applier: func(m *rules.Match, c *rules.Context, o *rules.Options, ref time.Time) (bool, error) { numStr := strings.TrimSpace(m.Captures[1]) var num int var err error if n, ok := INTEGER_WORDS[numStr]; ok { num = n } else if numStr == "a" || numStr == "an" { num = 1 } else if strings.Contains(numStr, "few") { num = 3 } else if strings.Contains(numStr, "half") { // pass } else { num, err = strconv.Atoi(numStr) if err != nil { return false, errors.Wrapf(err, "convert '%s' to int", numStr) } } exponent := strings.TrimSpace(m.Captures[2]) if !strings.Contains(numStr, "half") { switch { case strings.Contains(exponent, "second"): if c.Duration == 0 || overwrite { c.Duration = time.Duration(num) * time.Second } case strings.Contains(exponent, "min"): if c.Duration == 0 || overwrite { c.Duration = time.Duration(num) * time.Minute } case strings.Contains(exponent, "hour"): if c.Duration == 0 || overwrite { c.Duration = time.Duration(num) * time.Hour } case strings.Contains(exponent, "day"): if c.Duration == 0 || overwrite { c.Duration = time.Duration(num) * 24 * time.Hour } case strings.Contains(exponent, "week"): if c.Duration == 0 || overwrite { c.Duration = time.Duration(num) * 7 * 24 * time.Hour } case strings.Contains(exponent, "month"): if c.Month == nil || overwrite { c.Month = pointer.ToInt((int(ref.Month()) + num) % 12) } case strings.Contains(exponent, "year"): if c.Year == nil || overwrite { c.Year = pointer.ToInt(ref.Year() + num) } } } else { switch { case strings.Contains(exponent, "hour"): if c.Duration == 0 || overwrite { c.Duration = 30 * time.Minute } case strings.Contains(exponent, "day"): if c.Duration == 0 || overwrite { c.Duration = 12 * time.Hour } case strings.Contains(exponent, "week"): if c.Duration == 0 || overwrite { c.Duration = 7 * 12 * time.Hour } case strings.Contains(exponent, "month"): if c.Duration == 0 || overwrite { // 2 weeks c.Duration = 14 * 24 * time.Hour } case strings.Contains(exponent, "year"): if c.Month == nil || overwrite { c.Month = pointer.ToInt((int(ref.Month()) + 6) % 12) } } } return true, nil }, } } golang-github-olebedev-when-1.1.0/rules/en/deadline_test.go000066400000000000000000000022651472640151500236630ustar00rootroot00000000000000package en_test import ( "testing" "time" "github.com/olebedev/when" "github.com/olebedev/when/rules" "github.com/olebedev/when/rules/en" ) func TestDeadline(t *testing.T) { fixt := []Fixture{ {"within half an hour", 0, "within half an hour", time.Hour / 2}, {"within 1 hour", 0, "within 1 hour", time.Hour}, {"in 5 minutes", 0, "in 5 minutes", time.Minute * 5}, {"In 5 minutes I will go home", 0, "In 5 minutes", time.Minute * 5}, {"we have to do something within 10 days.", 24, "within 10 days", 10 * 24 * time.Hour}, {"we have to do something in five days.", 24, "in five days", 5 * 24 * time.Hour}, {"we have to do something in 5 days.", 24, "in 5 days", 5 * 24 * time.Hour}, {"In 5 seconds A car need to move", 0, "In 5 seconds", 5 * time.Second}, {"within two weeks", 0, "within two weeks", 14 * 24 * time.Hour}, {"within a month", 0, "within a month", 31 * 24 * time.Hour}, {"within a few months", 0, "within a few months", 91 * 24 * time.Hour}, {"within one year", 0, "within one year", 366 * 24 * time.Hour}, {"in a week", 0, "in a week", 7 * 24 * time.Hour}, } w := when.New(nil) w.Add(en.Deadline(rules.Skip)) ApplyFixtures(t, "en.Deadline", w, fixt) } golang-github-olebedev-when-1.1.0/rules/en/en.go000066400000000000000000000077721472640151500214710ustar00rootroot00000000000000package en import "github.com/olebedev/when/rules" var All = []rules.Rule{ Weekday(rules.Override), CasualDate(rules.Override), CasualTime(rules.Override), Hour(rules.Override), HourMinute(rules.Override), Deadline(rules.Override), PastTime(rules.Override), ExactMonthDate(rules.Override), } var WEEKDAY_OFFSET = map[string]int{ "sunday": 0, "sun": 0, "monday": 1, "mon": 1, "tuesday": 2, "tue": 2, "wednesday": 3, "wed": 3, "thursday": 4, "thur": 4, "thu": 4, "friday": 5, "fri": 5, "saturday": 6, "sat": 6, } var WEEKDAY_OFFSET_PATTERN = "(?:sunday|sun|monday|mon|tuesday|tue|wednesday|wed|thursday|thur|thu|friday|fri|saturday|sat)" var MONTH_OFFSET = map[string]int{ "january": 1, "jan": 1, "jan.": 1, "february": 2, "feb": 2, "feb.": 2, "march": 3, "mar": 3, "mar.": 3, "april": 4, "apr": 4, "apr.": 4, "may": 5, "june": 6, "jun": 6, "jun.": 6, "july": 7, "jul": 7, "jul.": 7, "august": 8, "aug": 8, "aug.": 8, "september": 9, "sep": 9, "sep.": 9, "sept": 9, "sept.": 9, "october": 10, "oct": 10, "oct.": 10, "november": 11, "nov": 11, "nov.": 11, "december": 12, "dec": 12, "dec.": 12, } var MONTH_OFFSET_PATTERN = `(?:january|jan\.?|february|feb\.?|march|mar\.?|april|apr\.?|may|june|jun\.?|july|jul\.?|august|aug\.?|september|sept?\.?|october|oct\.?|november|nov\.?|december|dec\.?)` var INTEGER_WORDS = map[string]int{ "one": 1, "two": 2, "three": 3, "four": 4, "five": 5, "six": 6, "seven": 7, "eight": 8, "nine": 9, "ten": 10, "eleven": 11, "twelve": 12, } var INTEGER_WORDS_PATTERN = `(?:one|two|three|four|five|six|seven|eight|nine|ten|eleven|twelve)` var ORDINAL_WORDS = map[string]int{ "first": 1, "1st": 1, "second": 2, "2nd": 2, "third": 3, "3rd": 3, "fourth": 4, "4th": 4, "fifth": 5, "5th": 5, "sixth": 6, "6th": 6, "seventh": 7, "7th": 7, "eighth": 8, "8th": 8, "ninth": 9, "9th": 9, "tenth": 10, "10th": 10, "eleventh": 11, "11th": 11, "twelfth": 12, "12th": 12, "thirteenth": 13, "13th": 13, "fourteenth": 14, "14th": 14, "fifteenth": 15, "15th": 15, "sixteenth": 16, "16th": 16, "seventeenth": 17, "17th": 17, "eighteenth": 18, "18th": 18, "nineteenth": 19, "19th": 19, "twentieth": 20, "20th": 20, "twenty first": 21, "twenty-first": 21, "21st": 21, "twenty second": 22, "twenty-second": 22, "22nd": 22, "twenty third": 23, "twenty-third": 23, "23rd": 23, "twenty fourth": 24, "twenty-fourth": 24, "24th": 24, "twenty fifth": 25, "twenty-fifth": 25, "25th": 25, "twenty sixth": 26, "twenty-sixth": 26, "26th": 26, "twenty seventh": 27, "twenty-seventh": 27, "27th": 27, "twenty eighth": 28, "twenty-eighth": 28, "28th": 28, "twenty ninth": 29, "twenty-ninth": 29, "29th": 29, "thirtieth": 30, "30th": 30, "thirty first": 31, "thirty-first": 31, "31st": 31, } var ORDINAL_WORDS_PATTERN = `(?:1st|first|2nd|second|3rd|third|4th|fourth|5th|fifth|6th|sixth|7th|seventh|8th|eighth|9th|ninth|10th|tenth|11th|eleventh|12th|twelfth|13th|thirteenth|14th|fourteenth|15th|fifteenth|16th|sixteenth|17th|seventeenth|18th|eighteenth|19th|nineteenth|20th|twentieth|21st|twenty[ -]first|22nd|twenty[ -]second|23rd|twenty[ -]third|24th|twenty[ -]fourth|25th|twenty[ -]fifth|26th|twenty[ -]sixth|27th|twenty[ -]seventh|28th|twenty[ -]eighth|29th|twenty[ -]ninth|30th|thirtieth|31st|thirty[ -]first)` golang-github-olebedev-when-1.1.0/rules/en/en_test.go000066400000000000000000000036541472640151500225230ustar00rootroot00000000000000package en_test import ( "testing" "time" "github.com/olebedev/when" "github.com/olebedev/when/rules/en" "github.com/stretchr/testify/require" ) var null = time.Date(2016, time.January, 6, 0, 0, 0, 0, time.UTC) type Fixture struct { Text string Index int Phrase string Diff time.Duration } func ApplyFixtures(t *testing.T, name string, w *when.Parser, fixt []Fixture) { for i, f := range fixt { res, err := w.Parse(f.Text, null) require.Nil(t, err, "[%s] err #%d", name, i) require.NotNil(t, res, "[%s] res #%d", name, i) require.Equal(t, f.Index, res.Index, "[%s] index #%d", name, i) require.Equal(t, f.Phrase, res.Text, "[%s] text #%d", name, i) require.Equal(t, f.Diff, res.Time.Sub(null), "[%s] diff #%d", name, i) } } func ApplyFixturesNil(t *testing.T, name string, w *when.Parser, fixt []Fixture) { for i, f := range fixt { res, err := w.Parse(f.Text, null) require.Nil(t, err, "[%s] err #%d", name, i) require.Nil(t, res, "[%s] res #%d", name, i) } } func ApplyFixturesErr(t *testing.T, name string, w *when.Parser, fixt []Fixture) { for i, f := range fixt { _, err := w.Parse(f.Text, null) require.NotNil(t, err, "[%s] err #%d", name, i) require.Equal(t, f.Phrase, err.Error(), "[%s] err text #%d", name, i) } } func TestAll(t *testing.T) { w := when.New(nil) w.Add(en.All...) // complex cases fixt := []Fixture{ {"tonight at 11:10 pm", 0, "tonight at 11:10 pm", (23 * time.Hour) + (10 * time.Minute)}, {"at Friday afternoon", 3, "Friday afternoon", ((2 * 24) + 15) * time.Hour}, {"in next tuesday at 14:00", 3, "next tuesday at 14:00", ((6 * 24) + 14) * time.Hour}, {"in next tuesday at 2p", 3, "next tuesday at 2p", ((6 * 24) + 14) * time.Hour}, {"in next wednesday at 2:25 p.m.", 3, "next wednesday at 2:25 p.m.", (((7 * 24) + 14) * time.Hour) + (25 * time.Minute)}, {"at 11 am past tuesday", 3, "11 am past tuesday", -13 * time.Hour}, } ApplyFixtures(t, "en.All...", w, fixt) } golang-github-olebedev-when-1.1.0/rules/en/exact_month_date.go000066400000000000000000000050741472640151500243660ustar00rootroot00000000000000package en import ( "regexp" "strconv" "strings" "time" "github.com/olebedev/when/rules" ) // <[]string{"third of march", "third", "", "march", "", ""}> // <[]string{"march third", "", "", "march", "third", ""}> // <[]string{"march 3rd", "", "", "march", "3rd", ""}> // <[]string{"3rd march", "3rd", "", "march", "", ""}> // <[]string{"march 3", "", "", "march", "", "3"}> // <[]string{"1st of september", "1st", "", "september", "", ""}> // <[]string{"sept. 1st", "", "", "sept.", "1st", ""}> // <[]string{"march 7th", "", "", "march", "7th", ""}> // <[]string{"october 21st", "", "", "october", "21st", ""}> // <[]string{"twentieth of december", "twentieth", "", "december", "", ""}> // <[]string{"march 10th", "", "", "march", "10th", ""}> // <[]string{"jan. 6", "", "", "jan.", "", "6"}> // <[]string{"february", "", "", "february", "", ""}> // <[]string{"october", "", "", "october", "", ""}> // <[]string{"jul.", "", "", "jul.", "", ""}> // <[]string{"june", "", "", "june", "", ""}> // https://play.golang.org/p/Zfjl6ERNkq // 1. - ordinal day? // 2. - numeric day? // 3. - month // 4. - ordinal day? // 5. - ordinal day? func ExactMonthDate(s rules.Strategy) rules.Rule { overwrite := s == rules.Override return &rules.F{ RegExp: regexp.MustCompile("(?i)" + "(?:\\W|^)" + "(?:(?:(" + ORDINAL_WORDS_PATTERN[3:] + "(?:\\s+of)?|([0-9]+))\\s*)?" + "(" + MONTH_OFFSET_PATTERN[3:] + // skip '(?:' "(?:\\s*(?:(" + ORDINAL_WORDS_PATTERN[3:] + "|([0-9]+)))?" + "(?:\\W|$)", ), Applier: func(m *rules.Match, c *rules.Context, o *rules.Options, ref time.Time) (bool, error) { _ = overwrite ord1 := strings.ToLower(strings.TrimSpace(m.Captures[0])) num1 := strings.ToLower(strings.TrimSpace(m.Captures[1])) mon := strings.ToLower(strings.TrimSpace(m.Captures[2])) ord2 := strings.ToLower(strings.TrimSpace(m.Captures[3])) num2 := strings.ToLower(strings.TrimSpace(m.Captures[4])) monInt, ok := MONTH_OFFSET[mon] if !ok { return false, nil } c.Month = &monInt if ord1 != "" { ordInt, ok := ORDINAL_WORDS[ord1] if !ok { return false, nil } c.Day = &ordInt } if num1 != "" { n, err := strconv.ParseInt(num1, 10, 8) if err != nil { return false, nil } num := int(n) c.Day = &num } if ord2 != "" { ordInt, ok := ORDINAL_WORDS[ord2] if !ok { return false, nil } c.Day = &ordInt } if num2 != "" { n, err := strconv.ParseInt(num2, 10, 8) if err != nil { return false, nil } num := int(n) c.Day = &num } return true, nil }, } } golang-github-olebedev-when-1.1.0/rules/en/exact_month_date_test.go000066400000000000000000000024421472640151500254210ustar00rootroot00000000000000package en_test import ( "testing" "time" "github.com/olebedev/when" "github.com/olebedev/when/rules" "github.com/olebedev/when/rules/en" ) func TestExactMonthDate(t *testing.T) { w := when.New(nil) w.Add(en.ExactMonthDate(rules.Override)) fixtok := []Fixture{ {"third of march", 0, "third of march", 1368 * time.Hour}, {"march third", 0, "march third", 1368 * time.Hour}, {"march 3rd", 0, "march 3rd", 1368 * time.Hour}, {"3rd march", 0, "3rd march", 1368 * time.Hour}, {"march 3", 0, "march 3", 1368 * time.Hour}, {"1 september", 0, "1 september", 5736 * time.Hour}, {"1 sept", 0, "1 sept", 5736 * time.Hour}, {"1 sept.", 0, "1 sept.", 5736 * time.Hour}, {"1st of september", 0, "1st of september", 5736 * time.Hour}, {"sept. 1st", 0, "sept. 1st", 5736 * time.Hour}, {"march 7th", 0, "march 7th", 1464 * time.Hour}, {"october 21st", 0, "october 21st", 6936 * time.Hour}, {"twentieth of december", 0, "twentieth of december", 8376 * time.Hour}, {"march 10th", 0, "march 10th", 1536 * time.Hour}, {"jan. 4", 0, "jan. 4", -48 * time.Hour}, {"february", 0, "february", 744 * time.Hour}, {"october", 0, "october", 6576 * time.Hour}, {"jul.", 0, "jul.", 4368 * time.Hour}, {"june", 0, "june", 3648 * time.Hour}, } ApplyFixtures(t, "en.ExactMonthDate", w, fixtok) } golang-github-olebedev-when-1.1.0/rules/en/hour.go000066400000000000000000000017311472640151500220310ustar00rootroot00000000000000package en import ( "regexp" "strconv" "time" "github.com/pkg/errors" "github.com/olebedev/when/rules" ) /* "5pm" "5 pm" "5am" "5pm" "5A." "5P." "11 P.M." https://play.golang.org/p/2Gh35Sl3KP */ func Hour(s rules.Strategy) rules.Rule { return &rules.F{ RegExp: regexp.MustCompile("(?i)(?:\\W|^)" + "(\\d{1,2})" + "(?:\\s*(A\\.|P\\.|A\\.M\\.|P\\.M\\.|AM?|PM?))" + "(?:\\W|$)"), Applier: func(m *rules.Match, c *rules.Context, o *rules.Options, ref time.Time) (bool, error) { if c.Hour != nil && s != rules.Override { return false, nil } hour, err := strconv.Atoi(m.Captures[0]) if err != nil { return false, errors.Wrap(err, "hour rule") } if hour > 12 { return false, nil } zero := 0 switch m.Captures[1][0] { case 65, 97: // am c.Hour = &hour case 80, 112: // pm if hour < 12 { hour += 12 } c.Hour = &hour } c.Minute = &zero c.Second = &zero return true, nil }, } } golang-github-olebedev-when-1.1.0/rules/en/hour_minute.go000066400000000000000000000032401472640151500234070ustar00rootroot00000000000000package en import ( "regexp" "strconv" "time" "github.com/olebedev/when/rules" "github.com/pkg/errors" ) /* {"5:30pm", 0, "5:30pm", 0}, {"5:30 pm", 0, "5:30 pm", 0}, {"7-10pm", 0, "7-10pm", 0}, {"5-30", 0, "5-30", 0}, {"05:30pm", 0, "05:30pm", 0}, {"05:30 pm", 0, "05:30 pm", 0}, {"05:30", 0, "05:30", 0}, {"05-30", 0, "05-30", 0}, {"7-10 pm", 0, "7-10 pm", 0}, {"11.10 pm", 0, "11.10 pm", 0}, https://play.golang.org/p/hXl7C8MWNr */ // 1. - int // 2. - int // 3. - ext? func HourMinute(s rules.Strategy) rules.Rule { return &rules.F{ RegExp: regexp.MustCompile("(?i)(?:\\W|^)" + "((?:[0-1]{0,1}[0-9])|(?:2[0-3]))" + "(?:\\:|:|\\-)" + "((?:[0-5][0-9]))" + "(?:\\s*(A\\.|P\\.|A\\.M\\.|P\\.M\\.|AM?|PM?))?" + "(?:\\W|$)"), Applier: func(m *rules.Match, c *rules.Context, o *rules.Options, ref time.Time) (bool, error) { if (c.Hour != nil || c.Minute != nil) && s != rules.Override { return false, nil } hour, err := strconv.Atoi(m.Captures[0]) if err != nil { return false, errors.Wrap(err, "hour minute rule") } minutes, err := strconv.Atoi(m.Captures[1]) if err != nil { return false, errors.Wrap(err, "hour minute rule") } if minutes > 59 { return false, nil } c.Minute = &minutes if m.Captures[2] != "" { if hour > 12 { return false, nil } switch m.Captures[2][0] { case 65, 97: // am c.Hour = &hour case 80, 112: // pm if hour < 12 { hour += 12 } c.Hour = &hour } } else { if hour > 23 { return false, nil } c.Hour = &hour } seconds := 0 // Truncate seconds c.Second = &seconds return true, nil }, } } golang-github-olebedev-when-1.1.0/rules/en/hour_minute_test.go000066400000000000000000000023421472640151500244500ustar00rootroot00000000000000package en_test import ( "testing" "time" "github.com/olebedev/when" "github.com/olebedev/when/rules" "github.com/olebedev/when/rules/en" ) func TestHourMinute(t *testing.T) { w := when.New(nil) w.Add(en.HourMinute(rules.Override)) fixtok := []Fixture{ {"5:30pm", 0, "5:30pm", (17 * time.Hour) + (30 * time.Minute)}, {"at 5:30 pm", 3, "5:30 pm", (17 * time.Hour) + (30 * time.Minute)}, {"at 5:59 pm", 3, "5:59 pm", (17 * time.Hour) + (59 * time.Minute)}, {"at 5-59 pm", 3, "5-59 pm", (17 * time.Hour) + (59 * time.Minute)}, {"at 17-59 pam", 3, "17-59", (17 * time.Hour) + (59 * time.Minute)}, {"up to 11:10 pm", 6, "11:10 pm", (23 * time.Hour) + (10 * time.Minute)}, } fixtnil := []Fixture{ {"28:30pm", 0, "", 0}, {"12:61pm", 0, "", 0}, {"24:10", 0, "", 0}, } ApplyFixtures(t, "en.HourMinute", w, fixtok) ApplyFixturesNil(t, "on.HourMinute nil", w, fixtnil) w.Add(en.Hour(rules.Skip)) ApplyFixtures(t, "en.HourMinute|en.Hour", w, fixtok) ApplyFixturesNil(t, "on.HourMinute|en.Hour nil", w, fixtnil) w = when.New(nil) w.Add( en.Hour(rules.Override), en.HourMinute(rules.Override), ) ApplyFixtures(t, "en.Hour|en.HourMinute", w, fixtok) ApplyFixturesNil(t, "on.Hour|en.HourMinute nil", w, fixtnil) } golang-github-olebedev-when-1.1.0/rules/en/hour_test.go000066400000000000000000000012741472640151500230720ustar00rootroot00000000000000package en_test import ( "testing" "time" "github.com/olebedev/when" "github.com/olebedev/when/rules" "github.com/olebedev/when/rules/en" ) func TestHour(t *testing.T) { fixt := []Fixture{ {"5pm", 0, "5pm", 17 * time.Hour}, {"at 5 pm", 3, "5 pm", 17 * time.Hour}, {"at 5 P.", 3, "5 P.", 17 * time.Hour}, {"at 12 P.", 3, "12 P.", 12 * time.Hour}, {"at 1 P.", 3, "1 P.", 13 * time.Hour}, {"at 5 am", 3, "5 am", 5 * time.Hour}, {"at 5A", 3, "5A", 5 * time.Hour}, {"at 5A.", 3, "5A.", 5 * time.Hour}, {"5A.", 0, "5A.", 5 * time.Hour}, {"11 P.M.", 0, "11 P.M.", 23 * time.Hour}, } w := when.New(nil) w.Add(en.Hour(rules.Override)) ApplyFixtures(t, "en.Hour", w, fixt) } golang-github-olebedev-when-1.1.0/rules/en/past_time.go000066400000000000000000000055731472640151500230510ustar00rootroot00000000000000package en import ( "regexp" "strconv" "strings" "time" "github.com/AlekSi/pointer" "github.com/olebedev/when/rules" "github.com/pkg/errors" ) func PastTime(s rules.Strategy) rules.Rule { overwrite := s == rules.Override return &rules.F{ RegExp: regexp.MustCompile( "(?i)(?:\\W|^)\\s*" + "(" + INTEGER_WORDS_PATTERN + "|[0-9]+|an?(?:\\s*few)?|half(?:\\s*an?)?)\\s*" + "(seconds?|min(?:ute)?s?|hours?|days?|weeks?|months?|years?) (ago)\\s*" + "(?:\\W|$)"), Applier: func(m *rules.Match, c *rules.Context, o *rules.Options, ref time.Time) (bool, error) { numStr := strings.TrimSpace(m.Captures[0]) var num int var err error if n, ok := INTEGER_WORDS[numStr]; ok { num = n } else if numStr == "a" || numStr == "an" { num = 1 } else if strings.Contains(numStr, "few") { num = 3 } else if strings.Contains(numStr, "half") { // pass } else { num, err = strconv.Atoi(numStr) if err != nil { return false, errors.Wrapf(err, "convert '%s' to int", numStr) } } exponent := strings.TrimSpace(m.Captures[1]) if !strings.Contains(numStr, "half") { switch { case strings.Contains(exponent, "second"): if c.Duration == 0 || overwrite { c.Duration = -(time.Duration(num) * time.Second) } case strings.Contains(exponent, "min"): if c.Duration == 0 || overwrite { c.Duration = -(time.Duration(num) * time.Minute) } case strings.Contains(exponent, "hour"): if c.Duration == 0 || overwrite { c.Duration = -(time.Duration(num) * time.Hour) } case strings.Contains(exponent, "day"): if c.Duration == 0 || overwrite { c.Duration = -(time.Duration(num) * 24 * time.Hour) } case strings.Contains(exponent, "week"): if c.Duration == 0 || overwrite { c.Duration = -(time.Duration(num) * 7 * 24 * time.Hour) } case strings.Contains(exponent, "month"): if c.Month == nil || overwrite { c.Month = pointer.ToInt((int(ref.Month()) - num) % 12) } case strings.Contains(exponent, "year"): if c.Year == nil || overwrite { c.Year = pointer.ToInt(ref.Year() - num) } } } else { switch { case strings.Contains(exponent, "hour"): if c.Duration == 0 || overwrite { c.Duration = -(30 * time.Minute) } case strings.Contains(exponent, "day"): if c.Duration == 0 || overwrite { c.Duration = -(12 * time.Hour) } case strings.Contains(exponent, "week"): if c.Duration == 0 || overwrite { c.Duration = -(7 * 12 * time.Hour) } case strings.Contains(exponent, "month"): if c.Duration == 0 || overwrite { // 2 weeks c.Duration = -(14 * 24 * time.Hour) } case strings.Contains(exponent, "year"): if c.Month == nil || overwrite { c.Month = pointer.ToInt((int(ref.Month()) - 6) % 12) } } } return true, nil }, } } golang-github-olebedev-when-1.1.0/rules/en/past_time_test.go000066400000000000000000000022511472640151500240760ustar00rootroot00000000000000package en_test import ( "testing" "time" "github.com/olebedev/when" "github.com/olebedev/when/rules" "github.com/olebedev/when/rules/en" ) func TestPastTime(t *testing.T) { fixt := []Fixture{ {"half an hour ago", 0, "half an hour ago", -(time.Hour / 2)}, {"1 hour ago", 0, "1 hour ago", -(time.Hour)}, {"5 minutes ago", 0, "5 minutes ago", -(time.Minute * 5)}, {"5 minutes ago I went to the zoo", 0, "5 minutes ago", -(time.Minute * 5)}, {"we did something 10 days ago.", 17, "10 days ago", -(10 * 24 * time.Hour)}, {"we did something five days ago.", 17, "five days ago", -(5 * 24 * time.Hour)}, {"we did something 5 days ago.", 17, "5 days ago", -(5 * 24 * time.Hour)}, {"5 seconds ago a car was moved", 0, "5 seconds ago", -(5 * time.Second)}, {"two weeks ago", 0, "two weeks ago", -(14 * 24 * time.Hour)}, {"a month ago", 0, "a month ago", -(31 * 24 * time.Hour)}, {"a few months ago", 0, "a few months ago", -(92 * 24 * time.Hour)}, {"one year ago", 0, "one year ago", -(365 * 24 * time.Hour)}, {"a week ago", 0, "a week ago", -(7 * 24 * time.Hour)}, } w := when.New(nil) w.Add(en.PastTime(rules.Skip)) ApplyFixtures(t, "en.PastTime", w, fixt) } golang-github-olebedev-when-1.1.0/rules/en/weekday.go000066400000000000000000000042061472640151500225050ustar00rootroot00000000000000package en import ( "regexp" "strings" "time" "github.com/olebedev/when/rules" ) func Weekday(s rules.Strategy) rules.Rule { overwrite := s == rules.Override return &rules.F{ RegExp: regexp.MustCompile("(?i)" + "(?:\\W|^)" + "(?:on\\s*?)?" + "(?:(this|last|past|next)\\s*)?" + "(" + WEEKDAY_OFFSET_PATTERN[3:] + // skip '(?:' "(?:\\s*(this|last|past|next)\\s*week)?" + "(?:\\W|$)", ), Applier: func(m *rules.Match, c *rules.Context, o *rules.Options, ref time.Time) (bool, error) { _ = overwrite day := strings.ToLower(strings.TrimSpace(m.Captures[1])) norm := strings.ToLower(strings.TrimSpace(m.Captures[0] + m.Captures[2])) if norm == "" { norm = "next" } dayInt, ok := WEEKDAY_OFFSET[day] if !ok { return false, nil } if c.Duration != 0 && !overwrite { return false, nil } // Switch: switch { case strings.Contains(norm, "past") || strings.Contains(norm, "last"): diff := int(ref.Weekday()) - dayInt if diff > 0 { c.Duration = -time.Duration(diff*24) * time.Hour } else if diff < 0 { c.Duration = -time.Duration(7+diff) * 24 * time.Hour } else { c.Duration = -(7 * 24 * time.Hour) } case strings.Contains(norm, "next"): diff := dayInt - int(ref.Weekday()) if diff > 0 { c.Duration = time.Duration(diff*24) * time.Hour } else if diff < 0 { c.Duration = time.Duration(7+diff) * 24 * time.Hour } else { c.Duration = 7 * 24 * time.Hour } case strings.Contains(norm, "this"): if int(ref.Weekday()) < dayInt { diff := dayInt - int(ref.Weekday()) if diff > 0 { c.Duration = time.Duration(diff*24) * time.Hour } else if diff < 0 { c.Duration = time.Duration(7+diff) * 24 * time.Hour } else { c.Duration = 7 * 24 * time.Hour } } else if int(ref.Weekday()) > dayInt { diff := int(ref.Weekday()) - dayInt if diff > 0 { c.Duration = -time.Duration(diff*24) * time.Hour } else if diff < 0 { c.Duration = -time.Duration(7+diff) * 24 * time.Hour } else { c.Duration = -(7 * 24 * time.Hour) } } } return true, nil }, } } golang-github-olebedev-when-1.1.0/rules/en/weekday_test.go000066400000000000000000000020571472640151500235460ustar00rootroot00000000000000package en_test import ( "testing" "time" "github.com/olebedev/when" "github.com/olebedev/when/rules" "github.com/olebedev/when/rules/en" ) func TestWeekday(t *testing.T) { // current is Friday fixt := []Fixture{ // past/last {"do it for the past Monday", 14, "past Monday", -(2 * 24 * time.Hour)}, {"past saturday", 0, "past saturday", -(4 * 24 * time.Hour)}, {"past friday", 0, "past friday", -(5 * 24 * time.Hour)}, {"past wednesday", 0, "past wednesday", -(7 * 24 * time.Hour)}, {"past tuesday", 0, "past tuesday", -(24 * time.Hour)}, // next {"next tuesday", 0, "next tuesday", 6 * 24 * time.Hour}, {"drop me a line at next wednesday", 18, "next wednesday", 7 * 24 * time.Hour}, {"next saturday", 0, "next saturday", 3 * 24 * time.Hour}, // this {"this tuesday", 0, "this tuesday", -(24 * time.Hour)}, {"drop me a line at this wednesday", 18, "this wednesday", 0}, {"this saturday", 0, "this saturday", 3 * 24 * time.Hour}, } w := when.New(nil) w.Add(en.Weekday(rules.Override)) ApplyFixtures(t, "en.Weekday", w, fixt) } golang-github-olebedev-when-1.1.0/rules/nl/000077500000000000000000000000001472640151500205325ustar00rootroot00000000000000golang-github-olebedev-when-1.1.0/rules/nl/casual_date.go000066400000000000000000000035371472640151500233360ustar00rootroot00000000000000package nl import ( "regexp" "strings" "time" "github.com/AlekSi/pointer" "github.com/olebedev/when/rules" ) func CasualDate(s rules.Strategy) rules.Rule { overwrite := s == rules.Override return &rules.F{ RegExp: regexp.MustCompile("(?i)(?:\\W|^)(nu|vandaag|vanavond|vannacht|afgelopen\\s*nacht|morgen|gister|gisteren)(ochtend|morgen|middag|avond)?(?:\\W|$)"), Applier: func(m *rules.Match, c *rules.Context, o *rules.Options, ref time.Time) (bool, error) { lower := strings.ToLower(strings.TrimSpace(m.String())) if regexp.MustCompile("ochtend|\\s*morgen|middag|avond").MatchString(lower) { switch { case strings.Contains(lower, "ochtend"), regexp.MustCompile("(?i)(?:\\W|^)(\\s*morgen)(?:\\W|$)").MatchString(lower): if o.Morning != 0 { c.Hour = &o.Morning } else { c.Hour = pointer.ToInt(8) } case strings.Contains(lower, "middag"): if o.Afternoon != 0 { c.Hour = &o.Afternoon } else { c.Hour = pointer.ToInt(15) } case strings.Contains(lower, "avond"): if o.Evening != 0 { c.Hour = &o.Evening } else { c.Hour = pointer.ToInt(18) } } } switch { case strings.Contains(lower, "vannacht"): if c.Hour == nil && c.Minute == nil || overwrite { c.Hour = pointer.ToInt(23) c.Minute = pointer.ToInt(0) } case strings.Contains(lower, "vandaag"): // c.Hour = pointer.ToInt(18) case strings.Contains(lower, "morgen"): if c.Duration == 0 || overwrite { c.Duration += time.Hour * 24 } case strings.Contains(lower, "gister"): if c.Duration == 0 || overwrite { c.Duration -= time.Hour * 24 } case strings.Contains(lower, "afgelopen nacht"): if (c.Hour == nil && c.Duration == 0) || overwrite { c.Hour = pointer.ToInt(23) c.Duration -= time.Hour * 24 } } return true, nil }, } } golang-github-olebedev-when-1.1.0/rules/nl/casual_test.go000066400000000000000000000032111472640151500233650ustar00rootroot00000000000000package nl_test import ( "github.com/olebedev/when/rules/nl" "testing" "time" "github.com/olebedev/when" "github.com/olebedev/when/rules" ) func TestCasualDate(t *testing.T) { fixt := []Fixture{ {"De deadline is nu, ok", 15, "nu", 0}, {"De deadline is vandaag", 15, "vandaag", 0}, {"De deadline is vannacht", 15, "vannacht", 23 * time.Hour}, {"De deadline is morgenavond", 15, "morgenavond", (18 + 24) * time.Hour}, {"De deadline is gisteravond", 15, "gisteravond", -((24 - 18) * time.Hour)}, {"De deadline is gisteren", 15, "gisteren", -(time.Hour * 24)}, } w := when.New(nil) w.Add(nl.CasualDate(rules.Skip)) ApplyFixtures(t, "nl.CasualDate", w, fixt) } func TestCasualTime(t *testing.T) { fixt := []Fixture{ {"De deadline was deze morgen", 16, "deze morgen", 8 * time.Hour}, {"De deadline was tussen de middag", 16, "tussen de middag", 12 * time.Hour}, {"De deadline was deze middag", 16, "deze middag", 15 * time.Hour}, {"De deadline was deze avond", 16, "deze avond", 18 * time.Hour}, {"De deadline is donderdagavond", 15, "donderdagavond", (18 + 24) * time.Hour}, {"De deadline is vrijdagavond", 15, "vrijdagavond", (18 + 24*2) * time.Hour}, } w := when.New(nil) w.Add(nl.CasualTime(rules.Skip)) ApplyFixtures(t, "nl.CasualTime", w, fixt) } func TestCasualDateCasualTime(t *testing.T) { fixt := []Fixture{ {"De deadline is morgenmiddag", 15, "morgenmiddag", (15 + 24) * time.Hour}, {"De deadline is morgenavond", 15, "morgenavond", (18 + 24) * time.Hour}, } w := when.New(nil) w.Add( nl.CasualDate(rules.Skip), nl.CasualTime(rules.Override), ) ApplyFixtures(t, "nl.CasualDate|nl.CasualTime", w, fixt) } golang-github-olebedev-when-1.1.0/rules/nl/casual_time.go000066400000000000000000000041121472640151500233450ustar00rootroot00000000000000package nl import ( "regexp" "strings" "time" "github.com/AlekSi/pointer" "github.com/olebedev/when/rules" ) func CasualTime(s rules.Strategy) rules.Rule { overwrite := s == rules.Override return &rules.F{ RegExp: regexp.MustCompile(`(?i)(?:\W|^)((deze|tussen de |maandag|dinsdag|woensdag|donderdag|vrijdag|zaterdag|zondag| )\s*(ochtend|morgen|middag|avond))`), Applier: func(m *rules.Match, c *rules.Context, o *rules.Options, ref time.Time) (bool, error) { lower := strings.ToLower(strings.TrimSpace(m.String())) if (c.Weekday != nil || c.Hour != nil || c.Minute != nil) && !overwrite { return false, nil } if regexp.MustCompile("(maandag|dinsdag|woensdag|donderdag|vrijdag|zaterdag|zondag)").MatchString(lower) { weekday := -1 switch { case strings.Contains(lower, "maandag"): weekday = 1 case strings.Contains(lower, "dinsdag"): weekday = 2 case strings.Contains(lower, "woensdag"): weekday = 3 case strings.Contains(lower, "donderdag"): weekday = 4 case strings.Contains(lower, "vrijdag"): weekday = 5 case strings.Contains(lower, "zaterdag"): weekday = 6 case strings.Contains(lower, "zondag"): weekday = 7 } if weekday != -1 { c.Duration += time.Hour * 24 * time.Duration((weekday+7-(int(ref.Weekday())))%7) } } switch { case strings.Contains(lower, "middag") && !strings.Contains(lower, "tussen de middag"): if o.Afternoon != 0 { c.Hour = &o.Afternoon } else { c.Hour = pointer.ToInt(15) } c.Minute = pointer.ToInt(0) case strings.Contains(lower, "avond"): if o.Evening != 0 { c.Hour = &o.Evening } else { c.Hour = pointer.ToInt(18) } c.Minute = pointer.ToInt(0) case strings.Contains(lower, "ochtend"), strings.Contains(lower, "morgen"): if o.Morning != 0 { c.Hour = &o.Morning } else { c.Hour = pointer.ToInt(8) } c.Minute = pointer.ToInt(0) case strings.Contains(lower, "tussen de middag"): c.Hour = pointer.ToInt(12) c.Minute = pointer.ToInt(0) } return true, nil }, } } golang-github-olebedev-when-1.1.0/rules/nl/deadline.go000066400000000000000000000060021472640151500226240ustar00rootroot00000000000000package nl import ( "regexp" "strconv" "strings" "time" "github.com/AlekSi/pointer" "github.com/olebedev/when/rules" "github.com/pkg/errors" ) func Deadline(s rules.Strategy) rules.Rule { overwrite := s == rules.Override return &rules.F{ RegExp: regexp.MustCompile( "(?i)(?:\\W|^)(binnen|in|over|na)\\s*" + "(" + INTEGER_WORDS_PATTERN + "|[0-9]+|een(?:\\s*(paar|half|halve))?)\\s*" + "(seconden?|minuut|minuten|uur|uren|dag|dagen|week|weken|maand|maanden|jaar|jaren)\\s*" + "(?:\\W|$)"), Applier: func(m *rules.Match, c *rules.Context, o *rules.Options, ref time.Time) (bool, error) { numStr := strings.TrimSpace(m.Captures[1]) var num int var err error if n, ok := INTEGER_WORDS[numStr]; ok { num = n } else if numStr == "een" { num = 1 } else if strings.Contains(numStr, "paar") { num = 3 } else if strings.Contains(numStr, "half") || strings.Contains(numStr, "halve") { // pass } else { num, err = strconv.Atoi(numStr) if err != nil { return false, errors.Wrapf(err, "convert '%s' to int", numStr) } } exponent := strings.TrimSpace(m.Captures[3]) if !strings.Contains(numStr, "half") && !strings.Contains(numStr, "halve") { switch { case strings.Contains(exponent, "second"): if c.Duration == 0 || overwrite { c.Duration = time.Duration(num) * time.Second } case strings.Contains(exponent, "min"): if c.Duration == 0 || overwrite { c.Duration = time.Duration(num) * time.Minute } case strings.Contains(exponent, "uur"), strings.Contains(exponent, "uren"): if c.Duration == 0 || overwrite { c.Duration = time.Duration(num) * time.Hour } case strings.Contains(exponent, "dag"): if c.Duration == 0 || overwrite { c.Duration = time.Duration(num) * 24 * time.Hour } case strings.Contains(exponent, "week"), strings.Contains(exponent, "weken"): if c.Duration == 0 || overwrite { c.Duration = time.Duration(num) * 7 * 24 * time.Hour } case strings.Contains(exponent, "maand"): if c.Month == nil || overwrite { c.Month = pointer.ToInt((int(ref.Month()) + num) % 12) } case strings.Contains(exponent, "jaar"): if c.Year == nil || overwrite { c.Year = pointer.ToInt(ref.Year() + num) } } } else { switch { case strings.Contains(exponent, "uur"): if c.Duration == 0 || overwrite { c.Duration = 30 * time.Minute } case strings.Contains(exponent, "dag"): if c.Duration == 0 || overwrite { c.Duration = 12 * time.Hour } case strings.Contains(exponent, "week"): if c.Duration == 0 || overwrite { c.Duration = 7 * 12 * time.Hour } case strings.Contains(exponent, "maand"): if c.Duration == 0 || overwrite { // 2 weeks c.Duration = 14 * 24 * time.Hour } case strings.Contains(exponent, "jaar"): if c.Month == nil || overwrite { c.Month = pointer.ToInt((int(ref.Month()) + 6) % 12) } } } return true, nil }, } } golang-github-olebedev-when-1.1.0/rules/nl/deadline_test.go000066400000000000000000000024241472640151500236670ustar00rootroot00000000000000package nl_test import ( "github.com/olebedev/when/rules/nl" "testing" "time" "github.com/olebedev/when" "github.com/olebedev/when/rules" ) func TestDeadline(t *testing.T) { fixt := []Fixture{ {"binnen een half uur", 0, "binnen een half uur", time.Hour / 2}, {"binnen 1 uur", 0, "binnen 1 uur", time.Hour}, {"in 5 minuten", 0, "in 5 minuten", time.Minute * 5}, {"Binnen 5 minuten ga ik naar huis", 0, "Binnen 5 minuten", time.Minute * 5}, {"we moeten binnen 10 dagen iets doen", 10, "binnen 10 dagen", 10 * 24 * time.Hour}, {"we moeten binnen vijf dagen iets doen", 10, "binnen vijf dagen", 5 * 24 * time.Hour}, {"we moeten over 5 dagen iets doen", 10, "over 5 dagen", 5 * 24 * time.Hour}, {"In 5 seconde moet een auto verplaatsen", 0, "In 5 seconde", 5 * time.Second}, {"binnen twee weken", 0, "binnen twee weken", 14 * 24 * time.Hour}, {"binnen een maand", 0, "binnen een maand", 31 * 24 * time.Hour}, {"na een maand", 0, "na een maand", 31 * 24 * time.Hour}, {"binnen een paar maanden", 0, "binnen een paar maanden", 91 * 24 * time.Hour}, {"binnen een jaar", 0, "binnen een jaar", 366 * 24 * time.Hour}, {"in een week", 0, "in een week", 7 * 24 * time.Hour}, } w := when.New(nil) w.Add(nl.Deadline(rules.Skip)) ApplyFixtures(t, "nl.Deadline", w, fixt) } golang-github-olebedev-when-1.1.0/rules/nl/exact_month_date.go000066400000000000000000000042721472640151500243740ustar00rootroot00000000000000package nl import ( "regexp" "strconv" "strings" "time" "github.com/olebedev/when/rules" ) // <[]string{"derde van maart", "derde", "", "maart", "", ""}> // <[]string{"3e van march", "3e", "", "maart", "", ""}> // <[]string{"1e van september", "1e", "", "september", "", ""}> // <[]string{"1 sept.", "", "", "1", "sept", ""}> // <[]string{"twintigste van december", "twintigste", "", "december", "", ""}> // <[]string{"februari", "", "", "februari", "", ""}> // <[]string{"oktober", "", "", "oktober", "", ""}> // <[]string{"jul.", "", "", "jul.", "", ""}> // <[]string{"juni", "", "", "juni", "", ""}> // https://play.golang.org/p/Zfjl6ERNkq // 1. - ordinal day? // 2. - numeric day? // 3. - month // 4. - ordinal day? // 5. - ordinal day? func ExactMonthDate(s rules.Strategy) rules.Rule { overwrite := s == rules.Override return &rules.F{ RegExp: regexp.MustCompile("(?i)" + "(?:\\W|^)" + "(?:(?:(" + ORDINAL_WORDS_PATTERN[3:] + "(?:\\s+van)?|([0-9]+))\\s*)?" + "(" + MONTH_OFFSET_PATTERN[3:] + // skip '(?:' "(?:\\s*(?:(" + ORDINAL_WORDS_PATTERN[3:] + "|([0-9]+)))?" + "(?:\\W|$)", ), Applier: func(m *rules.Match, c *rules.Context, o *rules.Options, ref time.Time) (bool, error) { _ = overwrite ord1 := strings.ToLower(strings.TrimSpace(m.Captures[0])) num1 := strings.ToLower(strings.TrimSpace(m.Captures[1])) mon := strings.ToLower(strings.TrimSpace(m.Captures[2])) ord2 := strings.ToLower(strings.TrimSpace(m.Captures[3])) num2 := strings.ToLower(strings.TrimSpace(m.Captures[4])) monInt, ok := MONTH_OFFSET[mon] if !ok { return false, nil } c.Month = &monInt if ord1 != "" { ordInt, ok := ORDINAL_WORDS[ord1] if !ok { return false, nil } c.Day = &ordInt } if num1 != "" { n, err := strconv.ParseInt(num1, 10, 8) if err != nil { return false, nil } num := int(n) c.Day = &num } if ord2 != "" { ordInt, ok := ORDINAL_WORDS[ord2] if !ok { return false, nil } c.Day = &ordInt } if num2 != "" { n, err := strconv.ParseInt(num2, 10, 8) if err != nil { return false, nil } num := int(n) c.Day = &num } return true, nil }, } } golang-github-olebedev-when-1.1.0/rules/nl/exact_month_date_test.go000066400000000000000000000016251472640151500254320ustar00rootroot00000000000000package nl_test import ( "github.com/olebedev/when/rules/nl" "testing" "time" "github.com/olebedev/when" "github.com/olebedev/when/rules" ) func TestExactMonthDate(t *testing.T) { w := when.New(nil) w.Add(nl.ExactMonthDate(rules.Override)) fixtok := []Fixture{ {"derde van maart", 0, "derde van maart", 1368 * time.Hour}, {"3e van maart", 0, "3e van maart", 1368 * time.Hour}, {"1 september", 0, "1 september", 5736 * time.Hour}, {"1 sept", 0, "1 sept", 5736 * time.Hour}, {"1 sept.", 0, "1 sept.", 5736 * time.Hour}, {"1e van september", 0, "1e van september", 5736 * time.Hour}, {"twintigste van december", 0, "twintigste van december", 8376 * time.Hour}, {"februari", 0, "februari", 744 * time.Hour}, {"oktober", 0, "oktober", 6576 * time.Hour}, {"jul.", 0, "jul.", 4368 * time.Hour}, {"juni", 0, "juni", 3648 * time.Hour}, } ApplyFixtures(t, "nl.ExactMonthDate", w, fixtok) } golang-github-olebedev-when-1.1.0/rules/nl/hour.go000066400000000000000000000024711472640151500220420ustar00rootroot00000000000000package nl import ( "regexp" "strconv" "strings" "time" "github.com/pkg/errors" "github.com/olebedev/when/rules" ) /* "5u" "5 uur" "5am" "5pm" "5A." "5P." "11 P.M." https://play.golang.org/p/2Gh35Sl3KP */ func Hour(s rules.Strategy) rules.Rule { return &rules.F{ RegExp: regexp.MustCompile("(?i)(?:\\W|^)" + "(?:\\s*((om)?))" + "(\\d{1,2})" + "(?:\\s*(U\\.?|UUR|A\\.|P\\.|A\\.M\\.|P\\.M\\.|AM?|PM?))" + "(?:\\s*((in de|\\'s) (middags?|avonds?))?)" + "(?:\\W|$)"), Applier: func(m *rules.Match, c *rules.Context, o *rules.Options, ref time.Time) (bool, error) { if c.Hour != nil && s != rules.Override { return false, nil } lower := strings.ToLower(strings.TrimSpace(m.String())) hour, err := strconv.Atoi(m.Captures[2]) if err != nil { return false, errors.Wrap(err, "hour rule") } zero := 0 if hour > 23 { return false, nil } c.Hour = &hour // pm if regexp.MustCompile("p.?(m.?)?").MatchString(strings.ToLower(strings.TrimSpace(m.Captures[3]))) { if hour < 12 { hour += 12 } c.Hour = &hour } // afternoon or evening if (strings.Contains(lower, "middag") || strings.Contains(lower, "avond")) && hour < 12 { hour += 12 c.Hour = &hour } c.Minute = &zero c.Second = &zero return true, nil }, } } golang-github-olebedev-when-1.1.0/rules/nl/hour_minute.go000066400000000000000000000033671472640151500234300ustar00rootroot00000000000000package nl import ( "regexp" "strconv" "strings" "time" "github.com/olebedev/when/rules" "github.com/pkg/errors" ) /* {"17:30", 0, "17:30", 0}, {"17:30u", 0, "17:30u", 0}, {"om 17:30 uur", 3, "17:30 uur", 0}, {"om 5:59 pm", 3, "5:59 pm", 0}, https://play.golang.org/p/hXl7C8MWNr */ // 1. - at? // 2. - int // 3. - int // 4. - ext? // 5. - day part? func HourMinute(s rules.Strategy) rules.Rule { return &rules.F{ RegExp: regexp.MustCompile("(?i)(?:\\W|^)" + "(?:\\s*((om)?))" + "((?:[0-1]{0,1}[0-9])|(?:2[0-3]))" + "(?:\\:|:)" + "((?:[0-5][0-9]))" + "(?:\\s*(U\\.?|UUR|A\\.|P\\.|A\\.M\\.|P\\.M\\.|AM?|PM?))?" + "(?:\\s*((in de|\\'s) (middags?|avonds?))?)" + "(?:\\W|$)"), Applier: func(m *rules.Match, c *rules.Context, o *rules.Options, ref time.Time) (bool, error) { if (c.Hour != nil || c.Minute != nil) && s != rules.Override { return false, nil } lower := strings.ToLower(strings.TrimSpace(m.String())) hour, err := strconv.Atoi(m.Captures[2]) if err != nil { return false, errors.Wrap(err, "hour minute rule") } minutes, err := strconv.Atoi(m.Captures[3]) if err != nil { return false, errors.Wrap(err, "hour minute rule") } if minutes > 59 { return false, nil } c.Minute = &minutes if hour > 23 { return false, nil } c.Hour = &hour // pm if regexp.MustCompile("p.?(m.?)?").MatchString(strings.ToLower(strings.TrimSpace(m.Captures[4]))) { if hour < 12 { hour += 12 } c.Hour = &hour } // afternoon or evening if (strings.Contains(lower, "middag") || strings.Contains(lower, "avond")) && hour < 12 { hour += 12 c.Hour = &hour } seconds := 0 // Truncate seconds c.Second = &seconds return true, nil }, } } golang-github-olebedev-when-1.1.0/rules/nl/hour_minute_test.go000066400000000000000000000021211472640151500244520ustar00rootroot00000000000000package nl_test import ( "github.com/olebedev/when/rules/nl" "testing" "time" "github.com/olebedev/when" "github.com/olebedev/when/rules" ) func TestHourMinute(t *testing.T) { w := when.New(nil) w.Add(nl.HourMinute(rules.Override)) fixtok := []Fixture{ {"17:30u", 0, "17:30u", (17 * time.Hour) + (30 * time.Minute)}, {"om 17:30 uur", 3, "17:30 uur", (17 * time.Hour) + (30 * time.Minute)}, {"om 5:59 pm", 3, "5:59 pm", (17 * time.Hour) + (59 * time.Minute)}, {"om 5:59 am", 3, "5:59 am", (5 * time.Hour) + (59 * time.Minute)}, } fixtnil := []Fixture{ {"28:30pm", 0, "", 0}, {"12:61u", 0, "", 0}, {"24:10", 0, "", 0}, } ApplyFixtures(t, "nl.HourMinute", w, fixtok) ApplyFixturesNil(t, "on.HourMinute nil", w, fixtnil) w.Add(nl.Hour(rules.Skip)) ApplyFixtures(t, "nl.HourMinute|nl.Hour", w, fixtok) ApplyFixturesNil(t, "on.HourMinute|nl.Hour nil", w, fixtnil) w = when.New(nil) w.Add( nl.Hour(rules.Override), nl.HourMinute(rules.Override), ) ApplyFixtures(t, "nl.Hour|nl.HourMinute", w, fixtok) ApplyFixturesNil(t, "on.Hour|nl.HourMinute nil", w, fixtnil) } golang-github-olebedev-when-1.1.0/rules/nl/hour_test.go000066400000000000000000000014751472640151500231040ustar00rootroot00000000000000package nl_test import ( "github.com/olebedev/when" "github.com/olebedev/when/rules" "github.com/olebedev/when/rules/nl" "testing" "time" ) func TestHour(t *testing.T) { fixt := []Fixture{ {"5pm", 0, "5pm", 17 * time.Hour}, {"5 uur in de avond", 0, "5 uur in de avond", 17 * time.Hour}, {"5 uur 's avonds", 0, "5 uur 's avonds", 17 * time.Hour}, {"om 17 uur", 3, "17 uur", 17 * time.Hour}, {"om 5 P.", 3, "5 P.", 17 * time.Hour}, {"om 12 P.", 3, "12 P.", 12 * time.Hour}, {"om 1 P.", 3, "1 P.", 13 * time.Hour}, {"om 5 am", 3, "5 am", 5 * time.Hour}, {"om 5A", 3, "5A", 5 * time.Hour}, {"om 5A.", 3, "5A.", 5 * time.Hour}, {"5A.", 0, "5A.", 5 * time.Hour}, {"11 P.M.", 0, "11 P.M.", 23 * time.Hour}, } w := when.New(nil) w.Add(nl.Hour(rules.Override)) ApplyFixtures(t, "nl.Hour", w, fixt) } golang-github-olebedev-when-1.1.0/rules/nl/nl.go000066400000000000000000000101071472640151500214710ustar00rootroot00000000000000package nl import "github.com/olebedev/when/rules" var All = []rules.Rule{ Weekday(rules.Override), CasualDate(rules.Override), CasualTime(rules.Override), Hour(rules.Override), HourMinute(rules.Override), Deadline(rules.Override), PastTime(rules.Override), ExactMonthDate(rules.Override), } var WEEKDAY_OFFSET = map[string]int{ "zondag": 0, "zon": 0, "zo": 0, "maandag": 1, "maa": 1, "ma": 1, "dinsdag": 2, "din": 2, "di": 2, "woensdag": 3, "woe": 3, "wo": 3, "donderdag": 4, "don": 4, "do": 4, "vrijdag": 5, "vrij": 5, "vr": 5, "zaterdag": 6, "zat": 6, "za": 6, } var WEEKDAY_OFFSET_PATTERN = "(?:zondag|zon|zo|maandag|maa|ma|dinsdag|din|di|woensdag|woe|wo|donderdag|don|do|vrijdag|vrij|vr|zaterdag|zat|za)" var MONTH_OFFSET = map[string]int{ "january": 1, "jan": 1, "jan.": 1, "februari": 2, "feb": 2, "feb.": 2, "maart": 3, "mrt": 3, "mrt.": 3, "april": 4, "apr": 4, "apr.": 4, "mei": 5, "juni": 6, "jun": 6, "jun.": 6, "juli": 7, "jul": 7, "jul.": 7, "augustus": 8, "aug": 8, "aug.": 8, "september": 9, "sep": 9, "sep.": 9, "sept": 9, "sept.": 9, "oktober": 10, "okt": 10, "okt.": 10, "november": 11, "nov": 11, "nov.": 11, "december": 12, "dec": 12, "dec.": 12, } var MONTH_OFFSET_PATTERN = `(?:january|jan\.?|februari|feb\.?|maart|mrt\.?|april|apr\.?|mei|juni|jun\.?|juli|jul\.?|augustus|aug\.?|september|sept?\.?|oktober|okt\.?|november|nov\.?|december|dec\.?)` var INTEGER_WORDS = map[string]int{ "een": 1, "één": 1, "twee": 2, "drie": 3, "vier": 4, "vijf": 5, "zes": 6, "zeven": 7, "acht": 8, "negen": 9, "tien": 10, "elf": 11, "twaalf": 12, } var INTEGER_WORDS_PATTERN = `(?:een|één|twee|drie|vier|vijf|zes|zeven|acht|negen|tien|elf|twaalf)` var ORDINAL_WORDS = map[string]int{ "eerste": 1, "1e": 1, "tweede": 2, "2e": 2, "derde": 3, "3e": 3, "vierde": 4, "4e": 4, "vijfde": 5, "5e": 5, "zesde": 6, "6e": 6, "zevende": 7, "7e": 7, "achtste": 8, "8e": 8, "negende": 9, "9e": 9, "tiende": 10, "10e": 10, "elfde": 11, "11e": 11, "twaalfde": 12, "12e": 12, "derdiende": 13, "13e": 13, "veertiende": 14, "14e": 14, "vijftiende": 15, "15e": 15, "zestiende": 16, "16e": 16, "zeventiende": 17, "17e": 17, "achttiende": 18, "18e": 18, "negentiende": 19, "19e": 19, "twintigste": 20, "20e": 20, "eenentwintigste": 21, "21e": 21, "tweeentwintigste": 22, "22e": 22, "drieentwintigste": 23, "23e": 23, "vierentwintigste": 24, "24e": 24, "vijfentwintigste": 25, "25e": 25, "zesentwintigste": 26, "26e": 26, "zevenentwintigste": 27, "27e": 27, "achtentwintigste": 28, "28e": 28, "negenentwintigste": 29, "29e": 29, "dertigste": 30, "30e": 30, "eenendertigste": 31, "31e": 31, } var ORDINAL_WORDS_PATTERN = `(?:1e|eerste|2e|tweede|3e|derde|4e|vierde|5e|vijfde|6e|zesde|7e|zevende|8e|achtste|9e|negende|10e|tiende|11e|elfde|12e|twaalfde|13e|derdiende|14e|veertiende|15e|vijftiende|16e|zestiende|17e|zeventiende|18e|achttiende|19e|negentiende|20e|twintigste|21e|eenentwintigste|22e|tweeentwintigste|23e|drieentwintigste|24e|vierentwintigste|25e|vijfentwintigste|26e|zesentwintigste|27e|zevenentwintigste|28e|achtentwintigste|29e|negenentwintigste|30e|dertigste|31e|eenendertigste)` golang-github-olebedev-when-1.1.0/rules/nl/nl_test.go000066400000000000000000000043651472640151500225410ustar00rootroot00000000000000package nl_test import ( "testing" "time" "github.com/olebedev/when/rules/nl" "github.com/olebedev/when" "github.com/stretchr/testify/require" ) var null = time.Date(2016, time.January, 6, 0, 0, 0, 0, time.UTC) type Fixture struct { Text string Index int Phrase string Diff time.Duration } func ApplyFixtures(t *testing.T, name string, w *when.Parser, fixt []Fixture) { for i, f := range fixt { res, err := w.Parse(f.Text, null) require.Nil(t, err, "[%s] err #%d", name, i) require.NotNil(t, res, "[%s] res #%d", name, i) require.Equal(t, f.Index, res.Index, "[%s] index #%d", name, i) require.Equal(t, f.Phrase, res.Text, "[%s] text #%d", name, i) require.Equal(t, f.Diff, res.Time.Sub(null), "[%s] diff #%d", name, i) } } func ApplyFixturesNil(t *testing.T, name string, w *when.Parser, fixt []Fixture) { for i, f := range fixt { res, err := w.Parse(f.Text, null) require.Nil(t, err, "[%s] err #%d", name, i) require.Nil(t, res, "[%s] res #%d", name, i) } } func ApplyFixturesErr(t *testing.T, name string, w *when.Parser, fixt []Fixture) { for i, f := range fixt { _, err := w.Parse(f.Text, null) require.NotNil(t, err, "[%s] err #%d", name, i) require.Equal(t, f.Phrase, err.Error(), "[%s] err text #%d", name, i) } } func TestAll(t *testing.T) { w := when.New(nil) w.Add(nl.All...) // complex cases fixt := []Fixture{ {"vorige week zondag om 10:00", 0, "vorige week zondag om 10:00", ((-3 * 24) + 10) * time.Hour}, {"vanavond om 23:10", 0, "vanavond om 23:10", (23 * time.Hour) + (10 * time.Minute)}, {"op vrijdagmiddag", 3, "vrijdagmiddag", ((2 * 24) + 15) * time.Hour}, {"komende dinsdag om 14:00", 0, "komende dinsdag om 14:00", ((6 * 24) + 14) * time.Hour}, {"komende dinsdag 2 uur 's middags", 0, "komende dinsdag 2 uur 's middags", ((6 * 24) + 14) * time.Hour}, {"komende woensdag om 14:25", 0, "komende woensdag om 14:25", (((7 * 24) + 14) * time.Hour) + (25 * time.Minute)}, {"om 11 uur afgelopen dinsdag", 3, "11 uur afgelopen dinsdag", -13 * time.Hour}, {"volgende week dinsdag om 18:15", 0, "volgende week dinsdag om 18:15", (((6 * 24) + 18) * time.Hour) + (15 * time.Minute)}, {"volgende week vrijdag", 0, "volgende week vrijdag", (9 * 24) * time.Hour}, } ApplyFixtures(t, "nl.All...", w, fixt) } golang-github-olebedev-when-1.1.0/rules/nl/past_time.go000066400000000000000000000060721472640151500230530ustar00rootroot00000000000000package nl import ( "regexp" "strconv" "strings" "time" "github.com/AlekSi/pointer" "github.com/olebedev/when/rules" "github.com/pkg/errors" ) func PastTime(s rules.Strategy) rules.Rule { overwrite := s == rules.Override return &rules.F{ RegExp: regexp.MustCompile( "(?i)(?:\\W|^)\\s*" + "(" + INTEGER_WORDS_PATTERN + "|[0-9]+|een(?:\\s*(paar|half|halve))?)\\s*" + "(seconden?|minuut|minuten|uur|uren|dag|dagen|week|weken|maand|maanden|jaar|jaren) (geleden)\\s*" + "(?:\\W|$)"), Applier: func(m *rules.Match, c *rules.Context, o *rules.Options, ref time.Time) (bool, error) { numStr := strings.TrimSpace(m.Captures[0]) var num int var err error if n, ok := INTEGER_WORDS[numStr]; ok { num = n } else if numStr == "een" { num = 1 } else if strings.Contains(numStr, "paar") { num = 3 } else if strings.Contains(numStr, "half") || strings.Contains(numStr, "halve") { // pass } else { num, err = strconv.Atoi(numStr) if err != nil { return false, errors.Wrapf(err, "convert '%s' to int", numStr) } } exponent := strings.TrimSpace(m.Captures[2]) if !strings.Contains(numStr, "half") && !strings.Contains(numStr, "halve") { switch { case strings.Contains(exponent, "seconde"): if c.Duration == 0 || overwrite { c.Duration = -(time.Duration(num) * time.Second) } case strings.Contains(exponent, "min"): if c.Duration == 0 || overwrite { c.Duration = -(time.Duration(num) * time.Minute) } case strings.Contains(exponent, "uur"), strings.Contains(exponent, "uren"): if c.Duration == 0 || overwrite { c.Duration = -(time.Duration(num) * time.Hour) } case strings.Contains(exponent, "dag"): if c.Duration == 0 || overwrite { c.Duration = -(time.Duration(num) * 24 * time.Hour) } case strings.Contains(exponent, "week"), strings.Contains(exponent, "weken"): if c.Duration == 0 || overwrite { c.Duration = -(time.Duration(num) * 7 * 24 * time.Hour) } case strings.Contains(exponent, "maand"): if c.Month == nil || overwrite { c.Month = pointer.ToInt((int(ref.Month()) - num) % 12) } case strings.Contains(exponent, "jaar"): if c.Year == nil || overwrite { c.Year = pointer.ToInt(ref.Year() - num) } } } else { switch { case strings.Contains(exponent, "uur"), strings.Contains(exponent, "uren"): if c.Duration == 0 || overwrite { c.Duration = -(30 * time.Minute) } case strings.Contains(exponent, "dag"): if c.Duration == 0 || overwrite { c.Duration = -(12 * time.Hour) } case strings.Contains(exponent, "week"): if c.Duration == 0 || overwrite { c.Duration = -(7 * 12 * time.Hour) } case strings.Contains(exponent, "maand"): if c.Duration == 0 || overwrite { // 2 weeks c.Duration = -(14 * 24 * time.Hour) } case strings.Contains(exponent, "jaar"): if c.Month == nil || overwrite { c.Month = pointer.ToInt((int(ref.Month()) - 6) % 12) } } } return true, nil }, } } golang-github-olebedev-when-1.1.0/rules/nl/past_time_test.go000066400000000000000000000024601472640151500241070ustar00rootroot00000000000000package nl_test import ( "github.com/olebedev/when/rules/nl" "testing" "time" "github.com/olebedev/when" "github.com/olebedev/when/rules" ) func TestPastTime(t *testing.T) { fixt := []Fixture{ {"een half uur geleden", 0, "een half uur geleden", -(time.Hour / 2)}, {"1 uur geleden", 0, "1 uur geleden", -(time.Hour)}, {"5 minuten geleden", 0, "5 minuten geleden", -(time.Minute * 5)}, {"5 minuten geleden ging ik naar de dierentuin", 0, "5 minuten geleden", -(time.Minute * 5)}, {"we deden iets 10 dagen geleden", 14, "10 dagen geleden", -(10 * 24 * time.Hour)}, {"we deden iets vijf dagen geleden", 14, "vijf dagen geleden", -(5 * 24 * time.Hour)}, {"we deden iets 5 dagen geleden", 14, "5 dagen geleden", -(5 * 24 * time.Hour)}, {"5 seconden geleden werd een auto weggesleept", 0, "5 seconden geleden", -(5 * time.Second)}, {"twee weken geleden", 0, "twee weken geleden", -(14 * 24 * time.Hour)}, {"een maand geleden", 0, "een maand geleden", -(31 * 24 * time.Hour)}, {"een paar maanden geleden", 0, "een paar maanden geleden", -(92 * 24 * time.Hour)}, {"een jaar geleden", 0, "een jaar geleden", -(365 * 24 * time.Hour)}, {"een week geleden", 0, "een week geleden", -(7 * 24 * time.Hour)}, } w := when.New(nil) w.Add(nl.PastTime(rules.Skip)) ApplyFixtures(t, "nl.PastTime", w, fixt) } golang-github-olebedev-when-1.1.0/rules/nl/weekday.go000066400000000000000000000054631472640151500225220ustar00rootroot00000000000000package nl import ( "regexp" "strings" "time" "github.com/olebedev/when/rules" ) func Weekday(s rules.Strategy) rules.Rule { overwrite := s == rules.Override return &rules.F{ RegExp: regexp.MustCompile("(?i)" + "(?:\\W|^)" + "(?:op\\s*?)?" + "(?:(deze|vorige|vorige week|afgelopen|volgende|volgende week|komende|komende week)\\s*)?" + "(" + WEEKDAY_OFFSET_PATTERN[3:] + // skip '(?:' "(?:\\s*(deze|vorige|afgelopen|volgende|komende)\\s*week)?" + "(?:\\W|$)", ), Applier: func(m *rules.Match, c *rules.Context, o *rules.Options, ref time.Time) (bool, error) { _ = overwrite day := strings.ToLower(strings.TrimSpace(m.Captures[1])) norm := strings.ToLower(strings.TrimSpace(m.Captures[0] + m.Captures[2])) if norm == "" { norm = "volgende" } dayInt, ok := WEEKDAY_OFFSET[day] if !ok { return false, nil } if c.Duration != 0 && !overwrite { return false, nil } // Switch: switch { case strings.Contains(norm, "vorige week"): if dayInt == 6 { dayInt = -1 } diff := int(ref.Weekday()) - dayInt if diff != 0 && dayInt <= 0 { c.Duration = -time.Duration(diff) * 24 * time.Hour } else if diff != 0 { c.Duration = -time.Duration(7+diff) * 24 * time.Hour } else { c.Duration = -(7 * 24 * time.Hour) } case strings.Contains(norm, "afgelopen") || strings.Contains(norm, "vorige"): diff := int(ref.Weekday()) - dayInt if diff > 0 { c.Duration = -time.Duration(diff*24) * time.Hour } else if diff < 0 { c.Duration = -time.Duration(7+diff) * 24 * time.Hour } else { c.Duration = -(7 * 24 * time.Hour) } case strings.Contains(norm, "volgende week"): if dayInt == 0 { dayInt = 7 } diff := dayInt - int(ref.Weekday()) c.Duration = time.Duration(7+diff) * 24 * time.Hour case strings.Contains(norm, "volgende"), strings.Contains(norm, "komende"): diff := dayInt - int(ref.Weekday()) if diff > 0 { c.Duration = time.Duration(diff) * 24 * time.Hour } else if diff < 0 { c.Duration = time.Duration(7+diff) * 24 * time.Hour } else { c.Duration = 7 * 24 * time.Hour } case strings.Contains(norm, "deze"): if int(ref.Weekday()) < dayInt { diff := dayInt - int(ref.Weekday()) if diff > 0 { c.Duration = time.Duration(diff) * 24 * time.Hour } else if diff < 0 { c.Duration = time.Duration(7+diff) * 24 * time.Hour } else { c.Duration = 7 * 24 * time.Hour } } else if int(ref.Weekday()) > dayInt { diff := int(ref.Weekday()) - dayInt if diff > 0 { c.Duration = -time.Duration(diff) * 24 * time.Hour } else if diff < 0 { c.Duration = -time.Duration(7+diff) * 24 * time.Hour } else { c.Duration = -(7 * 24 * time.Hour) } } } return true, nil }, } } golang-github-olebedev-when-1.1.0/rules/nl/weekday_test.go000066400000000000000000000046701472640151500235600ustar00rootroot00000000000000package nl_test import ( "testing" "time" "github.com/olebedev/when/rules/nl" "github.com/olebedev/when" "github.com/olebedev/when/rules" ) func TestWeekday(t *testing.T) { // current is Wednesday fixt := []Fixture{ // past week {"vorige week maandag", 0, "vorige week maandag", -(9 * 24 * time.Hour)}, {"vorige week dinsdag", 0, "vorige week dinsdag", -(8 * 24 * time.Hour)}, {"vorige week woensdag", 0, "vorige week woensdag", -(7 * 24 * time.Hour)}, {"vorige week donderdag", 0, "vorige week donderdag", -(6 * 24 * time.Hour)}, {"vorige week vrijdag", 0, "vorige week vrijdag", -(5 * 24 * time.Hour)}, {"vorige week zaterdag", 0, "vorige week zaterdag", -(4 * 24 * time.Hour)}, {"vorige week zondag", 0, "vorige week zondag", -(3 * 24 * time.Hour)}, // past/last {"doe het voor afgelopen maandag", 13, "afgelopen maandag", -(2 * 24 * time.Hour)}, {"afgelopen zaterdag", 0, "afgelopen zaterdag", -(4 * 24 * time.Hour)}, {"afgelopen vrijdag", 0, "afgelopen vrijdag", -(5 * 24 * time.Hour)}, {"afgelopen woensdag", 0, "afgelopen woensdag", -(7 * 24 * time.Hour)}, {"afgelopen dinsdag", 0, "afgelopen dinsdag", -(24 * time.Hour)}, // next week {"volgende week maandag", 0, "volgende week maandag", 5 * 24 * time.Hour}, {"volgende week dinsdag", 0, "volgende week dinsdag", 6 * 24 * time.Hour}, {"volgende week woensdag", 0, "volgende week woensdag", 7 * 24 * time.Hour}, {"volgende week donderdag", 0, "volgende week donderdag", 8 * 24 * time.Hour}, {"volgende week vrijdag", 0, "volgende week vrijdag", 9 * 24 * time.Hour}, {"volgende week zaterdag", 0, "volgende week zaterdag", 10 * 24 * time.Hour}, {"volgende week zondag", 0, "volgende week zondag", 11 * 24 * time.Hour}, // next {"komende dinsdag", 0, "komende dinsdag", 6 * 24 * time.Hour}, {"stuur me een bericht komende woensdag", 21, "komende woensdag", 7 * 24 * time.Hour}, {"komende zaterdag", 0, "komende zaterdag", 3 * 24 * time.Hour}, {"volgende dinsdag", 0, "volgende dinsdag", 6 * 24 * time.Hour}, {"stuur me een bericht volgende woensdag", 21, "volgende woensdag", 7 * 24 * time.Hour}, {"volgende zaterdag", 0, "volgende zaterdag", 3 * 24 * time.Hour}, // this {"deze dinsdag", 0, "deze dinsdag", -(24 * time.Hour)}, {"stuur me een bericht deze woensdag", 21, "deze woensdag", 0}, {"deze zaterdag", 0, "deze zaterdag", 3 * 24 * time.Hour}, } w := when.New(nil) w.Add(nl.Weekday(rules.Override)) ApplyFixtures(t, "nl.Weekday", w, fixt) } golang-github-olebedev-when-1.1.0/rules/ru/000077500000000000000000000000001472640151500205475ustar00rootroot00000000000000golang-github-olebedev-when-1.1.0/rules/ru/casual_date.go000066400000000000000000000016331472640151500233460ustar00rootroot00000000000000package ru import ( "regexp" "strings" "time" "github.com/olebedev/when/rules" ) // https://play.golang.org/p/QrFtjmjUoJ func CasualDate(s rules.Strategy) rules.Rule { return &rules.F{ RegExp: regexp.MustCompile("(?i)(?:\\P{L}|^)" + "((?:до|прямо)\\s+)?" + "(сейчас|сегодня|завтра|вчера)" + "(?:\\P{L}|$)"), Applier: func(m *rules.Match, c *rules.Context, o *rules.Options, ref time.Time) (bool, error) { lower := strings.ToLower(strings.TrimSpace(m.String())) switch { case strings.Contains(lower, "сегодня"): // c.Hour = pointer.ToInt(18) case strings.Contains(lower, "завтра"): if c.Duration == 0 || s == rules.Override { c.Duration += time.Hour * 24 } case strings.Contains(lower, "вчера"): if c.Duration == 0 || s == rules.Override { c.Duration -= time.Hour * 24 } } return true, nil }, } } golang-github-olebedev-when-1.1.0/rules/ru/casual_test.go000066400000000000000000000054651472640151500234170ustar00rootroot00000000000000package ru_test import ( "testing" "time" "github.com/olebedev/when" "github.com/olebedev/when/rules" "github.com/olebedev/when/rules/ru" ) func TestCasualDate(t *testing.T) { fixt := []Fixture{ {"Это нужно сделать прямо сейчас", 33, "прямо сейчас", 0}, {"Это нужно сделать сегодня", 33, "сегодня", 0}, {"Это нужно сделать завтра вечером", 33, "завтра", time.Hour * 24}, {"Это нужно было сделать вчера вечером", 42, "вчера", -(time.Hour * 24)}, {"Это нужно сделать до завтра", 33, "до завтра", time.Hour * 24}, } w := when.New(nil) w.Add(ru.CasualDate(rules.Skip)) ApplyFixtures(t, "ru.CasualDate", w, fixt) } func TestCasualTime(t *testing.T) { fixt := []Fixture{ {"Это нужно было сделать этим утром ", 42, "этим утром", 8 * time.Hour}, {"Это нужно сделать до обеда", 33, "до обеда", 12 * time.Hour}, {"Это нужно сделать после обеда", 33, "после обеда", 15 * time.Hour}, {"Это нужно сделать к вечеру", 33, "к вечеру", 18 * time.Hour}, {"вечером", 0, "вечером", 18 * time.Hour}, {"вечером", 0, "вечером", 18 * time.Hour}, } w := when.New(nil) w.Add(ru.CasualTime(rules.Skip)) ApplyFixtures(t, "ru.CasualTime", w, fixt) } func TestCasualDateCasualTime(t *testing.T) { fixt := []Fixture{ {"Это нужно сделать завтра после обеда", 33, "завтра после обеда", (15 + 24) * time.Hour}, {"Это нужно сделать завтра утром", 33, "завтра утром", (8 + 24) * time.Hour}, {"Это нужно было сделать вчера утром", 42, "вчера утром", (8 - 24) * time.Hour}, {"Это нужно было сделать вчера после обеда", 42, "вчера после обеда", (15 - 24) * time.Hour}, {"помыть окна до вечера", 22, "до вечера", 18 * time.Hour}, {"помыть окна до обеда", 22, "до обеда", 12 * time.Hour}, {"сделать это к вечеру", 22, "к вечеру", 18 * time.Hour}, {"помыть окна завтра утром", 22, "завтра утром", 32 * time.Hour}, {"написать письмо во вторник после обеда", 50, "после обеда", 15 * time.Hour}, {"написать письмо до утра ", 30, "до утра", 8 * time.Hour}, {"к вечеру", 0, "к вечеру", 18 * time.Hour}, } w := when.New(nil) w.Add( ru.CasualDate(rules.Skip), ru.CasualTime(rules.Override), ) ApplyFixtures(t, "ru.CasualDate|ru.CasualTime", w, fixt) } golang-github-olebedev-when-1.1.0/rules/ru/casual_time.go000066400000000000000000000026161472640151500233710ustar00rootroot00000000000000package ru import ( "regexp" "strings" "time" "github.com/AlekSi/pointer" "github.com/olebedev/when/rules" ) // https://play.golang.org/p/IUbYhm7Nu- func CasualTime(s rules.Strategy) rules.Rule { return &rules.F{ RegExp: regexp.MustCompile(`(?i)(?:\P{L}|^)((это|этим|этот|этим|до|к|после)?\s*(утр(?:ом|а|у)|вечер(?:у|ом|а)|обеда?))(?:\P{L}|$)`), Applier: func(m *rules.Match, c *rules.Context, o *rules.Options, ref time.Time) (bool, error) { lower := strings.ToLower(strings.TrimSpace(m.String())) if (c.Hour != nil || c.Minute != nil) && s == rules.Override { return false, nil } switch { case strings.Contains(lower, "после обеда"): if o.Afternoon != 0 { c.Hour = &o.Afternoon } else { c.Hour = pointer.ToInt(15) } c.Minute = pointer.ToInt(0) case strings.Contains(lower, "вечер"): if o.Evening != 0 { c.Hour = &o.Evening } else { c.Hour = pointer.ToInt(18) } c.Minute = pointer.ToInt(0) case strings.Contains(lower, "утр"): if o.Morning != 0 { c.Hour = &o.Morning } else { c.Hour = pointer.ToInt(8) } c.Minute = pointer.ToInt(0) case strings.Contains(lower, "обед"): if o.Noon != 0 { c.Hour = &o.Noon } else { c.Hour = pointer.ToInt(12) } c.Minute = pointer.ToInt(0) } return true, nil }, } } golang-github-olebedev-when-1.1.0/rules/ru/date.go000066400000000000000000000027671472640151500220270ustar00rootroot00000000000000package ru import ( "regexp" "strconv" "strings" "time" "github.com/olebedev/when/rules" "github.com/pkg/errors" ) // https://go.dev/play/p/YsVdaraCwIP func Date(s rules.Strategy) rules.Rule { return &rules.F{ RegExp: regexp.MustCompile(`(?i)(?:\b|^)(\d{1,2})\s*(` + MONTHS_PATTERN + `)(?:\s*(\d{4}))?(?:\s*в\s*(\d{1,2}):(\d{2}))?(?:\b|$)`), Applier: func(m *rules.Match, c *rules.Context, o *rules.Options, ref time.Time) (bool, error) { if (c.Day != nil || c.Month != nil || c.Year != nil) || s != rules.Override { return false, nil } day, err := strconv.Atoi(m.Captures[0]) if err != nil { return false, errors.Wrap(err, "date rule: day") } month, ok := MONTHS[strings.ToLower(m.Captures[1])] if !ok { return false, errors.New("date rule: invalid month") } year := time.Now().Year() if m.Captures[2] != "" { year, err = strconv.Atoi(m.Captures[2]) if err != nil { return false, errors.Wrap(err, "date rule: year") } } hour, minute := 0, 0 if m.Captures[3] != "" && m.Captures[4] != "" { hour, err = strconv.Atoi(m.Captures[3]) if err != nil { return false, errors.Wrap(err, "date rule: hour") } minute, err = strconv.Atoi(m.Captures[4]) if err != nil { return false, errors.Wrap(err, "date rule: minute") } } c.Day = &day c.Month = pointerToInt(int(month)) c.Year = &year c.Hour = &hour c.Minute = &minute return true, nil }, } } func pointerToInt(v int) *int { return &v } golang-github-olebedev-when-1.1.0/rules/ru/date_test.go000066400000000000000000000026071472640151500230570ustar00rootroot00000000000000package ru_test import ( "github.com/olebedev/when" "github.com/olebedev/when/rules" "github.com/olebedev/when/rules/ru" "testing" "time" ) func TestDate(t *testing.T) { w := when.New(nil) w.Add(ru.Date(rules.Override)) fixt := []Fixture{ // Simple dates {"встреча 15 января 2024", 15, "15 января 2024", time.Date(2024, 1, 15, 0, 0, 0, 0, time.UTC).Sub(null)}, {"5 марта 2025 запланирована встреча", 0, "5 марта 2025", time.Date(2025, 3, 5, 0, 0, 0, 0, time.UTC).Sub(null)}, {"31 декабря 2023", 0, "31 декабря 2023", time.Date(2023, 12, 31, 0, 0, 0, 0, time.UTC).Sub(null)}, // Dates with time {"15 января 2024 в 9:30", 0, "15 января 2024 в 9:30", time.Date(2024, 1, 15, 9, 30, 0, 0, time.UTC).Sub(null)}, {"5 марта 2025 в 15:00 запланирована встреча", 0, "5 марта 2025 в 15:00", time.Date(2025, 3, 5, 15, 0, 0, 0, time.UTC).Sub(null)}, {"31 декабря 2023 в 23:59", 0, "31 декабря 2023 в 23:59", time.Date(2023, 12, 31, 23, 59, 0, 0, time.UTC).Sub(null)}, } ApplyFixtures(t, "ru.Date", w, fixt) } func TestDateNil(t *testing.T) { w := when.New(nil) w.Add(ru.Date(rules.Override)) fixt := []Fixture{ {"это текст без даты", 0, "", 0}, {"15", 0, "", 0}, {"15 чего-то", 0, "", 0}, } ApplyFixturesNil(t, "ru.Date nil", w, fixt) } golang-github-olebedev-when-1.1.0/rules/ru/deadline.go000066400000000000000000000053361472640151500226520ustar00rootroot00000000000000package ru import ( "regexp" "strconv" "strings" "time" "github.com/AlekSi/pointer" "github.com/olebedev/when/rules" "github.com/pkg/errors" ) // https://play.golang.org/p/A-cF_q9U34 func Deadline(s rules.Strategy) rules.Rule { return &rules.F{ RegExp: regexp.MustCompile("(?i)(?:\\P{L}|^)" + "(в\\sтечении|за|через)\\s*" + "(" + INTEGER_WORDS_PATTERN + "|[0-9]+|полу?|несколько|нескольких)?\\s*" + "(секунд(?:у|ы)?|минут(?:у|ы)?|час(?:а|ов)?|день|дня|дней|недел(?:я|ь|и|ю)|месяц(?:а|ев)?|год(?:а)?|лет)\\s*" + "(?:\\P{L}|$)"), Applier: func(m *rules.Match, c *rules.Context, o *rules.Options, ref time.Time) (bool, error) { if c.Duration != 0 && s != rules.Override { return false, nil } numStr := strings.TrimSpace(m.Captures[1]) var num int var err error if n, ok := INTEGER_WORDS[numStr]; ok { num = n } else if numStr == "" { num = 1 } else if strings.Contains(numStr, "неск") { num = 3 } else if strings.Contains(numStr, "пол") { // pass } else { num, err = strconv.Atoi(numStr) if err != nil { return false, errors.Wrapf(err, "convert '%s' to int", numStr) } } exponent := strings.TrimSpace(m.Captures[2]) if !strings.Contains(numStr, "пол") { switch { case strings.Contains(exponent, "секунд"): c.Duration = time.Duration(num) * time.Second case strings.Contains(exponent, "мин"): c.Duration = time.Duration(num) * time.Minute case strings.Contains(exponent, "час"): c.Duration = time.Duration(num) * time.Hour case strings.Contains(exponent, "дн") || strings.Contains(exponent, "день"): c.Duration = time.Duration(num) * 24 * time.Hour case strings.Contains(exponent, "недел"): c.Duration = time.Duration(num) * 7 * 24 * time.Hour case strings.Contains(exponent, "месяц"): c.Month = pointer.ToInt((int(ref.Month()) + num) % 12) case strings.Contains(exponent, "год") || strings.Contains(exponent, "лет"): c.Year = pointer.ToInt(ref.Year() + num) } } else { switch { case strings.Contains(exponent, "час"): c.Duration = 30 * time.Minute case strings.Contains(exponent, "дн") || strings.Contains(exponent, "день"): c.Duration = 12 * time.Hour case strings.Contains(exponent, "недел"): c.Duration = 7 * 12 * time.Hour case strings.Contains(exponent, "месяц"): // 2 weeks c.Duration = 14 * 24 * time.Hour case strings.Contains(exponent, "год") || strings.Contains(exponent, "лет"): c.Month = pointer.ToInt((int(ref.Month()) + 6) % 12) } } return true, nil }, } } golang-github-olebedev-when-1.1.0/rules/ru/deadline_test.go000066400000000000000000000034441472640151500237070ustar00rootroot00000000000000package ru_test import ( "testing" "time" "github.com/olebedev/when" "github.com/olebedev/when/rules" "github.com/olebedev/when/rules/ru" ) func TestDeadline(t *testing.T) { fixt := []Fixture{ {"нужно сделать это в течении получаса", 33, "в течении получаса", time.Hour / 2}, {"нужно сделать это в течении одного часа", 33, "в течении одного часа", time.Hour}, {"нужно сделать это за один час", 33, "за один час", time.Hour}, {"за 5 минут", 0, "за 5 минут", time.Minute * 5}, {"Через 5 минут я пойду домой.", 0, "Через 5 минут", time.Minute * 5}, {"Нам необходимо сделать это за 10 дней.", 50, "за 10 дней", 10 * 24 * time.Hour}, {"Нам необходимо сделать это за пять дней.", 50, "за пять дней", 5 * 24 * time.Hour}, {"Нам необходимо сделать это через 5 дней.", 50, "через 5 дней", 5 * 24 * time.Hour}, {"Через 5 секунд нужно убрать машину", 0, "Через 5 секунд", 5 * time.Second}, {"за две недели", 0, "за две недели", 14 * 24 * time.Hour}, {"через месяц", 0, "через месяц", 31 * 24 * time.Hour}, {"за месяц", 0, "за месяц", 31 * 24 * time.Hour}, {"за несколько месяцев", 0, "за несколько месяцев", 91 * 24 * time.Hour}, {"за один год", 0, "за один год", 366 * 24 * time.Hour}, {"за неделю", 0, "за неделю", 7 * 24 * time.Hour}, } w := when.New(nil) w.Add(ru.Deadline(rules.Skip)) ApplyFixtures(t, "ru.Deadline", w, fixt) } golang-github-olebedev-when-1.1.0/rules/ru/dot_date_time.go000066400000000000000000000027661472640151500237120ustar00rootroot00000000000000package ru import ( "regexp" "strconv" "time" "github.com/olebedev/when/rules" "github.com/pkg/errors" ) // https://go.dev/play/p/vRzLhHHupUJ func DotDateTime(s rules.Strategy) rules.Rule { return &rules.F{ RegExp: regexp.MustCompile(`(?i)(?:^|\b)(\d{2})\.(\d{2})\.(\d{4})(?:\s+(\d{2}):(\d{2}))?(?:\b|$)`), Applier: func(m *rules.Match, c *rules.Context, o *rules.Options, ref time.Time) (bool, error) { if (c.Day != nil || c.Month != nil || c.Year != nil || c.Hour != nil || c.Minute != nil) && s != rules.Override { return false, nil } day, err := strconv.Atoi(m.Captures[0]) if err != nil { return false, errors.Wrap(err, "dot date time rule: day") } month, err := strconv.Atoi(m.Captures[1]) if err != nil { return false, errors.Wrap(err, "dot date time rule: month") } year, err := strconv.Atoi(m.Captures[2]) if err != nil { return false, errors.Wrap(err, "dot date time rule: year") } hour, minute := 0, 0 if m.Captures[3] != "" && m.Captures[4] != "" { hour, err = strconv.Atoi(m.Captures[3]) if err != nil { return false, errors.Wrap(err, "dot date time rule: hour") } minute, err = strconv.Atoi(m.Captures[4]) if err != nil { return false, errors.Wrap(err, "dot date time rule: minute") } } if day > 0 && day <= 31 && month > 0 && month <= 12 { c.Day = &day c.Month = &month c.Year = &year c.Hour = &hour c.Minute = &minute return true, nil } return false, nil }, } } golang-github-olebedev-when-1.1.0/rules/ru/dot_date_time_test.go000066400000000000000000000021441472640151500247370ustar00rootroot00000000000000package ru_test import ( "github.com/olebedev/when" "github.com/olebedev/when/rules" "github.com/olebedev/when/rules/ru" "testing" "time" ) func TestDotDateTime(t *testing.T) { w := when.New(nil) w.Add(ru.DotDateTime(rules.Override)) fixt := []Fixture{ // Basic date/time formats {"встреча 15.01.2024 09:30", 15, "15.01.2024 09:30", time.Date(2024, 1, 15, 9, 30, 0, 0, time.UTC).Sub(null)}, {"05.03.2025 15:00 запланирована встреча", 0, "05.03.2025 15:00", time.Date(2025, 3, 5, 15, 0, 0, 0, time.UTC).Sub(null)}, {"31.12.2023 23:59", 0, "31.12.2023 23:59", time.Date(2023, 12, 31, 23, 59, 0, 0, time.UTC).Sub(null)}, } ApplyFixtures(t, "ru.DateTime", w, fixt) } func TestDotDateTimeNil(t *testing.T) { w := when.New(nil) w.Add(ru.DotDateTime(rules.Override)) fixt := []Fixture{ {"это текст без даты и времени", 0, "", 0}, {"15.01", 0, "", 0}, {"32.01.2024 15:00", 0, "", 0}, // некорректный день {"15.13.2024 15:00", 0, "", 0}, // некорректный месяц } ApplyFixturesNil(t, "ru.DateTime nil", w, fixt) } golang-github-olebedev-when-1.1.0/rules/ru/hour.go000066400000000000000000000021701472640151500220530ustar00rootroot00000000000000package ru import ( "regexp" "strconv" "time" "github.com/olebedev/when/rules" "github.com/pkg/errors" ) /* "5pm" "5 pm" "5am" "5pm" "5A." "5P." "11 P.M." https://play.golang.org/p/w2PeQ3l_rp */ func Hour(s rules.Strategy) rules.Rule { return &rules.F{ RegExp: regexp.MustCompile("(?i)(?:\\W|^)" + "(" + INTEGER_WORDS_PATTERN + "|\\d{1,2})" + "(?:\\s*час(?:а|ов|ам)?)?(?:\\s*(утра|вечера|дня))" + "(?:\\P{L}|$)"), Applier: func(m *rules.Match, c *rules.Context, o *rules.Options, ref time.Time) (bool, error) { if c.Hour != nil && s != rules.Override { return false, nil } var hour int var err error if n, ok := INTEGER_WORDS[m.Captures[0]]; ok { hour = n } else { hour, err = strconv.Atoi(m.Captures[0]) if err != nil { return false, errors.Wrap(err, "hour rule") } } if hour > 12 { return false, nil } zero := 0 switch m.Captures[1] { case "утра": c.Hour = &hour case "вечера", "дня": if hour < 12 { hour += 12 } c.Hour = &hour } c.Minute = &zero return true, nil }, } } golang-github-olebedev-when-1.1.0/rules/ru/hour_minute.go000066400000000000000000000030721472640151500234360ustar00rootroot00000000000000package ru import ( "regexp" "strconv" "time" "github.com/olebedev/when/rules" "github.com/pkg/errors" ) /* {"5:30pm", 0, "5:30pm", 0}, {"5:30 pm", 0, "5:30 pm", 0}, {"7-10pm", 0, "7-10pm", 0}, {"5-30", 0, "5-30", 0}, {"05:30pm", 0, "05:30pm", 0}, {"05:30 pm", 0, "05:30 pm", 0}, {"05:30", 0, "05:30", 0}, {"05-30", 0, "05-30", 0}, {"7-10 pm", 0, "7-10 pm", 0}, {"11.1pm", 0, "11.1pm", 0}, {"11.10 pm", 0, "11.10 pm", 0}, https://go.dev/play/p/QiSvUkrni6N */ // 1. - int // 2. - int // 3. - ext? func HourMinute(s rules.Strategy) rules.Rule { return &rules.F{ RegExp: regexp.MustCompile("(?i)(?:\\A|\\s|\\D)" + "((?:[0-1]{0,1}[0-9])|(?:2[0-3]))" + "(?:\\:|:|\\-|\\.)" + "((?:[0-5][0-9]))" + "(?:\\s*(утра|вечера|дня))?" + "(?:\\s|\\D|\\z)"), Applier: func(m *rules.Match, c *rules.Context, o *rules.Options, ref time.Time) (bool, error) { if (c.Hour != nil || c.Minute != nil) && s != rules.Override { return false, nil } hour, err := strconv.Atoi(m.Captures[0]) if err != nil { return false, errors.Wrap(err, "hour minute rule") } minutes, err := strconv.Atoi(m.Captures[1]) if err != nil { return false, errors.Wrap(err, "hour minute rule") } c.Minute = &minutes if m.Captures[2] != "" { if hour > 12 { return false, nil } switch m.Captures[2] { case "утра": // am c.Hour = &hour case "вечера", "дня": // pm if hour < 12 { hour += 12 } c.Hour = &hour } } else { c.Hour = &hour } return true, nil }, } } golang-github-olebedev-when-1.1.0/rules/ru/hour_minute_test.go000066400000000000000000000025601472640151500244760ustar00rootroot00000000000000package ru_test import ( "testing" "time" "github.com/olebedev/when" "github.com/olebedev/when/rules" "github.com/olebedev/when/rules/ru" ) func TestHourMinute(t *testing.T) { w := when.New(nil) w.Add(ru.HourMinute(rules.Override)) fixtok := []Fixture{ {"5:30вечера", 0, "5:30вечера", (17 * time.Hour) + (30 * time.Minute)}, {"в 5:30 вечера", 3, "5:30 вечера", (17 * time.Hour) + (30 * time.Minute)}, {"в 5:59 вечера", 3, "5:59 вечера", (17 * time.Hour) + (59 * time.Minute)}, {"в 5-59 вечера", 3, "5-59 вечера", (17 * time.Hour) + (59 * time.Minute)}, {"в 17-59 вечерело", 3, "17-59", (17 * time.Hour) + (59 * time.Minute)}, {"до 11.10 вечера", 5, "11.10 вечера", (23 * time.Hour) + (10 * time.Minute)}, } fixtnil := []Fixture{ {"28:30вечера", 0, "", 0}, {"12:61вечера", 0, "", 0}, {"24:10", 0, "", 0}, } // ApplyFixtures(t, "ru.HourMinute", w, fixtok) // ApplyFixturesNil(t, "on.HourMinute nil", w, fixtnil) w.Add(ru.Hour(rules.Skip)) // ApplyFixtures(t, "ru.HourMinute|ru.Hour", w, fixtok) ApplyFixturesNil(t, "ru.HourMinute|ru.Hour nil", w, fixtnil) w = when.New(nil) w.Add( ru.Hour(rules.Override), ru.HourMinute(rules.Override), ) ApplyFixtures(t, "ru.Hour|ru.HourMinute", w, fixtok) ApplyFixturesNil(t, "ru.Hour|ru.HourMinute nil", w, fixtnil) } golang-github-olebedev-when-1.1.0/rules/ru/hour_test.go000066400000000000000000000015121472640151500231110ustar00rootroot00000000000000package ru_test import ( "testing" "time" "github.com/olebedev/when" "github.com/olebedev/when/rules" "github.com/olebedev/when/rules/ru" ) func TestHour(t *testing.T) { fixt := []Fixture{ {"5вечера", 0, "5вечера", 17 * time.Hour}, {"в 5 вечера", 3, "5 вечера", 17 * time.Hour}, {"нужно к 5 часам вечера", 14, "5 часам вечера", 17 * time.Hour}, {"в три часа дня", 3, "три часа дня", 15 * time.Hour}, {"в час дня", 3, "час дня", 13 * time.Hour}, {"в одиннадцать часов утра", 3, "одиннадцать часов утра", 11 * time.Hour}, {"в семь вечера", 3, "семь вечера", 19 * time.Hour}, } w := when.New(nil) w.Add(ru.Hour(rules.Override)) ApplyFixtures(t, "en.Hour", w, fixt) } golang-github-olebedev-when-1.1.0/rules/ru/ru.go000066400000000000000000000053771472640151500215400ustar00rootroot00000000000000package ru import ( "github.com/olebedev/when/rules" "time" ) var All = []rules.Rule{ Weekday(rules.Override), CasualDate(rules.Override), CasualTime(rules.Override), Hour(rules.Override), HourMinute(rules.Override), Deadline(rules.Override), Date(rules.Override), DotDateTime(rules.Override), } var WEEKDAY_OFFSET = map[string]int{ "воскресенье": 0, "воскресенья": 0, "воск": 0, "понедельник": 1, "понедельнику": 1, "понедельника": 1, "пн": 1, "вторник": 2, "вторника": 2, "вторнику": 2, "вт": 2, "среда": 3, "среду": 3, "среде": 3, "ср": 3, "четверг": 4, "четверга": 4, "четвергу": 4, "чт": 4, "пятница": 5, "пятнице": 5, "пятницы": 5, "пятницу": 5, "пт": 5, "суббота": 6, "субботы": 6, "субботе": 6, "субботу": 6, "сб": 6, } var WEEKDAY_OFFSET_PATTERN = "(?:воскресенье|воскресенья|воск|понедельник|понедельнику|понедельника|пн|вторник|вторника|вторнику|вт|среда|среду|среде|ср|четверг|четверга|четвергу|чт|пятница|пятнице|пятницы|пятницу|пт|суббота|субботы|субботе|субботу|сб)" var INTEGER_WORDS = map[string]int{ "час": 1, "один": 1, "одну": 1, "одного": 1, "два": 2, "две": 2, "три": 3, "четыре": 4, "пять": 5, "шесть": 6, "семь": 7, "восемь": 8, "девять": 9, "десять": 10, "одиннадцать": 11, "двенадцать": 12, } var INTEGER_WORDS_PATTERN = `(?:час|один|одну|одного|два|две|три|четыре|пять|шесть|семь|восемь|девять|десять|одиннадцать|двенадцать)` var MONTHS = map[string]time.Month{ "января": time.January, "февраля": time.February, "марта": time.March, "апреля": time.April, "мая": time.May, "июня": time.June, "июля": time.July, "августа": time.August, "сентября": time.September, "октября": time.October, "ноября": time.November, "декабря": time.December, } var MONTHS_PATTERN = `(?:января|февраля|марта|апреля|мая|июня|июля|августа|сентября|октября|ноября|декабря)` golang-github-olebedev-when-1.1.0/rules/ru/ru_test.go000066400000000000000000000114021472640151500225610ustar00rootroot00000000000000package ru_test import ( "testing" "time" "github.com/olebedev/when" "github.com/olebedev/when/rules/ru" "github.com/stretchr/testify/require" ) var null = time.Date(2016, time.January, 6, 0, 0, 0, 0, time.UTC) type Fixture struct { Text string Index int Phrase string Diff time.Duration } func ApplyFixtures(t *testing.T, name string, w *when.Parser, fixt []Fixture) { for i, f := range fixt { res, err := w.Parse(f.Text, null) require.Nil(t, err, "[%s] err #%d - %s", name, i, f.Text) require.NotNil(t, res, "[%s] res #%d - %s", name, i, f.Text) require.Equal(t, f.Index, res.Index, "[%s] index #%d - %s", name, i, f.Text) require.Equal(t, f.Phrase, res.Text, "[%s] text #%d - %s", name, i, f.Text) require.Equal(t, f.Diff, res.Time.Sub(null), "[%s] diff #%d - %s", name, i, f.Text) } } func ApplyFixturesNil(t *testing.T, name string, w *when.Parser, fixt []Fixture) { for i, f := range fixt { res, err := w.Parse(f.Text, null) require.Nil(t, err, "[%s] err #%d", name, i) require.Nil(t, res, "[%s] res #%d", name, i) } } func ApplyFixturesErr(t *testing.T, name string, w *when.Parser, fixt []Fixture) { for i, f := range fixt { _, err := w.Parse(f.Text, null) require.NotNil(t, err, "[%s] err #%d", name, i) require.Equal(t, f.Phrase, err.Error(), "[%s] err text #%d", name, i) } } func TestAll(t *testing.T) { w := when.New(nil) w.Add(ru.All...) // complex cases fixt := []Fixture{ {"завтра в 11:10 вечера", 0, "завтра в 11:10 вечера", (47 * time.Hour) + (10 * time.Minute)}, {"вечером в следующий понедельник", 0, "вечером в следующий понедельник", ((5 * 24) + 18) * time.Hour}, {"вечером в прошлый понедельник", 0, "вечером в прошлый понедельник", ((-2 * 24) + 18) * time.Hour}, {"в следующий понедельник вечером", 3, "следующий понедельник вечером", ((5 * 24) + 18) * time.Hour}, {"в Пятницу после обеда", 0, "в Пятницу после обеда", ((2 * 24) + 15) * time.Hour}, {"в следующий вторник в 14:00", 3, "следующий вторник в 14:00", ((6 * 24) + 14) * time.Hour}, {"в следующий вторник в четыре вечера", 3, "следующий вторник в четыре вечера", ((6 * 24) + 16) * time.Hour}, {"в следующую среду в 2:25 вечера", 3, "следующую среду в 2:25 вечера", (((7 * 24) + 14) * time.Hour) + (25 * time.Minute)}, {"в 11 утра в прошлый вторник", 3, "11 утра в прошлый вторник", -13 * time.Hour}, {"написать письмо во вторник после обеда", 30, "во вторник после обеда", ((6 * 24) + 15) * time.Hour}, {"написать письмо ко вторнику", 30, "ко вторнику", 6 * 24 * time.Hour}, {"написать письмо до утра субботы ", 30, "до утра субботы", ((3 * 24) + 8) * time.Hour}, {"написать письмо к субботе после обеда ", 30, "к субботе после обеда", ((3 * 24) + 15) * time.Hour}, {"В субботу вечером", 0, "В субботу вечером", ((3 * 24) + 18) * time.Hour}, {"встреча 15 января 2024", 15, "15 января 2024", time.Date(2024, 1, 15, 0, 0, 0, 0, time.UTC).Sub(null)}, {"5 марта 2025 запланирована встреча", 0, "5 марта 2025", time.Date(2025, 3, 5, 0, 0, 0, 0, time.UTC).Sub(null)}, {"31 декабря 2023", 0, "31 декабря 2023", time.Date(2023, 12, 31, 0, 0, 0, 0, time.UTC).Sub(null)}, {"15 января 2024 в 9:30", 0, "15 января 2024 в 9:30", time.Date(2024, 1, 15, 9, 30, 0, 0, time.UTC).Sub(null)}, {"5 марта 2025 в 15:00 запланирована встреча", 0, "5 марта 2025 в 15:00", time.Date(2025, 3, 5, 15, 0, 0, 0, time.UTC).Sub(null)}, {"31 декабря 2023 в 23:59", 0, "31 декабря 2023 в 23:59", time.Date(2023, 12, 31, 23, 59, 0, 0, time.UTC).Sub(null)}, {"31 декабря", 0, "31 декабря", time.Date(time.Now().Year(), 12, 31, 0, 0, 0, 0, time.UTC).Sub(null)}, {"встреча 15.01.2024 09:30", 15, "15.01.2024 09:30", time.Date(2024, 1, 15, 9, 30, 0, 0, time.UTC).Sub(null)}, {"05.03.2025 15:00 запланирована встреча", 0, "05.03.2025 15:00", time.Date(2025, 3, 5, 15, 0, 0, 0, time.UTC).Sub(null)}, {"31.12.2023 23:59", 0, "31.12.2023 23:59", time.Date(2023, 12, 31, 23, 59, 0, 0, time.UTC).Sub(null)}, {"31.12.2023", 0, "31.12.2023", time.Date(2023, 12, 31, 0, 0, 0, 0, time.UTC).Sub(null)}, } ApplyFixtures(t, "ru.All...", w, fixt) } golang-github-olebedev-when-1.1.0/rules/ru/weekday.go000066400000000000000000000047721472640151500225410ustar00rootroot00000000000000package ru import ( "regexp" "strings" "time" "github.com/olebedev/when/rules" ) // https://play.golang.org/p/aRWlil_64M func Weekday(s rules.Strategy) rules.Rule { return &rules.F{ RegExp: regexp.MustCompile("(?i)(?:\\P{L}|^)" + "(?:(на|во?|ко?|до|эт(?:от|ой|у|а)?|прошл(?:ую|ый|ая)|последн(?:юю|ий|ее|ая)|следующ(?:ую|ее|ая|ий))\\s*)?" + "(" + WEEKDAY_OFFSET_PATTERN[3:] + // skip '(?:' "(?:\\s*на\\s*(этой|прошлой|следующей)\\s*неделе)?" + "(?:\\P{L}|$)"), Applier: func(m *rules.Match, c *rules.Context, o *rules.Options, ref time.Time) (bool, error) { day := strings.ToLower(strings.TrimSpace(m.Captures[1])) norm := m.Captures[2] if norm == "" { norm = m.Captures[0] } if norm == "" { norm = "следующ" } norm = strings.ToLower(strings.TrimSpace(norm)) dayInt, ok := WEEKDAY_OFFSET[day] if !ok { return false, nil } if c.Duration != 0 && s != rules.Override { return false, nil } // Switch: switch { case strings.Contains(norm, "прошл") || strings.Contains(norm, "последн"): diff := int(ref.Weekday()) - dayInt if diff > 0 { c.Duration = -time.Duration(diff*24) * time.Hour } else if diff < 0 { c.Duration = -time.Duration(7+diff) * 24 * time.Hour } else { c.Duration = -(7 * 24 * time.Hour) } case strings.Contains(norm, "следующ"), norm == "в", norm == "к", strings.Contains(norm, "во"), strings.Contains(norm, "ко"), strings.Contains(norm, "до"): diff := dayInt - int(ref.Weekday()) if diff > 0 { c.Duration = time.Duration(diff*24) * time.Hour } else if diff < 0 { c.Duration = time.Duration(7+diff) * 24 * time.Hour } else { c.Duration = 7 * 24 * time.Hour } case strings.Contains(norm, "эт"): if int(ref.Weekday()) < dayInt { diff := dayInt - int(ref.Weekday()) if diff > 0 { c.Duration = time.Duration(diff*24) * time.Hour } else if diff < 0 { c.Duration = time.Duration(7+diff) * 24 * time.Hour } else { c.Duration = 7 * 24 * time.Hour } } else if int(ref.Weekday()) > dayInt { diff := int(ref.Weekday()) - dayInt if diff > 0 { c.Duration = -time.Duration(diff*24) * time.Hour } else if diff < 0 { c.Duration = -time.Duration(7+diff) * 24 * time.Hour } else { c.Duration = -(7 * 24 * time.Hour) } } } return true, nil }, } } golang-github-olebedev-when-1.1.0/rules/ru/weekday_test.go000066400000000000000000000036461472640151500235770ustar00rootroot00000000000000package ru_test import ( "testing" "time" "github.com/olebedev/when" "github.com/olebedev/when/rules" "github.com/olebedev/when/rules/ru" ) func TestWeekday(t *testing.T) { // current is Friday fixt := []Fixture{ // past/last {"это нужно было сделать в прошлый Понедельник", 45, "прошлый Понедельник", -(2 * 24 * time.Hour)}, {"прошлая суббота", 0, "прошлая суббота", -(4 * 24 * time.Hour)}, {"прошлая пятница", 0, "прошлая пятница", -(5 * 24 * time.Hour)}, {"в последнюю среду", 3, "последнюю среду", -(7 * 24 * time.Hour)}, {"в прошлый вторник", 3, "прошлый вторник", -(24 * time.Hour)}, // next {"в следующий вторник", 3, "следующий вторник", 6 * 24 * time.Hour}, {"напиши мне в следующую среду, договоримся", 23, "следующую среду", 7 * 24 * time.Hour}, {"следующая суббота", 0, "следующая суббота", 3 * 24 * time.Hour}, {"в следующую суббота", 3, "следующую суббота", 3 * 24 * time.Hour}, // this {"в этот вторник", 3, "этот вторник", -(24 * time.Hour)}, {"напиши мне в эту среду, договоримся", 23, "эту среду", 0}, {"эта суббота", 0, "эта суббота", 3 * 24 * time.Hour}, {"во вторник", 0, "во вторник", 6 * 24 * time.Hour}, {"в субботу", 0, "в субботу", 3 * 24 * time.Hour}, } w := when.New(nil) w.Add(ru.Weekday(rules.Override)) ApplyFixtures(t, "ru.Weekday", w, fixt) } func TestWeekdayNil(t *testing.T) { fixt := []Fixture{ {"завтра", 0, "", 0}, } w := when.New(nil) w.Add(ru.Weekday(rules.Override)) ApplyFixturesNil(t, "ru.Weekday nil", w, fixt) } golang-github-olebedev-when-1.1.0/rules/rules.go000066400000000000000000000025201472640151500216010ustar00rootroot00000000000000package rules import ( "regexp" "time" ) type Strategy int const ( Skip Strategy = iota Merge Override ) type Rule interface { Find(string) *Match } type Options struct { Afternoon, Evening, Morning, Noon int Distance int MatchByOrder bool // TODO // WeekStartsOn time.Weekday } type Match struct { Left, Right int Text string Captures []string Order float64 Applier func(*Match, *Context, *Options, time.Time) (bool, error) } func (m Match) String() string { return m.Text } func (m *Match) Apply(c *Context, o *Options, t time.Time) (bool, error) { return m.Applier(m, c, o, t) } type F struct { RegExp *regexp.Regexp Applier func(*Match, *Context, *Options, time.Time) (bool, error) } func (f *F) Find(text string) *Match { m := &Match{ Applier: f.Applier, Left: -1, } indexes := f.RegExp.FindStringSubmatchIndex(text) length := len(indexes) if length <= 2 { return nil } for i := 2; i < length; i += 2 { if m.Left == -1 && indexes[i] >= 0 { m.Left = indexes[i] } // check if capture was found if indexes[i] >= 0 && indexes[i+1] >= 0 { m.Captures = append(m.Captures, text[indexes[i]:indexes[i+1]]) m.Right = indexes[i+1] } else { m.Captures = append(m.Captures, "") } } if len(m.Captures) == 0 { return nil } m.Text = text[m.Left:m.Right] return m } golang-github-olebedev-when-1.1.0/rules/sort.go000066400000000000000000000007201472640151500214360ustar00rootroot00000000000000package rules type MatchByIndex []*Match func (m MatchByIndex) Len() int { return len(m) } func (m MatchByIndex) Swap(i, j int) { m[i], m[j] = m[j], m[i] } func (m MatchByIndex) Less(i, j int) bool { return m[i].Left < m[j].Left } type MatchByOrder []*Match func (m MatchByOrder) Len() int { return len(m) } func (m MatchByOrder) Swap(i, j int) { m[i], m[j] = m[j], m[i] } func (m MatchByOrder) Less(i, j int) bool { return m[i].Order < m[j].Order } golang-github-olebedev-when-1.1.0/rules/zh/000077500000000000000000000000001472640151500205425ustar00rootroot00000000000000golang-github-olebedev-when-1.1.0/rules/zh/after_time.go000066400000000000000000000023541472640151500232140ustar00rootroot00000000000000package zh import ( "regexp" "strconv" "time" "github.com/olebedev/when/rules" ) /* 5/五 分钟后 5 小时后 */ func AfterTime(s rules.Strategy) rules.Rule { return &rules.F{ RegExp: regexp.MustCompile("(?i)" + "((?:[0-9]{0,3}))?" + "(" + INTEGER_WORDS_PATTERN[3:] + "?" + "\\s*" + "(?:(分|分钟|小时|天|周|月)\\s*)" + "(后)" + "(?:\\W|$)", ), Applier: func(m *rules.Match, c *rules.Context, o *rules.Options, ref time.Time) (bool, error) { if c.Hour != nil && s != rules.Override { return false, nil } duration, _ := strconv.Atoi(m.Captures[0]) if d, exist := INTEGER_WORDS[compressStr(m.Captures[1])]; exist { duration = d } if m.Captures[1] == "半" && m.Captures[2] == "小时" { c.Duration = time.Minute * time.Duration(30) return true, nil } switch m.Captures[2] { case "分钟", "分": c.Duration = time.Minute * time.Duration(duration) case "小时": c.Duration = time.Hour * time.Duration(duration) case "天": c.Duration = time.Hour * 24 * time.Duration(duration) case "周": c.Duration = time.Hour * 24 * 7 * time.Duration(duration) case "月": _, _ = c.Time(time.Now().AddDate(0, duration, 0)) } return true, nil }, } } golang-github-olebedev-when-1.1.0/rules/zh/casual_date.go000066400000000000000000000054441472640151500233450ustar00rootroot00000000000000package zh import ( "regexp" "strconv" "strings" "time" "github.com/AlekSi/pointer" "github.com/olebedev/when/rules" ) func CasualDate(s rules.Strategy) rules.Rule { overwrite := s == rules.Override return &rules.F{ RegExp: regexp.MustCompile("(?i)" + "(大前|前|昨|今天|今|明|大后|后|下下|下|上|上上)" + "(天|月|个月|年|儿)" + "(1[0-9]|2[0-9]|3[0-1]|[1-9]|" + DAY_WORDS_PATTERN + ")?" + "(?:\\s*)?" + "(日|号)?" + "", // "(?:\\W|$)", ), Applier: func(m *rules.Match, c *rules.Context, o *rules.Options, ref time.Time) (bool, error) { lower := compressStr(strings.TrimSpace(m.String())) switch { case strings.Contains(lower, "号"), strings.Contains(lower, "日"): day, _ := strconv.Atoi(m.Captures[2]) c.Day = pointer.ToInt(day) } switch { case strings.Contains(lower, "后年"): c.Year = pointer.ToInt(ref.Year() + 2) case strings.Contains(lower, "明年"): c.Year = pointer.ToInt(ref.Year() + 1) case strings.Contains(lower, "下下"): monthInt := int(ref.Month()) + 2 c.Month = pointer.ToInt(monthInt) case strings.Contains(lower, "下月"), strings.Contains(lower, "下个月"): monthInt := int(ref.Month()) + 1 c.Month = pointer.ToInt(monthInt) case strings.Contains(lower, "上上"): monthInt := int(ref.Month()) - 2 c.Month = pointer.ToInt(monthInt) case strings.Contains(lower, "上月"), strings.Contains(lower, "上个月"): monthInt := int(ref.Month()) - 1 c.Month = pointer.ToInt(monthInt) case strings.Contains(lower, "今晚"), strings.Contains(lower, "晚上"): if c.Hour == nil && c.Minute == nil || overwrite { c.Hour = pointer.ToInt(22) c.Minute = pointer.ToInt(0) } case strings.Contains(lower, "今天"), strings.Contains(lower, "今儿"): // c.Hour = pointer.ToInt(18) case strings.Contains(lower, "明天"), strings.Contains(lower, "明儿"): if c.Duration == 0 || overwrite { c.Duration += time.Hour * 24 } case strings.Contains(lower, "昨天"): if c.Duration == 0 || overwrite { c.Duration -= time.Hour * 24 } case strings.Contains(lower, "大前天"): if c.Duration == 0 || overwrite { c.Duration -= time.Hour * 24 * 3 } case strings.Contains(lower, "前天"): if c.Duration == 0 || overwrite { c.Duration -= time.Hour * 24 * 2 } case strings.Contains(lower, "昨晚"): if (c.Hour == nil && c.Duration == 0) || overwrite { c.Hour = pointer.ToInt(23) c.Duration -= time.Hour * 24 } case strings.Contains(lower, "大后天"): if c.Duration == 0 || overwrite { c.Duration += time.Hour * 24 * 3 } case strings.Contains(lower, "后天"): if c.Duration == 0 || overwrite { c.Duration += time.Hour * 24 * 2 } } return true, nil }, } } golang-github-olebedev-when-1.1.0/rules/zh/casual_date_test.go000066400000000000000000000021071472640151500243750ustar00rootroot00000000000000package zh_test import ( "testing" "time" "github.com/olebedev/when" "github.com/olebedev/when/rules" "github.com/olebedev/when/rules/zh" ) func TestCasualDate(t *testing.T) { fixt := []Fixture{ {"后天中午", 0, "后天", (2 * 24) * time.Hour}, {"大后天中午", 0, "大后天", (3 * 24) * time.Hour}, {"昨天", 0, "昨天", (-1 * 24) * time.Hour}, {"前天", 0, "前天", (-2 * 24) * time.Hour}, {"大前天", 0, "大前天", (-3 * 24) * time.Hour}, {"下月", 0, "下月", (31 * 24) * time.Hour}, {"下个月", 0, "下个月", (31 * 24) * time.Hour}, {"下下月", 0, "下下月", (31*24 + 30*24) * time.Hour}, {"下下个月", 0, "下下个月", (31*24 + 30*24) * time.Hour}, {"明年", 0, "明年", (365 * 24) * time.Hour}, {"后年", 0, "后年", now.AddDate(2, 0, 0).Sub(now)}, {"下月6号", 0, "下月6号", 552 * time.Hour}, } w := when.New(nil) w.Add(zh.CasualDate(rules.Override)) ApplyFixtures(t, "zh.TestCasualDate", w, fixt) } /* (([1-9](?:月|-|/|\.|))|1[0-2])\s*(月|-|/|\.|)\s*([1-9]|1[0-9]|2[0-9]|3[0-1])\s*(日|号)?(?:\W|$) */ golang-github-olebedev-when-1.1.0/rules/zh/casual_time.go000066400000000000000000000027061472640151500233640ustar00rootroot00000000000000package zh import ( "regexp" "strings" "time" "github.com/AlekSi/pointer" "github.com/olebedev/when/rules" ) func CasualTime(s rules.Strategy) rules.Rule { overwrite := s == rules.Override return &rules.F{ RegExp: regexp.MustCompile(`(?i)(?:\W|^)((今天)?\s*(早晨|下午|傍晚|中午|晚上))`), Applier: func(m *rules.Match, c *rules.Context, o *rules.Options, ref time.Time) (bool, error) { lower := strings.ToLower(strings.TrimSpace(m.String())) if (c.Hour != nil || c.Minute != nil) && !overwrite { return false, nil } switch { case strings.Contains(lower, "晚上"): if o.Evening != 0 { c.Hour = &o.Evening } else { c.Hour = pointer.ToInt(20) } c.Minute = pointer.ToInt(0) case strings.Contains(lower, "下午"): if o.Afternoon != 0 { c.Hour = &o.Afternoon } else { c.Hour = pointer.ToInt(15) } c.Minute = pointer.ToInt(0) case strings.Contains(lower, "傍晚"): if o.Evening != 0 { c.Hour = &o.Evening } else { c.Hour = pointer.ToInt(18) } c.Minute = pointer.ToInt(0) case strings.Contains(lower, "早晨"): if o.Morning != 0 { c.Hour = &o.Morning } else { c.Hour = pointer.ToInt(8) } c.Minute = pointer.ToInt(0) case strings.Contains(lower, "中午"): if o.Noon != 0 { c.Hour = &o.Noon } else { c.Hour = pointer.ToInt(12) } c.Minute = pointer.ToInt(0) } return true, nil }, } } golang-github-olebedev-when-1.1.0/rules/zh/exact_month_date.go000066400000000000000000000030471472640151500244030ustar00rootroot00000000000000package zh import ( "regexp" "strconv" "time" "github.com/olebedev/when/rules" ) /* 规则名称:精确到月份的日期 */ func ExactMonthDate(s rules.Strategy) rules.Rule { overwrite := s == rules.Override return &rules.F{ RegExp: regexp.MustCompile("" + "(?:\\b|^)" + // can't use \W here due to Chinese characters "(?:" + "(1[0-2]|[1-9]|" + MON_WORDS_PATTERN + ")" + "(?:\\s*)" + "(月|-|/|\\.)" + "(?:\\s*)" + ")?" + "(?:" + "(1[0-9]|2[0-9]|3[0-1]|[1-9]|" + DAY_WORDS_PATTERN + ")" + "(?:\\s*)" + "(日|号)?" + ")?", ), Applier: func(m *rules.Match, c *rules.Context, o *rules.Options, ref time.Time) (bool, error) { _ = overwrite // the default value of month is the current month, and the default // value of day is the first day of the month, so that we can handle // cases like "4月" (Apr 1st) and "12号" (12th this month) var monInt = int(ref.Month()) var dayInt = 1 var exist bool if m.Captures[1] == "" && m.Captures[3] == "" { return false, nil } if m.Captures[0] != "" { monInt, exist = MON_WORDS[compressStr(m.Captures[0])] if !exist { mon, err := strconv.Atoi(m.Captures[0]) if err != nil { return false, nil } monInt = mon } } if m.Captures[2] != "" { dayInt, exist = DAY_WORDS[compressStr(m.Captures[2])] if !exist { day, err := strconv.Atoi(m.Captures[2]) if err != nil { return false, nil } dayInt = day } } c.Month = &monInt c.Day = &dayInt return true, nil }, } } golang-github-olebedev-when-1.1.0/rules/zh/exact_month_test.go000066400000000000000000000027121472640151500244430ustar00rootroot00000000000000package zh_test import ( "testing" "time" "github.com/olebedev/when" "github.com/olebedev/when/rules" "github.com/olebedev/when/rules/zh" ) func TestExactMonthDate(t *testing.T) { // current is Monday fixt := []Fixture{ {"4月1日", 0, "4月1日", (18 * 24) * time.Hour}, {"4月2日", 0, "4月2日", (19 * 24) * time.Hour}, {"4月 2日", 0, "4月 2日", (19 * 24) * time.Hour}, {"4 月 2 日", 0, "4 月 2 日", (19 * 24) * time.Hour}, {"四月一日", 0, "四月一日", (18 * 24) * time.Hour}, {"四月1日", 0, "四月1日", (18 * 24) * time.Hour}, {"四月", 0, "四月", (18 * 24) * time.Hour}, {"十一月一日", 0, "十一月一日", 5568 * time.Hour}, {"四月三十日", 0, "四月三十日", 1128 * time.Hour}, {"4月30日", 0, "4月30日", 1128 * time.Hour}, {"5月1号", 0, "5月1号", 1152 * time.Hour}, {"5/1", 0, "5/1", 1152 * time.Hour}, {"5月1日", 0, "5月1日", 1152 * time.Hour}, {"五月", 0, "五月", 1152 * time.Hour}, {"12号", 0, "12号", (-2 * 24) * time.Hour}, } w := when.New(nil) w.Add(zh.ExactMonthDate(rules.Override)) ApplyFixtures(t, "zh.ExactMonthDate", w, fixt) } func TestExactMonthDateNil(t *testing.T) { fixt := []Fixture{ {"41", 0, "", (18 * 24) * time.Hour}, } w := when.New(nil) w.Add(zh.ExactMonthDate(rules.Override)) ApplyFixturesNil(t, "zh.ExactMonthDate", w, fixt) } /* (([1-9](?:月|-|/|\.|))|1[0-2])\s*(月|-|/|\.|)\s*([1-9]|1[0-9]|2[0-9]|3[0-1])\s*(日|号)?(?:\W|$) */ golang-github-olebedev-when-1.1.0/rules/zh/hour_minute.go000066400000000000000000000027721472640151500234370ustar00rootroot00000000000000package zh import ( "regexp" "strconv" "time" "github.com/olebedev/when/rules" ) /* "上午 5点" "上午 5 点" "下午 3点" "下午 3 点" "下午 3点半" "下午 3点30" "下午 3:30" "下午 3:30" "下午 三点半" */ func HourMinute(s rules.Strategy) rules.Rule { return &rules.F{ RegExp: regexp.MustCompile("(?i)" + "(?:(凌\\s*晨|早\\s*晨|早\\s*上|上\\s*午|下\\s*午|晚\\s*上|今晚)?\\s*)" + "((?:[0-1]{0,1}[0-9])|(?:2[0-3]))?" + "(?:\\s*)" + "(" + INTEGER_WORDS_PATTERN[3:] + "?" + "(\\:|:|\\-|点)" + "((?:[0-5][0-9]))?" + "(" + INTEGER_WORDS_PATTERN + "+)?" + "(?:\\W|$)"), Applier: func(m *rules.Match, c *rules.Context, o *rules.Options, ref time.Time) (bool, error) { if (c.Hour != nil || c.Minute != nil) && s != rules.Override { return false, nil } hour, exist := INTEGER_WORDS[m.Captures[2]] // 中文 if !exist { hour, _ = strconv.Atoi(m.Captures[1]) } if hour > 24 { return false, nil } minutes, exist := INTEGER_WORDS[m.Captures[5]] if !exist { minutes, _ = strconv.Atoi(m.Captures[4]) } if minutes > 59 { return false, nil } c.Minute = &minutes lower := compressStr(m.Captures[0]) switch lower { case "上午", "凌晨", "早晨", "早上": c.Hour = &hour case "下午", "晚上", "今晚": if hour < 12 { hour += 12 } c.Hour = &hour case "": if hour > 23 { return false, nil } c.Hour = &hour } return true, nil }, } } golang-github-olebedev-when-1.1.0/rules/zh/hour_minute_test.go000066400000000000000000000023771472640151500244770ustar00rootroot00000000000000package zh_test import ( "github.com/olebedev/when/rules/zh" "testing" "time" "github.com/olebedev/when" "github.com/olebedev/when/rules" ) func TestHourMinute(t *testing.T) { // current is Monday fixt := []Fixture{ {"上午 11:30", 0, "上午 11:30", 11*time.Hour + 30*time.Minute}, {"下午 3:30", 0, "下午 3:30", 15*time.Hour + 30*time.Minute}, {"下午 3点半", 0, "下午 3点半", 15*time.Hour + 30*time.Minute}, {"凌晨 3点半", 0, "凌晨 3点半", 3*time.Hour + 30*time.Minute}, {"晚上8:00", 0, "晚上8:00", 20*time.Hour + 0*time.Minute}, {"晚上9:32", 0, "晚上9:32", 21*time.Hour + 32*time.Minute}, {"晚 上 8:00", 0, "晚 上 8:00", 20*time.Hour + 0*time.Minute}, {"晚上 8 点干啥去", 0, "晚上 8 点", 20*time.Hour + 0*time.Minute}, {"他俩凌晨 3点去散步太可怕了", 6, "凌晨 3点", 3*time.Hour + 0*time.Minute}, {"早晨八点一刻", 0, "早晨八点一刻", 8*time.Hour + 15*time.Minute}, {"早上八点半", 0, "早上八点半", 8*time.Hour + 30*time.Minute}, {"今晚八点", 0, "今晚八点", 20 * time.Hour}, {"今晚八点半", 0, "今晚八点半", 20*time.Hour + 30*time.Minute}, } w := when.New(nil) w.Add(zh.HourMinute(rules.Override)) ApplyFixtures(t, "zh.HourMinute", w, fixt) } golang-github-olebedev-when-1.1.0/rules/zh/tradition_hour.go000066400000000000000000000030101472640151500241150ustar00rootroot00000000000000package zh import ( "regexp" "time" "github.com/olebedev/when/rules" ) /* 子时 23:00 - 01:00 丑时 01:00 - 03:00 寅时 03:00 - 05:00 卯时 05:00 - 07:00 辰时 07:00 - 09:00 巳时 09:00 - 11:00 午时 11:00 - 13:00 未时 13:00 - 15:00 申时 15:00 - 17:00 酉时 17:00 - 19:00 戌时 19:00 - 21:00 亥时 21:00 - 23:00 */ func TraditionHour(s rules.Strategy) rules.Rule { return &rules.F{ RegExp: regexp.MustCompile("" + "(?:(子\\s?时|丑\\s?时|寅\\s?时|卯\\s?时|辰\\s?时|巳\\s?时|午\\s?时|未\\s?时|申\\s?时|酉\\s?时|戌\\s?时|亥\\s?时))\\s?" + "(?:(一\\s?刻|二\\s?刻|两\\s?刻|三\\s?刻|四\\s?刻|五\\s?刻|六\\s?刻|七\\s?刻|1\\s?刻|2\\s?刻|3\\s?刻|4\\s?刻|5\\s?刻|6\\s?刻|7\\s?刻))?", ), Applier: func(m *rules.Match, c *rules.Context, o *rules.Options, ref time.Time) (bool, error) { if c.Hour != nil && s != rules.Override { return false, nil } hour, exist := TRADITION_HOUR_WORDS[compressStr(m.Captures[0])] if !exist { return false, nil } c.Hour = &hour zero := 0 c.Minute = &zero if minute, exist := TRADITION_MINUTE_WORDS[compressStr(m.Captures[1])]; exist { if minute > 60 { hour := *c.Hour + 1 c.Hour = &hour minute = minute - 60 c.Minute = &minute } else { c.Minute = &minute } } return true, nil }, } } func compressStr(str string) string { if str == "" { return "" } reg := regexp.MustCompile(`\s+`) return reg.ReplaceAllString(str, "") } golang-github-olebedev-when-1.1.0/rules/zh/tradition_hour_test.go000066400000000000000000000013661472640151500251700ustar00rootroot00000000000000package zh_test import ( "github.com/olebedev/when/rules/zh" "testing" "time" "github.com/olebedev/when" "github.com/olebedev/when/rules" ) func TestTraditionHour(t *testing.T) { // current is Monday fixt := []Fixture{ {"午 时123", 0, "午 时", 11 * time.Hour}, {"子时", 0, "子时", 23 * time.Hour}, {"午时太阳正好", 0, "午时", 11 * time.Hour}, {"我们在酉时喝一杯吧", 9, "酉时", 17 * time.Hour}, {"午时三刻问斩", 0, "午时三刻", 11*time.Hour + 45*time.Minute}, {"午时四刻吃饭", 0, "午时四刻", 12 * time.Hour}, {"戌时1刻", 0, "戌时1刻", 19*time.Hour + 15*time.Minute}, } w := when.New(nil) w.Add(zh.TraditionHour(rules.Override)) ApplyFixtures(t, "zh.TraditionHour", w, fixt) } golang-github-olebedev-when-1.1.0/rules/zh/weedkay.go000066400000000000000000000037731472640151500225340ustar00rootroot00000000000000package zh import ( "regexp" "strings" "time" "github.com/olebedev/when/rules" ) func Weekday(s rules.Strategy) rules.Rule { overwrite := s == rules.Override return &rules.F{ RegExp: regexp.MustCompile("(?i)" + "(?:(本|这|下|上|这个|下个|上个|下下)\\s*)?" + "(?:(周|礼拜|星期)\\s*)" + "(1|2|3|4|5|6|天|一|二|三|四|五|六|日)" + "(?:\\W|$)", ), Applier: func(m *rules.Match, c *rules.Context, o *rules.Options, ref time.Time) (bool, error) { _ = overwrite if strings.TrimSpace(m.Captures[1]) == "" { return false, nil } day := strings.ToLower(strings.TrimSpace(m.Captures[2])) norm := strings.ToLower(strings.TrimSpace(m.Captures[0])) if norm == "" { norm = "本" } dayInt, ok := WEEKDAY_OFFSET[day] if !ok { return false, nil } if c.Duration != 0 && !overwrite { return false, nil } // Switch: switch { case strings.Contains(norm, "上"): diff := int(ref.Weekday()) - dayInt c.Duration = -time.Duration(7+diff) * 24 * time.Hour case strings.Contains(norm, "下下"): diff := dayInt - int(ref.Weekday()) c.Duration = time.Duration(7+7+diff) * 24 * time.Hour case strings.Contains(norm, "下"): diff := dayInt - int(ref.Weekday()) c.Duration = time.Duration(7+diff) * 24 * time.Hour case strings.Contains(norm, "本") || strings.Contains(norm, "这"): if int(ref.Weekday()) < dayInt { diff := dayInt - int(ref.Weekday()) if diff > 0 { c.Duration = time.Duration(diff*24) * time.Hour } else if diff < 0 { c.Duration = time.Duration(7+diff) * 24 * time.Hour } else { c.Duration = 7 * 24 * time.Hour } } else if int(ref.Weekday()) > dayInt { diff := int(ref.Weekday()) - dayInt if diff > 0 { c.Duration = -time.Duration(diff*24) * time.Hour } else if diff < 0 { c.Duration = -time.Duration(7+diff) * 24 * time.Hour } else { c.Duration = -(7 * 24 * time.Hour) } } } return true, nil }, } } golang-github-olebedev-when-1.1.0/rules/zh/weekday_test.go000066400000000000000000000022721472640151500235640ustar00rootroot00000000000000package zh_test import ( "github.com/olebedev/when/rules/zh" "testing" "time" "github.com/olebedev/when" "github.com/olebedev/when/rules" ) func TestWeekday(t *testing.T) { // current is Monday fixt := []Fixture{ {"和你下周一吃饭", 6, "下周一", 7 * 24 * time.Hour}, {"下星期三", 0, "下星期三", 9 * 24 * time.Hour}, {"和小西本周三一起打羽毛球", 9, "本周三", 2 * 24 * time.Hour}, {"这周三", 0, "这周三", 2 * 24 * time.Hour}, {"这礼拜四浇花", 0, "这礼拜四", 3 * 24 * time.Hour}, {"这星期 4", 0, "这星期 4", 3 * 24 * time.Hour}, {"和李星期这星期 4喝茶", 12, "这星期 4", 3 * 24 * time.Hour}, {"周日", 0, "周日", 6 * 24 * time.Hour}, {"下周日", 0, "下周日", (6 + 7) * 24 * time.Hour}, {"2下周天", 1, "下周天", (6 + 7) * 24 * time.Hour}, {"上周三", 0, "上周三", -5 * 24 * time.Hour}, {"下个周三", 0, "下个周三", (7 + 2) * 24 * time.Hour}, {"1下个礼拜 3", 1, "下个礼拜 3", (7 + 2) * 24 * time.Hour}, {"下下礼拜 3", 0, "下下礼拜 3", (7 + 7 + 2) * 24 * time.Hour}, } w := when.New(nil) w.Add(zh.Weekday(rules.Override)) ApplyFixtures(t, "zh.Weekday", w, fixt) } golang-github-olebedev-when-1.1.0/rules/zh/zh.go000066400000000000000000000073021472640151500215140ustar00rootroot00000000000000package zh import "github.com/olebedev/when/rules" var All = []rules.Rule{ Weekday(rules.Override), CasualDate(rules.Override), CasualTime(rules.Override), HourMinute(rules.Override), ExactMonthDate(rules.Override), TraditionHour(rules.Override), AfterTime(rules.Override), } var WEEKDAY_OFFSET = map[string]int{ "天": 7, "一": 1, "二": 2, "三": 3, "四": 4, "五": 5, "六": 6, "1": 1, "2": 2, "3": 3, "4": 4, "5": 5, "6": 6, "日": 7, } var INTEGER_WORDS = map[string]int{ "零一": 1, "零二": 2, "零三": 3, "零四": 4, "零五": 5, "零六": 6, "零七": 7, "零八": 8, "零九": 9, "一": 1, "二": 2, "两": 2, "三": 3, "四": 4, "五": 5, "六": 6, "七": 7, "八": 8, "九": 9, "十": 10, "十一": 11, "十二": 12, "十三": 13, "十四": 14, "十五": 15, "十六": 16, "十七": 17, "十八": 18, "十九": 19, "二十": 20, "二十一": 21, "二十二": 22, "二十三": 23, "二十五": 25, "二十六": 26, "二十七": 27, "二十八": 28, "二十九": 29, "三十": 30, "三十一": 31, "三十二": 32, "三十三": 33, "三十四": 34, "三十五": 35, "三十六": 36, "三十七": 37, "三十八": 38, "三十九": 39, "四十": 40, "四十一": 41, "四十二": 42, "四十三": 43, "四十四": 44, "四十五": 45, "四十六": 46, "四十七": 47, "四十八": 48, "四十九": 49, "五十": 50, "五十一": 51, "五十二": 52, "五十三": 53, "五十四": 54, "五十五": 55, "五十六": 56, "五十七": 57, "五十八": 58, "五十九": 59, "零": 0, "半": 30, "一刻": 15, } var INTEGER_WORDS_PATTERN = `(?:一刻|半|零一|零二|零三|零四|零五|零六|零七|零八|零九|一|两|二|三|四|五|六|七|八|九|十|十一|十二|十三|十四|十五|十六|十七|十八|十九|二十|二十一|二十二|二十三|二十五|二十六|二十七|二十八|二十九|三十|三十一|三十二|三十三|三十四|三十五|三十六|三十七|三十八|三十九|四十|四十一|四十二|四十三|四十四|四十五|四十六|四十七|四十八|四十九|五十|五十一|五十二|五十三|五十四|五十五|五十六|五十七|五十八|五十九|零)` var TRADITION_HOUR_WORDS = map[string]int{ "子时": 23, "丑时": 1, "寅时": 3, "卯时": 5, "辰时": 7, "巳时": 9, "午时": 11, "未时": 13, "申时": 15, "酉时": 17, "戌时": 19, "亥时": 21, } var TRADITION_MINUTE_WORDS = map[string]int{ "一刻": 15, "两刻": 30, "二刻": 30, "三刻": 45, "四刻": 60, "五刻": 75, "六刻": 90, "七刻": 105, "八刻": 120, "1刻": 15, "2刻": 30, "3刻": 45, "4刻": 60, "5刻": 75, "6刻": 90, "7刻": 105, "8刻": 120, } var MON_WORDS = map[string]int{ "一": 1, "二": 2, "三": 3, "四": 4, "五": 5, "六": 6, "七": 7, "八": 8, "九": 9, "十": 10, "十一": 11, "十二": 12, } var MON_WORDS_PATTERN = `十一|十二|一|二|三|四|五|六|七|八|九|十` var DAY_WORDS = map[string]int{ "一": 1, "二": 2, "四": 4, "五": 5, "六": 6, "七": 7, "八": 8, "九": 9, "十一": 11, "十二": 12, "十三": 13, "十四": 14, "十五": 15, "十六": 16, "十七": 17, "十八": 18, "十九": 19, "十": 10, "二十一": 21, "二十二": 22, "二十三": 23, "二十五": 25, "二十六": 26, "二十七": 27, "二十八": 28, "二十九": 29, "二十": 20, "三十一": 31, "三十": 30, "三": 3, } var DAY_WORDS_PATTERN = `一|二|四|五|六|七|八|九|十一|十二|十三|十四|十五|十六|十七|十八|十九|十|二十一|二十二|二十三|二十五|二十六|二十七|二十八|二十九|二十|三十一|三十|三` golang-github-olebedev-when-1.1.0/rules/zh/zh_test.go000066400000000000000000000016771472640151500225640ustar00rootroot00000000000000package zh_test import ( "github.com/olebedev/when" "github.com/stretchr/testify/require" "testing" "time" ) var now = time.Date(2022, time.March, 14, 0, 0, 0, 0, time.UTC) type Fixture struct { Text string Index int Phrase string Diff time.Duration } func ApplyFixtures(t *testing.T, name string, w *when.Parser, fixt []Fixture) { for i, f := range fixt { res, err := w.Parse(f.Text, now) require.Nil(t, err, "[%s] err #%d", name, i) require.NotNil(t, res, "[%s] res #%d", name, i) require.Equal(t, f.Index, res.Index, "[%s] index #%d", name, i) require.Equal(t, f.Phrase, res.Text, "[%s] text #%d", name, i) require.Equal(t, f.Diff, res.Time.Sub(now), "[%s] diff #%d", name, i) } } func ApplyFixturesNil(t *testing.T, name string, w *when.Parser, fixt []Fixture) { for i, f := range fixt { res, err := w.Parse(f.Text, now) require.Nil(t, err, "[%s] err #%d", name, i) require.Nil(t, res, "[%s] res #%d", name, i) } } golang-github-olebedev-when-1.1.0/wercker.yml000066400000000000000000000003541472640151500211560ustar00rootroot00000000000000box: golang build: steps: - setup-go-workspace - script: name: go get code: | go version go get -v -t -d ./... - script: name: go test code: | go test -v ./... golang-github-olebedev-when-1.1.0/when.go000066400000000000000000000064421472640151500202650ustar00rootroot00000000000000package when import ( "sort" "time" "github.com/olebedev/when/rules" "github.com/olebedev/when/rules/br" "github.com/olebedev/when/rules/common" "github.com/olebedev/when/rules/en" "github.com/olebedev/when/rules/nl" "github.com/olebedev/when/rules/ru" "github.com/pkg/errors" ) // Parser is a struct which contains options // rules, and middlewares to call type Parser struct { options *rules.Options rules []rules.Rule middleware []func(string) (string, error) } // Result is a struct which contains parsing meta-info type Result struct { // Index is a start index Index int // Text is a text found and processed Text string // Source is input string Source string // Time is an output time Time time.Time } // Parse returns Result and error if any. If have not matches it returns nil, nil. func (p *Parser) Parse(text string, base time.Time) (*Result, error) { res := Result{ Source: text, Time: base, Index: -1, } if p.options == nil { p.options = defaultOptions } var err error // apply middlewares for _, b := range p.middleware { text, err = b(text) if err != nil { return nil, err } } // find all matches matches := make([]*rules.Match, 0) c := float64(0) for _, rule := range p.rules { r := rule.Find(text) if r != nil { r.Order = c c++ matches = append(matches, r) } } // not found if len(matches) == 0 { return nil, nil } // find a cluster sort.Sort(rules.MatchByIndex(matches)) // get borders of the matches end := matches[0].Right res.Index = matches[0].Left for i, m := range matches { if m.Left <= end+p.options.Distance { end = m.Right } else { matches = matches[:i] break } } res.Text = text[res.Index:end] // apply rules if p.options.MatchByOrder { sort.Sort(rules.MatchByOrder(matches)) } ctx := &rules.Context{Text: res.Text} applied := false for _, applier := range matches { ok, err := applier.Apply(ctx, p.options, res.Time) if err != nil { return nil, err } applied = ok || applied } if !applied { return nil, nil } res.Time, err = ctx.Time(res.Time) if err != nil { return nil, errors.Wrap(err, "bind context") } return &res, nil } // Add adds given rules to the main chain. func (p *Parser) Add(r ...rules.Rule) { p.rules = append(p.rules, r...) } // Use adds give functions to middlewares. func (p *Parser) Use(f ...func(string) (string, error)) { p.middleware = append(p.middleware, f...) } // SetOptions sets options object to use. func (p *Parser) SetOptions(o *rules.Options) { p.options = o } // New returns Parser initialised with given options. func New(o *rules.Options) *Parser { if o == nil { return &Parser{options: defaultOptions} } return &Parser{options: o} } // default options for internal usage var defaultOptions = &rules.Options{ Distance: 5, MatchByOrder: true, } // EN is a parser for English language var EN *Parser // RU is a parser for Russian language var RU *Parser // BR is a parser for Brazilian Portuguese language var BR *Parser // NL is a parser for Dutch language var NL *Parser func init() { EN = New(nil) EN.Add(en.All...) EN.Add(common.All...) RU = New(nil) RU.Add(ru.All...) RU.Add(common.All...) BR = New(nil) BR.Add(br.All...) BR.Add(common.All...) NL = New(nil) NL.Add(nl.All...) NL.Add(common.All...) }