GDDateTime – Inspired by the need for better DateTime handling in Godot

When my son Otto was born, my partner and I were having a small issue timing and calculating the feed times and amounts in a quick and precise way. Sure, we could use a notepad and make figure it out day by day, but that small repeatative task got annoying quickly. At this point, i was learning small bits of python and also had been meddling with Godot game engne for the 6 months previous.

I took this “problem” we had and decided to fix it. I built a very small android app ( which i will share withe veryone at a later date ), which takes in the “first feed time”, “last feed time”, how many feeds to do, and total amount of formula to feed over the day. From here i calculate the time and amount of each feed.

First off, I was using Godot’s built in time function, but quickly ran into issues trying to manipulate the time easily. I couldn’t find any way to really change the time via a function. There are many ways to GET the current time/ date, but none to manipulate. From my very minor time looking and playing with Python, i was reminded of the Pendulum module, which has nice clean ways to do this. This is when i decided to try make a drop-in module in gdscript to sort of replicate the basics of Pendulum. It took me a day or 2 of tinkering but below is what i came up with.

Installation:

Download the GDDateTime.gd script
Add it to your Godot project
Use the classes from within

Usage:

Create a new time handler with e.g

var timeutil = GDDateTime.new()


Use your new handler to create a time date and datetime objects
var t = timeutil.create_new_time(15,15,00) – creates a new time object at time given in constructors ( this would be 15:15:00 or 3:15:00PM )
var t = timeutil.create_new_time_now() – creates a new time object with system time at time of creation ( this is pulled from the system the script is running on )
var d = timeutil.create_new_date(1995,12,12) – creates a new date object with the given constructors ( this would be 1995:12:12 )
var d = timeutl.create_new_date_now() – creates a new date object on date of creation ( this is pulled from the system the script is running on )
var dt = timeutil.create_new_datetime(1995,12,12,15,15,00) – creates a new datetime object at date and time given in constructors ( this would be 1995:12:12 — 15:15:00 )
var dt = timeutil.create_new_datetime_now() – creates a new datetime object at date and time of creation ( this is pulled from the system the script is running on )
Methods
return_to_string() returns a string format
add_time() – adds time to your date and time objects
subtract_time() – removes time to your date and time objects
set_time/date/datetime() – sets the date or time object to the new constructors
get_hours/minutes/seconds_until_next_day() – returns int
get_mintue/second_of_day() – returns int
is_leapyear() – returns bool
get_time_between_unix() – return dict of TOTAL SECONDS, Hours, minutes, seconds


Everyone is welcome and encouraged to use and play with this code for easy time and date creation and manipulation in Godot!

extends Node

# Time tracking class that provides similar functionality to Python's pendulum
class_name GDDateTime

const DAYS_BEEN_PER_MONTH = [31,59,90,120,151,181,212,243,273,304,334,365]
const DAYS_BEEN_PER_MONTH_LEAP = [31,60,91,121,152,182,213,244,274,305,335,336]
const DAYS_IN_MONTH = [31,28,31,30,31,30,31,31,30,31,30,31]
const DAYS_IN_MONTH_LEAP = [31,28,31,30,31,30,31,31,30,31,30,31]

#Time Class to create new time
class GTime:
	var hour: int
	var minute: int
	var second: int
	var timestamp: int
	var init_values: Dictionary
	
	func _init(h: int = 0, m: int = 0, s: int = 0):
		hour = clampi(h, 0, 23)
		minute = clampi(m, 0, 59)
		second = clampi(s, 0, 59)
		init_values = {"hour":hour,"minute":minute,"second":second}
		timestamp = Time.get_unix_time_from_datetime_dict(init_values) #returns on day 1 of unix time 1975
		var ts = {"timestamp":timestamp}
		init_values.merge(ts) 
		
	func get_hours_until_next_day(hour = hour):
		return 24 - hour
		
	func get_minutes_until_next_day(hour = hour, minute = minute):
		var h = get_hours_until_next_day(hour) * 60
		var m = (60-(minute)) 
		return h + m
		
	func get_seconds_until_next_day(hour = hour, minute = minute, second = second):
		var h = get_hours_until_next_day(hour) * 3600
		var m = (60 - (minute)) * 60
		var s = (60-second)
		return h + m + s
		
	func get_minute_of_day(hour = hour, minute = minute):
		return (hour * 60) + minute
	
	func get_second_of_day(hour = hour, minute = minute, second = second):
		return (hour * 3600) + (minute * 60) + second
		
	func return_to_string() -> String:
		return "%02d:%02d:%02d" % [hour, minute, second]
		
	func add_time(hours:int,mins:int, seconds:int):
		second += seconds
		while second >= 60:
			minute += 1
			second -= 60
		minute += mins
		while minute >= 60:
			hour += 1
			minute -= 60
		hour += hours
		while hour >= 24:
			hour -= 24

	func subtract_time(hours:int, mins:int, seconds:int):
		second -= seconds
		while second < 0:
			minute -= 1
			second += 60
			minute -= mins
		while minute < 0:
			hour -= 1
			minute += 60
			hour -= hours
		while hour < 0:
			hour += 24

	func set_time(hours:int,minutes:int, seconds:int):
		hour = clampi(hours, 0, 23)
		minute = clampi(minutes, 0, 59)
		second = clampi(seconds, 0, 59)
		
		
