How I can generate a date range, reject some days for example sundays or some holidays and extend the range with a next available day? Obviously I can do something like (Date.today..Date.today+5.days).reject{|day| day.sunday?} but this would remove sunday and make my range smaller. How can I solve this? Should I implement a custom Range class?
That is impossible in general. A range has to be continuous. Unless the date you want to reject is at either end of the original range, that is impossible.
However, by converting the range to an array, you can do a similar thing:
(Date.today..Date.today+5.days).to_a.reject(&:sunday?)
This cannot be done with a Range as #sawa already pointed out.
I think you need to use an array filled with qualified days:
def working_days(number)
[].tap do |days|
date = Date.today
while days.size < number
days << date unless date.sunday? || date.saturday?
date = date.next
end
end
end
working_days(5)
#=> [02 Dec 2015, 03 Dec 2015, 04 Dec 2015, 07 Dec 2015, 08 Dec 2015]
Related
So I have the function performing well.
func Today()(result string){
current_time := time.Now().Local()
result = current_time.Format("01/02/2006")
return
}
Prints MM/DD/YYYY And I thought that it would be more readable if I had a value greater than 12 in the days position to make it clear that it was MM/DD/YYYY so I changed the to following
func Today()(result string){
current_time := time.Now().Local()
result = current_time.Format("01/23/2004")
return
}
Which to my chagrin caused bad results. Prints MM/DDHH/DD0MM
Realizing my mistake I see that the format is defined by the reference time...
Mon Jan 2 15:04:05 -0700 MST 2006
I'm wondering if there is any other instances this moment being used as a formatting reference for date times, and if this reference moment has a nickname (like null island)?
The values in a date string are not arbitrary. You can't just change 02 to 03 and expect it to work. The date formatter looks for those specific values, and knows that 1 means month, 2 means day of month, etc.
Changing 01/02/2006 to 01/23/2004 is like changing a human-readable form that says First Name: ______ Last Name: ______ to one that says First Name: ______ Ice Cream: ______. You can't expect anyone to know that Ice Cream should mean Last Name.
The name
The only name provided for this is "reference time", here:
Parse parses a formatted string and returns the time value it represents. The layout defines the format by showing how the reference time, defined to be
Mon Jan 2 15:04:05 -0700 MST 2006
and here:
These are predefined layouts for use in Time.Format and Time.Parse. The reference time used in the layouts is the specific time:
Mon Jan 2 15:04:05 MST 2006
which is Unix time 1136239445. Since MST is GMT-0700, the reference time can be thought of as
01/02 03:04:05PM '06 -0700
To define your own format, write down what the reference time would look like formatted your way; see the values of constants like ANSIC, StampMicro or Kitchen for examples. The model is to demonstrate what the reference time looks like so that the Format and Parse methods can apply the same transformation to a general time value.
To specify that you're talking about Go's reference time, I'd say "Go's reference time." Or to be blatantly obvious, "Go's time.Parse reference time."
As an aside, your function can be greatly shortened:
func Today() string {
return time.Now().Local().Format("01/02/2006")
}
Background: I am trying to write a simple function that generates a list of calendar days, which I have mostly working except for one if/else loop.
The relevant variables and their initial declaration values:
monthsOfYear = %w[January February March April May June July August September October November December]
currentMonthName = "" # empty string
daysInMonth = 0 # integer
The relevant loop:
monthsOfYear.each do |month| #loop through each month
# first get the current month name
currentMonthName = "#{month}" # reads month name from monthsOfYear array
if ??month == 3 || 5 || 8 || 10 ?? # April, June, September, November
daysInMonth = 30
elsif ??month == 1?? # February
if isLeapYear
daysInMonth = 29
else
daysInMonth = 28
end
else # All the rest
daysInMonth = 31
end
I've marked the part that I'm having trouble with between ?? ??
Basically, I am trying to figure out how to access the numerical value of the index as it loops and test whether that index number matches the few specific cases. I have searched the documentation extensively trying to find a method that returns the index number value (NOT the value stored at x index), in other words I want to be able to read x in Array[x], not what's stored at Array[x]
Perhaps in this specific case it would be better to test whether month == "April" || "June" || "September" || "November" rather than trying to build the case from parsing the array index number?
But in general, what method can be called to find out the index number value? Or is that even possible?
Joel's answer is a better implementation but to keep with your code and to answer your question, Enumerable has an an each_with_index method (Enumberable#each_with_index):
monthsOfYear.each_with_index do |month, index|
then you can use index in your if/else conditionals. Note that arrays are zero based so January is actually going to be 0.
To get the index of an array item, use the index method:
monthsOfYear = [ "January", "February", "March", ... ]
monthsOfYear.index("February") #=> 1
If you're looking for date calculations specifically,
Ruby has a built-in way:
Date.new(date.year, date.month, -1).mday #=> the number of days in the month
If you're looking to iterate with the month and index, Anthony's answer is correct.
monthsOfYear.each_with_index do |month, index| {
...
# The first loop: month = "January", index = 0
...
}
If you're looking for a way to improve your code, use a case statement:
case month
when "April", "June", "September", "November"
daysInMonth = 30
when "February"
if isLeapYear
daysInMonth = 29
else
daysInMonth = 28
end
else
daysInMonth = 31
end
In Ruby, you can set anything equal to the result of a case statement, and also a case statement can match numbers, so it's possible to write:
daysInMonth = case monthsOfYear.index(currentMonthName)
when 3, 5, 8, 10
30
when 1
isLeapYear ? 29 : 28
else
31
end
Why am I getting this Type Mismatch error?
Code:
Dim data as Date
data = CDate(Format(31, "00") & "/" & Format("1/9/2013", "mm/yyyy"))
When I try this with strings of course it works perfect.
Obs: It works if I use a different day than 31 like 28 for exemple... But Why this error occurs only with the day 31.
It looks like you are trying to get the last day of the month. If that is the case, try this...
Dim data As Date
Dim OriginalDate As Date
OriginalDate = DateSerial(2013, 12, 20)
data = DateSerial(Year(OriginalDate), Month(OriginalDate) + 1, 0)
This code basically gets the first day of the next month and then subtracts one day. The nice thing about using the DateSerial function is that you can give it "invalid" values, For example, if you use Year = 2013 and Month = 13, you will get January 2014.
I found the answer, and it's stupid -.-" Month 9 = Agost = 30 days and not 31... I feel so stupid now ;x
You have to watch out for dates because depending on what region your computer is in (Control Panel / Region and Language) sometimes your date of Format("1/9/2013", "mm/yyyy")) can be interpreted as September 1, 2013 or as January 9, 2013
If you can, use DateSerial to specifically hook in the month/day numbers without relying on the format output.
I'm trying to convert strings like "Sep 11, Oct 31, Feb 28" into DateTime instances as part of a screen-scraper. Using DateTime.parse() works fine apart from when the data goes across years, and it naively (and probably correctly) returns a date in the current year.
For example the following test case.
test "dateA convert next year" do
TimeService.stubs(:now).returns(Time.new(2013, 12, 30, 9, 30))
assert_equal(Date.new(2014, 1, 2), Extraction.dateA("Jan 2"))
end
I updated my method to look at what would be date with year + 1, and return the closest to 'now' - this works fine. However it feels a bit ugly, and I'm looking for a more elegant solution.
def Extraction.dateA(content)
return_value = DateTime.parse(content)
next_year = return_value.change(:year => return_value.year + 1)
now = TimeService.now.to_i
if (next_year.to_i - now).abs < (return_value.to_i - now).abs then
return_value = next_year
end
return_value
end
TimeService.now is just a utility to return current time to help with stubbing.
Excuse my ruby, I'm new to it.
I think this works as intended, allowing for closest date in previous year as well:
module Extraction
def Extraction.date_a(content)
parsed_date = DateTime.parse(content)
now = DateTime.now
dates = [ parsed_date, parsed_date.next_year, parsed_date.prev_year ]
dates.min_by { |d| ( d - now ).abs }
end
end
A few points:
Changed method name to date_a, just a Ruby convention that differs from Java.
I made use of some built-in methods next_year and prev_year on DateTime
I used a time difference metric and selected date with the minimal value of it from three candidate dates (this is what min_by does). This is simpler code than the conditional switching, especially with three dates to consider.
I forgot about min_by originally, I don't use it often, but it's a very good fit for this problem.
Note there is a pathological case - "29 Feb". If it appears correctly in text, by its nature it will define which year is valid, and it won't parse if current year is e.g. 2015.
I need to loop through all of the days and months for the past couple decades numerically as well as to have the name of the month and day for each date. Obviously a few series of loops can accomplish this, but I wanted to know the most concise ruby-like way to accomplish this.
Essentially I'd need output like this for each day over the past X years:
3 January 2011 and 1/3/2011
What's the cleanest approach?
Dates can work as a range, so it's fairly easy to iterate over a range. The only real trick is how to output them as a formatted string, which can be found in the Date#strftime method, which is documented here.
from_date = Date.new(2011, 1, 1)
to_date = Date.new(2011, 1, 10)
(from_date..to_date).each { |d| puts d.strftime("%-d %B %Y and %-m/%-d/%Y") }
# => 1 January 2011 and 1/1/2011
# => 2 January 2011 and 1/2/2011
# => ...
# => 9 January 2011 and 1/9/2011
# => 10 January 2011 and 1/10/2011
(Note: I recall having some bad luck a ways back with unpadded percent formats like %-d in Windows, but if the above doesn't work and you want them unpadded in that environment you can remove the dash and employ your own workarounds.)
Given start_date & end_date:
(start_date..end_date).each do |date|
# do things with date
end
as David said, this is possible because of Date#succ. You can use Date#strftime to get the date in any format you'd like.
See if you can construct a Range where the min and max are Date objects, then call .each on the range. If the Date object supports the succ method this should work.