g | x | w | all
Bytes Lang Time Link
117Swift 6250603T183914ZmacOSist
nanawk250604T142135Zjena
046Perl 5 p250602T183314ZXcali
067APL+WIN250603T131548ZGraham
033Charcoal250603T074050ZNeil
050JavaScript ES6250602T184448ZArnauld
024Jelly250602T233952ZJonathan
066Google Sheets250602T152524Zdoubleun
061JavaScript Node.js250602T055534Zl4m2
075JavaScript Node.js250602T095444ZTed
056Retina 0.8.2250602T085557ZNeil
02605AB1E250602T075831ZKevin Cr

Swift 6, 122 117 bytes

{s in(s+"").matches(of:/(.+):(..)/).map{Int($0.1)!>23||Int($0.2)!>59 ?0:(1...12~=Int($0.1)! ?1:0)+($0.0==s ?1:0)}[0]}

Try it on SwiftFiddle!

Explanation

{ s in 
  (s + "")
    .matches(of: /(.+):(..)/)
    .map {
      Int($0.1)! < 24 && Int($0.2)! < 60
        ? (1...12 ~= Int($0.1)! ? 1 : 0)
          + ($0.0 == s ? 1 : 0)
        : 0
    }[0]
}

I'll go through this line-by-line.

{ s in

Start a closure. s is a String containing the clock.

  (s + "")
    .matches(of: /(.+):(..)/)

Extract data from the clock using a regex. (There will always be exactly 1 match.)

Code later down can use dynamic member lookup on the match result to access the captures.

s + "" serves as a hint to the type checker that s is of type String.

    .map {

Map over the array of matches (which will only have one element). Doing it like this lets us avoid the overhead of using let to bind the match to a variable (which would then necessitate an explicit return).

      Int($0.1)! < 24 && Int($0.2)! < 60

Check that the hour part of the clock ($0.1) is less than 24 and that the minute part ($0.2) is less than 60.

$0 here is of type (Substring, Substring, Substring). $0.0 is the entire match result; $0.1 is the hour; and $0.2 is the minute.

        ? (1...12 ~= Int($0.1)! ? 1 : 0)
          + ($0.0 == s ? 1 : 0)

If the hour and minute are in range, then we do two things:

We convert the results of these checks (which are of type Bool) to Int (1 corresponds to true, 0 to false). We then add these together and return it as the final score.

This bit was adapted from @Arnauld's JavaScript answer; I've reproduced his scoring table here for reference.

1...12 ~= hour matched == original Score Examples
false (0) false (0) 0 14:00 PM, 00:00 PM
false (0) true (1) 1 14:00, 00:00
true (1) false (0) 1 07:00 PM
true (1) true (1) 2 07:00
        : 0

If the hour and/or minute are not in range, return 0 — this indicates an invalid clock.

    }[0]
}

Finally, get the first (only) element from the mapping and implicitly return it.

awk, first stab at golf (162 135 120 bytes)

Even newer version abusing ternary operator and without an array:

# new attempt = 120 bytes
BEGIN{FS="[: ]"}1{o=($1>23||$2>59||(NF>2&&($1>12||$1<1)))?0:(NF>2||(NF<3&&(($1>12&&$1<24)||($1<1&&$2<1))))?1:2;print o}
# older attempt = 135 bytes
{split($1,t,":");o=(t[1]>23||t[2]>59||(NF>1&&(t[1]>12||t[1]<1)))?0:(NF>1||(NF<2&&((t[1]>12&&t[1]<24)||(t[1]<1&&t[2]<1))))?1:2;print o}

It's still awfully long :( I'm sure I could make it shorter by stealing the trick from top answer, but I can't make it work (getting fatal division by zero).

Original answer:

{split($1,t,":");if(t[1]>23||t[2]>59||(NF>1&&(t[1]>12||t[1]<1)))o=0;else if(NF>1||(NF<2&&((t[1]>12&&t[1]<24)||(t[1]<1&&t[2]<1))))o=1;else if(t[1]<12)o=2;print o}

Perl 5 -p, 46 bytes

$_=/:[0-5]\d/*(($`<13)-(!-$`)+($`<24)*(1-/M/))

Try it online!

APL+WIN, 67 bytes

Prompts for time.

d←~+/'M'=t←⎕⋄h←⍎¯2↓t←t~'APM: '⋄m←⍎¯2↑t⋄(m<60)×(h<24)×((1≤h)^h≤12)+d

Try it online! Thanks to Dyalog Classic

Charcoal, 33 bytes

≔I§⪪θ:⁰ηI∧∧‹⁻Σθη⁶⁰‹η²⁴⁺¬№θM¬÷⊖η¹²

Try it online! Link is to verbose version of code. Due to the way Charcoal's input processing works it is necessary to terminate the input with a newline. Explanation: Turns out to use the same approach as @Arnauld.

≔I§⪪θ:⁰η

Extract the hours from the time.

I∧∧‹⁻Σθη⁶⁰‹η²⁴⁺¬№θM¬÷⊖η¹²

Extract the sum of hours and minutes from the time and subtract the hours, checking that the minutes is less than 60, also that the hours is less than 24, then adding 1 if the input does not contain an M and 1 if the input time is within the 12-hour range.

JavaScript (ES6), 50 bytes

s=>([h,m,p]=s.split(/\W/),h<24&m<60)*((12%h<6)+!p)

Try it online!

Commented

s =>             // s = input string
(                //
  [              // get these variables ...
    h,           //   h = hours in "H" or "HH" format
    m,           //   m = minutes in "MM" format
    p            //   p = "AM", "PM" or undefined
  ] =            //
  s.split(/\W/), // ... by splitting s on ":" and " "
  h < 24 &       // make sure that h is less than 24
  m < 60         // and m is less than 60
)                // this gives 1 for "valid so far", 0 for invalid
* (              // multiply by the "score", i.e. the sum of:
  (12 % h < 6)   //   1 if 1 ≤ h ≤ 12, 0 otherwise
  + !p           //   1 if p is undefined, 0 otherwise
)                // (see the table below)

Scoring table

12-hour compatible (12 % h < 6) No suffix (!p) Score Examples
0 0 0 14:00 PM, 00:00 PM
0 1 1 14:00, 00:00
1 0 1 07:00 PM
1 1 2 07:00

Jelly, 24 bytes

ṭḲµḢṣ”:V36%€24¤p60Ḷ¤¤ċcL

A monadic Link that accepts a list of characters matching \d{1,2}:\d{2}( [AP]M)? and yields the count.

Try it online!

How?

A clock will be right \$\binom{N_{24}+N_{12}}{K}\$ times, where:

Note that \$N_{12}\$ can only ever be \$1\$ if \$N_{24}\$ is \$1\$, so all cases are:

N24 + N12 K (N24 + N12)-choose-K
1 + 1 2 1
1 + 1 1 2
1 + 0 2 0
1 + 0 1 1
0 + 0 2 0
0 + 0 1 0
Code breakdown
ṭḲµḢṣ”:V36%€24¤p60Ḷ¤¤ċcL - Link: list of characters, T
 Ḳ                       - split T at space characters
                            e.g.  "14:39" -> ["14:39"]
                            or "10:50 AM" -> ["10:50", "AM"]
ṭ                        - append a copy of T -> Parts = [Hr:Min, OptionalAM/PM, T]
  µ                      - start a new monadic chain f(Parts)...
   Ḣ                     - head: remove Hr:Min from Parts and yield -> Hr:Min
    ṣ”:                  - split at colons -> [Hr, Min]
       V                 - evaluate as Jelly code -> [int H, int M]
                     ċ   - count occurrences in... -> N
                    ¤    - ...nilad followed by link(s) as a nilad:
              ¤          -   nilad followed by link(s) as a nilad:
        36               -     thirty-six
          %€24           -     modulo [1..36] with 24 -> Hours = [1,2,...,23,0,1,...,12]
                   ¤     -   nilad followed by link(s) as a nilad:
                60       -     sixty
                  Ḷ      -     lowered range -> Minutes = [0,1,...,59]
               p         -   {Hours} Cartesian product {Minutes}
                       L - length of {(remaining) Parts} -> K = 2 if AM/PM else 1
                      c  - {N} choose {K}

Google Sheets, 66 bytes

=ifs(istext(A1),0,1<A1,0,0=A1,1,"M"=right(A1),1,12<hour(A1),1,1,2)

Put the value in cell A1 and the formula in B1. The formula assumes the United States locale and relies on automatic coercion that comes standard with spreadsheets. For some insight, see Working with date and time values in Google Sheets.

screenshot

JavaScript (Node.js), 61 bytes

-1 byte Ted by non-resurcive

x=>(x=1/x[2]?0+x:x)[3]<6?1+!x[6]>>((h=x[0]+x[1])/12^h%12<1):0

Try it online!

JavaScript (Node.js), 59 bytes from Ted's

x=>+new Date("0 "+x)?x[v=parseInt(x),6]?v&&1:v<13&v>0?2:1:0

Try it online!

JavaScript (Node.js), 89 75 bytes

Thanks to @Neil for optimisations :D

(x,v=parseInt(x))=>isNaN(new Date("0 "+x))?0:/M/.test(x)?v&&1:v<13&&v>0?2:1

Try it online!

Not as good as @l4m2 (also I borrowed your footer code, thanks!) but this a different approach which I think is decent.

In essence: v is the first 1 or 2 digit number in the clock

Retina 0.8.2, 72 56 bytes

A`:[6-9]
\d+
$*
^1{1,12}:|^1{0,23}:1*$|$(?<=^1{1,12}:1*)

Try it online! Link includes test cases. Explanation:

A`:[6-9]

Exclude inputs with minutes over 59.

\d+
$*

Convert the values to unary.

^1{1,12}:|

A time is valid if its hours is 1-12, or...

^1{0,23}:1*$|

... if its hours is 0-23 and has no AM/PM.

$(?<=^1{1,12}:1*)

Count a second match for a time whose hours is 1-12 but has no AM/PM.

05AB1E, 26 bytes

':¡`þ60‹s©12LsåIθdi>®24‹}P

Try it online or verify all test cases.

Explanation:

In pseudo-code, I check three things:

':¡           '# Split the (implicit) input-string by ":"
   `           # Pop and push both parts separated to the stack
    þ          # Only keep the digits (the minutes) of the top part
     60‹       # Check that these minutes are <60
     s         # Swap so the hours are at the top
      ©        # Store this in variable `®` (without popping)
      12L      # Push a list in the range [1,12]
         så    # Check if the hours are in this list
     I         # Push the input-string again
      θ        # Pop and keep its last character
       di      # If it's a digit (aka, it's not "M")
         >     #  Increase the hours_in_[1,12] check by 1
         ®     #  Push the hours from variable `®` again
          24‹  #  Check whether it's <24
        }      # Close the if-statement
         P     # Take the product of all two or three values on the stack
               # (after which the result is output implicitly)