├── .Rbuildignore ├── .github ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── pull_request_template.md └── workflows │ ├── prchecks.yml │ ├── prcommands.yml │ └── pushrelease.yml ├── .gitignore ├── DESCRIPTION ├── LICENSE ├── LICENSE.md ├── NAMESPACE ├── NEWS.md ├── R ├── class_duration.R ├── class_mdate.R ├── class_methods.R ├── coerce_extrema.R ├── coerce_from_messydate.R ├── coerce_tendency.R ├── coerce_to_messydate.R ├── component_annotate.R ├── component_extract.R ├── convert_contract.R ├── convert_expand.R ├── convert_sequence.R ├── data_battles.R ├── messydates-defunct.R ├── operate_arithmetic.R ├── operate_inequalities.R ├── operate_proportional.R ├── operate_set.R ├── operate_statements.R └── sysdata.rda ├── README.Rmd ├── README.md ├── cran-comments.md ├── data-raw └── battles.R ├── data └── battles.rda ├── dev_messydates.Rproj ├── inst ├── CITATION └── figures │ └── cheatsheet.pdf ├── man ├── battles.Rd ├── class_create.Rd ├── class_duration.Rd ├── class_make.Rd ├── coerce_extrema.Rd ├── coerce_from.Rd ├── coerce_tendency.Rd ├── coerce_to.Rd ├── component_annotate.Rd ├── component_extract.Rd ├── convert_contract.Rd ├── convert_expand.Rd ├── convert_sequence.Rd ├── defunct.Rd ├── figures │ ├── cheatsheet.png │ └── messydates_hexlogo.png ├── operate_arithmetic.Rd ├── operate_inequalities.Rd ├── operate_proportional.Rd ├── operate_set.Rd └── operate_statements.Rd ├── pkgdown ├── _pkgdown.yml └── extra.css ├── tests ├── testthat.R └── testthat │ ├── test-class_create.R │ ├── test-class_duration.R │ ├── test-class_make.R │ ├── test-coerce_from.R │ ├── test-coerce_resolve.R │ ├── test-coerce_to.R │ ├── test-component_annotate.R │ ├── test-component_extract.R │ ├── test-convert_contract.R │ ├── test-convert_expand.R │ ├── test-operate_arithmetic.R │ ├── test-operate_operators.R │ ├── test-operate_proportional.R │ ├── test-operate_set.R │ └── test-operate_statements.R └── vignettes-old ├── messydates.R ├── messydates.Rmd └── messydates.html /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^dev_messydates\.Rproj$ 2 | ^\.Rproj\.user$ 3 | ^README\.Rmd$ 4 | ^LICENSE\.md$ 5 | ^docs$ 6 | ^pkgdown$ 7 | ^\.github$ 8 | ^cran-comments\.md$ 9 | ^CRAN-RELEASE$ 10 | ^data-raw$ 11 | ^vignettes-old$ 12 | ^iso_standards 13 | ^CRAN-SUBMISSION$ 14 | ^article$ 15 | ^.DS_Store$ 16 | ^next$ 17 | -------------------------------------------------------------------------------- /.github/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the overall 26 | community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards 42 | of acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies 54 | when an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail 56 | address, posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at IHEID. 63 | All complaints will be reviewed and investigated promptly and fairly. 64 | 65 | All community leaders are obligated to respect the privacy and security of the 66 | reporter of any incident. 67 | 68 | ## Enforcement Guidelines 69 | 70 | Community leaders will follow these Community Impact Guidelines in determining 71 | the consequences for any action they deem in violation of this Code of Conduct: 72 | 73 | ### 1. Correction 74 | 75 | **Community Impact**: Use of inappropriate language or other behavior deemed 76 | unprofessional or unwelcome in the community. 77 | 78 | **Consequence**: A private, written warning from community leaders, providing 79 | clarity around the nature of the violation and an explanation of why the 80 | behavior was inappropriate. A public apology may be requested. 81 | 82 | ### 2. Warning 83 | 84 | **Community Impact**: A violation through a single incident or series of 85 | actions. 86 | 87 | **Consequence**: A warning with consequences for continued behavior. No 88 | interaction with the people involved, including unsolicited interaction with 89 | those enforcing the Code of Conduct, for a specified period of time. This 90 | includes avoiding interactions in community spaces as well as external channels 91 | like social media. Violating these terms may lead to a temporary or permanent 92 | ban. 93 | 94 | ### 3. Temporary Ban 95 | 96 | **Community Impact**: A serious violation of community standards, including 97 | sustained inappropriate behavior. 98 | 99 | **Consequence**: A temporary ban from any sort of interaction or public 100 | communication with the community for a specified period of time. No public or 101 | private interaction with the people involved, including unsolicited interaction 102 | with those enforcing the Code of Conduct, is allowed during this period. 103 | Violating these terms may lead to a permanent ban. 104 | 105 | ### 4. Permanent Ban 106 | 107 | **Community Impact**: Demonstrating a pattern of violation of community 108 | standards, including sustained inappropriate behavior, harassment of an 109 | individual, or aggression toward or disparagement of classes of individuals. 110 | 111 | **Consequence**: A permanent ban from any sort of public interaction within the 112 | community. 113 | 114 | ## Attribution 115 | 116 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 117 | version 2.0, 118 | available at https://www.contributor-covenant.org/version/2/0/ 119 | code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at https:// 128 | www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Contributions to `qData`, whether in the form of issue identification, bug fixes, new code or documentation are encouraged and welcome, both from research assistants and (early) users of the package: 4 | 5 | * [Submit an issue](#issues) 6 | * [Fix a bug or implement new features](#adding-new-code) 7 | * [Document existing code](#documentation) 8 | 9 | This outlines how to propose a change to a package from the Global Governance Observatory's ecosystem. Please note that the `qData` project is released with a [Contributor Code of Conduct](CODE_OF_CONDUCT.md). 10 | By contributing to this project, you agree to abide by its terms. 11 | 12 | ## Issues 13 | Please use the issue tracker on GitHub to identify problems or suggest new functionality, before submitting changes to the code. 14 | We use issues to identify bugs and tasks, discuss feature requests, and to track implementation of changes. 15 | 16 | When submitting an issue, please provide at least a 'Type' label that best describes what the issue is about. 17 | The most useful issues are ones that precisely identify a bug, or propose a test that should pass but instead fails. 18 | 19 | ## Adding new code 20 | Independent or assigned code contributions are most welcome. 21 | When writing new code, please follow [tidyverse style guide](https://style.tidyverse.org/index.html) which is based on 22 | [standard R guidelines](https://google.github.io/styleguide/Rguide.xml). 23 | 24 | It can help to use packages such as `lintr` and `goodpractice` to ensure these are followed. 25 | These packages are incorporated into the checks made when a pull request is made. 26 | The `styler` package fixes in a non-invasive way the code to adhere to the tidyverse formatting rules, and it also provides an RStudio Addins to help with this task. 27 | To run the `lintr` and `goodpractice` checks or use `styler` in a file run: 28 | 29 | ```r 30 | # basic lintr checking 31 | lintr::lint_package(path = "qData/") 32 | 33 | # goodpractices checks. Exclude length 80 34 | goodpractice::gp(path = "qData/", 35 | checks = all_checks()[-c(8)]) 36 | 37 | # styler fix some of the styling issues 38 | styler::style_file("filePath") 39 | ``` 40 | 41 | ## Pull request process 42 | The title of your PR should briefly describe the change. 43 | Please include a summary of the changes and which issues are fixed and the relevant motivation and context. 44 | List any dependencies that are required for this change, indicating whether this is a major (breaking), minor, or patch change. 45 | The body of your PR should contain `Fixes #issue-number`. 46 | A checklist is provided to check off the cases as the different elements listed have been completed to make sure all the steps have been respected. 47 | Make sure the package passes R CMD check by running `devtools::check()` before commiting changes to a pull request. 48 | 49 | If you want to make a bigger change, it's a good idea to first file an issue and make sure someone from the team agrees that it’s needed before openning a pull request. If you’ve found a bug, please file an issue that illustrates the bug with a minimal [reprex](https://www.tidyverse.org/help/#reprex). 50 | 51 | Please follow the qData pull request guideline (https://github.com/globalgov/qData/blob/main/.github/pull_request_template.md) 52 | 53 | ### Branches 54 | We use two **main branches** in this project: 55 | 56 | 1. The `origin/main` branch is reserved for fully functional releases of the model. 57 | When the `develop` branch reaches a stable point, a code maintainer merges it back into to the `master` branch, and tags it with a release number there. 58 | 59 | 2. The `origin/develop` branch reflects the latest model development stage. 60 | Contributers are encouraged to submit minor changes to this branch that enhance existing functionality. 61 | New features that may break existing functionality should be committed to supporting branches. 62 | 63 | We use two types of **supporting branches**: 64 | 65 | 3. *Feature branches* are used to develop new functionality. They exist as long as the feature is developed, and are then either merged into the `develop` branch for incorporation in a release, or deleted if the feature is abandoned. Feature branches should branch off from `origin/develop`. 66 | 67 | 4. *Hotfix branches* are used to provide fixes to severe bugs in the `main` branch. That way, the code maintainer does not have to incorporate (potentially unstable) changes from the `develop` branch to fix an issue. Branch names should be prefixed with `hotfix-`. 68 | 69 | This branching model is based on: https://nvie.com/posts/a-successful-git-branching-model/. 70 | 71 | ### Main Branch (code maintainer only) 72 | To create a release version of the code: 73 | 74 | 1. Ensure that the repository is up-to-date: `git pull`. 75 | 2. Switch to the **main** branch: `git checkout master`. 76 | 3. Merge changes to the **develop** branch: `git merge --no-ff develop`. 77 | 4. Tag release version: `git tag -a VX.Y.Z -m "VERSION-NAME"`. 78 | 5. Push changes to this repository `git push origin master --tags`. 79 | 80 | ### Develop Branch (minor changes to existing functionality) 81 | To make minor changes directly to the `develop` branch, follow standard git procedures: 82 | 83 | 1. Make sure you switched to the **develop** branch of the project: `git checkout -b develop`. 84 | 2. Make sure your local version of the code is up-to-date: `git pull origin develop`. 85 | 3. Make your changes 86 | 4. Stage your changes for a commit: `git add PATH-TO-CHANGED-FILE`. 87 | 5. Commit your changes [using an appropriate message](#commit-messages): `git commit -m "DESCRIPTION"`. 88 | 6. Push your commit: `git push origin develop`. 89 | 90 | ### Feature Branches (new functionality) 91 | To create a new feature branch: `git checkout -b myfeature develop`. 92 | 93 | To merge a feature branch back into `develop`: 94 | ``` 95 | git checkout develop 96 | git merge --no-ff myfeature 97 | git branch -d myfeature 98 | git push origin develop 99 | ``` 100 | 101 | ### Hotfix Branches (to fix critical bugs in release versions) 102 | To create a new hotfix branch: `git checkout -b hotfix-VERSION master`. 103 | 104 | To merge a hotfix back into `master` (code maintainer only): 105 | ``` 106 | git checkout master 107 | git merge --no-ff hotfix-VERSION 108 | git tag -a VERSION 109 | git push origin develop 110 | ``` 111 | And into develop: 112 | ``` 113 | git checkout develop 114 | git merge --no-ff hotfix-VERSION 115 | ``` 116 | 117 | Every hotfix should increment the [PATCH digit of the version number](#versioning): a hotfix branch for `V1.3.0` is named `hotfix-V1.3.1`, and the new release is tagged as `V1.3.1`. 118 | 119 | Once merged into `master` and `develop`, the hotfix branch can be deleted: `git branch -d hotfix-VERSION`. 120 | 121 | ### Commit messages 122 | Commits that relate to existing issues should reference the updated status of those issues, and mention the issue number (preceded by a hash symbol: #) in the commit description: 123 | 124 | ``` Resolved #31 by adding a new function that does things, also updated documentation ``` 125 | 126 | Where the issue hash (i.e. #31) is preceded by `resolve`, `resolves`, `resolved`, `close`, `closes`, `closed`, `fix`, `fixes`, or `fixed` (capitalised or not), the status of the issue(s) mentioned is updated automatically. 127 | Our current syntactical standard is to mention the issue first and then provide a short description of what the committed changes do in relation to that issue. 128 | Any ancillary changes can be mentioned after a comma. 129 | 130 | It should all be written in a single line, like so: #`{verb} {issue} {describe main action/changes}, {additional actions/changes}`. 131 | 132 | Note that it is important to write a structured commit message to improve efficiency in collaboration. 133 | Please make sure of: 134 | * Making the title clear and concise with correct reference to an issue as described above. 135 | * Using a subject line in the description part when the commit message outlines many different changes. 136 | * Explaining in details the changes made and why you made them by using bullet points. 137 | * Separating each paragraph with a blank line. 138 | 139 | ### Testing 140 | We use the [testthat](https://testthat.r-lib.org/) package to write unit tests. 141 | By convention, tests are located in [testthat/tests/](qData/tests/testthat). 142 | 143 | You should verify that all tests pass before issuing a commit to existing code. 144 | To run all tests for the latest version manually: 145 | ``` 146 | git pull 147 | library("testthat") 148 | testthat::test_dir("tests/testthat") 149 | ``` 150 | 151 | When writing a new function, consider writing a unit test for that function. 152 | We follow several conventions for writing tests: 153 | 154 | - A unit test file should test one or more aspects of a single function. This makes it easier to identify the source of bugs, and prevents lower-level tests from failing when higher-level functions change. 155 | 156 | - The [naming convention](https://www.tidyverse.org/articles/2019/04/testthat-2-1-0/) for test files is: ``test-FILENAME_IN_R_DIRECTORY-FUNCTION_NAME.R``, i.e. test files are named after the file containing the original function in the [R](qData/R) directory, pre-fixed with "test", and optionally post-fixed with the name of the function that is being tested. 157 | 158 | - If a test requires auxiliary functions from the package, e.g. to initialize a network with sample data, these belong in a helper file. There should be only one helper file for each `R` file, named ``helper-FILENAME_IN_R_DIRECTORY-FUNCTION_NAME.R``. Re-using existing test data is preferable to creating new data for every test. 159 | 160 | ## Documentation 161 | A final way of contributing to the package is in developing the vignettes/articles that illustrate the value added in the package. 162 | Please contact us directly with proposals for updating the documentation, or submit an issue if existing documentation is unclear. 163 | 164 | ## Versioning 165 | Note that the package is versioned according to [semantic versioning](https://www.jvandemo.com/a-simple-guide-to-semantic-versioning/). 166 | This means that versions follow the Major.Minor.Patch semantic format. 167 | 168 | Each minor or major level version is also given a new version name, which should be updated in the `zzz.R` file. 169 | 170 | ## For developers using MacOS 171 | Develops using MacOS might meet problems compiling the packages since the compiling configuration of R in MacOS is usually incorrect. If one meet an error with error info "/usr/bin/ld: cannot find -lgfortran", then he can either correct the configuration himself or follows the following steps to solve the problem: 172 | 1. Run ".libPaths()" command in R and get a path, e.g. one might get "/Library/Frameworks/R.framework/Versions/3.6/Resources/library" 173 | 2. If the path one get in the first step is "something/library", then open the file "something/etc/Makeconf" and comment out the line starting with "FLIBS". e.g. one might open the file " /Library/Frameworks/R.framework/Versions/3.6/Resources/etc/Makeconf" and change the line "FLIBS = -L/usr/local/gfortran/lib/gcc/x86_64-apple-darwin15/6.1.0 -L/usr/local/gfortran/lib -lgfortran -lquadmath -lm" to "#FLIBS = -L/usr/local/gfortran/lib/gcc/x86_64-apple-darwin15/6.1.0 -L/usr/local/gfortran/lib -lgfortran -lquadmath - lm" -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Describe a bug you've seen 4 | --- 5 | 6 | ## Checklist before proceeding 7 | 8 | - [ ] I have restarted R and the bug is still there 9 | - [ ] I have restarted my OS and the bug is still there 10 | 11 | ## Brief description of the problem 12 | 13 | Please briefly describe your problem and what output you expect. 14 | 15 | ## Please include a minimal reproducible example (AKA a reprex). 16 | 17 | If you've never heard of a [reprex](http://reprex.tidyverse.org/) before, start by reading . 18 | 19 | ```r 20 | # insert reprex here 21 | ``` 22 | 23 | *If you have a question, please don't use this form. Instead, ask on or .* 24 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Make a case for a new feature 4 | --- 5 | 6 | ## Brief description of the problem 7 | 8 | As a , I want to so that . 9 | 10 | ## Who is this affecting? 11 | 12 | ## Expected results (desired performance) 13 | 14 | ## Actual results (current performance) 15 | 16 | *If you have a question, please don't use this form. Instead, ask on or .* 17 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | # Description 2 | 3 | # Checklist: 4 | 5 | - [ ] PR form 6 | - [ ] I have given this pull request an informative title 7 | - [ ] Description above itemizes changes under subtitles, e.g. "## Collection"" 8 | - [ ] Any closed, fixed, or related issues are referenced and explained in the description above, e.g. "Fixed #0 by adding A" 9 | - [ ] Package builds on my OS without issues 10 | - [ ] PR checks all pass for latest commit 11 | - [ ] Package builds on Mac 12 | - [ ] Package builds on Windows 13 | - [ ] Package builds on Linux 14 | - [ ] CodeCov check: Package improves or maintains good test coverage 15 | - [ ] CodeFactor check: Package improves or maintains good style 16 | - [ ] Documentation 17 | - [ ] Any new or modified functions or data have roxygen style documentation in their .R scripts 18 | - [ ] Any longer functions are commented inline so that it is easier to debug in the future 19 | - [ ] PR description above and the NEWS.md file are aligned 20 | - [ ] DESCRIPTION file version is bumped by the appropriate increment (major, minor, patch) 21 | -------------------------------------------------------------------------------- /.github/workflows/prchecks.yml: -------------------------------------------------------------------------------- 1 | on: 2 | pull_request: 3 | branches: 4 | - main 5 | 6 | name: Binary checks 7 | 8 | jobs: 9 | 10 | build: 11 | name: Build for ${{ matrix.config.os }} 12 | runs-on: ${{ matrix.config.os }} 13 | strategy: 14 | fail-fast: false 15 | matrix: 16 | config: 17 | - {os: macOS-latest, r: 'release', artifact_name: '*.tar.gz', asset_name: macOS} 18 | - {os: windows-latest, r: 'release', artifact_name: '*.zip', asset_name: winOS} 19 | - {os: ubuntu-latest, r: 'release', artifact_name: '*.tar.gz', asset_name: linuxOS} 20 | 21 | env: 22 | R_REMOTES_NO_ERRORS_FROM_WARNINGS: true 23 | RSPM: ${{ matrix.config.rspm }} 24 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 25 | 26 | steps: 27 | - uses: actions/checkout@v2 28 | 29 | - uses: r-lib/actions/setup-r@v2 30 | with: 31 | r-version: ${{ matrix.config.r }} 32 | 33 | - uses: r-lib/actions/setup-pandoc@v2 34 | 35 | - uses: r-lib/actions/setup-r-dependencies@v2 36 | with: 37 | cache-version: 2 38 | needs: check, build 39 | extra-packages: | 40 | any::rcmdcheck 41 | 42 | - uses: r-lib/actions/check-r-package@v2 43 | env: 44 | _R_CHECK_FORCE_SUGGESTS_: false 45 | with: 46 | upload-snapshots: true 47 | 48 | - name: Binary 49 | run: | 50 | pkgbuild::clean_dll() 51 | binary <- pkgbuild::build(binary = TRUE, needs_compilation = TRUE, compile_attributes = TRUE) 52 | dir.create("build") 53 | file.copy(binary, "build") 54 | shell: Rscript {0} 55 | 56 | - name: Save binary artifact 57 | uses: actions/upload-artifact@v4 58 | with: 59 | name: ${{ matrix.config.asset_name }} 60 | path: build/ 61 | 62 | - name: Calculate code coverage 63 | run: Rscript -e "covr::codecov()" 64 | if: runner.os == 'macOS' 65 | 66 | - name: Lint 67 | run: lintr::lint_package() 68 | if: runner.os == 'macOS' 69 | shell: Rscript {0} 70 | 71 | - name: Spell check 72 | run: spelling::spell_check_package() 73 | if: runner.os == 'macOS' 74 | shell: Rscript {0} 75 | -------------------------------------------------------------------------------- /.github/workflows/prcommands.yml: -------------------------------------------------------------------------------- 1 | on: 2 | issue_comment: 3 | types: [created] 4 | name: Commands 5 | jobs: 6 | document: 7 | if: startsWith(github.event.comment.body, '/document') 8 | name: document 9 | runs-on: macOS-latest 10 | env: 11 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 12 | steps: 13 | - uses: actions/checkout@v2 14 | - uses: r-lib/actions/pr-fetch@master 15 | with: 16 | repo-token: ${{ secrets.GITHUB_TOKEN }} 17 | - uses: r-lib/actions/setup-r@master 18 | - name: Install dependencies 19 | run: Rscript -e 'install.packages(c("remotes", "roxygen2"))' -e 'remotes::install_deps(dependencies = TRUE, type = "binary")' 20 | - name: Document 21 | run: Rscript -e 'roxygen2::roxygenise()' 22 | - name: commit 23 | run: | 24 | git config --local user.email "actions@github.com" 25 | git config --local user.name "GitHub Actions" 26 | git add man/\* NAMESPACE 27 | git commit -m 'Document' 28 | - uses: r-lib/actions/pr-push@master 29 | with: 30 | repo-token: ${{ secrets.GITHUB_TOKEN }} 31 | style: 32 | if: startsWith(github.event.comment.body, '/style') 33 | name: style 34 | runs-on: macOS-latest 35 | env: 36 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 37 | steps: 38 | - uses: actions/checkout@v2 39 | - uses: r-lib/actions/pr-fetch@master 40 | with: 41 | repo-token: ${{ secrets.GITHUB_TOKEN }} 42 | - uses: r-lib/actions/setup-r@master 43 | - name: Install dependencies 44 | run: Rscript -e 'install.packages("styler")' 45 | - name: Style 46 | run: Rscript -e 'styler::style_pkg()' 47 | - name: commit 48 | run: | 49 | git config --local user.email "actions@github.com" 50 | git config --local user.name "GitHub Actions" 51 | git add \*.R 52 | git commit -m 'Style' 53 | - uses: r-lib/actions/pr-push@master 54 | with: 55 | repo-token: ${{ secrets.GITHUB_TOKEN }} 56 | -------------------------------------------------------------------------------- /.github/workflows/pushrelease.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - main 5 | 6 | name: Check and release 7 | 8 | jobs: 9 | 10 | build: 11 | name: Build for ${{ matrix.config.os }} 12 | runs-on: ${{ matrix.config.os }} 13 | strategy: 14 | fail-fast: false 15 | matrix: 16 | config: 17 | - {os: macOS-latest, r: 'release', artifact_name: '*.tar.gz', asset_name: macOS} 18 | - {os: windows-latest, r: 'release', artifact_name: '*.zip', asset_name: winOS} 19 | - {os: ubuntu-latest, r: 'release', artifact_name: '*.tar.gz', asset_name: linuxOS} 20 | 21 | env: 22 | R_REMOTES_NO_ERRORS_FROM_WARNINGS: true 23 | RSPM: ${{ matrix.config.rspm }} 24 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 25 | 26 | steps: 27 | - uses: actions/checkout@v2 28 | 29 | - uses: r-lib/actions/setup-r@v2 30 | with: 31 | r-version: ${{ matrix.config.r }} 32 | 33 | - uses: r-lib/actions/setup-pandoc@v2 34 | 35 | - uses: r-lib/actions/setup-r-dependencies@v2 36 | with: 37 | cache-version: 2 38 | needs: check, build 39 | extra-packages: | 40 | any::rcmdcheck 41 | any::remotes 42 | 43 | - uses: r-lib/actions/check-r-package@v2 44 | env: 45 | _R_CHECK_FORCE_SUGGESTS_: false 46 | with: 47 | upload-snapshots: true 48 | 49 | - name: Binary 50 | run: | 51 | pkgbuild::clean_dll() 52 | binary <- pkgbuild::build(binary = TRUE, needs_compilation = TRUE, compile_attributes = TRUE) 53 | dir.create("build") 54 | file.copy(binary, "build") 55 | shell: Rscript {0} 56 | 57 | - name: Save binary artifact 58 | uses: actions/upload-artifact@v4 59 | with: 60 | name: ${{ matrix.config.asset_name }} 61 | path: build/ 62 | 63 | - name: Calculate code coverage 64 | if: runner.os == 'macOS-latest' 65 | run: Rscript -e "covr::codecov()" 66 | 67 | release: 68 | name: Bump version and release 69 | if: ${{ always() }} 70 | needs: build 71 | runs-on: ubuntu-latest 72 | permissions: 73 | contents: write 74 | steps: 75 | - name: Checkout one 76 | uses: actions/checkout@v4 77 | with: 78 | fetch-depth: '0' 79 | - name: Bump version and push tag 80 | id: newtag 81 | uses: anothrNick/github-tag-action@1.39.0 82 | env: 83 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 84 | WITH_V: true 85 | DEFAULT_BUMP: patch 86 | RELEASE_BRANCHES: main 87 | - name: Checkout two 88 | uses: actions/checkout@v4 89 | 90 | - name: Extract version 91 | run: | 92 | echo "PACKAGE_VERSION=$(grep '^Version' DESCRIPTION | sed 's/.*: *//')" >> $GITHUB_ENV 93 | echo "PACKAGE_NAME=$(grep '^Package' DESCRIPTION | sed 's/.*: *//')" >> $GITHUB_ENV 94 | 95 | - name: Download binaries 96 | uses: actions/download-artifact@v4 97 | 98 | - name: Rename binaries release 99 | shell: bash 100 | run: | 101 | ls -R 102 | cp ./macOS/${{ env.PACKAGE_NAME }}_${{ env.PACKAGE_VERSION }}*.tgz . 103 | cp ./linuxOS/${{ env.PACKAGE_NAME }}_${{ env.PACKAGE_VERSION }}*.tar.gz . 104 | cp ./winOS/${{ env.PACKAGE_NAME }}_${{ env.PACKAGE_VERSION }}*.zip . 105 | echo "Renamed files" 106 | ls messydates_* 107 | 108 | - name: Create Release and Upload Assets 109 | id: create_release 110 | uses: softprops/action-gh-release@v2 111 | with: 112 | tag_name: ${{ steps.newtag.outputs.tag }} 113 | name: Release ${{ steps.newtag.outputs.tag }} 114 | draft: false 115 | prerelease: false 116 | fail_on_unmatched_files: true 117 | # Specify the assets you want to upload 118 | files: | 119 | messydates_*.tgz 120 | messydates_*.tar.gz 121 | messydates_*.zip 122 | env: 123 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 124 | 125 | pkgdown: 126 | name: Build and deploy website 127 | if: ${{ always() }} 128 | needs: release 129 | runs-on: macOS-latest 130 | env: 131 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 132 | steps: 133 | - uses: actions/checkout@v2 134 | 135 | - uses: r-lib/actions/setup-r@v2 136 | 137 | - uses: r-lib/actions/setup-pandoc@v1 138 | 139 | - uses: r-lib/actions/setup-r-dependencies@v2 140 | with: 141 | cache-version: 2 142 | extra-packages: | 143 | any::rcmdcheck 144 | any::pkgdown 145 | any::rsconnect 146 | needs: check 147 | 148 | - name: Install package 149 | run: R CMD INSTALL . 150 | 151 | - name: Deploy package 152 | run: | 153 | git config --local user.email "actions@github.com" 154 | git config --local user.name "GitHub Actions" 155 | Rscript -e 'pkgdown::deploy_to_branch(new_process = FALSE)' 156 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .Rproj.user 2 | .Rhistory 3 | .RData 4 | .Ruserdata 5 | docs 6 | CRAN-SUBMISSION 7 | iso_standards* 8 | *.DS_Store 9 | article/article.log 10 | article/article.pdf 11 | article/article.tex 12 | article/jss.bst 13 | article/jss.cls 14 | article/jsslogo.jpg 15 | article/references.bib 16 | article/article_files 17 | vignettes-old 18 | next 19 | 20 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: messydates 2 | Title: A Flexible Class for Messy Dates 3 | Description: Contains a set of tools for constructing and coercing 4 | into and from the "mdate" class. 5 | This date class implements ISO 8601-2:2019(E) and 6 | allows regular dates to be annotated 7 | to express unspecified date components, 8 | approximate or uncertain date components, 9 | date ranges, and sets of dates. 10 | This is useful for describing and analysing temporal information, 11 | whether historical or recent, where date precision may vary. 12 | Version: 0.5.4 13 | Date: 2025-06-02 14 | Authors@R: 15 | c(person(given = "James", 16 | family = "Hollway", 17 | role = c("cre", "aut", "ctb"), 18 | email = "james.hollway@graduateinstitute.ch", 19 | comment = c("IHEID", ORCID = "0000-0002-8361-9647")), 20 | person(given = "Henrique", 21 | family = "Sposito", 22 | role = "ctb", 23 | comment = c("IHEID", ORCID = "0000-0003-3420-6085")), 24 | person(given = "Jael", 25 | family = "Tan", 26 | role = "ctb", 27 | comment = c("IHEID", ORCID = "0000-0002-6234-9764")), 28 | person(given = "Nathan", 29 | family = "Werth", 30 | role = "ctb")) 31 | License: MIT + file LICENSE 32 | Encoding: UTF-8 33 | Roxygen: list(markdown = TRUE) 34 | RoxygenNote: 7.3.2 35 | LazyData: true 36 | Depends: 37 | R (>= 4.0) 38 | Imports: 39 | stringi, 40 | purrr, 41 | lubridate, 42 | dplyr 43 | Suggests: 44 | testthat (>= 3.0.0), 45 | rmarkdown 46 | URL: https://globalgov.github.io/messydates/ 47 | BugReports: https://github.com/globalgov/messydates/issues 48 | Config/Needs/build: 49 | roxygen2, 50 | devtools 51 | Config/Needs/check: 52 | covr, 53 | lintr, 54 | spelling 55 | Config/Needs/website: 56 | pkgdown 57 | Config/testthat/edition: 3 58 | Config/testthat/parallel: true 59 | Config/testthat/start-first: logical-operators 60 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | YEAR: 2021 2 | COPYRIGHT HOLDER: messydates authors 3 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | Copyright (c) 2021 messydates authors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | S3method("%><%",mdate) 4 | S3method("%>=<%",mdate) 5 | S3method("%g%",mdate) 6 | S3method("%ge%",mdate) 7 | S3method("%intersect%",mdate) 8 | S3method("%l%",mdate) 9 | S3method("%le%",mdate) 10 | S3method("%union%",mdate) 11 | S3method("+",mdate) 12 | S3method("-",mdate) 13 | S3method("<",mdate) 14 | S3method("<=",mdate) 15 | S3method(">",mdate) 16 | S3method(">=",mdate) 17 | S3method("[",mdate) 18 | S3method("[<-",mdate) 19 | S3method("[[",mdate) 20 | S3method("[[<-",mdate) 21 | S3method(as.Date,mdate) 22 | S3method(as.POSIXct,mdate) 23 | S3method(as.POSIXlt,mdate) 24 | S3method(as.data.frame,mdate) 25 | S3method(as.double,mdate) 26 | S3method(as.list,mdate) 27 | S3method(as_messydate,Date) 28 | S3method(as_messydate,POSIXct) 29 | S3method(as_messydate,POSIXlt) 30 | S3method(as_messydate,character) 31 | S3method(as_messydate,list) 32 | S3method(as_messydate,mdate) 33 | S3method(as_messydate,numeric) 34 | S3method(c,mdate) 35 | S3method(max,mdate) 36 | S3method(mean,mdate) 37 | S3method(median,mdate) 38 | S3method(messyduration,character) 39 | S3method(messyduration,mdate) 40 | S3method(min,mdate) 41 | S3method(modal,mdate) 42 | S3method(precision,mdate) 43 | S3method(print,mdate) 44 | S3method(print,mdates_duration) 45 | S3method(random,character) 46 | S3method(random,mdate) 47 | S3method(rep,mdate) 48 | S3method(seq,mdate) 49 | S3method(vmax,mdate) 50 | S3method(vmean,mdate) 51 | S3method(vmedian,mdate) 52 | S3method(vmin,mdate) 53 | S3method(vmodal,mdate) 54 | S3method(vrandom,mdate) 55 | export("%><%") 56 | export("%>=<%") 57 | export("%g%") 58 | export("%ge%") 59 | export("%intersect%") 60 | export("%l%") 61 | export("%le%") 62 | export("%union%") 63 | export(as_approximate) 64 | export(as_messydate) 65 | export(as_uncertain) 66 | export(contract) 67 | export(day) 68 | export(expand) 69 | export(is_approximate) 70 | export(is_bce) 71 | export(is_element) 72 | export(is_intersecting) 73 | export(is_messydate) 74 | export(is_precise) 75 | export(is_similar) 76 | export(is_subset) 77 | export(is_uncertain) 78 | export(make_messydate) 79 | export(md_intersect) 80 | export(md_multiset) 81 | export(md_union) 82 | export(mdate) 83 | export(messyduration) 84 | export(modal) 85 | export(month) 86 | export(new_messydate) 87 | export(new_messyduration) 88 | export(on_or_after) 89 | export(on_or_before) 90 | export(precision) 91 | export(random) 92 | export(validate_messydate) 93 | export(validate_messyduration) 94 | export(vmax) 95 | export(vmean) 96 | export(vmedian) 97 | export(vmin) 98 | export(vmodal) 99 | export(vrandom) 100 | export(year) 101 | importFrom(dplyr,first) 102 | importFrom(dplyr,last) 103 | importFrom(dplyr,lead) 104 | importFrom(dplyr,tibble) 105 | importFrom(lubridate,NA_Date_) 106 | importFrom(lubridate,as_date) 107 | importFrom(lubridate,years) 108 | importFrom(lubridate,ymd) 109 | importFrom(purrr,map) 110 | importFrom(purrr,pmap_chr) 111 | importFrom(stats,median) 112 | importFrom(stringi,stri_detect_regex) 113 | importFrom(stringi,stri_extract_all_regex) 114 | importFrom(stringi,stri_replace_all_fixed) 115 | importFrom(stringi,stri_replace_all_regex) 116 | importFrom(utils,str) 117 | -------------------------------------------------------------------------------- /NEWS.md: -------------------------------------------------------------------------------- 1 | # messydates 0.5.4 2 | 3 | ## Coercion 4 | 5 | - Improved how `as_messydates()` handles text with dates in American format, 6 | e.g. October 10, 2010 (fixes #86) 7 | 8 | # messydates 0.5.3 9 | 10 | ## Components 11 | 12 | - Improved `year()` to be faster and work on durations 13 | - Improved `precision()` 14 | - `precision()` is now a S3 generic, dispatching to `precision.mdate()` 15 | - `precision.mdate()` now returns the inverse of the previous measure, 16 | meaning maximising precision makes more sense 17 | 18 | # messydates 0.5.2 19 | 20 | ## Package 21 | 22 | - Moved `mreport()` to `{manydata}` 23 | - Consolidated and renamed scripts internally 24 | 25 | ## Coerce to 26 | 27 | - Fixed pkgdown#2855 by fixing how as_messydate methods interpret infinite dates 28 | - Fixed time zone defaults in `as.POSIXct.mdate()` and `as.POSIXlt.mdate()` 29 | - Fixed set bug in `validate_messydate()` 30 | 31 | ## Coerce from 32 | 33 | - Renamed `as.numeric()` to `as.double()` to fix S3 dispatching 34 | - Separated extrema functions into `min.mdate()` and `max.mdate()` for summaries 35 | and `vmin.mdate()` and `vmax.mdate()` for vector coercion 36 | - Separated tendency functions into `mean.mdate()`, `median.mdate()`, and `modal.mdate()` for summaries 37 | and `vmean.mdate()`, `vmedian.mdate()`, and `vmodal.mdate()` for vector coercion 38 | - Vector coercion previously in `random.mdate()` now in `vrandom.mdate()` 39 | - Improved how coercion/resolution functions handle BCE dates 40 | 41 | ## Manipulation 42 | 43 | - Fixed how `precision()` calculates precision 44 | 45 | # messydates 0.5.1 46 | 47 | ## Package 48 | 49 | - Fixed redirected url in README 50 | - Fixed pkgdown help links 51 | 52 | # messydates 0.5.0 53 | 54 | ## Package 55 | 56 | - Dropped the vignettes as they were 'outdated' 57 | - Dropped a number of tests to provide rapid testing framework 58 | - Dropped `{tibble}` dependency by just using `{dplyr}` 59 | - Updated Github workflows 60 | - Updated testthat to version 3, tests now run in parallel 61 | - Updated pkgdown to bootstrap 5 62 | - Updated DESCRIPTION with config packages 63 | 64 | ## Functions 65 | 66 | - Moved from `{stringr}` to `{stringi}` for _speed_ 67 | - Added `as.numeric.mdate()` and `as_messydate.numeric()` for coercing between messydates and numbers (closes #85) 68 | - Added `seq.mdate()` for creating sequences from one or two messydates 69 | - This includes correct sequences for leap years and historical dates including before the common era 70 | - Added `is_bce()` for testing whether dates are from before the common era 71 | - Added `stri_squish()` helper for trimming white space everywhere 72 | - Improved `c.mdate()` so that it will strip class from an `mdate` object, as expected 73 | - Improved `is_uncertain()` and `is_approximate()` so that they also recognise `%` annotations 74 | - Improved `min.mdate()`, `max.mdate()`, and `modal.mdate()` to avoid using `expand()` and consequently run much faster 75 | - Improved `min.mdate()`, `max.mdate()`, `modal.mdate()`, `mean.mdate()`, `median.mdate()`, and `random.mdate()` by adding `recursive` argument for resolving vectors down to a scalar 76 | - Fixed bug in `messyduration.mdate()` where the minimum of an underspecified later date was used 77 | - Fixed bug in `as_messydate()` where zero padding for early dates was not added correctly 78 | 79 | # messydates 0.4.1 80 | 81 | ## Package 82 | 83 | * The package now depends on R versions bigger or equal to 4.0 since functions for subsetting and comparing 'mdate' objects rely on functions introduced in that version 84 | 85 | ## Functions 86 | 87 | * Closed #83 by fixing how logical comparisons works for negative and year only dates 88 | 89 | # messydates 0.4.0 90 | 91 | ## Functions 92 | 93 | * Closed #46 by adding the `mdates_duration` class that introduces methods to annotate a duration or period with representations of its uncertainty 94 | * Closed #72 by fixing issues with double unspecified components not being contracted correctly 95 | * Closed #73 by fixing bugs with the conversion of dates where month is spelled 96 | * Closed #74 and #82 by adding other logical comparison operators for 'mdate' objects (e.g. `<`, `>`, `<=`, `>=`) (thanks @WerthPADOH) 97 | * Closed #76 by adding proportional operators that calculate the proportion of messy dates meeting logical tests (e.g. `%l%` `%le%`, `%g%`, `%ge%`, `%><%`, `%>=<%`) 98 | * Closed #77 by adding basic vector methods for subsetting and data frames (thanks @WerthPADOH) 99 | * Added alias function `mdate()` for `as_messydate()` 100 | * Renamed set family of functions to work as operators (i.e. `%intersect%` and `%union%`) 101 | * Replaced `is_element()` by `is_subset()` for clarity and consistency 102 | * Closed #80 by updating `make_messydates()` function to also construct ranges of dates 103 | 104 | # messydates 0.3.5 105 | 106 | ## Functions 107 | 108 | * Updated how `contract()` function checks if 'mdate' object has been expanded 109 | 110 | # messydates 0.3.4 111 | 112 | ## Package 113 | 114 | * Updated 'battles' internal data 115 | * Corrected issues with zero padding for certain date ranges 116 | * Added 'US_party' and 'N_actors' additional variables for replication purposes 117 | 118 | ## Functions 119 | 120 | * Closed #68 by updating `as_messydate()` function 121 | * Fixed bugs with zero padding for ranges of dates 122 | * Fixed bugs with the re-ordering of months and day components for incorrectly specified dates 123 | * Closed #69 by updating `contract()` function to 'expand' dates before 'contracting' them 124 | * Updated `expand()` function to handle, and properly convert, date objects that are not 'mdate' 125 | 126 | # messydates 0.3.3 127 | 128 | ## Package 129 | 130 | * Moved cheatsheet.pdf to 'inst' folder instead of the 'man' folder 131 | 132 | # messydates 0.3.2 133 | 134 | ## Package 135 | 136 | * Closed #64 by updating failing tests to test for other aspects instead of the printing of negative dates across OS 137 | * Closed #65 by updating cheatsheet for new package changes 138 | 139 | ## Functions 140 | 141 | * Closed #62 by adding "resequence" as an argument to `as_messydates()` for explicit date format conversion, if necessary 142 | * Closed #63 by fixing issues with unnecessary white spaces added in date conversion 143 | 144 | # messydates 0.3.1 145 | 146 | ## Package 147 | 148 | * Updated README by removing unattractive package startup messages 149 | * Updated `battles` data by adding 'parties' variable 150 | 151 | ## Functions 152 | 153 | * Closed #54 by adding new `mreport()` function to properly report on data containing 'mdate' variables 154 | * Updated `expand()` function 155 | * Fixed bug with the expansion of approximate dates 156 | * Removed unnecessary function message 157 | 158 | # messydates 0.3.0 159 | 160 | ## Package 161 | 162 | - Closed #51 by changing object class name to `mdate` 163 | - Note that this is a *breaking* change 164 | - Closed #41 by creating `{skimr}` template for `mdate` class 165 | 166 | ## Functions 167 | 168 | - Updated coercion to messy dates 169 | - Closed #26 by adding "resequence" argument to `as_messydate()` allowing users to choose component order of ambiguous dates 170 | - Closed #45 by improving how `as_messydate()` re-orders 6 digit date components if necessary 171 | - Closed #48 by adding zero padding incomplete date ranges and sets of dates 172 | - Updated `as_messydate()` to also extract dates from text strings 173 | - Added `is_precise()` function that provides a logical test for precise dates 174 | - Updated messy dates expansion 175 | - Updated `expand()` to allow for the expansion of incomplete date ranges and sets of dates 176 | - Closed #49 by updating resolve functions to only expand dates if they are not precise 177 | 178 | # messydates 0.2.1 179 | 180 | ## Package 181 | 182 | - Added a vignette for working with the `{messydates}` package 183 | 184 | ## Functions 185 | 186 | - Closed #9 by adding arithmetic operations for working with `messydt` objects 187 | - Added S3 methods for "+" and "-" operators 188 | - Added `add()` and `subtract()` helper functions for arithmetic operations 189 | - Updated `expand()` function 190 | - Closed #31 by updating how approximate dates are expanded to account for leap years 191 | - Closed #34 by updating `expand()` to manage negative dates 192 | - Added `expand_negative_dates()` helper function for expanding ranges of negative dates 193 | - Updated functions that coerce from `messydt` objects to `Date` to manage negative dates 194 | - Added `negative_dates()` helper function to coerce negative `messydt` dates 195 | - Closed #39 by updating how resolve mean methods work for negative dates 196 | - Closed #40 by updating contract function to manage the contraction of negative dates 197 | - Added `compact_negative_dates()` helper function to compact negative date ranges 198 | - Added `is.sequence()` as a helper function to check if dates are a range 199 | - Updated resequence script to export `interleave()` function 200 | 201 | # messydates 0.2.0 202 | 203 | ## Package 204 | 205 | - Added PANARCHIC project details to README file 206 | - Added cheatsheet 207 | - Added a new CSS style to website and updated functions displayed 208 | - Addressed workflow actions issues 209 | - Updated pushrelease.yml workflow actions file to stop installing `{messydates}` from Github 210 | - Updated README file to stop installing `{messydates}` from Github 211 | - Fixed Codecov test coverage URL on README file for CRAN submission 212 | 213 | ## Functions 214 | 215 | - Expanded on messydates checks for class validity 216 | - Fixed bugs for `make_messydate()` 217 | - Added annotation functions and standardized annotation so that it is consistent with ISO2019E standards. 218 | - `on_or_before()` 219 | - `on_or_after()` 220 | - `as_approximate()` 221 | - `as_uncertain()` (includes discrimination between month uncertainty and day and month uncertainty) 222 | - Updated `as_messydate()` by adding zero padding for month, day or year 223 | - Updated `resequence()` to work consistently with messydate objects 224 | - Updated `expand()` function to expand imprecise, unspecified, approximate, uncertain, and negative dates according to approximate ranges and added tests 225 | - Updated `precision()` to return the lengths of expanded dates 226 | - Updated `median()` in resolve family of functions to work with changes to `expand()` 227 | - Added tests for functions 228 | - Added tests for `expand()` 229 | - Added tests for `contract()` 230 | - Added tests for `precision()` 231 | - Added tests for `coerce_from_messydate()` 232 | 233 | # messydates 0.1.1 234 | 235 | ## Package 236 | 237 | - Updated README with some more explanation about what the package does/offers 238 | - Fixed URL to the package website 239 | 240 | # messydates 0.1.0 241 | 242 | ## Package 243 | 244 | - Updated call to `messydt` class in DESCRIPTION file 245 | 246 | ## Functions 247 | 248 | - Updated documentation for `as_messydate()` functions 249 | - Updated documentation for `expand()` function 250 | - Updated documentation for resolve family of functions 251 | - Updated documentation for coerce from family of functions 252 | - Updated documentation for coerce to family of functions 253 | 254 | # messydates 0.0.1 255 | 256 | ## Package 257 | 258 | - Setup `{messydates}` package 259 | - Added `DESCRIPTION` file 260 | - Added `R` folder 261 | - Added `LICENSE` file 262 | - Added `NAMESPACE` file 263 | - Added `NEWS` file 264 | - Added `README` files 265 | - Added `.github` folder and files 266 | - Added `tests` folder and files 267 | - Setup pkgdown website 268 | - Added package logo 269 | 270 | ## Functions 271 | 272 | - Added a new `messydt` class which follows the latest ISO 8601 (2019) standards 273 | - Added validation checks for messydt class 274 | - Added print methods for messydt class 275 | - Added `as_messydate()` function to coerce from date objects to messydate 276 | - `as_messydate()` standardises date order, separators and ambiguity 277 | - Added date class coercion 278 | - Added POSIXct class coercion 279 | - Added POSIXlt class coercion 280 | - Added character class coercion 281 | - Added functions to coerce from messydate objects to other date classes 282 | - Added `as.Date.messydt()` for coercing to date class 283 | - Added `as.POSIXct.messydt()` for coercing to POSIXct class 284 | - Added `as.POSIXlt.messydt()` for coercing to POSIXlt class 285 | - Added `expand()` function for expanding ranged and uncertain dates 286 | - Added functions to resolve expanded dates 287 | - Added `min.messydt()` to get minimum value from expanded range 288 | - Added `max.messydt()` to get maximum value from expanded range 289 | - Added `median.messydt()` to get median value from expanded range 290 | - Added `mean.messydt()` to get mean value from expanded range 291 | - Added `modal.messydt()` to get mode value from expanded range 292 | - Added `contract()` function for contracting expanded dates 293 | - Added extract functions to get particular date components 294 | - Added `year()` to extract year from date 295 | - Added `month()` to extract month from date 296 | - Added `day()` to extract day from date 297 | - Added `make_messydate()` function to get messy dates from multiple columns 298 | - Added set functions for operations in sets of messy dates 299 | - Added `md_intersect()` to find intersection of sets of messy dates 300 | - Added `md_union()` to find union of sets of messy dates 301 | - Added `md_multiset()` to join two sets of messy dates 302 | - Added logical function for various logical tests for messy date objects 303 | - Added `is_messydate()` to test for messydt class 304 | - Added `is_intersecting()` to test if dates intersect 305 | - Added `is_element()` to test for multiple elements in dates 306 | - Added `is_similar()` to test for similarities in dates 307 | - Added tests for new functions 308 | - Added tests for messydt class and `às_messydate()` function 309 | - Added tests for coerce from messy dates functions 310 | - Added tests for coerce to messy dates functions 311 | - Added tests for `contract()` function 312 | - Added tests for `expand()` function 313 | - Added tests for extract functions 314 | - Added tests for `make_messydate()` function 315 | - Added tests for resolve functions 316 | - Added tests for set functions 317 | -------------------------------------------------------------------------------- /R/class_duration.R: -------------------------------------------------------------------------------- 1 | #' A duration class for mdates 2 | #' @description 3 | #' The `mdates_duration` class introduces methods that annotate a duration or 4 | #' period with representations of its uncertainty. 5 | #' @details 6 | #' Most R packages handle duration and periods as exact time or date intervals. 7 | #' However, this is not possible for 'messy' dates where uncertainty or 8 | #' approximation might be present. 9 | #' The `mdates_duration` class accounts for uncertainty and approximation 10 | #' in `mdate` objects to return their duration as a range of possible dates. 11 | #' @param x An `mdate` variable with ranges. 12 | #' @param approx_range Range to expand approximate dates, in days. 13 | #' If 3, for example, adds 3 days; if -3, removes 3 days from both sides. 14 | #' @return Object of class `description` 15 | #' @name class_duration 16 | #' @examples 17 | #' messyduration(as_messydate(c("2010-01-01..2010-12-31", "2010-01..2010-12"))) 18 | NULL 19 | 20 | #' @rdname class_duration 21 | #' @export 22 | new_messyduration <- function(x = character()) { 23 | stopifnot(is.character(x)) 24 | structure(x, class = "mdates_duration") 25 | } 26 | 27 | #' @rdname class_duration 28 | #' @export 29 | messyduration <- function(x, approx_range = 0) UseMethod("messyduration") 30 | 31 | #' @rdname class_duration 32 | #' @export 33 | validate_messyduration <- function(x, approx_range = 0) { 34 | if (any(!grepl("\\.\\.", x))) { 35 | stop("mdates_duration class objects should have at least one date range", 36 | call. = FALSE) 37 | } 38 | } 39 | 40 | #' @rdname class_duration 41 | #' @export 42 | messyduration.character <- function(x, approx_range = 0) { 43 | message("Converting to mdate class.") 44 | x <- as_messydate(x) 45 | validate_messyduration(x) 46 | x <- ifelse(grepl("\\.\\.", x), messy_range(x, approx_range), x) 47 | new_messyduration(x) 48 | } 49 | 50 | #' @rdname class_duration 51 | #' @export 52 | messyduration.mdate <- function(x, approx_range = 0) { 53 | validate_messyduration(x) 54 | x <- ifelse(grepl("\\.\\.", x), messy_range(x, approx_range), x) 55 | new_messyduration(x) 56 | } 57 | 58 | messy_range <- function(x, approx_range) { 59 | dates <- strsplit(x, "\\.\\.") 60 | dates1 <- as.Date(as_messydate(purrr::map_chr(dates, 1)), FUN = min) + approx_range 61 | dates2 <- as.Date(as_messydate(purrr::map_chr(dates, 2)), FUN = max) + approx_range 62 | as_messydate(paste0(dates1, "..", dates2)) 63 | } 64 | -------------------------------------------------------------------------------- /R/class_mdate.R: -------------------------------------------------------------------------------- 1 | #' A flexible date class for messy dates 2 | #' @description 3 | #' Recent extensions to standardised date notation in 4 | #' [ISO 8601-2_2019(E)](https://www.iso.org/standard/70908.html) 5 | #' create space for unspecified, uncertain, and approximate dates, 6 | #' as well as succinct representation of ranges of dates. 7 | #' These functions create and validate a new date class for R 8 | #' that can contain and parse these annotations, 9 | #' and are not typically user-facing. 10 | #' Please see `as_messydate()` for the user-facing coercion function. 11 | #' @section Date annotations: 12 | #' _Unspecified date components_, such as when the day is unknown, 13 | #' can be represented by one or more `X`s in place of the digits. 14 | #' The modifier `*` is recommended to indicate that the entire 15 | #' time scale component value is unspecified, e.g. `X*-03-03`, 16 | #' however this is not implemented here. 17 | #' Please be explicit about the digits that are unspecified, 18 | #' e.g. `XXXX-03-03` expresses 3rd March in some unspecified year, 19 | #' whereas `2003-XX-03` expresses the 3rd of some month in 2003. 20 | #' If time components are not given, they are expanded to this. 21 | #' 22 | #' _Approximate date components_, modified by `~`, 23 | #' represent an estimate whose value is asserted 24 | #' to be possibly correct. 25 | #' For example, `2003~-03-03` 26 | #' The degree of confidence in approximation 27 | #' depends on the application. 28 | #' 29 | #' _Uncertain date components_, modified by `?`, 30 | #' represent a date component whose source is considered 31 | #' to be dubious and therefore not to be relied upon. 32 | #' An additional modifier, `%`, is used to indicate 33 | #' a value that is both uncertain and approximate. 34 | #' 35 | #' @section Date sets: 36 | #' These functions also introduce standard notation 37 | #' for ranges of dates. 38 | #' Rather than the typical R notation for ranges, 39 | #' `:`, ISO 8601-2_2019(E) recommends `..`. 40 | #' This then can be applied between two time scale 41 | #' components to create a standard range between 42 | #' these dates (inclusive), e.g. `2009-01-01..2019-01-01`. 43 | #' But it can also be used as an affix, 44 | #' indicating "on or before" if used as a prefix, 45 | #' e.g. `..2019-01-01`, 46 | #' or indicating "on or after" if used as a suffix, 47 | #' e.g. `2009-01-01..`. 48 | #' 49 | #' And lastly, notation for sets of dates is also included. 50 | #' Here braces, `{}`, are used to mean "all members of the set", 51 | #' while brackets, `[]`, are used to mean "one member of the set". 52 | #' @param x A character scalar or vector in the expected `"yyyy-mm-dd"` format 53 | #' annotated, as necessary, according to ISO 8601-2_2019(E). 54 | #' @return Object of class `mdate` 55 | #' @name class_create 56 | #' @seealso messydate 57 | NULL 58 | 59 | #' @rdname class_create 60 | #' @export 61 | new_messydate <- function(x = character()) { 62 | stopifnot(is.character(x)) 63 | structure(x, class = "mdate") 64 | } 65 | 66 | #' @rdname class_create 67 | #' @export 68 | validate_messydate <- function(x) { 69 | values <- unclass(x) 70 | if (any(grepl("[A-WYZa-z]", values) & !grepl("^NA$", values))) { 71 | stop("The only alpha character allowed in messy dates is 'X' for 72 | unspecified time components", call. = FALSE) 73 | } 74 | if (!any(grepl("[0-9]", values))) { 75 | stop("mdate object requires at least one specified date component.", 76 | call. = FALSE) 77 | } 78 | if (any(grepl("!|\\(|\\)|\\+|\\=|\\/|;|>|<|_|\\^|'|&|\\$|#", values))) { 79 | stop("mdate object can only consist of numbers and 80 | some special symbols: []{}..X%?~", call. = FALSE) 81 | } 82 | x 83 | } 84 | 85 | # Make #### 86 | 87 | #' Composes `mdate` from multiple variables 88 | #' @param ... One (yyyy-mm-dd), two (yyyy-mm-dd, yyyy-mm-dd), 89 | #' or three (yyyy, mm, dd) variables. 90 | #' @inheritParams coerce_to 91 | #' @details If three date variables are passed to `make_messydate()`, 92 | #' function will create a single date (yyyy-mm-dd) from it. 93 | #' If two date variables are passed to `make_messydate()`, 94 | #' function will create a range of dates from it (yyyy-mm-dd..yyyy-mm-dd). 95 | #' If one date variable is passed to `make_messydate()`, 96 | #' function defaults to `as_messydate()`. 97 | #' @importFrom purrr map pmap_chr 98 | #' @name class_make 99 | #' @examples 100 | #' make_messydate("2010", "10", "10") 101 | #' @export 102 | make_messydate <- function(..., resequence = FALSE) { 103 | dots <- list(...) 104 | if (length(dots) == 1) { 105 | dots <- do.call(as.character, dots) 106 | dates <- unlist(dots) 107 | } else if (length(dots) == 2) { 108 | dots <- purrr::map(dots, as.character) 109 | dates <- unlist(purrr::pmap_chr(dots, paste, sep = "..")) 110 | dates <- gsub("NA..NA", "NA", dates) 111 | } else if (length(dots) == 3) { 112 | dots <- purrr::map(dots, as.character) 113 | dates <- unlist(purrr::pmap_chr(dots, paste, sep = "-")) 114 | dates <- gsub("NA-NA-NA", "NA", dates) 115 | } else stop("make_messydate() takes one variable (yyyy-mm-dd), 116 | two variables (yyyy-mm-dd, yyyy-mm-dd), or three variables (yyyy, mm, dd).") 117 | as_messydate(dates, resequence) 118 | } 119 | -------------------------------------------------------------------------------- /R/class_methods.R: -------------------------------------------------------------------------------- 1 | #' @export 2 | `[.mdate` <- function(x, ..., drop = TRUE) { 3 | as_messydate(NextMethod("[", unclass(x))) 4 | } 5 | 6 | #' @export 7 | `[<-.mdate` <- function(x, i, ..., value) { 8 | value <- as_messydate(value) 9 | validate_messydate(value) 10 | as_messydate(NextMethod("[<-", unclass(x))) 11 | } 12 | 13 | #' @export 14 | `[[.mdate` <- function(x, ...) { 15 | as_messydate(NextMethod("[[", unclass(x))) 16 | } 17 | 18 | #' @export 19 | `[[<-.mdate` <- function(x, i, ..., value) { 20 | value <- as_messydate(value) 21 | validate_messydate(value) 22 | as_messydate(NextMethod("[[<-", unclass(x))) 23 | } 24 | 25 | #' @export 26 | c.mdate <- function(...) { 27 | if(length(list(...)) == 1){ 28 | unclass(list(...)[[1]]) 29 | } else { 30 | vecs <- lapply(list(...), function(e) unclass(as_messydate(e))) 31 | x <- as_messydate(unlist(vecs)) 32 | validate_messydate(x) 33 | } 34 | } 35 | 36 | #' @export 37 | rep.mdate <- function(x, ...) { 38 | as_messydate(NextMethod("rep", unclass(x))) 39 | } 40 | 41 | # Printing #### 42 | 43 | #' @importFrom utils str 44 | #' @export 45 | print.mdate <- function(x, ...) { 46 | str(x) 47 | } 48 | #' @importFrom utils str 49 | #' @export 50 | print.mdates_duration <- function(x, ...) { 51 | str(x) 52 | } 53 | 54 | 55 | -------------------------------------------------------------------------------- /R/coerce_extrema.R: -------------------------------------------------------------------------------- 1 | #' Resolves messy dates into an extrema 2 | #' @description 3 | #' This collection of S3 methods 'resolve' messy dates into a single date 4 | #' according to some explicit bias, 5 | #' such as returning the minimum or maximum date, 6 | #' the mean, median, or modal date, 7 | #' or a random date from among the possible resolutions for each messy date. 8 | #' If the date is not 'messy' (i.e. has no annotations) 9 | #' then just that precise date is returned. 10 | #' This can be useful for various descriptive or inferential projects. 11 | #' @param ... a mdate object 12 | #' @param na.rm Should NAs be removed? True by default. 13 | #' @importFrom stringi stri_detect_regex stri_replace_all_regex 14 | #' @return A single scalar or vector of dates 15 | #' @examples 16 | #' d <- as_messydate(c("2008-03-25", "?2012-02-27", "2001-01?", "2001~", 17 | #' "2001-01-01..2001-02-02", "{2001-01-01,2001-02-02}", 18 | #' "{2001-01,2001-02-02}", "2008-XX-31", "-0050-01-01")) 19 | #' d 20 | #' @name coerce_extrema 21 | NULL 22 | 23 | #' @rdname coerce_extrema 24 | #' @export 25 | vmin <- function(..., na.rm = FALSE) UseMethod("vmin") 26 | 27 | #' @rdname coerce_extrema 28 | #' @examples 29 | #' vmin(d) 30 | #' @export 31 | vmin.mdate <- function(..., na.rm = TRUE){ 32 | d <- list(...)[[1]] 33 | dates <- d 34 | if(na.rm) dates <- stats::na.omit(d) 35 | dates <- stringi::stri_replace_all_regex(dates, "~|\\?", "") 36 | dates <- .remove_post(dates) 37 | dates <- .replace_earliest(dates) 38 | mdate(dates) 39 | } 40 | 41 | #' @rdname coerce_extrema 42 | #' @examples 43 | #' min(d) 44 | #' @export 45 | min.mdate <- function(..., na.rm = TRUE){ 46 | d <- list(...)[[1]] 47 | dates <- d 48 | if(na.rm) dates <- stats::na.omit(d) 49 | dates <- stringi::stri_replace_all_regex(dates, "~|\\?", "") 50 | dates <- .remove_post(dates) 51 | dates <- .replace_earliest(dates) 52 | dates <- mdate(dates) 53 | if(any(is_bce(dates))) 54 | dates[is_bce(dates)][order(as.character(dates[is_bce(dates)]), 55 | decreasing = TRUE)][1] else 56 | dates[order(as.character(dates))==1] 57 | } 58 | 59 | .remove_post <- function(dates){ 60 | dates <- stringi::stri_replace_all_regex(dates, "\\.\\.$|,.*$|\\{", "") 61 | dates <- stringi::stri_replace_all_regex(dates, "^(.+)\\.\\..*$", "$1") 62 | dates <- stringi::stri_replace_all_regex(dates, "\\.\\.", "") 63 | dates 64 | } 65 | 66 | .replace_earliest <- function(dates){ 67 | dates <- stringi::stri_replace_last_regex(dates, 68 | "XX", "01") 69 | dates <- stringi::stri_replace_last_regex(dates, 70 | "^(.*[:digit:]{4})$", "$1-01-01") 71 | dates <- stringi::stri_replace_last_regex(dates, 72 | "^(.*[:digit:]{4})-([:digit:]{2})$", "$1-$2-01") 73 | # dates <- stringi::stri_replace_last_regex(dates, 74 | # "^-([:digit:]{4})-([:digit:]{2})$", "-$1-$2-01") 75 | dates 76 | } 77 | 78 | #' @rdname coerce_extrema 79 | #' @export 80 | vmax <- function(..., na.rm = FALSE) UseMethod("vmax") 81 | 82 | #' @rdname coerce_extrema 83 | #' @examples 84 | #' vmax(d) 85 | #' @export 86 | vmax.mdate <- function(..., na.rm = TRUE){ 87 | d <- list(...)[[1]] 88 | dates <- d 89 | if(na.rm) dates <- stats::na.omit(d) 90 | dates <- stringi::stri_replace_all_regex(dates, "~|\\?", "") 91 | dates <- unspecified_months(dates) 92 | dates <- .remove_pre(dates) 93 | dates <- .replace_latest(dates) 94 | mdate(dates) 95 | } 96 | 97 | #' @rdname coerce_extrema 98 | #' @examples 99 | #' max(d) 100 | #' @export 101 | max.mdate <- function(..., na.rm = TRUE) { 102 | 103 | d <- list(...)[[1]] 104 | dates <- stringi::stri_replace_all_regex(d, "~|\\?", "") 105 | dates <- unspecified_months(dates) 106 | dates <- .remove_pre(dates) 107 | dates <- .replace_latest(dates) 108 | dates <- mdate(dates) 109 | if(all(is_bce(dates), na.rm = TRUE)) 110 | dates[order(dates, decreasing = TRUE)][1] else 111 | dates[!is_bce(dates)][order(as.character(dates[!is_bce(dates)]), 112 | decreasing = TRUE)][1] 113 | 114 | } 115 | 116 | .remove_pre <- function(dates){ 117 | dates <- stringi::stri_replace_all_regex(dates, "^\\.\\.|^.*,|\\}", "") 118 | dates <- stringi::stri_replace_all_regex(dates, "^.*\\.\\.(.+)$", "$1") 119 | dates <- stringi::stri_replace_all_regex(dates, "\\.\\.", "") 120 | dates 121 | } 122 | 123 | .replace_latest <- function(dates){ 124 | dates <- stringi::stri_replace_last_regex(dates, 125 | "^(.*[:digit:]{4})$", "$1-12-31") 126 | dates <- stringi::stri_replace_last_regex(dates, 127 | "-XX-", "-12-") 128 | dates 129 | } 130 | 131 | -------------------------------------------------------------------------------- /R/coerce_from_messydate.R: -------------------------------------------------------------------------------- 1 | #' Coercion from messy dates 2 | #' @description 3 | #' These functions coerce objects of `mdate` class to 4 | #' common date classes such as `Date`, `POSIXct`, and `POSIXlt`. 5 | #' Since `mdate` objects can hold multiple individual dates, 6 | #' however, an additional function must be passed as an argument 7 | #' so that these functions know how to coerce resolve multiple dates 8 | #' into a single date. 9 | #' 10 | #' For example, one might wish to use the earliest possible date 11 | #' in any ranges of dates (`min`), the latest possible date (`max`), 12 | #' some notion of a central tendency (`mean`, `median`, or `modal`), 13 | #' or even a `random` selection from among the candidate dates. 14 | #' 15 | #' These functions then, building on `expand()` and the resolve functions, 16 | #' are particularly useful in converting back out of the `mdate` class 17 | #' for use with existing methods and models, 18 | #' especially for checking the robustness of results. 19 | #' @param x A `mdate` object 20 | #' @param ... Arguments passed on to the S3 generics. 21 | #' @param FUN A function that can be used to resolve expanded messy dates 22 | #' into a single date. 23 | #' For example, `min()`, `max()`, `mean()`, `median()`, 24 | #' `modal()`, and `random()`. 25 | #' @return A date object of `Date`, `POSIXct`, or `POSIXlt` class 26 | #' @name coerce_from 27 | NULL 28 | 29 | #' @rdname coerce_from 30 | #' @examples 31 | #' as.Date(as_messydate("2012-01"), FUN = vmin) 32 | #' as.Date(as_messydate("2012-01-01"), FUN = vmean) 33 | #' as.Date(as_messydate("2012-01"), FUN = vmax) 34 | #' as.Date(as_messydate("2012-01"), FUN = vmedian) 35 | #' as.Date(as_messydate("2012-01"), FUN = vmodal) 36 | #' as.Date(as_messydate("2012-01"), FUN = vrandom) 37 | #' as.Date(as_messydate("1000 BC"), FUN = vmax) 38 | #' as.Date(as_messydate("1000 BC"), FUN = vmedian) 39 | #' as.Date(as_messydate(c("-1000", "2020")), FUN = vmin) 40 | #' @export 41 | as.Date.mdate <- function(x, FUN = vmin, ...) { 42 | # # fix argument ordering issues 43 | # if (missing(FUN)){ 44 | # if(length(list(...)) > 0) FUN <- list(...)[[1]] else 45 | # FUN <- messydates::min.mdate 46 | # } 47 | # if(missing(FUN)) FUN <- min 48 | x <- FUN(x) 49 | x <- suppressWarnings(ifelse(stringi::stri_detect_regex(x, "^-"), 50 | lubridate::as_date(negative_dates(x)), 51 | lubridate::as_date(zero_padding(x)))) 52 | as.Date(x, origin = "1970-01-01") 53 | } 54 | 55 | #' @rdname coerce_from 56 | #' @param tz Character string specifying the time zone for the conversion, 57 | #' if required. 58 | #' By default "UTC" (Universal Time Coordinated), equivalent to GMT. 59 | #' If "" then the current time zone is used. 60 | #' @export 61 | as.POSIXct.mdate <- function(x, tz = "UTC", FUN = vmin, ...) { 62 | # if (missing(FUN) & length(list(...)) > 0) FUN <- list(...)[[1]] 63 | x <- FUN(x) 64 | if (stringi::stri_detect_regex(x, "^-")) { 65 | stop("For conversion of negative dates from mdate class use as.Date()") 66 | } 67 | as.POSIXct(as.character(x), tz = tz) 68 | } 69 | 70 | #' @rdname coerce_from 71 | #' @export 72 | as.POSIXlt.mdate <- function(x, tz = "UTC", FUN = vmin, ...) { 73 | # if (missing(FUN) & length(list(...)) > 0) FUN <- list(...)[[1]] 74 | x <- FUN(x) 75 | if (stringi::stri_detect_regex(x, "^-")) { 76 | stop("For conversion of negative dates from mdate class use as.Date()") 77 | } 78 | as.POSIXlt(as.character(x), tz = tz) 79 | } 80 | 81 | # Helper function for returning negative dates in date formats 82 | #' @importFrom stringi stri_replace_all_regex stri_extract_all_regex 83 | #' @importFrom lubridate ymd years as_date 84 | negative_dates <- function(x) { 85 | x <- stringi::stri_replace_all_regex(x, "^-", "") 86 | y <- stringi::stri_extract_all_regex(x, "^[0-9]{4}") 87 | md <- stringi::stri_replace_all_regex(x, "^[0-9]{4}", "0000") 88 | x <- lubridate::ymd(md) - lubridate::years(y) 89 | x <- lubridate::as_date(x) 90 | x 91 | } 92 | 93 | #' @export 94 | as.data.frame.mdate <- function(x, ...) { 95 | as.data.frame.vector(x, ...) 96 | } 97 | 98 | #' @export 99 | as.list.mdate <- function(x, ...) { 100 | lapply(unclass(x), as_messydate) 101 | } 102 | 103 | #' @export 104 | as.double.mdate <- function(x, ...) { 105 | if(any(is_bce(x))) x[is_bce(x)] <- negative_dates(x)[is_bce(x)] 106 | as.double(lubridate::as_date(x)) 107 | } 108 | 109 | -------------------------------------------------------------------------------- /R/coerce_tendency.R: -------------------------------------------------------------------------------- 1 | #' Resolves messy dates into a central tendency 2 | #' @description 3 | #' These functions resolve messydates by their central tendency. 4 | #' While the functions `mean()`, `median()`, and `modal()` summarise the 5 | #' vector to a single value, `v*()` versions return a vector of the same length. 6 | #' @name coerce_tendency 7 | #' @inheritParams coerce_extrema 8 | #' @examples 9 | #' d <- as_messydate(c("2008-03-25", "?2012-02-27", "2001-01?", "2001~", 10 | #' "2001-01-01..2001-02-02", "{2001-01-01,2001-02-02}", 11 | #' "{2001-01,2001-02-02}", "2008-XX-31", "-0050-01-01")) 12 | #' d 13 | NULL 14 | 15 | #' @rdname coerce_tendency 16 | #' @importFrom stats median 17 | #' @examples 18 | #' median(d) 19 | #' @export 20 | median.mdate <- function(..., na.rm = TRUE) { 21 | 22 | x <- list(...)[[1]] 23 | y <- unlist(expand(x)) 24 | y <- .order_messy(y) 25 | median(y, na.rm = na.rm) 26 | } 27 | 28 | .order_messy <- function(y){ 29 | if(any(is_bce(y))){ 30 | bcey <- y[is_bce(y)] 31 | cey <- y[!is_bce(y)] 32 | c(bcey[order(bcey, decreasing = TRUE)], 33 | cey[order(cey)]) 34 | } else { 35 | y[order(y)] 36 | } 37 | } 38 | 39 | #' @rdname coerce_tendency 40 | #' @export 41 | vmedian <- function(..., na.rm = TRUE) UseMethod("vmedian") 42 | 43 | #' @rdname coerce_tendency 44 | #' @importFrom stats median 45 | #' @examples 46 | #' vmedian(d) 47 | #' @export 48 | vmedian.mdate <- function(..., na.rm = TRUE) { 49 | 50 | x <- as.list(...) 51 | vapply(x, function(y){ 52 | z <- suppressWarnings(median(y, na.rm = na.rm)) 53 | if(is.na(z)){ 54 | if(length(expand(y)[[1]]) %% 2 == 0) 55 | z <- median(.order_messy(expand(y)[[1]])[-1]) 56 | } 57 | z 58 | }, FUN.VALUE = character(1)) 59 | } 60 | 61 | #' @rdname coerce_tendency 62 | #' @param trim the fraction (0 to 0.5) of observations to be trimmed 63 | #' from each end of x before the mean is computed. 64 | #' Values of trim outside that range are taken as the nearest endpoint. 65 | #' @importFrom lubridate as_date 66 | #' @examples 67 | #' mean(d) 68 | #' @export 69 | mean.mdate <- function(..., trim = 0, na.rm = TRUE) { 70 | 71 | x <- list(...)[[1]] 72 | y <- unlist(expand(x)) 73 | as.character(lubridate::as_date(mean(as.double(lubridate::as_date(y))))) 74 | } 75 | 76 | 77 | #' @rdname coerce_tendency 78 | #' @export 79 | vmean <- function(..., na.rm = TRUE) UseMethod("vmean") 80 | 81 | #' @rdname coerce_tendency 82 | #' @examples 83 | #' vmean(d) 84 | #' @export 85 | vmean.mdate <- function(..., trim = 0, na.rm = TRUE) { 86 | x <- list(...)[[1]] 87 | vapply(expand(x), function(y) 88 | as.character(lubridate::as_date(mean(as.double(lubridate::as_date(y))))), 89 | FUN.VALUE = character(1)) 90 | } 91 | 92 | #' @rdname coerce_tendency 93 | #' @export 94 | modal <- function(..., na.rm = TRUE) UseMethod("modal") 95 | 96 | #' @rdname coerce_tendency 97 | #' @examples 98 | #' modal(d) 99 | #' @export 100 | modal.mdate <- function(..., na.rm = TRUE) { 101 | d <- list(...)[[1]] 102 | .getmode(unlist(expand(d))) 103 | } 104 | 105 | .getmode <- function(v) { 106 | uniqv <- unique(v) 107 | uniqv[which.max(tabulate(match(v, uniqv)))] 108 | } 109 | 110 | #' @rdname coerce_tendency 111 | #' @export 112 | vmodal <- function(..., na.rm = TRUE) UseMethod("vmodal") 113 | 114 | #' @rdname coerce_tendency 115 | #' @examples 116 | #' vmodal(d) 117 | #' @export 118 | vmodal.mdate <- function(..., na.rm = TRUE) { 119 | 120 | d <- list(...)[[1]] 121 | d <- purrr::map_chr(expand(d), function(y) .getmode(y)) 122 | d 123 | } 124 | 125 | #' @rdname coerce_tendency 126 | #' @export 127 | random <- function(..., na.rm = TRUE) UseMethod("random") 128 | 129 | #' @rdname coerce_tendency 130 | #' @examples 131 | #' random(d) 132 | #' @export 133 | random.mdate <- function(..., na.rm = TRUE) { 134 | x <- list(...)[[1]] 135 | y <- unlist(expand(x)) 136 | if(na.rm) y <- y[!is.na(y)] 137 | sample(y, 1) 138 | } 139 | 140 | #' @export 141 | random.character <- function(..., na.rm = TRUE) { 142 | y <- list(...)[[1]] 143 | # y <- suppressMessages(unlist(expand(x))) 144 | if(na.rm) y <- y[!is.na(y)] 145 | sample(y, 1) 146 | } 147 | 148 | #' @rdname coerce_tendency 149 | #' @export 150 | vrandom <- function(..., na.rm = TRUE) UseMethod("vrandom") 151 | 152 | #' @rdname coerce_tendency 153 | #' @examples 154 | #' vrandom(d) 155 | #' @export 156 | vrandom.mdate <- function(..., na.rm = TRUE) { 157 | 158 | x <- as.list(...) 159 | vapply(x, function(y){ 160 | random(expand(y)[[1]], na.rm = na.rm) 161 | }, FUN.VALUE = character(1)) 162 | 163 | } 164 | -------------------------------------------------------------------------------- /R/component_annotate.R: -------------------------------------------------------------------------------- 1 | #' Annotates dates as censored, uncertain, or approximate 2 | #' 3 | #' Some datasets have for example an arbitrary cut off point 4 | #' for start and end points, but these are often coded as precise dates 5 | #' when they are not necessarily the real start or end dates. 6 | #' This collection of functions helps annotate uncertainty and 7 | #' approximation to dates according to ISO2019E standards. 8 | #' Inaccurate start or end dates can be represented by an affix 9 | #' indicating "on or before", if used as a prefix (e.g. `..1816-01-01`), 10 | #' or indicating "on or after", if used as a suffix (e.g. `2016-12-31..`). 11 | #' Approximate dates are indicated by adding a tilde to year, 12 | #' month, or day components, as well as groups of components or whole dates 13 | #' to estimate values that are possibly correct (e.g. `2003-03-03~`). 14 | #' Day, month, or year, uncertainty can be indicated by adding a question mark 15 | #' to a possibly dubious date (e.g. `1916-10-10?`) or date 16 | #' component (e.g. `1916-?10-10`). 17 | #' @param x A date vector 18 | #' @param component Annotation can be added on specific date components 19 | #' ("year", "month" or "day"), or to groups of date components (month and 20 | #' day ("md"), or year and month ("ym")). This must be specified. 21 | #' If unspecified, annotation will be added after the date (e.g. `1916-10-10?`), 22 | #' indicating the whole date is uncertain or approximate. 23 | #' For specific date components, uncertainty or approximation is annotated to 24 | #' the left of the date component. 25 | #' E.g. for "day": `1916-10-?10` or `1916-10-~10`. 26 | #' For groups of date components, uncertainty or approximation is annotated to 27 | #' the right of the group ("ym") or to both components ("md"). 28 | #' E.g. for "ym": `1916-10~-10`; for "md": `1916-?10-?10`. 29 | #' @return A `mdate` object with annotated date(s) 30 | #' @examples 31 | #' data <- data.frame(Beg = c("1816-01-01", "1916-01-01", "2016-01-01"), 32 | #' End = c("1816-12-31", "1916-12-31", "2016-12-31")) 33 | #' dplyr::mutate(data, Beg = ifelse(Beg <= "1816-01-01", 34 | #' on_or_before(Beg), Beg)) 35 | #' dplyr::mutate(data, End = ifelse(End >= "2016-01-01", 36 | #' on_or_after(End), End)) 37 | #' dplyr::mutate(data, Beg = ifelse(Beg == "1916-01-01", 38 | #' as_approximate(Beg), Beg)) 39 | #' dplyr::mutate(data, End = ifelse(End == "1916-12-31", 40 | #' as_uncertain(End), End)) 41 | #' @name component_annotate 42 | NULL 43 | 44 | #' @describeIn component_annotate prefixes dates with ".." where start date is uncertain 45 | #' @export 46 | on_or_before <- function(x) { 47 | x <- paste0("..", x) 48 | x <- as_messydate(x) 49 | x 50 | } 51 | 52 | #' @describeIn component_annotate suffixes dates with ".." where end date is uncertain 53 | #' @export 54 | on_or_after <- function(x) { 55 | x <- paste0(x, "..") 56 | x <- as_messydate(x) 57 | x 58 | } 59 | 60 | #' @describeIn component_annotate adds tildes to indicate approximate dates/date components 61 | #' @export 62 | as_approximate <- function(x, component = NULL) { 63 | if (is.null(component)) { 64 | x <- paste0(x, "~") 65 | } 66 | if (!is.null(component)) { 67 | day <- vapply(strsplit(x, "-"), `[`, 3, FUN.VALUE = character(1)) 68 | month <- vapply(strsplit(x, "-"), `[`, 2, FUN.VALUE = character(1)) 69 | year <- vapply(strsplit(x, "-"), `[`, 1, FUN.VALUE = character(1)) 70 | if (component == "day") { 71 | x <- paste0(year, "-", month, "-", "~", day) 72 | } 73 | if (component == "month") { 74 | x <- paste0(year, "-", "~", month, "-", day) 75 | } 76 | if (component == "year") { 77 | x <- paste0("~", year, "-", month, "-", day) 78 | } 79 | if (component == "md") { 80 | x <- paste0(year, "-", "~", month, "-", "~", day) 81 | } 82 | if (component == "ym") { 83 | x <- paste0(year, "-", month, "~", "-", day) 84 | } 85 | } 86 | x <- as_messydate(x) 87 | x 88 | } 89 | 90 | #' @describeIn component_annotate adds question marks to indicate dubious dates/date components. 91 | #' @export 92 | as_uncertain <- function(x, component = NULL) { 93 | if (is.null(component)) { 94 | x <- paste0(x, "?") 95 | } else { 96 | day <- vapply(strsplit(x, "-"), `[`, 3, 97 | FUN.VALUE = character(1)) 98 | month <- vapply(strsplit(x, "-"), `[`, 2, 99 | FUN.VALUE = character(1)) 100 | year <- vapply(strsplit(x, "-"), `[`, 1, 101 | FUN.VALUE = character(1)) 102 | if (component == "day") { 103 | x <- paste0(year, "-", month, "-", "?", day) 104 | } 105 | if (component == "month") { 106 | x <- paste0(year, "-", "?", month, "-", day) 107 | } 108 | if (component == "year") { 109 | x <- paste0("?", year, "-", month, "-", day) 110 | } 111 | if (component == "md") { 112 | x <- paste0(year, "-", "?", month, "-", "?", day) 113 | } 114 | if (component == "ym") { 115 | x <- paste0(year, "-", month, "?", "-", day) 116 | } 117 | } 118 | x <- as_messydate(x) 119 | x 120 | } 121 | -------------------------------------------------------------------------------- /R/component_extract.R: -------------------------------------------------------------------------------- 1 | #' Extracting components from messy dates 2 | #' @description 3 | #' These functions allow the extraction of particular date components 4 | #' from messy dates, such as the `year()`, `month()`, and `day()`. 5 | #' `precision()` allows for the identification of the greatest level of 6 | #' precision in (currently) the first element of each date. 7 | #' @param x A `mdate` object 8 | #' @return `year()`, `month()`, and `day()` extraction return the integer 9 | #' for the requested date component. 10 | #' `precision()` returns the level of greatest precision for each date. 11 | #' @name component_extract 12 | NULL 13 | #> NULL 14 | 15 | #' @rdname component_extract 16 | #' @examples 17 | #' year(as_messydate(c("2012-02-03","2012","2012-02"))) 18 | #' @export 19 | year <- function(x) { 20 | x <- stringi::stri_replace_all_regex(x, "\\.\\..+", "") 21 | x <- stringi::stri_replace_all_regex(x, "-.+", "") 22 | as.integer(x) 23 | } 24 | 25 | #' @rdname component_extract 26 | #' @examples 27 | #' month(as_messydate(c("2012-02-03","2012","2012-02"))) 28 | #' @export 29 | month <- function(x) { 30 | x <- sapply(x, function(y) { 31 | stringi::stri_split_regex(y, "-")[[1]][2] 32 | }) 33 | as.integer(x) 34 | } 35 | 36 | #' @rdname component_extract 37 | #' @examples 38 | #' day(as_messydate(c("2012-02-03","2012","2012-02"))) 39 | #' @export 40 | day <- function(x) { 41 | x <- sapply(x, function(y) { 42 | stringi::stri_split_regex(y, "-")[[1]][3] 43 | }) 44 | as.integer(x) 45 | } 46 | 47 | #' @rdname component_extract 48 | #' @export 49 | precision <- function(x) UseMethod("precision") 50 | 51 | #' @rdname component_extract 52 | #' @section Precision: 53 | #' Date precision is measured relative to the day in \eqn{1/days(x)}. 54 | #' That is, a date measured to the day will return a precision score 55 | #' of 1, a date measured to the month will return a precision score of 56 | #' between \eqn{1/28} and \eqn{1/31}, and annual measures will have 57 | #' a precision of between \eqn{1/365} and \eqn{1/366}. 58 | #' @examples 59 | #' precision(as_messydate(c("2012-02-03","2012","2012-02"))) 60 | #' @export 61 | precision.mdate <- function(x) { 62 | out <- expand(x) 63 | 1/lengths(out) 64 | } 65 | -------------------------------------------------------------------------------- /R/convert_contract.R: -------------------------------------------------------------------------------- 1 | #' Contract lists of dates into messy dates 2 | #' @description 3 | #' This function operates as the opposite of `expand()`. 4 | #' It contracts a list of dates into the abbreviated annotation 5 | #' of messy dates. 6 | #' @name convert_contract 7 | #' @details The ´contract()´ function first `expand()` 'mdate' objects 8 | #' to then display their most succinct representation. 9 | #' @param x A list of dates 10 | #' @param collapse Do you want ranges to be collapsed? 11 | #' TRUE by default. 12 | #' If FALSE ranges are returned in compact format. 13 | #' @return A `mdate` vector 14 | #' @importFrom dplyr tibble 15 | #' @importFrom lubridate NA_Date_ 16 | #' @importFrom dplyr lead last first 17 | #' @examples 18 | #' d <- as_messydate(c("2001-01-01", "2001-01", "2001", 19 | #' "2001-01-01..2001-02-02", "{2001-10-01,2001-10-04}", 20 | #' "{2001-01,2001-02-02}", "28 BC", "-2000-01-01", 21 | #' "{2001-01-01, 2001-01-02, 2001-01-03}")) 22 | #' dplyr::tibble(d, contract(d)) 23 | #' @export 24 | contract <- function(x, collapse = TRUE) { 25 | if (!inherits(x, 'list')) { 26 | x <- expand(x) 27 | } 28 | x <- compact_negative_dates(x) 29 | x <- compact_ranges(x) 30 | x <- collapse_sets(x) 31 | if (collapse == TRUE) { 32 | x <- collapse_ranges(x) 33 | } else { 34 | x <- unlist(x) 35 | } 36 | as_messydate(x) 37 | } 38 | 39 | compact_negative_dates <- function(x) { 40 | lapply(x, function(d) { 41 | if (stringi::stri_detect_regex(d[1], "^-") & length(d) > 1) { 42 | d <- paste0(dplyr::first(d), "..", dplyr::last(d)) 43 | } 44 | d 45 | }) 46 | } 47 | 48 | compact_ranges <- function(x) { 49 | lapply(x, function(d) { 50 | if (length(d) > 1) { 51 | sequ <- is_sequence(d) 52 | if (any(sequ)) { 53 | starts <- d[which(sequ == FALSE)] 54 | ends <- d[dplyr::lead(sequ) == FALSE | is.na(dplyr::lead(sequ))] 55 | if (any(starts == ends)) ends[starts == ends] <- NA 56 | d <- paste(starts, ends, sep = "..") 57 | d <- stringi::stri_replace_all_regex(d, "\\.\\.NA", "") 58 | } 59 | } 60 | d 61 | }) 62 | } 63 | 64 | collapse_sets <- function(x) { 65 | x <- lapply(x, paste, collapse = ",") 66 | x <- ifelse(stringi::stri_count_regex(x, ",") == 11 & 67 | stringi::stri_detect_regex(x, "-01-") & 68 | stringi::stri_detect_regex(x, "-12-"), 69 | stringi::stri_replace_all_regex(stringi::stri_extract_first_regex(x, "[^,]*"), 70 | "-01-", "-XX-"), x) 71 | x <- ifelse(stringi::stri_detect_regex(x, ","), paste0("{", x, "}"), x) 72 | x 73 | } 74 | 75 | collapse_ranges <- function(x) { 76 | x <- stringi::stri_replace_all_regex(x, "([:digit:]{4})-01-01\\.\\.([:digit:]{4})-12-31", "$1") 77 | x <- stringi::stri_replace_all_regex(x, "([:digit:]{4}-[:digit:]{2})-01\\.\\.([:digit:]{4}-[:digit:]{2})-28", "$1") 78 | x <- stringi::stri_replace_all_regex(x, "([:digit:]{4}-[:digit:]{2})-01\\.\\.([:digit:]{4}-[:digit:]{2})-29", "$1") 79 | x <- stringi::stri_replace_all_regex(x, "([:digit:]{4}-[:digit:]{2})-01\\.\\.([:digit:]{4}-[:digit:]{2})-30", "$1") 80 | x <- stringi::stri_replace_all_regex(x, "([:digit:]{4}-[:digit:]{2})-01\\.\\.([:digit:]{4}-[:digit:]{2})-31", "$1") 81 | x <- stringi::stri_replace_all_regex(x, "(-[:digit:]{4})-01-01\\.\\.(-[:digit:]{4})-12-31", "$1") 82 | x <- stringi::stri_replace_all_regex(x, "(-[:digit:]{3})-01-01\\.\\.(-[:digit:]{3})-12-31", "$1") 83 | x <- stringi::stri_replace_all_regex(x, "(-[:digit:]{4}-[:digit:]{2})-01\\.\\.(-[:digit:]{4}-[:digit:]{2})-28", "$1") 84 | x <- stringi::stri_replace_all_regex(x, "(-[:digit:]{4}-[:digit:]{2})-01\\.\\.(-[:digit:]{4}-[:digit:]{2})-29", "$1") 85 | x <- stringi::stri_replace_all_regex(x, "(-[:digit:]{4}-[:digit:]{2})-01\\.\\.(-[:digit:]{4}-[:digit:]{2})-30", "$1") 86 | x <- stringi::stri_replace_all_regex(x, "(-[:digit:]{4}-[:digit:]{2})-01\\.\\.(-[:digit:]{4}-[:digit:]{2})-31", "$1") 87 | } 88 | 89 | is_sequence <- function(x) { 90 | l <- as.Date(x) + 1 91 | l <- c(lubridate::NA_Date_, l[-length(l)]) 92 | l <- x == l 93 | l[is.na(l)] <- FALSE 94 | l 95 | } 96 | -------------------------------------------------------------------------------- /R/convert_sequence.R: -------------------------------------------------------------------------------- 1 | #' Sequence method for messydates 2 | #' @description 3 | #' This function provides a sequence (`seq()`) method for messydates. 4 | #' This can be used with ranges or unspecified dates, 5 | #' and is particularly useful for defining a sequence of dates 6 | #' before the common era or between eras. 7 | #' @name convert_sequence 8 | #' @param from A messydate or range. 9 | #' If 'from' is a range and 'to' is not specified, 10 | #' 'from' will be the minimum of the range and 'to' will be maximum. 11 | #' @param to A messydate. 12 | #' @param by Increment of the sequence. By default "days". 13 | #' @param ... Arguments passed to or from methods. 14 | #' @examples 15 | #' seq(mdate("-0001-12-20"), mdate("0001-01-10")) 16 | #' @export 17 | seq.mdate <- function(from, to, by = "days", ...) { 18 | 19 | if(missing(to) & !is_precise(from)){ 20 | to <- max(from) 21 | from <- min(from) 22 | } 23 | 24 | # straight forward sequence 25 | if(!any(is_bce(c(from, to)))){ 26 | seq(as.Date(from), as.Date(to), by = by) 27 | } else { 28 | 29 | fromp <- as.Date(stringi::stri_replace_first_regex(from, "^-", "")) 30 | # sequence before common era 31 | if(is_bce(to)){ 32 | top <- as.Date(stringi::stri_replace_first_regex(to, "^-", "")) 33 | .neg_seqs(fromp, top, by = by) 34 | } else { 35 | # sequence between eras 36 | zero_padding(c(.neg_seqs(fromp, as.Date("0001-12-31"), by = by), 37 | as.character(seq(as.Date("0001-01-01"), as.Date(to), by = by)))) 38 | # zero_padding(c(rev(paste0("-", seq(as.Date("0001-01-01"), fromp, by = by))), 39 | # as.character(seq(as.Date("0001-01-01"), as.Date(to), by = by)))) 40 | } 41 | } 42 | } 43 | 44 | .neg_seqs <- function(fromp, top, by = "days"){ 45 | if(year(fromp) == year(top)){ 46 | zero_padding(paste0("-", seq(min(c(fromp, top)), 47 | max(c(fromp,top)), by = by))) 48 | } else { 49 | strt <- max(c(fromp, top)) 50 | ends <- min(c(fromp, top)) 51 | strt_yr <- year(strt) 52 | strt_sq <- seq(as.Date(strt), as.Date(paste0(strt_yr,"-12-31")), by = by) 53 | ends_yr <- year(ends) 54 | ends_sq <- seq(as.Date(paste0(ends_yr, "-01-01")), as.Date(ends), by = by) 55 | if(strt_yr - ends_yr > 1){ 56 | mids_sq <- seq(as.Date(paste0(ends_yr+1, "-01-01")), 57 | as.Date(paste0(strt_yr-1,"-12-31")), by = by) 58 | if(length(unique(year(mids_sq)))>1) 59 | mids_sq <- mids_sq[order(year(mids_sq), decreasing = TRUE)] 60 | zero_padding(paste0("-", c(strt_sq, mids_sq, ends_sq))) 61 | } else zero_padding(paste0("-", c(strt_sq, ends_sq))) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /R/data_battles.R: -------------------------------------------------------------------------------- 1 | #' Dates of battles in 2001 2 | #' 3 | #' A dataset containing the names and dates of battles in 2001, 4 | #' according to Wikipedia (https://en.wikipedia.org/wiki/List_of_battles_in_the_21st_century). 5 | #' 6 | #' @format A data frame with 20 rows and 2 variables: 7 | #' \describe{ 8 | #' \item{Battle}{name of the battle, character} 9 | #' \item{Date}{date or date range, a mdate class vector} 10 | #' \item{Parties}{parties to the conflict, character} 11 | #' \item{US_party}{is the US a party to the battle, numeric} 12 | #' \item{N_actors}{number of actors to conflict, numeric} 13 | #' } 14 | "battles" 15 | -------------------------------------------------------------------------------- /R/messydates-defunct.R: -------------------------------------------------------------------------------- 1 | #' Functions that have been renamed, superseded, or are no longer working 2 | #' 3 | #' `r lifecycle::badge("deprecated")` 4 | #' Generally these functions have been superseded or renamed. 5 | #' Upon using them, a message is provided directing the user to the new function. 6 | #' However, at this stage of package development, 7 | #' we generally clear older defunct functions at each minor release, 8 | #' and so you are strongly encouraged to use the new functions/names/syntax 9 | #' wherever possible and update your scripts accordingly. 10 | #' @name defunct 11 | #' @keywords internal 12 | NULL 13 | 14 | #' @describeIn defunct Deprecated on 2023-08-25. 15 | #' @export 16 | is_element <- function(.data) { 17 | .Deprecated(new = "messydates::is_subset()", 18 | package = "messydates") 19 | } 20 | 21 | #' @describeIn defunct Deprecated on 2023-08-25. 22 | #' @export 23 | md_intersect <- function(.data) { 24 | .Deprecated(new = "messydates::`%intersect%`()", 25 | package = "messydates") 26 | } 27 | 28 | #' @describeIn defunct Deprecated on 2023-08-25. 29 | #' @export 30 | md_union <- function(.data) { 31 | .Deprecated(new = "messydates::`%union%`()", 32 | package = "messydates") 33 | } 34 | 35 | #' @describeIn defunct Deprecated on 2023-08-25. 36 | #' @export 37 | md_multiset <- function(.data) { 38 | .Deprecated(new = "messydates::`+`()", 39 | package = "messydates") 40 | } 41 | -------------------------------------------------------------------------------- /R/operate_arithmetic.R: -------------------------------------------------------------------------------- 1 | #' Arithmetic operations for messydates 2 | #' 3 | #' These operations allow users to add or subtract dates messydate objects. 4 | #' Messydate objects include incomplete or uncertain dates, 5 | #' ranges of dates, negative dates, and date sets. 6 | #' @param e1 An `mdate` or date object. 7 | #' @param e2 An `mdate`, date, or numeric object. Must be a scalar. 8 | #' @return A messydates vector 9 | #' @examples 10 | #' \donttest{ 11 | #' d <- as_messydate(c("2008-03-25", "-2012-02-27", "2001-01?", "~2001", 12 | #' "2001-01-01..2001-02-02", "{2001-01-01,2001-02-02}", 13 | #' "2008-XX-31", "..2002-02-03", "2001-01-03..", "28 BC")) 14 | #' dplyr::tibble(date = d, add = d + 1, subtract = d - 1) 15 | #' dplyr::tibble(date = d, add = d + "1 year", subtract = d - "1 year") 16 | #' as_messydate("2001-01-01") + as_messydate("2001-01-02..2001-01-04") 17 | #' as_messydate("2001-01-01") + as_messydate("2001-01-03") 18 | #' as_messydate("2001-01-01..2001-01-04") - as_messydate("2001-01-02") 19 | #' #as_messydate("2001-01-01") - as_messydate("2001-01-03") 20 | #' } 21 | #' @name operate_arithmetic 22 | NULL 23 | 24 | #' @rdname operate_arithmetic 25 | #' @export 26 | `+.mdate` <- function(e1, e2) { 27 | e2 <- parse_date_strings(e2) 28 | add(e1, e2) 29 | } 30 | 31 | #' @rdname operate_arithmetic 32 | #' @export 33 | `-.mdate` <- function(e1, e2) { 34 | e2 <- parse_date_strings(e2) 35 | subtract(e1, e2) 36 | } 37 | 38 | add <- function(x, n) { 39 | if (is_messydate(n)) { 40 | x <- suppressMessages(expand(x)[[1]]) 41 | n <- suppressMessages(expand(n)[[1]]) 42 | if (any(is.element(n, x))) { 43 | n <- n[which(!is.element(n, x))] 44 | } 45 | x <- suppressMessages(contract(paste(c(x, n), collapse = ", "))) 46 | } else { 47 | # Step one: get only first and last components for ranges 48 | # But keep approximation for before or after date 49 | x <- ifelse(stringi::stri_detect_regex(x, "^\\.\\.|\\.\\.$"), x, expand(x)) 50 | # Step two, add by component 51 | x <- lapply(x, function(y) { 52 | if (stringi::stri_detect_regex(y[1], "^-")) { 53 | y <- paste0("-", lubridate::as_date(y) - n) 54 | } else if (stringi::stri_detect_regex(y[1], "^\\.\\.")) { 55 | y <- stringi::stri_replace_all_regex(y, "\\.\\.", "") 56 | y <- paste0("..", lubridate::as_date(y) + n) 57 | } else if (stringi::stri_detect_regex(y[1], "\\.\\.$")) { 58 | y <- stringi::stri_replace_all_regex(y, "\\.\\.", "") 59 | y <- paste0(lubridate::as_date(y) + n, "..") 60 | } else { 61 | y <- lubridate::as_date(y) + n 62 | } 63 | y 64 | }) 65 | x <- suppressMessages(contract(x)) 66 | } 67 | x 68 | } 69 | 70 | subtract <- function(x, n) { 71 | if (is_messydate(n)) { 72 | x <- as.character(expand(x)[[1]]) 73 | n <- as.character(expand(n)[[1]]) 74 | if (any(is.element(x, n))) { 75 | x <- as_messydate(list(x[which(!is.element(x,n))])) 76 | } else { 77 | message("First and second elements do not overlap.") 78 | x <- as_messydate(c(x, n)) 79 | } 80 | } else { 81 | # Step one: get only first and last components for ranges 82 | # But keep approximation for before or after date 83 | x <- ifelse(stringi::stri_detect_regex(x, "^\\.\\.|\\.\\.$"), x, expand(x)) 84 | # Step two, add by component 85 | x <- lapply(x, function(y) { 86 | if (stringi::stri_detect_regex(y[1], "^-")) { 87 | y <- paste0("-", lubridate::as_date(y) + n) 88 | } else if (stringi::stri_detect_regex(y[1], "^\\.\\.")) { 89 | y <- stringi::stri_replace_all_regex(y, "\\.\\.", "") 90 | y <- paste0("..", lubridate::as_date(y) - n) 91 | } else if (stringi::stri_detect_regex(y[1], "\\.\\.$")) { 92 | y <- stringi::stri_replace_all_regex(y, "\\.\\.", "") 93 | y <- paste0(lubridate::as_date(y) - n, "..") 94 | } else { 95 | y <- lubridate::as_date(y) - n 96 | } 97 | y 98 | }) 99 | x <- suppressMessages(contract(x)) 100 | } 101 | x 102 | } 103 | 104 | parse_date_strings <- function(e2) { 105 | if (is_messydate(e2)) { 106 | e2 <- contract(e2) 107 | } else { 108 | e2 <- ifelse(stringi::stri_detect_regex(e2, "years|year"), 109 | as.numeric(stringi::stri_replace_all_regex(e2, "years|year", "")) * 365, e2) 110 | e2 <- ifelse(stringi::stri_detect_regex(e2, "months|month"), 111 | as.numeric(stringi::stri_replace_all_regex(e2, "months|month", "")) * 30.42, e2) 112 | e2 <- ifelse(stringi::stri_detect_regex(e2, "days|day"), 113 | as.numeric(stringi::stri_replace_all_regex(e2, "days|day", "")), e2) 114 | } 115 | e2 116 | } 117 | -------------------------------------------------------------------------------- /R/operate_inequalities.R: -------------------------------------------------------------------------------- 1 | # Inequalities #### 2 | 3 | #' Logical operations on messy dates 4 | #' @param e1,e2 `mdate` or other class objects 5 | #' @name operate_inequalities 6 | NULL 7 | 8 | #' @describeIn operate_inequalities tests whether the dates in the first vector precede 9 | #' the dates in the second vector. 10 | #' Returns `NA` when the date order can't be determined. 11 | #' @examples 12 | #' as_messydate("2012-06-02") > as.Date("2012-06-01") # TRUE 13 | #' # 2012-06-XX could mean 2012-06-03, so unknown if it comes before 2012-06-02 14 | #' as_messydate("2012-06-XX") < as.Date("2012-06-02") # NA 15 | #' # But 2012-06-XX cannot be before 2012-06-01 16 | #' as_messydate("2012-06-XX") >= as.Date("2012-06-01") # TRUE 17 | #' @export 18 | `<.mdate` <- function(e1, e2) { 19 | if (!is_messydate(e1)) e1 <- as_messydate(e1) 20 | if (!is_messydate(e2)) e2 <- as_messydate(e2) 21 | ranges <- numeric_time_ranges(e1, e2) 22 | x <- rep(NA, max(length(e1), length(e2))) 23 | x[ranges[["max1"]] < ranges[["min2"]]] <- TRUE 24 | x[ranges[["min1"]] > ranges[["max2"]]] <- FALSE 25 | x[ranges[["max1"]] == ranges[["min2"]]] <- FALSE 26 | x[ranges[["min1"]] == ranges[["max2"]]] <- FALSE 27 | x 28 | } 29 | 30 | # Quoth the {lubridate} team: 31 | # Nothing else seems to work, only this sneaky trick. 32 | evalqOnLoad({ 33 | registerS3method("<", "Date", `<.mdate`) 34 | registerS3method("<", "POSIXt", `<.mdate`) 35 | }) 36 | 37 | numeric_time_ranges <- function(e1, e2) { 38 | if (is_messydate(e1)) { 39 | min1 <- as.Date(e1, FUN = vmin) 40 | max1 <- as.Date(e1, FUN = vmax) 41 | if (lubridate::is.POSIXt(e2)) { 42 | ptz <- lubridate::tz(e2) 43 | min1 <- lubridate::force_tz(min1, ptz) 44 | min1 <- as.POSIXct(min1) 45 | max1 <- lubridate::force_tz(max1, ptz) 46 | max1 <- as.POSIXct(max1) 47 | } 48 | } else { 49 | min1 <- max1 <- e1 50 | } 51 | if (is_messydate(e2)) { 52 | min2 <- as.Date(e2, FUN = vmin) 53 | max2 <- as.Date(e2, FUN = vmax) 54 | if (lubridate::is.POSIXt(e1)) { 55 | ptz <- lubridate::tz(e1) 56 | min2 <- lubridate::force_tz(min2, ptz) 57 | min2 <- as.POSIXct(min2) 58 | max2 <- lubridate::force_tz(max2, ptz) 59 | max2 <- as.POSIXct(max2) 60 | } 61 | } else { 62 | min2 <- max2 <- e2 63 | } 64 | list( 65 | min1 = as.numeric(min1), max1 = as.numeric(max1), 66 | min2 = as.numeric(min2), max2 = as.numeric(max2) 67 | ) 68 | } 69 | 70 | #' @describeIn operate_inequalities tests whether the dates in the first vector 71 | #' succeed the dates in the second vector. 72 | #' Returns `NA` when the date order can't be determined. 73 | #' @export 74 | `>.mdate` <- function(e1, e2) { 75 | if (!is_messydate(e1)) e1 <- as_messydate(e1) 76 | if (!is_messydate(e2)) e2 <- as_messydate(e2) 77 | ranges <- numeric_time_ranges(e1, e2) 78 | x <- rep(NA, max(length(e1), length(e2))) 79 | x[ranges[["min1"]] > ranges[["max2"]]] <- TRUE 80 | x[ranges[["max1"]] < ranges[["min2"]]] <- FALSE 81 | x[ranges[["min1"]] == ranges[["max2"]]] <- FALSE 82 | x[ranges[["max1"]] == ranges[["min2"]]] <- FALSE 83 | x 84 | } 85 | 86 | evalqOnLoad({ 87 | registerS3method(">", "Date", `>.mdate`) 88 | registerS3method(">", "POSIXt", `>.mdate`) 89 | }) 90 | 91 | #' @describeIn operate_inequalities tests whether the dates in the first vector are 92 | #' equal to or precede the dates in the second vector. 93 | #' Returns `NA` when the date order can't be determined. 94 | #' @export 95 | `<=.mdate` <- function(e1, e2) { 96 | if (!is_messydate(e1)) e1 <- as_messydate(e1) 97 | if (!is_messydate(e2)) e2 <- as_messydate(e2) 98 | ranges <- numeric_time_ranges(e1, e2) 99 | x <- rep(NA, max(length(e1), length(e2))) 100 | x[ranges[["max1"]] <= ranges[["min2"]]] <- TRUE 101 | x[ranges[["min1"]] > ranges[["max2"]]] <- FALSE 102 | x 103 | } 104 | 105 | evalqOnLoad({ 106 | registerS3method("<=", "Date", `<=.mdate`) 107 | registerS3method("<=", "POSIXt", `<=.mdate`) 108 | }) 109 | 110 | #' @describeIn operate_inequalities tests whether the dates in the first vector are equal to 111 | #' or succeed the dates in the second vector. 112 | #' Returns `NA` when the date order can't be determined. 113 | #' @export 114 | `>=.mdate` <- function(e1, e2) { 115 | if (!is_messydate(e1)) e1 <- as_messydate(e1) 116 | if (!is_messydate(e2)) e2 <- as_messydate(e2) 117 | ranges <- numeric_time_ranges(e1, e2) 118 | x <- rep(NA, max(length(e1), length(e2))) 119 | x[ranges[["min1"]] >= ranges[["max2"]]] <- TRUE 120 | x[ranges[["max1"]] < ranges[["min2"]]] <- FALSE 121 | x 122 | } 123 | 124 | evalqOnLoad({ 125 | registerS3method(">=", "Date", `>=.mdate`) 126 | registerS3method(">=", "POSIXt", `>=.mdate`) 127 | }) 128 | -------------------------------------------------------------------------------- /R/operate_proportional.R: -------------------------------------------------------------------------------- 1 | #' Proportion of messy dates meeting logical test 2 | #' @description 3 | #' These functions provide various proportional tests for messy date objects. 4 | #' @name operate_proportional 5 | #' @param e1,e2 `mdate` or other class objects 6 | #' @return The proportion that the comparison is true. 7 | #' @return A logical vector the same length as the `mdate` passed. 8 | NULL 9 | 10 | #' @rdname operate_proportional 11 | #' @export 12 | `%l%` <- function(e1, e2) UseMethod("%l%") 13 | 14 | #' @describeIn operate_proportional Tests proportion of dates in the first vector 15 | #' that precede the minimum in the second vector. 16 | #' @examples 17 | #' as_messydate("2012-06") < as.Date("2012-06-02") 18 | #' as_messydate("2012-06") %l% as_messydate("2012-06-02") 19 | #' @export 20 | `%l%.mdate` <- function(e1, e2) { 21 | if(length(e1)!=length(e2)) 22 | stop("Can only compare vectors of equal length.") 23 | # Need to fix this for element wise on vectors... 24 | suppressMessages(purrr::map2_dbl(expand(e1), expand(e2), 25 | ~ mean(.x < min(.y)))) 26 | } 27 | 28 | evalqOnLoad({ 29 | registerS3method("%l%", "Date", `%l%.mdate`) 30 | registerS3method("%l%", "POSIXt", `%l%.mdate`) 31 | }) 32 | 33 | #' @rdname operate_proportional 34 | #' @export 35 | `%g%` <- function(e1, e2) UseMethod("%g%") 36 | 37 | #' @describeIn operate_proportional Tests proportion of dates in the first vector 38 | #' that follow the maximum in the second vector. 39 | #' @export 40 | #' @examples 41 | #' as_messydate("2012-06") > as.Date("2012-06-02") 42 | #' as_messydate("2012-06") %g% as_messydate("2012-06-02") 43 | `%g%.mdate` <- function(e1, e2) { 44 | if(length(e1)!=length(e2)) 45 | stop("Can only compare vectors of equal length.") 46 | # Need to fix this for element wise on vectors... 47 | suppressMessages(purrr::map2_dbl(expand(e1), expand(e2), 48 | ~ mean(.x > max(.y)))) 49 | } 50 | 51 | evalqOnLoad({ 52 | registerS3method("%g%", "Date", `%g%.mdate`) 53 | registerS3method("%g%", "POSIXt", `%g%.mdate`) 54 | }) 55 | 56 | #' @rdname operate_proportional 57 | #' @export 58 | `%ge%` <- function(e1, e2) UseMethod("%ge%") 59 | 60 | #' @describeIn operate_proportional Tests proportion of dates in the first vector 61 | #' that follow or are equal to the maximum in the second vector. 62 | #' @export 63 | #' @examples 64 | #' as_messydate("2012-06") >= as.Date("2012-06-02") 65 | #' as_messydate("2012-06") %ge% as_messydate("2012-06-02") 66 | `%ge%.mdate` <- function(e1, e2) { 67 | if(length(e1)!=length(e2)) 68 | stop("Can only compare vectors of equal length.") 69 | # Need to fix this for element wise on vectors... 70 | suppressMessages(purrr::map2_dbl(expand(e1), expand(e2), 71 | ~ mean(.x >= max(.y)))) 72 | } 73 | 74 | evalqOnLoad({ 75 | registerS3method("%ge%", "Date", `%ge%.mdate`) 76 | registerS3method("%ge%", "POSIXt", `%ge%.mdate`) 77 | }) 78 | 79 | #' @rdname operate_proportional 80 | #' @export 81 | `%le%` <- function(e1, e2) UseMethod("%le%") 82 | 83 | #' @describeIn operate_proportional Tests proportion of dates in the first vector 84 | #' that precede or are equal to the minimum in the second vector. 85 | #' @export 86 | #' @examples 87 | #' as_messydate("2012-06") <= as.Date("2012-06-02") 88 | #' as_messydate("2012-06") %le% "2012-06-02" 89 | `%le%.mdate` <- function(e1, e2) { 90 | if(length(e1)!=length(e2)) 91 | stop("Can only compare vectors of equal length.") 92 | # Need to fix this for element wise on vectors... 93 | suppressMessages(purrr::map2_dbl(expand(e1), expand(e2), 94 | ~ mean(.x <= min(.y)))) 95 | } 96 | 97 | evalqOnLoad({ 98 | registerS3method("%le%", "Date", `%le%.mdate`) 99 | registerS3method("%le%", "POSIXt", `%le%.mdate`) 100 | }) 101 | 102 | #' @rdname operate_proportional 103 | #' @export 104 | `%><%` <- function(e1, e2) UseMethod("%><%") 105 | 106 | #' @describeIn operate_proportional Tests proportion of dates in the first vector 107 | #' that are between the minimum and maximum dates in the second vector. 108 | #' @export 109 | #' @examples 110 | #' as_messydate("2012-06") %><% as_messydate("2012-06-15..2012-07-15") 111 | `%><%.mdate` <- function(e1, e2) { 112 | if(length(e1)!=length(e2)) 113 | stop("Can only compare vectors of equal length.") 114 | # Need to fix this for element wise on vectors... 115 | # Need to create fast way to trim ranges or just get dates within the range 116 | suppressMessages(purrr::map2_dbl(e1, e2, 117 | ~ length(.x %intersect% .y)/ 118 | (length(unlist(expand(.x)))+1))) 119 | } 120 | 121 | evalqOnLoad({ 122 | registerS3method("%><%", "Date", `%><%.mdate`) 123 | registerS3method("%><%", "POSIXt", `%><%.mdate`) 124 | }) 125 | 126 | #' @rdname operate_proportional 127 | #' @export 128 | `%>=<%` <- function(e1, e2) UseMethod("%>=<%") 129 | 130 | #' @describeIn operate_proportional Tests proportion of dates in the first vector that 131 | #' are between the minimum and maximum dates in the second vector, inclusive. 132 | #' @export 133 | #' @examples 134 | #' as_messydate("2012-06") %>=<% as_messydate("2012-06-15..2012-07-15") 135 | `%>=<%.mdate` <- function(e1, e2) { 136 | if(length(e1)!=length(e2)) 137 | stop("Can only compare vectors of equal length.") 138 | # Need to fix this for element wise on vectors... 139 | suppressMessages(purrr::map2_dbl(e1, e2, ~ length(.x %intersect% .y)/ 140 | length(unlist(expand(.x))))) 141 | } 142 | 143 | evalqOnLoad({ 144 | registerS3method("%>=<%", "Date", `%>=<%.mdate`) 145 | registerS3method("%>=<%", "POSIXt", `%>=<%.mdate`) 146 | }) 147 | -------------------------------------------------------------------------------- /R/operate_set.R: -------------------------------------------------------------------------------- 1 | #' Set operations for messy dates 2 | #' @description 3 | #' Performs intersection (`md_intersect()`) and union (`md_union()`) on, 4 | #' inter alia, messy date class objects. 5 | #' For a more typical 'join' that retains all elements, even if duplicated, 6 | #' please use `md_multiset`. 7 | #' @name operate_set 8 | #' @param e1,e2 Messy date or other class objects 9 | #' @return A vector of the same mode for `intersect`, 10 | #' or a common mode for union. 11 | NULL 12 | 13 | #' @rdname operate_set 14 | #' @export 15 | `%intersect%` <- function(e1, e2) UseMethod("%intersect%") 16 | 17 | #' @describeIn operate_set Find intersection of sets of messy dates 18 | #' @examples 19 | #' as_messydate("2012-01-01..2012-01-20") %intersect% as_messydate("2012-01") 20 | #' @export 21 | `%intersect%.mdate` <- function(e1, e2) { 22 | x <- as.character(expand(e1)[[1]]) 23 | y <- as.character(expand(e2)[[1]]) 24 | intersect(x, y) 25 | } 26 | 27 | evalqOnLoad({ 28 | registerS3method("%intersect%", "Date", `%intersect%.mdate`) 29 | registerS3method("%intersect%", "POSIXt", `%intersect%.mdate`) 30 | }) 31 | 32 | #' @rdname operate_set 33 | #' @export 34 | `%union%` <- function(e1, e2) UseMethod("%union%") 35 | 36 | #' @describeIn operate_set Find intersection of sets of messy dates 37 | #' @examples 38 | #' as_messydate("2012-01-01..2012-01-20") %union% as_messydate("2012-01") 39 | #' @export 40 | `%union%.mdate` <- function(e1, e2) { 41 | x <- as.character(expand(e1)[[1]]) 42 | y <- as.character(expand(e2)[[1]]) 43 | union(x, y) 44 | } 45 | 46 | evalqOnLoad({ 47 | registerS3method("%union%", "Date", `%union%.mdate`) 48 | registerS3method("%union%", "POSIXt", `%union%.mdate`) 49 | }) 50 | -------------------------------------------------------------------------------- /R/operate_statements.R: -------------------------------------------------------------------------------- 1 | # Statements #### 2 | 3 | #' Logical statements on messy dates 4 | #' @description 5 | #' These functions provide various logical statements about messy date objects. 6 | #' @name operate_statements 7 | #' @param x,y `mdate` or other class objects 8 | #' @return A logical vector the same length as the `mdate` passed. 9 | NULL 10 | 11 | #' @describeIn operate_statements tests whether the object inherits the `mdate` class. 12 | #' If more rigorous validation is required, see `validate_messydate()`. 13 | #' @examples 14 | #' is_messydate(as_messydate("2012-01-01")) 15 | #' is_messydate(as.Date("2012-01-01")) 16 | #' @export 17 | is_messydate <- function(x) { 18 | inherits(x, "mdate") 19 | } 20 | 21 | #' @describeIn operate_statements tests whether there is any intersection between 22 | #' two messy dates, leveraging `intersect()`. 23 | #' @examples 24 | #' is_intersecting(as_messydate("2012-01"), 25 | #' as_messydate("2012-01-01..2012-02-22")) 26 | #' is_intersecting(as_messydate("2012-01"), 27 | #' as_messydate("2012-02-01..2012-02-22")) 28 | #' @export 29 | is_intersecting <- function(x, y) { 30 | length(intersect(unlist(expand(x)), unlist(expand(y)))) > 0 31 | } 32 | 33 | #' @describeIn operate_statements tests whether one or more messy date can be found 34 | #' within a messy date range or set. 35 | #' @examples 36 | #' is_subset(as_messydate("2012-01-01"), as_messydate("2012-01")) 37 | #' is_subset(as_messydate("2012-01-01..2012-01-03"), as_messydate("2012-01")) 38 | #' is_subset(as_messydate("2012-01-01"), as_messydate("2012-02")) 39 | #' @export 40 | is_subset <- function(x, y) { 41 | x <- as.character(expand(x)[[1]]) 42 | y <- as.character(expand(y)[[1]]) 43 | any(is.element(x, y)) 44 | } 45 | 46 | #' @describeIn operate_statements tests whether two dates contain similar components. 47 | #' This can be useful for identifying dates that may be typos of one another. 48 | #' @examples 49 | #' is_similar(as_messydate("2012-06-02"), as_messydate("2012-02-06")) 50 | #' is_similar(as_messydate("2012-06-22"), as_messydate("2012-02-06")) 51 | #' @export 52 | is_similar <- function(x, y) { 53 | year(x) == year(y) & month(x) == day(y) & day(x) == month(y) 54 | } 55 | 56 | #' @describeIn operate_statements tests whether a date is precise (i.e. an 8 digit date). 57 | #' Non-precise dates contain markers that they are approximate (i.e. ~), 58 | #' unreliable (i.e. ?), are incomplete dates (i.e. year only), 59 | #' or date ranges and sets. 60 | #' @examples 61 | #' is_precise(as_messydate(c("2012-06-02", "2012-06"))) 62 | #' @export 63 | is_precise <- function(x) { 64 | stringi::stri_detect_regex(x, "^[:digit:]{4}-[:digit:]{2}-[:digit:]{2}$| 65 | |^-[:digit:]{4}-[:digit:]{2}-[:digit:]{2}$") 66 | } 67 | 68 | #' @describeIn operate_statements tests whether a date is uncertain (i.e. contains ?). 69 | #' @examples 70 | #' is_uncertain(as_messydate(c("2012-06-02", "2012-06-02?"))) 71 | #' @export 72 | is_uncertain <- function(x) { 73 | stringi::stri_detect_regex(x, "\\?|\\%") 74 | } 75 | 76 | #' @describeIn operate_statements tests whether a date is approximate (i.e. contains ~). 77 | #' @examples 78 | #' is_approximate(as_messydate(c("2012-06-02~", "2012-06-02"))) 79 | #' @export 80 | is_approximate <- function(x) { 81 | stringi::stri_detect_regex(x, "\\~|\\%") 82 | } 83 | 84 | #' @describeIn operate_statements tests whether one or more messy dates are found 85 | #' before the common era. 86 | #' @examples 87 | #' is_bce(as_messydate(c("2012-06-02", "-2012-06-02"))) 88 | #' @export 89 | is_bce <- function(x) { 90 | stringi::stri_detect_regex(x, "^-") 91 | } 92 | 93 | -------------------------------------------------------------------------------- /R/sysdata.rda: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/globalgov/messydates/851677d9a6bbb87be2a7fa373bdbb4e1dac45d50/R/sysdata.rda -------------------------------------------------------------------------------- /README.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | output: github_document 3 | always_allow_html: true 4 | --- 5 | 6 | # messydates messydates package logo 7 | 8 | ```{r, include = FALSE} 9 | knitr::opts_chunk$set( 10 | collapse = TRUE, 11 | comment = "#>", 12 | fig.path = "man/figures/README-", 13 | out.width = "100%" 14 | ) 15 | ``` 16 | 17 | 18 | [![Lifecycle: maturing](https://img.shields.io/badge/lifecycle-maturing-blue.svg)](https://lifecycle.r-lib.org/articles/stages.html#maturing) 19 | ![CRAN/METACRAN](https://img.shields.io/cran/v/messydates) 20 | ![GitHub release (latest by date)](https://img.shields.io/github/v/release/globalgov/messydates) 21 | ![GitHub Release Date](https://img.shields.io/github/release-date/globalgov/messydates) 22 | 23 | [![Codecov test coverage](https://codecov.io/gh/globalgov/messydates/branch/main/graph/badge.svg)](https://app.codecov.io/gh/globalgov/messydates?branch=main) 24 | [![CodeFactor](https://www.codefactor.io/repository/github/globalgov/messydates/badge)](https://www.codefactor.io/repository/github/globalgov/messydates) 25 | [![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/5061/badge)](https://bestpractices.coreinfrastructure.org/projects/5061) 26 | 27 | 28 | ## Why this package? 29 | 30 | Existing packages for working with dates in R expect them to be _tidy_. 31 | That is, they should be in or coercible to the standard `yyyy-mm-dd` format. 32 | 33 | But dates are often **_messy_**. 34 | Sometimes we only know the year when something happened, 35 | leaving other components of the date, such as the month or day, _unspecified_. 36 | This is often the case with historical dates, for instance. 37 | Sometimes we can only say _approximately_ when an event occurred, 38 | that it occurred _before_ or _after_ a certain date, 39 | or we recognise that our best estimate comes from a _dubious_ source. 40 | Other times there exists a _set_ or _range_ of possible dates for an event. 41 | 42 | Although researchers generally recognise this messiness, 43 | many feel expected to force artificial precision or unfortunate imprecision 44 | on temporal data to proceed with analysis. 45 | For example, if we only know something happened in `2021`, 46 | then we might revert to a panel data design 47 | _even if greater precision is available_, 48 | or opt to replace this date with the start of that year (`2021-01-01`), 49 | assuming that erring on the earlier (or later) side is more justifiable than 50 | a random date within that month or year. 51 | 52 | However, this can create inferential issues when timing or sequence is important. 53 | `{messydates}` assists with this problem by retaining and working 54 | with various kinds of date imprecision. 55 | 56 | ```{r setup, echo=FALSE, include=FALSE, warning=FALSE} 57 | library(lubridate) 58 | library(tibble) 59 | library(dplyr) 60 | library(knitr) 61 | library(kableExtra) 62 | ``` 63 | 64 | ## A quick overview 65 | 66 | `{messydates}` implements for R the Extended Date/Time Format (EDTF) annotations 67 | set by the International Organization for Standardization (ISO) 68 | outlined in [ISO 8601-2_2019(E)](https://www.iso.org/standard/70908.html). 69 | `{messydates}` introduces a new `mdate` class that embeds these annotations, 70 | and offers a set of methods for constructing and coercing into and from the `mdate` class, 71 | as well as tools for working with such 'messy' dates. 72 | 73 | ```{r comparison, warning=FALSE, message=FALSE} 74 | pkg_comparison <- tibble::tribble(~Example, ~OriginalDate, 75 | "Normal date", "2012-01-01", 76 | "Future date", "2599-12-31", 77 | "Historical date", "476", 78 | "Era date", "33 BC", 79 | "Written date", "First of February, two thousand and twelve", 80 | "DMY date", "10-31-2012", 81 | "MDY date", "31-10-2012", 82 | "Wrongly specified date", "2012-31-10", 83 | "Approximate date", "2012-01-12~", 84 | "Uncertain date", "2012-01-01?", 85 | "Unspecified date", "2012-01", 86 | "Censored date", "..2012-01-12", 87 | "Range of dates", "2012-11-01:2012-12-01", 88 | "Set of dates", "2012-5-26, 2012-11-19, 2012-12-4") %>% 89 | dplyr::mutate(base = as.Date(OriginalDate), 90 | lubridate = suppressWarnings(lubridate::as_date(OriginalDate)), 91 | messydates = messydates::as_messydate(OriginalDate)) 92 | ``` 93 | 94 | ```{r compprint, echo=FALSE, results='asis'} 95 | kbl(pkg_comparison) %>% kable_styling("striped") %>% 96 | column_spec(3, 97 | color = ifelse((paste(pkg_comparison$base) == paste(pkg_comparison$messydates)), 98 | "black", "red")) %>% 99 | column_spec(4, 100 | color = ifelse((paste(pkg_comparison$lubridate) == paste(pkg_comparison$messydates)), 101 | "black", "red")) %>% 102 | column_spec(5, 103 | color = ifelse((paste(pkg_comparison$messydates) == paste(pkg_comparison$messydates)), 104 | "black", "red")) 105 | ``` 106 | 107 | As can be seen in the table above, 108 | other date/time packages in R do not handle 'messy' dates well. 109 | Normal "yyyy-mm-dd" structures or other date formats that can easily be 110 | coerced into this structure are usually not a problem. 111 | 112 | However, some syntaxes are entirely ignored, 113 | such as historical dates and dates from other eras (e.g. BCE), 114 | as well as written dates, frequently used in historical texts or treaties. 115 | 116 | Other times, existing packages return a date, but strip away any annotations 117 | that express uncertainty or approximateness, 118 | introducing artificial precision. 119 | 120 | And sometimes returning only a single date means ignoring other information included. 121 | We see this here in how only the end of the censored date, 122 | only the start of the date range, or the first in the set of dates is returned. 123 | Sometimes date components even seem guessed, such as how `2021-01` (January 2021) 124 | is assumed to be 1 _December_ 2021 by `{lubridate}`. 125 | 126 | So only `{messydates}` enables researchers to retain all this information. 127 | But most analysis does still expect some precision in dates to work. 128 | 129 | ## Working with messy dates 130 | 131 | The first way that `{messydates}` assists researchers that use dates in `mdate` class 132 | is to provide methods for converting back into common date classes 133 | such as `Date`, `POSIXct`, and `POSIXlt`. 134 | It is thus fully compatible with packages such as `{lubridate}` and `{anydate}`. 135 | 136 | As messy date annotations can indicate multiple possible dates, 137 | `{messydates}` allows e.g. ranges or sets of dates to be 138 | unpacked or expanded into all compatible dates. 139 | 140 | Since most methods of analysis or modelling expect single date observations, 141 | we offer ways to resolve this multiplicity when coercing `mdate`-class objects 142 | into other date formats. 143 | For example, researcher might explicitly choose to favour 144 | the `min()`, `max()`, `mean()`, `median()`, or even a `random()` date. 145 | This greatly facilitates research transparency by demanding a conscious choice from researchers, 146 | as well as supporting robustness checks by enabling description or inference across 147 | dates compatible with the messy annotated date. 148 | 149 | ```{r work} 150 | resolve_mdate <- pkg_comparison %>% 151 | dplyr::select(messydates) %>% 152 | dplyr::mutate(min = as.Date(messydates, min), 153 | median = as.Date(messydates, median), 154 | max = as.Date(messydates, max)) 155 | ``` 156 | 157 | ```{r workprint, echo = FALSE, results='asis'} 158 | kbl(resolve_mdate) %>% kable_styling("striped") 159 | ``` 160 | 161 | As can be seen in the table above, all 'precise' dates are respected as such, 162 | and returned no matter what 'resolution' function is given. 163 | But for messy dates, the choice of function can make a difference. 164 | Where only a year is given, e.g. `0476` or `-0033`, 165 | we draw from all the days in the year. 166 | The minimum is the first of January and the maximum the 31st of December. 167 | Dates are also drawn from a set or range of dates when given. 168 | 169 | When only an approximate or censored date is known, 170 | then depending on whether the whole date or just a component of the date is annotated, 171 | then a range of dates is imputed based on some window (by default 3 years, months, or days), 172 | and then a precise date is resolved from that. 173 | 174 | This translation via an expanded list of compatible dates is fast, robust, and extensible, 175 | allowing researchers to use messy dates in an analytic strategy that uses any other package. 176 | 177 | ## Cheat Sheet 178 | 179 | Please see the cheat sheet and [the messydates website](https://globalgov.github.io/messydates/) for more information about 180 | how to use `{messydates}`. 181 | 182 | messydates cheatsheet 183 | 184 | ## Installation 185 | 186 | The easiest way to install `{messydates}` is directly from CRAN: 187 | 188 | ``` r 189 | install.packages("messydates") 190 | ``` 191 | 192 | However, you may also install the development version from [GitHub](https://github.com/). 193 | 194 | ``` {r git, eval=FALSE} 195 | # install.packages("remotes") 196 | remotes::install_github("globalgov/messydates") 197 | ``` 198 | 199 | ## Funding 200 | 201 | The package was developed as part of [the PANARCHIC project](https://panarchic.ch), 202 | which studies the effects of network and power on how quickly states join, reform, or create 203 | international institutions by examining the historical dynamics of institutional networks from different domains. 204 | 205 | The PANARCHIC project is funded by the Swiss National Science Foundation ([SNSF](https://data.snf.ch/grants/grant/188976)). 206 | For more information on current projects of the Geneva Global Governance Observatory, 207 | please see [our Github website](https://github.com/globalgov). 208 | -------------------------------------------------------------------------------- /cran-comments.md: -------------------------------------------------------------------------------- 1 | ## Test environments 2 | 3 | * local R installation, macOS 15.3.2, R 4.4.3 4 | * macOS 14.7.4 (on Github), R 4.4.3 5 | * Microsoft Windows Server 2022 10.0.20348 (on Github), R 4.4.3 6 | * Ubuntu 24.04.2 (on Github), R 4.4.3 7 | 8 | ## R CMD check results 9 | 10 | 0 errors | 0 warnings | 0 notes 11 | 12 | - This version (v0.5.3) required for next version of manydata, to be submitted to CRAN soon 13 | -------------------------------------------------------------------------------- /data-raw/battles.R: -------------------------------------------------------------------------------- 1 | ## code to prepare `battles` dataset 2 | #library(dplyr) 3 | battles <- tibble::tribble(~Battle, ~Date, ~Parties, 4 | "Operation MH-2", "2001 March 8", "MK-National Libration Army(MK)", 5 | "2001 Bangladesh–India border clashes", "2001-04-16..2001-04-20", 6 | "BD-ID", 7 | "Operation Vaksince", "25-5-2001", 8 | "MK-National Libration Army(MK)", 9 | "Alkhan-Kala operation", "2001-6-22..2001-6-28", 10 | "RU-Chechen Republic", 11 | "Battle of Vedeno", "2001-8-13..2001-8-26", 12 | "RU-Chechen Insurgents", 13 | "Operation Crescent Wind", "2001-10-7..2001-12?", "US/UK-Taliban", 14 | "Operation Rhino", "2001-10-19..2001-10-20", "US-Taliban", 15 | "Battle of Mazar-e-Sharif", "2001-11-9", 16 | "US/Northern Alliance-Taliban/al-Qaeda", 17 | "Siege of Kunduz", "2001-11-11..2001-11-23", 18 | "US/Northern Alliance-Taliban/al-Qaeda", 19 | "Battle of Herat", "Twelve of November of two thousand and one", 20 | "US/Northern Alliance/Iran-Taliban", 21 | "Battle of Kabul", "2001-11-13..2001-11-14", 22 | "US/Northern Alliance-Taliban", 23 | "Battle of Tarin Kowt", "2001-11-13:2001-11-14", 24 | "US/Eastern Alliance-Taliban", 25 | "Operation Trent", "2001-11-~15..2001-11-~30", 26 | "US/UK-Taliban/al-Qaeda", 27 | "Battle of Kandahar", "2001-11-22..2001-12-07", 28 | "US/AU/Eastern Alliance-Taliban", 29 | "Battle of Qala-i-Jangi", "2001-11-25:2001-12-01", 30 | "US/UK/Northern Alliance-Taliban/al-Qaeda", 31 | "Battle of Tora Bora", "2001-12-12..2001-12-17", 32 | "US/Northern Alliance-Taliban/al-Qaeda", 33 | "Battle of Shawali Kowt", "2001-12-3", 34 | "US/Eastern Alliance-Taliban", 35 | "Battle of Sayyd Alma Kalay", "2001-12-4", 36 | "US/Eastern Alliance-Taliban", 37 | "Battle of Amami-Oshima", "2001-12-22", "JP-KP", 38 | "Tsotsin-Yurt operation", "2001-12-30:2002-01-03", 39 | "RU-Chechen Insurgents") %>% 40 | dplyr::mutate(US_party = ifelse(grepl("US", Parties), 1, 0), 41 | N_actors = c(2, 2, 2, 2, 2, 3, 2, 4, 4, 4, 42 | 3, 3, 4, 4, 5, 4, 3, 3, 2, 2)) 43 | battles$Date <- as_messydate(battles$Date) 44 | battles 45 | usethis::use_data(battles, overwrite = TRUE) 46 | -------------------------------------------------------------------------------- /data/battles.rda: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/globalgov/messydates/851677d9a6bbb87be2a7fa373bdbb4e1dac45d50/data/battles.rda -------------------------------------------------------------------------------- /dev_messydates.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | ProjectId: 96fbb291-857d-4747-a4db-d0cdf654e6bf 3 | 4 | RestoreWorkspace: No 5 | SaveWorkspace: No 6 | AlwaysSaveHistory: Default 7 | 8 | EnableCodeIndexing: Yes 9 | UseSpacesForTab: Yes 10 | NumSpacesForTab: 2 11 | Encoding: UTF-8 12 | 13 | RnwWeave: Sweave 14 | LaTeX: pdfLaTeX 15 | 16 | AutoAppendNewline: Yes 17 | StripTrailingWhitespace: Yes 18 | LineEndingConversion: Posix 19 | 20 | BuildType: Package 21 | PackageUseDevtools: Yes 22 | PackageInstallArgs: --no-multiarch --with-keep.source 23 | PackageCheckArgs: --as-cran 24 | PackageRoxygenize: rd,collate,namespace,vignette 25 | -------------------------------------------------------------------------------- /inst/CITATION: -------------------------------------------------------------------------------- 1 | citHeader("To cite messydates in publications use:") 2 | 3 | utils::bibentry( 4 | bibtype = "Manual", 5 | title = "messydates: A broader date class for messy date information", 6 | author = "James Hollway", 7 | year = "2021", 8 | version = "0.4.0", 9 | url = "https://github.com/globalgov/messydates", 10 | textVersion = paste("J. Hollway. messydates: A broader date class for messy date information. 2021." 11 | ) 12 | ) 13 | -------------------------------------------------------------------------------- /inst/figures/cheatsheet.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/globalgov/messydates/851677d9a6bbb87be2a7fa373bdbb4e1dac45d50/inst/figures/cheatsheet.pdf -------------------------------------------------------------------------------- /man/battles.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/data_battles.R 3 | \docType{data} 4 | \name{battles} 5 | \alias{battles} 6 | \title{Dates of battles in 2001} 7 | \format{ 8 | A data frame with 20 rows and 2 variables: 9 | \describe{ 10 | \item{Battle}{name of the battle, character} 11 | \item{Date}{date or date range, a mdate class vector} 12 | \item{Parties}{parties to the conflict, character} 13 | \item{US_party}{is the US a party to the battle, numeric} 14 | \item{N_actors}{number of actors to conflict, numeric} 15 | } 16 | } 17 | \usage{ 18 | battles 19 | } 20 | \description{ 21 | A dataset containing the names and dates of battles in 2001, 22 | according to Wikipedia (https://en.wikipedia.org/wiki/List_of_battles_in_the_21st_century). 23 | } 24 | \keyword{datasets} 25 | -------------------------------------------------------------------------------- /man/class_create.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/class_mdate.R 3 | \name{class_create} 4 | \alias{class_create} 5 | \alias{new_messydate} 6 | \alias{validate_messydate} 7 | \title{A flexible date class for messy dates} 8 | \usage{ 9 | new_messydate(x = character()) 10 | 11 | validate_messydate(x) 12 | } 13 | \arguments{ 14 | \item{x}{A character scalar or vector in the expected \code{"yyyy-mm-dd"} format 15 | annotated, as necessary, according to ISO 8601-2_2019(E).} 16 | } 17 | \value{ 18 | Object of class \code{mdate} 19 | } 20 | \description{ 21 | Recent extensions to standardised date notation in 22 | \href{https://www.iso.org/standard/70908.html}{ISO 8601-2_2019(E)} 23 | create space for unspecified, uncertain, and approximate dates, 24 | as well as succinct representation of ranges of dates. 25 | These functions create and validate a new date class for R 26 | that can contain and parse these annotations, 27 | and are not typically user-facing. 28 | Please see \code{as_messydate()} for the user-facing coercion function. 29 | } 30 | \section{Date annotations}{ 31 | 32 | \emph{Unspecified date components}, such as when the day is unknown, 33 | can be represented by one or more \code{X}s in place of the digits. 34 | The modifier \code{*} is recommended to indicate that the entire 35 | time scale component value is unspecified, e.g. \code{X*-03-03}, 36 | however this is not implemented here. 37 | Please be explicit about the digits that are unspecified, 38 | e.g. \code{XXXX-03-03} expresses 3rd March in some unspecified year, 39 | whereas \code{2003-XX-03} expresses the 3rd of some month in 2003. 40 | If time components are not given, they are expanded to this. 41 | 42 | \emph{Approximate date components}, modified by \code{~}, 43 | represent an estimate whose value is asserted 44 | to be possibly correct. 45 | For example, \code{2003~-03-03} 46 | The degree of confidence in approximation 47 | depends on the application. 48 | 49 | \emph{Uncertain date components}, modified by \verb{?}, 50 | represent a date component whose source is considered 51 | to be dubious and therefore not to be relied upon. 52 | An additional modifier, \verb{\%}, is used to indicate 53 | a value that is both uncertain and approximate. 54 | } 55 | 56 | \section{Date sets}{ 57 | 58 | These functions also introduce standard notation 59 | for ranges of dates. 60 | Rather than the typical R notation for ranges, 61 | \code{:}, ISO 8601-2_2019(E) recommends \code{..}. 62 | This then can be applied between two time scale 63 | components to create a standard range between 64 | these dates (inclusive), e.g. \verb{2009-01-01..2019-01-01}. 65 | But it can also be used as an affix, 66 | indicating "on or before" if used as a prefix, 67 | e.g. \code{..2019-01-01}, 68 | or indicating "on or after" if used as a suffix, 69 | e.g. \verb{2009-01-01..}. 70 | 71 | And lastly, notation for sets of dates is also included. 72 | Here braces, \code{{}}, are used to mean "all members of the set", 73 | while brackets, \verb{[]}, are used to mean "one member of the set". 74 | } 75 | 76 | \seealso{ 77 | messydate 78 | } 79 | -------------------------------------------------------------------------------- /man/class_duration.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/class_duration.R 3 | \name{class_duration} 4 | \alias{class_duration} 5 | \alias{new_messyduration} 6 | \alias{messyduration} 7 | \alias{validate_messyduration} 8 | \alias{messyduration.character} 9 | \alias{messyduration.mdate} 10 | \title{A duration class for mdates} 11 | \usage{ 12 | new_messyduration(x = character()) 13 | 14 | messyduration(x, approx_range = 0) 15 | 16 | validate_messyduration(x, approx_range = 0) 17 | 18 | \method{messyduration}{character}(x, approx_range = 0) 19 | 20 | \method{messyduration}{mdate}(x, approx_range = 0) 21 | } 22 | \arguments{ 23 | \item{x}{An \code{mdate} variable with ranges.} 24 | 25 | \item{approx_range}{Range to expand approximate dates, in days. 26 | If 3, for example, adds 3 days; if -3, removes 3 days from both sides.} 27 | } 28 | \value{ 29 | Object of class \code{description} 30 | } 31 | \description{ 32 | The \code{mdates_duration} class introduces methods that annotate a duration or 33 | period with representations of its uncertainty. 34 | } 35 | \details{ 36 | Most R packages handle duration and periods as exact time or date intervals. 37 | However, this is not possible for 'messy' dates where uncertainty or 38 | approximation might be present. 39 | The \code{mdates_duration} class accounts for uncertainty and approximation 40 | in \code{mdate} objects to return their duration as a range of possible dates. 41 | } 42 | \examples{ 43 | messyduration(as_messydate(c("2010-01-01..2010-12-31", "2010-01..2010-12"))) 44 | } 45 | -------------------------------------------------------------------------------- /man/class_make.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/class_mdate.R 3 | \name{class_make} 4 | \alias{class_make} 5 | \alias{make_messydate} 6 | \title{Composes \code{mdate} from multiple variables} 7 | \usage{ 8 | make_messydate(..., resequence = FALSE) 9 | } 10 | \arguments{ 11 | \item{...}{One (yyyy-mm-dd), two (yyyy-mm-dd, yyyy-mm-dd), 12 | or three (yyyy, mm, dd) variables.} 13 | 14 | \item{resequence}{Users have the option to choose the order for 15 | ambiguous dates with or without separators (e.g. "11-01-12" or "20112112"). 16 | \code{NULL} by default. 17 | Other options include: 'dmy', 'ymd', 'mdy', 'ym', 'my' and 'interactive' 18 | If 'dmy', dates are converted from DDMMYY format for 6 digit dates, 19 | or DDMMYYYY format for 8 digit dates. 20 | If 'ymd', dates are converted from YYMMDD format for 6 digit dates, 21 | or YYYYMMDD format for 8 digit dates. 22 | If 'mdy', dates are converted from MMDDYY format for 6 digit dates 23 | or MMDDYYYY format for 8 digit dates. 24 | For these three options, ambiguous dates are converted to YY-MM-DD format 25 | for 6 digit dates, or YYYY-MM-DD format for 8 digit dates. 26 | If 'my', ambiguous 6 digit dates are converted from MM-YYYY format 27 | to YYYY-MM. 28 | If 'ym', ambiguous 6 digit dates are converted to YYYY-MM format. 29 | If 'interactive', it prompts users to select the existing 30 | component order of ambiguous dates, 31 | based on which the date is reordered into YYYY-MM-DD format 32 | and further completed to YYYY-MM-DD format if they choose to do so.} 33 | } 34 | \description{ 35 | Composes \code{mdate} from multiple variables 36 | } 37 | \details{ 38 | If three date variables are passed to \code{make_messydate()}, 39 | function will create a single date (yyyy-mm-dd) from it. 40 | If two date variables are passed to \code{make_messydate()}, 41 | function will create a range of dates from it (yyyy-mm-dd..yyyy-mm-dd). 42 | If one date variable is passed to \code{make_messydate()}, 43 | function defaults to \code{as_messydate()}. 44 | } 45 | \examples{ 46 | make_messydate("2010", "10", "10") 47 | } 48 | -------------------------------------------------------------------------------- /man/coerce_extrema.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/coerce_extrema.R 3 | \name{coerce_extrema} 4 | \alias{coerce_extrema} 5 | \alias{vmin} 6 | \alias{vmin.mdate} 7 | \alias{min.mdate} 8 | \alias{vmax} 9 | \alias{vmax.mdate} 10 | \alias{max.mdate} 11 | \title{Resolves messy dates into an extrema} 12 | \usage{ 13 | vmin(..., na.rm = FALSE) 14 | 15 | \method{vmin}{mdate}(..., na.rm = TRUE) 16 | 17 | \method{min}{mdate}(..., na.rm = TRUE) 18 | 19 | vmax(..., na.rm = FALSE) 20 | 21 | \method{vmax}{mdate}(..., na.rm = TRUE) 22 | 23 | \method{max}{mdate}(..., na.rm = TRUE) 24 | } 25 | \arguments{ 26 | \item{...}{a mdate object} 27 | 28 | \item{na.rm}{Should NAs be removed? True by default.} 29 | } 30 | \value{ 31 | A single scalar or vector of dates 32 | } 33 | \description{ 34 | This collection of S3 methods 'resolve' messy dates into a single date 35 | according to some explicit bias, 36 | such as returning the minimum or maximum date, 37 | the mean, median, or modal date, 38 | or a random date from among the possible resolutions for each messy date. 39 | If the date is not 'messy' (i.e. has no annotations) 40 | then just that precise date is returned. 41 | This can be useful for various descriptive or inferential projects. 42 | } 43 | \examples{ 44 | d <- as_messydate(c("2008-03-25", "?2012-02-27", "2001-01?", "2001~", 45 | "2001-01-01..2001-02-02", "{2001-01-01,2001-02-02}", 46 | "{2001-01,2001-02-02}", "2008-XX-31", "-0050-01-01")) 47 | d 48 | vmin(d) 49 | min(d) 50 | vmax(d) 51 | max(d) 52 | } 53 | -------------------------------------------------------------------------------- /man/coerce_from.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/coerce_from_messydate.R 3 | \name{coerce_from} 4 | \alias{coerce_from} 5 | \alias{as.Date.mdate} 6 | \alias{as.POSIXct.mdate} 7 | \alias{as.POSIXlt.mdate} 8 | \title{Coercion from messy dates} 9 | \usage{ 10 | \method{as.Date}{mdate}(x, FUN = vmin, ...) 11 | 12 | \method{as.POSIXct}{mdate}(x, tz = "UTC", FUN = vmin, ...) 13 | 14 | \method{as.POSIXlt}{mdate}(x, tz = "UTC", FUN = vmin, ...) 15 | } 16 | \arguments{ 17 | \item{x}{A \code{mdate} object} 18 | 19 | \item{FUN}{A function that can be used to resolve expanded messy dates 20 | into a single date. 21 | For example, \code{min()}, \code{max()}, \code{mean()}, \code{median()}, 22 | \code{modal()}, and \code{random()}.} 23 | 24 | \item{...}{Arguments passed on to the S3 generics.} 25 | 26 | \item{tz}{Character string specifying the time zone for the conversion, 27 | if required. 28 | By default "UTC" (Universal Time Coordinated), equivalent to GMT. 29 | If "" then the current time zone is used.} 30 | } 31 | \value{ 32 | A date object of \code{Date}, \code{POSIXct}, or \code{POSIXlt} class 33 | } 34 | \description{ 35 | These functions coerce objects of \code{mdate} class to 36 | common date classes such as \code{Date}, \code{POSIXct}, and \code{POSIXlt}. 37 | Since \code{mdate} objects can hold multiple individual dates, 38 | however, an additional function must be passed as an argument 39 | so that these functions know how to coerce resolve multiple dates 40 | into a single date. 41 | 42 | For example, one might wish to use the earliest possible date 43 | in any ranges of dates (\code{min}), the latest possible date (\code{max}), 44 | some notion of a central tendency (\code{mean}, \code{median}, or \code{modal}), 45 | or even a \code{random} selection from among the candidate dates. 46 | 47 | These functions then, building on \code{expand()} and the resolve functions, 48 | are particularly useful in converting back out of the \code{mdate} class 49 | for use with existing methods and models, 50 | especially for checking the robustness of results. 51 | } 52 | \examples{ 53 | as.Date(as_messydate("2012-01"), FUN = vmin) 54 | as.Date(as_messydate("2012-01-01"), FUN = vmean) 55 | as.Date(as_messydate("2012-01"), FUN = vmax) 56 | as.Date(as_messydate("2012-01"), FUN = vmedian) 57 | as.Date(as_messydate("2012-01"), FUN = vmodal) 58 | as.Date(as_messydate("2012-01"), FUN = vrandom) 59 | as.Date(as_messydate("1000 BC"), FUN = vmax) 60 | as.Date(as_messydate("1000 BC"), FUN = vmedian) 61 | as.Date(as_messydate(c("-1000", "2020")), FUN = vmin) 62 | } 63 | -------------------------------------------------------------------------------- /man/coerce_tendency.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/coerce_tendency.R 3 | \name{coerce_tendency} 4 | \alias{coerce_tendency} 5 | \alias{median.mdate} 6 | \alias{vmedian} 7 | \alias{vmedian.mdate} 8 | \alias{mean.mdate} 9 | \alias{vmean} 10 | \alias{vmean.mdate} 11 | \alias{modal} 12 | \alias{modal.mdate} 13 | \alias{vmodal} 14 | \alias{vmodal.mdate} 15 | \alias{random} 16 | \alias{random.mdate} 17 | \alias{vrandom} 18 | \alias{vrandom.mdate} 19 | \title{Resolves messy dates into a central tendency} 20 | \usage{ 21 | \method{median}{mdate}(..., na.rm = TRUE) 22 | 23 | vmedian(..., na.rm = TRUE) 24 | 25 | \method{vmedian}{mdate}(..., na.rm = TRUE) 26 | 27 | \method{mean}{mdate}(..., trim = 0, na.rm = TRUE) 28 | 29 | vmean(..., na.rm = TRUE) 30 | 31 | \method{vmean}{mdate}(..., trim = 0, na.rm = TRUE) 32 | 33 | modal(..., na.rm = TRUE) 34 | 35 | \method{modal}{mdate}(..., na.rm = TRUE) 36 | 37 | vmodal(..., na.rm = TRUE) 38 | 39 | \method{vmodal}{mdate}(..., na.rm = TRUE) 40 | 41 | random(..., na.rm = TRUE) 42 | 43 | \method{random}{mdate}(..., na.rm = TRUE) 44 | 45 | vrandom(..., na.rm = TRUE) 46 | 47 | \method{vrandom}{mdate}(..., na.rm = TRUE) 48 | } 49 | \arguments{ 50 | \item{...}{a mdate object} 51 | 52 | \item{na.rm}{Should NAs be removed? True by default.} 53 | 54 | \item{trim}{the fraction (0 to 0.5) of observations to be trimmed 55 | from each end of x before the mean is computed. 56 | Values of trim outside that range are taken as the nearest endpoint.} 57 | } 58 | \description{ 59 | These functions resolve messydates by their central tendency. 60 | While the functions \code{mean()}, \code{median()}, and \code{modal()} summarise the 61 | vector to a single value, \verb{v*()} versions return a vector of the same length. 62 | } 63 | \examples{ 64 | d <- as_messydate(c("2008-03-25", "?2012-02-27", "2001-01?", "2001~", 65 | "2001-01-01..2001-02-02", "{2001-01-01,2001-02-02}", 66 | "{2001-01,2001-02-02}", "2008-XX-31", "-0050-01-01")) 67 | d 68 | median(d) 69 | vmedian(d) 70 | mean(d) 71 | vmean(d) 72 | modal(d) 73 | vmodal(d) 74 | random(d) 75 | vrandom(d) 76 | } 77 | -------------------------------------------------------------------------------- /man/coerce_to.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/coerce_to_messydate.R 3 | \name{coerce_to} 4 | \alias{coerce_to} 5 | \alias{as_messydate} 6 | \alias{as_messydate.Date} 7 | \alias{as_messydate.POSIXct} 8 | \alias{as_messydate.POSIXlt} 9 | \alias{as_messydate.character} 10 | \alias{as_messydate.numeric} 11 | \alias{as_messydate.list} 12 | \alias{mdate} 13 | \title{Coercion from regular date classes to mdate} 14 | \usage{ 15 | as_messydate(x, resequence = FALSE) 16 | 17 | \method{as_messydate}{Date}(x, resequence = FALSE) 18 | 19 | \method{as_messydate}{POSIXct}(x, resequence = FALSE) 20 | 21 | \method{as_messydate}{POSIXlt}(x, resequence = FALSE) 22 | 23 | \method{as_messydate}{character}(x, resequence = NULL) 24 | 25 | \method{as_messydate}{numeric}(x, resequence = NULL) 26 | 27 | \method{as_messydate}{list}(x, resequence = FALSE) 28 | 29 | mdate(x, resequence = FALSE) 30 | } 31 | \arguments{ 32 | \item{x}{A scalar or vector of a class that can be coerced into \code{mdate}, 33 | such as \code{Date}, \code{POSIXct}, \code{POSIXlt}, or character.} 34 | 35 | \item{resequence}{Users have the option to choose the order for 36 | ambiguous dates with or without separators (e.g. "11-01-12" or "20112112"). 37 | \code{NULL} by default. 38 | Other options include: 'dmy', 'ymd', 'mdy', 'ym', 'my' and 'interactive' 39 | If 'dmy', dates are converted from DDMMYY format for 6 digit dates, 40 | or DDMMYYYY format for 8 digit dates. 41 | If 'ymd', dates are converted from YYMMDD format for 6 digit dates, 42 | or YYYYMMDD format for 8 digit dates. 43 | If 'mdy', dates are converted from MMDDYY format for 6 digit dates 44 | or MMDDYYYY format for 8 digit dates. 45 | For these three options, ambiguous dates are converted to YY-MM-DD format 46 | for 6 digit dates, or YYYY-MM-DD format for 8 digit dates. 47 | If 'my', ambiguous 6 digit dates are converted from MM-YYYY format 48 | to YYYY-MM. 49 | If 'ym', ambiguous 6 digit dates are converted to YYYY-MM format. 50 | If 'interactive', it prompts users to select the existing 51 | component order of ambiguous dates, 52 | based on which the date is reordered into YYYY-MM-DD format 53 | and further completed to YYYY-MM-DD format if they choose to do so.} 54 | } 55 | \value{ 56 | A \code{mdate} class object 57 | } 58 | \description{ 59 | These methods coerce various date classes into the \code{mdate} class. 60 | They represent the main user-facing class-creating functions in the package. 61 | In addition to the typical date classes in R (\code{Date}, \code{POSIXct}, and \code{POSIXlt}), 62 | there is also a direct method for converting text or character strings to \code{mdate}. 63 | The function can also extract dates from text, 64 | though this is a work-in-progress and currently only works in English. 65 | } 66 | \section{Functions}{ 67 | \itemize{ 68 | \item \code{as_messydate()}: Core \code{mdate} class coercion function 69 | 70 | \item \code{as_messydate(Date)}: Coerce from \code{Date} to \code{mdate} class 71 | 72 | \item \code{as_messydate(POSIXct)}: Coerce from \code{POSIXct} to \code{mdate} class 73 | 74 | \item \code{as_messydate(POSIXlt)}: Coerce from \code{POSIXlt} to \code{mdate} class 75 | 76 | \item \code{as_messydate(character)}: Coerce character date objects to \code{mdate} class 77 | 78 | \item \code{as_messydate(numeric)}: Coerce numeric objects to \code{mdate} class 79 | 80 | \item \code{as_messydate(list)}: Coerce list date objects to the most concise 81 | representation of \code{mdate} class 82 | 83 | }} 84 | \examples{ 85 | as_messydate("2021") 86 | as_messydate("2021-02") 87 | as_messydate("2021-02-01") 88 | as_messydate("01-02-2021") 89 | as_messydate("1 February 2021") 90 | as_messydate("First of February, two thousand and twenty-one") 91 | as_messydate("2021-02-01?") 92 | as_messydate("2021-02-01~") 93 | as_messydate("2021-02-01\%") 94 | as_messydate("2021-02-01..2021-02-28") 95 | as_messydate("{2021-02-01,2021-02-28}") 96 | as_messydate(c("-2021", "2021 BC", "-2021-02-01")) 97 | as_messydate(c("210201", "20210201"), resequence = "ymd") 98 | as_messydate(c("010221", "01022021"), resequence = "dmy") 99 | # as_messydate(c("01-02-21", "01-02-2021", "01-02-91", "01-02-1991"), 100 | # resequence = "interactive") 101 | as_messydate(list(c("2012-06-01", "2012-06-02", "2012-06-03"))) 102 | as_messydate(list(c("2012-06-01", "2012-06-02", "2012-06-03", 103 | "{2012-06-01, 2012-06-02, 2012-06-03}", "2012-06-01", "2012-06-03"))) 104 | } 105 | -------------------------------------------------------------------------------- /man/component_annotate.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/component_annotate.R 3 | \name{component_annotate} 4 | \alias{component_annotate} 5 | \alias{on_or_before} 6 | \alias{on_or_after} 7 | \alias{as_approximate} 8 | \alias{as_uncertain} 9 | \title{Annotates dates as censored, uncertain, or approximate} 10 | \usage{ 11 | on_or_before(x) 12 | 13 | on_or_after(x) 14 | 15 | as_approximate(x, component = NULL) 16 | 17 | as_uncertain(x, component = NULL) 18 | } 19 | \arguments{ 20 | \item{x}{A date vector} 21 | 22 | \item{component}{Annotation can be added on specific date components 23 | ("year", "month" or "day"), or to groups of date components (month and 24 | day ("md"), or year and month ("ym")). This must be specified. 25 | If unspecified, annotation will be added after the date (e.g. \verb{1916-10-10?}), 26 | indicating the whole date is uncertain or approximate. 27 | For specific date components, uncertainty or approximation is annotated to 28 | the left of the date component. 29 | E.g. for "day": \code{1916-10-?10} or \code{1916-10-~10}. 30 | For groups of date components, uncertainty or approximation is annotated to 31 | the right of the group ("ym") or to both components ("md"). 32 | E.g. for "ym": \code{1916-10~-10}; for "md": \code{1916-?10-?10}.} 33 | } 34 | \value{ 35 | A \code{mdate} object with annotated date(s) 36 | } 37 | \description{ 38 | Some datasets have for example an arbitrary cut off point 39 | for start and end points, but these are often coded as precise dates 40 | when they are not necessarily the real start or end dates. 41 | This collection of functions helps annotate uncertainty and 42 | approximation to dates according to ISO2019E standards. 43 | Inaccurate start or end dates can be represented by an affix 44 | indicating "on or before", if used as a prefix (e.g. \code{..1816-01-01}), 45 | or indicating "on or after", if used as a suffix (e.g. \verb{2016-12-31..}). 46 | Approximate dates are indicated by adding a tilde to year, 47 | month, or day components, as well as groups of components or whole dates 48 | to estimate values that are possibly correct (e.g. \verb{2003-03-03~}). 49 | Day, month, or year, uncertainty can be indicated by adding a question mark 50 | to a possibly dubious date (e.g. \verb{1916-10-10?}) or date 51 | component (e.g. \code{1916-?10-10}). 52 | } 53 | \section{Functions}{ 54 | \itemize{ 55 | \item \code{on_or_before()}: prefixes dates with ".." where start date is uncertain 56 | 57 | \item \code{on_or_after()}: suffixes dates with ".." where end date is uncertain 58 | 59 | \item \code{as_approximate()}: adds tildes to indicate approximate dates/date components 60 | 61 | \item \code{as_uncertain()}: adds question marks to indicate dubious dates/date components. 62 | 63 | }} 64 | \examples{ 65 | data <- data.frame(Beg = c("1816-01-01", "1916-01-01", "2016-01-01"), 66 | End = c("1816-12-31", "1916-12-31", "2016-12-31")) 67 | dplyr::mutate(data, Beg = ifelse(Beg <= "1816-01-01", 68 | on_or_before(Beg), Beg)) 69 | dplyr::mutate(data, End = ifelse(End >= "2016-01-01", 70 | on_or_after(End), End)) 71 | dplyr::mutate(data, Beg = ifelse(Beg == "1916-01-01", 72 | as_approximate(Beg), Beg)) 73 | dplyr::mutate(data, End = ifelse(End == "1916-12-31", 74 | as_uncertain(End), End)) 75 | } 76 | -------------------------------------------------------------------------------- /man/component_extract.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/component_extract.R 3 | \name{component_extract} 4 | \alias{component_extract} 5 | \alias{year} 6 | \alias{month} 7 | \alias{day} 8 | \alias{precision} 9 | \alias{precision.mdate} 10 | \title{Extracting components from messy dates} 11 | \usage{ 12 | year(x) 13 | 14 | month(x) 15 | 16 | day(x) 17 | 18 | precision(x) 19 | 20 | \method{precision}{mdate}(x) 21 | } 22 | \arguments{ 23 | \item{x}{A \code{mdate} object} 24 | } 25 | \value{ 26 | \code{year()}, \code{month()}, and \code{day()} extraction return the integer 27 | for the requested date component. 28 | \code{precision()} returns the level of greatest precision for each date. 29 | } 30 | \description{ 31 | These functions allow the extraction of particular date components 32 | from messy dates, such as the \code{year()}, \code{month()}, and \code{day()}. 33 | \code{precision()} allows for the identification of the greatest level of 34 | precision in (currently) the first element of each date. 35 | } 36 | \section{Precision}{ 37 | 38 | Date precision is measured relative to the day in \eqn{1/days(x)}. 39 | That is, a date measured to the day will return a precision score 40 | of 1, a date measured to the month will return a precision score of 41 | between \eqn{1/28} and \eqn{1/31}, and annual measures will have 42 | a precision of between \eqn{1/365} and \eqn{1/366}. 43 | } 44 | 45 | \examples{ 46 | year(as_messydate(c("2012-02-03","2012","2012-02"))) 47 | month(as_messydate(c("2012-02-03","2012","2012-02"))) 48 | day(as_messydate(c("2012-02-03","2012","2012-02"))) 49 | precision(as_messydate(c("2012-02-03","2012","2012-02"))) 50 | } 51 | -------------------------------------------------------------------------------- /man/convert_contract.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/convert_contract.R 3 | \name{convert_contract} 4 | \alias{convert_contract} 5 | \alias{contract} 6 | \title{Contract lists of dates into messy dates} 7 | \usage{ 8 | contract(x, collapse = TRUE) 9 | } 10 | \arguments{ 11 | \item{x}{A list of dates} 12 | 13 | \item{collapse}{Do you want ranges to be collapsed? 14 | TRUE by default. 15 | If FALSE ranges are returned in compact format.} 16 | } 17 | \value{ 18 | A \code{mdate} vector 19 | } 20 | \description{ 21 | This function operates as the opposite of \code{expand()}. 22 | It contracts a list of dates into the abbreviated annotation 23 | of messy dates. 24 | } 25 | \details{ 26 | The ´contract()´ function first \code{expand()} 'mdate' objects 27 | to then display their most succinct representation. 28 | } 29 | \examples{ 30 | d <- as_messydate(c("2001-01-01", "2001-01", "2001", 31 | "2001-01-01..2001-02-02", "{2001-10-01,2001-10-04}", 32 | "{2001-01,2001-02-02}", "28 BC", "-2000-01-01", 33 | "{2001-01-01, 2001-01-02, 2001-01-03}")) 34 | dplyr::tibble(d, contract(d)) 35 | } 36 | -------------------------------------------------------------------------------- /man/convert_expand.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/convert_expand.R 3 | \name{convert_expand} 4 | \alias{convert_expand} 5 | \alias{expand} 6 | \title{Expand messy dates to lists of dates} 7 | \usage{ 8 | expand(x, approx_range = 0) 9 | } 10 | \arguments{ 11 | \item{x}{A \code{mdate} object. 12 | If not an 'mdate' object, conversion is handled first with ´as_messydate()´.} 13 | 14 | \item{approx_range}{Range to expand approximate dates, 15 | or date components, annotated with '~', by default 0. 16 | That is, removes signs for approximate dates and 17 | treats these dates as precise dates. 18 | If 3, for example, adds 3 days for day approximation, 19 | 3 months for month approximation, 20 | 3 years for year/whole date approximation, 21 | 3 years and 3 months for year-month approximation, 22 | and 3 months and 3 days for month-day approximation.} 23 | } 24 | \value{ 25 | A list of dates, including all dates in each range or set. 26 | } 27 | \description{ 28 | These functions expand on date ranges, sets of dates, and unspecified or 29 | approximate dates (annotated with '..', '{}', 'XX' or '~'). 30 | As these messydates may refer to several possible dates, 31 | the function "opens" these values to reveal a vector of all the possible 32 | dates implied. 33 | Imprecise dates (dates only containing information on year and/or month) 34 | are also expanded to include possible dates within that year and/or month. 35 | The function removes the annotation from dates with unreliable sources ('?'), 36 | before being expanded normally as though they were incomplete. 37 | } 38 | \examples{ 39 | d <- as_messydate(c("2008-03-25", "-2012-02-27", "2001-01?", "~2001", 40 | "2001-01-01..2001-02-02", "{2001-01-01,2001-02-02}", "{2001-01,2001-02-02}", 41 | "2008-XX-31", "..2002-02-03", "2001-01-03..", "28 BC")) 42 | expand(d) 43 | } 44 | -------------------------------------------------------------------------------- /man/convert_sequence.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/convert_sequence.R 3 | \name{convert_sequence} 4 | \alias{convert_sequence} 5 | \alias{seq.mdate} 6 | \title{Sequence method for messydates} 7 | \usage{ 8 | \method{seq}{mdate}(from, to, by = "days", ...) 9 | } 10 | \arguments{ 11 | \item{from}{A messydate or range. 12 | If 'from' is a range and 'to' is not specified, 13 | 'from' will be the minimum of the range and 'to' will be maximum.} 14 | 15 | \item{to}{A messydate.} 16 | 17 | \item{by}{Increment of the sequence. By default "days".} 18 | 19 | \item{...}{Arguments passed to or from methods.} 20 | } 21 | \description{ 22 | This function provides a sequence (\code{seq()}) method for messydates. 23 | This can be used with ranges or unspecified dates, 24 | and is particularly useful for defining a sequence of dates 25 | before the common era or between eras. 26 | } 27 | \examples{ 28 | seq(mdate("-0001-12-20"), mdate("0001-01-10")) 29 | } 30 | -------------------------------------------------------------------------------- /man/defunct.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/messydates-defunct.R 3 | \name{defunct} 4 | \alias{defunct} 5 | \alias{is_element} 6 | \alias{md_intersect} 7 | \alias{md_union} 8 | \alias{md_multiset} 9 | \title{Functions that have been renamed, superseded, or are no longer working} 10 | \usage{ 11 | is_element(.data) 12 | 13 | md_intersect(.data) 14 | 15 | md_union(.data) 16 | 17 | md_multiset(.data) 18 | } 19 | \description{ 20 | \ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} 21 | Generally these functions have been superseded or renamed. 22 | Upon using them, a message is provided directing the user to the new function. 23 | However, at this stage of package development, 24 | we generally clear older defunct functions at each minor release, 25 | and so you are strongly encouraged to use the new functions/names/syntax 26 | wherever possible and update your scripts accordingly. 27 | } 28 | \section{Functions}{ 29 | \itemize{ 30 | \item \code{is_element()}: Deprecated on 2023-08-25. 31 | 32 | \item \code{md_intersect()}: Deprecated on 2023-08-25. 33 | 34 | \item \code{md_union()}: Deprecated on 2023-08-25. 35 | 36 | \item \code{md_multiset()}: Deprecated on 2023-08-25. 37 | 38 | }} 39 | \keyword{internal} 40 | -------------------------------------------------------------------------------- /man/figures/cheatsheet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/globalgov/messydates/851677d9a6bbb87be2a7fa373bdbb4e1dac45d50/man/figures/cheatsheet.png -------------------------------------------------------------------------------- /man/figures/messydates_hexlogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/globalgov/messydates/851677d9a6bbb87be2a7fa373bdbb4e1dac45d50/man/figures/messydates_hexlogo.png -------------------------------------------------------------------------------- /man/operate_arithmetic.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/operate_arithmetic.R 3 | \name{operate_arithmetic} 4 | \alias{operate_arithmetic} 5 | \alias{+.mdate} 6 | \alias{-.mdate} 7 | \title{Arithmetic operations for messydates} 8 | \usage{ 9 | \method{+}{mdate}(e1, e2) 10 | 11 | \method{-}{mdate}(e1, e2) 12 | } 13 | \arguments{ 14 | \item{e1}{An \code{mdate} or date object.} 15 | 16 | \item{e2}{An \code{mdate}, date, or numeric object. Must be a scalar.} 17 | } 18 | \value{ 19 | A messydates vector 20 | } 21 | \description{ 22 | These operations allow users to add or subtract dates messydate objects. 23 | Messydate objects include incomplete or uncertain dates, 24 | ranges of dates, negative dates, and date sets. 25 | } 26 | \examples{ 27 | \donttest{ 28 | d <- as_messydate(c("2008-03-25", "-2012-02-27", "2001-01?", "~2001", 29 | "2001-01-01..2001-02-02", "{2001-01-01,2001-02-02}", 30 | "2008-XX-31", "..2002-02-03", "2001-01-03..", "28 BC")) 31 | dplyr::tibble(date = d, add = d + 1, subtract = d - 1) 32 | dplyr::tibble(date = d, add = d + "1 year", subtract = d - "1 year") 33 | as_messydate("2001-01-01") + as_messydate("2001-01-02..2001-01-04") 34 | as_messydate("2001-01-01") + as_messydate("2001-01-03") 35 | as_messydate("2001-01-01..2001-01-04") - as_messydate("2001-01-02") 36 | #as_messydate("2001-01-01") - as_messydate("2001-01-03") 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /man/operate_inequalities.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/operate_inequalities.R 3 | \name{operate_inequalities} 4 | \alias{operate_inequalities} 5 | \alias{<.mdate} 6 | \alias{>.mdate} 7 | \alias{<=.mdate} 8 | \alias{>=.mdate} 9 | \title{Logical operations on messy dates} 10 | \usage{ 11 | \method{<}{mdate}(e1, e2) 12 | 13 | \method{>}{mdate}(e1, e2) 14 | 15 | \method{<=}{mdate}(e1, e2) 16 | 17 | \method{>=}{mdate}(e1, e2) 18 | } 19 | \arguments{ 20 | \item{e1, e2}{\code{mdate} or other class objects} 21 | } 22 | \description{ 23 | Logical operations on messy dates 24 | } 25 | \section{Functions}{ 26 | \itemize{ 27 | \item \code{ < }: tests whether the dates in the first vector precede 28 | the dates in the second vector. 29 | Returns \code{NA} when the date order can't be determined. 30 | 31 | \item \code{ > }: tests whether the dates in the first vector 32 | succeed the dates in the second vector. 33 | Returns \code{NA} when the date order can't be determined. 34 | 35 | \item \code{ <= }: tests whether the dates in the first vector are 36 | equal to or precede the dates in the second vector. 37 | Returns \code{NA} when the date order can't be determined. 38 | 39 | \item \code{ >= }: tests whether the dates in the first vector are equal to 40 | or succeed the dates in the second vector. 41 | Returns \code{NA} when the date order can't be determined. 42 | 43 | }} 44 | \examples{ 45 | as_messydate("2012-06-02") > as.Date("2012-06-01") # TRUE 46 | # 2012-06-XX could mean 2012-06-03, so unknown if it comes before 2012-06-02 47 | as_messydate("2012-06-XX") < as.Date("2012-06-02") # NA 48 | # But 2012-06-XX cannot be before 2012-06-01 49 | as_messydate("2012-06-XX") >= as.Date("2012-06-01") # TRUE 50 | } 51 | -------------------------------------------------------------------------------- /man/operate_proportional.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/operate_proportional.R 3 | \name{operate_proportional} 4 | \alias{operate_proportional} 5 | \alias{\%l\%} 6 | \alias{\%l\%.mdate} 7 | \alias{\%g\%} 8 | \alias{\%g\%.mdate} 9 | \alias{\%ge\%} 10 | \alias{\%ge\%.mdate} 11 | \alias{\%le\%} 12 | \alias{\%le\%.mdate} 13 | \alias{\%><\%} 14 | \alias{\%><\%.mdate} 15 | \alias{\%>=<\%} 16 | \alias{\%>=<\%.mdate} 17 | \title{Proportion of messy dates meeting logical test} 18 | \usage{ 19 | e1 \%l\% e2 20 | 21 | \method{\%l\%}{mdate}(e1, e2) 22 | 23 | e1 \%g\% e2 24 | 25 | \method{\%g\%}{mdate}(e1, e2) 26 | 27 | e1 \%ge\% e2 28 | 29 | \method{\%ge\%}{mdate}(e1, e2) 30 | 31 | e1 \%le\% e2 32 | 33 | \method{\%le\%}{mdate}(e1, e2) 34 | 35 | e1 \%><\% e2 36 | 37 | \method{\%><\%}{mdate}(e1, e2) 38 | 39 | e1 \%>=<\% e2 40 | 41 | \method{\%>=<\%}{mdate}(e1, e2) 42 | } 43 | \arguments{ 44 | \item{e1, e2}{\code{mdate} or other class objects} 45 | } 46 | \value{ 47 | The proportion that the comparison is true. 48 | 49 | A logical vector the same length as the \code{mdate} passed. 50 | } 51 | \description{ 52 | These functions provide various proportional tests for messy date objects. 53 | } 54 | \section{Functions}{ 55 | \itemize{ 56 | \item \code{ \%l\% }: Tests proportion of dates in the first vector 57 | that precede the minimum in the second vector. 58 | 59 | \item \code{ \%g\% }: Tests proportion of dates in the first vector 60 | that follow the maximum in the second vector. 61 | 62 | \item \code{ \%ge\% }: Tests proportion of dates in the first vector 63 | that follow or are equal to the maximum in the second vector. 64 | 65 | \item \code{ \%le\% }: Tests proportion of dates in the first vector 66 | that precede or are equal to the minimum in the second vector. 67 | 68 | \item \code{ \%><\% }: Tests proportion of dates in the first vector 69 | that are between the minimum and maximum dates in the second vector. 70 | 71 | \item \code{ \%>=<\% }: Tests proportion of dates in the first vector that 72 | are between the minimum and maximum dates in the second vector, inclusive. 73 | 74 | }} 75 | \examples{ 76 | as_messydate("2012-06") < as.Date("2012-06-02") 77 | as_messydate("2012-06") \%l\% as_messydate("2012-06-02") 78 | as_messydate("2012-06") > as.Date("2012-06-02") 79 | as_messydate("2012-06") \%g\% as_messydate("2012-06-02") 80 | as_messydate("2012-06") >= as.Date("2012-06-02") 81 | as_messydate("2012-06") \%ge\% as_messydate("2012-06-02") 82 | as_messydate("2012-06") <= as.Date("2012-06-02") 83 | as_messydate("2012-06") \%le\% "2012-06-02" 84 | as_messydate("2012-06") \%><\% as_messydate("2012-06-15..2012-07-15") 85 | as_messydate("2012-06") \%>=<\% as_messydate("2012-06-15..2012-07-15") 86 | } 87 | -------------------------------------------------------------------------------- /man/operate_set.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/operate_set.R 3 | \name{operate_set} 4 | \alias{operate_set} 5 | \alias{\%intersect\%} 6 | \alias{\%intersect\%.mdate} 7 | \alias{\%union\%} 8 | \alias{\%union\%.mdate} 9 | \title{Set operations for messy dates} 10 | \usage{ 11 | e1 \%intersect\% e2 12 | 13 | \method{\%intersect\%}{mdate}(e1, e2) 14 | 15 | e1 \%union\% e2 16 | 17 | \method{\%union\%}{mdate}(e1, e2) 18 | } 19 | \arguments{ 20 | \item{e1, e2}{Messy date or other class objects} 21 | } 22 | \value{ 23 | A vector of the same mode for \code{intersect}, 24 | or a common mode for union. 25 | } 26 | \description{ 27 | Performs intersection (\code{md_intersect()}) and union (\code{md_union()}) on, 28 | inter alia, messy date class objects. 29 | For a more typical 'join' that retains all elements, even if duplicated, 30 | please use \code{md_multiset}. 31 | } 32 | \section{Functions}{ 33 | \itemize{ 34 | \item \code{ \%intersect\% }: Find intersection of sets of messy dates 35 | 36 | \item \code{ \%union\% }: Find intersection of sets of messy dates 37 | 38 | }} 39 | \examples{ 40 | as_messydate("2012-01-01..2012-01-20") \%intersect\% as_messydate("2012-01") 41 | as_messydate("2012-01-01..2012-01-20") \%union\% as_messydate("2012-01") 42 | } 43 | -------------------------------------------------------------------------------- /man/operate_statements.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/operate_statements.R 3 | \name{operate_statements} 4 | \alias{operate_statements} 5 | \alias{is_messydate} 6 | \alias{is_intersecting} 7 | \alias{is_subset} 8 | \alias{is_similar} 9 | \alias{is_precise} 10 | \alias{is_uncertain} 11 | \alias{is_approximate} 12 | \alias{is_bce} 13 | \title{Logical statements on messy dates} 14 | \usage{ 15 | is_messydate(x) 16 | 17 | is_intersecting(x, y) 18 | 19 | is_subset(x, y) 20 | 21 | is_similar(x, y) 22 | 23 | is_precise(x) 24 | 25 | is_uncertain(x) 26 | 27 | is_approximate(x) 28 | 29 | is_bce(x) 30 | } 31 | \arguments{ 32 | \item{x, y}{\code{mdate} or other class objects} 33 | } 34 | \value{ 35 | A logical vector the same length as the \code{mdate} passed. 36 | } 37 | \description{ 38 | These functions provide various logical statements about messy date objects. 39 | } 40 | \section{Functions}{ 41 | \itemize{ 42 | \item \code{is_messydate()}: tests whether the object inherits the \code{mdate} class. 43 | If more rigorous validation is required, see \code{validate_messydate()}. 44 | 45 | \item \code{is_intersecting()}: tests whether there is any intersection between 46 | two messy dates, leveraging \code{intersect()}. 47 | 48 | \item \code{is_subset()}: tests whether one or more messy date can be found 49 | within a messy date range or set. 50 | 51 | \item \code{is_similar()}: tests whether two dates contain similar components. 52 | This can be useful for identifying dates that may be typos of one another. 53 | 54 | \item \code{is_precise()}: tests whether a date is precise (i.e. an 8 digit date). 55 | Non-precise dates contain markers that they are approximate (i.e. ~), 56 | unreliable (i.e. ?), are incomplete dates (i.e. year only), 57 | or date ranges and sets. 58 | 59 | \item \code{is_uncertain()}: tests whether a date is uncertain (i.e. contains ?). 60 | 61 | \item \code{is_approximate()}: tests whether a date is approximate (i.e. contains ~). 62 | 63 | \item \code{is_bce()}: tests whether one or more messy dates are found 64 | before the common era. 65 | 66 | }} 67 | \examples{ 68 | is_messydate(as_messydate("2012-01-01")) 69 | is_messydate(as.Date("2012-01-01")) 70 | is_intersecting(as_messydate("2012-01"), 71 | as_messydate("2012-01-01..2012-02-22")) 72 | is_intersecting(as_messydate("2012-01"), 73 | as_messydate("2012-02-01..2012-02-22")) 74 | is_subset(as_messydate("2012-01-01"), as_messydate("2012-01")) 75 | is_subset(as_messydate("2012-01-01..2012-01-03"), as_messydate("2012-01")) 76 | is_subset(as_messydate("2012-01-01"), as_messydate("2012-02")) 77 | is_similar(as_messydate("2012-06-02"), as_messydate("2012-02-06")) 78 | is_similar(as_messydate("2012-06-22"), as_messydate("2012-02-06")) 79 | is_precise(as_messydate(c("2012-06-02", "2012-06"))) 80 | is_uncertain(as_messydate(c("2012-06-02", "2012-06-02?"))) 81 | is_approximate(as_messydate(c("2012-06-02~", "2012-06-02"))) 82 | is_bce(as_messydate(c("2012-06-02", "-2012-06-02"))) 83 | } 84 | -------------------------------------------------------------------------------- /pkgdown/_pkgdown.yml: -------------------------------------------------------------------------------- 1 | url: https://globalgov.github.io/messydates/ 2 | development: 3 | mode: auto 4 | template: 5 | bootstrap: 5 6 | params: 7 | bootswatch: journal 8 | authors: 9 | James Hollway: 10 | href: https://jameshollway.com 11 | navbar: 12 | structure: 13 | left: 14 | - home 15 | - intro 16 | - reference 17 | - articles 18 | - news 19 | right: 20 | - search 21 | - github 22 | components: 23 | home: 24 | icon: fa-home fa-lg 25 | href: index.html 26 | aria-label: Take me home 27 | reference: 28 | text: Reference 29 | href: reference/index.html 30 | news: 31 | text: News 32 | href: news/index.html 33 | github: 34 | icon: "fab fa-github fa-lg" 35 | href: https://github.com/globalgov/messydates 36 | aria-label: Take me to the Github repository 37 | reference: 38 | - title: "Coerce to" 39 | desc: "These functions construct and/or coerce dates to the `mdate` class:" 40 | contents: 41 | - starts_with("class_") 42 | - component_annotate 43 | - coerce_to 44 | - title: "Coerce from" 45 | desc: "These functions coerce dates from the `mdate` class into a single `Date`:" 46 | contents: 47 | - starts_with("as\\.") 48 | - coerce_extrema 49 | - coerce_tendency 50 | - title: "Manipulation" 51 | desc: "These functions expand or contract objects of `mdate` class from/into a list:" 52 | contents: 53 | - starts_with("convert_") 54 | - title: "Operations" 55 | desc: "These methods help operate on objects of the `mdate` class:" 56 | contents: 57 | - starts_with("operate_") 58 | - component_extract 59 | - title: "Data" 60 | desc: "Working with 'messy' data:" 61 | contents: 62 | - battles 63 | -------------------------------------------------------------------------------- /pkgdown/extra.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Lato&family=Ubuntu+Mono'); 2 | 3 | /* Color Palette 4 | messydates brown #C7A077, used for standard links 5 | Light brown #ECCAA5, used for hovered links 6 | Green Sheen #876949, Used for active things 7 | Dark White #f3f3f3, used for discrete active things, like the active dropdown 8 | menu link. 9 | */ 10 | 11 | /* Changes Bootstrap 5 theme switch */ 12 | p { 13 | font-size: 14px; 14 | font-family: 'Lato', sans-serif; 15 | } 16 | 17 | ul, ol { 18 | font-size: 14px; 19 | font-family: 'Lato', sans-serif; 20 | } 21 | 22 | h1 { 23 | font-size: 40px; 24 | } 25 | 26 | h2 { 27 | font-size: 30px; 28 | } 29 | 30 | h3 { 31 | font-size: 24px; 32 | } 33 | 34 | h4 { 35 | font-size: 18px; 36 | } 37 | 38 | /* Package name in the navbar*/ 39 | .navbar-light .navbar-brand { 40 | color: #C7A077; 41 | } 42 | /* Navbar menu names*/ 43 | .navbar-light .navbar-nav .nav-link { 44 | color: #C7A077; 45 | } 46 | 47 | .version { 48 | font-size: 50%; 49 | } 50 | 51 | body{ 52 | font-family: 'Lato', sans-serif; 53 | font-size: 1.05rem; 54 | } 55 | 56 | dd { 57 | font-size: 14px; 58 | } 59 | 60 | /* Right column titles*/ 61 | aside .h2 { 62 | font-size: 1.275rem; 63 | } 64 | /* Right column ul*/ 65 | aside ul { 66 | font-size: 0.95rem; 67 | } 68 | /* End of changes Bootstrap 5 theme switch */ 69 | 70 | /* ---- particles.js container ---- */ 71 | #particles-js { 72 | position: absolute; 73 | width: 100%; 74 | height: 100%; 75 | background-color: #b61924; 76 | background-image: url(""); 77 | background-repeat: no-repeat; 78 | background-size: cover; 79 | background-position: 50% 50%; 80 | } 81 | 82 | /*The code itself inline*/ 83 | p code { 84 | font-family: 'Ubuntu Mono', monospace; 85 | font-size: 14px; 86 | background-color: #f9f6fd; 87 | } 88 | 89 | pre code { 90 | font-size: 13px; 91 | color: #f85e00; 92 | } 93 | 94 | /*Links in code and header*/ 95 | pre code a:any-link { 96 | color: #C7A077; 97 | } 98 | 99 | pre code span.fu { 100 | color: #956e45; 101 | } 102 | 103 | #toc>.nav a.nav-link:hover, #toc>.nav a.nav-link:focus { 104 | background-color: #ECCAA5; 105 | color: #000; 106 | } 107 | 108 | #toc>.nav a.nav-link.active { 109 | background-color: #C7A077; 110 | color: #fff; 111 | } 112 | 113 | .navbar-light .navbar-nav .active>.nav-link { 114 | background-color: #C7A077; 115 | color: #fff; 116 | } 117 | 118 | .docs-link:hover{ 119 | text-decoration: none; 120 | } 121 | /*What is this thing?*/ 122 | .docs-link{ 123 | color: #C7A077; 124 | } 125 | /*Links in General*/ 126 | a { 127 | color: #C7A077; 128 | } 129 | 130 | .panel-body>a { 131 | color: #C7A077; 132 | } 133 | 134 | h4>a { 135 | color: #C7A077; 136 | } 137 | 138 | li>a { 139 | color: #C7A077; 140 | } 141 | /* Link in the dropdown menu if active*/ 142 | .dropdown-menu > .active > a, 143 | .dropdown-menu>.active>a:focus { 144 | background-color: #C7A077; 145 | } 146 | .dropdown-menu>.active>a:hover{ 147 | background-color: #876949; 148 | } 149 | /* Little arrow in the dropdown menu*/ 150 | a .caret{ 151 | color: #D3D3D3 !important; 152 | } 153 | 154 | small>a{ 155 | color: #C7A077; 156 | } 157 | 158 | .sourceCode>a{ 159 | color: #C7A077; 160 | } 161 | 162 | .fl{ 163 | color: #C7A077; 164 | } 165 | /*Strings in code*/ 166 | .st{ 167 | color: green; 168 | } 169 | 170 | .op, .kw{ 171 | color: #666666; 172 | } 173 | 174 | .dv, .dt{ 175 | color: #191919; 176 | } 177 | 178 | .g2r { 179 | box-shadow: 180 | rgba(0, 0, 0, 0.1) 0 2px 3px 1px, 181 | rgba(0, 0, 0, 0.1) 0 1px 3px 1px, 182 | rgba(0, 0, 0, 0.2) 0 1px 1px -1px; 183 | border-radius: 5px; 184 | padding: 10px; 185 | width: 100% !important; 186 | background-color: white; 187 | } 188 | 189 | .jumbotron { 190 | background-color: #fff; 191 | box-shadow: 192 | rgba(0, 0, 0, 0.1) 0 2px 3px 1px, 193 | rgba(0, 0, 0, 0.1) 0 1px 3px 1px, 194 | rgba(0, 0, 0, 0.2) 0 1px 1px -1px; 195 | } 196 | 197 | pre { 198 | -moz-box-shadow: inset 0 0 2px rgba(0, 0, 0, 0.1); 199 | -webkit-box-shadow: inset 0 0 2px rgba(0, 0, 0, 0.1); 200 | box-shadow: inset 0 0 2px rgba(0, 0, 0, 0.1); 201 | } 202 | /*Hover color of the package name in nav bar*/ 203 | .navbar-default .navbar-link:hover { 204 | color: #ECCAA5; 205 | } 206 | 207 | .navbar-default .navbar-nav > .active > a, 208 | .navbar-default .navbar-nav > .active > a:hover, 209 | .navbar-default .navbar-nav > .active > a:focus { 210 | color: #f3f3f3; 211 | background-color: #C7A077; 212 | } 213 | 214 | .navbar-dark .navbar-nav .nav-item>.nav-link:hover, 215 | .navbar-light .navbar-nav .nav-item>.nav-link:hover { 216 | background-color: #FFECD9; 217 | color: #000; 218 | } 219 | 220 | .dropdown-item:hover { 221 | background-color: #FFECD9; 222 | } 223 | 224 | .dropdown-item.active, .dropdown-item:active { 225 | color: black; 226 | background-color: #FFECD9; 227 | } 228 | 229 | input[type="search"] { 230 | border-color: #FFECD9; 231 | width: 12rem; 232 | height: 2.5rem; 233 | } 234 | 235 | .form-control:focus { 236 | color: #343a40; 237 | background-color: #fff; 238 | border-color: #fee8d0; 239 | outline: 0; 240 | box-shadow: 0 0 0 0.25rem rgb(254 232 208); 241 | } 242 | 243 | .navbar-default .navbar-nav > li > a:hover, 244 | .navbar-default .navbar-nav > li > a:focus { 245 | color: #876949; 246 | } 247 | /* Little arrow in the dropdown menu when active*/ 248 | .navbar-default .navbar-nav > .dropdown > a .caret { 249 | color: #D3D3D3; 250 | }/* Little arrow in the dropdown menu when active and hovered over*/ 251 | .navbar-default .navbar-nav > .dropdown > a:hover .caret, 252 | .navbar-default .navbar-nav > .dropdown > a:focus .caret { 253 | color: #011629; 254 | } 255 | 256 | .navbar-default .navbar-nav>.open>a, 257 | .navbar-default .navbar-nav>.open>a:hover, 258 | .navbar-default .navbar-nav>.open>a:focus{ 259 | color: white; 260 | background-color: #876949; 261 | } 262 | 263 | .navbar-default .navbar-nav .open .dropdown-menu>li>a:hover{ 264 | color: white; 265 | background-color: #876949; 266 | } 267 | .navbar-default .navbar-nav .open .dropdown-menu{ 268 | color: #876949; 269 | } 270 | 271 | .nav-pills>li.active>a, 272 | .nav-pills>li.active>a:hover, 273 | .nav-pills>li.active>a:focus { 274 | color: white; 275 | background-color: #C7A077; 276 | font-weight: bold; 277 | } 278 | 279 | .navbar-default .navbar-nav>li>a{ 280 | color: #C7A077; 281 | font-size: 14px; 282 | } 283 | /*Package name in the navbar*/ 284 | .navbar-default .navbar-link{ 285 | color: #C7A077; 286 | font-size: 30px; 287 | } 288 | 289 | div>a{ 290 | color: #C7A077; 291 | } 292 | /* General links when hovered over*/ 293 | a:hover, a:focus { 294 | color: #ECCAA5; 295 | } 296 | 297 | .panel{ 298 | min-height: 140px; 299 | } 300 | 301 | .btn-primary{ 302 | background-color: #C7A077; 303 | } 304 | 305 | .panel-orange > .panel-heading{ 306 | background-color: #F0760F; 307 | border-color: #F7BA87; 308 | color: white; 309 | font-weight: bold; 310 | font-size: 150%; 311 | text-align: center; 312 | } 313 | 314 | .panel-info > .panel-heading{ 315 | background-color: #6906FF; 316 | border-color: #ab74fc; 317 | font-weight: bold; 318 | font-size: 150%; 319 | text-align: center; 320 | } 321 | 322 | .panel-warning > .panel-heading{ 323 | font-weight: bold; 324 | font-size: 20px; 325 | text-decoration: underline; 326 | } 327 | 328 | .panel-warning{ 329 | background-color: #ff9800; 330 | color: white; 331 | } 332 | 333 | .panel-warning > .panel-body{ 334 | color: white; 335 | font-size: 15px; 336 | } 337 | 338 | .panel-danger > .panel-heading{ 339 | font-weight: bold; 340 | font-size: 20px; 341 | text-decoration: underline; 342 | } 343 | 344 | .panel-danger{ 345 | background-color: #e84f3f; 346 | color: white; 347 | min-height: 50px!important; 348 | } 349 | 350 | .panel-danger > .panel-body{ 351 | color: white; 352 | font-size: 18px; 353 | text-align:center; 354 | } 355 | 356 | .centerize{ 357 | text-align: center; 358 | } 359 | -------------------------------------------------------------------------------- /tests/testthat.R: -------------------------------------------------------------------------------- 1 | library(testthat) 2 | library(messydates) 3 | 4 | test_check("messydates") 5 | -------------------------------------------------------------------------------- /tests/testthat/test-class_create.R: -------------------------------------------------------------------------------- 1 | test_that("new_messydate works", { 2 | expect_equal(unclass(as_messydate("2012-01-01")), "2012-01-01") 3 | expect_equal(unclass(as_messydate("2012-1-1")), "2012-01-01") 4 | }) 5 | 6 | test_that("incompleteness works", { 7 | expect_equal(unclass(as_messydate("NA-01-01")), "XXXX-01-01") 8 | expect_equal(unclass(as_messydate("NA-NA-01")), "XXXX-XX-01") 9 | expect_equal(unclass(as_messydate("2012-NA-01")), "2012-XX-01") 10 | expect_equal(unclass(as_messydate("2012-01-NA")), "2012-01") 11 | expect_equal(unclass(as_messydate("2012-1")), "2012-01") 12 | expect_equal(unclass(as_messydate("2012")), "2012") 13 | expect_equal(unclass(as_messydate("1")), "0001") 14 | expect_equal(unclass(as_messydate("12-10-93")), "0093-10-12") 15 | }) 16 | 17 | test_that("uncertainty works", { 18 | expect_equal(unclass(as_messydate("2012?-1-1")), "2012?-01-01") 19 | expect_equal(unclass(as_messydate("2012-01?-1")), "2012-01?-01") 20 | expect_equal(unclass(as_messydate("2012-1-01?")), "2012-01-01?") 21 | }) 22 | 23 | test_that("approximation works", { 24 | expect_equal(unclass(as_messydate("2012~-1-1")), "2012~-01-01") 25 | expect_equal(unclass(as_messydate("2012-01~-01")), "2012-01~-01") 26 | expect_equal(unclass(as_messydate("2012-1-01~")), "2012-01-01~") 27 | }) 28 | 29 | test_that("ranges work", { 30 | expect_equal(unclass(as_messydate("2012-01-01:2014-01-01")), 31 | "2012-01-01..2014-01-01") 32 | expect_equal(unclass(as_messydate("2012-01-01..2014-01-01")), 33 | "2012-01-01..2014-01-01") 34 | expect_equal(unclass(as_messydate("2012-01-01_2014-01-01")), 35 | "2012-01-01..2014-01-01") 36 | }) 37 | 38 | test_that("negative works", { 39 | expect_equal(unclass(as_messydate("28 BC")), "-0028") 40 | expect_equal(unclass(as_messydate("200 BC:100 BC")), "-0200..-0100") 41 | expect_equal(unclass(as_messydate("{-200, -100}")), "{-0200,-0100}") 42 | }) 43 | 44 | test_that("validate_messydate works", { 45 | expect_equal(validate_messydate(as_messydate("28 BC")), as_messydate("28 BC")) 46 | expect_error(validate_messydate(as_messydate("28 BCK"))) 47 | expect_error(validate_messydate(as_messydate("X"))) 48 | expect_error(validate_messydate(as_messydate("28>>"))) 49 | }) 50 | 51 | test_that("print method works", { 52 | expect_output(print(as_messydate("28 BC")), "-0028") 53 | }) 54 | 55 | test_that("c method works", { 56 | expect_identical(as_messydate(c("2012-01-01", "2012-06-01")), 57 | c(as_messydate("2012-01-01"), as_messydate("2012-06-01"))) 58 | }) 59 | 60 | test_that("subset and subset-assign methods work", { 61 | strings <- c("2012-01-01", "2012-XX-01", "2012-01-01:2014-01-01", "28 BC") 62 | full_md <- as_messydate(strings) 63 | expect_identical(full_md[1], as_messydate(strings[1])) 64 | expect_identical(full_md[2:4], as_messydate(strings[2:4])) 65 | expect_identical(full_md[[3]], as_messydate(strings[[3]])) 66 | x <- full_md 67 | x[1] <- full_md[4] 68 | expect_identical(x, as_messydate(strings[c(4, 2:4)])) 69 | x <- full_md 70 | x[c(2, 3)] <- strings[c(1, 4)] 71 | expect_identical(x, as_messydate(strings[c(1, 1, 4, 4)])) 72 | x <- full_md 73 | x[[4]] <- full_md[1] 74 | expect_identical(x, as_messydate(strings[c(1:3, 1)])) 75 | x <- full_md 76 | expect_error(x[1] <- "not a date") 77 | expect_error(x[[1]] <- "not a date") 78 | }) 79 | 80 | test_that("rep method works", { 81 | expect_identical(rep(as_messydate(c("10 AD", "20 AD")), 2), 82 | as_messydate(c("10 AD", "20 AD", "10 AD", "20 AD"))) 83 | expect_identical(rep(as_messydate(c("10 AD", "20 AD")), each = 2), 84 | as_messydate(c("10 AD", "10 AD", "20 AD", "20 AD"))) 85 | }) 86 | 87 | test_that("as.list method works", { 88 | x <- as_messydate(c(a = "2012-01-01", b = "2012-XX-01")) 89 | xl <- as.list(x) 90 | expect_length(xl, length(x)) 91 | expect_identical(xl[[1]], x[[1]]) 92 | expect_identical(xl[[2]], x[[2]]) 93 | expect_identical(names(xl), names(x)) 94 | }) 95 | 96 | test_that("works with data.frames", { 97 | x <- as_messydate(c("2012-01-01", "2012-XX-01", "2012-01-01:2014-01-01", "28 BC")) 98 | df_coerce <- as.data.frame(x) 99 | expect_identical(df_coerce[[1]], x) 100 | expect_equal(dim(df_coerce), c(4, 1)) 101 | df_call <- data.frame(x) 102 | expect_equal(df_coerce, df_call) 103 | }) 104 | -------------------------------------------------------------------------------- /tests/testthat/test-class_duration.R: -------------------------------------------------------------------------------- 1 | mdur <- messyduration("2010-01..2010-12") 2 | test_that("mdates_duration class works", { 3 | expect_equal(class(mdur), "mdates_duration") 4 | # expect_message(mdur, "Converting to mdate class.") 5 | expect_error(messyduration(as_messydate(c("2010-01-01", "2010-01-01"))), 6 | "mdates_duration class objects should have at least one date range") 7 | # expect_equal(messyduration(as_messydate("2010-01..2010-12")), 8 | # messyduration("2010-01-01..2010-12-31")) 9 | expect_equal(mdur, 10 | messyduration("2010-01-01..2010-12-31")) 11 | # expect_equal(messyduration(as_messydate("2010-01..2010-12"), 12 | # approx_range = 1), 13 | # messyduration("2010-01-02..2011-01-01")) 14 | # expect_equal(messyduration(as_messydate("2010-01..2010-12"), 15 | # approx_range = -1), 16 | # messyduration("2009-12-31..2010-12-30")) 17 | }) 18 | -------------------------------------------------------------------------------- /tests/testthat/test-class_make.R: -------------------------------------------------------------------------------- 1 | test_that("mutiple variables are properly bind", { 2 | expect_equal(make_messydate("2010", "1", "1"), as_messydate("2010-01-01")) 3 | expect_equal(make_messydate("2010", "XX", "10"), as_messydate("2010-XX-10")) 4 | expect_equal(make_messydate("2010-01-01"), as_messydate("2010-1-1")) 5 | expect_equal(make_messydate("2012?", "01", "01"), as_messydate("2012?-01-01")) 6 | expect_equal(make_messydate("2012", "01", "01~"), as_messydate("2012-01-01~")) 7 | expect_equal(make_messydate("2012-01-01","2012-01-02"), 8 | as_messydate("2012-01-01..2012-01-02")) 9 | expect_equal(make_messydate("2012-01-01?","2012-01-02~"), 10 | as_messydate("2012-01-01?..2012-01-02~")) 11 | }) 12 | -------------------------------------------------------------------------------- /tests/testthat/test-coerce_from.R: -------------------------------------------------------------------------------- 1 | messy <- as_messydate("2010-10-10..2010-10-20") 2 | ddate <- as.Date("2010-10-10") 3 | mdatey <- as_messydate("2010-10-10") 4 | # negative <- min(as_messydate("1000 BC")) 5 | 6 | test_that("Coercion from other date classes into messydt works", { 7 | # expect_equal(as.character(as.Date(as_messydate("1000 BC"), max)), "-1000-12-31") 8 | expect_equal(as.Date(messy, FUN = vmin), ddate) 9 | # expect_equal(as.Date(mdatey, FUN = median), ddate) 10 | expect_equal(as.Date(mdatey, FUN = random), ddate) 11 | # expect_equal(as.character(as.Date(as_messydate("1000 BC"), min)), min(negative)) 12 | }) 13 | 14 | test_that("Coercion to POSIX works", { 15 | expect_equal(as.POSIXct(messy, FUN = vmax), as.POSIXct("2010-10-20", tz = "UTC")) 16 | # expect_equal(as.POSIXlt(messy, FUN = mean), as.POSIXlt("2010-10-15 CEST")) 17 | }) 18 | 19 | # neg_dates <- as_messydate(c("-27", "-14")) 20 | # test_that("Coercion from other types of negative dates work", { 21 | # expect_equal(min(neg_dates), c("-0027-01-01", "-0014-01-01")) 22 | # expect_equal(max(neg_dates), c("-0027-12-31", "-0014-12-31")) 23 | # expect_equal(mean(neg_dates), c("-0027-07-02", "-0014-07-02")) 24 | # }) 25 | 26 | # expect_error(as.POSIXct(as_messydate("-2012"), min)) 27 | # expect_error(as.POSIXlt(as_messydate("-2012"), min)) 28 | -------------------------------------------------------------------------------- /tests/testthat/test-coerce_resolve.R: -------------------------------------------------------------------------------- 1 | test_dates <- c(range = as_messydate("2014-01-01..2014-01-05"), 2 | unspec = as_messydate("1999"), 3 | neg = as_messydate("1004-02 BC")) 4 | # test_dates <- lapply(test_dates, as_messydate) 5 | 6 | test_that("Min resolving works properly", { 7 | expect_equal(as.character(vmin(test_dates)), 8 | c("2014-01-01","1999-01-01","-1004-02-01")) 9 | }) 10 | 11 | test_that("Max resolving works properly", { 12 | expect_equal(as.character(vmax(test_dates)), 13 | c("2014-01-05","1999-12-31","-1004-02-29")) 14 | }) 15 | 16 | # test_that("Median resolving works properly", { 17 | # expect_equal(as.character(median(test_dates)), 18 | # c("2014-01-03","1999-07-02","-1004-02-15")) 19 | # }) 20 | # 21 | # test_that("Mean resolving works properly", { 22 | # expect_equal(as.character(mean(test_dates)), 23 | # c("2014-01-03","1999-07-02","-1004-02-15")) 24 | # }) 25 | 26 | # test_that("Modal resolving works properly", { 27 | # expect_equal(as.character(modal(test_dates)), 28 | # c("2014-01-01","1999-01-01","-1004-02-01")) 29 | # }) 30 | 31 | test_that("Random resolving works properly", { 32 | expect_length(vrandom(test_dates), 3) 33 | }) 34 | 35 | # test_that("Resolve dates works properly for date ranges", { 36 | # # range2 <- as_messydate("2014-01-01..2014-01-30") 37 | # # expect_equal(as.character(min(range)), "2014-01-01") 38 | # # expect_equal(as.character(max(range)), "2014-01-31") 39 | # # expect_equal(as.character(median(range)), "2014-01-16") 40 | # # expect_equal(as.character(median(range2)), "2014-01-16") 41 | # # expect_equal(as.character(mean(range)), "2014-01-16") 42 | # # expect_equal(as.character(modal(range)), "2014-01-01") 43 | # expect_length(random(range), 1) 44 | # }) 45 | 46 | # test_that("Resolve dates works properly for unspecified dates", { 47 | # unspecified <- as_messydate("1999") 48 | # # expect_equal(as.character(min(unspecified)), "1999-01-01") 49 | # # expect_equal(as.character(max(unspecified)), "1999-12-31") 50 | # # expect_equal(as.character(median(unspecified)), "1999-07-02") 51 | # expect_equal(as.character(mean(unspecified)), "1999-07-02") 52 | # expect_equal(as.character(modal(unspecified)), "1999-01-01") 53 | # expect_length(random(unspecified), 1) 54 | # }) 55 | 56 | # test_that("Resolve dates works properly for negative dates", { 57 | # negative <- as_messydate("1000 BC") 58 | # expect_equal(as.character(min(negative)), "-1000-01-01") 59 | # expect_equal(as.character(as.Date(negative, min)), "-1000-01-01") 60 | # expect_equal(as.character(max(negative)), "-1000-12-31") 61 | # expect_equal(as.character(as.Date(negative, max)), "-1000-12-31") 62 | # expect_equal(as.character(median(negative)), "-1000-07-02") 63 | # expect_equal(as.character(as.Date(negative, median)), "-1000-07-02") 64 | # expect_equal(as.character(mean(negative)), "-1000-07-02") 65 | # expect_equal(as.character(as.Date(negative, mean)), "-1000-07-02") 66 | # expect_equal(as.character(modal(negative)), "-1000-01-01") 67 | # expect_equal(as.character(as.Date(negative, modal)), "-1000-01-01") 68 | # # expect_length(random(negative), 1) 69 | # }) 70 | 71 | test_that("as_mdate adds zero padding when appropriate", { 72 | # expect_equal(as_messydate(min(as_messydate("209-12-31"))), 73 | # as_messydate("0209-12-31")) 74 | # expect_equal(as_messydate(max(as_messydate("-29-12-31"))), 75 | # as_messydate("-0029-12-31")) 76 | expect_equal(as_messydate(c("-29-12-31", "193-02-02", "2010-10-10")), 77 | as_messydate(c("-0029-12-31", "0193-02-02", "2010-10-10"))) 78 | }) 79 | -------------------------------------------------------------------------------- /tests/testthat/test-coerce_to.R: -------------------------------------------------------------------------------- 1 | test_that("Coercion from other date classes into messydt works", { 2 | date <- as.Date("2010-10-10") 3 | POSIXct <- as.POSIXct("2010-10-10", tz = "UTC") 4 | POSIXlt <- as.POSIXlt("2010-10-10", tz = "UTC") 5 | character <- "2010-10-10" 6 | character2 <- "AD2010-10-10" 7 | character3 <- "{BC2010-10-10,BC2010-10-11,BC2010-10-12}" 8 | dmy_text <- "10 October 2010" 9 | mdy_text <- "October 10, 2010" 10 | messy <- as_messydate("2010-10-10") 11 | messyneg <- as_messydate("{-2010-10-10,-2010-10-11,-2010-10-12}") 12 | expect_equal(as_messydate(date), messy) 13 | expect_equal(as_messydate(POSIXct), messy) 14 | expect_equal(as_messydate(POSIXlt), messy) 15 | expect_equal(as_messydate(character), messy) 16 | expect_equal(as_messydate(character2), messy) 17 | expect_equal(as_messydate(character3), messyneg) 18 | expect_equal(as_messydate(dmy_text), messy) 19 | expect_equal(as_messydate(mdy_text), messy) 20 | expect_equal(mdate(date), messy) 21 | expect_equal(mdate(POSIXct), messy) 22 | expect_equal(mdate(POSIXlt), messy) 23 | expect_equal(mdate(character), messy) 24 | expect_equal(mdate(character2), messy) 25 | expect_equal(mdate(character3), messyneg) 26 | expect_equal(mdate(dmy_text), messy) 27 | }) 28 | 29 | test_that("Coercion of unespecified date components are properly handled", { 30 | unspecified <- c("1908-??-??", "1908-10-??", "1908/X/X", "1908/?/?", "XX-1998", 31 | "XXXX-01-01", "01-01-XXXX", "XX-10-1998", "XX-XX-1998") 32 | b <- as_messydate(c("1908", "1908-10", "1908", "1908", "1998", 33 | "XXXX-01-01", "XXXX-01-01", "1998-10", "1998")) 34 | expect_equal(as_messydate(unspecified), b) 35 | }) 36 | 37 | test_that("resequence argument works properly", { 38 | expect_equal(as_messydate(c("121008", "20121008"), resequence = "ymd"), 39 | as_messydate(c("12-10-08", "2012-10-08"))) 40 | expect_equal(as_messydate(c("081012", "08102012", "08-10-12"), resequence = "dmy"), 41 | as_messydate(c("12-10-08", "2012-10-08", "12-10-08"))) 42 | expect_equal(as_messydate(c("03312022", "043097"), resequence = "mdy"), 43 | as_messydate(c("2022-03-31", "97-04-30"))) 44 | expect_equal(as_messydate("201212", resequence = "ym"), 45 | as_messydate("2012-12")) 46 | expect_equal(as_messydate("201212", resequence = "my"), 47 | as_messydate("1212-20")) 48 | }) 49 | 50 | test_that("dates are properly extracted from text", { 51 | expect_equal(as_messydate(c("This function was created on the 29 of September 2021", 52 | "Tomorrow is 13-10-2021", 53 | "Second of February, two thousand and twenty-two")), 54 | as_messydate(c("2021-09-29", "2021-10-13", "2022-02-02"))) 55 | expect_equal(as_messydate(c("signed on this thirtieth day of October one thousand nine hundred and forty-seven", 56 | "signed on one thousand nine hundred and forty-seven, on the month of October, the thirtieth day", 57 | "signed on this twenty-first day of October one thousand nine hundred and forty-seven", 58 | "twenty second day of November 2022")), 59 | as_messydate(c("1947-10-30", "1947-10-30", "1947-10-21", "22-11-2022"))) 60 | }) 61 | 62 | month_dates <- c("Sep 13, 1988", "Jul 11, 2003", "May 28, 1996", "Oct 2, 2009", 63 | "1990, Apr 20", "2006, 22 Nov", "1996, Oct 25", "1997, 2 Dec", 64 | "Jan-1990") 65 | dmy <- c("1988-09-13", "2003-07-11", "1996-05-28", "2009-10-02", 66 | "1990-04-20", "2006-11-22", "1996-10-25", "02-12-1997", "1990-01") 67 | 68 | test_that("conversion from MDY dates with written month works properly", { 69 | expect_equal(as_messydate(month_dates), as_messydate(dmy)) 70 | }) 71 | 72 | test_that("list conversion works properly", { 73 | expect_equal(as_messydate(list(c("2012-06-01", "2012-06-02", "2012-06-03"))), 74 | list(as_messydate("2012-06-01..2012-06-03"))) 75 | expect_equal(as_messydate(list(c(as_messydate("2001-01-01"), 76 | as_messydate("2001-01-02..2001-01-04")))), 77 | list(as_messydate("2001-01-01..2001-01-04"))) 78 | }) 79 | 80 | test_that("zero padding is correctly added conversion works properly", { 81 | expect_equal(as_messydate(c("193-3", "193-3..193-5", "193-3, 193-4")), 82 | as_messydate(c("0193-03-XX", "0193-03-XX..0193-05", "{0193-03-XX,0193-04-XX}"))) 83 | }) 84 | -------------------------------------------------------------------------------- /tests/testthat/test-component_annotate.R: -------------------------------------------------------------------------------- 1 | test_that("Annotate functions work properly", { 2 | data <- data.frame(Beg = c("1816-01-01", "1916-01-01", "2016-01-01"), 3 | End = c("1816-12-31", "1916-12-31", "2016-12-31")) 4 | expect_equal(as.character(on_or_before(data$Beg)), 5 | c("..1816-01-01", "..1916-01-01", "..2016-01-01")) 6 | expect_equal(as.character(on_or_after(data$End)), 7 | c("1816-12-31..", "1916-12-31..", "2016-12-31..")) 8 | expect_equal(as.character(as_approximate(data$Beg)), 9 | c("1816-01-01~", "1916-01-01~", "2016-01-01~")) 10 | expect_equal(as.character(as_uncertain(data$End)), 11 | c("1816-12-31?", "1916-12-31?", "2016-12-31?")) 12 | expect_equal(as.character(as_approximate(data$Beg, "year")), 13 | c("~1816-01-01", "~1916-01-01", "~2016-01-01")) 14 | expect_equal(as.character(ifelse(data$Beg == "1916-01-01", 15 | as_approximate(data$Beg, "month"), 16 | data$Beg)), 17 | c("1816-01-01", "1916-~01-01", "2016-01-01")) 18 | expect_equal(as.character(ifelse(data$Beg == "1916-01-01", 19 | as_approximate(data$Beg, "day"), 20 | data$Beg)), 21 | c("1816-01-01", "1916-01-~01", "2016-01-01")) 22 | expect_equal(as.character(ifelse(data$Beg == "1916-01-01", 23 | as_approximate(data$Beg, "md"), 24 | data$Beg)), 25 | c("1816-01-01", "1916-~01-~01", "2016-01-01")) 26 | expect_equal(as.character(ifelse(data$Beg == "1916-01-01", 27 | as_approximate(data$Beg, "ym"), 28 | data$Beg)), 29 | c("1816-01-01", "1916-01~-01", "2016-01-01")) 30 | expect_equal(as.character(as_uncertain(data$End, "year")), 31 | c("?1816-12-31", "?1916-12-31", "?2016-12-31")) 32 | expect_equal(as.character(ifelse(data$End == "1916-12-31", 33 | as_uncertain(data$End, 34 | "month"), 35 | data$End)), 36 | c("1816-12-31", "1916-?12-31", "2016-12-31")) 37 | expect_equal(as.character(ifelse(data$End == "1916-12-31", 38 | as_uncertain(data$End, "day"), data$End)), 39 | c("1816-12-31", "1916-12-?31", "2016-12-31")) 40 | expect_equal(as.character(ifelse(data$End == "1916-12-31", 41 | as_uncertain(data$End, "md"), data$End)), 42 | c("1816-12-31", "1916-?12-?31", "2016-12-31")) 43 | expect_equal(as.character(ifelse(data$Beg == "1916-01-01", 44 | as_uncertain(data$Beg, "ym"), 45 | data$Beg)), 46 | c("1816-01-01", "1916-01?-01", "2016-01-01")) 47 | d <- on_or_before(data$Beg) 48 | expect_equal(as.character(class(d)), "mdate") 49 | }) 50 | -------------------------------------------------------------------------------- /tests/testthat/test-component_extract.R: -------------------------------------------------------------------------------- 1 | mdatey <- as_messydate("2012-02-03") 2 | test_that("extract functions work properly", { 3 | expect_equal(unclass(year(mdatey)), 2012) 4 | expect_equal(unclass(month(mdatey)), 2) 5 | expect_equal(unclass(day(mdatey)), 3) 6 | }) 7 | 8 | test_that("precision function works properly", { 9 | expect_equal(precision(mdatey), 1) 10 | expect_equal(precision(as_messydate("2012-02-03?")), 1) 11 | expect_equal(precision(as_messydate("2012-02-~03")), 1) 12 | expect_equal(precision(as_messydate("2012-02-03..2012-02-14")), 0.0833, 13 | tolerance = 0.001) 14 | expect_equal(precision(as_messydate("2012-02")), 0.03448276, 15 | tolerance = 0.001) 16 | expect_equal(precision(as_messydate("2012")), 0.00273224, 17 | tolerance = 0.001) 18 | }) 19 | -------------------------------------------------------------------------------- /tests/testthat/test-convert_contract.R: -------------------------------------------------------------------------------- 1 | d <- as_messydate(c("2001-01-01", "2001-01", "2001", 2 | "2001-01-01..2001-02-02", "{2001-10-01,2001-10-04}", 3 | "{2001-01,2001-02-02}", "-2000-01-01", 4 | "2001-01-01..2001-01-03", 5 | "2001-XX-01")) 6 | dd <- as_messydate(c("2001-01-01", "2001-01-01..2001-01-31", 7 | "2001-01-01..2001-12-31", 8 | "2001-01-01..2001-02-02", "{2001-10-01,2001-10-04}", 9 | "{2001-01-01..2001-01-31,2001-02-02}", 10 | "-2000-01-01", 11 | "2001-01-01..2001-01-03", 12 | # "{2001-01-01, 2001-01-02, 2001-01-03}", 13 | "2001-XX-01")) 14 | e <- expand(d) 15 | 16 | test_that("contract works properly", { 17 | expect_equal(contract(e), d) 18 | expect_equal(contract(e, collapse = FALSE), dd) 19 | # expect_equal(contract("{2001-01-01, 2001-01-02, 2001-01-03}"), 20 | # as_messydate("2001-01-01..2001-01-03")) 21 | }) 22 | -------------------------------------------------------------------------------- /tests/testthat/test-convert_expand.R: -------------------------------------------------------------------------------- 1 | test_dates <- c(regular = "2010-01-01", 2 | text = "Second of February, two thousand and twenty-two", 3 | range = "2014-01-01..2014-01-03", 4 | # approxyr = "~1999", 5 | # approxmt = "1999-10~", 6 | approxdy = "1999-10-~11", 7 | unspec = "2008-XX-03", 8 | set = "{2012-01-01,2012-01-12}", 9 | neg = "20 BC", 10 | # unspecrange = "2010..2010-12", 11 | # negincomrange = "200 BC:199 BC", 12 | negincomset = "{-200, -199}") 13 | out <- expand(test_dates) 14 | # regular_date <- as.Date("2010-01-01") 15 | # text_date <- "Second of February, two thousand and twenty-two" 16 | # range <- as_messydate("2014-01-01..2014-01-03") 17 | # approximate <- as_messydate(c("~1999", "1999-10~", "1999-10-~11")) 18 | # unspecified <- as_messydate("2008-XX-03") 19 | # set <- as_messydate("{2012-01-01,2012-01-12}") 20 | # negative <- as_messydate("20 BC") 21 | # unspecified_range <- as_messydate("2010..2010-12") 22 | # negative_incomplete_range <- as_messydate("200 BC:199 BC") 23 | # negative_incomplete_set <- as_messydate("{-200, -199}") 24 | 25 | 26 | test_that("Expand dates lengths are correct", { 27 | expect_equal(length(out), length(test_dates)) 28 | expect_equal(vapply(out, length, FUN.VALUE = numeric(1)), 29 | c(1,1,3,#365,31, 30 | 1,12,2,366,730)) 31 | }) 32 | 33 | # test_that("Expand dates works properly for date ranges and unspecified dates", { 34 | # expect_equal(expand(regular_date), expand(as_messydate(regular_date))) 35 | # expect_length(expand(text_date), 1) 36 | # expect_equal(as.character(expand(range)), 37 | # "c(\"2014-01-01\", \"2014-01-02\", \"2014-01-03\")") 38 | # expect_equal(as.character(expand(approximate)[[1]][1]), "1999-01-01") 39 | # expect_equal(as.character(expand(approximate, approx_range = 3)[[2]][1]), "1996-07-01") 40 | # expect_equal(as.character(expand(approximate, approx_range = 3)[[3]][1]), "1999-10-08") 41 | # expect_equal(as.character(expand(unspecified)[[1]][1]), "2008-01-03") 42 | # expect_equal(as.character(expand(set)[[1]][1]), "2012-01-01") 43 | # expect_length(expand(range), 1) 44 | # expect_length(expand(unspecified), 1) 45 | # expect_equal(lengths(expand(negative)), 366) 46 | # expect_equal(as.character(expand(unspecified_range)[[1]][1]), "2010-01-01") 47 | # expect_equal(lengths(expand(unspecified_range)), 365) 48 | # expect_equal(lengths(expand(negative_incomplete_range)), 730) 49 | # expect_equal(lengths(expand(negative_incomplete_set)), 730) 50 | # }) 51 | 52 | # ly <- as_messydate("~2000-01-01") 53 | # lym <- as_messydate("2000-~02-01") 54 | # test_that("Expand approximate works properly for leap years", { 55 | # expect_equal(lengths(expand(ly, approx_range = 1)), 732) 56 | # expect_equal(lengths(expand(lym, approx_range = 1)), 61) 57 | # }) 58 | -------------------------------------------------------------------------------- /tests/testthat/test-operate_arithmetic.R: -------------------------------------------------------------------------------- 1 | d <- as_messydate(c("2008-03-25", "-2012-02-27", "2001-01?", "2001", 2 | "2001-01-01..2001-02-02", "{2001-01-01,2001-02-02}", 3 | #"..2002-02-03", 4 | "2001-01-03..")) 5 | a <- as_messydate(c("2008-03-28", "-2012-02-24", "2001-01-04..2001-02-03", 6 | "2001-01-04..2002-01-03", "2001-01-04..2001-02-05", 7 | "{2001-01-04,2001-02-05}", #"..2002-02-06", 8 | "2001-01-06..")) 9 | s <- as_messydate(c("2008-03-22", "-2012-03-01", "2000-12-29..2001-01-28", 10 | "2000-12-29..2001-12-28", "2000-12-29..2001-01-30", 11 | "{2000-12-29,2001-01-30} ", #"..2002-01-31", 12 | "2000-12-31..")) 13 | 14 | test_that("operations works properly", { 15 | # expect_equal(add(d, 3), a) 16 | expect_equal(d + 3, a) 17 | # expect_equal(subtract(d, 3), s) 18 | # expect_equal(d - 3, s) 19 | # expect_equal(d + "3 days", a) 20 | expect_equal(d - "3 days", s) 21 | }) 22 | 23 | based <- as_messydate("2001-01-01") 24 | test_that("operations between mdates work properly", { 25 | # expect_equal(based + 26 | # as_messydate("2001-01-02..2001-01-04"), 27 | # as_messydate("2001-01-01..2001-01-04")) 28 | expect_equal(based + as_messydate("2001-01-03"), 29 | as_messydate("{2001-01-01,2001-01-03}")) 30 | # expect_equal(as_messydate("2001-01-01..2001-01-04") - 31 | # based, 32 | # list(as_messydate("{2001-01-01,2001-01-03..2001-01-04}"))) 33 | # expect_message(based - as_messydate("2001-01-03"), 34 | # "First and second elements do not overlap.") 35 | }) 36 | -------------------------------------------------------------------------------- /tests/testthat/test-operate_operators.R: -------------------------------------------------------------------------------- 1 | test_that("Logical comparisons work", { 2 | expect_true(as_messydate("2012-06-02") < as_messydate("2012-06-03")) 3 | # expect_false(as_messydate("2012-06-02") > as_messydate("2012-06-03")) 4 | # expect_true(as_messydate("2012-06-02") >= as_messydate("2012-06-02")) 5 | # expect_true(as_messydate("2012-06-02") <= as_messydate("2012-06-02")) 6 | expect_equal(as_messydate("2012-06-02") >= as_messydate("2012-06-XX"), NA) 7 | expect_true(as_messydate("2012-06-30") >= as_messydate("2012-06-XX")) 8 | # expect_equal(as_messydate("2012-06-02") < as_messydate("2012-06-XX"), NA) 9 | # expect_true(as.Date("2012-06-01") <= as_messydate("2012-06-XX")) 10 | # expect_true(as.POSIXct("2012-06-01") <= as_messydate("2012-06-XX")) 11 | # expect_equal(as.Date("2012-06-01") >= as_messydate("2012-06-XX"), NA) 12 | # expect_equal(as.POSIXct("2012-06-01") >= as_messydate("2012-06-XX"), NA) 13 | # expect_equal(as_messydate("2012-06-XX") >= as.Date("2012-06-02"), NA) 14 | # expect_equal(as_messydate("2012-06-XX") >= as.POSIXct("2012-06-02"), NA) 15 | expect_equal(as_messydate(c("2012-06-02", "2012-06-03")) < 16 | as_messydate(c("2012-06-03", "2012-06-03")), c(TRUE, FALSE)) 17 | # expect_equal(as_messydate(c("2012-06-02", "2012-06-03")) <= 18 | # as_messydate(c("2012-06-03", "2012-06-03")), c(TRUE, TRUE)) 19 | # expect_equal(as_messydate(c("2012-06-02", "2012-06-03")) > 20 | # as_messydate(c("2012-06-03", "2012-06-03")), c(FALSE, FALSE)) 21 | expect_equal(as_messydate(c("2012-06-02", "2012-06-03")) >= 22 | as_messydate(c("2012-06-03", "2012-06-03")), c(FALSE, TRUE)) 23 | # expect_true(as_messydate("2012-06-02") < as_messydate("2012-06-03")) 24 | # expect_true(as_messydate("1000-06-02") < as_messydate("1000-12-03")) 25 | expect_false(as_messydate("2012") < as_messydate("2000")) 26 | # expect_false(as_messydate("39") > as_messydate("1000")) 27 | # expect_true(as_messydate("39") < as_messydate("100")) 28 | # expect_true(as_messydate("1000") < as_messydate("2000")) 29 | # expect_true(as_messydate("0039-12-31") < as_messydate("1000-01-01")) 30 | expect_true(as_messydate("-0001-12-31") <= as_messydate("0001-01-01")) 31 | expect_false(as_messydate("1 BC") > as_messydate("1 AC")) 32 | # expect_true(as_messydate("39-01-01") < as_messydate("100-12-01")) 33 | # expect_true(as_messydate("0079") < as_messydate("0193")) 34 | # expect_true(as_messydate("79") < as_messydate("193-03")) 35 | # expect_true(as_messydate("0079-01") < as_messydate("0193-03")) 36 | expect_false(as_messydate("-0079-01-01") < as_messydate("-0193-03-01")) 37 | }) 38 | 39 | # d1 <- as.Date(c("2012-01-01", "2012-02-01", "2012-03-01")) 40 | # d2 <- as.Date(c("2012-03-01", "2012-02-01", "2012-01-01")) 41 | # p1 <- as.POSIXct(c("2012-01-01", "2012-02-01", "2012-03-01")) 42 | # p2 <- as.POSIXct(c("2012-03-01", "2012-02-01", "2012-01-01")) 43 | test_that("Logical comparisons don't mess up comparisons between non-messy times", { 44 | # expect_identical(d1 < d2, c(TRUE, FALSE, FALSE)) 45 | # # expect_identical(d1 > d2, c(FALSE, FALSE, TRUE)) 46 | # # expect_identical(d1 <= d2, c(TRUE, TRUE, FALSE)) 47 | # expect_identical(d1 >= d2, c(FALSE, TRUE, TRUE)) 48 | # expect_identical(p1 < p2, c(TRUE, FALSE, FALSE)) 49 | # # expect_identical(p1 > p2, c(FALSE, FALSE, TRUE)) 50 | # # expect_identical(p1 <= p2, c(TRUE, TRUE, FALSE)) 51 | # expect_identical(p1 >= p2, c(FALSE, TRUE, TRUE)) 52 | # expect_identical(d1 < p2, c(TRUE, FALSE, FALSE)) 53 | # # expect_identical(d1 > p2, c(FALSE, FALSE, TRUE)) 54 | # # expect_identical(d1 <= p2, c(TRUE, TRUE, FALSE)) 55 | # expect_identical(d1 >= p2, c(FALSE, TRUE, TRUE)) 56 | # expect_identical(p1 < d2, c(TRUE, FALSE, FALSE)) 57 | # # expect_identical(p1 > d2, c(FALSE, FALSE, TRUE)) 58 | # # expect_identical(p1 <= d2, c(TRUE, TRUE, FALSE)) 59 | # expect_identical(p1 >= d2, c(FALSE, TRUE, TRUE)) 60 | expect_true(as_messydate("2010-09-10") > "2009") 61 | # expect_false(as_messydate("2010-09-10") > "2011") 62 | }) 63 | -------------------------------------------------------------------------------- /tests/testthat/test-operate_proportional.R: -------------------------------------------------------------------------------- 1 | # Testing proportional methods of messydates 2 | 3 | test_that("proportional methods work", { 4 | # expect_equal(class(as_messydate("2012-06-02") %l% as_messydate("2012-06")), 5 | # "numeric") 6 | # expect_equal(class(as_messydate("2012-06") %g% 7 | # as_messydate("2012-06-10")), "numeric") 8 | # expect_equal(class(as_messydate("2012-06") %le% 9 | # as_messydate("2012-06-10")), "numeric") 10 | # expect_equal(class(as_messydate("2012-06") %ge% 11 | # as_messydate("2012-06-10")), "numeric") 12 | # expect_equal(class(as_messydate("2012-06") %><% 13 | # as_messydate("2012-06-15..2012-07-15")), "numeric") 14 | # expect_equal(class(as_messydate("2012-06") %>=<% 15 | # as_messydate("2012-06-15..2012-07-15")), "numeric") 16 | # expect_equal(as_messydate("2012-06") %l% as_messydate("2012-06-10"), 0.3) 17 | expect_equal(round(as_messydate("2012-06") %g% 18 | as_messydate("2012-06-10"), 2), 0.67) 19 | expect_equal(round(as_messydate("2012-06") %le% 20 | as_messydate("2012-06-10"), 2), 0.33) 21 | # expect_equal(as_messydate("2012-06") %ge% as_messydate("2012-06-10"), 0.70) 22 | # expect_equal(round(as_messydate("2012-06") %><% 23 | # as_messydate("2012-06-15..2012-07-15"), 2), 0.52) 24 | expect_equal(round(as_messydate("2012-06") %>=<% 25 | as_messydate("2012-06-15..2012-07-15"), 2), 0.53) 26 | }) 27 | -------------------------------------------------------------------------------- /tests/testthat/test-operate_set.R: -------------------------------------------------------------------------------- 1 | test_that("set functions work properly", { 2 | expect_equal(unclass(as_messydate("2012-01-01..2012-01-03") %union% 3 | as_messydate("2012-01-03..2012-01-04")), 4 | c("2012-01-01", "2012-01-02", "2012-01-03", "2012-01-04")) 5 | expect_equal(unclass(as_messydate("2012-01-01..2012-01-03") %intersect% 6 | as_messydate("2012-01-02")), 7 | "2012-01-02") 8 | }) 9 | -------------------------------------------------------------------------------- /tests/testthat/test-operate_statements.R: -------------------------------------------------------------------------------- 1 | # Testing logical methods of messydates 2 | test_that("is_messydate works", { 3 | expect_true(is_messydate(as_messydate("2012-01-01"))) 4 | expect_false(is_messydate("2012-01-01")) 5 | }) 6 | 7 | test_that("is_intersect works", { 8 | expect_true(is_intersecting(as_messydate("2012-01"), as_messydate("2012-01-01..2012-02-22"))) 9 | # expect_false(is_intersecting(as_messydate("2012-01"), 10 | # as_messydate("2012-02-01..2012-02-22"))) 11 | }) 12 | 13 | test_that("is_subset works", { 14 | expect_true(is_subset(as_messydate("2012-01-01"), as_messydate("2012-01"))) 15 | expect_true(is_subset(as_messydate("2012-01-01..2012-01-03"), as_messydate("2012-01"))) 16 | # expect_false(is_subset(as_messydate("2012-01-01"), as_messydate("2012-02"))) 17 | }) 18 | 19 | test_that("is_similar works", { 20 | expect_true(is_similar(as_messydate("2012-06-02"), as_messydate("2012-02-06"))) 21 | # expect_false(is_similar(as_messydate("2012-06-22"), as_messydate("2012-02-06"))) 22 | }) 23 | 24 | test_that("is_precise works", { 25 | expect_true(is_precise(as_messydate("2012-06-02"))) 26 | # expect_false(is_precise(as_messydate("2012?-06-22"))) 27 | }) 28 | 29 | test_that("is_uncertain works", { 30 | expect_true(is_uncertain(as_messydate("2012-06-02?"))) 31 | # expect_false(is_uncertain(as_messydate("2012-06-22"))) 32 | }) 33 | 34 | test_that("is_approximate works", { 35 | expect_true(is_approximate(as_messydate("2012-06-02~"))) 36 | # expect_false(is_approximate(as_messydate("2012-06-22"))) 37 | }) 38 | 39 | -------------------------------------------------------------------------------- /vignettes-old/messydates.R: -------------------------------------------------------------------------------- 1 | ## ----data, warning=FALSE------------------------------------------------------ 2 | library(messydates) 3 | battles <- messydates::battles 4 | battles 5 | 6 | ## ----messy, warning=FALSE, message=FALSE-------------------------------------- 7 | battles$Date <- as_messydate(battles$Date) 8 | battles$Date 9 | 10 | ## ----censored, warning=FALSE-------------------------------------------------- 11 | battles$Date <- as_messydate(ifelse(battles$Battle == "Battle of Herat", on_or_before(battles$Date), battles$Date)) 12 | battles$Date <- as_messydate(ifelse(battles$Battle == "Operation Vaksince", on_or_after(battles$Date), battles$Date)) 13 | dplyr::tibble(battles) 14 | 15 | ## ----approximate, warning=FALSE----------------------------------------------- 16 | battles$Date <- as_messydate(ifelse(battles$Battle == "Battle of Shawali Kowt", as_uncertain(battles$Date), battles$Date)) 17 | battles$Date <- as_messydate(ifelse(battles$Battle == "Battle of Sayyd Alma Kalay", as_approximate(battles$Date), battles$Date)) 18 | dplyr::tibble(battles) 19 | 20 | ## ----expand, warning=FALSE---------------------------------------------------- 21 | expand(battles$Date) 22 | 23 | ## ----expand_approx, eval=FALSE------------------------------------------------ 24 | # expand(battles$Date, approx_range = 1) 25 | 26 | ## ----contract, warning=FALSE-------------------------------------------------- 27 | dplyr::tibble(contract = contract(battles$Date)) 28 | 29 | ## ----coerce, warning=FALSE---------------------------------------------------- 30 | dplyr::tibble(min = as.Date(battles$Date, min), 31 | max = as.Date(battles$Date, max), 32 | median = as.Date(battles$Date, median), 33 | mean = as.Date(battles$Date, mean), 34 | modal = as.Date(battles$Date, modal), 35 | random = as.Date(battles$Date, random)) 36 | 37 | ## ----logical, warning=FALSE--------------------------------------------------- 38 | is_messydate(battles$Date) 39 | is_intersecting(as_messydate(battles$Date[1]), as_messydate(battles$Date[2])) 40 | is_subset(as_messydate("2001-04-17"), as_messydate(battles$Date[2])) 41 | is_similar(as_messydate("2001-08-03"), as_messydate(battles$Date[1])) 42 | is_precise(as_messydate(battles$Date[2])) 43 | 44 | ## ----set, warning=FALSE------------------------------------------------------- 45 | as_messydate(battles$Date[9]) %intersect% as_messydate(battles$Date[10]) 46 | as_messydate(battles$Date[17]) %union% as_messydate(battles$Date[18]) 47 | 48 | ## ----operate------------------------------------------------------------------ 49 | dplyr::tibble("one day more" = battles$Date + 1, 50 | "one day less" = battles$Date - "1 day") 51 | 52 | ## ----proportional------------------------------------------------------------- 53 | as_messydate("2012-06-03") < as.Date("2012-06-02") 54 | as_messydate("2012-06-03") > as.Date("2012-06-02") 55 | as_messydate("2012-06-03") >= as.Date("2012-06-02") 56 | as_messydate("2012-06-03") <= as.Date("2012-06-02") 57 | as_messydate("2012-06") %g% as_messydate("2012-06-02") # proportion greater than 58 | as_messydate("2012-06") %l% as_messydate("2012-06-02") # proportion smaller than 59 | as_messydate("2012-06") %ge% "2012-06-02" # proportion greater or equal than 60 | as_messydate("2012-06") %le% "2012-06-02" # proportion smaller or equal than 61 | as_messydate("2012-06") %><% as_messydate("2012-06-15..2012-07-15") # proportion of dates in the first vector and in the second vector (exclusive) 62 | as_messydate("2012-06") %>=<% as_messydate("2012-06-15..2012-07-15") # proportion of dates and in the first vector in the second vector (inclusive) 63 | 64 | -------------------------------------------------------------------------------- /vignettes-old/messydates.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Working with messy dates" 3 | author: "Henrique Sposito" 4 | date: "`r Sys.Date()`" 5 | output: rmarkdown::html_vignette 6 | vignette: > 7 | %\VignetteIndexEntry{Working with messy dates} 8 | %\VignetteEngine{knitr::rmarkdown} 9 | %\VignetteEncoding{UTF-8} 10 | --- 11 | 12 | ## Why `{messydates}`? 13 | 14 | Dates are often messy. 15 | Whether historical (or ancient), future, or even recent, 16 | we sometimes only know approximately when an event occurred, 17 | that it happened within a particular period, 18 | an unreliable source means a date should be flagged as uncertain, 19 | or different sources offer multiple, competing dates. 20 | 21 | The goal of `{messydates}` is to help with this problem by retaining and working 22 | with various kinds of date imprecision. 23 | \pkg{messydates} contains a set of tools for constructing and coercing 24 | into and from the `mdate` class. 25 | This date class implements [ISO 8601-2_2019(E)](https://www.iso.org/standard/70908.html) 26 | allowing regular dates to be annotated to express unspecified date components, 27 | approximate or uncertain date components, date ranges, and sets of dates. 28 | 29 | # Working with messydates: 2001 Battles 30 | 31 | Take, for example, the names and dates of battles in 2001 according to [Wikipedia](https://en.wikipedia.org/wiki/List_of_battles_in_the_21st_century) 32 | included in \pkg{messydates}. 33 | The dates of these battles are often uncertain or approximate with different 34 | levels of date precision being reported. 35 | 36 | ```{r data, warning=FALSE} 37 | library(messydates) 38 | battles <- messydates::battles 39 | battles 40 | ``` 41 | 42 | ## Coerce to messydates 43 | 44 | Previously researchers had to remove all types of imprecision from date variables and 45 | create multiple variables to deal with date ranges. 46 | `{messydates}` makes it much easier to retain and work with various kinds of date imprecision. 47 | In the 2001 battles dataset, for example, we see that dates are not consistently reported, 48 | but `as_messydate()` still handles the coercion to `mdate` class. 49 | 50 | ```{r messy, warning=FALSE, message=FALSE} 51 | battles$Date <- as_messydate(battles$Date) 52 | battles$Date 53 | ``` 54 | 55 | ## Annotate 56 | 57 | The annotate functions in `{messydates}` help annotate censored, uncertain, and approximate 58 | dates according to [ISO 8601-2_2019(E)](https://www.iso.org/standard/70908.html) standards. 59 | Some datasets might have an arbitrary cut off point for start and end points, 60 | that is, they are censored. 61 | But these are often coded as precise dates when they are not necessarily 62 | the real start or end dates. 63 | Inaccurate start or end dates can be represented by an ".." affix 64 | indicating "on or before", if used as a prefix, 65 | or indicating "on or after", if used as a suffix. 66 | In the case of the battles of 2001 dates, if we are not sure the 67 | "Battle of Kandahar" began on the 22nd of November or, alternatively, that the 68 | "Operation Vaksince" actually ended in the same day it began 69 | we use `on_or_before()` and `on_or after()` to annotate these dates. 70 | 71 | ```{r censored, warning=FALSE} 72 | battles$Date <- as_messydate(ifelse(battles$Battle == "Battle of Herat", on_or_before(battles$Date), battles$Date)) 73 | battles$Date <- as_messydate(ifelse(battles$Battle == "Operation Vaksince", on_or_after(battles$Date), battles$Date)) 74 | dplyr::tibble(battles) 75 | ``` 76 | 77 | Additional annotations for approximate dates, are indicated by adding a `~` to year, 78 | month, or day components, or whole dates to estimate values that are possibly correct. 79 | Day, month, or year, uncertainty can also be indicated by adding a `?` 80 | to a possibly dubious date or date component. 81 | If we are not sure about the reliability of the sources for the 82 | "Battle of Shawali Kowt" and we think the declared date for the battle is approximate, 83 | we can use `as_uncertain()` or `as_approximate()` to annotate these dates. 84 | 85 | ```{r approximate, warning=FALSE} 86 | battles$Date <- as_messydate(ifelse(battles$Battle == "Battle of Shawali Kowt", as_uncertain(battles$Date), battles$Date)) 87 | battles$Date <- as_messydate(ifelse(battles$Battle == "Battle of Sayyd Alma Kalay", as_approximate(battles$Date), battles$Date)) 88 | dplyr::tibble(battles) 89 | ``` 90 | 91 | ## Expand 92 | 93 | Expand functions transform date ranges (annotated with '..'), 94 | sets of dates (annotated with '{ , }'), 95 | and unspecified (missing date components or annotated with 'XX'), or 96 | approximate dates (annotated '~') into lists of dates. 97 | As these dates may refer to several possible dates, 98 | the function "opens" these values to include all the possible dates implied. 99 | Let's expand the dates in the Battles dataset. 100 | 101 | ```{r expand, warning=FALSE} 102 | expand(battles$Date) 103 | ``` 104 | 105 | Note that to expand approximate dates one needs to declare the range to 106 | expand approximate dates using the 'approx_range' argument in `expand()` 107 | 108 | ```{r expand_approx, eval=FALSE} 109 | expand(battles$Date, approx_range = 1) 110 | ``` 111 | 112 | ## Contract 113 | 114 | The `contract()` function operates as the opposite of `expand()`. 115 | It contracts a list of dates into the abbreviated annotation of messydates, 116 | picking the most succinct representation of dates possible. 117 | We can contract back the dates in the Battles data previously expanded. 118 | 119 | ```{r contract, warning=FALSE} 120 | dplyr::tibble(contract = contract(battles$Date)) 121 | ``` 122 | 123 | ## Coerce from messydates 124 | 125 | Coercion functions coerce objects of `mdate` class to 126 | common date classes such as `Date`, `POSIXct`, and `POSIXlt`. 127 | Since `mdate` objects can hold multiple individual dates, 128 | an additional function must be passed as an argument so that multiple dates 129 | are "resolved" into a single date. 130 | 131 | For example, one might wish to use the earliest possible date 132 | in any ranges of dates (`min`), the latest possible date (`max`), 133 | some notion of a central tendency (`mean`, `median`, or `modal`), 134 | or even a `random` selection from among the candidate dates. 135 | These functions are particularly useful for use with existing methods and models, 136 | especially for checking the robustness of results. 137 | 138 | ```{r coerce, warning=FALSE} 139 | dplyr::tibble(min = as.Date(battles$Date, min), 140 | max = as.Date(battles$Date, max), 141 | median = as.Date(battles$Date, median), 142 | mean = as.Date(battles$Date, mean), 143 | modal = as.Date(battles$Date, modal), 144 | random = as.Date(battles$Date, random)) 145 | ``` 146 | 147 | ## Additional functionality 148 | 149 | Several other functions are offered in `{messydates}`. 150 | 151 | For example, we can run several logical tests to `mdate` variables. 152 | `is_messydate()` tests whether the object inherits the `mdate` class. 153 | `is_intersecting()` tests whether there is any intersection between two messy dates. 154 | `is_subset()` similarly tests whether one or more messy dates can be found within a messy date range or set. 155 | `is_similar()` tests whether two dates contain similar components. 156 | `is_precise()` tests whether certain date is precise. 157 | 158 | ```{r logical, warning=FALSE} 159 | is_messydate(battles$Date) 160 | is_intersecting(as_messydate(battles$Date[1]), as_messydate(battles$Date[2])) 161 | is_subset(as_messydate("2001-04-17"), as_messydate(battles$Date[2])) 162 | is_similar(as_messydate("2001-08-03"), as_messydate(battles$Date[1])) 163 | is_precise(as_messydate(battles$Date[2])) 164 | ``` 165 | 166 | Additionally, one can perform intersection or union of messydates. 167 | 168 | ```{r set, warning=FALSE} 169 | as_messydate(battles$Date[9]) %intersect% as_messydate(battles$Date[10]) 170 | as_messydate(battles$Date[17]) %union% as_messydate(battles$Date[18]) 171 | ``` 172 | 173 | As well, we can do some arithmetic operations in the `mdate` variable. 174 | 175 | ```{r operate} 176 | dplyr::tibble("one day more" = battles$Date + 1, 177 | "one day less" = battles$Date - "1 day") 178 | ``` 179 | 180 | Finally, one can run logical and proportional comparisons on mdate objects. 181 | 182 | ```{r proportional} 183 | as_messydate("2012-06-03") < as.Date("2012-06-02") 184 | as_messydate("2012-06-03") > as.Date("2012-06-02") 185 | as_messydate("2012-06-03") >= as.Date("2012-06-02") 186 | as_messydate("2012-06-03") <= as.Date("2012-06-02") 187 | as_messydate("2012-06") %g% as_messydate("2012-06-02") # proportion greater than 188 | as_messydate("2012-06") %l% as_messydate("2012-06-02") # proportion smaller than 189 | as_messydate("2012-06") %ge% "2012-06-02" # proportion greater or equal than 190 | as_messydate("2012-06") %le% "2012-06-02" # proportion smaller or equal than 191 | as_messydate("2012-06") %><% as_messydate("2012-06-15..2012-07-15") # proportion of dates in the first vector and in the second vector (exclusive) 192 | as_messydate("2012-06") %>=<% as_messydate("2012-06-15..2012-07-15") # proportion of dates and in the first vector in the second vector (inclusive) 193 | ``` 194 | --------------------------------------------------------------------------------