Short Date into English Long Date

14

1

Convert short date format into English long date in as few bytes as possible.

Input

Input will be in the form of a string with format, yyyy-mm-dd, with zero padding optional for all values. You can assume that this is syntactically correct, but not necessarily a valid date. Negative year values do not need to be supported.

Output

You must convert the date into the English long date format (e.g. 14th February 2017). Zero padding here is not allowed.

If the date is invalid (e.g. 2011-02-29), then this must be recognised in some way. Throwing an exception is allowed.

More examples can be seen below.

Test Cases

"1980-05-12" -> 12th May 1980
"2005-12-3"  -> 3rd December 2005
"150-4-21"   -> 21st April 150
"2011-2-29"  -> (error/invalid)
"1999-10-35" -> (error/invalid)

GarethPW

Posted 2017-06-28T23:19:48.683

Reputation: 1 119

5Is zero-padding allowed? aka 03rd instead of 3rd – Value Ink – 2017-06-29T02:00:09.307

@ValueInk If you read my previous comment, ignore it; I misunderstood the question. Zero padding in the output is not allowed. – GarethPW – 2017-06-29T08:27:21.920

Should we consider a year on more than 4 characters (eg 10987-01-01)? – mdahmoune – 2017-06-29T09:39:28.337

@mdahmoune You don't need to support this unless it's easier to do so. – GarethPW – 2017-06-29T10:25:26.817

What about 2016-2-29? – Olivier Grégoire – 2017-06-29T14:12:05.857

@OlivierGrégoire That date would be valid. – GarethPW – 2017-06-29T14:13:16.313

You say we don't have to support negative year values, does that imply we have to support starting at year 1? (There was no year 0). Or just back to year 150, per your earliest example? Any max year? Up to 9999 ok? – BradC – 2017-06-29T14:39:10.723

And we shouldn't 0-pad the year either? So output "21 April 3" for 0003-04-03? – BradC – 2017-06-29T14:46:41.373

@BradC I would say don't zero pad the year and have it support the range, 0 - 9999. – GarethPW – 2017-06-29T19:01:59.127

Actually there is no year zero, 31st Dec 1 BC is followed immediately by 1st Jan 1 AD. Also, there are several existing answers that pad the year already, so might be better to leave that unspecified. – BradC – 2017-06-29T19:23:28.293

@BradC TIL! In that case, it's optional. But I think it's better to enforce a lack of padding as I did include it in the examples and state it in the output description. – GarethPW – 2017-06-29T23:27:12.393

Does it have to work independently of the language setting of the OS? (My german Windows would use german names unless I force powershell to do otherwise) – whatever – 2017-06-30T07:12:38.873

@whatever No. As long as the code produces the desired result on English machines, you're fine. – GarethPW – 2017-06-30T09:54:57.940

Answers

5

PostgreSQL, 61 characters

prepare f(date)as select to_char($1,'fmDDth fmMonth fmYYYY');

Prepared statement, takes input as parameter.

Sample run:

Tuples only is on.
Output format is unaligned.
psql (9.6.3, server 9.4.8)
Type "help" for help.

psql=# prepare f(date)as select to_char($1,'fmDDth fmMonth fmYYYY');
PREPARE

psql=# execute f('1980-05-12');
12th May 1980

psql=# execute f('2005-12-3');
3rd December 2005

psql=# execute f('150-4-21');
21st April 150

psql=# execute f('2011-2-29');
ERROR:  date/time field value out of range: "2011-2-29"
LINE 1: execute f('2011-2-29');
                  ^
psql=# execute f('1999-10-35');
ERROR:  date/time field value out of range: "1999-10-35"
LINE 1: execute f('1999-10-35');
                  ^
HINT:  Perhaps you need a different "datestyle" setting.

manatwork

Posted 2017-06-28T23:19:48.683

Reputation: 17 865

Nice, wish MS-SQL recognized the "th" formatting style. – BradC – 2017-06-29T14:50:37.423

Considering you managed to complete the task properly with the fewest bytes, I suppose your solution is the winner! – GarethPW – 2017-06-29T23:26:16.943

