DNA.
wyrm-tiktok
Version.
1.0.9
Namespace.
::wyrm
Command.
::wyrm::tiktok
Language.
c
Manpage.
tiktok (1WY)
Manpage.
wyrm_tiktokNewClockObj (3WY)
Manpage.
wyrm_tiktokDefine (3WY)
Manpage.
wyrm_tiktokAdd (3WY)
Manpage.
wyrm_tiktokFromCalendar (3WY)
Manpage.
wyrm_tiktokUTC (3WY)
Testbase.
Test Script
Test Report
Import.
Interface.
wyrmwif
Package.
wyrmwif
wyrm-io
Export.
Implementation.
wyrm-tiktok.c
Interface.
wyrm-tiktok.h
Package.
wyrmtiktok.dylib
System.
wyrm-tiktok.sys
wyrm-tiktok.sys
wyrm-tiktok.sys

Extended Time Functions

Sections.
Extended Clock Functions
Object
Base
Operations
Calendars
Time Corrections
Command interface
Make.
Package.
compile -cc -ld -o [
  export package
] [
  export implementation
] -- -list [import interface] [export interface] [export system]
Script.
rule wyrm-tiktok.package $so/wyrmtiktok[info sharedlibextension]
rule wyrm-tiktok.test [list \
    pkgIndex.tcl \
    $test/wyrm-tiktok.test.html \
]

rule clean :: {} "
  -rm $test/wyrm-tiktok.TESTING
"
rule clobber :: {} "
  -rm $include/wyrm-tiktok.h
  -rm $so/wyrmtiktok[info sharedlibextension]
"
   
top
1 ::
Lifetime of the universe
<=200 billion years (38 bits)
<=7.3e13 days (48 bits)
<=6.3e18 seconds (63 bits)

This package implements a 64-bit second value that allows each second in this universe to be given a unique identifier. This second number is combined with an 4 character time base name.

Copyright (C) 2004 SM Ryan

Wyrmwif Tcl extensions. For non-profit uses only, provided this copyright is preserved on all copies, this work may be freely copied, modified, redistributed, compiled, and incorporated in other works. This work is distributed with no warranty of any kind; no author or distributor accepts any responsibility for the consequences of using it, or for whether it serves any particular purpose or works at all, unless he or she says so in writing.

   
   

Extended Clock Functions

   
top
 
section    top
 
section    top
4. wyrm_tiktokDefine (3WY) ::

NAME

wyrm_tiktokDefine — Relations between different time bases.

description

<Description>

A WyrmTiktokObj is a Tcl object which encapsulates a date-time or duration.

<Time bases> <Time object relations (3WY)>

CAVEATS

While it is usually easy to find the absolute value of the difference of two time bases, it is also easy to get the sign wrong. It is important to accurately determine what is added or subtracted from what.

 
section    top
 
section    top
 
section    top
 
section    top
		
#ifndef WYRM_TIKTOK_H
#define WYRM_TIKTOK_H

	//	wyrm-tiktok.dna - Copyright (C) 2004 SM Ryan.  All rights reserved.

	#include "wyrmwif.h"
	
	<Time bases>
	<Time object external interface>
	<Time relation external interface>
	<Time operations external interface>
	<Calendar external interface>
	<Time corrections external interface>

	int Wyrmtiktok_Init(Intr intr);
	int Wyrmtiktok_SafeInit(Intr intr);

#endif

		
 
section    top

static const char COPYRIGHT[] =
		"wyrm-tiktok.dna - Copyright (C) 2004 SM Ryan.  All rights reserved.";

#include "wyrm-tiktok.h"
#include "wyrm-io.h"

#include <ctype.h>
#include <math.h>
#include <stdlib.h>
#include <string.h>

<Description>
<Time object>
<Seconds from Julian day 1>
<Time base hash table>
#include "wyrm-tiktok.sys"
<Encode and decode time objects for internal consumption>
<Time object creation and conversion (3WY)>
<Time object relations (3WY)>
<Time object operations (3WY)>
<Calendar conversions>
<Leap seconds and local time (3WY)>
<tiktok command>

		
 
section    top
 
section    top
 
section    top
 
section    top

typedef struct {long long seconds; long base;} TimeValue;

		
   
   

Object

   
top

static void freeWyrmTiktokObj(Obj obj);
static void dupWyrmTiktokObj(Obj src,Obj dup);
static void updateWyrmTiktokObj(Obj obj);
static int  setWyrmTiktokObj(Tcl_Interp *intr,Obj obj);

static Tcl_ObjType WyrmTiktokObj = {
	"wyrm.tiktok",
	freeWyrmTiktokObj,
	dupWyrmTiktokObj,
	updateWyrmTiktokObj,
	setWyrmTiktokObj,
};

static void freeWyrmTiktokObj(Obj obj) {
	dispose(obj->internalRep.otherValuePtr);
}

static void dupWyrmTiktokObj(Obj src,Obj dup) {
	TimeValue *ss = src->internalRep.otherValuePtr;
	TimeValue *dd = heap(TimeValue);
	*dd = *ss;
	dup->typePtr = &WyrmTiktokObj;
	dup->internalRep.otherValuePtr = dd;
}

static void updateWyrmTiktokObj(Obj obj) {
	TimeValue *clock = (TimeValue*)(obj->internalRep.otherValuePtr);
	char sec[27];
	char sign = clock->seconds<0 ? 'N' : 'P';
	long long seconds = clock->seconds<0 ? -clock->seconds : clock->seconds;
	char *sep = clock->base ? ":" : "",ba[5],*p = ba+4; long base = clock->base;
	*p = 0; while (base && p>ba) {*--p = base; base = (base>>8) & 0xFFFFFF;}
	sprintf(sec,"%c%020lld%s%s",sign,seconds,sep,p);
	if (sign=='N') for (p=sec+1; p<sec+20+1; p++) *p = '9'-(*p-'0');
	obj->length = strlen(sec);
	obj->bytes = nheap(obj->length+1,char);
	strcpy(obj->bytes,sec);
}

