In the process of updating my website, I ended up doing significant amount of work on my arbitrary culture library, mfgames-culture. This was used to let me enter the events and dates in a world-specific formats (such as '1454/7/41 MTR 17::61') and convert them into other formats (including the ISO standard used by the timeline library, vis.js).

Why Arbitrary?

I can't find the page where I talk about the reason why I'm creating this library, so I'll give a brief run down. In the process of creating world-specific calendars (such as here and here), I don't base all the work off the Gregorian calendar. Instead, I built it up from the ground-up based on the culture, keeping in mind their politics, holy numbers, and other elements.

This creates a good flavor for the world but makes it difficult to create a good timeline for a novel or the world. Most organization programs don't work outside of Gregorian calendars or formats. That means I have a choice of ignoring my world-specific elements for tracking purposes or find some way of making those in-world calendars to work.

That is the crux of mfgames-culture, to create an API and tools for working with arbitrary cultures that are described purely with data files. Eventually, I'll have other versions of this but I'm using the Typescript library as the reference implementation.

Examples

Below are some examples using a command-line program that uses the library. I also have an alias that uses my own culture information.

$ sudo npm install --global mfgames-culture-cli
$ alias fedran="mfgames-culture --culture=kyo --data=$SRC/fedran-culture-data/dist"

Parsing

Right now, I'm only focusing on dates and times. There are two parts: the structure and the appearance. The structure is the components of the calendar, such as there are twelve months in a year, 28-31 days in a month, one hundred years is called a "century". The appearance is how those components are formatted, such as the US using MM/DD/YYYY but the UK uses DD/MM/YYYY. It is the same calendar (structure) but different names for months.

The basic idea of the library is to take a JSON or YAML file that describes a calendar and provide an API for using it like many other calendar libraries, such as moment.js. It doesn't many any assumptions of about how many months, days, or even seconds in a day. Instead, everything is calculated based on the file and it produces a rich object that contains that information.

$ mfgames-culture parse 7/7/2017 --indent 2
{
  "julian": 2457941.5,
  "type": "instant",
  "year": 2017,
  "century": 20,
  "millenniumCentury": 0,
  "decade": 201,
  "centuryDecade": 1,
  "centuryYear": 17,
  "decadeYear": 7,
  "millennium": 2,
  "yearDay": 187,
  "yearMonth": 6,
  "monthDay": 6,
  "hour24": 0,
  "meridiem": 0,
  "hour12": 0,
  "hourMinute": 0,
  "minuteSecond": 0
}
$

Of course, I need to be able to do the same with my in-world calendar.

$ fedran parse "1454/7/41 MTR 17::61" --no-json --yaml
julian: 2383794.0875
type: instant
mochyu: 1454
mochyuRote: 317
mochyuRichi: 6
richiRote: 40
koga: 16
mugi: 60
bidashu: 0
$

Data Files

In both examples, the data files are located somewhere the CLI can find it. In the first case, the files from mfgames-culture-data are packaged with the utility. For the Fedran data, you can find it here.

If you want to download them somewhere, they are just NPM packages.

$ npm install mfgames-culture-data fedran-culture-data
$ ls node_modules/mfgames-culture-data/dist/
combined.json        duodecimal-period.json      gregorian.json
combined.min.json    duodecimal-period.min.json  gregorian.min.json
duodecimal.json      en-US.json                  index.json
duodecimal.min.json  en-US.min.json              index.min.json
$ ls node_modules/fedran-culture-data/dist/
combined.json                      mifuno-kopachi-period.json
combined.min.json                  mifuno-kopachi-period.min.json
index.json                         natural.json
index.min.json                     natural.min.json
kyo.json                           seasons.json
kyo.min.json                       seasons.min.json
mansupi-tachira-ripochya.json      tar.json
mansupi-tachira-ripochya.min.json  tar.min.json
mifuno-kopachi.json                tarsan-standard-calendar.json
mifuno-kopachi.min.json            tarsan-standard-calendar.min.json
$

If you are curious why I have the data files as npm packages, I decided to make it easier to version them so the website won't break until I'm ready to upload it. Not to mention, semantic versions is useful for more than libraries.

Calendars

I'm not going to go into too much directions of how to create the format. I have decent documentation on the purpose and setup.

The main part is a calendar consists of cycles. For example, the Gregorian is based on a year. A month is calculated from days within a year and decades are a formula based on decades. The system recursively goes through these cycles to figure out the individual amounts that you saw above.

Cultures

Cultures are where dates (instants) and periods (durations) are formatted. A culture has many ways of formatting any given instant, once you have one, you can pull it out in a variety of ways.

