Parse "X years and Y weeks ago" alike strings in Ruby - ruby

Is there a generic parser to parse "... ago" strings and turn them into DateTime objects?
Possible sentences are:
1 year|#count years (and 1 week|#count weeks) ago
1 week|#count weeks (and 1 day|#count days) ago
1 day|#count days (and 1 hour|#count hours) ago
1 hour|#count hours ago (and 1 min|#count min) ago
1 min|#count min ago (and 1 sec|#count sec) ago
1 sec|#count sec ago
So its either a combination of two (Foo and Bar ago) or only one (Foo ago). And it can be singular or plural.
Ruby's DateTime::parse() cannot handle this, nor can DateTime::strptime().
I am hoping for a gem or a snippet somewhere that handles this.
Else I will have to create my own parser, in which case a pointer to how-to-create own DateTime Parsers would be very welcome.
Sidenote: For Future Reference: These are the timestrings generated by Drupals Format Interval

The Chronic gem may be what you're looking for.
require 'chronic'
Chronic.parse "2 days ago"
# => 2011-06-14 14:13:59 -0700
Per #injekt (one of Chronic's maintainers), you can handle "1 year and 1 week ago" like this:
str = "one year and 1 week ago"
chunks = str.split('and')
Chronic.parse(chunks[1], :now => Chronic.parse(chunks[0] + 'ago'))
#=> 2010-06-09 14:29:37 -0700

Related

How to refactor case..when in Ruby [closed]

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 5 years ago.
Improve this question
def readable
uptime = (Time.now - self).to_i
case uptime
when 0 then 'just now'
when 1 then 'uptime second ago'
when 2..59 then uptime.to_s + ' seconds ago'
when 60..119 then 'uptime minute ago' # 120 = 2 minutes
when 120..3540 then (uptime / 60).to_i.to_s + ' minutes ago'
when 3541..7100 then 'an hour ago' # 3600 = 1 hour
when 7101..82_800 then ((uptime + 99) / 3600).to_i.to_s + ' hours ago'
when 82_801..172_000 then 'uptime day ago' # 86400 = 1 day
else ((uptime + 800) / 86_400).to_i.to_s + ' days ago'
end
end
Linter speaks of the following mistakes, how can this be fixed?
FeatureEnvy: Time#readable refers to 'uptime' more than self (maybe move it to another class?) [https://github.com/troessner/reek/blob/master/docs/Feature-Envy.md]
TooManyStatements: Time#readable has approx 10 statements [https://github.com/troessner/reek/blob/master/docs/Too-Many-Statements.md]
Assignment Branch Condition size for readable is too
high. [20.64/15]
Cyclomatic complexity for readable is too high. [9/6]
Method has too many lines. [12/10]
Take a look at time_ago_in_words and feel free to use it or its code.
About code metrics - your code is pretty simple, you should extract only uptime method.

I need validate some tables for past dates, in the past 5 Saturdays from today using Chronic gem

I've installed the Chronic gem and while
I can get Chronic.parse('Saturday', :context => :past) to return last Saturday's date but not
'5 last saturdays' returns 'nil'
I would like it to return in this format: strftime "%m%d%Y%H%M"
I also can't tag on any methods like .exists? or should ==true so I can use it to validate that those reports were run
I would just like to verify that 02/11/2017 02/04/2017 01/28/2017 01/28/2017 01/21/2017 appear as text on the page, but since they will change every week i need logic that will return last 5 Saturdays from now
Use "weeks ago" to get the Saturday of a previous week:
Chronic.parse('1 week ago Saturday')
#=> "2017-02-11 12:00:00 -0500"
Chronic.parse('2 weeks ago Saturday')
#=> "2017-02-04 12:00:00 -0500"
Chronic.parse('3 weeks ago Saturday')
#=> "2017-01-28 12:00:00 -0500"
Chronic.parse('4 weeks ago Saturday')
#=> "2017-01-21 12:00:00 -0500"
Chronic.parse('5 weeks ago Saturday')
#=> "2017-01-14 12:00:00 -0500"
You can format the value returned using #strftime:
Chronic.parse('1 week ago Saturday').strftime("%m%d%Y%H%M")
#=> "021120171200"

Time ago in words convert into system date-time

Trying to convert strings like 9 weeks ago, 1 year, 6 months ago, 20 hours ago into a ruby time object like Tue, 10 Mar 2015 12:06:15 PDT -07:00.
I've been doing this:
eval("10 days ago".gsub(' ', '.'))
This works fine, but for strings like 1 year, 6 months ago blows up.
I just need to do comparisons like:
eval("10 days ago".gsub(' ', '.')) < (Time.now - 7.days)
I'm using sinatra so no fancy rails helpers.
Please never use eval in production code..
Converting from timeago notation would be quite complex and resource intensive.
However, this way seems the least error prone: It will convert a string like "5 seconds ago" to "5S" and use mapping to find what it means in time, after which it will subtract that time from the current time.
The parse string is dynamically built so it can accomodate most every timeago notation.
require('date')
mapping = {"D"=> "%d","W"=>"%U","H"=>"%T","Y"=>"%Y","M"=>"%m","S"=>"%S"}
timerel = "1 year, 6 months ago".split(",").map { |n| n.gsub(/\s+/, "").upcase()[0,2].split('')}
Date.strptime(
timerel.map {|n| n[0]}.join(" "),
timerel.map {|n| mapping[n[1]]}.join(" ")
)
date = Date.new(0) + (Date.today - Date.strptime(timerel.map {|n| n[0]}.join(" "), timerel.map {|n| mapping[n[1]]}.join(" ")))
=> #<Date: 2014-10-10 ((2456941j,0s,0n),+0s,2299161j)>
It goes without saying that is very error prone. Use at your own risk:
def parse(date:)
eval(date.gsub(/ ?(,|and) ?/, '+').tr(' ', '.').gsub(/^(.*)(\.ago)$/, '(\1)\2'))
end
parse(date: '1 year, 6 months ago') # => Wed, 10 Sep 2014 21:29:11 BST +01:00
parse(date: '1 year, 6 months, 3 weeks, 6 days, 9 hours and 12 seconds ago')
# => Thu, 14 Aug 2014 12:33:07 BST +01:00
The idea is to convert the original string to:
'(1.year+6.months).ago'