static int setWyrmTiktokObj(Tcl_Interp *intr,Obj obj) {
	if (obj->typePtr==&WyrmTiktokObj) return TCL_OK;
	chars p = obj->bytes; TimeValue t = {0,0};
	switch (*p) {
		case 'P':
			p++;
		case '0': case '1': case '2': case '3': case '4':
		case '5': case '6': case '7': case '8': case '9':
		case '+': case '-':
			t.seconds = strtoll(p,&p,10);
			break;
		case 'N': {
			chars complement = strcpy(nheap(strlen(p)+1,char),p),d;
			for (d=complement+1; isdigit(*d); d++) *d = '9'-(*d-'0');
			t.seconds = -strtoll(complement+1,&d,10);
			p += (d-complement);
			dispose(complement);
		}	break;
		default:
			return rprintf(intr,
					"%!invalid time representation: does not start with integer or nines-complement",
					TCL_ERROR);
	}
	switch (*p) {
		case 0: break;
		case ':': {
			int k;
			for (k=1,p++; *p && k<=4; k++,p++) t.base = (t.base<<8) | (*p&0xFF);
			if (*p) {
				return rprintf(intr,
						"%!invalid time representation: base name is more than four characters",
						TCL_ERROR);
			}
			if (!t.base) {
				return rprintf(intr,
						"%!invalid time representation: base name is empty",
						TCL_ERROR);
			}
		}   break;
		default:
			return rprintf(intr,
					"%!invalid time representation: seconds not followed by ':'",
					TCL_ERROR);
	}
	if (obj->typePtr && obj->typePtr->freeIntRepProc) obj->typePtr->freeIntRepProc(obj);
	obj->typePtr = &WyrmTiktokObj;
	TimeValue *T = heap(TimeValue); *T = t;
	obj->internalRep.otherValuePtr = T;
	return TCL_OK;
}

		
 
section    top

static int decodeObj(Intr intr,Obj clock,TimeValue *t) {
	if (clock->typePtr!=&WyrmTiktokObj && Tcl_ConvertToType(intr,clock,&WyrmTiktokObj)!=TCL_OK)
		return TCL_ERROR;
	*t = *((TimeValue*)(clock->internalRep.otherValuePtr));
	return TCL_OK;
}

static Obj encodeObj(TimeValue t) {
	Obj obj = Tcl_NewObj();
	if (obj->typePtr && obj->typePtr->freeIntRepProc) obj->typePtr->freeIntRepProc(obj);
	obj->typePtr = &WyrmTiktokObj;
	TimeValue *T = heap(TimeValue); *T = t;
	obj->internalRep.otherValuePtr = T;
	Tcl_InvalidateStringRep(obj);
	return obj;
}

		
 
section    top
 
section    top

case o_base:
	if (N==3) {
		long base;
		int rc = wyrm_tiktokGetClockFromObj(intr,P[2],0,&base,wyrm_tiktokUnspecified);
		if (rc==TCL_OK) rprintf(intr,"%{i}s",base);
		return rc;
	}else {
		Tcl_WrongNumArgs(intr,2,P,"date-time");
		return TCL_ERROR;
	}
case o_seconds:
	if (N==3) {
		long long secs;
		int rc = wyrm_tiktokGetClockFromObj(intr,P[2],&secs,0,wyrm_tiktokUnspecified);
		if (rc==TCL_OK)  Tcl_SetObjResult(intr,Tcl_NewWideIntObj(secs));
		return rc;
	}else if (N==4) {
		long long secs;
		int rc = wyrm_tiktokGetClockFromObj(intr,P[2],&secs,0,wyrm_stringToC(Tcl_GetString(P[3])));
		if (rc==TCL_OK) Tcl_SetObjResult(intr,Tcl_NewWideIntObj(secs));
		return rc;
	}else {
		Tcl_WrongNumArgs(intr,2,P,"date-time");
		return TCL_ERROR;
	}

		
    Time object extraction and conversion.
      Extract base from a clock reference.
        Extract base from a date-time.
          CLK200
        Extract base from a duration.
          CLK201
        Extract base from an invalid clock.
          CLK202
      Extract seconds from a clock reference.
        Extract seconds from a date-time.
          CLK210
        Extract seconds from a duration.
          CLK211
        Extract seconds from an invalid clock.
          CLK212
      Convert base on a clock reference.
        Convert seconds from a date-time.
          Bases are related.
            CLK220
          Bases are not related.
            CLK221
        Convert seconds from a duration.
          CLK222
 
section    top

Obj wyrm_tiktokNewClockObj(Intr intr,long long secs,long base) {
	if (base==wyrm_tiktokUnspecified) base = 'syst';
	TimeValue t = {secs,base};
	return encodeObj(t);
}

int wyrm_tiktokGetClockFromObj(Intr intr,Obj clock,long long *secs,long *base,long reqbase) {
	TimeValue t; long long plus;
	if (decodeObj(intr,clock,&t)!=TCL_OK) return TCL_ERROR;
	if (reqbase!=wyrm_tiktokUnspecified) {
		if (reqbase==0 && t.base!=0)
			return rprintf(intr,"%!cannot convert a date-time into a duration",TCL_ERROR);
		if (reqbase!=0 && t.base==0)
			return rprintf(intr,"%!cannot convert a duration into a date-time",TCL_ERROR);
		if (reqbase!=t.base) {
			if (baseRelation(intr,t.base,&plus,reqbase)==TCL_OK) {
				t.seconds += plus; t.base = reqbase;
				*((TimeValue*)(clock->internalRep.otherValuePtr)) = t;
				Tcl_InvalidateStringRep(clock);
			}else
				return TCL_ERROR;
		}
	}
	if (secs) *secs = t.seconds;
	if (base) *base = t.base;
	return TCL_OK;
}

		
 
section    top

Obj wyrm_tiktokNewClockObj(Intr intr,long long secs,long base);
int wyrm_tiktokGetClockFromObj(Intr intr,Obj clock,long long *secs,long *base,long reqbase);

		
   
   

