A tiny New Year's programming challenge - Printable Version +- HP Forums (https://www.hpmuseum.org/forum) +-- Forum: HP Calculators (and very old HP Computers) (/forum-3.html) +--- Forum: General Forum (/forum-4.html) +--- Thread: A tiny New Year's programming challenge (/thread-2765.html) Pages: 1 2 A tiny New Year's programming challenge - Dieter - 01-01-2015 04:51 PM The year has just begun - maybe it's time for a programming challenge that deals with dates and calendars, and even especially with the days around New Year. ;-) Instead of a particular date (e.g. "14.03.2014"), referring to a certain week of the year ("week 11/2014") often makes more sense, and so week numbers are commonly used in trade and business (at least over here in Europe). There is a international standard (ISO 8601) that defines the start of the first week of the year: it's the one with the major part (i.e. at least four days) belonging to January. So week #1 may start as late as 4 Jan or as early as 29 Dec of the previous year. Generally, weeks are defined as starting on Monday and ending on the following Sunday. More details can be found on Wikipedia. And here is your task: Write a program that accepts a valid Gregorian Date and returns the corresponding ISO week number, as well as the day number within that week. Some test cases: Code: ```04 Jul 1979  => week 27/1979, day 3 02 Jan 2010  => week 53/2009, day 6 04 Jan 2010  => week 01/2010, day 1 01 Jan 2012  => week 52/2011, day 7   // edit: year corrected, of course it's not 2012 #-) 31 Dec 2012  => week 01/2013, day 1``` The basic idea is very simple: determine the start of week #1, calculate the number of days between that Monday and the given date, and finally divide the result by 7. However, this may result in a negative value for the first days of a year, or it may return week #53 – which may or may not exist in that year. This is the "...challenging" part of the challenge. ;-) Maybe your program even accepts any input that is valid for the current date format setting (i.e. dd.mmyyyy, mm.ddyyyy or even yyyy.mmdd), and the output is formatted according to the ISO standard (e.g. 1979-W27-3). Of course calculators with date functions are very helpful here, that's why I would suggest using a 34s. Which does not mean that others cannot be used as well. ;-) Enjoy! Dieter RE: A tiny New Year's programming challenge - Gerald H - 01-01-2015 06:00 PM For HP 48G, 49G & 49G+ the progs are available here: http://www.hpcalc.org/hp49/utils/time/isodatev2.zip RE: A tiny New Year's programming challenge - Dieter - 01-02-2015 07:17 AM (01-01-2015 06:00 PM)Gerald H Wrote:  For HP 48G, 49G & 49G+ the progs are available here: Thank you – but I cannot find any source code. So how did you do it? Dieter RE: A tiny New Year's programming challenge - Gerald H - 01-02-2015 10:29 AM Sorry, I made the library in 2004 & don't recollect how the calculation works - I'm not really interested in opening an old can of worms, but would be grateful for improvements & bug alerts. While on this topic, why oh why didn't ISO count from 0 to 11 & 0 to 6? RE: A tiny New Year's programming challenge - Gilles - 01-02-2015 10:16 PM Hi There is one error in your test cases : 01 Jan 2012 => week 52/2011, day 7 Here is an HP49/50 version Work both with the flag 42 set (dd/mm/yy) or not (mm/dd/yy) Date must be entered with the current HP50g format.Ex 19.081999 IsoWk -> "1999-W33-4" (flag 42 set jj.mmaaa) 12.312012 IsoWk -> "2013-W1-1" (flag 42 not set mm.jjaaaa) Code: ``` IsoWk « 0 400 0 0 → d2 d1 Delta y y1  «   -1E-6 1E-6 FOR N    d2 100 * FP 100 / DUP 'y' STO 1.01 + N + DUP 12.00 TSTR    N 'y' STO+       CASE 1 3 SUB     DUP "MON" == THEN 0  END     DUP "TUE" == THEN -1 END     DUP "WED" == THEN -2 END     DUP "THU" == THEN -3 END     DUP "FRI" == THEN 3  END     DUP "SAT" == THEN 2  END     DUP "SUN" == THEN 1  END     0    END     NIP DATE+    IF DUP d2 DDAYS DUP 0 ≥ THEN     IF DUP Delta < THEN 'Delta' STO 'd1' STO y 'y1' STO     ELSE DROP2 END    ELSE DROP2 END   1E-6 STEP   y1 →STR 4 8 SUB "-W" +   d1 d2 DDAYS 7 IDIV2 1 + UNROT 1 + + "-" + SWAP +   »  »``` RE: A tiny New Year's programming challenge - Dieter - 01-03-2015 06:58 AM (01-02-2015 10:16 PM)Gilles Wrote:  There is one error in your test cases : 01 Jan 2012 => week 52/2011, day 7 Yes, of course. #-) Sorry, just a typo. (01-02-2015 10:16 PM)Gilles Wrote:  Here is an HP49/50 version Work both with the flag 42 set (dd/mm/yy) or not (mm/dd/yy) Great – thank you very much. I have to admit I never used any kind of RPL calculator, so it's a bit difficult for me to figure out how your program works. Would you mind explaining the algorithm, please? (01-02-2015 10:16 PM)Gilles Wrote:  Date must be entered with the current HP50g format.Ex 19.081999 IsoWk -> "1999-W33-4" (flag 42 set jj.mmaaa) 12.312012 IsoWk -> "2013-W1-1" (flag 42 not set mm.jjaaaa) Great. If the week was returned with two digits ("2013-W01-1") the result would even be perfectly ISO-compliant. BTW I like the way you write the date formats with j, m and a. ;-) Dieter, tt.mmjjjj RE: A tiny New Year's programming challenge - Dieter - 01-03-2015 07:04 AM (01-02-2015 10:29 AM)Gerald H Wrote:  While on this topic, why oh why didn't ISO count from 0 to 11 & 0 to 6? I think it's simply because humans usually start counting at 1, not 0. The week is an ordinal number, so the first week is week 1, just as the third day in a week is day 3. I suppose it wouldn't be very helpful if the fifth day of week twenty would be described as week 19, day 4. ;-) Dieter RE: A tiny New Year's programming challenge - Gilles - 01-03-2015 01:24 PM (01-03-2015 06:58 AM)Dieter Wrote:  I have to admit I never used any kind of RPL calculator, so it's a bit difficult for me to figure out how your program works. Would you mind explaining the algorithm, please? Hi dieter, I first tried to handle all the exceptions (negative result etc.) but it looks like marmalade ! So I change to handle all the cases in a single way. The general idea is : Giving a date d2 (dd.mmyyy or mm.ddyyyy format) : -Calculate the first of january for y-1, y , y+1 (the 1e-6 step loop) -for each year, calculte what day is the 1 january (mon, tue, wed ...). For this use the TSTR command and then add or substrat -3...3 days (the CASE serie) - with this, calculate what is the first day of the fisrt week of the year. For this use the DATE+ command - Then calculate the difference between d2 and the calculate fisrt day of the first week of the year. For this use the DDAYS command -> If the result is negative, it is not the 'good' year -> If it is positive, then the smallest delta is the good one (Delta) . You've get the correct year reference (y1) and the correct first day of first week of the year (d1) To finish: - Calculate the number of days between d1 and d2 (DDAYS) - Interger division by 7 (IDIV2 returns integer parts and remainder) - add 1 to each and you get the result Note that there is nothing special to handle dd.mmaaaa or mm.ddaaaa format. The 50G manage this (DATE+ DDAYS TSTR) and by chance (?) 1.012015 means the same thing in the 2 formats (first january) RE: A tiny New Year's programming challenge - Dieter - 01-03-2015 06:20 PM (01-03-2015 01:24 PM)Gilles Wrote:  I first tried to handle all the exceptions (negative result etc.) but it looks like marmalade ! So I change to handle all the cases in a single way. The general idea is : ... [ snip general idea ] ... Interesting approach. Just for comparison, I did it this way: Since 4 January is always in week 01, simply determine its weekday (Mon=0 ... Sun=6) and count back this number of days. This is the start of the year's first week. Determine the difference between this day and the entered date. If the result is < 0       add 7 days to get the day number. The week is the last of the previous year, i.e. 52 or 53. else      divide by 7 and use the integer part and the remainder (both +1) to get week and day number.      If this result is week 53           check if it exists           If it doesn't                return week 1 of the next year. My 34s program includes a short subroutine that returns the last week# for a given year. By the way: (01-03-2015 01:24 PM)Gilles Wrote:  - Interger division by 7 (IDIV2 returns integer parts and remainder) Yes, that's a very handy and useful command. Some time ago I suggested such a command that returns both the integer part and the remainder for the 34s (like DIV in x86 assembly language), but it did not make it into the final firmware. ;-\ (01-03-2015 01:24 PM)Gilles Wrote:  Note that there is nothing special to handle dd.mmaaaa or mm.ddaaaa format. The 50G manage this (DATE+ DDAYS TSTR) and by chance (?) 1.012015 means the same thing in the 2 formats (first january) Yes, I used this trick as well. ;-) On the other hand the 34s has a special DATE→ command that assembles year, month and day (given individually) into dd.mmyyyy or mm.ddyyyy or yyyy.mmdd, depending on the current date mode setting. Dieter RE: A tiny New Year's programming challenge - Dieter - 01-04-2015 06:37 PM (01-01-2015 04:51 PM)Dieter Wrote:  And here is your task: Write a program that accepts a valid Gregorian Date and returns the corresponding ISO week number, as well as the day number within that week. Just before week 2 starts, here is what I got with my 34s. Dates may be entered in dd.mmyyyy or mm.ddyyyy format. Code: ```LBL "KW"  // "KW" is the common German abbreviation for "Kalenderwoche" ;-) LocR 01   // a single temporary register (and one flag) will do D->J RCL L YEAR STO.00    // save year in R.00 SDR 004 INC X SDR 002 INC X     // build 01.01yyyy D->J      // JD(1 Jan) #003 +         // JD(4 Jan), which is always in week 1 #007 IDIV RCL*L     // = JD(4 Jan) - JD(4 Jan) mod 7 = day 1 of week 1 -         // # of days since day 1 of week 1 x>=0?     // date not before day 1 of week 1? GTO 51 #007 +         // adjust negative day# by adding back 7 days DEC.00    // week is last of previous year RCL.00 XEQ 53    // get last week# of previous year GTO 52    // and exit LBL 51    // standard case RCL X #007 RMDR x<> Y #007 IDIV      // day# in Y, week# in X RCL.00 XEQ 53    // get last week of year x? Y α"0" DROP αIP X α"-" αIP Y     // => "yyyy-Www-d" TOP?      // if directly called by user, VWα+X     // display formatted result RTN       // in any case return ww.yyyy in X and day# in Y LBL 53    // input: year CF.01     // output: 51 or 52 LEAP?     // = last week# (0-based) SF.01 SDR 004 INC X SDR 002 INC X     // build 01.01yyyy WDAY      // get weekday of 1 Jan #004 - x=0?      // is it Thursday? SKIP 002  // then continue with x=0 FS?C.01   // if not and leap year: INC X     // set result to 0 also for Wednesday (was 3-4=-1) SIGN ABS       // turn any non-zero value into 1 +/- #052 +         // last week# = 52-0 resp. 52-1 RTN R.00: year Flag .01: set if leap year, clear otherwise 04.071979 XEQ"KW" => 1979-W27-3   27.1979  [x<>y]  3 01.012012 XEQ"KW" => 2011-W52-7   52.2011  [x<>y]  7 31.122004 XEQ"KW" => 2004-W53-5   53.2004  [x<>y]  5 31.122012 XEQ"KW" => 2013-W01-1    1.2013  [x<>y]  1``` The subroutine at LBL 53 returns the last week# (zero-based) for a given year, i.e. 51 or 52. It checks whether the year starts with a Thursday or – if it is a leap year – with a Wednesday. These years have 53 weeks, otherwise 52. Dieter RE: A tiny New Year's programming challenge - Werner - 01-05-2015 10:55 AM Quote:The subroutine at LBL 53 returns the last week# (zero-based) for a given year, i.e. 51 or 52. It checks whether the year starts with a Thursday or – if it is a leap year – with a Wednesday. These years have 53 weeks, otherwise 52. Isn't it easier then to test Jan 1st of Y+1? If it's a Friday, Y has 53 weeks, else it's 52. No need for leap year testing. Werner RE: A tiny New Year's programming challenge - Dieter - 01-05-2015 07:45 PM (01-05-2015 10:55 AM)Werner Wrote:  Isn't it easier then to test Jan 1st of Y+1? If it's a Friday, Y has 53 weeks, else it's 52. No need for leap year testing. Sorry, but this does not cover all years with 53 weeks. Example: Y=2004. Here 1 Jan 2005 is a Saturday, not a Friday, but nevertheless 2004 had 53 weeks. The same is true for 2032 (1 Jan 2033 is a Saturday, but 2032 will have 53 weeks as well). Checking whether 1 January of the following year is a Friday means that the current year ends on a Thursday. This is only one of the two possible conditions for a 53-week-year. A common 53-week-year starts and ends on a Thursday. The other possible condition is a leap year starting on a Thursday and thus ending on a Friday. Which means that New Year of the following year is a Saturday, as shown in the two examples above. If a year has 53 weeks, it starts and/or ends on a Thursday. In common years both 1 Jan and 31 Dec are Thursdays. On the other hand, leap years with 53 weeks either start on a Wednesday (and therefore end on a Thursday – this case is covered by your suggestion), but they may as well start on a Thursday and end on a Friday. And this is what the routine checks: Is 1 Jan a Thursday OR (is it a leap year AND 1 Jan is a Wednesday). Cf. Wikipedia. Of course it is possible that the two tests can be implemented more elegantly. Any suggestions? Dieter RE: A tiny New Year's programming challenge - Gerald H - 01-05-2015 08:29 PM (01-02-2015 07:17 AM)Dieter Wrote:   (01-01-2015 06:00 PM)Gerald H Wrote:  For HP 48G, 49G & 49G+ the progs are available here: Thank you – but I cannot find any source code. So how did you do it? Dieter At long last the source for the 49G programme: GISO :: CK1&Dispatch BINT1 :: ID x003 DUP %20 DATE+DAYS % 10.0000001 %+ a%>\$ BINT6 BINT9 SUB\$ FPTR2 ^S>Z UNROTOVER DDAYS DUP %0< ITE :: DROPSWAP FPTR2 ^Z>R %1- DUP FPTR2 ^R>Z 3UNROLL % 1000000. %/ % 18.08 %+ ID x003 SWAPDROPSWAP DDAYS ; SWAPDROP SWAP FPTR2 ^Z>S \$>ID SWAP COERCE BINT7 #/ #1+ #>\$ DUPLEN\$ #3= IT :: CHR_0 >H\$ ; \$>ID SWP1+ FPTR2 ^#>Z TWO{}N {}>TAG_ TWO{}N {}>TAG_ ; ; ISOG :: CK1&Dispatch BINT13 :: TAG>_ palparse DROP FPTR2 ^CK1Z FPTR2 ^Z>R SWAP DUPTYPETAG? NcaseTYPEERR TAG>_ palparse DROP FPTR2 ^CK1Z FPTR2 ^Z>R SWAP FPTR2 ^CK1Z FPTR2 ^Z>R 3UNROLL %1- SWAP % 1000000. %/ % 18.08 %+ ID x003 SWAPDROPSWAP %7 %* ROT %+ %1- DATE+DAYS ; ; x002 { "MO" "TU" "WE" "TH" "FR" "SA" "SU" } x003 :: DUP %100 %* %FP %100 %/ % 4.010001 %+ 2DUP DDAYS %7 %< ITE :: ID x002 3PICK %1 TIMESTR BINT3 1_#1-SUB\$ EQUALPOSCOMP BINT3 #> ; TRUE IT :: % .000001 %- ; ID x002 OVER %1 TIMESTR BINT3 1_#1-SUB\$ EQUALPOSCOMP #1- UNCOERCE %CHS DATE+DAYS ; RE: A tiny New Year's programming challenge - Werner - 01-05-2015 09:21 PM @ Dieter: ah I see, I only went by your explanation ( that I quoted before), but I read it wrongly as non-leap years starting on a Thursday and leap years starting on a Wednesday .. RE: A tiny New Year's programming challenge - Werner - 01-06-2015 04:11 PM As to making the check more elegant: all I have been able to come up with so far is to try and test for Mondays instead of Thursdays, if the DOW routine returns 0. for Mondays, that is. A year has 53 weeks if and only if it starts or ends with a Thursday. A year that starts with a Thursday means 2.02yyyy will be a Monday. For a year that ends with a Thursday: Unfortunately, no xx.xxyyyy for x=3..12 (and Jan and Feb of y+1) are Mondays. No aa.bbyyyy exists for which both aa.bbyyyy and bb.aayyyy are Mondays (so that we can stay independent from the date setting). So I test 2.02yy+1 for a Tuesday instead.. Here's the two subroutines for 48/49/50 compatibles: Code: ```@ Day Of Week (as a number) @ In: date (MM.DDYYYY or DD.MMYYYY) @ Out: 0-6, with 0 being Monday  DOW  \<< 1.0119 SWAP DDAYS 7. MOD \>>``` Code: ```@ number of weeks in year Y @ In: Year @ Out: 52 or 53 @ 53 if year starts or ends in a Thursday, else 52  YW  \<<    6. ALOG / 2.02 + DUP DOW       @ year starts on Thursday? = 2.02yyyy is Mo    SWAP -6. ALOG + DOW 1. \=/    @ year ends on Thursday?   = 2.02yy+1 is Tu    * NOT 52. +    \>>``` Cheers, Werner RE: A tiny New Year's programming challenge - Dieter - 01-07-2015 12:21 AM (01-06-2015 04:11 PM)Werner Wrote:  Here's the two subroutines for 48/49/50 compatibles: And here's a shorter and slightly more elegant version for the 34s that replaces the original routine at label 53. It re-uses your idea of emulating a logical OR by a simple multiplication. Code: ```LBL 53    // input: year CF.01     // output: 51 or 52 LEAP?     // = last week# (0-based) SF.01 SDR 004 INC X SDR 002 INC X     // build 01.01yyyy WDAY      // get weekday of 1 Jan #004 -         // = zero if year starts on a Thursday (or -1 if on a Wednesday) ENTER FS?C.01 INC X     // = zero for a Wednesday in a leap year *         // = zero if Thursday or (leap year and Wednesday), otherwise positive SIGN      // turn any non-zero value into 1 +/- #052 +         // last week# = 52-0 resp. 52-1 RTN``` Since local flags are initially cleared, the CF.01 in the second line may even be omitted. Dieter RE: A tiny New Year's programming challenge - walter b - 01-07-2015 02:55 AM (01-07-2015 12:21 AM)Dieter Wrote:  It re-uses your idea of emulating a logical OR by a simple multiplication. So far, I thought a multiplication is equivalent to a logical AND. What did I miss? d:-? RE: A tiny New Year's programming challenge - Thomas Klemm - 01-07-2015 04:50 AM (01-07-2015 02:55 AM)walter b Wrote:  So far, I thought a multiplication is equivalent to a logical AND. What did I miss? De Morgan's laws RE: A tiny New Year's programming challenge - walter b - 01-07-2015 06:24 AM (01-07-2015 02:55 AM)walter b Wrote:   (01-07-2015 12:21 AM)Dieter Wrote:  It re-uses your idea of emulating a logical OR by a simple multiplication. So far, I thought a multiplication is equivalent to a logical AND. What did I miss? Ok, seems I forgot something I knew 35 years ago. I've to apologize for my bad memory. d:-) RE: A tiny New Year's programming challenge - Werner - 01-07-2015 08:19 AM Dieter: what's wrong with this? My first ever WP34S try! And no fiddling with flags. Code: ```LBL 53 # 202 SDL 004 + ENTER INC X SDR 006 WDAY DEC X DEC X X<>Y SDR 006 WDAY DEC X * NOT #051 + RTN```