Git Product home page Git Product logo

tickle's Introduction

Tickle

This is now the home of Tickle, previously found at github.com/noctivityinc/tickle

If you wish to contribute then please take a look at the Contribution section further down the page, but I'd be really, really grateful if anyone wishes to contribute specs. Not unit tests, but specs. This library's internals will be changing a lot over the coming months, and it would be good to have integration tests - a black-box spec of the library - to work against. Even if you've never contributed to a library before, now is your chance! I'll help anyone through with what they may need, and I promise not to be the standard snarky Open Source dictator that a lot of projects have. We'll try and improve this library together.

Take a look at the develop branch where all this stuff will be happening.

DESCRIPTION

Tickle is a natural language parser for recurring events.

Tickle is designed to be a complement to Chronic and can interpret things such as "every 2 days", "every Sunday", “Sundays", “Weekly", etc.

Tickle has one main method, Tickle.parse, which returns the next time the event should occur, at which point you simply call Tickle.parse again.

LEGACY WARNING

If you starting using Tickle pre version 0.1.X, you will need to update your code to either include the :next_only => true option or read correctly from the options hash. Sorry.

USAGE

You can parse strings containing a natural language interval using the Tickle.parse method.

You can either pass a string prefixed with the word "every", "each" or "on the" or simply the timeframe.

Tickle.parse returns a hash containing the following keys:

  • next the next occurrence of the event. This is NEVER today as its always the next date in the future.
  • starting the date all calculations as based on. If not passed as an option, the start date is right now.
  • until the last date you want this event to run until.
  • expression this is the natural language expression to store to run through tickle later to get the next occurrence.

Tickle returns nil if it cannot parse the string.

Tickle heavily uses Chronic for parsing both the event and the start date.

OPTIONS

There are two ways to pass options: natural language or an options hash.

NATURAL LANGUAGE:

  • Pass a start date with the word "starting", "start", or "stars" e.g. Tickle.parse('every 3 days starting next friday')
  • Pass an end date with the word "until", "end", "ends", or "ending" e.g. Tickle.parse('every 3 days until next friday')
  • Pass both at the same time e.g. "starting May 5th repeat every other week until December 1"

OPTIONS HASH Valid options are:

  • start - must be a valid date or Chronic date expression. e.g. Tickle.parse('every other day', {:start => Date.new(2010,8,1) })
  • until - must be a valid date or Chronic date expression. e.g. Tickle.parse('every other day', {:until => Date.new(2010,10,1) })
  • next_only - legacy switch to only return the next occurrence as a date and not return a hash

SUPER IMPORTANT NOTE ABOUT NEXT OCCURRENCE & START DATE

You may notice when parsing an expression with a start date that the next occurrence is the start date passed. This is designed behaviour.

Here's why - assume your user says "remind me every 3 weeks starting Dec 1" and today is May 8th. Well the first reminder needs to be sent on Dec 1, not Dec 21 (three weeks later).

If you don't like that, fork and have fun but don't say you weren't warned.

EXAMPLES

require 'tickle'

SIMPLE

Tickle.parse('day')
  #=> {:next=>2010-05-10 20:57:36 -0400, :expression=>"day", :starting=>2010-05-09 20:57:36 -0400, :until=>nil}

Tickle.parse('day')
  #=> {:next=>2010-05-10 20:57:36 -0400, :expression=>"day", :starting=>2010-05-09 20:57:36 -0400, :until=>nil}

Tickle.parse('week')
  #=> {:next=>2010-05-16 20:57:36 -0400, :expression=>"week", :starting=>2010-05-09 20:57:36 -0400, :until=>nil}

Tickle.parse('month')
  #=> {:next=>2010-06-09 20:57:36 -0400, :expression=>"month", :starting=>2010-05-09 20:57:36 -0400, :until=>nil}

Tickle.parse('year')
  #=> {:next=>2011-05-09 20:57:36 -0400, :expression=>"year", :starting=>2010-05-09 20:57:36 -0400, :until=>nil}

Tickle.parse('daily')
  #=> {:next=>2010-05-10 20:57:36 -0400, :expression=>"daily", :starting=>2010-05-09 20:57:36 -0400, :until=>nil}

Tickle.parse('weekly')
  #=> {:next=>2010-05-16 20:57:36 -0400, :expression=>"weekly", :starting=>2010-05-09 20:57:36 -0400, :until=>nil}

Tickle.parse('monthly')
  #=> {:next=>2010-06-09 20:57:36 -0400, :expression=>"monthly", :starting=>2010-05-09 20:57:36 -0400, :until=>nil}

Tickle.parse('yearly')
  #=> {:next=>2011-05-09 20:57:36 -0400, :expression=>"yearly", :starting=>2010-05-09 20:57:36 -0400, :until=>nil}

Tickle.parse('3 days')
  #=> {:next=>2010-05-12 20:57:36 -0400, :expression=>"3 days", :starting=>2010-05-09 20:57:36 -0400, :until=>nil}

Tickle.parse('3 weeks')
  #=> {:next=>2010-05-30 20:57:36 -0400, :expression=>"3 weeks", :starting=>2010-05-09 20:57:36 -0400, :until=>nil}

Tickle.parse('3 months')
  #=> {:next=>2010-08-09 20:57:36 -0400, :expression=>"3 months", :starting=>2010-05-09 20:57:36 -0400, :until=>nil}

