Find the last Sunday in every month of a given year

Ruby, 91+6=97

#!ruby -prdate
$_=(Date.new(y=$_.to_i)...Date.new(y+1)).select(&:sunday?).chunk(&:mon).map{|k,v|v[-1]}*' '

Works out pretty well. select(&:sunday?) is pretty, and surprisingly, *' ' does all of the formatting by itself.


Bash 4.x + ncal, 57

If newline separators are OK instead of spaces, then we can remove the -n switch and trailing space from the echo statement. And I guess it will still work without the shebang, so I removed that too:

for i in {01..12};{ echo "$1-$i-`ncal $i $1|tail -c-3`";}

Original script (73 bytes):

#!/bin/bash
for i in {01..12};{ echo -n "$1-$i-`ncal $i $1|tail -c-3` ";}

Usage:

$ bash sundays.sh 2014
2014-01-26
2014-02-23
2014-03-30
2014-04-27
2014-05-25
2014-06-29
2014-07-27
2014-08-31
2014-09-28
2014-10-26
2014-11-30
2014-12-28
$

Note: Bash versions prior to 4.0 will omit the leading zeroes from the months. This can be fixed with the addition of 5 characters by changing {01..12} to `seq -w 1 12)`. Also, tail -c-3 might cause problems on some systems where the output of ncal includes trailing spaces, but I'm not aware of any that do.


IBM DFSORT, 11 3 lines of 71, 72 or 80 characters

 OPTION COPY 
 OUTFIL REPEAT=12,OVERLAY=(5:SEQNUM,2,ZD,5,2,1,8,Y4T,LASTDAYM,TOJUL=Y4T*
 ,9,7,Y4T,ADDDAYS,+1,TOJUL=Y4T,1:16,7,Y4T,PREVDSUN,TOGREG=Y4T(-),12X) 

The two answers with columnar output format have stood the test of time. That gives me a "loop", sort of, in that on OUTFIL REPEAT= copies the current record that many times.

Different technique to get to the value, which seems longer but is shorter as I can't work out any unconditional way to deal with the 12th record being in the following year, and making it conditional means including IFTHEN=(WHEN=, twice, and some other stuff. Gain on the swings (first of month is simplest way to do it) lose heavily on the roundabouts (particular syntax requirements).

This uses an inbuilt function (all functions in DFSORT are inbuilt) to find the last day of the month. Then adds one day (function) to get to first of following month and uses the PREVDSUN function to get the previous Sunday (which will always be the last Sunday in the previous month, as before).

When turning the year (input) into a valid date, a two-digit sequence number is used for the month, and that value is copied for the day as well, since the starting point does not matter as long as valid, as we are after the last day of the month initially: 5,2 is shorter than C'01'.

Here's the detail:

OPTION COPY - copy input file to output

OUTFIL - to allow multiple output files, with different selection and formatting, produce formatted reports. Used in preference to the shorter INREC because of the use of REPEAT=.

REPEAT=12 - produce 12 copies of each record. In this example, there can be only one input record (unlike the previous version) because of the SEQNUM.

5: - start at column 5 on the record.

SEQNUM,2,ZD - sequence number, defaults to start at one, two digits, "zoned decimal" (for unsigned, which they will be, same as character).

1,8 - copy bytes 1 for length 8 to current location (9). This is because the Y4T need to see that 8, else a different date format will be used.

Y4T - ccyymmdd-format date (due to the 8 immediately in front of it).

LASTDAYM - Last day of Month (also possible of Week, Quarter and Year).

TOJUL= - output date-conversion for date functions (TOJUL is one character less than TOGREG)

9,7 - now that it is 7 long, Y4T is going to be CCYYDDD.

ADDDAYS - adds a number of days, adjusting automatically if goes into following month/year (could also be ADDMONS and ADDYEARS)

PREVDSUN - the Julian date comes in, previous Sunday is located, TOGREG to get the correct output format, with the "-" seperator (could be anything you like as separator)

