Google Earth Engine Authors a2703cb436 v0.1.397
PiperOrigin-RevId: 620069254
2024-04-03 21:41:23 +00:00

319 lines
9.2 KiB
Python

"""A wrapper for dates."""
from __future__ import annotations
import datetime
import math
from typing import Any, Dict, Optional, Union
from ee import _utils
from ee import apifunction
from ee import computedobject
from ee import daterange
from ee import ee_number
from ee import ee_string
from ee import ee_types as types
from ee import serializer
# TODO: Have a separate type when datetime.Datetime unavailable.
_DateType = Union[
datetime.datetime, float, str, 'Date', computedobject.ComputedObject
]
_IntegerType = Union[int, ee_number.Number, computedobject.ComputedObject]
_NumberType = Union[float, ee_number.Number, computedobject.ComputedObject]
_StringType = Union[str, ee_string.String, computedobject.ComputedObject]
class Date(computedobject.ComputedObject):
"""An object to represent dates."""
_initialized = False
# Tell pytype to not complain about dynamic attributes.
_HAS_DYNAMIC_ATTRIBUTES = True
@_utils.accept_opt_prefix('opt_tz')
def __init__(self, date: Union[_DateType], tz: Optional[_StringType] = None):
"""Construct a date.
This sends all inputs (except another Date) through the Date function.
This constructor accepts the following args:
1) A bare date.
2) An ISO string
3) An integer number of milliseconds since the epoch.
4) A ComputedObject.
Args:
date: The date to wrap.
tz: An optional timezone, only usable with a string date.
"""
self.initialize()
func = apifunction.ApiFunction(self.name())
args: Dict[str, Any]
var_name = None
if isinstance(date, datetime.datetime):
args = {'value':
math.floor(serializer.DatetimeToMicroseconds(date) / 1000)}
elif types.isNumber(date):
args = {'value': date}
elif isinstance(date, str):
args = {'value': date}
if tz:
if isinstance(tz, (str, computedobject.ComputedObject)):
args['timeZone'] = tz
else:
raise ValueError(
f'Invalid argument specified for ee.Date(..., opt_tz): {tz}'
)
elif isinstance(date, computedobject.ComputedObject):
if self.is_func_returning_same(date):
# If it's a call that's already returning a Date, just cast.
func = date.func
args = date.args
var_name = date.varName
else:
args = {'value': date}
if tz:
if isinstance(tz, (str, computedobject.ComputedObject)):
args['timeZone'] = tz
else:
raise ValueError(
f'Invalid argument specified for ee.Date(..., opt_tz): {tz}'
)
else:
raise ValueError(f'Invalid argument specified for ee.Date(): {date}')
super().__init__(func, args, var_name)
@classmethod
def initialize(cls) -> None:
"""Imports API functions to this class."""
if not cls._initialized:
apifunction.ApiFunction.importApi(cls, cls.name(), cls.name())
cls._initialized = True
@classmethod
def reset(cls) -> None:
"""Removes imported API functions from this class."""
apifunction.ApiFunction.clearApi(cls)
cls._initialized = False
@staticmethod
def name() -> str:
return 'Date'
def advance(
self,
delta: _NumberType,
unit: _StringType,
timeZone: Optional[_StringType] = None, # pylint: disable=invalid-name
) -> 'Date':
"""Create a new Date by adding the specified units to the given Date.
Args:
delta: The amount to move in the unit. Negative values go back in time.
unit: One of 'year', 'month', 'week', 'day', 'hour', 'minute', or
'second'.
timeZone: The time zone (e.g. 'America/Los_Angeles'); defaults to UTC.
Returns:
An ee.Date.
"""
return apifunction.ApiFunction.call_(
self.name() + '.advance', self, delta, unit, timeZone
)
def difference(self, start: _DateType, unit: _StringType) -> ee_number.Number:
"""Returns the difference between two Dates in the specified units.
Args:
start: The date to compare to.
unit: One of 'year', 'month', 'week', 'day', 'hour', 'minute', or
'second'.
Returns:
Returns an ee.Number based on the average length of the unit.
"""
return apifunction.ApiFunction.call_(
self.name() + '.difference', self, start, unit
)
def format(
self,
format: Optional[_StringType] = None, # pylint: disable=redefined-builtin
timeZone: Optional[_StringType] = None, # pylint: disable=invalid-name
) -> ee_string.String:
"""Convert a date to string.
The format string is described here:
http://joda-time.sourceforge.net/apidocs/org/joda/time/format/DateTimeFormat.html
Args:
format: A Joda Time pattern. If omitted, it uses the ISO default date.
timeZone: The time zone (e.g. 'America/Los_Angeles'); defaults to UTC.
Returns:
An ee.String.
"""
return apifunction.ApiFunction.call_(
self.name() + '.format', self, format, timeZone
)
def get(
self, unit: _StringType, timeZone: Optional[_StringType] = None # pylint: disable=invalid-name
) -> ee_number.Number:
"""Returns the specified unit of this date.
Args:
unit: One of 'year', 'month' (returns 1-12), 'week' (1-53), 'day' (1-31),
'hour' (0-23), 'minute' (0-59), or 'second' (0-59).
timeZone: The time zone (e.g. 'America/Los_Angeles'); defaults to UTC.
Returns:
An ee.Number.
"""
return apifunction.ApiFunction.call_(
self.name() + '.get', self, unit, timeZone
)
def getFraction(
self, unit: _StringType, timeZone: Optional[_StringType] = None # pylint: disable=invalid-name
) -> ee_number.Number:
"""Returns this date's elapsed fraction of the specified unit.
Args:
unit: One of 'year', 'month', 'week', 'day', 'hour', 'minute', or
'second'.
timeZone: The time zone (e.g. 'America/Los_Angeles'); defaults to UTC.
Returns:
An ee.Number between 0 and 1.
"""
return apifunction.ApiFunction.call_(
self.name() + '.getFraction', self, unit, timeZone
)
def getRange(
self, unit: _StringType, timeZone: Optional[_StringType] = None # pylint: disable=invalid-name
) -> daterange.DateRange:
"""Returns a DateRange covering the unit that contains this date.
For example,
Date('2013-3-15').getRange('year')
returns
DateRange('2013-1-1', '2014-1-1').
Args:
unit: One of 'year', 'month', 'week', 'day', 'hour', 'minute', or
'second'.
timeZone: The time zone (e.g. 'America/Los_Angeles'); defaults to UTC.
Returns:
An ee.DateRange.
"""
return apifunction.ApiFunction.call_(
self.name() + '.getRange', self, unit, timeZone
)
def getRelative(
self,
unit: _StringType,
inUnit: _StringType, # pylint: disable=invalid-name
timeZone: Optional[_StringType] = None, # pylint: disable=invalid-name
) -> ee_number.Number:
"""Returns the specified unit of this date relative to a larger unit.
For example, getRelative('day', 'year') returns a value between 0 and 365.
Args:
unit: One of 'month', 'week', 'day', 'hour', 'minute', or 'second'.
inUnit: One of 'year', 'month', 'week', 'day', 'hour', or 'minute'.
timeZone: The time zone (e.g., 'America/Los_Angeles'); defaults to UTC.
Returns:
A 0-based ee.Number.
"""
return apifunction.ApiFunction.call_(
self.name() + '.getRelative', self, unit, inUnit, timeZone
)
def millis(self) -> ee_number.Number:
"""The number of milliseconds since 1970-01-01T00:00:00Z."""
return apifunction.ApiFunction.call_(self.name() + '.millis', self)
@classmethod
def unitRatio(
cls, numerator: _StringType, denominator: _StringType
) -> ee_number.Number:
"""Returns the ratio of the length of one unit to the length of another.
For example, unitRatio('day', 'minute') returns 1440.
Valid units are 'year', 'month', 'week', 'day', 'hour', 'minute', and
'second'.
Args:
numerator: Unit to be divided by the denominator.
denominator: Unit to divide into the numerator.
Returns:
An ee.Number.
"""
return apifunction.ApiFunction.call_(
cls.name() + '.unitRatio', numerator, denominator
)
def update(
self,
year: Optional[_IntegerType] = None,
month: Optional[_IntegerType] = None,
day: Optional[_IntegerType] = None,
hour: Optional[_IntegerType] = None,
minute: Optional[_IntegerType] = None,
second: Optional[_NumberType] = None,
timeZone: Optional[_StringType] = None, # pylint: disable=invalid-name
) -> 'Date':
"""Create a new Date by setting one or more of the units of the given Date.
If a timeZone is given the new value(s) is interpreted in that zone. Skip or
set to None any argument to keep the same as the input Date.
Args:
year: Set the year.
month: Set the month.
day: Set the day.
hour: Set the hour.
minute: Set the minute.
second: Set the second.
timeZone: The time zone (e.g. 'America/Los_Angeles'); defaults to UTC.
Returns:
An ee.Date.
"""
return apifunction.ApiFunction.call_(
self.name() + '.update',
self,
year,
month,
day,
hour,
minute,
second,
timeZone,
)