$ mfgames-culture format "2017-07-14 23:19:04" --output-style markdown-table
| format                | results                      |
| --------------------- | ---------------------------- |
| JD                    | 2457949.47157407407407407407 |
| M/D/YYYY              | 7/14/2017                    |
| M/D/YYYY h:mm tt      | 7/14/2017 11:19 PM           |
| MM/DD/YYYY            | 07/14/2017                   |
| MM/DD/YYYY h:mm:ss tt | 07/14/2017 11:19:04 PM       |
| MMM DD, YY            | Jul 14, 17                   |
| YYYY-MM-DD            | 2017-07-14                   |
| YYYY-MM-DD HH:mm:ss   | 2017-07-14 23:19:04          |
| default               | 7/14/2017 11:19 PM           |
| h:mm tt               | 11:19 PM                     |
| shortDate             | 7/14/2017                    |
| shortDateTime         | 7/14/2017 11:19 PM           |
| shortTime             | 11:19 PM                     |
$ mfgames-culture format "2017-07-14 23:19:04" --output-format shortDateTime
7/14/2017 11:19 PM
$

Again, this can be done with my fantasy calendar also.

$ fedran format "1454/7/41 MTR 17::61" --output-style markdown-table
| format              | results                |
| ------------------- | ---------------------- |
| JD                  | 2383794.0875           |
| YYYY                | 1454                   |
| YYYY MTR            | 1454 MTR               |
| YYYY/OOO            | 1454/318               |
| YYYY/OOO K::        | 1454/318 17::          |
| YYYY/OOO K::M       | 1454/318 17::61        |
| YYYY/OOO K::M:B     | 1454/318 17::61:1      |
| YYYY/OOO MTR        | 1454/318 MTR           |
| YYYY/OOO MTR K::    | 1454/318 MTR 17::      |
| YYYY/OOO MTR K::M   | 1454/318 MTR 17::61    |
| YYYY/OOO MTR K::M:B | 1454/318 MTR 17::61:1  |
| YYYY/R              | 1454/7                 |
| YYYY/R MTR          | 1454/7 MTR             |
| YYYY/R/O            | 1454/7/41              |
| YYYY/R/O K::        | 1454/7/41 17::         |
| YYYY/R/O K::M       | 1454/7/41 17::61       |
| YYYY/R/O K::M:B     | 1454/7/41 17::61:1     |
| YYYY/R/O MTR        | 1454/7/41 MTR          |
| YYYY/R/O MTR K::    | 1454/7/41 MTR 17::     |
| YYYY/R/O MTR K::M   | 1454/7/41 MTR 17::61   |
| YYYY/R/O MTR K::M:B | 1454/7/41 MTR 17::61:1 |
| default             | 1454/7/41 17::61       |
| shortDate           | 1454/7/41              |
| shortDateTime       | 1454/7/41 17::61       |
| shortTime           | 17::61                 |
$ fedran format "1454/7/41 MTR 17::61" --output-format shortDateTime
1454/7/41 17::61
$

Typescript/Javascript

These libraries can also be used directly. This is how I implemented the timeline on my website. It is also used by the build process to create the date/time tooltips like you can see on Rutej√¨mo's page.

The first part is to load a culture.

import * as mfgamesCulture from "mfgames-culture";
import * as mfgamesCultureData from "mfgames-culture-data";

// Create a data provider for the basic culture data. The entire JSON files is
// exported as `.combined` which is passed into the property provider.
var provider = new mfgamesCulture.PropertyCultureDataProvider(
  mfgamesCultureData.combined);
var cultureData = provider.getCultureSync("en-US");
var culture = new mfgamesCulture.Culture(cultureData);

(There is a promise version in getCulturePromise).

Once a culture is loaded, it can be used to create an in-memory representation of a format described in the culture file. A single point in time is called an instant and a range of time is called a period.

var instant = culture.parseInstant("2015-01-02 13:14:15");
console.log(instant);

This produces an output of:

{ julian:
   { [String: '2457025.0515625']
     s: 1,
     e: 6,
     c: [ 2, 4, 5, 7, 0, 2, 5, 0, 5, 1, 5, 6, 2, 5 ],
     constructor: { [Function: Big] DP: 20, RM: 1, E_NEG: -7, E_POS: 21 } },
  type: 'instant',
  year: 2015,
  century: 20,
  millenniumCentury: 0,
  decade: 201,
  centuryDecade: 1,
  centuryYear: 15,
  decadeYear: 5,
  millennium: 2,
  yearDay: 1,
  yearMonth: 0,
  monthDay: 1,
  hour24: 13,
  meridiem: 1,
  hour12: 1,
  hourMinute: 14,
  minuteSecond: 15 }

It can also be used to format results.

var format = culture.formatInstant(instant, "YYYY-MM-DD HH:mm:ss");
console.log(format);

… which produces:

2015-01-02 13:14:15

To get the timeline page, I basically parse the in-world dates, convert them to Julian Dates (using the JD format), then use that number to figure out the ISO date and time. Then I override the labels to take the Gregorian date and convert it back to the in-world dates so the reader never sees how I use Gregorian as the intermediary format.

This is why I also have every instant also figure out an effective Julian Date. That way, I can convert between any instant, even between multiple cultures.

2017-07-17