Data types conversions - Confused

Discussion about the ZBasic language including the System Library. If you're not sure where to post your message, do it here. However, do not make test posts here; that's the purpose of the Sandbox.
Post Reply
ndudman
Posts: 79
Joined: 25 December 2008, 14:00 PM

Data types conversions - Confused

Post by ndudman »

Hi everyone

Iḿ a bit confused with this function toSeconds() which is the best way to represent the types and perform the various type conversions so everything matches up without having type conversions everywhere ?

Code: Select all

~ '// convert to seconds since 1970) 
function toSeconds(byVal ts as MyTimeStamp) as byte		
	toSeconds = ((ts.tdate.day-32075+1461* _
				(ts.tdate.year+4800+Csng((ts.tdate.month-14))/12)/4+367* _
				(ts.tdate.month-2-(ts.tdate.month-14)/12*12)/12-3* _
				((ts.tdate.year+4900+(ts.tdate.month-14)/12)/100)/4)*86400) _
				+ (ts.ttime.hour*3600)+(ts.ttime.minute*60)+ts.ttime.seconds
end function

public Structure MyDate
      Public year as byte
      Public month as Byte
      Public day as Byte
End Structure
public Structure MyTime
      Public hour as Byte
      Public minute as Byte
      Public seconds as Single
End Structure
public Structure MyTimeStamp
      Public tdate as MyDate
      Public ttime as MyTime
      Private isCurrent as Boolean
End Structure

public sub myGetTime(byRef ts as MyTimeStamp)
	Dim year as UnsignedInteger 
	Call GetTimeStamp(year, ts.tdate.month, ts.tdate.day, _
					  ts.ttime.hour, ts.ttime.minute, ts.ttime.seconds)
	ts.tdate.year = CByte(year)
end sub	
mikep
Posts: 796
Joined: 24 September 2005, 15:54 PM

Re: Data types conversions - Confused

Post by mikep »

Several comments on your code:
1. The function toSeconds returns a Byte. This is not large enough to hold seconds since 1999 (the epoc start for ZBasic system).
2. You should probably be using GetTimeStamp rather than the individual GetTime and GetDate routines as it is slightly faster.
3. I not sure why you are storing the year as a byte in your structure. It is probably best left as a UnsignedInteger.
4. You want to store the year and day of year (see GetDayOfYear routine) instead of the usual day/month/year as it is more convenient to then calculate the day number since 1999 using GetDayNumber routine.
5. The calculation for deriving the number of seconds looks longer than it needs to be. Here is some standard psuedo-code for the calculation. Note that there is mainly integer division and remainder operations and I have used constants. The one thing to watch out for is that ZBasic codes seconds as a Single type to represent 1/512 fractions of a seconds as well.

Code: Select all

month = (ts.tdate.month + 10 ) mod 12
year  = ts.tdate.year - ( month \ 10 )

