17
\$\begingroup\$

Goal

Write a program or function that returns the day of the week for a date, eg.

01/06/2020  ->  Mon

However, it's unknown if the date is in the format mm/dd/yyyy or dd/mm/yyyy. If you can be certain of the day of the week, return it. If there is uncertainty, return an error.

02/07/2020  ->  Err  (Thu? Fri?)

Input

A date in the format #/#/####.

  • The numbers can optionally have leading zeros.
  • The year should be read verbatim, assume that 19 = 19 AD, not 19 = 2019 AD.
  • The separators can be any non-digit character.
  • Assume the input date is valid in at least one format (ie. not 13/13/2020).
  • At a minimum, support dates from 2000 through 2030 (which are likely covered by any modern OS and language with a date library.)

Requirements

  • If the first and second numbers:
    • are the same (03/03/2020), the date is unambiguous.
    • either is greater than 12 (13/09/2020, 09/13/2020), the date is unambiguous.
    • are different and both 12 or less (09/11/2020), the date is ambiguous.

Output

  • If the date is unambiguous, return the day of the week.
  • If the date is ambiguous:
    • If both versions of the date have the same day of the week, return the day of the week.
    • If both versions of the date have a different day of the week, return an error.
  • A day of the week should be returned as a string of the day's three-letter abbreviation ("Mon"), case-insensitive.
  • An error should be returned as any other three-character string, not all whitespace, eg. "Err", "NaN", "???".

Test Cases

02/07/2020  ->  Err
01/06/2020  ->  Mon
05/05/2020  ->  Tue
24/06/2020  ->  Wed
05/09/2030  ->  Thu
03/13/2020  ->  Fri
29/02/2020  ->  Sat
03/12/2000  ->  Sun

Scoring

The code with the least byte count in a week wins the tick.

\$\endgroup\$
7
  • 6
    \$\begingroup\$ Can we take the three parts of the date as three separate arguments? \$\endgroup\$
    – RGS
    Mar 19 '20 at 1:09
  • 2
    \$\begingroup\$ Suggested test case 3/2/2019. Since 3rd of feburary and 2nd of march are both a monday, does this count as ambiguous or unambiguous for the purposes of this challenge? \$\endgroup\$
    – Darren H
    Mar 19 '20 at 15:12
  • 1
    \$\begingroup\$ @DarrenH the date is ambiguous, but Mon should still be outputted, according to output specification. \$\endgroup\$ Mar 19 '20 at 16:13
  • 2
    \$\begingroup\$ @DarrenH I think you meant 3/2/2020. Those dates in 2019 fell on different days, but they're both Mondays in 2020. So Err should be the output for 3/2/2019, but Mon should be the output for 3/2/2020. \$\endgroup\$ Mar 19 '20 at 18:22
  • 1
    \$\begingroup\$ @MitchellSpector yes I did mean 2020, years are flying by I forgot we are in another one already, comes with old age \$\endgroup\$
    – Darren H
    Mar 20 '20 at 7:32

12 Answers 12

8
\$\begingroup\$

Bash + GNU utilities, 97 96 93 85 80 76 73 58 56 51 bytes

(${d=date +%a -d$3-}$1-$2;$d$2-$1)|uniq|sed N\;cErr

Try it online!

5 bytes off thanks to user41805, who pointed out that I could use sed's c command instead of the final s command.

2 bytes off thanks to Nahuel Fouilleul. (Nahuel also helped by shaving 4 bytes off in an earlier version that has since been revamped.)

When I noticed that the separators can be any non-digit character, I changed the code to use spaces as the separator.

This is called with a command like

checkdate 01 06 2020

