Author Topic: week number  (Read 17211 times)

jbryant87

  • Newbie
  • Posts: 11
  • I'm a llama!
    • View Profile
week number
« on: January 04, 2018, 12:40:55 AM »
Does anyone know of a quick method of using the RTC to generate a week number 1-52?

garysdickinson

  • Hero Member
  • Posts: 502
  • Old PLC Coder
    • View Profile
Re:week number
« Reply #1 on: January 04, 2018, 07:04:27 PM »
"Quick" is up to you to decide.

If you start counting at 0 until you get to the current day of the year, then you know how many days have passed since the first of the year.  Take that number and divide it by 7 and add 1 to get the week into the year that the current day represents.

OK.  Pretty simple.

The more difficult bit is to quickly calculate the day number within a year based on knowing the month, day of month and year.  Oh and you need to know the number of days in each month and you need to know if you need to deal with February that may have 28 or 29 days.

The solution is "Julian Dates". I'll present the TBASIC algorithm to calculate Julian Dates in the next post.  

Gary D*ckinson
« Last Edit: January 04, 2018, 07:47:53 PM by garysdickinson »

garysdickinson

  • Hero Member
  • Posts: 502
  • Old PLC Coder
    • View Profile
Julian Date Algorithm for TRI PLC
« Reply #2 on: January 04, 2018, 07:44:41 PM »
This is the PLC code that I use to calculate the Julian date:

' GenJD - custom function to compute Julian Date.
'  Algorithm agrees with USNO (United States Naval Observatory) online Julian Date Converter
'  at http://aa.usno.navy.mil/data/docs/JulianDate.php
'
' 9/17/2013 is JD 2456553
'
' On entry:
'   I = year 2013
'   J = month 1..12
'   K = day of month
'
' On exit:
'   A holds Julian date
'
A = K-32075+1461*(I+4800+(J-14)/12)/4+367*(J-2-(J-14)/12*12) /12-3*((I+4900+(J-14)/12)/100)/4
[/color]

I didn't write this stuff, I just trans-coded it form FORTRAN to TBASIC.

OK how to you use it to calculate how many days into 2018 the date, February 1 occurs.

Set: I = 2018   ' year
Set: J = 2         ' month
Set: K = 1        ' day

Then run the algorithm.  The answer should be 2458150.

Now calculate the Julian date for January 1, 2018. This answer should be 2458119.

Now you can calculate how many days into 2018 February 1st is by a bit of subtraction:

31 = 2458150 - 2458119

Now you know that you February 1, 2018 is the 31 day in 2018 (the first day is day 0).

And if you will find that a bit of division will give you the week into 2018 that February 1 is the 5th week in 2018:

5 = 31 / 7 + 1

Pretty slick.  

If you definition of week number is for weeks that start on a set day like Monday, then you will need to adjust the algorithm.  2018 started on a Monday, but 2019 will not.

Work weeks or factory weeks, usually, have some interesting rules. Google "ISO 8601" for the international standards for counting weeks within a year.  I'll bet you didn't know the first week in the year must have a Thursday...otherwise the first few days of the year belong to the last week of the previous year.  Obviously!

I'll give you a bit of a hint about what you might consider.  The Julian date algorithm starts at the following date:  Tuesday, 4713 B.C. Jan 2

Day 0 was Tuesday.   Every seven days since Day 0 will be a Tuesday.  So you can take any Julian date and MOD it by 7 and the resulting value (0..7) will represent the days Tuesday, Wednesday..Monday.

Best regards

Gary D*ckinson
« Last Edit: January 05, 2018, 12:10:28 PM by garysdickinson »

jbryant87

  • Newbie
  • Posts: 11
  • I'm a llama!
    • View Profile
Re:week number
« Reply #3 on: January 08, 2018, 11:25:00 PM »
Thbaks for the reply.

I also did some digging and found a calculation for the ISO week number using the ordinal date and day of week.
I converted to work in trilogi and it workly just fine.
It also allows for the leap year.
  Code below for reference

