Compare commits
119 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 |
17 changed files with 1232 additions and 122 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,8 +1,10 @@
|
||||||
*~
|
*~
|
||||||
*.beam
|
*.beam
|
||||||
*.sw?
|
*.sw?
|
||||||
|
*.iml
|
||||||
deps/
|
deps/
|
||||||
ebin/
|
ebin/
|
||||||
.eunit/
|
.eunit/
|
||||||
|
.idea/
|
||||||
_build
|
_build
|
||||||
rebar.lock
|
doc/
|
||||||
|
|
11
.travis.yml
11
.travis.yml
|
@ -1,11 +0,0 @@
|
||||||
language: erlang
|
|
||||||
script: "make test"
|
|
||||||
otp_release:
|
|
||||||
- 17.4
|
|
||||||
- 17.1
|
|
||||||
- 17.0
|
|
||||||
- R16B
|
|
||||||
- R15B02
|
|
||||||
- R15B01
|
|
||||||
- R15B
|
|
||||||
before_script: "sudo apt-get --yes --force-yes install libpam0g-dev"
|
|
|
@ -1,8 +1,50 @@
|
||||||
## 0.5.0 (in development)
|
## 0.7.3
|
||||||
|
|
||||||
* Update to rebar3 and add hex compatability. (@Licenser)
|
* 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)
|
* Properly add dependent apps to .app.src (@Licenser)
|
||||||
* Remove R14 from travis testing.
|
* 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
|
## 0.4.2
|
||||||
|
|
||||||
|
|
42
Makefile
42
Makefile
|
@ -1,21 +1,41 @@
|
||||||
REBAR = $(shell pwd)/rebar3
|
|
||||||
|
|
||||||
all: compile
|
all: compile
|
||||||
|
|
||||||
compile:
|
# 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
|
||||||
|
|
||||||
|
# rebar3.mk adds a new rebar3 rule to your Makefile
|
||||||
|
# (see https://github.com/choptastic/rebar3.mk) for full info
|
||||||
|
include rebar3.mk
|
||||||
|
|
||||||
|
compile: rebar3
|
||||||
$(REBAR) compile
|
$(REBAR) compile
|
||||||
|
|
||||||
update:
|
update: rebar3
|
||||||
$(REBAR) update
|
$(REBAR) update
|
||||||
|
|
||||||
test: compile
|
test:
|
||||||
$(REBAR) eunit
|
EUNIT=1 $(REBAR) compile
|
||||||
|
EUNIT=1 $(REBAR) eunit
|
||||||
|
|
||||||
run:
|
dialyzer: compile
|
||||||
|
DIALYZER=1 $(REBAR) dialyzer
|
||||||
|
|
||||||
|
dev:
|
||||||
|
mkdir -p _checkouts
|
||||||
|
cd _checkouts; git clone https://github.com/choptastic/qdate_localtime
|
||||||
|
|
||||||
|
|
||||||
|
run: rebar3
|
||||||
$(REBAR) shell
|
$(REBAR) shell
|
||||||
|
|
||||||
publish:
|
push_tags:
|
||||||
$(REBAR) as pkg upgrade
|
git push --tag
|
||||||
$(REBAR) as pkg hex publish
|
|
||||||
$(REBAR) upgrade
|
pull_tags:
|
||||||
|
git pull --tag
|
||||||
|
|
||||||
|
publish: rebar3 pull_tags
|
||||||
|
$(REBAR) hex publish
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# qdate - Erlang Date and Timezone Library
|
# qdate - Erlang Date and Timezone Library
|
||||||
|
|
||||||
[](https://travis-ci.org/choptastic/qdate)
|
[](https://github.com/choptastic/qdate/actions/workflows/tests-workflow.yml)
|
||||||
|
|
||||||
## Purpose
|
## Purpose
|
||||||
|
|
||||||
|
@ -98,14 +98,16 @@ will infer the timezone in the following order.
|
||||||
`set_timezone/1` only applies to that *specific* process. If none is
|
`set_timezone/1` only applies to that *specific* process. If none is
|
||||||
specified.
|
specified.
|
||||||
+ If no timezone is specified for the process, `qdate` looks at the `qdate`
|
+ 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"
|
+ If no timezone is specified by either of the above, `qdate` assumes "GMT"
|
||||||
for all dates.
|
for all dates.
|
||||||
+ A timezone value of `auto` will act as if no timezone is specified.
|
+ A timezone value of `auto` will act as if no timezone is specified.
|
||||||
|
|
||||||
#### Disambiguating Ambiguous Timezone Conversions
|
#### Disambiguating Ambiguous Timezone Conversions
|
||||||
|
|
||||||
Sometimes, when youre converting a datetime from one timezone to another, there
|
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
|
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,
|
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
|
converting "11-Nov-2013 1:00:am" in "America/New York" to "GMT" could be both
|
||||||
|
@ -190,24 +192,72 @@ ok
|
||||||
|
|
||||||
`qdate` provides a few convenience functions for performing date comparisons.
|
`qdate` provides a few convenience functions for performing date comparisons.
|
||||||
|
|
||||||
+ `compare(A, B)` - Like C's `strcmp`, returns:
|
+ `compare(A, B) -> -1|0|1` - Like C's `strcmp`, returns:
|
||||||
+ `0`: `A` and `B` are exactly the same.
|
+ `0`: `A` and `B` are exactly the same.
|
||||||
+ `-1`: `A` is less than (before) `B`.
|
+ `-1`: `A` is less than (before) `B`.
|
||||||
+ `1`: `A` is greater than (after) `B`.
|
+ `1`: `A` is greater than (after) `B`.
|
||||||
+ `compare(A, Operator, B)` - Operator is an infix comparison operator, and
|
+ `compare(A, Operator, B) -> true|false` - Operator is an infix comparison operator, and
|
||||||
the function will return true if:
|
the function will return a boolean. Will return `true` if:
|
||||||
+ `'='`, or `'=='` - `A` is the same time as `B`
|
+ `'='`, or `'=='` - `A` is the same time as `B`
|
||||||
+ `'/='`, or `'=/='` or `'!='` - `A` is not the same time as `B`
|
+ `'/='`, or `'=/='` or `'!='` - `A` is not the same time as `B`
|
||||||
+ `'<'` - `A` is before `B`
|
+ `'<'` - `A` is before `B`
|
||||||
+ `'>'` - `A` is after `B`
|
+ `'>'` - `A` is after `B`
|
||||||
+ `'=<'` or `'<='` - `A` is before or equal to `B`
|
+ `'=<'` or `'<='` - `A` is before or equal to `B`
|
||||||
+ `'>='` or `'=>'` - `A` is after 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 1:** `Operator` must be an atom.
|
||||||
|
|
||||||
**Note 2:** These functions will properly compare times with different timezones
|
**Note 2:** These functions will properly compare times with different timezones
|
||||||
(for example: `compare("12am CST",'==',"1am EST")` will properly return true)
|
(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
|
### Timezone Functions
|
||||||
|
|
||||||
+ `set_timezone(Key, TZ)` - Set the timezone to TZ for the key `Key`
|
+ `set_timezone(Key, TZ)` - Set the timezone to TZ for the key `Key`
|
||||||
|
@ -448,8 +498,31 @@ the crash.
|
||||||
|
|
||||||
**Another Note:** Custom parsers are expected to return either:
|
**Another Note:** Custom parsers are expected to return either:
|
||||||
+ A `datetime()` tuple. (ie {{2012,12,21},{14,45,23}}).
|
+ 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
|
+ 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
|
### Registering Custom Formats
|
||||||
|
|
||||||
|
@ -463,7 +536,7 @@ the crash.
|
||||||
|
|
||||||
%% But, you don't have to: if that's a common format you use in your
|
%% 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
|
%% 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
|
%% So let's take that format and register it
|
||||||
16> qdate:register_format(longdate, "l, F jS, Y g:i A T").
|
16> qdate:register_format(longdate, "l, F jS, Y g:i A T").
|
||||||
|
@ -619,25 +692,76 @@ ok
|
||||||
%% that timezone to our intended timezone.
|
%% that timezone to our intended timezone.
|
||||||
```
|
```
|
||||||
|
|
||||||
## Date Arithmetic
|
## Beginning or Ending of time periods (hours, days, years, weeks, etc)
|
||||||
|
|
||||||
(not fully tested yet, but will have full tests for 0.4.0)
|
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.
|
The current implementation of qdate's date arithmetic returns Unixtimes.
|
||||||
|
|
||||||
There are 8 main functions for date arithmetic:
|
There are 8 main functions for date arithmetic:
|
||||||
|
|
||||||
+ `add_seconds(Seconds, Date)`
|
+ `add_seconds(Seconds, Date)`
|
||||||
+ `add_minutes(Minutes, Date)`
|
+ `add_minutes(Minutes, Date)`
|
||||||
+ `add_hours(Hours, Date)`
|
+ `add_hours(Hours, Date)`
|
||||||
+ `add_days(Days, Date)`
|
+ `add_days(Days, Date)`
|
||||||
+ `add_weeks(Weeks, Date)`
|
+ `add_weeks(Weeks, Date)`
|
||||||
+ `add_months(Months, Date)`
|
+ `add_months(Months, Date)`
|
||||||
+ `add_years(Years, Date)`
|
+ `add_years(Years, Date)`
|
||||||
+ `add_date(DateToAdd, Date)` - `DateToAdd` is a shortcut way of adding
|
+ `add_date(DateToAdd, Date)` - `DateToAdd` is a shortcut way of adding
|
||||||
numerous options. For example. `qdate:add_date({{1, 2, -3}, {-500, 20, 0}})`
|
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
|
will add 1 year, add 2 months, subtract 3 days, subtract 500 hours, add 20
|
||||||
minutes, and not make any changes to seconds.
|
minutes, and not make any changes to seconds.
|
||||||
|
|
||||||
For the date arithmetic functions, `Date`, like all `qdate` functions, can be any
|
For the date arithmetic functions, `Date`, like all `qdate` functions, can be any
|
||||||
format.
|
format.
|
||||||
|
@ -654,6 +778,89 @@ There are 7 other arithmetic functions that take a single argument, and these do
|
||||||
+ `add_months(Months)`
|
+ `add_months(Months)`
|
||||||
+ `add_years(Years)`
|
+ `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
|
## Thanks
|
||||||
|
|
||||||
A few shoutouts to [Dale Harvey](http://github.com/daleharvey) and the
|
A few shoutouts to [Dale Harvey](http://github.com/daleharvey) and the
|
||||||
|
@ -680,7 +887,6 @@ See [CHANGELOG.markdown](https://github.com/choptastic/qdate/blob/master/CHANGEL
|
||||||
+ Make `qdate` backend-agnostic (allow specifying either ec_date or dh_date as
|
+ Make `qdate` backend-agnostic (allow specifying either ec_date or dh_date as
|
||||||
the backend)
|
the backend)
|
||||||
+ Add `-spec` and `-type` info for dialyzer
|
+ Add `-spec` and `-type` info for dialyzer
|
||||||
+ Provide a sample qdate.config for users to see
|
|
||||||
+ Research the viability of [ezic](https://github.com/drfloob/ezic) for a
|
+ Research the viability of [ezic](https://github.com/drfloob/ezic) for a
|
||||||
timezone backend replacement for `erlang_localtime`.
|
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}
|
||||||
|
]}].
|
34
rebar.config
34
rebar.config
|
@ -2,25 +2,23 @@
|
||||||
%% vim:ts=4 sw=4 et ft=erlang
|
%% vim:ts=4 sw=4 et ft=erlang
|
||||||
{cover_enabled, true}.
|
{cover_enabled, true}.
|
||||||
|
|
||||||
%% For rebar2 compat
|
{dialyzer, [
|
||||||
|
{exclude_apps, []},
|
||||||
|
{warnings, []}
|
||||||
|
]}.
|
||||||
|
|
||||||
{deps,
|
{deps,
|
||||||
[
|
[
|
||||||
%% This uses an older erlware_commons version so retain compatibility with
|
erlware_commons,
|
||||||
%% rebar2. v0.16.1 introduced a 'cf' dependency, which seems to cause
|
{qdate_localtime, "~> 1.2.0"}
|
||||||
%% breakage.
|
|
||||||
{erlware_commons, ".*", {git, "git://github.com/erlware/erlware_commons.git", {tag, "v0.15.0"}}},
|
|
||||||
|
|
||||||
%% We'll temporarily still use choptastic/erlang_localtime until
|
|
||||||
%% https://github.com/dmitryme/erlang_localtime/pull/24 gets merged. Then we
|
|
||||||
%% can switch to the mainline repo
|
|
||||||
{erlang_localtime, ".*", {git, "git://github.com/choptastic/erlang_localtime.git", {branch, master}}}
|
|
||||||
]}.
|
]}.
|
||||||
|
|
||||||
%% for rebar3
|
{project_plugins, [rebar3_ex_doc]}.
|
||||||
{profiles,
|
|
||||||
[{pkg,
|
{hex, [{doc, ex_doc}]}.
|
||||||
[{deps,
|
|
||||||
[
|
{ex_doc, [
|
||||||
erlware_commons,
|
{source_url, <<"https://github.com/choptastic/qdate">>},
|
||||||
erlang_localtime
|
{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">>}]}
|
||||||
|
].
|
BIN
rebar3
BIN
rebar3
Binary file not shown.
|
@ -1,17 +1,17 @@
|
||||||
{application, qdate,
|
{application, qdate,
|
||||||
[
|
[
|
||||||
{description, "Simple Date and Timezone handling for Erlang"},
|
{description, "Simple Date and Timezone handling for Erlang"},
|
||||||
{vsn, "0.4.2"},
|
{vsn, git},
|
||||||
{registered, []},
|
{registered, []},
|
||||||
{applications, [
|
{applications, [
|
||||||
kernel,
|
kernel,
|
||||||
erlang_localtime,
|
stdlib,
|
||||||
erlware_commons,
|
qdate_localtime,
|
||||||
stdlib
|
erlware_commons
|
||||||
]},
|
]},
|
||||||
{modules, [qdate, qdate_srv]},
|
{modules, [qdate, qdate_srv, qdate_sup, qdate_app]},
|
||||||
{env, []},
|
{env, []},
|
||||||
{contributors, ["Jesse Gumm"]},
|
|
||||||
{licenses, ["MIT"]},
|
{licenses, ["MIT"]},
|
||||||
|
{mod, {qdate_app, []}},
|
||||||
{links, [{"Github", "https://github.com/choptastic/qdate"}]}
|
{links, [{"Github", "https://github.com/choptastic/qdate"}]}
|
||||||
]}.
|
]}.
|
||||||
|
|
711
src/qdate.erl
711
src/qdate.erl
|
@ -1,9 +1,11 @@
|
||||||
% vim: ts=4 sw=4 et
|
% vim: ts=4 sw=4 et
|
||||||
% Copyright (c) 2013 Jesse Gumm
|
% Copyright (c) 2013-2023 Jesse Gumm
|
||||||
% See LICENSE for licensing information.
|
% See LICENSE for licensing information.
|
||||||
%
|
%
|
||||||
-module(qdate).
|
-module(qdate).
|
||||||
|
|
||||||
|
%-compile(export_all).
|
||||||
|
|
||||||
-export([
|
-export([
|
||||||
start/0,
|
start/0,
|
||||||
stop/0
|
stop/0
|
||||||
|
@ -24,9 +26,50 @@
|
||||||
unixtime/0
|
unixtime/0
|
||||||
]).
|
]).
|
||||||
|
|
||||||
|
-export([
|
||||||
|
beginning_minute/1,
|
||||||
|
beginning_minute/0,
|
||||||
|
beginning_hour/1,
|
||||||
|
beginning_hour/0,
|
||||||
|
beginning_day/1,
|
||||||
|
beginning_day/0,
|
||||||
|
beginning_week/0,
|
||||||
|
beginning_week/1,
|
||||||
|
beginning_week/2,
|
||||||
|
beginning_month/1,
|
||||||
|
beginning_month/0,
|
||||||
|
beginning_year/1,
|
||||||
|
beginning_year/0
|
||||||
|
]).
|
||||||
|
|
||||||
|
-export([
|
||||||
|
end_minute/1,
|
||||||
|
end_minute/0,
|
||||||
|
end_hour/1,
|
||||||
|
end_hour/0,
|
||||||
|
end_day/1,
|
||||||
|
end_day/0,
|
||||||
|
end_week/0,
|
||||||
|
end_week/1,
|
||||||
|
end_week/2,
|
||||||
|
end_month/1,
|
||||||
|
end_month/0,
|
||||||
|
end_year/1,
|
||||||
|
end_year/0
|
||||||
|
]).
|
||||||
|
|
||||||
-export([
|
-export([
|
||||||
compare/2,
|
compare/2,
|
||||||
compare/3
|
compare/3,
|
||||||
|
between/2,
|
||||||
|
between/3,
|
||||||
|
between/5
|
||||||
|
]).
|
||||||
|
|
||||||
|
-export([
|
||||||
|
sort/1,
|
||||||
|
sort/2,
|
||||||
|
sort/3
|
||||||
]).
|
]).
|
||||||
|
|
||||||
-export([
|
-export([
|
||||||
|
@ -44,9 +87,29 @@
|
||||||
add_months/1,
|
add_months/1,
|
||||||
add_years/2,
|
add_years/2,
|
||||||
add_years/1,
|
add_years/1,
|
||||||
|
add_unit/2,
|
||||||
|
add_unit/3,
|
||||||
add_date/2
|
add_date/2
|
||||||
]).
|
]).
|
||||||
|
|
||||||
|
-export([
|
||||||
|
range/4,
|
||||||
|
range_seconds/3,
|
||||||
|
range_minutes/3,
|
||||||
|
range_hours/3,
|
||||||
|
range_days/3,
|
||||||
|
range_weeks/3,
|
||||||
|
range_months/3,
|
||||||
|
range_years/3
|
||||||
|
]).
|
||||||
|
|
||||||
|
-export([
|
||||||
|
age/1,
|
||||||
|
age/2,
|
||||||
|
age_days/1,
|
||||||
|
age_days/2
|
||||||
|
]).
|
||||||
|
|
||||||
-export([
|
-export([
|
||||||
register_parser/2,
|
register_parser/2,
|
||||||
register_parser/1,
|
register_parser/1,
|
||||||
|
@ -66,6 +129,9 @@
|
||||||
clear_timezone/1
|
clear_timezone/1
|
||||||
]).
|
]).
|
||||||
|
|
||||||
|
-export([
|
||||||
|
parse_relative/1
|
||||||
|
]).
|
||||||
|
|
||||||
%% Exported for API compatibility with ec_date
|
%% Exported for API compatibility with ec_date
|
||||||
-export([
|
-export([
|
||||||
|
@ -74,28 +140,32 @@
|
||||||
parse/1
|
parse/1
|
||||||
]).
|
]).
|
||||||
|
|
||||||
|
-type qdate() :: any().
|
||||||
|
-type datetime() :: {{integer(), integer(), integer()}, {integer(), integer(), integer()}} |
|
||||||
|
{{integer(), integer(), integer()}, {integer(), integer(), integer(), integer()}}.
|
||||||
|
-type erlnow() :: {integer(), integer(), integer()}.
|
||||||
|
-type binary_or_string() :: binary() | string().
|
||||||
|
-type disambiguate() :: prefer_standard | prefer_daylight | both.
|
||||||
|
|
||||||
|
%% erlang:get_stacktrace/0 is deprecated in OTP 21
|
||||||
|
-ifndef(OTP_RELEASE).
|
||||||
|
-define(WITH_STACKTRACE(T, R, S), T:R -> S = erlang:get_stacktrace(), ).
|
||||||
|
-else.
|
||||||
|
-define(WITH_STACKTRACE(T, R, S), T:R:S ->).
|
||||||
|
-endif.
|
||||||
|
|
||||||
%% This the value in gregorian seconds for jan 1st 1970, 12am
|
%% This the value in gregorian seconds for jan 1st 1970, 12am
|
||||||
%% It's used to convert to and from unixtime, since unixtime starts
|
%% It's used to convert to and from unixtime, since unixtime starts
|
||||||
%% 1970-01-01 12:00am
|
%% 1970-01-01 12:00am
|
||||||
-define(UNIXTIME_BASE,62167219200).
|
-define(UNIXTIME_BASE,62167219200).
|
||||||
|
|
||||||
%% This is the timezone only if the qdate application variable
|
|
||||||
%% "default_timezone" isn't set or is set to undefined.
|
|
||||||
%% It's recommended that your app sets the var in a config, or at least using
|
|
||||||
%%
|
|
||||||
%% application:set_env(qdate, default_timezone, "GMT").
|
|
||||||
%%
|
|
||||||
-define(DEFAULT_TZ, case application:get_env(qdate, default_timezone) of
|
|
||||||
undefined -> "GMT";
|
|
||||||
{ok, TZ} -> TZ
|
|
||||||
end).
|
|
||||||
|
|
||||||
-define(DETERMINE_TZ, determine_timezone()).
|
-define(DETERMINE_TZ, determine_timezone()).
|
||||||
-define(DEFAULT_DISAMBIG, prefer_standard).
|
-define(DEFAULT_DISAMBIG, prefer_standard).
|
||||||
-define(else, true).
|
|
||||||
|
|
||||||
start() ->
|
start() ->
|
||||||
application:load(qdate).
|
application:ensure_all_started(qdate).
|
||||||
|
|
||||||
stop() ->
|
stop() ->
|
||||||
ok.
|
ok.
|
||||||
|
@ -109,6 +179,7 @@ to_string(Format, Date) ->
|
||||||
to_string(Format, ToTZ, Date) ->
|
to_string(Format, ToTZ, Date) ->
|
||||||
to_string(Format, ToTZ, ?DEFAULT_DISAMBIG, Date).
|
to_string(Format, ToTZ, ?DEFAULT_DISAMBIG, Date).
|
||||||
|
|
||||||
|
-spec to_string(Format :: any(), ToTZ :: any(), Disambiguate :: disambiguate(), Date :: qdate()) -> binary_or_string() | {ambiguous, binary_or_string() , binary_or_string()}.
|
||||||
to_string(FormatKey, ToTZ, Disambiguate, Date) when is_atom(FormatKey) orelse is_tuple(FormatKey) ->
|
to_string(FormatKey, ToTZ, Disambiguate, Date) when is_atom(FormatKey) orelse is_tuple(FormatKey) ->
|
||||||
Format = case qdate_srv:get_format(FormatKey) of
|
Format = case qdate_srv:get_format(FormatKey) of
|
||||||
undefined -> throw({undefined_format_key,FormatKey});
|
undefined -> throw({undefined_format_key,FormatKey});
|
||||||
|
@ -175,7 +246,13 @@ to_string_worker([$r | RestFormat], ToTZ, Disamb, Date) ->
|
||||||
to_string_worker(NewFormat, ToTZ, Disamb, Date) ++ to_string_worker(RestFormat, ToTZ, Disamb, Date);
|
to_string_worker(NewFormat, ToTZ, Disamb, Date) ++ to_string_worker(RestFormat, ToTZ, Disamb, Date);
|
||||||
to_string_worker([$c | RestFormat], ToTZ, Disamb, Date) ->
|
to_string_worker([$c | RestFormat], ToTZ, Disamb, Date) ->
|
||||||
Format1 = "Y-m-d",
|
Format1 = "Y-m-d",
|
||||||
Format2 = "H:i:sP",
|
Format2 = case Date of
|
||||||
|
{_, {_,_,_,_}} ->
|
||||||
|
%% Have milliseconds
|
||||||
|
"H:i:s.fP";
|
||||||
|
_ ->
|
||||||
|
"H:i:sP"
|
||||||
|
end,
|
||||||
to_string_worker(Format1, ToTZ, Disamb, Date)
|
to_string_worker(Format1, ToTZ, Disamb, Date)
|
||||||
++ "T"
|
++ "T"
|
||||||
++ to_string_worker(Format2, ToTZ, Disamb, Date)
|
++ to_string_worker(Format2, ToTZ, Disamb, Date)
|
||||||
|
@ -226,6 +303,7 @@ to_date(RawDate) ->
|
||||||
to_date(ToTZ, RawDate) ->
|
to_date(ToTZ, RawDate) ->
|
||||||
to_date(ToTZ, ?DEFAULT_DISAMBIG, RawDate).
|
to_date(ToTZ, ?DEFAULT_DISAMBIG, RawDate).
|
||||||
|
|
||||||
|
-spec to_date(ToTZ :: any(), Disambiguate :: disambiguate(), RawDate :: any()) -> {ambiguous, datetime(), datetime()} | datetime().
|
||||||
to_date(ToTZ, Disambiguate, RawDate) when is_binary(RawDate) ->
|
to_date(ToTZ, Disambiguate, RawDate) when is_binary(RawDate) ->
|
||||||
to_date(ToTZ, Disambiguate, binary_to_list(RawDate));
|
to_date(ToTZ, Disambiguate, binary_to_list(RawDate));
|
||||||
to_date(ToTZ, Disambiguate, RawDate) when is_binary(ToTZ) ->
|
to_date(ToTZ, Disambiguate, RawDate) when is_binary(ToTZ) ->
|
||||||
|
@ -240,9 +318,13 @@ to_date(ToTZ, Disambiguate, RawDate) ->
|
||||||
{ParsedDate,ParsedTZ} ->
|
{ParsedDate,ParsedTZ} ->
|
||||||
{ParsedDate,ParsedTZ}
|
{ParsedDate,ParsedTZ}
|
||||||
end,
|
end,
|
||||||
|
PreserveMs = preserve_ms(),
|
||||||
try raw_to_date(RawDate3) of
|
try raw_to_date(RawDate3) of
|
||||||
D={{_,_,_},{_,_,_}} ->
|
D={{_,_,_},{_,_,_}} ->
|
||||||
date_tz_to_tz(D, Disambiguate, FromTZ, ToTZ);
|
date_tz_to_tz(D, Disambiguate, FromTZ, ToTZ);
|
||||||
|
{{Year, Month, Date},{Hour,Minute,Second,Millis}} when PreserveMs ->
|
||||||
|
{ODate, {OHour,OMinute,OSecond}} = date_tz_to_tz({{Year, Month, Date},{Hour,Minute,Second}}, Disambiguate, FromTZ, ToTZ),
|
||||||
|
{ODate, {OHour,OMinute,OSecond, Millis}};
|
||||||
{{Year, Month, Date},{Hour,Minute,Second,_Millis}} ->
|
{{Year, Month, Date},{Hour,Minute,Second,_Millis}} ->
|
||||||
date_tz_to_tz({{Year, Month, Date},{Hour,Minute,Second}}, Disambiguate, FromTZ, ToTZ)
|
date_tz_to_tz({{Year, Month, Date},{Hour,Minute,Second}}, Disambiguate, FromTZ, ToTZ)
|
||||||
catch
|
catch
|
||||||
|
@ -279,9 +361,10 @@ get_deterministic_datetime() ->
|
||||||
to_unixtime(Date) ->
|
to_unixtime(Date) ->
|
||||||
to_unixtime(?DEFAULT_DISAMBIG, Date).
|
to_unixtime(?DEFAULT_DISAMBIG, Date).
|
||||||
|
|
||||||
|
-spec to_unixtime(Disamb :: disambiguate(), qdate()) -> {ambiguous, integer(), integer()} | integer().
|
||||||
to_unixtime(_, Unixtime) when is_integer(Unixtime) ->
|
to_unixtime(_, Unixtime) when is_integer(Unixtime) ->
|
||||||
Unixtime;
|
Unixtime;
|
||||||
to_unixtime(_, {MegaSecs,Secs,_}) ->
|
to_unixtime(_, {MegaSecs,Secs,_}) when is_integer(MegaSecs), is_integer(Secs) ->
|
||||||
MegaSecs*1000000 + Secs;
|
MegaSecs*1000000 + Secs;
|
||||||
to_unixtime(Disamb, ToParse) ->
|
to_unixtime(Disamb, ToParse) ->
|
||||||
%% We want to treat all unixtimes as GMT
|
%% We want to treat all unixtimes as GMT
|
||||||
|
@ -300,11 +383,12 @@ unixtime() ->
|
||||||
to_now(Date) ->
|
to_now(Date) ->
|
||||||
to_now(?DEFAULT_DISAMBIG, Date).
|
to_now(?DEFAULT_DISAMBIG, Date).
|
||||||
|
|
||||||
|
-spec to_now(Disamb :: disambiguate(), qdate()) -> erlnow() | {ambiguous, erlnow(), erlnow()}.
|
||||||
to_now(_, Now = {_,_,_}) ->
|
to_now(_, Now = {_,_,_}) ->
|
||||||
Now;
|
Now;
|
||||||
to_now(Disamb, ToParse) ->
|
to_now(Disamb, ToParse) ->
|
||||||
case to_unixtime(Disamb, ToParse) of
|
case to_unixtime(Disamb, ToParse) of
|
||||||
{ambiguous, Standard, Daylight} ->
|
{ambiguous, Standard, Daylight} when is_integer(Standard), is_integer(Daylight) ->
|
||||||
{ambiguous,
|
{ambiguous,
|
||||||
unixtime_to_now(Standard),
|
unixtime_to_now(Standard),
|
||||||
unixtime_to_now(Daylight)};
|
unixtime_to_now(Daylight)};
|
||||||
|
@ -313,10 +397,144 @@ to_now(Disamb, ToParse) ->
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
|
||||||
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||||
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Beginning/Truncation %%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||||
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||||
|
|
||||||
|
beginning_minute() ->
|
||||||
|
beginning_minute({date(),time()}).
|
||||||
|
|
||||||
|
beginning_minute(Date) ->
|
||||||
|
{{Y,M,D},{H,I,_}} = to_date(Date),
|
||||||
|
{{Y,M,D},{H,I,0}}.
|
||||||
|
|
||||||
|
beginning_hour() ->
|
||||||
|
beginning_hour({date(),time()}).
|
||||||
|
|
||||||
|
beginning_hour(Date) ->
|
||||||
|
{{Y,M,D},{H,_,_}} = to_date(Date),
|
||||||
|
{{Y,M,D},{H,0,0}}.
|
||||||
|
|
||||||
|
beginning_day() ->
|
||||||
|
beginning_day(unixtime()).
|
||||||
|
|
||||||
|
beginning_day(Date) ->
|
||||||
|
{{Y,M,D},{_,_,_}} = to_date(Date),
|
||||||
|
{{Y,M,D},{0,0,0}}.
|
||||||
|
|
||||||
|
beginning_month() ->
|
||||||
|
beginning_month({date(),time()}).
|
||||||
|
|
||||||
|
beginning_month(Date) ->
|
||||||
|
{{Y,M,_},{_,_,_}} = to_date(Date),
|
||||||
|
{{Y,M,1},{0,0,0}}.
|
||||||
|
|
||||||
|
beginning_year() ->
|
||||||
|
beginning_year({date(),time()}).
|
||||||
|
|
||||||
|
beginning_year(Date) ->
|
||||||
|
{{Y,_,_},{_,_,_}} = to_date(Date),
|
||||||
|
{{Y,1,1},{0,0,0}}.
|
||||||
|
|
||||||
|
beginning_week() ->
|
||||||
|
beginning_week({date(), time()}).
|
||||||
|
|
||||||
|
%% 1 = Monday, 7 = Sunday
|
||||||
|
beginning_week(Date) ->
|
||||||
|
beginning_week(1, Date).
|
||||||
|
|
||||||
|
beginning_week(BeginningDayOfWeek, Date) when is_atom(BeginningDayOfWeek) ->
|
||||||
|
DOW = weekday_map(BeginningDayOfWeek),
|
||||||
|
beginning_week(DOW, Date);
|
||||||
|
beginning_week(BeginningDayOfWeek, Date0) when
|
||||||
|
BeginningDayOfWeek >= 1,
|
||||||
|
BeginningDayOfWeek =< 7,
|
||||||
|
is_integer(BeginningDayOfWeek) ->
|
||||||
|
{DateOnly, _} = Date = to_date(Date0),
|
||||||
|
CurDOW = calendar:day_of_the_week(DateOnly),
|
||||||
|
if
|
||||||
|
CurDOW==BeginningDayOfWeek ->
|
||||||
|
{DateOnly, {0,0,0}};
|
||||||
|
CurDOW > BeginningDayOfWeek->
|
||||||
|
Diff = CurDOW - BeginningDayOfWeek,
|
||||||
|
beginning_day(add_days(-Diff, Date));
|
||||||
|
CurDOW < BeginningDayOfWeek ->
|
||||||
|
Diff = 7 - (BeginningDayOfWeek - CurDOW),
|
||||||
|
beginning_day(add_days(-Diff, Date))
|
||||||
|
end.
|
||||||
|
|
||||||
|
weekday_map(mon) -> 1;
|
||||||
|
weekday_map(tue) -> 2;
|
||||||
|
weekday_map(wed) -> 3;
|
||||||
|
weekday_map(thu) -> 4;
|
||||||
|
weekday_map(fri) -> 5;
|
||||||
|
weekday_map(sat) -> 6;
|
||||||
|
weekday_map(sun) -> 7;
|
||||||
|
|
||||||
|
weekday_map(monday) -> 1;
|
||||||
|
weekday_map(tuesday) -> 2;
|
||||||
|
weekday_map(wednesday) -> 3;
|
||||||
|
weekday_map(thursday) -> 4;
|
||||||
|
weekday_map(friday) -> 5;
|
||||||
|
weekday_map(saturday) -> 6;
|
||||||
|
weekday_map(sunday) -> 7.
|
||||||
|
|
||||||
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||||
|
%%%%%%%%%%%%%%%%%%%%%% End of Period (day/hour, etc) %%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||||
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||||
|
|
||||||
|
|
||||||
|
end_minute() ->
|
||||||
|
end_minute({date(),time()}).
|
||||||
|
|
||||||
|
end_minute(Date) ->
|
||||||
|
{{Y,M,D},{H,I,_}} = to_date(Date),
|
||||||
|
{{Y,M,D},{H,I,59}}.
|
||||||
|
|
||||||
|
end_hour() ->
|
||||||
|
end_hour({date(),time()}).
|
||||||
|
|
||||||
|
end_hour(Date) ->
|
||||||
|
{{Y,M,D},{H,_,_}} = to_date(Date),
|
||||||
|
{{Y,M,D},{H,59,59}}.
|
||||||
|
|
||||||
|
end_day() ->
|
||||||
|
end_day({date(),time()}).
|
||||||
|
|
||||||
|
end_day(Date) ->
|
||||||
|
{{Y,M,D},_} = to_date(Date),
|
||||||
|
{{Y,M,D},{23,59,59}}.
|
||||||
|
|
||||||
|
end_month() ->
|
||||||
|
end_month({date(), time()}).
|
||||||
|
|
||||||
|
end_month(Date) ->
|
||||||
|
Beginning = beginning_month(Date),
|
||||||
|
add_seconds(-1, add_months(1, Beginning)).
|
||||||
|
|
||||||
|
end_year() ->
|
||||||
|
end_year({date(),time()}).
|
||||||
|
|
||||||
|
end_year(Date) ->
|
||||||
|
{{Y,_,_},_} = to_date(Date),
|
||||||
|
{{Y,12,31},{23,59,59}}.
|
||||||
|
|
||||||
|
end_week() ->
|
||||||
|
end_week({date(), time()}).
|
||||||
|
|
||||||
|
end_week(Date) ->
|
||||||
|
end_week(1, Date).
|
||||||
|
|
||||||
|
end_week(BeginningDayOfWeek, Date) ->
|
||||||
|
Beginning = beginning_week(BeginningDayOfWeek, Date),
|
||||||
|
PlusWeek = add_weeks(1, Beginning),
|
||||||
|
add_seconds(-1, PlusWeek).
|
||||||
|
|
||||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Comparisons %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Comparisons %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||||
|
|
||||||
|
-spec compare(A :: qdate(), B :: qdate()) -> integer().
|
||||||
compare(A, B) ->
|
compare(A, B) ->
|
||||||
NowA = to_now(A),
|
NowA = to_now(A),
|
||||||
NowB = to_now(B),
|
NowB = to_now(B),
|
||||||
|
@ -326,6 +544,7 @@ compare(A, B) ->
|
||||||
NowA > NowB -> 1
|
NowA > NowB -> 1
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
-spec compare(A :: qdate(), Op :: atom(), B :: qdate()) -> boolean().
|
||||||
compare(A, Op, B) ->
|
compare(A, Op, B) ->
|
||||||
Comp = compare(A, B),
|
Comp = compare(A, B),
|
||||||
case Op of
|
case Op of
|
||||||
|
@ -336,15 +555,93 @@ compare(A, Op, B) ->
|
||||||
'=/=' -> Comp =/= 0;
|
'=/=' -> Comp =/= 0;
|
||||||
'/=' -> Comp =/= 0;
|
'/=' -> Comp =/= 0;
|
||||||
|
|
||||||
|
'before'-> Comp =:= -1;
|
||||||
'<' -> Comp =:= -1;
|
'<' -> Comp =:= -1;
|
||||||
'<=' -> Comp =:= -1 orelse Comp =:= 0;
|
'<=' -> Comp =:= -1 orelse Comp =:= 0;
|
||||||
'=<' -> Comp =:= -1 orelse Comp =:= 0;
|
'=<' -> Comp =:= -1 orelse Comp =:= 0;
|
||||||
|
|
||||||
|
'after' -> Comp =:= 1;
|
||||||
'>' -> Comp =:= 1;
|
'>' -> Comp =:= 1;
|
||||||
'>=' -> Comp =:= 1 orelse Comp =:= 0;
|
'>=' -> Comp =:= 1 orelse Comp =:= 0;
|
||||||
'=>' -> Comp =:= 1 orelse Comp =:= 0
|
'=>' -> Comp =:= 1 orelse Comp =:= 0
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
between(A, B) ->
|
||||||
|
between(A, unixtime(), B).
|
||||||
|
|
||||||
|
between(A, Date, B) ->
|
||||||
|
between(A, '=<', Date, '=<', B).
|
||||||
|
|
||||||
|
between(A, Op1, Date, Op2, B) ->
|
||||||
|
compare(A, Op1, Date) andalso compare(Date, Op2, B).
|
||||||
|
|
||||||
|
|
||||||
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||||
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Sorting %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||||
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||||
|
|
||||||
|
sort(List) ->
|
||||||
|
sort('=<', List).
|
||||||
|
|
||||||
|
sort(Op, List) ->
|
||||||
|
sort(Op, List, [{non_dates, back}]).
|
||||||
|
|
||||||
|
sort(Op, List, Opts) ->
|
||||||
|
NonDateOpt = proplists:get_value(non_dates, Opts, back),
|
||||||
|
WithNorm = add_sort_normalization(List, NonDateOpt),
|
||||||
|
SortFun = make_sort_fun(Op, NonDateOpt),
|
||||||
|
Sorted = lists:sort(SortFun, WithNorm),
|
||||||
|
strip_sort_normalization(Sorted).
|
||||||
|
|
||||||
|
%% Normalization pre-processes the dates (converting them to unixtimes for easy
|
||||||
|
%% comparison, and also tags non-dates (dates that crashed during parsing) as such
|
||||||
|
add_sort_normalization(List, NonDateOpt) ->
|
||||||
|
lists:map(fun(Date) ->
|
||||||
|
Sortable = try to_unixtime(Date)
|
||||||
|
catch _:_ when NonDateOpt=/=crash ->
|
||||||
|
{non_date, Date}
|
||||||
|
end,
|
||||||
|
{Sortable, Date}
|
||||||
|
end, List).
|
||||||
|
|
||||||
|
%% Remove the normalization tag to return the original term
|
||||||
|
strip_sort_normalization(List) ->
|
||||||
|
[Date || {_, Date} <- List].
|
||||||
|
|
||||||
|
-spec make_sort_fun(Op :: atom(), NonDateOpt :: front | back) -> fun().
|
||||||
|
make_sort_fun(Op, NonDateOpt) ->
|
||||||
|
DateComp = sort_op_comp_fun(Op),
|
||||||
|
|
||||||
|
fun({{non_date, A}, _}, {{non_date, B},_}) ->
|
||||||
|
DateComp(A,B);
|
||||||
|
({{non_date, _}, _}, _) when NonDateOpt == front ->
|
||||||
|
true;
|
||||||
|
({{non_date, _}, _}, _) when NonDateOpt == back ->
|
||||||
|
false;
|
||||||
|
(_, {{non_date, _}, _}) when NonDateOpt == front ->
|
||||||
|
false;
|
||||||
|
(_, {{non_date, _}, _}) when NonDateOpt == back ->
|
||||||
|
true;
|
||||||
|
(A, B) ->
|
||||||
|
DateComp(A, B)
|
||||||
|
end.
|
||||||
|
|
||||||
|
sort_op_comp_fun(Op) ->
|
||||||
|
fun(A, B) ->
|
||||||
|
case Op of
|
||||||
|
'before'-> A < B;
|
||||||
|
'<' -> A < B;
|
||||||
|
'<=' -> A =< B;
|
||||||
|
'=<' -> A =< B;
|
||||||
|
|
||||||
|
'after' -> A > B;
|
||||||
|
'>' -> A > B;
|
||||||
|
'>=' -> A >= B;
|
||||||
|
'=>' -> A >= B
|
||||||
|
end
|
||||||
|
end.
|
||||||
|
|
||||||
|
|
||||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Date Math %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Date Math %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||||
|
@ -405,6 +702,48 @@ add_years(Years, Date) ->
|
||||||
add_years(Years) ->
|
add_years(Years) ->
|
||||||
add_years(Years, os:timestamp()).
|
add_years(Years, os:timestamp()).
|
||||||
|
|
||||||
|
-type unit() :: second | seconds |
|
||||||
|
minute | minutes |
|
||||||
|
hour | hours |
|
||||||
|
day | days |
|
||||||
|
week | weeks |
|
||||||
|
month | months |
|
||||||
|
year | years.
|
||||||
|
|
||||||
|
-spec add_unit(Unit :: unit(), Value :: integer(), Date :: qdate()) -> qdate().
|
||||||
|
add_unit(second, Value, Date) ->
|
||||||
|
add_unit(seconds, Value, Date);
|
||||||
|
add_unit(seconds, Value, Date) ->
|
||||||
|
add_seconds(Value, Date);
|
||||||
|
add_unit(minute, Value, Date) ->
|
||||||
|
add_unit(minutes, Value, Date);
|
||||||
|
add_unit(minutes, Value, Date) ->
|
||||||
|
add_minutes(Value, Date);
|
||||||
|
add_unit(hour, Value, Date) ->
|
||||||
|
add_unit(hours, Value, Date);
|
||||||
|
add_unit(hours, Value, Date) ->
|
||||||
|
add_hours(Value, Date);
|
||||||
|
add_unit(day, Value, Date) ->
|
||||||
|
add_unit(days, Value, Date);
|
||||||
|
add_unit(days, Value, Date) ->
|
||||||
|
add_days(Value, Date);
|
||||||
|
add_unit(week, Value, Date) ->
|
||||||
|
add_unit(weeks, Value, Date);
|
||||||
|
add_unit(weeks, Value, Date) ->
|
||||||
|
add_weeks(Value, Date);
|
||||||
|
add_unit(month, Value, Date) ->
|
||||||
|
add_unit(months, Value, Date);
|
||||||
|
add_unit(months, Value, Date) ->
|
||||||
|
add_months(Value, Date);
|
||||||
|
add_unit(year, Value, Date) ->
|
||||||
|
add_unit(years, Value, Date);
|
||||||
|
add_unit(years, Value, Date) ->
|
||||||
|
add_years(Value, Date).
|
||||||
|
|
||||||
|
add_unit(Unit, Value) ->
|
||||||
|
add_unit(Unit, Value, os:timestamp()).
|
||||||
|
|
||||||
|
|
||||||
add_date({{AddY, AddM, AddD}, {AddH, AddI, AddS}}, Date) ->
|
add_date({{AddY, AddM, AddD}, {AddH, AddI, AddS}}, Date) ->
|
||||||
{{Y, M, D}, {H, I, S}} = to_date(Date),
|
{{Y, M, D}, {H, I, S}} = to_date(Date),
|
||||||
Date1 = fix_maybe_improper_date({{Y+AddY, M+AddM, D+AddD}, {H, I, S}}),
|
Date1 = fix_maybe_improper_date({{Y+AddY, M+AddM, D+AddD}, {H, I, S}}),
|
||||||
|
@ -420,12 +759,22 @@ fix_maybe_improper_date({Date0, Time}) ->
|
||||||
Date = fmid(Date0),
|
Date = fmid(Date0),
|
||||||
{Date, Time}.
|
{Date, Time}.
|
||||||
|
|
||||||
|
|
||||||
|
%% Originally, this function didn't recurse. Here's the story. Some numbers,
|
||||||
|
%% like M = 12 (December) or M = -11 (January) would trigger an overflow or
|
||||||
|
%% underflow, resulting in fix_year_month returning something nonsensical like
|
||||||
|
%% {2018, 13}. I added some extra clauses to special treat those "overflow but
|
||||||
|
%% shouldn't" situations, but realized it was just cleaner to recurse, calling
|
||||||
|
%% fix_year_month on the calculated result, knowing that the numbers will
|
||||||
|
%% normalize on their own. So for all the clauses of fix_year_month, we recurse
|
||||||
|
%% as a sanity check, eventually only returning the result of the "Everything
|
||||||
|
%% Looks good" clause at the bottom.
|
||||||
fix_year_month({Y, M}) when M > 12 ->
|
fix_year_month({Y, M}) when M > 12 ->
|
||||||
YearsOver = M div 12,
|
YearsOver = M div 12,
|
||||||
{Y + YearsOver, M-(YearsOver*12)};
|
fix_year_month({Y + YearsOver, M-(YearsOver*12)});
|
||||||
fix_year_month({Y, M}) when M < 1 ->
|
fix_year_month({Y, M}) when M < 1 ->
|
||||||
YearsUnder = (abs(M-1) div 12) + 1,
|
YearsUnder = (abs(M-1) div 12) + 1,
|
||||||
{Y - YearsUnder, M+(YearsUnder*12)};
|
fix_year_month({Y - YearsUnder, M+(YearsUnder*12)});
|
||||||
fix_year_month({Y, M}) ->
|
fix_year_month({Y, M}) ->
|
||||||
{Y, M}.
|
{Y, M}.
|
||||||
|
|
||||||
|
@ -460,6 +809,170 @@ fmid({Y, M, D}) when D < 1 ->
|
||||||
fmid(Date) ->
|
fmid(Date) ->
|
||||||
Date.
|
Date.
|
||||||
|
|
||||||
|
age(Birth) ->
|
||||||
|
age(Birth, os:timestamp()).
|
||||||
|
|
||||||
|
age(Birth, Now) ->
|
||||||
|
%% B=Birth
|
||||||
|
{{BY, BM, BD}, _} = to_date(Birth),
|
||||||
|
%% C=Current
|
||||||
|
{{CY, CM, CD}, _} = to_date(Now),
|
||||||
|
if
|
||||||
|
(CM > BM);
|
||||||
|
(CM == BM andalso CD >= BD)
|
||||||
|
-> CY - BY;
|
||||||
|
true ->
|
||||||
|
(CY - BY) - 1
|
||||||
|
end.
|
||||||
|
|
||||||
|
age_days(Birth) ->
|
||||||
|
age_days(Birth, os:timestamp()).
|
||||||
|
|
||||||
|
age_days(Birth, Now) ->
|
||||||
|
case {to_date(Birth), to_date(Now)} of
|
||||||
|
{{SameDay, _}, {SameDay, _}} ->
|
||||||
|
0;
|
||||||
|
{{BirthDate, BirthTime}, {NowDate, NowTime}} ->
|
||||||
|
BirthDays = calendar:date_to_gregorian_days(BirthDate),
|
||||||
|
NowDays = calendar:date_to_gregorian_days(NowDate),
|
||||||
|
DiffDays = NowDays - BirthDays,
|
||||||
|
if
|
||||||
|
NowTime >= BirthTime ->
|
||||||
|
DiffDays;
|
||||||
|
true ->
|
||||||
|
DiffDays-1
|
||||||
|
end
|
||||||
|
end.
|
||||||
|
|
||||||
|
|
||||||
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||||
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Ranges %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||||
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||||
|
|
||||||
|
range(seconds, Interval, Start, Finish) ->
|
||||||
|
range_inner(fun add_seconds/2, Interval, Start, Finish);
|
||||||
|
range(minutes, Interval, Start, Finish) ->
|
||||||
|
range_inner(fun add_minutes/2, Interval, Start, Finish);
|
||||||
|
range(hours, Interval, Start, Finish) ->
|
||||||
|
range_inner(fun add_hours/2, Interval, Start, Finish);
|
||||||
|
range(days, Interval, Start, Finish) ->
|
||||||
|
range_inner(fun add_days/2, Interval, Start, Finish);
|
||||||
|
range(weeks, Interval, Start, Finish) ->
|
||||||
|
range_inner(fun add_weeks/2, Interval, Start, Finish);
|
||||||
|
range(months, Interval, Start, Finish) ->
|
||||||
|
range_inner(fun add_months/2, Interval, Start, Finish);
|
||||||
|
range(years, Interval, Start, Finish) ->
|
||||||
|
range_inner(fun add_years/2, Interval, Start, Finish).
|
||||||
|
|
||||||
|
range_inner(IntervalFun, Interval, Start, Finish) when Interval > 0 ->
|
||||||
|
%% If Interval>0, then we're ascending, and we want to compare start/end
|
||||||
|
%% dates normally
|
||||||
|
CompareFun = fun(S, F) -> compare(S, F) end,
|
||||||
|
range_normalizer(IntervalFun, Interval, CompareFun, Start, Finish);
|
||||||
|
range_inner(IntervalFun, Interval, Start, Finish) when Interval < 0 ->
|
||||||
|
%% If Interval<0, then we're descending, and we want to compare start/end
|
||||||
|
%% dates backwards (we want to end when the Start Date is Lower than
|
||||||
|
%% Finish)
|
||||||
|
CompareFun = fun(S, F) -> compare(F, S) end,
|
||||||
|
range_normalizer(IntervalFun, Interval, CompareFun, Start, Finish);
|
||||||
|
range_inner(_, Interval, _, _) when Interval==0 ->
|
||||||
|
throw(interval_cannot_be_zero).
|
||||||
|
|
||||||
|
range_normalizer(IntervalFun, Interval, CompareFun, Start0, Finish0) ->
|
||||||
|
%% Convert dates to unixtime for speed's sake
|
||||||
|
Start = to_unixtime(Start0),
|
||||||
|
Finish = to_unixtime(Finish0),
|
||||||
|
%% Prepare the incrementer, so we just need to pass the date to the incrementer.
|
||||||
|
Incrementer = fun(D) -> IntervalFun(Interval, D) end,
|
||||||
|
range_worker(Incrementer, CompareFun, Start, Finish).
|
||||||
|
|
||||||
|
range_worker(Incrementer, CompareFun, Start, Finish) ->
|
||||||
|
case CompareFun(Start, Finish) of
|
||||||
|
0 -> [Finish]; %% Equal, so we add our Finish value
|
||||||
|
1 -> []; %% Start is after Finish, so we add nothing
|
||||||
|
-1 -> %% Start is before Finish, so we include it, and recurse
|
||||||
|
NextDay = Incrementer(Start),
|
||||||
|
[Start | range_worker(Incrementer, CompareFun, NextDay, Finish)]
|
||||||
|
end.
|
||||||
|
|
||||||
|
range_seconds(Interval, Start, Finish) ->
|
||||||
|
range(seconds, Interval, Start, Finish).
|
||||||
|
|
||||||
|
range_minutes(Interval, Start, Finish) ->
|
||||||
|
range(minutes, Interval, Start, Finish).
|
||||||
|
|
||||||
|
range_hours(Interval, Start, Finish) ->
|
||||||
|
range(hours, Interval, Start, Finish).
|
||||||
|
|
||||||
|
range_days(Interval, Start, Finish) ->
|
||||||
|
range(days, Interval, Start, Finish).
|
||||||
|
|
||||||
|
range_weeks(Interval, Start, Finish) ->
|
||||||
|
range(weeks, Interval, Start, Finish).
|
||||||
|
|
||||||
|
range_months(Interval, Start, Finish) ->
|
||||||
|
range(months, Interval, Start, Finish).
|
||||||
|
|
||||||
|
range_years(Interval, Start, Finish) ->
|
||||||
|
range(years, Interval, Start, Finish).
|
||||||
|
|
||||||
|
|
||||||
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||||
|
%%%%%%%%%%%%%%%%%%%%%%%%%%% Relative Date Parsing %%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||||
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||||
|
|
||||||
|
parse_relative({relative, Date, Relation}) when is_atom(Relation) ->
|
||||||
|
parse_relative({relative, Date, atom_to_list(Relation)});
|
||||||
|
parse_relative({relative, Date, Relation}) when is_list(Relation); is_binary(Relation) ->
|
||||||
|
case parse_actual_relation(Relation) of
|
||||||
|
undefined -> undefined;
|
||||||
|
{OpStr, NumStr, UnitStr} ->
|
||||||
|
{Num, Unit} = normalize_relative_matches(OpStr, NumStr, UnitStr),
|
||||||
|
add_unit(Unit, Num, Date)
|
||||||
|
end;
|
||||||
|
parse_relative(now) ->
|
||||||
|
unixtime();
|
||||||
|
parse_relative("now") ->
|
||||||
|
unixtime();
|
||||||
|
parse_relative(<<"now">>) ->
|
||||||
|
unixtime();
|
||||||
|
parse_relative(Relation) when is_list(Relation); is_binary(Relation) ->
|
||||||
|
parse_relative({relative, unixtime(), Relation});
|
||||||
|
parse_relative(_) ->
|
||||||
|
undefined.
|
||||||
|
|
||||||
|
|
||||||
|
%% I would do this function recursively, but the return order of arguments
|
||||||
|
%% inconsistent, so I just leave it like this. It's a little nasty to have the
|
||||||
|
%% nested case expressions, but I can deal with it.
|
||||||
|
parse_actual_relation(Relation) ->
|
||||||
|
PrefixRE = "^(\\-|\\+|in)\\s?(\\d+) (second|minute|hour|day|week|month|year)s?$",
|
||||||
|
SuffixRE = "^(\\d+) (second|minute|hour|day|week|month|year)s?\\s?(ago|from now)?$",
|
||||||
|
case re:run(Relation, PrefixRE, [{capture, all_but_first, list}]) of
|
||||||
|
nomatch ->
|
||||||
|
case re:run(Relation, SuffixRE, [{capture, all_but_first, list}]) of
|
||||||
|
nomatch -> undefined;
|
||||||
|
{match, [NumStr, UnitStr, OpStr]} ->
|
||||||
|
{OpStr, NumStr, UnitStr}
|
||||||
|
end;
|
||||||
|
{match, [OpStr, NumStr, UnitStr]} ->
|
||||||
|
{OpStr, NumStr, UnitStr}
|
||||||
|
end.
|
||||||
|
|
||||||
|
normalize_relative_matches(OpStr, NumStr, UnitStr) ->
|
||||||
|
Op = normalize_relative_op(OpStr),
|
||||||
|
Num = list_to_integer(Op ++ NumStr),
|
||||||
|
Unit = list_to_existing_atom(UnitStr),
|
||||||
|
{Num, Unit}.
|
||||||
|
|
||||||
|
normalize_relative_op(Op) ->
|
||||||
|
case Op of
|
||||||
|
"+" -> "+";
|
||||||
|
"-" -> "-";
|
||||||
|
"ago" -> "-";
|
||||||
|
"from now" -> "+";
|
||||||
|
"in" -> "+"
|
||||||
|
end.
|
||||||
|
|
||||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Timezone Stuff %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Timezone Stuff %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||||
|
@ -471,6 +984,7 @@ get_timezone_shift(TZ, Disambiguate, Date) ->
|
||||||
{error,T} -> {error,T};
|
{error,T} -> {error,T};
|
||||||
{Sh, _} when Disambiguate==prefer_standard -> Sh;
|
{Sh, _} when Disambiguate==prefer_standard -> Sh;
|
||||||
{_, Sh} when Disambiguate==prefer_daylight -> Sh;
|
{_, Sh} when Disambiguate==prefer_daylight -> Sh;
|
||||||
|
0 -> {'+', 0, 0};
|
||||||
Sh -> Sh
|
Sh -> Sh
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
@ -488,6 +1002,8 @@ extract_timezone(DateString) when is_list(DateString) ->
|
||||||
end;
|
end;
|
||||||
extract_timezone(Date={{_,_,_},{_,_,_}}) ->
|
extract_timezone(Date={{_,_,_},{_,_,_}}) ->
|
||||||
{Date, ?DETERMINE_TZ};
|
{Date, ?DETERMINE_TZ};
|
||||||
|
extract_timezone(Rel={relative, _, _}) ->
|
||||||
|
{Rel, "GMT"};
|
||||||
extract_timezone(Now={_,_,_}) ->
|
extract_timezone(Now={_,_,_}) ->
|
||||||
{Now, "GMT"};
|
{Now, "GMT"};
|
||||||
extract_timezone({MiscDate,TZ}) ->
|
extract_timezone({MiscDate,TZ}) ->
|
||||||
|
@ -523,15 +1039,33 @@ extract_timezone_helper(RevDate, [TZ | TZs]) when length(RevDate) >= length(TZ)
|
||||||
extract_timezone_helper(RevDate, [_TZ | TZs]) ->
|
extract_timezone_helper(RevDate, [_TZ | TZs]) ->
|
||||||
extract_timezone_helper(RevDate, TZs).
|
extract_timezone_helper(RevDate, TZs).
|
||||||
|
|
||||||
|
preserve_ms() ->
|
||||||
|
application:get_env(qdate, preserve_ms, false).
|
||||||
|
|
||||||
|
%% This is the timezone only if the qdate application variable
|
||||||
|
%% "default_timezone" isn't set or is set to undefined.
|
||||||
|
%% It's recommended that your app sets the var in a config, or at least using
|
||||||
|
%%
|
||||||
|
%% application:set_env(qdate, default_timezone, "GMT").
|
||||||
|
%%
|
||||||
|
default_timezone() ->
|
||||||
|
case application:get_env(qdate, default_timezone) of
|
||||||
|
undefined -> "GMT";
|
||||||
|
{ok, {Mod, Fun}} -> Mod:Fun();
|
||||||
|
{ok, TZ} -> TZ
|
||||||
|
end.
|
||||||
|
|
||||||
determine_timezone() ->
|
determine_timezone() ->
|
||||||
case qdate_srv:get_timezone() of
|
case qdate_srv:get_timezone() of
|
||||||
undefined -> ?DEFAULT_TZ;
|
undefined -> default_timezone();
|
||||||
TZ -> TZ
|
TZ -> TZ
|
||||||
end.
|
end.
|
||||||
|
|
||||||
%% If FromTZ is an integer, then it's an integer that represents the number of minutes
|
%% If FromTZ is an integer, then it's an integer that represents the number of minutes
|
||||||
%% relative to GMT. So we convert the date to GMT based on that number, then we can
|
%% relative to GMT. So we convert the date to GMT based on that number, then we can
|
||||||
%% do the other timezone conversion.
|
%% do the other timezone conversion.
|
||||||
|
-spec date_tz_to_tz(Date :: datetime(), Disambiguate :: disambiguate(), FromTZ :: any(), ToTZ :: any()) ->
|
||||||
|
datetime() | {ambiguous, datetime(), datetime()}.
|
||||||
date_tz_to_tz(Date, Disambiguate, FromTZ, ToTZ) when is_integer(FromTZ) ->
|
date_tz_to_tz(Date, Disambiguate, FromTZ, ToTZ) when is_integer(FromTZ) ->
|
||||||
NewDate = localtime:adjust_datetime(Date, FromTZ),
|
NewDate = localtime:adjust_datetime(Date, FromTZ),
|
||||||
date_tz_to_tz(NewDate, Disambiguate, "GMT", ToTZ);
|
date_tz_to_tz(NewDate, Disambiguate, "GMT", ToTZ);
|
||||||
|
@ -546,13 +1080,14 @@ date_tz_to_tz(Date, Disambiguate, FromTZ, ToTZ) ->
|
||||||
date_tz_to_tz_both(Date, FromTZ, ToTZ)
|
date_tz_to_tz_both(Date, FromTZ, ToTZ)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
-spec date_tz_to_tz_both(Date :: datetime(), FromTZ :: string(), ToTZ :: string()) -> datetime() | {ambiguous, datetime(), datetime()}.
|
||||||
date_tz_to_tz_both(Date, FromTZ, ToTZ) ->
|
date_tz_to_tz_both(Date, FromTZ, ToTZ) ->
|
||||||
Standard = localtime:local_to_local(Date, FromTZ, ToTZ),
|
Standard = localtime:local_to_local(Date, FromTZ, ToTZ),
|
||||||
Daylight = localtime:local_to_local_dst(Date, FromTZ, ToTZ),
|
Daylight = localtime:local_to_local_dst(Date, FromTZ, ToTZ),
|
||||||
if
|
if
|
||||||
Standard=:=Daylight ->
|
Standard=:=Daylight ->
|
||||||
Standard;
|
Standard;
|
||||||
?else ->
|
true ->
|
||||||
{ambiguous, Standard, Daylight}
|
{ambiguous, Standard, Daylight}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
@ -568,7 +1103,7 @@ set_timezone(Key,TZ) ->
|
||||||
qdate_srv:set_timezone(Key, TZ).
|
qdate_srv:set_timezone(Key, TZ).
|
||||||
|
|
||||||
get_timezone() ->
|
get_timezone() ->
|
||||||
qdate_srv:get_timezone().
|
?DETERMINE_TZ.
|
||||||
|
|
||||||
get_timezone(Key) ->
|
get_timezone(Key) ->
|
||||||
qdate_srv:get_timezone(Key).
|
qdate_srv:get_timezone(Key).
|
||||||
|
@ -619,6 +1154,8 @@ try_parsers(_RawDate,[]) ->
|
||||||
undefined;
|
undefined;
|
||||||
try_parsers(RawDate,[{ParserKey,Parser}|Parsers]) ->
|
try_parsers(RawDate,[{ParserKey,Parser}|Parsers]) ->
|
||||||
try Parser(RawDate) of
|
try Parser(RawDate) of
|
||||||
|
Timestamp when is_integer(Timestamp) ->
|
||||||
|
{Timestamp, "GMT"};
|
||||||
{{_,_,_},{_,_,_}} = DateTime ->
|
{{_,_,_},{_,_,_}} = DateTime ->
|
||||||
{DateTime,undefined};
|
{DateTime,undefined};
|
||||||
{DateTime={{_,_,_},{_,_,_}},Timezone} ->
|
{DateTime={{_,_,_},{_,_,_}},Timezone} ->
|
||||||
|
@ -628,8 +1165,8 @@ try_parsers(RawDate,[{ParserKey,Parser}|Parsers]) ->
|
||||||
Other ->
|
Other ->
|
||||||
throw({invalid_parser_return_value,[{parser_key,ParserKey},{return,Other}]})
|
throw({invalid_parser_return_value,[{parser_key,ParserKey},{return,Other}]})
|
||||||
catch
|
catch
|
||||||
Error:Reason ->
|
?WITH_STACKTRACE(Error, Reason, Stacktrace)
|
||||||
throw({error_in_parser,[{error,{Error,Reason}},{parser_key,ParserKey}]})
|
throw({error_in_parser,[{error,{Error,Reason}},{parser_key,ParserKey}, {stacktrace, Stacktrace}]})
|
||||||
end.
|
end.
|
||||||
|
|
||||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||||
|
@ -648,7 +1185,7 @@ deregister_format(Key) ->
|
||||||
|
|
||||||
|
|
||||||
unixtime_to_now(T) when is_integer(T) ->
|
unixtime_to_now(T) when is_integer(T) ->
|
||||||
MegaSec = floor(T/1000000),
|
MegaSec = flooring(T/1000000),
|
||||||
Secs = T - MegaSec*1000000,
|
Secs = T - MegaSec*1000000,
|
||||||
{MegaSec,Secs,0}.
|
{MegaSec,Secs,0}.
|
||||||
|
|
||||||
|
@ -656,10 +1193,10 @@ unixtime_to_date(T) ->
|
||||||
Now = unixtime_to_now(T),
|
Now = unixtime_to_now(T),
|
||||||
calendar:now_to_datetime(Now).
|
calendar:now_to_datetime(Now).
|
||||||
|
|
||||||
floor(N) when N >= 0 ->
|
flooring(N) when N >= 0 ->
|
||||||
trunc(N);
|
erlang:trunc(N);
|
||||||
floor(N) when N < 0 ->
|
flooring(N) when N < 0 ->
|
||||||
Int = trunc(N),
|
Int = erlang:trunc(N),
|
||||||
if
|
if
|
||||||
Int==N -> Int;
|
Int==N -> Int;
|
||||||
true -> Int-1
|
true -> Int-1
|
||||||
|
@ -669,6 +1206,8 @@ floor(N) when N < 0 ->
|
||||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% TESTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% TESTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||||
|
|
||||||
|
-ifdef(EUNIT).
|
||||||
|
|
||||||
-include_lib("eunit/include/eunit.hrl").
|
-include_lib("eunit/include/eunit.hrl").
|
||||||
|
|
||||||
%% emulates as if a forum-type website has a Site tz, and a user-specified tz
|
%% emulates as if a forum-type website has a Site tz, and a user-specified tz
|
||||||
|
@ -696,6 +1235,30 @@ tz_test_() ->
|
||||||
end
|
end
|
||||||
}.
|
}.
|
||||||
|
|
||||||
|
tz_preserve_ms_true_test_() ->
|
||||||
|
{
|
||||||
|
setup,
|
||||||
|
fun start_test/0,
|
||||||
|
fun stop_test/1,
|
||||||
|
fun(SetupData) ->
|
||||||
|
{inorder,[
|
||||||
|
preserve_ms_true_tests(SetupData)
|
||||||
|
]}
|
||||||
|
end
|
||||||
|
}.
|
||||||
|
|
||||||
|
tz_preserve_ms_false_test_() ->
|
||||||
|
{
|
||||||
|
setup,
|
||||||
|
fun start_test/0,
|
||||||
|
fun stop_test/1,
|
||||||
|
fun(SetupData) ->
|
||||||
|
{inorder,[
|
||||||
|
preserve_ms_false_tests(SetupData)
|
||||||
|
]}
|
||||||
|
end
|
||||||
|
}.
|
||||||
|
|
||||||
test_deterministic_parser(_) ->
|
test_deterministic_parser(_) ->
|
||||||
{inorder, [
|
{inorder, [
|
||||||
?_assertEqual(ok, application:set_env(qdate, deterministic_parsing, {now, now})),
|
?_assertEqual(ok, application:set_env(qdate, deterministic_parsing, {now, now})),
|
||||||
|
@ -754,6 +1317,20 @@ tz_tests(_) ->
|
||||||
?_assertEqual({{2013,3,6},{18,0,0}}, to_date("GMT","3/7/2013 12:00am +0600")),
|
?_assertEqual({{2013,3,6},{18,0,0}}, to_date("GMT","3/7/2013 12:00am +0600")),
|
||||||
?_assertEqual({{2013,3,6},{12,0,0}}, to_date("CST","3/7/2013 12:00am +0600")),
|
?_assertEqual({{2013,3,6},{12,0,0}}, to_date("CST","3/7/2013 12:00am +0600")),
|
||||||
|
|
||||||
|
%% These next two test check to make sure that the tz database properly
|
||||||
|
%% interprets GMT+/-X timezones (an earlier issue with
|
||||||
|
%% erlang_localtime's tz database had it incrementing/decrementing the
|
||||||
|
%% minute field rather than hours.
|
||||||
|
%%
|
||||||
|
%% It also ensures that GMT+/-X handling is interpreted the way you'd
|
||||||
|
%% intuitively expect, rather than the POSIX way, which is, quite
|
||||||
|
%% frankly, broken.
|
||||||
|
?_assertEqual({{2013,3,7},{10,0,0}}, to_date("GMT-0","3/7/2013 10:00am GMT")),
|
||||||
|
?_assertEqual({{2013,3,7},{10,0,0}}, to_date("GMT+0","3/7/2013 10:00am GMT")),
|
||||||
|
?_assertEqual({{2013,3,7},{9,0,0}}, to_date("GMT-1","3/7/2013 10:00am GMT")),
|
||||||
|
?_assertEqual({{2013,3,7},{11,0,0}}, to_date("GMT+1","3/7/2013 10:00am GMT")),
|
||||||
|
|
||||||
|
|
||||||
%% parsing, then reformatting the same time with a different timezone using the php "r" (rfc2822)
|
%% parsing, then reformatting the same time with a different timezone using the php "r" (rfc2822)
|
||||||
?_assertEqual("Thu, 07 Mar 2013 12:15:00 -0600",
|
?_assertEqual("Thu, 07 Mar 2013 12:15:00 -0600",
|
||||||
to_string("r","CST",to_string("r","EST",{{2013,3,7},{13,15,0}}))),
|
to_string("r","CST",to_string("r","EST",{{2013,3,7},{13,15,0}}))),
|
||||||
|
@ -769,8 +1346,13 @@ tz_tests(_) ->
|
||||||
?_assertEqual(555555555,to_unixtime("1987-08-10 00:59:15 GMT")),
|
?_assertEqual(555555555,to_unixtime("1987-08-10 00:59:15 GMT")),
|
||||||
?_assertEqual({555,555555,0},to_now("1987-08-10 00:59:15 GMT")),
|
?_assertEqual({555,555555,0},to_now("1987-08-10 00:59:15 GMT")),
|
||||||
?_assertEqual(ok, set_timezone("GMT")),
|
?_assertEqual(ok, set_timezone("GMT")),
|
||||||
?_assertEqual({{1970, 1, 1}, {1, 0, 0}}, to_date("CET", "1970-01-01T00:00:00Z"))
|
?_assertEqual({{1970, 1, 1}, {1, 0, 0}}, to_date("CET", "1970-01-01T00:00:00Z")),
|
||||||
|
?_assertEqual(ok, set_timezone("UTC")),
|
||||||
|
?_assertEqual(1521945120, to_unixtime("2018-3-25T2:32:00")),
|
||||||
|
?_assertEqual(true, between("-1 seconds", os:timestamp(), "+1 seconds")),
|
||||||
|
?_assertEqual(true, between("60 hours ago", unixtime(), "in 15 days")),
|
||||||
|
?_assertEqual(false, between("+1 seconds", qdate:to_string("n/j/Y g:ia"), "+2 seconds")),
|
||||||
|
?_assertEqual(false, between("5 seconds ago","1 second ago"))
|
||||||
]}.
|
]}.
|
||||||
|
|
||||||
|
|
||||||
|
@ -836,22 +1418,81 @@ arith_tests(_) ->
|
||||||
?_assertEqual({{2015,1,31},{23,59,59}}, to_date(add_months(1, {{2014,12,31},{23,59,59}}))),
|
?_assertEqual({{2015,1,31},{23,59,59}}, to_date(add_months(1, {{2014,12,31},{23,59,59}}))),
|
||||||
?_assertEqual({{2015,2,28},{0,0,0}}, to_date(add_months(2, {{2014,12,31},{0,0,0}}))),
|
?_assertEqual({{2015,2,28},{0,0,0}}, to_date(add_months(2, {{2014,12,31},{0,0,0}}))),
|
||||||
?_assertEqual({{2016,2,28},{0,0,0}}, to_date(add_years(1, {{2015,2,28},{0,0,0}}))),
|
?_assertEqual({{2016,2,28},{0,0,0}}, to_date(add_years(1, {{2015,2,28},{0,0,0}}))),
|
||||||
|
|
||||||
|
?_assertEqual({{2017,2,1},{0,0,0}}, to_date(add_months(-11, {{2018,1,1},{0,0,0}}))),
|
||||||
|
?_assertEqual({{2017,1,1},{0,0,0}}, to_date(add_months(-12, {{2018,1,1},{0,0,0}}))),
|
||||||
|
?_assertEqual({{2016,12,1},{0,0,0}}, to_date(add_months(-13, {{2018,1,1},{0,0,0}}))),
|
||||||
|
|
||||||
|
?_assertEqual({{2018,12,1},{0,0,0}}, to_date(add_months(11, {{2018,1,1},{0,0,0}}))),
|
||||||
|
?_assertEqual({{2019,1,1},{0,0,0}}, to_date(add_months(12, {{2018,1,1},{0,0,0}}))),
|
||||||
|
?_assertEqual({{2019,2,1},{0,0,0}}, to_date(add_months(13, {{2018,1,1},{0,0,0}}))),
|
||||||
|
|
||||||
|
?_assertEqual({{2018,1,1},{0,0,0}}, to_date(add_months(-11, {{2018,12,1},{0,0,0}}))),
|
||||||
|
?_assertEqual({{2017,12,1},{0,0,0}}, to_date(add_months(-12, {{2018,12,1},{0,0,0}}))),
|
||||||
|
?_assertEqual({{2017,11,1},{0,0,0}}, to_date(add_months(-13, {{2018,12,1},{0,0,0}}))),
|
||||||
|
|
||||||
|
?_assertEqual({{2019,11,1},{0,0,0}}, to_date(add_months(11, {{2018,12,1},{0,0,0}}))),
|
||||||
|
?_assertEqual({{2019,12,1},{0,0,0}}, to_date(add_months(12, {{2018,12,1},{0,0,0}}))),
|
||||||
|
?_assertEqual({{2020,1,1},{0,0,0}}, to_date(add_months(13, {{2018,12,1},{0,0,0}}))),
|
||||||
|
|
||||||
|
|
||||||
?_assertEqual({{2014,2,28},{0,0,0}}, to_date(add_months(-24, {{2016,2,29},{0,0,0}}))),
|
?_assertEqual({{2014,2,28},{0,0,0}}, to_date(add_months(-24, {{2016,2,29},{0,0,0}}))),
|
||||||
|
?_assertEqual({{2018,12,15},{0,0,0}}, to_date(add_months(24, {{2016,12,15},{0,0,0}}))),
|
||||||
?_assertEqual({{2012,2,29},{0,0,0}}, to_date(add_months(-48, {{2016,2,29},{0,0,0}}))),
|
?_assertEqual({{2012,2,29},{0,0,0}}, to_date(add_months(-48, {{2016,2,29},{0,0,0}}))),
|
||||||
?_assertEqual({{2016,2,29},{0,0,0}}, to_date(add_months(-1, {{2016,3,31},{0,0,0}}))),
|
?_assertEqual({{2016,2,29},{0,0,0}}, to_date(add_months(-1, {{2016,3,31},{0,0,0}}))),
|
||||||
?_assertEqual({{2017,2,28},{0,0,0}}, to_date(add_years(1, {{2016,2,29},{0,0,0}}))),
|
?_assertEqual({{2017,2,28},{0,0,0}}, to_date(add_years(1, {{2016,2,29},{0,0,0}}))),
|
||||||
?_assertEqual({{2015,3,1},{0,0,0}}, to_date(add_days(1, {{2015,2,28},{0,0,0}}))),
|
?_assertEqual({{2015,3,1},{0,0,0}}, to_date(add_days(1, {{2015,2,28},{0,0,0}}))),
|
||||||
?_assertEqual({{2015,3,3},{0,0,0}}, to_date(add_days(3, {{2015,2,28},{0,0,0}})))
|
?_assertEqual({{2015,3,3},{0,0,0}}, to_date(add_days(3, {{2015,2,28},{0,0,0}}))),
|
||||||
|
?_assertEqual({{2017,1,2},{0,0,0}}, beginning_week({{2017,1,2},{0,0,0}})),
|
||||||
|
?_assertEqual({{2017,1,2},{0,0,0}}, beginning_week({{2017,1,3},{0,0,0}})),
|
||||||
|
?_assertEqual({{2017,1,3},{0,0,0}}, beginning_week(2, {{2017,1,4},{0,0,0}})),
|
||||||
|
?_assertEqual({{2016,12,29},{0,0,0}}, beginning_week(4, {{2017,1,4},{0,0,0}})),
|
||||||
|
?_assertEqual({{2016,12,31},{0,0,0}}, beginning_week(6, {{2017,1,6},{0,0,0}})),
|
||||||
|
?_assertEqual({{2017,1,1},{0,0,0}}, beginning_week(7, {{2017,1,6},{0,0,0}})),
|
||||||
|
|
||||||
|
?_assertEqual(0, age("1981-01-15", "1981-12-31")),
|
||||||
|
?_assertEqual(39, age("1981-01-15", "2020-01-15")),
|
||||||
|
?_assertEqual(39, age("1981-01-15", "2020-01-15 12am")),
|
||||||
|
?_assertEqual(38, age("1981-01-15", "2020-01-14")),
|
||||||
|
?_assertEqual(38, age("1981-01-15", "2020-01-14 11:59pm")),
|
||||||
|
|
||||||
|
%% checking pre-unix-epoch
|
||||||
|
?_assertEqual(100, age("1901-01-01","2001-01-01")),
|
||||||
|
?_assertEqual(20, age("1900-01-01", "1920-01-01")),
|
||||||
|
|
||||||
|
?_assertEqual(0, age_days("2020-11-20 12am","2020-11-20 11:59:59pm")),
|
||||||
|
?_assertEqual(1, age_days("2020-11-19 11:59:59pm","2020-11-20 11:59:59pm")),
|
||||||
|
?_assertEqual(7, age_days("2020-11-20","2020-11-27")),
|
||||||
|
?_assertEqual(7, age_days("2020-11-27","2020-12-04"))
|
||||||
|
|
||||||
]}.
|
]}.
|
||||||
|
|
||||||
|
preserve_ms_true_tests(_) ->
|
||||||
|
application:set_env(qdate, preserve_ms, true),
|
||||||
|
{inorder, [
|
||||||
|
?_assertEqual({{2021,5,8},{23,0,16,472000}}, qdate:to_date(<<"UTC">>,<<"2021-5-09 0:0:16.472+01:00">>)),
|
||||||
|
?_assertEqual(<<"2021-05-08T23:00:16.472000+00:00">>, qdate:to_string(<<"Y-m-d\\TH:i:s.fP">>, <<"UTC">>,<<"2021-5-09 0:0:16.472+01:00">>)),
|
||||||
|
?_assertEqual(<<"2021-05-08T23:00:16+00:00">>, qdate:to_string(<<"Y-m-d\\TH:i:sP">>, <<"UTC">>,<<"2021-5-09 0:0:16.472+01:00">>)),
|
||||||
|
?_assertEqual(<<"2021-05-08T23:00:16.472000+00:00">>, qdate:to_string(<<"c">>, <<"UTC">>,<<"2021-5-09 0:0:16.472+01:00">>))
|
||||||
|
]}.
|
||||||
|
|
||||||
|
preserve_ms_false_tests(_) ->
|
||||||
|
application:set_env(qdate, preserve_ms, false),
|
||||||
|
{inorder, [
|
||||||
|
?_assertEqual({{2021,5,8},{23,0,16}}, qdate:to_date(<<"UTC">>,<<"2021-5-09 0:0:16.472+01:00">>)),
|
||||||
|
?_assertEqual(<<"2021-05-08T23:00:16.0+00:00">>, qdate:to_string(<<"Y-m-d\\TH:i:s.fP">>, <<"UTC">>,<<"2021-5-09 0:0:16.472+01:00">>)),
|
||||||
|
?_assertEqual(<<"2021-05-08T23:00:16+00:00">>, qdate:to_string(<<"Y-m-d\\TH:i:sP">>, <<"UTC">>,<<"2021-5-09 0:0:16.472+01:00">>)),
|
||||||
|
?_assertEqual(<<"2021-05-08T23:00:16+00:00">>, qdate:to_string(<<"c">>, <<"UTC">>,<<"2021-5-09 0:0:16.472+01:00">>))
|
||||||
|
]}.
|
||||||
|
|
||||||
start_test() ->
|
start_test() ->
|
||||||
application:start(qdate),
|
qdate:start(),
|
||||||
set_timezone(?SELF_TZ),
|
set_timezone(?SELF_TZ),
|
||||||
set_timezone(?SITE_KEY,?SITE_TZ),
|
set_timezone(?SITE_KEY,?SITE_TZ),
|
||||||
set_timezone(?USER_KEY,?USER_TZ),
|
set_timezone(?USER_KEY,?USER_TZ),
|
||||||
register_parser(compressed,fun compressed_parser/1),
|
register_parser(compressed,fun compressed_parser/1),
|
||||||
register_parser(microsoft_date,fun microsoft_parser/1),
|
register_parser(microsoft_date,fun microsoft_parser/1),
|
||||||
|
register_parser(parse_relative, fun parse_relative/1),
|
||||||
register_format(shortdate,"n/j/Y"),
|
register_format(shortdate,"n/j/Y"),
|
||||||
register_format(longdate,"n/j/Y g:ia").
|
register_format(longdate,"n/j/Y g:ia").
|
||||||
|
|
||||||
|
@ -873,7 +1514,7 @@ compressed_parser(_) ->
|
||||||
|
|
||||||
microsoft_parser(FloatDate) when is_float(FloatDate) ->
|
microsoft_parser(FloatDate) when is_float(FloatDate) ->
|
||||||
try
|
try
|
||||||
DaysSince1900 = floor(FloatDate),
|
DaysSince1900 = flooring(FloatDate),
|
||||||
Days0to1900 = calendar:date_to_gregorian_days(1900,1,1),
|
Days0to1900 = calendar:date_to_gregorian_days(1900,1,1),
|
||||||
GregorianDays = Days0to1900 + DaysSince1900,
|
GregorianDays = Days0to1900 + DaysSince1900,
|
||||||
Date = calendar:gregorian_days_to_date(GregorianDays),
|
Date = calendar:gregorian_days_to_date(GregorianDays),
|
||||||
|
@ -890,3 +1531,5 @@ microsoft_parser(_) ->
|
||||||
|
|
||||||
stop_test(_) ->
|
stop_test(_) ->
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
|
-endif.
|
||||||
|
|
15
src/qdate_app.erl
Normal file
15
src/qdate_app.erl
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
-module(qdate_app).
|
||||||
|
|
||||||
|
-behaviour(application).
|
||||||
|
|
||||||
|
%% Application callbacks
|
||||||
|
-export([start/2, stop/1]).
|
||||||
|
|
||||||
|
start(_StartType, _StartArgs) ->
|
||||||
|
qdate_sup:start_link().
|
||||||
|
|
||||||
|
|
||||||
|
stop(_State) ->
|
||||||
|
ok.
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,9 @@
|
||||||
% vim: ts=4 sw=4 et
|
% vim: ts=4 sw=4 et
|
||||||
% Copyright (c) 2013-2015 Jesse Gumm
|
% Copyright (c) 2013-2021 Jesse Gumm
|
||||||
% See LICENSE for licensing information.
|
% See LICENSE for licensing information.
|
||||||
%
|
|
||||||
% NOTE: You'll probably notice that this isn't *actually* a server. It *used*
|
|
||||||
% to be a server, but is now instead just where we interact with the qdate
|
|
||||||
% application environment. Anyway, sorry for the confusion.
|
|
||||||
|
|
||||||
-module(qdate_srv).
|
-module(qdate_srv).
|
||||||
|
-behaviour(gen_server).
|
||||||
|
|
||||||
-export([
|
-export([
|
||||||
set_timezone/1,
|
set_timezone/1,
|
||||||
|
@ -28,6 +25,19 @@
|
||||||
get_formats/0
|
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
|
%% Simple wrappers for unique keys
|
||||||
-define(BASETAG, qdate_var).
|
-define(BASETAG, qdate_var).
|
||||||
-define(KEY(Name), {?BASETAG, Name}).
|
-define(KEY(Name), {?BASETAG, Name}).
|
||||||
|
@ -39,6 +49,42 @@
|
||||||
-define(FORMATTAG, qdate_format).
|
-define(FORMATTAG, qdate_format).
|
||||||
-define(FORMATKEY(Name), {?FORMATTAG, Name}).
|
-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
|
%% PUBLIC API FUNCTIONS
|
||||||
|
|
||||||
set_timezone(TZ) ->
|
set_timezone(TZ) ->
|
||||||
|
@ -91,26 +137,41 @@ get_formats() ->
|
||||||
%% App Vars
|
%% App Vars
|
||||||
|
|
||||||
set_env(Key, Val) ->
|
set_env(Key, Val) ->
|
||||||
application:set_env(qdate, ?KEY(Key), Val).
|
gen_server:call(?SERVER, {set, ?KEY(Key), Val}).
|
||||||
|
|
||||||
get_env(Key) ->
|
get_env(Key) ->
|
||||||
get_env(Key, undefined).
|
get_env(Key, undefined).
|
||||||
|
|
||||||
get_env(Key, Default) ->
|
get_env(Key, Default) ->
|
||||||
%% Soon, this can just be replaced with application:get_env/3
|
case ets:lookup(?TABLE, ?KEY(Key)) of
|
||||||
%% which was introduced in R16B.
|
[{__Key, Val}] -> Val;
|
||||||
case application:get_env(qdate, ?KEY(Key)) of
|
[] -> Default
|
||||||
undefined -> Default;
|
|
||||||
{ok, Val} -> Val
|
|
||||||
end.
|
end.
|
||||||
|
|
||||||
unset_env(Key) ->
|
unset_env(Key) ->
|
||||||
application:unset_env(qdate, ?KEY(Key)).
|
gen_server:call(?SERVER, {unset, ?KEY(Key)}).
|
||||||
|
|
||||||
get_all_env(FilterTag) ->
|
get_all_env(FilterTag) ->
|
||||||
All = application:get_all_env(qdate),
|
try ets:tab2list(?TABLE) of
|
||||||
%% Maybe this is a little nasty.
|
All ->
|
||||||
[{Key, V} || {{?BASETAG, {Tag, Key}}, V} <- All, Tag==FilterTag].
|
[{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
|
%% ProcDic Vars
|
||||||
|
|
||||||
|
@ -123,3 +184,9 @@ put_pd(Key, Val) ->
|
||||||
|
|
||||||
unset_pd(Key) ->
|
unset_pd(Key) ->
|
||||||
put_pd(Key, undefined).
|
put_pd(Key, undefined).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
32
src/qdate_sup.erl
Normal file
32
src/qdate_sup.erl
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
-module(qdate_sup).
|
||||||
|
|
||||||
|
-behaviour(supervisor).
|
||||||
|
|
||||||
|
%% API
|
||||||
|
-export([start_link/0]).
|
||||||
|
|
||||||
|
%% Supervisor callbacks
|
||||||
|
-export([init/1]).
|
||||||
|
|
||||||
|
-define(SERVER, ?MODULE).
|
||||||
|
|
||||||
|
start_link() ->
|
||||||
|
supervisor:start_link({local, ?SERVER}, ?MODULE, []).
|
||||||
|
|
||||||
|
init([]) ->
|
||||||
|
|
||||||
|
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