mapping date with unique id ruby on rails - ruby-on-rails

I want to write an action where I can return one quote per day from DB. my db look like this
|id|quote|
so,if I call this action today it should return the same quote throughout the day but tomorrow it should return another quote from the db. I thought of mapping the quote ids to the today's date but not sure how to do it.

You can get consecutive quotes via:
Quote.order(:id).offset(0).first
Quote.order(:id).offset(1).first
Quote.order(:id).offset(2).first
# ...
To get a quote for the current day, you can utilize Date#jd which returns the date's Julian day:
Date.parse('2020-06-01').jd #=> 2459002
Date.parse('2020-06-02').jd #=> 2459003
Date.parse('2020-06-03').jd #=> 2459004
Since you (probably) don't have that many quotes, this value needs to be transformed into a value we can pass to offset, i.e. a value between 0 and the total number of quotes. This is where the modulo operator % can help – it returns just that, looping over at the end: (e.g. for 22 quotes)
Date.parse('2020-06-01').jd % 22 #=> 18
Date.parse('2020-06-02').jd % 22 #=> 19
Date.parse('2020-06-03').jd % 22 #=> 20
Date.parse('2020-06-04').jd % 22 #=> 21
Date.parse('2020-06-05').jd % 22 #=> 0
Date.parse('2020-06-06').jd % 22 #=> 1
Putting it all together:
require 'date'
def quote_of_the_day(date = Date.current)
offset = date.jd % Quote.count
Quote.order(:id).offset(offset).first
end
Note that this runs two queries: one to get the total number of quotes and another one to get the quote for the given date. You might want to cache the result.
Also note that adding new quotes might have unexpected results because the modulo operation would return a totally different offset:
Date.parse('2020-06-03').jd % 22 #=> 20
# after adding a new Quote
Date.parse('2020-06-03').jd % 23 #=> 5
You can compensate this by adjusting the jd result accordingly:
ADJUSTMENT = 15
(Date.parse('2020-06-03').jd + ADJUSTMENT) % 23 #=> 20

Related

formatting function output similar os.date() in lua

I have a function that get current time and do some calculation that return a table. Something like this:
functuin newtime()
t1 = os.date("*t")
-- do some calculation that calculate this variable chha_ye , chha_mo , chha_da
t={}
t["ye"] = chha_ye
t["mo"] = chha_mo
t["da"] = chha_da
return t
end
Now I can get newtime().ye.
I need formatting output of this function something similar os.date()
for example, if chhaa_ye = 4 and chha_mo = 9 :
newtime("%ye")
4
newtime("%ye - %mo")
4 - 9
Default os.date do this, for example
os.date("%Y")
22
os.date("%Y - %m")
22 - 10
How should I do this?
I will provide an answer based on the comment from Egor, slightly modified to make the answer directly testable in a Lua interpreter. First, the os.date function from Lua is quite handy, it returns a hashtable with the corresponding field/values:
if format is the string "*t", then date returns a table with the following fields: year, month (1–12), day (1–31), hour (0–23), min (0–59), sec (0–61, due to leap seconds), wday (weekday, 1–7, Sunday is 1), yday (day of the year, 1–366), and isdst (daylight saving flag, a boolean). This last field may be absent if the information is not available.
We could test the function with the following code snippet:
for k,v in pairs(os.date("*t")) do
print(k,v)
end
This will print the following:
month 10
year 2022
yday 290
day 17
wday 2
isdst false
sec 48
min 34
hour 9
The gsub function, intended for string substitutions, is not limited to a single string, but could take a table as argument.
string.gsub (s, pattern, repl [, n])
If repl is a table, then the table is queried for every match, using the first capture as the key.
function my_stringformat (format)
local date_fields = os.date("*t")
local date_string = format:gsub("%%(%a+)", date_fields)
return date_string
end
The function can be used as most would expect:
> my_stringformat("%year - %month")
2022 - 10
Obviously, it's trivial to rename or add the fields names:
function my_stringformat_2 (format)
local date_fields = os.date("*t")
-- Rename the fields with shorter name
date_fields.ye = date_fields.year
date_fields.mo = date_fields.month
-- Delete previous field names
date_fields.year = nil
date_fields.month = nil
-- Interpolate the strings
local date_string = format:gsub("%%(%a+)", date_fields)
-- return the new string
return date_string
end
And the behavior is the same as the previous one:
> my_stringformat_2("%ye - %mo")
2022 - 10

How to increment the number in a string while keeping the appropriate trailing/leading zeros

