How do I make leap years work? - time

I have some code here in TCL that tries to measure the time between two dates.
The two first work, but as you see the last one not working; it counts totally wrong, as there can only be 12 month in a year, but this has more than that.
Anyway, I think that the leap year is the problem, but I'm not sure. Can you help?
proc isTimeAgo {t1} {
set t2 "1387524660"
#set t2 [clock seconds]
set cnt [expr {(($t2 - $t1) / 31536000)}]
set cur [clock add $t1 $cnt years]
set res {}
foreach unit {years months weeks days hours minutes seconds} {
while {$cur <= $t2} {
set cur [clock add $cur 1 $unit]
incr cnt
}
set cur [clock add $cur -1 $unit]
incr cnt -1
if {$cnt} {
lappend res $cnt $unit
}
set cnt 0
}
return $res
}
puts "1: [isTimeAgo "1355988659"]"
puts "2: [isTimeAgo "1355988660"]"
puts "3: [isTimeAgo "1355988661"]"
proc days_per_month year {
set leap [expr {($year%4)==0 && (!($year%400) || ($year%100))}]
set days [list 31 [expr {$leap ? 29 : 28}] 31 30 31 30 31 31 30 31 30 31]
return $days
}
The result of this is:
1: 1 years 1 seconds <- Correct
2: 1 years <- Correct
3: 11 months 4 weeks 1 days 23 hours 59 minutes 59 seconds <- Wrong

Date and time arithmetic is astonishingly hard to get right because what people mean by it is so thoroughly uncertain. When adding an interval to a time, you've got to add the years before the months (because of leap years), the months before the days (because of varying month lengths), and the days before the times (because of DST changes). This is sufficiently tricky that we've got a command that handles the complexity: clock add (requires at least Tcl 8.5). Going in the reverse direction? It's a matter of trying each unit until you overshoot.
proc getInterval {from to} {
set result {}
foreach unit {year month week day hour minute second} {
set n 0
while 1 {
set new [clock add $from 1 $unit]
if {$new > $to} break
set from $new
incr n
}
lappend result $n $unit
}
return $result
}
Let's try that out:
% getInterval 1355988659 [clock seconds]
1 year 4 month 0 week 6 day 6 hour 52 minute 39 second
I suppose we could add in ensuring that from precedes to and omit items that are zero. I'll leave those as an exercise.
Be aware that you might need to change the locale and timezone used by clock add to get the answer you are expecting (using the -locale and -timezone options, respectively). See the documentation for exactly what this may affect and some examples.

Related

console output of the current calendar month in Ruby

