How to represent a calendar month as a field in django models

2,538 views
Skip to first unread message

Matthys

unread,
May 3, 2015, 5:35:40 PM5/3/15
to django...@googlegroups.com
I posted the question also on stackoverflow:

http://stackoverflow.com/questions/30017229/how-to-represent-month-as-field-on-django-model

The question is for situations where a model instance relates to a specific month, but not to a specific day.

Tom Lockhart

unread,
May 3, 2015, 9:41:36 PM5/3/15
to django...@googlegroups.com
I would use a date field and, perhaps, clean the data to force the day to be the first day of the month. Then you can do date/time arithmetic using standard features.

hth

- Tom

Mike Dewhirst

unread,
May 3, 2015, 10:18:35 PM5/3/15
to django...@googlegroups.com
Surely that would destroy data which wouldn't be desirable.

I would probably add a month (character or integer) field to the model
and extract and update that month field every time the instance is saved.

>
> hth
>
> - Tom
>

Tom Lockhart

unread,
May 3, 2015, 10:31:32 PM5/3/15
to django...@googlegroups.com
On May 3, 2015, at 7:17 PM, Mike Dewhirst <mi...@dewhirst.com.au> wrote:

> On 4/05/2015 11:41 AM, Tom Lockhart wrote:
>> On May 3, 2015, at 2:35 PM, Matthys <matt...@gmail.com> wrote:
>>
>>> I posted the question also on stackoverflow:
>>>
>>> http://stackoverflow.com/questions/30017229/how-to-represent-month-as-field-on-django-model
>>>
>>> The question is for situations where a model instance relates to a specific month, but not to a specific day.
>>
>> I would use a date field and, perhaps, clean the data to force the day to be the first day of the month. Then you can do date/time arithmetic using standard features.
>
> Surely that would destroy data which wouldn’t be desirable.

I’m not sure how that follows. Although the question does not state it explicitly, I believe that the OP is interested in dates for which only the year and month are significant. Saving the month as a separate field seems to overly complicate the problem if any date/time arithmetic is required.

Perhaps I’m not understanding the downsides here (data destruction did not seem to be a side effect of my suggestion, beyond rounding the date to the application’s precision of interest): under what circumstances would it be desirable to store dates but not use date field functionality?

Regards,

- Tom

Mike Dewhirst

unread,
May 3, 2015, 11:48:20 PM5/3/15
to django...@googlegroups.com
On 4/05/2015 12:31 PM, Tom Lockhart wrote:
>> Surely that would destroy data which wouldn’t be desirable.
> I’m not sure how that follows.

If you are adjusting a date from whatever it actually is to the first of
the month you lose the actual date.

Although the question does not state
> it explicitly, I believe that the OP is interested in dates for which
> only the year and month are significant.

Granted. If that is correct.

Saving the month as a
> separate field seems to overly complicate the problem if any
> date/time arithmetic is required.

Saving a date adjusted to the first of the month also involves a
complication - or at least some work - then you extract the month to be
displayed or queried.

Extracting the month from a date is the same no matter whether it is the
first or the ninth.

Who knows when the requirements will change but you can guarantee they
will change and Murphy's law says it will happen after you have thrown
data away.

Anyway, your choices are to calculate it every time you need it or do it
once and use it thereafter. The right choice will depend on the
requirements. Which will change.

Cheers

Mike

Matthys Kroon

unread,
May 4, 2015, 7:26:20 AM5/4/15
to django...@googlegroups.com
Hi,

I'm specifically looking at only situations where the year and month alone are significant.

The downside I see with using a DateField and forcing the day to the first of the month, with custom widget etc. is that somebody looking at the database may not realize that something unusual is going on and I'm not sure how I would go about finding the month x months before or after after a given month ... solutions like
where 12 * year(t1.month_field) + month(t1.month_field) = 12 * year(t2.month_field) + month(t2.month_field) + x
come to mind for sql but not sure how this would translate into django. Not that I'm saying it is not the correct solution, all of the options I considered have some downside.

The logic for adding and subtracting these "months" requires something like timedelta(month=2), which doesn't exist in the standard library as far as I know - though I'm not an expert.

There are recipes on stackoverflow for incrementing months though not very elegant.

Thanks for your interest!

Matthys



--
You received this message because you are subscribed to a topic in the Google Groups "Django users" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/django-users/vmsiHYzI-gg/unsubscribe.
To unsubscribe from this group and all its topics, send an email to django-users...@googlegroups.com.
To post to this group, send email to django...@googlegroups.com.
Visit this group at http://groups.google.com/group/django-users.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-users/5546EBE6.1010705%40dewhirst.com.au.

For more options, visit https://groups.google.com/d/optout.

Erik Cederstrand

unread,
May 4, 2015, 8:21:37 AM5/4/15
to Django Users

> Den 04/05/2015 kl. 13.26 skrev Matthys Kroon <matt...@gmail.com>:
>
> I'm specifically looking at only situations where the year and month alone are significant.
>
> The downside I see with using a DateField and forcing the day to the first of the month, with custom widget etc. is that somebody looking at the database may not realize that something unusual is going on and I'm not sure how I would go about finding the month x months before or after after a given month ... solutions like
> where 12 * year(t1.month_field) + month(t1.month_field) = 12 * year(t2.month_field) + month(t2.month_field) + x
> come to mind for sql but not sure how this would translate into django. Not that I'm saying it is not the correct solution, all of the options I considered have some downside.

