Unix Date and Time Conversions

┌───────────────────────────────────────┐
│    Unix Time and Date Conversions     │
│         Author: Phil Pilgrim          │
│(c) Copyright 2012 Bueno Systems, Inc. │
│   See end of file for terms of use.   │
└───────────────────────────────────────┘

This object converts between Unix time and other date/time formats.

Revision History

2012.03.27: v1.0 Initial release.

Contact Information

Phil Pilgrim: propeller@phipi.com

Introduction

This object provides methods that convert back and forth between Unix time and year.month.day hour:minute:second values. Unix time is a signed, 32-bit integer representing the number of seconds elapsed since 01 Jan 1970 at 00:00:00 UTC, ignoring leap seconds. The object also provides date and time from Unix time in ASCII string format.

Instance Variables


SOURCE CODE...
  byte  dt_str[16]    'Buffer for date strings.
  byte  tm_str[12]    'Buffer for time strings.


Public Spin Methods


unix_time(yr, mo, da, hr, mn, sec, timezone)

Convert date and time to Unix time.

Parameters:
yr: Year number: must be entire number, e.g. 1984, not 84.
mo: Month number: 1 == Jan, ..., 12 == Dec.
da: Day number: 1 .. 31.
hr: Hour: 0 .. 23.
mn: Minute: 0 .. 59.
sec: Second: 0 .. 59.
timezone: Timezone for time and date given: -12 .. 12, e.g. 0 == UTC, -5 == EST, -7 == PDT, etc.
Return: A signed 32-bit integer representing Unix time in whole seconds.
Example: utime := udt.unix_time(2000, 1, 1, 12, 0, 0, -6)
Return Unix time for 1 Jan 2000 at 12 noon CST and assigne to the long variable utime.

SOURCE CODE...
PUB unix_time(yr, mo, da, hr, mn, sec, timezone) | a 

  if (mo < 3)
    yr--
    mo += 12
  a := yr / 100
  return ((36525 * yr) / 100 + 306001 * (mo + 1) / 10000 + da - a + a ~> 2 - 719591) * 86400 + (hr - timezone) * 3600 + mn * 60 + sec


date_time24_str(unixtime, timezone)

Given Unix time and a local timezone, produce a string in the format: "Wdy dd Mmm yyyy hh:mm:ss"

Parameters:
unixtime: Unix time in whole seconds.
timezone: Timezone for date returned: -12 .. 12, e.g. 0 == UTC, -5 == EST, -7 == PDT, etc.
Return: Address of the resulting date/time string.
NOTE: The contents of this string are transient and must be either copied or output before another date string method in this object is called.
Example: sio.str(udt.date_time24_str(1_000_000_000, -4))
Output a string representing the date given by 1_000_000_000 (in Unix time), corrected for EDT (in this case the string "Sat 08 Sep 2001 01:46:40").

SOURCE CODE...
PUB date_time24_str(unixtime, timezone)

  date_str(unixtime, timezone)
  time24_str(unixtime, timezone)
  dt_str[15] := " "
  return @dt_str


date_time12_str(unixtime, timezone)

Given Unix time and a local timezone, produce a string in the format: "Wdy dd Mmm yyyy hh:mm:ss xm"

Parameters:
unixtime: Unix time in whole seconds.
timezone: Timezone for date returned: -12 .. 12, e.g. 0 == UTC, -5 == EST, -7 == PDT, etc.
Return: Address of the resulting date/time string.
NOTE: The contents of this string are transient and must be either copied or output before another date string method in this object is called.
Example: sio.str(udt.date_time24_str(1_000_000_000, -4))
Output a string representing the date given by 1_000_000_000 (in Unix time), corrected for EDT (in this case the string "Sat 08 Sep 2001 01:46:40 am").

SOURCE CODE...
PUB date_time12_str(unixtime, timezone)

  date_str(unixtime, timezone)
  time12_str(unixtime, timezone)
  dt_str[15] := " "
  return @dt_str


date_str(unixtime, timezone)

Given Unix time and a local timezone, produce a string in the format: "day dd Mmm yyyy"

Parameters:
unixtime: Unix time in whole seconds.
timezone: Timezone for date returned: -12 .. 12, e.g. 0 == UTC, -5 == EST, -7 == PDT, etc.
Return: Address of the resulting date string.
NOTE: The contents of this string are transient and must be either copied or output before another date string method in this object is called.
Example: sio.str(udt.date_str(1_000_000_000, -4))
Output a string representing the date given by 1_000_000_000 (in Unix time), corrected for EDT (in this case the string "Sat 08 Sep 2001").

