10
\$\begingroup\$

Today's date is quite an interesting one. Let's take a look at why. The date 07/12/2017 (in DD/MM/YYYY date format) can be split into the year (2017) and the rest (07/12). You may notice that the digits in the year can be rearranged to match the digits in the rest of the date, which is the basis of this challenge.

The task

Given an valid integer (e.g. no leading zeros) representing a year, consisting of exactly four digits, output all unique possible valid dates that can be made from those digits. You may take the number as a list of digits, or a string representing the input if you want. Each number in the input must be used exactly once in each of the final outputs. As leading 0s aren't used in integers, the input will be in the range 1000 <= input <= 9999.

A valid date is one in which the date exists on the Gregorian Calendar, such as 17/05/2000. The only corner case is February 29th, which is only a valid date when the year is a leap year. You can (but don't have to) assume that leap years are years that are divisible by 4.

You have to output a separator between the day and the month (e.g. /) and a different separator between different dates. You may optionally output the year with each date, but this must be consistent between dates.

Finally, you may choose between the date format order, i.e. you can output as any of DD/MM/YYYY, MM/DD/YYYY etc. but leading 0s are required when the input contains at least one 0.

Essentially, you may output in any obviously understandable format, such as an array of strings, one concatenated string etc.

This is a so shortest code wins!

Test cases

input
outputs

2017
07/12, 12/07, 17/02, 21/07, 27/01, 27/10

2092
22/09, 29/02

2029
22/09

1000
Nothing

8502
25/08, 28/05

Reference Implementation

\$\endgroup\$
18
  • 1
    \$\begingroup\$ Not all years divisible by 4 are leap. Should we consider that this is the case nonetheless? \$\endgroup\$
    – Arnauld
    Commented Dec 7, 2017 at 17:31
  • 1
    \$\begingroup\$ @Arnauld Yes, you can assume that all leap years are divisible by 4 \$\endgroup\$ Commented Dec 7, 2017 at 17:32
  • 1
    \$\begingroup\$ Are duplicated dates a problem? \$\endgroup\$
    – Uriel
    Commented Dec 7, 2017 at 17:53
  • 1
    \$\begingroup\$ @Uriel output all unique possible valid dates \$\endgroup\$ Commented Dec 7, 2017 at 17:54
  • 1
    \$\begingroup\$ @Shaggy Yes, that’s no problem \$\endgroup\$ Commented Sep 4, 2020 at 19:11

7 Answers 7

6
\$\begingroup\$

Japt, 31 30 26 24 23 22 17 bytes

Locale dependent. Outputs a 2D-array of the format [["MM","DD"],...].

á â mò fÈÎ-ÐNcX)Î

Try it or Try It Online! (to avoid the locale dependency)

Explanation

If it wasn't for JavaScript's Date object wrapping invalid dates, I'd be able to get this down to about 20 18 16 bytes. Even when providing its argument as a single string, which the spec says shouldn't lead to wrapping, dates up to 31 still do for months that don't have 31 days. However, as the month's can only wrap over by one, we can take advantage of the fact that string arguments use 1-based months to simply filter by subtraction - if the month rolls over then the result of that subtraction will be 0.

á â mò fÈÎ-ÐNcX)Î     :Implicit input of N=[U="YYYY"]
á                     :Permutations of U
  â                   :Deduplicate
    m                 :Map
     ò                :  Partitions of length 2 (=[["MM","DD"],...])
       f              :Filter
        È             :By passing each X through the following function
         Î            :  First element of X
          -           :  Subtract
           Ð          :  Create new Date from
            NcX       :    N concatenated with X (=["YYYY","MM","DD"], which gets coerced to "YYYY-MM-DD", with MM being 1-based)
               )      :  End date creation
                Î     :  0-based month (=NaN for invalid dates, n-NaN=NaN)

Original, 22 bytes

Preserving this, 'cause I really liked the setwise union trick.

á
â¡ÐNcXòÃfÈÎ-2îUvÃms7

Try it (footer formats the output to match the examples in the challenge)
Or Try it online! to somewhat mitigate the locale dependency - TIO's locale doesn't include leading 0s for dates & months, so they're added back in in the footer.