I'm curious as to how accounting softwares enable incrementing "invoice number" ie from INV-001 increments to INV-002. Let's dissect by only focusing on "001".
I've done some "Googling" and found the use of "%03d":
puts "%03d" % 1
#=> "001"
That's a start, but I struggle with many variations:
str = "001"
str = "009"
At school we were taught:
# Let's assume we knew nothing about strings
001 + 1 # gives us 002. How?
# This is what really happens
#
# 001
# + 1
# ______
# 002
Using the above, if we "add" 009 + 1, we get 010 if we use the above method.
Things are much different with programming as converting "001" to integer becomes 1.
How can I create a method that knows how to add "001" plus 1 which returns "002"?
I assume a lot of things are going on with the above formula:
How it knows what to add 1 to.
How it knows to bring the "remainder" to the left then add ie 009 + 1 = 010
For 3, how it knows to keep a zero at the end 010 and not 10
I've tried many things but are all incorrect. Basically I need to increment the strings:
# Result should be when str is incremented by 1
str = "002" + 1 #=> "003"
str = "0002" + 1 #=> "0003"
str = "009" + 1 #=> "010"
str = "0002" + 1 #=> "0010"
str = "02" + 1 #=> "03"
str = "1" + 1 #=> "2"
Converting the str to float loses the zeros and I cant seem to use any logic successfully with "%03d".
I'm curious as to how accounting softwares enable incrementing "invoice number" ie from INV-001 increments to INV-002.
Accounting software typically doesn't operate on the previously generated string. Instead, it just stores the invoice number (i.e. the numeric part) as an ordinary integer, e.g.:
invoice_number = 9
It then formats that number according to a small template, e.g.
format('INV-%03d', invoice_number)
#=> "INV-009"
Now, going from INV-009 to INV-010 is simply a matter of incrementing the integer and running it through the formatter:
invoice_number += 1
#=> 10
format('INV-%03d', invoice_number)
#=> "INV-010"
You can use next like so:
("%03d" % 1).next #=> '002'

How do I write a function to convert duration to time in milliseconds and account for invalid values?

