{"id":21867800,"url":"https://github.com/taxjar/date_time_parser","last_synced_at":"2025-04-14T22:25:03.195Z","repository":{"id":35035115,"uuid":"199029416","full_name":"taxjar/date_time_parser","owner":"taxjar","description":"Parse strings into DateTime, NaiveDateTime, Date, or Time  https://hexdocs.pm/date_time_parser","archived":false,"fork":false,"pushed_at":"2022-10-05T20:27:28.000Z","size":558,"stargazers_count":80,"open_issues_count":1,"forks_count":9,"subscribers_count":11,"default_branch":"master","last_synced_at":"2025-03-28T10:21:54.943Z","etag":null,"topics":["datetime","elixir","hacktoberfest","parser"],"latest_commit_sha":null,"homepage":"","language":"Elixir","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/taxjar.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE.md","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-07-26T14:19:27.000Z","updated_at":"2023-12-31T13:28:23.000Z","dependencies_parsed_at":"2022-08-08T18:30:15.768Z","dependency_job_id":null,"html_url":"https://github.com/taxjar/date_time_parser","commit_stats":null,"previous_names":[],"tags_count":14,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/taxjar%2Fdate_time_parser","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/taxjar%2Fdate_time_parser/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/taxjar%2Fdate_time_parser/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/taxjar%2Fdate_time_parser/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/taxjar","download_url":"https://codeload.github.com/taxjar/date_time_parser/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248970225,"owners_count":21191394,"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":["datetime","elixir","hacktoberfest","parser"],"created_at":"2024-11-28T05:10:34.171Z","updated_at":"2025-04-14T22:25:03.169Z","avatar_url":"https://github.com/taxjar.png","language":"Elixir","funding_links":[],"categories":[],"sub_categories":[],"readme":"**NOTICE**: This GitHub repo will be archived. Development and maintenance will\ncontinue at https://github.com/dbernheisel/date_time_parser. The hex.pm package\nwill continue receiving updates and will publish from the new repo. [See this\nissue for more details](https://github.com/taxjar/date_time_parser/issues/50).\n\n---\n\n# DateTimeParser\n\n[![Hex.pm Version](http://img.shields.io/hexpm/v/date_time_parser.svg)](https://hex.pm/packages/date_time_parser)\n[![Hex docs](http://img.shields.io/badge/hex.pm-docs-blue.svg?style=flat)](https://hexdocs.pm/date_time_parser)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE.md)\n[![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-v1.4%20adopted-ff69b4.svg)](./CODE_OF_CONDUCT.md)\n\nDateTimeParser is a tokenizer for strings that attempts to parse into a\nDateTime, NaiveDateTime if timezone is not determined, Date, or Time.\n\nYou're currently looking at the master branch. [Check out the docs for the latest\npublished version.](https://hexdocs.pm/date_time_parser)\n\n## Documentation\n\n[See examples automatically generated by the tests](./EXAMPLES.md)\n\n\u003c!-- MDOC --\u003e\n\nThe biggest ambiguity between datetime formats is whether it's `ymd` (year month\nday), `mdy` (month day year), or `dmy` (day month year); this is resolved by\nchecking if there are slashes or dashes. If slashes, then it will try `dmy`\nfirst. All other cases will use the international format `ymd`. Sometimes, if\nthe conditions are right, it can even parse `dmy` with dashes if the month is a\nvocal month (eg, `\"Jan\"`).\n\nIf the string consists of only numbers, then we will try two other parsers\ndepending on the number of digits: [Epoch] or [Serial]. Otherwise, we'll try the\ntokenizer.\n\nIf the string is 10-11 digits with optional precision, then we'll try to parse\nit as a Unix [Epoch] timestamp.\n\nIf the string is 1-5 digits with optional precision, then we'll try to parse it\nas a [Serial] timestamp (spreadsheet time) treating 1899-12-31 as 1. This will\ncause Excel-produced dates from 1900-01-01 until 1900-03-01 to be incorrect, as\nthey really are.\n\n|digits|parser|range|notes|\n|---|----|---|---|\n|1-5|Serial|low = `1900-01-01`, high = `2173-10-15`. Negative numbers go to `1626-03-17`|Floats indicate time. Integers do not.|\n|6-9|Tokenizer|any|This allows for \"20190429\" to be parsed as `2019-04-29`|\n|10-11|Epoch|low = `-1100-02-15 14:13:21`, high = `5138-11-16 09:46:39`|If padded with 0s, then it can capture entire range.|\n|else|Tokenizer|any| |\n\n[Epoch]: https://en.wikipedia.org/wiki/Unix_time\n[Serial]: https://support.office.com/en-us/article/date-systems-in-excel-e7fe7167-48a9-4b96-bb53-5612a800b487\n\n## Required reading\n\n* [Elixir DateTime docs](https://hexdocs.pm/elixir/DateTime.html)\n* [Elixir NaiveDateTime docs](https://hexdocs.pm/elixir/NaiveDateTime.html)\n* [Elixir Date docs](https://hexdocs.pm/elixir/Date.html)\n* [Elixir Time docs](https://hexdocs.pm/elixir/Time.html)\n* [Elixir Calendar docs](https://hexdocs.pm/elixir/Calendar.html)\n\n## Examples\n\n```elixir\niex\u003e DateTimeParser.parse(\"19 September 2018 08:15:22 AM\")\n{:ok, ~N[2018-09-19 08:15:22]}\n\niex\u003e DateTimeParser.parse_datetime(\"19 September 2018 08:15:22 AM\")\n{:ok, ~N[2018-09-19 08:15:22]}\n\niex\u003e DateTimeParser.parse_datetime(\"2034-01-13\", assume_time: true)\n{:ok, ~N[2034-01-13 00:00:00]}\n\niex\u003e DateTimeParser.parse_datetime(\"2034-01-13\", assume_time: ~T[06:00:00])\n{:ok, ~N[2034-01-13 06:00:00]}\n\niex\u003e DateTimeParser.parse(\"invalid date 10:30pm\")\n{:ok, ~T[22:30:00]}\n\niex\u003e DateTimeParser.parse(\"2019-03-11T99:99:99\")\n{:ok, ~D[2019-03-11]}\n\niex\u003e DateTimeParser.parse(\"2019-03-11T10:30:00pm UNK\")\n{:ok, ~N[2019-03-11T22:30:00]}\n\niex\u003e DateTimeParser.parse(\"2019-03-11T22:30:00.234+00:00\")\n{:ok, DateTime.from_naive!(~N[2019-03-11T22:30:00.234Z], \"Etc/UTC\")}\n# `~U[2019-03-11T22:30:00.234Z]` in Elixir 1.9+\n\niex\u003e DateTimeParser.parse_date(\"2034-01-13\")\n{:ok, ~D[2034-01-13]}\n\niex\u003e DateTimeParser.parse_date(\"01/01/2017\")\n{:ok, ~D[2017-01-01]}\n\niex\u003e DateTimeParser.parse_datetime(\"1564154204\")\n{:ok, DateTime.from_naive!(~N[2019-07-26T15:16:44Z], \"Etc/UTC\")}\n# `~U[2019-07-26T15:16:44Z]` in Elixir 1.9+\n\niex\u003e DateTimeParser.parse_datetime(\"41261.6013888889\")\n{:ok, ~N[2012-12-18T14:26:00]}\n\niex\u003e DateTimeParser.parse_date(\"44262\")\n{:ok, ~D[2021-03-07]}\n# This is a serial number date, commonly found in spreadsheets, eg: `=VALUE(\"03/07/2021\")`\n\niex\u003e DateTimeParser.parse_datetime(\"1/1/18 3:24 PM\")\n{:ok, ~N[2018-01-01T15:24:00]}\n\niex\u003e DateTimeParser.parse_datetime(\"1/1/18 3:24 PM\", assume_utc: true)\n{:ok, DateTime.from_naive!(~N[2018-01-01T15:24:00Z], \"Etc/UTC\")}\n# `~U[2018-01-01T15:24:00Z]` in Elixir 1.9+\n\niex\u003e DateTimeParser.parse_datetime(~s|\"Mar 28, 2018 7:39:53 AM PDT\"|, to_utc: true)\n{:ok, DateTime.from_naive!(~N[2018-03-28T14:39:53Z], \"Etc/UTC\")}\n\niex\u003e {:ok, datetime} = DateTimeParser.parse_datetime(~s|\"Mar 1, 2018 7:39:53 AM PST\"|)\niex\u003e datetime\n#DateTime\u003c2018-03-01 07:39:53-08:00 PST PST8PDT\u003e\n\niex\u003e DateTimeParser.parse_datetime(~s|\"Mar 1, 2018 7:39:53 AM PST\"|, to_utc: true)\n{:ok, DateTime.from_naive!(~N[2018-03-01T15:39:53Z], \"Etc/UTC\")}\n\niex\u003e {:ok, datetime} = DateTimeParser.parse_datetime(~s|\"Mar 28, 2018 7:39:53 AM PDT\"|)\niex\u003e datetime\n#DateTime\u003c2018-03-28 07:39:53-07:00 PDT PST8PDT\u003e\n\niex\u003e DateTimeParser.parse_time(\"10:13pm\")\n{:ok, ~T[22:13:00]}\n\niex\u003e DateTimeParser.parse_time(\"10:13:34\")\n{:ok, ~T[10:13:34]}\n\niex\u003e DateTimeParser.parse_time(\"18:14:21.145851000000Z\")\n{:ok, ~T[18:14:21.145851]}\n\niex\u003e DateTimeParser.parse_datetime(nil)\n{:error, \"Could not parse nil\"}\n```\n\n## Installation\n\nAdd `date_time_parser` to your list of dependencies in `mix.exs`:\n\n```elixir\ndef deps do\n  [\n    {:date_time_parser, \"~\u003e 1.1.2\"}\n  ]\nend\n```\n\n## Configuration\n\n```elixir\n# This is the default config\nalias DateTimeParser.Parser\nconfig :date_time_parser, parsers: [Parser.Epoch, Parser.Serial, Parser.Tokenizer]\n\n# To enable only specific parsers, include them in the :parsers key.\nconfig :date_time_parser, parsers: [Parser.Tokenizer]\n\n# Or in runtime, pass in the parsers in the function.\nDateTimeParser.parse(mystring, parsers: [Parser.Tokenizer])\n```\n\n## Write your own parser\n\nYou can write your own parser!\n\nIf the built-in parsers are not applicable for your use-case, you may build your\nown parser to use with this library. Let's write a simple one together.\n\nFirst I will check `DateTimeParser.Parser` to see what behaviour my new parser\nshould implement. It needs two functions:\n\n1. `c:DateTimeParser.Parser.preflight/1`\n1. `c:DateTimeParser.Parser.parse/1`\n\nThese functions accept the `t:DateTimeParser.Parser.t/0` struct which contains the\noptions supplied by the user, the string itself, and the context for which you\nshould return your result. For example, if the context is `:time` then you should\nreturn a `%Time{}`; if `:datetime` you should return either a\n`%NaiveDateTime{}` or a `%DateTime{}`; if `:date` then you should return a\n`%Date{}`.\n\nLet's implement a parser that reads a special time string. Our string will\nrepresent time, but all the digits are shifted up by 10 and must be prefixed\nwith the secret word: `\"boomshakalaka:\"`. For example, the real world time of\n`01:10` is represented as `boomshakalaka:11:20` in our toy time format. `12:30`\nis represented as `boomshakalaka:22:40`, and `5:55` is represented as\n`boomshakalaka:15:65`.\n\n```elixir\ndefmodule MyParser do\n  @behaviour DateTimeParser.Parser\n  @secret_regex ~r|boomshakalaka:(?\u003ctime\u003e\\d{2}:\\d{2})|\n\n  def preflight(%{string: string} = parser) do\n    case Regex.named_captures(@secret_regex, string) do\n      %{\"time\" =\u003e time} -\u003e\n        {:ok, %{parser | preflight: time}}\n\n      nil -\u003e\n        {:error, :not_compatible}\n    end\n  end\n\n  # ... more below\nend\n```\n\nWe'll stop here first and go through the preflight function. Our special parser\nwill only be attempted if the supplied string has any named captures from the\nregex. That is, it must begin with `bookshakalaka:` followed by 2 digits, a\ncolon, and 2 more digits. These digits are extracted out like `00:00` where 0 is\nany digit. If `05:40` is passed in, it would not be compatible so the parser\nwill be skipped.\n\nNow let's parse the time:\n\n```elixir\ndef parse(%{preflight: time} = parser) do\n  [hour, minute] = String.split(time, \":\")\n  {hour, \"\"} = Integer.parse(hour)\n  {minute, \"\"} = Integer.parse(minute)\n  result = Time.new(hour - 10, minute - 10, 0, {0, 0})\n  for_context(parser.context, result)\nend\n\ndefp for_context(:datetime, _result), do: :error\ndefp for_context(:date, _result), do: :error\ndefp for_context(:time, result), do: result\n```\n\nNotice that we need to consider context of the result. If the user asked for a\nDateTime, then we need to give them one. In our toy format, it only represents\ntime, so therefore we must return an error when the context is a `:datetime` or\n`:date`.\n\n```elixir\nDateTimeParser.parse_time(\"boomshakalaka:11:11\", parsers: [MyParser])\n#=\u003e {:ok, ~T[01:01:00]}\n\nDateTimeParser.parse_date(\"boomshakalaka:11:11\", parsers: [MyParser])\n#=\u003e {:error, \"Could not parse \\\"boomshakalaka:11:11\\\"\"}\n\nDateTimeParser.parse_datetime(\"boomshakalaka:11:11\", parsers: [MyParser])\n#=\u003e {:error, \"Could not parse \\\"boomshakalaka:11:11\\\"\"}\n\nDateTimeParser.parse(\"boomshakalaka:11:11\", parsers: [MyParser])\n#=\u003e {:ok, ~T[01:01:00]}\n\n```\n\n## Should I use this library?\n\nOnly as a last resort. Parsing dates from strings is educated guessing at best.\nSince Elixir natively supports [ISO-8601] parsing (see `from_iso8601/2`\nfunctions), it's highly recommended that you rely on that first and foremost.\n\nWhen designing your API that involves dates and strings, be specific with your\nrequirements and supported DateTime strings, and preferably only support\n[ISO-8601] with no exceptions. There is _no_ ambiguity with this format so\nparsing to DateTime (or Date or Time) will always be correct.\n\nThis library is helpful when you _must_ accept ambiguous DateTime string formats\nand having incorrect results is acceptable. Do not use this library when the\nresulting (and possibly incorrect) DateTime has catastrophic and dangerous\neffects in your system.\n\n[ISO-8601]: https://en.wikipedia.org/wiki/ISO_8601\n\n\u003c!-- MDOC --\u003e\n\n## How to store future timestamps\n\n[see guide](./pages/Future-UTC-DateTime.md)\n\ntldr: rules change, so don't convert to UTC too early. The future might change\nthe timezone conversion rules.\n\n## Changelog\n\n[View Changelog](./CHANGELOG.md)\n\n## Upgrading from 0.x to 1.0\n\n* If you use `parse_datetime/1`, then change to `parse_datetime/2` with the\n  second argument as a keyword list to `assume_time: true` and `to_utc: true`.\n  In 0.x, it would merge `~T[00:00:00]` if the time tokens could not be parsed;\n  in 1.x, you have to opt into this behavior. Also in 0.x, a non-UTC timezone\n  would automatically convert to UTC; in 1.x, the original timezone will be\n  kept instead.\n* If you use `parse_date/1`, then change to `parse_date/2` with the second\n  argument as a keyword list to `assume_date: true`. In 0.x, it would merge\n  `Date.utc_today()` with the found date tokens; in 1.x, you need to opt into\n  this behavior.\n* If you use `parse_time`, there is no breaking change but parsing has been\n  improved.\n* Not a breaking change, but 1.x introduces `parse/2` that will return the best\n  struct from the tokens. This may influence your usage.\n\n## Contributing\n\n[How to contribute](./CONTRIBUTING.md)\n\n## Special Thanks\n\n[\u003cimg src=\"https://github.com/taxjar/date_time_parser/blob/master/images/taxjar_sponsor.jpg?raw=true\" alt=\"TaxJar\" height=75 /\u003e](https://www.taxjar.com)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftaxjar%2Fdate_time_parser","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftaxjar%2Fdate_time_parser","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftaxjar%2Fdate_time_parser/lists"}