New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Methods for ActiveSupport::Duration parsing from ISO 8601 and output to it #16917

Closed
wants to merge 21 commits into
base: master
from

Conversation

Projects
None yet
@Envek
Contributor

Envek commented Sep 14, 2014

This PR add methods to ActiveSupport::Duration to allow present it in ISO 8601 Duration format and instantiate from it.

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). So, I think it should be part of ActiveSupport::Duration.

Some parts of code and tests are taken from ISO8601 gem by Arnau Siches (@arnau) and contributors. Many thanks to them.

This PR is required for PostgreSQL interval datatype support (for converting it from and to ActiveSupport::Duration) as I've proposed in Google Group here. See #16919. Because of that my parsing allows individual datetime parts to be negative (see PostgreSQL interval output).

There is no backward incompatible changes as I want it to be included in upcoming 4.2 release, if it still possible (pleeeaasee!).

arnau added a commit to arnau/ISO8601 that referenced this pull request Sep 14, 2014

Change sign checking for Durations.
Thanks to @egilburg comment in rails/rails#16917 pull
request for catching this.

arnau added a commit to arnau/ISO8601 that referenced this pull request Sep 14, 2014

@Envek

This comment has been minimized.

Show comment
Hide comment
@Envek

Envek Sep 15, 2014

Contributor

Fixed some issues:

  • extracted parsing in separate ActiveSupport::Duration::ISO8601DurationParser class in separate file
  • extracted parts normalization into separate method
  • fixed parts normalization so returned hash default value is nil (as usual)

Method parse! throws exception ActiveSupport::Duration::ISO8601DurationParser::ParsingError on invalid input while parse returns nil.

Rebased branch on top of current master due to changes in ActiveSupport::Duration in recently merged #16574 .

Contributor

Envek commented Sep 15, 2014

Fixed some issues:

  • extracted parsing in separate ActiveSupport::Duration::ISO8601DurationParser class in separate file
  • extracted parts normalization into separate method
  • fixed parts normalization so returned hash default value is nil (as usual)

Method parse! throws exception ActiveSupport::Duration::ISO8601DurationParser::ParsingError on invalid input while parse returns nil.

Rebased branch on top of current master due to changes in ActiveSupport::Duration in recently merged #16574 .

@Envek

This comment has been minimized.

Show comment
Hide comment
@Envek

Envek Sep 16, 2014

Contributor

Fixed some more remarks.

I've noticed that now there is next error occurs when I run activerecord's tests for #16919 on top of this branch:

/Users/anovikov/rails/activesupport/lib/active_support/duration/iso8601_duration_parser.rb:2: warning: loading in progress, circular require considered harmful - /Users/anovikov/rails/activesupport/lib/active_support/duration.rb
    from /Users/anovikov/.rvm/gems/ruby-2.1.2-gost/gems/rake-10.3.2/lib/rake/rake_test_loader.rb:4:in  `<main>'
    from /Users/anovikov/.rvm/gems/ruby-2.1.2-gost/gems/rake-10.3.2/lib/rake/rake_test_loader.rb:4:in  `select'
    from /Users/anovikov/.rvm/gems/ruby-2.1.2-gost/gems/rake-10.3.2/lib/rake/rake_test_loader.rb:15:in  `block in <main>'
    from /Users/anovikov/.rvm/gems/ruby-2.1.2-gost/gems/rake-10.3.2/lib/rake/rake_test_loader.rb:15:in  `require'
    from /Users/anovikov/rails/activerecord/test/cases/adapter_test.rb:1:in  `<top (required)>'
    from /Users/anovikov/rails/activerecord/test/cases/adapter_test.rb:1:in  `require'
    from /Users/anovikov/rails/activerecord/test/cases/helper.rb:15:in  `<top (required)>'
    from /Users/anovikov/rails/activesupport/lib/active_support/dependencies.rb:248:in  `require'
...
    from /Users/anovikov/rails/activesupport/lib/active_support/dependencies.rb:248:in  `block in require'
    from /Users/anovikov/rails/activesupport/lib/active_support/dependencies.rb:248:in  `require'
    from /Users/anovikov/rails/activesupport/lib/active_support/duration/iso8601_duration_parser.rb:1:in  `<top (required)>'
    from /Users/anovikov/rails/activesupport/lib/active_support/duration/iso8601_duration_parser.rb:2:in  `<module:ActiveSupport>'