Base

   
top

20. Time bases :: Date-times are given relative to a base designation, a one to four character name. The base designations are packed into a long integer value. The base values 0 and -1 are reserved and cannot be used. Base 0 is used to indicate the clock is a duration rather than a date-time; base -1 used to indicate a missing or default base on some calls. The system clock base, the same value returned by [clock seconds] has the base value 'syst'.

If the system clock can be related to Greenwich Mean Time, additional bases are defined

zulu
Is Greenwich Mean Time.
unix
The unix base time, 1 January 1970 GMT.
cMac
The Mac Classic base time, 1 January 1904 GMT.
JDAY
Julian day 1. This time base needs to be defined to do calendar conversions.
1AD
1 January AD 1, relative to 'JDAY'.
here
is local time, either the system base or relative to GMT.

The difference between 'here' and 'zulu' should be current timezone difference only.


enum {
	wyrm_tiktokDuration = 0,
	wyrm_tiktokUnspecified = -1,
	wyrm_tiktokSystem = 'syst'
};

		
 
section    top

#define secondsPerDay 86400LL
#define halfDay 43200LL
#define jan1_1904 (2416480LL*secondsPerDay+halfDay)
#define jan1_1970 (2440587LL*secondsPerDay+halfDay)
#define dec31_0 (1721424LL*secondsPerDay+halfDay)

		
 
section    top

#include <time.h>

static void initializeBases(void) {
	defineRelation(0,'unix',0,'syst');
	defineRelation(0,'zulu',0,'syst');
	defineRelation(0,'JDAY',-jan1_1970,'syst');
	defineRelation(0,'1AD',dec31_0,'JDAY');
	time_t now = time(0);
	struct tm *lo = localtime(&now);
	long base = 0; int k; chars p = lo->tm_zone;
	for (k=1; k<=4 && *p; k++,p++) base = (base<<8) | (*p&0xFF);
	defineRelation(0,'here',lo->tm_gmtoff,'zulu');
	defineRelation(0,base,0,'here');
	defineRelation(0,'cMac',jan1_1970-jan1_1904,'here');
}

		
 
section    top

#include <time.h>

static void initializeBases(void) {
	defineRelation(0,'unix',0,'syst');
	defineRelation(0,'zulu',0,'syst');
	defineRelation(0,'JDAY',-jan1_1970,'syst');
	defineRelation(0,'1AD',dec31_0,'JDAY');
	time_t now = time(0);
	struct tm *lo = localtime(&now);
	long base = 0; int k; chars p = tzname[!!daylight];
	for (k=1; k<=4 && *p; k++,p++) base = (base<<8) | (*p&0xFF);
	defineRelation(0,'here',timezone,'zulu');
	defineRelation(0,base,0,'here');
	defineRelation(0,'cMac',jan1_1970-jan1_1904,'here');
}

		
 
section    top
 
section    top

static TimeValue findFundamental(long base) {
	Tcl_HashTable *(*baseDefinitions) = Tcl_GetThreadData(&tiktokKey,sizeof(Tcl_HashTable*));
	Tcl_HashEntry *e = (*baseDefinitions) ? Tcl_FindHashEntry((*baseDefinitions),(chars)base) : 0;
	TimeValue *definition = e ? (TimeValue*)Tcl_GetHashValue(e) : 0;
	if (definition && definition->base!=base) {
		TimeValue fundamental = findFundamental(definition->base);
		definition->seconds += fundamental.seconds;
		definition->base = fundamental.base;
		return *definition;
	}else {
		TimeValue self = {0,base}; return self;
	}
}

		
 
section    top

26. Define one base with relative to another :: Define a base as equal to another plus some number of seconds. (Or minus if the seconds are negative.) If both are already defined relative to a fundamental (themselves implicitly if no explicit definition). If they are defined relative to the same fundamental, this code verifies the relation is consistent with any previous definition. Otherwise the fundamental of one is defined in terms of the fundamental of the other.

The fundamental references form a tree with the fundamental at the root, and nonfundamental bases at the leaves and intermediate nodes. Intermediate nodes are previous fundamentals that have been linked here. The find routine will flatten out the tree again on subsequent references.

Returns TCL_OK if the definition is consistent with previous definitions. Otherwise it returns TCL_ERROR and leaves an error message.


static int defineRelation(Intr intr,long x,long long plus,long y) {
	Tcl_HashTable *(*baseDefinitions) = Tcl_GetThreadData(&tiktokKey,sizeof(Tcl_HashTable*));
	if (x==wyrm_tiktokUnspecified || y==wyrm_tiktokUnspecified)
		return rprintf(intr,"%!defineRelation called with unspecified-base",TCL_ERROR);
	if (x==0 || y==0)
		return rprintf(intr,"%!durations cannot be defined relative another base",TCL_ERROR);
	TimeValue X = findFundamental(x);
	TimeValue Y = findFundamental(y);
	if (!(*baseDefinitions)) {
		(*baseDefinitions) = heap(Tcl_HashTable);
		Tcl_InitHashTable((*baseDefinitions),TCL_ONE_WORD_KEYS);
	}
	if (X.base==Y.base) {
		return X.seconds==plus+Y.seconds
				? TCL_OK
				: rprintf(intr,"%!inconsistent base definition %{i}s is not %.0f+%{i}s",
						TCL_ERROR,x,(double)plus,y);
	}else if (X.base==x || Y.base!=y) {
		int new; Tcl_HashEntry *e = Tcl_CreateHashEntry((*baseDefinitions),(chars)(X.base),&new);
		TimeValue *px;
		if (new) {
			px = heap(TimeValue);
			Tcl_SetHashValue(e,(ClientData)px);
		}else {
			px = (TimeValue*)Tcl_GetHashValue(e);
		}
		*px = Y; px->seconds += plus;
	}else {
		int new; Tcl_HashEntry *e = Tcl_CreateHashEntry((*baseDefinitions),(chars)(Y.base),&new);
		TimeValue *py;
		if (new) {
			py = heap(TimeValue);
			Tcl_SetHashValue(e,(ClientData)py);
		}else {
			py = (TimeValue*)Tcl_GetHashValue(e);
		}
		*py = X; py->seconds -= plus;
	}
	return TCL_OK;
}

		
 