The output is on stdout. (There's spurious output on stderr.)

\$\endgroup\$
8
  • \$\begingroup\$ small improvement d=date/+%a/-d IFS=/ \$\endgroup\$ Mar 19 '20 at 12:50
  • \$\begingroup\$ @NahuelFouilleul Very nice -- thank you! \$\endgroup\$ Mar 19 '20 at 13:20
  • \$\begingroup\$ 56 bytes \$\endgroup\$ Mar 19 '20 at 17:25
  • \$\begingroup\$ @NahuelFouilleul Thank you again. I didn't know that bash construct -- the man entry I have shows the ${var:=...} construct but not ${var=...}, unless I'm just missing it. \$\endgroup\$ Mar 19 '20 at 18:15
  • \$\begingroup\$ seems undocumented the difference between two is when the parameter is set to null example \$\endgroup\$ Mar 19 '20 at 19:42
8
\$\begingroup\$

05AB1E, 88 82 79 76 79 73 72 bytes

#ÂÀ‚Dε`UÐ3‹12*+>₂*T÷s3‹Xα¬Ésт%D4÷O.•A±₁γβCüIÊä6’C•3ôsè}DËiнës€¨13‹WiтëθÏ

-3 bytes thanks to @Arnauld (initially -6, but +3 bytes again, since Jan./Feb. 2000 would still result in the previous century in the formula).
-7 bytes thanks to @Grimmy.

Input is space-separated; and outputs in lowercase with 100 as error.

Try it online or verify all test cases.

Explanation:

Since 05AB1E has no Date builtins, we'll have to calculate the Day of the Week manually.

The general formula to do this is:

$${\displaystyle h=\left(q+\left\lfloor{\frac{13(m+1)}{5}}\right\rfloor+K+\left\lfloor{\frac{K}{4}}\right\rfloor+\left\lfloor{\frac{J}{4}}\right\rfloor-2J\right){\bmod{7}}}$$

Where for the months March through December:

  • \$q\$ is the \$day\$ of the month ([1, 31])
  • \$m\$ is the 1-indexed \$month\$ ([3, 12])
  • \$K\$ is the year of the century (\$year \bmod 100\$)
  • \$J\$ is the 0-indexed century (\$\left\lfloor {\frac {year}{100}}\right\rfloor\$)

And for the months January and February:

  • \$q\$ is the \$day\$ of the month ([1, 31])
  • \$m\$ is the 1-indexed \$month + 12\$ ([13, 14])
  • \$K\$ is the year of the century for the previous year (\$(year - 1) \bmod 100\$)
  • \$J\$ is the 0-indexed century for the previous year (\$\left\lfloor {\frac {year-1}{100}}\right\rfloor\$)

Resulting in in the day of the week \$h\$, where 0 = Saturday, 1 = Sunday, ..., 6 = Friday.
Source: Zeller's congruence

Since the input is guaranteed to be in the year-range \$[2000, 2030]\$, we can modify \$+ \left\lfloor{\frac{J}{4}}\right\rfloor - 2J\$ to a hard-coded \$-35\$ to save some bytes. EXCEPT, when it's Jan./Feb. 2000, in which case it's the previous century. For those, we use ƒs, to add 1 to our hard-coded replacement of \$J\$.
In addition, we won't need the \${\bmod {7}}\$, since we only use it to index into the string list, and 05AB1E uses modular indexing anyway.

We can save 2 more bytes, by just removing the \$-35\$ all together, since it's a multiple of 7 anyway.
And one more byte, by changing the \$\left\lfloor{\frac{13(m+1)}{5}}\right\rfloor\$ to \$\left\lfloor{\frac{26(m+1)}{10}}\right\rfloor\$, since 05AB1E has a single-byte builtin for 26 and 10 (which are and T respectively).

Code explanation:

#            # Split the (implicit) input-string by spaces
 Â           # Bifurcate it (short for Duplicate & Reverse copy)
  À          # Rotate the reversed copy once towards the left
   ‚         # Pair the top two values together
             # (we now have a pair [[day,month,year],[month,day,year]])
    D        # Duplicate it
     ε       # Map over both values in this pair:

Now comes Zeller's congruence into play:

`            #  Push the day, month, and year separated to the stack
 U           #  Pop and save the year in variable `X`
  Ð          #  Triplicate the month
   3‹        #  Check if the month is below 3 (Jan. / Feb.),
             #  resulting in 1 or 0 for truthy/falsey respectively
     12*     #  Multiply this by 12 (either 0 or 12)
        +    #  And add it to the month
             #  This first part was to make Jan. / Feb. 13 and 14

>            #  Month + 1
 ₂*          #  Multiplied by 26
   T÷        #  Integer-divided by 10
s3‹          #  Check if the month is below 3 again (resulting in 1 / 0)
   Xα        #  Take the absolute difference with the year
     ¬       #  Push the first digit (without popping the mYear itself)
      É      #  Check if its odd (1 if 1; 0 if 2)
     s       #  Swap to take the mYear again
      т%     #  mYear modulo-100
D4÷          #  mYear modulo-100, integer-divided by 4
O            #  Take the sum of all values on the stack

And then we'll continue:

.•A±₁γβCüIÊä6’C•
             #  Push compressed string "satsunmontuewedthufri"
  3ô         #  Split it into parts of size 3: ["sat","sun","mon","tue","wed","thu","fri"]
    s        #  Swap to get the earlier calculated number
     è       #  And index it into this list (0-based and with wrap-around)
}D           # After the map: duplicate the resulting two days
Ëi           # If they are both the same:
  н          #  Leave just one of them
ë            # Else (they are different):
 s           #  Swap, to take the initially duplicated pair of 
             #  [[day,month,year],[month,day,year]]
  ۬         #  Remove the year from each
    13‹      #  Check for the remaining values if they are smaller than 13
       W     #  Push the flattened minimum (without popping)
        i    #  If it's truthy (so day and month are both below 13):
         т   #   Push 100 (as error value)
        ë    #  Else:
         θ   #   Pop and leave just the last one (either [0,1] or [1,0])
          Ï  #   And only keep the day at the truthy index
             # (after which the top of the stack is output implicitly)

See this 05AB1E tip of mine (section How to compress strings not part of the dictionary?) to understand why .•A±₁γβCüIÊä6’C• is "satsunmontuewedthufri".

\$\endgroup\$
7
  • \$\begingroup\$ "Look mah" would look less "mah" as "Looks meh" :D I would edit it in, but I'm not sure if the "mah" is the "Dutch version" of "meh" :) \$\endgroup\$
    – RGS
    Mar 19 '20 at 9:59
  • 1
    \$\begingroup\$ You can probably get rid of \$J\$ since we're only asked to support dates from 2000 through 2030. \$\endgroup\$
    – Arnauld
    Mar 19 '20 at 10:14
  • \$\begingroup\$ @Arnauld Thanks, -6 bytes by hard-coding \$+\left\lfloor {\frac {J}{4}}\right\rfloor -2J\$ to \$-35\$. :) \$\endgroup\$ Mar 19 '20 at 11:48
  • 1
    \$\begingroup\$ My bad, my previous comment was wrong, I deleted it. #Ð2£TSǝ => #ÂÀ for -4. Rotate the list of days back to what it was so you can delete the -36 (-35 is a noop mod 7). \$\endgroup\$
    – Grimmy
    Mar 19 '20 at 22:55
  • 1
    \$\begingroup\$ 13*5÷ => ₂*T÷ for -1. \$\endgroup\$
    – Grimmy
    Mar 20 '20 at 11:02
4
\$\begingroup\$

Perl 6, 124 121 bytes

{<Err Mon Tue Wed Thu Fri Sat Sun>[2>set($/=|grep ?*,map {try Date.new(|$_).day-of-week},m:g/\d+/[[2,1,0],[2,0,1]])&&$0]}

Try it online!

\$\endgroup\$
3
\$\begingroup\$

JavaScript (ES6),  136 132  131 bytes

Returns "Err" for an error.

s=>'SunMonTueWedThuFriSatErr'.match(/.../g)[[i,j]=([d,m,y]=s.split`/`).map(M=>new Date(y,M-1,M^m^d).getDay()),d<13?m>12|i==j?i:7:j]

Try it online!

Commented

s =>                           // s = input string
  'SunMonTueWedThuFriSatErr'   // lookup string
  .match(/.../g)[              // split it and pick the relevant entry:
    [i, j] =                   //   (i, j) = day-of-week indices
      ([d, m, y] = s.split`/`) //   split s into (d, m, y)
      .map(M =>                //   for each value M in there (ignoring y):
        new Date(              //     create a date:
          y,                   //       using y as the year
          M - 1,               //       using M - 1 as the 0-indexed month
          M ^ m ^ d            //       using the other value as the day
        )                      //     end of Date()
        .getDay()              //     get the day-of-week index (Sun:0, ..., Sat:6)
      ),                       //   end of map()
    d < 13 ?                   //   if d is a valid month:
      m > 12 | i == j ? i      //     non-ambiguous if m is an invalid month or i = j
                      : 7      //     ambiguous otherwise (-> "Err")
    :                          //   else:
      j                        //     use j
  ]                            // end of lookup
\$\endgroup\$
3
\$\begingroup\$

Red, 186 169 167 bytes

-2 bytes thanks to Kevin Cruijssen

func[d][a: to-date t: load form split d"/"m: min a b:
to-date reduce[t/2 t/1 t/3]either any[a/10 = b/10 t/1 > 12
t/2 > 12][pick[:Mon:Tue:Wed:Thu:Fri:Sat:Sun]m/10][.1]]

Try it online!

Takes the input as string, because Red doesn't accept invalid dates in dd/mm/yyyy format. That's strange, because the coerce function to-date that takes a block of 3 values [dd mm yyyy] accepts any integer, including 0 and negative numbers and creates a "correct" date based on them.

\$\endgroup\$
4
  • 2
    \$\begingroup\$ You can save a byte by changing 'Err to 123 (or any other 3-digit number). \$\endgroup\$ Mar 19 '20 at 12:51
  • \$\begingroup\$ @Kevin Cruijssen Thanks, I always forget that. \$\endgroup\$ Mar 19 '20 at 13:04
  • 2
    \$\begingroup\$ Another -1 thanks to @Arnauld, by using .1 instead of 911, which will automatically become 0.1. \$\endgroup\$ Mar 19 '20 at 13:24
  • \$\begingroup\$ @KevinCruijssen Of course! That's nice. \$\endgroup\$ Mar 19 '20 at 13:31
2
\$\begingroup\$

PHP, 115 bytes

foreach(['/','-']as$s){if($x=strtotime(strtr($argn,'/',$s)))$o=date('D',$x);if($p&&$o!=$p)die('err');$p=$o;}echo$p;

Try it online!

Uses the peculiarity that the builtin strtotime parses dates as either European or American format according to whether the delimiter is either slash or dash.

Takes input as a/b/Y and returns err for an error condition.

\$\endgroup\$
2
\$\begingroup\$

Perl 5 -MPOSIX -naF/, 125 bytes

sub d{substr ctime(mktime 6,0,0,$_[0],$_[1]-1,$_[2]-1900),0,3}@G=@F[1,0,2];say$F[0]>12?d@F:$F[1]>12?d@G:d(@F)eq d(@G)?d@F:Err

Try it online!

Not completely happy with this one, but haven't thought up a better way (yet).

\$\endgroup\$
1
  • \$\begingroup\$ Save two bytes by replacing d(@F)eq d(@G) with d@F,eq d@G, \$\endgroup\$
    – Abigail
    Mar 19 '20 at 9:03
2
\$\begingroup\$

Python, 180 164 158 bytes

Saved 3 bytes thanks to Kevin Cruijssen and Arnauld!!!
Saved 3 bytes thanks to Surculose Sputum!!!

lambda a,b,c:(x:=d(a,b,c))==(y:=d(b,a,c))and x or(x and y and.1)or y or x
from datetime import*
def d(a,b,y):
 try:return date(y,a,b).strftime("%a")
 except:0

Try it online!

Assumes we can input the date as three separate arguments and returns 0.1 if ambiguous.

Date as string

Python, 197 191 bytes

Ditto the above saves and thanks! :D

lambda s:(x:=d(s))==(y:=d(s,1))and x or(x and y and.1)or y or x
from datetime import*
def d(s,q=0):
 a,b,c=map(int,s.split('/'))
 if q:a,b=b,a
 try:return date(c,a,b).strftime("%a")
 except:0

Try it online!

\$\endgroup\$
8
  • \$\begingroup\$ Why pre-release? Python 3.8 is released. \$\endgroup\$
    – Tyilo
    Mar 19 '20 at 12:26
  • \$\begingroup\$ @Tyilo Just because TIO calls it that. Hand edited to Python. \$\endgroup\$
    – Noodle9
    Mar 19 '20 at 12:36
  • 1
    \$\begingroup\$ You can save 2 bytes by changing "Err" to -99 (or any other negative 2-digit number). \$\endgroup\$ Mar 19 '20 at 12:50
  • 1
    \$\begingroup\$ @KevinCruijssen .1 (rendered as 0.1) would be even shorter. \$\endgroup\$
    – Arnauld
    Mar 19 '20 at 13:22
  • \$\begingroup\$ except:0 saves 3 bytes \$\endgroup\$ Mar 19 '20 at 14:08
1
\$\begingroup\$

Python 3.8 (pre-release), 125 bytes

Improvement over @Noodle9's answer

lambda a,b,y:[.1,w:=g(y,a,b)][w==g(y,b,a)]
g=lambda y,m,d:m<13and date(y,m,d).strftime("%a")or g(y,d,m)
from datetime import*

Try it online!

Input: 3 integers representing date, month, and year.
Output: A 3-character abbreviation of a weekday, or 0.1 if the weekday is ambiguous.

How:

  • date(y,m,d).strftime("%a") returns the abbreviated weekday of the date d/m/y.
  • g(y,m,d) returns the weekday if the date is valid, otherwise returns the weekday of the date where month and day are swapped. Thus if g(y,a,b) and g(y,b,a) are the same, then the weekday is not ambiguous. Otherwise, the weekday is ambiguous.
  • [.1,w:=g(y,a,b)][w==g(y,b,a)] evaluates to g(y,a,b) if the weekday is not ambiguous, or 0.1 if the weekday is ambiguous.
\$\endgroup\$
1
\$\begingroup\$

Perl 5 -MTime::Local=timegm_modern -pF'/', 116 113 108 bytes

sub f{$_[1]--;eval{timegm_modern 0,0,9,@_}}$_=($a=f@g=@F)*($b=f@F[1,0,2])&$a-$b?Err:substr gmtime($a|$b),0,3

Try it online!

Tries converting both formats into an epoch time. The eval traps the error that would occur for an invalid date. Multiplies the two epoch times together to make sure that one is 0, and checks that they are not the same with substraction. Uses their bitwise or to convert back to an actual date, then picks off the day of the week from that.

\$\endgroup\$
2
  • \$\begingroup\$ This has no effect on bytecount now that flags don't count towards that, but why not -pF/ and forgo the quotes? \$\endgroup\$
    – Value Ink
    Mar 21 '20 at 1:12
  • \$\begingroup\$ It could have been. I'm just so in the mindset of quoting certain things, I did it out of habit. \$\endgroup\$
    – Xcali
    Mar 21 '20 at 4:54
1
\$\begingroup\$

Excel, 85 Bytes

=IF(OR(DAY(A1)>12,MOD(A1-DATE(YEAR(A1),DAY(A1),MONTH(A1)),7)=0),TEXT(A1,"ddd"),"???")

Input the date into cell A1

Since Excel will automatically evaluate the date into a valid form for us, we can first check if the Day is >12, since this would immediately make the date unambiguous.

We then work out if difference between the "dd/mm/yyyy" and "mm/dd/yyyy" versions is a multiple of 7, using MOD, since this would mean both Weekdays are the same

If either of these conditions are True, then the Weekday is unambiguous.

\$\endgroup\$
1
\$\begingroup\$

Ruby -apl, 102 bytes

Takes input as a line of input from STDIN with space as the separator. Outputs false if the dates are ambiguous and the days of week don't match.

In order to make sure we get a correct date the first time, we sort the first two arguments to ensure the smaller of the two (which is guaranteed to be less than 13) is placed in the month field. We then reverse the arguments when comparing the days of week using wday (if it would be a valid date, done by checking b>12 first), and if they match (or the date was unambiguous) we use strftime("%a") to output the day of the week in a human-readable format.

-p takes each line of input and outputs the contents of $_ at the end of a program run.

-a is the autosplit flag, which will split the input on spaces and save into $F.

-l makes the input ignore newlines (they are included by default for Ruby's input function).

*x,y=$F.map &:to_i
d=Time.gm y,*x.sort!
a,b=x
$_=(b>12||d.wday==Time.gm(y,b,a).wday)&&d.strftime("%a")

Try it online!

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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