{"action":"created","comment":{"url":"https://api.github.com/repos/rails/rails/pulls/comments/25768329","id":25768329,"diff_hunk":"@@ -0,0 +1,61 @@\n+# Parses a string formatted according to ISO 8601 Duration into the hash .\n+#\n+# See http://en.wikipedia.org/wiki/ISO_8601#Durations\n+# Parts of code are taken from ISO8601 gem by Arnau Siches (@arnau).\n+# This parser isn't so strict and allows negative parts to be present in pattern.\n+class ActiveSupport::Duration::ISO8601DurationParser\n+  attr_reader :parts\n+\n+  class ParsingError < ::StandardError; end\n+\n+  def initialize(iso8601duration)\n+    @raw = iso8601duration\n+    parse_iso_duration!\n+    construct_parts\n+    validate_parts!\n+  end\n+\n+  private\n+\n+  def parse_iso_duration!\n+    @match = @raw.match(/^\n+                (?<sign>\\+|-)?\n+                P(?:\n+                  (?:\n+                    (?:(?<years>-?\\d+(?:[,.]\\d+)?)Y)?\n+                    (?:(?<months>-?\\d+(?:[.,]\\d+)?)M)?\n+                    (?:(?<days>-?\\d+(?:[.,]\\d+)?)D)?\n+                    (?<time>T\n+                      (?:(?<hours>-?\\d+(?:[.,]\\d+)?)H)?\n+                      (?:(?<minutes>-?\\d+(?:[.,]\\d+)?)M)?\n+                      (?:(?<seconds>-?\\d+(?:[.,]\\d+)?)S)?\n+                    )?\n+                  ) |\n+                  (?<weeks>-?\\d+(?:[.,]\\d+)?W)\n+                ) # Duration\n+              $/x) || raise(ParsingError.new(\"Invalid ISO 8601 duration: #{@raw}\"))\n+  end\n+\n+  # Constructs parts compatible with +ActiveSupport::Duration+ ones.\n+  def construct_parts\n+    sign = @match[:sign] == '-' ? -1 : 1\n+    @parts = @match.names.zip(@match.captures).reject { |_k,v| v.nil? }.map do |k, v|\n+      value = /\\d+[\\.,]\\d+/ =~ v ? v.sub(',', '.').to_f : v.to_i\n+      [ k.to_sym, sign * value ]\n+    end\n+    @parts = ::Hash[@parts].slice(:years, :months, :weeks, :days, :hours, :minutes, :seconds)\n+  end","path":"activesupport/lib/active_support/duration/iso8601_duration_parser.rb","position":47,"original_position":47,"commit_id":"9f12c3768639e5db8eda93286a2e0ab668c51350","original_commit_id":"9f12c3768639e5db8eda93286a2e0ab668c51350","user":{"login":"kaspth","id":350807,"avatar_url":"https://avatars.githubusercontent.com/u/350807?v=3","gravatar_id":"","url":"https://api.github.com/users/kaspth","html_url":"https://github.com/kaspth","followers_url":"https://api.github.com/users/kaspth/followers","following_url":"https://api.github.com/users/kaspth/following{/other_user}","gists_url":"https://api.github.com/users/kaspth/gists{/gist_id}","starred_url":"https://api.github.com/users/kaspth/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/kaspth/subscriptions","organizations_url":"https://api.github.com/users/kaspth/orgs","repos_url":"https://api.github.com/users/kaspth/repos","events_url":"https://api.github.com/users/kaspth/events{/privacy}","received_events_url":"https://api.github.com/users/kaspth/received_events","type":"User","site_admin":false},"body":"Won't something like this work?\r\n\r\n```ruby\r\ndef construct_parts\r\n  sign = @match[:sign] == '-' ? -1 : 1\r\n\r\n  @parts = @match.names.zip(@match.captures).each_with_object({}) do |key, capture, parts|\r\n    if capture\r\n      part = capture =~ /\\d+[\\.,]\\d+/ ? capture.sub(',', '.').to_f : capture.to_i\r\n\r\n      parts[key.to_sym] = sign * part\r\n    end\r\n  end\r\nend\r\n```","created_at":"2015-03-04T11:35:34Z","updated_at":"2015-03-04T11:35:34Z","html_url":"https://github.com/rails/rails/pull/16917#discussion_r25768329","pull_request_url":"https://api.github.com/repos/rails/rails/pulls/16917","_links":{"self":{"href":"https://api.github.com/repos/rails/rails/pulls/comments/25768329"},"html":{"href":"https://github.com/rails/rails/pull/16917#discussion_r25768329"},"pull_request":{"href":"https://api.github.com/repos/rails/rails/pulls/16917"}}},"pull_request":{"url":"https://api.github.com/repos/rails/rails/pulls/16917","id":21204165,"html_url":"https://github.com/rails/rails/pull/16917","diff_url":"https://github.com/rails/rails/pull/16917.diff","patch_url":"https://github.com/rails/rails/pull/16917.patch","issue_url":"https://api.github.com/repos/rails/rails/issues/16917","number":16917,"state":"open","locked":false,"title":"Methods for ActiveSupport::Duration parsing from ISO 8601 and output to it","user":{"login":"Envek","id":264400,"avatar_url":"https://avatars.githubusercontent.com/u/264400?v=3","gravatar_id":"","url":"https://api.github.com/users/Envek","html_url":"https://github.com/Envek","followers_url":"https://api.github.com/users/Envek/followers","following_url":"https://api.github.com/users/Envek/following{/other_user}","gists_url":"https://api.github.com/users/Envek/gists{/gist_id}","starred_url":"https://api.github.com/users/Envek/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Envek/subscriptions","organizations_url":"https://api.github.com/users/Envek/orgs","repos_url":"https://api.github.com/users/Envek/repos","events_url":"https://api.github.com/users/Envek/events{/privacy}","received_events_url":"https://api.github.com/users/Envek/received_events","type":"User","site_admin":false},"body":"This PR add methods to `ActiveSupport::Duration` to allow present it in [ISO 8601 Duration] format and instantiate from it.\r\n\r\n[ISO 8601 Duration] format is standartized way to represent duration for interchange, it's already recognized by some database engines (e.g. PostgreSQL) and client side libraries (e.g. [plugin for Moment.js durations](https://github.com/dordille/moment-isoduration)). So, I think it should be part of `ActiveSupport::Duration`.\r\n\r\nSome parts of code and tests are taken from [ISO8601 gem] by Arnau Siches (@arnau) and contributors. Many thanks to them.\r\n\r\nThis PR is required for PostgreSQL `interval` datatype support (for converting it from and to `ActiveSupport::Duration`) as I've proposed in Google Group [here](https://groups.google.com/forum/#!topic/rubyonrails-core/zcscu1p4ucA). See #16919. Because of that my parsing allows individual datetime parts to be negative (see [PostgreSQL interval output]).\r\n\r\nThere is no backward incompatible changes as I want it to be included in upcoming 4.2 release, if it still possible (pleeeaasee!).\r\n\r\n[ISO 8601 Duration]: http://en.wikipedia.org/wiki/ISO_8601#Durations\r\n[ISO8601 gem]: https://github.com/arnau/ISO8601\r\n[PostgreSQL interval output]: http://www.postgresql.org/docs/9.4/static/datatype-datetime.html#DATATYPE-INTERVAL-OUTPUT","created_at":"2014-09-14T13:54:05Z","updated_at":"2015-03-04T11:35:34Z","closed_at":null,"merged_at":null,"merge_commit_sha":"3a7b2ad9c5da723a413eb76c5eace96cbbd74c7a","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/rails/rails/pulls/16917/commits","review_comments_url":"https://api.github.com/repos/rails/rails/pulls/16917/comments","review_comment_url":"https://api.github.com/repos/rails/rails/pulls/comments{/number}","comments_url":"https://api.github.com/repos/rails/rails/issues/16917/comments","statuses_url":"https://api.github.com/repos/rails/rails/statuses/9f12c3768639e5db8eda93286a2e0ab668c51350","head":{"label":"Envek:iso8601_duration","ref":"iso8601_duration","sha":"9f12c3768639e5db8eda93286a2e0ab668c51350","user":{"login":"Envek","id":264400,"avatar_url":"https://avatars.githubusercontent.com/u/264400?v=3","gravatar_id":"","url":"https://api.github.com/users/Envek","html_url":"https://github.com/Envek","followers_url":"https://api.github.com/users/Envek/followers","following_url":"https://api.github.com/users/Envek/following{/other_user}","gists_url":"https://api.github.com/users/Envek/gists{/gist_id}","starred_url":"https://api.github.com/users/Envek/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Envek/subscriptions","organizations_url":"https://api.github.com/users/Envek/orgs","repos_url":"https://api.github.com/users/Envek/repos","events_url":"https://api.github.com/users/Envek/events{/privacy}","received_events_url":"https://api.github.com/users/Envek/received_events","type":"User","site_admin":false},"repo":{"id":21684544,"name":"rails","full_name":"Envek/rails","owner":{"login":"Envek","id":264400,"avatar_url":"https://avatars.githubusercontent.com/u/264400?v=3","gravatar_id":"","url":"https://api.github.com/users/Envek","html_url":"https://github.com/Envek","followers_url":"https://api.github.com/users/Envek/followers","following_url":"https://api.github.com/users/Envek/following{/other_user}","gists_url":"https://api.github.com/users/Envek/gists{/gist_id}","starred_url":"https://api.github.com/users/Envek/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Envek/subscriptions","organizations_url":"https://api.github.com/users/Envek/orgs","repos_url":"https://api.github.com/users/Envek/repos","events_url":"https://api.github.com/users/Envek/events{/privacy}","received_events_url":"https://api.github.com/users/Envek/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/Envek/rails","description":"Ruby on Rails","fork":true,"url":"https://api.github.com/repos/Envek/rails","forks_url":"https://api.github.com/repos/Envek/rails/forks","keys_url":"https://api.github.com/repos/Envek/rails/keys{/key_id}","collaborators_url":"https://api.github.com/repos/Envek/rails/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/Envek/rails/teams","hooks_url":"https://api.github.com/repos/Envek/rails/hooks","issue_events_url":"https://api.github.com/repos/Envek/rails/issues/events{/number}","events_url":"https://api.github.com/repos/Envek/rails/events","assignees_url":"https://api.github.com/repos/Envek/rails/assignees{/user}","branches_url":"https://api.github.com/repos/Envek/rails/branches{/branch}","tags_url":"https://api.github.com/repos/Envek/rails/tags","blobs_url":"https://api.github.com/repos/Envek/rails/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/Envek/rails/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/Envek/rails/git/refs{/sha}","trees_url":"https://api.github.com/repos/Envek/rails/git/trees{/sha}","statuses_url":"https://api.github.com/repos/Envek/rails/statuses/{sha}","languages_url":"https://api.github.com/repos/Envek/rails/languages","stargazers_url":"https://api.github.com/repos/Envek/rails/stargazers","contributors_url":"https://api.github.com/repos/Envek/rails/contributors","subscribers_url":"https://api.github.com/repos/Envek/rails/subscribers","subscription_url":"https://api.github.com/repos/Envek/rails/subscription","commits_url":"https://api.github.com/repos/Envek/rails/commits{/sha}","git_commits_url":"https://api.github.com/repos/Envek/rails/git/commits{/sha}","comments_url":"https://api.github.com/repos/Envek/rails/comments{/number}","issue_comment_url":"https://api.github.com/repos/Envek/rails/issues/comments{/number}","contents_url":"https://api.github.com/repos/Envek/rails/contents/{+path}","compare_url":"https://api.github.com/repos/Envek/rails/compare/{base}...{head}","merges_url":"https://api.github.com/repos/Envek/rails/merges","archive_url":"https://api.github.com/repos/Envek/rails/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/Envek/rails/downloads","issues_url":"https://api.github.com/repos/Envek/rails/issues{/number}","pulls_url":"https://api.github.com/repos/Envek/rails/pulls{/number}","milestones_url":"https://api.github.com/repos/Envek/rails/milestones{/number}","notifications_url":"https://api.github.com/repos/Envek/rails/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/Envek/rails/labels{/name}","releases_url":"https://api.github.com/repos/Envek/rails/releases{/id}","created_at":"2014-07-10T07:34:40Z","updated_at":"2015-02-08T16:44:40Z","pushed_at":"2015-02-25T15:32:25Z","git_url":"git://github.com/Envek/rails.git","ssh_url":"git@github.com:Envek/rails.git","clone_url":"https://github.com/Envek/rails.git","svn_url":"https://github.com/Envek/rails","homepage":"http://rubyonrails.org","size":127507,"stargazers_count":0,"watchers_count":0,"language":"Ruby","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"rails:master","ref":"master","sha":"2abed7af6e4b87da7277952e9ad73681ac337f2e","user":{"login":"rails","id":4223,"avatar_url":"https://avatars.githubusercontent.com/u/4223?v=3","gravatar_id":"","url":"https://api.github.com/users/rails","html_url":"https://github.com/rails","followers_url":"https://api.github.com/users/rails/followers","following_url":"https://api.github.com/users/rails/following{/other_user}","gists_url":"https://api.github.com/users/rails/gists{/gist_id}","starred_url":"https://api.github.com/users/rails/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/rails/subscriptions","organizations_url":"https://api.github.com/users/rails/orgs","repos_url":"https://api.github.com/users/rails/repos","events_url":"https://api.github.com/users/rails/events{/privacy}","received_events_url":"https://api.github.com/users/rails/received_events","type":"Organization","site_admin":false},"repo":{"id":8514,"name":"rails","full_name":"rails/rails","owner":{"login":"rails","id":4223,"avatar_url":"https://avatars.githubusercontent.com/u/4223?v=3","gravatar_id":"","url":"https://api.github.com/users/rails","html_url":"https://github.com/rails","followers_url":"https://api.github.com/users/rails/followers","following_url":"https://api.github.com/users/rails/following{/other_user}","gists_url":"https://api.github.com/users/rails/gists{/gist_id}","starred_url":"https://api.github.com/users/rails/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/rails/subscriptions","organizations_url":"https://api.github.com/users/rails/orgs","repos_url":"https://api.github.com/users/rails/repos","events_url":"https://api.github.com/users/rails/events{/privacy}","received_events_url":"https://api.github.com/users/rails/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/rails/rails","description":"Ruby on Rails","fork":false,"url":"https://api.github.com/repos/rails/rails","forks_url":"https://api.github.com/repos/rails/rails/forks","keys_url":"https://api.github.com/repos/rails/rails/keys{/key_id}","collaborators_url":"https://api.github.com/repos/rails/rails/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/rails/rails/teams","hooks_url":"https://api.github.com/repos/rails/rails/hooks","issue_events_url":"https://api.github.com/repos/rails/rails/issues/events{/number}","events_url":"https://api.github.com/repos/rails/rails/events","assignees_url":"https://api.github.com/repos/rails/rails/assignees{/user}","branches_url":"https://api.github.com/repos/rails/rails/branches{/branch}","tags_url":"https://api.github.com/repos/rails/rails/tags","blobs_url":"https://api.github.com/repos/rails/rails/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/rails/rails/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/rails/rails/git/refs{/sha}","trees_url":"https://api.github.com/repos/rails/rails/git/trees{/sha}","statuses_url":"https://api.github.com/repos/rails/rails/statuses/{sha}","languages_url":"https://api.github.com/repos/rails/rails/languages","stargazers_url":"https://api.github.com/repos/rails/rails/stargazers","contributors_url":"https://api.github.com/repos/rails/rails/contributors","subscribers_url":"https://api.github.com/repos/rails/rails/subscribers","subscription_url":"https://api.github.com/repos/rails/rails/subscription","commits_url":"https://api.github.com/repos/rails/rails/commits{/sha}","git_commits_url":"https://api.github.com/repos/rails/rails/git/commits{/sha}","comments_url":"https://api.github.com/repos/rails/rails/comments{/number}","issue_comment_url":"https://api.github.com/repos/rails/rails/issues/comments{/number}","contents_url":"https://api.github.com/repos/rails/rails/contents/{+path}","compare_url":"https://api.github.com/repos/rails/rails/compare/{base}...{head}","merges_url":"https://api.github.com/repos/rails/rails/merges","archive_url":"https://api.github.com/repos/rails/rails/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/rails/rails/downloads","issues_url":"https://api.github.com/repos/rails/rails/issues{/number}","pulls_url":"https://api.github.com/repos/rails/rails/pulls{/number}","milestones_url":"https://api.github.com/repos/rails/rails/milestones{/number}","notifications_url":"https://api.github.com/repos/rails/rails/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/rails/rails/labels{/name}","releases_url":"https://api.github.com/repos/rails/rails/releases{/id}","created_at":"2008-04-11T02:19:47Z","updated_at":"2015-03-04T09:15:47Z","pushed_at":"2015-03-04T07:49:06Z","git_url":"git://github.com/rails/rails.git","ssh_url":"git@github.com:rails/rails.git","clone_url":"https://github.com/rails/rails.git","svn_url":"https://github.com/rails/rails","homepage":"http://rubyonrails.org","size":385645,"stargazers_count":25159,"watchers_count":25159,"language":"Ruby","has_issues":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":9834,"mirror_url":null,"open_issues_count":713,"forks":9834,"open_issues":713,"watchers":25159,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/rails/rails/pulls/16917"},"html":{"href":"https://github.com/rails/rails/pull/16917"},"issue":{"href":"https://api.github.com/repos/rails/rails/issues/16917"},"comments":{"href":"https://api.github.com/repos/rails/rails/issues/16917/comments"},"review_comments":{"href":"https://api.github.com/repos/rails/rails/pulls/16917/comments"},"review_comment":{"href":"https://api.github.com/repos/rails/rails/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/rails/rails/pulls/16917/commits"},"statuses":{"href":"https://api.github.com/repos/rails/rails/statuses/9f12c3768639e5db8eda93286a2e0ab668c51350"}}}}