Skip to content

Use custom datetime #9

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 17 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
[project]
name = "proves-circuitpython-rv3028"
version = "1.0.0"
version = "1.0.1"
description = "RV3028 device driver"
readme = "README.md"
requires-python = ">=3.12.3"
dependencies = [
"coverage==7.6.10",
"pre-commit==4.0.1",
"pytest==8.3.2",
"pytest==8.3.2"
]

[tool.ruff.format]
Expand All @@ -27,6 +27,7 @@ skip_covered = false
include = [
"rv3028/registers.py",
"rv3028/rv3028.py",
"rv3028/rtc_datetime.py",
]

[tool.coverage.html]
Expand Down
170 changes: 170 additions & 0 deletions rv3028/rtc_datetime.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
"""
Basic date and time formatting library for RV-3028-C7 RTC module.
"""

_MONTH_DAYS = (31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)


def _is_leap_year(year: int) -> bool:
"year -> True if leap year, else False."
return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)


def _validate_times(hour: int, minute: int, second: int) -> None:
if not (0 <= hour <= 23):
raise ValueError("Hour must be in 0..24")
if not (0 <= minute <= 59):
raise ValueError("Minute must be in 0..59")
if not (0 <= second <= 59):
raise ValueError("Second must be in 0..59")


def _validate_dates(year: int, month: int, day: int) -> None:
if not (2000 <= year <= 2099):
raise ValueError("Year must be in 2000..2099")
if not (1 <= month <= 12):
raise ValueError("Month must be in 1..12")
if month == 2 and _is_leap_year(year):
if not (1 <= day <= 29):
raise ValueError("Day must be in 1..29")
elif not (1 <= day <= _MONTH_DAYS[month - 1]):
raise ValueError(f"Day must be in 1..{_MONTH_DAYS[month - 1]}")


class time:
"""
Class that represents time in hours, minutes and seconds.

Attributes:
hour (int): The hour of the time.
minute (int): The minute of the time.
second (int): The second of the time.
"""

def __new__(cls, hour: int, minute: int, second: int) -> "time":
_validate_times(hour, minute, second)
self = object.__new__(cls)
self._hour = hour
self._minute = minute
self._second = second
return self

def __repr__(self) -> str:
return f"{self.hour:2d}:{self.minute:2d}:{self.second:2d}"

__str__ = __repr__

@property
def hour(self) -> int:
return self._hour

@property
def minute(self) -> int:
return self._minute

@property
def second(self) -> int:
return self._second


class date:
"""
Class that represents date in year, month and day.

Attributes:
year (int): The year of the date.
month (int): The month of the date.
day (int): The day of the date.
"""

def __new__(cls, year: int, month: int, day: int) -> "date":
_validate_dates(year, month, day)
self = object.__new__(cls)
self._year = year
self._month = month
self._day = day
return self

def __repr__(self) -> str:
return f"{self.year}-{self.month}-{self.day}"

__str__ = __repr__

@property
def year(self) -> int:
return self._year

@property
def month(self) -> int:
return self._month

@property
def day(self) -> int:
return self._day


class datetime:
"""
Class that represents date and time combined.

Attributes:
year (int): The year of the datetime.
month (int): The month of the datetime.
day (int): The day of the datetime.
hour (int): The hour of the datetime.
minute (int): The minute of the datetime.
second (int): The second of the datetime.
"""

def __new__(
cls, year: int, month: int, day: int, hour: int, minute: int, second: int
) -> "datetime":
_validate_dates(year, month, day)
_validate_times(hour, minute, second)
self = object.__new__(cls)
self._year = year
self._month = month
self._day = day
self._second = second
self._minute = minute
self._hour = hour
return self

@classmethod
def combine(cls, date: date, time: time) -> "datetime":
return cls(date.year, date.month, date.day, time.hour, time.minute, time.second)

def __repr__(self) -> str:
return f"{self.year}-{self.month:02d}-{self.day:02d} {self.hour:02d}:{self.minute:02d}:{self.second:02d}"

__str__ = __repr__

@property
def year(self) -> int:
return self._year

@property
def month(self) -> int:
return self._month

@property
def day(self) -> int:
return self._day

@property
def hour(self) -> int:
return self._hour

@property
def minute(self) -> int:
return self._minute

@property
def second(self) -> int:
return self._second

def time(self) -> time:
return time(self.hour, self.minute, self.second)

def date(self) -> date:
return date(self.year, self.month, self.day)
124 changes: 56 additions & 68 deletions rv3028/rv3028.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
Authors: Nicole Maggard, Michael Pham, and Rachel Sarmiento
"""

import rv3028.rtc_datetime as dt
from rv3028.registers import (
BSM,
EECMD,
Expand All @@ -25,17 +26,8 @@
from adafruit_bus_device.i2c_device import I2CDevice
from busio import I2C

_RV3028_DEFAULT_ADDRESS = 0x52


class WEEKDAY:
SUNDAY = 0
MONDAY = 1
TUESDAY = 2
WEDNESDAY = 3
THURSDAY = 4
FRIDAY = 5
SATURDAY = 6
_RV3028_DEFAULT_ADDRESS = 0x52


class RV3028:
Expand Down Expand Up @@ -108,98 +100,94 @@ def _bcd_to_int(self, bcd):
def _int_to_bcd(self, value):
return ((value // 10) << 4) | (value % 10)

def set_time(self, hours: int, minutes: int, seconds: int) -> None:
@property
def time(self) -> dt.time:
"""
Retrieves the current time from the device.