How can I fix it? The reason seems to be in that I'm explicitly requires file with parser in duration.rb

Contributor

Envek commented Sep 16, 2014

Fixed some more remarks.

I've noticed that now there is next error occurs when I run activerecord's tests for #16919 on top of this branch:

/Users/anovikov/rails/activesupport/lib/active_support/duration/iso8601_duration_parser.rb:2: warning: loading in progress, circular require considered harmful - /Users/anovikov/rails/activesupport/lib/active_support/duration.rb
    from /Users/anovikov/.rvm/gems/ruby-2.1.2-gost/gems/rake-10.3.2/lib/rake/rake_test_loader.rb:4:in  `<main>'
    from /Users/anovikov/.rvm/gems/ruby-2.1.2-gost/gems/rake-10.3.2/lib/rake/rake_test_loader.rb:4:in  `select'
    from /Users/anovikov/.rvm/gems/ruby-2.1.2-gost/gems/rake-10.3.2/lib/rake/rake_test_loader.rb:15:in  `block in <main>'
    from /Users/anovikov/.rvm/gems/ruby-2.1.2-gost/gems/rake-10.3.2/lib/rake/rake_test_loader.rb:15:in  `require'
    from /Users/anovikov/rails/activerecord/test/cases/adapter_test.rb:1:in  `<top (required)>'
    from /Users/anovikov/rails/activerecord/test/cases/adapter_test.rb:1:in  `require'
    from /Users/anovikov/rails/activerecord/test/cases/helper.rb:15:in  `<top (required)>'
    from /Users/anovikov/rails/activesupport/lib/active_support/dependencies.rb:248:in  `require'
...
    from /Users/anovikov/rails/activesupport/lib/active_support/dependencies.rb:248:in  `block in require'
    from /Users/anovikov/rails/activesupport/lib/active_support/dependencies.rb:248:in  `require'
    from /Users/anovikov/rails/activesupport/lib/active_support/duration/iso8601_duration_parser.rb:1:in  `<top (required)>'
    from /Users/anovikov/rails/activesupport/lib/active_support/duration/iso8601_duration_parser.rb:2:in  `<module:ActiveSupport>'

How can I fix it? The reason seems to be in that I'm explicitly requires file with parser in duration.rb

@Envek

This comment has been minimized.

Show comment
Hide comment
@Envek

Envek Feb 8, 2015

Contributor

To avoid circular require warnings (absolutely don't understand why are they appearing such a lot) included ISO8601 parser into file activesupport/lib/active_support/duration.rb. Squashed and rebased on top of current master. Please review one more time.

Contributor

Envek commented Feb 8, 2015

To avoid circular require warnings (absolutely don't understand why are they appearing such a lot) included ISO8601 parser into file activesupport/lib/active_support/duration.rb. Squashed and rebased on top of current master. Please review one more time.

@kaspth

This comment has been minimized.

Show comment
Hide comment
@kaspth

kaspth Feb 8, 2015

Member

Did you require duration in the parser and vice versa? That sounds like it could cause the circular requires.

Member

kaspth commented Feb 8, 2015

Did you require duration in the parser and vice versa? That sounds like it could cause the circular requires.

@Envek

This comment has been minimized.

Show comment
Hide comment
@Envek

Envek Feb 8, 2015

Contributor

No, I had required parser only from duration (no requires from file with parser at all): commit that caused a lot of warnings.

Contributor

Envek commented Feb 8, 2015

No, I had required parser only from duration (no requires from file with parser at all): commit that caused a lot of warnings.

@kaspth

This comment has been minimized.

Show comment
Hide comment
@kaspth

kaspth Feb 8, 2015

Member

Duration was being autoloaded. It hit class Duration in the parser which would trigger another autoload and thus the files would keep trying to load each other.

Member

kaspth commented Feb 8, 2015

Duration was being autoloaded. It hit class Duration in the parser which would trigger another autoload and thus the files would keep trying to load each other.

@fxn

This comment has been minimized.

Show comment
Hide comment
@fxn

fxn Feb 8, 2015

Member

@kaspth the contrib app certainly understands a trailing [...] with authors separated by commas and other separators. See for example 6f8d9bd.

I didn't know GitHub had that feature... you sure it does?

Member

fxn commented Feb 8, 2015

@kaspth the contrib app certainly understands a trailing [...] with authors separated by commas and other separators. See for example 6f8d9bd.

I didn't know GitHub had that feature... you sure it does?

@Envek

This comment has been minimized.

Show comment
Hide comment
@Envek

Envek Feb 8, 2015

Contributor

I've accounted all the notes and force pushed changes in the same commit. I've tried split initializer on separate methods, but dislike current implementations (they are linked too tight with each other).

P.S> in 6f8d9bd only one author specified. Git doesn't support multiple authors (at least with each one's email)

Contributor

Envek commented Feb 8, 2015

I've accounted all the notes and force pushed changes in the same commit. I've tried split initializer on separate methods, but dislike current implementations (they are linked too tight with each other).

P.S> in 6f8d9bd only one author specified. Git doesn't support multiple authors (at least with each one's email)

