1
\$\begingroup\$

Introduction

In the United States, national elections are normally held on the first Tuesday after the first Monday in November.

Challenge

Write a program that takes a Year as integer, and a pair of (day of week as character and ordinal as integer) and output the date as "yyyy-mm-dd" Remember, the second pair is after the first pair. use this for inspiration: https://codegolf.stackexchange.com/questions/178227/get-the-date-of-the-nth-day-of-week-in-a-given-year-and-month[][1]

Example Input and Output

Example input:

  • 2021 November Tuesday 1 Monday 1
  • 2020 November Tuesday 1 Monday 1
  • 2020 December Friday 2 Wednesday 1
  • 2019 April Wednesday 1 Friday 1
  • 2023 February Tuesday 4 Wednesday 1
  • 2023 February Wednesday 5 Thursday 1
  • 2022 December Sunday 5 Sunday 55

Expected output:

  • "2021-11-02"
  • "2020-11-03"
  • "2020-12-11"
  • "2019-04-10"
  • "2023-02-28"
  • "NA"
  • "NA"

Note: the output indicated as "NA" could be any negative finding such as null, but not an error. [1]: Get the date of the nth day of week in a given year and month

\$\endgroup\$
21
  • 11
    \$\begingroup\$ Hi and welcome! This is an interesting challenge, but please avoid cumbersome I/O formats, and for next time, I highly recommend using the sandbox. \$\endgroup\$
    – Adám
    Feb 1 at 22:22
  • 1
    \$\begingroup\$ Suggested test case: 2023 February Tuesday 4 Wednesday 1 \$\endgroup\$
    – Adám
    Feb 1 at 22:27
  • 1
    \$\begingroup\$ Suggested test case: 2023 February Wednesday 5 Thursday 1 \$\endgroup\$
    – Adám
    Feb 1 at 22:28
  • 1
    \$\begingroup\$ Suggested test case: 2022 December Sunday 5 Sunday 55 \$\endgroup\$
    – Adám
    Feb 1 at 22:29
  • 2
    \$\begingroup\$ @gildux - The example that you ask about, 2021 November Monday 1 Tuesday 1 is asking for "the first Tuesday that is after the first Monday in November, 2021". You should mentally parse it as (2021 November) (Monday 1) (Tuesday 1) \$\endgroup\$ Feb 2 at 11:25

4 Answers 4

3
\$\begingroup\$

JavaScript, 102 93 91 88 80 78 77 bytes

Oof, this made my brain itch. Possibly still some room for improvement.

(m,t,x=0)=>g=w=>(d=new Date(...m,++x)).getDay()-w[0]||--w[1]?g(w):w!=t?g(t):d

Try it online!

Call with f([y,m],[W,O])([w,o]), where:

  • y is the year,
  • m is the 0-indexed month,
  • W is the 0-indexed target weekday (0=Sunday),
  • O is the ordinal of W,
  • w is the 0-indexed starting weekday, and,
  • o is the ordinal of w

EDIT: Looking at the rest of the solutions coming in, they seem to be interpreting the I/O requirements of the spec a lot more strictly than I am. After a few read-throughs, I'm not seeing that strictness, nor would it be reasonable to assume it's that strict. Still, though, seeing as I appear to be the only one taking the requirements this loosely, I'll take a stab at an alternative solution on Tuesday. (It's a bank holiday weekend in Ireland)

\$\endgroup\$
2
  • \$\begingroup\$ Based on the last two test cases, if the calculated date exceeds the input year+month, it should output NA. But maybe it's good to verify with OP whether this is optional or mandatory, since just calculating beyond the current year+month works perfectly fine for larger input. 🤷 \$\endgroup\$ Feb 2 at 17:02
  • 1
    \$\begingroup\$ Hopefully, "undefined behaviour" is sufficient in those cases, @KevinCruijssen. \$\endgroup\$
    – Shaggy
    Feb 2 at 17:11
2
\$\begingroup\$

C (gcc), 439 428 bytes

  • -11 thanks to ceilingcat

Most of the code is parsing the input.

#define z(d)(*d-83?*d-84?*d-77?*d-87?5:3:1:d[1]-117?4:2:d[1]-117?6:0)
#define Z(d,e)(d-e+(d<e?7:0))
t[20],a,b,n,x,y;char*m,*d,*e;f(s){sscanf(s,"%d %ms %ms %d %ms %d",&y,&m,&d,&a,&e,&b);t[5]=y-=1900;x=*m-78;t[4]=n=x+4?~x?x+13?x+8?x-5?x-1?x?11:10:9:8:1:m[1]-112?7:3:m[2]-114?2:4:m[1]-97?m[2]-110?6:5:0;t[3]=1;mktime(&t);t[3]+=Z(z(e),t[6])+(a+b-2)*7+Z(z(d),z(e));mktime(&t);t[4]-n|t[5]-y||printf("%04d-%02d-%02d",y+1900,n+1,t[3]);}

Try it online!

Ungolfed:

