Date and time

Introduction

YugabyteDB has extensive date and time capability that may be daunting for the new user. Once understood, the rich functionality will allow you to perform very sophisticated calculations and granular time capture.

For date and time data types, see Data types.

Special values

There are special values that you can reference - YugabyteDB only caters for some, other special values from postgresql are not implemented in YugabyteDB, but some can be recreated if you require them. Below is YSQL to select special date and time values. First start ysql from your command line.

  1. ./bin/ysqlsh
  1. yugabyte=# select current_date, current_time, current_timestamp, now();
  2. current_date | current_time | current_timestamp | now
  3. --------------+--------------------+-------------------------------+-------------------------------
  4. 2019-07-09 | 00:53:13.924407+00 | 2019-07-09 00:53:13.924407+00 | 2019-07-09 00:53:13.924407+00
  5. yugabyte=# select make_timestamptz(1970, 01, 01, 00, 00, 00, 'UTC') as epoch;
  6. epoch
  7. ------------------------
  8. 1970-01-01 00:00:00+00
  9. yugabyte=# select (current_date-1)::timestamp as yesterday,
  10. current_date::timestamp as today,
  11. (current_date+1)::timestamp as tomorrow;
  12. yesterday | today | tomorrow
  13. ---------------------+---------------------+---------------------
  14. 2019-07-08 00:00:00 | 2019-07-09 00:00:00 | 2019-07-10 00:00:00

NoteYugabyteDB cannot create the special values of infinity, -infinity, and allballs that can be found in postgresql. If you are wondering, ‘allballs’ is a theoretical time of “00:00:00.00 UTC”.

Formatting

Date formatting is an important aspect. As you can see above, the examples show the default ISO format for dates and timestamps. We will now do some formatting of dates.

  1. yugabyte=# select to_char(current_timestamp, 'DD-MON-YYYY');
  2. to_char
  3. -------------
  4. 09-JUL-2019
  5. yugabyte=# select to_date(to_char(current_timestamp, 'DD-MON-YYYY'), 'DD-MON-YYYY');
  6. to_date
  7. ------------
  8. 2019-07-09
  9. yugabyte=# select to_char(current_timestamp, 'DD-MON-YYYY HH:MI:SS PM');
  10. to_char
  11. -------------------------
  12. 09-JUL-2019 01:50:13 AM

In the above you will see that to present the date in a friendly readable format, the date and time needs to be represented in text using TO_CHAR. When it is represented as a date or time data type, it is displayed using system settings, hence why the date representation of text 09-JUL-2019 appears as 2019-07-09.

Time zones

Thus far, we have been operating with the default time zone installed for YugabyteDB being UTC (+0). Lets select what time zones are available from Yugabyte:

  1. yugabyte=# select * from pg_timezone_names;
  2. name | abbrev | utc_offset | is_dst
  3. ----------------------------------+--------+------------+--------
  4. W-SU | MSK | 03:00:00 | f
  5. GMT+0 | GMT | 00:00:00 | f
  6. ROK | KST | 09:00:00 | f
  7. UTC | UTC | 00:00:00 | f
  8. US/Eastern | EDT | -04:00:00 | t
  9. US/Pacific | PDT | -07:00:00 | t
  10. US/Central | CDT | -05:00:00 | t
  11. MST | MST | -07:00:00 | f
  12. Zulu | UTC | 00:00:00 | f
  13. posixrules | EDT | -04:00:00 | t
  14. GMT | GMT | 00:00:00 | f
  15. Etc/UTC | UTC | 00:00:00 | f
  16. Etc/Zulu | UTC | 00:00:00 | f
  17. Etc/Universal | UTC | 00:00:00 | f
  18. Etc/GMT+2 | -02 | -02:00:00 | f
  19. Etc/Greenwich | GMT | 00:00:00 | f
  20. Etc/GMT+12 | -12 | -12:00:00 | f
  21. Etc/GMT+8 | -08 | -08:00:00 | f
  22. Etc/GMT-12 | +12 | 12:00:00 | f
  23. WET | WEST | 01:00:00 | t
  24. EST | EST | -05:00:00 | f
  25. Australia/West | AWST | 08:00:00 | f
  26. Australia/Sydney | AEST | 10:00:00 | f
  27. GMT-0 | GMT | 00:00:00 | f
  28. PST8PDT | PDT | -07:00:00 | t
  29. Hongkong | HKT | 08:00:00 | f
  30. Singapore | +08 | 08:00:00 | f
  31. Universal | UTC | 00:00:00 | f
  32. Arctic/Longyearbyen | CEST | 02:00:00 | t
  33. UCT | UCT | 00:00:00 | f
  34. GMT0 | GMT | 00:00:00 | f
  35. Europe/London | BST | 01:00:00 | t
  36. GB | BST | 01:00:00 | t
  37. ...
  38. (593 rows)

