Giter Site home page Giter Site logo

Comments (12)

jmettraux avatar jmettraux commented on August 27, 2024 1

@gr8bit thanks for sharing your use case, such pieces of information yield so much learning!

from fugit.

jmettraux avatar jmettraux commented on August 27, 2024

Hello,

@gr8bit wrote:

E.g. if current time was 2020-11-01 08:32:02, this interval
'* 8 * * *' should return a time of 2020-11-01 08:59:59 (or even 2020-11-01 09:00:00).

I am not sure I follow you on that one. * 8 * * * maps to 08:00:00, 08:01:00, 08:02:00, ... , 08:59:00.

Maybe something like

require 'fugit'


c = Fugit::Cron.parse('* 8 * * *')

MAXD = 366 * 24 * 3600 # 1 year

ds = []

t0 = Time.now
t = t0
t1 = nil
  #
loop do
  t = c.next_time(t)
  ds << [ t.to_s, t - t1 ] if t1
  t1 = t
  break if t > t0 + MAXD
end

#ds.each do |d|
#  p d
#end

longest = ds.collect { |t, d| d }.max
  # only pick the longest :-( ...

p [ :start, ds.first ]
p [ :end, ds.last ]
p [ :longest, longest ]

intervals =
  ds.inject([]) { |a, d|
    a << [] if a.empty? || d[1] == longest
    if a.last.empty?
      a.last << d
    else
      a.last[1] = d
    end
    a }

intervals.each do |i|
  p i
end

Closing the issue but not the conversation.

from fugit.

jmettraux avatar jmettraux commented on August 27, 2024

@gr8bit Are you a friend of the OP in #44 ?

from fugit.

gr8bit avatar gr8bit commented on August 27, 2024

Thanks a lot for the comment! I will look into it right after my answer.

E.g. if current time was 2020-11-01 08:32:02, this interval
'* 8 * * *' should return a time of 2020-11-01 08:59:59 (or even 2020-11-01 09:00:00).

I am not sure I follow you on that one. * 8 * * * maps to 08:00:00, 08:01:00, 08:02:00, ... , 08:59:00.

That's right, it maps to the times you stated - so if I use match? when the current time is between 08:00:00 and even 08:59:59, it will return true. From 09:00:00 on, it will again return false until it will be 8:00h again. If I understand correctly, the next_time method will point to the next day at 8:00:00 in our example.

What I need is, when match? is true, the next_end_time of the "interval" I'm in. (I'm always quoting interval to indicate I'm aware cron notation is not for intervals ;)).

@gr8bit Are you a friend of the OP in #44 ?

No, I don't know him but I seems he had a quite similar use case. 😄

from fugit.

gr8bit avatar gr8bit commented on August 27, 2024

Aaahhh, I think I understood next_time wrong - it returns the next minute the cron would run at. If in an "interval" it would just be the next minute. That's why you loop in your example (although I'm not sure why MAXD is 366 * 24 * 3600 and not 366 * 24 * 60 - we have only minute (not second) precision, don't we?).

from fugit.

jmettraux avatar jmettraux commented on August 27, 2024

@gr8bit wrote:

What I need is, when match? is true, the next_end_time of the "interval" I'm in. (I'm always quoting interval to indicate I'm aware cron notation is not for intervals ;)).

I am sorry but the Fugit::Cron#match?(time) method is point based. But nothing prevents you building your own abstraction on top of it that determines given a cron string what are the intervals and has its own #match?(time). As you have seen in my code example, my routine said "let's consider that the longest delta is the delta between two intervals". There isn't much in the cron strings that says that and Fugit::Cron is for cron string, not for time intervals.

from fugit.

jmettraux avatar jmettraux commented on August 27, 2024

@gr8bit wrote:

although I'm not sure why MAXD is 366 * 24 * 3600 and not 366 * 24 * 60 - we have only minute (not second) precision, don't we?

MAXD is one year, because 0 0 1 1 * happens once per year. I could have gone further to accept 0 0 29 2 * which happens usually every four years... I was trying to accept not just your example * 8 * * * but other cron strings. I wanted to generalize a bit...

from fugit.

gr8bit avatar gr8bit commented on August 27, 2024

What a great library. I think I have a very quick way to determine the end of the interval. I'll post code here once it's done. :) Thank you so much for the kind help!!

Also, I now understand 366 * 24 * 3600 - Cron actually is at seconds precision, I passed one parameter less and it was set to 0, that's why next_time stepped minutes in my tests.

from fugit.

jmettraux avatar jmettraux commented on August 27, 2024

You're welcome!

from fugit.

gr8bit avatar gr8bit commented on August 27, 2024

I think I got it. Comments please! :)

At first I've added a simple "change and clone" method to EoTime, I hope you don't mind.

