HP Forums

Full Version: A tiny New Year's programming challenge
You're currently viewing a stripped down version of our content. View the full version with proper formatting.
Pages: 1 2
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
For HP 48G, 49G & 49G+ the progs are available here:

http://www.hpcalc.org/hp49/utils/time/isodatev2.zip
(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
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?
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 + 
 » 
»
(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
(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
(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)
(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
(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     // if computed week# is larger
INC.00    // increment year
x<? Y
CLx       // and set week# to first week in year
MIN

LBL 52    // exit routine
INC X     // adjust week# from 0...52 to 1...53
INC Y     // adjust day# from 0...6 to 1...7
RCL.00
SDR 004
+         // generate ww.yyyy
CLα
αIP.00    // build output string
α"-W"
#010
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
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
(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
(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
;
@ 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 ..
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
(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
(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:-?
(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
(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. Undecided I've to apologize for my bad memory.

d:-)
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
Pages: 1 2
Reference URL's