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

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.

Related

MetaTrader 4 / MQL4 Time is off by -5 hours but only when using epoc time

I'm working with MetaTrader4 running in WINE in Ubuntu 16.04. I have a simple inline function that saves the time and various other info to a file using this line:
FileWrite(data_filehandle, "sys_time:" + (string)TimeLocal() + "." + StringFormat("%06lu", usec_instance) + ", sym:" + (string)Symbol() + ", tick_time:" + (string)last_tick.time + ", ask:" + (string)last_tick.ask + ", bid:" + (string)last_tick.bid);
Using the directive:
#property strict
will cause it to output the time in a Date Time format.
Removing that directive will cause it to output time in epoc format.
When it uses Date Time format ( by using '#property strict' ), the time is correct.
It outputs:
sys_time:2020.01.21 07:38:02.994394, sym:EURUSD, tick_time:2020.01.21 14:38:03, ask:1.1104, bid:1.1103
This matches my system time correctly.
Now if I remove '#property strict' to switch to epoc time
It outputs:
sys_time:1579592538.630395, sym:EURUSD, tick_time:1579617738, ask:1.1105, bid:1.11041
my local time is:
$ date '+%s'
1579610544
$ date '+%Z %z'
EST -0500
my time: 1579610544 - MT4 LocalTime: 1579592538 = 18006 seconds (Which is 5 hours and 6 sec behind me)
Any idea on what could be causing this?
I might be slightly less confused if it were +5 hours because that would be GMT.
But it's -5 hours which is Hawaii.
Also... why is the time correct in one format, but not in the other format?
Additional info
I ran some more tests using additional MQL4 functions. I had them constantly pump out their results to my text file. I then quickly whipped together a BASH script to check out the results. I found the following:
Using this code in MT4
FileWrite(data_filehandle, "TimeDaylightSavings(): " + (string)TimeDaylightSavings());
FileWrite(data_filehandle,"TimeLocal(): "+(string)TimeLocal());
FileWrite(data_filehandle,"TimeGMTOffset(): "+(string)TimeGMTOffset());
FileWrite(data_filehandle,"TimeGMT(): "+(string)TimeGMT()+"\n\n");
Gave this output to my text file ( one fresh record about every second ):
TimeDaylightSavings(): 0
TimeLocal(): 1579601184
TimeGMTOffset(): 18000
TimeGMT(): 1579619184
I whipped up this BASH script to scan and check the results in real time:
#!/bin/bash
IFS=$'\n'$'\b';
while true
do
my_time=$(date);
my_epoc=$(date '+%s');
my_record="$( cat EURUSD_price_data.txt| dos2unix | tail -5 )";
mt4_time_local=$( echo "$my_record" | grep -w 'TimeLocal' );
echo "Reading line: $mt4_time_local";
mt4_time_local=$(echo $mt4_time_local | awk '{print $2}' );
echo "My time: $my_time -- My epoc: $my_epoc -- MT4_TimeLocal epoc: $mt4_time_local -- Difference: $(( $my_epoc - $mt4_time_local ))";
mt4_time_GMT=$( echo "$my_record" | grep -w 'TimeGMT' );
echo "Reading line: $mt4_time_GMT";
mt4_time_GMT=$(echo $mt4_time_GMT | awk '{print $2}' );
echo "My time: $my_time -- My epoc: $my_epoc -- MT4_TimeGMT epoc: $mt4_time_GMT -- Difference: $(( $my_epoc - $mt4_time_GMT ))";
echo "";
sleep 1;
done
and got this result:
Reading line: TimeLocal(): 1579601184
My time: Tue Jan 21 10:06:25 EST 2020 -- My epoc: 1579619185 -- MT4_TimeLocal epoc: 1579601184 -- Difference: 18001
Reading line: TimeGMT(): 1579619184
My time: Tue Jan 21 10:06:25 EST 2020 -- My epoc: 1579619185 -- MT4_TimeGMT epoc: 1579619184 -- Difference: 1
Now if I add '#property strict' to switch back to Date Time format I get:
TimeDaylightSavings(): 0
TimeLocal(): 2020.01.21 10:23:56
TimeGMTOffset(): 18000
TimeGMT(): 2020.01.21 15:23:56
My system time:
$ date
Tue Jan 21 10:23:57 EST 2020
Conclusion
For some reason when getting epoc time TimeLocal() gives the wrong time (Hawaiian time for some reason ) , but surprisingly TimeGMT() gives the correct time, even though I am in the EST timezone.
Using the exact same code and set up, when getting the time in Date Time format ( using the '#property strict' directive ) the situation is reversed. TimeLocal() gives the correct time and TimeGMT() gives the wrong time ( but at least it gives correct GMT time )
Is this a bug in MT4, or is there something going on behind the scenes that I haven't fully understood yet?
Q : Any idea on what could be causing this?
The #property strict is a compiler-phase kill-switch, which changes lots of details how the MQL4, syntactically correct, compositions will get understood in either { "old" | "new" }-fashion
( not reading this part of the documentation each time after MT4 IDE update may and will surprise you, so better re-read it always after each and every update )
"Old"-MQL4 used a int32 for datetime internal storage, "New"-MQL4.56789… uses int64, so any roll-overs are way farther.
FileWrite( data_filehandle, "sys_time:"
+ (string)TimeLocal() // localhost-dependent
+ "."
+ StringFormat( "%06lu", usec_instance )
+ ", sym:"
+ (string)Symbol()
+ ", tick_time:"
+ (string)last_tick.time // Fx-QUOTE-dependent
+ ", ask:"
+ (string)last_tick.ask
+ ", bid:"
+ (string)last_tick.bid
);
See TimeGMT() and TimeGMTOffset() for other built-in options.
After a lot of reading, thinking and testing I have found the answer ( though I don't know what they were thinking when they did things this way ).
The issue is right here:
TimeDaylightSavings(): 0
TimeLocal(): 1579601184 <-- should be the same as TimeGMT(). Epoc does not respect timezone
TimeGMTOffset(): 18000
TimeGMT(): 1579619184
Unix epoc time is based on an event that happened on Jan 1st 1970 00:00 UTC.
It does not respect time zones. It is supposed to be the same for everyone on Earth at all times.
You can also see from the original problem that anytime I use the:
date '+%s'
command that my system gives me a correct Unix epoc which matches the output of TimeGMT() from MT4. So the problem is not my system.
However, above we can see that TimeLocal() is not treating the epoc the way that it should. It is adjusting the unix epoc in an attempt to compensate for my time zone. This behavior is probably how it accomplishes producing the correct time when it is displayed in Date Time format. The problem is that when it is asked to produce the time in epoc format it is still doing time zone conversions, which violates that very meaning of Unix epoc time.
So, the solution ( as far as I can tell ) is to simply use TimeGMT() anytime I want a correct Unix epoc. It seems pretty crazy that I have to specifically avoid using TimeLocal() in certain cases... but I guess that's just the way it is?

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

Parse "X years and Y weeks ago" alike strings in 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

Resources