NoteNot all 593 rows are shown, so don’t be concerned if the timezone you want is not there. Check your YSQL output to find the timezone you are interested in. What has been left in the results above is that there is a lot of inconsistency in the naming convention and definition of the timezones, this is not the doing of Yugabyte!

You can set the timezone to use for your session using the SET command. You can SET timezone using the timezone name as listed in pg_timezone_names, but not the abbreviation. You can also set the timezone to a numeric/decimal representation of the time offset. For example, -3.5 is 3 hours and 30 minutes before UTC.

It seems logical to be able to set the timezone using the UTC_OFFSET format above. YugabyteDB will allow this, however, be aware of the following behaviour if you choose this method:

TipWhen using POSIX time zone names, positive offsets are used for locations west of Greenwich. Everywhere else, YugabyteDB follows the ISO-8601 convention that positive timezone offsets are east of Greenwich. Therefore an entry of ‘+10:00:00’ will result in a timezone offset of -10 Hours as this is deemed East of Greenwich.

Lets start examining dates and times within Yugabyte.

  1. yugabyte=# \echo `date`
  2. Tue 09 Jul 12:27:08 AEST 2019

Note that the above does not use quotes, but is the “Grave Accent” symbol, which is normally found below the Tilde ~ symbol on your keyboard.

The above is showing you the current date and time of the underlying server. It is not the date and time of the database. However, in a single node implementation of YugabyteDB there will be a relationship between your computer’s date and the database date because YugabyteDB would have obtained the date from the server when it was started. We will explore the date and time (timestamps) within the database.

  1. yugabyte=# SHOW timezone;
  2. TimeZone
  3. ----------
  4. UTC
  5. yugabyte=# select current_timestamp;
  6. current_timestamp
  7. ------------------------------
  8. 2019-07-09 02:27:46.65152+00
  9. yugabyte=# SET timezone = +1;
  10. SET
  11. yugabyte=# SHOW timezone;
  12. TimeZone
  13. ----------
  14. <+01>-01
  15. yugabyte=# select current_timestamp;
  16. current_timestamp
  17. ------------------------------
  18. 2019-07-09 03:28:11.52311+01
  19. yugabyte=# SET timezone = -1.5;
  20. SET
  21. yugabyte=# select current_timestamp;
  22. current_timestamp
  23. ----------------------------------
  24. 2019-07-09 00:58:27.906963-01:30
  25. yugabyte=# SET timezone = 'Australia/Sydney';
  26. SET
  27. yugabyte=# SHOW timezone;
  28. TimeZone
  29. ------------------
  30. Australia/Sydney
  31. yugabyte=# select current_timestamp;
  32. current_timestamp
  33. -------------------------------
  34. 2019-07-09 12:28:46.610746+10
  35. yugabyte=# SET timezone = 'UTC';
  36. SET
  37. yugabyte=# select current_timestamp;
  38. current_timestamp
  39. -------------------------------
  40. 2019-07-09 02:28:57.610746+00
  41. yugabyte=# select current_timestamp AT TIME ZONE 'Australia/Sydney';
  42. timezone
  43. ----------------------------
  44. 2019-07-09 12:29:03.416867
  45. yugabyte=# select current_timestamp(0);
  46. current_timestamp
  47. ------------------------
  48. 2019-07-09 03:15:38+00
  49. yugabyte=# select current_timestamp(2);
  50. current_timestamp
  51. ---------------------------
  52. 2019-07-09 03:15:53.07+00

As shown above, you can set your SESSION to a particular timezone. When working with timestamps, you can control their seconds precision by specifying a value from 0 -> 6. Timestamps cannot go beyond millisecond precision which is 1,000,000 parts to one second.

If your application assumes a local time, ensure that it issues a SET command to set to the correct time offset. Note that Daylight Savings is quite an advanced topic, so for the time being it is recommended to instead use the offset notation, for example -3.5 for 3 hours and 30 minutes before UTC.

Note that the AT TIME ZONE statement above does not cater for the variants of WITH TIME ZONE and WITHOUT TIME ZONE.

Timestamps

Note