section    top

static int baseRelation(Intr intr,long x,long long *plus,long y) {
	if (x==wyrm_tiktokUnspecified || y==wyrm_tiktokUnspecified)
		return rprintf(intr,"%!baseRelation called with unspecified-base",TCL_ERROR);
	TimeValue X = findFundamental(x);
	TimeValue Y = findFundamental(y);
	if (X.base==Y.base) {
		*plus = X.seconds-Y.seconds;
		return TCL_OK;
	}else if (x==0 || y==0) {
		return rprintf(intr,"%!durations cannot be related to date-times",TCL_ERROR);
	}else {
		return rprintf(intr,"%!date-times cannot be related: %{i}s and %{i}s",TCL_ERROR,x,y);
	}
}

		
 
section    top
 
section    top

case o_define:
	if (N==4) {
		int rc = wyrm_tiktokDefine(intr,P[2],P[3]);
		if (rc==TCL_OK) Tcl_ResetResult(intr);
		return rc;
	}else {
		Tcl_WrongNumArgs(intr,2,P,"date-time-1 date-time-2");
		return TCL_ERROR;
	}

case o_relation:
	if (N==4) {
		int rc = wyrm_tiktokRelation(intr,P[2],P[3]);
		return rc;
	}else {
		Tcl_WrongNumArgs(intr,2,P,"date-time-1 date-time-2");
		return TCL_ERROR;
	}

		
    Time object relations.
      Predefined relations.
        syst.
          CLK100
        unix.
          CLK101
        zulu.
          CLK102
        1AD.
          CLK103
        here.
          CLK104
        Local timezone.
          CLK105
        cMac.
          CLK106
      New relations relations.
        New definitions.
          CLK110
        Consistent direct definitions.
          CLK111
        Consistent indirect definitions.
          CLK112
        Inconsistent direct definitions.
          CLK113
        Inconsistent indirect definitions.
          CLK114
 
section    top

int wyrm_tiktokDefine(Intr intr,Obj time1,Obj time2) {
	TimeValue x,y; long long plus;
	if (decodeObj(intr,time1,&x)!=TCL_OK) return TCL_ERROR;
	if (decodeObj(intr,time2,&y)!=TCL_OK) return TCL_ERROR;
	plus = y.seconds-x.seconds;
	if (defineRelation(intr,x.base,plus,y.base)!=TCL_OK) return TCL_ERROR;
	return TCL_OK;
}

int wyrm_tiktokRelation(Intr intr,Obj time1,Obj time2) {
	TimeValue x,y; long long plus;
	if (decodeObj(intr,time1,&x)!=TCL_OK) return TCL_ERROR;
	if (decodeObj(intr,time2,&y)!=TCL_OK) return TCL_ERROR;
	if (baseRelation(intr,x.base,&plus,y.base)!=TCL_OK) return TCL_ERROR;
	plus += y.seconds-x.seconds;
	Tcl_SetObjResult(intr,Tcl_NewWideIntObj(plus));
	return TCL_OK;
}

		
 
section    top

int wyrm_tiktokDefine(Intr intr,Obj time1,Obj time2);
int wyrm_tiktokRelation(Intr intr,Obj time1,Obj time2);

		
   
   

Operations

   
top
 
section    top

33. Time object operations (1WY) :: tiktok add adds two durations or adds a duration to a date-time. Two date-times cannot be added.

tiktok subtract subtracts two durations, subtracts a duration from a date-time, or subtracts two date-times to get the duration between them. A date-time be subtracted from a duration. If two date-times have no defined relation, an error is returned.

tiktok symetricsubtract subtracts two date-times and returns a two element list. The first element is true if the first date-time is earlier, false if later. The second element is the absolute value of the duration between the date-times. If two date-times have no defined relation, an error is returned.

tiktok scale scales a duration (multplies by) by a scalar factor. A date-time cannot be scaled.

tiktok maximum returns the later of two date-times or the longer of two durations. A date-time cannot be compared to a duration. A negative duration is less than any positive duration. If two date-times have no defined relation, an error is returned.

tiktok minimum returns the earlier of two date-times or the shorter of two durations. A date-time cannot be compared to a duration. A negative duration is less than any positive duration. If two date-times have no defined relation, an error is returned.

tiktok compare compares two date-times or two durations. A date-time cannot be compared to a duration. Errors are not returned; 0 is returned for undefined comparions. Errors can be detected by calling tiktok subtract. A negative duration is less than any positive duration.


case o_add:
	if (N==4) {
		Obj rs = wyrm_tiktokAdd(intr,P[2],P[3]);
		if (rs) {Tcl_SetObjResult(intr,rs); return TCL_OK;}
		else return TCL_ERROR;
	}else {
		Tcl_WrongNumArgs(intr,2,P,"clock-1 clock-2");
		return TCL_ERROR;
	}
case o_subtract:
	if (N==4) {
		Obj rs = wyrm_tiktokSub(intr,P[2],P[3]);
		if (rs) {Tcl_SetObjResult(intr,rs); return TCL_OK;}
		else return TCL_ERROR;
	}else {
		Tcl_WrongNumArgs(intr,2,P,"clock-1 clock-2");
		return TCL_ERROR;
	}
case o_symetricsubtract: case o_symsubtract:
	if (N==4) {
		Obj rs = wyrm_tiktokSymSub(intr,P[2],P[3]);
		if (rs) {Tcl_SetObjResult(intr,rs); return TCL_OK;}
		else return TCL_ERROR;
	}else {
		Tcl_WrongNumArgs(intr,2,P,"clock-1 clock-2");
		return TCL_ERROR;
	}
case o_scale:
	if (N==4) {
		double factor; if (Tcl_GetDoubleFromObj(intr,P[3],&factor)!=TCL_OK) return TCL_ERROR;
		Obj rs = wyrm_tiktokScale(intr,P[2],factor);
		if (rs) {Tcl_SetObjResult(intr,rs); return TCL_OK;}
		else return TCL_ERROR;
	}else {
		Tcl_WrongNumArgs(intr,2,P,"clock-1 clock-2");
		return TCL_ERROR;
	}
case o_maximum:
	if (N==4) {
		Obj rs = wyrm_tiktokMax(intr,P[2],P[3]);
		if (rs) {Tcl_SetObjResult(intr,rs); return TCL_OK;}
		else return TCL_ERROR;
	}else {
		Tcl_WrongNumArgs(intr,2,P,"clock-1 clock-2");
		return TCL_ERROR;
	}
case o_minimum:
	if (N==4) {
		Obj rs = wyrm_tiktokMin(intr,P[2],P[3]);
		if (rs) {Tcl_SetObjResult(intr,rs); return TCL_OK;}
		else return TCL_ERROR;
	}else {
		Tcl_WrongNumArgs(intr,2,P,"clock-1 clock-2");
		return TCL_ERROR;
	}
case o_compare: case o_cmp:
	if (N==4) {
		Tcl_SetObjResult(intr,Tcl_NewIntObj(wyrm_tiktokCmp(intr,P[2],P[3])));
		return TCL_OK;
	}else {
		Tcl_WrongNumArgs(intr,2,P,"clock-1 clock-2");
		return TCL_ERROR;
	}

		
    Inconsistent indirect definitions.
      Addition.
        Date-time and date-time.
          CLK300
        Date-time and duration.
          CLK301
        Duration and date-time.
          CLK302
        Duration and duration.
          CLK303
        Nonclocks.
          CLK304
      Subtraction.
        Date-time and date-time.
          CLK310
        Date-time and duration.
          CLK311
        Duration and date-time.
          CLK312
        Duration and duration.
          CLK313
        Nonclocks.
          CLK314
      Symetric subtraction.
        Date-time and date-time.
          CLK320
        Date-time and duration.
          CLK321
        Duration and date-time.
          CLK322
        Duration and duration.
          CLK323
        Nonclocks.
          CLK324
      Scale.
        Date-time and real number.
          CLK330
        Date-time and not a real number.
          CLK331
        Duration and real number.
          CLK332
        Duration and not a real number.
          CLK333
        Nonclocks.
          CLK334
      Maximum.
        Date-time and date-time.
          CLK340
        Date-time and duration.
          CLK341
        Duration and date-time.
          CLK342
        Duration and duration.
          CLK343
        Nonclocks.
          CLK344
      Minimum.
        Date-time and date-time.
          CLK350
        Date-time and duration.
          CLK351
        Duration and date-time.
          CLK352
        Duration and duration.
          CLK353
        Nonclocks.
          CLK354
      Compare.
        Date-time and date-time.
          CLK360
        Date-time and duration.
          CLK361
        Duration and date-time.
          CLK362
        Duration and duration.
          CLK363
        Nonclocks.
          CLK364
 
section    top

34. Time object operations (3WY) :: wyrm_tiktokAdd adds two durations or adds a duration to a date-time. Two date-times cannot be added.

wyrm_tiktokSub subtracts two durations, subtracts a duration from a date-time, or subtracts two date-times to get the duration between them. A date-time be subtracted from a duration. If two date-times have no defined relation, an error is returned.

wyrm_tiktokSymSub subtracts two date-times and returns a two element list. The first element is true if the first date-time is earlier, false if later. The second element is the absolute value of the duration between the date-times. If two date-times have no defined relation, an error is returned.

wyrm_tiktokScale scales a duration (multplies by) by a scalar factor. A date-time cannot be scaled.

wyrm_tiktokMax returns the later of two date-times or the longer of two durations. A date-time cannot be compared to a duration. A negative duration is less than any positive duration. If two date-times have no defined relation, an error is returned.

wyrm_tiktokMin returns the earlier of two date-times or the shorter of two durations. A date-time cannot be compared to a duration. A negative duration is less than any positive duration. If two date-times have no defined relation, an error is returned.

Errors are indicated in all of the above by returning NULL instead of a time object. Returned objects have a reference count of zero, or they are an input object without the reference count incremented.

wyrm_tiktokCmp compares two date-times or two durations. A date-time cannot be compared to a duration. Errors are not returned; 0 is returned for undefined comparions. Errors can be detected by calling wyrm_tiktokSub. A negative duration is less than any positive duration.


Obj wyrm_tiktokAdd(Intr intr,Obj X,Obj Y) {
	TimeValue x,y;
	if (decodeObj(intr,X,&x)!=TCL_OK) return 0;
	if (decodeObj(intr,Y,&y)!=TCL_OK) return 0;
	if (x.base && y.base) {
		rprintf(intr,"cannot add two date-times"); return 0;
	}else if (!x.base && y.base) {
		y.seconds += x.seconds; return encodeObj(y);
	}else {
		x.seconds += y.seconds; return encodeObj(x);
	}
}

Obj wyrm_tiktokSub(Intr intr,Obj X,Obj Y) {
	TimeValue x,y;
	if (decodeObj(intr,X,&x)!=TCL_OK) return 0;
	if (decodeObj(intr,Y,&y)!=TCL_OK) return 0;
	if (x.base && y.base) {
		long long plus; if (baseRelation(intr,x.base,&plus,y.base)!=TCL_OK) return 0;
		//  X - Y
		//		= (x.base + x.seconds) - (y.base + y.seconds)
		//				x.base = plus + y.base
		//		= (plus+y.base + x.seconds) - (y.base + y.seconds)
		//		= plus + x.seconds - y.seconds + y.base - y.base
		//		= plus + x.seconds - y.seconds
		x.base = 0;
		x.seconds = plus+x.seconds-y.seconds;
		return encodeObj(x);
	}else if (!x.base && y.base) {
		rprintf(intr,"cannot subtract a date-time from a duration"); return 0;
	}else {
		x.seconds -= y.seconds; return encodeObj(x);
	}
}

Obj wyrm_tiktokSymSub(Intr intr,Obj X,Obj Y) {
	TimeValue x,y;
	if (decodeObj(intr,X,&x)!=TCL_OK) return 0;
	if (decodeObj(intr,Y,&y)!=TCL_OK) return 0;
	if ((x.base==0) == (y.base==0)) {
		TimeValue r; Obj E[2];
		long long plus; if (baseRelation(intr,x.base,&plus,y.base)!=TCL_OK) return 0;
		r.base = 0;
		r.seconds = plus+x.seconds-y.seconds;
		bool earlier = r.seconds<0;
		if (earlier) r.seconds = -r.seconds;
		E[0] = Tcl_NewBooleanObj(earlier);
		E[1] = encodeObj(r);
		return Tcl_NewListObj(2,E);
	}else {
		rprintf(intr,"cannot symetrically subtract a date-time and a duration"); return 0;
	}
}

Obj wyrm_tiktokScale(Intr intr,Obj X,double factor) {
	TimeValue x;
	if (decodeObj(intr,X,&x)!=TCL_OK) return 0;
	if (x.base) {
		rprintf(intr,"cannot scale a date-time"); return 0;
	}else {
		x.seconds *= factor; return encodeObj(x);
	}
}

int wyrm_tiktokCmp(Intr intr,Obj X,Obj Y) {
	TimeValue x,y;
	if (decodeObj(intr,X,&x)!=TCL_OK) return 0;
	if (decodeObj(intr,Y,&y)!=TCL_OK) return 0;
	if ((x.base==0) == (y.base==0)) {
		long long plus; if (baseRelation(intr,x.base,&plus,y.base)!=TCL_OK) return 0;
		long long diff = plus+x.seconds-y.seconds;
		return diff<0 ? -1 : diff>0 ? 1 : 0;
	}else {
		rprintf(intr,"cannot compare a date-time and a duration"); return 0;
	}
}

Obj wyrm_tiktokMax(Intr intr,Obj X,Obj Y) {
	TimeValue x,y;
	if (decodeObj(intr,X,&x)!=TCL_OK) return 0;
	if (decodeObj(intr,Y,&y)!=TCL_OK) return 0;
	if ((x.base==0) == (y.base==0)) {
		long long plus; if (baseRelation(intr,x.base,&plus,y.base)!=TCL_OK) return 0;
		long long diff = plus+x.seconds-y.seconds;
		return diff>0 ? X : Y;
	}else {
		rprintf(intr,"cannot compare a date-time and a duration"); return 0;
	}
}

Obj wyrm_tiktokMin(Intr intr,Obj X,Obj Y) {
	TimeValue x,y;
	if (decodeObj(intr,X,&x)!=TCL_OK) return 0;
	if (decodeObj(intr,Y,&y)!=TCL_OK) return 0;
	if ((x.base==0) == (y.base==0)) {
		long long plus; if (baseRelation(intr,x.base,&plus,y.base)!=TCL_OK) return 0;
		long long diff = plus+x.seconds-y.seconds;
		return diff<0 ? X : Y;
	}else {
		rprintf(intr,"cannot compare a date-time and a duration"); return 0;
	}
}

		
 
section    top
	
Obj wyrm_tiktokAdd(Intr,Obj,Obj);
Obj wyrm_tiktokSub(Intr,Obj,Obj);
Obj wyrm_tiktokSymSub(Intr intr,Obj x,Obj y);
Obj wyrm_tiktokScale(Intr,Obj,double);
Obj wyrm_tiktokMax(Intr,Obj,Obj);
Obj wyrm_tiktokMin(Intr,Obj,Obj);
int wyrm_tiktokCmp(Intr,Obj,Obj);

		
   
   

Calendars

   
top

36. Calendar conversions :: This code is adapted from javascript at fourmilab.

A number of calendar conversions are provided to and from a date-time. Calendar dates are represented as a calendar name (one or more letters), a dot, a leap indicator, a dot, and various dot separated integers. The last three integers are hour, minute, and seconds from start of day. The other interpretation of the other integers is calendar dependent. The leap indicator is '1' if this is a leap year, otherwise an '0'.

The calendar calculations do not include local timezone or other modifications. These are handled by adding or subtracting corrective durations to the date-time before and after calendar conversions. Not all calendars define the start of day as midnight: the hours given are the hours from the calendar's start of day.

	
static const double J0000 = 1721424.5;                // Julian date of Gregorian epoch: 0000-01-01
static const double J1970 = 2440587.5;                // Julian date at Unix epoch: 1970-01-01
static const double JMJD  = 2400000.5;                // Epoch of Modified Julian Date system
static const double J1900 = 2415020.5;                // Epoch (day 1) of Excel 1900 date system (PC)
static const double J1904 = 2416480.5;                // Epoch (day 0) of Excel 1904 date system (Mac)

/*  MOD  --  Modulus function which works for non-integers.  */

static double mod(double a,double b) {
	return a - (b * floor(a / b));
}

//  AMOD  --  Modulus function which returns numerator if modulus is zero

static double amod(double a,double b) {
	return mod(a - 1, b) + 1;
}

//  JWDAY  --  Calculate day of week from Julian day

static int jwday(double j) {
	return mod((j + 1.5),7);
}

static long nextLong(chars r,chars *v) {
	if (r) {
		while (!isdigit(*r) && *r) r++;
		if (!*r) r = 0;
	}
	long x = r ? strtol(r,v,10) : 0;
	if (!r && v) *v = 0;
	return x;
}

static long long nextWideInt(chars r,chars *v) {
	if (r) {
		while (!isdigit(*r) && *r) r++;
		if (!*r) r = 0;
	}
	long long x = r ? strtoll(r,v,10) : 0;
	if (!r && v) *v = 0;
	return x;
}

