The M_time(3f) Fortran module and associated utility programs provide date and time-related procedures. Both a procedural and OOP (Object Oriented Programming) interface are provided. The primary conversion procedures are provided as both a simple function and a subroutine returning error flags and finer-grained control. Each routine is accompanied by a man-page which includes a sample program for that procedure. This is also provided in HTML format both in individual pages and a book format. The man-page example programs, unit tests and additional examples are all provided in the distribution package.
The M_time(3f) module
The M_TIME(3f) module complements the DATE_AND_TIME(3f) procedure, which is the standard intrinsic subroutine that returns the current date and time in the Gregorian calendar. That is, the primary way this module represents dates is as an integer array with the same meaning for elements as defined by the DATE_AND_TIME(3f) routine. In addition it can calculate or read many other date representations such as ...
Julian and Unix Epoch Dates are particularly useful for manipulating dates in simple numeric expressions.
The extensive formatting options include showing SYSTEM_CLOCK(3f) and CPU_USAGE(3f) information along with Gregorian date information, allowing for the easy incorporation of timing information into program messages. In addition to conventional Civilian Calendar dates, the module supports the ISO-8601 standard methods of displaying dates.
A Fortran-callable sleep(3c)/usleep(3c) procedure is also provided.
UNIX EPOCH | ||
---|---|---|
date_to_unix(dat,UNIXTIME,IERR) or d2u(dat) result(UNIXTIME) | %epoch() | Convert date array to Unix Time |
unix_to_date(unixtime,DAT,IERR) or u2d(unixtime) result(DAT) | Convert Unix Time to date array | |
JULIAN | ||
julian_to_date(julian,DAT,IERR) or j2d(julian) result(DAT) | Convert Julian Date to date array | |
date_to_julian(dat,JULIAN,IERR) or d2j(dat) result(JULIAN) | %julian() | Converts date array to Julian Date |
MODIFIED JULIAN | ||
modified_julian_to_date(modified_julian,DAT,IERR) or m2d(modified_julian) result(DAT) | Convert Modified Julian Date to date array | |
date_to_modified_julian(dat,MODIFIED_JULIAN,IERR) or d2m(dat) result(MODIFIED_JULIAN) | %modified_julian() | Converts date array to Modified Julian Date |
BASEDAY AND SECONDS | ||
bas_to_date(bas,DAT,IERR) or b2d(bas) result(DAT) | Convert Baseday And Seconds (BAS) to date array | |
date_to_bas(dat,BAS,IERR) or d2m(dat) result(BAS) | %nbsp; | Converts date array to Baseday And Seconds date |
WEEK OF YEAR | ||
d2w(dat,ISO_YEAR,ISO_WEEK,ISO_WEEKDAY,ISO_NAME) | calculate iso-8601 Week- numerically and as string yyyy-Www-d | |
w2d(iso_year,iso_week,iso_weekday,DAT) or w2d(iso_string,DAT,[IERR]) | calculate date DAT given iso-8601 Week date (numerically or as a string yyyy-Www-d) | `|
ORDINAL DAY | ||
d2o(dat) result(ORDINAL) | %ordinal() | given date array return ordinal day of year, Jan 1st=1 |
ordinal_to_date(ordinal,year,DAT) or o2d(ordinal,[year]) result(DAT) | given ordinal day of year return date array, Jan 1st=1 | |
ordinal_seconds() | return seconds since beginning of year | |
PRINTING DATES | ||
fmtdate(dat,format) result(TIMESTR) | %format([string]) | Convert date array to string using format |
fmtdate_usage(indent) | display macros recognized by fmtdate(3f) | |
now(format) result(TIMESTR) | return string representing current time given format | |
box_month(dat,CALEN) | print specified month into character array | |
MONTH NAME | ||
mo2v(month_name) result(MONTH_NUMBER) | given month name return month number | |
v2mo(month_number) result(MONTH_NAME) | given month number return month name | |
mo2d(month_name,year) result(DAT) | return date array for first day of given month name in specified year | |
DAY OF WEEK | ||
dow(dat,[WEEKDAY],[DAY],IERR) | %weekday() | Convert date array to day of the week as number(Mon=1) and name |
LOCALE | ||
locale(name,month_names,weekday_names,month_names_abbr,weekday_names_abbr,IERR) | basic ASCII locale definition | |
ASTROLOGICAL | ||
easter(year,dat) | calculate month and day Easter falls on for given year | |
moon_fullness(dat) result(FULLNESS) | percentage of moon phase from new to full | |
phase_of_moon(dat) result(PHASE) | return name for phase of moon for given date | |
DURATION | ||
sec2days(seconds) result(DHMS) | converts seconds to string D-HH:MM:SS | |
days2sec(string) result(SECONDS) | converts string D-HH:MM:SS or NNdNNhNNmNNs to seconds | |
READING DATES | ||
guessdate(anot,DAT) | Converts a date string to a date array, in various formats | |
C INTERFACE | ||
system_sleep(wait_seconds) | Call sleep(3c) or usleep(3c) |
FMTDATE
You can easily use Julian Dates and Unix Epoch Times to add and subtract times from dates or to calculate the interval between dates. But JEDs and UETs and even the Gregorian Calendar arrays in the DAT arrays are not the way we typically describe a date on the Civilian Calendar. So the fmtdate(3f) routine lets us print a DAT array in a variety of familiar styles.
The fmtdate(3f) and now(3f) procedures let you display a Gregorian date using either keywords for standard formats or using macros in a user-specified formatting string. A formatting string may contain the following macros:
Description Example Base time array: (1) %Y -- year, yyyy 2016 (2) %M -- month of year, 01 to 12 07 (3) %D -- day of month, 01 to 31 27 %d -- day of month, with suffix (1st, 2nd,...) 27th (4) %Z -- minutes from UTC -0240m %z -- -+hh:mm from UTC -04:00 %T -- -+hhmm from UTC -0400 (5) %h -- hours, 00 to 23 21 %H -- hour (1 to 12, or twelve-hour clock) 09 %N -- midnight< AM <=noon; noon<= PM <midnight PM (6) %m -- minutes, 00 to 59 24 (7) %s -- sec, 00 to 59 22 (8) %x -- milliseconds 000 to 999 512 Conversions: %E -- Unix Epoch time 1469669062.5129952 %e -- integer value of Unix Epoch time 1469669063 %F -- Modified Julian Date 60400.559 %f -- integer value of Modified Julian Date 60400 %G -- Baseday and Seconds (60400,4000.55944412) %g -- Baseday seconds 4000.55944412 %J -- Julian date 2457597.559 %j -- integer value of Julian Date(Julian Day) 2457597 %O -- Ordinal day (day of year) 209 %o -- whole days since Unix Epoch date 17009 %U -- day of week, 1..7 Sunday=1 4 %u -- day of week, 1..7 Monday=1 3 %i -- ISO week of year 1..53 30 %I -- iso-8601 week with weekday: (yyyy-Www-d) 2016-W30-3 Names: %l -- abbreviated month name Jul %L -- full month name July %w -- first three characters of weekday Wed %W -- weekday name Wednesday %p -- phase of moon New %P -- percent of way from new to full moon -1% %X -- day of month in English twenty-first Literals: %% -- a literal % % %t -- tab character %b -- blank character %B -- exclamation(bang) character %n -- new line (system dependent) %q -- single quote (apostrophe) %Q -- double quote Duration: %a -- Time since now (age) as d-hh:mm:ss 1-20:14:40 %A -- Time since now (age) as seconds 123456.890 Program timing: %c -- CPU_TIME(3f) output .78125000000000000E-001 %C -- number of times this routine is used 1 %S -- seconds since last use of this format .0000000000000000 %k -- time in seconds from SYSTEM_CLOCK(3f) 588272.750 %K -- time in clicks from SYSTEM_CLOCK(3f) 588272750 Help: %? -- call fmtdate_usage If no percent (%) is found in the format one of several alternate substitutions occurs. If the format is composed entirely of one of the following keywords the following substitution occurs: "iso-8601", "iso" ==> %Y-%M-%DT%h:%m:%s%z Ex: 2017-08-26T18:56:33,510912700-04:00 "iso-8601W", "isoweek" ==> %I Ex: 2024-W26-6 "sql" ==> "%Y-%M-%D %h:%m:%s.%x" Ex: "2024-06-29 08:42:14.311" "sqlday" ==> "%Y-%M-%D" Ex: "2024-06-29" "sqltime" ==> "%h:%m:%s.%x" Ex: "08:42:31.528" "dash" ==> %Y-%M-%D Ex: 2024-06-29 "rfc-2822" ==> %w, %D %l %Y %h:%m:%s %T Ex: Mon, 14 Aug 2006 02:34:56 -0600 "rfc-3339" ==> %Y-%M-%DT%h:%m:%s%z Ex: 2006-08-14 02:34:56-06:00 "date" ==> %w %l %D %h:%m:%s UTC%z %Y Ex: Sat Jun 29 08:43:14 UTC-04:00 2024 "short" ==> %w, %l %d, %Y %H:%m:%s %N UTC%z Ex: Sat, Jun 29th, 2024 8:43:18 AM UTC-04:00 "long"," " ==> %W, %L %d, %Y %H:%m:%s %N UTC%z Ex: Saturday, June 29th, 2024 8:43:23 AM UTC-04:00 "suffix" ==> %Y%D%M%h%m%s Ex: 20242906084327 "formal" ==> The %d of %L %Y Ex: The 29th of June 2024 "lord" ==> the %d day of %L in the year of our Lord %Y Ex: the 29th day of June in the year of our Lord 2024 "easter" ==> Easter day: the %d day of %L in the year of our Lord %Y Ex: Easter day: ... the 31st day of March in the year of our Lord 2024 "all" ==> A SAMPLE OF DATE FORMATS Civil Calendar: Saturday June 29th Civil Date: 2024-06-29 08:44:29 -04:00 Julian Date: 2460491.0308968634 Unix Epoch Time: 1719665069.4890008 Day Of Year: 181 ISO-8601 week: 2024-W26-6 otherwise the following words are replaced with the most common macros. For duplicate names the lowercase one is a numeric value string and all uppercase is an alphanumeric string: String Macro Example year %Y 2016 month %M 07 longmonth|MONTH %L July shortmonth|Month|Mth %l Jul day %D 27 shortday|DAY %d 27th longday %X twentyseventh hour %h 21 goodhour|HOUR %H 11 GOOD %N AM minute %m 24 second %s 22 weekday %u 3 longweekday|WEEKDAY %W Thursday shortweekday|Weekday|wkday %w Thu timezone %T -0400 (+-HHMM) TIMEZONE %z -04:00 (+-HH:MM) Timezone %Z -240m (+-MMMMm) epoch %e 1469669063 julian %j 2457597 MJD %f 57597 ordinal %O 209 age %A 100-20:30:40 AGE %a 20900.3030 usage|help|? %? call fmtdate_usage() If none of these keywords are found then every letter that is a macro is assumed to have an implied percent in front of it. For example: YMDhms ==> %Y%M%D%h%m%s ==> 20160727212422
If you prefer an Object-oriented interface the M_time__oop module (included with the M_time module source) provides an OOP interface to the M_time module.
The following example program demonstrates the extensive options available for formatting a date as well as how to use the module to calculate dates such as "Yesterday" and "Tomorrow".
Sample program:! yesterday, today, tomorrow program demo_M_time use M_time, only: j2d, d2j, u2d, d2u, fmtdate, realtime integer :: dat(8) real(kind=realtime) :: julian, unixtime character(len=*),parameter :: iso_fmt='%Y-%M-%DT%h:%m:%s.%x%z' character(len=:),allocatable :: friendly friendly='%W, %L %d, %Y %H:%m:%s %N' ! a nice friendly format call date_and_time(values=dat) ! current time is placed in array write(*,*)'Today' write(*,*)'ISO ',fmtdate(dat,iso_fmt) write(*,*)'Friendly ',fmtdate(dat,friendly) write(*,*)'ISO week ',fmtdate(dat,'%I') julian=d2j(dat) unixtime=d2u(dat) write(*,*)'Yesterday' ! subtract a day from scalar time and print write(*,*)' ',fmtdate(u2d(unixtime-86400),iso_fmt) write(*,*)' ',fmtdate(j2d(julian-1.0),friendly) write(*,*)' ',fmtdate(j2d(julian-1.0),'%I') write(*,*)'Tomorrow' ! add a day to scalar time and print write(*,*)' ',fmtdate(u2d(unixtime+86400),iso_fmt) write(*,*)' ',fmtdate(j2d(julian+1.0),friendly) write(*,*)' ',fmtdate(j2d(julian+1.0),'%I') write(*,*)'Next Week' ! add a week to scalar time and print write(*,*)' ',fmtdate(u2d(unixtime+7*86400),iso_fmt) write(*,*)' ',fmtdate(j2d(julian+7.0),friendly) write(*,*)' ',fmtdate(j2d(julian+7.0),'%I') end program demo_M_time
Today ISO 2015-12-22T08:07:34.025-0300 Friendly Tuesday, December 22nd, 2015 08:07:34 AM ISO week 2015-W52-2 Yesterday 2015-12-21T08:07:34.025-0300 Monday, December 21st, 2015 08:07:34 AM 2015-W52-1 Tomorrow 2015-12-23T08:07:34.025-0300 Wednesday, December 23rd, 2015 08:07:34 AM 2015-W52-3 Next Week 2015-12-29T08:07:34.025-0300 Tuesday, December 29th, 2015 08:07:34 AM 2015-W53-2
A "date_and_time" array "DAT" has the same format as the array of values generated by the Fortran intrinsic DATE_AND_TIME(3f). That is, it is an 8-element integer array containing year, month, day, Time zone difference from UTC in minutes, hour, minutes, seconds, and milliseconds of the second. This array represents a date on the Proleptic Gregorian Calendar.
The Proleptic Gregorian Calendar assumes the Gregorian Calendar existed back to the beginning of the Julian Day calendar (4713 BC). This means historic dates will often be confused, as the Julian Calendar was used in the USA until 1752-09-03, for example. The Gregorian Calendar was formally decreed on 1582-10-15 but was not adapted in many countries. The Julian Calendar was first used around 45 BC.
Note that the Proleptic Gregorian Calendar includes a year zero (0). It is frequently used in computer software to simplify the handling of older dates. For example, it is the calendar used by MySQL, SQLite, PHP, CIM, Delphi, Python and COBOL. The Proleptic Gregorian Calendar is explicitly required for all dates before 1582 by ISO 8601:2004 (clause 4.3.2.1 The Gregorian calendar) if the partners to information exchange agree.
Unix Epoch Time (UET) is defined as the number of seconds since 00:00:00 on January 1st. 1970, UTC.
A JD is defined as a Julian Date. JD days start at noon (not at midnight). 4713-01-01 BC at noon is defined as JD 0.0.
If you are not familiar with them, in this context Julian Dates and Unix Epoch Times are scalar numbers that allow for easy computations using dates (to go back one day just subtract one from a Julian Date, for example). Since these values are generally not considered intelligible, routines are included to convert between these scalar values and the date array so human-readable results can be obtained.
Modified Julian Date (MJD) measures days (and fractional days) since the start of 17 Nov 1858 CE in Universal Time (UTC). Julian Date (JD) measures days (and fractional days) since noon on 1 January, 4713 BCE in Universal Time (UTC). As an expression
Modified Julian Date (MJD) = Julian Date (JD) - 2400000.5
Baseday And Seconds is a date composed of two values. The first is an integer representing a Modified Julian Day. The second is a double-precision value representing an offset from the start of this base day in seconds .
type BAStime integer :: base_day ! whole days since the MJD Epoch date real(kind=real64) :: secs ! offset in seconds from start of BASE_DAY end type BAStime
This facilitates storing a date at a higher precision that the other formats used by the library (although sometimes that lower precision is limited primarily by the definition -- for example the milliseconds in a DAT could be smaller units).
This format uses seconds for the units of the fractional day component instead of fractions of a day as is used by JD and MJD, as seconds are often the unit of measure in many computations.
BAS days start at midnight (00:00:00) so truncating the fractional component always gives the same Civil Calendar day whatever the time of day (unlike JD).
The seconds offset may take any double-precision value, so that any date/time may be expressed in terms of an offset from the same BAS day. Given that, floating point precision is finite so it is intended that the offset value be relatively small or the spacing between representable numbers becomes so large that precise small intervals cannot be represented. The seconds field thus may exceed a single day but should generally have a small exponent to retain precision, and may also be negative.
Coordinated Universal Time (French: Temps universel coordonn'e), abbreviated as UTC, is the primary time standard by which the world regulates clocks and time. It is within about 1 second of mean solar time at 0o longitude;[1] it does not observe daylight saving time. It is one of several closely related successors to Greenwich Mean Time (GMT). For most purposes, UTC is considered interchangeable with GMT, but GMT is no longer precisely defined by the scientific community.
Like most collections of date and time procedures M_time is not a high-precision library that accounts internally for leap seconds and relativistic effects.
M_time(3f) is intended for use in the recent era and is not appropriate for use with historical dates that used some other calendar scheme such as the Julian Calendar. That is, you have to remember to account for conversions to other calendar systems when using historical dates.
When Daylight Savings is in effect calculations will generally be correct, as the date model includes a timezone value; but you are responsible for ensuring dates you create use the correct timezone value or otherwise account for Daylight Savings Time as needed.
Currently, dates are manipulated using the current system timezone, which can typically be set using the environment variable TZ. So if you desire to set the default timezone you generally set the environment variable before executing your program. This is compatible with current observed behavior for the intrinsic procedure DATE_AND_TIME(3f) with compilers I have tested with, but does not seem to be a specified behavior as far as the standard is concerned. That is, DATE_AND_TIME(3f) returns a vector that contains a current time zone, but does not specify how a current time zone can be explicitly set. Since this library is intentionally designed to complement DATE_AND_TIME(3f) it adopts the same behavior. A routine to let you set a default time zone could be added in the future.
Note the environment variable can be set using put_environment_variable(3f) from the libGPF library:
use M_system, only : put_environment_variable call put_environment_variable('TZ','America/New_York',ierr)
The ISO-8601 standard is often used for business-related transactions.
There are C/C++ intrinsics which provide much of the same functionality that should be bindable to Fortran via the ISO_C_BINDING module.
The Fortran Wiki fortranwiki.org contains information on other libraries and modules that provide date-time procedures.
If you care about Leap Seconds, Orbital Mechanics, GPS/Satellite communications, and Astronomy the high-precision NASA SPICElib Fortran library is worth a look. It is well tested for manipulating high-precision dates.