module EtOrbi
  class EoTime

    def change(options = {})
      EtOrbi.make_time '%04d-%02d-%02dT%02d:%02d:%02d.%d%s' % [
        options[:year] || year,
        options[:month] || month,
        options[:day] || day,
        options[:hour] || hour,
        options[:min] || min,
        options[:sec] || sec,
        options[:usec] || usec,
        strfz('%:z')
      ]
    end

  end
end

And the main code for Fugit::Cron:

module Fugit
  class Cron

    ATTRS = {
      %i[seconds sec] => [0, 60, 1],
      %i[minutes min] => [0, 60, 60],
      %i[hours hour] => [0, 24, 60 * 60],
      %i[weekdays day] => [nil, 31, 24 * 60 * 60],
      %i[monthdays day] => [1, 31, 24 * 60 * 60],
      %i[months month] => [1, 12, nil]
    }.freeze

    def interval_end_time(from = ::EtOrbi::EoTime.now)
      return unless match?(from)

      # clone date and eliminate fractional seconds at once
      # @type till [EtOrbi::EoTime]
      till = ::EtOrbi.make_time(from.iso8601)
      # @type copy [Fugit::Cron]
      copy = self.class.new(original)

      changes = {}
      key, (_, incrs, secs) =
        ATTRS.detect do |(cron_attr, eo_attr), (default)|
          next true unless copy.instance_variable_get("@#{cron_attr}").nil?

          if default
            changes[eo_attr] = default
            copy.instance_variable_set("@#{cron_attr}", [default])
          end
          false
        end
      return unless key
      till = till.change(changes)

      incrs.times do
        till =
          if secs
            till.inc(secs)
          else
            # as months don't have the same amount of seconds, increase it differently
            till.change(month: till.month % 12 + 1, year: till.month % 12 == 0 ? till.year + 1 : nil)
          end
        break unless copy.match?(till)
      end

      till
    end

  end
end

It works like this: I need to sample future times until the first point in time the cron does not match. That point in time would be the end of the "interval" I'm in.
I can't sample all seconds of a year though and I don't need to - in fact, I can decrease the precision from seconds to minutes if all seconds match (=are set as "*"). Same goes for minutes to hours, hours to days and days to months.

So, from seconds up to months, I find the first field that's not "*" (nil in the cron object). That's my precision I want to test against. All "*" until the first non-"*" are set tho their respective "first value" (0 for seconds, minutes, hours, 1 for days and months, nil for weekday as that one is special).

I need to test one loop cycle of the found property only as it would equal "*" if it included the whole cycle (I do not explicitly check that though). Each iteration I increment my time object by the fields unit (second or minute or day etc.). So I'll need 60 loops at maximum if someone set something like * 1-59 * * * *. Even * 30-29 * * * * is possible that way. The first loop iteration that doesn't match marks the end time which I wanted to find.

That was a hell of a brain teaser. ;)

from fugit.

jmettraux avatar jmettraux commented on August 27, 2024

Well done!

I am not sure about your use case, but I think that if I needed that, I'd wrap it in its own library, a library that uses fugit, not a fugit augmentation. After all, you might decide later on that you want to parse different interval representations like "0800-0900,1500-1600" with which fugit has no business. Your tool would use fugit when presented with a cron string and some other lib if necessary for other interval representations. Really, fugit is about point in times, not intervals.

Gute Nacht!

from fugit.

gr8bit avatar gr8bit commented on August 27, 2024

Thanks and good point. Initially, my code used many more of the internals of Fugit::Cron than it does now. However, I rely on internal method names (for instance_variable_set especially), that's why I left it as augmenting code for now (also, because I never intend to change it but we both know: never say never when it comes to software development ;)).

If you're interested in my use case: for a small custom shop (for a game to be precise), a recurring sales events feature was asked for by the game designer. While thinking about an own implementation the similarities to the cron syntax became more and more obvious (ability to select weekdays for recurrence, "every X days" etc.). So I figured if I did not "sample" the cron definition by the minute (like classic cron does) it would also be well suited for defining "a time pattern matching definition which you can match any point in time against and it will tell you if it matches or not". So there was my "is this offer active?"-method.
When an offer came out as active for a certain point in time, I needed to know how long it would consecutively stay active as well - simply to show the user when this offer will end. That's where this issue's functionality came in. As the highest precision is "seconds", the brute force method would be to sample second by second until one came out as false, which would then be the end of the current "active interval". As you know, the code above is basically that except it's optimized to sample the highest used (as in: "not *") precision.
With your hash and modulo extensions in place, this is close to game designer marketing heaven. ;) I might even think about allowing more than one cron definition per offer so multiple time ranges like 08:00h-09:00h, 15:30h-16:40h would easily be possible.

Thanks a lot again for the quick and helpful replies! You rock!

from fugit.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.