<Day of week utilities>
<Durations calendar>
<Gregorian calendar>
<ISO week calendar>
<ISO day of year calendar>
<Julian day number>
<Julian (Roman) calendar>
<Hebrew calendar>
<Islamic lunar calendar>
<Persian calendar>
<Mayan calendars>
<Bahai calendar>
<Indian civil calendar>
<Discordian calendar>
<Easter (3WY)>
<Convert from calendar to date-time (3WY)>
<Convert from date-time to calendar (3WY)>

		
    Calendar conversions.
      Duration.
        To Duration.
          CLK400
        From Duration.
          CLK401
      Gregorian.
        To Gregorian.
          CLK405
        From Gregorian.
          CLK406
        Gregorian calendar Easter.
          CLK407
      Julian day.
        To Julian day.
          CLK410
        From Julian day.
          CLK411
      Julian.
        To Julian.
          CLK415
        From Julian.
          CLK416
        Julian calendar Easter.
          CLK417
      Hebrew.
        To Hebrew.
          CLK420
        From Hebrew.
          CLK421
      Islamic.
        To Islamic.
          CLK425
        From Islamic.
          CLK426
      Persian.
        To Persian.
          CLK430
        From Persian.
          CLK431
      Mayan.
        To Mayan.
          CLK435
        From Mayan.
          CLK436
      Bahai.
        To Bahai.
          CLK440
        From Bahai.
          CLK441
      Indian civil.
        To Indian civil.
          CLK445
        From Indian civil.
          CLK446
      ISO week.
        To ISO week.
          CLK455
        From ISO week.
          CLK456
      ISO day.
        To ISO day.
          CLK460
        From ISO day.
          CLK461
      Discordian.
        To Discordian.
          CLK465
        From Discordian.
          CLK466
      Unknown.
        To ZZTOP.
          CLK495
        From ZZTOP.
          CLK496
 
section    top
 
section    top

case o_clock:
	if (N==3) {
		Obj rs = wyrm_tiktokFromCalendar(intr,P[2]);
		if (rs) {Tcl_SetObjResult(intr,rs); return TCL_OK;}
		else return TCL_ERROR;
	}else {
		Tcl_WrongNumArgs(intr,2,P,"date-time-1 date-time-2");
		return TCL_ERROR;
	}
case o_calendar:
	if (N==4) {
		long type = wyrm_stringToC(Tcl_GetString(P[3]));
		Obj rs = wyrm_tiktokToCalendar(intr,P[2],type);
		if (rs) {Tcl_SetObjResult(intr,rs); return TCL_OK;}
		else return TCL_ERROR;
	}else {
		Tcl_WrongNumArgs(intr,2,P,"date-time-1 date-time-2");
		return TCL_ERROR;
	}

		
 
section    top
		
Obj wyrm_tiktokFromCalendar(Intr intr,Obj calendar) {
	chars p = Tcl_GetString(calendar);
	int k; long type = 0;
	for (k=1; k<=4 && isalpha(*p); k++,p++) type = (type<<8)|(*p&0xFF);
	int leap = nextLong(p,&p);
	double jdate = 0; long long extraseconds = 0;
	switch (type) {
		<Convert from calendar to date-time>
		default: rprintf(intr,"unknown calendar type: %{i}s",type); return 0;
	}
	long long jdsecs = floor(jdate*secondsPerDay);
	int hours = nextLong(p,&p);
	int minutes = nextLong(p,&p);
	int seconds = nextLong(p,0);
	jdsecs += 3600*hours + 60*minutes + seconds + extraseconds;
	TimeValue t = {jdsecs,'JDAY'};
	return encodeObj(t);
}

		
 
section    top

Obj wyrm_tiktokToCalendar(Intr intr,Obj datetime,long type) {
	TimeValue t; if (decodeObj(intr,datetime,&t)!=TCL_OK) return 0;
	long long o; double jdate; char r[500];
	long long jdsecs = t.seconds; bool standardclock = true;
	if (t.base==0) goto duration;
	if (baseRelation(intr,t.base,&o,'JDAY')!=TCL_OK) return 0;
	jdate = ((double)(t.seconds+o))/secondsPerDay;
	jdsecs += o;
	switch (type) {
		<Convert from date-time to calendar>
		default: rprintf(intr,"unknown calendar type: %{i}s",type); return 0;
	}
	if (standardclock) {
		int secs = jdsecs % secondsPerDay;
		if (secs<0) secs += secondsPerDay;
		int hours = secs/3600;
		int minutes = (secs/60)%60;
		secs = secs%60;
		sprintf(r+strlen(r),".%d.%d.%d",hours,minutes,secs);
	}
	return Tcl_NewStringObj(r,-1);
}

		
 
section    top
	
Obj wyrm_tiktokFromCalendar(Intr,Obj);
Obj wyrm_tiktokToCalendar(Intr,Obj,long type);

		
 
section    top


/*  WEEKDAY_BEFORE  --  Return Julian date of given weekday (0 = Sunday)
                        in the seven days ending on jd.  */

static int weekday_before(int weekday,double jd) {
	return jd - jwday(jd - weekday);
}

/*  SEARCH_WEEKDAY  --  Determine the Julian date for: 

            weekday      Day of week desired, 0 = Sunday
            jd           Julian date to begin search
            direction    1 = next weekday, -1 = last weekday
            offset       Offset from jd to begin search
*/

static int search_weekday(int weekday,double jd, int direction, int offset) {
	return weekday_before(weekday, jd + (direction * offset));
}

//  Utility weekday functions, just wrappers for search_weekday

static int nearest_weekday(int weekday,double jd) {
	return search_weekday(weekday, jd, 1, 3);
}

static int next_weekday(int weekday,double jd) {
	return search_weekday(weekday, jd, 1, 7);
}

static int next_or_current_weekday(int weekday,double jd) {
	return search_weekday(weekday, jd, 1, 6);
}

static int previous_weekday(int weekday,double jd) {
	return search_weekday(weekday, jd, -1, 1);
}

static int previous_or_current_weekday(int weekday,double jd) {
	return search_weekday(weekday, jd, 1, 0);
}

		
 