@kaspth

This comment has been minimized.

Show comment
Hide comment
@kaspth

kaspth Feb 8, 2015

Member

@fxn I did remember seeing commits like that. I assumed it was for GitHub and not the contributors app, but it was the other way. Sorry about that!

Member

kaspth commented Feb 8, 2015

@fxn I did remember seeing commits like that. I assumed it was for GitHub and not the contributors app, but it was the other way. Sorry about that!

@Envek

This comment has been minimized.

Show comment
Hide comment
@Envek

Envek Feb 8, 2015

Contributor

Ah, sorry, misunderstood you. Added both authors in [] in end of commit message.

Contributor

Envek commented Feb 8, 2015

Ah, sorry, misunderstood you. Added both authors in [] in end of commit message.

@fxn

This comment has been minimized.

Show comment
Hide comment
@fxn

fxn Feb 8, 2015

Member

@Envek If the body of the commit message ends with

[Andrey Novikov, Arnau Siches]

at the very bottom, last line, the contrib app will pick it up automatically and give both of you credit.

See for example 84c0f73. That one uses "&" but a comma also works.

Member

fxn commented Feb 8, 2015

@Envek If the body of the commit message ends with

[Andrey Novikov, Arnau Siches]

at the very bottom, last line, the contrib app will pick it up automatically and give both of you credit.

See for example 84c0f73. That one uses "&" but a comma also works.

@fxn

This comment has been minimized.

Show comment
Hide comment
@fxn

fxn Feb 8, 2015

Member

@kaspth cool :)

Member

fxn commented Feb 8, 2015

@kaspth cool :)

@fxn

This comment has been minimized.

Show comment
Hide comment
@fxn

fxn Feb 8, 2015

Member

@Envek looking good!

Member

fxn commented Feb 8, 2015

@Envek looking good!

@kaspth

This comment has been minimized.

Show comment
Hide comment
@kaspth

kaspth Feb 8, 2015

Member

@fxn do you have time for an autoloading question? @Envek was seeing circular require errors: #16917 (comment)

Member

kaspth commented Feb 8, 2015

@fxn do you have time for an autoloading question? @Envek was seeing circular require errors: #16917 (comment)

@fxn

This comment has been minimized.

Show comment
Hide comment
@fxn

fxn Feb 8, 2015

Member

Looks like a regular Ruby warning for circular requires (an autoloading error would look like this). I can't tonight but will have a look as soon as I can.

Member

fxn commented Feb 8, 2015

Looks like a regular Ruby warning for circular requires (an autoloading error would look like this). I can't tonight but will have a look as soon as I can.

@kaspth

This comment has been minimized.

Show comment
Hide comment
@kaspth

kaspth Feb 8, 2015

Member

@fxn Thanks :)

Member

kaspth commented Feb 8, 2015

@fxn Thanks :)

@Envek

This comment has been minimized.

Show comment
Hide comment
@Envek

Envek Feb 10, 2015

Contributor

Also I've noticed that currently ActiveSupport::Duration#to_s (and, thus, to_json) method returns just a number of seconds, but it may be more appropriate to return ISO 8601 formatted duration string as it recommended by Google JSON Style Guide for example. Also there is some support in JS libraries (see moment.js duration asJson() and moment-duration).

