How do I get a file's age in days in Ruby? - ruby

How would I get the age of a file in days in Ruby?
NOTE that I need a way to accurately get the age of a given file; this means that leap years need to be taken into account.
I need this for a program that removes files after they reach a certain age in days, such as files that are 20 days or older.
And by age, I mean the last access time of a given file, so if a file hasn't been accessed in the past 20 days or more, it gets deleted.
In Perl, I know that you can use date::calc to calculate a date in terms of days since 1 AD, and I used to have a Common-Lisp program that used the Common-Lisp implementation of date::calc, but I don't have that anymore, so I've been looking for an alternative and Ruby seems to have the required capability.

path = '/path/to/file'
(Time.now - File.stat(path).mtime).to_i / 86400.0
#=> 1.001232
Here is the implementation of my above comment, it returns a floating point number expressing the number of days passed.

I know it is an old question, but I needed the same and came up with this solution that might be helpful for others.
As the difference is in days, there is not need to directly deal with seconds.
require 'date'
age_in_days = (Date.today - File.mtime(path).to_date).to_i
if age_in_days > 20
# delete logs
end

If using Rails, you can take advantage of ActiveSupport:
if File.mtime(path) < 20.days.ago
# delete logs
end
If you aren't using Rails, Eduardo's solution above would be my pick.

Related

Syntax for Sequential Variable Names

I am struggling to compile my large dataset and am assuming syntax commands are the answer, however, I am not skilled at all with syntax. My questions are specific to what syntax commands (or other methods) I should use to create hundreds/thousands of new variable names so I do not need to do it manually.
I am working with a dataset involving intimate partner homicides and domestic violence services utilization among victims from 2012-2021 (10 years), examined monthly (120 months). Across that timeframe, I have a three variable name set (REC [number of clients who received services], CALL [number of calls for services], HOUR [number of hours advocates/employees spent providing services]) that needs to be repeated monthly Jan-Dec across 10 years 2012-2021 and 39 separate services. See below:
MonthYear_REC_ServiceName
MonthYear_CALL_ServiceName
MonthYear_HOUR_ServiceName
"Month" in the above is Jan-Dec (01-12), "Year" is 2012-2021 (12-21), and "ServiceName" would be replaced with 39 different services. As an example for the year 2017 and "Shelter" services:
0117_REC_Shelter
0117_CALL_Shelter
0117_HOUR_Shelter
0217_REC_Shelter
0217_CALL_Shelter
0217_HOUR_Shelter
0317_REC_Shelter
0317_CALL_Shelter
0317_HOUR_Shelter
.....so on and so forth until December of 2017.
To further explain: This sequential monthly order would need to be repeated for each year in the 2012-2021 timeframe for each of 39 services for which I have data. "Shelter" services are shown as an example above, but I also need the same set of variable names across 38 other service types such as group counseling, legal advocacy, economic assistance, etc.
My overall question is (sorry for the repetition)- What syntax commands would I need to input to create this MASSIVE amount of variable names/variables? I hope this makes sense to everyone in the same way it makes sense to me! Sorry for the length and thank you in advance.
Best,
Shannon H.
Assuming what you want is to create an empty dataset with all the variable names you described, this will do it:
INPUT PROGRAM.
LOOP ind = 1 to (12*10*3*39).
END CASE.
END LOOP.
END FILE.
END INPUT PROGRAM.
EXECUTE.
do repeat vr=month year set service/vl=12 10 3 39.
compute vr=mod(ind,vl).
recode vr (0=vl).
compute ind=trunc((ind-1)/vl)+1.
end repeat.
compute year=year+11.
formats all (f2).
alter type month year (a2) set (a4).
compute month = char.lpad(ltrim(month), 2, "0").
recode set (" 1"="REC")(" 2"="CALL")(" 3"="HOUR").
* I suggest at this point you use "match files" to match the service numbers here with a list of service names.
* The following code creates fictitious service names instead to demonstrate how to use them.
string serviceName (a20) vrnm (a50).
compute serviceName=concat("service", char.lpad(ltrim(string(service, f2)), 2, "0") ).
* now to create the final variable names.
compute vrnm=concat(month, year, "_", set, "_", serviceNAme).
flip NEWNAMES = vrnm.
select if CASE_LBL="".
exe.

How to get user to enter in 24 hour format in BBC Basic