section    top
 
section    top

case 'dura': {
	long long day = nextWideInt(p,&p);
	int hours = nextLong(p,&p);
	int minutes = nextLong(p,&p);
	int seconds = nextLong(p,0);
	TimeValue t = {secondsPerDay*day + 3600*hours + 60*minutes + seconds,0};
	return encodeObj(t);
}

		
 
section    top

duration: {
	jdsecs = t.seconds;
	sprintf(r,"dura.0.%lld",jdsecs/secondsPerDay);
}   break;

		
 
section    top

//  LEAP_GREGORIAN  --  Is a given year in the Gregorian calendar a leap year ?

static bool leap_gregorian(long long year) {
	return ((year % 4) == 0) && (!(((year % 100) == 0) && ((year % 400) != 0)));
}

//  GREGORIAN_TO_JD  --  Determine Julian day number from Gregorian calendar date

static const double GREGORIAN_EPOCH = 1721425.5;

static double gregorian_to_jd(long year,long month,long day) {
	return (GREGORIAN_EPOCH - 1)
		+ (365 * (year - 1))
		+ floor((year - 1) / 4.0)
		- floor((year - 1) / 100.0)
		+ floor((year - 1) / 400.0)
		+ floor((((367 * month) - 362) / 12)
			+ ((month <= 2) ? 0 : (leap_gregorian(year) ? -1 : -2))
			+ day);
}

//  JD_TO_GREGORIAN  --  Calculate Gregorian calendar date from Julian day

static void jd_to_gregorian(double jd,long long *year,long *month,long *day) {
	double wjd = floor(jd - 0.5) + 0.5;
	long long depoch = wjd - GREGORIAN_EPOCH;
	long long quadricent = floor(depoch / 146097.0);
	long long dqc = mod(depoch, 146097.0);
	long long cent = floor(dqc / 36524.0);
	long long dcent = mod(dqc, 36524.0);
	long long quad = floor(dcent / 1461.0);
	long long dquad = mod(dcent, 1461.0);
	int yindex = floor(dquad / 365.0);
	long long y = (quadricent * 400) + (cent * 100) + (quad * 4) + yindex;
	if (!((cent == 4) || (yindex == 4))) y++;
	long yearday = wjd - gregorian_to_jd(y, 1, 1);
	long leapadj = (wjd < gregorian_to_jd(y, 3, 1)) ? 0 : leap_gregorian(y) ? 1 : 2;
	long m = floor((((yearday + leapadj) * 12.0) + 373) / 367);
	long d = (wjd - gregorian_to_jd(y,m,1)) + 1;
	*year = y;
	*month = m;
	*day = d;
}

		
 
section    top

case 'GREG': {
	long long year = nextWideInt(p,&p);
	long month = nextLong(p,&p);
	long day = nextLong(p,&p);
	if (year<0) ++year;
	jdate = gregorian_to_jd(year,month,day);
}   break;

		
 
section    top

case 'GREG': {
	long long year; long month,day; jd_to_gregorian(jdate,&year,&month,&day);
	if (year<=0) --year;
	sprintf(r,"GREG.%d.%lld.%ld.%ld",leap_gregorian(year),year,month,day);
	jdsecs += halfDay;
}   break;

		
 
section    top

//  ISO_TO_JULIAN  --  Return Julian day of given ISO year, week, and day

static int n_weeks(int weekday,double jd,int nthweek) {
	int j = 7 * nthweek;
	if (nthweek > 0) {
		j += previous_weekday(weekday, jd);
	} else {
		j += next_weekday(weekday, jd);
	}
	return j;
}

static double iso_to_julian(long long year,long week,long day) {
	return day + n_weeks(0, gregorian_to_jd(year - 1, 12, 28), week);
}

//  JD_TO_ISO  --  Return array of ISO (year, week, day) for Julian day

static void jd_to_iso(double jd,long long *year,long *week,long *day) {
	long month,w,d; long long y;
	jd_to_gregorian(jd - 3,&y,&month,&d);
	if (jd >= iso_to_julian(y + 1, 1, 1)) (y)++;
	w = floor((jd - iso_to_julian(y, 1, 1)) / 7) + 1;
	d = jwday(jd);
	if (d == 0) d = 7;
	*year = y;
	*week = w;
	*day = d;
}

		
 
section    top

case 'ISOW': {
	long long year = nextWideInt(p,&p);
	int week = nextLong(p,&p);
	long day = nextLong(p,&p);
	if (year<0) ++year;
	jdate = iso_to_julian(year,week,day) + 0.5;
}   break;

		
 
section    top

case 'ISOW': {
	long long year; long week,day; jd_to_iso(jdate,&year,&week,&day);
	sprintf(r,"ISOW.0.%lld.%ld.%ld",year,week,day);
	jdsecs += halfDay;
}   break;

		
 
section    top

//  ISO_DAY_TO_JULIAN  --  Return Julian day of given ISO year, and day of year

static double iso_day_to_julian(long  long year,long day) {
	return (day - 1) + gregorian_to_jd(year, 1, 1);
}

//  JD_TO_ISO_DAY  --  Return array of ISO (year, day_of_year) for Julian day

static void jd_to_iso_day(double jd,long long *year,long *day) {
	long long y; long month,d;
	jd_to_gregorian(jd,&y,&month,&d);
	d = floor(jd - gregorian_to_jd(y, 1, 1)) + 1;
	*year = y;
	*day = d;
}

		
 
section    top

case 'ISOD': {
	long long year = nextWideInt(p,&p);
	long day = nextLong(p,&p);
	jdate = iso_day_to_julian(year,day);
}   break;

		
 
section    top

case 'ISOD': {
	long long year; long day; jd_to_iso_day(jdate,&year,&day);
	sprintf(r,"ISOD.0.%lld.%ld",year,day);
	jdsecs += halfDay;
}   break;

		
 
section    top
 
section    top

case 'JDAY': {
	long long daynumber = nextWideInt(p,&p);
	jdate