Get seconds from the day to Thursday 10:00 at that week

How do I get the seconds from the day to Thursday 10:00 at that week? If later than Thursday 10:00, I want to get zero. For example:
seconds = (Thursday 10:00) - Time.now
Use Chronic:
require 'chronic'
Chronic.parse('this Thursday at 10:00 am') - Time.now
#=> 98688.251918432
You can subtract two time to get difference in seconds (see docs):
require 'time'
Time.parse(end_time) - Time.parse(time)
# => 57600.0
Update
To calculate difference between two time getting two fixed time is an absolute must. You can get time for next week simply by adding numeric time difference in seconds to existing time. Here:
next_week_time = Time.parse(end_time) + (1*7*24*60*60)
Or if you are on Rails, with ActiveSupport you can simply do:
next_week_time = Time.parse(end_time) + 1.weeks
(4-Time.now.wday-1)*24*3600: get the number of days from the day morning to Thursday of the week.
Time.now.seconds_until_end_of_day: get the rest seconds of the day.
seconds = (4-Time.now.wday-1)*24*3600 + Time.now.seconds_until_end_of_day + 10*3600
seconds = seconds > 0 ? seconds : 0

NEXT_DAY in Crystal Reports

Is there anything like the Oracle "NEXT_DAY" function available in the syntax that Crystal Reports uses?
I'm trying to write a formula to output the following Monday # 9:00am if the datetime tested falls between Friday # 9:00pm and Monday # 9:00am.
So far I have
IF DAYOFWEEK ({DATETIMEFROMMYDB}) IN [7,1]
OR (DAYOFWEEK({DATETIMEFROMMYDB}) = 6 AND TIME({DATETIMEFROMMYDB}) in time(21,00,00) to time(23,59,59))
OR (DAYOFWEEK({DATETIMEFROMMYDB}) = 2 AND TIME({DATETIMEFROMMYDB}) in time(00,00,00) to time(08,59,59))
THEN ...
I know I can write seperate IF statements to do a different amount of DateAdd for each of Fri, Sat, Sun, Mon, but if I can keep it concise by lumping all of these into one I would much prefer it. I'm already going to be adding additional rules for if the datetime falls outside of business hours on the other weekdays so I want to do as much as possible to prevent this from becoming a very overgrown and ugly formula.
Since there is no CR equivalent that I know of, you can just cheat and borrow the NEXT_DAY() function from the Oracle database. You can do this by creating a SQL Expression and then entering something like:
-- SQL Expression {%NextDay}
(SELECT NEXT_DAY("MYTABLE"."MYDATETIME", 'MONDAY')
FROM DUAL)
then you could either use that directly in your formula:
IF DAYOFWEEK ({MYTABLE.MYDATETIME}) IN [7,1]
OR (DAYOFWEEK({MYTABLE.MYDATETIME}) = 6 AND TIME({MYTABLE.MYDATETIME}) in time(21,00,00) to time(23,59,59))
OR (DAYOFWEEK({MYTABLE.MYDATETIME}) = 2 AND TIME({MYTABLE.MYDATETIME) in time(00,00,00) to time(08,59,59))
THEN DateTime(date({%NextDay}),time(09,00,00))
Or, the even better way would be to just stuff ALL of the logic into the SQL Expression and do away with the formula altogether.
Considering Sunday is 1
And the first 7 is the week we want to back
7 = 1 week
14 = 2 weeks
The last Number (1) is 1 for Sunday, 2 for Monday, 3 for Tuestday
Last Sunday 1 week ago
Today - 7 + ( 7 - WEEKDAY(TODAY) )+1
Last Monday 2 weeks ago
Today - 14 + ( 7 - WEEKDAY(TODAY) )+2
So this 2 formulas give me MONDAY LAST WEEK and SUNDAY LAST WEEK.
EvaluateAfter({DATETIMEFROMMYDB}) ;
If DayOfWeek ({DATETIMEFROMMYDB}) In [crFriday,crSaturday,crSunday,crMonday]
then
IF DayOfWeek ({DATETIMEFROMMYDB}) In [crFriday]
AND TIME({DATETIMEFROMMYDB}) >= time(21,00,00)
then //your code here
Else if Not(DayOfWeek ({DATETIMEFROMMYDB}) In [crFriday] )
AND (TIME({DATETIMEFROMMYDB}) >= time(00,00,00) AND TIME({DATETIMEFROMMYDB}) <= time(23,59,59))
then //your code here
Else if DayOfWeek ({DATETIMEFROMMYDB})In [crMonday]
AND TIME({DATETIMEFROMMYDB}) < time(09,00,00)
then //your code here

Resources