Calculating number of days between two dates that are in a leap year - algorithm

Given two dates, what is the best method to calculate the number of days between those two dates that fall in a leap year.
For example if d1 = 12/1/2007 and d2 = 1/31/2008 then the total number of days between d1 and d2 would be 62 and the number of days that fall in a leap year would be 31.
Another example is if d1 = 12/1/2007 and d2 = 6/30/2012 then the total number of days between d1 and d2 would be 1674 and the number of days that fall in a leap year would be 548.
I already have function to calculate if a specific year is a leap year and and a function to calculate the number of days between two dates.
If anyone has such a algorithm in Delphi (Pascal) or C/C++/C# that would be greatly appreciated. Any suggestions and assistance would be great.

The solution is in python, and it shouldn't be hard to convert to any other language.
def isLeapYear(year):
if year%4 == 0:
if year%100 == 0:
if year%400 == 0:
return True
else:
return False
else:
return True
else:
return False
def daysBetweenDates(year1, month1, day1, year2, month2, day2):
cumDays = [0,31,59,90,120,151,181,212,243,273,304,334] #cumulative Days by month
leapcumDays = [0,31,60,91,121,152,182,213,244,274,305,335] # Cumulative Days by month for leap year
totdays = 0
if year1 == year2:
if isLeapYear(year1):
return (leapcumDays[month2-1] + day2) - (leapcumDays[month1-1] + day1)
else:
return (cumDays[month2-1] + day2) - (cumDays[month1-1] + day1)
if isLeapYear(year1):
totdays = totdays + 366 - (leapcumDays[month1-1] + day1)
else:
totdays = totdays + 365 - (cumDays[month1-1] + day1)
year = year1 + 1
while year < year2:
if isLeapYear(year):
totdays = totdays + 366
else:
totdays = totdays + 365
year = year + 1
if isLeapYear(year2):
totdays = totdays + (leapcumDays[month2-1] + day2)
else:
totdays = totdays + (cumDays[month2-1] + day2)
return totdays

