Reproducing the XKCD "Consensus New Year" plot

Building off the other answer, CountryData has the data for the time zones for each country, as well as their population. So we can split each country proportionally into its individual timezones:

countries = CountryData[];
populations = CountryData[#, "Population"] & /@ countries;

timeZones = CountryData[#, "TimeZones"] & /@ countries;

data = Flatten[SortBy[Transpose[{timeZones, populations}] /. {a_, b_} :> 
      Thread[{a, b/Length[a]}] /; ListQ[a], First], 1];

gathered = Reverse@SortBy[
    Map[{#[[1, 1]], Total[#[[All, 2]]]} &, GatherBy[data, First]], First];

houroffsets = (Quantity[#, "Hours"] & /@ (-gathered[[All, 1]])) + 
   Quantity[14 - 5, "Hours"];
ESTtimes = DateString[DatePlus[DateObject[{2018, 12, 31, 10, 0, 0}, 
       TimeZone -> -5], #], 
     {"Hour12", ":", "Minute", "AMPM", " EST"}] & /@ houroffsets;
xticks = Transpose[{houroffsets, ESTtimes}][[{10, 18, 26, 33, 36, 40}]]

plot = ListLinePlot[Transpose[{houroffsets, Accumulate[gathered[[All, 2]]]/Total[gathered[[All, 2]]]}], 
  FrameTicks -> {xticks, {{0, "0%"}, {0.2, "20%"}, {0.4, "40%"}, {0.6,
   "60%"}, {0.8, "80%"}, {1.0, "100%"}}}, Joined -> True, 
  PlotLabel -> "PERCENTAGE OF THE WORLD'S POPULATION LIVING IN 2019", 
  Frame -> {True, True, False, False}, InterpolationOrder -> 1, 
  ImageSize -> 800, GridLines -> {xticks[[All, 1]], {0.5}}, Axes -> False]

enter image description here

Note that this graph differs from the xkcd one, as the spaces on the xkcd plot are not spaced according to the actual time, just based on the individual timezones.

Additionally we can use the xkcdconvert code defined in this answer to change the style (although the labels start to overlap at this size).

xkcdConvert[plot]

enter image description here


While it takes a lot longer to get the data, one can go a bit more granular by using "AdministrativeDivision" rather than "Country". This could likely be done more efficiently, but I break the steps down quite a bit here:

countries = EntityList["Country"];
divisions = 
 Map[EntityValue[
    Entity["AdministrativeDivision", {EntityProperty[
        "AdministrativeDivision", "ParentRegion"] -> #}], 
    "Entities"] &, countries];
flat = divisions // Flatten;

Get the associated populations:

tzp = EntityValue[flat, {"TimeZones", "Population"}];

Now a lot of stuff happens in the following: first I get rid of missing data, then I use the timezone information to convert to a time and date (using TimeZoneConvert), finally I group the results and take the Total. (You might have noticed that I make an approximation here, if an AdministrativeDivision contains multiple time zones, I just take the first one. I don't think this is too terrible to do).

results = 
  KeySort@GroupBy[MapAt[
    TimeZoneConvert[
      DateObject[{2019, 1, 1, 0, 0}, TimeZone -> First[#]], -5] &, 
    DeleteMissing[tzp, 1, 1], {All, 1}], First -> Last, Total];

Short[results]

Mathematica graphics

Now to visualize it. I use Block to set the time zone used by DateListPlot to be UTC-5.

Block[{$TimeZone = -5},
 DateListPlot[
  Thread[{Keys[results], 
    Accumulate@Normalize[QuantityMagnitude@Values@results, Total]}], 
  InterpolationOrder -> 0, Frame -> True, Axes -> False,
  , DateTicksFormat -> {"Hour12Short", ":", "Minute", "AMPM", " EST", 
    "\n", "MonthNameShort", " ", "DayShort", "st"}, 
  FrameTicks -> {{{{0, "0%"}, {1/2, "50%"}, {1, "100%"}}, 
     Automatic}, {{DateObject[{2018, 12, 31, 5}], 
      DateObject[{2018, 12, 31, 11}], 
      DateObject[{2018, 12, 31, 13, 30}], 
      DateObject[{2018, 12, 31, 19, 0}], 
      DateObject[{2019, 1, 1, 0, 0}], DateObject[{2019, 1, 1, 3, 0}], 
      DateObject[{2019, 1, 1, 6, 0}]}, None}}, ImageSize -> 800, 
  FrameTicksStyle -> {Directive["Label", 12], 
    Directive["Label", 10]}, AspectRatio -> 1/2, 
  GridLines -> {Automatic, {1/2}}, 
  PlotLabel -> "Percentage of the World's Population Living in 2019", 
  LabelStyle -> 18]]

Mathematica graphics


Here is the outline of the solution, illustrated for just the UN countries. (Replace with CountryData[] to get all countries.)

List of UN countries:

theCountries = CountryData["UN"];

The longitudes and populations of these countries:

theLongs = Longitude[#] & /@ theCountries;
thePops = CountryData[#, "Population"] & /@ theCountries;

The data sorted by longitude (going around the world):

allData = SortBy[Transpose[{theLongs, thePops}], First]

Rounding each country to be in a time zone (15 degree separation in longitude):

roundedData = {Round[#[[1]], 15], #[[2]]} & /@ allData

Accumulating (and normalizing) the populations as we go "around the world":

finalData = 
 Transpose[{roundedData[[All, 1]], 
   Accumulate[roundedData[[All, 2]]]/
    Last[Accumulate[roundedData[[All, 2]]]]}]

Plot it:

        ListPlot[finalData, 
          Joined -> True,
          Ticks -> {Transpose[{Range[-165, 165, 15], 
    Table[Rotate["GMT+" <> ToString[i], π/2], {i, 23}]}],
  {{0.4,  "40%"}, {0.6, "60%"}, {0.8, "80%"}, {1.0, "100%"}}},
          PlotLabel -> 
          "CONSENSUS NEW YEAR:  AS OF 1:30PM EASTERN TIME (6:30PM UTC)\n A MAJORITY OF THE WORLD'S POPULATION WILL BE LIVING IN 2019",
         ImageSize -> 500]

enter image description here

(Of course, this makes the simplification that all a country's population is in the same time zone... of course not quite correct for many large countries.)

If you want to be really ambitious, plot the data in the XKCD style.