A database normally obtains its date and time from the underlying server. However, in the case of a distributed database, it is one synchronized database that is spread across many servers that are unlikely to have synchronized time.

A detailed explanation of how time is obtained can be found at the blog post describing the architecture of the storage layer

A simpler explanation is that the time is determined by the ‘Shard Leader’ of the table and this is the time used by all followers of the leader. Therefore there could be differences to the UTC timestamp of the underlying server to the current timestamp that is used for a transaction on a particular table.

Lets start working with dates and timestamps. The following assumes that you have installed the yb_demo database and its demo data.

  1. yugabyte=# \c yb_demo
  2. You are now connected to database "yb_demo" as user "yugabyte".
  3. yb_demo=# select to_char(max(orders.created_at), 'DD-MON-YYYY HH24:MI') AS "Last Order Date" from orders;
  4. Last Order Date
  5. -------------------
  6. 19-APR-2020 14:07
  7. yb_demo=# select extract(MONTH from o.created_at) AS "Mth Num", to_char(o.created_at, 'MON') AS "Month",
  8. extract(YEAR from o.created_at) AS "Year", count(*) AS "Orders"
  9. from orders o
  10. where o.created_at > current_timestamp(0)
  11. group by 1,2,3
  12. order by 3 DESC, 1 DESC limit 10;
  13. Mth Num | Month | Year | Orders
  14. ---------+-------+------+--------
  15. 4 | APR | 2020 | 344
  16. 3 | MAR | 2020 | 527
  17. 2 | FEB | 2020 | 543
  18. 1 | JAN | 2020 | 580
  19. 12 | DEC | 2019 | 550
  20. 11 | NOV | 2019 | 542
  21. 10 | OCT | 2019 | 540
  22. 9 | SEP | 2019 | 519
  23. 8 | AUG | 2019 | 566
  24. 7 | JUL | 2019 | 421
  25. (10 rows)
  26. yb_demo=# select to_char(o.created_at, 'HH AM') AS "Popular Hours", count(*) AS "Orders"
  27. from orders o
  28. group by 1
  29. order by 2 DESC
  30. limit 4;
  31. Popular Hours | Orders
  32. ---------------+--------
  33. 12 PM | 827
  34. 11 AM | 820
  35. 03 PM | 812
  36. 08 PM | 812
  37. (4 rows)
  38. yb_demo=# update orders
  39. set created_at = created_at + ((floor(random() * (25-2+2) + 2))::int * interval '1 day 14 hours');
  40. UPDATE 18760
  41. yb_demo=# select to_char(o.created_at, 'Day') AS "Top Day",
  42. count(o.*) AS "SALES"
  43. from orders o
  44. group by 1
  45. order by 2 desc;
  46. Top Day | SALES
  47. -----------+---------
  48. Monday | 2786
  49. Tuesday | 2737
  50. Saturday | 2710
  51. Wednesday | 2642
  52. Friday | 2634
  53. Sunday | 2630
  54. Thursday | 2621
  55. (7 rows)
  56. yb_demo=# create table order_deliveries (
  57. order_id bigint,
  58. creation_date date DEFAULT current_date,
  59. delivery_date timestamptz);
  60. CREATE TABLE
  61. yb_demo=# insert into order_deliveries
  62. (order_id, delivery_date)
  63. select o.id, o.created_at + ((floor(random() * (25-2+2) + 2))::int * interval '1 day 3 hours')
  64. from orders o
  65. where o.created_at < current_timestamp - (20 * interval '1 day');
  66. INSERT 0 12268
  67. yb_demo=# select * from order_deliveries limit 5;
  68. order_id | creation_date | delivery_date
  69. ----------+---------------+----------------------------
  70. 5636 | 2019-07-09 | 2017-01-06 03:06:01.071+00
  71. 10990 | 2019-07-09 | 2018-12-16 12:02:56.169+00
  72. 13417 | 2019-07-09 | 2018-06-26 09:28:02.153+00
  73. 9367 | 2019-07-09 | 2017-05-21 06:49:42.298+00
  74. 13954 | 2019-07-09 | 2019-02-08 04:07:01.457+00
  75. (5 rows)
  76. yb_demo=# select d.order_id, to_char(o.created_at, 'DD-MON-YYYY HH AM') AS "Ordered",
  77. to_char(d.delivery_date, 'DD-MON-YYYY HH AM') AS "Delivered",
  78. d.delivery_date - o.created_at AS "Delivery Days"
  79. from orders o, order_deliveries d
  80. where o.id = d.order_id
  81. and d.delivery_date - o.created_at > interval '15 days'
  82. order by d.delivery_date - o.created_at DESC, d.delivery_date DESC limit 10;
  83. order_id | Ordered | Delivered | Delivery Days
  84. ----------+-------------------+-------------------+------------------
  85. 10984 | 12-JUN-2019 08 PM | 07-JUL-2019 02 AM | 24 days 06:00:00
  86. 6263 | 01-JUN-2019 03 AM | 25-JUN-2019 09 AM | 24 days 06:00:00
  87. 10498 | 18-MAY-2019 01 AM | 11-JUN-2019 07 AM | 24 days 06:00:00
  88. 14996 | 14-MAR-2019 05 PM | 08-APR-2019 12 AM | 24 days 06:00:00
  89. 6841 | 06-FEB-2019 01 AM | 02-MAR-2019 07 AM | 24 days 06:00:00
  90. 10977 | 11-MAY-2019 01 PM | 03-JUN-2019 07 PM | 23 days 06:00:00
  91. 14154 | 09-APR-2019 01 PM | 02-MAY-2019 07 PM | 23 days 06:00:00
  92. 6933 | 31-MAY-2019 05 PM | 23-JUN-2019 12 AM | 22 days 06:00:00
  93. 5289 | 04-MAY-2019 04 PM | 26-MAY-2019 10 PM | 22 days 06:00:00
  94. 10226 | 01-MAY-2019 06 AM | 23-MAY-2019 12 PM | 22 days 06:00:00
  95. (10 rows)

