26
\$\begingroup\$

RFC 2550 is a satirical proposal (published on April 1, 1999) for a space-efficient ASCII representation of timestamps that can support any date (even those prior to the beginning of the universe and those past the predicted end of the universe). The algorithm for computing a RFC 2550-compliant timestamp is as follows (note: all ranges include the start but exclude the end - 0 to 10,000 means all n where 0 <= n < 10000):

  • Year format
    • Years 0 to 10,000: a 4-digit decimal number, left-padded with zeroes.
    • Years 10,000 to 100,000: a 5-digit decimal number, prefixed with the character A.
    • Years 100,000 to 1030: the decimal number for the year, prefixed with the uppercase ASCII letter whose index in the English alphabet is equal to the number of digits in the decimal year, minus 5 (B for 6-digit years, C for 7-digit years, etc.).
    • Years 1030 to 1056: the same format as 10,000 to 1030, starting the letters over with A, and additionally prefixing a caret (^) to the string (so the year 1030 is represented by ^A1000000000000000000000000000000, and the year 1031 is represented by ^B10000000000000000000000000000000).
    • Years 1056 to 10732: the year is prefixed by two carets and two ASCII uppercase letters. The uppercase letters form a base-26 number representing the number of digits in the year, minus 57.
    • Years 10732 onwards: the same format for 1056 to 10732 is used, extending it by adding an additional caret and uppercase letter when necessary.
    • BCE years (prior to Year 0): compute the year string of the absolute value of the year. Then, replace all letters by their base-26 complement (A <-> Z, B <-> Y, etc.), replace all digits by their base-10 complement (0 <-> 9, 1 <-> 8, etc.), and replace carets with exclamation marks (!). If the year string is 4 digits or less (i.e. -1 to -10,000), prepend a forward slash (/). If the year string is not prefixed by a forward slash or an exclamation mark, prepend an asterisk (*).
  • Months, days, hours, minutes, and seconds: since these values are only ever 2 digits at the most, they are simply appended to the right of the year string, in decreasing order of significance, left-padded with zeroes if necessary to form 2-digit strings.
  • Additional precision: if additional precision (in the form of milliseconds, microseconds, nanoseconds, etc.) is needed, those values are left-padded with zeroes to 3 digits (because each value is 1/1000 of the previous value, and thus is at most 999) and appended to the end of the timestamp, in decreasing order of significance.

This format has the benefit of lexical sorting being equivalent to numeric sorting of the corresponding timestamp - if time A comes before time B, then the timestamp for A will come before the timestamp for B when lexical sorting is applied.

The challenge

Given an arbitrarily-long list of numeric values (corresponding to time values in decreasing order of significance, e.g. [year, month, day, hour, minute, second, millisecond]), output the corresponding RFC 2550 timestamp.