Explanation

á\nâ¡ÐNcXòÃfÈÎ-2îUvÃms7     :Implicit input of N=[U="YYYY"]
á                           :Permutations of U
 \n                         :Reassign to U, leaving the value in N unchanged
   â                        :Setwise union with
    ¡                       :Map each X in U
     Ð                      :  New Date from
      Nc                    :  N concatenated with
        Xò                  :  Partitions of X of length 2 (=["YYYY","MM","DD"], which gets coerced to "YYYY-MM-DD", with MM being 1-based)
          Ã                 :End map
           f                :Filter by
            È               :Passing each through the following function
             Î              :  Get 0-based month (=NaN for invalid date)
              -             :  Subtract (NaN-n=NaN)
               2î           :  First 2 characters of
                 Uv         :  Remove and return first element of U
                   Ã        :End filter
                    m       :Map
                     s7     :  To locale date string (="DD/MM/YYYY")

The reason the setwise union doesn't include any elements from the first array is because v modifies that array so the filter ends up removing all elements from it.

In the 16 byte version, which assumes a way to avoid dates wrapping over, we don't need to reassign the array of possible dates to U and instead filter by using Í to try to subtract the timestamp from 2, which will result in NaN if the date is invalid. We can't use the setwise union trick, though, so â get tacked on the end to deduplicate the final array.

\$\endgroup\$
6
  • 1
    \$\begingroup\$ Man, this must be the only time I've gone 1000+ days between asking a question and it being answered :P \$\endgroup\$ Commented Sep 4, 2020 at 16:43
  • \$\begingroup\$ Only noticed that once you said it, @cairdcoinheringaahing! :o \$\endgroup\$
    – Shaggy
    Commented Sep 4, 2020 at 16:44
  • 2
    \$\begingroup\$ Of course being deleted for 1000 days might help :P \$\endgroup\$ Commented Sep 4, 2020 at 16:45
  • 1
    \$\begingroup\$ @cairdcoinheringaahing, I do admit that I was confused as to why you thought it was 2017! \$\endgroup\$
    – Shaggy
    Commented Sep 4, 2020 at 21:30
  • 1
    \$\begingroup\$ Yup, looks like it is, @Dingus. I get no results from the 17-byte version for an input of "2000", as expected. I'll add a note that it's locale dependent with an alternative link to TIO. \$\endgroup\$
    – Shaggy
    Commented Sep 7, 2020 at 16:49
4
\$\begingroup\$

05AB1E, 36 35 bytes

œÙ2δôʒ`ÐU13‹rD•ü¯B•5°I4Ö*+ºS₂+Xè‹PĀ

05AB1E doesn't have any date builtins, so manual calculations it is. Therefore all years divisible by 4 are counted as leap years (which is allowed in the challenge description), since that's shorter than actual leap year calculations.

Output as a list of [dd,MM]-pairs.

Try it online or verify all test cases.

Explanation:

œ                # Get all permutations of the (implicit) input-integer
 Ù               # Uniquify this list
   δ             # For each permutation:
  2 ô            #  Split it into parts of size 2
ʒ                # Filter this list of [dd,MM]-pairs by:
 `               #  Pop and push both values separated to the stack
  Ð              #  Triplicate the top value MM
   U             #  Pop and store one MM in variable `X`
    13‹          #  Check whether MM is smaller than 13
  r              #  Reverse the values on the stack, so the order is MM<13,MM,dd
   D             #  Duplicate the top value dd
    •ü¯B•        #  Push compressed integer 16365656
     5°          #  Push 10⁵: 100000
       I         #  Push the input-year
        4Ö       #  Check if it's divisible by 4 (1 if truthy; 0 if falsey)
          *      #  Multiply that by the 100000 (100000 if it's a 'leap year'; 0 if not)
           +     #  Add it to the 16365656 (16465656 if it's a 'leap year'; 16365656 if not) 
            º    #  Mirror it: 1636565665656461 / 1646565665656461
             S   #  Convert it to a list of digits: [1,6,3/4,6,5,6,5,6,6,5,6,5,6,3/4,6,1]
              ₂+ #  Add 26 to each: [27,32,29/30,32,31,32,31,32,32,31,32,31,32,29/30,32,27]
    X            #  Push MM from variable `X`
     è           #  Index it into this list (0-based modulair, hence the leading dummy 27)
    ‹            #  Check if dd is smaller than the indexed value
  P              #  Take the product of all values on the stack: (MM<13)*MM*dd*(dd<...)
   Ā             #  Check that this is NOT 0
                 # (after which the filtered list of [dd,MM]-pairs is output implicitly)

See this 05AB1E tip of mine (section How to compress large integers?) to understand why •ü¯B• is 16365656.

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

R, 231 216 189 186 bytes

Edit: -42 bytes by keeping numbers as numbers (no conversion to text) and using sprintf to fix the leading zeros

function(y){e=unique(gtools::permutations(4,4,y%/%10^(0:3)%%10,F))
m=e[,1]*10+e[,2];n=e[,3]*10+e[,4]
sprintf("%02d/%02d",m,n)[which(m&n&(m<(31-rep(-1:0,l=13)[-8])[n]-(n==2)*(2-!y%%4)))]}

Try it online!

This challenge seemed quite innocuous when I read it, but turned-out to be much, much more complicated than I thought to implement! The actual checking for possible day/month combinations is less than one-third of the code!

Finds all unique permutations of the digits of the year, combines them in pairs as numbers text (to keep leading zeros), then finds the indices that are possible after converting back to numbers, and sprintfs the output to keep the leading zeros.

Calculates days in months as (31-rep(-1:0,l=13)[-8]) which gives all correct except Feb, then adjusts with -(month==2)*(2-!y%%4)) to fix Feb for leap-years.

Original commented code (before quite a lot of golfing):

possible_dates=
function(y,                     # y = year (number)
 `+`=as.double,                 # `+` = alias to as.double() function, to easily
                                #       convert text to numbers
  a=apply,                      # a = alias to apply() function (runs helper function over all elements of matrix)
  p=paste0){                    # p = alias to paste0 function (joins text together)
  d=a(                          # d (digits) = 
   unique(                      # get the unique combinations of...
    gtools::permutations(4,4,   # all permutations of...
     y%/%10^(0:3)%%10,F))       # the digits of the year...
   ,1,function(i)               # and, for each combination... 
    p(i[1:2],i[3:4]))           # paste-together the first & second two digits as text
 a(d,2,p,collapse="/")          # now, paste these together with '/' as separator,
  [which(                       # but only output those that satisfy...
   (m=+d[1,])&                  # first element (days) non-zero, and...
   (n=+d[2,])&                  # second element (months) non-zero, and...
   (m<(31-rep(-1:0,l=13)[-8])[n]  # days less than days in the month...
   -(m==2)*(2-!y%%4))           # with correction for leap-years
  )]
}
\$\endgroup\$
2
  • \$\begingroup\$ I have to say, I'm also quite surprised at how difficult this turns out to be. I'm not familiar enough with R to have a benchmark expectation, but I was fully expecting Shaggy's Japt answer to be around half the length. Interesting how that happens. \$\endgroup\$ Commented Sep 5, 2020 at 16:13
  • \$\begingroup\$ @cairdcoinheringaahing Well, I'd like to encourage you to try your hand at R while it's the 'language of the month', but... ...I can't really recommend you use this challenge as a first attempt! It was fun, though (in the end), so thanks for posting it 3 years ago! \$\endgroup\$ Commented Sep 5, 2020 at 16:19
3
\$\begingroup\$

Ruby, 94 91 90 88 bytes

-3 bytes thanks to @Shaggy and another -2 bytes thanks to @vrintle