Tickle.parse('3 years')
  #=> {:next=>2013-05-09 20:57:36 -0400, :expression=>"3 years", :starting=>2010-05-09 20:57:36 -0400, :until=>nil}

Tickle.parse('other day')
  #=> {:next=>2010-05-11 20:57:36 -0400, :expression=>"other day", :starting=>2010-05-09 20:57:36 -0400, :until=>nil}

Tickle.parse('other week')
  #=> {:next=>2010-05-23 20:57:36 -0400, :expression=>"other week", :starting=>2010-05-09 20:57:36 -0400, :until=>nil}

Tickle.parse('other month')
  #=> {:next=>2010-07-09 20:57:36 -0400, :expression=>"other month", :starting=>2010-05-09 20:57:36 -0400, :until=>nil}

Tickle.parse('other year')
  #=> {:next=>2012-05-09 20:57:36 -0400, :expression=>"other year", :starting=>2010-05-09 20:57:36 -0400, :until=>nil}

Tickle.parse('Monday')
  #=> {:next=>2010-05-10 12:00:00 -0400, :expression=>"monday", :starting=>2010-05-09 20:57:36 -0400, :until=>nil}

Tickle.parse('Wednesday')
  #=> {:next=>2010-05-12 12:00:00 -0400, :expression=>"wednesday", :starting=>2010-05-09 20:57:36 -0400, :until=>nil}

Tickle.parse('Friday')
  #=> {:next=>2010-05-14 12:00:00 -0400, :expression=>"friday", :starting=>2010-05-09 20:57:36 -0400, :until=>nil}