Here's my pseudo code version using your functions for - is_leap_year, days_between. As a commenter noted, these are tricky functions to write correctly.
int leap_year_days_between(Date d1, Date d2) {
if (d1.year == d2.year) {
if (is_leap_year(d1.year) { return days_between(d1,d2); }
else { return 0; }
}
else {
Date last_day_in_year(12, 31, d1.year);
int count=0;
Date tmp = d1;
while (tmp.year < d2.year) {
if ( is_leap_year(tmp.year) ) {
count += days_between(tmp,last_day_in_year);
}
tmp = (1, 1, tmp.year+1);
}
if ( is_leap_year(d2.year) ) {
count += days_between(tmp, d2);
}
}
}

A naive approach would be:
Check your start year. If it's a leap year, count the number of days from your current day to December 31 (inclusive). If not, until your starting year equals your ending year, increment the year by 1. Then, check the year. If it is a leap year, start counting days, if not increment the year. Once the current year and ending year are the same, then check to see if the current (== ending) year is a leap year. If it is, count days in months from January to the ending month, otherwise break the algorithm. Once your current month is your ending month, count your days.

Related

Ruby - Get time at start of next minute

I'm looking for a concise way to get a Ruby Time object representing the top of the next minute (and hour/day/month/year, if possible). I want this to work in a pure Ruby environment, so the Rails function Time.change or similar doesn't fit the bill.
At first this seems simple - just add 1 to Time.now, but there are edge cases where if, for example, you try to instantiate a Time object with Time.now.min + 1 when the current minute is 59, you get an ArgumentError: min out of range. This goes for hour, day, and month as well.
I have some lengthy code that does the job. It's ugly, but I'm just experimenting:
def add_minute
now = Time.local
year = now.year
month = now.month
day = now.day
hour = now.hour
min = now.min
if now.min == 59
if now.hour == 23
if now.day == Date.civil(now.year, now.month, -1).day
if month == 12
year = year + 1
month = 1
day = 1
hour = 0
min = 0
else
month = now.month + 1
day = 1
hour = 0
min = 0
end
else
day = now.day + 1
hour = 0
min = 0
end
else
hour = now.hour + 1
min = 0
end
else
min = now.min + 1
end
Time.local year, month, day, hour, min, 0
end
This seems absurdly verbose for what seems like it should be a simple or built-in task, but I haven't found a native Ruby solution. Does one exist?
You could convert the Time object to UNIX epoch time (seconds since 1970) using #to_i, add 60 s, and then convert back to a Time object.
time_unix = Time.now.to_i
time_unix_one_min_later = time_unix + 60
time_one_min_later = t = Time.at(time_unix_one_min_later)
time_one_min_later_rounded_down = Time.new(t.year, t.month, t.day, t.hour, t.min)
EDIT: Even shorter - you can just add integer seconds to Time.now directly:
time_one_min_later = t = Time.now + 60
time_one_min_later_rounded_down = Time.new(t.year, t.month, t.day, t.hour, t.min)
EDIT 2: One-liner - just subtract Time.now.sec:
time_one_min_later_rounded_down = Time.now + 60 - Time.now.sec
Other option, given one second to midnight:
require 'time'
now = Time.strptime('2018-12-31 23:59:59', '%Y-%m-%d %H:%M:%S')
Within one minute:
Time.at(now + 60) #=> 2019-01-01 00:00:59 +0100
Time.at(now + 60 - now.sec) #=> 2019-01-01 00:00:00 +0100
You get: # HAPPY NEW YEAR!
Ruby has built in methods for adding months (>>) and days (+). A year is 12 months, and an hour is 1/24th of a day.
require 'date'
def add_time(time, year: 0 ,month: 0, day: 0, hour: 0, minute: 0)
time >>= 12*year
time >>= month
time += day
time += Rational(hour,24) # or (hour/24.0) if you dislike rationals
time += Rational(minute, 24*60) # (minute/24.0*60) if you dislike rationals
end
p t = DateTime.now
p add_time(t, year: 1, minute: 30)
Not that clean without ActiveSupport:
new_date = (DateTime.now + 1.to_f / (60*24))
DateTime.new(new_date.year, new_date.month, new_date.day, new_date.hour, new_date.minute)
We can make this calculation easier to understand by getting the current number of seconds we are through the day. (optional)
DateTime.current.to_i gives us the number of seconds since 1970
DateTime.current.to_i - DateTime.current.beginning_of_day.to_i gives us the number of seconds since the start of the day.
(((number_of_seconds_through_the_day + 60)/60) * 60) gives us the number of seconds we will be at when the next minute starts
Then we subtract the two to give us the number of seconds until the top of the next minute.
If we want the exact time at start of the next minute then we can do:
DateTime.current + seconds_until_start_of_the_next_minute.seconds
def seconds_until_start_of_the_next_minute
number_of_seconds_through_the_day = DateTime.current.to_i - DateTime.current.beginning_of_day.to_i
number_of_seconds_through_the_day_at_next_minute = (((number_of_seconds_through_the_day + 60)/60) * 60)
seconds_until_next_minute_starts = number_of_seconds_through_the_day_at_next_minute - number_of_seconds_through_the_day
return seconds_until_next_minute_starts
end

Count day combinations in the date range

Imagine that you have a range of dates, for example 2017-08-01 - 2017-09-15, and start day (Monday - sonday in numerical format 1-7) and endDay. You have to calculate the number of combinations of this days.
For the input 4-7 what is thusday - sonday and mentioned dates the output will be 6. How would you do that?
In your example you would have these values:
rangeStart = 2017-08-01
rangeEnd = 2017-09-15
startDay = 4
endDay = 7
I suppose you also have the following functions:
weekday(date) determines the day of the week for the given date, and returns it as a number: 1 for Monday, ... 7 for Sunday.
date_add(date, days) adds days days to the given date and returns the resulting date.
date_diff(date1, date2) returns the number of days between two dates, excluding date2 itself, i.e. when both dates fall on the same week day, this will be a multiple of 7.
Then this could be the algorithm:
# Align range start with closest start day in the range
rangeStart = date_add(rangeStart, (startDay + 7 - weekday(rangeStart)) % 7)
# Align range end with closest end day within the range
rangeEnd = date_add(rangeEnd, -((weekday(rangeEnd) + 7 - endDay) % 7))
# Get number of full weeks in range, and add 1
result = floor(date_diff(rangeStart, rangeEnd) / 7) + 1
NB: % is the modulo operator, and floor truncates a decimal number down to the nearest integer value.
Implementation in JavaScript:
// Define utility functions whose implementations depend on the programming language
function weekday(date) {
return (date.getDay()+6)%7+1; // in JavaScript Sunday is 0
}
function date_add(date, days) {
var result = new Date(date); // get clone
result.setDate(result.getDate() + days); // mutate
return result;
}
function date_diff(date1, date2) {
return Math.round((date2-date1)/(1000*60*60*24));
}
var floor = Math.floor;
// Main algorithm
function count(rangeStart, rangeEnd, startDay, endDay) {
rangeStart = date_add(rangeStart, (startDay + 7 - weekday(rangeStart)) % 7);
rangeEnd = date_add(rangeEnd, -((weekday(rangeEnd) + 7 - endDay) % 7));
return floor(date_diff(rangeStart, rangeEnd) / 7) + 1;
}
var rangeStart = new Date("2017-08-01"),
rangeEnd = new Date("2017-09-15"),
startDay = 4,
endDay = 7;
var result = count(rangeStart, rangeEnd, startDay, endDay);
console.log(result);
Implementation in Python:
import datetime
def count(rangeStart, rangeEnd, startDay, endDay):
rangeStart = rangeStart + datetime.timedelta((startDay + 7 - rangeStart.isoweekday()) % 7)
rangeEnd = rangeEnd - datetime.timedelta((rangeEnd.isoweekday() + 7 - endDay) % 7)
return (rangeEnd - rangeStart).days // 7 + 1
rangeStart = datetime.date(2017, 8, 3)
rangeEnd = datetime.date(2017, 9, 15)
startDay = 4
endDay = 7
result = count(rangeStart, rangeEnd, startDay, endDay)
print(result)

How do I convert seconds since Epoch to current date and time?

I know I posted this a while ago, but I figured out the solution. I wrote this code for a game called Roblox, but I'm just posting the code here in case anyone else who has this same problem needs a solution. Anyways, here's the code:
outputTime = true -- true: will print the current time to output window. false: won't print time
createVariable = true -- true: creates variables under game.Lighting. false: won't create variables
-----------------------------------------------------------------------------------------------
--DO NOT EDIT BELOW----------------------------------------------------------------------------
-----------------------------------------------------------------------------------------------
if(createVariable) then
yearVar = Instance.new("IntValue", game.Lighting)
yearVar.Name = "Year"
yearVar.Value = 0
monthVar = Instance.new("IntValue", game.Lighting)
monthVar.Name = "Month"
monthVar.Value = 0
dayVar = Instance.new("IntValue", game.Lighting)
dayVar.Name = "Day"
dayVar.Value = 0
hourVar = Instance.new("IntValue", game.Lighting)
hourVar.Name = "Hour"
hourVar.Value = 0
minuteVar = Instance.new("IntValue", game.Lighting)
minuteVar.Name = "Minute"
minuteVar.Value = 0
secondVar = Instance.new("IntValue", game.Lighting)
secondVar.Name = "Second"
secondVar.Value = 0
dayOfWeek = Instance.new("StringValue", game.Lighting)
dayOfWeek.Name = "DayOfWeek"
dayOfWeek.Value = "Thursday"
end
function giveZero(data)
if string.len(data) <= 1 then
return "0" .. data
else
return data
end
end
function hasDecimal(value)
if not(value == math.floor(value)) then
return true
else
return false
end
end
function isLeapYear(year)
if(not hasDecimal(year / 4)) then
if(hasDecimal(year / 100)) then
return true
else
if(not hasDecimal(year / 400)) then
return true
else
return false
end
end
else
return false
end
end
local eYear = 1970
local timeStampDayOfWeak = 5
local secondsInHour = 3600
local secondsInDay = 86400
local secondsInYear = 31536000
local secondsInLeapYear = 31622400
local monthWith28 = 2419200
local monthWith29 = 2505600
local monthWith30 = 2592000
local monthWith31 = 2678400
local monthsWith30 = {4, 6, 9, 11}
local monthsWith31 = {1, 3, 5, 7, 8, 10, 12}
local daysSinceEpoch = 0
local DOWAssociates = {"Tursday", "Friday", "Saturday", "Sunday", "Monday", "Tuesday", "Wednesday"}
while(true) do
now = tick()
year = 1970
secs = 0
daysSinceEpoch = 0
while((secs + secondsInLeapYear) < now or (secs + secondsInYear) < now) do
if(isLeapYear(year+1)) then
if((secs + secondsInLeapYear) < now) then
secs = secs + secondsInLeapYear
year = year + 1
daysSinceEpoch = daysSinceEpoch + 366
end
else
if((secs + secondsInYear) < now) then
secs = secs + secondsInYear
year = year + 1
daysSinceEpoch = daysSinceEpoch + 365
end
end
end
secondsRemaining = now - secs
monthSecs = 0
yearIsLeapYear = isLeapYear(year)
month = 1 -- January
while((monthSecs + monthWith28) < secondsRemaining or (monthSecs + monthWith30) < secondsRemaining or (monthSecs + monthWith31) < secondsRemaining) do
if(month == 1) then
if((monthSecs + monthWith31) < secondsRemaining) then
month = 2
monthSecs = monthSecs + monthWith31
daysSinceEpoch = daysSinceEpoch + 31
else
break
end
end
if(month == 2) then
if(not yearIsLeapYear) then
if((monthSecs + monthWith28) < secondsRemaining) then
month = 3
monthSecs = monthSecs + monthWith28
daysSinceEpoch = daysSinceEpoch + 28
else
break
end
else
if((monthSecs + monthWith29) < secondsRemaining) then
month = 3
monthSecs = monthSecs + monthWith29
daysSinceEpoch = daysSinceEpoch + 29
else
break
end
end
end
if(month == 3) then
if((monthSecs + monthWith31) < secondsRemaining) then
month = 4
monthSecs = monthSecs + monthWith31
daysSinceEpoch = daysSinceEpoch + 31
else
break
end
end
if(month == 4) then
if((monthSecs + monthWith30) < secondsRemaining) then
month = 5
monthSecs = monthSecs + monthWith30
daysSinceEpoch = daysSinceEpoch + 30
else
break
end
end
if(month == 5) then
if((monthSecs + monthWith31) < secondsRemaining) then
month = 6
monthSecs = monthSecs + monthWith31
daysSinceEpoch = daysSinceEpoch + 31
else
break
end
end
if(month == 6) then
if((monthSecs + monthWith30) < secondsRemaining) then
month = 7
monthSecs = monthSecs + monthWith30
daysSinceEpoch = daysSinceEpoch + 30
else
break
end
end
if(month == 7) then
if((monthSecs + monthWith31) < secondsRemaining) then
month = 8
monthSecs = monthSecs + monthWith31
daysSinceEpoch = daysSinceEpoch + 31
else
break
end
end
if(month == 8) then
if((monthSecs + monthWith31) < secondsRemaining) then
month = 9
monthSecs = monthSecs + monthWith31
daysSinceEpoch = daysSinceEpoch + 31
else
break
end
end
if(month == 9) then
if((monthSecs + monthWith30) < secondsRemaining) then
month = 10
monthSecs = monthSecs + monthWith30
daysSinceEpoch = daysSinceEpoch + 30
else
break
end
end
if(month == 10) then
if((monthSecs + monthWith31) < secondsRemaining) then
month = 11
monthSecs = monthSecs + monthWith31
daysSinceEpoch = daysSinceEpoch + 31
else
break
end
end
if(month == 11) then
if((monthSecs + monthWith30) < secondsRemaining) then
month = 12
monthSecs = monthSecs + monthWith30
daysSinceEpoch = daysSinceEpoch + 30
else
break
end
end
end
day = 1 -- 1st
daySecs = 0
daySecsRemaining = secondsRemaining - monthSecs
while((daySecs + secondsInDay) < daySecsRemaining) do
day = day + 1
daySecs = daySecs + secondsInDay
daysSinceEpoch = daysSinceEpoch + 1
end
hour = 0 -- Midnight
hourSecs = 0
hourSecsRemaining = daySecsRemaining - daySecs
while((hourSecs + secondsInHour) < hourSecsRemaining) do
hour = hour + 1
hourSecs = hourSecs + secondsInHour
end
minute = 0 -- Midnight
minuteSecs = 0
minuteSecsRemaining = hourSecsRemaining - hourSecs
while((minuteSecs + 60) < minuteSecsRemaining) do
minute = minute + 1
minuteSecs = minuteSecs + 60
end
second = math.floor(now % 60)
year = giveZero(year)
month = giveZero(month)
day = giveZero(day)
hour = giveZero(hour)
minute = giveZero(minute)
second = giveZero(second)
remanderForDOW = daysSinceEpoch % 7
DOW = DOWAssociates[remanderForDOW + 1]
if(createVariable) then
yearVar.Value = year
monthVar.Value = month
dayVar.Value = day
hourVar.Value = hour
minuteVar.Value = minute
secondVar.Value = second
dayOfWeek.Value = DOW
end
if(outputTime) then
str = "Year: " .. year .. ", Month: " .. month .. ", Day: " .. day .. ", Hour: " .. hour .. ", Minute: " .. minute .. ", Second: ".. second .. ", Day of Week: " .. DOW
print(str)
end
wait(1)
end
----ORIGINAL POST----
What are the formulas for calculating the following given no resources except the seconds since Epoch?
Here's a list of what I need:
Current Month of year Ex: 7
Current day of month Ex: 25
Current day of week Ex: Thursday (1-7 would be acceptable)
Current hour of day Ex: 22
Current minute of hour Ex: 34
Current second of minute: 07
Here is some Lua code adapted from some C code found by Google. It does not handle timezones or Daylight Saving Time and so the outputs refers to Universal Coordinated Time (UTC).
-- based on http://www.ethernut.de/api/gmtime_8c_source.html
local floor=math.floor
local DSEC=24*60*60 -- secs in a day
local YSEC=365*DSEC -- secs in a year
local LSEC=YSEC+DSEC -- secs in a leap year
local FSEC=4*YSEC+DSEC -- secs in a 4-year interval
local BASE_DOW=4 -- 1970-01-01 was a Thursday
local BASE_YEAR=1970 -- 1970 is the base year
local _days={
-1, 30, 58, 89, 119, 150, 180, 211, 242, 272, 303, 333, 364
}
local _lpdays={}
for i=1,2 do _lpdays[i]=_days[i] end
for i=3,13 do _lpdays[i]=_days[i]+1 end
function gmtime(t)
print(os.date("!\n%c\t%j",t),t)
local y,j,m,d,w,h,n,s
local mdays=_days
s=t
-- First calculate the number of four-year-interval, so calculation
-- of leap year will be simple. Btw, because 2000 IS a leap year and
-- 2100 is out of range, this formula is so simple.
y=floor(s/FSEC)
s=s-y*FSEC
y=y*4+BASE_YEAR -- 1970, 1974, 1978, ...
if s>=YSEC then
y=y+1 -- 1971, 1975, 1979,...
s=s-YSEC
if s>=YSEC then
y=y+1 -- 1972, 1976, 1980,... (leap years!)
s=s-YSEC
if s>=LSEC then
y=y+1 -- 1971, 1975, 1979,...
s=s-LSEC
else -- leap year
mdays=_lpdays
end
end
end
j=floor(s/DSEC)
s=s-j*DSEC
local m=1
while mdays[m]<j do m=m+1 end
m=m-1
local d=j-mdays[m]
-- Calculate day of week. Sunday is 0
w=(floor(t/DSEC)+BASE_DOW)%7
-- Calculate the time of day from the remaining seconds
h=floor(s/3600)
s=s-h*3600
n=floor(s/60)
s=s-n*60
print("y","j","m","d","w","h","n","s")
print(y,j+1,m,d,w,h,n,s)
end
local t=os.time()
gmtime(t)
t=os.time{year=1970, month=1, day=1, hour=0} gmtime(t)
t=os.time{year=1970, month=1, day=3, hour=0} gmtime(t)
t=os.time{year=1970, month=1, day=2, hour=23-3, min=59, sec=59} gmtime(t)
The formula is not simple for a few reasons, especially leap years. You should probably use the date function on this page rather than trying to calculate it yourself.
You could use luatz
x = 1234567890
t = require "luatz.timetable".new_from_timestamp ( x )
print(t.year,t.month,t.day,t.hour,t.min,t.sec,t.yday,t.wday)
-- Or just
print(t:rfc_3339())
This is how I do it.
> time0=os.time()
> time0
1571439964
> os.date("%Y%m%d%H%M%S",time0)
20191019120604
>
os.date is a standard Lua function, if passing the first argument as "%t", it will return a table containing the following fields: year (four digits), month (1--12), day (1--31), hour (0--23), min (0--59), sec (0--61), wday (weekday, Sunday is 1), yday (day of the year), and isdst (daylight saving flag, a boolean).
Give it a test:
time = os.time()
print("time since epoch: " .. time)
date = os.date("*t", time)
print("year: " .. date.year)
print("month: " .. date.month)
print("day: " .. date.day)
print("hour: " .. date.hour)
print("minute: " .. date.min)
print("second: " .. date.sec)
print("weekday: " .. date.wday)
Output:
time since epoch: 1374826427
year: 2013
month: 7
day: 26
hour: 16
minute: 13
second: 47
weekday: 6
A much faster solution would be to use my method, which I haven't really seen anyone else use because most have access to os.date()
Since I do not have access to os.date(), here is my solution:
local tabIndexOverflow = function(seed, table)
-- This subtracts values from the table from seed until an overflow
-- This can be used for probability :D
for i = 1, #table do
if seed - table[i] <= 0 then
return i, seed
end
seed = seed - table[i]
end
end
local getDate = function(unix)
-- Given unix date, return string date
assert(unix == nil or type(unix) == "number" or unix:find("/Date%((%d+)"), "Please input a valid number to \"getDate\"")
local unix = (type(unix) == "string" and unix:match("/Date%((%d+)") / 1000 or unix or os.time()) -- This is for a certain JSON compatability. It works the same even if you don't need it
local dayCount, year, days, month = function(yr) return (yr % 4 == 0 and (yr % 100 ~= 0 or yr % 400 == 0)) and 366 or 365 end, 1970, math.ceil(unix/86400)
while days >= dayCount(year) do days = days - dayCount(year) year = year + 1 end -- Calculate year and days into that year
month, days = tabIndexOverflow(days, {31,(dayCount(year) == 366 and 29 or 28),31,30,31,30,31,31,30,31,30,31}) -- Subtract from days to find current month and leftover days
-- hours = hours > 12 and hours - 12 or hours == 0 and 12 or hours -- Change to proper am or pm time
-- local period = hours > 12 and "pm" or "am"
-- Formats for you!
-- string.format("%d/%d/%04d", month, days, year)
-- string.format("%02d:%02d:%02d %s", hours, minutes, seconds, period)
return {Month = month, day = days, year = year, hours = math.floor(unix / 3600 % 24), minutes = math.floor(unix / 60 % 60), seconds = math.floor(unix % 60)}
end
You are, however, on your own when it comes to finding the day of the week. I never cared to find whether it be the day of Thor or the day of Frige.

Ruby Determine Season (Fall, Winter, Spring or Summer)

I am working on a script that is supposed to determine the "season" of the year based on date ranges:
For Example:
January 1 - April 1: Winter
April 2 - June 30: Spring
July 1 - September 31: Summer
October 1 - December 31: Fall
I am not sure how the best way (or the best ruby way) to go about doing this. Anyone else run across how to do this?
31 September?
As leifg suggested, here it is in code:
require 'Date'
class Date
def season
# Not sure if there's a neater expression. yday is out due to leap years
day_hash = month * 100 + mday
case day_hash
when 101..401 then :winter
when 402..630 then :spring
when 701..930 then :summer
when 1001..1231 then :fall
end
end
end
Once defined, call it e.g. like this:
d = Date.today
d.season
You could try with ranges and Date objects:
http://www.tutorialspoint.com/ruby/ruby_ranges.htm
without ranges.
require 'date'
def season
year_day = Date.today.yday().to_i
year = Date.today.year.to_i
is_leap_year = year % 4 == 0 && year % 100 != 0 || year % 400 == 0
if is_leap_year and year_day > 60
# if is leap year and date > 28 february
year_day = year_day - 1
end
if year_day >= 355 or year_day < 81
result = :winter
elsif year_day >= 81 and year_day < 173
result = :spring
elsif year_day >= 173 and year_day < 266
result = :summer
elsif year_day >= 266 and year_day < 355
result = :autumn
end
return result
end
Neil Slater's answer's approach is great but for me those dates aren't quite correct. They show fall ending on December 31st which isn't the case in any scenario I can think of.
Using the northern meteorological seasons:
Spring runs from March 1 to May 31;
Summer runs from June 1 to August 31;
Fall (autumn) runs from September 1 to November 30; and
Winter runs from December 1 to February 28 (February 29 in a leap year).
The code would need to be updated to:
require "date"
class Date
def season
day_hash = month * 100 + mday
case day_hash
when 101..300 then :winter
when 301..531 then :spring
when 601..831 then :summer
when 901..1130 then :fall
when 1201..1231 then :winter
end
end
end

Algorithm to determine if a given date/time is between two date/time pairs

I have an array of dates in a one week range stored in an unusual way.
The Dates are stored in this numeric format: 12150
From left to right:
1st digit represents day: 1 = sunday, 2 = monday, 3 = tuesday, ...., 7 = saturday
next two digits represent hour in a 24 hour system: 00 = midnight, 23 = 11pm
next two digits represent minutes: 00-59
Given an input date and a start date and end date I need to know if the input date is between the start and end date.
I have an algorithm right now that I think works 100% of the time, but I am not sure.
In any case, I think there is probably a better and simpler way to do this and I was wondering if anybody knew what that algorithm was.
If not it would be cool if someone could double check my work and verify that it does actually work for 100% of valid cases.
What I have right now is:
if (startDate < inputDate &&
endDate > inputDate) {
inRange = yes;
}
else if (endDate < startDate) {
if((inputDate + 72359) > startDate &&
(inputDate + 72359) < endDate) {
inRange = yes;
}
else if((inputDate + 72359) > startDate &&
(inputDate + 72359) < (endDate + 72359)) {
inRange = yes;
}
}
How about
const int MAX = 72460; // Or anything more than the highest legal value
inRange = (MAX + inputDate - startDate) % MAX <
(MAX + endDate - startDate) % MAX;
This assumes of course that all the dates are well formed (according to your specs).
This addresses the case where the start is "after" the end. (e.g. Friday is in range if start is Wednesday and end is Monday)
It may take a second to see (which probably isn't good, because readability is usually the most important) but I think it does work.
Here's the basic trick:
Legend:
0: Minimum time
M: Maximum time
S: Start time
1,2,3: Input Time test points
E: End Time
The S E => Not in range
2 In range
3 > E => Not in range
The S > E case
0 M
Original -1--E----2---S--3--
Add Max -------------------1--E----2---S--3--
Subtract StartDate ------1--E----2---S--3--
% Max S--3--1--E----2----
1 In range
2 > E => Not in range
3 In range
If you really want to go nuts (and be even more difficult to decipher)
const int MAX = 0x20000;
const int MASK = 0x1FFFF;
int maxMinusStart = MAX - startDate;
inRange = (maxMinusStart + inputDate) & MASK <
(maxMinusStart + endDate) & MASK;
which ought to be slightly faster (trading modulus for a bitwise and) which we can do since the value of MAX doesn't really matter (as long as it exceeds the maximum well-formed value) and we're free to choose one that makes our computations easy.
(And of course you can replace the < with a <= if that's what you really need)
There is some logic error with dates in that format. Since the month and year information is missing, you cannot know what calendar day is missing. e.g. 50755 might be Thursday March 12 2009, but it might just as well be exactly a week ago, or 18 weeks ahead. That for you could never be 100% sure if any date in that format is between any other 2 dates.
Here the condition of the inner if can never be true, since endDate < startDate:
if (endDate < startDate) {
if((inputDate + 72359) > startDate &&
(inputDate + 72359) < endDate) {
// never reached
inRange = yes;
}
The following if also can't be optimal, since the first part is always true and the second part is just identical to inputDate < endDate:
if((inputDate + 72359) > startDate &&
(inputDate + 72359) < (endDate + 72359))
I think you want something like this:
if (startDate < endDate)
inRange = (startDate < inputDate) && (inputDate < endDate);
else
inRange = (startDate < inputDate) || (inputDate < endDate);
you should use >= and <= if you really want it in range
say i pick this date 10000 or 72359, how you would handle this? it is in range or not?
also i didn't know value for startDate and endDate since you didn't initialize it, correct me if i were wrong, variable that didn't initialized will start with 0 or null or ''
so i assume the startDate = 10000 and endDate 72359
btw why you pick this kind of array (as int or string value?) why first value was day? not date example:
010000 -> date 1st of the month 00:00
312359 -> date 31th of the month 23:59
but it's up to you :D
so sorry if i were wrong i took algorithm class only on university and it was 5 years ago :D
A better approach might be to normalize your data converting all the day of the week values to be relative to the start date. Something like this:
const int dayScale = 10000; // scale factor for the day of the week
int NormalizeDate(int date, int startDay)
{
int day = (date / dayScale) - 1; // this would be a lot easier if Sunday was 0
int sday = startDay - 1;
if (day < sday)
day = (day + 7 - sday) % 7;
return ((day+1) * dayScale) + (date % dayScale);
}
int startDay = startDate / dayScale; // isolate the day of the week
int normalizedStartDate = NormalizeDate(startDate, startDay);
int normalizedEndDate = NormalizeDate(endDate, startDay);
int normalizedInputDate = NormalizeDate(inputDate, startDay);
inRange = normalizedInputDate >= normalizedStartDate &&
normalizedInputDate <= normalizedEndDate;
I am pretty sure this will work as written. In any case, the concept is cleaner that multiple comparisons.
The simplest solution i found is this:
said x your generic time and S, E the start and end time respectively (with 0 < S,E < T):
f(x) = [(x-S) * (x-E) * (E-S) < 0]
This function returns TRUE if x is in between the start and end time, and FALSE otherwise.
It will also take care of start time bigger than end time (i.e. you start working at 20:00 and finish at 04:00, 23:13 will return TRUE)
i must say, considering the multiplications, it could not be the most efficient in terms of speed, but it is definitely the most compact (and pretty IMHO)
EDIT:
i found a much more elegant and efficient solution:
f(x) = (x<S) XOR (x<E) XOR (E<S)
you can substitute XOR with the "different" operator ( != )
I explain it:
The first formula comes from the considering the relation inequality study:
if S < E:
...............S.....E..........
(x-S)----------+++++++++++++++++
(x-E)----------------+++++++++++
(E-S)+++++++++++++++++++++++++++
total++++++++++------+++++++++++
so, the total is negative if x is in between S and E
if S > E:
...............E.....S..........
(x-S)----------------+++++++++++
(x-E)----------+++++++++++++++++
(E-S)---------------------------
total----------++++++-----------
so, the total is negative if x is bigger than S or smaller than E
To reach the final equation, you decompose the first formula in 3 terms:
(x-S)<0 => x<S
(x-E)<0 => x<E
(E-S)<0 => E<S
the product of these terms is negative only if they are all negative (true, true, true) or only one is negative and the other are positive (true, false, false, but the order does not matter)
Therefore the problem can be solved via
f(x) = (x<S) != (x<E) != (E<S)
These solution can be applied to any similar problem with periodic system, such as checking if the angle x is inside the arc formed by the two angles S and E.
Just make sure that all the variable are between 0 and the period of your system (2PI for arcs in a circle, 24h for hours, 24*60*60 for the seconds count of a day.....and so on)

Resources