Compare commits

..

171 commits

Author SHA1 Message Date
Jesse Gumm
108c796687 Remove travis and replace badge with GH actions
Some checks failed
qdate tests and dialyzer / OTP 23.x (push) Has been cancelled
qdate tests and dialyzer / OTP 24.x (push) Has been cancelled
qdate tests and dialyzer / OTP 25.x (push) Has been cancelled
qdate tests and dialyzer / OTP 26.x (push) Has been cancelled
qdate tests and dialyzer / OTP 27.x (push) Has been cancelled
2024-09-05 15:04:50 -05:00
Jesse Gumm
43c1f3412b Update to checkout@v4 2024-09-05 15:01:11 -05:00
Jesse Gumm
dc2184ea06 Remove reference to Postgres/MySQL (GH actions config was largely copied from sql_bridge) 2024-09-05 14:56:59 -05:00
Jesse Gumm
70bab7df95 Updates to fix dialyzer failing on otp 26+ 2024-09-05 14:55:48 -05:00
Jesse Gumm
06347e7f31 Add github actions workflow 2024-09-05 14:35:38 -05:00
Jesse Gumm
9d0fb6d895 Remove ?else macro 2024-09-05 08:36:05 -05:00
Jesse Gumm
2072b49220 Update changelog - remove rebar3 binary 2024-01-09 19:18:29 -06:00
Jesse Gumm
cb873afb22 Update rebar.lock 2024-01-09 19:10:19 -06:00
Jesse Gumm
c109e7969b Rework the filenames because hex doesn't like them 2024-01-09 19:06:22 -06:00
Jesse Gumm
2a20cdea1e Update rebar.config wiht some hex stuff 2024-01-09 19:04:33 -06:00
Jesse Gumm
ea203ec90d Add some tag rules to makefile 2024-01-09 19:01:14 -06:00
Jesse Gumm
5a847014c9 Make qdate's version in app.src track the git tags 2024-01-09 18:59:58 -06:00
Jesse Gumm
794d03a5ee Produce a better error message if qdate hasn't been started 2024-01-09 18:58:02 -06:00
Jesse Gumm
817c14c46e Update copyright date comment in qdate.erl 2023-08-22 09:11:48 -05:00
Jesse Gumm
280f8e72f2 Increment version 2023-08-12 19:46:32 -05:00
Jesse Gumm
5afd1335f1 Add a make dev for easily working with qdate_localtime 2023-08-12 19:44:13 -05:00
Jesse Gumm
2ce94f82ce Fix dialyzer bugs 2023-08-12 19:43:46 -05:00
Jesse Gumm
5a352dc599 Fix the unnused erlnow() type 2023-08-12 10:40:13 -05:00
Jesse Gumm
357b5844eb
Merge pull request #42 from kianmeng/fix-typos
Fix typos
2022-01-28 08:33:04 -06:00
Kian-Meng Ang
d87d7d8b6c Fix typos 2022-01-28 08:17:41 +08:00
Jesse Gumm
92e20c474f
git: -> https: 2022-01-11 11:48:45 -06:00
Jesse Gumm
c5971cdcf4 update rebar3 2021-07-01 09:24:18 -05:00
Jesse Gumm
6d6b6ec9bd Update changelog and makefile 2021-07-01 08:43:18 -05:00
Jesse Gumm
c789fca51f Update to 0.7.0 2021-06-30 21:11:11 -05:00
Jesse Gumm
d6a431362d More dialyzer updates 2021-06-30 18:03:55 -05:00
Jesse Gumm
bbfef69d2e Convert qdate_srv to be an ETS server (dialyzer doesn't like using tuples as keys for application:set_env) 2021-06-30 14:44:45 -05:00
Jesse Gumm
3d72448491 bump to version 0.6.0 2021-06-01 08:35:13 -05:00
Jesse Gumm
596b573775 Erlware Commons => 1.5.0 for rebar2 2021-06-01 08:28:15 -05:00
Jesse Gumm
d0b6fc0011 fix error warning about _SameDay bound twice 2021-06-01 08:25:00 -05:00
Jesse Gumm
d159878f62 update rebar3 version 2021-06-01 08:21:55 -05:00
Jesse Gumm
69d7fdd626
Merge pull request #41 from SiftLogic/fix/preserve-millsec
Fix/preserve millsec
2021-05-03 14:28:56 -05:00
Leonard Boyce
f49368d3fc (feat) Support preserving Millisec in date parsing and formatting 2021-05-03 10:26:56 -04:00
Leonard Boyce
22b48f90de (chore) Ignore editor artifacts 2021-05-03 09:46:08 -04:00
Jesse Gumm
0a7d808290 Add age functions 2020-11-20 14:14:25 -06:00
Jesse Gumm
2f3afd7dbb Travis doesn't support 23.1 yet, so don't test with that 2020-11-15 08:37:52 -06:00
Jesse Gumm
68b462469f travis doesn't support 23.1 yet, so just do 23.0 until then 2020-11-15 01:09:31 -06:00
Jesse Gumm
1c405bbfc3 update travis for 23 2020-11-15 01:09:02 -06:00
Jesse Gumm
accda0db1a Just get rid of the old erlang versions 2019-08-21 13:21:57 -05:00
Jesse Gumm
28847aa896 Use local rebar3 only if there isn't one installed 2019-08-21 11:59:43 -05:00
Jesse Gumm
a0b9ad0e5f contributors=>maintainers for hex 2019-08-21 11:32:35 -05:00
Jesse Gumm
73d9706c7a travis tweaks 2019-08-21 11:30:46 -05:00
Jesse Gumm
b8a5075026 Version => 0.5.0 2019-08-21 11:23:32 -05:00
Jesse Gumm
ef98ff4ea4 Update changelog 2019-08-21 11:21:37 -05:00
Jesse Gumm
08a98032e2 Update changelog. Update rebar version 2019-08-21 11:19:18 -05:00
Jesse Gumm
a443d27c79 Update travis 2019-08-21 11:08:05 -05:00
Jesse Gumm
ed00a606f3 Add more versions for travis testing 2018-12-31 10:58:46 -06:00
Jesse Gumm
3448a6f5e0
Merge pull request #39 from tnt-dev/update-erlware_commons-url
Use actual erlware_commons for rebar 2
2018-12-31 10:35:00 -06:00
Jesse Gumm
b849ebde02
Merge pull request #38 from tnt-dev/fix-stacktrace
Stacktrace workaround for compatibility with OTP 21
2018-12-29 19:53:48 -06:00
Pavel Abalikhin
a51edf2e3e Use actual erlware_commons for rebar 2 2018-12-24 14:16:32 +03:00
Pavel Abalikhin
7314545b34 Stacktrace workaround for compatibility with OTP 21 2018-12-24 13:01:41 +03:00
Jesse Gumm
fa59231e1e
Merge pull request #37 from loudferret/iss36-UTC_problem_with_time_shift
fixed issue https://github.com/choptastic/qdate/issues/36
2018-09-18 13:00:00 -05:00
LoudFerret
67ee655bbb fixed issue https://github.com/choptastic/qdate/issues/36 2018-08-07 20:07:51 +02:00
Jesse Gumm
1cccb8392a Add support for named days of the week in beginning_week 2018-02-04 12:21:47 -06:00
Jesse Gumm
628c87557a Removing io:format left over from debuggin 2018-01-06 12:07:39 -06:00
Jesse Gumm
dd38e5cf98 Unexport fix_year_month. Was for testing only. 2018-01-06 12:05:08 -06:00
Jesse Gumm
99e55979dc Fix add_month(-12, January). Add more tests. Fixes #35 2018-01-06 12:03:26 -06:00
Jesse Gumm
4c91fc4c01
Merge pull request #34 from tnt-dev/fix-floor
Rename floor/1 to flooring/1
2017-11-22 05:35:24 -06:00
Pavel Abalikhin
31e123d9f1 Rename floor/1 to flooring/1
Fix for Erlang/OTP 20
2017-11-22 12:33:15 +03:00
Jesse Gumm
bab222aeb4 Fix adding months to date that ends in dec following year 2017-09-29 15:18:02 -05:00
Jesse Gumm
fc247e296b Update rebar2 to use working erlware_commons 2017-03-18 17:43:30 -05:00
Jesse Gumm
3e2688cbec Update changelog 2017-01-04 19:23:55 -06:00
Jesse Gumm
aa114b1ef8 Add end_week/[0-2] and beginning_week/0 2017-01-04 19:22:21 -06:00
Jesse Gumm
c42272a9eb Update changelog 2017-01-04 15:59:37 -06:00
Jesse Gumm
6fadc548d2 Update readme. Add some sanity guards on beginning_week 2017-01-04 15:58:27 -06:00
Jesse Gumm
d7267a7d67 Add sunday test to beginning_week 2017-01-04 15:53:16 -06:00
Jesse Gumm
ef5c9cfbb6 Add beginning_week/[1-2] 2017-01-04 15:45:33 -06:00
Jesse Gumm
0d5cd470e6 Merge pull request #25 from LIB53/master
Use qdate_localtime in app.src
2016-07-14 15:48:55 -05:00
LIB53
af9cb4dc4b Use qdate_localtime in app.src 2016-07-14 15:41:48 -05:00
Jesse Gumm
78bb08c4f8 Use the qdate_localtime hex package for rebar3 2016-07-07 16:15:39 -05:00
Jesse Gumm
5a3929bcea Update travis 2016-07-06 20:02:42 -05:00
Jesse Gumm
89555d25ef Rebar3 built with R15B03, and also use hex package for erlware_commons 2016-07-06 19:59:18 -05:00
Jesse Gumm
fd493ecf3d Reflect the qdate_localtime in rebar.script 2016-07-06 17:40:16 -05:00
Jesse Gumm
b193876305 Update rebar3, use qdate_localtime 2016-07-06 17:39:00 -05:00
Jesse Gumm
10d56c2e04 Add another note to sort/[1-3] 2016-06-17 17:14:46 -05:00
Jesse Gumm
7230f747e7 Update readme, Ensure sort can crash with option 2016-06-17 17:11:29 -05:00
Jesse Gumm
2a85d5d92f Add sort/[1-3] 2016-06-17 16:49:22 -05:00
Jesse Gumm
2dfcc52dd5 New config opt for default_timezone with fun 2016-05-31 18:46:02 -05:00
Jesse Gumm
366bc5e8d5 Fix beginning_day with different timezone 2016-05-19 03:56:43 +00:00
Jesse Gumm
4d42f5eddf Fix broken beginning_minute/1 2016-04-20 19:36:06 -05:00
Jesse Gumm
e413fcbf56 Add end_X/[0,1] functions. 2016-04-18 20:02:34 -05:00
Jesse Gumm
220883945a Update changelog 2016-04-14 11:24:50 -05:00
Jesse Gumm
66e9dc6ae9 get_timezone should return default tz if not set 2016-04-14 11:20:50 -05:00
Jesse Gumm
7ed65a0af1 Compress phrasing a bit in relative readme 2016-04-13 19:50:26 -05:00
Jesse Gumm
8671750e66 Update readme with relative parser 2016-04-13 19:47:32 -05:00
Jesse Gumm
795b3d8961 Add some between tests with the relative tests 2016-04-13 19:39:30 -05:00
Jesse Gumm
b550391a4e Add tests for relative parser 2016-04-13 19:38:04 -05:00
Jesse Gumm
6d88edea06 get rid of export_all (from testing) 2016-04-13 23:13:49 +00:00
Jesse Gumm
ff6b7139ca Fix relative calculation with improper timezone 2016-04-13 23:12:45 +00:00
Jesse Gumm
bb934b522f Fix relative parsing 2016-03-07 05:48:56 +00:00
Jesse Gumm
3564470578 Merge branch 'relative' of git://github.com/choptastic/qdate 2016-03-07 05:27:23 +00:00
Jesse Gumm
1ea2cadb1a Fix relative parser 2016-03-05 16:26:58 -06:00
Jesse Gumm
41b313d425 Add relative date/time parsing parser. 2016-03-05 15:16:03 -06:00
Jesse Gumm
5d73286e92 Allow a custom parser to return a unix timestamp 2016-03-05 13:52:02 -06:00
Jesse Gumm
5e43673c34 Add before and after as operators 2016-03-05 13:51:45 -06:00
Jesse Gumm
6036208bb5 Add test for #22.
Fixed in erlang_localtime (c0c4e3e)
2016-03-01 00:12:23 -06:00
Jesse Gumm
bf7f84590f Update readme 2016-02-26 13:51:14 -06:00
Jesse Gumm
1f7f5d9f69 Update changelog 2016-02-26 13:50:49 -06:00
Jesse Gumm
f39b4edb92 Add code markers around between 2016-02-24 13:21:00 -06:00
Jesse Gumm
4cd1a41cb4 Add readme for between 2016-02-24 13:17:13 -06:00
Jesse Gumm
569c7db56b Rework between/3, and add between/5 2016-02-24 13:09:33 -06:00
Jesse Gumm
69967d71ff Add initial experiment with between/[2,3] 2016-02-24 11:09:28 -06:00
Jesse Gumm
cc9dee4e08 Add config to readme 2016-02-22 21:14:18 -06:00
Jesse Gumm
d0ee71a0a7 Fix tabs in qdate.config 2016-02-21 16:13:47 -06:00
Jesse Gumm
43f6a78870 Merge branch 'master' of github.com:choptastic/qdate 2016-02-21 16:12:26 -06:00
Jesse Gumm
2eb0f88c61 Add sample qdate.config file 2016-02-21 16:12:05 -06:00
Jesse Gumm
7910de376e Merge branch 'master' of github.com:choptastic/qdate
Conflicts:
	rebar.config
2016-01-31 09:10:19 -06:00
Jesse Gumm
2a053253fb Point to my master branch 2016-01-31 09:08:31 -06:00
Jesse Gumm
2d40869c1a Add some tests for new tz_database changes 2016-01-31 09:07:54 -06:00
Jesse Gumm
0bf12290ec Update travis 2016-01-28 20:18:02 -06:00
Jesse Gumm
63a34f1c68 update readme 2016-01-28 20:12:49 -06:00
Jesse Gumm
37f8bf48e9 Update changelog 2015-11-07 11:42:25 -06:00
Jesse Gumm
d73a31e664 Add beginning_X/0 variations 2015-11-07 11:39:24 -06:00
Jesse Gumm
6ca6037f29 Add beginning_X functions with documentation
* Still need tests
2015-11-07 11:33:16 -06:00
Jesse Gumm
792245bb26 Fix tab issue (again) 2015-11-06 17:29:02 -06:00
Jesse Gumm
faeadb732a Fix tab issue in readme 2015-11-06 17:28:27 -06:00
Jesse Gumm
c685f4dc12 Add range docs to readme 2015-11-06 17:26:26 -06:00
Jesse Gumm
ef2075d475 Update changelog 2015-11-06 17:06:30 -06:00
Jesse Gumm
647e26db15 Update copyright notice on qdate.erl 2015-11-06 17:04:55 -06:00
Jesse Gumm
b1387c08c0 Add range functions
* Documentation needed
2015-11-06 17:03:16 -06:00
Jesse Gumm
c4c20db815 Update changelog 2015-11-05 11:35:16 -06:00
Jesse Gumm
a504a6adfc Update rebar.config back to choptastic/erlang_localtime
*temporary* until a commit is merged
2015-11-05 11:31:38 -06:00
Jesse Gumm
8427b7bdc6 Update dependencies
* Erlware Commons to non-crashing-with-rebar 0.15.0
* Erlang Localtime to base version, since it now has the necessary
  support for qdate functionality.
2015-11-05 09:57:00 -06:00
Jesse Gumm
48e8523e39 Merge pull request #16 from mfelsche/m/ec_date_z
Allow time to be a 4-tuple with subsecond accuracy. Does not yet *truly* support it, but merely accepts it and ignores it.
2015-10-14 14:45:53 -05:00
Matthias Wahl
52770185f9 fix date parse compatibility with ec_date
as it turns out ec_dates datetime() type can also be {{Y,M,D},{H,M,S,Ms}} which was not yet covered
2015-10-14 16:18:16 +02:00
Jesse Gumm
a443d476ac Update makefile and changelog 2015-07-27 14:25:21 -05:00
Jesse Gumm
7ab98975d8 Merge branch 'appsrc-fix' of git://github.com/project-fifo/qdate into project-fifo-appsrc-fix 2015-07-27 14:03:59 -05:00
Heinz N. Gies
15ded5317f added missing applications to app.src 2015-07-27 15:58:38 +02:00
Jesse Gumm
80b3a88530 Add 18 2015-07-26 08:48:58 -05:00
Jesse Gumm
d6ec97246f Merge branch 'master2' into rebar3 2015-07-26 08:35:36 -05:00
Jesse Gumm
b71173dc65 version bump 2015-07-26 08:34:12 -05:00
Jesse Gumm
0e0eb4186f Add some comments to rebar.config 2015-07-26 08:28:49 -05:00
Jesse Gumm
0b963146fe Remove R14 from travis. rebar3 doesn't work on R14 2015-07-26 08:27:46 -05:00
Heinz N. Gies
6dbeefc99b profile trick to make it rebar2 compatible. 2015-07-25 21:36:35 +02:00
Heinz N. Gies
02734c103b Fixed test task in makefile, damn me... 2015-07-25 20:35:09 +02:00
Heinz N. Gies
5f1f4a7785 rebar3ified makefile 2015-07-25 20:33:15 +02:00
Heinz N. Gies
5387ec8f02 rebar3 and hex. 2015-07-25 20:21:07 +02:00
Jesse Gumm
4f19dcf5ec Merge pull request #12 from aramallo/master
Removed io:format/2 call from try_parsers/2.
2015-07-09 11:07:20 -05:00
Alejandro M. Ramallo
c3b490ec52 Removed io:format/2 call from try_parsers/2.
Something that was left during debugging I guess ;-)
2015-07-09 16:54:23 +01:00
Jesse Gumm
644ed69cda remove "in-development" from changelog 2015-04-30 14:51:10 -05:00
Jesse Gumm
eeb797e1f8 Bump to 0.4.0 2015-04-30 14:49:24 -05:00
Jesse Gumm
6d7a031d46 Add clarification comments at the top of qdate_srv 2015-04-30 14:48:47 -05:00
Jesse Gumm
5fbbf93d4d Update readme. Remove unnecessary macro from qdate_srv 2015-04-30 14:45:53 -05:00
Jesse Gumm
bb33398614 Remove dependency on a server
Everything just uses application vars and procdict now.
2015-04-30 14:31:41 -05:00
Jesse Gumm
cd1faf8631 Update readme 2015-04-29 12:39:12 -05:00
Jesse Gumm
b4c3d4a302 Add date arithmetic function alts from "now" 2015-04-29 12:31:52 -05:00
Jesse Gumm
c750c339ff Update readme wording a bit 2015-02-27 09:40:30 -06:00
Jesse Gumm
91a502550a Add 17.4 to travis 2015-02-23 17:15:02 -06:00
Jesse Gumm
1e5bc5dfc5 Update Readme 2015-01-06 14:44:53 -06:00
Jesse Gumm
80a856721d Merge pull request #11 from pdincau/travis-matrix
Adding erl17 to travis build matrix
2014-12-01 15:19:32 -06:00
Paolo D'Incau
ff4a9a6eee Adding erl17 to travis build matrix 2014-12-01 12:41:49 +01:00
Jesse Gumm
e7bf41e3ad Update TODO 2014-08-30 18:06:58 -05:00
Jesse Gumm
aeed7641da Update chagelog 2014-08-30 18:05:55 -05:00
Jesse Gumm
72cececf88 Fix when relying on env var for TZ 2014-08-30 18:04:34 -05:00
Jesse Gumm
928c4e9be4 Add get_parsers() and get_formats() 2014-08-30 15:53:46 -05:00
Jesse Gumm
58f75fc966 Fix some logic, add a few more tests 2014-08-23 01:22:15 -05:00
Jesse Gumm
8cd386bb40 Fix weird accidental indentation in ensure_timezone 2014-08-23 00:31:22 -05:00
Jesse Gumm
fb63b7a318 Preliminary arithmetic tests. One failing test 2014-08-23 00:29:59 -05:00
Jesse Gumm
df55df9db6 Add some "in dev" notes to README for date arith 2014-08-22 23:59:37 -05:00
Jesse Gumm
21a9fc1618 Update changelog to add (in development) 2014-08-22 23:58:47 -05:00
Jesse Gumm
4cc45533ce Remove deps dir from rebar.config (unnecessary)
(Thanks @mworrell for pointing this out)
2014-08-22 23:57:34 -05:00
Jesse Gumm
daa04395f2 Update changelog and README 2014-08-22 23:57:23 -05:00
Jesse Gumm
5cb5612857 Add Date Arithmetic (still needs tests) 2014-08-22 23:47:39 -05:00
Jesse Gumm
4135dfa8e3 Update CHANGELOG 2014-05-11 23:12:16 -05:00
Jesse Gumm
58a92482d1 Make work with OTP 17 2014-04-11 01:37:44 -05:00
Jesse Gumm
6f9ca7d13c Add thanks to @nlundgaard-al 2014-03-11 22:57:17 -05:00
Jesse Gumm
328b2e0b32 Fix test where CDT was being implied despite CST specification
flipping daylight saving time.
2014-03-11 22:41:17 -05:00
Jesse Gumm
5139108fcc Merge pull request #6 from stwind/master
Support character escaping
2014-01-16 19:32:27 -08:00
stwind
13bab9fc8e Support character escaping 2014-01-17 11:08:20 +08:00
Jesse Gumm
4a98e54d9d Add disambiguation to to_now and to_unixtime 2013-10-22 17:42:17 -05:00
Jesse Gumm
7384819edb Add Timezone/DST Disambiguation 2013-10-22 17:29:16 -05:00
Jesse Gumm
ef659da9a4 Fix formatting of rebar.config
{branch, master} instead of "HEAD"
2013-10-22 13:32:42 -05:00
17 changed files with 1730 additions and 294 deletions