Python stdlib doesn't support operating on months the way I think you expect. If you want to truly only operate on years and month values, then I think the most Pythonic/Djangoic way is to create your own Month model/class (unless you want to marry ourself to PostgreSQL and its' tuple type). The math to add/subtract months is very simple:


class Month(models.Model):
year = models.IntegerField()
month = models.PositiveSmallIntegerField()

def add_months(self, n):
assert n >= 0
dy, dm = divmod(self.month + n, 12)
self.year += dy
self.month += dm

# Expand as needed with __add__, __sub__, remove_months() etc.


> The logic for adding and subtracting these "months" requires something like timedelta(month=2), which doesn't exist in the standard library as far as I know - though I'm not an expert.
>
> There are recipes on stackoverflow for incrementing months though not very elegant.

That's because incrementing months is ambiguous when operating on actual datetimes. timedelta(months=2) is nonsense because months have different lengths. Adding 1 month to March 2 would mean April 2 to most people, but what about January 31 (let's ignore for a moment that both PHP and MySQL accept February 31 as a valid date)? Do you want February 28 (or possibly 29), or March 2 (or 3), or possibly 4 weeks ahead?

Erik

Erik Cederstrand

unread,
May 4, 2015, 8:30:56 AM5/4/15
to Django Users

> Den 04/05/2015 kl. 14.21 skrev Erik Cederstrand <erik+...@cederstrand.dk>:
>
> class Month(models.Model):
> year = models.IntegerField()
> month = models.PositiveSmallIntegerField()
>
> def add_months(self, n):
> assert n >= 0
> dy, dm = divmod(self.month + n, 12)
> self.year += dy
> self.month += dm

Sorry, that's supposed to be:

def add_months(self, n):
assert n >= 0
dy, self.month = divmod(self.month + n, 12)
self.year += dy


Disclaimer: This possibly only applies to the Gregorian calendar. Read up on the others as needed: http://en.wikipedia.org/wiki/List_of_calendars :-)

Erik

Matthys Kroon

unread,
May 4, 2015, 12:10:06 PM5/4/15
to django...@googlegroups.com
Hi Erik,

Thanks for your reply.

I'm aware of the problems with timedelta(months=2), since the length of a month is not fixed, a length of time specified in months is ambiguous. Though months do have an internally consistent algebra as you point out and people often refer to time-periods in month without any real-world confusion. I agree it is possible to create a class modelling this behaviour.

The downside to creating a new classt, is that all the existing date-related functionality in the standard library and django is lost and has to be rewritten or wrapped somehow.

I think I'll just have to look at my use-cases and pick the option that most easily facilitates those uses.

Cheers,




Erik

--
You received this message because you are subscribed to a topic in the Google Groups "Django users" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/django-users/vmsiHYzI-gg/unsubscribe.
To unsubscribe from this group and all its topics, send an email to django-users...@googlegroups.com.
To post to this group, send email to django...@googlegroups.com.
Visit this group at http://groups.google.com/group/django-users.

Tim Chase

unread,
May 4, 2015, 1:09:05 PM5/4/15
to django...@googlegroups.com
If the other suggestions don't work for you, you could store it as a
regular integer field for the number of months. A custom widget
would allow you to create an interface to manage it, and the value
would simply be "years * 12 + months - 1" and to extract the year and
month, you'd use something like

year, month = divmod(mymodel.my_yearmonth_field, 12)
month += 1

It would have the peculiar side-effect that internally the month
would be represented by a value 0..11 instead of 1..12

However, it has the advantage that it's only one field in the
database and compares naturally ("ORDER BY my_yearmonth_field")

You could even fancy it up something like the below.

-tkc

from django.db import models
from django.forms import widgets

class YearMonth(object):
def __init__(self, year, month):
self.year = year
self.month = month

class YearMonthFormField(widgets.Widget):
# for implementation ideas, see
# https://docs.djangoproject.com/en/1.8/_modules/django/forms/extras/widgets/#SelectDateWidget
pass

class YearMonthField(models.IntegerField):
description = "A field for managing year+month without dates"
def __init__(self, *args, **kwargs):
super(YearMonthField, self).__init__(*args, **kwargs)
def to_python(self, value):
if isinstance(value, YearMonth):
return value
if value is None:
return value
year, month = divmod(value, 12)
return YearMonth(year, month + 1)
def from_db_value(self, value, expression, connection, context):
return self.to_python(value)
def get_prep_value(self, value):
return value.year * 12 + value.month - 1
def formfield(self, **kwargs):
defaults = {'form_class': YearMonthFormField}
defaults.update(kwargs)
return super(YearMonthField, self).formfield(**defaults)

class MyModel(models.Model):
my_yearmonth_field = YearMonthField()

Matthys Kroon

unread,
May 12, 2015, 4:46:05 AM5/12/15
to django...@googlegroups.com
Hi,

I ended up creating a custom field (https://github.com/clearspark/django-monthfield).

I used a lot of your input, thank you all.

Cheers,

Matthys


--
You received this message because you are subscribed to a topic in the Google Groups "Django users" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/django-users/vmsiHYzI-gg/unsubscribe.
To unsubscribe from this group and all its topics, send an email to django-users...@googlegroups.com.
To post to this group, send email to django...@googlegroups.com.
Visit this group at http://groups.google.com/group/django-users.
Reply all
Reply to author
Forward
0 new messages