// Day of week: Su=0, M=1, Tu=2, W=3, Th=4, F=5, Sa=6
#define z(d)(*d-'S'?*d-'T'?*d-'M'?*d-'W'?5:3:1:d[1]-'u'?4:2:d[1]-'u'?6:0)
// Add one week if the target day of week precedes the current one
#define Z(d,e)(d-e+(d-e<0?7:0))
t[20], // struct tm for time conversions (important parts are initialized each run through)
a, b, // week numbers
x, // temporary for month calculation
n, y; // month and year
char *m, *d, *e; // original string versions of days, month and year
f(int*s) {
  sscanf(s,"%d %ms %ms %d %ms %d",&y,&m,&d,&a,&e,&b); // get the parsed data
  t[5]=y-=1900; // adjust the year
  x=*m-78; // get most values into single-digit
  // Month: Ja=0 F=1 M_r=2 Ap=3 M_y=4 J_n=5 J_l=6 Au=7 S=8 O=9 N=10 (else) 11
  //t[4]=n=*m-'J'?*m-'M'?*m-'A'?*m-'F'?*m-'S'?*m-'O'?*m-'N'?11:10:9:8:1:m[1]-'p'?7:3:m[2]-'r'?2:4:m[1]-'a'?m[2]-'n'?6:5:0;
  t[4]=n=x+4?~x?x+13?x+8?x-5?x-1?x?11:10:9:8:1:m[1]-112?7:3:m[2]-114?2:4:m[1]-97?m[2]-110?6:5:0;
  t[3]=1; // start on the first day of the month
  mktime(&t); // get the day of week information
  t[3]+=Z(z(e),t[6])+(a+b-2)*7+Z(z(d),z(e)); // add offsets
  mktime(&t); // convert to canonical form
  t[4]-n|t[5]-y?printf("%04d-%02d-%02d",y+1900,n+1,t[3]); // only print date if it is still inside the month
}
\$\endgroup\$
0
1
\$\begingroup\$

05AB1E, 204 (or 126?) bytes

With strict I/O and outputting NA when the date goes beyond the given input month+year: 204 bytes:

"Y`т‰0Kθ4ÖUD<i\28X+ë<7%É31α}‹iY¬>0ëY1¾ǝDÅsD12‹i>1ë\1Dǝ¤>2}}ǝV"ˆ$”‚應…ä†ï€¿…Ë…ê†Ä…æ…Ì…Í……”#s#©1èk>Dˆ®н)V®2ô¦RvyθFNĀi¯н.V}[Y`UD3‹©12*+>₂*T÷®Xα©т%D4÷®т÷©4÷®·(O7%”ŒÍ‹Ó‹ŽŒ¹ŒêŒÛН”#yнkQ#¯н.V]YÂT‰J'-ýsÅs¯θÊi„NA

The cumbersome I/O and 05AB1E's lack of date builtins make for a pretty big program. 🙃

Try it online or verify all test cases.

With flexible I/O and just continue calculating instead of outputting NA: 126 bytes:

"Y`т‰0Kθ4ÖUD<i\28X+ë<7%É31α}‹iY¬>0ëY1¾ǝDÅsD12‹i>1ë\1Dǝ¤>2}}ǝV"ˆ1šVvyθFNĀi¯н.V}[Y`UD3‹©12*+>₂*T÷®Xα©т%D4÷®т÷©4÷®·(O7%yнQ#¯н.V]Y

Two inputs in the formats [month,year] and [[weekday2,amount2],[weekday1,amount1]], where month is an integer 1-12 and weekday are integers 0-6 for Saturday to Friday.
Outputs in the format [day,month,year].

Still pretty big with the manual calculations, but apparently more than 1/3rd of my top program's bytes are to deal with just the I/O formats.

Try it online.

Explanation (of the larger strict I/O version):

Step 0: Create a function to go to the next day, which we'll re-use later on (see this 05AB1E answer of mine for an in-depth explanation of how we're going to the next day manually of a given date):

"Y`т‰0Kθ4ÖUD<i\28X+ë<7%É31α}‹iY¬>0ëY1¾ǝDÅsD12‹i>1ë\1Dǝ¤>2}}ǝV"
                 # Push this string to act as function later on with an eval
 ˆ               # Add it to the global array†

† The reason I use the global array instead of a variable: I'm already using all three U/X, V/Y, and ©® variable setters/getters in the actual manual date calculations.

Step 1: Extract the year and month from the input, and parse it as [1,m,y] triplet-list:

$                # Push 1 and the input-string
 ”‚應…ä†ï€¿…Ë…ê†Ä…æ…Ì…Í……”
                 # Push dictionary string "January February March April May June July August September October November December"
  #              # Split it on spaces to a list
   s             # Swap so the input-string is at the top of the stack
    #            # Split it on spaces as well
     ©           # Store this sextuple input-list in variable `®` (without popping)
      1è         # Pop and get the Month at (0-based) index 1
        k        # Get the (0-based) index of this month in the list
         >       # Increase it by 1 to a 1-based index
          Dˆ     # Add a copy to the global array as well
            ®    # Push the input-list of variable `®` again
             н   # Pop and leave its first item (the year)
              )  # Wrap all three values into a list: [1,m,y]
               V # Pop and store it in variable `Y`

