// TimeConvB.bcpl -- conversion between packed and unpacked representations. // Requires companion module TimeConvA.asm // Copyright Xerox Corporation 1979 // Last modified December 31, 1977 7:05 PM get "AltoDefs.d" get "Time.d" external [ // Outgoing procedures UNPACKDT; PACKDT; WEEKDAY // Incoming procedures TimeDiv; TimeMul MoveBlock; ReadCalendar; DoubleAdd; SysErr // Incoming statics monthTable; OsVersion ] // The internal format for a calendar date and time // (or simply "daytime") is a 32-bit integer (PTV, packed time vector) // representing seconds relative to midnight, January 1, 1901 GMT. // There is an unpacked format (UTV, unpacked time vector, defined in Time.d) // more suitable for person-oriented I/O: a 7-word vector containing year, // month (January=0), day (first day of month=1), hour, minute, // second, day of week, daylight saving time flag, and time zone information. // Conversion between the two forms is not trivial, since it must deal with // leap years, daylight time, time zones, and lengths of months. // Notes: // (1) This implementation does not need to know the Gregorian calendar // rule whereby years divisible by 100 aren't leap years unless also divisible // by 400. This is because the Alto calendar only spans the years // 1901 to 2036, and the year 2000 is a leap year. // (2) This version of the code is intended to work whether or not the // local time parameters are available. If they aren't, // we assume that the local time zone is 8 (Pacific Standard). This // compatibility code should be removed eventually. It is marked by "****". //---------------------------------------------------------------------------- structure ULT: // Unpacked Local Time information //---------------------------------------------------------------------------- // UNPACKDT and PACKDT know about the order of these words. Beware! [ beginDST word // Day of year on or before which DST starts endDST word // Day of year on or before which DST ends zone: // Local time zone -- format same as in UTV structure [ blank bit 5 sign bit 1 hour bit 4 minute bit 6 ] compZone word // Composite zone: minutes west (+) or east (-) of GMT ] manifest ecOSVersion = 2900 // Error code for OS older than version 14 //---------------------------------------------------------------------------- let UNPACKDT(ptv, utv) = valof //---------------------------------------------------------------------------- // Unpacks 32-bit time at ptv into UTV structure (see Time.d). // If ptv is 0, uses current date and time. [ // Set up local time parameters let beginDST, endDST, zone, compZone = nil, nil, nil, nil SetupULT(lv beginDST, 0) utv>>UTV.word6 = 0 utv>>UTV.zone = zone // Get or default packed time let pv = vec 2 test ptv eq 0 ifso ReadCalendar(pv) ifnot MoveBlock(pv, ptv, 2) // Unpack second straightforwardly utv>>UTV.second = TimeDiv(pv, 60, pv) // pv now has number of minutes since Jan 1, 1901. // At this point, apply the local time zone by subtracting it from pv. // Since this may cause the result to underflow, also add 4 years' worth // of minutes so as to make it positive again, thereby making day zero be // Jan 1, 1897. (We ignore the fact that 1900 was not a leap year). // Note that compZone is in the range [-720..720] let tv = vec 2 tv!0, tv!1 = 32, 6688-compZone // 4 years = 2103840 = 32*2↑16 + 6688 minutes DoubleAdd(pv, tv) // Now unpack minute utv>>UTV.minute = TimeDiv(pv, 60, pv) // Go around this loop twice if daylight savings time must be applied. for i = 0 to 1 do [ // Now unpack hour in day let pv2 = vec 2 utv>>UTV.hour = TimeDiv(pv, 24, pv2) // pv2!1 has days since Jan 1, 1897. Compute day of week. // Jan 1, 1897 would have been a Thursday (=3) if 1900 were a leap year. tv!0, tv!1 = 0, pv2!1 + 3 utv>>UTV.weekday = TimeDiv(tv, 7, tv) // Unpack 4-year cycles and day within cycle, and normalize day to // 366-day years. let day = TimeDiv(pv2, 3*365+366, pv2) day = day + (day ge 3*365+31+28? 3, (day+(365-31-28))/365) // pv2!1 now has number of 4-year cycles since Jan 1, 1897. // Compute real year and day in year. utv>>UTV.year = 1897 + 4*pv2!1 + day/366 day = day rem 366 // Compute month and day in month let month = 0 while day ge monthTable!(month+1) do month = month+1 utv>>UTV.month = month utv>>UTV.day = day - monthTable!month +1 // If daylight savings time applies, add one hour and go around // the loop again. Note that daylight savings time goes into effect at // 2 AM standard time and out of effect at 2 AM daylight time, which is // 1 AM standard time (standard time is what utv presently contains). if not CheckDateGE(utv, beginDST, 2) % CheckDateGE(utv, endDST, 1) break utv>>UTV.daylight = true DoubleAdd(pv, table [ 0; 1 ]) ] resultis utv ] //---------------------------------------------------------------------------- and PACKDT(utv, ptv, useWord6; numargs na) = valof //---------------------------------------------------------------------------- // Packs the time described by utv into a 32-bit time, returning zero if // successful and the index of the incorrect UTV cell if unsuccessful. // If useWord6 then uses the zone and daylight information in word 6 of utv; // otherwise ignores it and uses the local time zone and computes DST on the // basis of the date in utv. Recomputes the weekday field unconditionally. [ if na ls 3 then useWord6 = false // Setup local time parameters let beginDST, endDST, zone, compZone = nil, nil, nil, nil SetupULT(lv beginDST, (useWord6? utv, 0)) // Test validity of unpacked form let year, month, day = utv>>UTV.year-1901, utv>>UTV.month, utv>>UTV.day let hour, minute, second = utv>>UTV.hour, utv>>UTV.minute, utv>>UTV.second if year ls 0 % year ge 136 resultis iYear let year4 = year & 3 // year rem 4 -- position in 4-year cycle if month ls 0 % month ge 12 resultis iMonth if day ls 1 % day gr monthTable!(month+1)-monthTable!month % (month eq 1 & day eq 29 & year4 ne 3) resultis iDay if hour ls 0 % hour ge 24 resultis iHour if minute ls 0 % minute ge 60 resultis iMinute if second ls 0 % second ge 60 resultis iSecond // Compute days since January 1, 1897, local time. It will be corrected to // January 1, 1901 later (see below for reason). day = ((year+4)/4)*(3*365+366) + // Days in previous 4-year cycles 365*year4 + // Days in this 4-year cycle monthTable!month + // Days in previous months this year day - // Days in this month (year4 ne 3 & month ge 2? 2, 1) // Correction for leap year // Compute weekday for DST check. // Note that January 1, 1897 was a Thursday (=3). ptv!0, ptv!1 = 0, day+3 let tv = vec 2 utv>>UTV.weekday = TimeDiv(ptv, 7, tv) // Compute minutes since January 1, 1897, local time. ptv!1 = day TimeMul(hour, 24, ptv) TimeMul(minute, 60, ptv) // Apply time zone correction (convert to GMT) by adding local time zone. // Simultaneously, subtract 4 years' worth of minutes to correct the origin // from 1897 to 1901. This forces the sign of the added quantity always // to be negative. Note that compZone is in the range [-720..720] tv!0, tv!1 = -32-1, compZone-6688 // High part must be ones-complement // If daylight savings time is now applicable, subtract one hour to convert // to standard time. if (useWord6? utv>>UTV.daylight, CheckDateGE(utv, beginDST, 2) & not CheckDateGE(utv, endDST, 2)) then tv!1 = tv!1 -60 DoubleAdd(ptv, tv) // Pack seconds and we are finished TimeMul(second, 60, ptv) resultis 0 ] //---------------------------------------------------------------------------- and WEEKDAY(ptv) = valof //---------------------------------------------------------------------------- // Returns the day of week of the packed daytime in ptv. [ let utv = vec lenUTV UNPACKDT(ptv, utv) resultis utv>>UTV.weekday ] //---------------------------------------------------------------------------- and CheckDateGE(utv, dstDay, dstHour) = valof //---------------------------------------------------------------------------- // Returns true if the current time is greater than or equal to the // specified DST time and false otherwise. // utv = unpacked time vector describing current date and time // (month, day, hour, and weekday fields must be valid) // dstDay = day on or before which DST switch occurs // dstHour = hour at which DST switch occurs [ let day = monthTable!(utv>>UTV.month) + utv>>UTV.day let weekday = utv>>UTV.weekday resultis (day ls dstDay-6? false, day gr dstDay? true, weekday eq 6? utv>>UTV.hour ge dstHour, day-weekday gr dstDay-6) ] //---------------------------------------------------------------------------- and SetupULT(ult, utv) be //---------------------------------------------------------------------------- // Sets up unpacked local time information for use by UNPACKDT and PACKDT. // Stores results in ult (see ULT structure for contents). // If utv is nonzero, uses it (rather than 570) to supply the local time zone. // **** Defaults time information to Pacific if unavailable from system. [ if OsVersion ls 14 then SysErr(0, ecOSVersion) // Extract or default time parameters test timeParams!0 eq 0 % timeParams!1 eq 0 ifnot [ ult>>ULT.beginDST = timeParams>>LTP.beginDST ult>>ULT.endDST = timeParams>>LTP.endDST ult>>ULT.zone.sign = timeParams>>LTP.sign ult>>ULT.zone.hour = timeParams>>LTP.zoneH ult>>ULT.zone.minute = timeParams>>LTP.zoneM ] ifso [ // **** Default them if not present ult>>ULT.beginDST, ult>>ULT.endDST = 121, 305 ult>>ULT.zone = 8 lshift size ULT.zone.minute ] // **** if utv ne 0 then ult>>ULT.zone = utv>>UTV.zone // Compute composite zone -- minutes west (+) or east (-) of GMT ult>>ULT.compZone = 60*(ult>>ULT.zone.hour) + ult>>ULT.zone.minute if ult>>ULT.zone.sign ne 0 then ult>>ULT.compZone = -(ult>>ULT.compZone) ]