Compare commits
No commits in common. "master" and "0.2.0" have entirely different histories.
17 changed files with 297 additions and 1761 deletions
47
.github/workflows/tests-workflow.yml
vendored
47
.github/workflows/tests-workflow.yml
vendored
|
@ -1,47 +0,0 @@
|
||||||
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,10 +1,6 @@
|
||||||
*~
|
*~
|
||||||
*.beam
|
*.beam
|
||||||
*.sw?
|
*.sw?
|
||||||
*.iml
|
|
||||||
deps/
|
deps/
|
||||||
ebin/
|
ebin/
|
||||||
.eunit/
|
.eunit/
|
||||||
.idea/
|
|
||||||
_build
|
|
||||||
doc/
|
|
||||||
|
|
10
.travis.yml
Normal file
10
.travis.yml
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
language: erlang
|
||||||
|
script: "make test"
|
||||||
|
otp_release:
|
||||||
|
- R16B
|
||||||
|
- R15B02
|
||||||
|
- R15B01
|
||||||
|
- R15B
|
||||||
|
- R14B03
|
||||||
|
- R14B02
|
||||||
|
before_script: "sudo apt-get --yes --force-yes install libpam0g-dev"
|
|
@ -1,87 +0,0 @@
|
||||||
## 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,41 +1,13 @@
|
||||||
all: compile
|
all: get-deps compile
|
||||||
|
|
||||||
# Check if rebar3.mk exists, and if not, download it
|
get-deps:
|
||||||
ifeq ("$(wildcard rebar3.mk)","")
|
./rebar get-deps
|
||||||
$(shell curl -O https://raw.githubusercontent.com/choptastic/rebar3.mk/master/rebar3.mk)
|
|
||||||
endif
|
|
||||||
|
|
||||||
# rebar3.mk adds a new rebar3 rule to your Makefile
|
compile:
|
||||||
# (see https://github.com/choptastic/rebar3.mk) for full info
|
./rebar compile
|
||||||
include rebar3.mk
|
|
||||||
|
|
||||||
compile: rebar3
|
test: get-deps compile
|
||||||
$(REBAR) compile
|
./rebar skip_deps=true eunit
|
||||||
|
|
||||||
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 - Erlang Date and Timezone Library
|
# qdate - A Wrapper for Erlang Date and Timezone Management
|
||||||
|
|
||||||
[](https://github.com/choptastic/qdate/actions/workflows/tests-workflow.yml)
|
[](https://travis-ci.org/choptastic/qdate)
|
||||||
|
|
||||||
## Purpose
|
## Purpose
|
||||||
|
|
||||||
|
@ -21,15 +21,12 @@ 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
|
capabilities of both to provide for other needed tools found in a single
|
||||||
module.
|
module.
|
||||||
|
|
||||||
`qdate` provides date and time formatting and parsing from and into:
|
`qdate` will provide, under the roof of a single module date and time formatting
|
||||||
|
and parsing from and into:
|
||||||
+ Formatting Strings
|
+ Formatting Strings
|
||||||
+ Erlang Date Format
|
+ Erlang Date Format
|
||||||
+ Erlang Now Format
|
+ Erlang Now Format
|
||||||
+ Unixtime integers
|
+ Unixtime integers
|
||||||
+ Timezones
|
|
||||||
|
|
||||||
And all this while dealing with timezone parsing, formatting, conversion
|
|
||||||
and overall management.
|
|
||||||
|
|
||||||
#### Acceptable Date Formats
|
#### Acceptable Date Formats
|
||||||
|
|
||||||
|
@ -68,15 +65,10 @@ T, Z, r, and c), `qdate` will handle them for us.
|
||||||
+ `to_now(Date)` - converts any date/time format to Erlang now format.
|
+ `to_now(Date)` - converts any date/time format to Erlang now format.
|
||||||
+ `to_unixtime(Date)` - converts any date/time format to a unixtime integer
|
+ `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
|
**A Note About Argument Order**: In all cases, `ToTimezone` is optional and if
|
||||||
omitted, will be determined as described below in "Understanding Timezone
|
omitted, will be determined as described below in "Understanding Timezone
|
||||||
Determining and Conversion". If `ToTimezone` is specified, it will always be
|
Determining and Conversion". If `ToTimezone` is specified, it will always be
|
||||||
immediately left of the `Disambiguate` argument (if it's specified), which is
|
immediately left of the `Date` argument. `Date` will always be the last
|
||||||
always immediately left of `Date` argument. `Date` will always be the last
|
|
||||||
argument to any of the conversion and formatting functions.
|
argument to any of the conversion and formatting functions.
|
||||||
|
|
||||||
#### Understanding Timezone Determining and Conversions
|
#### Understanding Timezone Determining and Conversions
|
||||||
|
@ -98,88 +90,9 @@ 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`. `default_timezone` can be either a
|
application variable `default_timezone`.
|
||||||
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.
|
|
||||||
|
|
||||||
#### 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`
|
#### Conversion Functions provided for API compatibility with `ec_date`
|
||||||
|
|
||||||
|
@ -192,72 +105,22 @@ ok
|
||||||
|
|
||||||
`qdate` provides a few convenience functions for performing date comparisons.
|
`qdate` provides a few convenience functions for performing date comparisons.
|
||||||
|
|
||||||
+ `compare(A, B) -> -1|0|1` - Like C's `strcmp`, returns:
|
+ `compare(A, B)` - 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) -> true|false` - Operator is an infix comparison operator, and
|
+ `compare(A, Operator, B)` - Operator is an infix comparison operator, and
|
||||||
the function will return a boolean. Will return `true` if:
|
the function 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:** 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`
|
||||||
|
@ -293,7 +156,6 @@ be attempted before engaging the `ec_date` parser.
|
||||||
able to parse the string, then it should return `undefined`.
|
able to parse the string, then it should return `undefined`.
|
||||||
+ `deregister_parser(Key)` - If you previously registered a parser with the
|
+ `deregister_parser(Key)` - If you previously registered a parser with the
|
||||||
`qdate` server, you can deregister it by its `Key`.
|
`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
|
### Registering and Deregistering Formatters
|
||||||
+ `register_format(Key, FormatString)` - Register a formatting string with
|
+ `register_format(Key, FormatString)` - Register a formatting string with
|
||||||
|
@ -301,7 +163,6 @@ be attempted before engaging the `ec_date` parser.
|
||||||
formatting string.
|
formatting string.
|
||||||
+ `deregister_format(Key)` - Deregister the formatting string from the
|
+ `deregister_format(Key)` - Deregister the formatting string from the
|
||||||
`qdate` server.
|
`qdate` server.
|
||||||
+ `get_formats()` - Get the list of all registered formats and their keys.
|
|
||||||
|
|
||||||
### About backwards compatibility with `ec_date` and deterministic parsing
|
### About backwards compatibility with `ec_date` and deterministic parsing
|
||||||
|
|
||||||
|
@ -498,31 +359,8 @@ 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
|
||||||
|
|
||||||
|
@ -536,7 +374,7 @@ qdate:between(qdate:add_minutes(-15), Date, qdate:add_minutes(15)).
|
||||||
|
|
||||||
%% 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
|
||||||
%% easily refer to that format by its key.
|
%% easiy 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").
|
||||||
|
@ -692,175 +530,6 @@ ok
|
||||||
%% that timezone to our intended timezone.
|
%% 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
|
## 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
|
||||||
|
@ -869,24 +538,12 @@ 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
|
package. Without the hard work of all involved in those projects, `qdate` would
|
||||||
not exist.
|
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
|
## TODO
|
||||||
|
|
||||||
+ 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
|
||||||
|
+ Add date and time arithmetic.
|
||||||
+ 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
20
qdate.config
|
@ -1,20 +0,0 @@
|
||||||
%% 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
Executable file
BIN
rebar
vendored
Executable file
Binary file not shown.
31
rebar.config
31
rebar.config
|
@ -1,24 +1,13 @@
|
||||||
%% -*- erlang -*-
|
% vim:ts=4 sw=4 et ft=erlang
|
||||||
%% vim:ts=4 sw=4 et ft=erlang
|
{require_otp_vsn, "R13B04|R14|R15|R16"}.
|
||||||
|
|
||||||
{cover_enabled, true}.
|
{cover_enabled, true}.
|
||||||
|
|
||||||
{dialyzer, [
|
%{erl_opts, [debug_info,{i,"site/include"}]}.
|
||||||
{exclude_apps, []},
|
|
||||||
{warnings, []}
|
{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"}}
|
||||||
]}.
|
]}.
|
||||||
|
|
||||||
{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">>}]}.
|
|
||||||
|
|
||||||
|
|
|
@ -1,15 +0,0 @@
|
||||||
%% -*- 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
14
rebar.lock
|
@ -1,14 +0,0 @@
|
||||||
{"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,17 +1,12 @@
|
||||||
{application, qdate,
|
{application, qdate,
|
||||||
[
|
[
|
||||||
{description, "Simple Date and Timezone handling for Erlang"},
|
{description, "Simple Date and Timezone handling for Erlang"},
|
||||||
{vsn, git},
|
{vsn, "0.2.0"},
|
||||||
{registered, []},
|
{registered, []},
|
||||||
{applications, [
|
{applications, [
|
||||||
kernel,
|
kernel,
|
||||||
stdlib,
|
stdlib
|
||||||
qdate_localtime,
|
|
||||||
erlware_commons
|
|
||||||
]},
|
]},
|
||||||
{modules, [qdate, qdate_srv, qdate_sup, qdate_app]},
|
{mod, { qdate_app, []}},
|
||||||
{env, []},
|
{env, []}
|
||||||
{licenses, ["MIT"]},
|
|
||||||
{mod, {qdate_app, []}},
|
|
||||||
{links, [{"Github", "https://github.com/choptastic/qdate"}]}
|
|
||||||
]}.
|
]}.
|
||||||
|
|
1110
src/qdate.erl
1110
src/qdate.erl
File diff suppressed because it is too large
Load diff
|
@ -1,3 +1,7 @@
|
||||||
|
% vim: ts=4 sw=4 et
|
||||||
|
% Copyright (c) 2013 Jesse Gumm
|
||||||
|
% See LICENSE for licensing information.
|
||||||
|
|
||||||
-module(qdate_app).
|
-module(qdate_app).
|
||||||
|
|
||||||
-behaviour(application).
|
-behaviour(application).
|
||||||
|
@ -5,11 +9,12 @@
|
||||||
%% Application callbacks
|
%% Application callbacks
|
||||||
-export([start/2, stop/1]).
|
-export([start/2, stop/1]).
|
||||||
|
|
||||||
|
%% ===================================================================
|
||||||
|
%% Application callbacks
|
||||||
|
%% ===================================================================
|
||||||
|
|
||||||
start(_StartType, _StartArgs) ->
|
start(_StartType, _StartArgs) ->
|
||||||
qdate_sup:start_link().
|
qdate_sup:start_link().
|
||||||
|
|
||||||
|
|
||||||
stop(_State) ->
|
stop(_State) ->
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,22 @@
|
||||||
% vim: ts=4 sw=4 et
|
% vim: ts=4 sw=4 et
|
||||||
% Copyright (c) 2013-2021 Jesse Gumm
|
% Copyright (c) 2013 Jesse Gumm
|
||||||
% See LICENSE for licensing information.
|
% See LICENSE for licensing information.
|
||||||
|
|
||||||
-module(qdate_srv).
|
-module(qdate_srv).
|
||||||
-behaviour(gen_server).
|
-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([
|
-export([
|
||||||
set_timezone/1,
|
set_timezone/1,
|
||||||
set_timezone/2,
|
set_timezone/2,
|
||||||
|
@ -21,172 +33,133 @@
|
||||||
|
|
||||||
register_format/2,
|
register_format/2,
|
||||||
get_format/1,
|
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
|
%% PUBLIC API FUNCTIONS
|
||||||
|
|
||||||
set_timezone(TZ) ->
|
start_link() ->
|
||||||
put_pd(?TZTAG, TZ).
|
gen_server:start_link({local, ?SRV}, ?MODULE, [], []).
|
||||||
|
|
||||||
set_timezone(Key, TZ) ->
|
set_timezone(TZ) ->
|
||||||
set_env(?TZKEY(Key), TZ).
|
set_timezone(self(),TZ).
|
||||||
|
|
||||||
|
set_timezone(Key,TZ) ->
|
||||||
|
ok = gen_server:call(?SRV,{set_timezone,Key,TZ}).
|
||||||
|
|
||||||
get_timezone() ->
|
get_timezone() ->
|
||||||
get_pd(?TZTAG).
|
get_timezone(self()).
|
||||||
|
|
||||||
get_timezone(Key) ->
|
get_timezone(Key) ->
|
||||||
get_env(?TZKEY(Key)).
|
gen_server:call(?SRV,{get_timezone,Key}).
|
||||||
|
|
||||||
clear_timezone() ->
|
clear_timezone() ->
|
||||||
unset_pd(?TZTAG).
|
clear_timezone(self()).
|
||||||
|
|
||||||
clear_timezone(Key) ->
|
clear_timezone(Key) ->
|
||||||
unset_env(?TZKEY(Key)).
|
ok = gen_server:call(?SRV, {clear_timezone, Key}).
|
||||||
|
|
||||||
register_parser(Parser) when is_function(Parser,1) ->
|
register_parser(Parser) when is_function(Parser,1) ->
|
||||||
register_parser(erlang:make_ref(),Parser).
|
register_parser(erlang:make_ref(),Parser).
|
||||||
|
|
||||||
register_parser(Key,Parser) when is_function(Parser,1) ->
|
register_parser(Key,Parser) when is_function(Parser,1) ->
|
||||||
set_env(?PARSERKEY(Key), Parser).
|
Key = gen_server:call(?SRV,{register_parser,Key,Parser}).
|
||||||
|
|
||||||
deregister_parser(Key) ->
|
deregister_parser(Key) ->
|
||||||
unset_env(?PARSERKEY(Key)).
|
ok = gen_server:call(?SRV,{deregister_parser,Key}).
|
||||||
|
|
||||||
deregister_parsers() ->
|
deregister_parsers() ->
|
||||||
[deregister_parser(Key) || {Key, _} <- get_parsers()].
|
ok = gen_server:call(?SRV,{deregister_parsers}).
|
||||||
|
|
||||||
get_parsers() ->
|
get_parsers() ->
|
||||||
get_all_env(?PARSERTAG).
|
gen_server:call(?SRV,{get_parsers}).
|
||||||
|
|
||||||
register_format(Key, Format) ->
|
register_format(Key,Format) ->
|
||||||
set_env(?FORMATKEY(Key), Format).
|
ok = gen_server:call(?SRV,{register_format,Key,Format}).
|
||||||
|
|
||||||
get_format(Key) ->
|
get_format(Key) ->
|
||||||
get_env(?FORMATKEY(Key)).
|
gen_server:call(?SRV,{get_format,Key}).
|
||||||
|
|
||||||
deregister_format(Key) ->
|
deregister_format(Key) ->
|
||||||
unset_env(?FORMATKEY(Key)).
|
ok = gen_server:call(?SRV,{deregister_format,Key}).
|
||||||
|
|
||||||
get_formats() ->
|
|
||||||
get_all_env(?FORMATTAG).
|
%% 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}.
|
||||||
|
|
||||||
%% PRIVATE TOOLS
|
%% PRIVATE TOOLS
|
||||||
|
|
||||||
%% App Vars
|
monitor_if_pid(Key) when is_pid(Key) ->
|
||||||
|
erlang:monitor(process,Key);
|
||||||
set_env(Key, Val) ->
|
monitor_if_pid(_) ->
|
||||||
gen_server:call(?SERVER, {set, ?KEY(Key), Val}).
|
do_nothing.
|
||||||
|
|
||||||
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,3 +1,7 @@
|
||||||
|
% vim: ts=4 sw=4 et
|
||||||
|
% Copyright (c) 2013 Jesse Gumm
|
||||||
|
% See LICENSE for licensing information.
|
||||||
|
|
||||||
-module(qdate_sup).
|
-module(qdate_sup).
|
||||||
|
|
||||||
-behaviour(supervisor).
|
-behaviour(supervisor).
|
||||||
|
@ -8,25 +12,21 @@
|
||||||
%% Supervisor callbacks
|
%% Supervisor callbacks
|
||||||
-export([init/1]).
|
-export([init/1]).
|
||||||
|
|
||||||
-define(SERVER, ?MODULE).
|
%% Helper macro for declaring children of supervisor
|
||||||
|
-define(CHILD(I, Type), {I, {I, start_link, []}, permanent, 5000, Type, [I]}).
|
||||||
|
|
||||||
|
%% ===================================================================
|
||||||
|
%% API functions
|
||||||
|
%% ===================================================================
|
||||||
|
|
||||||
start_link() ->
|
start_link() ->
|
||||||
supervisor:start_link({local, ?SERVER}, ?MODULE, []).
|
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
|
||||||
|
|
||||||
|
%% ===================================================================
|
||||||
|
%% Supervisor callbacks
|
||||||
|
%% ===================================================================
|
||||||
|
|
||||||
init([]) ->
|
init([]) ->
|
||||||
|
Server = ?CHILD(qdate_srv, worker),
|
||||||
SupFlags = #{},
|
{ok, { {one_for_one, 5, 10}, [Server]} }.
|
||||||
|
|
||||||
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