47
.github/workflows/tests-workflow.yml vendored Normal file
View 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
View file

@ -1,6 +1,10 @@
*~ *~
*.beam *.beam
*.sw? *.sw?
*.iml
deps/ deps/
ebin/ ebin/
.eunit/ .eunit/
.idea/
_build
doc/

View file

@ -1,10 +0,0 @@
language: erlang
script: "make test"
otp_release:
- R16B
- R15B02
- R15B01
- R15B
- R14B03
- R14B02
before_script: "sudo apt-get --yes --force-yes install libpam0g-dev"

View file

@ -1,3 +1,79 @@
## 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 ## 0.2.1
* Fix allowing timezone names to be binary * Fix allowing timezone names to be binary

View file

View file

@ -1,13 +1,41 @@
all: get-deps compile all: compile
get-deps: # Check if rebar3.mk exists, and if not, download it
./rebar get-deps ifeq ("$(wildcard rebar3.mk)","")
$(shell curl -O https://raw.githubusercontent.com/choptastic/rebar3.mk/master/rebar3.mk)
endif
compile: # rebar3.mk adds a new rebar3 rule to your Makefile
./rebar compile # (see https://github.com/choptastic/rebar3.mk) for full info
include rebar3.mk
test: get-deps compile compile: rebar3
./rebar skip_deps=true eunit $(REBAR) compile
update: rebar3
$(REBAR) update
test:
EUNIT=1 $(REBAR) compile
EUNIT=1 $(REBAR) eunit
dialyzer: compile
DIALYZER=1 $(REBAR) dialyzer
dev:
mkdir -p _checkouts
cd _checkouts; git clone https://github.com/choptastic/qdate_localtime
run: rebar3
$(REBAR) shell
push_tags:
git push --tag
pull_tags:
git pull --tag
publish: rebar3 pull_tags
$(REBAR) hex publish
run:
erl -pa ebin/ deps/*/ebin/ -eval "application:start(qdate)"

View file

@ -1,6 +1,6 @@
# qdate - A Wrapper for Erlang Date and Timezone Management # qdate - Erlang Date and Timezone Library
[![Build Status](https://travis-ci.org/choptastic/qdate.png?branch=master)](https://travis-ci.org/choptastic/qdate) [![qdate tests and dialyzer](https://github.com/choptastic/qdate/actions/workflows/tests-workflow.yml/badge.svg)](https://github.com/choptastic/qdate/actions/workflows/tests-workflow.yml)
## Purpose ## Purpose
@ -21,12 +21,15 @@ benefits of `ec_date` and `erlang_localtime`, as well as extending the
capabilities of both to provide for other needed tools found in a single capabilities of both to provide for other needed tools found in a single
module. module.
`qdate` will provide, under the roof of a single module date and time formatting `qdate` provides date and time formatting and parsing from and into:
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
@ -65,10 +68,15 @@ T, Z, r, and c), `qdate` will handle them for us.
+ `to_now(Date)` - converts any date/time format to Erlang now format. + `to_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 `Date` argument. `Date` will always be the last immediately left of the `Disambiguate` argument (if it's specified), which is
always immediately left of `Date` argument. `Date` will always be the last
argument to any of the conversion and formatting functions. argument to any of the conversion and formatting functions.
#### Understanding Timezone Determining and Conversions #### Understanding Timezone Determining and Conversions
@ -90,9 +98,88 @@ will infer the timezone in the following order.
`set_timezone/1` only applies to that *specific* process. If none is `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.
#### 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`
@ -105,24 +192,72 @@ will infer the timezone in the following order.
`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`
@ -158,6 +293,7 @@ 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
@ -165,6 +301,7 @@ 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
@ -361,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
@ -376,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").
@ -532,6 +692,175 @@ 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
@ -543,6 +872,11 @@ not exist.
### Thanks to Additional Contributors ### Thanks to Additional Contributors
+ [Mark Allen](https://github.com/mrallen1) + [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 ## Changelog
@ -553,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
+ 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 Normal file
View file

@ -0,0 +1,20 @@
%% vim: ts=4 sw=4 et ft=erlang
[{qdate, [
%% default_timezone can be one of two things:
%% 1) An actual timezone. Either short-form like "GMT", "UTC", or a
%% longer-form (but more likely to pick the correct daylight saving
%% config timezone like "America/Chicago"
%% 2) A 2-tuple of {Module, Function}, which will be called as
%% Module:Function() to determine the timezone (say you wanted to
%% determine timezone based on some kind of environmental conditions)
{default_timezone, "GMT"},
%% See readme section here:
%% https://github.com/choptastic/qdate#about-backwards-compatibility-with-ec_date-and-deterministic-parsing
{deterministic_parsing, {zero, zero}},
%% Preserve Milliseconds in parsing
%% Changes the return for qdate:to_date to include any Ms value
%% and also changes the return of to_string to include the Ms value
{preserve_ms, false}
]}].

BIN
rebar vendored

Binary file not shown.

View file

@ -1,13 +1,24 @@
% vim:ts=4 sw=4 et ft=erlang %% -*- erlang -*-
{require_otp_vsn, "R13B04|R14|R15|R16"}. %% vim:ts=4 sw=4 et ft=erlang
{cover_enabled, true}. {cover_enabled, true}.
%{erl_opts, [debug_info,{i,"site/include"}]}. {dialyzer, [
{exclude_apps, []},
{deps_dir, ["deps"]}. {warnings, []}
{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">>}]}.

15
rebar.config.script Normal file
View 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
View 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">>}]}
].

View file

@ -1,12 +1,17 @@
{application, qdate, {application, qdate,
[ [
{description, "Simple Date and Timezone handling for Erlang"}, {description, "Simple Date and Timezone handling for Erlang"},
{vsn, "0.2.1"}, {vsn, git},
{registered, []}, {registered, []},
{applications, [ {applications, [
kernel, kernel,
stdlib stdlib,
qdate_localtime,
erlware_commons
]}, ]},
{mod, { qdate_app, []}}, {modules, [qdate, qdate_srv, qdate_sup, qdate_app]},
{env, []} {env, []},
{licenses, ["MIT"]},
{mod, {qdate_app, []}},
{links, [{"Github", "https://github.com/choptastic/qdate"}]}
]}. ]}.

File diff suppressed because it is too large Load diff

View file

@ -1,7 +1,3 @@
% 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).
@ -9,12 +5,11 @@
%% 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.

View file

@ -1,22 +1,10 @@
% vim: ts=4 sw=4 et % vim: ts=4 sw=4 et
% Copyright (c) 2013 Jesse Gumm % Copyright (c) 2013-2021 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,
@ -33,133 +21,172 @@
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
start_link() ->
gen_server:start_link({local, ?SRV}, ?MODULE, [], []).
set_timezone(TZ) -> set_timezone(TZ) ->
set_timezone(self(),TZ). put_pd(?TZTAG, TZ).
set_timezone(Key,TZ) -> set_timezone(Key, TZ) ->
ok = gen_server:call(?SRV,{set_timezone,Key,TZ}). set_env(?TZKEY(Key), TZ).
get_timezone() -> get_timezone() ->
get_timezone(self()). get_pd(?TZTAG).
get_timezone(Key) -> get_timezone(Key) ->
gen_server:call(?SRV,{get_timezone,Key}). get_env(?TZKEY(Key)).
clear_timezone() -> clear_timezone() ->
clear_timezone(self()). unset_pd(?TZTAG).
clear_timezone(Key) -> clear_timezone(Key) ->
ok = gen_server:call(?SRV, {clear_timezone, Key}). unset_env(?TZKEY(Key)).
register_parser(Parser) when is_function(Parser,1) -> register_parser(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) ->
Key = gen_server:call(?SRV,{register_parser,Key,Parser}). set_env(?PARSERKEY(Key), Parser).
deregister_parser(Key) -> deregister_parser(Key) ->
ok = gen_server:call(?SRV,{deregister_parser,Key}). unset_env(?PARSERKEY(Key)).
deregister_parsers() -> deregister_parsers() ->
ok = gen_server:call(?SRV,{deregister_parsers}). [deregister_parser(Key) || {Key, _} <- get_parsers()].
get_parsers() -> get_parsers() ->
gen_server:call(?SRV,{get_parsers}). get_all_env(?PARSERTAG).
register_format(Key,Format) -> register_format(Key, Format) ->
ok = gen_server:call(?SRV,{register_format,Key,Format}). set_env(?FORMATKEY(Key), Format).
get_format(Key) -> get_format(Key) ->
gen_server:call(?SRV,{get_format,Key}). get_env(?FORMATKEY(Key)).
deregister_format(Key) -> deregister_format(Key) ->
ok = gen_server:call(?SRV,{deregister_format,Key}). unset_env(?FORMATKEY(Key)).
get_formats() ->
%% SERVER FUNCTIONS get_all_env(?FORMATTAG).
-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
monitor_if_pid(Key) when is_pid(Key) -> %% App Vars
erlang:monitor(process,Key);
monitor_if_pid(_) -> set_env(Key, Val) ->
do_nothing. gen_server:call(?SERVER, {set, ?KEY(Key), Val}).
get_env(Key) ->
get_env(Key, undefined).
get_env(Key, Default) ->
case ets:lookup(?TABLE, ?KEY(Key)) of
[{__Key, Val}] -> Val;
[] -> Default
end.
unset_env(Key) ->
gen_server:call(?SERVER, {unset, ?KEY(Key)}).
get_all_env(FilterTag) ->
try ets:tab2list(?TABLE) of
All ->
[{Key, V} || {{?BASETAG, {Tag, Key}}, V} <- All, Tag==FilterTag]
catch
error:badarg:S ->
Msg = maybe_start_msg(),
logger:error(Msg),
error({qdate_get_all_env_failed, #{
original_error => {error, badarg, S}
}})
end.
maybe_start_msg() ->
"Attempting to read qdate environment failed (qdate_srv:get_all_env/1).\n"
"qdate may not have been started properly.\n"
"Please ensure qdate is started properly by either:\n"
"* Putting qdate in the 'applications' key in your .app.src or .app file, or\n"
"* Starting it manually with application:ensure_all_started(qdate) or qdate:start().".
%% ProcDic Vars
get_pd(Key) ->
erlang:get(?KEY(Key)).
put_pd(Key, Val) ->
erlang:put(?KEY(Key), Val),
ok.
unset_pd(Key) ->
put_pd(Key, undefined).

View file

@ -1,7 +1,3 @@
% 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).
@ -12,21 +8,25 @@
%% Supervisor callbacks %% Supervisor callbacks
-export([init/1]). -export([init/1]).
%% Helper macro for declaring children of supervisor -define(SERVER, ?MODULE).
-define(CHILD(I, Type), {I, {I, start_link, []}, permanent, 5000, Type, [I]}).
%% ===================================================================
%% API functions
%% ===================================================================
start_link() -> start_link() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, []). supervisor:start_link({local, ?SERVER}, ?MODULE, []).
%% ===================================================================
%% Supervisor callbacks
%% ===================================================================
init([]) -> init([]) ->
Server = ?CHILD(qdate_srv, worker),
{ok, { {one_for_one, 5, 10}, [Server]} }. SupFlags = #{},
ChildSpec = #{
id=>qdate_srv,
start=>{qdate_srv, start_link, []}
},
{ok, {SupFlags, [ChildSpec]}}.
%%%===================================================================
%%% Internal functions
%%%===================================================================