Compare commits
183 commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
108c796687 | ||
![]() |
43c1f3412b | ||
![]() |
dc2184ea06 | ||
![]() |
70bab7df95 | ||
![]() |
06347e7f31 | ||
![]() |
9d0fb6d895 | ||
![]() |
2072b49220 | ||
![]() |
cb873afb22 | ||
![]() |
c109e7969b | ||
![]() |
2a20cdea1e | ||
![]() |
ea203ec90d | ||
![]() |
5a847014c9 | ||
![]() |
794d03a5ee | ||
![]() |
817c14c46e | ||
![]() |
280f8e72f2 | ||
![]() |
5afd1335f1 | ||
![]() |
2ce94f82ce | ||
![]() |
5a352dc599 | ||
![]() |
357b5844eb | ||
![]() |
d87d7d8b6c | ||
![]() |
92e20c474f | ||
![]() |
c5971cdcf4 | ||
![]() |
6d6b6ec9bd | ||
![]() |
c789fca51f | ||
![]() |
d6a431362d | ||
![]() |
bbfef69d2e | ||
![]() |
3d72448491 | ||
![]() |
596b573775 | ||
![]() |
d0b6fc0011 | ||
![]() |
d159878f62 | ||
![]() |
69d7fdd626 | ||
![]() |
f49368d3fc | ||
![]() |
22b48f90de | ||
![]() |
0a7d808290 | ||
![]() |
2f3afd7dbb | ||
![]() |
68b462469f | ||
![]() |
1c405bbfc3 | ||
![]() |
accda0db1a | ||
![]() |
28847aa896 | ||
![]() |
a0b9ad0e5f | ||
![]() |
73d9706c7a | ||
![]() |
b8a5075026 | ||
![]() |
ef98ff4ea4 | ||
![]() |
08a98032e2 | ||
![]() |
a443d27c79 | ||
![]() |
ed00a606f3 | ||
![]() |
3448a6f5e0 | ||
![]() |
b849ebde02 | ||
![]() |
a51edf2e3e | ||
![]() |
7314545b34 | ||
![]() |
fa59231e1e | ||
![]() |
67ee655bbb | ||
![]() |
1cccb8392a | ||
![]() |
628c87557a | ||
![]() |
dd38e5cf98 | ||
![]() |
99e55979dc | ||
![]() |
4c91fc4c01 | ||
![]() |
31e123d9f1 | ||
![]() |
bab222aeb4 | ||
![]() |
fc247e296b | ||
![]() |
3e2688cbec | ||
![]() |
aa114b1ef8 | ||
![]() |
c42272a9eb | ||
![]() |
6fadc548d2 | ||
![]() |
d7267a7d67 | ||
![]() |
ef5c9cfbb6 | ||
![]() |
0d5cd470e6 | ||
![]() |
af9cb4dc4b | ||
![]() |
78bb08c4f8 | ||
![]() |
5a3929bcea | ||
![]() |
89555d25ef | ||
![]() |
fd493ecf3d | ||
![]() |
b193876305 | ||
![]() |
10d56c2e04 | ||
![]() |
7230f747e7 | ||
![]() |
2a85d5d92f | ||
![]() |
2dfcc52dd5 | ||
![]() |
366bc5e8d5 | ||
![]() |
4d42f5eddf | ||
![]() |
e413fcbf56 | ||
![]() |
220883945a | ||
![]() |
66e9dc6ae9 | ||
![]() |
7ed65a0af1 | ||
![]() |
8671750e66 | ||
![]() |
795b3d8961 | ||
![]() |
b550391a4e | ||
![]() |
6d88edea06 | ||
![]() |
ff6b7139ca | ||
![]() |
bb934b522f | ||
![]() |
3564470578 | ||
![]() |
1ea2cadb1a | ||
![]() |
41b313d425 | ||
![]() |
5d73286e92 | ||
![]() |
5e43673c34 | ||
![]() |
6036208bb5 | ||
![]() |
bf7f84590f | ||
![]() |
1f7f5d9f69 | ||
![]() |
f39b4edb92 | ||
![]() |
4cd1a41cb4 | ||
![]() |
569c7db56b | ||
![]() |
69967d71ff | ||
![]() |
cc9dee4e08 | ||
![]() |
d0ee71a0a7 | ||
![]() |
43f6a78870 | ||
![]() |
2eb0f88c61 | ||
![]() |
7910de376e | ||
![]() |
2a053253fb | ||
![]() |
2d40869c1a | ||
![]() |
0bf12290ec | ||
![]() |
63a34f1c68 | ||
![]() |
37f8bf48e9 | ||
![]() |
d73a31e664 | ||
![]() |
6ca6037f29 | ||
![]() |
792245bb26 | ||
![]() |
faeadb732a | ||
![]() |
c685f4dc12 | ||
![]() |
ef2075d475 | ||
![]() |
647e26db15 | ||
![]() |
b1387c08c0 | ||
![]() |
c4c20db815 | ||
![]() |
a504a6adfc | ||
![]() |
8427b7bdc6 | ||
![]() |
48e8523e39 | ||
![]() |
52770185f9 | ||
![]() |
a443d476ac | ||
![]() |
7ab98975d8 | ||
![]() |
15ded5317f | ||
![]() |
80b3a88530 | ||
![]() |
d6ec97246f | ||
![]() |
b71173dc65 | ||
![]() |
0e0eb4186f | ||
![]() |
0b963146fe | ||
![]() |
6dbeefc99b | ||
![]() |
02734c103b | ||
![]() |
5f1f4a7785 | ||
![]() |
5387ec8f02 | ||
![]() |
4f19dcf5ec | ||
![]() |
c3b490ec52 | ||
![]() |
644ed69cda | ||
![]() |
eeb797e1f8 | ||
![]() |
6d7a031d46 | ||
![]() |
5fbbf93d4d | ||
![]() |
bb33398614 | ||
![]() |
cd1faf8631 | ||
![]() |
b4c3d4a302 | ||
![]() |
c750c339ff | ||
![]() |
91a502550a | ||
![]() |
1e5bc5dfc5 | ||
![]() |
80a856721d | ||
![]() |
ff4a9a6eee | ||
![]() |
e7bf41e3ad | ||
![]() |
aeed7641da | ||
![]() |
72cececf88 | ||
![]() |
928c4e9be4 | ||
![]() |
58f75fc966 | ||
![]() |
8cd386bb40 | ||
![]() |
fb63b7a318 | ||
![]() |
df55df9db6 | ||
![]() |
21a9fc1618 | ||
![]() |
4cc45533ce | ||
![]() |
daa04395f2 | ||
![]() |
5cb5612857 | ||
![]() |
4135dfa8e3 | ||
![]() |
58a92482d1 | ||
![]() |
6f9ca7d13c | ||
![]() |
328b2e0b32 | ||
![]() |
5139108fcc | ||
![]() |
13bab9fc8e | ||
![]() |
4a98e54d9d | ||
![]() |
7384819edb | ||
![]() |
ef659da9a4 | ||
![]() |
a32086d865 | ||
![]() |
6183b60e96 | ||
![]() |
d8b08ae3ba | ||
![]() |
65ff159411 | ||
![]() |
25442e0f74 | ||
![]() |
b8d13ddbb4 | ||
![]() |
3ffaa2a060 | ||
![]() |
f65cb65d07 | ||
![]() |
322daab8ff | ||
![]() |
8e69e5785b | ||
![]() |
0770b0a5da | ||
![]() |
97ad84deb8 |
17 changed files with 1903 additions and 349 deletions
47
.github/workflows/tests-workflow.yml
vendored
Normal file
47
.github/workflows/tests-workflow.yml
vendored
Normal file
|
@ -0,0 +1,47 @@
|
|||
name: qdate tests and dialyzer
|
||||
on: push
|
||||
|
||||
jobs:
|
||||
linux:
|
||||
name: OTP ${{ matrix.otp_version }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
continue-on-error: true
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-22.04
|
||||
otp_version: '27.x'
|
||||
rebar3_version: "3.24.0"
|
||||
- os: ubuntu-22.04
|
||||
otp_version: '27.x'
|
||||
rebar3_version: "3.23.0"
|
||||
- os: ubuntu-22.04
|
||||
otp_version: '26.x'
|
||||
rebar3_version: "3.22.1"
|
||||
- os: ubuntu-22.04
|
||||
otp_version: '25.x'
|
||||
rebar3_version: "3.22.1"
|
||||
- os: ubuntu-22.04
|
||||
otp_version: '24.x'
|
||||
rebar3_version: "3.22.1"
|
||||
- os: ubuntu-20.04
|
||||
otp_version: '23.x'
|
||||
rebar3_version: "3.19.0"
|
||||
|
||||
steps:
|
||||
- name: Install OTP ${{matrix.otp_version}}
|
||||
uses: erlef/setup-beam@v1
|
||||
with:
|
||||
version-type: loose
|
||||
otp-version: ${{ matrix.otp_version}}
|
||||
rebar3-version: ${{ matrix.rebar3_version}}
|
||||
|
||||
- name: Checkout qdate
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Run Tests
|
||||
run: make test
|
||||
|
||||
- name: Run Dialyzer
|
||||
run: make dialyzer
|
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -1,6 +1,10 @@
|
|||
*~
|
||||
*.beam
|
||||
*.sw?
|
||||
*.iml
|
||||
deps/
|
||||
ebin/
|
||||
.eunit/
|
||||
.idea/
|
||||
_build
|
||||
doc/
|
||||
|
|
10
.travis.yml
10
.travis.yml
|
@ -1,10 +0,0 @@
|
|||
language: erlang
|
||||
script: "make test"
|
||||
otp_release:
|
||||
- R16B
|
||||
- R15B02
|
||||
- R15B01
|
||||
- R15B
|
||||
- R14B03
|
||||
- R14B02
|
||||
before_script: "sudo apt-get --yes --force-yes install libpam0g-dev"
|
87
CHANGELOG.markdown
Normal file
87
CHANGELOG.markdown
Normal file
|
@ -0,0 +1,87 @@
|
|||
## 0.7.3
|
||||
|
||||
* Remove the `?else` macro.
|
||||
|
||||
## 0.7.2
|
||||
|
||||
* Update the error message when qdate is not started with some better instructions.
|
||||
* Some minor updates for hex.pm
|
||||
* Removed the `erlnow()` warning
|
||||
* Fix some typos in the documentation
|
||||
* Update the makefile to use [rebar3.mk](https://rebar3.mk)
|
||||
* Skipped 0.7.1 only because there was a partially tagged 0.7.1 for a while,
|
||||
but was never published to hex. Just to ensure upgrades are easier, I just skipped
|
||||
a proper 0.7.1 release
|
||||
|
||||
## 0.7.0
|
||||
|
||||
* Re-introduce the qdate server for storing qdate timezones, formats, and parsers,
|
||||
rather than overloading the `application` env vars (since the `application`
|
||||
module only wants keys to be atoms).
|
||||
* Convert to using `qdate_localtime` 1.2.0 (which passes dialyzer checks)
|
||||
* `qdate` is passing dialyzer again
|
||||
|
||||
## 0.6.0
|
||||
|
||||
* Add `age` and `age_days` functions
|
||||
* Add option to preserve millisecond accuracy in date parsing and formatting (@Leonardb)
|
||||
|
||||
|
||||
## 0.5.0
|
||||
|
||||
* Add `range_X` functions for getting a list of dates/times within a range
|
||||
(such as `range_day/3` to get a range of days between a start and end date.
|
||||
* Add `beginning_X` functions to return the beginning of the provided precision
|
||||
(minute, hour, day, week, month, or year)
|
||||
+ Add `end_X` functions to return the last second of each time period (this is
|
||||
just the opposite of `beginning_X`)
|
||||
* Add `between/[2,3,5]` functions for computing whether a date/time is between
|
||||
two others.
|
||||
* Update to rebar3 and add hex compatibility. (@Licenser)
|
||||
* Properly add dependent apps to .app.src (@Licenser)
|
||||
* Add an optional "relative date/time parser".
|
||||
* Fix: Ensure `get_timezone()` returns the default timezone (from config) if it
|
||||
hasn't been set by `get_timezone()`
|
||||
* Fix UTC/GMT bug (@loudferret)
|
||||
* Fix Erlang 21 Stacktrace changes (@tnt-dev)
|
||||
* Set a better rebar2 version of erlware commons (@tnt-dev)
|
||||
|
||||
## 0.4.2
|
||||
|
||||
* Add partial support for `ec_date`'s 4-tuple subsecond accuracy time format.
|
||||
* Fix `erlware_commons` dependency to a rebar2-compatible version.
|
||||
|
||||
## 0.4.1
|
||||
|
||||
* Remove unnecessary `io:format` call.
|
||||
|
||||
## 0.4.0
|
||||
|
||||
* Remove dependency on a running server for tracking application state.
|
||||
Instead, parsers and formats are registered to the application environment
|
||||
vars (e.g. `application:get_env`), and timezones are registered to the
|
||||
application environment or the process dictionary. A side-effect of this
|
||||
change is that you can no longer query another process's timezone.
|
||||
* Add basic date arithmetic (e.g. `qdate:add_hours/[1-2]`, etc).
|
||||
* Add `get_formats()` and `get_parsers()` to see list of registered formats and
|
||||
parsers.
|
||||
* Fix bug related to relying on the application environment variable
|
||||
`default_timezone`
|
||||
|
||||
## 0.3.0
|
||||
|
||||
* Add Timezone/Daylight Saving Disambiguation
|
||||
* Add the `auto` timezone shortcut
|
||||
* Fix rebar.config to allow for compilation on Erlang 17
|
||||
|
||||
## 0.2.1
|
||||
|
||||
* Fix allowing timezone names to be binary
|
||||
|
||||
## 0.2.0
|
||||
|
||||
* Adding `qdate:compare/2,3` for easily comparing dates
|
||||
|
||||
## 0.1.0
|
||||
|
||||
* Initial Release
|
46
Makefile
46
Makefile
|
@ -1,13 +1,41 @@
|
|||
all: get-deps compile
|
||||
all: compile
|
||||
|
||||
get-deps:
|
||||
./rebar get-deps
|
||||
# Check if rebar3.mk exists, and if not, download it
|
||||
ifeq ("$(wildcard rebar3.mk)","")
|
||||
$(shell curl -O https://raw.githubusercontent.com/choptastic/rebar3.mk/master/rebar3.mk)
|
||||
endif
|
||||
|
||||
compile:
|
||||
./rebar compile
|
||||
# rebar3.mk adds a new rebar3 rule to your Makefile
|
||||
# (see https://github.com/choptastic/rebar3.mk) for full info
|
||||
include rebar3.mk
|
||||
|
||||
test: get-deps compile
|
||||
./rebar skip_deps=true eunit
|
||||
compile: rebar3
|
||||
$(REBAR) compile
|
||||
|
||||
update: rebar3
|
||||
$(REBAR) update
|
||||
|
||||
test:
|
||||
EUNIT=1 $(REBAR) compile
|
||||
EUNIT=1 $(REBAR) eunit
|
||||
|
||||
dialyzer: compile
|
||||
DIALYZER=1 $(REBAR) dialyzer
|
||||
|
||||
dev:
|
||||
mkdir -p _checkouts
|
||||
cd _checkouts; git clone https://github.com/choptastic/qdate_localtime
|
||||
|
||||
|
||||
run: rebar3
|
||||
$(REBAR) shell
|
||||
|
||||
push_tags:
|
||||
git push --tag
|
||||
|
||||
pull_tags:
|
||||
git pull --tag
|
||||
|
||||
publish: rebar3 pull_tags
|
||||
$(REBAR) hex publish
|
||||
|
||||
run:
|
||||
erl -pa ebin/ deps/*/ebin/ -eval "application:start(qdate)"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# qdate - A Wrapper for Erlang Date and Timezone Management
|
||||
# qdate - Erlang Date and Timezone Library
|
||||
|
||||
[](https://travis-ci.org/choptastic/qdate)
|
||||
[](https://github.com/choptastic/qdate/actions/workflows/tests-workflow.yml)
|
||||
|
||||
## Purpose
|
||||
|
||||
|
@ -21,12 +21,15 @@ benefits of `ec_date` and `erlang_localtime`, as well as extending the
|
|||
capabilities of both to provide for other needed tools found in a single
|
||||
module.
|
||||
|
||||
`qdate` will provide, under the roof of a single module date and time formatting
|
||||
and parsing from and into:
|
||||
`qdate` provides date and time formatting and parsing from and into:
|
||||
+ Formatting Strings
|
||||
+ Erlang Date Format
|
||||
+ Erlang Now Format
|
||||
+ Unixtime integers
|
||||
+ Timezones
|
||||
|
||||
And all this while dealing with timezone parsing, formatting, conversion
|
||||
and overall management.
|
||||
|
||||
#### Acceptable Date Formats
|
||||
|
||||
|
@ -65,10 +68,15 @@ T, Z, r, and c), `qdate` will handle them for us.
|
|||
+ `to_now(Date)` - converts any date/time format to Erlang now format.
|
||||
+ `to_unixtime(Date)` - converts any date/time format to a unixtime integer
|
||||
|
||||
A **ToTimezone** value of the atom `auto` will automatically determine the
|
||||
timezone. For example, `to_date(Date, auto)` is exactly the same as
|
||||
`to_date(Date)`
|
||||
|
||||
**A Note About Argument Order**: In all cases, `ToTimezone` is optional and if
|
||||
omitted, will be determined as described below in "Understanding Timezone
|
||||
Determining and Conversion". If `ToTimezone` is specified, it will always be
|
||||
immediately left of the `Date` argument. `Date` will always be the last
|
||||
immediately left of the `Disambiguate` argument (if it's specified), which is
|
||||
always immediately left of `Date` argument. `Date` will always be the last
|
||||
argument to any of the conversion and formatting functions.
|
||||
|
||||
#### Understanding Timezone Determining and Conversions
|
||||
|
@ -90,9 +98,88 @@ will infer the timezone in the following order.
|
|||
`set_timezone/1` only applies to that *specific* process. If none is
|
||||
specified.
|
||||
+ If no timezone is specified for the process, `qdate` looks at the `qdate`
|
||||
application variable `default_timezone`.
|
||||
application variable `default_timezone`. `default_timezone` can be either a
|
||||
hard-specified timezone, or a `{Module, Function}` tuple. The tuple format
|
||||
should return either a timezone or the atom `undefined`.
|
||||
+ If no timezone is specified by either of the above, `qdate` assumes "GMT"
|
||||
for all dates.
|
||||
+ A timezone value of `auto` will act as if no timezone is specified.
|
||||
|
||||
#### Disambiguating Ambiguous Timezone Conversions
|
||||
|
||||
Sometimes, when you're converting a datetime from one timezone to another, there
|
||||
are potentially two different results if the conversion happens to land on in a
|
||||
timezone that's in the middle of a Daylight Saving conversion. For example,
|
||||
converting "11-Nov-2013 1:00:am" in "America/New York" to "GMT" could be both
|
||||
"5am" and "6am" in GMT, since "1am EST". This is a side effect of the
|
||||
"intelligence" of `qdate` - `qdate` would notice that 1am in New York is EST,
|
||||
and should be converted to "1am EST", and then do the conversion from "1am EST"
|
||||
to "GMT". This can lead to confusion.
|
||||
|
||||
Further, since `qdate` attempts to be "smart" about mistakenly entered
|
||||
timezones (ie, if you entered "2013-01-01 EDT", `qdate` knows that "EDT"
|
||||
(Eastern Daylight Time) doesn't apply to January first, so it *assumes* you
|
||||
meant "EST".
|
||||
|
||||
**THE SOLUTION** to this tangled mess that we call Daylight Saving Time is to
|
||||
provide an option to disambiguate if you so desire. By default disambiguation
|
||||
is disabled, and `qdate` will just guess as to it's best choice. But if you so
|
||||
desire, you can make sure qdate does *both* conversions, and returns both.
|
||||
|
||||
You can do this by passing a `Disambiguation` argument to `to_string`,
|
||||
`to_date`, `to_unixtime`, and `to_now`. `Disambiguation` can be an atom of the
|
||||
values:
|
||||
|
||||
+ `prefer_standard` *(Default Behavior)*: If an ambiguous result occurs,
|
||||
qdate will return the date in standard time rather than daylight time.
|
||||
+ `prefer_daylight`: If an ambiguous result occurs, qdate will return the
|
||||
preferred daylight time.
|
||||
+ `both`: If an ambiguous result occurs, `qdate` will return the tuple:
|
||||
`{ambiguous, DateStandard, DateDaylight}`, where `DateStandard` is the date
|
||||
in Standard Time, and `DateDaylight` is the date in Daylight Saving Time.
|
||||
|
||||
So the expanded conversions functions are:
|
||||
|
||||
+ `to_date(ToTimezone, Disambiguate, Date)`
|
||||
+ `to_string(FormatString, ToTimezone, Disambiguate, Date)`
|
||||
+ `to_unixtime(Disambiguate, Date)`
|
||||
+ `to_now(Disambiguate, Date)`
|
||||
|
||||
Examples:
|
||||
|
||||
```erlang
|
||||
1> qdate:set_timezone("GMT").
|
||||
ok
|
||||
|
||||
%% Here, converting GMT 2013-11-03 6AM to America/New York yields an ambiguous
|
||||
%% result
|
||||
2> qdate:to_date("America/New York", both, {{2013,11,3},{6,0,0}}).
|
||||
{ambiguous,{{2013,11,3},{1,0,0}},{{2013,11,3},{2,0,0}}}
|
||||
|
||||
%% Let's just use daylight time
|
||||
3> qdate:to_date("America/New York", prefer_daylight, {{2013,11,3},{6,0,0}}).
|
||||
{{2013,11,3},{2,0,0}}
|
||||
|
||||
%% Let's just use standard time (the default behavior)
|
||||
4> qdate:to_date("America/New York", prefer_standard, {{2013,11,3},{6,0,0}}).
|
||||
{{2013,11,3},{1,0,0}}
|
||||
|
||||
5> qdate:set_timezone("America/New York").
|
||||
ok
|
||||
|
||||
%% Switching from 1AM Eastern Time to GMT yields a potentially ambiguous result
|
||||
6> qdate:to_date("GMT", both, {{2013,11,3},{1,0,0}}).
|
||||
{ambiguous,{{2013,11,3},{6,0,0}},{{2013,11,3},{5,0,0}}}
|
||||
|
||||
%% Use daylight time for conversion
|
||||
7> qdate:to_date("GMT", prefer_daylight, {{2013,11,3},{1,0,0}}).
|
||||
{{2013,11,3},{5,0,0}}
|
||||
|
||||
%% Here we demonstrated that even if we ask for "both", if there is no
|
||||
%% ambiguity, the plain date is returned
|
||||
8> qdate:to_date("GMT", both, {{2013,11,3},{5,0,0}}).
|
||||
{{2013,11,3},{10,0,0}}
|
||||
```
|
||||
|
||||
#### Conversion Functions provided for API compatibility with `ec_date`
|
||||
|
||||
|
@ -101,6 +188,76 @@ will infer the timezone in the following order.
|
|||
+ `format/1` - Same as `to_string/1`
|
||||
+ `format/2` - Same as `to_string/2`
|
||||
|
||||
### Date and Time Comparison
|
||||
|
||||
`qdate` provides a few convenience functions for performing date comparisons.
|
||||
|
||||
+ `compare(A, B) -> -1|0|1` - Like C's `strcmp`, returns:
|
||||
+ `0`: `A` and `B` are exactly the same.
|
||||
+ `-1`: `A` is less than (before) `B`.
|
||||
+ `1`: `A` is greater than (after) `B`.
|
||||
+ `compare(A, Operator, B) -> true|false` - Operator is an infix comparison operator, and
|
||||
the function will return a boolean. Will return `true` if:
|
||||
+ `'='`, or `'=='` - `A` is the same time as `B`
|
||||
+ `'/='`, or `'=/='` or `'!='` - `A` is not the same time as `B`
|
||||
+ `'<'` - `A` is before `B`
|
||||
+ `'>'` - `A` is after `B`
|
||||
+ `'=<'` or `'<='` - `A` is before or equal to `B`
|
||||
+ `'>='` or `'=>'` - `A` is after or equal to `B`
|
||||
+ `between(A, Date, B) -> true|false` - The provided `Date` is (inclusively)
|
||||
between `A` and `B`. That is, `A =< Date =< B`.
|
||||
+ `between(A, B) -> true|false` - shortcut for `between(A, now(), B)`
|
||||
+ `between(A, Op1, Date, Op2, B) -> true|false` - the fully verbose option of
|
||||
comparing between. `Op1` and `Op2` are custom operators. For example, if
|
||||
you wanted to do an exclusive `between`, you can do:
|
||||
`between(A, '<', Date, '<', B)`
|
||||
|
||||
**Note 1:** `Operator` must be an atom.
|
||||
|
||||
**Note 2:** These functions will properly compare times with different timezones
|
||||
(for example: `compare("12am CST",'==',"1am EST")` will properly return true)
|
||||
|
||||
### Sorting
|
||||
|
||||
`qdate` also provides a convenience functions for sorting lists of dates/times:
|
||||
|
||||
+ `sort(List)` - Sort the list in ascending order of earliest to latest.
|
||||
+ `sort(Op, List)` - Sort the list where `Op` is one of the following:
|
||||
+ `'<'` or `'=<'` or `'<='` - Sort ascending
|
||||
+ `'>'` or `'>='` or `'=>'` - Sort descending
|
||||
+ `sort(Op, List, Opts)` - Sort the list according to the `Op`, with options provided in `Opts`. `Opts` is a proplist of the following options:
|
||||
+ `{non_dates, NonDates}` - Tells it how to handle non-dates. `NonDates` can be any of the following:
|
||||
+ `back` **(default)** - put any non-dates at the end (the back) of the list
|
||||
+ `front` - put any non-dates at the beginning of the list
|
||||
+ `crash` - if there are any non-dates, crash.
|
||||
|
||||
Example:
|
||||
|
||||
```erlang
|
||||
1> Dates = ["non date string", <<"garbage">>,
|
||||
1466200861, "2011-01-01", "7pm",
|
||||
{{1999,6,21},{5,30,0}}, non_date_atom, {some_tuple,123}].
|
||||
2> qdate:sort('>=', Dates, [{non_dates, front}]).
|
||||
[<<"garbage">>,"non date string",
|
||||
{some_tuple,123},
|
||||
non_date_atom,1466200861,"2011-01-01",
|
||||
{{1999,6,21},{5,30,0}},
|
||||
"7pm"]
|
||||
```
|
||||
|
||||
**Note 1:** This sorting is optimized to be much faster than using a home-grown
|
||||
sort using the `compare` functions, as this normalizes the items in the list
|
||||
before comparing (so it's only really comparing integers, which is quite fast).
|
||||
|
||||
**Note 2:** This is one of the few qdate functions that don't have the "Date"
|
||||
as the last argument. This follows the pattern in Erlang/OTP to put options as
|
||||
the last argument (for example, `re:run/3`)
|
||||
|
||||
**Note 3:** You'll notice that qdate's sorting retains the original terms (in
|
||||
the example above, we compared a datetime tuple, unix timestamp, and two
|
||||
strings (along with a number of non-dates, which were just prepended to the
|
||||
front of the list).
|
||||
|
||||
### Timezone Functions
|
||||
|
||||
+ `set_timezone(Key, TZ)` - Set the timezone to TZ for the key `Key`
|
||||
|
@ -136,6 +293,7 @@ be attempted before engaging the `ec_date` parser.
|
|||
able to parse the string, then it should return `undefined`.
|
||||
+ `deregister_parser(Key)` - If you previously registered a parser with the
|
||||
`qdate` server, you can deregister it by its `Key`.
|
||||
+ `get_parsers()` - Get the list of all registered parsers and their keys.
|
||||
|
||||
### Registering and Deregistering Formatters
|
||||
+ `register_format(Key, FormatString)` - Register a formatting string with
|
||||
|
@ -143,6 +301,7 @@ be attempted before engaging the `ec_date` parser.
|
|||
formatting string.
|
||||
+ `deregister_format(Key)` - Deregister the formatting string from the
|
||||
`qdate` server.
|
||||
+ `get_formats()` - Get the list of all registered formats and their keys.
|
||||
|
||||
### About backwards compatibility with `ec_date` and deterministic parsing
|
||||
|
||||
|
@ -339,8 +498,31 @@ the crash.
|
|||
|
||||
**Another Note:** Custom parsers are expected to return either:
|
||||
+ A `datetime()` tuple. (ie {{2012,12,21},{14,45,23}}).
|
||||
+ An integer, which represents the Unix timestamp.
|
||||
+ The atom `undefined` if this parser is not a match for the supplied value
|
||||
|
||||
#### Included Parser: Relative Times
|
||||
|
||||
`qdate` ships with an optional relative time parser. To speed up performance
|
||||
(since this parser uses regular expressions), this parser is disabled by
|
||||
default. But if you wish to use it, make sure you call
|
||||
`qdate:register_parser(parse_relative, fun qdate:parse_relative/1)`.
|
||||
|
||||
Doing this allows you to parse relative time strings of the following formats:
|
||||
|
||||
+ "1 hour ago"
|
||||
+ "-15 minutes"
|
||||
+ "in 45 days"
|
||||
+ "+2 years"
|
||||
|
||||
And doing so allows you to construct slightly more readable comparison calls
|
||||
for sometimes common comparisons. For example, the following two calls are identical:
|
||||
|
||||
```erlang
|
||||
qdate:between("-15 minutes", Date, "+15 minutes").
|
||||
|
||||
qdate:between(qdate:add_minutes(-15), Date, qdate:add_minutes(15)).
|
||||
```
|
||||
|
||||
### Registering Custom Formats
|
||||
|
||||
|
@ -354,7 +536,7 @@ the crash.
|
|||
|
||||
%% But, you don't have to: if that's a common format you use in your
|
||||
%% application, you can register your format with the `qdate` server, and then
|
||||
%% easiy refer to that format by its key.
|
||||
%% easily refer to that format by its key.
|
||||
|
||||
%% So let's take that format and register it
|
||||
16> qdate:register_format(longdate, "l, F jS, Y g:i A T").
|
||||
|
@ -510,6 +692,175 @@ ok
|
|||
%% that timezone to our intended timezone.
|
||||
```
|
||||
|
||||
## Beginning or Ending of time periods (hours, days, years, weeks, etc)
|
||||
|
||||
qdate can determine beginnings and endings of time periods, like "beginning of the month"
|
||||
|
||||
This is abstracted to `beginning_X` functions, which return a date/time format
|
||||
with the dates and times truncated to the specified level.
|
||||
|
||||
+ `beginning_minute(Date)`
|
||||
+ `beginning_hour(Date)`
|
||||
+ `beginning_day(Date)`
|
||||
+ `beginning_month(Date)`
|
||||
+ `beginning_year(Date)`
|
||||
|
||||
There are also 0-arity versions of the above, in which `Date` is assumed to be
|
||||
"right now". For example, calling `qdate:beginning_month()` would return
|
||||
midnight on the first day of the current month.
|
||||
|
||||
#### Beginning of Week
|
||||
|
||||
qdate can also do a special "beginning" case, particularly the "beginning of
|
||||
the week" calculation. This has three forms, specifically:
|
||||
|
||||
+ `beginning_week()` - Returns first day of the current week.
|
||||
+ `beginning_week(Date)` - Assumes the beginning of the week is Monday
|
||||
(chosen because Erlang's calendar:day_of_the_week uses 1=Monday and
|
||||
7=Sunday).
|
||||
+ `beginning_week(DayOfWeek, Date)` - Calculates the beginning of the week
|
||||
based on the provided `DayOfWeek`. Valid values for DayOfWeek are the
|
||||
integers 1-7 or the atom versions of the days of the week. Specifically:
|
||||
|
||||
* Monday: `1 | monday | mon`
|
||||
* Tuesday: `2 | tuesday | tue`
|
||||
* Wednesday: `3 | wednesday | wed`
|
||||
* Thursday: `4 | thursday | thu`
|
||||
* Friday: `5 | friday | fri`
|
||||
* Saturday: `6 | saturday | sat`
|
||||
* Sunday: `7 | sunday | sun`
|
||||
|
||||
These all return 12am on the day that is the first day of the week of the
|
||||
provided date.
|
||||
|
||||
(My apologies to non-English speakers. I'm a lazy American who only speaks
|
||||
English, hence the Anglocentric day names).
|
||||
|
||||
### End of time period
|
||||
|
||||
There are also the related `end_X` functions available, using the same
|
||||
conventions, except return the last second of that time period.
|
||||
|
||||
So `end_month("2016-01-05")` will return the unix timestamp representing
|
||||
"2016-01-31 11:59:59pm"
|
||||
|
||||
|
||||
## Date Arithmetic
|
||||
|
||||
The current implementation of qdate's date arithmetic returns Unixtimes.
|
||||
|
||||
There are 8 main functions for date arithmetic:
|
||||
|
||||
+ `add_seconds(Seconds, Date)`
|
||||
+ `add_minutes(Minutes, Date)`
|
||||
+ `add_hours(Hours, Date)`
|
||||
+ `add_days(Days, Date)`
|
||||
+ `add_weeks(Weeks, Date)`
|
||||
+ `add_months(Months, Date)`
|
||||
+ `add_years(Years, Date)`
|
||||
+ `add_date(DateToAdd, Date)` - `DateToAdd` is a shortcut way of adding
|
||||
numerous options. For example. `qdate:add_date({{1, 2, -3}, {-500, 20, 0}})`
|
||||
will add 1 year, add 2 months, subtract 3 days, subtract 500 hours, add 20
|
||||
minutes, and not make any changes to seconds.
|
||||
|
||||
For the date arithmetic functions, `Date`, like all `qdate` functions, can be any
|
||||
format.
|
||||
|
||||
### Date Arithmetic from "now"
|
||||
|
||||
There are 7 other arithmetic functions that take a single argument, and these do arithmetic from "now." For example, `add_years(4)` is a shortcut for `add_years(4, os:timestamp())`.
|
||||
|
||||
+ `add_seconds(Seconds)`
|
||||
+ `add_minutes(Minutes)`
|
||||
+ `add_hours(Hours)`
|
||||
+ `add_days(Days)`
|
||||
+ `add_weeks(Weeks)`
|
||||
+ `add_months(Months)`
|
||||
+ `add_years(Years)`
|
||||
|
||||
## Date and Time Ranges
|
||||
|
||||
qdate provides a number of `range` functions that give applicable dates/times
|
||||
within a start and end time. For example, "All days from 2015-01-01 to today",
|
||||
"every 3rd month from 2000-01-01 to 2009-12-31", or "every 15 minutes from
|
||||
midnight to 11:59pm on 2015-04-15".
|
||||
|
||||
The functions are as follows:
|
||||
|
||||
+ `range_seconds(Interval, Start, End)`
|
||||
+ `range_minutes(Interval, Start, End)`
|
||||
+ `range_hours(Interval, Start, End)`
|
||||
+ `range_days(Interval, Start, End)`
|
||||
+ `range_weeks(Interval, Start, End)`
|
||||
+ `range_months(Interval, Start, End)`
|
||||
+ `range_years(Interval, Start, End)`
|
||||
|
||||
Where `Interval` is the number of seconds/days/years/etc.
|
||||
|
||||
So for example:
|
||||
|
||||
```erlang
|
||||
%% Get every 15th minute from "2015-04-15 12:00am to 2015-04-15 11:59am"
|
||||
> qdate:range_minutes(15, "2015-04-15 12:00am", "2015-04-15 11:59am").
|
||||
[1429056000,1429056900,1429057800,1429058700,1429059600,
|
||||
1429060500,1429061400,1429062300,1429063200,1429064100,
|
||||
1429065000,1429065900,1429066800,1429067700,1429068600,
|
||||
1429069500,1429070400,1429071300,1429072200,1429073100,
|
||||
1429074000,1429074900,1429075800,1429076700,1429077600,
|
||||
1429078500,1429079400,1429080300,1429081200|...]
|
||||
|
||||
%% Get every day of April, 2014
|
||||
> qdate:range_days(1, "2014-04-01", "2014-04-30").
|
||||
[1396310400,1396396800,1396483200,1396569600,1396656000,
|
||||
1396742400,1396828800,1396915200,1397001600,1397088000,
|
||||
1397174400,1397260800,1397347200,1397433600,1397520000,
|
||||
1397606400,1397692800,1397779200,1397865600,1397952000,
|
||||
1398038400,1398124800,1398211200,1398297600,1398384000,
|
||||
1398470400,1398556800,1398643200,1398729600|...]
|
||||
```
|
||||
|
||||
Note, that the return value (just like qdate's arithmetic functions) is a list
|
||||
of integers. These integers are unix timestamps and can be easily formatted
|
||||
with qdate:
|
||||
|
||||
```erlang
|
||||
> Mins = qdate:range_minutes(15, "2015-04-15 12:00am", "2015-04-15 11:59am"),
|
||||
> [qdate:to_string("Y-m-d h:ia", M) || M <- Mins].
|
||||
["2015-04-15 00:00am","2015-04-15 00:15am",
|
||||
"2015-04-15 00:30am","2015-04-15 00:45am",
|
||||
"2015-04-15 01:00am","2015-04-15 01:15am",
|
||||
"2015-04-15 01:30am","2015-04-15 01:45am",
|
||||
"2015-04-15 02:00am","2015-04-15 02:15am",
|
||||
"2015-04-15 02:30am","2015-04-15 02:45am",
|
||||
"2015-04-15 03:00am","2015-04-15 03:15am",
|
||||
"2015-04-15 03:30am","2015-04-15 03:45am",
|
||||
"2015-04-15 04:00am","2015-04-15 04:15am",
|
||||
"2015-04-15 04:30am","2015-04-15 04:45am",
|
||||
"2015-04-15 05:00am","2015-04-15 05:15am",
|
||||
"2015-04-15 05:30am","2015-04-15 05:45am",
|
||||
"2015-04-15 06:00am","2015-04-15 06:15am",
|
||||
"2015-04-15 06:30am","2015-04-15 06:45am",
|
||||
[...]|...]
|
||||
```
|
||||
|
||||
Also note that the range functions are *inclusive*.
|
||||
|
||||
|
||||
## Age Comparison
|
||||
|
||||
There are two main comparisons right now, age in years, and age in days.
|
||||
|
||||
+ `age(Date)` - Number of years since `Date`
|
||||
+ `age(FromDate, ToDate)` - Number of years between `FromDate` to `ToDate`
|
||||
+ `age_days(Date)` - Number of full days since `Date` (for example from `2pm` yesterday to `1:59pm` today is still 0.
|
||||
+ `age_days(FromDate, ToDate)` - Number of full days between `FromDate` and `ToDate`.
|
||||
|
||||
## Configuration Sample
|
||||
|
||||
There is a sample configuration file can be found in the root of the qdate
|
||||
directory. Or you can just [look at it
|
||||
here](https://github.com/choptastic/qdate/blob/master/qdate.config).
|
||||
|
||||
## Thanks
|
||||
|
||||
A few shoutouts to [Dale Harvey](http://github.com/daleharvey) and the
|
||||
|
@ -518,12 +869,24 @@ A few shoutouts to [Dale Harvey](http://github.com/daleharvey) and the
|
|||
package. Without the hard work of all involved in those projects, `qdate` would
|
||||
not exist.
|
||||
|
||||
### Thanks to Additional Contributors
|
||||
|
||||
+ [Mark Allen](https://github.com/mrallen1)
|
||||
+ [Christopher Phillips](https://github.com/lostcolony)
|
||||
+ [Nicholas Lundgaard](https://github.com/nlundgaard-al)
|
||||
+ [Alejandro Ramallo](https://github.com/aramallo)
|
||||
+ [Heinz Gies](https://github.com/Licenser)
|
||||
|
||||
|
||||
## Changelog
|
||||
|
||||
See [CHANGELOG.markdown](https://github.com/choptastic/qdate/blob/master/CHANGELOG.markdown)
|
||||
|
||||
## TODO
|
||||
|
||||
+ Make `qdate` backend-agnostic (allow specifying either ec_date or dh_date as
|
||||
the backend)
|
||||
+ Add `-spec` and `-type` info for dialyzer
|
||||
+ Add date and time arithmetic.
|
||||
+ Research the viability of [ezic](https://github.com/drfloob/ezic) for a
|
||||
timezone backend replacement for `erlang_localtime`.
|
||||
|
20
qdate.config
Normal file
20
qdate.config
Normal file
|
@ -0,0 +1,20 @@
|
|||
%% vim: ts=4 sw=4 et ft=erlang
|
||||
[{qdate, [
|
||||
%% default_timezone can be one of two things:
|
||||
%% 1) An actual timezone. Either short-form like "GMT", "UTC", or a
|
||||
%% longer-form (but more likely to pick the correct daylight saving
|
||||
%% config timezone like "America/Chicago"
|
||||
%% 2) A 2-tuple of {Module, Function}, which will be called as
|
||||
%% Module:Function() to determine the timezone (say you wanted to
|
||||
%% determine timezone based on some kind of environmental conditions)
|
||||
{default_timezone, "GMT"},
|
||||
|
||||
%% See readme section here:
|
||||
%% https://github.com/choptastic/qdate#about-backwards-compatibility-with-ec_date-and-deterministic-parsing
|
||||
{deterministic_parsing, {zero, zero}},
|
||||
|
||||
%% Preserve Milliseconds in parsing
|
||||
%% Changes the return for qdate:to_date to include any Ms value
|
||||
%% and also changes the return of to_string to include the Ms value
|
||||
{preserve_ms, false}
|
||||
]}].
|
BIN
rebar
vendored
BIN
rebar
vendored
Binary file not shown.
31
rebar.config
31
rebar.config
|
@ -1,13 +1,24 @@
|
|||
% vim:ts=4 sw=4 et ft=erlang
|
||||
{require_otp_vsn, "R13B04|R14|R15|R16"}.
|
||||
|
||||
%% -*- erlang -*-
|
||||
%% vim:ts=4 sw=4 et ft=erlang
|
||||
{cover_enabled, true}.
|
||||
|
||||
%{erl_opts, [debug_info,{i,"site/include"}]}.
|
||||
|
||||
{deps_dir, ["deps"]}.
|
||||
|
||||
{deps, [
|
||||
{erlware_commons, ".*", {git, "git://github.com/erlware/erlware_commons.git", "HEAD"}},
|
||||
{erlang_localtime, ".*", {git, "git://github.com/choptastic/erlang_localtime.git", "HEAD"}}
|
||||
{dialyzer, [
|
||||
{exclude_apps, []},
|
||||
{warnings, []}
|
||||
]}.
|
||||
|
||||
{deps,
|
||||
[
|
||||
erlware_commons,
|
||||
{qdate_localtime, "~> 1.2.0"}
|
||||
]}.
|
||||
|
||||
{project_plugins, [rebar3_ex_doc]}.
|
||||
|
||||
{hex, [{doc, ex_doc}]}.
|
||||
|
||||
{ex_doc, [
|
||||
{source_url, <<"https://github.com/choptastic/qdate">>},
|
||||
{extras, [<<"README.md">>, <<"LICENSE.md">>]},
|
||||
{main, <<"readme">>}]}.
|
||||
|
||||
|
|
15
rebar.config.script
Normal file
15
rebar.config.script
Normal file
|
@ -0,0 +1,15 @@
|
|||
%% -*- mode: erlang -*-
|
||||
%% -*- tab-width: 4;erlang-indent-level: 4;indent-tabs-mode: nil -*-
|
||||
%% ex: ts=4 sw=4 sts ft=erlang et
|
||||
|
||||
case erlang:function_exported(rebar3, main, 1) of
|
||||
true -> % rebar3
|
||||
CONFIG;
|
||||
false -> % rebar 2.x or older
|
||||
%% Rebuild deps, possibly including those that have been moved to
|
||||
%% profiles
|
||||
[{deps, [
|
||||
{erlware_commons, "", {git, "https://github.com/erlware/erlware_commons", {tag, "v1.5.0"}}}, %% this is the version of erlware_commons that works until erlware tags a new version
|
||||
{qdate_localtime, "", {git, "https://github.com/choptastic/qdate_localtime", {tag, "1.1.0"}}}
|
||||
]} | lists:keydelete(deps, 1, CONFIG)]
|
||||
end.
|
14
rebar.lock
Normal file
14
rebar.lock
Normal file
|
@ -0,0 +1,14 @@
|
|||
{"1.2.0",
|
||||
[{<<"cf">>,{pkg,<<"cf">>,<<"0.3.1">>},1},
|
||||
{<<"erlware_commons">>,{pkg,<<"erlware_commons">>,<<"1.6.0">>},0},
|
||||
{<<"qdate_localtime">>,{pkg,<<"qdate_localtime">>,<<"1.2.1">>},0}]}.
|
||||
[
|
||||
{pkg_hash,[
|
||||
{<<"cf">>, <<"5CB902239476E141EA70A740340233782D363A31EEA8AD37049561542E6CD641">>},
|
||||
{<<"erlware_commons">>, <<"E0DA62F91E65DEFAE28BB450DDC3C24E85ECB99B926F857CDC6ED8E7DD7076B4">>},
|
||||
{<<"qdate_localtime">>, <<"72E1034DC6B7FEE8F588281EDDD0BD0DC5260005D758052F50634D265D382C18">>}]},
|
||||
{pkg_hash_ext,[
|
||||
{<<"cf">>, <<"315E8D447D3A4B02BCDBFA397AD03BBB988A6E0AA6F44D3ADD0F4E3C3BF97672">>},
|
||||
{<<"erlware_commons">>, <<"B57C299C39A2992A8C413F9D2C1B8A20061310FB4432B0EEE5E4C4DF7AAA0AA3">>},
|
||||
{<<"qdate_localtime">>, <<"1109958D205C65C595C8C5694CB83EBAF2DBE770CF902E4DCE8AFB2C4123764D">>}]}
|
||||
].
|
|
@ -1,12 +1,17 @@
|
|||
{application, qdate,
|
||||
[
|
||||
{description, "Simple Date and Timezone handling for Erlang"},
|
||||
{vsn, "0.1.0"},
|
||||
{vsn, git},
|
||||
{registered, []},
|
||||
{applications, [
|
||||
kernel,
|
||||
stdlib
|
||||
stdlib,
|
||||
qdate_localtime,
|
||||
erlware_commons
|
||||
]},
|
||||
{mod, { qdate_app, []}},
|
||||
{env, []}
|
||||
{modules, [qdate, qdate_srv, qdate_sup, qdate_app]},
|
||||
{env, []},
|
||||
{licenses, ["MIT"]},
|
||||
{mod, {qdate_app, []}},
|
||||
{links, [{"Github", "https://github.com/choptastic/qdate"}]}
|
||||
]}.
|
||||
|
|
1286
src/qdate.erl
1286
src/qdate.erl
File diff suppressed because it is too large
Load diff
|
@ -1,7 +1,3 @@
|
|||
% vim: ts=4 sw=4 et
|
||||
% Copyright (c) 2013 Jesse Gumm
|
||||
% See LICENSE for licensing information.
|
||||
|
||||
-module(qdate_app).
|
||||
|
||||
-behaviour(application).
|
||||
|
@ -9,12 +5,11 @@
|
|||
%% Application callbacks
|
||||
-export([start/2, stop/1]).
|
||||
|
||||
%% ===================================================================
|
||||
%% Application callbacks
|
||||
%% ===================================================================
|
||||
|
||||
start(_StartType, _StartArgs) ->
|
||||
qdate_sup:start_link().
|
||||
|
||||
|
||||
stop(_State) ->
|
||||
ok.
|
||||
|
||||
|
||||
|
|
|
@ -1,22 +1,10 @@
|
|||
% vim: ts=4 sw=4 et
|
||||
% Copyright (c) 2013 Jesse Gumm
|
||||
% Copyright (c) 2013-2021 Jesse Gumm
|
||||
% See LICENSE for licensing information.
|
||||
|
||||
-module(qdate_srv).
|
||||
-behaviour(gen_server).
|
||||
|
||||
-define(SRV, ?MODULE).
|
||||
|
||||
-export([
|
||||
start_link/0,
|
||||
init/1,
|
||||
handle_call/3,
|
||||
handle_cast/2,
|
||||
handle_info/2,
|
||||
code_change/3,
|
||||
terminate/2
|
||||
]).
|
||||
|
||||
-export([
|
||||
set_timezone/1,
|
||||
set_timezone/2,
|
||||
|
@ -33,133 +21,172 @@
|
|||
|
||||
register_format/2,
|
||||
get_format/1,
|
||||
deregister_format/1
|
||||
deregister_format/1,
|
||||
get_formats/0
|
||||
]).
|
||||
|
||||
|
||||
%% API
|
||||
-export([start_link/0]).
|
||||
|
||||
%% gen_server callbacks
|
||||
-export([init/1,
|
||||
handle_call/3,
|
||||
handle_cast/2,
|
||||
handle_info/2,
|
||||
terminate/2,
|
||||
code_change/3]).
|
||||
|
||||
|
||||
%% Simple wrappers for unique keys
|
||||
-define(BASETAG, qdate_var).
|
||||
-define(KEY(Name), {?BASETAG, Name}).
|
||||
|
||||
-define(TZTAG, qdate_tz).
|
||||
-define(TZKEY(Name), {?TZTAG, Name}).
|
||||
-define(PARSERTAG, qdate_parser).
|
||||
-define(PARSERKEY(Name), {?PARSERTAG, Name}).
|
||||
-define(FORMATTAG, qdate_format).
|
||||
-define(FORMATKEY(Name), {?FORMATTAG, Name}).
|
||||
|
||||
-define(SERVER, ?MODULE).
|
||||
-define(TABLE, ?MODULE).
|
||||
|
||||
-record(state, {}).
|
||||
|
||||
start_link() ->
|
||||
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
|
||||
|
||||
init([]) ->
|
||||
error_logger:info_msg("Creating qdate ETS Table: ~p",[?TABLE]),
|
||||
?TABLE = ets:new(?TABLE, [public, {read_concurrency, true}, named_table]),
|
||||
{ok, #state{}}.
|
||||
|
||||
handle_call({set, Key, Val}, _From, State) ->
|
||||
ets:insert(?TABLE, {Key, Val}),
|
||||
{reply, ok, State};
|
||||
handle_call({unset, Key}, _From, State) ->
|
||||
ets:delete(?TABLE, Key),
|
||||
{reply, ok, State};
|
||||
handle_call(_, _From, State) ->
|
||||
{reply, invalid_request, State}.
|
||||
|
||||
handle_cast(_Msg, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
handle_info(_Info, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
terminate(_Reason, _State) ->
|
||||
ok.
|
||||
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
||||
|
||||
|
||||
|
||||
%% PUBLIC API FUNCTIONS
|
||||
|
||||
start_link() ->
|
||||
gen_server:start_link({local, ?SRV}, ?MODULE, [], []).
|
||||
|
||||
set_timezone(TZ) ->
|
||||
set_timezone(self(),TZ).
|
||||
put_pd(?TZTAG, TZ).
|
||||
|
||||
set_timezone(Key,TZ) ->
|
||||
ok = gen_server:call(?SRV,{set_timezone,Key,TZ}).
|
||||
set_timezone(Key, TZ) ->
|
||||
set_env(?TZKEY(Key), TZ).
|
||||
|
||||
get_timezone() ->
|
||||
get_timezone(self()).
|
||||
get_pd(?TZTAG).
|
||||
|
||||
get_timezone(Key) ->
|
||||
gen_server:call(?SRV,{get_timezone,Key}).
|
||||
get_env(?TZKEY(Key)).
|
||||
|
||||
clear_timezone() ->
|
||||
clear_timezone(self()).
|
||||
unset_pd(?TZTAG).
|
||||
|
||||
clear_timezone(Key) ->
|
||||
ok = gen_server:call(?SRV, {clear_timezone, Key}).
|
||||
unset_env(?TZKEY(Key)).
|
||||
|
||||
register_parser(Parser) when is_function(Parser,1) ->
|
||||
register_parser(erlang:make_ref(),Parser).
|
||||
|
||||
register_parser(Key,Parser) when is_function(Parser,1) ->
|
||||
Key = gen_server:call(?SRV,{register_parser,Key,Parser}).
|
||||
set_env(?PARSERKEY(Key), Parser).
|
||||
|
||||
deregister_parser(Key) ->
|
||||
ok = gen_server:call(?SRV,{deregister_parser,Key}).
|
||||
unset_env(?PARSERKEY(Key)).
|
||||
|
||||
deregister_parsers() ->
|
||||
ok = gen_server:call(?SRV,{deregister_parsers}).
|
||||
[deregister_parser(Key) || {Key, _} <- get_parsers()].
|
||||
|
||||
get_parsers() ->
|
||||
gen_server:call(?SRV,{get_parsers}).
|
||||
get_all_env(?PARSERTAG).
|
||||
|
||||
register_format(Key,Format) ->
|
||||
ok = gen_server:call(?SRV,{register_format,Key,Format}).
|
||||
register_format(Key, Format) ->
|
||||
set_env(?FORMATKEY(Key), Format).
|
||||
|
||||
get_format(Key) ->
|
||||
gen_server:call(?SRV,{get_format,Key}).
|
||||
get_env(?FORMATKEY(Key)).
|
||||
|
||||
deregister_format(Key) ->
|
||||
ok = gen_server:call(?SRV,{deregister_format,Key}).
|
||||
unset_env(?FORMATKEY(Key)).
|
||||
|
||||
|
||||
%% SERVER FUNCTIONS
|
||||
|
||||
-record(state, {tz, parsers, formats}).
|
||||
|
||||
init(_) ->
|
||||
State = #state{tz=dict:new(),parsers=dict:new(),formats=dict:new()},
|
||||
{ok, State}.
|
||||
|
||||
handle_cast(_,State) ->
|
||||
{noreply, State}.
|
||||
|
||||
handle_info({'DOWN', MonitorRef, process, Pid, _Reason}, State) ->
|
||||
erlang:demonitor(MonitorRef),
|
||||
NewTZ = dict:erase(Pid, State#state.tz),
|
||||
NewParsers = dict:erase(Pid, State#state.parsers),
|
||||
NewFormats = dict:erase(Pid, State#state.formats),
|
||||
NewState = State#state{tz=NewTZ, parsers=NewParsers, formats=NewFormats},
|
||||
{noreply, NewState };
|
||||
handle_info(_, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
handle_call({set_timezone,Key,TZ}, _From, State) ->
|
||||
monitor_if_pid(Key),
|
||||
NewTZ = dict:store(Key, TZ, State#state.tz),
|
||||
NewState = State#state{tz=NewTZ},
|
||||
{reply, ok, NewState};
|
||||
handle_call({clear_timezone,Key},_From, State) ->
|
||||
NewTZ = dict:erase(Key, State#state.tz),
|
||||
NewState = State#state{tz=NewTZ},
|
||||
{reply, ok, NewState};
|
||||
handle_call({get_timezone,Key},_From, State) ->
|
||||
Reply = case dict:find(Key, State#state.tz) of
|
||||
error -> undefined;
|
||||
{ok,TZ} -> TZ
|
||||
end,
|
||||
{reply, Reply, State};
|
||||
|
||||
handle_call({register_parser,Key,Parser},_From,State) ->
|
||||
NewParsers = dict:store(Key, Parser, State#state.parsers),
|
||||
NewState = State#state{parsers=NewParsers},
|
||||
{reply, Key, NewState};
|
||||
handle_call({get_parsers},_From,State) ->
|
||||
Reply = dict:to_list(State#state.parsers),
|
||||
{reply, Reply, State};
|
||||
handle_call({deregister_parser,Key},_From,State) ->
|
||||
NewParsers = dict:erase(Key, State#state.parsers),
|
||||
NewState = State#state{parsers=NewParsers},
|
||||
{reply, ok, NewState};
|
||||
handle_call({deregister_parsers},_From,State) ->
|
||||
NewState = State#state{parsers=dict:new()},
|
||||
{reply, ok, NewState};
|
||||
|
||||
handle_call({register_format,Key,Format},_From,State) ->
|
||||
NewFormats = dict:store(Key, Format, State#state.formats),
|
||||
NewState = State#state{formats=NewFormats},
|
||||
{reply, ok, NewState};
|
||||
handle_call({get_format,Key},_From,State) ->
|
||||
Reply = case dict:find(Key, State#state.formats) of
|
||||
error -> undefined;
|
||||
{ok, Format} -> Format
|
||||
end,
|
||||
{reply, Reply,State};
|
||||
handle_call({deregister_format,Key},_From,State) ->
|
||||
NewFormats = dict:erase(Key, State#state.formats),
|
||||
NewState = State#state{formats=NewFormats},
|
||||
{reply, ok, NewState}.
|
||||
|
||||
terminate(_Reason, _State) ->
|
||||
ok.
|
||||
|
||||
code_change(_OldVersion, State, _Extra) ->
|
||||
{ok, State}.
|
||||
get_formats() ->
|
||||
get_all_env(?FORMATTAG).
|
||||
|
||||
%% PRIVATE TOOLS
|
||||
|
||||
monitor_if_pid(Key) when is_pid(Key) ->
|
||||
erlang:monitor(process,Key);
|
||||
monitor_if_pid(_) ->
|
||||
do_nothing.
|
||||
%% App Vars
|
||||
|
||||
set_env(Key, Val) ->
|
||||
gen_server:call(?SERVER, {set, ?KEY(Key), Val}).
|
||||
|
||||
get_env(Key) ->
|
||||
get_env(Key, undefined).
|
||||
|
||||
get_env(Key, Default) ->
|
||||
case ets:lookup(?TABLE, ?KEY(Key)) of
|
||||
[{__Key, Val}] -> Val;
|
||||
[] -> Default
|
||||
end.
|
||||
|
||||
unset_env(Key) ->
|
||||
gen_server:call(?SERVER, {unset, ?KEY(Key)}).
|
||||
|
||||
get_all_env(FilterTag) ->
|
||||
try ets:tab2list(?TABLE) of
|
||||
All ->
|
||||
[{Key, V} || {{?BASETAG, {Tag, Key}}, V} <- All, Tag==FilterTag]
|
||||
catch
|
||||
error:badarg:S ->
|
||||
Msg = maybe_start_msg(),
|
||||
logger:error(Msg),
|
||||
|
||||
error({qdate_get_all_env_failed, #{
|
||||
original_error => {error, badarg, S}
|
||||
}})
|
||||
end.
|
||||
|
||||
maybe_start_msg() ->
|
||||
"Attempting to read qdate environment failed (qdate_srv:get_all_env/1).\n"
|
||||
"qdate may not have been started properly.\n"
|
||||
"Please ensure qdate is started properly by either:\n"
|
||||
"* Putting qdate in the 'applications' key in your .app.src or .app file, or\n"
|
||||
"* Starting it manually with application:ensure_all_started(qdate) or qdate:start().".
|
||||
|
||||
|
||||
%% ProcDic Vars
|
||||
|
||||
get_pd(Key) ->
|
||||
erlang:get(?KEY(Key)).
|
||||
|
||||
put_pd(Key, Val) ->
|
||||
erlang:put(?KEY(Key), Val),
|
||||
ok.
|
||||
|
||||
unset_pd(Key) ->
|
||||
put_pd(Key, undefined).
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -1,7 +1,3 @@
|
|||
% vim: ts=4 sw=4 et
|
||||
% Copyright (c) 2013 Jesse Gumm
|
||||
% See LICENSE for licensing information.
|
||||
|
||||
-module(qdate_sup).
|
||||
|
||||
-behaviour(supervisor).
|
||||
|
@ -12,21 +8,25 @@
|
|||
%% Supervisor callbacks
|
||||
-export([init/1]).
|
||||
|
||||
%% Helper macro for declaring children of supervisor
|
||||
-define(CHILD(I, Type), {I, {I, start_link, []}, permanent, 5000, Type, [I]}).
|
||||
|
||||
%% ===================================================================
|
||||
%% API functions
|
||||
%% ===================================================================
|
||||
-define(SERVER, ?MODULE).
|
||||
|
||||
start_link() ->
|
||||
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
|
||||
|
||||
%% ===================================================================
|
||||
%% Supervisor callbacks
|
||||
%% ===================================================================
|
||||
supervisor:start_link({local, ?SERVER}, ?MODULE, []).
|
||||
|
||||
init([]) ->
|
||||
Server = ?CHILD(qdate_srv, worker),
|
||||
{ok, { {one_for_one, 5, 10}, [Server]} }.
|
||||
|
||||
SupFlags = #{},
|
||||
|
||||
ChildSpec = #{
|
||||
id=>qdate_srv,
|
||||
start=>{qdate_srv, start_link, []}
|
||||
},
|
||||
|
||||
{ok, {SupFlags, [ChildSpec]}}.
|
||||
|
||||
%%%===================================================================
|
||||
%%% Internal functions
|
||||
%%%===================================================================
|
||||
|
||||
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue