16
\$\begingroup\$

The date format varies between countries; the arrangement of date, month and year can be categorized into these three:

  • Year-month-date
  • Month-date-year
  • Date-month-year

In Gregorian calendar it's year of 2024 now (at this point I publish this problem); we often shorten the notation of year by using only bottom two digits: e.g. 24.

Month and date are usually represented as one or two digits.

Today you are having a triple desicion-problem who do something with date format.

Input

One of:

  • A string of six digits
  • An nonnegative integer up to 999999
  • Three nonnegative integers up to 99
  • Six nonnegative integers up to 9
  • Any other reasonable representation that can be converted to a string of six digits or vise versa in consistent way

Here is a Python snippet of generator to generate every input as string:

f"{d:06}" for d in range(999999+1)

Attempt This Online!

Output

A tuple of three boolean values whether it can be interpreted as YYMMDD, MMDDYY, and DDMMYY date format. As long as it is consistent, you can have flexible output. Examples but not limited to:

  • a set of data
  • an integer with possibly three bits
  • 0-3 characters of string consists of y, m and/or d to indicate it is truthy for YYMMDD, MMDDYY, and DDMMYY respectively
  • different exception classes

Specification

TLDR: Gregorian Calendar. YY is bottom two digits of the year. Handle leap year as usual. If YYMMDD is 000229 it is optional whether its format is valid or not.

A digit is a character one of 0, 1, 2, 3, 4, 5, 6, 7, 8, and 9.

The YY format is any combination of two digits.

The MM format is one of 01, 02, 03, 04, 05, 06, 07, 08, 09, 10, 11, or 12.

The DD format varies depending on the YY and MM formats:

  1. if MM is one of 04, 06, 09, or 11, then the format is one of 01, 02, 03, ..., 09, 10, 11, 12, ..., 19, 20, 21, ..., 29, or 30.
  2. if MM is one of 01, 03, 05, 07, 08, 10, or 12 then the format is one of 01, 02, ..., 29, 30 or 31.
  3. if MM is 02 and YY is NOT a multiple of 4, then the format is one of 01, ..., 27, or 28.
  4. if MM is 02 and YY is 00 then the format is one of what rule 3 says or optionally a string of 29.
  5. if MM is 02 and YY is NOT 00 and is a multiple of 4, then the format is what rule 3 says or 29.

The YYMMDD format is a concatenation of the YY, MM and DD formats.

The MMDDYY format is a concatenation of the MM, DD and YY formats.

The DDMMYY format is a concatenation of the DD, MM and YY formats.

Test cases

In the following table, maybe means it is implementation-defined to output whether the value is truthy or falsey.

Input   YYMMDD MMDDYY DDMMYY
000000  false  false  false
000001  false  false  false
000099  false  false  false
000100  false  false  false
000101  true   false  false
000102  true   false  false
000131  true   false  false
000132  false  false  false
000199  false  false  false
000200  false  false  false
000201  true   false  false
000228  true   false  false
000229  maybe  false  false
000230  false  false  false
000300  false  false  false
000301  true   false  false
000331  true   false  false
000332  false  false  false
001130  true   false  false
001231  true   false  false
001232  false  false  false
010000  false  false  false
010001  false  false  false
010099  false  false  false
010100  false  true   true
010101  true   true   true
010102  true   true   true
010131  true   true   true
010132  false  true   true
010199  false  true   true
010200  false  true   true
010201  true   true   true
010228  true   true   true
010229  false  true   true
010300  false  true   true
010301  true   true   true
010331  true   true   true
010332  false  true   true
010430  true   true   true
010431  false  true   true
010531  true   true   true
010532  false  true   true
011231  true   true   true
011232  false  true   true
011299  false  true   true
011300  false  true   false
011399  false  true   false
011400  false  true   false
013199  false  true   false
013200  false  false  false
014567  false  false  false
019999  false  false  false
020000  false  false  false
020001  false  false  false
020099  false  false  false
020100  false  true   true
020101  true   true   true
020102  true   true   true
020128  true   true   true
020131  true   true   true
020132  false  true   true
020199  false  true   true
020200  false  true   true
020201  true   true   true
121212  true   true   true
121213  true   true   true
121230  true   true   true
121231  true   true   true
121232  false  true   true
121299  false  true   true
121300  false  true   false
121301  false  true   false
121399  false  true   false
121400  false  true   false
123100  false  true   false
123199  false  true   false
123200  false  false  false
129999  false  false  false
130000  false  false  false
130099  false  false  false
130100  false  false  true
990101  true   false  false
990202  true   false  false
991230  true   false  false
991231  true   false  false
991232  false  false  false
991233  false  false  false
999999  false  false  false

Every case truthy for all of YYMMDD, MMDDYY, and DDMMYY

Too long; pasted on GitHub Gists.

Every maybe ones

Maybe for YYMMDD: 000229

Maybe for MMDDYY: 022900

Maybe for DDMMYY: 290200

That's all; just three.

Test case generator

inputrange = list(range(200, 231)) + list(range(0, 1000000, 2763))

yymmdds, mmddyys, ddmmyys = [], [], []
maybey, maybem, maybed = [], [], []
for y in range(99+1):
  for m in range(1,12+1):
    if m == 2:
      dlast = 28 + (y%4==0)
    else:
      dlast = 31 - (m in [4,6,9,11])
    for d in range(1,dlast+1):
      yy, mm, dd = f"{y:02} {m:02} {d:02}".split(" ")
      ys, ms, ds=yy+mm+dd, mm+dd+yy, dd+mm+yy
      if (y,m,d) == (0,2,29):
        [maybel.append(s)for (maybel,s)in [[maybey,ys],[maybem,ms],[maybed,ds]]]
        continue
      yymmdds.append(ys)
      mmddyys.append(ms)
      ddmmyys.append(ds)

for x in [f"{x:06}" for x in inputrange]:
  y, m, d = ["maybe" if x in maybel else x in l for (maybel,l) in ((maybey,yymmdds),(maybem,mmddyys),(maybed,ddmmyys))]
  print(x, y, m, d)

Try it online!

Rules

See also

Challenges involving possibly ambiguous date format:

sandbox

https://codegolf.meta.stackexchange.com/a/26415/113493

\$\endgroup\$
1
  • 1
    \$\begingroup\$ xkcd.com/1179 or "xkcd 1179" if you're one of the cool kids. \$\endgroup\$
    – Criggie
    Commented Sep 4 at 19:06

12 Answers 12

11
\$\begingroup\$

JavaScript (ES6), 86 bytes

A mix of my previous answer with @l4m2's answer.

Expects a string of 6 digits.

Returns a triplet [YYMMDD, MMDDYY, DDMMYY] of Boolean values (true for valid). Returns valid for the "maybe" cases.

s=>[2,6,8].map(p=>(new Date(4+(g=q=>s[i=(p^q)%6]+s[i+1]+" ")(2)+g``+g(8))+0)[8]==s[i])

Try it online!


JavaScript (ES6), 91 bytes

Without Date().

Expects a string of 6 digits.

Returns a triplet [YYMMDD, MMDDYY, DDMMYY] of binary values, with \$0\$ for valid and \$1\$ for invalid. Returns valid for the "maybe" cases.

s=>[2,6,8].map(p=>(g=q=>v=+s.substr((p^q)%6,2))``>12|!v|28+(v-2?3^~v%9&1:g(2)%4<1)<g(8)|!v)

Try it online!

Method

We need a short way to extract the fields at the correct positions from the input string.

We use a parameter \$p\in\{2,6,8\}\$ for the date format and a parameter \$q\in\{0,2,8\}\$ for the field type. Given \$p\$ and \$q\$, we compute the index with:

$$(p \operatorname{XOR} q)\bmod 6$$

Applying the XOR, this gives:

XOR \$q=0\$ (month) \$q=2\$ (year) \$q=8\$ (day)
\$p=2\$ 2 0 10
\$p=6\$ 6 4 14
\$p=8\$ 8 10 0

And reducing modulo \$6\$, this gives:

XOR \$q=0\$ (month) \$q=2\$ (year) \$q=8\$ (day) format
\$p=2\$ 2 0 4 YYMMDD
\$p=6\$ 0 4 2 MMDDYY
\$p=8\$ 2 4 0 DDMMYY

Commented

s =>               // s = input string
[ 2,               // used for YYMMDD
  6,               // used for MMDDYY
  8 ]              // used for DDMMYY
.map(p =>          // for each value p in the above array:
  ( g = q =>       //   g is a helper function taking q:
    v = +s.substr( //     return the 2-digit substring extracted
      (p ^ q) % 6, //     from s using p and q, coerced to an integer
      2            //     and saved in v
    )              //     q = 0 -> month, q = 2 -> year, q = 8 -> day
  )`` > 12         //   fail if the month is greater than 12
  | !v             //   or the month is 0
  | 28 + (         //   compute the upper day bound:
    v - 2 ?        //     if this is not February:
      3 ^ ~v % 9   //       use either 30 or 31 depending on
      & 1          //       the LSB of ~v % 9
    :              //     else:
      g(2) % 4 < 1 //       use 29 if the year is a multiple of 4
                   //       or 28 otherwise
  ) < g(8)         //   fail if the day is greater than the upper bound
  | !v             //   or the day is 0
)                  // end of map()
\$\endgroup\$
3
  • \$\begingroup\$ I'd argue that new Date() and Date.parse() are such validators. \$\endgroup\$ Commented Sep 2 at 11:00
  • \$\begingroup\$ Who preserve which or both preserve both? \$\endgroup\$
    – l4m2
    Commented Sep 3 at 9:20
  • \$\begingroup\$ momentjs looks promising. Still new here, not sure about conventions. \$\endgroup\$ Commented Sep 3 at 15:31
7
\$\begingroup\$

JavaScript (Node.js), 79 bytes

s=>[2,6,8].map(p=>(new Date(4+[2,0,8].map(q=>s[i=(p^q)%6]+s[i+1]))+0)[8]==s[i])

Try it online!

Refer to Arnauld's solution for part of revision and version without Date

Arnauld's comment say pure Date won't work

JavaScript (Node.js), 88 bytes

s=>[2,6,8].map(p=>`a3${(g=q=>+s.substr((p^q)%6,2)||g)(2)%4?0:1}3232332323`[g``]>g(8)-29)

Try it online!

Without Date it's possibly shorter

s => (
    g = q =>
        +s.substr(
            (p^q)%6,   // magic from Arnauld
            2          // XOR q=0 q=2 q=8
        ) || NaN,      // p=2  2   0   4         If value is 0, replace
    [2,6,8].map(       // p=6  0   4   2         with NaN so it won't
        p =>           // p=8  2   4   0         pass date range test
            `a3${      // 0 unused, 2 decided by year
            g(2)%4?0:1
            }3232332323`[g(0)]
        >= g(8) - 28   // Shifted by 28 to fit in string
)

Modifying Arnauld's also result 88, with maybe be false

\$\endgroup\$
4
\$\begingroup\$

Google Sheets, 69 bytes

=bycol({A3,C3,C3;B3,A3,B3;C3,B3,A3},lambda(_,isdate(20&join("-",_))))

Put the three date components in cells A3:C3 as in 24, 12, 31 and the formula in cell H3.

screenshot

The formula lists the components in a matrix with the combinations listed in the question down each of three columns, iterates those columns to get each combo as yyyy-MM-dd, and determines whether it is a valid date.

Years like 2000 and 2400 are leap years, but years like 1800, 1900 and 2100 aren't. A string like 000229 would therefore be indeterminate in the yyMMdd format, because we don't know whether it refers to 1900, 2000 or some other year. To show maybe with these strings, use this (99 bytes):

=bycol({A3,C3,C3;B3,A3,B3;C3,B3,A3},lambda(_,if(join(,_)="000229","maybe",isdate(20&join("-",_)))))

Ungolfed:

=bycol( 
  { 
    A3, C3, C3; 
    B3, A3, B3; 
    C3, B3, A3 
  }, 
  lambda(col, 
    if(join("", col) = "000229", "maybe", isdate(20 & join("-", col))) 
  ) 
)
\$\endgroup\$
1
  • 6
    \$\begingroup\$ I feel this answer is going to end up in production some time in the future in some ungodly codebase \$\endgroup\$ Commented Sep 3 at 10:08
2
\$\begingroup\$

Python 3, 128 bytes

f=lambda y,m,d:(0<m<13)*(0<d<(62648012>>2*m&3)+29or((y%4,m,d)==(0,2,29))*(2-0**y)/2)
g=lambda y,m,d:(f(y,m,d),f(d,y,m),f(d,m,y))

Try it online!

f returns 1 if y,m,d is valid, 0 if y,m,d is invalid and 0.5 if y,m,d is maybe valid.

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

05AB1E, 52 bytes

11Ýε7%É31αyΘ-LтL<ây>δšʒ`4Ör<s29QP≠]€`T‰JD2δ.IDí)JQ€à

Try it online. (No test suite, since it's too slow to run more than 2-3 test cases at once.

Explanation:

Brute-force approach to generate all valid [MM,dd,yy], [dd,MM,yy], [yy,MM,dd] dates and see if the input is in them.

Step 1a: Generate all [mm,dd,yy] dates:

11Ý           # Push a list in the range [0,11]
   ε          # Map each value to:
    7%É31αyΘ- #  Calculate the amount of days in this 0-based month †:
    7%        #   Modulo-7
      É       #   Modulo-2 (check whether it's odd)
       31α    #   Absolute difference with 31
          y   #   Push the current value again
           Θ  #   05AB1E truthify (1 if 1; 0 otherwise)
            - #   Subtract that from the earlier number
    L         #  Push a list in the range [1,daysInMonth]
     тL<      #  Push a list in the range [0,99]
        â     #  Cartesian product to create all [dd,yy] pairs
           δ  #  Map over each pair:
         y> š #   Prepend the current value+1 (MM)

†: Taken from this answer of mine.

Try just step 1a online.

Step 1b: Remove all invalid [2,29,leapYear] dates:

    ʒ         # Filter this list of triplets by:
     `        #  Pop and push the three values to the stack
      4Ö      #  Check whether the year is divisible by 4
     r        #  Reverse the stack so the month is at the top
      <       #  Decrease it by 1 to check whether it was 2 (aka February)
     s        #  Swap so the day is at the top
      29Q     #  Check whether it equals 29
         P    #  Check if all three are truthy
          ≠   #  Invert that check

Try just steps 1a and 1b online.

Step 1c: Format all single digits to two digits with leading 0s:

]             # Close both the filter and map
 €`           # Flatten one level down
T‰            # Divmod each inner-most value by 10
  J           # Then join each of those pairs back together

Try just step 1 online.

Step 2: Now that we have a list of all valid [MM,dd,yy]-dates, also create the other two formats:

D             # Duplicate the current list
  δ           # Map over each inner triplet:
 2 .I         #  Get its 0-based 2nd permutation, so [MM,dd,yy] becomes [dd,MM,yy]
     D        # Duplicate that list again
      í       # Reverse each inner triplet, so [dd,MM,yy] becomes [yy,MM,dd]
       )      # Wrap all three lists of triplets on the stack into a list

Try the first two steps online, with added footer to have ten random dates from each format.