Returns:
An rtc_datetime.time object representing the time to set.
"""
data = self._read_register(Reg.SECONDS, 3)
return dt.time(
hour=self._bcd_to_int(data[2]),
minute=self._bcd_to_int(data[1]),
second=self._bcd_to_int(data[0]),
)

@time.setter
def time(self, time: dt.time) -> None:
"""
Sets the time on the device. This method configures the device's clock.

Args:
hours (int): The hour value to set (0-23 for 24-hour format).
minutes (int): The minute value to set (0-59).
seconds (int): The second value to set (0-59).
time: A rtc_datetime.time object representing the time to set.
"""
if hours < 0 or hours > 23:
raise ValueError("Hour value must be between 0 and 23")
if minutes < 0 or minutes > 59:
raise ValueError("Minute value must be between 0 and 59")
if seconds < 0 or seconds > 59:
raise ValueError("Second vaue must be between 0 and 59")

data = bytes(
[
self._int_to_bcd(seconds),
self._int_to_bcd(minutes),
self._int_to_bcd(hours),
self._int_to_bcd(time.second),
self._int_to_bcd(time.minute),
self._int_to_bcd(time.hour),
]
)
self._write_register(Reg.SECONDS, data)

def get_time(self) -> tuple[int, int, int]:
@property
def date(self) -> dt.date:
"""
Retrieves the current time from the device.
Gets the date of the device.

Returns:
tuple: A tuple containing the current time as (hours, minutes, seconds),
where:
hours (int): The hour value (0-23 for 24-hour format).
minutes (int): The minute value (0-59).
seconds (int): The second value (0-59).
An rtc_datetime.date object representing the date.
"""
data = self._read_register(Reg.SECONDS, 3)
return (
self._bcd_to_int(data[2]), # hours
self._bcd_to_int(data[1]), # minutes
self._bcd_to_int(data[0]), # seconds
data = self._read_register(Reg.DATE, 3)
return dt.date(
year=self._bcd_to_int(data[2]) + 2000,
month=self._bcd_to_int(data[1]),
day=self._bcd_to_int(data[0]),
)

def set_date(self, year: int, month: int, date: int, weekday: int) -> None:
@date.setter
def date(self, date: dt.date) -> None:
"""
Sets the date of the device.

Args:
year (int): The year value to set
month (int): The month value to set (1-12).
date (int): The date value to set (1-31).
weekday (int): The day of the week to set (0-6, where 0 represents Sunday).
date: A rtc_datetime.date object representing the date to set.
"""
if year < 0 or year > 99:
raise ValueError("Year value must be between 0 and 99")
if month < 1 or month > 12:
raise ValueError("Month value must be between 1 and 12")
if date < 1 or date > 31:
raise ValueError("Date value must be between 1 and 31")
if weekday < 0 or weekday > 6:
raise ValueError("Weekday value must be between 0 and 6")

data = bytes(
[
self._int_to_bcd(weekday),
self._int_to_bcd(date),
self._int_to_bcd(month),
self._int_to_bcd(year),
self._int_to_bcd(date.day),
self._int_to_bcd(date.month),
self._int_to_bcd(date.year - 2000),
]
)
self._write_register(
Reg.WEEKDAY, data
Reg.DATE, data
) # this is a weird way to do it but it works

def get_date(self) -> tuple[int, int, int, int]:
@property
def datetime(self) -> dt.datetime:
"""
Gets the date of the device.
Get the current date and time as a combined datetime object.

Returns:
tuple: A 4-tuple (year, month, date, weekday) where:
year (int): The year value (0-99).
month (int): The month value (1-12).
date (int): The date value (1-31).
weekday (int): The day of the week (0-6, where 0 represents Sunday).
rtc_datetime.datetime: The current date and time.
"""
data = self._read_register(Reg.WEEKDAY, 4)
return (
self._bcd_to_int(data[3]), # year
self._bcd_to_int(data[2]), # month
self._bcd_to_int(data[1]), # date
self._bcd_to_int(data[0]), # weekday
)
return dt.datetime.combine(self.date, self.time)

@datetime.setter
def datetime(self, datetime: dt.datetime | str) -> None:
"""
Set the date and time for the RV3028.

Args:
datetime (rtc_datetime.datetime): A datetime object
"""

self.time = datetime.time()
self.date = datetime.date()

def set_alarm(
self, minute: int = None, hour: int = None, weekday: int = None
Expand Down
Loading