I am making a program that will enable me to work out the avergae speed of something over a set distance
For this to work the user needs to input the start time and the end time.. I am not sure how you input time in a 24 hour format.
Furthermore I need to find the difference in the 2 times and then work out the speed.. which is distance/time taken.
Let's say distance was 1000 meters
I lack a bbc basic compiler but you should create some like this
print str$(secondsinday("22:50:01")-secondsinday("17:09:17"))
sub secondsinday(t$)
return val(left$(t$,2))*3600+val(mid$(t$,4,2))*60+val(right$(t$,2))
end sub
I saw some bbc basic examples and the formula should be the same, only the function syntax is diffrent (I'll try and convert it after some research)

How can I QUICKLY get a string from one of the first couple lines of a long CSV at a remote URL?

I'm working on an assignment where I retrieve several stock prices from online, using Yahoo's stock price system. Unfortunately, the Yahoo API I'm required to use returns a .csv file that apparently contains a line for every single day that stock has been traded, which is at least 5 thousand lines for the stocks I'm working with, and over 10 thousand lines for some of them (example).
I only care about the current price, though, which is in the second line.
I'm currently doing this:
require 'open-uri'
def get_ticker_price(stock)
open("http://ichart.finance.yahoo.com/table.csv?s=#{stock}") do |io|
io.read.split(',')[10].to_f
end
end
…but it's really slow.
Is all the delay coming from getting the file, or is there some from the way I'm handling it? Is io.read reading the entire file?
Is there a way to download only the first couple lines from the Yahoo CSV file?
If the answers to questions 1 & 2 don't render this one irrelevant, is there a better way to process it that doesn't require looking at the entire file (assuming that's what io.read is doing)?
You can use query string parameters to reduce the data to the current date, by using date range parameters.
example for MO on 7/13/2012: (start/end month starts w/ a zero-index, { 00 - 11 } ).
http://ichart.finance.yahoo.com/table.csv?s=MO&a=06&b=13&c=2012&d=6&e=13&f=2012&g=d
api description here:
http://etraderzone.com/free-scripts/47-historical-quotes-yahoo.html

Time manipulation in ruby

I want to create a DateTime instance that lies 20 minutes and 10 seconds in the future.
I tried around with Time and DateTime in irb, but can't seem to figure out a way that really makes sense. I can only add days to DateTime objects and only add seconds to the Time objects.
Isn't there a better way than to always convert the time I want to add into seconds?
A Time is a number of seconds since an epoch whereas a DateTime is a number of days since an epoch which is why adding 1 to a DateTime adds a whole day. You can however add fractions of a day, for example
d = DateTime.now
d + Rational(10, 86400)
Will add 10 seconds to d (since there are 86400 seconds in a day).
If you are using Rails, Active Support adds some helper methods and you can do
d + 20.minutes + 10.seconds
Which will do the right thing is d is a DateTime or a Time. You can use Active Support on its own, and these days you can pull in just the bits you need. I seem to recall that this stuff is in activesupport/duration. I believe there are a few other gems that offer help with time handling too.
Assuming you have required Active Support or you're working in a Rails project. A very simple and readable way to do this in Ruby is:
DateTime + 5.minutes
Time + 5.minutes
Also works with seconds, hours, days, weeks, months, years.
Just use the Active Support Time extensions. They are very convenient and less error prone than trying to do this by hand. You can import just the module you need:
# gem 'activesupport'
require 'active_support/core_ext/numeric/time.rb'
DateTime.now + 20.minutes
N.B: Yes, this goes against the StackOverflow party line of staying away from 3rd party libraries, but you shouldn't avoid using libraries when they are practically standard, reduce your risk significantly, and provide better code clarity.
Pure Ruby (no Rails)
class Numeric
def minutes; self/1440.0 end
alias :minute :minutes
def seconds; self/86400.0 end
alias :second :seconds
end
Where 1440 is the number of minutes and 86400 is the number of seconds in a day.
Based on how Rails does.
Then you can just let the magic happen:
d + 20.minutes + 10.seconds
Source: https://github.com/rails/rails/blob/v6.0.3.1/activesupport/lib/active_support/core_ext/numeric/time.rb
As noted above, you can add seconds to a Time object, so if you call to_time on a DateTime object, you can add seconds to it:
DateTime.strptime("11/19/2019 18:50","%m/%d/%Y %H:%M") + 1 => adds a day
(DateTime.strptime("11/19/2019 18:50","%m/%d/%Y %H:%M").to_time) +1 => adds a second
This doesn't require adding gems.

Yearless Ruby dates?

Is there a way to represent dates like 12/25 without year information? I'm thinking of just using an array of [month, year] unless there is a better way.
You could use the Date class and hard set the year to a leap year (so that you could represent 2/29 if you wanted). This would be convenient if you needed to perform 'distance' calculations between two dates (assuming that you didn't need to wrap across year boundaries and that you didn't care about the off-by-one day answers you'd get when crossing 2/29 incorrectly for some years).
It might also be convenient because you could use #strftime to display the date as (for example) "Mar-3" if you wanted.
Depending on the usage, though, I think I would probably represent them explicitly, either in a paired array or something like YearlessDate = Struct.new(:month,:day). That way you're not tempted to make mistakes like those mentioned above.
However, I've never had a date that wasn't actually associated with a year. Assuming this is the case for you, then #SeanHill's answer is best: keep the year info but don't display it to the user when it's not appropriate.
You would use the strftime function from the Time class.
time = Time.now
time.strftime("%m/%d")
While #Phrogz answer makes perfect sense, it has a downside:
YearlessDate = Struct.new(:month,:day)
yearless_date = YearlessDate.new(5, 8)
This interface is prone to MM, DD versus DD, MM confusion.
You might want to use Date instead and consider the year 0 as "yearless date" (provided you're not a historian dealing with real dates around bc/ad of course).
The year 0 is a leap year and therefore accommodates every possible day/month duple:
Date.parse("0000-02-29").leap? #=> true
If you want to make this convention air tight, just define your own class around it, here's a minimalistic example:
class YearlessDate < Date
private :year
end
The most "correct" way to represent a date without a year is as a Fixnum between 001 and 365. You can do comparisons on them without having to turn it into a date, and can easily create a date for a given year as needed using Date.ordinal

Resources