├── .github ├── FUNDING.yml └── workflows │ ├── clippy.yml │ ├── rustfmt.yml │ ├── tests.yml │ └── wasm.yml ├── .gitignore ├── .gitmodules ├── .vscode └── settings.json ├── CHANGELOG.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── Makefile ├── README.md ├── assets └── hello.png ├── cli ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md └── src │ └── main.rs ├── libwhen ├── Cargo.toml ├── LICENSE ├── README.md ├── build.rs └── src │ ├── date_grammar.pest │ ├── lib.rs │ ├── location.rs │ ├── parser.rs │ └── utils.rs └── web ├── Cargo.lock ├── Cargo.toml ├── src └── lib.rs ├── tests └── web.rs └── www ├── .gitignore ├── bootstrap.js ├── index.html ├── index.jsx ├── package-lock.json ├── package.json ├── style.css ├── webpack.config.js └── webpack.prod.js /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [mitsuhiko] 2 | -------------------------------------------------------------------------------- /.github/workflows/clippy.yml: -------------------------------------------------------------------------------- 1 | name: Clippy 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - uses: actions/checkout@v2 11 | with: 12 | submodules: recursive 13 | - uses: actions-rs/toolchain@v1 14 | with: 15 | toolchain: stable 16 | profile: minimal 17 | components: clippy, rustfmt 18 | override: true 19 | - name: Run clippy 20 | run: make lint 21 | -------------------------------------------------------------------------------- /.github/workflows/rustfmt.yml: -------------------------------------------------------------------------------- 1 | name: Rustfmt 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - uses: actions/checkout@v2 11 | with: 12 | submodules: recursive 13 | - uses: actions-rs/toolchain@v1 14 | with: 15 | toolchain: stable 16 | profile: minimal 17 | components: clippy, rustfmt 18 | override: true 19 | - name: Run rustfmt 20 | run: make format-check 21 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build-latest: 7 | name: Test on Latest 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v2 12 | with: 13 | submodules: recursive 14 | - uses: actions-rs/toolchain@v1 15 | with: 16 | toolchain: stable 17 | profile: minimal 18 | override: true 19 | - name: Test 20 | run: make test 21 | -------------------------------------------------------------------------------- /.github/workflows/wasm.yml: -------------------------------------------------------------------------------- 1 | name: WASM Build 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | build-latest: 10 | name: WASM on Latest 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v2 15 | with: 16 | submodules: recursive 17 | - uses: actions-rs/toolchain@v1 18 | with: 19 | toolchain: stable 20 | profile: minimal 21 | override: true 22 | - uses: jetli/wasm-pack-action@v0.3.0 23 | - name: Build 24 | run: (cd web/www; npm ci) && make web-dist && touch web/www/dist/.nojekyll 25 | - name: Deploy 26 | uses: JamesIves/github-pages-deploy-action@4.1.7 27 | with: 28 | branch: gh-pages 29 | folder: web/www/dist 30 | single-commit: true 31 | clean: true 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "libwhen/data"] 2 | path = libwhen/data 3 | url = https://github.com/mitsuhiko/when-data 4 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "rust-analyzer.checkOnSave.command": "clippy" 3 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to `when` are documented here. 4 | 5 | ## 0.4.0 6 | 7 | - Added `unix:` syntax to support timestamps. 8 | 9 | ## 0.3.0 10 | 11 | - Added support for multiple target locations. 12 | - Added timezone name to short output. 13 | - Added `--json` flag to enable JSON output. 14 | - Added timezone abbreviations to output. 15 | - Added `--list-timezones` option. 16 | 17 | ## 0.2.0 18 | 19 | - Fixed an issue where admin code was same as country code. 20 | - Add support for colors. 21 | - Improved error message output. 22 | - Added week day output. 23 | - If no `to` zone is requested, the current local timezone is assumed if the timezones do not match. 24 | 25 | ## 0.1.0 26 | 27 | - Initial version 28 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "anyhow" 7 | version = "1.0.51" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "8b26702f315f53b6071259e15dd9d64528213b44d61de1ec926eca7715d62203" 10 | 11 | [[package]] 12 | name = "atty" 13 | version = "0.2.14" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 16 | dependencies = [ 17 | "hermit-abi", 18 | "libc", 19 | "winapi", 20 | ] 21 | 22 | [[package]] 23 | name = "autocfg" 24 | version = "1.0.1" 25 | source = "registry+https://github.com/rust-lang/crates.io-index" 26 | checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" 27 | 28 | [[package]] 29 | name = "bitflags" 30 | version = "1.3.2" 31 | source = "registry+https://github.com/rust-lang/crates.io-index" 32 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 33 | 34 | [[package]] 35 | name = "block-buffer" 36 | version = "0.7.3" 37 | source = "registry+https://github.com/rust-lang/crates.io-index" 38 | checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" 39 | dependencies = [ 40 | "block-padding", 41 | "byte-tools", 42 | "byteorder", 43 | "generic-array", 44 | ] 45 | 46 | [[package]] 47 | name = "block-padding" 48 | version = "0.1.5" 49 | source = "registry+https://github.com/rust-lang/crates.io-index" 50 | checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" 51 | dependencies = [ 52 | "byte-tools", 53 | ] 54 | 55 | [[package]] 56 | name = "bumpalo" 57 | version = "3.8.0" 58 | source = "registry+https://github.com/rust-lang/crates.io-index" 59 | checksum = "8f1e260c3a9040a7c19a12468758f4c16f31a81a1fe087482be9570ec864bb6c" 60 | 61 | [[package]] 62 | name = "byte-tools" 63 | version = "0.3.1" 64 | source = "registry+https://github.com/rust-lang/crates.io-index" 65 | checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" 66 | 67 | [[package]] 68 | name = "byteorder" 69 | version = "1.4.3" 70 | source = "registry+https://github.com/rust-lang/crates.io-index" 71 | checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" 72 | 73 | [[package]] 74 | name = "cfg-if" 75 | version = "1.0.0" 76 | source = "registry+https://github.com/rust-lang/crates.io-index" 77 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 78 | 79 | [[package]] 80 | name = "chrono" 81 | version = "0.4.19" 82 | source = "registry+https://github.com/rust-lang/crates.io-index" 83 | checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" 84 | dependencies = [ 85 | "libc", 86 | "num-integer", 87 | "num-traits", 88 | "serde", 89 | "time", 90 | "winapi", 91 | ] 92 | 93 | [[package]] 94 | name = "chrono-humanize" 95 | version = "0.2.1" 96 | source = "registry+https://github.com/rust-lang/crates.io-index" 97 | checksum = "2eddc119501d583fd930cb92144e605f44e0252c38dd89d9247fffa1993375cb" 98 | dependencies = [ 99 | "chrono", 100 | ] 101 | 102 | [[package]] 103 | name = "chrono-tz" 104 | version = "0.6.1" 105 | source = "registry+https://github.com/rust-lang/crates.io-index" 106 | checksum = "58549f1842da3080ce63002102d5bc954c7bc843d4f47818e642abdc36253552" 107 | dependencies = [ 108 | "chrono", 109 | "chrono-tz-build", 110 | "phf", 111 | ] 112 | 113 | [[package]] 114 | name = "chrono-tz-build" 115 | version = "0.0.2" 116 | source = "registry+https://github.com/rust-lang/crates.io-index" 117 | checksum = "db058d493fb2f65f41861bfed7e3fe6335264a9f0f92710cab5bdf01fef09069" 118 | dependencies = [ 119 | "parse-zoneinfo", 120 | "phf", 121 | "phf_codegen", 122 | ] 123 | 124 | [[package]] 125 | name = "clap" 126 | version = "3.0.0-rc.0" 127 | source = "registry+https://github.com/rust-lang/crates.io-index" 128 | checksum = "79b70f999da60e6619a29b131739d2211ed4d4301f40372e94a8081422e9d6c7" 129 | dependencies = [ 130 | "atty", 131 | "bitflags", 132 | "clap_derive", 133 | "indexmap", 134 | "lazy_static", 135 | "os_str_bytes", 136 | "strsim", 137 | "termcolor", 138 | "terminal_size", 139 | "textwrap", 140 | ] 141 | 142 | [[package]] 143 | name = "clap_derive" 144 | version = "3.0.0-rc.0" 145 | source = "registry+https://github.com/rust-lang/crates.io-index" 146 | checksum = "fe8c0f28022faaef0387fa54f8e33fee22b804a88bbd91303197da2ff8ca6a5d" 147 | dependencies = [ 148 | "heck", 149 | "proc-macro-error", 150 | "proc-macro2", 151 | "quote", 152 | "syn", 153 | ] 154 | 155 | [[package]] 156 | name = "console" 157 | version = "0.15.0" 158 | source = "registry+https://github.com/rust-lang/crates.io-index" 159 | checksum = "a28b32d32ca44b70c3e4acd7db1babf555fa026e385fb95f18028f88848b3c31" 160 | dependencies = [ 161 | "encode_unicode", 162 | "libc", 163 | "once_cell", 164 | "regex", 165 | "terminal_size", 166 | "unicode-width", 167 | "winapi", 168 | ] 169 | 170 | [[package]] 171 | name = "digest" 172 | version = "0.8.1" 173 | source = "registry+https://github.com/rust-lang/crates.io-index" 174 | checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" 175 | dependencies = [ 176 | "generic-array", 177 | ] 178 | 179 | [[package]] 180 | name = "encode_unicode" 181 | version = "0.3.6" 182 | source = "registry+https://github.com/rust-lang/crates.io-index" 183 | checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" 184 | 185 | [[package]] 186 | name = "fake-simd" 187 | version = "0.1.2" 188 | source = "registry+https://github.com/rust-lang/crates.io-index" 189 | checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" 190 | 191 | [[package]] 192 | name = "generic-array" 193 | version = "0.12.4" 194 | source = "registry+https://github.com/rust-lang/crates.io-index" 195 | checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd" 196 | dependencies = [ 197 | "typenum", 198 | ] 199 | 200 | [[package]] 201 | name = "getrandom" 202 | version = "0.2.3" 203 | source = "registry+https://github.com/rust-lang/crates.io-index" 204 | checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" 205 | dependencies = [ 206 | "cfg-if", 207 | "libc", 208 | "wasi", 209 | ] 210 | 211 | [[package]] 212 | name = "hashbrown" 213 | version = "0.11.2" 214 | source = "registry+https://github.com/rust-lang/crates.io-index" 215 | checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" 216 | 217 | [[package]] 218 | name = "heck" 219 | version = "0.3.3" 220 | source = "registry+https://github.com/rust-lang/crates.io-index" 221 | checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" 222 | dependencies = [ 223 | "unicode-segmentation", 224 | ] 225 | 226 | [[package]] 227 | name = "hermit-abi" 228 | version = "0.1.19" 229 | source = "registry+https://github.com/rust-lang/crates.io-index" 230 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 231 | dependencies = [ 232 | "libc", 233 | ] 234 | 235 | [[package]] 236 | name = "indexmap" 237 | version = "1.7.0" 238 | source = "registry+https://github.com/rust-lang/crates.io-index" 239 | checksum = "bc633605454125dec4b66843673f01c7df2b89479b32e0ed634e43a91cff62a5" 240 | dependencies = [ 241 | "autocfg", 242 | "hashbrown", 243 | ] 244 | 245 | [[package]] 246 | name = "itoa" 247 | version = "0.4.8" 248 | source = "registry+https://github.com/rust-lang/crates.io-index" 249 | checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" 250 | 251 | [[package]] 252 | name = "js-sys" 253 | version = "0.3.55" 254 | source = "registry+https://github.com/rust-lang/crates.io-index" 255 | checksum = "7cc9ffccd38c451a86bf13657df244e9c3f37493cce8e5e21e940963777acc84" 256 | dependencies = [ 257 | "wasm-bindgen", 258 | ] 259 | 260 | [[package]] 261 | name = "lazy_static" 262 | version = "1.4.0" 263 | source = "registry+https://github.com/rust-lang/crates.io-index" 264 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 265 | 266 | [[package]] 267 | name = "libc" 268 | version = "0.2.108" 269 | source = "registry+https://github.com/rust-lang/crates.io-index" 270 | checksum = "8521a1b57e76b1ec69af7599e75e38e7b7fad6610f037db8c79b127201b5d119" 271 | 272 | [[package]] 273 | name = "libwhen" 274 | version = "0.4.0" 275 | dependencies = [ 276 | "chrono", 277 | "chrono-humanize", 278 | "chrono-tz", 279 | "localzone", 280 | "pest", 281 | "pest_derive", 282 | "serde", 283 | ] 284 | 285 | [[package]] 286 | name = "localzone" 287 | version = "0.2.0" 288 | source = "registry+https://github.com/rust-lang/crates.io-index" 289 | checksum = "7865f5a13ecb548180b8fe7e538d739090329fdb984eef9e9a16db214f216c6f" 290 | dependencies = [ 291 | "js-sys", 292 | "windows", 293 | ] 294 | 295 | [[package]] 296 | name = "log" 297 | version = "0.4.14" 298 | source = "registry+https://github.com/rust-lang/crates.io-index" 299 | checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" 300 | dependencies = [ 301 | "cfg-if", 302 | ] 303 | 304 | [[package]] 305 | name = "maplit" 306 | version = "1.0.2" 307 | source = "registry+https://github.com/rust-lang/crates.io-index" 308 | checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" 309 | 310 | [[package]] 311 | name = "memchr" 312 | version = "2.4.1" 313 | source = "registry+https://github.com/rust-lang/crates.io-index" 314 | checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" 315 | 316 | [[package]] 317 | name = "num-integer" 318 | version = "0.1.44" 319 | source = "registry+https://github.com/rust-lang/crates.io-index" 320 | checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" 321 | dependencies = [ 322 | "autocfg", 323 | "num-traits", 324 | ] 325 | 326 | [[package]] 327 | name = "num-traits" 328 | version = "0.2.14" 329 | source = "registry+https://github.com/rust-lang/crates.io-index" 330 | checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" 331 | dependencies = [ 332 | "autocfg", 333 | ] 334 | 335 | [[package]] 336 | name = "once_cell" 337 | version = "1.8.0" 338 | source = "registry+https://github.com/rust-lang/crates.io-index" 339 | checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" 340 | 341 | [[package]] 342 | name = "opaque-debug" 343 | version = "0.2.3" 344 | source = "registry+https://github.com/rust-lang/crates.io-index" 345 | checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" 346 | 347 | [[package]] 348 | name = "os_str_bytes" 349 | version = "6.0.0" 350 | source = "registry+https://github.com/rust-lang/crates.io-index" 351 | checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64" 352 | dependencies = [ 353 | "memchr", 354 | ] 355 | 356 | [[package]] 357 | name = "parse-zoneinfo" 358 | version = "0.3.0" 359 | source = "registry+https://github.com/rust-lang/crates.io-index" 360 | checksum = "c705f256449c60da65e11ff6626e0c16a0a0b96aaa348de61376b249bc340f41" 361 | dependencies = [ 362 | "regex", 363 | ] 364 | 365 | [[package]] 366 | name = "pest" 367 | version = "2.1.3" 368 | source = "registry+https://github.com/rust-lang/crates.io-index" 369 | checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53" 370 | dependencies = [ 371 | "ucd-trie", 372 | ] 373 | 374 | [[package]] 375 | name = "pest_derive" 376 | version = "2.1.0" 377 | source = "registry+https://github.com/rust-lang/crates.io-index" 378 | checksum = "833d1ae558dc601e9a60366421196a8d94bc0ac980476d0b67e1d0988d72b2d0" 379 | dependencies = [ 380 | "pest", 381 | "pest_generator", 382 | ] 383 | 384 | [[package]] 385 | name = "pest_generator" 386 | version = "2.1.3" 387 | source = "registry+https://github.com/rust-lang/crates.io-index" 388 | checksum = "99b8db626e31e5b81787b9783425769681b347011cc59471e33ea46d2ea0cf55" 389 | dependencies = [ 390 | "pest", 391 | "pest_meta", 392 | "proc-macro2", 393 | "quote", 394 | "syn", 395 | ] 396 | 397 | [[package]] 398 | name = "pest_meta" 399 | version = "2.1.3" 400 | source = "registry+https://github.com/rust-lang/crates.io-index" 401 | checksum = "54be6e404f5317079812fc8f9f5279de376d8856929e21c184ecf6bbd692a11d" 402 | dependencies = [ 403 | "maplit", 404 | "pest", 405 | "sha-1", 406 | ] 407 | 408 | [[package]] 409 | name = "phf" 410 | version = "0.10.0" 411 | source = "registry+https://github.com/rust-lang/crates.io-index" 412 | checksum = "b9fc3db1018c4b59d7d582a739436478b6035138b6aecbce989fc91c3e98409f" 413 | dependencies = [ 414 | "phf_shared", 415 | ] 416 | 417 | [[package]] 418 | name = "phf_codegen" 419 | version = "0.10.0" 420 | source = "registry+https://github.com/rust-lang/crates.io-index" 421 | checksum = "4fb1c3a8bc4dd4e5cfce29b44ffc14bedd2ee294559a294e2a4d4c9e9a6a13cd" 422 | dependencies = [ 423 | "phf_generator", 424 | "phf_shared", 425 | ] 426 | 427 | [[package]] 428 | name = "phf_generator" 429 | version = "0.10.0" 430 | source = "registry+https://github.com/rust-lang/crates.io-index" 431 | checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" 432 | dependencies = [ 433 | "phf_shared", 434 | "rand", 435 | ] 436 | 437 | [[package]] 438 | name = "phf_shared" 439 | version = "0.10.0" 440 | source = "registry+https://github.com/rust-lang/crates.io-index" 441 | checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" 442 | dependencies = [ 443 | "siphasher", 444 | "uncased", 445 | ] 446 | 447 | [[package]] 448 | name = "ppv-lite86" 449 | version = "0.2.15" 450 | source = "registry+https://github.com/rust-lang/crates.io-index" 451 | checksum = "ed0cfbc8191465bed66e1718596ee0b0b35d5ee1f41c5df2189d0fe8bde535ba" 452 | 453 | [[package]] 454 | name = "proc-macro-error" 455 | version = "1.0.4" 456 | source = "registry+https://github.com/rust-lang/crates.io-index" 457 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 458 | dependencies = [ 459 | "proc-macro-error-attr", 460 | "proc-macro2", 461 | "quote", 462 | "syn", 463 | "version_check", 464 | ] 465 | 466 | [[package]] 467 | name = "proc-macro-error-attr" 468 | version = "1.0.4" 469 | source = "registry+https://github.com/rust-lang/crates.io-index" 470 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 471 | dependencies = [ 472 | "proc-macro2", 473 | "quote", 474 | "version_check", 475 | ] 476 | 477 | [[package]] 478 | name = "proc-macro2" 479 | version = "1.0.32" 480 | source = "registry+https://github.com/rust-lang/crates.io-index" 481 | checksum = "ba508cc11742c0dc5c1659771673afbab7a0efab23aa17e854cbab0837ed0b43" 482 | dependencies = [ 483 | "unicode-xid", 484 | ] 485 | 486 | [[package]] 487 | name = "quote" 488 | version = "1.0.10" 489 | source = "registry+https://github.com/rust-lang/crates.io-index" 490 | checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05" 491 | dependencies = [ 492 | "proc-macro2", 493 | ] 494 | 495 | [[package]] 496 | name = "rand" 497 | version = "0.8.4" 498 | source = "registry+https://github.com/rust-lang/crates.io-index" 499 | checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" 500 | dependencies = [ 501 | "libc", 502 | "rand_chacha", 503 | "rand_core", 504 | "rand_hc", 505 | ] 506 | 507 | [[package]] 508 | name = "rand_chacha" 509 | version = "0.3.1" 510 | source = "registry+https://github.com/rust-lang/crates.io-index" 511 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 512 | dependencies = [ 513 | "ppv-lite86", 514 | "rand_core", 515 | ] 516 | 517 | [[package]] 518 | name = "rand_core" 519 | version = "0.6.3" 520 | source = "registry+https://github.com/rust-lang/crates.io-index" 521 | checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" 522 | dependencies = [ 523 | "getrandom", 524 | ] 525 | 526 | [[package]] 527 | name = "rand_hc" 528 | version = "0.3.1" 529 | source = "registry+https://github.com/rust-lang/crates.io-index" 530 | checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7" 531 | dependencies = [ 532 | "rand_core", 533 | ] 534 | 535 | [[package]] 536 | name = "regex" 537 | version = "1.5.4" 538 | source = "registry+https://github.com/rust-lang/crates.io-index" 539 | checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" 540 | dependencies = [ 541 | "regex-syntax", 542 | ] 543 | 544 | [[package]] 545 | name = "regex-syntax" 546 | version = "0.6.25" 547 | source = "registry+https://github.com/rust-lang/crates.io-index" 548 | checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" 549 | 550 | [[package]] 551 | name = "ryu" 552 | version = "1.0.6" 553 | source = "registry+https://github.com/rust-lang/crates.io-index" 554 | checksum = "3c9613b5a66ab9ba26415184cfc41156594925a9cf3a2057e57f31ff145f6568" 555 | 556 | [[package]] 557 | name = "serde" 558 | version = "1.0.131" 559 | source = "registry+https://github.com/rust-lang/crates.io-index" 560 | checksum = "b4ad69dfbd3e45369132cc64e6748c2d65cdfb001a2b1c232d128b4ad60561c1" 561 | dependencies = [ 562 | "serde_derive", 563 | ] 564 | 565 | [[package]] 566 | name = "serde_derive" 567 | version = "1.0.131" 568 | source = "registry+https://github.com/rust-lang/crates.io-index" 569 | checksum = "b710a83c4e0dff6a3d511946b95274ad9ca9e5d3ae497b63fda866ac955358d2" 570 | dependencies = [ 571 | "proc-macro2", 572 | "quote", 573 | "syn", 574 | ] 575 | 576 | [[package]] 577 | name = "serde_json" 578 | version = "1.0.72" 579 | source = "registry+https://github.com/rust-lang/crates.io-index" 580 | checksum = "d0ffa0837f2dfa6fb90868c2b5468cad482e175f7dad97e7421951e663f2b527" 581 | dependencies = [ 582 | "itoa", 583 | "ryu", 584 | "serde", 585 | ] 586 | 587 | [[package]] 588 | name = "sha-1" 589 | version = "0.8.2" 590 | source = "registry+https://github.com/rust-lang/crates.io-index" 591 | checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df" 592 | dependencies = [ 593 | "block-buffer", 594 | "digest", 595 | "fake-simd", 596 | "opaque-debug", 597 | ] 598 | 599 | [[package]] 600 | name = "siphasher" 601 | version = "0.3.7" 602 | source = "registry+https://github.com/rust-lang/crates.io-index" 603 | checksum = "533494a8f9b724d33625ab53c6c4800f7cc445895924a8ef649222dcb76e938b" 604 | 605 | [[package]] 606 | name = "strsim" 607 | version = "0.10.0" 608 | source = "registry+https://github.com/rust-lang/crates.io-index" 609 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 610 | 611 | [[package]] 612 | name = "syn" 613 | version = "1.0.82" 614 | source = "registry+https://github.com/rust-lang/crates.io-index" 615 | checksum = "8daf5dd0bb60cbd4137b1b587d2fc0ae729bc07cf01cd70b36a1ed5ade3b9d59" 616 | dependencies = [ 617 | "proc-macro2", 618 | "quote", 619 | "unicode-xid", 620 | ] 621 | 622 | [[package]] 623 | name = "termcolor" 624 | version = "1.1.2" 625 | source = "registry+https://github.com/rust-lang/crates.io-index" 626 | checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" 627 | dependencies = [ 628 | "winapi-util", 629 | ] 630 | 631 | [[package]] 632 | name = "terminal_size" 633 | version = "0.1.17" 634 | source = "registry+https://github.com/rust-lang/crates.io-index" 635 | checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df" 636 | dependencies = [ 637 | "libc", 638 | "winapi", 639 | ] 640 | 641 | [[package]] 642 | name = "textwrap" 643 | version = "0.14.2" 644 | source = "registry+https://github.com/rust-lang/crates.io-index" 645 | checksum = "0066c8d12af8b5acd21e00547c3797fde4e8677254a7ee429176ccebbe93dd80" 646 | dependencies = [ 647 | "terminal_size", 648 | ] 649 | 650 | [[package]] 651 | name = "time" 652 | version = "0.1.44" 653 | source = "registry+https://github.com/rust-lang/crates.io-index" 654 | checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" 655 | dependencies = [ 656 | "libc", 657 | "wasi", 658 | "winapi", 659 | ] 660 | 661 | [[package]] 662 | name = "typenum" 663 | version = "1.14.0" 664 | source = "registry+https://github.com/rust-lang/crates.io-index" 665 | checksum = "b63708a265f51345575b27fe43f9500ad611579e764c79edbc2037b1121959ec" 666 | 667 | [[package]] 668 | name = "ucd-trie" 669 | version = "0.1.3" 670 | source = "registry+https://github.com/rust-lang/crates.io-index" 671 | checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" 672 | 673 | [[package]] 674 | name = "uncased" 675 | version = "0.9.6" 676 | source = "registry+https://github.com/rust-lang/crates.io-index" 677 | checksum = "5baeed7327e25054889b9bd4f975f32e5f4c5d434042d59ab6cd4142c0a76ed0" 678 | dependencies = [ 679 | "version_check", 680 | ] 681 | 682 | [[package]] 683 | name = "unicode-segmentation" 684 | version = "1.8.0" 685 | source = "registry+https://github.com/rust-lang/crates.io-index" 686 | checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b" 687 | 688 | [[package]] 689 | name = "unicode-width" 690 | version = "0.1.9" 691 | source = "registry+https://github.com/rust-lang/crates.io-index" 692 | checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" 693 | 694 | [[package]] 695 | name = "unicode-xid" 696 | version = "0.2.2" 697 | source = "registry+https://github.com/rust-lang/crates.io-index" 698 | checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" 699 | 700 | [[package]] 701 | name = "version_check" 702 | version = "0.9.3" 703 | source = "registry+https://github.com/rust-lang/crates.io-index" 704 | checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" 705 | 706 | [[package]] 707 | name = "wasi" 708 | version = "0.10.0+wasi-snapshot-preview1" 709 | source = "registry+https://github.com/rust-lang/crates.io-index" 710 | checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" 711 | 712 | [[package]] 713 | name = "wasm-bindgen" 714 | version = "0.2.78" 715 | source = "registry+https://github.com/rust-lang/crates.io-index" 716 | checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce" 717 | dependencies = [ 718 | "cfg-if", 719 | "wasm-bindgen-macro", 720 | ] 721 | 722 | [[package]] 723 | name = "wasm-bindgen-backend" 724 | version = "0.2.78" 725 | source = "registry+https://github.com/rust-lang/crates.io-index" 726 | checksum = "a317bf8f9fba2476b4b2c85ef4c4af8ff39c3c7f0cdfeed4f82c34a880aa837b" 727 | dependencies = [ 728 | "bumpalo", 729 | "lazy_static", 730 | "log", 731 | "proc-macro2", 732 | "quote", 733 | "syn", 734 | "wasm-bindgen-shared", 735 | ] 736 | 737 | [[package]] 738 | name = "wasm-bindgen-macro" 739 | version = "0.2.78" 740 | source = "registry+https://github.com/rust-lang/crates.io-index" 741 | checksum = "d56146e7c495528bf6587663bea13a8eb588d39b36b679d83972e1a2dbbdacf9" 742 | dependencies = [ 743 | "quote", 744 | "wasm-bindgen-macro-support", 745 | ] 746 | 747 | [[package]] 748 | name = "wasm-bindgen-macro-support" 749 | version = "0.2.78" 750 | source = "registry+https://github.com/rust-lang/crates.io-index" 751 | checksum = "7803e0eea25835f8abdc585cd3021b3deb11543c6fe226dcd30b228857c5c5ab" 752 | dependencies = [ 753 | "proc-macro2", 754 | "quote", 755 | "syn", 756 | "wasm-bindgen-backend", 757 | "wasm-bindgen-shared", 758 | ] 759 | 760 | [[package]] 761 | name = "wasm-bindgen-shared" 762 | version = "0.2.78" 763 | source = "registry+https://github.com/rust-lang/crates.io-index" 764 | checksum = "0237232789cf037d5480773fe568aac745bfe2afbc11a863e97901780a6b47cc" 765 | 766 | [[package]] 767 | name = "when-cli" 768 | version = "0.4.0" 769 | dependencies = [ 770 | "anyhow", 771 | "chrono", 772 | "chrono-tz", 773 | "clap", 774 | "console", 775 | "libwhen", 776 | "localzone", 777 | "pest", 778 | "pest_derive", 779 | "serde", 780 | "serde_json", 781 | ] 782 | 783 | [[package]] 784 | name = "winapi" 785 | version = "0.3.9" 786 | source = "registry+https://github.com/rust-lang/crates.io-index" 787 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 788 | dependencies = [ 789 | "winapi-i686-pc-windows-gnu", 790 | "winapi-x86_64-pc-windows-gnu", 791 | ] 792 | 793 | [[package]] 794 | name = "winapi-i686-pc-windows-gnu" 795 | version = "0.4.0" 796 | source = "registry+https://github.com/rust-lang/crates.io-index" 797 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 798 | 799 | [[package]] 800 | name = "winapi-util" 801 | version = "0.1.5" 802 | source = "registry+https://github.com/rust-lang/crates.io-index" 803 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 804 | dependencies = [ 805 | "winapi", 806 | ] 807 | 808 | [[package]] 809 | name = "winapi-x86_64-pc-windows-gnu" 810 | version = "0.4.0" 811 | source = "registry+https://github.com/rust-lang/crates.io-index" 812 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 813 | 814 | [[package]] 815 | name = "windows" 816 | version = "0.28.0" 817 | source = "registry+https://github.com/rust-lang/crates.io-index" 818 | checksum = "054d31561409bbf7e1ee4a4f0a1233ac2bb79cfadf2a398438a04d8dda69225f" 819 | dependencies = [ 820 | "windows-sys", 821 | ] 822 | 823 | [[package]] 824 | name = "windows-sys" 825 | version = "0.28.0" 826 | source = "registry+https://github.com/rust-lang/crates.io-index" 827 | checksum = "82ca39602d5cbfa692c4b67e3bcbb2751477355141c1ed434c94da4186836ff6" 828 | dependencies = [ 829 | "windows_aarch64_msvc", 830 | "windows_i686_gnu", 831 | "windows_i686_msvc", 832 | "windows_x86_64_gnu", 833 | "windows_x86_64_msvc", 834 | ] 835 | 836 | [[package]] 837 | name = "windows_aarch64_msvc" 838 | version = "0.28.0" 839 | source = "registry+https://github.com/rust-lang/crates.io-index" 840 | checksum = "52695a41e536859d5308cc613b4a022261a274390b25bd29dfff4bf08505f3c2" 841 | 842 | [[package]] 843 | name = "windows_i686_gnu" 844 | version = "0.28.0" 845 | source = "registry+https://github.com/rust-lang/crates.io-index" 846 | checksum = "f54725ac23affef038fecb177de6c9bf065787c2f432f79e3c373da92f3e1d8a" 847 | 848 | [[package]] 849 | name = "windows_i686_msvc" 850 | version = "0.28.0" 851 | source = "registry+https://github.com/rust-lang/crates.io-index" 852 | checksum = "51d5158a43cc43623c0729d1ad6647e62fa384a3d135fd15108d37c683461f64" 853 | 854 | [[package]] 855 | name = "windows_x86_64_gnu" 856 | version = "0.28.0" 857 | source = "registry+https://github.com/rust-lang/crates.io-index" 858 | checksum = "bc31f409f565611535130cfe7ee8e6655d3fa99c1c61013981e491921b5ce954" 859 | 860 | [[package]] 861 | name = "windows_x86_64_msvc" 862 | version = "0.28.0" 863 | source = "registry+https://github.com/rust-lang/crates.io-index" 864 | checksum = "3f2b8c7cbd3bfdddd9ab98769f9746a7fad1bca236554cd032b78d768bc0e89f" 865 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = ["libwhen", "cli"] 3 | 4 | [profile.release] 5 | opt-level = "s" 6 | lto = true 7 | codegen-units = 1 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: build 2 | 3 | build: 4 | @(cd cli; cargo build --all-features) 5 | 6 | test: 7 | @cargo test --all 8 | 9 | release: 10 | @(cd cli; cargo build --release) 11 | 12 | install: 13 | @cargo install --path=./cli 14 | 15 | format: 16 | @rustup component add rustfmt 2> /dev/null 17 | @cargo fmt --all 18 | 19 | format-check: 20 | @rustup component add rustfmt 2> /dev/null 21 | @cargo fmt --all -- --check 22 | 23 | lint: 24 | @rustup component add clippy 2> /dev/null 25 | @cargo clippy 26 | 27 | web-dev: 28 | @cd web; wasm-pack build 29 | @cd web/www; npm run start 30 | 31 | web-dist: 32 | @rm -rf web/www/dist 33 | @cd web; wasm-pack build --release 34 | @cd web/www; npm run build 35 | 36 | .PHONY: all test format format-check lint clean-data build release web-dev web-dist 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | example 2 | 3 |
4 |