I need to output to the console the calendar of the current month in Ruby. The result should be similar to ncal on UNIX-like systems. I found a solution for C ++ but can't adapt for Ruby. So far, I only realized that I need to use nested loops to output the height and width. Tell me in which direction to move?
require 'date'
days = %w[Mun Tue Wed Thu Fri Sat Sun]
puts " #{Date::MONTHNAMES[Date.today.month]} #{Date.today.year}"
i = 0
start_month = (Date.today - Date.today.mday + 1).strftime("%a")
while i < days.size
print days[i]
j = 1
while j <= 31
if days[i] == start_month
print " #{j}"
end
j += 7
end
i += 1
puts
end
I'll take your solution so far, and try to give some specific pointers for how to progress with it - but of course, there are many different ways to approach this problem in general, so this is by no means the only approach!
The first critical issue (as you're aware!) is that you're only printing things for the row starting on the 1st of the month, due to this line:
if days[i] == start_month
Sticking with the current overall design, we know we'll need to print something for every line, so clearly a conditional like this isn't going to work. Let's try removing it.
Firstly, it will be more convenient to know which day of the week the month started on as a number, not a string, so we can easily calculate offsets against another day. Let's do that with:
# e.g. for 1st July 2021 this was a Thursday, so we get `4`.
start_of_month_weekday = (Date.today - Date.today.mday + 1).cwday
Next (and this is the crucial step!), we can use the above information to find out "which day of the month is it, on this day of the week?"
Here a first version of that calculation, incorporated into your solution so far:
require 'date'
days = %w[Mon Tue Wed Thu Fri Sat Sun]
puts " #{Date::MONTHNAMES[Date.today.month]} #{Date.today.year}"
i = 0
# e.g. for 1st July 2021 this was a Thursday, so we get `4`.
start_of_month_weekday = (Date.today - Date.today.mday + 1).cwday
while i < days.size
print days[i]
day_of_month = i - start_of_month_weekday + 2 # !!!
while day_of_month <= 31
print " #{day_of_month}"
day_of_month += 7
end
i += 1
puts
end
This outputs:
July 2021
Mon -2 5 12 19 26
Tue -1 6 13 20 27
Wed 0 7 14 21 28
Thu 1 8 15 22 29
Fri 2 9 16 23 30
Sat 3 10 17 24 31
Sun 4 11 18 25
Not bad! Now we're getting somewhere!
I'll leave you to figure out the rest 😉 .... But here are some clues, for what I'd tackle next:
This code, print " #{day_of_month}", needs to print a "blank space" if the day number is less than 1. This could be done with a simple if statement.
Similarly, since you want this calendar to line up neatly in a grid, you need this code to always print a something two characters wide. sprintf is your friend here! Check out the "Examples of width", about halfway down the page.
You've hardcoded 31 for the number of days in the month. This should be fixed, of course. (Use the Date library!)
It's funny how you used strftime("%a") in one place, yet constructed the calendar title awkwardly in the line above! 😄 Take a look at the documentation for formatting dates; it's extremely flexible. I think you can use: Date.today.strftime("%B %Y").
If you'd like to add some colour (or background colour?) to the current day of the month, consider doing something like this, or use a library to assist.
Using while loops works OK, but is quite un-rubyish. In 99% of cases, ruby has even better tools for the job; it's a very expressive language - iterators are king! (I'm guessing you first learned another language, before ruby? Seeing while loops, and/or for loops, is a dead giveaway that you're more familiar with a different language.) Instead of the outer while loop (while i < days.size), you could use days.each_with_index. And instead of the inner while loop (while j < 31), you could use day_of_month.step(31, 7) (how cool is that!!).
This is one way:
Construct a one-dimensional array, beginning with the daynames (Mon Tue ...).
Figure out a way to determine with how many "blanks" the month starts (these are days from the previous month. wday might help). Attach that amount of empty strings to the array.
Determine how many days the month has (hint Date.new(2021,7,-1), and attach all these daynumbers to the array.
Attach empty strings to the array until the size of the array is divisible by 7 (or better, calculate). Skip this if you're skipping the last bullet.
Convert all elements of this array to right-adjusted strings of size 3 or some-such.
Use each_slice(7) to slice the array into weeks.
If desired, transpose this array of week-slices to mimic the ncal output.
Thank you for your help, literally 10 hours and I figured it out thanks to you. I apologize once again for the initially incorrectly posed question.
With the help of hints, I assembled such a solution.
require 'date'
days = %w[Mon Tue Wed Thu Fri Sat Sun]
p days
blanks = Date.new(2021,7,1).wday - 1
blanks.times do
days.push(' ')
end
days_in_month = Date.new(2021, 7, -1).day
days_in_month
day = 1
while day <= days_in_month
days.push(day)
day += 1
end
unless (days.size % 7) == 0
days.push(' ')
end
days.join(', ')
new_arr = days.each_slice(7).to_a
puts"Массив дней: #{new_arr}"
for i in 0...7
for j in 0...new_arr.size
print " #{new_arr[j][i]}"
end
puts
end
require 'date'
# init
DAYS_ORDER = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
today = Date.today
month = today.month
year = today.year
first_day = Date.new(year, month, 1)
last_day = Date.new(year, month, -1)
hash_days = {}
# get all current months days and add to hash_days
first_day.upto(last_day) { |day| hash_days[day.day] = day.strftime('%a') }
# group by wday
grouped_hash = hash_days.group_by { |day| day.pop }.transform_values { |days| days.flatten }
# sort by wday from DAYS_ORDER
sorted_arr = grouped_hash.sort_by { |k, v| DAYS_ORDER.index(k) }
# rendering current month's calendar with mark current day
## title
print "\x1b[4m#{today.strftime("%B %Y")}\x1b[0m\n"
## calendar
indent = true
sorted_arr.each do |wday, days|
print wday
if days[0] != 1 && indent == true
print " "
else
indent = false
end
days.each do |value|
spaces = " " * (value > 9 ? 1 : 2)
str_day = spaces + value.to_s
current_day = "\x1b[1;31m#{str_day}\x1b[0m"
print value == today.day ? current_day : str_day
end
puts
end
view

Compute the number of seconds to a specific time in a specific Time Zone

I want to trigger a notification for all my users at a specific time in their time zone. I want to compute the delay the server should wait before firing the notification. I can compute the time at the users Time Zone using Time.now.in_time_zone(person.time_zone)
I can strip out the hours, minutes and seconds from that time and find out the seconds remaining to the specific time. However, I was wondering if there's a more elegant method where I could set 9:00 AM on today and tomorrow in a timezone and compare it with Time.now.in_time_zone(person.time_zone) and just find out the number of seconds using arithmetic operations in the ruby Time Class.
Or in short my question is: (was: before the downvote!)
How do I compute the number of seconds to the next 9:00 AM in New York?
What about this
next9am = Time.now.in_time_zone(person.time_zone).seconds_until_end_of_day + 3600 * 9
next9am -= 24 * 60 * 60 if Time.now.in_time_zone(person.time_zone).hour < 9
NOTIFICATION_HOUR = 9
local_time = Time.now.in_time_zone(person.time_zone)
desired_time = local_time.hour >= NOTIFICATION_HOUR ? local_time + 1.day : local_time
desired_time = Time.new(desired_time.year, desired_time.month, desired_time.day, NOTIFICATION_HOUR, 0, 0, desired_time.utc_offset)
return desired_time - local_time

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

calculates the average value contained in a file for every 16 seconds

I have a big file "values.txt" in which there are a lot of values. These values ​​can be found in the file valeur.txt as follow :
File: "values.txt"
************************************************** *****************************************
2
14
18
20
23
.
.
.
24
******************************************************************************************
I want to write a script that calculate the average of values ​​read from the file values.txt for every 16 seconds
i e : After each 16 seconds I will calculate the average of values ​​that I have read in the file values.txt
for exmpl :
To read the file "value.txt", we will take (16 * 4 = 64) seconds.
So, when the exuction of script is finished, it must show 4 values. because every 16 seconds it will calculate an average.
I tried to do things but the concept of time (16 seconds) I still can't manage it
let "i=0"
let"cum=0"
while read var
do
if [timer] # how I could control the period of 16 seconde ?
then
let "i = i +1"
let "cum = cum + var"
else # 16 seconds have elapsed, so i calculate the averag
let "avrg_var= cum /i"
echo "$avrg_var">> new_file.txt
fi
done < values.txt
Thank you in advance for your answers and suggestions
Try this:
while :
do
let "i=0"
let "cum=0"
while read var
do
let "i = i +1"
let "cum = cum + var"
done < /tmp/values.txt
let "avrg_var= cum /i"
echo "$avrg_var">> new_file.txt
sleep 16
done
This doesn't work on a timer, just sleeps 16 seconds between runs.
Write a script that averages the values once, and then you can run your script with the watch command assuming your on a *nix based system. The default is 2 seconds but you can change it to what ever you want and it will repeatedly run in the terminal until canceled

How to convert 24:00 hours to 12:00 format

I need help with my pseudocode assignment: convert 2400 hours to 12 format
Design an algorithm that will prompt for and receive the item expresses in 2400 format (e.g. 2305 Hours), convert it to 12 hour format (e.g. 11.05pm) and display sentinel time of 9999 is entered.
Prompt for hrs, mins
Get hrs, mins
DOWHILE(hrs NOT = 99) AND (mins NOT = 99) // If hrs & mins not = to 99 then it will run if not it will stop
IF (hrs = 00) THEN // midnight. 0030. It will + 12 and display 12:30am
format = am
time = hrs + 12
Display hrs, ":" , mins, format
ELSE
IF (hrs > 12) THEN // afternoon. 1630. It will – 12 and display 4:30pm
format = pm
hrs = hrs – 12
Display hrs, ":" , mins, format
ELSE
IF (hrs < 12) THEN // from midnight 0100 to 1159. It will display AM
format = am
Display hrs, ":" , mins, format
IF (hrs = 12) THEN // if format is 1230. It will display 1230PM
format = pm
Display hrs, ":" , mins, format
ENDIF
ENDIF
ENDIF
ENDIF
IF (hrs < 0) OR (hrs > 23) THEN // hrs less than 0 or more than 23 is error.
Display ‘Invalid hour input’
IF (mins < 0) OR (mins >59) THEN // mins less than 0 or more than 59 is error.
Display ‘Invalid mins input’
ENDIF
ENDIF
Prompt for hrs, mins // you prompt again , we are still in the loop until we hit 9999
Get hrs, mins
ENDDO // which stop here because it’s 9999
Am i doing correctly? Please advice. New student here! many thanks!
Well, depending upon how your professor expects your pseudocode to look like, what you have should work fine, I think. A few of the lines are a bit redundant, though. You could combine the out-of-bounds checking of the hours and minutes into one IF statement. You could then set your time variable to "am" by default, which would turn your IF - ELSE IF - ELSE to a single IF - ELSE. Oh, and not that I'm sure it matters much, but rather than using hours = hours + 12 when hours = 0, you could probably just do hours = 12. Again, what you have should work just fine, I think.
EDIT: Ah... again, not sure if this matters, but have a way to possibly terminate the program might be useful, too. Otherwise, you'll be stuck in your loop forever, it seems.
EDIT 2: Here's what I would do...
done = false
DOWHILE !done
PROMPT hours, minutes
GET hours, minutes
IF hours < 0 OR hours > 23 OR minutes < 0 OR minutes > 60
DISPLAY "Invalid Time"
ELSE
format = "AM"
IF hours > 12
format = "PM"
hours = hours - 12
ELSE IF hours == 0
hours = 12
ELSE IF hours == 12
format = "PM"
DISPLAY hours ":" minutes format
ENDIF
ENDIF
PROMPT "Are you done?"
GET done
ENDLOOP

Resources