Rules

  • Solutions must work for any given input. The only limitations should be time and available memory.
  • Input may be taken in any reasonable, convenient format (such as a list of numerics, a list of strings, a string delimited by a single non-digit character, etc.).
  • The input will always contain at least one value (the year). Additional values are always in decreasing order of significance (e.g. the input will never contain a day value without a month value, or a second value followed by a month value).
  • Input will always be a valid time (e.g. there won't be any timestamps for February 30th).
  • Builtins which compute RFC 2550 timestamps are forbidden.

Examples

These examples use input as a single string, with the individual values separated by periods (.).

1000.12.31.13.45.16.8 -> 10001231134516008
12.1.5.1 -> 0012010501
45941 -> A45941
8675309.11.16 -> C86753091116
47883552573911529811831375872990.1.1.2.3.5.8.13 -> ^B478835525739115298118313758729900101020305008013
4052107100422150625478207675901330514555829957419806023121389455865117429470888094459661251.2.3.5.7.11 -> ^^BI40521071004221506254782076759013305145558299574198060231213894558651174294708880944596612510203050711
-696443266.1.3.6.10.15.21.28 -> *V3035567330103061015021028
-5342 -> /4657
-4458159579886412234725624633605648497202 -> !Q5541840420113587765274375366394351502797

Reference implementation

#!/usr/bin/env python

import string

# thanks to Leaky Nun for help with this
def base26(n):
    if n == 0:
        return ''
    digits = []
    while n:
        n -= 1
        n, digit = divmod(n, 26)
        digit += 1
        if digit < 0:
            n += 1
            digit -= 26
        digits.append(digit)
    return ''.join(string.ascii_uppercase[x-1] for x in digits[::-1])

year, *vals = input().split('.')

res = ""
negative = False

if year[0] == '-':
    negative = True
    year = year[1:]

if len(year) < 5:
    y = "{0:0>4}".format(year)
elif len(year) <= 30:
    y = "{0}{1}".format(string.ascii_uppercase[len(year)-5], year)
else:
    b26len = base26(len(year)-30)
    y = "{0}{1}{2}".format('^'*len(b26len), b26len, year)

if negative:
    y = y.translate(str.maketrans(string.ascii_uppercase+string.digits+'^', string.ascii_uppercase[::-1]+string.digits[::-1]+'!'))
    if len(year) == 4:
        y = '/' + y
    if y[0] not in ['/', '!']:
        y = '*' + y

res += y
for val in vals[:5]: #month, day, hour, minute, second
    res += '{0:0>2}'.format(val)

for val in vals[5:]: #fractional seconds
    res += '{0:0>3}'.format(val)

print(res)
\$\endgroup\$
  • \$\begingroup\$ Surely -696443266.1.3.6.10.15.21.28 should be *V3035567339896938984978971? \$\endgroup\$ – Neil May 30 '16 at 20:46
  • 11
    \$\begingroup\$ @Neil Until we invent negative months. Negember. \$\endgroup\$ – Mego May 30 '16 at 22:53
  • 1
    \$\begingroup\$ @TaylorScott Additional precision: if additional precision (in the form of milliseconds, microseconds, nanoseconds, etc.) is needed, those values are left-padded with zeroes to 3 digits. \$\endgroup\$ – Mego Jan 4 '18 at 22:44
  • 2
    \$\begingroup\$ It looks to me like the spec given in the question doesn't actually match RFC2550. As I understand it, once you get past three carets the number of letters should increase faster than the carets do, because it's derived from the Fibonacci series (4 carets means 5 letters, 5 carets means 8 letters, etc.) Is it safe to assume we should be ignoring that aspect of the RFC? \$\endgroup\$ – James Holderness Jan 8 '18 at 3:34
  • 1
    \$\begingroup\$ @JamesHolderness You're right, I messed up the spec. However, it's too late to correct it, as there are already answers that would be invalidated. \$\endgroup\$ – Mego Jan 8 '18 at 4:58
5
\$\begingroup\$

JavaScript (ES6), 325 bytes

f=
s=>s.split`.`.map((n,i)=>i?`00${n}`.slice(i>5?-3:-2):n<'0'?g(n.slice(1),'!','*','/').replace(/\w/g,c=>c>'9'?(45-parseInt(c,36)).toString(36):9-c):g(n),g=(n,c='^',d='',e='',l=n.length)=>l<5?e+`000${n}`.slice(-4):l<31?d+(l+5).toString(36)+n:h(l-30,c)+n,h=(n,c)=>n?c+h(--n/26|0,c)+(n%26+10).toString(36):'').join``.toUpperCase()
;
<input oninput=o.value=f(this.value);><input id=o>

Shockingly long.

\$\endgroup\$
  • \$\begingroup\$ Would you mind adding a Stack Snippet for easy testing? \$\endgroup\$ – Mego May 31 '16 at 6:25
  • \$\begingroup\$ @Mego Done. Also fixed some typos that crept in (I accidentally deleted some of the code when copying and pasting because the line wrapping confused me. Oops.) \$\endgroup\$ – Neil May 31 '16 at 7:53
3
\$\begingroup\$

Befunge, 418 384 bytes

It's hard to tell in advance how large a Befunge program is likely to end up, and when I started working on this I thought it might actually have some chance of competing. Turns out I was wrong.

~:59*-!:00p:2*1\-10p:9*68*+20p>0>#~$_v
68*-:0\`30p\>>:"P"%\"P"/9+p30g#v_1+:~>
0\`v`\0:\p04<<:+1g04-$<_\49+2*v>0>+#1:#\4#g\#0`#2_130p040p5-::01-`\49+2*-:
v:$_\50p\$:130g:1+30p:!^!:-1\*<>g*"A"++\49+2*/50g1-:
_$1+7g00g40g!**:!>_40g:!v!:\g8<^00*55*g01%*2+94:p05
|#9/"P"\%"P":<:_,#!>#:<$_1-00g^v3$\_\#`\0:>#g+
>10g*20g+,1+:^v\&\0+2`4:_@#`<0+<
/*v*86%+55:p00<_$$>:#,_$1+~0^
^!>+\55+/00g1-:^

Try it online!

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

Perl 5, 328 322 317 301 + 1 (-a) = 302 bytes

$_=shift@F;if(($l=y/-//c)<5){s/^/0 x(4-$l)/e}elsif($l<57){s/^/'^'x($l>30).chr 65+($l-5)%26/e}else{$l-=57;do{s/\^*\K/'^'.chr 65+$l%26/e}while$l=int$l/26;s/^\^\K\D-?\d/^A$&/}if(s/-//){s%^....$%/$&%;eval join'',reverse'!/',0..9,A..Z,"y/A-Z0-9^/";s%^[^!/]%*$&%}printf$_.'%02d'x(@F>5?5:@F).'%03d'x(@F-5),@F

Try it online!

Ungolfed

$_=shift@F; # Store the year in the default variable for easier regex

if(($l=y/-//c)<5){      # if the length of the year is less than 5
    s/^/0 x(4-$l)/e         # pad with leading zeros to 4 digits
}elsif($l<57){          # if the length is less than 57
    s/^/'^'x($l>30).chr 65+($l-5)%26/e  # put a carat at the front if there are more than 30 characters
                        # and map the length minus 5 to A-Z
}else{
    $l-=57;         # offset the length by 57
    do{         
        s/\^*\K/'^'.chr 65+$l%26/e # put a carat at the front and map the length to base 26 (A-Z)
    }while$l=int$l/26;  # until the length is down to 0
    s/^\^\K\D-?\d/^A$&/ # insert an extra '^A' to pad the result to at least 2 characters if there was only 1
}
if(s/-//){          # if the year is negative
    s%^....$%/$&%;          # put a '/' in front of a 4 digit year
    eval join'',reverse'!/',0..9,A..Z,"y/A-Z0-9^/"; # map A-Z,0-9, and ^ to Z-A,9-0, and ! respectively
    s%^[^!/]%*$&%           # add a * at the front if there are no other indicators
}
printf$_.           # output the year
'%02d'x(@F>5?5:@F).             # followed by the month, day, hour, and minutes, padded to 2 digits
'%03d'x(@F-5),@F                # followed by fractional seconds, padded to three digits
\$\endgroup\$
3
+75
\$\begingroup\$

Java 8, 653 640 637 623 bytes

s->{String r="",q="ABCDEFGHIJKLMNOP",z=q+"QRSTUVWXYZ",y="0123456789",x;int i=0,f=0,t,u;for(String p:s){if(p.charAt(0)<46){p=p.substring(1);f=1;}t=p.length();if(i++<1){r+=(t<5?"000".substring(t-1):t<32?(char)(t+60):t<58?"^"+(char)(t+34):"");if(t>57){for(r+="^^",u=675;u<t-57;u*=26)r+="^";x="";for(String c:Long.toString(t-57,26).toUpperCase().split(""))x+=z.charAt((y+q).indexOf(c));r+=x;}r+=p;if(f>0){x=t<5?"/":t<32?"*":r.replace("^","!").replaceAll("[^!]","");for(char c:r.toCharArray())x+=c>93?"":"ZYXWVUTSRQPONMLKJIHGFEDCBA9876543210".charAt((z+y).indexOf(c));r=x;}}else r+=i>6?t<2?"00"+p:t<3?0+p:p:t<2?0+p:p;}return r;}

Input as String-array and return-type as String.

Turned out to be quite long (as expected), but can definitely be golfed some more. I'm just glad it works after fiddling with it for quite a while..

Try it here.

Explanation:

  • for(String p:s){: Loop over the parts
    • if(p.charAt(0)<46){p=p.substring(1);f=1;}: Determine if it's negative, and if it is, remove the minus sign and set a flag to reduce bytes
    • t=p.length();: Get the amount of digits
    • if(i++<1){: If it's the first number (the year):
      • t<5?"000".substring(t-1): If it's 0-100,000 (exclusive): add leading zeroes if necessary
      • t<32?(char)(t+60): If it's 100,000-1030 (exclusive): Add a leading letter
      • t<58?"^"+(char)(t+34): If it's 1030-10732 (exclusive): Add a literal "^" + leading letter
      • if(t>57)for(r+="^^",u=675;u<t-57;u*=26)r+="^";: Add the appropriate amount of literal "^" + x="";for(String c:Long.toString(t-57,26).toUpperCase().split(""))x+=z.charAt((y+q).indexOf(c));r+=x;: leading letters (base-26 to alphabet conversion)
      • r+=p;: Add the year itself to the result-String
      • if(f>0){: If the year was negative:
        • x=t<5?"/":t<32?"*":r.replace("^","!").replaceAll("[^!]","");: Create a temp String x with the correct /, * or one or multiple !
        • for(char c c:r.toCharArray())x+=c>93?"":"ZYXWVUTSRQPONMLKJIHGFEDCBA9876543210".charAt((z+y).indexOf(c));: Do the conversion (A↔Z, B↔Y, 0↔9, 1↔8, etc.)
        • r=x;: And then set the result to this temp String x
    • else: If it's the month, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, or smaller:
      • i>6?t<2?"00"+p:t<3?0+p:p: If it's milliseconds or smaller: Add leading zeroes if necessary
      • :t<2?0+p:p;: Else (month, days, hours, minutes, seconds): Add single leading zero if necessary
  • return r: Return the result
\$\endgroup\$
  • \$\begingroup\$ Input may be taken in any reasonable, convenient format (such as a list of numerics, a list of strings, a string delimited by a single non-digit character, etc.). - you could take input as a list of numerics, and skip the costly splitting and conversion. \$\endgroup\$ – Mego Jan 4 '18 at 22:45
  • 1
    \$\begingroup\$ @Mego Unfortunately default numerics (long with 64 bits being the largest) are too small in Java for some of the inputs, so String is shorter than java.math.BigInteger. I did change it to a String-array though, so I don't need to do the split by dots, which saved some bytes, so thanks. \$\endgroup\$ – Kevin Cruijssen Jan 5 '18 at 7:48
2
\$\begingroup\$

Excel VBA, 500 486 485 470 Bytes

Anonymous VBE Immediate Window Function

Anonymous VBE immediate window function that takes input as year from [A1], month from [B1], days from [C1], hours from [D1], minutes from [E1], seconds from [F1] and an optional extra precision array from [G1:Z1], calculates the RFC2550 timestamp and outputs to the VBE immediate window. Makes use of the declared helper function below.

n=Left([A1],1)="-":y=Mid([A1],1-n):l=Len(y):o=IIf(l<5,Right("000"&y,4),IIf(l<31,"",String(Len(b(l-30)),94))&B(l-IIf(l<31,4,30))&y):For Each c In[B1:Z1]:j=j+1:p=p+IIf(c,Format(c,IIf(j>5,"000","00")),""):Next:If n Then For i=1To Len(o):c=Asc(Mid(o,i,1)):Mid$(o,i,1)=Chr(IIf(c<60,105,155)-c):Next:?IIf(l<5,"/",IIf(InStr(1,o,"="),"","*"))Replace(o,"=","!")p:Else?o;p

Helper Function

Declared helper function that takes an input number and returns that number in base-26 such that 1->A and 26->Z

Must be placed into a public module.

Function b(n)
While n
n=n-1
d=n Mod 26+1
n=Int(n/26)
d=d+26*(d<0):n=n-(d<0)
b=Chr(64+d)+b
Wend
End Function

Usage

Must be used in a clear module, or the module must be cleared before execution as the vars j, o and p are assumed to be in their default, uninitialized state at the beginning of execution of the code. For j, which is a Variant\Integer variable, this default value is 0 and for o and p, which are Variant\String variables, this default value is the empty string ("").

Input, an array of strings, is taken from 1:1 on the ActiveSheet and output is to the VBE immediate window.

Sample I/O

[A1:F1]=Split("4052107100422150625478207675901330514555829957419806023121389455865117429470888094459661251.2.3.5.7.11",".")
n=Left([A1],1)="-":y=Mid([A1],1-n):l=Len(y):o=IIf(l<5,Right("000"&y,4),IIf(l<31,"",String(Len(b(l-30)),94))&B(l-IIf(l<31,4,30))&y):For Each c In[B1:ZZ1]:j=j+1:p=p+IIf(c,Format(c,IIf(j>5,"000","00")),""):Next:If n Then For i=1To Len(o):c=Asc(Mid(o,i,1)):Mid$(o,i,1)=Chr(IIf(c<60,105,155)-c):Next:?IIf(l<5,"/",IIf(InStr(1,o,"="),"","*"))Replace(o,"=","!")p:Else?o;p
^^BI40521071004221506254782076759013305145558299574198060231213894558651174294708880944596612510203050711021028

Cells.Clear:j=0:o="":p="" '' clear the worksheet and vars
[A1:H1]=Array("-696443266","1","3","6","10","15","21","28")
n=Left([A1],1)="-":y=Mid([A1],1-n):l=Len(y):o=IIf(l<5,Right("000"&y,4),IIf(l<31,"",String(Len(b(l-30)),94))&B(l-IIf(l<31,4,30))&y):For Each c In[B1:ZZ1]:j=j+1:p=p+IIf(c,Format(c,IIf(j>5,"000","00")),""):Next:If n Then For i=1To Len(o):c=Asc(Mid(o,i,1)):Mid$(o,i,1)=Chr(IIf(c<60,105,155)-c):Next:?IIf(l<5,"/",IIf(InStr(1,o,"="),"","*"))Replace(o,"=","!")p:Else?o;p
*V3035567330103061015021028

Cells.Clear:j=0:o="":p="" '' clear the worksheet and vars
[A1]="45941"
n=Left([A1],1)="-":y=Mid([A1],1-n):l=Len(y):o=IIf(l<5,Right("000"&y,4),IIf(l<31,"",String(Len(b(l-30)),94))&B(l-IIf(l<31,4,30))&y):For Each c In[B1:ZZ1]:j=j+1:p=p+IIf(c,Format(c,IIf(j>5,"000","00")),""):Next:If n Then For i=1To Len(o):c=Asc(Mid(o,i,1)):Mid$(o,i,1)=Chr(IIf(c<60,105,155)-c):Next:?IIf(l<5,"/",IIf(InStr(1,o,"="),"","*"))Replace(o,"=","!")p:Else?o;p
A45941

Cells.Clear:j=0:o="":p="" '' clear the worksheet and vars
[A1:F1]=Split("4052107100422150625478207675901330514555829957419806023121389455865117429470888094459661251.2.3.5.7.11",".")
n=Left([A1],1)="-":y=Mid([A1],1-n):l=Len(y):o=IIf(l<5,Right("000"&y,4),IIf(l<31,"",String(Len(b(l-30)),94))&B(l-IIf(l<31,4,30))&y):For Each c In[B1:ZZ1]:j=j+1:p=p+IIf(c,Format(c,IIf(j>5,"000","00")),""):Next:If n Then For i=1To Len(o):c=Asc(Mid(o,i,1)):Mid$(o,i,1)=Chr(IIf(c<60,105,155)-c):Next:?IIf(l<5,"/",IIf(InStr(1,o,"="),"","*"))Replace(o,"=","!")p:Else?o;p
^^BI40521071004221506254782076759013305145558299574198060231213894558651174294708880944596612510203050711

Subroutine Version

Declared Subroutine that takes input as year from [A1], month from [B1], days from [C1], hours from [D1], minutes from [E1], seconds from [F1] and an optional extra precision array from [G1:Z1], calculates the RFC2550 timestamp and outputs to the VBE immediate window.

Sub R(x)
a=x(0)
n=Left(a,1)="-"'<- that `"` is only there to make sure highlighting is correct
y=Mid(a,1-n)
l=Len(y)
o=IIf(l<5,Right("000"&y,4),IIf(l<31,"",String(Len(b(l-30)),94))&B(l-IIf(l<31,4,30))&y)
If n Then For i=1To Len(o):c=Asc(Mid(o,i,1)):Mid$(o,i,1)=Chr(IIf(c<60,105,155)-c):Next:o=IIf(l<5,"/",IIf(InStr(1,o,"="),"","*"))&Replace(o,"=","!")
For j=1To UBound(x)
o=o+IIf(x(j),Format(x(j),IIf(j>5,"000","00")),"")
Next
[A2]=o
End Sub
Function b(n)
While n
n=n-1
d=n Mod 26+1
n=Int(n/26)
d=d+26*(d<0):n=n-(d<0)
b=Chr(64+d)+b
Wend
End Function

Usage

Input to the range [A1:ZZ1] may be done either manually, by typing into the cells, from leftmost to rightmost, as needed or by assigning from the VBE immediate window.

Of note, due to Excel auto-converting numbers into scientific notation, any numbers which have a base-10 length equal to or greater than 12 digits must be inserted into the cell explicitly as text either by setting the cell to be a text cell or by prepending the literal ' to the beginning of the cell's value

Sample I/O

r Split("4052107100422150625478207675901330514555829957419806023121389455865117429470888094459661251.2.3.5.7.11",".")
?[A2]  '' <- print output to VBE console
^^BI40521071004221506254782076759013305145558299574198060231213894558651174294708880944596612510203050711 ''  <- Output

r Array("47883552573911529811831375872990","1","1","2","3","5","8","13")
?[A2]
^B478835525739115298118313758729900101020305008013

r Array("-696443266","1","3","6","10","15","21","28")
?[A2]
*V3035567330103061015021028

r Array("45941")
?[A2]
A45941

Ungolfed And Explained

''  Returns RFC2550 timestamp corresponding to passed vars
Public Function RFC2550(ByVal pYear As String, ParamArray Extra() As Variant) As String

    ''  Declare Vars
    Dim Negative As Boolean, _
        leny As Long, _
        i As Long, _
        c As Byte, _
        s As Variant, _
        out As String

    ''  Check if year is negative and store the absolute value of the year
    Let Negative = Left(pYear, 1) = "-"
    Let pYear = Mid(pYear, 1 - Negative)

    ''  Take the length of the year
    Let leny = Len(pYear)
    If leny < 5 Then
        ''  If the length is less than 5, pad the year left to 4 characters 
        ''  using zeros
        Let out = Format("0000", pYear)
    Else
        ''  If the length of the year is greater than 30, then set out to be 
        ''  as string comprised of length-30 instances of `^`
        Let out = IIf(leny < 31, "", String(Len(Base26(leny - 30)), 94)) 
        ''  append the Base26 representation of the length of the year,
        ''  minus 30, if the length is greater than 30
        Let out = out & Base26(leny - IIf(leny < 31, 4, 30)) 
        ''  append the year to out
        Let out = out & pYear
    End If


    If Negative Then
        ''  iterate across out
        For i = 1 To Len(out)
            ''  store the char code for the current char
            Let c = Asc(Mid(out, i, 1))
            ''  swap letter/number with its inverse (0->9,A->Z)
            Mid$(out, i, 1) = Chr(IIf(c < 60, 105, 155) - c)
        Next i

        ''  replace `=` (the inverse of `^`) with `!`
        Let out = Replace(out, "=", "!")
        ''  Prepend either `/`, `*`, or nothing depending on length and 
        ''  start of out
        Let out = IIf(leny < 5, "/", IIf(InStr(1, out, "!"), "", "*")) & out
    End If

    Let i = 1
    For Each s In Extra
        ''  append all of the extra precision data - padding to 2 chars for 
        ''  the first 5 elements in the array (month, day, hour, minute and 
        ''  second) and to 3 chars for all following elements (milli, micro, 
        ''  nano, pico, femto, atto, zepto, yocto - seconds) with the char 0
        Let out = out & IIf(s, Format(s, IIf(i > 5, "000", "00")), "")
        Let i = i + 1
    Next

    ''  return out
    Let RFC2550 = out 

End Function


''  returns non-standard base26 version of input number 
''  1->A, 2->B,... 26->Z
Function Base26(ByVal n As Long) As String

    ''  declare vars
    Dim out As String, _
        digit As Integer

    ''  init out, digit
    Let out = ""
    Let digit = 0

    ''  iterate through n 
    While n
        ''  Decrement, hold the value of the digit
        Let n = n - 1
        Let digit = n Mod 26 + 1

        ''  divide by 26
        Let n = Int(n / 26)

        ''  correct for negative numbers
        If digit < 0 Then Let n = n + 1: Let digit = digit - 26

        ''  prepend char corresponding to the digit to out
        Let out = Chr(64 + digit) & out
    Wend

    ''  return out
    Let Base26 = out
End Function
\$\endgroup\$
2
\$\begingroup\$

Jelly, 165 126 bytes

ḣ5ṫ€3
ØD,“^ /*!”,ØA
_µ‘l26Ċṗ@€ØAẎị@
Lµç30;€”^UZFµç4⁶;µ®L>30¤?µḟ®L>4¤?;®AṾ€¤µL=4”/x2£FiЀị€2£UF¤µ®S<0¤¡
4R¬+DU$UµḢ©Ç;Ñ;ṫ6ṫ€2$$F

Try it online!

Line 4 does the year formatting with help from lines 2 and 3. The first and last line deal with zero padding the elements of the input to their proper lengths then concatenating them with the formatted year.

  • _µ‘l26Ċṗ@€ØAẎị@ finds the base 26 prefix. It takes the cartesian power of the alphabet (ØA) for each number between 1 and ceil(log26(floor(log10(year))-n+1)) (where n is either 30 or 4) then gets indexes into this list with floor(log10(year))-n (ị@).
  • ç30;€”^UZF formats years >= 1030 (®L>30¤?)
  • ç4⁶; formats years < 1030. (Edit: Saved a byte by using ⁶; instead of ;@⁶)
  • 1RḊ gives an empty prefix for years < 105 (®L>4¤?). It takes the list of digits then filters out every element in itself. Just using this to yield [] because doesn't work here. This just evaluates to []. and [] don't work here and I couldn't find another 2 bytes that return an empty list.
  • ;®AṾ€¤ appends the year to the prefix then flattens it.
  • L=4”/x prefixes a / if the length of the year is 4 in the do statement of ®S<0¤¡.
  • 2£FiЀ¹ị€2£UF¤ takes the complements of A .. Z, 0 .. 9 and ^ /*! if the year is negative (®S<0¤¡). refers to the second link, ØD,“^ *!”,ØA which is the list [['0' .. '9'], ['^',' ','/','*','!'], ['A' .. 'Z']]. With a formatted year like ^C125... this link finds the index of each character in the flattened version of then uses those indices to construct a new string from the flattened version of where each sublist is reversed, i.e. ['9' .. '0','!','*','/',' ','^','Z' .. 'A'], yielding !X874.... / maps to itself because it is prefixed before everything has its complement taken.
  • L=4a®S<0x@”/; adds a / to the beginning of negative years in [-9999 .. -0001]. My guess is this can be shortened. I ended up including this in the previous do statement (¡) and saved 7 bytes because then I didn't need to test for negative years twice.

There are a lot of uses of ¡ in line 4 and I think they could be compressed by using ? instead but I'm not sure how to get those to work. I got ? to work and saved a few bytes.

James Holderness pointed out that my first submission didn't handle years with 30 digits correct. It turned out the bug was for any year that needed a Z in the base 26 prefix. It turns out I couldn't use because when you convert 26 to base 26 it gives you [1,0] instead of 26 (duh). Instead I used ordered pairs with replacement. I don't think there is an atom for that but if there is I can save a few bytes. Fixing this ended up costing me ~40 bytes. Definitely my longest Jelly program yet. Edit: Found a shorter way to do the cartesian product. I realized that I wasn't sure that the last one worked for prefixes with more than two letters anyway but the new way does work.

Sorry for the many times I've edited this post, I just keep discovering ways of shortening it.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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