NoteYour data will be slightly different as we used a RANDOM() function for setting the ‘delivery_date’ in the new ‘order_deliveries’ table.

You can use views of the YugabyteDB Data Catalogs to create data that is already prepared and formatted for your application code so that your SQL is simpler. Below is an example that is defined in the yb_demo database (has no dependency on yb_demo). This demonstration shows how you can nominate a shortlist of timezones that are formatted and ready to use for display purposes.

  1. yb_demo=# CREATE OR REPLACE VIEW TZ AS
  2. select '* Current time' AS "tzone", '' AS "offset", to_char(current_timestamp AT TIME ZONE 'Australia/Sydney', 'Dy dd-Mon-yy hh:mi PM') AS "Local Time"
  3. UNION
  4. select x.name AS "tzone",
  5. left(x.utc_offset::text, 5) AS "offset",
  6. to_char(current_timestamp AT TIME ZONE x.name, 'Dy dd-Mon-yy hh:mi PM') AS "Local Time"
  7. from pg_catalog.pg_timezone_names x
  8. where x.name like 'Australi%' or name in('Singapore', 'NZ', 'UTC')
  9. order by 1 asc;
  10. CREATE VIEW
  11. yb_demo=# select * from tz;
  12. tzone | offset | Local Time
  13. -----------------------+--------+------------------------
  14. * Current time | | Wed 10-Jul-19 11:49 AM
  15. Australia/ACT | 10:00 | Wed 10-Jul-19 11:49 AM
  16. Australia/Adelaide | 09:30 | Wed 10-Jul-19 11:19 AM
  17. Australia/Brisbane | 10:00 | Wed 10-Jul-19 11:49 AM
  18. Australia/Broken_Hill | 09:30 | Wed 10-Jul-19 11:19 AM
  19. Australia/Canberra | 10:00 | Wed 10-Jul-19 11:49 AM
  20. Australia/Currie | 10:00 | Wed 10-Jul-19 11:49 AM
  21. Australia/Darwin | 09:30 | Wed 10-Jul-19 11:19 AM
  22. Australia/Eucla | 08:45 | Wed 10-Jul-19 10:34 AM
  23. Australia/Hobart | 10:00 | Wed 10-Jul-19 11:49 AM
  24. Australia/LHI | 10:30 | Wed 10-Jul-19 12:19 PM
  25. Australia/Lindeman | 10:00 | Wed 10-Jul-19 11:49 AM
  26. Australia/Lord_Howe | 10:30 | Wed 10-Jul-19 12:19 PM
  27. Australia/Melbourne | 10:00 | Wed 10-Jul-19 11:49 AM
  28. Australia/NSW | 10:00 | Wed 10-Jul-19 11:49 AM
  29. Australia/North | 09:30 | Wed 10-Jul-19 11:19 AM
  30. Australia/Perth | 08:00 | Wed 10-Jul-19 09:49 AM
  31. Australia/Queensland | 10:00 | Wed 10-Jul-19 11:49 AM
  32. Australia/South | 09:30 | Wed 10-Jul-19 11:19 AM
  33. Australia/Sydney | 10:00 | Wed 10-Jul-19 11:49 AM
  34. Australia/Tasmania | 10:00 | Wed 10-Jul-19 11:49 AM
  35. Australia/Victoria | 10:00 | Wed 10-Jul-19 11:49 AM
  36. Australia/West | 08:00 | Wed 10-Jul-19 09:49 AM
  37. Australia/Yancowinna | 09:30 | Wed 10-Jul-19 11:19 AM
  38. NZ | 12:00 | Wed 10-Jul-19 01:49 PM
  39. Singapore | 08:00 | Wed 10-Jul-19 09:49 AM
  40. UTC | 00:00 | Wed 10-Jul-19 01:49 AM
  41. (27 rows)

