{"id":16241681,"url":"https://github.com/5ht/qdate","last_synced_at":"2025-04-08T09:59:24.653Z","repository":{"id":145087099,"uuid":"186714904","full_name":"5HT/qdate","owner":"5HT","description":"QDATE","archived":false,"fork":false,"pushed_at":"2019-05-14T23:27:00.000Z","size":2300,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-02-14T06:52:44.140Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Erlang","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/5HT.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-05-14T23:26:17.000Z","updated_at":"2021-11-07T15:02:23.000Z","dependencies_parsed_at":null,"dependency_job_id":"9a20e2bd-16ec-4ac9-9a49-c392b2eca2ef","html_url":"https://github.com/5HT/qdate","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/5HT%2Fqdate","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/5HT%2Fqdate/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/5HT%2Fqdate/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/5HT%2Fqdate/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/5HT","download_url":"https://codeload.github.com/5HT/qdate/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247819948,"owners_count":21001394,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-10T14:08:19.634Z","updated_at":"2025-04-08T09:59:24.632Z","avatar_url":"https://github.com/5HT.png","language":"Erlang","readme":"# qdate - Erlang Date and Timezone Library\n\n[![Build Status](https://travis-ci.org/choptastic/qdate.png?branch=master)](https://travis-ci.org/choptastic/qdate)\n\n## Purpose\n\nErlang Date and Time management is rather primitive, but improving.\n\n[dh_date](https://github.com/daleharvey/dh_date), of which `ec_date` in \n[erlware_commons](https://github.com/erlware/erlware_commons) is a fork, is a\nhuge step towards formatting and parsing dates in a way that compares nicely\nwith PHP's [date](http://php.net/manual/en/function.date.php) and \n[strtotime](http://php.net/manual/en/function.strtotime.php) functions.\n\nUnfortunately, `ec_date` doesn't deal with timezones, but conveniently, \nthe project [erlang_localtime](https://github.com/dmitryme/erlang_localtime)\ndoes.\n\nIt is the express purpose of this `qdate` package to bring together the\nbenefits of `ec_date` and `erlang_localtime`, as well as extending the\ncapabilities of both to provide for other needed tools found in a single\nmodule.\n\n`qdate` provides date and time formatting and parsing from and into:\n + Formatting Strings\n + Erlang Date Format\n + Erlang Now Format\n + Unixtime integers\n + Timezones\n\nAnd all this while dealing with timezone parsing, formatting, conversion\nand overall management.\n\n#### Acceptable Date Formats\n\n  + Erlang Date Format: `{{Y,M,D},{H,M,S}}`\n  + Erlang Now Format: `{MegaSecs, Secs, MicroSecs}`\n  + Date String: `\"2013-12-31 08:15pm\"` (including custom formats as defined\n    with `qdate:register_parser/2` - see below)\n  + Integer Unix Timestamp: 1388448000\n  + A Two-tuple, where the first element is one of the above, and the second\n    is a timezone.  (i.e. `{{{2008,12,21},{23,59,45}}, \"EST\"}` or\n    `{\"2008-12-21 11:59:45pm\", \"EST\"}`). **Note:** While, you can specify a\n    timezone along with unix timestamps or the Erlang now format, it won't do\n    anything, as both of those timestamps are absolute, and imply GMT.\n\n\nAll while doing so by allowing you to either set a timezone by some arbitrary\nkey or by using the current process's Pid is the key.\n\nFurther, while `ec_date` doesn't support PHP's timezone characters (e, I, O, P,\nT, Z, r, and c), `qdate` will handle them for us.\n\n## Exported Functions:\n\n### Conversion Functions\n\n  + `to_string(FormatString, ToTimezone, Date)` - \"FormatString\" is a string\n    that follows PHP's `date` function formatting rules. The date will be\n    converted to the specified `ToTimezone`.\n  + `to_string(FormatString, Date)` - same as `to_string/3`, but the `Timezone`\n    is intelligently determined (see below)\n  + `to_string(FormatString)` - same as `to_string/2`. but uses the current\n    time as `Date`\n  + `to_date(ToTimezone, Date)` - converts any date/time format to Erlang date\n    format. Will first convert the date to the timezone `ToTimezone`.\n  + `to_date(Date)` - same as `to_date/2`, but the timezone is determined (see below).\n  + `to_now(Date)` - converts any date/time format to Erlang now format.\n  + `to_unixtime(Date)` - converts any date/time format to a unixtime integer\n\nA **ToTimezone** value of the atom `auto` will automatically determine the\ntimezone. For example, `to_date(Date, auto)` is exactly the same as\n`to_date(Date)`\n\n**A Note About Argument Order**: In all cases, `ToTimezone` is optional and if\nomitted, will be determined as described below in \"Understanding Timezone\nDetermining and Conversion\". If `ToTimezone` is specified, it will always be\nimmediately left of the `Disambiguate` argument (if it's specified), which is\nalways immediately left of `Date` argument. `Date` will always be the last\nargument to any of the conversion and formatting functions.\n\n#### Understanding Timezone Determining and Conversions\n\nThere is a lot of timezone inferring going on here.\n\nIf a `Date` string contains timezone information (i.e.\n`\"2008-12-21 6:00pm PST\"`), then `qdate` will parse that properly, determine\nthe specified `PST` timezone, and do conversions based on that timezone.\nFurther, you can specify a timezone manually, by specifying it as as a\ntwo-tuple for `Date` (see \"Acceptable Date formats\" above).\n\nIf no timezone is specified or determinable in a `Date` variable, then `qdate`\nwill infer the timezone in the following order.\n\n  + If specified by `qdate:set_timezone(Timezone)` for that process. Note, as \n    specified below (in the \"Timezone Functions\" section), `set_timezone/1` is\n    a shortcut to `set_timezone(self(), Timezone)`, meaning that\n    `set_timezone/1` only applies to that *specific* process. If none is\n    specified.\n  + If no timezone is specified for the process, `qdate` looks at the `qdate`\n\tapplication variable `default_timezone`. `default_timezone` can be either a\n    hard-specified timezone, or a `{Module, Function}` tuple.  The tuple format\n    should return either a timezone or the atom `undefined`.\n  + If no timezone is specified by either of the above, `qdate` assumes \"GMT\"\n    for all dates.\n  + A timezone value of `auto` will act as if no timezone is specified.\n\n#### Disambiguating Ambiguous Timezone Conversions\n\nSometimes, when youre converting a datetime from one timezone to another, there\nare potentially two different results if the conversion happens to land on in a\ntimezone that's in the middle of a Daylight Saving conversion.  For example,\nconverting \"11-Nov-2013 1:00:am\" in \"America/New York\" to \"GMT\" could be both\n\"5am\" and \"6am\" in GMT, since \"1am EST\". This is a side effect of the\n\"intelligence\" of `qdate` - `qdate` would notice that 1am in New York is EST,\nand should be converted to \"1am EST\", and then do the conversion from \"1am EST\"\nto \"GMT\".  This can lead to confusion.\n\nFurther, since `qdate` attempts to be \"smart\" about mistakenly entered\ntimezones (ie, if you entered \"2013-01-01 EDT\", `qdate` knows that \"EDT\"\n(Eastern Daylight Time) doesn't apply to January first, so it *assumes* you\nmeant \"EST\".\n\n**THE SOLUTION** to this tangled mess that we call Daylight Saving Time is to\nprovide an option to disambiguate if you so desire. By default disambiguation\nis disabled, and `qdate` will just guess as to it's best choice. But if you so\ndesire, you can make sure qdate does *both* conversions, and returns both.\n\nYou can do this by passing a `Disambiguation` argument to `to_string`,\n`to_date`, `to_unixtime`, and `to_now`. `Disambiguation` can be an atom of the\nvalues:\n\n  + `prefer_standard` *(Default Behavior)*: If an ambiguous result occurs,\n    qdate will return the date in standard time rather than daylight time.\n  + `prefer_daylight`: If an ambiguous result occurs, qdate will return the\n    preferred daylight time.\n  + `both`: If an ambiguous result occurs, `qdate` will return the tuple:\n    `{ambiguous, DateStandard, DateDaylight}`, where `DateStandard` is the date\n\tin Standard Time, and `DateDaylight` is the date in Daylight Saving Time.\n\nSo the expanded conversions functions are:\n\n  + `to_date(ToTimezone, Disambiguate, Date)`\n  + `to_string(FormatString, ToTimezone, Disambiguate, Date)`\n  + `to_unixtime(Disambiguate, Date)`\n  + `to_now(Disambiguate, Date)`\n\nExamples:\n\n```erlang\n1\u003e qdate:set_timezone(\"GMT\").\nok\n\n%% Here, converting GMT 2013-11-03 6AM to America/New York yields an ambiguous\n%% result\n2\u003e qdate:to_date(\"America/New York\", both, {{2013,11,3},{6,0,0}}).\n{ambiguous,{{2013,11,3},{1,0,0}},{{2013,11,3},{2,0,0}}}\n\n%% Let's just use daylight time\n3\u003e qdate:to_date(\"America/New York\", prefer_daylight, {{2013,11,3},{6,0,0}}).\n{{2013,11,3},{2,0,0}}\n\n%% Let's just use standard time (the default behavior)\n4\u003e qdate:to_date(\"America/New York\", prefer_standard, {{2013,11,3},{6,0,0}}).\n{{2013,11,3},{1,0,0}}\n\n5\u003e qdate:set_timezone(\"America/New York\").\nok\n\n%% Switching from 1AM Eastern Time to GMT yields a potentially ambiguous result\n6\u003e qdate:to_date(\"GMT\", both, {{2013,11,3},{1,0,0}}).\n{ambiguous,{{2013,11,3},{6,0,0}},{{2013,11,3},{5,0,0}}}\n\n%% Use daylight time for conversion\n7\u003e qdate:to_date(\"GMT\", prefer_daylight, {{2013,11,3},{1,0,0}}).\n{{2013,11,3},{5,0,0}}\n\n%% Here we demonstrated that even if we ask for \"both\", if there is no\n%% ambiguity, the plain date is returned\n8\u003e qdate:to_date(\"GMT\", both, {{2013,11,3},{5,0,0}}).\n{{2013,11,3},{10,0,0}}\n```\n\n#### Conversion Functions provided for API compatibility with `ec_date`\n\n  + `parse/1` - Same as `to_date(Date)`\n  + `nparse/1` - Same as `to_now(Date)`\n  + `format/1` - Same as `to_string/1`\n  + `format/2` - Same as `to_string/2`\n\n### Date and Time Comparison\n\n`qdate` provides a few convenience functions for performing date comparisons.\n\n  + `compare(A, B) -\u003e -1|0|1` - Like C's `strcmp`, returns:\n    + `0`: `A` and `B` are exactly the same.\n    + `-1`: `A` is less than (before) `B`.\n    + `1`: `A` is greater than (after) `B`.\n  + `compare(A, Operator, B) -\u003e true|false` - Operator is an infix comparison operator, and\n    the function will return a boolean. Will return `true` if:\n    + `'='`, or `'=='` - `A` is the same time as `B`\n    + `'/='`, or `'=/='` or `'!='` - `A` is not the same time as `B`\n    + `'\u003c'` - `A` is before `B`\n    + `'\u003e'` - `A` is after `B`\n    + `'=\u003c'` or `'\u003c='` - `A` is before or equal to `B`\n    + `'\u003e='` or `'=\u003e'` - `A` is after or equal to `B`\n  + `between(A, Date, B) -\u003e true|false` - The provided `Date` is (inclusively)\n    between `A` and `B`. That is, `A =\u003c Date =\u003c B`.\n  + `between(A, B) -\u003e true|false` - shortcut for `between(A, now(), B)`\n  + `between(A, Op1, Date, Op2, B) -\u003e true|false` - the fully verbose option of\n    comparing between. `Op1` and `Op2` are custom operators. For example, if\n    you wanted to do an exclusive `between`, you can do:\n\t`between(A, '\u003c', Date, '\u003c', B)`\n\n**Note 1:** `Operator` must be an atom.\n\n**Note 2:** These functions will properly compare times with different timezones\n(for example: `compare(\"12am CST\",'==',\"1am EST\")` will properly return true)\n\n### Sorting\n\n`qdate` also provides a convenience functions for sorting lists of dates/times:\n\n  + `sort(List)` - Sort the list in ascending order of earliest to latest.\n  + `sort(Op, List)` - Sort the list where `Op` is one of the following:\n    + `'\u003c'` or `'=\u003c'` or `'\u003c='` - Sort ascending\n    + `'\u003e'` or `'\u003e='` or `'=\u003e'` - Sort descending\n  + `sort(Op, List, Opts)` - Sort the list according to the `Op`, with options provided in `Opts`. `Opts` is a proplist of the following options:\n    + `{non_dates, NonDates}` - Tells it how to handle non-dates.  `NonDates` can be any of the following:\n      + `back` **(default)** - put any non-dates at the end (the back) of the list\n      + `front` - put any non-dates at the beginning of the list\n      + `crash` - if there are any non-dates, crash.\n\nExample:\n\n```erlang\n\t 1\u003e Dates = [\"non date string\", \u003c\u003c\"garbage\"\u003e\u003e,\n\t\t1466200861, \"2011-01-01\", \"7pm\",\n\t\t{{1999,6,21},{5,30,0}}, non_date_atom, {some_tuple,123}].\n\t 2\u003e qdate:sort('\u003e=', Dates, [{non_dates, front}]).\n     [\u003c\u003c\"garbage\"\u003e\u003e,\"non date string\",\n\t  {some_tuple,123},\n\t  non_date_atom,1466200861,\"2011-01-01\",\n\t  {{1999,6,21},{5,30,0}},\n\t  \"7pm\"]\n```\n\n**Note 1:** This sorting is optimized to be much faster than using a home-grown\nsort using the `compare` functions, as this normalizes the items in the list\nbefore comparing (so it's only really comparing integers, which is quite fast).\n\n**Note 2:** This is one of the few qdate functions that don't have the \"Date\"\nas the last argument. This follows the pattern in Erlang/OTP to put options as\nthe last argument (for example, `re:run/3`)\n\n**Note 3:** You'll notice that qdate's sorting retains the original terms (in\nthe example above, we compared a datetime tuple, unix timestamp, and two\nstrings (along with a number of non-dates, which were just prepended to the\nfront of the list).\n\n### Timezone Functions\n\n  + `set_timezone(Key, TZ)` - Set the timezone to TZ for the key `Key`\n  + `set_timezone(TZ)` - Sets the timezone, and uses the Pid from `self()` as\n    the `Key`. Also links the process for removal from the record when the Pid\n    dies.\n  + `get_timezone(Key)` - Gets the timezone assigned to `Key`\n  + `get_timezone()` - Gets the timezone using `self()` as the `Key`\n  + `clear_timezone(Key)` - Removes the timezone record associated with `Key`.\n  + `clear_timezone()` - Removes the timezone record using `self()` as `Key`.\n    This function is not necessary for cleanup, most of the time, since if\n    `Key` is a Pid, the `qdate` server will automatically clean up when the\n    Pid dies.\n\n**Note:** If no timezone is set, then anything relying on the timezone will\ndefault to GMT.\n\n### Registering Custom Parsers and Formatters\n\nYou can register custom parsers and formatters with the `qdate` server. This\nallows you to specify application-wide aliases for certain common formatting\nstrings in your application, or to register custom parsing engines which will\nbe attempted before engaging the `ec_date` parser.\n\n### Registering and Deregistering Parsers\n  + `register_parser(Key, ParseFun)` - Registers a parsing function with the\n    `qdate` server. `ParseFun` is expected to have the arity of 1, and is\n    expected to return a DateTime format (`{{Year,Month,Day},{Hour,Min,Sec}}`)\n    or, if your ParseFun is capable of parsing out a Timezone, the return\n    the tuple `{DateTime, Timezone}`. Keep in mind, if your string already ends\n    with a Timezone, the parser will almost certainly extract the timezone\n    before it gets to your custom `ParseFun`. If your custom parser is not\n    able to parse the string, then it should return `undefined`.\n  + `deregister_parser(Key)` - If you previously registered a parser with the\n    `qdate` server, you can deregister it by its `Key`.\n  + `get_parsers()` - Get the list of all registered parsers and their keys.\n\n### Registering and Deregistering Formatters\n  + `register_format(Key, FormatString)` - Register a formatting string with\n    the `qdate` server, which can then be used in place of the typical\n    formatting string.\n  + `deregister_format(Key)` - Deregister the formatting string from the\n    `qdate` server.\n  + `get_formats()` - Get the list of all registered formats and their keys.\n\n### About backwards compatibility with `ec_date` and deterministic parsing\n\n`ec_date` and `dh_date` both have a quirk that bothers me with respect to the\nparsing of dates that causes some date parsing to be *non-deterministic*. That\nis, if parsing an incomplete date or time (ie, a text string that is missing a\ntime or a date), `ec_date` will automatically insert the current values of\nthose as read by the system clock.\n\nFor example, if the following lines are run a few seconds apart:\n\n```erlang\n1\u003e ec_date:parse(\"2012-02-04\").\n{{2012,2,4},{0,1,10}}\n2\u003e ec_date:parse(\"2012-02-04\").\n{{2012,2,4},{0,1,12}}\n3\u003e ec_date:parse(\"2012-02-04\").\n{{2012,2,4},{0,1,13}}\n```\n\nAs you can see, even though the inputs are the same each time, the resulting\nparsed dates have the current time inferred. The same behavior can be observed\nif parsing a time without a date:\n\n```erlang\n4\u003e ec_date:parse(\"7pm\").\n{{2013,4,30},{19,0,0}}\n```\n\nAs you can see, even though the time did not specify a date, the resulting\nparsed datetime has the date inferred from the current date. Admittedly,\ninferring the date bothers me less than inferring the time, but in the name of\nconsistency, there should be options for enabling or disabling both.\n\n#### The Solution For Non-deterministic parsing\n\nTo solve this issue for users that are bothered by this, while preserving\nbackwards compatibility for folks who prefer this, we're going to introduce a\n`qdate` application environment variable called `deterministic_parsing`.\n\nThe value of `deterministic_parsing` can be a tuple of the following format:\n\n`{DatePref, TimePref}`\n\nWhere `DatePref` and `TimePref` are either of the following atoms:\n\n  + `now` - Automatically fill in the missing date or time components with the\n    current time (the is the behavior described above)\n  + `zero` - Fill in the missing date or time components with zeroed out\n    values. This means that if a date is missing, it'll be set to the unix\n    epoch (`{1970,1,1}`) and if a time is missing, it'll be set to midnight:\n    `{0,0,0}`.\n\nSo, the acceptable combinations can be the following:\n\n  + `{zero, zero}` - Any missing components will be replaced with zero-values.\n    **(This is the qdate default behavior)**\n  + `{now, zero}` - If a date is missing, insert the current date, but if a\n    time is missing, set it to midnight.\n  + `{zero, now}` - If a date is missing, set it to the unix epoch, and if a\n    time is missing, set it to the current time of day.\n  + `{now, now}` - If either date or time are missing, set it to the current\n    date or current time.\n\n**Note:** If this application value is not set, the default behavior for\n`qdate` is to avoid non-determinism and use `{zero, zero}`.\n\nTo set this value, you can either set the value manually in code with:\n\n```erlang\napplication:set_env(qdate, deterministic_parsing, {now, zero}).\n```\n\nor (and this is the preferred method) use a config file and load it with\n\n`erl -config path/to/file.config`\n\nSample config file specifying this application variable:\n\n```erlang\n[{qdate, [\n    {deterministic_parsing, {now, zero}}\n]}].\n```\n\n## Demonstration\n\n### Basic Conversion and Formatting\n```erlang\n%% Let's start by making a standard Erlang DateTime tuple\n1\u003e Date = {{2013,12,21},{12,24,21}}.\n{{2013,12,21},{12,24,21}}\n\n%% Let's do a simple formatting of the date\n2\u003e DateString = qdate:to_string(\"Y-m-d h:ia\", Date).\n\"2013-12-21 12:24pm\"\n\n%% We can also specify the format string as a binary\n3\u003e DateBinary = qdate:to_string(\u003c\u003c\"Y-m-d h:ia\"\u003e\u003e,Date).\n\u003c\u003c\"2013-12-21 12:24pm\"\u003e\u003e\n\n%% And we can parse the original string to get back a DateTime object\n4\u003e qdate:to_date(DateString).\n{{2013,12,21},{12,24,0}}\n\n\n%% We can do the same with a binary\n5\u003e qdate:to_date(DateBinary).\n{{2013,12,21},{12,24,0}}\n\n%% We can also parse that date and get a Unix timestamp\n6\u003e DateUnix = qdate:to_unixtime(DateString).\n1387628640\n\n%% And we can take that Unix timestamp and format it to a string\n7\u003e qdate:to_string(\"n/j/Y g:ia\", DateUnix).\n\"12/21/2013 12:24pm\"\n\n%% We can take a date string and get an Erlang now() tuple\n8\u003e DateNow = qdate:to_now(DateString).\n{1387,628640,0}\n\n%% And we can convert it back\n\n9\u003e DateString2 = qdate:to_string(\"n/j/Y g:ia\", DateNow).\n\"12/21/2013 12:24pm\"\n```\n\n**Note:** That by this point, we've used, as the `Date` parameter, all natively\nsupported date formats: Erlang `datetime()`, Erlang `now()`, Unix timestamp,\nand formatted text strings either as a list or as a binary.\n\nFor the most part, this will be the bread and butter usage of `qdate`.  Easily\nconverting from one format to another without having to worry about what format\nyour data is currently in. `qdate` will figure it out for you.\n\n*But now, we're going to start getting interesting!*\n\n### Registering Custom Parsers\n\n```erlang\n%% Let's format our date into something shorter. This may, for example, be a\n%% date format you may deal with when receiving a data-set from a client.\n10\u003e CompactDate = qdate:to_string(\"Ymd\", DateNow).\n\"20131221\"\n\n%% Let's try to parse it\n11\u003e qdate:to_date(CompactDate).\n** exception throw: {ec_date,{bad_date,\"20131221\"}}\n     in function  ec_date:do_parse/3 (src/ec_date.erl, line 92)\n     in call from qdate:to_date/2 (src/qdate.erl, line 169)\n\n%% Well obviously, this isn't a standard format by any means, so it crashes.\n%% You can parse it yourself before passing it to `qdate` or if you deal with\n%% this format frequently enough, you can register it as a custom parser and\n%% qdate will intelligently parse it if it can.\n\n%% So let's make a simple parser for it that uses regular expressions:\n12\u003e ParseCompressedDate =\n12\u003e  fun(RawDate) when length(RawDate)==8 -\u003e\n12\u003e       try re:run(RawDate,\"^(\\\\d{4})(\\\\d{2})(\\\\d{2})$\",[{capture,all_but_first,list}]) of\n12\u003e         nomatch -\u003e undefined;\n12\u003e         {match, [Y,M,D]} -\u003e\n12\u003e           ParsedDate = {list_to_integer(Y), list_to_integer(M), list_to_integer(D)},\n12\u003e           case calendar:valid_date(ParsedDate) of\n12\u003e              true -\u003e {ParsedDate, {0,0,0}};\n12\u003e              false -\u003e undefined\n12\u003e           end\n12\u003e       catch _:_ -\u003e undefined\n12\u003e       end;\n12\u003e     (_) -\u003e undefined\n12\u003e  end.\n#Fun\u003cerl_eval.6.82930912\u003e\n\n%% And now we'll register the parser with the `qdate` server, giving it a \"Key\"\n%% of the atom 'compressed_date'\n13\u003e qdate:register_parser(compressed_date,ParseCompressedDate).\ncompressed_date\n\n%% Now, let's try parsing that again\n14\u003e qdate:to_date(CompactDate).\n{{2013,12,21},{0,0,0}}\n\n%% Huzzah! It worked. From here on out, `qdate`, will properly parse that kind\n%% of data if that format is passed, otherwise, it will merely skip over that\n%% parser and engage the standard parser in `ec_date`\n```\n\n**Note:** Currently, `qdate` expects custom parsers to not crash. If a custom\nparser crashes, an exception will be thrown. This is done in order to help you\ndebug your parsers. If a parser receives an unexpected input and crashes, the\nexception will be generated and you will be able to track down what input caused\nthe crash.\n\n**Another Note:** Custom parsers are expected to return either:\n  + A `datetime()` tuple. (ie {{2012,12,21},{14,45,23}}).\n  + An integer, which represents the Unix timestamp.\n  + The atom `undefined` if this parser is not a match for the supplied value\n\n#### Included Parser: Relative Times\n\n`qdate` ships with an optional relative time parser. To speed up performance\n(since this parser uses regular expressions), this parser is disabled by\ndefault. But if you wish to use it, make sure you call\n`qdate:register_parser(parse_relative, fun qdate:parse_relative/1)`. \n\nDoing this allows you to parse relative time strings of the following formats:\n\n  + \"1 hour ago\"\n  + \"-15 minutes\"\n  + \"in 45 days\"\n  + \"+2 years\"\n\nAnd doing so allows you to construct slightly more readable comparison calls\nfor sometimes common comparisons. For example, the following two calls are identical:\n\n```erlang\nqdate:between(\"-15 minutes\", Date, \"+15 minutes\").\n\nqdate:between(qdate:add_minutes(-15), Date, qdate:add_minutes(15)).\n```\n\n### Registering Custom Formats\n\n```erlang\n%% Let's format a date to a rather long string\n15\u003e qdate:to_string(\"l, F jS, Y g:i A T\",DateString).\n\"Saturday, December 12st, 2013 12:24 PM GMT\"\n\n%% Boy, that sure was a long string, I hope you can remember all those\n%% characters in that order!\n\n%% But, you don't have to: if that's a common format you use in your\n%% application, you can register your format with the `qdate` server, and then\n%% easiy refer to that format by its key.\n\n%% So let's take that format and register it\n16\u003e qdate:register_format(longdate, \"l, F jS, Y g:i A T\").\nok\n\n%% Now, let's try to format our string \n17\u003e LongDateString = qdate:to_string(longdate, DateString).\n\"Saturday, December 21st, 2013 12:24 PM GMT\"\n\n%% It was certainly easier to remember the atom 'longdate' than trying to\n%% remember the seemingly random \"l, F jS, Y g:i A T\".\n```\n\nAin't it nice, making things easier for you?\n\n### Timezone Demonstrations\n\nThe observant reader would have noticed something else. We used **timezones**\nin the last couple of calls. Indeed, not only can `qdate` deal with formatting\ntimezones, but it can also parse them, convert them, and set them for\nsimplified conversions.\n\nLet's see how we do this\n\n```erlang\n%% Let's take that last long date string (that was in GMT) and move it to\n%% Pacific time\n18\u003e LongDatePDT = qdate:to_string(longdate, \"PDT\", LongDateString).\n\"Saturday, December 21st, 2013 4:24 AM PST\"\n\n%% See something interesting there? Yeah, we told it it was PDT, but it output\n%% PST.  That's because PST is not in daylight saving time in December, and \n%% `qdate` was able to intelligently infer that, and fix it for us.\n\n%% Note, that when in doubt, `qdate` will *not* convert. For example, not all\n%% places in Eastern Standard Time do daylight saving time, and as such, EST\n%% will not necessarily convert to EDT.\n\n%% However, if you provide the timezone as something like \"America/New York\",\n%% it *will* figure that out, and do the correct conversion for you. \n\n%% Let's see how it handles unix times with strings that contain timezones.\n%% If you recall, LongDateString = \"Saturday, December 21st, 2013 12:24 PM GMT\"\n%% and LongDatePDT = \"Saturday, December 21st, 2013 4:24 AM PST\"\n19\u003e qdate:to_unixtime(LongDateString).\n1387628640\n\n%% Now let's try it with the Pacific Time one\n20\u003e qdate:to_unixtime(LongDatePDT).\n1387628640\n\n%% How exciting! `qdate` properly returned the same unix timestamp, since unix\n%% timestamps are timezone neutral. That is because unix timestamps are the\n%% number of seconds since midnight on 1970-01-01 GMT. As such, unix timestamps\n%% should not change, just because you're in a different timezone.\n\n%% Let's set the timezone for the current process to EST to test that previous\n%% assertion\n21\u003e qdate:set_timezone(\"EST\").\nok\n\n%% Now let's try converting those dates to unixtimes again\n22\u003e qdate:to_unixtime(LongDateString).\n1387628640\n23\u003e qdate:to_unixtime(LongDatePDT).\n1387628640\n\n%% Great! They didn't change, as we expected. The unix timestamps have remained\n%% Timezone neutral.\n\n%% Let's clear the current process's timezone (which basically means setting it\n%% to the application variable `default_timezone`, or, in this case, just\n%% resetting it to \"GMT\"\n24\u003e qdate:clear_timezone().\nok\n\n%% Now, let's imagine you run a website. The main site may have its own\n%% timezone, and the users each also have their own timezones.  So we'll\n%% register timezones for each the main site, and each user. That way, if we\n%% need to ensure that a date is presented in an appropriate timezone.\n\n\n%% Let's register some timezones by \"Timezone Keys\".  \n25\u003e qdate:set_timezone(my_site, \"America/Chicago\").\nok\n26\u003e qdate:set_timezone({user,1},\"Australia/Melbourne\").\nok\n\n%% So we'll get the date object of the previously set unix timestamp `DateUnix`\n27\u003e qdate:to_date(DateUnix).\n{{2013,12,21},{12,24,0}}\n\n%% And let's format it, also showing the timezone offset that was used\n28\u003e qdate:to_string(\"Y-m-d H:i P\", DateUnix).\n\"2013-12-21 12:24 +00:00\"\n\n%% Since we cleared the timezone for the current process, it just used \"GMT\"\n\n%% Let's get the date again, but this time, use to the Timezone key `my_site`\n29\u003e qdate:to_date(my_site, DateUnix).\n{{2013,12,21},{6,24,0}}\n\n%% And let's format it to show again the timezone offset\n30\u003e qdate:to_string(\"Y-m-d H:i P\", my_site, DateUnix).\n\"2013-12-21 06:24 -06:00\"\n\n%% Finally, let's get the date using the User's timezone key\n31\u003e qdate:to_date({user,1}, DateUnix).\n{{2013,12,21},{23,24,0}}\n\n%% And again, formatted to show the timezone offset\n32\u003e UserDateWithHourOffset = qdate:to_string(\"Y-m-d H:i P\", {user,1}, DateUnix).\n\"2013-12-21 23:24 +11:00\"\n\n%% And finally, let's just test some more parsing and converting. Here, despite\n%% the fact that the timezone is presented as \"+11:00\", `qdate` is able to\n%% do the proper conversion, and give us back the same unix timestamp that was\n%% used.\n33\u003e qdate:to_unixtime(UserDateWithHourOffset).\n1387628640\n```\n\n### One last bit of magic that may confuse you without an explanation\n\nMagic is usually bad, you know what's worse? Timezones and Daylight Saving\nTime. So we use a little magic to try and simplify them for us. Below is the\nextent of the confusion with related to inferring timezones and formatting dates\n\n```erlang\n%% First, let's set the timezone to something arbitrary\n34\u003e qdate:set_timezone(\"EST\").\nok\n\n%% Let's convert this date to basically the same time format, just without the\n%% timezone identifier.\n35\u003e qdate:to_string(\"Y-m-d H:i\",\"2012-12-21 00:00 PST\").\n\"2012-12-21 03:00\"\n\n%% WHAT?! We entered a date and time, and out came a different time?!\n%% I CALL SHENANIGANS!\n\n%% Let's add that timezone indicator back in with the conversion to see what\n%% happened:\n\n36\u003e qdate:to_string(\"Y-m-d H:i T\",\"2012-12-21 00:00 PST\").\n\"2012-12-21 03:00 EST\"\n\n%% OOOOOOOHHH! I see!\n%% Because we set our current timezone to EST, it took the original provided\n%% date in PST, and converted it to EST (since EST is the timezone we've chosen\n%% for the current process). So it's taking whatever date, and if it can\n%% determine a timezone, it'll extract that timezone, and convert the time from\n%% that timezone to our intended timezone.\n```\n\n## Beginning or Ending of time periods (hours, days, years, weeks, etc)\n\nqdate can determine beginnings and endings of time periods, like \"beginning of the month\"\n\nThis is abstracted to `beginning_X` functions, which return a date/time format\nwith the dates and times truncated to the specified level.\n\n   + `beginning_minute(Date)`\n   + `beginning_hour(Date)`\n   + `beginning_day(Date)`\n   + `beginning_month(Date)`\n   + `beginning_year(Date)`\n\nThere are also 0-arity versions of the above, in which `Date` is assumed to be\n\"right now\". For example, calling `qdate:beginning_month()` would return\nmidnight on the first day of the current month.\n\n#### Beginning of Week\n\nqdate can also do a special \"beginning\" case, particularly the \"beginning of\nthe week\" calculation.  This has three forms, specifically:\n  \n   + `beginning_week()` - Returns first day of the current week.\n   + `beginning_week(Date)` - Assumes the beginning of the week is Monday\n     (chosen because Erlang's calendar:day_of_the_week uses 1=Monday and\n     7=Sunday).\n   + `beginning_week(DayOfWeek, Date)` - Calculates the beginning of the week\n     based on the provided `DayOfWeek`. Valid values for DayOfWeek are the\n     integers 1-7 or the atom verions of the days of the week. Specifically:\n\t \n     * Monday: `1 | monday | mon`\n     * Tuesday: `2 | tuesday | tue`\n     * Wednesday: `3 | wednesday | wed`\n     * Thursday: `4 | thursday | thu`\n     * Friday: `5 | friday | fri`\n     * Saturday: `6 | saturday | sat`\n     * Sunday: `7 | sunday | sun`\n\nThese all return 12am on the day that is the first day of the week of the\nprovided date.\n\n(My apologies to non-English speakers. I'm a lazy American who only speaks\nEnglish, hence the Anglocentric day names).\n\n### End of time period\n\nThere are also the related `end_X` functions available, using the same\nconventions, except return the last second of that time period.\n\nSo `end_month(\"2016-01-05\")` will return the unix timestamp representing\n\"2016-01-31 11:59:59pm\"\n\n\n## Date Arithmetic\n\nThe current implementation of qdate's date arithmetic returns Unixtimes.\n\nThere are 8 main functions for date arithmetic:\n\n   + `add_seconds(Seconds, Date)`\n   + `add_minutes(Minutes, Date)`\n   + `add_hours(Hours, Date)`\n   + `add_days(Days, Date)`\n   + `add_weeks(Weeks, Date)`\n   + `add_months(Months, Date)`\n   + `add_years(Years, Date)`\n   + `add_date(DateToAdd, Date)` - `DateToAdd` is a shortcut way of adding\n      numerous options. For example. `qdate:add_date({{1, 2, -3}, {-500, 20, 0}})`\n      will add 1 year, add 2 months, subtract 3 days, subtract 500 hours, add 20\n      minutes, and not make any changes to seconds.\n\nFor the date arithmetic functions, `Date`, like all `qdate` functions, can be any\nformat.\n\n### Date Arithmetic from \"now\"\n\nThere 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())`.\n\n   + `add_seconds(Seconds)`\n   + `add_minutes(Minutes)`\n   + `add_hours(Hours)`\n   + `add_days(Days)`\n   + `add_weeks(Weeks)`\n   + `add_months(Months)`\n   + `add_years(Years)`\n\n## Date and Time Ranges\n\nqdate provides a number of `range` functions that give applicable dates/times\nwithin a start and end time. For example, \"All days from 2015-01-01 to today\",\n\"every 3rd month from 2000-01-01 to 2009-12-31\", or \"every 15 minutes from\nmidnight to 11:59pm on 2015-04-15\".\n\nThe functions are as follows:\n\n   + `range_seconds(Interval, Start, End)`\n   + `range_minutes(Interval, Start, End)`\n   + `range_hours(Interval, Start, End)`\n   + `range_days(Interval, Start, End)`\n   + `range_weeks(Interval, Start, End)`\n   + `range_months(Interval, Start, End)`\n   + `range_years(Interval, Start, End)`\n\nWhere `Interval` is the number of seconds/days/years/etc.\n\nSo for example:\n\n```erlang\n%% Get every 15th minute from \"2015-04-15 12:00am to 2015-04-15 11:59am\"\n\u003e qdate:range_minutes(15, \"2015-04-15 12:00am\", \"2015-04-15 11:59am\").\n[1429056000,1429056900,1429057800,1429058700,1429059600,\n 1429060500,1429061400,1429062300,1429063200,1429064100,\n 1429065000,1429065900,1429066800,1429067700,1429068600,\n 1429069500,1429070400,1429071300,1429072200,1429073100,\n 1429074000,1429074900,1429075800,1429076700,1429077600,\n 1429078500,1429079400,1429080300,1429081200|...]\n\n%% Get every day of April, 2014\n\u003e qdate:range_days(1, \"2014-04-01\", \"2014-04-30\").\n[1396310400,1396396800,1396483200,1396569600,1396656000,\n 1396742400,1396828800,1396915200,1397001600,1397088000,\n 1397174400,1397260800,1397347200,1397433600,1397520000,\n 1397606400,1397692800,1397779200,1397865600,1397952000,\n 1398038400,1398124800,1398211200,1398297600,1398384000,\n 1398470400,1398556800,1398643200,1398729600|...]\n```\n\nNote, that the return value (just like qdate's arithmetic functions) is a list\nof integers. These integers are unix timestamps and can be easily formatted\nwith qdate:\n\n```erlang\n\u003e Mins = qdate:range_minutes(15, \"2015-04-15 12:00am\", \"2015-04-15 11:59am\"),\n\u003e [qdate:to_string(\"Y-m-d h:ia\", M) || M \u003c- Mins].\n[\"2015-04-15 00:00am\",\"2015-04-15 00:15am\",\n \"2015-04-15 00:30am\",\"2015-04-15 00:45am\",\n \"2015-04-15 01:00am\",\"2015-04-15 01:15am\",\n \"2015-04-15 01:30am\",\"2015-04-15 01:45am\",\n \"2015-04-15 02:00am\",\"2015-04-15 02:15am\",\n \"2015-04-15 02:30am\",\"2015-04-15 02:45am\",\n \"2015-04-15 03:00am\",\"2015-04-15 03:15am\",\n \"2015-04-15 03:30am\",\"2015-04-15 03:45am\",\n \"2015-04-15 04:00am\",\"2015-04-15 04:15am\",\n \"2015-04-15 04:30am\",\"2015-04-15 04:45am\",\n \"2015-04-15 05:00am\",\"2015-04-15 05:15am\",\n \"2015-04-15 05:30am\",\"2015-04-15 05:45am\",\n \"2015-04-15 06:00am\",\"2015-04-15 06:15am\",\n \"2015-04-15 06:30am\",\"2015-04-15 06:45am\",\n [...]|...]\n```\n\nAlso note that the range functions are *inclusive*.\n\n## Configuration Sample\n\nThere is a sample configuration file can be found in the root of the qdate\ndirectory. Or you can just [look at it\nhere](https://github.com/choptastic/qdate/blob/master/qdate.config).\n\n## Thanks\n\nA few shoutouts to [Dale Harvey](http://github.com/daleharvey) and the\n[Erlware team](https://github.com/erlware) for `dh_date`/`ec_date`, and to\n[Dmitry Melnikov](https://github.com/dmitryme) for the `erlang_localtime`\npackage. Without the hard work of all involved in those projects, `qdate` would\nnot exist.\n\n### Thanks to Additional Contributors\n\n+ [Mark Allen](https://github.com/mrallen1)\n+ [Christopher Phillips](https://github.com/lostcolony)\n+ [Nicholas Lundgaard](https://github.com/nlundgaard-al)\n+ [Alejandro Ramallo](https://github.com/aramallo)\n+ [Heinz Gies](https://github.com/Licenser)\n\n\n## Changelog\n\nSee [CHANGELOG.markdown](https://github.com/choptastic/qdate/blob/master/CHANGELOG.markdown)\n\n## TODO\n\n+ Make `qdate` backend-agnostic (allow specifying either ec_date or dh_date as\n  the backend)\n+ Add `-spec` and `-type` info for dialyzer\n+ Research the viability of [ezic](https://github.com/drfloob/ezic) for a\n  timezone backend replacement for `erlang_localtime`.\n+ Add age calculation stuff: `age_years(Date)`, `age_minutes(Date)`, etc.\n\n## Conclusion\n\nI hope you find `qdate` helpful in all your endeavors and it helps make your\nwildest dreams come true!\n\nIf you have any bugs, feature requests, or whatnot, feel free to post a Github\nissue, ping me on Twitter, or email me below.\n\nI'm open to pull requests. Feel free to get your hands dirty!\n\nAuthor: [Jesse Gumm](http://sigma-star.com/page/jesse)\n\nEmail: gumm@sigma-star.com\n\nTwitter: [@jessegumm](http://twitter.com/jessegumm)\n\nReleased under the MIT License (see LICENSE file)\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2F5ht%2Fqdate","html_url":"https://awesome.ecosyste.ms/projects/github.com%2F5ht%2Fqdate","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2F5ht%2Fqdate/lists"}