Difference between revisions of "Talk:1190: Time"

Explain xkcd: It's 'cause you're dumb.
Jump to: navigation, search
(Create an animation on this page)
Line 623: Line 623:
  
 
— [http://jan.moesen.nu/ Jan!] [[Special:Contributions/94.23.195.79|94.23.195.79]] 09:22, 27 March 2013 (UTC)
 
— [http://jan.moesen.nu/ Jan!] [[Special:Contributions/94.23.195.79|94.23.195.79]] 09:22, 27 March 2013 (UTC)
 +
 +
 +
 +
The one for the prior half hour (5AM - 5:30AM EST, 27 March 2013) is located at http://imgs.xkcd.com/comics/time/5450bd39ee84a394467fabcaf92f1a5711c2a4eca24c8bd8a8cec829496e3dd7.png
 +
[[Special:Contributions/141.161.133.106|141.161.133.106]] 09:26, 27 March 2013 (UTC)

Revision as of 09:26, 27 March 2013

Pretty sure we're just getting trolled with this one 99.108.190.136 04:48, 25 March 2013 (UTC)

Can't tell if this is emo xkcd or trolling xkcd. Alpha (talk) 04:53, 25 March 2013 (UTC)


Something seems a little fishy because the image url is different than normal. Bugefun (talk) 04:55, 25 March 2013 (UTC)

Maybe the comic slowly changes throughout the day. Alpha (talk) 04:56, 25 March 2013 (UTC)
Oh god, it does. Alpha (talk) 04:57, 25 March 2013 (UTC)
When uploading different versions of the image, use the naming convention time[iterationNumber].png. We'll compile all the images into one and display them as per Traffic Lights. Davidy²²[talk] 05:05, 25 March 2013 (UTC)
Alright, so the comic appears to be switching between two states here: between this and this. If nothing new happens, I'll get to clipping the comics together. Davidy²²[talk] 05:28, 25 March 2013 (UTC)
Whoop, nope, this just came up. Is there more to come? Davidy²²[talk] 05:34, 25 March 2013 (UTC)
Alright, so a new one is posted every half-hour. Whoopee. Davidy²²[talk] 06:06, 25 March 2013 (UTC)
And there's a new one! Megan leaning back and looking up...
Well, the image changed, who has the time to make a script to catch the new images and compile them into a gif? https://dl.dropbox.com/u/932170/time.png Statharas.903 (talk) 07:14, 25 March 2013 (UTC)

72.21.198.66 05:11, 25 March 2013 (UTC)It could be a reference to the old proverb " time and tide wait for none" Cueball and the girl could be waiting for the tide in the beach! (Just a guess)72.21.198.66 05:11, 25 March 2013 (UTC)

The picture does chance with time. The URL includes a changing timestamp that I can't decipher. Compare these two URLS (which have slightly different images: http://imgs.xkcd.com/comics/time/8eb156cce408df8bb83528382d6a2aa2ce6c74f3c573fd12b058cd1c56420672.png http://imgs.xkcd.com/comics/time/1e349a579b5f9b5ed487ddf7e88244b70330941ddedac9c6abf6ed2e3f589b97.png

Perhaps there is a way to hack the URL to view future images. 199.30.248.121 05:29, 25 March 2013 (UTC)

I would also like to add that knowing randall, these are not the only images. For all we know, the image will still be changing in 5 years while a tree grows in front of them. My point is: Are the URLs hackable, or did he encrypt them? 199.30.248.121 05:33, 25 March 2013 (UTC)

Likely there is a way to hack the URLs; they look like some sort of hash, probably a hashed timestamp. Of course, he could easily have added some salt to the hash, making it significantly *harder* to hack. But they're strings of a specific length, so it should be pretty easy to bruteforce it, fetch all the images, and then (maybe) reverse-engineer the sequence. *That* all depends on how many of them there are. 76.90.249.178 05:44, 25 March 2013 (UTC)

Good god, do you see how many digits are *in* that hash? The sun'll have burned out by the time we've tested every possible combination of digits. Davidy²²[talk] 05:47, 25 March 2013 (UTC)

It seems that the image is updated every 1/2 hour. 152.23.97.150 06:17, 25 March 2013 (UTC)

Given that the images switch back and forth between other images already seen, and that the comic should be viewable in the future, it seems unlikely that it's any thing like a simple sha256 of part of the timestamp. I think it's more likely a function of half-hours and minutes (assuming we continue to get a new possible image every half-hour). 99.153.248.206 06:59, 25 March 2013 (UTC)
The images do cycle, yes. But for some reason I have never seen the img where Megan is looking behind her. Also wouldn't it be difficult to show a sequential story (like the rising tide) if the previous images keep cycling ?

Hash appears to be SHA-256. I tried some obvious hashes ("1", "11901", "1190_1", "1190.1") to no avail. Maybe this is HMAC-SHA256? Also, I would suggest trying Unix timestamps. 131.156.236.149 06:19, 25 March 2013 (UTC)

I've been trying to make educated guesses as to what's being hashed here: http://www.xorbin.com/tools/sha256-hash-calculator ... he could also be using hash(hash2(value)) which would be virtually impossible to crack. 99.153.248.206 06:59, 25 March 2013 (UTC)

It's entirely possible that the "hash" is actually randomly generated. Just a thought. 129.21.119.153 07:03, 25 March 2013 (UTC)

Alright, this is probably not going to work, but I'm trying to exploit Randall's awesomeness here. Maybe he decided to take the time-stamps from the user? I don't know if that's even possible... That would then allow people in different time zones to obtain different images simultaneously. (What's the corollary of Godwin's law for a bunch of math-and-science nerds and relativity? Is there one?) Clicking the img src url on the comic's html page, give me this: http://imgs.xkcd.com/comics/time/752687b61523144c61736cd89f8c153dc41e19128f72d78d44947ff800f057fa.png : Never mind.. apparently others see the same image too.

Could he be doing this live? Monitoring the discussion on the net? Collaborative, crowdsourced comic-ing? Reminds me of those you-decide-what-the-character-does-next-and-flip-to-appropriate-page parallel plot novels.

220.224.246.97 07:14, 25 March 2013 (UTC)

Let's just compare the two pictures and see how the bottom right changes, which I believe is water and they are indeed waiting for the tide. Statharas.903 (talk) 07:19, 25 March 2013 (UTC)

I'm adding urls to pictures bellow, edit freely.
They change every 5 minutes, will try to keep track.

http://www.explainxkcd.com/wiki/images/f/f8/time.png http://imgs.xkcd.com/comics/time/1e349a579b5f9b5ed487ddf7e88244b70330941ddedac9c6abf6ed2e3f589b97.png http://imgs.xkcd.com/comics/time/752687b61523144c61736cd89f8c153dc41e19128f72d78d44947ff800f057fa.png http://dl.dropbox.com/u/932170/timeasdf.png http://dl.dropbox.com/u/932170/time6.png

I have uploaded all the different images onto the wiki, in the order that they were revealed. To avoid needless duplication of effort, I'll put them up in the explanation page. Davidy²²[talk] 07:44, 25 March 2013 (UTC)

It just went back to the second image... 220.224.246.97 07:59, 25 March 2013 (UTC)

And now changed to something new. http://imgs.xkcd.com/comics/time/cdcc6b46b32c53f8596cd0106958b42c4260b9cbc022e6d94054147aa6554960.png
The images do look alike, but they're all different. Thanks David. Statharas.903 (talk) 08:04, 25 March 2013 (UTC)
No..I checked the random string. They're exactly the same. In fact, now it's gone back to the second image. Again. 220.224.246.97 08:07, 25 March 2013 (UTC)

Just found this JavaScript code embedded in the comic HTML source (Update: Reformatted to prevent eye-bleeding):

(function (e) {
    "use strict";

    function t() {
        this.data = {}
    }
    function n() {
        this.listeners = new t
    }
    function r(e) {
        setTimeout(function () {
            throw e
        }, 0)
    }
    function i(e) {
        this.type = e
    }
    function s(e, t) {
        i.call(this, e), this.data = t.data, this.lastEventId = t.lastEventId
    }
    function g(e, t) {
        var n = Number(e);
        return (n < 1 ? 1 : n > 18e6 ? 18e6 : n) || t
    }
    function y(e, t, n) {
        try {
            typeof e[t] == "function" && e[t](n)
        } catch (i) {
            r(i)
        }
    }
    function b(t, r) {
        function B() {
            L = d, N !== null && (N.abort(), N = null), C !== 0 && (clearTimeout(C), C = 0), S.readyState = d
        }
        function j(e) {
            var t = L === p || L === h ? N.responseText || "" : "",
                n = null;
            if (L === h) {
                var r = f ? t !== "" ? N.getResponseHeader("Content-Type") : "" : N.contentType;
                if (r && v.test(r)) {
                    L = p, T = !0, x = u, S.readyState = p, n = new i("open"), S.dispatchEvent(n), y(S, "onopen", n);
                    if (L === d) return
                }
            }
            if (L === p) {
                t.length > k && (H = !0, T = !0);
                var o = 0,
                    a = t.indexOf("\r", k),
                    l = t.indexOf("\n", k);
                while (a !== -1 || l !== -1) {
                    a === -1 || l !== -1 && l < a ? (o = l, l = t.indexOf("\n", o + 1)) : (o = a, a = t.indexOf("\r", o + 1));
                    var m = t.slice(k, o),
                        B = D;
                    D = t.slice(o, o + 1) === "\r", k = o + 1;
                    if (!B || m.length !== 0 || D) {
                        _.push(m);
                        var j = _.join("");
                        _.length = 0;
                        if (j !== "") {
                            var I = "",
                                q = j.indexOf(":", 0);
                            q !== -1 && (I = j.slice(q + (j.slice(q + 1, q + 2) === " " ? 2 : 1)), j = j.slice(0, q)), j === "data" ? A.push(I) : j === "id" ? O = I : j === "event" ? M = I : j === "retry" ? (u = g(I, u), x = u, b < u && (b = u)) : j === "retryLimit" ? b = g(I, b) : j === "heartbeatTimeout" && (w = g(I, w), C !== 0 && (clearTimeout(C), C = setTimeout(R, w)))
                        } else {
                            if (A.length !== 0) {
                                E = O;
                                var U = M || "message";
                                n = new s(U, {
                                    data: A.join("\n"),
                                    lastEventId: O
                                }), S.dispatchEvent(n), U === "message" && y(S, "onmessage", n);
                                if (L === d) return
                            }
                            A.length = 0, M = ""
                        }
                    }
                }
                k !== t.length && (_.push(t.slice(k)), k = t.length)
            }
            H && P === 0 && (H = !1, P = setTimeout(F, 80)), L !== p && L !== h || !(e || k > 1048576 || C === 0 && !T) ? C === 0 && (T = !1, C = setTimeout(R, w)) : (L = c, N.abort(), C !== 0 && (clearTimeout(C), C = 0), x > b && (x = b), C = setTimeout(R, x), x = x * 2 + 1, S.readyState = h, n = new i("error"), S.dispatchEvent(n), y(S, "onerror", n))
        }
        function F() {
            P = 0, j(!1)
        }
        function I() {
            j(!1)
        }
        function q() {
            j(!0)
        }
        function R() {
            C = 0;
            if (L !== c) {
                j(!1);
                return
            }
            if (navigator.onLine === !1) {
                C = setTimeout(R, 500);
                return
            }
            if (m && e.document && (e.document.readyState === "loading" || e.document.readyState === "interactive")) {
                C = setTimeout(R, 100);
                return
            }
            N.onload = N.onerror = q, N.mozAnon === undefined ? N.onprogress = I : N.onreadystatechange = I, T = !1, C = setTimeout(R, w), k = 0, L = h, A.length = 0, M = "", O = E, _.length = 0, D = !1, N.open("GET", t + ((t.indexOf("?", 0) === -1 ? "?" : "&") + "lastEventId=" + encodeURIComponent(E) + "&r=" + String(Math.random() + 1).slice(2)), !0), N.withCredentials = o, N.responseType = "text", f && N.setRequestHeader("Accept", "text/event-stream"), N.send(null)
        }
        t = String(t);
        var o = Boolean(a && r && r.withCredentials),
            u = g(r ? r.retry : NaN, 1e3),
            b = g(r ? r.retryLimit : NaN, 3e5),
            w = g(r ? r.heartbeatTimeout : NaN, 45e3),
            E = r && r.lastEventId && String(r.lastEventId) || "",
            S = this,
            x = u,
            T = !1,
            N = new l,
            C = 0,
            k = 0,
            L = c,
            A = [],
            O = "",
            M = "",
            _ = [],
            D = !1,
            P = 0,
            H = !1;
        r = null, n.call(this), this.close = B, this.url = t, this.readyState = h, this.withCredentials = o, R()
    }
    function w() {
        this.CONNECTING = h, this.OPEN = p, this.CLOSED = d
    }
    t.prototype = {
        get: function (e) {
            return this.data[e + "~"]
        },
        set: function (e, t) {
            this.data[e + "~"] = t
        },
        "delete": function (e) {
            delete this.data[e + "~"]
        }
    }, n.prototype = {
        dispatchEvent: function (e) {
            var t = String(e.type),
                n = this.listeners,
                i = n.get(t);
            if (!i) return;
            var s = i.length,
                o = -1;
            while (++o < s) {
                var u = i[o];
                try {
                    u.call(this, e)
                } catch (a) {
                    r(a)
                }
            }
        },
        addEventListener: function (e, t) {
            e = String(e);
            var n = this.listeners,
                r = n.get(e);
            r || n.set(e, r = []);
            var i = r.length;
            while (--i >= 0) if (r[i] === t) return;
            r.push(t)
        },
        removeEventListener: function (e, t) {
            e = String(e);
            var n = this.listeners,
                r = n.get(e);
            if (!r) return;
            var i = r.length,
                s = [],
                o = -1;
            while (++o < i) r[o] !== t && s.push(r[o]);
            s.length === 0 ? n["delete"](e) : n.set(e, s)
        }
    }, s.prototype = i.prototype;
    var o = e.XMLHttpRequest,
        u = e.XDomainRequest,
        a = Boolean(o && (new o).withCredentials !== undefined),
        f = a,
        l = a ? o : u,
        c = -1,
        h = 0,
        p = 1,
        d = 2,
        v = /^text\/event\-stream;?(\s*charset\=utf\-8)?$/i,
        m = /AppleWebKit\/5([0-2][0-9]|3[0-4])[^\d]/.test(navigator.userAgent);
    w.prototype = n.prototype, b.prototype = new w, w.call(b), l && (e.EventSource = b)
})(this),
function () {
    function e(e) {
        (new Image).src = "http://xkcd.com/events/" + e
    }
    function t() {
        location.hash == "#verbose" && console.log.apply(console, arguments)
    }
    try {
        var n = "http://c0.xkcd.com/stream/comic/time?method=EventSource",
            r = new EventSource(n);
        t("connecting to event source:", n), r.addEventListener("open", function (t) {
            e("connect_start")
        }, !1), r.addEventListener("error", function (t) {
            e("connect_error")
        }, !1), r.addEventListener("loadtest", t, !1), r.addEventListener("comic/time", t, !1), r.addEventListener("comic/time", function (e) {
            var n = JSON.parse(e.data),
                r = document.getElementById("comic").getElementsByTagName("img")[0],
                i = Math.round(Math.random() * n.spread);
            t("waiting", i, "seconds before displaying comic", n.image), setTimeout(function () {
                r.src = "http://imgs.xkcd.com/comics/time/" + n.image
            }, i * 1e3)
        }, !1)
    } catch (i) {
        e("js_error")
    }
}();

I'm no programmer but this looks important to me...

Doesn't really help. The script basically changes the image when something happens (probably some time passes, although it's possible there is more hidden there). WHAT image then appears is not directed by the script, but by the site. Specifically, the image displayed as first is taken from http://c0.xkcd.com/redirect/comic/time, while the script asks for http://c0.xkcd.com/stream/comic/time?method=EventSource&r=(somenumber) ... which is, if you get correct "r", probably some json containing the image url. So, even if you hack the script, you will not get all possible urls. -- Hkmaly (talk) 09:17, 25 March 2013 (UTC)
... actually, given that the script part doesn't seem to do anything just now, it's even possible it's for later (ie, starts producing images when the correct time come). Or maybe there is a bug somewhere in the code :-). -- Hkmaly (talk) 09:27, 25 March 2013 (UTC)
Thanks for explaining. Why hasn't anyone posted this before? Could "location.hash" possibly have anything to do with the method used to generate the image hash key? Also, why is this code so difficult to follow (Obfuscation?)? So many questions... Sorry if this is just a huge waste of Time.
location is the URI of the page. location.hash is the part of the uri after the # character. If you go to https://xkcd.com/1190/#verbose, you'll see some debugging output in your browser's debugging console (Firefox: Web Console or Firebug, Chrome: Development Tools). But nothing to decode the algorithm... :-( --83.243.48.2 10:01, 25 March 2013 (UTC)
Well, I don't know what's doing it, but there's definitely some script (probably this script) that's refreshing the image automatically. I left the comic open for an hour or so and noticed the image had changed. I refreshed with #verbose in Chrome right before the 30 minute mark and got the following in the console.
connecting to event source: http://c0.xkcd.com/stream/comic/time?method=EventSource time07.min.js:1
s {type: "comic/time", data: "{"spread":5,"image":"832a7f13ca0fadc46e93475bb617d78211e32c81c3af0e289a51f8f149707759.png"}", lastEventId: "e2992bf0-9557-11e2-8001-1c6f659cb250"} time07.min.js:1
waiting 0 seconds before displaying comic 832a7f13ca0fadc46e93475bb617d78211e32c81c3af0e289a51f8f149707759.png time07.min.js:1
Resource interpreted as Image but transferred with MIME type application/octet-stream: "http://xkcd.com/events/connect_start". time07.min.js:1
s {type: "comic/time", data: "{"spread":5,"image":"847265673986f085460bf1a95b96f7171bcd9a4f1f0a598b2188307d03bcfaa3.png"}", lastEventId: "79580fe8-9558-11e2-8001-1c6f659cb250"} time07.min.js:1
waiting 4 seconds before displaying comic 847265673986f085460bf1a95b96f7171bcd9a4f1f0a598b2188307d03bcfaa3.png time07.min.js:1
connection error i {type: "error"} time07.min.js:1
Resource interpreted as Image but transferred with MIME type application/octet-stream: "http://xkcd.com/events/connect_error".
The script seems to poll the server every minute or two. It's different from before, where the image server itself redirected to the correct image. The auto refresh was probably always intended, but not quite ready when the comic went live. It may have turned out to be necessary too, so the image server doesn't have to do all the work. 129.21.119.153 14:45, 25 March 2013 (UTC)

Before obfuscation...

(function (global) {
    "use strict";

    function Map() {
        this.data = {}
    }
    function EventTarget() {
        this.listeners = new Map
    }
    function throwError(e) {
        setTimeout(function () {
            throw e
        }, 0)
    }
    function Event(type) {
        this.type = type
    }
    function MessageEvent(type, options) {
        Event.call(this, type), this.data = options.data, this.lastEventId = options.lastEventId
    }
    function getDuration(value, def) {
        var n = Number(value);
        return (n < 1 ? 1 : n > 18e6 ? 18e6 : n) || def
    }
    function fire(that, property, event) {
        try {
            typeof that[property] == "function" && that[property](event)
        } catch (e) {
            throwError(e)
        }
    }
    function EventSource(url, options) {
        function close() {
            currentState = CLOSED, xhr !== null && (xhr.abort(), xhr = null), timeout !== 0 && (clearTimeout(timeout), timeout = 0), that.readyState = CLOSED
        }
        function onProgress(isLoadEnd) {
            var responseText = currentState === OPEN || currentState === CONNECTING ? xhr.responseText || "" : "",
                event = null;
            if (currentState === CONNECTING) {
                var contentType = isXHR ? responseText !== "" ? xhr.getResponseHeader("Content-Type") : "" : xhr.contentType;
                if (contentType && contentTypeRegExp.test(contentType)) {
                    currentState = OPEN, wasActivity = !0, retry = initialRetry, that.readyState = OPEN, event = new Event("open"), that.dispatchEvent(event), fire(that, "onopen", event);
                    if (currentState === CLOSED) return
                }
            }
            if (currentState === OPEN) {
                responseText.length > charOffset && (wasAct = !0, wasActivity = !0);
                var i = 0,
                    i1 = responseText.indexOf("\r", charOffset),
                    i2 = responseText.indexOf("\n", charOffset);
                while (i1 !== -1 || i2 !== -1) {
                    i1 === -1 || i2 !== -1 && i2 < i1 ? (i = i2, i2 = responseText.indexOf("\n", i + 1)) : (i = i1, i1 = responseText.indexOf("\r", i + 1));
                    var line = responseText.slice(charOffset, i),
                        oldWasCR = wasCR;
                    wasCR = responseText.slice(i, i + 1) === "\r", charOffset = i + 1;
                    if (!oldWasCR || line.length !== 0 || wasCR) {
                        responseBuffer.push(line);
                        var field = responseBuffer.join("");
                        responseBuffer.length = 0;
                        if (field !== "") {
                            var value = "",
                                j = field.indexOf(":", 0);
                            j !== -1 && (value = field.slice(j + (field.slice(j + 1, j + 2) === " " ? 2 : 1)), field = field.slice(0, j)), field === "data" ? dataBuffer.push(value) : field === "id" ? lastEventIdBuffer = value : field === "event" ? eventTypeBuffer = value : field === "retry" ? (initialRetry = getDuration(value, initialRetry), retry = initialRetry, retryLimit < initialRetry && (retryLimit = initialRetry)) : field === "retryLimit" ? retryLimit = getDuration(value, retryLimit) : field === "heartbeatTimeout" && (heartbeatTimeout = getDuration(value, heartbeatTimeout), timeout !== 0 && (clearTimeout(timeout), timeout = setTimeout(onTimeout, heartbeatTimeout)))
                        } else {
                            if (dataBuffer.length !== 0) {
                                lastEventId = lastEventIdBuffer;
                                var type = eventTypeBuffer || "message";
                                event = new MessageEvent(type, {
                                    data: dataBuffer.join("\n"),
                                    lastEventId: lastEventIdBuffer
                                }), that.dispatchEvent(event), type === "message" && fire(that, "onmessage", event);
                                if (currentState === CLOSED) return
                            }
                            dataBuffer.length = 0, eventTypeBuffer = ""
                        }
                    }
                }
                charOffset !== responseText.length && (responseBuffer.push(responseText.slice(charOffset)), charOffset = responseText.length)
            }
            wasAct && progressTimeout === 0 && (wasAct = !1, progressTimeout = setTimeout(p, 80)), currentState !== OPEN && currentState !== CONNECTING || !(isLoadEnd || charOffset > 1048576 || timeout === 0 && !wasActivity) ? timeout === 0 && (wasActivity = !1, timeout = setTimeout(onTimeout, heartbeatTimeout)) : (currentState = WAITING, xhr.abort(), timeout !== 0 && (clearTimeout(timeout), timeout = 0), retry > retryLimit && (retry = retryLimit), timeout = setTimeout(onTimeout, retry), retry = retry * 2 + 1, that.readyState = CONNECTING, event = new Event("error"), that.dispatchEvent(event), fire(that, "onerror", event))
        }
        function p() {
            progressTimeout = 0, onProgress(!1)
        }
        function onProgress2() {
            onProgress(!1)
        }
        function onLoadEnd() {
            onProgress(!0)
        }
        function onTimeout() {
            timeout = 0;
            if (currentState !== WAITING) {
                onProgress(!1);
                return
            }
            if (navigator.onLine === !1) {
                timeout = setTimeout(onTimeout, 500);
                return
            }
            if (webkitBefore535 && global.document && (global.document.readyState === "loading" || global.document.readyState === "interactive")) {
                timeout = setTimeout(onTimeout, 100);
                return
            }
            xhr.onload = xhr.onerror = onLoadEnd, xhr.mozAnon === undefined ? xhr.onprogress = onProgress2 : xhr.onreadystatechange = onProgress2, wasActivity = !1, timeout = setTimeout(onTimeout, heartbeatTimeout), charOffset = 0, currentState = CONNECTING, dataBuffer.length = 0, eventTypeBuffer = "", lastEventIdBuffer = lastEventId, responseBuffer.length = 0, wasCR = !1, xhr.open("GET", url + ((url.indexOf("?", 0) === -1 ? "?" : "&") + "lastEventId=" + encodeURIComponent(lastEventId) + "&r=" + String(Math.random() + 1).slice(2)), !0), xhr.withCredentials = withCredentials, xhr.responseType = "text", isXHR && xhr.setRequestHeader("Accept", "text/event-stream"), xhr.send(null)
        }
        url = String(url);
        var withCredentials = Boolean(xhr2 && options && options.withCredentials),
            initialRetry = getDuration(options ? options.retry : NaN, 1e3),
            retryLimit = getDuration(options ? options.retryLimit : NaN, 3e5),
            heartbeatTimeout = getDuration(options ? options.heartbeatTimeout : NaN, 45e3),
            lastEventId = options && options.lastEventId && String(options.lastEventId) || "",
            that = this,
            retry = initialRetry,
            wasActivity = !1,
            xhr = new Transport,
            timeout = 0,
            charOffset = 0,
            currentState = WAITING,
            dataBuffer = [],
            lastEventIdBuffer = "",
            eventTypeBuffer = "",
            responseBuffer = [],
            wasCR = !1,
            progressTimeout = 0,
            wasAct = !1;
        options = null, EventTarget.call(this), this.close = close, this.url = url, this.readyState = CONNECTING, this.withCredentials = withCredentials, onTimeout()
    }
    function F() {
        this.CONNECTING = CONNECTING, this.OPEN = OPEN, this.CLOSED = CLOSED
    }
    Map.prototype = {
        get: function (key) {
            return this.data[key + "~"]
        },
        set: function (key, value) {
            this.data[key + "~"] = value
        },
        "delete": function (key) {
            delete this.data[key + "~"]
        }
    }, EventTarget.prototype = {
        dispatchEvent: function (event) {
            var type = String(event.type),
                listeners = this.listeners,
                typeListeners = listeners.get(type);
            if (!typeListeners) return;
            var length = typeListeners.length,
                i = -1;
            while (++i < length) {
                var listener = typeListeners[i];
                try {
                    listener.call(this, event)
                } catch (e) {
                    throwError(e)
                }
            }
        },
        addEventListener: function (type, callback) {
            type = String(type);
            var listeners = this.listeners,
                typeListeners = listeners.get(type);
            typeListeners || listeners.set(type, typeListeners = []);
            var i = typeListeners.length;
            while (--i >= 0) if (typeListeners[i] === callback) return;
            typeListeners.push(callback)
        },
        removeEventListener: function (type, callback) {
            type = String(type);
            var listeners = this.listeners,
                typeListeners = listeners.get(type);
            if (!typeListeners) return;
            var length = typeListeners.length,
                filtered = [],
                i = -1;
            while (++i < length) typeListeners[i] !== callback && filtered.push(typeListeners[i]);
            filtered.length === 0 ? listeners["delete"](type) : listeners.set(type, filtered)
        }
    }, MessageEvent.prototype = Event.prototype;
    var XHR = global.XMLHttpRequest,
        XDR = global.XDomainRequest,
        xhr2 = Boolean(XHR && (new XHR).withCredentials !== undefined),
        isXHR = xhr2,
        Transport = xhr2 ? XHR : XDR,
        WAITING = -1,
        CONNECTING = 0,
        OPEN = 1,
        CLOSED = 2,
        contentTypeRegExp = /^text\/event\-stream;?(\s*charset\=utf\-8)?$/i,
        webkitBefore535 = /AppleWebKit\/5([0-2][0-9]|3[0-4])[^\d]/.test(navigator.userAgent);
    F.prototype = EventTarget.prototype, EventSource.prototype = new F, F.call(EventSource), Transport && (global.EventSource = EventSource)
 })(this),
function () {
    function record(name) {
        (new Image).src = "http://xkcd.com/events/" + name
    }
    function log() {
        location.hash == "#verbose" && console.log.apply(console, arguments)
    }
    try {
        var esURL = "http://c0.xkcd.com/stream/comic/time?method=EventSource",
            source = new EventSource(esURL);
        log("connecting to event source:", esURL), source.addEventListener("open", function (ev) {
            record("connect_start")
        }, !1), source.addEventListener("error", function (ev) {
            record("connect_error")
        }, !1), source.addEventListener("loadtest", log, !1), source.addEventListener("comic/time", log, !1), source.addEventListener("comic/time", function (ev) {
            var data = JSON.parse(ev.data),
                img = document.getElementById("comic").getElementsByTagName("img")[0],
                delay = Math.round(Math.random() * data.spread);
            log("waiting", delay, "seconds before displaying comic", data.image), setTimeout(function () {
                img.src = "http://imgs.xkcd.com/comics/time/" + data.image
            }, delay * 1e3)
        }, !1)
    } catch (e) {
        record("js_error")
    }
}();

79.180.173.88 09:48, 25 March 2013 (UTC)

http://imgs.xkcd.com/comics/time/426033682a26a0012a6f8e0c47287af91b7991a852d81c77402c937ffbd650c6.png

http://www.explainxkcd.com/wiki/images/1/1e/f46c6571393bee1ee649a7daae41f6328e63482506aef1e22607d22c47dd7027.png --Johnsmith (talk) 22:51, 25 March 2013 (UTC)

http://www.explainxkcd.com/wiki/images/b/b0/88e3a0c8bba935c669606d9134314f811a0961985f968dd5d329e4695acc67c8.png --Johnsmith (talk) 23:10, 25 March 2013 (UTC)


Is it just me or or did Randall manage to make all of us perform a Denial of Service on xkcd.com, and explainxkcd.com ? xkcd.com seems much slower, and I keep getting "500 Internal server error" when accessing this site (explainxkcd.com). I guess that's the effect of having everybody hit F5 every few minutes :) 193.239.192.194 11:57, 25 March 2013 (UTC)

Earlier today, the server handled all the image redirections. The script you see above went through several mutations (currently at #8), with each mutation it seems that Randall is adding more servers and trying to split the load between them. This is basically how a bot-net works - we all run code written by some evil genius, and he's changing the code as time passes to serve some hidden purpose. 79.180.173.88 15:44, 25 March 2013 (UTC)

   If he is using us as a botnet, then maybe the next comic will be something alluding to that.
   Probably like this: http://xkcd.com/350/

When I saw this comic last night and that there was no explanation up, I thought to myself "How zen." I figured that Randall was going through a calm streak before throwing us the utterly ridiculous April 1st comic. Did it come early, or does he have something even bigger planned for us? 76.106.251.87 07:05, 26 March 2013 (UTC)

Sorry, did you miss the bit where this comic updates every 30 minutes and all the server error messages being caused by the massive traffic to both the wiki and the main xkcd website? Davidy²²[talk] 07:08, 26 March 2013 (UTC)
Well, when I said "last night" and "no explanation", I implied that I wasn't aware of that at the time, which is why I thought what I did. Of course, it is now "now" and there is an explanation, so that should answer your question. Also, since it's not April 1st, and Randall has consistently released something major on that day, the jury is still out, leaving my question quite open (though I was really only asking for opinions). 76.106.251.87 07:20, 26 March 2013 (UTC)


wanted to add an image to the list above, but didn't know at what timestamp to add it, got 69085b480cb82911b19fe8f114909756989eed89b0d227db0f59c1843de7ba24.png at 2013-03-26 09:47 CET (UTC+0100)

/Puggan
The hours denote the time since the initial release of the comic. The page is still a work in progress, we're going to bring that all into one image file soon. Davidy²²[talk] 09:13, 26 March 2013 (UTC)

This site should seriously consider cloudflare, it's perfect at times like this and takes minutes to set up. I run all my sites through it and it saves a lot of page huts and bandwidth. 123.3.136.228Evan Pyle

Or at least make the main page a static page that refreshes every so often. I'm guessing that most of the traffic is going to the front page with not as much traffic to the actual comic page Odysseus654 (talk) 15:43, 26 March 2013 (UTC)

Some of the images on the wiki (looks like time38.png through time48.png) are slightly different than what is on the main site. The lines are slightly thicker, as though someone did them based on screen captures. Royce (talk) 14:37, 26 March 2013 (UTC)

Well, at least we have the hashes so they can be re-retrieved, so nothing is really lost, right? Should we add links to the original? Odysseus654 (talk) 15:43, 26 March 2013 (UTC)
I uploaded two of the "thicker" images and one of the "regular" ones, and I did the same thing for all of them: right-click->save-as. Given that the "thick" ones are all clustered together, I think the files on the xkcd site changed. Druid816 (talk) 18:21, 26 March 2013 (UTC)

Story so far: linky Odysseus654 (talk) 19:30, 26 March 2013 (UTC)

I guess we shall find out in ~10 minutes if Randall is trolling us. 129.138.30.95 04:20, 27 March 2013 (UTC)

... so that's it?

Did I just miss something or we've all been epically trolled for 48 hours? 189.59.175.92 04:22, 27 March 2013 (UTC)

Motherofgod, no, he's *still* going! Davidy²²[talk] 05:20, 27 March 2013 (UTC)

What makes you think he's done? 129.138.30.95 04:25, 27 March 2013 (UTC)

I'm still waiting for the water level to drop precipitously... and then for red spiders to run over everything Odysseus654 (talk) 04:28, 27 March 2013 (UTC)

Well, strip 1191 is up so I assumed it was over. I guess it's not. Until April's Fools maybe? 189.59.175.92 04:32, 27 March 2013 (UTC)

It is not over -- the image is still updating, at least it did for me Spongebog (talk)
Yes, it's not over. Last frame shows just a minimal movement of Cueball's head, but no doubt it's still ongoing. 189.59.175.92 04:49, 27 March 2013 (UTC)
Given the fact the strips for the last 2 weeks have been comparatively simple, I expect Randall has been planning this for at least that long. -- 101.98.156.239 (talk) (please sign your comments with ~~~~)

Considering the common theme with "today's" strip, anyone wanna guess that he's sending us a hex-encoded file over a really slow modem link, slated to complete April 1? Anyone wanna run "magic" over the hashes and see if they come up with a compression codec or something? Odysseus654 (talk) 04:45, 27 March 2013 (UTC)

I liked this idea and crunched the hex data for 00:00 to 51:00 into a binary file (http://filebin.ca/bcGyfUvdgBi). Can't see anything resembling a file header, but that doesn't really say much. If this is compressed header-less data there wouldn't likely be any easily discernible patterns. Haven't really tried running the data through anything, zlib was one that came to mind but haven't tried it.

194.114.62.72 I'm pretty shure it's not the seaside, but a lake - the water level is not changing at all.

Its probably going to loop back on itself, eventually, and repeat this way forever. 113.160.224.209 07:12, 27 March 2013 (UTC)

For those wondering about the Javascript behind this: I posted my analysis of the Javascript on the xkcd forums, and further de-obfuscated and annotated the code over on GitHub. Here's a quick summary though: it holds open a connection to xkcd's servers and listens for instructions and follows them. Those instructions are either "load a new image" or "reload the page". So, you don't have to mash F5, it will automatically update the image when they're available. We have no way to control how fast the images come or when they do, and it's quite possible for them to update forever. --Fiveofoh (talk) 06:41, 27 March 2013 (UTC)

This is a series of animation frames. I suspect they will only ever be shown once (based on the fact you can only get the current image, not previous or future images -- this is in keeping with the title, "Time", which passes and which you can't ever get back]. The filenames are UUIDs too long to guess, so somebody needs to start collecting the filenames here so that a proper flipbook can be assembled. Here's the latest URL: http://imgs.xkcd.com/comics/time/81efa7c4509ac7a329407d9da25d12ec0a3baec50e06588586961575e2d65c2c.png Go here to collect URLs: http://c0.xkcd.com/stream/comic/time?method=EventSource

We've kinda already been doing that. They're the big long filenames next to each timestamp. Davidy²²[talk] 09:06, 27 March 2013 (UTC)

Create an animation on this page

Here's a bit of JavaScript to execute in your browser's JavaScript console. (Cmd+Alt+K on Firefox for Mac, for example.)

/* Collect all frame image URLs */
var images = [];
Array.prototype.slice.call(document.querySelectorAll('a[href*="/time"][href$=".png"]')).forEach(function (a) {
	images.push(a.href);
});

/* Create an image in the top-right corner of the screen */
var img = document.body.appendChild(document.createElement('img'));
img.setAttribute('style', 'position: fixed; top: 1em; right: 1em;');

/* Allow removing the image by clicking */
img.onclick = function () {
	img.parentNode.removeChild(img);
};

/* Cycle through the frames */
img.onload = function () {
	/* Pause a bit longer for the "Later! Bye!" frame */
	var delay = img.i === 51 ? 500 : 100;
	setTimeout(function () {
		img.src = images[++img.i % images.length];
	}, delay);
};

/* Start with the first frame */
img.i = 0;
img.src = images[img.i];


Jan! 94.23.195.79 09:22, 27 March 2013 (UTC)


The one for the prior half hour (5AM - 5:30AM EST, 27 March 2013) is located at http://imgs.xkcd.com/comics/time/5450bd39ee84a394467fabcaf92f1a5711c2a4eca24c8bd8a8cec829496e3dd7.png 141.161.133.106 09:26, 27 March 2013 (UTC)