Assuming that you chose the timezones that interest you, then your results should be different to those shown above.

Fun FactWho would have thought that Australia needs 23 timezone records ?

Date and time intervals

You may have noticed that the above YSQL has references to INTERVAL. An interval is a data type that describes an increment of time. An interval allows you to show the difference between two timestamps or to create a new timestamp by adding or subtracting a particular unit of measure. Some examples are:

  1. yugabyte=# select current_timestamp AS "Current Timestamp",
  2. current_timestamp + (10 * interval '1 min') AS "Plus 10 Mins",
  3. current_timestamp + (10 * interval '3 min') AS "Plus 30 Mins",
  4. current_timestamp + (10 * interval '2 hour') AS "Plus 20 hours",
  5. current_timestamp + (10 * interval '1 month') AS "Plus 10 Months"
  6. Current Timestamp | Plus 10 Mins | Plus 30 Mins | Plus 20 hours | Plus 10 Months
  7. -------------------------------+-------------------------------+-------------------------------+-------------------------------+-------------------------------
  8. 2019-07-09 05:08:58.859123+00 | 2019-07-09 05:18:58.859123+00 | 2019-07-09 05:38:58.859123+00 | 2019-07-10 01:08:58.859123+00 | 2020-05-09 05:08:58.859123+00
  9. yugabyte=# select current_time::time(0), time '05:00' + interval '5 hours 7 mins' AS "New time";
  10. current_time | New Time
  11. --------------+----------
  12. 05:09:24 | 10:16:24
  13. yugabyte=# select current_date - date '01-01-2019' AS "Day of Year(A)", current_date - date_trunc('year', current_date) AS "Day of Year(B)";
  14. Day of Year(A) | Day of Year(B)
  15. ----------------+----------------
  16. 189 | 189 days
  17. yugabyte=# select timestamp '2019-07-09 10:00:00.000000+00' - timestamp '2019-07-09 09:00:00.000000+00' AS "Time Difference";
  18. Time Difference
  19. -----------------
  20. 01:00:00
  21. yugabyte=# select timestamp '2019-07-09 10:00:00.000000+00' - timestamptz '2019-07-09 10:00:00.000000+00' AS "Time Offset";
  22. Time Offset
  23. -------------
  24. 00:00:00
  25. yugabyte=# select timestamp '2019-07-09 10:00:00.000000+00' - timestamptz '2019-07-09 10:00:00.000000EST' AS "Time Offset";
  26. Time Offset
  27. -------------
  28. -05:00:00
  29. yugabyte=# select timestamp '2019-07-09 10:00:00.000000+00' - timestamptz '2019-07-08 10:00:00.000000EST' AS "Time Offset";
  30. Time Offset
  31. -------------
  32. 19:00:00
  33. yugabyte=# select timestamp '2019-07-09 10:00:00.000000+00' - timestamptz '2019-07-07 10:00:00.000000EST' AS "Time Offset";
  34. Time Offset
  35. ----------------
  36. 1 day 19:00:00
  37. yugabyte=# select age(timestamp '2019-07-09 10:00:00.000000+00', timestamptz '2019-07-07 10:00:00.000000EST') AS "Age Diff";
  38. Age Diff
  39. ----------------
  40. 1 day 19:00:00
  41. yugabyte=# select (extract('days' from age(timestamp '2019-07-09 10:00:00.000000+00', timestamptz '2019-07-07 10:00:00.000000EST'))*24)+
  42. (extract('hours' from age(timestamp '2019-07-09 10:00:00.000000+00', timestamptz '2019-07-07 10:00:00.000000EST'))) AS "Hours Diff";
  43. Hours Diff
  44. ------------
  45. 43