Wow. Thanks. That's completely unexpected. Didn't noticed until now the absence of dedicated golfing languages. However accepting a solution after just one day is a bit soon. – manatwork – 2017-06-30T00:01:10.697

@manatwork I did wonder if it might be a little early. But I can change it if need be anyway. – GarethPW – 2017-06-30T00:34:58.303

7

Python 3.6, 137 129 bytes

from datetime import*
def f(k):g=[*map(int,k.split('-'))];n=g[2];return f"{date(*g):%-d{'tsnrhtdd'[n%5*(n^15>4>n%10)::4]} %B %Y}"

Try it online!

Uriel

Posted 2017-06-28T23:19:48.683

Reputation: 11 708

3%-d is the no-padding version of %d that you can use in your string formatting instead of {g[2]}. Also, 12 should become 12th, not 12nd (numbers from 10-19 don't follow the same rules as 1-9 and 20+) – Value Ink – 2017-06-29T02:15:26.290

1+1. didn't know about the f strings – Felipe Nardi Batista – 2017-06-29T12:44:47.247

@ValueInk thanks! also, fixed the ordinals issue – Uriel – 2017-06-29T13:02:47.960

5

Python 3.6, 154 bytes

from datetime import*
s=[*map(int,input().split('-'))]
b=s[2]
print(date(*s).strftime(f"%-d{'th'if(3<b<21)+(23<b<31)else('st','nd','rd')[b%10-1]} %B %Y"))

Try it online! (Set input stream and then run.)

Thanks to good suggestions from commenters below.

Luke Sawczak

Posted 2017-06-28T23:19:48.683

Reputation: 415

You can save a byte by removing the space between int(x) and for in your list comp. – Christian Dean – 2017-06-29T00:03:35.497

@ChristianDean Thanks, done! – Luke Sawczak – 2017-06-29T00:05:05.997

(('st','nd','rd')[b%10-1]if b<4 or 20<b<24 else'th') instead of your current conditional for -3 bytes. – Value Ink – 2017-06-29T02:19:16.843

@ValueInk Sadly, that will produce 31th. Another way I thought of breaking it down was 'th' if not 0<b%10<4 or 10<b<14 but it didn't save any bytes. – Luke Sawczak – 2017-06-29T04:08:49.510

In that case, abuse type coersion. (3<b<21)+(23<b<31) for -1 byte. Try it online!

– Value Ink – 2017-06-29T04:19:45.013

@ValueInk Thanks; 1 less byte. :) Also "upgraded" to Python 3.6 for the new string format. – Luke Sawczak – 2017-06-29T04:23:38.593

140 Bytes – officialaimm – 2017-06-29T05:00:52.100

@officialaimm Thanks, and hence even a little less with f"". I consider that structure different enough from mine that you can feel free to make it an answer if you want. – Luke Sawczak – 2017-06-29T20:10:59.660

5

JavaScript (ES6), 142 140 bytes

Outputs NaNth Invalid Date for invalid dates.

The code for ordinal numbers was adapted from this answer.

d=>`${s=(D=new Date(d)).getDate()+''}${[,'st','nd','rd'][s.match`1?.$`]||'th'} `+D.toLocaleDateString('en-GB',{month:'long',year:'numeric'})

f=
d=>`${s=(D=new Date(d)).getDate()+''}${[,'st','nd','rd'][s.match`1?.$`]||'th'} `+D.toLocaleDateString('en-GB',{month:'long',year:'numeric'})

console.log(
  f('2005-12-3'),
  f('1980-05-12'),
  f('2005-12-3'),
  f('150-4-21'),
  f('2011-2-29'),
  f('1999-10-35')
)

darrylyeo

Posted 2017-06-28T23:19:48.683

Reputation: 6 214

1Gives "1st March 2011" for 2011-2-29 in Chrome. That may be a tough fix. – Rick Hitchcock – 2017-06-29T03:35:13.067

5

Bash + coreutils, 115 78

  • 2 bytes saved thanks to @manatwork.
