// twhen.c // // by John D. de Boer #include "twist.h" const int TIMERATIO = 4; // the number of time steps in a 'big' step (scrolling with shift key); // also equal to one half of the number of time steps shown // in the time-line window; TimeGlobs Time; // time information StepGlobs Step; // time-step information // "Day" and "Span" variables void InitDay(Day *D) { if (!D) return; D->kind=timeInval; D->guess=0; } void InitSpan(Span *S) { if (!S) return; S->start.kind=timeInval; S->end.kind=timeInval; } bool DatesEqual(const Day *A, const Day *B) { if (!A || !B) return 0; if (A->kind!=B->kind) return 0; if (A->kind==timeInval && B->kind==timeInval) return 1; if (A->jd!=B->jd || A->unc!=B->unc) return 0; return 1; } // Does .guess matter? bool DatesInOrder(const Day *A, const Day *B, bool Unc) { if (!A || A->kind==timeInval) return 0; if (!B || B->kind==timeInval) return 0; if (!Unc) return (A->jd<=B->jd); return (A->jd + A->unc <= B->jd - B->unc); } bool DatesInStrictOrder(const Day *A, const Day *B, bool Unc) { if (!A || A->kind==timeInval) return 0; if (!B || B->kind==timeInval) return 0; if (!Unc) return (A->jdjd); return (A->jd + A->unc < B->jd - B->unc); } bool ZeroSpan(const Span *S) { if (!S) return 1; if (S->start.kind!=S->end.kind) return 0; if (S->start.kind!=timeJD) return 1; return S->start.jd==S->end.jd; } bool DateInSpan(const Day *D, const Span *S, bool Unc) { if (!D || !S) return 0; if (D->kind==timeInval) return 0; if (S->start.kind==timeInval || S->end.kind==timeInval) return 0; if (Unc) { if (D->jd + D->unc < S->start.jd - S->start.unc) return 0; if (S->end.jd + S->end.unc < D->jd - D->unc) return 0; } else { if (D->jdstart.jd || D->jd>S->end.jd) return 0; } return 1; } bool SpansOverlap(const Span *A, const Span *B, bool Unc) { bool AV,BV; if (!A || !B) return 0; AV=(A->start.kind!=timeInval && A->end.kind!=timeInval); BV=(B->start.kind!=timeInval && B->end.kind!=timeInval); if (!AV && !BV) return 0; if (!AV) { if (A->start.kind==timeJD) return DateInSpan(&(A->start),B,Unc); if (A->end.kind ==timeJD) return DateInSpan(&(A->end ),B,Unc); return 0; } if (!BV) { if (B->start.kind==timeJD) return DateInSpan(&(B->start),A,Unc); if (B->end.kind ==timeJD) return DateInSpan(&(B->end ),A,Unc); return 0; } if (A->end.kind==timeStill && B->end.kind==timeStill) return 1; if (A->end.kind==timeStill) return DatesInOrder(&(A->start),&(B->end),Unc); if (B->end.kind==timeStill) return DatesInOrder(&(B->start),&(A->end),Unc); if (Unc) { if (A->end.jd + A->end.unc < B->start.jd - B->start.unc) return 0; if (B->end.jd + B->end.unc < A->start.jd - A->start.unc) return 0; } else { if (A->end.jdstart.jd) return 0; if (B->end.jdstart.jd) return 0; } return 1; } void StretchSpanToDate(Span *Sp, const Day *D) { Day *S,*E; long EX; if (!Sp || !D) return; S=&(Sp->start); E=&(Sp->end); if (D->kind==timeBegin) { S->kind=timeBegin; return; } if (D->kind==timeStill) { E->kind=timeStill; return; } if (D->kind!=timeJD) return; if (S->kind==timeInval) *S=*D; else if (S->kind==timeJD) { EX=lsr(S->jd - S->unc,D->jd - D->unc); S->jd=lsr(S->jd,D->jd); S->unc=gtr(0,S->jd - EX); } if (E->kind==timeInval) *E=*D; else if (E->kind==timeJD) { EX=gtr(E->jd + E->unc,D->jd + D->unc); E->jd=gtr(E->jd,D->jd); E->unc=gtr(0,EX - E->jd); } } static void LaterDate(const Day *A, const Day *B, Day *L) { if (!A || !B || !L) return; if (A->kind==timeInval) { *L=*B; return; } if (B->kind==timeInval) { *L=*A; return; } if (A->kind==timeBegin) { *L=*B; return; } if (B->kind==timeBegin) { *L=*A; return; } if (A->kind==timeStill) { *L=*A; return; } if (B->kind==timeStill) { *L=*B; return; } L->jd=gtr(A->jd,B->jd); L->unc= gtr(A->jd + A->unc,B->jd + B->unc) - L->jd; L->kind=timeJD; } static void EarlierDate(const Day *A, const Day *B, Day *E) { if (!A || !B || !E) return; if (A->kind==timeInval) { *E=*B; return; } if (B->kind==timeInval) { *E=*A; return; } if (A->kind==timeStill) { *E=*B; return; } if (B->kind==timeStill) { *E=*A; return; } if (A->kind==timeBegin) { *E=*A; return; } if (B->kind==timeBegin) { *E=*B; return; } E->jd=lsr(A->jd,B->jd); E->unc= E->jd - lsr(A->jd - A->unc,B->jd - B->unc); E->kind=timeJD; } void SpanIntersection(const Span *A, const Span *B, Span *In) { if (!A || !B || !In) return; LaterDate(&(A->start),&(B->start),&(In->start)); EarlierDate(&(A->end),&(B->end),&(In->end)); } void SpanUnion(const Span *A, const Span *B, Span *Un) { if (!A || !B || !Un) return; EarlierDate(&(A->start),&(B->start),&(Un->start)); LaterDate(&(A->end),&(B->end),&(Un->end)); } // Animation control void StartAnim(void) { Step.running=1; } void StopAnim(void) { Step.running=0; } void ReverseAnim(void) { Step.running=-1; } // Setting time-step size #define MAXUNIT (6) // number of different step units (days, weeks, months, years, decades, centuries) #define MAXSPEED (5) // max number of speeds for any of the 6 step units static const long StepDays[MAXUNIT] = { 1,7,31,366,3653,36525L }; // maxima (step may be rounded) static const int Speeds[MAXUNIT] = { 4,2,5,4,4,5 }; // number of valid elements in rows of Incrm[] static const int Incrm[MAXUNIT * MAXSPEED] = { 1,2,3,4,0, 1,2,0,0,0, 1,2,3,4,6, 1,2,3,5,0, 1,2,3,5,0, 1,2,3,5,10,}; static long AddYear(long YEAR, long INCR) { long SUM; SUM= YEAR + INCR; if (YEAR<0 && SUM>=0) SUM++; if (YEAR>0 && SUM<=0) SUM--; return SUM; } static long StepJD(long JD, int N) { short Y,YR,M; jdtodate(JD,&YR,&M,NULL); switch (Step.unit) { case stepDay: return JD + (N * Step.incr); case stepWeek: return JD + (7 * N * Step.incr); case stepMonth: M+= N * Step.incr; Y= YR + (M / 12); M%=12; if (M<0) { M+=12; Y--; } if (Y==0) Y= (N>0) ? 1 : -1; return datetojd(Y,M,0); case stepYear: Y=AddYear(YR,N * Step.incr); return datetojd(Y,0,0); case stepDecade: Y=AddYear(YR,10 * N * Step.incr); return datetojd(Y,0,0); case stepCentury: Y=AddYear(YR,100 * N * Step.incr); if (Y>0) Y-= (Y - 1) % 10; // round to nearest decade if (Y<0) Y-= mod(Y,10); return datetojd(Y,0,0); } return JD + (N * Step.incr); } static void UpdateInterval(void) { long NEXT,STEP,STANDARD; NEXT= StepJD(Time.jd,1); STEP= NEXT - Time.jd; STANDARD= Step.incr * StepDays[Step.unit]; if (StepJD(NEXT,-1)==Time.jd) Step.days=STEP; else Step.days=STANDARD; Step.intv.start.kind=timeJD; Step.intv.start.jd=Time.jd; Step.intv.start.unc=0; Step.intv.end=Step.intv.start; Step.intv.end.jd+= Step.days - 1; } void SetStep(int UNIT, int SPEED) { if (!range(UNIT,MAXUNIT) || !range(SPEED,MAXSPEED)) return; Step.unit=UNIT; Step.speed=SPEED; Step.incr=Incrm[(MAXSPEED * UNIT) + SPEED]; Step.days= Step.incr * StepDays[Step.unit]; Time.dt= TIMERATIO * Step.days; /*based on standard steps*/ UpdateInterval(); } void Slower(bool Much) { if (Much) { if (Step.speed>0) SetStep(Step.unit,0); else if (Step.unit>stepDay) SetStep(Step.unit - 1,0); } else { if (Step.speed>0) SetStep(Step.unit,Step.speed - 1); else if (Step.unit>stepDay) SetStep(Step.unit - 1,Speeds[Step.unit - 1] - 1); } } void Faster(bool Much) { if (Much) { if (Step.unitDaysPerStep;i++) Slower(0); for (i=0;i<20 && Step.days=Time.jd2) { Time.jd=Time.jd2; StopAnim(); } jdtodate(Time.jd,&(Time.year),&(Time.month),&(Time.day)); UpdateInterval(); } void GoToYear(int YEAR) { YEAR=between(YEAR,Time.year1,Time.year2); Time.jd=datetojd(Time.year=YEAR,Time.month=0,Time.day=0); UpdateInterval(); StopAnim(); } void GoToJD(long JD) { JD=between(JD,Time.jd1,Time.jd2); jdtodate(Time.jd=JD,&(Time.year),&(Time.month),&(Time.day)); UpdateInterval(); StopAnim(); } // Time bounds static void AdjustEndPoints(void) { Time.jd1=datetojd(Time.year1,0,0); Time.jd2=datetojd(Time.year2,0,0); } void InitTimeBounds(void) { Time.year1=-4000; Time.year2=2001; AdjustEndPoints(); GoToJD(Time.jd); } static void StretchBoundsToDate(const Day *D) { short YEAR; if (!D || D->kind!=timeJD) return; jdtodate(D->jd,&YEAR,NULL,NULL); while (YEAR=Time.year2) Time.year2+=10; AdjustEndPoints(); } void StretchBoundsToSpan(const Span *S) { if (!S) return; StretchBoundsToDate(&(S->start)); StretchBoundsToDate(&(S->end)); } // Initialisation void InitTime(void) { InitTimeBounds(); SetStep(stepCentury,0); GoToYear(400); } // String functions static string YearOblique(int YEAR) { string S,T; int LS,LT; bool Two; S=fig(abso(YEAR)); YEAR++; if (YEAR==0) YEAR++; T=fig(abso(YEAR)); LS=length(S); LT=length(T); Two= (LT<2) ? 0 : (LS>=2 && S[LS - 2]!=T[LT - 2]); S=cat3(5,S,"/",rightstr(T,Two ? 2 : 1)); old(T); return S; } static string AddSeasonString(string S, int SEASON) { char *T; if (!S) return NULL; switch (SEASON) { case 0: T="winter"; break; case 1: T="spring"; break; case 2: T="summer"; break; case 3: T="autumn"; break; default: return S; } return cat3(1,S,", ",T); } string DateStr(const Day *D, bool EraIfBC, bool ForceEra, bool Circa) { short YEAR,MONTH,DATE,MUNC; string S; if (!D) return NULL; if (D->kind==timeBegin) return copyof("a long time ago"); if (D->kind==timeStill) return copyof("many years hence"); if (D->kind!=timeJD) return copyof("unknown"); jdtodate(D->jd,&YEAR,&MONTH,&DATE); if (D->unc==365 && MONTH==11 && DATE==30) return YearOblique(YEAR); S=fig(abso(YEAR)); if (D->unc>=365) S= Circa ? cat(2,"c. ",S) : cat3(5,S," ± ",fig(D->unc / 365)); if (YEAR<0 && (ForceEra || EraIfBC)) S=cat(1,S," B.C."); if (YEAR>0 && ForceEra) S=cat(2,"A.D. ",S); if (D->unc>=180) return S; if (D->unc==45 && mod(MONTH,3)==1 && DATE==5) return AddSeasonString(S,MONTH / 3); S=cat3(1,S," ",monthname(MONTH)); if (D->unc>=30) { MUNC= D->unc / 30; S= Circa ? cat(2,"c. ",S) : cat4(5,S," ± ",fig(MUNC),MUNC>1 ? " months" : "month"); } if (D->unc>=14/*15*/) return S; S=cat3(5,S," ",fig(DATE + 1)); if (D->unc>=1) return Circa ? cat(2,"c. ",S) : cat3(5,S," ± ",fig(D->unc)); return cat3(1,S,", ",dayname(dayofweek(D->jd))); }