Tickle.parse('February', {:start=>#<Date: 2020-04-01 (4917881/2,0,2299161)>, :now=>#<Date: 2020-04-01 (4917881/2,0,2299161)>})
  #=> {:next=>2021-02-01 12:00:00 -0500, :expression=>"february", :starting=>2020-04-01 00:00:00 -0400, :until=>nil}

Tickle.parse('May', {:start=>#<Date: 2020-04-01 (4917881/2,0,2299161)>, :now=>#<Date: 2020-04-01 (4917881/2,0,2299161)>})
  #=> {:next=>2020-05-01 12:00:00 -0400, :expression=>"may", :starting=>2020-04-01 00:00:00 -0400, :until=>nil}

Tickle.parse('june', {:start=>#<Date: 2020-04-01 (4917881/2,0,2299161)>, :now=>#<Date: 2020-04-01 (4917881/2,0,2299161)>})
  #=> {:next=>2020-06-01 12:00:00 -0400, :expression=>"june", :starting=>2020-04-01 00:00:00 -0400, :until=>nil}

Tickle.parse('beginning of the week')
  #=> {:next=>2010-05-16 12:00:00 -0400, :expression=>"beginning of the week", :starting=>2010-05-09 20:57:36 -0400, :until=>nil}

Tickle.parse('middle of the week')
  #=> {:next=>2010-05-12 12:00:00 -0400, :expression=>"middle of the week", :starting=>2010-05-09 20:57:36 -0400, :until=>nil}

Tickle.parse('end of the week')
  #=> {:next=>2010-05-15 12:00:00 -0400, :expression=>"end of the week", :starting=>2010-05-09 20:57:36 -0400, :until=>nil}

Tickle.parse('beginning of the month', {:start=>#<Date: 2020-04-01 (4917881/2,0,2299161)>, :now=>#<Date: 2020-04-01 (4917881/2,0,2299161)>})
  #=> {:next=>2020-05-01 00:00:00 -0400, :expression=>"beginning of the month", :starting=>2020-04-01 00:00:00 -0400, :until=>nil}

Tickle.parse('middle of the month', {:start=>#<Date: 2020-04-01 (4917881/2,0,2299161)>, :now=>#<Date: 2020-04-01 (4917881/2,0,2299161)>})
  #=> {:next=>2020-04-15 00:00:00 -0400, :expression=>"middle of the month", :starting=>2020-04-01 00:00:00 -0400, :until=>nil}

Tickle.parse('end of the month', {:start=>#<Date: 2020-04-01 (4917881/2,0,2299161)>, :now=>#<Date: 2020-04-01 (4917881/2,0,2299161)>})
  #=> {:next=>2020-04-30 00:00:00 -0400, :expression=>"end of the month", :starting=>2020-04-01 00:00:00 -0400, :until=>nil}

Tickle.parse('beginning of the year', {:start=>#<Date: 2020-04-01 (4917881/2,0,2299161)>, :now=>#<Date: 2020-04-01 (4917881/2,0,2299161)>})
  #=> {:next=>2021-01-01 00:00:00 -0500, :expression=>"beginning of the year", :starting=>2020-04-01 00:00:00 -0400, :until=>nil}

Tickle.parse('middle of the year', {:start=>#<Date: 2020-04-01 (4917881/2,0,2299161)>, :now=>#<Date: 2020-04-01 (4917881/2,0,2299161)>})
  #=> {:next=>2020-06-15 00:00:00 -0400, :expression=>"middle of the year", :starting=>2020-04-01 00:00:00 -0400, :until=>nil}

Tickle.parse('end of the year', {:start=>#<Date: 2020-04-01 (4917881/2,0,2299161)>, :now=>#<Date: 2020-04-01 (4917881/2,0,2299161)>})
  #=> {:next=>2020-12-31 00:00:00 -0500, :expression=>"end of the year", :starting=>2020-04-01 00:00:00 -0400, :until=>nil}

Tickle.parse('the 3rd of May', {:start=>#<Date: 2020-04-01 (4917881/2,0,2299161)>, :now=>#<Date: 2020-04-01 (4917881/2,0,2299161)>})
  #=> {:next=>2020-05-03 00:00:00 -0400, :expression=>"the 3rd of may", :starting=>2020-04-01 00:00:00 -0400, :until=>nil}

Tickle.parse('the 3rd of February', {:start=>#<Date: 2020-04-01 (4917881/2,0,2299161)>, :now=>#<Date: 2020-04-01 (4917881/2,0,2299161)>})
  #=> {:next=>2021-02-03 00:00:00 -0500, :expression=>"the 3rd of february", :starting=>2020-04-01 00:00:00 -0400, :until=>nil}

Tickle.parse('the 3rd of February 2022', {:start=>#<Date: 2020-04-01 (4917881/2,0,2299161)>, :now=>#<Date: 2020-04-01 (4917881/2,0,2299161)>})
  #=> {:next=>2022-02-03 00:00:00 -0500, :expression=>"the 3rd of february 2022", :starting=>2020-04-01 00:00:00 -0400, :until=>nil}

Tickle.parse('the 3rd of Feb 2022', {:start=>#<Date: 2020-04-01 (4917881/2,0,2299161)>, :now=>#<Date: 2020-04-01 (4917881/2,0,2299161)>})
  #=> {:next=>2022-02-03 00:00:00 -0500, :expression=>"the 3rd of feb 2022", :starting=>2020-04-01 00:00:00 -0400, :until=>nil}

Tickle.parse('the 4th of the month', {:start=>#<Date: 2020-04-01 (4917881/2,0,2299161)>, :now=>#<Date: 2020-04-01 (4917881/2,0,2299161)>})
  #=> {:next=>2020-04-04 00:00:00 -0400, :expression=>"the 4th of the month", :starting=>2020-04-01 00:00:00 -0400, :until=>nil}

Tickle.parse('the 10th of the month', {:start=>#<Date: 2020-04-01 (4917881/2,0,2299161)>, :now=>#<Date: 2020-04-01 (4917881/2,0,2299161)>})
  #=> {:next=>2020-04-10 00:00:00 -0400, :expression=>"the 10th of the month", :starting=>2020-04-01 00:00:00 -0400, :until=>nil}

Tickle.parse('the tenth of the month', {:start=>#<Date: 2020-04-01 (4917881/2,0,2299161)>, :now=>#<Date: 2020-04-01 (4917881/2,0,2299161)>})
  #=> {:next=>2020-04-10 00:00:00 -0400, :expression=>"the tenth of the month", :starting=>2020-04-01 00:00:00 -0400, :until=>nil}

Tickle.parse('first', {:start=>#<Date: 2020-04-01 (4917881/2,0,2299161)>, :now=>#<Date: 2020-04-01 (4917881/2,0,2299161)>})
  #=> {:next=>2020-05-01 00:00:00 -0400, :expression=>"first", :starting=>2020-04-01 00:00:00 -0400, :until=>nil}

Tickle.parse('the first of the month', {:start=>#<Date: 2020-04-01 (4917881/2,0,2299161)>, :now=>#<Date: 2020-04-01 (4917881/2,0,2299161)>})
  #=> {:next=>2020-05-01 00:00:00 -0400, :expression=>"the first of the month", :starting=>2020-04-01 00:00:00 -0400, :until=>nil}

Tickle.parse('the thirtieth', {:start=>#<Date: 2020-04-01 (4917881/2,0,2299161)>, :now=>#<Date: 2020-04-01 (4917881/2,0,2299161)>})
  #=> {:next=>2020-04-30 00:00:00 -0400, :expression=>"the thirtieth", :starting=>2020-04-01 00:00:00 -0400, :until=>nil}

Tickle.parse('the fifth', {:start=>#<Date: 2020-04-01 (4917881/2,0,2299161)>, :now=>#<Date: 2020-04-01 (4917881/2,0,2299161)>})
  #=> {:next=>2020-04-05 00:00:00 -0400, :expression=>"the fifth", :starting=>2020-04-01 00:00:00 -0400, :until=>nil}

Tickle.parse('the 1st Wednesday of the month', {:start=>#<Date: 2020-04-01 (4917881/2,0,2299161)>, :now=>#<Date: 2020-04-01 (4917881/2,0,2299161)>})
  #=> {:next=>2020-05-01 00:00:00 -0400, :expression=>"the 1st wednesday of the month", :starting=>2020-04-01 00:00:00 -0400, :until=>nil}

Tickle.parse('the 3rd Sunday of May', {:start=>#<Date: 2020-04-01 (4917881/2,0,2299161)>, :now=>#<Date: 2020-04-01 (4917881/2,0,2299161)>})
  #=> {:next=>2020-05-17 12:00:00 -0400, :expression=>"the 3rd sunday of may", :starting=>2020-04-01 00:00:00 -0400, :until=>nil}

Tickle.parse('the 3rd Sunday of the month', {:start=>#<Date: 2020-04-01 (4917881/2,0,2299161)>, :now=>#<Date: 2020-04-01 (4917881/2,0,2299161)>})
  #=> {:next=>2020-04-19 12:00:00 -0400, :expression=>"the 3rd sunday of the month", :starting=>2020-04-01 00:00:00 -0400, :until=>nil}

Tickle.parse('the 23rd of June', {:start=>#<Date: 2020-04-01 (4917881/2,0,2299161)>, :now=>#<Date: 2020-04-01 (4917881/2,0,2299161)>})
  #=> {:next=>2020-06-23 00:00:00 -0400, :expression=>"the 23rd of june", :starting=>2020-04-01 00:00:00 -0400, :until=>nil}

Tickle.parse('the twenty third of June', {:start=>#<Date: 2020-04-01 (4917881/2,0,2299161)>, :now=>#<Date: 2020-04-01 (4917881/2,0,2299161)>})
  #=> {:next=>2020-06-23 00:00:00 -0400, :expression=>"the twenty third of june", :starting=>2020-04-01 00:00:00 -0400, :until=>nil}

Tickle.parse('the thirty first of July', {:start=>#<Date: 2020-04-01 (4917881/2,0,2299161)>, :now=>#<Date: 2020-04-01 (4917881/2,0,2299161)>})
  #=> {:next=>2020-07-31 00:00:00 -0400, :expression=>"the thirty first of july", :starting=>2020-04-01 00:00:00 -0400, :until=>nil}

Tickle.parse('the twenty first', {:start=>#<Date: 2020-04-01 (4917881/2,0,2299161)>, :now=>#<Date: 2020-04-01 (4917881/2,0,2299161)>})
  #=> {:next=>2020-04-21 00:00:00 -0400, :expression=>"the twenty first", :starting=>2020-04-01 00:00:00 -0400, :until=>nil}

Tickle.parse('the twenty first of the month', {:start=>#<Date: 2020-04-01 (4917881/2,0,2299161)>, :now=>#<Date: 2020-04-01 (4917881/2,0,2299161)>})
  #=> {:next=>2020-04-21 00:00:00 -0400, :expression=>"the twenty first of the month", :starting=>2020-04-01 00:00:00 -0400, :until=>nil}

COMPLEX

Tickle.parse('starting today and ending one week from now')
  #=> {:next=>2010-05-10 22:30:00 -0400, :expression=>"day", :starting=>2010-05-09 22:30:00 -0400, :until=>2010-05-16 20:57:35 -0400}

Tickle.parse('starting tomorrow and ending one week from now')
  #=> {:next=>2010-05-10 12:00:00 -0400, :expression=>"day", :starting=>2010-05-10 12:00:00 -0400, :until=>2010-05-16 20:57:35 -0400}

Tickle.parse('starting Monday repeat every month')
  #=> {:next=>2010-05-10 12:00:00 -0400, :expression=>"month", :starting=>2010-05-10 12:00:00 -0400, :until=>nil}

Tickle.parse('starting May 13th repeat every week')
  #=> {:next=>2010-05-13 12:00:00 -0400, :expression=>"week", :starting=>2010-05-13 12:00:00 -0400, :until=>nil}

Tickle.parse('starting May 13th repeat every other day')
  #=> {:next=>2010-05-13 12:00:00 -0400, :expression=>"other day", :starting=>2010-05-13 12:00:00 -0400, :until=>nil}

Tickle.parse('every other day starts May 13th')
  #=> {:next=>2010-05-13 12:00:00 -0400, :expression=>"other day", :starting=>2010-05-13 12:00:00 -0400, :until=>nil}

Tickle.parse('every other day starts May 13')
  #=> {:next=>2010-05-13 12:00:00 -0400, :expression=>"other day", :starting=>2010-05-13 12:00:00 -0400, :until=>nil}

Tickle.parse('every other day starting May 13th')
  #=> {:next=>2010-05-13 12:00:00 -0400, :expression=>"other day", :starting=>2010-05-13 12:00:00 -0400, :until=>nil}

Tickle.parse('every other day starting May 13')
  #=> {:next=>2010-05-13 12:00:00 -0400, :expression=>"other day", :starting=>2010-05-13 12:00:00 -0400, :until=>nil}

Tickle.parse('every week starts this wednesday')
  #=> {:next=>2010-05-12 12:00:00 -0400, :expression=>"week", :starting=>2010-05-12 12:00:00 -0400, :until=>nil}

Tickle.parse('every week starting this wednesday')
  #=> {:next=>2010-05-12 12:00:00 -0400, :expression=>"week", :starting=>2010-05-12 12:00:00 -0400, :until=>nil}

Tickle.parse('every other day starting May 1st 2021')
  #=> {:next=>2021-05-01 12:00:00 -0400, :expression=>"other day", :starting=>2021-05-01 12:00:00 -0400, :until=>nil}

Tickle.parse('every other day starting May 1 2021')
  #=> {:next=>2021-05-01 12:00:00 -0400, :expression=>"other day", :starting=>2021-05-01 12:00:00 -0400, :until=>nil}

Tickle.parse('every other week starting this Sunday')
  #=> {:next=>2010-05-16 12:00:00 -0400, :expression=>"other week", :starting=>2010-05-16 12:00:00 -0400, :until=>nil}

Tickle.parse('every week starting this wednesday until May 13th')
  #=> {:next=>2010-05-12 12:00:00 -0400, :expression=>"week", :starting=>2010-05-12 12:00:00 -0400, :until=>2010-05-13 12:00:00 -0400}

Tickle.parse('every week starting this wednesday ends May 13th')
  #=> {:next=>2010-05-12 12:00:00 -0400, :expression=>"week", :starting=>2010-05-12 12:00:00 -0400, :until=>2010-05-13 12:00:00 -0400}

Tickle.parse('every week starting this wednesday ending May 13th')
  #=> {:next=>2010-05-12 12:00:00 -0400, :expression=>"week", :starting=>2010-05-12 12:00:00 -0400, :until=>2010-05-13 12:00:00 -0400}

OPTIONS HASH

Tickle.parse('May 1st 2020', {:next_only=>true})
  #=> 2020-05-01 00:00:00 -0400

Tickle.parse('3 days', {:start=>2010-05-09 20:57:36 -0400})
  #=> {:next=>2010-05-12 20:57:36 -0400, :expression=>"3 days", :starting=>2010-05-09 20:57:36 -0400, :until=>nil}

Tickle.parse('3 weeks', {:start=>2010-05-09 20:57:36 -0400})
  #=> {:next=>2010-05-30 20:57:36 -0400, :expression=>"3 weeks", :starting=>2010-05-09 20:57:36 -0400, :until=>nil}

Tickle.parse('3 months', {:start=>2010-05-09 20:57:36 -0400})
  #=> {:next=>2010-08-09 20:57:36 -0400, :expression=>"3 months", :starting=>2010-05-09 20:57:36 -0400, :until=>nil}

Tickle.parse('3 years', {:start=>2010-05-09 20:57:36 -0400})
  #=> {:next=>2013-05-09 20:57:36 -0400, :expression=>"3 years", :starting=>2010-05-09 20:57:36 -0400, :until=>nil}

Tickle.parse('3 days', {:start=>2010-05-09 20:57:36 -0400, :until=>2010-10-09 00:00:00 -0400})
  #=> {:next=>2010-05-12 20:57:36 -0400, :expression=>"3 days", :starting=>2010-05-09 20:57:36 -0400, :until=>2010-10-09 00:00:00 -0400}

Tickle.parse('3 weeks', {:start=>2010-05-09 20:57:36 -0400, :until=>2010-10-09 00:00:00 -0400})
  #=> {:next=>2010-05-30 20:57:36 -0400, :expression=>"3 weeks", :starting=>2010-05-09 20:57:36 -0400, :until=>2010-10-09 00:00:00 -0400}

Tickle.parse('3 months', {:until=>2010-10-09 00:00:00 -0400})
  #=> {:next=>2010-08-09 20:57:36 -0400, :expression=>"3 months", :starting=>2010-05-09 20:57:36 -0400, :until=>2010-10-09 00:00:00 -0400}

US HOLIDAYS

Tickle.parse('New Years Day', {:start=>#<Date: 2020-01-01 (4917699/2,0,2299161)>, :now=>#<Date: 2020-01-01 (4917699/2,0,2299161)>})
  #=> {:next=>2021-01-01 12:00:00 -0500, :expression=>"january 1, 2021", :starting=>2020-01-01 00:00:00 -0500, :until=>nil}

Tickle.parse('Inauguration', {:start=>#<Date: 2020-01-01 (4917699/2,0,2299161)>, :now=>#<Date: 2020-01-01 (4917699/2,0,2299161)>})
  #=> {:next=>2020-01-20 12:00:00 -0500, :expression=>"january 20", :starting=>2020-01-01 00:00:00 -0500, :until=>nil}

Tickle.parse('Martin Luther King Day', {:start=>#<Date: 2020-01-01 (4917699/2,0,2299161)>, :now=>#<Date: 2020-01-01 (4917699/2,0,2299161)>})
  #=> {:next=>2020-01-20 12:00:00 -0500, :expression=>"third monday in january", :starting=>2020-01-01 00:00:00 -0500, :until=>nil}

Tickle.parse('MLK', {:start=>#<Date: 2020-01-01 (4917699/2,0,2299161)>, :now=>#<Date: 2020-01-01 (4917699/2,0,2299161)>})
  #=> {:next=>2020-01-20 12:00:00 -0500, :expression=>"third monday in january", :starting=>2020-01-01 00:00:00 -0500, :until=>nil}

Tickle.parse('Presidents Day', {:start=>#<Date: 2020-01-01 (4917699/2,0,2299161)>, :now=>#<Date: 2020-01-01 (4917699/2,0,2299161)>})
  #=> {:next=>2020-02-17 12:00:00 -0500, :expression=>"third monday in february", :starting=>2020-01-01 00:00:00 -0500, :until=>nil}

Tickle.parse('Memorial Day', {:start=>#<Date: 2020-01-01 (4917699/2,0,2299161)>, :now=>#<Date: 2020-01-01 (4917699/2,0,2299161)>})
  #=> {:next=>2020-05-25 12:00:00 -0400, :expression=>"4th monday of may", :starting=>2020-01-01 00:00:00 -0500, :until=>nil}

Tickle.parse('Independence Day', {:start=>#<Date: 2020-01-01 (4917699/2,0,2299161)>, :now=>#<Date: 2020-01-01 (4917699/2,0,2299161)>})
  #=> {:next=>2020-07-04 12:00:00 -0400, :expression=>"july 4, 2020", :starting=>2020-01-01 00:00:00 -0500, :until=>nil}

Tickle.parse('Labor Day', {:start=>#<Date: 2020-01-01 (4917699/2,0,2299161)>, :now=>#<Date: 2020-01-01 (4917699/2,0,2299161)>})
  #=> {:next=>2020-09-07 12:00:00 -0400, :expression=>"first monday in september", :starting=>2020-01-01 00:00:00 -0500, :until=>nil}

Tickle.parse('Columbus Day', {:start=>#<Date: 2020-01-01 (4917699/2,0,2299161)>, :now=>#<Date: 2020-01-01 (4917699/2,0,2299161)>})
  #=> {:next=>2020-10-12 12:00:00 -0400, :expression=>"second monday in october", :starting=>2020-01-01 00:00:00 -0500, :until=>nil}

Tickle.parse('Veterans Day', {:start=>#<Date: 2020-01-01 (4917699/2,0,2299161)>, :now=>#<Date: 2020-01-01 (4917699/2,0,2299161)>})
  #=> {:next=>2020-11-11 12:00:00 -0500, :expression=>"november 11, 2020", :starting=>2020-01-01 00:00:00 -0500, :until=>nil}

Tickle.parse('Christmas', {:start=>#<Date: 2020-01-01 (4917699/2,0,2299161)>, :now=>#<Date: 2020-01-01 (4917699/2,0,2299161)>})
  #=> {:next=>2020-12-25 12:00:00 -0500, :expression=>"december 25, 2020", :starting=>2020-01-01 00:00:00 -0500, :until=>nil}

Tickle.parse('Super Bowl Sunday', {:start=>#<Date: 2020-01-01 (4917699/2,0,2299161)>, :now=>#<Date: 2020-01-01 (4917699/2,0,2299161)>})
  #=> {:next=>2020-02-02 12:00:00 -0500, :expression=>"first sunday in february", :starting=>2020-01-01 00:00:00 -0500, :until=>nil}

Tickle.parse('Groundhog Day', {:start=>#<Date: 2020-01-01 (4917699/2,0,2299161)>, :now=>#<Date: 2020-01-01 (4917699/2,0,2299161)>})
  #=> {:next=>2020-02-02 12:00:00 -0500, :expression=>"february 2, 2020", :starting=>2020-01-01 00:00:00 -0500, :until=>nil}

Tickle.parse('Valentines Day', {:start=>#<Date: 2020-01-01 (4917699/2,0,2299161)>, :now=>#<Date: 2020-01-01 (4917699/2,0,2299161)>})
  #=> {:next=>2020-02-14 12:00:00 -0500, :expression=>"february 14, 2020", :starting=>2020-01-01 00:00:00 -0500, :until=>nil}

Tickle.parse('Saint Patricks day', {:start=>#<Date: 2020-01-01 (4917699/2,0,2299161)>, :now=>#<Date: 2020-01-01 (4917699/2,0,2299161)>})
  #=> {:next=>2020-03-17 12:00:00 -0400, :expression=>"march 17, 2020", :starting=>2020-01-01 00:00:00 -0500, :until=>nil}

Tickle.parse('April Fools Day', {:start=>#<Date: 2020-01-01 (4917699/2,0,2299161)>, :now=>#<Date: 2020-01-01 (4917699/2,0,2299161)>})
  #=> {:next=>2020-04-01 12:00:00 -0400, :expression=>"april 1, 2020", :starting=>2020-01-01 00:00:00 -0500, :until=>nil}

Tickle.parse('Earth Day', {:start=>#<Date: 2020-01-01 (4917699/2,0,2299161)>, :now=>#<Date: 2020-01-01 (4917699/2,0,2299161)>})
  #=> {:next=>2020-04-22 12:00:00 -0400, :expression=>"april 22, 2020", :starting=>2020-01-01 00:00:00 -0500, :until=>nil}

Tickle.parse('Arbor Day', {:start=>#<Date: 2020-01-01 (4917699/2,0,2299161)>, :now=>#<Date: 2020-01-01 (4917699/2,0,2299161)>})
  #=> {:next=>2020-04-24 12:00:00 -0400, :expression=>"fourth friday in april", :starting=>2020-01-01 00:00:00 -0500, :until=>nil}

Tickle.parse('Cinco De Mayo', {:start=>#<Date: 2020-01-01 (4917699/2,0,2299161)>, :now=>#<Date: 2020-01-01 (4917699/2,0,2299161)>})
  #=> {:next=>2020-05-05 12:00:00 -0400, :expression=>"may 5, 2020", :starting=>2020-01-01 00:00:00 -0500, :until=>nil}

Tickle.parse('Mothers Day', {:start=>#<Date: 2020-01-01 (4917699/2,0,2299161)>, :now=>#<Date: 2020-01-01 (4917699/2,0,2299161)>})
  #=> {:next=>2020-05-10 12:00:00 -0400, :expression=>"second sunday in may", :starting=>2020-01-01 00:00:00 -0500, :until=>nil}

Tickle.parse('Flag Day', {:start=>#<Date: 2020-01-01 (4917699/2,0,2299161)>, :now=>#<Date: 2020-01-01 (4917699/2,0,2299161)>})
  #=> {:next=>2020-06-14 12:00:00 -0400, :expression=>"june 14, 2020", :starting=>2020-01-01 00:00:00 -0500, :until=>nil}

Tickle.parse('Fathers Day', {:start=>#<Date: 2020-01-01 (4917699/2,0,2299161)>, :now=>#<Date: 2020-01-01 (4917699/2,0,2299161)>})
  #=> {:next=>2020-06-21 12:00:00 -0400, :expression=>"third sunday in june", :starting=>2020-01-01 00:00:00 -0500, :until=>nil}

Tickle.parse('Halloween', {:start=>#<Date: 2020-01-01 (4917699/2,0,2299161)>, :now=>#<Date: 2020-01-01 (4917699/2,0,2299161)>})
  #=> {:next=>2020-10-31 12:00:00 -0400, :expression=>"october 31, 2020", :starting=>2020-01-01 00:00:00 -0500, :until=>nil}

Tickle.parse('Christmas Day', {:start=>#<Date: 2020-01-01 (4917699/2,0,2299161)>, :now=>#<Date: 2020-01-01 (4917699/2,0,2299161)>})
  #=> {:next=>2020-12-25 12:00:00 -0500, :expression=>"december 25, 2020", :starting=>2020-01-01 00:00:00 -0500, :until=>nil}

Tickle.parse('Christmas Eve', {:start=>#<Date: 2020-01-01 (4917699/2,0,2299161)>, :now=>#<Date: 2020-01-01 (4917699/2,0,2299161)>})
  #=> {:next=>2020-12-24 12:00:00 -0500, :expression=>"december 24, 2020", :starting=>2020-01-01 00:00:00 -0500, :until=>nil}

Tickle.parse('Kwanzaa', {:start=>#<Date: 2020-01-01 (4917699/2,0,2299161)>, :now=>#<Date: 2020-01-01 (4917699/2,0,2299161)>})
  #=> {:next=>2021-01-01 12:00:00 -0500, :expression=>"january 1, 2021", :starting=>2020-01-01 00:00:00 -0500, :until=>nil}

USING IN APP

To use in your app, we recommend adding two attributes to your database model:

  • next_occurrence
  • tickle_expression

Then call Tickle.parse("date expression goes here") when you need to and save the results accordingly. In your code, each day, simply check to see if today is >= next_occurrence and, if so, run your block.

After it completes, call next_occurrence = Tickle.parse(tickle_expression) again to update the next occurrence of the event.

INSTALLATION

Tickle can be installed via RubyGems:

gem install tickle

or if you're using Bundler, add this to the Gemfile:

gem "tickle"

DEPENDENCIES

Chronic gem:

gem install chronic

thoughtbot's shoulda:

gem install shoulda

or just run bundle install.

LIMITATIONS

Currently, Tickle only works for day intervals but feel free to fork and add time-based interval support or send me a note if you really want me to add it.

CONTRIBUTING

Fork it, create a new branch for your changes, and send in a pull request.

  • Only tested code gets in.
  • Document it.
  • If you want to work on something but aren't sure whether it'll get in, create a new branch and open a pull request before you've done any code. That will open an issue and we can discuss it.
  • Do not mess with the Rakefile, version, or history (if you want to have your own version, that is fine but do it on a separate branch from the one I'm going to merge.)

TESTING

Tickle comes with a full testing suite for simple, complex, options hash and invalid arguments.

You also have some command line options:

  • --v verbose output like the examples above
  • --d debug output showing the guts of a date expression

CREDIT

The original work on the library was done by Joshua Lippiner a.k.a. Noctivity.

HUGE shout-out to both the creator of Chronic, Tom Preston-Werner as well as Brian Brownling who maintains a Github version at github.com/mojombo/chronic.

As always, BIG shout-out to the RVM Master himself, Wayne Seguin, for putting up with me and Ruby from day one. Ask Wayne to make you some ciabatta bread next time you see him.

LICENCE

See the LICENCE file.

tickle's People

Contributors

bjonord avatar dan335 avatar gampleman avatar jessealdridge avatar noctivityinc avatar yb66 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

tickle's Issues

Error undefined method `year'

I am trying to run a .rb file with the provided examples, but I get this error:

/Users/UserName/.rvm/rubies/ruby-2.4.0/lib/ruby/gems/2.4.0/gems/tickle-1.1.0/lib/tickle/tickle.rb:265:in next_appropriate_year': undefined method year' for nil:NilClass (NoMethodError)

Does it have to do with ruby version?

Every work day

Is there an option for saying "every work day" (i.e. from monday to friday) ? I looked for it but I couldn't find any

Unnecessarily limits to future dates only

Took out the raises in tickle/tickle.rb and it fixed it for me - this may not be desired behavior for others, but it works perfectly fine to use past dates for most cases.

What is the best way to add our own patterns?

For example, when we use a month and day format common in the US like 4/3 which represents April 3, tickle doesn't seem to catch that. Is there a method we could use to add a few more patterns?

To make things a bit more complex, outside the US a lot of countries have the day and month backwards like 4/3 would represent March 4th. Any ideas on how we could accomodate those conditions?

the start date cannot occur after the end date

In 2.0.0.rc1

Today being a Sunday, this

Tickle.parse("every other week starting this Sunday")

would generate this error:

/Users/UserName/.rvm/gems/ruby-2.4.0/gems/tickle-2.0.0.rc1/lib/tickle/tickle.rb:54:in `_parse': the start date (2017-03-19) cannot occur after the end date (Tickle::InvalidDateExpression)
	from /Users/UserName/.rvm/gems/ruby-2.4.0/gems/tickle-2.0.0.rc1/lib/tickle.rb:42:in `parse'
	from /Users/UserName/Desktop/file.rb:26:in `block in <top (required)>'
	from /Users/UserName/Desktop/file.rb:25:in `each'
	from /Users/UserName/Desktop/file.rb:25:in `<top (required)>'
	from -e:1:in `load'
	from -e:1:in `<main>'

Process finished with exit code 1

argument error

From the docs:

> Tickle.parse('day')
>   #=> {:next=>2010-05-10 20:57:36 -0400, :expression=>"day", :starting=>2010-05-09 20:57:36 -0400, :until=>nil}

My console:

2.2.1 :013 > Tickle.parse('day')
ArgumentError: argument out of range

Was working fine, now failing.

"Every day at 9am" stopped working in v2.0.0rc

Not sure if this is being worked on or if the incantation is going to be dropped?

> Tickle.parse('every day at 9am')
Traceback (most recent call last):
        1: from (irb):12
ArgumentError (argument out of range)

This works as before:

> Tickle.parse('every tuesday')
=> {:next=>2018-11-06 12:00:00 +0000, :expression=>"every tuesday", :starting=>2018-10-31 09:28:52 +0000, :until=>nil}

Let me know if I can lend a hand.

Cheers

every year, every christmas, ..

Using the pry debugging branch here are just a few examples that Chronic can't parse and can't be Tickled

require 'tickle'

["every year on the second Wednesday of April until 2030",

"every Chritsmas until 2100",

 "",

"every month starting next July until 2022",
].each {|s|
  time = Tickle.parse(s)
  print s, " --> date: ", time
  puts
}

First wed each month

I don't get it

require 'tickle'

d = Date.today
10.times {|i|
    d = Tickle.parse('1st wed each month', {:start => d })[:next]
    puts d
    d += 24*60*60
}

prints

2022-04-06 12:00:00 +0200
2022-04-07 12:00:00 +0200
2022-04-08 12:00:00 +0200
2022-04-09 12:00:00 +0200
2022-04-10 12:00:00 +0200
2022-04-11 12:00:00 +0200
2022-04-12 12:00:00 +0200
2022-04-13 12:00:00 +0200
2022-04-14 12:00:00 +0200
2022-04-15 12:00:00 +0200

but it should step over months, shouldn't it?

daily versus every day at a time have different behaviors

2.2.1 :023 > t = Tickle.parse('every day starting today at 11am')
 => {:next=>2018-05-06 11:00:00 +0000, :expression=>"day", :starting=>2018-05-06 11:00:00 +0000, :until=>nil} 
2.2.1 :024 > t = Tickle.parse('daily starting today at 11am')
 => {:next=>2017-05-07 18:18:41 +0000, :expression=>"daily starting today at 11am", :starting=>2017-05-06 18:18:41 +0000, :until=>nil} 

tickle's next occurrence is next year when starting today

[6] pry(#<Event>)> self[:tickle_expression]
=> "every day starting today at 11:30 am"
[7] pry(#<Event>)> Time.now
=> 2017-05-06 18:26:05 +0000
[8] pry(#<Event>)> t = Tickle.parse(self[:tickle_expression])
=> {:next=>2018-05-06 11:30:00 +0000, :expression=>"day", :starting=>2018-05-06 11:30:00 +0000, :until=>nil}

tickle changed an instance variable?

Before I use @recurring_natural_language, the value is 'every day'
After that instance variable is used as a parameter for Tickle, it changes to 'day'....

[4] pry(#)> @recurring_natural_language => "every day" [5] pry(#)> next

From: /home/nitrous/code/site/iron.io/refactor_schedule_kaya.rb @ line 150 ScheduleKaya#add_next_occurrence:

143:   def add_next_occurrence #need to REFACTOR for NON-DAILY
144: 
145: binding.pry
146:     start = Chronic.parse("today at #{@time_string}")
147: 
148:     tickle = Tickle.parse(@recurring_natural_language, {:start => start})
149: 

=> 150: @next_occurrence = tickle[:next]
151:
152:
153: end

[5] pry(#)> @recurring_natural_language
=> "day"

Adding synonymous expression

Hey man

If you do Tickle.parse('day after tomorrow') it parses it as tomorrow.
But if you do Tickle.parse('2 days from now') it works just fine.

Was wondering, if there is some way to add synonymous expressions, training the parser what expressions can be similar.
Or anyway to get this to work?

The :now option doesn't seem to work very well

I have an app that supports cancelling the next occurrence of an event, but for this we need to calculate the next occurrence of the event for an event in the future, i.e.:

Time.now 
# => 2015-10-05 18:13:15 +0100
Tickle.parse('monday', now: Time.now)
# => {:next=>2015-10-12 12:00:00 +0100, :expression=>"monday", :starting=>2015-10-05 18:13:18 +0100, :until=>nil}
Tickle.parse('monday', now: Time.now + 1.day)
# => {:next=>2015-10-12 12:00:00 +0100, :expression=>"monday", :starting=>2015-10-05 18:13:26 +0100, :until=>nil}
Tickle.parse('monday', now: Time.parse('2015-10-12 12:00:00 +0100'))
# => {:next=>2015-10-12 12:00:00 +0100, :expression=>"monday", :starting=>2015-10-05 18:13:53 +0100, :until=>nil}
Tickle.parse('monday', now: Time.parse('2015-10-13 12:00:00 +0100'))
# => {:next=>2015-10-12 12:00:00 +0100, :expression=>"monday", :starting=>2015-10-05 18:14:37 +0100, :until=>nil}

how do I set/know the Time Zone

I want to be able to explicitly check for the next occurrence based on a time-zone.

It looks like the time-zone for next_occurrence is what the time-zone was explicitly set when it was set.

It seems like, if I have users from different time-zones, I would need to always normalize the next_occurrence to something like UTC, so all are set at UTC.

Is that done basically by converting the intended time for the user (they may want every day at 10:00AM PST), to convert that time to UTC and use that as the starting time?

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.