8 releases

0.1.6 Oct 17, 2023
0.1.5 Oct 16, 2023

#119 in Date and time

AGPL-3.0

230KB
3K SLoC

Rust 2.5K SLoC // 0.0% comments Python 416 SLoC // 0.5% comments

cftime-rs

cftime-rs is an implementation in rust of the cf time convention. Python bindins are available for this project and use the great maturin library. The python bindings are highly inspired by the cftime python package developped by Unidata.

CI Status Crates.io version badge Pypi version badge CI/CD Status License: AGPL v3 Documentation Status Issue Badge Pull requests Badge

Documentation : Rust | Python
Packages : Rust | Python
Repository : Github

Rust

Installation

cargo install cftime-rs

Examples

Decoding

Decoding needs units, and calendar and can work with i32, i64, f32, f64 and their corresponding vector type Vec<i32>, Vec<i64>, Vec<f32> and Vec<f64>. From these type it return either a CFDatetime object or a Vec<CFDatetime>.

use cftime_rs::calendars::Calendar;
use cftime_rs::decoder::*;
use std::str::FromStr;
fn main() {
    let to_decode = vec![0, 1, 2, 3, 4, 5];
    let units = "days since 2000-01-01 00:00:00";
    let calendar = Calendar::from_str("standard").unwrap();
    let datetimes = to_decode.decode_cf(units, calendar).unwrap();
    for datetime in datetimes {
        println!("{}", datetime);
    }
}

will print :

2000-01-01 00:00:00.000
2000-01-02 00:00:00.000
2000-01-03 00:00:00.000
2000-01-04 00:00:00.000
2000-01-05 00:00:00.000
2000-01-06 00:00:00.000

Encoding

Encoding needs units and calendar and can convert a CFDatetime object into an i32, i64, f32 or f64 or a Vec<CFDatetime> into Vec<i32>, Vec<i64>, Vec<f32> or Vec<f64>.

use cftime_rs::calendars::Calendar;
use cftime_rs::datetime::CFDatetime;
use cftime_rs::encoder::*;
use cftime_rs::errors::Error;
use std::str::FromStr;
fn main() {
    let calendar = Calendar::from_str("standard").unwrap();
    // Create vector of datetimes and convert Vec<Result<CFDatetime, Error>>
    // into Result<Vec<CFDatetime>, Error>
    let to_encode: Result<Vec<CFDatetime>, Error> = vec![
        CFDatetime::from_ymd(2000, 1, 1, calendar),
        CFDatetime::from_ymd(2000, 1, 2, calendar),
        CFDatetime::from_ymd(2000, 1, 3, calendar),
        CFDatetime::from_ymd(2000, 1, 4, calendar),
        CFDatetime::from_ymd(2000, 1, 5, calendar),
        CFDatetime::from_ymd(2000, 1, 6, calendar),
    ]
    .into_iter()
    .collect();
    // define the units
    let units = "days since 2000-01-01 00:00:00";
    // The type annotation for result allow us to cast to type we want
    // here we use Vec<i64>
    let results: Vec<i64> = to_encode.unwrap().encode_cf(units, calendar).unwrap();
    for result in results {
        println!("{}", result);
    }
}

will print :

0
1
2
3
4
5

Python

Installation

pip install cftime-rs

Examples

Decoding to PyCfDatetimes

import cftime_rs

to_decode = [0, 1, 2, 3, 4, 5]
units = "days since 2000-01-01 00:00:00"
calendar = "standard"
datetimes = cftime_rs.num2date(arr, units, calendar)
for datetime in datetimes:
    print(datetime)

will print :

2000-01-01 00:00:00.000
2000-01-02 00:00:00.000
2000-01-03 00:00:00.000
2000-01-04 00:00:00.000
2000-01-05 00:00:00.000
2000-01-06 00:00:00.000

Encoding PyCFDatetimes

calendar = cftime_rs.PyCFCalendar.from_str("standard")
to_encode = [
    cftime_rs.PyCFDatetime.from_ymd(2000, 1, 1, calendar),
    cftime_rs.PyCFDatetime.from_ymd(2000, 1, 2, calendar),
    cftime_rs.PyCFDatetime.from_ymd(2000, 1, 3, calendar),
    cftime_rs.PyCFDatetime.from_ymd(2000, 1, 4, calendar),
    cftime_rs.PyCFDatetime.from_ymd(2000, 1, 5, calendar),
    cftime_rs.PyCFDatetime.from_ymd(2000, 1, 6, calendar),
]
units = "days since 2000-01-01 00:00:00"
calendar = "standard"
numbers = cftime_rs.date2num(to_encode, units, calendar, dtype="int")
for number in numbers:
    print(number)

will print :

0
1
2
3
4
5

Decoding to Python datetimes

to_decode = [0, 1, 2, 3, 4, 5]
units = "days since 2000-01-01 00:00:00"
calendar = "standard"
datetimes = cftime_rs.num2pydate(to_decode, units, calendar)
for datetime in datetimes:
    print(datetime)

will print

2000-01-01 00:00:00
2000-01-02 00:00:00
2000-01-03 00:00:00
2000-01-04 00:00:00
2000-01-05 00:00:00
2000-01-06 00:00:00

Decoding Python datetimes

to_encode = [
    dt.datetime(2000, 1, 1),
    dt.datetime(2000, 1, 2),
    dt.datetime(2000, 1, 3),
    dt.datetime(2000, 1, 4),
    dt.datetime(2000, 1, 5),
    dt.datetime(2000, 1, 6),
]
units = "days since 2000-01-01 00:00:00"
calendar = "standard"
numbers = cftime_rs.pydate2num(to_encode, units, calendar, dtype="int")
for number in numbers:
    print(number)

will print

0
1
2
3
4
5

Known issues

While this date calculation library can handle a wide range of dates, from approximately -291,672,107,014 BC to 291,672,107,014 AD, there are some performance considerations you should be aware of. As you move further away from the reference date of 1970-01-01 00:00:00, the time of calculation increases. This is because the library needs to account for leap years in various calendars.

Here is an example of the computation of 1_000_000_000_000_000 seconds using the units "seconds since 2000-01-01 00:00:00" on my personal computer in release mode :

Calendar Computation Time
Standard Calendar 44.470405ms
Leap Day Calendar 8.052179ms
360-Day Calendar 12.834µs

Comparison with cftime

Here is a benchmark on my computer of three methods. This is not really rigorous but this is to give an idea.

We are comparing cftime with cftime_rs. The first method involves decoding a series of numbers using the standard calendar, calling the .str() method, and then re-encoding them to the same unit and calendar. The second method is to decode a series of numbers using the standard calendar and re-encode them to the same unit and calendar without calling .str(). The third method is to decode a series of numbers using the standard calendar into python datetimes and re-encode them to the same unit and calendar without calling .str().

First and second methods are designed to be fair between the two libraries because cftime_rs only uses timestamps (i64) as its internal representation and never recalculates the year, month, day, hour, minutes, and seconds, except if you explicitly request this representation.

First method Second method
First method

Dependencies

~4–9.5MB
~94K SLoC