Step 2: Parse the remainder of the input-string, and start looping:

®                # Push the input-list from variable `®` again
 2ô              # Split it into parts of size 2
   ¦             # Remove the first part (the [year,"Month"])
    R            # Reverse the other two parts
     v           # For each over the pairs `y` in the pair:
      yθ         #  Push the last item of the current pair (the amount of days)
        F        #  Pop and inner loop that many times:
         NĀi     #   If it's NOT the first iteration:
            ¯н.V #    Go to the next day
                 #    by evaluating the first item of the global array of step 0
           }     #   Close the if-statement
         [       #   Start an inner infinite loop:

Step 3a: Calculate the DayOfWeek of the current date as [0,1,2,3,4,5,6] for [Saturday,Sunday,Monday,Tuesday,Wednesday,Thursday,Friday] respectively (see again this 05AB1E answer of mine for an in-depth explanation of how I calculate the Day of the Week manually of a given date):

Y`UD3‹©12*+>₂*T÷®Xα©т%D4÷®т÷©4÷®·(O7%

Step 3b+c: If this day is equal to the input-date of the current pair y, stop the infinite loop. If not, go to the next day and continue looping.

”ŒÍ‹Ó‹ŽŒ¹ŒêŒÛН” #    Push dictionary string "Saturday Sunday Monday Tuesday Wednesday Thursday Friday"
 #               #    Split it on spaces to a list
  yн             #    Push the first item of the current pair (the day)
    k            #    Get its 0-based index in the list of weekdays
     Q           #    Check whether it's equal to the calculated DayOfWeek from step 3a
      #          #    If they're equal:
      #          #     Stop the inner infinite loop
       ¯н.V      #    (Else) Go to the next day by evaluating step 0 again

Step 4: Format the resulting date to the desired output-format:

]                # Close the three loops
 Y               # Push the resulting date `Y`
  Â              # Bifurcate this triplet; short for Duplicate & Reverse copy
   T‰J           # Format the day/month with leading 0:
   T‰            #  Divmod each inner value by 10
     J           #  Join each inner pair together
      '-ý       '# Join the triplet-list with "-" delimiter
  s              # Swap the triplet to the top of the stack again
   Ås            # Only leave its middle item (the month)
     ¯θ          # Push the last item of the global array (the parsed input-month)
       Êi        # If they are NOT equal:
         „NA     #  Push string "NA"
                 # (after which the top of the stack is output implicitly as result)

See this 05AB1E tip of mine (section How to use the dictionary?) to understand why ”‚應…ä†ï€¿…Ë…ê†Ä…æ…Ì…Í……” is "January February March April May June July August September October November December" and ”ŒÍ‹Ó‹ŽŒ¹ŒêŒÛН” is "Saturday Sunday Monday Tuesday Wednesday Thursday Friday".

Both "ˆ$”‚應…ä†ï€¿…Ë…ê†Ä…æ…Ì…Í……”#I#1èk> and ”ŒÍ‹Ó‹ŽŒ¹ŒêŒÛН”#yнk can probably be golfed a bit with magic numbers of some sort, but since I suck at those and I also couldn't really be bothered in an answer already this big because of manual date calculations, the indexing into dictionary strings will do for now.

\$\endgroup\$
3
  • \$\begingroup\$ OP has confirmed that any undefined behaviour, except an error, is acceptable for the "NA" test cases. \$\endgroup\$
    – Shaggy
    Feb 2 at 17:50
  • \$\begingroup\$ @Shaggy His comment to me contradicts that lol.. "it should not return a date and should not result in an error". But I'll just look at his reply to you. ;) \$\endgroup\$ Feb 2 at 17:59
  • \$\begingroup\$ @KevinCruijssen these comments do not contradict. consider them together. \$\endgroup\$ Feb 2 at 18:51
1
\$\begingroup\$

Go, 262 bytes

import."time"
func f(y,m,d,e string,n,o int)string{t,_:=Parse("2006January",y+m)
D,i,j:=Hour*24,1,1
for t.Weekday().String()!=e{t=t.Add(D)}
for;j<o;j++{t=t.Add(D*7)}
for t.Weekday().String()!=d{t=t.Add(D)}
for;i<n;i++{t=t.Add(D*7)}
return t.Format("2006-01-02")}

Attempt This Online!

Extremely similar to my solution for "Get the date of the nth day of week in a given year and month".

For the NA test cases, it continues on to the next month/year until it finds the n-th dday after the o-th eday.

Explanation

import."time"
func f(y,m,d,e string,n,o int)string{
t,_:=Parse("2006January",y+m)
D:=Hour*24

Some initial setup: getting the 1st of month m in the year y, and defining a Day variable.

for t.Weekday().String()!=e{t=t.Add(D)}
for j:=1;j<o;j++{t=t.Add(D*7)}

Skip forward to the o-th eday.

for t.Weekday().String()!=d{t=t.Add(D)}
for i:=1;i<n;i++{t=t.Add(D*7)}

Skip forward to the n-th dday.

return t.Format("2006-01-02")}

Return a formatted date.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.