26
4
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 most999
) 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)
Surely
-696443266.1.3.6.10.15.21.28
should be*V3035567339896938984978971
? – Neil – 2016-05-30T20:46:27.830@Neil Only the years get transformed for BCE. – Mego – 2016-05-30T22:03:45.193
Sorry, I was getting confused there, although the year is negative, the other parts are still positive of course. – Neil – 2016-05-30T22:11:44.230
11@Neil Until we invent negative months. Negember. – Mego – 2016-05-30T22:53:38.640
1@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. – Mego – 2018-01-04T22:44:22.717
@TaylorScott The explanation of the format describes it clearly: each character maps to the corresponding character on the opposite end of the alphabet (A -> Z, B -> Y, etc, and vice-versa), and each digit
d
maps to9-d
. – Mego – 2018-01-05T03:08:19.0402It 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? – James Holderness – 2018-01-08T03:34:43.443
1@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. – Mego – 2018-01-08T04:58:23.580
@Mego : isn’t a bounty of 2550 more appropiate? ;-# – agtoever – 2018-01-08T18:09:15.773
@Mego Is a single leading space allowed in the output? – dylnan – 2018-01-09T04:44:58.727
@dylnan Sure, since it doesn't affect the format any – Mego – 2018-01-09T04:46:15.633
@Mego What was the winning criterion for the bounty? Also wasn't it 150 points? – dylnan – 2018-01-12T15:15:51.230
@dylnan There was no winning criteria for the bounty. Because I wasn’t available to award it, it got awarded automatically, which I believe is why the bounty amount got cut in half. – Mego – 2018-01-12T21:08:02.710