Perpetual Calendar - Part 2

The previous part looked at a way to find the day of the week for arbitrary dates outside secs() range. Now, time to put that to use!

One obvious application is to extend time() and secs(). For example, I can make an ictime() ufun / @function that keeps track of time in a game set in the 19th century.

Ideally, you would set the starting date in secs(), then keep track of how much time has passed, comparing the current secs() with the stored starting value. Since we can't do that, we cheat a little.

We break the starting secs() value into two: one for bookkeeping the passage of time in RL, and one for the passage of time IC.

BASESECS [#1764]: 1180009252
BASESECS|NOTE [#1764]: This holds the RL secs at which the calendar was reset. Used to calculate how much time in real life has passed since the start of the calendar.

We can compare secs() with &basesecs to see how much time has passed in real life.

Now we need some settings to initialize IC date:
START_DATE [#1764]: 1
START_MONTH [#1764]: Jan
START_TIME [#1764]: 12:00:00
START_YEAR [#1764]: 1876

We put these together with
STARTSECS [#1764]: convtime(v(start_month) [v(start_date)] [v(start_time)] [add(v(start_year),100)])

Notice this actually returns the secs() for Jan 1 12:00:00 1976. Since convtime() and such can't handle values before 1970, we offset the year by 100 to keep it in legal range. We will compensate for this later on. Of course, if the game were set in the 18th century, we'd have to use a larger offset; if the game were set far in the future, we'd have to use a negative offset.

Now we need to figure out how much time has passed since the calendar was set. This is pretty easy:
DELTA [#1764]: sub(secs(),u(basesecs))

But a lot of people want to set a time compression for ictime, so that time passes faster in IC. We'll set the compression ration with
FACTOR [#1764]: 2

So that we can figure out how much time has passed in IC with
IC_DELTA [#1764]: mul(v(factor),u(delta))

Then, the current time in IC (still offset by 100 years) will be
CUR_TIME [#1764]: convsecs(add(u(startsecs),u(ic_delta),%0))

Now we need to do two more things: compensate for the offset we used, then find out the day of the week. We'll do both of these with

ICTIME [#1764]: u(dayofweek,elements(setr(1,index(setr(0,u(cur_time,%0)),%b,2,sub(words(%q0),2)) [sub(last(%q0),100)]),1 2 4)) %q1
ICTIME|NOTE [#1764]: This is the main public function that returns the IC time, in the same format as standard time() function. It can take an argument in number of seconds to returns the IC time in future or past.

The complete ICTIME object with some accessory functions and attributes is below. There are obviously a lot of things you can do to customize this. The &sunrise and &sunset attributes can be set dynamically depending on the season, for instance. In the next part we'll look at how to present the calendar in a nice, easy-to-use format.

ictime(#10911TLVXn)
Type: THING Flags: LINK_OK VISUAL SAFE NO_COMMAND
The IC Clock / Calendar object. The main function is ICTIME()%R%R[iter(setdiff(lattr(me),lattr(me/*|NOTE)),%B%B[itext(0)] -- [v(itext(0)|NOTE)],%b,%R)]
Owner: Nammyung(#1764PweAC) Zone: *NOTHING* Ducats: 10
Parent: *NOTHING*
Powers:
Warnings checked:
Created: Wed Jun 13 00:16:27 2007
Last Modification: Wed Jun 13 00:43:23 2007
BASESECS [#1764]: 1180009252
BASESECS|NOTE [#1764]: This holds the RL secs at which the calendar was reset. Used to calculate how much time in real life has passed since the start of the calendar.
CD [#1764]: mod(add(mul(remainder(div(%0,100),4),5),2),7)
CUR_TIME [#1764]: convsecs(add(u(startsecs),u(ic_delta),%0))
CUR_TIME|NOTE [#1764]: This is an intermediate value that is necessary to get around the fact that secs() doesn't go beyond 1970 in the past.
DAWN [#1764]: 6
DAWN|NOTE [#1764]: The hour at which sun rises and daytime begins.
DAYOFWEEK [#1764]: switch(mod(add(u(dd,last(%0)),mod(sub(first(rest(%0)),u(mdd,first(%0),last(%0))),7)),7),1,Mon,2,Tue,3,Wed,4,Thu,5,Fri,6,Sat,7,Sun,0,Sun,Error)
DAYOFWEEK|NOTE [#1764]: This function calculates the day of the week for any arbitrary date, even those outside the usual range of secs(). Dang, this was tough to make, y'all better be impressed.
DD [#1764]: add(div(mod(%0,100),12),mod(mod(%0,100),12),div(mod(mod(%0,100),12),4),u(cd,%0))
DELTA [#1764]: sub(secs(),u(basesecs))
DELTA|NOTE [#1764]: This holds the multiplication factor for in-game time. Factor of 2, for example, means that time flows twice as fast in-game as it does in real life.
FACTOR [#1764]: 2
FACTOR|NOTE [#1764]: This holds the multiplication factor for IC time. For example, for a factor of 2, the IC time flows twice as fast as RL time.
ICTIME [#1764]: u(dayofweek,elements(setr(1,index(setr(0,u(cur_time,%0)),%b,2,sub(words(%q0),2)) [sub(last(%q0),100)]),1 2 4)) %q1
ICTIME|NOTE [#1764]: This is the main public function that returns the IC time, in the same format as standard time() function. It can take an argument in number of seconds to returns the IC time in future or past. See +bbread 3/8 for examples.
IC_DATE [#1764]: extract(u(ictime),3)
IC_DATE|NOTE [#1764]: This returns the IC day of the month.
IC_DAY [#1764]: extract(u(ictime),3)
IC_DAY|NOTE [#1764]: This returns the IC day of the month.
IC_DELTA [#1764]: mul(v(factor),u(delta))
IC_DELTA|NOTE [#1764]: This returns how many seconds have passed in IC time.
IC_MONTH [#1764]: extract(u(ictime),2)
IC_MONTH|NOTE [#1764]: This returns the IC month of the year.
IC_TIME [#1764]: extract(u(ictime),4)
IC_TIME|NOTE [#1764]: This returns the IC time of the day, in 24-hour format.
IC_YEAR [#1764]: extract(u(ictime),5)
IC_YEAR|NOTE [#1764]: This returns the IC year.
ISDAY [#1764]: and(gte(setr(0,first(u(ic_time),:)),v(dawn)),lt(%q0,v(sunset)))
ISDAY|NOTE [#1764]: Boolean function.
ISLEAPYEAR [#1764]: cand(not(mod(%0,4)),nand(not(mod(%0,100)),mod(%0,400)))
ISLEAPYEAR|NOTE [#1764]: Boolean function.
ISNIGHT [#1764]: not(u(isday))
ISNIGHT|NOTE [#1764]: Boolean function.
MDD [#1764]: switch(%0,Jan,if(u(isleapyear,%1),4,3),Feb,2,Mar,7,Apr,4,May,9,Jun,6,Jul,11,Aug,8,Sep,5,Oct,10,Nov,7,Dec,12,Error)
STARTSECS [#1764]: convtime(v(start_month) [v(start_date)] [v(start_time)] [add(v(start_year),100)])
STARTSECS|NOTE [#1764]: This is used to come up with a fake secs() value for the IC starting date. The value returned is offset by 100 years - so for a starting date of Jan 1 1876, this returns the value for Jan 1 1976. We need to do this because standard secs() and convtime() cannot handle dates before Jan 1 1970. We compensate for this 100-year difference in the main function, ICTIME.
START_DATE [#1764]: 1
START_MONTH [#1764]: Jan
START_MONTH|NOTE [#1764]: This holds the month of the year that the IC time begins on when it is initialized.
START_TIME [#1764]: 12:00:00
START_TIME|NOTE [#1764]: This holds the time of the day that the IC time begins on when it is initialized.
START_YEAR [#1764]: 1876
START_YEAR|NOTE [#1764]: This holds the year that the IC time begins on when it is initialized.
SUNRISE [#1764]: 7
SUNSET [#1764]: 18
SUNSET|NOTE [#1764]: The hour at which sun sets and nighttime begins, in 24-hour format: 18 = 6pm.
TIMEOFDAY [#1764]: if(u(isday),day,night)
TIMEOFDAY|NOTE [#1764]: This function returns either day or night, depending on ictime(), DAWN, and SUNSET.
Home: Upstairs(#759RnJ)
Location: shelf(#5923Tnt)