So, question: should I add support for this in this PR (while it is not merged)

EDIT: Sorry, now I see that there is already as_json method that returns a @value (number of seconds).

Contributor

Envek commented Feb 10, 2015

Also I've noticed that currently ActiveSupport::Duration#to_s (and, thus, to_json) method returns just a number of seconds, but it may be more appropriate to return ISO 8601 formatted duration string as it recommended by Google JSON Style Guide for example. Also there is some support in JS libraries (see moment.js duration asJson() and moment-duration).

So, question: should I add support for this in this PR (while it is not merged)

EDIT: Sorry, now I see that there is already as_json method that returns a @value (number of seconds).

@Envek

This comment has been minimized.

Show comment
Hide comment
@Envek

Envek Feb 25, 2015

Contributor

I have rebased changes on top of current master and also

  1. Extracted parser to separate file and require it only when it first time used. Circular require warnings are gone. Commit. Question: is it fine to require inside method? I've seen such requires somewhere in rails codebase.
  2. Changed as_json to return ISO8601 formatted string. This is backward incompatible. Commit. Question: Should I keep or delete this change? Or may be also add a note to upgrade guide or somewhere?

I placed these changes in separate commits for review convenience.

Contributor

Envek commented Feb 25, 2015

I have rebased changes on top of current master and also

  1. Extracted parser to separate file and require it only when it first time used. Circular require warnings are gone. Commit. Question: is it fine to require inside method? I've seen such requires somewhere in rails codebase.
  2. Changed as_json to return ISO8601 formatted string. This is backward incompatible. Commit. Question: Should I keep or delete this change? Or may be also add a note to upgrade guide or somewhere?

I placed these changes in separate commits for review convenience.

@Envek

This comment has been minimized.

Show comment
Hide comment
@Envek

Envek Nov 24, 2015

Contributor

@pixeltrix I fixed your today notes. If there is nothing else left I will be glad to squash and rebase. Thanks for review.

Contributor

Envek commented Nov 24, 2015

@pixeltrix I fixed your today notes. If there is nothing else left I will be glad to squash and rebase. Thanks for review.

@rafaelfranca rafaelfranca removed this from the 5.0.0 [temp] milestone Apr 5, 2016

@nambrot

This comment has been minimized.

Show comment
Hide comment
@nambrot

nambrot Apr 15, 2016

Just wanted to give another thumbs up for this one. I'll be implementing this external, but it would have been very nice to be part of ActiveSupport

nambrot commented Apr 15, 2016

Just wanted to give another thumbs up for this one. I'll be implementing this external, but it would have been very nice to be part of ActiveSupport

@Envek

This comment has been minimized.

Show comment
Hide comment
@Envek

Envek Apr 16, 2016

Contributor

@nambrot thank you for your support.

You can just use serializer from my gist that will enable ISO8601 parsing and output: https://gist.github.com/Envek/7077bfc36b17233f60ad

Meanwhile I'm still wating for feedback in this PR as I don't know whether I should change something.

Contributor

Envek commented Apr 16, 2016

@nambrot thank you for your support.

You can just use serializer from my gist that will enable ISO8601 parsing and output: https://gist.github.com/Envek/7077bfc36b17233f60ad

Meanwhile I'm still wating for feedback in this PR as I don't know whether I should change something.

@jeremy

This comment has been minimized.

Show comment
Hide comment
@jeremy

jeremy Apr 17, 2016

Member

If we're pulling in code under MIT license, we need to include the license and copyright statement.

Also, if we're pulling in code, perhaps we should do a gem dep instead?

Nice work on this, and patience 😁

Member

jeremy commented Apr 17, 2016

If we're pulling in code under MIT license, we need to include the license and copyright statement.

Also, if we're pulling in code, perhaps we should do a gem dep instead?

Nice work on this, and patience 😁

@Envek

This comment has been minimized.

Show comment
Hide comment
@Envek

Envek Apr 17, 2016

Contributor

@jeremy there is not a much code left from the original code from ISO8601 gem as I've rewritten parser from regexp to string scanner by @pixeltrix suggestion. As far as I can see for now only duration examples strings inside tests are same with ISO8601. See the original code and tests.

Anyway where I should place license and copyright statement if I should? @arnau what do you think?

