In my app, when the user adds a document, I store the timestamp of the document creation.
My app calls an api written in Flask.
I store the timestamp in python with:
timestamp = firestore.SERVER_TIMESTAMP
The timestamp is stored in Firestore as:
When the user requests the document, I process the timestamp to be shown in a readable way with the following code:
def get_proper_timestamp(timestamp) -> str:
if not timestamp:
return None
date: datetime.date = timestamp.date()
day = str(date.day)
month = str(date.month)
year = str(date.year)
if len(month) == 1:
month = f"0{month}"
proper_date = f"{day} {month} {year}"
weekday: int = date.weekday()
proper_weekday: str = get_day_from_int(weekday)
time = timestamp.time()
hour = time.hour
minute = time.minute
temp = strptime(f"{hour}:{minute}", "%H:%M")
proper_time: str = strftime("%I:%M %p", temp)
return f"{proper_weekday}, {proper_date}.{proper_time}"
def get_day_from_int(integer: int) -> str:
mapper = {
0: "Mon",
1: "Tues",
2: "Wed",
3: "Thur",
4: "Fri",
5: "Sat",
6: "Sun",
}
return mapper[integer]
In Postman, this outputs:
My flutter code reads the timestamp in a straight-forward way:
List<DataCell> _buildCells(Map<String, dynamic> entry) {
String timestamp = entry["timestamp"];
print(timestamp);
return [
DataCell(Text(entry["timestamp"].toString())),
DataCell(Text(entry["amount"].toString())),
DataCell(Text(entry["after"].toString()))
];
}
This outputs:
This is four hours before. Which I think it means that I'm the UTF offset somewhere.
But why is Postman showing the date correctly?
I'd actually prefer flask to return the raw timestamp, but I couldn't deserialize it.
I read most the answers on SO on this issue, but nothing worked for me.
The value stored in Firestore is an internal structure, which is timezone agnostic (internally based on UTC).
The value you see in the Firestore console has been localized to your local timezone for your convenience, which is apparently UTC+4.
The value you get when fetching the time with Flutter is a Timestamp, also UTC-based and timezone agnostic. When you convert that with .toDate() (or the equivalent with millisecondsSinceEpoch), it becomes a traditional DateTime, which is internally in UTC, but carries a localtime offset, and normally displays itself as localtime. This is probably the value you are seeing as +4.
Related
I am trying to link a script to an excel file using xlwings that takes data from the sheet and queries skyscanners API to return flight prices. When I type dates in manually (yyyy-mm-dd), it works fine since it is a String. However, when I try to run the query by automatically loading the date from excel, it cannot work with excel's date format.
When I just return type(dept_date), I get the error: Unexpected Python Error: TypeError: must be a pywintypes time object (got type)"
When I try to convert dept_date to a string first and return that type, I get: Unexpected Python Error: TypeError: Objects of type 'type' can not be converted to a COM VARIANT.
For context, the API takes the yyyy-mm-dd format and when I print the querystring, excel automatically adds the "00:00:00" to the end of the date. I've tried subscripting [:9] to just pull the date but since it isn't a String, that doesn't work. I've tried casting to a string and doing that too with no luck. Any help is appreciated! I've included the whole function below just in case:
#xw.func
def airfare(trip_type, home_iata, dest_iata, dept_date, return_date=""):
""" Pass home IATA, destination IATA, departure/return dates (yyyy-mm-dd). Returns average of "best"
results. All params are strings. To account for last minute booking, if flight is < 500, result doubles.
If flight is 500+, it adds a 30% surcharge onto result. Trip type 1 = Round Trip; 0 = One-Way
"""
home_iata = str(home_iata)
dest_iata = str(dest_iata)
dept_date = str(dept_date)
# dept_date.strftime('%d-%m-%Y')
if trip_type == "0":
return_date = ""
url = "https://skyscanner44.p.rapidapi.com/search"
querystring = {"adults": "1",
"origin": home_iata,
"destination": dest_iata,
"departureDate": dept_date,
"returnDate": return_date,
"cabinClass": "economy",
"currency": "USD"}
headers = {
"X-RapidAPI-Key": "XXX",
"X-RapidAPI-Host": "skyscanner44.p.rapidapi.com"
}
# return querystring
# return type(dept_date)
response = requests.request("GET", url, verify=False, headers=headers, params=querystring).json()
best_flight = int(response["itineraries"]["buckets"][0]["items"][0]["price"]["raw"])
if best_flight < 500:
best_flight *= 2
else:
best_flight *= 1.3
return "$" + str(best_flight)
I am using Django and have a problem with a date that I need to calculate.
The Variable data > test should be 17:00 and not 15:00. Why does this happen as soon as I format the date?
My timezone is Europe/Berlin. Changing the timezone has to effect to the time printing in test. It is always -2h
def date(req):
now = timezone.now()
model = MyModel.objects.filter(date__gt=now).first()
next = model.date
future = timezone.timedelta(hours=float(model.future)) #model.future = 1.5
open = next-future
date = next.strftime('%Y/%m/%d')
data = {
'next': next,
'date': date,
'time': open.astimezone(timezone.utc).strftime('%Y-%m-%d %H:%M:%S.%f'),
'test': open.strftime('%Y/%m/%d %H:%M:%S%z')
}
What I get:
next: 20. November 2021 18:30
date: 2021/11/20
time: 2021-11-20 15:15:00.000000
test: 2021/11/20 15:00:00+0000
https://docs.djangoproject.com/en/3.2/topics/i18n/timezones/#naive-and-aware-datetime-objects
You should use:
from django.utils import timezone
now = timezone.now()
Datetime isn't time-zone aware.
You cut the timezone info (the offset +02:00) with .strftime(). You need to include it with %z.
In case you want to convert it to a time string with the offset already added.
open.astimezone(timezone.utc).strftime('%Y-%m-%d %H:%M:%S.%f')
This works for me.
from django.utils import timezone
def date(req):
now = timezone.now()
model = MyModel.objects.filter(date__gt=now).first()
next = model.date
future = timezone.timedelta(hours=float(model.future)) #model.future = 1.5
open = next-future
date = timezone.localtime(open)
data = {
'next': date.strftime('%Y/%m/%d %H:%M:%S'),
'date': date.strftime('%Y/%m/%d'),
'time': date.strftime('%H:%M:%S'),
}
I'm working on a project in which when users mention a specific date range from and to, the corresponding data gets printed. The page takes input from users through input type date when the specific date is mentioned the value of date passes to user_input_date_to and user_input_date_to. But when I'm executing I'm getting the error ValueError at / time data '' does not match format '%Y-%m-%d'
My views file
def indexview(request):
url=requests.get('https://data.covid19india.org/v4/min/timeseries.min.json')
json_data=url.json()
user_input_state=''
user_input_date_from=''
user_input_date_to=''
user_data_type=''
user_required_type=''
if request.method == 'POST':
user_input_state=request.POST.get('state')
x=request.POST['date_from']
user_input_date_to=request.POST['date_to']
user_data_type=request.POST.get('data_period')
user_required_type=request.POST.get('required_type')
#To store dates list
start_date =user_input_date_from
end_date = user_input_date_to
start_date_object = dt.datetime.strptime(start_date,"%Y-%m-%d").date()
end_date_object = dt.datetime.strptime(end_date,"%Y-%m-%d").date()
days = end_date_object - start_date_object
dates=[]
otp=[]
for i in range(days.days+1):
dates.append(str(start_date_object+dt.timedelta(days=i)))
for i in dates:
try:
otp.append(json_data[user_input_state]['dates'][i][user_data_type][user_required_type])
except KeyError:
otp.append(0)
dict_pass={
'dates':dates,
'otp':otp
}
return render(request,'index.html',dict_pass)
HTML date form
<input type="date" name="date_from"><br>
<input type="date" name="date_to">
The problem is, that you are trying to create datetime object of format '%Y-%m-%d' from the invalid user input (in your case it's empty string).
You should validate user input first, then do the business logic.
You could do it manually, or try to use existing libraries for the validation
(e.g. pydantic, marshmallow ...)
this code cannot work and give json serializable error
class Bank(peewee.Model): // create Bank table
bank_id = peewee.PrimaryKeyField()
bank_name = peewee.CharField()
account_no = peewee.CharField()
ifc_code = peewee.CharField()
swift_code = peewee.CharField(null = True)
modify_date = peewee.DateTimeField(default=datetime.datetime.now(),formats=['%Y-%m-%d'])/*date in yyyy-mm-dd formate*/
status = peewee.IntegerField(default = 0)
class Meta:
database = db
This answer is very incorrect - please see my answer below (#coleifer).
The default date that you are providing is not a datetime object. Rather it's a string!
modify_date = peewee.DateTimeField(default=datetime.datetime.now().strftime('%Y-%m-%d'))
type(datetime.datetime.now().strftime('%Y-%m-%d')) --> str
You can pass default current datetime object like this:
date = datetime.datetime.now().strftime('%Y-%m-%d')
need_date = datetime.strptime(date, '%Y-%m-%d')
modify_date = peewee.DateTimeField(default=need_date)
or
peewee.DateTimeField(default=datetime.datetime.now)
It looks like non-timezone aware datetimes work fine, so if you're using UTC then you can store datetime.utcnow() as that returns the current UTC date and time with tzinfo None i.e. as a "naive" datetime object.
I found this solution to store and retrieve the timezone aware field as text, however it's not ideal as the datetime object isn't being stored.
from datetime import datetime
from peewee import *
class TimestampTzField(Field):
"""
A timestamp field that supports a timezone by serializing the value
with isoformat.
"""
field_type = "TEXT"
def db_value(self, value: datetime) -> str:
if value:
return value.isoformat()
def python_value(self, value: str) -> str:
if value:
return datetime.fromisoformat(value)
https://compileandrun.com/python-peewee-timezone-aware-datetime/
If you want to store a date, use the DateField. Also, the default needs to be a callable -- in other words, leave OFF the parentheses!
class Bank(peewee.Model): // create Bank table
bank_id = peewee.PrimaryKeyField()
bank_name = peewee.CharField()
account_no = peewee.CharField()
ifc_code = peewee.CharField()
swift_code = peewee.CharField(null = True)
modify_date = peewee.DateField(default=datetime.date.today)
status = peewee.IntegerField(default = 0)
class Meta:
database = db
When it comes time to serialize this as Json, just use a custom json formatter that can handle python datetime.date objects. This is the proper way. You should always store your data using the appropriate format and worry about presentation (and serialization) in another layer.
It is very simple to extend Python's json serializer to handle unsupported types:
def convert_date(o):
if isinstance(o, datetime.date):
return o.__str__()
json.dumps(my_obj, default=convert_date)
I am using Django REST Framework, specifically a ModelSerializer instance, to receive some date/time information, among other fields. The Django form which POSTs or PUTs to my view is using a single field for date, and separate fields for hour, minute, and am/pm.
I wrote a function to deal with recombining the values into a Python datetime object, but for some reason, when my function returns a correct datetime, the time portion is getting zero'ed out when the datetime is assigned back to the serializer object for saving.
I am new to DRF so maybe I just need to approach this another way altogether....
def roomeventrequest(request, roomevent_id):
"""
THIS IS THE VIEW
"""
...
elif request.method == 'PUT':
data = JSONParser().parse(request)
roomevent = RoomEvent.objects.get(pk=roomevent_id)
serializer = RoomEventSerializer(roomevent, data=data)
if serializer.is_valid():
serializer.data['start_datetime'] = _process_datetime(serializer.validated_data['start_datetime'],
data['start_hour'],
data['start_min'],
data['start_ampm'])
serializer.data['end_datetime'] = _process_datetime(serializer.validated_data['start_datetime'],
data['end_hour'],
data['end_min'],
data['start_ampm'])
print (serializer.data['start_datetime'])
print (serializer.data['end_datetime'])
serializer.save()
return JSONResponse(serializer.data, status=201)
return JSONResponse(serializer.errors, status=400)
def _process_datetime(date_obj, hour, minute, ampm):
print (date_obj)
if ampm == 'am' and hour == 12:
hour = 0
elif ampm == 'pm':
hour += 12
return_date = date_obj.replace(minute=int(minute), hour=int(hour))
print(return_date)
return return_date
And the above outputs the following from the print statements:
2015-05-21 00:00:00
2015-05-21 08:00:00
2015-05-21 00:00:00
2015-05-21 09:00:00
2015-05-21T00:00:00
2015-05-21T00:00:00
Why is the resulting time portion blank? Where have I gotten off track here?
The problem you are seeing is that you are modifying the serializer data from the outside, which doesn't actually propagate to the data used internally. So even though you are changing the start_datetime and end_datetime fields, internally DRF still sees the datetime objects that only contain the date.
You have a few options
Validate the date fields in a separate serializer (or just manually) and construct the correct date input on your own.
Combine all of the date fields before passing them into the serializer, such that they match one of the Django datetime input formats.
Directly modify serializer.validated_data (instead of serializer.data) in your code. This is what is passed on to create and update.
I would recommend avoiding #3 for now, as the validated_data dictionary is designed to be read-only and that may be enforced in the future. So that leaves you with #1 and #2, both of which work require modifications to different parts of your code and work better for different situations.
The first option works best if your validation needs to return errors to the frontend that need to match the specific field, instead of just commenting on the incorrect date format. But it also requires the creation of a custom MultipartDatetimeSerializer that is used for validating across all of the fields.
from datetime import date, datetime, time
class MultipartDatetimeSerializer(serializers.Serializer):
date = serializers.DateField()
hour = serializers.IntegerField(
min_value=1,
max_value=12
)
minute = serializers.IntegerField(
min_value=0,
max_value=59,
)
period = serializers.ChoiceField(
choices=(
('am', 'A.M.', ),
('pm', 'P.M.', ),
)
)
def to_internal_value(self, data):
parsed_data = super(MultipartDatetimeSerializer, self).to_internal_value(data)
hour = parsed_data['hour']
if parsed_data['period'] == 'pm':
hour += 12
elif hour == 12:
hour = 0
time_data = time(
hour=hour,
minute=parsed_data['minute']
)
return datetime.combine(
date=parsed_data['date'],
time=time_data
)
def to_representation(self, instance):
"""
Convert a datetime to a dictionary containing the
four different fields.
The period must be manually determined (and set), so there
is some pre-processing that has to happen here.
"""
obj = {
"date": instance.date,
"hour": instance.hour,
"minute": instance.minute,
}
if obj["hour"] > 12:
obj["hour"] -= 12
obj["period"] = 'pm'
else:
if obj["hour"] == 0:
obj["hour"] = 12
obj["period"] = 'am'
return super(MultipartDatetimeSerializer, self).to_representation(obj)
This serializer can now be used to split a datetime into the date, hour, minute, and period components.
obj = datetime.now()
serializer = MultipartDatetimeSerializer(obj)
print(serializer.data)
As well as combine them back together
data = {
"date": "2015-01-01",
"hour": "11",
"minute": "59",
"period": "pm",
}
serializer = MultipartDatetimeSerializer(data=data)
if serializer.is_valid():
print(serializer.to_internal_value(serializers.validated_data))
else:
print(serializer.errors)
The second option works best if you just need to return an error saying that the data given is not an actual date. You can find a date format that closely matches what is being entered and then concatenate the incoming data to match that.
In your case, the closest date format appears to be %Y-%m-%d %I:%M %p which will match a date like 2015-01-01 11:59 PM.
So, all that is left is to set the date format of the date field on your serializer to accept the above format (as well as ISO 8601, the default), which is as simple as setting input_formats on the field to
['iso-8601', '%Y-%m-%d %I:%M %p']
And changing the data passed to the serializer to concatenate the incoming values to match the field
data = JSONParser().parse(request)
data['start_datetime'] = "%s %s:%s %s" % (data['start_datetime'], data['start_hour'], data['start_min'], data['start_ampm'], )
data['end_datetime'] = "%s %s:%s %s" % (data['end_datetime'], data['end_hour'], data['end_min'], data['end_ampm'], )
Note that I'm always using the %s modifier instead of the %d modifier as DRF can handle incorrect numbers being passed into the fields, and it prevents an unhandled exception from occurring when a string is passed in.