class GDateTime:
	var year: int
	var month: int
	var day: int
	var hour: int
	var minute: int
	var second: int
	var init_values: Dictionary
	var timestamp: int
	
	func _init(y: int = 0, mo: int = 0, d: int = 0, h: int = 0, m: int = 0, s: int = 0):
		year = clampi(y,1,9999)
		month = clampi(mo,1,12)
		day = clampi(d,1,get_days_in_month(month,year))
		hour = clampi(h, 0, 23)
		minute = clampi(m, 0, 59)
		second = clampi(s, 0, 59)
		init_values = {"year":year,"month":month,"day":day,"hour":hour,"minute":minute,"second":second}
		timestamp = Time.get_unix_time_from_datetime_dict(init_values)
		var ts = {"timestamp":timestamp}
		init_values.merge(ts) 
		
	func is_leapyear(year = year) -> bool:
		if (year % 4 == 0 and year % 100 != 0) or (year % 400 == 0):
			return true
		else:
			return false
	
	func get_minute_of_day(hour = hour, minute = minute):
		return (hour * 60) + minute
	
	func get_second_of_day(hour = hour, minute = minute, second = second):
		return (hour * 3600) + (minute * 60) + second
	
	func get_hours_until_next_day(hour = hour):
		return 24 - hour
		
	func get_minutes_until_next_day(hour = hour, minute = minute):
		var h = get_hours_until_next_day(hour) * 60
		var m = (60-(minute)) 
		return h + m
		
	func get_seconds_until_next_day(hour = hour, minute = minute, second = second):
		var h = get_hours_until_next_day(hour) * 3600
		var m = (60 - (minute)) * 60
		var s = (60-second)
		return h + m + s
	
	func get_day_of_year(day = day,month = month, year = year):
		var x = 0
		if month == 1:
			return day
		elif (year % 4 == 0 and year % 100 != 0) or (year % 400 == 0):
			for i in range(month - 1):
				x += DAYS_IN_MONTH[i-1]
			x += day
			return x
		else:
			for i in range(month - 1):
				x += DAYS_IN_MONTH[i-1]
			x += day
			return x
	
	func get_days_in_month(month: int, year: int) -> int:    
		if month in [1, 3, 5, 7, 8, 10, 12]:
			return 31
		elif month == 2:
			# Leap year check
			if (year % 4 == 0 and year % 100 != 0) or (year % 400 == 0):
				return 29
			else:
				return 28
		else:
			return 30
	
	func return_to_string() -> String:
		return "%02d:%02d:%02d -- %02d:%02d:%02d" % [year,month,day,hour, minute, second]
		
	func add_time(years:int, months:int, days:int,hours:int,mins:int, seconds:int):
		second += seconds
		while second >= 60:
			minute += 1
			second -= 60
		minute += mins
		while minute >= 60:
			hour += 1
			minute -= 60
		hour += hours
		while hour >= 24:
			day += 1
			hour -= 24
		year += years
		month += months
		while month > 12:
			year += 1
			month -= 12
		day += days
		while day > get_days_in_month(month,year):
			day -= get_days_in_month(month,year)
			month += 1
			if month > 12:
				month = 1
				year += 1
		
	func subtract_time(years:int, months:int, days:int, hours:int, mins:int, seconds:int):
		second -= seconds
		while second < 0:
			minute -= 1
			second += 60
			minute -= mins
		while minute < 0:
			hour -= 1
			minute += 60
			hour -= hours
		while hour < 0:
			day -= 1
			hour += 24
		year -= years
		month -= months
		while month < 1:
			year -= 1
			month += 12
		day -= days
		while day < 1:
			month -= 1
			if month < 1:
				month = 12
				year -= 1
				day += get_days_in_month(month, year)
		
	func set_datetime(years:int, months:int, days:int,hours:int,minutes:int, seconds:int):
		year = clampi(years,1,9999)
		month = clampi(months,1,12)
		day = clampi(days,1,get_days_in_month(month,year))
		hour = clampi(hours, 0, 23)
		minute = clampi(minutes, 0, 59)
		second = clampi(seconds, 0, 59)