'PUT DATE IN M$
M$ = STR$(DATE[1])
'DIVIDE YEAR NUMBER BY 4
M# =VAL(M$)/4.0
'PUT NUMBER INTO STRING
N$ = STR$(M#)
'PUT FIRST DECIMAL PLACE INTO O$
O$ = MID$(N$,6,1)
'IF DECIMAL IS ZERO, THEN SET LEAP YEAR
IF VAL(O$) = O THEN
SETIO LEAP_YEAR
ELSE CLRIO LEAP_YEAR
ENDIF
'CALCULATE ORDINAL DAY
IF (DATE[2])=1 THEN P=0 ENDIF
IF TESTIO (LEAP_YEAR) = 0 & (DATE[2])=2 THEN P=(DATE[3]+31) ENDIF
IF TESTIO (LEAP_YEAR) = 0 & (DATE[2])=3 THEN P=(DATE[3]+59) ENDIF
IF TESTIO (LEAP_YEAR) = 0 & (DATE[2])=4 THEN P=(DATE[3]+90) ENDIF
IF TESTIO (LEAP_YEAR) = 0 & (DATE[2])=5 THEN P=(DATE[3]+120) ENDIF
IF TESTIO (LEAP_YEAR) = 0 & (DATE[2])=6 THEN P=(DATE[3]+151) ENDIF
IF TESTIO (LEAP_YEAR) = 0 & (DATE[2])=7 THEN P=(DATE[3]+181) ENDIF
IF TESTIO (LEAP_YEAR) = 0 & (DATE[2])=8 THEN P=(DATE[3]+212) ENDIF
IF TESTIO (LEAP_YEAR) = 0 & (DATE[2])=9 THEN P=(DATE[3]+243) ENDIF
IF TESTIO (LEAP_YEAR) = 0 & (DATE[2])=10 THEN P=(DATE[3]+273) ENDIF
IF TESTIO (LEAP_YEAR) = 0 & (DATE[2])=11 THEN P=(DATE[3]+304) ENDIF
IF TESTIO (LEAP_YEAR) = 0 & (DATE[2])=12 THEN P=(DATE[3]+334) ENDIF
IF TESTIO (LEAP_YEAR) = 1 & (DATE[2])=2 THEN P=(DATE[3]+31) ENDIF
IF TESTIO (LEAP_YEAR) = 1 & (DATE[2])=3 THEN P=(DATE[3]+60) ENDIF
IF TESTIO (LEAP_YEAR) = 1 & (DATE[2])=4 THEN P=(DATE[3]+91) ENDIF
IF TESTIO (LEAP_YEAR) = 1 & (DATE[2])=5 THEN P=(DATE[3]+121) ENDIF
IF TESTIO (LEAP_YEAR) = 1 & (DATE[2])=6 THEN P=(DATE[3]+152) ENDIF
IF TESTIO (LEAP_YEAR) = 1 & (DATE[2])=7 THEN P=(DATE[3]+182) ENDIF
IF TESTIO (LEAP_YEAR) = 1 & (DATE[2])=8 THEN P=(DATE[3]+213) ENDIF
IF TESTIO (LEAP_YEAR) = 1 & (DATE[2])=9 THEN P=(DATE[3]+244) ENDIF
IF TESTIO (LEAP_YEAR) = 1 & (DATE[2])=10 THEN P=(DATE[3]+274) ENDIF
IF TESTIO (LEAP_YEAR) = 1 & (DATE[2])=11 THEN P=(DATE[3]+305) ENDIF
IF TESTIO (LEAP_YEAR) = 1 & (DATE[2])=12 THEN P=(DATE[3]+335) ENDIF

'ORDINAL DAY + CURRENT DAY
Q = P + (DATE[3])
'RESULT MINUS DAY OF WEEK NUMBER * 10
R = (Q - (DATE[4])) + 10
'RESULT DIVIDE BY SEVEN, ROUNDED NUMBER IS WEEK NUMBER
S = R/7
S$ = STR$(S,2)

garysdickinson

  • Hero Member
  • Posts: 502
  • Old PLC Coder
    • View Profile
Re:week number
« Reply #4 on: January 09, 2018, 08:00:23 PM »
I am glad that you have a good working algorithm.

As a suggestion, there are more direct ways to determine if something can be divided by 4 without a remainder.

Your code sample:

'PUT DATE IN M$
M$ = STR$(DATE[1])
'DIVIDE YEAR NUMBER BY 4
M# =VAL(M$)/4.0
'PUT NUMBER INTO STRING
N$ = STR$(M#)
'PUT FIRST DECIMAL PLACE INTO O$
O$ = MID$(N$,6,1)
'IF DECIMAL IS ZERO, THEN SET LEAP YEAR
IF VAL(O$) = O THEN
SETIO LEAP_YEAR
ELSE CLRIO LEAP_YEAR
ENDIF
[/color]

Could probably be written in a much more direct way using the modulo (MOD) operator:

If (DATE[1] MOD 4)
   ClrIO LEAP_YEAR
else
   SetIO LEAP_YEAR
endif
[/color]

I'm certain that you are aware that your algorithm for determining leap year is not technically correct as it will fail on some years that are divisible by 100.

The only other thing that you should give a little bit of thought to is the accessing of the DATE[] and TIME[] registers.  The issue is that you access these registers multiple times in your algorithm.  The values in these registers are updated by PLC low-level firmware and it is possible for the register values to change between the first and last access that your CF makes to these registers.  For the DATE[] registers these changes tend to happen at the last second of the day.  The worst case is on the last day of the year 31-12-2018 the registers will update to 1-1-2019 and it is possible for your PLC software to read a mixture of the old and new date as the registers are updated one at a time.

One approach is to use the SETSYSTEM 253,0    to shut down updates of the RTC registers, read all of the DATE[] registers and store the values, store them in DM[] and then use the SETSYSTEM 253,1 statement to turn back on the RTC registers.

Another approach is to NOT access the DATE[] registers around midnight.

Best regards,

Gary D*ckinson
« Last Edit: January 09, 2018, 08:01:41 PM by garysdickinson »

jbryant87

  • Newbie
  • Posts: 11
  • I'm a llama!
    • View Profile
Re:week number
« Reply #5 on: January 09, 2018, 09:25:46 PM »
I was aware of the inacuracy of the leap year calc.
I suppose the better way to caculate would be below:

If (DATE[1] MOD 400)&(DATE[1] MOD 100)&(DATE[1] MOD 4)|(DATE[1] MOD 400)&(DATE[1] MOD 4)

So if the date is divisible by 400 and 100, or 4 and 400.

Not that ill be around to see this work, but it works in the simulation

garysdickinson

  • Hero Member
  • Posts: 502
  • Old PLC Coder
    • View Profile
Re:week number
« Reply #6 on: January 10, 2018, 09:08:10 AM »
I think that you should stick with the simpler algorithm that treats any year that is divisble by 4 without a remainder as being a leap year.

That algorithm will pick the following years as being leap years. The red years are in error:

2000 2004 2008 2012 2016 2020 2024 2028
2032 2036 2040 2044 2048 2052 2056 2060
2064 2068 2072 2076 2080 2084 2088 2092
2096 2100

Your algorithm makes the same mistake at the year 2100:

2000 2004 2008 2012 2016 2020 2024 2028
2032 2036 2040 2044 2048 2052 2056 2060
2064 2068 2072 2076 2080 2084 2088 2092
2096 2100

This is the test code that I ran in simulation to check your algorithm:

c = 8   ' column count
for i = 2000 to 2100
   If (i MOD 400)&(i MOD 100)&(i MOD 4)|(i MOD 400)&(i MOD 4)
      continue      ' not a leap year
   endif

   ' this is a leap year, so print it out
   '
   c = c - 1
   if (c <> 0)
      print #1 str$(i);" ";
   else
      print #1 str$(i)
      c = 8
   endif
next
[/color]

As this code is run in simulation, the print #1 statements are "output" to a window that I can inspect and copy into documentation.

Gary D*ckinson
« Last Edit: January 10, 2018, 09:19:36 AM by garysdickinson »