I'm using Rails 5 with Ruby 2.4. I want to convert a string, which represents duration (time) to milliseconds, so I have this function
def duration_in_milliseconds(input)
if input && input.count("a-zA-Z ") == 0
if input.index(".") && !input.index(":")
input.gsub!(/\./, ':')
end
# replace bizarre apostraphes where they intended ":"'s
input.gsub!(/'/, ':')
# Strip out unnecessary white space
input.gsub!(/\A\p{Space}+|\p{Space}+\z/, '')
input.split(':').map(&:to_f).inject(0) { |a, b| a * 60 + b } * 1000
else
0
end
end
This works great for strings like "6:52" (6 mintues, 52 seconds), but also works for things like "52:6" (which is invalid, "52:06" would be correct). I wish to have the function return zero if someone enters in a single digit in any spot other than the first number before the ":". How do I do this? NOte that I still want a duration like "15:24.8" to parse normally (that is 15 minutes, 24 seconds, and eight tenths of a second).
I was trying to come up with a solution for this and ended up reworking the code.
I started splitting the string and removing all the white space. To remove the white space first I ensure that there's no leading nor trailing white space and then I incorporate any amount of extra space before or after the : characters.
Then I assign the values returned from the split to 3 variables I named hr, min and seg. If you're unfamiliar with this, the variables are filled out from left to right and, in case there aren't enough elements, the remaining vars get a nil.
Then, the condition. For this I say: "Return zero if either min or seg don't match two consecutive numeric characters while being present"
And finally, I sum their respective values in seconds, which returns the numer of seconds. And at the end of the line I convert it to milliseconds by multiplying it by 1000
def duration_in_milliseconds(input)
hr, min, seg = input.strip.split(/\s*:\s*/)
return 0 if [min, seg].any? {|value| value && !(value =~ /\d\d/)}
(hr.to_i * 60 * 60 + min.to_i * 60 + seg.to_i) * 1000
end
And I tried it out on a bunch of strings to make sure it worked.
duration_in_milliseconds("0:00:01") # => 1000
duration_in_milliseconds("0:01:01") # => 61000
duration_in_milliseconds("1:01:01") # => 3661000
duration_in_milliseconds("0:15") # => 900000
duration_in_milliseconds("1: 05") # => 3900000
duration_in_milliseconds("1:5") # => 0
duration_in_milliseconds("1:5 ") # => 0
duration_in_milliseconds(" \t 1 : 05 : 15 ") # => 3915000
duration_in_milliseconds("1:5:15") # => 0
duration_in_milliseconds("1") # => 3600000
duration_in_milliseconds("1:5D:15") # => 0
I hope it helped and answered the question (in some way), if not, please don't hessitate to ping me.
Cheers
Fede

Rails absolute time from reference + french "ago" string

I need to reimport some data that was exported using the "ago" stringification helper, in French.
I have a reference Time/DateTime date at which the import was done, and from there I need to substract this "time ago" difference to find the absolute time.
I need to code the parse_relative_time method below
Some sample input/output of what I'm trying to achieve
IMPORT_DATE = Time.parse('Sat, 11 Jun 2016 15:15:19 CEST +02:00')
sample_ago_day = 'Il y a 5j' # Note : 'Il y a 5j" = "5d ago"
parse_relative_time(from: IMPORT_DATE, ago: sample_ago_day)
# => Should output sthing like Sat, 6 Jun 2016 (DateTime object)
sample_ago_month = 'Il y a 1 mois' # Note : 'Il y a 5j" = "1 month ago"
parse_relative_time(from: IMPORT_DATE, ago: sample_ago_month)
# => 11 May 2016 (it's not big deal if it's 10 or 11 or 12 because of months with odd numbers, just need something approximate)
EDIT
Range of values
"il y a xj" -> x belongs to (1..31)
"il y a y mois" -> y belongs to (2..10) and "un"
(for one)
Let's divide the problem into 2 sub-tasks:
Parse the 'ago' string
Since there is no reversible way in ruby to parse an 'ago' string, lets use regular expressions to extract the data as seconds:
def parse_ago(value)
# If the current value matches 'il y a Xj'
if match = /^il y a (.*?)j$/i.match(value)
# Convert the matched data to an integer
value = match[1].to_i
# Validate the numeric value (between 1 and 31)
raise 'Invalid days value!' unless (1..31).include? value
# Convert to seconds with `days` rails helper
value.days
# If the current value matches 'il y a X mois'
elsif match = /^il y a (.*?) mois$/i.match(value)
# If the matched value is 'un', then use 1. Otherwise, use the matched value
value = match[1] == 'un' ? 1 : match[1].to_i
# Validate the numeric value (between 1 and 10)
raise 'Invalid months value!' unless (1..10).include? value
# Convert to seconds with `months` rails helper
value.months
# Otherwise, something is wrong (or not implemented)
else
raise "Invalid 'ago' value!"
end
end
Substract from current time
This is pretty straightforward; once we have the seconds from the 'ago' string; just call the ago method on the seconds extracted from the 'ago' string. An example of usage of this method for Ruby on Rails:
5.months.ago # "Tue, 12 Jan 2016 15:21:59 UTC +00:00"
The thing is, you are substracting it from IMPORT_DATE, and not from current time. For your code, you need to specify the current time to IMPORT_DATE:
parse_ago('Il y a 5j').ago(IMPORT_DATE)
Hope this helps!

Using distance_of_time_in_words in Rails

I have a start month (3), start year (2004), and I have an end year (2008). I want to calculate the time in words between the start and end dates. This is what I'm trying and it's not working..
# first want to piece the start dates together to make an actual date
# I don't have a day, so I'm using 01, couldn't work around not using a day
st = (start_year + "/" + start_month + "/01").to_date
ed = (end_year + "/01/01").to_date
# the above gives me the date March 1st, 2004
# now I go about using the method
distance_of_time_in_words(st, ed)
..this throws an error, "string can't me coerced into fixnum". Anyone seen this error?
You can't just concatenate strings and numbers in Ruby. You should either convert numbers to strings as mliebelt suggested or use string interpolation like that:
st = "#{start_year}/#{start_month}/01".to_date
But for your particular case I think there is no need for strings at all. You can do it like that:
st = Date.new(start_year, start_month, 1)
ed = Date.new(end_year, 1, 1)
distance_of_time_in_words(st, ed)
or even like that:
st = Date.new(start_year, start_month)
ed = Date.new(end_year)
distance_of_time_in_words(st, ed)
See Date class docs for more information.
Given that the context in which you are calling the method is one that knows the methods from ActionView::Helpers::DateHelper, you should change the following:
# first want to piece the start dates together to make an actual date
# I don't have a day, so I'm using 01, couldn't work around not using a day
st = (start_year.to_s + "/" + start_month.to_s + "/01").to_date
ed = (end_year.to_s + "/01/01").to_date
# the above gives me the date March 1st, 2004
# now I go about using the method
distance_of_time_in_words(st, ed)
=> "almost 3 years"
So I have added calls to to_s for the numbers, to ensure that the operation + is working. There may be more efficient ways to construct a date, but yours is sufficient.

Resources