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)
14 Answers 14
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}"
-
3\$\begingroup\$
%-dis the no-padding version of%dthat you can use in your string formatting instead of{g[2]}. Also,12should become12th, not12nd(numbers from 10-19 don't follow the same rules as 1-9 and 20+) \$\endgroup\$Value Ink– Value Ink2017年06月29日 02:15:26 +00:00Commented Jun 29, 2017 at 2:15
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.
-
\$\begingroup\$ Nice, wish MS-SQL recognized the "th" formatting style. \$\endgroup\$BradC– BradC2017年06月29日 14:50:37 +00:00Commented Jun 29, 2017 at 14:50
-
\$\begingroup\$ Considering you managed to complete the task properly with the fewest bytes, I suppose your solution is the winner! \$\endgroup\$Gareth– Gareth2017年06月29日 23:26:16 +00:00Commented Jun 29, 2017 at 23:26
-
\$\begingroup\$ 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. \$\endgroup\$manatwork– manatwork2017年06月30日 00:01:10 +00:00Commented Jun 30, 2017 at 0:01
-
\$\begingroup\$ @manatwork I did wonder if it might be a little early. But I can change it if need be anyway. \$\endgroup\$Gareth– Gareth2017年06月30日 00:34:58 +00:00Commented Jun 30, 2017 at 0:34
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')
)
-
1\$\begingroup\$ Gives "1st March 2011" for 2011-2-29 in Chrome. That may be a tough fix. \$\endgroup\$Rick Hitchcock– Rick Hitchcock2017年06月29日 03:35:13 +00:00Commented Jun 29, 2017 at 3:35
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.
-
\$\begingroup\$ You can save a byte by removing the space between
int(x)andforin your list comp. \$\endgroup\$Chris– Chris2017年06月29日 00:03:35 +00:00Commented Jun 29, 2017 at 0:03 -
\$\begingroup\$ @ChristianDean Thanks, done! \$\endgroup\$Luke Sawczak– Luke Sawczak2017年06月29日 00:05:05 +00:00Commented Jun 29, 2017 at 0:05
-
\$\begingroup\$
(('st','nd','rd')[b%10-1]if b<4 or 20<b<24 else'th')instead of your current conditional for -3 bytes. \$\endgroup\$Value Ink– Value Ink2017年06月29日 02:19:16 +00:00Commented Jun 29, 2017 at 2:19 -
\$\begingroup\$ @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. \$\endgroup\$Luke Sawczak– Luke Sawczak2017年06月29日 04:08:49 +00:00Commented Jun 29, 2017 at 4:08
-
\$\begingroup\$ In that case, abuse type coersion.
(3<b<21)+(23<b<31)for -1 byte. Try it online! \$\endgroup\$Value Ink– Value Ink2017年06月29日 04:19:45 +00:00Commented Jun 29, 2017 at 4:19
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));
Bash + coreutils, (削除) 115 (削除ここまで) 78
- 2 bytes saved thanks to @manatwork.
d="date -d1ドル +%-e"
t=`$d`
f=thstndrd
$d"${f:t/10-1?t%10<4?t%10*2:0:0:2} %B %Y"
-
1\$\begingroup\$ Seems 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". \$\endgroup\$manatwork– manatwork2017年06月29日 09:52:46 +00:00Commented Jun 29, 2017 at 9:52 -
1\$\begingroup\$ BTW, your revision 1 inspired a Bash tip. ;) \$\endgroup\$manatwork– manatwork2017年06月29日 10:14:18 +00:00Commented Jun 29, 2017 at 10:14
-
\$\begingroup\$ @manatwork yes - its funny - I considered trying that but didn't think it would help. Thanks for the nudge. \$\endgroup\$Digital Trauma– Digital Trauma2017年06月29日 20:23:25 +00:00Commented Jun 29, 2017 at 20:23
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.
-
\$\begingroup\$ Could you not replace the second
t.Daywithdfor a 4 byte saving? \$\endgroup\$Justinw– Justinw2017年06月29日 12:19:19 +00:00Commented Jun 29, 2017 at 12:19 -
\$\begingroup\$ @The_Lone_Devil Of course I could thanks, don't know how I missed that. \$\endgroup\$TheLethalCoder– TheLethalCoder2017年06月29日 12:34:55 +00:00Commented Jun 29, 2017 at 12:34
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))
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=eval1ドル;(d<4||d>20)&&"..stndrd"[d%10*2,2]||:th} %B %-Y"}
-
\$\begingroup\$ Am I missing a reason why you not used ternary operator?
d<4||d>20?"..stndrd"[d%10*2,2]:"th"\$\endgroup\$manatwork– manatwork2017年06月29日 11:56:56 +00:00Commented Jun 29, 2017 at 11:56 -
\$\begingroup\$ @manatwork A number like
26will try to access indices12..13in the lookup string, which is out of bounds, and thus returnsnil. Thus using the ternary makes itd<4||d>20?"..stndrd"[d%10*2,2]||"th":"th", which is longer by 2 bytes. \$\endgroup\$Value Ink– Value Ink2017年06月29日 12:08:37 +00:00Commented Jun 29, 2017 at 12:08 -
\$\begingroup\$ Ah, I see. Well, then cool trick @ValueInk. \$\endgroup\$manatwork– manatwork2017年06月29日 12:14:02 +00:00Commented Jun 29, 2017 at 12:14
-
\$\begingroup\$ Almost forgot, a tiny change:
"th"→:th. \$\endgroup\$manatwork– manatwork2017年06月29日 12:30:37 +00:00Commented Jun 29, 2017 at 12:30
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
+30 bytes for
using System;
DateTime.Parse()
-
\$\begingroup\$ 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 uses.Day7 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;}\$\endgroup\$Kevin Cruijssen– Kevin Cruijssen2017年06月29日 08:34:31 +00:00Commented Jun 29, 2017 at 8:34 -
\$\begingroup\$ @KevinCruijssen Thanks! \$\endgroup\$kakkarot– kakkarot2017年06月29日 08:38:40 +00:00Commented Jun 29, 2017 at 8:38
-
\$\begingroup\$ You also need to include
using System;or fully qualify theDateTimeobject. \$\endgroup\$TheLethalCoder– TheLethalCoder2017年06月29日 09:11:50 +00:00Commented Jun 29, 2017 at 9:11 -
\$\begingroup\$ Also
DateTime.MinValueis1-1-1so I don't think you need that check. Which would also make my previous point irrelevant. \$\endgroup\$TheLethalCoder– TheLethalCoder2017年06月29日 09:22:24 +00:00Commented Jun 29, 2017 at 9:22 -
1\$\begingroup\$ Taking input as a
DateTimeand 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. \$\endgroup\$TheLethalCoder– TheLethalCoder2017年06月29日 09:47:54 +00:00Commented Jun 29, 2017 at 9:47
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,ABSconverts it to positive.IF((ABS-12)<2,"th",SWITCH())adds the ordinal. The-12bit is because 11, 12, and 13 don't follow the normal rule and they all getthinstead ofst,nd, andrd. This corrects for that.- Note: The
SWITCHfunction is only available in Excel 2016 and later. (Source) It's shorter thanCHOOSEin this case because it can return a value if no match is found whereasCHOOSErequires numeric input and must have a corresponding return for each possible value.
- Note: The
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.) andTEXT()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")
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)")}
-
8\$\begingroup\$ Welcome 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. \$\endgroup\$TheLethalCoder– TheLethalCoder2017年06月29日 12:38:20 +00:00Commented Jun 29, 2017 at 12:38
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.
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.
03rdinstead of3rd\$\endgroup\$