├── .github
└── workflows
│ ├── build-and-test.yml
│ ├── coverage.yml
│ └── release.yml
├── .gitignore
├── .npmignore
├── LICENSE
├── README.md
├── api
└── index.js
├── builds
├── spacetime.cjs
├── spacetime.min.js
└── spacetime.mjs
├── changelog.md
├── codecov.yml
├── contributing.md
├── eslint.config.js
├── package-lock.json
├── package.json
├── plugins
├── age
│ ├── README.md
│ ├── cli.js
│ ├── package-lock.json
│ ├── package.json
│ ├── rollup.config.js
│ └── src
│ │ └── index.js
├── daylight
│ ├── README.md
│ ├── builds
│ │ ├── spacetime-daylight.cjs
│ │ ├── spacetime-daylight.min.js
│ │ └── spacetime-daylight.mjs
│ ├── package-lock.json
│ ├── package.json
│ ├── rollup.config.js
│ ├── scratch.js
│ ├── src
│ │ ├── index.js
│ │ ├── solstices.js
│ │ └── sunPosition.js
│ └── tests
│ │ ├── daytime.test.js
│ │ ├── hemisphere.test.js
│ │ ├── solstice.test.js
│ │ └── sunlight.test.js
├── dst
│ ├── README.md
│ ├── anytime
│ │ ├── lux.js
│ │ └── parse.js
│ ├── old
│ │ ├── scripts
│ │ │ ├── findPatterns.js
│ │ │ ├── getzones.js
│ │ │ ├── split-2.js
│ │ │ ├── split-to-files.js
│ │ │ └── tznames-lib.js
│ │ ├── zonefile
│ │ │ ├── aliases.js
│ │ │ ├── dst.js
│ │ │ ├── missing.js
│ │ │ ├── north.js
│ │ │ └── south.js
│ │ └── zones
│ │ │ ├── dst-patterns.js
│ │ │ ├── index.js
│ │ │ └── patterns.js
│ ├── script
│ │ └── list-changes.js
│ ├── src
│ │ ├── calc.js
│ │ ├── index.js
│ │ ├── patterns.js
│ │ ├── test.js
│ │ └── zonefile.2022.js
│ ├── test
│ │ ├── eastern.test.js
│ │ └── europe.test.js
│ ├── tzdb
│ │ ├── aliases.js
│ │ ├── download.js
│ │ ├── parse.js
│ │ ├── test.js
│ │ ├── time_zone.csv
│ │ └── titlecase.js
│ └── zonefile
│ │ ├── dst-patterns.js
│ │ ├── index.js
│ │ ├── metas.js
│ │ └── zones.js
├── dst2
│ ├── package.json
│ ├── reporter.js
│ ├── scratch.js
│ ├── src
│ │ ├── add.js
│ │ ├── index.js
│ │ ├── lib
│ │ │ ├── format.js
│ │ │ ├── native.js
│ │ │ └── parse.js
│ │ └── units.js
│ └── tests
│ │ ├── _lib.js
│ │ ├── auto.test.js
│ │ ├── manual.test.js
│ │ └── tmp.test.js
├── geo
│ ├── README.md
│ ├── builds
│ │ ├── spacetime-geo.cjs
│ │ ├── spacetime-geo.min.js
│ │ └── spacetime-geo.mjs
│ ├── package-lock.json
│ ├── package.json
│ ├── rollup.config.js
│ ├── scratch.js
│ ├── scripts
│ │ └── getPoints
│ │ │ ├── iana.json
│ │ │ ├── index.js
│ │ │ └── qa-test.js
│ ├── src
│ │ ├── findTz
│ │ │ └── index.js
│ │ ├── index.js
│ │ └── point
│ │ │ ├── IANA-points.js
│ │ │ └── index.js
│ └── tests
│ │ ├── in.test.js
│ │ └── point.test.js
├── holiday
│ ├── README.md
│ ├── builds
│ │ ├── spacetime-holiday.cjs
│ │ ├── spacetime-holiday.min.js
│ │ └── spacetime-holiday.mjs
│ ├── changelog.md
│ ├── package-lock.json
│ ├── package.json
│ ├── rollup.config.js
│ ├── scratch.js
│ ├── src
│ │ ├── 01-fixedDates.js
│ │ ├── 02-nthWeekday.js
│ │ ├── 03-easterDates.js
│ │ ├── 04-astronomical.js
│ │ ├── 05-lunarDates.js
│ │ ├── holidays
│ │ │ ├── astro-holidays.js
│ │ │ ├── calendar-holidays.js
│ │ │ ├── easter-holidays.js
│ │ │ ├── fixed-holidays.js
│ │ │ ├── lunar-holidays.js
│ │ │ └── misc-holidays.js
│ │ ├── index.js
│ │ └── lib
│ │ │ ├── calcEaster.js
│ │ │ └── seasons.js
│ └── tests
│ │ ├── _lib.js
│ │ └── misc.test.js
├── play
│ ├── scratch.js
│ └── src
│ │ ├── Ticker.js
│ │ └── index.js
├── ticks
│ ├── README.md
│ ├── _version.js
│ ├── assets
│ │ ├── bundle.js
│ │ └── spencer.min.css
│ ├── builds
│ │ ├── spacetime-ticks.cjs
│ │ ├── spacetime-ticks.min.js
│ │ └── spacetime-ticks.mjs
│ ├── demo
│ │ ├── _drawGraph.js
│ │ ├── custom.js
│ │ └── duration.js
│ ├── index.html
│ ├── index.js
│ ├── package-lock.json
│ ├── package.json
│ ├── rollup.config.js
│ ├── scratch.js
│ ├── scripts
│ │ ├── filesize.js
│ │ └── version.js
│ └── src
│ │ ├── _reduce.js
│ │ ├── index.js
│ │ └── methods.js
├── tz
│ ├── README.md
│ ├── cli.js
│ ├── package-lock.json
│ └── package.json
├── week-of-month
│ ├── README.md
│ ├── builds
│ │ ├── spacetime-week-of-month.cjs
│ │ ├── spacetime-week-of-month.min.js
│ │ └── spacetime-week-of-month.mjs
│ ├── package-lock.json
│ ├── package.json
│ ├── rollup.config.js
│ ├── src
│ │ └── index.js
│ └── week.test.js
└── week-start
│ ├── README.md
│ ├── _version.js
│ ├── builds
│ ├── spacetime-week-start.cjs
│ ├── spacetime-week-start.min.js
│ └── spacetime-week-start.mjs
│ ├── package-lock.json
│ ├── package.json
│ ├── rollup.config.js
│ ├── scratch.js
│ ├── src
│ ├── data
│ │ └── countries.js
│ ├── index.js
│ └── input
│ │ └── weekStart.js
│ ├── test
│ └── basic.test.js
│ ├── types
│ └── types.d.ts
│ ├── weekStart-demo.js
│ └── zonefile
│ └── iana.js
├── rollup.config.js
├── scratch.js
├── scripts
├── tz
│ ├── build.js
│ └── iana.js
├── updateZonefile.js
└── version.js
├── src
├── _version.js
├── data
│ ├── ampm.js
│ ├── caseFormat.js
│ ├── days.js
│ ├── distance.js
│ ├── milliseconds.js
│ ├── monthLengths.js
│ ├── months.js
│ ├── quarters.js
│ ├── seasons.js
│ └── units.js
├── fns.js
├── index.js
├── input
│ ├── formats
│ │ ├── 01-ymd.js
│ │ ├── 02-mdy.js
│ │ ├── 03-dmy.js
│ │ ├── 04-misc.js
│ │ ├── _parsers.js
│ │ ├── index.js
│ │ ├── parseOffset.js
│ │ └── parseTime.js
│ ├── helpers.js
│ ├── index.js
│ ├── named-dates.js
│ ├── normalize.js
│ └── parse.js
├── methods.js
├── methods
│ ├── add.js
│ ├── compare.js
│ ├── diff
│ │ ├── index.js
│ │ ├── one.js
│ │ └── waterfall.js
│ ├── every.js
│ ├── format
│ │ ├── _offset.js
│ │ ├── index.js
│ │ └── unixFmt.js
│ ├── i18n.js
│ ├── nearest.js
│ ├── progress.js
│ ├── query
│ │ ├── 01-time.js
│ │ ├── 02-date.js
│ │ ├── 03-year.js
│ │ └── index.js
│ ├── same.js
│ ├── set
│ │ ├── _model.js
│ │ ├── set.js
│ │ └── walk.js
│ ├── since
│ │ ├── _iso.js
│ │ ├── getDiff.js
│ │ ├── index.js
│ │ └── soften.js
│ └── startOf.js
├── spacetime.js
├── timezone
│ ├── find.js
│ ├── guessTz.js
│ ├── index.js
│ ├── parseOffset.js
│ ├── quick.js
│ └── summerTime.js
└── whereIts.js
├── test
├── add.test.js
├── api.test.js
├── clone.test.js
├── compare.test.js
├── day.test.js
├── dayTime.test.js
├── daysThisMonth.test.js
├── diff.test.js
├── dst-add.ignore.js
├── dst-diff.ignore.js
├── dst-fns.ignore.js
├── dst-misc.ignore.js
├── dst-north.test.js
├── dst-sneak.test.js
├── dst-south.test.js
├── epoch.test.js
├── every.test.js
├── findTz.test.js
├── format.test.js
├── fullDay.test.js
├── goforward.test.js
├── goto.test.js
├── hemisphere.test.js
├── i18n.test.js
├── immutable.test.js
├── informal-tzs.test.js
├── inputs.test.js
├── integration.test.js
├── intl.test.js
├── iso-full.test.js
├── json.test.js
├── leapYear.test.js
├── lib
│ ├── dstParse.js
│ ├── index.js
│ └── useOldTz.js
├── long-years.test.js
├── misc.test.js
├── nearest.test.js
├── now.test.js
├── progress.test.js
├── quarter.test.js
├── query.test.js
├── same.test.js
├── season.test.js
├── semi-destructive.test.js
├── set.test.js
├── since.test.js
├── smoke.test.js
├── startOf.test.js
├── str-parse.test.js
├── subtract.test.js
├── swapTz.test.js
├── timezone-name.test.js
├── toNativeDate.test.js
├── today.test.js
├── types
│ ├── constructor.test.ts
│ ├── index.ts
│ ├── spacetime-static.ts
│ ├── tsconfig.json
│ └── types.test.ts
├── utcOffset.test.js
├── validation.test.js
├── week.test.js
├── weekStart.test.js
└── world.test.js
├── types
├── constraints.d.ts
├── constructors.d.ts
├── index.d.cts
├── index.d.ts
└── types.d.ts
└── zonefile
├── _build.js
├── _prefixes.js
├── aliases.js
├── iana.js
├── pack.js
└── unpack.js
/.github/workflows/build-and-test.yml:
--------------------------------------------------------------------------------
1 | name: Build and test
2 |
3 | on: [pull_request]
4 |
5 | jobs:
6 | build:
7 | runs-on: ubuntu-latest
8 |
9 | strategy:
10 | matrix:
11 | node-version: [18.x, 22.x, 24.x]
12 |
13 | steps:
14 | - uses: actions/checkout@v4
15 |
16 | - name: use node.js ${{ matrix.node-version }}
17 | uses: actions/setup-node@v4
18 | with:
19 | node-version: ${{ matrix.node-version }}
20 |
21 | - name: cache dependencies
22 | uses: actions/cache@v4
23 | with:
24 | path: ~/.npm
25 | key: ${{ runner.os }}-npm-${{ matrix.node-version }}-${{ hashFiles('package-lock.json') }}
26 | restore-keys: |
27 | ${{ runner.os }}-npm-${{ matrix.node-version }}-
28 | ${{ runner.os }}-npm-
29 |
30 | - name: npm install, build, and test
31 | run: |
32 | npm ci
33 | npm i eslint ts-node typescript
34 | npm run lint
35 | npm run pack
36 | npm run build
37 | npm run testb
38 | # npm run test:types
39 | env:
40 | CI: true
41 |
--------------------------------------------------------------------------------
/.github/workflows/coverage.yml:
--------------------------------------------------------------------------------
1 | # sends test-coverage data to codecov.io
2 | # https://codecov.io/gh/spencermountain/spacetime
3 | name: Coverage
4 |
5 | on:
6 | push:
7 | branches: [master]
8 |
9 | jobs:
10 | getCoverage:
11 | runs-on: ubuntu-latest
12 |
13 | steps:
14 | - uses: actions/checkout@v4
15 | - uses: actions/setup-node@v4
16 | with:
17 | node-version: '20'
18 |
19 | - uses: actions/cache@v4
20 | with:
21 | path: ~/.npm
22 | key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
23 | restore-keys: |
24 | ${{ runner.os }}-node-
25 |
26 | - run: npm ci
27 | - run: npm run codecov
28 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release
2 |
3 | on:
4 | release:
5 | types: [created]
6 |
7 | jobs:
8 | release:
9 | runs-on: ubuntu-latest
10 | permissions:
11 | contents: read
12 | id-token: write
13 | env:
14 | CI: true
15 |
16 | # Note that these steps are *identical* to build-and-test (with the caveat
17 | # that build-and-test uses several versions of Node, and Release only uses
18 | # 10.x) at least until the actual publishing happens. Ideally, we could
19 | # delegate to the build- and-test workflow, but I haven't found a way to do
20 | # that yet.
21 | steps:
22 | - uses: actions/checkout@v4
23 | with:
24 | persist-credentials: false
25 | - uses: actions/setup-node@v4
26 | with:
27 | node-version: 20.x
28 |
29 | - name: cache dependencies
30 | uses: actions/cache@v3
31 | with:
32 | path: ~/.npm
33 | key: ${{ runner.os }}-npm-10.x-${{ hashFiles('package-lock.json') }}
34 | restore-keys: |
35 | ${{ runner.os }}-npm-10.x-
36 | ${{ runner.os }}-npm-
37 |
38 | - name: install
39 | run: |
40 | npm ci
41 | npm i --no-save eslint ts-node typescript
42 |
43 | - name: static checks
44 | run: |
45 | npm run lint
46 |
47 | - name: build
48 | run: |
49 | npm run build
50 |
51 | - name: test
52 | run: |
53 | npm run testb
54 | npm run test:types
55 |
56 | # And finally... publish it! Note that we create the .npmrc file
57 | # "just in time" so that `npm publish` can get the auth token from the
58 | # environment
59 | - name: publish
60 | run: |
61 | echo '//registry.npmjs.org/:_authToken=${NPM_TOKEN}' > .npmrc
62 | npm publish --access public --provenance
63 | env:
64 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
65 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .jshintrc
3 | .env
4 | node_modules/
5 | .nyc_output
6 | npm-debug.log
7 | viz/
8 | coverage/
9 | coverage.lcov
10 | .vscode
11 | .gitignore
12 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | tests/
2 | demo/
3 | scripts/
4 | examples/
5 | .babelrc
6 | .esformatter
7 | .eslintrc
8 | scratch.js
9 | *.tsv
10 | changelog.md
11 | contributing.md
12 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2025 Spencer Kelly
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 |
--------------------------------------------------------------------------------
/codecov.yml:
--------------------------------------------------------------------------------
1 | codecov:
2 | require_ci_to_pass: false
3 | comment: false
4 | branches:
5 | - 'master'
6 |
--------------------------------------------------------------------------------
/contributing.md:
--------------------------------------------------------------------------------
1 | Hi! Pull requests are welcome and always respected.
2 |
3 | If the PR is a big one, or adds a new feature, please open an issue to ask questions first.
4 |
5 | We promise to avoid pedantic or cosmetic disputes!
6 |
7 | ### To get it running:
8 |
9 | - [fork](https://help.github.com/articles/fork-a-repo/) the repository
10 | - [clone](https://help.github.com/articles/cloning-a-repository/) it to your computer
11 | - set environment variable `TESTENV=dev` or `TESTENV=prod` in .env file
12 |
13 | ```bash
14 | cd spacetime
15 | npm install
16 | npm test
17 | ```
18 |
19 | ### Making changes
20 |
21 | - make your changes in `./src`
22 | - make sure the tests still pass `npm test`
23 | - for bonus points - add a few tests in `./tests` (doesn't matter where) for the new behaviour
24 |
25 | - create a [Pull Request](https://help.github.com/articles/creating-a-pull-request-from-a-fork/)
26 |
27 | don't worry about incrementing package numbers, or kicking-off builds. Releases will be handled by the maintainers.
28 |
29 | Lastly, thank you! A usable timezone library in the browser is serious and important project.
30 |
31 | It's done collaboratively or badly!
32 |
33 | ### Agreement to Code of Conduct
34 |
35 | By participating and contributing to Spacetime -- you agree to the [Contributor Covenant](https://www.contributor-covenant.org/version/2/0/code_of_conduct) Code of Conduct. Lack of familiarity with this Code of Conduct is not an excuse for not adhering to it.
36 |
--------------------------------------------------------------------------------
/eslint.config.js:
--------------------------------------------------------------------------------
1 | import * as regexpPlugin from "eslint-plugin-regexp"
2 |
3 | export default [
4 | regexpPlugin.configs["flat/recommended"],
5 | {
6 | "ignores": ["**/builds/*"],
7 | "rules": {
8 | "regexp/no-misleading-capturing-group": 0, //todo remove this
9 | "regexp/no-super-linear-backtracking": 0, //todo remove this, too
10 | "comma-dangle": [1, "only-multiline"],
11 | "quotes": [0, "single", "avoid-escape"],
12 | "max-nested-callbacks": [1, 4],
13 | "max-params": [1, 5],
14 | "consistent-return": 1,
15 | "no-bitwise": 1,
16 | "no-empty": 1,
17 | "no-console": 1,
18 | "no-duplicate-imports": 1,
19 | "no-eval": 2,
20 | "no-implied-eval": 2,
21 | "no-mixed-operators": 2,
22 | "no-multi-assign": 2,
23 | "no-nested-ternary": 1,
24 | "no-prototype-builtins": 0,
25 | "no-self-compare": 1,
26 | "no-sequences": 1,
27 | "no-shadow": 2,
28 | "no-unmodified-loop-condition": 1,
29 | "no-use-before-define": 1,
30 | "prefer-const": 1,
31 | "radix": 1,
32 | "no-unused-vars": 1,
33 | "regexp/prefer-d": 0,
34 | "regexp/prefer-w": 0,
35 | "regexp/prefer-range": 0,
36 | "regexp/no-unused-capturing-group": 0,
37 | "regexp/optimal-quantifier-concatenation": 0
38 | }
39 | }
40 | ]
41 |
--------------------------------------------------------------------------------
/plugins/age/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
calculate human age
4 |
5 |
6 |
14 |
15 | npm i space-age
16 |
17 |
18 | by
19 | Spencer Kelly
20 |
21 |
22 |
23 |
24 | a spacetime plugin to reckon a person's age, in any unit, given their birthday.
25 |
26 | ### javascript api:
27 | ```js
28 | import spacetime from 'spacetime'
29 | import plugin from 'space-age'
30 | spacetime.extend(plugin)
31 |
32 | // set a birthday
33 | let s = spacetime('march 28 1986')
34 | s.age()
35 | // 35
36 |
37 | // get your age in months, weeks
38 | s.age('days')
39 | // 12,770
40 |
41 | s.age('months')
42 | // 441
43 | ```
44 |
45 | ### command-line api:
46 | ```bash
47 | npx space-age may 18 1984
48 |
49 | npx space-age may 1st 1984 --months
50 | ```
51 | or you can install it locally with `npm i -g space-age`
52 |
53 |
54 | MIT
--------------------------------------------------------------------------------
/plugins/age/cli.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | import spacetime from 'spacetime'
3 | import minimist from 'minimist'
4 | import plg from './src/index.js'
5 | spacetime.extend(plg)
6 |
7 | const defaults = {
8 | unit: 'years'
9 | }
10 | const help = function () {
11 | console.log(`\n\n space-age - calculate human age from a birthdate`)
12 | console.log(`\n Usage: \`npx space-age march 24th 1982\``)
13 | console.log(`\n Usage: \`npx space-age march 24th 1982 --months\``)
14 | console.log('\n\n')
15 | }
16 |
17 | const alias = {
18 | h: 'help',
19 | y: 'years',
20 | d: 'days',
21 | day: 'days',
22 | hour: 'hours',
23 | year: 'years',
24 | month: 'months',
25 | m: 'months',
26 | q: 'quarters'
27 | }
28 |
29 | let opts = minimist(process.argv.slice(2), { alias: alias })
30 | const str = opts._.join(' ').trim()
31 |
32 | if (!str || opts.help) {
33 | help()
34 | process.exit()
35 | }
36 |
37 | opts = Object.assign({}, defaults, opts)
38 |
39 | let unit = 'years'
40 | if (opts.months) {
41 | unit = 'months'
42 | }
43 | if (opts.quarters) {
44 | unit = 'quarters'
45 | }
46 | if (opts.days) {
47 | unit = 'days'
48 | }
49 | if (opts.hours) {
50 | unit = 'hours'
51 | }
52 | if (opts.weeks) {
53 | unit = 'weeks'
54 | }
55 |
56 | const num = spacetime(str).age(unit)
57 | let output = `${num.toLocaleString()}`
58 | if (unit !== 'years') {
59 | output += ` (${unit})`
60 | }
61 | console.log(output)
62 |
--------------------------------------------------------------------------------
/plugins/age/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "space-age",
3 | "version": "0.0.1",
4 | "lockfileVersion": 2,
5 | "requires": true,
6 | "packages": {
7 | "": {
8 | "name": "space-age",
9 | "version": "0.0.1",
10 | "license": "MIT",
11 | "dependencies": {
12 | "minimist": "^1.2.5",
13 | "spacetime": "6.16.3"
14 | },
15 | "bin": {
16 | "space-age": "cli.js"
17 | },
18 | "engines": {
19 | "node": ">=8"
20 | }
21 | },
22 | "node_modules/minimist": {
23 | "version": "1.2.5",
24 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
25 | "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
26 | },
27 | "node_modules/spacetime": {
28 | "version": "6.16.3",
29 | "resolved": "https://registry.npmjs.org/spacetime/-/spacetime-6.16.3.tgz",
30 | "integrity": "sha512-JQEfj3VHT1gU1IMV5NvhgAP8P+2mDFd84ZCiHN//dp6hRKmuW0IizHissy62lO0nilfFjVhnoSaMC7te+Y5f4A=="
31 | }
32 | },
33 | "dependencies": {
34 | "minimist": {
35 | "version": "1.2.5",
36 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
37 | "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
38 | },
39 | "spacetime": {
40 | "version": "6.16.3",
41 | "resolved": "https://registry.npmjs.org/spacetime/-/spacetime-6.16.3.tgz",
42 | "integrity": "sha512-JQEfj3VHT1gU1IMV5NvhgAP8P+2mDFd84ZCiHN//dp6hRKmuW0IizHissy62lO0nilfFjVhnoSaMC7te+Y5f4A=="
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/plugins/age/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "space-age",
3 | "version": "0.1.0",
4 | "description": "a CLI calendar",
5 | "main": "cli.js",
6 | "bin": {
7 | "space-age": "cli.js"
8 | },
9 | "type": "module",
10 | "sideEffects": false,
11 | "exports": {
12 | ".": {
13 | "import": "./cli.js",
14 | "default": "./cli.js"
15 | }
16 | },
17 | "homepage": "https://github.com/spencermountain/spacetime/tree/master/plugins/space-age",
18 | "repository": {
19 | "type": "git",
20 | "url": "git+https://github.com/spencermountain/spacetime.git"
21 | },
22 | "engines": {
23 | "node": ">=8"
24 | },
25 | "scripts": {
26 | "test": ""
27 | },
28 | "prettier": {
29 | "trailingComma": "none",
30 | "tabWidth": 2,
31 | "semi": false,
32 | "singleQuote": true,
33 | "printWidth": 100
34 | },
35 | "dependencies": {
36 | "minimist": "^1.2.5",
37 | "spacetime": ">=6.3.0"
38 | },
39 | "license": "MIT"
40 | }
--------------------------------------------------------------------------------
/plugins/age/rollup.config.js:
--------------------------------------------------------------------------------
1 | import commonjs from 'rollup-plugin-commonjs'
2 | import json from 'rollup-plugin-json'
3 | import { terser } from 'rollup-plugin-terser'
4 | import resolve from 'rollup-plugin-node-resolve'
5 | import babel from 'rollup-plugin-babel'
6 | import sizeCheck from 'rollup-plugin-filesize-check'
7 | import { version } from './package.json'
8 |
9 | console.log('\n 📦 - running rollup..\n')
10 |
11 | const banner = '/* spencermountain/space-age ' + version + ' MIT */'
12 |
13 | export default [
14 | {
15 | input: 'src/index.js',
16 | output: [{ banner: banner, file: 'builds/space-age.mjs', format: 'esm' }],
17 | plugins: [
18 | resolve(),
19 | json(),
20 | commonjs(),
21 | babel({
22 | babelrc: false,
23 | presets: ['@babel/preset-env']
24 | }),
25 | sizeCheck({ expect: 2, warn: 10 })
26 | ]
27 | },
28 | {
29 | input: 'src/index.js',
30 | output: [
31 | {
32 | banner: banner,
33 | file: 'builds/space-age.js',
34 | format: 'umd',
35 | sourcemap: false,
36 | name: 'spaceAge'
37 | }
38 | ],
39 | plugins: [
40 | resolve(),
41 | json(),
42 | commonjs(),
43 | babel({
44 | babelrc: false,
45 | presets: ['@babel/preset-env']
46 | }),
47 | sizeCheck({ expect: 2, warn: 10 })
48 | ]
49 | },
50 | {
51 | input: 'src/index.js',
52 | output: [
53 | {
54 | banner: banner,
55 | file: 'builds/space-age.min.js',
56 | format: 'umd',
57 | name: 'spaceAge'
58 | }
59 | ],
60 | plugins: [
61 | resolve(),
62 | json(),
63 | commonjs(),
64 | babel({
65 | babelrc: false,
66 | presets: ['@babel/preset-env']
67 | }),
68 | terser(),
69 | sizeCheck({ expect: 2, warn: 10 })
70 | ]
71 | }
72 | ]
73 |
--------------------------------------------------------------------------------
/plugins/age/src/index.js:
--------------------------------------------------------------------------------
1 | export default {
2 | age: function (unit = 'years') {
3 | const now = this.set()
4 | const diff = this.diff(now, unit)
5 | return diff
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/plugins/daylight/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "spacetime-daylight",
3 | "version": "1.5.2",
4 | "description": "calculate approximate sunlight times for a given timezone",
5 | "main": "builds/spacetime-daylight.mjs",
6 | "unpkg": "builds/spacetime-daylight.min.js",
7 | "module": "builds/spacetime-daylight.mjs",
8 | "type": "module",
9 | "sideEffects": false,
10 | "exports": {
11 | ".": {
12 | "require": "./builds/spacetime-daylight.cjs",
13 | "import": "./builds/spacetime-daylight.mjs",
14 | "default": "./builds/spacetime-daylight.mjs"
15 | }
16 | },
17 | "homepage": "https://github.com/spencermountain/spacetime/tree/master/plugins/spacetime-daylight",
18 | "scripts": {
19 | "watch": "node --watch ./scratch.js",
20 | "test": "\"node_modules/.bin/tape\" \"./tests/*.test.js\" | \"node_modules/.bin/tap-dancer\" --color",
21 | "build": "rollup -c --silent"
22 | },
23 | "repository": {
24 | "type": "git",
25 | "url": "git+https://github.com/spencermountain/spacetime.git"
26 | },
27 | "keywords": [
28 | "sunlight",
29 | "timezone"
30 | ],
31 | "author": "spencermountain@gmail.com",
32 | "dependencies": {
33 | "spacetime-geo": "1.4.1",
34 | "suncalc": "1.9.0"
35 | },
36 | "devDependencies": {
37 | "spacetime": ">=7.4.0",
38 | "tap-dancer": "0.3.4",
39 | "tape": "5.9.0"
40 | },
41 | "license": "MIT"
42 | }
--------------------------------------------------------------------------------
/plugins/daylight/rollup.config.js:
--------------------------------------------------------------------------------
1 | import commonjs from 'rollup-plugin-commonjs'
2 | import json from 'rollup-plugin-json'
3 | import { terser } from 'rollup-plugin-terser'
4 | import resolve from 'rollup-plugin-node-resolve'
5 | import sizeCheck from 'rollup-plugin-filesize-check'
6 | import pkg from './package.json' assert { type: "json" };
7 | console.log('\n 📦 - running rollup..\n')
8 |
9 | const banner = '/* spencermountain/spacetime-daylight ' + pkg.version + ' MIT */'
10 |
11 | export default [
12 | {
13 | input: 'src/index.js',
14 | output: [{ banner: banner, file: 'builds/spacetime-daylight.mjs', format: 'esm' }],
15 | plugins: [resolve(), json(), commonjs(), sizeCheck({ expect: 132, warn: 10 })]
16 | },
17 | {
18 | input: 'src/index.js',
19 | output: [{ banner: banner, file: 'builds/spacetime-daylight.cjs', format: 'umd', sourcemap: false, name: 'spacetimeDaylight' }],
20 | plugins: [resolve(), json(), commonjs(), sizeCheck({ expect: 134, warn: 10 })]
21 | },
22 | {
23 | input: 'src/index.js',
24 | output: [{ banner: banner, file: 'builds/spacetime-daylight.min.js', format: 'umd', name: 'spacetimeDaylight' }],
25 | plugins: [resolve(), json(), commonjs(), terser(), sizeCheck({ expect: 95, warn: 10 })]
26 | }
27 | ]
28 |
--------------------------------------------------------------------------------
/plugins/daylight/scratch.js:
--------------------------------------------------------------------------------
1 | import spacetime from 'spacetime'
2 | import sunlight from './src/index.js'
3 | // console.log(tzlookup(42.7235, -73.6931)); // "America/New_York"
4 | // console.log(tzlookup(48.7235, 1.9931)); // paris
5 | // console.log(tzlookup(50.4050, -31.8971)); // atlantic ocean
6 |
7 | spacetime.extend(sunlight)
8 |
9 | // let s = spacetime.today('America/Iqaluit').time('3:00am')
10 | const s = spacetime.today('america/argentina/comodrivadavia')
11 | // let s = spacetime.today('America/Havana').time('3:00am')
12 |
13 | // ---day---
14 | // let hours = s.every('hour', s.add(1, 'day').time('3:00am'))
15 | console.log(s.sunPosition())
16 | // })
17 | // ---year--
18 | // let hours = s.every('hour', s.add(1, 'day'))
19 |
20 |
21 |
--------------------------------------------------------------------------------
/plugins/daylight/src/solstices.js:
--------------------------------------------------------------------------------
1 | // the average time between solstices, on timeanddate.com
2 | // approx 88 days, 23 hours, 30 mins
3 | const oneYear = 31557060000
4 |
5 | const halfYear = 15855660000
6 | // strangely, this does not seem to be exactly half.
7 | // const halfYear = oneYear / 2
8 |
9 | // the 2015 winter solstice
10 | const oneWinter = 1450759620000
11 |
12 | const goForward = function (epoch) {
13 | let num = oneWinter + oneYear
14 | while (num < epoch) {
15 | num += oneYear
16 | }
17 | return num
18 | }
19 | const goBackward = function (epoch) {
20 | let num = oneWinter - oneYear
21 | while (num > epoch) {
22 | num -= oneYear
23 | }
24 | return num
25 | }
26 |
27 | const solstice = function (s) {
28 | let found = null
29 | if (s.epoch > oneWinter) {
30 | found = goForward(s.epoch)
31 | } else {
32 | found = goBackward(s.epoch)
33 | }
34 | let winter = s.set(found)
35 | // ensure it's the right year
36 | if (winter.year() < s.year()) {
37 | winter = winter.set(winter.epoch + oneYear)
38 | }
39 | if (winter.year() > s.year()) {
40 | winter = winter.set(winter.epoch - oneYear)
41 | }
42 | const summer = winter.set(winter.epoch - halfYear)
43 | return {
44 | winter: winter,
45 | summer: summer,
46 | }
47 | }
48 | // const equinox = function (s) {
49 | // return {
50 | // summer: null,
51 | // winter: null,
52 | // }
53 | // }
54 | export default solstice
55 |
--------------------------------------------------------------------------------
/plugins/daylight/src/sunPosition.js:
--------------------------------------------------------------------------------
1 | import sunCalc from 'suncalc'
2 | import spacetimeGeo from 'spacetime-geo'
3 |
4 | function toDegree(radians) {
5 | const pi = Math.PI
6 | return radians * (180 / pi)
7 | }
8 |
9 | const sunPosition = function (s, lat, lng) {
10 | if (lat === undefined || lng === undefined) {
11 | const guess = s.point()
12 | lat = guess.lat
13 | lng = guess.lng
14 | }
15 | if (!lat || !lng) {
16 | return {}
17 | }
18 | s.in = s.in || spacetimeGeo.in //bolt-on the plugin
19 | s = s.in(lat, lng)
20 | const d = new Date(s.epoch)
21 | const res = sunCalc.getPosition(d, lat, lng)
22 | // return res
23 | return {
24 | azimuth: toDegree(res.azimuth),
25 | altitude: toDegree(res.altitude),
26 | }
27 | }
28 | export default sunPosition
29 |
--------------------------------------------------------------------------------
/plugins/daylight/tests/daytime.test.js:
--------------------------------------------------------------------------------
1 | import test from 'tape'
2 | import spacetime from 'spacetime'
3 | import daylight from '../src/index.js'
4 | // import daylight from '../builds/spacetime-daylight.mjs'
5 | spacetime.extend(daylight)
6 |
7 | test('day-status-summer', function (t) {
8 | const s = spacetime('June 26 2018', 'Canada/Eastern')
9 | let o = {}
10 | o = s.time('3:30am').daylight()
11 | t.equal(o.current.status, 'night', '3:30am')
12 | o = s.time('5:30am').daylight()
13 | t.equal(o.current.status, 'dawn', '5:30am')
14 | o = s.time('11:30am').daylight()
15 | t.equal(o.current.status, 'day', '11:30am')
16 | o = s.time('5:30pm').daylight()
17 | t.equal(o.current.status, 'day', '5:30pm')
18 | o = s.time('9:30pm').daylight()
19 | t.equal(o.current.status, 'dusk', '9:30pm')
20 | o = s.time('11:30pm').daylight()
21 | t.equal(o.current.status, 'night', '11:30pm')
22 | t.equal(o.current.progress, 0, 'no-sun-11:30pm')
23 | t.end()
24 | })
25 |
26 | test('day-status-winter', function (t) {
27 | const s = spacetime('November 26 2018', 'Canada/Eastern')
28 | let o = {}
29 | o = s.time('3:30am').daylight()
30 | t.equal(o.current.status, 'night', '3:30am')
31 | t.equal(o.current.progress, 0, 'no-sun-3am')
32 | o = s.time('7:00am').daylight()
33 | t.equal(o.current.status, 'dawn', '7:00am')
34 | o = s.time('11:30am').daylight()
35 | t.equal(o.current.status, 'day', '11:30am')
36 | o = s.time('4:30pm').daylight()
37 | t.equal(o.current.status, 'day', '4:30pm')
38 | o = s.time('5:00pm').daylight()
39 | t.equal(o.current.status, 'dusk', '5:00pm')
40 | o = s.time('11:30pm').daylight()
41 | t.equal(o.current.status, 'night', '11:30pm')
42 | t.end()
43 | })
44 |
--------------------------------------------------------------------------------
/plugins/daylight/tests/hemisphere.test.js:
--------------------------------------------------------------------------------
1 | import test from 'tape'
2 | import spacetime from 'spacetime'
3 | import daylight from '../src/index.js'
4 | // import daylight from '../builds/spacetime-daylight.mjs'
5 | spacetime.extend(daylight)
6 |
7 | test('southern-hemisphere-opposite', function (t) {
8 | let s = spacetime('December 16 2018', 'Australia/Canberra')
9 | t.equal(s.daylight().duration.human.hours, 14, 'long-days in Australia')
10 |
11 | s = spacetime('June 12 2018', 'Australia/Canberra')
12 | t.equal(s.daylight().duration.human.hours, 9, 'short-days in Australia')
13 |
14 | s = spacetime('December 12 2018', 'America/Sao_Paulo')
15 | t.equal(s.daylight().duration.human.hours, 13, 'long-days in brazil')
16 |
17 | s = spacetime('June 12 2018', 'America/Sao_Paulo')
18 | t.equal(s.daylight().duration.human.hours, 10, 'short-days in brazil')
19 | t.end()
20 | })
21 |
--------------------------------------------------------------------------------
/plugins/daylight/tests/sunlight.test.js:
--------------------------------------------------------------------------------
1 | import test from 'tape'
2 | import spacetime from 'spacetime'
3 | import daylight from '../src/index.js'
4 | // import daylight from '../builds/spacetime-daylight.mjs'
5 | spacetime.extend(daylight)
6 |
7 | test('day-length-winter', function (t) {
8 | const s = spacetime('December 16 2018')
9 | const newYork = s.daylight(42.7235, -73.6931)
10 | t.equal(newYork.duration.human.hours, 9, 'short-days in NY')
11 | const equator = s.daylight(0, 0)
12 | t.equal(equator.duration.human.hours, 12, 'medium-days in equator')
13 | t.end()
14 | })
15 |
16 | test('day-length-summer', function (t) {
17 | const s = spacetime('June 21 2018')
18 | const newYork = s.daylight(42.7235, -73.6931)
19 | t.equal(newYork.duration.human.hours, 15, 'long-days in NY')
20 | const equator = s.daylight(0, 0)
21 | t.equal(equator.duration.human.hours, 12, 'medium-days in equator')
22 | t.end()
23 | })
24 |
25 | test('using-point()-winter', function (t) {
26 | let s = spacetime('December 16 2018', 'Canada/Eastern')
27 | const newYork = s.daylight()
28 | t.equal(newYork.duration.human.hours, 8, 'short-days in Toronto')
29 | s = s.goto('Africa/Freetown')
30 | const equator = s.daylight()
31 | t.equal(equator.duration.human.hours, 11, 'medium-days in sierra-leone')
32 | t.end()
33 | })
34 |
35 | test('using-point()-summer', function (t) {
36 | let s = spacetime('June 21 2018', 'Canada/Eastern')
37 | const newYork = s.daylight()
38 | t.equal(newYork.duration.human.hours, 15, 'long-days in Toronto')
39 | s = s.goto('Africa/Freetown')
40 | const equator = s.daylight()
41 | t.equal(equator.duration.human.hours, 12, 'medium-days in sierra-leone')
42 | t.end()
43 | })
44 |
--------------------------------------------------------------------------------
/plugins/dst/anytime/lux.js:
--------------------------------------------------------------------------------
1 | import spacetime from '../../../src/index.js'
2 | import { DateTime } from "luxon";
3 | const test = function (epoch, tz) {
4 | // DateTime.now().setZone('America/New_York').minus({ weeks: 1 }).endOf('day').toISO();
5 | const d = DateTime.fromMillis(epoch).setZone()
6 | console.log('lux: ', d.year, d.month, d.day, d.hour)
7 |
8 | const s = spacetime(epoch, tz)
9 | console.log('spc:', s.year(), s.month(), s.day(), s.hour())
10 | }
11 | test(1636264800000, 'Europe/London')
--------------------------------------------------------------------------------
/plugins/dst/anytime/parse.js:
--------------------------------------------------------------------------------
1 | import spacetime from '../../../src/index.js'
2 | import changes from '../tzdb/parse.js'
3 |
4 | import { DateTime } from "luxon";
5 |
6 | const fromEpoch = function (epoch, tz) {
7 | return DateTime.fromMillis(epoch).setZone(tz)
8 | }
9 |
10 |
11 | const hour = 1000 * 60 * 60
12 | const start = 2020
13 | const doTZ = function (tz) {
14 | const arr = []
15 | for (let y = start; y < start + 5; y += 1) {
16 | // add start of year
17 | // let s = spacetime.now(tz)
18 | // s = s.year(y).startOf('year')
19 | // arr.push([s.epoch, y, 1, 1])
20 | // add dst
21 | if (changes[tz][y]) {
22 | // add dst start
23 | let epoch = changes[tz][y][0]
24 | const on = fromEpoch(epoch, tz)
25 | arr.push([epoch, on.year, on.month, on.day, on.hour])
26 |
27 | // add dst end
28 | epoch = changes[tz][y][1]
29 | // epoch -= hour
30 | // epoch += hour
31 | const off = fromEpoch(epoch, tz)
32 | arr.push([epoch, off.year, off.month, off.day, off.hour])
33 | }
34 | }
35 | return arr
36 | }
37 |
38 | const arr = ['America/Toronto', 'America/Vancouver', 'europe/london']
39 | const out = arr.reduce((h, id) => {
40 | h[id] = doTZ('America/Toronto')
41 | return h
42 | }, {})
43 | console.log(out)
--------------------------------------------------------------------------------
/plugins/dst/old/scripts/findPatterns.js:
--------------------------------------------------------------------------------
1 | // eu - 03/28:02->10/31:03
2 | const zones = require('/Users/spencer/mountain/spacetime/zonefile/iana.js')
3 | let keys = Object.keys(zones)
4 | console.log(keys.length)
5 | keys = keys.filter((k) => {
6 | return zones[k].dst //&& !zones[k].dst.includes('03/28')
7 | })
8 | // usa
9 | keys = keys.filter((k) => zones[k].dst !== '03/14:02->11/07:02')
10 | // australia
11 | // keys = keys.filter((k) => zones[k].dst !== '04/04:03->10/03:02')
12 | // // mexico
13 | // keys = keys.filter((k) => zones[k].dst !== '04/04:02->10/31:02')
14 | console.log(keys.length)
15 | console.log(keys)
16 |
--------------------------------------------------------------------------------
/plugins/dst/old/scripts/getzones.js:
--------------------------------------------------------------------------------
1 | // const fs = require('fs')
2 | // const path = require('path')
3 | // const sh = require('shelljs')
4 | // let year = 2021
5 | // let tz = 'australia/melbourne'
6 |
7 | const tzs = require('/Users/spencer/mountain/spacetime/zonefile/iana.js')
8 | const list = Object.keys(tzs)
9 |
10 | // const titleCase = (str) => {
11 | // str = str[0].toUpperCase() + str.substr(1)
12 | // str = str.replace(/\/gmt/, '/GMT')
13 | // str = str.replace(/[\/_]([a-z])/gi, (s) => {
14 | // return s.toUpperCase()
15 | // })
16 | // return str
17 | // }
18 |
19 | // console.log(list)
20 | // list.forEach((tz) => {
21 | // tz = titleCase(tz)
22 | // console.log(tz)
23 | // let lines = sh.exec(`zdump -v ${tz} | grep ${year}`).toString().split('\n')
24 | // console.log(lines)
25 | // })
26 |
27 | // const zones = require('/Users/spencer/Desktop/zones.json')
28 | // console.log(Object.keys(zones.zoneData))
29 | // console.log(Object.keys(zones.zoneData.Canada))
30 | // console.log(Object.keys(zones.zoneData.EST))
31 | // console.log(zones.zoneData.Canada.Pacific)
32 |
33 | const parse = require('parse-zoneinfo')
34 |
35 | list.slice(9, 10).forEach((tz) => {
36 | parse(tz, function (err, tzdata) {
37 | if (err) {
38 | console.log(tz)
39 | // console.log(err)
40 | } else {
41 | console.log(tzdata)
42 | }
43 | })
44 | })
45 |
--------------------------------------------------------------------------------
/plugins/dst/old/scripts/split-2.js:
--------------------------------------------------------------------------------
1 | const zones = require('/Users/spencer/mountain/spacetime/zonefile/iana.js')
2 | let keys = Object.keys(zones)
3 |
4 | const titleCase = (str) => {
5 | str = str[0].toUpperCase() + str.substr(1)
6 | str = str.replace(/\/gmt/, '/GMT')
7 | str = str.replace(/[\/_]([a-z])/gi, (s) => {
8 | return s.toUpperCase()
9 | })
10 | return str
11 | }
12 |
13 | keys = keys.filter((k) => zones[k].dst)
14 | // keys = keys.filter((k) => zones[k].hem === 's')
15 | // keys = keys.map((k) => titleCase(k))
16 |
17 | const byDate = {}
18 | keys.forEach((k) => {
19 | const off = '' + zones[k].offset + 'h ' + zones[k].dst
20 | const init = {
21 | name: '',
22 | abbrev: '',
23 | abbrev_dst: '',
24 | offset: zones[k].offset,
25 | pattern: {},
26 | dst: zones[k].dst,
27 | ids: []
28 | }
29 | byDate[off] = byDate[off] || init
30 | const id = titleCase(k)
31 | byDate[off].ids.push(id)
32 | })
33 | let result = Object.values(byDate)
34 | result = result
35 | .sort((a, b) => {
36 | if (a.offset > b.offset) {
37 | return -1
38 | } else if (a.offset < b.offset) {
39 | return 1
40 | }
41 | return 0
42 | })
43 | .reverse()
44 | console.log(result.length)
45 | // console.log(JSON.stringify(result, null, 2))
46 |
--------------------------------------------------------------------------------
/plugins/dst/old/scripts/split-to-files.js:
--------------------------------------------------------------------------------
1 | const zones = require('/Users/spencer/mountain/spacetime/zonefile/iana.js')
2 | let keys = Object.keys(zones)
3 |
4 | const titleCase = (str) => {
5 | str = str[0].toUpperCase() + str.substr(1)
6 | str = str.replace(/\/gmt/, '/GMT')
7 | str = str.replace(/[\/_]([a-z])/gi, (s) => {
8 | return s.toUpperCase()
9 | })
10 | return str
11 | }
12 |
13 | keys = keys.filter((k) => !zones[k].dst)
14 | keys = keys.filter((k) => zones[k].hem === 's')
15 | // keys = keys.map((k) => titleCase(k))
16 |
17 | const byOffset = {}
18 | keys.forEach((k) => {
19 | const off = '' + zones[k].offset
20 | byOffset[off] = byOffset[off] || []
21 | const id = titleCase(k)
22 | byOffset[off].push(id)
23 | })
24 |
25 | const res = {}
26 | let nums = Object.keys(byOffset)
27 | nums = nums.sort((a, b) => {
28 | if (a > b) {
29 | return -1
30 | } else if (a < b) {
31 | return 1
32 | }
33 | return 0
34 | })
35 | nums.forEach((num) => {
36 | res[num] = byOffset[num]
37 | byOffset[num] = byOffset[num].sort((a, b) => {
38 | a = Number(a)
39 | b = Number(b)
40 | if (a > b) {
41 | return -1
42 | } else if (a < b) {
43 | return 1
44 | }
45 | return 0
46 | })
47 | })
48 | // let byRegion = {}
49 | // keys.forEach((k) => {
50 | // let split = k.split(/\//)
51 | // byRegion[split[0]] = byRegion[split[0]] || []
52 | // byRegion[split[0]].push(split[1])
53 | // })
54 |
55 | console.log(JSON.stringify(res, null, 2))
56 |
--------------------------------------------------------------------------------
/plugins/dst/old/scripts/tznames-lib.js:
--------------------------------------------------------------------------------
1 | const data = require('/Users/spencer/Downloads/data.json')
2 |
3 | // let key = 'DisplayNames' // 140 metazones
4 | // let key = 'TzdbZoneCountries' // which country iana is in (424 of them)
5 | // let key = 'CldrZoneCountries'
6 | // let key = 'CldrMetazones' // *
7 | // let key = 'CldrPrimaryZones'
8 | // let key = 'CldrAliases'
9 | const key = 'CldrLanguageData' //i18n place-names
10 | // let key = 'SelectionZones'
11 | // console.log(Object.keys(data))
12 | console.log(Object.keys(data[key]))
13 | // console.log(data[key].en)
14 |
15 | // displaynames[en] = 140
16 | // "'North Asia East Standard Time'"
17 | // console.log(data['DisplayNames']['en_US'])
18 |
19 | let names = {}
20 | console.log(JSON.stringify(Object.keys(data[key]), null, 2))
21 | Object.keys(data[key]).forEach((lang) => {
22 | // console.log(data[key][lang].ShortNames)
23 | names = Object.assign(names, data[key][lang].ShortNames)
24 | })
25 | // console.log(JSON.stringify(names, null, 2))
26 |
27 | // 'Africa_Eastern'
28 | // console.log(data['DisplayNames'])
29 | // const byMeta = {}
30 | // Object.keys(data.CldrMetazones).forEach((k) => {
31 | // let code = data.CldrMetazones[k]
32 | // byMeta[code] = byMeta[code] || []
33 | // byMeta[code].push(k)
34 | // })
35 | // console.log(byMeta)
36 | // const byMeta = {}
37 | // Object.keys(data.CldrMetazones).forEach((k) => {
38 | // let code = data.CldrMetazones[k]
39 | // byMeta[code] = byMeta[code] || []
40 | // byMeta[code].push(k)
41 | // })
42 | // console.log(byMeta)
43 |
44 | // const byCountry = {}
45 | // Object.keys(data.TzdbZoneCountries).forEach((k) => {
46 | // let code = data.TzdbZoneCountries[k][0]
47 | // byCountry[code] = byCountry[code] || []
48 | // byCountry[code].push(k)
49 | // })
50 | // console.log(byCountry)
51 |
--------------------------------------------------------------------------------
/plugins/dst/old/zonefile/missing.js:
--------------------------------------------------------------------------------
1 | export default [
2 | 'America/Argentina/La_Rioja',
3 | 'America/Argentina/Rio_Gallegos',
4 | 'America/Argentina/Salta',
5 | 'America/Argentina/San_Juan',
6 | 'America/Argentina/San_Luis',
7 | 'America/Argentina/Tucuman',
8 | 'America/Argentina/Ushuaia',
9 | 'America/Indiana/Knox',
10 | 'America/Indiana/Tell_City',
11 | 'America/North_Dakota/Beulah',
12 | 'America/North_Dakota/Center',
13 | 'America/North_Dakota/New_Salem',
14 | 'America/Indiana/Marengo',
15 | 'America/Indiana/Petersburg',
16 | 'America/Indiana/Vevay',
17 | 'America/Indiana/Vincennes',
18 | 'America/Indiana/Winamac',
19 | 'America/Kentucky/Monticello'
20 | ]
21 |
--------------------------------------------------------------------------------
/plugins/dst/old/zonefile/south.js:
--------------------------------------------------------------------------------
1 | export default {
2 | 1: ['Africa/Kinshasa', 'Africa/Luanda'],
3 | 2: [
4 | 'Africa/Gaborone',
5 | 'Africa/Harare',
6 | 'Africa/Johannesburg',
7 | 'Africa/Lubumbashi',
8 | 'Africa/Lusaka',
9 | 'Africa/Maputo',
10 | 'Africa/Maseru',
11 | 'Africa/Mbabane'
12 | ],
13 | 3: ['Antarctica/Syowa', 'Indian/Antananarivo'],
14 | 4: ['Indian/Reunion'],
15 | 5: ['Antarctica/Mawson', 'Indian/Kerguelen'],
16 | 6: ['Antarctica/Vostok'],
17 | 7: ['Antarctica/Davis', 'Asia/Jakarta', 'Indian/Christmas'],
18 | 8: ['Asia/Kuala_Lumpur', 'Asia/Makassar', 'Asia/Singapore', 'Australia/Perth', 'Australia/West'],
19 | 9: ['Asia/Dili', 'Asia/Jayapura'],
20 | 9.5: ['Australia/Darwin', 'Australia/North'],
21 | 8.75: ['Australia/Eucla'],
22 | 10: [
23 | 'Antarctica/Dumontdurville',
24 | 'Australia/Brisbane',
25 | 'Australia/Lindeman',
26 | 'Australia/Queensland'
27 | ],
28 | 11: ['Pacific/Bougainville'],
29 |
30 | // eastern hemisphere
31 | '-5': ['America/Lima', 'America/Rio_Branco', 'Brazil/Acre'],
32 | '-4': ['America/La_Paz', 'America/Manaus', 'Brazil/West'],
33 | '-3': [
34 | 'America/Argentina',
35 | 'America/Buenos_Aires',
36 | 'America/Cordoba',
37 | 'America/Fortaleza',
38 | 'America/Montevideo',
39 | 'America/Punta_Arenas',
40 | 'America/Sao_Paulo',
41 | 'Antarctica/Rothera',
42 | 'Atlantic/Stanley',
43 | 'Brazil/East'
44 | ],
45 | '-2': ['Brazil/Denoronha']
46 | }
47 |
--------------------------------------------------------------------------------
/plugins/dst/old/zones/patterns.js:
--------------------------------------------------------------------------------
1 | // (From 1987 to 2006, DST began on the first Sunday in April and ended on the last Sunday of October)
2 | const northAmerica = {
3 | // the second Sunday of March
4 | start: {
5 | day: 'sunday',
6 | num: 2,
7 | month: 'march',
8 | hour: 2
9 | },
10 | // first Sunday of November
11 | end: {
12 | day: 'sunday',
13 | num: 1,
14 | month: 'november',
15 | hour: 2
16 | }
17 | }
18 |
19 | const eu = {
20 | // last Sunday in March
21 | start: {
22 | day: 'sunday',
23 | num: 'last',
24 | month: 'march'
25 | // hour: ()=>{}
26 | },
27 | // the last Sunday in October.
28 | end: {
29 | day: 'sunday',
30 | num: 'last',
31 | month: 'october'
32 | // hour: ()=>{}
33 | }
34 | }
35 |
36 | export default {
37 | northAmerica,
38 | eu
39 | }
40 |
--------------------------------------------------------------------------------
/plugins/dst/script/list-changes.js:
--------------------------------------------------------------------------------
1 | import spacetime from '../../../src/index.js'
2 | import zones from '../src/zonefile.2022.js'
3 | import { fromSpace } from '../src/calc.js'
4 | import parsedDst from '../src/patterns.js'
5 | const start = 2020
6 |
7 | const byTz = function (tz) {
8 | let s = spacetime.now(tz)
9 | let dst = zones[tz].pattern
10 | dst = parsedDst[dst]
11 | console.log(fromSpace(dst, tz, start))
12 | const arr = []
13 | for (let y = start; y < start + 5; y += 1) {
14 | s = s.year(y).startOf('year')
15 | arr.push([s.epoch, y, 1, 1])
16 |
17 | // if (dst) {
18 |
19 | // }
20 | }
21 | // console.log(s.timezone())
22 | // console.log(spacetime.timezones())
23 |
24 | return arr
25 | }
26 | console.log(byTz('america/toronto'))
--------------------------------------------------------------------------------
/plugins/dst/src/calc.js:
--------------------------------------------------------------------------------
1 | import spacetime from '../../../src/index.js'
2 |
3 | const fromJSDate = function (obj, year) {
4 | const d = new Date([year, obj.month, 1])
5 | const currentDay = d.getDay()
6 | // set to the right day eg 'monday'
7 | if (currentDay !== obj.day) {
8 | const distance = (obj.day + 7 - currentDay) % 7;
9 | d.setDate(1 + distance)
10 | }
11 | if (obj.num === 1) {
12 | return d
13 | }
14 | if (obj.num === 2) {
15 | d.setDate(d.getDate() + 7)
16 | return d
17 | }
18 | if (obj.num === 3) {
19 | d.setDate(d.getDate() + 14)
20 | return d
21 | }
22 | if (obj.num === 'last') {
23 | // get the last sunday in the month
24 | const m = d.getMonth()
25 | while (d.getMonth() === m) {
26 | d.setDate(d.getDate() + 7)
27 | }
28 | d.setDate(d.getDate() - 7)
29 | }
30 | return d
31 | }
32 |
33 | const months = ['jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec']
34 | const days = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat']
35 |
36 | const fromSpace = function (obj, tz, year) {
37 | let s = spacetime.now(tz).year(year).startOf('year')
38 | s = s.month(months[obj.month - 1])
39 | s = s.startOf('month')
40 | s = s.day(days[obj.day], true)
41 | if (obj.num === 2) {
42 | s = s.add(1, 'week')
43 | } else if (obj.num === 3) {
44 | s = s.add(2, 'week')
45 | } else if (obj.num === 'last') {
46 | s = s.endOf('month')
47 | s = s.day(obj.day, false)//roll backward
48 | }
49 | s = s.hour(obj.hour)
50 | return s
51 | }
52 | export { fromJSDate, fromSpace }
--------------------------------------------------------------------------------
/plugins/dst/src/index.js:
--------------------------------------------------------------------------------
1 | import zones from './zonefile.2022.js'
2 | import patterns from './patterns.js'
3 |
4 |
5 | const topk = function (arr) {
6 | const obj = {}
7 | arr.forEach(a => {
8 | obj[a] = obj[a] || 0
9 | obj[a] += 1
10 | })
11 | const res = Object.keys(obj).map(k => [k, obj[k]])
12 | return res.sort((a, b) => (a[1] > b[1] ? -1 : 0))
13 | }
14 |
15 | let all = Object.keys(zones).map(k => {
16 | return (zones[k].dst || '') + '|' + (zones[k].pattern || '')
17 | })
18 | all = topk(all)
19 | all = all.map(a => {
20 | return a
21 | })
22 | console.log(all)
--------------------------------------------------------------------------------
/plugins/dst/src/test.js:
--------------------------------------------------------------------------------
1 | import zones from './zonefile.2022.js'
2 | import patterns from './patterns.js'
3 | import { fromJSDate, fromSpace } from './calc.js'
4 | // import spacetime from '../../../src/index.js'
5 | // let zones = {
6 | // }
7 |
8 |
9 | const pad = (num) => {
10 | return String(num).padStart(2, '0')
11 | }
12 |
13 | // test spacetime version
14 | Object.keys(zones).forEach(k => {
15 | if (zones[k].dst) {
16 | const pattern = patterns[zones[k].pattern]
17 | const start = fromSpace(pattern.start, 2022)
18 | const end = fromSpace(pattern.end, 2022)
19 | let dst = `${pad(start.month() + 1)}/${pad(start.date())}:${pad(pattern.start.hour)}->`
20 | dst += `${pad(end.month() + 1)}/${pad(end.date())}:${pad(pattern.end.hour)}`
21 | // console.log(k, pattern)
22 | if (dst !== zones[k].dst) {
23 | console.log('\n', k)
24 | console.log(pattern)
25 | console.log('made', dst)
26 | console.log('want', zones[k].dst)
27 | }
28 | console.log(dst === zones[k].dst)
29 | }
30 | })
31 |
32 | // test js date version
33 | // Object.keys(zones).forEach(k => {
34 | // if (zones[k].dst) {
35 | // let pattern = patterns[zones[k].pattern]
36 | // let start = fromJSDate(pattern.start, 2022)
37 | // let end = fromJSDate(pattern.end, 2022)
38 | // let dst = `${pad(start.getMonth() + 1)}/${pad(start.getDate())}:${pad(pattern.start.hour)}->`
39 | // dst += `${pad(end.getMonth() + 1)}/${pad(end.getDate())}:${pad(pattern.end.hour)}`
40 | // // console.log(k, pattern)
41 | // if (dst !== zones[k].dst) {
42 | // console.log('\n', k)
43 | // console.log(pattern)
44 | // console.log('made', dst)
45 | // console.log('want', zones[k].dst)
46 | // }
47 | // console.log(dst === zones[k].dst)
48 | // }
49 | // })
50 |
51 | // console.log(calculate({ num: 1, day: 0, month: 3, hour: 0 }, 2022))
--------------------------------------------------------------------------------
/plugins/dst/tzdb/aliases.js:
--------------------------------------------------------------------------------
1 | import aliases from '/Users/spencer/mountain/spacetime/zonefile/aliases.js'
2 | import zone from '../src/zonefile.2022.js'
3 | Object.entries(aliases).forEach(a => {
4 | const [k, v] = a
5 | if (zone[k]) {
6 | console.log(k)
7 | }
8 | if (!zone[v]) {
9 | console.log(v)
10 | }
11 | })
12 |
13 |
--------------------------------------------------------------------------------
/plugins/dst/tzdb/download.js:
--------------------------------------------------------------------------------
1 | import sh from 'shelljs'
2 |
3 | sh.exec(`wget -qO- https://timezonedb.com/files/TimeZoneDB.csv.zip | bsdtar -xvf-`)
4 | sh.exec(`rm database.sql`)
5 | sh.rm(`readme.txt`)
6 | sh.rm(`country.csv`)
7 | sh.mv(`time_zone.csv`, './plugins/dst/tzdb')
--------------------------------------------------------------------------------
/plugins/dst/tzdb/parse.js:
--------------------------------------------------------------------------------
1 | import fs from 'fs'
2 | import path from 'path'
3 | import { fileURLToPath } from 'url'
4 | const dir = path.dirname(fileURLToPath(import.meta.url)) // eslint-disable-line
5 |
6 | // https://timezonedb.com/files/TimeZoneDB.csv.zip
7 | const rows = fs.readFileSync(dir + '/time_zone.csv').toString().split(/\n/g)
8 | const data = {}
9 | const want = new Set([2021, 2022, 2023, 2024])
10 | rows.forEach(str => {
11 | let [id, _, _co, epoch, offset, on] = str.split(/,/g)
12 | epoch = Number(epoch) * 1000
13 | const d = new Date(epoch)
14 | const year = d.getFullYear()
15 | if (!want.has(year)) {
16 | return
17 | }
18 | data[id] = data[id] || {}
19 | data[id][year] = data[id][year] || []
20 | data[id][year].push(epoch)
21 | })
22 | // delete data['Pacific/Apia']
23 | // delete data['Asia/Tehran']
24 | // delete data['Pacific/Fiji']
25 | // delete data['Africa/Juba'] //no longer dst
26 | // //aliases
27 | // delete data['America/Kentucky/Louisville']
28 | // delete data['America/Indiana/Indianapolis']
29 |
30 | export default data
31 | // console.log(data)
--------------------------------------------------------------------------------
/plugins/dst/tzdb/test.js:
--------------------------------------------------------------------------------
1 | import data from './parse.js'
2 | import spacetime from '../../../src/index.js'
3 | import zone from '../src/zonefile.2022.js'
4 |
5 |
6 | import patterns from '../src/patterns.js'
7 | import { fromSpace } from '../src/calc.js'
8 |
9 | const year = 2021
10 | // let k = 'America/Toronto'
11 | Object.keys(data).forEach(k => {
12 | let [start, end] = data[k][year]
13 | const hour = 1000 * 60 * 60
14 | start = spacetime(start, k)
15 | end = spacetime(end + hour, k)
16 |
17 | const name = zone[k.toLowerCase()].pattern
18 | const pattern = patterns[name]
19 | const pStart = fromSpace(pattern.start, k, year).add(1, 'hour')
20 | const pEnd = fromSpace(pattern.end, k, year)
21 |
22 | if (Math.abs(start.diff(pStart, 'hour')) > 2 || Math.abs(end.diff(pEnd, 'hour')) > 2) {
23 | console.log(k, Math.abs(start.diff(pStart, 'hour')))
24 | console.log(start.format('{nice-day} {time}'), ' ', end.format('{nice-day} {time}'))
25 | console.log(pStart.format('{nice-day} {time}'), ' ', pEnd.format('{nice-day} {time}'))
26 | } else {
27 | console.log(true)
28 | }
29 | })
--------------------------------------------------------------------------------
/plugins/dst/tzdb/titlecase.js:
--------------------------------------------------------------------------------
1 | import spacetime from '../../../src/index.js'
2 | import zone from '../src/zonefile.2022.js'
3 |
4 | import fs from 'fs'
5 | import path from 'path'
6 | import { fileURLToPath } from 'url'
7 | const dir = path.dirname(fileURLToPath(import.meta.url)) // eslint-disable-line
8 |
9 | // https://timezonedb.com/files/TimeZoneDB.csv.zip
10 | const rows = fs.readFileSync(dir + '/time_zone.csv').toString().split(/\n/g)
11 | const data = {}
12 | rows.forEach(str => {
13 | const [id] = str.split(/,/g)
14 | data[id] = true
15 | })
16 | Object.keys(zone).forEach(k => {
17 | const s = spacetime.now(k)
18 | const tz = s.timezone().name
19 | if (!data.hasOwnProperty(tz)) {
20 | console.log(tz)
21 | }
22 | })
23 |
--------------------------------------------------------------------------------
/plugins/dst/zonefile/dst-patterns.js:
--------------------------------------------------------------------------------
1 | // these are the folk heuristics that timezones use to set their dst change dates
2 | // for example, the US changes:
3 | // the second Sunday of March -> first Sunday of November
4 | // http://www.webexhibits.org/daylightsaving/g.html
5 | const patterns = {
6 | usa: '2nd-sun-mar-2h|1st-sun-nov-2h',// (From 1987 to 2006)
7 | // mexico
8 | mex: '1st-sun-apr-2h|last-sun-oct-2h',
9 |
10 | // European Union zone
11 | eu0: 'last-sun-mar-0h|last-sun-oct-1h',
12 | eu1: 'last-sun-mar-1h|last-sun-oct-2h',
13 | eu2: 'last-sun-mar-2h|last-sun-oct-3h',
14 | eu3: 'last-sun-mar-3h|last-sun-oct-4h',
15 | //greenland
16 | green: 'last-sat-mar-22h|last-sat-oct-23h',
17 |
18 | // australia
19 | aus: '1st-sun-apr-3h|1st-sun-oct-2h',
20 | //lord howe australia
21 | lhow: '1st-sun-apr-2h|1st-sun-oct-2h',
22 | // new zealand
23 | chat: '1st-sun-apr-3h|last-sun-sep-2h', //technically 3:45h -> 2:45h
24 | // new Zealand, antarctica
25 | nz: '1st-sun-apr-3h|last-sun-sep-2h',
26 | // casey - antarctica
27 | ant: '2nd-sun-mar-0h|1st-sun-oct-0h',
28 | // troll - antarctica
29 | troll: '3rd-sun-mar-1h|last-sun-oct-3h',
30 |
31 | //jordan
32 | jord: 'last-fri-feb-0h|last-fri-oct-1h',
33 | // lebanon
34 | leb: 'last-sun-mar-0h|last-sun-oct-0h',
35 | // syria
36 | syr: 'last-fri-mar-0h|last-fri-oct-0h',
37 | //israel
38 | // Start: Last Friday before April 2 -> The Sunday between Rosh Hashana and Yom Kippur
39 | isr: 'last-fri-mar-2h|last-sun-oct-2h',
40 | //palestine
41 | pal: 'last-sun-mar-0h|last-fri-oct-1h',
42 |
43 | // el aaiun
44 | //this one seems to be on arabic calendar?
45 | saha: 'last-sun-mar-3h|1st-sun-may-2h',
46 |
47 | // paraguay
48 | par: 'last-sun-mar-0h|1st-sun-oct-0h',
49 | //cuba
50 | cuba: '2nd-sun-mar-0h|1st-sun-nov-1h',
51 | //chile
52 | chile: '1st-sun-apr-0h|1st-sun-sep-0h',
53 | //easter island
54 | east: '1st-sat-apr-22h|1st-sat-sep-22h',
55 | //fiji
56 | fiji: '3rd-sun-jan-3h|2nd-sun-nov-2h',
57 | }
58 |
59 | export default patterns
--------------------------------------------------------------------------------
/plugins/dst/zonefile/index.js:
--------------------------------------------------------------------------------
1 | import metas from './metas.js'
2 | import patterns from './dst-patterns.js'
3 | import zones from './zones.js'
4 |
5 | const parsePattern = function (str) {
6 | const a = str.split(/-/)
7 | return {
8 | nth: a[0],
9 | day: a[1],
10 | month: a[2],
11 | hour: a[3],
12 | }
13 | }
14 |
15 | Object.keys(zones).forEach(k => {
16 | const obj = zones[k]
17 | const meta = metas[obj.meta]
18 | zones[k].std = {
19 | abbr: meta.std[0],
20 | offset: meta.std[1]
21 | }
22 | if (obj.dst) {
23 | const pattern = patterns[obj.dst].split(/\|/)
24 | zones[k].dst = {
25 | abbr: meta.dst[0],
26 | offset: meta.dst[1],
27 | start: parsePattern(pattern[0]),
28 | end: parsePattern(pattern[1]),
29 | }
30 | }
31 | obj.name = obj.meta + ' Time'
32 | delete obj.meta
33 | })
34 | console.dir(zones, { depth: 5 })
35 |
--------------------------------------------------------------------------------
/plugins/dst2/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "dst2",
3 | "version": "0.1.0",
4 | "type": "module",
5 | "main": "datetime-math.js",
6 | "exports": {
7 | "./units": "./datetime-units.js",
8 | "./math": "./datetime-math.js"
9 | },
10 | "scripts": {
11 | "test": "node --test tests/",
12 | "watch": "node --watch ./scratch.js"
13 | }
14 | }
--------------------------------------------------------------------------------
/plugins/dst2/scratch.js:
--------------------------------------------------------------------------------
1 | import { addUnits } from './src/index.js'
2 | import { parseDuration, parseDateTime } from './src/lib/parse.js'
3 | import { formatDate } from './src/lib/format.js'
4 | import { applyDurationToJsDate, compareWithJsDate } from './tests/_lib.js'
5 |
6 | let a
7 | a = ['2025-04-29T00:00:00', 'P1D']
8 | // a = ['2024-03-15T23:45:00', 'PT2H30M']
9 | // a = ['2024-02-28T00:00:00', 'P1D']
10 | // a = ['2023-02-28T00:00:00', 'P1D']
11 | // a = ['2024-12-31T23:59:59', 'PT1S']
12 | // a = ['2024-01-31T00:00:00', 'P6M']
13 | // a = ['2024-01-31T00:00:00', 'P14M']
14 |
15 | // Our implementation
16 | const state = parseDateTime(a[0])
17 | const changes = parseDuration(a[1])
18 | const result = addUnits(state, changes)
19 |
20 | console.log(result)
21 | console.log(formatDate(result))
22 | // Compare with JS Date
23 | // const jsDate = applyDurationToJsDate(
24 | // new Date(dateStr),
25 | // parseDuration(durationStr)
26 | // )
27 |
28 | // // console.log('\nTest:', dateStr, '+', durationStr)
29 | // console.log(formatDate(result))
30 | // console.log(formatDate(jsDate))
31 |
32 | // // Compare results
33 | // const matches = compareWithJsDate(result, jsDate)
34 | // console.log('Match?', matches ? '✅' : '❌')
35 |
--------------------------------------------------------------------------------
/plugins/dst2/src/add.js:
--------------------------------------------------------------------------------
1 | import { units, baseUnit } from './units.js'
2 |
3 | const processUnit = (unitName, state, changes) => {
4 | // Base case - no more units to process
5 | if (!unitName) return state
6 |
7 | const unit = units[unitName]
8 |
9 | // Add any changes for this unit
10 | if (changes[unitName]) {
11 | state[unitName] += changes[unitName]
12 | }
13 |
14 | // Handle overflow if needed
15 | if (state[unitName] > unit.max(state)) {
16 | const overflow = Math.floor(state[unitName] - unit.max(state))
17 | state[unitName] -= overflow
18 |
19 | // Add overflow to next unit if it exists
20 | if (unit.next) {
21 | changes[unit.next] = (changes[unit.next] || 0) + overflow
22 | }
23 | }
24 |
25 | // Normalize current unit
26 | state[unitName] = unit.normalize(state[unitName], state)
27 |
28 | // Process next unit
29 | return processUnit(unit.next, state, changes)
30 | }
31 |
32 | export const addUnits = (state, additions) => {
33 | return processUnit(baseUnit, { ...state }, additions)
34 | }
--------------------------------------------------------------------------------
/plugins/dst2/src/index.js:
--------------------------------------------------------------------------------
1 | import { addUnits } from './add.js'
2 |
3 | export { addUnits }
4 |
--------------------------------------------------------------------------------
/plugins/dst2/src/lib/format.js:
--------------------------------------------------------------------------------
1 | // Helper to print dates nicely
2 | export const formatDate = (state) => {
3 | const pad = (n) => String(n).padStart(2, '0')
4 | return `${state.year}-${pad(state.month)}-${pad(state.day)}T` +
5 | `${pad(state.hour)}:${pad(state.minute)}:${pad(state.second)}`
6 | }
7 |
--------------------------------------------------------------------------------
/plugins/dst2/src/lib/native.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spencermountain/spacetime/93e8fc821ea1c2a124bb3a1e46668857bfac4bf3/plugins/dst2/src/lib/native.js
--------------------------------------------------------------------------------
/plugins/dst2/src/lib/parse.js:
--------------------------------------------------------------------------------
1 | // Parse ISO duration string (e.g. P1Y2M3DT4H5M6S)
2 | export const parseDuration = (iso) => {
3 | const matches = iso.match(/P(?:(\d+)Y)?(?:(\d+)M)?(?:(\d+)D)?(?:T(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?)?/)
4 | if (!matches) return null
5 |
6 | return {
7 | year: parseInt(matches[1]) || 0,
8 | month: parseInt(matches[2]) || 0,
9 | day: parseInt(matches[3]) || 0,
10 | hour: parseInt(matches[4]) || 0,
11 | minute: parseInt(matches[5]) || 0,
12 | second: parseInt(matches[6]) || 0
13 | }
14 | }
15 |
16 | // Parse ISO date string (e.g. 2024-03-15T14:30:45)
17 | export const parseDateTime = (iso) => {
18 | const [date, time] = iso.split('T')
19 | const [year, month, day] = date.split('-')
20 | const [hour, minute, second] = (time || '00:00:00').split(':')
21 |
22 | return {
23 | year: parseInt(year),
24 | month: parseInt(month),
25 | day: parseInt(day),
26 | hour: parseInt(hour),
27 | minute: parseInt(minute),
28 | second: parseInt(second)
29 | }
30 | }
--------------------------------------------------------------------------------
/plugins/dst2/tests/_lib.js:
--------------------------------------------------------------------------------
1 | import { formatDate } from '../src/lib/format.js'
2 | // Convert our state object to JS Date
3 | export const stateToDate = (state) => {
4 | return new Date(
5 | state.year,
6 | state.month - 1, // JS months are 0-based
7 | state.day,
8 | state.hour,
9 | state.minute,
10 | state.second
11 | )
12 | }
13 |
14 | // Convert JS Date to our state format
15 | export const dateToState = (date) => ({
16 | year: date.getFullYear(),
17 | month: date.getMonth() + 1, // Convert back to 1-based
18 | day: date.getDate(),
19 | hour: date.getHours(),
20 | minute: date.getMinutes(),
21 | second: date.getSeconds()
22 | })
23 |
24 | // Apply duration changes to JS Date object
25 | export const applyDurationToJsDate = (jsDate, duration) => {
26 | if (duration.year) jsDate.setFullYear(jsDate.getFullYear() + duration.year)
27 | if (duration.month) jsDate.setMonth(jsDate.getMonth() + duration.month)
28 | if (duration.day) jsDate.setDate(jsDate.getDate() + duration.day)
29 | if (duration.hour) jsDate.setHours(jsDate.getHours() + duration.hour)
30 | if (duration.minute) jsDate.setMinutes(jsDate.getMinutes() + duration.minute)
31 | if (duration.second) jsDate.setSeconds(jsDate.getSeconds() + duration.second)
32 | return jsDate
33 | }
34 |
35 | // Compare our result with JS Date behavior
36 | export const compareWithJsDate = (result, jsDate) => {
37 | const jsResult = dateToState(jsDate)
38 | const matches = formatDate(result) === formatDate(jsResult)
39 |
40 | if (!matches) {
41 | console.log('Difference found:')
42 | Object.keys(result).forEach(key => {
43 | if (result[key] !== jsResult[key]) {
44 | console.log(` ${key}: ${result[key]} vs ${jsResult[key]}`)
45 | }
46 | })
47 | }
48 |
49 | return matches
50 | }
--------------------------------------------------------------------------------
/plugins/dst2/tests/auto.test.js:
--------------------------------------------------------------------------------
1 | import test from 'node:test'
2 | import assert from 'node:assert/strict'
3 | import { addUnits } from '../src/index.js'
4 | import { parseDuration, parseDateTime } from '../src/lib/parse.js'
5 | import { applyDurationToJsDate, compareWithJsDate } from './_lib.js'
6 |
7 | // [starting datetime, duration to add]
8 | const tests = [
9 | // Basic additions
10 | ['2024-03-15T23:45:00', 'PT2H30M'],
11 | ['2024-01-01T00:00:00', 'PT24H'],
12 |
13 | // // Month end cases
14 | // ['2024-01-31T00:00:00', 'P1M'], // Jan 31 -> Feb 29 (leap year)
15 | // ['2024-01-31T00:00:00', 'P2M'], // Jan 31 -> Mar 31
16 | // ['2024-01-31T00:00:00', 'P3M'], // Jan 31 -> Apr 30
17 |
18 | // // Leap year cases
19 | // ['2024-02-28T00:00:00', 'P1D'], // Feb 28 -> Feb 29
20 | // ['2023-02-28T00:00:00', 'P1D'], // Feb 28 -> Mar 1 (non-leap)
21 | // ['2024-02-29T00:00:00', 'P1Y'], // Feb 29 -> Feb 28 (next year)
22 |
23 | // // Multiple unit overflow
24 | // ['2024-12-31T23:59:59', 'PT1S'], // Full year rollover
25 | // ['2024-01-31T00:00:00', 'P6M'], // Multi-month
26 | // ['2024-01-31T00:00:00', 'P14M'], // Month overflow to year
27 |
28 | // // Complex cases
29 | // ['2024-02-29T23:59:59', 'P1Y1D'], // Leap day plus overflow
30 | // ['2024-01-31T23:59:59', 'P1M1D'] // Month end plus day
31 | ]
32 |
33 | test('datetime additions', async (t) => {
34 | for (const [dateStr, durationStr] of tests) {
35 | t.test(`${dateStr} + ${durationStr}`, () => {
36 | // Our implementation
37 | const state = parseDateTime(dateStr)
38 | const changes = parseDuration(durationStr)
39 | const result = addUnits(state, changes)
40 |
41 | // Compare with JS Date
42 | const jsDate = applyDurationToJsDate(
43 | new Date(dateStr),
44 | parseDuration(durationStr)
45 | )
46 |
47 | assert.ok(compareWithJsDate(result, jsDate))
48 | })
49 | }
50 | })
--------------------------------------------------------------------------------
/plugins/dst2/tests/manual.test.js:
--------------------------------------------------------------------------------
1 | import { test, describe } from 'node:test'
2 | import assert from 'node:assert/strict'
3 | import { addUnits } from '../src/index.js'
4 | import { parseDuration, parseDateTime } from '../src/lib/parse.js'
5 | import { formatDate } from '../src/lib/format.js'
6 |
7 | describe('manual tests', () => {
8 | const tests = [
9 | ['2024-03-15T00:00:00', 'P1D', '2024-03-16T00:00:00'],
10 | ['2024-03-15T00:00:00', 'P2D', '2024-03-17T00:00:00'],
11 | ['2024-03-15T00:00:00', 'P3D', '2024-03-18T00:00:00'],
12 | ]
13 | tests.forEach(([date, duration, expected], i) => {
14 | test(`${date} + ${duration}`, () => {
15 | let res = addUnits(parseDateTime(date), parseDuration(duration))
16 | res = formatDate(res)
17 | assert.strictEqual(res, expected)
18 | if (i == 1) {
19 | console.log('=====HERE=====')
20 | }
21 | })
22 | })
23 | });
24 |
--------------------------------------------------------------------------------
/plugins/dst2/tests/tmp.test.js:
--------------------------------------------------------------------------------
1 | import { test, describe } from 'node:test'
2 | import assert from 'node:assert/strict'
3 |
4 | describe('Basic test suite', () => {
5 | test('should pass this test', () => {
6 | assert.strictEqual(1 + 1, 2);
7 | });
8 |
9 | test('should pass with async', async () => {
10 | // Simulate async operation
11 | await new Promise(resolve => setTimeout(resolve, 100));
12 | assert.strictEqual(2 + 2, 4);
13 | console.log('hello inside')
14 | });
15 | console.log('hello outside')
16 | process.stderr.write('This log should appear even with dot reporter\n');
17 |
18 | // test('should fail this test', () => {
19 | // assert.strictEqual(1 + 1, 3, 'Expected 1+1 to equal 3');
20 | // });
21 |
22 | test.skip('this test is skipped', () => {
23 | assert.strictEqual(1, 1);
24 | });
25 | });
26 |
27 | describe('Another test group', () => {
28 | test('should pass with object comparison', () => {
29 | const obj1 = { a: 1, b: 2 };
30 | const obj2 = { a: 1, b: 2 };
31 | assert.deepStrictEqual(obj1, obj2);
32 | });
33 |
34 | // test('should fail with object comparison', () => {
35 | // const obj1 = { a: 1, b: 2 };
36 | // const obj2 = { a: 1, b: 3 };
37 | // assert.deepStrictEqual(obj1, obj2);
38 | // });
39 | });
--------------------------------------------------------------------------------
/plugins/geo/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "spacetime-geo",
3 | "version": "1.4.2",
4 | "description": "determine a timezone from a lat/lng",
5 | "main": "./builds/spacetime-geo.js",
6 | "unpkg": "./builds/spacetime-geo.js",
7 | "type": "module",
8 | "sideEffects": false,
9 | "exports": {
10 | ".": {
11 | "require": "./builds/spacetime-geo.cjs",
12 | "import": "./builds/spacetime-geo.mjs",
13 | "default": "./builds/spacetime-geo.mjs"
14 | }
15 | },
16 | "homepage": "https://github.com/spencermountain/spacetime/tree/master/plugins/spacetime-geo",
17 | "scripts": {
18 | "watch": "node --watch ./scratch.js",
19 | "test": "\"node_modules/.bin/tape\" \"./tests/*.test.js\" | \"node_modules/.bin/tap-dancer\" --color",
20 | "build": "rollup -c --silent"
21 | },
22 | "repository": {
23 | "type": "git",
24 | "url": "git+https://github.com/spencermountain/spacetime.git"
25 | },
26 | "keywords": [
27 | "timezone"
28 | ],
29 | "author": "spencermountain@gmail.com",
30 | "dependencies": {
31 | "tz-lookup": "6.1.25"
32 | },
33 | "devDependencies": {
34 | "shelljs": "0.8.5",
35 | "spacetime": ">=7.4.0",
36 | "tap-dancer": "0.3.4",
37 | "tape": "5.6.3"
38 | },
39 | "license": "MIT"
40 | }
--------------------------------------------------------------------------------
/plugins/geo/rollup.config.js:
--------------------------------------------------------------------------------
1 | import commonjs from 'rollup-plugin-commonjs'
2 | import json from 'rollup-plugin-json'
3 | import { terser } from 'rollup-plugin-terser'
4 | import resolve from 'rollup-plugin-node-resolve'
5 | import sizeCheck from 'rollup-plugin-filesize-check'
6 | import pkg from './package.json' assert { type: "json" };
7 | const version = pkg.version
8 | console.log('\n 📦 - running rollup..\n')
9 |
10 | const banner = '/* spencermountain/spacetime-geo ' + version + ' MIT */'
11 |
12 | export default [
13 | {
14 | input: 'src/index.js',
15 | output: [{ banner: banner, file: 'builds/spacetime-geo.mjs', format: 'esm' }],
16 | plugins: [resolve(), json(), commonjs(), sizeCheck({ expect: 109, warn: 10 })]
17 | },
18 | {
19 | input: 'src/index.js',
20 | output: [{ banner: banner, file: 'builds/spacetime-geo.cjs', format: 'umd', sourcemap: false, name: 'spacetimeGeo' }],
21 | plugins: [resolve(), json(), commonjs(), sizeCheck({ expect: 109, warn: 10 })]
22 | },
23 | {
24 | input: 'src/index.js',
25 | output: [{ banner: banner, file: 'builds/spacetime-geo.min.js', format: 'umd', name: 'spacetimeGeo' }],
26 | plugins: [resolve(), json(), commonjs(), terser(), sizeCheck({ expect: 109, warn: 10 })]
27 | }
28 | ]
29 |
--------------------------------------------------------------------------------
/plugins/geo/scratch.js:
--------------------------------------------------------------------------------
1 | import spacetime from 'spacetime'
2 | import geo from './src/index.js'
3 | spacetime.extend(geo)
4 |
5 |
6 | // let s = spacetime('june 4 2018', 'Canada/Eastern') //.time('3:37pm')
7 | // s = s.in([48.7235, 1.9931]) //near paris
8 | // console.log(s.timezone().name)
9 |
10 | let s = spacetime.today('America/Havana')
11 | console.log(s.point())
12 |
13 | s = spacetime.today('Asia/Famagusta')
14 | console.log(s.point())
15 |
--------------------------------------------------------------------------------
/plugins/geo/scripts/getPoints/index.js:
--------------------------------------------------------------------------------
1 | const fetch = require('node-fetch')
2 | let iana = require('./iana')
3 | // const fs = require('fs');
4 | const key = 'get-your-own'
5 |
6 | const done = {}
7 |
8 | iana = iana.map((arr) => {
9 | const cities = arr[3].split(',')
10 | let tz = arr[1]
11 | tz = tz.split('/')
12 | const city = cities[0] || tz[tz.length - 1]
13 | const str = `${city}, ${arr[2]}`
14 | return {
15 | str: str,
16 | tz: arr[1]
17 | }
18 | })
19 |
20 | const roundIt = function(num) {
21 | return Math.round(num * 100) / 100
22 | }
23 |
24 | function doit(i) {
25 | const str = iana[i].str
26 | const url = `https://maps.googleapis.com/maps/api/geocode/json?address=${encodeURIComponent(str)}&key=${key}`
27 | fetch(url).then(res => res.json()).then(res => {
28 | if (res.results && res.results[0]) {
29 | let point = res.results[0].geometry.location
30 | const place = res.results[0].formatted_address
31 | point = [roundIt(point.lat), roundIt(point.lng)]
32 | done[iana[i].tz] = {
33 | point: point,
34 | place: place
35 | }
36 | console.log(place)
37 | } else {
38 | console.log('\nmissing ' + str)
39 | }
40 | i += 1
41 | if (iana[i]) {
42 | doit(i)
43 | } else {
44 | // fs.writeFileSync('./src/data.json', JSON.stringify(done, null, 2));
45 | console.log('{')
46 | Object.keys(done).forEach((k) => {
47 | console.log(` "${k}" : '${done[k].point[0]},${done[k].point[1]}', //${done[k].place}`)
48 | })
49 | console.log('}')
50 | }
51 | }).catch(console.log)
52 | }
53 |
54 | doit(0)
55 |
--------------------------------------------------------------------------------
/plugins/geo/scripts/getPoints/qa-test.js:
--------------------------------------------------------------------------------
1 | const tzlookup = require("tz-lookup");
2 | const points = require('../../src/IANA-points.js')
3 |
4 | Object.keys(points).forEach((k) => {
5 | const geo = points[k].split(',')
6 | const tz = tzlookup(geo[0], geo[1])
7 | if (k !== tz) {
8 | console.log(`want: ${k}, have: ${tz}`)
9 | }
10 | })
11 |
--------------------------------------------------------------------------------
/plugins/geo/src/findTz/index.js:
--------------------------------------------------------------------------------
1 | import tzlookup from 'tz-lookup'
2 |
3 | //.trim() pollyfill
4 | if (!String.prototype.trim) {
5 | const rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g
6 | String.prototype.trim = function () {
7 | return this.replace(rtrim, '')
8 | }
9 | }
10 | const isArray = function (hmm) {
11 | return Object.prototype.toString.call(hmm) === '[object Array]'
12 | }
13 | const isString = function (hmm) {
14 | return typeof hmm === 'string'
15 | }
16 | function isObject(hmm) {
17 | return hmm instanceof Object && hmm.constructor === Object
18 | }
19 |
20 | const findTz = function (geo, b) {
21 | let lat = null
22 | let lng = null
23 | //accept weird formats
24 | if (typeof b === 'number' && typeof geo === 'number') {
25 | lat = geo
26 | lng = b
27 | } else if (isArray(geo) === true) {
28 | lat = geo[0]
29 | lng = geo[1]
30 | } else if (isString(geo) === true) {
31 | const arr = geo.split(/[,/]/)
32 | lat = arr[0].trim()
33 | lng = arr[1].trim()
34 | } else if (isObject(geo) === true) {
35 | lat = geo.lat || geo.latitude
36 | lng = geo.lng || geo.lon || geo.long || geo.longitude
37 | } else {
38 | return this
39 | }
40 | //validate lat/lng
41 | if (lat < -90 || lat > 90) {
42 | console.warn('Invalid latitude: ' + lat)
43 | return this
44 | }
45 | if (lng < -180 || lng > 180) {
46 | console.warn('Invalid longitude: ' + lng)
47 | return this
48 | }
49 | const tz = tzlookup(lat, lng)
50 | if (!tz) {
51 | console.warn('Found no timezone for ' + lat + ', ' + lng)
52 | return this
53 | }
54 | return this.goto(tz)
55 | }
56 | export default findTz
57 |
--------------------------------------------------------------------------------
/plugins/geo/src/index.js:
--------------------------------------------------------------------------------
1 | import find from './findTz/index.js'
2 | import point from './point/index.js'
3 |
4 | export default {
5 | in: find,
6 | point: point,
7 | }
8 |
--------------------------------------------------------------------------------
/plugins/geo/src/point/index.js:
--------------------------------------------------------------------------------
1 | import points from './IANA-points.js'
2 | //
3 | const point = function () {
4 | const tz = this.timezone().name
5 | if (points.hasOwnProperty(tz) === false) {
6 | console.warn('Unable to find location for timezone ' + tz)
7 | return {}
8 | }
9 | const arr = points[tz].split(',')
10 | return {
11 | lat: parseFloat(arr[0]),
12 | lng: parseFloat(arr[1])
13 | }
14 | }
15 | export default point
16 |
--------------------------------------------------------------------------------
/plugins/geo/tests/in.test.js:
--------------------------------------------------------------------------------
1 | import test from 'tape'
2 | import spacetime from '../../../src/index.js'
3 | // import spacetime from 'spacetime'
4 | import geo from '../src/index.js'
5 | // import geo from '../builds/spacetime-geo.mjs'
6 |
7 | test('test some lat/lngs', function (t) {
8 | spacetime.extend(geo)
9 |
10 | let s = spacetime('june 4 2018', 'Canada/Eastern').time('3:37pm')
11 | s = s.in([48.7235, 1.9931]) //near paris
12 | t.equal(s.timezone().name, 'Europe/Paris', 'found-paris')
13 | t.equal(s.time(), '9:37pm', 'time has moved')
14 |
15 | s = s.in([42.7235, -73.6931]) //new york
16 | t.equal(s.timezone().name, 'America/New_York', 'found-ny')
17 | t.equal(s.time(), '3:37pm', 'time has back to eastern')
18 |
19 | s = s.in([50.405, -31.8971]) // atlantic ocean
20 | t.equal(s.timezone().name, 'Etc/GMT+2', 'found-ocean')
21 | t.equal(s.time(), '5:37pm', 'time has moved to ocean')
22 |
23 | s = s.in([50.405, -18.8971]) //bit further atlantic ocean
24 | t.equal(s.timezone().name, 'Etc/GMT+1', 'futher-into-ocean')
25 | t.equal(s.time(), '6:37pm', 'almost europe')
26 |
27 | s = s.in([50.405, -40.8971]) //bit closer to canada
28 | t.equal(s.timezone().name, 'Etc/GMT+3', 'closer-to-halifax')
29 | t.equal(s.time(), '4:37pm', 'almost halifax')
30 |
31 | s = s.in([-20, -40.8971]) //down to brazil
32 | t.equal(s.timezone().name, 'America/Sao_Paulo', 'closer-to-halifax')
33 | t.equal(s.time(), '4:37pm', 'almost halifax')
34 |
35 | t.end()
36 | })
37 |
--------------------------------------------------------------------------------
/plugins/geo/tests/point.test.js:
--------------------------------------------------------------------------------
1 | import test from 'tape'
2 | import spacetime from 'spacetime'
3 | import geo from '../src/index.js'
4 | // import geo from '../builds/spacetime-geo.mjs'
5 |
6 | test('test some lat/lngs', function (t) {
7 | spacetime.extend(geo)
8 |
9 | let s = spacetime('june 4 2018', 'Canada/Eastern')
10 | let point = s.point()
11 | t.equal(parseInt(point.lat, 10), 43, 'toronto-lat')
12 | t.equal(parseInt(point.lng, 10), -79, 'toronto-lng')
13 |
14 | s = spacetime('june 14 2018', 'Canada/Pacific')
15 | point = s.point()
16 | t.equal(parseInt(point.lat, 10), 49, 'vancouver-lat')
17 | t.equal(parseInt(point.lng, 10), -123, 'vancouver-lng')
18 |
19 | s = spacetime.now('Europe/Paris')
20 | point = s.point()
21 | t.equal(parseInt(point.lat, 10), 48, 'paris-lat')
22 | t.equal(parseInt(point.lng, 10), 2, 'paris-lng')
23 |
24 | t.end()
25 | })
26 |
--------------------------------------------------------------------------------
/plugins/holiday/changelog.md:
--------------------------------------------------------------------------------
1 | ### 0.1.0
2 | - **[change]** add timezone as third parameter
3 | - update deps
--------------------------------------------------------------------------------
/plugins/holiday/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "spacetime-holiday",
3 | "description": "reckoning of common holiday dates ",
4 | "version": "0.3.0",
5 | "main": "builds/spacetime-holiday.cjs",
6 | "unpkg": "builds/spacetime-holiday.min.js",
7 | "module": "./builds/spacetime-holiday.mjs",
8 | "type": "module",
9 | "sideEffects": false,
10 | "exports": {
11 | ".": {
12 | "require": "./builds/spacetime-holiday.cjs",
13 | "import": "./builds/spacetime-holiday.mjs",
14 | "default": "./builds/spacetime-holiday.mjs"
15 | }
16 | },
17 | "author": "Spencer Kelly",
18 | "homepage": "https://github.com/spencermountain/spacetime/tree/master/plugins/holiday",
19 | "repository": {
20 | "type": "git",
21 | "url": "git+https://github.com/spencermountain/spacetime.git"
22 | },
23 | "scripts": {
24 | "watch": "node --watch ./scratch.js",
25 | "build": "rollup -c --silent",
26 | "test": "TESTENV=dev tape ./tests/**/*.test.js | tap-dancer",
27 | "testb": "TESTENV=prod tape ./tests/**/*.test.js | tap-dancer"
28 | },
29 | "files": [
30 | "builds/"
31 | ],
32 | "prettier": {
33 | "trailingComma": "none",
34 | "tabWidth": 2,
35 | "semi": false,
36 | "singleQuote": true,
37 | "printWidth": 100
38 | },
39 | "peerDependencies": {
40 | "spacetime": ">=6.3.0"
41 | },
42 | "devDependencies": {
43 | "tap-dancer": "0.3.4",
44 | "tape": "5.5.3"
45 | },
46 | "licence": "MIT"
47 | }
--------------------------------------------------------------------------------
/plugins/holiday/rollup.config.js:
--------------------------------------------------------------------------------
1 | import commonjs from 'rollup-plugin-commonjs'
2 | import json from 'rollup-plugin-json'
3 | import { terser } from 'rollup-plugin-terser'
4 | import resolve from 'rollup-plugin-node-resolve'
5 | import sizeCheck from 'rollup-plugin-filesize-check'
6 |
7 | export default [
8 | {
9 | input: 'src/index.js',
10 | output: [{ file: 'builds/spacetime-holiday.mjs', format: 'esm' }],
11 | plugins: [resolve(), json(), commonjs(), sizeCheck({ expect: 13, warn: 10 })],
12 | external: ['spacetime']
13 | },
14 | {
15 | input: 'src/index.js',
16 | output: [{
17 | file: 'builds/spacetime-holiday.cjs', format: 'umd', name: 'spacetimeHoliday',
18 | globals: {
19 | spacetime: 'spacetime'
20 | }
21 | }
22 | ],
23 | plugins: [resolve(), json(), commonjs(), sizeCheck({ expect: 6, warn: 10 })],
24 | external: ['spacetime']
25 | },
26 | {
27 | input: 'src/index.js',
28 | output: [{
29 | file: 'builds/spacetime-holiday.min.js', format: 'umd', name: 'spacetimeHoliday',
30 | globals: {
31 | spacetime: 'spacetime'
32 | }
33 | }],
34 | plugins: [resolve(), json(), commonjs(), terser(), sizeCheck({ expect: 12, warn: 10 })],
35 | external: ['spacetime']
36 | }
37 | ]
38 |
--------------------------------------------------------------------------------
/plugins/holiday/scratch.js:
--------------------------------------------------------------------------------
1 | // const spacetimeHoliday = require('./builds/spacetime-holiday.js')
2 | import spacetimeHoliday from './src'
3 |
4 | const s = spacetimeHoliday('ramadan', 2019, 'Canada/Pacific')
5 | console.log(s.format('iso'))
6 |
--------------------------------------------------------------------------------
/plugins/holiday/src/01-fixedDates.js:
--------------------------------------------------------------------------------
1 | import spacetime from 'spacetime'
2 | import fixed from './holidays/fixed-holidays.js'
3 |
4 | // holidays that are the same date every year
5 | const fixedDates = function (str, normal, year, tz) {
6 | if (fixed.hasOwnProperty(str) || fixed.hasOwnProperty(normal)) {
7 | const arr = fixed[str] || fixed[normal] || []
8 | let s = spacetime.now(tz)
9 | s = s.year(year)
10 | s = s.startOf('year')
11 | s = s.month(arr[0])
12 | s = s.date(arr[1])
13 | if (s.isValid()) {
14 | return s
15 | }
16 | }
17 | return null
18 | }
19 | export default fixedDates
20 |
--------------------------------------------------------------------------------
/plugins/holiday/src/02-nthWeekday.js:
--------------------------------------------------------------------------------
1 | import spacetime from 'spacetime'
2 | import calendar from './holidays/calendar-holidays.js'
3 |
4 | // holidays that are the same date every year
5 | const fixedDates = function (str, normal, year, tz) {
6 | if (calendar.hasOwnProperty(str) || calendar.hasOwnProperty(normal)) {
7 | const arr = calendar[str] || calendar[normal] || []
8 | let s = spacetime.now(tz)
9 | s = s.year(year)
10 |
11 | // [3rd, 'monday', 'january']
12 | s = s.month(arr[2])
13 | s = s.startOf('month')
14 | // make it january
15 | const month = s.month()
16 |
17 | // make it the 1st monday
18 | s = s.day(arr[1])
19 | if (s.month() !== month) {
20 | s = s.add(1, 'week')
21 | }
22 | // make it nth monday
23 | if (arr[0] > 1) {
24 | s = s.add(arr[0] - 1, 'week')
25 | }
26 | if (s.isValid()) {
27 | return s
28 | }
29 | }
30 |
31 | return null
32 | }
33 | export default fixedDates
34 |
--------------------------------------------------------------------------------
/plugins/holiday/src/03-easterDates.js:
--------------------------------------------------------------------------------
1 | import holidays from './holidays/easter-holidays.js'
2 | import spacetime from 'spacetime'
3 | import calcEaster from './lib/calcEaster.js'
4 |
5 | //calculate any holidays based on easter
6 | const easterDates = function (str, normal, year, tz) {
7 | if (holidays.hasOwnProperty(str) || holidays.hasOwnProperty(normal)) {
8 | const days = holidays[str] || holidays[normal] || []
9 |
10 | const date = calcEaster(year)
11 | if (!date) {
12 | return null //no easter for this year
13 | }
14 | let e = spacetime(date, tz)
15 | e = e.year(year)
16 |
17 | const s = e.add(days, 'day')
18 | if (s.isValid()) {
19 | return s
20 | }
21 | }
22 | return null
23 | }
24 | export default easterDates
25 |
--------------------------------------------------------------------------------
/plugins/holiday/src/04-astronomical.js:
--------------------------------------------------------------------------------
1 | import spacetime from 'spacetime'
2 | import calcSeasons from './lib/seasons.js'
3 | import holidays from './holidays/astro-holidays.js'
4 |
5 | const astroDates = function (str, normal, year, tz) {
6 | if (holidays.hasOwnProperty(str) || holidays.hasOwnProperty(normal)) {
7 | const season = holidays[str] || holidays[normal]
8 | const seasons = calcSeasons(year)
9 | if (!season || !seasons || !seasons[season]) {
10 | return null // couldn't figure it out
11 | }
12 | const s = spacetime(seasons[season], tz)
13 | if (s.isValid()) {
14 | return s
15 | }
16 | }
17 |
18 | return null
19 | }
20 | export default astroDates
21 |
--------------------------------------------------------------------------------
/plugins/holiday/src/05-lunarDates.js:
--------------------------------------------------------------------------------
1 | import spacetime from 'spacetime'
2 | import holidays from './holidays/lunar-holidays.js'
3 | // (lunar year is 354.36 days)
4 | const dayDiff = -10.64
5 |
6 | const lunarDates = function (str, normal, year, tz) {
7 | if (holidays.hasOwnProperty(str) || holidays.hasOwnProperty(normal)) {
8 | const date = holidays[str] || holidays[normal] || []
9 | if (!date) {
10 | return null
11 | }
12 | // start at 2018
13 | let s = spacetime(date + ' 2018', tz)
14 | const diff = year - 2018
15 | const toAdd = diff * dayDiff
16 | s = s.add(toAdd, 'day')
17 | s = s.startOf('day')
18 |
19 | // now set the correct year
20 | s = s.year(year)
21 |
22 | if (s.isValid()) {
23 | return s
24 | }
25 | }
26 | return null
27 | }
28 | export default lunarDates
29 |
--------------------------------------------------------------------------------
/plugins/holiday/src/holidays/astro-holidays.js:
--------------------------------------------------------------------------------
1 | // these are properly calculated in ./lib/seasons
2 | const dates = {
3 | 'spring equinox': 'spring',
4 | 'summer solistice': 'summer',
5 | 'fall equinox': 'fall',
6 | 'winter solstice': 'winter'
7 | }
8 |
9 | // aliases
10 | dates['march equinox'] = dates['spring equinox']
11 | dates['vernal equinox'] = dates['spring equinox']
12 | dates['ostara'] = dates['spring equinox']
13 |
14 | dates['june solstice'] = dates['summer solistice']
15 | dates['litha'] = dates['summer solistice']
16 |
17 | dates['autumn equinox'] = dates['fall equinox']
18 | dates['autumnal equinox'] = dates['fall equinox']
19 | dates['september equinox'] = dates['fall equinox']
20 | dates['sept equinox'] = dates['fall equinox']
21 | dates['mabon'] = dates['fall equinox']
22 |
23 | dates['december solstice'] = dates['winter solistice']
24 | dates['dec solstice'] = dates['winter solistice']
25 | dates['yule'] = dates['winter solistice']
26 |
27 | export default dates
28 |
--------------------------------------------------------------------------------
/plugins/holiday/src/holidays/calendar-holidays.js:
--------------------------------------------------------------------------------
1 | //these are holidays on the 'nth weekday of month'
2 | const jan = 'january'
3 | const feb = 'february'
4 | const mar = 'march'
5 | // const apr = 'april'
6 | const may = 'may'
7 | const jun = 'june'
8 | // const jul = 'july'
9 | // const aug = 'august'
10 | const sep = 'september'
11 | const oct = 'october'
12 | const nov = 'november'
13 | // const dec = 'december'
14 |
15 | const mon = 'monday'
16 | // const tues = 'tuesday'
17 | // const wed = 'wednesday'
18 | const thurs = 'thursday'
19 | const fri = 'friday'
20 | // const sat = 'saturday'
21 | const sun = 'sunday'
22 |
23 | const holidays = {
24 | 'martin luther king day': [3, mon, jan], //[third monday in january],
25 | 'presidents day': [3, mon, feb], //[third monday in february],
26 |
27 | 'commonwealth day': [2, mon, mar], //[second monday in march],
28 | 'mothers day': [2, sun, may], //[second Sunday in May],
29 | 'fathers day': [3, sun, jun], //[third Sunday in June],
30 | 'labor day': [1, mon, sep], //[first monday in september],
31 | 'columbus day': [2, mon, oct], //[second monday in october],
32 | 'canadian thanksgiving': [2, mon, oct], //[second monday in october],
33 | thanksgiving: [4, thurs, nov], // [fourth Thursday in November],
34 | 'black friday': [4, fri, nov] //[fourth friday in november],
35 |
36 | // 'memorial day': [may], //[last monday in may],
37 | // 'us election': [nov], // [Tuesday following the first Monday in November],
38 | // 'cyber monday': [nov]
39 | // 'advent': [] // fourth Sunday before Christmas
40 | }
41 |
42 | // add aliases
43 | holidays['turday day'] = holidays.thanksgiving
44 | holidays['indigenous peoples day'] = holidays['columbus day']
45 | holidays['mlk day'] = holidays['martin luther king day']
46 | export default holidays
47 |
--------------------------------------------------------------------------------
/plugins/holiday/src/holidays/easter-holidays.js:
--------------------------------------------------------------------------------
1 | // https://www.timeanddate.com/calendar/determining-easter-date.html
2 |
3 | const dates = {
4 | easter: 0,
5 | 'ash wednesday': -46, // (46 days before easter)
6 | 'palm sunday': 7, // (1 week before easter)
7 | 'maundy thursday': -3, // (3 days before easter)
8 | 'good friday': -2, // (2 days before easter)
9 | 'holy saturday': -1, // (1 days before easter)
10 | 'easter saturday': -1, // (1 day before easter)
11 | 'easter monday': 1, // (1 day after easter)
12 | 'ascension day': 39, // (39 days after easter)
13 | 'whit sunday': 49, // / pentecost (49 days after easter)
14 | 'whit monday': 50, // (50 days after easter)
15 | 'trinity sunday': 65, // (56 days after easter)
16 | 'corpus christi': 60, // (60 days after easter)
17 |
18 | 'mardi gras': -47 //(47 days before easter)
19 | }
20 | dates['easter sunday'] = dates.easter
21 | dates.pentecost = dates['whit sunday']
22 | dates.whitsun = dates['whit sunday']
23 |
24 | export default dates
25 |
--------------------------------------------------------------------------------
/plugins/holiday/src/holidays/lunar-holidays.js:
--------------------------------------------------------------------------------
1 | const dates = {
2 | // Muslim holidays
3 | 'isra and miraj': 'april 13',
4 | 'lailat al-qadr': 'june 10',
5 | 'eid al-fitr': 'june 15',
6 | 'id al-Fitr': 'june 15',
7 | 'eid ul-Fitr': 'june 15',
8 | ramadan: 'may 16', // Range holiday
9 | 'eid al-adha': 'sep 22',
10 | muharram: 'sep 12',
11 | 'prophets birthday': 'nov 21'
12 | }
13 | export default dates
14 |
--------------------------------------------------------------------------------
/plugins/holiday/src/holidays/misc-holidays.js:
--------------------------------------------------------------------------------
1 | //yep,
2 | const jan = 0
3 | const feb = 1
4 | const march = 2
5 | const april = 3
6 | const may = 4
7 | // const june = 5
8 | const july = 6
9 | // const august = 7
10 | const sep = 8
11 | const oct = 9
12 | const nov = 10
13 | const dec = 11
14 |
15 | // hardcoded dates for astronomical holidays
16 | // ----please change, every few years(!)---
17 | const dates = {
18 | // Jewish
19 | 'tu bishvat': [jan, 31],
20 | 'tu bshevat': [jan, 31],
21 | purim: [march, 1],
22 | passover: [march, 31], // Ranged holiday [april, 7],
23 | 'yom hashoah': [april, 11],
24 | 'lag baomer': [may, 3],
25 | shavuot: [may, 20],
26 | 'tisha bav': [july, 22],
27 | 'rosh hashana': [sep, 10],
28 | 'yom kippur': [sep, 19],
29 | sukkot: [sep, 24], // Ranged holiday [sep, 30],
30 | 'shmini atzeret': [oct, 1],
31 | 'simchat torah': [oct, 2],
32 | chanukah: [dec, 3], // Ranged holiday [dec, 30],
33 | hanukkah: [dec, 3], // Ranged holiday [dec, 30],
34 |
35 | // Additional important holidays
36 | 'chinese new year': [feb, 16],
37 | diwali: [nov, 7]
38 | }
39 | export default dates
40 |
--------------------------------------------------------------------------------
/plugins/holiday/src/index.js:
--------------------------------------------------------------------------------
1 | import spacetime from 'spacetime'
2 | import fixedDates from './01-fixedDates.js'
3 | import nthWeekday from './02-nthWeekday.js'
4 | import easterDates from './03-easterDates.js'
5 | import astroDates from './04-astronomical.js'
6 | import lunarDates from './05-lunarDates.js'
7 | const nowYear = spacetime.now().year()
8 |
9 | const spacetimeHoliday = function (str, year, tz) {
10 | year = year || nowYear
11 | str = str || ''
12 | str = String(str)
13 | str = str.trim().toLowerCase()
14 | str = str.replace(/'s/, 's') // 'mother's day'
15 |
16 | let normal = str.replace(/ day$/, '')
17 | normal = normal.replace(/^the /, '')
18 | normal = normal.replace(/^orthodox /, '') //orthodox good friday
19 |
20 | // try easier, unmoving holidays
21 | let s = fixedDates(str, normal, year, tz)
22 | if (s !== null) {
23 | return s
24 | }
25 | // try 'nth monday' holidays
26 | s = nthWeekday(str, normal, year, tz)
27 | if (s !== null) {
28 | return s
29 | }
30 | // easter-based holidays
31 | s = easterDates(str, normal, year, tz)
32 | if (s !== null) {
33 | return s
34 | }
35 | // solar-based holidays
36 | s = astroDates(str, normal, year, tz)
37 | if (s !== null) {
38 | return s
39 | }
40 | // mostly muslim holidays
41 | s = lunarDates(str, normal, year, tz)
42 | if (s !== null) {
43 | return s
44 | }
45 |
46 | return null
47 | }
48 | export default spacetimeHoliday
49 |
--------------------------------------------------------------------------------
/plugins/holiday/src/lib/calcEaster.js:
--------------------------------------------------------------------------------
1 | // by John Dyer
2 | // based on the algorithm by Oudin (1940) from http://www.tondering.dk/claus/cal/easter.php
3 | const calcEaster = function (year) {
4 | let f = Math.floor,
5 | // Golden Number - 1
6 | G = year % 19,
7 | C = f(year / 100),
8 | // related to Epact
9 | H = (C - f(C / 4) - f((8 * C + 13) / 25) + 19 * G + 15) % 30,
10 | // number of days from 21 March to the Paschal full moon
11 | I = H - f(H / 28) * (1 - f(29 / (H + 1)) * f((21 - G) / 11)),
12 | // weekday for the Paschal full moon
13 | J = (year + f(year / 4) + I + 2 - C + f(C / 4)) % 7,
14 | // number of days from 21 March to the Sunday on or before the Paschal full moon
15 | L = I - J,
16 | month = 3 + f((L + 40) / 44),
17 | date = L + 28 - 31 * f(month / 4)
18 |
19 | month = month === 4 ? 'April' : 'March'
20 | return month + ' ' + date
21 | }
22 |
23 | export default calcEaster
24 |
--------------------------------------------------------------------------------
/plugins/holiday/tests/_lib.js:
--------------------------------------------------------------------------------
1 | import src from '../src/index.js'
2 | import build from '../builds/spacetime-holiday.mjs'
3 | let lib = src
4 |
5 | if (typeof process !== undefined && typeof module !== undefined) {
6 | if (process.env.TESTENV === 'prod') {
7 | console.warn('== production build test 🚀 ==')
8 | lib = build
9 | }
10 |
11 | }
12 | export default lib
13 |
--------------------------------------------------------------------------------
/plugins/play/scratch.js:
--------------------------------------------------------------------------------
1 | import spacetime from 'spacetime'
2 | import plugin from './src/index.js'
3 |
4 | spacetime.extend(plugin)
5 |
6 | const s = spacetime.now()
7 | s.play()
8 |
--------------------------------------------------------------------------------
/plugins/play/src/Ticker.js:
--------------------------------------------------------------------------------
1 | /* global performance */
2 |
3 | // recursive setTimeOut - not perfect, but does not drift
4 | // https://stackoverflow.com/questions/29971898/how-to-create-an-accurate-timer-in-javascript
5 | // see benchmarks at https://github.com/dbkaplun/driftless
6 | class Ticker {
7 | constructor(hertz, callback) {
8 | this.target = performance.now() // target time for the next frame
9 | this.interval = (1 / hertz) * 1000 // the milliseconds between ticks
10 | this.callback = callback
11 | this.stopped = false
12 | this.frame = 0
13 | this.tick(this)
14 | }
15 |
16 | tick(self) {
17 | if (self.stopped) {
18 | return
19 | }
20 | const currentTime = performance.now()
21 | const currentTarget = self.target
22 | const currentInterval = (self.target += self.interval) - currentTime
23 |
24 | setTimeout(self.tick, currentInterval, self)
25 | self.callback(self.frame++, currentTime, currentTarget, self)
26 | }
27 |
28 | stop() {
29 | this.stopped = true
30 | return this.frame
31 | }
32 | }
33 |
34 | export default Ticker
35 |
36 | // let c = new Ticker(2, () => { console.log('tick') })
37 |
--------------------------------------------------------------------------------
/plugins/play/src/index.js:
--------------------------------------------------------------------------------
1 | const methods = {
2 | start: function () {
3 | this.startEpoch = this.epoch
4 | return this
5 | },
6 | stop: function () {
7 | this.startEpoch = null
8 | this.isRunning = false
9 | return this
10 | },
11 | pause: function () {
12 | this.isRunning = false
13 | return this
14 | },
15 | elapsed: async function () {
16 | const start = this._from(this.startEpoch, this.tz)
17 | return this.diff(start)
18 | }
19 | }
20 | methods.play = methods.start
21 | export default methods
22 |
--------------------------------------------------------------------------------
/plugins/ticks/README.md:
--------------------------------------------------------------------------------
1 |
12 |
13 | calculate some sensible break-points between two dates, using the [spacetime](https://github.com/spencermountain/spacetime) date library.
14 |
15 | `npm i spacetime-ticks`
16 |
17 |
18 |
19 |
20 |
21 | ```js
22 | import spacetimeTicks from 'spacetime-ticks'
23 |
24 | let ticks = spacetimeTicks('June 5th 1992', 'Oct 4 2002', 5)
25 | // [
26 | // { label: "1993", epoch: 725864400000, value: 0.055 }
27 | // { label: "1995", epoch: 788936400000, value: 0.248 }
28 | // { label: "1997", epoch: 852094800000, value: 0.442 }
29 | // { label: "1999", epoch: 915166800000, value: 0.636 }
30 | // { label: "2001", epoch: 978325200000, value: 0.829 }
31 | // ]
32 | ```
33 |
34 | This library has some opinions:
35 | * ticks should always be `spaced evenly`, even if this means less ticks
36 | * a tick should appear **at the start** of months, years, days
37 | * they don't need to begin or end at the start andend.
38 | * *less ticks* are better than too-many ticks
39 |
40 | it was built for labelling an x-axis in a space-limited way, but you can use it for whatever weird stuff.
41 |
42 | ## See also:
43 | * [d3-time](https://github.com/d3/d3-time)
44 | * [sometime](https://github.com/spencermountain/sometime) - spacetime-calendar
45 |
46 | MIT
47 |
--------------------------------------------------------------------------------
/plugins/ticks/_version.js:
--------------------------------------------------------------------------------
1 | export default '0.3.0'
--------------------------------------------------------------------------------
/plugins/ticks/demo/_drawGraph.js:
--------------------------------------------------------------------------------
1 | const somehow = require('somehow')
2 |
3 | const drawGraph = function (ticks, id) {
4 | const el = document.querySelector(id)
5 | const w = somehow({
6 | width: 500,
7 | height: 20,
8 | })
9 | ticks.map((tick) => {
10 | w.dot().at(tick.value, 1)
11 | })
12 | w.y.fit()
13 | w.x.fit(0, 1)
14 | w.yAxis.remove()
15 | // w.xAxis.remove()
16 | el.innerHTML = w.build()
17 | }
18 | export default drawGraph
19 |
--------------------------------------------------------------------------------
/plugins/ticks/demo/custom.js:
--------------------------------------------------------------------------------
1 | const htm = require('htm')
2 | const vhtml = require('vhtml');
3 | const h = htm.bind(vhtml);
4 | const inputs = require('somehow-input');
5 | const drawGraph = require('./_drawGraph')
6 | const spacetimeTicks = require('../src')
7 |
8 | const printTicks = function() {
9 | const start = document.querySelector('#start').querySelector('input').value
10 | const end = document.querySelector('#end').querySelector('input').value
11 | const n = document.querySelector('#ticks').querySelector('select').value
12 | let ticks = spacetimeTicks(start, end, n)
13 | drawGraph(ticks, '#graph-two')
14 | ticks = ticks.map((o) => {
15 | return h`
16 | ${o.label} |
17 | ${o.value} |
18 |
`
19 | })
20 | document.querySelector('#results-two').innerHTML = h``
21 | }
22 |
23 | const start = inputs.input({
24 | label: 'start',
25 | value: 'June 5th 1998',
26 | width: 130,
27 | cb: () => printTicks()
28 | })
29 | const end = inputs.input({
30 | label: 'end',
31 | value: 'Oct 4 2002',
32 | width: 130,
33 | cb: () => printTicks()
34 | })
35 | const select = inputs.select({
36 | label: 'max-ticks',
37 | value: '6',
38 | width: 50,
39 | options: ['4', '5', '6', '7', '8', '9', '10', '11'],
40 | cb: () => printTicks()
41 | })
42 | document.querySelector('#start').innerHTML = start.build()
43 | document.querySelector('#ticks').innerHTML = select.build()
44 | document.querySelector('#end').innerHTML = end.build()
45 |
46 | printTicks()
47 |
--------------------------------------------------------------------------------
/plugins/ticks/demo/duration.js:
--------------------------------------------------------------------------------
1 | const spacetime = require('spacetime')
2 | const htm = require('htm')
3 | const vhtml = require('vhtml');
4 | const h = htm.bind(vhtml);
5 | const inputs = require('somehow-input');
6 | const drawGraph = require('./_drawGraph')
7 | const spacetimeTicks = require('../src')
8 |
9 | const printTicks = function() {
10 | const start = document.querySelector('#origin').querySelector('input').value
11 | const duration = document.querySelector('#duration').querySelector('input').value
12 | const n = document.querySelector('#ticks-two').querySelector('select').value
13 | const end = spacetime(start).epoch + Number(duration)
14 | let ticks = spacetimeTicks(start, end, n)
15 | drawGraph(ticks, '#graph')
16 | ticks = ticks.map((o) => {
17 | return h`
18 | ${o.label} |
19 | ${o.value} |
20 |
`
21 | })
22 | document.querySelector('#results').innerHTML = h``
23 | }
24 |
25 | const start = inputs.input({
26 | label: 'start',
27 | value: 'June 5th 1998',
28 | width: 130,
29 | cb: () => printTicks()
30 | })
31 | const select = inputs.select({
32 | label: 'max-ticks',
33 | value: '6',
34 | width: 50,
35 | options: ['4', '5', '6', '7', '8', '9', '10', '11'],
36 | cb: () => printTicks()
37 | })
38 | const end = inputs.duration({
39 | label: '',
40 | value: {
41 | month: 3
42 | },
43 | max: {
44 | year: 4
45 | },
46 | min: {
47 | hour: 3
48 | },
49 | cb: () => printTicks()
50 | })
51 | document.querySelector('#origin').innerHTML = start.build()
52 | document.querySelector('#ticks-two').innerHTML = select.build()
53 | document.querySelector('#duration').innerHTML = end.build()
54 |
55 | printTicks()
56 |
--------------------------------------------------------------------------------
/plugins/ticks/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | •.
8 |
9 |
10 |
12 |
13 |
14 |
15 |
16 |
19 |
20 |
25 |
26 |
27 |
28 |
29 |
30 |
custom times:
31 |
36 |
37 |
38 |
39 |
40 |
43 |
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/plugins/ticks/index.js:
--------------------------------------------------------------------------------
1 | import lib from './src/index.js'
2 | export default lib
--------------------------------------------------------------------------------
/plugins/ticks/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "spacetime-ticks",
3 | "version": "0.4.0",
4 | "description": "calculate the best breakpoints between two dates",
5 | "main": "src/index.js",
6 | "unpkg": "builds/spacetime-ticks.min.js",
7 | "module": "builds/spacetime-ticks.mjs",
8 | "type": "module",
9 | "sideEffects": false,
10 | "exports": {
11 | ".": {
12 | "require": "./builds/spacetime-ticks.cjs",
13 | "import": "./builds/spacetime-ticks.mjs",
14 | "default": "./builds/spacetime-ticks.mjs"
15 | }
16 | },
17 | "author": "Spencer Kelly (spencermountain)",
18 | "scripts": {
19 | "test": "echo \"Error: no test specified\" && exit 1",
20 | "watch": "node --watch ./scratch.js",
21 | "start": "budo index.js:assets/bundle.js --live",
22 | "build:demo": "browserify index.js -t [ babelify --presets [ @babel/preset-env ] ] | derequire > ./assets/bundle.js",
23 | "version": "node ./scripts/version.js",
24 | "filesize": "node ./scripts/filesize.js",
25 | "build": "npm run version && rollup -c && npm run filesize"
26 | },
27 | "repository": {
28 | "type": "git",
29 | "url": "git+https://github.com/spencermountain/spacetime.git"
30 | },
31 | "bugs": {
32 | "url": "https://github.com/spencermountain/spacetime/issues"
33 | },
34 | "homepage": "https://github.com/spencermountain/spacetime/tree/master/plugins/spacetime-ticks",
35 | "peerDependencies": {
36 | "spacetime": ">=6.1.0"
37 | },
38 | "devDependencies": {
39 | "spencer-color": "0.1.0",
40 | "spencer-css": "1.1.3"
41 | },
42 | "license": "MIT"
43 | }
--------------------------------------------------------------------------------
/plugins/ticks/rollup.config.js:
--------------------------------------------------------------------------------
1 | import commonjs from 'rollup-plugin-commonjs'
2 | import json from 'rollup-plugin-json'
3 | import { terser } from 'rollup-plugin-terser'
4 | import resolve from 'rollup-plugin-node-resolve'
5 |
6 | export default [
7 | {
8 | input: 'src/index.js',
9 | output: [{ file: 'builds/spacetime-ticks.mjs', format: 'esm' }],
10 | plugins: [resolve(), json(), commonjs()]
11 | },
12 | {
13 | input: 'src/index.js',
14 | output: [{ file: 'builds/spacetime-ticks.cjs', format: 'umd', name: 'spacetime-ticks' }],
15 | plugins: [resolve(), json(), commonjs()]
16 | },
17 | {
18 | input: 'src/index.js',
19 | output: [{ file: 'builds/spacetime-ticks.min.js', format: 'umd', name: 'spacetime-ticks' }],
20 | plugins: [resolve(), json(), commonjs(), terser()]
21 | }
22 | ]
23 |
--------------------------------------------------------------------------------
/plugins/ticks/scratch.js:
--------------------------------------------------------------------------------
1 | import spacetimeTicks from './src/index.js'
2 |
3 | console.time('time')
4 | const ticks = spacetimeTicks('jan 1 2019', 'jan 1 2020', 12)
5 | console.log(ticks)
6 | console.timeEnd('time')
7 |
--------------------------------------------------------------------------------
/plugins/ticks/scripts/filesize.js:
--------------------------------------------------------------------------------
1 | import { statSync } from 'fs'
2 | //log the size of our builds
3 | const stats = statSync('./builds/spacetime-ticks.min.js')
4 | const fileSize = (stats['size'] / 1000.0).toFixed(2)
5 | console.log('\n\n min: ' + fileSize + 'kb')
6 |
--------------------------------------------------------------------------------
/plugins/ticks/scripts/version.js:
--------------------------------------------------------------------------------
1 | import fs from 'fs'
2 | // avoid requiring our whole package.json file
3 | // make a small file for our version number
4 | const pkg = JSON.parse(fs.readFileSync('./package.json').toString())
5 |
6 | fs.writeFileSync('./_version.js', `export default '${pkg.version}'`)
7 |
--------------------------------------------------------------------------------
/plugins/ticks/src/_reduce.js:
--------------------------------------------------------------------------------
1 | const reduceTo = function (arr, n) {
2 | if (arr.length <= n || arr.length <= 5) {
3 | return arr
4 | }
5 | while (arr.length > n) {
6 | //remove every other one
7 | arr = arr.filter((o, i) => {
8 | return i % 2 === 0
9 | })
10 | if (arr.length <= n || arr.length <= 5) {
11 | return arr
12 | }
13 | }
14 | return arr
15 | }
16 | export default reduceTo
17 |
--------------------------------------------------------------------------------
/plugins/ticks/src/index.js:
--------------------------------------------------------------------------------
1 | import spacetime from 'spacetime'
2 | import methods from './methods.js'
3 | import version from '../_version.js'
4 |
5 | const chooseMethod = function (start, end, n = 6) {
6 | const diff = start.diff(end)
7 | if (diff.years > 300) {
8 | return methods.centuries(start, end, n)
9 | }
10 | if (diff.years > 30) {
11 | return methods.decades(start, end, n)
12 | }
13 | if (diff.years > 3) {
14 | return methods.years(start, end, n)
15 | }
16 | if (diff.months > 3) {
17 | return methods.months(start, end, n)
18 | }
19 | if (diff.days > 3) {
20 | return methods.days(start, end, n)
21 | }
22 | if (diff.hours > 3) {
23 | return methods.hours(start, end, n)
24 | }
25 | if (diff.minutes > 3) {
26 | return methods.minutes(start, end, n)
27 | }
28 | return methods.months(start, end, n)
29 | }
30 |
31 | //flip it around backwards
32 | const reverseTicks = function (ticks) {
33 | ticks = ticks.map(o => {
34 | o.value = 1 - o.value
35 | return o
36 | })
37 | return ticks.reverse()
38 | }
39 |
40 | const spacetimeTicks = function (start, end, n = 6) {
41 | let reverse = false
42 | start = spacetime(start)
43 | end = spacetime(end)
44 | //reverse them, if necessary
45 | if (start.epoch > end.epoch) {
46 | reverse = true
47 | const tmp = start.epoch
48 | start.epoch = end.epoch
49 | end.epoch = tmp
50 | }
51 | // nudge first one back 1 minute
52 | if (start.time() === '12:00am') {
53 | start = start.minus(1, 'minute')
54 | }
55 | let ticks = chooseMethod(start, end, n)
56 | //support backwards ticks
57 | if (reverse === true) {
58 | ticks = reverseTicks(ticks)
59 | }
60 | return ticks
61 | }
62 | spacetimeTicks.version = version
63 |
64 | export default spacetimeTicks
65 |
--------------------------------------------------------------------------------
/plugins/tz/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
a CLI app to reckon a timezone
4 |
5 |
6 |
14 |
15 | npm i -g stz
16 |
17 |
18 | by
19 | Spencer Kelly
20 |
21 |
22 |
23 | an easy way to check what time it is, somewhere else, from the command-line:
24 | ```bash
25 | $ npx stz milwaukee
26 | # 5:20pm
27 | $ npx stz pacific time
28 | # 4:20pm
29 | ```
30 | or you can install it locally with `npm i -g stz`
31 |
32 | this library uses [spacetime]() and [timezone-soft](https://github.com/spencermountain/timezone-soft) to loosely match a given timezone.
33 |
34 | MIT
--------------------------------------------------------------------------------
/plugins/tz/cli.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | import spacetime from 'spacetime'
3 | import soft from 'timezone-soft'
4 | const help = function () {
5 | console.log(`\n\n stz - calculate current time in a given location`)
6 | console.log(`\n Usage: \`npx stz boston\``)
7 | console.log(`\n Usage: \`npx stz ACST\``)
8 | console.log('\n\n')
9 | }
10 |
11 | const str = process.argv.slice(2).join(' ').trim()
12 | if (!str) {
13 | help()
14 | process.exit()
15 | }
16 |
17 | const res = soft(str)
18 | if (res.length === 0) {
19 | console.log(`\n\nCould not find timezone for \'${str}\'`)
20 | help()
21 | process.exit()
22 | }
23 | const tz = res[0]
24 | // are we in standard time, or daylight time?
25 | const s = spacetime.now(tz.iana)
26 | let out = `${s.time()}`
27 |
28 | if (tz.daylight && s.isDST()) {
29 | out += ' ' + tz.daylight.abbr
30 | } else {
31 | out += ' ' + tz.standard.abbr
32 | }
33 |
34 | const here = spacetime.now()
35 | if (!s.isSame('day', here)) {
36 | out += ' ' + s.format('nice')
37 | } else {
38 | out += ' (today)'
39 | }
40 |
41 | console.log('\n' + out + '\n')
42 |
--------------------------------------------------------------------------------
/plugins/tz/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "stz",
3 | "version": "0.2.0",
4 | "lockfileVersion": 2,
5 | "requires": true,
6 | "packages": {
7 | "": {
8 | "name": "stz",
9 | "version": "0.2.0",
10 | "license": "MIT",
11 | "dependencies": {
12 | "minimist": "^1.2.5",
13 | "spacetime": ">=6.16.3",
14 | "timezone-soft": "1.5.2"
15 | },
16 | "bin": {
17 | "stz": "cli.js"
18 | },
19 | "engines": {
20 | "node": ">=8"
21 | }
22 | },
23 | "node_modules/minimist": {
24 | "version": "1.2.5",
25 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
26 | "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
27 | },
28 | "node_modules/spacetime": {
29 | "version": "6.16.3",
30 | "resolved": "https://registry.npmjs.org/spacetime/-/spacetime-6.16.3.tgz",
31 | "integrity": "sha512-JQEfj3VHT1gU1IMV5NvhgAP8P+2mDFd84ZCiHN//dp6hRKmuW0IizHissy62lO0nilfFjVhnoSaMC7te+Y5f4A=="
32 | },
33 | "node_modules/timezone-soft": {
34 | "version": "1.5.2",
35 | "resolved": "https://registry.npmjs.org/timezone-soft/-/timezone-soft-1.5.2.tgz",
36 | "integrity": "sha512-BUr+CfBfeWXJwFAuEzPO9uF+v6sy3pL5SKLkDg4vdEhsyXgbBnpFoBCW8oEKSNTqNq9YHbVOjNb31xE7WyGmrA==",
37 | "license": "MIT"
38 | }
39 | },
40 | "dependencies": {
41 | "minimist": {
42 | "version": "1.2.5",
43 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
44 | "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
45 | },
46 | "spacetime": {
47 | "version": "6.16.3",
48 | "resolved": "https://registry.npmjs.org/spacetime/-/spacetime-6.16.3.tgz",
49 | "integrity": "sha512-JQEfj3VHT1gU1IMV5NvhgAP8P+2mDFd84ZCiHN//dp6hRKmuW0IizHissy62lO0nilfFjVhnoSaMC7te+Y5f4A=="
50 | },
51 | "timezone-soft": {
52 | "version": "1.5.2",
53 | "resolved": "https://registry.npmjs.org/timezone-soft/-/timezone-soft-1.5.2.tgz",
54 | "integrity": "sha512-BUr+CfBfeWXJwFAuEzPO9uF+v6sy3pL5SKLkDg4vdEhsyXgbBnpFoBCW8oEKSNTqNq9YHbVOjNb31xE7WyGmrA=="
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/plugins/tz/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "stz",
3 | "version": "0.2.0",
4 | "description": "a CLI timezone calculator",
5 | "main": "cli.js",
6 | "type": "module",
7 | "sideEffects": false,
8 | "exports": {
9 | ".": {
10 | "import": "./cli.js",
11 | "default": "./cli.js"
12 | }
13 | },
14 | "bin": {
15 | "stz": "cli.js"
16 | },
17 | "homepage": "https://github.com/spencermountain/spacetime/tree/master/plugins/tz",
18 | "repository": {
19 | "type": "git",
20 | "url": "git+https://github.com/spencermountain/spacetime.git"
21 | },
22 | "engines": {
23 | "node": ">=8"
24 | },
25 | "scripts": {
26 | "test": ""
27 | },
28 | "prettier": {
29 | "trailingComma": "none",
30 | "tabWidth": 2,
31 | "semi": false,
32 | "singleQuote": true,
33 | "printWidth": 100
34 | },
35 | "dependencies": {
36 | "minimist": "^1.2.5",
37 | "spacetime": ">=6.16.3",
38 | "timezone-soft": "1.5.2"
39 | },
40 | "license": "MIT"
41 | }
42 |
--------------------------------------------------------------------------------
/plugins/week-of-month/builds/spacetime-week-of-month.cjs:
--------------------------------------------------------------------------------
1 | /* spencermountain/spacetime-week-of-month 0.1.0 Apache 2.0 */
2 | (function (global, factory) {
3 | typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
4 | typeof define === 'function' && define.amd ? define(factory) :
5 | (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.weekOfMonth = factory());
6 | })(this, (function () { 'use strict';
7 |
8 | // the first week of a month includes a thursday, in that month
9 | // (leap days do not effect week-ordering!)
10 | const getFirstWeek = function (s) {
11 | let month = s.month();
12 | let start = s.date(1);
13 | start = start.startOf('week');
14 | let thu = start.add(3, 'days');
15 | if (thu.month() !== month) {
16 | start = start.add(1, 'week');
17 | }
18 | return start
19 | };
20 |
21 | var index = {
22 | weekOfMonth: function (n) {
23 | let start = getFirstWeek(this.clone());
24 | // week-setter
25 | if (n !== undefined) {
26 | return start.add(n, 'weeks')
27 | }
28 | // week-getter
29 | let num = 0;
30 | let end = start.endOf('week');
31 | for (let i = 0; i < 5; i += 1) {
32 | if (end.isAfter(this)) {
33 | return num + 1
34 | }
35 | end = end.add(1, 'week');
36 | num += 1;
37 | }
38 | return num + 1
39 | },
40 | whichWeek: function () {
41 | let s = this.startOf('week');
42 | // it's always in the same month that it's thursday is...
43 | let thurs = s.add(3, 'days');
44 | let month = thurs.monthName();
45 | let num = thurs.weekOfMonth();
46 |
47 | return { num, month }
48 | },
49 | firstWeek: function () {
50 | return getFirstWeek(this.clone())
51 | },
52 | lastSunday: function () {
53 | let s = this.endOf('month'); //last day
54 | // if it's after thursday
55 | if (s.day() > 4) {
56 | return s.endOf('week')
57 | }
58 | // else, the previous sunday
59 | s = s.minus(1, 'week');
60 | return s.endOf('week')
61 | }
62 | };
63 |
64 | return index;
65 |
66 | }));
67 |
--------------------------------------------------------------------------------
/plugins/week-of-month/builds/spacetime-week-of-month.min.js:
--------------------------------------------------------------------------------
1 | !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).weekOfMonth=t()}(this,(function(){"use strict";const e=function(e){let t=e.month(),n=e.date(1);return n=n.startOf("week"),n.add(3,"days").month()!==t&&(n=n.add(1,"week")),n};return{weekOfMonth:function(t){let n=e(this.clone());if(void 0!==t)return n.add(t,"weeks");let o=0,f=n.endOf("week");for(let e=0;e<5;e+=1){if(f.isAfter(this))return o+1;f=f.add(1,"week"),o+=1}return o+1},whichWeek:function(){let e=this.startOf("week").add(3,"days"),t=e.monthName();return{num:e.weekOfMonth(),month:t}},firstWeek:function(){return e(this.clone())},lastSunday:function(){let e=this.endOf("month");return e.day()>4||(e=e.minus(1,"week")),e.endOf("week")}}}));
2 |
--------------------------------------------------------------------------------
/plugins/week-of-month/builds/spacetime-week-of-month.mjs:
--------------------------------------------------------------------------------
1 | /* spencermountain/spacetime-week-of-month 0.1.0 Apache 2.0 */
2 | // the first week of a month includes a thursday, in that month
3 | // (leap days do not effect week-ordering!)
4 | const getFirstWeek = function (s) {
5 | let month = s.month();
6 | let start = s.date(1);
7 | start = start.startOf('week');
8 | let thu = start.add(3, 'days');
9 | if (thu.month() !== month) {
10 | start = start.add(1, 'week');
11 | }
12 | return start
13 | };
14 |
15 | var index = {
16 | weekOfMonth: function (n) {
17 | let start = getFirstWeek(this.clone());
18 | // week-setter
19 | if (n !== undefined) {
20 | return start.add(n, 'weeks')
21 | }
22 | // week-getter
23 | let num = 0;
24 | let end = start.endOf('week');
25 | for (let i = 0; i < 5; i += 1) {
26 | if (end.isAfter(this)) {
27 | return num + 1
28 | }
29 | end = end.add(1, 'week');
30 | num += 1;
31 | }
32 | return num + 1
33 | },
34 | whichWeek: function () {
35 | let s = this.startOf('week');
36 | // it's always in the same month that it's thursday is...
37 | let thurs = s.add(3, 'days');
38 | let month = thurs.monthName();
39 | let num = thurs.weekOfMonth();
40 |
41 | return { num, month }
42 | },
43 | firstWeek: function () {
44 | return getFirstWeek(this.clone())
45 | },
46 | lastSunday: function () {
47 | let s = this.endOf('month'); //last day
48 | // if it's after thursday
49 | if (s.day() > 4) {
50 | return s.endOf('week')
51 | }
52 | // else, the previous sunday
53 | s = s.minus(1, 'week');
54 | return s.endOf('week')
55 | }
56 | };
57 |
58 | export { index as default };
59 |
--------------------------------------------------------------------------------
/plugins/week-of-month/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "spacetime-week-of-month",
3 | "version": "0.2.0",
4 | "description": "calculate nth week of the month",
5 | "main": "src/index.js",
6 | "unpkg": "builds/spacetime-week-of-month.min.js",
7 | "module": "builds/spacetime-week-of-month.mjs",
8 | "type": "module",
9 | "sideEffects": false,
10 | "exports": {
11 | ".": {
12 | "require": "./builds/spacetime-week-of-month.cjs",
13 | "import": "./builds/spacetime-week-of-month.mjs",
14 | "default": "./builds/spacetime-week-of-month.mjs"
15 | }
16 | },
17 | "homepage": "https://github.com/spencermountain/spacetime/tree/master/plugins/spacetime-week-of-month",
18 | "scripts": {
19 | "test": "TESTENV=dev tape ./week.test.js | tap-dancer --color always",
20 | "build": "rollup -c --silent"
21 | },
22 | "repository": {
23 | "type": "git",
24 | "url": "https://github.com/spencermountain/spacetime.git"
25 | },
26 | "prettier": {
27 | "trailingComma": "none",
28 | "tabWidth": 2,
29 | "semi": false,
30 | "singleQuote": true,
31 | "printWidth": 100
32 | },
33 | "devDependencies": {
34 | "tap-dancer": "0.3.4",
35 | "tape": "5.5.3"
36 | },
37 | "license": "MIT"
38 | }
--------------------------------------------------------------------------------
/plugins/week-of-month/rollup.config.js:
--------------------------------------------------------------------------------
1 | import commonjs from 'rollup-plugin-commonjs'
2 | import json from 'rollup-plugin-json'
3 | import { terser } from 'rollup-plugin-terser'
4 | import resolve from 'rollup-plugin-node-resolve'
5 | import sizeCheck from 'rollup-plugin-filesize-check'
6 | import { version } from './package.json'
7 |
8 | console.log('\n 📦 - running rollup..\n')
9 |
10 | const name = 'spacetime-week-of-month'
11 | const banner = `/* spencermountain/${name} ` + version + ' Apache 2.0 */'
12 |
13 | export default [
14 | {
15 | input: 'src/index.js',
16 | output: [{ banner: banner, file: `builds/${name}.mjs`, format: 'esm' }],
17 | plugins: [resolve(), json(), commonjs(), sizeCheck({ expect: 1, warn: 10 })]
18 | },
19 | {
20 | input: 'src/index.js',
21 | output: [{ banner: banner, file: `builds/${name}.cjs`, format: 'umd', sourcemap: false, name: 'weekOfMonth' }],
22 | plugins: [resolve(), json(), commonjs(), sizeCheck({ expect: 1, warn: 10 })]
23 | },
24 | {
25 | input: 'src/index.js',
26 | output: [{ banner: banner, file: `builds/${name}.min.js`, format: 'umd', name: 'weekOfMonth' }],
27 | plugins: [resolve(), json(), commonjs(), terser(), sizeCheck({ expect: 1, warn: 10 })]
28 | }
29 | ]
30 |
--------------------------------------------------------------------------------
/plugins/week-of-month/src/index.js:
--------------------------------------------------------------------------------
1 | // the first week of a month includes a thursday, in that month
2 | // (leap days do not effect week-ordering!)
3 | const getFirstWeek = function (s) {
4 | const month = s.month()
5 | let start = s.date(1)
6 | start = start.startOf('week')
7 | const thu = start.add(3, 'days')
8 | if (thu.month() !== month) {
9 | start = start.add(1, 'week')
10 | }
11 | return start
12 | }
13 |
14 | export default {
15 | weekOfMonth: function (n) {
16 | const start = getFirstWeek(this.clone())
17 | // week-setter
18 | if (n !== undefined) {
19 | return start.add(n, 'weeks')
20 | }
21 | // week-getter
22 | let num = 0
23 | let end = start.endOf('week')
24 | for (let i = 0; i < 5; i += 1) {
25 | if (end.isAfter(this)) {
26 | return num + 1
27 | }
28 | end = end.add(1, 'week')
29 | num += 1
30 | }
31 | return num + 1
32 | },
33 | whichWeek: function () {
34 | const s = this.startOf('week')
35 | // it's always in the same month that it's thursday is...
36 | const thurs = s.add(3, 'days')
37 | const month = thurs.monthName()
38 | const num = thurs.weekOfMonth()
39 |
40 | return { num, month }
41 | },
42 | firstWeek: function () {
43 | return getFirstWeek(this.clone())
44 | },
45 | lastSunday: function () {
46 | let s = this.endOf('month') //last day
47 | // if it's after thursday
48 | if (s.day() > 4) {
49 | return s.endOf('week')
50 | }
51 | // else, the previous sunday
52 | s = s.minus(1, 'week')
53 | return s.endOf('week')
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/plugins/week-of-month/week.test.js:
--------------------------------------------------------------------------------
1 | import test from 'tape'
2 | import spacetime from '../../src/index.js'
3 | import plugin from './src/index.js'
4 | spacetime.plugin(plugin)
5 |
6 | test('weekOfMonth-getter', (t) => {
7 | // october 1st starts on a thursday
8 | const arr = [
9 | // ['sep 28 2020', 1], //mon
10 | // ['sep 29 2020', 1], //tues
11 | // ['sep 30 2020', 1], //wed
12 | ['oct 1 2020', 1], //thurs
13 | ['oct 2 2020', 1], //fri
14 | ['oct 3 2020', 1], //sat
15 | ['oct 4 2020', 1], //sun
16 | ['oct 5 2020', 2], //mon
17 | ['oct 6 2020', 2], //tues
18 | ['oct 7 2020', 2], //wed
19 | ['oct 8 2020', 2] //thurs
20 | ]
21 | arr.forEach((a) => {
22 | const s = spacetime(a[0])
23 | t.equal(s.weekOfMonth(), a[1], a[0] + ' ' + a[1])
24 | })
25 | t.end()
26 | })
27 |
28 | test('weekOfMonth-setter', (t) => {
29 | let s = spacetime('oct 8 2020')
30 | s = s.weekOfMonth(0)
31 | t.equal(s.format('iso-short'), '2020-09-28', '0')
32 |
33 | s = spacetime('oct 8 2020')
34 | s = s.weekOfMonth(1)
35 | t.equal(s.format('iso-short'), '2020-10-05', '1')
36 |
37 | s = spacetime('oct 8 2020')
38 | s = s.weekOfMonth(2)
39 | t.equal(s.format('iso-short'), '2020-10-12', '2')
40 | s = spacetime('oct 8 2020')
41 |
42 | s = spacetime('oct 8 2020')
43 | s = s.weekOfMonth(3)
44 | t.equal(s.format('iso-short'), '2020-10-19', '3')
45 |
46 | s = spacetime('oct 8 2020')
47 | s = s.weekOfMonth(4)
48 | t.equal(s.format('iso-short'), '2020-10-26', '4')
49 |
50 | s = spacetime('oct 8 2020')
51 | s = s.weekOfMonth(5)
52 | t.equal(s.format('iso-short'), '2020-11-02', '5')
53 | t.end()
54 | })
55 |
--------------------------------------------------------------------------------
/plugins/week-start/_version.js:
--------------------------------------------------------------------------------
1 | export default '0.0.1'
--------------------------------------------------------------------------------
/plugins/week-start/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "spacetime-week-start",
3 | "description": "calculate start-of-week",
4 | "version": "0.3.0",
5 | "main": "src/index.js",
6 | "unpkg": "builds/spacetime-week-start.min.js",
7 | "type": "module",
8 | "sideEffects": false,
9 | "exports": {
10 | ".": {
11 | "require": "./builds/spacetime-week-start.cjs",
12 | "import": "./builds/spacetime-week-start.mjs",
13 | "default": "./builds/spacetime-week-start.mjs"
14 | }
15 | },
16 | "author": "Martin Spodniak & Spencer Kelly",
17 | "homepage": "https://github.com/spencermountain/spacetime/tree/master/plugins/week-start",
18 | "repository": {
19 | "type": "git",
20 | "url": "git+https://github.com/spencermountain/spacetime.git"
21 | },
22 | "scripts": {
23 | "watch": "node --watch ./scratch.js",
24 | "test": "TESTENV=dev tape ./test/**/*.test.js | tap-dancer",
25 | "build": "rollup -c --silent"
26 | },
27 | "prettier": {
28 | "trailingComma": "none",
29 | "tabWidth": 2,
30 | "semi": false,
31 | "singleQuote": true,
32 | "printWidth": 100
33 | },
34 | "devDependencies": {
35 | "shelljs": "0.8.5",
36 | "tap-dancer": "0.3.4",
37 | "tape": "5.5.3",
38 | "terser": "5.39.0"
39 | },
40 | "peerDependencies": {
41 | "spacetime": ">=5.8.2"
42 | },
43 | "licence": "MIT"
44 | }
--------------------------------------------------------------------------------
/plugins/week-start/rollup.config.js:
--------------------------------------------------------------------------------
1 | import fs from 'fs'
2 | import commonjs from 'rollup-plugin-commonjs'
3 | import json from 'rollup-plugin-json'
4 | import { terser } from 'rollup-plugin-terser'
5 | import resolve from 'rollup-plugin-node-resolve'
6 | import sizeCheck from 'rollup-plugin-filesize-check'
7 | const pkg = JSON.parse(fs.readFileSync('./package.json').toString())
8 | const version = pkg.version
9 |
10 | console.log('\n 📦 - running rollup..\n')
11 |
12 | const name = 'spacetime-week-start'
13 | const banner = `/* spencermountain/${name} ` + version + ' Apache 2.0 */'
14 |
15 | export default [
16 | {
17 | input: 'src/index.js',
18 | output: [{ banner: banner, file: `builds/${name}.mjs`, format: 'esm' }],
19 | plugins: [resolve(), json(), commonjs(), sizeCheck({ expect: 147, warn: 10 })]
20 | },
21 | {
22 | input: 'src/index.js',
23 | output: [{ banner: banner, file: `builds/${name}.cjs`, format: 'umd', sourcemap: false, name: 'weekStart' }],
24 | plugins: [resolve(), json(), commonjs(), sizeCheck({ expect: 159, warn: 10 })]
25 | },
26 | {
27 | input: 'src/index.js',
28 | output: [{ banner: banner, file: `builds/${name}.min.js`, format: 'umd', name: 'weekStart' }],
29 | plugins: [resolve(), json(), commonjs(), terser(), sizeCheck({ expect: 79, warn: 10 })]
30 | }
31 | ]
32 |
--------------------------------------------------------------------------------
/plugins/week-start/scratch.js:
--------------------------------------------------------------------------------
1 | import spacetime from 'spacetime'
2 | import spacetimeWeek from './src/index.js'
3 |
4 | spacetime.extend(spacetimeWeek)
5 | const d = spacetime.now('Europe/Berlin')
6 | console.log(d.weekStart('iran'))
7 |
--------------------------------------------------------------------------------
/plugins/week-start/src/index.js:
--------------------------------------------------------------------------------
1 | import { getWeekStart } from './input/weekStart.js'
2 |
3 | export default {
4 | weekStart: function (input) {
5 | input = input || this.timezone().name
6 | return getWeekStart(input)
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/plugins/week-start/test/basic.test.js:
--------------------------------------------------------------------------------
1 | import test from 'tape'
2 | import { getWeekStart as weekStart } from '../src/input/weekStart.js'
3 |
4 | test('week by country returns values, when no or falsy argument is supplied', t => {
5 | t.notEqual(weekStart(), null)
6 | t.notEqual(weekStart(), undefined)
7 | t.notEqual(weekStart(12), null)
8 | t.notEqual(weekStart(12), undefined)
9 |
10 | t.notEqual(weekStart(null), null)
11 | t.notEqual(weekStart(null), undefined)
12 | t.notEqual(weekStart(''), null)
13 | t.notEqual(weekStart(''), undefined)
14 |
15 | t.notEqual(weekStart(undefined), null)
16 | t.notEqual(weekStart(undefined), undefined)
17 | t.notEqual(weekStart('abc'), null)
18 | t.notEqual(weekStart('abc'), undefined)
19 | t.end()
20 | })
21 |
22 | test('JSON values are string and not empty', t => {
23 | t.equal(typeof weekStart().day, 'string')
24 | t.notEqual(weekStart().day, '')
25 | t.equal(typeof weekStart().country, 'string')
26 | t.notEqual(weekStart().country, '')
27 | t.end()
28 | })
29 |
30 | test('when supplied full coutry name returns day and country', t => {
31 | t.equal(weekStart('canada').day, 'sunday')
32 | t.equal(weekStart('canada').country, 'canada')
33 | t.end()
34 | })
35 |
36 | test('when supplied partial coutry name returns day and country', t => {
37 | t.equal(weekStart('united arab emirates').day, 'sunday')
38 | t.equal(weekStart('emirates').country, 'united arab emirates')
39 | t.end()
40 | })
41 |
42 | test('country name can be in lower case, upper case or camel case', t => {
43 | t.equal(weekStart('cana').day, 'sunday')
44 | t.equal(weekStart('nada').country, 'grenada')
45 | t.notEqual(weekStart('nada').country, 'canada')
46 | // finds first occourance of string 'nada' in JSON
47 | t.equal(weekStart('CANADA').day, 'sunday')
48 | t.equal(weekStart('Canada').country, 'canada')
49 | // it's located in array under key "monday" for
50 | // grenada and after that appears located in
51 | // canada under key "sunday"
52 | t.equal(weekStart('CaNa').day, 'sunday')
53 | t.equal(weekStart('nAdA').country, 'grenada')
54 | t.notEqual(weekStart('nAdA').country, 'canada')
55 | t.end()
56 | })
57 |
--------------------------------------------------------------------------------
/plugins/week-start/weekStart-demo.js:
--------------------------------------------------------------------------------
1 | import weekStart from './src/input/weekStart.js';
2 |
3 | console.log('#1: ', weekStart());
4 | console.log('#2: ', weekStart(12));
5 | console.log('#3: ', weekStart(null));
6 | console.log('#4: ', weekStart(''));
7 | console.log('#5: ', weekStart(undefined));
8 | console.log('#6: ', weekStart('abc'));
9 | // all returns results for current tz, f.e.
10 | // { day: 'sunday', country: 'canada' }
11 |
12 | console.log('#7: ', weekStart('slovakia'));
13 | // tz: europe/bratislava
14 | // { day: 'monday', country: 'slovakia' }
15 |
16 | console.log('#8: ', weekStart('iran'));
17 | //tz: asia/tehran
18 | // { day: 'saturday', country: 'iran' }
19 |
20 | console.log('#9: ', weekStart('canAda'));
21 | //tz: f.e. america/montreal
22 | // { day: 'sunday', country: 'canada' }
23 |
24 | console.log('#10: ', weekStart('lize'));
25 | // tz: america/belize
26 | // { day: 'monday', country: 'belize' }
27 |
28 | console.log('#11: ', weekStart('el salvador'));
29 | // tz: america/el_salvador
30 | // { day: 'monday', country: 'el salvador' }
31 |
32 | console.log('#12: ', weekStart('zulu'));
33 | // tz: etc/zulu
34 | // { day: 'monday', location: 'zulu' }
35 |
36 | console.log('#13: ', weekStart('gmt'));
37 | // tz: f.e. etc/gmt
38 | // { day: 'monday', location: 'gmt' }
39 |
40 | console.log('#14: ', weekStart('antarctica'));
41 | // tz: f.e. antarctica/south_pole
42 | // { day: 'monday', location: 'antarctica' }
43 |
44 | console.log('#15: ', weekStart('arctic'));
45 | // tz: f.e. arctic/longyearbyen
46 | // { day: 'monday', location: 'arctic' }
47 |
48 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import commonjs from 'rollup-plugin-commonjs'
2 | import json from 'rollup-plugin-json'
3 | import { terser } from 'rollup-plugin-terser'
4 | import resolve from 'rollup-plugin-node-resolve'
5 | import sizeCheck from 'rollup-plugin-filesize-check'
6 | import fs from 'fs'
7 |
8 | const pkg = JSON.parse(fs.readFileSync('./package.json').toString())
9 | const version = pkg.version
10 | console.log('\n 📦 - running rollup..\n')
11 |
12 | const banner = '/* spencermountain/spacetime ' + version + ' Apache 2.0 */'
13 |
14 | export default [
15 | {
16 | input: 'src/index.js',
17 | output: [{ banner: banner, file: 'builds/spacetime.mjs', format: 'esm' }],
18 | plugins: [resolve(), json(), terser(), sizeCheck({ expect: 48, warn: 10 })]
19 | },
20 | {
21 | input: 'src/index.js',
22 | output: [
23 | {
24 | banner: banner,
25 | file: 'builds/spacetime.cjs',
26 | format: 'umd',
27 | sourcemap: false,
28 | name: 'spacetime'
29 | }
30 | ],
31 | plugins: [
32 | resolve(),
33 | json(),
34 | commonjs(),
35 | sizeCheck({ expect: 110, warn: 10 })
36 | ]
37 | },
38 | {
39 | input: 'src/index.js',
40 | output: [{ banner: banner, file: 'builds/spacetime.min.js', format: 'umd', name: 'spacetime' }],
41 | plugins: [
42 | resolve(),
43 | json(),
44 | commonjs(),
45 | terser(),
46 | sizeCheck({ expect: 48, warn: 10 })
47 | ]
48 | }
49 | ]
50 |
--------------------------------------------------------------------------------
/scratch.js:
--------------------------------------------------------------------------------
1 | import spacetime from './src/index.js'
2 |
3 | // console.log(spacetime('Feb 29 2001').iso())
4 |
5 | //
6 | // let s = spacetime('1995-12-07T03:24:30', 'Africa/Cairo')
7 | // s = s.timezone('America/Toronto')
8 | // console.log(s.iso())
9 |
10 |
11 |
12 | let mils = 1744200453183
13 | let secs = 1744200453
14 |
15 | let s = spacetime('jan 5 2028 4:30pm')
16 | console.log(s.epochSeconds());
17 | console.log(s.iso());
18 |
19 |
20 |
21 | // let s = spacetime("foobar", 'UTC')
22 | // console.log(s.time())
23 | // console.log(s.epoch)
24 | // console.log(s.year())
25 | const arr = [
26 | 'millisecond',
27 | 'second',
28 | 'minute',
29 | 'hour',
30 | 'hourFloat',
31 | 'hour12',
32 | 'time',
33 | 'ampm',
34 | 'dayTime',
35 | 'iso',
36 | 'epochsecs',
37 | 'date',
38 | 'day',
39 | 'dayName',
40 | 'dayOfYear',
41 | 'week',
42 | 'month',
43 | 'monthName',
44 | 'quarter',
45 | 'season',
46 | 'year',
47 | 'era',
48 | 'decade',
49 | 'century',
50 | 'millenium',
51 | ]
52 | // arr.forEach(fn => {
53 | // console.log(s[fn](), fn)
54 | // })
55 |
56 | // console.log(s.epochsecs(), 1735689600)
57 | // console.log(s.epochsecs() == 1735689600)
--------------------------------------------------------------------------------
/scripts/version.js:
--------------------------------------------------------------------------------
1 | import fs from 'fs'
2 | // avoid requiring our whole package.json file
3 | // make a small file for our version number
4 | const pkg = JSON.parse(fs.readFileSync('./package.json').toString())
5 |
6 | fs.writeFileSync('./src/_version.js', `export default '${pkg.version}'`)
7 |
--------------------------------------------------------------------------------
/src/_version.js:
--------------------------------------------------------------------------------
1 | export default '7.10.0'
--------------------------------------------------------------------------------
/src/data/ampm.js:
--------------------------------------------------------------------------------
1 | let morning = 'am'
2 | let evening = 'pm'
3 |
4 | export function am() { return morning }
5 | export function pm() { return evening }
6 | export function set(i18n) {
7 | morning = i18n.am || morning
8 | evening = i18n.pm || evening
9 | }
--------------------------------------------------------------------------------
/src/data/caseFormat.js:
--------------------------------------------------------------------------------
1 | let titleCaseEnabled = true
2 |
3 | export function useTitleCase() {
4 | return titleCaseEnabled
5 | }
6 |
7 | export function set(val) {
8 | titleCaseEnabled = val
9 | }
10 |
--------------------------------------------------------------------------------
/src/data/days.js:
--------------------------------------------------------------------------------
1 | let shortDays = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat']
2 | let longDays = ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday']
3 |
4 | export function short() { return shortDays }
5 | export function long() { return longDays }
6 | export function set(i18n) {
7 | shortDays = i18n.short || shortDays
8 | longDays = i18n.long || longDays
9 | }
10 | export const aliases = {
11 | mo: 1,
12 | tu: 2,
13 | we: 3,
14 | th: 4,
15 | fr: 5,
16 | sa: 6,
17 | su: 7,
18 | tues: 2,
19 | weds: 3,
20 | wedn: 3,
21 | thur: 4,
22 | thurs: 4
23 | }
24 |
--------------------------------------------------------------------------------
/src/data/distance.js:
--------------------------------------------------------------------------------
1 | let past = 'past'
2 | let future = 'future'
3 | let present = 'present'
4 | let now = 'now'
5 | let almost = 'almost'
6 | let over = 'over'
7 | let pastDistance = (value) => `${value} ago`
8 | let futureDistance = (value) => `in ${value}`
9 |
10 | export function pastDistanceString(value) { return pastDistance(value) }
11 | export function futureDistanceString(value) { return futureDistance(value) }
12 | export function pastString() { return past }
13 | export function futureString() { return future }
14 | export function presentString() { return present }
15 | export function nowString() { return now }
16 | export function almostString() { return almost }
17 | export function overString() { return over }
18 |
19 | export function set(i18n) {
20 | pastDistance = i18n.pastDistance || pastDistance
21 | futureDistance = i18n.futureDistance || futureDistance
22 | past = i18n.past || past
23 | future = i18n.future || future
24 | present = i18n.present || present
25 | now = i18n.now || now
26 | almost = i18n.almost || almost
27 | over = i18n.over || over
28 | }
--------------------------------------------------------------------------------
/src/data/milliseconds.js:
--------------------------------------------------------------------------------
1 | const o = {
2 | millisecond: 1
3 | }
4 | o.second = 1000
5 | o.minute = 60000
6 | o.hour = 3.6e6 // dst is supported post-hoc
7 | o.day = 8.64e7 //
8 | o.date = o.day
9 | o.month = 8.64e7 * 29.5 //(average)
10 | o.week = 6.048e8
11 | o.year = 3.154e10 // leap-years are supported post-hoc
12 | //add plurals
13 | Object.keys(o).forEach(k => {
14 | o[k + 's'] = o[k]
15 | })
16 | export default o
17 |
--------------------------------------------------------------------------------
/src/data/monthLengths.js:
--------------------------------------------------------------------------------
1 | const monthLengths = [
2 | 31, // January - 31 days
3 | 28, // February - 28 days in a common year and 29 days in leap years
4 | 31, // March - 31 days
5 | 30, // April - 30 days
6 | 31, // May - 31 days
7 | 30, // June - 30 days
8 | 31, // July - 31 days
9 | 31, // August - 31 days
10 | 30, // September - 30 days
11 | 31, // October - 31 days
12 | 30, // November - 30 days
13 | 31 // December - 31 days
14 | ]
15 | export default monthLengths
16 |
17 | // 28 - feb
18 | // 30 - april, june, sept, nov
19 | // 31 - jan, march, may, july, aug, oct, dec
20 |
--------------------------------------------------------------------------------
/src/data/months.js:
--------------------------------------------------------------------------------
1 | let shortMonths = [
2 | 'jan',
3 | 'feb',
4 | 'mar',
5 | 'apr',
6 | 'may',
7 | 'jun',
8 | 'jul',
9 | 'aug',
10 | 'sep',
11 | 'oct',
12 | 'nov',
13 | 'dec'
14 | ]
15 | let longMonths = [
16 | 'january',
17 | 'february',
18 | 'march',
19 | 'april',
20 | 'may',
21 | 'june',
22 | 'july',
23 | 'august',
24 | 'september',
25 | 'october',
26 | 'november',
27 | 'december'
28 | ]
29 |
30 | function buildMapping() {
31 | const obj = {
32 | sep: 8 //support this format
33 | }
34 | for (let i = 0; i < shortMonths.length; i++) {
35 | obj[shortMonths[i]] = i
36 | }
37 | for (let i = 0; i < longMonths.length; i++) {
38 | obj[longMonths[i]] = i
39 | }
40 | return obj
41 | }
42 |
43 | export function short() { return shortMonths }
44 | export function long() { return longMonths }
45 | export function mapping() { return buildMapping() }
46 | export function set(i18n) {
47 | shortMonths = i18n.short || shortMonths
48 | longMonths = i18n.long || longMonths
49 | }
50 |
--------------------------------------------------------------------------------
/src/data/quarters.js:
--------------------------------------------------------------------------------
1 | export default [
2 | null,
3 | [0, 1], //jan 1
4 | [3, 1], //apr 1
5 | [6, 1], //july 1
6 | [9, 1] //oct 1
7 | ]
8 |
--------------------------------------------------------------------------------
/src/data/seasons.js:
--------------------------------------------------------------------------------
1 | //https://www.timeanddate.com/calendar/aboutseasons.html
2 | // Spring - from March 1 to May 31;
3 | // Summer - from June 1 to August 31;
4 | // Fall (autumn) - from September 1 to November 30; and,
5 | // Winter - from December 1 to February 28 (February 29 in a leap year).
6 | const north = [
7 | ['spring', 2, 1],
8 | ['summer', 5, 1],
9 | ['fall', 8, 1],
10 | ['autumn', 8, 1],
11 | ['winter', 11, 1] //dec 1
12 | ];
13 | const south = [
14 | ['fall', 2, 1],
15 | ['autumn', 2, 1],
16 | ['winter', 5, 1],
17 | ['spring', 8, 1],
18 | ['summer', 11, 1] //dec 1
19 | ];
20 |
21 | export default { north, south }
--------------------------------------------------------------------------------
/src/data/units.js:
--------------------------------------------------------------------------------
1 | let units = {
2 | second: 'second',
3 | seconds: 'seconds',
4 | minute: 'minute',
5 | minutes: 'minutes',
6 | hour: 'hour',
7 | hours: 'hours',
8 | day: 'day',
9 | days: 'days',
10 | month: 'month',
11 | months: 'months',
12 | year: 'year',
13 | years: 'years',
14 | };
15 |
16 | export function unitsString(unit) {
17 | return units[unit] || '';
18 | }
19 |
20 | export function set(i18n = {}) {
21 | units = {
22 | second: i18n.second || units.second,
23 | seconds: i18n.seconds || units.seconds,
24 | minute: i18n.minute || units.minute,
25 | minutes: i18n.minutes || units.minutes,
26 | hour: i18n.hour || units.hour,
27 | hours: i18n.hours || units.hours,
28 | day: i18n.day || units.day,
29 | days: i18n.days || units.days,
30 | month: i18n.month || units.month,
31 | months: i18n.months || units.months,
32 | year: i18n.year || units.year,
33 | years: i18n.years || units.years,
34 | };
35 | }
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import Spacetime from './spacetime.js'
2 | import whereIts from './whereIts.js'
3 | import version from './_version.js'
4 |
5 | const main = (input, tz, options) => new Spacetime(input, tz, options)
6 |
7 | // set all properties of a given 'today' object
8 | const setToday = function (s) {
9 | const today = s._today || {}
10 | Object.keys(today).forEach((k) => {
11 | s = s[k](today[k])
12 | })
13 | return s
14 | }
15 |
16 | //some helper functions on the main method
17 | main.now = (tz, options) => {
18 | let s = new Spacetime(new Date().getTime(), tz, options)
19 | s = setToday(s)
20 | return s
21 | }
22 | main.today = (tz, options) => {
23 | let s = new Spacetime(new Date().getTime(), tz, options)
24 | s = setToday(s)
25 | return s.startOf('day')
26 | }
27 | main.tomorrow = (tz, options) => {
28 | let s = new Spacetime(new Date().getTime(), tz, options)
29 | s = setToday(s)
30 | return s.add(1, 'day').startOf('day')
31 | }
32 | main.yesterday = (tz, options) => {
33 | let s = new Spacetime(new Date().getTime(), tz, options)
34 | s = setToday(s)
35 | return s.subtract(1, 'day').startOf('day')
36 | }
37 | main.fromUnixSeconds = (secs, tz, options) => {
38 | return new Spacetime(secs * 1000, tz, options)
39 | }
40 |
41 | main.extend = function (obj = {}) {
42 | Object.keys(obj).forEach((k) => {
43 | Spacetime.prototype[k] = obj[k]
44 | })
45 | return this
46 | }
47 | main.timezones = function () {
48 | const s = new Spacetime()
49 | return s.timezones
50 | }
51 | main.max = function (tz, options) {
52 | const s = new Spacetime(null, tz, options)
53 | s.epoch = 8640000000000000
54 | return s
55 | }
56 | main.min = function (tz, options) {
57 | const s = new Spacetime(null, tz, options)
58 | s.epoch = -8640000000000000
59 | return s
60 | }
61 |
62 | //find tz by time
63 | main.whereIts = whereIts
64 | main.version = version
65 |
66 | //aliases:
67 | main.plugin = main.extend
68 | export default main
69 |
--------------------------------------------------------------------------------
/src/input/formats/03-dmy.js:
--------------------------------------------------------------------------------
1 | import walkTo from '../../methods/set/walk.js'
2 | import { toCardinal } from '../../fns.js'
3 | import { validate, parseTime, parseYear, parseMonth } from './_parsers.js'
4 |
5 | export default [
6 | // =====
7 | // d-m-y
8 | // =====
9 | //common british format - "25-feb-2015"
10 | {
11 | reg: /^([0-9]{1,2})[-/]([a-z]+)[\-/]?([0-9]{4})?$/i,
12 | parse: (s, m) => {
13 | const obj = {
14 | year: parseYear(m[3], s._today),
15 | month: parseMonth(m[2]),
16 | date: toCardinal(m[1] || '')
17 | }
18 | if (validate(obj) === false) {
19 | s.epoch = null
20 | return s
21 | }
22 | walkTo(s, obj)
23 | s = parseTime(s, m[4])
24 | return s
25 | }
26 | },
27 | // "25 Mar 2015"
28 | {
29 | reg: /^([0-9]{1,2})( [a-z]+)( [0-9]{4}| '[0-9]{2})? ?([0-9]{1,2}:[0-9]{2}:?[0-9]{0,2} ?(am|pm|gmt))?$/i,
30 | parse: (s, m) => {
31 | const obj = {
32 | year: parseYear(m[3], s._today),
33 | month: parseMonth(m[2]),
34 | date: toCardinal(m[1])
35 | }
36 | if (!obj.month || validate(obj) === false) {
37 | s.epoch = null
38 | return s
39 | }
40 | walkTo(s, obj)
41 | s = parseTime(s, m[4])
42 | return s
43 | }
44 | },
45 | // 01-jan-2020
46 | {
47 | reg: /^([0-9]{1,2})[. \-/]([a-z]+)[. \-/]([0-9]{4})?( [0-9]{1,2}(:[0-9]{0,2})?(:[0-9]{0,3})? ?(am|pm)?)?$/i,
48 | parse: (s, m) => {
49 | const obj = {
50 | date: Number(m[1]),
51 | month: parseMonth(m[2]),
52 | year: Number(m[3])
53 | }
54 | if (validate(obj) === false) {
55 | s.epoch = null
56 | return s
57 | }
58 | walkTo(s, obj)
59 | s = s.startOf('day')
60 | s = parseTime(s, m[4])
61 | return s
62 | }
63 | }
64 | ]
65 |
--------------------------------------------------------------------------------
/src/input/formats/_parsers.js:
--------------------------------------------------------------------------------
1 | import monthLengths from '../../data/monthLengths.js'
2 | import { isLeapYear } from '../../fns.js'
3 | import { mapping } from '../../data/months.js'
4 | const months = mapping()
5 |
6 | import parseOffset from './parseOffset.js'
7 | import parseTime from './parseTime.js'
8 |
9 | //given a month, return whether day number exists in it
10 | const validate = (obj) => {
11 | //invalid values
12 | if (monthLengths.hasOwnProperty(obj.month) !== true) {
13 | return false
14 | }
15 | //support leap-year in february
16 | if (obj.month === 1) {
17 | if (isLeapYear(obj.year) && obj.date <= 29) {
18 | return true
19 | } else {
20 | return obj.date <= 28
21 | }
22 | }
23 | //is this date too-big for this month?
24 | const max = monthLengths[obj.month] || 0
25 | if (obj.date <= max) {
26 | return true
27 | }
28 | return false
29 | }
30 |
31 | const parseYear = (str = '', today) => {
32 | str = str.trim()
33 | // parse '86 shorthand
34 | if (/^'[0-9][0-9]$/.test(str) === true) {
35 | const num = Number(str.replace(/'/, ''))
36 | if (num > 50) {
37 | return 1900 + num
38 | }
39 | return 2000 + num
40 | }
41 | let year = parseInt(str, 10)
42 | // use a given year from options.today
43 | if (!year && today) {
44 | year = today.year
45 | }
46 | // fallback to this year
47 | year = year || new Date().getFullYear()
48 | return year
49 | }
50 |
51 | const parseMonth = function (str) {
52 | str = str.toLowerCase().trim()
53 | if (str === 'sept') {
54 | return months.sep
55 | }
56 | return months[str]
57 | }
58 |
59 | const parseTz = function (str) {
60 | str = str.trim()
61 | str = str.replace(/[[\]]/g, '')
62 | return str
63 | }
64 |
65 | export {
66 | parseOffset,
67 | parseTime,
68 | parseYear,
69 | parseMonth,
70 | validate,
71 | parseTz
72 | }
--------------------------------------------------------------------------------
/src/input/formats/index.js:
--------------------------------------------------------------------------------
1 | import ymd from './01-ymd.js'
2 | import mdy from './02-mdy.js'
3 | import dmy from './03-dmy.js'
4 | import misc from './04-misc.js'
5 |
6 | export default [].concat(ymd, mdy, dmy, misc)
7 |
--------------------------------------------------------------------------------
/src/input/formats/parseOffset.js:
--------------------------------------------------------------------------------
1 | //pull-apart ISO offsets, like "+0100"
2 | const parseOffset = (s, offset) => {
3 | if (!offset) {
4 | return s
5 | }
6 | offset = offset.trim().toLowerCase()
7 | // according to ISO8601, tz could be hh:mm, hhmm or hh
8 | // so need few more steps before the calculation.
9 | let num = 0
10 |
11 | // for (+-)hh:mm
12 | if (/^[+-]?[0-9]{2}:[0-9]{2}$/.test(offset)) {
13 | //support "+01:00"
14 | if (/:00/.test(offset) === true) {
15 | offset = offset.replace(/:00/, '')
16 | }
17 | //support "+01:30"
18 | if (/:30/.test(offset) === true) {
19 | offset = offset.replace(/:30/, '.5')
20 | }
21 | }
22 |
23 | // for (+-)hhmm
24 | if (/^[+-]?[0-9]{4}$/.test(offset)) {
25 | offset = offset.replace(/30$/, '.5')
26 | }
27 | num = parseFloat(offset)
28 |
29 | //divide by 100 or 10 - , "+0100", "+01"
30 | if (Math.abs(num) > 100) {
31 | num = num / 100
32 | }
33 | //this is a fancy-move
34 | if (num === 0 || offset === 'Z' || offset === 'z') {
35 | s.tz = 'etc/gmt'
36 | return s
37 | }
38 | //okay, try to match it to a utc timezone
39 | //remember - this is opposite! a -5 offset maps to Etc/GMT+5 ¯\_(:/)_/¯
40 | //https://askubuntu.com/questions/519550/why-is-the-8-timezone-called-gmt-8-in-the-filesystem
41 | num *= -1
42 |
43 | if (num >= 0) {
44 | num = '+' + num
45 | }
46 | const tz = 'etc/gmt' + num
47 | const zones = s.timezones
48 |
49 | if (zones[tz]) {
50 | // log a warning if we're over-writing a given timezone?
51 | // console.log('changing timezone to: ' + tz)
52 | s.tz = tz
53 | }
54 | return s
55 | }
56 | export default parseOffset
57 |
--------------------------------------------------------------------------------
/src/input/formats/parseTime.js:
--------------------------------------------------------------------------------
1 | // truncate any sub-millisecond values
2 | const parseMs = function (str = '') {
3 | str = String(str)
4 | //js does not support sub-millisecond values
5 | // so truncate these - 2021-11-02T19:55:30.087772
6 | if (str.length > 3) {
7 | str = str.substring(0, 3)
8 | } else if (str.length === 1) {
9 | // assume ms are zero-padded on the left
10 | // but maybe not on the right.
11 | // turn '.10' into '.100'
12 | str = str + '00'
13 | } else if (str.length === 2) {
14 | str = str + '0'
15 | }
16 | return Number(str) || 0
17 | }
18 |
19 | const parseTime = (s, str = '') => {
20 | // remove all whitespace
21 | str = str.replace(/^\s+/, '').toLowerCase()
22 | //formal time format - 04:30.23
23 | let arr = str.match(/([0-9]{1,2}):([0-9]{1,2}):?([0-9]{1,2})?[:.]?([0-9]{1,4})?/)
24 | if (arr !== null) {
25 | // eslint-disable-next-line prefer-const
26 | let [, h, m, sec, ms] = arr
27 | //validate it a little
28 | h = Number(h)
29 | if (h < 0 || h > 24) {
30 | return s.startOf('day')
31 | }
32 | m = Number(m) //don't accept '5:3pm'
33 | if (arr[2].length < 2 || m < 0 || m > 59) {
34 | return s.startOf('day')
35 | }
36 | s = s.hour(h)
37 | s = s.minute(m)
38 | s = s.seconds(sec || 0)
39 | s = s.millisecond(parseMs(ms))
40 | //parse-out am/pm
41 | const ampm = str.match(/[0-9] ?(am|pm)\b/)
42 | if (ampm !== null && ampm[1]) {
43 | s = s.ampm(ampm[1])
44 | }
45 | return s
46 | }
47 |
48 | //try an informal form - 5pm (no minutes)
49 | arr = str.match(/([0-9]+) ?(am|pm)/)
50 | if (arr !== null && arr[1]) {
51 | const h = Number(arr[1])
52 | //validate it a little..
53 | if (h > 12 || h < 1) {
54 | return s.startOf('day')
55 | }
56 | s = s.hour(arr[1] || 0)
57 | s = s.ampm(arr[2])
58 | s = s.startOf('hour')
59 | return s
60 | }
61 |
62 | //no time info found, use start-of-day
63 | s = s.startOf('day')
64 | return s
65 | }
66 | export default parseTime
67 |
--------------------------------------------------------------------------------
/src/input/helpers.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-console */
2 | const defaults = {
3 | year: new Date().getFullYear(),
4 | month: 0,
5 | date: 1
6 | }
7 | const units = ['year', 'month', 'date', 'hour', 'minute', 'second', 'millisecond']
8 |
9 | //support [2016, 03, 01] format
10 | const parseArray = (s, arr, today) => {
11 | if (arr.length === 0) {
12 | return s
13 | }
14 | for (let i = 0; i < units.length; i++) {
15 | const num = arr[i] || today[units[i]] || defaults[units[i]] || 0
16 | s = s[units[i]](num)
17 | }
18 | return s
19 | }
20 |
21 | //support {year:2016, month:3} format
22 | const parseObject = (s, obj) => {
23 | if (Object.keys(obj).length === 0) {
24 | return s
25 | }
26 | obj = Object.assign({}, defaults, obj)
27 | if (obj.timezone) {
28 | s.tz = obj.timezone
29 | }
30 | for (let i = 0; i < units.length; i++) {
31 | const unit = units[i]
32 | if (obj[unit] !== undefined) {
33 | s = s[unit](obj[unit])
34 | }
35 | }
36 | return s
37 | }
38 |
39 | // this may seem like an arbitrary number, but it's 'within jan 1970'
40 | // this is only really ambiguous until 2054 or so
41 | const parseNumber = function (s, input) {
42 | const minimumEpoch = 2500000000
43 | // if the given epoch is really small, they've probably given seconds and not milliseconds
44 | // anything below this number is likely (but not necessarily) a mistaken input.
45 | if (input > 0 && input < minimumEpoch && s.silent === false) {
46 | console.warn(' - Warning: You are setting the date to January 1970.')
47 | console.warn(' - did input seconds instead of milliseconds?')
48 | }
49 | s.epoch = input
50 | return s
51 | }
52 |
53 | export default {
54 | parseArray,
55 | parseObject,
56 | parseNumber
57 | }
58 |
--------------------------------------------------------------------------------
/src/input/named-dates.js:
--------------------------------------------------------------------------------
1 | // pull in 'today' data for the baseline moment
2 | const getNow = function (s) {
3 | s.epoch = Date.now()
4 | Object.keys(s._today || {}).forEach((k) => {
5 | if (typeof s[k] === 'function') {
6 | s = s[k](s._today[k])
7 | }
8 | })
9 | return s
10 | }
11 |
12 | const dates = {
13 | now: (s) => {
14 | return getNow(s)
15 | },
16 | today: (s) => {
17 | return getNow(s)
18 | },
19 | tonight: (s) => {
20 | s = getNow(s)
21 | s = s.hour(18) //6pm
22 | return s
23 | },
24 | tomorrow: (s) => {
25 | s = getNow(s)
26 | s = s.add(1, 'day')
27 | s = s.startOf('day')
28 | return s
29 | },
30 | yesterday: (s) => {
31 | s = getNow(s)
32 | s = s.subtract(1, 'day')
33 | s = s.startOf('day')
34 | return s
35 | },
36 | christmas: (s) => {
37 | const year = getNow(s).year()
38 | s = s.set([year, 11, 25, 18, 0, 0]) // Dec 25
39 | return s
40 | },
41 | 'new years': (s) => {
42 | const year = getNow(s).year()
43 | s = s.set([year, 11, 31, 18, 0, 0]) // Dec 31
44 | return s
45 | }
46 | }
47 | dates['new years eve'] = dates['new years']
48 | export default dates
49 |
--------------------------------------------------------------------------------
/src/input/normalize.js:
--------------------------------------------------------------------------------
1 | //little cleanup..
2 | const normalize = function (str) {
3 | // remove all day-names
4 | str = str.replace(/\b(mon|tues?|wed|wednes|thur?s?|fri|sat|satur|sun)(day)?\b/i, '')
5 | //remove ordinal ending
6 | str = str.replace(/([0-9])(th|rd|st|nd)/, '$1')
7 | str = str.replace(/,/g, '')
8 | str = str.replace(/ +/g, ' ').trim()
9 | return str
10 | }
11 |
12 | export default normalize
13 |
--------------------------------------------------------------------------------
/src/input/parse.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-console */
2 | import parsers from './formats/index.js'
3 |
4 | const parseString = function (s, input, givenTz) {
5 | // let parsers = s.parsers || []
6 | //try each text-parse template, use the first good result
7 | for (let i = 0; i < parsers.length; i++) {
8 | const m = input.match(parsers[i].reg)
9 | if (m) {
10 | const res = parsers[i].parse(s, m, givenTz)
11 | if (res !== null && res.isValid()) {
12 | return res
13 | }
14 | }
15 | }
16 | if (s.silent === false) {
17 | console.warn("Warning: couldn't parse date-string: '" + input + "'")
18 | }
19 | s.epoch = null
20 | return s
21 | }
22 | export default parseString
23 |
--------------------------------------------------------------------------------
/src/methods/compare.js:
--------------------------------------------------------------------------------
1 | import { beADate, getEpoch } from '../fns.js'
2 |
3 | const addMethods = SpaceTime => {
4 | const methods = {
5 | isAfter: function (d) {
6 | d = beADate(d, this)
7 | const epoch = getEpoch(d)
8 | if (epoch === null) {
9 | return null
10 | }
11 | return this.epoch > epoch
12 | },
13 | isBefore: function (d) {
14 | d = beADate(d, this)
15 | const epoch = getEpoch(d)
16 | if (epoch === null) {
17 | return null
18 | }
19 | return this.epoch < epoch
20 | },
21 | isEqual: function (d) {
22 | d = beADate(d, this)
23 | const epoch = getEpoch(d)
24 | if (epoch === null) {
25 | return null
26 | }
27 | return this.epoch === epoch
28 | },
29 | isBetween: function (start, end, isInclusive = false) {
30 | start = beADate(start, this)
31 | end = beADate(end, this)
32 | const startEpoch = getEpoch(start)
33 | if (startEpoch === null) {
34 | return null
35 | }
36 | const endEpoch = getEpoch(end)
37 | if (endEpoch === null) {
38 | return null
39 | }
40 | if (isInclusive) {
41 | return this.isBetween(start, end) || this.isEqual(start) || this.isEqual(end);
42 | }
43 | return startEpoch < this.epoch && this.epoch < endEpoch
44 | }
45 | }
46 |
47 | //hook them into proto
48 | Object.keys(methods).forEach(k => {
49 | SpaceTime.prototype[k] = methods[k]
50 | })
51 | }
52 |
53 | export default addMethods
54 |
--------------------------------------------------------------------------------
/src/methods/diff/index.js:
--------------------------------------------------------------------------------
1 | import { beADate, normalize } from '../../fns.js'
2 | import waterfall from './waterfall.js'
3 |
4 | const reverseDiff = function (obj) {
5 | Object.keys(obj).forEach((k) => {
6 | obj[k] *= -1
7 | })
8 | return obj
9 | }
10 |
11 | // this method counts a total # of each unit, between a, b.
12 | // '1 month' means 28 days in february
13 | // '1 year' means 366 days in a leap year
14 | const main = function (a, b, unit) {
15 | b = beADate(b, a)
16 | //reverse values, if necessary
17 | let reversed = false
18 | if (a.isAfter(b)) {
19 | const tmp = a
20 | a = b
21 | b = tmp
22 | reversed = true
23 | }
24 | //compute them all (i know!)
25 | let obj = waterfall(a, b)
26 | if (reversed) {
27 | obj = reverseDiff(obj)
28 | }
29 | //return just the requested unit
30 | if (unit) {
31 | //make sure it's plural-form
32 | unit = normalize(unit)
33 | if (/s$/.test(unit) !== true) {
34 | unit += 's'
35 | }
36 | if (unit === 'dates') {
37 | unit = 'days'
38 | }
39 | return obj[unit]
40 | }
41 | return obj
42 | }
43 |
44 | export default main
45 |
--------------------------------------------------------------------------------
/src/methods/diff/one.js:
--------------------------------------------------------------------------------
1 | //increment until dates are the same
2 | const climb = (a, b, unit) => {
3 | let i = 0
4 | a = a.clone()
5 | while (a.isBefore(b)) {
6 | //do proper, expensive increment to catch all-the-tricks
7 | a = a.add(1, unit)
8 | i += 1
9 | }
10 | //oops, we went too-far..
11 | if (a.isAfter(b, unit)) {
12 | i -= 1
13 | }
14 | return i
15 | }
16 |
17 | // do a thurough +=1 on the unit, until they match
18 | // for speed-reasons, only used on day, month, week.
19 | const diffOne = (a, b, unit) => {
20 | if (a.isBefore(b)) {
21 | return climb(a, b, unit)
22 | } else {
23 | return climb(b, a, unit) * -1 //reverse it
24 | }
25 | }
26 |
27 | export default diffOne
28 |
--------------------------------------------------------------------------------
/src/methods/diff/waterfall.js:
--------------------------------------------------------------------------------
1 | import diffOne from './one.js'
2 |
3 | // don't do anything too fancy here.
4 | // 2020 - 2019 may be 1 year, or 0 years
5 | // - '1 year difference' means 366 days during a leap year
6 | const fastYear = (a, b) => {
7 | let years = b.year() - a.year()
8 | // should we decrement it by 1?
9 | a = a.year(b.year())
10 | if (a.isAfter(b)) {
11 | years -= 1
12 | }
13 | return years
14 | }
15 |
16 | // use a waterfall-method for computing a diff of any 'pre-knowable' units
17 | // compute years, then compute months, etc..
18 | // ... then ms-math for any very-small units
19 | const diff = function (a, b) {
20 | // an hour is always the same # of milliseconds
21 | // so these units can be 'pre-calculated'
22 | const msDiff = b.epoch - a.epoch
23 | const obj = {
24 | milliseconds: msDiff,
25 | seconds: parseInt(msDiff / 1000, 10)
26 | }
27 | obj.minutes = parseInt(obj.seconds / 60, 10)
28 | obj.hours = parseInt(obj.minutes / 60, 10)
29 |
30 | //do the year
31 | let tmp = a.clone()
32 | obj.years = fastYear(tmp, b)
33 | tmp = a.add(obj.years, 'year')
34 |
35 | //there's always 12 months in a year...
36 | obj.months = obj.years * 12
37 | tmp = a.add(obj.months, 'month')
38 | obj.months += diffOne(tmp, b, 'month')
39 |
40 | // there's always 4 quarters in a year...
41 | obj.quarters = obj.years * 4
42 | obj.quarters += parseInt((obj.months % 12) / 3, 10)
43 |
44 | // there's always atleast 52 weeks in a year..
45 | // (month * 4) isn't as close
46 | obj.weeks = obj.years * 52
47 | tmp = a.add(obj.weeks, 'week')
48 | obj.weeks += diffOne(tmp, b, 'week')
49 |
50 | // there's always atleast 7 days in a week
51 | obj.days = obj.weeks * 7
52 | tmp = a.add(obj.days, 'day')
53 | obj.days += diffOne(tmp, b, 'day')
54 |
55 | return obj
56 | }
57 | export default diff
58 |
--------------------------------------------------------------------------------
/src/methods/every.js:
--------------------------------------------------------------------------------
1 | import { normalize } from '../fns.js'
2 | import { short, long } from '../data/days.js'
3 |
4 | //is it 'wednesday'?
5 | const isDay = function (unit) {
6 | if (short().find((s) => s === unit)) {
7 | return true
8 | }
9 | if (long().find((s) => s === unit)) {
10 | return true
11 | }
12 | return false
13 | }
14 |
15 | // return a list of the weeks/months/days between a -> b
16 | // returns spacetime objects in the timezone of the input
17 | const every = function (start, unit, end, stepCount = 1) {
18 | if (!unit || !end) {
19 | return []
20 | }
21 | //cleanup unit param
22 | unit = normalize(unit)
23 | //cleanup to param
24 | end = start.clone().set(end)
25 | //swap them, if they're backwards
26 | if (start.isAfter(end)) {
27 | const tmp = start
28 | start = end
29 | end = tmp
30 | }
31 | //prevent going beyond end if unit/stepCount > than the range
32 | if (start.diff(end, unit) < stepCount) {
33 | return []
34 | }
35 | //support 'every wednesday'
36 | let d = start.clone()
37 | if (isDay(unit)) {
38 | d = d.next(unit)
39 | unit = 'week'
40 | } else {
41 | const first = d.startOf(unit)
42 | if (first.isBefore(start)) {
43 | d = d.next(unit)
44 | }
45 | }
46 | //okay, actually start doing it
47 | const result = []
48 | while (d.isBefore(end)) {
49 | result.push(d)
50 | d = d.add(stepCount, unit)
51 | }
52 | return result
53 | }
54 | export default every
55 |
--------------------------------------------------------------------------------
/src/methods/format/_offset.js:
--------------------------------------------------------------------------------
1 | import { formatTimezone } from '../../fns.js'
2 |
3 | // create the timezone offset part of an iso timestamp
4 | // it's kind of nuts how involved this is
5 | // "+01:00", "+0100", or simply "+01"
6 | const isoOffset = s => {
7 | const offset = s.timezone().current.offset
8 | return !offset ? 'Z' : formatTimezone(offset, ':')
9 | }
10 |
11 | export default isoOffset
12 |
--------------------------------------------------------------------------------
/src/methods/i18n.js:
--------------------------------------------------------------------------------
1 | import { isObject, isBoolean } from '../fns.js'
2 | import { set as setD } from '../data/days.js'
3 | import { set as setM } from '../data/months.js'
4 | import { set as setTcf } from '../data/caseFormat.js'
5 | import { set as setAmpm } from '../data/ampm.js'
6 | import { set as setDistance } from '../data/distance.js'
7 | import { set as setUnits } from '../data/units.js'
8 |
9 | const addMethods = SpaceTime => {
10 | const methods = {
11 | i18n: function (data) {
12 | //change the day names
13 | if (isObject(data.days)) {
14 | setD(data.days)
15 | }
16 | //change the month names
17 | if (isObject(data.months)) {
18 | setM(data.months)
19 | }
20 |
21 | //change the display style of the month / day names
22 | if (isBoolean(data.useTitleCase)) {
23 | setTcf(data.useTitleCase)
24 | }
25 |
26 | //change am and pm strings
27 | if (isObject(data.ampm)) {
28 | setAmpm(data.ampm)
29 | }
30 |
31 | //change distance strings
32 | if(isObject(data.distance)){
33 | setDistance(data.distance)
34 | }
35 |
36 | //change units strings
37 | if(isObject(data.units)){
38 | setUnits(data.units)
39 | }
40 |
41 | return this
42 | }
43 | }
44 |
45 | //hook them into proto
46 | Object.keys(methods).forEach(k => {
47 | SpaceTime.prototype[k] = methods[k]
48 | })
49 | }
50 |
51 | export default addMethods
52 |
--------------------------------------------------------------------------------
/src/methods/nearest.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-console */
2 | import { normalize } from '../fns.js'
3 |
4 | //round to either current, or +1 of this unit
5 | const nearest = (s, unit) => {
6 | //how far have we gone?
7 | const prog = s.progress()
8 | unit = normalize(unit)
9 | //fix camel-case for this one
10 | if (unit === 'quarterhour') {
11 | unit = 'quarterHour'
12 | }
13 | if (prog[unit] !== undefined) {
14 | // go forward one?
15 | if (prog[unit] > 0.5) {
16 | s = s.add(1, unit)
17 | }
18 | // go to start
19 | s = s.startOf(unit)
20 | } else if (s.silent === false) {
21 | console.warn("no known unit '" + unit + "'")
22 | }
23 | return s
24 | }
25 | export default nearest
26 |
--------------------------------------------------------------------------------
/src/methods/progress.js:
--------------------------------------------------------------------------------
1 | import { normalize } from '../fns.js'
2 | const units = ['year', 'season', 'quarter', 'month', 'week', 'day', 'quarterHour', 'hour', 'minute']
3 |
4 | const doUnit = function (s, k) {
5 | const start = s.clone().startOf(k)
6 | const end = s.clone().endOf(k)
7 | const duration = end.epoch - start.epoch
8 | const percent = (s.epoch - start.epoch) / duration
9 | return parseFloat(percent.toFixed(2))
10 | }
11 |
12 | //how far it is along, from 0-1
13 | const progress = (s, unit) => {
14 | if (unit) {
15 | unit = normalize(unit)
16 | return doUnit(s, unit)
17 | }
18 | const obj = {}
19 | units.forEach(k => {
20 | obj[k] = doUnit(s, k)
21 | })
22 | return obj
23 | }
24 |
25 | export default progress
26 |
--------------------------------------------------------------------------------
/src/methods/query/02-date.js:
--------------------------------------------------------------------------------
1 | import { date as _date } from '../set/set.js'
2 | import { aliases, short, long } from '../../data/days.js'
3 | import walkTo from '../set/walk.js'
4 |
5 | const methods = {
6 | // # day in the month
7 | date: function (num, goFwd) {
8 | if (num !== undefined) {
9 | const s = this.clone()
10 | num = parseInt(num, 10)
11 | if (num) {
12 | s.epoch = _date(s, num, goFwd)
13 | }
14 | return s
15 | }
16 | return this.d.getDate()
17 | },
18 |
19 | //like 'wednesday' (hard!)
20 | day: function (input, goFwd) {
21 | if (input === undefined) {
22 | return this.d.getDay()
23 | }
24 | const original = this.clone()
25 | let want = input
26 | // accept 'wednesday'
27 | if (typeof input === 'string') {
28 | input = input.toLowerCase()
29 | if (aliases.hasOwnProperty(input)) {
30 | want = aliases[input]
31 | } else {
32 | want = short().indexOf(input)
33 | if (want === -1) {
34 | want = long().indexOf(input)
35 | }
36 | }
37 | }
38 | //move approx
39 | const day = this.d.getDay()
40 | let diff = day - want
41 | if (goFwd === true && diff > 0) {
42 | diff = diff - 7
43 | }
44 | if (goFwd === false && diff < 0) {
45 | diff = diff + 7
46 | }
47 | const s = this.subtract(diff, 'days')
48 | //tighten it back up
49 | walkTo(s, {
50 | hour: original.hour(),
51 | minute: original.minute(),
52 | second: original.second()
53 | })
54 | return s
55 | },
56 |
57 | //these are helpful name-wrappers
58 | dayName: function (input, goFwd) {
59 | if (input === undefined) {
60 | return long()[this.day()]
61 | }
62 | let s = this.clone()
63 | s = s.day(input, goFwd)
64 | return s
65 | }
66 | }
67 | export default methods
68 |
--------------------------------------------------------------------------------
/src/methods/query/index.js:
--------------------------------------------------------------------------------
1 | import timeFns from './01-time.js'
2 | import dateFns from './02-date.js'
3 | import yearFns from './03-year.js'
4 |
5 | const methods = Object.assign({}, timeFns, dateFns, yearFns)
6 |
7 | //aliases
8 | methods.milliseconds = methods.millisecond
9 | methods.seconds = methods.second
10 | methods.minutes = methods.minute
11 | methods.hours = methods.hour
12 | methods.hour24 = methods.hour
13 | methods.h12 = methods.hour12
14 | methods.h24 = methods.hour24
15 | methods.days = methods.day
16 |
17 | const addMethods = Space => {
18 | //hook the methods into prototype
19 | Object.keys(methods).forEach(k => {
20 | Space.prototype[k] = methods[k]
21 | })
22 | }
23 |
24 | export default addMethods
25 |
--------------------------------------------------------------------------------
/src/methods/same.js:
--------------------------------------------------------------------------------
1 | //make a string, for easy comparison between dates
2 | const print = {
3 | millisecond: (s) => {
4 | return s.epoch
5 | },
6 | second: (s) => {
7 | return [s.year(), s.month(), s.date(), s.hour(), s.minute(), s.second()].join('-')
8 | },
9 | minute: (s) => {
10 | return [s.year(), s.month(), s.date(), s.hour(), s.minute()].join('-')
11 | },
12 | hour: (s) => {
13 | return [s.year(), s.month(), s.date(), s.hour()].join('-')
14 | },
15 | day: (s) => {
16 | return [s.year(), s.month(), s.date()].join('-')
17 | },
18 | week: (s) => {
19 | return [s.year(), s.week()].join('-')
20 | },
21 | month: (s) => {
22 | return [s.year(), s.month()].join('-')
23 | },
24 | quarter: (s) => {
25 | return [s.year(), s.quarter()].join('-')
26 | },
27 | year: (s) => {
28 | return s.year()
29 | }
30 | }
31 | print.date = print.day
32 |
33 | const addMethods = (SpaceTime) => {
34 | SpaceTime.prototype.isSame = function (b, unit, tzAware = true) {
35 | const a = this
36 | if (!unit) {
37 | return null
38 | }
39 | // support swapped params
40 | if (typeof b === 'string' && typeof unit === 'object') {
41 | const tmp = b
42 | b = unit
43 | unit = tmp
44 | }
45 | if (typeof b === 'string' || typeof b === 'number') {
46 | b = new SpaceTime(b, this.timezone.name)
47 | }
48 | //support 'seconds' aswell as 'second'
49 | unit = unit.replace(/s$/, '')
50 |
51 | // make them the same timezone for proper comparison
52 | if (tzAware === true && a.tz !== b.tz) {
53 | b = b.clone()
54 | b.tz = a.tz
55 | }
56 | if (print[unit]) {
57 | return print[unit](a) === print[unit](b)
58 | }
59 | return null
60 | }
61 | }
62 |
63 | export default addMethods
64 |
--------------------------------------------------------------------------------
/src/methods/set/_model.js:
--------------------------------------------------------------------------------
1 | import monthLength from '../../data/monthLengths.js'
2 | import { isLeapYear } from '../../fns.js'
3 |
4 | const getMonthLength = function (month, year) {
5 | if (month === 1 && isLeapYear(year)) {
6 | return 29
7 | }
8 | return monthLength[month]
9 | }
10 |
11 | //month is the one thing we 'model/compute'
12 | //- because ms-shifting can be off by enough
13 | const rollMonth = (want, old) => {
14 | //increment year
15 | if (want.month > 0) {
16 | const years = parseInt(want.month / 12, 10)
17 | want.year = old.year() + years
18 | want.month = want.month % 12
19 | } else if (want.month < 0) {
20 | const m = Math.abs(want.month)
21 | let years = parseInt(m / 12, 10)
22 | if (m % 12 !== 0) {
23 | years += 1
24 | }
25 | want.year = old.year() - years
26 | //ignore extras
27 | want.month = want.month % 12
28 | want.month = want.month + 12
29 | if (want.month === 12) {
30 | want.month = 0
31 | }
32 | }
33 | return want
34 | }
35 |
36 | // briefly support day=-2 (this does not need to be perfect.)
37 | const rollDaysDown = (want, old, sum) => {
38 | want.year = old.year()
39 | want.month = old.month()
40 | const date = old.date()
41 | want.date = date - Math.abs(sum)
42 | while (want.date < 1) {
43 | want.month -= 1
44 | if (want.month < 0) {
45 | want.month = 11
46 | want.year -= 1
47 | }
48 | const max = getMonthLength(want.month, want.year)
49 | want.date += max
50 | }
51 | return want
52 | }
53 |
54 | // briefly support day=33 (this does not need to be perfect.)
55 | const rollDaysUp = (want, old, sum) => {
56 | let year = old.year()
57 | let month = old.month()
58 | let max = getMonthLength(month, year)
59 | while (sum > max) {
60 | sum -= max
61 | month += 1
62 | if (month >= 12) {
63 | month -= 12
64 | year += 1
65 | }
66 | max = getMonthLength(month, year)
67 | }
68 | want.month = month
69 | want.date = sum
70 | return want
71 | }
72 |
73 | export const months = rollMonth
74 | export const days = rollDaysUp
75 | export const daysBack = rollDaysDown
76 |
--------------------------------------------------------------------------------
/src/methods/since/_iso.js:
--------------------------------------------------------------------------------
1 | /*
2 | ISO 8601 duration format
3 | // https://en.wikipedia.org/wiki/ISO_8601#Durations
4 | "P3Y6M4DT12H30M5S"
5 | P the start of the duration representation.
6 | Y the number of years.
7 | M the number of months.
8 | W the number of weeks.
9 | D the number of days.
10 | T of the representation.
11 | H the number of hours.
12 | M the number of minutes.
13 | S the number of seconds.
14 | */
15 |
16 | const fmt = (n) => Math.abs(n) || 0
17 |
18 | const toISO = function (diff) {
19 | let iso = 'P'
20 | iso += fmt(diff.years) + 'Y'
21 | iso += fmt(diff.months) + 'M'
22 | iso += fmt(diff.days) + 'DT'
23 | iso += fmt(diff.hours) + 'H'
24 | iso += fmt(diff.minutes) + 'M'
25 | iso += fmt(diff.seconds) + 'S'
26 | return iso
27 | }
28 | export default toISO
--------------------------------------------------------------------------------
/src/methods/since/getDiff.js:
--------------------------------------------------------------------------------
1 |
2 | //get number of hours/minutes... between the two dates
3 | function getDiff(a, b) {
4 | const isBefore = a.isBefore(b)
5 | const later = isBefore ? b : a
6 | let earlier = isBefore ? a : b
7 | earlier = earlier.clone()
8 | const diff = {
9 | years: 0,
10 | months: 0,
11 | days: 0,
12 | hours: 0,
13 | minutes: 0,
14 | seconds: 0
15 | }
16 | Object.keys(diff).forEach((unit) => {
17 | if (earlier.isSame(later, unit)) {
18 | return
19 | }
20 | const max = earlier.diff(later, unit)
21 | earlier = earlier.add(max, unit)
22 | diff[unit] = max
23 | })
24 | //reverse it, if necessary
25 | if (isBefore) {
26 | Object.keys(diff).forEach((u) => {
27 | if (diff[u] !== 0) {
28 | diff[u] *= -1
29 | }
30 | })
31 | }
32 | return diff
33 | }
34 | export default getDiff
--------------------------------------------------------------------------------
/src/methods/since/index.js:
--------------------------------------------------------------------------------
1 | import { beADate } from '../../fns.js'
2 | import toISO from './_iso.js'
3 | import getDiff from './getDiff.js'
4 | import soften from './soften.js'
5 | import {
6 | pastString,
7 | futureString,
8 | nowString,
9 | presentString,
10 | pastDistanceString,
11 | futureDistanceString
12 | } from "../../data/distance.js";
13 | //by spencermountain + Shaun Grady
14 |
15 | //create the human-readable diff between the two dates
16 | const since = (start, end) => {
17 | end = beADate(end, start)
18 | const diff = getDiff(start, end)
19 | const isNow = Object.keys(diff).every((u) => !diff[u])
20 | if (isNow === true) {
21 | return {
22 | diff,
23 | rounded: nowString(),
24 | qualified: nowString(),
25 | precise: nowString(),
26 | abbreviated: [],
27 | iso: 'P0Y0M0DT0H0M0S',
28 | direction: presentString(),
29 | }
30 | }
31 | let precise
32 | let direction = futureString()
33 |
34 | let { rounded, qualified } = soften(diff)
35 | const { englishValues, abbreviated } = soften(diff)
36 |
37 | //make them into a string
38 | precise = englishValues.splice(0, 2).join(', ')
39 | //handle before/after logic
40 | if (start.isAfter(end) === true) {
41 | rounded = pastDistanceString(rounded)
42 | qualified = pastDistanceString(qualified)
43 | precise = pastDistanceString(precise)
44 | direction = pastString()
45 | } else {
46 | rounded = futureDistanceString(rounded)
47 | qualified = futureDistanceString(qualified)
48 | precise = futureDistanceString(precise)
49 | }
50 | // https://en.wikipedia.org/wiki/ISO_8601#Durations
51 | // P[n]Y[n]M[n]DT[n]H[n]M[n]S
52 | const iso = toISO(diff)
53 | return {
54 | diff,
55 | rounded,
56 | qualified,
57 | precise,
58 | abbreviated,
59 | iso,
60 | direction,
61 | }
62 | }
63 |
64 | export default since
65 |
--------------------------------------------------------------------------------
/src/methods/since/soften.js:
--------------------------------------------------------------------------------
1 | //our conceptual 'break-points' for each unit
2 | import { unitsString } from "../../data/units.js";
3 | import { almostString, overString } from "../../data/distance.js";
4 |
5 | const qualifiers = {
6 | months: {
7 | almost: 10,
8 | over: 4
9 | },
10 | days: {
11 | almost: 25,
12 | over: 10
13 | },
14 | hours: {
15 | almost: 20,
16 | over: 8
17 | },
18 | minutes: {
19 | almost: 50,
20 | over: 20
21 | },
22 | seconds: {
23 | almost: 50,
24 | over: 20
25 | }
26 | }
27 |
28 | // Expects a plural unit arg
29 | function pluralize(value, unit) {
30 | if (value === 1) {
31 | return value + ' ' + unitsString(unit.slice(0, -1))
32 | }
33 | return value + ' ' + unitsString(unit)
34 | }
35 |
36 | const toSoft = function (diff) {
37 | let rounded = null
38 | let qualified = null
39 | const abbreviated = []
40 | const englishValues = []
41 | //go through each value and create its text-representation
42 | Object.keys(diff).forEach((unit, i, units) => {
43 | const value = Math.abs(diff[unit])
44 | if (value === 0) {
45 | return
46 | }
47 | abbreviated.push(value + unit[0])
48 | const englishValue = pluralize(value, unit)
49 | englishValues.push(englishValue)
50 | if (!rounded) {
51 | rounded = englishValue
52 | qualified = englishValue
53 | if (i > 4) {
54 | return
55 | }
56 | //is it a 'almost' something, etc?
57 | const nextUnit = units[i + 1]
58 | const nextValue = Math.abs(diff[nextUnit])
59 | if (nextValue > qualifiers[nextUnit].almost) {
60 | rounded = pluralize(value + 1, unit)
61 | qualified = almostString() + ' ' + rounded
62 | } else if (nextValue > qualifiers[nextUnit].over) {
63 | qualified = overString() + ' ' + englishValue
64 | }
65 | }
66 | })
67 |
68 | return { qualified, rounded, abbreviated, englishValues }
69 | }
70 | export default toSoft
71 |
--------------------------------------------------------------------------------
/src/timezone/guessTz.js:
--------------------------------------------------------------------------------
1 | //find the implicit iana code for this machine.
2 | //safely query the Intl object
3 | //based on - https://bitbucket.org/pellepim/jstimezonedetect/src
4 | const fallbackTZ = 'utc' //
5 |
6 | //this Intl object is not supported often, yet
7 | const safeIntl = () => {
8 | if (typeof Intl === 'undefined' || typeof Intl.DateTimeFormat === 'undefined') {
9 | return null
10 | }
11 | const format = Intl.DateTimeFormat()
12 | if (typeof format === 'undefined' || typeof format.resolvedOptions === 'undefined') {
13 | return null
14 | }
15 | const timezone = format.resolvedOptions().timeZone
16 | if (!timezone) {
17 | return null
18 | }
19 | return timezone.toLowerCase()
20 | }
21 |
22 | const guessTz = () => {
23 | const timezone = safeIntl()
24 | if (timezone === null) {
25 | return fallbackTZ
26 | }
27 | return timezone
28 | }
29 | //do it once per computer
30 | export default guessTz
31 |
--------------------------------------------------------------------------------
/src/timezone/parseOffset.js:
--------------------------------------------------------------------------------
1 | const isOffset = /(-?[0-9]+)h(rs)?/i
2 | const isNumber = /(-?[0-9]+)/
3 | const utcOffset = /utc([\-+]?[0-9]+)/i
4 | const gmtOffset = /gmt([\-+]?[0-9]+)/i
5 |
6 | const toIana = function (num) {
7 | num = Number(num)
8 | if (num >= -13 && num <= 13) {
9 | num = num * -1 //it's opposite!
10 | num = (num > 0 ? '+' : '') + num //add plus sign
11 | return 'etc/gmt' + num
12 | }
13 | return null
14 | }
15 |
16 | const parseOffset = function (tz) {
17 | // '+5hrs'
18 | let m = tz.match(isOffset)
19 | if (m !== null) {
20 | return toIana(m[1])
21 | }
22 | // 'utc+5'
23 | m = tz.match(utcOffset)
24 | if (m !== null) {
25 | return toIana(m[1])
26 | }
27 | // 'GMT-5' (not opposite)
28 | m = tz.match(gmtOffset)
29 | if (m !== null) {
30 | const num = Number(m[1]) * -1
31 | return toIana(num)
32 | }
33 | // '+5'
34 | m = tz.match(isNumber)
35 | if (m !== null) {
36 | return toIana(m[1])
37 | }
38 | return null
39 | }
40 | export default parseOffset
41 |
--------------------------------------------------------------------------------
/src/timezone/quick.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-console */
2 | import isSummer from './summerTime.js'
3 |
4 | // this method avoids having to do a full dst-calculation on every operation
5 | // it reproduces some things in ./index.js, but speeds up spacetime considerably
6 | const quickOffset = s => {
7 | const zones = s.timezones
8 | const obj = zones[s.tz]
9 | if (obj === undefined) {
10 | console.warn("Warning: couldn't find timezone " + s.tz)
11 | return 0
12 | }
13 | if (obj.dst === undefined) {
14 | return obj.offset
15 | }
16 |
17 | //get our two possible offsets
18 | const jul = obj.offset
19 | let dec = obj.offset + 1 // assume it's the same for now
20 | if (obj.hem === 'n') {
21 | dec = jul - 1
22 | }
23 | const split = obj.dst.split('->')
24 | const inSummer = isSummer(s.epoch, split[0], split[1], jul, dec)
25 | if (inSummer === true) {
26 | return jul
27 | }
28 | return dec
29 | }
30 | export default quickOffset
31 |
--------------------------------------------------------------------------------
/src/timezone/summerTime.js:
--------------------------------------------------------------------------------
1 | const MSEC_IN_HOUR = 60 * 60 * 1000
2 |
3 | //convert our local date syntax a javascript UTC date
4 | const toUtc = (dstChange, offset, year) => {
5 | const [month, rest] = dstChange.split('/')
6 | const [day, hour] = rest.split(':')
7 | return Date.UTC(year, month - 1, day, hour) - (offset * MSEC_IN_HOUR)
8 | }
9 |
10 | // compare epoch with dst change events (in utc)
11 | const inSummerTime = (epoch, start, end, summerOffset, winterOffset) => {
12 | const year = new Date(epoch).getUTCFullYear()
13 | const startUtc = toUtc(start, winterOffset, year)
14 | const endUtc = toUtc(end, summerOffset, year)
15 | // simple number comparison now
16 | return epoch >= startUtc && epoch < endUtc
17 | }
18 |
19 | export default inSummerTime
20 |
--------------------------------------------------------------------------------
/src/whereIts.js:
--------------------------------------------------------------------------------
1 | import Spacetime from './spacetime.js'
2 | // const timezones = require('../data');
3 |
4 | const whereIts = (a, b) => {
5 | let start = new Spacetime(null)
6 | let end = new Spacetime(null)
7 | start = start.time(a)
8 | //if b is undefined, use as 'within one hour'
9 | if (b) {
10 | end = end.time(b)
11 | } else {
12 | end = start.add(59, 'minutes')
13 | }
14 |
15 | const startHour = start.hour()
16 | const endHour = end.hour()
17 | const tzs = Object.keys(start.timezones).filter((tz) => {
18 | if (tz.indexOf('/') === -1) {
19 | return false
20 | }
21 | const m = new Spacetime(null, tz)
22 | const hour = m.hour()
23 | //do 'calendar-compare' not real-time-compare
24 | if (hour >= startHour && hour <= endHour) {
25 | //test minutes too, if applicable
26 | if (hour === startHour && m.minute() < start.minute()) {
27 | return false
28 | }
29 | if (hour === endHour && m.minute() > end.minute()) {
30 | return false
31 | }
32 | return true
33 | }
34 | return false
35 | })
36 | return tzs
37 | }
38 | export default whereIts
39 |
--------------------------------------------------------------------------------
/test/api.test.js:
--------------------------------------------------------------------------------
1 | import spacetime from './lib/index.js'
2 | import test from 'tape'
3 | import api from '../api/index.js'
4 |
5 | test('test main methods', (t) => {
6 | Object.keys(api.main).forEach((k) => {
7 | const s = spacetime('1998-03-28')
8 | s[k]()
9 | t.ok(true, k)
10 | })
11 | t.end()
12 | })
13 |
14 | test('test getter methods', (t) => {
15 | Object.keys(api.getters).forEach((k) => {
16 | const s = spacetime('1998-03-28')
17 | s[k]()
18 | t.ok(true, k)
19 | })
20 | t.end()
21 | })
22 |
23 | test('test util methods', (t) => {
24 | Object.keys(api.utils).forEach((k) => {
25 | //skip these ones
26 | if (k === 'd' || k === 'log' || k === 'i18n' || k === 'weekStart') {
27 | t.ok(true, k)
28 | return
29 | }
30 | const s = spacetime('1998-03-28')
31 | s[k]()
32 | t.ok(true, k)
33 | })
34 | t.end()
35 | })
36 |
--------------------------------------------------------------------------------
/test/clone.test.js:
--------------------------------------------------------------------------------
1 | import test from 'tape'
2 | import spacetime from './lib/index.js'
3 |
4 | test('clone', (t) => {
5 | let a = spacetime('March 18, 1999 23:42:00', 'Canada/Eastern')
6 | let b = a.clone()
7 | t.equal(a.date(), 18, 'start-date')
8 | t.equal(a.hour(), 23, 'start hour')
9 | t.equal(a.isSame(b, 'hour'), true, 'same-hour')
10 |
11 | a = a.hour(7)
12 | t.equal(a.hour(), 7, 'new-hour')
13 | t.equal(b.hour(), 23, 'old-hour')
14 |
15 | b = b.date(17)
16 | t.equal(b.date(), 17, 'new-date')
17 | t.equal(a.date(), 18, 'old-date')
18 |
19 | t.end()
20 | })
21 |
--------------------------------------------------------------------------------
/test/day.test.js:
--------------------------------------------------------------------------------
1 | import test from 'tape'
2 | import spacetime from './lib/index.js'
3 |
4 | test('set day forward', (t) => {
5 | const days = ['thu', 'fri', 'sat', 'sun', 'mon', 'tue', 'wed']
6 | const d = spacetime('march 18 2021') //thursday
7 | days.forEach((day, i) => {
8 | const s = d.day(day, true)
9 | const want = d.add(i, 'days')
10 | t.equal(s.format('iso-short'), want.format('iso-short'), day)
11 | })
12 | t.end()
13 | })
14 |
15 | test('set day backward', (t) => {
16 | const days = ['tue', 'mon', 'sun', 'sat', 'fri', 'thu', 'wed']
17 | const d = spacetime('march 23 2021') //tuesday
18 | days.forEach((day, i) => {
19 | const s = d.day(day, false)
20 | const want = d.minus(i, 'days')
21 | t.equal(s.format('iso-short'), want.format('iso-short'), day)
22 | })
23 | t.end()
24 | })
25 |
--------------------------------------------------------------------------------
/test/dayTime.test.js:
--------------------------------------------------------------------------------
1 | import test from 'tape'
2 | import spacetime from './lib/index.js'
3 |
4 | test('daytime-consistent', (t) => {
5 | let s = spacetime.now()
6 | const times = ['morning', 'afternoon', 'evening', 'night']
7 | times.forEach((daytime) => {
8 | s = s.dayTime(daytime)
9 | t.equal(s.dayTime(), daytime, daytime + ' is ' + daytime)
10 | })
11 | t.end()
12 | })
13 |
14 | test('daytime-sanity-test', (t) => {
15 | let s = spacetime.now()
16 | let time = '2am'
17 | s = s.time(time)
18 | t.equal(s.dayTime(), 'night', time + ' is night')
19 |
20 | time = '7am'
21 | s = s.time(time)
22 | t.equal(s.dayTime(), 'morning', time + ' is morning')
23 |
24 | time = '7:01am'
25 | s = s.time(time)
26 | t.equal(s.dayTime(), 'morning', time + ' is morning')
27 |
28 | time = '11:59am'
29 | s = s.time(time)
30 | t.equal(s.dayTime(), 'morning', time + ' is morning')
31 |
32 | time = '12:00pm'
33 | s = s.time(time)
34 | t.equal(s.dayTime(), 'afternoon', time + ' is afternoon')
35 |
36 | time = '12:01pm'
37 | s = s.time(time)
38 | t.equal(s.dayTime(), 'afternoon', time + ' is afternoon')
39 |
40 | time = '2:47pm'
41 | s = s.time(time)
42 | t.equal(s.dayTime(), 'afternoon', time + ' is afternoon')
43 |
44 | time = '6pm'
45 | s = s.time(time)
46 | t.equal(s.dayTime(), 'evening', time + ' is evening')
47 |
48 | time = '6:02pm'
49 | s = s.time(time)
50 | t.equal(s.dayTime(), 'evening', time + ' is evening')
51 |
52 | time = '9:07pm'
53 | s = s.time(time)
54 | t.equal(s.dayTime(), 'evening', time + ' is evening')
55 |
56 | time = '11pm'
57 | s = s.time(time)
58 | t.equal(s.dayTime(), 'night', time + ' is night')
59 |
60 | time = '12am'
61 | s = s.time(time)
62 | t.equal(s.dayTime(), 'night', time + ' is night')
63 |
64 | time = '12:00am'
65 | s = s.time(time)
66 | t.equal(s.dayTime(), 'night', time + ' is night')
67 |
68 | time = '12:01am'
69 | s = s.time(time)
70 | t.equal(s.dayTime(), 'night', time + ' is night')
71 |
72 | t.end()
73 | })
74 |
--------------------------------------------------------------------------------
/test/daysThisMonth.test.js:
--------------------------------------------------------------------------------
1 | import test from 'tape'
2 | import spacetime from './lib/index.js'
3 |
4 | test('test daysInMonth() on all months', (t) => {
5 | let d = spacetime('January 12, 2016')
6 | t.equal(d.daysInMonth(), 31, 'daysInMonth - Jan. 2016')
7 |
8 | d = spacetime('February 12, 2016')
9 | t.equal(d.daysInMonth(), 29, 'daysInMonth - Feb. 2016 (29 Days, leap year)')
10 |
11 | d = spacetime('March 12, 2016')
12 | t.equal(d.daysInMonth(), 31, 'daysInMonth - Mar. 2016')
13 |
14 | d = spacetime('April 12, 2016')
15 | t.equal(d.daysInMonth(), 30, 'daysInMonth - Apr. 2016')
16 |
17 | d = spacetime('May 12, 2016')
18 | t.equal(d.daysInMonth(), 31, 'daysInMonth - May 2016')
19 |
20 | d = spacetime('June 12, 2016')
21 | t.equal(d.daysInMonth(), 30, 'daysInMonth - Jun. 2016')
22 |
23 | d = spacetime('July 12, 2016')
24 | t.equal(d.daysInMonth(), 31, 'daysInMonth - Jul. 2016')
25 |
26 | d = spacetime('August 12, 2016')
27 | t.equal(d.daysInMonth(), 31, 'daysInMonth - Aug. 2016')
28 |
29 | d = spacetime('September 12, 2016')
30 | t.equal(d.daysInMonth(), 30, 'daysInMonth - Sep. 2016')
31 |
32 | d = spacetime('October 12, 2016')
33 | t.equal(d.daysInMonth(), 31, 'daysInMonth - Oct. 2016')
34 |
35 | d = spacetime('November 12, 2016')
36 | t.equal(d.daysInMonth(), 30, 'daysInMonth - Oct. 2016')
37 |
38 | d = spacetime('December 12, 2016')
39 | t.equal(d.daysInMonth(), 31, 'daysInMonth - Oct. 2016')
40 |
41 | d = spacetime('February 12, 2023')
42 | t.equal(d.daysInMonth(), 28, 'daysInMonth - Feb. 2023 (28 Days, no leap year)')
43 |
44 | t.end()
45 | })
46 |
--------------------------------------------------------------------------------
/test/dst-diff.ignore.js:
--------------------------------------------------------------------------------
1 | import test from 'tape'
2 | import spacetime from './lib/index.js'
3 | const useOldTz = require('./lib/useOldTz')
4 |
5 | // 2am is skipped
6 | test('spring-diff', (t) => {
7 | let before = spacetime('2020-03-08T01:00:00', 'America/Chicago')
8 | let after = spacetime('2020-03-08T03:00:00', 'America/Chicago')
9 | let delta = after.since(before).diff
10 | t.equal(delta.hours, 1, '1 hour later')
11 |
12 | before = spacetime('2020-03-08T01:59:00', 'America/Chicago')
13 | after = spacetime('2020-03-08T03:01:00', 'America/Chicago')
14 | delta = after.since(before).diff
15 | t.equal(delta.minutes, 2, '2 min later')
16 |
17 | t.end()
18 | })
19 |
20 | // there are two 1:00ams
21 | test('fall-diff', (t) => {
22 | let before = spacetime('2020-11-01T01:50:00', 'America/Chicago')
23 | let after = spacetime('2020-11-01T03:10:00', 'America/Chicago')
24 | before = useOldTz(before)
25 | after = useOldTz(after)
26 | const delta = after.since(before).diff
27 | t.equal(delta.minutes, 20, '20 minutes later')
28 | t.end()
29 | })
30 |
--------------------------------------------------------------------------------
/test/epoch.test.js:
--------------------------------------------------------------------------------
1 | import test from 'tape'
2 | import spacetime from './lib/index.js'
3 |
4 | test('fromUnixSeconds', (t) => {
5 | let mils = 1744200453183
6 | let secs = 1744200453
7 | let a = spacetime.fromUnixSeconds(secs)
8 | let b = spacetime(mils)
9 | t.ok(a.isSame('hour', b), 'mils=secs')
10 |
11 | let s = spacetime.fromUnixSeconds(secs, 'Canada/Eastern')
12 | t.equal(s.iso(), '2025-04-09T08:07:33.000-04:00', '8am et');
13 | s = spacetime.fromUnixSeconds(secs, 'Canada/Pacific')
14 | t.equal(s.iso(), '2025-04-09T05:07:33.000-07:00', '5am pt');
15 |
16 | // test getter method
17 | t.equal(s.epochSeconds(), secs, 'retrieve seconds')
18 |
19 | // test setter method
20 | let futureSeconds = 1830720600
21 | s = spacetime.now('UTC').epochSeconds(futureSeconds)
22 | t.equal(s.epochSeconds(), futureSeconds, 'set seconds')
23 | t.equal(s.iso(), '2028-01-05T21:30:00.000Z', 'is future seconds')
24 | t.end()
25 | })
26 |
--------------------------------------------------------------------------------
/test/every.test.js:
--------------------------------------------------------------------------------
1 | import test from 'tape'
2 | import spacetime from './lib/index.js'
3 |
4 | test('every-unit', (t) => {
5 | const start = spacetime('April 6th 2019', 'Europe/Paris')
6 | const end = spacetime('April 20th 2019', 'Europe/Paris').add(1, 'hour')
7 |
8 | const days = start.every('day', end)
9 | t.equal(days.length, 15, '15 days')
10 | t.equal(days[0].timezone().name, 'Europe/Paris', 'results in right timezone')
11 |
12 | const weeks = start.every(' weEK ', end)
13 | t.equal(weeks.length, 2, '2 weeks')
14 |
15 | const years = start.every('years', end)
16 | t.equal(years.length, 0, '0 years')
17 |
18 | t.end()
19 | })
20 |
21 | test('step-count', (t) => {
22 | const start = spacetime('April 6th 2019', 'Europe/Paris')
23 | const end = spacetime('April 20th 2019', 'Europe/Paris').add(3, 'years')
24 |
25 | const biannualInterval = start.every('quarter', end, 2)
26 | t.equal(biannualInterval.length, 6, 'every 2 quarters')
27 | t.equal(biannualInterval[0].timezone().name, 'Europe/Paris', 'results in right timezone')
28 |
29 | const fortnights = start.every('week', end, 2)
30 | t.equal(fortnights.length, 80, 'every fortnight')
31 | t.equal(biannualInterval[0].timezone().name, 'Europe/Paris', 'results in right timezone')
32 |
33 | const everyFourYears = start.every('years', end, 4)
34 | t.equal(everyFourYears.length, 0, 'interval/step count too large for range')
35 |
36 | t.end()
37 | })
38 |
39 | test('monday-sunday', (t) => {
40 | const days = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday']
41 | const start = spacetime('April 8th 2019').startOf('week')
42 | const end = start.endOf('week')
43 | const eachDay = start.every('day', end).map((d) => d.dayName())
44 | t.deepEqual(eachDay, days, 'got mon-sunday')
45 | t.end()
46 | })
47 |
48 | test('long-every is stable', (t) => {
49 | const d = spacetime('jan 1st 1872')
50 | d.every('year', 'jan 1st 1902').forEach((s) => {
51 | const year = s.year()
52 | t.equal(s.month(), 0, year + ' is-january')
53 | t.equal(s.date(), 1, year + ' is-first')
54 | })
55 | t.end()
56 | })
57 |
--------------------------------------------------------------------------------
/test/findTz.test.js:
--------------------------------------------------------------------------------
1 | import test from 'tape'
2 | import spacetime from './lib/index.js'
3 |
4 | test('whereits', (t) => {
5 | let tzs = spacetime.whereIts('9am')
6 | t.ok(tzs.length > 0, '9am somewhere')
7 | t.ok(tzs.length < 90, '9am-is-subset')
8 | tzs = spacetime.whereIts('10am')
9 | t.ok(tzs.length > 0, '10am somewhere')
10 | t.ok(tzs.length < 90, '10am-is-subset')
11 | tzs = spacetime.whereIts('8pm')
12 | t.ok(tzs.length > 0, '8pm somewhere')
13 | t.ok(tzs.length < 90, '8pm-is-subset')
14 | tzs = spacetime.whereIts('11pm')
15 | t.ok(tzs.length > 0, '11pm somewhere')
16 | t.ok(tzs.length < 90, '11pm-is-subset')
17 |
18 | tzs = spacetime.whereIts('9:00am', '11:00am')
19 | t.ok(tzs.length > 0, '9am-11am somewhere')
20 | t.ok(tzs.length < 120, '9am-11am-is-subset')
21 |
22 | tzs = spacetime.whereIts('9am', '11pm')
23 | t.ok(tzs.length > 0, '9am-11pm somewhere')
24 | t.ok(tzs.length < 503, '9am-11pm-is-subset')
25 |
26 | tzs = spacetime.whereIts('8pm', '11pm')
27 | t.ok(tzs.length > 0, '8pm-11pm somewhere')
28 | t.ok(tzs.length < 503, '8pm-11pm-is-subset')
29 |
30 | tzs = spacetime.whereIts('8pm', '7pm')
31 | t.ok(tzs.length === 0, '8pm-7pm nowhere')
32 |
33 | tzs = spacetime.whereIts('8pm', '7am')
34 | t.ok(tzs.length === 0, '8pm-apm nowhere')
35 |
36 | t.end()
37 | })
38 |
39 | test('get all timezones method', (t) => {
40 | const obj = spacetime.timezones()
41 | t.ok(Object.keys(obj).length > 60, 'got a lot of timezones')
42 | t.equal(
43 | typeof obj['america/st_vincent'].offset,
44 | 'number',
45 | 'got a list of timeszones with offsets'
46 | )
47 | t.end()
48 | })
49 |
50 | test('throw-error-on-invalid', (t) => {
51 | try {
52 | spacetime('12pm', 'invalid-timezone')
53 | t.ok(false, 'did-not-throw-exception')
54 | } catch (e) {
55 | t.ok(true, 'threw-exception-on-input')
56 | }
57 | try {
58 | spacetime.now().goto('canada/nope')
59 | t.ok(false, 'goto-did-not-throw-exception')
60 | } catch (e) {
61 | t.ok(true, 'threw-exception-on-goto')
62 | }
63 | t.end()
64 | })
65 |
--------------------------------------------------------------------------------
/test/informal-tzs.test.js:
--------------------------------------------------------------------------------
1 | import test from 'tape'
2 | import spacetime from './lib/index.js'
3 |
4 | test('informal timezones', (t) => {
5 | const arr = [
6 | ['Toronto', 'America/Toronto'],
7 | ['toronto', 'America/Toronto'],
8 | ['toronto time', 'America/Toronto'],
9 | ['toronto standard time', 'America/Toronto'],
10 | ['eastern daylight', 'Canada/Eastern'],
11 | ['Jamaica', 'America/Jamaica'],
12 | // ['PST', 'America/Los_Angeles'],
13 | // ['pdt', 'America/Los_Angeles'],
14 | // ['bst', 'Europe/London'],
15 | ['pacific', 'America/Los_Angeles'],
16 | ['pacific standard', 'America/Los_Angeles'],
17 | ['pacific daylight', 'America/Los_Angeles'],
18 | ['GMT+8', '-8h']
19 | // ['east african', 'eastern africa']
20 | ]
21 | const date = 'November 11, 1999'
22 | arr.forEach((a) => {
23 | const left = spacetime(date, a[0])
24 | const right = spacetime(date, a[1])
25 | t.equal(left.format('nice'), right.format('nice'), a[0])
26 | })
27 | t.end()
28 | })
29 |
--------------------------------------------------------------------------------
/test/json.test.js:
--------------------------------------------------------------------------------
1 | import test from 'tape'
2 | import spacetime from './lib/index.js'
3 |
4 | test('json in-out', (t) => {
5 | const arr = [
6 | '2011-12-03T10:15:30.003+01:00',
7 | '2011-12-03T10:15:30.003Z',
8 | '2020-03-20T22:15:33.645-04:00',
9 | '2022-01-01T00:00:00.000Z',
10 | '2022-12-31T23:59:59.999Z',
11 | '2023-06-15T12:30:00.000-07:00'
12 | ]
13 | arr.forEach(str => {
14 | const a = spacetime(str)
15 | const json = a.json()
16 | const b = spacetime(json)
17 | t.equal(b.format('iso'), str, 'constr json' + str)
18 | const c = spacetime.now().json(json)
19 | t.equal(c.format('iso'), str, 'json input' + str)
20 | })
21 | t.end()
22 | })
--------------------------------------------------------------------------------
/test/lib/dstParse.js:
--------------------------------------------------------------------------------
1 | //local time of fall dst change-over
2 | const dstParse = (dstChange, num) => {
3 | const fall = dstChange.split('->')[num]
4 | const [month, rest] = fall.split('/')
5 | let [day, hour] = rest.split(':')
6 | if (hour === '24') {
7 | hour = '0'
8 | day = Number(day) + 1
9 | }
10 | if (hour === '00') {
11 | hour = '23'
12 | day = Number(day) - 1
13 | }
14 | return {
15 | year: new Date().getFullYear(),
16 | month: Number(month) - 1, //
17 | date: Number(day),
18 | hour: Number(hour),
19 | minute: 2
20 | }
21 | }
22 | export default dstParse
23 |
--------------------------------------------------------------------------------
/test/lib/index.js:
--------------------------------------------------------------------------------
1 | import src from '../../src/index.js'
2 | import build from '../../builds/spacetime.mjs'
3 | let lib = src
4 | //export dev, or compiled lib
5 | if (typeof process !== undefined && typeof module !== undefined) {
6 | if (process.env.TESTENV === 'prod') {
7 | console.log('== production build test 🚀 ==');
8 | lib = build
9 | }
10 | }
11 |
12 | export default lib
--------------------------------------------------------------------------------
/test/lib/useOldTz.js:
--------------------------------------------------------------------------------
1 | //use the old dst changes, from 2017, when we made the tests
2 | const changeTz = (s) => {
3 | const timezones = s.timezones
4 | timezones['canada/eastern'].dst = '03/12:03->11/05:01'
5 | timezones['australia/canberra'].dst = '04/02:02->10/01:03'
6 | timezones['pacific/fiji'].dst = '01/15:02->11/05:03'
7 | timezones['europe/brussels'].dst = '03/29:02->10/25:03'
8 | timezones['america/chicago'].dst = '03/08:02->11/01:02'
9 | timezones['canada/pacific'].dst = '03/08:02->11/01:02'
10 | s.timezones = timezones
11 | return s
12 | }
13 | export default changeTz
14 |
--------------------------------------------------------------------------------
/test/nearest.test.js:
--------------------------------------------------------------------------------
1 | import test from 'tape'
2 | import spacetime from './lib/index.js'
3 |
4 | test('nearest', (t) => {
5 | const s = spacetime('jan 2 2019', 'Canada/Eastern')
6 | const month = s.nearest('month')
7 | const year = s.nearest('year')
8 | const quarter = s.nearest('quarter')
9 | t.equal(month.format('iso'), year.format('iso'), 'nearest year=nearest month')
10 | t.equal(quarter.format('iso'), year.format('iso'), 'nearest quarter=nearest month')
11 | t.end()
12 | })
13 |
14 | test('nearest-time', (t) => {
15 | let s = spacetime('feb 20 2017', 'Canada/Pacific')
16 | s = s.time('3:29am')
17 | const hour = s.nearest('hour')
18 | t.equal(hour.format('time'), '3:00am', 'close-call nearest-hour')
19 | t.end()
20 | })
21 |
22 | test('nearest-quarter-hour', (t) => {
23 | let s = spacetime([2019, 4, 8, 10, 11, 12], 'Canada/Eastern')
24 | s = s.nearest('quarter-hour')
25 | t.equal(s.format('iso'), '2019-05-08T10:15:00.000-04:00', 'nearest-quarterhour')
26 | t.end()
27 | })
28 |
--------------------------------------------------------------------------------
/test/now.test.js:
--------------------------------------------------------------------------------
1 | import test from 'tape'
2 | import spacetime from './lib/index.js'
3 | import tk from 'timekeeper'
4 |
5 | test('now-is-now', (t) => {
6 | const time = new Date(1554092400000) // 4:20, april 1st 2019 GMT
7 | tk.travel(time)
8 |
9 | let d = spacetime(null, 'Etc/GMT')
10 | t.equal(d.format('nice-short'), 'Apr 1st, 4:20am', 'date object mocked to 4:20')
11 |
12 | d = spacetime.now('Etc/GMT')
13 | t.equal(d.format('nice-short'), 'Apr 1st, 4:20am', 'its 4:20 now')
14 |
15 | d = spacetime.now('Canada/Eastern')
16 | t.equal(d.format('nice-short'), 'Apr 1st, 12:20am', 'its not 4:20 in toronto')
17 |
18 | d = spacetime.today('Etc/GMT')
19 | t.equal(d.format('nice-short'), 'Apr 1st, 12:00am', 'its april 1st today')
20 |
21 | d = spacetime.tomorrow('Etc/GMT')
22 | t.equal(d.format('nice-short'), 'Apr 2nd, 12:00am', 'its april 2nd tomorrow')
23 |
24 | d = spacetime.yesterday('Etc/GMT')
25 | t.equal(d.format('nice-short'), 'Mar 31st, 12:00am', 'its march 31st yesterday')
26 | tk.reset()
27 | t.end()
28 | })
29 |
30 | test('epoch-input', (t) => {
31 | const gmt420 = 1554092400000 // 4:20, april 1st 2019 GMT
32 | const time = new Date(gmt420)
33 | tk.travel(time)
34 |
35 | let moved = spacetime.now('Etc/GMT') //4:20
36 | moved = moved.goto('Canada/Eastern')
37 |
38 | const epoch = spacetime(gmt420, 'Canada/Eastern')
39 | t.equal(moved.format('nice-short'), epoch.format('nice-short'), 'epoch input moves with goto')
40 |
41 | const explicit = spacetime([2019, 3, 1, 0, 20], 'Canada/Eastern')
42 | t.ok(explicit.isSame(epoch, 'minute'), 'explicit inputs==epoch inputs')
43 |
44 | tk.reset()
45 | t.end()
46 | })
47 |
--------------------------------------------------------------------------------
/test/progress.test.js:
--------------------------------------------------------------------------------
1 | import test from 'tape'
2 | import spacetime from './lib/index.js'
3 |
4 | test('progress', (t) => {
5 | let d = spacetime('December 31, 1999 23:59:58', 'Canada/Eastern')
6 | let obj = d.progress()
7 | t.ok(obj.year > 0.95, 'almost-done-year')
8 | t.ok(obj.quarter > 0.9, 'almost-done-quarter')
9 | t.ok(obj.month > 0.9, 'almost-done-month')
10 | t.ok(obj.week > 0.7, 'almost-done-week') //friday
11 | t.ok(obj.day > 0.95, 'almost-done-day')
12 | t.ok(obj.quarterHour > 0.9, 'almost-done-hour')
13 | t.ok(obj.hour > 0.95, 'almost-done-hour')
14 | t.ok(obj.minute > 0.95, 'almost-done-minute')
15 |
16 | d = d.startOf('year')
17 | obj = d.progress()
18 | t.ok(obj.year <= 0.1, 'just-starting-year')
19 | t.ok(obj.month <= 0.1, 'just-starting-month')
20 | t.ok(obj.day <= 0.1, 'just-starting-day')
21 | t.ok(obj.hour <= 0.1, 'just-starting-hour')
22 | t.ok(obj.minute <= 0.1, 'just-starting-minute')
23 | t.end()
24 | })
25 |
26 | test('progress-param', (t) => {
27 | const s = spacetime('jan 2 2019', 'Canada/Eastern')
28 | t.equal(s.progress('year'), 0, 'start-year')
29 | t.equal(s.progress('month'), 0.03, 'early-month')
30 | t.end()
31 | })
32 |
--------------------------------------------------------------------------------
/test/query.test.js:
--------------------------------------------------------------------------------
1 | import test from 'tape'
2 | import spacetime from './lib/index.js'
3 |
4 | test('get', (t) => {
5 | const s = spacetime('February 22, 2017 15:30:00', 'Canada/Eastern')
6 | t.equal(s.date(), 22, '.date()')
7 | t.equal(s.year(), 2017, '.year()')
8 | t.equal(s.quarter(), 1, '.quarter()')
9 | t.equal(s.hour(), 15, '.hour()')
10 | t.equal(s.ampm(), 'pm', '.ampm()')
11 | t.equal(s.hourFloat(), 15.5, '.hourFloat()')
12 | t.equal(s.minute(), 30, '.minute()')
13 | t.equal(s.season(), 'winter', '.season()')
14 | t.equal(s.monthName(), 'february', '.month()')
15 | t.equal(s.dayName(), 'wednesday', '.day()')
16 | t.end()
17 | })
18 |
19 | test('get-quarters', (t) => {
20 | let s = spacetime('January 22, 2017 15:42:00', 'Canada/Eastern')
21 | t.equal(s.quarter(), 1, '.quarter()')
22 |
23 | s = s.month(1)
24 | t.equal(s.quarter(), 1, '.quarter()')
25 |
26 | s = s.month('march')
27 | t.equal(s.quarter(), 1, '.quarter()')
28 |
29 | s = s.month(3)
30 | t.equal(s.quarter(), 2, '.quarter()')
31 |
32 | s = s.month('december')
33 | t.equal(s.quarter(), 4, '.quarter()')
34 | t.end()
35 | })
36 |
37 | test('get-weeks', (t) => {
38 | let s = spacetime('January 1, 2015 2:00:00', 'Canada/Eastern')
39 | t.equal(s.week(), 1, '.weeks()1')
40 | s = s.month('december').date(29)
41 | t.equal(s.week(), 52, '.weeks()3')
42 | t.end()
43 | })
44 |
45 | test('day-of-year', (t) => {
46 | let s = spacetime('January 5, 2017 2:00:00', 'Canada/Eastern')
47 | t.equal(s.ampm(), 'am', '.date()')
48 | t.equal(s.date(), 5, '.date()')
49 | t.equal(s.dayOfYear(), 5, 'jan-5th()')
50 |
51 | s = spacetime('February 1, 2017 2:00:00', 'Canada/Eastern')
52 | t.equal(s.dayOfYear(), 32, 'feb 1()')
53 |
54 | s = spacetime('February 11, 2017 2:00:00', 'Canada/Eastern')
55 | t.equal(s.dayOfYear(), 42, 'feb 1()')
56 |
57 | //after feb29th, there could be a leapyear
58 | // s = spacetime('December 31, 2017 2:00:00', 'Canada/Eastern');
59 | // t.equal(s.dayOfYear(), 364, 'December 31()');
60 |
61 | t.end()
62 | })
63 |
--------------------------------------------------------------------------------
/test/season.test.js:
--------------------------------------------------------------------------------
1 | import test from 'tape'
2 | import spacetime from './lib/index.js'
3 |
4 | const south = [
5 | 'Africa/Johannesburg',
6 | 'Brazil/Acre',
7 | 'Australia/Canberra',
8 | 'Asia/Jakarta',
9 | 'America/Argentina',
10 | 'Africa/Lusaka'
11 | ]
12 | const north = [
13 | 'America/Detroit',
14 | 'Mexico/BajaSur',
15 | 'Canada/Eastern',
16 | 'Europe/Oslo',
17 | 'Asia/Baghdad',
18 | 'Asia/Istanbul'
19 | ]
20 |
21 | test('season-by-hemisphere', (t) => {
22 | //june
23 | let s = spacetime('june 6 2017', 'Canada/Eastern')
24 | south.forEach((tz) => {
25 | s = s.goto(tz)
26 | t.equal(s.season(), 'winter', tz + ' june-winter')
27 | })
28 | north.forEach((tz) => {
29 | s = s.goto(tz)
30 | t.equal(s.season(), 'summer', tz + ' june-summer')
31 | })
32 | t.end()
33 | })
34 |
35 | test('set season - north', (t) => {
36 | let s = spacetime('winter', 'Canada/Eastern')
37 | t.equal(s.monthName(), 'december', 'winter .month()')
38 | t.equal(s.date(), 1, 'q1 .date()')
39 |
40 | s = spacetime('spring', 'Canada/Eastern')
41 | t.equal(s.monthName(), 'march', 'spring .month()')
42 | t.equal(s.date(), 1, 'spring .date()')
43 |
44 | s = spacetime('summer', 'Canada/Eastern')
45 | t.equal(s.monthName(), 'june', 'summer .month()')
46 | t.equal(s.date(), 1, 'summer .date()')
47 |
48 | s = spacetime('fall', 'Canada/Eastern')
49 | t.equal(s.monthName(), 'september', 'fall .month()')
50 | t.equal(s.date(), 1, 'fall .date()')
51 |
52 | s = spacetime('fall 2001', 'Canada/Eastern')
53 | t.equal(s.monthName(), 'september', 'fall year .month()')
54 | t.equal(s.date(), 1, 'fall year .date()')
55 | t.equal(s.year(), 2001, 'fall .year()')
56 |
57 | s = spacetime('fall of 1960', 'Canada/Eastern')
58 | t.equal(s.monthName(), 'september', 'fall of year .month()')
59 | t.equal(s.date(), 1, 'fall of year .date()')
60 | t.equal(s.year(), 1960, 'fall of .year()')
61 |
62 | t.end()
63 | })
64 |
65 | test('season - south', (t) => {
66 | let s = spacetime('nov 11 2022', 'australia/adelaide')
67 | t.equal(s.season(), 'spring', 'south-spring')
68 | s = s.add(4, 'weeks')
69 | t.equal(s.season(), 'summer', 'south-summer')
70 | t.end()
71 | })
72 |
--------------------------------------------------------------------------------
/test/semi-destructive.test.js:
--------------------------------------------------------------------------------
1 | import test from 'tape'
2 | import spacetime from './lib/index.js'
3 |
4 | test('non-destructive', (t) => {
5 | let s = spacetime([2017, 5, 25])
6 | s = s.seconds(5)
7 | s = s.year(2025)
8 | t.equal(s.date(), 25, 'init-date')
9 | t.equal(s.seconds(), 5, 'still-5-seconds')
10 |
11 | //but this method 0's-out things:
12 | s = s.quarter('q2')
13 | t.equal(s.date(), 1, 'moved-date')
14 | t.equal(s.seconds(), 0, 'now-not-5-seconds')
15 | t.end()
16 | })
17 |
18 | test('semi-destructive', (t) => {
19 | let s = spacetime('June 12, 2017 20:01:00', 'Australia/Brisbane')
20 | t.equal(s.date(), 12, 'date-init')
21 | s = s.month('march')
22 | t.equal(s.monthName(), 'march', 'now-march')
23 | t.equal(s.date(), 12, 'still-12th')
24 |
25 | s = spacetime('June 30, 2017 20:01:00', 'Australia/Brisbane')
26 | t.equal(s.date(), 30, 'date-init')
27 | s = s.month('february')
28 | t.equal(s.monthName(), 'february', 'now-february')
29 | //close-as-possible
30 | t.equal(s.date(), 28, 'now-28th')
31 |
32 | t.end()
33 | })
34 |
--------------------------------------------------------------------------------
/test/swapTz.test.js:
--------------------------------------------------------------------------------
1 | import test from 'tape'
2 | import spacetime from './lib/index.js'
3 |
4 | test('swapTz', (t) => {
5 | const arr = [
6 | 'Africa/Dar_es_Salaam',
7 | 'Africa/Porto-Novo',
8 | 'America/Blanc-Sablon',
9 | 'America/Port-au-Prince',
10 | 'America/Port_of_Spain',
11 | 'Europe/Isle_of_Man',
12 | 'Antarctica/DumontDUrville',
13 | 'Antarctica/McMurdo',
14 | 'Asia/Ust-Nera',
15 | 'Europe/Zagreb',
16 | 'America/Bahia_Banderas',
17 | 'Asia/Kuching',
18 | 'Etc/GMT+7',
19 | ]
20 | let s = spacetime('2011-12-03T10:15:30', 'america/montreal')
21 | t.equal(s.time(), '10:15am', 'first-time')
22 | arr.forEach(tz => {
23 | s = s.timezone(tz)
24 | t.equal(s.timezone().name, tz, 'swapped tz ', tz)
25 | t.equal(s.time(), '10:15am', 'swap time ' + tz)
26 | })
27 | t.end()
28 | })
29 |
--------------------------------------------------------------------------------
/test/timezone-name.test.js:
--------------------------------------------------------------------------------
1 | import test from 'tape'
2 | import spacetime from './lib/index.js'
3 |
4 | test('titlecase', (t) => {
5 | const arr = [
6 | 'Africa/Dar_es_Salaam',
7 | 'Africa/Porto-Novo',
8 | 'America/Blanc-Sablon',
9 | 'America/Port-au-Prince',
10 | 'America/Port_of_Spain',
11 | 'Europe/Isle_of_Man',
12 | 'Antarctica/DumontDUrville',
13 | 'Antarctica/McMurdo',
14 | 'Asia/Ust-Nera',
15 | 'Europe/Zagreb',
16 | 'America/Bahia_Banderas',
17 | 'Asia/Kuching',
18 | 'Etc/GMT+7',
19 | ]
20 | arr.forEach(tz => {
21 | const s = spacetime.now(tz)
22 | t.equal(s.timezone().name, tz, tz)
23 | })
24 | t.end()
25 | })
26 |
--------------------------------------------------------------------------------
/test/toNativeDate.test.js:
--------------------------------------------------------------------------------
1 | import test from 'tape'
2 | import spacetime from './lib/index.js'
3 |
4 | test('toNativeDate-is-epoch', (t) => {
5 | let d = spacetime(1554092400000, 'Australia/Brisbane') // 4:20, april 1st 2019 GMT
6 | d = d.hour('3').minute('14')
7 |
8 | const localDate = d.toNativeDate()
9 | const localDateSeconds = localDate.getTime()
10 |
11 | t.equal(localDateSeconds, d.epoch, 'toNativeDate is not epoch')
12 | t.end()
13 | })
14 |
--------------------------------------------------------------------------------
/test/types/index.ts:
--------------------------------------------------------------------------------
1 | import { default as test } from 'tape'
2 | import { spacetime } from './spacetime-static'
3 |
4 | test('typefile smoketest', (t: test.Test) => {
5 | t.ok(spacetime, 'import works')
6 | const d = spacetime('June 5th 2019')
7 | t.equal(d.format('iso-short'), '2019-06-05', 'basic-smoketest')
8 | t.end()
9 | })
10 |
11 | // Add reference to the other files so they included in the test build
12 | import './constructor.test'
13 | import './types.test'
14 |
--------------------------------------------------------------------------------
/test/types/spacetime-static.ts:
--------------------------------------------------------------------------------
1 | import { default as spacetimejs } from '../../builds/spacetime.cjs'
2 | import { SpacetimeStatic } from '../../types/constructors'
3 |
4 | export const spacetime: SpacetimeStatic = spacetimejs
5 |
--------------------------------------------------------------------------------
/test/types/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "outDir": "dist",
4 | "target": "es5",
5 | "esModuleInterop": true,
6 | "module": "commonjs",
7 | "lib": [],
8 | "allowJs": true,
9 | "checkJs": true,
10 | "declaration": true,
11 | "sourceMap": false,
12 | "importHelpers": true,
13 | "downlevelIteration": true,
14 | "isolatedModules": true,
15 | "strict": true,
16 | "noImplicitAny": true,
17 | "strictNullChecks": true,
18 | "noImplicitThis": true,
19 | "alwaysStrict": true,
20 | "noUnusedLocals": true,
21 | "noUnusedParameters": true,
22 | "noImplicitReturns": true,
23 | "noFallthroughCasesInSwitch": true,
24 | "moduleResolution": "node",
25 | "allowSyntheticDefaultImports": true
26 | }
27 | }
--------------------------------------------------------------------------------
/test/types/types.test.ts:
--------------------------------------------------------------------------------
1 | import { default as test } from 'tape'
2 | import { spacetime } from './spacetime-static'
3 |
4 | test('Spacetime base properties exist', (t: test.Test) => {
5 | const obj = spacetime.now()
6 |
7 | t.ok(obj.d instanceof Date, '.d is a date')
8 | t.equal(typeof obj.epoch, 'number', '.epoch is a number')
9 | t.equal(typeof obj.silent, 'boolean', '.silent is a boolean')
10 | t.equal(typeof obj.tz, 'string', '.tz is a string')
11 | t.ok(obj.timezones != undefined, '.timezones exists')
12 | t.end()
13 | })
14 |
--------------------------------------------------------------------------------
/test/utcOffset.test.js:
--------------------------------------------------------------------------------
1 | import test from 'tape'
2 | import spacetime from './lib/index.js'
3 |
4 | //https://en.wikipedia.org/wiki/ISO_8601
5 | //the zone designator would be "+01:00", "+0100", or simply "+01"
6 | test('set-offset-from-ISO-8601', (t) => {
7 | const defaultTz = 'Canada/Eastern'
8 | const arr = [
9 | ['2017-04-03T08:00:00', defaultTz],
10 | ['2017-04-03T08:00:00-0700', 'Etc/GMT+7'],
11 | ['2017-04-03T08:00:00-1000', 'Etc/GMT+10'],
12 | ['2017-04-03T08:00:00+0700', 'Etc/GMT-7'],
13 | ['2017-10-03T08:00:00+0000', 'Etc/GMT'],
14 | // ['2017-04-03T08:00:00-0500', defaultTz], //the same
15 | ['2017-05-03T13:00:00+0500', 'Etc/GMT-5'],
16 | ['2017-04-02T08:00:00-10:00', 'Etc/GMT+10'],
17 | ['2017-04-11T02:00:00+10:00', 'Etc/GMT-10'],
18 | ['2017-04-02T08:00:00-01:00', 'Etc/GMT+1'],
19 | ['2017-04-11T02:00:00+01:00', 'Etc/GMT-1'],
20 | ['2018-04-10T08:00:00-03', 'Etc/GMT+3'],
21 | ['2017-04-03T12:00:00+03', 'Etc/GMT-3'],
22 | ['2017-04-03T01:00:00+00', 'Etc/GMT'],
23 | ['2017-04-03T01:00:00Z', 'Etc/GMT'],
24 | ['2019-02-22T20:00:00+05:30', 'Etc/GMT-5.5'],
25 | ['2019-02-22T01:00:00+0530', 'Etc/GMT-5.5']
26 | ]
27 | arr.forEach((a) => {
28 | const s = spacetime(a[0], defaultTz)
29 | t.equal(s.timezone().name, a[1], a[0])
30 | })
31 |
32 | t.end()
33 | })
34 |
35 | test('offset-should-be-consistant', (t) => {
36 | const s = spacetime('2019-03-13T18:00:00.000-05:00')
37 | t.equal(s.format('iso').slice(-6), '-05:00')
38 | t.end()
39 | })
40 |
--------------------------------------------------------------------------------
/test/validation.test.js:
--------------------------------------------------------------------------------
1 | import test from 'tape'
2 | import spacetime from './lib/index.js'
3 |
4 | test('large date numbers', function (t) {
5 | let d = spacetime([2019, 'february'])
6 | d = d.date(30)
7 | t.equal(d.date(), 28, 'feb is <= 28')
8 |
9 | d = spacetime([2019, 'june'])
10 | d = d.date(300)
11 | t.equal(d.date(), 30, 'june is <= 30')
12 |
13 | d = spacetime([2022, 'december', 900])
14 | t.equal(d.date(), 31, 'dec is <= 31')
15 | t.end()
16 | })
17 |
18 | test('small date numbers', function (t) {
19 | let d = spacetime([2019, 'february'])
20 | d = d.date(0)
21 | t.equal(d.date(), 1, 'date is >= 1')
22 |
23 | d = d.date(-10)
24 | t.equal(d.date(), 1, 'date is still >= 1')
25 |
26 | d = spacetime([2022, 'december', 0])
27 | t.equal(d.date(), 1, 'dec is >= 1')
28 |
29 | t.end()
30 | })
31 |
32 | test('large month numbers', function (t) {
33 | let d = spacetime([2019])
34 | d = d.month(14)
35 | t.equal(d.monthName(), 'december', 'month is <= december')
36 |
37 | d = spacetime([2019])
38 | d = d.month(-14)
39 | t.equal(d.monthName(), 'january', 'month is >= january')
40 |
41 | d = spacetime([2019, 13, 5])
42 | t.equal(d.monthName(), 'december', 'array-set month is <= december')
43 | t.equal(d.date(), 5, 'date is still valid')
44 | t.end()
45 | })
46 |
--------------------------------------------------------------------------------
/types/constraints.d.ts:
--------------------------------------------------------------------------------
1 | export type TimeUnit =
2 | | 'millisecond'
3 | | 'second'
4 | | 'minute'
5 | | 'quarterHour'
6 | | 'hour'
7 | | 'day'
8 | | 'week'
9 | | 'month'
10 | | 'quarter'
11 | | 'season'
12 | | 'year'
13 | | 'decade'
14 | | 'century'
15 | | 'date'
16 | | 'milliseconds' //plural forms
17 | | 'seconds'
18 | | 'minutes'
19 | | 'quarterHours'
20 | | 'hours'
21 | | 'days'
22 | | 'weeks'
23 | | 'months'
24 | | 'quarters'
25 | | 'seasons'
26 | | 'years'
27 | | 'decades'
28 | | 'centuries'
29 | | 'dates'
30 |
31 | export type Format =
32 | | 'day'
33 | | 'day-short'
34 | | 'day-number'
35 | | 'day-ordinal'
36 | | 'day-pad'
37 | | 'date'
38 | | 'date-ordinal'
39 | | 'date-pad'
40 | | 'month'
41 | | 'month-short'
42 | | 'month-number'
43 | | 'month-ordinal'
44 | | 'month-pad'
45 | | 'year'
46 | | 'year-short'
47 | | 'time'
48 | | 'time-24'
49 | | 'hour'
50 | | 'hour-pad'
51 | | 'hour-24'
52 | | 'hour-24-pad'
53 | | 'minute'
54 | | 'minute-pad'
55 | | 'second'
56 | | 'second-pad'
57 | | 'millisecond'
58 | | 'ampm'
59 | | 'quarter'
60 | | 'season'
61 | | 'era'
62 | | 'timezone'
63 | | 'offset'
64 | | 'numeric'
65 | | 'numeric-us'
66 | | 'numeric-uk'
67 | | 'mm/dd'
68 | | 'iso'
69 | | 'json'
70 | | 'iso-short'
71 | | 'iso-utc'
72 | | 'iso-full'
73 | | 'sql'
74 | | 'nice'
75 | | 'nice-year'
76 | | 'nice-day'
77 | | 'nice-full'
78 | | string
79 |
80 | export interface I18nOptions {
81 | /** Alternatives to Monday, Tuesday..*/
82 | days?: {
83 | short: string[]
84 | long: string[]
85 | }
86 | /** Alternatives to Jan, Feb..*/
87 | months?: {
88 | short: string[]
89 | long: string[]
90 | }
91 | /** Alternatives to am, pm*/
92 | ampm?: {
93 | am: string
94 | pm: string
95 | }
96 | /** Default dayname formatting */
97 | useTitleCase?: boolean
98 | }
99 |
--------------------------------------------------------------------------------
/types/index.d.cts:
--------------------------------------------------------------------------------
1 | import type { SpacetimeStatic } from './constructors'
2 |
3 | declare const spacetime: SpacetimeStatic
4 |
5 | // We need to use a single default export here so everything lines up with the actual imported object from JS
6 | export = spacetime
7 |
--------------------------------------------------------------------------------
/types/index.d.ts:
--------------------------------------------------------------------------------
1 | import type { SpacetimeStatic } from './constructors.js'
2 |
3 | declare const spacetime: SpacetimeStatic
4 |
5 | // We need to use a single default export here so everything lines up with the actual imported object from JS
6 | export default spacetime
7 |
8 |
9 | export { SpacetimeConstructor, SpacetimeConstructorOptions, SpacetimeStatic } from './constructors.js'
10 | export { Format, I18nOptions, TimeUnit } from './constraints.js'
11 | export { Spacetime, Diff, ParsableDate, Progress, Since, TimezoneMeta, TimezoneSet } from './types.js'
12 |
--------------------------------------------------------------------------------
/zonefile/_prefixes.js:
--------------------------------------------------------------------------------
1 | //prefixes for iana names..
2 | export default [
3 | 'africa',
4 | 'america',
5 | 'asia',
6 | 'atlantic',
7 | 'australia',
8 | 'brazil',
9 | 'canada',
10 | 'chile',
11 | 'europe',
12 | 'indian',
13 | 'mexico',
14 | 'pacific',
15 | 'antarctica',
16 | 'etc'
17 | ]
18 |
--------------------------------------------------------------------------------
/zonefile/pack.js:
--------------------------------------------------------------------------------
1 | //turn our timezone data into a small-as-possible string
2 | import { writeFileSync } from 'fs'
3 | import iana from './iana.js'
4 | import aliases from './aliases.js'
5 | import prefixes from './_prefixes.js'
6 | const all = {}
7 |
8 | // add aliases in
9 | Object.keys(aliases).forEach((k) => {
10 | const found = iana[aliases[k]]
11 | if (!found) {
12 | console.log('missing', aliases[k])
13 | }
14 | iana[k] = Object.assign({}, found)
15 | })
16 |
17 | //pack iana data into a [o|h] object
18 | Object.keys(iana).forEach((k) => {
19 | const o = iana[k]
20 | let key = o.offset + '|' + o.hem
21 | if (o.dst) {
22 | key += '|' + o.dst
23 | }
24 | all[key] = all[key] || []
25 | const name = k.replace(/(.*?)\//, (a, prefix) => {
26 | const index = prefixes.indexOf(prefix)
27 | if (index !== -1) {
28 | return index + '/'
29 | }
30 | return a
31 | })
32 | all[key].push(name)
33 | })
34 |
35 | //add-in informal abbreviations
36 | // all = addHemisphere(all, informal.south, 's')
37 | // all = addHemisphere(all, informal.north, 'n')
38 |
39 | let keys = Object.keys(all)
40 | keys = keys.sort((a, b) => (a < b ? 1 : -1))
41 | const result = {}
42 | keys.forEach((k) => {
43 | result[k] = all[k].join(',')
44 | })
45 |
46 | // console.log(result)
47 | writeFileSync('./zonefile/_build.js', 'export default ' + JSON.stringify(result, null, 2))
48 |
--------------------------------------------------------------------------------
/zonefile/unpack.js:
--------------------------------------------------------------------------------
1 | import data from './_build.js'
2 | import prefixes from './_prefixes.js'
3 |
4 | const all = {}
5 | Object.keys(data).forEach((k) => {
6 | const split = k.split('|')
7 | const obj = {
8 | offset: Number(split[0]),
9 | hem: split[1]
10 | }
11 | if (split[2]) {
12 | obj.dst = split[2]
13 | }
14 | const names = data[k].split(',')
15 | names.forEach((str) => {
16 | str = str.replace(/(^[0-9]+)\//, (before, num) => {
17 | num = Number(num)
18 | return prefixes[num] + '/'
19 | })
20 | all[str] = obj
21 | })
22 | })
23 |
24 | all.utc = {
25 | offset: 0,
26 | hem: 'n' //default to northern hemisphere - (sorry!)
27 | }
28 |
29 | //add etc/gmt+n
30 | for (let i = -14; i <= 14; i += 0.5) {
31 | let num = i
32 | if (num > 0) {
33 | num = '+' + num
34 | }
35 | let name = 'etc/gmt' + num
36 | all[name] = {
37 | offset: i * -1, //they're negative!
38 | hem: 'n' //(sorry)
39 | }
40 | name = 'utc/gmt' + num //this one too, why not.
41 | all[name] = {
42 | offset: i * -1,
43 | hem: 'n'
44 | }
45 | }
46 |
47 | export default all
48 |
--------------------------------------------------------------------------------