The above shows that date and time manipulation can be achieved in several ways. It is important to note that some outputs are of type INTEGER, whilst others are of type INTERVAL (not text as they may appear). The final YSQL above for “Hours Diff” uses the output of EXTRACT which produces an INTEGER so that it may be multiplied by the hours per day whereas the EXTRACT function itself requires either a INTERVAL or TIMESTAMP(TZ) data type as its input.

Ensure to cast your values thoroughly. Casts can be done for time(tz), date and timestamp(tz) like MY_VALUE::timestamptz.

NoteThe EXTRACT command is the preferred command to DATE_PART.

Manipulating using truncation

Another useful command is DATE_TRUNC which is used to ‘floor’ the timestamp to a particular unit. For the following YSQL, we assume that you are in the ‘yb_demo’ database with the demo data loaded.

  1. yb_demo=# select date_trunc('hour', current_timestamp);
  2. date_trunc
  3. ------------------------
  4. 2019-07-09 06:00:00+00
  5. (1 row)
  6. yb_demo=# select to_char((date_trunc('month', generate_series)::date)-1, 'DD-MON-YYYY') AS "Last Day of Month"
  7. from generate_series(current_date-(365-1), current_date, '1 month');
  8. Last Day of Month
  9. -------------------
  10. 30-JUN-2018
  11. 31-JUL-2018
  12. 31-AUG-2018
  13. 30-SEP-2018
  14. 31-OCT-2018
  15. 30-NOV-2018
  16. 31-DEC-2018
  17. 31-JAN-2019
  18. 28-FEB-2019
  19. 31-MAR-2019
  20. 30-APR-2019
  21. 31-MAY-2019
  22. (12 rows)
  23. yb_demo=# select date_trunc('days', age(created_at)) AS "Product Age" from products order by 1 desc limit 10;
  24. Product Age
  25. ------------------------
  26. 3 years 2 mons 12 days
  27. 3 years 2 mons 10 days
  28. 3 years 2 mons 6 days
  29. 3 years 2 mons 4 days
  30. 3 years 1 mon 28 days
  31. 3 years 1 mon 27 days
  32. 3 years 1 mon 15 days
  33. 3 years 1 mon 9 days
  34. 3 years 1 mon 9 days
  35. 3 years 1 mon
  36. (10 rows)

Bringing it all together

A common requirement is to find out the date of next Monday, for example that might be the first day of the new week for scheduling purposes. This can be achieved in many ways, maybe in a simpler fashion than I have illustrated below. Below illustrates the chaining together of different date and time operators and functions to achieve the result you want.

  1. yugabyte=# select to_char(current_date, 'Day, DD-MON-YYYY') AS "Today",
  2. to_char((current_timestamp AT TIME ZONE 'Australia/Sydney')::date +
  3. (7-(extract('isodow' from current_timestamp AT TIME ZONE 'Australia/Sydney'))::int + 1),
  4. 'Day, DD-MON-YYYY') AS "Start of Next Week";
  5. Today | Start of Next Week
  6. ------------------------+------------------------
  7. Tuesday , 09-JUL-2019 | Monday , 15-JUL-2019

The above approach is to EXTRACT the current day of the week as an integer. As today is a Tuesday, the result will be 2. As we know there are 7 days per week, we need to target a calculation that has a result of 8, being 1 day more than the 7th day. We use this to calculate how many days to add to the current date (7 days - 2 + 1 day) to arrive at the next Monday which is day of the week (ISO dow) #1. My addition of the AT TIME ZONE was purely illustrative and would not impact the result because I am dealing with days, and my timezone difference is only +10 hours, therefore it does not affect the date. However, if you are working with hours or smaller, then the timezone will potentially have a bearing on your result.

Fun FactFor the very curious, why is there a gap after ‘Tuesday’ and ‘Monday’ in the example above? All ‘Day’ values are space padded to 9 characters. You could use string functions to remove the extra spaces if needed for formatting purposes or you could do a trimmed TO_CHAR for the ‘Day’ then concatenate with a comma and another TO_CHAR for the ‘DD-MON-YYYY’.

Ambiguity - Using DateStyle

People in different locations of the world are familiar with local representations of dates. Times are reasonably similar, but dates can differ. Within the USA, they use 3/5/19, whereas in Australia we would use 5/3/19 and in Europe they would use either 5.3.19 or 5/3/19. What is the date in question? 5th March, 2019.

YugabyteDB has DateStyle which is a setting that you apply to your session so that ambiguous dates can be determined and the display of dates in YSQL can be defaulted to a particular format.

