16
\$\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\$
  • 6
    \$\begingroup\$ Can we take the three parts of the date as three separate arguments? \$\endgroup\$ – RGS Mar 19 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 at 15:12
  • 1
    \$\begingroup\$ @DarrenH the date is ambiguous, but Mon should still be outputted, according to output specification. \$\endgroup\$ – Surculose Sputum Mar 19 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\$ – Mitchell Spector Mar 19 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 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.)

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ small improvement d=date/+%a/-d IFS=/ \$\endgroup\$ – Nahuel Fouilleul Mar 19 at 12:50
  • \$\begingroup\$ @NahuelFouilleul Very nice -- thank you! \$\endgroup\$ – Mitchell Spector Mar 19 at 13:20
  • \$\begingroup\$ 56 bytes \$\endgroup\$ – Nahuel Fouilleul Mar 19 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\$ – Mitchell Spector Mar 19 at 18:15
  • \$\begingroup\$ seems undocumented the difference between two is when the parameter is set to null example \$\endgroup\$ – Nahuel Fouilleul Mar 19 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".

| improve this answer | |
\$\endgroup\$
  • \$\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 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 at 10:14
  • \$\begingroup\$ @Arnauld Thanks, -6 bytes by hard-coding \$+\left\lfloor {\frac {J}{4}}\right\rfloor -2J\$ to \$-35\$. :) \$\endgroup\$ – Kevin Cruijssen Mar 19 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 at 22:55
  • 1
    \$\begingroup\$ 13*5÷ => ₂*T÷ for -1. \$\endgroup\$ – Grimmy Mar 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!

| improve this answer | |
\$\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
| improve this answer | |
\$\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.

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

| improve this answer | |
\$\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).

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ Save two bytes by replacing d(@F)eq d(@G) with d@F,eq d@G, \$\endgroup\$ – Abigail Mar 19 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!

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ Why pre-release? Python 3.8 is released. \$\endgroup\$ – Tyilo Mar 19 at 12:26
  • \$\begingroup\$ @Tyilo Just because TIO calls it that. Hand edited to Python. \$\endgroup\$ – Noodle9 Mar 19 at 12:36
  • 1
    \$\begingroup\$ You can save 2 bytes by changing "Err" to -99 (or any other negative 2-digit number). \$\endgroup\$ – Kevin Cruijssen Mar 19 at 12:50
  • 1
    \$\begingroup\$ @KevinCruijssen .1 (rendered as 0.1) would be even shorter. \$\endgroup\$ – Arnauld Mar 19 at 13:22
  • \$\begingroup\$ except:0 saves 3 bytes \$\endgroup\$ – Surculose Sputum Mar 19 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.
| improve this answer | |
\$\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.

| improve this answer | |
\$\endgroup\$
  • \$\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 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 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.

| improve this answer | |
\$\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!

| improve this answer | |
\$\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.