def date_in_timezone(date_, timezone): """ Given a naive postgres date object (postgres doesn't have tzd dates), returns a timezone-aware timestamp for the start of that date in that timezone. E.g. if postgres is in 'America/New_York', SET SESSION TIME ZONE 'America/New_York'; CREATE TABLE tz_trouble (to_date date, timezone text); INSERT INTO tz_trouble(to_date, timezone) VALUES ('2021-03-10'::date, 'Australia/Sydney'), ('2021-03-20'::date, 'Europe/Berlin'), ('2021-04-15'::date, 'America/New_York'); SELECT timezone(timezone, to_date::timestamp) FROM tz_trouble; The result is: timezone ------------------------ 2021-03-09 08:00:00-05 2021-03-19 19:00:00-04 2021-04-15 00:00:00-04 """ return func.timezone(timezone, cast(date_, DateTime(timezone=False)))
class BaseModel(db.Model): __abstract__ = True created_date = db.Column(db.DateTime, server_default=func.timezone( 'UTC', func.current_timestamp())) modified_date = db.Column(db.DateTime, onupdate=func.timezone('UTC', func.current_timestamp())) def save(self): """Adds the object to the session and commits the transaction""" db.session.add(self) db.session.commit() def delete(self): """Deletes the object and commits the transaction""" db.session.delete(self) db.session.commit()
class TblReservation(Base): __tablename__ = "tbl_reservation" reserv_id = Column(Integer, primary_key=True, autoincrement=True) # room_number = Column(Integer, nullable=False) reserve_start_date = Column(Date, default=func.timezone( 'UTC', func.current_timestamp()), nullable=False) reserve_end_date = Column(Date, nullable=False) # cust_id = Column(Integer, nullable=False) room = relationship('TblRoom', backref='reserveroom') room_number = Column(Integer, ForeignKey('tbl_room.room_number')) cust_id = Column(Integer, ForeignKey('tbl_customer.cust_id')) cust = relationship('TblCustomer', backref='reservecust') def __init__(self, reserve_start_date, reserve_end_date, room, cust): self.reserve_start_date = reserve_start_date self.reserve_end_date = reserve_end_date self.room = room self.cust = cust
def query_work_day_stats( company_id, start_date=None, end_date=None, first=None, after=None, tzname="Europe/Paris", ): tz = gettz(tzname) if after: max_time, user_id_ = parse_datetime_plus_id_cursor(after) max_date = max_time.date() end_date = min(max_date, end_date) if end_date else max_date query = (Activity.query.join(Mission).join( Expenditure, and_( Activity.user_id == Expenditure.user_id, Activity.mission_id == Expenditure.mission_id, ), isouter=True, ).with_entities( Activity.id, Activity.user_id, Activity.mission_id, Mission.name, Activity.start_time, Activity.end_time, Activity.type, Expenditure.id.label("expenditure_id"), Expenditure.type.label("expenditure_type"), func.generate_series( func.date_trunc( "day", func.timezone( tzname, func.timezone("UTC", Activity.start_time), ), ), func.timezone( tzname, func.coalesce( func.timezone("UTC", Activity.end_time), func.now(), ), ), "1 day", ).label("day"), ).filter( Mission.company_id == company_id, ~Activity.is_dismissed, Activity.start_time != Activity.end_time, )) query = _apply_time_range_filters( query, to_datetime(start_date, tz_for_date=tz), to_datetime(end_date, tz_for_date=tz, convert_dates_to_end_of_day_times=True), ) has_next_page = False if first: activity_first = max(first * 5, 200) query = query.order_by(desc("day"), desc( Activity.user_id)).limit(activity_first + 1) has_next_page = query.count() > activity_first query = query.subquery() query = (db.session.query(query).group_by( query.c.user_id, query.c.day, query.c.mission_id, query.c.name).with_entities( query.c.user_id.label("user_id"), query.c.day, func.timezone("UTC", func.timezone(tzname, query.c.day)).label("utc_day_start"), query.c.mission_id.label("mission_id"), query.c.name.label("mission_name"), func.min( func.greatest( query.c.start_time, func.timezone("UTC", func.timezone(tzname, query.c.day)), )).label("start_time"), func.max( func.least( func.timezone( "UTC", func.timezone( tzname, query.c.day + func.cast("1 day", Interval)), ), func.coalesce(query.c.end_time, func.now()), )).label("end_time"), func.bool_or( and_( query.c.end_time.is_(None), query.c.day == func.current_date(), )).label("is_running"), *[ func.sum( case( [( query.c.type == a_type.value, extract( "epoch", func.least( func.timezone( "UTC", func.timezone( tzname, query.c.day + func.cast("1 day", Interval), ), ), func.coalesce(query.c.end_time, func.now()), ) - func.greatest( query.c.start_time, func.timezone( "UTC", func.timezone(tzname, query.c.day), ), ), ), )], else_=0, )).label(f"{a_type.value}_duration") for a_type in ActivityType ], func.greatest(func.count(distinct(query.c.expenditure_id)), 1).label("n_exp_dups"), func.count(distinct(query.c.id)).label("n_act_dups"), *[ func.sum( case( [(query.c.expenditure_type == e_type.value, 1)], else_=0, )).label(f"n_{e_type.value}_expenditures") for e_type in ExpenditureType ], ).subquery()) query = (db.session.query(query).group_by( query.c.user_id, query.c.day).with_entities( query.c.user_id.label("user_id"), query.c.day, func.array_agg(distinct( query.c.mission_name)).label("mission_names"), func.min(query.c.start_time).label("start_time"), func.max(query.c.end_time).label("end_time"), func.bool_or(query.c.is_running).label("is_running"), *[ func.sum( getattr(query.c, f"{a_type.value}_duration") / query.c.n_exp_dups).cast(Integer).label( f"{a_type.value}_duration") for a_type in ActivityType ], *[ func.sum( getattr(query.c, f"n_{e_type.value}_expenditures") / query.c.n_act_dups).cast(Integer).label( f"n_{e_type.value}_expenditures") for e_type in ExpenditureType ], ).order_by(desc("day"), desc("user_id")).subquery()) query = db.session.query(query).with_entities( *query.c, extract("epoch", query.c.end_time - query.c.start_time).label("service_duration"), reduce( lambda a, b: a + b, [ getattr(query.c, f"{a_type.value}_duration") for a_type in ActivityType ], ).label("total_work_duration"), ) results = query.all() if after: results = [ r for r in results if r.day.date() < max_date or ( r.day.date() == max_date and r.user_id < user_id_) ] if first: if has_next_page: # The last work day may be incomplete because we didn't fetch all the activities => remove it results = results[:-1] if len(results) > first: results = results[:first] has_next_page = True return results, has_next_page