By default, YugabyteDB uses the ISO Standard of YYYY-MM-DD HH24:MI:SS. Other settings you can use are ‘SQL’, ‘German’, and ‘Postgres’. These are all referenced below allowing you to see examples.

All settings except ISO allow you specify whether a Day appears before or after the Month. Therefore, a setting of ‘DMY’ will result in 35 being 3rd May, whereas ‘MDY’ will result in 5th March.

If you are reading dates as text fields from a file or any source that is not a YugabyteDB date or timestamp data type, then it is very important that you set your DateStyle properly unless you are very specific on how to convert a text field to a date - an example of which is included below.

Note that YugabyteDB will always interpret ‘66’ as 6th June, and ‘1312’ as 13th December (because the month cannot be 13), but what about about ‘612’? Lets work through some examples within YSQL.

  1. yugabyte=# SHOW DateStyle;
  2. DateStyle
  3. -----------
  4. ISO, DMY
  5. yugabyte=# select current_date, current_time(0), current_timestamp(0);
  6. current_date | current_time | current_timestamp
  7. --------------+--------------+------------------------
  8. 2019-07-09 | 20:26:28+00 | 2019-07-09 20:26:28+00
  9. yugabyte=# SET DateStyle = 'SQL, DMY';
  10. SET
  11. yugabyte=# select current_date, current_time(0), current_timestamp(0);
  12. current_date | current_time | current_timestamp
  13. --------------+--------------+-------------------------
  14. 09/07/2019 | 20:26:48+00 | 09/07/2019 20:26:48 UTC
  15. yugabyte=# SET DateStyle = 'SQL, MDY';
  16. SET
  17. yugabyte=# select current_date, current_time(0), current_timestamp(0);
  18. current_date | current_time | current_timestamp
  19. --------------+--------------+-------------------------
  20. 07/09/2019 | 20:27:04+00 | 07/09/2019 20:27:04 UTC
  21. yugabyte=# SET DateStyle = 'German, DMY';
  22. SET
  23. yugabyte=# select current_date, current_time(0), current_timestamp(0);
  24. current_date | current_time | current_timestamp
  25. --------------+--------------+-------------------------
  26. 09.07.2019 | 20:27:30+00 | 09.07.2019 20:27:30 UTC
  27. yugabyte=# SET DateStyle = 'Postgres, DMY';
  28. SET
  29. yugabyte=# select current_date, current_time(0), current_timestamp(0);
  30. current_date | current_time | current_timestamp
  31. --------------+--------------+------------------------------
  32. 09-07-2019 | 20:28:07+00 | Tue 09 Jul 20:28:07 2019 UTC
  33. yugabyte=# SET DateStyle = 'Postgres, MDY';
  34. SET
  35. yugabyte=# select current_date, current_time(0), current_timestamp(0);
  36. current_date | current_time | current_timestamp
  37. --------------+--------------+------------------------------
  38. 07-09-2019 | 20:28:38+00 | Tue Jul 09 20:28:38 2019 UTC
  39. yugabyte=# select '01-01-2019'::date;
  40. date
  41. ------------
  42. 01-01-2019
  43. yugabyte=# select to_char('01-01-2019'::date, 'DD-MON-YYYY');
  44. to_char
  45. -------------
  46. 01-JAN-2019
  47. yugabyte=# select to_char('05-03-2019'::date, 'DD-MON-YYYY');
  48. to_char
  49. -------------
  50. 03-MAY-2019
  51. yugabyte=# SET DateStyle = 'Postgres, DMY';
  52. SET
  53. yugabyte=# select to_char('05-03-2019'::date, 'DD-MON-YYYY');
  54. to_char
  55. -------------
  56. 05-MAR-2019
  57. yugabyte=# select to_char(to_date('05-03-2019', 'MM-DD-YYYY'), 'DD-MON-YYYY');
  58. to_char
  59. -------------
  60. 03-MAY-2019

Best practise is to pass all text representations of date and time data types through a TO_DATE or TO_TIMESTAMP function. There is not a ‘to_time’ function as its format is always fixed of ‘HH24:MI:SS.ms’, therefore be careful of AM/PM times and your milliseconds can also be thousandths of a second, so either 3 or 6 digits should be supplied.

The final example above illustrates the difficulty that can occur with dates. The system is expecting a ‘DMY’ value but your source is of format ‘MDY’, therefore YugabyteDB will not know how to convert it in ambiguous cases, therefore be explicit as shown.

Getting dirty - into the logs we go