Step 3: Verify in which of the three lists of date-formats the input-string is:

J             # Join each inner triplet together to a string of six digits
 Q            # Check for each of those whether it equals the (implicit) input
  €           # Map over each inner list of checks:
   à          #  Check of any is truthy by leaving the maximum
              # (after which the resulting triplet is output implicitly)
\$\endgroup\$
1
\$\begingroup\$

Ruby, 71 61+4=65 bytes

->a,b,c{[a+b+c,c+a+b,c+b+a].map{|x|!!Date.parse(x)rescue !0}}

Try it online!

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

Retina 0.8.2, 120 bytes

(..)(..)
$'$2$1¶$'$&¶$&
%`([02468][048]|[13579][26])0229|..(?!0229|023|(0[469]|11)31)(0[1-9]|1[0-2])(?!00)([0-2].|30|31)

Try it online! Outputs three flags depending on whether the input is DDMMYY, MMDDYY or YYMMDD respectively, but link is to test suite that produces a human-readable form. Explanation:

(..)(..)
$'$2$1¶$'$&¶$&

Generate three dates, the first being YYMMDD if the input was DDMMYY, the second being YYMMDD if the input was MMDDYY, and the last being the input.

%`

Check each date separately.

([02468][048]|[13579][26])0229|..

Is this February 29th in a leap year?

(?!0229|023|(0[469]|11)31)

Disallow February 29th (in non-leap years), 30th and 31st, and also April, June, September and November 31st.

(0[1-9]|1[0-2])(?!00)([0-2].|30|31)

Check that the month is 1-12 and the day is 0-31 but not 0.

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

Go, 134 bytes

import."time"
func f(d string)(b[]bool){for _,F:=range[]string{"060102","010206","020106"}{_,e:=Parse(F,d);b=append(b,e==nil)}
return}

Attempt This Online!

3 calls to time.Parse, with the following formats:

  • 060102 for YYMMDD
  • 010206 for MMDDYY
  • 020106 for DDMMYY

The solution uses the fact that time.Parse returns an error if the parsing fails; checking if it's nil allows for checking if parsing succeeded.

Takes in the date as a string, and returns a slice of bools. Returns true for the maybe cases.

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

JavaScript (Node.js) + momentjs, 76 bytes

m=require('moment')
f=(a,b,c)=>[a+b+c,c+a+b,c+b+a].map(s=>m(20+s).isValid())

Based on my Google Sheets answer. Expects three string arguments as in f('24','12','31').

Sorry no TIO, don't know how to import momentjs there. Only ran full tests in the browser + Google Apps Script (V8). The snippet below seems to work though (thanks Neil!)

m = moment

f=(a,b,c)=>[a+b+c,c+a+b,c+b+a].map(s=>m(20+s).isValid())

document.write(f('00', '01', '01') + '<br />')
document.write(f('01', '01', '31') + '<br />')
document.write(f('31', '01', '01') + '<br />')
document.write(f('01', '02', '29') + '<br />')
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.30.1/moment.min.js"></script>

\$\endgroup\$
4
  • \$\begingroup\$ Can you not demonstrate this using a Stack Snippet? \$\endgroup\$
    – Neil
    Commented Sep 3 at 22:28
  • \$\begingroup\$ @Neil thanks, cannot require but fetch + eval seems to work. \$\endgroup\$ Commented Sep 3 at 22:40
  • \$\begingroup\$ Oops, I didn't mean to do that edit... \$\endgroup\$
    – Neil
    Commented Sep 3 at 23:32
  • \$\begingroup\$ @Neil thanks, that's much cleaner. \$\endgroup\$ Commented Sep 4 at 6:42
1
\$\begingroup\$

C (clang), 98 bytes

i;f(*r,y,m,d){for(i=3;i--;d^=m^=d^=m)r[i]=!m|m>12|!d|d>(m-2?30|m^m>7:y%4?28:29),i>1?y^=d^=y^=d:0;}

Try it online!

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

Charcoal, 58 bytes

F¹⁰⁰F¹²F⎇⊖κ⁺²⁸¬﹪ι⁴⁻³¹﹪﹪κ⁷¦²⊞υ⟦ι⊕κ⊕λ⟧⭆⪪⭆”)∨^⍘”§⪪θ²Iι⁶№υI⪪ι²

Try it online! Link is to verbose version of code. Outputs three 0s or 1s depending on whether the input is in YYMMDD, MMDDYY or DDMMYY respectively. Explanation: Inspired by @KevinCruijssen's 05AB1E answer.

F¹⁰⁰

Loop over 100 years.

F¹²

Loop over 12 months (0-indexed).

F⎇⊖κ⁺²⁸¬﹪ι⁴⁻³¹﹪﹪κ⁷¦²

Loop over 28 or 29 days (February) or 31 or 30 days for the other months as appropriate.

⊞υ⟦ι⊕κ⊕λ⟧

Push a Y, M, D triplet for each valid date.

⭆⪪⭆”)∨^⍘”§⪪θ²Iι⁶№υI⪪ι²

Use the compressed string 012201210 to rearrange the input from YYMMDD, MMDDYY or DDMMYY into YYMMDD, then split each arrangement into a triplet and check whether they are in the list.

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

C# (.NET 6+), 114 bytes (function) or 163 bytes (program)

var f=(string s)=>new[]{"yyMMdd","MMddyy","ddMMyy"}.Select(x=>DateTime.TryParseExact(s,x,null,0,out _)).ToList();

It takes a string as parameter and returns a list of 3 booleans corresponding to whether the string can be interpreted as {"yyMMdd", "MMddyy", "ddMMyy"}, in that order. I added ToList() because Select() returns an IEnumerable that is not computed.
The snippet makes use of the implicit using System; and using System.Linq; (since .NET 6).
Because a year on 2 digits is ambiguous, DateTime.Parse() (and similar date parsing functions) interpret it as 19xx from 50 to 99, otherwise as 20xx. Thus, for the "maybe" cases, it considers that it is for year 2000 and returns true.

Full program (163 bytes):

class P{static void Main(string[]a)=>Console.WriteLine(string.Join(',',new[]{"yyMMdd","MMddyy","ddMMyy"}.Select(x=>DateTime.TryParseExact(a[0],x,null,0,out _))));}

Try it here!
Note that due to TIO limitations (old version of C#/.NET), I added the required usings (but I didn't include them in the bytes count).
The program takes the first command line argument, and doesn't return the values but prints them to standard output instead.

\$\endgroup\$
5
  • \$\begingroup\$ This is my first contribution to a code golf challenge. I read the rules so hopefully I didn't do anything wrong, but feel free to tell me if anything can be improved. \$\endgroup\$
    – Arkane
    Commented Sep 4 at 12:05
  • 2
    \$\begingroup\$ Welcome, and nice first answer! "considers the 29th February of year 0 an invalid date" — 2000-02-09 is a valid date, while 1900-02-29 is not. Use a 20 prefix to get yyyy-MM-dd instead of yy-MM-dd to resolve the ambiguity? \$\endgroup\$ Commented Sep 4 at 13:05
  • \$\begingroup\$ Ooh, silly me. I thought that yy == 00 was interpreted as year 0 but it's interpreted as year 2000 much more logically. And there is no issue with TIO: I don't know how I messed up, but quotes aren't required for arguments. \$\endgroup\$
    – Arkane
    Commented Sep 4 at 14:29
  • 1
    \$\begingroup\$ Welcome and nice answer! You can put the using statements in the header section like this: TIO \$\endgroup\$
    – jdt
    Commented Sep 6 at 12:18
  • \$\begingroup\$ @jdt the consensus appears to be "characters used to import the library will likely be counted"? \$\endgroup\$ Commented Sep 6 at 13:23

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.