class GDate:
	var year: int
	var month: int
	var day: int
	var init_values: Dictionary
	var timestamp: int
	
	
	func _init(y: int = 0, mo: int = 0, d: int = 0):
		year = clampi(y,1,9999)
		month = clampi(mo,1,12)
		day = clampi(d,1,get_days_in_month(month,year))
		init_values = {"year":year,"month":month,"day":day,"hour":0,"minute":0,"second":0}
		timestamp = Time.get_unix_time_from_datetime_dict(init_values) # will return the date @ 12am
		var ts = {"timestamp":timestamp}
		init_values.merge(ts)
		
	func is_leapyear(year = year) -> bool:
		if (year % 4 == 0 and year % 100 != 0) or (year % 400 == 0):
			return true
		else:
			return false
	
	func get_day_of_year(day = day,month = month, year = year) -> int:
		var x = 0
		if month == 1:
			return day
		elif (year % 4 == 0 and year % 100 != 0) or (year % 400 == 0):
			for i in range(month - 1):
				x += DAYS_IN_MONTH[i-1]
			x += day
			return x
		else:
			for i in range(month - 1):
				x += DAYS_IN_MONTH[i-1]
			x += day
			return x

	func get_days_until_next_year(day = day,month = month, year = year):
		if (year % 4 == 0 and year % 100 != 0) or (year % 400 == 0):
			var x = 366 - get_day_of_year()
			return x
		else: 
			var x = 365 - get_day_of_year()
			return x

	func get_days_in_month(month: int, year: int) -> int:    
		if month in [1, 3, 5, 7, 8, 10, 12]:
			return 31
		elif month == 2:
			# Leap year check
			if (year % 4 == 0 and year % 100 != 0) or (year % 400 == 0):
				return 29
			else:
				return 28
		else:
			return 30
	
	func return_to_string() -> String:
		return "%02d:%02d:%02d" % [year,month,day]
		
	func add_time(years:int, months:int, days:int):
		year += years
		month += months
		while month > 12:
			year += 1
			month -= 12
		day += days
		while day > get_days_in_month(month,year):
			day -= get_days_in_month(month,year)
			month += 1
			if month > 12:
				month = 1
				year += 1
		
	func subtract_time(years:int, months:int, days:int):
		while month < 1:
			year -= 1
			month += 12
		day -= days
		while day < 1:
			month -= 1
			if month < 1:
				month = 12
				year -= 1
				day += get_days_in_month(month, year)

	func set_date(years:int, months:int, days:int):
		year = clampi(years,1,9999)
		month = clampi(months,1,12)
		day = clampi(days,1,get_days_in_month(month,year))


# Create a time object from input
func create_new_time(hour: int = 0, minute: int = 0, second: int = 0) -> GTime:
	return GTime.new(hour, minute, second)

# Create a time object with right nows time
func create_new_time_now() -> GTime:
	var x = Time.get_time_dict_from_system()
	var hour = int(x["hour"])
	var minute = int(x["minute"])
	var second = int(x["second"])
	return GTime.new(hour,minute,second)
	
func create_new_datetime(year: int = 1 ,month: int = 1, day: int = 1, hour: int = 0, minute: int = 0, second: int = 0) -> GDateTime:
	return GDateTime.new(year, month, day, hour, minute, second)
	
func create_new_datetime_now() -> GDateTime:
	var x = Time.get_datetime_dict_from_system()
	var year = int(x["year"])
	var month = int(x["month"])
	var day = int(x["day"])
	var hour = int(x["hour"])
	var minute = int(x["minute"])
	var second = int(x["second"])
	return GDateTime.new(year,month,day,hour,minute,second)
	
func create_new_date(year: int = 1 ,month: int = 1, day: int = 1) -> GDate:
	return GDate.new(year, month, day)
	
func create_new_date_now() -> GDate:
	var x = Time.get_datetime_dict_from_system()
	var year = int(x["year"])
	var month = int(x["month"])
	var day = int(x["day"])
	return GDate.new(year,month,day)
	
func convert_minutes_to_hours(minutes: int) -> Dictionary:
	var hours: int = minutes / 60
	var remaining_minutes: int = minutes % 60
	return {"hours": hours, "minutes": remaining_minutes}

func get_total_days_in_year(year: int):
	if (year % 4 == 0 and year % 100 != 0) or (year % 400 == 0):
		return 366
	else:
		return 365
		
	
func get_time_between_unix(unix1: int, unix2:int) -> Dictionary:
	var dif
	var mins
	var hours
	var seconds
	if unix1 > unix2:
		dif = unix1 - unix2
		mins = dif / 60
		seconds = dif - (mins * 60)
		hours = mins / 60
		mins -= hours * 60
	else:
		dif = unix2 - unix1
		mins = dif / 60
		seconds = dif - (mins * 60)
		hours = mins / 60
		mins -= hours * 60
	print("Total Seconds: " +str(dif))
	print("Hours: " +str(hours))
	print("Minutes: " +str(mins))
	print("Seconds: " +str(seconds))
	var ret_dict = {"Total Seconds": dif, "Hours": hours, "Minutes": mins, "Seconds": seconds}
	return ret_dict
		

Leave a comment