NoteThis is for those more interested in getting into some of the more finer points of control.

YugabyteDB has inherited a lot of similar capability of the YSQL API to the PostgreSQL SQL API, and this will explain why when we start to look under the hood, it is looking very much like pg.

YugabyteDB tracks its settings in its catalog, lets query some relevant settings and this time we will transform the layout of the query results using the Expanded display setting. This can be done in any database.

  1. yugabyte=# \x on
  2. Expanded display is on.
  3. yugabyte=# select name, short_desc, coalesce(setting, reset_val) AS "setting_value", sourcefile
  4. from pg_catalog.pg_settings
  5. where name in('log_timezone', 'log_directory', 'log_filename', 'lc_time')
  6. order by name asc;
  7. -[ RECORD 1 ]-+----------------------------------------------------------------
  8. name | lc_time
  9. short_desc | Sets the locale for formatting date and time values.
  10. setting_value | en_US.UTF-8
  11. sourcefile | /home/xxxxx/yugabyte-data/node-1/disk-1/pg_data/postgresql.conf
  12. -[ RECORD 2 ]-+----------------------------------------------------------------
  13. name | log_directory
  14. short_desc | Sets the destination directory for log files.
  15. setting_value | /home/xxxxx/yugabyte-data/node-1/disk-1/yb-data/tserver/logs
  16. sourcefile |
  17. -[ RECORD 3 ]-+----------------------------------------------------------------
  18. name | log_filename
  19. short_desc | Sets the file name pattern for log files.
  20. setting_value | postgresql-%Y-%m-%d_%H%M%S.log
  21. sourcefile |
  22. -[ RECORD 4 ]-+----------------------------------------------------------------
  23. name | log_timezone
  24. short_desc | Sets the time zone to use in log messages.
  25. setting_value | UTC
  26. sourcefile | /home/xxxxx/yugabyte-data/node-1/disk-1/pg_data/postgresql.conf
  27. yugabyte=# \x off

Using the log_directory and log_filename references, we can find the YugabyteDB log to examine the timestamps being inserted into the logs. These are all UTC timestamps and should remain that way.

You will see that the lc_time setting is currently UTF and the file the setting is obtained from is listed. Opening that file as sudo/superuser, you will see contents that look like the below (after much scrolling or searching for ‘datestyle’):

  1. # - Locale and Formatting -
  2. datestyle = 'iso, mdy'
  3. #intervalstyle = 'postgres'
  4. timezone = 'UTC'
  5. #timezone_abbreviations = 'Default' # Select the set of available time zone
  6. # abbreviations. Currently, there are
  7. # Default
  8. # Australia (historical usage)
  9. # India
  10. # You can create your own file in
  11. # share/timezonesets/.
  12. #extra_float_digits = 0 # min -15, max 3
  13. #client_encoding = sql_ascii # actually, defaults to database
  14. # encoding
  15. # These settings are initialized by initdb, but they can be changed.
  16. lc_messages = 'en_US.UTF-8' # locale for system error message
  17. # strings
  18. lc_monetary = 'en_US.UTF-8' # locale for monetary formatting
  19. lc_numeric = 'en_US.UTF-8' # locale for number formatting
  20. lc_time = 'en_US.UTF-8' # locale for time formatting
  21. # default configuration for text search
  22. default_text_search_config = 'pg_catalog.english'

Make a backup of the original file and then change datestyle = 'SQL, DMY', timezone = 'GB' (or any other timezone name you prefer) and save the file. You will need to restart your YugabyteDB cluster for the changes to take affect using the shell command ./bin/yb-ctl restart (and ensure you append any startup flags if you do this).

Once the cluster is running as expected, then:

  1. $ ./bin/ysqlsh
  2. ysqlsh (11.2)
  3. Type "help" for help.
  4. yugabyte=# SHOW timezone;
  5. TimeZone
  6. ----------
  7. GB
  8. yugabyte=# select current_date;
  9. current_date
  10. --------------
  11. 09/07/2019

Now you don’t need to make those settings each time you enter YSQL. However, applications should not rely upon these settings, they should always SET their requirements before submitting their SQL. These settings should only be used by ‘casual querying’ such as we are doing now.

Conclusion

As illustrated, the area of dates and times is a comprehensive area that is well addressed by PostgreSQL and hence YSQL within YugabyteDB. All of the date-time data types are implemented, and the vast majority of methods, operators and special values are available. The functionality is complex enough for you to be able to code any shortfalls that you find within the YSQL implementation of its SQL API.