I want to store date in my Postgres database.
The only problem is that this date can have optional day or even month.
Example:
User provides time period when he was employed - not necessary full date (day + month + year), but only start year and end year.
However there are users, who worked only from may to october in the same year so month have to be provided too.
How to handle this kind of optional date parts?
Use a proper date type anyway. Do not store text or multiple columns. That would be more expensive and less reliable.
Use the function to_date(), which is fit to deal with your requirements out of the box. For instance, if you call it with a pattern 'YYYYMMDD' and the actual string is missing characters for day, or month and day, it defaults to the first month / day of the year / month:
db=# SELECT to_date('2001', 'YYYYMMDD');
to_date
------------
2001-01-01
db=# SELECT to_date('200103', 'YYYYMMDD');
to_date
------------
2001-03-01
You could store a precision flag indicating year / month / day in addition if you need that.
While the accepted answer is a good one, there is another alternative.
ISO 8601
The ISO 8601 standard defines sensible formats for textual representations of various kinds of date-time values.
A year is represented in the obvious manner, a four-digit number: 2014
A year-month is represented with a required hyphen: 2014-01Note that in other ISO 8601 formats, the hyphen is optional. But not for year month, to avoid ambiguity.
A full date is similar: 2014-08-21 or without optional hyphens: 20140821. I recommend keeping the hyphens.
So you could store the values as text. The length of text would tell you whether it is year-only, year-month, or date.
Related
I'm building an iOS app where I want to retrieve all the values from my database between two dates that the user picks. So for example, I want all the rows from the 1st of March to the 5th of March. Would look something like
SELECT * FROM MAIN WHERE DATE = '01/03/2020' AND ENDS ='05/03/2020'
So from that I would hope to retrieve all data from the 1st,2nd,3rd,4th and 5th of march. Any ideas on how to do this?
Thank you
Try to use comparison operators like:
DATE >= '01/03/2020' AND DATE <= '05/03/2020'
There are two issues:
Date types:
As Datatypes In SQLite Version 3 says:
2.2. Date and Time Datatype
SQLite does not have a storage class set aside for storing dates and/or times. Instead, the built-in Date And Time Functions of SQLite are capable of storing dates and times as TEXT, REAL, or INTEGER values:
TEXT as ISO8601 strings ("YYYY-MM-DD HH:MM:SS.SSS").
REAL as Julian day numbers, the number of days since noon in Greenwich on November 24, 4714 B.C. according to the proleptic Gregorian calendar.
INTEGER as Unix Time, the number of seconds since 1970-01-01 00:00:00 UTC.
Applications can chose to store dates and times in any of these formats and freely convert between formats using the built-in date and time functions.
So storing dates in a dd/MM/yyyy format (using the DateFormatter capitalization convention) is problematic because in the absence of a native date type, it’s going to store them as strings, and therefore all comparisons will be done alphabetically, not chronologically, sorting values like 03/10/2009 (or nonsense strings like 02foobar, for that matter) in between the strings 01/05/2020 and 05/05/2020.
If, however you store them as yyyy-MM-dd, then it just so happens that alphabetical comparisons will yield chronologically correct comparisons, too.
SQL syntax:
Once you have your dates in your database in a format that is comparable, then if you have all of your dates in a single column, you can use the BETWEEN syntax. For example, let’s say you stored all of your dates in yyyy-MM-dd format, then you could do things like:
SELECT * FROM main WHERE date BETWEEN '2020-03-01' AND '2020-03-05';
But needless to say, you can’t use this pattern (or any comparison operators other than equality) as long as your dates are stored in dd/MM/yyyy format.
If you want to show all the data that has values of column "date" between this two dates then:
Select *
from MAIN
where `date` between '01.03.2020' and '05.03.2020';
If you want to show all the data that has values of column "ends" between this two dates then:
Select *
from MAIN
where ends between '01.03.2020' and '05.03.2020';
If you want to show all the data that has values of columns "date" and "ends" between this two dates then:
Select *
from MAIN
where ends between '01.03.2020' and '05.03.2020'
and `date` between '01.03.2020' and '05.03.2020';
Here is a demo
I want to compare string representations of weeks, e.g. week "01/17" is before "02/17" and after "52/16".
The following code throws an exception, I guess because my string doesn't hint at the exact day of each week. However, I don't care - it could all be Mondays or Thursdays or whatever:
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("ww/YY", Locale.GERMANY);
LocalDate date1 = formatter.parse(str1, LocalDate::from);
Do I need to modify the parser? Or parse to some other format? Unfortunatley there is no object like YearMonth for weeks...
One solution would be to always default to the same day, say the Monday. You could build a custom formatter for that:
DateTimeFormatter fmt = new DateTimeFormatterBuilder()
.appendPattern("ww/YY")
.parseDefaulting(ChronoField.DAY_OF_WEEK, 1)
.toFormatter(Locale.GERMANY);
You can now build LocalDates representing the Monday of the given week:
LocalDate d1 = LocalDate.parse("01/17", fmt);
LocalDate d2 = LocalDate.parse("52/16", fmt);
System.out.println(d1.isAfter(d2));
which prints true because 01/17 is after 52/16.
I wasn't able to find a way for this to work with the DateTimeFormatter class, but I would like to suggest a different approach.
The Threeten Extra library contains a number of classes that were deemed too specific to include in the java.time library. One of them is the YearWeek class you mention.
Your problem can be solved by parsing the week-number and year manually from the input-string and then invoking the YearWeek creator-method like this:
YearWeek yw = YearWeek.of(year, monthOfYear);
tl;dr
YearWeek.parse( "2017-W01" )
ISO 8601
Or parse to some other format?
Yes, use another format.
Use the standard ISO 8601 formats when serializing date-time values to text. The standard includes support for week dates.
For a year-week that would be four year digits, a hyphen, a W, and two digits for the week of the year.
2017-W01
Get clear on your definition of a “week”. The ISO 8601 definition is that:
The Week # 1 contains the first Thursday of the year, and
Runs Monday-Sunday.
So years run either 52 or 53 weeks long. And note that under this definition, the first few days of the year may be in the prior year when week-numbering. Likewise, the last few days of the year may be in the following year when week-numbering.
If you want to indicate a particular day within that week, append a hyphen and a single digit running 1-7 for Monday-Sunday.
Tip: To see ISO 8601 week numbers by default on your computer, you may need to adjust your OS setting. For example, on macOS set System Preferences > Language & Region > Calendar > ISO 8601 to make apps such as Calendar.app to display week numbers with this standard definition.
2017-W01-7
By the way, a couple of similar representations:
An ordinal date meaning the year and the day-of-year-number running from 1-366 is year, a hyphen, and a three-digit number: 2017-123
Month-Day without year is two hyphens, month number, hyphen, and day-of-month number: --01-07
Note that the use of Locale as seen in the Question is irrelevant here with the standard ISO 8601 formats.
YearWeek
Unfortunatley there is no object like YearMonth for weeks...
Ahhh, but there is such a class.
For a class to directly represent the idea of a week-year, see the correct Answer by Henrik. That Answer shows the ThreeTen-Extra library’s class YearWeek.
The YearWeek class can directly parse and generate strings in standard format.
YearWeek yw = YearWeek.parse( "2017-W01" );
You can compare the YearWeek objects with methods: compareTo, equals, isBefore, isAfter.
yw.isBefore( thatYw )
The ThreeTen-Extra project offers other classes such as YearQuarter that you may find useful.
I am using the below query with date filtering, but I am getting wrong result.
SELECT * FROM TRANSACTIONSHISTORY
WHERE DATE > "29-01-2015 12:00:00"
AND DATE < "30-01-2015 00:00:00" AND USERID=abc
I am getting result with date column with value of 29-Jan-2016 records, what am I missing here, can any one help me to get out of this value.
The date format in your SQL will not work because SQLite doesn't have a native datetime type, so it's generally stored either as a string, in YYYY-MM-DD HH:MM:SS.SSS format, or as an numeric value representing the number of seconds since 1970-01-01 00:00:00 UTC. See date and time types on SQLite.org. Note that if you're using the string representation that the sequence is year, month, day (which, when sorting/querying this string field, the this alphanumeric string will sort correctly by year first, then month, and then day, which is critical when doing queries like yours).
If you really stored dates in the database as a string in the DD-MM-YYYY HH:MM:SS format, you should consider changing the format in which you saved the values into one of the approved date formats. It will make the date interactions with the database much, much easier, allowing queries like the one you asked for (though, obviously, with DD-MM-YYYY replaced with YYYY-MM-DD format).
You have cast your string to Date
SELECT * FROM TRANSACTIONSHISTORY WHERE DATE between Datetime('29-01-2015 12:00:00') and Datetime('30-01-2015 00:00:00') AND USERID=abc
The first answer is exactly what you need. What you did in your code would be comparing strings using ASCII values.
I would recommend you to use the linux time stamps like: 1453818208, which is easier to save and compare. In addition, it can always be translated to human-readable dates like: 29-01-2015 12:00:00.
SELECT * FROM TRANSACTIONSHISTORY
WHERE DATE > "29-01-2015 12:00:00"
AND DATE < "30-01-2015 00:00:00" AND USERID=abc
I hope this helps you :)
Try this first try without Time,after that try date and time both , Hope i will work for you
SELECT TRANSACTIONSHISTORY
FROM SHIPMENT
WHERE DATE
BETWEEN '11-15-2010'
AND '30-01-2015'
// you can try this one also
SELECT * FROM TRANSACTIONSHISTORY WHERE DATE BETWEEN "2011-01-11" AND "2011-8-11"
I have a two-part question about storing days of the week and time in a database. I'm using Rails 4.0, Ruby 2.0.0, and Postgres.
I have certain events, and those events have a schedule. For the event "Skydiving", for example, I might have Tuesday and Wednesday and 3 pm.
Is there a way for me to store the record for Tuesday and Wednesday in one row or should I have two records?
What is the best way to store the day and time? Is there a way to store day of week and time (not datetime) or should these be separate columns? If they should be separate, how would I store the day of the week? I was thinking of storing them as integer values, 0 for Sunday, 1 for Monday, since that's how the wday method for the Time class does it.
Any suggestions would be super helpful.
Is there a way for me to store the the record for Tuesday and
Wednesday in one row or do should I have two records?
There are several ways to store multiple time ranges in a single row. #bma already provided a couple of them. That might be useful to save disk space with very simple time patterns. The clean, flexible and "normalized" approach is to store one row per time range.
What is the best way to store the day and time?
Use a timestamp (or timestamptz if multiple time zones may be involved). Pick an arbitrary "staging" week and just ignore the date part while using the day and time aspect of the timestamp. Simplest and fastest in my experience, and all date and time related sanity-checks are built-in automatically. I use a range starting with 1996-01-01 00:00 for several similar applications for two reasons:
The first 7 days of the week coincide with the day of the month (for sun = 7).
It's the most recent leap year (providing Feb. 29 for yearly patterns) at the same time.
Range type
Since you are actually dealing with time ranges (not just "day and time") I suggest to use the built-in range type tsrange (or tstzrange). A major advantage: you can use the arsenal of built-in Range Functions and Operators. Requires Postgres 9.2 or later.
For instance, you can have an exclusion constraint building on that (implemented internally by way of a fully functional GiST index that may provide additional benefit), to rule out overlapping time ranges. Consider this related answer for details:
Preventing adjacent/overlapping entries with EXCLUDE in PostgreSQL
For this particular exclusion constraint (no overlapping ranges per event), you need to include the integer column event_id in the constraint, so you need to install the additional module btree_gist. Install once per database with:
CREATE EXTENSION btree_gist; -- once per db
Or you can have one simple CHECK constraint to restrict the allowed time period using the "range is contained by" operator <#.
Could look like this:
CREATE TABLE event (event_id serial PRIMARY KEY, ...);
CREATE TABLE schedule (
event_id integer NOT NULL REFERENCES event(event_id)
ON DELETE CASCADE ON UPDATE CASCADE
, t_range tsrange
, PRIMARY KEY (event_id, t_range)
, CHECK (t_range <# '[1996-01-01 00:00, 1996-01-09 00:00)') -- restrict period
, EXCLUDE USING gist (event_id WITH =, t_range WITH &&) -- disallow overlap
);
For a weekly schedule use the first seven days, Mon-Sun, or whatever suits you. Monthly or yearly schedules in a similar fashion.
How to extract day of week, time, etc?
#CDub provided a module to deal with it on the Ruby end. I can't comment on that, but you can do everything in Postgres as well, with impeccable performance.
SELECT ts::time AS t_time -- get the time (practically no cost)
SELECT EXTRACT(DOW FROM ts) AS dow -- get day of week (very cheap)
Or in similar fashion for range types:
SELECT EXTRACT(DOW FROM lower(t_range)) AS dow_from -- day of week lower bound
, EXTRACT(DOW FROM upper(t_range)) AS dow_to -- same for upper
, lower(t_range)::time AS time_from -- start time
, upper(t_range)::time AS time_to -- end time
FROM schedule;
db<>fiddle here
Old sqliddle
ISODOW instead of DOW for EXTRACT() returns 7 instead of 0 for sundays. There is a long list of what you can extract.
This related answer demonstrates how to use range type operator to compute a total duration for time ranges (last chapter):
Calculate working hours between 2 dates in PostgreSQL
Check out the ice_cube gem (link).
It can create a schedule object for you which you can persist to your database. You need not create two separate records. For the second part, you can create schedule based on any rule and you need not worry on how that will be saved in the database. You can use the methods provided by the gem to get whatever information you want from the persisted schedule object.
Depending how complex your scheduling needs are, you might want to have a look at RFC 5545, the iCalendar scheduling data format, for ideas on how to store the data.
If you needs are pretty simple, than that is probably overkill. Postgresql has many functions to convert date and time to whatever format you need.
For a simple way to store relative dates and times, you could store the day of week as an integer as you suggested, and the time as a TIME datatype. If you can have multiple days of the week that are valid, you might want to use an ARRAY.
Eg.
ARRAY[2,3]::INTEGER[] = Tues, Wed as Day of Week
'15:00:00'::TIME = 3pm
[EDIT: Add some simple examples]
/* Custom the time and timetz range types */
CREATE TYPE timerange AS RANGE (subtype = time);
--drop table if exists schedule;
create table schedule (
event_id integer not null, /* should be an FK to "events" table */
day_of_week integer[],
time_of_day time,
time_range timerange,
recurring text CHECK (recurring IN ('DAILY','WEEKLY','MONTHLY','YEARLY'))
);
insert into schedule (event_id, day_of_week, time_of_day, time_range, recurring)
values
(1, ARRAY[1,2,3,4,5]::INTEGER[], '15:00:00'::TIME, NULL, 'WEEKLY'),
(2, ARRAY[6,0]::INTEGER[], NULL, '(08:00:00,17:00:00]'::timerange, 'WEEKLY');
select * from schedule;
event_id | day_of_week | time_of_day | time_range | recurring
----------+-------------+-------------+---------------------+-----------
1 | {1,2,3,4,5} | 15:00:00 | | WEEKLY
2 | {6,0} | | (08:00:00,17:00:00] | WEEKLY
The first entry could be read as: the event is valid at 3pm Mon - Fri, with this schedule occurring every week.
The second entry could be read as: the event is valid Saturday and Sunday between 8am and 5pm, occurring every week.
The custom range type "timerange" is used to denote the lower and upper boundaries of your time range.
The '(' means "inclusive", and the trailing ']' means "exclusive", or in other words "greater than or equal to 8am and less than 5pm".
Why not just store the datestamp then use the built in functionality for Date to get the day of the week?
2.0.0p247 :139 > Date.today
=> Sun, 10 Nov 2013
2.0.0p247 :140 > Date.today.strftime("%A")
=> "Sunday"
strftime sounds like it can do everything for you. Here are the specific docs for it.
Specifically for what you're talking about, it sounds like you'd need an Event table that has_many :schedules, where a Schedule would have a start_date timestamp...
How can I display am or PM with a time display in informix 4GL. In my database the field called crttime_a_p and stores for am "A" and for pm "P". How can I display this in my application as "AM" or "PM". Any help would be appreciated. Thank you.
You'll need to map the database values ('A' ⟶ 'AM', 'P' ⟶ 'PM') and display the mapped value to a FORMONLY field. The field can be used for input if you want. You can add form attributes such as UPSHIFT, AUTONEXT, INCLUDE = ('AM', 'PM') to the field in the form. You just won't be able to use the RECORD LIKE Table.* notation because the field in the DB is CHAR(1) but you need CHAR(2) for display and input.
More seriously, you'll need to consider whether you have to futz with associated time field. If it is a DATETIME field, the time will be in 24 hour notation, and the conversion from 24 hour to AM/PM notation is not entirely straight-forward. Hint: 00:01:02 in 24 hour clock is 12:01:02 AM; 12:02:03 in 24 hour notation is 12:02:03 PM, but 13:03:04 in 24 hour is 1:02:03 PM in AM/PM notation (usually without the leading zero on the hours). See also Converting 24-hour military time to AM/PM time.
If the time field is simply a string, then you probably don't have these problems, but you'll want to design your form carefully to only accept numeric characters (attributes FORMAT and PICTURE?).