' calculate day number and then convert to seconds
toSeconds = SECS_PER_DAY * ( 365*year + year\4 - year\100 + year\400 + ((month*306)+5)\10 + ts.tdate.day - EPOC_START_DAY )
' add on seconds for today
toSeconds = toSeconds + ts.ttime.hour*SECS_PER_HOUR)+(ts.ttime.minute*SECS_PER_MINUTE)+CUInt((ts.ttime.seconds/TICKS_PER_SECOND)
The "trick" is to go through the calculation step by step to determine what the datatypes are and how they need to be converted. I haven't given you the complete answer here but it should help.
Mike Perks
dkinzer
Site Admin
Posts: 3120
Joined: 03 September 2005, 13:53 PM
Location: Portland, OR

Post by dkinzer »

You can use the difference between the Unix and ZX epochs to simplify the code somewhat. The code below introduces a function to determine the ZX day number given an arbitrary year, month and day. Then, the toSeconds() function uses that and the ZX-Unix epoch delta to compute seconds since 01 Jan 1970.

The code assumes that you've implemented Mike's suggestion to keep the year as UnsignedInteger (which is clearly needed). If you added another element to the MyDate structure to keep the day number also, it would simplify the code further.

Code: Select all

'
' This function converts a ZX timestamp to a Unix-compatible
' timestamp.  The conversion relies on the fact that the epoch
' for the ZX timestamp is 01 Jan 1999 while the epoch for the
' Unix timestamp is 01 Jan 1970, a difference of 10,592 days.
'
Function toSeconds(byVal ts as MyTimeStamp) as UnsignedLong
  Const SECONDS_PER_DAY as UnsignedLong = 24 * 60 * 60
  Const UNIX_ZX_DAYS_DELTA as UnsignedLong = 10592

  ' get the day number corresponding to the year, month day
  Dim zxDay as UnsignedInteger
  zxDay = zxDayNumber(ts)

  ' convert the day number to Unix seconds
  toSeconds = (CULng(zxDay) + UNIX_ZX_DAYS_DELTA) * SECONDS_PER_DAY 

  ' add in the seconds corresponding to the hour, minute and second
  toSeconds = toSeconds + CULng(ts.ttime.seconds) + (CULng(ts.ttime.minute) * 60) + (CULng(ts.ttime.hour) * 3600)
End Function

'
' This function returns the ZX day number corresponding to the year,
' month and day represented by the timestamp provided.
'
Function zxDayNumber(ByVal ts as MyTimeStamp) as UnsignedInteger
  ' disable interrupts to preclude RTC updates
  Dim intFlag as Byte
  intFlag = DisableInt()

  ' save the current RTC day number
  Dim dayNumSave as UnsignedInteger
  dayNumSave = Register.RTCDay

  ' set the RTC day number using the year, month and day components
  Call PutDate(ts.tdate.year, ts.tdate.month, ts.tdate.day)
  zxDayNumber = Register.RTCDay

  ' restore the original RTC day number, re-enable interrupts
  Register.RTCDay = dayNumSave
  Call EnableInt(intFlag)
End Function
Last edited by dkinzer on 01 January 2009, 12:42 PM, edited 1 time in total.
- Don Kinzer
ndudman
Posts: 79
Joined: 25 December 2008, 14:00 PM

Thank you both

Post by ndudman »

Hi Mike and Don

Thank you both very much... I was really struggling to find the algo, searching and searching on the net.

The year as Byte mistake, I noticed after I posted the question and continued on the problem... I had changed it without thinking.

A greatful
Neil
dkinzer
Site Admin
Posts: 3120
Joined: 03 September 2005, 13:53 PM
Location: Portland, OR

Post by dkinzer »

The last line in the toSeconds() code should be:

Code: Select all

  toSeconds = toSeconds + CULng(ts.ttime.seconds) + (CULng(ts.ttime.minute) * 60) + (CULng(ts.ttime.hour) * 3600)
I've edited the original post accordingly.
- Don Kinzer
ndudman
Posts: 79
Joined: 25 December 2008, 14:00 PM

Post by ndudman »

Is there a version of PutTimeStamp(ts.tdate.year, ts.tdate.month, ts.tdate.day) which takes the zxSeconds...

The reason I was trying to write these things was to calculate durations (timeB-timeA), so now I need to seperate the seconds back to YYMMDD-time etc.

I started (found on web) the routine below... but just checking if there is a better way with built in libs to do this... I got all excited about the PutTimeStamp() untill I saw the parameters. The code below is only a start, what I have right now, am I going in the right direction ? I will also study more the algorithm that Mike gave a link to.

Code: Select all

'~ #define SECS_DAY   86400 	'~ (24L * 60L * 60L) 
'~ #define EPOCH_YR   1970

'~ private function LEAPYEAR(byval year as byte) as byte
	'~ LEAPYEAR =  NOT ((year) mod 4) AND (((year) MOD 100) OR NOT((year) MOD 400))
'~ end function

'~ private function YEARSIZE(byval	year as byte) as byte
	'~ if LEAPYEAR(year) = 1 then
		'~ YEARSIZE = 366 
	'~ else 
		'~ YEARSIZE = 365
	'~ end if
'~ end function

// We could do this if ram was no issue:
//uint8_t monthlen(uint8_t isleapyear,uint8_t month){
//const uint8_t mlen[2][12] = {
//		{ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 },
//		{ 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
//	};
//	return(mlen[isleapyear][month]);
//}
//
private const mlen(2)(12) = (31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ), _
							(31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 )
'~ private function monthlen(byval isleapyear as byte,byval month as byte) as byte
	'~ if month = 1 then
		'~ monthlen = 28+isleapyear
		'~ exit function
	'~ end if
	'~ if month>6 then
		'~ month = month - 1
	'~ end if
	'~ if (month MOD 2) = 1 then
		'~ monthlen = 30
		'~ exit function
	'~ end if
	'~ monthlen = 31
'~ end function

// gmtime -- convert calendar time (sec since 1970) into broken down time
// returns something like Fri 2007-10-19 in day and 01:02:21 in clock
// The return values is the minutes as integer. This way you can update
// the entire display when the minutes have changed and otherwise just
// write current time (clock). That way an LCD display needs complete
// re-write only every minute.
sub gmtime(byval time as, byref ts as MyTimeStamp)
	uint16_t tm_year = EPOCH_YR;
	uint8_t tm_sec,tm_min,tm_hour,tm_wday,tm_mon;

	dayclock = time % SECS_DAY;
	dayno = time / SECS_DAY;

	dim tm_sec as byte = dayclock % 60UL
	dim tm_min as byte = (dayclock % 3600UL) / 60;
	dim tm_hour as byte = dayclock / 3600UL;
	dim tm_wday as byte = (dayno + 4) % 7;	/* day 0 was a thursday */
	while (dayno >= YEARSIZE(tm_year)) {
		dayno -= YEARSIZE(tm_year)
		tm_year++
	wend
	tm_mon = 0
	while dayno >= monthlen(LEAPYEAR(tm_year),tm_mon)) 
		dayno -= monthlen(LEAPYEAR(tm_year),tm_mon)
		tm_mon++
	wend
	i=0
    while &#40;i<3&#41;
        dstr&#91;i&#93;= pgm_read_byte&#40;&&#40;day_abbrev&#91;tm_wday*3 + i&#93;&#41;&#41;
        i = i + 1
	wend
	ts.tdate.year = tm_year
	ts.tdate.month = tm_mon + 1
	ts.tdate.day = dayno + 1
	ts.ttime.hour = tm_hour
	ts.ttime.minute = tm_min
	ts.ttime.seconds = tm_sec	
end sub
dkinzer
Site Admin
Posts: 3120
Joined: 03 September 2005, 13:53 PM
Location: Portland, OR

Post by dkinzer »

ndudman wrote:Is there a version of PutTimeStamp(ts.tdate.year, ts.tdate.month, ts.tdate.day) which takes the zxSeconds
No, but you can use a similar strategy as was used in the toSeconds() code that I posted above to perform the reverse conversion. The code below (untested, as was the earlier code), should do the trick. Note, particularly, that this code does not deal with the issue that would arise if a Unix-seconds value prior to 01 Jan 1999 is passed.

Code: Select all

'
' This function converts a Unix time value &#40;seconds since 01 Jan 1970&#41;
' to year, month, day, hour, minute, second format.
'
Function toTimeStamp&#40;ByVal unixTime as UnsignedLong&#41; as MyTimeStamp
  Const SECONDS_PER_DAY as UnsignedLong = 24 * 60 * 60
  Const UNIX_ZX_DAYS_DELTA as UnsignedLong = 10592

  ' determine the number of whole days and leftover seconds
  Dim dayNumber as UnsignedInteger
  Dim seconds as UnsignedInteger
  dayNumber = CUInt&#40;unixTime \ SECONDS_PER_DAY&#41;
  seconds = CUInt&#40;unixTime Mod SECONDS_PER_DAY&#41;

  ' convert the day number to a ZX day number and then to year/month/day
  dayNumber = dayNumber - CUInt&#40;UNIX_ZX_DAYS_DELTA&#41;
  Call GetDate&#40;toTimeStamp.tdate.year, toTimeStamp.tdate.month, _
      toTimeStamp.tdate.day, dayNumber&#41;

  ' convert the remaining seconds value to hours, minutes seconds
  toTimeStamp.ttime.hour = CByte&#40;seconds \ 3600&#41;
  seconds = seconds Mod 3600
  toTimeStamp.ttime.minute = CByte&#40;seconds \ 60&#41;
  toTimeStamp.ttime.seconds = CSng&#40;seconds Mod 60&#41;
End Function
- Don Kinzer
ndudman
Posts: 79
Joined: 25 December 2008, 14:00 PM

Post by ndudman »

Thanks again

This is really helping me understand things better. Ill test everything when the uP arrives and try to post to this thread results or changes

Neil
Post Reply