Source code for geetools.ee_date

"""Extra methods for the :py:class:`ee.Date` class."""
from __future__ import annotations

from datetime import datetime
from zoneinfo import ZoneInfo

import ee

from .accessors import register_class_accessor

[docs] EE_EPOCH = datetime(1970, 1, 1, 0, 0, 0)
@register_class_accessor(ee.Date, "geetools") class DateAccessor: """Toolbox for the :py:class:`ee.Date` class.""" def __init__(self, obj: ee.Date): """Initialize the Date class.""" self._obj = obj @classmethod def fromEpoch(cls, number: int, unit: str = "day") -> ee.Date: """Set an the number of units since epoch (1970-01-01). Parameters: number: The number of units since the epoch. unit: The unit to return the number of. One of: ``second``, ``minute``, ``hour``, ``day``, ``month``, ``year``. Returns: The date as a :py:class:`ee.Date` object. Examples: .. jupyter-execute:: import ee, geetools from geetools.utils import initialize_documentation initialize_documentation() d = ee.Date.geetools.fromEpoch(49, 'year') d.format("YYYY-MM-DD").getInfo() """ cls.check_unit(unit) return ee.Date(EE_EPOCH.isoformat()).advance(number, unit) @classmethod def fromDOY(cls, doy: int, year: int) -> ee.Date: """Create a date from a day of year and a year. This method is computing doy from a year agnostic representation, the year parameter being only used to compute a complete date. the doy should have been generated from the :py:meth:`geetools.DateAccessor.toDOY` method. Parameters: doy: The day of year. year: The year. Returns: The date as a :py:class:`ee.Date` object. Examples: .. jupyter-execute:: import ee, geetools from geetools.utils import initialize_documentation initialize_documentation() d = ee.Date.geetools.fromDOY(1, 2020) d.format("YYYY-MM-DD").getInfo() """ d, y = ee.Number(doy).toInt(), ee.Number(year).toInt() # create the 2 masks to adjust the day of year divisibleBy4 = y.mod(4).eq(0) divisibleBy100 = y.mod(100).eq(0) divisibleBy400 = y.mod(400).eq(0) isNotLeap = divisibleBy400.Or(divisibleBy4.And(divisibleBy100.Not())).Not().toInt() isAfterMarch = d.gte(60).toInt() d = d.subtract(isNotLeap.multiply(isAfterMarch)) return ee.Date.fromYMD(y, 1, 1).advance(d.subtract(1), "day") @classmethod def now(cls) -> ee.Date: """Create a date on current date. Returns: The current date. Examples: .. jupyter-execute:: import ee, geetools from geetools.utils import initialize_documentation initialize_documentation() d = ee.Date.geetools.now() d.format("YYYY-MM-dd").getInfo() """ return ee.Date(datetime.now().isoformat()) def to_datetime(self, tz: str | ZoneInfo = ZoneInfo("UTC")) -> datetime: """Convert a :py:class:`ee.Date` to a :py:class:`datetime.datetime`. Since `ee.Date` object is not timezone aware, there is no way to get the timezone from it, thus it must be passed as argument. Args: tz: time zone, defaulted to "UTC". Other names can be found here: https://www.joda.org/joda-time/timezones.html Returns: The :py:class:`datetime.datetime` representation of the :py:class:`ee.Date`. Examples: .. jupyter-execute:: import ee, geetools from geetools.utils import initialize_documentation initialize_documentation() d = ee.Date('2020-01-01').geetools.to_datetime() d.strftime('%Y-%m-%d') """ tz = tz if isinstance(tz, ZoneInfo) else ZoneInfo(tz) timestamp = self._obj.millis().getInfo() / 1000 return datetime.fromtimestamp(timestamp, tz=ZoneInfo("UTC")).astimezone(tz) def getUnitSinceEpoch(self, unit: str = "day") -> ee.Number: """Get the number of units since epoch (1970-01-01). Parameters: unit: The unit to return the number of. One of: ``second``, ``minute``, ``hour``, ``day``, ``month``, ``year``. Returns: The number of units since the epoch. Examples: .. jupyter-execute:: import ee, geetools from geetools.utils import initialize_documentation initialize_documentation() d = ee.Date('2020-01-01').geetools.getUnitSinceEpoch('year') d.getInfo() """ self.check_unit(unit) return self._obj.difference(EE_EPOCH, unit).toInt() def isLeap(self) -> ee.Number: """Check if the year of the date is a leap year. Returns: ``1`` if the year is a leap year, ``0`` otherwise. Examples: .. jupyter-execute:: import ee, geetools from geetools.utils import initialize_documentation initialize_documentation() isLeap = ee.Date('2020-01-01').geetools.isLeap() isLeap.getInfo() """ year = self._obj.get("year") divisibleBy4 = year.mod(4).eq(0) divisibleBy100 = year.mod(100).eq(0) divisibleBy400 = year.mod(400).eq(0) # d400 or (d4 and not d100) isLeap = divisibleBy400.Or(divisibleBy4.And(divisibleBy100.Not())) return isLeap.toInt() def toDOY(self) -> ee.Number: """Convert a date to a day of year. This method is computing a year agnostic day of year. It means that the year will always be described as a 366 day interval and the day of year will be between 0 and 365. it means that for non leap year all days will be shifted by 1 after the 28th of February. Thus the 1st of march will always be the 60th day of year. Returns: The day of year. Examples: .. jupyter-execute:: import ee, geetools from geetools.utils import initialize_documentation initialize_documentation() doy = ee.Date('2020-01-01').geetools.toDOY() doy.getInfo() """ # create the 2 masks for day offset isNotLeap = self.isLeap().Not() isAfterMarch = self._obj.get("month").gte(3).toInt() # create the day of year doy = self._obj.getRelative("day", "year") return doy.add(isNotLeap.multiply(isAfterMarch)) @staticmethod def check_unit(unit: str) -> None: """Check if the provided value is a valid time unit. Parameters: unit: The unit to check. Raises: ValueError: If the unit is not valid. """ if unit not in (units := ["second", "minute", "hour", "day", "month", "year"]): raise ValueError(f"unit must be one of: {','.join(units)}")