12X - blanks to clear up the mess which has allowed us to do it in such a short way

The output from the above, for 2014, is:

2014-01-26
2014-02-23
2014-03-30
2014-04-27
2014-05-25
2014-06-29
2014-07-27
2014-08-31
2014-09-28
2014-10-26
2014-11-23
2014-12-28

Something is required to tell the SORT what to do. There is no default. OPTION COPY is the shortest, SORT FIELDS=COPY is equivalent but longer.

The work itself it done this time in OUTFIL (to allow the use of REPEAT). The working code is arguably any of 160 (2 * 80), 144 (2 * 72), 140 (72 + 69), or 138 (70 + 68) (excluding the leading blanks, forced continuation and trailing blanks).

Given that the receiver would have to know what they are doing, I think I can say that the DFSORT code to list the last Sunday of each month for any year from 1900 (will run from year 0001, but I'm avoiding the research as well) up to 9999 (although DFSORT supports years up to 9999, previous solution would not work in year 9999 since the 12th date goes into the following year) can be Tweeted.

Why is the code so long, if there are especially apt inbuilt-functions?

Field-definitions are ephemeral. A field is only defined as a particular location within the data (which is a record) for its immediate use. To put it another way, fields are not defined as such, but are defined for each use and only for use. Date functions need to know which (of many) formats of date are used for the source, and the output must be in a date format, so that has to be specified.

Now that we have a Julian date.... TBC?


 OPTION COPY 
 INREC OVERLAY=(1,4,C'0201',1,8,1,8,1,8,1,8,1,8,1,8,1,8,1,8,1,8,1,8,1,8*
 ,94:C'1',89:1,4,ZD,ADD,+1,ZD,LENGTH=4,14:C'3',22:C'4',30:C'5',38:C'6',*
 46:C'7',54:C'8',62:C'9',69:C'10',77:C'11',85:C'12',127:X,89,8,Y4T,PREV*
 DSUN,TOGREG=Y4T(-),116:X,81,8,Y4T,PREVDSUN,TOGREG=Y4T(-),105:X,73,8,Y4*
 T,PREVDSUN,TOGREG=Y4T(-),94:X,65,8,Y4T,PREVDSUN,TOGREG=Y4T(-),83:X,57,*
 8,Y4T,PREVDSUN,TOGREG=Y4T(-),72:X,49,8,Y4T,PREVDSUN,TOGREG=Y4T(-),61:X*
 ,41,8,Y4T,PREVDSUN,TOGREG=Y4T(-),50:X,33,8,Y4T,PREVDSUN,TOGREG=Y4T(-),*
 39:X,25,8,Y4T,PREVDSUN,TOGREG=Y4T(-),28:X,17,8,Y4T,PREVDSUN,TOGREG=Y4T*
 (-),17:X,09,8,Y4T,PREVDSUN,TOGREG=Y4T(-),1:1,8,Y4T,PREVDSUN,TOGREG=Y4T*
 (-),11:X,18,120,6X) 

Needs some JCL

//LASTSUNG EXEC PGM=SORT 
//SYSOUT   DD SYSOUT=* 
//SORTOUT  DD SYSOUT=* 
//SYSIN    DD * 

And an input file (another line of JCL and three instream items of data):

//SORTIN DD *
2014 
1900 
2000 

Produces:

2014-01-26 2014-02-23 2014-03-30 2014-04-27 2014-05-25 2014-06-29 2014-07-27 2014-08-31 2014-09-28 2014-10-26 2014-11-30 2014-12-28
1900-01-28 1900-02-25 1900-03-25 1900-04-29 1900-05-27 1900-06-24 1900-07-29 1900-08-26 1900-09-30 1900-10-28 1900-11-25 1900-12-30
2000-01-30 2000-02-27 2000-03-26 2000-04-30 2000-05-28 2000-06-25 2000-07-30 2000-08-27 2000-09-24 2000-10-29 2000-11-26 2000-12-31

Will actually work up to the year 9999.

DFSORT is IBM's Mainframe sorting product. Data can be manipulated, but since sorting is key and sorts are often large and long-running, the DFSORT control cards have no looping constructs, so we can't put a SORT into a loop. Makes things a bit long-winded for tasks like Golf.

Why to post the answer, is because DFSORT has a PREVDday function. So last Sunday in a month is easy. It is the Sunday previous (PREVDSUN) to the first day of the following month.

It was also fun to do it within one "operand" (OVERLAY), a bit like doing it all within sprintf or similar.

Here it is ungolfed:

 OPTION COPY 

 INREC OVERLAY=(1,4,C'0201',1,8,1,8,1,8,1,8,1,8,1,8, 
         1,8,1,8,1,8,1,8, 
         1,8,94:C'1',89:1,4,ZD,ADD,+1,ZD,LENGTH=4, 
         14:C'3',22:C'4',30:C'5',38:C'6',46:C'7',54:C'8',
         62:C'9',69:C'10',77:C'11',85:C'12', 
        127:X,89,8,Y4T,PREVDSUN,TOGREG=Y4T(-), 
        116:X,81,8,Y4T,PREVDSUN,TOGREG=Y4T(-), 
        105:X,73,8,Y4T,PREVDSUN,TOGREG=Y4T(-), 
         94:X,65,8,Y4T,PREVDSUN,TOGREG=Y4T(-), 
         83:X,57,8,Y4T,PREVDSUN,TOGREG=Y4T(-), 
         72:X,49,8,Y4T,PREVDSUN,TOGREG=Y4T(-), 
         61:X,41,8,Y4T,PREVDSUN,TOGREG=Y4T(-), 
         50:X,33,8,Y4T,PREVDSUN,TOGREG=Y4T(-), 
         39:X,25,8,Y4T,PREVDSUN,TOGREG=Y4T(-), 
         28:X,17,8,Y4T,PREVDSUN,TOGREG=Y4T(-), 
         17:X,09,8,Y4T,PREVDSUN,TOGREG=Y4T(-), 
          1:1,8,Y4T,PREVDSUN,TOGREG=Y4T(-), 
         11:X,18,120,6X) 

Whilst not quite abuse, it wouldn't be usual to attempt to cram all this into one OVERLAY, and there is some seemingly unnecessary stuff which is needed to allow it all to go into one OVERLAY. There is some room for golfing, but since it would only remove one line at most, I'm not tempted.

The INREC is processed for each record.

OVERLAY allows the content of an existing record to be changed. If the record is extended beyond its length in the process, that is not a problem.

1,4 is the year coming in. It has a literal of 0201 appended to it, and then the successive 1,8s repeat it 11 times to give one long chuck of 96 bytes,

The 12th year on the extended current record gets 1 added to it, and its month made to 1 (January).

The remaining 10 months are changed to 3 through 11.

Then there are 12, in reverse order (due to OVERLAY) of these type of thing:

127:X,89,8,Y4T,PREVDSUN,TOGREG=Y4T(-),

The n: is a column-number on the record. The X inserts a blank. 89,8 takes the data from that column/length, Y4T treats it as a CCYYMMDD date, PREVDSUM works out the previous Sunday,TOGREG=Y4T(-) outputs it as a Gregorian CCYY-MM-DD date.

Because you get rubbish if the source and target of a particular part of an OVERLAY overlap destructively, the final 11:X,18,120,6X) rearranges and masks a bit of mess.

Manuals and papers can be found at: http://www-01.ibm.com/support/docview.wss?uid=isg3T7000080, and includes the 900+ page DFSORT Application Programming Guide.

As with all IBM products all manuals are available for free (except an excruciatingly small amount of very expensive ones which only a very small number of people in the world would even pretend to understand).

All DFSORT Control Cards must start with a blank. Column 72 is only used for continuation (any non-blank will do, but * is conventional). Column 72 is followed by a sequence number area which is ignored, making the each record 80 bytes.

Another couple of solutions to come, maybe.

Tags:

Date

Code Golf