d="date -d$1 +%-e"
t=`$d`
f=thstndrd
$d"${f:t/10-1?t%10<4?t%10*2:0:0:2} %B %Y"

Try it online.

Digital Trauma

Posted 2017-06-28T23:19:48.683

Reputation: 64 644

1Seems that using string instead of array would help a bit: f=thstndrd; $d"${f:t/10-1?t%10<4?t%10*2:0:0:2} %B %Y". – manatwork – 2017-06-29T09:52:46.810

1

BTW, your revision 1 inspired a Bash tip. ;)

– manatwork – 2017-06-29T10:14:18.163

@manatwork yes - its funny - I considered trying that but didn't think it would help. Thanks for the nudge. – Digital Trauma – 2017-06-29T20:23:25.343

5

PHP, 87 bytes

<?=checkdate(($a=explode("-",$argn))[1],$a[2],$a[0])?date("jS F Y",strtotime($argn)):E;

Run as pipe with -F or test it online. Always prints a 4 digit year; fails for years > 9999.

no validity check, 35 bytes:

<?=date("jS F Y",strtotime($argn));

Titus

Posted 2017-06-28T23:19:48.683

Reputation: 13 814

4

C#, 147 143 bytes

s=>{var t=System.DateTime.Parse(s);int d=t.Day,o=d%10;return d+((d/10)%10==1?"th":o==1?"st":o==2?"nd":o==3?"rd":"th")+t.ToString(" MMMM yyy");}

Saved 4 bytes thanks to @The_Lone_Devil.

TheLethalCoder

Posted 2017-06-28T23:19:48.683

Reputation: 6 930

Could you not replace the second t.Day with d for a 4 byte saving? – The_Lone_Devil – 2017-06-29T12:19:19.287

@The_Lone_Devil Of course I could thanks, don't know how I missed that. – TheLethalCoder – 2017-06-29T12:34:55.400

4

mIRC version 7.49 (197 bytes)

//tokenize 45 2-2-2 | say $iif($3 isnum 1- $iif($2 = 2,$iif(4 // $1 && 25 \\ $1||16//$1,29,28),$iif($or($2,6) isin 615,30,31))&&$2 isnum1-12&&1//$1,$asctime($ctime($+($1,-,$2,-,$3)date), doo mmmm yyyy))

O S

Posted 2017-06-28T23:19:48.683

Reputation: 221

3

Ruby, 104 103 102+8 = 112 111 110 bytes

Uses -rdate -p program flags.

-1 byte from manatwork.

sub(/.*-(\d*)/){Date.parse($&).strftime"%-d#{d=eval$1;(d<4||d>20)&&"..stndrd"[d%10*2,2]||:th} %B %-Y"}

Try it online!

Value Ink

Posted 2017-06-28T23:19:48.683

Reputation: 10 608

Am I missing a reason why you not used ternary operator? d<4||d>20?"..stndrd"[d%10*2,2]:"th" – manatwork – 2017-06-29T11:56:56.717

@manatwork A number like 26 will try to access indices 12..13 in the lookup string, which is out of bounds, and thus returns nil. Thus using the ternary makes it d<4||d>20?"..stndrd"[d%10*2,2]||"th":"th", which is longer by 2 bytes. – Value Ink – 2017-06-29T12:08:37.217

Ah, I see. Well, then cool trick @ValueInk. – manatwork – 2017-06-29T12:14:02.427

Almost forgot, a tiny change: "th":th. – manatwork – 2017-06-29T12:30:37.527

2

C# (.NET Core), 167 197 bytes

s=>s.Equals(DateTime.MinValue)?"":s.Day+((s.Day%10==1&s.Day!=11)?"st":(s.Day%10==2&s.Day!=12)?"nd":(s.Day%10==3&s.Day!=13)?"rd":"th")+" "+s.ToString("MMMM")+" "+s.Year

Try it online!

+30 bytes for

using System;

DateTime.Parse()

kakkarot

Posted 2017-06-28T23:19:48.683

Reputation: 269