Gem dependency means that we should throw away whole ActiveSupport::Duration and replace it with ISO8601::Duration. Are you serious with that? 😄

@jeremy please take a look at #22806 while I'm fixing your notes— it's extracted from this pull request.

Contributor

Envek commented Apr 17, 2016

@jeremy there is not a much code left from the original code from ISO8601 gem as I've rewritten parser from regexp to string scanner by @pixeltrix suggestion. As far as I can see for now only duration examples strings inside tests are same with ISO8601. See the original code and tests.

Anyway where I should place license and copyright statement if I should? @arnau what do you think?

Gem dependency means that we should throw away whole ActiveSupport::Duration and replace it with ISO8601::Duration. Are you serious with that? 😄

@jeremy please take a look at #22806 while I'm fixing your notes— it's extracted from this pull request.

Envek added some commits Apr 17, 2016

@Envek

This comment has been minimized.

Show comment
Hide comment
@Envek

Envek Apr 17, 2016

Contributor

@jeremy fixed your notes

Contributor

Envek commented Apr 17, 2016

@jeremy fixed your notes

@jeremy

This comment has been minimized.

Show comment
Hide comment
@jeremy

jeremy Apr 17, 2016

Member

@Envek We can't replace AS::Duration with ISO8601::Duration, but perhaps we could delegate implementation to it. You tell me :)

If you've rewritten the code, then you're no longer using the original code and aren't redistributing it, so you needn't pull in its license.

If you are reusing code, include the license+copyright along with the code, in a Ruby comment.

Member

jeremy commented Apr 17, 2016

@Envek We can't replace AS::Duration with ISO8601::Duration, but perhaps we could delegate implementation to it. You tell me :)

If you've rewritten the code, then you're no longer using the original code and aren't redistributing it, so you needn't pull in its license.

If you are reusing code, include the license+copyright along with the code, in a Ruby comment.

@arnau

This comment has been minimized.

Show comment
Hide comment
@arnau

arnau Apr 18, 2016

@Envek do whatever makes more sense to you :)

arnau commented Apr 18, 2016

@Envek do whatever makes more sense to you :)

Envek added some commits Apr 18, 2016

@Envek

This comment has been minimized.

Show comment
Hide comment
@Envek

Envek Apr 18, 2016

Contributor

@jeremy, @arnau, I changed notices about licensing, take a look at 3a92929 . Is it enough?

If yes I would be glad to rebase and squash.

Contributor

Envek commented Apr 18, 2016

@jeremy, @arnau, I changed notices about licensing, take a look at 3a92929 . Is it enough?

If yes I would be glad to rebase and squash.

Envek added some commits Apr 18, 2016

@jeremy

This comment has been minimized.

Show comment
Hide comment
@jeremy

jeremy Apr 18, 2016

Member

Merged! 04c512d

Member

jeremy commented Apr 18, 2016

Merged! 04c512d

@jeremy jeremy closed this Apr 18, 2016

vipulnsward added a commit to vipulnsward/rails that referenced this pull request Apr 18, 2016

spastorino added a commit that referenced this pull request Apr 19, 2016

@Envek

This comment has been minimized.

Show comment
Hide comment
@Envek

Envek Apr 19, 2016

Contributor

@jeremy thank you for merging! You have just unlocked a special #16919!

P.S> I have a one more small ActiveSupport goodness: #20625, you can review it too if you have free time and wish :-)

Contributor

Envek commented Apr 19, 2016

@jeremy thank you for merging! You have just unlocked a special #16919!

P.S> I have a one more small ActiveSupport goodness: #20625, you can review it too if you have free time and wish :-)

@Envek Envek deleted the Envek:iso8601_duration branch Apr 19, 2016

chancancode added a commit that referenced this pull request Jun 27, 2016

@Dorian

This comment has been minimized.

Show comment
Hide comment
@Dorian

Dorian Jun 2, 2017

Contributor

This makes using duration in where condition easy, thanks @Envek !!!

House.where("(houses.sold_at - houses.bought_at) >= ?", 20.years.iso8601).size
Contributor

Dorian commented Jun 2, 2017

This makes using duration in where condition easy, thanks @Envek !!!

House.where("(houses.sold_at - houses.bought_at) >= ?", 20.years.iso8601).size
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment