Calendar - for "REAL" programmers: Difference between revisions

m (Omit Factor since its primitive words are lower case)
Line 3,466:
21 22 23 24 25 26 27 18 19 20 21 22 23 24 22 23 24 25 26 27 28 20 21 22 23 24 25 26 17 18 19 20 21 22 23 22 23 24 25 26 27 28
28 29 30 31 25 26 27 28 29 30 31 29 30 27 28 29 30 31 24 25 26 27 28 29 30 29 30 31
</pre>
 
=={{header|Visual Basic .NET}}==
'''Compiler:''' Roslyn Visual Basic (language version >= 15.8)
{{works with|.NET Core|2.1}}
 
Copied from [[Calendar#Visual_Basic_.NET]] and converted to uppercase. See entry there for description.
 
<lang vbnet>OPTION COMPARE BINARY
OPTION EXPLICIT ON
OPTION INFER ON
OPTION STRICT ON
 
IMPORTS SYSTEM.GLOBALIZATION
IMPORTS SYSTEM.TEXT
IMPORTS SYSTEM.RUNTIME.INTEROPSERVICES
IMPORTS SYSTEM.RUNTIME.COMPILERSERVICES
 
MODULE ARGHELPER
READONLY _ARGDICT AS NEW DICTIONARY(OF STRING, STRING)()
 
DELEGATE FUNCTION TRYPARSE(OF T, TRESULT)(VALUE AS T, <OUT> BYREF RESULT AS TRESULT) AS BOOLEAN
 
SUB INITIALIZEARGUMENTS(ARGS AS STRING())
FOR EACH ITEM IN ARGS
ITEM = ITEM.TOUPPERINVARIANT()
 
IF ITEM.LENGTH > 0 ANDALSO ITEM(0) <> """"C THEN
DIM COLONPOS = ITEM.INDEXOF(":"C, STRINGCOMPARISON.ORDINAL)
 
IF COLONPOS <> -1 THEN
' SPLIT ARGUMENTS WITH COLUMNS INTO KEY(PART BEFORE COLON) / VALUE(PART AFTER COLON) PAIRS.
_ARGDICT.ADD(ITEM.SUBSTRING(0, COLONPOS), ITEM.SUBSTRING(COLONPOS + 1, ITEM.LENGTH - COLONPOS - 1))
END IF
END IF
NEXT
END SUB
 
SUB FROMARGUMENT(OF T)(
KEY AS STRING,
<OUT> BYREF VAR AS T,
GETDEFAULT AS FUNC(OF T),
TRYPARSE AS TRYPARSE(OF STRING, T),
OPTIONAL VALIDATE AS PREDICATE(OF T) = NOTHING)
 
DIM VALUE AS STRING = NOTHING
IF _ARGDICT.TRYGETVALUE(KEY.TOUPPERINVARIANT(), VALUE) THEN
IF NOT (TRYPARSE(VALUE, VAR) ANDALSO (VALIDATE IS NOTHING ORELSE VALIDATE(VAR))) THEN
CONSOLE.WRITELINE($"INVALID VALUE FOR {KEY}: {VALUE}")
ENVIRONMENT.EXIT(-1)
END IF
ELSE
VAR = GETDEFAULT()
END IF
END SUB
END MODULE
 
MODULE PROGRAM
SUB MAIN(ARGS AS STRING())
DIM DT AS DATE
DIM COLUMNS, ROWS, MONTHSPERROW AS INTEGER
DIM VERTSTRETCH, HORIZSTRETCH, RESIZEWINDOW AS BOOLEAN
 
INITIALIZEARGUMENTS(ARGS)
FROMARGUMENT("DATE", DT, FUNCTION() NEW DATE(1969, 1, 1), ADDRESSOF DATE.TRYPARSE)
FROMARGUMENT("COLS", COLUMNS, FUNCTION() 80, ADDRESSOF INTEGER.TRYPARSE, FUNCTION(V) V >= 20)
FROMARGUMENT("ROWS", ROWS, FUNCTION() 43, ADDRESSOF INTEGER.TRYPARSE, FUNCTION(V) V >= 0)
FROMARGUMENT("MS/ROW", MONTHSPERROW, FUNCTION() 0, ADDRESSOF INTEGER.TRYPARSE, FUNCTION(V) V <= 12 ANDALSO V <= COLUMNS \ 20)
FROMARGUMENT("VSTRETCH", VERTSTRETCH, FUNCTION() TRUE, ADDRESSOF BOOLEAN.TRYPARSE)
FROMARGUMENT("HSTRETCH", HORIZSTRETCH, FUNCTION() TRUE, ADDRESSOF BOOLEAN.TRYPARSE)
FROMARGUMENT("WSIZE", RESIZEWINDOW, FUNCTION() TRUE, ADDRESSOF BOOLEAN.TRYPARSE)
 
' THE SCROLL BAR IN COMMAND PROMPT SEEMS TO TAKE UP PART OF THE LAST COLUMN.
IF RESIZEWINDOW THEN
CONSOLE.WINDOWWIDTH = COLUMNS + 1
CONSOLE.WINDOWHEIGHT = ROWS
END IF
 
IF MONTHSPERROW < 1 THEN MONTHSPERROW = MATH.MAX(COLUMNS \ 22, 1)
 
FOR EACH ROW IN GETCALENDARROWS(DT:=DT, WIDTH:=COLUMNS, HEIGHT:=ROWS, MONTHSPERROW:=MONTHSPERROW, VERTSTRETCH:=VERTSTRETCH, HORIZSTRETCH:=HORIZSTRETCH)
CONSOLE.WRITE(ROW)
NEXT
END SUB
 
ITERATOR FUNCTION GETCALENDARROWS(
DT AS DATE,
WIDTH AS INTEGER,
HEIGHT AS INTEGER,
MONTHSPERROW AS INTEGER,
VERTSTRETCH AS BOOLEAN,
HORIZSTRETCH AS BOOLEAN) AS IENUMERABLE(OF STRING)
 
DIM YEAR = DT.YEAR
DIM CALENDARROWCOUNT AS INTEGER = CINT(MATH.CEILING(12 / MONTHSPERROW))
' MAKE ROOM FOR THE THREE EMPTY LINES ON TOP.
DIM MONTHGRIDHEIGHT AS INTEGER = HEIGHT - 3
 
YIELD "[SNOOPY]".PADCENTER(WIDTH) & ENVIRONMENT.NEWLINE
YIELD YEAR.TOSTRING(CULTUREINFO.INVARIANTCULTURE).PADCENTER(WIDTH) & ENVIRONMENT.NEWLINE
YIELD ENVIRONMENT.NEWLINE
 
DIM MONTH = 0
DO WHILE MONTH < 12
DIM ROWHIGHESTMONTH = MATH.MIN(MONTH + MONTHSPERROW, 12)
 
DIM CELLWIDTH = WIDTH \ MONTHSPERROW
DIM CELLCONTENTWIDTH = IF(MONTHSPERROW = 1, CELLWIDTH, (CELLWIDTH * 19) \ 20)
 
DIM CELLHEIGHT = MONTHGRIDHEIGHT \ CALENDARROWCOUNT
DIM CELLCONTENTHEIGHT = (CELLHEIGHT * 19) \ 20
 
' CREATES A MONTH CELL FOR THE SPECIFIED MONTH (1-12).
DIM GETMONTHFROM =
FUNCTION(M AS INTEGER) BUILDMONTH(
DT:=NEW DATE(DT.YEAR, M, 1),
WIDTH:=CELLCONTENTWIDTH,
HEIGHT:=CELLCONTENTHEIGHT,
VERTSTRETCH:=VERTSTRETCH,
HORIZSTRETCH:=HORIZSTRETCH).SELECT(FUNCTION(X) X.PADCENTER(CELLWIDTH))
 
' THE MONTHS IN THIS ROW OF THE CALENDAR.
DIM MONTHSTHISROW AS IENUMERABLE(OF IENUMERABLE(OF STRING)) =
ENUMERABLE.SELECT(ENUMERABLE.RANGE(MONTH + 1, ROWHIGHESTMONTH - MONTH), GETMONTHFROM)
 
DIM CALENDARROW AS IENUMERABLE(OF STRING) =
INTERLEAVED(
MONTHSTHISROW,
USEINNERSEPARATOR:=FALSE,
USEOUTERSEPARATOR:=TRUE,
OUTERSEPARATOR:=ENVIRONMENT.NEWLINE)
 
DIM EN = CALENDARROW.GETENUMERATOR()
DIM HASNEXT = EN.MOVENEXT()
DO WHILE HASNEXT
 
DIM CURRENT AS STRING = EN.CURRENT
 
' TO MAINTAIN THE (NOT STRICTLY NEEDED) CONTRACT OF YIELDING COMPLETE ROWS, KEEP THE NEWLINE AFTER
' THE CALENDAR ROW WITH THE LAST TERMINAL ROW OF THE ROW.
HASNEXT = EN.MOVENEXT()
YIELD IF(HASNEXT, CURRENT, CURRENT & ENVIRONMENT.NEWLINE)
LOOP
 
MONTH += MONTHSPERROW
LOOP
END FUNCTION
 
''' <SUMMARY>
''' INTERLEAVES THE ELEMENTS OF THE SPECIFIED SUB-SOURCES BY MAKING SUCCESSIVE PASSES THROUGH THE SOURCE
''' ENUMERABLE, YIELDING A SINGLE ELEMENT FROM EACH SUB-SOURCE IN SEQUENCE IN EACH PASS, OPTIONALLY INSERTING A
''' SEPARATOR BETWEEN ELEMENTS OF ADJACENT SUB-SOURCES AND OPTIONALLY A DIFFERENT SEPARATOR AT THE END OF EACH
''' PASS THROUGH ALL THE SOURCES. (I.E., BETWEEN ELEMENTS OF THE LAST AND FIRST SOURCE)
''' </SUMMARY>
''' <TYPEPARAM NAME="T">THE TYPE OF THE ELEMENTS OF THE SUB-SOURCES.</TYPEPARAM>
''' <PARAM NAME="SOURCES">A SEQUENCE OF THE SEQUENCES WHOSE ELEMENTS ARE TO BE INTERLEAVED.</PARAM>
''' <PARAM NAME="USEINNERSEPARATOR">WHETHER TO INSERT <PARAMREF NAME="USEINNERSEPARATOR"/> BETWEEN THE ELEMENTS OFADJACENT SUB-SOURCES.</PARAM>
''' <PARAM NAME="INNERSEPARATOR">THE SEPARATOR BETWEEN ELEMENTS OF ADJACENT SUB-SOURCES.</PARAM>
''' <PARAM NAME="USEOUTERSEPARATOR">WHETHER TO INSERT <PARAMREF NAME="OUTERSEPARATOR"/> BETWEEN THE ELEMENTS OF THE LAST AND FIRST SUB-SOURCES.</PARAM>
''' <PARAM NAME="OUTERSEPARATOR">THE SEPARATOR BETWEEN ELEMENTS OF THE LAST AND FIRST SUB-SOURCE.</PARAM>
''' <PARAM NAME="WHILEANY">IF <SEE LANGWORD="TRUE"/>, THE ENUMERATION CONTINUES UNTIL EVERY GIVEN SUBSOURCE IS EMPTY;
''' IF <SEE LANGWORD="FALSE"/>, THE ENUMERATION STOPS AS SOON AS ANY ENUMERABLE NO LONGER HAS AN ELEMENT TO SUPPLY FOR THE NEXT PASS.</PARAM>
ITERATOR FUNCTION INTERLEAVED(OF T)(
SOURCES AS IENUMERABLE(OF IENUMERABLE(OF T)),
OPTIONAL USEINNERSEPARATOR AS BOOLEAN = FALSE,
OPTIONAL INNERSEPARATOR AS T = NOTHING,
OPTIONAL USEOUTERSEPARATOR AS BOOLEAN = FALSE,
OPTIONAL OUTERSEPARATOR AS T = NOTHING,
OPTIONAL WHILEANY AS BOOLEAN = TRUE) AS IENUMERABLE(OF T)
DIM SOURCEENUMERATORS AS IENUMERATOR(OF T)() = NOTHING
 
TRY
SOURCEENUMERATORS = SOURCES.SELECT(FUNCTION(X) X.GETENUMERATOR()).TOARRAY()
DIM NUMSOURCES = SOURCEENUMERATORS.LENGTH
DIM ENUMERATORSTATES(NUMSOURCES - 1) AS BOOLEAN
 
DIM ANYPREVITERS AS BOOLEAN = FALSE
DO
' INDICES OF FIRST AND LAST SUB-SOURCES THAT HAVE ELEMENTS.
DIM FIRSTACTIVE = -1, LASTACTIVE = -1
 
' DETERMINE WHETHER EACH SUB-SOURCE THAT STILL HAVE ELEMENTS.
FOR I = 0 TO NUMSOURCES - 1
ENUMERATORSTATES(I) = SOURCEENUMERATORS(I).MOVENEXT()
IF ENUMERATORSTATES(I) THEN
IF FIRSTACTIVE = -1 THEN FIRSTACTIVE = I
LASTACTIVE = I
END IF
NEXT
 
' DETERMINE WHETHER TO YIELD ANYTHING IN THIS ITERATION BASED ON WHETHER WHILEANY IS TRUE.
' NOT YIELDING ANYTHING THIS ITERATION IMPLIES THAT THE ENUMERATION HAS ENDED.
DIM THISITERHASRESULTS AS BOOLEAN = IF(WHILEANY, FIRSTACTIVE <> -1, FIRSTACTIVE = 0 ANDALSO LASTACTIVE = NUMSOURCES - 1)
IF NOT THISITERHASRESULTS THEN EXIT DO
 
' DON'T INSERT A SEPARATOR ON THE FIRST PASS.
IF ANYPREVITERS THEN
IF USEOUTERSEPARATOR THEN YIELD OUTERSEPARATOR
ELSE
ANYPREVITERS = TRUE
END IF
 
' GO THROUGH AND YIELD FROM THE SUB-SOURCES THAT STILL HAVE ELEMENTS.
FOR I = 0 TO NUMSOURCES - 1
IF ENUMERATORSTATES(I) THEN
' DON'T INSERT A SEPARATOR BEFORE THE FIRST ELEMENT.
IF I > FIRSTACTIVE ANDALSO USEINNERSEPARATOR THEN YIELD INNERSEPARATOR
YIELD SOURCEENUMERATORS(I).CURRENT
END IF
NEXT
LOOP
 
FINALLY
IF SOURCEENUMERATORS ISNOT NOTHING THEN
FOR EACH EN IN SOURCEENUMERATORS
EN.DISPOSE()
NEXT
END IF
END TRY
END FUNCTION
 
''' <SUMMARY>
''' RETURNS THE ROWS REPRESENTING ONE MONTH CELL WITHOUT TRAILING NEWLINES. APPROPRIATE LEADING AND TRAILING
''' WHITESPACE IS ADDED SO THAT EVERY ROW HAS THE LENGTH OF WIDTH.
''' </SUMMARY>
''' <PARAM NAME="DT">A DATE WITHIN THE MONTH TO REPRESENT.</PARAM>
''' <PARAM NAME="WIDTH">THE WIDTH OF THE CELL.</PARAM>
''' <PARAM NAME="HEIGHT">THE HEIGHT.</PARAM>
''' <PARAM NAME="VERTSTRETCH">IF <SEE LANGWORD="TRUE" />, BLANK ROWS ARE INSERTED TO FIT THE AVAILABLE HEIGHT.
''' OTHERWISE, THE CELL HAS A CONSTANT HEIGHT OF </PARAM>
''' <PARAM NAME="HORIZSTRETCH">IF <SEE LANGWORD="TRUE" />, THE SPACING BETWEEN INDIVIDUAL DAYS IS INCREASED TO
''' FIT THE AVAILABLE WIDTH. OTHERWISE, THE CELL HAS A CONSTANT WIDTH OF 20 CHARACTERS AND IS PADDED TO BE IN
''' THE CENTER OF THE EXPECTED WIDTH.</PARAM>
ITERATOR FUNCTION BUILDMONTH(DT AS DATE, WIDTH AS INTEGER, HEIGHT AS INTEGER, VERTSTRETCH AS BOOLEAN, HORIZSTRETCH AS BOOLEAN) AS IENUMERABLE(OF STRING)
CONST DAY_WDT = 2 ' WIDTH OF A DAY.
CONST ALLDAYS_WDT = DAY_WDT * 7 ' WIDTH OF AL LDAYS COMBINED.
 
' NORMALIZE THE DATE TO JANUARY 1.
DT = NEW DATE(DT.YEAR, DT.MONTH, 1)
 
' HORIZONTAL WHITESPACE BETWEEN DAYS OF THE WEEK. CONSTANT OF 6 REPRESENTS 6 SEPARATORS PER LINE.
DIM DAYSEP AS NEW STRING(" "C, MATH.MIN((WIDTH - ALLDAYS_WDT) \ 6, IF(HORIZSTRETCH, INTEGER.MAXVALUE, 1)))
' NUMBER OF BLANK LINES BETWEEN ROWS.
DIM VERTBLANKCOUNT = IF(NOT VERTSTRETCH, 0, (HEIGHT - 8) \ 7)
 
' WIDTH OF EACH DAY * 7 DAYS IN ONE ROW + DAY SEPARATOR LENGTH * 6 SEPARATORS PER LINE.
DIM BLOCKWIDTH = ALLDAYS_WDT + DAYSEP.LENGTH * 6
 
' THE WHITESPACE AT THE BEGINNING OF EACH LINE.
DIM LEFTPAD AS NEW STRING(" "C, (WIDTH - BLOCKWIDTH) \ 2)
' THE WHITESPACE FOR BLANK LINES.
DIM FULLPAD AS NEW STRING(" "C, WIDTH)
 
' LINES ARE "STAGED" IN THE STRINGBUILDER.
DIM SB AS NEW STRINGBUILDER(LEFTPAD)
DIM NUMLINES = 0
 
' GET THE CURRENT LINE SO FAR FORM THE STRINGBUILDER AND BEGIN A NEW LINE.
' RETURNS THE CURRENT LINE AND TRAILING BLANK LINES USED FOR VERTICAL PADDING (IF ANY).
' RETURNS EMPTY ENUMERABLE IF THE HEIGHT REQUIREMENT HAS BEEN REACHED.
DIM ENDLINE =
FUNCTION() AS IENUMERABLE(OF STRING)
DIM FINISHEDLINE AS STRING = SB.TOSTRING().PADRIGHT(WIDTH)
SB.CLEAR()
SB.APPEND(LEFTPAD)
 
' USE AN INNER ITERATOR TO PREVENT LAZY EXECUTION OF SIDE EFFECTS OF OUTER FUNCTION.
RETURN IF(NUMLINES >= HEIGHT,
ENUMERABLE.EMPTY(OF STRING)(),
ITERATOR FUNCTION() AS IENUMERABLE(OF STRING)
YIELD FINISHEDLINE
NUMLINES += 1
 
FOR I = 1 TO VERTBLANKCOUNT
IF NUMLINES >= HEIGHT THEN RETURN
YIELD FULLPAD
NUMLINES += 1
NEXT
END FUNCTION())
END FUNCTION
 
' YIELD THE MONTH NAME.
SB.APPEND(PADCENTER(DT.TOSTRING("MMMM", CULTUREINFO.INVARIANTCULTURE), BLOCKWIDTH).TOUPPER())
FOR EACH L IN ENDLINE()
YIELD L
NEXT
 
' YIELD THE HEADER OF WEEKDAY NAMES.
DIM WEEKNMABBREVS = [ENUM].GETNAMES(GETTYPE(DAYOFWEEK)).SELECT(FUNCTION(X) X.SUBSTRING(0, 2).TOUPPER())
SB.APPEND(STRING.JOIN(DAYSEP, WEEKNMABBREVS))
FOR EACH L IN ENDLINE()
YIELD L
NEXT
 
' DAY OF WEEK OF FIRST DAY OF MONTH.
DIM STARTWKDY = CINT(DT.DAYOFWEEK)
 
' INITIALIZE WITH EMPTY SPACE FOR THE FIRST LINE.
DIM FIRSTPAD AS NEW STRING(" "C, (DAY_WDT + DAYSEP.LENGTH) * STARTWKDY)
SB.APPEND(FIRSTPAD)
 
DIM D = DT
DO WHILE D.MONTH = DT.MONTH
SB.APPENDFORMAT(CULTUREINFO.INVARIANTCULTURE, $"{{0,{DAY_WDT}}}", D.DAY)
 
' EACH ROW ENDS ON SATURDAY.
IF D.DAYOFWEEK = DAYOFWEEK.SATURDAY THEN
FOR EACH L IN ENDLINE()
YIELD L
NEXT
ELSE
SB.APPEND(DAYSEP)
END IF
 
D = D.ADDDAYS(1)
LOOP
 
' KEEP ADDING EMPTY LINES UNTIL THE HEIGHT QUOTA IS MET.
DIM NEXTLINES AS IENUMERABLE(OF STRING)
DO
NEXTLINES = ENDLINE()
FOR EACH L IN NEXTLINES
YIELD L
NEXT
LOOP WHILE NEXTLINES.ANY()
END FUNCTION
 
''' <SUMMARY>
''' RETURNS A NEW STRING THAT CENTER-ALIGNS THE CHARACTERS IN THIS STRING BY PADDING TO THE LEFT AND RIGHT WITH
''' THE SPECIFIED CHARACTER TO A SPECIFIED TOTAL LENGTH.
''' </SUMMARY>
''' <PARAM NAME="S">THE STRING TO CENTER-ALIGN.</PARAM>
''' <PARAM NAME="TOTALWIDTH">THE NUMBER OF CHARACTERS IN THE RESULTING STRING.</PARAM>
''' <PARAM NAME="PADDINGCHAR">THE PADDING CHARACTER.</PARAM>
<EXTENSION()>
PRIVATE FUNCTION PADCENTER(S AS STRING, TOTALWIDTH AS INTEGER, OPTIONAL PADDINGCHAR AS CHAR = " "C) AS STRING
RETURN S.PADLEFT(((TOTALWIDTH - S.LENGTH) \ 2) + S.LENGTH, PADDINGCHAR).PADRIGHT(TOTALWIDTH, PADDINGCHAR)
END FUNCTION
END MODULE</lang>
 
{{out|input=COLS:132 ROWS:25 MS/ROW:6 HSTRETCH:FALSE VSTRETCH:FALSE}}
<pre> [SNOOPY]
1969
 
JANUARY FEBRUARY MARCH APRIL MAY JUNE
SU MO TU WE TH FR SA SU MO TU WE TH FR SA SU MO TU WE TH FR SA SU MO TU WE TH FR SA SU MO TU WE TH FR SA SU MO TU WE TH FR SA
1 2 3 4 1 1 1 2 3 4 5 1 2 3 1 2 3 4 5 6 7
5 6 7 8 9 10 11 2 3 4 5 6 7 8 2 3 4 5 6 7 8 6 7 8 9 10 11 12 4 5 6 7 8 9 10 8 9 10 11 12 13 14
12 13 14 15 16 17 18 9 10 11 12 13 14 15 9 10 11 12 13 14 15 13 14 15 16 17 18 19 11 12 13 14 15 16 17 15 16 17 18 19 20 21
19 20 21 22 23 24 25 16 17 18 19 20 21 22 16 17 18 19 20 21 22 20 21 22 23 24 25 26 18 19 20 21 22 23 24 22 23 24 25 26 27 28
26 27 28 29 30 31 23 24 25 26 27 28 23 24 25 26 27 28 29 27 28 29 30 25 26 27 28 29 30 31 29 30
30 31
JULY AUGUST SEPTEMBER OCTOBER NOVEMBER DECEMBER
SU MO TU WE TH FR SA SU MO TU WE TH FR SA SU MO TU WE TH FR SA SU MO TU WE TH FR SA SU MO TU WE TH FR SA SU MO TU WE TH FR SA
1 2 3 4 5 1 2 1 2 3 4 5 6 1 2 3 4 1 1 2 3 4 5 6
6 7 8 9 10 11 12 3 4 5 6 7 8 9 7 8 9 10 11 12 13 5 6 7 8 9 10 11 2 3 4 5 6 7 8 7 8 9 10 11 12 13
13 14 15 16 17 18 19 10 11 12 13 14 15 16 14 15 16 17 18 19 20 12 13 14 15 16 17 18 9 10 11 12 13 14 15 14 15 16 17 18 19 20
20 21 22 23 24 25 26 17 18 19 20 21 22 23 21 22 23 24 25 26 27 19 20 21 22 23 24 25 16 17 18 19 20 21 22 21 22 23 24 25 26 27
27 28 29 30 31 24 25 26 27 28 29 30 28 29 30 26 27 28 29 30 31 23 24 25 26 27 28 29 28 29 30 31
31 30
</pre>
 
Anonymous user