You can reverse the ternary check to get rid of the ! for -1 byte. And you can change the && to & for -3 bytes. Also, since you use s.Day 7 times it saves some bytes to create a temp value for it: s=>{var t=s.Day;return s.Equals(DateTime.MinValue)?"":t+((t%10==1&t!=11)?"st":(t%10==2&t!=12)?"nd":(t%10==3&t!=13)?"rd":"th")+" "+s.ToString("MMMM")+" "+s.Year;} – Kevin Cruijssen – 2017-06-29T08:34:31.317

@KevinCruijssen Thanks! – kakkarot – 2017-06-29T08:38:40.627

You also need to include using System; or fully qualify the DateTime object. – TheLethalCoder – 2017-06-29T09:11:50.980

Also DateTime.MinValue is 1-1-1 so I don't think you need that check. Which would also make my previous point irrelevant. – TheLethalCoder – 2017-06-29T09:22:24.943

Are you taking input as a string or DateTime because I belive you should be taking it as a string. – TheLethalCoder – 2017-06-29T09:23:27.777

1Taking input as a DateTime and parsing outside of the method isn't acceptable you should do all the work inside the method. Or add an extra method to split work off. – TheLethalCoder – 2017-06-29T09:47:54.303

2

Excel, 212 bytes

=ABS(RIGHT(A1,2))&IF(ABS(ABS(RIGHT(A1,2))-12)<2,"th",SWITCH(RIGHT(A1,1),"1","st","2","nd","3","rd","th"))&TEXT(MID(A1,FIND("-",A1)+1,FIND("-",REPLACE(A1,1,FIND("-",A1),""))-1)*30," mmmm ")&LEFT(A1,FIND("-",A1)-1)

If you break it into chunks at every ampersand, you get these pieces:

  • ABS() pulls the day number from the last two characters in the string. Since that may include a hyphen, ABS converts it to positive.
  • IF((ABS-12)<2,"th",SWITCH()) adds the ordinal. The -12 bit is because 11, 12, and 13 don't follow the normal rule and they all get th instead of st, nd, and rd. This corrects for that.
    • Note: The SWITCH function is only available in Excel 2016 and later. (Source) It's shorter than CHOOSE in this case because it can return a value if no match is found whereas CHOOSE requires numeric input and must have a corresponding return for each possible value.
  • TEXT(MID()*30," mmmm ") extracts the month name. MID() pulls out the month number as a string and multiplying by 30 returns a number. Excel sees that number as a date (1900-01-30, 1900-02-29, 1900-03-30, etc.) and TEXT() formats it as a month name with a space on both ends. 28 and 29 would have also works but 30 looks "nicer".
  • LEFT() extracts the year number.

Now, given all that, it would have been way easier if the test cases were all in a date range that Excel can handle as an actual date: 1900-01-01 to 9999-12-31. The big advantage is that the entire date is formatted at once. That solution is 133 bytes:

=TEXT(DATEVALUE(A1),"d""" & IF(ABS(ABS(RIGHT(A1,2))-12)<2,"th",SWITCH(RIGHT(A1,1),"1","st","2","nd","3","rd","th")) & """ mmmm yyyy")

The other big hurdle was having to include the ordinal. Without that, the solution is just 34 bytes:

=TEXT(DATEVALUE(A1),"d mmmm yyyy")

Engineer Toast

Posted 2017-06-28T23:19:48.683

Reputation: 5 769

1

Swift 3 : 298 bytes

let d=DateFormatter()
d.dateFormat="yyyy-MM-dd"
if let m=d.date(from:"1999-10-3"){let n=NumberFormatter()
n.numberStyle = .ordinal
let s=n.string(from:NSNumber(value:Calendar.current.component(.day, from:m)))
d.dateFormat="MMMM YYY"
print("\(s!) \(d.string(from:m))")}else{print("(error/invalid)")}

Try it online!

A. Pooja

Posted 2017-06-28T23:19:48.683

Reputation: 41

8Welcome to the site! Here the goal is to make the code as short as possible, I can see you have long variable names and lots of whitespace, you can shorten and remove these to save a lot of bytes. We also usually include a header at the top of the answer in the form of # Language, N bytes. It would be good if you could add one on too. – TheLethalCoder – 2017-06-29T12:38:20.067

1

T-SQL, 194 bytes

DECLARE @ DATE;SELECT @=PARSE('00'+i AS DATE)FROM t;PRINT DATENAME(d,@)+CASE WHEN DAY(@)IN(1,21,31)THEN'st'WHEN DAY(@)IN(2,22)THEN'nd'WHEN DAY(@)IN(3,23)THEN'rd'ELSE'th'END+FORMAT(@,' MMMM yyy')

Input is via text column i in pre-existing table t, per our IO standards.

Works for dates from Jan 1, 0001 to Dec 31, 9999. The year is output with at least 3 digits (per 150AD example).

Invalid dates will result in the following ugly error:

Error converting string value 'foo' into data type date using culture ''.

Different default language/culture settings might change this behavior. If you want a slightly more graceful error output (NULL), add 4 bytes by changing PARSE() to TRY_PARSE().

Format and explanation:

DECLARE @ DATE;
SELECT @=PARSE('00'+i AS DATE)FROM t;
PRINT DATENAME(d,@) + 
    CASE WHEN DAY(@) IN (1,21,31) THEN 'st'
         WHEN DAY(@) IN (2,22)    THEN 'nd'
         WHEN DAY(@) IN (3,23)    THEN 'rd'
         ELSE 'th' END
    + FORMAT(@, ' MMMM yyy')

The DATE data type introduced in SQL 2008 allows much wider range than DATETIME, from Jan 1, 0001 to Dec 31, 9999.

Some very early dates can be parsed wrong with my US locality settings ("01-02-03" becomes "Jan 2 2003"), so I pre-pended a couple extra zeros so it knows that first value is the year.

After that, its just a messy CASE statement to add the ordinal suffix to the day. Annoyingly, the SQL FORMAT command has no way to do that automatically.

BradC

Posted 2017-06-28T23:19:48.683

Reputation: 6 099

1

q/kdb+ 210 bytes, non-competing

Solution:

f:{a:"I"$"-"vs x;if[(12<a 1)|31<d:a 2;:0];" "sv(raze($)d,$[d in 1 21 31;`st;d in 2 22;`nd;d in 3 23;`rd;`th];$:[``January`February`March`April`May`June`July`August`September`October`November`December]a 1;($)a 0)};

Examples:

q)f "2017-08-03"
"3rd August 2017"
q)f "1980-05-12"
"12th May 1980"
q)f "2005-12-3"
"3rd December 2005"
q)f "150-4-21" 
"21st April 150"
q)f "2011-2-29"       / yes it's wrong :(
"29th February 2011"
q)f "1999-10-35"
0

Explanation:

This is a horrible challenge as there is no date formatting, so I have to create months from scratch (95 bytes) as well as generating the suffix.

Ungolfed solution is below, basically split the input string and then join back together after we've added the suffix and switched out the month.

f:{
   // split input on "-", cast to integers, save as variable a
   a:"I"$ "-" vs x;
   // if a[1] (month) > 12 or a[2] (day) > 31 return 0; note: save day in variable d for later
   if[(12<a 1) | 31<d:a 2;
     :0];
   // joins the list on " " (like " ".join(...) in python)
   " " sv (
           // the day with suffix
           raze string d,$[d in 1 21 31;`st;d in 2 22;`nd;d in 3 23;`rd;`th];
           // index into a of months, start with 0 as null, to mimic 1-indexing
           string[``January`February`March`April`May`June`July`August`September`October`November`December]a 1;
           // the year cast back to a string (removes any leading zeroes)
           string a 0)
  };

Notes:

Dates in q only go back to ~1709 so I don't have a trivial way of validating the date, hence this is a non-competing entry... The best I can do is check whether the day is > 31 or month is > 12 and return 0.

streetster

Posted 2017-06-28T23:19:48.683

Reputation: 3 635