SOURCE CODE...
PUB date_str(unixtime, timezone) | yyyymmdd 

  yyyymmdd := date(unixtime, timezone)
  bytefill(@dt_str, " ", 15)
  dt_str[15]~
  dec(@dt_str[4], yyyymmdd.byte[0], 2)
  dec(@dt_str[11], yyyymmdd.word[1], 4)
  bytemove(@dt_str, weekday_str(weekday(unixtime, timezone)), 3)
  bytemove(@dt_str[7], month_str(yyyymmdd.byte[1]), 3)
  return @dt_str


time24_str(unixtime, timezone)

Given Unix time and a local timezone, produce a zero-terminated ASCII string in the format: "hh:mm:ss" (24-hour format).

Parameters:
unixtime: Unix time in whole seconds.
timezone: Timezone for time returned: -12 .. 12, e.g. 0 == UTC, -5 == EST, -7 == PDT, etc.
Return: Address of the resulting time string.
NOTE: The contents of this string are transient and must be either copied or output before another time string method in this object is called.
Example: sio.str(udt.time24_str(1_000_000_000, 0))
Output a string representing the time given by 1_000_000_000 (in Unix time), UTC (in this case the string "01:46:40").

SOURCE CODE...
PUB time24_str(unixtime, timezone) | hhmmss 

  bytemove(@tm_str, string("xx:xx:xx"), 9)
  hhmmss := time(unixtime, timezone)
  dec(@tm_str, hhmmss.byte[2], 2)
  dec(@tm_str[3], hhmmss.byte[1], 2)
  dec(@tm_str[6], hhmmss.byte[0], 2)
  return @tm_str


time12_str(unixtime, timezone)

Given Unix time and a local timezone, return a zero-terminated ASCII string in the format: "hh:mm:ss am" or "hh:mm:ss pm" (12-hour am/pm format).

Parameters:
unixtime: Unix time in whole seconds.
timezone: Timezone for time returned: -12 .. 12, e.g. 0 == UTC, -5 == EST, -7 == PDT, etc.
Return: Address of the resulting time string.
NOTE: The contents of this string are transient and must be either copied or output before another time string method in this object is called.
Example: sio.str(udt.time12_str(1_000_000_000, 0))
Output a string representing the time given by 1_000_000_000 (in Unix time), UTC (in this case the string "01:46:40 am").

SOURCE CODE...
PUB time12_str(unixtime, timezone) | hhmmss, hr  

  bytemove(@tm_str, string("xx:xx:xx am"), 12)
  hhmmss := time(unixtime, timezone)
  if ((hr := hhmmss.byte[2]) > 12)
    tm_str[9] := "p"
    hr -= 12
  if (hr == 0)
    hr := 12
  dec(@tm_str, hr, 2)
  dec(@tm_str[3], hhmmss.byte[1], 2)
  dec(@tm_str[6], hhmmss.byte[0], 2)
  return @tm_str


date(unixtime, timezone)

Compute a calendar date from Unix time and a local timezone.

Parameters:
unixtime: Unix time in whole seconds.
timezone: Timezone for date returned: -12 .. 12, e.g. 0 == UTC, -5 == EST, -7 == PDT, etc.
Return: A packed long containing the year in the upper word, the month in byte 1, and the day in byte 0.
Example: yyyymmdd := udt.date(1_234_567_890, 0)
Return the date (UTC) for Unix time == 1_234_567_890 (in this case 2009 << 16 | 2 << 8 | 13, i.e. 13 Feb 2009), and assign it to the long variable yyyymmdd.

SOURCE CODE...
PUB date(unixtime, timezone) : yyyymmdd  | year, month, day, leap

  day := floor_div(unixtime + timezone * 3600, 86400)
  year := floor_div(day << 2 + 2, 1461)
  yyyymmdd.word[1] := year + 1970
  leap := floor_mod(year, 4) == 2
  day -= (year * 1461 + 1) ~> 2
  if (day > 58 - leap)
    day += leap + 2
  yyyymmdd.byte[1] := (day * 12 + 6) / 367
  yyyymmdd.byte[0] := day + 1 - (yyyymmdd.byte[1]++ * 367 + 5) / 12


time(unixtime, timezone)

Compute a time of day from Unix time and a local timezone.

Parameters:
unixtime: Unix time in whole seconds.
timezone: Timezone for date returned: -12 .. 12, e.g. 0 == UTC, -5 == EST, -7 == PDT, etc.
Return: A packed long containing the hour in byte 2, minutes in byte 1, and seconds in byte 0.
Example: hhmmss := udt.time(1_234_567_890, -5)
Return the time (EST) for Unix time == 1_234_567_890 (in this case 16 << 16 | 31 << 8 | 30, i.e. 16:31:30), and assign it to the long variable hhmmss.

