Bijan
Bijan

Reputation: 6772

Python generator to return series of times

I hope this is not outside of the abilities of Python generators, but I'd like to build one so that every time the function is called, it returns the next minute up until the end time.

So the function reads in a start and end time, and returns the time on a minute by minute basis until all the time in between has been covered.

How would this be implemented? TIA

Upvotes: 1

Views: 2906

Answers (3)

Jan Vlcinsky
Jan Vlcinsky

Reputation: 44142

Here is generator I use for this purpose. It could be enhanced to stop after some time, or you could keep that logic in your calling code, or in some other iterators arround.

import time

def sleep_gen(period):
    """Generator, returning not sooner, then period seconds since last call.

    returned value: time of next planned start (no need to use this value)
    """
    next_time = 0
    while True:
        now = time.time()
        sleep_time = max(next_time - now, 0)
        next_time = now + sleep_time + period
        time.sleep(sleep_time)
        yield next_time

You could use following code to test the behaviour

import plac
import time
from itertools import count, izip
import random

#sleep_gen to be defined here

@plac.annotations(
    period=      ("planned period for cycling in seconds (default: %(default)s)", "positional", None, float),
    min_duration=("minimal real 'action' duration in seconds (default:%(default)s)", "positional", None, float),
    max_duration=("maximal 'real action' duration in seconds (default:%(default)s)", "positional", None, float),
)    
def main(period = 1.0, min_duration = 0.1, max_duration = 2.0):
    """Tries to start some randomly long action in regular periods"""
    print """call with -h for help.
    period      : %(period)f
    min_duration: %(min_duration)f
    max_duration: %(max_duration)f""" % locals()
    try:
        last_time = now = time.time()
        header = (   "%5s|"          +"%14s|"    +"%14s|"           +"%8s|"         +"%8s|"          +"%14s|") % ("cycle", "last_time", "now", "action", "real", "next_time")
        row =    "%(i) 5d|%(last_time)14.2f|%(now)14.2f|%(action_dur)8.3f|%(real_dur)8.3f|%(next_time)14.2f|"
        print header
        action_dur = real_dur = 0.0
        for i, next_time in izip(count(), sleep_gen(period)):
            #we care about starting the action on time, not ending
            now = time.time() #sleep_gen just tried to keep the period on schedule
            real_dur = now - last_time
            print row % locals()
            last_time = now
            #performing the "action"
            action_dur = random.random() * (max_duration - min_duration) + min_duration
            time.sleep(action_dur)
    except KeyboardInterrupt:
        print "...cancelled."

if __name__ == "__main__":
    plac.call(main)

Calling it from command line:

$ python cycle.py
call with -h for help.
    period      : 1.000000
    min_duration: 0.100000
    max_duration: 2.000000
cycle|     last_time|           now|  action|    real|     next_time|
    0| 1337368558.55| 1337368558.55|   0.000|   0.002| 1337368559.55|
    1| 1337368558.55| 1337368559.59|   1.042|   1.042| 1337368560.59|
    2| 1337368559.59| 1337368561.32|   1.722|   1.723| 1337368562.32|
    3| 1337368561.32| 1337368562.32|   0.686|   1.000| 1337368563.32|
    4| 1337368562.32| 1337368563.32|   0.592|   1.000| 1337368564.32|
    5| 1337368563.32| 1337368564.75|   1.439|   1.439| 1337368565.75|
    6| 1337368564.75| 1337368566.08|   1.323|   1.323| 1337368567.08|
    7| 1337368566.08| 1337368567.08|   0.494|   0.999| 1337368568.08|
    8| 1337368567.08| 1337368568.20|   1.120|   1.121| 1337368569.20|
    9| 1337368568.20| 1337368569.20|   0.572|   1.000| 1337368570.20|
   10| 1337368569.20| 1337368570.20|   0.586|   1.000| 1337368571.20|
   11| 1337368570.20| 1337368571.20|   0.309|   0.999| 1337368572.20|
   12| 1337368571.20| 1337368572.20|   0.290|   1.000| 1337368573.20|
   13| 1337368572.20| 1337368573.25|   1.052|   1.053| 1337368574.25|
   14| 1337368573.25| 1337368574.25|   0.737|   1.000| 1337368575.25|
   15| 1337368574.25| 1337368575.83|   1.579|   1.579| 1337368576.83|
...cancelled.

Comparing your question with my answer:

  • generator: yes, it is
  • enter start time: I use just the current moment (what could be what one often needs)
  • enter end time: Stop calling the generator at that moment. Or modify generator to loop only some number of loops or end at certain time.

Upvotes: 1

srgerg
srgerg

Reputation: 19339

Just because fewer lines of code is always better (tm):

def minutes(s, e):
    secs = (e - s).seconds 
    return (s + datetime.timedelta(minutes = x) for x in xrange(secs / 60 + 1))

Use it like this:

>>> today = datetime.datetime(2012, 1, 31, 15, 20)
>>> for m in minutes(today, today + datetime.timedelta(minutes = 5)):
...     print m
2012-01-31 15:20:00
2012-01-31 15:21:00
2012-01-31 15:22:00
2012-01-31 15:23:00
2012-01-31 15:24:00
2012-01-31 15:25:00

Upvotes: 2

Donald Miner
Donald Miner

Reputation: 39933

The datetime module is quite awesome. There are two datatypes you need to know about: datetime and timedelta. datetime is a point in time, while timedelta is a period of time. Basically, what I'm going to do here is start at a time and end at a time (as a datetime object), and progressively add 1 minute.

This obviously has the caveat that you have to figure out how to get your start and end time into a datetime. There are a number of ways to do this: through the constructor, right now, from UTC timestamp, etc.

import datetime

def minute_range(start, end, step=1):
   cur = start
   while cur < end:
      yield cur
      cur += datetime.timedelta(minutes=step)

Upvotes: 7

Related Questions