->y{[]|y.permutation.map{|d|("#{Time.gm y*'',(d*'')[/../],$'}"rescue$/)[/\S+-#$'/]}-[p]}

Try it online!

Takes input as an array of digits and returns an array of date strings in yyyy-mm-dd format. Leap years are handled correctly, i.e. it is not assumed that all years that are divisible by 4 are leap years. A gotcha with the Time library is that it sets an upper limit of 31 days for all months, silently wrapping to the next real calendar date, so a sanity check is required. Invalid months are automatically rejected.

->y{
  []|                                   # combine with empty array
  y.permutation                         # form all permutations of the year digits
   .map{|d|                             # loop over permutations
     ("#{Time.gm y*'',(d*'')[/../],$'}" # (attempt to) create a time string using the first two digits of the permutation as the month and the last two digits as the day
       rescue$/)                        # if the time is invalid, rescue the exception and return a dummy string ($/) instead
     [/\S+-#$'/]                        # check that the day is the same as expected (to prevent date wrapping) and extract the full date if so
   }
  -[p]                                  # remove nil elements
}
\$\endgroup\$
4
  • \$\begingroup\$ y.permutation() seems to work without the 4. \$\endgroup\$
    – Shaggy
    Commented Sep 7, 2020 at 15:58
  • \$\begingroup\$ @Shaggy Thanks. I didn't know you could do that. \$\endgroup\$
    – Dingus
    Commented Sep 7, 2020 at 16:03
  • 1
    \$\begingroup\$ Neither did I - I don't know Ruby at all! \$\endgroup\$
    – Shaggy
    Commented Sep 8, 2020 at 11:11
  • \$\begingroup\$ @vrintle Nice one - it was bound to happen. :) (d*'')[/../],$' splits the four-digit string d*'' (i.e. the joined array d) into two parts. The first part, (d*'')[/../], returns the first two digits (the month). The second part, the predefined variable $', returns everything to the right of the previous regex match, which in this case is the last two digits. $/ is the input record separator ("\n" by default), but I'm just using it so that rescue returns a string. Any other string would do but you'd need a space in between. \$\endgroup\$
    – Dingus
    Commented Dec 12, 2020 at 9:14
2
\$\begingroup\$

Python 3.8, 149 140 bytes

Uses the simplified definition of leap years.

lambda y:{i+o+'/'+z+u for z,u,i,o in permutations(str(y))if((m:=int(z+u))==2)*~(y%4>0)+(m//8^m%2)+31>int(i+o)>0<m<13}
from itertools import*

Try it online!

\$\endgroup\$
3
  • 2
    \$\begingroup\$ "leading 0s are required when the input contains at least one 0" \$\endgroup\$ Commented Sep 7, 2020 at 10:36
  • 1
    \$\begingroup\$ @DominicvanEssen thanks for pointing it out, should be fixed now. \$\endgroup\$
    – ovs
    Commented Sep 7, 2020 at 10:46
  • \$\begingroup\$ Yes, and I've just copied your 'output the digits' approach, which has saved me 15 bytes, too! Thanks! \$\endgroup\$ Commented Sep 7, 2020 at 11:37
1
\$\begingroup\$

Python 3, 186 197 bytes

import itertools;import datetime;y=input();s=set()
for p in itertools.permutations(list(y)):
 try:f=datetime.date(int(y),int(p[2]+p[3]),int(p[0]+p[1]));s.add(f)
 except:pass
for d in s:print(d)
\$\endgroup\$
2
  • 1
    \$\begingroup\$ "leading 0s are required when the input contains at least one 0" \$\endgroup\$ Commented Sep 7, 2020 at 10:38
  • \$\begingroup\$ @DominicvanEssen: should be fixed \$\endgroup\$
    – friol
    Commented Sep 7, 2020 at 13:40
1
\$\begingroup\$

k4, 55 bytes

{?,/(@[.:;;()]x,"."/:0 0 2_)'(,"")(,/{(1_x),*x}\',')/x}

Not the most elegant, but it outputs a list of valid dates (accounting for e.g. leap years). Takes a string as input, e.g. "2017".

  • (,"")(,/{(1_x),*x}\',')/x builds a list of the permutations of the input
  • x,"."/:0 0 2_ takes each permutation, prepends the input year, and inserts "."s to format it as a date. E.g., for an input of "0127", the output is "2017.01.27".
  • @[.:;;()] is error trap; it attempts to eval the generated date string, returning an empty list if the value is an invalid date.
  • ?,/ returns the distinct valid dates
\$\endgroup\$

Your Answer

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

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