SOURCE CODE...
PUB time(unixtime, timezone) | hours, minutes, seconds

  seconds := floor_mod(unixtime + timezone * 3600, 86400)
  minutes := seconds / 60
  seconds -= minutes * 60
  hours := minutes / 60
  minutes -= hours * 60
  return hours << 16 + minutes << 8 + seconds


weekday(unixtime, timezone)

Compute the day of the week from the given Unix time and local timezone.

Parameters:
unixtime: Unix time in whole seconds.
timezone: Timezone for date returned: -12 .. 12, e.g. 0 == UTC, -5 == EST, -7 == PDT, etc.
Return: Number (0 == Sun .. 6 == Sat) corresponding to the day of the week.
Example: wkday := udt.weekday(0, 0)
Find the day of the week corresponding to the beginning of the Unix epoch, in this case 4 (Thursday) and assign it to the variable wkday.

SOURCE CODE...
PUB weekday(unixtime, timezone)

  return floor_mod(floor_div(unixtime + timezone * 3600, 86400) + 4, 7)


weekday_str(wkday)

Given a weekday number, return the address of a zero-terminated ASCII string representing the day of the week.

Parameters:
wkday: Weekday number (0 == Sun .. 6 == Sat).
Return: Address of a three-character ASCII string giving an abbreviation for the corresponding weekday.
Example: sio.str(udt.weekday_str(udt.weekday(0, 0)))
Output "Thu" to the serial port. (See example for weekday above.)

SOURCE CODE...
PUB weekday_str(wkday)

  return @wkdays + (wkday // 7) << 2


month_str(month)

Given a month number, return the address of an ASCII representing the day of the week.

Parameters:
month: The number of the month (1 == Jan .. 12 == Dec).
Return: Address of a three-character ASCII string giving an abbreviation for the corresponding month.
Example: bytecopy(@my_month, udt.month_str(5), 4)
Copy the four bytes "May", 0 to the byte array my_month. Next lines, if needed...

SOURCE CODE...
PUB month_str(month)

  return @months + ((month - 1) // 12) << 2


Private Spin Methods

Decimal conversion and math floor routines.

dec(addr, value, digits)

Convert a value to ASCII decimal and copy the digits to an array whose address is given.

Parameters:
addr: Address of the array to copy to.
value: Value of the number to convert.
digits: Number of least-significant digits to convert.
Return: none.
Example: dec(@str, 1234, 2)
Copies the digits "34" to the byte array str.

SOURCE CODE...
PRI dec(addr, value, digits) | i    

  repeat i from digits - 1 to 0
    byte[addr][i] := value // 10 + "0"
    value /= 10


floor_div(numer, denom)

Return the floor quotient of numer / denom. Floor quotient of -1/n == -1, -n/n == -1, -(n+1)/n == -2, etc.

Parameters:
numer: Numerator.
denom: Denominator.
Return: Floor quotient of numer / denom.

SOURCE CODE...
PRI floor_div(numer, denom)

  if (numer < 0)
    numer -= denom - 1
  return numer / denom


floor_mod(numer, denom)

Return the floor modulus of numer // denom. Floor modulus of -1//n == n-1, -2//n == n-2, -n/n == 0, etc.

Parameters:
numer: Numerator.
denom: Modulus.
Return: Floor modulus of numer // denom.

SOURCE CODE...
PRI floor_mod(numer, denom)

  result := numer // denom
  if (result < 0)
    result += denom


String Constants


SOURCE CODE...
wkdays        byte      "Sun",0,"Mon",0,"Tue",0,"Wed",0,"Thu",0,"Fri",0,"Sat"
months        byte      "Jan",0,"Feb",0,"Mar",0,"Apr",0,"May",0,"Jun",0,"Jul",0,"Aug",0,"Sep",0,"Oct",0,"Nov",0,"Dec",0


License

┌──────────────────────────────────────────────────────────────────────────────────────┐
│                            TERMS OF USE: MIT License                                 │                                                            
├──────────────────────────────────────────────────────────────────────────────────────┤
│Permission is hereby granted, free of charge, to any person obtaining a copy of this  │
│software and associated documentation files (the "Software"), to deal in the Software │
│without restriction, including without limitation the rights to use, copy, modify,    │
│merge, publish, distribute, sublicense, and/or sell copies of the Software, and to    │
│permit persons to whom the Software is furnished to do so, subject to the following   │
│conditions:                                                                           │
│                                                                                      │
│The above copyright notice and this permission notice shall be included in all copies │
│or substantial portions of the Software.                                              │
│                                                                                      │
│THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,   │
│INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A         │
│PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT    │
│HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF  │
│CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE  │
│OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                                         │
└──────────────────────────────────────────────────────────────────────────────────────┘