when: a timezone utility for the command line

5 |
6 | 7 | [![Build Status](https://github.com/mitsuhiko/when/workflows/Tests/badge.svg?branch=main)](https://github.com/mitsuhiko/when/actions?query=workflow%3ATests) 8 | [![Crates.io](https://img.shields.io/crates/d/when-cli.svg)](https://crates.io/crates/when-cli) 9 | [![License](https://img.shields.io/github/license/mitsuhiko/when)](https://github.com/mitsuhiko/when/blob/main/LICENSE) 10 | 11 | ``` 12 | $ when "now in vienna" 13 | ``` 14 | 15 | `when` is a small utility which tells you what time it is somewhere or what some time is somewhere. 16 | You can use it from the command line or [uses it online from the browser](https://mitsuhiko.github.io/when/). 17 | 18 | **These are some input examples**: 19 | 20 | * `now` 21 | * `2 hours ago in yyz` 22 | * `5pm in yyz -> sfo` 23 | * `5pm in vienna -> london` 24 | * `4pm on 17.05.2021 in vienna -> tokyo` 25 | * `4pm yesterday in vienna -> vienna va` 26 | * `in 4 hours in san francisco` 27 | * `2pm in 2 days in new delhi` 28 | * `now in yyz -> sfo -> vie -> lhr` 29 | * `unix 1639067620 in tokyo` 30 | 31 | ## Installation 32 | 33 | Conveniently via cargo: 34 | 35 | ``` 36 | $ cargo install when-cli 37 | ``` 38 | 39 | There is also an [online version](https://mitsuhiko.github.io/when/) you can use 40 | from your browser. 41 | 42 | Note that this project requires a Rust 2021 compatible compiler (1.56.0 or 43 | later). Attempting to install this package on an older compiler will result 44 | in compilation errors (``feature `edition2021` is required``). If you're 45 | using rustup make sure to update (`rustup update`), you might be on an older 46 | version. 47 | 48 | ## Usage 49 | 50 | Basically takes a single argument which is a string which describes the format 51 | in roughly this syntax. Both locations are optional. The "local" location always 52 | refers to the current machine's timezone. 53 | 54 | ``` 55 | time and date in location -> other location 56 | ``` 57 | 58 | Multiple locations can be suplied by using the arrow operator multiple times. This 59 | means you can do things like `now in yyz -> sfo -> vie`. 60 | 61 | Time and date can be provided roughly like this: 62 | 63 | * `2:30pm`, `14:30`, `7:00`, `now` 64 | * `14:30 tomorrow` 65 | * `14:30` 66 | * `17:00 on 20.05.` (DD.MM.) 67 | * `17:00 on 20.05.2020` (DD.MM.YYYY) 68 | * relative times (`in 4 hours` or `4 hours ago`) 69 | * unix timestamps (`unix:TS` or `unix TS`) 70 | 71 | For locations many major cities are supported as well as common timezone names 72 | like `Europe/Vienna`. A certain amount of disambiugation is possible with city 73 | names. For instance `Vienna VA` (Virginia) is different than `Vienna AT` 74 | (Austria). 75 | -------------------------------------------------------------------------------- /assets/hello.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitsuhiko/when/4cc12ce4dcd5f1a155ecb866c70809b2332b5172/assets/hello.png -------------------------------------------------------------------------------- /cli/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "anyhow" 7 | version = "1.0.51" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "8b26702f315f53b6071259e15dd9d64528213b44d61de1ec926eca7715d62203" 10 | 11 | [[package]] 12 | name = "atty" 13 | version = "0.2.14" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 16 | dependencies = [ 17 | "hermit-abi", 18 | "libc", 19 | "winapi", 20 | ] 21 | 22 | [[package]] 23 | name = "autocfg" 24 | version = "1.0.1" 25 | source = "registry+https://github.com/rust-lang/crates.io-index" 26 | checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" 27 | 28 | [[package]] 29 | name = "bitflags" 30 | version = "1.3.2" 31 | source = "registry+https://github.com/rust-lang/crates.io-index" 32 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 33 | 34 | [[package]] 35 | name = "block-buffer" 36 | version = "0.7.3" 37 | source = "registry+https://github.com/rust-lang/crates.io-index" 38 | checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" 39 | dependencies = [ 40 | "block-padding", 41 | "byte-tools", 42 | "byteorder", 43 | "generic-array", 44 | ] 45 | 46 | [[package]] 47 | name = "block-padding" 48 | version = "0.1.5" 49 | source = "registry+https://github.com/rust-lang/crates.io-index" 50 | checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" 51 | dependencies = [ 52 | "byte-tools", 53 | ] 54 | 55 | [[package]] 56 | name = "bumpalo" 57 | version = "3.8.0" 58 | source = "registry+https://github.com/rust-lang/crates.io-index" 59 | checksum = "8f1e260c3a9040a7c19a12468758f4c16f31a81a1fe087482be9570ec864bb6c" 60 | 61 | [[package]] 62 | name = "byte-tools" 63 | version = "0.3.1" 64 | source = "registry+https://github.com/rust-lang/crates.io-index" 65 | checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" 66 | 67 | [[package]] 68 | name = "byteorder" 69 | version = "1.4.3" 70 | source = "registry+https://github.com/rust-lang/crates.io-index" 71 | checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" 72 | 73 | [[package]] 74 | name = "cfg-if" 75 | version = "1.0.0" 76 | source = "registry+https://github.com/rust-lang/crates.io-index" 77 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 78 | 79 | [[package]] 80 | name = "chrono" 81 | version = "0.4.19" 82 | source = "registry+https://github.com/rust-lang/crates.io-index" 83 | checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" 84 | dependencies = [ 85 | "libc", 86 | "num-integer", 87 | "num-traits", 88 | "serde", 89 | "time", 90 | "winapi", 91 | ] 92 | 93 | [[package]] 94 | name = "chrono-tz" 95 | version = "0.6.1" 96 | source = "registry+https://github.com/rust-lang/crates.io-index" 97 | checksum = "58549f1842da3080ce63002102d5bc954c7bc843d4f47818e642abdc36253552" 98 | dependencies = [ 99 | "chrono", 100 | "chrono-tz-build", 101 | "phf", 102 | ] 103 | 104 | [[package]] 105 | name = "chrono-tz-build" 106 | version = "0.0.2" 107 | source = "registry+https://github.com/rust-lang/crates.io-index" 108 | checksum = "db058d493fb2f65f41861bfed7e3fe6335264a9f0f92710cab5bdf01fef09069" 109 | dependencies = [ 110 | "parse-zoneinfo", 111 | "phf", 112 | "phf_codegen", 113 | ] 114 | 115 | [[package]] 116 | name = "clap" 117 | version = "3.0.0-rc.3" 118 | source = "registry+https://github.com/rust-lang/crates.io-index" 119 | checksum = "098d281b47bf725a0bddd829e0070ee76560faab8af123050a86c440d7f0a1fd" 120 | dependencies = [ 121 | "atty", 122 | "bitflags", 123 | "clap_derive", 124 | "indexmap", 125 | "lazy_static", 126 | "os_str_bytes", 127 | "strsim", 128 | "termcolor", 129 | "terminal_size", 130 | "textwrap", 131 | ] 132 | 133 | [[package]] 134 | name = "clap_derive" 135 | version = "3.0.0-rc.3" 136 | source = "registry+https://github.com/rust-lang/crates.io-index" 137 | checksum = "26de8102ffb96701066cea36f9a104285b67fbcc302a520640289d476c15ed8a" 138 | dependencies = [ 139 | "heck", 140 | "proc-macro-error", 141 | "proc-macro2", 142 | "quote", 143 | "syn", 144 | ] 145 | 146 | [[package]] 147 | name = "console" 148 | version = "0.15.0" 149 | source = "registry+https://github.com/rust-lang/crates.io-index" 150 | checksum = "a28b32d32ca44b70c3e4acd7db1babf555fa026e385fb95f18028f88848b3c31" 151 | dependencies = [ 152 | "encode_unicode", 153 | "libc", 154 | "once_cell", 155 | "regex", 156 | "terminal_size", 157 | "unicode-width", 158 | "winapi", 159 | ] 160 | 161 | [[package]] 162 | name = "digest" 163 | version = "0.8.1" 164 | source = "registry+https://github.com/rust-lang/crates.io-index" 165 | checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" 166 | dependencies = [ 167 | "generic-array", 168 | ] 169 | 170 | [[package]] 171 | name = "encode_unicode" 172 | version = "0.3.6" 173 | source = "registry+https://github.com/rust-lang/crates.io-index" 174 | checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" 175 | 176 | [[package]] 177 | name = "fake-simd" 178 | version = "0.1.2" 179 | source = "registry+https://github.com/rust-lang/crates.io-index" 180 | checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" 181 | 182 | [[package]] 183 | name = "generic-array" 184 | version = "0.12.4" 185 | source = "registry+https://github.com/rust-lang/crates.io-index" 186 | checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd" 187 | dependencies = [ 188 | "typenum", 189 | ] 190 | 191 | [[package]] 192 | name = "getrandom" 193 | version = "0.2.3" 194 | source = "registry+https://github.com/rust-lang/crates.io-index" 195 | checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" 196 | dependencies = [ 197 | "cfg-if", 198 | "libc", 199 | "wasi", 200 | ] 201 | 202 | [[package]] 203 | name = "hashbrown" 204 | version = "0.11.2" 205 | source = "registry+https://github.com/rust-lang/crates.io-index" 206 | checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" 207 | 208 | [[package]] 209 | name = "heck" 210 | version = "0.3.3" 211 | source = "registry+https://github.com/rust-lang/crates.io-index" 212 | checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" 213 | dependencies = [ 214 | "unicode-segmentation", 215 | ] 216 | 217 | [[package]] 218 | name = "hermit-abi" 219 | version = "0.1.20" 220 | source = "registry+https://github.com/rust-lang/crates.io-index" 221 | checksum = "c7a30908dbce072eca83216eab939d2290080e00ca71611b96a09e5cdce5f3fa" 222 | dependencies = [ 223 | "libc", 224 | ] 225 | 226 | [[package]] 227 | name = "indexmap" 228 | version = "1.7.0" 229 | source = "registry+https://github.com/rust-lang/crates.io-index" 230 | checksum = "bc633605454125dec4b66843673f01c7df2b89479b32e0ed634e43a91cff62a5" 231 | dependencies = [ 232 | "autocfg", 233 | "hashbrown", 234 | ] 235 | 236 | [[package]] 237 | name = "itoa" 238 | version = "0.4.8" 239 | source = "registry+https://github.com/rust-lang/crates.io-index" 240 | checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" 241 | 242 | [[package]] 243 | name = "js-sys" 244 | version = "0.3.55" 245 | source = "registry+https://github.com/rust-lang/crates.io-index" 246 | checksum = "7cc9ffccd38c451a86bf13657df244e9c3f37493cce8e5e21e940963777acc84" 247 | dependencies = [ 248 | "wasm-bindgen", 249 | ] 250 | 251 | [[package]] 252 | name = "lazy_static" 253 | version = "1.4.0" 254 | source = "registry+https://github.com/rust-lang/crates.io-index" 255 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 256 | 257 | [[package]] 258 | name = "libc" 259 | version = "0.2.110" 260 | source = "registry+https://github.com/rust-lang/crates.io-index" 261 | checksum = "b58a4469763e4e3a906c4ed786e1c70512d16aa88f84dded826da42640fc6a1c" 262 | 263 | [[package]] 264 | name = "localzone" 265 | version = "0.2.0" 266 | source = "registry+https://github.com/rust-lang/crates.io-index" 267 | checksum = "7865f5a13ecb548180b8fe7e538d739090329fdb984eef9e9a16db214f216c6f" 268 | dependencies = [ 269 | "js-sys", 270 | "windows", 271 | ] 272 | 273 | [[package]] 274 | name = "log" 275 | version = "0.4.14" 276 | source = "registry+https://github.com/rust-lang/crates.io-index" 277 | checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" 278 | dependencies = [ 279 | "cfg-if", 280 | ] 281 | 282 | [[package]] 283 | name = "maplit" 284 | version = "1.0.2" 285 | source = "registry+https://github.com/rust-lang/crates.io-index" 286 | checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" 287 | 288 | [[package]] 289 | name = "memchr" 290 | version = "2.4.1" 291 | source = "registry+https://github.com/rust-lang/crates.io-index" 292 | checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" 293 | 294 | [[package]] 295 | name = "num-integer" 296 | version = "0.1.44" 297 | source = "registry+https://github.com/rust-lang/crates.io-index" 298 | checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" 299 | dependencies = [ 300 | "autocfg", 301 | "num-traits", 302 | ] 303 | 304 | [[package]] 305 | name = "num-traits" 306 | version = "0.2.14" 307 | source = "registry+https://github.com/rust-lang/crates.io-index" 308 | checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" 309 | dependencies = [ 310 | "autocfg", 311 | ] 312 | 313 | [[package]] 314 | name = "once_cell" 315 | version = "1.8.0" 316 | source = "registry+https://github.com/rust-lang/crates.io-index" 317 | checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" 318 | 319 | [[package]] 320 | name = "opaque-debug" 321 | version = "0.2.3" 322 | source = "registry+https://github.com/rust-lang/crates.io-index" 323 | checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" 324 | 325 | [[package]] 326 | name = "os_str_bytes" 327 | version = "6.0.0" 328 | source = "registry+https://github.com/rust-lang/crates.io-index" 329 | checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64" 330 | dependencies = [ 331 | "memchr", 332 | ] 333 | 334 | [[package]] 335 | name = "parse-zoneinfo" 336 | version = "0.3.0" 337 | source = "registry+https://github.com/rust-lang/crates.io-index" 338 | checksum = "c705f256449c60da65e11ff6626e0c16a0a0b96aaa348de61376b249bc340f41" 339 | dependencies = [ 340 | "regex", 341 | ] 342 | 343 | [[package]] 344 | name = "pest" 345 | version = "2.1.3" 346 | source = "registry+https://github.com/rust-lang/crates.io-index" 347 | checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53" 348 | dependencies = [ 349 | "ucd-trie", 350 | ] 351 | 352 | [[package]] 353 | name = "pest_derive" 354 | version = "2.1.0" 355 | source = "registry+https://github.com/rust-lang/crates.io-index" 356 | checksum = "833d1ae558dc601e9a60366421196a8d94bc0ac980476d0b67e1d0988d72b2d0" 357 | dependencies = [ 358 | "pest", 359 | "pest_generator", 360 | ] 361 | 362 | [[package]] 363 | name = "pest_generator" 364 | version = "2.1.3" 365 | source = "registry+https://github.com/rust-lang/crates.io-index" 366 | checksum = "99b8db626e31e5b81787b9783425769681b347011cc59471e33ea46d2ea0cf55" 367 | dependencies = [ 368 | "pest", 369 | "pest_meta", 370 | "proc-macro2", 371 | "quote", 372 | "syn", 373 | ] 374 | 375 | [[package]] 376 | name = "pest_meta" 377 | version = "2.1.3" 378 | source = "registry+https://github.com/rust-lang/crates.io-index" 379 | checksum = "54be6e404f5317079812fc8f9f5279de376d8856929e21c184ecf6bbd692a11d" 380 | dependencies = [ 381 | "maplit", 382 | "pest", 383 | "sha-1", 384 | ] 385 | 386 | [[package]] 387 | name = "phf" 388 | version = "0.10.0" 389 | source = "registry+https://github.com/rust-lang/crates.io-index" 390 | checksum = "b9fc3db1018c4b59d7d582a739436478b6035138b6aecbce989fc91c3e98409f" 391 | dependencies = [ 392 | "phf_shared", 393 | ] 394 | 395 | [[package]] 396 | name = "phf_codegen" 397 | version = "0.10.0" 398 | source = "registry+https://github.com/rust-lang/crates.io-index" 399 | checksum = "4fb1c3a8bc4dd4e5cfce29b44ffc14bedd2ee294559a294e2a4d4c9e9a6a13cd" 400 | dependencies = [ 401 | "phf_generator", 402 | "phf_shared", 403 | ] 404 | 405 | [[package]] 406 | name = "phf_generator" 407 | version = "0.10.0" 408 | source = "registry+https://github.com/rust-lang/crates.io-index" 409 | checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" 410 | dependencies = [ 411 | "phf_shared", 412 | "rand", 413 | ] 414 | 415 | [[package]] 416 | name = "phf_shared" 417 | version = "0.10.0" 418 | source = "registry+https://github.com/rust-lang/crates.io-index" 419 | checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" 420 | dependencies = [ 421 | "siphasher", 422 | "uncased", 423 | ] 424 | 425 | [[package]] 426 | name = "ppv-lite86" 427 | version = "0.2.15" 428 | source = "registry+https://github.com/rust-lang/crates.io-index" 429 | checksum = "ed0cfbc8191465bed66e1718596ee0b0b35d5ee1f41c5df2189d0fe8bde535ba" 430 | 431 | [[package]] 432 | name = "proc-macro-error" 433 | version = "1.0.4" 434 | source = "registry+https://github.com/rust-lang/crates.io-index" 435 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 436 | dependencies = [ 437 | "proc-macro-error-attr", 438 | "proc-macro2", 439 | "quote", 440 | "syn", 441 | "version_check", 442 | ] 443 | 444 | [[package]] 445 | name = "proc-macro-error-attr" 446 | version = "1.0.4" 447 | source = "registry+https://github.com/rust-lang/crates.io-index" 448 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 449 | dependencies = [ 450 | "proc-macro2", 451 | "quote", 452 | "version_check", 453 | ] 454 | 455 | [[package]] 456 | name = "proc-macro2" 457 | version = "1.0.33" 458 | source = "registry+https://github.com/rust-lang/crates.io-index" 459 | checksum = "fb37d2df5df740e582f28f8560cf425f52bb267d872fe58358eadb554909f07a" 460 | dependencies = [ 461 | "unicode-xid", 462 | ] 463 | 464 | [[package]] 465 | name = "quote" 466 | version = "1.0.10" 467 | source = "registry+https://github.com/rust-lang/crates.io-index" 468 | checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05" 469 | dependencies = [ 470 | "proc-macro2", 471 | ] 472 | 473 | [[package]] 474 | name = "rand" 475 | version = "0.8.4" 476 | source = "registry+https://github.com/rust-lang/crates.io-index" 477 | checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" 478 | dependencies = [ 479 | "libc", 480 | "rand_chacha", 481 | "rand_core", 482 | "rand_hc", 483 | ] 484 | 485 | [[package]] 486 | name = "rand_chacha" 487 | version = "0.3.1" 488 | source = "registry+https://github.com/rust-lang/crates.io-index" 489 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 490 | dependencies = [ 491 | "ppv-lite86", 492 | "rand_core", 493 | ] 494 | 495 | [[package]] 496 | name = "rand_core" 497 | version = "0.6.3" 498 | source = "registry+https://github.com/rust-lang/crates.io-index" 499 | checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" 500 | dependencies = [ 501 | "getrandom", 502 | ] 503 | 504 | [[package]] 505 | name = "rand_hc" 506 | version = "0.3.1" 507 | source = "registry+https://github.com/rust-lang/crates.io-index" 508 | checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7" 509 | dependencies = [ 510 | "rand_core", 511 | ] 512 | 513 | [[package]] 514 | name = "regex" 515 | version = "1.5.4" 516 | source = "registry+https://github.com/rust-lang/crates.io-index" 517 | checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" 518 | dependencies = [ 519 | "regex-syntax", 520 | ] 521 | 522 | [[package]] 523 | name = "regex-syntax" 524 | version = "0.6.25" 525 | source = "registry+https://github.com/rust-lang/crates.io-index" 526 | checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" 527 | 528 | [[package]] 529 | name = "ryu" 530 | version = "1.0.6" 531 | source = "registry+https://github.com/rust-lang/crates.io-index" 532 | checksum = "3c9613b5a66ab9ba26415184cfc41156594925a9cf3a2057e57f31ff145f6568" 533 | 534 | [[package]] 535 | name = "serde" 536 | version = "1.0.131" 537 | source = "registry+https://github.com/rust-lang/crates.io-index" 538 | checksum = "b4ad69dfbd3e45369132cc64e6748c2d65cdfb001a2b1c232d128b4ad60561c1" 539 | dependencies = [ 540 | "serde_derive", 541 | ] 542 | 543 | [[package]] 544 | name = "serde_derive" 545 | version = "1.0.131" 546 | source = "registry+https://github.com/rust-lang/crates.io-index" 547 | checksum = "b710a83c4e0dff6a3d511946b95274ad9ca9e5d3ae497b63fda866ac955358d2" 548 | dependencies = [ 549 | "proc-macro2", 550 | "quote", 551 | "syn", 552 | ] 553 | 554 | [[package]] 555 | name = "serde_json" 556 | version = "1.0.72" 557 | source = "registry+https://github.com/rust-lang/crates.io-index" 558 | checksum = "d0ffa0837f2dfa6fb90868c2b5468cad482e175f7dad97e7421951e663f2b527" 559 | dependencies = [ 560 | "itoa", 561 | "ryu", 562 | "serde", 563 | ] 564 | 565 | [[package]] 566 | name = "sha-1" 567 | version = "0.8.2" 568 | source = "registry+https://github.com/rust-lang/crates.io-index" 569 | checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df" 570 | dependencies = [ 571 | "block-buffer", 572 | "digest", 573 | "fake-simd", 574 | "opaque-debug", 575 | ] 576 | 577 | [[package]] 578 | name = "siphasher" 579 | version = "0.3.7" 580 | source = "registry+https://github.com/rust-lang/crates.io-index" 581 | checksum = "533494a8f9b724d33625ab53c6c4800f7cc445895924a8ef649222dcb76e938b" 582 | 583 | [[package]] 584 | name = "strsim" 585 | version = "0.10.0" 586 | source = "registry+https://github.com/rust-lang/crates.io-index" 587 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 588 | 589 | [[package]] 590 | name = "syn" 591 | version = "1.0.82" 592 | source = "registry+https://github.com/rust-lang/crates.io-index" 593 | checksum = "8daf5dd0bb60cbd4137b1b587d2fc0ae729bc07cf01cd70b36a1ed5ade3b9d59" 594 | dependencies = [ 595 | "proc-macro2", 596 | "quote", 597 | "unicode-xid", 598 | ] 599 | 600 | [[package]] 601 | name = "termcolor" 602 | version = "1.1.2" 603 | source = "registry+https://github.com/rust-lang/crates.io-index" 604 | checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" 605 | dependencies = [ 606 | "winapi-util", 607 | ] 608 | 609 | [[package]] 610 | name = "terminal_size" 611 | version = "0.1.17" 612 | source = "registry+https://github.com/rust-lang/crates.io-index" 613 | checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df" 614 | dependencies = [ 615 | "libc", 616 | "winapi", 617 | ] 618 | 619 | [[package]] 620 | name = "textwrap" 621 | version = "0.14.2" 622 | source = "registry+https://github.com/rust-lang/crates.io-index" 623 | checksum = "0066c8d12af8b5acd21e00547c3797fde4e8677254a7ee429176ccebbe93dd80" 624 | dependencies = [ 625 | "terminal_size", 626 | ] 627 | 628 | [[package]] 629 | name = "time" 630 | version = "0.1.44" 631 | source = "registry+https://github.com/rust-lang/crates.io-index" 632 | checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" 633 | dependencies = [ 634 | "libc", 635 | "wasi", 636 | "winapi", 637 | ] 638 | 639 | [[package]] 640 | name = "typenum" 641 | version = "1.14.0" 642 | source = "registry+https://github.com/rust-lang/crates.io-index" 643 | checksum = "b63708a265f51345575b27fe43f9500ad611579e764c79edbc2037b1121959ec" 644 | 645 | [[package]] 646 | name = "ucd-trie" 647 | version = "0.1.3" 648 | source = "registry+https://github.com/rust-lang/crates.io-index" 649 | checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" 650 | 651 | [[package]] 652 | name = "uncased" 653 | version = "0.9.6" 654 | source = "registry+https://github.com/rust-lang/crates.io-index" 655 | checksum = "5baeed7327e25054889b9bd4f975f32e5f4c5d434042d59ab6cd4142c0a76ed0" 656 | dependencies = [ 657 | "version_check", 658 | ] 659 | 660 | [[package]] 661 | name = "unicode-segmentation" 662 | version = "1.8.0" 663 | source = "registry+https://github.com/rust-lang/crates.io-index" 664 | checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b" 665 | 666 | [[package]] 667 | name = "unicode-width" 668 | version = "0.1.9" 669 | source = "registry+https://github.com/rust-lang/crates.io-index" 670 | checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" 671 | 672 | [[package]] 673 | name = "unicode-xid" 674 | version = "0.2.2" 675 | source = "registry+https://github.com/rust-lang/crates.io-index" 676 | checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" 677 | 678 | [[package]] 679 | name = "version_check" 680 | version = "0.9.3" 681 | source = "registry+https://github.com/rust-lang/crates.io-index" 682 | checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" 683 | 684 | [[package]] 685 | name = "wasi" 686 | version = "0.10.0+wasi-snapshot-preview1" 687 | source = "registry+https://github.com/rust-lang/crates.io-index" 688 | checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" 689 | 690 | [[package]] 691 | name = "wasm-bindgen" 692 | version = "0.2.78" 693 | source = "registry+https://github.com/rust-lang/crates.io-index" 694 | checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce" 695 | dependencies = [ 696 | "cfg-if", 697 | "wasm-bindgen-macro", 698 | ] 699 | 700 | [[package]] 701 | name = "wasm-bindgen-backend" 702 | version = "0.2.78" 703 | source = "registry+https://github.com/rust-lang/crates.io-index" 704 | checksum = "a317bf8f9fba2476b4b2c85ef4c4af8ff39c3c7f0cdfeed4f82c34a880aa837b" 705 | dependencies = [ 706 | "bumpalo", 707 | "lazy_static", 708 | "log", 709 | "proc-macro2", 710 | "quote", 711 | "syn", 712 | "wasm-bindgen-shared", 713 | ] 714 | 715 | [[package]] 716 | name = "wasm-bindgen-macro" 717 | version = "0.2.78" 718 | source = "registry+https://github.com/rust-lang/crates.io-index" 719 | checksum = "d56146e7c495528bf6587663bea13a8eb588d39b36b679d83972e1a2dbbdacf9" 720 | dependencies = [ 721 | "quote", 722 | "wasm-bindgen-macro-support", 723 | ] 724 | 725 | [[package]] 726 | name = "wasm-bindgen-macro-support" 727 | version = "0.2.78" 728 | source = "registry+https://github.com/rust-lang/crates.io-index" 729 | checksum = "7803e0eea25835f8abdc585cd3021b3deb11543c6fe226dcd30b228857c5c5ab" 730 | dependencies = [ 731 | "proc-macro2", 732 | "quote", 733 | "syn", 734 | "wasm-bindgen-backend", 735 | "wasm-bindgen-shared", 736 | ] 737 | 738 | [[package]] 739 | name = "wasm-bindgen-shared" 740 | version = "0.2.78" 741 | source = "registry+https://github.com/rust-lang/crates.io-index" 742 | checksum = "0237232789cf037d5480773fe568aac745bfe2afbc11a863e97901780a6b47cc" 743 | 744 | [[package]] 745 | name = "when-cli" 746 | version = "0.3.0" 747 | dependencies = [ 748 | "anyhow", 749 | "chrono", 750 | "chrono-tz", 751 | "clap", 752 | "console", 753 | "localzone", 754 | "pest", 755 | "pest_derive", 756 | "serde", 757 | "serde_json", 758 | ] 759 | 760 | [[package]] 761 | name = "winapi" 762 | version = "0.3.9" 763 | source = "registry+https://github.com/rust-lang/crates.io-index" 764 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 765 | dependencies = [ 766 | "winapi-i686-pc-windows-gnu", 767 | "winapi-x86_64-pc-windows-gnu", 768 | ] 769 | 770 | [[package]] 771 | name = "winapi-i686-pc-windows-gnu" 772 | version = "0.4.0" 773 | source = "registry+https://github.com/rust-lang/crates.io-index" 774 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 775 | 776 | [[package]] 777 | name = "winapi-util" 778 | version = "0.1.5" 779 | source = "registry+https://github.com/rust-lang/crates.io-index" 780 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 781 | dependencies = [ 782 | "winapi", 783 | ] 784 | 785 | [[package]] 786 | name = "winapi-x86_64-pc-windows-gnu" 787 | version = "0.4.0" 788 | source = "registry+https://github.com/rust-lang/crates.io-index" 789 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 790 | 791 | [[package]] 792 | name = "windows" 793 | version = "0.28.0" 794 | source = "registry+https://github.com/rust-lang/crates.io-index" 795 | checksum = "054d31561409bbf7e1ee4a4f0a1233ac2bb79cfadf2a398438a04d8dda69225f" 796 | dependencies = [ 797 | "windows-sys", 798 | ] 799 | 800 | [[package]] 801 | name = "windows-sys" 802 | version = "0.28.0" 803 | source = "registry+https://github.com/rust-lang/crates.io-index" 804 | checksum = "82ca39602d5cbfa692c4b67e3bcbb2751477355141c1ed434c94da4186836ff6" 805 | dependencies = [ 806 | "windows_aarch64_msvc", 807 | "windows_i686_gnu", 808 | "windows_i686_msvc", 809 | "windows_x86_64_gnu", 810 | "windows_x86_64_msvc", 811 | ] 812 | 813 | [[package]] 814 | name = "windows_aarch64_msvc" 815 | version = "0.28.0" 816 | source = "registry+https://github.com/rust-lang/crates.io-index" 817 | checksum = "52695a41e536859d5308cc613b4a022261a274390b25bd29dfff4bf08505f3c2" 818 | 819 | [[package]] 820 | name = "windows_i686_gnu" 821 | version = "0.28.0" 822 | source = "registry+https://github.com/rust-lang/crates.io-index" 823 | checksum = "f54725ac23affef038fecb177de6c9bf065787c2f432f79e3c373da92f3e1d8a" 824 | 825 | [[package]] 826 | name = "windows_i686_msvc" 827 | version = "0.28.0" 828 | source = "registry+https://github.com/rust-lang/crates.io-index" 829 | checksum = "51d5158a43cc43623c0729d1ad6647e62fa384a3d135fd15108d37c683461f64" 830 | 831 | [[package]] 832 | name = "windows_x86_64_gnu" 833 | version = "0.28.0" 834 | source = "registry+https://github.com/rust-lang/crates.io-index" 835 | checksum = "bc31f409f565611535130cfe7ee8e6655d3fa99c1c61013981e491921b5ce954" 836 | 837 | [[package]] 838 | name = "windows_x86_64_msvc" 839 | version = "0.28.0" 840 | source = "registry+https://github.com/rust-lang/crates.io-index" 841 | checksum = "3f2b8c7cbd3bfdddd9ab98769f9746a7fad1bca236554cd032b78d768bc0e89f" 842 | -------------------------------------------------------------------------------- /cli/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "when-cli" 3 | version = "0.4.0" 4 | edition = "2021" 5 | authors = ["Armin Ronacher "] 6 | license = "Apache-2.0" 7 | description = "a command line tool for converting times between timezones" 8 | repository = "https://github.com/mitsuhiko/when" 9 | keywords = ["timezone", "convert", "cli"] 10 | readme = "README.md" 11 | rust-version = "1.56.0" 12 | 13 | [[bin]] 14 | name = "when" 15 | path = "src/main.rs" 16 | 17 | [dependencies] 18 | libwhen = { version = "0.4.0", path = "../libwhen" } 19 | anyhow = "1.0.51" 20 | chrono = { version = "0.4.19", features = ["serde"] } 21 | chrono-tz = "0.6.1" 22 | clap = { version = "3.0.0-rc.0", features = ["color", "derive", "cargo", "wrap_help"] } 23 | console = "0.15.0" 24 | localzone = "0.2.0" 25 | pest = "2.1.3" 26 | pest_derive = "2.1.0" 27 | serde = { version = "1.0.130", features = ["derive"] } 28 | serde_json = "1.0.72" 29 | -------------------------------------------------------------------------------- /cli/LICENSE: -------------------------------------------------------------------------------- 1 | ../LICENSE -------------------------------------------------------------------------------- /cli/README.md: -------------------------------------------------------------------------------- 1 | ../README.md -------------------------------------------------------------------------------- /cli/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use anyhow::bail; 4 | use chrono::{DateTime, Utc}; 5 | use chrono_tz::Tz; 6 | use clap::Parser; 7 | use console::style; 8 | 9 | use libwhen::{get_time_of_day, InputExpr, LocationKind, TimeAtLocation}; 10 | 11 | /// A small utility to convert times from the command line. 12 | /// 13 | /// When takes a time and date expression and helps converting it into 14 | /// different timezones. If no arguments are supplied the current time 15 | /// in the current location is returned. 16 | /// 17 | /// The basic syntax for the expression is "time_spec [in location_spec]". 18 | /// Translations between locations is done by using the "->" operator. 19 | /// 20 | /// For instance "2pm in vie -> yyz" takes 14:00 in vienna time and 21 | /// translates it to toronto (airport). It then prints out both 22 | /// timestamps on stdout with additional information. 23 | /// 24 | /// For more examples see https://github.com/mitsuhiko/when 25 | #[derive(Parser)] 26 | #[clap(version = clap::crate_version!(), max_term_width = 100)] 27 | struct Cli { 28 | /// use short output. 29 | /// 30 | /// When short output is enabled one line per timezone is returned. 31 | #[clap(short = 's', long = "short")] 32 | short: bool, 33 | 34 | /// controls when to use colors. Choices are `auto`, `never`, `always`. 35 | #[clap(long = "colors")] 36 | colors: Option, 37 | 38 | /// output in JSON format. 39 | #[clap(long = "json")] 40 | json: bool, 41 | 42 | /// returns a list of all known IANA/Olson timezones. 43 | #[clap(long = "list-timezones")] 44 | list_timezones: bool, 45 | 46 | /// the input expression to evaluate. 47 | /// 48 | /// If this is not supplied then "now" is assumed to return the time 49 | /// in the current timezone. 50 | expr: Option, 51 | } 52 | 53 | pub struct ZoneOffset(DateTime); 54 | 55 | impl fmt::Display for ZoneOffset { 56 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 57 | let abbrev = self.0.format("%Z").to_string(); 58 | if abbrev.chars().all(|x| x.is_ascii_alphabetic()) { 59 | write!(f, "{}; {}", abbrev, self.0.format("%z"))? 60 | } else { 61 | write!(f, "{}", self.0.format("%z"))? 62 | } 63 | Ok(()) 64 | } 65 | } 66 | 67 | fn print_date(tod: &TimeAtLocation, now: DateTime) { 68 | let date = tod.datetime(); 69 | let zone = tod.zone(); 70 | let adjusted = date.with_timezone(&zone.tz()); 71 | println!( 72 | "time: {} ({}; {})", 73 | style(adjusted.format("%H:%M:%S")).bold().cyan(), 74 | tod.relative_to_human(now), 75 | get_time_of_day(adjusted), 76 | ); 77 | println!( 78 | "date: {} ({})", 79 | style(adjusted.format("%Y-%m-%d")).yellow(), 80 | style(adjusted.format("%A")), 81 | ); 82 | println!( 83 | "zone: {} ({})", 84 | style(zone.tz().name()).underlined(), 85 | ZoneOffset(adjusted), 86 | ); 87 | if zone.kind() != LocationKind::Timezone { 88 | print!("location: {}", style(zone.name()).bold()); 89 | print!(" ("); 90 | let mut with_code = false; 91 | if let Some(code) = zone.admin_code() { 92 | print!("{}", code); 93 | with_code = true; 94 | } 95 | if let Some(country) = zone.country() { 96 | if with_code { 97 | print!("; "); 98 | } 99 | print!("{}", country); 100 | } 101 | print!(")"); 102 | println!(); 103 | } 104 | } 105 | 106 | fn list_timezones() -> Result<(), anyhow::Error> { 107 | let now = Utc::now(); 108 | let mut zone_list = Vec::new(); 109 | for zone in chrono_tz::TZ_VARIANTS { 110 | let there = now.with_timezone(&zone); 111 | zone_list.push((zone, there)); 112 | } 113 | zone_list.sort_by_key(|x| x.0.name()); 114 | 115 | for (zone, there) in zone_list { 116 | println!("{} ({})", zone.name(), ZoneOffset(there)); 117 | } 118 | 119 | Ok(()) 120 | } 121 | 122 | pub fn execute() -> Result<(), anyhow::Error> { 123 | let cli = Cli::parse(); 124 | 125 | match cli.colors.as_deref() { 126 | None | Some("auto") => {} 127 | Some("always") => console::set_colors_enabled(true), 128 | Some("never") => console::set_colors_enabled(false), 129 | Some(other) => bail!("unknown value for --colors ({})", other), 130 | }; 131 | 132 | if cli.list_timezones { 133 | return list_timezones(); 134 | } 135 | 136 | let expr = InputExpr::parse(cli.expr.as_deref().unwrap_or("now"))?; 137 | let timestamps = expr.process()?; 138 | 139 | if cli.json { 140 | println!("{}", serde_json::to_string_pretty(×tamps).unwrap()); 141 | } else if cli.short { 142 | for t in timestamps.iter() { 143 | println!( 144 | "{} ({})", 145 | t.datetime().format("%Y-%m-%d %H:%M:%S %z"), 146 | t.zone() 147 | ); 148 | } 149 | } else { 150 | let now = Utc::now(); 151 | for (idx, t) in timestamps.iter().enumerate() { 152 | if idx > 0 { 153 | println!(); 154 | } 155 | print_date(t, now); 156 | } 157 | } 158 | 159 | Ok(()) 160 | } 161 | 162 | fn main() { 163 | match execute() { 164 | Ok(()) => {} 165 | Err(err) => { 166 | eprintln!("error: {}", err); 167 | std::process::exit(1); 168 | } 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /libwhen/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "libwhen" 3 | version = "0.4.0" 4 | edition = "2021" 5 | authors = ["Armin Ronacher "] 6 | license = "Apache-2.0" 7 | description = "utility crate for the when command line tool" 8 | repository = "https://github.com/mitsuhiko/when" 9 | keywords = ["timezone", "convert", "cli"] 10 | readme = "README.md" 11 | rust-version = "1.56.0" 12 | 13 | [dependencies] 14 | chrono = { version = "0.4.19", features = ["serde"] } 15 | chrono-humanize = "0.2.1" 16 | chrono-tz = "0.6.1" 17 | localzone = "0.2.0" 18 | pest = "2.1.3" 19 | pest_derive = "2.1.0" 20 | serde = { version = "1.0.131", features = ["derive"] } 21 | -------------------------------------------------------------------------------- /libwhen/LICENSE: -------------------------------------------------------------------------------- 1 | ../LICENSE -------------------------------------------------------------------------------- /libwhen/README.md: -------------------------------------------------------------------------------- 1 | ../README.md -------------------------------------------------------------------------------- /libwhen/build.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::fs; 3 | use std::io::{BufRead, BufReader, Write}; 4 | use std::path::PathBuf; 5 | 6 | fn main() { 7 | let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); 8 | let mut out = fs::File::create(out_dir.join("locations.rs")).unwrap(); 9 | 10 | writeln!(out, "static COUNTRIES: &[(&str, &str)] = &[",).unwrap(); 11 | for line in BufReader::new(fs::File::open("data/countries.txt").unwrap()).lines() { 12 | let line = line.unwrap(); 13 | let pieces = line.split('\t').collect::>(); 14 | writeln!(out, " ({:?}, {:?}),", pieces[0], pieces[1]).unwrap(); 15 | } 16 | writeln!(out, "];").unwrap(); 17 | 18 | writeln!(out, "static LOCATIONS: &[Location] = &[",).unwrap(); 19 | for line in BufReader::new(fs::File::open("data/locations.txt").unwrap()).lines() { 20 | let line = line.unwrap(); 21 | let pieces = line.split('\t').collect::>(); 22 | writeln!( 23 | out, 24 | " Location {{ name: {:?}, aliases: &{:?}, country: {:?}, admin_code: {:?}, kind: LocationKind::{}, tz: Tz::{} }},", 25 | pieces[0], 26 | if pieces[1].is_empty() { vec![] } else { pieces[1].split(';').collect::>() }, 27 | pieces[2], 28 | if pieces[3].is_empty() { None } else { Some(pieces[3]) }, 29 | match pieces[4] { 30 | "city" => "City", 31 | "airport" => "Airport", 32 | "division" => "Division", 33 | _ => unreachable!(), 34 | }, 35 | pieces[5].replace(" ", "_").replace("-", "").replace("/", "__"), 36 | ).unwrap(); 37 | } 38 | writeln!(out, "];").unwrap(); 39 | } 40 | -------------------------------------------------------------------------------- /libwhen/src/date_grammar.pest: -------------------------------------------------------------------------------- 1 | WHITESPACE = _{ WHITE_SPACE } 2 | 3 | spec = ${ 4 | (unix_time ~ WHITE_SPACE* ~ ^"->" ~ WHITE_SPACE* ~ location) | 5 | ((neg_rel_time | abs_time | rel_time | unix_time) ~ (WHITE_SPACE+ ~ ^"in" ~ WHITE_SPACE+ ~ location)?) 6 | } 7 | 8 | number = { ASCII_DIGIT+ } 9 | abs_time = { time ~ (WHITE_SPACE+ ~ (^"on" ~ WHITE_SPACE+)? ~ date)? | date ~ WHITE_SPACE+ ~ time } 10 | rel_time = ${ ^"in" ~ WHITE_SPACE+ ~ rel_time_spec ~ (WHITE_SPACE+ ~ ^"and" ~ WHITE_SPACE+ ~ rel_time_spec)* } 11 | rel_time_spec = _{ rel_hours | rel_minutes | rel_seconds } 12 | neg_rel_time = ${ rel_time_spec ~ (WHITE_SPACE+ ~ ^"and" ~ WHITE_SPACE+ ~ rel_time_spec)* ~ WHITE_SPACE+ ~ ^"ago" } 13 | rel_hours = { number ~ WHITE_SPACE* ~ (^"hours" | ^"hour" | ^"h") } 14 | rel_minutes = { number ~ WHITE_SPACE* ~ (^"minutes" | ^"mins" | ^"min" | ^"m") } 15 | rel_seconds = { number ~ WHITE_SPACE* ~ (^"seconds" | ^ "secs" | ^"sec" | ^"s") } 16 | unix_time = { (^"unix:" ~ WHITE_SPACE* | ^"unix" ~ WHITE_SPACE+) ~ number } 17 | 18 | time = { time_special | time12 | time24 } 19 | location = @{ (LETTER | NUMBER | MARK | SEPARATOR | PUNCTUATION | SYMBOL | WHITE_SPACE)+ } 20 | time_special = { ^"midnight" | ^"noon" | ^"now" } 21 | time12 = _{ HH12 ~ (":" ~ MM)? ~ (":" ~ SS)? ~ meridiem } 22 | time24 = _{ HH24 ~ (":" ~ MM)? ~ (":" ~ SS)? } 23 | HH12 = { "12" | "11" | "10" | ("0" ~ '1'..'9') | '0'..'9' } 24 | HH24 = { ("1" ~ '0'..'9') | ("2" ~ '0'..'3') | ("0" ~ '1'..'9') | '0'..'9' } 25 | MM = { "00" | ('0'..'5' ~ '0'..'9') | '0'..'9' } 26 | SS = { "00" | ('0'..'5' ~ '0'..'9') | '0'..'9' } 27 | meridiem = { am | pm } 28 | am = { "AM" | "A.M." | "am" | "a.m." } 29 | pm = { "PM" | "P.M." | "pm" | "p.m." } 30 | 31 | date = _{ date_relative | date_absolute } 32 | date_relative = { tomorrow | yesterday | today | in_days } 33 | tomorrow = { (^"in" ~ WHITE_SPACE+ ~ "1" ~ WHITE_SPACE+ ~ ^"day") | ^"tomorrow" | ^"tmw" | ^"tmrw" } 34 | yesterday = { ^"yesterday" | ^"yd" } 35 | today = { ^"today" } 36 | in_days = ${ ^"in" ~ WHITE_SPACE+ ~ rel_days ~ WHITE_SPACE* ~ ^"days" } 37 | rel_days = { ASCII_DIGIT+ } 38 | date_absolute = { ddmmyyyy | english_date } 39 | 40 | ddmmyyyy = { dd ~ ( "-" | "." ) ~ mm ~ ((( "-" | "." ) ~ yyyy) | ".")? } 41 | english_date = ${ 42 | (english_month ~ WHITE_SPACE+ ~ (english_day | dd) ~ (WHITE_SPACE+ ~ yyyy)?) | 43 | ((english_day | dd) ~ WHITE_SPACE+ ~ "of" ~ WHITE_SPACE+ ~ english_month ~ (WHITE_SPACE+ ~ yyyy)?) 44 | } 45 | english_day = { "1st" | "2nd" | "3rd" | ('4'..'9') ~ "th" | "1" ~ ('0'..'9') ~ "th" | ("2" ~ ("1st" | "2nd" | "3rd" | '4'..'9' ~ "th")) | "30th" | "31st" } 46 | 47 | english_month = { m01 | m02 | m03 | m04 | m05 | m06 | m07 | m08 | m09 | m10 | m11 | m12 } 48 | m01 = { ^"january" | ^"jan" ~ "."? } 49 | m02 = { ^"february" | ^"feb" ~ "."? } 50 | m03 = { ^"april" | ^"apr" ~ "."? } 51 | m04 = { ^"march" | ^"mar" ~ "."? } 52 | m05 = { ^"may" ~ "."? } 53 | m06 = { ^"june" | ^"jun" ~ "."? } 54 | m07 = { ^"july" | ^"jul" ~ "."? } 55 | m08 = { ^"august" | ^"aug" ~ "."? } 56 | m09 = { ^"september" | ^"sep" ~ ^"t"? ~ "."? } 57 | m10 = { ^"october" | ^"oct" ~ "."? } 58 | m11 = { ^"november" | ^"nov" ~ "."? } 59 | m12 = { ^"december" | ^"dec" ~ "."? } 60 | 61 | dd = { "00" | ('0'..'2' ~ '0'..'9') | "30" | "31" } 62 | mm = { "00" | ('0'..'1' ~ '0'..'9') | "11" | "12" } 63 | yyyy = { '0'..'9' ~ '0'..'9' ~ '0'..'9' ~ '0'..'9' } 64 | -------------------------------------------------------------------------------- /libwhen/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This is the internal library for the 2 | //! [`when`](https://github.com/mitsuhiko/when) command line utility. 3 | //! 4 | //! Using this crate directly is not recommended as it's not maintained with a stable 5 | //! API interface. It primarily exists so that it can be compiled to web assembly 6 | //! independently of the CLI tool. 7 | mod location; 8 | mod parser; 9 | mod utils; 10 | 11 | pub use self::location::{find_zone, Location, LocationKind, ZoneRef}; 12 | pub use self::parser::{InputExpr, TimeAtLocation}; 13 | pub use self::utils::{get_time_of_day, TimeOfDay}; 14 | -------------------------------------------------------------------------------- /libwhen/src/location.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Cow; 2 | use std::fmt; 3 | 4 | use chrono_tz::Tz; 5 | 6 | /// The type of location. 7 | #[derive(Debug, Copy, Clone, PartialEq)] 8 | pub enum LocationKind { 9 | City, 10 | Timezone, 11 | Airport, 12 | Division, 13 | } 14 | 15 | /// Represents a timezone location. 16 | #[derive(Debug)] 17 | pub struct Location { 18 | pub(crate) name: &'static str, 19 | pub(crate) country: &'static str, 20 | pub(crate) admin_code: Option<&'static str>, 21 | pub(crate) aliases: &'static [&'static str], 22 | pub(crate) kind: LocationKind, 23 | pub(crate) tz: Tz, 24 | } 25 | 26 | /// Reference to a timezone. 27 | #[derive(Debug, Clone, Copy)] 28 | pub enum ZoneRef { 29 | Tz(Tz), 30 | Location(&'static Location), 31 | } 32 | 33 | impl fmt::Display for ZoneRef { 34 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 35 | if self.kind() == LocationKind::Timezone { 36 | write!(f, "{}", self.name()) 37 | } else { 38 | write!(f, "{}", self.name())?; 39 | if let Some(code) = self.admin_code() { 40 | write!(f, ", {}", code)?; 41 | } 42 | if let Some(country) = self.country() { 43 | write!(f, "; ")?; 44 | write!(f, "{}", country)?; 45 | } 46 | Ok(()) 47 | } 48 | } 49 | } 50 | 51 | impl ZoneRef { 52 | /// Returns the name of the zone reference. 53 | /// 54 | /// For actual timezones that can be the IANA name, for cities 55 | /// and airports this will be the actual name of the location. 56 | pub fn name(&self) -> &str { 57 | match self { 58 | ZoneRef::Tz(tz) => tz.name(), 59 | ZoneRef::Location(loc) => loc.name, 60 | } 61 | } 62 | 63 | /// True if this zone is the UTC zone. 64 | /// 65 | /// Note that this is different than checking if the zone is currently 66 | /// at UTC+0. 67 | pub fn is_utc(&self) -> bool { 68 | matches!( 69 | self.tz().name(), 70 | "Universal" 71 | | "UTC" 72 | | "UCT" 73 | | "Zulu" 74 | | "Etc/Universal" 75 | | "Etc/UCT" 76 | | "Etc/UTC" 77 | | "Etc/Zulu" 78 | ) 79 | } 80 | 81 | /// Returns the kind of location. 82 | pub fn kind(&self) -> LocationKind { 83 | match self { 84 | ZoneRef::Tz(_) => LocationKind::Timezone, 85 | ZoneRef::Location(loc) => loc.kind, 86 | } 87 | } 88 | 89 | /// If this zone reference points to a country, returns the country name. 90 | pub fn country(&self) -> Option<&str> { 91 | match self { 92 | ZoneRef::Tz(_) => None, 93 | ZoneRef::Location(loc) => COUNTRIES 94 | .binary_search_by_key(&loc.country, |x| x.0) 95 | .ok() 96 | .map(|pos| COUNTRIES[pos].1), 97 | } 98 | } 99 | 100 | /// If the zone has an admin code returns it. 101 | /// 102 | /// For the US for instance this can be the name of the US state. 103 | pub fn admin_code(&self) -> Option<&str> { 104 | match self { 105 | ZoneRef::Tz(_) => None, 106 | ZoneRef::Location(loc) => loc.admin_code, 107 | } 108 | } 109 | 110 | /// Returns a `chrono_tz` timezone object. 111 | pub fn tz(&self) -> Tz { 112 | match self { 113 | ZoneRef::Tz(tz) => *tz, 114 | ZoneRef::Location(loc) => loc.tz, 115 | } 116 | } 117 | } 118 | 119 | include!(concat!(env!("OUT_DIR"), "/locations.rs")); 120 | 121 | /// Tries to locate a zone by name 122 | pub fn find_zone(name: &str) -> Option { 123 | let name = if name.eq_ignore_ascii_case("local") { 124 | match localzone::get_local_zone() { 125 | Some(zone) => Cow::Owned(zone), 126 | None => Cow::Borrowed("UTC"), 127 | } 128 | } else { 129 | Cow::Borrowed(name) 130 | }; 131 | 132 | let tz_name = name.replace(" ", "_"); 133 | for tz in chrono_tz::TZ_VARIANTS { 134 | if tz.name().eq_ignore_ascii_case(&tz_name) { 135 | return Some(ZoneRef::Tz(tz)); 136 | } 137 | } 138 | 139 | for delim in [',', ' '] { 140 | if let Some((name, code)) = name.rsplit_once(delim) { 141 | let name = name.trim_end(); 142 | let code = code.trim_start(); 143 | if let Some(rv) = LOCATIONS.iter().find(|x| { 144 | x.name.eq_ignore_ascii_case(name) 145 | && (x.country.eq_ignore_ascii_case(code) 146 | || x.admin_code.map_or(false, |x| x.eq_ignore_ascii_case(code))) 147 | }) { 148 | return Some(ZoneRef::Location(rv)); 149 | } 150 | } 151 | } 152 | 153 | if let Some(loc) = LOCATIONS 154 | .iter() 155 | .find(|x| x.name.eq_ignore_ascii_case(&name)) 156 | .map(ZoneRef::Location) 157 | { 158 | return Some(loc); 159 | } 160 | 161 | if name.len() == 3 { 162 | if let Some(loc) = LOCATIONS 163 | .iter() 164 | .find(|x| x.aliases.iter().any(|x| x.eq_ignore_ascii_case(&name))) 165 | .map(ZoneRef::Location) 166 | { 167 | return Some(loc); 168 | } 169 | } 170 | 171 | None 172 | } 173 | -------------------------------------------------------------------------------- /libwhen/src/parser.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | use std::ops::Add; 3 | 4 | use chrono::{DateTime, Datelike, Duration, NaiveDateTime, TimeZone, Timelike, Utc}; 5 | use chrono_humanize::HumanTime; 6 | use chrono_tz::Tz; 7 | use pest::error::ErrorVariant; 8 | use pest::iterators::Pair; 9 | use pest::Parser; 10 | use pest_derive::Parser; 11 | use serde::ser::SerializeMap; 12 | use serde::{Serialize, Serializer}; 13 | 14 | use crate::location::{find_zone, LocationKind, ZoneRef}; 15 | use crate::utils::get_time_of_day; 16 | 17 | /// Represents a parsing error. 18 | #[derive(Debug)] 19 | pub enum DateParseError { 20 | Parser(pest::error::Error), 21 | Garbage(String), 22 | OutOfRange(&'static str), 23 | MissingLocation(String), 24 | } 25 | 26 | impl std::error::Error for DateParseError {} 27 | 28 | impl fmt::Display for DateParseError { 29 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 30 | struct Enumerate<'a, T: fmt::Debug>(&'a [T]); 31 | 32 | impl<'a, T: fmt::Debug> fmt::Display for Enumerate<'a, T> { 33 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 34 | for (idx, item) in self.0.iter().enumerate() { 35 | if idx > 0 { 36 | write!(f, ", ")?; 37 | } 38 | write!(f, "{:?}", item)?; 39 | } 40 | Ok(()) 41 | } 42 | } 43 | 44 | match self { 45 | DateParseError::Parser(p) => { 46 | write!(f, "invalid syntax (")?; 47 | match &p.variant { 48 | ErrorVariant::ParsingError { 49 | positives, 50 | negatives, 51 | } => match (negatives.is_empty(), positives.is_empty()) { 52 | (false, false) => write!( 53 | f, 54 | "unexpected {}; expected {}", 55 | Enumerate(negatives), 56 | Enumerate(positives) 57 | )?, 58 | (false, true) => write!(f, "unexpected {}", Enumerate(negatives))?, 59 | (true, false) => write!(f, "expected {}", Enumerate(positives))?, 60 | (true, true) => write!(f, "unknown parsing error")?, 61 | }, 62 | ErrorVariant::CustomError { message } => write!(f, "{}", message)?, 63 | } 64 | write!(f, ")")?; 65 | Ok(()) 66 | } 67 | DateParseError::Garbage(leftover) => { 68 | write!(f, "invalid syntax (unsure how to interpret {:?})", leftover) 69 | } 70 | DateParseError::OutOfRange(context) => { 71 | write!(f, "{} out of range", context) 72 | } 73 | DateParseError::MissingLocation(loc) => { 74 | write!(f, "unknown timezone '{}'", loc) 75 | } 76 | } 77 | } 78 | } 79 | 80 | #[derive(Parser)] 81 | #[grammar = "date_grammar.pest"] 82 | struct DateParser; 83 | 84 | /// Represents a human readable date expression 85 | #[derive(Debug)] 86 | pub struct InputExpr<'a> { 87 | time_spec: Option, 88 | date_spec: Option, 89 | locations: Vec<&'a str>, 90 | } 91 | 92 | /// A tuple of time and location. 93 | #[derive(Debug)] 94 | pub struct TimeAtLocation { 95 | datetime: DateTime, 96 | zone_ref: ZoneRef, 97 | } 98 | 99 | impl TimeAtLocation { 100 | /// Returns the timestamp in the given location. 101 | pub fn datetime(&self) -> DateTime { 102 | self.datetime 103 | } 104 | 105 | /// Gives me the relative time to now. 106 | pub fn relative_to(&self, now: DateTime) -> Duration { 107 | self.datetime.signed_duration_since(now) 108 | } 109 | 110 | /// Human readable relative date. 111 | pub fn relative_to_human(&self, now: DateTime) -> String { 112 | format!( 113 | "{:#}", 114 | HumanTime::from( 115 | self.datetime 116 | .with_second(0) 117 | .unwrap() 118 | .with_nanosecond(0) 119 | .unwrap() 120 | .signed_duration_since(now.with_second(0).unwrap().with_nanosecond(0).unwrap()) 121 | ) 122 | ) 123 | } 124 | 125 | /// Returns the zone reference for the timestamp. 126 | pub fn zone(&self) -> ZoneRef { 127 | self.zone_ref 128 | } 129 | } 130 | 131 | impl<'a> Serialize for TimeAtLocation { 132 | fn serialize(&self, serializer: S) -> Result 133 | where 134 | S: Serializer, 135 | { 136 | let mut m = serializer.serialize_map(None)?; 137 | let now = Utc::now(); 138 | m.serialize_entry("datetime", &self.datetime)?; 139 | m.serialize_entry("time_of_day", &get_time_of_day(self.datetime))?; 140 | m.serialize_entry("relative_to_now_sec", &self.relative_to(now).num_seconds())?; 141 | m.serialize_entry("relative_to_now_human", &self.relative_to_human(now))?; 142 | m.serialize_entry("timezone", &SerializeZone(&self.zone_ref, &self.datetime))?; 143 | if self.zone_ref.kind() != LocationKind::Timezone { 144 | m.serialize_entry("location", &SerializeLocation(&self.zone_ref))?; 145 | } 146 | m.end() 147 | } 148 | } 149 | 150 | pub struct SerializeZone<'a>(&'a ZoneRef, &'a DateTime); 151 | 152 | impl<'a> Serialize for SerializeZone<'a> { 153 | fn serialize(&self, serializer: S) -> Result 154 | where 155 | S: Serializer, 156 | { 157 | let mut m = serializer.serialize_map(None)?; 158 | m.serialize_entry("name", self.0.tz().name())?; 159 | m.serialize_entry("abbrev", &self.1.format("%Z").to_string())?; 160 | m.serialize_entry("utc_offset", &self.1.format("%z").to_string())?; 161 | m.end() 162 | } 163 | } 164 | 165 | pub struct SerializeLocation<'a>(&'a ZoneRef); 166 | 167 | impl<'a> Serialize for SerializeLocation<'a> { 168 | fn serialize(&self, serializer: S) -> Result 169 | where 170 | S: Serializer, 171 | { 172 | let mut m = serializer.serialize_map(None)?; 173 | m.serialize_entry("name", self.0.name())?; 174 | if let Some(admin_code) = self.0.admin_code() { 175 | m.serialize_entry("admin_code", &admin_code)?; 176 | } 177 | if let Some(country) = self.0.country() { 178 | m.serialize_entry("country", &country)?; 179 | } 180 | m.end() 181 | } 182 | } 183 | 184 | impl<'a> InputExpr<'a> { 185 | /// Parses an expression from a string. 186 | pub fn parse(value: &'a str) -> Result, DateParseError> { 187 | parse_input(value) 188 | } 189 | 190 | /// Returns the location if available. 191 | pub fn location(&self) -> Option<&str> { 192 | self.locations.get(0).copied() 193 | } 194 | 195 | /// Returns the target locations if available. 196 | pub fn to_locations(&self) -> &[&str] { 197 | self.locations.get(1..).unwrap_or_default() 198 | } 199 | 200 | /// Is this relative time? 201 | pub fn is_relative(&self) -> bool { 202 | matches!(self.time_spec, None | Some(TimeSpec::Rel { .. })) 203 | || matches!(self.date_spec, Some(DateSpec::Rel { .. })) 204 | } 205 | 206 | /// Resolves the expression into all referenced locations. 207 | pub fn process(&self) -> Result, DateParseError> { 208 | let zone_ref = self.location().unwrap_or("local"); 209 | let from_zone = find_zone(zone_ref) 210 | .ok_or_else(|| DateParseError::MissingLocation(zone_ref.to_string()))?; 211 | let now = Utc::now().with_timezone(&from_zone.tz()); 212 | let from = self.apply(now)?; 213 | 214 | let mut rv = vec![TimeAtLocation { 215 | datetime: from, 216 | zone_ref: from_zone, 217 | }]; 218 | 219 | for to_zone_ref in self.to_locations() { 220 | let to_zone = find_zone(to_zone_ref) 221 | .ok_or_else(|| DateParseError::MissingLocation(to_zone_ref.to_string()))?; 222 | let to = from.with_timezone(&to_zone.tz()); 223 | rv.push(TimeAtLocation { 224 | datetime: to, 225 | zone_ref: to_zone, 226 | }); 227 | } 228 | 229 | if rv.len() == 1 { 230 | if let Some(to_zone) = find_zone("local") { 231 | if to_zone.tz().name() != from_zone.tz().name() { 232 | rv.push(TimeAtLocation { 233 | datetime: from.with_timezone(&to_zone.tz()), 234 | zone_ref: to_zone, 235 | }); 236 | } 237 | } 238 | } 239 | 240 | Ok(rv) 241 | } 242 | 243 | /// Applies the expression to a current reference date. 244 | pub fn apply(&self, mut date: DateTime) -> Result, DateParseError> { 245 | match self.time_spec { 246 | Some(TimeSpec::Abs { 247 | hour, 248 | minute, 249 | second, 250 | }) => { 251 | date = date 252 | .with_hour(hour as u32) 253 | .unwrap() 254 | .with_minute(minute as u32) 255 | .unwrap() 256 | .with_second(second as u32) 257 | .unwrap(); 258 | } 259 | Some(TimeSpec::Rel { 260 | hours, 261 | minutes, 262 | seconds, 263 | }) => { 264 | date = date.add(Duration::hours(hours as i64)); 265 | date = date.add(Duration::minutes(minutes as i64)); 266 | date = date.add(Duration::seconds(seconds as i64)); 267 | } 268 | None => {} 269 | } 270 | match self.date_spec { 271 | Some(DateSpec::Abs { day, month, year }) => { 272 | date = date 273 | .with_day(day as u32) 274 | .ok_or(DateParseError::OutOfRange("day"))?; 275 | if let Some(month) = month { 276 | date = date 277 | .with_month(month as u32) 278 | .ok_or(DateParseError::OutOfRange("month"))?; 279 | } 280 | if let Some(year) = year { 281 | date = date 282 | .with_year(year) 283 | .ok_or(DateParseError::OutOfRange("year"))?; 284 | } 285 | } 286 | Some(DateSpec::Rel { days }) => { 287 | date = date.add(Duration::days(days as i64)); 288 | } 289 | None => {} 290 | } 291 | Ok(date) 292 | } 293 | } 294 | 295 | #[derive(Debug)] 296 | enum TimeSpec { 297 | Abs { 298 | hour: i32, 299 | minute: i32, 300 | second: i32, 301 | }, 302 | Rel { 303 | hours: i32, 304 | minutes: i32, 305 | seconds: i32, 306 | }, 307 | } 308 | 309 | #[derive(Debug)] 310 | enum DateSpec { 311 | Abs { 312 | day: i32, 313 | month: Option, 314 | year: Option, 315 | }, 316 | Rel { 317 | days: i32, 318 | }, 319 | } 320 | 321 | fn as_int(pair: Pair) -> i32 { 322 | pair.into_inner().next().unwrap().as_str().parse().unwrap() 323 | } 324 | 325 | fn parse_input(expr: &str) -> Result, DateParseError> { 326 | let expr = expr.trim(); 327 | let pair = DateParser::parse(Rule::spec, expr) 328 | .map_err(DateParseError::Parser)? 329 | .next() 330 | .unwrap(); 331 | 332 | if pair.as_str() != expr { 333 | return Err(DateParseError::Garbage( 334 | expr[pair.as_str().len()..].to_string(), 335 | )); 336 | } 337 | 338 | let mut rv = InputExpr { 339 | time_spec: None, 340 | date_spec: None, 341 | locations: vec![], 342 | }; 343 | let mut unix_time = false; 344 | 345 | for piece in pair.into_inner() { 346 | match piece.as_rule() { 347 | Rule::location => { 348 | for loc in piece.as_str().split("->") { 349 | let loc = loc.trim(); 350 | if !loc.is_empty() { 351 | rv.locations.push(loc); 352 | } 353 | } 354 | } 355 | Rule::unix_time => { 356 | let ts: i64 = piece.into_inner().next().unwrap().as_str().parse().unwrap(); 357 | let dt = NaiveDateTime::from_timestamp_opt(ts, 0) 358 | .ok_or(DateParseError::OutOfRange("unix timestamp"))?; 359 | rv.time_spec = Some(TimeSpec::Abs { 360 | hour: dt.hour() as _, 361 | minute: dt.minute() as _, 362 | second: dt.second() as _, 363 | }); 364 | rv.date_spec = Some(DateSpec::Abs { 365 | day: dt.day() as _, 366 | month: Some(dt.month() as _), 367 | year: Some(dt.year() as _), 368 | }); 369 | unix_time = true; 370 | } 371 | Rule::abs_time => { 372 | let mut now = false; 373 | for abs_time_piece in piece.into_inner() { 374 | match abs_time_piece.as_rule() { 375 | Rule::time => { 376 | let mut hour = 0; 377 | let mut minute = 0; 378 | let mut second = 0; 379 | for time_piece in abs_time_piece.into_inner() { 380 | match time_piece.as_rule() { 381 | Rule::HH12 | Rule::HH24 => { 382 | hour = time_piece.as_str().parse::().unwrap(); 383 | } 384 | Rule::MM => { 385 | minute = time_piece.as_str().parse::().unwrap(); 386 | } 387 | Rule::SS => { 388 | second = time_piece.as_str().parse::().unwrap(); 389 | } 390 | Rule::meridiem => { 391 | if matches!( 392 | time_piece.into_inner().next().unwrap().as_rule(), 393 | Rule::pm 394 | ) { 395 | // don't change for 12pm 396 | if hour != 12 { 397 | hour += 12; 398 | } 399 | } else { 400 | // special case 12am = midnight 401 | if hour == 12 { 402 | hour = 0; 403 | } 404 | } 405 | } 406 | Rule::time_special => { 407 | if time_piece.as_str().eq_ignore_ascii_case("midnight") { 408 | hour = 0; 409 | } else if time_piece.as_str().eq_ignore_ascii_case("noon") { 410 | hour = 12; 411 | } else if time_piece.as_str().eq_ignore_ascii_case("now") { 412 | now = true; 413 | } 414 | } 415 | _ => unreachable!(), 416 | } 417 | } 418 | if !now { 419 | rv.time_spec = Some(TimeSpec::Abs { 420 | hour, 421 | minute, 422 | second, 423 | }); 424 | } 425 | } 426 | Rule::date_absolute => { 427 | let mut day = 0; 428 | let mut month = None; 429 | let mut year = None; 430 | for date_piece in abs_time_piece.into_inner() { 431 | match date_piece.as_rule() { 432 | Rule::english_date => { 433 | for english_piece in date_piece.into_inner() { 434 | match english_piece.as_rule() { 435 | Rule::english_month => { 436 | month = Some( 437 | match english_piece 438 | .into_inner() 439 | .next() 440 | .unwrap() 441 | .as_rule() 442 | { 443 | Rule::m01 => 1, 444 | Rule::m02 => 2, 445 | Rule::m03 => 3, 446 | Rule::m04 => 4, 447 | Rule::m05 => 5, 448 | Rule::m06 => 6, 449 | Rule::m07 => 7, 450 | Rule::m08 => 8, 451 | Rule::m09 => 9, 452 | Rule::m10 => 10, 453 | Rule::m11 => 11, 454 | Rule::m12 => 12, 455 | _ => unreachable!(), 456 | }, 457 | ); 458 | } 459 | Rule::english_day => { 460 | day = english_piece.as_str() 461 | [0..english_piece.as_str().len() - 2] 462 | .parse() 463 | .unwrap(); 464 | } 465 | Rule::dd => { 466 | day = english_piece 467 | .as_str() 468 | .parse::() 469 | .unwrap(); 470 | } 471 | Rule::yyyy => { 472 | year = Some( 473 | english_piece.as_str().parse().unwrap(), 474 | ); 475 | } 476 | _ => unreachable!(), 477 | } 478 | } 479 | } 480 | Rule::ddmmyyyy => { 481 | for date_piece in date_piece.into_inner() { 482 | match date_piece.as_rule() { 483 | Rule::dd => { 484 | day = 485 | date_piece.as_str().parse::().unwrap(); 486 | } 487 | Rule::mm => { 488 | month = Some( 489 | date_piece.as_str().parse::().unwrap(), 490 | ); 491 | } 492 | Rule::yyyy => { 493 | year = 494 | Some(date_piece.as_str().parse().unwrap()); 495 | } 496 | _ => unreachable!(), 497 | } 498 | } 499 | } 500 | _ => unreachable!(), 501 | } 502 | } 503 | rv.date_spec = Some(DateSpec::Abs { day, month, year }); 504 | } 505 | Rule::date_relative => { 506 | let mut days = 0; 507 | for days_piece in abs_time_piece.into_inner() { 508 | match days_piece.as_rule() { 509 | Rule::tomorrow => { 510 | days = 1; 511 | } 512 | Rule::yesterday => { 513 | days = -1; 514 | } 515 | Rule::today => { 516 | days = 0; 517 | } 518 | Rule::in_days => { 519 | days = as_int(days_piece); 520 | } 521 | _ => unreachable!(), 522 | } 523 | } 524 | rv.date_spec = Some(DateSpec::Rel { days }); 525 | } 526 | _ => unreachable!(), 527 | } 528 | } 529 | } 530 | Rule::rel_time | Rule::neg_rel_time => { 531 | let mut hours = 0; 532 | let mut minutes = 0; 533 | let mut seconds = 0; 534 | let is_negative = piece.as_rule() == Rule::neg_rel_time; 535 | for time_piece in piece.into_inner() { 536 | match time_piece.as_rule() { 537 | Rule::rel_hours => { 538 | hours = as_int(time_piece); 539 | } 540 | Rule::rel_minutes => { 541 | minutes = as_int(time_piece); 542 | } 543 | Rule::rel_seconds => { 544 | seconds = as_int(time_piece); 545 | } 546 | _ => unreachable!(), 547 | } 548 | } 549 | if is_negative { 550 | hours *= -1; 551 | minutes *= -1; 552 | seconds *= -1; 553 | } 554 | rv.time_spec = Some(TimeSpec::Rel { 555 | hours, 556 | minutes, 557 | seconds, 558 | }); 559 | } 560 | _ => unreachable!(), 561 | } 562 | } 563 | 564 | // if unix time is used there is always an implied utc location 565 | // as this is the main thing that makes sense with unix timestamps 566 | if unix_time 567 | && (rv.locations.is_empty() || !find_zone(rv.locations[0]).map_or(false, |x| x.is_utc())) 568 | { 569 | rv.locations.insert(0, "utc"); 570 | } 571 | 572 | Ok(rv) 573 | } 574 | -------------------------------------------------------------------------------- /libwhen/src/utils.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use chrono::{DateTime, Timelike}; 4 | use chrono_tz::Tz; 5 | use serde::{Deserialize, Serialize}; 6 | 7 | /// Human readable time-of-day description. 8 | #[derive(Serialize, Deserialize, Debug, PartialEq, Eq)] 9 | #[serde(rename_all = "snake_case")] 10 | pub enum TimeOfDay { 11 | EarlyMorning, 12 | Morning, 13 | LateMorning, 14 | Noon, 15 | Afternoon, 16 | EarlyEvening, 17 | Evening, 18 | LateEvening, 19 | Night, 20 | } 21 | 22 | impl fmt::Display for TimeOfDay { 23 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 24 | write!( 25 | f, 26 | "{}", 27 | match self { 28 | TimeOfDay::EarlyMorning => "early morning", 29 | TimeOfDay::Morning => "morning", 30 | TimeOfDay::LateMorning => "late morning", 31 | TimeOfDay::Noon => "noon", 32 | TimeOfDay::Afternoon => "afternoon", 33 | TimeOfDay::EarlyEvening => "early evening", 34 | TimeOfDay::Evening => "evening", 35 | TimeOfDay::LateEvening => "late evening", 36 | TimeOfDay::Night => "night", 37 | } 38 | ) 39 | } 40 | } 41 | 42 | /// Given a datetime object returns a human readable time-of-day description. 43 | pub fn get_time_of_day(dt: DateTime) -> TimeOfDay { 44 | match dt.hour() { 45 | 5 => TimeOfDay::EarlyMorning, 46 | 6..=8 => TimeOfDay::Morning, 47 | 9..=11 => TimeOfDay::LateMorning, 48 | 12 => TimeOfDay::Noon, 49 | 13..=16 => TimeOfDay::Afternoon, 50 | 17..=18 => TimeOfDay::EarlyEvening, 51 | 19..=20 => TimeOfDay::Evening, 52 | 21..=22 => TimeOfDay::LateEvening, 53 | 23 | 0..=4 => TimeOfDay::Night, 54 | 24.. => unreachable!(), 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /web/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "autocfg" 7 | version = "1.0.1" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" 10 | 11 | [[package]] 12 | name = "block-buffer" 13 | version = "0.7.3" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" 16 | dependencies = [ 17 | "block-padding", 18 | "byte-tools", 19 | "byteorder", 20 | "generic-array", 21 | ] 22 | 23 | [[package]] 24 | name = "block-padding" 25 | version = "0.1.5" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" 28 | dependencies = [ 29 | "byte-tools", 30 | ] 31 | 32 | [[package]] 33 | name = "bumpalo" 34 | version = "3.8.0" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "8f1e260c3a9040a7c19a12468758f4c16f31a81a1fe087482be9570ec864bb6c" 37 | 38 | [[package]] 39 | name = "byte-tools" 40 | version = "0.3.1" 41 | source = "registry+https://github.com/rust-lang/crates.io-index" 42 | checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" 43 | 44 | [[package]] 45 | name = "byteorder" 46 | version = "1.4.3" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" 49 | 50 | [[package]] 51 | name = "cfg-if" 52 | version = "0.1.10" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 55 | 56 | [[package]] 57 | name = "cfg-if" 58 | version = "1.0.0" 59 | source = "registry+https://github.com/rust-lang/crates.io-index" 60 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 61 | 62 | [[package]] 63 | name = "chrono" 64 | version = "0.4.19" 65 | source = "registry+https://github.com/rust-lang/crates.io-index" 66 | checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" 67 | dependencies = [ 68 | "js-sys", 69 | "libc", 70 | "num-integer", 71 | "num-traits", 72 | "serde", 73 | "time", 74 | "wasm-bindgen", 75 | "winapi", 76 | ] 77 | 78 | [[package]] 79 | name = "chrono-humanize" 80 | version = "0.2.1" 81 | source = "registry+https://github.com/rust-lang/crates.io-index" 82 | checksum = "2eddc119501d583fd930cb92144e605f44e0252c38dd89d9247fffa1993375cb" 83 | dependencies = [ 84 | "chrono", 85 | ] 86 | 87 | [[package]] 88 | name = "chrono-tz" 89 | version = "0.6.1" 90 | source = "registry+https://github.com/rust-lang/crates.io-index" 91 | checksum = "58549f1842da3080ce63002102d5bc954c7bc843d4f47818e642abdc36253552" 92 | dependencies = [ 93 | "chrono", 94 | "chrono-tz-build", 95 | "phf", 96 | ] 97 | 98 | [[package]] 99 | name = "chrono-tz-build" 100 | version = "0.0.2" 101 | source = "registry+https://github.com/rust-lang/crates.io-index" 102 | checksum = "db058d493fb2f65f41861bfed7e3fe6335264a9f0f92710cab5bdf01fef09069" 103 | dependencies = [ 104 | "parse-zoneinfo", 105 | "phf", 106 | "phf_codegen", 107 | ] 108 | 109 | [[package]] 110 | name = "console_error_panic_hook" 111 | version = "0.1.7" 112 | source = "registry+https://github.com/rust-lang/crates.io-index" 113 | checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" 114 | dependencies = [ 115 | "cfg-if 1.0.0", 116 | "wasm-bindgen", 117 | ] 118 | 119 | [[package]] 120 | name = "digest" 121 | version = "0.8.1" 122 | source = "registry+https://github.com/rust-lang/crates.io-index" 123 | checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" 124 | dependencies = [ 125 | "generic-array", 126 | ] 127 | 128 | [[package]] 129 | name = "fake-simd" 130 | version = "0.1.2" 131 | source = "registry+https://github.com/rust-lang/crates.io-index" 132 | checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" 133 | 134 | [[package]] 135 | name = "generic-array" 136 | version = "0.12.4" 137 | source = "registry+https://github.com/rust-lang/crates.io-index" 138 | checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd" 139 | dependencies = [ 140 | "typenum", 141 | ] 142 | 143 | [[package]] 144 | name = "getrandom" 145 | version = "0.2.3" 146 | source = "registry+https://github.com/rust-lang/crates.io-index" 147 | checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" 148 | dependencies = [ 149 | "cfg-if 1.0.0", 150 | "libc", 151 | "wasi", 152 | ] 153 | 154 | [[package]] 155 | name = "itoa" 156 | version = "0.4.8" 157 | source = "registry+https://github.com/rust-lang/crates.io-index" 158 | checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" 159 | 160 | [[package]] 161 | name = "js-sys" 162 | version = "0.3.55" 163 | source = "registry+https://github.com/rust-lang/crates.io-index" 164 | checksum = "7cc9ffccd38c451a86bf13657df244e9c3f37493cce8e5e21e940963777acc84" 165 | dependencies = [ 166 | "wasm-bindgen", 167 | ] 168 | 169 | [[package]] 170 | name = "lazy_static" 171 | version = "1.4.0" 172 | source = "registry+https://github.com/rust-lang/crates.io-index" 173 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 174 | 175 | [[package]] 176 | name = "libc" 177 | version = "0.2.109" 178 | source = "registry+https://github.com/rust-lang/crates.io-index" 179 | checksum = "f98a04dce437184842841303488f70d0188c5f51437d2a834dc097eafa909a01" 180 | 181 | [[package]] 182 | name = "libwhen" 183 | version = "0.4.0" 184 | dependencies = [ 185 | "chrono", 186 | "chrono-humanize", 187 | "chrono-tz", 188 | "localzone", 189 | "pest", 190 | "pest_derive", 191 | "serde", 192 | ] 193 | 194 | [[package]] 195 | name = "localzone" 196 | version = "0.2.0" 197 | source = "registry+https://github.com/rust-lang/crates.io-index" 198 | checksum = "7865f5a13ecb548180b8fe7e538d739090329fdb984eef9e9a16db214f216c6f" 199 | dependencies = [ 200 | "js-sys", 201 | "windows", 202 | ] 203 | 204 | [[package]] 205 | name = "log" 206 | version = "0.4.14" 207 | source = "registry+https://github.com/rust-lang/crates.io-index" 208 | checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" 209 | dependencies = [ 210 | "cfg-if 1.0.0", 211 | ] 212 | 213 | [[package]] 214 | name = "maplit" 215 | version = "1.0.2" 216 | source = "registry+https://github.com/rust-lang/crates.io-index" 217 | checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" 218 | 219 | [[package]] 220 | name = "memory_units" 221 | version = "0.4.0" 222 | source = "registry+https://github.com/rust-lang/crates.io-index" 223 | checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3" 224 | 225 | [[package]] 226 | name = "num-integer" 227 | version = "0.1.44" 228 | source = "registry+https://github.com/rust-lang/crates.io-index" 229 | checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" 230 | dependencies = [ 231 | "autocfg", 232 | "num-traits", 233 | ] 234 | 235 | [[package]] 236 | name = "num-traits" 237 | version = "0.2.14" 238 | source = "registry+https://github.com/rust-lang/crates.io-index" 239 | checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" 240 | dependencies = [ 241 | "autocfg", 242 | ] 243 | 244 | [[package]] 245 | name = "opaque-debug" 246 | version = "0.2.3" 247 | source = "registry+https://github.com/rust-lang/crates.io-index" 248 | checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" 249 | 250 | [[package]] 251 | name = "parse-zoneinfo" 252 | version = "0.3.0" 253 | source = "registry+https://github.com/rust-lang/crates.io-index" 254 | checksum = "c705f256449c60da65e11ff6626e0c16a0a0b96aaa348de61376b249bc340f41" 255 | dependencies = [ 256 | "regex", 257 | ] 258 | 259 | [[package]] 260 | name = "pest" 261 | version = "2.1.3" 262 | source = "registry+https://github.com/rust-lang/crates.io-index" 263 | checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53" 264 | dependencies = [ 265 | "ucd-trie", 266 | ] 267 | 268 | [[package]] 269 | name = "pest_derive" 270 | version = "2.1.0" 271 | source = "registry+https://github.com/rust-lang/crates.io-index" 272 | checksum = "833d1ae558dc601e9a60366421196a8d94bc0ac980476d0b67e1d0988d72b2d0" 273 | dependencies = [ 274 | "pest", 275 | "pest_generator", 276 | ] 277 | 278 | [[package]] 279 | name = "pest_generator" 280 | version = "2.1.3" 281 | source = "registry+https://github.com/rust-lang/crates.io-index" 282 | checksum = "99b8db626e31e5b81787b9783425769681b347011cc59471e33ea46d2ea0cf55" 283 | dependencies = [ 284 | "pest", 285 | "pest_meta", 286 | "proc-macro2", 287 | "quote", 288 | "syn", 289 | ] 290 | 291 | [[package]] 292 | name = "pest_meta" 293 | version = "2.1.3" 294 | source = "registry+https://github.com/rust-lang/crates.io-index" 295 | checksum = "54be6e404f5317079812fc8f9f5279de376d8856929e21c184ecf6bbd692a11d" 296 | dependencies = [ 297 | "maplit", 298 | "pest", 299 | "sha-1", 300 | ] 301 | 302 | [[package]] 303 | name = "phf" 304 | version = "0.10.0" 305 | source = "registry+https://github.com/rust-lang/crates.io-index" 306 | checksum = "b9fc3db1018c4b59d7d582a739436478b6035138b6aecbce989fc91c3e98409f" 307 | dependencies = [ 308 | "phf_shared", 309 | ] 310 | 311 | [[package]] 312 | name = "phf_codegen" 313 | version = "0.10.0" 314 | source = "registry+https://github.com/rust-lang/crates.io-index" 315 | checksum = "4fb1c3a8bc4dd4e5cfce29b44ffc14bedd2ee294559a294e2a4d4c9e9a6a13cd" 316 | dependencies = [ 317 | "phf_generator", 318 | "phf_shared", 319 | ] 320 | 321 | [[package]] 322 | name = "phf_generator" 323 | version = "0.10.0" 324 | source = "registry+https://github.com/rust-lang/crates.io-index" 325 | checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" 326 | dependencies = [ 327 | "phf_shared", 328 | "rand", 329 | ] 330 | 331 | [[package]] 332 | name = "phf_shared" 333 | version = "0.10.0" 334 | source = "registry+https://github.com/rust-lang/crates.io-index" 335 | checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" 336 | dependencies = [ 337 | "siphasher", 338 | "uncased", 339 | ] 340 | 341 | [[package]] 342 | name = "ppv-lite86" 343 | version = "0.2.15" 344 | source = "registry+https://github.com/rust-lang/crates.io-index" 345 | checksum = "ed0cfbc8191465bed66e1718596ee0b0b35d5ee1f41c5df2189d0fe8bde535ba" 346 | 347 | [[package]] 348 | name = "proc-macro2" 349 | version = "1.0.33" 350 | source = "registry+https://github.com/rust-lang/crates.io-index" 351 | checksum = "fb37d2df5df740e582f28f8560cf425f52bb267d872fe58358eadb554909f07a" 352 | dependencies = [ 353 | "unicode-xid", 354 | ] 355 | 356 | [[package]] 357 | name = "quote" 358 | version = "1.0.10" 359 | source = "registry+https://github.com/rust-lang/crates.io-index" 360 | checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05" 361 | dependencies = [ 362 | "proc-macro2", 363 | ] 364 | 365 | [[package]] 366 | name = "rand" 367 | version = "0.8.4" 368 | source = "registry+https://github.com/rust-lang/crates.io-index" 369 | checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" 370 | dependencies = [ 371 | "libc", 372 | "rand_chacha", 373 | "rand_core", 374 | "rand_hc", 375 | ] 376 | 377 | [[package]] 378 | name = "rand_chacha" 379 | version = "0.3.1" 380 | source = "registry+https://github.com/rust-lang/crates.io-index" 381 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 382 | dependencies = [ 383 | "ppv-lite86", 384 | "rand_core", 385 | ] 386 | 387 | [[package]] 388 | name = "rand_core" 389 | version = "0.6.3" 390 | source = "registry+https://github.com/rust-lang/crates.io-index" 391 | checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" 392 | dependencies = [ 393 | "getrandom", 394 | ] 395 | 396 | [[package]] 397 | name = "rand_hc" 398 | version = "0.3.1" 399 | source = "registry+https://github.com/rust-lang/crates.io-index" 400 | checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7" 401 | dependencies = [ 402 | "rand_core", 403 | ] 404 | 405 | [[package]] 406 | name = "regex" 407 | version = "1.5.4" 408 | source = "registry+https://github.com/rust-lang/crates.io-index" 409 | checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" 410 | dependencies = [ 411 | "regex-syntax", 412 | ] 413 | 414 | [[package]] 415 | name = "regex-syntax" 416 | version = "0.6.25" 417 | source = "registry+https://github.com/rust-lang/crates.io-index" 418 | checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" 419 | 420 | [[package]] 421 | name = "ryu" 422 | version = "1.0.6" 423 | source = "registry+https://github.com/rust-lang/crates.io-index" 424 | checksum = "3c9613b5a66ab9ba26415184cfc41156594925a9cf3a2057e57f31ff145f6568" 425 | 426 | [[package]] 427 | name = "scoped-tls" 428 | version = "1.0.0" 429 | source = "registry+https://github.com/rust-lang/crates.io-index" 430 | checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" 431 | 432 | [[package]] 433 | name = "serde" 434 | version = "1.0.131" 435 | source = "registry+https://github.com/rust-lang/crates.io-index" 436 | checksum = "b4ad69dfbd3e45369132cc64e6748c2d65cdfb001a2b1c232d128b4ad60561c1" 437 | dependencies = [ 438 | "serde_derive", 439 | ] 440 | 441 | [[package]] 442 | name = "serde_derive" 443 | version = "1.0.131" 444 | source = "registry+https://github.com/rust-lang/crates.io-index" 445 | checksum = "b710a83c4e0dff6a3d511946b95274ad9ca9e5d3ae497b63fda866ac955358d2" 446 | dependencies = [ 447 | "proc-macro2", 448 | "quote", 449 | "syn", 450 | ] 451 | 452 | [[package]] 453 | name = "serde_json" 454 | version = "1.0.72" 455 | source = "registry+https://github.com/rust-lang/crates.io-index" 456 | checksum = "d0ffa0837f2dfa6fb90868c2b5468cad482e175f7dad97e7421951e663f2b527" 457 | dependencies = [ 458 | "itoa", 459 | "ryu", 460 | "serde", 461 | ] 462 | 463 | [[package]] 464 | name = "sha-1" 465 | version = "0.8.2" 466 | source = "registry+https://github.com/rust-lang/crates.io-index" 467 | checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df" 468 | dependencies = [ 469 | "block-buffer", 470 | "digest", 471 | "fake-simd", 472 | "opaque-debug", 473 | ] 474 | 475 | [[package]] 476 | name = "siphasher" 477 | version = "0.3.7" 478 | source = "registry+https://github.com/rust-lang/crates.io-index" 479 | checksum = "533494a8f9b724d33625ab53c6c4800f7cc445895924a8ef649222dcb76e938b" 480 | 481 | [[package]] 482 | name = "syn" 483 | version = "1.0.82" 484 | source = "registry+https://github.com/rust-lang/crates.io-index" 485 | checksum = "8daf5dd0bb60cbd4137b1b587d2fc0ae729bc07cf01cd70b36a1ed5ade3b9d59" 486 | dependencies = [ 487 | "proc-macro2", 488 | "quote", 489 | "unicode-xid", 490 | ] 491 | 492 | [[package]] 493 | name = "time" 494 | version = "0.1.44" 495 | source = "registry+https://github.com/rust-lang/crates.io-index" 496 | checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" 497 | dependencies = [ 498 | "libc", 499 | "wasi", 500 | "winapi", 501 | ] 502 | 503 | [[package]] 504 | name = "typenum" 505 | version = "1.14.0" 506 | source = "registry+https://github.com/rust-lang/crates.io-index" 507 | checksum = "b63708a265f51345575b27fe43f9500ad611579e764c79edbc2037b1121959ec" 508 | 509 | [[package]] 510 | name = "ucd-trie" 511 | version = "0.1.3" 512 | source = "registry+https://github.com/rust-lang/crates.io-index" 513 | checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" 514 | 515 | [[package]] 516 | name = "uncased" 517 | version = "0.9.6" 518 | source = "registry+https://github.com/rust-lang/crates.io-index" 519 | checksum = "5baeed7327e25054889b9bd4f975f32e5f4c5d434042d59ab6cd4142c0a76ed0" 520 | dependencies = [ 521 | "version_check", 522 | ] 523 | 524 | [[package]] 525 | name = "unicode-xid" 526 | version = "0.2.2" 527 | source = "registry+https://github.com/rust-lang/crates.io-index" 528 | checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" 529 | 530 | [[package]] 531 | name = "version_check" 532 | version = "0.9.3" 533 | source = "registry+https://github.com/rust-lang/crates.io-index" 534 | checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" 535 | 536 | [[package]] 537 | name = "wasi" 538 | version = "0.10.0+wasi-snapshot-preview1" 539 | source = "registry+https://github.com/rust-lang/crates.io-index" 540 | checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" 541 | 542 | [[package]] 543 | name = "wasm-bindgen" 544 | version = "0.2.78" 545 | source = "registry+https://github.com/rust-lang/crates.io-index" 546 | checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce" 547 | dependencies = [ 548 | "cfg-if 1.0.0", 549 | "wasm-bindgen-macro", 550 | ] 551 | 552 | [[package]] 553 | name = "wasm-bindgen-backend" 554 | version = "0.2.78" 555 | source = "registry+https://github.com/rust-lang/crates.io-index" 556 | checksum = "a317bf8f9fba2476b4b2c85ef4c4af8ff39c3c7f0cdfeed4f82c34a880aa837b" 557 | dependencies = [ 558 | "bumpalo", 559 | "lazy_static", 560 | "log", 561 | "proc-macro2", 562 | "quote", 563 | "syn", 564 | "wasm-bindgen-shared", 565 | ] 566 | 567 | [[package]] 568 | name = "wasm-bindgen-futures" 569 | version = "0.4.28" 570 | source = "registry+https://github.com/rust-lang/crates.io-index" 571 | checksum = "8e8d7523cb1f2a4c96c1317ca690031b714a51cc14e05f712446691f413f5d39" 572 | dependencies = [ 573 | "cfg-if 1.0.0", 574 | "js-sys", 575 | "wasm-bindgen", 576 | "web-sys", 577 | ] 578 | 579 | [[package]] 580 | name = "wasm-bindgen-macro" 581 | version = "0.2.78" 582 | source = "registry+https://github.com/rust-lang/crates.io-index" 583 | checksum = "d56146e7c495528bf6587663bea13a8eb588d39b36b679d83972e1a2dbbdacf9" 584 | dependencies = [ 585 | "quote", 586 | "wasm-bindgen-macro-support", 587 | ] 588 | 589 | [[package]] 590 | name = "wasm-bindgen-macro-support" 591 | version = "0.2.78" 592 | source = "registry+https://github.com/rust-lang/crates.io-index" 593 | checksum = "7803e0eea25835f8abdc585cd3021b3deb11543c6fe226dcd30b228857c5c5ab" 594 | dependencies = [ 595 | "proc-macro2", 596 | "quote", 597 | "syn", 598 | "wasm-bindgen-backend", 599 | "wasm-bindgen-shared", 600 | ] 601 | 602 | [[package]] 603 | name = "wasm-bindgen-shared" 604 | version = "0.2.78" 605 | source = "registry+https://github.com/rust-lang/crates.io-index" 606 | checksum = "0237232789cf037d5480773fe568aac745bfe2afbc11a863e97901780a6b47cc" 607 | 608 | [[package]] 609 | name = "wasm-bindgen-test" 610 | version = "0.3.28" 611 | source = "registry+https://github.com/rust-lang/crates.io-index" 612 | checksum = "96f1aa7971fdf61ef0f353602102dbea75a56e225ed036c1e3740564b91e6b7e" 613 | dependencies = [ 614 | "console_error_panic_hook", 615 | "js-sys", 616 | "scoped-tls", 617 | "wasm-bindgen", 618 | "wasm-bindgen-futures", 619 | "wasm-bindgen-test-macro", 620 | ] 621 | 622 | [[package]] 623 | name = "wasm-bindgen-test-macro" 624 | version = "0.3.28" 625 | source = "registry+https://github.com/rust-lang/crates.io-index" 626 | checksum = "6006f79628dfeb96a86d4db51fbf1344cd7fd8408f06fc9aa3c84913a4789688" 627 | dependencies = [ 628 | "proc-macro2", 629 | "quote", 630 | ] 631 | 632 | [[package]] 633 | name = "web-sys" 634 | version = "0.3.55" 635 | source = "registry+https://github.com/rust-lang/crates.io-index" 636 | checksum = "38eb105f1c59d9eaa6b5cdc92b859d85b926e82cb2e0945cd0c9259faa6fe9fb" 637 | dependencies = [ 638 | "js-sys", 639 | "wasm-bindgen", 640 | ] 641 | 642 | [[package]] 643 | name = "wee_alloc" 644 | version = "0.4.5" 645 | source = "registry+https://github.com/rust-lang/crates.io-index" 646 | checksum = "dbb3b5a6b2bb17cb6ad44a2e68a43e8d2722c997da10e928665c72ec6c0a0b8e" 647 | dependencies = [ 648 | "cfg-if 0.1.10", 649 | "libc", 650 | "memory_units", 651 | "winapi", 652 | ] 653 | 654 | [[package]] 655 | name = "when-web" 656 | version = "0.1.0" 657 | dependencies = [ 658 | "chrono", 659 | "console_error_panic_hook", 660 | "libwhen", 661 | "serde", 662 | "serde_json", 663 | "wasm-bindgen", 664 | "wasm-bindgen-test", 665 | "wee_alloc", 666 | ] 667 | 668 | [[package]] 669 | name = "winapi" 670 | version = "0.3.9" 671 | source = "registry+https://github.com/rust-lang/crates.io-index" 672 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 673 | dependencies = [ 674 | "winapi-i686-pc-windows-gnu", 675 | "winapi-x86_64-pc-windows-gnu", 676 | ] 677 | 678 | [[package]] 679 | name = "winapi-i686-pc-windows-gnu" 680 | version = "0.4.0" 681 | source = "registry+https://github.com/rust-lang/crates.io-index" 682 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 683 | 684 | [[package]] 685 | name = "winapi-x86_64-pc-windows-gnu" 686 | version = "0.4.0" 687 | source = "registry+https://github.com/rust-lang/crates.io-index" 688 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 689 | 690 | [[package]] 691 | name = "windows" 692 | version = "0.28.0" 693 | source = "registry+https://github.com/rust-lang/crates.io-index" 694 | checksum = "054d31561409bbf7e1ee4a4f0a1233ac2bb79cfadf2a398438a04d8dda69225f" 695 | dependencies = [ 696 | "windows-sys", 697 | ] 698 | 699 | [[package]] 700 | name = "windows-sys" 701 | version = "0.28.0" 702 | source = "registry+https://github.com/rust-lang/crates.io-index" 703 | checksum = "82ca39602d5cbfa692c4b67e3bcbb2751477355141c1ed434c94da4186836ff6" 704 | dependencies = [ 705 | "windows_aarch64_msvc", 706 | "windows_i686_gnu", 707 | "windows_i686_msvc", 708 | "windows_x86_64_gnu", 709 | "windows_x86_64_msvc", 710 | ] 711 | 712 | [[package]] 713 | name = "windows_aarch64_msvc" 714 | version = "0.28.0" 715 | source = "registry+https://github.com/rust-lang/crates.io-index" 716 | checksum = "52695a41e536859d5308cc613b4a022261a274390b25bd29dfff4bf08505f3c2" 717 | 718 | [[package]] 719 | name = "windows_i686_gnu" 720 | version = "0.28.0" 721 | source = "registry+https://github.com/rust-lang/crates.io-index" 722 | checksum = "f54725ac23affef038fecb177de6c9bf065787c2f432f79e3c373da92f3e1d8a" 723 | 724 | [[package]] 725 | name = "windows_i686_msvc" 726 | version = "0.28.0" 727 | source = "registry+https://github.com/rust-lang/crates.io-index" 728 | checksum = "51d5158a43cc43623c0729d1ad6647e62fa384a3d135fd15108d37c683461f64" 729 | 730 | [[package]] 731 | name = "windows_x86_64_gnu" 732 | version = "0.28.0" 733 | source = "registry+https://github.com/rust-lang/crates.io-index" 734 | checksum = "bc31f409f565611535130cfe7ee8e6655d3fa99c1c61013981e491921b5ce954" 735 | 736 | [[package]] 737 | name = "windows_x86_64_msvc" 738 | version = "0.28.0" 739 | source = "registry+https://github.com/rust-lang/crates.io-index" 740 | checksum = "3f2b8c7cbd3bfdddd9ab98769f9746a7fad1bca236554cd032b78d768bc0e89f" 741 | -------------------------------------------------------------------------------- /web/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "when-web" 3 | version = "0.1.0" 4 | authors = ["Armin Ronacher "] 5 | edition = "2021" 6 | 7 | [lib] 8 | crate-type = ["cdylib", "rlib"] 9 | 10 | [features] 11 | default = ["console_error_panic_hook"] 12 | 13 | [dependencies] 14 | wasm-bindgen = "0.2.63" 15 | console_error_panic_hook = { version = "0.1.6", optional = true } 16 | wee_alloc = { version = "0.4.5", optional = true } 17 | libwhen = { path = "../libwhen" } 18 | chrono = { version = "0.4.19", features = ["wasmbind", "js-sys"] } 19 | serde_json = "1.0.72" 20 | serde = { version = "1.0.131", features = ["derive"] } 21 | 22 | [dev-dependencies] 23 | wasm-bindgen-test = "0.3.13" 24 | 25 | [profile.release] 26 | # Tell `rustc` to optimize for small code size. 27 | opt-level = "s" 28 | 29 | [workspace] 30 | 31 | [package.metadata.wasm-pack.profile.release] 32 | wasm-opt = false 33 | -------------------------------------------------------------------------------- /web/src/lib.rs: -------------------------------------------------------------------------------- 1 | use serde::Serialize; 2 | use wasm_bindgen::prelude::*; 3 | 4 | use libwhen::TimeAtLocation; 5 | 6 | // When the `wee_alloc` feature is enabled, use `wee_alloc` as the global 7 | // allocator. 8 | #[cfg(feature = "wee_alloc")] 9 | #[global_allocator] 10 | static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; 11 | 12 | #[derive(Serialize)] 13 | pub struct ParseResult { 14 | is_relative: bool, 15 | locations: Vec, 16 | error: Option, 17 | } 18 | 19 | fn handle_expr(input: &str) -> Result<(Vec, bool), String> { 20 | let expr = libwhen::InputExpr::parse(&input).map_err(|x| x.to_string())?; 21 | Ok(expr 22 | .process() 23 | .map(|x| (x, expr.is_relative())) 24 | .map_err(|x| x.to_string())?) 25 | } 26 | 27 | #[wasm_bindgen] 28 | pub fn parse_expr(input: String) -> String { 29 | let (locations, is_relative, error) = match handle_expr(&input) { 30 | Ok((locations, is_relative)) => (locations, is_relative, None), 31 | Err(err) => (Vec::new(), false, Some(err.to_string())), 32 | }; 33 | serde_json::to_string(&ParseResult { 34 | is_relative, 35 | locations, 36 | error, 37 | }) 38 | .unwrap() 39 | } 40 | 41 | #[wasm_bindgen] 42 | pub fn set_panic_hook() { 43 | // When the `console_error_panic_hook` feature is enabled, we can call the 44 | // `set_panic_hook` function at least once during initialization, and then 45 | // we will get better error messages if our code ever panics. 46 | // 47 | // For more details see 48 | // https://github.com/rustwasm/console_error_panic_hook#readme 49 | #[cfg(feature = "console_error_panic_hook")] 50 | console_error_panic_hook::set_once(); 51 | } 52 | -------------------------------------------------------------------------------- /web/tests/web.rs: -------------------------------------------------------------------------------- 1 | //! Test suite for the Web and headless browsers. 2 | 3 | #![cfg(target_arch = "wasm32")] 4 | 5 | extern crate wasm_bindgen_test; 6 | use wasm_bindgen_test::*; 7 | 8 | wasm_bindgen_test_configure!(run_in_browser); 9 | 10 | #[wasm_bindgen_test] 11 | fn pass() { 12 | assert_eq!(1 + 1, 2); 13 | } 14 | -------------------------------------------------------------------------------- /web/www/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | -------------------------------------------------------------------------------- /web/www/bootstrap.js: -------------------------------------------------------------------------------- 1 | // A dependency graph that contains any wasm must all be imported 2 | // asynchronously. This `bootstrap.js` file does the single async import, so 3 | // that no one else needs to worry about it again. 4 | import("./index.jsx") 5 | .catch(e => console.error("Error importing `index.js`:", e)); 6 | -------------------------------------------------------------------------------- /web/www/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | <%= htmlWebpackPlugin.options.title %> 7 | when? 8 | 11 | 12 | 13 |
14 | 15 | 16 | -------------------------------------------------------------------------------- /web/www/index.jsx: -------------------------------------------------------------------------------- 1 | import * as wasm from "when"; 2 | import React, { useEffect, useState, useRef } from "react"; 3 | import ReactDOM from "react-dom"; 4 | import "./style.css"; 5 | 6 | wasm.set_panic_hook(); 7 | 8 | const EXAMPLES = [ 9 | "now", 10 | "2 hours ago in yyz", 11 | "5pm in yyz -> sfo", 12 | "5pm in Vienna -> London", 13 | "4pm on 17.05.2021 in Vienna -> Tokyo", 14 | "in 4 hours in San Francisco", 15 | "2pm in 2 days in New Delhi", 16 | "now in yyz -> sfo -> vie -> lhr", 17 | "unix 1639067620 in Tokyo", 18 | ]; 19 | 20 | function evaluateDateExpr(input) { 21 | return JSON.parse(wasm.parse_expr(input || "now")); 22 | } 23 | 24 | function parseDate(datetime) { 25 | const match = datetime.match(/^([^T]+)T([^.]+)/); 26 | return { 27 | time: match[2], 28 | date: match[1], 29 | }; 30 | } 31 | 32 | function Location({ location: loc }) { 33 | const dt = parseDate(loc.datetime); 34 | return ( 35 | 36 | 37 | 38 | 39 | 44 | 45 | 46 | 47 | 50 | 51 | 52 | 53 | 57 | 58 | {loc.location && ( 59 | 60 | 61 | 69 | 70 | )} 71 | 72 |
Time 40 | {dt.time} ( 41 | {loc.relative_to_now_human}{' — '} 42 | {loc.time_of_day.replace(/_/g, " ")}) 43 |
Date 48 | {dt.date} 49 |
Zone 54 | {loc.timezone.name} ( 55 | {loc.timezone.abbrev}; {loc.timezone.utc_offset}) 56 |
Location 62 | {loc.location.name} 63 | {loc.location.admin_code 64 | ? ` (${loc.location.admin_code}; ${loc.location.country})` 65 | : loc.location.country 66 | ? ` (${loc.location.country})` 67 | : null} 68 |
73 | ); 74 | } 75 | 76 | function Examples({setExpr}) { 77 | return ( 78 |
79 |

Need inspiration? Try some of these

80 | 95 |
96 | ); 97 | } 98 | 99 | function Results({locations}) { 100 | return ( 101 |
    102 | {locations.map((loc, idx) => ( 103 |
  • 104 | 105 |
  • 106 | ))} 107 |
108 | ); 109 | } 110 | 111 | function getTextResults(locations) { 112 | return locations 113 | .map((loc) => { 114 | const dt = parseDate(loc.datetime); 115 | const lines = [ 116 | `time: ${dt.time} (${loc.relative_to_now_human}; ${loc.time_of_day.replace(/_/g, " ")})`, 117 | `date: ${dt.date}`, 118 | `zone: ${loc.timezone.name} (${loc.timezone.abbrev}; ${loc.timezone.utc_offset})`, 119 | ]; 120 | if (loc.location) { 121 | let location = `location: ${loc.location.name}`; 122 | if (loc.location.admin_code) { 123 | location += ` (${loc.location.admin_code}; ${loc.location.country})`; 124 | } else if (loc.location.country) { 125 | location += ` (${loc.location.country})`; 126 | } 127 | lines.push(location); 128 | } 129 | return lines.join("\n"); 130 | }) 131 | .join("\n\n"); 132 | } 133 | 134 | function PlainTextResults({locations}) { 135 | const ref = useRef(); 136 | return
 {
137 |     let range = document.createRange();
138 |     range.selectNodeContents(ref.current);
139 |     let sel = window.getSelection();
140 |     sel.removeAllRanges();
141 |     sel.addRange(range);
142 |   }}>{getTextResults(locations)}
; 143 | } 144 | 145 | function App() { 146 | const url = new URL(window.location); 147 | const [inc, setInc] = useState(0); 148 | const [asText, setAsText] = useState(url.searchParams.get("format") == "text"); 149 | const [expr, setExpr] = useState(url.searchParams.get("input") || ""); 150 | const inputRef = useRef(); 151 | const rv = evaluateDateExpr(expr); 152 | 153 | useEffect(() => { 154 | const url = new URL(window.location); 155 | url.searchParams.set("input", expr); 156 | if (asText) { 157 | url.searchParams.set("format", "text"); 158 | } else { 159 | url.searchParams.delete("format"); 160 | } 161 | window.history.replaceState({}, "", url); 162 | 163 | if (rv.is_relative && !asText) { 164 | const timer = setTimeout(() => { 165 | setInc(inc + 1); 166 | }, 1000); 167 | return () => clearTimeout(timer); 168 | } 169 | }, [inc, rv.is_relative, asText, location.search]); 170 | 171 | function setExprAndFocus(value) { 172 | setExpr(value); 173 | if (inputRef.current) { 174 | inputRef.current.focus(); 175 | } 176 | } 177 | 178 | const showResults = expr && rv.locations.length > 0; 179 | 180 | return ( 181 |
182 |
183 |

when?

184 | huh? 185 | {" | "} 186 | who? 187 |
188 | setExprAndFocus("")} 190 | title="Clear input (ESC)" 191 | className="clear" 192 | > 193 | x 194 | 195 | { 200 | if (evt.key === "Escape") { 201 | setExprAndFocus(""); 202 | } 203 | }} 204 | onChange={(evt) => { 205 | setExpr(evt.target.value); 206 | }} 207 | size="40" 208 | autoFocus 209 | /> 210 | {!expr && } 211 | {showResults ? ( 212 | 221 | ) : null} 222 | {showResults ? (asText 223 | ? 224 | : ) : null} 225 | {rv.error && ( 226 |

227 | Ugh: 228 | {" " + rv.error + " :-("} 229 |

230 | )} 231 |
232 | ); 233 | } 234 | 235 | ReactDOM.render(, document.getElementById("root")); 236 | -------------------------------------------------------------------------------- /web/www/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "when", 3 | "version": "0.1.0", 4 | "main": "bootstrap.js", 5 | "scripts": { 6 | "build": "webpack --config webpack.prod.js", 7 | "start": "webpack-dev-server" 8 | }, 9 | "license": "(MIT OR Apache-2.0)", 10 | "devDependencies": { 11 | "@babel/core": "^7.16.0", 12 | "@babel/preset-env": "^7.16.4", 13 | "@babel/preset-react": "^7.16.0", 14 | "babel-loader": "^8.2.3", 15 | "css-loader": "^6.5.1", 16 | "html-webpack-plugin": "^5.5.0", 17 | "style-loader": "^3.3.1", 18 | "webpack": "^5.56.0", 19 | "webpack-cli": "^4.9.0", 20 | "webpack-dev-server": "^4.6.0", 21 | "webpack-merge": "^5.8.0", 22 | "when": "file:../pkg" 23 | }, 24 | "dependencies": { 25 | "react": "^17.0.2", 26 | "react-dom": "^17.0.2" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /web/www/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | background: rgb(41, 41, 40); 3 | margin: 0; 4 | padding: 20px; 5 | } 6 | a { 7 | color: #888; 8 | } 9 | a:hover { 10 | color: rgb(208, 145, 28); 11 | cursor: pointer; 12 | text-decoration: underline; 13 | } 14 | body, input { 15 | color: white; 16 | font-family: Verdana, sans-serif; 17 | font-size: 19px; 18 | } 19 | h1 { 20 | font-size: 28px; 21 | font-weight: normal; 22 | margin: 0; 23 | padding: 0 10px 0 0; 24 | display: inline; 25 | } 26 | #root { 27 | max-width: 900px; 28 | margin: 30px auto 10px auto; 29 | position: relative; 30 | } 31 | input { 32 | width: calc(100% - 16px); 33 | background: rgb(57, 57, 53); 34 | border: none; 35 | border-bottom: 2px solid #888; 36 | padding: 4px 8px; 37 | color: #aaa; 38 | font-weight: bold; 39 | margin: 20px 0 20px 0; 40 | border-radius: 0; 41 | } 42 | input:focus { 43 | outline: none; 44 | color: white; 45 | } 46 | a.clear { 47 | position: absolute; 48 | right: 10px; 49 | margin-top: 22px; 50 | text-decoration: none; 51 | color: white; 52 | } 53 | #root ul { 54 | margin: 0; 55 | padding: 0; 56 | list-style: none; 57 | } 58 | #root table { 59 | margin-bottom: 20px; 60 | } 61 | #root table th { 62 | width: 100px; 63 | font-size: 14px; 64 | text-align: right; 65 | padding-right: 7px; 66 | color: #aaa; 67 | } 68 | span.time { color: rgb(208, 145, 28); } 69 | span.date { color: rgb(23, 143, 143); } 70 | span.zone { text-decoration: underline; } 71 | p.error { color: rgb(196, 138, 117); margin: 0; } 72 | div.examples { 73 | padding: 0 0 0 20px; 74 | } 75 | div.examples h3 { 76 | font-size: 18px; 77 | margin: 0 0 8px -20px; 78 | padding: 0; 79 | } 80 | 81 | div.actions { 82 | margin-top: -10px; 83 | text-align: right; 84 | font-size: 80%; 85 | } 86 | 87 | pre { 88 | font-size: 85%; 89 | } 90 | 91 | @media only screen and (max-width: 600px) { 92 | body { 93 | font-size: 15px; 94 | } 95 | a.clear { 96 | margin-top: 25px; 97 | } 98 | #root table th { 99 | width: 70px; 100 | } 101 | } -------------------------------------------------------------------------------- /web/www/webpack.config.js: -------------------------------------------------------------------------------- 1 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 2 | const path = require('path'); 3 | 4 | module.exports = { 5 | entry: "./bootstrap.js", 6 | output: { 7 | path: path.resolve(__dirname, "dist"), 8 | filename: "bootstrap.js", 9 | }, 10 | module: { 11 | rules: [ 12 | { 13 | test: /\.?jsx?$/, 14 | exclude: /node_modules/, 15 | use: { 16 | loader: "babel-loader", 17 | options: { 18 | presets: ['@babel/preset-env', '@babel/preset-react'] 19 | } 20 | } 21 | }, 22 | { 23 | test: /\.css$/i, 24 | use: ["style-loader", "css-loader"], 25 | } 26 | ] 27 | }, 28 | mode: "development", 29 | experiments: { 30 | asyncWebAssembly: true, 31 | }, 32 | plugins: [ 33 | new HtmlWebpackPlugin({ 34 | title: "when?", 35 | template: "index.html" 36 | }), 37 | ], 38 | }; 39 | -------------------------------------------------------------------------------- /web/www/webpack.prod.js: -------------------------------------------------------------------------------- 1 | const { merge } = require('webpack-merge'); 2 | const common = require('./webpack.config.js'); 3 | 4 | module.exports = merge(common, { 5 | mode: 'production', 6 | }); 7 | --------------------------------------------------------------------------------